/*--------------------------------------* | File: DT.c - Rev. 1.18 920229 | +--------------------------------------+ | DT: disk test, a la Norton Utilities | +--------------------------------------+------------------* | Author: Maurizio Loreti, aka MLO or I3NOO. | | Address: University of Padova - Department of Physics | | Via F. Marzolo, 8 - 35131 PADOVA - Italy | | Phone: (39)(49) 844-313 FAX: (39)(49) 844-245 | | E-Mail: LORETI at IPDINFN (BITNET); or VAXFPD::LORETI | | (DECnet) - VAXFPD is node 38.257 i.e. 39169; or | | LORETI@VAXFPD.PD.INFN.IT (INTERNET). | | Home: Via G. Donizetti 6 - 35010 CADONEGHE (PD) - Italy | *---------------------------------------------------------*/ /** | #include's **/ #include #include #include #include #include #include #include #include #include #include #include #include #include "mlo.h" #include "proto.h" /** | #define's. | | FILENAME_MAX was supposed to be in for ANSI | C compilers, but is not there in SAS-C 5.10. | NUMHEADS is the number of heads in the drive: it could be obtained | with a TD_GETGEOMETRY call, but this is v2.0 specific; for AmigaDOS | v1.3 there is no command to obtain this parameter but (RKM devices, | page 313, under TD_GETNUMTRACKS) "... the standard 3.5" Amiga drive | has two heads". So this program will not work for non-standard drives. | TD_CYL is the number of bytes per cylinder. **/ #define FILENAME_MAX 108 #define NUMHEADS 2 #define TD_CYL (TD_SECTOR * NUMSECS) #define ON 1 #define OFF 0 #define BRK_DETECTED 0x1 #define WARN_PRINTED 0x10 #define INTERNAL_ERR (BRK_DETECTED | WARN_PRINTED) /** | A structure definition to store directory entries, when recursively | checking all files. **/ typedef struct sdirEntry { struct sdirEntry *next; char name[1]; } dirEntry; /** | Global variables **/ struct IntuitionBase *IntuitionBase = NULL; /* Pointer to Int. library */ /** | 'Local' global variables **/ static struct MsgPort *diskPort = NULL; /* Various Intuition pointers */ static struct IOExtTD *diskReq = NULL; /* for disk input */ static BYTE *diskBuffer = NULL; /* Disk input buffer */ static struct FileInfoBlock *pFIB = NULL; /* Buffer for directory scan */ static ULONG diskChangeCount; /* Disk change count */ static int nErFil = 0; /* Total number of errors */ static int nDirs = 0; /* Nr. of checked directories */ static int nFiles = 0; /* Nr. of checked files */ static Boolean fromWorkBench; /* Scheduled from WB or CLI ? */ static unsigned abortDT = 0; /* True when CTRL-C hit */ static int maxDrive = NUMUNITS - 1; /* Highest possible drive */ static int numCyls; /* Number of cylinders on drive */ /** | Local procedures **/ static unsigned checkBreak(void); static void checkDir(char *path, const Boolean root); static void checkFile(char *name); static void motor(const ULONG action); static void pcl(int before, int after, char *fmt, ...); static void readCyl(const int cyl, const int hd); static void seekFullRange(const SHORT howmany); static void syntax(void); void main( int argc, char **argv ){ int drive, cyl, head; SHORT error; char driveName[5]; /** | To be called from CLI, with DT DFx[:] ; if called from the | Workbench, a prompt for the floppy unit is sent to the console | window---created from the Lattice initialisation routine umain(), | gently hacked for this program. | | Pass 1: a seek over full range; | Pass 2: read all cylinders; | Pass 3: read all files record by record. | | But first, check the input arguments... **/ if (fromWorkBench = !argc) { do { fprintf(stdout, "\nDrive to test (DF0-DF%d) ? ", maxDrive); (void) fgets(driveName, sizeof(driveName), stdin); } while (strnicmp(driveName, "df", 2) || (drive = atoi(driveName+2)) < 0 || drive > maxDrive); } else { if (argc != 2 || strnicmp(*++argv, "df", 2) || (drive = atoi(*argv+2)) < 0 || drive > maxDrive) syntax(); } /** | Open properly the device, check for a disk in drive, | obtain disk parameters from various status commands | and obtain memory for our internal buffers. **/ if (!(diskPort = CreatePort(NULL, 0L))) { fprintf(stderr, "Can't create I/O port!\n"); cleanup(SYS_ABORT_CODE); } if (!(diskReq = (struct IOExtTD *) CreateExtIO(diskPort, sizeof(struct IOExtTD))) ) { fprintf(stderr, "Can't obtain I/O request block!\n"); cleanup(SYS_ABORT_CODE); } sprintf(driveName, "DF%d:", drive); if (error = OpenDevice(TD_NAME, drive, (struct IORequest *) diskReq, 0L)) { fprintf(stderr, "Error 0x%X returned by OpenDevice for drive %s ...\n", error, driveName); cleanup(SYS_ABORT_CODE); } if ((diskBuffer = (BYTE *) AllocMem(TD_CYL, MEMF_CHIP)) == NULL || (pFIB = (struct FileInfoBlock *) AllocMem(sizeof(struct FileInfoBlock), MEMF_CLEAR)) == NULL) { fprintf(stderr, "Can't allocate internal buffers ...\n"); cleanup(SYS_ABORT_CODE); } diskReq->iotd_Req.io_Command = TD_GETNUMTRACKS; (void) DoIO((struct IORequest *) diskReq); numCyls = diskReq->iotd_Req.io_Actual / NUMHEADS; diskReq->iotd_Req.io_Command = TD_CHANGESTATE; (void) DoIO((struct IORequest *) diskReq); if (diskReq->iotd_Req.io_Actual) { fprintf(stdout, "No disk present in drive %d ...\n", drive); cleanup(SYS_ABORT_CODE); } diskReq->iotd_Req.io_Command = TD_CHANGENUM; (void) DoIO((struct IORequest *) diskReq); fprintf(stdout, "Change number for drive %s is %d;\n", driveName, (diskChangeCount = diskReq->iotd_Req.io_Actual)); /** | Pass 1 **/ motor(ON); seekFullRange(1); /** | Pass 2 **/ fprintf(stdout, "Checking all disk tracks:\n"); for (cyl=0; cyliotd_Req.io_Error) { pcl(0, 1, "* Error 0x%X detected for cylinder %d, head %d", error, cyl, head); nErFil++; } } } motor(OFF); if (nErFil) { pcl(0, 1, "* %d hard errors detected reading drive %s.", nErFil, driveName); cleanup(SYS_ABORT_CODE); } else { pcl(0, 1, " no errors detected reading drive %s.", driveName); } /** | Pass 3 **/ pcl(0, 1, "Checking all files in drive %s", driveName); checkDir(driveName, True); pcl(0, 2, "%d director%s and %d file%s checked: %d error%s detected.", nDirs, (nDirs == 1 ? "y" : "ies"), nFiles, (nFiles == 1 ? "" : "s"), nErFil, (nErFil == 1 ? "" : "s")); cleanup(SYS_NORMAL_CODE); } static unsigned checkBreak(void) { if (!abortDT && (SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)) abortDT |= BRK_DETECTED; if (abortDT && !(abortDT & WARN_PRINTED)) { pcl(1, 1, "*** DT: BREAK ***"); abortDT |= WARN_PRINTED; } return abortDT; } static void checkDir( char *path, const Boolean root ){ BPTR dlock; char fileName[FILENAME_MAX]; char *pc; dirEntry *rdE = NULL; dirEntry *pdE; /** | This procedure checks (recursively) a directory. | "path" contains the full directory name, and "root" is non-zero the | first time that checkDir() is called - i.e. for the root directory. | checkDir() scans the wanted directory, checking immediately the 'true' | files; the subdirectories (if any) are checked recursively at the | end, one by one. | If an error is detected from checkDir(), the directory/file test is | stopped and the global flag "abortDT" is set; this makes possible, in | the further steps, to recursively free() all the memory that has been | allocated in order to store subdirectory names. | | First: obtain a lock on the wanted directory, and Examine() the lock; | since only one directory is being scanned at a time, we can use a | single FileInfoBlock buffer. **/ if ((dlock = Lock(path, ACCESS_READ)) == NULL) { pcl(0, 1, "* Can't access directory %s !", path); abortDT = INTERNAL_ERR; nErFil++; } else { if (!Examine(dlock, pFIB)) { pcl(0, 1, "* Error return from Examine(), directory %s", path); abortDT = INTERNAL_ERR; nErFil++; } else { /** | Prepare in "fileName" the full directory name - to which local | filenames will be appended. **/ if (root) { pc = strcpy(fileName, pFIB->fib_FileName); pc += strlen(fileName); *pc++ = ':'; *pc = NIHIL; pcl(0, 1, " checking files in root directory %s ...", fileName); } else { pc = strcpy(fileName, path); pc += strlen(fileName); pcl(0, 1, " checking files in directory %s ...", fileName); *pc++ = '/'; } nDirs++; /** | Now, loop over all directory entries. As already said, all the | 'real' files are immediately checked; the subdirectory names are | stored in a linked list to be examined at the end. This list is | implemented as a LIFO tree (the simplest type). **/ while (ExNext(dlock, pFIB)) { (void) strcpy(pc, pFIB->fib_FileName); if (pFIB->fib_DirEntryType < 0) { checkFile(fileName); } else { /** | If a memory allocation error is detected when asking space for | our linked list, we exit the "while" loop; setting the "abortDT" | flag before exiting, ensures that all the memory we had from | these malloc()'s will later be free()-ed recursively. **/ if ((pdE = malloc(sizeof(dirEntry) + strlen(fileName))) == NULL) { pcl(1, 1, "* Can't allocate heap memory!"); abortDT = INTERNAL_ERR; break; } (void) strcpy(pdE->name, fileName); pdE->next = rdE; rdE = pdE; } if (checkBreak()) break; } /** | We should check if ExNext() has failed, or if the last | entry has been found. **/ if (!abortDT && IoErr() != ERROR_NO_MORE_ENTRIES) { pcl(1, 1, "* Error reading directory %s !", path); nErFil++; } } UnLock(dlock); } /** | Now, loop over all detected subdirectories (if any); | freeing in the same time the memory used to store their names. **/ while (rdE != NULL) { if (!abortDT) checkDir(rdE->name, False); pdE = rdE->next; free(rdE); rdE = pdE; } } static void checkFile( char *name ){ BPTR pFH; long ier; /** | Check a file, opening and reading it record by record. **/ nFiles++; pcl(0, 0, " file %s ...", name); if ((pFH = Open(name, MODE_OLDFILE)) == NULL) { pcl(0, 1, "* Error %d opening file \"%s\".", IoErr(), name); nErFil++; } else { while (!abortDT && (ier = Read(pFH, diskBuffer, TD_CYL)) > 0) { (void) checkBreak(); } Close(pFH); if (ier < 0) { pcl(0, 1, "* Error %d reading file \"%s\".", IoErr(), name); nErFil++; } } } void cleanup( const int code ){ /** | Releases all global resources, then exit to the operating system. **/ if (diskBuffer != NULL) FreeMem(diskBuffer, TD_CYL); if (pFIB != NULL) FreeMem(pFIB, sizeof(struct FileInfoBlock)); if (diskReq != NULL) { CloseDevice((struct IORequest *) diskReq); DeleteExtIO((struct IORequest *) diskReq); } if (diskPort != NULL) DeletePort(diskPort); if (fromWorkBench) { int i; fprintf(stdout, "Strike to continue ... "); while ( (i = getchar()) != '\n' && i != EOF) { } } if (IntuitionBase != NULL) CloseLibrary((struct Library *) IntuitionBase); exit(code); } int CXBRK(void) { /** | If a CTRL-C is detected from the operating system, | we silently defer all handling until checkBreak() is called. **/ abortDT |= BRK_DETECTED; return 0; } static void motor( const ULONG action ){ diskReq->iotd_Req.io_Length = action; diskReq->iotd_Req.io_Command = TD_MOTOR; (void) DoIO((struct IORequest *) diskReq); } static void pcl( int before, int after, char *fmt, ... ){ va_list vl; static length = 0; int nc; /** | What the hell is the delete-to-end-of-line sequence on the Amiga? | The AmigaDOS manual refers to the ANSI sequence [1K - that do | not work in my NewCon windows; so I wrote this simple interface. When | overprinting, we check if the length of the new line is greater than | the length of the old one - if not, we output some trailing blanks. | "before" and "after" are the number of newlines to be printed before | and after this line; if "after" is 0 no newline but a carriage return | is output. **/ if (before) { while (before--) puts(""); length = 0; } va_start(vl, fmt); nc = vfprintf(stdout, fmt, vl); va_end(vl); length -= nc; if (length > 0) fprintf(stdout, "%*s", length, " "); if (after) { while (after--) puts(""); length = 0; } else { fprintf(stdout, "%c", '\r'); length = nc; } } static void readCyl( const int cyl, const int hd ){ diskReq->iotd_Req.io_Length = TD_CYL; diskReq->iotd_Req.io_Data = (APTR) diskBuffer; diskReq->iotd_Req.io_Command = ETD_READ; diskReq->iotd_Count = diskChangeCount; diskReq->iotd_Req.io_Offset = TD_SECTOR * (NUMSECS * (hd + NUMHEADS * cyl)); (void) DoIO((struct IORequest *) diskReq); } static void seekFullRange( const SHORT howmany ){ int i; SHORT error; for (i=1; i<=howmany; i++) { diskReq->iotd_Req.io_Offset = ((numCyls - 1) * NUMSECS * NUMHEADS - 1) * TD_SECTOR; diskReq->iotd_Req.io_Command = TD_SEEK; (void) DoIO((struct IORequest *) diskReq); if (error = diskReq -> iotd_Req.io_Error) { fprintf(stdout, "* Seek cycle %d, error 0x%X ...\n", i, error); cleanup(SYS_ABORT_CODE); } diskReq->iotd_Req.io_Offset = 0; diskReq->iotd_Req.io_Command = TD_SEEK; (void) DoIO((struct IORequest *) diskReq); if (error = diskReq->iotd_Req.io_Error) { fprintf(stdout, "* Seek cycle %d, error 0x%X ...\n", i, error); cleanup(SYS_ABORT_CODE); } } fprintf(stdout, " no errors detected seeking over full disk range.\n"); } static void syntax(void) { fprintf(stdout, "\n\tUsage:\t\tDT DFn, where 'n' (0-%d) is the drive number.\n", maxDrive); fprintf(stdout, "\tPurpose:\tDisk test.\n\n"); cleanup(SYS_NORMAL_CODE); }