lim@mullian.oz.AU (Lim Ngee Ching) (12/09/87)
[Moderator's note: This software arrived as two postings, the second arriving before I had posted the first. The second shar contained new versions of the files du.c and msd_dir.c, and a new file showbug.c. I have repackaged these along with the other files from the first shar into a single shar. Here is the author's note from the first posting:] I have waited around long enough for a proper MS DOS DU, so I wrote my own. This "DU" compiles in MSC 4.0 under MS DOS. As such it has very strong MS DOS flavour. It is basically a superset of the Unix DU - a lot more options available - to cater for the quirks of MS DOS. Essentially it produces the same reading as CHKDSK in MS DOS except in cases where you have directories which used to hold a lot of files which has been erased - the erased directory entries will not be accounted for, and can't be without resorting to very low level FAT fiddling which is not worth the while. At the moment, it doesn't allow specification of files - only the whole directory - may be someone would like to change this. It also displays sizes in bytes and not kilobytes - a feature to suit MS DOS's various cluster sizes. Msd_dir.* are borrowed from the TAR package put together by Michael Rendell ({uunet,utai}michael@garfield) with some fairly kludgy modifi- cations and extensions. See read.me for some extra info (not much :-)). For now unshar this message and type 'make make.msc' to compile DU. ----------------------------------------------------------------------- This program is placed in the public domain without any warranty. As far as I know it is not a virius ;-). From: Peter Lim, lim@mullian.oz [And the author's note from the second posting: -- John.] Oops ! I didn't test the program thoroughly enough. Just found one bug. Fixed here. It allows du .. if .. specifies the root directory. I also stumble on a rather serious bug in MSC 4.0 with stat(). Read msd_dir.c for detail, a program SHOWBUG.C is included to demonstrate this bug. Hope I don't have to fix any more bug. :-) Peter Lim # This is a shell archive. Remove anything before this line, # then unpack it by saving it in a file and typing "sh file". # # Wrapped by john on Thu Dec 10 02:05:11 EST 1987 # Contents: read.me du.c du.hlp make.msc msd_dir.c msd_dir.h showbug.c echo x - read.me sed 's/^@//' > "read.me" <<'@//E*O*F read.me//' This program (DU) should be compiled at least using COMPACT memory model. The recursive nature would readily overflow a SMALL memory model program's data space. Beside that, the stack should be much bigger than the default value of 2048 (say 10k or 20k would be fine). However, one might consider using the xVARSTCK.OBJ supplied by Microsoft (see page 197, chapter 9 -- Advanced Topics, of Microsoft C compiler User's Guide). Then one should use the /Gs or /Ox option during compilation. One probable compilation command would be : CL /Gs /Ox /AC du.c msd_dir.c cvarstck.obj -o du -link /STACK:20480 Peter lim 07-Dec-87 @//E*O*F read.me// chmod u=rw,g=r,o=r read.me echo x - du.c sed 's/^@//' > "du.c" <<'@//E*O*F du.c//' /* * DU - Disk Usage. A Unix style utility. Written by Peter Lim 07-Dec-87. * * Usage: du [-s] [-a] [-z] [-c] [-r] [-h] [-nnnn] [pathname(s) ... ] * where, * -s : Summary listing only (Toggle, default=off). * -a : Generate a list of all files * (Toggle, default=off). * -z : Show total statistics (Toggle, default=on). * -c : Show cluster size (Toggle, default=on). * -r : Recursive traversal of sub-directories * (Toggle, default=on). * -h : Include HIDDEN & SYSTEM files * (Toggle, default=off). * -nnnn : Force cluster size to be nnnn bytes. * nnnn = 0 releases previous forcing. * Default pathname is the current directory on current drive. */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include "msd_dir.h" #include <dos.h> #include <strings.h> #include <ctype.h> unsigned long traverse_dir(); unsigned long get_cluster_size(); unsigned long get_path_size (); #define print_size(size, path) printf ("%-11lu%s\n", size, path) unsigned long bpc; /* number of bytes per cluster */ int filecnt=0, dircnt=0; int summary=0, show_all=0, show_stat=1, show_cluster=1, force_cluster=0, recurse=1, incl_hidn=0; unsigned int att_mask; main (argc, argv) int argc; char **argv; { unsigned long total=0; int path_specified=0; for (; --argc > 0; ) { *++argv; if (**argv == '-') switch ((*argv)[1]) { case 's' : case 'S' : summary = !summary; continue; case 'z' : case 'Z' : show_stat = !show_stat; continue; case 'a' : case 'A' : show_all = !show_all; continue; case 'c' : case 'C' : show_cluster = !show_cluster; continue; case 'r' : case 'R' : recurse = !recurse; continue; case 'h' : case 'H' : incl_hidn = !incl_hidn; continue; default : if (!sscanf (*argv, "-%lu", &bpc)) printf ("Unknown option %s\n", *argv); else force_cluster = bpc ? 1 : 0; continue; } path_specified = 1; /* At this point we know at least one path is specified. */ total += get_path_size(*argv); } if (!path_specified) total = get_path_size("."); /* If no pathname were specified. */ if (show_stat) { printf ("Total %d files in %d directories.\n", filecnt, dircnt); printf ("Total disk space used = %lu bytes (%.2lfk).\n", total, total / 1024.0); } } unsigned long get_path_size (pathname) char *pathname; { unsigned char drive_id; unsigned long total; if (incl_hidn) att_mask = (A_HIDDEN | A_SYSTEM); else att_mask = 0; /* Set attribute mask for files to find. A_DIR will always be set. */ if (!force_cluster) { if (isalpha (*pathname) && (pathname[1] == ':')) drive_id = *pathname - ((islower(*pathname)) ? 'a' : 'A') + 1; else drive_id = 0; if (!(bpc = get_cluster_size(drive_id))) { printf ("Invalid drive %c\:\n", *pathname); exit (1); } } if (show_cluster) printf ("Cluster size = %lu bytes.\n", bpc); total = traverse_dir(pathname); if (summary) print_size (total, pathname); /* At least say something even if only summary is required. */ return (total); } unsigned long traverse_dir(cur_path) char *cur_path; { DIR *dp; struct direct *direntry; char s[MAXPATHLEN+1]; char c; unsigned long total, file_size; unsigned int dir_ent_cnt; /* Count the number of directory entry. */ #define bpdent (unsigned int) 32 /* Number of bytes per directory entry, = 32 from DOS 2.10 tech ref pp. 4-5. lim@mullian.oz */ int not_root_dir; total = 0; if (!(dp=opendir(cur_path, att_mask))) { printf ("Can't open directory \"%s\" or memory allocation failure.\n", cur_path); exit(2); } if (recurse) { while (direntry=readdir(dp)) if (((*direntry).d_attribute == A_DIR) && (strcmp ((*direntry).d_name, ".")) && (strcmp ((*direntry).d_name, ".."))) { strcpy (s, cur_path); if ((c = s[strlen(s)-1]) != '\\' && c != '/' && c != ':') strcat (s, "\\"); strcat (s, (*direntry).d_name); total += traverse_dir(s); } (void) rewinddir(dp); } dir_ent_cnt = not_root_dir = 0; while (direntry=readdir(dp)) { dir_ent_cnt++; if ((*direntry).d_attribute != A_DIR) { total += file_size = ( ((*direntry).d_size / bpc) + (((*direntry).d_size % bpc) ? 1 : 0) ) * bpc; if (show_all) { strcpy (s, cur_path); if ((c = s[strlen(s)-1]) != '\\' && c != '/') strcat (s, "\\"); print_size (file_size, strcat (s, (*direntry).d_name)); } filecnt++; /* Counting all files (exclude dir). */ } else if (!strcmp ((*direntry).d_name, ".")) { dircnt++; /* Counting every occurance of ".". */ not_root_dir = 1; /* Not root directory if "." exist. */ } } if (not_root_dir) total += ( ((dir_ent_cnt * bpdent) / bpc) + (((dir_ent_cnt * bpdent) % bpc) ? 1 : 0) ) * bpc; /* Add the number of directory entry counted * bytes per entry rounded up to the nearest cluster. The only things missed by this method of counting are the directories with a lot of erased files. Can't be helped without resorting to very low level FAT probing. NOTE: The root directory uses zero byte here - complying with CHKDSK from MS DOS. Another MS DOS quirk. */ if (!summary) print_size (total, cur_path); closedir(dp); return (total); } #define DOSI_GDFREE 0x36; static union REGS reg, nreg; unsigned long get_cluster_size(drive_id) unsigned char drive_id; { reg.h.ah = DOSI_GDFREE; reg.h.dl = drive_id; intdos(®, &nreg); if (nreg.x.ax == 0xffff) return ((unsigned long) 0); else return ((unsigned long) nreg.x.cx * nreg.x.ax); } @//E*O*F du.c// chmod u=rw,g=r,o=r du.c echo x - du.hlp sed 's/^@//' > "du.hlp" <<'@//E*O*F du.hlp//' DU - Disk Usage. A Unix style utility. Written by Peter Lim 07-Dec-87. Usage: du [-s] [-a] [-z] [-c] [-r] [-h] [-nnnn] [pathname(s) ... ] where, -s : Summary listing only (Toggle, default=off). -a : Generate a list of all files (Toggle, default=off). -z : Show total statistics (Toggle, default=on). -c : Show cluster size (Toggle, default=on). -r : Recursive traversal of sub-directories (Toggle, default=on). -h : Include HIDDEN & SYSTEM files (Toggle, default=off). -nnnn : Force cluster size to be nnnn bytes. nnnn = 0 releases previous forcing. Default pathname is the current directory on current drive. @//E*O*F du.hlp// chmod u=rw,g=r,o=r du.hlp echo x - make.msc sed 's/^@//' > "make.msc" <<'@//E*O*F make.msc//' CFLAGS = /AC LFLAGS = /STACK:20480 @.c.obj: msc $(CFLAGS) $*; du.obj: du.c msd_dir.h msd_dir.obj: msd_dir.c msd_dir.h du.exe: du.obj msd_dir.obj link $(LFLAGS) du+msd_dir; @//E*O*F make.msc// chmod u=rw,g=r,o=r make.msc echo x - msd_dir.c sed 's/^@//' > "msd_dir.c" <<'@//E*O*F msd_dir.c//' /* * @(#)msd_dir.c 1.4 87/11/06 Public Domain. * * A public domain implementation of BSD directory routines for * MS-DOS. Written by Michael Rendell ({uunet,utai}michael@garfield), * August 1897 * * Extended by Peter Lim (lim@mullian.oz) to overcome some MS DOS quirks * and returns 2 more pieces of information - file size & attribute. * Plus a little reshuffling of #define's positions December 1987 */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include "msd_dir.h" #include <malloc.h> #include <string.h> #include <ctype.h> #include <dos.h> #include <direct.h> #ifndef NULL # define NULL 0 #endif /* NULL */ /* dos call values */ #define DOSI_FINDF 0x4e #define DOSI_FINDN 0x4f #define DOSI_SDTA 0x1a #define DOSI_GCDIR 0x47 #define Newisnull(a, t) ((a = (t *) malloc(sizeof(t))) == (t *) NULL) /* what find first/next calls look use */ typedef struct { char d_buf[21]; char d_attribute; unsigned short d_time; unsigned short d_date; unsigned long d_size; char d_name[13]; } Dta_buf; static char *getdirent(); static void setdta(); static void free_dircontents(); static char *extgetcwd(); static Dta_buf dtabuf; static Dta_buf *dtapnt = &dtabuf; static union REGS reg, nreg; #if defined(M_I86LM) static struct SREGS sreg; #endif DIR * opendir(pathname, att_mask) char *pathname; unsigned int att_mask; { struct stat statb; DIR *dirp; char c; char *s; struct _dircontents *dp; char nbuf[MAXPATHLEN + 1]; char name[MAXPATHLEN + 1]; unsigned char drive_id; char *phead; strcpy (name, pathname); /* Work on temporary buffer only. Never write it back, the calling argument may not have room for it ! */ if (stat(name, &statb) < 0 || (statb.st_mode & S_IFMT) != S_IFDIR) { /* Give it a second try, after modifying input pathname, before giving up to counter some MS DOS quirks. This kludge is fairly simple and will not handle weird though valid path name correctly such as multiple ../.. and other mix which eventually end up on the root directory. */ if (isalpha (*name) && (name[1] == ':')) { drive_id = *name - ((islower(*name)) ? 'a' : 'A') + 1; phead = pathname+2; } else { drive_id = 0; phead = pathname; } if ((c = name[strlen(name) - 1]) == '\\' || c == '/') name[strlen(name) - 1] = NULL; /* Try removing one trailing / or \ */ if (*phead == '.' || *phead == '\0') { /* If . or nothing specified, assume current directory and go get the directory. */ if (extgetcwd (drive_id, name, MAXPATHLEN) == NULL) return (DIR *) NULL; strcpy (nbuf, name); /* There is an undocumented BUG in MSC 4.0 such that stat (root, ..) will cause the current directory on the specified drive to be changed to root if the current directory in question is exactly one level deep ! So, keep current directory for chdir() back after doing stat (root, ..). lim@mullian.oz */ if (*(phead+1) == '.') { /* i.e. ".." specified. Then backup one level. Firstly check that we are not already at the root. */ if (name[strlen(name) - 1] == '\\') return (DIR *) NULL; while (name[strlen(name) - 1] != '\\') name[strlen(name) - 1] = NULL; if (*(phead+2) == '\\' || *(phead+2) == '/') /* Make sure we don't have a '\' double up. */ strcat (name, phead+3); } else if (*(phead) == '.') /* Just plain "." specified. */ strcat (name, phead+1); } else *nbuf = NULL; /* Don't chdir() wrongly. */ if (stat(name, &statb) < 0 || (statb.st_mode & S_IFMT) != S_IFDIR) return (DIR *) NULL; if (*nbuf) (void) chdir (nbuf); /* Fixing the stat() BUG ! */ } if (Newisnull(dirp, DIR)) return (DIR *) NULL; if (*name && (c = name[strlen(name) - 1]) != '\\' && c != '/') (void) strcat(strcpy(nbuf, name), "\\*.*"); else (void) strcat(strcpy(nbuf, name), "*.*"); dirp->dd_loc = 0; setdta(); dirp->dd_contents = dirp->dd_cp = (struct _dircontents *) NULL; if ((s = getdirent(nbuf, att_mask | A_DIR)) == (char *) NULL) return dirp; do { if (Newisnull(dp, struct _dircontents) || (dp->_d_entry = (char *) malloc((unsigned) (strlen(s) + 1))) == (char *) NULL) { if (dp) free((char *) dp); free_dircontents(dirp->dd_contents); return (DIR *) NULL; } if (dirp->dd_contents) dirp->dd_cp = dirp->dd_cp->_d_next = dp; else dirp->dd_contents = dirp->dd_cp = dp; (void) strcpy(dp->_d_entry, s); dp->d_attribute = dtabuf.d_attribute; dp->d_size = dtabuf.d_size; /* A SUPER Kludge ! Using 'dtabuf' as global variable. lim@mullian.oz */ dp->_d_next = (struct _dircontents *) NULL; } while ((s = getdirent((char *) NULL, att_mask | A_DIR)) != (char *) NULL); dirp->dd_cp = dirp->dd_contents; return dirp; } void closedir(dirp) DIR *dirp; { free_dircontents(dirp->dd_contents); free((char *) dirp); } struct direct * readdir(dirp) DIR *dirp; { static struct direct dp; if (dirp->dd_cp == (struct _dircontents *) NULL) return (struct direct *) NULL; dp.d_namlen = dp.d_reclen = strlen(strcpy(dp.d_name, dirp->dd_cp->_d_entry)); dp.d_ino = 0; dp.d_attribute = dirp->dd_cp->d_attribute; dp.d_size = dirp->dd_cp->d_size; dirp->dd_cp = dirp->dd_cp->_d_next; dirp->dd_loc++; return &dp; } void seekdir(dirp, off) DIR *dirp; long off; { long i = off; struct _dircontents *dp; if (off < 0) return; for (dp = dirp->dd_contents ; --i >= 0 && dp ; dp = dp->_d_next) ; dirp->dd_loc = off - (i + 1); dirp->dd_cp = dp; } long telldir(dirp) DIR *dirp; { return dirp->dd_loc; } static void free_dircontents(dp) struct _dircontents *dp; { struct _dircontents *odp; while (dp) { if (dp->_d_entry) free(dp->_d_entry); dp = (odp = dp)->_d_next; free((char *) odp); } } static char * getdirent(dir, att_mask) char *dir; unsigned int att_mask; { if (dir != (char *) NULL) { /* get first entry */ reg.h.ah = DOSI_FINDF; reg.h.cl = att_mask; #if defined(M_I86LM) reg.x.dx = FP_OFF(dir); sreg.ds = FP_SEG(dir); #else reg.x.dx = (unsigned) dir; #endif } else { /* get next entry */ reg.h.ah = DOSI_FINDN; #if defined(M_I86LM) reg.x.dx = FP_OFF(dtapnt); sreg.ds = FP_SEG(dtapnt); #else reg.x.dx = (unsigned) dtapnt; #endif } #if defined(M_I86LM) intdosx(®, &nreg, &sreg); #else intdos(®, &nreg); #endif if (nreg.x.cflag) return (char *) NULL; return dtabuf.d_name; } static void setdta() { reg.h.ah = DOSI_SDTA; #if defined(M_I86LM) reg.x.dx = FP_OFF(dtapnt); sreg.ds = FP_SEG(dtapnt); intdosx(®, &nreg, &sreg); #else reg.x.dx = (int) dtapnt; intdos(®, &nreg); #endif } static char *extgetcwd(drive_id, buffer, buffer_size) /* Extended get current directory on specified drive. Peter Lim 07-Dec-87. */ unsigned char drive_id; char *buffer; int buffer_size; { char tmpbuffer[MAXPATHLEN+1]; if (!drive_id) return (getcwd (buffer, buffer_size)); /* If it is current drive, use the standard getcwd() */ reg.h.ah = DOSI_GCDIR; reg.h.dl = drive_id; #if defined(M_I86LM) reg.x.si = FP_OFF(tmpbuffer); sreg.ds = FP_SEG(tmpbuffer); intdosx(®, &nreg, &sreg); #else reg.x.si = (int) tmpbuffer; intdos(®, &nreg); #endif if (nreg.x.ax == 0xf) return ((char *) NULL); /* Invalid drive specification. */ else { if (drive_id) sprintf (buffer, "%c:\\%s", drive_id+'A'-1, tmpbuffer); else sprintf (buffer, "\\%s", tmpbuffer); return (buffer); } } @//E*O*F msd_dir.c// chmod u=rw,g=r,o=r msd_dir.c echo x - msd_dir.h sed 's/^@//' > "msd_dir.h" <<'@//E*O*F msd_dir.h//' /* * @(#)msd_dir.h 1.4 87/11/06 Public Domain. * * A public domain implementation of BSD directory routines for * MS-DOS. Written by Michael Rendell ({uunet,utai}michael@garfield), * August 1897 * * Extended by Peter Lim (lim@mullian.oz) to overcome some MS DOS quirks * and returns 2 more pieces of information - file size & attribute. * Plus a little reshuffling of some #define's positions December 1987 */ #define rewinddir(dirp) seekdir(dirp, 0L) #define MAXNAMLEN 12 #ifndef MAXPATHLEN # define MAXPATHLEN 255 #endif /* MAXPATHLEN */ /* attribute stuff */ #define A_RONLY 0x01 #define A_HIDDEN 0x02 #define A_SYSTEM 0x04 #define A_LABEL 0x08 #define A_DIR 0x10 #define A_ARCHIVE 0x20 struct direct { ino_t d_ino; /* a bit of a farce */ int d_reclen; /* more farce */ int d_namlen; /* length of d_name */ char d_name[MAXNAMLEN + 1]; /* garentee null termination */ char d_attribute; unsigned long d_size; }; struct _dircontents { char *_d_entry; char d_attribute; unsigned long d_size; struct _dircontents *_d_next; }; typedef struct _dirdesc { int dd_id; /* uniquely identify each open directory */ long dd_loc; /* where we are in directory entry is this */ struct _dircontents *dd_contents; /* pointer to contents of dir */ struct _dircontents *dd_cp; /* pointer to current position */ } DIR; extern DIR *opendir(); extern struct direct *readdir(); extern void seekdir(); extern long telldir(); extern void closedir(); @//E*O*F msd_dir.h// chmod u=rw,g=r,o=r msd_dir.h echo x - showbug.c sed 's/^@//' > "showbug.c" <<'@//E*O*F showbug.c//' /* * To show the bug associated with stat(), it works okay if the drive * letter entered is for the current drive. */ #include <stdio.h> #include <dos.h> #include <sys/types.h> #include <sys/stat.h> #include <strings.h> #include <ctype.h> struct stat statb; static char *extgetcwd(); #define DOSI_GCDIR 0x47 static union REGS reg, nreg; #if defined(M_I86LM) static struct SREGS sreg; #endif #define MAXPATHLEN 128 main() { char *s = ":\\tmp"; char ss[MAXPATHLEN+1], dd[MAXPATHLEN+1]; char c; printf ("Enter drive letter with directory \\tmp : "); c = getchar(); if (isupper(c)) c += 'a' - 'A'; ss[0] = c; ss[1] = NULL; strcat (ss, s); chdir(ss); extgetcwd (c+1, ss, MAXPATHLEN); printf ("Current directory before stat() = %s\n", ss); dd[0] = c; dd[1] = NULL; strcat (dd, ":\\"); stat(dd, &statb); extgetcwd (c+1, dd, MAXPATHLEN); printf ("Current directory after stat() = %s\n", dd); } static char *extgetcwd(drive_id, buffer, buffer_size) /* Extended get current directory on specified drive. Peter Lim 07-Dec-87. */ unsigned char drive_id; char *buffer; int buffer_size; { char tmpbuffer[MAXPATHLEN+1]; if (!drive_id) return (getcwd (buffer, buffer_size)); /* If it is current drive, use the standard getcwd() */ reg.h.ah = DOSI_GCDIR; reg.h.dl = drive_id; #if defined(M_I86LM) reg.x.si = FP_OFF(tmpbuffer); sreg.ds = FP_SEG(tmpbuffer); intdosx(®, &nreg, &sreg); #else reg.x.si = (int) tmpbuffer; intdos(®, &nreg); #endif if (nreg.x.ax == 0xf) return ((char *) NULL); /* Invalid drive specification. */ else { if (drive_id) sprintf (buffer, "%c:\\%s", drive_id+'A'-1, tmpbuffer); else sprintf (buffer, "\\%s", tmpbuffer); return (buffer); } } @//E*O*F showbug.c// chmod u=rw,g=r,o=r showbug.c exit 0