From cebad0b583861b43c2920a21b3ee704392229628 Mon Sep 17 00:00:00 2001 From: Aaron Date: Sun, 1 Nov 2026 20:22:07 +0000 Subject: [PATCH] Fix USB volume discovery, automounting, and file saves --- src/app/app.diskutil.pas | 24 +++++---- src/app/app.notepad.pas | 6 ++- src/app/app.volcmd.pas | 4 +- .../storage/ctl/driver.storage.ctl.usb.pas | 48 +++++++++++++++-- .../storage/driver.storage.metatest.pas | 53 ++++++++++++++++++- src/driver/storage/driver.storage.types.pas | 1 + src/driver/storage/driver.storage.vfs.pas | 45 +++++++++++++++- .../fs/fat32/driver.storage.fs.fat32.vol.pas | 35 +++++++++--- 8 files changed, 189 insertions(+), 27 deletions(-) diff --git a/src/app/app.diskutil.pas b/src/app/app.diskutil.pas index 719c887b..df6d4202 100644 --- a/src/app/app.diskutil.pas +++ b/src/app/app.diskutil.pas @@ -1200,7 +1200,8 @@ begin vol := findVolumeForPartition(device, part.LBA_start); if vol <> nil then begin freeB := uint64(vol^.freeSectors) * vol^.sectorSize; - freeStr := uiConcat(formatSizeStr(freeB), ' free'); + if (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0 then freeStr := 'Free: unknown' + else freeStr := uiConcat(formatSizeStr(freeB), ' free'); lbl := lv_label_create(row); lv_label_set_text(lbl, freeStr); lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0); @@ -1240,7 +1241,6 @@ var dw : sint32; i : uint32; totalBytes : uint64; - usedSectors : uint64; freeBytes : uint64; volCnt : uint32; devVolCnt : uint32; @@ -1274,22 +1274,18 @@ begin totalBytes := uint64(device^.maxSectorCount) * device^.sectorSize; addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes)); - { Compute free space from in-memory volume data (no I/O) } - usedSectors := 0; + { Use the same cached partition geometry as Add Partition. Volumes may + still be undergoing filesystem probing after the device is published. } devVolCnt := 0; volCnt := driver.storage.vol.mgr.get_volume_count(); if volCnt > 0 then for i := 0 to volCnt - 1 do begin vol := driver.storage.vol.mgr.get_volume(i); if (vol <> nil) and (vol^.device = device) then begin devVolCnt := devVolCnt + 1; - usedSectors := usedSectors + vol^.sectorCount; end; end; - if device^.maxSectorCount > usedSectors then - freeBytes := uint64(device^.maxSectorCount - usedSectors) * device^.sectorSize - else - freeBytes := 0; - addInfoRow(detail, dw, 'Free Space', formatSizePrecise(freeBytes)); + freeBytes := uint64(driver.storage.vol.mgr.get_free_sector_count(device)) * device^.sectorSize; + addInfoRow(detail, dw, 'Unallocated Space', formatSizePrecise(freeBytes)); addInfoRow(detail, dw, 'Volumes', uiInt(devVolCnt)); { Usage bar } @@ -1352,7 +1348,8 @@ begin { Free space } freeB := uint64(vol^.freeSectors) * vol^.sectorSize; - freeStr := uiConcat(formatSizeStr(freeB), ' free'); + if (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0 then freeStr := 'Free: unknown' + else freeStr := uiConcat(formatSizeStr(freeB), ' free'); lbl := lv_label_create(row); lv_label_set_text(lbl, freeStr); lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0); @@ -1494,6 +1491,10 @@ begin usedBytes := totalBytes - freeBytes; addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes)); + if (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0 then begin + addInfoRow(detail, dw, 'Free Space', 'Unknown'); + addInfoRow(detail, dw, 'Used Space', 'Unknown'); + end else begin addInfoRow(detail, dw, 'Free Space', formatSizePrecise(freeBytes)); addInfoRow(detail, dw, 'Used Space', formatSizePrecise(usedBytes)); @@ -1502,6 +1503,7 @@ begin addInfoRow(detail, dw, 'Usage', uiConcat(uiInt(pctUsed), '%')); addUsageBar(detail, dw, usedBytes, totalBytes); end; + end; addInfoRow(detail, dw, 'Boot Drive', boolStr(vol^.isBootDrive)); diff --git a/src/app/app.notepad.pas b/src/app/app.notepad.pas index 3a24563d..0a524c56 100644 --- a/src/app/app.notepad.pas +++ b/src/app/app.notepad.pas @@ -355,8 +355,10 @@ begin proc.mgr.kill(state^.pid); state^.pid := 0; end; - freeState(state); + if state^.ta <> nil then lv_group_remove_obj(state^.ta); + state^.ta := nil; driver.video.windows.destroyWindow(wid); + freeState(state); end; { ============================================================ @@ -440,7 +442,7 @@ begin written := driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), len); driver.storage.vfs.CloseFile(fHandle); kfree(void(buf)); - if written = 0 then begin + if written <> len then begin io.syslog.logln('NOTEPAD', 'saveFile: WriteFile returned 0'); showErrorMsgbox('Save Failed', 'File write failed. Check filesystem.'); updateStatusBar(state); diff --git a/src/app/app.volcmd.pas b/src/app/app.volcmd.pas index 6e3196b2..aa7983e0 100644 --- a/src/app/app.volcmd.pas +++ b/src/app/app.volcmd.pas @@ -179,7 +179,9 @@ begin print('Size: '); printsize(vol^.sectorCount * vol^.sectorSize); println(''); - print('Free: '); printsize(vol^.freeSectors * vol^.sectorSize); println(''); + print('Free: '); + if (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0 then println('Unknown') + else begin printsize(vol^.freeSectors * vol^.sectorSize); println(''); end; print('Boot Drive: '); if vol^.isBootDrive then println('yes') else println('no'); diff --git a/src/driver/storage/ctl/driver.storage.ctl.usb.pas b/src/driver/storage/ctl/driver.storage.ctl.usb.pas index e229e651..c02e00f9 100644 --- a/src/driver/storage/ctl/driver.storage.ctl.usb.pas +++ b/src/driver/storage/ctl/driver.storage.ctl.usb.pas @@ -66,6 +66,7 @@ type Request : PIORequest; Tag : uint32; Offline, Ready, InFlight, Attaching, VolumesDetached : boolean; + SyncUnsupported : boolean; end; var @@ -276,6 +277,9 @@ begin if transport(c, actual) <> 0 then exit; if actual < 14 then exit; if (puint8(c^.Buffer)[0] and $7E) <> $70 then exit; + io.syslog.log('USBMSC', 'SCSI sense key='); io.syslog.writeint(puint8(c^.Buffer)[2] and $0F); + io.syslog.writestring(' ASC='); io.syslog.writeint(puint8(c^.Buffer)[12]); + io.syslog.writestring(' ASCQ='); io.syslog.writeintln(puint8(c^.Buffer)[13]); case puint8(c^.Buffer)[2] and $0F of 2: sense := eDeviceNotReady; 3: sense := eBadSector; @@ -286,14 +290,36 @@ begin end; function execute(c : PMSDevice; required : uint32) : TError; -var actual : uint32; resultCode : uint8; +var actual : uint32; resultCode, opcode : uint8; begin + opcode := c^.CBW.Command[0]; resultCode := transport(c, actual); execute := eIOError; if resultCode = 1 then execute := sense(c) else if (resultCode = 0) and (actual = required) then execute := eNone; if c^.Offline then execute := eIOTimeout; if c^.Original = nil then execute := eDeviceRemoved; + if execute <> eNone then begin + io.syslog.log('USBMSC', 'Command failed: opcode='); io.syslog.writeint(opcode); + io.syslog.writestring(' error='); io.syslog.writeintln(ord(execute)); + end; +end; + +function sync_cache(c : PMSDevice) : TError; +begin + sync_cache := eNone; + if c^.SyncUnsupported then exit; + command(c, $35, 10, 0, false); + sync_cache := execute(c, 0); + { Some removable flash devices reject SYNCHRONIZE CACHE with ILLEGAL + REQUEST despite accepting WRITE(10). This optional command must not turn + an acknowledged data write into a zero-byte failure. Other errors remain + failures; do not retry or suppress an unsuccessful WRITE(10). } + if sync_cache = eNotSupported then begin + c^.SyncUnsupported := true; + io.syslog.logln('USBMSC', 'SYNCHRONIZE CACHE unsupported; using device write completion.'); + sync_cache := eNone; + end; end; function probe(c : PMSDevice; var sectors, sectorSize : uint32) : boolean; @@ -393,10 +419,10 @@ begin inc(doneBlocks, blocks); inc(offset, bytes); end; end; - { Complete writes only after the device has committed its cache. } + { Flush devices which support it; unsupported removable media use the + WRITE command completion contract. } if (error = eNone) and (r^.RequestType = ioWrite) then begin - command(c, $35, 10, 0, false); - error := execute(c, 0); + error := sync_cache(c); end; if error = eReadOnly then c^.Disk^.writable := false; r^.ByteCount := offset; @@ -597,6 +623,7 @@ type Request : TIORequest; C : TMSDevice; Stage, Fault, Controls, DataBytes : uint32; + SenseKey : uint8; GoodWrite, GoodRecovery : boolean; end; @@ -629,7 +656,8 @@ begin case m^.C.CBW.Command[0] of $12: puint8(t^.Buffer)[0] := 0; $25: begin put32(puint8(t^.Buffer), 8191); put32(puint8(uint32(t^.Buffer)+4), 512); end; - $03: begin memset(uint32(t^.Buffer),0,t^.BufferLen); puint8(t^.Buffer)[0]:=$70; puint8(t^.Buffer)[2]:=7; end; + $03: begin memset(uint32(t^.Buffer),0,t^.BufferLen); puint8(t^.Buffer)[0]:=$70; + if m^.SenseKey = 0 then puint8(t^.Buffer)[2]:=7 else puint8(t^.Buffer)[2]:=m^.SenseKey; end; end; end else begin m^.GoodWrite := true; @@ -726,6 +754,16 @@ begin Assert(transport(@m^.C,actual) = 2, 'short CSW rejected'); m^.Fault := 0; command(@m^.C, $35, 10, 0, false); Assert(execute(@m^.C,0) = eNone, 'cache synchronization without data stage'); + m^.Fault := 3; m^.SenseKey := 3; + Assert(sync_cache(@m^.C) = eBadSector, 'flush media errors propagate'); + Assert(not m^.C.SyncUnsupported, 'media error does not disable flush'); + m^.Fault := 3; m^.SenseKey := 5; + Assert(sync_cache(@m^.C) = eNone, 'unsupported flush accepts completed write'); + sectors := m^.C.Tag; + Assert((sync_cache(@m^.C) = eNone) and (m^.C.Tag = sectors), 'unsupported flush is not repeatedly submitted'); + m^.Fault := 3; command(@m^.C, $2A, 10, 512, false); + Assert(execute(@m^.C,512) = eNotSupported, 'unsupported data write remains a failure'); + m^.Fault := 0; m^.SenseKey := 0; m^.Disk.sectorSize := 512; m^.Disk.maxSectorCount := 8192; m^.Request.Buffer := m^.C.Buffer; m^.Request.LBA := 8191; m^.Request.SectorCount := 1; Assert(valid_request(@m^.Disk,@m^.Request), 'last sector valid'); diff --git a/src/driver/storage/driver.storage.metatest.pas b/src/driver/storage/driver.storage.metatest.pas index bd09ffe5..0b7a4ad8 100644 --- a/src/driver/storage/driver.storage.metatest.pas +++ b/src/driver/storage/driver.storage.metatest.pas @@ -15,6 +15,7 @@ type TMock = record Data : puint8; Writes, FailWrite, Callbacks, Depth, MaxDepth : uint32; + MaxReadSectors, FailReadLBA : uint32; Error : TError; OutOfBounds, FailRead : boolean; end; @@ -27,7 +28,10 @@ begin (request^.SectorCount > device^.maxSectorCount-request^.LBA) then begin m^.OutOfBounds := true; complete_io(request, false, eInvalidArgument); exit; end; - if (request^.RequestType <> ioWrite) and m^.FailRead then begin + if request^.RequestType <> ioWrite then + if request^.SectorCount > m^.MaxReadSectors then m^.MaxReadSectors := request^.SectorCount; + if (request^.RequestType <> ioWrite) and (m^.FailRead or + ((m^.FailReadLBA <> 0) and (request^.LBA = m^.FailReadLBA))) then begin Inc(m^.Depth); if m^.Depth > m^.MaxDepth then m^.MaxDepth := m^.Depth; complete_io(request,false,eIOError); Dec(m^.Depth); exit; end; @@ -197,6 +201,53 @@ begin mock^.FailWrite := 0; Assert(remove_partition(device,0) = eNone,'delete commits'); Assert(get_volume_count() = oldCount,'delete detaches volume after write'); + { More than eight FAT sectors, with a partial final batch and FAT padding. } + kfree(void(mock^.Data)); + mock^.Data := puint8(kalloc(4096*512)); + if mock^.Data <> nil then begin + memset(uint32(mock^.Data),0,1500*512); + device^.maxSectorCount := 1500; + volume^.sectorStart := 0; volume^.sectorCount := 1500; + Assert(create_volume(volume,1500,0,@params) = eNone,'format multi-batch FAT fixture'); + mock^.MaxReadSectors := 0; + Assert(identify_volume(volume),'identify multi-batch FAT'); + Assert(volume^.freeSectors = 1454,'batched count excludes padding and allocated clusters'); + Assert(mock^.MaxReadSectors = 8,'FAT scan batches eight sectors'); + mock^.FailReadLBA := 40; + Assert(not identify_volume(volume),'failure in final FAT batch rejects probe'); + mock^.FailReadLBA := 0; + Assert(not mock^.OutOfBounds,'batched scan stays within device'); + device^.maxSectorCount := 4096; volume^.sectorCount := 4096; + Assert(create_volume(volume,4096,0,@params) = eNone,'format large FAT fixture'); + mock^.FailReadLBA := 32; mock^.MaxReadSectors := 0; + Assert(identify_volume(volume),'large FAT mounts without reading allocation table'); + Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'missing FSInfo reports unknown free space'); + Assert(mock^.MaxReadSectors = 1,'large volume discovery uses only metadata sector reads'); + PBootRecord(mock^.Data)^.FSInfoCluster := 1; + puint32(uint32(mock^.Data)+512)^ := $41615252; + puint32(uint32(mock^.Data)+512+484)^ := $61417272; + puint32(uint32(mock^.Data)+512+488)^ := 100; + puint32(uint32(mock^.Data)+512+508)^ := $AA550000; + Assert(identify_volume(volume),'valid FSInfo avoids FAT scan'); + Assert((volume^.freeSectors = 100) and + ((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) = 0),'FSInfo count displayed'); + puint32(uint32(mock^.Data)+512+488)^ := $FFFFFFFF; + Assert(identify_volume(volume),'unknown FSInfo count does not prevent mount'); + Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'unknown FSInfo count stays unknown'); + puint32(uint32(mock^.Data)+512+488)^ := 4096; + Assert(identify_volume(volume),'out-of-range FSInfo count does not prevent mount'); + Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'out-of-range FSInfo count rejected'); + puint32(uint32(mock^.Data)+512+488)^ := 0; + Assert(identify_volume(volume),'full volume FSInfo accepted'); + Assert((volume^.freeSectors = 0) and + ((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) = 0),'known zero differs from unknown'); + puint32(uint32(mock^.Data)+512+508)^ := 0; + Assert(identify_volume(volume),'invalid FSInfo signature does not prevent mount'); + Assert((volume^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0,'invalid FSInfo signature rejected'); + mock^.FailReadLBA := 1; + Assert(identify_volume(volume),'optional FSInfo read failure does not prevent mount'); + mock^.FailReadLBA := 0; + end else Assert(false,'allocate multi-batch FAT fixture'); kfree(device^.cachedMBR); CFIFO_Free(device^.requestQueue); kfree(void(mock^.Data)); kfree(void(mock)); kfree(void(volume)); kfree(void(device)); kfree(void(br)); io.syslog.log('STORMETA','Passed: '); io.syslog.writeint(passed); diff --git a/src/driver/storage/driver.storage.types.pas b/src/driver/storage/driver.storage.types.pas index 134c9345..283b07e3 100644 --- a/src/driver/storage/driver.storage.types.pas +++ b/src/driver/storage/driver.storage.types.pas @@ -357,6 +357,7 @@ const STORAGE_VOLUME_FLAG_FORMATTING = 2; STORAGE_VOLUME_FLAG_INVALIDATING = 4; STORAGE_VOLUME_FLAG_REMOVED = 8; + STORAGE_VOLUME_FLAG_FREE_UNKNOWN = 16; implementation diff --git a/src/driver/storage/driver.storage.vfs.pas b/src/driver/storage/driver.storage.vfs.pas index e819cbc6..54a8682a 100644 --- a/src/driver/storage/driver.storage.vfs.pas +++ b/src/driver/storage/driver.storage.vfs.pas @@ -1501,6 +1501,10 @@ begin asm popf end; while puint32(@wait.Done)^ = 0 do asm hlt end; if wait.Error = eNone then TransferFileSync := wait.BytesDone; + if Writing and (wait.Error <> eNone) then begin + io.syslog.log('VFS', 'Write failed: error='); io.syslog.writeint(ord(wait.Error)); + io.syslog.writestring(' bytes='); io.syslog.writeintln(wait.BytesDone); + end; end; function WriteFile(FileHandle : TFileHandle; Position : uint32; Buffer : puint8; Length : uint32) : uint32; @@ -3288,6 +3292,22 @@ begin debug.tracer.push_trace('driver.storage.vfs.VFS_COMMAND_UMOUNT.exit'); end; +{ Installed at the boot mount phase, after the VFS is ready. Registration + happens in the discovery task (usb.attach for USB), not an IRQ or UI callback. + Mounting only creates VFS objects; filesystem probing has already completed. } +var autoMountWatch : uint32 = 0; + +procedure mount_added_volume(event : TStorageLifecycleEvent; device : PStorage_Device; + volume : PStorage_Volume; error : TError; userdata : pointer); +begin + if (event <> sleVolumeAdded) or (error <> eNone) then exit; + if (device = nil) or (volume = nil) then exit; + if device^.removed or (volume^.filesystem = nil) then exit; + if driver.storage.vol.mgr.volume_is_busy(volume) then exit; + if mountDefaultVolume(volume) <> eNone then + io.syslog.logln('VFS','Could not automatically mount newly discovered volume.'); +end; + { Auto-mount all discovered volumes into /disk/ and honour persistent mounts } procedure auto_mount_volumes(); @@ -3312,6 +3332,11 @@ var begin debug.tracer.push_trace('driver.storage.vfs.auto_mount_volumes.enter'); + { Subscribe even when no disks were present at boot. Avoid duplicate watches + if the boot mount pass is invoked again. } + if autoMountWatch = 0 then + autoMountWatch := driver.storage.vol.mgr.watch_lifecycle(@mount_added_volume, nil); + volCount := driver.storage.vol.mgr.get_volume_count(); if volCount = 0 then begin debug.tracer.push_trace('driver.storage.vfs.auto_mount_volumes.exit'); @@ -3658,7 +3683,25 @@ begin obj := GetObjectFromPath('/disk/__RENAMED_TEST__'); Assert((obj <> nil) and (obj^.Reference = void(mountVolA)),'renamed mount points to same volume'); InvalidateVolume(mountVolA); InvalidateVolume(mountVolB); - kfree(void(mountVolA)); kfree(void(mountVolB)); kfree(void(mountFS)); + { Exercise real registration notifications after the boot mount pass. } + mountDevice := PStorage_Device(kalloc(sizeof(TStorage_Device))); + memset(uint32(mountDevice),0,sizeof(TStorage_Device)); + mountDevice^.writable := true; + mountVolA^.device := mountDevice; mountVolB^.device := mountDevice; + memcpy(uint32(pchar('__HOTPLUG_TEST__')),uint32(@mountVolA^.labelText[0]),17); + mountVolB^.labelText := mountVolA^.labelText; + mountVolA^.filesystem := nil; + driver.storage.vol.mgr.register_volume(mountDevice,mountVolA); + Assert(GetObjectFromPath('/disk/__HOTPLUG_TEST__') = nil,'unrecognized hotplug volume is not mounted'); + mountVolB^.filesystem := mountFS; + driver.storage.vol.mgr.register_volume(mountDevice,mountVolB); + obj := GetObjectFromPath('/disk/__HOTPLUG_TEST__'); + Assert((obj <> nil) and (obj^.Reference = void(mountVolB)),'registration automatically mounts recognized volume'); + Assert((mountVolB^.lifecycleFlags and STORAGE_VOLUME_FLAG_MOUNTED) <> 0,'hotplug mount publishes mounted state'); + driver.storage.vol.mgr.delete_volume(mountVolA); + driver.storage.vol.mgr.delete_volume(mountVolB); + Assert(GetObjectFromPath('/disk/__HOTPLUG_TEST__') = nil,'hotplug removal clears mount'); + kfree(void(mountDevice)); kfree(void(mountFS)); watchFired := 0; io.syslog.logln('VFS', 'Unit tests starting...'); diff --git a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.vol.pas b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.vol.pas index 4c1021df..89e01cc1 100644 --- a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.vol.pas +++ b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.vol.pas @@ -62,24 +62,47 @@ begin end; function countFreeFATClusters(volume : PStorage_Volume; br : PBootRecord) : uint32; -var limit, sectorIdx, entryIdx, clusterNum, count : uint32; buffer : puint32; +var limit, sectorIdx, entryIdx, clusterNum, count, sectors, batch : uint32; buffer : puint32; begin countFreeFATClusters := $FFFFFFFF; limit := FAT32ClusterLimit(br); if limit <= 2 then exit; - buffer := puint32(kalloc(512)); + volume^.lifecycleFlags := volume^.lifecycleFlags and (not STORAGE_VOLUME_FLAG_FREE_UNKNOWN); + { Match the USB storage transfer buffer: eight sectors per command instead + of a CBW/data/CSW round trip for every 512 bytes of a large FAT. } + buffer := puint32(kalloc(4096)); if buffer = nil then exit; + { FSInfo is optional, and its count is advisory. Validate every signature, + the reserved-sector location, and the count before displaying it. } + if (br^.FSInfoCluster > 0) and (br^.FSInfoCluster < br^.rsvSectors) then begin + if storage_read(volume^.device, volume^.sectorStart + br^.FSInfoCluster, 1, buffer) = eNone then + if (buffer[0] = $41615252) and (buffer[121] = $61417272) and + (buffer[127] = $AA550000) and (buffer[122] <= limit - 2) then begin + countFreeFATClusters := buffer[122]; kfree(buffer); exit; + end; + end; count := 0; sectorIdx := 0; - while sectorIdx * 128 < limit do begin - if storage_read(volume^.device, volume^.sectorStart + br^.rsvSectors + sectorIdx, 1, buffer) <> eNone then begin + sectors := (limit + 127) div 128; + { Mounting must not wait for an O(disk-size) allocation scan. Small FATs + remain cheap to count; larger volumes without FSInfo mount with unknown + free space. Allocation always consults the actual FAT, never this hint. } + if sectors > 16 then begin + volume^.lifecycleFlags := volume^.lifecycleFlags or STORAGE_VOLUME_FLAG_FREE_UNKNOWN; + countFreeFATClusters := 0; + kfree(buffer); exit; + end; + while sectorIdx < sectors do begin + batch := sectors - sectorIdx; + if batch > 8 then batch := 8; + if storage_read(volume^.device, volume^.sectorStart + br^.rsvSectors + sectorIdx, batch, buffer) <> eNone then begin kfree(buffer); exit; end; - for entryIdx := 0 to 127 do begin + for entryIdx := 0 to batch * 128 - 1 do begin clusterNum := sectorIdx * 128 + entryIdx; if clusterNum >= limit then break; if (clusterNum >= 2) and ((buffer[entryIdx] and $0FFFFFFF) = 0) then Inc(count); end; - Inc(sectorIdx); + Inc(sectorIdx, batch); end; kfree(buffer); countFreeFATClusters := count;