minix/commands/install/install.c
2010-07-06 12:10:23 +00:00

646 lines
14 KiB
C

/* 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 link if possible. */
int cflag= 0; /* Copy if you can't link, otherwise symlink. */
int dflag= 0; /* Create a directory. */
int pflag= 0; /* Preserve timestamps. */
int strip= 0; /* Strip the copy. */
char **compress= nil; /* Compress utility to make a compressed executable. */
char *zcat= nil; /* Line one to decompress. */
int make_hardlink = 0; /* Make a hard link */
int make_symlink = 0; /* Make a symbolic link */
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);
}
if (pflag) {
struct utimbuf ubuf;
ubuf.actime= st.st_atime;
ubuf.modtime= st.st_mtime;
if (utime(dir, &ubuf) < 0 && errno != EPERM) {
report(dir); return;
}
}
}
}
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 (!make_symlink && !S_ISREG(sst.st_mode)) {
fprintf(stderr, "install: %s is not a regular file\n", source);
excode= 1;
return;
}
r= lstat(dest, &dst);
if (r < 0) {
if (errno != ENOENT) { report(dest); return; }
} else {
if (!make_symlink && !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) {
/* Try to link the files. */
if (r >= 0 && unlink(dest) < 0) {
report(dest); return;
}
if (make_hardlink) {
r = link(source, dest);
}
else if (make_symlink) {
r = symlink(source, dest);
} else {
fprintf(stderr,
"install: must specify hardlink or symlink\n");
}
if (r >= 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);
}
}
/* Don't modify metadata if symlink target doesn't exist */
if (make_symlink && stat(dest, &dst) < 0) return;
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 || pflag) {
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 [-lcpsz#] [-o owner] [-g group] [-m mode] [-S stack] [file1] file2\n\
install [-lcpsz#] [-o owner] [-g group] [-m mode] [-S stack] file ... dir\n\
install -d [-o owner] [-g group] [-m mode] directory\n");
exit(1);
}
int 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 'c': cflag= 1; break;
case 's': strip= 1; break;
case 'd': dflag= 1; break;
case 'p': pflag= 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 'l':
lflag= 1;
if (*p == 0) {
if (i == argc) usage();
p= argv[i++];
if (*p == 0) usage();
}
switch(*p) {
case 'h':
make_hardlink = 1;
break;
case 's':
make_symlink = 1;
break;
default:
break;
}
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 (lstat(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);
}