home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS 1992 June / SIMTEL_0692.cdr / msdos / dirutl / sz15.arc / SZ.C next >
Text File  |  1989-10-21  |  20KB  |  664 lines

  1. #pragma inline
  2.  
  3. /*
  4.  
  5. SZ.C  Copyright (C) 1988  Mark Adler  Pasadena, CA
  6.       All rights reserved.
  7.  
  8. Version history -
  9.  
  10. 1.0     29 May 1988     First public version
  11. 1.1      4 Jun 1988     Added /F, /A
  12. 1.2      6 Nov 1988     Added /Y, /K, commas in long integers
  13. 1.3     18 Nov 1988     Fixed for Turbo C 2.0 (label needs statement)
  14. 1.4      3 Feb 1989     Display files and directories in lower case
  15. 1.5     21 Oct 1989     Catch dangling options and ignore command
  16.  
  17.  
  18. SZ will determine the amount of space taken up by all the files in a
  19. directory and its subdirectories, including system and hidden files.
  20. The command:
  21.  
  22.      sz
  23.  
  24. will give a message like:
  25.  
  26.      bytes:space:floppy = 391,670 : 474k : 405k  in 85 files.
  27.  
  28. which is information about the files in the current directory and its
  29. subdirectories on the current drive.  The first number is the sum of the
  30. byte counts for all the files.  The second number is the number of
  31. kbytes (multiples of 1024) that the files actually occupy on the drive
  32. they are on.  The third number is the number of kbytes the files would
  33. take up on a floppy drive with a blocking of 512 bytes.  The last number
  34. is the number of files included in those totals.  The space taken by the
  35. directory entries in the subdirectories is not computed or included,
  36. since DOS does not provide this information.  k values are rounded up to
  37. the next highest value.
  38.  
  39. Using the /Y option for bYtes, SZ can display the second two values in
  40. bytes instead of kbytes, so:
  41.  
  42.      sz/y
  43.  
  44. might display this for the first example:
  45.  
  46.      bytes:space:floppy = 391,670 : 485,376 : 414,208  in 85 files.
  47.  
  48. SZ will take arguments for drives and directories other than the current
  49. one.  For example:
  50.  
  51.      sz a:\sys
  52.  
  53. Arguments may contain wild card characters, but note that the ambiguous
  54. name only applies to the level in the path where it appears:
  55.  
  56.      sz \dos\*.com
  57.  
  58. Multiple arguments can be used and the result will be the sum for all
  59. the files:
  60.  
  61.      sz \bin \bat \dos
  62.  
  63. SZ can list the files that it finds and produce subtotals for the
  64. directories it processes.  The option for this is "/L":
  65.  
  66.      sz/l \dos
  67.  
  68. might produce the output:
  69.  
  70. directory \dos\*.*
  71.    command.com  ansi.sys     country.sys  display.sys  driver.sys
  72.    fastopen.exe fdisk.com    keyb.com     keyboard.sys mode.com
  73.    nlsfunc.exe  printer.sys  replace.exe  sys.com      vdisk.sys
  74.    xcopy.exe    ega.cpi      lcd.cpi      4201.cpi     5202.cpi
  75.    append.exe   assign.com   attrib.exe   backup.com   basic.com
  76.    basica.com   chkdsk.com   comp.com     debug.com    diskcomp.com
  77.    diskcopy.com edlin.com    find.exe     graftabl.com graphics.com
  78.    join.exe     label.com    more.com     print.com    recover.com
  79.    restore.com  share.exe    sort.exe     subst.exe    tree.com
  80.    basic.pif    basica.pif   mortgage.bas exe2bin.exe  lib.exe
  81.    link.exe     vdisk.asm
  82. directory \dos\format\*.*
  83.       format.com   select.com
  84.       bytes:space:floppy = 15,779 : 18k : 16k  in 2 files.
  85.    bytes:space:floppy = 671,088 : 708k : 669k  in 54 files.
  86.  
  87. Note the indentation of subdirectories.
  88.  
  89. SZ can try to find matches for a filename in all the directories.  The
  90. option is "/F".  For example:
  91.  
  92.      sz/fl \*.tex
  93.  
  94. will find and display all *.tex files anywhere on the current drive.
  95.  
  96. SZ will take options that are preceded by a slash (/).  The command line
  97. is read from left to right, processing options and file/path names as
  98. they appear.  However, options that are appended to a file/path name
  99. take effect before that name is processed.  For example, this command
  100. would do the same thing as "sz/l \dos":
  101.  
  102.      sz \dos/l
  103.  
  104. The options are:
  105.  
  106.      /L   - Turn on listing.
  107.      /Q   - Quiet---turn off listing (default).
  108.      /N   - Do not include subdirectories in total or listing.
  109.      /S   - Include subdirectories (default).
  110.      /I   - Ignore hidden and system files.
  111.      /H   - Include hidden and system files (default).
  112.      /W   - Weird---exclude "typical" files, where atypical files are
  113.             ones that are hidden, system, or read-only.
  114.      /T   - Include typical files (default).
  115.      /B   - Exclude backed up files.
  116.      /U   - Include backed up files (default).
  117.      /F   - Find a file---use the last name in to match in subdirs also.
  118.      /A   - All files---use *.* in the subdirs (default).
  119.      /Y   - Display values in bytes.
  120.      /K   - Display space and floppy space in kbytes (defualt).
  121.      /nnn - Change the floppy blocking factor to nnn (default is 512).
  122.      /?   - Display version number and list of options.
  123.  
  124. Options can be combined with or without additional slashes.  For
  125. example, these commands do the same thing:
  126.  
  127.      sz /l /b \
  128.      sz /l/b \
  129.      sz/lb \
  130.      sz \/lb
  131.  
  132. What those commands do is list all the files that have not been backed
  133. up yet and what subdirectories they are in.  Examples of useful
  134. combinations of options are:
  135.  
  136.      sz /l \            List all files on the current drive.
  137.      sz /fl \name       Find and list all files on the current drive
  138.                         that match 'name'.
  139.      sz /lb \           List all files on the current drive that need to
  140.                         be backed up.
  141.      sz /wl a:          List all atypical files on A:
  142.      sz /wil \          List all read-only files on current drive
  143.      sz /nl             List the files in the current directory
  144.  
  145. More complicated combinations are possible, of course.  For example:
  146.  
  147.      sz /n128bl d: /s \ /q a:
  148.  
  149. which lists the files that need to be backed only from the current
  150. directory in D:, lists all the files that need to be backed on the
  151. current drive, and adds the sizes for the files in the current directory
  152. on A: and its subdirectories, without listing those files.  For all of
  153. this, the floppy blocking factor is 128 instead of 512.  Note that
  154. options remain in effect on a command line until contradicted.  For
  155. example, the /n turns off subdirectory searches for d:, but the /s turns
  156. it back on again for \ and A:.  The /L turns on listing for D: and \,
  157. but the /Q turns off listing for A:.  The /B and /128 is in effect for
  158. all three.
  159.  
  160. Note that the commands:
  161.  
  162.      sz \/l
  163.      sz \ /l
  164.  
  165. are different.  The first lists all the files on the current drive.  The
  166. second says to compute the space for all files on the current drive but
  167. the /L would have no effect.  In this case, SZ notices the "dangling"
  168. option before it does anything and simply ignores the entire command,
  169. printing:
  170.  
  171.     Dangling option---entire command ignored
  172.  
  173. The exception to this is if no names appear on the command line, in
  174. which case the default name "*.*" is appended to the end of the command
  175. line.
  176.  
  177. The last line displayed by SZ is always the grand total for all files
  178. counted, whether it is indented or not.
  179.  
  180. If list is on (/L) and the directory level becomes more that 10 deep,
  181. then the listing is not done for that level (or deeper levels).  The
  182. message "(level too deep---no files will be listed)" will be displayed
  183. to indicate this.
  184.  
  185. */
  186.  
  187.  
  188. typedef unsigned long ulong;
  189.  
  190. #define IND 3           /* Indentation per level */
  191. #define MXV 10          /* Maximum level display goes to */
  192.  
  193.  
  194. /* Option flags, assigned to default values */
  195. char lis = 0,           /* List filenames as found */
  196.      sub = 1,           /* Include subdirectories */
  197.      hid = 1,           /* Include hidden files */
  198.      typ = 1,           /* Include typical files */
  199.      bak = 1,           /* Include backed up files */
  200.      fin = 0,           /* Find---use same name in subdirs */
  201.      ink = 1;           /* Show space, floppy in k (1024) */
  202. unsigned flp = 512;     /* Default floppy blocking */
  203.  
  204. unsigned curb;          /* Blocking factor for current device */
  205. ulong lstn;             /* Last number of files displayed */
  206.  
  207.  
  208. /* Structure for fnd1st(), fndnxt() */
  209. struct find {
  210.   char rsvd[21];        /* What DOS needs to keep track of find */
  211.   char attr;            /* File attribute */
  212.   unsigned time;        /* Time stamp */
  213.   unsigned date;        /* Date stamp */
  214.   ulong size;           /* File size in bytes */
  215.   char name[13];        /* FIle name as a zero terminated string */
  216. };
  217.  
  218.  
  219. /* Send character 'c' to stdout */
  220. void pputc(char c)
  221. {
  222.   asm mov DL,c
  223.   asm mov AH,2
  224.   asm int 21h
  225. }
  226.  
  227.  
  228. /* Send string 's' to stdout, convert upper to lower case */
  229. void pputs(char *s)
  230. {
  231.   asm mov SI,s
  232.   asm cld
  233.     plp:
  234.   asm  lodsb
  235.   asm  test AL,AL
  236.   asm  jz pfin
  237.   asm  mov DL,AL
  238.   asm  mov AH,2
  239.   asm  int 21h
  240.   asm  jmp short plp
  241.     pfin: ;
  242. }
  243.  
  244.  
  245. /* Convert the string s from upper case to lower case */
  246. char *strlow(char *s)
  247. {
  248.   asm mov AX,DS
  249.   asm mov ES,AX
  250.   asm cld
  251.   asm mov SI,s
  252.   asm lea DI,[SI-1]
  253.     llp:
  254.   asm  inc DI
  255.     llpnoi:
  256.   asm  lodsb
  257.   asm  test AL,AL
  258.   asm  jz lfin
  259.   asm  cmp AL,'A'
  260.   asm  jb llp
  261.   asm  cmp AL,'Z'
  262.   asm  ja llp
  263.   asm   add AL,'a'-'A'
  264.   asm   stosb
  265.   asm   jmp short llpnoi
  266.     lfin: ;
  267.   return s;
  268. }
  269.  
  270.  
  271. /* Find first match to 'p', attribute 'a', results in 'd' */
  272. int fnd1st(char *p, struct find *f, int a)
  273. {
  274.   /* Set DTA to f */
  275.   asm mov DX,f
  276.   asm mov AH,1Ah
  277.   asm int 21h
  278.  
  279.   /* Do find first */
  280.   asm mov DX,p
  281.   asm mov CX,a
  282.   asm mov AH,4Eh
  283.   asm int 21h
  284.   asm sbb AX,AX
  285.   return _AX;
  286. }
  287.  
  288.  
  289. /* Find next match for fnd1st() done on 'd' */
  290. int fndnxt(struct find *f)
  291. {
  292.   /* Set DTA to f */
  293.   asm mov DX,f
  294.   asm mov AH,1Ah
  295.   asm int 21h
  296.  
  297.   /* Do find next */
  298.   asm mov AH,4Fh
  299.   asm int 21h
  300.   asm sbb AX,AX
  301.   return _AX;
  302. }
  303.  
  304.  
  305. /* Return the allocation block size for drive 'd' (0=default) */
  306. int block(int d)
  307. {
  308.   asm push DS
  309.   asm mov DL,d
  310.   asm mov AH,1Ch
  311.   asm int 21h
  312.   asm pop DS
  313.   asm mov AH,0
  314.   asm mul CX
  315.   return _AX;
  316. }
  317.  
  318.  
  319. /* Copy string s to string d, return end of d */
  320. char *scpy(char *d, char *s)
  321. {
  322.   asm mov SI,s
  323.   asm mov DI,d
  324.   asm cld
  325.   asm mov AX,DS
  326.   asm mov ES,AX
  327.     slp:
  328.   asm  lodsb
  329.   asm  stosb
  330.   asm  test AL,AL
  331.   asm  jnz slp
  332.   asm mov AX,DI
  333.   asm dec AX
  334.   return (char *) _AX;
  335. }
  336.  
  337.  
  338. /* Convert unsigned long 'n' to decimal in string 's' with commas */
  339. char *ultod(ulong n, char *s)
  340. {
  341.   /* Load n into SI:AX, s into DI, and the radix into BX */
  342.   asm mov DI,s
  343.   asm mov AX,n
  344.   asm mov SI,n+2
  345.   asm mov BX,10
  346.  
  347.   /* Convert n into a digit string, least significant digit first */
  348.   asm mov CX,3          /* Digits before the next comma */
  349.      dlp:
  350.         /* Divide SI:AX by BX, quotient to SI:AX, remainder to DX */
  351.   asm  xchg AX,SI       /* SI = low n */
  352.   asm  sub DX,DX        /* DX:AX = high n */
  353.   asm  div BX           /* AX = high q, DX = temporary r */
  354.   asm  xchg AX,SI       /* SI = high q, DX:AX = temp r:low q */
  355.   asm  div BX           /* SI:AX = q, DX = r */
  356.         /* Put digit in string */
  357.   asm  add DL,'0'
  358.   asm  mov [DI],DL
  359.   asm  inc DI
  360.         /* Do until SI:AX is zero */
  361.   asm  mov DX,AX
  362.   asm  or DX,SI
  363.   asm  jz dfin
  364.         /* Put in comma if three digits since last comma */
  365.   asm  loop dlp
  366.   asm   mov byte ptr [DI],','
  367.   asm   inc DI
  368.   asm   mov CL,3
  369.   asm   jmp short dlp
  370.         /* Terminate string */
  371.      dfin:
  372.   asm mov [DI],AL
  373.  
  374.   /* Reverse the string, putting most significant digit first */
  375.   asm mov SI,s
  376.      rlp:
  377.   asm  dec DI
  378.   asm  cmp DI,SI
  379.   asm  jna rfin
  380.   asm  mov AL,[SI]
  381.   asm  xchg AL,[DI]
  382.   asm  mov [SI],AL
  383.   asm  inc SI
  384.   asm  jmp short rlp
  385.      rfin:
  386.  
  387.   /* Return pointer to start of string */
  388.   asm mov AX,s
  389.   return (char *) _AX;
  390. }
  391.  
  392.  
  393. /* Print 'n' spaces to stdout */
  394. void spaces(int n)
  395. {
  396.   asm mov CX,n
  397.   asm jcxz infin
  398.   asm mov DL,' '
  399.   asm mov AH,2
  400.      ilp:
  401.   asm  int 21h
  402.   asm  loop ilp
  403.      infin: ;
  404. }
  405.  
  406.  
  407. /* Display a ulong in bytes or k depending on ink */
  408. void shval(ulong n)
  409. {
  410.   char s[11];
  411.  
  412.   if (ink)                      /* If in K, go up to next K */
  413.     n = (n >> 10) + ((n & 0x3ffL) != 0);
  414.   pputs(ultod(n, s));
  415.   if (ink)
  416.     pputc('k');
  417. }
  418.  
  419.  
  420. /* Display the sizes and number of files */
  421. void show(ulong n, ulong b, ulong h, ulong f)
  422. {
  423.   char s[11];
  424.  
  425.   pputs("bytes:space:floppy = ");
  426.   pputs(ultod(b, s));
  427.   pputs(" : ");
  428.   shval(h);
  429.   pputs(" : ");
  430.   shval(f);
  431.   pputs("  in ");
  432.   pputs(ultod(n, s));
  433.   pputs(" file");
  434.   if (n != 1)
  435.     pputc('s');
  436.   pputs(".\r\n");
  437. }
  438.  
  439.  
  440. void size(int v, char *a, ulong *nn, ulong *bb, ulong *hh, ulong *ff)
  441. {
  442.   register char *p;
  443.   register int e;
  444.   int c, m;
  445.   char *q;
  446.   ulong n, b, h, f;
  447.   struct find d;
  448.   char s[128];
  449.   char t[128];
  450.  
  451.   /* Setup */
  452.   p = scpy(s, a) - 1;           /* Copy path being searched */
  453.   if (p < s || *p == '\\' || *p == ':')
  454.     scpy(p + 1, "*.*");         /* If no name, append wildcard */
  455.   else
  456.     do {
  457.       p--;                      /* Scan back for path delimiter */
  458.     } while (p >= s && *p != '\\' && *p != ':');
  459.   p++;                          /* Point past delimiter */
  460.   n = b = h = f = 0;            /* Initialize sizes */
  461.   c = 0;                        /* File column */
  462.  
  463.   /* Find matching files */
  464.   m = 0;                        /* No matches yet */
  465.   e = fnd1st(s, &d, 7);         /* Don't look for subdirectories */
  466.   while (!e)
  467.   {
  468.     e = d.attr;                 /* Get attribute */
  469.     if ((bak || (e & 0x20)) &&  /* Exclude backed up if bak = 0 */
  470.         (typ || (e & 7)) &&     /* Exclude typical if typ = 0 */
  471.         (hid || !(e & 6)))      /* Exclude hidden/system if hid = 0 */
  472.     {
  473.       if (lis && !m)
  474.       {
  475.         m = 1;
  476.         pputs("directory ");
  477.         pputs(strlow(s));
  478.         pputs("\r\n");
  479.         if (v > MXV)
  480.           pputs("(level too deep---no files will be listed)\r\n");
  481.       }
  482.       n++;
  483.       b += d.size;
  484.       h += ((d.size + curb - 1) / curb) * curb;
  485.       f += ((d.size + flp - 1) / flp) * flp;
  486.       if (lis && v <= MXV)
  487.       {
  488.         if (!c)
  489.           spaces(v * IND);
  490.         pputs(strlow(d.name));
  491.         c = (c + 1) % ((79 - v * IND) / 14);
  492.         if (c)
  493.         {
  494.           for (e = 0; d.name[e]; e++)
  495.             ;
  496.           spaces(14 - e);
  497.         }
  498.         else
  499.           pputs("\r\n");
  500.       }
  501.     }
  502.     e = fndnxt(&d);
  503.   }
  504.   if (c)
  505.     pputs("\r\n");
  506.  
  507.   /* Find matching subdirectories, if requested */
  508.   if (sub)
  509.   {
  510.     if (fin)
  511.     {
  512.       scpy(t, p);               /* If find, save the name */
  513.       scpy(p, "*.*");           /*  and search all subdirectories */
  514.     }
  515.     e = fnd1st(s, &d, 0x17);    /* Look at everything except labels */
  516.     while (!e)                  /* Do all subdirectories */
  517.     {
  518.       if ((d.attr & 0x10) &&
  519.           (d.name[0] != '.' || (d.name[1] && d.name[1] != '.')))
  520.       {                         /* Is subdir and not "." or ".." */
  521.         q = scpy(p, d.name);    /* Overlay wildcard with found name */
  522.         *q++ = '\\';            /* Append path delimiter */
  523.         scpy(q, fin ? t : "*.*");       /* New wildcard */
  524.         size(v + 1, s, &n, &b, &h, &f); /* Do subdirectory */
  525.       }
  526.       e = fndnxt(&d);
  527.     }
  528.   }
  529.   if (lis && v <= MXV && m)
  530.   {
  531.     spaces(v * IND);
  532.     show(n, b, h, f);
  533.     lstn = n;
  534.   }
  535.  
  536.   /* Update counts for caller */
  537.   *nn += n;
  538.   *bb += b;
  539.   *hh += h;
  540.   *ff += f;
  541. }
  542.  
  543.  
  544. void main(int argc, char *argv[])
  545. {
  546.   register char *p;
  547.   register int k;
  548.   int i, m, s;
  549.   ulong n, b, h, f;     /* Sizes */
  550.   char *a[64];          /* Tokens and options */
  551.   char t[64];           /* Token/~option flags */
  552.  
  553.  
  554.   /* Parse line into tokens and options */
  555.   m = k = 0;
  556.   for (i = 1; i < argc; i++)    /* Do command line */
  557.   {
  558.     p = argv[i];                /* Next argument */
  559.     if (*p != '/')              /* See if token */
  560.     {
  561.       a[k] = p++;
  562.       t[k++] = 1;               /* Token */
  563.       m = 1;                    /* Token flag */
  564.       while (*p && *p != '/')
  565.         p++;
  566.       if (*p)                   /* See if options on token */
  567.       {
  568.         *p++ = '\0';            /* Terminate token string */
  569.         a[k] = a[k-1];          /* Put options appended to token */
  570.         t[k] = 1;               /*  BEFORE that token. */
  571.         a[k-1] = p;
  572.         t[k-1] = 0;             /* Option(s) */
  573.         k++;
  574.       }
  575.     }
  576.     else
  577.     {
  578.       a[k] = ++p;               /* Options start after the slash */
  579.       t[k++] = 0;               /* Option(s) */
  580.       if (m)
  581.         m = -1;                 /* Dangling option flag */
  582.     }
  583.   }
  584.   if (m < 0)
  585.   {
  586.     pputs("Dangling option---entire command ignored\r\n");
  587.     asm mov AX,4C01h            /* Exit with error */
  588.     asm int 21h
  589.   }
  590.   m = k;                        /* Number of tokens and options */
  591.  
  592.   /* Process tokens and options */
  593.   n = b = h = f = 0;            /* Initialize sizes */
  594.   s = 1;                        /* Haven't done a size() yet */
  595.   lstn = -1;                    /* Haven't shown any sizes yet */
  596.   for (i = 0; i < m; i++)
  597.     if (t[i])                   /* Token */
  598.     {
  599.       s = 0;                    /* size() called at least once */
  600.       curb = block(a[i][1] == ':' ? (a[i][0] & 0x5f) - 'A' + 1 : 0);
  601.       size(0, a[i], &n, &b, &h, &f);
  602.     }
  603.     else                        /* Options */
  604.       for (p = a[i]; (k = *p) != 0; p++)
  605.         if (k == '/')       {}          /* Ignore extra /'s */
  606.         else if ((k &= 0x5f) == 'Q')  lis = 0;    /* Quiet */
  607.         else if (k == 'L')  lis = 1;    /* List */
  608.         else if (k == 'N')  sub = 0;    /* No subdirs */
  609.         else if (k == 'S')  sub = 1;    /* Subdirs */
  610.         else if (k == 'I')  hid = 0;    /* Ignore hidden */
  611.         else if (k == 'H')  hid = 1;    /* Show hidden */
  612.         else if (k == 'W')  typ = 0;    /* Weird (no typical) */
  613.         else if (k == 'T')  typ = 1;    /* Include typical */
  614.         else if (k == 'B')  bak = 0;    /* Exclude backed up */
  615.         else if (k == 'U')  bak = 1;    /* Include backed up */
  616.         else if (k == 'A')  fin = 0;    /* Use *.* in subs */
  617.         else if (k == 'F')  fin = 1;    /* Find file */
  618.         else if (k == 'Y')  ink = 0;    /* Display in bytes */
  619.         else if (k == 'K')  ink = 1;    /* Display in K bytes */
  620.         else if (*p < '0' || *p > '9')  /* Invalid option or ? */
  621.         {
  622.           if (*p != '?')
  623.             pputs("Invalid option\r\n");
  624.           pputs("\
  625. SZ 1.5  Copyright (C) 1988,1989  Mark Adler  All rights reserved.\r\n\
  626. Valid options are (*=default):\r\n\
  627.  /L\t- Turn on listing\r\n\
  628.  /Q\t- Turn off listing *\r\n\
  629.  /N\t- Exclude subdirectories\r\n\
  630.  /S\t- Include subdirectories *\r\n\
  631.  /I\t- Ignore hidden files\r\n\
  632.  /H\t- Include hidden files *\r\n\
  633.  /W\t- Exclude typical files\r\n\
  634.  /T\t- Include typical files *\r\n\
  635.  /B\t- Exclude backed up files\r\n\
  636.  /U\t- Include backed up files *\r\n\
  637.  /F\t- Find files\r\n\
  638.  /A\t- All files *\r\n\
  639.  /Y\t- Display in bytes\r\n\
  640.  /K\t- Display in K *\r\n\
  641.  /nnn\t- Change floppy blocking to nnn\r\n\
  642.  /?\t- Display this list\r\n");
  643.             asm mov AX,4C01h    /* Exit with error */
  644.             asm int 21h
  645.         }
  646.         else                            /* New floppy blocking factor */
  647.         {
  648.           flp = 0;
  649.           do {
  650.             flp = 10 * flp + *p++ - '0';
  651.           } while (*p >= '0' && *p <= '9');
  652.           p--;
  653.         }
  654.   if (s)                        /* Haven't done any calls to size() */
  655.   {
  656.     curb = block(0);
  657.     size(0, "", &n, &b, &h, &f);
  658.   }
  659.  
  660.   /* Show grand total if not already shown */
  661.   if (lstn != n)
  662.     show(n, b, h, f);
  663. }
  664.