/* * MDCFS: Minimal Dos Compatible File System * * These routines provide the bare minimum needed to read and write * files on an MS-DOS format floppy disk. You could use them with a hard * disk as well, however since only 12 bit FAT's are supported, you are * limited to a total of 4096 clusters, and total drive space is limited * to 32MB due to 16 bit sector numbers (assuming 512 byte sectors). * * The functions were written for use in embedded systems (where memory * is often limited), and therefore provide only the basic open, read/write, * close and delete operations. I have documented the functions which * manipulate the directory and FAT, and it should be fairly easy to add * other features if you need them (directory display etc.). Only access to * files in the ROOT directory is provided, subdirectories are NOT supported. * * For simplicity and memory conservation, these functions buffer only 1 * sector (512 bytes) in memory. This makes them run quite slowly, but is * adaquate for reading/writing setup information and occational data logging. * If you have lots of memory and need extra speed, you could modify the * functions to read/write multiple sectors (a cluster would be easy). * You can also experiment with different interleave factors, to obtain * optimim performance with the existing I/O functions. * * As they stand, the functions really support access to only one drive * at a time. You can use multiple drives if you call "open_drive()" between * disk operations to the separate drives. This "switches" the active drive * to the specified one. Note however, that the selected drive will seek to * track zero each time this function is called, so performing many small * operations on more than one drive gets VERY inefficent. DO NOT read or * write to an open file located on any drive other than the currently * selected one! Call "open_drive()" first! * * Concurrent access to multiple files (on the same drive) is supported, * however since only one "work" buffer is used for directory/FAT access, * the drive may have to perform extra read/write operation when switching * from one file to the other. For this reason, it is best to try and do * as many reads or writes as possible on one file before accessing others. * Avoid many small operations to multiple files. * * At present, only the first copy of the disk File Allocation Table * (FAT) is used by these functions. * * Functions read/write data in RAW (binary) form, without regard for * NEWLINE characters etc. If you want to read/write ASCII text, you will * have to write "wrapper" functions to drop RETURN (0x0D) characters on * reading, and to add them before NEWLINE (0x0A) when writing. * * Due to the use of 'C' structures, Version 3.0 (or later) of MICRO-C * is REQUIRED! It should not be difficult to compile with a different * compiler, but I have not attempted to do so. * * You can compile this DEMO program to run under DOS with the MICRO-C * DOS compiler (available from our BBS: 613-256-6289). To use the functions * without DOS, replace the I/O routines (at the end of this file) with the * ones from the file DK86IO.C, and compile it with our stand-alone * 8086 Developers Kit. * * Copyright 1993-2000 Dave Dunfield * All rights reserved. * * Permission granted for personal (non-commercial) use only. */ /* Required definitions from MICRO-C stdio.h (not part of MDCFS) */ extern register printf(); #define FILE unsigned /* Misc fixed parameters */ #define SECTOR_SIZE 512 /* Size of a disk sector */ #define BPB_SIZE 17 /* Number of bytes in BIOS Parm Block */ #define EMPTY 0xE5 /* Signals empty directory */ #define EOF -1 /* End of file indicator */ #define ERROR -2 /* Report error in file */ #define READ 0 /* File opened for READ */ #define WRITE 1 /* File opened for WRITE */ /* * Structure of MSDOS directory entry */ struct Dentry { unsigned char Dname[11]; /* Filename + extension */ unsigned char Dattr; /* File attributes */ unsigned char Dreserved[10]; /* Reserved area */ unsigned Dtime; /* Time last modified */ unsigned Ddata; /* Date last modified */ unsigned Dcluster; /* First cluster number */ unsigned Dsizel; /* File size (LOW) */ unsigned Dsizeh; } ; /* File size (HIGH) */ /* * Structure of internal file control block */ struct Fblock { unsigned char Fattr; /* Open attributes */ unsigned char Fsector; /* Sector within cluster */ struct Dentry *Fdirptr; /* Pointer to directory entry */ unsigned Fdirsec; /* Directory sector */ unsigned Ffirstcls; /* First cluster in file */ unsigned Flastcls; /* Last cluster read/written */ unsigned Fnextcls; /* Next cluster to read/write */ unsigned Foffset; /* Read/Write offset */ unsigned Fsizel; /* File size (LOW) */ unsigned Fsizeh; /* File size (HIGH) */ unsigned char Fbuffer[]; } ; /* Data transfer buffer */ /* Internal "work" sector variables */ unsigned wrkdrv = 0, /* Current work drive number */ wrksec = -1; /* Current work sector number */ char wrkchg = 0; /* Indicates work sector changed */ unsigned char wrkbuff[SECTOR_SIZE]; /* Work sector buffer */ /* Active drive information (other than contained in BPB) */ char active_drive = -1; /* Open disk drive number */ unsigned dirsec = 5, /* First sector of directory */ datasec = 12; /* First sector of data area */ /* Disk information (from BIOS Parameter Block) */ unsigned int bytsec = 512; /* Bytes / sector */ unsigned char seccls = 2; /* Sectors / cluster */ unsigned int ressec = 1; /* # reserved sectors */ unsigned char numfat = 2; /* Number of FAT's */ unsigned int dirent = 112; /* Number of directory entries */ unsigned int sectors = 720; /* Sectors on disk */ unsigned char mediaid = 0xFD; /* Media ID byte */ unsigned int secfat = 2; /* Sectors / fat */ unsigned int sectrk = 9; /* Sectors / track */ unsigned int numhead = 2; /* Number of heads */ /* * Function Prototypes */ extern struct Dentry *lookup(), *create_file(); /* * File accessing functions: * * open_drive(drive) - Initialize a drive for file access * drive - Drive id (0=A, 1=B ...) * * open_file(name, attrs) - Open file for read or write * name - Name of file to open * attrs - Open attributes: READ or WRITE * returns : Pointer to file structure, or 0 if failure * * close_file(fp) - Close an open file * fp - Pointer to open file structure * * read_byte(fp) - Read a byte from an open file * fp - Pointer to open file structure * returns : Value read (0-255), -1 if EOF, -2 if not open for read * * write_byte(byte, fp) - Write a byte to a file * byte - Value to write to file (0-255) * returns : Value written (0-255) or -2 if error * * delete_file(name) - Erases the named file * name - Name of file to erase * returns : 0 if Success, -1 if failure (file not found) */ /* * Open a disk drive and set up control information * * THIS FUNCTION MUST BE CALLED BEFORE ACCESSING ANY FILES, * AND ANYTIME YOU SWITCH TO ACCESS A DIFFERENT DRIVE. */ open_drive(drive) char drive; { read_work(active_drive = drive, 0); memcpy(&bytsec, wrkbuff+11, BPB_SIZE); dirsec = (numfat * secfat) + ressec; datasec = ((((dirent * sizeof(struct Dentry)) + bytsec) - 1) / bytsec) + dirsec; } /* * Open a file & return a pointer to an allocated file structure */ struct Fblock *open_file(name, attrs) char *name; int attrs; { struct Dentry *dirptr; struct Fblock *fp; if(dirptr = lookup(name)) { /* File already exists */ if(attrs == WRITE) /* Zero size on write */ dirptr->Dsizel = dirptr->Dsizeh = 0; } else { if(attrs != WRITE) /* Not writing file */ return 0; if(!(dirptr = create_file(name, 0))) /* Unable to create */ return 0; } /* Allocate buffer for file control block */ if(!(fp = malloc(bytsec+sizeof(struct Fblock)))) return 0; /* Fill in file control block from directory information */ fp->Fdirsec = wrksec; fp->Fdirptr = dirptr; fp->Fattr = attrs; fp->Fnextcls = fp->Ffirstcls = dirptr->Dcluster; fp->Fsizel = dirptr->Dsizel; fp->Fsizeh = dirptr->Dsizeh; fp->Fsector = fp->Foffset = fp->Flastcls = 0; return fp; } /* * Close an open file */ close_file(fp) struct Fblock *fp; { struct Dentry *dirptr; unsigned sizeh, sizel; /* Special actions to be taken when writing */ if(fp->Fattr == WRITE) { sizel = fp->Fsizel; sizeh = fp->Fsizeh; /* If there is a partial last sector, fill it with DOS EOF */ /* characters, which also causes it to be written out */ while(fp->Foffset) write_byte(0x1A, fp); /* Release remaining clusters in file. If any sectors in the */ /* current cluster are used, begin with the next one. */ release(fp->Fsector ? get_fat(fp->Fnextcls) : fp->Fnextcls); /* Update size entry in file directory */ read_work(active_drive, fp->Fdirsec); dirptr = fp->Fdirptr; dirptr->Dcluster= fp->Ffirstcls; dirptr->Dsizel = sizel; dirptr->Dsizeh = sizeh; /* Set directory date of modification here - if you wish */ wrkchg = -1; } free(fp); update_work(); /* Insure disk is in sync */ } /* * Delete a file from the disk */ delete_file(name) char *name; { struct Dentry *dirptr; if(dirptr = lookup(name)) { *dirptr->Dname = EMPTY; wrkchg = -1; release(dirptr->Dcluster); update_work(); return 0; } return -1; } /* * Read next byte from open file */ int read_byte(fp) struct Fblock *fp; { unsigned cluster, sector; /* If all data read, return EOF */ if(!(fp->Fsizel || fp->Fsizeh)) { return EOF; } /* Decrement file size (32 bit) */ if((--fp->Fsizel) == -1) --fp->Fsizeh; /* If no data buffered ... read next sector */ if((fp->Foffset >= bytsec) || !fp->Foffset) { /* If not open for READ, return ERROR */ if(fp->Fattr != READ) return ERROR; /* If past last cluster, return EOF */ if(((cluster = fp->Fnextcls) >= 0xFF8) || !cluster) return EOF; /* Read the sector into memory */ read_sector(active_drive, (sector = fp->Fsector) + ((cluster-2)*seccls) + datasec, fp->Fbuffer); /* Advance sector in cluster, if past end, get next cluster number */ if(++sector >= seccls) { fp->Fnextcls = get_fat(cluster); sector = 0; } /* Update file control block information */ fp->Flastcls = cluster; fp->Fsector = sector; fp->Foffset = 0; } /* Read character, and advance circular buffer pointer */ return fp->Fbuffer[fp->Foffset++]; } /* * Write a byte to an open file */ int write_byte(c, fp) unsigned c; struct Fblock *fp; { unsigned cluster, sector; /* Test for writable file */ if(fp->Fattr != WRITE) return ERROR; /* Advance file size */ if(!++fp->Fsizel) ++fp->Fsizeh; /* Write character to buffer */ fp->Fbuffer[fp->Foffset++] = c; /* If buffer is full, write it */ if(fp->Foffset >= bytsec) { /* If no sector allocated, allocate one */ if(!fp->Fnextcls) fp->Ffirstcls = fp->Fnextcls = allocate(0); else if(fp->Fnextcls >= 0xFF8) fp->Fnextcls = allocate(fp->Flastcls); if(!(cluster = fp->Fnextcls)) return ERROR; /* Write the data to the drive */ write_sector(active_drive, (sector = fp->Fsector) + ((cluster-2)*seccls) + datasec, fp->Fbuffer); /* Advance to next sector in cluster */ if(++sector >= seccls) { fp->Fnextcls = get_fat(cluster); sector = 0; } /* Update file control block information */ fp->Flastcls = cluster; fp->Fsector = sector; fp->Foffset = 0; } return c; } /* * FAT manipulation functions: * * allocate(cluster) - Allocate a free cluster * cluster - Cluster to link this one to (o if none). * returns : Cluster number allocated, 0 if failure * * release(cluster) - Release a cluster chain (if allocated) * cluster - Beginnig cluster to release * * get_fat(cluster) - Get FAT entry for cluster * cluster - Cluster number to obtain entry for * returns : Cluster number linked (0 if free, 0xFF8+ for EOF) * * set_fat(cluster, link) - Set the FAT entry for a cluster * cluster - Cluster number to set link for * link - Cluster number to link to (0=free, 0xFFF = EOF) */ /* * Allocate a free cluster on disk, and cross connect FAT if necessary * mark cluster as used & end of file. */ int allocate(cluster) int cluster; { int begin, end, i; /* Calculate start and end clusters for search */ /* If we have a "FAT" sector loaded, begin searching from there, */ /* To attempt to keep allocated sectors in same "FAT" sector */ begin = ((i = wrksec-1) && (i <= secfat)) ? ((i * (SECTOR_SIZE*2)) / 3) + 1 : 2; end = (sectors - datasec) / seccls; do { #ifdef DEBUG printf("Allocate(%u) : Wrk=%u Begin=%u End=%u\n", cluster, wrksec, begin, end); #endif for(i = begin; i < end; ++i) if(!get_fat(i)) { #ifdef DEBUG printf("Allocated %u\n", i); #endif set_fat(i, 0xFFF); if(cluster) set_fat(cluster, i); return i; } /* Didn't find, reset to start cluster, and try again */ end = begin; begin = 2; } while(end != 2); #ifdef DEBUG printf("Failed!\n"); #endif return 0; } /* * Release a chain of allocated clusters */ release(cluster) int cluster; { unsigned i; while(cluster && (cluster < 0xFF8)) { i = get_fat(cluster); set_fat(cluster, 0); cluster = i; } } /* * Get a FAT entry for a given cluster number */ int get_fat(cluster) unsigned cluster; { unsigned sec, fat; #ifdef DEBUG printf("Get FAT for %u - ", cluster); #endif sec = (cluster *= 3)/2; read_work(active_drive, (sec / bytsec) + 1); fat = wrkbuff[sec % bytsec]; read_work(active_drive, (++sec / bytsec) + 1); fat |= wrkbuff[sec % bytsec] << 8; if(cluster & 1) fat >>= 4; #ifdef DEBUG printf("%u\n", fat & 0xFFF); #endif return fat & 0xfff; } /* * Set a FAT entry for a given cluster number */ set_fat(cluster, value) unsigned cluster, value; { unsigned sec, x; #ifdef DEBUG printf("Set FAT for %u - %u\n", cluster, value); #endif sec = (cluster *= 3)/2; /* Set LOW byte of FAT */ read_work(active_drive, (sec / bytsec) + 1); x = sec % bytsec; if(cluster & 1) wrkbuff[x] = (wrkbuff[x] & 0x0F) | (value << 4); else wrkbuff[x] = value; wrkchg = -1; /* Set high byte of FAT */ read_work(active_drive, (++sec / bytsec) + 1); x = sec % bytsec; if(cluster & 1) wrkbuff[x] = value >> 4; else wrkbuff[x] = (wrkbuff[x] & 0xF0) | ((value >> 8) & 0x0F); wrkchg = -1; } /* * Directory manipulation functions: * * lookup(file) - Locate a files directory entry * file - Name of file to locate * returns : Pointer to directory entry, 0 if failure. * * create_file(file,attrs) - Create a mew file * file - Name of file to create * attrs - Attributes for file (0 = normal) * returns : Pointer to directory entry, 0 if failure. * * parse_filename(buffer,name) - Get filename in directory format * buffer - Buffer for directory format filename (13 bytes) * name - ASCII filename to convert * * memcmp(ptr1, ptr2, size) - Block memory compare * ptr1 - Pointer to first block * ptr2 - Pointer to second block * size - Number of bytes to compare * returns : -1 of match, 0 if different. */ /* * Lookup a directory entry & return pointer to it */ struct Dentry *lookup(file) char *file; { unsigned i, j, k; char fbuffer[12]; parse_filename(fbuffer, file); #ifdef DEBUG printf("Lookup: '%s' ", fbuffer); #endif j = bytsec; k = dirsec; for(i=0; i < dirent; ++i) { if(j >= bytsec) { read_work(active_drive, k++); j = 0; } if(memcmp(wrkbuff+j, fbuffer, 11)) { #ifdef DEBUG printf("Found - Cluster: %u\n", *(int*)(wrkbuff+j+0x1A)); #endif return wrkbuff+j; } j += sizeof(struct Dentry); } #ifdef DEBUG printf("Not found\n"); #endif return 0; } /* * Create a file with the specified name * NOTE: Does NOT check for duplicates. It is assumed that "lookup()" * has been called first, to verify that the file does not already * exist on the disk. */ struct Dentry *create_file(file, attrs) char *file; int attrs; { int i, j, k; char fbuffer[12]; struct Dentry *dirptr; parse_filename(fbuffer, file); #ifdef DEBUG printf("Create '%s' ", fbuffer); #endif j = bytsec; k = dirsec; for(i=0; i < dirent; ++i) { if(j >= bytsec) { read_work(active_drive, k++); j = 0; } if((*(dirptr = wrkbuff+j)->Dname == EMPTY) || !*dirptr->Dname) { #ifdef DEBUG printf("Dir entry #%u\n", i); #endif memset(dirptr, 0, sizeof(struct Dentry)); strcpy(dirptr, fbuffer); dirptr->Dattr = attrs; /* Set directory date of creation here - if you wish */ wrkchg = -1; return wrkbuff+j; } j += sizeof(struct Dentry); } #ifdef DEBUG printf("No directory!\n"); #endif return 0; } /* * Parse a filename into directory format (12 characters, space filled) */ parse_filename(buffer, name) char buffer[], *name; { int i; i = 0; while(i < 8) buffer[i++] = (*name && (*name != '.')) ? toupper(*name++) : ' '; if(*name == '.') ++name; while(i < 11) buffer[i++] = *name ? toupper(*name++) : ' '; buffer[i] = 0; } /* * Memory to memory compare */ int memcmp(ptr1, ptr2, size) char *ptr1, *ptr2; unsigned size; { while(size--) if(*ptr1++ != *ptr2++) return 0; return -1; } /* * Internal "work" sector manipulation functions: * * read_work(sector) - Read sector into work buffer (if necessary) * sector - Sector number to read. * * update_work() - Write work sector back to disk if changed */ /* * Read a work sector into memory if it is not already in memory. */ read_work(drive, sector) char drive; int sector; { if((wrkdrv != drive) || (wrksec != sector)) { update_work(); read_sector(wrkdrv = drive, wrksec = sector, wrkbuff); } } /* * Write the work sector if it has been marked as modified */ update_work() { if(wrkchg) { write_sector(wrkdrv, wrksec, wrkbuff); wrkchg = 0; } } /* * Low level disk I/O functions: * * disk_error(code) - Called if unrecoverable disk error * code - Disk failure code (system depandant) * * read_sector(d, s, b) - Read a sector from the disk * d - Drive to read (0=A, 1=B ...) * s - Sector number to read (1-n) * b - Pointer to buffer to receive data * * write_sector(d, s, b) - Write a sector to the disk * d - Drive to write (0=A, 1=B ...) * s - Sector number to write (1-n) * b - Pointer to buffer containing the data * * These functions make use of the IBM PC BIOS, and are compatible * with Microsoft MASM assembler. Use these if you are compiling the * demo program with the MICRO-C DOS compiler. */ /* * Report a disk error */ disk_error(code) int code; { printf("Disk error - Code: %04x\n", code); exit(-1); } /* * Read a sector from the disk drive */ read_sector(drive, sector, buffer) asm { PUSH DS ; Save DS POP ES ; Set ES MOV BX,4[BP] ; Get buffer MOV AX,6[BP] ; Get sector XOR DX,DX ; Zero high DIV WORD PTR _sectrk; Calculate track MOV CL,DL ; CL = sector INC CL ; 1- XOR DX,DX ; Zero high DIV WORD PTR _numhead; Compute head MOV CH,AL ; CH = cylinder MOV DH,DL ; DH = head MOV DL,8[BP] ; DL = drive MOV DI,3 ; Try three times read1: MOV AX,0201h ; Read 1 sector INT 13h ; Call BIOS JNC read2 ; Success DEC DI ; Reduce count JNZ read1 ; Keep trying PUSH AX ; Pass parameter CALL _disk_error ; Report an error POP AX ; Clean stack read2: XOR AX,AX ; Zero return } /* * Write a sector to the disk drive */ write_sector(drive, sector, buffer) asm { PUSH DS ; Save DS POP ES ; Set ES MOV BX,4[BP] ; Get buffer MOV AX,6[BP] ; Get sector XOR DX,DX ; Zero high DIV WORD PTR _sectrk; Calculate track MOV CL,DL ; CL = sector INC CL ; 1- XOR DX,DX ; Zero high DIV WORD PTR _numhead; Compute head MOV CH,AL ; CH = cylinder MOV DH,DL ; DH = head MOV DL,8[BP] ; DL = drive MOV DI,3 ; Try three times write1: MOV AX,0301h ; Write 1 sector INT 13h ; Call BIOS JNC write2 ; Success DEC DI ; Reduce count JNZ write1 ; Keep trying PUSH AX ; Pass parameter CALL _disk_error ; Report an error POP AX ; Clean stack write2: XOR AX,AX ; Zero return } /* * Sample Main program (Not part of MDCFS) to demonstrate reading, * writing and deleteing files. This also uses the standard file * I/O of the MICRO-C DOS compiler, and will not run stand-alone. * * Demo command syntax: * MCDFS W - Copy file from current DOS dir to floppy * MCDFS R - Copy file from floppy to current DOS dir * MCDFS D - Delete file from floppy. * * All accesss to the floppy are performed via MCDFS. */ main(argc, argv) int argc; char *argv[]; { int c; struct Fblock *fp; FILE *xfp; open_drive(0); /* Change to (1) for drive B: */ switch((argc > 2) ? toupper(*argv[1]) : 0) { case 'R' : /* Read file from floppy drive */ if(!(fp = open_file(argv[2], READ))) abort("Couldn't read MDCFS file"); xfp = fopen(argv[2], "wbvq"); while((c = read_byte(fp)) >= 0) putc(c, xfp); close_file(fp); fclose(xfp); c = 0; /* Disk has not been modified */ break; case 'W' : /* Write file to floppy drive */ xfp = fopen(argv[2], "rvbq"); if(!(fp = open_file(argv[2], WRITE))) abort("Couldn't write MDCFS file"); while((c = getc(xfp)) >= 0) write_byte(c, fp); fclose(xfp); close_file(fp); c = -1; /* Note that disk may have been written */ break; case 'D' : /* Delete file from floppy drive */ delete_file(argv[2]); c = -1; /* Note that disk may have been written */ break; default: abort("Use: MDCFS (Read/Write/Delete) "); } if(c) { printf("\nNote: Since MDCFS bypasses DOS completely, DOS will be unaware\n"); printf("of any changes to the disk... Due to DOS buffering, files written\n"); printf("may not appear in DOS 'dir' command, or may have their sizes\n"); printf("reported incorrectly... Press CONTROL-C to force DOS to flush\n"); printf("its buffers before accessing the floppy disk"); } }