/* $Id: fatvfs.cpp $ */ /** @file * IPRT - FAT Virtual Filesystem. */ /* * Copyright (C) 2017-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * 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, in version 3 of the * License. * * 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, see . * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included * in the VirtualBox distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_FS #include "internal/iprt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "internal/fs.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** * Gets the cluster from a directory entry. * * @param a_pDirEntry Pointer to the directory entry. * @param a_pVol Pointer to the volume. */ #define RTFSFAT_GET_CLUSTER(a_pDirEntry, a_pVol) \ ( (a_pVol)->enmFatType >= RTFSFATTYPE_FAT32 \ ? RT_MAKE_U32((a_pDirEntry)->idxCluster, (a_pDirEntry)->u.idxClusterHigh) \ : (a_pDirEntry)->idxCluster ) /** * Rotates a unsigned 8-bit value one bit to the right. * * @returns Rotated 8-bit value. * @param a_bValue The value to rotate. */ #define RTFSFAT_ROT_R1_U8(a_bValue) (((a_bValue) >> 1) | (uint8_t)((a_bValue) << 7)) /** Maximum number of characters we will create in a long file name. */ #define RTFSFAT_MAX_LFN_CHARS 255 /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** Pointer to a FAT directory instance. */ typedef struct RTFSFATDIRSHRD *PRTFSFATDIRSHRD; /** Pointer to a FAT volume (VFS instance data). */ typedef struct RTFSFATVOL *PRTFSFATVOL; /** The number of entire in a chain part. */ #define RTFSFATCHAINPART_ENTRIES (256U - 4U) /** * A part of the cluster chain covering up to 252 clusters. */ typedef struct RTFSFATCHAINPART { /** List entry. */ RTLISTNODE ListEntry; /** Chain entries. */ uint32_t aEntries[RTFSFATCHAINPART_ENTRIES]; } RTFSFATCHAINPART; AssertCompile(sizeof(RTFSFATCHAINPART) <= _1K); typedef RTFSFATCHAINPART *PRTFSFATCHAINPART; typedef RTFSFATCHAINPART const *PCRTFSFATCHAINPART; /** * A FAT cluster chain. */ typedef struct RTFSFATCHAIN { /** The chain size in bytes. */ uint32_t cbChain; /** The chain size in entries. */ uint32_t cClusters; /** The cluster size. */ uint32_t cbCluster; /** The shift count for converting between clusters and bytes. */ uint8_t cClusterByteShift; /** List of chain parts (RTFSFATCHAINPART). */ RTLISTANCHOR ListParts; } RTFSFATCHAIN; /** Pointer to a FAT chain. */ typedef RTFSFATCHAIN *PRTFSFATCHAIN; /** Pointer to a const FAT chain. */ typedef RTFSFATCHAIN const *PCRTFSFATCHAIN; /** * FAT file system object (common part to files and dirs (shared)). */ typedef struct RTFSFATOBJ { /** The parent directory keeps a list of open objects (RTFSFATOBJ). */ RTLISTNODE Entry; /** Reference counter. */ uint32_t volatile cRefs; /** The parent directory (not released till all children are close). */ PRTFSFATDIRSHRD pParentDir; /** The byte offset of the directory entry in the parent dir. * This is set to UINT32_MAX for the root directory. */ uint32_t offEntryInDir; /** Attributes. */ RTFMODE fAttrib; /** The object size. */ uint32_t cbObject; /** The access time. */ RTTIMESPEC AccessTime; /** The modificaton time. */ RTTIMESPEC ModificationTime; /** The birth time. */ RTTIMESPEC BirthTime; /** Cluster chain. */ RTFSFATCHAIN Clusters; /** Pointer to the volume. */ struct RTFSFATVOL *pVol; /** Set if we've maybe dirtied the FAT. */ bool fMaybeDirtyFat; /** Set if we've maybe dirtied the directory entry. */ bool fMaybeDirtyDirEnt; } RTFSFATOBJ; /** Poitner to a FAT file system object. */ typedef RTFSFATOBJ *PRTFSFATOBJ; /** * Shared FAT file data. */ typedef struct RTFSFATFILESHRD { /** Core FAT object info. */ RTFSFATOBJ Core; } RTFSFATFILESHRD; /** Pointer to shared FAT file data. */ typedef RTFSFATFILESHRD *PRTFSFATFILESHRD; /** * Per handle data for a FAT file. */ typedef struct RTFSFATFILE { /** Pointer to the shared data. */ PRTFSFATFILESHRD pShared; /** The current file offset. */ uint32_t offFile; } RTFSFATFILE; /** Pointer to the per handle data of a FAT file. */ typedef RTFSFATFILE *PRTFSFATFILE; /** * FAT shared directory structure. * * We work directories in one of two buffering modes. If there are few entries * or if it's the FAT12/16 root directory, we map the whole thing into memory. * If it's too large, we use an inefficient sector buffer for now. * * Directory entry updates happens exclusively via the directory, so any open * files or subdirs have a parent reference for doing that. The parent OTOH, * keeps a list of open children. */ typedef struct RTFSFATDIRSHRD { /** Core FAT object info. */ RTFSFATOBJ Core; /** Open child objects (RTFSFATOBJ). */ RTLISTNODE OpenChildren; /** Number of directory entries. */ uint32_t cEntries; /** If fully buffered. */ bool fFullyBuffered; /** Set if this is a linear root directory. */ bool fIsLinearRootDir; /** The size of the memory paEntries points at. */ uint32_t cbAllocatedForEntries; /** Pointer to the directory buffer. * In fully buffering mode, this is the whole of the directory. Otherwise it's * just a sector worth of buffers. */ PFATDIRENTRYUNION paEntries; /** The disk offset corresponding to what paEntries points to. * UINT64_MAX if notthing read into paEntries yet. */ uint64_t offEntriesOnDisk; union { /** Data for the full buffered mode. * No need to messing around with clusters here, as we only uses this for * directories with a contiguous mapping on the disk. * So, if we grow a directory in a non-contiguous manner, we have to switch * to sector buffering on the fly. */ struct { /** Number of sectors mapped by paEntries and pbDirtySectors. */ uint32_t cSectors; /** Number of dirty sectors. */ uint32_t cDirtySectors; /** Dirty sector bitmap (one bit per sector). */ uint8_t *pbDirtySectors; } Full; /** The simple sector buffering. * This only works for clusters, so no FAT12/16 root directory fun. */ struct { /** The directory offset, UINT32_MAX if invalid. */ uint32_t offInDir; /** Dirty flag. */ bool fDirty; } Simple; } u; } RTFSFATDIRSHRD; /** Pointer to a shared FAT directory instance. */ typedef RTFSFATDIRSHRD *PRTFSFATDIRSHRD; /** * The per handle FAT directory data. */ typedef struct RTFSFATDIR { /** Core FAT object info. */ PRTFSFATDIRSHRD pShared; /** The current directory offset. */ uint32_t offDir; } RTFSFATDIR; /** Pointer to a per handle FAT directory data. */ typedef RTFSFATDIR *PRTFSFATDIR; /** * File allocation table cache entry. */ typedef struct RTFSFATCLUSTERMAPENTRY { /** The byte offset into the fat, UINT32_MAX if invalid entry. */ uint32_t offFat; /** Pointer to the data. */ uint8_t *pbData; /** Dirty bitmap. Indexed by byte offset right shifted by * RTFSFATCLUSTERMAPCACHE::cDirtyShift. */ uint64_t bmDirty; } RTFSFATCLUSTERMAPENTRY; /** Pointer to a file allocation table cache entry. */ typedef RTFSFATCLUSTERMAPENTRY *PRTFSFATCLUSTERMAPENTRY; /** * File allocation table cache. */ typedef struct RTFSFATCLUSTERMAPCACHE { /** Number of cache entries (power of two). */ uint32_t cEntries; /** This shift count to use in the first step of the index calculation. */ uint32_t cEntryIndexShift; /** The AND mask to use in the second step of the index calculation. */ uint32_t fEntryIndexMask; /** The max size of data in a cache entry (power of two). */ uint32_t cbEntry; /** The AND mask to use to get the entry offset. */ uint32_t fEntryOffsetMask; /** Dirty bitmap shift count. */ uint32_t cDirtyShift; /** The dirty cache line size (multiple of two). */ uint32_t cbDirtyLine; /** The FAT size. */ uint32_t cbFat; /** The Number of clusters in the FAT. */ uint32_t cClusters; /** Cluster allocation search hint. */ uint32_t idxAllocHint; /** Pointer to the volume (for disk access). */ PRTFSFATVOL pVol; /** The cache name. */ const char *pszName; /** Cache entries. */ RT_FLEXIBLE_ARRAY_EXTENSION RTFSFATCLUSTERMAPENTRY aEntries[RT_FLEXIBLE_ARRAY]; } RTFSFATCLUSTERMAPCACHE; /** Pointer to a FAT linear metadata cache. */ typedef RTFSFATCLUSTERMAPCACHE *PRTFSFATCLUSTERMAPCACHE; /** * BPB version. */ typedef enum RTFSFATBPBVER { RTFSFATBPBVER_INVALID = 0, RTFSFATBPBVER_NO_BPB, RTFSFATBPBVER_DOS_2_0, //RTFSFATBPBVER_DOS_3_2, - we don't try identify this one. RTFSFATBPBVER_DOS_3_31, RTFSFATBPBVER_EXT_28, RTFSFATBPBVER_EXT_29, RTFSFATBPBVER_FAT32_28, RTFSFATBPBVER_FAT32_29, RTFSFATBPBVER_END } RTFSFATBPBVER; /** * A FAT volume. */ typedef struct RTFSFATVOL { /** Handle to itself. */ RTVFS hVfsSelf; /** The file, partition, or whatever backing the FAT volume. */ RTVFSFILE hVfsBacking; /** The size of the backing thingy. */ uint64_t cbBacking; /** Byte offset of the bootsector relative to the start of the file. */ uint64_t offBootSector; /** The UTC offset in nanoseconds to use for this file system (FAT traditionally * stores timestamps in local time). * @remarks This may need improving later. */ int64_t offNanoUTC; /** The UTC offset in minutes to use for this file system (FAT traditionally * stores timestamps in local time). * @remarks This may need improving later. */ int32_t offMinUTC; /** Set if read-only mode. */ bool fReadOnly; /** Media byte. */ uint8_t bMedia; /** Reserved sectors. */ uint32_t cReservedSectors; /** The BPB version. Gives us an idea of the FAT file system version. */ RTFSFATBPBVER enmBpbVersion; /** Logical sector size. */ uint32_t cbSector; /** The shift count for converting between sectors and bytes. */ uint8_t cSectorByteShift; /** The shift count for converting between clusters and bytes. */ uint8_t cClusterByteShift; /** The cluster size in bytes. */ uint32_t cbCluster; /** The number of data clusters, including the two reserved ones. */ uint32_t cClusters; /** The offset of the first cluster. */ uint64_t offFirstCluster; /** The total size from the BPB, in bytes. */ uint64_t cbTotalSize; /** The FAT type. */ RTFSFATTYPE enmFatType; /** Number of FAT entries (clusters). */ uint32_t cFatEntries; /** The size of a FAT, in bytes. */ uint32_t cbFat; /** Number of FATs. */ uint32_t cFats; /** The end of chain marker used by the formatter (FAT entry \#2). */ uint32_t idxEndOfChain; /** The maximum last cluster supported by the FAT format. */ uint32_t idxMaxLastCluster; /** FAT byte offsets. */ uint64_t aoffFats[8]; /** Pointer to the FAT (cluster map) cache. */ PRTFSFATCLUSTERMAPCACHE pFatCache; /** The root directory byte offset. */ uint64_t offRootDir; /** Root directory cluster, UINT32_MAX if not FAT32. */ uint32_t idxRootDirCluster; /** Number of root directory entries, if fixed. UINT32_MAX for FAT32. */ uint32_t cRootDirEntries; /** The size of the root directory, rounded up to the nearest sector size. */ uint32_t cbRootDir; /** The root directory data (shared). */ PRTFSFATDIRSHRD pRootDir; /** Serial number. */ uint32_t uSerialNo; /** The stripped volume label, if included in EBPB. */ char szLabel[12]; /** The file system type from the EBPB (also stripped). */ char szType[9]; /** Number of FAT32 boot sector copies. */ uint8_t cBootSectorCopies; /** FAT32 flags. */ uint16_t fFat32Flags; /** Offset of the FAT32 boot sector copies, UINT64_MAX if none. */ uint64_t offBootSectorCopies; /** The FAT32 info sector byte offset, UINT64_MAX if not present. */ uint64_t offFat32InfoSector; /** The FAT32 info sector if offFat32InfoSector isn't UINT64_MAX. */ FAT32INFOSECTOR Fat32InfoSector; } RTFSFATVOL; /** Pointer to a const FAT volume (VFS instance data). */ typedef RTFSFATVOL const *PCRTFSFATVOL; /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** * Codepage 437 translation table with invalid 8.3 characters marked as 0xffff or 0xfffe. * * The 0xfffe notation is used for characters that are valid in long file names but not short. * * @remarks The valid first 128 entries are 1:1 with unicode. * @remarks Lower case characters are all marked invalid. */ static RTUTF16 g_awchFatCp437ValidChars[] = { /* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f */ 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0xffff, 0xfffe, 0xfffe, 0x002d, 0xfffe, 0xffff, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0xffff, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xffff, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0xfffe, 0xffff, 0xfffe, 0x005e, 0x005f, 0x0060, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xfffe, 0xffff, 0xffff, 0xffff, 0x007e, 0xffff, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 }; AssertCompileSize(g_awchFatCp437ValidChars, 256*2); /** * Codepage 437 translation table without invalid 8.3. character markings. */ static RTUTF16 g_awchFatCp437Chars[] = { /* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f */ 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c, 0x25ba, 0x25c4, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302, 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 }; AssertCompileSize(g_awchFatCp437Chars, 256*2); /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static PRTFSFATOBJ rtFsFatDirShrd_LookupShared(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir); static void rtFsFatDirShrd_AddOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild); static void rtFsFatDirShrd_RemoveOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild); static int rtFsFatDirShrd_GetEntryForUpdate(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir, PFATDIRENTRY *ppDirEntry, uint32_t *puWriteLock); static int rtFsFatDirShrd_PutEntryAfterUpdate(PRTFSFATDIRSHRD pThis, PFATDIRENTRY pDirEntry, uint32_t uWriteLock); static int rtFsFatDirShrd_Flush(PRTFSFATDIRSHRD pThis); static int rtFsFatDir_NewWithShared(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pShared, PRTVFSDIR phVfsDir); static int rtFsFatDir_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir, uint32_t idxCluster, uint64_t offDisk, uint32_t cbDir, PRTVFSDIR phVfsDir); /** * Convers a cluster to a disk offset. * * @returns Disk byte offset, UINT64_MAX on invalid cluster. * @param pThis The FAT volume instance. * @param idxCluster The cluster number. */ DECLINLINE(uint64_t) rtFsFatClusterToDiskOffset(PRTFSFATVOL pThis, uint32_t idxCluster) { AssertReturn(idxCluster >= FAT_FIRST_DATA_CLUSTER, UINT64_MAX); AssertReturn(idxCluster < pThis->cClusters, UINT64_MAX); return (idxCluster - FAT_FIRST_DATA_CLUSTER) * (uint64_t)pThis->cbCluster + pThis->offFirstCluster; } #ifdef RT_STRICT /** * Assert chain consistency. */ static bool rtFsFatChain_AssertValid(PCRTFSFATCHAIN pChain) { bool fRc = true; uint32_t cParts = 0; PRTFSFATCHAINPART pPart; RTListForEach(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry) cParts++; uint32_t cExpected = (pChain->cClusters + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES; AssertMsgStmt(cExpected == cParts, ("cExpected=%#x cParts=%#x\n", cExpected, cParts), fRc = false); AssertMsgStmt(pChain->cbChain == (pChain->cClusters << pChain->cClusterByteShift), ("cExpected=%#x cParts=%#x\n", cExpected, cParts), fRc = false); return fRc; } #endif /* RT_STRICT */ /** * Initializes an empty cluster chain. * * @param pChain The chain. * @param pVol The volume. */ static void rtFsFatChain_InitEmpty(PRTFSFATCHAIN pChain, PRTFSFATVOL pVol) { pChain->cbCluster = pVol->cbCluster; pChain->cClusterByteShift = pVol->cClusterByteShift; pChain->cbChain = 0; pChain->cClusters = 0; RTListInit(&pChain->ListParts); } /** * Deletes a chain, freeing it's resources. * * @param pChain The chain. */ static void rtFsFatChain_Delete(PRTFSFATCHAIN pChain) { Assert(RT_IS_POWER_OF_TWO(pChain->cbCluster)); Assert(RT_BIT_32(pChain->cClusterByteShift) == pChain->cbCluster); PRTFSFATCHAINPART pPart = RTListRemoveLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry); while (pPart) { RTMemFree(pPart); pPart = RTListRemoveLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry); } pChain->cbChain = 0; pChain->cClusters = 0; } /** * Appends a cluster to a cluster chain. * * @returns IPRT status code. * @param pChain The chain. * @param idxCluster The cluster to append. */ static int rtFsFatChain_Append(PRTFSFATCHAIN pChain, uint32_t idxCluster) { PRTFSFATCHAINPART pPart; uint32_t idxLast = pChain->cClusters % RTFSFATCHAINPART_ENTRIES; if (idxLast != 0) pPart = RTListGetLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry); else { pPart = (PRTFSFATCHAINPART)RTMemAllocZ(sizeof(*pPart)); if (!pPart) return VERR_NO_MEMORY; RTListAppend(&pChain->ListParts, &pPart->ListEntry); } pPart->aEntries[idxLast] = idxCluster; pChain->cClusters++; pChain->cbChain += pChain->cbCluster; return VINF_SUCCESS; } /** * Reduces the number of clusters in the chain to @a cClusters. * * @param pChain The chain. * @param cClustersNew The new cluster count. Must be equal or smaller to * the current number of clusters. */ static void rtFsFatChain_Shrink(PRTFSFATCHAIN pChain, uint32_t cClustersNew) { uint32_t cOldParts = (pChain->cClusters + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES; uint32_t cNewParts = (cClustersNew + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES; Assert(cOldParts >= cNewParts); while (cOldParts-- > cNewParts) RTMemFree(RTListRemoveLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry)); pChain->cClusters = cClustersNew; pChain->cbChain = cClustersNew << pChain->cClusterByteShift; Assert(rtFsFatChain_AssertValid(pChain)); } /** * Converts a file offset to a disk offset. * * The disk offset is only valid until the end of the cluster it is within. * * @returns Disk offset. UINT64_MAX if invalid file offset. * @param pChain The chain. * @param offFile The file offset. * @param pVol The volume. */ static uint64_t rtFsFatChain_FileOffsetToDiskOff(PCRTFSFATCHAIN pChain, uint32_t offFile, PCRTFSFATVOL pVol) { uint32_t idxCluster = offFile >> pChain->cClusterByteShift; if (idxCluster < pChain->cClusters) { PRTFSFATCHAINPART pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry); while (idxCluster >= RTFSFATCHAINPART_ENTRIES) { idxCluster -= RTFSFATCHAINPART_ENTRIES; pPart = RTListGetNext(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry); } return pVol->offFirstCluster + ((uint64_t)(pPart->aEntries[idxCluster] - FAT_FIRST_DATA_CLUSTER) << pChain->cClusterByteShift) + (offFile & (pChain->cbCluster - 1)); } return UINT64_MAX; } /** * Checks if the cluster chain is contiguous on the disk. * * @returns true / false. * @param pChain The chain. */ static bool rtFsFatChain_IsContiguous(PCRTFSFATCHAIN pChain) { if (pChain->cClusters <= 1) return true; PRTFSFATCHAINPART pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry); uint32_t idxNext = pPart->aEntries[0]; uint32_t cLeft = pChain->cClusters; for (;;) { uint32_t const cInPart = RT_MIN(cLeft, RTFSFATCHAINPART_ENTRIES); for (uint32_t iPart = 0; iPart < cInPart; iPart++) if (pPart->aEntries[iPart] == idxNext) idxNext++; else return false; cLeft -= cInPart; if (!cLeft) return true; pPart = RTListGetNext(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry); } } /** * Gets a cluster array index. * * This works the chain thing as an indexed array. * * @returns The cluster number, UINT32_MAX if out of bounds. * @param pChain The chain. * @param idx The index. */ static uint32_t rtFsFatChain_GetClusterByIndex(PCRTFSFATCHAIN pChain, uint32_t idx) { if (idx < pChain->cClusters) { /* * In the first part? */ PRTFSFATCHAINPART pPart; if (idx < RTFSFATCHAINPART_ENTRIES) { pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry); return pPart->aEntries[idx]; } /* * In the last part? */ uint32_t cParts = (pChain->cClusters + RTFSFATCHAINPART_ENTRIES - 1) / RTFSFATCHAINPART_ENTRIES; uint32_t idxPart = idx / RTFSFATCHAINPART_ENTRIES; uint32_t idxInPart = idx % RTFSFATCHAINPART_ENTRIES; if (idxPart + 1 == cParts) pPart = RTListGetLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry); else { /* * No, do linear search from the start, skipping the first part. */ pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry); while (idxPart-- > 0) pPart = RTListGetNext(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry); } return pPart->aEntries[idxInPart]; } return UINT32_MAX; } /** * Gets the first cluster. * * @returns The cluster number, UINT32_MAX if empty * @param pChain The chain. */ static uint32_t rtFsFatChain_GetFirstCluster(PCRTFSFATCHAIN pChain) { if (pChain->cClusters > 0) { PRTFSFATCHAINPART pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry); return pPart->aEntries[0]; } return UINT32_MAX; } /** * Gets the last cluster. * * @returns The cluster number, UINT32_MAX if empty * @param pChain The chain. */ static uint32_t rtFsFatChain_GetLastCluster(PCRTFSFATCHAIN pChain) { if (pChain->cClusters > 0) { PRTFSFATCHAINPART pPart = RTListGetLast(&pChain->ListParts, RTFSFATCHAINPART, ListEntry); return pPart->aEntries[(pChain->cClusters - 1) % RTFSFATCHAINPART_ENTRIES]; } return UINT32_MAX; } /** * Creates a cache for the file allocation table (cluster map). * * @returns Pointer to the cache. * @param pThis The FAT volume instance. * @param pbFirst512FatBytes The first 512 bytes of the first FAT. */ static int rtFsFatClusterMap_Create(PRTFSFATVOL pThis, uint8_t const *pbFirst512FatBytes, PRTERRINFO pErrInfo) { Assert(RT_ALIGN_32(pThis->cbFat, pThis->cbSector) == pThis->cbFat); Assert(pThis->cbFat != 0); /* * Figure the cache size. Keeping it _very_ simple for now as we just need * something that works, not anything the performs like crazy. * * Note! Lowering the max cache size below 128KB will break ASSUMPTIONS in the FAT16 * and eventually FAT12 code. */ uint32_t cEntries; uint32_t cEntryIndexShift; uint32_t fEntryIndexMask; uint32_t cbEntry = pThis->cbFat; uint32_t fEntryOffsetMask; if (cbEntry <= _512K) { cEntries = 1; cEntryIndexShift = 0; fEntryIndexMask = 0; fEntryOffsetMask = UINT32_MAX; } else { Assert(pThis->cbSector < _512K / 8); cEntries = 8; cEntryIndexShift = 9; fEntryIndexMask = cEntries - 1; AssertReturn(RT_IS_POWER_OF_TWO(cEntries), VERR_INTERNAL_ERROR_4); cbEntry = pThis->cbSector; fEntryOffsetMask = pThis->cbSector - 1; AssertReturn(RT_IS_POWER_OF_TWO(cbEntry), VERR_INTERNAL_ERROR_5); } /* * Allocate and initialize it all. */ PRTFSFATCLUSTERMAPCACHE pFatCache; pFatCache = (PRTFSFATCLUSTERMAPCACHE)RTMemAllocZ(RT_UOFFSETOF_DYN(RTFSFATCLUSTERMAPCACHE, aEntries[cEntries])); pThis->pFatCache = pFatCache; if (!pFatCache) return RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "Failed to allocate FAT cache"); pFatCache->cEntries = cEntries; pFatCache->fEntryIndexMask = fEntryIndexMask; pFatCache->cEntryIndexShift = cEntryIndexShift; pFatCache->cbEntry = cbEntry; pFatCache->fEntryOffsetMask = fEntryOffsetMask; pFatCache->pVol = pThis; pFatCache->cbFat = pThis->cbFat; pFatCache->cClusters = pThis->cClusters; unsigned i = cEntries; while (i-- > 0) { pFatCache->aEntries[i].pbData = (uint8_t *)RTMemAlloc(cbEntry); if (pFatCache->aEntries[i].pbData == NULL) { for (i++; i < cEntries; i++) RTMemFree(pFatCache->aEntries[i].pbData); RTMemFree(pFatCache); return RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "Failed to allocate FAT cache entry (%#x bytes)", cbEntry); } pFatCache->aEntries[i].offFat = UINT32_MAX; pFatCache->aEntries[i].bmDirty = 0; } Log3(("rtFsFatClusterMap_Create: cbFat=%#RX32 cEntries=%RU32 cEntryIndexShift=%RU32 fEntryIndexMask=%#RX32\n", pFatCache->cbFat, pFatCache->cEntries, pFatCache->cEntryIndexShift, pFatCache->fEntryIndexMask)); Log3(("rtFsFatClusterMap_Create: cbEntries=%#RX32 fEntryOffsetMask=%#RX32\n", pFatCache->cbEntry, pFatCache->fEntryOffsetMask)); /* * Calc the dirty shift factor. */ cbEntry /= 64; if (cbEntry < pThis->cbSector) cbEntry = pThis->cbSector; pFatCache->cDirtyShift = 1; pFatCache->cbDirtyLine = 1; while (pFatCache->cbDirtyLine < cbEntry) { pFatCache->cDirtyShift++; pFatCache->cbDirtyLine <<= 1; } Assert(pFatCache->cEntries == 1 || pFatCache->cbDirtyLine == pThis->cbSector); Log3(("rtFsFatClusterMap_Create: cbDirtyLine=%#RX32 cDirtyShift=%u\n", pFatCache->cbDirtyLine, pFatCache->cDirtyShift)); /* * Fill the cache if single entry or entry size is 512. */ if (pFatCache->cEntries == 1 || pFatCache->cbEntry == 512) { memcpy(pFatCache->aEntries[0].pbData, pbFirst512FatBytes, RT_MIN(512, pFatCache->cbEntry)); if (pFatCache->cbEntry > 512) { int rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->aoffFats[0] + 512, &pFatCache->aEntries[0].pbData[512], pFatCache->cbEntry - 512, NULL); if (RT_FAILURE(rc)) return RTErrInfoSet(pErrInfo, rc, "Error reading FAT into memory"); } pFatCache->aEntries[0].offFat = 0; pFatCache->aEntries[0].bmDirty = 0; } return VINF_SUCCESS; } /** * Worker for rtFsFatClusterMap_Flush and rtFsFatClusterMap_FlushEntry. * * @returns IPRT status code. On failure, we're currently kind of screwed. * @param pThis The FAT volume instance. * @param iFirstEntry Entry to start flushing at. * @param iLastEntry Last entry to flush. */ static int rtFsFatClusterMap_FlushWorker(PRTFSFATVOL pThis, uint32_t const iFirstEntry, uint32_t const iLastEntry) { PRTFSFATCLUSTERMAPCACHE pFatCache = pThis->pFatCache; Log3(("rtFsFatClusterMap_FlushWorker: %p %#x %#x\n", pThis, iFirstEntry, iLastEntry)); /* * Walk the cache entries, accumulating segments to flush. */ int rc = VINF_SUCCESS; uint64_t off = UINT64_MAX; uint64_t offEdge = UINT64_MAX; RTSGSEG aSgSegs[8]; RT_ZERO(aSgSegs); /* Initialization required for GCC >= 11. */ RTSGBUF SgBuf; RTSgBufInit(&SgBuf, aSgSegs, RT_ELEMENTS(aSgSegs)); SgBuf.cSegs = 0; /** @todo RTSgBuf API is stupid, make it smarter. */ for (uint32_t iFatCopy = 0; iFatCopy < pThis->cFats; iFatCopy++) { for (uint32_t iEntry = iFirstEntry; iEntry <= iLastEntry; iEntry++) { uint64_t bmDirty = pFatCache->aEntries[iEntry].bmDirty; if ( bmDirty != 0 && pFatCache->aEntries[iEntry].offFat != UINT32_MAX) { uint32_t offEntry = 0; uint64_t iDirtyLine = 1; while (offEntry < pFatCache->cbEntry) { if (pFatCache->aEntries[iEntry].bmDirty & iDirtyLine) { /* * Found dirty cache line. */ uint64_t offDirtyLine = pThis->aoffFats[iFatCopy] + pFatCache->aEntries[iEntry].offFat + offEntry; /* Can we simply extend the last segment? */ if ( offDirtyLine == offEdge && offEntry) { Assert(SgBuf.cSegs > 0); Assert( (uintptr_t)aSgSegs[SgBuf.cSegs - 1].pvSeg + aSgSegs[SgBuf.cSegs - 1].cbSeg == (uintptr_t)&pFatCache->aEntries[iEntry].pbData[offEntry]); aSgSegs[SgBuf.cSegs - 1].cbSeg += pFatCache->cbDirtyLine; offEdge += pFatCache->cbDirtyLine; } else { /* Starting new job? */ if (off == UINT64_MAX) { off = offDirtyLine; Assert(SgBuf.cSegs == 0); } /* flush if not adjacent or if we're out of segments. */ else if ( offDirtyLine != offEdge || SgBuf.cSegs >= RT_ELEMENTS(aSgSegs)) { int rc2 = RTVfsFileSgWrite(pThis->hVfsBacking, off, &SgBuf, true /*fBlocking*/, NULL); if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) rc = rc2; RTSgBufReset(&SgBuf); SgBuf.cSegs = 0; off = offDirtyLine; } /* Append segment. */ aSgSegs[SgBuf.cSegs].cbSeg = pFatCache->cbDirtyLine; aSgSegs[SgBuf.cSegs].pvSeg = &pFatCache->aEntries[iEntry].pbData[offEntry]; SgBuf.cSegs++; offEdge = offDirtyLine + pFatCache->cbDirtyLine; } bmDirty &= ~iDirtyLine; if (!bmDirty) break; } iDirtyLine <<= 1; offEntry += pFatCache->cbDirtyLine; } Assert(!bmDirty); } } } /* * Final flush job. */ if (SgBuf.cSegs > 0) { int rc2 = RTVfsFileSgWrite(pThis->hVfsBacking, off, &SgBuf, true /*fBlocking*/, NULL); if (RT_FAILURE(rc2) && RT_SUCCESS(rc)) rc = rc2; } /* * Clear the dirty flags on success. */ if (RT_SUCCESS(rc)) for (uint32_t iEntry = iFirstEntry; iEntry <= iLastEntry; iEntry++) pFatCache->aEntries[iEntry].bmDirty = 0; return rc; } /** * Flushes out all dirty lines in the entire file allocation table cache. * * @returns IPRT status code. On failure, we're currently kind of screwed. * @param pThis The FAT volume instance. */ static int rtFsFatClusterMap_Flush(PRTFSFATVOL pThis) { return rtFsFatClusterMap_FlushWorker(pThis, 0, pThis->pFatCache->cEntries - 1); } /** * Flushes out all dirty lines in the file allocation table (cluster map) cache * entry. * * This is typically called prior to reusing the cache entry. * * @returns IPRT status code. On failure, we're currently kind of screwed. * @param pFatCache The FAT cache * @param iEntry The cache entry to flush. */ static int rtFsFatClusterMap_FlushEntry(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t iEntry) { return rtFsFatClusterMap_FlushWorker(pFatCache->pVol, iEntry, iEntry); } /** * Gets a pointer to a FAT entry. * * @returns IPRT status code. On failure, we're currently kind of screwed. * @param pFatCache The FAT cache. * @param offFat The FAT byte offset to get the entry off. * @param ppbEntry Where to return the pointer to the entry. */ static int rtFsFatClusterMap_GetEntry(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t offFat, uint8_t **ppbEntry) { int rc; if (offFat < pFatCache->cbFat) { uint32_t const iEntry = (offFat >> pFatCache->cEntryIndexShift) & pFatCache->fEntryIndexMask; uint32_t const offInEntry = offFat & pFatCache->fEntryOffsetMask; uint32_t const offFatEntry = offFat - offInEntry; *ppbEntry = pFatCache->aEntries[iEntry].pbData + offInEntry; /* If it's already ready, return immediately. */ if (pFatCache->aEntries[iEntry].offFat == offFatEntry) { Log3(("rtFsFatClusterMap_GetEntry: Hit entry %u for offFat=%#RX32\n", iEntry, offFat)); return VINF_SUCCESS; } /* Do we need to flush it? */ rc = VINF_SUCCESS; if ( pFatCache->aEntries[iEntry].bmDirty != 0 && pFatCache->aEntries[iEntry].offFat != UINT32_MAX) { Log3(("rtFsFatClusterMap_GetEntry: Flushing entry %u for offFat=%#RX32\n", iEntry, offFat)); rc = rtFsFatClusterMap_FlushEntry(pFatCache, iEntry); } if (RT_SUCCESS(rc)) { pFatCache->aEntries[iEntry].bmDirty = 0; /* Read in the entry from disk */ rc = RTVfsFileReadAt(pFatCache->pVol->hVfsBacking, pFatCache->pVol->aoffFats[0] + offFatEntry, pFatCache->aEntries[iEntry].pbData, pFatCache->cbEntry, NULL); if (RT_SUCCESS(rc)) { Log3(("rtFsFatClusterMap_GetEntry: Loaded entry %u for offFat=%#RX32\n", iEntry, offFat)); pFatCache->aEntries[iEntry].offFat = offFatEntry; return VINF_SUCCESS; } /** @todo We can try other FAT copies here... */ LogRel(("rtFsFatClusterMap_GetEntry: Error loading entry %u for offFat=%#RX32 (%#64RX32 LB %#x): %Rrc\n", iEntry, offFat, pFatCache->pVol->aoffFats[0] + offFatEntry, pFatCache->cbEntry, rc)); pFatCache->aEntries[iEntry].offFat = UINT32_MAX; } } else rc = VERR_OUT_OF_RANGE; *ppbEntry = NULL; return rc; } /** * Gets a pointer to a FAT entry, extended version. * * @returns IPRT status code. On failure, we're currently kind of screwed. * @param pFatCache The FAT cache. * @param offFat The FAT byte offset to get the entry off. * @param ppbEntry Where to return the pointer to the entry. * @param pidxEntry Where to return the entry index. */ static int rtFsFatClusterMap_GetEntryEx(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t offFat, uint8_t **ppbEntry, uint32_t *pidxEntry) { int rc; if (offFat < pFatCache->cbFat) { uint32_t const iEntry = (offFat >> pFatCache->cEntryIndexShift) & pFatCache->fEntryIndexMask; uint32_t const offInEntry = offFat & pFatCache->fEntryOffsetMask; uint32_t const offFatEntry = offFat - offInEntry; *ppbEntry = pFatCache->aEntries[iEntry].pbData + offInEntry; *pidxEntry = iEntry; /* If it's already ready, return immediately. */ if (pFatCache->aEntries[iEntry].offFat == offFatEntry) { Log3(("rtFsFatClusterMap_GetEntryEx: Hit entry %u for offFat=%#RX32\n", iEntry, offFat)); return VINF_SUCCESS; } /* Do we need to flush it? */ rc = VINF_SUCCESS; if ( pFatCache->aEntries[iEntry].bmDirty != 0 && pFatCache->aEntries[iEntry].offFat != UINT32_MAX) { Log3(("rtFsFatClusterMap_GetEntryEx: Flushing entry %u for offFat=%#RX32\n", iEntry, offFat)); rc = rtFsFatClusterMap_FlushEntry(pFatCache, iEntry); } if (RT_SUCCESS(rc)) { pFatCache->aEntries[iEntry].bmDirty = 0; /* Read in the entry from disk */ rc = RTVfsFileReadAt(pFatCache->pVol->hVfsBacking, pFatCache->pVol->aoffFats[0] + offFatEntry, pFatCache->aEntries[iEntry].pbData, pFatCache->cbEntry, NULL); if (RT_SUCCESS(rc)) { Log3(("rtFsFatClusterMap_GetEntryEx: Loaded entry %u for offFat=%#RX32\n", iEntry, offFat)); pFatCache->aEntries[iEntry].offFat = offFatEntry; return VINF_SUCCESS; } /** @todo We can try other FAT copies here... */ LogRel(("rtFsFatClusterMap_GetEntryEx: Error loading entry %u for offFat=%#RX32 (%#64RX32 LB %#x): %Rrc\n", iEntry, offFat, pFatCache->pVol->aoffFats[0] + offFatEntry, pFatCache->cbEntry, rc)); pFatCache->aEntries[iEntry].offFat = UINT32_MAX; } } else rc = VERR_OUT_OF_RANGE; *ppbEntry = NULL; *pidxEntry = UINT32_MAX; return rc; } /** * Destroys the file allcation table cache, first flushing any dirty lines. * * @returns IRPT status code from flush (we've destroyed it regardless of the * status code). * @param pThis The FAT volume instance which cluster map shall be * destroyed. */ static int rtFsFatClusterMap_Destroy(PRTFSFATVOL pThis) { int rc = VINF_SUCCESS; PRTFSFATCLUSTERMAPCACHE pFatCache = pThis->pFatCache; if (pFatCache) { /* flush stuff. */ rc = rtFsFatClusterMap_Flush(pThis); /* free everything. */ uint32_t i = pFatCache->cEntries; while (i-- > 0) { RTMemFree(pFatCache->aEntries[i].pbData); pFatCache->aEntries[i].pbData = NULL; } pFatCache->cEntries = 0; RTMemFree(pFatCache); pThis->pFatCache = NULL; } return rc; } /** * Worker for rtFsFatClusterMap_ReadClusterChain handling FAT12. */ static int rtFsFatClusterMap_Fat12_ReadClusterChain(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, PRTFSFATCHAIN pChain) { /* ASSUME that for FAT12 we cache the whole FAT in a single entry. That way we don't need to deal with entries in different sectors and whatnot. */ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4); /* Special case for empty files. */ if (idxCluster == 0) return VINF_SUCCESS; /* Work cluster by cluster. */ uint8_t const *pbFat = pFatCache->aEntries[0].pbData; for (;;) { /* Validate the cluster, checking for end of file. */ if ((uint32_t)(idxCluster - FAT_FIRST_DATA_CLUSTER) >= pFatCache->cClusters) { if (idxCluster >= FAT_FIRST_FAT12_EOC) return VINF_SUCCESS; Log(("Fat/ReadChain12: bogus cluster %#x vs %#x total\n", idxCluster, pFatCache->cClusters)); return VERR_VFS_BOGUS_OFFSET; } /* Add cluster to chain. */ int rc = rtFsFatChain_Append(pChain, idxCluster); if (RT_FAILURE(rc)) return rc; /* Next cluster. */ #ifdef LOG_ENABLED const uint32_t idxPrevCluster = idxCluster; #endif bool fOdd = idxCluster & 1; uint32_t offFat = idxCluster * 3 / 2; idxCluster = RT_MAKE_U16(pbFat[offFat], pbFat[offFat + 1]); if (fOdd) idxCluster >>= 4; else idxCluster &= 0x0fff; Log4(("Fat/ReadChain12: [%#x] %#x (next: %#x)\n", pChain->cClusters - 1, idxPrevCluster, idxCluster)); } } /** * Worker for rtFsFatClusterMap_ReadClusterChain handling FAT16. */ static int rtFsFatClusterMap_Fat16_ReadClusterChain(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, PRTFSFATCHAIN pChain) { /* ASSUME that for FAT16 we cache the whole FAT in a single entry. That way we don't need to deal with entries in different sectors and whatnot. */ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4); /* Special case for empty files. */ if (idxCluster == 0) return VINF_SUCCESS; /* Work cluster by cluster. */ uint8_t const *pbFat = pFatCache->aEntries[0].pbData; for (;;) { /* Validate the cluster, checking for end of file. */ if ((uint32_t)(idxCluster - FAT_FIRST_DATA_CLUSTER) >= pFatCache->cClusters) { if (idxCluster >= FAT_FIRST_FAT16_EOC) return VINF_SUCCESS; Log(("Fat/ReadChain16: bogus cluster %#x vs %#x total\n", idxCluster, pFatCache->cClusters)); return VERR_VFS_BOGUS_OFFSET; } /* Add cluster to chain. */ int rc = rtFsFatChain_Append(pChain, idxCluster); if (RT_FAILURE(rc)) return rc; /* Next cluster. */ idxCluster = RT_MAKE_U16(pbFat[idxCluster * 2], pbFat[idxCluster * 2 + 1]); } } /** * Worker for rtFsFatClusterMap_ReadClusterChain handling FAT32. */ static int rtFsFatClusterMap_Fat32_ReadClusterChain(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, PRTFSFATCHAIN pChain) { /* Special case for empty files. */ if (idxCluster == 0) return VINF_SUCCESS; /* Work cluster by cluster. */ for (;;) { /* Validate the cluster, checking for end of file. */ if ((uint32_t)(idxCluster - FAT_FIRST_DATA_CLUSTER) >= pFatCache->cClusters) { if (idxCluster >= FAT_FIRST_FAT32_EOC) return VINF_SUCCESS; Log(("Fat/ReadChain32: bogus cluster %#x vs %#x total\n", idxCluster, pFatCache->cClusters)); return VERR_VFS_BOGUS_OFFSET; } /* Add cluster to chain. */ int rc = rtFsFatChain_Append(pChain, idxCluster); if (RT_FAILURE(rc)) return rc; /* Get the next cluster. */ uint8_t *pbEntry; rc = rtFsFatClusterMap_GetEntry(pFatCache, idxCluster * 4, &pbEntry); if (RT_SUCCESS(rc)) idxCluster = RT_MAKE_U32_FROM_U8(pbEntry[0], pbEntry[1], pbEntry[2], pbEntry[3]); else return rc; } } /** * Reads a cluster chain into memory * * @returns IPRT status code. * @param pThis The FAT volume instance. * @param idxFirstCluster The first cluster. * @param pChain The chain element to read into (and thereby * initialize). */ static int rtFsFatClusterMap_ReadClusterChain(PRTFSFATVOL pThis, uint32_t idxFirstCluster, PRTFSFATCHAIN pChain) { pChain->cbCluster = pThis->cbCluster; pChain->cClusterByteShift = pThis->cClusterByteShift; pChain->cClusters = 0; pChain->cbChain = 0; RTListInit(&pChain->ListParts); switch (pThis->enmFatType) { case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_Fat12_ReadClusterChain(pThis->pFatCache, idxFirstCluster, pChain); case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_Fat16_ReadClusterChain(pThis->pFatCache, idxFirstCluster, pChain); case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_Fat32_ReadClusterChain(pThis->pFatCache, idxFirstCluster, pChain); default: AssertFailedReturn(VERR_INTERNAL_ERROR_2); } } /** * Sets bmDirty for entry @a iEntry. * * @param pFatCache The FAT cache. * @param iEntry The cache entry. * @param offEntry The offset into the cache entry that was dirtied. */ DECLINLINE(void) rtFsFatClusterMap_SetDirtyByte(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t iEntry, uint32_t offEntry) { uint8_t iLine = offEntry / pFatCache->cbDirtyLine; pFatCache->aEntries[iEntry].bmDirty |= RT_BIT_64(iLine); } /** * Sets bmDirty for entry @a iEntry. * * @param pFatCache The FAT cache. * @param iEntry The cache entry. * @param pbIntoEntry Pointer into the cache entry that was dirtied. */ DECLINLINE(void) rtFsFatClusterMap_SetDirtyByteByPtr(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t iEntry, uint8_t *pbIntoEntry) { uintptr_t offEntry = pbIntoEntry - pFatCache->aEntries[iEntry].pbData; Assert(offEntry < pFatCache->cbEntry); rtFsFatClusterMap_SetDirtyByte(pFatCache, iEntry, (uint32_t)offEntry); } /** Sets a FAT12 cluster value. */ static int rtFsFatClusterMap_SetCluster12(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, uint32_t uValue) { /* ASSUME that for FAT12 we cache the whole FAT in a single entry. That way we don't need to deal with entries in different sectors and whatnot. */ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4); AssertReturn(uValue < 0x1000, VERR_INTERNAL_ERROR_2); /* Make the change. */ uint8_t *pbFat = pFatCache->aEntries[0].pbData; uint32_t offFat = idxCluster * 3 / 2; if (idxCluster & 1) { Log3(("Fat/SetCluster12: [%#x]: %#x -> %#x\n", idxCluster, (((pbFat[offFat]) & 0xf0) >> 4) | ((unsigned)pbFat[offFat + 1] << 4), uValue)); pbFat[offFat] = ((uint8_t)0x0f & pbFat[offFat]) | ((uint8_t)uValue << 4); pbFat[offFat + 1] = (uint8_t)(uValue >> 4); } else { Log3(("Fat/SetCluster12: [%#x]: %#x -> %#x\n", idxCluster, pbFat[offFat] | ((pbFat[offFat + 1] & 0x0f) << 8), uValue)); pbFat[offFat] = (uint8_t)uValue; pbFat[offFat + 1] = ((uint8_t)0xf0 & pbFat[offFat + 1]) | (uint8_t)(uValue >> 8); } /* Update the dirty bits. */ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat); rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat + 1); return VINF_SUCCESS; } /** Sets a FAT16 cluster value. */ static int rtFsFatClusterMap_SetCluster16(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, uint32_t uValue) { /* ASSUME that for FAT16 we cache the whole FAT in a single entry. */ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4); AssertReturn(uValue < 0x10000, VERR_INTERNAL_ERROR_2); /* Make the change. */ uint8_t *pbFat = pFatCache->aEntries[0].pbData; uint32_t offFat = idxCluster * 2; pbFat[offFat] = (uint8_t)idxCluster; pbFat[offFat + 1] = (uint8_t)(idxCluster >> 8); /* Update the dirty bits. */ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat); return VINF_SUCCESS; } /** Sets a FAT32 cluster value. */ static int rtFsFatClusterMap_SetCluster32(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxCluster, uint32_t uValue) { AssertReturn(uValue < 0x10000000, VERR_INTERNAL_ERROR_2); /* Get the fat cache entry. */ uint8_t *pbEntry; uint32_t idxEntry; int rc = rtFsFatClusterMap_GetEntryEx(pFatCache, idxCluster * 4, &pbEntry, &idxEntry); if (RT_SUCCESS(rc)) { /* Make the change. */ pbEntry[0] = (uint8_t)idxCluster; pbEntry[1] = (uint8_t)(idxCluster >> 8); pbEntry[2] = (uint8_t)(idxCluster >> 16); pbEntry[3] = (uint8_t)(idxCluster >> 24); /* Update the dirty bits. */ rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry); } return rc; } /** * Marks the cluster @a idxCluster as the end of the cluster chain. * * @returns IPRT status code * @param pThis The FAT volume instance. * @param idxCluster The cluster to end the chain with. */ static int rtFsFatClusterMap_SetEndOfChain(PRTFSFATVOL pThis, uint32_t idxCluster) { AssertReturn(idxCluster >= FAT_FIRST_DATA_CLUSTER, VERR_VFS_BOGUS_OFFSET); AssertMsgReturn(idxCluster < pThis->cClusters, ("idxCluster=%#x cClusters=%#x\n", idxCluster, pThis->cClusters), VERR_VFS_BOGUS_OFFSET); switch (pThis->enmFatType) { case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_SetCluster12(pThis->pFatCache, idxCluster, FAT_FIRST_FAT12_EOC); case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_SetCluster16(pThis->pFatCache, idxCluster, FAT_FIRST_FAT16_EOC); case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_SetCluster32(pThis->pFatCache, idxCluster, FAT_FIRST_FAT32_EOC); default: AssertFailedReturn(VERR_INTERNAL_ERROR_3); } } /** * Marks the cluster @a idxCluster as free. * @returns IPRT status code * @param pThis The FAT volume instance. * @param idxCluster The cluster to free. */ static int rtFsFatClusterMap_FreeCluster(PRTFSFATVOL pThis, uint32_t idxCluster) { AssertReturn(idxCluster >= FAT_FIRST_DATA_CLUSTER, VERR_VFS_BOGUS_OFFSET); AssertReturn(idxCluster < pThis->cClusters, VERR_VFS_BOGUS_OFFSET); switch (pThis->enmFatType) { case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_SetCluster12(pThis->pFatCache, idxCluster, 0); case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_SetCluster16(pThis->pFatCache, idxCluster, 0); case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_SetCluster32(pThis->pFatCache, idxCluster, 0); default: AssertFailedReturn(VERR_INTERNAL_ERROR_3); } } /** * Worker for rtFsFatClusterMap_AllocateCluster that handles FAT12. */ static int rtFsFatClusterMap_AllocateCluster12(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxPrevCluster, uint32_t *pidxCluster) { /* ASSUME that for FAT12 we cache the whole FAT in a single entry. That way we don't need to deal with entries in different sectors and whatnot. */ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4); /* * Check that the previous cluster is a valid chain end. */ uint8_t *pbFat = pFatCache->aEntries[0].pbData; uint32_t offFatPrev; if (idxPrevCluster != UINT32_MAX) { offFatPrev = idxPrevCluster * 3 / 2; AssertReturn(offFatPrev + 1 < pFatCache->cbFat, VERR_INTERNAL_ERROR_3); uint32_t idxPrevValue; if (idxPrevCluster & 1) idxPrevValue = (pbFat[offFatPrev] >> 4) | ((uint32_t)pbFat[offFatPrev + 1] << 4); else idxPrevValue = pbFat[offFatPrev] | ((uint32_t)(pbFat[offFatPrev + 1] & 0x0f) << 8); AssertReturn(idxPrevValue >= FAT_FIRST_FAT12_EOC, VERR_VFS_BOGUS_OFFSET); } else offFatPrev = UINT32_MAX; /* * Search cluster by cluster from the start (it's small, so easy trumps * complicated optimizations). */ uint32_t idxCluster = FAT_FIRST_DATA_CLUSTER; uint32_t offFat = 3; while (idxCluster < pFatCache->cClusters) { if (idxCluster & 1) { if ( (pbFat[offFat] & 0xf0) != 0 || pbFat[offFat + 1] != 0) { offFat += 2; idxCluster++; continue; } /* Set EOC. */ pbFat[offFat] |= 0xf0; pbFat[offFat + 1] = 0xff; } else { if ( pbFat[offFat] || pbFat[offFat + 1] & 0x0f) { offFat += 1; idxCluster++; continue; } /* Set EOC. */ pbFat[offFat] = 0xff; pbFat[offFat + 1] |= 0x0f; } /* Update the dirty bits. */ rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat); rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat + 1); /* Chain it onto the previous cluster. */ if (idxPrevCluster != UINT32_MAX) { if (idxPrevCluster & 1) { pbFat[offFatPrev] = (pbFat[offFatPrev] & (uint8_t)0x0f) | (uint8_t)(idxCluster << 4); pbFat[offFatPrev + 1] = (uint8_t)(idxCluster >> 4); } else { pbFat[offFatPrev] = (uint8_t)idxCluster; pbFat[offFatPrev + 1] = (pbFat[offFatPrev + 1] & (uint8_t)0xf0) | ((uint8_t)(idxCluster >> 8) & (uint8_t)0x0f); } rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFatPrev); rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFatPrev + 1); } *pidxCluster = idxCluster; return VINF_SUCCESS; } return VERR_DISK_FULL; } /** * Worker for rtFsFatClusterMap_AllocateCluster that handles FAT16. */ static int rtFsFatClusterMap_AllocateCluster16(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxPrevCluster, uint32_t *pidxCluster) { /* ASSUME that for FAT16 we cache the whole FAT in a single entry. */ AssertReturn(pFatCache->cEntries == 1, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->cbEntry == pFatCache->cbFat, VERR_INTERNAL_ERROR_4); AssertReturn(pFatCache->aEntries[0].offFat == 0, VERR_INTERNAL_ERROR_4); /* * Check that the previous cluster is a valid chain end. */ uint8_t *pbFat = pFatCache->aEntries[0].pbData; uint32_t offFatPrev; if (idxPrevCluster != UINT32_MAX) { offFatPrev = idxPrevCluster * 2; AssertReturn(offFatPrev + 1 < pFatCache->cbFat, VERR_INTERNAL_ERROR_3); uint32_t idxPrevValue = RT_MAKE_U16(pbFat[offFatPrev], pbFat[offFatPrev + 1]); AssertReturn(idxPrevValue >= FAT_FIRST_FAT16_EOC, VERR_VFS_BOGUS_OFFSET); } else offFatPrev = UINT32_MAX; /* * We start searching at idxAllocHint and continues to the end. The next * iteration starts searching from the start and up to idxAllocHint. */ uint32_t idxCluster = RT_MIN(pFatCache->idxAllocHint, FAT_FIRST_DATA_CLUSTER); uint32_t offFat = idxCluster * 2; uint32_t cClusters = pFatCache->cClusters; for (uint32_t i = 0; i < 2; i++) { while (idxCluster < cClusters) { if ( pbFat[offFat + 0] != 0x00 || pbFat[offFat + 1] != 0x00) { /* In use - advance to the next one. */ offFat += 2; idxCluster++; } else { /* * Found one. Grab it. */ /* Set EOC. */ pbFat[offFat + 0] = 0xff; pbFat[offFat + 1] = 0xff; rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFat); /* Chain it onto the previous cluster (if any). */ if (idxPrevCluster != UINT32_MAX) { pbFat[offFatPrev + 0] = (uint8_t)idxCluster; pbFat[offFatPrev + 1] = (uint8_t)(idxCluster >> 8); rtFsFatClusterMap_SetDirtyByte(pFatCache, 0, offFatPrev); } /* Update the allocation hint. */ pFatCache->idxAllocHint = idxCluster + 1; /* Done. */ *pidxCluster = idxCluster; return VINF_SUCCESS; } } /* Wrap around to the start of the map. */ cClusters = RT_MIN(pFatCache->idxAllocHint, pFatCache->cClusters); idxCluster = FAT_FIRST_DATA_CLUSTER; offFat = 4; } return VERR_DISK_FULL; } /** * Worker for rtFsFatClusterMap_AllocateCluster that handles FAT32. */ static int rtFsFatClusterMap_AllocateCluster32(PRTFSFATCLUSTERMAPCACHE pFatCache, uint32_t idxPrevCluster, uint32_t *pidxCluster) { /* * Check that the previous cluster is a valid chain end. */ int rc; uint8_t *pbEntry; if (idxPrevCluster != UINT32_MAX) { rc = rtFsFatClusterMap_GetEntry(pFatCache, idxPrevCluster * 4, &pbEntry); if (RT_SUCCESS(rc)) { uint32_t idxPrevValue = RT_MAKE_U32_FROM_U8(pbEntry[0], pbEntry[1], pbEntry[2], pbEntry[3]); AssertReturn(idxPrevValue >= FAT_FIRST_FAT32_EOC, VERR_VFS_BOGUS_OFFSET); } else return rc; } /* * We start searching at idxAllocHint and continues to the end. The next * iteration starts searching from the start and up to idxAllocHint. */ uint32_t idxCluster = RT_MIN(pFatCache->idxAllocHint, FAT_FIRST_DATA_CLUSTER); uint32_t offFat = idxCluster * 4; uint32_t cClusters = pFatCache->cClusters; for (uint32_t i = 0; i < 2; i++) { while (idxCluster < cClusters) { /* Note! This could be done in cache entry chunks. */ uint32_t idxEntry; rc = rtFsFatClusterMap_GetEntryEx(pFatCache, offFat, &pbEntry, &idxEntry); if (RT_SUCCESS(rc)) { if ( pbEntry[0] != 0x00 || pbEntry[1] != 0x00 || pbEntry[2] != 0x00 || pbEntry[3] != 0x00) { /* In use - advance to the next one. */ offFat += 4; idxCluster++; } else { /* * Found one. Grab it. */ /* Set EOC. */ pbEntry[0] = 0xff; pbEntry[1] = 0xff; pbEntry[2] = 0xff; pbEntry[3] = 0x0f; rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry); /* Chain it on the previous cluster (if any). */ if (idxPrevCluster != UINT32_MAX) { rc = rtFsFatClusterMap_GetEntryEx(pFatCache, idxPrevCluster * 4, &pbEntry, &idxEntry); if (RT_SUCCESS(rc)) { pbEntry[0] = (uint8_t)idxCluster; pbEntry[1] = (uint8_t)(idxCluster >> 8); pbEntry[2] = (uint8_t)(idxCluster >> 16); pbEntry[3] = (uint8_t)(idxCluster >> 24); rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry); } else { /* Try free the cluster. */ int rc2 = rtFsFatClusterMap_GetEntryEx(pFatCache, offFat, &pbEntry, &idxEntry); if (RT_SUCCESS(rc2)) { pbEntry[0] = 0; pbEntry[1] = 0; pbEntry[2] = 0; pbEntry[3] = 0; rtFsFatClusterMap_SetDirtyByteByPtr(pFatCache, idxEntry, pbEntry); } return rc; } } /* Update the allocation hint. */ pFatCache->idxAllocHint = idxCluster + 1; /* Done. */ *pidxCluster = idxCluster; return VINF_SUCCESS; } } } /* Wrap around to the start of the map. */ cClusters = RT_MIN(pFatCache->idxAllocHint, pFatCache->cClusters); idxCluster = FAT_FIRST_DATA_CLUSTER; offFat = 4; } return VERR_DISK_FULL; } /** * Allocates a cluster an appends it to the chain given by @a idxPrevCluster. * * @returns IPRT status code. * @retval VERR_DISK_FULL if no more available clusters. * @param pThis The FAT volume instance. * @param idxPrevCluster The previous cluster, UINT32_MAX if first. * @param pidxCluster Where to return the cluster number on success. */ static int rtFsFatClusterMap_AllocateCluster(PRTFSFATVOL pThis, uint32_t idxPrevCluster, uint32_t *pidxCluster) { AssertReturn(idxPrevCluster == UINT32_MAX || (idxPrevCluster >= FAT_FIRST_DATA_CLUSTER && idxPrevCluster < pThis->cClusters), VERR_INTERNAL_ERROR_5); *pidxCluster = UINT32_MAX; switch (pThis->enmFatType) { case RTFSFATTYPE_FAT12: return rtFsFatClusterMap_AllocateCluster12(pThis->pFatCache, idxPrevCluster, pidxCluster); case RTFSFATTYPE_FAT16: return rtFsFatClusterMap_AllocateCluster16(pThis->pFatCache, idxPrevCluster, pidxCluster); case RTFSFATTYPE_FAT32: return rtFsFatClusterMap_AllocateCluster32(pThis->pFatCache, idxPrevCluster, pidxCluster); default: AssertFailedReturn(VERR_INTERNAL_ERROR_3); } } /** * Allocates clusters. * * Will free the clusters if it fails to allocate all of them. * * @returns IPRT status code. * @param pThis The FAT volume instance. * @param pChain The chain. * @param cClusters Number of clusters to add to the chain. */ static int rtFsFatClusterMap_AllocateMoreClusters(PRTFSFATVOL pThis, PRTFSFATCHAIN pChain, uint32_t cClusters) { int rc = VINF_SUCCESS; uint32_t const cOldClustersInChain = pChain->cClusters; uint32_t const idxOldLastCluster = rtFsFatChain_GetLastCluster(pChain); uint32_t idxPrevCluster = idxOldLastCluster; uint32_t iCluster = 0; while (iCluster < cClusters) { uint32_t idxCluster; rc = rtFsFatClusterMap_AllocateCluster(pThis, idxPrevCluster, &idxCluster); if (RT_SUCCESS(rc)) { rc = rtFsFatChain_Append(pChain, idxCluster); if (RT_SUCCESS(rc)) { /* next */ iCluster++; continue; } /* Bail out, freeing any clusters we've managed to allocate by now. */ rtFsFatClusterMap_FreeCluster(pThis, idxCluster); } if (idxOldLastCluster != UINT32_MAX) rtFsFatClusterMap_SetEndOfChain(pThis, idxOldLastCluster); while (iCluster-- > 0) rtFsFatClusterMap_FreeCluster(pThis, rtFsFatChain_GetClusterByIndex(pChain, cOldClustersInChain + iCluster)); rtFsFatChain_Shrink(pChain, iCluster); break; } return rc; } /** * Converts a FAT timestamp into an IPRT timesspec. * * @param pTimeSpec Where to return the IRPT time. * @param uDate The date part of the FAT timestamp. * @param uTime The time part of the FAT timestamp. * @param cCentiseconds Centiseconds part if applicable (0 otherwise). * @param pVol The volume. */ static void rtFsFatDateTime2TimeSpec(PRTTIMESPEC pTimeSpec, uint16_t uDate, uint16_t uTime, uint8_t cCentiseconds, PCRTFSFATVOL pVol) { RTTIME Time; Time.fFlags = RTTIME_FLAGS_TYPE_UTC; Time.offUTC = 0; Time.i32Year = 1980 + (uDate >> 9); Time.u8Month = RT_MAX((uDate >> 5) & 0xf, 1); Time.u8MonthDay = RT_MAX(uDate & 0x1f, 1); Time.u8WeekDay = UINT8_MAX; Time.u16YearDay = 0; Time.u8Hour = uTime >> 11; Time.u8Minute = (uTime >> 5) & 0x3f; Time.u8Second = (uTime & 0x1f) << 1; Time.u32Nanosecond = 0; if (cCentiseconds > 0 && cCentiseconds < 200) /* screw complicated stuff for now. */ { if (cCentiseconds >= 100) { cCentiseconds -= 100; Time.u8Second++; } Time.u32Nanosecond = cCentiseconds * UINT64_C(100000000); } RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time)); RTTimeSpecSubNano(pTimeSpec, pVol->offNanoUTC); } /** * Converts an IPRT timespec to a FAT timestamp. * * @returns The centiseconds part. * @param pVol The volume. * @param pTimeSpec The IPRT timespec to convert (UTC). * @param puDate Where to return the date part of the FAT timestamp. * @param puTime Where to return the time part of the FAT timestamp. */ static uint8_t rtFsFatTimeSpec2FatDateTime(PCRTFSFATVOL pVol, PCRTTIMESPEC pTimeSpec, uint16_t *puDate, uint16_t *puTime) { RTTIMESPEC TimeSpec = *pTimeSpec; RTTIME Time; RTTimeExplode(&Time, RTTimeSpecSubNano(&TimeSpec, pVol->offNanoUTC)); if (puDate) *puDate = ((uint16_t)(RT_MAX(Time.i32Year, 1980) - 1980) << 9) | (Time.u8Month << 5) | Time.u8MonthDay; if (puTime) *puTime = ((uint16_t)Time.u8Hour << 11) | (Time.u8Minute << 5) | (Time.u8Second >> 1); return (Time.u8Second & 1) * 100 + Time.u32Nanosecond / 10000000; } /** * Gets the current FAT timestamp. * * @returns The centiseconds part. * @param pVol The volume. * @param puDate Where to return the date part of the FAT timestamp. * @param puTime Where to return the time part of the FAT timestamp. */ static uint8_t rtFsFatCurrentFatDateTime(PCRTFSFATVOL pVol, uint16_t *puDate, uint16_t *puTime) { RTTIMESPEC TimeSpec; return rtFsFatTimeSpec2FatDateTime(pVol, RTTimeNow(&TimeSpec), puDate, puTime); } /** * Initialization of a RTFSFATOBJ structure from a FAT directory entry. * * @note The RTFSFATOBJ::pParentDir and RTFSFATOBJ::Clusters members are * properly initialized elsewhere. * * @param pObj The structure to initialize. * @param pDirEntry The directory entry. * @param offEntryInDir The offset in the parent directory. * @param pVol The volume. */ static void rtFsFatObj_InitFromDirEntry(PRTFSFATOBJ pObj, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir, PRTFSFATVOL pVol) { RTListInit(&pObj->Entry); pObj->cRefs = 1; pObj->pParentDir = NULL; pObj->pVol = pVol; pObj->offEntryInDir = offEntryInDir; pObj->fAttrib = ((RTFMODE)pDirEntry->fAttrib << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_OS2; pObj->fAttrib = rtFsModeFromDos(pObj->fAttrib, (char *)&pDirEntry->achName[0], sizeof(pDirEntry->achName), 0, 0); pObj->cbObject = pDirEntry->cbFile; pObj->fMaybeDirtyFat = false; pObj->fMaybeDirtyDirEnt = false; rtFsFatDateTime2TimeSpec(&pObj->ModificationTime, pDirEntry->uModifyDate, pDirEntry->uModifyTime, 0, pVol); rtFsFatDateTime2TimeSpec(&pObj->BirthTime, pDirEntry->uBirthDate, pDirEntry->uBirthTime, pDirEntry->uBirthCentiseconds, pVol); rtFsFatDateTime2TimeSpec(&pObj->AccessTime, pDirEntry->uAccessDate, 0, 0, pVol); } /** * Dummy initialization of a RTFSFATOBJ structure. * * @note The RTFSFATOBJ::pParentDir and RTFSFATOBJ::Clusters members are * properly initialized elsewhere. * * @param pObj The structure to initialize. * @param cbObject The object size. * @param fAttrib The attributes. * @param pVol The volume. */ static void rtFsFatObj_InitDummy(PRTFSFATOBJ pObj, uint32_t cbObject, RTFMODE fAttrib, PRTFSFATVOL pVol) { RTListInit(&pObj->Entry); pObj->cRefs = 1; pObj->pParentDir = NULL; pObj->pVol = pVol; pObj->offEntryInDir = UINT32_MAX; pObj->fAttrib = fAttrib; pObj->cbObject = cbObject; pObj->fMaybeDirtyFat = false; pObj->fMaybeDirtyDirEnt = false; RTTimeSpecSetDosSeconds(&pObj->AccessTime, 0); RTTimeSpecSetDosSeconds(&pObj->ModificationTime, 0); RTTimeSpecSetDosSeconds(&pObj->BirthTime, 0); } /** * Flushes FAT object meta data. * * @returns IPRT status code * @param pObj The common object structure. */ static int rtFsFatObj_FlushMetaData(PRTFSFATOBJ pObj) { int rc = VINF_SUCCESS; if (pObj->fMaybeDirtyFat) { rc = rtFsFatClusterMap_Flush(pObj->pVol); if (RT_SUCCESS(rc)) pObj->fMaybeDirtyFat = false; } if (pObj->fMaybeDirtyDirEnt) { int rc2 = rtFsFatDirShrd_Flush(pObj->pParentDir); if (RT_SUCCESS(rc2)) pObj->fMaybeDirtyDirEnt = false; else if (RT_SUCCESS(rc)) rc = rc2; } return rc; } /** * Worker for rtFsFatFile_Close and rtFsFatDir_Close that does common work. * * @returns IPRT status code. * @param pObj The common object structure. */ static int rtFsFatObj_Close(PRTFSFATOBJ pObj) { int rc = rtFsFatObj_FlushMetaData(pObj); if (pObj->pParentDir) rtFsFatDirShrd_RemoveOpenChild(pObj->pParentDir, pObj); rtFsFatChain_Delete(&pObj->Clusters); return rc; } /** * Worker for rtFsFatFile_QueryInfo and rtFsFatDir_QueryInfo */ static int rtFsFatObj_QueryInfo(PRTFSFATOBJ pThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { LogFlow(("rtFsFatObj_QueryInfo: %p fMode=%#x\n", pThis, pThis->fAttrib)); pObjInfo->cbObject = pThis->cbObject; pObjInfo->cbAllocated = pThis->Clusters.cbChain; pObjInfo->AccessTime = pThis->AccessTime; pObjInfo->ModificationTime = pThis->ModificationTime; pObjInfo->ChangeTime = pThis->ModificationTime; pObjInfo->BirthTime = pThis->BirthTime; pObjInfo->Attr.fMode = pThis->fAttrib; pObjInfo->Attr.enmAdditional = enmAddAttr; switch (enmAddAttr) { case RTFSOBJATTRADD_NOTHING: RT_FALL_THRU(); case RTFSOBJATTRADD_UNIX: pObjInfo->Attr.u.Unix.uid = NIL_RTUID; pObjInfo->Attr.u.Unix.gid = NIL_RTGID; pObjInfo->Attr.u.Unix.cHardlinks = 1; pObjInfo->Attr.u.Unix.INodeIdDevice = 0; pObjInfo->Attr.u.Unix.INodeId = 0; /* Could probably use the directory entry offset. */ pObjInfo->Attr.u.Unix.fFlags = 0; pObjInfo->Attr.u.Unix.GenerationId = 0; pObjInfo->Attr.u.Unix.Device = 0; break; case RTFSOBJATTRADD_UNIX_OWNER: pObjInfo->Attr.u.UnixOwner.uid = 0; pObjInfo->Attr.u.UnixOwner.szName[0] = '\0'; break; case RTFSOBJATTRADD_UNIX_GROUP: pObjInfo->Attr.u.UnixGroup.gid = 0; pObjInfo->Attr.u.UnixGroup.szName[0] = '\0'; break; case RTFSOBJATTRADD_EASIZE: pObjInfo->Attr.u.EASize.cb = 0; break; default: return VERR_INVALID_PARAMETER; } return VINF_SUCCESS; } /** * Worker for rtFsFatFile_SetMode and rtFsFatDir_SetMode. */ static int rtFsFatObj_SetMode(PRTFSFATOBJ pThis, RTFMODE fMode, RTFMODE fMask) { #if 0 if (fMask != ~RTFS_TYPE_MASK) { fMode |= ~fMask & ObjInfo.Attr.fMode; } #else RT_NOREF(pThis, fMode, fMask); return VERR_NOT_IMPLEMENTED; #endif } /** * Worker for rtFsFatFile_SetTimes and rtFsFatDir_SetTimes. */ static int rtFsFatObj_SetTimes(PRTFSFATOBJ pThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) { #if 0 PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; #else RT_NOREF(pThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime); return VERR_NOT_IMPLEMENTED; #endif } /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtFsFatFile_Close(void *pvThis) { PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; LogFlow(("rtFsFatFile_Close(%p/%p)\n", pThis, pThis->pShared)); PRTFSFATFILESHRD pShared = pThis->pShared; pThis->pShared = NULL; int rc = VINF_SUCCESS; if (pShared) { if (ASMAtomicDecU32(&pShared->Core.cRefs) == 0) { LogFlow(("rtFsFatFile_Close: Destroying shared structure %p\n", pShared)); rc = rtFsFatObj_Close(&pShared->Core); RTMemFree(pShared); } else rc = rtFsFatObj_FlushMetaData(&pShared->Core); } return rc; } /** * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} */ static DECLCALLBACK(int) rtFsFatFile_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; return rtFsFatObj_QueryInfo(&pThis->pShared->Core, pObjInfo, enmAddAttr); } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead} */ static DECLCALLBACK(int) rtFsFatFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead) { PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; PRTFSFATFILESHRD pShared = pThis->pShared; AssertReturn(pSgBuf->cSegs != 0, VERR_INTERNAL_ERROR_3); RT_NOREF(fBlocking); /* * Check for EOF. */ if (off == -1) off = pThis->offFile; if ((uint64_t)off >= pShared->Core.cbObject) { if (pcbRead) { *pcbRead = 0; return VINF_EOF; } return VERR_EOF; } /* * Do the reading cluster by cluster. */ int rc = VINF_SUCCESS; uint32_t cbFileLeft = pShared->Core.cbObject - (uint32_t)off; uint32_t cbRead = 0; size_t cbLeft = pSgBuf->paSegs[0].cbSeg; uint8_t *pbDst = (uint8_t *)pSgBuf->paSegs[0].pvSeg; while (cbLeft > 0) { if (cbFileLeft > 0) { uint64_t offDisk = rtFsFatChain_FileOffsetToDiskOff(&pShared->Core.Clusters, (uint32_t)off, pShared->Core.pVol); if (offDisk != UINT64_MAX) { uint32_t cbToRead = pShared->Core.Clusters.cbCluster - ((uint32_t)off & (pShared->Core.Clusters.cbCluster - 1)); if (cbToRead > cbLeft) cbToRead = (uint32_t)cbLeft; if (cbToRead > cbFileLeft) cbToRead = cbFileLeft; rc = RTVfsFileReadAt(pShared->Core.pVol->hVfsBacking, offDisk, pbDst, cbToRead, NULL); if (RT_SUCCESS(rc)) { off += cbToRead; pbDst += cbToRead; cbRead += cbToRead; cbFileLeft -= cbToRead; cbLeft -= cbToRead; continue; } } else rc = VERR_VFS_BOGUS_OFFSET; } else rc = pcbRead ? VINF_EOF : VERR_EOF; break; } /* Update the offset and return. */ pThis->offFile = off; if (pcbRead) *pcbRead = cbRead; return rc; } /** * Changes the size of a file or directory FAT object. * * @returns IPRT status code * @param pObj The common object. * @param cbFile The new file size. */ static int rtFsFatObj_SetSize(PRTFSFATOBJ pObj, uint32_t cbFile) { AssertReturn( ((pObj->cbObject + pObj->Clusters.cbCluster - 1) >> pObj->Clusters.cClusterByteShift) == pObj->Clusters.cClusters, VERR_INTERNAL_ERROR_3); /* * Do nothing if the size didn't change. */ if (pObj->cbObject == cbFile) return VINF_SUCCESS; /* * Do we need to allocate or free clusters? */ int rc = VINF_SUCCESS; uint32_t const cClustersNew = (cbFile + pObj->Clusters.cbCluster - 1) >> pObj->Clusters.cClusterByteShift; AssertReturn(pObj->pParentDir, VERR_INTERNAL_ERROR_2); if (pObj->Clusters.cClusters == cClustersNew) { /* likely when writing small bits at a time. */ } else if (pObj->Clusters.cClusters < cClustersNew) { /* Allocate and append new clusters. */ do { uint32_t idxCluster; rc = rtFsFatClusterMap_AllocateCluster(pObj->pVol, rtFsFatChain_GetLastCluster(&pObj->Clusters), &idxCluster); if (RT_SUCCESS(rc)) rc = rtFsFatChain_Append(&pObj->Clusters, idxCluster); } while (pObj->Clusters.cClusters < cClustersNew && RT_SUCCESS(rc)); pObj->fMaybeDirtyFat = true; } else { /* Free clusters we don't need any more. */ if (cClustersNew > 0) rc = rtFsFatClusterMap_SetEndOfChain(pObj->pVol, rtFsFatChain_GetClusterByIndex(&pObj->Clusters, cClustersNew - 1)); if (RT_SUCCESS(rc)) { uint32_t iClusterToFree = cClustersNew; while (iClusterToFree < pObj->Clusters.cClusters && RT_SUCCESS(rc)) { rc = rtFsFatClusterMap_FreeCluster(pObj->pVol, rtFsFatChain_GetClusterByIndex(&pObj->Clusters, iClusterToFree)); iClusterToFree++; } rtFsFatChain_Shrink(&pObj->Clusters, cClustersNew); } pObj->fMaybeDirtyFat = true; } if (RT_SUCCESS(rc)) { /* * Update the object size, since we've got the right number of clusters backing it now. */ pObj->cbObject = cbFile; /* * Update the directory entry. */ uint32_t uWriteLock; PFATDIRENTRY pDirEntry; rc = rtFsFatDirShrd_GetEntryForUpdate(pObj->pParentDir, pObj->offEntryInDir, &pDirEntry, &uWriteLock); if (RT_SUCCESS(rc)) { pDirEntry->cbFile = cbFile; uint32_t idxFirstCluster; if (cClustersNew == 0) idxFirstCluster = 0; /** @todo figure out if setting the cluster to 0 is the right way to deal with empty files... */ else idxFirstCluster = rtFsFatChain_GetFirstCluster(&pObj->Clusters); pDirEntry->idxCluster = (uint16_t)idxFirstCluster; if (pObj->pVol->enmFatType >= RTFSFATTYPE_FAT32) pDirEntry->u.idxClusterHigh = (uint16_t)(idxFirstCluster >> 16); rc = rtFsFatDirShrd_PutEntryAfterUpdate(pObj->pParentDir, pDirEntry, uWriteLock); pObj->fMaybeDirtyDirEnt = true; } } Log3(("rtFsFatObj_SetSize: Returns %Rrc\n", rc)); return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite} */ static DECLCALLBACK(int) rtFsFatFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten) { PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; PRTFSFATFILESHRD pShared = pThis->pShared; PRTFSFATVOL pVol = pShared->Core.pVol; AssertReturn(pSgBuf->cSegs != 0, VERR_INTERNAL_ERROR_3); RT_NOREF(fBlocking); if (pVol->fReadOnly) return VERR_WRITE_PROTECT; if (off == -1) off = pThis->offFile; /* * Do the reading cluster by cluster. */ int rc = VINF_SUCCESS; uint32_t cbWritten = 0; size_t cbLeft = pSgBuf->paSegs[0].cbSeg; uint8_t const *pbSrc = (uint8_t const *)pSgBuf->paSegs[0].pvSeg; while (cbLeft > 0) { /* Figure out how much we can write. Checking for max file size and such. */ uint32_t cbToWrite = pShared->Core.Clusters.cbCluster - ((uint32_t)off & (pShared->Core.Clusters.cbCluster - 1)); if (cbToWrite > cbLeft) cbToWrite = (uint32_t)cbLeft; uint64_t offNew = (uint64_t)off + cbToWrite; if (offNew < _4G) { /*likely*/ } else if ((uint64_t)off < _4G - 1U) cbToWrite = _4G - 1U - off; else { rc = VERR_FILE_TOO_BIG; break; } /* Grow the file? */ if ((uint32_t)offNew > pShared->Core.cbObject) { rc = rtFsFatObj_SetSize(&pShared->Core, (uint32_t)offNew); if (RT_SUCCESS(rc)) { /* likely */} else break; } /* Figure the disk offset. */ uint64_t offDisk = rtFsFatChain_FileOffsetToDiskOff(&pShared->Core.Clusters, (uint32_t)off, pVol); if (offDisk != UINT64_MAX) { rc = RTVfsFileWriteAt(pVol->hVfsBacking, offDisk, pbSrc, cbToWrite, NULL); if (RT_SUCCESS(rc)) { off += cbToWrite; pbSrc += cbToWrite; cbWritten += cbToWrite; cbLeft -= cbToWrite; } else break; } else { rc = VERR_VFS_BOGUS_OFFSET; break; } } /* Update the offset and return. */ pThis->offFile = off; if (pcbWritten) *pcbWritten = cbWritten; return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush} */ static DECLCALLBACK(int) rtFsFatFile_Flush(void *pvThis) { PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; PRTFSFATFILESHRD pShared = pThis->pShared; int rc1 = rtFsFatObj_FlushMetaData(&pShared->Core); int rc2 = RTVfsFileFlush(pShared->Core.pVol->hVfsBacking); return RT_FAILURE(rc1) ? rc1 : rc2; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne} */ static DECLCALLBACK(int) rtFsFatFile_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr, uint32_t *pfRetEvents) { NOREF(pvThis); int rc; if (fEvents != RTPOLL_EVT_ERROR) { *pfRetEvents = fEvents & ~RTPOLL_EVT_ERROR; rc = VINF_SUCCESS; } else if (fIntr) rc = RTThreadSleep(cMillies); else { uint64_t uMsStart = RTTimeMilliTS(); do rc = RTThreadSleep(cMillies); while ( rc == VERR_INTERRUPTED && !fIntr && RTTimeMilliTS() - uMsStart < cMillies); if (rc == VERR_INTERRUPTED) rc = VERR_TIMEOUT; } return rc; } /** * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell} */ static DECLCALLBACK(int) rtFsFatFile_Tell(void *pvThis, PRTFOFF poffActual) { PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; *poffActual = pThis->offFile; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} */ static DECLCALLBACK(int) rtFsFatFile_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) { PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; return rtFsFatObj_SetMode(&pThis->pShared->Core, fMode, fMask); } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} */ static DECLCALLBACK(int) rtFsFatFile_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) { PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; return rtFsFatObj_SetTimes(&pThis->pShared->Core, pAccessTime, pModificationTime, pChangeTime, pBirthTime); } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} */ static DECLCALLBACK(int) rtFsFatFile_SetOwner(void *pvThis, RTUID uid, RTGID gid) { RT_NOREF(pvThis, uid, gid); return VERR_NOT_SUPPORTED; } /** * @interface_method_impl{RTVFSFILEOPS,pfnSeek} */ static DECLCALLBACK(int) rtFsFatFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual) { PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; PRTFSFATFILESHRD pShared = pThis->pShared; RTFOFF offNew; switch (uMethod) { case RTFILE_SEEK_BEGIN: offNew = offSeek; break; case RTFILE_SEEK_END: offNew = (RTFOFF)pShared->Core.cbObject + offSeek; break; case RTFILE_SEEK_CURRENT: offNew = (RTFOFF)pThis->offFile + offSeek; break; default: return VERR_INVALID_PARAMETER; } if (offNew >= 0) { if (offNew <= _4G) { pThis->offFile = offNew; *poffActual = offNew; return VINF_SUCCESS; } return VERR_OUT_OF_RANGE; } return VERR_NEGATIVE_SEEK; } /** * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize} */ static DECLCALLBACK(int) rtFsFatFile_QuerySize(void *pvThis, uint64_t *pcbFile) { PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; PRTFSFATFILESHRD pShared = pThis->pShared; *pcbFile = pShared->Core.cbObject; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSFILEOPS,pfnSetSize} */ static DECLCALLBACK(int) rtFsFatFile_SetSize(void *pvThis, uint64_t cbFile, uint32_t fFlags) { PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis; PRTFSFATFILESHRD pShared = pThis->pShared; AssertReturn(!fFlags, VERR_NOT_SUPPORTED); if (cbFile > UINT32_MAX) return VERR_FILE_TOO_BIG; return rtFsFatObj_SetSize(&pShared->Core, (uint32_t)cbFile); } /** * @interface_method_impl{RTVFSFILEOPS,pfnQueryMaxSize} */ static DECLCALLBACK(int) rtFsFatFile_QueryMaxSize(void *pvThis, uint64_t *pcbMax) { RT_NOREF(pvThis); *pcbMax = UINT32_MAX; return VINF_SUCCESS; } /** * FAT file operations. */ DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtFsFatFileOps = { { /* Stream */ { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_FILE, "FatFile", rtFsFatFile_Close, rtFsFatFile_QueryInfo, NULL, RTVFSOBJOPS_VERSION }, RTVFSIOSTREAMOPS_VERSION, RTVFSIOSTREAMOPS_FEAT_NO_SG, rtFsFatFile_Read, rtFsFatFile_Write, rtFsFatFile_Flush, rtFsFatFile_PollOne, rtFsFatFile_Tell, NULL /*pfnSkip*/, NULL /*pfnZeroFill*/, RTVFSIOSTREAMOPS_VERSION, }, RTVFSFILEOPS_VERSION, 0, { /* ObjSet */ RTVFSOBJSETOPS_VERSION, RT_UOFFSETOF(RTVFSFILEOPS, ObjSet) - RT_UOFFSETOF(RTVFSFILEOPS, Stream.Obj), rtFsFatFile_SetMode, rtFsFatFile_SetTimes, rtFsFatFile_SetOwner, RTVFSOBJSETOPS_VERSION }, rtFsFatFile_Seek, rtFsFatFile_QuerySize, rtFsFatFile_SetSize, rtFsFatFile_QueryMaxSize, RTVFSFILEOPS_VERSION }; /** * Instantiates a new file. * * @returns IPRT status code. * @param pThis The FAT volume instance. * @param pParentDir The parent directory. * @param pDirEntry The parent directory entry. * @param offEntryInDir The byte offset of the directory entry in the parent * directory. * @param fOpen RTFILE_O_XXX flags. * @param phVfsFile Where to return the file handle. */ static int rtFsFatFile_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir, uint64_t fOpen, PRTVFSFILE phVfsFile) { AssertPtr(pParentDir); Assert(!(offEntryInDir & (sizeof(FATDIRENTRY) - 1))); PRTFSFATFILE pNewFile; int rc = RTVfsNewFile(&g_rtFsFatFileOps, sizeof(*pNewFile), fOpen, pThis->hVfsSelf, NIL_RTVFSLOCK /*use volume lock*/, phVfsFile, (void **)&pNewFile); if (RT_SUCCESS(rc)) { pNewFile->offFile = 0; pNewFile->pShared = NULL; /* * Look for existing shared object, create a new one if necessary. */ PRTFSFATFILESHRD pShared = (PRTFSFATFILESHRD)rtFsFatDirShrd_LookupShared(pParentDir, offEntryInDir); if (pShared) { LogFlow(("rtFsFatFile_New: cbObject=%#RX32 \n", pShared->Core.cbObject)); pNewFile->pShared = pShared; return VINF_SUCCESS; } pShared = (PRTFSFATFILESHRD)RTMemAllocZ(sizeof(*pShared)); if (pShared) { rtFsFatObj_InitFromDirEntry(&pShared->Core, pDirEntry, offEntryInDir, pThis); pNewFile->pShared = pShared; rc = rtFsFatClusterMap_ReadClusterChain(pThis, RTFSFAT_GET_CLUSTER(pDirEntry, pThis), &pShared->Core.Clusters); if (RT_SUCCESS(rc)) { /* * Link into parent directory so we can use it to update * our directory entry. */ rtFsFatDirShrd_AddOpenChild(pParentDir, &pShared->Core); /* * Should we truncate the file or anything of that sort? */ if ( (fOpen & RTFILE_O_TRUNCATE) || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE) { Log3(("rtFsFatFile_New: calling rtFsFatObj_SetSize to zap the file size.\n")); rc = rtFsFatObj_SetSize(&pShared->Core, 0); } if (RT_SUCCESS(rc)) { LogFlow(("rtFsFatFile_New: cbObject=%#RX32 pShared=%p\n", pShared->Core.cbObject, pShared)); return VINF_SUCCESS; } } } else rc = VERR_NO_MEMORY; /* Destroy the file object. */ RTVfsFileRelease(*phVfsFile); } *phVfsFile = NIL_RTVFSFILE; return rc; } /** * Looks up the shared structure for a child. * * @returns Referenced pointer to the shared structure, NULL if not found. * @param pThis The directory. * @param offEntryInDir The directory record offset of the child. */ static PRTFSFATOBJ rtFsFatDirShrd_LookupShared(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir) { PRTFSFATOBJ pCur; RTListForEach(&pThis->OpenChildren, pCur, RTFSFATOBJ, Entry) { if (pCur->offEntryInDir == offEntryInDir) { uint32_t cRefs = ASMAtomicIncU32(&pCur->cRefs); Assert(cRefs > 1); RT_NOREF(cRefs); return pCur; } } return NULL; } /** * Flush directory changes when having a fully buffered directory. * * @returns IPRT status code * @param pThis The directory. */ static int rtFsFatDirShrd_FlushFullyBuffered(PRTFSFATDIRSHRD pThis) { Assert(pThis->fFullyBuffered); uint32_t const cbSector = pThis->Core.pVol->cbSector; RTVFSFILE const hVfsBacking = pThis->Core.pVol->hVfsBacking; int rc = VINF_SUCCESS; for (uint32_t i = 0; i < pThis->u.Full.cSectors; i++) if (ASMBitTest(pThis->u.Full.pbDirtySectors, i)) { int rc2 = RTVfsFileWriteAt(hVfsBacking, pThis->offEntriesOnDisk + i * cbSector, (uint8_t *)pThis->paEntries + i * cbSector, cbSector, NULL); if (RT_SUCCESS(rc2)) ASMBitClear(pThis->u.Full.pbDirtySectors, i); else if (RT_SUCCESS(rc)) rc = rc2; } return rc; } /** * Flush directory changes when using simple buffering. * * @returns IPRT status code * @param pThis The directory. */ static int rtFsFatDirShrd_FlushSimple(PRTFSFATDIRSHRD pThis) { Assert(!pThis->fFullyBuffered); int rc; if ( !pThis->u.Simple.fDirty || pThis->offEntriesOnDisk != UINT64_MAX) rc = VINF_SUCCESS; else { Assert(pThis->u.Simple.offInDir != UINT32_MAX); rc = RTVfsFileWriteAt(pThis->Core.pVol->hVfsBacking, pThis->offEntriesOnDisk, pThis->paEntries, pThis->Core.pVol->cbSector, NULL); if (RT_SUCCESS(rc)) pThis->u.Simple.fDirty = false; } return rc; } /** * Flush directory changes. * * @returns IPRT status code * @param pThis The directory. */ static int rtFsFatDirShrd_Flush(PRTFSFATDIRSHRD pThis) { if (pThis->fFullyBuffered) return rtFsFatDirShrd_FlushFullyBuffered(pThis); return rtFsFatDirShrd_FlushSimple(pThis); } /** * Gets one or more entires at @a offEntryInDir. * * Common worker for rtFsFatDirShrd_GetEntriesAt and rtFsFatDirShrd_GetEntryForUpdate * * @returns IPRT status code. * @param pThis The directory. * @param offEntryInDir The directory offset in bytes. * @param fForUpdate Whether it's for updating. * @param ppaEntries Where to return pointer to the entry at * @a offEntryInDir. * @param pcEntries Where to return the number of entries * @a *ppaEntries points to. * @param puBufferReadLock Where to return the buffer read lock handle. * Call rtFsFatDirShrd_ReleaseBufferAfterReading when * done. */ static int rtFsFatDirShrd_GetEntriesAtCommon(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir, bool fForUpdate, PFATDIRENTRYUNION *ppaEntries, uint32_t *pcEntries, uint32_t *puLock) { *puLock = UINT32_MAX; int rc; Assert(RT_ALIGN_32(offEntryInDir, sizeof(FATDIRENTRY)) == offEntryInDir); Assert(pThis->Core.cbObject / sizeof(FATDIRENTRY) == pThis->cEntries); uint32_t const idxEntryInDir = offEntryInDir / sizeof(FATDIRENTRY); if (idxEntryInDir < pThis->cEntries) { if (pThis->fFullyBuffered) { /* * Fully buffered: Return pointer to all the entires starting at offEntryInDir. */ *ppaEntries = &pThis->paEntries[idxEntryInDir]; *pcEntries = pThis->cEntries - idxEntryInDir; *puLock = !fForUpdate ? 1 : UINT32_C(0x80000001); rc = VINF_SUCCESS; } else { /* * Simple buffering: If hit, return the number of entries. */ PRTFSFATVOL pVol = pThis->Core.pVol; uint32_t off = offEntryInDir - pThis->u.Simple.offInDir; if (off < pVol->cbSector) { *ppaEntries = &pThis->paEntries[off / sizeof(FATDIRENTRY)]; *pcEntries = (pVol->cbSector - off) / sizeof(FATDIRENTRY); *puLock = !fForUpdate ? 1 : UINT32_C(0x80000001); rc = VINF_SUCCESS; } else { /* * Simple buffering: Miss. * Flush dirty. Read in new sector. Return entries in sector starting * at offEntryInDir. */ if (!pThis->u.Simple.fDirty) rc = VINF_SUCCESS; else rc = rtFsFatDirShrd_FlushSimple(pThis); if (RT_SUCCESS(rc)) { off = offEntryInDir & (pVol->cbSector - 1); pThis->u.Simple.offInDir = (offEntryInDir & ~(pVol->cbSector - 1)); pThis->offEntriesOnDisk = rtFsFatChain_FileOffsetToDiskOff(&pThis->Core.Clusters, pThis->u.Simple.offInDir, pThis->Core.pVol); rc = RTVfsFileReadAt(pThis->Core.pVol->hVfsBacking, pThis->offEntriesOnDisk, pThis->paEntries, pVol->cbSector, NULL); if (RT_SUCCESS(rc)) { *ppaEntries = &pThis->paEntries[off / sizeof(FATDIRENTRY)]; *pcEntries = (pVol->cbSector - off) / sizeof(FATDIRENTRY); *puLock = !fForUpdate ? 1 : UINT32_C(0x80000001); rc = VINF_SUCCESS; } else { pThis->u.Simple.offInDir = UINT32_MAX; pThis->offEntriesOnDisk = UINT64_MAX; } } } } } else rc = VERR_FILE_NOT_FOUND; return rc; } /** * Puts back a directory entry after updating it, releasing the write lock and * marking it dirty. * * @returns IPRT status code * @param pThis The directory. * @param pDirEntry The directory entry. * @param uWriteLock The write lock. */ static int rtFsFatDirShrd_PutEntryAfterUpdate(PRTFSFATDIRSHRD pThis, PFATDIRENTRY pDirEntry, uint32_t uWriteLock) { Assert(uWriteLock == UINT32_C(0x80000001)); RT_NOREF(uWriteLock); if (pThis->fFullyBuffered) { uint32_t idxSector = ((uintptr_t)pDirEntry - (uintptr_t)pThis->paEntries) / pThis->Core.pVol->cbSector; ASMBitSet(pThis->u.Full.pbDirtySectors, idxSector); } else pThis->u.Simple.fDirty = true; return VINF_SUCCESS; } /** * Gets the pointer to the given directory entry for the purpose of updating it. * * Call rtFsFatDirShrd_PutEntryAfterUpdate afterwards. * * @returns IPRT status code. * @param pThis The directory. * @param offEntryInDir The byte offset of the directory entry, within the * directory. * @param ppDirEntry Where to return the pointer to the directory entry. * @param puWriteLock Where to return the write lock. */ static int rtFsFatDirShrd_GetEntryForUpdate(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir, PFATDIRENTRY *ppDirEntry, uint32_t *puWriteLock) { uint32_t cEntriesIgn; return rtFsFatDirShrd_GetEntriesAtCommon(pThis, offEntryInDir, true /*fForUpdate*/, (PFATDIRENTRYUNION *)ppDirEntry, &cEntriesIgn, puWriteLock); } /** * Release a directory buffer after done reading from it. * * This is currently just a placeholder. * * @param pThis The directory. * @param uBufferReadLock The buffer lock. */ static void rtFsFatDirShrd_ReleaseBufferAfterReading(PRTFSFATDIRSHRD pThis, uint32_t uBufferReadLock) { RT_NOREF(pThis, uBufferReadLock); Assert(uBufferReadLock == 1); } /** * Gets one or more entires at @a offEntryInDir. * * @returns IPRT status code. * @param pThis The directory. * @param offEntryInDir The directory offset in bytes. * @param ppaEntries Where to return pointer to the entry at * @a offEntryInDir. * @param pcEntries Where to return the number of entries * @a *ppaEntries points to. * @param puBufferReadLock Where to return the buffer read lock handle. * Call rtFsFatDirShrd_ReleaseBufferAfterReading when * done. */ static int rtFsFatDirShrd_GetEntriesAt(PRTFSFATDIRSHRD pThis, uint32_t offEntryInDir, PCFATDIRENTRYUNION *ppaEntries, uint32_t *pcEntries, uint32_t *puBufferReadLock) { return rtFsFatDirShrd_GetEntriesAtCommon(pThis, offEntryInDir, false /*fForUpdate*/, (PFATDIRENTRYUNION *)ppaEntries, pcEntries, puBufferReadLock); } /** * Translates a unicode codepoint to an uppercased CP437 index. * * @returns CP437 index if valie, UINT16_MAX if not. * @param uc The codepoint to convert. */ static uint16_t rtFsFatUnicodeCodepointToUpperCodepage(RTUNICP uc) { /* * The first 128 chars have 1:1 translation for valid FAT chars. */ if (uc < 128) { if (g_awchFatCp437ValidChars[uc] == uc) return (uint16_t)uc; if (RT_C_IS_LOWER(uc)) return uc - 0x20; return UINT16_MAX; } /* * Try for uppercased, settle for lower case if no upper case variant in the table. * This is really expensive, btw. */ RTUNICP ucUpper = RTUniCpToUpper(uc); for (unsigned i = 128; i < 256; i++) if (g_awchFatCp437ValidChars[i] == ucUpper) return i; if (ucUpper != uc) for (unsigned i = 128; i < 256; i++) if (g_awchFatCp437ValidChars[i] == uc) return i; return UINT16_MAX; } /** * Convert filename string to 8-dot-3 format, doing necessary ASCII uppercasing * and such. * * @returns true if 8.3 formattable name, false if not. * @param pszName8Dot3 Where to return the 8-dot-3 name when returning * @c true. Filled with zero on false. 8+3+1 bytes. * @param pszName The filename to convert. */ static bool rtFsFatDir_StringTo8Dot3(char *pszName8Dot3, const char *pszName) { /* * Don't try convert names with more than 12 unicode chars in them. */ size_t const cucName = RTStrUniLen(pszName); if (cucName <= 12 && cucName > 0) { /* * Recode the input string as CP437, uppercasing it, validating the * name, formatting it as a FAT directory entry string. */ size_t offDst = 0; bool fExt = false; for (;;) { RTUNICP uc; int rc = RTStrGetCpEx(&pszName, &uc); if (RT_SUCCESS(rc)) { if (uc) { if (offDst < 8+3) { uint16_t idxCp = rtFsFatUnicodeCodepointToUpperCodepage(uc); if (idxCp != UINT16_MAX) { pszName8Dot3[offDst++] = (char)idxCp; Assert(uc != '.'); continue; } /* Maybe the dot? */ if ( uc == '.' && !fExt && offDst <= 8) { fExt = true; while (offDst < 8) pszName8Dot3[offDst++] = ' '; continue; } } } /* String terminator: Check length, pad and convert 0xe5. */ else if (offDst <= (size_t)(fExt ? 8 + 3 : 8)) { while (offDst < 8 + 3) pszName8Dot3[offDst++] = ' '; Assert(offDst == 8 + 3); pszName8Dot3[offDst] = '\0'; if ((uint8_t)pszName8Dot3[0] == FATDIRENTRY_CH0_DELETED) pszName8Dot3[0] = FATDIRENTRY_CH0_ESC_E5; return true; } } /* invalid */ break; } } memset(&pszName8Dot3[0], 0, 8+3+1); return false; } /** * Calculates the checksum of a directory entry. * @returns Checksum. * @param pDirEntry The directory entry to checksum. */ static uint8_t rtFsFatDir_CalcChecksum(PCFATDIRENTRY pDirEntry) { uint8_t bChecksum = pDirEntry->achName[0]; for (uint8_t off = 1; off < RT_ELEMENTS(pDirEntry->achName); off++) { bChecksum = RTFSFAT_ROT_R1_U8(bChecksum); bChecksum += pDirEntry->achName[off]; } return bChecksum; } /** * Locates a directory entry in a directory. * * @returns IPRT status code. * @retval VERR_FILE_NOT_FOUND if not found. * @param pThis The directory to search. * @param pszEntry The entry to look for. * @param poffEntryInDir Where to return the offset of the directory * entry. * @param pfLong Where to return long name indicator. * @param pDirEntry Where to return a copy of the directory entry. */ static int rtFsFatDirShrd_FindEntry(PRTFSFATDIRSHRD pThis, const char *pszEntry, uint32_t *poffEntryInDir, bool *pfLong, PFATDIRENTRY pDirEntry) { /* Set return values. */ *pfLong = false; *poffEntryInDir = UINT32_MAX; /* * Turn pszEntry into a 8.3 filename, if possible. */ char szName8Dot3[8+3+1]; bool fIs8Dot3Name = rtFsFatDir_StringTo8Dot3(szName8Dot3, pszEntry); /* * Scan the directory buffer by buffer. */ RTUTF16 wszName[260+1]; uint8_t bChecksum = UINT8_MAX; uint8_t idNextSlot = UINT8_MAX; size_t cwcName = 0; uint32_t offEntryInDir = 0; uint32_t const cbDir = pThis->Core.cbObject; Assert(RT_ALIGN_32(cbDir, sizeof(*pDirEntry)) == cbDir); AssertCompile(FATDIRNAMESLOT_MAX_SLOTS * FATDIRNAMESLOT_CHARS_PER_SLOT < RT_ELEMENTS(wszName)); wszName[260] = '\0'; while (offEntryInDir < cbDir) { /* Get chunk of entries starting at offEntryInDir. */ uint32_t uBufferLock = UINT32_MAX; uint32_t cEntries = 0; PCFATDIRENTRYUNION paEntries = NULL; int rc = rtFsFatDirShrd_GetEntriesAt(pThis, offEntryInDir, &paEntries, &cEntries, &uBufferLock); if (RT_FAILURE(rc)) return rc; /* * Now work thru each of the entries. */ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY)) { switch ((uint8_t)paEntries[iEntry].Entry.achName[0]) { default: break; case FATDIRENTRY_CH0_DELETED: cwcName = 0; continue; case FATDIRENTRY_CH0_END_OF_DIR: if (pThis->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0) { rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock); return VERR_FILE_NOT_FOUND; } cwcName = 0; break; /* Technically a valid entry before DOS 2.0, or so some claim. */ } /* * Check for long filename slot. */ if ( paEntries[iEntry].Slot.fAttrib == FAT_ATTR_NAME_SLOT && paEntries[iEntry].Slot.idxZero == 0 && paEntries[iEntry].Slot.fZero == 0 && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) <= FATDIRNAMESLOT_HIGHEST_SLOT_ID && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) != 0) { /* New slot? */ if (paEntries[iEntry].Slot.idSlot & FATDIRNAMESLOT_FIRST_SLOT_FLAG) { idNextSlot = paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG; bChecksum = paEntries[iEntry].Slot.bChecksum; cwcName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT; wszName[cwcName] = '\0'; } /* Is valid next entry? */ else if ( paEntries[iEntry].Slot.idSlot == idNextSlot && paEntries[iEntry].Slot.bChecksum == bChecksum) { /* likely */ } else cwcName = 0; if (cwcName) { idNextSlot--; size_t offName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT; memcpy(&wszName[offName], paEntries[iEntry].Slot.awcName0, sizeof(paEntries[iEntry].Slot.awcName0)); memcpy(&wszName[offName + 5], paEntries[iEntry].Slot.awcName1, sizeof(paEntries[iEntry].Slot.awcName1)); memcpy(&wszName[offName + 5 + 6], paEntries[iEntry].Slot.awcName2, sizeof(paEntries[iEntry].Slot.awcName2)); } } /* * Regular directory entry. Do the matching, first 8.3 then long name. */ else if ( fIs8Dot3Name && !(paEntries[iEntry].Entry.fAttrib & FAT_ATTR_VOLUME) && memcmp(paEntries[iEntry].Entry.achName, szName8Dot3, sizeof(paEntries[iEntry].Entry.achName)) == 0) { *poffEntryInDir = offEntryInDir; *pDirEntry = paEntries[iEntry].Entry; *pfLong = false; rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock); return VINF_SUCCESS; } else if ( cwcName != 0 && idNextSlot == 0 && !(paEntries[iEntry].Entry.fAttrib & FAT_ATTR_VOLUME) && rtFsFatDir_CalcChecksum(&paEntries[iEntry].Entry) == bChecksum && RTUtf16ICmpUtf8(wszName, pszEntry) == 0) { *poffEntryInDir = offEntryInDir; *pDirEntry = paEntries[iEntry].Entry; *pfLong = true; rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock); return VINF_SUCCESS; } else cwcName = 0; } rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock); } return VERR_FILE_NOT_FOUND; } /** * Watered down version of rtFsFatDirShrd_FindEntry that is used by the short name * generator to check for duplicates. * * @returns IPRT status code. * @retval VERR_FILE_NOT_FOUND if not found. * @retval VINF_SUCCESS if found. * @param pThis The directory to search. * @param pszEntry The entry to look for. */ static int rtFsFatDirShrd_FindEntryShort(PRTFSFATDIRSHRD pThis, const char *pszName8Dot3) { Assert(strlen(pszName8Dot3) == 8+3); /* * Scan the directory buffer by buffer. */ uint32_t offEntryInDir = 0; uint32_t const cbDir = pThis->Core.cbObject; Assert(RT_ALIGN_32(cbDir, sizeof(FATDIRENTRY)) == cbDir); while (offEntryInDir < cbDir) { /* Get chunk of entries starting at offEntryInDir. */ uint32_t uBufferLock = UINT32_MAX; uint32_t cEntries = 0; PCFATDIRENTRYUNION paEntries = NULL; int rc = rtFsFatDirShrd_GetEntriesAt(pThis, offEntryInDir, &paEntries, &cEntries, &uBufferLock); if (RT_FAILURE(rc)) return rc; /* * Now work thru each of the entries. */ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY)) { switch ((uint8_t)paEntries[iEntry].Entry.achName[0]) { default: break; case FATDIRENTRY_CH0_DELETED: continue; case FATDIRENTRY_CH0_END_OF_DIR: if (pThis->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0) { rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock); return VERR_FILE_NOT_FOUND; } break; /* Technically a valid entry before DOS 2.0, or so some claim. */ } /* * Skip long filename slots. */ if ( paEntries[iEntry].Slot.fAttrib == FAT_ATTR_NAME_SLOT && paEntries[iEntry].Slot.idxZero == 0 && paEntries[iEntry].Slot.fZero == 0 && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) <= FATDIRNAMESLOT_HIGHEST_SLOT_ID && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) != 0) { /* skipped */ } /* * Regular directory entry. Do the matching, first 8.3 then long name. */ else if (memcmp(paEntries[iEntry].Entry.achName, pszName8Dot3, sizeof(paEntries[iEntry].Entry.achName)) == 0) { rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock); return VINF_SUCCESS; } } rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock); } return VERR_FILE_NOT_FOUND; } /** * Calculates the FATDIRENTRY::fCase flags for the given name. * * ASSUMES that the name is a 8.3 name. * * @returns Case flag mask. * @param pszName The name. */ static uint8_t rtFsFatDir_CalcCaseFlags(const char *pszName) { uint8_t bRet = FATDIRENTRY_CASE_F_LOWER_BASE | FATDIRENTRY_CASE_F_LOWER_EXT; uint8_t bCurrent = FATDIRENTRY_CASE_F_LOWER_BASE; for (;;) { RTUNICP uc; int rc = RTStrGetCpEx(&pszName, &uc); if (RT_SUCCESS(rc)) { if (uc != 0) { if (uc != '.') { if (RTUniCpIsUpper(uc)) { bRet &= ~bCurrent; if (!bRet) return 0; } } else bCurrent = FATDIRENTRY_CASE_F_LOWER_EXT; } else if (bCurrent == FATDIRENTRY_CASE_F_LOWER_BASE) return bRet & ~FATDIRENTRY_CASE_F_LOWER_EXT; else return bRet; } else return 0; } } /** * Checks if we need to generate a long name for @a pszEntry. * * @returns true if we need to, false if we don't. * @param pszEntry The UTF-8 directory entry entry name. * @param fIs8Dot3Name Whether we've managed to create a 8-dot-3 name. * @param pDirEntry The directory entry with the 8-dot-3 name when * fIs8Dot3Name is set. */ static bool rtFsFatDir_NeedLongName(const char *pszEntry, bool fIs8Dot3Name, PCFATDIRENTRY pDirEntry) { /* * Check the easy ways out first. */ /* If we couldn't make a straight 8-dot-3 name out of it, the we must do the long name thing. No question. */ if (!fIs8Dot3Name) return true; /* If both lower case flags are set, then the whole name must be lowercased, so we won't need a long entry. */ if (pDirEntry->fCase == (FATDIRENTRY_CASE_F_LOWER_BASE | FATDIRENTRY_CASE_F_LOWER_EXT)) return false; /* * Okay, check out the whole string then, part by part. (This is code * similar to rtFsFatDir_CalcCaseFlags.) */ uint8_t fCurrent = pDirEntry->fCase & FATDIRENTRY_CASE_F_LOWER_BASE; for (;;) { RTUNICP uc; int rc = RTStrGetCpEx(&pszEntry, &uc); if (RT_SUCCESS(rc)) { if (uc != 0) { if (uc != '.') { if ( fCurrent || !RTUniCpIsLower(uc)) { /* okay */ } else return true; } else fCurrent = pDirEntry->fCase & FATDIRENTRY_CASE_F_LOWER_EXT; } /* It checked out to the end, so we don't need a long name. */ else return false; } else return true; } } /** * Checks if the given long name is valid for a long file name or not. * * Encoding, length and character set limitations are checked. * * @returns IRPT status code. * @param pwszEntry The long filename. * @param cwc The length of the filename in UTF-16 chars. */ static int rtFsFatDir_ValidateLongName(PCRTUTF16 pwszEntry, size_t cwc) { /* Length limitation. */ if (cwc <= RTFSFAT_MAX_LFN_CHARS) { /* Character set limitations. */ for (size_t off = 0; off < cwc; off++) { RTUTF16 wc = pwszEntry[off]; if (wc < 128) { if (g_awchFatCp437ValidChars[wc] <= UINT16_C(0xfffe)) { /* likely */ } else return VERR_INVALID_NAME; } } /* Name limitations. */ if ( cwc == 1 && pwszEntry[0] == '.') return VERR_INVALID_NAME; if ( cwc == 2 && pwszEntry[0] == '.' && pwszEntry[1] == '.') return VERR_INVALID_NAME; /** @todo Check for more invalid names, also in the 8.3 case! */ return VINF_SUCCESS; } return VERR_FILENAME_TOO_LONG; } /** * Worker for rtFsFatDirShrd_GenerateShortName. */ static void rtFsFatDir_CopyShortName(char *pszDst, uint32_t cchDst, const char *pszSrc, size_t cchSrc, char chPad) { /* Copy from source. */ if (cchSrc > 0) { const char *pszSrcEnd = &pszSrc[cchSrc]; while (cchDst > 0 && pszSrc != pszSrcEnd) { RTUNICP uc; int rc = RTStrGetCpEx(&pszSrc, &uc); if (RT_SUCCESS(rc)) { if (uc < 128) { if (g_awchFatCp437ValidChars[uc] != uc) { if (uc) { uc = RTUniCpToUpper(uc); if (g_awchFatCp437ValidChars[uc] != uc) uc = '_'; } else break; } } else uc = '_'; } else uc = '_'; *pszDst++ = (char)uc; cchDst--; } } /* Pad the remaining space. */ while (cchDst-- > 0) *pszDst++ = chPad; } /** * Generates a short filename. * * @returns IPRT status code. * @param pThis The directory. * @param pszEntry The long name (UTF-8). * @param pDirEntry Where to put the short name. */ static int rtFsFatDirShrd_GenerateShortName(PRTFSFATDIRSHRD pThis, const char *pszEntry, PFATDIRENTRY pDirEntry) { /* Do some input parsing. */ const char *pszExt = RTPathSuffix(pszEntry); size_t const cchBasename = pszExt ? pszExt - pszEntry : strlen(pszEntry); size_t const cchExt = pszExt ? strlen(++pszExt) : 0; /* Fill in the extension first. It stays the same. */ char szShortName[8+3+1]; rtFsFatDir_CopyShortName(&szShortName[8], 3, pszExt, cchExt, ' '); szShortName[8+3] = '\0'; /* * First try single digit 1..9. */ rtFsFatDir_CopyShortName(szShortName, 6, pszEntry, cchBasename, '_'); szShortName[6] = '~'; for (uint32_t iLastDigit = 1; iLastDigit < 10; iLastDigit++) { szShortName[7] = iLastDigit + '0'; int rc = rtFsFatDirShrd_FindEntryShort(pThis, szShortName); if (rc == VERR_FILE_NOT_FOUND) { memcpy(pDirEntry->achName, szShortName, sizeof(pDirEntry->achName)); return VINF_SUCCESS; } if (RT_FAILURE(rc)) return rc; } /* * First try two digits 10..99. */ szShortName[5] = '~'; for (uint32_t iFirstDigit = 1; iFirstDigit < 10; iFirstDigit++) for (uint32_t iLastDigit = 0; iLastDigit < 10; iLastDigit++) { szShortName[6] = iFirstDigit + '0'; szShortName[7] = iLastDigit + '0'; int rc = rtFsFatDirShrd_FindEntryShort(pThis, szShortName); if (rc == VERR_FILE_NOT_FOUND) { memcpy(pDirEntry->achName, szShortName, sizeof(pDirEntry->achName)); return VINF_SUCCESS; } if (RT_FAILURE(rc)) return rc; } /* * Okay, do random numbers then. */ szShortName[2] = '~'; for (uint32_t i = 0; i < 8192; i++) { char szHex[68]; ssize_t cchHex = RTStrFormatU32(szHex, sizeof(szHex), RTRandU32(), 16, 5, 0, RTSTR_F_CAPITAL | RTSTR_F_WIDTH | RTSTR_F_ZEROPAD); AssertReturn(cchHex >= 5, VERR_NET_NOT_UNIQUE_NAME); szShortName[7] = szHex[cchHex - 1]; szShortName[6] = szHex[cchHex - 2]; szShortName[5] = szHex[cchHex - 3]; szShortName[4] = szHex[cchHex - 4]; szShortName[3] = szHex[cchHex - 5]; int rc = rtFsFatDirShrd_FindEntryShort(pThis, szShortName); if (rc == VERR_FILE_NOT_FOUND) { memcpy(pDirEntry->achName, szShortName, sizeof(pDirEntry->achName)); return VINF_SUCCESS; } if (RT_FAILURE(rc)) return rc; } return VERR_NET_NOT_UNIQUE_NAME; } /** * Considers whether we need to create a long name or not. * * If a long name is needed and the name wasn't 8-dot-3 compatible, a 8-dot-3 * name will be generated and stored in *pDirEntry. * * @returns IPRT status code * @param pThis The directory. * @param pszEntry The name. * @param fIs8Dot3Name Whether we have a 8-dot-3 name already. * @param pDirEntry Where to return the generated 8-dot-3 name. * @param paSlots Where to return the long name entries. The array * can hold at least FATDIRNAMESLOT_MAX_SLOTS entries. * @param pcSlots Where to return the actual number of slots used. */ static int rtFsFatDirShrd_MaybeCreateLongNameAndShortAlias(PRTFSFATDIRSHRD pThis, const char *pszEntry, bool fIs8Dot3Name, PFATDIRENTRY pDirEntry, PFATDIRNAMESLOT paSlots, uint32_t *pcSlots) { RT_NOREF(pThis, pDirEntry, paSlots, pszEntry); /* * If we don't need to create a long name, return immediately. */ if (!rtFsFatDir_NeedLongName(pszEntry, fIs8Dot3Name, pDirEntry)) { *pcSlots = 0; return VINF_SUCCESS; } /* * Convert the name to UTF-16 and figure it's length (this validates the * input encoding). Then do long name validation (length, charset limitation). */ RTUTF16 wszEntry[FATDIRNAMESLOT_MAX_SLOTS * FATDIRNAMESLOT_CHARS_PER_SLOT + 4]; PRTUTF16 pwszEntry = wszEntry; size_t cwcEntry; int rc = RTStrToUtf16Ex(pszEntry, RTSTR_MAX, &pwszEntry, RT_ELEMENTS(wszEntry), &cwcEntry); if (RT_SUCCESS(rc)) rc = rtFsFatDir_ValidateLongName(pwszEntry, cwcEntry); if (RT_SUCCESS(rc)) { /* * Generate a short name if we need to. */ if (!fIs8Dot3Name) rc = rtFsFatDirShrd_GenerateShortName(pThis, pszEntry, pDirEntry); if (RT_SUCCESS(rc)) { /* * Fill in the long name slots. First we pad the wszEntry with 0xffff * until it is a multiple of of the slot count. That way we can copy * the name straight into the entry without constaints. */ memset(&wszEntry[cwcEntry + 1], 0xff, RT_MIN(sizeof(wszEntry) - (cwcEntry + 1) * sizeof(RTUTF16), FATDIRNAMESLOT_CHARS_PER_SLOT * sizeof(RTUTF16))); uint8_t const bChecksum = rtFsFatDir_CalcChecksum(pDirEntry); size_t const cSlots = (cwcEntry + FATDIRNAMESLOT_CHARS_PER_SLOT - 1) / FATDIRNAMESLOT_CHARS_PER_SLOT; size_t iSlot = cSlots; PCRTUTF16 pwszSrc = wszEntry; while (iSlot-- > 0) { memcpy(paSlots[iSlot].awcName0, pwszSrc, sizeof(paSlots[iSlot].awcName0)); pwszSrc += RT_ELEMENTS(paSlots[iSlot].awcName0); memcpy(paSlots[iSlot].awcName1, pwszSrc, sizeof(paSlots[iSlot].awcName1)); pwszSrc += RT_ELEMENTS(paSlots[iSlot].awcName1); memcpy(paSlots[iSlot].awcName2, pwszSrc, sizeof(paSlots[iSlot].awcName2)); pwszSrc += RT_ELEMENTS(paSlots[iSlot].awcName2); paSlots[iSlot].idSlot = (uint8_t)(cSlots - iSlot); paSlots[iSlot].fAttrib = FAT_ATTR_NAME_SLOT; paSlots[iSlot].fZero = 0; paSlots[iSlot].idxZero = 0; paSlots[iSlot].bChecksum = bChecksum; } paSlots[0].idSlot |= FATDIRNAMESLOT_FIRST_SLOT_FLAG; *pcSlots = (uint32_t)cSlots; return VINF_SUCCESS; } } *pcSlots = UINT32_MAX; return rc; } /** * Searches the directory for a given number of free directory entries. * * The free entries must be consecutive of course. * * @returns IPRT status code. * @retval VERR_DISK_FULL if no space was found, *pcFreeTail set. * @param pThis The directory to search. * @param cEntriesNeeded How many entries we need. * @param poffEntryInDir Where to return the offset of the first entry we * found. * @param pcFreeTail Where to return the number of free entries at the * end of the directory when VERR_DISK_FULL is * returned. */ static int rtFsFatChain_FindFreeEntries(PRTFSFATDIRSHRD pThis, uint32_t cEntriesNeeded, uint32_t *poffEntryInDir, uint32_t *pcFreeTail) { /* First try make gcc happy. */ *pcFreeTail = 0; *poffEntryInDir = UINT32_MAX; /* * Scan the whole directory, buffer by buffer. */ uint32_t offStartFreeEntries = UINT32_MAX; uint32_t cFreeEntries = 0; uint32_t offEntryInDir = 0; uint32_t const cbDir = pThis->Core.cbObject; Assert(RT_ALIGN_32(cbDir, sizeof(FATDIRENTRY)) == cbDir); while (offEntryInDir < cbDir) { /* Get chunk of entries starting at offEntryInDir. */ uint32_t uBufferLock = UINT32_MAX; uint32_t cEntries = 0; PCFATDIRENTRYUNION paEntries = NULL; int rc = rtFsFatDirShrd_GetEntriesAt(pThis, offEntryInDir, &paEntries, &cEntries, &uBufferLock); if (RT_FAILURE(rc)) return rc; /* * Now work thru each of the entries. */ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY)) { uint8_t const bFirst = paEntries[iEntry].Entry.achName[0]; if ( bFirst == FATDIRENTRY_CH0_DELETED || bFirst == FATDIRENTRY_CH0_END_OF_DIR) { if (offStartFreeEntries != UINT32_MAX) cFreeEntries++; else { offStartFreeEntries = offEntryInDir; cFreeEntries = 1; } if (cFreeEntries >= cEntriesNeeded) { *pcFreeTail = cEntriesNeeded; *poffEntryInDir = offStartFreeEntries; rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock); return VINF_SUCCESS; } if (bFirst == FATDIRENTRY_CH0_END_OF_DIR) { if (pThis->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0) { rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock); *pcFreeTail = cFreeEntries = (cbDir - offStartFreeEntries) / sizeof(FATDIRENTRY); if (cFreeEntries >= cEntriesNeeded) { *poffEntryInDir = offStartFreeEntries; rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock); return VINF_SUCCESS; } return VERR_DISK_FULL; } } } else if (offStartFreeEntries != UINT32_MAX) { offStartFreeEntries = UINT32_MAX; cFreeEntries = 0; } } rtFsFatDirShrd_ReleaseBufferAfterReading(pThis, uBufferLock); } *pcFreeTail = cFreeEntries; return VERR_DISK_FULL; } /** * Try grow the directory. * * This is not called on the root directory. * * @returns IPRT status code. * @retval VERR_DISK_FULL if we failed to allocated new space. * @param pThis The directory to grow. * @param cMinNewEntries The minimum number of new entries to allocated. */ static int rtFsFatChain_GrowDirectory(PRTFSFATDIRSHRD pThis, uint32_t cMinNewEntries) { RT_NOREF(pThis, cMinNewEntries); return VERR_DISK_FULL; } /** * Inserts a directory with zero of more long name slots preceeding it. * * @returns IPRT status code. * @param pThis The directory. * @param pDirEntry The directory entry. * @param paSlots The long name slots. * @param cSlots The number of long name slots. * @param poffEntryInDir Where to return the directory offset. */ static int rtFsFatChain_InsertEntries(PRTFSFATDIRSHRD pThis, PCFATDIRENTRY pDirEntry, PFATDIRNAMESLOT paSlots, uint32_t cSlots, uint32_t *poffEntryInDir) { uint32_t const cTotalEntries = cSlots + 1; /* * Find somewhere to put the entries. Try extend the directory if we're * not successful at first. */ uint32_t cFreeTailEntries; uint32_t offFirstInDir; int rc = rtFsFatChain_FindFreeEntries(pThis, cTotalEntries, &offFirstInDir, &cFreeTailEntries); if (rc == VERR_DISK_FULL) { Assert(cFreeTailEntries < cTotalEntries); /* Try grow it and use the newly allocated space. */ if ( pThis->Core.pParentDir && pThis->cEntries < _64K /* Don't grow beyond 64K entries */) { offFirstInDir = pThis->Core.cbObject - cFreeTailEntries * sizeof(FATDIRENTRY); rc = rtFsFatChain_GrowDirectory(pThis, cTotalEntries - cFreeTailEntries); } if (rc == VERR_DISK_FULL) { /** @todo Try compact the directory if we couldn't grow it. */ } } if (RT_SUCCESS(rc)) { /* * Update the directory. */ uint32_t offCurrent = offFirstInDir; for (uint32_t iSrcSlot = 0; iSrcSlot < cTotalEntries; iSrcSlot++, offCurrent += sizeof(FATDIRENTRY)) { uint32_t uBufferLock; PFATDIRENTRY pDstEntry; rc = rtFsFatDirShrd_GetEntryForUpdate(pThis, offCurrent, &pDstEntry, &uBufferLock); if (RT_SUCCESS(rc)) { if (iSrcSlot < cSlots) memcpy(pDstEntry, &paSlots[iSrcSlot], sizeof(*pDstEntry)); else memcpy(pDstEntry, pDirEntry, sizeof(*pDstEntry)); rc = rtFsFatDirShrd_PutEntryAfterUpdate(pThis, pDstEntry, uBufferLock); if (RT_SUCCESS(rc)) continue; /* * Bail out: Try mark any edited entries as deleted. */ iSrcSlot++; } while (iSrcSlot-- > 0) { int rc2 = rtFsFatDirShrd_GetEntryForUpdate(pThis, offFirstInDir + iSrcSlot * sizeof(FATDIRENTRY), &pDstEntry, &uBufferLock); if (RT_SUCCESS(rc2)) { pDstEntry->achName[0] = FATDIRENTRY_CH0_DELETED; rtFsFatDirShrd_PutEntryAfterUpdate(pThis, pDstEntry, uBufferLock); } } *poffEntryInDir = UINT32_MAX; return rc; } AssertRC(rc); /* * Successfully inserted all. */ *poffEntryInDir = offFirstInDir + cSlots * sizeof(FATDIRENTRY); return VINF_SUCCESS; } *poffEntryInDir = UINT32_MAX; return rc; } /** * Creates a new directory entry. * * @returns IPRT status code * @param pThis The directory. * @param pszEntry The name of the new entry. * @param fAttrib The attributes. * @param cbInitial The initialize size. * @param poffEntryInDir Where to return the offset of the directory entry. * @param pDirEntry Where to return a copy of the directory entry. * * @remarks ASSUMES caller has already called rtFsFatDirShrd_FindEntry to make sure * the entry doesn't exist. */ static int rtFsFatDirShrd_CreateEntry(PRTFSFATDIRSHRD pThis, const char *pszEntry, uint8_t fAttrib, uint32_t cbInitial, uint32_t *poffEntryInDir, PFATDIRENTRY pDirEntry) { PRTFSFATVOL pVol = pThis->Core.pVol; *poffEntryInDir = UINT32_MAX; if (pVol->fReadOnly) return VERR_WRITE_PROTECT; /* * Create the directory entries on the stack. */ bool fIs8Dot3Name = rtFsFatDir_StringTo8Dot3((char *)pDirEntry->achName, pszEntry); pDirEntry->fAttrib = fAttrib; pDirEntry->fCase = fIs8Dot3Name ? rtFsFatDir_CalcCaseFlags(pszEntry) : 0; pDirEntry->uBirthCentiseconds = rtFsFatCurrentFatDateTime(pVol, &pDirEntry->uBirthDate, &pDirEntry->uBirthTime); pDirEntry->uAccessDate = pDirEntry->uBirthDate; pDirEntry->uModifyDate = pDirEntry->uBirthDate; pDirEntry->uModifyTime = pDirEntry->uBirthTime; pDirEntry->idxCluster = 0; /* Will fill this in later if cbInitial is non-zero. */ pDirEntry->u.idxClusterHigh = 0; pDirEntry->cbFile = cbInitial; /* * Create long filename slots if necessary. */ uint32_t cSlots = UINT32_MAX; FATDIRNAMESLOT aSlots[FATDIRNAMESLOT_MAX_SLOTS]; AssertCompile(RTFSFAT_MAX_LFN_CHARS < RT_ELEMENTS(aSlots) * FATDIRNAMESLOT_CHARS_PER_SLOT); int rc = rtFsFatDirShrd_MaybeCreateLongNameAndShortAlias(pThis, pszEntry, fIs8Dot3Name, pDirEntry, aSlots, &cSlots); if (RT_SUCCESS(rc)) { Assert(cSlots <= FATDIRNAMESLOT_MAX_SLOTS); /* * Allocate initial clusters if requested. */ RTFSFATCHAIN Clusters; rtFsFatChain_InitEmpty(&Clusters, pVol); if (cbInitial > 0) { rc = rtFsFatClusterMap_AllocateMoreClusters(pVol, &Clusters, (cbInitial + Clusters.cbCluster - 1) >> Clusters.cClusterByteShift); if (RT_SUCCESS(rc)) { uint32_t idxFirstCluster = rtFsFatChain_GetFirstCluster(&Clusters); pDirEntry->idxCluster = (uint16_t)idxFirstCluster; if (pVol->enmFatType >= RTFSFATTYPE_FAT32) pDirEntry->u.idxClusterHigh = (uint16_t)(idxFirstCluster >> 16); } } if (RT_SUCCESS(rc)) { /* * Insert the directory entry and name slots. */ rc = rtFsFatChain_InsertEntries(pThis, pDirEntry, aSlots, cSlots, poffEntryInDir); if (RT_SUCCESS(rc)) { rtFsFatChain_Delete(&Clusters); return VINF_SUCCESS; } for (uint32_t iClusterToFree = 0; iClusterToFree < Clusters.cClusters; iClusterToFree++) rtFsFatClusterMap_FreeCluster(pVol, rtFsFatChain_GetClusterByIndex(&Clusters, iClusterToFree)); rtFsFatChain_Delete(&Clusters); } } return rc; } /** * Releases a reference to a shared directory structure. * * @param pShared The shared directory structure. */ static int rtFsFatDirShrd_Release(PRTFSFATDIRSHRD pShared) { uint32_t cRefs = ASMAtomicDecU32(&pShared->Core.cRefs); Assert(cRefs < UINT32_MAX / 2); if (cRefs == 0) { LogFlow(("rtFsFatDirShrd_Release: Destroying shared structure %p\n", pShared)); Assert(pShared->Core.cRefs == 0); int rc; if (pShared->paEntries) { rc = rtFsFatDirShrd_Flush(pShared); RTMemFree(pShared->paEntries); pShared->paEntries = NULL; } else rc = VINF_SUCCESS; if ( pShared->fFullyBuffered && pShared->u.Full.pbDirtySectors) { RTMemFree(pShared->u.Full.pbDirtySectors); pShared->u.Full.pbDirtySectors = NULL; } int rc2 = rtFsFatObj_Close(&pShared->Core); if (RT_SUCCESS(rc)) rc = rc2; RTMemFree(pShared); return rc; } return VINF_SUCCESS; } /** * Retains a reference to a shared directory structure. * * @param pShared The shared directory structure. */ static void rtFsFatDirShrd_Retain(PRTFSFATDIRSHRD pShared) { uint32_t cRefs = ASMAtomicIncU32(&pShared->Core.cRefs); Assert(cRefs > 1); NOREF(cRefs); } /** * @interface_method_impl{RTVFSOBJOPS,pfnClose} */ static DECLCALLBACK(int) rtFsFatDir_Close(void *pvThis) { PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis; PRTFSFATDIRSHRD pShared = pThis->pShared; pThis->pShared = NULL; if (pShared) return rtFsFatDirShrd_Release(pShared); return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo} */ static DECLCALLBACK(int) rtFsFatDir_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis; return rtFsFatObj_QueryInfo(&pThis->pShared->Core, pObjInfo, enmAddAttr); } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnMode} */ static DECLCALLBACK(int) rtFsFatDir_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask) { PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis; return rtFsFatObj_SetMode(&pThis->pShared->Core, fMode, fMask); } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes} */ static DECLCALLBACK(int) rtFsFatDir_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime, PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime) { PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis; return rtFsFatObj_SetTimes(&pThis->pShared->Core, pAccessTime, pModificationTime, pChangeTime, pBirthTime); } /** * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner} */ static DECLCALLBACK(int) rtFsFatDir_SetOwner(void *pvThis, RTUID uid, RTGID gid) { RT_NOREF(pvThis, uid, gid); return VERR_NOT_SUPPORTED; } /** * @interface_method_impl{RTVFSDIROPS,pfnOpen} */ static DECLCALLBACK(int) rtFsFatDir_Open(void *pvThis, const char *pszEntry, uint64_t fOpen, uint32_t fFlags, PRTVFSOBJ phVfsObj) { PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis; PRTFSFATDIRSHRD pShared = pThis->pShared; int rc; /* * Special cases '.' and '.' */ if (pszEntry[0] == '.') { PRTFSFATDIRSHRD pSharedToOpen; if (pszEntry[1] == '\0') pSharedToOpen = pShared; else if (pszEntry[1] == '.' && pszEntry[2] == '\0') { pSharedToOpen = pShared->Core.pParentDir; if (!pSharedToOpen) pSharedToOpen = pShared; } else pSharedToOpen = NULL; if (pSharedToOpen) { if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY) { if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE) { rtFsFatDirShrd_Retain(pSharedToOpen); RTVFSDIR hVfsDir; rc = rtFsFatDir_NewWithShared(pShared->Core.pVol, pSharedToOpen, &hVfsDir); if (RT_SUCCESS(rc)) { *phVfsObj = RTVfsObjFromDir(hVfsDir); RTVfsDirRelease(hVfsDir); AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); } } else rc = VERR_ACCESS_DENIED; } else rc = VERR_IS_A_DIRECTORY; return rc; } } /* * Try open existing file. */ uint32_t offEntryInDir; bool fLong; FATDIRENTRY DirEntry; rc = rtFsFatDirShrd_FindEntry(pShared, pszEntry, &offEntryInDir, &fLong, &DirEntry); if (RT_SUCCESS(rc)) { switch (DirEntry.fAttrib & (FAT_ATTR_DIRECTORY | FAT_ATTR_VOLUME)) { case 0: if (fFlags & RTVFSOBJ_F_OPEN_FILE) { if ( !(DirEntry.fAttrib & FAT_ATTR_READONLY) || !(fOpen & RTFILE_O_WRITE)) { if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE) { RTVFSFILE hVfsFile; rc = rtFsFatFile_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir, fOpen, &hVfsFile); if (RT_SUCCESS(rc)) { *phVfsObj = RTVfsObjFromFile(hVfsFile); RTVfsFileRelease(hVfsFile); AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); } } else rc = VERR_ALREADY_EXISTS; } else rc = VERR_ACCESS_DENIED; } else rc = VERR_IS_A_FILE; break; case FAT_ATTR_DIRECTORY: if (fFlags & RTVFSOBJ_F_OPEN_DIRECTORY) { if ( !(DirEntry.fAttrib & FAT_ATTR_READONLY) || !(fOpen & RTFILE_O_WRITE)) { if ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE) { RTVFSDIR hVfsDir; rc = rtFsFatDir_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir, RTFSFAT_GET_CLUSTER(&DirEntry, pShared->Core.pVol), UINT64_MAX /*offDisk*/, DirEntry.cbFile, &hVfsDir); if (RT_SUCCESS(rc)) { *phVfsObj = RTVfsObjFromDir(hVfsDir); RTVfsDirRelease(hVfsDir); AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); } } else if ((fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE) rc = VERR_INVALID_FUNCTION; else rc = VERR_ALREADY_EXISTS; } else rc = VERR_ACCESS_DENIED; } else rc = VERR_IS_A_DIRECTORY; break; default: rc = VERR_PATH_NOT_FOUND; break; } } /* * Create a file or directory? */ else if (rc == VERR_FILE_NOT_FOUND) { if ( ( (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_OPEN_CREATE || (fOpen & RTFILE_O_ACTION_MASK) == RTFILE_O_CREATE_REPLACE) && (fFlags & RTVFSOBJ_F_CREATE_MASK) != RTVFSOBJ_F_CREATE_NOTHING) { if ((fFlags & RTVFSOBJ_F_CREATE_MASK) == RTVFSOBJ_F_CREATE_FILE) { rc = rtFsFatDirShrd_CreateEntry(pShared, pszEntry, FAT_ATTR_ARCHIVE, 0 /*cbInitial*/, &offEntryInDir, &DirEntry); if (RT_SUCCESS(rc)) { RTVFSFILE hVfsFile; rc = rtFsFatFile_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir, fOpen, &hVfsFile); if (RT_SUCCESS(rc)) { *phVfsObj = RTVfsObjFromFile(hVfsFile); RTVfsFileRelease(hVfsFile); AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); } } } else if ((fFlags & RTVFSOBJ_F_CREATE_MASK) == RTVFSOBJ_F_CREATE_DIRECTORY) { rc = rtFsFatDirShrd_CreateEntry(pShared, pszEntry, FAT_ATTR_ARCHIVE | FAT_ATTR_DIRECTORY, pShared->Core.pVol->cbCluster, &offEntryInDir, &DirEntry); if (RT_SUCCESS(rc)) { RTVFSDIR hVfsDir; rc = rtFsFatDir_New(pShared->Core.pVol, pShared, &DirEntry, offEntryInDir, RTFSFAT_GET_CLUSTER(&DirEntry, pShared->Core.pVol), UINT64_MAX /*offDisk*/, DirEntry.cbFile, &hVfsDir); if (RT_SUCCESS(rc)) { *phVfsObj = RTVfsObjFromDir(hVfsDir); RTVfsDirRelease(hVfsDir); AssertStmt(*phVfsObj != NIL_RTVFSOBJ, rc = VERR_INTERNAL_ERROR_3); } } } else rc = VERR_VFS_UNSUPPORTED_CREATE_TYPE; } } return rc; } /** * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink} */ static DECLCALLBACK(int) rtFsFatDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink) { RT_NOREF(pvThis, pszSymlink, phVfsSymlink); return VERR_NOT_SUPPORTED; } /** * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink} */ static DECLCALLBACK(int) rtFsFatDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget, RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink) { RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink); return VERR_NOT_SUPPORTED; } /** * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry} */ static DECLCALLBACK(int) rtFsFatDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType) { RT_NOREF(pvThis, pszEntry, fType); return VERR_NOT_IMPLEMENTED; } /** * @interface_method_impl{RTVFSDIROPS,pfnRenameEntry} */ static DECLCALLBACK(int) rtFsFatDir_RenameEntry(void *pvThis, const char *pszEntry, RTFMODE fType, const char *pszNewName) { RT_NOREF(pvThis, pszEntry, fType, pszNewName); return VERR_NOT_IMPLEMENTED; } /** * @interface_method_impl{RTVFSDIROPS,pfnRewindDir} */ static DECLCALLBACK(int) rtFsFatDir_RewindDir(void *pvThis) { PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis; pThis->offDir = 0; return VINF_SUCCESS; } /** * Calculates the UTF-8 length of the name in the given directory entry. * * @returns The length in characters (bytes), excluding terminator. * @param pShared The shared directory structure (for codepage). * @param pEntry The directory entry. */ static size_t rtFsFatDir_CalcUtf8LengthForDirEntry(PRTFSFATDIRSHRD pShared, PCFATDIRENTRY pEntry) { RT_NOREF(pShared); PCRTUTF16 g_pawcMap = &g_awchFatCp437Chars[0]; /* The base name (this won't work with DBCS, but that's not a concern at the moment). */ size_t offSrc = 8; while (offSrc > 1 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[offSrc - 1]])) offSrc--; size_t cchRet = 0; while (offSrc-- > 0) cchRet += RTStrCpSize(g_pawcMap[pEntry->achName[offSrc]]); /* Extension. */ offSrc = 11; while (offSrc > 8 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[offSrc - 1]])) offSrc--; if (offSrc > 8) { cchRet += 1; /* '.' */ while (offSrc-- > 8) cchRet += RTStrCpSize(g_pawcMap[pEntry->achName[offSrc]]); } return cchRet; } /** * Copies the name from the directory entry into a UTF-16 buffer. * * @returns Number of UTF-16 items written (excluding terminator). * @param pShared The shared directory structure (for codepage). * @param pEntry The directory entry. * @param pwszDst The destination buffer. * @param cwcDst The destination buffer size. */ static uint16_t rtFsFatDir_CopyDirEntryToUtf16(PRTFSFATDIRSHRD pShared, PCFATDIRENTRY pEntry, PRTUTF16 pwszDst, size_t cwcDst) { Assert(cwcDst > 0); RT_NOREF(pShared); PCRTUTF16 g_pawcMap = &g_awchFatCp437Chars[0]; /* The base name (this won't work with DBCS, but that's not a concern at the moment). */ size_t cchSrc = 8; while (cchSrc > 1 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[cchSrc - 1]])) cchSrc--; size_t offDst = 0; for (size_t offSrc = 0; offSrc < cchSrc; offSrc++) { AssertReturnStmt(offDst + 1 < cwcDst, pwszDst[cwcDst - 1] = '\0', (uint16_t)cwcDst); pwszDst[offDst++] = g_pawcMap[pEntry->achName[offSrc]]; } /* Extension. */ cchSrc = 3; while (cchSrc > 0 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[8 + cchSrc - 1]])) cchSrc--; if (cchSrc > 0) { AssertReturnStmt(offDst + 1 < cwcDst, pwszDst[cwcDst - 1] = '\0', (uint16_t)cwcDst); pwszDst[offDst++] = '.'; for (size_t offSrc = 0; offSrc < cchSrc; offSrc++) { AssertReturnStmt(offDst + 1 < cwcDst, pwszDst[cwcDst - 1] = '\0', (uint16_t)cwcDst); pwszDst[offDst++] = g_pawcMap[pEntry->achName[8 + offSrc]]; } } pwszDst[offDst] = '\0'; return (uint16_t)offDst; } /** * Copies the name from the directory entry into a UTF-8 buffer. * * @returns Number of UTF-16 items written (excluding terminator). * @param pShared The shared directory structure (for codepage). * @param pEntry The directory entry. * @param pszDst The destination buffer. * @param cbDst The destination buffer size. */ static uint16_t rtFsFatDir_CopyDirEntryToUtf8(PRTFSFATDIRSHRD pShared, PCFATDIRENTRY pEntry, char *pszDst, size_t cbDst) { Assert(cbDst > 0); RT_NOREF(pShared); PCRTUTF16 g_pawcMap = &g_awchFatCp437Chars[0]; /* The base name (this won't work with DBCS, but that's not a concern at the moment). */ size_t cchSrc = 8; while (cchSrc > 1 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[cchSrc - 1]])) cchSrc--; char * const pszDstEnd = pszDst + cbDst; char *pszCurDst = pszDst; for (size_t offSrc = 0; offSrc < cchSrc; offSrc++) { RTUNICP const uc = g_pawcMap[pEntry->achName[offSrc]]; size_t cbCp = RTStrCpSize(uc); AssertReturnStmt(cbCp < (size_t)(pszDstEnd - pszCurDst), *pszCurDst = '\0', (uint16_t)(pszDstEnd - pszCurDst)); pszCurDst = RTStrPutCp(pszCurDst, uc); } /* Extension. */ cchSrc = 3; while (cchSrc > 0 && RTUniCpIsSpace(g_pawcMap[pEntry->achName[8 + cchSrc - 1]])) cchSrc--; if (cchSrc > 0) { AssertReturnStmt(1U < (size_t)(pszDstEnd - pszCurDst), *pszCurDst = '\0', (uint16_t)(pszDstEnd - pszCurDst)); *pszCurDst++ = '.'; for (size_t offSrc = 0; offSrc < cchSrc; offSrc++) { RTUNICP const uc = g_pawcMap[pEntry->achName[8 + offSrc]]; size_t cbCp = RTStrCpSize(uc); AssertReturnStmt(cbCp < (size_t)(pszDstEnd - pszCurDst), *pszCurDst = '\0', (uint16_t)(pszDstEnd - pszCurDst)); pszCurDst = RTStrPutCp(pszCurDst, uc); } } *pszCurDst = '\0'; return (uint16_t)(pszDstEnd - pszCurDst); } /** * @interface_method_impl{RTVFSDIROPS,pfnReadDir} */ static DECLCALLBACK(int) rtFsFatDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAddAttr) { PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis; PRTFSFATDIRSHRD pShared = pThis->pShared; /* * Fake '.' and '..' entries (required for root, we do it everywhere). */ if (pThis->offDir < 2) { size_t cbNeeded = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[pThis->offDir + 2]); if (cbNeeded < *pcbDirEntry) *pcbDirEntry = cbNeeded; else { *pcbDirEntry = cbNeeded; return VERR_BUFFER_OVERFLOW; } int rc; if ( pThis->offDir == 0 || pShared->Core.pParentDir == NULL) rc = rtFsFatObj_QueryInfo(&pShared->Core, &pDirEntry->Info, enmAddAttr); else rc = rtFsFatObj_QueryInfo(&pShared->Core.pParentDir->Core, &pDirEntry->Info, enmAddAttr); pDirEntry->cwcShortName = 0; pDirEntry->wszShortName[0] = '\0'; pDirEntry->szName[0] = '.'; pDirEntry->szName[1] = '.'; pDirEntry->szName[++pThis->offDir] = '\0'; pDirEntry->cbName = pThis->offDir; return rc; } if ( pThis->offDir == 2 && pShared->cEntries >= 2) { /* Skip '.' and '..' entries if present. */ uint32_t uBufferLock = UINT32_MAX; uint32_t cEntries = 0; PCFATDIRENTRYUNION paEntries = NULL; int rc = rtFsFatDirShrd_GetEntriesAt(pShared, 0, &paEntries, &cEntries, &uBufferLock); if (RT_FAILURE(rc)) return rc; if ( (paEntries[0].Entry.fAttrib & FAT_ATTR_DIRECTORY) && memcmp(paEntries[0].Entry.achName, RT_STR_TUPLE(". ")) == 0) { if ( (paEntries[1].Entry.fAttrib & FAT_ATTR_DIRECTORY) && memcmp(paEntries[1].Entry.achName, RT_STR_TUPLE(".. ")) == 0) pThis->offDir += sizeof(paEntries[0]) * 2; else pThis->offDir += sizeof(paEntries[0]); } rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock); } /* * Scan the directory buffer by buffer. */ RTUTF16 wszName[260+1]; uint8_t bChecksum = UINT8_MAX; uint8_t idNextSlot = UINT8_MAX; size_t cwcName = 0; uint32_t offEntryInDir = pThis->offDir - 2; uint32_t const cbDir = pShared->Core.cbObject; Assert(RT_ALIGN_32(cbDir, sizeof(*pDirEntry)) == cbDir); AssertCompile(FATDIRNAMESLOT_MAX_SLOTS * FATDIRNAMESLOT_CHARS_PER_SLOT < RT_ELEMENTS(wszName)); wszName[260] = '\0'; while (offEntryInDir < cbDir) { /* Get chunk of entries starting at offEntryInDir. */ uint32_t uBufferLock = UINT32_MAX; uint32_t cEntries = 0; PCFATDIRENTRYUNION paEntries = NULL; int rc = rtFsFatDirShrd_GetEntriesAt(pShared, offEntryInDir, &paEntries, &cEntries, &uBufferLock); if (RT_FAILURE(rc)) return rc; /* * Now work thru each of the entries. */ for (uint32_t iEntry = 0; iEntry < cEntries; iEntry++, offEntryInDir += sizeof(FATDIRENTRY)) { switch ((uint8_t)paEntries[iEntry].Entry.achName[0]) { default: break; case FATDIRENTRY_CH0_DELETED: cwcName = 0; continue; case FATDIRENTRY_CH0_END_OF_DIR: if (pShared->Core.pVol->enmBpbVersion >= RTFSFATBPBVER_DOS_2_0) { pThis->offDir = cbDir + 2; rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock); return VERR_NO_MORE_FILES; } cwcName = 0; break; /* Technically a valid entry before DOS 2.0, or so some claim. */ } /* * Check for long filename slot. */ if ( paEntries[iEntry].Slot.fAttrib == FAT_ATTR_NAME_SLOT && paEntries[iEntry].Slot.idxZero == 0 && paEntries[iEntry].Slot.fZero == 0 && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) <= FATDIRNAMESLOT_HIGHEST_SLOT_ID && (paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG) != 0) { /* New slot? */ if (paEntries[iEntry].Slot.idSlot & FATDIRNAMESLOT_FIRST_SLOT_FLAG) { idNextSlot = paEntries[iEntry].Slot.idSlot & ~FATDIRNAMESLOT_FIRST_SLOT_FLAG; bChecksum = paEntries[iEntry].Slot.bChecksum; cwcName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT; wszName[cwcName] = '\0'; } /* Is valid next entry? */ else if ( paEntries[iEntry].Slot.idSlot == idNextSlot && paEntries[iEntry].Slot.bChecksum == bChecksum) { /* likely */ } else cwcName = 0; if (cwcName) { idNextSlot--; size_t offName = idNextSlot * FATDIRNAMESLOT_CHARS_PER_SLOT; memcpy(&wszName[offName], paEntries[iEntry].Slot.awcName0, sizeof(paEntries[iEntry].Slot.awcName0)); memcpy(&wszName[offName + 5], paEntries[iEntry].Slot.awcName1, sizeof(paEntries[iEntry].Slot.awcName1)); memcpy(&wszName[offName + 5 + 6], paEntries[iEntry].Slot.awcName2, sizeof(paEntries[iEntry].Slot.awcName2)); } } /* * Got a regular directory entry. Try return it to the caller if not volume label. */ else if (!(paEntries[iEntry].Entry.fAttrib & FAT_ATTR_VOLUME)) { /* Do the length calc and check for overflows. */ bool fLongName = false; size_t cchName = 0; if ( cwcName != 0 && idNextSlot == 0 && rtFsFatDir_CalcChecksum(&paEntries[iEntry].Entry) == bChecksum) { rc = RTUtf16CalcUtf8LenEx(wszName, cwcName, &cchName); if (RT_SUCCESS(rc)) fLongName = true; } if (!fLongName) cchName = rtFsFatDir_CalcUtf8LengthForDirEntry(pShared, &paEntries[iEntry].Entry); size_t cbNeeded = RT_UOFFSETOF_DYN(RTDIRENTRYEX, szName[cchName + 1]); if (cbNeeded <= *pcbDirEntry) *pcbDirEntry = cbNeeded; else { *pcbDirEntry = cbNeeded; return VERR_BUFFER_OVERFLOW; } /* To avoid duplicating code in rtFsFatObj_InitFromDirRec and rtFsFatObj_QueryInfo, we create a dummy RTFSFATOBJ on the stack. */ RTFSFATOBJ TmpObj; RT_ZERO(TmpObj); rtFsFatObj_InitFromDirEntry(&TmpObj, &paEntries[iEntry].Entry, offEntryInDir, pShared->Core.pVol); rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock); rc = rtFsFatObj_QueryInfo(&TmpObj, &pDirEntry->Info, enmAddAttr); /* Copy out the names. */ pDirEntry->cbName = (uint16_t)cchName; if (fLongName) { char *pszDst = &pDirEntry->szName[0]; int rc2 = RTUtf16ToUtf8Ex(wszName, cwcName, &pszDst, cchName + 1, NULL); AssertRC(rc2); pDirEntry->cwcShortName = rtFsFatDir_CopyDirEntryToUtf16(pShared, &paEntries[iEntry].Entry, pDirEntry->wszShortName, RT_ELEMENTS(pDirEntry->wszShortName)); } else { rtFsFatDir_CopyDirEntryToUtf8(pShared, &paEntries[iEntry].Entry, &pDirEntry->szName[0], cchName + 1); pDirEntry->wszShortName[0] = '\0'; pDirEntry->cwcShortName = 0; } if (RT_SUCCESS(rc)) pThis->offDir = offEntryInDir + sizeof(paEntries[iEntry]) + 2; Assert(RTStrValidateEncoding(pDirEntry->szName) == VINF_SUCCESS); return rc; } else cwcName = 0; } rtFsFatDirShrd_ReleaseBufferAfterReading(pShared, uBufferLock); } pThis->offDir = cbDir + 2; return VERR_NO_MORE_FILES; } /** * FAT directory operations. */ static const RTVFSDIROPS g_rtFsFatDirOps = { { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_DIR, "FatDir", rtFsFatDir_Close, rtFsFatDir_QueryInfo, NULL, RTVFSOBJOPS_VERSION }, RTVFSDIROPS_VERSION, 0, { /* ObjSet */ RTVFSOBJSETOPS_VERSION, RT_UOFFSETOF(RTVFSDIROPS, ObjSet) - RT_UOFFSETOF(RTVFSDIROPS, Obj), rtFsFatDir_SetMode, rtFsFatDir_SetTimes, rtFsFatDir_SetOwner, RTVFSOBJSETOPS_VERSION }, rtFsFatDir_Open, NULL /* pfnFollowAbsoluteSymlink */, NULL /* pfnOpenFile*/, NULL /* pfnOpenDir */, NULL /* pfnCreateDir */, rtFsFatDir_OpenSymlink, rtFsFatDir_CreateSymlink, NULL /* pfnQueryEntryInfo */, rtFsFatDir_UnlinkEntry, rtFsFatDir_RenameEntry, rtFsFatDir_RewindDir, rtFsFatDir_ReadDir, RTVFSDIROPS_VERSION, }; /** * Adds an open child to the parent directory. * * Maintains an additional reference to the parent dir to prevent it from going * away. If @a pDir is the root directory, it also ensures the volume is * referenced and sticks around until the last open object is gone. * * @param pDir The directory. * @param pChild The child being opened. * @sa rtFsFatDirShrd_RemoveOpenChild */ static void rtFsFatDirShrd_AddOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild) { rtFsFatDirShrd_Retain(pDir); RTListAppend(&pDir->OpenChildren, &pChild->Entry); pChild->pParentDir = pDir; } /** * Removes an open child to the parent directory. * * @param pDir The directory. * @param pChild The child being removed. * * @remarks This is the very last thing you do as it may cause a few other * objects to be released recursively (parent dir and the volume). * * @sa rtFsFatDirShrd_AddOpenChild */ static void rtFsFatDirShrd_RemoveOpenChild(PRTFSFATDIRSHRD pDir, PRTFSFATOBJ pChild) { AssertReturnVoid(pChild->pParentDir == pDir); RTListNodeRemove(&pChild->Entry); pChild->pParentDir = NULL; rtFsFatDirShrd_Release(pDir); } /** * Instantiates a new shared directory instance. * * @returns IPRT status code. * @param pThis The FAT volume instance. * @param pParentDir The parent directory. This is NULL for the root * directory. * @param pDirEntry The parent directory entry. This is NULL for the * root directory. * @param offEntryInDir The byte offset of the directory entry in the parent * directory. UINT32_MAX if root directory. * @param idxCluster The cluster where the directory content is to be * found. This can be UINT32_MAX if a root FAT12/16 * directory. * @param offDisk The disk byte offset of the FAT12/16 root directory. * This is UINT64_MAX if idxCluster is given. * @param cbDir The size of the directory. * @param ppSharedDir Where to return shared FAT directory instance. */ static int rtFsFatDirShrd_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir, uint32_t idxCluster, uint64_t offDisk, uint32_t cbDir, PRTFSFATDIRSHRD *ppSharedDir) { Assert((idxCluster == UINT32_MAX) != (offDisk == UINT64_MAX)); Assert((pDirEntry == NULL) == (offEntryInDir == UINT32_MAX)); *ppSharedDir = NULL; int rc = VERR_NO_MEMORY; PRTFSFATDIRSHRD pShared = (PRTFSFATDIRSHRD)RTMemAllocZ(sizeof(*pShared)); if (pShared) { /* * Initialize it all so rtFsFatDir_Close doesn't trip up in anyway. */ RTListInit(&pShared->OpenChildren); if (pDirEntry) rtFsFatObj_InitFromDirEntry(&pShared->Core, pDirEntry, offEntryInDir, pThis); else rtFsFatObj_InitDummy(&pShared->Core, cbDir, RTFS_TYPE_DIRECTORY | RTFS_DOS_DIRECTORY | RTFS_UNIX_ALL_PERMS, pThis); pShared->cEntries = cbDir / sizeof(FATDIRENTRY); pShared->fIsLinearRootDir = idxCluster == UINT32_MAX; pShared->fFullyBuffered = pShared->fIsLinearRootDir; pShared->paEntries = NULL; pShared->offEntriesOnDisk = UINT64_MAX; if (pShared->fFullyBuffered) pShared->cbAllocatedForEntries = RT_ALIGN_32(cbDir, pThis->cbSector); else pShared->cbAllocatedForEntries = pThis->cbSector; /* * If clustered backing, read the chain and see if we cannot still do the full buffering. */ if (idxCluster != UINT32_MAX) { rc = rtFsFatClusterMap_ReadClusterChain(pThis, idxCluster, &pShared->Core.Clusters); if (RT_SUCCESS(rc)) { if ( pShared->Core.Clusters.cClusters >= 1 && pShared->Core.Clusters.cbChain <= _64K && rtFsFatChain_IsContiguous(&pShared->Core.Clusters)) { Assert(pShared->Core.Clusters.cbChain >= cbDir); pShared->cbAllocatedForEntries = pShared->Core.Clusters.cbChain; pShared->fFullyBuffered = true; } /* DOS doesn't set a size on directores, so use the cluster length instead. */ if ( cbDir == 0 && pShared->Core.Clusters.cbChain > 0) { cbDir = pShared->Core.Clusters.cbChain; pShared->Core.cbObject = cbDir; pShared->cEntries = cbDir / sizeof(FATDIRENTRY); if (pShared->fFullyBuffered) pShared->cbAllocatedForEntries = RT_ALIGN_32(cbDir, pThis->cbSector); } } } else { rtFsFatChain_InitEmpty(&pShared->Core.Clusters, pThis); rc = VINF_SUCCESS; } if (RT_SUCCESS(rc)) { /* * Allocate and initialize the buffering. Fill the buffer. */ pShared->paEntries = (PFATDIRENTRYUNION)RTMemAlloc(pShared->cbAllocatedForEntries); if (!pShared->paEntries) { if (pShared->fFullyBuffered && !pShared->fIsLinearRootDir) { pShared->fFullyBuffered = false; pShared->cbAllocatedForEntries = pThis->cbSector; pShared->paEntries = (PFATDIRENTRYUNION)RTMemAlloc(pShared->cbAllocatedForEntries); } if (!pShared->paEntries) rc = VERR_NO_MEMORY; } if (RT_SUCCESS(rc)) { if (pShared->fFullyBuffered) { pShared->u.Full.cDirtySectors = 0; pShared->u.Full.cSectors = pShared->cbAllocatedForEntries / pThis->cbSector; pShared->u.Full.pbDirtySectors = (uint8_t *)RTMemAllocZ((pShared->u.Full.cSectors + 63) / 8); if (pShared->u.Full.pbDirtySectors) pShared->offEntriesOnDisk = offDisk != UINT64_MAX ? offDisk : rtFsFatClusterToDiskOffset(pThis, idxCluster); else rc = VERR_NO_MEMORY; } else { pShared->offEntriesOnDisk = rtFsFatClusterToDiskOffset(pThis, idxCluster); pShared->u.Simple.offInDir = 0; pShared->u.Simple.fDirty = false; } if (RT_SUCCESS(rc)) rc = RTVfsFileReadAt(pThis->hVfsBacking, pShared->offEntriesOnDisk, pShared->paEntries, pShared->cbAllocatedForEntries, NULL); if (RT_SUCCESS(rc)) { /* * Link into parent directory so we can use it to update * our directory entry. */ if (pParentDir) rtFsFatDirShrd_AddOpenChild(pParentDir, &pShared->Core); *ppSharedDir = pShared; return VINF_SUCCESS; } } /* Free the buffer on failure so rtFsFatDir_Close doesn't try do anything with it. */ RTMemFree(pShared->paEntries); pShared->paEntries = NULL; } Assert(pShared->Core.cRefs == 1); rtFsFatDirShrd_Release(pShared); } return rc; } /** * Instantiates a new directory with a shared structure presupplied. * * @returns IPRT status code. * @param pThis The FAT volume instance. * @param pShared Referenced pointer to the shared structure. The * reference is always CONSUMED. * @param phVfsDir Where to return the directory handle. */ static int rtFsFatDir_NewWithShared(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pShared, PRTVFSDIR phVfsDir) { /* * Create VFS object around the shared structure. */ PRTFSFATDIR pNewDir; int rc = RTVfsNewDir(&g_rtFsFatDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf, NIL_RTVFSLOCK /*use volume lock*/, phVfsDir, (void **)&pNewDir); if (RT_SUCCESS(rc)) { /* * Look for existing shared object, create a new one if necessary. * We CONSUME a reference to pShared here. */ pNewDir->offDir = 0; pNewDir->pShared = pShared; return VINF_SUCCESS; } rtFsFatDirShrd_Release(pShared); *phVfsDir = NIL_RTVFSDIR; return rc; } /** * Instantiates a new directory VFS, creating the shared structure as necessary. * * @returns IPRT status code. * @param pThis The FAT volume instance. * @param pParentDir The parent directory. This is NULL for the root * directory. * @param pDirEntry The parent directory entry. This is NULL for the * root directory. * @param offEntryInDir The byte offset of the directory entry in the parent * directory. UINT32_MAX if root directory. * @param idxCluster The cluster where the directory content is to be * found. This can be UINT32_MAX if a root FAT12/16 * directory. * @param offDisk The disk byte offset of the FAT12/16 root directory. * This is UINT64_MAX if idxCluster is given. * @param cbDir The size of the directory. * @param phVfsDir Where to return the directory handle. */ static int rtFsFatDir_New(PRTFSFATVOL pThis, PRTFSFATDIRSHRD pParentDir, PCFATDIRENTRY pDirEntry, uint32_t offEntryInDir, uint32_t idxCluster, uint64_t offDisk, uint32_t cbDir, PRTVFSDIR phVfsDir) { /* * Look for existing shared object, create a new one if necessary. */ PRTFSFATDIRSHRD pShared = (PRTFSFATDIRSHRD)rtFsFatDirShrd_LookupShared(pParentDir, offEntryInDir); if (!pShared) { int rc = rtFsFatDirShrd_New(pThis, pParentDir, pDirEntry, offEntryInDir, idxCluster, offDisk, cbDir, &pShared); if (RT_FAILURE(rc)) { *phVfsDir = NIL_RTVFSDIR; return rc; } } return rtFsFatDir_NewWithShared(pThis, pShared, phVfsDir); } /** * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose} */ static DECLCALLBACK(int) rtFsFatVol_Close(void *pvThis) { PRTFSFATVOL pThis = (PRTFSFATVOL)pvThis; LogFlow(("rtFsFatVol_Close(%p)\n", pThis)); int rc = VINF_SUCCESS; if (pThis->pRootDir != NULL) { Assert(RTListIsEmpty(&pThis->pRootDir->OpenChildren)); Assert(pThis->pRootDir->Core.cRefs == 1); rc = rtFsFatDirShrd_Release(pThis->pRootDir); pThis->pRootDir = NULL; } int rc2 = rtFsFatClusterMap_Destroy(pThis); if (RT_SUCCESS(rc)) rc = rc2; RTVfsFileRelease(pThis->hVfsBacking); pThis->hVfsBacking = NIL_RTVFSFILE; return rc; } /** * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo} */ static DECLCALLBACK(int) rtFsFatVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr) { RT_NOREF(pvThis, pObjInfo, enmAddAttr); return VERR_WRONG_TYPE; } /** * @interface_method_impl{RTVFSOPS,pfnOpenRoot} */ static DECLCALLBACK(int) rtFsFatVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir) { PRTFSFATVOL pThis = (PRTFSFATVOL)pvThis; rtFsFatDirShrd_Retain(pThis->pRootDir); /* consumed by the next call */ return rtFsFatDir_NewWithShared(pThis, pThis->pRootDir, phVfsDir); } /** * @interface_method_impl{RTVFSOPS,pfnQueryRangeState} */ static DECLCALLBACK(int) rtFsFatVol_QueryRangeState(void *pvThis, uint64_t off, size_t cb, bool *pfUsed) { RT_NOREF(pvThis, off, cb, pfUsed); return VERR_NOT_IMPLEMENTED; } DECL_HIDDEN_CONST(const RTVFSOPS) g_rtFsFatVolOps = { { /* Obj */ RTVFSOBJOPS_VERSION, RTVFSOBJTYPE_VFS, "FatVol", rtFsFatVol_Close, rtFsFatVol_QueryInfo, NULL, RTVFSOBJOPS_VERSION }, RTVFSOPS_VERSION, 0 /* fFeatures */, rtFsFatVol_OpenRoot, rtFsFatVol_QueryRangeState, RTVFSOPS_VERSION }; /** * Tries to detect a DOS 1.x formatted image and fills in the BPB fields. * * There is no BPB here, but fortunately, there isn't much variety. * * @returns IPRT status code. * @param pThis The FAT volume instance, BPB derived fields are filled * in on success. * @param pBootSector The boot sector. * @param pbFatSector Points to the FAT sector, or whatever is 512 bytes after * the boot sector. * @param pErrInfo Where to return additional error information. */ static int rtFsFatVolTryInitDos1x(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, uint8_t const *pbFatSector, PRTERRINFO pErrInfo) { /* * PC-DOS 1.0 does a 2fh byte short jump w/o any NOP following it. * Instead the following are three words and a 9 byte build date * string. The remaining space is zero filled. * * Note! No idea how this would look like for 8" floppies, only got 5"1/4'. * * ASSUME all non-BPB disks are using this format. */ if ( pBootSector->abJmp[0] != 0xeb /* jmp rel8 */ || pBootSector->abJmp[1] < 0x2f || pBootSector->abJmp[1] >= 0x80 || pBootSector->abJmp[2] == 0x90 /* nop */) return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "No DOS v1.0 bootsector either - invalid jmp: %.3Rhxs", pBootSector->abJmp); uint32_t const offJump = 2 + pBootSector->abJmp[1]; uint32_t const offFirstZero = 2 /*jmp */ + 3 * 2 /* words */ + 9 /* date string */; Assert(offFirstZero >= RT_UOFFSETOF(FATBOOTSECTOR, Bpb)); uint32_t const cbZeroPad = RT_MIN(offJump - offFirstZero, sizeof(pBootSector->Bpb.Bpb20) - (offFirstZero - RT_UOFFSETOF(FATBOOTSECTOR, Bpb))); if (!ASMMemIsAllU8((uint8_t const *)pBootSector + offFirstZero, cbZeroPad, 0)) return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "No DOS v1.0 bootsector either - expected zero padding %#x LB %#x: %.*Rhxs", offFirstZero, cbZeroPad, cbZeroPad, (uint8_t const *)pBootSector + offFirstZero); /* * Check the FAT ID so we can tell if this is double or single sided, * as well as being a valid FAT12 start. */ if ( (pbFatSector[0] != 0xfe && pbFatSector[0] != 0xff) || pbFatSector[1] != 0xff || pbFatSector[2] != 0xff) return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "No DOS v1.0 bootsector either - unexpected start of FAT: %.3Rhxs", pbFatSector); /* * Fixed DOS 1.0 config. */ pThis->enmFatType = RTFSFATTYPE_FAT12; pThis->enmBpbVersion = RTFSFATBPBVER_NO_BPB; pThis->bMedia = pbFatSector[0]; pThis->cReservedSectors = 1; pThis->cbSector = 512; pThis->cbCluster = pThis->bMedia == 0xfe ? 1024 : 512; pThis->cFats = 2; pThis->cbFat = 512; pThis->aoffFats[0] = pThis->offBootSector + pThis->cReservedSectors * 512; pThis->aoffFats[1] = pThis->aoffFats[0] + pThis->cbFat; pThis->offRootDir = pThis->aoffFats[1] + pThis->cbFat; pThis->cRootDirEntries = 512; pThis->offFirstCluster = pThis->offRootDir + RT_ALIGN_32(pThis->cRootDirEntries * sizeof(FATDIRENTRY), pThis->cbSector); pThis->cbTotalSize = pThis->bMedia == 0xfe ? 8 * 1 * 40 * 512 : 8 * 2 * 40 * 512; pThis->cClusters = (pThis->cbTotalSize - (pThis->offFirstCluster - pThis->offBootSector)) / pThis->cbCluster; return VINF_SUCCESS; } /** * Worker for rtFsFatVolTryInitDos2Plus that handles remaining BPB fields. * * @returns IPRT status code. * @param pThis The FAT volume instance, BPB derived fields are filled * in on success. * @param pBootSector The boot sector. * @param fMaybe331 Set if it could be a DOS v3.31 BPB. * @param pErrInfo Where to return additional error information. */ static int rtFsFatVolTryInitDos2PlusBpb(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, bool fMaybe331, PRTERRINFO pErrInfo) { pThis->enmBpbVersion = RTFSFATBPBVER_DOS_2_0; /* * Figure total sector count. Could both be zero, in which case we have to * fall back on the size of the backing stuff. */ if (pBootSector->Bpb.Bpb20.cTotalSectors16 != 0) pThis->cbTotalSize = pBootSector->Bpb.Bpb20.cTotalSectors16 * pThis->cbSector; else if ( pBootSector->Bpb.Bpb331.cTotalSectors32 != 0 && fMaybe331) { pThis->enmBpbVersion = RTFSFATBPBVER_DOS_3_31; pThis->cbTotalSize = pBootSector->Bpb.Bpb331.cTotalSectors32 * (uint64_t)pThis->cbSector; } else pThis->cbTotalSize = pThis->cbBacking - pThis->offBootSector; if (pThis->cReservedSectors * pThis->cbSector >= pThis->cbTotalSize) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT12/16 total or reserved sector count: %#x vs %#x", pThis->cReservedSectors, pThis->cbTotalSize / pThis->cbSector); /* * The fat size. Complete FAT offsets. */ if ( pBootSector->Bpb.Bpb20.cSectorsPerFat == 0 || ((uint32_t)pBootSector->Bpb.Bpb20.cSectorsPerFat * pThis->cFats + 1) * pThis->cbSector > pThis->cbTotalSize) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT12/16 sectors per FAT: %#x (total sectors %#RX64)", pBootSector->Bpb.Bpb20.cSectorsPerFat, pThis->cbTotalSize / pThis->cbSector); pThis->cbFat = pBootSector->Bpb.Bpb20.cSectorsPerFat * pThis->cbSector; AssertReturn(pThis->cFats < RT_ELEMENTS(pThis->aoffFats), VERR_VFS_BOGUS_FORMAT); for (unsigned iFat = 1; iFat <= pThis->cFats; iFat++) pThis->aoffFats[iFat] = pThis->aoffFats[iFat - 1] + pThis->cbFat; /* * Do root directory calculations. */ pThis->idxRootDirCluster = UINT32_MAX; pThis->offRootDir = pThis->aoffFats[pThis->cFats]; if (pThis->cRootDirEntries == 0) return RTErrInfoSet(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Zero FAT12/16 root directory size"); pThis->cbRootDir = pThis->cRootDirEntries * sizeof(FATDIRENTRY); pThis->cbRootDir = RT_ALIGN_32(pThis->cbRootDir, pThis->cbSector); /* * First cluster and cluster count checks and calcs. Determin FAT type. */ pThis->offFirstCluster = pThis->offRootDir + pThis->cbRootDir; uint64_t cbSystemStuff = pThis->offFirstCluster - pThis->offBootSector; if (cbSystemStuff >= pThis->cbTotalSize) return RTErrInfoSet(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT12/16 total size, root dir, or fat size"); pThis->cClusters = (pThis->cbTotalSize - cbSystemStuff) / pThis->cbCluster; if (pThis->cClusters >= FAT_MAX_FAT16_DATA_CLUSTERS) { pThis->cClusters = FAT_MAX_FAT16_DATA_CLUSTERS; pThis->enmFatType = RTFSFATTYPE_FAT16; } else if (pThis->cClusters >= FAT_MIN_FAT16_DATA_CLUSTERS) pThis->enmFatType = RTFSFATTYPE_FAT16; else pThis->enmFatType = RTFSFATTYPE_FAT12; /** @todo Not sure if this is entirely the right way to go about it... */ uint32_t cClustersPerFat; if (pThis->enmFatType == RTFSFATTYPE_FAT16) cClustersPerFat = pThis->cbFat / 2; else cClustersPerFat = pThis->cbFat * 2 / 3; if (pThis->cClusters > cClustersPerFat) pThis->cClusters = cClustersPerFat; return VINF_SUCCESS; } /** * Worker for rtFsFatVolTryInitDos2Plus and rtFsFatVolTryInitDos2PlusFat32 that * handles common extended BPBs fields. * * @returns IPRT status code. * @param pThis The FAT volume instance. * @param bExtSignature The extended BPB signature. * @param uSerialNumber The serial number. * @param pachLabel Pointer to the volume label field. * @param pachType Pointer to the file system type field. */ static void rtFsFatVolInitCommonEbpbBits(PRTFSFATVOL pThis, uint8_t bExtSignature, uint32_t uSerialNumber, char const *pachLabel, char const *pachType) { pThis->uSerialNo = uSerialNumber; if (bExtSignature == FATEBPB_SIGNATURE) { memcpy(pThis->szLabel, pachLabel, RT_SIZEOFMEMB(FATEBPB, achLabel)); pThis->szLabel[RT_SIZEOFMEMB(FATEBPB, achLabel)] = '\0'; RTStrStrip(pThis->szLabel); memcpy(pThis->szType, pachType, RT_SIZEOFMEMB(FATEBPB, achType)); pThis->szType[RT_SIZEOFMEMB(FATEBPB, achType)] = '\0'; RTStrStrip(pThis->szType); } else { pThis->szLabel[0] = '\0'; pThis->szType[0] = '\0'; } } /** * Worker for rtFsFatVolTryInitDos2Plus that deals with FAT32. * * @returns IPRT status code. * @param pThis The FAT volume instance, BPB derived fields are filled * in on success. * @param pBootSector The boot sector. * @param pErrInfo Where to return additional error information. */ static int rtFsFatVolTryInitDos2PlusFat32(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, PRTERRINFO pErrInfo) { pThis->enmFatType = RTFSFATTYPE_FAT32; pThis->enmBpbVersion = pBootSector->Bpb.Fat32Ebpb.bExtSignature == FATEBPB_SIGNATURE ? RTFSFATBPBVER_FAT32_29 : RTFSFATBPBVER_FAT32_28; pThis->fFat32Flags = pBootSector->Bpb.Fat32Ebpb.fFlags; if (pBootSector->Bpb.Fat32Ebpb.uVersion != FAT32EBPB_VERSION_0_0) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Unsupported FAT32 version: %d.%d (%#x)", RT_HI_U8(pBootSector->Bpb.Fat32Ebpb.uVersion), RT_LO_U8(pBootSector->Bpb.Fat32Ebpb.uVersion), pBootSector->Bpb.Fat32Ebpb.uVersion); /* * Figure total sector count. We expected it to be filled in. */ bool fUsing64BitTotalSectorCount = false; if (pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors16 != 0) pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors16 * pThis->cbSector; else if (pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors32 != 0) pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors32 * (uint64_t)pThis->cbSector; else if ( pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 <= UINT64_MAX / 512 && pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 > 3 && pBootSector->Bpb.Fat32Ebpb.bExtSignature != FATEBPB_SIGNATURE_OLD) { pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 * pThis->cbSector; fUsing64BitTotalSectorCount = true; } else return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "FAT32 total sector count out of range: %#RX64", pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64); if (pThis->cReservedSectors * pThis->cbSector >= pThis->cbTotalSize) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT32 total or reserved sector count: %#x vs %#x", pThis->cReservedSectors, pThis->cbTotalSize / pThis->cbSector); /* * Fat size. We check the 16-bit field even if it probably should be zero all the time. */ if (pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat != 0) { if ( pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 != 0 && pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 != pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Both 16-bit and 32-bit FAT size fields are set: %#RX16 vs %#RX32", pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat, pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32); pThis->cbFat = pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat * pThis->cbSector; } else { uint64_t cbFat = pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 * (uint64_t)pThis->cbSector; if ( cbFat == 0 || cbFat >= FAT_MAX_FAT32_TOTAL_CLUSTERS * 4 + pThis->cbSector * 16) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus 32-bit FAT size: %#RX32", pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32); pThis->cbFat = (uint32_t)cbFat; } /* * Complete the FAT offsets and first cluster offset, then calculate number * of data clusters. */ AssertReturn(pThis->cFats < RT_ELEMENTS(pThis->aoffFats), VERR_VFS_BOGUS_FORMAT); for (unsigned iFat = 1; iFat <= pThis->cFats; iFat++) pThis->aoffFats[iFat] = pThis->aoffFats[iFat - 1] + pThis->cbFat; pThis->offFirstCluster = pThis->aoffFats[pThis->cFats]; if (pThis->offFirstCluster - pThis->offBootSector >= pThis->cbTotalSize) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus 32-bit FAT size or total sector count: cFats=%d cbFat=%#x cbTotalSize=%#x", pThis->cFats, pThis->cbFat, pThis->cbTotalSize); uint64_t cClusters = (pThis->cbTotalSize - (pThis->offFirstCluster - pThis->offBootSector)) / pThis->cbCluster; if (cClusters <= FAT_MAX_FAT32_DATA_CLUSTERS) pThis->cClusters = (uint32_t)cClusters; else pThis->cClusters = FAT_MAX_FAT32_DATA_CLUSTERS; if (pThis->cClusters > (pThis->cbFat / 4 - FAT_FIRST_DATA_CLUSTER)) pThis->cClusters = (pThis->cbFat / 4 - FAT_FIRST_DATA_CLUSTER); /* * Root dir cluster. */ if ( pBootSector->Bpb.Fat32Ebpb.uRootDirCluster < FAT_FIRST_DATA_CLUSTER || pBootSector->Bpb.Fat32Ebpb.uRootDirCluster >= pThis->cClusters) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT32 root directory cluster: %#x", pBootSector->Bpb.Fat32Ebpb.uRootDirCluster); pThis->idxRootDirCluster = pBootSector->Bpb.Fat32Ebpb.uRootDirCluster; pThis->offRootDir = pThis->offFirstCluster + (pBootSector->Bpb.Fat32Ebpb.uRootDirCluster - FAT_FIRST_DATA_CLUSTER) * pThis->cbCluster; /* * Info sector. */ if ( pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo == 0 || pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo == UINT16_MAX) pThis->offFat32InfoSector = UINT64_MAX; else if (pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo >= pThis->cReservedSectors) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT32 info sector number: %#x (reserved sectors %#x)", pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo, pThis->cReservedSectors); else { pThis->offFat32InfoSector = pThis->cbSector * pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo + pThis->offBootSector; int rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->offFat32InfoSector, &pThis->Fat32InfoSector, sizeof(pThis->Fat32InfoSector), NULL); if (RT_FAILURE(rc)) return RTErrInfoSetF(pErrInfo, rc, "Failed to read FAT32 info sector at offset %#RX64", pThis->offFat32InfoSector); if ( pThis->Fat32InfoSector.uSignature1 != FAT32INFOSECTOR_SIGNATURE_1 || pThis->Fat32InfoSector.uSignature2 != FAT32INFOSECTOR_SIGNATURE_2 || pThis->Fat32InfoSector.uSignature3 != FAT32INFOSECTOR_SIGNATURE_3) return RTErrInfoSetF(pErrInfo, rc, "FAT32 info sector signature mismatch: %#x %#x %#x", pThis->Fat32InfoSector.uSignature1, pThis->Fat32InfoSector.uSignature2, pThis->Fat32InfoSector.uSignature3); } /* * Boot sector copy. */ if ( pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo == 0 || pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo == UINT16_MAX) { pThis->cBootSectorCopies = 0; pThis->offBootSectorCopies = UINT64_MAX; } else if (pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo >= pThis->cReservedSectors) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT32 info boot sector copy location: %#x (reserved sectors %#x)", pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo, pThis->cReservedSectors); else { /** @todo not sure if cbSector is correct here. */ pThis->cBootSectorCopies = 3; if ( (uint32_t)pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo + pThis->cBootSectorCopies > pThis->cReservedSectors) pThis->cBootSectorCopies = (uint8_t)(pThis->cReservedSectors - pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo); pThis->offBootSectorCopies = pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo * pThis->cbSector + pThis->offBootSector; if ( pThis->offFat32InfoSector != UINT64_MAX && pThis->offFat32InfoSector - pThis->offBootSectorCopies < (uint64_t)(pThis->cBootSectorCopies * pThis->cbSector)) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "FAT32 info sector and boot sector copies overlap: %#x vs %#x", pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo, pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo); } /* * Serial number, label and type. */ rtFsFatVolInitCommonEbpbBits(pThis, pBootSector->Bpb.Fat32Ebpb.bExtSignature, pBootSector->Bpb.Fat32Ebpb.uSerialNumber, pBootSector->Bpb.Fat32Ebpb.achLabel, fUsing64BitTotalSectorCount ? pBootSector->achOemName : pBootSector->Bpb.Fat32Ebpb.achLabel); if (pThis->szType[0] == '\0') memcpy(pThis->szType, "FAT32", 6); return VINF_SUCCESS; } /** * Tries to detect a DOS 2.0+ formatted image and fills in the BPB fields. * * We ASSUME BPB here, but need to figure out which version of the BPB it is, * which is lots of fun. * * @returns IPRT status code. * @param pThis The FAT volume instance, BPB derived fields are filled * in on success. * @param pBootSector The boot sector. * @param pbFatSector Points to the FAT sector, or whatever is 512 bytes after * the boot sector. On successful return it will contain * the first FAT sector. * @param pErrInfo Where to return additional error information. */ static int rtFsFatVolTryInitDos2Plus(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, uint8_t *pbFatSector, PRTERRINFO pErrInfo) { /* * Check if we've got a known jump instruction first, because that will * give us a max (E)BPB size hint. */ uint8_t offJmp = UINT8_MAX; if ( pBootSector->abJmp[0] == 0xeb && pBootSector->abJmp[1] <= 0x7f) offJmp = pBootSector->abJmp[1] + 2; else if ( pBootSector->abJmp[0] == 0x90 && pBootSector->abJmp[1] == 0xeb && pBootSector->abJmp[2] <= 0x7f) offJmp = pBootSector->abJmp[2] + 3; else if ( pBootSector->abJmp[0] == 0xe9 && pBootSector->abJmp[2] <= 0x7f) offJmp = RT_MIN(127, RT_MAKE_U16(pBootSector->abJmp[1], pBootSector->abJmp[2])); uint8_t const cbMaxBpb = offJmp - RT_UOFFSETOF(FATBOOTSECTOR, Bpb); /* * Do the basic DOS v2.0 BPB fields. */ if (cbMaxBpb < sizeof(FATBPB20)) return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, but jmp too short for any BPB: %#x (max %#x BPB)", offJmp, cbMaxBpb); if (pBootSector->Bpb.Bpb20.cFats == 0) return RTErrInfoSet(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, number of FATs is zero, so not FAT file system"); if (pBootSector->Bpb.Bpb20.cFats > 4) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "DOS signature, too many FATs: %#x", pBootSector->Bpb.Bpb20.cFats); pThis->cFats = pBootSector->Bpb.Bpb20.cFats; if (!FATBPB_MEDIA_IS_VALID(pBootSector->Bpb.Bpb20.bMedia)) return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, invalid media byte: %#x", pBootSector->Bpb.Bpb20.bMedia); pThis->bMedia = pBootSector->Bpb.Bpb20.bMedia; if (!RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb20.cbSector)) return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, sector size not power of two: %#x", pBootSector->Bpb.Bpb20.cbSector); if ( pBootSector->Bpb.Bpb20.cbSector != 512 && pBootSector->Bpb.Bpb20.cbSector != 4096 && pBootSector->Bpb.Bpb20.cbSector != 1024 && pBootSector->Bpb.Bpb20.cbSector != 128) return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, unsupported sector size: %#x", pBootSector->Bpb.Bpb20.cbSector); pThis->cbSector = pBootSector->Bpb.Bpb20.cbSector; if ( !RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb20.cSectorsPerCluster) || !pBootSector->Bpb.Bpb20.cSectorsPerCluster) return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, cluster size not non-zero power of two: %#x", pBootSector->Bpb.Bpb20.cSectorsPerCluster); pThis->cbCluster = pBootSector->Bpb.Bpb20.cSectorsPerCluster * pThis->cbSector; uint64_t const cMaxRoot = (pThis->cbBacking - pThis->offBootSector - 512) / sizeof(FATDIRENTRY); /* we'll check again later. */ if (pBootSector->Bpb.Bpb20.cMaxRootDirEntries >= cMaxRoot) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "DOS signature, too many root entries: %#x (max %#RX64)", pBootSector->Bpb.Bpb20.cSectorsPerCluster, cMaxRoot); pThis->cRootDirEntries = pBootSector->Bpb.Bpb20.cMaxRootDirEntries; if ( pBootSector->Bpb.Bpb20.cReservedSectors == 0 || pBootSector->Bpb.Bpb20.cReservedSectors >= _32K) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "DOS signature, bogus reserved sector count: %#x", pBootSector->Bpb.Bpb20.cReservedSectors); pThis->cReservedSectors = pBootSector->Bpb.Bpb20.cReservedSectors; pThis->aoffFats[0] = pThis->offBootSector + pThis->cReservedSectors * pThis->cbSector; /* * Jump ahead and check for FAT32 EBPB. * If found, we simply ASSUME it's a FAT32 file system. */ int rc; if ( ( sizeof(FAT32EBPB) <= cbMaxBpb && pBootSector->Bpb.Fat32Ebpb.bExtSignature == FATEBPB_SIGNATURE) || ( RT_UOFFSETOF(FAT32EBPB, achLabel) <= cbMaxBpb && pBootSector->Bpb.Fat32Ebpb.bExtSignature == FATEBPB_SIGNATURE_OLD) ) { rc = rtFsFatVolTryInitDos2PlusFat32(pThis, pBootSector, pErrInfo); if (RT_FAILURE(rc)) return rc; } else { /* * Check for extended BPB, otherwise we'll have to make qualified guesses * about what kind of BPB we're up against based on jmp offset and zero fields. * ASSUMES either FAT16 or FAT12. */ if ( ( sizeof(FATEBPB) <= cbMaxBpb && pBootSector->Bpb.Ebpb.bExtSignature == FATEBPB_SIGNATURE) || ( RT_UOFFSETOF(FATEBPB, achLabel) <= cbMaxBpb && pBootSector->Bpb.Ebpb.bExtSignature == FATEBPB_SIGNATURE_OLD) ) { rtFsFatVolInitCommonEbpbBits(pThis, pBootSector->Bpb.Ebpb.bExtSignature, pBootSector->Bpb.Ebpb.uSerialNumber, pBootSector->Bpb.Ebpb.achLabel, pBootSector->Bpb.Ebpb.achType); rc = rtFsFatVolTryInitDos2PlusBpb(pThis, pBootSector, true /*fMaybe331*/, pErrInfo); pThis->enmBpbVersion = pBootSector->Bpb.Ebpb.bExtSignature == FATEBPB_SIGNATURE ? RTFSFATBPBVER_EXT_29 : RTFSFATBPBVER_EXT_28; } else rc = rtFsFatVolTryInitDos2PlusBpb(pThis, pBootSector, cbMaxBpb >= sizeof(FATBPB331), pErrInfo); if (RT_FAILURE(rc)) return rc; if (pThis->szType[0] == '\0') memcpy(pThis->szType, pThis->enmFatType == RTFSFATTYPE_FAT12 ? "FAT12" : "FAT16", 6); } /* * Check the FAT ID. May have to read a bit of the FAT into the buffer. */ if (pThis->aoffFats[0] != pThis->offBootSector + 512) { rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->aoffFats[0], pbFatSector, 512, NULL); if (RT_FAILURE(rc)) return RTErrInfoSet(pErrInfo, rc, "error reading first FAT sector"); } if (pbFatSector[0] != pThis->bMedia) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Media byte and FAT ID mismatch: %#x vs %#x (%.7Rhxs)", pbFatSector[0], pThis->bMedia, pbFatSector); uint32_t idxOurEndOfChain; switch (pThis->enmFatType) { case RTFSFATTYPE_FAT12: if ((pbFatSector[1] & 0xf) != 0xf) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT12): %.3Rhxs", pbFatSector); pThis->idxMaxLastCluster = FAT_LAST_FAT12_DATA_CLUSTER; pThis->idxEndOfChain = (pbFatSector[1] >> 4) | ((uint32_t)pbFatSector[2] << 4); idxOurEndOfChain = FAT_FIRST_FAT12_EOC | 0xf; break; case RTFSFATTYPE_FAT16: if (pbFatSector[1] != 0xff) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT16): %.4Rhxs", pbFatSector); pThis->idxMaxLastCluster = FAT_LAST_FAT16_DATA_CLUSTER; pThis->idxEndOfChain = RT_MAKE_U16(pbFatSector[2], pbFatSector[3]); idxOurEndOfChain = FAT_FIRST_FAT16_EOC | 0xf; break; case RTFSFATTYPE_FAT32: if ( pbFatSector[1] != 0xff || pbFatSector[2] != 0xff || (pbFatSector[3] & 0x0f) != 0x0f) return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT32): %.8Rhxs", pbFatSector); pThis->idxMaxLastCluster = FAT_LAST_FAT32_DATA_CLUSTER; pThis->idxEndOfChain = RT_MAKE_U32_FROM_U8(pbFatSector[4], pbFatSector[5], pbFatSector[6], pbFatSector[7]); idxOurEndOfChain = FAT_FIRST_FAT32_EOC | 0xf; break; default: AssertFailedReturn(VERR_INTERNAL_ERROR_2); } if (pThis->idxEndOfChain <= pThis->idxMaxLastCluster) { Log(("rtFsFatVolTryInitDos2Plus: Bogus idxEndOfChain=%#x, using %#x instead\n", pThis->idxEndOfChain, idxOurEndOfChain)); pThis->idxEndOfChain = idxOurEndOfChain; } RT_NOREF(pbFatSector); return VINF_SUCCESS; } /** * Given a power of two value @a cb return exponent value. * * @returns Shift count * @param cb The value. */ static uint8_t rtFsFatVolCalcByteShiftCount(uint32_t cb) { Assert(RT_IS_POWER_OF_TWO(cb)); unsigned iBit = ASMBitFirstSetU32(cb); Assert(iBit >= 1); iBit--; return iBit; } /** * Worker for RTFsFatVolOpen. * * @returns IPRT status code. * @param pThis The FAT VFS instance to initialize. * @param hVfsSelf The FAT VFS handle (no reference consumed). * @param hVfsBacking The file backing the alleged FAT file system. * Reference is consumed (via rtFsFatVol_Destroy). * @param fReadOnly Readonly or readwrite mount. * @param offBootSector The boot sector offset in bytes. * @param pErrInfo Where to return additional error info. Can be NULL. */ static int rtFsFatVolTryInit(PRTFSFATVOL pThis, RTVFS hVfsSelf, RTVFSFILE hVfsBacking, bool fReadOnly, uint64_t offBootSector, PRTERRINFO pErrInfo) { /* * First initialize the state so that rtFsFatVol_Destroy won't trip up. */ pThis->hVfsSelf = hVfsSelf; pThis->hVfsBacking = hVfsBacking; /* Caller referenced it for us, we consume it; rtFsFatVol_Destroy releases it. */ pThis->cbBacking = 0; pThis->offBootSector = offBootSector; pThis->offNanoUTC = RTTimeLocalDeltaNano(); pThis->offMinUTC = pThis->offNanoUTC / RT_NS_1MIN; pThis->fReadOnly = fReadOnly; pThis->cReservedSectors = 1; pThis->cbSector = 512; pThis->cbCluster = 512; pThis->cClusters = 0; pThis->offFirstCluster = 0; pThis->cbTotalSize = 0; pThis->enmFatType = RTFSFATTYPE_INVALID; pThis->cFatEntries = 0; pThis->cFats = 0; pThis->cbFat = 0; for (unsigned i = 0; i < RT_ELEMENTS(pThis->aoffFats); i++) pThis->aoffFats[i] = UINT64_MAX; pThis->pFatCache = NULL; pThis->offRootDir = UINT64_MAX; pThis->idxRootDirCluster = UINT32_MAX; pThis->cRootDirEntries = UINT32_MAX; pThis->cbRootDir = 0; pThis->pRootDir = NULL; pThis->uSerialNo = 0; pThis->szLabel[0] = '\0'; pThis->szType[0] = '\0'; pThis->cBootSectorCopies = 0; pThis->fFat32Flags = 0; pThis->offBootSectorCopies = UINT64_MAX; pThis->offFat32InfoSector = UINT64_MAX; RT_ZERO(pThis->Fat32InfoSector); /* * Get stuff that may fail. */ int rc = RTVfsFileQuerySize(hVfsBacking, &pThis->cbBacking); if (RT_FAILURE(rc)) return rc; pThis->cbTotalSize = pThis->cbBacking - pThis->offBootSector; /* * Read the boot sector and the following sector (start of the allocation * table unless it a FAT32 FS). We'll then validate the boot sector and * start of the FAT, expanding the BPB into the instance data. */ union { uint8_t ab[512*2]; uint16_t au16[512*2 / 2]; uint32_t au32[512*2 / 4]; FATBOOTSECTOR BootSector; FAT32INFOSECTOR InfoSector; } Buf; RT_ZERO(Buf); rc = RTVfsFileReadAt(hVfsBacking, offBootSector, &Buf.BootSector, 512 * 2, NULL); if (RT_FAILURE(rc)) return RTErrInfoSet(pErrInfo, rc, "Unable to read bootsect"); /* * Extract info from the BPB and validate the two special FAT entries. * * Check the DOS signature first. The PC-DOS 1.0 boot floppy does not have * a signature and we ASSUME this is the case for all floppies formated by it. */ if (Buf.BootSector.uSignature != FATBOOTSECTOR_SIGNATURE) { if (Buf.BootSector.uSignature != 0) return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "No DOS bootsector signature: %#06x", Buf.BootSector.uSignature); rc = rtFsFatVolTryInitDos1x(pThis, &Buf.BootSector, &Buf.ab[512], pErrInfo); } else rc = rtFsFatVolTryInitDos2Plus(pThis, &Buf.BootSector, &Buf.ab[512], pErrInfo); if (RT_FAILURE(rc)) return rc; /* * Calc shift counts. */ pThis->cSectorByteShift = rtFsFatVolCalcByteShiftCount(pThis->cbSector); pThis->cClusterByteShift = rtFsFatVolCalcByteShiftCount(pThis->cbCluster); /* * Setup the FAT cache. */ rc = rtFsFatClusterMap_Create(pThis, &Buf.ab[512], pErrInfo); if (RT_FAILURE(rc)) return rc; /* * Create the root directory fun. */ if (pThis->idxRootDirCluster == UINT32_MAX) rc = rtFsFatDirShrd_New(pThis, NULL /*pParentDir*/, NULL /*pDirEntry*/, UINT32_MAX /*offEntryInDir*/, UINT32_MAX, pThis->offRootDir, pThis->cbRootDir, &pThis->pRootDir); else rc = rtFsFatDirShrd_New(pThis, NULL /*pParentDir*/, NULL /*pDirEntry*/, UINT32_MAX /*offEntryInDir*/, pThis->idxRootDirCluster, UINT64_MAX, pThis->cbRootDir, &pThis->pRootDir); return rc; } /** * Opens a FAT file system volume. * * @returns IPRT status code. * @param hVfsFileIn The file or device backing the volume. * @param fReadOnly Whether to mount it read-only. * @param offBootSector The offset of the boot sector relative to the start * of @a hVfsFileIn. Pass 0 for floppies. * @param phVfs Where to return the virtual file system handle. * @param pErrInfo Where to return additional error information. */ RTDECL(int) RTFsFatVolOpen(RTVFSFILE hVfsFileIn, bool fReadOnly, uint64_t offBootSector, PRTVFS phVfs, PRTERRINFO pErrInfo) { /* * Quick input validation. */ AssertPtrReturn(phVfs, VERR_INVALID_POINTER); *phVfs = NIL_RTVFS; uint32_t cRefs = RTVfsFileRetain(hVfsFileIn); AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE); /* * Create a new FAT VFS instance and try initialize it using the given input file. */ RTVFS hVfs = NIL_RTVFS; void *pvThis = NULL; int rc = RTVfsNew(&g_rtFsFatVolOps, sizeof(RTFSFATVOL), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, &pvThis); if (RT_SUCCESS(rc)) { rc = rtFsFatVolTryInit((PRTFSFATVOL)pvThis, hVfs, hVfsFileIn, fReadOnly, offBootSector, pErrInfo); if (RT_SUCCESS(rc)) *phVfs = hVfs; else RTVfsRelease(hVfs); } else RTVfsFileRelease(hVfsFileIn); return rc; } /** * Fills a range in the file with zeros in the most efficient manner. * * @returns IPRT status code. * @param hVfsFile The file to write to. * @param off Where to start filling with zeros. * @param cbZeros How many zero blocks to write. */ static int rtFsFatVolWriteZeros(RTVFSFILE hVfsFile, uint64_t off, uint32_t cbZeros) { while (cbZeros > 0) { uint32_t cbToWrite = sizeof(g_abRTZero64K); if (cbToWrite > cbZeros) cbToWrite = cbZeros; int rc = RTVfsFileWriteAt(hVfsFile, off, g_abRTZero64K, cbToWrite, NULL); if (RT_FAILURE(rc)) return rc; off += cbToWrite; cbZeros -= cbToWrite; } return VINF_SUCCESS; } /** * Formats a FAT volume. * * @returns IRPT status code. * @param hVfsFile The volume file. * @param offVol The offset into @a hVfsFile of the file. * Typically 0. * @param cbVol The size of the volume. Pass 0 if the rest of * hVfsFile should be used. * @param fFlags See RTFSFATVOL_FMT_F_XXX. * @param cbSector The logical sector size. Must be power of two. * Optional, pass zero to use 512. * @param cSectorsPerCluster Number of sectors per cluster. Power of two. * Optional, pass zero to auto detect. * @param enmFatType The FAT type (12, 16, 32) to use. * Optional, pass RTFSFATTYPE_INVALID for default. * @param cHeads The number of heads to report in the BPB. * Optional, pass zero to auto detect. * @param cSectorsPerTrack The number of sectors per track to put in the * BPB. Optional, pass zero to auto detect. * @param bMedia The media byte value and FAT ID to use. * Optional, pass zero to auto detect. * @param cRootDirEntries Number of root directory entries. * Optional, pass zero to auto detect. * @param cHiddenSectors Number of hidden sectors. Pass 0 for * unpartitioned media. * @param pErrInfo Additional error information, maybe. Optional. */ RTDECL(int) RTFsFatVolFormat(RTVFSFILE hVfsFile, uint64_t offVol, uint64_t cbVol, uint32_t fFlags, uint16_t cbSector, uint16_t cSectorsPerCluster, RTFSFATTYPE enmFatType, uint32_t cHeads, uint32_t cSectorsPerTrack, uint8_t bMedia, uint16_t cRootDirEntries, uint32_t cHiddenSectors, PRTERRINFO pErrInfo) { int rc; uint32_t cFats = 2; /* * Validate input. */ if (!cbSector) cbSector = 512; else AssertMsgReturn( cbSector == 128 || cbSector == 512 || cbSector == 1024 || cbSector == 4096, ("cbSector=%#x\n", cbSector), VERR_INVALID_PARAMETER); AssertMsgReturn(cSectorsPerCluster == 0 || (cSectorsPerCluster <= 128 && RT_IS_POWER_OF_TWO(cSectorsPerCluster)), ("cSectorsPerCluster=%#x\n", cSectorsPerCluster), VERR_INVALID_PARAMETER); if (bMedia != 0) { AssertMsgReturn(FAT_ID_IS_VALID(bMedia), ("bMedia=%#x\n", bMedia), VERR_INVALID_PARAMETER); AssertMsgReturn(FATBPB_MEDIA_IS_VALID(bMedia), ("bMedia=%#x\n", bMedia), VERR_INVALID_PARAMETER); } AssertReturn(!(fFlags & ~RTFSFATVOL_FMT_F_VALID_MASK), VERR_INVALID_FLAGS); AssertReturn(enmFatType >= RTFSFATTYPE_INVALID && enmFatType < RTFSFATTYPE_END, VERR_INVALID_PARAMETER); if (!cbVol) { uint64_t cbFile; rc = RTVfsFileQuerySize(hVfsFile, &cbFile); AssertRCReturn(rc, rc); AssertMsgReturn(cbFile > offVol, ("cbFile=%#RX64 offVol=%#RX64\n", cbFile, offVol), VERR_INVALID_PARAMETER); cbVol = cbFile - offVol; } uint64_t const cSectorsInVol = cbVol / cbSector; /* * Guess defaults if necessary. */ if (!cSectorsPerCluster || !cHeads || !cSectorsPerTrack || !bMedia || !cRootDirEntries) { static struct { uint64_t cbVol; uint8_t bMedia; uint8_t cHeads; uint8_t cSectorsPerTrack; uint8_t cSectorsPerCluster; uint16_t cRootDirEntries; } s_aDefaults[] = { /* cbVol, bMedia, cHeads, cSectorsPTrk, cSectorsPClstr, cRootDirEntries */ { 163840, 0xfe, /* cyl: 40,*/ 1, 8, 1, 64 }, { 184320, 0xfc, /* cyl: 40,*/ 1, 9, 2, 64 }, { 327680, 0xff, /* cyl: 40,*/ 2, 8, 2, 112 }, { 368640, 0xfd, /* cyl: 40,*/ 2, 9, 2, 112 }, { 737280, 0xf9, /* cyl: 80,*/ 2, 9, 2, 112 }, { 1228800, 0xf9, /* cyl: 80,*/ 2, 15, 2, 112 }, { 1474560, 0xf0, /* cyl: 80,*/ 2, 18, 1, 224 }, { 2949120, 0xf0, /* cyl: 80,*/ 2, 36, 2, 224 }, { 528482304, 0xf8, /* cyl: 1024,*/ 16, 63, 0, 512 }, // 504MB limit { UINT64_C(7927234560), 0xf8, /* cyl: 1024,*/ 240, 63, 0, 512 }, // 7.3GB limit { UINT64_C(8422686720), 0xf8, /* cyl: 1024,*/ 255, 63, 0, 512 }, // 7.84GB limit }; uint32_t iDefault = 0; while ( iDefault < RT_ELEMENTS(s_aDefaults) - 1U && cbVol > s_aDefaults[iDefault].cbVol) iDefault++; if (!cHeads) cHeads = s_aDefaults[iDefault].cHeads; if (!cSectorsPerTrack) cSectorsPerTrack = s_aDefaults[iDefault].cSectorsPerTrack; if (!bMedia) bMedia = s_aDefaults[iDefault].bMedia; if (!cRootDirEntries) cRootDirEntries = s_aDefaults[iDefault].cRootDirEntries; if (!cSectorsPerCluster) { cSectorsPerCluster = s_aDefaults[iDefault].cSectorsPerCluster; if (!cSectorsPerCluster) { uint32_t cbFat12Overhead = cbSector /* boot sector */ + RT_ALIGN_32(FAT_MAX_FAT12_TOTAL_CLUSTERS * 3 / 2, cbSector) * cFats /* FATs */ + RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector) /* root dir */; uint32_t cbFat16Overhead = cbSector /* boot sector */ + RT_ALIGN_32(FAT_MAX_FAT16_TOTAL_CLUSTERS * 2, cbSector) * cFats /* FATs */ + RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector) /* root dir */; if ( enmFatType == RTFSFATTYPE_FAT12 || cbVol <= cbFat12Overhead + FAT_MAX_FAT12_DATA_CLUSTERS * 4 * cbSector) { enmFatType = RTFSFATTYPE_FAT12; cSectorsPerCluster = 1; while ( cSectorsPerCluster < 128 && cSectorsInVol > cbFat12Overhead / cbSector + (uint32_t)cSectorsPerCluster * FAT_MAX_FAT12_DATA_CLUSTERS + cSectorsPerCluster - 1) cSectorsPerCluster <<= 1; } else if ( enmFatType == RTFSFATTYPE_FAT16 || cbVol <= cbFat16Overhead + FAT_MAX_FAT16_DATA_CLUSTERS * 128 * cbSector) { enmFatType = RTFSFATTYPE_FAT16; cSectorsPerCluster = 1; while ( cSectorsPerCluster < 128 && cSectorsInVol > cbFat12Overhead / cbSector + (uint32_t)cSectorsPerCluster * FAT_MAX_FAT16_DATA_CLUSTERS + cSectorsPerCluster - 1) cSectorsPerCluster <<= 1; } else { /* The target here is keeping the FAT size below 8MB. Seems windows likes a minimum 4KB cluster size as wells as a max of 32KB (googling). */ enmFatType = RTFSFATTYPE_FAT32; uint32_t cbFat32Overhead = cbSector * 32 /* boot sector, info sector, boot sector copies, reserved sectors */ + _8M * cFats; if (cbSector >= _4K) cSectorsPerCluster = 1; else cSectorsPerCluster = _4K / cbSector; while ( cSectorsPerCluster < 128 && cSectorsPerCluster * cbSector < _32K && cSectorsInVol > cbFat32Overhead / cbSector + (uint64_t)cSectorsPerCluster * _2M) cSectorsPerCluster <<= 1; } } } } Assert(cSectorsPerCluster); Assert(cRootDirEntries); uint32_t cbRootDir = RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector); uint32_t const cbCluster = cSectorsPerCluster * cbSector; /* * If we haven't figured out the FAT type yet, do so. * The file system code determins the FAT based on cluster counts, * so we must do so here too. */ if (enmFatType == RTFSFATTYPE_INVALID) { uint32_t cbFat12Overhead = cbSector /* boot sector */ + RT_ALIGN_32(FAT_MAX_FAT12_TOTAL_CLUSTERS * 3 / 2, cbSector) * cFats /* FATs */ + RT_ALIGN_32(cRootDirEntries * sizeof(FATDIRENTRY), cbSector) /* root dir */; if ( cbVol <= cbFat12Overhead + cbCluster || (cbVol - cbFat12Overhead) / cbCluster <= FAT_MAX_FAT12_DATA_CLUSTERS) enmFatType = RTFSFATTYPE_FAT12; else { uint32_t cbFat16Overhead = cbSector /* boot sector */ + RT_ALIGN_32(FAT_MAX_FAT16_TOTAL_CLUSTERS * 2, cbSector) * cFats /* FATs */ + cbRootDir; if ( cbVol <= cbFat16Overhead + cbCluster || (cbVol - cbFat16Overhead) / cbCluster <= FAT_MAX_FAT16_DATA_CLUSTERS) enmFatType = RTFSFATTYPE_FAT16; else enmFatType = RTFSFATTYPE_FAT32; } } if (enmFatType == RTFSFATTYPE_FAT32) cbRootDir = cbCluster; /* * Calculate the FAT size and number of data cluster. * * Since the FAT size depends on how many data clusters there are, we start * with a minimum FAT size and maximum clust count, then recalucate it. The * result isn't necessarily stable, so we will only retry stabalizing the * result a few times. */ uint32_t cbReservedFixed = enmFatType == RTFSFATTYPE_FAT32 ? 32 * cbSector : cbSector + cbRootDir; uint32_t cbFat = cbSector; if (cbReservedFixed + cbFat * cFats >= cbVol) return RTErrInfoSetF(pErrInfo, VERR_DISK_FULL, "volume is too small (cbVol=%#RX64 rsvd=%#x cbFat=%#x cFat=%#x)", cbVol, cbReservedFixed, cbFat, cFats); uint32_t cMaxClusters = enmFatType == RTFSFATTYPE_FAT12 ? FAT_MAX_FAT12_DATA_CLUSTERS : enmFatType == RTFSFATTYPE_FAT16 ? FAT_MAX_FAT16_DATA_CLUSTERS : FAT_MAX_FAT12_DATA_CLUSTERS; uint32_t cClusters = (uint32_t)RT_MIN((cbVol - cbReservedFixed - cbFat * cFats) / cbCluster, cMaxClusters); uint32_t cPrevClusters; uint32_t cTries = 4; do { cPrevClusters = cClusters; switch (enmFatType) { case RTFSFATTYPE_FAT12: cbFat = (uint32_t)RT_MIN(FAT_MAX_FAT12_TOTAL_CLUSTERS, cClusters) * 3 / 2; break; case RTFSFATTYPE_FAT16: cbFat = (uint32_t)RT_MIN(FAT_MAX_FAT16_TOTAL_CLUSTERS, cClusters) * 2; break; case RTFSFATTYPE_FAT32: cbFat = (uint32_t)RT_MIN(FAT_MAX_FAT32_TOTAL_CLUSTERS, cClusters) * 4; cbFat = RT_ALIGN_32(cbFat, _4K); break; default: AssertFailedReturn(VERR_INTERNAL_ERROR_2); } cbFat = RT_ALIGN_32(cbFat, cbSector); if (cbReservedFixed + cbFat * cFats >= cbVol) return RTErrInfoSetF(pErrInfo, VERR_DISK_FULL, "volume is too small (cbVol=%#RX64 rsvd=%#x cbFat=%#x cFat=%#x)", cbVol, cbReservedFixed, cbFat, cFats); cClusters = (uint32_t)RT_MIN((cbVol - cbReservedFixed - cbFat * cFats) / cbCluster, cMaxClusters); } while ( cClusters != cPrevClusters && cTries-- > 0); uint64_t const cTotalSectors = cClusters * (uint64_t)cSectorsPerCluster + (cbReservedFixed + cbFat * cFats) / cbSector; /* * Check that the file system type and cluster count matches up. If they * don't the type will be misdetected. * * Note! These assertions could trigger if the above calculations are wrong. */ switch (enmFatType) { case RTFSFATTYPE_FAT12: AssertMsgReturn(cClusters >= FAT_MIN_FAT12_DATA_CLUSTERS && cClusters <= FAT_MAX_FAT12_DATA_CLUSTERS, ("cClusters=%#x\n", cClusters), VERR_OUT_OF_RANGE); break; case RTFSFATTYPE_FAT16: AssertMsgReturn(cClusters >= FAT_MIN_FAT16_DATA_CLUSTERS && cClusters <= FAT_MAX_FAT16_DATA_CLUSTERS, ("cClusters=%#x\n", cClusters), VERR_OUT_OF_RANGE); break; case RTFSFATTYPE_FAT32: AssertMsgReturn(cClusters >= FAT_MIN_FAT32_DATA_CLUSTERS && cClusters <= FAT_MAX_FAT32_DATA_CLUSTERS, ("cClusters=%#x\n", cClusters), VERR_OUT_OF_RANGE); RT_FALL_THRU(); default: AssertFailedReturn(VERR_INTERNAL_ERROR_2); } /* * Okay, create the boot sector. */ size_t cbBuf = RT_MAX(RT_MAX(_64K, cbCluster), cbSector * 2U); uint8_t *pbBuf = (uint8_t *)RTMemTmpAllocZ(cbBuf); AssertReturn(pbBuf, VERR_NO_TMP_MEMORY); const char *pszLastOp = "boot sector"; PFATBOOTSECTOR pBootSector = (PFATBOOTSECTOR)pbBuf; pBootSector->abJmp[0] = 0xeb; pBootSector->abJmp[1] = RT_UOFFSETOF(FATBOOTSECTOR, Bpb) + (enmFatType == RTFSFATTYPE_FAT32 ? sizeof(FAT32EBPB) : sizeof(FATEBPB)) - 2; pBootSector->abJmp[2] = 0x90; memcpy(pBootSector->achOemName, enmFatType == RTFSFATTYPE_FAT32 ? "FAT32 " : "IPRT 6.2", sizeof(pBootSector->achOemName)); pBootSector->Bpb.Bpb331.cbSector = (uint16_t)cbSector; pBootSector->Bpb.Bpb331.cSectorsPerCluster = (uint8_t)cSectorsPerCluster; pBootSector->Bpb.Bpb331.cReservedSectors = enmFatType == RTFSFATTYPE_FAT32 ? cbReservedFixed / cbSector : 1; pBootSector->Bpb.Bpb331.cFats = (uint8_t)cFats; pBootSector->Bpb.Bpb331.cMaxRootDirEntries = enmFatType == RTFSFATTYPE_FAT32 ? 0 : cRootDirEntries; pBootSector->Bpb.Bpb331.cTotalSectors16 = cTotalSectors <= UINT16_MAX ? (uint16_t)cTotalSectors : 0; pBootSector->Bpb.Bpb331.bMedia = bMedia; pBootSector->Bpb.Bpb331.cSectorsPerFat = enmFatType == RTFSFATTYPE_FAT32 ? 0 : cbFat / cbSector; pBootSector->Bpb.Bpb331.cSectorsPerTrack = cSectorsPerTrack; pBootSector->Bpb.Bpb331.cTracksPerCylinder = cHeads; pBootSector->Bpb.Bpb331.cHiddenSectors = cHiddenSectors; /* XP barfs if both cTotalSectors32 and cTotalSectors16 are set */ pBootSector->Bpb.Bpb331.cTotalSectors32 = cTotalSectors <= UINT32_MAX && pBootSector->Bpb.Bpb331.cTotalSectors16 == 0 ? (uint32_t)cTotalSectors : 0; if (enmFatType != RTFSFATTYPE_FAT32) { pBootSector->Bpb.Ebpb.bInt13Drive = 0; pBootSector->Bpb.Ebpb.bReserved = 0; pBootSector->Bpb.Ebpb.bExtSignature = FATEBPB_SIGNATURE; pBootSector->Bpb.Ebpb.uSerialNumber = RTRandU32(); memset(pBootSector->Bpb.Ebpb.achLabel, ' ', sizeof(pBootSector->Bpb.Ebpb.achLabel)); memcpy(pBootSector->Bpb.Ebpb.achType, enmFatType == RTFSFATTYPE_FAT12 ? "FAT12 " : "FAT16 ", sizeof(pBootSector->Bpb.Ebpb.achType)); } else { pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 = cbFat / cbSector; pBootSector->Bpb.Fat32Ebpb.fFlags = 0; pBootSector->Bpb.Fat32Ebpb.uVersion = FAT32EBPB_VERSION_0_0; pBootSector->Bpb.Fat32Ebpb.uRootDirCluster = FAT_FIRST_DATA_CLUSTER; pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo = 1; pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo = 6; RT_ZERO(pBootSector->Bpb.Fat32Ebpb.abReserved); pBootSector->Bpb.Fat32Ebpb.bInt13Drive = 0; pBootSector->Bpb.Fat32Ebpb.bReserved = 0; pBootSector->Bpb.Fat32Ebpb.bExtSignature = FATEBPB_SIGNATURE; pBootSector->Bpb.Fat32Ebpb.uSerialNumber = RTRandU32(); memset(pBootSector->Bpb.Fat32Ebpb.achLabel, ' ', sizeof(pBootSector->Bpb.Fat32Ebpb.achLabel)); if (cTotalSectors > UINT32_MAX) pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 = cTotalSectors; else memcpy(pBootSector->Bpb.Fat32Ebpb.u.achType, "FAT32 ", sizeof(pBootSector->Bpb.Fat32Ebpb.u.achType)); } pbBuf[pBootSector->abJmp[1] + 2 + 0] = 0xcd; /* int 18h */ /** @todo find/implement booting of next boot device. */ pbBuf[pBootSector->abJmp[1] + 2 + 1] = 0x18; pbBuf[pBootSector->abJmp[1] + 2 + 2] = 0xcc; /* int3 */ pbBuf[pBootSector->abJmp[1] + 2 + 3] = 0xcc; pBootSector->uSignature = FATBOOTSECTOR_SIGNATURE; if (cbSector != sizeof(*pBootSector)) *(uint16_t *)&pbBuf[cbSector - 2] = FATBOOTSECTOR_SIGNATURE; /** @todo figure out how disks with non-512 byte sectors work! */ rc = RTVfsFileWriteAt(hVfsFile, offVol, pBootSector, cbSector, NULL); uint32_t const offFirstFat = pBootSector->Bpb.Bpb331.cReservedSectors * cbSector; /* * Write the FAT32 info sector, 3 boot sector copies, and zero fill * the other reserved sectors. */ if (RT_SUCCESS(rc) && enmFatType == RTFSFATTYPE_FAT32) { pszLastOp = "fat32 info sector"; PFAT32INFOSECTOR pInfoSector = (PFAT32INFOSECTOR)&pbBuf[cbSector]; /* preserve the boot sector. */ RT_ZERO(*pInfoSector); pInfoSector->uSignature1 = FAT32INFOSECTOR_SIGNATURE_1; pInfoSector->uSignature2 = FAT32INFOSECTOR_SIGNATURE_2; pInfoSector->uSignature3 = FAT32INFOSECTOR_SIGNATURE_3; pInfoSector->cFreeClusters = cClusters - 1; /* ASSUMES 1 cluster for the root dir. */ pInfoSector->cLastAllocatedCluster = FAT_FIRST_DATA_CLUSTER; rc = RTVfsFileWriteAt(hVfsFile, offVol + cbSector, pInfoSector, cbSector, NULL); uint32_t iSector = 2; if (RT_SUCCESS(rc)) { pszLastOp = "fat32 unused reserved sectors"; rc = rtFsFatVolWriteZeros(hVfsFile, offVol + iSector * cbSector, (pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo - iSector) * cbSector); iSector = pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo; } if (RT_SUCCESS(rc)) { pszLastOp = "boot sector copy"; for (uint32_t i = 0; i < 3 && RT_SUCCESS(rc); i++, iSector++) rc = RTVfsFileWriteAt(hVfsFile, offVol + iSector * cbSector, pBootSector, cbSector, NULL); } if (RT_SUCCESS(rc)) { pszLastOp = "fat32 unused reserved sectors"; rc = rtFsFatVolWriteZeros(hVfsFile, offVol + iSector * cbSector, (pBootSector->Bpb.Bpb331.cReservedSectors - iSector) * cbSector); } } /* * The FATs. */ if (RT_SUCCESS(rc)) { pszLastOp = "fat"; pBootSector = NULL; /* invalid */ RT_BZERO(pbBuf, cbSector); switch (enmFatType) { case RTFSFATTYPE_FAT32: pbBuf[11] = 0x0f; /* EOC for root dir*/ pbBuf[10] = 0xff; pbBuf[9] = 0xff; pbBuf[8] = 0xff; pbBuf[7] = 0x0f; /* Formatter's EOC, followed by signed extend FAT ID. */ pbBuf[6] = 0xff; pbBuf[5] = 0xff; pbBuf[4] = 0xff; RT_FALL_THRU(); case RTFSFATTYPE_FAT16: pbBuf[3] = 0xff; RT_FALL_THRU(); case RTFSFATTYPE_FAT12: pbBuf[2] = 0xff; pbBuf[1] = 0xff; pbBuf[0] = bMedia; /* FAT ID */ break; default: AssertFailed(); } for (uint32_t iFatCopy = 0; iFatCopy < cFats && RT_SUCCESS(rc); iFatCopy++) { rc = RTVfsFileWriteAt(hVfsFile, offVol + offFirstFat + cbFat * iFatCopy, pbBuf, cbSector, NULL); if (RT_SUCCESS(rc) && cbFat > cbSector) rc = rtFsFatVolWriteZeros(hVfsFile, offVol + offFirstFat + cbFat * iFatCopy + cbSector, cbFat - cbSector); } } /* * The root directory. */ if (RT_SUCCESS(rc)) { /** @todo any mandatory directory entries we need to fill in here? */ pszLastOp = "root dir"; rc = rtFsFatVolWriteZeros(hVfsFile, offVol + offFirstFat + cbFat * cFats, cbRootDir); } /* * If long format, fill the rest of the disk with 0xf6. */ AssertCompile(RTFSFATVOL_FMT_F_QUICK != 0); if (RT_SUCCESS(rc) && !(fFlags & RTFSFATVOL_FMT_F_QUICK)) { pszLastOp = "formatting data clusters"; uint64_t offCur = offFirstFat + cbFat * cFats + cbRootDir; uint64_t cbLeft = cTotalSectors * cbSector; if (cbVol - cbLeft <= _256K) /* HACK ALERT! Format to end of volume if it's a cluster rounding thing. */ cbLeft = cbVol; if (cbLeft > offCur) { cbLeft -= offCur; offCur += offVol; memset(pbBuf, 0xf6, cbBuf); while (cbLeft > 0) { size_t cbToWrite = cbLeft >= cbBuf ? cbBuf : (size_t)cbLeft; rc = RTVfsFileWriteAt(hVfsFile, offCur, pbBuf, cbToWrite, NULL); if (RT_SUCCESS(rc)) { offCur += cbToWrite; cbLeft -= cbToWrite; } else break; } } } /* * Done. */ RTMemTmpFree(pbBuf); if (RT_SUCCESS(rc)) return rc; return RTErrInfoSet(pErrInfo, rc, pszLastOp); } /** * Formats a 1.44MB floppy image. * * @returns IPRT status code. * @param hVfsFile The image. */ RTDECL(int) RTFsFatVolFormat144(RTVFSFILE hVfsFile, bool fQuick) { return RTFsFatVolFormat(hVfsFile, 0 /*offVol*/, 1474560, fQuick ? RTFSFATVOL_FMT_F_QUICK : RTFSFATVOL_FMT_F_FULL, 512 /*cbSector*/, 1 /*cSectorsPerCluster*/, RTFSFATTYPE_FAT12, 2 /*cHeads*/, 18 /*cSectors*/, 0xf0 /*bMedia*/, 224 /*cRootDirEntries*/, 0 /*cHiddenSectors*/, NULL /*pErrInfo*/); } /** * Formats a 2.88MB floppy image. * * @returns IPRT status code. * @param hVfsFile The image. */ RTDECL(int) RTFsFatVolFormat288(RTVFSFILE hVfsFile, bool fQuick) { return RTFsFatVolFormat(hVfsFile, 0 /*offVol*/, 2949120, fQuick ? RTFSFATVOL_FMT_F_QUICK : RTFSFATVOL_FMT_F_FULL, 512 /*cbSector*/, 2 /*cSectorsPerCluster*/, RTFSFATTYPE_FAT12, 2 /*cHeads*/, 36 /*cSectors*/, 0xf0 /*bMedia*/, 224 /*cRootDirEntries*/, 0 /*cHiddenSectors*/, NULL /*pErrInfo*/); } /** * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate} */ static DECLCALLBACK(int) rtVfsChainFatVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec, PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo) { RT_NOREF(pProviderReg); /* * Basic checks. */ if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE) return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE; if ( pElement->enmType != RTVFSOBJTYPE_VFS && pElement->enmType != RTVFSOBJTYPE_DIR) return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS; if (pElement->cArgs > 1) return VERR_VFS_CHAIN_AT_MOST_ONE_ARG; /* * Parse the flag if present, save in pElement->uProvider. */ bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ; if (pElement->cArgs > 0) { const char *psz = pElement->paArgs[0].psz; if (*psz) { if (!strcmp(psz, "ro")) fReadOnly = true; else if (!strcmp(psz, "rw")) fReadOnly = false; else { *poffError = pElement->paArgs[0].offSpec; return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument"); } } } pElement->uProvider = fReadOnly; return VINF_SUCCESS; } /** * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate} */ static DECLCALLBACK(int) rtVfsChainFatVol_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj, PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo) { RT_NOREF(pProviderReg, pSpec, poffError); int rc; RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj); if (hVfsFileIn != NIL_RTVFSFILE) { RTVFS hVfs; rc = RTFsFatVolOpen(hVfsFileIn, pElement->uProvider != false, 0, &hVfs, pErrInfo); RTVfsFileRelease(hVfsFileIn); if (RT_SUCCESS(rc)) { *phVfsObj = RTVfsObjFromVfs(hVfs); RTVfsRelease(hVfs); if (*phVfsObj != NIL_RTVFSOBJ) return VINF_SUCCESS; rc = VERR_VFS_CHAIN_CAST_FAILED; } } else rc = VERR_VFS_CHAIN_CAST_FAILED; return rc; } /** * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement} */ static DECLCALLBACK(bool) rtVfsChainFatVol_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement, PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement) { RT_NOREF(pProviderReg, pSpec, pReuseSpec); if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider || !pReuseElement->paArgs[0].uProvider) return true; return false; } /** VFS chain element 'file'. */ static RTVFSCHAINELEMENTREG g_rtVfsChainFatVolReg = { /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION, /* fReserved = */ 0, /* pszName = */ "fat", /* ListEntry = */ { NULL, NULL }, /* pszHelp = */ "Open a FAT file system, requires a file object on the left side.\n" "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n", /* pfnValidate = */ rtVfsChainFatVol_Validate, /* pfnInstantiate = */ rtVfsChainFatVol_Instantiate, /* pfnCanReuseElement = */ rtVfsChainFatVol_CanReuseElement, /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION }; RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainFatVolReg, rtVfsChainFatVolReg);