home *** CD-ROM | disk | FTP | other *** search
/ PC-Online 1996 May / PCOnline_05_1996.bin / linux / source / a / bin / fileutil.12 / fileutil / fileutils-3.12 / src / rm.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-10-07  |  13.7 KB  |  544 lines

  1. /* `rm' file deletion utility for GNU.
  2.    Copyright (C) 1988, 1990, 1991, 1994 Free Software Foundation, Inc.
  3.  
  4.    This program is free software; you can redistribute it and/or modify
  5.    it under the terms of the GNU General Public License as published by
  6.    the Free Software Foundation; either version 2, or (at your option)
  7.    any later version.
  8.  
  9.    This program is distributed in the hope that it will be useful,
  10.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.    GNU General Public License for more details.
  13.  
  14.    You should have received a copy of the GNU General Public License
  15.    along with this program; if not, write to the Free Software
  16.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
  17.  
  18. /* Written by Paul Rubin, David MacKenzie, and Richard Stallman. */
  19.  
  20. #include <config.h>
  21. #include <stdio.h>
  22. #include <getopt.h>
  23. #include <sys/types.h>
  24. #include "system.h"
  25. #include "version.h"
  26. #include "safe-lstat.h"
  27.  
  28. #ifdef D_INO_IN_DIRENT
  29. #define D_INO(dp) ((dp)->d_ino)
  30. #else
  31. /* POSIX.1 doesn't have inodes, so fake them to avoid lots of ifdefs. */
  32. #define D_INO(dp) 1
  33. #endif
  34.  
  35. char *basename ();
  36. char *stpcpy ();
  37. char *xmalloc ();
  38. char *xrealloc ();
  39. int eaccess_stat ();
  40. int yesno ();
  41. void error ();
  42. void strip_trailing_slashes ();
  43.  
  44. static int clear_directory ();
  45. static int duplicate_entry ();
  46. static int remove_dir ();
  47. static int remove_file ();
  48. static int rm ();
  49. static void usage ();
  50.  
  51. /* Name this program was run with.  */
  52. char *program_name;
  53.  
  54. /* Path of file now being processed; extended as necessary. */
  55. static char *pathname;
  56.  
  57. /* Number of bytes currently allocated for `pathname';
  58.    made larger when necessary, but never smaller.  */
  59. static int pnsize;
  60.  
  61. /* If nonzero, display the name of each file removed. */
  62. static int verbose;
  63.  
  64. /* If nonzero, ignore nonexistant files. */
  65. static int ignore_missing_files;
  66.  
  67. /* If nonzero, recursively remove directories. */
  68. static int recursive;
  69.  
  70. /* If nonzero, query the user about whether to remove each file. */
  71. static int interactive;
  72.  
  73. /* If nonzero, remove directories with unlink instead of rmdir, and don't
  74.    require a directory to be empty before trying to unlink it.
  75.    Only works for the super-user. */
  76. static int unlink_dirs;
  77.  
  78. /* If nonzero, stdin is a tty. */
  79. static int stdin_tty;
  80.  
  81. /* If non-zero, display usage information and exit.  */
  82. static int show_help;
  83.  
  84. /* If non-zero, print the version on standard output and exit.  */
  85. static int show_version;
  86.  
  87. static struct option const long_opts[] =
  88. {
  89.   {"directory", no_argument, &unlink_dirs, 1},
  90.   {"force", no_argument, NULL, 'f'},
  91.   {"interactive", no_argument, NULL, 'i'},
  92.   {"recursive", no_argument, &recursive, 1},
  93.   {"verbose", no_argument, &verbose, 1},
  94.   {"help", no_argument, &show_help, 1},
  95.   {"version", no_argument, &show_version, 1},
  96.   {NULL, 0, NULL, 0}
  97. };
  98.  
  99. void
  100. main (argc, argv)
  101.      int argc;
  102.      char **argv;
  103. {
  104.   int err = 0;
  105.   int c;
  106.  
  107.   verbose = ignore_missing_files = recursive = interactive
  108.     = unlink_dirs = 0;
  109.   pnsize = 256;
  110.   pathname = xmalloc (pnsize);
  111.   program_name = argv[0];
  112.  
  113.   while ((c = getopt_long (argc, argv, "dfirvR", long_opts, (int *) 0)) != EOF)
  114.     {
  115.       switch (c)
  116.     {
  117.     case 0:        /* Long option. */
  118.       break;
  119.     case 'd':
  120.       unlink_dirs = 1;
  121.       break;
  122.     case 'f':
  123.       interactive = 0;
  124.       ignore_missing_files = 1;
  125.       break;
  126.     case 'i':
  127.       interactive = 1;
  128.       ignore_missing_files = 0;
  129.       break;
  130.     case 'r':
  131.     case 'R':
  132.       recursive = 1;
  133.       break;
  134.     case 'v':
  135.       verbose = 1;
  136.       break;
  137.     default:
  138.       usage (1);
  139.     }
  140.     }
  141.  
  142.   if (show_version)
  143.     {
  144.       printf ("%s\n", version_string);
  145.       exit (0);
  146.     }
  147.  
  148.   if (show_help)
  149.     usage (0);
  150.  
  151.   if (optind == argc)
  152.     {
  153.       if (ignore_missing_files)
  154.     exit (0);
  155.       else
  156.     {
  157.       error (0, 0, "too few arguments");
  158.       usage (1);
  159.     }
  160.     }
  161.  
  162.   stdin_tty = isatty (0);
  163.  
  164.   for (; optind < argc; optind++)
  165.     {
  166.       int len;
  167.  
  168.       /* Stripping slashes is harmless for rmdir;
  169.      if the arg is not a directory, it will fail with ENOTDIR.  */
  170.       strip_trailing_slashes (argv[optind]);
  171.       len = strlen (argv[optind]);
  172.       if (len + 1 > pnsize)
  173.     {
  174.       free (pathname);
  175.       pnsize = 2 * (len + 1);
  176.       pathname = xmalloc (pnsize);
  177.     }
  178.       strcpy (pathname, argv[optind]);
  179.       err += rm ();
  180.     }
  181.  
  182.   exit (err > 0);
  183. }
  184.  
  185. /* Remove file or directory `pathname' after checking appropriate things.
  186.    Return 0 if `pathname' is removed, 1 if not. */
  187.  
  188. static int
  189. rm ()
  190. {
  191.   struct stat path_stats;
  192.   char *base = basename (pathname);
  193.  
  194.   if (base[0] == '.' && (base[1] == '\0'
  195.                  || (base[1] == '.' && base[2] == '\0')))
  196.     {
  197.       error (0, 0, "cannot remove `.' or `..'");
  198.       return 1;
  199.     }
  200.  
  201.   if (SAFE_LSTAT (pathname, &path_stats))
  202.     {
  203.       if (errno == ENOENT && ignore_missing_files)
  204.     return 0;
  205.       error (0, errno, "%s", pathname);
  206.       return 1;
  207.     }
  208.  
  209.   if (S_ISDIR (path_stats.st_mode) && !unlink_dirs)
  210.     return remove_dir (&path_stats);
  211.   else
  212.     return remove_file (&path_stats);
  213. }
  214.  
  215. /* Query the user if appropriate, and if ok try to remove the
  216.    non-directory `pathname', which STATP contains info about.
  217.    Return 0 if `pathname' is removed, 1 if not. */
  218.  
  219. static int
  220. remove_file (statp)
  221.      struct stat *statp;
  222. {
  223.   if (!ignore_missing_files && (interactive || stdin_tty)
  224.       && eaccess_stat (statp, W_OK, pathname)
  225. #ifdef S_ISLNK
  226.       && !S_ISLNK (statp->st_mode)
  227. #endif
  228.       )
  229.     {
  230.       fprintf (stderr, "%s: remove %s`%s', overriding mode %04o? ",
  231.            program_name,
  232.            S_ISDIR (statp->st_mode) ? "directory " : "",
  233.            pathname,
  234.            (unsigned int) (statp->st_mode & 07777));
  235.       if (!yesno ())
  236.     return 1;
  237.     }
  238.   else if (interactive)
  239.     {
  240.       fprintf (stderr, "%s: remove %s`%s'? ", program_name,
  241.            S_ISDIR (statp->st_mode) ? "directory " : "",
  242.            pathname);
  243.       if (!yesno ())
  244.     return 1;
  245.     }
  246.  
  247.   if (verbose)
  248.     printf ("%s\n", pathname);
  249.  
  250.   if (unlink (pathname) && (errno != ENOENT || !ignore_missing_files))
  251.     {
  252.       error (0, errno, "%s", pathname);
  253.       return 1;
  254.     }
  255.   return 0;
  256. }
  257.  
  258. /* If not in recursive mode, print an error message and return 1.
  259.    Otherwise, query the user if appropriate, then try to recursively
  260.    remove directory `pathname', which STATP contains info about.
  261.    Return 0 if `pathname' is removed, 1 if not. */
  262.  
  263. static int
  264. remove_dir (statp)
  265.      struct stat *statp;
  266. {
  267.   int err;
  268.  
  269.   if (!recursive)
  270.     {
  271.       error (0, 0, "%s: is a directory", pathname);
  272.       return 1;
  273.     }
  274.  
  275.   if (!ignore_missing_files && (interactive || stdin_tty)
  276.       && eaccess_stat (statp, W_OK, pathname))
  277.     {
  278.       fprintf (stderr,
  279.            "%s: descend directory `%s', overriding mode %04o? ",
  280.            program_name, pathname,
  281.            (unsigned int) (statp->st_mode & 07777));
  282.       if (!yesno ())
  283.     return 1;
  284.     }
  285.   else if (interactive)
  286.     {
  287.       fprintf (stderr, "%s: descend directory `%s'? ",
  288.            program_name, pathname);
  289.       if (!yesno ())
  290.     return 1;
  291.     }
  292.  
  293.   if (verbose)
  294.     printf ("%s\n", pathname);
  295.  
  296.   err = clear_directory (statp);
  297.  
  298.   if (interactive)
  299.     {
  300.       if (err)
  301.     fprintf (stderr, "%s: remove directory `%s' (might be nonempty)? ",
  302.          program_name, pathname);
  303.       else
  304.     fprintf (stderr, "%s: remove directory `%s'? ",
  305.          program_name, pathname);
  306.       if (!yesno ())
  307.     return 1;
  308.     }
  309.  
  310.   if (rmdir (pathname) && (errno != ENOENT || !ignore_missing_files))
  311.     {
  312.       error (0, errno, "%s", pathname);
  313.       return 1;
  314.     }
  315.   return 0;
  316. }
  317.  
  318. /* An element in a stack of pointers into `pathname'.
  319.    `pathp' points to where in `pathname' the terminating '\0' goes
  320.    for this level's directory name. */
  321. struct pathstack
  322. {
  323.   struct pathstack *next;
  324.   char *pathp;
  325.   ino_t inum;
  326. };
  327.  
  328. /* Linked list of pathnames of directories in progress in recursive rm.
  329.    The entries actually contain pointers into `pathname'.
  330.    `pathstack' is the current deepest level. */
  331. static struct pathstack *pathstack = NULL;
  332.  
  333. /* Read directory `pathname' and remove all of its entries,
  334.    avoiding use of chdir.
  335.    On entry, STATP points to the results of stat on `pathname'.
  336.    Return 0 for success, error count for failure.
  337.    Upon return, `pathname' will have the same contents as before,
  338.    but its address might be different; in that case, `pnsize' will
  339.    be larger, as well. */
  340.  
  341. static int
  342. clear_directory (statp)
  343.      struct stat *statp;
  344. {
  345.   DIR *dirp;
  346.   struct dirent *dp;
  347.   char *name_space;        /* Copy of directory's filenames. */
  348.   char *namep;            /* Current entry in `name_space'. */
  349.   unsigned name_size;        /* Bytes allocated for `name_space'. */
  350.   int name_length;        /* Length of filename in `namep' plus '\0'. */
  351.   int pathname_length;        /* Length of `pathname'. */
  352.   ino_t *inode_space;        /* Copy of directory's inodes. */
  353.   ino_t *inodep;        /* Current entry in `inode_space'. */
  354.   unsigned n_inodes_allocated;    /* There is space for this many inodes
  355.                       in `inode_space'. */
  356.   int err = 0;            /* Return status. */
  357.   struct pathstack pathframe;    /* New top of stack. */
  358.   struct pathstack *pp;        /* Temporary. */
  359.  
  360.   name_size = statp->st_size;
  361.   name_space = (char *) xmalloc (name_size);
  362.  
  363.   n_inodes_allocated = (statp->st_size + sizeof (ino_t) - 1) / sizeof (ino_t);
  364.   inode_space = (ino_t *) xmalloc (n_inodes_allocated * sizeof (ino_t));
  365.  
  366.   do
  367.     {
  368.       namep = name_space;
  369.       inodep = inode_space;
  370.  
  371.       errno = 0;
  372.       dirp = opendir (pathname);
  373.       if (dirp == NULL)
  374.     {
  375.       if (errno != ENOENT || !ignore_missing_files)
  376.         {
  377.           error (0, errno, "%s", pathname);
  378.           err = 1;
  379.         }
  380.       free (name_space);
  381.       free (inode_space);
  382.       return err;
  383.     }
  384.  
  385.       while ((dp = readdir (dirp)) != NULL)
  386.     {
  387.       /* Skip "." and ".." (some NFS filesystems' directories lack them). */
  388.       if (dp->d_name[0] != '.'
  389.           || (dp->d_name[1] != '\0'
  390.           && (dp->d_name[1] != '.' || dp->d_name[2] != '\0')))
  391.         {
  392.           unsigned size_needed = (namep - name_space) + NLENGTH (dp) + 2;
  393.  
  394.           if (size_needed > name_size)
  395.         {
  396.           char *new_name_space;
  397.  
  398.           while (size_needed > name_size)
  399.             name_size += 1024;
  400.  
  401.           new_name_space = xrealloc (name_space, name_size);
  402.           namep += new_name_space - name_space;
  403.           name_space = new_name_space;
  404.         }
  405.           namep = stpcpy (namep, dp->d_name) + 1;
  406.  
  407.           if (inodep == inode_space + n_inodes_allocated)
  408.         {
  409.           ino_t *new_inode_space;
  410.  
  411.           n_inodes_allocated += 1024;
  412.           new_inode_space = (ino_t *) xrealloc (inode_space,
  413.                     n_inodes_allocated * sizeof (ino_t));
  414.           inodep += new_inode_space - inode_space;
  415.           inode_space = new_inode_space;
  416.         }
  417.           *inodep++ = D_INO (dp);
  418.         }
  419.     }
  420.       *namep = '\0';
  421.       if (CLOSEDIR (dirp))
  422.     {
  423.       error (0, errno, "%s", pathname);
  424.       err = 1;
  425.     }
  426.  
  427.       pathname_length = strlen (pathname);
  428.  
  429.       for (namep = name_space, inodep = inode_space; *namep != '\0';
  430.        namep += name_length, inodep++)
  431.     {
  432.       name_length = strlen (namep) + 1;
  433.  
  434.       /* Satisfy GNU requirement that filenames can be arbitrarily long. */
  435.       if (pathname_length + 1 + name_length > pnsize)
  436.         {
  437.           char *new_pathname;
  438.  
  439.           pnsize = (pathname_length + 1 + name_length) * 2;
  440.           new_pathname = xrealloc (pathname, pnsize);
  441.           /* Update the all the pointers in the stack to use the new area. */
  442.           for (pp = pathstack; pp != NULL; pp = pp->next)
  443.         pp->pathp += new_pathname - pathname;
  444.           pathname = new_pathname;
  445.         }
  446.  
  447.       /* Add a new frame to the top of the path stack. */
  448.       pathframe.pathp = pathname + pathname_length;
  449.       pathframe.inum = *inodep;
  450.       pathframe.next = pathstack;
  451.       pathstack = &pathframe;
  452.  
  453.       /* Append '/' and the filename to current pathname, take care of the
  454.          file (which could result in recursive calls), and take the filename
  455.          back off. */
  456.  
  457.       *pathstack->pathp = '/';
  458.       strcpy (pathstack->pathp + 1, namep);
  459.  
  460.       /* If the i-number has already appeared, there's an error. */
  461.       if (duplicate_entry (pathstack->next, pathstack->inum))
  462.         err++;
  463.       else if (rm ())
  464.         err++;
  465.  
  466.       *pathstack->pathp = '\0';
  467.       pathstack = pathstack->next;    /* Pop the stack. */
  468.     }
  469.     }
  470.   /* Keep trying while there are still files to remove. */
  471.   while (namep > name_space && err == 0);
  472.  
  473.   free (name_space);
  474.   free (inode_space);
  475.   return err;
  476. }
  477.  
  478. /* If STACK does not already have an entry with the same i-number as INUM,
  479.    return 0. Otherwise, ask the user whether to continue;
  480.    if yes, return 1, and if no, exit.
  481.    This assumes that no one tries to remove filesystem mount points;
  482.    doing so could cause duplication of i-numbers that would not indicate
  483.    a corrupted file system. */
  484.  
  485. static int
  486. duplicate_entry (stack, inum)
  487.      struct pathstack *stack;
  488.      ino_t inum;
  489. {
  490. #ifndef _POSIX_SOURCE
  491.   struct pathstack *p;
  492.  
  493.   for (p = stack; p != NULL; p = p->next)
  494.     {
  495.       if (p->inum == inum)
  496.     {
  497.       fprintf (stderr, "\
  498. %s: WARNING: Circular directory structure.\n\
  499. This almost certainly means that you have a corrupted file system.\n\
  500. NOTIFY YOUR SYSTEM MANAGER.\n\
  501. Cycle detected:\n\
  502. %s\n\
  503. is the same file as\n", program_name, pathname);
  504.       *p->pathp = '\0';    /* Truncate pathname. */
  505.       fprintf (stderr, "%s\n", pathname);
  506.       *p->pathp = '/';    /* Put it back. */
  507.       if (interactive)
  508.         {
  509.           fprintf (stderr, "%s: continue? ", program_name);
  510.           if (!yesno ())
  511.         exit (1);
  512.           return 1;
  513.         }
  514.       else
  515.         exit (1);
  516.     }
  517.     }
  518. #endif
  519.   return 0;
  520. }
  521.  
  522. static void
  523. usage (status)
  524.      int status;
  525. {
  526.   if (status != 0)
  527.     fprintf (stderr, "Try `%s --help' for more information.\n",
  528.          program_name);
  529.   else
  530.     {
  531.       printf ("Usage: %s [OPTION]... PATH...\n", program_name);
  532.       printf ("\
  533. \n\
  534.   -d, --directory       unlink directory, even if non-empty (super-user only)\n\
  535.   -f, --force           ignore nonexistent files, never prompt\n\
  536.   -i, --interactive     prompt before any removal\n\
  537.   -v, --verbose         explain what is being done\n\
  538.   -r, -R, --recursive   remove the contents of directories recursively\n\
  539.       --help            display this help and exit\n\
  540.       --version         output version information and exit\n");
  541.     }
  542.   exit (status);
  543. }
  544.