386BSD 0.1 development
[unix-history] / usr / src / bin / rm / rm.c
index 5be8189..739ef68 100644 (file)
-static char *sccsid = "@(#)rm.c        4.16 (Berkeley) %G%";
-
-/*
- * rm - for ReMoving files, directories & trees.
+/*-
+ * Copyright (c) 1990 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.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 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.
  */
 
  */
 
-#include <stdio.h>
-#include <sys/param.h>
-#include <sys/stat.h>
-#include <sys/dir.h>
-#include <sys/file.h>
+#ifndef lint
+char copyright[] =
+"@(#) Copyright (c) 1990 The Regents of the University of California.\n\
+ All rights reserved.\n";
+#endif /* not lint */
 
 
-int    fflg;           /* -f force - supress error messages */
-int    iflg;           /* -i interrogate user on each file */
-int    rflg;           /* -r recurse */
+#ifndef lint
+static char sccsid[] = "@(#)rm.c       4.26 (Berkeley) 3/10/91";
+#endif /* not lint */
 
 
-int    errcode;        /* true if errors occured */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/errno.h>
+#include <fts.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
 
 
-char   *strcpy(), *malloc(), *realloc();
+int dflag, fflag, iflag, retval, stdin_ok;
+
+/*
+ * 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.
+ */
 
 main(argc, argv)
 
 main(argc, argv)
-       char *argv[];
+       int argc;
+       char **argv;
 {
 {
-       register char *arg;
+       extern char *optarg;
+       extern int optind;
+       int ch, rflag;
 
 
-       fflg = !isatty(0);
-       iflg = 0;
-       rflg = 0;
-       while (argc > 1 && argv[1][0] == '-') {
-               arg = *++argv;
-               argc--;
-
-               /*
-                *  all files following a null option are considered file names
-                */
-               if (arg[1] == '\0')
+       rflag = 0;
+       while ((ch = getopt(argc, argv, "dfiRr")) != EOF)
+               switch(ch) {
+               case 'd':
+                       dflag = 1;
                        break;
                        break;
+               case 'f':
+                       fflag = 1;
+                       iflag = 0;
+                       break;
+               case 'i':
+                       fflag = 0;
+                       iflag = 1;
+                       break;
+               case 'R':
+               case 'r':                       /* compatibility */
+                       rflag = 1;
+                       break;
+               case '?':
+               default:
+                       usage();
+               }
+       argc -= optind;
+       argv += optind;
 
 
-               while (*++arg != '\0')
-                       switch(*arg) {
-                       case 'f':
-                               fflg++;
-                               break;
-
-                       case 'i':
-                               iflg++;
-                               break;
-
-                       case 'R':
-                       case 'r':
-                               rflg++;
-                               break;
-
-                       default:
-                               fprintf(stderr, "usage: rm [-rif] file ...\n");
-                               exit(1);
-                       }
-       }
+       if (argc < 1)
+               usage();
 
 
-       if (argc < 2) {
-               fprintf(stderr, "usage: rm [-rif] file ...\n");
-               exit(1);
-       }
+       checkdot(argv);
+       if (!*argv)
+               exit(retval);
 
 
-       while (--argc > 0)
-               (void) rm(*++argv, 0);
+       stdin_ok = isatty(STDIN_FILENO);
 
 
-       exit(errcode != 0);
+       if (rflag)
+               rmtree(argv);
+       else
+               rmfile(argv);
+       exit(retval);
 }
 
 }
 
-char   *path;          /* pointer to malloc'ed buffer for path */
-char   *pathp;         /* current pointer to end of path */
-int    pathsz;         /* size of path */
-
-/*
- * Return TRUE if sucessful. Recursive with -r (rflg)
- */
-rm(arg, level)
-       char arg[];
+rmtree(argv)
+       char **argv;
 {
 {
-       int ok;                         /* true if recursive rm succeeded */
-       struct stat buf;                /* for finding out what a file is */
-       struct direct *dp;              /* for reading a directory */
-       DIR *dirp;                      /* for reading a directory */
-       char prevname[MAXNAMLEN + 1];   /* previous name for -r */
-       char *cp;
+       register FTS *fts;
+       register FTSENT *p;
+       register int needstat;
+       struct stat sb;
 
 
-       if (dotname(arg)) {
-               fprintf(stderr, "rm: cannot remove `.' or `..'\n");
-               return (0);
-       }
-       if (lstat(arg, &buf)) {
-               if (!fflg) {
-                       fprintf(stderr, "rm: %s nonexistent\n", arg);
-                       errcode++;
-               }
-               return (0);             /* error */
+       /*
+        * Remove a file hierarchy.  If forcing removal (-f), or interactive
+        * (-i) or can't ask anyway (stdin_ok), don't stat the file.
+        */
+       needstat = !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
+
+       if (!(fts = fts_open(argv,
+           needstat ? FTS_PHYSICAL : FTS_PHYSICAL|FTS_NOSTAT,
+           (int (*)())NULL))) {
+               (void)fprintf(stderr, "rm: %s.\n", strerror(errno));
+               exit(1);
        }
        }
-       if ((buf.st_mode&S_IFMT) == S_IFDIR) {
-               if (!rflg) {
-                       if (!fflg) {
-                               fprintf(stderr, "rm: %s directory\n", arg);
-                               errcode++;
-                       }
-                       return (0);
-               }
-               if (iflg && level != 0) {
-                       printf("rm: remove directory %s? ", arg);
-                       if (!yes())
-                               return (0);     /* didn't remove everything */
-               }
-               if (access(arg, R_OK|W_OK|X_OK) != 0) {
-                       if (rmdir(arg) == 0)
-                               return (1);     /* salvaged: removed empty dir */
-                       if (!fflg) {
-                               fprintf(stderr, "rm: %s not changed\n", arg);
-                               errcode++;
-                       }
-                       return (0);             /* error */
-               }
-               if ((dirp = opendir(arg)) == NULL) {
-                       if (!fflg) {
-                               fprintf(stderr, "rm: cannot read %s?\n", arg);
-                               errcode++;
-                       }
-                       return (0);
-               }
-               if (level == 0)
-                       append(arg);
-               prevname[0] = '\0';
-               while ((dp = readdir(dirp)) != NULL) {
-                       if (dotname(dp->d_name)) {
-                               strcpy(prevname, dp->d_name);
-                               continue;
-                       }
-                       append(dp->d_name);
-                       closedir(dirp);
-                       ok = rm(path, level + 1);
-                       for (cp = pathp; *--cp != '/' && cp > path; )
-                               ;
-                       pathp = cp;
-                       *cp++ = '\0';
-                       if ((dirp = opendir(arg)) == NULL) {
-                               if (!fflg) {
-                                       fprintf(stderr, "rm: cannot read %s?\n", arg);
-                                       errcode++;
-                               }
+       while (p = fts_read(fts)) {
+               switch(p->fts_info) {
+               case FTS_DNR:
+               case FTS_ERR:
+                       error(p->fts_path, errno);
+                       exit(1);
+               /*
+                * FTS_NS: assume that if can't stat the file, it can't be
+                * unlinked.
+                */
+               case FTS_NS:
+                       if (!needstat)
                                break;
                                break;
+                       if (!fflag || errno != ENOENT)
+                               error(p->fts_path, errno);
+                       continue;
+               /* Pre-order: give user chance to skip. */
+               case FTS_D:
+                       if (iflag && !check(p->fts_path, p->fts_accpath,
+                           &p->fts_statb)) {
+                               (void)fts_set(fts, p, FTS_SKIP);
+                               p->fts_number = SKIPPED;
                        }
                        }
-                       /* pick up where we left off */
-                       if (prevname[0] != '\0') {
-                               while ((dp = readdir(dirp)) != NULL &&
-                                   strcmp(prevname, dp->d_name) != 0)
-                                       ;
-                       }
-                       /* skip the one we just failed to delete */
-                       if (!ok) {
-                               dp = readdir(dirp);
-                               if (dp != NULL && strcmp(cp, dp->d_name)) {
-                                       fprintf(stderr,
-                       "rm: internal synchronization error: %s, %s, %s\n",
-                                               arg, cp, dp->d_name);
-                               }
-                               strcpy(prevname, dp->d_name);
-                       }
-               }
-               closedir(dirp);
-               if (level == 0) {
-                       pathp = path;
-                       *pathp = '\0';
-               }
-               if (iflg) {
-                       printf("rm: remove %s? ", arg);
-                       if (!yes())
-                               return (0);
-               }
-               if (rmdir(arg) < 0) {
-                       if (!fflg || iflg) {
-                               fprintf(stderr, "rm: %s not removed\n", arg);
-                               errcode++;
-                       }
-                       return (0);
+                       continue;
+               /* Post-order: see if user skipped. */
+               case FTS_DP:
+                       if (p->fts_number == SKIPPED)
+                               continue;
+                       break;
                }
                }
-               return (1);
+
+               if (!fflag &&
+                   !check(p->fts_path, p->fts_accpath, &p->fts_statb))
+                       continue;
+
+               /*
+                * 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.
+                */
+               if (p->fts_info == FTS_DP || p->fts_info == FTS_DNR) {
+                       if (!rmdir(p->fts_accpath))
+                               continue;
+                       if (errno == ENOENT) {
+                               if (fflag)
+                                       continue;
+                       } else if (p->fts_info != FTS_DP)
+                               (void)fprintf(stderr,
+                                   "rm: unable to read %s.\n", p->fts_path);
+               } else if (!unlink(p->fts_accpath) || fflag && errno == ENOENT)
+                       continue;
+               error(p->fts_path, errno);
        }
        }
+}
+
+rmfile(argv)
+       char **argv;
+{
+       register int df;
+       register char *f;
+       struct stat sb;
 
 
-       if (iflg) {
-               printf("rm: remove %s? ", arg);
-               if (!yes())
-                       return (0);
-       } else if (!fflg) {
-               if ((buf.st_mode&S_IFMT) != S_IFLNK && access(arg, W_OK) < 0) {
-                       printf("rm: override protection %o for %s? ",
-                               buf.st_mode&0777, arg);
-                       if (!yes())
-                               return (0);
+       df = dflag;
+       /*
+        * 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++) {
+               /* Assume if can't stat the file, can't unlink it. */
+               if (lstat(f, &sb)) {
+                       if (!fflag || errno != ENOENT)
+                               error(f, errno);
+                       continue;
                }
                }
-       }
-       if (unlink(arg) < 0) {
-               if (!fflg || iflg) {
-                       fprintf(stderr, "rm: %s not removed\n", arg);
-                       errcode++;
+               if (S_ISDIR(sb.st_mode) && !df) {
+                       (void)fprintf(stderr, "rm: %s: is a directory\n", f);
+                       retval = 1;
+                       continue;
                }
                }
-               return (0);
+               if (!fflag && !check(f, f, &sb))
+                       continue;
+               if ((S_ISDIR(sb.st_mode) ? rmdir(f) : unlink(f)) &&
+                   (!fflag || errno != ENOENT))
+                       error(f, errno);
        }
        }
-       return (1);
 }
 
 }
 
-/*
- * boolean: is it "." or ".." ?
- */
-dotname(s)
-       char *s;
+check(path, name, sp)
+       char *path, *name;
+       struct stat *sp;
 {
 {
-       if (s[0] == '.')
-               if (s[1] == '.')
-                       if (s[2] == '\0')
-                               return (1);
-                       else
-                               return (0);
-               else if (s[1] == '\0')
-                       return (1);
-       return (0);
+       register int first, ch;
+       char modep[15], *user_from_uid(), *group_from_gid();
+
+       /* 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.
+                */
+               if (S_ISLNK(sp->st_mode) || !stdin_ok || !access(name, W_OK))
+                       return(1);
+               strmode(sp->st_mode, modep);
+               (void)fprintf(stderr, "override %s%s%s/%s for %s? ",
+                   modep + 1, modep[9] == ' ' ? "" : " ",
+                   user_from_uid(sp->st_uid, 0),
+                   group_from_gid(sp->st_gid, 0), path);
+       }
+       (void)fflush(stderr);
+
+       first = ch = getchar();
+       while (ch != '\n' && ch != EOF)
+               ch = getchar();
+       return(first == 'y');
 }
 
 }
 
-/*
- * Get a yes/no answer from the user.
- */
-yes()
+#define ISDOT(a)       ((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2]))
+checkdot(argv)
+       char **argv;
 {
 {
-       int i, b;
+       register char *p, **t, **save;
+       int complained;
 
 
-       i = b = getchar();
-       while (b != '\n' && b != EOF)
-               b = getchar();
-       return (i == 'y');
+       complained = 0;
+       for (t = argv; *t;) {
+               if (p = rindex(*t, '/'))
+                       ++p;
+               else
+                       p = *t;
+               if (ISDOT(p)) {
+                       if (!complained++)
+                           (void)fprintf(stderr,
+                               "rm: \".\" and \"..\" may not be removed.\n");
+                       retval = 1;
+                       for (save = t; t[0] = t[1]; ++t);
+                       t = save;
+               } else
+                       ++t;
+       }
 }
 
 }
 
-/*
- * Append 'name' to 'path'.
- */
-append(name)
+error(name, val)
        char *name;
        char *name;
+       int val;
 {
 {
-       register int n;
+       (void)fprintf(stderr, "rm: %s: %s.\n", name, strerror(val));
+       retval = 1;
+}
 
 
-       n = strlen(name);
-       if (path == NULL) {
-               pathsz = n + 2048;
-               if ((path = malloc(pathsz)) == NULL) {
-                       fprintf(stderr, "rm: ran out of memory\n");
-                       exit(1);
-               }
-               pathp = path;
-       } else if (pathp + n + 2 > path + pathsz) {
-               pathsz = n + 2048;
-               if ((path = realloc(path, pathsz)) == NULL) {
-                       fprintf(stderr, "rm: ran out of memory\n");
-                       exit(1);
-               }
-               pathp = path;
-       } else if (pathp != path && pathp[-1] != '/')
-               *pathp++ = '/';
-       strcpy(pathp, name);
-       pathp += n;
+usage()
+{
+       (void)fprintf(stderr, "usage: rm [-dfiRr] file ...\n");
+       exit(1);
 }
 }