home *** CD-ROM | disk | FTP | other *** search
/ Shareware Supreme Volume 6 #1 / swsii.zip / swsii / 126 / SPRDPR20.ZIP / RM.C < prev    next >
Text File  |  1989-10-21  |  20KB  |  713 lines

  1. #pragma inline
  2.  
  3. /*
  4.  
  5. RM.C  Copyright (C) 1988  Mark Adler  Pasadena, CA
  6.       All rights reserved.
  7.  
  8. Version history -
  9.  
  10. 1.0      4 Jun 1988     First public version
  11. 1.1      4 Nov 1988     Make rm as fast as del (use FCB delete)
  12. 1.2     18 Nov 1988     Fixed for Turbo C 2.0 (label needs statement)
  13. 1.3      3 Feb 1989     Display files and directories in lower case
  14. 1.4     21 Oct 1989     Catch dangling options and ignore command
  15.  
  16.  
  17. RM is an extended delete command.  It can delete all the files in a
  18. directory and its subdirectories, etc. and remove all the directories.
  19. Alternatively, it can delete all the files in any directories that match
  20. the specified pattern.  Since RM is a more powerful delete command, it
  21. can be that much more dangerous.  So the following warning is in order:
  22.  
  23. WARNING:  RM IS A VERY DESTRUCTIVE COMMAND.  It will NOT ask for
  24. verification of a particularily destructive argument like the DOS DEL
  25. command does.  What you ask it is what it does.  BE CAREFUL.
  26.  
  27. Normally, RM acts like the DEL command, deleting any files that match
  28. the pattern, except that it does not verify the argument *.*---it just
  29. does it.  For example:
  30.  
  31.      rm *.bak
  32.  
  33. will delete all the *.bak files in the current directory.  The command:
  34.  
  35.      rm *.*
  36.  
  37. will delete all the files in the current directory without asking first.
  38.  
  39. RM can also delete the contents of subdirectories and then remove the
  40. subdirectories.  The option for this is "/S".  For example:
  41.  
  42.      rm/s \tex
  43.  
  44. will delete all the files and subdirectories contained in \tex and
  45. finally remove the directory \tex.  The command:
  46.  
  47.      rm/s \tex\*.*
  48.  
  49. will do the same thing, but not remove the directory tex, just
  50. everything in it.
  51.  
  52. Alternatively, RM can delete any files that match the pattern in all
  53. subdirectories.  The option for this is "/F":
  54.  
  55.      rm/f \*.bak
  56.  
  57. will delete all *.bak files on the current drive, no matter where they
  58. are.  No subdirectories will be removed.
  59.  
  60. If you think that's dangerous, RM can also remove hidden, system, or
  61. read-only files if asked to.  The options are "/H", "/Y", and "/R".
  62. For example:
  63.  
  64.      rm/shyr \*.*               DON'T EVEN THINK OF TYPING THIS COMMAND.
  65.  
  66. will remove every single file and directory on the current drive,
  67. including the system---leaving only the drive label, a lot of empty
  68. space, and a user in a state of panic.
  69.  
  70. RM can take multiple arguments, processing each in turn.  For example:
  71.  
  72.      rm *.obj *.lst *.map
  73.  
  74. will delete all the files specified.
  75.  
  76. RM does have one option that makes it a little less dangerous; "/P".
  77. This option causes RM to prompt you for every file or directory it tries
  78. to get rid of.  For example:
  79.  
  80.      rm/p *.*
  81.  
  82. might ask you:
  83.  
  84.      Delete file test.c (y/n)? _
  85.  
  86. or
  87.  
  88.      Remove directory \tmp (y/n)? _
  89.  
  90. In either case, you must respond with either an upper or lower case "Y"
  91. or "N".  Any other response is ignored.
  92.  
  93. When it is done, RM will tell you how many files it deleted and how many
  94. directories it removed.  It might say, for example:
  95.  
  96.      13 files deleted
  97.  
  98. or
  99.  
  100.      129 files deleted and 4 directories removed
  101.  
  102. If RM is, for some reason, unable to delete a file or remove a
  103. directory, it will say:
  104.  
  105.      Could not delete file test.c
  106.  
  107. or
  108.  
  109.      Could not remove directory \tmp - part of current path
  110.  
  111. or
  112.  
  113.      Could not remove directory \tmp - unable to empty it
  114.  
  115. It should not be possible to get the first error.  The second error
  116. occurs when the directory RM tried to remove is part of the current path
  117. for that drive.  DOS does not allow pulling the rug out from under your
  118. feet by removing a directory you are in.  The remedy is to change out of
  119. the directory you wish to remove and repeating the RM command.
  120.  
  121. The third error can happen when RM tries to remove a directory with
  122. read-only, hidden, or system files in it, and no options were specified
  123. to allow RM to remove those.  The remedy is to repeat the RM command
  124. with the necessary options to delete the files desired.
  125.  
  126. RM takes options that are preceded by a slash (/).  The command line is
  127. read from left to right, processing options and file/path names as they
  128. appear.  However, options that are appended to a file/path name take
  129. effect before that name is processed.  For example, this command would
  130. do the same thing as "rm/p *.c":
  131.  
  132.      rm *.c/p
  133.  
  134. But the command "rm *.c /p" (notice the extra space before the /p) would
  135. not do the same thing.  In this case, RM notices the "dangling" option
  136. before it does anything and simply ignores the entire command for
  137. safety, printing:
  138.  
  139.     Dangling option---entire command ignored
  140.  
  141. Since there are options to change the defaults along a command line,
  142. there are options to change them back for processing the next thing
  143. on the command line.  All the options are listed here:
  144.  
  145.      /P  - Prompt for each deletion or removal.
  146.      /Q  - Quiet---don't prompt (default).
  147.      /S  - Remove matching subdirectories and all of their contents.
  148.      /N  - Ignore subdirectories (default).
  149.      /H  - Remove hidden files.
  150.      /V  - Visible---ignore hidden files (default).
  151.      /Y  - Remove system files.
  152.      /T  - Typical---ignore system files (default).
  153.      /R  - Remove read-only files!
  154.      /W  - Only delete writable files (default).
  155.      /F  - Remove all files that match pattern, and no subdirs.
  156.      /A  - Remove all files in subdirectories, period (default).
  157.      /?  - Display version number and list of options.
  158.  
  159. For example, the command:
  160.  
  161.      rm /p *.lst /q *.obj
  162.  
  163. will ask before deleting each file that matches *.lst, and then delete
  164. all the files that match *.obj without asking.
  165.  
  166. Options can be combined with or without additional slashes.  For
  167. example, these commands do the same thing:
  168.  
  169.      rm *.lst/pr
  170.      rm/p/r *.lst
  171.      rm /p /r *.lst
  172.      rm /p *.lst/r
  173.  
  174. Once again---BE CAREFUL WITH THIS COMMAND.  Thanks to Kurt Schmidt for
  175. suggesting that RM be changed to notice a dangling option.
  176.  
  177. */
  178.  
  179.  
  180. typedef unsigned long ulong;
  181.  
  182.  
  183. /* Option flags, assigned to default values */
  184. char ask = 0,           /* Prompt for each deletion */
  185.      sub = 0,           /* Include subdirectories */
  186.      att = 0,           /* Attribute for hidden, system */
  187.      rof = 0,           /* Remove read-only files also */
  188.      fin = 0;           /* Find---use same name in subdirs */
  189.  
  190.  
  191. ulong fls, drs;         /* Number of files, directories removed */
  192.  
  193.  
  194. /* Structure for fnd1st(), fndnxt() */
  195. struct find {
  196.   char rsvd[21];        /* What DOS needs to keep track of find */
  197.   char attr;            /* File attribute */
  198.   unsigned time;        /* Time stamp */
  199.   unsigned date;        /* Date stamp */
  200.   ulong size;           /* File size in bytes */
  201.   char name[13];        /* FIle name as a zero terminated string */
  202. };
  203.  
  204.  
  205. /* Send character 'c' to stdout */
  206. void pputc(char c)
  207. {
  208.   asm mov DL,c
  209.   asm mov AH,2
  210.   asm int 21h
  211. }
  212.  
  213.  
  214. /* Send string 's' to stdout */
  215. void pputs(char *s)
  216. {
  217.   asm mov SI,s
  218.   asm cld
  219.     plp:
  220.   asm  lodsb
  221.   asm  test AL,AL
  222.   asm  jz pfin
  223.   asm  mov DL,AL
  224.   asm  mov AH,2
  225.   asm  int 21h
  226.   asm  jmp short plp
  227.     pfin: ;
  228. }
  229.  
  230.  
  231. /* Convert the string s from upper case to lower case */
  232. char *strlow(char *s)
  233. {
  234.   asm mov AX,DS
  235.   asm mov ES,AX
  236.   asm cld
  237.   asm mov SI,s
  238.   asm lea DI,[SI-1]
  239.     llp:
  240.   asm  inc DI
  241.     llpnoi:
  242.   asm  lodsb
  243.   asm  test AL,AL
  244.   asm  jz lfin
  245.   asm  cmp AL,'A'
  246.   asm  jb llp
  247.   asm  cmp AL,'Z'
  248.   asm  ja llp
  249.   asm   add AL,'a'-'A'
  250.   asm   stosb
  251.   asm   jmp short llpnoi
  252.     lfin: ;
  253.   return s;
  254. }
  255.  
  256.  
  257. /* Copy string s to string d, return end of d */
  258. char *scpy(char *d, char *s)
  259. {
  260.   asm mov SI,s
  261.   asm mov DI,d
  262.   asm cld
  263.   asm mov AX,DS
  264.   asm mov ES,AX
  265.     slp:
  266.   asm  lodsb
  267.   asm  stosb
  268.   asm  test AL,AL
  269.   asm  jnz slp
  270.   asm mov AX,DI
  271.   asm dec AX
  272.   return (char *) _AX;
  273. }
  274.  
  275.  
  276. /* Convert unsigned long 'n' to decimal in string 's' */
  277. char *ultod(ulong n, char *s)
  278. {
  279.   /* Load n into BX:AX, s into SI and DI, and the radix into CX */
  280.   asm mov DI,s
  281.   asm mov SI,DI
  282.   asm mov AX,n
  283.   asm mov BX,n+2
  284.   asm mov CX,10
  285.  
  286.   /* Convert n into a digit string, least significant digit first */
  287.      dlp:
  288.         /* Divide BX:AX by CX, quotient to BX:AX, remainder to DX */
  289.   asm  xchg AX,BX       /* BX = low n */
  290.   asm  sub DX,DX        /* DX:AX = high n */
  291.   asm  div CX           /* AX = high q, DX = temporary r */
  292.   asm  xchg AX,BX       /* BX = high q, DX:AX = temp r:low q */
  293.   asm  div CX           /* BX:AX = q, DX = r */
  294.         /* Put digit in string */
  295.   asm  add DL,'0'
  296.   asm  mov [DI],DL
  297.   asm  inc DI
  298.         /* Do until BX:AX is zero */
  299.   asm  mov DX,AX
  300.   asm  or DX,BX
  301.   asm  jnz dlp
  302.         /* Terminate string */
  303.   asm mov [DI],AL
  304.  
  305.   /* Reverse the string, putting most significant digit first */
  306.      rlp:
  307.   asm  dec DI
  308.   asm  cmp DI,SI
  309.   asm  jna rfin
  310.   asm  mov AL,[SI]
  311.   asm  xchg AL,[DI]
  312.   asm  mov [SI],AL
  313.   asm  inc SI
  314.   asm  jmp short rlp
  315.      rfin:
  316.  
  317.   /* Return pointer to start of string */
  318.   asm mov AX,s
  319.   return (char *) _AX;
  320. }
  321.  
  322.  
  323. /* Ask user yes/no question */
  324. int query(char *s, char *t)
  325. {
  326.   pputs(s);
  327.   pputs(t);
  328.   pputs(" (y/n)? ");
  329.  
  330.      qlp:
  331.   asm  mov AH,8         /* Get console input without echo */
  332.   asm  int 21h
  333.   asm  test AL,AL
  334.   asm  jnz got
  335.   asm   int 21h         /* If extended code, ignore it */
  336.   asm   jmp short qlp
  337.       got:
  338.   asm  mov DL,AL        /* Save character typed */
  339.   asm  and AL,5Fh       /* Convert lower to upper case */
  340.   asm  sub SI,SI        /* Return 0 if no */
  341.   asm  cmp AL,'N'
  342.   asm  je qfin
  343.   asm  inc SI           /* Return 1 if yes */
  344.   asm  cmp AL,'Y'
  345.   asm  jne qlp          /* Do until we get a proper answer */
  346.      qfin:
  347.  
  348.   pputc(_DX);           /* Echo y, n, Y, or N */
  349.   pputs("\r\n");
  350.   return _SI;
  351. }
  352.  
  353.  
  354. /* Find first match to 'p', attribute 'a', results in 'f' */
  355. int fnd1st(char *p, struct find *f, int a)
  356. {
  357.   /* Set DTA to f */
  358.   asm mov DX,f
  359.   asm mov AH,1Ah
  360.   asm int 21h
  361.  
  362.   /* Do find first */
  363.   asm mov DX,p
  364.   asm mov CX,a
  365.   asm mov AH,4Eh
  366.   asm int 21h
  367.   asm sbb AX,AX
  368.   return _AX;
  369. }
  370.  
  371.  
  372. /* Find next match for fnd1st() done on 'f' */
  373. int fndnxt(struct find *f)
  374. {
  375.   /* Set DTA to f */
  376.   asm mov DX,f
  377.   asm mov AH,1Ah
  378.   asm int 21h
  379.  
  380.   /* Do find next */
  381.   asm mov AH,4Fh
  382.   asm int 21h
  383.   asm sbb AX,AX
  384.   return _AX;
  385. }
  386.  
  387.  
  388. /* Parse file name n into FCB f */
  389. int parse(char *n, char *f)
  390. {
  391.   asm mov AX,DS
  392.   asm mov ES,AX
  393.   asm mov SI,n
  394.   asm mov DI,f
  395.   asm mov AX,2901h
  396.   asm int 21h
  397.   asm cbw
  398.   return _AX;
  399. }
  400.  
  401.  
  402. /* Get current directory on drive d into string p */
  403. int curdir(int d, char *p)
  404. {
  405.   asm mov DL,d
  406.   asm mov SI,p
  407.   asm mov AH,47h
  408.   asm int 21h
  409.   asm jb cerr
  410.   asm  sub AX,AX
  411.      cerr:
  412.   return _AX;
  413. }
  414.  
  415.  
  416. /* Set new directory p */
  417. int newdir(char *p)
  418. {
  419.   asm mov DX,p
  420.   asm mov AH,3Bh
  421.   asm int 21h
  422.   asm jb nerr
  423.   asm  sub AX,AX
  424.      nerr:
  425.   return _AX;
  426. }
  427.  
  428.  
  429. /* Count the matches for q on drive e (0=default) with attributes */
  430. /*  att/rof and adjust the attribute if needed for the global delete */
  431. ulong count(int e, char *q)
  432. {
  433.   int r;
  434.   char *p;
  435.   ulong n;
  436.   struct find d;
  437.   char m[16];
  438.  
  439.   p = m;
  440.   if (e)
  441.   {
  442.     *p++ = e + 'A' - 1;
  443.     *p++ = ':';
  444.   }
  445.   scpy(p, q);
  446.   n = 0;
  447.   r = fnd1st(m, &d, att);
  448.   while (!r)
  449.   {
  450.     if (rof || !(d.attr & 1))   /* Ignore read-only unless specified */
  451.     {
  452.       n++;
  453.       if (d.attr & 7)           /* Adjust attribute if needed */
  454.       {
  455.         scpy(p, d.name);
  456.         asm lea DX,m
  457.         asm sub CX,CX
  458.         asm mov AX,4301h
  459.         asm int 21h
  460.       }
  461.     }
  462.     r = fndnxt(&d);
  463.   }
  464.   return n;
  465. }
  466.  
  467.  
  468. void remove(char *a)
  469. {
  470.   register char *p, *q;
  471.   int e;
  472.   struct find d;
  473.   char s[128];
  474.   char t[128];
  475.  
  476.   /* Setup */
  477.   q = s;
  478.   p = scpy(q, a) - 1;           /* Copy path being searched */
  479.   while (p >= q && *p != '\\' && *p != ':')
  480.     p--;                        /* Scan back for path delimiter */
  481.   p++;                          /* Point past delimiter */
  482.   if (sub || fin)
  483.     scpy(t, p);                 /* Save name to match */
  484.  
  485.   /* First try global delete since it is faster */
  486.   if (!ask)                     /* Of course, skip if prompting */
  487.   do {                          /* (use "do" so can use "break") */
  488.     char *r, f[44], u[128], v[128];
  489.  
  490.     /* Set up to do global delete using FCB calls */
  491.     f[0] = 0;                   /* Not an extended FCB */
  492.     if (parse(p, f+7) != 1)     /* Make an FCB out of the name */
  493.       break;                    /* If not global, skip all this */
  494.     if (q[0] && q[1] == ':')    /* Get drive */
  495.       e = (q[0] & 0x5f) - 'A' + 1;
  496.     else
  497.       e = 0;                    /* Use default drive */
  498.     f[7] = e;                   /* Set drive */
  499.     *v = '\0';                  /* No current path yet */
  500.     if (p - q && *(p-1) != ':') /* Change path if needed */
  501.     {
  502.       r = v;                    /* Construct old path */
  503.       if (e)
  504.       {
  505.         *r++ = e + 'A' - 1;     /* Put in drive if needed, */
  506.         *r++ = ':';
  507.       }
  508.       *r++ = '\\';              /*  and backslash to start at root */
  509.       if (curdir(e, r))         /* Get current directory */
  510.         break;                  /* If can't get it, skip rest */
  511.       scpy(u, q);               /* Copy and terminate new path */
  512.       u[p - q - (p - q != 1 && u[p - q - 1] != ':')] = '\0';
  513.       if (newdir(u))
  514.         break;                  /* If no such path, skip rest */
  515.     }
  516.  
  517.     /* Do global delete and count how many files were deleted */
  518.     fls += count(e, p);         /* Adjust attributes if neeeded */
  519.     asm lea DX,f+7
  520.     asm mov AH,13h
  521.     asm int 21h
  522.     fls -= count(e, p);
  523.  
  524.     /* Change path on that drive back to what it was */
  525.     if (*v)
  526.       newdir(v);
  527.   } while (0);                  /* (not really a loop) */
  528.  
  529.   /* Find matching files */
  530.   e = fnd1st(q, &d, att);       /* Search allowed attributes */
  531.   while (!e)
  532.   {
  533.     if (rof || !(d.attr & 1))   /* Ignore read-only unless specified */
  534.     {
  535.       scpy(p, d.name);
  536.       if (!ask || query("Delete file ", strlow(q)))
  537.       {
  538.         if (d.attr & 1)         /* If read-only and got here, */
  539.         {                       /*  then change the attribute */
  540.           asm mov DX,q
  541.           asm sub CX,CX
  542.           asm mov AX,4301h
  543.           asm int 21h
  544.         }
  545.         asm mov DX,q            /* Delete file */
  546.         asm mov AH,41h
  547.         asm int 21h
  548.         asm jnb delok
  549.           pputs("Could not delete file ");
  550.           pputs(strlow(q));
  551.           pputs("\r\n");
  552.           asm jmp short delbad
  553.        delok:
  554.           fls++;
  555.        delbad: ;
  556.       }
  557.     }
  558.     e = fndnxt(&d);
  559.   }
  560.  
  561.   /* Find matching subdirectories, if requested */
  562.   if (sub || fin)
  563.   {
  564.     scpy(p, fin ? "*.*" : t);   /* What directories to match */
  565.     e = fnd1st(q, &d, 0x17);    /* Look at everything except labels */
  566.     while (!e)                  /* Do all subdirectories */
  567.     {
  568.       if ((d.attr & 0x10) &&
  569.           (d.name[0] != '.' || (d.name[1] && d.name[1] != '.')))
  570.       {                         /* Is subdir and not "." or ".." */
  571.         q = scpy(p, d.name);    /* Overlay wildcard with found name */
  572.         *q++ = '\\';            /* Append path delimiter */
  573.         scpy(q, fin ? t : "*.*");       /* New wildcard */
  574.         remove(s);              /* Do subdirectory */
  575.         if (!fin)               /* Remove directory if not in find */
  576.         {
  577.           *--q = '\0';
  578.           if (!ask || query("Remove directory ", strlow(s)))
  579.           {
  580.             asm lea DX,s        /* Remove directory */
  581.             asm mov AH,3Ah
  582.             asm int 21h
  583.             asm jnb rdok
  584.               e = _AX;          /* Save error code */
  585.               pputs("Could not remove directory ");
  586.               pputs(strlow(s));
  587.               if (e == 0x10)    /* See if current directory */
  588.                 pputs(" - part of current path");
  589.               else
  590.                 pputs(" - unable to empty it");
  591.               pputs("\r\n");
  592.               asm jmp short rdbad
  593.            rdok:
  594.               drs++;
  595.            rdbad: ;
  596.           }
  597.         }
  598.       }
  599.       e = fndnxt(&d);
  600.     }
  601.   }
  602. }
  603.  
  604.  
  605. void main(int argc, char *argv[])
  606. {
  607.   register char *p;
  608.   register int k;
  609.   int i, m;
  610.   char *a[64];          /* Tokens and options */
  611.   char t[64];           /* Token/~option flags */
  612.  
  613.  
  614.   /* Parse line into tokens and options */
  615.   m = k = 0;
  616.   for (i = 1; i < argc; i++)    /* Do command line */
  617.   {
  618.     p = argv[i];                /* Next argument */
  619.     if (*p != '/')              /* See if token */
  620.     {
  621.       a[k] = p++;
  622.       t[k++] = 1;               /* Token */
  623.       m = 1;                    /* Token flag */
  624.       while (*p && *p != '/')
  625.         p++;
  626.       if (*p)                   /* See if options on token */
  627.       {
  628.         *p++ = '\0';            /* Terminate token string */
  629.         a[k] = a[k-1];          /* Put options appended to token */
  630.         t[k] = 1;               /*  BEFORE that token. */
  631.         a[k-1] = p;
  632.         t[k-1] = 0;             /* Option(s) */
  633.         k++;
  634.       }
  635.     }
  636.     else
  637.     {
  638.       a[k] = ++p;               /* Options start after the slash */
  639.       t[k++] = 0;               /* Option(s) */
  640.       if (m)
  641.         m = -1;                 /* Dangling option flag */
  642.     }
  643.   }
  644.   if (m < 0)
  645.   {
  646.     pputs("Dangling option---entire command ignored\r\n");
  647.     asm mov AX,4C01h            /* Exit with error */
  648.     asm int 21h
  649.   }
  650.   m = k;                        /* Number of tokens and options */
  651.  
  652.   /* Process tokens and options */
  653.   fls = drs = 0;                /* Initialize files, dirs removed */
  654.   for (i = 0; i < m; i++)
  655.     if (t[i])                   /* Token */
  656.       remove(a[i]);
  657.     else                        /* Options */
  658.       for (p = a[i]; (k = *p) != 0; p++)
  659.         if (k == '/')       {}          /* Ignore extra /'s */
  660.         else if ((k &= 0x5f) == 'Q')  ask = 0;  /* Don't prompt */
  661.         else if (k == 'P')  ask = 1;    /* Prompt */
  662.         else if (k == 'N')  sub = 0;    /* No subdirs */
  663.         else if (k == 'S')  sub = 1;    /* Subdirs */
  664.         else if (k == 'V')  att &= ~2;  /* Ignore hidden */
  665.         else if (k == 'H')  att |= 2;   /* Delete hidden */
  666.         else if (k == 'T')  att &= ~4;  /* Ignore system */
  667.         else if (k == 'Y')  att |= 4;   /* Delete system */
  668.         else if (k == 'W')  rof = 0;    /* Ignore read-only */
  669.         else if (k == 'R')  rof = 1;    /* Delete read-only */
  670.         else if (k == 'A')  fin = 0;    /* Use *.* in subs */
  671.         else if (k == 'F')  fin = 1;    /* Find file */
  672.         else                            /* Invalid option */
  673.         {
  674.           if (*p != '?')
  675.             pputs("Invalid option\r\n");
  676.           pputs("\
  677. RM 1.4  Copyright (C) 1988,1989  Mark Adler  All rights reserved.\r\n\
  678. Valid options are (*=default):\r\n\
  679.  /P\t- Prompt for each deletion\r\n\
  680.  /Q\t- No prompting *\r\n\
  681.  /S\t- Remove subdirectories\r\n\
  682.  /N\t- Ignore subdirectories *\r\n\
  683.  /H\t- Delete hidden files\r\n\
  684.  /V\t- Ignore hidden files *\r\n\
  685.  /Y\t- Delete system files\r\n\
  686.  /T\t- Ignore system files *\r\n\
  687.  /R\t- Delete read-only files!\r\n\
  688.  /W\t- Ignore read-only files *\r\n\
  689.  /F\t- Find files\r\n\
  690.  /A\t- All files *\r\n\
  691.  /?\t- Display this list\r\n");
  692.             asm mov AX,4C01h    /* Exit with error */
  693.             asm int 21h
  694.         }
  695.   pputs(ultod(fls, t));
  696.   pputs(" file");
  697.   if (fls != 1)
  698.     pputc('s');
  699.   pputs(" deleted");
  700.   if (drs)
  701.   {
  702.     pputs(" and ");
  703.     pputs(ultod(drs, t));
  704.     pputs(" director");
  705.     if (drs != 1)
  706.       pputs("ies");
  707.     else
  708.       pputc('y');
  709.     pputs(" removed");
  710.   }
  711.   pputs("\r\n");
  712. }
  713.