VirtualBox

source: vbox/trunk/src/VBox/Runtime/common/filesystem/fatvfs.cpp@ 66615

Last change on this file since 66615 was 66615, checked in by vboxsync, 8 years ago

IPRT: More FAT fun (couldn't stop myself).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 73.6 KB
Line 
1/* $Id: fatvfs.cpp 66615 2017-04-19 19:29:36Z vboxsync $ */
2/** @file
3 * IPRT - FAT Virtual Filesystem.
4 */
5
6/*
7 * Copyright (C) 2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.215389.xyz. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * The contents of this file may alternatively be used under the terms
18 * of the Common Development and Distribution License Version 1.0
19 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
20 * VirtualBox OSE distribution, in which case the provisions of the
21 * CDDL are applicable instead of those of the GPL.
22 *
23 * You may elect to license modified versions of this file under the
24 * terms and conditions of either the GPL or the CDDL or both.
25 */
26
27
28/*********************************************************************************************************************************
29* Header Files *
30*********************************************************************************************************************************/
31#include "internal/iprt.h"
32#include <iprt/fs.h>
33
34#include <iprt/asm.h>
35#include <iprt/assert.h>
36#include <iprt/ctype.h>
37#include <iprt/file.h>
38#include <iprt/err.h>
39#include <iprt/mem.h>
40#include <iprt/poll.h>
41#include <iprt/string.h>
42#include <iprt/sg.h>
43#include <iprt/thread.h>
44#include <iprt/vfs.h>
45#include <iprt/vfslowlevel.h>
46#include <iprt/formats/fat.h>
47
48
49/*********************************************************************************************************************************
50* Defined Constants And Macros *
51*********************************************************************************************************************************/
52
53
54/*********************************************************************************************************************************
55* Structures and Typedefs *
56*********************************************************************************************************************************/
57/**
58 * A part of the cluster chain covering up to 252 clusters.
59 */
60typedef struct RTFSFATCHAINPART
61{
62 /** List entry. */
63 RTLISTNODE ListEntry;
64 /** Chain entries. */
65 uint32_t aEntries[256 - 4];
66} RTFSFATCHAINPART;
67AssertCompile(sizeof(RTFSFATCHAINPART) <= _1K);
68typedef RTFSFATCHAINPART *PRTFSFATCHAINPART;
69typedef RTFSFATCHAINPART const *PCRTFSFATCHAINPART;
70
71/**
72 * A FAT cluster chain.
73 */
74typedef struct RTFSFATCHAIN
75{
76 /** The chain size in bytes. */
77 uint32_t cbChain;
78 /** The chain size in entries. */
79 uint32_t cClusters;
80 /** List of chain parts (RTFSFATCHAINPART). */
81 RTLISTANCHOR ListParts;
82} RTFSFATCHAIN;
83typedef RTFSFATCHAIN *PRTFSFATCHAIN;
84typedef RTFSFATCHAIN const *PCRTFSFATCHAIN;
85
86
87/**
88 * FAT file system object (common part to files and dirs).
89 */
90typedef struct RTFSFATOBJ
91{
92 /** The parent directory keeps a list of open objects (RTFSFATOBJ). */
93 RTLISTNODE Entry;
94 /** The byte offset of the directory entry.
95 * This is set to UINT64_MAX if special FAT12/16 root directory. */
96 uint64_t offDirEntry;
97 /** Attributes. */
98 RTFMODE fAttrib;
99 /** The object size. */
100 uint32_t cbObject;
101 /** The access time. */
102 RTTIMESPEC AccessTime;
103 /** The modificaton time. */
104 RTTIMESPEC ModificationTime;
105 /** The birth time. */
106 RTTIMESPEC BirthTime;
107 /** Cluster chain. */
108 RTFSFATCHAIN Clusters;
109 /** Pointer to the volume. */
110 struct RTFSFATVOL *pVol;
111} RTFSFATOBJ;
112typedef RTFSFATOBJ *PRTFSFATOBJ;
113
114typedef struct RTFSFATFILE
115{
116 /** Core FAT object info. */
117 RTFSFATOBJ Core;
118 /** The current file offset. */
119 uint32_t offFile;
120} RTFSFATFILE;
121typedef RTFSFATFILE *PRTFSFATFILE;
122
123
124/**
125 * FAT directory.
126 *
127 * We work directories in one of two buffering modes. If there are few entries
128 * or if it's the FAT12/16 root directory, we map the whole thing into memory.
129 * If it's too large, we use an inefficient sector buffer for now.
130 *
131 * Directory entry updates happens exclusively via the directory, so any open
132 * files or subdirs have a parent reference for doing that. The parent OTOH,
133 * keeps a list of open children.
134 */
135typedef struct RTFSFATDIR
136{
137 /** Core FAT object info. */
138 RTFSFATOBJ Core;
139 /** Open child objects (RTFSFATOBJ). */
140 RTLISTNODE OpenChildren;
141
142 /** Number of directory entries. */
143 uint32_t cEntries;
144
145 /** If fully buffered. */
146 bool fFullyBuffered;
147 /** Set if this is a linear root directory. */
148 bool fIsFlatRootDir;
149
150 union
151 {
152 /** Data for the full buffered mode.
153 * No need to messing around with clusters here, as we only uses this for
154 * directories with a contiguous mapping on the disk.
155 * So, if we grow a directory in a non-contiguous manner, we have to switch
156 * to sector buffering on the fly. */
157 struct
158 {
159 /** Directory offset. */
160 uint64_t offDir;
161 /** Number of sectors mapped by paEntries and pbDirtySectors. */
162 uint32_t cSectors;
163 /** Number of dirty sectors. */
164 uint32_t cDirtySectors;
165 /** Pointer to the linear mapping of the directory entries. */
166 PFATDIRENTRYUNION paEntries;
167 /** Dirty sector map. */
168 uint8_t *pbDirtySectors;
169 } Full;
170 /** The simple sector buffering.
171 * This only works for clusters, so no FAT12/16 root directory fun. */
172 struct
173 {
174 /** The disk offset of the current sector, UINT64_MAX if invalid. */
175 uint64_t offOnDisk;
176 /** The directory offset, UINT32_MAX if invalid. */
177 uint32_t offInDir;
178 uint32_t u32Reserved; /**< Puts pbSector and paEntries at the same location */
179 /** Sector buffer. */
180 PFATDIRENTRYUNION *paEntries;
181 /** Dirty flag. */
182 bool fDirty;
183 } Simple;
184 } u;
185} RTFSFATDIR;
186/** Pointer to a FAT directory instance. */
187typedef RTFSFATDIR *PRTFSFATDIR;
188
189
190/**
191 * File allocation table cache entry.
192 */
193typedef struct RTFSFATCLUSTERMAPENTRY
194{
195 /** The byte offset into the fat, UINT32_MAX if invalid entry. */
196 uint32_t offFat;
197 /** Pointer to the data. */
198 uint8_t *pbData;
199 /** Dirty bitmap. Indexed by byte offset right shifted by
200 * RTFSFATCLUSTERMAPCACHE::cDirtyShift. */
201 uint64_t bmDirty;
202} RTFSFATCLUSTERMAPENTRY;
203/** Pointer to a file allocation table cache entry. */
204typedef RTFSFATCLUSTERMAPENTRY *PRTFSFATCLUSTERMAPENTRY;
205
206/**
207 * File allocation table cache.
208 */
209typedef struct RTFSFATCLUSTERMAPCACHE
210{
211 /** Number of cache entries. */
212 uint32_t cEntries;
213 /** The max size of data in a cache entry. */
214 uint32_t cbEntry;
215 /** Dirty bitmap shift count. */
216 uint32_t cDirtyShift;
217 /** The dirty cache line size (multiple of two). */
218 uint32_t cbDirtyLine;
219 /** The cache name. */
220 const char *pszName;
221 /** Cache entries. */
222 RTFSFATCLUSTERMAPENTRY aEntries[RT_FLEXIBLE_ARRAY];
223} RTFSFATCLUSTERMAPCACHE;
224/** Pointer to a FAT linear metadata cache. */
225typedef RTFSFATCLUSTERMAPCACHE *PRTFSFATCLUSTERMAPCACHE;
226
227
228/**
229 * FAT type (format).
230 */
231typedef enum RTFSFATTYPE
232{
233 RTFSFATTYPE_INVALID = 0,
234 RTFSFATTYPE_FAT12,
235 RTFSFATTYPE_FAT16,
236 RTFSFATTYPE_FAT32,
237 RTFSFATTYPE_END
238} RTFSFATTYPE;
239
240/**
241 * A FAT volume.
242 */
243typedef struct RTFSFATVOL
244{
245 /** Handle to itself. */
246 RTVFS hVfsSelf;
247 /** The file, partition, or whatever backing the FAT volume. */
248 RTVFSFILE hVfsBacking;
249 /** The size of the backing thingy. */
250 uint64_t cbBacking;
251 /** Byte offset of the bootsector relative to the start of the file. */
252 uint64_t offBootSector;
253 /** Set if read-only mode. */
254 bool fReadOnly;
255 /** Media byte. */
256 uint8_t bMedia;
257 /** Reserved sectors. */
258 uint32_t cReservedSectors;
259
260 /** Logical sector size. */
261 uint32_t cbSector;
262 /** The cluster size in bytes. */
263 uint32_t cbCluster;
264 /** The number of data clusters, including the two reserved ones. */
265 uint32_t cClusters;
266 /** The offset of the first cluster. */
267 uint64_t offFirstCluster;
268 /** The total size from the BPB, in bytes. */
269 uint64_t cbTotalSize;
270
271 /** The FAT type. */
272 RTFSFATTYPE enmFatType;
273
274 /** Number of FAT entries (clusters). */
275 uint32_t cFatEntries;
276 /** The size of a FAT, in bytes. */
277 uint32_t cbFat;
278 /** Number of FATs. */
279 uint32_t cFats;
280 /** The end of chain marker used by the formatter (FAT entry \#2). */
281 uint32_t idxEndOfChain;
282 /** The maximum last cluster supported by the FAT format. */
283 uint32_t idxMaxLastCluster;
284 /** FAT byte offsets. */
285 uint64_t aoffFats[8];
286 /** Pointer to the FAT (cluster map) cache. */
287 PRTFSFATCLUSTERMAPCACHE pFatCache;
288
289 /** The root directory byte offset. */
290 uint64_t offRootDir;
291 /** Root directory cluster, UINT32_MAX if not FAT32. */
292 uint32_t idxRootDirCluster;
293 /** Number of root directory entries, if fixed. UINT32_MAX for FAT32. */
294 uint32_t cRootDirEntries;
295 /** The size of the root directory, rounded up to the nearest sector size. */
296 uint32_t cbRootDir;
297 /** The root directory handle. */
298 RTVFSDIR hVfsRootDir;
299 /** The root directory instance data. */
300 PRTFSFATDIR pRootDir;
301
302 /** Serial number. */
303 uint32_t uSerialNo;
304 /** The stripped volume label, if included in EBPB. */
305 char szLabel[12];
306 /** The file system type from the EBPB (also stripped). */
307 char szType[9];
308 /** Number of FAT32 boot sector copies. */
309 uint8_t cBootSectorCopies;
310 /** FAT32 flags. */
311 uint16_t fFat32Flags;
312 /** Offset of the FAT32 boot sector copies, UINT64_MAX if none. */
313 uint64_t offBootSectorCopies;
314
315 /** The FAT32 info sector byte offset, UINT64_MAX if not present. */
316 uint64_t offFat32InfoSector;
317 /** The FAT32 info sector if offFat32InfoSector isn't UINT64_MAX. */
318 FAT32INFOSECTOR Fat32InfoSector;
319} RTFSFATVOL;
320/** Pointer to a FAT volume (VFS instance data). */
321typedef RTFSFATVOL *PRTFSFATVOL;
322
323
324
325/**
326 * Checks if the cluster chain is contiguous on the disk.
327 *
328 * @returns true / false.
329 * @param pChain The chain.
330 */
331static bool rtFsFatChain_IsContiguous(PCRTFSFATCHAIN pChain)
332{
333 if (pChain->cClusters <= 1)
334 return true;
335
336 PRTFSFATCHAINPART pPart = RTListGetFirst(&pChain->ListParts, RTFSFATCHAINPART, ListEntry);
337 uint32_t idxNext = pPart->aEntries[0];
338 uint32_t cLeft = pChain->cClusters;
339 for (;;)
340 {
341 uint32_t const cInPart = RT_MIN(cLeft, RT_ELEMENTS(pPart->aEntries));
342 for (uint32_t iPart = 0; iPart < cInPart; iPart++)
343 if (pPart->aEntries[iPart] == idxNext)
344 idxNext++;
345 else
346 return false;
347 cLeft -= cInPart;
348 if (!cLeft)
349 return true;
350 pPart = RTListGetNext(&pChain->ListParts, pPart, RTFSFATCHAINPART, ListEntry);
351 }
352}
353
354
355
356/**
357 * Creates a cache for the file allocation table (cluster map).
358 *
359 * @returns Pointer to the cache.
360 * @param pThis The FAT volume instance.
361 * @param pbFirst512FatBytes The first 512 bytes of the first FAT.
362 */
363static int rtFsFatClusterMap_Create(PRTFSFATVOL pThis, uint8_t const *pbFirst512FatBytes, PRTERRINFO pErrInfo)
364{
365 Assert(RT_ALIGN_32(pThis->cbFat, pThis->cbSector) == pThis->cbFat);
366 Assert(pThis->cbFat != 0);
367
368 /*
369 * Figure the cache size. Keeping it _very_ simple for now as we just need
370 * something that works, not anything the performs like crazy.
371 */
372 uint32_t cEntries;
373 uint32_t cbEntry = pThis->cbFat;
374 if (cbEntry <= _512K)
375 cEntries = 1;
376 else
377 {
378 Assert(pThis->cbSector < _512K / 8);
379 cEntries = 8;
380 cbEntry = pThis->cbSector;
381 }
382
383 /*
384 * Allocate and initialize it all.
385 */
386 PRTFSFATCLUSTERMAPCACHE pCache;
387 pThis->pFatCache = pCache = (PRTFSFATCLUSTERMAPCACHE)RTMemAllocZ(RT_OFFSETOF(RTFSFATCLUSTERMAPCACHE, aEntries[cEntries]));
388 if (!pCache)
389 return RTErrInfoSet(pErrInfo, VERR_NO_MEMORY, "Failed to allocate FAT cache");
390 pCache->cEntries = cEntries;
391 pCache->cbEntry = cbEntry;
392
393 unsigned i = cEntries;
394 while (i-- > 0)
395 {
396 pCache->aEntries[i].pbData = (uint8_t *)RTMemAlloc(cbEntry);
397 if (pCache->aEntries[i].pbData == NULL)
398 {
399 for (i++; i < cEntries; i++)
400 RTMemFree(pCache->aEntries[i].pbData);
401 RTMemFree(pCache);
402 return RTErrInfoSetF(pErrInfo, VERR_NO_MEMORY, "Failed to allocate FAT cache entry (%#x bytes)", cbEntry);
403 }
404
405 pCache->aEntries[i].offFat = UINT32_MAX;
406 pCache->aEntries[i].bmDirty = 0;
407 }
408
409 /*
410 * Calc the dirty shift factor.
411 */
412 cbEntry /= 64;
413 if (cbEntry < pThis->cbSector)
414 cbEntry = pThis->cbSector;
415
416 pCache->cDirtyShift = 1;
417 pCache->cbDirtyLine = 1;
418 while (pCache->cbDirtyLine < cbEntry)
419 {
420 pCache->cDirtyShift++;
421 pCache->cbDirtyLine <<= 1;
422 }
423
424 /*
425 * Fill the cache if single entry or entry size is 512.
426 */
427 if (pCache->cEntries == 1 || pCache->cbEntry == 512)
428 {
429 memcpy(pCache->aEntries[0].pbData, pbFirst512FatBytes, RT_MIN(512, pCache->cbEntry));
430 if (pCache->cbEntry > 512)
431 {
432 int rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->aoffFats[0] + 512,
433 &pCache->aEntries[0].pbData[512], pCache->cbEntry - 512, NULL);
434 if (RT_FAILURE(rc))
435 return RTErrInfoSet(pErrInfo, rc, "Error reading FAT into memory");
436 }
437 pCache->aEntries[0].offFat = 0;
438 pCache->aEntries[0].bmDirty = 0;
439 }
440
441 return VINF_SUCCESS;
442}
443
444
445/**
446 * Worker for rtFsFatClusterMap_Flush and rtFsFatClusterMap_FlushEntry.
447 *
448 * @returns IPRT status code. On failure, we're currently kind of screwed.
449 * @param pThis The FAT volume instance.
450 * @param iFirstEntry Entry to start flushing at.
451 * @param iLastEntry Last entry to flush.
452 */
453static int rtFsFatClusterMap_FlushWorker(PRTFSFATVOL pThis, uint32_t const iFirstEntry, uint32_t const iLastEntry)
454{
455 PRTFSFATCLUSTERMAPCACHE pCache = pThis->pFatCache;
456
457 /*
458 * Walk the cache entries, accumulating segments to flush.
459 */
460 int rc = VINF_SUCCESS;
461 uint64_t off = UINT64_MAX;
462 uint64_t offEdge = UINT64_MAX;
463 RTSGSEG aSgSegs[8];
464 RTSGBUF SgBuf;
465 RTSgBufInit(&SgBuf, aSgSegs, RT_ELEMENTS(aSgSegs));
466 SgBuf.cSegs = 0; /** @todo RTSgBuf API is stupid, make it smarter. */
467
468 for (uint32_t iFatCopy = 0; iFatCopy < pThis->cFats; iFatCopy++)
469 {
470 for (uint32_t iEntry = iFirstEntry; iEntry <= iLastEntry; iEntry++)
471 {
472 uint64_t bmDirty = pCache->aEntries[iEntry].bmDirty;
473 if ( bmDirty != 0
474 && pCache->aEntries[iEntry].offFat != UINT32_MAX)
475 {
476 uint32_t offEntry = 0;
477 uint64_t iDirtyLine = 1;
478 while (offEntry < pCache->cbEntry)
479 {
480 if (pCache->aEntries[iEntry].bmDirty & iDirtyLine)
481 {
482 /*
483 * Found dirty cache line.
484 */
485 uint64_t offDirtyLine = pThis->aoffFats[iFatCopy] + pCache->aEntries[iEntry].offFat + offEntry;
486
487 /* Can we simply extend the last segment? */
488 if ( offDirtyLine == offEdge
489 && offEntry)
490 {
491 Assert( (uintptr_t)aSgSegs[SgBuf.cSegs - 1].pvSeg + aSgSegs[SgBuf.cSegs - 1].cbSeg
492 == (uintptr_t)&pCache->aEntries[iEntry].pbData[offEntry]);
493 aSgSegs[SgBuf.cSegs - 1].cbSeg += pCache->cbDirtyLine;
494 offEdge += pCache->cbDirtyLine;
495 }
496 else
497 {
498 /* flush if not adjacent or if we're out of segments. */
499 if ( offDirtyLine != offEdge
500 || SgBuf.cSegs >= RT_ELEMENTS(aSgSegs))
501 {
502 int rc2 = RTVfsFileSgWrite(pThis->hVfsBacking, off, &SgBuf, true /*fBlocking*/, NULL);
503 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
504 rc = rc2;
505 RTSgBufReset(&SgBuf);
506 SgBuf.cSegs = 0;
507 }
508
509 /* Append segment. */
510 aSgSegs[SgBuf.cSegs].cbSeg = pCache->cbDirtyLine;
511 aSgSegs[SgBuf.cSegs].pvSeg = &pCache->aEntries[iEntry].pbData[offEntry];
512 SgBuf.cSegs++;
513 offEdge = offDirtyLine + pCache->cbDirtyLine;
514 }
515
516 bmDirty &= ~iDirtyLine;
517 if (!bmDirty)
518 break;
519 }
520 }
521 iDirtyLine++;
522 offEntry += pCache->cbDirtyLine;
523 }
524 }
525 }
526
527 /*
528 * Final flush job.
529 */
530 if (SgBuf.cSegs > 0)
531 {
532 int rc2 = RTVfsFileSgWrite(pThis->hVfsBacking, off, &SgBuf, true /*fBlocking*/, NULL);
533 if (RT_FAILURE(rc2) && RT_SUCCESS(rc))
534 rc = rc2;
535 }
536
537 /*
538 * Clear the dirty flags on success.
539 */
540 if (RT_SUCCESS(rc))
541 for (uint32_t iEntry = iFirstEntry; iEntry <= iLastEntry; iEntry++)
542 pCache->aEntries[iEntry].bmDirty = 0;
543
544 return rc;
545}
546
547
548/**
549 * Flushes out all dirty lines in the entire file allocation table cache.
550 *
551 * @returns IPRT status code. On failure, we're currently kind of screwed.
552 * @param pThis The FAT volume instance.
553 */
554static int rtFsFatClusterMap_Flush(PRTFSFATVOL pThis)
555{
556 return rtFsFatClusterMap_FlushWorker(pThis, 0, pThis->pFatCache->cEntries - 1);
557}
558
559
560/**
561 * Flushes out all dirty lines in the file allocation table (cluster map) cache.
562 *
563 * This is typically called prior to reusing the cache entry.
564 *
565 * @returns IPRT status code. On failure, we're currently kind of screwed.
566 * @param pThis The FAT volume instance.
567 * @param iEntry The cache entry to flush.
568 */
569static int rtFsFatClusterMap_FlushEntry(PRTFSFATVOL pThis, uint32_t iEntry)
570{
571 return rtFsFatClusterMap_FlushWorker(pThis, iEntry, iEntry);
572}
573
574
575/**
576 * Destroys the file allcation table cache, first flushing any dirty lines.
577 *
578 * @returns IRPT status code from flush (we've destroyed it regardless of the
579 * status code).
580 * @param pThis The FAT volume instance which cluster map shall be
581 * destroyed.
582 */
583static int rtFsFatClusterMap_Destroy(PRTFSFATVOL pThis)
584{
585 int rc = VINF_SUCCESS;
586 PRTFSFATCLUSTERMAPCACHE pCache = pThis->pFatCache;
587 if (pCache)
588 {
589 /* flush stuff. */
590 rc = rtFsFatClusterMap_Flush(pThis);
591
592 /* free everything. */
593 uint32_t i = pCache->cEntries;
594 while (i-- > 0)
595 {
596 RTMemFree(pCache->aEntries[i].pbData);
597 pCache->aEntries[i].pbData = NULL;
598 }
599 pCache->cEntries = 0;
600 RTMemFree(pCache);
601
602 pThis->pFatCache = NULL;
603 }
604
605 return rc;
606}
607
608
609/**
610 * Reads a cluster chain into memory
611 *
612 * @returns IPRT status code.
613 * @param pThis The FAT volume instance.
614 * @param idxFirstCluster The first cluster.
615 * @param pChain The chain element to read into (and thereby
616 * initialize).
617 */
618static int rtFsFatClusterMap_ReadClusterChain(PRTFSFATVOL pThis, uint32_t idxFirstCluster, PRTFSFATCHAIN pChain)
619{
620 pChain->cClusters = 0;
621 pChain->cbChain = 0;
622 RTListInit(&pChain->ListParts);
623
624 if ( idxFirstCluster >= pThis->cClusters
625 && idxFirstCluster >= FAT_FIRST_DATA_CLUSTER)
626 return VERR_VFS_BOGUS_OFFSET;
627
628 return VERR_NOT_IMPLEMENTED;
629}
630
631
632static void rtFsFatDosDateTimeToSpec(PRTTIMESPEC pTimeSpec, uint16_t uDate, uint16_t uTime, uint8_t cCentiseconds)
633{
634 RTTIME Time;
635 Time.i32Year = 1980 + (uDate >> 9);
636 Time.u8Month = ((uDate >> 5) & 0xf);
637 Time.u8WeekDay = UINT8_MAX;
638 Time.u16YearDay = UINT16_MAX;
639 Time.u8MonthDay = RT_MAX(uDate & 0x1f, 1);
640 Time.u8Hour = uTime >> 11;
641 Time.u8Minute = (uTime >> 5) & 0x3f;
642 Time.u8Second = (uTime & 0x1f) << 1;
643 if (cCentiseconds > 0 && cCentiseconds < 200) /* screw complicated stuff for now. */
644 {
645 if (cCentiseconds >= 100)
646 {
647 cCentiseconds -= 100;
648 Time.u8Second++;
649 }
650 Time.u32Nanosecond = cCentiseconds * UINT64_C(100000000);
651 }
652
653 RTTimeImplode(pTimeSpec, RTTimeNormalize(&Time));
654}
655
656
657static void rtFsFatObj_InitFromDirEntry(PRTFSFATOBJ pObj, PCFATDIRENTRY pDirEntry, uint64_t offDirEntry, PRTFSFATVOL pThis)
658{
659 RTListInit(&pObj->Entry);
660 pObj->pVol = pThis;
661 pObj->offDirEntry = offDirEntry;
662 pObj->fAttrib = ((RTFMODE)pDirEntry->fAttrib << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_OS2;
663 pObj->cbObject = pDirEntry->cbFile;
664 rtFsFatDosDateTimeToSpec(&pObj->ModificationTime, pDirEntry->uAccessDate, pDirEntry->uModifyTime, 0);
665 rtFsFatDosDateTimeToSpec(&pObj->BirthTime, pDirEntry->uBirthDate, pDirEntry->uBirthTime, pDirEntry->uBirthCentiseconds);
666 rtFsFatDosDateTimeToSpec(&pObj->AccessTime, pDirEntry->uAccessDate, 0, 0);
667}
668
669
670static void rtFsFatObj_InitDummy(PRTFSFATOBJ pObj, uint64_t offDirEntry, uint32_t cbObject, RTFMODE fAttrib, PRTFSFATVOL pThis)
671{
672 RTListInit(&pObj->Entry);
673 pObj->pVol = pThis;
674 pObj->offDirEntry = offDirEntry;
675 pObj->fAttrib = fAttrib;
676 pObj->cbObject = cbObject;
677 RTTimeSpecSetDosSeconds(&pObj->AccessTime, 0);
678 RTTimeSpecSetDosSeconds(&pObj->ModificationTime, 0);
679 RTTimeSpecSetDosSeconds(&pObj->BirthTime, 0);
680}
681
682
683/**
684 * @interface_method_impl{RTVFSOBJOPS,pfnClose}
685 */
686static DECLCALLBACK(int) rtFsFatFile_Close(void *pvThis)
687{
688 PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
689 RT_NOREF(pThis);
690 return VERR_NOT_IMPLEMENTED;
691}
692
693
694/**
695 * @interface_method_impl{RTVFSOBJOPS,pfnQueryInfo}
696 */
697static DECLCALLBACK(int) rtFsFatObj_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
698{
699 PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
700
701 pObjInfo->cbObject = pThis->Core.cbObject;
702 pObjInfo->cbAllocated = pThis->Core.Clusters.cClusters * pThis->Core.pVol->cbCluster;
703 pObjInfo->AccessTime = pThis->Core.AccessTime;
704 pObjInfo->ModificationTime = pThis->Core.ModificationTime;
705 pObjInfo->ChangeTime = pThis->Core.ModificationTime;
706 pObjInfo->BirthTime = pThis->Core.BirthTime;
707 pObjInfo->Attr.fMode = pThis->Core.fAttrib;
708 pObjInfo->Attr.enmAdditional = enmAddAttr;
709
710 switch (enmAddAttr)
711 {
712 case RTFSOBJATTRADD_NOTHING: /* fall thru */
713 case RTFSOBJATTRADD_UNIX:
714 pObjInfo->Attr.u.Unix.uid = NIL_RTUID;
715 pObjInfo->Attr.u.Unix.gid = NIL_RTGID;
716 pObjInfo->Attr.u.Unix.cHardlinks = 1;
717 pObjInfo->Attr.u.Unix.INodeIdDevice = 0;
718 pObjInfo->Attr.u.Unix.INodeId = 0; /* Could probably use the directory entry offset. */
719 pObjInfo->Attr.u.Unix.fFlags = 0;
720 pObjInfo->Attr.u.Unix.GenerationId = 0;
721 pObjInfo->Attr.u.Unix.Device = 0;
722 break;
723 case RTFSOBJATTRADD_UNIX_OWNER:
724 pObjInfo->Attr.u.UnixOwner.uid = 0;
725 pObjInfo->Attr.u.UnixOwner.szName[0] = '\0';
726 break;
727 case RTFSOBJATTRADD_UNIX_GROUP:
728 pObjInfo->Attr.u.UnixGroup.gid = 0;
729 pObjInfo->Attr.u.UnixGroup.szName[0] = '\0';
730 break;
731 case RTFSOBJATTRADD_EASIZE:
732 pObjInfo->Attr.u.EASize.cb = 0;
733 break;
734 default:
735 return VERR_INVALID_PARAMETER;
736 }
737 return VINF_SUCCESS;
738}
739
740
741/**
742 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnRead}
743 */
744static DECLCALLBACK(int) rtFsFatFile_Read(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbRead)
745{
746 PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
747 RT_NOREF(pThis, off, pSgBuf, fBlocking, pcbRead);
748 return VERR_NOT_IMPLEMENTED;
749}
750
751
752/**
753 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnWrite}
754 */
755static DECLCALLBACK(int) rtFsFatFile_Write(void *pvThis, RTFOFF off, PCRTSGBUF pSgBuf, bool fBlocking, size_t *pcbWritten)
756{
757 PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
758 RT_NOREF(pThis, off, pSgBuf, fBlocking, pcbWritten);
759 return VERR_NOT_IMPLEMENTED;
760}
761
762
763/**
764 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnFlush}
765 */
766static DECLCALLBACK(int) rtFsFatFile_Flush(void *pvThis)
767{
768 PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
769 RT_NOREF(pThis);
770 return VERR_NOT_IMPLEMENTED;
771}
772
773
774/**
775 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnPollOne}
776 */
777static DECLCALLBACK(int) rtFsFatFile_PollOne(void *pvThis, uint32_t fEvents, RTMSINTERVAL cMillies, bool fIntr,
778 uint32_t *pfRetEvents)
779{
780 NOREF(pvThis);
781 int rc;
782 if (fEvents != RTPOLL_EVT_ERROR)
783 {
784 *pfRetEvents = fEvents & ~RTPOLL_EVT_ERROR;
785 rc = VINF_SUCCESS;
786 }
787 else if (fIntr)
788 rc = RTThreadSleep(cMillies);
789 else
790 {
791 uint64_t uMsStart = RTTimeMilliTS();
792 do
793 rc = RTThreadSleep(cMillies);
794 while ( rc == VERR_INTERRUPTED
795 && !fIntr
796 && RTTimeMilliTS() - uMsStart < cMillies);
797 if (rc == VERR_INTERRUPTED)
798 rc = VERR_TIMEOUT;
799 }
800 return rc;
801}
802
803
804/**
805 * @interface_method_impl{RTVFSIOSTREAMOPS,pfnTell}
806 */
807static DECLCALLBACK(int) rtFsFatFile_Tell(void *pvThis, PRTFOFF poffActual)
808{
809 PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
810 *poffActual = pThis->offFile;
811 return VINF_SUCCESS;
812}
813
814
815/**
816 * @interface_method_impl{RTVFSOBJSETOPS,pfnMode}
817 */
818static DECLCALLBACK(int) rtFsFatObj_SetMode(void *pvThis, RTFMODE fMode, RTFMODE fMask)
819{
820#if 0
821 PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
822 if (fMask != ~RTFS_TYPE_MASK)
823 {
824 fMode |= ~fMask & ObjInfo.Attr.fMode;
825 }
826#else
827 RT_NOREF(pvThis, fMode, fMask);
828 return VERR_NOT_IMPLEMENTED;
829#endif
830}
831
832
833/**
834 * @interface_method_impl{RTVFSOBJSETOPS,pfnSetTimes}
835 */
836static DECLCALLBACK(int) rtFsFatObj_SetTimes(void *pvThis, PCRTTIMESPEC pAccessTime, PCRTTIMESPEC pModificationTime,
837 PCRTTIMESPEC pChangeTime, PCRTTIMESPEC pBirthTime)
838{
839#if 0
840 PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
841#else
842 RT_NOREF(pvThis, pAccessTime, pModificationTime, pChangeTime, pBirthTime);
843 return VERR_NOT_IMPLEMENTED;
844#endif
845}
846
847
848/**
849 * @interface_method_impl{RTVFSOBJSETOPS,pfnSetOwner}
850 */
851static DECLCALLBACK(int) rtFsFatObj_SetOwner(void *pvThis, RTUID uid, RTGID gid)
852{
853 RT_NOREF(pvThis, uid, gid);
854 return VERR_NOT_SUPPORTED;
855}
856
857
858/**
859 * @interface_method_impl{RTVFSFILEOPS,pfnSeek}
860 */
861static DECLCALLBACK(int) rtFsFatFile_Seek(void *pvThis, RTFOFF offSeek, unsigned uMethod, PRTFOFF poffActual)
862{
863 PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
864 RTFOFF offNew;
865 switch (uMethod)
866 {
867 case RTFILE_SEEK_BEGIN:
868 offNew = offSeek;
869 break;
870 case RTFILE_SEEK_END:
871 offNew = (RTFOFF)pThis->Core.cbObject + offSeek;
872 break;
873 case RTFILE_SEEK_CURRENT:
874 offNew = (RTFOFF)pThis->offFile + offSeek;
875 break;
876 default:
877 return VERR_INVALID_PARAMETER;
878 }
879 if (offNew >= 0)
880 {
881 if (offNew <= _4G)
882 {
883 pThis->offFile = offNew;
884 *poffActual = offNew;
885 return VINF_SUCCESS;
886 }
887 return VERR_OUT_OF_RANGE;
888 }
889 return VERR_NEGATIVE_SEEK;
890}
891
892
893/**
894 * @interface_method_impl{RTVFSFILEOPS,pfnQuerySize}
895 */
896static DECLCALLBACK(int) rtFsFatFile_QuerySize(void *pvThis, uint64_t *pcbFile)
897{
898 PRTFSFATFILE pThis = (PRTFSFATFILE)pvThis;
899 *pcbFile = pThis->Core.cbObject;
900 return VINF_SUCCESS;
901}
902
903
904/**
905 * FAT file operations.
906 */
907DECL_HIDDEN_CONST(const RTVFSFILEOPS) g_rtFsFatFileOps =
908{
909 { /* Stream */
910 { /* Obj */
911 RTVFSOBJOPS_VERSION,
912 RTVFSOBJTYPE_FILE,
913 "FatFile",
914 rtFsFatFile_Close,
915 rtFsFatObj_QueryInfo,
916 RTVFSOBJOPS_VERSION
917 },
918 RTVFSIOSTREAMOPS_VERSION,
919 0,
920 rtFsFatFile_Read,
921 rtFsFatFile_Write,
922 rtFsFatFile_Flush,
923 rtFsFatFile_PollOne,
924 rtFsFatFile_Tell,
925 NULL /*pfnSkip*/,
926 NULL /*pfnZeroFill*/,
927 RTVFSIOSTREAMOPS_VERSION,
928 },
929 RTVFSFILEOPS_VERSION,
930 0,
931 { /* ObjSet */
932 RTVFSOBJSETOPS_VERSION,
933 RT_OFFSETOF(RTVFSFILEOPS, Stream.Obj) - RT_OFFSETOF(RTVFSFILEOPS, ObjSet),
934 rtFsFatObj_SetMode,
935 rtFsFatObj_SetTimes,
936 rtFsFatObj_SetOwner,
937 RTVFSOBJSETOPS_VERSION
938 },
939 rtFsFatFile_Seek,
940 rtFsFatFile_QuerySize,
941 RTVFSFILEOPS_VERSION
942};
943
944
945/**
946 * @interface_method_impl{RTVFSOBJOPS,pfnClose}
947 */
948static DECLCALLBACK(int) rtFsFatDir_Close(void *pvThis)
949{
950 PRTFSFATDIR pThis = (PRTFSFATDIR)pvThis;
951 RT_NOREF(pThis);
952 return VERR_NOT_IMPLEMENTED;
953}
954
955
956/**
957 * @interface_method_impl{RTVFSOBJOPS,pfnTraversalOpen}
958 */
959static DECLCALLBACK(int) rtFsFatDir_TraversalOpen(void *pvThis, const char *pszEntry, PRTVFSDIR phVfsDir,
960 PRTVFSSYMLINK phVfsSymlink, PRTVFS phVfsMounted)
961{
962 RT_NOREF(pvThis, pszEntry, phVfsDir, phVfsSymlink, phVfsMounted);
963 return VERR_NOT_IMPLEMENTED;
964}
965
966
967/**
968 * @interface_method_impl{RTVFSDIROPS,pfnOpenFile}
969 */
970static DECLCALLBACK(int) rtFsFatDir_OpenFile(void *pvThis, const char *pszFilename, uint32_t fOpen, PRTVFSFILE phVfsFile)
971{
972 RT_NOREF(pvThis, pszFilename, fOpen, phVfsFile);
973 return VERR_NOT_IMPLEMENTED;
974}
975
976
977/**
978 * @interface_method_impl{RTVFSDIROPS,pfnOpenDir}
979 */
980static DECLCALLBACK(int) rtFsFatDir_OpenDir(void *pvThis, const char *pszSubDir, uint32_t fFlags, PRTVFSDIR phVfsDir)
981{
982 RT_NOREF(pvThis, pszSubDir, fFlags, phVfsDir);
983 return VERR_NOT_IMPLEMENTED;
984}
985
986
987/**
988 * @interface_method_impl{RTVFSDIROPS,pfnCreateDir}
989 */
990static DECLCALLBACK(int) rtFsFatDir_CreateDir(void *pvThis, const char *pszSubDir, RTFMODE fMode, PRTVFSDIR phVfsDir)
991{
992 RT_NOREF(pvThis, pszSubDir, fMode, phVfsDir);
993 return VERR_NOT_IMPLEMENTED;
994}
995
996
997/**
998 * @interface_method_impl{RTVFSDIROPS,pfnOpenSymlink}
999 */
1000static DECLCALLBACK(int) rtFsFatDir_OpenSymlink(void *pvThis, const char *pszSymlink, PRTVFSSYMLINK phVfsSymlink)
1001{
1002 RT_NOREF(pvThis, pszSymlink, phVfsSymlink);
1003 return VERR_NOT_SUPPORTED;
1004}
1005
1006
1007/**
1008 * @interface_method_impl{RTVFSDIROPS,pfnCreateSymlink}
1009 */
1010static DECLCALLBACK(int) rtFsFatDir_CreateSymlink(void *pvThis, const char *pszSymlink, const char *pszTarget,
1011 RTSYMLINKTYPE enmType, PRTVFSSYMLINK phVfsSymlink)
1012{
1013 RT_NOREF(pvThis, pszSymlink, pszTarget, enmType, phVfsSymlink);
1014 return VERR_NOT_SUPPORTED;
1015}
1016
1017
1018/**
1019 * @interface_method_impl{RTVFSDIROPS,pfnUnlinkEntry}
1020 */
1021static DECLCALLBACK(int) rtFsFatDir_UnlinkEntry(void *pvThis, const char *pszEntry, RTFMODE fType)
1022{
1023 RT_NOREF(pvThis, pszEntry, fType);
1024 return VERR_NOT_IMPLEMENTED;
1025}
1026
1027
1028/**
1029 * @interface_method_impl{RTVFSDIROPS,pfnRewindDir}
1030 */
1031static DECLCALLBACK(int) rtFsFatDir_RewindDir(void *pvThis)
1032{
1033 RT_NOREF(pvThis);
1034 return VERR_NOT_IMPLEMENTED;
1035}
1036
1037
1038/**
1039 * @interface_method_impl{RTVFSDIROPS,pfnReadDir}
1040 */
1041static DECLCALLBACK(int) rtFsFatDir_ReadDir(void *pvThis, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
1042 RTFSOBJATTRADD enmAddAttr)
1043{
1044 RT_NOREF(pvThis, pDirEntry, pcbDirEntry, enmAddAttr);
1045 return VERR_NOT_IMPLEMENTED;
1046}
1047
1048
1049/**
1050 * FAT file operations.
1051 */
1052static const RTVFSDIROPS g_rtFsFatDirOps =
1053{
1054 { /* Obj */
1055 RTVFSOBJOPS_VERSION,
1056 RTVFSOBJTYPE_FILE,
1057 "FatDir",
1058 rtFsFatDir_Close,
1059 rtFsFatObj_QueryInfo,
1060 RTVFSOBJOPS_VERSION
1061 },
1062 RTVFSDIROPS_VERSION,
1063 0,
1064 { /* ObjSet */
1065 RTVFSOBJSETOPS_VERSION,
1066 RT_OFFSETOF(RTVFSFILEOPS, Stream.Obj) - RT_OFFSETOF(RTVFSFILEOPS, ObjSet),
1067 rtFsFatObj_SetMode,
1068 rtFsFatObj_SetTimes,
1069 rtFsFatObj_SetOwner,
1070 RTVFSOBJSETOPS_VERSION
1071 },
1072 rtFsFatDir_TraversalOpen,
1073 rtFsFatDir_OpenFile,
1074 rtFsFatDir_OpenDir,
1075 rtFsFatDir_CreateDir,
1076 rtFsFatDir_OpenSymlink,
1077 rtFsFatDir_CreateSymlink,
1078 rtFsFatDir_UnlinkEntry,
1079 rtFsFatDir_RewindDir,
1080 rtFsFatDir_ReadDir,
1081 RTVFSDIROPS_VERSION,
1082};
1083
1084
1085/**
1086 * Instantiates a new directory.
1087 *
1088 * @returns IPRT status code.
1089 * @param pThis The FAT volume instance.
1090 * @param pParentDir The parent directory. This is NULL for the root
1091 * directory.
1092 * @param pDirEntry The parent directory entry. This is NULL for the root
1093 * directory.
1094 * @param offDirEntry The byte offset of the directory entry. UINT64_MAX if
1095 * root directory.
1096 * @param idxCluster The cluster where the directory content is to be found.
1097 * This can be UINT32_MAX if a root FAT12/16 directory.
1098 * @param offDisk The disk byte offset of the FAT12/16 root directory.
1099 * This is UINT64_MAX if idxCluster is given.
1100 * @param cbDir The size of the directory.
1101 * @param phVfsDir Where to return the directory handle.
1102 * @param ppDir Where to return the FAT directory instance data.
1103 */
1104static int rtFsFatDir_New(PRTFSFATVOL pThis, PRTFSFATDIR pParentDir, PCFATDIRENTRY pDirEntry, uint64_t offDirEntry,
1105 uint32_t idxCluster, uint64_t offDisk, uint32_t cbDir, PRTVFSDIR phVfsDir, PRTFSFATDIR *ppDir)
1106{
1107 Assert((idxCluster == UINT32_MAX) != (offDisk == UINT64_MAX));
1108 *ppDir = NULL;
1109
1110 PRTFSFATDIR pNewDir;
1111 int rc = RTVfsNewDir(&g_rtFsFatDirOps, sizeof(*pNewDir), 0 /*fFlags*/, pThis->hVfsSelf,
1112 NIL_RTVFSLOCK, phVfsDir, (void **)&pNewDir);
1113 if (RT_SUCCESS(rc))
1114 {
1115 /*
1116 * Initialize it all so rtFsFatDir_Close doesn't trip up in anyway.
1117 */
1118 RTListInit(&pNewDir->OpenChildren);
1119 if (pDirEntry)
1120 rtFsFatObj_InitFromDirEntry(&pNewDir->Core, pDirEntry, offDirEntry, pThis);
1121 else
1122 rtFsFatObj_InitDummy(&pNewDir->Core, offDirEntry, cbDir, RTFS_DOS_DIRECTORY, pThis);
1123
1124 pNewDir->cEntries = cbDir / sizeof(FATDIRENTRY);
1125 pNewDir->fIsFlatRootDir = idxCluster == UINT32_MAX;
1126 pNewDir->fFullyBuffered = pNewDir->fIsFlatRootDir;
1127 if (pNewDir->fFullyBuffered)
1128 {
1129 pNewDir->u.Full.offDir = UINT64_MAX;
1130 pNewDir->u.Full.cSectors = 0;
1131 pNewDir->u.Full.cDirtySectors = 0;
1132 pNewDir->u.Full.paEntries = NULL;
1133 pNewDir->u.Full.pbDirtySectors = NULL;
1134 }
1135 else
1136 {
1137 pNewDir->u.Simple.offOnDisk = UINT64_MAX;
1138 pNewDir->u.Simple.offInDir = UINT32_MAX;
1139 pNewDir->u.Simple.u32Reserved = 0;
1140 pNewDir->u.Simple.paEntries = NULL;
1141 pNewDir->u.Simple.fDirty = false;
1142 }
1143
1144 /*
1145 * If clustered backing, read the chain and see if we cannot still do the full buffering.
1146 */
1147 if (idxCluster != UINT32_MAX)
1148 {
1149 rc = rtFsFatClusterMap_ReadClusterChain(pThis, idxCluster, &pNewDir->Core.Clusters);
1150 if (RT_SUCCESS(rc))
1151 {
1152 if ( pNewDir->Core.Clusters.cClusters >= 1
1153 && pNewDir->Core.Clusters.cbChain <= _64K
1154 && rtFsFatChain_IsContiguous(&pNewDir->Core.Clusters))
1155 pNewDir->fFullyBuffered = true;
1156 }
1157 }
1158
1159 if (RT_SUCCESS(rc))
1160 {
1161 RT_NOREF(pParentDir);
1162 }
1163
1164 RTVfsDirRelease(*phVfsDir);
1165 }
1166 *phVfsDir = NIL_RTVFSDIR;
1167 *ppDir = NULL;
1168 return rc;
1169}
1170
1171
1172
1173
1174
1175/**
1176 * @interface_method_impl{RTVFSOBJOPS::Obj,pfnClose}
1177 */
1178static DECLCALLBACK(int) rtFsFatVol_Close(void *pvThis)
1179{
1180 PRTFSFATVOL pThis = (PRTFSFATVOL)pvThis;
1181 int rc = rtFsFatClusterMap_Destroy(pThis);
1182
1183 if (pThis->hVfsRootDir != NIL_RTVFSDIR)
1184 {
1185 Assert(RTListIsEmpty(&pThis->pRootDir->OpenChildren));
1186 uint32_t cRefs = RTVfsDirRelease(pThis->hVfsRootDir);
1187 Assert(cRefs == 0);
1188 pThis->hVfsRootDir = NIL_RTVFSDIR;
1189 pThis->pRootDir = NULL;
1190 }
1191
1192 RTVfsFileRelease(pThis->hVfsBacking);
1193 pThis->hVfsBacking = NIL_RTVFSFILE;
1194
1195 return rc;
1196}
1197
1198
1199/**
1200 * @interface_method_impl{RTVFSOBJOPS::Obj,pfnQueryInfo}
1201 */
1202static DECLCALLBACK(int) rtFsFatVol_QueryInfo(void *pvThis, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAddAttr)
1203{
1204 RT_NOREF(pvThis, pObjInfo, enmAddAttr);
1205 return VERR_WRONG_TYPE;
1206}
1207
1208
1209/**
1210 * @interface_method_impl{RTVFSOPS,pfnOpenRoo}
1211 */
1212static DECLCALLBACK(int) rtFsFatVol_OpenRoot(void *pvThis, PRTVFSDIR phVfsDir)
1213{
1214 RT_NOREF(pvThis, phVfsDir);
1215 return VERR_NOT_IMPLEMENTED;
1216}
1217
1218
1219/**
1220 * @interface_method_impl{RTVFSOPS,pfnIsRangeInUse}
1221 */
1222static DECLCALLBACK(int) rtFsFatVol_IsRangeInUse(void *pvThis, RTFOFF off, size_t cb, bool *pfUsed)
1223{
1224 RT_NOREF(pvThis, off, cb, pfUsed);
1225 return VERR_NOT_IMPLEMENTED;
1226}
1227
1228
1229DECL_HIDDEN_CONST(const RTVFSOPS) g_rtFsFatVolOps =
1230{
1231 { /* Obj */
1232 RTVFSOBJOPS_VERSION,
1233 RTVFSOBJTYPE_VFS,
1234 "FatVol",
1235 rtFsFatVol_Close,
1236 rtFsFatVol_QueryInfo,
1237 RTVFSOBJOPS_VERSION
1238 },
1239 RTVFSOPS_VERSION,
1240 0 /* fFeatures */,
1241 rtFsFatVol_OpenRoot,
1242 rtFsFatVol_IsRangeInUse,
1243 RTVFSOPS_VERSION
1244};
1245
1246
1247/**
1248 * Tries to detect a DOS 1.x formatted image and fills in the BPB fields.
1249 *
1250 * There is no BPB here, but fortunately, there isn't much variety.
1251 *
1252 * @returns IPRT status code.
1253 * @param pThis The FAT volume instance, BPB derived fields are filled
1254 * in on success.
1255 * @param pBootSector The boot sector.
1256 * @param pbFatSector Points to the FAT sector, or whatever is 512 bytes after
1257 * the boot sector.
1258 * @param pErrInfo Where to return additional error information.
1259 */
1260static int rtFsFatVolTryInitDos1x(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, uint8_t const *pbFatSector,
1261 PRTERRINFO pErrInfo)
1262{
1263 /*
1264 * PC-DOS 1.0 does a 2fh byte short jump w/o any NOP following it.
1265 * Instead the following are three words and a 9 byte build date
1266 * string. The remaining space is zero filled.
1267 *
1268 * Note! No idea how this would look like for 8" floppies, only got 5"1/4'.
1269 *
1270 * ASSUME all non-BPB disks are using this format.
1271 */
1272 if ( pBootSector->abJmp[0] != 0xeb /* jmp rel8 */
1273 || pBootSector->abJmp[1] < 0x2f
1274 || pBootSector->abJmp[1] >= 0x80
1275 || pBootSector->abJmp[2] == 0x90 /* nop */)
1276 return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
1277 "No DOS v1.0 bootsector either - invalid jmp: %.3Rhxs", pBootSector->abJmp);
1278 uint32_t const offJump = 2 + pBootSector->abJmp[1];
1279 uint32_t const offFirstZero = 2 /*jmp */ + 3 * 2 /* words */ + 9 /* date string */;
1280 Assert(offFirstZero >= RT_OFFSETOF(FATBOOTSECTOR, Bpb));
1281 uint32_t const cbZeroPad = RT_MIN(offJump - offFirstZero,
1282 sizeof(pBootSector->Bpb.Bpb20) - (offFirstZero - RT_OFFSETOF(FATBOOTSECTOR, Bpb)));
1283
1284 if (!ASMMemIsAllU8((uint8_t const *)pBootSector + offFirstZero, cbZeroPad, 0))
1285 return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
1286 "No DOS v1.0 bootsector either - expected zero padding %#x LB %#x: %.*Rhxs",
1287 offFirstZero, cbZeroPad, cbZeroPad, (uint8_t const *)pBootSector + offFirstZero);
1288
1289 /*
1290 * Check the FAT ID so we can tell if this is double or single sided,
1291 * as well as being a valid FAT12 start.
1292 */
1293 if ( (pbFatSector[0] != 0xfe && pbFatSector[0] != 0xff)
1294 || pbFatSector[1] != 0xff
1295 || pbFatSector[2] != 0xff)
1296 return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
1297 "No DOS v1.0 bootsector either - unexpected start of FAT: %.3Rhxs", pbFatSector);
1298
1299 /*
1300 * Fixed DOS 1.0 config.
1301 */
1302 pThis->enmFatType = RTFSFATTYPE_FAT12;
1303 pThis->bMedia = pbFatSector[0];
1304 pThis->cReservedSectors = 1;
1305 pThis->cbSector = 512;
1306 pThis->cbCluster = pThis->bMedia == 0xfe ? 1024 : 512;
1307 pThis->cFats = 2;
1308 pThis->cbFat = 512;
1309 pThis->aoffFats[0] = pThis->offBootSector + pThis->cReservedSectors * 512;
1310 pThis->aoffFats[1] = pThis->aoffFats[0] + pThis->cbFat;
1311 pThis->offRootDir = pThis->aoffFats[1] + pThis->cbFat;
1312 pThis->cRootDirEntries = 512;
1313 pThis->offFirstCluster = pThis->offRootDir + RT_ALIGN_32(pThis->cRootDirEntries * sizeof(FATDIRENTRY),
1314 pThis->cbSector);
1315 pThis->cbTotalSize = pThis->bMedia == 0xfe ? 8 * 1 * 40 * 512 : 8 * 2 * 40 * 512;
1316 pThis->cClusters = (pThis->cbTotalSize - (pThis->offFirstCluster - pThis->offBootSector)) / pThis->cbCluster;
1317 return VINF_SUCCESS;
1318}
1319
1320
1321/**
1322 * Worker for rtFsFatVolTryInitDos2Plus that handles remaining BPB fields.
1323 *
1324 * @returns IPRT status code.
1325 * @param pThis The FAT volume instance, BPB derived fields are filled
1326 * in on success.
1327 * @param pBootSector The boot sector.
1328 * @param fMaybe331 Set if it could be a DOS v3.31 BPB.
1329 * @param pErrInfo Where to return additional error information.
1330 */
1331static int rtFsFatVolTryInitDos2PlusBpb(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, bool fMaybe331, PRTERRINFO pErrInfo)
1332{
1333 /*
1334 * Figure total sector count. Could both be zero, in which case we have to
1335 * fall back on the size of the backing stuff.
1336 */
1337 if (pBootSector->Bpb.Bpb20.cTotalSectors16 != 0)
1338 pThis->cbTotalSize = pBootSector->Bpb.Bpb20.cTotalSectors16 * pThis->cbSector;
1339 else if ( pBootSector->Bpb.Bpb331.cTotalSectors32 != 0
1340 && fMaybe331)
1341 pThis->cbTotalSize = pBootSector->Bpb.Bpb331.cTotalSectors32 * (uint64_t)pThis->cbSector;
1342 else
1343 pThis->cbTotalSize = pThis->cbBacking - pThis->offBootSector;
1344 if (pThis->cReservedSectors * pThis->cbSector >= pThis->cbTotalSize)
1345 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
1346 "Bogus FAT12/16 total or reserved sector count: %#x vs %#x",
1347 pThis->cReservedSectors, pThis->cbTotalSize / pThis->cbSector);
1348
1349 /*
1350 * The fat size. Complete FAT offsets.
1351 */
1352 if ( pBootSector->Bpb.Bpb20.cSectorsPerFat == 0
1353 || ((uint32_t)pBootSector->Bpb.Bpb20.cSectorsPerFat * pThis->cFats + 1) * pThis->cbSector > pThis->cbTotalSize)
1354 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT12/16 sectors per FAT: %#x (total sectors %#RX64)",
1355 pBootSector->Bpb.Bpb20.cSectorsPerFat, pThis->cbTotalSize / pThis->cbSector);
1356 pThis->cbFat = pBootSector->Bpb.Bpb20.cSectorsPerFat * pThis->cbSector;
1357
1358 AssertReturn(pThis->cFats < RT_ELEMENTS(pThis->aoffFats), VERR_VFS_BOGUS_FORMAT);
1359 for (unsigned iFat = 1; iFat <= pThis->cFats; iFat++)
1360 pThis->aoffFats[iFat] = pThis->aoffFats[iFat - 1] + pThis->cbFat;
1361
1362 /*
1363 * Do root directory calculations.
1364 */
1365 pThis->idxRootDirCluster = UINT32_MAX;
1366 pThis->offRootDir = pThis->aoffFats[pThis->cFats];
1367 if (pThis->cRootDirEntries == 0)
1368 return RTErrInfoSet(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Zero FAT12/16 root directory size");
1369 pThis->cbRootDir = pThis->cRootDirEntries * sizeof(FATDIRENTRY);
1370 pThis->cbRootDir = RT_ALIGN_32(pThis->cbRootDir, pThis->cbSector);
1371
1372 /*
1373 * First cluster and cluster count checks and calcs. Determin FAT type.
1374 */
1375 pThis->offFirstCluster = pThis->offRootDir + pThis->cbRootDir;
1376 uint64_t cbSystemStuff = pThis->offFirstCluster - pThis->offBootSector;
1377 if (cbSystemStuff >= pThis->cbTotalSize)
1378 return RTErrInfoSet(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT12/16 total size, root dir, or fat size");
1379 pThis->cClusters = (pThis->cbTotalSize - cbSystemStuff) / pThis->cbCluster;
1380
1381 if (pThis->cClusters >= FAT_LAST_FAT16_DATA_CLUSTER)
1382 {
1383 pThis->cClusters = FAT_LAST_FAT16_DATA_CLUSTER + 1;
1384 pThis->enmFatType = RTFSFATTYPE_FAT16;
1385 }
1386 else if (pThis->cClusters > FAT_LAST_FAT12_DATA_CLUSTER)
1387 pThis->enmFatType = RTFSFATTYPE_FAT16;
1388 else
1389 pThis->enmFatType = RTFSFATTYPE_FAT12; /** @todo Not sure if this is entirely the right way to go about it... */
1390
1391 uint32_t cClustersPerFat;
1392 if (pThis->enmFatType == RTFSFATTYPE_FAT16)
1393 cClustersPerFat = pThis->cbFat / 2;
1394 else
1395 cClustersPerFat = pThis->cbFat * 2 / 3;
1396 if (pThis->cClusters > cClustersPerFat)
1397 pThis->cClusters = cClustersPerFat;
1398
1399 return VINF_SUCCESS;
1400}
1401
1402
1403/**
1404 * Worker for rtFsFatVolTryInitDos2Plus and rtFsFatVolTryInitDos2PlusFat32 that
1405 * handles common extended BPBs fields.
1406 *
1407 * @returns IPRT status code.
1408 * @param pThis The FAT volume instance.
1409 * @param bExtSignature The extended BPB signature.
1410 * @param uSerialNumber The serial number.
1411 * @param pachLabel Pointer to the volume label field.
1412 * @param pachType Pointer to the file system type field.
1413 */
1414static void rtFsFatVolInitCommonEbpbBits(PRTFSFATVOL pThis, uint8_t bExtSignature, uint32_t uSerialNumber,
1415 char const *pachLabel, char const *pachType)
1416{
1417 pThis->uSerialNo = uSerialNumber;
1418 if (bExtSignature == FATEBPB_SIGNATURE)
1419 {
1420 memcpy(pThis->szLabel, pachLabel, RT_SIZEOFMEMB(FATEBPB, achLabel));
1421 pThis->szLabel[RT_SIZEOFMEMB(FATEBPB, achLabel)] = '\0';
1422 RTStrStrip(pThis->szLabel);
1423
1424 memcpy(pThis->szType, pachType, RT_SIZEOFMEMB(FATEBPB, achType));
1425 pThis->szType[RT_SIZEOFMEMB(FATEBPB, achType)] = '\0';
1426 RTStrStrip(pThis->szType);
1427 }
1428 else
1429 {
1430 pThis->szLabel[0] = '\0';
1431 pThis->szType[0] = '\0';
1432 }
1433}
1434
1435
1436/**
1437 * Worker for rtFsFatVolTryInitDos2Plus that deals with FAT32.
1438 *
1439 * @returns IPRT status code.
1440 * @param pThis The FAT volume instance, BPB derived fields are filled
1441 * in on success.
1442 * @param pBootSector The boot sector.
1443 * @param pErrInfo Where to return additional error information.
1444 */
1445static int rtFsFatVolTryInitDos2PlusFat32(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, PRTERRINFO pErrInfo)
1446{
1447 pThis->enmFatType = RTFSFATTYPE_FAT32;
1448 pThis->fFat32Flags = pBootSector->Bpb.Fat32Ebpb.fFlags;
1449
1450 if (pBootSector->Bpb.Fat32Ebpb.uVersion != FAT32EBPB_VERSION_0_0)
1451 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Unsupported FAT32 version: %d.%d (%#x)",
1452 RT_HI_U8(pBootSector->Bpb.Fat32Ebpb.uVersion), RT_LO_U8(pBootSector->Bpb.Fat32Ebpb.uVersion),
1453 pBootSector->Bpb.Fat32Ebpb.uVersion);
1454
1455 /*
1456 * Figure total sector count. We expected it to be filled in.
1457 */
1458 bool fUsing64BitTotalSectorCount = false;
1459 if (pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors16 != 0)
1460 pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors16 * pThis->cbSector;
1461 else if (pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors32 != 0)
1462 pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.Bpb.cTotalSectors32 * (uint64_t)pThis->cbSector;
1463 else if ( pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 <= UINT64_MAX / 512
1464 && pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 > 3
1465 && pBootSector->Bpb.Fat32Ebpb.bExtSignature != FATEBPB_SIGNATURE_OLD)
1466 {
1467 pThis->cbTotalSize = pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64 * pThis->cbSector;
1468 fUsing64BitTotalSectorCount = true;
1469 }
1470 else
1471 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "FAT32 total sector count out of range: %#RX64",
1472 pBootSector->Bpb.Fat32Ebpb.u.cTotalSectors64);
1473 if (pThis->cReservedSectors * pThis->cbSector >= pThis->cbTotalSize)
1474 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
1475 "Bogus FAT32 total or reserved sector count: %#x vs %#x",
1476 pThis->cReservedSectors, pThis->cbTotalSize / pThis->cbSector);
1477
1478 /*
1479 * Fat size. We check the 16-bit field even if it probably should be zero all the time.
1480 */
1481 if (pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat != 0)
1482 {
1483 if ( pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 != 0
1484 && pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 != pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat)
1485 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
1486 "Both 16-bit and 32-bit FAT size fields are set: %#RX16 vs %#RX32",
1487 pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat, pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32);
1488 pThis->cbFat = pBootSector->Bpb.Fat32Ebpb.Bpb.cSectorsPerFat * pThis->cbSector;
1489 }
1490 else
1491 {
1492 uint64_t cbFat = pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32 * (uint64_t)pThis->cbSector;
1493 if ( cbFat == 0
1494 || cbFat >= (FAT_LAST_FAT32_DATA_CLUSTER + 1) * 4 + pThis->cbSector * 16)
1495 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
1496 "Bogus 32-bit FAT size: %#RX32", pBootSector->Bpb.Fat32Ebpb.cSectorsPerFat32);
1497 pThis->cbFat = (uint32_t)cbFat;
1498 }
1499
1500 /*
1501 * Complete the FAT offsets and first cluster offset, then calculate number
1502 * of data clusters.
1503 */
1504 AssertReturn(pThis->cFats < RT_ELEMENTS(pThis->aoffFats), VERR_VFS_BOGUS_FORMAT);
1505 for (unsigned iFat = 1; iFat <= pThis->cFats; iFat++)
1506 pThis->aoffFats[iFat] = pThis->aoffFats[iFat - 1] + pThis->cbFat;
1507 pThis->offFirstCluster = pThis->aoffFats[pThis->cFats];
1508
1509 if (pThis->offFirstCluster - pThis->offBootSector >= pThis->cbTotalSize)
1510 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
1511 "Bogus 32-bit FAT size or total sector count: cFats=%d cbFat=%#x cbTotalSize=%#x",
1512 pThis->cFats, pThis->cbFat, pThis->cbTotalSize);
1513
1514 uint64_t cClusters = (pThis->cbTotalSize - (pThis->offFirstCluster - pThis->offBootSector)) / pThis->cbCluster;
1515 if (cClusters <= FAT_LAST_FAT32_DATA_CLUSTER)
1516 pThis->cClusters = (uint32_t)cClusters;
1517 else
1518 pThis->cClusters = FAT_LAST_FAT32_DATA_CLUSTER + 1;
1519 if (pThis->cClusters > pThis->cbFat / 4)
1520 pThis->cClusters = pThis->cbFat / 4;
1521
1522 /*
1523 * Root dir cluster.
1524 */
1525 if ( pBootSector->Bpb.Fat32Ebpb.uRootDirCluster < FAT_FIRST_DATA_CLUSTER
1526 || pBootSector->Bpb.Fat32Ebpb.uRootDirCluster >= pThis->cClusters)
1527 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
1528 "Bogus FAT32 root directory cluster: %#x", pBootSector->Bpb.Fat32Ebpb.uRootDirCluster);
1529 pThis->idxRootDirCluster = pBootSector->Bpb.Fat32Ebpb.uRootDirCluster;
1530 pThis->offRootDir = pThis->offFirstCluster
1531 + (pBootSector->Bpb.Fat32Ebpb.uRootDirCluster - FAT_FIRST_DATA_CLUSTER) * pThis->cbCluster;
1532
1533 /*
1534 * Info sector.
1535 */
1536 if ( pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo == 0
1537 || pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo == UINT16_MAX)
1538 pThis->offFat32InfoSector = UINT64_MAX;
1539 else if (pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo >= pThis->cReservedSectors)
1540 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
1541 "Bogus FAT32 info sector number: %#x (reserved sectors %#x)",
1542 pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo, pThis->cReservedSectors);
1543 else
1544 {
1545 pThis->offFat32InfoSector = pThis->cbSector * pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo + pThis->offBootSector;
1546 int rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->offFat32InfoSector,
1547 &pThis->Fat32InfoSector, sizeof(pThis->Fat32InfoSector), NULL);
1548 if (RT_FAILURE(rc))
1549 return RTErrInfoSetF(pErrInfo, rc, "Failed to read FAT32 info sector at offset %#RX64", pThis->offFat32InfoSector);
1550 if ( pThis->Fat32InfoSector.uSignature1 != FAT32INFOSECTOR_SIGNATURE_1
1551 || pThis->Fat32InfoSector.uSignature2 != FAT32INFOSECTOR_SIGNATURE_2
1552 || pThis->Fat32InfoSector.uSignature3 != FAT32INFOSECTOR_SIGNATURE_3)
1553 return RTErrInfoSetF(pErrInfo, rc, "FAT32 info sector signature mismatch: %#x %#x %#x",
1554 pThis->Fat32InfoSector.uSignature1, pThis->Fat32InfoSector.uSignature2,
1555 pThis->Fat32InfoSector.uSignature3);
1556 }
1557
1558 /*
1559 * Boot sector copy.
1560 */
1561 if ( pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo == 0
1562 || pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo == UINT16_MAX)
1563 {
1564 pThis->cBootSectorCopies = 0;
1565 pThis->offBootSectorCopies = UINT64_MAX;
1566 }
1567 else if (pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo >= pThis->cReservedSectors)
1568 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
1569 "Bogus FAT32 info boot sector copy location: %#x (reserved sectors %#x)",
1570 pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo, pThis->cReservedSectors);
1571 else
1572 {
1573 /** @todo not sure if cbSector is correct here. */
1574 pThis->cBootSectorCopies = 3;
1575 if ( (uint32_t)pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo + pThis->cBootSectorCopies
1576 > pThis->cReservedSectors)
1577 pThis->cBootSectorCopies = (uint8_t)(pThis->cReservedSectors - pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo);
1578 pThis->offBootSectorCopies = pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo * pThis->cbSector + pThis->offBootSector;
1579 if ( pThis->offFat32InfoSector != UINT64_MAX
1580 && pThis->offFat32InfoSector - pThis->offBootSectorCopies < (uint64_t)(pThis->cBootSectorCopies * pThis->cbSector))
1581 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "FAT32 info sector and boot sector copies overlap: %#x vs %#x",
1582 pBootSector->Bpb.Fat32Ebpb.uInfoSectorNo, pBootSector->Bpb.Fat32Ebpb.uBootSectorCopySectorNo);
1583 }
1584
1585 /*
1586 * Serial number, label and type.
1587 */
1588 rtFsFatVolInitCommonEbpbBits(pThis, pBootSector->Bpb.Fat32Ebpb.bExtSignature, pBootSector->Bpb.Fat32Ebpb.uSerialNumber,
1589 pBootSector->Bpb.Fat32Ebpb.achLabel,
1590 fUsing64BitTotalSectorCount ? pBootSector->achOemName : pBootSector->Bpb.Fat32Ebpb.achLabel);
1591 if (pThis->szType[0] == '\0')
1592 memcpy(pThis->szType, "FAT32", 6);
1593
1594 return VINF_SUCCESS;
1595}
1596
1597
1598/**
1599 * Tries to detect a DOS 2.0+ formatted image and fills in the BPB fields.
1600 *
1601 * We ASSUME BPB here, but need to figure out which version of the BPB it is,
1602 * which is lots of fun.
1603 *
1604 * @returns IPRT status code.
1605 * @param pThis The FAT volume instance, BPB derived fields are filled
1606 * in on success.
1607 * @param pBootSector The boot sector.
1608 * @param pbFatSector Points to the FAT sector, or whatever is 512 bytes after
1609 * the boot sector. On successful return it will contain
1610 * the first FAT sector.
1611 * @param pErrInfo Where to return additional error information.
1612 */
1613static int rtFsFatVolTryInitDos2Plus(PRTFSFATVOL pThis, PCFATBOOTSECTOR pBootSector, uint8_t *pbFatSector, PRTERRINFO pErrInfo)
1614{
1615 /*
1616 * Check if we've got a known jump instruction first, because that will
1617 * give us a max (E)BPB size hint.
1618 */
1619 uint8_t offJmp = UINT8_MAX;
1620 if ( pBootSector->abJmp[0] == 0xeb
1621 && pBootSector->abJmp[1] <= 0x7f)
1622 offJmp = pBootSector->abJmp[1] + 2;
1623 else if ( pBootSector->abJmp[0] == 0x90
1624 && pBootSector->abJmp[1] == 0xeb
1625 && pBootSector->abJmp[2] <= 0x7f)
1626 offJmp = pBootSector->abJmp[2] + 3;
1627 else if ( pBootSector->abJmp[0] == 0xe9
1628 && pBootSector->abJmp[2] <= 0x7f)
1629 offJmp = RT_MIN(127, RT_MAKE_U16(pBootSector->abJmp[1], pBootSector->abJmp[2]));
1630 uint8_t const cbMaxBpb = offJmp - RT_OFFSETOF(FATBOOTSECTOR, Bpb);
1631
1632 /*
1633 * Do the basic DOS v2.0 BPB fields.
1634 */
1635 if (cbMaxBpb < sizeof(FATBPB20))
1636 return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
1637 "DOS signature, but jmp too short for any BPB: %#x (max %#x BPB)", offJmp, cbMaxBpb);
1638
1639 if (pBootSector->Bpb.Bpb20.cFats == 0)
1640 return RTErrInfoSet(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, number of FATs is zero, so not FAT file system");
1641 if (pBootSector->Bpb.Bpb20.cFats > 4)
1642 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "DOS signature, too many FATs: %#x", pBootSector->Bpb.Bpb20.cFats);
1643 pThis->cFats = pBootSector->Bpb.Bpb20.cFats;
1644
1645 if (!FATBPB_MEDIA_IS_VALID(pBootSector->Bpb.Bpb20.bMedia))
1646 return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
1647 "DOS signature, invalid media byte: %#x", pBootSector->Bpb.Bpb20.bMedia);
1648 pThis->bMedia = pBootSector->Bpb.Bpb20.bMedia;
1649
1650 if (!RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb20.cbSector))
1651 return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
1652 "DOS signature, sector size not power of two: %#x", pBootSector->Bpb.Bpb20.cbSector);
1653 if ( pBootSector->Bpb.Bpb20.cbSector != 512
1654 && pBootSector->Bpb.Bpb20.cbSector != 4096
1655 && pBootSector->Bpb.Bpb20.cbSector != 1024
1656 && pBootSector->Bpb.Bpb20.cbSector != 128)
1657 return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT,
1658 "DOS signature, unsupported sector size: %#x", pBootSector->Bpb.Bpb20.cbSector);
1659 pThis->cbSector = pBootSector->Bpb.Bpb20.cbSector;
1660
1661 if ( !RT_IS_POWER_OF_TWO(pBootSector->Bpb.Bpb20.cSectorsPerCluster)
1662 || !pBootSector->Bpb.Bpb20.cSectorsPerCluster)
1663 return RTErrInfoSetF(pErrInfo, VERR_VFS_UNKNOWN_FORMAT, "DOS signature, cluster size not non-zero power of two: %#x",
1664 pBootSector->Bpb.Bpb20.cSectorsPerCluster);
1665 pThis->cbCluster = pBootSector->Bpb.Bpb20.cSectorsPerCluster * pThis->cbSector;
1666
1667 uint64_t const cMaxRoot = (pThis->cbBacking - pThis->offBootSector - 512) / sizeof(FATDIRENTRY); /* we'll check again later. */
1668 if (pBootSector->Bpb.Bpb20.cMaxRootDirEntries >= cMaxRoot)
1669 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "DOS signature, too many root entries: %#x (max %#RX64)",
1670 pBootSector->Bpb.Bpb20.cSectorsPerCluster, cMaxRoot);
1671 pThis->cRootDirEntries = pBootSector->Bpb.Bpb20.cMaxRootDirEntries;
1672
1673 if ( pBootSector->Bpb.Bpb20.cReservedSectors == 0
1674 || pBootSector->Bpb.Bpb20.cReservedSectors >= _32K)
1675 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
1676 "DOS signature, bogus reserved sector count: %#x", pBootSector->Bpb.Bpb20.cReservedSectors);
1677 pThis->cReservedSectors = pBootSector->Bpb.Bpb20.cReservedSectors;
1678 pThis->aoffFats[0] = pThis->offBootSector + pThis->cReservedSectors * pThis->cbSector;
1679
1680 /*
1681 * Jump ahead and check for FAT32 EBPB.
1682 * If found, we simply ASSUME it's a FAT32 file system.
1683 */
1684 int rc;
1685 if ( ( sizeof(FAT32EBPB) <= cbMaxBpb
1686 && pBootSector->Bpb.Fat32Ebpb.bExtSignature == FATEBPB_SIGNATURE)
1687 || ( RT_OFFSETOF(FAT32EBPB, achLabel) <= cbMaxBpb
1688 && pBootSector->Bpb.Fat32Ebpb.bExtSignature == FATEBPB_SIGNATURE_OLD) )
1689 {
1690 rc = rtFsFatVolTryInitDos2PlusFat32(pThis, pBootSector, pErrInfo);
1691 if (RT_FAILURE(rc))
1692 return rc;
1693 }
1694 else
1695 {
1696 /*
1697 * Check for extended BPB, otherwise we'll have to make qualified guesses
1698 * about what kind of BPB we're up against based on jmp offset and zero fields.
1699 * ASSUMES either FAT16 or FAT12.
1700 */
1701 if ( ( sizeof(FATEBPB) <= cbMaxBpb
1702 && pBootSector->Bpb.Ebpb.bExtSignature == FATEBPB_SIGNATURE)
1703 || ( RT_OFFSETOF(FATEBPB, achLabel) <= cbMaxBpb
1704 && pBootSector->Bpb.Ebpb.bExtSignature == FATEBPB_SIGNATURE_OLD) )
1705 {
1706 rtFsFatVolInitCommonEbpbBits(pThis, pBootSector->Bpb.Ebpb.bExtSignature, pBootSector->Bpb.Ebpb.uSerialNumber,
1707 pBootSector->Bpb.Ebpb.achLabel, pBootSector->Bpb.Ebpb.achType);
1708 rc = rtFsFatVolTryInitDos2PlusBpb(pThis, pBootSector, true /*fMaybe331*/, pErrInfo);
1709 }
1710 else
1711 rc = rtFsFatVolTryInitDos2PlusBpb(pThis, pBootSector, cbMaxBpb >= sizeof(FATBPB331), pErrInfo);
1712 if (RT_FAILURE(rc))
1713 return rc;
1714 if (pThis->szType[0] == '\0')
1715 memcpy(pThis->szType, pThis->enmFatType == RTFSFATTYPE_FAT12 ? "FAT12" : "FAT16", 6);
1716 }
1717
1718 /*
1719 * Check the FAT ID. May have to read a bit of the FAT into the buffer.
1720 */
1721 if (pThis->aoffFats[0] != pThis->offBootSector + 512)
1722 {
1723 rc = RTVfsFileReadAt(pThis->hVfsBacking, pThis->aoffFats[0], pbFatSector, 512, NULL);
1724 if (RT_FAILURE(rc))
1725 return RTErrInfoSet(pErrInfo, rc, "error reading first FAT sector");
1726 }
1727 if (pbFatSector[0] != pThis->bMedia)
1728 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT,
1729 "Media byte and FAT ID mismatch: %#x vs %#x (%.7Rhxs)", pbFatSector[0], pThis->bMedia, pbFatSector);
1730 switch (pThis->enmFatType)
1731 {
1732 case RTFSFATTYPE_FAT12:
1733 if ((pbFatSector[1] & 0xf) != 0xf)
1734 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT12): %.3Rhxs", pbFatSector);
1735 pThis->idxMaxLastCluster = FAT_LAST_FAT12_DATA_CLUSTER;
1736 pThis->idxEndOfChain = (pbFatSector[1] >> 4) | ((uint32_t)pbFatSector[2] << 4);
1737 break;
1738
1739 case RTFSFATTYPE_FAT16:
1740 if (pbFatSector[1] != 0xff)
1741 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT16): %.4Rhxs", pbFatSector);
1742 pThis->idxMaxLastCluster = FAT_LAST_FAT16_DATA_CLUSTER;
1743 pThis->idxEndOfChain = RT_MAKE_U16(pbFatSector[2], pbFatSector[3]);
1744 break;
1745
1746 case RTFSFATTYPE_FAT32:
1747 if ( pbFatSector[1] != 0xff
1748 || pbFatSector[2] != 0xff
1749 || (pbFatSector[3] & 0x0f) != 0x0f)
1750 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus FAT ID patting (FAT32): %.8Rhxs", pbFatSector);
1751 pThis->idxMaxLastCluster = FAT_LAST_FAT32_DATA_CLUSTER;
1752 pThis->idxEndOfChain = RT_MAKE_U32_FROM_U8(pbFatSector[4], pbFatSector[5], pbFatSector[6], pbFatSector[7]);
1753 break;
1754
1755 default: AssertFailedReturn(VERR_INTERNAL_ERROR_2);
1756 }
1757 if (pThis->idxEndOfChain <= pThis->idxMaxLastCluster)
1758 return RTErrInfoSetF(pErrInfo, VERR_VFS_BOGUS_FORMAT, "Bogus formatter end-of-chain value: %#x, must be above %#x",
1759 pThis->idxEndOfChain, pThis->idxMaxLastCluster);
1760
1761 RT_NOREF(pbFatSector);
1762 return VINF_SUCCESS;
1763}
1764
1765
1766/**
1767 * Worker for RTFsFatVolOpen.
1768 *
1769 * @returns IPRT status code.
1770 * @param pThis The FAT VFS instance to initialize.
1771 * @param hVfsSelf The FAT VFS handle (no reference consumed).
1772 * @param hVfsBacking The file backing the alleged FAT file system.
1773 * Reference is consumed (via rtFsFatVol_Destroy).
1774 * @param fReadOnly Readonly or readwrite mount.
1775 * @param offBootSector The boot sector offset in bytes.
1776 * @param pErrInfo Where to return additional error info. Can be NULL.
1777 */
1778static int rtFsFatVolTryInit(PRTFSFATVOL pThis, RTVFS hVfsSelf, RTVFSFILE hVfsBacking,
1779 bool fReadOnly, uint64_t offBootSector, PRTERRINFO pErrInfo)
1780{
1781 /*
1782 * First initialize the state so that rtFsFatVol_Destroy won't trip up.
1783 */
1784 pThis->hVfsSelf = hVfsSelf;
1785 pThis->hVfsBacking = hVfsBacking; /* Caller referenced it for us, we consume it; rtFsFatVol_Destroy releases it. */
1786 pThis->cbBacking = 0;
1787 pThis->offBootSector = offBootSector;
1788 pThis->fReadOnly = fReadOnly;
1789 pThis->cReservedSectors = 1;
1790
1791 pThis->cbSector = 512;
1792 pThis->cbCluster = 512;
1793 pThis->cClusters = 0;
1794 pThis->offFirstCluster = 0;
1795 pThis->cbTotalSize = 0;
1796
1797 pThis->enmFatType = RTFSFATTYPE_INVALID;
1798 pThis->cFatEntries = 0;
1799 pThis->cFats = 0;
1800 pThis->cbFat = 0;
1801 for (unsigned i = 0; i < RT_ELEMENTS(pThis->aoffFats); i++)
1802 pThis->aoffFats[i] = UINT64_MAX;
1803 pThis->pFatCache = NULL;
1804
1805 pThis->offRootDir = UINT64_MAX;
1806 pThis->idxRootDirCluster = UINT32_MAX;
1807 pThis->cRootDirEntries = UINT32_MAX;
1808 pThis->cbRootDir = 0;
1809 pThis->hVfsRootDir = NIL_RTVFSDIR;
1810 pThis->pRootDir = NULL;
1811
1812 pThis->uSerialNo = 0;
1813 pThis->szLabel[0] = '\0';
1814 pThis->szType[0] = '\0';
1815 pThis->cBootSectorCopies = 0;
1816 pThis->fFat32Flags = 0;
1817 pThis->offBootSectorCopies = UINT64_MAX;
1818 pThis->offFat32InfoSector = UINT64_MAX;
1819 RT_ZERO(pThis->Fat32InfoSector);
1820
1821 /*
1822 * Get stuff that may fail.
1823 */
1824 int rc = RTVfsFileGetSize(hVfsBacking, &pThis->cbBacking);
1825 if (RT_FAILURE(rc))
1826 return rc;
1827 pThis->cbTotalSize = pThis->cbBacking - pThis->offBootSector;
1828
1829 /*
1830 * Read the boot sector and the following sector (start of the allocation
1831 * table unless it a FAT32 FS). We'll then validate the boot sector and
1832 * start of the FAT, expanding the BPB into the instance data.
1833 */
1834 union
1835 {
1836 uint8_t ab[512*2];
1837 uint16_t au16[512*2 / 2];
1838 uint32_t au32[512*2 / 4];
1839 FATBOOTSECTOR BootSector;
1840 FAT32INFOSECTOR InfoSector;
1841 } Buf;
1842 RT_ZERO(Buf);
1843
1844 rc = RTVfsFileReadAt(hVfsBacking, offBootSector, &Buf.BootSector, 512 * 2, NULL);
1845 if (RT_FAILURE(rc))
1846 return RTErrInfoSet(pErrInfo, rc, "Unable to read bootsect");
1847
1848 /*
1849 * Extract info from the BPB and validate the two special FAT entries.
1850 *
1851 * Check the DOS signature first. The PC-DOS 1.0 boot floppy does not have
1852 * a signature and we ASSUME this is the case for all flopies formated by it.
1853 */
1854 if (Buf.BootSector.uSignature != FATBOOTSECTOR_SIGNATURE)
1855 {
1856 if (Buf.BootSector.uSignature != 0)
1857 return RTErrInfoSetF(pErrInfo, rc, "No DOS bootsector signature: %#06x", Buf.BootSector.uSignature);
1858 rc = rtFsFatVolTryInitDos1x(pThis, &Buf.BootSector, &Buf.ab[512], pErrInfo);
1859 }
1860 else
1861 rc = rtFsFatVolTryInitDos2Plus(pThis, &Buf.BootSector, &Buf.ab[512], pErrInfo);
1862 if (RT_FAILURE(rc))
1863 return rc;
1864
1865 /*
1866 * Setup the FAT cache.
1867 */
1868 rc = rtFsFatClusterMap_Create(pThis, &Buf.ab[512], pErrInfo);
1869 if (RT_FAILURE(rc))
1870 return rc;
1871
1872 /*
1873 * Create the root directory fun.
1874 */
1875 if (pThis->idxRootDirCluster == UINT32_MAX)
1876 rc = rtFsFatDir_New(pThis, NULL /*pParentDir*/, NULL /*pDirEntry*/, 0 /*offDirEntry*/,
1877 UINT32_MAX, pThis->offRootDir, pThis->cbRootDir,
1878 &pThis->hVfsRootDir, &pThis->pRootDir);
1879 else
1880 rc = rtFsFatDir_New(pThis, NULL /*pParentDir*/, NULL /*pDirEntry*/, 0 /*offDirEntry*/,
1881 pThis->idxRootDirCluster, UINT64_MAX, pThis->cbRootDir,
1882 &pThis->hVfsRootDir, &pThis->pRootDir);
1883 if (RT_FAILURE(rc))
1884 return rc;
1885
1886
1887 return RTErrInfoSetF(pErrInfo, VERR_NOT_IMPLEMENTED,
1888 "cbSector=%#x cbCluster=%#x cReservedSectors=%#x\n"
1889 "cFats=%#x cbFat=%#x offFirstFat=%#RX64 offSecondFat=%#RX64\n"
1890 "cbRootDir=%#x offRootDir=%#RX64 offFirstCluster=%#RX64",
1891 pThis->cbSector, pThis->cbCluster, pThis->cReservedSectors,
1892 pThis->cFats, pThis->cbFat, pThis->aoffFats[0], pThis->aoffFats[1],
1893 pThis->cbRootDir, pThis->offRootDir, pThis->offFirstCluster);
1894}
1895
1896
1897RTDECL(int) RTFsFatVolOpen(RTVFSFILE hVfsFileIn, bool fReadOnly, uint64_t offBootSector, PRTVFS phVfs, PRTERRINFO pErrInfo)
1898{
1899 /*
1900 * Quick input validation.
1901 */
1902 AssertPtrReturn(phVfs, VERR_INVALID_POINTER);
1903 *phVfs = NIL_RTVFS;
1904
1905 uint32_t cRefs = RTVfsFileRetain(hVfsFileIn);
1906 AssertReturn(cRefs != UINT32_MAX, VERR_INVALID_HANDLE);
1907
1908 /*
1909 * Create a new FAT VFS instance and try initialize it using the given input file.
1910 */
1911 RTVFS hVfs = NIL_RTVFS;
1912 void *pvThis = NULL;
1913 int rc = RTVfsNew(&g_rtFsFatVolOps, sizeof(RTFSFATVOL), NIL_RTVFS, RTVFSLOCK_CREATE_RW, &hVfs, &pvThis);
1914 if (RT_SUCCESS(rc))
1915 {
1916 rc = rtFsFatVolTryInit((PRTFSFATVOL)pvThis, hVfs, hVfsFileIn, fReadOnly, offBootSector, pErrInfo);
1917 if (RT_SUCCESS(rc))
1918 *phVfs = hVfs;
1919 else
1920 RTVfsRelease(hVfs);
1921 }
1922 else
1923 RTVfsFileRelease(hVfsFileIn);
1924 return rc;
1925}
1926
1927
1928/**
1929 * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnValidate}
1930 */
1931static DECLCALLBACK(int) rtVfsChainFatVol_Validate(PCRTVFSCHAINELEMENTREG pProviderReg, PRTVFSCHAINSPEC pSpec,
1932 PRTVFSCHAINELEMSPEC pElement, uint32_t *poffError, PRTERRINFO pErrInfo)
1933{
1934 RT_NOREF(pProviderReg);
1935
1936 /*
1937 * Basic checks.
1938 */
1939 if (pElement->enmTypeIn != RTVFSOBJTYPE_FILE)
1940 return pElement->enmTypeIn == RTVFSOBJTYPE_INVALID ? VERR_VFS_CHAIN_CANNOT_BE_FIRST_ELEMENT : VERR_VFS_CHAIN_TAKES_FILE;
1941 if ( pElement->enmType != RTVFSOBJTYPE_VFS
1942 && pElement->enmType != RTVFSOBJTYPE_DIR)
1943 return VERR_VFS_CHAIN_ONLY_DIR_OR_VFS;
1944 if (pElement->cArgs > 1)
1945 return VERR_VFS_CHAIN_AT_MOST_ONE_ARG;
1946
1947 /*
1948 * Parse the flag if present, save in pElement->uProvider.
1949 */
1950 bool fReadOnly = (pSpec->fOpenFile & RTFILE_O_ACCESS_MASK) == RTFILE_O_READ;
1951 if (pElement->cArgs > 0)
1952 {
1953 const char *psz = pElement->paArgs[0].psz;
1954 if (*psz)
1955 {
1956 if (!strcmp(psz, "ro"))
1957 fReadOnly = true;
1958 else if (!strcmp(psz, "rw"))
1959 fReadOnly = false;
1960 else
1961 {
1962 *poffError = pElement->paArgs[0].offSpec;
1963 return RTErrInfoSet(pErrInfo, VERR_VFS_CHAIN_INVALID_ARGUMENT, "Expected 'ro' or 'rw' as argument");
1964 }
1965 }
1966 }
1967
1968 pElement->uProvider = fReadOnly;
1969 return VINF_SUCCESS;
1970}
1971
1972
1973/**
1974 * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnInstantiate}
1975 */
1976static DECLCALLBACK(int) rtVfsChainFatVol_Instantiate(PCRTVFSCHAINELEMENTREG pProviderReg, PCRTVFSCHAINSPEC pSpec,
1977 PCRTVFSCHAINELEMSPEC pElement, RTVFSOBJ hPrevVfsObj,
1978 PRTVFSOBJ phVfsObj, uint32_t *poffError, PRTERRINFO pErrInfo)
1979{
1980 RT_NOREF(pProviderReg, pSpec, poffError);
1981
1982 int rc;
1983 RTVFSFILE hVfsFileIn = RTVfsObjToFile(hPrevVfsObj);
1984 if (hVfsFileIn != NIL_RTVFSFILE)
1985 {
1986 RTVFS hVfs;
1987 rc = RTFsFatVolOpen(hVfsFileIn, pElement->uProvider != false, 0, &hVfs, pErrInfo);
1988 RTVfsFileRelease(hVfsFileIn);
1989 if (RT_SUCCESS(rc))
1990 {
1991 *phVfsObj = RTVfsObjFromVfs(hVfs);
1992 RTVfsRelease(hVfs);
1993 if (*phVfsObj != NIL_RTVFSOBJ)
1994 return VINF_SUCCESS;
1995 rc = VERR_VFS_CHAIN_CAST_FAILED;
1996 }
1997 }
1998 else
1999 rc = VERR_VFS_CHAIN_CAST_FAILED;
2000 return rc;
2001}
2002
2003
2004/**
2005 * @interface_method_impl{RTVFSCHAINELEMENTREG,pfnCanReuseElement}
2006 */
2007static DECLCALLBACK(bool) rtVfsChainFatVol_CanReuseElement(PCRTVFSCHAINELEMENTREG pProviderReg,
2008 PCRTVFSCHAINSPEC pSpec, PCRTVFSCHAINELEMSPEC pElement,
2009 PCRTVFSCHAINSPEC pReuseSpec, PCRTVFSCHAINELEMSPEC pReuseElement)
2010{
2011 RT_NOREF(pProviderReg, pSpec, pReuseSpec);
2012 if ( pElement->paArgs[0].uProvider == pReuseElement->paArgs[0].uProvider
2013 || !pReuseElement->paArgs[0].uProvider)
2014 return true;
2015 return false;
2016}
2017
2018
2019/** VFS chain element 'file'. */
2020static RTVFSCHAINELEMENTREG g_rtVfsChainFatVolReg =
2021{
2022 /* uVersion = */ RTVFSCHAINELEMENTREG_VERSION,
2023 /* fReserved = */ 0,
2024 /* pszName = */ "fat",
2025 /* ListEntry = */ { NULL, NULL },
2026 /* pszHelp = */ "Open a FAT file system, requires a file object on the left side.\n"
2027 "First argument is an optional 'ro' (read-only) or 'rw' (read-write) flag.\n",
2028 /* pfnValidate = */ rtVfsChainFatVol_Validate,
2029 /* pfnInstantiate = */ rtVfsChainFatVol_Instantiate,
2030 /* pfnCanReuseElement = */ rtVfsChainFatVol_CanReuseElement,
2031 /* uEndMarker = */ RTVFSCHAINELEMENTREG_VERSION
2032};
2033
2034RTVFSCHAIN_AUTO_REGISTER_ELEMENT_PROVIDER(&g_rtVfsChainFatVolReg, rtVfsChainFatVolReg);
2035
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette