145769Sbostic /*-
266605Sbostic * Copyright (c) 1990, 1993, 1994
361008Sbostic * The Regents of the University of California. All rights reserved.
445769Sbostic *
545769Sbostic * %sccs.include.redist.c%
618277Sralph */
718277Sralph
845769Sbostic #ifndef lint
961008Sbostic static char copyright[] =
1066605Sbostic "@(#) Copyright (c) 1990, 1993, 1994\n\
1161008Sbostic The Regents of the University of California. All rights reserved.\n";
1245769Sbostic #endif /* not lint */
1345769Sbostic
1445769Sbostic #ifndef lint
15*68969Sbostic static char sccsid[] = "@(#)rm.c 8.8 (Berkeley) 04/27/95";
1645769Sbostic #endif /* not lint */
1745769Sbostic
1845769Sbostic #include <sys/types.h>
1945769Sbostic #include <sys/stat.h>
2059515Sbostic
2159515Sbostic #include <err.h>
2266561Spendry #include <errno.h>
2366860Sbostic #include <fcntl.h>
2445769Sbostic #include <fts.h>
251083Sbill #include <stdio.h>
2659515Sbostic #include <stdlib.h>
2745769Sbostic #include <string.h>
2859515Sbostic #include <unistd.h>
291083Sbill
3067575Spendry int dflag, eval, fflag, iflag, Pflag, Wflag, stdin_ok;
311083Sbill
3259515Sbostic int check __P((char *, char *, struct stat *));
3359515Sbostic void checkdot __P((char **));
3466860Sbostic void rm_file __P((char **));
3566860Sbostic void rm_overwrite __P((char *, struct stat *));
3666860Sbostic void rm_tree __P((char **));
3759515Sbostic void usage __P((void));
3859515Sbostic
3945769Sbostic /*
4045769Sbostic * rm --
4145769Sbostic * This rm is different from historic rm's, but is expected to match
4245769Sbostic * POSIX 1003.2 behavior. The most visible difference is that -f
4345769Sbostic * has two specific effects now, ignore non-existent files and force
4445769Sbostic * file removal.
4545769Sbostic */
4659515Sbostic int
main(argc,argv)471083Sbill main(argc, argv)
4834044Sbostic int argc;
4959515Sbostic char *argv[];
501083Sbill {
5145769Sbostic int ch, rflag;
521083Sbill
5366860Sbostic Pflag = rflag = 0;
54*68969Sbostic while ((ch = getopt(argc, argv, "dfiPRrW")) != -1)
5545769Sbostic switch(ch) {
5645769Sbostic case 'd':
5745769Sbostic dflag = 1;
5845769Sbostic break;
5934044Sbostic case 'f':
6045769Sbostic fflag = 1;
6145769Sbostic iflag = 0;
6218277Sralph break;
6334044Sbostic case 'i':
6445769Sbostic fflag = 0;
6545769Sbostic iflag = 1;
6634044Sbostic break;
6766860Sbostic case 'P':
6866860Sbostic Pflag = 1;
6966860Sbostic break;
7034044Sbostic case 'R':
7166860Sbostic case 'r': /* Compatibility. */
7245769Sbostic rflag = 1;
7334044Sbostic break;
7467575Spendry case 'W':
7567575Spendry Wflag = 1;
7667575Spendry break;
7734044Sbostic case '?':
7834044Sbostic default:
7934044Sbostic usage();
8034044Sbostic }
8145769Sbostic argc -= optind;
8245769Sbostic argv += optind;
831932Sroot
8445769Sbostic if (argc < 1)
8534044Sbostic usage();
8645769Sbostic
8745769Sbostic checkdot(argv);
8845769Sbostic
89*68969Sbostic if (*argv) {
90*68969Sbostic stdin_ok = isatty(STDIN_FILENO);
9145769Sbostic
92*68969Sbostic if (rflag)
93*68969Sbostic rm_tree(argv);
94*68969Sbostic else
95*68969Sbostic rm_file(argv);
96*68969Sbostic }
97*68969Sbostic
9859515Sbostic exit (eval);
991083Sbill }
1001083Sbill
10159515Sbostic void
rm_tree(argv)10266860Sbostic rm_tree(argv)
10345769Sbostic char **argv;
1041083Sbill {
10566561Spendry FTS *fts;
10666561Spendry FTSENT *p;
10766561Spendry int needstat;
10867575Spendry int flags;
1091083Sbill
11045769Sbostic /*
11145769Sbostic * Remove a file hierarchy. If forcing removal (-f), or interactive
11245769Sbostic * (-i) or can't ask anyway (stdin_ok), don't stat the file.
11345769Sbostic */
11445769Sbostic needstat = !fflag && !iflag && stdin_ok;
11545769Sbostic
11645769Sbostic /*
11745769Sbostic * If the -i option is specified, the user can skip on the pre-order
11845769Sbostic * visit. The fts_number field flags skipped directories.
11945769Sbostic */
12045769Sbostic #define SKIPPED 1
12145769Sbostic
12267575Spendry flags = FTS_PHYSICAL;
12367575Spendry if (!needstat)
12467575Spendry flags |= FTS_NOSTAT;
12567575Spendry if (Wflag)
12667575Spendry flags |= FTS_WHITEOUT;
12767575Spendry if (!(fts = fts_open(argv, flags, (int (*)())NULL)))
12859515Sbostic err(1, NULL);
12966605Sbostic while ((p = fts_read(fts)) != NULL) {
13059515Sbostic switch (p->fts_info) {
13145769Sbostic case FTS_DNR:
13266605Sbostic if (!fflag || p->fts_errno != ENOENT) {
13366605Sbostic warnx("%s: %s",
13466605Sbostic p->fts_path, strerror(p->fts_errno));
13561007Sbostic eval = 1;
13661007Sbostic }
13761007Sbostic continue;
13845769Sbostic case FTS_ERR:
13966605Sbostic errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
14045769Sbostic case FTS_NS:
14159515Sbostic /*
14259515Sbostic * FTS_NS: assume that if can't stat the file, it
14359515Sbostic * can't be unlinked.
14459515Sbostic */
14545769Sbostic if (!needstat)
14645769Sbostic break;
14766605Sbostic if (!fflag || p->fts_errno != ENOENT) {
14866605Sbostic warnx("%s: %s",
14966605Sbostic p->fts_path, strerror(p->fts_errno));
15059515Sbostic eval = 1;
15159515Sbostic }
15245769Sbostic continue;
15345769Sbostic case FTS_D:
15459515Sbostic /* Pre-order: give user chance to skip. */
155*68969Sbostic if (!fflag && !check(p->fts_path, p->fts_accpath,
15652238Sbostic p->fts_statp)) {
15745769Sbostic (void)fts_set(fts, p, FTS_SKIP);
15845769Sbostic p->fts_number = SKIPPED;
15918327Sralph }
16045769Sbostic continue;
16145769Sbostic case FTS_DP:
16259515Sbostic /* Post-order: see if user skipped. */
16345769Sbostic if (p->fts_number == SKIPPED)
16445769Sbostic continue;
16545769Sbostic break;
166*68969Sbostic default:
167*68969Sbostic if (!fflag &&
168*68969Sbostic !check(p->fts_path, p->fts_accpath, p->fts_statp))
169*68969Sbostic continue;
17018277Sralph }
17145769Sbostic
17245769Sbostic /*
17345769Sbostic * If we can't read or search the directory, may still be
17445769Sbostic * able to remove it. Don't print out the un{read,search}able
17545769Sbostic * message unless the remove fails.
17645769Sbostic */
17767575Spendry switch (p->fts_info) {
17867575Spendry case FTS_DP:
17967575Spendry case FTS_DNR:
180*68969Sbostic if (!rmdir(p->fts_accpath) || fflag && errno == ENOENT)
18118277Sralph continue;
18267575Spendry break;
18367575Spendry
18467575Spendry case FTS_W:
18567835Smckusick if (!undelete(p->fts_accpath) ||
18667575Spendry fflag && errno == ENOENT)
18767575Spendry continue;
18867575Spendry break;
18967575Spendry
19067575Spendry default:
19166860Sbostic if (Pflag)
19266860Sbostic rm_overwrite(p->fts_accpath, NULL);
19366860Sbostic if (!unlink(p->fts_accpath) || fflag && errno == ENOENT)
19466860Sbostic continue;
19566860Sbostic }
19659515Sbostic warn("%s", p->fts_path);
19759515Sbostic eval = 1;
1981083Sbill }
19966605Sbostic if (errno)
20066605Sbostic err(1, "fts_read");
20145769Sbostic }
2021083Sbill
20359515Sbostic void
rm_file(argv)20466860Sbostic rm_file(argv)
20545769Sbostic char **argv;
20645769Sbostic {
20766860Sbostic struct stat sb;
208*68969Sbostic int rval;
20966561Spendry char *f;
21045769Sbostic
21145769Sbostic /*
21245769Sbostic * Remove a file. POSIX 1003.2 states that, by default, attempting
21345769Sbostic * to remove a directory is an error, so must always stat the file.
21445769Sbostic */
21566605Sbostic while ((f = *argv++) != NULL) {
21645769Sbostic /* Assume if can't stat the file, can't unlink it. */
21745769Sbostic if (lstat(f, &sb)) {
21867575Spendry if (Wflag) {
21967575Spendry sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
22067575Spendry } else {
22167575Spendry if (!fflag || errno != ENOENT) {
22267575Spendry warn("%s", f);
22367575Spendry eval = 1;
22467575Spendry }
22567575Spendry continue;
22659515Sbostic }
22767575Spendry } else if (Wflag) {
22867575Spendry warnx("%s: %s", f, strerror(EEXIST));
22967575Spendry eval = 1;
23045769Sbostic continue;
2311083Sbill }
23267575Spendry
233*68969Sbostic if (S_ISDIR(sb.st_mode) && !dflag) {
23459515Sbostic warnx("%s: is a directory", f);
23559515Sbostic eval = 1;
23645769Sbostic continue;
23718327Sralph }
23867575Spendry if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
23945769Sbostic continue;
24067575Spendry if (S_ISWHT(sb.st_mode))
24167835Smckusick rval = undelete(f);
24267575Spendry else if (S_ISDIR(sb.st_mode))
24366860Sbostic rval = rmdir(f);
24466860Sbostic else {
24566860Sbostic if (Pflag)
24666860Sbostic rm_overwrite(f, &sb);
24766860Sbostic rval = unlink(f);
24866860Sbostic }
24966860Sbostic if (rval && (!fflag || errno != ENOENT)) {
25059515Sbostic warn("%s", f);
25159515Sbostic eval = 1;
25259515Sbostic }
2531083Sbill }
2541083Sbill }
2551083Sbill
25666860Sbostic /*
25766860Sbostic * rm_overwrite --
25866860Sbostic * Overwrite the file 3 times with varying bit patterns.
25966860Sbostic *
26066860Sbostic * XXX
26166860Sbostic * This is a cheap way to *really* delete files. Note that only regular
26266860Sbostic * files are deleted, directories (and therefore names) will remain.
26366860Sbostic * Also, this assumes a fixed-block file system (like FFS, or a V7 or a
26466860Sbostic * System V file system). In a logging file system, you'll have to have
26566860Sbostic * kernel support.
26666860Sbostic */
26766860Sbostic void
rm_overwrite(file,sbp)26866860Sbostic rm_overwrite(file, sbp)
26966860Sbostic char *file;
27066860Sbostic struct stat *sbp;
27166860Sbostic {
27266860Sbostic struct stat sb;
27366860Sbostic off_t len;
27466860Sbostic int fd, wlen;
27566860Sbostic char buf[8 * 1024];
27666860Sbostic
27766860Sbostic fd = -1;
27866860Sbostic if (sbp == NULL) {
27966860Sbostic if (lstat(file, &sb))
28066860Sbostic goto err;
28166860Sbostic sbp = &sb;
28266860Sbostic }
28366860Sbostic if (!S_ISREG(sbp->st_mode))
28466860Sbostic return;
28566860Sbostic if ((fd = open(file, O_WRONLY, 0)) == -1)
28666860Sbostic goto err;
28766860Sbostic
28866860Sbostic #define PASS(byte) { \
28966860Sbostic memset(buf, byte, sizeof(buf)); \
29066860Sbostic for (len = sbp->st_size; len > 0; len -= wlen) { \
29166860Sbostic wlen = len < sizeof(buf) ? len : sizeof(buf); \
29266860Sbostic if (write(fd, buf, wlen) != wlen) \
29366860Sbostic goto err; \
29466860Sbostic } \
29566860Sbostic }
29666860Sbostic PASS(0xff);
29766860Sbostic if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
29866860Sbostic goto err;
29966860Sbostic PASS(0x00);
30066860Sbostic if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
30166860Sbostic goto err;
30266860Sbostic PASS(0xff);
30366860Sbostic if (!fsync(fd) && !close(fd))
30466860Sbostic return;
30566860Sbostic
30666860Sbostic err: eval = 1;
30766860Sbostic warn("%s", file);
30866860Sbostic }
30966860Sbostic
31066860Sbostic
31159515Sbostic int
check(path,name,sp)31245769Sbostic check(path, name, sp)
31345769Sbostic char *path, *name;
31445769Sbostic struct stat *sp;
3151083Sbill {
31666561Spendry int ch, first;
31759515Sbostic char modep[15];
3181083Sbill
31945769Sbostic /* Check -i first. */
32045769Sbostic if (iflag)
32145769Sbostic (void)fprintf(stderr, "remove %s? ", path);
32245769Sbostic else {
32345769Sbostic /*
32445769Sbostic * If it's not a symbolic link and it's unwritable and we're
32545769Sbostic * talking to a terminal, ask. Symbolic links are excluded
32665344Sbostic * because their permissions are meaningless. Check stdin_ok
32765344Sbostic * first because we may not have stat'ed the file.
32845769Sbostic */
32965344Sbostic if (!stdin_ok || S_ISLNK(sp->st_mode) || !access(name, W_OK))
33059515Sbostic return (1);
33145769Sbostic strmode(sp->st_mode, modep);
33245769Sbostic (void)fprintf(stderr, "override %s%s%s/%s for %s? ",
33345769Sbostic modep + 1, modep[9] == ' ' ? "" : " ",
33445769Sbostic user_from_uid(sp->st_uid, 0),
33545769Sbostic group_from_gid(sp->st_gid, 0), path);
33645769Sbostic }
33745769Sbostic (void)fflush(stderr);
33845769Sbostic
33945769Sbostic first = ch = getchar();
34045769Sbostic while (ch != '\n' && ch != EOF)
34145769Sbostic ch = getchar();
34259515Sbostic return (first == 'y');
3431083Sbill }
34418277Sralph
34545769Sbostic #define ISDOT(a) ((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2]))
34659515Sbostic void
checkdot(argv)34745769Sbostic checkdot(argv)
34845769Sbostic char **argv;
34918277Sralph {
35066561Spendry char *p, **save, **t;
35145769Sbostic int complained;
35218277Sralph
35345769Sbostic complained = 0;
35445769Sbostic for (t = argv; *t;) {
35566605Sbostic if ((p = strrchr(*t, '/')) != NULL)
35645769Sbostic ++p;
35745769Sbostic else
35845769Sbostic p = *t;
35945769Sbostic if (ISDOT(p)) {
36045769Sbostic if (!complained++)
36159515Sbostic warnx("\".\" and \"..\" may not be removed");
36259515Sbostic eval = 1;
363*68969Sbostic for (save = t; (t[0] = t[1]) != NULL; ++t)
364*68969Sbostic continue;
36545769Sbostic t = save;
36645769Sbostic } else
36745769Sbostic ++t;
36845769Sbostic }
36918277Sralph }
37034044Sbostic
37159515Sbostic void
usage()37234044Sbostic usage()
37334044Sbostic {
37466561Spendry
375*68969Sbostic (void)fprintf(stderr, "usage: rm [-dfiPRrW] file ...\n");
37634044Sbostic exit(1);
37734044Sbostic }
378