new version of rm from scratch and the POSIX.2 description
authorKeith Bostic <bostic@ucbvax.Berkeley.EDU>
Sat, 8 Dec 1990 16:01:02 +0000 (08:01 -0800)
committerKeith Bostic <bostic@ucbvax.Berkeley.EDU>
Sat, 8 Dec 1990 16:01:02 +0000 (08:01 -0800)
SCCS-vsn: bin/rm/Makefile 5.3
SCCS-vsn: bin/rm/rm.1 6.5
SCCS-vsn: bin/rm/rm.c 4.25

usr/src/bin/rm/Makefile
usr/src/bin/rm/rm.1
usr/src/bin/rm/rm.c

index 0cb7de9..cab2eff 100644 (file)
@@ -1,5 +1,7 @@
-#      @(#)Makefile    5.2 (Berkeley) %G%
+#      @(#)Makefile    5.3 (Berkeley) %G%
 
 PROG=  rm
 
 PROG=  rm
+LDADD= -lutil
+DPADD= ${LIBUTIL}
 
 .include <bsd.prog.mk>
 
 .include <bsd.prog.mk>
index 72c4218..37d1dcd 100644 (file)
@@ -3,7 +3,7 @@
 .\"
 .\" %sccs.include.redist.man%
 .\"
 .\"
 .\" %sccs.include.redist.man%
 .\"
-.\"     @(#)rm.1       6.4 (Berkeley) %G%
+.\"     @(#)rm.1       6.5 (Berkeley) %G%
 .\"
 .Dd 
 .Dt RM 1
 .\"
 .Dd 
 .Dt RM 1
 .Sh SYNOPSIS
 .Nm rm
 .Op Fl f Li \&| Fl i
 .Sh SYNOPSIS
 .Nm rm
 .Op Fl f Li \&| Fl i
-.Op Fl Rr
+.Op Fl dRr
 .Ar file ...
 .Sh DESCRIPTION
 .Ar file ...
 .Sh DESCRIPTION
-The rm utility removes the directory entry specified by each
-file argument.
+The
+.Nm rm
+utility attempts to remove the non-directory type files specified on the
+command line.
+If the permissions of the file do not permit writing, and the standard
+input device is a terminal, the user is prompted for confirmation.
 .Pp
 The following options are available:
 .Tw 8n
 .Pp
 The following options are available:
 .Tw 8n
+.Tp Fl d
+Attempt to remove directories as well as other types of files.
 .Tp Fl f
 .Tp Fl f
-Force each specified directory entry to be removed
-without prompting for confirmation, regardless of
-the permissions of the file to which it refers.
-Suppress diagnostic messages regarding non-existent
-operands.
+Attempt to remove the files without prompting for confirmation,
+regardless of the file's permissions.
+If the file does not exist, do not display a diagnostic message or modify
+the exit status to reflect an error.
+The
+.Fl f
+option overrides any previous
+.Fl i 
+options.
 .Tp Fl i
 .Tp Fl i
-Write a prompt to the standard error output
-requesting confirmation before removing each existing
-directory entry, regardless of the permissions
-of the file to which it refers.
+Request confirmation before attempting to remove each file, regardless of
+the file's permissions, or whether or not the standard input device is a
+terminal.
+The
+.Fl i
+option overrides any previous
+.Fl f 
+options.
 .Tp Fl R
 .Tp Fl R
-Permit directories to be removed (recursively).
+Attempt to remove the file hierarchy rooted in each file argument.
+The 
+.Fl R
+option implies the
+.Fl d
+option.
+If the
+.Fl i
+option is specified, the user is prompted for confirmation before 
+each directory's contents are processed (as well as before the attempt
+is made to remove the directory).
+If the user does not respond affirmatively, the file hierarchy rooted in
+that directory is skipped.
 .Pp
 .Tp Fl r
 Equivalent to
 .Pp
 .Tp Fl r
 Equivalent to
@@ -49,15 +75,26 @@ A pathname of a directory entry to be removed.
 .Pp
 The
 .Nm rm
 .Pp
 The
 .Nm rm
+utility removes symbolic links, not the files referenced by the links.
+.Pp
+It is an error to attempt to remove the files ``.'' and ``..''.
+.Pp
+The
+.Nm rm
 utility exits with one of the following values:
 .Tw Ds
 .Tp Li 0
 utility exits with one of the following values:
 .Tw Ds
 .Tp Li 0
-All the named directory entries were removed.
+All the named files were removed or the
+.Fl f
+option was specified and some of the directory entries did not exist.
 .Tp Li >0
 An error occurred.
 .Tp
 .Sh SEE ALSO
 .Tp Li >0
 An error occurred.
 .Tp
 .Sh SEE ALSO
-.Xr rmdir 1
+.Xr rmdir 1 ,
+.Xr stat 2 ,
+.Xr unlink 2 ,
+.Xr fts 3
 .Sh STANDARDS
 The
 .Nm rm
 .Sh STANDARDS
 The
 .Nm rm
index 58847f2..6dcf347 100644 (file)
-static char *sccsid = "@(#)rm.c        4.24 (Berkeley) %G%";
-
-/*
- * rm - for ReMoving files, directories & trees.
+/*-
+ * Copyright (c) 1990 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * %sccs.include.redist.c%
  */
 
  */
 
-#include <stdio.h>
-#include <sys/param.h>
-#include <sys/errno.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.25 (Berkeley) %G%";
+#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();
+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)
        int argc;
        char **argv;
 {
 
 main(argc, argv)
        int argc;
        char **argv;
 {
+       extern char *optarg;
        extern int optind;
        extern int optind;
-       int ch;
+       int ch, rflag;
 
 
-       fflg = !isatty(0);
-       while ((ch = getopt(argc, argv, "-Rfir")) != EOF)
-               switch((char)ch) {
-               case '-':
-                       goto endarg;
+       rflag = 0;
+       while ((ch = getopt(argc, argv, "dfiRr")) != EOF)
+               switch(ch) {
+               case 'd':
+                       dflag = 1;
+                       break;
                case 'f':
                case 'f':
-                       fflg++;
+                       fflag = 1;
+                       iflag = 0;
                        break;
                case 'i':
                        break;
                case 'i':
-                       iflg++;
+                       fflag = 0;
+                       iflag = 1;
                        break;
                case 'R':
                case 'r':
                        break;
                case 'R':
                case 'r':
-                       rflg++;
+                       rflag = 1;
                        break;
                case '?':
                default:
                        usage();
                }
                        break;
                case '?':
                default:
                        usage();
                }
-endarg:        argv += optind;
+       argc -= optind;
+       argv += optind;
 
 
-       if (!*argv) {
-               if (fflg)
-                       exit(0);
+       if (argc < 1)
                usage();
                usage();
-       }
-       do {
-               (void)rm(*argv, 0);
-       } while (*++argv);
-       exit(errcode != 0);
-}
 
 
-char   *path;          /* pointer to malloc'ed buffer for path */
-char   *pathp;         /* current pointer to end of path */
-int    pathsz;         /* size of path */
+       checkdot(argv);
+       if (!*argv)
+               exit(retval);
 
 
-#define        isdot(a)        (a[0] == '.' && (!a[1] || a[1] == '.' && !a[2]))
-/*
- * Return TRUE if sucessful. Recursive with -r (rflg)
- */
-rm(arg, level)
-       char arg[];
-{
-       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, *rindex();
-
-       cp = rindex(arg, '/');
-       if (cp == NULL)
-               cp = arg;
+       stdin_ok = isatty(STDIN_FILENO);
+
+       if (rflag)
+               rmtree(argv);
        else
        else
-               ++cp;
-       if (isdot(cp)) {
-               fprintf(stderr, "rm: cannot remove `.' or `..'\n");
-               return (0);
-       }
-       if (lstat(arg, &buf)) {
-               if (!fflg) {
-                       fprintf(stderr, "rm: %s: %s\n", arg, strerror(errno));
-                       errcode++;
-               }
-               return (0);             /* error */
+               rmfile(argv);
+       exit(retval);
+}
+
+rmtree(argv)
+       char **argv;
+{
+       register FTS *fts;
+       register FTSENT *p;
+       register int needstat;
+       struct stat sb;
+
+       /*
+        * 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 removed: %s\n", arg,
-                                   strerror(errno));
-                               errcode++;
-                       }
-                       return (0);             /* error */
-               }
-               if ((dirp = opendir(arg)) == NULL) {
-                       if (!fflg) {
-                               fprintf(stderr, "rm: cannot read %s: %s\n",
-                                   arg, strerror(errno));
-                               errcode++;
-                       }
-                       return (0);
-               }
-               if (level == 0)
-                       append(arg);
-               prevname[0] = '\0';
-               while ((dp = readdir(dirp)) != NULL) {
-                       if (isdot(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 reopen %s?: %s\n",
-                                           arg, strerror(errno));
-                                       errcode++;
-                               }
+       while (p = fts_read(fts)) {
+               switch(p->fts_info) {
+               case FTS_DC:
+               case FTS_DNR:
+               case FTS_DNX:
+                       break;
+               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.  If we need stat information, should never see
+                * FTS_NS (unless a file specified as an argument doesn't
+                * exist), since such files are in directories that will be
+                * returned FTS_DNX.
+                */
+               case FTS_NS:
+                       if (!needstat)
                                break;
                                break;
+                       if (!lstat(p->fts_accpath, &sb))        /* XXX */
+                               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: %s\n", arg,
-                                   strerror(errno));
-                               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 ||
+                   p->fts_info == FTS_DNX) {
+                       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 %s %s.\n",
+                                   p->fts_info == FTS_DNR ? "read" : "search",
+                                   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 (!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);
-                       goto rm;
+       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 (iflg) {
-               printf("rm: remove %s? ", arg);
-               if (!yes())
-                       return (0);
-       }
-rm:    if (unlink(arg) < 0) {
-               if (!fflg || iflg) {
-                       fprintf(stderr, "rm: %s: %s\n", arg, strerror(errno));
-                       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);
 }
 
 }
 
-/*
- * Get a yes/no answer from the user.
- */
-yes()
+check(path, name, sp)
+       char *path, *name;
+       struct stat *sp;
 {
 {
-       int i, b;
+       register int first, ch;
+       char modep[15], *user_from_uid(), *group_from_gid();
 
 
-       i = b = getchar();
-       while (b != '\n' && b != EOF)
-               b = getchar();
-       return (i == 'y');
+       /* 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 the 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');
 }
 
 }
 
-/*
- * Append 'name' to 'path'.
- */
-append(name)
+#define ISDOT(a)       ((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2]))
+checkdot(argv)
+       char **argv;
+{
+       register char *p, **t, **save;
+       int complained;
+
+       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;
+       }
+}
+
+error(name, val)
        char *name;
        char *name;
+       int val;
 {
 {
-       register int n;
-       char *malloc();
-
-       n = strlen(name);
-       if (path == NULL) {
-               pathsz = MAXNAMLEN + MAXPATHLEN + 2;
-               if ((path = malloc((u_int)pathsz)) == NULL) {
-                       fprintf(stderr, "rm: ran out of memory\n");
-                       exit(1);
-               }
-               pathp = path;
-       } else if (pathp + n + 2 > path + pathsz) {
-               fprintf(stderr, "rm: path name too long: %s\n", path);
-               exit(1);
-       } else if (pathp != path && pathp[-1] != '/')
-               *pathp++ = '/';
-       strcpy(pathp, name);
-       pathp += n;
+       (void)fprintf(stderr, "rm: %s: %s.\n", name, strerror(val));
+       retval = 1;
 }
 
 usage()
 {
 }
 
 usage()
 {
-       fputs("usage: rm [-rif] file ...\n", stderr);
+       (void)fprintf(stderr, "usage: rm [-dfiRr] file ...\n");
        exit(1);
 }
        exit(1);
 }