diff --git a/bin/ln/ln.c b/bin/ln/ln.c index fc3afc133083..e5fdeee072db 100644 --- a/bin/ln/ln.c +++ b/bin/ln/ln.c @@ -1,358 +1,358 @@ /*- * Copyright (c) 1987, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if 0 #ifndef lint static char const copyright[] = "@(#) Copyright (c) 1987, 1993, 1994\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint static char sccsid[] = "@(#)ln.c 8.2 (Berkeley) 3/31/94"; #endif /* not lint */ #endif #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include int fflag; /* Unlink existing files. */ int Fflag; /* Remove empty directories also. */ int hflag; /* Check new name for symlink first. */ int iflag; /* Interactive mode. */ int Pflag; /* Create hard links to symlinks. */ int sflag; /* Symbolic, not hard, link. */ int vflag; /* Verbose output. */ int wflag; /* Warn if symlink target does not * exist, and -f is not enabled. */ char linkch; int linkit(const char *, const char *, int); void usage(void); int main(int argc, char *argv[]) { struct stat sb; char *p, *targetdir; int ch, exitval; /* * Test for the special case where the utility is called as * "link", for which the functionality provided is greatly * simplified. */ - if ((p = rindex(argv[0], '/')) == NULL) + if ((p = strrchr(argv[0], '/')) == NULL) p = argv[0]; else ++p; if (strcmp(p, "link") == 0) { while (getopt(argc, argv, "") != -1) usage(); argc -= optind; argv += optind; if (argc != 2) usage(); exit(linkit(argv[0], argv[1], 0)); } while ((ch = getopt(argc, argv, "FLPfhinsvw")) != -1) switch (ch) { case 'F': Fflag = 1; break; case 'L': Pflag = 0; break; case 'P': Pflag = 1; break; case 'f': fflag = 1; iflag = 0; wflag = 0; break; case 'h': case 'n': hflag = 1; break; case 'i': iflag = 1; fflag = 0; break; case 's': sflag = 1; break; case 'v': vflag = 1; break; case 'w': wflag = 1; break; case '?': default: usage(); } argv += optind; argc -= optind; linkch = sflag ? '-' : '='; if (sflag == 0) Fflag = 0; if (Fflag == 1 && iflag == 0) { fflag = 1; wflag = 0; /* Implied when fflag != 0 */ } switch(argc) { case 0: usage(); /* NOTREACHED */ case 1: /* ln source */ exit(linkit(argv[0], ".", 1)); case 2: /* ln source target */ exit(linkit(argv[0], argv[1], 0)); default: ; } /* ln source1 source2 directory */ targetdir = argv[argc - 1]; if (hflag && lstat(targetdir, &sb) == 0 && S_ISLNK(sb.st_mode)) { /* * We were asked not to follow symlinks, but found one at * the target--simulate "not a directory" error */ errno = ENOTDIR; err(1, "%s", targetdir); } if (stat(targetdir, &sb)) err(1, "%s", targetdir); if (!S_ISDIR(sb.st_mode)) usage(); for (exitval = 0; *argv != targetdir; ++argv) exitval |= linkit(*argv, targetdir, 1); exit(exitval); } /* * Two pathnames refer to the same directory entry if the directories match * and the final components' names match. */ static int samedirent(const char *path1, const char *path2) { const char *file1, *file2; char pathbuf[PATH_MAX]; struct stat sb1, sb2; if (strcmp(path1, path2) == 0) return 1; file1 = strrchr(path1, '/'); if (file1 != NULL) file1++; else file1 = path1; file2 = strrchr(path2, '/'); if (file2 != NULL) file2++; else file2 = path2; if (strcmp(file1, file2) != 0) return 0; if (file1 - path1 >= PATH_MAX || file2 - path2 >= PATH_MAX) return 0; if (file1 == path1) memcpy(pathbuf, ".", 2); else { memcpy(pathbuf, path1, file1 - path1); pathbuf[file1 - path1] = '\0'; } if (stat(pathbuf, &sb1) != 0) return 0; if (file2 == path2) memcpy(pathbuf, ".", 2); else { memcpy(pathbuf, path2, file2 - path2); pathbuf[file2 - path2] = '\0'; } if (stat(pathbuf, &sb2) != 0) return 0; return sb1.st_dev == sb2.st_dev && sb1.st_ino == sb2.st_ino; } int linkit(const char *source, const char *target, int isdir) { struct stat sb; const char *p; int ch, exists, first; char path[PATH_MAX]; char wbuf[PATH_MAX]; char bbuf[PATH_MAX]; if (!sflag) { /* If source doesn't exist, quit now. */ if ((Pflag ? lstat : stat)(source, &sb)) { warn("%s", source); return (1); } /* Only symbolic links to directories. */ if (S_ISDIR(sb.st_mode)) { errno = EISDIR; warn("%s", source); return (1); } } /* * If the target is a directory (and not a symlink if hflag), * append the source's name. */ if (isdir || (lstat(target, &sb) == 0 && S_ISDIR(sb.st_mode)) || (!hflag && stat(target, &sb) == 0 && S_ISDIR(sb.st_mode))) { if (strlcpy(bbuf, source, sizeof(bbuf)) >= sizeof(bbuf) || (p = basename(bbuf)) == NULL || snprintf(path, sizeof(path), "%s/%s", target, p) >= (ssize_t)sizeof(path)) { errno = ENAMETOOLONG; warn("%s", source); return (1); } target = path; } /* * If the link source doesn't exist, and a symbolic link was * requested, and -w was specified, give a warning. */ if (sflag && wflag) { if (*source == '/') { /* Absolute link source. */ if (stat(source, &sb) != 0) warn("warning: %s inaccessible", source); } else { /* * Relative symlink source. Try to construct the * absolute path of the source, by appending `source' * to the parent directory of the target. */ strlcpy(bbuf, target, sizeof(bbuf)); p = dirname(bbuf); if (p != NULL) { (void)snprintf(wbuf, sizeof(wbuf), "%s/%s", p, source); if (stat(wbuf, &sb) != 0) warn("warning: %s", source); } } } /* * If the file exists, first check it is not the same directory entry. */ exists = !lstat(target, &sb); if (exists) { if (!sflag && samedirent(source, target)) { warnx("%s and %s are the same directory entry", source, target); return (1); } } /* * Then unlink it forcibly if -f was specified * and interactively if -i was specified. */ if (fflag && exists) { if (Fflag && S_ISDIR(sb.st_mode)) { if (rmdir(target)) { warn("%s", target); return (1); } } else if (unlink(target)) { warn("%s", target); return (1); } } else if (iflag && exists) { fflush(stdout); fprintf(stderr, "replace %s? ", target); first = ch = getchar(); while(ch != '\n' && ch != EOF) ch = getchar(); if (first != 'y' && first != 'Y') { fprintf(stderr, "not replaced\n"); return (1); } if (Fflag && S_ISDIR(sb.st_mode)) { if (rmdir(target)) { warn("%s", target); return (1); } } else if (unlink(target)) { warn("%s", target); return (1); } } /* Attempt the link. */ if (sflag ? symlink(source, target) : linkat(AT_FDCWD, source, AT_FDCWD, target, Pflag ? 0 : AT_SYMLINK_FOLLOW)) { warn("%s", target); return (1); } if (vflag) (void)printf("%s %c> %s\n", target, linkch, source); return (0); } void usage(void) { (void)fprintf(stderr, "%s\n%s\n%s\n", "usage: ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file [target_file]", " ln [-s [-F] | -L | -P] [-f | -i] [-hnv] source_file ... target_dir", " link source_file target_file"); exit(1); } diff --git a/bin/rm/rm.c b/bin/rm/rm.c index 653833abab1b..af362a981666 100644 --- a/bin/rm/rm.c +++ b/bin/rm/rm.c @@ -1,623 +1,623 @@ /*- * Copyright (c) 1990, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if 0 #ifndef lint static const char copyright[] = "@(#) Copyright (c) 1990, 1993, 1994\n\ The Regents of the University of California. All rights reserved.\n"; #endif /* not lint */ #ifndef lint static char sccsid[] = "@(#)rm.c 8.5 (Berkeley) 4/18/94"; #endif /* not lint */ #endif #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok; int rflag, Iflag; uid_t uid; volatile sig_atomic_t info; int check(char *, char *, struct stat *); int check2(char **); void checkdot(char **); void checkslash(char **); void rm_file(char **); int rm_overwrite(char *, struct stat *); void rm_tree(char **); static void siginfo(int __unused); void usage(void); /* * rm -- * This rm is different from historic rm's, but is expected to match * POSIX 1003.2 behavior. The most visible difference is that -f * has two specific effects now, ignore non-existent files and force * file removal. */ int main(int argc, char *argv[]) { int ch; char *p; /* * Test for the special case where the utility is called as * "unlink", for which the functionality provided is greatly * simplified. */ - if ((p = rindex(argv[0], '/')) == NULL) + if ((p = strrchr(argv[0], '/')) == NULL) p = argv[0]; else ++p; if (strcmp(p, "unlink") == 0) { while (getopt(argc, argv, "") != -1) usage(); argc -= optind; argv += optind; if (argc != 1) usage(); rm_file(&argv[0]); exit(eval); } Pflag = rflag = 0; while ((ch = getopt(argc, argv, "dfiIPRrvW")) != -1) switch(ch) { case 'd': dflag = 1; break; case 'f': fflag = 1; iflag = 0; break; case 'i': fflag = 0; iflag = 1; break; case 'I': Iflag = 1; break; case 'P': Pflag = 1; break; case 'R': case 'r': /* Compatibility. */ rflag = 1; break; case 'v': vflag = 1; break; case 'W': Wflag = 1; break; default: usage(); } argc -= optind; argv += optind; if (argc < 1) { if (fflag) return (0); usage(); } checkdot(argv); if (getenv("POSIXLY_CORRECT") == NULL) checkslash(argv); uid = geteuid(); (void)signal(SIGINFO, siginfo); if (*argv) { stdin_ok = isatty(STDIN_FILENO); if (Iflag) { if (check2(argv) == 0) exit (1); } if (rflag) rm_tree(argv); else rm_file(argv); } exit (eval); } void rm_tree(char **argv) { FTS *fts; FTSENT *p; int needstat; int flags; int rval; /* * Remove a file hierarchy. If forcing removal (-f), or interactive * (-i) or can't ask anyway (stdin_ok), don't stat the file. */ needstat = !uid || (!fflag && !iflag && stdin_ok); /* * If the -i option is specified, the user can skip on the pre-order * visit. The fts_number field flags skipped directories. */ #define SKIPPED 1 flags = FTS_PHYSICAL; if (!needstat) flags |= FTS_NOSTAT; if (Wflag) flags |= FTS_WHITEOUT; if (!(fts = fts_open(argv, flags, NULL))) { if (fflag && errno == ENOENT) return; err(1, "fts_open"); } while ((p = fts_read(fts)) != NULL) { switch (p->fts_info) { case FTS_DNR: if (!fflag || p->fts_errno != ENOENT) { warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); eval = 1; } continue; case FTS_ERR: errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno)); case FTS_NS: /* * Assume that since fts_read() couldn't stat the * file, it can't be unlinked. */ if (!needstat) break; if (!fflag || p->fts_errno != ENOENT) { warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); eval = 1; } continue; case FTS_D: /* Pre-order: give user chance to skip. */ if (!fflag && !check(p->fts_path, p->fts_accpath, p->fts_statp)) { (void)fts_set(fts, p, FTS_SKIP); p->fts_number = SKIPPED; } else if (!uid && (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) && !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && lchflags(p->fts_accpath, p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0) goto err; continue; case FTS_DP: /* Post-order: see if user skipped. */ if (p->fts_number == SKIPPED) continue; break; default: if (!fflag && !check(p->fts_path, p->fts_accpath, p->fts_statp)) continue; } rval = 0; if (!uid && (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) && !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE))) rval = lchflags(p->fts_accpath, p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)); if (rval == 0) { /* * If we can't read or search the directory, may still be * able to remove it. Don't print out the un{read,search}able * message unless the remove fails. */ switch (p->fts_info) { case FTS_DP: case FTS_DNR: rval = rmdir(p->fts_accpath); if (rval == 0 || (fflag && errno == ENOENT)) { if (rval == 0 && vflag) (void)printf("%s\n", p->fts_path); if (rval == 0 && info) { info = 0; (void)printf("%s\n", p->fts_path); } continue; } break; case FTS_W: rval = undelete(p->fts_accpath); if (rval == 0 && (fflag && errno == ENOENT)) { if (vflag) (void)printf("%s\n", p->fts_path); if (info) { info = 0; (void)printf("%s\n", p->fts_path); } continue; } break; case FTS_NS: /* * Assume that since fts_read() couldn't stat * the file, it can't be unlinked. */ if (fflag) continue; /* FALLTHROUGH */ default: if (Pflag) if (!rm_overwrite(p->fts_accpath, NULL)) continue; rval = unlink(p->fts_accpath); if (rval == 0 || (fflag && errno == ENOENT)) { if (rval == 0 && vflag) (void)printf("%s\n", p->fts_path); if (rval == 0 && info) { info = 0; (void)printf("%s\n", p->fts_path); } continue; } } } err: warn("%s", p->fts_path); eval = 1; } if (errno) err(1, "fts_read"); fts_close(fts); } void rm_file(char **argv) { struct stat sb; int rval; char *f; /* * Remove a file. POSIX 1003.2 states that, by default, attempting * to remove a directory is an error, so must always stat the file. */ while ((f = *argv++) != NULL) { /* Assume if can't stat the file, can't unlink it. */ if (lstat(f, &sb)) { if (Wflag) { sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR; } else { if (!fflag || errno != ENOENT) { warn("%s", f); eval = 1; } continue; } } else if (Wflag) { warnx("%s: %s", f, strerror(EEXIST)); eval = 1; continue; } if (S_ISDIR(sb.st_mode) && !dflag) { warnx("%s: is a directory", f); eval = 1; continue; } if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb)) continue; rval = 0; if (!uid && !S_ISWHT(sb.st_mode) && (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) && !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE))) rval = lchflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE)); if (rval == 0) { if (S_ISWHT(sb.st_mode)) rval = undelete(f); else if (S_ISDIR(sb.st_mode)) rval = rmdir(f); else { if (Pflag) if (!rm_overwrite(f, &sb)) continue; rval = unlink(f); } } if (rval && (!fflag || errno != ENOENT)) { warn("%s", f); eval = 1; } if (vflag && rval == 0) (void)printf("%s\n", f); if (info && rval == 0) { info = 0; (void)printf("%s\n", f); } } } /* * rm_overwrite -- * Overwrite the file 3 times with varying bit patterns. * * XXX * This is a cheap way to *really* delete files. Note that only regular * files are deleted, directories (and therefore names) will remain. * Also, this assumes a fixed-block file system (like FFS, or a V7 or a * System V file system). In a logging or COW file system, you'll have to * have kernel support. */ int rm_overwrite(char *file, struct stat *sbp) { struct stat sb; struct statfs fsb; off_t len; int bsize, fd, wlen; char *buf = NULL; fd = -1; if (sbp == NULL) { if (lstat(file, &sb)) goto err; sbp = &sb; } if (!S_ISREG(sbp->st_mode)) return (1); if (sbp->st_nlink > 1 && !fflag) { warnx("%s (inode %u): not overwritten due to multiple links", file, sbp->st_ino); return (0); } if ((fd = open(file, O_WRONLY, 0)) == -1) goto err; if (fstatfs(fd, &fsb) == -1) goto err; bsize = MAX(fsb.f_iosize, 1024); if ((buf = malloc(bsize)) == NULL) err(1, "%s: malloc", file); #define PASS(byte) { \ memset(buf, byte, bsize); \ for (len = sbp->st_size; len > 0; len -= wlen) { \ wlen = len < bsize ? len : bsize; \ if (write(fd, buf, wlen) != wlen) \ goto err; \ } \ } PASS(0xff); if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) goto err; PASS(0x00); if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET)) goto err; PASS(0xff); if (!fsync(fd) && !close(fd)) { free(buf); return (1); } err: eval = 1; if (buf) free(buf); if (fd != -1) close(fd); warn("%s", file); return (0); } int check(char *path, char *name, struct stat *sp) { int ch, first; char modep[15], *flagsp; /* Check -i first. */ if (iflag) (void)fprintf(stderr, "remove %s? ", path); else { /* * If it's not a symbolic link and it's unwritable and we're * talking to a terminal, ask. Symbolic links are excluded * because their permissions are meaningless. Check stdin_ok * first because we may not have stat'ed the file. */ if (!stdin_ok || S_ISLNK(sp->st_mode) || (!access(name, W_OK) && !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid))) return (1); strmode(sp->st_mode, modep); if ((flagsp = fflagstostr(sp->st_flags)) == NULL) err(1, "fflagstostr"); if (Pflag) errx(1, "%s: -P was specified, but file is not writable", path); (void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ", modep + 1, modep[9] == ' ' ? "" : " ", user_from_uid(sp->st_uid, 0), group_from_gid(sp->st_gid, 0), *flagsp ? flagsp : "", *flagsp ? " " : "", path); free(flagsp); } (void)fflush(stderr); first = ch = getchar(); while (ch != '\n' && ch != EOF) ch = getchar(); return (first == 'y' || first == 'Y'); } #define ISSLASH(a) ((a)[0] == '/' && (a)[1] == '\0') void checkslash(char **argv) { char **t, **u; int complained; complained = 0; for (t = argv; *t;) { if (ISSLASH(*t)) { if (!complained++) warnx("\"/\" may not be removed"); eval = 1; for (u = t; u[0] != NULL; ++u) u[0] = u[1]; } else { ++t; } } } int check2(char **argv) { struct stat st; int first; int ch; int fcount = 0; int dcount = 0; int i; const char *dname = NULL; for (i = 0; argv[i]; ++i) { if (lstat(argv[i], &st) == 0) { if (S_ISDIR(st.st_mode)) { ++dcount; dname = argv[i]; /* only used if 1 dir */ } else { ++fcount; } } } first = 0; while (first != 'n' && first != 'N' && first != 'y' && first != 'Y') { if (dcount && rflag) { fprintf(stderr, "recursively remove"); if (dcount == 1) fprintf(stderr, " %s", dname); else fprintf(stderr, " %d dirs", dcount); if (fcount == 1) fprintf(stderr, " and 1 file"); else if (fcount > 1) fprintf(stderr, " and %d files", fcount); } else if (dcount + fcount > 3) { fprintf(stderr, "remove %d files", dcount + fcount); } else { return(1); } fprintf(stderr, "? "); fflush(stderr); first = ch = getchar(); while (ch != '\n' && ch != EOF) ch = getchar(); if (ch == EOF) break; } return (first == 'y' || first == 'Y'); } #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2]))) void checkdot(char **argv) { char *p, **save, **t; int complained; complained = 0; for (t = argv; *t;) { if ((p = strrchr(*t, '/')) != NULL) ++p; else p = *t; if (ISDOT(p)) { if (!complained++) warnx("\".\" and \"..\" may not be removed"); eval = 1; for (save = t; (t[0] = t[1]) != NULL; ++t) continue; t = save; } else ++t; } } void usage(void) { (void)fprintf(stderr, "%s\n%s\n", "usage: rm [-f | -i] [-dIPRrvW] file ...", " unlink file"); exit(EX_USAGE); } static void siginfo(int sig __unused) { info = 1; } diff --git a/bin/test/test.c b/bin/test/test.c index 1259e681c5c5..62c0309698c5 100644 --- a/bin/test/test.c +++ b/bin/test/test.c @@ -1,603 +1,603 @@ /* $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */ /*- * test(1); version 7-like -- author Erik Baalbergen * modified by Eric Gisin to be used as built-in. * modified by Arnold Robbins to add SVR3 compatibility * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). * modified by J.T. Conklin for NetBSD. * * This program is in the Public Domain. */ /* * Important: This file is used both as a standalone program /bin/test and * as a builtin for /bin/sh (#define SHELL). */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SHELL #define main testcmd #include "bltin/bltin.h" #else #include static void error(const char *, ...) __dead2 __printf0like(1, 2); static void error(const char *msg, ...) { va_list ap; va_start(ap, msg); verrx(2, msg, ap); /*NOTREACHED*/ va_end(ap); } #endif /* test(1) accepts the following grammar: oexpr ::= aexpr | aexpr "-o" oexpr ; aexpr ::= nexpr | nexpr "-a" aexpr ; nexpr ::= primary | "!" primary primary ::= unary-operator operand | operand binary-operator operand | operand | "(" oexpr ")" ; unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| "-nt"|"-ot"|"-ef"; operand ::= */ enum token { EOI, FILRD, FILWR, FILEX, FILEXIST, FILREG, FILDIR, FILCDEV, FILBDEV, FILFIFO, FILSOCK, FILSYM, FILGZ, FILTT, FILSUID, FILSGID, FILSTCK, FILNT, FILOT, FILEQ, FILUID, FILGID, STREZ, STRNZ, STREQ, STRNE, STRLT, STRGT, INTEQ, INTNE, INTGE, INTGT, INTLE, INTLT, UNOT, BAND, BOR, LPAREN, RPAREN, OPERAND }; enum token_types { UNOP, BINOP, BUNOP, BBINOP, PAREN }; struct t_op { const char *op_text; short op_num, op_type; } const ops [] = { {"-r", FILRD, UNOP}, {"-w", FILWR, UNOP}, {"-x", FILEX, UNOP}, {"-e", FILEXIST,UNOP}, {"-f", FILREG, UNOP}, {"-d", FILDIR, UNOP}, {"-c", FILCDEV,UNOP}, {"-b", FILBDEV,UNOP}, {"-p", FILFIFO,UNOP}, {"-u", FILSUID,UNOP}, {"-g", FILSGID,UNOP}, {"-k", FILSTCK,UNOP}, {"-s", FILGZ, UNOP}, {"-t", FILTT, UNOP}, {"-z", STREZ, UNOP}, {"-n", STRNZ, UNOP}, {"-h", FILSYM, UNOP}, /* for backwards compat */ {"-O", FILUID, UNOP}, {"-G", FILGID, UNOP}, {"-L", FILSYM, UNOP}, {"-S", FILSOCK,UNOP}, {"=", STREQ, BINOP}, {"==", STREQ, BINOP}, {"!=", STRNE, BINOP}, {"<", STRLT, BINOP}, {">", STRGT, BINOP}, {"-eq", INTEQ, BINOP}, {"-ne", INTNE, BINOP}, {"-ge", INTGE, BINOP}, {"-gt", INTGT, BINOP}, {"-le", INTLE, BINOP}, {"-lt", INTLT, BINOP}, {"-nt", FILNT, BINOP}, {"-ot", FILOT, BINOP}, {"-ef", FILEQ, BINOP}, {"!", UNOT, BUNOP}, {"-a", BAND, BBINOP}, {"-o", BOR, BBINOP}, {"(", LPAREN, PAREN}, {")", RPAREN, PAREN}, {0, 0, 0} }; struct t_op const *t_wp_op; int nargc; char **t_wp; int parenlevel; static int aexpr(enum token); static int binop(void); static int equalf(const char *, const char *); static int filstat(char *, enum token); static int getn(const char *); static intmax_t getq(const char *); static int intcmp(const char *, const char *); static int isunopoperand(void); static int islparenoperand(void); static int isrparenoperand(void); static int newerf(const char *, const char *); static int nexpr(enum token); static int oexpr(enum token); static int olderf(const char *, const char *); static int primary(enum token); static void syntax(const char *, const char *); static enum token t_lex(char *); int main(int argc, char **argv) { int res; char *p; - if ((p = rindex(argv[0], '/')) == NULL) + if ((p = strrchr(argv[0], '/')) == NULL) p = argv[0]; else p++; if (strcmp(p, "[") == 0) { if (strcmp(argv[--argc], "]") != 0) error("missing ]"); argv[argc] = NULL; } /* no expression => false */ if (--argc <= 0) return 1; #ifndef SHELL (void)setlocale(LC_CTYPE, ""); #endif nargc = argc; t_wp = &argv[1]; parenlevel = 0; if (nargc == 4 && strcmp(*t_wp, "!") == 0) { /* Things like ! "" -o x do not fit in the normal grammar. */ --nargc; ++t_wp; res = oexpr(t_lex(*t_wp)); } else res = !oexpr(t_lex(*t_wp)); if (--nargc > 0) syntax(*t_wp, "unexpected operator"); return res; } static void syntax(const char *op, const char *msg) { if (op && *op) error("%s: %s", op, msg); else error("%s", msg); } static int oexpr(enum token n) { int res; res = aexpr(n); if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR) return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || res; t_wp--; nargc++; return res; } static int aexpr(enum token n) { int res; res = nexpr(n); if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND) return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && res; t_wp--; nargc++; return res; } static int nexpr(enum token n) { if (n == UNOT) return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)); return primary(n); } static int primary(enum token n) { enum token nn; int res; if (n == EOI) return 0; /* missing expression */ if (n == LPAREN) { parenlevel++; if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == RPAREN) { parenlevel--; return 0; /* missing expression */ } res = oexpr(nn); if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) syntax(NULL, "closing paren expected"); parenlevel--; return res; } if (t_wp_op && t_wp_op->op_type == UNOP) { /* unary expression */ if (--nargc == 0) syntax(t_wp_op->op_text, "argument expected"); switch (n) { case STREZ: return strlen(*++t_wp) == 0; case STRNZ: return strlen(*++t_wp) != 0; case FILTT: return isatty(getn(*++t_wp)); default: return filstat(*++t_wp, n); } } if (t_lex(nargc > 0 ? t_wp[1] : NULL), t_wp_op && t_wp_op->op_type == BINOP) { return binop(); } return strlen(*t_wp) > 0; } static int binop(void) { const char *opnd1, *opnd2; struct t_op const *op; opnd1 = *t_wp; (void) t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL); op = t_wp_op; if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL) syntax(op->op_text, "argument expected"); switch (op->op_num) { case STREQ: return strcmp(opnd1, opnd2) == 0; case STRNE: return strcmp(opnd1, opnd2) != 0; case STRLT: return strcmp(opnd1, opnd2) < 0; case STRGT: return strcmp(opnd1, opnd2) > 0; case INTEQ: return intcmp(opnd1, opnd2) == 0; case INTNE: return intcmp(opnd1, opnd2) != 0; case INTGE: return intcmp(opnd1, opnd2) >= 0; case INTGT: return intcmp(opnd1, opnd2) > 0; case INTLE: return intcmp(opnd1, opnd2) <= 0; case INTLT: return intcmp(opnd1, opnd2) < 0; case FILNT: return newerf (opnd1, opnd2); case FILOT: return olderf (opnd1, opnd2); case FILEQ: return equalf (opnd1, opnd2); default: abort(); /* NOTREACHED */ } } static int filstat(char *nm, enum token mode) { struct stat s; if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) return 0; switch (mode) { case FILRD: return (eaccess(nm, R_OK) == 0); case FILWR: return (eaccess(nm, W_OK) == 0); case FILEX: /* XXX work around eaccess(2) false positives for superuser */ if (eaccess(nm, X_OK) != 0) return 0; if (S_ISDIR(s.st_mode) || geteuid() != 0) return 1; return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; case FILEXIST: return (eaccess(nm, F_OK) == 0); case FILREG: return S_ISREG(s.st_mode); case FILDIR: return S_ISDIR(s.st_mode); case FILCDEV: return S_ISCHR(s.st_mode); case FILBDEV: return S_ISBLK(s.st_mode); case FILFIFO: return S_ISFIFO(s.st_mode); case FILSOCK: return S_ISSOCK(s.st_mode); case FILSYM: return S_ISLNK(s.st_mode); case FILSUID: return (s.st_mode & S_ISUID) != 0; case FILSGID: return (s.st_mode & S_ISGID) != 0; case FILSTCK: return (s.st_mode & S_ISVTX) != 0; case FILGZ: return s.st_size > (off_t)0; case FILUID: return s.st_uid == geteuid(); case FILGID: return s.st_gid == getegid(); default: return 1; } } static enum token t_lex(char *s) { struct t_op const *op = ops; if (s == 0) { t_wp_op = NULL; return EOI; } while (op->op_text) { if (strcmp(s, op->op_text) == 0) { if (((op->op_type == UNOP || op->op_type == BUNOP) && isunopoperand()) || (op->op_num == LPAREN && islparenoperand()) || (op->op_num == RPAREN && isrparenoperand())) break; t_wp_op = op; return op->op_num; } op++; } t_wp_op = NULL; return OPERAND; } static int isunopoperand(void) { struct t_op const *op = ops; char *s; char *t; if (nargc == 1) return 1; s = *(t_wp + 1); if (nargc == 2) return parenlevel == 1 && strcmp(s, ")") == 0; t = *(t_wp + 2); while (op->op_text) { if (strcmp(s, op->op_text) == 0) return op->op_type == BINOP && (parenlevel == 0 || t[0] != ')' || t[1] != '\0'); op++; } return 0; } static int islparenoperand(void) { struct t_op const *op = ops; char *s; if (nargc == 1) return 1; s = *(t_wp + 1); if (nargc == 2) return parenlevel == 1 && strcmp(s, ")") == 0; if (nargc != 3) return 0; while (op->op_text) { if (strcmp(s, op->op_text) == 0) return op->op_type == BINOP; op++; } return 0; } static int isrparenoperand(void) { char *s; if (nargc == 1) return 0; s = *(t_wp + 1); if (nargc == 2) return parenlevel == 1 && strcmp(s, ")") == 0; return 0; } /* atoi with error detection */ static int getn(const char *s) { char *p; long r; errno = 0; r = strtol(s, &p, 10); if (s == p) error("%s: bad number", s); if (errno != 0) error((errno == EINVAL) ? "%s: bad number" : "%s: out of range", s); while (isspace((unsigned char)*p)) p++; if (*p) error("%s: bad number", s); return (int) r; } /* atoi with error detection and 64 bit range */ static intmax_t getq(const char *s) { char *p; intmax_t r; errno = 0; r = strtoimax(s, &p, 10); if (s == p) error("%s: bad number", s); if (errno != 0) error((errno == EINVAL) ? "%s: bad number" : "%s: out of range", s); while (isspace((unsigned char)*p)) p++; if (*p) error("%s: bad number", s); return r; } static int intcmp (const char *s1, const char *s2) { intmax_t q1, q2; q1 = getq(s1); q2 = getq(s2); if (q1 > q2) return 1; if (q1 < q2) return -1; return 0; } static int newerf (const char *f1, const char *f2) { struct stat b1, b2; if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) return 0; if (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec) return 1; if (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec) return 0; return (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec); } static int olderf (const char *f1, const char *f2) { return (newerf(f2, f1)); } static int equalf (const char *f1, const char *f2) { struct stat b1, b2; return (stat (f1, &b1) == 0 && stat (f2, &b2) == 0 && b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino); }