home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / std_unix / pax / 5 / list.c < prev    next >
Encoding:
C/C++ Source or Header  |  1989-01-07  |  15.8 KB  |  668 lines

  1. /* $Source: /u/mark/src/pax/RCS/list.c,v $
  2.  *
  3.  * $Revision: 1.1 $
  4.  *
  5.  * list.c - List all files on an archive
  6.  *
  7.  * DESCRIPTION
  8.  *
  9.  *    These function are needed to support archive table of contents and
  10.  *    verbose mode during extraction and creation of achives.
  11.  *
  12.  * AUTHOR
  13.  *
  14.  *    Mark H. Colburn, NAPS International (mark@jhereg.mn.org)
  15.  *
  16.  * Sponsored by The USENIX Association for public distribution. 
  17.  *
  18.  * Copyright (c) 1989 Mark H. Colburn.
  19.  * All rights reserved.
  20.  *
  21.  * Redistribution and use in source and binary forms are permitted
  22.  * provided that the above copyright notice is duplicated in all such 
  23.  * forms and that any documentation, advertising materials, and other 
  24.  * materials related to such distribution and use acknowledge that the 
  25.  * software was developed * by Mark H. Colburn and sponsored by The 
  26.  * USENIX Association. 
  27.  *
  28.  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
  29.  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
  30.  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  31.  *
  32.  * $Log:    list.c,v $
  33.  * Revision 1.1  88/12/23  18:02:14  mark
  34.  * Initial revision
  35.  * 
  36.  */
  37.  
  38. #ifndef lint
  39. static char *ident = "$Id: list.c,v 1.1 88/12/23 18:02:14 mark Rel $";
  40. static char *copyright = "Copyright (c) 1989 Mark H. Colburn.\nAll rights reserved.\n";
  41. #endif /* ! lint */
  42.  
  43.  
  44. /* Headers */
  45.  
  46. #include "pax.h"
  47.  
  48.  
  49. /* Defines */
  50.  
  51. /*
  52.  * isodigit returns non zero iff argument is an octal digit, zero otherwise
  53.  */
  54. #define    ISODIGIT(c)    (((c) >= '0') && ((c) <= '7'))
  55.  
  56.  
  57. /* Function Prototypes */
  58.  
  59. #ifdef __STDC__
  60.  
  61. static void cpio_entry(char *, Stat *);
  62. static void tar_entry(char *, Stat *);
  63. static void pax_entry(char *, Stat *);
  64. static void print_mode(ushort);
  65. static long from_oct(int digs, char *where);
  66.  
  67. #else /* !__STDC__ */
  68.  
  69. static void cpio_entry();
  70. static void tar_entry();
  71. static void pax_entry();
  72. static void print_mode();
  73. static long from_oct();
  74.  
  75. #endif /* __STDC__ */
  76.  
  77.  
  78. /* Internal Identifiers */
  79.  
  80. static char       *monnames[] = {
  81.     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  82.     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
  83. };
  84.  
  85.  
  86. /* read_header - read a header record
  87.  *
  88.  * DESCRIPTION
  89.  *
  90.  *     Read a record that's supposed to be a header record. Return its 
  91.  *    address in "head", and if it is good, the file's size in 
  92.  *    asb->sb_size.  Decode things from a file header record into a "Stat". 
  93.  *    Also set "head_standard" to !=0 or ==0 depending whether header record 
  94.  *    is "Unix Standard" tar format or regular old tar format. 
  95.  *
  96.  * PARAMETERS
  97.  *
  98.  *    char   *name        - pointer which will contain name of file
  99.  *    Stat   *asb        - pointer which will contain stat info
  100.  *
  101.  * RETURNS
  102.  *
  103.  *     Return 1 for success, 0 if the checksum is bad, EOF on eof, 2 for a 
  104.  *     record full of zeros (EOF marker). 
  105.  */
  106.  
  107. #ifdef __STDC__
  108.  
  109. int read_header(char *name, Stat *asb)
  110.  
  111. #else
  112.     
  113. int read_header(name, asb)
  114. char           *name;
  115. Stat           *asb;
  116.  
  117. #endif
  118. {
  119.     int             i;
  120.     long            sum;
  121.     long        recsum;
  122.     Link           *link;
  123.     char           *p;
  124.     char            hdrbuf[BLOCKSIZE];
  125.  
  126.     memset((char *)asb, 0, sizeof(Stat));
  127.     /* read the header from the buffer */
  128.     if (buf_read(hdrbuf, BLOCKSIZE) != 0) {
  129.     return (EOF);
  130.     }
  131.  
  132.     strcpy(name, hdrbuf);
  133.  
  134.     recsum = from_oct(8, &hdrbuf[148]);
  135.     sum = 0;
  136.     p = hdrbuf;
  137.     for (i = 0 ; i < 500; i++) {
  138.  
  139.     /*
  140.      * We can't use unsigned char here because of old compilers, e.g. V7. 
  141.      */
  142.     sum += 0xFF & *p++;
  143.     }
  144.  
  145.     /* Adjust checksum to count the "chksum" field as blanks. */
  146.     for (i = 0; i < 8; i++) {
  147.     sum -= 0xFF & hdrbuf[148 + i];
  148.     }
  149.     sum += ' ' * 8;
  150.  
  151.     if (sum == 8 * ' ') {
  152.  
  153.     /*
  154.      * This is a zeroed record...whole record is 0's except for the 8
  155.      * blanks we faked for the checksum field. 
  156.      */
  157.     return (2);
  158.     }
  159.     if (sum == recsum) {
  160.     /*
  161.      * Good record.  Decode file size and return. 
  162.      */
  163.     if (hdrbuf[156] != LNKTYPE) {
  164.         asb->sb_size = from_oct(1 + 12, &hdrbuf[124]);
  165.     }
  166.     asb->sb_mtime = from_oct(1 + 12, &hdrbuf[136]);
  167.     asb->sb_mode = from_oct(8, &hdrbuf[100]);
  168.  
  169.     if (strcmp(&hdrbuf[257], TMAGIC) == 0) {
  170.         /* Unix Standard tar archive */
  171.         head_standard = 1;
  172. #ifdef NONAMES
  173.         asb->sb_uid = from_oct(8, &hdrbuf[108]);
  174.         asb->sb_gid = from_oct(8, &hdrbuf[116]);
  175. #else
  176.         asb->sb_uid = finduid(&hdrbuf[265]);
  177.         asb->sb_gid = findgid(&hdrbuf[297]);
  178. #endif
  179.         switch (hdrbuf[156]) {
  180.         case BLKTYPE:
  181.         case CHRTYPE:
  182.         asb->sb_rdev = makedev(from_oct(8, &hdrbuf[329]),
  183.                       from_oct(8, &hdrbuf[337]));
  184.         break;
  185.         default:
  186.         /* do nothing... */
  187.         break;
  188.         }
  189.     } else {
  190.         /* Old fashioned tar archive */
  191.         head_standard = 0;
  192.         asb->sb_uid = from_oct(8, &hdrbuf[108]);
  193.         asb->sb_gid = from_oct(8, &hdrbuf[116]);
  194.     }
  195.  
  196.     switch (hdrbuf[156]) {
  197.     case REGTYPE:
  198.     case AREGTYPE:
  199.         /*
  200.          * Berkeley tar stores directories as regular files with a
  201.          * trailing /
  202.          */
  203.         if (name[strlen(name) - 1] == '/') {
  204.         name[strlen(name) - 1] = '\0';
  205.         asb->sb_mode |= S_IFDIR;
  206.         } else {
  207.         asb->sb_mode |= S_IFREG;
  208.         }
  209.         break;
  210.     case LNKTYPE:
  211.         asb->sb_nlink = 2;
  212.         linkto(&hdrbuf[157], asb);
  213.         linkto(name, asb);
  214.         asb->sb_mode |= S_IFREG;
  215.         break;
  216.     case BLKTYPE:
  217.         asb->sb_mode |= S_IFBLK;
  218.         break;
  219.     case CHRTYPE:
  220.         asb->sb_mode |= S_IFCHR;
  221.         break;
  222.     case DIRTYPE:
  223.         asb->sb_mode |= S_IFDIR;
  224.         break;
  225. #ifdef S_IFLNK
  226.     case SYMTYPE:
  227.         asb->sb_mode |= S_IFLNK;
  228.         strcpy(asb->sb_link, &hdrbuf[157]);
  229.         break;
  230. #endif
  231. #ifdef S_IFIFO
  232.     case FIFOTYPE:
  233.         asb->sb_mode |= S_IFIFO;
  234.         break;
  235. #endif
  236. #ifdef S_IFCTG
  237.     case CONTTYPE:
  238.         asb->sb_mode |= S_IFCTG;
  239.         break;
  240. #endif
  241.     }
  242.     return (1);
  243.     }
  244.     return (0);
  245. }
  246.  
  247.  
  248. /* print_entry - print a single table-of-contents entry
  249.  *
  250.  * DESCRIPTION
  251.  * 
  252.  *    Print_entry prints a single line of file information.  The format
  253.  *    of the line is the same as that used by the LS command.  For some
  254.  *    archive formats, various fields may not make any sense, such as
  255.  *    the link count on tar archives.  No error checking is done for bad
  256.  *    or invalid data.
  257.  *
  258.  * PARAMETERS
  259.  *
  260.  *    char   *name        - pointer to name to print an entry for
  261.  *    Stat   *asb        - pointer to the stat structure for the file
  262.  */
  263.  
  264. #ifdef __STDC__
  265.  
  266. void print_entry(char *name, Stat *asb)
  267.  
  268. #else
  269.     
  270. void print_entry(name, asb)
  271. char        *name;
  272. Stat            *asb;
  273.  
  274. #endif
  275. {
  276.     switch (ar_interface) {
  277.     case TAR:
  278.     tar_entry(name, asb);
  279.     break;
  280.     case CPIO:
  281.     cpio_entry(name, asb);
  282.     break;
  283.     case PAX: pax_entry(name, asb);
  284.     break;
  285.     }
  286. }
  287.  
  288.  
  289. /* cpio_entry - print a verbose cpio-style entry
  290.  *
  291.  * DESCRIPTION
  292.  *
  293.  *    Print_entry prints a single line of file information.  The format
  294.  *    of the line is the same as that used by the traditional cpio 
  295.  *    command.  No error checking is done for bad or invalid data.
  296.  *
  297.  * PARAMETERS
  298.  *
  299.  *    char   *name        - pointer to name to print an entry for
  300.  *    Stat   *asb        - pointer to the stat structure for the file
  301.  */
  302.  
  303. #ifdef __STDC__
  304.  
  305. static void cpio_entry(char *name, Stat *asb)
  306.  
  307. #else
  308.     
  309. static void cpio_entry(name, asb)
  310. char           *name;
  311. Stat           *asb;
  312.  
  313. #endif
  314. {
  315.     struct tm           *atm;
  316.     Link           *from;
  317.     struct passwd      *pwp;
  318.     struct group       *grp;
  319.  
  320.     if (f_list && f_verbose) {
  321.     fprintf(msgfile, "%-7o", asb->sb_mode);
  322.     atm = localtime(&asb->sb_mtime);
  323.     if (pwp = getpwuid((int) USH(asb->sb_uid))) {
  324.         fprintf(msgfile, "%-6s", pwp->pw_name);
  325.     } else {
  326.         fprintf(msgfile, "%-6u", USH(asb->sb_uid));
  327.     }
  328.     fprintf(msgfile,"%7ld  %3s %2d %02d:%02d:%02d %4d  ",
  329.                    asb->sb_size, monnames[atm->tm_mon], 
  330.                atm->tm_mday, atm->tm_hour, atm->tm_min, 
  331.                atm->tm_sec, atm->tm_year + 1900);
  332.     }
  333.     fprintf(msgfile, "%s", name);
  334.     if ((asb->sb_nlink > 1) && (from = islink(name, asb))) {
  335.     fprintf(msgfile, " linked to %s", from->l_name);
  336.     }
  337. #ifdef    S_IFLNK
  338.     if ((asb->sb_mode & S_IFMT) == S_IFLNK) {
  339.     fprintf(msgfile, " symbolic link to %s", asb->sb_link);
  340.     }
  341. #endif    /* S_IFLNK */
  342.     putc('\n', msgfile);
  343. }
  344.  
  345.  
  346. /* tar_entry - print a tar verbose mode entry
  347.  *
  348.  * DESCRIPTION
  349.  *
  350.  *    Print_entry prints a single line of tar file information.  The format
  351.  *    of the line is the same as that produced by the traditional tar 
  352.  *    command.  No error checking is done for bad or invalid data.
  353.  *
  354.  * PARAMETERS
  355.  *
  356.  *    char   *name        - pointer to name to print an entry for
  357.  *    Stat   *asb        - pointer to the stat structure for the file
  358.  */
  359.  
  360. #ifdef __STDC__
  361.  
  362. static void tar_entry(char *name, Stat *asb)
  363.  
  364. #else
  365.     
  366. static void tar_entry(name, asb)
  367. char        *name;
  368. Stat            *asb;
  369.  
  370. #endif
  371. {
  372.     struct tm             *atm;
  373.     int            i;
  374.     int            mode;
  375.     char               *symnam = "NULL";
  376.     Link               *link;
  377.  
  378.     if ((mode = asb->sb_mode & S_IFMT) == S_IFDIR) {
  379.     return;            /* don't print directories */
  380.     }
  381.     if (f_extract) {
  382.     switch (mode) {
  383. #ifdef S_IFLNK
  384.     case S_IFLNK:     /* This file is a symbolic link */
  385.         i = readlink(name, symnam, PATH_MAX - 1);
  386.         if (i < 0) {        /* Could not find symbolic link */
  387.         warn("can't read symbolic link", syserr());
  388.         } else {         /* Found symbolic link filename */
  389.         symnam[i] = '\0';
  390.         fprintf(msgfile, "x %s symbolic link to %s\n", name, symnam);
  391.         }
  392.         break;
  393. #endif
  394.     case S_IFREG:     /* It is a link or a file */
  395.         if ((asb->sb_nlink > 1) && (link = islink(name, asb))) {
  396.         fprintf(msgfile, "%s linked to %s\n", name, link->l_name); 
  397.         } else {
  398.         fprintf(msgfile, "x %s, %d bytes, %d tape blocks\n", 
  399.             name, asb->sb_size, ROUNDUP(asb->sb_size, 
  400.             BLOCKSIZE) / BLOCKSIZE);
  401.         }
  402.     }
  403.     } else if (f_append || f_create) {
  404.     switch (mode) {
  405. #ifdef S_IFLNK
  406.     case S_IFLNK:     /* This file is a symbolic link */
  407.         i = readlink(name, symnam, PATH_MAX - 1);
  408.         if (i < 0) {        /* Could not find symbolic link */
  409.         warn("can't read symbolic link", syserr());
  410.         } else {         /* Found symbolic link filename */
  411.         symnam[i] = '\0';
  412.         fprintf(msgfile, "a %s symbolic link to %s\n", name, symnam);
  413.         }
  414.         break;
  415. #endif
  416.     case S_IFREG:     /* It is a link or a file */
  417.         fprintf(msgfile, "a %s ", name);
  418.         if ((asb->sb_nlink > 1) && (link = islink(name, asb))) {
  419.         fprintf(msgfile, "link to %s\n", link->l_name); 
  420.         } else {
  421.         fprintf(msgfile, "%d Blocks\n", 
  422.             ROUNDUP(asb->sb_size, BLOCKSIZE) / BLOCKSIZE);
  423.         }
  424.         break;
  425.     }
  426.     } else if (f_list) {
  427.     if (f_verbose) {
  428.         atm = localtime(&asb->sb_mtime);
  429.         print_mode(asb->sb_mode);
  430.         fprintf(msgfile," %d/%d %6d %3s %2d %02d:%02d %4d %s",
  431.             asb->sb_uid, asb->sb_gid, asb->sb_size,
  432.             monnames[atm->tm_mon], atm->tm_mday, atm->tm_hour, 
  433.             atm->tm_min, atm->tm_year + 1900, name);
  434.     } else {
  435.         fprintf(msgfile, "%s", name);
  436.     }
  437.     switch (mode) {
  438. #ifdef S_IFLNK
  439.     case S_IFLNK:     /* This file is a symbolic link */
  440.         i = readlink(name, symnam, PATH_MAX - 1);
  441.         if (i < 0) {        /* Could not find symbolic link */
  442.         warn("can't read symbolic link", syserr());
  443.         } else {         /* Found symbolic link filename */
  444.         symnam[i] = '\0';
  445.         fprintf(msgfile, " symbolic link to %s", name, symnam);
  446.         }
  447.         break;
  448. #endif
  449.     case S_IFREG:     /* It is a link or a file */
  450.         if ((asb->sb_nlink > 1) && (link = islink(name, asb))) {
  451.         fprintf(msgfile, " linked to %s", link->l_name);
  452.         }
  453.         break;        /* Do not print out directories */
  454.     }
  455.     fputc('\n', msgfile);
  456.     } else {
  457.     fprintf(msgfile, "? %s %d blocks\n", name,
  458.         ROUNDUP(asb->sb_size, BLOCKSIZE) / BLOCKSIZE);
  459.     }
  460. }
  461.  
  462.  
  463. /* pax_entry - print a verbose cpio-style entry
  464.  *
  465.  * DESCRIPTION
  466.  *
  467.  *    Print_entry prints a single line of file information.  The format
  468.  *    of the line is the same as that used by the LS command.  
  469.  *    No error checking is done for bad or invalid data.
  470.  *
  471.  * PARAMETERS
  472.  *
  473.  *    char   *name        - pointer to name to print an entry for
  474.  *    Stat   *asb        - pointer to the stat structure for the file
  475.  */
  476.  
  477. #ifdef __STDC__
  478.  
  479. static void pax_entry(char *name, Stat *asb)
  480.  
  481. #else
  482.     
  483. static void pax_entry(name, asb)
  484. char           *name;
  485. Stat           *asb;
  486.  
  487. #endif
  488. {
  489.     struct tm           *atm;
  490.     Link           *from;
  491.     struct passwd      *pwp;
  492.     struct group       *grp;
  493.  
  494.     if (f_list && f_verbose) {
  495.     print_mode(asb->sb_mode);
  496.     fprintf(msgfile, " %2d", asb->sb_nlink);
  497.     atm = localtime(&asb->sb_mtime);
  498.     if (pwp = getpwuid((int) USH(asb->sb_uid))) {
  499.         fprintf(msgfile, " %-8s", pwp->pw_name);
  500.     } else {
  501.         fprintf(msgfile, " %-8u", USH(asb->sb_uid));
  502.     }
  503.     if (grp = getgrgid((int) USH(asb->sb_gid))) {
  504.         fprintf(msgfile, " %-8s", grp->gr_name);
  505.     } else {
  506.         fprintf(msgfile, " %-8u", USH(asb->sb_gid));
  507.     }
  508.     switch (asb->sb_mode & S_IFMT) {
  509.     case S_IFBLK:
  510.     case S_IFCHR:
  511.         fprintf(msgfile, "\t%3d, %3d",
  512.                    major(asb->sb_rdev), minor(asb->sb_rdev));
  513.         break;
  514.     case S_IFREG:
  515.         fprintf(msgfile, "\t%8ld", asb->sb_size);
  516.         break;
  517.     default:
  518.         fprintf(msgfile, "\t        ");
  519.     }
  520.     fprintf(msgfile," %3s %2d %02d:%02d ",
  521.                    monnames[atm->tm_mon], atm->tm_mday, 
  522.                atm->tm_hour, atm->tm_min);
  523.     }
  524.     fprintf(msgfile, "%s", name);
  525.     if ((asb->sb_nlink > 1) && (from = islink(name, asb))) {
  526.     fprintf(msgfile, " == %s", from->l_name);
  527.     }
  528. #ifdef    S_IFLNK
  529.     if ((asb->sb_mode & S_IFMT) == S_IFLNK) {
  530.     fprintf(msgfile, " -> %s", asb->sb_link);
  531.     }
  532. #endif    /* S_IFLNK */
  533.     putc('\n', msgfile);
  534. }
  535.  
  536.  
  537.  
  538.  
  539. /* print_mode - fancy file mode display
  540.  *
  541.  * DESCRIPTION
  542.  *
  543.  *    Print_mode displays a numeric file mode in the standard unix
  544.  *    representation, ala ls (-rwxrwxrwx).  No error checking is done
  545.  *    for bad mode combinations.  FIFOS, sybmbolic links, sticky bits,
  546.  *    block- and character-special devices are supported if supported
  547.  *    by the hosting implementation.
  548.  *
  549.  * PARAMETERS
  550.  *
  551.  *    ushort    mode    - The integer representation of the mode to print.
  552.  */
  553.  
  554. #ifdef __STDC__
  555.  
  556. static void print_mode(ushort mode)
  557.  
  558. #else
  559.     
  560. static void print_mode(mode)
  561. ushort    mode;
  562.  
  563. #endif
  564. {
  565.     /* Tar does not print the leading identifier... */
  566.     if (ar_interface != TAR) {
  567.     switch (mode & S_IFMT) {
  568.     case S_IFDIR: 
  569.         putc('d', msgfile); 
  570.         break;
  571. #ifdef    S_IFLNK
  572.     case S_IFLNK: 
  573.         putc('l', msgfile); 
  574.         break;
  575. #endif    /* S_IFLNK */
  576.     case S_IFBLK: 
  577.         putc('b', msgfile); 
  578.         break;
  579.     case S_IFCHR: 
  580.         putc('c', msgfile); 
  581.         break;
  582. #ifdef    S_IFIFO
  583.     case S_IFIFO: 
  584.         putc('p', msgfile); 
  585.         break; 
  586. #endif    /* S_IFIFO */ 
  587.     case S_IFREG: 
  588.     default:
  589.         putc('-', msgfile); 
  590.         break;
  591.     }
  592.     }
  593.     putc(mode & 0400 ? 'r' : '-', msgfile);
  594.     putc(mode & 0200 ? 'w' : '-', msgfile);
  595.     putc(mode & 0100
  596.      ? mode & 04000 ? 's' : 'x'
  597.      : mode & 04000 ? 'S' : '-', msgfile);
  598.     putc(mode & 0040 ? 'r' : '-', msgfile);
  599.     putc(mode & 0020 ? 'w' : '-', msgfile);
  600.     putc(mode & 0010
  601.      ? mode & 02000 ? 's' : 'x'
  602.      : mode & 02000 ? 'S' : '-', msgfile);
  603.     putc(mode & 0004 ? 'r' : '-', msgfile);
  604.     putc(mode & 0002 ? 'w' : '-', msgfile);
  605.     putc(mode & 0001
  606.      ? mode & 01000 ? 't' : 'x'
  607.      : mode & 01000 ? 'T' : '-', msgfile);
  608. }
  609.  
  610.  
  611. /* from_oct - quick and dirty octal conversion
  612.  *
  613.  * DESCRIPTION
  614.  *
  615.  *    From_oct will convert an ASCII representation of an octal number
  616.  *    to the numeric representation.  The number of characters to convert
  617.  *    is given by the parameter "digs".  If there are less numbers than
  618.  *    specified by "digs", then the routine returns -1.
  619.  *
  620.  * PARAMETERS
  621.  *
  622.  *    int digs    - Number to of digits to convert 
  623.  *    char *where    - Character representation of octal number
  624.  *
  625.  * RETURNS
  626.  *
  627.  *    The value of the octal number represented by the first digs
  628.  *    characters of the string where.  Result is -1 if the field 
  629.  *    is invalid (all blank, or nonoctal). 
  630.  *
  631.  * ERRORS
  632.  *
  633.  *    If the field is all blank, then the value returned is -1.
  634.  *
  635.  */
  636.  
  637. #ifdef __STDC__
  638.  
  639. static long from_oct(int digs, char *where)
  640.  
  641. #else
  642.  
  643. static long from_oct(digs, where)
  644. int             digs;        /* number of characters to convert */
  645. char           *where;        /* character representation of octal number */
  646.  
  647. #endif
  648. {
  649.     long            value;
  650.  
  651.     while (isspace(*where)) {    /* Skip spaces */
  652.     where++;
  653.     if (--digs <= 0) {
  654.         return(-1);        /* All blank field */
  655.     }
  656.     }
  657.     value = 0;
  658.     while (digs > 0 && ISODIGIT(*where)) {    /* Scan til nonoctal */
  659.     value = (value << 3) | (*where++ - '0');
  660.     --digs;
  661.     }
  662.  
  663.     if (digs > 0 && *where && !isspace(*where)) {
  664.     return(-1);        /* Ended on non-space/nul */
  665.     }
  666.     return(value);
  667. }
  668.