]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - bin/rm/rm.c
This commit was generated by cvs2svn to compensate for changes in r98530,
[FreeBSD/FreeBSD.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
34 #ifndef lint
35 static const char copyright[] =
36 "@(#) Copyright (c) 1990, 1993, 1994\n\
37         The Regents of the University of California.  All rights reserved.\n";
38 #endif /* not lint */
39
40 #ifndef lint
41 #if 0
42 static char sccsid[] = "@(#)rm.c        8.5 (Berkeley) 4/18/94";
43 #else
44 static const char rcsid[] =
45   "$FreeBSD$";
46 #endif
47 #endif /* not lint */
48
49 #include <sys/stat.h>
50 #include <sys/param.h>
51 #include <sys/mount.h>
52
53 #include <err.h>
54 #include <errno.h>
55 #include <fcntl.h>
56 #include <fts.h>
57 #include <grp.h>
58 #include <pwd.h>
59 #include <stdio.h>
60 #include <stdlib.h>
61 #include <string.h>
62 #include <sysexits.h>
63 #include <unistd.h>
64
65 int dflag, eval, fflag, iflag, Pflag, vflag, Wflag, stdin_ok;
66 uid_t uid;
67
68 int     check(char *, char *, struct stat *);
69 void    checkdot(char **);
70 void    rm_file(char **);
71 void    rm_overwrite(char *, struct stat *);
72 void    rm_tree(char **);
73 void    usage(void);
74
75 /*
76  * rm --
77  *      This rm is different from historic rm's, but is expected to match
78  *      POSIX 1003.2 behavior.  The most visible difference is that -f
79  *      has two specific effects now, ignore non-existent files and force
80  *      file removal.
81  */
82 int
83 main(int argc, char *argv[])
84 {
85         int ch, rflag;
86         char *p;
87
88         /*
89          * Test for the special case where the utility is called as
90          * "unlink", for which the functionality provided is greatly
91          * simplified.
92          */
93         if ((p = rindex(argv[0], '/')) == NULL)
94                 p = argv[0];
95         else
96                 ++p;
97         if (strcmp(p, "unlink") == 0) {
98                 while (getopt(argc, argv, "") != -1)
99                         usage();
100                 argc -= optind;
101                 argv += optind;
102                 if (argc == 0)
103                         usage();
104                 rm_file(&argv[0]);
105                 exit(eval);
106         }
107
108         Pflag = rflag = 0;
109         while ((ch = getopt(argc, argv, "dfiPRrvW")) != -1)
110                 switch(ch) {
111                 case 'd':
112                         dflag = 1;
113                         break;
114                 case 'f':
115                         fflag = 1;
116                         iflag = 0;
117                         break;
118                 case 'i':
119                         fflag = 0;
120                         iflag = 1;
121                         break;
122                 case 'P':
123                         Pflag = 1;
124                         break;
125                 case 'R':
126                 case 'r':                       /* Compatibility. */
127                         rflag = 1;
128                         break;
129                 case 'v':
130                         vflag = 1;
131                         break;
132                 case 'W':
133                         Wflag = 1;
134                         break;
135                 default:
136                         usage();
137                 }
138         argc -= optind;
139         argv += optind;
140
141         if (argc < 1) {
142                 if (fflag)
143                         return 0;
144                 usage();
145         }
146
147         checkdot(argv);
148         uid = geteuid();
149
150         if (*argv) {
151                 stdin_ok = isatty(STDIN_FILENO);
152
153                 if (rflag)
154                         rm_tree(argv);
155                 else
156                         rm_file(argv);
157         }
158
159         exit (eval);
160 }
161
162 void
163 rm_tree(char **argv)
164 {
165         FTS *fts;
166         FTSENT *p;
167         int needstat;
168         int flags;
169         int rval;
170
171         /*
172          * Remove a file hierarchy.  If forcing removal (-f), or interactive
173          * (-i) or can't ask anyway (stdin_ok), don't stat the file.
174          */
175         needstat = !uid || (!fflag && !iflag && stdin_ok);
176
177         /*
178          * If the -i option is specified, the user can skip on the pre-order
179          * visit.  The fts_number field flags skipped directories.
180          */
181 #define SKIPPED 1
182
183         flags = FTS_PHYSICAL;
184         if (!needstat)
185                 flags |= FTS_NOSTAT;
186         if (Wflag)
187                 flags |= FTS_WHITEOUT;
188         if (!(fts = fts_open(argv, flags, NULL)))
189                 err(1, NULL);
190         while ((p = fts_read(fts)) != NULL) {
191                 switch (p->fts_info) {
192                 case FTS_DNR:
193                         if (!fflag || p->fts_errno != ENOENT) {
194                                 warnx("%s: %s",
195                                     p->fts_path, strerror(p->fts_errno));
196                                 eval = 1;
197                         }
198                         continue;
199                 case FTS_ERR:
200                         errx(1, "%s: %s", p->fts_path, strerror(p->fts_errno));
201                 case FTS_NS:
202                         /*
203                          * FTS_NS: assume that if can't stat the file, it
204                          * can't be unlinked.
205                          */
206                         if (!needstat)
207                                 break;
208                         if (!fflag || p->fts_errno != ENOENT) {
209                                 warnx("%s: %s",
210                                     p->fts_path, strerror(p->fts_errno));
211                                 eval = 1;
212                         }
213                         continue;
214                 case FTS_D:
215                         /* Pre-order: give user chance to skip. */
216                         if (!fflag && !check(p->fts_path, p->fts_accpath,
217                             p->fts_statp)) {
218                                 (void)fts_set(fts, p, FTS_SKIP);
219                                 p->fts_number = SKIPPED;
220                         }
221                         else if (!uid &&
222                                  (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
223                                  !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
224                                  chflags(p->fts_accpath,
225                                          p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)) < 0)
226                                 goto err;
227                         continue;
228                 case FTS_DP:
229                         /* Post-order: see if user skipped. */
230                         if (p->fts_number == SKIPPED)
231                                 continue;
232                         break;
233                 default:
234                         if (!fflag &&
235                             !check(p->fts_path, p->fts_accpath, p->fts_statp))
236                                 continue;
237                 }
238
239                 rval = 0;
240                 if (!uid &&
241                     (p->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
242                     !(p->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)))
243                         rval = chflags(p->fts_accpath,
244                                        p->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE));
245                 if (rval == 0) {
246                         /*
247                          * If we can't read or search the directory, may still be
248                          * able to remove it.  Don't print out the un{read,search}able
249                          * message unless the remove fails.
250                          */
251                         switch (p->fts_info) {
252                         case FTS_DP:
253                         case FTS_DNR:
254                                 rval = rmdir(p->fts_accpath);
255                                 if (rval == 0 || (fflag && errno == ENOENT)) {
256                                         if (rval == 0 && vflag)
257                                                 (void)printf("%s\n",
258                                                     p->fts_path);
259                                         continue;
260                                 }
261                                 break;
262
263                         case FTS_W:
264                                 rval = undelete(p->fts_accpath);
265                                 if (rval == 0 && (fflag && errno == ENOENT)) {
266                                         if (vflag)
267                                                 (void)printf("%s\n",
268                                                     p->fts_path);
269                                         continue;
270                                 }
271                                 break;
272
273                         default:
274                                 if (Pflag)
275                                         rm_overwrite(p->fts_accpath, NULL);
276                                 rval = unlink(p->fts_accpath);
277                                 if (rval == 0 || (fflag && errno == ENOENT)) {
278                                         if (rval == 0 && vflag)
279                                                 (void)printf("%s\n",
280                                                     p->fts_path);
281                                         continue;
282                                 }
283                         }
284                 }
285 err:
286                 warn("%s", p->fts_path);
287                 eval = 1;
288         }
289         if (errno)
290                 err(1, "fts_read");
291 }
292
293 void
294 rm_file(char **argv)
295 {
296         struct stat sb;
297         int rval;
298         char *f;
299
300         /*
301          * Remove a file.  POSIX 1003.2 states that, by default, attempting
302          * to remove a directory is an error, so must always stat the file.
303          */
304         while ((f = *argv++) != NULL) {
305                 /* Assume if can't stat the file, can't unlink it. */
306                 if (lstat(f, &sb)) {
307                         if (Wflag) {
308                                 sb.st_mode = S_IFWHT|S_IWUSR|S_IRUSR;
309                         } else {
310                                 if (!fflag || errno != ENOENT) {
311                                         warn("%s", f);
312                                         eval = 1;
313                                 }
314                                 continue;
315                         }
316                 } else if (Wflag) {
317                         warnx("%s: %s", f, strerror(EEXIST));
318                         eval = 1;
319                         continue;
320                 }
321
322                 if (S_ISDIR(sb.st_mode) && !dflag) {
323                         warnx("%s: is a directory", f);
324                         eval = 1;
325                         continue;
326                 }
327                 if (!fflag && !S_ISWHT(sb.st_mode) && !check(f, f, &sb))
328                         continue;
329                 rval = 0;
330                 if (!uid &&
331                     (sb.st_flags & (UF_APPEND|UF_IMMUTABLE)) &&
332                     !(sb.st_flags & (SF_APPEND|SF_IMMUTABLE)))
333                         rval = chflags(f, sb.st_flags & ~(UF_APPEND|UF_IMMUTABLE));
334                 if (rval == 0) {
335                         if (S_ISWHT(sb.st_mode))
336                                 rval = undelete(f);
337                         else if (S_ISDIR(sb.st_mode))
338                                 rval = rmdir(f);
339                         else {
340                                 if (Pflag)
341                                         rm_overwrite(f, &sb);
342                                 rval = unlink(f);
343                         }
344                 }
345                 if (rval && (!fflag || errno != ENOENT)) {
346                         warn("%s", f);
347                         eval = 1;
348                 }
349                 if (vflag && rval == 0)
350                         (void)printf("%s\n", f);
351         }
352 }
353
354 /*
355  * rm_overwrite --
356  *      Overwrite the file 3 times with varying bit patterns.
357  *
358  * XXX
359  * This is a cheap way to *really* delete files.  Note that only regular
360  * files are deleted, directories (and therefore names) will remain.
361  * Also, this assumes a fixed-block filesystem (like FFS, or a V7 or a
362  * System V filesystem).  In a logging filesystem, you'll have to have
363  * kernel support.
364  */
365 void
366 rm_overwrite(char *file, struct stat *sbp)
367 {
368         struct stat sb;
369         struct statfs fsb;
370         off_t len;
371         int bsize, fd, wlen;
372         char *buf = NULL;
373
374         fd = -1;
375         if (sbp == NULL) {
376                 if (lstat(file, &sb))
377                         goto err;
378                 sbp = &sb;
379         }
380         if (!S_ISREG(sbp->st_mode))
381                 return;
382         if ((fd = open(file, O_WRONLY, 0)) == -1)
383                 goto err;
384         if (fstatfs(fd, &fsb) == -1)
385                 goto err;
386         bsize = MAX(fsb.f_iosize, 1024);
387         if ((buf = malloc(bsize)) == NULL)
388                 err(1, "malloc");
389
390 #define PASS(byte) {                                                    \
391         memset(buf, byte, bsize);                                       \
392         for (len = sbp->st_size; len > 0; len -= wlen) {                \
393                 wlen = len < bsize ? len : bsize;                       \
394                 if (write(fd, buf, wlen) != wlen)                       \
395                         goto err;                                       \
396         }                                                               \
397 }
398         PASS(0xff);
399         if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
400                 goto err;
401         PASS(0x00);
402         if (fsync(fd) || lseek(fd, (off_t)0, SEEK_SET))
403                 goto err;
404         PASS(0xff);
405         if (!fsync(fd) && !close(fd)) {
406                 free(buf);
407                 return;
408         }
409
410 err:    eval = 1;
411         if (buf)
412                 free(buf);
413         warn("%s", file);
414 }
415
416
417 int
418 check(char *path, char *name, struct stat *sp)
419 {
420         int ch, first;
421         char modep[15], *flagsp;
422
423         /* Check -i first. */
424         if (iflag)
425                 (void)fprintf(stderr, "remove %s? ", path);
426         else {
427                 /*
428                  * If it's not a symbolic link and it's unwritable and we're
429                  * talking to a terminal, ask.  Symbolic links are excluded
430                  * because their permissions are meaningless.  Check stdin_ok
431                  * first because we may not have stat'ed the file.
432                  */
433                 if (!stdin_ok || S_ISLNK(sp->st_mode) ||
434                     (!access(name, W_OK) &&
435                     !(sp->st_flags & (SF_APPEND|SF_IMMUTABLE)) &&
436                     (!(sp->st_flags & (UF_APPEND|UF_IMMUTABLE)) || !uid)))
437                         return (1);
438                 strmode(sp->st_mode, modep);
439                 if ((flagsp = fflagstostr(sp->st_flags)) == NULL)
440                         err(1, NULL);
441                 (void)fprintf(stderr, "override %s%s%s/%s %s%sfor %s? ",
442                     modep + 1, modep[9] == ' ' ? "" : " ",
443                     user_from_uid(sp->st_uid, 0),
444                     group_from_gid(sp->st_gid, 0),
445                     *flagsp ? flagsp : "", *flagsp ? " " : "", 
446                     path);
447                 free(flagsp);
448         }
449         (void)fflush(stderr);
450
451         first = ch = getchar();
452         while (ch != '\n' && ch != EOF)
453                 ch = getchar();
454         return (first == 'y' || first == 'Y');
455 }
456
457 #define ISDOT(a)        ((a)[0] == '.' && (!(a)[1] || ((a)[1] == '.' && !(a)[2])))
458 void
459 checkdot(char **argv)
460 {
461         char *p, **save, **t;
462         int complained;
463
464         complained = 0;
465         for (t = argv; *t;) {
466                 if ((p = strrchr(*t, '/')) != NULL)
467                         ++p;
468                 else
469                         p = *t;
470                 if (ISDOT(p)) {
471                         if (!complained++)
472                                 warnx("\".\" and \"..\" may not be removed");
473                         eval = 1;
474                         for (save = t; (t[0] = t[1]) != NULL; ++t)
475                                 continue;
476                         t = save;
477                 } else
478                         ++t;
479         }
480 }
481
482 void
483 usage(void)
484 {
485
486         (void)fprintf(stderr, "%s\n%s\n",
487             "usage: rm [-f | -i] [-dPRrvW] file ...",
488             "       unlink file");
489         exit(EX_USAGE);
490 }