home *** CD-ROM | disk | FTP | other *** search
/ The World of Computer Software / World_Of_Computer_Software-02-386-Vol-2of3.iso / b / bm_rm.zip / RM.C < prev    next >
C/C++ Source or Header  |  1991-07-17  |  21KB  |  473 lines

  1. /* RM - Remove files and/or directory subtrees
  2.  
  3. Copyright (C) 1989, 1990, 1991 Brian B. McGuinness
  4.                                15 Kevin Road
  5.                                Scotch Plains, NJ 07076
  6.  
  7. This program is free software; you can redistribute it and/or modify it under 
  8. the terms of the GNU General Public License as published by the Free Software 
  9. Foundation; either version 1, or (at your option) any later version.
  10.  
  11. This program is distributed in the hope that it will be useful, but WITHOUT 
  12. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  13. FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  14. details.
  15.  
  16. You should have received a copy of the GNU General Public License along with 
  17. this program; if not, write to the Free Software Foundation, Inc., 675 Mass 
  18. Ave, Cambridge, MA 02139, USA. 
  19.  
  20. This is an extended MS-DOS implementation of the *NIX RM utility.
  21.  
  22. Syntax:  RM [/AEFIRSV?] {name | @}...
  23.  
  24. /A = Detect all files, even hidden and system files, which are not normally 
  25.      detected.  If RM is used to remove files in the root directory of a 
  26.      bootable disk, especially a hard disk, /A should not be used with /F 
  27.      as there is a risk of removing the system files IBMBIO.COM (IO.SYS) and 
  28.      IBMDOS.COM (MSDOS.SYS).  This would make it impossible to boot the system 
  29.      from that disk.  
  30.  
  31. /E = Only delete matching files whose file size is zero.  Normally, we delete 
  32.      matching files regardless of their size.  This option is useful for 
  33.      disposing of empty "junk" files produced by programs or by failed 
  34.      attempts at I/O redirection.  For example, to remove ALL empty files from 
  35.      drive C:, use the command  RM /ES C:\*.*  (/S is explained below).  Note 
  36.      that empty files use up no disk space but they do use up a directory entry.
  37.  
  38. /F = Force deletion of files without asking.  Normally, we prompt the user for 
  39.      confirmation before removing read-only, hidden, or system files or before 
  40.      assuming "\*.*" at the end of a directory name (see /R below).  If the /F 
  41.      switch is used, these files are removed without any prompt being given. 
  42.  
  43. /I = Interactive mode: the user is prompted for confirmation before each file 
  44.      is removed, regardless of the file's attributes.
  45.  
  46. /R = Recursively delete entire subtrees.  Normally, when a directory name 
  47.      appears on the command line it is treated as if "\*.*" was appended to 
  48.      it.  If the /R switch is used, RM will recursively descend through any 
  49.      directory whose name is given and will remove the entire subtree that 
  50.      begins with that directory. 
  51.  
  52. /S = Delete matching files in entire subtree.  For example, entering 
  53.      RM \UTIL\*.BAK  will delete all files in the \UTIL directory which have 
  54.      the extension .BAK.  Typing  RM /S \UTIL\*.BAK  will delete all .BAK 
  55.      files in the \UTIL directory and all subdirectories of \UTIL.  Typing 
  56.      RM /S *.DOC  will remove all .DOC files in the current default directory 
  57.      and all of its subdirectories.  /S ignores directory names: e.g., if you 
  58.      have a directory called \ASM, then typing RM /S \ASM will do nothing.  To 
  59.      remove all files in \ASM and its subdirectories, type RM /S \ASM\*.*.  
  60.      This will only remove files, not directories.  Use /R to remove a whole 
  61.      subtree.
  62.  
  63. /V = Verbose: display the name of each file or directory as it is removed.
  64.  
  65. /? = Display instructions for using this program
  66.  
  67. Note: Switches become active as they are encountered on the command line.  If 
  68.       a switch is to affect a given file, it must appear before the file's 
  69.       name on the command line.
  70.  
  71. name = name of a file or directory to be removed.  Wildcards may be used.
  72.  
  73. @    = Read a list of names and/or switches from the standard input device and 
  74.        treat each of these as if it had been typed on the command line as an 
  75.        argument of RM.  More than one name and/or switch may appear on each 
  76.        line of input.  When an EOF is encountered, processing of the command 
  77.        line resumes.  
  78.  
  79.        This feature is useful when used with a file containing a lengthy list 
  80.        of names of files to be deleted.  It may also be used with any program 
  81.        which writes a list of file names to standard output: e.g. a program 
  82.        which lists all files in a given directory which have not been modified 
  83.        since a certain date. 
  84.  
  85. If the environment string VERBOSE exists and has the value ON, then RM will 
  86. act as though the /V switch is in effect even if /V is not included on the 
  87. command line.  
  88.  
  89. 1.00 August 1989    - Original version.
  90. 1.01 August 1989    - If the user presses a special key (e.g. F10) after being 
  91.                       prompted for a yes or no answer, don't echo a weird char.
  92.                       Also speed up character string output.
  93. 1.02 September 1989 - Allow the use of Esc to exit from the program.
  94. 1.03 May 1990       - Minor bug fixes and code cleanup.
  95. 1.10 July 1991      - /E switch added, messages improved.
  96.  
  97. Version 1.10          C language 
  98. ----------------------------------------------------------------------------- */
  99.  
  100. #include <stdio.h>
  101. #include <direct.h>     /* for remove() */
  102. #include <io.h>         /* for chmod() */
  103.  
  104. /* IF THE FOLLOWING KEYSTROKE IS READ IN RESPONSE TO A YES/NO PROMPT, THIS 
  105.    PROGRAM WILL IMMEDIATELY TERMINATE EXECUTION */
  106.  
  107. #define ESCAPE 27
  108.  
  109. /* DEFINE ERROR CODES */
  110.  
  111. #define SUCCESS     0
  112. #define BADSYNTAX  11
  113. #define BADSWITCH  13
  114.  
  115. /* DEFINE FILE ATTRIBUTES */
  116.  
  117. #define NORMAL     0
  118. #define READONLY   1
  119. #define HIDDEN     2
  120. #define SYSTEM     4
  121. #define DIRECTORY 16
  122. #define ARCHIVE   32
  123.  
  124. /* DEFINE FLAG VALUES FOR COMMAND LINE SWITCHES */
  125.  
  126. #define EMPTY        1          /* /E */
  127. #define FORCED       2          /* /F */
  128. #define INTERACTIVE  4          /* /I */
  129. #define RECURSIVE    8          /* /R */
  130. #define GLOBAL      16          /* /S */
  131. #define VERBOSE     32          /* /V */
  132.  
  133. /* DEFINE DATA STRUCTURE RETURNED FROM findfile() */
  134. /* Note: In the time, S = seconds / 2; in the date, Y = year - 1980 */
  135.  
  136. struct DIRINFO {
  137.   char reserved[21];            /* Undocumented (varies with DOS version) */
  138.   unsigned char attrib;         /* Attribute byte: 00ADVSHR */
  139.   unsigned short time;          /* Time file was saved: HHHHHMMMMMMSSSSS */
  140.   unsigned short date;          /* Date file was saved: YYYYYYYMMMMDDDDD */
  141.   unsigned long size;           /* File size in bytes */
  142.   char name[13];                /* File name */
  143. };
  144.  
  145. /*----------------------------------------------------------------------------*/
  146. main (int argc, char **argv) {
  147.  
  148. void delete    (char *pathname, struct DIRINFO *file, int flags);   /* Remove a file */
  149. int  delglobal (char *fname, int attributes, int flags, char *end); /* Delete file in all subdirectories */
  150. int  delmatch  (char *fname, int attributes, int flags, char *end); /* Delete matching files */
  151.  
  152. int fexpand (char *relname, char *absname);                 /* Fully qualify file name */
  153. int findfile (char *fname, int srchattr, struct DIRINFO *); /* Search directory for a file */
  154. int getkeyn();                                              /* Read keystroke and print newline */
  155. short getmode (char *fname);                                /* Get file attributes */
  156. char *nexttok (int *argc, char ***argv, int *inpflag);      /* Get next argument */
  157. void recurse (char *path, int attributes, int flags);       /* Remove a subtree */
  158. void usage (char switchar);                                 /* Display instructions */
  159.  
  160. char switchar;  /* Current MS-DOS switch character */
  161. int attrib;     /* Attributes to use to search for matching files */
  162. int flags;      /* Keeps track of command line switches */
  163. int filecnt;    /* Keeps track of how many names we've found on cmd line */
  164. int inpflag;    /* 0 = get tokens from cmd line, otherwise get them from stdin */
  165. char *token;    /* Command line token */
  166. int i;
  167.  
  168. char fullname[84];      /* Buffer for fully qualified file/directory name */
  169. char *end;              /* Location of end of fully qualified file name */
  170.  
  171. char defdir[80];        /* Buffer for default directory name */
  172. int  cdlen;             /* Length of name of current default directory */
  173.  
  174. /* SET UP DEFAULT PARAMETERS */
  175. attrib=ARCHIVE | READONLY;      /* Only search for normal & read-only files */
  176. flags=0;                        /* No switches found yet */
  177. inpflag=0;                      /* Read tokens from command line */
  178. filecnt=0;                      /* No file names found yet */
  179.  
  180. token=getenv ("VERBOSE");       /* If VERBOSE=ON, set flag accordingly */
  181. if (token != NULL && !stricmp (token, "ON")) flags=VERBOSE;
  182.  
  183. switchar=getswchr();    /* Get the current DOS switch char */
  184. fexpand (".", defdir);  /* Get current default directory, fully qualified */
  185. for (cdlen=0; defdir[cdlen]; cdlen++);
  186.  
  187. /* Process each token on the command line */
  188.  
  189. while ((token=nexttok (&argc, &argv, &inpflag)) != NULL) {
  190.  
  191.   if (*token == switchar) {                          /* It's a switch */
  192.     while (*++token) switch (toupper (*token)) {
  193.       case 'A': attrib |= HIDDEN | SYSTEM;
  194.                 break;
  195.  
  196.       case 'E': flags |= EMPTY;
  197.                 break;
  198.  
  199.       case 'F': flags |= FORCED;
  200.                 flags &= ~INTERACTIVE;
  201.                 break;
  202.  
  203.       case 'I': flags |= INTERACTIVE;
  204.                 flags &= ~FORCED;
  205.                 break;
  206.  
  207.       case 'R': flags |= RECURSIVE;
  208.                 break;
  209.  
  210.       case 'S': flags |= GLOBAL;
  211.                 break;
  212.  
  213.       case 'V': flags |= VERBOSE;
  214.                 break;
  215.  
  216.       case '?': usage (switchar);
  217.                 break;
  218.  
  219.       default: iprintf ("rm: Unknown switch: %c%c\n", switchar, *token);
  220.                exit (BADSWITCH);
  221.     }
  222.   }
  223.  
  224.   else {                                             /* It's a filespec */
  225.     filecnt++;
  226.     if (fexpand (token, fullname)) {  /* Expand token to fully-qualified name */
  227.       iprintf ("rm: Invalid name: %s\n", token);
  228.       continue;
  229.     }
  230.     for (end=fullname; *end; end++);    /* Find the end of the name */
  231.  
  232.     if (end[-2] == ':') {       /* DOS doesn't recognize root as a directory */
  233.       i=DIRECTORY;
  234.       end--;
  235.     } else {
  236.       if (end[-1] == '\\') *--end = '\0';
  237.       i=getmode (fullname);
  238.     }
  239.  
  240.     if (i != -1 && i & DIRECTORY) {     /* DEAL WITH A DIRECTORY */
  241.  
  242.       if (flags & RECURSIVE) {  /* DELETE ENTIRE SUBTREE */
  243.         i=end - fullname;
  244.         if (cdlen >= i && !strnicmp (defdir, fullname, i)) {
  245.           iprintf ("rm: Can't remove tree containing current directory:\n    %s\n ",
  246.                    fullname);
  247.         }
  248.         else recurse (fullname, attrib, flags);
  249.       }
  250.  
  251.       else {                    /* DELETE ALL FILES IN THE DIRECTORY */
  252.         if (!(flags & FORCED)) {
  253.           iprintf ("Delete all files in directory %s [n]? ", fullname);
  254.           i=toupper(getkeyn());
  255.           if (i == ESCAPE) exit (SUCCESS);
  256.           if (i != 'Y') continue;
  257.         }
  258.         strcpy (end++, "\\*.*");
  259.         delmatch (fullname, attrib, flags, end);
  260.       }
  261.     }
  262.     else {      /* IT'S NOT A DIRECTORY, SO DELETE ALL MATCHING FILES */ 
  263.       for (; end > fullname && end[-1] != '\\' && end[-1] != ':'; end--);
  264.  
  265.       if (flags & GLOBAL) i=delglobal (fullname, attrib, flags, end);
  266.       else i=delmatch (fullname, attrib, flags, end);
  267.       if (!i) iprintf ("rm: Not found: %s\n", token);
  268.     }
  269.   }   /* end of "arg is a pathname" */
  270. }     /* end of "while more cmd line args" */
  271.  
  272. /* IF NO PATHNAMES WERE GIVEN, PRINT SYNTAX AND EXIT WITH ERROR CODE */
  273. if (!filecnt) {
  274.   usage (switchar);
  275.   exit (BADSYNTAX);
  276. }
  277. exit (SUCCESS);
  278. }
  279.  
  280. /*----------------------------------------------------------------------------*/
  281. /* delete() - Delete a specified file                                         */
  282. /*                                                                            */
  283. /* filename = pathname of file to be deleted                                  */
  284. /* attrib   = attributes of file to be deleted                                */
  285. /* flags    = flags set by command line switches                              */
  286. /*----------------------------------------------------------------------------*/
  287. void delete (char *pathname, struct DIRINFO *file, int flags) {
  288.   int i;
  289.   static char attr[]="RHSVDA";
  290.   static char *month[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
  291.  
  292.   /* FOR SPECIAL FILES, OR IF IN INTERACTIVE MODE, MAKE SURE THAT THE USER 
  293.      REALLY WANTS TO DELETE THE FILE */
  294.  
  295.   if ((flags & INTERACTIVE) || 
  296.       ((file->attrib & (HIDDEN | SYSTEM | READONLY)) && !(flags & FORCED))) {
  297.     iprintf ("Delete %s  %ld  %d %s %04d  %d:%02d  ", pathname, file->size, 
  298.       file->date & 0x001F, month[((file->date & 0x01E0) >> 5) - 1],
  299.       1980 + (file->date >> 9), file->time >> 11, (file->time & 0x07E0) >> 5);
  300.     for (i=5; i >= 0; i--) iprintf ("%c", (file->attrib & (1 << i)) ? attr[i] : '-');
  301.     iprintf ("  [N]? ");
  302.     i=toupper(getkeyn());
  303.     if (i == ESCAPE) exit (SUCCESS);
  304.     if (i != 'Y') return;
  305.   }
  306.  
  307. #ifdef DEBUG
  308.   iprintf ("remove (%s)\n", pathname);
  309. #else
  310.  
  311.   /* MODIFY ATTRIBUTES, IF NECESSARY, SO WE CAN DELETE THE FILE */
  312.   if (file->attrib & (HIDDEN | SYSTEM | READONLY)) chmod (pathname, NORMAL);
  313.  
  314.   if (remove (pathname)) iprintf ("rm: Can't delete %s\n", pathname);
  315.   else if (flags & VERBOSE) iprintf ("%s\n", pathname);
  316. #endif
  317. }
  318.  
  319. /*----------------------------------------------------------------------------*/
  320. /* delglobal() - Delete all matching files in a subtree                       */
  321. /*                                                                            */
  322. /* name       = file specification for files to be removed                    */
  323. /* attributes = attributes to be used in file search                          */
  324. /* flags      = flags set by command line switches                            */
  325. /* end        = pointer to beginning of file name (just beyond path)          */
  326. /*                                                                            */
  327. /* We return the number of matching files found.                              */
  328. /*----------------------------------------------------------------------------*/
  329.  
  330. int delglobal (char *fname, int attributes, int flags, char *end) {
  331.  
  332.   struct DIRINFO dir;
  333.   int i, nfiles;
  334.   char *cp, filespec[13];
  335.  
  336.   strcpy (filespec, end);       /* Save pattern to match in file search */
  337.  
  338.   /* DELETE MATCHING FILES IN CURRENT DIRECTORY */
  339.   nfiles=delmatch (fname, attributes, flags, end);
  340.  
  341.   /* DELETE MATCHING FILES IN ALL SUBDIRECTORIES */
  342.   attributes |= DIRECTORY;
  343.   strcpy (end,"*.*");
  344.   i=findfile (fname, attributes, &dir);
  345.   attributes &= ~DIRECTORY;   /* Restore attributes to their original state */
  346.   while (!i) {
  347.     /* IGNORE THE "." AND ".." FILES */
  348.     if ((dir.name[0] != '.') || (dir.name[1] && (dir.name[1] != '.' || dir.name[2]))) {
  349.       if (dir.attrib & DIRECTORY) {
  350.         strcpy (end, dir.name); /* Append directory name to the path */
  351.         for (cp=end; *cp; cp++); /* Append file spec to the directory name */
  352.         *cp++='\\';
  353.         strcpy (cp, filespec);
  354.         nfiles += delglobal (fname, attributes, flags, cp);
  355.       }
  356.     }
  357.     i=findfile (NULL, 0, &dir);
  358.   }
  359.   end[-1]='\0';               /* Restore path to its original state */
  360.   return nfiles;
  361. }
  362.  
  363. /*----------------------------------------------------------------------------*/
  364. /* delmatch() - Delete all files matching a given file specification          */
  365. /*                                                                            */
  366. /* name       = file specification for files to be removed                    */
  367. /* attributes = attributes to be used in file search                          */
  368. /* flags      = flags set by command line switches                            */
  369. /* end        = pointer to beginning of file name (just beyond path)          */
  370. /*                                                                            */
  371. /* We return the number of matching files found.                              */
  372. /*----------------------------------------------------------------------------*/
  373.  
  374. int delmatch (char *fname, int attributes, int flags, char *end) {
  375.  
  376.   struct DIRINFO file;
  377.   int i, nfiles;
  378.  
  379.   nfiles=0;     /* Keep track of how many matching files we find */
  380.  
  381.   i=findfile (fname, attributes, &file);
  382.   while (!i) {
  383.     nfiles++;
  384.     strcpy (end, file.name);
  385.     if (!(file.size && (flags & EMPTY))) delete (fname, &file, flags);
  386.     i=findfile (NULL, 0, &file);
  387.   }
  388.   return nfiles;
  389. }
  390.  
  391. /*----------------------------------------------------------------------------*/
  392. /* recurse() - Recursively delete a subtree                                   */
  393. /*                                                                            */
  394. /* path       = full path of directory to be removed                          */
  395. /* attributes = attributes to be used in file search                          */
  396. /* flags      = flags set by command line switches                            */
  397. /*----------------------------------------------------------------------------*/
  398.  
  399. void recurse (char *path, int attributes, int flags) {
  400.  
  401.   char *pathend;
  402.   int i;
  403.   struct DIRINFO file;
  404.  
  405.   /* FOR INTERACTIVE MODE, CHECK IF WE'RE TO REMOVE THIS DIRECTORY */
  406.   if (flags & INTERACTIVE) {
  407.     iprintf ("Remove directory %s [N]? ", path);
  408.     i=toupper(getkeyn());
  409.     if (i == ESCAPE) exit (SUCCESS);
  410.     if (i != 'Y') return;
  411.   }
  412.  
  413.   /* FIND END OF PATH STRING AND SAVE ITS LOCATION */
  414.   for (pathend=path; *pathend; pathend++);
  415.  
  416.   strcpy (pathend++, "\\*.*");    /* Append '\*.*' to the path. */
  417.  
  418.   attributes |= DIRECTORY;        /* Search for directories as well as files */
  419.  
  420.   /* REMOVE EACH FILE AND SUBTREE IN THIS DIRECTORY */
  421.   i=findfile (path, attributes, &file);
  422.   while (!i) {
  423.     /* IGNORE THE "." AND ".." FILES */
  424.     if ((file.name[0] != '.') || (file.name[1] && (file.name[1] != '.' || file.name[2]))) {
  425.  
  426.       strcpy (pathend, file.name); /* Append current file name to the path */
  427.  
  428.       if (file.attrib & DIRECTORY) recurse (path, attributes, flags);
  429.       else if (!(file.size && (flags & EMPTY))) delete (path, &file, flags);
  430.  
  431.     }
  432.     i=findfile (NULL, 0, &file);
  433.   }
  434.   pathend[-1]='\0';             /* Restore path to its original state */
  435.   attributes &= ~DIRECTORY;     /* Restore attributes to their original state */
  436.  
  437. #ifdef DEBUG
  438.   iprintf ("rmdir (%s)\n", path);
  439. #else
  440.   if (rmdir (path)) {           /* Remove the directory */
  441.     iprintf ("rm: Can't remove directory: %s\n", path);
  442.   }
  443.   else if (flags & VERBOSE) iprintf ("[%s]\n", path);
  444. #endif
  445. }
  446.  
  447. /*------------------------------------------------------------------------------
  448.   usage() - Display instructions for using this program
  449. ------------------------------------------------------------------------------*/
  450. void usage (char c) {
  451.   static int displayed=0;       /* Only display instructions once */
  452.  
  453.   if (!displayed++) {
  454.     iprintf ("RM - Remove files and/or directory subtrees\n"
  455.       "Copyright (C) 1989, 1990, 1991 Brian B. McGuinness\n\n"
  456.       "Syntax: RM [%cAEFIRSV?] {name | @}...\n"
  457.       "  %cA = Remove ALL matching files, even hidden & system files (careful!)\n"
  458.       "  %cE = Only remove empty (size zero) files\n"
  459.       "  %cF = Force removal even if hidden or read-only (don't ask user)\n"
  460.       "  %cI = Interactive: ask user to confirm each removal\n"
  461.       "  %cR = Recursively remove entire subtrees\n", c, c, c, c, c, c);
  462.     iprintf ("  %cS = Remove matching files from subtrees\n"
  463.       "  %cV = Verbose: display the name of each object as it is removed\n"
  464.       "  %c? = Display instructions\n\n"
  465.       "  @ = Read pathnames and switches from standard input until end-of-file is\n"
  466.       "      encountered.  Then continue processing the command line.\n\n", c, c, c);
  467.     iprintf ("Setting the environment variable VERBOSE=ON is equivalent to %cV\n\n"
  468.       "This program is free software; you can redistribute it and/or modify it under\n"
  469.       "the terms of the GNU General Public License as published by the Free Software\n" 
  470.       "Foundation; either version 1, or (at your option) any later version.\n", c);
  471.   }
  472. }
  473.