Minix1.5/commands/tar.c

Compare this file to the similar file:
Show the results in this format:

/* tar - tape archiver			Author: Michiel Huisjes */

/* Usage: tar [cxt][vo][F][f] tapefile [files]
 *
 * attempt to make tar to conform to POSIX 1003.1
 * disclaimer: based on an old (1986) POSIX draft.
 * Klamer Schutte, 20/9/89
 *
 * Changes:
 *  Changed to handle the original minix-tar format.	KS 22/9/89
 *  Changed to handle BSD4.3 tar format.		KS 22/9/89
 *  Conform to current umask if not super-user.		KS 22/9/89
 *  Update usage message to show f option		KS 22/9/89
 *
 *
 * 1)	tar will back itself up, should check archive inode num(&dev) and
	then check the target inode number. In verbose mode, issue
	warning, in all cases ignore target.
	marks@mgse		Mon Sep 25 10:38:58 CDT 1989
  	added global varaibles, made changes to main() and add_file();
	maks@mgse Mon Sep 25 12:09:20 CDT 1989

   2)	tar will not notice that a file has changed size while it was being
	backed up. should issue warning.
	marks@mgse		Mon Sep 25 10:38:58 CDT 1989

   3)	the 'f' option was not documented in usage[].
	marks@mgse		Mon Sep 25 12:03:20 CDT 1989
  	changed both usage[] defines. Why are there two (one is commented out)?
  	( deleted by me (was done twice) -- KS, 2/10/89 )
 *
 *  changed stat on tar_fd to an fstat				KS 2/10/89
 *  deleted mkfifo() code -- belongs in libc.a			KS 2/10/89
 *  made ar_dev default to -1 : an illegal device		KS 2/10/89
 *  made impossible to chown if normal user			KS 2/10/89
 *  if names in owner fields not known use numirical values	KS 2/10/89
 *  creat with mask 666 -- use umask if to liberal		KS 2/10/89
 *  allow to make directories as ../directory			KS 2/10/89
 *  allow tmagic field to end with a space (instead of \0)	KS 2/10/89
 *  correct usage of tmagic field 				KS 3/10/89
 *  made mkdir() to return a value if directory == "."  	KS 3/10/89
 *  made lint complains less (On a BSD 4.3 system)		KS 3/10/89
 *  use of directory(3) routines				KS 3/10/89
 *  deleted use of d_namlen selector of struct dirent		KS 18/10/89
 *
 * Bugs:
 *  verbose mode is not reporting consistent
 *  code needs cleanup
 *  prefix field is not used
 *  timestamp of a directory will not be correct if there are files to be
 *  unpacked in the directory
 *	(add you favorite bug here (or two (or three (or ...))))
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <tar.h>
#include <stdio.h>		/* need NULL */

#define	POSIX_COMP		/* POSIX compatible */
#define DIRECT_3		/* use directory(3) routines */

#ifdef DIRECT_3
#ifndef BSD
/* To all minix users: i am sorry, developed this piece of code on a
 * BSD system. KS 18/10/89 */
#include <dirent.h>
#define	direct	dirent		/* stupid BSD non-POSIX compatible name! */
#else				/* BSD */
#include <sys/dir.h>
#include <dir.h>
#endif				/* BSD */
#endif				/* DIRECT_3 */

#ifdef S_IFIFO
#define	HAVE_FIFO		/* have incorporated Simon Pooles' changes */
#endif

typedef char BOOL;
#define TRUE	1
#define FALSE	0

#define HEADER_SIZE	TBLOCK
#define NAME_SIZE	NAMSIZ
/* #define BLOCK_BOUNDARY	 20 -- not in POSIX ! */

typedef union hblock HEADER;

/* Make the MINIX member names overlap to the POSIX names */
#define	m_name		name
#define m_mode		mode
#define m_uid		uid
#define m_gid		gid
#define m_size		size
#define	m_time		mtime
#define	m_checksum	chksum
#define	m_linked	typeflag
#define	m_link		linkname
#define	hdr_block	dummy
#define	m		header
#define	member		dbuf

#if 0				/* original structure -- see tar.h for new
			 * structure */
typedef union {
  char hdr_block[HEADER_SIZE];
  struct m {
	char m_name[NAME_SIZE];
	char m_mode[8];
	char m_uid[8];
	char m_gid[8];
	char m_size[12];
	char m_time[12];
	char m_checksum[8];
	char m_linked;
	char m_link[NAME_SIZE];
  } member;
} HEADER;

#endif

/* Structure used to note links */
struct link {
  ino_t ino;
  dev_t dev;
  char name[NAMSIZ];
  struct link *next;
} *link_top = NULL;

HEADER header;

#define INT_TYPE	(sizeof(header.member.m_uid))
#define LONG_TYPE	(sizeof(header.member.m_size))

#define MKDIR		"/bin/mkdir"

#define NIL_HEADER	((HEADER *) 0)
#define NIL_PTR		((char *) 0)
#define BLOCK_SIZE	TBLOCK

#define flush()		print(NIL_PTR)

BOOL show_fl, creat_fl, ext_fl;

int tar_fd;
/* Char usage[] = "Usage: tar [cxt] tarfile [files]."; */
char usage[] = "Usage: tar [cxt][vo][F][f] tarfile [files].";
char io_buffer[BLOCK_SIZE];
char path[NAME_SIZE];
char pathname[NAME_SIZE];
int force_flag = 0;
#ifdef ORIGINAL_DEFAULTS
int chown_flag = 1;
int verbose_flag = 1;
#else
int chown_flag = 0;
int verbose_flag = 0;
#endif

/* Make sure we don't tar ourselves. marks@mgse Mon Sep 25 12:06:28 CDT 1989 */
ino_t ar_inode;			/* archive inode number	 */
dev_t ar_dev;			/* archive device number */

int total_blocks;
int u_mask;			/* one's complement of current umask */

long convert();

#define block_size()	(int) ((convert(header.member.m_size, LONG_TYPE) \
	+ (long) BLOCK_SIZE - 1) / (long) BLOCK_SIZE)

error(s1, s2)
char *s1, *s2;
{
  string_print(NIL_PTR, "%s %s\n", s1, s2 ? s2 : "");
  flush();
  exit(1);
}

BOOL get_header();

main(argc, argv)
int argc;
register char *argv[];
{
  register char *mem_name;
  register char *ptr;
  struct stat st;
  int i;

  if (argc < 3) error(usage, NIL_PTR);

  for (ptr = argv[1]; *ptr; ptr++) {
	switch (*ptr) {
	    case 'c':	creat_fl = TRUE;	break;
	    case 'x':	ext_fl = TRUE;	break;
	    case 't':	show_fl = TRUE;	break;
	    case 'v':		/* verbose output  -Dal */
		verbose_flag = !verbose_flag;
		break;
	    case 'o':		/* chown/chgrp files  -Dal */
		chown_flag = TRUE;
		break;
	    case 'F':		/* IGNORE ERRORS  -Dal */
		force_flag = TRUE;
		break;
	    case 'f':		/* standard U*IX usage -KS */
		break;
	    default:	error(usage, NIL_PTR);
	}
  }

  if (creat_fl + ext_fl + show_fl != 1) error(usage, NIL_PTR);

  if (strcmp(argv[2], "-") == 0)/* only - means stdin/stdout - KS */
	tar_fd = creat_fl ? 1 : 0;	/* '-' means used
					 * stdin/stdout  -Dal */
  else
	tar_fd = creat_fl ? creat(argv[2], 0666) : open(argv[2], O_RDONLY);

  if (tar_fd < 0) error("Cannot open ", argv[2]);

  if (geteuid()) {		/* check if super-user */
	int save_umask;
	save_umask = umask(0);
	u_mask = ~save_umask;
	umask(save_umask);
	chown_flag = TRUE;	/* normal user can't chown */
  } else
	u_mask = 0777;		/* don't restrict if 'privileged utiliy' */

  ar_dev = -1;			/* impossible device nr */
  if (creat_fl) {
	if (tar_fd > 1 && fstat(tar_fd, &st) < 0)
		error("Can't stat ", argv[2]);	/* will never be here,
						 * right? */
	else {			/* get archive inode & device	 */
		ar_inode = st.st_ino;	/* save files inode	 */
		ar_dev = st.st_dev;	/* save files device	 */
	}			/* marks@mgse Mon Sep 25 11:30:45 CDT 1989 */

	for (i = 3; i < argc; i++) {
		add_file(argv[i]);
		path[0] = '\0';
	}
	adjust_boundary();
  } else if (ext_fl) {
	/* extraction code moved here from tarfile() MSP */
	while (get_header()) {
		mem_name = header.member.m_name;
		if (is_dir(mem_name)) {
			for (ptr = mem_name; *ptr; ptr++);
			*(ptr - 1) = '\0';
			header.dbuf.typeflag = '5';
		}
		for (i = 3; i < argc; i++)
			if (!strncmp(argv[i], mem_name, strlen(argv[i])))
				break;
		if (argc == 3 || (i < argc)) {
			extract(mem_name);
		} else
			if (header.dbuf.typeflag == '0' ||
			    header.dbuf.typeflag == 0 ||
			    header.dbuf.typeflag == ' ')
				skip_entry();
		flush();
  	}
  } else
	tarfile();	/* tarfile() justs prints info. now MSP */

  flush();
  exit(0);
}

BOOL get_header()
{
  register int check;

  mread(tar_fd, (char *) &header, sizeof(header));
  if (header.member.m_name[0] == '\0') return FALSE;

  if (force_flag)		/* skip checksum verification  -Dal */
	return TRUE;

  check = (int) convert(header.member.m_checksum, INT_TYPE);

  if (check != checksum()) error("Tar: header checksum error.", NIL_PTR);

  return TRUE;
}

/* tarfile() just lists info about archive now; as of the t flag. */
/* Extraction has been moved into main() as that needs access to argv[] */

tarfile()
{
  register char *ptr;
  register char *mem_name;

  while (get_header()) {
	mem_name = header.member.m_name;
	string_print(NIL_PTR, "%s%s", mem_name,
		     (verbose_flag ? " " : "\n"));
	switch (header.dbuf.typeflag) {
	    case '1':
		verb_print("linked to", header.dbuf.linkname);
		break;
	    case '6':
		verb_print("", "fifo");
		break;
	    case '3':
	    case '4':
		if (verbose_flag) string_print(NIL_PTR,
				     "%s special file major %s minor %s\n",
			      (header.dbuf.typeflag == '3' ?
			       "character" : "block"),
				     header.dbuf.devmajor, header.dbuf.devminor);
		break;
	    case '0':	/* official POSIX */
	    case 0:	/* also mentioned in POSIX */
	    case ' ':	/* ofetn used */
		if (!is_dir(mem_name)) {
			if (verbose_flag)
				string_print(NIL_PTR, "%d tape blocks\n",
					     block_size());
			skip_entry();
			break;
		} else	/* FALL TROUGH */
	    case '5':
			verb_print("", "directory");
		break;
	    default:
		string_print(NIL_PTR, "not recogised item %d\n",
			     header.dbuf.typeflag);
	}
	flush();
  }
}

skip_entry()
{
  register int blocks = block_size();

  while (blocks--) (void) read(tar_fd, io_buffer, BLOCK_SIZE);
}

extract(file)
register char *file;
{
  register int fd;

  switch (header.dbuf.typeflag) {
      case '1':			/* Link */
	if (link(header.member.m_link, file) < 0)
		string_print(NIL_PTR, "Cannot link %s to %s\n",
			     header.member.m_link, file);
	else if (verbose_flag)
		string_print(NIL_PTR, "Linked %s to %s\n",
			     header.member.m_link, file);
	return;
      case '5':			/* directory */
	if (make_dir(file) == 0) {
		do_chown(file);
		verb_print("created directory", file);
	}			/* no else: mkdir will print a message if it
			 * fails */
	return;
      case '3':			/* character special */
      case '4':			/* block special */
	{
		int dmajor, dminor, mode;

		dmajor = (int) convert(header.dbuf.devmajor, INT_TYPE);
		dminor = (int) convert(header.dbuf.devminor, INT_TYPE);
		mode = (header.dbuf.typeflag == '3' ? S_IFCHR : S_IFBLK);
		if (mknod(file, mode, (dmajor << 8 | dminor), 0) == 0) {
			if (verbose_flag) string_print(NIL_PTR,
					     "made %s special file major %s minor %s\n",
				      (header.dbuf.typeflag == '3' ?
				       "character" : "block"),
					     header.dbuf.devmajor, header.dbuf.devminor);
			do_chown(file);
		}
		return;
	}
      case '2':			/* symbolic link */
      case '7':			/* contiguous file -- what is this (KS) */
	print("Not implemented file type\n");
	return;			/* not implemented, but break out */
#ifdef HAVE_FIFO
      case '6':			/* fifo */
	if (mkfifo(file, 0) == 0) {	/* is chmod'ed in do_chown */
		do_chown(file);
		verb_print("made fifo", file);
	} else
		string_print(NIL_PTR, "Can't make fifo %s\n", file);
	return;
#endif
  }

  /* Security change: creat with mode 0600, chown and then chmod -- KS */
  if ((fd = creat(file, 0600)) < 0) {
	string_print(NIL_PTR, "Cannot create %s\n", file);
	return;
  }
  copy(file, tar_fd, fd, convert(header.member.m_size, LONG_TYPE));
  (void) close(fd);

  do_chown(file);
}

do_chown(file)
char *file;
{
  int uid = -1, gid = -1;	/* these are illegal ??? -- KS */

  if (!chown_flag) {		/* set correct owner and group  -Dal */
	if (header.dbuf.magic[TMAGLEN] == ' ')
		header.dbuf.magic[TMAGLEN] == '\0';	/* some tars out there
							 * ... */
	if (strncmp(TMAGIC, header.dbuf.magic, TMAGLEN)) {
		struct passwd *pwd, *getpwnam();
		struct group *grp, *getgrnam();

		pwd = getpwnam(header.dbuf.uname);
		if (pwd != NULL) uid = pwd->pw_uid;
		grp = getgrnam(header.dbuf.gname);
		if (grp != NULL) gid = grp->gr_gid;
	}
	if (uid == -1) uid = (int) convert(header.member.m_uid, INT_TYPE);
	if (gid == -1) gid = (int) convert(header.member.m_gid, INT_TYPE);
	chown(file, uid, gid);
  }
  chmod(file, u_mask & (int) convert(header.member.m_mode, INT_TYPE));

  /* Should there be a timestamp if the chown failes? -- KS */
  timestamp(file);

}

timestamp(file)
char *file;
{
  time_t times[2];

  times[0] = times[1] = (long) convert(header.dbuf.mtime, LONG_TYPE);
  utime(file, times);
}

copy(file, from, to, bytes)
char *file;
int from, to;
register long bytes;
{
  register int rest;
  int blocks = (int) ((bytes + (long) BLOCK_SIZE - 1) / (long) BLOCK_SIZE);

  if (verbose_flag)
	string_print(NIL_PTR, "%s, %d tape blocks\n", file, blocks);

  while (blocks--) {
	(void) read(from, io_buffer, BLOCK_SIZE);
	rest = (bytes > (long) BLOCK_SIZE) ? BLOCK_SIZE : (int) bytes;
	mwrite(to, io_buffer, (to == tar_fd) ? BLOCK_SIZE : rest);
	bytes -= (long) rest;
  }
}

long convert(str, type)
char str[];
int type;
{
  register long ac = 0L;
  register int i;

  for (i = 0; i < type; i++) {
	if (str[i] >= '0' && str[i] <= '7') {
		ac <<= 3;
		ac += (long) (str[i] - '0');
	}
  }

  return ac;
}

make_dir(dir_name)
char *dir_name;
{
  register int pid, w;
  int ret;

  /* Why not allow to mkdir(../directory)? -- changed now	KS 2/10/89 */
  if ((dir_name[0] == '.') && (dir_name[1] == '\0')) return 0;

  if ((pid = fork()) < 0) error("Cannot fork().", NIL_PTR);

  if (pid == 0) {
	execl(MKDIR, "mkdir", dir_name, (char *) 0);
	error("Cannot execute mkdir.", NIL_PTR);
  }
  do {
	w = wait(&ret);
  } while (w != -1 && w != pid);

  return ret;
}

checksum()
{
  register char *ptr = header.member.m_checksum;
  register int ac = 0;

  while (ptr < &header.member.m_checksum[INT_TYPE]) *ptr++ = ' ';

  ptr = header.hdr_block;
  while (ptr < &header.hdr_block[BLOCK_SIZE]) ac += *ptr++;

  return ac;
}

is_dir(file)
register char *file;
{
  while (*file++ != '\0');

  return(*(file - 2) == '/');
}


char *path_name(file)
register char *file;
{

  string_print(pathname, "%s%s", path, file);
  return pathname;
}

add_path(name)
register char *name;
{
  register char *path_ptr = path;

  while (*path_ptr) path_ptr++;

  if (name == NIL_PTR) {
	while (*path_ptr-- != '/');
	while (*path_ptr != '/' && path_ptr != path) path_ptr--;
	if (*path_ptr == '/') path_ptr++;
	*path_ptr = '\0';
  } else {
	while (*name) {
		if (path_ptr == &path[NAME_SIZE])
			error("Pathname too long", NIL_PTR);
		*path_ptr++ = *name++;
	}
	*path_ptr++ = '/';
	*path_ptr = '\0';
  }
}

/*
 *	add a file to the archive
*/
add_file(file)
register char *file;
{
  struct stat st;
#ifdef DIRECT_3
  struct direct dir;
#endif
  register int fd = -1;
  char namebuf[16];		/* -Dal */
  char cwd[129];		/* -KS */
  char *getcwd();		/* marks@mgse Mon Sep 25 10:06:08 CDT 1989 */

  if (stat(file, &st) < 0) {
	string_print(NIL_PTR, "Cannot find %s\n", file);
	return;
  }
  if (st.st_dev == ar_dev && st.st_ino == ar_inode) {
	string_print(NIL_PTR, "Cannot tar current archive file (%s)\n", file);
	return;
  }				/* marks@mgse Mon Sep 25 12:06:28 CDT 1989 */
  if ((fd = add_open(file, &st)) < 0) {
	string_print(NIL_PTR, "Cannot open %s\n", file);
	return;
  }
  make_header(path_name(file), &st);
  switch (st.st_mode & S_IFMT) {
      case S_IFREG:
	header.dbuf.typeflag = '0';
	string_print(header.member.m_checksum, "%I ", checksum());
	mwrite(tar_fd, (char *) &header, sizeof(header));
	copy(path_name(file), fd, tar_fd, (long) st.st_size);
	break;
      case S_IFDIR:
	header.dbuf.typeflag = '5';
	string_print(header.member.m_checksum, "%I ", checksum());
	mwrite(tar_fd, (char *) &header, sizeof(header));
	if (NULL == getcwd(cwd, 129))
		string_print(NIL_PTR, "Error: cannot getcwd()\n");
	else if (chdir(file) < 0)
		string_print(NIL_PTR, "Cannot chdir to %s\n", file);
	else {
		is_added(&st, file);
		verb_print("read directory", file);
		add_path(file);
#ifdef	DIRECT_3
		{
			DIR *dirp;
			struct direct *dp;

			dirp = opendir(".");
			while (NULL != (dp = readdir(dirp)))
				if ((strcmp(dp->d_name, ".") == 0) ||
				    (strcmp(dp->d_name, "..") == 0))
					continue;
				else {
					strcpy(namebuf, dp->d_name);
					add_file(namebuf);
				}
			closedir(dirp);
		}
#else
		mread(fd, &dir, sizeof(dir));	/* "." */
		mread(fd, &dir, sizeof(dir));	/* ".." */
		while (read(fd, &dir, sizeof(dir)) == sizeof(dir))
			if (dir.d_ino) {
				strncpy(namebuf, dir.d_name, 14);
				namebuf[14] = '\0';
				add_file(namebuf);
			}
#endif
		chdir(cwd);
		add_path(NIL_PTR);
		*file = 0;
	}
	break;
#ifdef HAVE_FIFO
      case S_IFIFO:
	header.dbuf.typeflag = '6';
	verb_print("read fifo", file);
	string_print(header.member.m_checksum, "%I ", checksum());
	mwrite(tar_fd, (char *) &header, sizeof(header));
	break;
#endif
      case S_IFBLK:
	header.dbuf.typeflag = '4';
	if (verbose_flag) string_print(NIL_PTR,
			 "read block device %s major %s minor %s\n",
		  file, header.dbuf.devmajor, header.dbuf.devminor);
	string_print(header.member.m_checksum, "%I ", checksum());
	mwrite(tar_fd, (char *) &header, sizeof(header));
	break;
      case S_IFCHR:
	header.dbuf.typeflag = '3';
	if (verbose_flag) string_print(NIL_PTR,
		     "read character device %s major %s minor %s\n",
		  file, header.dbuf.devmajor, header.dbuf.devminor);
	string_print(header.member.m_checksum, "%I ", checksum());
	mwrite(tar_fd, (char *) &header, sizeof(header));
	break;
      case -1 & S_IFMT:
	header.dbuf.typeflag = '1';
	if (verbose_flag) string_print(NIL_PTR, "linked %s to %s\n",
			     header.dbuf.linkname, file);
	string_print(header.member.m_checksum, "%I ", checksum());
	mwrite(tar_fd, (char *) &header, sizeof(header));
	break;
      default:
	string_print(NIL_PTR, "Tar: %s unknown file type. Not added.\n", file);
	*file = 0;
  }

  flush();
  is_added(&st, file);
  add_close(fd);
}

verb_print(s1, s2)
char *s1, *s2;
{
  if (verbose_flag) string_print(NIL_PTR, "%s: %s\n", s1, s2);
}

add_close(fd)
int fd;
{
  if (fd != 0) close(fd);
}

/*
 *	open file 'file' to be added to archive, return file descriptor
*/
add_open(file, st)
char *file;
struct stat *st;
{
  int fd;
  if (((st->st_mode & S_IFMT) != S_IFREG) &&
      ((st->st_mode & S_IFMT) != S_IFDIR))
	return 0;
  fd = open(file, O_RDONLY);
  return fd;
}

make_header(file, st)
char *file;
register struct stat *st;
{
  register char *ptr = header.member.m_name;
  char *is_linked();
  struct passwd *pwd, *getpwuid();
  struct group *grp, *getgrgid();

  clear_header();

  while (*ptr++ = *file++);

  if ((st->st_mode & S_IFMT) == S_IFDIR) {	/* fixed test  -Dal */
	*(ptr - 1) = '/';
  }
  string_print(header.member.m_mode, "%I ", st->st_mode & 07777);
  string_print(header.member.m_uid, "%I ", st->st_uid);
  string_print(header.member.m_gid, "%I ", st->st_gid);
  if ((st->st_mode & S_IFMT) == S_IFREG)
	string_print(header.member.m_size, "%L ", st->st_size);
  else
	strncpy(header.dbuf.size, "0", TSIZLEN);
  string_print(header.member.m_time, "%L ", st->st_mtime);
  /* Header.member.m_linked = ''; */
  if ((ptr = is_linked(st)) != NULL) {
	strncpy(header.dbuf.linkname, ptr, NAMSIZ);
	st->st_mode = -1;	/* invalid value */
  }
  strncpy(header.dbuf.magic, TMAGIC, TMAGLEN);
  header.dbuf.version[0] = 0;
  header.dbuf.version[1] = 0;
  pwd = getpwuid(st->st_uid);
  strncpy(header.dbuf.uname, (pwd != NULL ? pwd->pw_name : "nobody"), TUNMLEN);
  grp = getgrgid(st->st_gid);
  strncpy(header.dbuf.gname, (grp != NULL ? grp->gr_name : "nobody"), TGNMLEN);
  if (st->st_mode & (S_IFBLK | S_IFCHR)) {
	string_print(header.dbuf.devmajor, "%I ", (st->st_rdev >> 8));
	string_print(header.dbuf.devminor, "%I ", (st->st_rdev & 0xFF));
  }
  header.dbuf.prefix[0] = 0;
}

is_added(st, file)
struct stat *st;
char *file;
{
  struct link *new;
  char *malloc();

  if ((*file == 0) || (st->st_nlink == 1)) return;
  new = (struct link *) malloc(sizeof(struct link));
  if (new == NULL) {
	print("Out of memory\n");
	return;
  }
  new->next = link_top;
  new->dev = st->st_dev;
  new->ino = st->st_ino;
  strncpy(new->name, path_name(file), NAMSIZ);
  link_top = new;
}

char *is_linked(st)
struct stat *st;
{
  struct link *cur = link_top;

  while (cur != NULL)
	if ((cur->dev == st->st_dev) && (cur->ino == st->st_ino))
		return cur->name;
	else
		cur = cur->next;
  return NULL;
}

clear_header()
{
  register char *ptr = header.hdr_block;

  while (ptr < &header.hdr_block[BLOCK_SIZE]) *ptr++ = '\0';
}

adjust_boundary()
{
  clear_header();
  mwrite(tar_fd, (char *) &header, sizeof(header));
#ifndef POSIX_COMP
  while (total_blocks++ < BLOCK_BOUNDARY)
	mwrite(tar_fd, (char *) &header, sizeof(header));
#else
  mwrite(tar_fd, (char *) &header, sizeof(header));
#endif
  (void) close(tar_fd);
}

mread(fd, address, bytes)
int fd, bytes;
char *address;
{
  if (read(fd, address, bytes) != bytes) error("Tar: read error.", NIL_PTR);
}

mwrite(fd, address, bytes)
int fd, bytes;
char *address;
{
  if (write(fd, address, bytes) != bytes)
	error("Tar: write error.", NIL_PTR);

  total_blocks++;
}

char output[BLOCK_SIZE];
print(str)			/* changed to use stderr rather than stdout
			 * -Dal */
register char *str;
{
  static int index = 0;

  if (str == NIL_PTR) {
	write(2, output, index);
	index = 0;
	return;
  }
  while (*str) {
	output[index++] = *str++;
	if (index == BLOCK_SIZE) {
		write(2, output, BLOCK_SIZE);
		index = 0;
	}
  }
}

char *num_out(number)
register long number;
{
  static char num_buf[12];
  register int i;

  for (i = 11; i--;) {
	num_buf[i] = (number & 07) + '0';
	number >>= 3;
  }

  return num_buf;
}

/*VARARGS2*/
string_print(buffer, fmt, args)
char *buffer;
register char *fmt;
int args;
{
  register char *buf_ptr;
  char *scan_ptr;
  char buf[NAME_SIZE];
  char *argptr = (char *) &args;
  BOOL pr_fl, i;

  if (pr_fl = (buffer == NIL_PTR)) buffer = buf;

  buf_ptr = buffer;
  while (*fmt) {
	if (*fmt == '%') {
		fmt++;
		switch (*fmt++) {
		    case 's':
			scan_ptr = *((char **) argptr);
			argptr += sizeof(char *);
			break;
		    case 'I':
			scan_ptr = num_out((long) *((int *) argptr));
			argptr += sizeof(int);
			for (i = 0; i < 5; i++) scan_ptr++;
			break;
		    case 'L':
			scan_ptr = num_out(*((long *) argptr));
			argptr += sizeof(long);
			break;
		    case 'd':
			scan_ptr = num_out((long) *((int *) argptr));
			argptr += sizeof(int);
			while (*scan_ptr == '0') scan_ptr++;
			scan_ptr--;
			break;
		    default:	scan_ptr = "";
		}
		while (*buf_ptr++ = *scan_ptr++);
		buf_ptr--;
	} else
		*buf_ptr++ = *fmt++;
  }
  *buf_ptr = '\0';

  if (pr_fl) print(buffer);
}