/* * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 2.0 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* * Copyright (c) 1998 Robert Nordier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "libsaio.h" #include "sl.h" #include "msdos_private.h" #include "msdos.h" #define LABEL_LENGTH 11 #define MAX_DOS_BLOCKSIZE 2048 #define CLUST_FIRST 2 /* first legal cluster number */ #define CLUST_RSRVD 0xfffffff6 /* reserved cluster range */ #define false 0 #define true 1 #if DEBUG #define DLOG(x) { outb(0x80, (x)); getc(); } #else #define DLOG(x) #endif #if UNUSED /* * Check a volume label. */ static int oklabel(const char *src) { int c, i; for (i = 0, c = 0; i <= 11; i++) { c = (u_char)*src++; if (c < ' ' + !i || strchr("\"*+,./:;<=>?[\\]|", c)) break; } return i && !c; } #endif /* UNUSED */ /* Fix up volume label. */ static void fixLabel(char *label, char *str, long strMaxLen) { int i; //unsigned char labelUTF8[LABEL_LENGTH*3]; /* Convert leading 0x05 to 0xE5 for multibyte languages like Japanese */ if (label[0] == 0x05) label[0] = 0xE5; #if UNUSED /* Check for illegal characters */ if (!oklabel(label)) label[0] = 0; #endif /* UNUSED */ /* Remove any trailing spaces */ for (i=LABEL_LENGTH-1; i>=0; --i) { if (label[i] == ' ') label[i] = 0; else break; } /* TODO: Convert it to UTF-8 from DOSLatin1 encoding */ strncpy(str, label, strMaxLen); } void MSDOSGetDescription(CICell ih, char *str, long strMaxLen) { struct direntry *dirp; union bootsector *bsp; struct bpb33 *b33; struct bpb50 *b50; struct bpb710 *b710; u_int16_t bps; int8_t spc; int rootDirSectors; int i, finished; char *buf; char label[LABEL_LENGTH+1]; DLOG(0); buf = (char *)malloc(MAX_DOS_BLOCKSIZE); if (buf == 0) goto error; DLOG(1); /* * Read the boot sector of the filesystem, and then check the * boot signature. If not a dos boot sector then error out. * * NOTE: 2048 is a maximum sector size in current... */ Seek(ih, 0); Read(ih, (long)buf, MAX_DOS_BLOCKSIZE); DLOG(2); bsp = (union bootsector *)buf; b33 = (struct bpb33 *)bsp->bs33.bsBPB; b50 = (struct bpb50 *)bsp->bs50.bsBPB; b710 = (struct bpb710 *)bsp->bs710.bsBPB; DLOG(3); /* [2699033] * * The first three bytes are an Intel x86 jump instruction. It should be one * of the following forms: * 0xE9 0x?? 0x?? * 0xEC 0x?? 0x90 * where 0x?? means any byte value is OK. */ if (bsp->bs50.bsJump[0] != 0xE9 && (bsp->bs50.bsJump[0] != 0xEB || bsp->bs50.bsJump[2] != 0x90)) { goto error; } DLOG(4); /* It is possible that the above check could match a partition table, or some */ /* non-FAT disk meant to boot a PC. Check some more fields for sensible values. */ /* We only work with 512, 1024, and 2048 byte sectors */ bps = OSSwapLittleToHostInt16(b33->bpbBytesPerSec); if ((bps < 0x200) || (bps & (bps - 1)) || (bps > 0x800)) { goto error; } DLOG(5); /* Check to make sure valid sectors per cluster */ spc = b33->bpbSecPerClust; if ((spc == 0 ) || (spc & (spc - 1))) { goto error; } DLOG(6); /* we know this disk, find the volume label */ /* First, find the root directory */ label[0] = '\0'; finished = false; rootDirSectors = ((OSSwapLittleToHostInt16(b50->bpbRootDirEnts) * sizeof(struct direntry)) + (bps-1)) / bps; DLOG(7); if (rootDirSectors) { /* FAT12 or FAT16 */ int firstRootDirSecNum; u_int8_t *rootDirBuffer; int j; rootDirBuffer = (u_int8_t *)malloc(MAX_DOS_BLOCKSIZE); DLOG(8); firstRootDirSecNum = OSSwapLittleToHostInt16(b33->bpbResSectors) + (b33->bpbFATs * OSSwapLittleToHostInt16(b33->bpbFATsecs)); for (i=0; i< rootDirSectors; i++) { Seek(ih, (firstRootDirSecNum+i)*bps); Read(ih, (long)rootDirBuffer, bps); dirp = (struct direntry *)rootDirBuffer; for (j=0; jdeName[0] == SLOT_EMPTY) { finished = true; break; } else if (dirp->deName[0] == SLOT_DELETED) continue; else if (dirp->deAttributes == ATTR_WIN95) continue; else if (dirp->deAttributes & ATTR_VOLUME) { strncpy(label, (char *)dirp->deName, LABEL_LENGTH); finished = true; break; } } /* j */ if (finished == true) { break; } } /* i */ free(rootDirBuffer); } else { /* FAT32 */ u_int32_t cluster; u_int32_t bytesPerCluster; u_int8_t *rootDirBuffer; off_t readOffset; DLOG(9); bytesPerCluster = bps * spc; rootDirBuffer = malloc(bytesPerCluster); cluster = OSSwapLittleToHostInt32(b710->bpbRootClust); DLOG(0x20); finished = false; while (!finished && cluster >= CLUST_FIRST && cluster < CLUST_RSRVD) { DLOG(0x21); /* Find sector where clusters start */ readOffset = OSSwapLittleToHostInt16(b710->bpbResSectors) + (b710->bpbFATs * OSSwapLittleToHostInt16(b710->bpbBigFATsecs)); /* Find sector where "cluster" starts */ readOffset += ((off_t) cluster - CLUST_FIRST) * (off_t) spc; /* Convert to byte offset */ readOffset *= (off_t) bps; DLOG(0x22); /* Read in "cluster" */ Seek(ih, readOffset); Read(ih, (long)rootDirBuffer, bytesPerCluster); dirp = (struct direntry *) rootDirBuffer; DLOG(0x23); /* Examine each directory entry in this cluster */ for (i=0; i < bytesPerCluster; i += sizeof(struct direntry), dirp++) { DLOG(0x24); if (dirp->deName[0] == SLOT_EMPTY) { finished = true; // Reached end of directory (never used entry) break; } else if (dirp->deName[0] == SLOT_DELETED) continue; else if (dirp->deAttributes == ATTR_WIN95) continue; else if (dirp->deAttributes & ATTR_VOLUME) { DLOG(0x31); strncpy(label, (char *)dirp->deName, LABEL_LENGTH); finished = true; break; } DLOG(0x25); } if (finished) break; DLOG(0x26); /* Find next cluster in the chain by reading the FAT */ /* Find first sector of FAT */ readOffset = OSSwapLittleToHostInt16(b710->bpbResSectors); /* Find sector containing "cluster" entry in FAT */ readOffset += (cluster * 4) / bps; /* Convert to byte offset */ readOffset *= bps; DLOG(0x27); /* Read one sector of the FAT */ Seek(ih, readOffset); Read(ih, (long)rootDirBuffer, bps); DLOG(0x28); cluster = OSReadLittleInt32(rootDirBuffer + ((cluster * 4) % bps), 0); cluster &= 0x0FFFFFFF; // ignore reserved upper bits } free(rootDirBuffer); } /* rootDirSectors */ DLOG(10); /* else look in the boot blocks */ if (str[0] == '\0') { if (OSSwapLittleToHostInt16(b50->bpbRootDirEnts) == 0) { /* It's FAT32 */ strncpy(label, (char *)((struct extboot *)bsp->bs710.bsExt)->exVolumeLabel, LABEL_LENGTH); } else if (((struct extboot *)bsp->bs50.bsExt)->exBootSignature == EXBOOTSIG) { strncpy(label, (char *)((struct extboot *)bsp->bs50.bsExt)->exVolumeLabel, LABEL_LENGTH); } } fixLabel(label, str, strMaxLen); free(buf); return; error: DLOG(0xee); if (buf) free(buf); return; } /*-----------------------------------------------------------------------*/ // Swiped from msdosfs_conv.c and slightly modified typedef unsigned short u_short; typedef unsigned long u_long; /* * Total number of days that have passed for each month in a regular year. */ static u_short regyear[] = { 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; /* * Total number of days that have passed for each month in a leap year. */ static u_short leapyear[] = { 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }; /* * This variable contains the number of seconds that local time is west of GMT * It is updated every time an msdosfs volume is mounted. This value is * essentially tz_minuteswest * 60 - (tz_dsttime ? 3600 : 0) based on the * timezone returned by gettimeofday() in the mount_msdosfs tool. * * This has a problem with daylight savings time. If the current daylight * savings time setting does not match the date being converted, then it * will be off by one hour. The only way to properly fix this is to know * (inside the kernel) what dates should be adjusted for daylight savings, * and which should not. That would let us return correct GMT for dates * throughout the year. Of course, the GUI would have to do the opposite * conversion when converting to local time for display to the user. */ __private_extern__ long msdos_secondsWest = 0; /* * The number of seconds between Jan 1, 1970 and Jan 1, 1980. In that * interval there were 8 regular years and 2 leap years. */ #define SECONDSTO1980 (((8 * 365) + (2 * 366)) * (24 * 60 * 60)) static u_short lastdosdate; static u_long lastseconds; /* * Convert from dos' idea of time to unix'. This will probably only be * called from the stat(), and fstat() system calls and so probably need * not be too efficient. */ __private_extern__ long dos2unixseconds(uint16_t dd, uint16_t dt, uint8_t dh) { u_long seconds; u_long month; u_long year; u_long days; u_short *months; if (dd == 0) { /* * Uninitialized field, return the epoch. */ return 0; } seconds = (((dt & DT_2SECONDS_MASK) >> DT_2SECONDS_SHIFT) << 1) + ((dt & DT_MINUTES_MASK) >> DT_MINUTES_SHIFT) * 60 + ((dt & DT_HOURS_MASK) >> DT_HOURS_SHIFT) * 3600 + dh / 100; /* * If the year, month, and day from the last conversion are the * same then use the saved value. */ if (lastdosdate != dd) { lastdosdate = dd; days = 0; year = (dd & DD_YEAR_MASK) >> DD_YEAR_SHIFT; days = year * 365; days += year / 4 + 1; /* add in leap days */ if ((year & 0x03) == 0) days--; /* if year is a leap year */ months = year & 0x03 ? regyear : leapyear; month = (dd & DD_MONTH_MASK) >> DD_MONTH_SHIFT; if (month < 1 || month > 12) { printf("dos2unixtime(): month value out of range (%ld)\n", month); month = 1; } if (month > 1) days += months[month - 2]; days += ((dd & DD_DAY_MASK) >> DD_DAY_SHIFT) - 1; lastseconds = (days * 24 * 60 * 60) + SECONDSTO1980; } return seconds + lastseconds + msdos_secondsWest; } /*-----------------------------------------------------------------------*/ // Copyright 2009 David Elliott. All rights reserved. // Pull this in from hfs_compare.c extern long FastUnicodeCompare(u_int16_t *uniStr1, u_int32_t len1, u_int16_t *uniStr2, u_int32_t len2); // The offset of the BPB is the same regardless of type but we use the appropriate member of the union anyway. #define BPB33(bsp) ( (struct bpb33 *)((bsp)->bs33.bsBPB) ) #define BPB50(bsp) ( (struct bpb50 *)((bsp)->bs50.bsBPB) ) #define BPB710(bsp) ( (struct bpb710 *)((bsp)->bs710.bsBPB) ) // Here the offset of the EBS structure is wildly different between 5.0 and 7.10 #define EBS50(bsp) ( (struct extboot *)((bsp)->bs50.bsExt) ) #define EBS710(bsp) ( (struct extboot *)((bsp)->bs710.bsExt) ) #define CLUSTER_LIMIT(fatbits) ( (1 << (fatbits)) - 11 ) // Provides a place to store filenames static char *gTempStr; // Holds a pointer to the last FAT filesystem we worked with. static CICell gCurrentIH = NULL; static uint32_t gBlockSize; // How to read a FAT static uint32_t gClusterMask; // FAT12 = 3/2, FAT16 = 2/1, FAT32 = 4/1 static uint32_t gClusterMultN; static uint32_t gClusterMultD; static uint32_t gOddClusterShift; // 4 for FAT12, 0 for everything else. // EOC marker for this type of FAT. static uint32_t gEOC; static uint32_t gBytesPerSector; static uint32_t gFatStartLBA; // Starting LBA of FAT static uint32_t gFatBufferLBA; // Buffer starts with this LBA static uint32_t gFatBufferSecs; // Buffer contains this many blocks. static void *gFatBuffer = NULL; // For FAT12/16 where we have the special root directory: static uint32_t gRootDirLBA; // LBA of root directory. static uint32_t gRootEntries; // Number of root entries static uint32_t gDataLBA; // LBA of root directory. // 8-bit sectors per cluster uint8_t BPB_SecPerClus; // For FAT32 when the root dir is in the normal data area. static uint32_t gRootDirCluster; union doswinentry { struct direntry de; struct winentry we; }; void MSDOSFree(CICell ih) { if(gCurrentIH == ih) gCurrentIH = 0; free(ih); } long MSDOSInitPartition(CICell ih) { if(ih == gCurrentIH) { #ifdef __i386__ CacheInit(ih, gBlockSize); #endif return 0; } if(gTempStr == NULL) gTempStr = malloc(4096); char *buf; buf = malloc(MAX_DOS_BLOCKSIZE); Seek(ih, 0); Read(ih, (long)buf, MAX_DOS_BLOCKSIZE); union bootsector *bsp = (union bootsector *)buf; if (!( (bsp->bs33.bsJump[0] == 0xe9) || (bsp->bs33.bsJump[0] == 0xEB && bsp->bs33.bsJump[2] == 0x90) )) goto error; gBytesPerSector = BPB33(bsp)->bpbBytesPerSec; BPB_SecPerClus = BPB33(bsp)->bpbSecPerClust; // SPC must not be zero and must be a power of two. if (BPB_SecPerClus == 0 || (BPB_SecPerClus & (BPB_SecPerClus -1)) != 0) goto error; uint16_t BPB_RsvdSecCnt = BPB33(bsp)->bpbResSectors; if( BPB_RsvdSecCnt < 1) goto error; uint16_t BPB_NumFATs = BPB33(bsp)->bpbFATs; if( BPB_NumFATs < 1) goto error; gRootEntries = BPB33(bsp)->bpbRootDirEnts; // Calculate RootDirSectors by calculating the number of bytes required to store the root directory then // dividing (rounding up) by BPB_BytsPerSec to arrive at the number of sectors requires to store it. uint32_t RootDirSectors = (BPB33(bsp)->bpbRootDirEnts * sizeof(struct direntry) + (gBytesPerSector - 1)) / gBytesPerSector; uint32_t BPB_TotSec = BPB33(bsp)->bpbSectors; if( BPB_TotSec == 0 ) { // We can only truly trust the BPB_TotSec32 field if we are sure we have a 50 or 710 sector. if( EBS50(bsp)->exBootSignature == EXBOOTSIG || EBS710(bsp)->exBootSignature == EXBOOTSIG ) BPB_TotSec = BPB50(bsp)->bpbHugeSectors; else goto error; } uint32_t BPB_FATSz = BPB33(bsp)->bpbFATsecs; if( BPB_FATSz == 0) { if( EBS710(bsp)->exBootSignature == EXBOOTSIG ) { BPB_FATSz = BPB710(bsp)->bpbBigFATsecs; } else goto error; } if( EBS710(bsp)->exBootSignature == EXBOOTSIG ) { gRootDirCluster = BPB710(bsp)->bpbRootClust; } else gRootDirCluster = 0; // For FAT32, gRootDirLBA == DataLBA since RootDirSectors == 0 gRootDirLBA = BPB_RsvdSecCnt + BPB_NumFATs * BPB_FATSz; // The DataLBA depends on BPB_FATSz which means it depends on version gDataLBA = gRootDirLBA + RootDirSectors; // DataSectors depends on gDataLBA and on BPB_TotSec which means it depends on version uint32_t DataSectors = BPB_TotSec - gDataLBA; uint32_t ClusterCount = DataSectors / BPB_SecPerClus; uint8_t fatbits; if( ClusterCount < CLUSTER_LIMIT(12) ) { // FAT12 fatbits = 12; gClusterMultN = 3; gClusterMultD = 2; gOddClusterShift = 4; } else if( ClusterCount < CLUSTER_LIMIT(16) ) { // FAT16 fatbits = 16; gClusterMultN = 2; gClusterMultD = 1; gOddClusterShift = 0; } else if( ClusterCount < CLUSTER_LIMIT(28) ) { // FAT32 (really 28) fatbits = 28; gClusterMultN = 4; gClusterMultD = 1; gOddClusterShift = 0; } else goto error; gClusterMask = (1 << fatbits) - 1; gEOC = (1 << fatbits) - 8; uint32_t desiredFat = 0; if(EBS710(bsp)->exBootSignature == EXBOOTSIG) { desiredFat = BPB710(bsp)->bpbExtFlags & 0xf; if(desiredFat >= BPB_NumFATs) desiredFat = 0; } gFatStartLBA = (BPB_RsvdSecCnt + desiredFat * BPB_FATSz); free(gFatBuffer); uint64_t fatBytes = BPB_FATSz * gBytesPerSector; // If the fat size is less than 256k then load all of it into the heap if( fatBytes < 256 * 1024 ) { gFatBufferLBA = gFatStartLBA; gFatBufferSecs = BPB_FATSz; gFatBuffer = malloc(fatBytes); if(gFatBuffer == NULL) goto error; // Seek to first block of FAT we want Seek(ih, (long long)gFatStartLBA * gBytesPerSector); Read(ih, (long)gFatBuffer, fatBytes); } // Otherwise, set up a buffer to hold two sectors. else { gFatBufferLBA = 0; gFatBufferSecs = 0; gFatBuffer = malloc(2 * gBytesPerSector); if(gFatBuffer == NULL) goto error; } gCurrentIH = ih; return 0; error: free(buf); gCurrentIH = NULL; return -1; } static uint32_t NextCluster(uint32_t cluster) { CICell ih = gCurrentIH; uint32_t FATOffset = cluster * gClusterMultN / gClusterMultD; uint32_t fatSec = gFatStartLBA + (FATOffset / gBytesPerSector); uint32_t fatEntOff = FATOffset % gBytesPerSector; // We don't need to worry about sector spanning because it only // happens for FAT12 in which case we loaded the entire FAT. if(!( gFatBufferLBA <= fatSec && fatSec < (gFatBufferLBA + gFatBufferSecs) )) { Seek(ih, fatSec * gBytesPerSector); Read(ih, (long)gFatBuffer, 2 * gBytesPerSector); gFatBufferLBA = fatSec; gFatBufferSecs = 2; } void *entP = (uint8_t*)gFatBuffer + (fatSec - gFatBufferLBA) * gBytesPerSector + fatEntOff; // NOTE: The code below depends on little-endian arch. if( cluster % 2 != 0 ) return (*(uint32_t*)entP >> gOddClusterShift) & gClusterMask; else return *(uint32_t*)entP & gClusterMask; } //static char const badSFNChars[] = "\"*+,./:;<=>?[\\]|"; static char const badSFNChars[] = {0x22, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D, 0x7C, 0x00}; static int sfn_toupper(int c) { if(c < 0x20) return -1; char const *badchar; for(badchar = badSFNChars; badchar < badSFNChars + sizeof(badSFNChars); ++badchar) if( c == *badchar ) return -1; if( c >= 'a' && c <= 'z' ) return c & ~0x20; else return c; } static void sfn_to_fn(uint8_t const *sfn, char *fileName) { int i; // Since we use ' ' in our backwards search we must not // allow for filenames that start with a space. if(sfn[0] == ' ') { *fileName = '\0'; return; } // Copy the 8 filename characters for(i=8; i > 0; --i) *(fileName++) = *(sfn++); // Backtrack until we find a non-space character. while(*(--fileName) == ' ') ; // Put in the period. *(++fileName) = '.'; // Advance fileName to account for the period we just added. ++fileName; // Copy the 3 extension characters. for(i=3; i > 0; --i) *(fileName++) = *(sfn++); // Backtrack until we find a non-space character. while(*(--fileName) == ' ') ; // Period? There's no extension. Terminate string with NUL and bail. if(*fileName == '.') { *fileName = '\0'; return; } // Next character is a space, change this to NUL terminator. *(++fileName) = '\0'; // Tada! } static void sfn_for_fn(uint8_t *sfn, char const *fileName) { int i = 0; // Look for "." and ".." as these are valid SFN // Anything else starting with "." is not valid SFN if(fileName[0] == '.') { sfn[0] = '.'; if( fileName[1] == '\0') i = 1; else if( fileName[1] == '.' && fileName[2] == '\0') { sfn[1] = '.'; i = 2; } else goto nosfn; // Pad with space to match SFN in dir entry for(; i < 11; ++i) sfn[i] = ' '; return; } bool doExtension = false; char const *curr; for(curr = fileName; *curr != '\0'; ++curr) { if(*curr == '.') { // Cannot have a dot within the extension portion. if(doExtension) goto nosfn; for(; i < 8; ++i) sfn[i] = ' '; doExtension = true; continue; } if(!doExtension && i >= 8) goto nosfn; if(i >= 11) goto nosfn; int sfnchar = sfn_toupper(*curr); if(sfnchar < 0 || sfnchar > 255) goto nosfn; sfn[i] = (char)sfnchar; ++i; } // Fill the rest of the extension with spaces. for( ; i < 11; ++i) sfn[i] = ' '; return; nosfn: bzero(sfn, 11); } static uint8_t rotate_right(uint8_t c) { return ((c << 7) & 0x80) | ((c >> 1) & 0x7f); } static uint8_t LFNChecksum(uint8_t const *sfn) { int i; uint8_t sum = 0; for( i=11; i>0; --i ) { sum = rotate_right(sum) + *sfn++; } return sum; } static int32_t UnpackLFN(union doswinentry const *lfnEntries, uint16_t *unpackedLFN) { uint8_t sfnChecksum = LFNChecksum(lfnEntries[0].de.deName); int i; uint16_t *unpackedCurr = unpackedLFN; bool getChars = true; for(i=1; i < 64 ; ++i) { // Check that the LFN entry is in sequence. If not, the entire LFN // sequence is invalidated. if( (lfnEntries[i].we.weCnt & WIN_CNT) != i ) return -1; // No need to check weAttributes. It was checked before filling the buffer. // No need to check weReserved1 (LDIR_Type) // If the checksum doesn't match this entry.. it's no good. if( lfnEntries[i].we.weChksum != sfnChecksum ) return -1; // No need to check weReserved2 (LDIR_FstClusLO). int c; uint16_t *inptr; if(getChars) { inptr = ((uint16_t*)(lfnEntries[i].we.wePart1)); for(c=sizeof(lfnEntries[i].we.wePart1)/sizeof(uint16_t); c > 0 && *inptr != 0; --c) *(unpackedCurr++) = *(inptr++); getChars = (c == 0); } if(getChars) { inptr = ((uint16_t*)(lfnEntries[i].we.wePart2)); for(c=sizeof(lfnEntries[i].we.wePart2)/sizeof(uint16_t); c > 0 && *inptr != 0; --c) *(unpackedCurr++) = *(inptr++); getChars = (c == 0); } if(getChars) { inptr = ((uint16_t*)(lfnEntries[i].we.wePart3)); for(c=sizeof(lfnEntries[i].we.wePart3)/sizeof(uint16_t); c > 0 && *inptr != 0; --c) *(unpackedCurr++) = *(inptr++); getChars = (c == 0); } // If this is marked as the last entry, stop looking for more. if( (lfnEntries[i].we.weCnt & WIN_LAST) != 0 ) return (unpackedCurr - unpackedLFN); } return -1; } static bool IsLFNMatch(uint16_t *fileName, uint32_t len, union doswinentry lfnEntries[]) { uint16_t unpackedLFN[13 * 63]; int32_t unpackedLen = UnpackLFN(lfnEntries, unpackedLFN); if(unpackedLen < 0) return false; // Ok, we have a buffer now. return FastUnicodeCompare(fileName, len, unpackedLFN, unpackedLen ) == 0; } struct ReadDir_ctx; typedef int32_t (*ReadDir_getNextEntry_t)(struct ReadDir_ctx *ctx, uint32_t *entryOffs); // TODO: Read in the sectors within the cluster and iterate through the entries. // There are two uses for this function: // 1) Read the next entry and return information about it. // 2) Find an entry matching a filename // 2 can be implemented in terms of 1 so it's probably saner to implement 1 struct ReadDir_ctx { // Current sector being read uint32_t currSec; // End sector uint32_t endSec; // Offset of the first dir entry within currSec // e.g. the first time through, secBaseOffset = 0 and it remains so until // moving on to the next sector at which point it will be gBytesPerSector // This allows us to take an entry offset relative to the start of the directory // and figure out the offset relative to the start of the current sector. uint32_t secBaseOffset; // End entry offset uint32_t endEntryOffset; void *dirbuf; // Which sector is in the buffer. uint32_t bufferSec; // Space to stow up to 64 directory entries // which is 1 real dir entry and 63 potential LFN entries. union doswinentry *lfnEntries; }; struct ReadClusteredDir_ctx { struct ReadDir_ctx baseCtx; // Current cluster, 0 for traditional root dir uint32_t dirCluster; }; static long ReadDir_read(struct ReadDir_ctx *ctx) { CICell ih = gCurrentIH; Seek(ih, ctx->currSec * gBytesPerSector) long ret = Read(ih, (long)ctx->dirbuf, gBytesPerSector); if(ret < 0) ctx->bufferSec = 0; else ctx->bufferSec = ctx->currSec; return ret; } static long ReadDir_open(struct ReadDir_ctx *ctx, uint32_t firstSec, uint32_t endSec, uint32_t endEntryOffset) { ctx->currSec = firstSec; ctx->endSec = endSec; ctx->secBaseOffset = 0; ctx->endEntryOffset = endEntryOffset; ctx->dirbuf = malloc(gBytesPerSector); if(ctx->dirbuf == NULL) return -1; ctx->lfnEntries = malloc(sizeof(*ctx->lfnEntries) * 64); if(ctx->lfnEntries == NULL) goto error; bzero(ctx->lfnEntries, sizeof(*ctx->lfnEntries) * 64); ctx->bufferSec = 0; return 0; error: free(ctx->dirbuf); return -1; } static void ReadDir_close(struct ReadDir_ctx *ctx) { free(ctx->dirbuf); ctx->dirbuf = NULL; } static long ReadClusteredDir_open(struct ReadClusteredDir_ctx *ctx, uint32_t dirCluster) { ctx->dirCluster = dirCluster; return ReadDir_open( &ctx->baseCtx, (dirCluster - 2) * BPB_SecPerClus + gDataLBA, (dirCluster - 2 + 1) * BPB_SecPerClus + gDataLBA, BPB_SecPerClus * gBytesPerSector); } static long ReadRootDir_open(struct ReadDir_ctx *ctx) { return ReadDir_open(ctx, gRootDirLBA, gDataLBA, sizeof(struct direntry) * gRootEntries); } // Works like postfix operator ++ in that *pOffs will be the offset of the // _next_ directory entry // However, the return value will always indicate the distance to the end of // the buffer. This distance will be negative. // Rationale: // You want to know if *pOffs < bufferLength // So you can do *pOffs - bufferLength < 0 // So we return *pOffs - bufferLength allowing you to test if the value is < 0 static int32_t ReadDir_getNextEntryInBuffer(struct ReadDir_ctx *ctx, uint32_t *pOffs, uint32_t bufferLength) { union doswinentry const *curr = ctx->dirbuf + *pOffs; for(; *pOffs < bufferLength; *pOffs += sizeof(struct direntry), ++curr) { // 0xE5 is a deleted file, skip this entry and continue processing. if(curr->de.deName[0] == 0xE5) continue; // 0x00 is an uninitialized entry which is also used as an EOF marker. if(curr->de.deName[0] == 0x00) {} // nothing to do // Check if it's an LFN part. else if( (curr->de.deAttributes & (ATTR_WIN95 | ATTR_DIRECTORY | ATTR_ARCHIVE)) == ATTR_WIN95 ) { // According to MS we need to ignore entries where these values are non-zero. if(curr->we.weReserved1 == 0 && curr->we.weReserved2 == 0) { // Stow the current entry into the buffer. Validity will be checked later. int i = (curr->we.weCnt & WIN_CNT); memcpy(&ctx->lfnEntries[i], curr, sizeof(ctx->lfnEntries[i])); } continue; } // Either it's a directory entry or it's the EOF marker. Either way we need // to copy this into the buffer so the caller can determine this. memcpy(&ctx->lfnEntries[0], curr, sizeof(ctx->lfnEntries[0])); return (*pOffs += sizeof(struct direntry)) - sizeof(struct direntry) - bufferLength; } // End of buffer was hit. // Should always be 0 return *pOffs - bufferLength; } static int32_t ReadDir_getNextEntry(struct ReadDir_ctx *ctx, uint32_t *pEntryOffset) { // Calculate the requested offset relative to the current sector. uint32_t secRelOffset = *pEntryOffset - ctx->secBaseOffset; while(ctx->currSec < ctx->endSec) { if(ctx->currSec != ctx->bufferSec) { if(ReadDir_read(ctx) < 0) break; // FIXME: Is this right anymore?? // If we fail to read the sector then we'll wind up returning // >=0 which is ordinarily something we only do when we've finished // reading all of the sectors. // The distinguishing feature is that ctx->currSec < ctx->endSec ctx->bufferSec = ctx->currSec; } // Is offset within the current sector? if(secRelOffset < gBytesPerSector) { // Read entries from the sector int32_t ret = ReadDir_getNextEntryInBuffer(ctx, &secRelOffset, gBytesPerSector); // Update the working offset. Note that it points it to the NEXT entry to read. // That is, where to start the search when called again. *pEntryOffset = secRelOffset + ctx->secBaseOffset; // Return now with entry filled into buffer if an entry was found or if the // 0x00 entry was found. We know this because the return value is how many // bytes are remaining to process (0 == end, < 0 is more bytes) if(ret < 0) return ret; } // At this point we can be certain that secRelOffset >= gBytesPerSector // Because if it wasn't when we entered then ReadDir_getNextEntryInBuffer made it so // or if it didn't we returned because it found something. // Advance through sectors // This loop should ALWAYS run once. while( secRelOffset >= gBytesPerSector && ctx->currSec < ctx->endSec ) { // Add BPS to secBaseOffset and subtract BPS from secRelOffset // Advance the sector # // The effect of this is that *pEntryOffset does not change but the // iterator is updated to advance to the next sector. secRelOffset -= gBytesPerSector; ctx->secBaseOffset += gBytesPerSector; ++(ctx->currSec); } // 0 <= secRelOffset < gBytesPerSector // UNLESS we ran out of sectors in which case it will be // >= gBytesPerSector } // Done iterating over all the sectors (possibly within the cluster) // This will always be non-negative. // In the normal case of wanting to iterate one by one it will be zero. // If a larger number is used then it will be > 0 which indicates // how many more bytes have to be skipped before reaching *pEntryOffset. return secRelOffset; } static int32_t ReadClusteredDir_getNextEntry(struct ReadClusteredDir_ctx *ctx, uint32_t *pEntryOffset) { // Upon entry we can assume that the fields have all been initialized while(ctx->dirCluster < gEOC) { if( ctx->dirCluster < 2) break; int32_t ret = ReadDir_getNextEntry(&ctx->baseCtx, pEntryOffset); // ret will always be < 0 if an entry was found (including EOF) if( ret < 0) return ret; // If not all sectors were exhausted it's because the read failed. // Return to caller if(ctx->baseCtx.currSec < ctx->baseCtx.endSec) return ret; // Move on to the next cluster. uint32_t nextCluster = NextCluster(ctx->dirCluster); if( ctx->dirCluster < 2) break; ctx->dirCluster = nextCluster; ctx->baseCtx.currSec = (ctx->dirCluster - 2) * BPB_SecPerClus + gDataLBA; ctx->baseCtx.endSec = (ctx->dirCluster - 2 + 1) * BPB_SecPerClus + gDataLBA; } return *pEntryOffset - ctx->baseCtx.secBaseOffset; } static long ResolvePathToDirEntry( uint32_t parentDirCluster, char const *filePath, struct direntry *de ) { long ret = -1; uint32_t dirCluster = parentDirCluster; char const *nextComponent = filePath; struct ReadClusteredDir_ctx ctx; while( *nextComponent != '\0' ) { char fileName[1024]; // Split the path to fnBuf/nextComponent char *currOut; for(currOut = fileName; *nextComponent != '\0' && *nextComponent != '/'; ++nextComponent, ++currOut) { *currOut = *nextComponent; } *currOut = '\0'; if(*nextComponent == '/') ++nextComponent; // fileName has a NUL terminated component // nextComponent points to the next filename component // Setup the SFN we are looking for uint8_t sfn[11]; sfn_for_fn(sfn, fileName); if(sfn[0] == 0xe5) sfn[0] = 0x05; // Decode fileName into UCS-2 uint16_t lfnLength; uint16_t lfnBuf[512]; utf_decodestr((uint8_t*)fileName, lfnBuf, &lfnLength, 512, OSLittleEndian); ReadDir_getNextEntry_t getNextEntry; if(dirCluster == 0) { if(ReadRootDir_open(&ctx.baseCtx) < 0) return -1; getNextEntry = &ReadDir_getNextEntry; } else { if(ReadClusteredDir_open(&ctx, dirCluster) < 0) return -1; getNextEntry = (ReadDir_getNextEntry_t)&ReadClusteredDir_getNextEntry; } uint32_t entryOffs = 0; int32_t gneRet; struct direntry const *found; while( (gneRet = (*getNextEntry)(&ctx.baseCtx, &entryOffs)) < 0) { found = &ctx.baseCtx.lfnEntries[0].de; if(ctx.baseCtx.lfnEntries[0].de.deName[0] == '\0') { gneRet = 0; break; } if(ctx.baseCtx.lfnEntries[0].de.deAttributes & ATTR_VOLUME) continue; // Now compare filenames int i; for(i=0; i < 11; ++i) if(found->deName[i] != sfn[i]) break; if(i == 11) break; // SFN didn't match, but maybe an LFN will. if(IsLFNMatch(lfnBuf, lfnLength, ctx.baseCtx.lfnEntries)) break; } if(gneRet >= 0) break; // Out of loop over path components. // Are we looking for the last path component? if( *nextComponent == '\0' ) { memcpy(de, found, sizeof(*de)); ret = 0; break; } // The one we found must be a directory. if( (found->deAttributes & ATTR_DIRECTORY) == 0 ) { found = NULL; break; // out of loop over path components } // We found a directory. dirCluster = (((uint32_t)*(uint16_t*)found->deHighClust << 16) + *(uint16_t*)found->deStartCluster) & gClusterMask; // Handle "." in root dir or .. in subdir of root which is cluster 0 even on FAT32 if(dirCluster == 0) dirCluster = gRootDirCluster; // We're now setup to look through the subdir. ReadDir_close(&ctx.baseCtx); } ReadDir_close(&ctx.baseCtx); return ret; } long MSDOSReadFile(CICell ih, char /*const*/ *filePath, void *base, unsigned long offset, unsigned long length) { long ret; if ((ret = MSDOSInitPartition(ih)) < 0) return ret; // Strip leading '/' while (filePath[0] == '/') ++filePath; struct direntry de; if( ResolvePathToDirEntry(gRootDirCluster, filePath, &de) < 0 ) return -1; if( (de.deAttributes & ATTR_DIRECTORY) != 0 ) { return -1; } else { uint32_t fileCluster = ((uint32_t)*(uint16_t*)de.deHighClust << 16) + *(uint16_t*)de.deStartCluster; fileCluster &= gClusterMask; if(length == 0) length = *(uint32_t*)de.deFileSize; ret = length; uint32_t BytesPerCluster = BPB_SecPerClus * gBytesPerSector; for(; length > 0 && fileCluster < gEOC; fileCluster = NextCluster(fileCluster) ) { if(fileCluster < 2) { ret = -1; break; } // Is the desired offset within this cluster? if( offset < BytesPerCluster ) { uint32_t const clusSec = (fileCluster - 2) * BPB_SecPerClus + gDataLBA; Seek(ih, clusSec * gBytesPerSector + offset); uint32_t readLength = BytesPerCluster - offset; readLength = (readLength < length)?readLength:length; Read(ih, (long)base, readLength); // We want to read the next cluster from offset 0 offset = 0; length -= readLength; base += readLength; } else // Skip reading this cluster, and reduce offset by // the number of bytes in the cluster. offset -= BytesPerCluster; } } return ret; } long MSDOSLoadFile(CICell ih, char /*const*/ *filePath) { return MSDOSReadFile(ih, filePath, (void *)gFSLoadAddress, 0, 0); } long MSDOSGetDirEntry(CICell ih, char * dirPath, long * dirIndex, char ** name, long * flags, long * time, FinderInfo * finderInfo, long * infoValid) { long ret; if ((ret = MSDOSInitPartition(ih)) < 0) return ret; if (infoValid != NULL) *infoValid = false; while (dirPath[0] == '/') ++dirPath; uint32_t dirCluster; if(dirPath[0] != '\0') { struct direntry de; if( ResolvePathToDirEntry(gRootDirCluster, dirPath, &de) < 0 ) return -1; dirCluster = ( ((uint32_t)*(uint16_t*)de.deHighClust << 16) + *(uint16_t*)de.deStartCluster) & gClusterMask; if(dirCluster < 2) return -1; } else dirCluster = gRootDirCluster; struct ReadClusteredDir_ctx ctx; ReadDir_getNextEntry_t getNextEntry; if(dirCluster == 0) { if(ReadRootDir_open(&ctx.baseCtx) < 0) return -1; getNextEntry = &ReadDir_getNextEntry; } else { if(ReadClusteredDir_open(&ctx, dirCluster) < 0) return -1; getNextEntry = (ReadDir_getNextEntry_t)&ReadClusteredDir_getNextEntry; } uint32_t entryOffs = *dirIndex; while((*getNextEntry)(&ctx.baseCtx, &entryOffs) < 0) { *dirIndex = entryOffs; if(ctx.baseCtx.lfnEntries[0].de.deName[0] == '\0') break; if(ctx.baseCtx.lfnEntries[0].de.deAttributes & ATTR_VOLUME) continue; struct direntry const *de = &(ctx.baseCtx.lfnEntries[0].de); // Fill in name, flags, time if(name != NULL) { uint16_t unpackedLFN[13 * 63]; int32_t unpackedLen = UnpackLFN(ctx.baseCtx.lfnEntries, unpackedLFN); if(unpackedLen > 0) { utf_encodestr(unpackedLFN, unpackedLen, (uint8_t*)gTempStr, 256, OSLittleEndian); } else { sfn_to_fn(de->deName, gTempStr); } *name = gTempStr; } if(flags != NULL) { *flags = kPermMask; if(de->deAttributes & ATTR_READONLY) *flags &= ~(kPermOtherWrite | kPermGroupWrite | kPermOwnerWrite); if(de->deAttributes & ATTR_DIRECTORY) *flags |= kFileTypeDirectory; else { // TODO: We could handle symlinks by using the msdosfs method. *flags |= kFileTypeFlat; *flags &= ~(kPermOtherExecute | kPermGroupExecute | kPermOwnerExecute); } } if(time != NULL) { *time = dos2unixseconds(*(uint16_t*)de->deCDate, *(uint16_t*)de->deCTime, de->deCHundredth); } ReadDir_close(&ctx.baseCtx); return 0; } ReadDir_close(&ctx.baseCtx); *dirIndex = entryOffs; return -1; } long MSDOSGetFileBlock(CICell ih, char *filePath, unsigned long long *firstBlock) { return -1; } long MSDOSGetUUID(CICell ih, char *uuidStr) { return -1; }