/*
* 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 <file> - Copy file from current DOS dir to floppy
* MCDFS R <file> - Copy file from floppy to current DOS dir
* MCDFS D <file> - 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) <filename>"); }
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"); }
}
Interested:
Questions:
Comments: