/bin/rm: fix removing symlinks with uchg/uappnd set
[dragonfly.git] / bin / rm / rm.c
1 /*-
2  * Copyright (c) 1990, 1993, 1994
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * @(#) Copyright (c) 1990, 1993, 1994 The Regents of the University of California.  All rights reserved.
34  * @(#)rm.c     8.5 (Berkeley) 4/18/94
35  * $FreeBSD: src/bin/rm/rm.c,v 1.29.2.5 2002/07/12 07:25:48 tjr Exp $
36  */
37
38 #include <sys/stat.h>
39 #include <sys/param.h>
40 #include <sys/mount.h>
41 #include <sys/ioctl.h>
42
43 #include <err.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <fts.h>
47 #include <grp.h>
48 #include <pwd.h>
49 #include <signal.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <sysexits.h>
54 #include <unistd.h>
55
56 static int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
57 static int rflag, Iflag;
58 static uid_t uid;
59 volatile sig_atomic_t info;
60
61 static int      check(const char *, const char *, struct stat *);
62 static int      check2(char **);
63 static void     checkdot(char **);
64 static void     rm_file(char **);
65 static int      rm_overwrite(const char *, struct stat *);
66 static void     rm_tree(char **);
67 static void     siginfo(int);
68 static void     usage(void);
69
70 /*
71  * rm --
72  *      This rm is different from historic rm's, but is expected to match
73  *      POSIX 1003.2 behavior.  The most visible difference is that -f
74  *      has two specific effects now, ignore non-existent files and force
75  *      file removal.
76  */
77 int
78 main(int argc, char *argv[])
79 {
80         int ch;
81         const char *p;
82         pid_t tty_pgrp;
83
84         /*
85          * Test for the special case where the utility is called as
86          * "unlink", for which the functionality provided is greatly
87          * simplified.
88          */
89         if ((p = strrchr(argv[0], '/')) == NULL)
90                 p = argv[0];
91         else
92                 ++p;
93         if (strcmp(p, "unlink") == 0) {
94                 while (getopt(argc, argv, "") != -1)
95                         usage();
96                 argc -= optind;
97                 argv += optind;
98                 if (argc != 1)
99                         usage();
100                 rm_file(&argv[0]);
101                 exit(eval);
102         }
103
104         Pflag = rflag = 0;
105         while ((ch = getopt(argc, argv, "dfiIPRrvW")) != -1) {
106                 switch(ch) {
107                 case 'd':
108                         dflag = 1;
109                         break;
110                 case 'f':
111                         fflag = 1;
112                         iflag = 0;
113                         break;
114                 case 'i':
115                         fflag = 0;
116                         iflag = 1;
117                         break;
118                 case 'I':
119                         /*
120                          * The -I flag is intended to be generally aliasable
121                          * in /etc/csh.cshrc.  We apply it only to foreground
122                          * processes.
123                          */
124                         if (ioctl(0, TIOCGPGRP, &tty_pgrp) == 0) {
125                                 if (tty_pgrp == getpgrp())
126                                         Iflag = 1;
127                         }
128                         break;
129                 case 'P':
130                         Pflag = 1;
131                         break;
132                 case 'R':
133                 case 'r':                       /* Compatibility. */
134                         rflag = 1;
135                         break;
136                 case 'v':
137                         vflag = 1;
138                         break;
139                 case 'W':
140                         Wflag = 1;
141                         break;
142                 default:
143                         usage();
144                 }
145         }
146         argc -= optind;
147         argv += optind;
148
149         if (argc < 1) {
150                 if (fflag)
151                         return 0;
152                 usage();
153         }
154
155         checkdot(argv);
156         uid = geteuid();
157         
158         signal(SIGINFO, siginfo);
159
160         if (*argv) {
161                 stdin_ok = isatty(STDIN_FILENO);
162
163                 if (Iflag && !iflag) {
164                         if (check2(argv) == 0)
165                                 exit (1);
166                 }
167                 if (rflag)
168                         rm_tree(argv);
169                 else
170                         rm_file(argv);
171         }
172
173         exit (eval);
174 }
175
176 static void
177 rm_tree(char **argv)
178 {
179         FTS *fts;
180         FTSENT *p;
181         int needstat;
182         int flags;
183         int rval;
184
185         /*
186          * Remove a file hierarchy.  If forcing removal (-f), or interactive
187          * (-i) or can't ask anyway (stdin_ok), don't stat the file.
188          */
189         needstat = !uid || (!fflag && !iflag && stdin_ok);
190
191         /*
192          * If the -i option is specified, the user can skip on the pre-order
193          * visit.  The fts_number field flags skipped directories.
194          */
195 #define SKIPPED 1
196
197         flags = FTS_PHYSICAL;
198         if (!needstat)
199                 flags |= FTS_NOSTAT;
200         if (Wflag)
201                 flags |= FTS_WHITEOUT;
202         if ((fts = fts_open(argv, flags, NULL)) == NULL) {
203                 if (fflag && errno == ENOENT)
204                         return;
205                 err(1, NULL);
206         }
207         while ((p = fts_read(fts)) != NULL) {
208                 switch (p->fts_info) {
209                 case FTS_DNR:
210                         if (!fflag || p->fts_errno != ENOENT) {
211                                 warnx("%s: %s",
212                                     p->fts_path, strerror(p->fts_errno));
213                                 eval = 1;
214                         }
215                         continue;
216                 case FTS_ERR:
217                         errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
218                 case FTS_NS:
219                        /*
220                         * Assume that since fts_read() couldn't stat
221                         * the file, it can't be unlinked.
222                         */
223                         if (!needstat)
224                                 break;
225                         if (!fflag || p->fts_errno != ENOENT) {
226                                 warnx("%s: %s",
227                                     p->fts_path, strerror(p->fts_errno));
228                                 eval = 1;
229                         }
230                         continue;
231                 case FTS_D:
232                         /* Pre-order: give user chance to skip. */
233                         if (!fflag && !check(p->fts_path, p->fts_accpath,
234                             p->fts_statp)) {
235                                 fts_set(fts, p, FTS_SKIP);
236                                 p->fts_number = SKIPPED;
237                         }
238                         else if (!uid &&
239                                  (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
240                                  !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
241                                  lchflags(p->fts_accpath,
242                                          p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
243                                 goto err;
244                         continue;
245                 case FTS_DP:
246                         /* Post-order: see if user skipped. */
247                         if (p->fts_number == SKIPPED)
248                                 continue;
249                         break;
250                 default:
251                         if (!fflag &&
252                             !check(p->fts_path, p->fts_accpath, p->fts_statp))
253                                 continue;
254                 }
255
256                 if (info) {
257                         info = 0;
258                         fprintf(stderr, "Currently removing: %s\n", p->fts_path);
259                 }
260
261                 rval = 0;
262                 if (!uid &&
263                     (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
264                     !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
265                         rval = lchflags(p->fts_accpath,
266                                        p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
267
268                 if (rval == 0) {
269                         /*
270                          * If we can't read or search the directory, may still be
271                          * able to remove it.  Don't print out the un{read,search}able
272                          * message unless the remove fails.
273                          */
274                         switch (p->fts_info) {
275                         case FTS_DP:
276                         case FTS_DNR:
277                                 rval = rmdir(p->fts_accpath);
278                                 if (rval == 0 || (fflag && errno == ENOENT)) {
279                                         if (rval == 0 && vflag)
280                                                 printf("%s\n",
281                                                     p->fts_path);
282                                         continue;
283                                 }
284                                 break;
285
286                         case FTS_W:
287                                 rval = undelete(p->fts_accpath);
288                                 if (rval == 0 && (fflag && errno == ENOENT)) {
289                                         if (vflag)
290                                                 printf("%s\n",
291                                                     p->fts_path);
292                                         continue;
293                                 }
294                                 break;
295
296                         case FTS_NS:
297                         /*
298                          * Assume that since fts_read() couldn't stat
299                          * the file, it can't be unlinked.
300                          */
301                                 if (fflag)
302                                         continue;
303                                 /* FALLTHROUGH */
304                         default:
305                                 if (Pflag)
306                                         if (!rm_overwrite(p->fts_accpath, NULL))
307                                                 continue;
308                                 rval = unlink(p->fts_accpath);
309                                 if (rval == 0 || (fflag && errno == ENOENT)) {
310                                         if (rval == 0 && vflag)
311                                                 printf("%s\n",
312                                                     p->fts_path);
313                                         continue;
314                                 }
315                         }
316                 }
317 err:
318                 warn("%s", p->fts_path);
319                 eval = 1;
320         }
321         if (errno)
322                 err(1, "fts_read");
323         fts_close(fts);
324 }
325
326 static void
327 rm_file(char **argv)
328 {
329         struct stat sb;
330         int rval;
331         const char *f;
332
333         /*
334          * Remove a file.  POSIX 1003.2 states that, by default, attempting
335          * to remove a directory is an error, so must always stat the file.
336          */
337         while ((f = *argv++) != NULL) {
338                 if (info) {
339                         info = 0;
340                         fprintf(stderr, "Currently removing: %s\n", f);
341                 }
342
343                 /* Assume if can't stat the file, can't unlink it. */
344                 if (lstat(f, &sb)) {
345                         if (Wflag) {
346                                 sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
347                         } else {
348                                 if (!fflag || errno != ENOENT) {
349                                         warn("%s", f);
350                                         eval = 1;
351                                 }
352                                 continue;
353                         }
354                 } else if (Wflag) {
355                         warnx("%s: %s", f, strerror(EEXIST));
356                         eval = 1;
357                         continue;
358                 }
359
360                 if (S_ISDIR(sb.st_mode) && !dflag) {
361                         warnx("%s: is a directory", f);
362                         eval = 1;
363                         continue;
364                 }
365                 if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
366                         continue;
367                 rval = 0;
368                 if (!uid &&
369                     (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
370                     !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
371                         rval = lchflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
372                 if (rval == 0) {
373                         if (S_ISWHT(sb.st_mode))
374                                 rval = undelete(f);
375                         else if (S_ISDIR(sb.st_mode))
376                                 rval = rmdir(f);
377                         else {
378                                 if (Pflag)
379                                         if (!rm_overwrite(f, &sb))
380                                                 continue;
381                                 rval = unlink(f);
382                         }
383                 }
384                 if (rval && (!fflag || errno != ENOENT)) {
385                         warn("%s", f);
386                         eval = 1;
387                 }
388                 if (vflag && rval == 0)
389                         printf("%s\n", f);
390         }
391 }
392
393 /*
394  * rm_overwrite --
395  *      Overwrite the file 3 times with varying bit patterns.
396  *
397  * XXX
398  * This is a cheap way to *really* delete files.  Note that only regular
399  * files are deleted, directories (and therefore names) will remain.
400  * Also, this assumes a fixed-block filesystem (like FFS, or a V7 or a
401  * System V filesystem).  In a logging filesystem, you'll have to have
402  * kernel support.
403  */
404 static int 
405 rm_overwrite(const char *file, struct stat *sbp)
406 {
407         struct stat sb;
408         struct statfs fsb;
409         off_t len;
410         int bsize, fd, wlen;
411         char *buf = NULL;
412
413         fd = -1;
414         if (sbp == NULL) {
415                 if (lstat(file, &sb))
416                         goto err;
417                 sbp = &sb;
418         }
419         if (!S_ISREG(sbp->st_mode)) {
420                 warnx("%s: cannot overwrite a non-regular file", file);
421                 return (1);
422         }
423         if (sbp->st_nlink > 1) {
424                 warnx("%s (inode %ju): not overwritten due to multiple links",
425                       file, (uintmax_t)sbp->st_ino);
426                 return (0);
427         }
428         if ((fd = open(file, O_WRONLY, 0)) == -1)
429                 goto err;
430         if (fstatfs(fd, &fsb) == -1)
431                 goto err;
432         bsize = MAX(fsb.f_iosize, 1024);
433         if ((buf = malloc(bsize)) == NULL)
434                 err(1, "%s malloc failed", file);
435
436 #define PASS(byte) {                                                    \
437         memset(buf, byte, bsize);                                       \
438         for (len = sbp->st_size; len > 0; len -= wlen) {                \
439                 wlen = len < bsize ? len : bsize;                       \
440                 if (write(fd, buf, wlen) != wlen)                       \
441                         goto err;                                       \
442         }                                                               \
443 }
444         PASS(0xff);
445         if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
446                 goto err;
447         PASS(0x00);
448         if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
449                 goto err;
450         PASS(0xff);
451         if (!fsync(fd) && !close(fd)) {
452                 free(buf);
453                 return (1);
454         }
455
456 err:    eval = 1;
457         if (buf)
458                 free(buf);
459         if (fd != -1)
460                 close(fd);
461         warn("%s", file);
462         return (0);
463 }
464
465
466 static int
467 check(const char *path, const char *name, struct stat *sp)
468 {
469         static int perm_answer = -1;
470         struct choice {
471                 int ch;
472                 const char *str;
473                 int res;
474                 int perm;
475         } *choice, choices[] = {
476                 { 'y', "yes"   , 1, 0 },
477                 { 'n', "no"    , 0, 0 },
478                 { 'a', "always", 1, 1 },
479                 { 'v', "never" , 0, 1 },
480                 { 0, NULL, 0, 0 }
481         };
482         char modep[15], *flagsp;
483
484         if (perm_answer != -1)
485                 return (perm_answer);
486
487         /* Check -i first. */
488         if (iflag)
489                 fprintf(stderr, "remove %s? ", path);
490         else {
491                 /*
492                  * If it's not a symbolic link and it's unwritable and we're
493                  * talking to a terminal, ask.  Symbolic links are excluded
494                  * because their permissions are meaningless.  Check stdin_ok
495                  * first because we may not have stat'ed the file.
496                  * Also skip this check if the -P option was specified because
497                  * we will not be able to overwrite file contents and will
498                  * barf later.
499                  */
500                 if (!stdin_ok || S_ISLNK(sp->st_mode) || Pflag ||
501                     (!access(name, W_OK) &&
502                     !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
503                     (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
504                         return (1);
505                 strmode(sp->st_mode, modep);
506                 if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
507                         err(1, NULL);
508                 fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
509                     modep + 1, modep[9] == ' ' ? "" : " ",
510                     user_from_uid(sp->st_uid, 0),
511                     group_from_gid(sp->st_gid, 0),
512                     *flagsp ? flagsp : "", *flagsp ? " " : "",
513                     path);
514                 free(flagsp);
515         }
516         fflush(stderr);
517
518         for (;;) {
519                 size_t len;
520                 char *answer;
521
522                 answer = fgetln(stdin, &len);
523                 /* clearerr(stdin); */
524                 if (answer == NULL)
525                         return (0);
526                 if (answer[len - 1] == '\n')
527                         len--;
528                 if (len == 0)
529                         continue;
530
531                 for (choice = choices; choice->str != NULL; choice++) {
532                         if (len == 1 && choice->ch == answer[0])
533                                 goto valid_choice;
534                         if (strncasecmp(answer, choice->str, len) == 0)
535                                 goto valid_choice;
536                 }
537
538                 fprintf(stderr, "invalid answer, try again (y/n/a/v): ");
539         }
540
541 valid_choice:
542         if (choice->perm)
543                 perm_answer = choice->res;
544         return (choice->res);
545 }
546
547 static int
548 check2(char **argv)
549 {
550         struct stat st;
551         int first;
552         int ch;
553         int fcount = 0;
554         int dcount = 0;
555         int i;
556         const char *dname = NULL;
557
558         for (i = 0; argv[i]; ++i) {
559                 if (lstat(argv[i], &st) == 0) {
560                         if (S_ISDIR(st.st_mode)) {
561                                 ++dcount;
562                                 dname = argv[i];    /* only used if 1 dir */
563                         } else {
564                                 ++fcount;
565                         }
566                 }
567         }
568         first = 0;
569         while (first != 'n' && first != 'N' && first != 'y' && first != 'Y') {
570                 if (dcount && rflag) {
571                         fprintf(stderr, "recursively remove");
572                         if (dcount == 1)
573                                 fprintf(stderr, " %s", dname);
574                         else
575                                 fprintf(stderr, " %d dirs", dcount);
576                         if (fcount == 1)
577                                 fprintf(stderr, " and 1 file");
578                         else if (fcount > 1)
579                                 fprintf(stderr, " and %d files", fcount);
580                 } else if (dcount + fcount > 3) {
581                         fprintf(stderr, "remove %d files", dcount + fcount);
582                 } else {
583                         return(1);
584                 }
585                 fprintf(stderr, "? ");
586                 fflush(stderr);
587
588                 first = ch = getchar();
589                 while (ch != '\n' && ch != EOF)
590                         ch = getchar();
591                 if (ch == EOF)
592                         break;
593         }
594         return (first == 'y' || first == 'Y');
595 }
596
597 #define ISDOT(a)        ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
598 static void
599 checkdot(char **argv)
600 {
601         char *p, **save, **t;
602         int complained;
603
604         complained = 0;
605         for (t = argv; *t;) {
606                 if ((p = strrchr(*t, '/')) != NULL)
607                         ++p;
608                 else
609                         p = *t;
610                 if (ISDOT(p)) {
611                         if (!complained++)
612                                 warnx("\".\" and \"..\" may not be removed");
613                         eval = 1;
614                         for (save = t; (t[0] = t[1]) != NULL; ++t)
615                                 continue;
616                         t = save;
617                 } else
618                         ++t;
619         }
620 }
621
622 static void
623 usage(void)
624 {
625
626         fprintf(stderr, "%s\n%s\n",
627             "usage: rm [-f | -i] [-dIPRrvW] file ...",
628             "       unlink file");
629         exit(EX_USAGE);
630 }
631
632 static void
633 siginfo(int notused __unused)
634 {
635         info = 1;
636 }