Fix USB volume discovery, automounting, and file saves
This commit is contained in:
1 parent
53cc758e60
commit
cebad0b583
8 files changed
+189
-27
No files matched your search
+13
-11
@@ -1200,7 +1200,8 @@ begin
|
|||||||
vol := findVolumeForPartition(device, part.LBA_start);
|
vol := findVolumeForPartition(device, part.LBA_start);
|
||||||
if vol <> nil then begin
|
if vol <> nil then begin
|
||||||
freeB := uint64(vol^.freeSectors) * vol^.sectorSize;
|
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);
|
lbl := lv_label_create(row);
|
||||||
lv_label_set_text(lbl, freeStr);
|
lv_label_set_text(lbl, freeStr);
|
||||||
lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0);
|
lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0);
|
||||||
@@ -1240,7 +1241,6 @@ var
|
|||||||
dw : sint32;
|
dw : sint32;
|
||||||
i : uint32;
|
i : uint32;
|
||||||
totalBytes : uint64;
|
totalBytes : uint64;
|
||||||
usedSectors : uint64;
|
|
||||||
freeBytes : uint64;
|
freeBytes : uint64;
|
||||||
volCnt : uint32;
|
volCnt : uint32;
|
||||||
devVolCnt : uint32;
|
devVolCnt : uint32;
|
||||||
@@ -1274,22 +1274,18 @@ begin
|
|||||||
totalBytes := uint64(device^.maxSectorCount) * device^.sectorSize;
|
totalBytes := uint64(device^.maxSectorCount) * device^.sectorSize;
|
||||||
addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes));
|
addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes));
|
||||||
|
|
||||||
{ Compute free space from in-memory volume data (no I/O) }
|
{ Use the same cached partition geometry as Add Partition. Volumes may
|
||||||
usedSectors := 0;
|
still be undergoing filesystem probing after the device is published. }
|
||||||
devVolCnt := 0;
|
devVolCnt := 0;
|
||||||
volCnt := driver.storage.vol.mgr.get_volume_count();
|
volCnt := driver.storage.vol.mgr.get_volume_count();
|
||||||
if volCnt > 0 then for i := 0 to volCnt - 1 do begin
|
if volCnt > 0 then for i := 0 to volCnt - 1 do begin
|
||||||
vol := driver.storage.vol.mgr.get_volume(i);
|
vol := driver.storage.vol.mgr.get_volume(i);
|
||||||
if (vol <> nil) and (vol^.device = device) then begin
|
if (vol <> nil) and (vol^.device = device) then begin
|
||||||
devVolCnt := devVolCnt + 1;
|
devVolCnt := devVolCnt + 1;
|
||||||
usedSectors := usedSectors + vol^.sectorCount;
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
if device^.maxSectorCount > usedSectors then
|
freeBytes := uint64(driver.storage.vol.mgr.get_free_sector_count(device)) * device^.sectorSize;
|
||||||
freeBytes := uint64(device^.maxSectorCount - usedSectors) * device^.sectorSize
|
addInfoRow(detail, dw, 'Unallocated Space', formatSizePrecise(freeBytes));
|
||||||
else
|
|
||||||
freeBytes := 0;
|
|
||||||
addInfoRow(detail, dw, 'Free Space', formatSizePrecise(freeBytes));
|
|
||||||
addInfoRow(detail, dw, 'Volumes', uiInt(devVolCnt));
|
addInfoRow(detail, dw, 'Volumes', uiInt(devVolCnt));
|
||||||
|
|
||||||
{ Usage bar }
|
{ Usage bar }
|
||||||
@@ -1352,7 +1348,8 @@ begin
|
|||||||
|
|
||||||
{ Free space }
|
{ Free space }
|
||||||
freeB := uint64(vol^.freeSectors) * vol^.sectorSize;
|
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);
|
lbl := lv_label_create(row);
|
||||||
lv_label_set_text(lbl, freeStr);
|
lv_label_set_text(lbl, freeStr);
|
||||||
lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0);
|
lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0);
|
||||||
@@ -1494,6 +1491,10 @@ begin
|
|||||||
usedBytes := totalBytes - freeBytes;
|
usedBytes := totalBytes - freeBytes;
|
||||||
|
|
||||||
addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes));
|
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, 'Free Space', formatSizePrecise(freeBytes));
|
||||||
addInfoRow(detail, dw, 'Used Space', formatSizePrecise(usedBytes));
|
addInfoRow(detail, dw, 'Used Space', formatSizePrecise(usedBytes));
|
||||||
|
|
||||||
@@ -1502,6 +1503,7 @@ begin
|
|||||||
addInfoRow(detail, dw, 'Usage', uiConcat(uiInt(pctUsed), '%'));
|
addInfoRow(detail, dw, 'Usage', uiConcat(uiInt(pctUsed), '%'));
|
||||||
addUsageBar(detail, dw, usedBytes, totalBytes);
|
addUsageBar(detail, dw, usedBytes, totalBytes);
|
||||||
end;
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
addInfoRow(detail, dw, 'Boot Drive', boolStr(vol^.isBootDrive));
|
addInfoRow(detail, dw, 'Boot Drive', boolStr(vol^.isBootDrive));
|
||||||
|
|
||||||
|
|||||||
@@ -355,8 +355,10 @@ begin
|
|||||||
proc.mgr.kill(state^.pid);
|
proc.mgr.kill(state^.pid);
|
||||||
state^.pid := 0;
|
state^.pid := 0;
|
||||||
end;
|
end;
|
||||||
freeState(state);
|
if state^.ta <> nil then lv_group_remove_obj(state^.ta);
|
||||||
|
state^.ta := nil;
|
||||||
driver.video.windows.destroyWindow(wid);
|
driver.video.windows.destroyWindow(wid);
|
||||||
|
freeState(state);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{ ============================================================
|
{ ============================================================
|
||||||
@@ -440,7 +442,7 @@ begin
|
|||||||
written := driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), len);
|
written := driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), len);
|
||||||
driver.storage.vfs.CloseFile(fHandle);
|
driver.storage.vfs.CloseFile(fHandle);
|
||||||
kfree(void(buf));
|
kfree(void(buf));
|
||||||
if written = 0 then begin
|
if written <> len then begin
|
||||||
io.syslog.logln('NOTEPAD', 'saveFile: WriteFile returned 0');
|
io.syslog.logln('NOTEPAD', 'saveFile: WriteFile returned 0');
|
||||||
showErrorMsgbox('Save Failed', 'File write failed. Check filesystem.');
|
showErrorMsgbox('Save Failed', 'File write failed. Check filesystem.');
|
||||||
updateStatusBar(state);
|
updateStatusBar(state);
|
||||||
|
|||||||
@@ -179,7 +179,9 @@ begin
|
|||||||
|
|
||||||
print('Size: '); printsize(vol^.sectorCount * vol^.sectorSize); println('');
|
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: ');
|
print('Boot Drive: ');
|
||||||
if vol^.isBootDrive then println('yes') else println('no');
|
if vol^.isBootDrive then println('yes') else println('no');
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ type
|
|||||||
Request : PIORequest;
|
Request : PIORequest;
|
||||||
Tag : uint32;
|
Tag : uint32;
|
||||||
Offline, Ready, InFlight, Attaching, VolumesDetached : boolean;
|
Offline, Ready, InFlight, Attaching, VolumesDetached : boolean;
|
||||||
|
SyncUnsupported : boolean;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
var
|
var
|
||||||
@@ -276,6 +277,9 @@ begin
|
|||||||
if transport(c, actual) <> 0 then exit;
|
if transport(c, actual) <> 0 then exit;
|
||||||
if actual < 14 then exit;
|
if actual < 14 then exit;
|
||||||
if (puint8(c^.Buffer)[0] and $7E) <> $70 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
|
case puint8(c^.Buffer)[2] and $0F of
|
||||||
2: sense := eDeviceNotReady;
|
2: sense := eDeviceNotReady;
|
||||||
3: sense := eBadSector;
|
3: sense := eBadSector;
|
||||||
@@ -286,14 +290,36 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
function execute(c : PMSDevice; required : uint32) : TError;
|
function execute(c : PMSDevice; required : uint32) : TError;
|
||||||
var actual : uint32; resultCode : uint8;
|
var actual : uint32; resultCode, opcode : uint8;
|
||||||
begin
|
begin
|
||||||
|
opcode := c^.CBW.Command[0];
|
||||||
resultCode := transport(c, actual);
|
resultCode := transport(c, actual);
|
||||||
execute := eIOError;
|
execute := eIOError;
|
||||||
if resultCode = 1 then execute := sense(c)
|
if resultCode = 1 then execute := sense(c)
|
||||||
else if (resultCode = 0) and (actual = required) then execute := eNone;
|
else if (resultCode = 0) and (actual = required) then execute := eNone;
|
||||||
if c^.Offline then execute := eIOTimeout;
|
if c^.Offline then execute := eIOTimeout;
|
||||||
if c^.Original = nil then execute := eDeviceRemoved;
|
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;
|
end;
|
||||||
|
|
||||||
function probe(c : PMSDevice; var sectors, sectorSize : uint32) : boolean;
|
function probe(c : PMSDevice; var sectors, sectorSize : uint32) : boolean;
|
||||||
@@ -393,10 +419,10 @@ begin
|
|||||||
inc(doneBlocks, blocks); inc(offset, bytes);
|
inc(doneBlocks, blocks); inc(offset, bytes);
|
||||||
end;
|
end;
|
||||||
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
|
if (error = eNone) and (r^.RequestType = ioWrite) then begin
|
||||||
command(c, $35, 10, 0, false);
|
error := sync_cache(c);
|
||||||
error := execute(c, 0);
|
|
||||||
end;
|
end;
|
||||||
if error = eReadOnly then c^.Disk^.writable := false;
|
if error = eReadOnly then c^.Disk^.writable := false;
|
||||||
r^.ByteCount := offset;
|
r^.ByteCount := offset;
|
||||||
@@ -597,6 +623,7 @@ type
|
|||||||
Request : TIORequest;
|
Request : TIORequest;
|
||||||
C : TMSDevice;
|
C : TMSDevice;
|
||||||
Stage, Fault, Controls, DataBytes : uint32;
|
Stage, Fault, Controls, DataBytes : uint32;
|
||||||
|
SenseKey : uint8;
|
||||||
GoodWrite, GoodRecovery : boolean;
|
GoodWrite, GoodRecovery : boolean;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -629,7 +656,8 @@ begin
|
|||||||
case m^.C.CBW.Command[0] of
|
case m^.C.CBW.Command[0] of
|
||||||
$12: puint8(t^.Buffer)[0] := 0;
|
$12: puint8(t^.Buffer)[0] := 0;
|
||||||
$25: begin put32(puint8(t^.Buffer), 8191); put32(puint8(uint32(t^.Buffer)+4), 512); end;
|
$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;
|
||||||
end else begin
|
end else begin
|
||||||
m^.GoodWrite := true;
|
m^.GoodWrite := true;
|
||||||
@@ -726,6 +754,16 @@ begin
|
|||||||
Assert(transport(@m^.C,actual) = 2, 'short CSW rejected');
|
Assert(transport(@m^.C,actual) = 2, 'short CSW rejected');
|
||||||
m^.Fault := 0; command(@m^.C, $35, 10, 0, false);
|
m^.Fault := 0; command(@m^.C, $35, 10, 0, false);
|
||||||
Assert(execute(@m^.C,0) = eNone, 'cache synchronization without data stage');
|
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^.Disk.sectorSize := 512; m^.Disk.maxSectorCount := 8192;
|
||||||
m^.Request.Buffer := m^.C.Buffer; m^.Request.LBA := 8191; m^.Request.SectorCount := 1;
|
m^.Request.Buffer := m^.C.Buffer; m^.Request.LBA := 8191; m^.Request.SectorCount := 1;
|
||||||
Assert(valid_request(@m^.Disk,@m^.Request), 'last sector valid');
|
Assert(valid_request(@m^.Disk,@m^.Request), 'last sector valid');
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type
|
|||||||
TMock = record
|
TMock = record
|
||||||
Data : puint8;
|
Data : puint8;
|
||||||
Writes, FailWrite, Callbacks, Depth, MaxDepth : uint32;
|
Writes, FailWrite, Callbacks, Depth, MaxDepth : uint32;
|
||||||
|
MaxReadSectors, FailReadLBA : uint32;
|
||||||
Error : TError;
|
Error : TError;
|
||||||
OutOfBounds, FailRead : boolean;
|
OutOfBounds, FailRead : boolean;
|
||||||
end;
|
end;
|
||||||
@@ -27,7 +28,10 @@ begin
|
|||||||
(request^.SectorCount > device^.maxSectorCount-request^.LBA) then begin
|
(request^.SectorCount > device^.maxSectorCount-request^.LBA) then begin
|
||||||
m^.OutOfBounds := true; complete_io(request, false, eInvalidArgument); exit;
|
m^.OutOfBounds := true; complete_io(request, false, eInvalidArgument); exit;
|
||||||
end;
|
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;
|
Inc(m^.Depth); if m^.Depth > m^.MaxDepth then m^.MaxDepth := m^.Depth;
|
||||||
complete_io(request,false,eIOError); Dec(m^.Depth); exit;
|
complete_io(request,false,eIOError); Dec(m^.Depth); exit;
|
||||||
end;
|
end;
|
||||||
@@ -197,6 +201,53 @@ begin
|
|||||||
mock^.FailWrite := 0;
|
mock^.FailWrite := 0;
|
||||||
Assert(remove_partition(device,0) = eNone,'delete commits');
|
Assert(remove_partition(device,0) = eNone,'delete commits');
|
||||||
Assert(get_volume_count() = oldCount,'delete detaches volume after write');
|
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(device^.cachedMBR); CFIFO_Free(device^.requestQueue);
|
||||||
kfree(void(mock^.Data)); kfree(void(mock)); kfree(void(volume)); kfree(void(device)); kfree(void(br));
|
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);
|
io.syslog.log('STORMETA','Passed: '); io.syslog.writeint(passed);
|
||||||
|
|||||||
@@ -357,6 +357,7 @@ const
|
|||||||
STORAGE_VOLUME_FLAG_FORMATTING = 2;
|
STORAGE_VOLUME_FLAG_FORMATTING = 2;
|
||||||
STORAGE_VOLUME_FLAG_INVALIDATING = 4;
|
STORAGE_VOLUME_FLAG_INVALIDATING = 4;
|
||||||
STORAGE_VOLUME_FLAG_REMOVED = 8;
|
STORAGE_VOLUME_FLAG_REMOVED = 8;
|
||||||
|
STORAGE_VOLUME_FLAG_FREE_UNKNOWN = 16;
|
||||||
|
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|||||||
@@ -1501,6 +1501,10 @@ begin
|
|||||||
asm popf end;
|
asm popf end;
|
||||||
while puint32(@wait.Done)^ = 0 do asm hlt end;
|
while puint32(@wait.Done)^ = 0 do asm hlt end;
|
||||||
if wait.Error = eNone then TransferFileSync := wait.BytesDone;
|
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;
|
end;
|
||||||
|
|
||||||
function WriteFile(FileHandle : TFileHandle; Position : uint32; Buffer : puint8; Length : uint32) : uint32;
|
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');
|
debug.tracer.push_trace('driver.storage.vfs.VFS_COMMAND_UMOUNT.exit');
|
||||||
end;
|
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 }
|
{ Auto-mount all discovered volumes into /disk/ and honour persistent mounts }
|
||||||
|
|
||||||
procedure auto_mount_volumes();
|
procedure auto_mount_volumes();
|
||||||
@@ -3312,6 +3332,11 @@ var
|
|||||||
begin
|
begin
|
||||||
debug.tracer.push_trace('driver.storage.vfs.auto_mount_volumes.enter');
|
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();
|
volCount := driver.storage.vol.mgr.get_volume_count();
|
||||||
if volCount = 0 then begin
|
if volCount = 0 then begin
|
||||||
debug.tracer.push_trace('driver.storage.vfs.auto_mount_volumes.exit');
|
debug.tracer.push_trace('driver.storage.vfs.auto_mount_volumes.exit');
|
||||||
@@ -3658,7 +3683,25 @@ begin
|
|||||||
obj := GetObjectFromPath('/disk/__RENAMED_TEST__');
|
obj := GetObjectFromPath('/disk/__RENAMED_TEST__');
|
||||||
Assert((obj <> nil) and (obj^.Reference = void(mountVolA)),'renamed mount points to same volume');
|
Assert((obj <> nil) and (obj^.Reference = void(mountVolA)),'renamed mount points to same volume');
|
||||||
InvalidateVolume(mountVolA); InvalidateVolume(mountVolB);
|
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;
|
watchFired := 0;
|
||||||
io.syslog.logln('VFS', 'Unit tests starting...');
|
io.syslog.logln('VFS', 'Unit tests starting...');
|
||||||
|
|||||||
@@ -62,24 +62,47 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
function countFreeFATClusters(volume : PStorage_Volume; br : PBootRecord) : uint32;
|
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
|
begin
|
||||||
countFreeFATClusters := $FFFFFFFF;
|
countFreeFATClusters := $FFFFFFFF;
|
||||||
limit := FAT32ClusterLimit(br);
|
limit := FAT32ClusterLimit(br);
|
||||||
if limit <= 2 then exit;
|
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;
|
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;
|
count := 0; sectorIdx := 0;
|
||||||
while sectorIdx * 128 < limit do begin
|
sectors := (limit + 127) div 128;
|
||||||
if storage_read(volume^.device, volume^.sectorStart + br^.rsvSectors + sectorIdx, 1, buffer) <> eNone then begin
|
{ 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;
|
kfree(buffer); exit;
|
||||||
end;
|
end;
|
||||||
for entryIdx := 0 to 127 do begin
|
for entryIdx := 0 to batch * 128 - 1 do begin
|
||||||
clusterNum := sectorIdx * 128 + entryIdx;
|
clusterNum := sectorIdx * 128 + entryIdx;
|
||||||
if clusterNum >= limit then break;
|
if clusterNum >= limit then break;
|
||||||
if (clusterNum >= 2) and ((buffer[entryIdx] and $0FFFFFFF) = 0) then Inc(count);
|
if (clusterNum >= 2) and ((buffer[entryIdx] and $0FFFFFFF) = 0) then Inc(count);
|
||||||
end;
|
end;
|
||||||
Inc(sectorIdx);
|
Inc(sectorIdx, batch);
|
||||||
end;
|
end;
|
||||||
kfree(buffer);
|
kfree(buffer);
|
||||||
countFreeFATClusters := count;
|
countFreeFATClusters := count;
|
||||||
|
|||||||
Reference in new issue
Block a user