1394 lines
31 KiB
C
1394 lines
31 KiB
C
/*
|
|
* Copyright 1989 Silicon Graphics, Inc. All rights reserved.
|
|
* $Revision: 1.28 $
|
|
*
|
|
* NAME
|
|
* install - install software OR generate an installation database
|
|
* SYNOPSIS
|
|
* install [-m mode] [-u owner] [-g group] [-idb attribute] [-new]
|
|
* [-o] [-O] [-rawidb idbpath] [-root rootpath] [-s] [-t] [-v]
|
|
* optionally, one of [-blk maj,min[,off]] [-chr maj,min[,off]] [-dir]
|
|
* [-fifo] [-ln path] [-lns path] [-src path]
|
|
* one of [-f dir] [-F dir] (mandatory unless -dir is used)
|
|
* file1 file2 ... fileN (pathnames of files to install)
|
|
* DESCRIPTION
|
|
* See install(1).
|
|
* ENVIRONMENT
|
|
* The following optional environment variables are used if set:
|
|
* INSTOPTS Install options, overridden by command line options.
|
|
* RAWIDB Absolute pathname of raw idb to generate.
|
|
* ROOT Pathname of target tree root, overridden by -root.
|
|
* SRC Pathname of source tree top (idb generation only).
|
|
* AUTHOR
|
|
* Brendan Eich, 04/29/88
|
|
* Ariel Faigon, April 1997:
|
|
* Added BSD compatibility to keep GNU, FreeBSD, Linux,
|
|
* and XFree Makefiles and configure's happy.
|
|
* NOTE
|
|
* The somewhat awkward options are derived from a version of the
|
|
* V.3 install script, heavily hacked by SGI and MIPS.
|
|
*
|
|
* The -o and -O options don't work when using range arguments
|
|
* for the minor number with -chr and -blk. It would be painful
|
|
* to do so, and doesn't really make sense anyway.
|
|
* TODO
|
|
* Unify -f, -F and -dir.
|
|
* Obsolete old options and use getopt (it'll never happen)
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h> /* for lockf flags and utimbuf */
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/sysmacros.h>
|
|
#include <sys/wait.h>
|
|
|
|
/*
|
|
* Maximum number of IDB attributes as listed by multiple -idb options.
|
|
* Maximum number of quoted arguments, also options with arguments.
|
|
*/
|
|
#define MAXIDBTAGS 10
|
|
#define MAXARGS 100
|
|
|
|
/*
|
|
* A file's type from the point of view of installation.
|
|
*/
|
|
enum ftype { REG, BLKDEV, CHRDEV, DIR, FIFO, LINK, SYMLINK };
|
|
|
|
/*
|
|
* A structure for mode and user/group id.
|
|
*/
|
|
struct value {
|
|
char *name;
|
|
int number;
|
|
};
|
|
|
|
/*
|
|
* Global error reporting and option variables.
|
|
*/
|
|
char *progname; /* invoked name for diagnostics */
|
|
int superuser; /* true if effective uid is 0 */
|
|
int trytoremoveold; /* whether to attempt to unlink an existing target */
|
|
int saveold; /* whether to save an existing target as OLDtarget */
|
|
int trytosymlink; /* install by symlinking dst to src if possible */
|
|
int newtimes; /* install with new access and modified times */
|
|
|
|
/*
|
|
* Necessary forward function prototypes.
|
|
*/
|
|
void parseoptstr(char *options, char *varname);
|
|
int parseoptvec(int argc, char **argv);
|
|
void usage(void);
|
|
int mtoi(char *s, int *ip);
|
|
int utoi(char *s, int *ip);
|
|
int gtoi(char *s, int *ip);
|
|
void initowner(struct value *vp);
|
|
void initgroup(struct value *vp);
|
|
void fail(char *format, ...);
|
|
void sfail(char *format, ...);
|
|
void makedirpath(char *name, struct value *owner, struct value *group);
|
|
void install(enum ftype ftype, int mode, struct value *owner,
|
|
struct value *group, char *dst, char *src);
|
|
void idbopen(char *name);
|
|
void idbwrite(enum ftype ftype, char *mode, char *owner, char *group,
|
|
char *dst, char *src, int tagc, char **tagv);
|
|
void idbclose(char *name);
|
|
|
|
/*
|
|
* Variables shared by main and parseoptvec.
|
|
*/
|
|
char *root, *rawidb; /* ROOT and RAWIDB values */
|
|
enum ftype ftype = REG; /* file type */
|
|
struct value mode, owner, group; /* file permission attributes */
|
|
char *dstdir, *src; /* destination directory, source path */
|
|
char *idbtagvec[MAXIDBTAGS]; /* vector of idb attributes */
|
|
int idbtagcnt; /* count of idb attributes */
|
|
int makedstdir; /* create prerequisite directories */
|
|
int verbose; /* if true, blab while installing */
|
|
|
|
/*
|
|
* Option processing definitions. Options are looked-up by name in a table
|
|
* which associates an enum opt value with each valid name.
|
|
*/
|
|
enum opt {
|
|
Opt_Unknown = -1,
|
|
Opt_m, Opt_u, Opt_g, Opt_idb, Opt_o, Opt_O, Opt_s, Opt_v,
|
|
Opt_blk, Opt_chr, Opt_dir, Opt_fifo, Opt_ln, Opt_lns,
|
|
Opt_src, Opt_f, Opt_F, Opt_t, Opt_root, Opt_rawidb, Opt_new,
|
|
Opt_c, Opt_d
|
|
};
|
|
|
|
char *optnamemap[] = {
|
|
"m", "u", "g", "idb", "o", "O", "s", "v",
|
|
"blk", "chr", "dir", "fifo", "ln", "lns",
|
|
"src", "f", "F", "t", "root", "rawidb", "new",
|
|
"c", "d"
|
|
};
|
|
#define NOPTS (sizeof optnamemap / sizeof optnamemap[0])
|
|
#define optname(opt) optnamemap[(int)(opt)]
|
|
|
|
char optvarname[] = "INSTOPTS";
|
|
char white[] = " \t\n";
|
|
|
|
int *argc_p; /* for BSD compatibility argument faking */
|
|
int opt_s; /* for BSD compatibility: strip */
|
|
char *strip_prog = "/bin/strip";
|
|
|
|
main(int argc, char **argv)
|
|
{
|
|
char *list, *path;
|
|
int first, nosrc;
|
|
struct value dirowner, dirgroup;
|
|
char *nargv[MAXARGS];
|
|
char srcpath[MAXPATHLEN];
|
|
|
|
/*
|
|
* Initialize and process environment variables and options.
|
|
* Set default root and open rawidb after parsing options, since
|
|
* these parameters may be set by either the INSTOPTS envariable
|
|
* or by command line options.
|
|
*/
|
|
argc_p = &argc; /* for BSD compat transformations */
|
|
|
|
progname = *argv;
|
|
superuser = (geteuid() == 0);
|
|
root = getenv("ROOT");
|
|
rawidb = getenv("RAWIDB");
|
|
list = getenv(optvarname);
|
|
if (list)
|
|
parseoptstr(list, optvarname);
|
|
first = parseoptvec(argc, argv);
|
|
if (root == 0)
|
|
root = "";
|
|
if (rawidb && *rawidb == '\0')
|
|
rawidb = NULL;
|
|
if (rawidb)
|
|
idbopen(rawidb);
|
|
else
|
|
umask(0);
|
|
|
|
/*
|
|
* If no arguments remain, or no destination directory was specified,
|
|
* complain and bail out.
|
|
*/
|
|
argc -= first;
|
|
argv += first;
|
|
if (argc <= 0 || dstdir == 0)
|
|
usage();
|
|
|
|
/*
|
|
* Now set default mode, owner, and group if not set by options.
|
|
*/
|
|
if (mode.name == 0) {
|
|
switch (ftype) {
|
|
case REG:
|
|
case DIR:
|
|
mode.name = "755";
|
|
break;
|
|
case LINK:
|
|
case SYMLINK:
|
|
mode.name = "0";
|
|
break;
|
|
default:
|
|
mode.name = "666";
|
|
}
|
|
mtoi(mode.name, &mode.number);
|
|
}
|
|
if (owner.name == 0) {
|
|
owner.name = "root";
|
|
if (rawidb == 0
|
|
&& (!superuser || !utoi(owner.name, &owner.number))) {
|
|
initowner(&owner);
|
|
}
|
|
}
|
|
if (group.name == 0) {
|
|
group.name = "sys";
|
|
if (rawidb == 0
|
|
&& (!superuser || !gtoi(group.name, &group.number))) {
|
|
initgroup(&group);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If -F was specified, initialize dirowner and dirgroup so that
|
|
* the user can write in implicitly created directories.
|
|
*/
|
|
if (makedstdir) {
|
|
if (rawidb == 0 && !superuser) {
|
|
initowner(&dirowner);
|
|
initgroup(&dirgroup);
|
|
} else {
|
|
dirowner = owner;
|
|
dirgroup = group;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If there's only one argument and it contains blanks, break it
|
|
* up into arguments (backwards compatibility).
|
|
*/
|
|
if (argc == 1 && strpbrk(*argv, white)) {
|
|
argc = 0;
|
|
list = *argv;
|
|
while ((path = strtok(list, white)) != 0) {
|
|
if (argc >= MAXARGS)
|
|
fail("too many quoted arguments");
|
|
list = 0;
|
|
nargv[argc++] = path;
|
|
}
|
|
nargv[argc] = 0;
|
|
argv = nargv;
|
|
}
|
|
|
|
/*
|
|
* Process remaining arguments as relative paths from the current
|
|
* directory to files to be installed. Prepend $ROOT to absolute
|
|
* pathnames if and only if we are not generating an idb.
|
|
*/
|
|
nosrc = (src == 0);
|
|
if (rawidb == 0 && src && src[0] == '/') {
|
|
sprintf(srcpath, "%s%s", root, src);
|
|
src = srcpath;
|
|
}
|
|
while (--argc >= 0) {
|
|
char dst[MAXPATHLEN];
|
|
|
|
path = *argv++;
|
|
if (nosrc)
|
|
src = path;
|
|
while (*path == '/')
|
|
path++;
|
|
if (rawidb == 0) {
|
|
sprintf(dst, "%s%s/%s", root, dstdir, path);
|
|
if (nosrc && src[0] == '/') {
|
|
sprintf(srcpath, "%s%s", root, src);
|
|
src = srcpath;
|
|
}
|
|
if (makedstdir)
|
|
makedirpath(dst, &dirowner, &dirgroup);
|
|
install(ftype, mode.number, &owner, &group, dst, src);
|
|
if (verbose)
|
|
printf("%s installed as %s.\n", src, dst);
|
|
} else {
|
|
/*
|
|
* Idb generation is always silent.
|
|
*/
|
|
sprintf(dst, "%s/%s", dstdir, path);
|
|
idbwrite(ftype, mode.name, owner.name, group.name,
|
|
dst, src, idbtagcnt, idbtagvec);
|
|
}
|
|
}
|
|
if (rawidb)
|
|
idbclose(rawidb);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* More option processing gunk. The opts vector is used by findopt to
|
|
* record options and detect duplicates. The next free slot in opts is
|
|
* indexed by nextoptslot.
|
|
*/
|
|
enum opt opts[NOPTS];
|
|
int nextoptslot;
|
|
|
|
/*
|
|
* Parse options listed in the string options, which is the value of the
|
|
* envariable named by varname.
|
|
*/
|
|
void
|
|
parseoptstr(char *options, char *varname)
|
|
{
|
|
int argc;
|
|
char *argv[MAXARGS+1];
|
|
char *word;
|
|
|
|
argc = 0;
|
|
argv[argc++] = progname;
|
|
while ((word = strtok(options, white)) != 0) {
|
|
if (argc >= MAXARGS)
|
|
fail("too many %s options", varname);
|
|
options = 0;
|
|
argv[argc++] = word;
|
|
}
|
|
|
|
/*
|
|
* Reset nextoptslot after parsing, to avoid checking for
|
|
* environment/argv duplicates, which are ok.
|
|
*/
|
|
argv[argc] = 0;
|
|
parseoptvec(argc, argv);
|
|
nextoptslot = 0;
|
|
}
|
|
|
|
enum opt findopt(char *name);
|
|
void missingoptarg(enum opt opt);
|
|
void badoptarg(enum opt opt, char *arg);
|
|
char *target_dir(char * name);
|
|
char *filename(char * name);
|
|
int is_dir(char * name);
|
|
|
|
int
|
|
parseoptvec(int argc, char **argv)
|
|
{
|
|
int oargc;
|
|
enum opt opt;
|
|
|
|
oargc = argc;
|
|
while (--argc > 0 && (*++argv)[0] == '-') {
|
|
opt = findopt(&(*argv)[1]);
|
|
switch (opt) {
|
|
case Opt_m:
|
|
if (--argc == 0)
|
|
missingoptarg(opt);
|
|
if (!mtoi(*++argv, &mode.number))
|
|
badoptarg(opt, *argv);
|
|
mode.name = *argv;
|
|
break;
|
|
|
|
case Opt_u:
|
|
if (--argc == 0)
|
|
missingoptarg(opt);
|
|
argv++;
|
|
if (rawidb == 0 && !utoi(*argv, &owner.number))
|
|
badoptarg(opt, *argv);
|
|
owner.name = *argv;
|
|
break;
|
|
|
|
case Opt_g:
|
|
if (--argc == 0)
|
|
missingoptarg(opt);
|
|
argv++;
|
|
if (rawidb == 0 && !gtoi(*argv, &group.number))
|
|
badoptarg(opt, *argv);
|
|
group.name = *argv;
|
|
break;
|
|
|
|
case Opt_idb:
|
|
if (--argc == 0)
|
|
missingoptarg(opt);
|
|
if (idbtagcnt >= MAXIDBTAGS)
|
|
fail("too many -%s options", optname(opt));
|
|
idbtagvec[idbtagcnt++] = *++argv;
|
|
break;
|
|
|
|
case Opt_O:
|
|
trytoremoveold = 1;
|
|
/* FALL THROUGH */
|
|
case Opt_o:
|
|
saveold = 1;
|
|
break;
|
|
|
|
case Opt_s:
|
|
/* silence was optional, is now the default */
|
|
/*
|
|
* Note: In BSD: -s means strip
|
|
* But on SGI strip is not necessarily installed
|
|
* so we cannot guarantee BSD compatibility here :-(
|
|
*/
|
|
opt_s = 1;
|
|
break;
|
|
|
|
case Opt_v:
|
|
verbose = 1;
|
|
break;
|
|
|
|
case Opt_blk:
|
|
case Opt_chr:
|
|
if (--argc == 0)
|
|
missingoptarg(opt);
|
|
ftype = (opt == Opt_blk) ? BLKDEV : CHRDEV;
|
|
src = *++argv;
|
|
break;
|
|
|
|
case Opt_d: /* BSD compat */
|
|
case Opt_dir:
|
|
ftype = DIR;
|
|
if (dstdir == 0)
|
|
dstdir = "";
|
|
makedstdir = 1;
|
|
break;
|
|
|
|
case Opt_fifo:
|
|
ftype = FIFO;
|
|
break;
|
|
|
|
case Opt_ln:
|
|
case Opt_lns:
|
|
if (--argc == 0)
|
|
missingoptarg(opt);
|
|
src = *++argv;
|
|
ftype = (opt == Opt_ln) ? LINK : SYMLINK;
|
|
break;
|
|
|
|
case Opt_src:
|
|
if (--argc == 0 || src)
|
|
missingoptarg(opt);
|
|
src = *++argv;
|
|
break;
|
|
|
|
case Opt_f:
|
|
case Opt_F:
|
|
if (--argc == 0)
|
|
missingoptarg(opt);
|
|
dstdir = *++argv;
|
|
makedstdir = (opt == Opt_F);
|
|
break;
|
|
|
|
case Opt_t:
|
|
trytosymlink = 1;
|
|
break;
|
|
|
|
case Opt_root:
|
|
if (--argc == 0)
|
|
missingoptarg(opt);
|
|
root = *++argv;
|
|
break;
|
|
|
|
case Opt_rawidb:
|
|
if (--argc == 0)
|
|
missingoptarg(opt);
|
|
rawidb = *++argv;
|
|
break;
|
|
|
|
case Opt_new:
|
|
newtimes = 1;
|
|
break;
|
|
|
|
case Opt_c: /* BSD old compatibility mode: no-op */
|
|
break;
|
|
|
|
default:
|
|
fprintf(stderr, "%s: illegal option %s.\n", progname,
|
|
*argv);
|
|
usage();
|
|
}
|
|
}
|
|
/*
|
|
* BSD/GNU compatibility:
|
|
* If we didn't get any SGI specific (and mandatory) flags
|
|
* -f, -F, or -dir, we assume a BSD call:
|
|
*
|
|
* install source... dest
|
|
*
|
|
* where dest can be a directory.
|
|
* The trick here is to accept the BSD invocation and to do
|
|
* the right thing while supporting the SGI idb cool stuff
|
|
* so that you may do a "make install" in a GNU directory
|
|
* and get the IDB file populated correctly.
|
|
*
|
|
* Still this is not 100% BSD compatible where we have
|
|
* a conflict with the SGI version. Here's What is not covered:
|
|
*
|
|
* o -s (for strip) if strip isn't installed just give a warning
|
|
* o long GNU options --version --help
|
|
* o Behavior of BSD in the presence on RAWIDB and SRC
|
|
* env variables may be confusing to those not familiar
|
|
* with them.
|
|
*/
|
|
if (! dstdir && argc >= 2) {
|
|
|
|
char * file1 = filename(argv[0]);
|
|
char * fileN = filename(argv[argc-1]);
|
|
|
|
if (opt_s) {
|
|
if (access(strip_prog, X_OK) != 0) {
|
|
/* probably not installed */
|
|
fprintf(stderr, "%s: warning: "
|
|
"BSD compatibility: strip not found, "
|
|
"won't strip.\n", progname);
|
|
strip_prog = NULL;
|
|
}
|
|
} else
|
|
strip_prog = NULL;
|
|
|
|
if (is_dir(argv[argc-1])) {
|
|
dstdir = argv[argc-1];
|
|
argv[argc-1] = NULL;
|
|
makedstdir = 1;
|
|
argc--;
|
|
oargc--;
|
|
(*argc_p)--;
|
|
|
|
} else if (argc > 2) { /* last arg must be a dir */
|
|
usage();
|
|
|
|
} else if (argc == 2) {
|
|
/* extract directory part of last name */
|
|
|
|
dstdir = target_dir(argv[argc-1]);
|
|
makedstdir = 1;
|
|
argv[argc-1] = fileN; /* ignore dir part */
|
|
|
|
argc--; /* to skip the first arg */
|
|
|
|
/*
|
|
* two cases:
|
|
* filename1 == filename2 (already handled above)
|
|
* filename1 != filename2
|
|
*/
|
|
if (file1 && fileN && strcmp(file1, fileN)) {
|
|
/*
|
|
* filenames are different
|
|
* emulate -src filename1
|
|
*/
|
|
src = *argv++;
|
|
}
|
|
}
|
|
} else { /* not a BSD/GNU call */
|
|
strip_prog = NULL;
|
|
}
|
|
return oargc - argc;
|
|
}
|
|
|
|
/*
|
|
* Option processing tables and option name lookup function.
|
|
*/
|
|
struct sortedopt {
|
|
char **namep;
|
|
enum opt opt;
|
|
} sortedopts[NOPTS] = {
|
|
#define SOI(opt) { &optname(opt), opt }
|
|
SOI(Opt_F),
|
|
SOI(Opt_O),
|
|
SOI(Opt_blk),
|
|
SOI(Opt_c),
|
|
SOI(Opt_chr),
|
|
SOI(Opt_d),
|
|
SOI(Opt_dir),
|
|
SOI(Opt_f),
|
|
SOI(Opt_fifo),
|
|
SOI(Opt_g),
|
|
SOI(Opt_idb),
|
|
SOI(Opt_ln),
|
|
SOI(Opt_lns),
|
|
SOI(Opt_m),
|
|
SOI(Opt_new),
|
|
SOI(Opt_o),
|
|
SOI(Opt_rawidb),
|
|
SOI(Opt_root),
|
|
SOI(Opt_s),
|
|
SOI(Opt_src),
|
|
SOI(Opt_t),
|
|
SOI(Opt_u),
|
|
SOI(Opt_v),
|
|
#undef SOI
|
|
};
|
|
|
|
char optcompat[] = {
|
|
/* m */ 0,
|
|
/* u */ 1,0,
|
|
/* g */ 1,1,0,
|
|
/* idb */ 1,1,1,1,
|
|
/* o */ 1,1,1,1,1,
|
|
/* O */ 1,1,1,1,0,1,
|
|
/* s */ 1,1,1,1,1,1,1,
|
|
/* v */ 1,1,1,1,1,1,1,1,
|
|
/* blk */ 1,1,1,1,1,1,1,1,0,
|
|
/* chr */ 1,1,1,1,1,1,1,1,0,0,
|
|
/* dir */ 1,1,1,1,1,0,1,1,0,0,1,
|
|
/* fifo */ 1,1,1,1,1,1,1,1,0,0,0,1,
|
|
/* ln */ 1,1,1,1,1,1,1,1,0,0,0,0,0,
|
|
/* lns */ 1,1,1,1,1,1,1,1,0,0,0,0,0,0,
|
|
/* src */ 1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,
|
|
/* f */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,
|
|
/* F */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,
|
|
/* t */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
|
/* root */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
|
/* rawidb */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
|
/* new */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
|
/* c */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
|
/* d */ 1,1,1,1,1,0,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1
|
|
/*
|
|
m u g i o O s v b c d f l l s f F t r r n c d
|
|
d l h i i n n r o a e
|
|
b k r r f s c o w w
|
|
o t i
|
|
d
|
|
b
|
|
*/
|
|
};
|
|
|
|
enum opt
|
|
findopt(char *name)
|
|
{
|
|
enum opt opt;
|
|
int low, high, slot;
|
|
|
|
/*
|
|
* Binary search for name in the sorted option name table.
|
|
*/
|
|
opt = Opt_Unknown;
|
|
low = 0;
|
|
high = (sizeof sortedopts / sizeof sortedopts[0]) - 1;
|
|
while (low <= high) {
|
|
int mid, cmp;
|
|
|
|
mid = (low+high) / 2;
|
|
cmp = strcmp(name, *sortedopts[mid].namep);
|
|
if (cmp < 0)
|
|
high = mid - 1;
|
|
else if (cmp > 0)
|
|
low = mid + 1;
|
|
else {
|
|
opt = sortedopts[mid].opt;
|
|
break;
|
|
}
|
|
}
|
|
if (opt == Opt_Unknown)
|
|
return opt;
|
|
/*
|
|
* Check for incompatible or duplicate options. Use high and low
|
|
* to compute a lower triangular matrix index.
|
|
*/
|
|
for (slot = 0; slot < nextoptslot; slot++) {
|
|
low = (int) opt;
|
|
high = (int) opts[slot];
|
|
if (high < low) {
|
|
low = high;
|
|
high = (int) opt;
|
|
}
|
|
if (!optcompat[(high*(high+1))/2 + low]) {
|
|
if (opts[slot] == opt)
|
|
fail("duplicate use of -%s option", name);
|
|
fail("incompatible options -%s and -%s",
|
|
optname(opts[slot]), name);
|
|
}
|
|
}
|
|
if (nextoptslot >= NOPTS)
|
|
fail("too many options (-%s)", name);
|
|
opts[nextoptslot++] = opt;
|
|
return opt;
|
|
}
|
|
|
|
/*
|
|
* Abusage notifiers.
|
|
*/
|
|
void
|
|
usage(void)
|
|
{
|
|
fprintf(stderr, "\
|
|
Usage: %s [-m mode] [-u owner] [-g group] [-idb attribute] [-new]\n\
|
|
[-o] [-O] [-rawidb idbpath] [-root rootpath] [-s] [-t] [-v]\n\
|
|
optionally, one of [-blk maj,min[,off]] [-chr maj,min[,off]] [-dir]\n\
|
|
[-fifo] [-ln path] [-lns path] [-src path]\n\
|
|
one of [-f dir] [-F dir] (mandatory unless -dir is used)\n\
|
|
file1 file2 ... fileN (pathnames of files to install)\n\
|
|
\n\
|
|
BSD/GNU compatibility (if none of -f, -F, or -dir was given):\n\
|
|
%s [options] file1 file2\n\
|
|
%s [options] files... directory\n\
|
|
-c: ignored.\n\
|
|
-d: equivalent to -dir\n\
|
|
-s: strip target (if strip is installed)\n\
|
|
",
|
|
progname, progname, progname);
|
|
|
|
exit(-1);
|
|
}
|
|
|
|
void
|
|
missingoptarg(enum opt opt)
|
|
{
|
|
fprintf(stderr, "%s: missing argument for -%s option.\n", progname,
|
|
optname(opt));
|
|
usage();
|
|
}
|
|
|
|
void
|
|
badoptarg(enum opt opt, char *arg)
|
|
{
|
|
fail("malformed argument %s for -%s option", arg, optname(opt));
|
|
}
|
|
|
|
/*
|
|
* Convert mode/user/group to integer.
|
|
*/
|
|
int
|
|
mtoi(char *s, int *ip)
|
|
{
|
|
return sscanf(s, "%o", ip);
|
|
}
|
|
|
|
int
|
|
utoi(char *s, int *ip)
|
|
{
|
|
struct passwd *pw;
|
|
|
|
pw = getpwnam(s);
|
|
if (pw) {
|
|
*ip = pw->pw_uid;
|
|
return 1;
|
|
}
|
|
return sscanf(s, "%d", ip);
|
|
}
|
|
|
|
int
|
|
gtoi(char *s, int *ip)
|
|
{
|
|
struct group *gr;
|
|
|
|
gr = getgrnam(s);
|
|
if (gr) {
|
|
*ip = gr->gr_gid;
|
|
return 1;
|
|
}
|
|
return sscanf(s, "%d", ip);
|
|
}
|
|
|
|
/*
|
|
* Set default values from effective ids.
|
|
*/
|
|
void
|
|
initowner(struct value *vp)
|
|
{
|
|
int uid;
|
|
struct passwd *pw;
|
|
static char buf[8];
|
|
|
|
uid = geteuid();
|
|
pw = getpwuid(uid);
|
|
if (pw) {
|
|
vp->name = pw->pw_name;
|
|
vp->number = pw->pw_uid;
|
|
return;
|
|
}
|
|
sprintf(buf, "%d", uid);
|
|
vp->name = buf;
|
|
vp->number = uid;
|
|
}
|
|
|
|
void
|
|
initgroup(struct value *vp)
|
|
{
|
|
int gid;
|
|
struct group *gr;
|
|
static char buf[8];
|
|
|
|
gid = getegid();
|
|
gr = getgrgid(gid);
|
|
if (gr) {
|
|
vp->name = gr->gr_name;
|
|
vp->number = gr->gr_gid;
|
|
return;
|
|
}
|
|
sprintf(buf, "%d", gid);
|
|
vp->name = buf;
|
|
vp->number = gid;
|
|
}
|
|
|
|
/*
|
|
* Create a directory ancestor line.
|
|
*/
|
|
void
|
|
makedirpath(char *name, struct value *owner, struct value *group)
|
|
{
|
|
struct stat64 sb;
|
|
char *lastslash;
|
|
|
|
lastslash = strrchr(name, '/');
|
|
if (lastslash == 0 || lastslash == name)
|
|
return;
|
|
*lastslash = '\0';
|
|
if (stat64(name, &sb) == 0) {
|
|
if (S_ISDIR(sb.st_mode)) {
|
|
*lastslash = '/';
|
|
return;
|
|
}
|
|
errno = EEXIST;
|
|
sfail("can't create directory %s", name);
|
|
}
|
|
makedirpath(name, owner, group);
|
|
install(DIR, 0755, owner, group, name, 0);
|
|
*lastslash = '/';
|
|
}
|
|
|
|
/*
|
|
* Return target directory for install:
|
|
* if argument is a directory - return it,
|
|
* Otherwise, extract the directory part of the name in a non-destructive way
|
|
* (unlike dirname(3G)) and return it. Used for BSD compatibility.
|
|
*/
|
|
int is_dir(char * name)
|
|
{
|
|
struct stat64 sb;
|
|
|
|
return (stat64(name, &sb) == 0 && S_ISDIR(sb.st_mode)) ? 1 : 0;
|
|
}
|
|
|
|
char *
|
|
target_dir(char * name)
|
|
{
|
|
char *lastslash;
|
|
char *dirname;
|
|
|
|
/* arg is directory ? */
|
|
if (is_dir(name)) {
|
|
return name;
|
|
}
|
|
|
|
/* filename */
|
|
dirname = strdup(name);
|
|
lastslash = strrchr(dirname, '/');
|
|
if (lastslash == 0) { /* no slashes, assume current dir */
|
|
return ".";
|
|
}
|
|
|
|
*lastslash = '\0';
|
|
return dirname;
|
|
}
|
|
|
|
char *
|
|
filename(char * name)
|
|
{
|
|
char *lastslash;
|
|
|
|
/* arg is directory ? */
|
|
if (is_dir(name))
|
|
return NULL;
|
|
|
|
lastslash = strrchr(name, '/');
|
|
return (lastslash == NULL) ? name : lastslash + 1;
|
|
}
|
|
|
|
|
|
int removeold(char *dst, mode_t mode);
|
|
int renameold(char *dst);
|
|
struct devname;
|
|
void devname(char *magic, struct devname *dn);
|
|
void dtoi(char *s, char **p, int *ip);
|
|
void makedevice(struct stat64 *dsb, int mode, struct value *owner,
|
|
struct value *group, char *dst, char *src);
|
|
void setowner(char *dst, struct value *owner, struct value *group);
|
|
void lsetowner(char *dst, struct value *owner, struct value *group);
|
|
|
|
/*
|
|
* Install src of type ftype as dst with attributes mode/owner/group.
|
|
*/
|
|
void
|
|
install(enum ftype ftype, int mode, struct value *owner, struct value *group,
|
|
char *dst, char *src)
|
|
{
|
|
struct stat64 sb;
|
|
struct stat64 *dsb;
|
|
char abspath[MAXPATHLEN];
|
|
static char cwd[MAXPATHLEN];
|
|
|
|
/*
|
|
* Implement the -O and -o options if the target node exists.
|
|
* If the target exists and can't be removed, dsb points to its
|
|
* attributes. Otherwise dsb is null.
|
|
*/
|
|
if (lstat64(dst, &sb) == 0
|
|
&& (!trytoremoveold || removeold(dst, sb.st_mode) < 0)
|
|
&& (!saveold || renameold(dst) < 0)) {
|
|
dsb = &sb;
|
|
} else
|
|
dsb = 0;
|
|
|
|
/*
|
|
* Implement the -t option to optimize regular file installation
|
|
* with a symbolic link.
|
|
*/
|
|
if (trytosymlink && ftype == REG) {
|
|
ftype = SYMLINK;
|
|
if (src[0] != '/') {
|
|
if (cwd[0] == '\0' && getwd(cwd) == 0)
|
|
fail(cwd);
|
|
sprintf(abspath, "%s/%s", cwd, src);
|
|
src = abspath;
|
|
}
|
|
}
|
|
|
|
switch (ftype) {
|
|
case REG: {
|
|
int from, to, cc, wc;
|
|
char buf[BUFSIZ];
|
|
|
|
from = open(src, O_RDONLY);
|
|
if (from < 0)
|
|
sfail("can't open %s", src);
|
|
|
|
/*
|
|
* If dst exists and is not a regular file, try to remove it.
|
|
*/
|
|
if (dsb && !S_ISREG(dsb->st_mode)
|
|
&& removeold(dst, dsb->st_mode) == 0) {
|
|
dsb = 0;
|
|
}
|
|
|
|
/*
|
|
* If dst exists, check for a non-privileged attempt to
|
|
* overwrite a readonly file.
|
|
*/
|
|
if (dsb && !superuser
|
|
&& (!S_ISLNK(dsb->st_mode) || stat64(dst, dsb) == 0)
|
|
&& dsb->st_uid == owner->number
|
|
&& !(dsb->st_mode & S_IWRITE)) {
|
|
/*
|
|
* Perhaps we cannot unlink dst because we aren't
|
|
* allowed to write in the target directory; try to
|
|
* acquire write permission on the file.
|
|
*/
|
|
dsb->st_mode |= S_IWRITE;
|
|
if (chmod(dst, dsb->st_mode) < 0)
|
|
sfail("can't overwrite %s", dst);
|
|
}
|
|
|
|
/*
|
|
* Truncate after overwriting dst rather than opening with
|
|
* O_TRUNC, just in cast dst is identical to src.
|
|
*/
|
|
to = open(dst, O_WRONLY|O_CREAT, mode|S_IWRITE);
|
|
if (to < 0)
|
|
sfail("can't create %s", dst);
|
|
|
|
while ((cc = read(from, buf, sizeof buf)) > 0) {
|
|
wc = write(to, buf, cc);
|
|
if (wc < 0)
|
|
sfail("error writing %s", dst);
|
|
if (wc != cc)
|
|
fail("short write to %s", dst);
|
|
}
|
|
if (cc < 0)
|
|
sfail("error reading %s", src);
|
|
|
|
/*
|
|
* Truncate to size of from, according to tell(from).
|
|
*/
|
|
if (ftruncate(to, lseek(from, 0L, 1)) < 0)
|
|
sfail("can't truncate %s", dst);
|
|
close(from);
|
|
|
|
/*
|
|
* Remove the write permission bit we added when creating
|
|
* or (possibly) prior to opening.
|
|
*/
|
|
if (fchmod(to, mode) < 0)
|
|
sfail("can't change mode of %s to %#o", dst, mode);
|
|
close(to);
|
|
|
|
/*
|
|
* BSD compatibility and -s: do strip
|
|
* Don't use system(3) since this is a setuid prog.
|
|
*/
|
|
if (strip_prog) {
|
|
pid_t pid;
|
|
|
|
switch (pid = fork()) {
|
|
case -1:
|
|
sfail("can't fork for stripping %s", dst);
|
|
case 0:
|
|
execl(strip_prog, "strip", dst, (char *)0);
|
|
sfail("can't exec %s", strip_prog);
|
|
}
|
|
/*
|
|
* Parent: wait for child/strip to complete
|
|
* I don't check the status because strip will
|
|
* complain nicely if the file is not ELF etc.
|
|
*/
|
|
waitpid(pid, (int *)0, 0);
|
|
}
|
|
|
|
/*
|
|
* Set dst's timestamps to match src's.
|
|
*/
|
|
if (!newtimes) {
|
|
struct stat64 ssb;
|
|
struct utimbuf utb;
|
|
|
|
if (stat64(src, &ssb) < 0)
|
|
sfail("can't get attributes for %s", src);
|
|
utb.actime = ssb.st_atime;
|
|
utb.modtime = ssb.st_mtime;
|
|
if (utime(dst, &utb))
|
|
sfail("can't set times for %s", dst);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Device cases return early because they exclusively create one or
|
|
* more targets and set owner and group for each node.
|
|
*/
|
|
case BLKDEV:
|
|
makedevice(dsb, S_IFBLK | mode, owner, group, dst, src);
|
|
return;
|
|
|
|
case CHRDEV:
|
|
makedevice(dsb, S_IFCHR | mode, owner, group, dst, src);
|
|
return;
|
|
|
|
case FIFO:
|
|
if (dsb && !S_ISDIR(dsb->st_mode) && unlink(dst) < 0)
|
|
sfail("can't unlink target node %s", dst);
|
|
dsb = 0;
|
|
if (mknod(dst, S_IFIFO | mode, NODEV) < 0)
|
|
sfail("can't create pipe %s", dst);
|
|
break;
|
|
|
|
case DIR:
|
|
if ((dsb == 0 || !S_ISDIR(dsb->st_mode))
|
|
&& mkdir(dst, mode) < 0) {
|
|
sfail("can't make directory %s", dst);
|
|
}
|
|
break;
|
|
|
|
/*
|
|
* Link and symlink return early to avoid following the link when
|
|
* setting attributes.
|
|
*/
|
|
case LINK:
|
|
if (dsb && !S_ISDIR(dsb->st_mode)) {
|
|
struct stat64 ssb;
|
|
|
|
if (lstat64(src, &ssb) == 0
|
|
&& ssb.st_dev == dsb->st_dev
|
|
&& ssb.st_ino == dsb->st_ino) {
|
|
return;
|
|
}
|
|
unlink(dst);
|
|
}
|
|
if (link(src, dst) < 0)
|
|
sfail("can't make hard link %s to %s", dst, src);
|
|
return;
|
|
|
|
case SYMLINK:
|
|
if (dsb && !S_ISDIR(dsb->st_mode)) {
|
|
if (S_ISLNK(dsb->st_mode)) {
|
|
register int cc;
|
|
char val[MAXPATHLEN];
|
|
|
|
cc = readlink(dst, val, sizeof val - 1);
|
|
if (cc >= 0) {
|
|
val[cc] = '\0';
|
|
if (!strcmp(val, src))
|
|
return;
|
|
}
|
|
} else {
|
|
struct stat64 ssb;
|
|
|
|
if (lstat64(src, &ssb) == 0
|
|
&& ssb.st_dev == dsb->st_dev
|
|
&& ssb.st_ino == dsb->st_ino
|
|
&& ssb.st_nlink == 1) {
|
|
return;
|
|
}
|
|
}
|
|
unlink(dst);
|
|
}
|
|
if (symlink(src, dst) < 0)
|
|
sfail("can't make symbolic link %s to %s", dst, src);
|
|
else
|
|
lsetowner(dst, owner, group);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Set target owner and group. We don't need to set mode because
|
|
* our umask was set to 0 early in main.
|
|
*/
|
|
if (dsb == 0
|
|
|| dsb->st_uid != owner->number
|
|
|| dsb->st_gid != group->number) {
|
|
setowner(dst, owner, group);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A wrapper for unlink or rmdir, depending on inode format.
|
|
*/
|
|
int
|
|
removeold(char *dst, mode_t mode)
|
|
{
|
|
return S_ISDIR(mode) ? rmdir(dst) : unlink(dst);
|
|
}
|
|
|
|
/*
|
|
* A wrapper for rename that formulates the "old" name used by -o.
|
|
*/
|
|
int
|
|
renameold(char *dst)
|
|
{
|
|
char *lastslash;
|
|
char saved[MAXPATHLEN];
|
|
|
|
lastslash = strrchr(dst, '/');
|
|
assert(lastslash);
|
|
*lastslash++ = '\0';
|
|
sprintf(saved, "%s/OLD%s", dst, lastslash);
|
|
*--lastslash = '/';
|
|
return rename(dst, saved);
|
|
}
|
|
|
|
/*
|
|
* Structure for one or a range of minor devices.
|
|
*/
|
|
struct devname {
|
|
int maj; /* major device number */
|
|
int min; /* minor device number or first if range */
|
|
int lastmin; /* last minor if range, -1 otherwise */
|
|
int offset; /* added to minor when naming nodes in range */
|
|
};
|
|
|
|
void
|
|
devname(char *magic, struct devname *dn)
|
|
{
|
|
char *low, *high;
|
|
|
|
dtoi(magic, &low, &dn->maj);
|
|
if (*low != ',')
|
|
fail("comma expected in device name %s", magic);
|
|
dtoi(low + 1, &high, &dn->min);
|
|
if (*high == '-')
|
|
dtoi(high + 1, &high, &dn->lastmin);
|
|
else
|
|
dn->lastmin = -1;
|
|
if (*high == ',')
|
|
dtoi(high + 1, 0, &dn->offset);
|
|
else
|
|
dn->offset = 0;
|
|
}
|
|
|
|
void
|
|
dtoi(char *s, char **p, int *ip)
|
|
{
|
|
unsigned long ul;
|
|
char *cp;
|
|
|
|
ul = strtoul(s, &cp, 0);
|
|
if (ul == 0 && cp == s)
|
|
fail("malformed device number %s", s);
|
|
if (p)
|
|
*p = cp;
|
|
*ip = (int) ul;
|
|
}
|
|
|
|
void makedevnode(struct stat64 *dsb, int mode, struct value *owner,
|
|
struct value *group, char *dst, dev_t rdev);
|
|
|
|
/*
|
|
* Make a special filesystem node named dst with the given inode format,
|
|
* mode, and magic device name (src).
|
|
*/
|
|
void
|
|
makedevice(struct stat64 *dsb, int mode, struct value *owner,
|
|
struct value *group, char *dst, char *src)
|
|
{
|
|
struct devname dn;
|
|
int min;
|
|
|
|
devname(src, &dn);
|
|
if (dn.lastmin < 0) {
|
|
makedevnode(dsb, mode, owner, group, dst,
|
|
makedev(dn.maj, dn.min));
|
|
return;
|
|
}
|
|
for (min = dn.min; min <= dn.lastmin; min++) {
|
|
char devpath[MAXPATHLEN];
|
|
struct stat64 sb;
|
|
|
|
sprintf(devpath, "%s%d", dst, min + dn.offset);
|
|
dsb = (lstat64(devpath, &sb) < 0) ? 0 : &sb;
|
|
makedevnode(dsb, mode, owner, group, devpath,
|
|
makedev(dn.maj, min));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Note that this routine now leaves an existing device alone if it has the
|
|
* specified device type and number. It does NOT check or change the owner
|
|
* or mode, since the main use of this is to leave existing tty's, etc. with
|
|
* the owners and modes that the system manager has set them to.
|
|
*/
|
|
void
|
|
makedevnode(struct stat64 *dsb, int mode, struct value *owner,
|
|
struct value *group, char *dst, dev_t rdev)
|
|
{
|
|
if (dsb) {
|
|
if (dsb->st_rdev == rdev
|
|
&& (dsb->st_mode & S_IFMT) == (mode & S_IFMT)) {
|
|
return;
|
|
}
|
|
if (unlink(dst) < 0)
|
|
sfail("can't unlink target node %s", dst);
|
|
}
|
|
if (mknod(dst, mode, rdev) < 0)
|
|
sfail("can't create device %s", dst);
|
|
setowner(dst, owner, group);
|
|
}
|
|
|
|
void
|
|
setowner(char *dst, struct value *owner, struct value *group)
|
|
{
|
|
if (chown(dst, owner->number, group->number) < 0) {
|
|
sfail("can't change ownership of %s to %s.%s",
|
|
dst, owner->name, group->name);
|
|
}
|
|
}
|
|
|
|
void
|
|
lsetowner(char *dst, struct value *owner, struct value *group)
|
|
{
|
|
if (lchown(dst, owner->number, group->number) < 0) {
|
|
sfail("can't change symlink ownership of %s to %s.%s",
|
|
dst, owner->name, group->name);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Idb construction variables and procedures.
|
|
*/
|
|
FILE *rawidbf;
|
|
char srcbase[MAXPATHLEN];
|
|
|
|
void
|
|
idbopen(char *name)
|
|
{
|
|
char *base, *dir, *lastdir;
|
|
|
|
rawidbf = fopen(name, "a");
|
|
if (rawidbf == 0)
|
|
sfail("can't open idb %s", name);
|
|
if (lockf(fileno(rawidbf), F_LOCK, 0) < 0)
|
|
sfail("can't lock idb %s", name);
|
|
|
|
if (getwd(srcbase) == 0)
|
|
fail(srcbase);
|
|
base = getenv("SRC");
|
|
if (base) {
|
|
int len = strlen(base);
|
|
|
|
if (!strncmp(base, srcbase, len)) {
|
|
if (srcbase[len] != '\0')
|
|
strcpy(srcbase, srcbase + len + 1);
|
|
else
|
|
srcbase[0] = '\0';
|
|
if (srcbase[0] == '\0')
|
|
strcpy(srcbase, ".");
|
|
return;
|
|
}
|
|
}
|
|
|
|
base = srcbase;
|
|
lastdir = ".";
|
|
while ((dir = strtok(base, "/")) != 0) {
|
|
if (!strcmp(dir, "src")) {
|
|
dir += 4;
|
|
if (*dir)
|
|
strcpy(srcbase, dir + strspn(dir, "/"));
|
|
else
|
|
strcpy(srcbase, ".");
|
|
return;
|
|
}
|
|
base = 0;
|
|
lastdir = dir;
|
|
}
|
|
fail("can't find 'src' directory above %s", lastdir);
|
|
}
|
|
|
|
void
|
|
idbclose(char *name)
|
|
{
|
|
if (fclose(rawidbf) == EOF)
|
|
sfail("error writing idb %s", name);
|
|
}
|
|
|
|
void
|
|
idbwrite(enum ftype ftype, char *mode, char *owner, char *group,
|
|
char *dst, char *src, int tagc, char **tagv)
|
|
{
|
|
int min;
|
|
struct devname dn;
|
|
|
|
dst += strspn(dst, "/");
|
|
if (*dst == '\0')
|
|
dst = "."; /* idb-ish for the root directory */
|
|
|
|
switch (ftype) {
|
|
case REG:
|
|
fprintf(rawidbf, "f %s %s %s %s %s/%s",
|
|
mode, owner, group, dst, srcbase, src);
|
|
break;
|
|
|
|
case BLKDEV:
|
|
case CHRDEV:
|
|
devname(src, &dn); /* validate device name */
|
|
if (dn.lastmin < 0) {
|
|
fprintf(rawidbf, "%c %s %s %s %s %s dev(%d,%d)",
|
|
(ftype == BLKDEV) ? 'b' : 'c',
|
|
mode, owner, group, dst, srcbase,
|
|
dn.maj, dn.min);
|
|
break;
|
|
}
|
|
for (min = dn.min; min <= dn.lastmin; min++) {
|
|
fprintf(rawidbf, "%c %s %s %s %s%d %s dev(%d,%d)",
|
|
(ftype == BLKDEV) ? 'b' : 'c',
|
|
mode, owner, group, dst, min, srcbase,
|
|
dn.maj, min);
|
|
if (min < dn.lastmin)
|
|
putc('\n', rawidbf);
|
|
}
|
|
break;
|
|
|
|
case DIR:
|
|
case FIFO:
|
|
fprintf(rawidbf, "%c %s %s %s %s %s",
|
|
(ftype == DIR) ? 'd' : 'p',
|
|
mode, owner, group, dst, srcbase);
|
|
break;
|
|
|
|
case LINK:
|
|
case SYMLINK:
|
|
fprintf(rawidbf, "%c %s %s %s %s %s %s(%s)",
|
|
(ftype == LINK) ? 'h' : 'l',
|
|
mode, owner, group, dst, srcbase,
|
|
(ftype == LINK) ? "links" : "symval",
|
|
src);
|
|
}
|
|
|
|
while (--tagc >= 0)
|
|
fprintf(rawidbf, " %s", *tagv++);
|
|
putc('\n', rawidbf);
|
|
}
|
|
|
|
/*
|
|
* Fatal error reporters: sfail is for system call failure.
|
|
*/
|
|
void
|
|
fail(char *format, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
fprintf(stderr, "%s: ", progname);
|
|
vfprintf(stderr, format, ap);
|
|
va_end(ap);
|
|
fprintf(stderr, ".\n");
|
|
exit(-1);
|
|
}
|
|
|
|
void
|
|
sfail(char *format, ...)
|
|
{
|
|
int error;
|
|
va_list ap;
|
|
|
|
error = errno;
|
|
va_start(ap, format);
|
|
fprintf(stderr, "%s: ", progname);
|
|
vfprintf(stderr, format, ap);
|
|
va_end(ap);
|
|
assert(0 < error && error < sys_nerr);
|
|
fprintf(stderr, ": %s.\n", strerror(error));
|
|
exit(error);
|
|
}
|