600 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			600 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
/*	install 1.11 - install files.			Author: Kees J. Bot
 | 
						|
 *								21 Feb 1993
 | 
						|
 */
 | 
						|
#define nil 0
 | 
						|
#include <sys/types.h>
 | 
						|
#include <sys/stat.h>
 | 
						|
#include <sys/wait.h>
 | 
						|
#include <stdio.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <string.h>
 | 
						|
#include <errno.h>
 | 
						|
#include <a.out.h>
 | 
						|
#include <limits.h>
 | 
						|
#include <pwd.h>
 | 
						|
#include <grp.h>
 | 
						|
#include <utime.h>
 | 
						|
#include <signal.h>
 | 
						|
 | 
						|
/* First line used on a self-decompressing executable. */
 | 
						|
char ZCAT[]=	"#!/usr/bin/zexec /usr/bin/zcat\n";
 | 
						|
char GZCAT[]=	"#!/usr/bin/zexec /usr/bin/gzcat\n";
 | 
						|
 | 
						|
/* Compression filters. */
 | 
						|
char *COMPRESS[]=	{ "compress", nil };
 | 
						|
char *GZIP[]=		{ "gzip", "-#", nil };
 | 
						|
 | 
						|
int excode= 0;		/* Exit code. */
 | 
						|
 | 
						|
void report(char *label)
 | 
						|
{
 | 
						|
	if (label == nil || label[0] == 0)
 | 
						|
		fprintf(stderr, "install: %s\n", strerror(errno));
 | 
						|
	else
 | 
						|
		fprintf(stderr, "install: %s: %s\n", label, strerror(errno));
 | 
						|
	excode= 1;
 | 
						|
}
 | 
						|
 | 
						|
void fatal(char *label)
 | 
						|
{
 | 
						|
	report(label);
 | 
						|
	exit(1);
 | 
						|
}
 | 
						|
 | 
						|
void *allocate(void *mem, size_t size)
 | 
						|
/* Safe malloc/realloc. */
 | 
						|
{
 | 
						|
	mem= mem == nil ? malloc(size) : realloc(mem, size);
 | 
						|
 | 
						|
	if (mem == nil) fatal(nil);
 | 
						|
	return mem;
 | 
						|
}
 | 
						|
 | 
						|
void deallocate(void *mem)
 | 
						|
{
 | 
						|
	if (mem != nil) free(mem);
 | 
						|
}
 | 
						|
 | 
						|
int lflag= 0;		/* Make a hard link if possible. */
 | 
						|
int cflag= 0;		/* Copy if you can't link, otherwise symlink. */
 | 
						|
int dflag= 0;		/* Create a directory. */
 | 
						|
int strip= 0;		/* Strip the copy. */
 | 
						|
char **compress= nil;	/* Compress utility to make a compressed executable. */
 | 
						|
char *zcat= nil;	/* Line one to decompress. */
 | 
						|
 | 
						|
long stack= -1;		/* Amount of heap + stack. */
 | 
						|
int wordpow= 1;		/* Must be multiplied with wordsize ** wordpow */
 | 
						|
			/* So 8kb for an 8086 and 16kb for the rest. */
 | 
						|
 | 
						|
pid_t filter(int fd, char **command)
 | 
						|
/* Let a command filter the output to fd. */
 | 
						|
{
 | 
						|
	pid_t pid;
 | 
						|
	int pfd[2];
 | 
						|
 | 
						|
	if (pipe(pfd) < 0) {
 | 
						|
		report("pipe()");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	switch ((pid= fork())) {
 | 
						|
	case -1:
 | 
						|
		report("fork()");
 | 
						|
		return -1;
 | 
						|
	case 0:
 | 
						|
		/* Run the filter. */
 | 
						|
		dup2(pfd[0], 0);
 | 
						|
		dup2(fd, 1);
 | 
						|
		close(pfd[0]);
 | 
						|
		close(pfd[1]);
 | 
						|
		close(fd);
 | 
						|
		signal(SIGPIPE, SIG_DFL);
 | 
						|
		execvp(command[0], command);
 | 
						|
		fatal(command[0]);
 | 
						|
	}
 | 
						|
	/* Connect fd to the pipe. */
 | 
						|
	dup2(pfd[1], fd);
 | 
						|
	close(pfd[0]);
 | 
						|
	close(pfd[1]);
 | 
						|
	return pid;
 | 
						|
}
 | 
						|
 | 
						|
int mkdirp(char *dir, int mode, int owner, int group)
 | 
						|
/* mkdir -p dir */
 | 
						|
{
 | 
						|
	int keep;
 | 
						|
	char *sep, *pref;
 | 
						|
 | 
						|
	sep= dir;
 | 
						|
	while (*sep == '/') sep++;
 | 
						|
	
 | 
						|
	if (*sep == 0) {
 | 
						|
		errno= EINVAL;
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	do {
 | 
						|
		while (*sep != '/' && *sep != 0) sep++;
 | 
						|
		pref= sep;
 | 
						|
		while (*sep == '/') sep++;
 | 
						|
 | 
						|
		keep= *pref; *pref= 0;
 | 
						|
 | 
						|
		if (strcmp(dir, ".") == 0 || strcmp(dir, "..") == 0) continue;
 | 
						|
 | 
						|
		if (mkdir(dir, mode) < 0) {
 | 
						|
			if (errno != EEXIST || *sep == 0) {
 | 
						|
				/* On purpose not doing: *pref= keep; */
 | 
						|
				return -1;
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			if (chown(dir, owner, group) < 0 && errno != EPERM)
 | 
						|
				return -1;
 | 
						|
		}
 | 
						|
	} while (*pref= keep, *sep != 0);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
void makedir(char *dir, int mode, int owner, int group)
 | 
						|
/* Install a directory, and set it's modes. */
 | 
						|
{
 | 
						|
	struct stat st;
 | 
						|
 | 
						|
	if (stat(dir, &st) < 0) {
 | 
						|
		if (errno != ENOENT) { report(dir); return; }
 | 
						|
 | 
						|
		/* The target doesn't exist, make it. */
 | 
						|
		if (mode == -1) mode= 0755;
 | 
						|
		if (owner == -1) owner= getuid();
 | 
						|
		if (group == -1) group= getgid();
 | 
						|
 | 
						|
		if (mkdirp(dir, mode, owner, group) < 0) {
 | 
						|
			report(dir); return;
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		/* The target does exist, change mode and ownership. */
 | 
						|
		if (mode == -1) mode= (st.st_mode & 07777) | 0555;
 | 
						|
 | 
						|
		if ((st.st_mode & 07777) != mode) {
 | 
						|
			if (chmod(dir, mode) < 0) { report(dir); return; }
 | 
						|
		}
 | 
						|
		if (owner == -1) owner= st.st_uid;
 | 
						|
		if (group == -1) group= st.st_gid;
 | 
						|
		if (st.st_uid != owner || st.st_gid != group) {
 | 
						|
			if (chown(dir, owner, group) < 0 && errno != EPERM) {
 | 
						|
				report(dir); return;
 | 
						|
			}
 | 
						|
			/* Set the mode again, chown may have wrecked it. */
 | 
						|
			(void) chmod(dir, mode);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
int setstack(struct exec *hdr)
 | 
						|
/* Set the stack size in a header.  Return true if something changed. */
 | 
						|
{
 | 
						|
	long total;
 | 
						|
 | 
						|
	total= stack;
 | 
						|
	while (wordpow > 0) {
 | 
						|
		total *= hdr->a_cpu == A_I8086 ? 2 : 4;
 | 
						|
		wordpow--;
 | 
						|
	}
 | 
						|
	total+= hdr->a_data + hdr->a_bss;
 | 
						|
 | 
						|
	if (!(hdr->a_flags & A_SEP)) {
 | 
						|
		total+= hdr->a_text;
 | 
						|
#ifdef A_PAL
 | 
						|
		if (hdr->a_flags & A_PAL) total+= hdr->a_hdrlen;
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	if (hdr->a_cpu == A_I8086 && total > 0x10000L)
 | 
						|
		total= 0x10000L;
 | 
						|
 | 
						|
	if (hdr->a_total != total) {
 | 
						|
		/* Need to change stack allocation. */
 | 
						|
		hdr->a_total= total;
 | 
						|
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
void copylink(char *source, char *dest, int mode, int owner, int group)
 | 
						|
{
 | 
						|
	struct stat sst, dst;
 | 
						|
	int sfd, dfd, n;
 | 
						|
	int r, same= 0, change= 0, docopy= 1;
 | 
						|
	char buf[4096];
 | 
						|
#	define hdr ((struct exec *) buf)
 | 
						|
	pid_t pid = 0;
 | 
						|
	int status = 0;
 | 
						|
 | 
						|
	/* Source must exist as a plain file, dest may exist as a plain file. */
 | 
						|
 | 
						|
	if (stat(source, &sst) < 0) { report(source); return; }
 | 
						|
 | 
						|
	if (mode == -1) {
 | 
						|
		mode= sst.st_mode & 07777;
 | 
						|
		if (!lflag || cflag) {
 | 
						|
			mode|= 0444;
 | 
						|
			if (mode & 0111) mode|= 0111;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (owner == -1) owner= sst.st_uid;
 | 
						|
	if (group == -1) group= sst.st_gid;
 | 
						|
 | 
						|
	if (!S_ISREG(sst.st_mode)) {
 | 
						|
		fprintf(stderr, "install: %s is not a regular file\n", source);
 | 
						|
		excode= 1;
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	r= stat(dest, &dst);
 | 
						|
	if (r < 0) {
 | 
						|
		if (errno != ENOENT) { report(dest); return; }
 | 
						|
	} else {
 | 
						|
		if (!S_ISREG(dst.st_mode)) {
 | 
						|
			fprintf(stderr, "install: %s is not a regular file\n",
 | 
						|
									dest);
 | 
						|
			excode= 1;
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		/* Are the files the same? */
 | 
						|
		if (sst.st_dev == dst.st_dev && sst.st_ino == dst.st_ino) {
 | 
						|
			if (!lflag && cflag) {
 | 
						|
				fprintf(stderr,
 | 
						|
				"install: %s and %s are the same, can't copy\n",
 | 
						|
					source, dest);
 | 
						|
				excode= 1;
 | 
						|
				return;
 | 
						|
			}
 | 
						|
			same= 1;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (lflag && !same) {
 | 
						|
		/* Try to link the files. */
 | 
						|
 | 
						|
		if (r >= 0 && unlink(dest) < 0) {
 | 
						|
			report(dest); return;
 | 
						|
		}
 | 
						|
 | 
						|
		if (link(source, dest) >= 0) {
 | 
						|
			docopy= 0;
 | 
						|
		} else {
 | 
						|
			if (!cflag || errno != EXDEV) {
 | 
						|
				fprintf(stderr,
 | 
						|
					"install: can't link %s to %s: %s\n",
 | 
						|
					source, dest, strerror(errno));
 | 
						|
				excode= 1;
 | 
						|
				return;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (docopy && !same) {
 | 
						|
		/* Copy the files, stripping if necessary. */
 | 
						|
		long count= LONG_MAX;
 | 
						|
		int first= 1;
 | 
						|
 | 
						|
		if ((sfd= open(source, O_RDONLY)) < 0) {
 | 
						|
			report(source); return;
 | 
						|
		}
 | 
						|
 | 
						|
		/* Open for write is less simple, its mode may be 444. */
 | 
						|
		dfd= open(dest, O_WRONLY|O_CREAT|O_TRUNC, mode | 0600);
 | 
						|
		if (dfd < 0 && errno == EACCES) {
 | 
						|
			(void) chmod(dest, mode | 0600);
 | 
						|
			dfd= open(dest, O_WRONLY|O_TRUNC);
 | 
						|
		}
 | 
						|
		if (dfd < 0) {
 | 
						|
			report(dest);
 | 
						|
			close(sfd);
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		pid= 0;
 | 
						|
		while (count > 0 && (n= read(sfd, buf, sizeof(buf))) > 0) {
 | 
						|
			if (first && n >= A_MINHDR && !BADMAG(*hdr)) {
 | 
						|
				if (strip) {
 | 
						|
					count= hdr->a_hdrlen
 | 
						|
						+ hdr->a_text + hdr->a_data;
 | 
						|
#ifdef A_NSYM
 | 
						|
					hdr->a_flags &= ~A_NSYM;
 | 
						|
#endif
 | 
						|
					hdr->a_syms= 0;
 | 
						|
				}
 | 
						|
				if (stack != -1 && setstack(hdr)) change= 1;
 | 
						|
 | 
						|
				if (compress != nil) {
 | 
						|
					/* Write first #! line. */
 | 
						|
					(void) write(dfd, zcat, strlen(zcat));
 | 
						|
 | 
						|
					/* Put a compressor in between. */
 | 
						|
					if ((pid= filter(dfd, compress)) < 0) {
 | 
						|
						close(sfd);
 | 
						|
						close(dfd);
 | 
						|
						return;
 | 
						|
					}
 | 
						|
					change= 1;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if (count < n) n= count;
 | 
						|
 | 
						|
			if (write(dfd, buf, n) < 0) {
 | 
						|
				report(dest);
 | 
						|
				close(sfd);
 | 
						|
				close(dfd);
 | 
						|
				if (pid != 0) (void) waitpid(pid, nil, 0);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
			count-= n;
 | 
						|
			first= 0;
 | 
						|
		}
 | 
						|
		if (n < 0) report(source);
 | 
						|
		close(sfd);
 | 
						|
		close(dfd);
 | 
						|
		if (pid != 0 && waitpid(pid, &status, 0) < 0 || status != 0) {
 | 
						|
			excode= 1;
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		if (n < 0) return;
 | 
						|
	} else {
 | 
						|
		if (stack != -1) {
 | 
						|
			/* The file has been linked into place.  Set the
 | 
						|
			 * stack size.
 | 
						|
			 */
 | 
						|
			if ((dfd= open(dest, O_RDWR)) < 0) {
 | 
						|
				report(dest);
 | 
						|
				return;
 | 
						|
			}
 | 
						|
 | 
						|
			if ((n= read(dfd, buf, sizeof(*hdr))) < 0) {
 | 
						|
				report(dest); return;
 | 
						|
			}
 | 
						|
 | 
						|
			if (n >= A_MINHDR && !BADMAG(*hdr) && setstack(hdr)) {
 | 
						|
				if (lseek(dfd, (off_t) 0, SEEK_SET) == -1
 | 
						|
					|| write(dfd, buf, n) < 0
 | 
						|
				) {
 | 
						|
					report(dest);
 | 
						|
					close(dfd);
 | 
						|
					return;
 | 
						|
				}
 | 
						|
				change= 1;
 | 
						|
			}
 | 
						|
			close(dfd);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (stat(dest, &dst) < 0) { report(dest); return; }
 | 
						|
 | 
						|
	if ((dst.st_mode & 07777) != mode) {
 | 
						|
		if (chmod(dest, mode) < 0) { report(dest); return; }
 | 
						|
	}
 | 
						|
	if (dst.st_uid != owner || dst.st_gid != group) {
 | 
						|
		if (chown(dest, owner, group) < 0 && errno != EPERM) {
 | 
						|
			report(dest); return;
 | 
						|
		}
 | 
						|
		/* Set the mode again, chown may have wrecked it. */
 | 
						|
		(void) chmod(dest, mode);
 | 
						|
	}
 | 
						|
	if (!change) {
 | 
						|
		struct utimbuf ubuf;
 | 
						|
 | 
						|
		ubuf.actime= dst.st_atime;
 | 
						|
		ubuf.modtime= sst.st_mtime;
 | 
						|
 | 
						|
		if (utime(dest, &ubuf) < 0 && errno != EPERM) {
 | 
						|
			report(dest); return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void usage(void)
 | 
						|
{
 | 
						|
	fprintf(stderr, "\
 | 
						|
Usage:\n\
 | 
						|
  install [-lcsz#] [-o owner] [-g group] [-m mode] [-S stack] [file1] file2\n\
 | 
						|
  install [-lcsz#] [-o owner] [-g group] [-m mode] [-S stack] file ... dir\n\
 | 
						|
  install -d [-o owner] [-g group] [-m mode] directory\n");
 | 
						|
	exit(1);
 | 
						|
}
 | 
						|
 | 
						|
void main(int argc, char **argv)
 | 
						|
{
 | 
						|
	int i= 1;
 | 
						|
	int mode= -1;		/* Mode of target. */
 | 
						|
	int owner= -1;		/* Owner. */
 | 
						|
	int group= -1;		/* Group. */
 | 
						|
	int super = 0;
 | 
						|
#if NGROUPS_MAX > 0
 | 
						|
	gid_t groups[NGROUPS_MAX];
 | 
						|
	int ngroups;
 | 
						|
	int g;
 | 
						|
#endif
 | 
						|
 | 
						|
	/* Only those in group 0 are allowed to set owner and group. */
 | 
						|
	if (getgid() == 0) super = 1;
 | 
						|
#if NGROUPS_MAX > 0
 | 
						|
	ngroups= getgroups(NGROUPS_MAX, groups);
 | 
						|
	for (g= 0; g < ngroups; g++) if (groups[g] == 0) super= 1;
 | 
						|
#endif
 | 
						|
	if (!super) {
 | 
						|
		setgid(getgid());
 | 
						|
		setuid(getuid());
 | 
						|
	}
 | 
						|
 | 
						|
	/* May use a filter. */
 | 
						|
	signal(SIGPIPE, SIG_IGN);
 | 
						|
 | 
						|
	while (i < argc && argv[i][0] == '-') {
 | 
						|
		char *p= argv[i++]+1;
 | 
						|
		char *end;
 | 
						|
		unsigned long num;
 | 
						|
		int wp;
 | 
						|
		struct passwd *pw;
 | 
						|
		struct group *gr;
 | 
						|
 | 
						|
		if (strcmp(p, "-") == 0) break;
 | 
						|
 | 
						|
		while (*p != 0) {
 | 
						|
			switch (*p++) {
 | 
						|
			case 'l':	lflag= 1;	break;
 | 
						|
			case 'c':	cflag= 1;	break;
 | 
						|
			case 's':	strip= 1;	break;
 | 
						|
			case 'd':	dflag= 1;	break;
 | 
						|
			case 'z':
 | 
						|
				if (compress == nil) {
 | 
						|
					compress= COMPRESS;
 | 
						|
					zcat= ZCAT;
 | 
						|
				}
 | 
						|
				break;
 | 
						|
			case 'o':
 | 
						|
				if (*p == 0) {
 | 
						|
					if (i == argc) usage();
 | 
						|
					p= argv[i++];
 | 
						|
					if (*p == 0) usage();
 | 
						|
				}
 | 
						|
				num= strtoul(p, &end, 10);
 | 
						|
				if (*end == 0) {
 | 
						|
					if ((uid_t) num != num) usage();
 | 
						|
					owner= num;
 | 
						|
				} else {
 | 
						|
					if ((pw= getpwnam(p)) == nil) {
 | 
						|
						fprintf(stderr,
 | 
						|
						"install: %s: unknown user\n",
 | 
						|
							p);
 | 
						|
						exit(1);
 | 
						|
					}
 | 
						|
					owner= pw->pw_uid;
 | 
						|
				}
 | 
						|
				p= "";
 | 
						|
				break;
 | 
						|
			case 'g':
 | 
						|
				if (*p == 0) {
 | 
						|
					if (i == argc) usage();
 | 
						|
					p= argv[i++];
 | 
						|
					if (*p == 0) usage();
 | 
						|
				}
 | 
						|
				num= strtoul(p, &end, 10);
 | 
						|
				if (*end == 0) {
 | 
						|
					if ((gid_t) num != num) usage();
 | 
						|
					group= num;
 | 
						|
				} else {
 | 
						|
					if ((gr= getgrnam(p)) == nil) {
 | 
						|
						fprintf(stderr,
 | 
						|
						"install: %s: unknown user\n",
 | 
						|
							p);
 | 
						|
						exit(1);
 | 
						|
					}
 | 
						|
					group= gr->gr_gid;
 | 
						|
				}
 | 
						|
				p= "";
 | 
						|
				break;
 | 
						|
			case 'm':
 | 
						|
				if (*p == 0) {
 | 
						|
					if (i == argc) usage();
 | 
						|
					p= argv[i++];
 | 
						|
					if (*p == 0) usage();
 | 
						|
				}
 | 
						|
				num= strtoul(p, &end, 010);
 | 
						|
				if (*end != 0 || (num & 07777) != num) usage();
 | 
						|
				mode= num;
 | 
						|
				if ((mode & S_ISUID) && super && owner == -1) {
 | 
						|
					/* Setuid what?  Root most likely. */
 | 
						|
					owner= 0;
 | 
						|
				}
 | 
						|
				if ((mode & S_ISGID) && super && group == -1) {
 | 
						|
					group= 0;
 | 
						|
				}
 | 
						|
				p= "";
 | 
						|
				break;
 | 
						|
			case 'S':
 | 
						|
				if (*p == 0) {
 | 
						|
					if (i == argc) usage();
 | 
						|
					p= argv[i++];
 | 
						|
					if (*p == 0) usage();
 | 
						|
				}
 | 
						|
				stack= strtol(p, &end, 0);
 | 
						|
				wp= 0;
 | 
						|
				if (end == p || stack < 0) usage();
 | 
						|
				p= end;
 | 
						|
				while (*p != 0) {
 | 
						|
					switch (*p++) {
 | 
						|
					case 'm':
 | 
						|
					case 'M': num= 1024 * 1024L; break;
 | 
						|
					case 'k':
 | 
						|
					case 'K': num= 1024; break;
 | 
						|
					case 'w':
 | 
						|
					case 'W': num= 4; wp++; break;
 | 
						|
					case 'b':
 | 
						|
					case 'B': num= 1; break;
 | 
						|
					default: usage();
 | 
						|
					}
 | 
						|
					if (stack > LONG_MAX / num) usage();
 | 
						|
					stack*= num;
 | 
						|
				}
 | 
						|
				wordpow= 0;
 | 
						|
				while (wp > 0) { stack /= 4; wordpow++; wp--; }
 | 
						|
				break;
 | 
						|
			default:
 | 
						|
				if ((unsigned) (p[-1] - '1') <= ('9' - '1')) {
 | 
						|
					compress= GZIP;
 | 
						|
					GZIP[1][1]= p[-1];
 | 
						|
					zcat= GZCAT;
 | 
						|
					break;
 | 
						|
				}
 | 
						|
				usage();
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	/* Some options don't mix. */
 | 
						|
	if (dflag && (cflag || lflag || strip)) usage();
 | 
						|
 | 
						|
	/* Don't let the user umask interfere. */
 | 
						|
	umask(000);
 | 
						|
 | 
						|
	if (dflag) {
 | 
						|
		/* install directory */
 | 
						|
		if ((argc - i) != 1) usage();
 | 
						|
 | 
						|
		makedir(argv[i], mode, owner, group);
 | 
						|
	} else {
 | 
						|
		struct stat st;
 | 
						|
 | 
						|
		if ((argc - i) < 1) usage();
 | 
						|
		if ((lflag || cflag) && (argc - i) == 1) usage();
 | 
						|
 | 
						|
		if (stat(argv[argc-1], &st) >= 0 && S_ISDIR(st.st_mode)) {
 | 
						|
			/* install file ... dir */
 | 
						|
			char *target= nil;
 | 
						|
			char *base;
 | 
						|
 | 
						|
			if ((argc - i) == 1) usage();
 | 
						|
 | 
						|
			while (i < argc-1) {
 | 
						|
				if ((base= strrchr(argv[i], '/')) == nil)
 | 
						|
					base= argv[i];
 | 
						|
				else
 | 
						|
					base++;
 | 
						|
				target= allocate(target, strlen(argv[argc-1])
 | 
						|
						+ 1 + strlen(base) + 1);
 | 
						|
				strcpy(target, argv[argc-1]);
 | 
						|
				strcat(target, "/");
 | 
						|
				strcat(target, base);
 | 
						|
 | 
						|
				copylink(argv[i++], target, mode, owner, group);
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			/* install [file1] file2 */
 | 
						|
 | 
						|
			copylink(argv[i], argv[argc-1], mode, owner, group);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	exit(excode);
 | 
						|
}
 |