home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / magazine / drdobbs / c_spec / sources / dir.c < prev    next >
C/C++ Source or Header  |  1986-02-20  |  16KB  |  598 lines

  1. #include <stdio.h>
  2. #include <ctype.h>
  3. #include "getargs.h"
  4. #include "mydos.h"
  5. #include "dir.h"
  6.  
  7. /*----------------------------------------------------------------------+
  8.  *  DIR.C: An MSDOS directory access function.                |
  9.  *                                    |
  10.  *     (c) Copyright 1986, Allen I. Holub. All rights reserved.    |
  11.  *----------------------------------------------------------------------+
  12.  *  11/22/85    Modified so that the total amount of disk space used    |
  13.  *        (ie. # of clusters) is put into the total, rather    |
  14.  *        than the file size.                    |
  15.  *  12/28/85    Modified so that the names: name1 name2 name10 name11    |
  16.  *        will be sorted in that order, rather than in ASCII    |
  17.  *        (which would yield name1 name10 name11 name2)        |
  18.  *----------------------------------------------------------------------+
  19.  */
  20.  
  21. /* ROUND(n,u):    if n is an even multiple of u, evaluate to n, else
  22.  *        round n up to the next even multiple of u.
  23.  */
  24.  
  25. #define ROUND(n,u) ( !((n) % (u)) ? (n) : (((n) / (u)) + 1) * (u))
  26.  
  27.  
  28. #define BOLDFACE  "\033[1m"  /* Ansi esc sequence to turn bold face on  */
  29. #define ALL_OFF   "\033[0m"  /*          "     attributes off */
  30.  
  31. #define ATTRIBUTES    (READONLY | DIRTY | SYSTEM | HIDDEN | SUBDIR)
  32. #define iswhite(c)    ((c) == ' ' || (c) == '\t')
  33.  
  34. /*----------------------------------------------------------------------*/
  35.  
  36. extern char    *calloc (unsigned,unsigned); /* In standard library    */
  37. extern char    *cptolower(char*,char*);     /* In /src/tools/cptolow.c    */
  38. extern char    *cpy    (char*,char*);         /* In /src/tools/cpy.c    */
  39. extern int    mydos   (REGS *);         /* In /src/tools/dos.asm    */
  40. extern void    gregs   (REGS *);         /* In /src/tools/dos.asm    */
  41. extern char    *malloc (unsigned);         /* In standard library    */
  42. extern char    *next   (char**,int,int);    /* In /src/tools/next.c    */
  43. extern void    ssort  (char*,int,int,int(*)());/*in /src/tools/ssort.c */
  44. extern int    strcmp  (char*, char*);         /* in standard library    */
  45.  
  46. /*----------------------------------------------------------------------*/
  47.  
  48. static unsigned   Longfmt = 0;    /* True if we're using long format. This
  49.                  * has to be global for the comparison
  50.                  * routine used for sroting to work.
  51.                  */
  52.  
  53. static unsigned      Cluster_size;        /* Number of bytes per cluster on
  54.                      * requested disk.
  55.                      */
  56.  
  57. /*----------------------------------------------------------------------*/
  58. /*    Do a DOS system call using the dos() routine    */
  59.  
  60. #define DOSCALL(id,regs) { regs.h.ah = id ; mydos( ®s ); }
  61.  
  62. /*----------------------------------------------------------------------*/
  63.  
  64. static int  find_first( filespec, attributes, regp )
  65. char     *filespec ;
  66. short         attributes;
  67. register REGS    *regp;
  68. {
  69.     /*    Get directory information for the indicated file.
  70.      *    Ambiguous file references are ok but you have to use
  71.      *    find_next to get the rest of the file references.
  72.      *    In this case, The regs structure used by find_first
  73.      *    must be passed to find_next. 0 is returned on success,
  74.      *    otherwise the DOS error code is returned.
  75.      */
  76.  
  77.     regp->h.ah = (char)  FINDFIRST    ;
  78.     regp->x.dx = (short) filespec    ;
  79.     regp->x.cx = attributes        ;
  80.  
  81.     return (int)( (mydos(regp) & CARRY)  ?  regp->x.ax  :  0 );
  82. }
  83.  
  84. /*----------------------------------------------------------------------*/
  85.  
  86. static  int    find_next ( regp )
  87. REGS    *regp;
  88. {
  89.     /*    Get the next file in an ambiguous file reference. A
  90.      *    call to this function must be preceded by a 
  91.      *    find_first call. The regp argument must be the
  92.      *    same register image used by the find_first call.
  93.      *    0 is returned on success, otherwise the error code
  94.      *    generated by DOS is returned.
  95.      */
  96.  
  97.     regp->h.ah = FINDNEXT ;
  98.     return (int)(  (mydos(regp) & CARRY)  ?  regp->x.ax  :  0 );
  99. }
  100.  
  101. /*----------------------------------------------------------------------*/
  102.  
  103. int    haswild(s)
  104. register char    *s;
  105. {
  106.     /*    Return true if s has a unix wild card in it.    */
  107.  
  108.     for( ; *s ; s++)
  109.         if( *s == '*' || *s == '?' )
  110.             return 1;
  111.     return 0;
  112. }
  113.  
  114. /*----------------------------------------------------------------------*/
  115.  
  116. static   int    isrootdir( name )
  117. register char    *name;
  118. {
  119.     /*    return true if name is explicitly specifying the root
  120.      *    directory (ie. is one of:  d:/  d:\   /   \  where
  121.      *    'd' can be any disk designator.
  122.      */
  123.  
  124.     if( *name && name[1] == ':' )
  125.         name += 2;
  126.  
  127.     return( (*name == '\\' || *name == '/') && !name[1] );
  128. }
  129.  
  130. /*----------------------------------------------------------------------*/
  131.  
  132. has_only( str, inclusion_set )
  133. register char    *str;
  134. char        *inclusion_set;
  135. {
  136.     /*    Return true only if every character in str is also in
  137.      *    inclusion_set. True is returned if str is empty.
  138.      */
  139.  
  140.     register char   *p;
  141.  
  142.     for(; *str ; str++)
  143.     {
  144.         for( p = inclusion_set ; *p && *p != *str ; p++ )
  145.             ;
  146.  
  147.         if( !*p )
  148.             return 0;
  149.     }
  150.  
  151.     return 1;
  152. }
  153.  
  154. /*----------------------------------------------------------------------*/
  155.  
  156. static    char    *fixup_name( name, regs, info )
  157. register char    *name;
  158. REGS        *regs;
  159. FILE_INFO    *info;
  160. {
  161.     /*  If the name specifies an implicit file (ie. it asks for
  162.      *  the directory rather than the files in the directory),
  163.      *  modify it to ask for files (eg. ".." becomes "..\*.*").
  164.      *  If the name is actually modified, a pointer to a modified
  165.      *  copy of the original name is returned. Otherwise the
  166.      *  original buffer is returned.
  167.      */
  168.  
  169.     static   char    buf[80]  ;        /* Place to put modified name */
  170.     register char    *p = buf ;        /* Always points into buf      */
  171.     char        *start_name = name; /* Remember start of name      */
  172.  
  173.     if( isrootdir(name) || (name[0] && name[1]==':' && !name[2]) )
  174.     {
  175.         /* Handle an explicitly requested root directory or
  176.          * the current directory on another disk.
  177.          */
  178.  
  179.         sprintf(buf, "%s*.*", name );
  180.     }
  181.     else if( !find_first( name, ALL, regs) )
  182.     {
  183.         /* Look for the indicated name & see if it's a directory.
  184.          * If so, append  slash-*.* to the requested name
  185.          */
  186.  
  187.         if( !IS_SUBDIR(info) )
  188.             return name;
  189.         else
  190.             sprintf(buf, "%s/*.*", name );
  191.     }
  192.     else
  193.     {
  194.         /*    If we get here then a non-existant file or directory
  195.          *    was requested.
  196.          *    If the name consists of nothing but the characters
  197.          *    \ / . or a drive designator, assume that the root
  198.          *    directory was requested and adjust the name 
  199.          *    accordingly.
  200.          */
  201.  
  202.         if( *name && name[1] == ':') /* Copy drive designator if */
  203.         {                 /* one's present.        */
  204.             *p++ = *name++ ;
  205.             *p++ = *name++ ;
  206.         }
  207.  
  208.         if( has_only(name, ".\\/") )
  209.             strcpy( p, "/*.*" );
  210.         else
  211.             return( start_name );
  212.     }
  213.  
  214.     return( buf );
  215. }
  216.  
  217. /*----------------------------------------------------------------------*/
  218.  
  219. static int  dirtoa( target, infop, graphics, pathname )
  220. register char        *target   ;
  221. char            *pathname ;
  222. register FILE_INFO  *infop    ;
  223. unsigned        graphics;
  224. {
  225.     /*    Convert directory entry held in infop to an ascii string
  226.      *    in target. If Longfmt use a long format, if graphics then
  227.      *    directory names are printed in bold face, else they're
  228.      *    printed as "<name>." If pathname is true then the name
  229.      *    will be preceeded with the full pathname.
  230.      */
  231.  
  232.     char *startstr = target;
  233.     int  i;
  234.  
  235.     if( Longfmt )
  236.     {
  237.         *target++ = ( IS_READONLY(infop) ) ? 'r' : '.' ;
  238.         *target++ = ( IS_HIDDEN  (infop) ) ? 'h' : '.' ;
  239.         *target++ = ( IS_SYSTEM  (infop) ) ? 's' : '.' ;
  240.         *target++ = ( IS_SUBDIR  (infop) ) ? 'd' : '.' ;
  241.         *target++ = ( IS_DIRTY   (infop) ) ? 'm' : '.' ;
  242.  
  243.         sprintf(target, " %6ld %2d/%02d/%02d %2d:%02d:%02d - ",
  244.             infop->fi_fsize,
  245.             C_MONTH(infop), C_DAY(infop), C_YEAR(infop)-1900,
  246.             C_HR(infop),    C_MIN(infop), C_SEC(infop)       );
  247.  
  248.         while( *target )
  249.             target++;
  250.     }
  251.  
  252.     if( IS_SUBDIR(infop) && graphics )
  253.         target = cpy( target, BOLDFACE );
  254.  
  255.     target = cpy      ( target, pathname );
  256.     target = cptolower( target, infop->fi_name );
  257.  
  258.     if( IS_SUBDIR(infop) && graphics )
  259.         target = cpy( target, ALL_OFF );
  260.  
  261.     return( target - startstr );
  262. }
  263.  
  264. /*----------------------------------------------------------------------*/
  265.  
  266. static int    add_entry( infop, dp, path )
  267. FILE_INFO        *infop    ;
  268. register DIRECTORY    *dp    ;
  269. char            *path    ;
  270. {
  271.     /*    Add an entry to the DIRECTORY structure. Return 0 if
  272.      *    it was added, one if it wasn't.
  273.      */
  274.     
  275.     char        buf[128] ;
  276.     register int    len   ;
  277.  
  278.     /*
  279.      *    If we're not printing hidden directories but the current
  280.      *    directory is nonetheless hidden, return immediately.
  281.      *    Similarly, return if the directory is full.
  282.      */
  283.  
  284.     if( !dp->hidden && (IS_HIDDEN(infop) || *infop->fi_name == '.')  )
  285.         return 1;
  286.  
  287.     if( dp->maxdirs <= 0  )        /* No more room in dirv. return    */
  288.         return 0;        /* error status            */
  289.  
  290.     /*
  291.      *    Update the directory count or the file count as appropriate
  292.      *    return immeadialy if we're looking at a file and we aren't
  293.      *    supposed to list file. The same with directories.
  294.      */
  295.  
  296.     if( IS_SUBDIR(infop) )    
  297.     {
  298.         if( dp->dirs )
  299.             dp->ndirs++  ;
  300.         else
  301.             return 1;
  302.     }
  303.     else
  304.     {
  305.         if( dp->files )
  306.             dp->nfiles++  ;
  307.         else
  308.             return 1;
  309.     }
  310.  
  311.     /*
  312.      *    Convert the FILE_INFO structure to an ascii string and put
  313.      *    it into buf. Then malloc a chunk of memory the correct size,
  314.      *    copy the ascii string there, and put the malloced memory
  315.      *    into dirv at the correct place.
  316.      */
  317.  
  318.     Longfmt = dp->longf;
  319.  
  320.     len = dirtoa( buf, infop, dp->graphics, path );
  321.  
  322.     if( len > dp->width )
  323.         dp->width = len ;
  324.         
  325.     if( *dp->lastdir = malloc(len + 1) )
  326.     {
  327.         strcpy( *dp->lastdir++, buf )    ;
  328.  
  329.         /* Add file size to total. Note that the actual amount
  330.          * of space (# of clusters) used by the file on the
  331.          * disk is used.
  332.          */
  333.  
  334.         dp->nbytes += ROUND( infop->fi_fsize, Cluster_size );
  335.  
  336.         --dp->maxdirs;
  337.         return 1;
  338.     }
  339.  
  340.     fprintf(stderr,"Can't get memory for directory\n");
  341.     return 0;
  342. }
  343.  
  344. /*----------------------------------------------------------------------*/
  345.  
  346.  
  347. static  void    copy_path( dest, src )
  348. char    *dest, *src;
  349. {
  350.     /*    Copy only the pathname part of the file spec contained in
  351.      *    src to dest. Path names longer than 64 characters are
  352.      *    truncated so dest must be at least 64 characters long.
  353.      */
  354.  
  355.     register char    *p, *slash;
  356.  
  357.     for( p = slash = src ; *p ; p++ )
  358.         if( *p == '/' || *p == '\\' || *p == ':' )
  359.             slash = p + 1;
  360.  
  361.     for(p = src; p < slash  &&  p - src < 64 ; *dest++ = *p++ )
  362.             ;
  363.  
  364.     *dest = 0;
  365. }
  366.  
  367. /*----------------------------------------------------------------------*/
  368.  
  369. static  void    clab( dest, src )
  370. register char    *dest, *src;
  371. {
  372.     for(; *src ;  src++, dest++ )
  373.         if( *src != '.')
  374.             *dest = *src ;
  375. }
  376.  
  377. /*----------------------------------------------------------------------*/
  378.  
  379. static  int  cmp( pp1, pp2 )
  380. char    **pp1, **pp2;
  381. {
  382.     /*    Comparison routine needed for ssort(), it can deal with
  383.      *    long format output. It also sorts names including
  384.      *    numbers so that xxx10xx is put after xxx9xx
  385.      */
  386.  
  387.     register char    *p1 = *pp1;
  388.     register char    *p2 = *pp2;
  389.     int        num1, num2;
  390.  
  391.     if( Longfmt )
  392.     {
  393.         /*    Skip forward to the '-' that will preceede
  394.          *    the filename.
  395.          */
  396.  
  397.         while( *p1 && *p1 != '-' )
  398.             p1++;
  399.  
  400.         while( *p2 && *p2 != '-' )
  401.             p2++;
  402.     }
  403.  
  404.     /*    The following code deals with getting similar names
  405.      *    with numeric componants sorted correctly. If you don't
  406.      *    want this, replace the following code with a
  407.      *        return( strcmp(p1, p2) );
  408.      *
  409.      *    The while statement says to loop if the characters are
  410.      *    equal and non-null OR if both characters are digits.
  411.      *    If the characters aren't digits, num1 is false and we'll
  412.      *    just advance the pointers, otherwise we'll compare the
  413.      *    numbers.
  414.      */
  415.  
  416.     while( (num1 = (isdigit(*p1) && isdigit(*p2))) || (*p1 == *p2 && *p1) )
  417.     {
  418.         if( !num1 )
  419.         {
  420.             p1++;
  421.             p2++;
  422.         }
  423.         else
  424.         {
  425.             /*  *p1 and *p2 are both numbers. Extract the
  426.              *  numeric values from the strings and
  427.              *  compare those values, rather than the
  428.              *  ASCII values. Advance p1 and p2 past
  429.              *  the numbers.
  430.              */
  431.  
  432.             num1 = num2 = 0;
  433.  
  434.             do {
  435.                 num1 = (num1 * 10) + ( *p1++ - '0');
  436.             } while( isdigit(*p1) );
  437.  
  438.             do {
  439.                 num2 = (num2 * 10) + ( *p2++ - '0');
  440.             } while( isdigit(*p2) );
  441.  
  442.             if( num1 != num2 )
  443.                 return( num1 - num2 );
  444.         }
  445.     }
  446.  
  447.     return( *p1 - *p2 );
  448. }
  449.  
  450. /*----------------------------------------------------------------------*/
  451.  
  452. DIRECTORY  *mk_dir( size )
  453. register unsigned size;
  454. {
  455.     /*    Make a DIRECTORY with the indicated number of dirv entries.
  456.      *    Note that since one dirv entry is declared as part of the
  457.      *    DIRECTORY header, we'll actually have size+1 entries
  458.      *    available, though the last one is never used. We allocate
  459.      *    it so that we can terminate the list with a null
  460.      *    entry, even if the list is full.
  461.      */
  462.  
  463.     register DIRECTORY  *dp;
  464.  
  465.  
  466.  
  467.  
  468.     if( !( dp = (DIRECTORY *)calloc( (unsigned)1,
  469.             sizeof(DIRECTORY) + (size * sizeof(char *))) ))
  470.         return 0;
  471.  
  472.     dp->maxdirs = size ;
  473.     dp->lastdir = dp->dirv;
  474.     return dp;
  475. }
  476.  
  477. /*----------------------------------------------------------------------*/
  478.  
  479. del_dir( dp )
  480. register DIRECTORY    *dp;
  481. {
  482.     /*    Delete a directory made with a previous mk_dir call.
  483.      *    Note that all the strings pointed to by dirv entries
  484.      *    are assumed to have been gotten from malloc (this is
  485.      *    always true if dir() is used to fill the strings.
  486.      */
  487.  
  488.     register char    **v;
  489.  
  490.     for( v = dp->dirv; v < dp->lastdir ; free( *v++ ) )
  491.         ;
  492.  
  493.     free( dp );
  494. }
  495.  
  496. /*----------------------------------------------------------------------*/
  497.  
  498. dir( spec, dp )
  499. char       *spec;
  500. DIRECTORY  *dp;
  501. {
  502.     /*    Get a directory for the indicated spec. DOS wildcards are
  503.      *    permitted. If the DIRECTORY pointed to by dp already has
  504.      *    entries in it, new ones will be appended onto the existing
  505.      *    ones. If *spec is null, no files will be gotten, this is
  506.      *    useful if all you want is the volume label.
  507.      *
  508.      *    Note that the DTA is not modified by this routine. It
  509.      *    sets the DTA to its own address, but then restores the
  510.      *    DTA before returning.
  511.      */
  512.  
  513.     REGS          regs    ;    /* Needed for DOS calls        */
  514.     FILE_INFO     info      ;    /* DOS puts dirs here        */
  515.     char          path[80]    ;    /* place to put path        */
  516.     char          **firstdir;    /* Used for sorting        */
  517.     short          seg,off    ;    /* Segment and offset of    */
  518.                     /* original DTA            */
  519.     unsigned      sec_per_cluster,    /* Used to compute number of    */
  520.               bytes_per_sector, /* bytes in a cluster        */
  521.               garbage;
  522.  
  523.     gregs( ®s );            /* Get the original DTA        */
  524.     DOSCALL( GETDTA, regs );
  525.  
  526.     seg = regs.x.es ;        /* remember it in set:off    */
  527.     off = regs.x.bx ;
  528.  
  529.     regs.x.dx = (word) &info;    /* Change the Disk Transfer Addr  */
  530.     DOSCALL( SETDTA, regs );    /* to point at info structure      */
  531.  
  532.  
  533.     /*    Find the number of bytes/cluster on the indicated
  534.      *    disk drive (or on the current drive if none is
  535.      *    specified.
  536.      */
  537.  
  538.     if( !diskinfo( (!*spec||spec[1]!=':') ? 0 : (toupper(*spec)-'A')+1,
  539.             &sec_per_cluster, &bytes_per_sector, &garbage, &garbage))
  540.         fprintf(stderr,"dir: Can't access indicated disk\n");
  541.  
  542.     Cluster_size = sec_per_cluster * bytes_per_sector ;
  543.  
  544.  
  545.     /*    If a volume label is requested, get it and copy it into
  546.      *    dp->vol_lab. Any imbedded '.'s are stripped by clab.
  547.      *    If no volume label is present, the string is nulled.
  548.      */
  549.  
  550.     if( dp->label )
  551.     {
  552.         *dp->vol_label = 0;
  553.  
  554.         if( spec[1] != ':' )
  555.             strcpy( path, "/*.*" );
  556.         else
  557.         {
  558.             *path = *spec ;
  559.             strcpy( path+1, ":/*.*" );
  560.         }
  561.  
  562.         if( !find_first(path, LABEL, ®s) )
  563.             clab( dp->vol_label, info.fi_name );
  564.     }
  565.  
  566.     /*
  567.      *    Now get the directories:
  568.      */
  569.  
  570.     if( dp->exp && !haswild(spec) )    
  571.         spec = fixup_name( spec, ®s, &info );
  572.  
  573.     copy_path( path, dp->path ? spec : "" );
  574.  
  575.     firstdir = dp->lastdir;
  576.  
  577.     /*    Now go look for the file:
  578.      */
  579.  
  580.     if( !find_first(spec, ATTRIBUTES, ®s)  )
  581.         if( !add_entry(&info, dp, path) )    
  582.             goto abort;
  583.  
  584.     if( haswild(spec) )
  585.         while( !find_next( ®s ) )
  586.             if( !add_entry(&info, dp, path) )
  587.                 goto abort;
  588.  
  589.     if( dp->sort )
  590.         ssort( (char *)firstdir, dp->lastdir - firstdir,
  591.                     sizeof(char*), cmp);
  592.  
  593. abort:
  594.     regs.x.ds = seg ;        /* Restore the original disk    */
  595.     regs.x.dx = off ;        /* transfer address.        */
  596.     DOSCALL( SETDTA, regs );
  597. }
  598.