/*************************************************** * * * chkfrag - check disk for fragmentation * * * ***************************************************/ #include /* standard library */ #include /* dos access and registers */ #include /* memory allocation */ #include /* common lib modules */ #define UINT unsigned int /* unsigned integer type */ #define ULONG unsigned long /* unsigned long type */ #define NOT ! /* logical not */ #define BOOT b_rec /* boot record shorthand */ #define FILES (_A_SYSTEM | _A_HIDDEN | _A_SUBDIR) /* files type */ #define LABEL (_A_VOLID) /* label type */ #define CLEAR(s,c) strclr(s,c,sizeof(s)) /* string clear */ /* * Globals */ char huge *fat, /* address of FAT */ cdrive[66], /* startup drive and path */ fat_16; /* true if 16 bit FAT entries */ int sections = 0, /* file sections */ secsize = 0 , /* sector size of drive */ frag = 0, /* fragmented files */ unfrag = 0, /* unfragmented files */ files = 0, /* processed files */ dirs = 0, /* processed directories */ list = 0; /* list frag'd files switch */ UINT nclusters; /* number of clusters */ /* * formal declarations */ void get_fat(int), /* read FAT into memory */ cfexit(int), /* exit routine */ check_frag(char *, UINT, int), /* check if file/dir is frag'd */ dir_search(char *), /* search directory */ strclr(char *, int, int), /* clear a string */ check_unlinked(); /* check for unlinked clusters */ int chkdrv(char); /* check local, SUBST/ASSIGN */ char *fname(char *, char*), /* fcb filename to normal fname */ *readlabel(char), /* read the drive label */ *translate_name(char far *); /* translate name */ UINT next_cluster(UINT, int, int *); /* find the next cluster in FAT */ struct fcb { char hexff; /* extended fcb first byte */ char extra[5]; /* extended fcb work area */ char attrib; /* extended fcb attribute */ char drive; /* fcb - drive */ char filename[8]; /* fcb - filename */ char ext[3]; /* fcb - extension */ unsigned int block; /* fcb - block number */ unsigned long filesize; /* fcb - file size */ int date; /* fcb - file date */ char system[10]; /* fcb - reserved area */ char record; /* fcb - current record */ unsigned long rnd_recno; /* fcb - random record number */ } ; /************************************************************************ * * * mainline * * * ************************************************************************/ main(argc, argv) int argc; /* count of arguments */ char *argv[]; /* argument strings */ { int pf = 0, /* percent fragmented */ ctc = 0, /* return code chosen */ dc = 0, /* drive chosen */ pe = 0, /* parm in error */ rc, /* return code */ i, j, /* loop counter, work */ option = 0; /* program option */ char *p; /* work pointer */ static char drive[] = " :\\", /* drive and path to check */ *rc_type[] = { "Percentage", "Number of Files", "Number of Extra Segments" }, *suggestion[] = { "No fragmentation -- No action suggested", "Little fragmentation -- No action suggested", "Moderate fragmentation -- Defrag should be performed soon", "Fragmentation critical -- Defrag or Backup/Format/Restore" }, *errors[] = { "Invalid drive specified", "Cannot CHKFRAG a network drive", "Cannot CHKFRAG a SUBST'd or ASSIGN'd drive", "Must run with DOS 2.0 or greater" }, *options[] = { "/%", "/N", "/E", "/L" } ; printf("CHKFRAG 1.0 (c) Ziff Communications Co.\n%s%c%s\n", "PC Magazine ", 254, " Bob Flanders & Michael Holmes\n"); getcwd(cdrive, sizeof(cdrive)); /* get current drive/path */ *drive = *cdrive; /* ..setup default drive */ for (i = 1; i < argc; i++) /* check each argument */ { strupr(p = argv[i]); /* uppercase argument */ if (strlen(p) == 2 && p[1] == ':') /* q. drive parm specified? */ { *drive = *p; /* a. yes .. setup drive */ dc++; /* .. show drive selected */ } else { for (j = 0; strcmp(p, options[j]) /* search arguments */ && (j < 4); j++); switch(j) /* based on argument */ { case 0: /* /% option */ case 1: /* /N option */ case 2: /* /E option */ option = j; /* set up the option value */ ctc++; /* increment code type count*/ break; /* exit switch */ case 3: /* /L switch */ list++; /* .. show listing wanted */ break; case 4: /* error */ pe = j; /* argument in error */ break; /* .. error */ } } } if (pe || (ctc > 1) || (list>1) || (dc > 1))/* q. any error? */ { /* a. yes .. handle error */ printf("\n\tformat\tCHKFRAG [d:] [/%% | /N | /E] [/L]\n\n"); printf("\twhere\td: is the drive to check for fragmentation\n"); printf("\t\t/%% sets errorlevel as a percentage\n"); printf("\t\t/N sets errorlevel to number of fragmented files (max 254)\n"); printf("\t\t/E sets errorlevel number of extra sections (max 254)\n"); printf("\t\t/L causes fragmented files to be listed\n"); cfexit(255); } if (i = chkdrv(*drive)) /* check drive, version err */ { printf("Error: %s", errors[--i]); /* display any error */ cfexit(255); /* tell the batch job */ } get_fat(*drive - 'A'); /* read FAT into memory */ dir_search(drive); /* search for files */ check_unlinked(); /* check unlinked clusters */ if (files + dirs) /* q. any files and dirs? */ pf = (frag * 100) / (files + dirs); /* a. yes .. % files frag'd */ if (!pf && frag) /* q. something frag'd */ pf = 1; /* a. yes .. show non-zero */ printf("\n%d Files, %d Directories,\n", /* report to user */ files, dirs); printf("%d Unfragmented, %d Fragmented, %d Extra Sections\n", unfrag, frag, sections); printf("%d%% of files are fragmented\n\n", pf); switch(option) /* return w/errorlevel */ { case 0: /* percentage return */ rc = pf; break; case 1: /* files return */ rc = frag > 254 ? 254 : frag; break; case 2: /* extra sections return */ rc = sections > 254 ? 254 : sections; } if (pf == 0) /* q. no fragments? */ i = 0; /* a. yes .. tell 'em */ else if (pf < 11) /* q. little fragmentation? */ i = 1; /* a. yes .. set index */ else if (pf < 76) /* q. moderate fragm'tion */ i = 2; /* a. yes .. setup msg */ else i = 3; /* ..push the button, Jim */ printf("%s%s\nCHKFRAG finished, Return code %d\n\n%s%s\n", "Return type chosen: ", rc_type[option], rc, "Suggestion:\n ", suggestion[i]); cfexit(rc); /* return w/errorlevel */ } /************************************************************************ * * * get_fat -- read boot record and fat into memory * * * ************************************************************************/ void get_fat(drv) int drv; /* drive number */ { union REGS r; /* work registers */ struct SREGS s; /* ..and work segment regs */ struct bootrec { char jmp[3], /* jump instruction */ oem[8]; /* OEM name */ UINT bytes; /* bytes per sector */ char cluster; /* sectors per cluster */ UINT res_sectors; /* reserved sectors */ char fats; /* number of fats */ UINT roots, /* number of root dir entries */ sectors; /* total sectors */ char media; /* media descriptor block */ UINT fatsize, /* sectors per fat */ tracksize, /* sectors per track */ heads, /* number of heads */ hidden; /* hidden sectors */ } far *b_rec; /* boot record definition */ char *nomem = "Not enough memory for processing\n"; r.h.ah = 0x36; /* ah = get freespace */ r.h.dl = drv + 1; /* get drive */ int86(0x21, &r, &r); /* r.x.cx = bytes/sector */ if ((BOOT = (struct bootrec far *) malloc(r.x.cx)) == NULL) { /* q. no memory? */ printf(nomem); /* a. yes .. give */ cfexit(255); /* ..error msg/exit */ } r.h.al = drv; /* al = drive number */ r.x.cx = 1; /* cx = number of sectors */ r.x.dx = 0; /* dx = starting sector */ r.x.bx = FP_OFF(BOOT); /* bx = offset of buffer */ s.ds = FP_SEG(BOOT); /* ds = segment of buffer */ int86x(0x25, &r, &r, &s); /* read boot sector */ if (r.x.cflag) /* q. error reading disk? */ { printf("Error reading boot record\n"); /* a. yes .. give error msg */ cfexit(255); /* ..and return to DOS */ } if ((fat = (char huge *) halloc((long) BOOT->fatsize * (long) BOOT->bytes, 1)) == (char huge *) NULL) { /* q. no memory? */ printf(nomem); /* a. yes .. give */ cfexit(255); /* ..error msg/exit */ } fat_16 = (BOOT->sectors / BOOT->cluster) > 4087;/* set if 16bit FAT tbl */ r.h.al = drv; /* al = drive number */ r.x.cx = BOOT->fatsize; /* cx = number of sectors */ r.x.dx = BOOT->res_sectors; /* dx = starting sector */ r.x.bx = FP_OFF(fat); /* bx = offset of buffer */ s.ds = FP_SEG(fat); /* ds = segment of buffer */ int86x(0x25, &r, &r, &s); /* read boot sector */ if (r.x.cflag) /* q. error reading disk? */ { printf("%02.2x %02.2x Error reading FAT\n", r.h.ah, r.x.di); /* a. yes .. give error msg */ cfexit(255); /* ..and return to DOS */ } nclusters = (BOOT->sectors - (BOOT->res_sectors + (BOOT->fatsize * BOOT->fats) + ((BOOT->roots * 32) / BOOT->bytes))) / BOOT->cluster; printf("Drive %c:%s %u Sectors, %u Clusters, %u Clustersize\n", drv + 'A', readlabel(drv + 'A'), BOOT->sectors, nclusters, BOOT->cluster * BOOT->bytes); printf("\nChecking disk structure .."); } /************************************************************************ * * * check_frag -- check a file/directory for fragmentation * * * ************************************************************************/ void check_frag(s, n, dflag) char *s; /* file/directory name */ UINT n; /* starting cluster number */ int dflag; /* directory flag */ { UINT i, j; /* working storage */ int flag = 0, /* flag for frag'd file */ rc; /* error return code */ for(; i = next_cluster(n, 1, &rc); n = i) /* walk down the chain */ { if (i == 1) /* q. invalid cluster? */ { printf("\n\t%s -- %s%s\n%s\n", /* a. yes .. give err msg */ s, rc ? "Invalid cluster detected" : "File cross-linked", ", Run aborted", "\n\t** Please run CHKDSK **"); cfexit(255); /* ..and exit w/error code */ } if ((n + 1) != i) /* q. non-contiguous area? */ { flag++; /* show fragmented file */ if (i > n) /* q. possibly bad cluster? */ { for (j = n + 1; /* check for bad spots */ next_cluster(j, 0, &rc) == 0xfff7 && j < i; j++); if (j == i) /* q. was entire area bad? */ flag--; /* a. yes .. don't report */ else sections++; /* incr files sections count*/ } else sections++; /* incr files sections count*/ } } if (flag) /* q. fragmented file */ { if (NOT frag && list) /* q. first frag file? */ printf("\nFragmented Files/Directories:\n"); if (list) /* q. list frag'd files? */ printf("%s%s\n", /* a. yes .. give it to them*/ dflag ? "DIR> " : " ", s); frag++; /* accumulate frag'd count */ } else unfrag++; /* else total unfrag'd files*/ } /************************************************************************ * * * next_cluster -- return next cluster number from FAT * * * ************************************************************************/ UINT next_cluster(n, x, rc) UINT n; /* current cluster number */ int x, /* flag, 1 = reset FAT entry */ *rc; /* error return code */ { ULONG e; /* entry number in FAT */ UINT huge *p, /* pointer for 16 bit FAT entry */ mask1, mask2; /* mask for and'ing and or'ing */ int flag; /* shift/and flag */ *rc = 0; /* clear return code */ if (! (e = n)) /* q. invalid cluster nbr */ return(0); /* a. yes .. rtn EOF */ if (fat_16) /* q. 16 bit FAT entries? */ { p = (UINT *) &fat[0]; /* a. yes .. get FAT addr */ n = p[e]; /* retrieve next entry */ if (NOT n) /* q. unallocated cluster? */ { n = 1; /* a. yes .. error condition*/ *rc = 1; /* set return code */ } if (x) /* q. need to reset entry? */ p[e] = 1; /* a. yes .. show processed */ if (n >= 0xfff0 && n != 0xfff7) /* q. reserved and not bad */ n = 0; /* a. yes .. show EOF */ } else { e = (n << 1) + n; /* cluster number * 3 */ flag = e & 1; /* need to do shift later? */ e >>= 1; /* cluster number * 1.5 */ n = *(UINT *) &fat[e]; /* get next cluster */ if (flag) /* q. need to do shift? */ { n >>= 4; /* a. yes .. shift by 4 bits*/ mask1 = 0x000f; /* mask to clear upper bits */ mask2 = 0x0010; /* ..and footprint mask */ } else { n &= 0xfff; /* else .. strip upper bits */ mask1 = 0xf000; /* mask to clear lower bits */ mask2 = 0x0001; /* ..and footprint mask */ } if (NOT n) /* q. unallocated cluster? */ { n = 1; /* a. yes .. error condition*/ *rc = 1; /* set return code */ } if (x) /* q. need to reset entry? */ { *(UINT *) &fat[e] &= mask1; /* a. yes .. 'and' off bits */ *(UINT *) &fat[e] |= mask2; /* ..and put down footprint */ } if (n >= 0xff0) /* q. EOF/reserved range? */ if (n == 0xff7) /* q. bad cluster? */ n = 0xfff7; /* a. yes .. show bad one */ else n = 0; /* else .. show EOF */ } return(n); } /************************************************************************ * * * check_unlinked -- check for unlinked clusters * * * ************************************************************************/ void check_unlinked() { int rc; /* error return code */ UINT i, /* loop counter */ j; /* work return cluster nbr */ for (i = 2; i < nclusters; i++) /* check thru entire FAT */ { if ((j = next_cluster(i, 0, &rc)) != 0 /* q. unallocated cluster? */ && j != 1 && j != 0xfff7) /* ..or used/bad cluster? */ { printf("\nLost clusters detected, %s%s",/* a. no .. give msg */ "Run aborted\n", "\t** Please run CHKDSK **\n"); cfexit(255); /* ..and exit w/error */ } } } /************************************************************************ * * * dir_search -- recursively search all files & subdirectories * * * ************************************************************************/ void dir_search(base_dir) char *base_dir; /* base subdirectory to search */ { int oldds, /* old dta segment */ oldda; /* old dta address */ char pass, /* pass number */ work_dir[65], /* work directory */ first_done; /* find first done */ struct fcb find_work; /* fcb work area */ /* * The following areas are STATIC .. not allocated on recursion */ static struct SREGS s; /* segment registers */ static union REGS r; /* other registers */ static char far *cftmp; /* work pointer */ static int rc; /* work return code */ static union { char dtabuff[128]; /* dta area */ struct /* Disk transfer area layout */ { char dta1[6]; /* first part of dta */ char attrib; /* attribute byte */ char drive; /* drive */ char filename[8]; /* filename */ char ext[3]; /* extension */ char d_attrib; /* directory attribute */ char dta2[10]; /* more reserved space */ unsigned int d_time; /* directory time */ unsigned int d_date; /* directory date */ unsigned int d_cluster; /* first cluster */ unsigned long d_filesize; /* size of file */ } dta; } dta; /* * End of static area */ r.h.ah = 0x2f; /* ah = get dta */ int86x(0x21, &r, &r, &s); /* .. ask DOS */ oldds = s.es; /* save old DTA segment */ oldda = r.x.bx; /* .. and offset */ cftmp = (char far *) &dta; /* get current fcb address */ r.h.ah = 0x1a; /* ah = set DTA */ s.ds = FP_SEG(cftmp); /* ds -> DTA segment */ r.x.dx = FP_OFF(cftmp); /* ds:dx -> DTA */ int86x(0x21, &r, &r, &s); /* setup new DTA */ if (strcmp(base_dir, translate_name(base_dir))) /* q. JOIN'd? */ return; /* a. yes .. skip it */ chdir(base_dir); /* get the base directory */ for(first_done=pass=0;;) /* look through current dir */ { if (first_done == 0) /* q. find first done? */ { /* a. no .. do it */ if (base_dir[1] == ':') /* q. disk specified? */ find_work.drive = /* a. yes .. set fcb drive */ ((base_dir[0] & 0xdf) - 'A') + 1; else find_work.drive = 0; /* else use default */ find_work.hexff = 0xff; /* extended fcb */ CLEAR(find_work.extra, 0); /* set extra area */ CLEAR(find_work.filename, '?'); /* .. and file name */ CLEAR(find_work.ext, '?'); /* .. and extension */ find_work.attrib = FILES; /* set up attribute to find */ r.h.ah = 0x11; /* ah = find first */ cftmp = (char far *) &find_work; /* get pointer to work fcb */ s.ds = FP_SEG(cftmp); /* ds -> segment of fcb */ r.x.dx = FP_OFF(cftmp); /* ds:dx -> offset */ int86x(0x21, &r, &r, &s); /* .. find first */ rc = r.h.al; /* get return code */ first_done = 1; /* first find done */ } else { r.h.ah = 0x12; /* ah = find next */ cftmp = (char far *) &find_work; /* get pointer to work fcb */ s.ds = FP_SEG(cftmp); /* ds -> segment of fcb */ r.x.dx = FP_OFF(cftmp); /* ds:dx -> offset */ int86x(0x21, &r, &r, &s); /* .. find first */ rc = r.h.al; /* get return code */ } strcpy(work_dir, base_dir); /* get current base */ if (work_dir[strlen(work_dir)-1] != '\\') /* if needed .. */ strcat(work_dir, "\\"); /* .. add a backslash */ strcat(work_dir, /* .. add the name */ fname(dta.dta.filename, dta.dta.ext)); if (pass) /* q. second pass? */ { if (rc) /* q. more files found? */ break; /* a. no .. exit */ if (!(dta.dta.d_attrib & _A_SUBDIR) /* q. directory? */ || (dta.dta.filename[0] == '.')) /* .. or a dot dir? */ continue; /* a. get next entry */ dirs++; /* accumulate dir count */ dir_search(work_dir); /* recursively call ourself */ } else /* first pass processing */ { if (rc) /* q. anything found? */ { /* a. no .. */ first_done = 0; /* re-execute find-first */ pass++; /* go to next pass */ continue; /* .. continue processing */ } if (dta.dta.filename[0] == '.') /* q. dot directory? */ continue; /* a. yes .. skip it */ if (!(dta.dta.d_attrib & _A_SUBDIR)) /* q. a file? */ files++; /* a. yes .. count them */ check_frag(work_dir, /* check for frag'd file */ dta.dta.d_cluster, dta.dta.d_attrib & _A_SUBDIR); } } r.h.ah = 0x1a; /* ah = set DTA */ s.ds = oldds; /* ds -> DTA segment */ r.x.dx = oldda; /* ds:dx -> DTA */ int86x(0x21, &r, &r, &s); /* setup new DTA */ } /************************************************************************ * * * fname -- build a normalized filename from an FCB * * * ************************************************************************/ char *fname(filename, ext) char *filename; /* filename with trailing blanks*/ char *ext; /* extension */ { int i; /* loop control */ char *p; /* work pointer */ static char fwork[13]; /* returned work area */ p = fwork; /* initialize string pointer*/ for (i = 0; (i < 8) && (*filename != ' '); i++) /* move fname w/o blanks*/ *p++ = *filename++; if (*ext != ' ') /* q. extension blank? */ { *p++ = '.'; /* a. no .. add the dot */ for (i = 0; (i < 3) && (*ext != ' '); i++) /* add ext w/o blanks */ *p++ = *ext++; } *p = 0; /* terminate string w/null */ return(fwork); /* return string to caller */ } /************************************************************************ * * * strclr -- clear an area to a value * * * ************************************************************************/ void strclr(s, c, n) char *s; /* area to initialize */ int c, /* value to use */ n; /* length of area to clear */ { while(n--) /* initialize whole area */ *s++ = c; /* ..to value specified */ } /************************************************************************ * * * translate_name -- translate a DOS name * * * ************************************************************************/ char *translate_name(name) char far *name; /* name to translate */ { static char translate_area[65], /* work/return area */ far *sp; /* work pointer */ union REGS r; /* work registers */ struct SREGS s; /* ..and work segment regs */ r.h.ah = 0x60; /* ah = translate */ sp = (char far *) name; /* set up a pointer .. */ r.x.si = FP_OFF(sp); /* set pointer to input name */ s.ds = FP_SEG(sp); /* .. and segment */ sp = (char far *) translate_area; /* set up a pointer .. */ r.x.di = FP_OFF(sp); /* set pointer to output area */ s.es = FP_SEG(sp); /* .. and segment */ int86x(0x21, &r, &r, &s); /* translate the name */ if (r.x.cflag) /* if bad name .. */ return((char far *) NULL); /* .. return error */ else return(translate_area); /* return xlated name */ } /************************************************************************ * * * chkdrv -- assure drive is LOCAL and not SUBST'd or ASSIGN'd * * * ************************************************************************/ int chkdrv(c) char c; /* drive to check */ { union REGS r; /* work registers */ struct SREGS s; /* ..and work segment regs */ static char wdrv[] = " :\\"; /* work area for drive name */ if (_osmajor < 2) /* q. pre-DOS 2.00? */ return(4); /* a. yes .. can't run it */ if (_osmajor >= 3 && _osminor >= 1) /* q. DOS 3.1 or higher? */ { r.x.ax = 0x4409; /* ah = ioctl, local test */ r.h.bl = (c - 'A') + 1; /* bl = drive to test */ int86(0x21, &r, &r); /* test drive */ if (r.x.cflag) /* q. bad drive? */ return(1); /* a. yes .. error */ if (r.x.dx & 0x1000) /* q. remote? */ return(2); /* a. yes .. error */ wdrv[0] = c; /* set up name */ if (strcmp(wdrv, translate_name(wdrv))) /* q. SUBST or ASSIGNED? */ return(3); /* a. yes .. return error */ } return(0); /* return ok */ } /************************************************************************ * * * Read drive label, if available * * * ************************************************************************/ char *readlabel(c) char c; /* drive to check */ { char *p, *q; /* work pointers */ struct find_t f; /* structure for directory entry*/ static char work_dir[13] = { " :\\*.*" } ; /* directory to check */ work_dir[0] = c; /* setup for find first */ if (_dos_findfirst(work_dir, LABEL, &f)) /* q. error on label get? */ work_dir[0] = 0; /* a. yes .. then no label */ else { for(p = work_dir, q = f.name; *q; q++) /* copy label w/o middle . */ if (*q != '.') /* q. is this char a dot? */ *p++ = *q; /* a. no .. copy it */ *p = 0; /* terminate string */ } return(work_dir); /* ..and return label string*/ } /************************************************************************ * * * cfexit() - return to DOS after resetting dir, dir * * * ************************************************************************/ void cfexit(rc) int rc; /* return code to exit with */ { int i; /* work variable */ _dos_setdrive((*cdrive - 'A')+1, &i); /* reset the default drive */ chdir(cdrive); /* .. and directory */ exit(rc); /* .. and return to DOS */ }