/*
* fatfs.c - Routines for reading a FAT filesystem
*
* Copyright (C) 2002-2003 Gero Kuhlmann <gero@gkminix.han.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id: fatfs.c,v 1.3 2003/01/25 23:29:43 gkminix Exp $
*/
#define NEED_BINARY 1
#define NEED_TIME 1
#include <common.h>
#include <nblib.h>
#include "mknbi.h"
#include "dir.h"
#ifndef _MKNBI_H_DOS_
#error Included wrong header file
#endif
/*
* Buffers for temporary holding the disk FAT
*/
static enum { TYPE_FAT12, TYPE_FAT16 } fattype;
static unsigned long fatsize;
static __u8 *fatbuf = NULL;
/*
* Variables holding important values of a FAT filesystem, All size
* values here are in bytes, not sectors.
*/
static int fatfile = -1;
static unsigned long clustsize;
static unsigned long rootsize;
static unsigned long dataoffset;
static unsigned long rootoffset;
/***************************************************************************
Routines to read a file sector
***************************************************************************/
/* Current file position */
struct filepos {
unsigned long cursect;
unsigned long curclust;
unsigned long cursize;
};
/* Current disk position */
static unsigned long lastpos = 0L;
/*
* Read one sector from FAT image file
*/
static void readsect(buf, offset)
__u8 *buf;
unsigned long offset;
{
if (offset != lastpos) {
if (lseek(fatfile, offset, 0) != offset) {
prnerr0("unable to seek within DOS image file");
exit(EXIT_SEEK);
}
}
if (nbread(buf, SECTSIZE, fatfile) != SECTSIZE) {
prnerr0("unexpected end of DOS image file/device");
exit(EXIT_DOS_RDEOF);
}
lastpos = offset + SECTSIZE;
}
/*
* Open a file/directory
*/
static struct filepos *openfile(cluster, size)
unsigned long cluster;
unsigned long size;
{
struct filepos *fp;
fp = (struct filepos *)nbmalloc(sizeof(struct filepos));
fp->curclust = cluster;
fp->cursize = size;
fp->cursect = 0;
return(fp);
}
/*
* Close a file/directory
*/
static void closefile(fp)
struct filepos *fp;
{
if (fp != NULL)
free(fp);
}
/*
* Read a sector from a cluster, returns FALSE if end of file
*/
static int readclust(fp, buf)
struct filepos *fp;
__u8 *buf;
{
unsigned long offset;
unsigned long entry;
/* Check if at end of file */
if (((fp->cursize > 0) && ((fp->cursect * SECTSIZE) >= fp->cursize)) ||
((fattype == TYPE_FAT12) && (fp->curclust >= 0x0ff0)) ||
((fattype == TYPE_FAT16) && (fp->curclust >= 0xfff0)))
return(FALSE);
/* Read requested sector */
if (fp->curclust > 0) {
offset = (fp->curclust - FIRST_CLUSTER) * clustsize + dataoffset;
offset += (fp->cursect * SECTSIZE) % clustsize;
readsect(buf, offset);
} else {
offset = rootoffset + (fp->cursect * SECTSIZE);
readsect(buf, offset);
}
/* Advance pointers to next sector */
fp->cursect++;
if ((fp->curclust > 0) &&
(((fp->cursect * SECTSIZE) % clustsize) == 0)) {
if (fattype == TYPE_FAT12) {
entry = ttoh(getval(*((__u16 *)(fatbuf +
(fp->curclust * 12) / 8))));
entry |= ttoh(getval(*((__u16 *)(fatbuf +
(fp->curclust * 12) / 8 + 2)))) << 16;
entry >>= (fp->curclust * 12) % 8;
entry &= 0x0fff;
} else {
entry = ttoh(getval(*((__u16 *)
(fatbuf + (fp->curclust * 2)))));
entry &= 0xffff;
}
fp->curclust = entry;
}
return(TRUE);
}
/***************************************************************************
Routines to read directory tree
***************************************************************************/
/* Directory buffers */
struct dirpos {
__u8 dirbuf[SECTSIZE];
unsigned int diroffset;
struct filepos *fp;
};
/*
* Open directory
*/
static struct dirpos *fatopendir(dirp)
struct dos_dir *dirp;
{
struct dirpos *dp;
dp = (struct dirpos *)nbmalloc(sizeof(struct dirpos));
if (dirp != NULL)
dp->fp = openfile(ttoh(getval(dirp->cluster)), get_long(dirp->size));
else
dp->fp = openfile(0, rootsize);
dp->diroffset = SECTSIZE;
return(dp);
}
/*
* Close directory
*/
static void fatclosedir(dp)
struct dirpos *dp;
{
if (dp != NULL)
free(dp);
}
/*
* Get next directory entry, returns NULL if nothing to read
*/
static struct dos_dir *getnextdir(dp)
struct dirpos *dp;
{
struct dos_dir *dirp;
while (TRUE) {
/* Read next directory sector */
if (dp->diroffset >= SECTSIZE) {
if (!readclust(dp->fp, dp->dirbuf))
return(NULL);
dp->diroffset = 0;
}
/* Check that directory entry is not empty */
dirp = (struct dos_dir *)&(dp->dirbuf[dp->diroffset]);
dp->diroffset += sizeof(struct dos_dir);
if (dirp->name[0] != 0 && dirp->name[0] != 0xe5)
break;
}
return(dirp);
}
/*
* Compute checksum of short file name
*/
static int getchksum(dirp)
struct dos_dir *dirp;
{
int chksum = 0;
int i;
for (i = 0; i < 8; i++) {
chksum = (chksum >> 1) | ((chksum & 0x01) << 7);
chksum = (chksum + dirp->name[i]) & 0xff;
}
for (i = 0; i < 3; i++) {
chksum = (chksum >> 1) | ((chksum & 0x01) << 7);
chksum = (chksum + dirp->ext[i]) & 0xff;
}
return(chksum);
}
/*
* Table to convert DOS characters into latin1 characters
*/
static unsigned char const doschars[256] = {
0, 1, 2, 3, 4, 5, 6, 7, /* 0 - 7 */
8, 9, 10, 11, 12, 13, 14, 15, /* 8 - 15 */
16, 17, 18, 19, 182, 167, 22, 23, /* 16 - 23 */
24, 25, 26, 27, 28, 29, 30, 31, /* 24 - 31 */
32, 33, 34, 35, 36, 37, 38, 39, /* 32 - 39 */
40, 41, 42, 43, 44, 45, 46, 47, /* 40 - 47 */
48, 49, 50, 51, 52, 53, 54, 55, /* 48 - 55 */
56, 57, 58, 59, 60, 61, 62, 63, /* 56 - 63 */
64, 65, 66, 67, 68, 69, 70, 71, /* 64 - 71 */
72, 73, 74, 75, 76, 77, 78, 79, /* 72 - 79 */
80, 81, 82, 83, 84, 85, 86, 87, /* 80 - 87 */
88, 89, 90, 91, 92, 93, 94, 95, /* 88 - 95 */
96, 97, 98, 99, 100, 101, 102, 103, /* 96 - 103 */
104, 105, 106, 107, 108, 109, 110, 111, /* 104 - 111 */
112, 113, 114, 115, 116, 117, 118, 119, /* 112 - 119 */
120, 121, 122, 123, 124, 125, 126, 127, /* 120 - 127 */
199, 252, 233, 226, 228, 224, 229, 231, /* 128 - 135 */
234, 235, 232, 239, 238, 236, 196, 197, /* 136 - 143 */
201, 230, 198, 244, 246, 242, 251, 249, /* 144 - 151 */
255, 214, 220, 162, 163, 165, 158, 159, /* 152 - 159 */
225, 237, 243, 250, 241, 209, 170, 186, /* 160 - 167 */
191, 169, 172, 189, 188, 161, 171, 187, /* 168 - 175 */
248, 164, 253, 179, 180, 145, 20, 156, /* 176 - 183 */
184, 185, 21, 175, 166, 174, 190, 168, /* 184 - 191 */
192, 193, 194, 195, 142, 143, 146, 128, /* 192 - 199 */
200, 144, 202, 203, 204, 205, 206, 207, /* 200 - 207 */
208, 157, 210, 211, 212, 213, 153, 215, /* 208 - 215 */
216, 217, 218, 219, 154, 221, 222, 152, /* 216 - 223 */
133, 223, 131, 227, 132, 134, 181, 135, /* 224 - 231 */
138, 130, 136, 137, 141, 173, 140, 139, /* 232 - 239 */
240, 177, 149, 155, 147, 245, 247, 148, /* 240 - 247 */
176, 151, 183, 150, 129, 178, 254, 160, /* 248 - 255 */
};
/*
* Copy file name from directory entry into directory record.
* We do this byte by byte because on the host system, characters
* don't necessarily need to be encoded as with DOS.
*/
static void namecopy(dest, src, len)
char *dest;
__u8 *src;
int len;
{
int i;
for (i = 0; i < len; i++)
*(dest++) = doschars[(int)*(src++)];
}
/*
* Read in the complete directory structure
*/
static struct dir_struct *rdfatdir(src_dirp, src_lfn_name, src_lfn_num)
struct dos_dir *src_dirp;
utf16_t *src_lfn_name;
int src_lfn_num;
{
struct dirpos *dp;
struct dos_dir *dirp;
struct dir_struct *dsp;
struct dir_struct *tmpdsp;
struct file_struct *fsp;
utf16_t *tmp_name;
utf16_t *lfn_name;
__u16 *tmpptr;
int lfn_size = 0;
int lfn_chksum = 0;
int i, j;
/* Initialize temporary LFN buffer */
tmp_name = (utf16_t *)nbmalloc(LFN_CHARS * 64 * sizeof(utf16_t));
memset(tmp_name, 0xff, LFN_CHARS * 64 * sizeof(utf16_t));
/* Initialize directory structure */
dsp = (struct dir_struct *)nbmalloc(sizeof(struct dir_struct));
if (src_dirp != NULL) {
namecopy(dsp->name.name, src_dirp->name, sizeof(src_dirp->name));
namecopy(dsp->name.ext, src_dirp->ext, sizeof(src_dirp->ext));
dsp->name.lfn_name = src_lfn_name;
dsp->name.lfn_num = src_lfn_num;
dsp->attrib = src_dirp->attrib;
dsp->time = ttoh(getval(src_dirp->time));
dsp->date = ttoh(getval(src_dirp->date));
dsp->src.cluster = ttoh(getval(src_dirp->cluster));
} else {
dsp->attrib = ATTR_DIR;
gettime(time(NULL), &(dsp->date), &(dsp->time));
}
dsp->subdirnum = 0;
dsp->filenum = 0;
dsp->lfnnum = 0;
dsp->totsize = 0L;
dsp->subdirs = NULL;
dsp->files = NULL;
dsp->next = NULL;
/* Read in whole directory */
dp = fatopendir(src_dirp);
while ((dirp = getnextdir(dp)) != NULL) {
if ((bytecmp(". ", dirp->name, 8) ||
bytecmp(".. ", dirp->name, 8)) &&
bytecmp(" ", dirp->ext, 3))
continue;
if (dirp->attrib == ATTR_LFN) {
struct lfn_dir *lfnp = (struct lfn_dir *)dirp;
/*
* Check that the checksum is the same as of the preceding
* long file name entry.
*/
if (lfn_size == 0)
lfn_chksum = lfnp->checksum;
else if (lfn_chksum != lfnp->checksum) {
lfn_size = 0;
continue;
}
/*
* Copy long file name characters into temporary buffer
*/
i = ((lfnp->sequence & LFN_SEQ_MASK) - 1) * LFN_CHARS;
tmpptr = lfnp->first_part;
for (j = 0; j < LFN_CHARS; j++) {
tmp_name[i + j] = ttoh(getval(*tmpptr));
if (tmp_name[i + j] == 0)
break;
lfn_size++;
if (j == 5)
tmpptr = lfnp->second_part;
else if (j == 11)
tmpptr = lfnp->third_part;
else
tmpptr++;
}
continue;
} else if ((lfn_size > 0) && (lfn_chksum != getchksum(dirp))) {
/*
* If the checksum of the directory entry name doesn't
* fit to the checksum of the preceding long filename
* records, don't use a long file name for this directory
* entry.
*/
lfn_size = 0;
}
lfn_name = NULL;
if (lfn_size > 0) {
lfn_size = roundup(lfn_size, LFN_CHARS);
lfn_name = (utf16_t *)nbmalloc(lfn_size * sizeof(utf16_t));
memcpy(lfn_name, tmp_name, (lfn_size * sizeof(utf16_t)));
memset(tmp_name, 0xff, LFN_CHARS * 64 * sizeof(utf16_t));
}
if (!(dirp->attrib & ATTR_DIR) &&
!(dirp->attrib & ATTR_LABEL)) {
/* Handle entry for ordinary files */
fsp = (struct file_struct *)nbmalloc (sizeof(struct file_struct));
namecopy(fsp->name.name, dirp->name, sizeof(dirp->name));
namecopy(fsp->name.ext, dirp->ext, sizeof(dirp->ext));
fsp->name.lfn_name = lfn_name;
fsp->name.lfn_num = howmany(lfn_size, LFN_CHARS);
fsp->attrib = dirp->attrib;
fsp->time = ttoh(getval(dirp->time));
fsp->date = ttoh(getval(dirp->date));
fsp->src.cluster = ttoh(getval(dirp->cluster));
fsp->size = get_long(dirp->size);
fsp->next = dsp->files;
dsp->totsize += howmany(fsp->size, 1024);
dsp->lfnnum += fsp->name.lfn_num;
dsp->files = fsp;
dsp->filenum++;
} else if (!(dirp->attrib & ATTR_LABEL)) {
/* Handle directory entry */
tmpdsp = rdfatdir(dirp, lfn_name, howmany(lfn_size, LFN_CHARS));
tmpdsp->next = dsp->subdirs;
dsp->totsize += tmpdsp->totsize;
dsp->lfnnum += tmpdsp->name.lfn_num;
dsp->subdirs = tmpdsp;
dsp->subdirnum++;
} else {
/* Handle entry for volume name */
if (lfn_name != NULL) {
/* For labels just eat long file name */
free(lfn_name);
lfn_name = NULL;
}
if (volumename == NULL) {
volumename = (char *)nbmalloc(12);
namecopy(&(volumename[0]), dirp->name,
sizeof(dirp->name));
namecopy(&(volumename[8]), dirp->ext,
sizeof(dirp->ext));
}
}
lfn_size = 0;
}
fatclosedir(dp);
free(tmp_name);
/* Add size of directory to total size, except for root directory */
if (src_dirp != NULL)
dsp->totsize += howmany(DIR_ENTRIES(dsp) * sizeof(struct dos_dir),
1024);
return(dsp);
}
/***************************************************************************
Routines to open and close the image file
***************************************************************************/
/*
* Open a FAT file system image/device
*/
void fatopen(name)
char *name;
{
static __u8 buf[SECTSIZE];
struct boot_record *bootrec;
unsigned long total_sects;
unsigned long fat_offset;
unsigned int sect_per_fat;
unsigned int fat_num;
unsigned int i, j;
/*
* Open FAT image file or device and read the boot sector.
*/
if ((fatfile = open(name, O_RDONLY | O_BINARY)) < 0) {
prnerr1("unable to open DOS image file %s", name);
exit(EXIT_DOS_RDOPEN);
}
readsect(buf, 0);
if (getval(*((__u16 *)&(buf[BOOT_SIG_OFF]))) != htot(BOOT_SIGNATURE)) {
prnerr1("DOS image file %s has no boot signature", name);
exit(EXIT_DOS_NOBOOT);
}
bootrec = (struct boot_record *)buf;
/*
* Check that the boot record of the image file really contains a FAT
* filesystem and read some important values necessary for accessing the
* filesystem lateron.
*/
if (bytecmp(BOOT_FAT12_NAME, bootrec->fat_name, sizeof(bootrec->fat_name)))
fattype = TYPE_FAT12;
else if (bytecmp(BOOT_FAT16_NAME, bootrec->fat_name, sizeof(bootrec->fat_name)))
fattype = TYPE_FAT16;
else {
prnerr0("DOS image file has invalid filesystem");
exit(EXIT_DOS_INVFS);
}
if (getval(bootrec->bytes_per_sect) != htot(SECTSIZE)) {
prnerr0("DOS image file has wrong sector size");
exit(EXIT_DOS_SECTSIZE);
}
fat_num = bootrec->fat_num;
if ((fat_num == 0) || (fat_num > 2)) {
prnerr0("DOS image file has invalid number of FATs");
exit(EXIT_DOS_FATNUM);
}
fat_offset = ttoh(getval(bootrec->reserved_sect)) * SECTSIZE;
total_sects = ttoh(getval(bootrec->sect_num));
if (total_sects == 0)
total_sects = get_long(bootrec->sect_num_32);
if (total_sects < (MIN_RDSIZE * SECTS_PER_KB)) {
prnerr0("DOS image file has invalid number of sectors");
exit(EXIT_DOS_INVSIZE);
} else if (total_sects > ((ULONG_MAX / SECTSIZE) - MAX_SECTS)) {
prnerr0("DOS image file has too many sectors");
exit(EXIT_DOS_INVSIZE);
}
sect_per_fat = ttoh(getval(bootrec->sect_per_fat));
if (sect_per_fat == 0) {
prnerr0("DOS image file has invalid FAT size");
exit(EXIT_DOS_INVFAT);
}
fatsize = sect_per_fat * SECTSIZE;
clustsize = bootrec->sect_per_cluster * SECTSIZE;
rootsize = roundup(ttoh(getval(bootrec->dir_num)) * sizeof(struct dos_dir),
SECTSIZE);
rootoffset = fat_offset + (fat_num * fatsize);
dataoffset = rootoffset + rootsize;
/*
* Prepare the boot block by reading all reserved sectors. This is necesary
* so that any additional boot sectors get copied unmodified.
*/
boot_size = fat_offset;
boot_block = (__u8 *)nbmalloc(boot_size);
memcpy(boot_block, buf, SECTSIZE);
for (i = SECTSIZE; i < boot_size; i += SECTSIZE)
readsect(&(boot_block[i]), i);
/*
* Read first FAT. Note that this will destroy the buffer which
* contains the boot block!
*/
fatbuf = (__u8 *)nbmalloc(fatsize);
for (i = 0; i < sect_per_fat; i++)
readsect(&fatbuf[i * SECTSIZE], ((i * SECTSIZE) + fat_offset));
if (fatbuf[0] != bootrec->media_id) {
prnerr0("DOS image file has invalid FAT");
exit(EXIT_DOS_INVFAT);
}
for (j = 1; j < fat_num; j++) {
fat_offset += sect_per_fat * SECTSIZE;
for (i = 0; i < sect_per_fat; i++) {
readsect(buf, ((i * SECTSIZE) + fat_offset));
if (memcmp(buf, &fatbuf[i * SECTSIZE], SECTSIZE)) {
prnerr0("FAT copies in DOS image file differ");
exit(EXIT_DOS_INVFAT);
}
}
}
/* Read root directory recursively */
root_dir = rdfatdir(NULL, NULL, 0);
}
/*
* Close FAT image file or device
*/
void fatclose()
{
if (fatfile >= 0)
close(fatfile);
if (fatbuf != NULL)
free(fatbuf);
if (boot_block != NULL)
free(boot_block);
}
/*
* Copy a file from the FAT image into the output file
*/
void fatcopy(clustsize, handle, fsp)
int clustsize;
int handle;
struct file_struct *fsp;
{
struct filepos *fp;
__u8 *buf;
unsigned long num;
int eof = FALSE;
int i;
buf = (__u8 *)nbmalloc(clustsize);
fp = openfile(fsp->src.cluster, fsp->size);
for (num = 0; num < fsp->clustnum; num++) {
memset(buf, 0, clustsize);
for (i = 0; !eof && (i < clustsize); i += SECTSIZE)
eof = !readclust(fp, &(buf[i]));
(void)nbwrite(buf, clustsize, handle);
}
closefile(fp);
free(buf);
}
syntax highlighted by Code2HTML, v. 0.9.1