386BSD 0.1 development
[unix-history] / usr / src / bin / rm / rm.c
index 66c3a84..739ef68 100644 (file)
-static char *sccsid = "@(#)rm.c        4.5 (Berkeley) %G%";
-int    errcode;
-short  uid, euid;
+/*-
+ * 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.
+ */
+
+#ifndef lint
+char copyright[] =
+"@(#) Copyright (c) 1990 The Regents of the University of California.\n\
+ All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static char sccsid[] = "@(#)rm.c       4.26 (Berkeley) 3/10/91";
+#endif /* not lint */
 
 
-#include <stdio.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/stat.h>
-#include <ndir.h>
+#include <sys/errno.h>
+#include <fts.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
 
 
-char   *sprintf();
+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;
+{
+       extern char *optarg;
+       extern int optind;
+       int ch, rflag;
+
+       rflag = 0;
+       while ((ch = getopt(argc, argv, "dfiRr")) != EOF)
+               switch(ch) {
+               case 'd':
+                       dflag = 1;
+                       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;
+
+       if (argc < 1)
+               usage();
+
+       checkdot(argv);
+       if (!*argv)
+               exit(retval);
+
+       stdin_ok = isatty(STDIN_FILENO);
+
+       if (rflag)
+               rmtree(argv);
+       else
+               rmfile(argv);
+       exit(retval);
+}
+
+rmtree(argv)
+       char **argv;
 {
 {
-       register char *arg;
-       int fflg, iflg, rflg;
-
-       fflg = 0;
-       if (isatty(0) == 0)
-               fflg++;
-       iflg = 0;
-       rflg = 0;
-       uid = getuid();
-       euid = geteuid();
-       while(argc>1 && argv[1][0]=='-') {
-               arg = *++argv;
-               argc--;
+       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);
+       }
+       while (p = fts_read(fts)) {
+               switch(p->fts_info) {
+               case FTS_DNR:
+               case FTS_ERR:
+                       error(p->fts_path, errno);
+                       exit(1);
                /*
                /*
-                *  all files following a null option are considered file names
+                * FTS_NS: assume that if can't stat the file, it can't be
+                * unlinked.
                 */
                 */
-               if (*(arg+1) == '\0') break;
-
-               while(*++arg != '\0')
-                       switch(*arg) {
-                       case 'f':
-                               fflg++;
+               case FTS_NS:
+                       if (!needstat)
                                break;
                                break;
-                       case 'i':
-                               iflg++;
-                               break;
-                       case 'r':
-                               rflg++;
-                               break;
-                       default:
-                               printf("rm: unknown option %s\n", *argv);
-                               exit(1);
+                       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;
                        }
                        }
-       }
-       while(--argc > 0) {
-               if(!strcmp(*++argv, "..")) {
-                       fprintf(stderr, "rm: cannot remove `..'\n");
                        continue;
                        continue;
+               /* Post-order: see if user skipped. */
+               case FTS_DP:
+                       if (p->fts_number == SKIPPED)
+                               continue;
+                       break;
                }
                }
-               rm(*argv, fflg, rflg, iflg, 0);
-       }
 
 
-       exit(errcode);
+               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);
+       }
 }
 
 }
 
-rm(arg, fflg, rflg, iflg, level)
-char arg[];
+rmfile(argv)
+       char **argv;
 {
 {
-       struct stat buf;
-       struct direct *dp;
-       DIR *dirp;
-       char name[BUFSIZ];
-       int d;
-
-       if(stat(arg, &buf)) {
-               if (fflg==0) {
-                       printf("rm: %s nonexistent\n", arg);
-                       ++errcode;
-               }
-               return;
-       }
-       if ((buf.st_mode&S_IFMT) == S_IFDIR) {
-               if(rflg) {
-                       if (access(arg, 02) < 0) {
-                               if (fflg==0)
-                                       printf("%s not changed\n", arg);
-                               errcode++;
-                               return;
-                       }
-                       if(iflg && level!=0) {
-                               printf("remove directory %s? ", arg);
-                               if(!yes())
-                                       return;
-                       }
-                       if((dirp = opendir(arg)) == NULL) {
-                               printf("rm: cannot read %s?\n", arg);
-                               exit(1);
-                       }
-                       while((dp = readdir(dirp)) != NULL) {
-                               if(dp->d_ino != 0 && !dotname(dp->d_name)) {
-                                       sprintf(name, "%s/%.14s", arg, dp->d_name);
-                                       rm(name, fflg, rflg, iflg, level+1);
-                               }
-                       }
-                       closedir(dirp);
-                       errcode += rmdir(arg, iflg);
-                       return;
-               }
-               printf("rm: %s directory\n", arg);
-               ++errcode;
-               return;
-       }
+       register int df;
+       register char *f;
+       struct stat sb;
 
 
-       if(iflg) {
-               printf("rm: remove %s? ", arg);
-               if(!yes())
-                       return;
-       }
-       else if(!fflg) {
-               if (access(arg, 02)<0) {
-                       if (uid == buf.st_uid || euid == buf.st_uid) {
-                               printf("rm: override protection %o for %s? ",
-                                       buf.st_mode&0777, arg);
-                               if(!yes())
-                                       return;
-                       } else {
-                               printf("rm: %s: not owner.\n", arg);
-                               return;
-                       }
+       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) && (fflg==0 || iflg)) {
-               printf("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;
+               }
+               if (!fflag && !check(f, f, &sb))
+                       continue;
+               if ((S_ISDIR(sb.st_mode) ? rmdir(f) : unlink(f)) &&
+                   (!fflag || errno != ENOENT))
+                       error(f, errno);
        }
 }
 
        }
 }
 
-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')
+       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);
                        return(1);
-       return(0);
+               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');
 }
 
 }
 
-rmdir(f, iflg)
-char *f;
+#define ISDOT(a)       ((a)[0] == '.' && (!(a)[1] || (a)[1] == '.' && !(a)[2]))
+checkdot(argv)
+       char **argv;
 {
 {
-       int status, i;
-
-       if(dotname(f))
-               return(0);
-       if(iflg) {
-               printf("rm: remove %s? ", f);
-               if(!yes())
-                       return(0);
-       }
-       while((i=fork()) == -1)
-               sleep(3);
-       if(i) {
-               wait(&status);
-               return(status);
+       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;
        }
        }
-       execl("/bin/rmdir", "rmdir", f, 0);
-       execl("/usr/bin/rmdir", "rmdir", f, 0);
-       printf("rm: can't find rmdir\n");
-       exit(1);
 }
 
 }
 
-yes()
+error(name, val)
+       char *name;
+       int val;
 {
 {
-       int i, b;
+       (void)fprintf(stderr, "rm: %s: %s.\n", name, strerror(val));
+       retval = 1;
+}
 
 
-       i = b = getchar();
-       while(b != '\n' && b != EOF)
-               b = getchar();
-       return(i == 'y');
+usage()
+{
+       (void)fprintf(stderr, "usage: rm [-dfiRr] file ...\n");
+       exit(1);
 }
 }