1
0
Files
irix-657m-src/eoe/cmd/tlink/tlink.c
2022-09-29 17:59:04 +03:00

967 lines
20 KiB
C

/*
* Copyright 1989 Silicon Graphics, Inc. All rights reserved.
* $Revision: 1.24 $
*
* NAME
* tlink - clone a file tree via symbolic links
* SYNOPSIS
* tlink [-chnrvX] [-d name] [-x name] source target [path ...]
* DESCRIPTION
* See tlink(1).
* AUTHOR
* Brendan Eich, 01/14/87
* NOTE
* Relative pathname manipulation assumes overlapping bcopy().
*/
#include <assert.h>
#include <bstring.h>
#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <sys/stat.h>
#include <sys/wait.h>
/*
* A pathname structure. PATH_MAX is the maximum path length including
* the terminating 0. The number of significant components is kept for
* relative symbolic link formulation.
*/
struct path {
char name[PATH_MAX];
unsigned short len;
unsigned short components;
struct dirstack *dirstack;
struct relpath *toward;
};
struct dirstack {
unsigned short kids;
unsigned short pruned;
struct dirstack *parent;
};
struct relpath {
struct path path;
struct path *to;
};
/*
* Call path() with a filename pname to initialize a path struct *pp.
* Add a component to a path with push(); remove it with pop().
*/
void path(char *pname, struct path *pp);
void push(struct path *pp, char *name, int namlen);
void pop(struct path *pp, int namlen);
/*
* A limit on the number of file descriptors to keep open during tree-linking.
* Since we use depth-first search, the limit corresponds to the depth of the
* tree at which we begin to close and re-open a directory's descriptor each
* time we descend into one of its children.
*/
int maxdirfd;
int curdirfd;
/*
* A list of regular expressions describing filenames to avoid.
*/
#define MAXBADPAT 200
char *badbuiltin[] = {
"^RCS$",
"^.*,v$",
0
};
char *badpat[MAXBADPAT+1];
char *baddirpat[MAXBADPAT+1];
/*
* Compile expr into patvec[index] unless index exceeds patveclen, in which
* case issue an error message saying "too many <toomany>: <expr> ignored",
* where <toomany> and <expr> stand for the pointed-at strings.
*/
void setpat(char *expr, int index, char *patvec[], int patveclen,
char *toomany);
/*
* With the -d option, we maintain a vector of regular expressions naming
* directories to link into the target tree.
*/
#define MAXDIRLINK 8
char *dirlink[MAXDIRLINK+1];
int (*linkfun)(const char *, const char *) = symlink;
/* -h: make hard rather than symbolic links */
int null_effect; /* -n: don't create anything */
int verbose; /* -v: spit out created pathnames */
int relative; /* -r: create relative symbolic links */
/* error handling utilities */
char *progname;
int status;
void message(char *format, ...);
void perrorf(char *format, ...);
void perrnof(int error, char *format, ...);
void sfail(char *format, ...);
void fail(char *format, ...);
enum opmode { CLONE, CLEAN, PRUNE };
void treelink(struct path *fp, struct path *tp, enum opmode mode);
int
main(argc, argv)
int argc;
char **argv;
{
int npat, ndirpat, ndirlink, opt;
enum opmode mode;
struct path source, target;
/*
* Initialize.
*/
progname = *argv;
(void) fclose(stdin);
maxdirfd = getdtablesize() - 1;
for (npat = 0; badbuiltin[npat]; npat++)
badpat[npat] = regcmp(badbuiltin[npat], (char *) 0);
ndirpat = 0;
ndirlink = 0;
/*
* Process options.
*/
mode = CLONE;
while ((opt = getopt(argc, argv, "cd:hnprvx:X")) != EOF) {
switch (opt) {
case 'c':
mode = CLEAN;
break;
case 'd':
setpat(optarg, ndirlink++, dirlink, MAXDIRLINK,
"directories to link (-d)");
break;
case 'h':
linkfun = link;
break;
case 'n':
null_effect = 1;
break;
case 'p':
mode = PRUNE;
break;
case 'r':
relative = 1;
break;
case 'v':
verbose = 1;
break;
case 'X':
npat = 0;
break;
case 'x':
if (strchr(optarg, '/')) {
setpat(optarg, ndirpat++, baddirpat, MAXBADPAT,
"pathnames to exclude (-x)");
} else {
setpat(optarg, npat++, badpat, MAXBADPAT,
"filenames to exclude (-x)");
}
break;
default:
goto usage;
}
}
argc -= optind;
if (argc < 2)
goto usage;
argv += optind;
if (npat <= MAXBADPAT)
badpat[npat] = 0;
/*
* Construct the path or restrictive sub-paths and call treelink().
*/
path(argv[0], &source);
path(argv[1], &target);
if (argc == 2)
treelink(&source, &target, mode);
else {
argc -= 2, argv += 2;
while (--argc >= 0) {
int arglen;
arglen = strlen(*argv);
push(&source, *argv, arglen);
push(&target, *argv, arglen);
treelink(&source, &target, mode);
pop(&source, arglen);
pop(&target, arglen);
argv++;
}
}
return status;
usage:
/*
* Complain about abusage.
*/
fprintf(stderr,
"usage: %s [-cnprvX] [-d name] [-x name] source target [path ...]\n",
progname);
return -1;
}
void
setpat(char *expr, int index, char *patvec[], int patveclen, char *toomany)
{
if (index >= patveclen) {
message("too many %s: %s ignored\n", toomany, expr);
return;
}
patvec[index] = regcmp(expr, (char *) 0);
}
int
makedir(char *name, int mode)
{
if (!null_effect && mkdir(name, mode|S_IWRITE) < 0)
return -1;
if (verbose)
printf("%s\n", name);
return 0;
}
int
makelink(struct path *sp, char *target)
{
char *source;
if (relative && linkfun == symlink) {
assert(sp->toward && sp->toward->to == sp);
source = sp->toward->path.name;
} else
source = sp->name;
if (!null_effect && (*linkfun)(source, target) < 0)
return -1;
if (verbose)
printf("%s\n", target);
return 0;
}
void fixpath(char *pname, struct path *pp);
char *getwd(char *buf);
/*
* Construct in *pp an absolute canonical path to pname.
*/
void
path(char *pname, struct path *pp)
{
char cwd[PATH_MAX];
struct path subdir;
pp->dirstack = 0;
pp->toward = 0;
if (pname[0] == '/') {
fixpath(pname, pp);
return;
}
/* on error, getwd puts a message in its result parameter */
if (getwd(cwd) == 0)
sfail(cwd);
fixpath(cwd, pp);
fixpath(pname, &subdir);
if (subdir.len > 0) {
if (pp->len + 1 + subdir.len >= PATH_MAX)
fail("pathname %s/%s too long", pp->name, subdir.name);
pp->name[pp->len++] = '/';
bcopy(subdir.name, pp->name + pp->len, subdir.len + 1);
pp->len += subdir.len;
pp->components += subdir.components;
}
}
/*
* Canonicalize pathname pb into the path structure *pp by collapsing /./
* into / and removing extra slashes. Compute canonical length and number
* of components; fail if pathname is too long.
*/
void
fixpath(char *pb, struct path *pp)
{
char *pb0, *pa; /* b for before, a for after */
pa = pp->name;
pb0 = pb;
pp->components = 0;
do {
while (*pb == '.'
&& (pb == pb0 || pb[-1] == '/')
&& (pb[1] == '\0' || pb[1] == '/')) {
if (pb[1] == '\0') {
if (pb > pb0)
--pa; /* elide /. */
pb++;
} else
pb += 2; /* elide ./ */
}
if (pa - pp->name >= PATH_MAX)
fail("pathname %s too long", pb0);
*pa++ = *pb;
if ((*pb == '\0' || *pb == '/') && pb > pb0)
pp->components++;
if (*pb == '/') {
while (*++pb == '/') /* compress ///s */
;
--pb;
}
} while (*pb++ != '\0');
pp->len = pa - pp->name - 1;
}
void
push(struct path *pp, char *name, int namlen)
{
int adjslash; /* character delta for slash separator */
if (pp->name[pp->len-1] == '/')
adjslash = (name[0] == '/') ? -1 : 0;
else
adjslash = (name[0] != '/') ? 1 : 0;
if (pp->len + adjslash + namlen >= PATH_MAX)
fail("pathname %s/%s too long", pp->name, name);
if (adjslash > 0)
pp->name[pp->len++] = '/';
else if (adjslash < 0)
name++, --namlen;
(void) strncpy(&pp->name[pp->len], name, namlen);
pp->len += namlen;
pp->name[pp->len] = '\0';
pp->components++;
if (pp->toward)
push(&pp->toward->path, name, namlen);
}
void
pop(struct path *pp, int namlen)
{
pp->len -= namlen;
if (pp->name[pp->len] != '/') {
--pp->len;
assert(pp->name[pp->len] == '/');
}
pp->name[pp->len] = '\0';
--pp->components;
if (pp->toward)
pop(&pp->toward->path, namlen);
}
/*
* Enter the directory named absolutely by pp, relative to the current
* directory by name.
*/
char cdfail[] = "cannot change directory to %s";
void
down(struct path *pp, char *name)
{
if (chdir(name) < 0)
sfail(cdfail, pp->name);
if (pp->toward) {
pp = &pp->toward->path;
if (3 + pp->len >= PATH_MAX)
fail("relative pathname ../%s too long", pp->name);
bcopy(pp->name, pp->name + 3, pp->len + 1);
bcopy("../", pp->name, 3);
pp->len += 3;
pp->components++;
}
}
/*
* Leave subdirectory pp, returning to its parent.
*/
void
up(struct path *pp)
{
if (chdir("..") < 0)
sfail("cannot change directory from %s to ..", pp->name);
if (pp->toward) {
pp = &pp->toward->path;
assert(pp->name[0]=='.'&&pp->name[1]=='.'&&pp->name[2]=='/');
bcopy(pp->name + 3, pp->name, pp->len - 2);
pp->len -= 3;
--pp->components;
}
}
/*
* Compute in rp the relative pathname up from the uncommon part of fp and
* down to the uncommon part of tp (fp and tp cannot be identical).
*/
void
relatepaths(struct path *fp, struct path *tp, struct relpath *rp)
{
char *f, *t, *downto;
int ncommon, ndotdot, downtolen;
assert(fp->name[0] == '/' && tp->name[0] == '/');
rp->path.toward = 0;
downto = tp->name + 1;
ncommon = 0;
for (f = fp->name + 1, t = downto; *f == *t; f++, t++) {
assert(*f != '\0');
if (*f == '/') {
downto = t + 1;
ncommon++;
}
}
ndotdot = fp->components - ncommon;
rp->path.components = ndotdot + (tp->components - ncommon);
downtolen = tp->len - (downto - tp->name);
rp->path.len = 3 * ndotdot + downtolen;
if (rp->path.len >= PATH_MAX) {
fail("relative pathname from %s to %s too long",
fp->name, tp->name);
}
t = rp->path.name;
while (--ndotdot >= 0) { /* build the "upfrom" path */
*t++ = '.';
*t++ = '.';
*t++ = '/';
}
assert(t + downtolen == rp->path.name + rp->path.len);
bcopy(downto, t, downtolen + 1);
tp->toward = rp;
rp->to = tp;
}
#ifdef NOTDEF
void
unrelatepaths(struct relpath *rp)
{
assert(rp->to->toward == rp);
rp->to->toward = 0;
rp->to = 0;
}
#endif
char *
tail(struct path *pp)
{
char *cp;
for (cp = pp->name+pp->len-1; *cp != '/'; --cp) {
if (cp == pp->name)
return cp; /* no slash in pp */
}
return cp + 1;
}
int maketargetroot(char *name, int mode);
int treewalk(struct path *fp, struct path *tp,
int (*nodefun)(struct path *, struct path *));
int clean(struct path *, struct path *);
int prune(struct path *, struct path *);
int clone(struct path *, struct path *);
#define SAME_INODE(sb1,sb2) ((sb1).st_dev == (sb2).st_dev && \
(sb1).st_ino == (sb2).st_ino)
void
treelink(struct path *sp, struct path *tp, enum opmode mode)
{
int perm;
struct stat ssb, tsb;
struct relpath rp;
if (stat(sp->name, &ssb) < 0 || !S_ISDIR(ssb.st_mode))
fail("source %s is not a directory", sp->name);
perm = ssb.st_mode & ~S_IFMT;
if (stat(tp->name, &tsb) < 0) {
if (mode != CLONE)
fail("target %s does not exist", tp->name);
if (maketargetroot(tp->name, perm) < 0)
sfail("cannot create target directory %s", tp->name);
} else if (!S_ISDIR(tsb.st_mode)) {
fail("target %s is not a directory", tp->name);
} else if (SAME_INODE(ssb, tsb)) {
fail("%s and %s are identical", sp->name, tp->name);
}
if (mode == CLONE) {
if (relative)
relatepaths(tp, sp, &rp);
if (chdir(sp->name) < 0)
sfail(cdfail, sp->name);
(void) treewalk(sp, tp, clone);
} else {
if (relative)
relatepaths(sp, tp, &rp);
if (chdir(tp->name) < 0)
sfail(cdfail, tp->name);
(void) treewalk(tp, sp, (mode == PRUNE) ? prune : clean);
}
#ifdef NOTDEF
if (relative)
unrelatepaths(&rp);
#endif
}
int
maketargetroot(char *name, int mode)
{
char *lastslash;
lastslash = strrchr(name, '/');
if (lastslash) {
struct stat sb;
*lastslash = '\0';
if (stat(name, &sb) < 0) {
if (maketargetroot(name, mode) < 0)
return -1;
}
*lastslash = '/';
}
return makedir(name, mode);
}
int match(char *name, char **revec);
int removetree(char *name);
/*
* Walk a tree rooted at the name in fp, maintaining a parallel name in tp.
* Our caller must have chdir'd into fp. Return 1 if the directory named by
* fp should be removed, 0 otherwise.
*/
int
treewalk(struct path *fp, struct path *tp,
int (*pf)(struct path *, struct path *))
{
DIR *dirp;
off_t offset;
struct dirstack fds;
int remove;
char **rep;
dirp = 0;
offset = 0;
fds.kids = fds.pruned = 0;
fds.parent = fp->dirstack;
fp->dirstack = &fds;
for (;;) {
dirent_t *dp;
int namlen;
if (dirp == 0) {
dirp = opendir(".");
if (dirp == 0) {
sfail("cannot open current directory %s/..",
fp->name);
}
curdirfd = dirp->dd_fd;
if (offset)
seekdir(dirp, offset);
}
dp = readdir(dirp);
if (dp == 0)
break;
if ((strcmp(dp->d_name, ".") == 0) ||
(strcmp(dp->d_name, "..") == 0))
continue;
fds.kids++;
if (match(dp->d_name, badpat))
continue;
namlen = strlen(dp->d_name);
push(fp, dp->d_name, namlen);
if (baddirpat[0] && match(fp->name, baddirpat)) {
pop(fp, namlen);
continue;
}
push(tp, dp->d_name, namlen);
if ((*pf)(fp, tp)) {
down(fp, dp->d_name);
if (curdirfd >= maxdirfd) {
offset = telldir(dirp);
closedir(dirp);
dirp = 0;
}
remove = treewalk(fp, tp, pf);
up(fp);
if (dirp)
curdirfd = dirp->dd_fd;
if (remove
&& (null_effect || removetree(fp->name))
&& verbose) {
printf("%s\n", fp->name);
}
}
pop(fp, namlen);
pop(tp, namlen);
}
remove = (*pf)(fp, 0);
fp->dirstack = fds.parent;
assert(dirp);
closedir(dirp);
return remove;
}
/*
* Return true if name matches one of the patterns in the vector rep.
*/
int
match(char *name, char **rep)
{
char *res;
while (*rep) {
if ((res = regex(*rep, name)) && (*res == '\0')) {
return 1;
}
rep++;
}
return 0;
}
int
removetree(char *name)
{
pid_t pid;
int fd, rmstatus;
if (rmdir(name) == 0)
return 1;
if (errno != ENOTEMPTY && errno != EEXIST) {
perrorf("cannot remove %s", name);
return 0;
}
pid = fork();
if (pid < 0)
sfail("cannot fork to remove %s", name);
if (pid == 0) {
for (fd = curdirfd; fd > 2; --fd)
(void) close(fd);
(void) execl("/bin/rm", "rm", "-rf", name, (char *) 0);
sfail("cannot execute /bin/rm");
}
(void) waitpid(pid, &rmstatus, 0);
if (rmstatus != 0) {
message("cannot remove %s\n", name);
return 0;
}
return 1;
}
/*
* Given a file (full path tp->name, relative path tail(tp)) in the target
* tree, check whether its counterpart sp->name exists. If not then remove
* tail(tp). As clean() is a treewalk() node processing function, it must
* return 1 if tp names a searchable directory, 0 otherwise.
*/
int
clean(struct path *tp, struct path *sp)
{
char *target;
struct stat tsb, ssb;
int cleaned;
if (sp == 0)
return 0;
target = tail(tp);
if (lstat(target, &tsb) < 0) {
perrorf("cannot get attributes of %s", tp->name);
return 0;
}
/*
* If the source exists, check whether both target and source are
* directories, returning 1 if so. If the target is a link check
* whether it names the source.
*/
if (stat(sp->name, &ssb) == 0) {
if (S_ISDIR(tsb.st_mode)) {
if (S_ISDIR(ssb.st_mode))
return 1;
} else if (S_ISLNK(tsb.st_mode)) {
struct stat sb; /* link source attributes */
if ((!S_ISDIR(ssb.st_mode) || match(tp->name, dirlink))
&& stat(target, &sb) == 0
&& SAME_INODE(sb, ssb)) {
return 0;
}
}
}
/*
* Attempt to remove the target, which may be a non-empty directory.
*/
if (null_effect)
cleaned = 1; /* so we can be verbose */
else if (S_ISDIR(tsb.st_mode))
cleaned = removetree(target);
else {
cleaned = (unlink(target) == 0);
if (!cleaned)
perrorf("cannot unlink %s", target);
}
if (cleaned && verbose)
printf("%s\n", tp->name);
return 0;
}
int
prune(struct path *tp, struct path *sp)
{
struct dirstack *tds;
char *target;
struct stat tsb, ssb;
int cc, pruned;
tds = tp->dirstack;
if (sp == 0) {
if (tds->kids != 0 && tds->pruned == tds->kids) {
tds = tds->parent;
if (tds)
tds->pruned++;
return 1;
}
return 0;
}
target = tail(tp);
if (lstat(target, &tsb) < 0) {
perrorf("cannot get attributes of %s", tp->name);
return 0;
}
/*
* If the target is a directory, return 1 whether or not the source
* counterpart exists, so that we can prune dead growth hanging under
* obsoleted source directories.
*/
if (S_ISDIR(tsb.st_mode))
return 1;
/*
* Return early if the target is not a dangling link to a non-existent
* source file (i.e., a stale link).
*/
if (!S_ISLNK(tsb.st_mode) || stat(target, &ssb) == 0)
return 0;
/*
* Attempt to remove the stale link.
*/
if (null_effect)
pruned = 1; /* so we can be verbose */
else {
pruned = (unlink(target) == 0);
if (!pruned)
perrorf("cannot unlink %s", target);
}
if (pruned) {
tds->pruned++;
if (verbose)
printf("%s\n", tp->name);
}
return 0;
}
int stalepurge(char *target);
/*
* If the source is a directory, clone it in the target tree. Otherwise make
* a symbolic link named target pointing at source. Return 1 if source is a
* directory and should be searched, 0 otherwise.
*/
int
clone(struct path *sp, struct path *tp)
{
char *source, *target;
struct stat ssb, tsb;
if (tp == 0)
return 0;
source = tail(sp);
if (lstat(source, &ssb) < 0) {
perrorf("cannot get attributes of %s", sp->name);
return 0;
}
if (S_ISLNK(ssb.st_mode)) {
if (stat(source, &tsb) < 0) {
if (errno == ENOENT) {
errno = 0;
perrorf("%s is a dangling link.", sp->name);
} else {
perrorf("couldn't follow symlink %s", sp->name);
}
}
}
/*
* If the target exists, check whether it's a stale link. If it is a
* fresh link, return 0 to prevent searching deeper in the source tree.
* Otherwise stalepurge() has removed the target. If the target is a
* directory, check whether the source is also.
*/
target = tp->name;
if (lstat(target, &tsb) == 0) {
if (S_ISLNK(tsb.st_mode)) {
if (!stalepurge(target))
return 0;
} else {
if (S_ISDIR(tsb.st_mode)) {
if (S_ISDIR(ssb.st_mode))
return 1;
if (verbose)
perrnof(EISDIR, target);
} else if (S_ISDIR(ssb.st_mode)) {
if (verbose)
perrnof(ENOTDIR, target);
}
return 0;
}
}
/*
* Either the target did not exist upon entry, or it was a stale link
* and we removed it. Either way, if the source is a directory and if
* tp is not one of the directories to be linked, make an empty twin
* in the target tree and search the source directory. Otherwise make
* a symbolic link.
*/
if (S_ISDIR(ssb.st_mode) && match(target, dirlink) == 0) {
if (makedir(target, ssb.st_mode & ~S_IFMT) < 0)
sfail("cannot make directory %s", target);
return 1;
}
if (makelink(sp, target) < 0)
sfail("cannot link %s to %s", target, source);
return 0;
}
/*
* Return 1 if target, a symbolic link, names a nonexistent file and is
* successfully removed; return 0 otherwise.
*/
int
stalepurge(char *target)
{
struct stat sb;
if (stat(target, &sb) == 0)
return 0;
if (!null_effect && unlink(target) < 0) {
perrorf("cannot unlink stale link %s", target);
return 0;
}
if (verbose)
perrnof(ENOENT, target);
return 1;
}
/*
* Formatted message output, with progname.
*/
void vmessage(char *format, va_list ap);
void
message(char *format, ...)
{
va_list ap;
va_start(ap, format);
vmessage(format, ap);
va_end(ap);
}
/*
* Formatted perror, with progname. NB: potentially clobbers errno.
*/
void vperrnof(int error, char *format, va_list ap);
void
perrorf(char *format, ...)
{
va_list ap;
va_start(ap, format);
vperrnof(errno, format, ap);
va_end(ap);
}
void
perrnof(int error, char *format, ...)
{
va_list ap;
va_start(ap, format);
vperrnof(error, format, ap);
va_end(ap);
}
void
vperrnof(int error, char *format, va_list ap)
{
vmessage(format, ap);
if (0 < error && error <= sys_nerr)
fprintf(stderr, ": %s", sys_errlist[error]);
fputc('\n', stderr);
}
void
vmessage(char *format, va_list ap)
{
fprintf(stderr, "%s: ", progname);
vfprintf(stderr, format, ap);
status++;
}
/*
* System call failure.
*/
void
sfail(char *format, ...)
{
va_list ap;
va_start(ap, format);
vperrnof(errno, format, ap);
exit(status);
}
void
fail(char *format, ...)
{
va_list ap;
va_start(ap, format);
vperrnof(0, format, ap);
exit(status);
}