[comp.os.minix] ar.c whole file

ast@cs.vu.nl (Andy Tanenbaum) (05/30/88)

/* ar - archiver		Author: Michiel Huisjes */
/* V7 upgrade			Author:	Monty Walls */

/*
 * Usage: ar 'key' [posname] archive [file] ...
 *
 *	where 'key' is one of: qrqtpmx
 *
 *	  q: quickly append to the end of the archive file
 *	  m: move named files 
 *	  r: replace (append when not in archive)
 *	  d: delete
 *	  t: print contents of archive
 *	  p: print named files
 *	  x: extract
 *
 *	concatencated with one or more of: vuaibcl
 *  
 *	  l: local temporary file for work instead of /tmp/ar.$$$$$
 *	  v: verbose
 *	  a: after 'posname'
 *	  b: before 'posname'
 *	  i: before 'posname'
 *	  c: create  (suppresses creation message)
 *	  u: replace only if dated later than member in archive
 */

/* mods:
 *	1.2 upgrade.
 *	local directory support	(mrw).
 *	full V7 functionality + complete rewrite (mrw).
 *	changed verbose mode to give member name on print (mrw).
 *	fixed up error messages to give more info (mrw).
 *
 * notes:
 *	pdp11 long format & Intel long format are different.
 *
 * change log:
 *	forgot that ar_size is a long for printing - 2/14/88 - mrw
 *	got the mode bit maps mirrored - 2/19/88 - mrw
 *	print & extract member logic fixed - 2/19/88 - mrw
 */

/* include files */
#include <stdio.h>
#include <sys/stat.h>
#include <signal.h>

/* ar.c header file V7 */

#define ARMAG	0177545l
struct ar_hdr {
	char ar_name[14];
	long ar_date;		/* not Intel format */
	char ar_uid;
	char ar_gid;
	int ar_mode;
	long ar_size;		/* not Intel format */
};


/* macro functions */
#define FOREVER		(32766)
#define odd(nr)		(nr & 1)
#define even(nr)	(odd(nr) ? nr + 1 : nr)
#define quit(pid,sig)	(kill(pid,sig),sleep(FOREVER))
#ifndef tell 
#	define tell(f)	(lseek(f, 0l, 1))
#endif

/* option switches */
/* major options */
#define EXTRACT		0x01
#define REPLACE		0x02
#define PRINT		0x04
#define TABLE		0x08
#define DELETE		0x10
#define APPEND		0x20
#define MOVE		0x40

/* minor options */
#define BEFORE		0x01
#define AFTER		0x02
#define LOCAL		0x01
#define VERBOSE		0x01
#define CREATE		0x01
#define ONLY		0x01

/* mode bits maps */
#define EXEC_OWNER	00100
#define EXEC_GROUP	00010
#define EXEC_ALL	00001
#define READ_OWNER	00400
#define READ_GROUP	00040
#define READ_ALL	00004
#define WRITE_OWNER	00200
#define WRITE_GROUP	00020
#define WRITE_ALL	00002
#define SET_UID		04000
#define SET_GID		02000

/* global defines */
#define BUFFERSIZE	4096
#define WRITE		2		/* both read & write */
#define READ		0
#define MAGICSIZE	sizeof(short)	/* size of magic number in file */

/* option switches */
char verbose = 0;
char local = 0;
char create = 0;
char only = 0;
char major = 0;
char minor = 0;

/* global variables */
char *tmp1;
char *tmp2;
char *progname;
char *posname = NULL;
char *afile;
char buffer[BUFFERSIZE];
long pos_offset = -1;
int mypid;

/* keep track of member moves using this struct */
struct mov_list {
	long pos;
	struct mov_list *next;
} *moves = NULL;

/* forward declarations and external references */
extern char *malloc();
extern char *mktemp(), *rindex();
extern int strcmp();
extern print_date();
extern user_abort(), usage();
extern long lseek();
extern char *basename();

int
main(argc, argv)
int argc;
char **argv;
{
	int ac, opts_seen = 0, rc;
	char *av;
	
	progname = argv[0];		
	if (argc < 3) 
		usage();

	for (av = argv[1]; *av; ++av) {	
		switch (*av) {		/* major option */
			case 'q':
				major |= APPEND;
				++opts_seen;
				break;
			case 'r':
				major |= REPLACE;
				++opts_seen;
				break;
			case 'x':
				major |= EXTRACT;
				++opts_seen;
				break;
			case 'p':
				major |= PRINT;
				++opts_seen;
				break;
			case 'm':
				major |= MOVE;
				++opts_seen;
				break;
			case 'd':
				major |= DELETE;
				++opts_seen;
				break;
			case 't':
				major |= TABLE;
				++opts_seen;
				break;
			case 'l':
				local |= LOCAL;
				break;
			case 'a':
				minor |= AFTER;
				break;
			case 'i':
			case 'b':
				minor |= BEFORE;
				break;
			case 'v':
				verbose |= VERBOSE;
				break;
			case 'c':
				create |= CREATE;
				break;
			case 'u':
				only |= ONLY;
				break;
			default:
				usage();
		}
	}

	if (opts_seen != 1) 
		usage();

	/* now do edits on options */
	if (!(major & (REPLACE | MOVE))) {
		if (minor & (AFTER | BEFORE))
			usage();
	}
	else if (major & MOVE) {
		if (!(minor & (AFTER | BEFORE)))
			usage();
	}
	else if (only & ONLY)
		if (!(major & REPLACE))
			usage();
	
	if (local) 
		tmp1 = mktemp("./ar.1.XXXXXX");
	else
		tmp1 = mktemp("/tmp/ar.1.XXXXXX");
			
	/* now if minor says AFTER or BEFORE - then get posname */
	if (minor & (AFTER | BEFORE) && argc >= 4) {
		posname = argv[2];
		afile = argv[3];
		ac = 4;
	}
	else {
		posname = (char *)NULL;
		afile = argv[2];
		ac = 3;
	}
	
	/* exit logic consists of doing a kill on my pid to insure that we */
	/* get the current clean up and exit logic */
	mypid = getpid();	
	signal(SIGINT, user_abort);
	
	switch (major) {
		case REPLACE:
		case DELETE:
		case MOVE:
			ar_members(ac, argc, argv);
			break;
		case EXTRACT:
		case TABLE:
		case PRINT:
			ar_common(ac, argc, argv);
			break;
		case APPEND:
			append_members(ac, argc, argv);
			break;
		default:
			usage();
	}

	for (rc = 0; ac < argc; ++ac) {
		if (*argv[ac] != '\0') {
			/* no processing done on this name */
			fprintf(stderr,"Error %s: %s not found in %s\n", progname, argv[ac], afile);
			rc = 1;
		}
	}
	fflush(stdout);
	exit(rc);
}

usage()
{
	fprintf(stderr,"Usage: %s [qrxdpmt][abivulc] [posname] afile name ... \n",progname);
	exit(1);
}

user_abort()
{
	unlink(tmp1);
	exit(1);
}

insert_abort()
{
	unlink(tmp1);
	unlink(tmp2);
	exit(1);
}

mwrite(fd, address, bytes)
int fd;
register char *address;
register int bytes;
{
  if (write(fd, address, bytes) != bytes) {
	fprintf(stderr," Error: %s - Write error\n",progname);
	quit(mypid, SIGINT);
  }
}

long
swap(l)
long l;
{
	union {
		struct {
			int word1, word2;
		} words;
		long n;
	} u_in, u_out;
	
	u_in.n = l;
	u_out.words.word1 = u_in.words.word2;
	u_out.words.word2 = u_in.words.word1;
	return (u_out.n);
	
}

addmove(pos)
long pos;
{
	struct mov_list *newmove;

	newmove = (struct mov_list *)malloc(sizeof(struct mov_list));
	newmove->pos = pos;
	newmove->next = moves;
	moves = newmove;		
}

struct ar_hdr *
get_member(fd)
int fd;
{
	int ret;
	static struct ar_hdr member;
	
	if ((ret = read(fd,  &member, sizeof(struct ar_hdr))) <= 0) 
		return ((struct ar_hdr *)NULL);
	if (ret != sizeof(struct ar_hdr)) {
		fprintf(stderr,"Error: ar corrupted archive %s\n",afile);
		quit(mypid,SIGINT);
	}

	/* the archive long format is pdp11 not intel
	 * therefore we must reformat them for our internal use
	 */

	member.ar_date = swap(member.ar_date);
	member.ar_size = swap(member.ar_size);
	return (&member);
}

int
open_archive(filename, opt, to_create)
char *filename;
int opt;
{
	static unsigned short magic;
	int fd;
	
	/* to_create can have values of 0,1,2 */
	/* 0 - don't create a file. */
	/* 1 - create file but use create switch message mode */
	/* 2 - create file but don't talk about it */
	
	if (to_create) {
		if ((fd = creat(filename, 0644)) < 0) {
			fprintf(stderr, "Error: %s can not create %s\n",progname, filename);
			quit(mypid,SIGINT);
		}
		if (!create && to_create == 1) fprintf(stderr, "%s:%s created\n", progname, filename);
		magic = ARMAG;
		mwrite(fd, &magic, MAGICSIZE);
		return (fd);
	}
	else {
		if ((fd = open(filename, opt)) < 0) {
			if (opt == WRITE) 
				return (open_archive(filename, opt, 1));
			else {
				fprintf(stderr, "Error: %s can not open %s\n",progname, filename);
				quit(mypid,SIGINT);
			}
		}
		/* now check the magic number for ar V7 file */
		lseek(fd, 0l, 0);
		read(fd, &magic, MAGICSIZE);
		if (magic != ARMAG) {
			fprintf(stderr, "Error: not %s V7 format - %s\n",progname, filename);
			quit(mypid,SIGINT);
		}
		if (major & APPEND)
			lseek(fd, 0l, 2);	/* seek eof position */
			
		return (fd);
	}		
}


int
rebuild(fd, tempfd)
register int fd, tempfd;
{
	register int n;

	/* after we have built the archive to a temporary file and */
	/* everything has worked out- we copy the archive back to */
	/* original file */
		
	signal(SIGINT, SIG_IGN);
	close(fd);
	close(tempfd);
	fd = open_archive(afile, WRITE, 2);
	tempfd = open_archive(tmp1, WRITE, 0);
	while ((n = read(tempfd, buffer, BUFFERSIZE)) > 0) 
		mwrite(fd, buffer, n);
	close(tempfd);
	unlink(tmp1);
	return (fd);
}

print_mode(mode)
short mode;
{
	char g_ex, o_ex, all_ex;
	char g_rd, o_rd, all_rd;
	char g_wr, o_wr, all_wr;
	
	g_ex = EXEC_GROUP & mode ? 'x' : '-';
	o_ex = EXEC_OWNER & mode ? 'x' : '-';
	all_ex = EXEC_ALL & mode ? 'x' : '-';
	
	g_ex = SET_GID & mode ? 's' : g_ex;
	o_ex = SET_UID & mode ? 's' : o_ex;
	
	g_rd = READ_GROUP & mode ? 'r' : '-';
	o_rd = READ_OWNER & mode ? 'r' : '-';
	all_rd = READ_ALL & mode ? 'r' : '-';
	
	g_wr = WRITE_GROUP & mode ? 'w' : '-';
	o_wr = WRITE_OWNER & mode ? 'w' : '-';
	all_wr = WRITE_ALL & mode ? 'w' : '-';

	fprintf(stdout,"%c%c%c",o_rd, o_wr, o_ex);
	fprintf(stdout,"%c%c%c",g_rd, g_wr, g_ex);
	fprintf(stdout,"%c%c%c",all_rd, all_wr, all_ex);
}

print_header(member)
struct ar_hdr *member;
{
	if (verbose) {
		print_mode(member->ar_mode);
		fprintf(stdout,"%3.3d",member->ar_uid);
		fprintf(stdout,"/%-3.3d ",member->ar_gid);
		fprintf(stdout,"%5.5D",member->ar_size);	/* oops is long - mrw */
		print_date(member->ar_date);
	}
	fprintf(stdout,"%-14.14s\n",member->ar_name);
}

print(fd,member)
int fd;
struct ar_hdr *member;
{
	int outfd;
	long size;
	register int cnt, ret;
	int do_align;

	if (major & EXTRACT) {
		if ((outfd = creat(member->ar_name,0666)) < 0) {
			fprintf(stderr,"Error: %s could not creat %s\n",progname, member->ar_name);
			quit(mypid,SIGINT);
		}
		if (verbose)
			fprintf(stdout,"x - %s\n",member->ar_name);
	}
	else {
		if (verbose) {
			fprintf(stdout,"p - %s\n",member->ar_name);
			fflush(stdout);
		}
		outfd = fileno(stdout);
	}
	/* changed loop to use long size for correct extracts */	
	for (size = member->ar_size; size > 0; size -= ret) {
		cnt = (size < BUFFERSIZE ? size : BUFFERSIZE);
		ret = read(fd, buffer, cnt);
		if (ret > 0)
			write(outfd,buffer, ret);
	}
	if (odd(member->ar_size))
		lseek(fd,1l,1);		/* realign ourselves */
		
	if (major & EXTRACT) {
		close(outfd);
		chmod(member->ar_name, member->ar_mode);
	}		
}

/* copy a given member from fd1 to fd2 */
copy_member(infd, outfd, member)
int infd, outfd;
struct ar_hdr *member;
{
	int n, cnt;
	long m, size;

	/* save copies for our use */
	m = size = member->ar_size;

	/* format for disk usage */
	member->ar_size = swap(member->ar_size);
	member->ar_date = swap(member->ar_date);

	mwrite(outfd, member, sizeof(struct ar_hdr));
	for (; m > 0; m -= n) {
		cnt = (m < BUFFERSIZE ? m : BUFFERSIZE);
		if ((n = read(infd, buffer, cnt)) != cnt) {
			fprintf(stderr,"Error: %s - read error on %s\n",progname, member->ar_name);
			quit(mypid, SIGINT);
		}
		mwrite(outfd, buffer, n);
	}	
	if (odd(size)) {		/* pad to word boundary */
		mwrite(outfd, buffer, 1);
		lseek(infd,1l,1);		/* realign reading fd */ 
	}
}

/* insert at current offset - name file */
insert(fd, name, mess, oldmember)
int fd;
char *name, *mess;
struct ar_hdr *oldmember;
{
	static struct ar_hdr member;
	static struct stat status;
	int in_fd;

	if (stat(name, &status) < 0) {
		fprintf(stderr,"Error: %s cannot find file %s\n",progname,name);
		quit(mypid,SIGINT);
	}
	else if ((in_fd = open(name, READ)) < 0) {
		fprintf(stderr,"Error: %s cannot open file %s\n",progname,name);
		quit(mypid,SIGINT);
	}
	strcpy(member.ar_name, basename(name));
	member.ar_uid = status.st_uid;
	member.ar_gid = status.st_gid;
	member.ar_mode = status.st_mode & 07777;
	member.ar_date = status.st_mtime;
	member.ar_size = status.st_size;
	if (only & ONLY)
		if (oldmember != (struct ar_hdr *)NULL)  
			if (member.ar_date <= oldmember->ar_date) {
				close(in_fd);
				if (verbose) fprintf(stdout, "not %s - %s\n",mess, name);
				return (-1);
			}
		
	copy_member(in_fd, fd, &member); 
	if (verbose) 
		fprintf(stdout, "%s - %s\n",mess, name);
	close(in_fd);
	return (1);
}

int
ar_move(oldfd, arfd,mov)
int oldfd, arfd;
struct mov_list *mov;
{
	long pos;
	int cnt, want, a, newfd;
	struct ar_hdr *member;

	if (local) 
		tmp2 = mktemp("./ar.2.XXXXXX");
	else
		tmp2 = mktemp("/tmp/ar.2.XXXXXX");

	close(oldfd);				/* close old temp file */
	signal(SIGINT, insert_abort);		/* set new signal handler */
	newfd = open_archive(tmp2, WRITE, 2);	/* open new tmp file */
	oldfd = open_archive(tmp1, WRITE, 0);	/* reopen old tmp file */

	/* copy archive till we get to pos_offset */
	for (pos = pos_offset; pos > 0; pos -= cnt) {
		want = (pos < BUFFERSIZE ? pos : BUFFERSIZE);
		if ((cnt = read(oldfd, buffer, want)) > 0)
			mwrite(newfd, buffer, cnt);
	}
	/* if minor = 'a' then skip over posname */
	if (minor & AFTER) {
		if ((member = get_member(oldfd)) != NULL)
			copy_member(oldfd, newfd, member);
	}
	/* move members in the library */
	while (mov != NULL) {
		lseek(arfd, mov->pos, 0);
		if ((member = get_member(arfd)) != NULL)
			copy_member(arfd, newfd, member);
		mov = mov->next;
		if (verbose) fprintf(stdout, "m - %s\n", member->ar_name);
	}

	/* copy rest of library into new tmp file */
	while ((member = get_member(oldfd)) != NULL) 
		copy_member(oldfd, newfd, member);

	/* detach old temp file */
	close(oldfd);
	unlink(tmp1);

	/* change context temp file */
	tmp1 = tmp2;
	return (newfd);
}

int
ar_insert(oldfd, ac, argc, argv)
int oldfd;
int ac, argc;
char **argv;
{
	long pos;
	int cnt, want, a, newfd;
	struct ar_hdr *member;

	if (local) 
		tmp2 = mktemp("./ar.2.XXXXXX");
	else
		tmp2 = mktemp("/tmp/ar.2.XXXXXX");

	close(oldfd);				/* close old temp file */
	signal(SIGINT, insert_abort);		/* set new signal handler */
	newfd = open_archive(tmp2, WRITE, 2);	/* open new tmp file */
	oldfd = open_archive(tmp1, WRITE, 0);	/* reopen old tmp file */

	/* copy archive till we get to pos_offset */
	for (pos = pos_offset; pos > 0; pos -= cnt) {
		want = (pos < BUFFERSIZE ? pos : BUFFERSIZE);
		if ((cnt = read(oldfd, buffer, want)) > 0)
			mwrite(newfd, buffer, cnt);
	}
	/* if minor = 'a' then skip over posname */
	if (minor & AFTER) {
		if ((member = get_member(oldfd)) != NULL)
			copy_member(oldfd, newfd, member);
	}
	
	/* copy new members into the library */
	for (a = ac+1; a <= argc; ++a)
		if (argv[a-1] && *argv[a-1] != '\0') {
			insert(newfd, argv[a-1], "a", (struct ar_hdr *)NULL);
			*argv[a-1] = '\0';
		}

	/* copy rest of library into new tmp file */
	while ((member = get_member(oldfd)) != NULL) 
		copy_member(oldfd, newfd, member);

	/* detach old temp file */
	close(oldfd);
	unlink(tmp1);

	/* change context temp file */
	tmp1 = tmp2;
	return (newfd);
}

ar_common(ac, argc, argv)
int ac, argc;
char **argv;
{
	int a, fd, did_print;
	struct ar_hdr *member;

	fd = open_archive(afile, READ, 0);
	while ((member = get_member(fd)) != NULL) {
		did_print = 0;
		if (ac < argc) {
			for (a = ac+1; a <= argc; ++a) {
				if (strcmp(basename(argv[a-1]),member->ar_name) == 0) {
					if (major & TABLE)
						print_header(member);
					else if (major & (PRINT | EXTRACT)) {
						print(fd, member);
						did_print = 1;
					}
					*argv[a-1] = '\0';
					break;
				}
			}
		}
		else {
			if (major & TABLE)
				print_header(member);
			else if (major & (PRINT | EXTRACT)) {
				print(fd, member);
				did_print = 1;
			}
		}
		/* if we didn't print the member or didn't use it we will
		 * have to seek over its contents
		 */
		if (!did_print)
			lseek(fd, (long)even(member->ar_size), 1);
	}
}

ar_members(ac, argc, argv)
int ac, argc;
char **argv;
{
	int a, fd, tempfd, rc;
	struct ar_hdr *member;
	long *lpos;

	fd = open_archive(afile, WRITE, 0);
	tempfd = open_archive(tmp1, WRITE, 2);
	while ((member = get_member(fd)) != NULL) {

		/* if posname specified check for our member */
		/* if our member save his starting pos in our working file*/
		if (posname && strcmp(posname, member->ar_name) == 0)
			pos_offset = tell(tempfd) - MAGICSIZE;

		if (ac < argc) { /* we have a list of members to check */
			for (a = ac+1; a <= argc; ++a)
				if (strcmp(basename(argv[a-1]),member->ar_name) == 0) {
					if (major & REPLACE) {
						if (insert(tempfd,argv[a-1],"r", member) < 0)
							copy_member(fd, tempfd, member);
						else 
							lseek(fd, (long)even(member->ar_size), 1);
					}
					else if (major & MOVE) {
						/* cheat by saving pos in archive */
						addmove((tell(fd) - sizeof(struct ar_hdr)));
						lseek(fd, (long)even(member->ar_size), 1);
					}
					*argv[a-1] = '\0';
					break;
				}
		}
		if (ac >= argc || a > argc) 	/*nomatch on a member name */
			copy_member(fd, tempfd, member);
		else if (major & DELETE) {
			if (verbose) fprintf(stdout,"d - %s\n",member->ar_name);
			lseek(fd, (long)even(member->ar_size), 1);
		}
	}
	if (major & MOVE) {
		if (posname == NULL) 
			pos_offset = lseek(fd, 0l, 2);
		else if (pos_offset == (-1)) {
			fprintf(stderr,"Error: %s cannot find file %s\n",progname,posname);
			quit(mypid,SIGINT);
		}
		tempfd = ar_move(tempfd, fd, moves);
	}
	else if (major & REPLACE) {
		/* take care to add left overs */
		/* if the posname is not found we just add to end of ar */
		if (posname && pos_offset != (-1)) {
			tempfd = ar_insert(tempfd, ac, argc, argv);
		}
		else {
			for (a = ac+1; a <= argc; ++a)
				if (*argv[a-1]) {
					insert(tempfd, argv[a-1], "a", (struct ar_hdr *)NULL);
					*argv[a-1] = '\0';
				}
		}
	}
	fd = rebuild(fd, tempfd);
	close(fd);
}

append_members(ac, argc, argv)
int ac, argc;
char **argv;
{
	int a, fd;
	struct ar_hdr *member;
	
	/* quickly append members don't worry about dups in ar */
	fd = open_archive(afile, WRITE, 0);
	if (ac < argc) {
		if (odd(lseek(fd, 0l, 2)))
			mwrite(fd, buffer, 1);
		/* while not end of member list insert member at end */
		for (a = ac+1; a <= argc; ++a) {
			insert(fd, argv[a-1], "a", (struct ar_hdr *)NULL);
			*argv[a-1] = '\0';
		}
	}
	close(fd);
}


char *basename(path)
char *path;
{
  register char *ptr = path;
  register char *last = (char *)NULL;

  while (*ptr != '\0') {
	if (*ptr == '/')
		last = ptr;
	ptr++;
  }
  if (last == (char *)NULL)
	return path;
  if (*(last + 1) == '\0') {
	*last = '\0';
	return basename(path);
  }
  return last + 1;
}


#define MINUTE	60L
#define HOUR	(60L * MINUTE)
#define DAY	(24L * HOUR)
#define YEAR	(365L * DAY)
#define LYEAR	(366L * DAY)

int mo[] = {
  31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

char *moname[] = {
  " Jan ", " Feb ", " Mar ", " Apr ", " May ", " Jun ",
  " Jul ", " Aug ", " Sep ", " Oct ", " Nov ", " Dec "
};

/* Print the date.  This only works from 1970 to 2099. */
print_date(t)
long t;
{
  int i, year, day, month, hour, minute;
  long length, time(), original;

  year = 1970;
  original = t;
  while (t > 0) {
	length = (year % 4 == 0 ? LYEAR : YEAR);
	if (t < length)
		break;
	t -= length;
	year++;
  }

 /* Year has now been determined.  Now the rest. */
  day = (int) (t / DAY);
  t -= (long) day * DAY;
  hour = (int) (t / HOUR);
  t -= (long) hour * HOUR;
  minute = (int) (t / MINUTE);

 /* Determine the month and day of the month. */
  mo[1] = (year % 4 == 0 ? 29 : 28);
  month = 0;
  i = 0;
  while (day >= mo[i]) {
	month++;
	day -= mo[i];
	i++;
  }

  /* At this point, 'year', 'month', 'day', 'hour', 'minute'  ok */
  fprintf(stdout, "%s%2.2d ",moname[month],++day);
  if (time((long *)NULL) - original >= YEAR / 2L)
	fprintf(stdout,"%4.4D ",(long)year);
  else 
	fprintf(stdout,"%02.2d:%02.2d ",hour, minute);
}