From d1ecd3fc41718dd1b79abdbd96727e25821f217b Mon Sep 17 00:00:00 2001 From: Aaron Date: Sat, 31 Oct 2026 16:30:40 +0000 Subject: [PATCH] fix(storage): harden disk operations, add volume labels, and stabilize AHCI discovery --- src/app/app.diskutil.pas | 500 +++++++---- src/app/app.partcmd.pas | 25 +- src/arch/x86/system.pas | 13 + .../ctl/ahci/driver.storage.ctl.ahci.pas | 281 +++---- .../storage/driver.storage.metatest.pas | 204 +++++ src/driver/storage/driver.storage.mgr.pas | 112 ++- src/driver/storage/driver.storage.types.pas | 15 +- src/driver/storage/driver.storage.vfs.pas | 30 +- .../storage/fs/driver.storage.fs.flatfs.pas | 129 ++- .../storage/fs/driver.storage.fs.iso9660.pas | 11 + .../fs/fat32/driver.storage.fs.fat32.core.pas | 18 +- .../fat32/driver.storage.fs.fat32.labels.pas | 205 +++++ .../fs/fat32/driver.storage.fs.fat32.pas | 8 +- .../fat32/driver.storage.fs.fat32.types.pas | 22 + .../fs/fat32/driver.storage.fs.fat32.vol.pas | 690 ++++----------- .../storage/vol/driver.storage.vol.mbr.pas | 4 +- .../storage/vol/driver.storage.vol.mgr.pas | 785 ++++++++---------- src/test/test.pas | 4 + 18 files changed, 1563 insertions(+), 1493 deletions(-) create mode 100644 src/driver/storage/driver.storage.metatest.pas create mode 100644 src/driver/storage/fs/fat32/driver.storage.fs.fat32.labels.pas diff --git a/src/app/app.diskutil.pas b/src/app/app.diskutil.pas index 4a96e032..719c887b 100644 --- a/src/app/app.diskutil.pas +++ b/src/app/app.diskutil.pas @@ -14,6 +14,7 @@ unit app.diskutil; interface procedure init(); +procedure UnitTest; implementation @@ -63,6 +64,7 @@ var fmt_cluster_input : Plv_obj; fmt_filecount_input: Plv_obj; fmt_label_input : Plv_obj; + label_target_id, label_target_index : uint32; fmt_param_error_lbl: Plv_obj; add_textarea : Plv_obj; add_err_lbl : Plv_obj; @@ -75,12 +77,15 @@ var storage_dirty_flag : uint32; storage_watch_id : uint32; ui_session_id : uint32; + operation_pending : boolean; + done_kind, done_device : uint32; type PFmtUICallbackCtx = ^TFmtUICallbackCtx; TFmtUICallbackCtx = record SessionID : uint32; VolumeIdx : uint32; + Kind, DeviceIdx : uint32; end; { ============================================================ @@ -100,41 +105,84 @@ procedure rebuild_fmt_param_ui(fs : PFilesystem); forward; { ============================================================ Helpers — size formatting ============================================================ } -function formatSizeStr(bytes: uint32): pchar; +{ All transient text belongs to gfxd and is released on its next UI tick. + LVGL copies labels/options; no widget retains these pointers. A header in the + same allocation handles nested concatenations without leaking intermediates. } +type + PUIText = ^TUIText; + TUIText = record Next : PUIText; end; +var TextTemps : PUIText = nil; + +procedure releaseTextTemps; +var item : PUIText; begin - if bytes >= 1073741824 then - formatSizeStr := stringConcat(intToString(bytes div 1073741824), ' GB') - else if bytes >= 1048576 then - formatSizeStr := stringConcat(intToString(bytes div 1048576), ' MB') - else if bytes >= 1024 then - formatSizeStr := stringConcat(intToString(bytes div 1024), ' KB') - else - formatSizeStr := stringConcat(intToString(bytes), ' B'); + while TextTemps <> nil do begin + item := TextTemps; TextTemps := item^.Next; kfree(void(item)); + end; end; -function formatSizePrecise(bytes: uint32): pchar; +function uiConcat(a, b : pchar) : pchar; +var item : PUIText; n, m : uint32; dest : pchar; +begin + uiConcat := ''; + n := stringSize(a); m := stringSize(b); + item := PUIText(kalloc(sizeof(TUIText)+n+m+1)); + if item = nil then exit; + item^.Next := TextTemps; TextTemps := item; + dest := pchar(uint32(item)+sizeof(TUIText)); + if n > 0 then memcpy(uint32(a), uint32(dest), n); + if m > 0 then memcpy(uint32(b), uint32(dest)+n, m); + dest[n+m] := #0; uiConcat := dest; +end; + +function uiInt(value : uint64) : pchar; +var digits : array[0..20] of char; pos : uint32; +begin + pos := 20; digits[pos] := #0; + repeat + Dec(pos); digits[pos] := char(ord('0') + value mod 10); value := value div 10; + until value = 0; + uiInt := uiConcat(@digits[pos], ''); +end; + +procedure operation_done(error : TError; userdata : pointer); forward; +function begin_partition_ui(kind : uint32) : PFmtUICallbackCtx; forward; + +function formatSizeStr(bytes: uint64): pchar; +begin + if bytes >= 1073741824 then + formatSizeStr := uiConcat(uiInt(bytes div 1073741824), ' GB') + else if bytes >= 1048576 then + formatSizeStr := uiConcat(uiInt(bytes div 1048576), ' MB') + else if bytes >= 1024 then + formatSizeStr := uiConcat(uiInt(bytes div 1024), ' KB') + else + formatSizeStr := uiConcat(uiInt(bytes), ' B'); +end; + +function formatSizePrecise(bytes: uint64): pchar; var - whole, frac : uint32; + whole, frac : uint64; begin if bytes >= 1073741824 then begin whole := bytes div 1073741824; frac := ((bytes mod 1073741824) * 10) div 1073741824; if frac > 0 then - formatSizePrecise := stringConcat(stringConcat(intToString(whole), stringConcat('.', intToString(frac))), ' GB') + formatSizePrecise := uiConcat(uiConcat(uiInt(whole), uiConcat('.', uiInt(frac))), ' GB') else - formatSizePrecise := stringConcat(intToString(whole), ' GB'); + formatSizePrecise := uiConcat(uiInt(whole), ' GB'); end else if bytes >= 1048576 then begin whole := bytes div 1048576; frac := ((bytes mod 1048576) * 10) div 1048576; if frac > 0 then - formatSizePrecise := stringConcat(stringConcat(intToString(whole), stringConcat('.', intToString(frac))), ' MB') + formatSizePrecise := uiConcat(uiConcat(uiInt(whole), uiConcat('.', uiInt(frac))), ' MB') else - formatSizePrecise := stringConcat(intToString(whole), ' MB'); + formatSizePrecise := uiConcat(uiInt(whole), ' MB'); end else if bytes >= 1024 then begin whole := bytes div 1024; - formatSizePrecise := stringConcat(intToString(whole), ' KB'); + formatSizePrecise := uiConcat(uiInt(whole), ' KB'); end else - formatSizePrecise := stringConcat(intToString(bytes), ' B'); + formatSizePrecise := uiConcat(uiInt(bytes), ' B'); end; function controllerStr(ct: TControllerType): pchar; @@ -316,7 +364,7 @@ begin end; { Add a usage bar (colored bar showing used vs total) } -procedure addUsageBar(parent: Plv_obj; row_w: sint32; used, total: uint32); +procedure addUsageBar(parent: Plv_obj; row_w: sint32; used, total: uint64); var bar : Plv_obj; pct : sint32; @@ -352,75 +400,32 @@ end; Returns 0 on invalid input. ============================================================ } function parseSize(s: pchar; sectorSize: uint32): uint32; -var - len : uint32; - last : char; - prev : char; - numEnd : uint32; - value : uint32; - bytes : uint32; +var i, digit : uint32; value, limit, multiplier, bytes : uint64; begin parseSize := 0; - if s = nil then exit; - len := stringSize(s); - if len = 0 then exit; - - last := s[len - 1]; - - if (last = 'B') or (last = 'b') then begin - if len >= 3 then - prev := s[len - 2] - else - prev := #0; - - if (prev = 'G') or (prev = 'g') then begin - numEnd := len - 2; - s[numEnd] := #0; - if not isNumericStr(s) then begin s[numEnd] := prev; exit; end; - value := stringToInt(s); - s[numEnd] := prev; - if value = 0 then exit; - bytes := value * 1073741824; - end else if (prev = 'M') or (prev = 'm') then begin - numEnd := len - 2; - s[numEnd] := #0; - if not isNumericStr(s) then begin s[numEnd] := prev; exit; end; - value := stringToInt(s); - s[numEnd] := prev; - if value = 0 then exit; - bytes := value * 1048576; - end else if (prev = 'K') or (prev = 'k') then begin - numEnd := len - 2; - s[numEnd] := #0; - if not isNumericStr(s) then begin s[numEnd] := prev; exit; end; - value := stringToInt(s); - s[numEnd] := prev; - if value = 0 then exit; - bytes := value * 1024; - end else begin - { Just 'B' suffix — plain bytes } - numEnd := len - 1; - s[numEnd] := #0; - if not isNumericStr(s) then begin s[numEnd] := last; exit; end; - value := stringToInt(s); - s[numEnd] := last; - if value = 0 then exit; - bytes := value; - end; - end else begin - { Plain number (bytes) } - if not isNumericStr(s) then exit; - bytes := stringToInt(s); - if bytes = 0 then exit; + if (s = nil) or (sectorSize = 0) then exit; + limit := uint64($FFFFFFFF) * sectorSize; + i := 0; value := 0; + while (s[i] >= '0') and (s[i] <= '9') do begin + digit := ord(s[i])-ord('0'); + if value > (limit-digit) div 10 then exit; + value := value*10+digit; Inc(i); end; - - if sectorSize = 0 then exit; - parseSize := bytes div sectorSize; + if (i = 0) or (value = 0) then exit; + multiplier := 1; + case s[i] of + 'K','k': begin multiplier := 1024; Inc(i); end; + 'M','m': begin multiplier := 1048576; Inc(i); end; + 'G','g': begin multiplier := 1073741824; Inc(i); end; + end; + if (s[i] = 'B') or (s[i] = 'b') then Inc(i); + if s[i] <> #0 then exit; + if value > limit div multiplier then exit; + bytes := value*multiplier; + if (bytes mod sectorSize) <> 0 then exit; + parseSize := uint32(bytes div sectorSize); end; -{ ============================================================ - Close any active message box - ============================================================ } procedure closeMsgBox; begin if active_mbox <> nil then begin @@ -433,6 +438,7 @@ begin fmt_param_area := nil; fmt_cluster_input := nil; fmt_filecount_input := nil; + fmt_label_input := nil; fmt_param_error_lbl := nil; end; @@ -452,10 +458,11 @@ var lbl : Plv_obj; begin if fmt_param_area = nil then exit; - lv_obj_clean(fmt_param_area); fmt_cluster_input := nil; fmt_filecount_input := nil; + fmt_label_input := nil; fmt_param_error_lbl := nil; + lv_obj_clean(fmt_param_area); if fs = nil then exit; if fs^.formatParamFlags = 0 then begin @@ -484,6 +491,15 @@ begin lv_textarea_set_placeholder_text(fmt_filecount_input, '1000'); lv_obj_set_width(fmt_filecount_input, 220); end; + if (fs^.formatParamFlags and FS_FORMAT_PARAM_LABEL) <> 0 then begin + lbl := lv_label_create(fmt_param_area); + lv_label_set_text(lbl, 'Volume label (up to 11 ASCII characters)'); + fmt_label_input := lv_textarea_create(fmt_param_area); + lv_textarea_set_one_line(fmt_label_input,true); + lv_textarea_set_max_length(fmt_label_input,11); + lv_textarea_set_placeholder_text(fmt_label_input,'Optional'); + lv_obj_set_width(fmt_label_input,260); + end; end; procedure fmt_fs_change_cb(e: Plv_event); cdecl; @@ -514,6 +530,7 @@ var part : TPartition_table; mbr_rec : PMaster_Boot_Record; needsInit : boolean; + cbCtx : PFmtUICallbackCtx; begin code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; @@ -568,8 +585,7 @@ begin { If disk needs init, do it now (async write, but cache updated synchronously) } if needsInit then begin - driver.storage.vol.mgr.init_disk_async(device, nil, nil); - { Cache is now fresh mbr with $AA55 signature, all slots empty } + { Blank MBR and first partition are committed in one worker write. } slot := 0; lba := 1; end else begin @@ -592,11 +608,9 @@ begin memset(uint32(@part), 0, sizeof(TPartition_table)); driver.storage.vol.mbr.setup_partition(@part, lba, sectors); - driver.storage.vol.mgr.add_partition_async(device, uint32(slot), part, nil, nil); - - closeMsgBox; - refreshSidebar; - showDeviceDetail(sel_dev_idx); + cbCtx := begin_partition_ui(1); + if cbCtx = nil then exit; + driver.storage.vol.mgr.add_partition_async(device, uint32(slot), part, @operation_done, cbCtx); end; procedure add_part_cancel_cb(e: Plv_event); cdecl; @@ -611,22 +625,16 @@ end; Dialog callbacks — Delete Partition ============================================================ } procedure del_part_ok_cb(e: Plv_event); cdecl; -var - code : uint32; - device : PStorage_Device; +var device : PStorage_Device; cbCtx : PFmtUICallbackCtx; slot : uint32; begin - code := lv_event_get_code(e); - if code <> LV_EVENT_CLICKED then exit; - + if lv_event_get_code(e) <> LV_EVENT_CLICKED then exit; + if operation_pending then exit; device := driver.storage.mgr.get_device(sel_dev_idx); - if device = nil then begin closeMsgBox; exit; end; - - if (pending_del_slot >= 0) and (pending_del_slot <= 3) then - driver.storage.vol.mgr.remove_partition_async(device, uint32(pending_del_slot), nil, nil); - - closeMsgBox; - refreshSidebar; - showDeviceDetail(sel_dev_idx); + if (device = nil) or (pending_del_slot < 0) or (pending_del_slot > 3) then exit; + slot := uint32(pending_del_slot); + cbCtx := begin_partition_ui(2); + if cbCtx = nil then exit; + driver.storage.vol.mgr.remove_partition_async(device, slot, @operation_done, cbCtx); end; procedure del_part_cancel_cb(e: Plv_event); cdecl; @@ -656,7 +664,7 @@ begin slot := uint32(lv_event_get_user_data(e)); pending_del_slot := sint32(slot); - txt := stringConcat('Delete partition in slot ', intToString(slot)); + txt := uiConcat('Delete partition in slot ', uiInt(slot)); device := driver.storage.mgr.get_device(sel_dev_idx); if device <> nil then begin @@ -664,13 +672,13 @@ begin if mbr_rec <> nil then begin part := mbr_rec^.partition[slot]; if part.sector_count > 0 then begin - txt := stringConcat(txt, ' ('); - txt := stringConcat(txt, formatSizeStr(part.sector_count * device^.sectorSize)); - txt := stringConcat(txt, ')'); + txt := uiConcat(txt, ' ('); + txt := uiConcat(txt, formatSizeStr(uint64(part.sector_count) * device^.sectorSize)); + txt := uiConcat(txt, ')'); end; end; end; - txt := stringConcat(txt, '?'); + txt := uiConcat(txt, '?'); closeMsgBox; mbox := lv_msgbox_create(lv_layer_top); @@ -691,17 +699,21 @@ end; { Async completion callback — fired from ISR context when format finishes. Only sets completion flags; LVGL work happens later on the UI poll timer. } -procedure fmt_done_cb(error : TError; userdata : pointer); +procedure operation_done(error : TError; userdata : pointer); var ctx : PFmtUICallbackCtx; + flags : uint32; begin ctx := PFmtUICallbackCtx(userdata); if ctx <> nil then begin + asm pushfd; cli; pop flags end; if ctx^.SessionID = ui_session_id then begin fmt_done_error := error; fmt_done_vol_idx := ctx^.VolumeIdx; + done_kind := ctx^.Kind; done_device := ctx^.DeviceIdx; puint32(@fmt_done_flag)^ := 1; end; + if (flags and $200) <> 0 then asm sti end; kfree(void(ctx)); end; end; @@ -715,6 +727,7 @@ procedure fmt_poll_cb(tmr: Plv_timer); cdecl; var mbox : Plv_obj; begin + releaseTextTemps; if puint32(@storage_dirty_flag)^ <> 0 then begin puint32(@storage_dirty_flag)^ := 0; refreshSidebar; @@ -736,24 +749,49 @@ begin closeMsgBox; refreshSidebar; - showVolumeDetail(fmt_done_vol_idx); + operation_pending := false; + if done_kind = 0 then showVolumeDetail(fmt_done_vol_idx) + else showDeviceDetail(done_device); if fmt_done_error <> eNone then begin mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; - lv_msgbox_add_title(mbox, 'Format Failed'); - lv_msgbox_add_text(mbox, 'The volume was formatted but could not be verified.'); + lv_msgbox_add_title(mbox, 'Storage Operation Failed'); + lv_msgbox_add_text(mbox, 'The operation did not complete. Check the error before retrying.'); lv_msgbox_add_text(mbox, formatErrorName(fmt_done_error)); - lv_msgbox_add_footer_button(mbox, 'OK'); + lv_obj_add_event_cb(lv_msgbox_add_footer_button(mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); end else begin mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; - lv_msgbox_add_title(mbox, 'Format Complete'); - lv_msgbox_add_text(mbox, 'The volume is ready.'); - lv_msgbox_add_footer_button(mbox, 'OK'); + lv_msgbox_add_title(mbox, 'Storage Operation Complete'); + lv_msgbox_add_text(mbox, 'The requested changes have been written successfully.'); + lv_obj_add_event_cb(lv_msgbox_add_footer_button(mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); end; end; +function begin_partition_ui(kind : uint32) : PFmtUICallbackCtx; +var ctx : PFmtUICallbackCtx; +begin + begin_partition_ui := nil; + if operation_pending then exit; + ctx := PFmtUICallbackCtx(kalloc(sizeof(TFmtUICallbackCtx))); + if ctx = nil then begin + closeMsgBox; + active_mbox := lv_msgbox_create(lv_layer_top); + lv_msgbox_add_title(active_mbox, 'Out of memory'); + lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); + exit; + end; + ctx^.SessionID := ui_session_id; ctx^.VolumeIdx := sel_vol_idx; + ctx^.Kind := kind; ctx^.DeviceIdx := sel_dev_idx; + closeMsgBox; + active_mbox := lv_msgbox_create(lv_layer_top); + lv_msgbox_add_title(active_mbox, 'Updating partition table'); + lv_msgbox_add_text(active_mbox, 'The operation is running in the background.'); + operation_pending := true; + begin_partition_ui := ctx; +end; + procedure fmt_ok_cb(e: Plv_event); cdecl; var code : uint32; @@ -765,7 +803,9 @@ var paramPtr : PFSFormatParams; text : pchar; value : uint32; + normalized : TVolumeLabel; begin + if operation_pending then exit; code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; @@ -776,7 +816,7 @@ begin active_mbox := lv_msgbox_create(lv_layer_top); lv_msgbox_add_title(active_mbox, 'Format Busy'); lv_msgbox_add_text(active_mbox, 'This volume is busy. Wait for the current operation to finish.'); - lv_msgbox_add_footer_button(active_mbox, 'OK'); + lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); exit; end; @@ -792,7 +832,7 @@ begin if fmt_cluster_input <> nil then begin text := lv_textarea_get_text(fmt_cluster_input); if (text <> nil) and (text^ <> #0) then begin - if not isNumericStr(text) then begin + if (stringSize(text) > 5) or (not isNumericStr(text)) then begin set_fmt_param_error('Cluster size must be a number.'); exit; end; @@ -812,7 +852,7 @@ begin if fmt_filecount_input <> nil then begin text := lv_textarea_get_text(fmt_filecount_input); if (text <> nil) and (text^ <> #0) then begin - if not isNumericStr(text) then begin + if (stringSize(text) > 5) or (not isNumericStr(text)) then begin set_fmt_param_error('File count must be a number.'); exit; end; @@ -828,23 +868,40 @@ begin end; end; + if (fmt_label_input <> nil) and ((fs^.formatParamFlags and FS_FORMAT_PARAM_LABEL) <> 0) then begin + text := lv_textarea_get_text(fmt_label_input); + if (fs^.validateLabelCallback = nil) then exit; + if fs^.validateLabelCallback(text,normalized) <> eNone then begin + set_fmt_param_error('Use up to 11 ASCII characters without punctuation such as / or :'); exit; + end; + memcpy(uint32(@normalized[0]),uint32(@params.LabelText[0]),12); + params.Flags := params.Flags or FS_FORMAT_PARAM_LABEL; paramPtr := @params; + end; + closeMsgBox; cbCtx := PFmtUICallbackCtx(kalloc(sizeof(TFmtUICallbackCtx))); - if cbCtx = nil then exit; + if cbCtx = nil then begin + active_mbox := lv_msgbox_create(lv_layer_top); + lv_msgbox_add_title(active_mbox, 'Out of memory'); + lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); + exit; + end; cbCtx^.SessionID := ui_session_id; cbCtx^.VolumeIdx := sel_vol_idx; + cbCtx^.Kind := 0; cbCtx^.DeviceIdx := sel_dev_idx; { Submit async format — returns immediately, fmt_done_cb only flips a flag. } if not driver.storage.vol.mgr.format_volume_async(vol^.device, sel_vol_idx, fs^.sName, paramPtr, - @fmt_done_cb, cbCtx) then begin + @operation_done, cbCtx) then begin kfree(void(cbCtx)); closeMsgBox; active_mbox := lv_msgbox_create(lv_layer_top); lv_msgbox_add_title(active_mbox, 'Format Failed'); - lv_msgbox_add_text(active_mbox, 'Could not start the format operation.'); - lv_msgbox_add_footer_button(active_mbox, 'OK'); + lv_msgbox_add_text(active_mbox, 'Cannot start: device busy, unsupported geometry/options, or insufficient memory.'); + lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); end else begin + operation_pending := true; active_mbox := lv_msgbox_create(lv_layer_top); lv_msgbox_add_title(active_mbox, 'Formatting'); lv_msgbox_add_text(active_mbox, 'Formatting is running in the background.'); @@ -936,9 +993,9 @@ begin active_mbox := mbox; lv_msgbox_add_title(mbox, 'Add Partition'); - infoTxt := stringConcat('Free space: ', formatSizePrecise(freeSec * device^.sectorSize)); - infoTxt := stringConcat(infoTxt, ' | Free slots: '); - infoTxt := stringConcat(infoTxt, intToString(freeSlotCnt)); + infoTxt := uiConcat('Free space: ', formatSizePrecise(uint64(freeSec) * device^.sectorSize)); + infoTxt := uiConcat(infoTxt, ' | Free slots: '); + infoTxt := uiConcat(infoTxt, uiInt(freeSlotCnt)); lv_msgbox_add_text(mbox, infoTxt); lv_msgbox_add_text(mbox, 'Enter size (e.g. 100MB, 2GB) or leave blank for all free space:'); @@ -946,7 +1003,7 @@ begin body := lv_msgbox_get_content(mbox); add_textarea := lv_textarea_create(body); lv_textarea_set_one_line(add_textarea, true); - lv_textarea_set_placeholder_text(add_textarea, formatSizeStr(freeSec * device^.sectorSize)); + lv_textarea_set_placeholder_text(add_textarea, formatSizeStr(uint64(freeSec) * device^.sectorSize)); lv_obj_set_width(add_textarea, 280); add_err_lbl := nil; @@ -1016,7 +1073,7 @@ begin body := lv_msgbox_get_content(mbox); addStatusLabel(body, - stringConcat('Volume size: ', formatSizePrecise(vol^.sectorCount * vol^.sectorSize)), + uiConcat('Volume size: ', formatSizePrecise(uint64(vol^.sectorCount) * vol^.sectorSize)), 160, 170, 190); { Build newline-separated options for dropdown } @@ -1027,8 +1084,8 @@ begin if opts = nil then opts := fs^.sName else begin - opts := stringConcat(opts, #10); - opts := stringConcat(opts, fs^.sName); + opts := uiConcat(opts, #10); + opts := uiConcat(opts, fs^.sName); end; end; end; @@ -1064,7 +1121,6 @@ begin if detail <> nil then lv_obj_clean(detail); sel_mode := SEL_NONE; - pending_del_slot := -1; end; { Find the volume that corresponds to a partition on a device } @@ -1076,7 +1132,7 @@ var begin findVolumeForPartition := nil; volCnt := driver.storage.vol.mgr.get_volume_count(); - 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); if vol = nil then continue; if (vol^.device = device) and (vol^.sectorStart = lbaStart) then begin @@ -1100,8 +1156,8 @@ var sizeStr : pchar; vol : PStorage_Volume; freeStr : pchar; - totalB : uint32; - freeB : uint32; + totalB : uint64; + freeB : uint64; begin row := lv_obj_create(parent); lv_obj_remove_style_all(row); @@ -1119,20 +1175,20 @@ begin { Slot number badge } lbl := lv_label_create(row); - lv_label_set_text(lbl, intToString(slot)); + lv_label_set_text(lbl, uiInt(slot)); lv_obj_set_style_text_color(lbl, lv_color_make(100, 160, 255), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 20); { System ID } lbl := lv_label_create(row); - lv_label_set_text(lbl, stringConcat('ID:', intToString(part.system_id))); + lv_label_set_text(lbl, uiConcat('ID:', uiInt(part.system_id))); lv_obj_set_style_text_color(lbl, lv_color_make(160, 170, 190), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 55); { Size } - totalB := part.sector_count * sectorSize; + totalB := uint64(part.sector_count) * sectorSize; sizeStr := formatSizeStr(totalB); lbl := lv_label_create(row); lv_label_set_text(lbl, sizeStr); @@ -1143,8 +1199,8 @@ begin { Free space — look up the corresponding volume } vol := findVolumeForPartition(device, part.LBA_start); if vol <> nil then begin - freeB := vol^.freeSectors * vol^.sectorSize; - freeStr := stringConcat(formatSizeStr(freeB), ' free'); + freeB := uint64(vol^.freeSectors) * vol^.sectorSize; + 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); @@ -1183,9 +1239,9 @@ var btn : Plv_obj; dw : sint32; i : uint32; - totalBytes : uint32; - usedSectors : uint32; - freeBytes : uint32; + totalBytes : uint64; + usedSectors : uint64; + freeBytes : uint64; volCnt : uint32; devVolCnt : uint32; vol : PStorage_Volume; @@ -1194,8 +1250,8 @@ var lbl : Plv_obj; sizeStr : pchar; freeStr : pchar; - totalB : uint32; - freeB : uint32; + totalB : uint64; + freeB : uint64; begin clearDetail; sel_mode := SEL_DEVICE; @@ -1211,18 +1267,18 @@ begin { ---- Device Info ---- } addSectionHeader(detail, 'Device Info'); - addInfoRow(detail, dw, 'Device Index', intToString(devIdx)); + addInfoRow(detail, dw, 'Device Index', uiInt(devIdx)); addInfoRow(detail, dw, 'Controller', controllerStr(device^.controller)); addInfoRow(detail, dw, 'Writable', boolStr(device^.writable)); - addInfoRow(detail, dw, 'Sector Size', stringConcat(intToString(device^.sectorSize), ' bytes')); - totalBytes := device^.maxSectorCount * device^.sectorSize; + addInfoRow(detail, dw, 'Sector Size', uiConcat(uiInt(device^.sectorSize), ' bytes')); + 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; devVolCnt := 0; volCnt := driver.storage.vol.mgr.get_volume_count(); - 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); if (vol <> nil) and (vol^.device = device) then begin devVolCnt := devVolCnt + 1; @@ -1230,11 +1286,11 @@ begin end; end; if device^.maxSectorCount > usedSectors then - freeBytes := (device^.maxSectorCount - usedSectors) * device^.sectorSize + freeBytes := uint64(device^.maxSectorCount - usedSectors) * device^.sectorSize else freeBytes := 0; addInfoRow(detail, dw, 'Free Space', formatSizePrecise(freeBytes)); - addInfoRow(detail, dw, 'Volumes', intToString(devVolCnt)); + addInfoRow(detail, dw, 'Volumes', uiInt(devVolCnt)); { Usage bar } if totalBytes > 0 then @@ -1248,7 +1304,7 @@ begin if devVolCnt = 0 then addStatusLabel(detail, 'No volumes found.', 120, 125, 145) else begin - 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); if (vol = nil) or (vol^.device <> device) then continue; @@ -1272,20 +1328,21 @@ begin else fsName := 'Unknown'; lbl := lv_label_create(row); - lv_label_set_text(lbl, fsName); + if vol^.labelText[0] <> #0 then lv_label_set_text(lbl,@vol^.labelText[0]) + else lv_label_set_text(lbl, fsName); lv_obj_set_style_text_color(lbl, lv_color_make(100, 160, 255), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 70); { LBA start } lbl := lv_label_create(row); - lv_label_set_text(lbl, stringConcat('LBA:', intToString(vol^.sectorStart))); + lv_label_set_text(lbl, uiConcat('LBA:', uiInt(vol^.sectorStart))); lv_obj_set_style_text_color(lbl, lv_color_make(160, 170, 190), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 80); { Size } - totalB := vol^.sectorCount * vol^.sectorSize; + totalB := uint64(vol^.sectorCount) * vol^.sectorSize; sizeStr := formatSizeStr(totalB); lbl := lv_label_create(row); lv_label_set_text(lbl, sizeStr); @@ -1294,8 +1351,8 @@ begin lv_obj_set_width(lbl, 70); { Free space } - freeB := vol^.freeSectors * vol^.sectorSize; - freeStr := stringConcat(formatSizeStr(freeB), ' free'); + freeB := uint64(vol^.freeSectors) * vol^.sectorSize; + 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); @@ -1327,13 +1384,65 @@ end; { ============================================================ Show volume detail — volume info + format action ============================================================ } +procedure label_save_cb(e : Plv_event); cdecl; +var vol : PStorage_Volume; ctx : PFmtUICallbackCtx; value : TVolumeLabel; err : TError; +begin + if (lv_event_get_code(e) <> LV_EVENT_CLICKED) or operation_pending then exit; + vol := driver.storage.vol.mgr.get_volume(label_target_index); + if (vol = nil) or (vol^.id <> label_target_id) or (vol^.filesystem = nil) then begin + lv_label_set_text(add_err_lbl,'The selected volume is no longer available.'); exit; + end; + if vol^.filesystem^.validateLabelCallback = nil then exit; + err := vol^.filesystem^.validateLabelCallback(lv_textarea_get_text(fmt_label_input),value); + if err <> eNone then begin + lv_label_set_text(add_err_lbl,'Invalid label. Use ASCII letters, digits, spaces, _ or -.'); exit; + end; + ctx := PFmtUICallbackCtx(kalloc(sizeof(TFmtUICallbackCtx))); + if ctx = nil then begin lv_label_set_text(add_err_lbl,'Out of memory.'); exit; end; + ctx^.SessionID := ui_session_id; ctx^.VolumeIdx := label_target_index; + ctx^.Kind := 0; ctx^.DeviceIdx := sel_dev_idx; + err := driver.storage.vol.mgr.set_volume_label_async(vol^.device,label_target_index,@value[0],@operation_done,ctx); + if err <> eNone then begin + kfree(void(ctx)); lv_label_set_text(add_err_lbl,formatErrorName(err)); exit; + end; + operation_pending := true; + closeMsgBox; + active_mbox := lv_msgbox_create(lv_layer_top); + lv_msgbox_add_title(active_mbox,'Saving volume label'); + lv_msgbox_add_text(active_mbox,'Updating the label on disk.'); +end; + +procedure label_edit_cb(e : Plv_event); cdecl; +var vol : PStorage_Volume; content : Plv_obj; +begin + if (lv_event_get_code(e) <> LV_EVENT_CLICKED) or operation_pending then exit; + vol := driver.storage.vol.mgr.get_volume(sel_vol_idx); + if (vol = nil) or (vol^.filesystem = nil) then exit; + if vol^.filesystem^.setLabelCallback = nil then exit; + closeMsgBox; + label_target_id := vol^.id; label_target_index := sel_vol_idx; + active_mbox := lv_msgbox_create(lv_layer_top); + lv_msgbox_add_title(active_mbox,'Rename Volume'); + lv_msgbox_add_text(active_mbox,'Up to 11 ASCII characters, stored in uppercase. Leave blank to remove the label.'); + content := lv_msgbox_get_content(active_mbox); + fmt_label_input := lv_textarea_create(content); + lv_textarea_set_one_line(fmt_label_input,true); + lv_textarea_set_max_length(fmt_label_input,11); + lv_textarea_set_text(fmt_label_input,@vol^.labelText[0]); + lv_obj_set_width(fmt_label_input,300); + add_err_lbl := lv_label_create(content); lv_label_set_text(add_err_lbl,''); + lv_obj_set_style_text_color(add_err_lbl,lv_color_make(230,80,80),0); + lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox,'Save'),@label_save_cb,LV_EVENT_CLICKED,nil); + lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox,'Cancel'),@add_part_cancel_cb,LV_EVENT_CLICKED,nil); +end; + procedure showVolumeDetail(volIdx: uint32); var vol : PStorage_Volume; dw : sint32; - totalBytes : uint32; - freeBytes : uint32; - usedBytes : uint32; + totalBytes : uint64; + freeBytes : uint64; + usedBytes : uint64; fsName : pchar; btn_row : Plv_obj; btn : Plv_obj; @@ -1354,17 +1463,20 @@ begin dw := WIN_W - SIDEBAR_W - 20; addSectionHeader(detail, 'Volume Info'); - addInfoRow(detail, dw, 'Volume Index', intToString(volIdx)); + addInfoRow(detail, dw, 'Volume Index', uiInt(volIdx)); + if vol^.labelText[0] <> #0 then addInfoRow(detail,dw,'Label',@vol^.labelText[0]) + else addInfoRow(detail,dw,'Label','Unlabelled'); { Find parent device index } devIdx := 0; + if driver.storage.mgr.get_device_count() > 0 then for i := 0 to driver.storage.mgr.get_device_count() - 1 do begin if driver.storage.mgr.get_device(i) = vol^.device then begin devIdx := i; break; end; end; - addInfoRow(detail, dw, 'Parent Device', stringConcat('Disk ', intToString(devIdx))); + addInfoRow(detail, dw, 'Parent Device', uiConcat('Disk ', uiInt(devIdx))); if vol^.filesystem <> nil then fsName := vol^.filesystem^.sName @@ -1372,12 +1484,13 @@ begin fsName := 'Unknown'; addInfoRow(detail, dw, 'Filesystem', fsName); - addInfoRow(detail, dw, 'Sector Start', intToString(vol^.sectorStart)); - addInfoRow(detail, dw, 'Sector Count', intToString(vol^.sectorCount)); - addInfoRow(detail, dw, 'Sector Size', stringConcat(intToString(vol^.sectorSize), ' bytes')); + addInfoRow(detail, dw, 'Sector Start', uiInt(vol^.sectorStart)); + addInfoRow(detail, dw, 'Sector Count', uiInt(vol^.sectorCount)); + addInfoRow(detail, dw, 'Sector Size', uiConcat(uiInt(vol^.sectorSize), ' bytes')); - totalBytes := vol^.sectorCount * vol^.sectorSize; - freeBytes := vol^.freeSectors * vol^.sectorSize; + totalBytes := uint64(vol^.sectorCount) * vol^.sectorSize; + freeBytes := uint64(vol^.freeSectors) * vol^.sectorSize; + if freeBytes > totalBytes then freeBytes := totalBytes; usedBytes := totalBytes - freeBytes; addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes)); @@ -1386,7 +1499,7 @@ begin if totalBytes > 0 then begin pctUsed := (usedBytes * 100) div totalBytes; - addInfoRow(detail, dw, 'Usage', stringConcat(intToString(pctUsed), '%')); + addInfoRow(detail, dw, 'Usage', uiConcat(uiInt(pctUsed), '%')); addUsageBar(detail, dw, usedBytes, totalBytes); end; @@ -1410,6 +1523,10 @@ begin else begin btn := makeButton(btn_row, 'Format', 100); lv_obj_add_event_cb(btn, @btn_format_cb, LV_EVENT_CLICKED, nil); + if vol^.filesystem <> nil then if vol^.filesystem^.setLabelCallback <> nil then begin + btn := makeButton(btn_row,'Rename Volume',150); + lv_obj_add_event_cb(btn,@label_edit_cb,LV_EVENT_CLICKED,nil); + end; end; end else begin addStatusLabel(detail, 'Device is read-only. Format disabled.', 160, 150, 130); @@ -1470,8 +1587,8 @@ var sizeStr : pchar; begin if sidebar = nil then exit; - lv_obj_clean(sidebar); active_sidebar_btn := nil; + lv_obj_clean(sidebar); { Sidebar title } hdr := lv_label_create(sidebar); @@ -1480,12 +1597,12 @@ begin lv_obj_set_style_text_font(hdr, @lv_font_montserrat_14, 0); devCnt := driver.storage.mgr.get_device_count(); - for i := 0 to devCnt - 1 do begin + if devCnt > 0 then for i := 0 to devCnt - 1 do begin device := driver.storage.mgr.get_device(i); if device = nil then continue; { Section label for this device } - addSectionHeader(sidebar, stringConcat('Disk ', intToString(i))); + addSectionHeader(sidebar, uiConcat('Disk ', uiInt(i))); { Clickable device row } btn := lv_button_create(sidebar); @@ -1506,7 +1623,7 @@ begin lv_obj_set_style_text_color(lbl, lv_color_make(210, 215, 230), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); - sizeStr := formatSizeStr(device^.maxSectorCount * device^.sectorSize); + sizeStr := formatSizeStr(uint64(device^.maxSectorCount) * device^.sectorSize); lbl := lv_label_create(btn); lv_label_set_text(lbl, sizeStr); lv_obj_set_style_text_color(lbl, lv_color_make(140, 150, 170), 0); @@ -1520,7 +1637,7 @@ begin { List volumes that belong to this device } volCnt := driver.storage.vol.mgr.get_volume_count(); - for j := 0 to volCnt - 1 do begin + if volCnt > 0 then for j := 0 to volCnt - 1 do begin vol := driver.storage.vol.mgr.get_volume(j); if vol = nil then continue; if vol^.device <> device then continue; @@ -1539,14 +1656,16 @@ begin lv_obj_set_flex_align(btn, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lbl := lv_label_create(btn); - if vol^.filesystem <> nil then + if vol^.labelText[0] <> #0 then + lv_label_set_text(lbl,@vol^.labelText[0]) + else if vol^.filesystem <> nil then lv_label_set_text(lbl, vol^.filesystem^.sName) else lv_label_set_text(lbl, 'Volume'); lv_obj_set_style_text_color(lbl, lv_color_make(185, 195, 215), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); - sizeStr := formatSizeStr(vol^.sectorCount * vol^.sectorSize); + sizeStr := formatSizeStr(uint64(vol^.sectorCount) * vol^.sectorSize); lbl := lv_label_create(btn); lv_label_set_text(lbl, sizeStr); lv_obj_set_style_text_color(lbl, lv_color_make(120, 130, 150), 0); @@ -1585,6 +1704,8 @@ procedure onClose(wid: uint32); begin debug.tracer.push_trace('diskutil.onClose'); closeMsgBox; + releaseTextTemps; + operation_pending := false; ui_session_id := ui_session_id + 1; puint32(@fmt_done_flag)^ := 0; puint32(@storage_dirty_flag)^ := 0; @@ -1694,6 +1815,7 @@ begin lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); { Reset state } + operation_pending := false; sel_mode := SEL_NONE; sel_dev_idx := 0; sel_vol_idx := 0; @@ -1703,6 +1825,7 @@ begin fmt_param_area := nil; fmt_cluster_input := nil; fmt_filecount_input := nil; + fmt_label_input := nil; fmt_param_error_lbl := nil; add_textarea := nil; add_err_lbl := nil; @@ -1712,6 +1835,7 @@ begin fmt_done_vol_idx := 0; storage_dirty_flag := 0; fmt_poll_timer := lv_timer_create(@fmt_poll_cb, 50, nil); + if fmt_poll_timer = nil then begin onClose(win_id); exit; end; storage_watch_id := driver.storage.vol.mgr.watch_lifecycle(@diskutil_storage_event, nil); refreshSidebar; @@ -1729,6 +1853,31 @@ end; { ============================================================ Init — register with the driver.video.desktop program launcher ============================================================ } +procedure UnitTest; +var passed, failed, sectors, sectorSize : uint32; + procedure Assert(ok : boolean; name : pchar); + begin + if ok then Inc(passed) else begin Inc(failed); io.syslog.logln('DISKUTIL FAIL', name); end; + end; +begin + passed := 0; failed := 0; + sectors := 8388608; sectorSize := 512; + Assert(stringEquals(formatSizeStr(uint64(sectors)*sectorSize),'4 GB'),'4GB label'); + sectors := 10485760; + Assert(stringEquals(formatSizePrecise(uint64(sectors)*sectorSize),'5 GB'),'5GB label'); + releaseTextTemps; + Assert(TextTemps = nil,'temporary labels released'); + Assert(parseSize('4GB',512) = 8388608, '4GB does not wrap to zero'); + Assert(parseSize('5GB',512) = 10485760, '5GB does not wrap to 1GB'); + Assert(parseSize('999999999999999999999GB',512) = 0, 'reject decimal overflow'); + Assert(parseSize('2097152GB',512) = 0, 'reject LBA overflow'); + Assert(parseSize('513B',512) = 0, 'reject fractional sectors'); + Assert(parseSize('512B',0) = 0, 'reject zero sector size'); + Assert(parseSize('',512) = 0, 'reject empty size'); + io.syslog.log('DISKUTIL', 'Passed: '); io.syslog.writeint(passed); + io.syslog.writestring(' Failed: '); io.syslog.writeintln(failed); +end; + procedure init(); begin debug.tracer.push_trace('diskutil.init'); @@ -1745,6 +1894,7 @@ begin fmt_param_area := nil; fmt_cluster_input := nil; fmt_filecount_input := nil; + fmt_label_input := nil; fmt_param_error_lbl := nil; add_textarea := nil; add_err_lbl := nil; diff --git a/src/app/app.partcmd.pas b/src/app/app.partcmd.pas index cf25a86d..3364b07e 100644 --- a/src/app/app.partcmd.pas +++ b/src/app/app.partcmd.pas @@ -288,7 +288,9 @@ begin if bootrecord <> nil then begin if bootrecord^.boot_sector <> $AA55 then begin println('No valid mbr found, initialising disk...'); - driver.storage.vol.mgr.init_disk(device); + if driver.storage.vol.mgr.init_disk(device) <> eNone then begin + println('Error: disk initialization failed.'); exit; + end; end; kfree(puint32(bootrecord)); end; @@ -335,7 +337,9 @@ begin driver.storage.vol.mbr.setup_partition(@part, lba, sectors); part.system_id := 0; - driver.storage.vol.mgr.add_partition(device, slot, part); + if driver.storage.vol.mgr.add_partition(device, slot, part) <> eNone then begin + println('Error: partition creation failed.'); exit; + end; print('Partition added in slot '); printint(slot); @@ -407,7 +411,9 @@ begin exit; end; - driver.storage.vol.mgr.remove_partition(device, slot); + if driver.storage.vol.mgr.remove_partition(device, slot) <> eNone then begin + println('Error: partition removal failed.'); exit; + end; print('Partition slot '); printint(slot); @@ -523,11 +529,14 @@ begin exit; end; - vol^.filesystem := fs; - fs^.createCallback(vol, vol^.sectorCount, vol^.sectorStart, nil); - - { Re-probe filesystem so identify picks up the new signature } - driver.storage.fs.mgr.probe_volume(vol); + i := 0; + while i < driver.storage.vol.mgr.get_volume_count() do begin + if driver.storage.vol.mgr.get_volume(i) = vol then break; + Inc(i); + end; + if not driver.storage.vol.mgr.format_volume(device, i, fsName, nil) then begin + println('Error: format failed; the volume was not reported ready.'); exit; + end; print('Partition '); printint(slot); diff --git a/src/arch/x86/system.pas b/src/arch/x86/system.pas index b13b1038..d8d22ea4 100644 --- a/src/arch/x86/system.pas +++ b/src/arch/x86/system.pas @@ -271,6 +271,7 @@ procedure fpc_shortstr_assign(len: longint; sstr, dstr: Pointer); compilerproc; any int64/uint64 arithmetic occurs (e.g. sint32 * uint32 promotion). Must live in the system unit so the compiler can resolve it. } function fpc_mul_int64(f1, f2: int64): int64; compilerproc; +function fpc_mul_dword_to_qword(f1, f2: uint32): qword; compilerproc; function fpc_div_int64(d, n: int64): int64; compilerproc; function fpc_mod_int64(d, n: int64): int64; compilerproc; function fpc_mod_qword(d, n: qword): qword; compilerproc; @@ -523,6 +524,18 @@ begin fpc_mul_int64 := res; end; +function fpc_mul_dword_to_qword(f1, f2: uint32): qword; [public, alias: 'FPC_MUL_DWORD_TO_QWORD']; compilerproc; +var res: qword; +begin + asm + MOV EAX, f1 + MUL f2 + MOV DWORD [res], EAX + MOV DWORD [res+4], EDX + end; + fpc_mul_dword_to_qword := res; +end; + { ---------- 64-bit division / modulo ---------- } { Helper: unsigned 64÷64→64 using shift-subtract (binary long division). diff --git a/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.pas b/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.pas index 745d7c2f..83fc73fe 100644 --- a/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.pas +++ b/src/driver/storage/ctl/ahci/driver.storage.ctl.ahci.pas @@ -46,7 +46,7 @@ var procedure init(); function load(ptr : void) : boolean; procedure check_ports(controller : PAHCI_Controller); -procedure identify_device(controller : PAHCI_Controller; portIndex : uint32; isATAPI : boolean); +function identify_device(controller : PAHCI_Controller; portIndex : uint32; isATAPI : boolean) : boolean; procedure ahci_isr(); function find_cmd_slot(device : PAHCI_Device) : uint32; @@ -95,121 +95,63 @@ begin //TODO check driver.storage.ctl.ide devices in driver.storage.ctl.ide for sata devices end; -procedure stop_port(port : PHBA_Port); -var - timeout : uint32; +{ Poll MMIO through a pointer on every iteration. The PIT runs at ~8 kHz; + 16 psleep ticks give a conservative ~2 ms poll interval, not a CPU-speed loop. } +function wait_register(reg : puint32; mask, expected, attempts : uint32) : boolean; +var n : uint32; begin - // Clear ST (bit 0) first - port^.cmd := port^.cmd and not $1; - - // Wait for CR (bit 15) to clear - timeout := 0; - while (port^.cmd and $8000) <> 0 do begin - timeout := timeout + 1; - if timeout > 100000 then break; - end; - - // Clear FRE (bit 4) - port^.cmd := port^.cmd and not $10; - - // Wait for FR (bit 14) to clear - timeout := 0; - while (port^.cmd and $4000) <> 0 do begin - timeout := timeout + 1; - if timeout > 100000 then break; - end; + n := 0; + repeat + if (reg^ and mask) = expected then begin wait_register := true; exit; end; + if n >= attempts then break; + psleep(16); Inc(n); + until false; + wait_register := false; end; -procedure start_port(port : PHBA_Port); +function stop_port(port : PHBA_Port) : boolean; begin - // Wait for CR (bit 15) to clear before starting - while (port^.cmd and $8000) <> 0 do begin - end; + port^.cmd := port^.cmd and not $1; + stop_port := wait_register(@port^.cmd,$8000,0,250); + if not stop_port then exit; + port^.cmd := port^.cmd and not $10; + stop_port := wait_register(@port^.cmd,$4000,0,250); +end; - // Enable FRE (bit 4) first so port can receive FIS +function start_port(port : PHBA_Port) : boolean; +begin + start_port := wait_register(@port^.cmd,$8000,0,250); + if not start_port then exit; port^.cmd := port^.cmd or $10; - - // Then enable ST (bit 0) to allow command processing port^.cmd := port^.cmd or $1; end; -procedure reset_port(port : PHBA_Port); -var - timeout : uint32; +{ DMA addresses must already be installed before enabling FIS reception. } +function reset_port(port : PHBA_Port) : boolean; begin - // io.syslog.writestringln('driver.storage.ctl.ahci: Performing a full port reset.'); - - // Stop the port: clear ST (bit 0) + reset_port := false; port^.cmd := port^.cmd and not $1; - - // Wait until CR (bit 15) is cleared. - timeout := 0; - while (port^.cmd and $8000) <> 0 do begin - timeout := timeout + 1; - if timeout > 100000 then begin - io.syslog.writestringln('driver.storage.ctl.ahci: Port reset timeout (CR not clearing).'); - break; - end; - end; - - // Initiate COMRESET: set DET=1 in PxSCTL (bits 3:0) + if not wait_register(@port^.cmd,$8000,0,250) then exit; + port^.int_enable := 0; + port^.cmd := port^.cmd or $16; { FRE, power-on and spin-up } port^.sata_ctrl := (port^.sata_ctrl and $FFFFFFF0) or $1; - - // Wait for COMRESET to propagate (spec says >= 1ms) - psleep(2); - - // Clear COMRESET: set DET=0 + psleep(16); { >= 1 ms COMRESET assertion at the ~8 kHz PIT rate } port^.sata_ctrl := port^.sata_ctrl and $FFFFFFF0; - - // Wait for device detection (DET field in SStatus = 3) - timeout := 0; - while (port^.sata_status and $F) <> 3 do begin - timeout := timeout + 1; - if timeout > 100000 then begin - io.syslog.writestringln('driver.storage.ctl.ahci: Port reset timeout (waiting for device ready).'); - break; - end; - end; - - // Clear the SERR register by writing 1s to it. + if not wait_register(@port^.sata_status,$F,3,500) then exit; + if not wait_register(@port^.tfd,$88,0,2500) then exit; port^.sata_error := $FFFFFFFF; - - start_port(port); + port^.int_status := $FFFFFFFF; + reset_port := start_port(port); end; -// procedure reset_port(port : PHBA_Port); -// var -// ssts, serr, timeout : uint32; -// begin -// io.syslog.writestringln('driver.storage.ctl.ahci: Performing a full port reset.'); - -// port^.cmd := port^.cmd and not $1; - -// // Wait until CR (bit 15) is cleared. -// timeout := 0; -// while (port^.cmd and $8000) <> 0 do begin -// timeout := timeout + 1; -// if timeout > 1000000 then begin -// io.syslog.writestringln('driver.storage.ctl.ahci: Port reset timeout.'); -// break; -// end; -// end; - -// port^.sata_ctrl := port^.sata_ctrl and $FFFF0000; -// port^.sata_ctrl := port^.sata_ctrl or $1; -// psleep(10); -// port^.sata_ctrl := port^.sata_ctrl and $FFFF0000; - -// while (port^.sata_status and $F) <> $3 do begin -// //wait for the port to be ready -// end; - -// // Clear the SERR register by writing 1s to it. -// port^.sata_error := $FFFFFFFF; - -// // start_port(port); -// end; - +procedure log_port_failure(portIndex : uint32; port : PHBA_Port; reason : pchar); +begin + io.syslog.log('AHCI','Port '); io.syslog.writeint(portIndex); + io.syslog.writestring(': '); io.syslog.writestring(reason); + io.syslog.writestring(' SSTS='); io.syslog.writehex(port^.sata_status); + io.syslog.writestring(' SIG='); io.syslog.writehex(port^.signature); + io.syslog.writestring(' TFD='); io.syslog.writehexln(port^.tfd); +end; { Check the ports on the controller and setup the command list, FIS, and command table @@ -217,7 +159,9 @@ end; procedure check_ports(controller : PAHCI_Controller); var i : uint32; - ii : uint32; + ii, attempt, signatureWait : uint32; + registered : boolean; + listAlloc, fisAlloc, tableAlloc : puint32; port : PHBA_Port; device : PAHCI_Device; cmd_list_base : puint32; @@ -232,29 +176,18 @@ begin device := PAHCI_Device(@controller^.devices[i]); device^.port := port; - //NEEED TO STOP the port before doing anything - stop_port(port); - reset_port(port); - - { Read device signature AFTER reset so the device has re-sent - its D2H Register FIS with the correct signature. } - case port^.signature of - SATA_SIG_ATA: begin - device^.device_type := SATA; - end; - SATA_SIG_ATAPI: begin - device^.device_type := ATAPI; - end; - SATA_SIG_SEMB: begin - device^.device_type := SEMB; - end; - SATA_SIG_PM: begin - device^.device_type := PM; - end; + port^.int_enable := 0; + if not stop_port(port) then begin log_port_failure(i,port,'cannot stop engine'); continue; end; + { Bring up power before checking for an attached device. } + port^.cmd := port^.cmd or $6; + if not wait_register(@port^.sata_status,$F,3,100) then begin + if (port^.sata_status and $F) = 0 then continue; + { Present but not yet communicating: COMRESET below may recover it. } end; //allocate memory for the command list and ensure it is aligned to 1024 bytes // Need 32 command headers. Allocate extra (alignment-1) bytes for alignment. - cmd_list_base := kalloc(sizeof(THBA_CMD_HEADER) * 32 + 1023); + listAlloc := kalloc(sizeof(THBA_CMD_HEADER) * 32 + 1023); + cmd_list_base := listAlloc; cmd_list_base := puint32((uint32(cmd_list_base) + 1023) and $FFFFFC00); memset(uint32(cmd_list_base), 0, sizeof(THBA_CMD_HEADER) * 32); @@ -265,7 +198,8 @@ begin device^.command_list := PHBA_CMD_HEADER(cmd_list_base); //allocate memory for the FIS and ensure it is aligned to 256 bytes - fis_base := kalloc(sizeof(THBA_FIS) + 255); + fisAlloc := kalloc(sizeof(THBA_FIS) + 255); + fis_base := fisAlloc; fis_base := puint32((uint32(fis_base) + 255) and $FFFFFF00); //set the FIS base address @@ -279,7 +213,8 @@ begin //allocate memory for the command table and ensure it is aligned to 128 bytes // Need 32 command tables. Allocate extra (alignment-1) bytes for alignment. - cmd_table_base := kalloc(sizeof(THBA_CMD_TABLE) * 32 + 127); + tableAlloc := kalloc(sizeof(THBA_CMD_TABLE) * 32 + 127); + cmd_table_base := tableAlloc; cmd_table_base := puint32((uint32(cmd_table_base) + 127) and $FFFFFF80); memset(uint32(cmd_table_base), 0, sizeof(THBA_CMD_TABLE) * 32); @@ -294,14 +229,30 @@ begin command_header^.cmd_table_baseu := 0; end; - //reset the port - reset_port(port); - if (device^.device_type = SATA) then begin - identify_device(controller, i, false); - end else if (device^.device_type = ATAPI) then begin - identify_device(controller, i, true); - end else begin - io.syslog.writestringln('driver.storage.ctl.ahci: Unsupported device type'); + registered := false; + for attempt := 1 to 2 do begin + if not reset_port(port) then begin log_port_failure(i,port,'reset/readiness timeout'); continue; end; + signatureWait := 0; + while (puint32(@port^.signature)^ = 0) or (puint32(@port^.signature)^ = $FFFFFFFF) do begin + if signatureWait >= 500 then break; + psleep(16); Inc(signatureWait); + end; + case puint32(@port^.signature)^ of + SATA_SIG_ATA: begin device^.device_type := SATA; registered := identify_device(controller,i,false); end; + SATA_SIG_ATAPI: begin device^.device_type := ATAPI; registered := identify_device(controller,i,true); end; + else log_port_failure(i,port,'unsupported or missing signature'); + end; + if registered then break; + log_port_failure(i,port,'IDENTIFY failed; retrying detection'); + end; + if not registered then begin + port^.int_enable := 0; + if stop_port(port) then begin + port^.cmdl_basel := 0; port^.fis_basel := 0; + kfree(listAlloc); kfree(fisAlloc); kfree(tableAlloc); + device^.command_list := nil; device^.fis := nil; device^.command_table := nil; + end; + log_port_failure(i,port,'device unavailable after bounded retries'); end; controller^.mio^.int_status := $FFFFFFFF; @@ -309,7 +260,7 @@ begin end; end; -procedure identify_device(controller : PAHCI_Controller; portIndex : uint32; isATAPI : boolean); +function identify_device(controller : PAHCI_Controller; portIndex : uint32; isATAPI : boolean) : boolean; var fis : PHBA_FIS_REG_H2D; cmd_header : PHBA_CMD_HEADER; @@ -327,6 +278,7 @@ var slotCount : uint32; ncqSupported : boolean; begin + identify_device := false; device := PAHCI_Device(@controller^.devices[portIndex]); slotCount := ((controller^.mio^.capabilites shr 8) and $1F) + 1; device^.slotCount := slotCount; @@ -347,9 +299,7 @@ begin cmd_header^.wrt := 0; cmd_header^.prdtl := 1; cmd_header^.clear_busy := 1; - if isATAPI then begin - cmd_header^.atapi := 1; - end; + cmd_header^.atapi := 0; { IDENTIFY PACKET is ATA $A1, not an ATAPI PACKET $A0 command } // Setup the command table (using slot 0) @@ -375,56 +325,19 @@ begin end; fis^.device := $40; - //waut for the port to be ready, bit 7 in the TFD register and bit 3 tfd - while (device^.port^.tfd and $88) <> 0 do begin - // io.syslog.writestring('driver.storage.ctl.ahci: tfd: '); - // io.syslog.writehexln(device^.port^.tfd); + if not wait_register(@device^.port^.tfd,$88,0,2500) then begin + log_port_failure(portIndex,device^.port,'busy before IDENTIFY'); kfree(buffer); exit; end; - - // Issue the command by setting bit 0 in the port's Command Issue register. - cmd := device^.port^.cmd_issue; - cmd := cmd or $1; - device^.port^.cmd_issue := cmd; - - // io.syslog.writestringln('driver.storage.ctl.ahci: Sent identify command'); - - - // Wait for command completion with a timeout. - timeout := 0; - repeat - tfd := device^.port^.tfd; - - if device^.port^.int_status <> 0 then begin - device^.port^.int_status := device^.port^.int_status; - end; - - timeout := timeout + 1; - if timeout > 100 then begin - io.syslog.writestringln('driver.storage.ctl.ahci: IDENTIFY command timeout'); - break; - end; - if (tfd and $1) <> 0 then break; // ERR bit set - psleep(1); - until ((device^.port^.cmd_issue and $1) = 0); // Wait until slot 0 is cleared - - // io.syslog.writestringln('driver.storage.ctl.ahci: Command complete'); - - // Check if the command slot is still set (command didn't complete) - if (device^.port^.cmd_issue and $1) <> 0 then begin - io.syslog.writestringln('driver.storage.ctl.ahci: Error sending identify command'); - io.syslog.writestring('driver.storage.ctl.ahci: Error sata register: '); - io.syslog.writehexln(device^.port^.sata_error); - - { Cancel the pending command by stopping/starting the port so - no DMA fires after we free the buffer. } - stop_port(device^.port); - start_port(device^.port); - - { Re-enable port interrupts and bail — device is non-functional. } - device^.port^.int_enable := $4000000B; - kfree(buffer); + device^.port^.cmd_issue := 1; + if (not wait_register(@device^.port^.cmd_issue,1,0,2500)) or + ((device^.port^.tfd and $1) <> 0) or ((device^.port^.int_status and $40000000) <> 0) then begin + log_port_failure(portIndex,device^.port,'IDENTIFY timeout or task-file error'); + { Free DMA memory only once both engines have stopped. If hardware cannot + stop, retaining this one buffer is safer than a late DMA into freed memory. } + if stop_port(device^.port) then kfree(buffer); exit; end; + device^.port^.int_status := $FFFFFFFF; { Re-enable port interrupts now that IDENTIFY polling is done. $4000000B = bit 0 (D2H Register FIS) | bit 1 (PIO Setup FIS) | bit 3 (Set Device Bits / NCQ) | bit 30 (Task File Error) @@ -488,12 +401,14 @@ begin end; driver.storage.mgr.register_device(storageDev); + kfree(void(storageDev)); { manager owns a copy } + identify_device := true; io.syslog.writestring('driver.storage.ctl.ahci: Registered device on port '); io.syslog.writeint(portIndex); if sec_count > 0 then begin io.syslog.writestring(' with storage manager ('); - io.syslog.writeint((sec_count * storageDev_sectorSize) div 1024 div 1024); + io.syslog.writeint((uint64(sec_count) * storageDev_sectorSize) div 1024 div 1024); io.syslog.writestringln(' MB)'); end else begin io.syslog.writestringln(' with storage manager (no media)'); diff --git a/src/driver/storage/driver.storage.metatest.pas b/src/driver/storage/driver.storage.metatest.pas new file mode 100644 index 00000000..83219061 --- /dev/null +++ b/src/driver/storage/driver.storage.metatest.pas @@ -0,0 +1,204 @@ +unit driver.storage.metatest; + +interface +procedure UnitTest; +procedure init; + +implementation +uses boot.mgr, io.stdio, io.syslog, memory.heap, core.util, core.ds.cfifo, + driver.storage.types, driver.storage.mgr, driver.storage.vol.mbr, + driver.storage.vol.mgr, driver.storage.fs.fat32.types, + driver.storage.fs.fat32.vol, driver.storage.fs.fat32.labels, core.strings; + +type + PMock = ^TMock; + TMock = record + Data : puint8; + Writes, FailWrite, Callbacks : uint32; + Error : TError; + OutOfBounds, FailRead : boolean; + end; + +procedure dispatch(device : PStorage_Device; request : PIORequest); +var m : PMock; offset, size : uint32; +begin + m := PMock(device^.controllerId0); + if (request^.LBA >= device^.maxSectorCount) or + (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 complete_io(request, false, eIOError); exit; end; + offset := request^.LBA*512; size := request^.SectorCount*512; + if request^.RequestType = ioWrite then begin + Inc(m^.Writes); + if m^.Writes = m^.FailWrite then begin complete_io(request, false, eIOError); exit; end; + memcpy(uint32(request^.Buffer), uint32(m^.Data)+offset, size); + end else memcpy(uint32(m^.Data)+offset, uint32(request^.Buffer), size); + request^.ByteCount := size; + complete_io(request, true, eNone); +end; + +procedure completion(error : TError; data : pointer); +var m : PMock; +begin m := PMock(data); Inc(m^.Callbacks); m^.Error := error; end; + +{ Only heap-backed synthetic storage is used. Never registers a hardware device, + never accesses a host/guest disk, and every fake request completes inline. } +procedure UnitTest; +var + device : PStorage_Device; + volume, added : PStorage_Volume; + mock : PMock; + br : PBootRecord; + mbr, before : PMaster_Boot_Record; + params : TFSFormatParams; + labelValue : TVolumeLabel; + part : TPartition_table; + passed, failed, oldCount, writes, i : uint32; + err : TError; + procedure Assert(ok : boolean; name : pchar); + begin + if ok then Inc(passed) else begin Inc(failed); io.syslog.logln('STORMETA FAIL', name); end; + end; +begin + passed := 0; failed := 0; + device := PStorage_Device(kalloc(sizeof(TStorage_Device))); + volume := PStorage_Volume(kalloc(sizeof(TStorage_Volume))); + mock := PMock(kalloc(sizeof(TMock))); + br := PBootRecord(kalloc(sizeof(TBootRecord))); + if (device = nil) or (volume = nil) or (mock = nil) or (br = nil) then begin + if device <> nil then kfree(void(device)); + if volume <> nil then kfree(void(volume)); + if mock <> nil then kfree(void(mock)); + if br <> nil then kfree(void(br)); + io.syslog.logln('STORMETA FAIL', 'test allocation'); exit; + end; + memset(uint32(device),0,sizeof(TStorage_Device)); + memset(uint32(volume),0,sizeof(TStorage_Volume)); + memset(uint32(mock),0,sizeof(TMock)); memset(uint32(br),0,sizeof(TBootRecord)); + mock^.Data := puint8(kalloc(256*512)); + memset(uint32(mock^.Data),$CC,256*512); + device^.controllerId0 := uint32(mock); device^.sectorSize := 512; + device^.maxSectorCount := 256; device^.writable := true; device^.maxActive := 1; + device^.dispatchRead := @dispatch; device^.dispatchWrite := @dispatch; + device^.requestQueue := CFIFO_New(sizeof(Pointer),4); + device^.cachedMBR := kalloc(512); + mbr := PMaster_Boot_Record(device^.cachedMBR); + memset(uint32(mbr),0,512); mbr^.boot_sector := $AA55; mbr^.signature := $12345678; + Assert(write_mbr(device,mbr) = eNone,'aliased MBR write succeeds'); + Assert(PMaster_Boot_Record(mock^.Data)^.signature = $12345678,'aliased MBR bytes preserved'); + before := PMaster_Boot_Record(device^.cachedMBR); + mock^.FailWrite := mock^.Writes+1; + Assert(write_mbr(device,before) = eIOError,'write failure propagated'); + Assert(device^.cachedMBR = before,'failed write retains cache'); + mock^.FailWrite := 0; mock^.Callbacks := 0; + write_mbr_async(device,PMaster_Boot_Record(device^.cachedMBR),@completion,mock); + Assert((mock^.Callbacks = 1) and (mock^.Error = eNone),'async MBR callback once'); + Assert(PMaster_Boot_Record(mock^.Data)^.signature = $12345678,'async alias preserved'); + before := PMaster_Boot_Record(device^.cachedMBR); mock^.Callbacks := 0; + mock^.FailWrite := mock^.Writes+1; + write_mbr_async(device,before,@completion,mock); + Assert((mock^.Callbacks = 1) and (mock^.Error = eIOError),'async failure callback once'); + Assert(device^.cachedMBR = before,'async failure retains cache'); + mock^.FailWrite := 0; + volume^.device := device; volume^.sectorStart := 64; volume^.sectorCount := 128; volume^.sectorSize := 512; + memset(uint32(@params),0,sizeof(params)); params.Version := 1; + params.Flags := FS_FORMAT_PARAM_CLUSTER_SIZE; params.ClusterSize := 1; + Assert(validate_format(volume,128,64,@params) = eNone,'valid format geometry'); + params.ClusterSize := 3; + Assert(validate_format(volume,128,64,@params) = eInvalidArgument,'reject non-power-of-two clusters'); + params.ClusterSize := 1; volume^.sectorCount := 33; writes := mock^.Writes; + Assert(create_volume(volume,33,64,@params) = eInvalidArgument,'reject tiny partition'); + Assert(mock^.Writes = writes,'rejected format does not write'); + volume^.sectorCount := 128; + for i := 1 to 6 do begin + mock^.FailWrite := mock^.Writes+i; + Assert(create_volume(volume,128,64,@params) = eIOError,'each failed format write propagated'); + end; + mock^.FailWrite := 0; + Assert(create_volume(volume,128,64,@params) = eNone,'format completes'); + Assert(not mock^.OutOfBounds,'all writes within device'); + Assert((mock^.Data[63*512] = $CC) and (mock^.Data[192*512] = $CC),'neighbor partitions untouched'); + memcpy(uint32(mock^.Data)+64*512,uint32(br),sizeof(TBootRecord)); + Assert(FAT32ClusterLimit(br) = 97,'cluster bound excludes FAT padding'); + Assert(identify_volume(volume),'fresh format identified'); + Assert(volume^.freeSectors = 93,'free space excludes metadata and allocated directories'); + mock^.FailRead := true; + Assert(not identify_volume(volume),'failed probe read cannot report success'); + mock^.FailRead := false; + Assert(normalize_label(' projects ',labelValue) = eNone,'label accepts spaces'); + Assert(stringEquals(@labelValue[0],'PROJECTS'),'label trims and uppercases'); + Assert(normalize_label('ABCDEFGHIJKL',labelValue) = eInvalidArgument,'reject long label'); + Assert(normalize_label('BAD/NAME',labelValue) = eInvalidArgument,'reject invalid label character'); + writes := mock^.Writes; + Assert(write_label(volume,'BAD/NAME') = eInvalidArgument,'invalid rename rejected'); + Assert(mock^.Writes = writes,'invalid label does not write'); + Assert(write_label(volume,'Projects') = eNone,'write volume label'); + volume^.labelText[0] := #0; + Assert(read_label(volume) = eNone,'reload label from disk'); + Assert(stringEquals(@volume^.labelText[0],'PROJECTS'),'label survives cache reset'); + Assert(PDirectory(uint32(mock^.Data)+97*512)^.fileName[0] = 'S','rename preserves SYSTEM directory'); + { Add a valid backup boot sector and verify all three metadata writes. } + PBootRecord(uint32(mock^.Data)+64*512)^.backupCluster := 6; + memcpy(uint32(mock^.Data)+64*512,uint32(mock^.Data)+70*512,512); + for i := 1 to 3 do begin + mock^.FailWrite := mock^.Writes+i; + Assert(write_label(volume,'NEW NAME') = eIOError,'label write failure propagated'); + mock^.FailWrite := 0; + Assert(read_label(volume) = eNone,'label readable after failed rename'); + Assert(stringEquals(@volume^.labelText[0],'PROJECTS'),'failed rename retains previous label'); + end; + Assert(write_label(volume,'ABCDEFGHIJK') = eNone,'maximum length label'); + Assert(PBootRecord(uint32(mock^.Data)+70*512)^.volumeLabel[10] = ord('K'),'backup label updated'); + Assert(write_label(volume,'') = eNone,'clear label'); + volume^.labelText[0] := 'X'; + Assert(read_label(volume) = eNone,'reload cleared label'); + Assert(volume^.labelText[0] = #0,'blank label persists'); + params.Flags := params.Flags or FS_FORMAT_PARAM_LABEL; + memcpy(uint32(pchar('MY DATA')),uint32(@params.LabelText[0]),8); + Assert(create_volume(volume,128,64,@params) = eNone,'format with label'); + Assert(read_label(volume) = eNone,'read format label'); + Assert(stringEquals(@volume^.labelText[0],'MY DATA'),'format label persists'); + Assert(PDirectory(uint32(mock^.Data)+97*512)^.attributes = $08,'format creates root volume label entry'); + Assert(not mock^.OutOfBounds,'label operations stay inside volume'); + oldCount := get_volume_count(); + memset(uint32(@part),0,sizeof(part)); part.LBA_start := 64; part.sector_count := 128; + mock^.FailWrite := mock^.Writes+1; + Assert(add_partition(device,0,part) = eIOError,'failed partition creation reported'); + Assert(get_volume_count() = oldCount,'failed create publishes no volume'); + Assert(PMaster_Boot_Record(device^.cachedMBR)^.partition[0].sector_count = 0,'failed create leaves MBR cache'); + mock^.FailWrite := 0; + Assert(add_partition(device,0,part) = eNone,'partition creation committed'); + added := get_volume(get_volume_count()-1); + added^.lifecycleFlags := STORAGE_VOLUME_FLAG_FORMATTING; + writes := mock^.Writes; + Assert(remove_partition(device,0) = eFileInUse,'delete rejects formatting volume'); + Assert(mock^.Writes = writes,'busy delete leaves disk untouched'); + mock^.Callbacks := 0; + Assert(not format_volume_async(device,get_volume_count()-1,'FAT32',@params,@completion,mock),'busy format rejected'); + Assert(mock^.Callbacks = 0,'false submission never calls callback'); + added^.lifecycleFlags := 0; + mock^.FailWrite := mock^.Writes+1; + Assert(remove_partition(device,0) = eIOError,'delete propagates failed MBR write'); + Assert(get_volume_count() = oldCount+1,'failed delete retains volume'); + mock^.FailWrite := 0; + Assert(remove_partition(device,0) = eNone,'delete commits'); + Assert(get_volume_count() = oldCount,'delete detaches volume after write'); + 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); + io.syslog.writestring(' Failed: '); io.syslog.writeintln(failed); +end; + +procedure command(params : PParamList; stdin_buf, stdout_buf, stderr_buf : POutBuf); +begin + UnitTest; + io.stdio.bufWriteStrLn(stdout_buf,'Storage metadata regression results written to serial log.'); +end; + +procedure init; +begin io.stdio.registerCommand('STORMETA',@command,'Run memory-backed storage metadata regression tests'); end; + +initialization + boot.mgr.registerBoot('driver.storage.metatest',@init,'Storage Metadata Tests',BOOT_MGR_BARRIER_MID); +end. diff --git a/src/driver/storage/driver.storage.mgr.pas b/src/driver/storage/driver.storage.mgr.pas index 22bf178e..7c8ef025 100644 --- a/src/driver/storage/driver.storage.mgr.pas +++ b/src/driver/storage/driver.storage.mgr.pas @@ -68,7 +68,7 @@ function get_device_count() : uint32; function get_device(index : uint32) : PStorage_Device; function controller_type_2_string(controllerType : TControllerType) : pchar; function read_mbr(device : PStorage_Device) : PMaster_Boot_Record; -procedure write_mbr(device : PStorage_Device; mbr : PMaster_Boot_Record); +function write_mbr(device : PStorage_Device; mbr : PMaster_Boot_Record) : TError; { Return pointer to cached mbr data (nil if not cached). Caller must NOT free the returned pointer. } @@ -139,6 +139,7 @@ type hardware completion fires, then frees it before forwarding the user's callback. } TMBRWriteAsyncData = record Buffer : pointer; + Device : PStorage_Device; UserCallback : TIOCallback; UserData : pointer; end; @@ -181,17 +182,33 @@ begin alloc_mbr_io_buffer := buf; end; +function claim_mbr(device : PStorage_Device) : boolean; +var flags : uint32; +begin + asm pushfd; cli; pop flags end; + claim_mbr := not device^.mbrWriteBusy; + if claim_mbr then device^.mbrWriteBusy := true; + if (flags and $200) <> 0 then asm sti end; +end; + +procedure publish_mbr(device : PStorage_Device; buffer : pointer); +var old : pointer; +begin + { The DMA buffer becomes the cache only after a successful write. } + old := device^.cachedMBR; + device^.cachedMBR := buffer; + if old <> nil then kfree(old); +end; + procedure mbr_write_async_callback(error : TError; userdata : pointer); -var - ctx : PMBRWriteAsyncData; +var ctx : PMBRWriteAsyncData; begin ctx := PMBRWriteAsyncData(userdata); if ctx = nil then exit; - - if ctx^.Buffer <> nil then - kfree(void(ctx^.Buffer)); - if ctx^.UserCallback <> nil then - ctx^.UserCallback(error, ctx^.UserData); + if error = eNone then publish_mbr(ctx^.Device, ctx^.Buffer) + else kfree(ctx^.Buffer); + ctx^.Device^.mbrWriteBusy := false; + if ctx^.UserCallback <> nil then ctx^.UserCallback(error, ctx^.UserData); kfree(void(ctx)); end; @@ -231,6 +248,8 @@ begin device^.activeCount := 0; if device^.maxActive = 0 then device^.maxActive := 1; device^.cachedMBR := nil; + device^.metadataBusy := false; + device^.mbrWriteBusy := false; device^.isBootDevice := false; { dispatchRead/dispatchWrite: left as-is — driver sets them before calling register_device. memset(0) guarantees nil for unmigrated drivers. } @@ -334,27 +353,21 @@ begin end; end; -procedure write_mbr(device : PStorage_Device; mbr : PMaster_Boot_Record); -var - oldCache : pointer; - cache : puint32; - buf : puint32; +function write_mbr(device : PStorage_Device; mbr : PMaster_Boot_Record) : TError; +var buf : puint32; begin - push_trace('driver.storage.mgr.write_mbr'); - if (device = nil) or (mbr = nil) or (not device^.writable) then exit; - { Update cache } - oldCache := device^.cachedMBR; - cache := puint32(kalloc(sizeof(TMaster_Boot_Record))); - if cache <> nil then - memcpy(uint32(mbr), uint32(cache), sizeof(TMaster_Boot_Record)); - device^.cachedMBR := pointer(cache); - if oldCache <> nil then - kfree(oldCache); - + write_mbr := eInvalidArgument; + if (device = nil) or (mbr = nil) then exit; + if not device^.writable then begin write_mbr := eReadOnly; exit; end; + if not claim_mbr(device) then begin write_mbr := eFileInUse; exit; end; + { Snapshot first: mbr may alias device^.cachedMBR. } buf := alloc_mbr_io_buffer(device, mbr); - if buf = nil then exit; - storage_write(device, 0, 1, buf); - kfree(void(buf)); + if buf = nil then write_mbr := eOutOfMemory + else begin + write_mbr := storage_write(device, 0, 1, buf); + if write_mbr = eNone then publish_mbr(device, buf) else kfree(buf); + end; + device^.mbrWriteBusy := false; end; function get_cached_mbr(device : PStorage_Device) : PMaster_Boot_Record; @@ -365,53 +378,32 @@ end; procedure write_mbr_async(device : PStorage_Device; mbr : PMaster_Boot_Record; callback : TIOCallback; callbackData : pointer); -var - oldCache : pointer; - cache : puint32; - buf : puint32; - ctx : PMBRWriteAsyncData; - err : TError; +var ctx : PMBRWriteAsyncData; buf : puint32; err : TError; begin - push_trace('driver.storage.mgr.write_mbr_async'); - if (device = nil) or (mbr = nil) then begin - if callback <> nil then callback(eInvalidArgument, callbackData); + err := eNone; + if (device = nil) or (mbr = nil) then err := eInvalidArgument + else if not device^.writable then err := eReadOnly + else if not claim_mbr(device) then err := eFileInUse; + if err <> eNone then begin + if callback <> nil then callback(err, callbackData); exit; end; - if not device^.writable then begin - if callback <> nil then callback(eReadOnly, callbackData); - exit; - end; - { Update cache } - oldCache := device^.cachedMBR; - cache := puint32(kalloc(sizeof(TMaster_Boot_Record))); - if cache <> nil then - memcpy(uint32(mbr), uint32(cache), sizeof(TMaster_Boot_Record)); - device^.cachedMBR := pointer(cache); - if oldCache <> nil then - kfree(oldCache); - buf := alloc_mbr_io_buffer(device, mbr); if buf = nil then begin + device^.mbrWriteBusy := false; if callback <> nil then callback(eOutOfMemory, callbackData); exit; end; - ctx := PMBRWriteAsyncData(kalloc(sizeof(TMBRWriteAsyncData))); if ctx = nil then begin - kfree(void(buf)); + kfree(buf); device^.mbrWriteBusy := false; if callback <> nil then callback(eOutOfMemory, callbackData); exit; end; - ctx^.Buffer := pointer(buf); - ctx^.UserCallback := callback; - ctx^.UserData := callbackData; - + ctx^.Buffer := buf; ctx^.Device := device; + ctx^.UserCallback := callback; ctx^.UserData := callbackData; err := storage_write_async(device, 0, 1, buf, @mbr_write_async_callback, ctx); - if err <> eNone then begin - kfree(void(buf)); - kfree(void(ctx)); - if callback <> nil then callback(err, callbackData); - end; + if err <> eNone then mbr_write_async_callback(err, ctx); end; { ========================================================================== } diff --git a/src/driver/storage/driver.storage.types.pas b/src/driver/storage/driver.storage.types.pas index 0a71a094..5027fe2d 100644 --- a/src/driver/storage/driver.storage.types.pas +++ b/src/driver/storage/driver.storage.types.pas @@ -154,7 +154,8 @@ type sleVolumeInvalidated, sleVolumeRemoved, sleDeviceAdded, - sleDeviceRemoved + sleDeviceRemoved, + sleVolumeLabelChanged ); TStorageLifecycleCallback = procedure(event : TStorageLifecycleEvent; @@ -163,6 +164,8 @@ type error : TError; userdata : pointer); + TVolumeLabel = array[0..32] of char; { cached, NUL-terminated display name } + TFSFormatParams = record Version : uint16; Flags : uint16; @@ -186,13 +189,15 @@ type TIOCallback = procedure(error : TError; userdata : pointer); { Filesystem callback core.types } - PPCreateHook = procedure(volume : PStorage_volume; start : uint32; size : uint32; config : PFSFormatParams); + PPCreateHook = function(volume : PStorage_volume; sectors : uint32; start : uint32; config : PFSFormatParams) : TError; PPCreateAsyncHook = procedure(volume : PStorage_volume; start : uint32; size : uint32; config : PFSFormatParams; callback : TIOCallback; callbackData : pointer); PPDetectHook = procedure(disk : PStorage_Device); PPCreateDirHook = procedure(volume : PStorage_volume; directory : pchar; dirname : pchar; attributes : uint32; status : puint32); PPReadDirHook = function(volume : PStorage_volume; directory : pchar; status : puint32) : PLinkedListBase; PPDeleteFileHook = procedure(volume : PStorage_Volume; filePath : pchar; status : puint32); PPDeleteDirHook = procedure(volume : PStorage_Volume; path : pchar; status : puint32); + PPValidateLabelHook = function(text : pchar; var normalized : TVolumeLabel) : TError; + PPSetLabelHook = function(volume : PStorage_Volume; text : pchar) : TError; PPIdentifyHook = function(volume : PStorage_Volume) : boolean; PPRenameFileHook = procedure(volume : PStorage_Volume; filePath : pchar; newName : pchar; status : puint32); { Offset-based read: reads byteCount bytes starting at byte offset into the file. @@ -271,6 +276,8 @@ type maxSectorCount : uint32; sectorSize : uint32; //in bytes start : uint32; //start of device in sectors + metadataBusy : boolean; { volume/partition operation owns device } + mbrWriteBusy : boolean; { serializes staged sector-zero writes } cachedMBR : pointer; { cached mbr data, nil if not yet read } isBootDevice : boolean; { true if this is the device GRUB booted from } end; @@ -280,6 +287,7 @@ type sName : pchar; system_id : uint8; createCallback : PPCreateHook; + validateFormat : PPCreateHook; { pure preflight, before invalidation } detectCallback : PPDetectHook; createDirCallback : PPCreateDirHook; readDirCallback : PPReadDirHook; @@ -304,6 +312,8 @@ type openFileCallback : PPOpenFileHook; closeFileCallback : PPCloseFileHook; formatParamFlags : uint32; + validateLabelCallback : PPValidateLabelHook; + setLabelCallback : PPSetLabelHook; end; { Generic storage volume } @@ -317,6 +327,7 @@ type filesystem : PFilesystem; isBootDrive : boolean; lifecycleFlags : uint32; + labelText : TVolumeLabel; fsPrivate : pointer; { FS-driver private data (e.g. PFATCache for FAT32) } end; diff --git a/src/driver/storage/driver.storage.vfs.pas b/src/driver/storage/driver.storage.vfs.pas index bf676d97..b9521175 100644 --- a/src/driver/storage/driver.storage.vfs.pas +++ b/src/driver/storage/driver.storage.vfs.pas @@ -134,6 +134,7 @@ procedure UnwatchDirectory(WatchID : uint32); { Volume invalidation — must be called before freeing a volume. Closes all open FDs referencing the volume across all processes, evicts directory cache entries, and removes VFS mount points. } +function VolumeHasOpenHandles(vol : PStorage_Volume) : boolean; function InvalidateVolume(vol : PStorage_Volume) : boolean; { VFS Functions } @@ -509,7 +510,7 @@ end; function GetObjectFromPath(path : pchar) : PVFSObject; forward; function GetObjectFromPathEx(path : pchar; var volRelPath : pchar) : PVFSObject; forward; -function volumeHasAsyncHandles(vol : PStorage_Volume) : boolean; +function volumeHasHandles(vol : PStorage_Volume; asyncOnly : boolean) : boolean; var pCount : uint32; pi : uint32; @@ -517,7 +518,7 @@ var pCtx : proc.types.PProcessContext; tbl : PFDTable; begin - volumeHasAsyncHandles := false; + volumeHasHandles := false; if vol = nil then exit; pCount := proc.mgr.processCount(); @@ -530,14 +531,17 @@ begin if tbl = nil then continue; for fi := 0 to MAX_FDS - 1 do begin if tbl^.Entries[fi].InUse and (tbl^.Entries[fi].Volume = vol) - and (tbl^.Entries[fi].AsyncRefs > 0) then begin - volumeHasAsyncHandles := true; + and ((not asyncOnly) or (tbl^.Entries[fi].AsyncRefs > 0)) then begin + volumeHasHandles := true; exit; end; end; end; end; +function VolumeHasOpenHandles(vol : PStorage_Volume) : boolean; +begin VolumeHasOpenHandles := volumeHasHandles(vol,false); end; + function InvalidateVolume(vol : PStorage_Volume) : boolean; var pCount : uint32; @@ -554,7 +558,7 @@ begin debug.tracer.push_trace('driver.storage.vfs.InvalidateVolume.enter'); InvalidateVolume := false; if vol = nil then exit; - if volumeHasAsyncHandles(vol) then begin + if volumeHasHandles(vol,true) then begin debug.tracer.push_trace('driver.storage.vfs.InvalidateVolume.busy'); exit; end; @@ -986,6 +990,7 @@ begin volumeGetDirectories := nil; if vol = nil then exit; + if (vol^.device = nil) or vol^.device^.metadataBusy or driver.storage.vol.mgr.volume_is_busy(vol) then exit; if vol^.filesystem = nil then exit; if vol^.filesystem^.readDirCallback = nil then exit; @@ -1153,6 +1158,15 @@ begin exit; end; + mountObj := PVFSObject(core.ds.hashmap.get(ht, mountName)); + if mountObj <> nil then begin + if (mountObj^.ObjectType = otDRIVE) and (mountObj^.Reference = void(volume)) then begin + mountVolume := pvRegistered; + driver.storage.vol.mgr.volume_set_mounted(volume, true); + end; + kfree(void(parentPath)); STRLL_Free(splitPath); exit; + end; + { Create the mount object storing the volume pointer } mountObj := PVFSObject(kalloc(sizeof(TVFSObject))); @@ -1219,6 +1233,12 @@ begin end; volOut^ := PStorage_Volume(Obj^.Reference); + if (volOut^ = nil) or (volOut^^.device = nil) then begin + if volRel <> nil then kfree(void(volRel)); exit; + end; + if volOut^^.device^.metadataBusy or driver.storage.vol.mgr.volume_is_busy(volOut^) then begin + if volRel <> nil then kfree(void(volRel)); volOut^ := nil; exit; + end; { volRel is the path within the volume, e.g. '/mydir/myfile.txt' } if (volRel = nil) or StringEquals(volRel, '/') then begin diff --git a/src/driver/storage/fs/driver.storage.fs.flatfs.pas b/src/driver/storage/fs/driver.storage.fs.flatfs.pas index 10b53fe2..73052ac0 100644 --- a/src/driver/storage/fs/driver.storage.fs.flatfs.pas +++ b/src/driver/storage/fs/driver.storage.fs.flatfs.pas @@ -63,7 +63,7 @@ var procedure init; -procedure create_volume(volume : PStorage_Volume; sectors : uint32; start : uint32; config : PFSFormatParams); +function create_volume(volume : PStorage_Volume; sectors : uint32; start : uint32; config : PFSFormatParams) : TError; procedure detect_volumes(disk : PStorage_Device); function read_directory(volume : PStorage_Volume; directory : pchar; status : PuInt32) : PLinkedListBase; procedure write_directory(volume : PStorage_Volume; directory : pchar; status : PuInt32); @@ -72,87 +72,55 @@ procedure read_file(volume : PStorage_Volume; fileName : pchar; data : PuInt32; implementation -procedure create_volume(volume : PStorage_Volume; sectors : uint32; start : uint32; config : PFSFormatParams); -var - info : PDisk_Info; - entryTable : PFile_Entry; - i : uint32; - - directories : PLinkedListBase; - status : PuInt32; - - data : pchar; - -const - asuroArray : array[0..7] of char = 'ASURO'; - systemArray : array[0..6] of char = '/system'; - programsArray : array[0..8] of char = '/programs'; - userArray : array[0..4] of char = '/user'; - - testArray : array[0..23] of char = 'ASURO TEST DATA ARRAY'; +function validate_format(volume : PStorage_Volume; sectors, start : uint32; config : PFSFormatParams) : TError; begin - - info := PDisk_Info(Kalloc(512)); - memset(uint32(info), 0, 512); - - info^.jmp2boot := $0; - info^.OEMName := asuroArray; - info^.version := 1; - info^.sectorCount := sectors; - info^.fileCount := 1000; - info^.signature := $0B00B1E5; - - if (config <> nil) and - (config^.Version = FS_FORMAT_PARAMS_VERSION_1) and - ((config^.Flags and FS_FORMAT_PARAM_FILE_COUNT) <> 0) and - (config^.FileCount <> 0) then begin - info^.fileCount := config^.FileCount; + validate_format := eInvalidArgument; + if (volume = nil) or (volume^.device = nil) then exit; + if not volume^.device^.writable then begin validate_format := eReadOnly; exit; end; + { Legacy FlatFS readers use 512-byte sectors and exactly 1000 entries. } + if (volume^.device^.sectorSize <> 512) or (sectors > 65535) or + (sectors <= 126) or (start <> volume^.sectorStart) or + (sectors <> volume^.sectorCount) or (start >= volume^.device^.maxSectorCount) or + (sectors > volume^.device^.maxSectorCount-start) then exit; + if config <> nil then begin + if (config^.Version <> FS_FORMAT_PARAMS_VERSION_1) or + ((config^.Flags and (not FS_FORMAT_PARAM_FILE_COUNT)) <> 0) then exit; + if ((config^.Flags and FS_FORMAT_PARAM_FILE_COUNT) <> 0) and + (config^.FileCount <> 1000) then exit; end; + validate_format := eNone; +end; - driver.storage.mgr.storage_write(volume^.device, start, 1, PuInt32(info));// what happens if buffer is smaller than 512? - - // create file table - entryTable := PFile_Entry(Kalloc(126 * 512)); - memset(uint32(entryTable), 0, 126 * 512); - - //create system folders - entryTable[0].attribues := $10; - memcpy(uint32(@systemArray), uint32(@entryTable[0].name), 7); - entryTable[0].size := 0; - entryTable[0].start := 0; - - entryTable[1].attribues := $10; - memcpy(uint32(@programsArray), uint32(@entryTable[1].name), 9); - entryTable[1].size := 0; - entryTable[1].start := 0; - - entryTable[2].attribues := $10; - memcpy(uint32(@userArray), uint32(@entryTable[2].name), 5); - entryTable[2].size := 0; - entryTable[2].start := 0; - - //write file table - driver.storage.mgr.storage_write(volume^.device, start + 1, ((sizeof(TFile_Entry) * info^.fileCount) div 512), puint32(entryTable)); - //volume^.device^.writeCallback(volume^.device, start + 1, 1, puint32(entryTable)); - - //temp read - status := PuInt32(Kalloc(sizeof(uint32))); - // directories := read_directory(volume, '/', status); - - data := stringNew(1024); - memset(uint32(data), 0, 513); - memcpy(uint32(@testArray), uint32(data), 23); - - write_directory(volume, '/logs', status); - write_directory(volume, '/logs/test', status); - write_file(volume, '/logs/log.txt', PuInt32(data), 1, status); - write_file(volume, '/logs/log2.txt', PuInt32(data), 1, status); - write_file(volume, '/logs/log3.txt', PuInt32(data), 1, status); - read_directory(volume, '/logs', status); - - kfree(puint32(data)); - - read_file(volume, '/logs/log.txt', PuInt32(data), status); +function create_volume(volume : PStorage_Volume; sectors : uint32; start : uint32; config : PFSFormatParams) : TError; +var info : PDisk_Info; entries : PFile_Entry; err : TError; +begin + create_volume := validate_format(volume, sectors, start, config); + if create_volume <> eNone then exit; + info := PDisk_Info(kalloc(512)); entries := PFile_Entry(kalloc(125*512)); + if (info = nil) or (entries = nil) then begin + if info <> nil then kfree(void(info)); + if entries <> nil then kfree(void(entries)); + create_volume := eOutOfMemory; exit; + end; + memset(uint32(info), 0, 512); memset(uint32(entries), 0, 125*512); + err := storage_write(volume^.device, start, 1, puint32(info)); + if err = eNone then begin + entries[0].attribues := $10; + memcpy(uint32(pchar('/system')), uint32(@entries[0].name[0]), 7); + entries[1].attribues := $10; + memcpy(uint32(pchar('/programs')), uint32(@entries[1].name[0]), 9); + entries[2].attribues := $10; + memcpy(uint32(pchar('/user')), uint32(@entries[2].name[0]), 5); + err := storage_write(volume^.device, start+1, 125, puint32(entries)); + end; + if err = eNone then begin + memcpy(uint32(pchar('ASURO ')), uint32(@info^.OEMName[0]), 8); + info^.version := 1; info^.sectorCount := sectors; info^.fileCount := 1000; + info^.signature := $0B00B1E5; + err := storage_write(volume^.device, start, 1, puint32(info)); + end; + kfree(void(entries)); kfree(void(info)); + create_volume := err; end; procedure write_directory(volume : PStorage_Volume; directory : pchar; status : PuInt32); @@ -976,9 +944,10 @@ begin filesystem.readOffsetCallback:= @readFileAtOffset; // filesystem.createDirCallback:= @writeDirectoryGen; filesystem.createcallback:= @create_volume; + filesystem.validateFormat := @validate_format; filesystem.detectcallback:= @detect_volumes; filesystem.identifyCallback:= @identify_volume; - filesystem.formatParamFlags := FS_FORMAT_PARAM_FILE_COUNT; + filesystem.formatParamFlags := 0; { fixed 1000-entry legacy format } // filesystem.writecallback:= @writeFile; // filesystem.readcallback := @readFile; diff --git a/src/driver/storage/fs/driver.storage.fs.iso9660.pas b/src/driver/storage/fs/driver.storage.fs.iso9660.pas index c3f7d767..50ca0aa6 100644 --- a/src/driver/storage/fs/driver.storage.fs.iso9660.pas +++ b/src/driver/storage/fs/driver.storage.fs.iso9660.pas @@ -392,6 +392,15 @@ end; { ==================== Filesystem callbacks ==================== } +procedure cacheVolumeLabel(volume : PStorage_Volume; pvd : PPVD); +var n, i : uint32; +begin + memset(uint32(@volume^.labelText[0]),0,sizeof(volume^.labelText)); + n := 32; + while (n > 0) and ((pvd^.volumeId[n-1] = ' ') or (pvd^.volumeId[n-1] = #0)) do Dec(n); + i := 0; while i < n do begin volume^.labelText[i] := pvd^.volumeId[i]; Inc(i); end; +end; + { Identify: check for 'CD001' at sector 16 } function identify_volume(volume : PStorage_Volume) : boolean; var @@ -405,6 +414,7 @@ begin buf := readSectors(volume^.device, volume^.sectorStart + PVD_SECTOR, 1); if buf = nil then exit; identify_volume := isPVD(buf); + if identify_volume then cacheVolumeLabel(volume,PPVD(buf)); kfree(buf); end; @@ -437,6 +447,7 @@ begin volume^.filesystem := @filesystem; volume^.isBootDrive := disk^.isBootDevice; + cacheVolumeLabel(volume,pvd); driver.storage.vol.mgr.register_volume(disk, volume); kfree(buf); diff --git a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.core.pas b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.core.pas index 177a40d7..01ccea54 100644 --- a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.core.pas +++ b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.core.pas @@ -94,10 +94,7 @@ end; function fatMaxClusterCount(bootRecord : PBootRecord) : uint32; begin - if (bootRecord = nil) or (bootRecord^.sectorSize = 0) then - fatMaxClusterCount := 0 - else - fatMaxClusterCount := (bootRecord^.FATSize * bootRecord^.sectorSize) div 4; + fatMaxClusterCount := FAT32ClusterLimit(bootRecord); end; function fatClustersForBytes(byteCount : uint32; bytesPerCluster : uint32) : uint32; @@ -391,7 +388,9 @@ begin sectorBuf := puint32(kalloc(bufSize)); if sectorBuf = nil then exit; memset(uint32(sectorBuf), 0, bufSize); - driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart, 1, sectorBuf); + if driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart, 1, sectorBuf) <> eNone then begin + kfree(sectorBuf); exit; + end; bootRecord := PBootRecord(sectorBuf)^; kfree(sectorBuf); readBootRecordRaw := true; @@ -699,9 +698,13 @@ begin { Keep runtime addressing compatible with the legacy formatter and metadata paths. } ctx^.Info.DataStart := volume^.sectorStart + ctx^.Info.BootRecord.rsvSectors + - ctx^.Info.BootRecord.FATSize; + uint32(ctx^.Info.BootRecord.numFats) * ctx^.Info.BootRecord.FATSize; ctx^.Info.BytesPerCluster := uint32(ctx^.Info.BootRecord.spc) * uint32(ctx^.Info.BootRecord.sectorSize); ctx^.Info.MaxCluster := fatMaxClusterCount(@ctx^.Info.BootRecord); + if (ctx^.Info.MaxCluster <= 2) or + (ctx^.Info.BootRecord.manySectors > volume^.sectorCount) then begin + fatCtxFree(ctx); exit; + end; ctx^.InfoValid := true; ctx^.Cache.Data := puint32(kalloc(FAT_CACHE_LINES * 512)); @@ -1135,6 +1138,7 @@ begin end; if dir^.fileName[0] = char($E5) then continue; + if (dir^.attributes and $08) <> 0 then continue; { volume labels and LFN entries are not files } if compareByteArray8(dir^.fileName, cleanName) and matchExtension(dir^.fileExtension, ext) then begin findEntryInSector := integer(idx); exit; @@ -2048,7 +2052,7 @@ begin end; if dir^.fileName[0] = char($E5) then continue; - if dir^.attributes = $0F then + if (dir^.attributes and $08) <> 0 then continue; if fatIsDotEntry(dir) then continue; diff --git a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.labels.pas b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.labels.pas new file mode 100644 index 00000000..dc3af03e --- /dev/null +++ b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.labels.pas @@ -0,0 +1,205 @@ +unit driver.storage.fs.fat32.labels; + +interface +uses driver.storage.types; +function normalize_label(text : pchar; var normalized : TVolumeLabel) : TError; +function read_label(volume : PStorage_Volume) : TError; +{ Caller owns the device metadata lock and has invalidated filesystem caches. } +function write_label(volume : PStorage_Volume; text : pchar) : TError; + +implementation +uses driver.storage.fs.fat32.types, driver.storage.mgr, memory.heap, core.util; + +function normalize_label(text : pchar; var normalized : TVolumeLabel) : TError; +var i, n, first : uint32; c : char; +begin + normalize_label := eInvalidArgument; + memset(uint32(@normalized[0]), 0, sizeof(normalized)); + if text = nil then exit; + n := 0; + while (n < 12) and (text[n] <> #0) do Inc(n); + if n > 11 then exit; + while (n > 0) and (text[n-1] = ' ') do Dec(n); + first := 0; while (first < n) and (text[first] = ' ') do Inc(first); + i := first; + while i < n do begin + c := text[i]; + if (ord(c) < 32) or (ord(c) > 126) then exit; + case c of + '"','*','+',',','.','/',':',';','<','=','>','?','[',']','\','|': exit; + end; + if (c >= 'a') and (c <= 'z') then c := char(ord(c)-32); + normalized[i-first] := c; Inc(i); + end; + normalize_label := eNone; +end; + +procedure unpack_label(src : pchar; var dest : TVolumeLabel); +var n, i : uint32; +begin + memset(uint32(@dest[0]), 0, sizeof(dest)); + n := 11; while (n > 0) and ((src[n-1] = ' ') or (src[n-1] = #0)) do Dec(n); + for i := 0 to 10 do if i < n then dest[i] := src[i]; +end; + +procedure pack_label(src : pchar; dest : pchar); +var i : uint32; +begin + for i := 0 to 10 do dest[i] := ' '; + i := 0; while (i < 11) and (src[i] <> #0) do begin dest[i] := src[i]; Inc(i); end; +end; + +function load_boot(volume : PStorage_Volume; buffer : puint32) : TError; +var br : PBootRecord; lim : uint32; +begin + load_boot := eInvalidArgument; + if (volume = nil) or (volume^.device = nil) then exit; + if (volume^.device^.sectorSize <> 512) or (volume^.sectorCount = 0) or + (volume^.sectorStart >= volume^.device^.maxSectorCount) or + (volume^.sectorCount > volume^.device^.maxSectorCount-volume^.sectorStart) then exit; + load_boot := storage_read(volume^.device,volume^.sectorStart,1,buffer); + if load_boot <> eNone then exit; + br := PBootRecord(buffer); lim := FAT32ClusterLimit(br); + if (lim <= 2) or (br^.manySectors = 0) or (br^.manySectors > volume^.sectorCount) or + (br^.rootCluster < 2) or (br^.rootCluster >= lim) or + (puint16(uint32(buffer)+510)^ <> $AA55) then load_boot := eCorruptFilesystem; +end; + +{ Bounded walk of the root FAT chain. A label is authoritative when present. + Also remembers the first reusable slot; never scans beyond the end marker. } +function locate_label(volume : PStorage_Volume; br : PBootRecord; scratch : puint32; + var sector, offset : uint32; var found : boolean) : TError; +var cluster, next, limit, steps, ds, i, lba, dataStart, fatStart : uint32; + freeSector, freeOffset : uint32; entry : PDirectory; +begin + locate_label := eNone; sector := 0; offset := 0; found := false; + freeSector := 0; freeOffset := 0; limit := FAT32ClusterLimit(br); + dataStart := volume^.sectorStart + br^.rsvSectors + br^.numFats*br^.FATSize; + fatStart := volume^.sectorStart + br^.rsvSectors; + if (br^.flags and $80) <> 0 then begin + i := br^.flags and $0F; + if i >= br^.numFats then begin locate_label := eCorruptFilesystem; exit; end; + fatStart := fatStart+i*br^.FATSize; + end; + cluster := br^.rootCluster; steps := 0; + while steps < limit-2 do begin + if (cluster < 2) or (cluster >= limit) then begin locate_label := eCorruptFilesystem; exit; end; + lba := dataStart+(cluster-2)*br^.spc; + for ds := 0 to br^.spc-1 do begin + locate_label := storage_read(volume^.device,lba+ds,1,scratch); + if locate_label <> eNone then exit; + for i := 0 to 15 do begin + entry := @PDirectory(scratch)[i]; + if (entry^.fileName[0] = #0) or (entry^.fileName[0] = char($E5)) then begin + if freeSector = 0 then begin freeSector := lba+ds; freeOffset := i*32; end; + if entry^.fileName[0] = #0 then begin sector := freeSector; offset := freeOffset; exit; end; + end else if (entry^.attributes and $3F) = $08 then begin + sector := lba+ds; offset := i*32; found := true; exit; + end; + end; + end; + locate_label := storage_read(volume^.device,fatStart+cluster div 128,1,scratch); + if locate_label <> eNone then exit; + next := scratch[cluster mod 128] and $0FFFFFFF; + if next >= $0FFFFFF8 then begin sector := freeSector; offset := freeOffset; exit; end; + cluster := next; Inc(steps); + end; + locate_label := eCorruptFilesystem; +end; + +function read_label(volume : PStorage_Volume) : TError; +var mem, boot, scratch : puint32; sector, offset, i : uint32; found, noName : boolean; + value : TVolumeLabel; +begin + mem := puint32(kalloc(1024)); + if mem = nil then begin read_label := eOutOfMemory; exit; end; + boot := mem; scratch := puint32(uint32(mem)+512); + read_label := load_boot(volume,boot); + if read_label = eNone then begin + read_label := locate_label(volume,PBootRecord(boot),scratch,sector,offset,found); + if read_label = eNone then begin + if found then unpack_label(pchar(uint32(scratch)+offset),value) + else begin + unpack_label(pchar(@PBootRecord(boot)^.volumeLabel[0]),value); + noName := true; + for i := 0 to 10 do + if pchar(@PBootRecord(boot)^.volumeLabel[0])[i] <> pchar('NO NAME ')[i] then noName := false; + if noName then value[0] := #0; + end; + volume^.labelText := value; + end; + end; + kfree(mem); +end; + +function write_label(volume : PStorage_Volume; text : pchar) : TError; +var mem, boot, scratch, rootOld, backupOld, bootOld : puint32; + br : PBootRecord; value : TVolumeLabel; sector, offset, backup : uint32; + found, bootWritten, backupWritten, rootWritten : boolean; entry : PDirectory; +begin + write_label := normalize_label(text,value); if write_label <> eNone then exit; + if (volume = nil) or (volume^.device = nil) then begin write_label := eInvalidArgument; exit; end; + if not volume^.device^.writable then begin write_label := eReadOnly; exit; end; + mem := puint32(kalloc(5*512)); + if mem = nil then begin write_label := eOutOfMemory; exit; end; + boot := mem; scratch := puint32(uint32(mem)+512); rootOld := puint32(uint32(mem)+1024); + backupOld := puint32(uint32(mem)+1536); bootOld := puint32(uint32(mem)+2048); + bootWritten := false; backupWritten := false; rootWritten := false; backup := 0; + repeat + write_label := load_boot(volume,boot); if write_label <> eNone then break; + br := PBootRecord(boot); + write_label := locate_label(volume,br,scratch,sector,offset,found); + if write_label <> eNone then break; + if (sector = 0) and (value[0] <> #0) then begin write_label := eDiskFull; break; end; + if sector <> 0 then begin + write_label := storage_read(volume^.device,sector,1,scratch); if write_label <> eNone then break; + memcpy(uint32(scratch),uint32(rootOld),512); + entry := PDirectory(uint32(scratch)+offset); + if value[0] = #0 then begin + if found then entry^.fileName[0] := char($E5); + end else begin + if entry^.fileName[0] = #0 then begin + { Retain an end marker after a newly inserted label. } + if offset = 480 then begin write_label := eDiskFull; break; end; + puint8(uint32(entry)+32)^ := 0; + end; + memset(uint32(entry),0,32); pack_label(@value[0],@entry^.fileName[0]); entry^.attributes := $08; + end; + end; + if (br^.backupCluster <> 0) and (br^.backupCluster <> $FFFF) then begin + if br^.backupCluster >= br^.rsvSectors then begin write_label := eCorruptFilesystem; break; end; + backup := volume^.sectorStart+br^.backupCluster; + write_label := storage_read(volume^.device,backup,1,backupOld); if write_label <> eNone then break; + end; + memcpy(uint32(boot),uint32(bootOld),512); + if value[0] = #0 then pack_label('NO NAME',pchar(@br^.volumeLabel[0])) + else pack_label(@value[0],pchar(@br^.volumeLabel[0])); + bootWritten := true; + write_label := storage_write(volume^.device,volume^.sectorStart,1,boot); + if write_label <> eNone then break; + bootWritten := true; + if backup <> 0 then begin + { Preserve backup-specific bytes; only update its label. } + memcpy(uint32(backupOld),uint32(boot),512); + if value[0] = #0 then pack_label('NO NAME',pchar(@br^.volumeLabel[0])) + else pack_label(@value[0],pchar(@br^.volumeLabel[0])); + backupWritten := true; + write_label := storage_write(volume^.device,backup,1,boot); + if write_label <> eNone then break; + backupWritten := true; + end; + if (sector <> 0) and (found or (value[0] <> #0)) then begin + rootWritten := true; + write_label := storage_write(volume^.device,sector,1,scratch); + end; + until true; + if write_label = eNone then volume^.labelText := value + else begin + { Best-effort rollback on I/O failure; preserve the original error. } + if rootWritten then storage_write(volume^.device,sector,1,rootOld); + if backupWritten then storage_write(volume^.device,backup,1,backupOld); + if bootWritten then storage_write(volume^.device,volume^.sectorStart,1,bootOld); + end; + kfree(mem); +end; +end. diff --git a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.pas b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.pas index dcd461b4..449cd930 100644 --- a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.pas +++ b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.pas @@ -16,6 +16,7 @@ uses io.syslog, core.util, driver.storage.fs.fat32.vol, + driver.storage.fs.fat32.labels, driver.storage.fs.fat32.core, driver.storage.fs.fat32.transfer; @@ -36,7 +37,8 @@ begin filesystem.readDirCallback := @FAT32ReadDirectory; filesystem.createDirCallback := @FAT32CreateDirectory; filesystem.createCallback := @create_volume; - filesystem.createAsyncCallback := @create_volume_async; + filesystem.validateFormat := @validate_format; + filesystem.createAsyncCallback := nil; { volume manager runs checked formatter in a worker } filesystem.detectCallback := @detect_volumes; filesystem.readOffsetCallback := @FAT32ReadFileAtOffset; filesystem.writeOffsetCallback := @FAT32WriteFileAtOffset; @@ -51,7 +53,9 @@ begin filesystem.closeFileCallback := @FAT32CloseFile; filesystem.createDirAsyncCallback := nil; filesystem.readDirAsyncCallback := nil; - filesystem.formatParamFlags := FS_FORMAT_PARAM_CLUSTER_SIZE; + filesystem.formatParamFlags := FS_FORMAT_PARAM_CLUSTER_SIZE or FS_FORMAT_PARAM_LABEL; + filesystem.validateLabelCallback := @normalize_label; + filesystem.setLabelCallback := @write_label; FAT32VolInit(@filesystem); driver.storage.fs.mgr.register_filesystem(@filesystem); diff --git a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.types.pas b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.types.pas index 38e2bd83..e4eef98c 100644 --- a/src/driver/storage/fs/fat32/driver.storage.fs.fat32.types.pas +++ b/src/driver/storage/fs/fat32/driver.storage.fs.fat32.types.pas @@ -224,6 +224,28 @@ type WriteRefs : array[0..FAT_OPEN_HANDLE_SLOTS - 1] of TFATWriteRef; end; +{ Exclusive cluster bound, constrained by both data area and FAT capacity. } +function FAT32ClusterLimit(br : PBootRecord) : uint32; + implementation +function FAT32ClusterLimit(br : PBootRecord) : uint32; +var overhead, total, capacity, limit : uint64; +begin + FAT32ClusterLimit := 0; + if br = nil then exit; + if (br^.sectorSize <> 512) or (br^.spc = 0) or (br^.spc > 128) or + ((br^.spc and (br^.spc - 1)) <> 0) or (br^.rsvSectors = 0) or + (br^.numFats = 0) or (br^.FATSize = 0) then exit; + total := br^.manySectors; + if total = 0 then total := br^.numSectors; + overhead := uint64(br^.rsvSectors) + uint64(br^.numFats) * br^.FATSize; + if overhead >= total then exit; + limit := (total - overhead) div br^.spc + 2; + capacity := uint64(br^.FATSize) * (br^.sectorSize div 4); + if (limit > capacity) or (limit > $0FFFFFF0) then exit; + FAT32ClusterLimit := uint32(limit); +end; + + end. 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 dd6d7269..4c1021df 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 @@ -1,335 +1,88 @@ unit driver.storage.fs.fat32.vol; -{ - Volume lifecycle functions for the FAT32 filesystem driver. - Handles volume detection, identification, and formatting. -} - interface - -uses - driver.storage.types, - driver.storage.fs.fat32.types; - +uses driver.storage.types, driver.storage.fs.fat32.types; procedure FAT32VolInit(fs : PFilesystem); procedure detect_volumes(disk : PStorage_Device); -function identify_volume(volume : PStorage_Volume) : boolean; -procedure create_volume(volume : PStorage_Volume; sectors : uint32; start : uint32; config : PFSFormatParams); -procedure create_volume_async(volume : PStorage_Volume; sectors : uint32; start : uint32; - config : PFSFormatParams; callback : TIOCallback; callbackData : pointer); +function identify_volume(volume : PStorage_Volume) : boolean; +function validate_format(volume : PStorage_Volume; sectors, start : uint32; config : PFSFormatParams) : TError; +function create_volume(volume : PStorage_Volume; sectors, start : uint32; config : PFSFormatParams) : TError; implementation +uses io.syslog, debug.tracer, memory.heap, core.util, arch.x86.util, + driver.storage.mgr, driver.storage.vol.mgr, driver.storage.fs.fat32.labels; +var fat32FilesystemPtr : PFilesystem = nil; -uses - io.syslog, - debug.tracer, - memory.heap, - core.util, arch.x86.util, - driver.storage.mgr, - driver.storage.vol.mgr, - driver.storage.fs.fat32.core; - -{ ===================== Types ===================== } - -type - TFmtStep = (fmtBootSector, fmtZeroFAT, fmtFATEntries, fmtRootDir, fmtSystemDir, fmtDone); - - PFmtContext = ^TFmtContext; - TFmtContext = record - Volume : PStorage_Volume; - Disk : PStorage_Device; - SectorStart : uint32; - RootCluster : uint32; - Callback : TIOCallback; - CallbackData : pointer; - Buffer : puint32; - ZeroBuffer : puint32; - SPC : uint32; - FATSize : uint32; - FATStart : uint32; - DataStart : uint32; - BatchSize : uint32; - BatchPos : uint32; - Step : TFmtStep; - end; - -{ ===================== Module state ===================== } - -var - fat32FilesystemPtr : PFilesystem = nil; - -{ ===================== Private helpers ===================== } +procedure FAT32VolInit(fs : PFilesystem); +begin fat32FilesystemPtr := fs; end; function bootSectorHasSignature(buffer : puint32) : boolean; begin - bootSectorHasSignature := (puint8(uint32(buffer) + 510)^ = $55) and - (puint8(uint32(buffer) + 511)^ = $AA); + bootSectorHasSignature := (puint8(uint32(buffer)+510)^ = $55) and + (puint8(uint32(buffer)+511)^ = $AA); end; -function clusterToLBA(dataStart : uint32; spc : uint32; cluster : uint32) : uint32; +function formatSPC(config : PFSFormatParams) : uint32; begin - if cluster < 2 then - clusterToLBA := dataStart - else - clusterToLBA := dataStart + ((cluster - 2) * spc); + formatSPC := 8; + if (config <> nil) and ((config^.Flags and FS_FORMAT_PARAM_CLUSTER_SIZE) <> 0) then + formatSPC := config^.ClusterSize; end; -function fatDefaultSPC(sectors : uint32; sectorSize : uint32) : uint32; +function formatFATSize(sectors, spc : uint32) : uint32; begin - if sectorSize = 0 then begin - fatDefaultSPC := 1; - exit; + { Conservative size includes reserved FAT entries; uint64 prevents wrap. } + formatFATSize := uint32(((uint64(sectors - 32) div spc + 2) * 4 + 511) div 512); +end; + +function validate_format(volume : PStorage_Volume; sectors, start : uint32; config : PFSFormatParams) : TError; +var spc, fat : uint32; dataClusters : uint64; labelValue : TVolumeLabel; +begin + validate_format := eInvalidArgument; + if (volume = nil) or (volume^.device = nil) then exit; + if not volume^.device^.writable then begin validate_format := eReadOnly; exit; end; + { Current FAT cache addresses 512-byte sectors. Reject unsupported geometry. } + if volume^.device^.sectorSize <> 512 then exit; + if (sectors <> volume^.sectorCount) or (start <> volume^.sectorStart) or + (start >= volume^.device^.maxSectorCount) or + (sectors > volume^.device^.maxSectorCount - start) or (sectors <= 32) then exit; + if config <> nil then begin + if (config^.Version <> FS_FORMAT_PARAMS_VERSION_1) or + ((config^.Flags and (not (FS_FORMAT_PARAM_CLUSTER_SIZE or FS_FORMAT_PARAM_LABEL))) <> 0) then exit; end; - fatDefaultSPC := 4096 div sectorSize; - if fatDefaultSPC = 0 then - fatDefaultSPC := 1; - if fatDefaultSPC > 64 then - fatDefaultSPC := 64; + if (config <> nil) and ((config^.Flags and FS_FORMAT_PARAM_LABEL) <> 0) then + if normalize_label(@config^.LabelText[0],labelValue) <> eNone then exit; + spc := formatSPC(config); + if (spc = 0) or (spc > 128) or ((spc and (spc-1)) <> 0) then exit; + fat := formatFATSize(sectors, spc); + if uint64(32) + fat + uint64(spc)*2 > sectors then exit; + dataClusters := (uint64(sectors) - 32 - fat) div spc; + if dataClusters + 2 > $0FFFFFF0 then exit; + validate_format := eNone; end; -function fatComputeFATSize(sectors : uint32; spc : uint32; sectorSize : uint32) : uint32; -var - clusterCount : uint32; - fatBytes : uint32; +function countFreeFATClusters(volume : PStorage_Volume; br : PBootRecord) : uint32; +var limit, sectorIdx, entryIdx, clusterNum, count : uint32; buffer : puint32; begin - if spc = 0 then spc := 1; - if sectorSize = 0 then begin - fatComputeFATSize := 0; - exit; + countFreeFATClusters := $FFFFFFFF; + limit := FAT32ClusterLimit(br); + if limit <= 2 then exit; + buffer := puint32(kalloc(512)); + if buffer = nil then exit; + 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 + kfree(buffer); exit; + end; + for entryIdx := 0 to 127 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); end; - clusterCount := (sectors + spc - 1) div spc; - fatBytes := clusterCount * 4; - fatComputeFATSize := (fatBytes + sectorSize - 1) div sectorSize; - if fatComputeFATSize = 0 then - fatComputeFATSize := 1; -end; - -function fatFormatConfigSPC(config : PFSFormatParams; sectors : uint32; sectorSize : uint32) : uint32; -begin - fatFormatConfigSPC := 0; - if (config <> nil) and - (config^.Version = FS_FORMAT_PARAMS_VERSION_1) and - ((config^.Flags and FS_FORMAT_PARAM_CLUSTER_SIZE) <> 0) then - fatFormatConfigSPC := config^.ClusterSize; - if fatFormatConfigSPC = 0 then - fatFormatConfigSPC := fatDefaultSPC(sectors, sectorSize); - if fatFormatConfigSPC = 0 then - fatFormatConfigSPC := 1; -end; - -{ Counts free FAT clusters by scanning the FAT directly (no cache). - Used during detect/identify to compute freeSectors. } -function countFreeFATClusters(volume : PStorage_Volume; bootRecord : PBootRecord) : uint32; -var - fatStart : uint32; - maxCluster : uint32; - entriesPerSect : uint32; - sectorIdx : uint32; - entryIdx : uint32; - clusterNum : uint32; - freeCount : uint32; - fatSectors : uint32; - fatBuffer : puint32; -begin - countFreeFATClusters := 0; - if (bootRecord^.sectorSize = 0) or (bootRecord^.FATSize = 0) then exit; - - fatStart := volume^.sectorStart + bootRecord^.rsvSectors; - maxCluster := (bootRecord^.FATSize * bootRecord^.sectorSize) div 4; - entriesPerSect := bootRecord^.sectorSize div 4; - fatSectors := bootRecord^.FATSize; - - fatBuffer := puint32(kalloc(bootRecord^.sectorSize)); - if fatBuffer = nil then exit; - freeCount := 0; - - if fatSectors > 0 then - for sectorIdx := 0 to fatSectors - 1 do begin - if sectorIdx * entriesPerSect >= maxCluster then break; - driver.storage.mgr.storage_read(volume^.device, fatStart + sectorIdx, 1, fatBuffer); - if entriesPerSect > 0 then - for entryIdx := 0 to entriesPerSect - 1 do begin - clusterNum := sectorIdx * entriesPerSect + entryIdx; - if clusterNum < 2 then continue; - if clusterNum >= maxCluster then break; - if fatBuffer[entryIdx] = 0 then - freeCount := freeCount + 1; - end; - end; - - kfree(fatBuffer); - countFreeFATClusters := freeCount; -end; - -{ ===================== Async format state machine ===================== } - -procedure fmt_run_next(ctx : PFmtContext); forward; - -procedure fmt_step_complete(error : TError; userdata : pointer); -var - ctx : PFmtContext; -begin - ctx := PFmtContext(userdata); - if error <> eNone then begin - io.syslog.logln('FAT32', 'fmt_step_complete: I/O error — aborting format'); - if ctx^.Buffer <> nil then kfree(ctx^.Buffer); - if ctx^.ZeroBuffer <> nil then kfree(ctx^.ZeroBuffer); - if ctx^.Callback <> nil then - ctx^.Callback(error, ctx^.CallbackData); - kfree(puint32(ctx)); - exit; - end; - - case ctx^.Step of - fmtBootSector: begin - ctx^.Step := fmtZeroFAT; - ctx^.BatchPos := 0; - end; - fmtZeroFAT: begin - ctx^.BatchPos := ctx^.BatchPos + ctx^.BatchSize; - end; - fmtFATEntries: begin - ctx^.Step := fmtRootDir; - end; - fmtRootDir: begin - ctx^.Step := fmtSystemDir; - end; - fmtSystemDir: begin - ctx^.Step := fmtDone; - end; - end; - fmt_run_next(ctx); -end; - -procedure fmt_run_next(ctx : PFmtContext); -var - writeCount : uint32; -begin - case ctx^.Step of - fmtBootSector: begin - io.syslog.logln('FAT32', 'fmt: fmtBootSector'); - driver.storage.mgr.storage_write_async(ctx^.Disk, ctx^.SectorStart, 1, - ctx^.Buffer, @fmt_step_complete, pointer(ctx)); - end; - - fmtZeroFAT: begin - { Transition to next step once the FAT is fully zeroed } - if ctx^.BatchPos >= ctx^.FATSize then begin - io.syslog.logln('FAT32', 'fmt: FAT zero complete, moving to fmtFATEntries'); - kfree(ctx^.ZeroBuffer); - ctx^.ZeroBuffer := nil; - kfree(ctx^.Buffer); - ctx^.Buffer := puint32(kalloc(ctx^.Disk^.sectorSize)); - memset(uint32(ctx^.Buffer), 0, ctx^.Disk^.sectorSize); - { FAT entries: [0]=media end-of-partition, [1]=EOC, [2]=root cluster EOC, [3]=SYSTEM EOC } - puint32(ctx^.Buffer)[0] := $0FFFFFF8; - puint32(ctx^.Buffer)[1] := $0FFFFFFF; - puint32(ctx^.Buffer)[2] := $0FFFFFF8; - puint32(ctx^.Buffer)[3] := $0FFFFFF8; - ctx^.Step := fmtFATEntries; - fmt_run_next(ctx); - exit; - end; - if (ctx^.FATSize - ctx^.BatchPos) >= ctx^.BatchSize then - writeCount := ctx^.BatchSize - else - writeCount := ctx^.FATSize - ctx^.BatchPos; - driver.storage.mgr.storage_write_async(ctx^.Disk, - ctx^.FATStart + ctx^.BatchPos, writeCount, ctx^.ZeroBuffer, - @fmt_step_complete, pointer(ctx)); - end; - - fmtFATEntries: begin - io.syslog.logln('FAT32', 'fmt: fmtFATEntries'); - driver.storage.mgr.storage_write_async(ctx^.Disk, ctx^.FATStart, 1, - ctx^.Buffer, @fmt_step_complete, pointer(ctx)); - end; - - fmtRootDir: begin - io.syslog.logln('FAT32', 'fmt: fmtRootDir'); - memset(uint32(ctx^.Buffer), 0, ctx^.Disk^.sectorSize); - { '.' entry — volume label } - PDirectory(ctx^.Buffer)[0].fileName[0] := '.'; - PDirectory(ctx^.Buffer)[0].fileName[1] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[2] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[3] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[4] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[5] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[6] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[7] := ' '; - PDirectory(ctx^.Buffer)[0].attributes := $08; - PDirectory(ctx^.Buffer)[0].clusterLow := ctx^.RootCluster; - { '..' entry } - PDirectory(ctx^.Buffer)[1].fileName[0] := '.'; - PDirectory(ctx^.Buffer)[1].fileName[1] := '.'; - PDirectory(ctx^.Buffer)[1].fileName[2] := ' '; - PDirectory(ctx^.Buffer)[1].fileName[3] := ' '; - PDirectory(ctx^.Buffer)[1].fileName[4] := ' '; - PDirectory(ctx^.Buffer)[1].fileName[5] := ' '; - PDirectory(ctx^.Buffer)[1].fileName[6] := ' '; - PDirectory(ctx^.Buffer)[1].fileName[7] := ' '; - PDirectory(ctx^.Buffer)[1].attributes := $10; - PDirectory(ctx^.Buffer)[1].clusterLow := ctx^.RootCluster; - { Pre-allocated SYSTEM dir entry (cluster 3) } - PDirectory(ctx^.Buffer)[2].fileName[0] := 'S'; - PDirectory(ctx^.Buffer)[2].fileName[1] := 'Y'; - PDirectory(ctx^.Buffer)[2].fileName[2] := 'S'; - PDirectory(ctx^.Buffer)[2].fileName[3] := 'T'; - PDirectory(ctx^.Buffer)[2].fileName[4] := 'E'; - PDirectory(ctx^.Buffer)[2].fileName[5] := 'M'; - PDirectory(ctx^.Buffer)[2].fileName[6] := ' '; - PDirectory(ctx^.Buffer)[2].fileName[7] := ' '; - PDirectory(ctx^.Buffer)[2].attributes := $10; - PDirectory(ctx^.Buffer)[2].clusterLow := 3; - driver.storage.mgr.storage_write_async(ctx^.Disk, - clusterToLBA(ctx^.DataStart, ctx^.SPC, ctx^.RootCluster), 1, ctx^.Buffer, - @fmt_step_complete, pointer(ctx)); - end; - - fmtSystemDir: begin - io.syslog.logln('FAT32', 'fmt: fmtSystemDir'); - memset(uint32(ctx^.Buffer), 0, ctx^.Disk^.sectorSize); - PDirectory(ctx^.Buffer)[0].fileName[0] := '.'; - PDirectory(ctx^.Buffer)[0].fileName[1] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[2] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[3] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[4] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[5] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[6] := ' '; - PDirectory(ctx^.Buffer)[0].fileName[7] := ' '; - PDirectory(ctx^.Buffer)[0].attributes := $10; - PDirectory(ctx^.Buffer)[0].clusterLow := 3; - PDirectory(ctx^.Buffer)[1].fileName[0] := '.'; - PDirectory(ctx^.Buffer)[1].fileName[1] := '.'; - PDirectory(ctx^.Buffer)[1].fileName[2] := ' '; - PDirectory(ctx^.Buffer)[1].fileName[3] := ' '; - PDirectory(ctx^.Buffer)[1].fileName[4] := ' '; - PDirectory(ctx^.Buffer)[1].fileName[5] := ' '; - PDirectory(ctx^.Buffer)[1].fileName[6] := ' '; - PDirectory(ctx^.Buffer)[1].fileName[7] := ' '; - PDirectory(ctx^.Buffer)[1].attributes := $10; - PDirectory(ctx^.Buffer)[1].clusterLow := ctx^.RootCluster; - driver.storage.mgr.storage_write_async(ctx^.Disk, - clusterToLBA(ctx^.DataStart, ctx^.SPC, 3), 1, ctx^.Buffer, - @fmt_step_complete, pointer(ctx)); - end; - - fmtDone: begin - io.syslog.logln('FAT32', 'fmt: fmtDone — format complete'); - kfree(ctx^.Buffer); - ctx^.Buffer := nil; - if ctx^.Callback <> nil then - ctx^.Callback(eNone, ctx^.CallbackData); - kfree(puint32(ctx)); - end; - end; -end; - -{ ===================== Exported procedures ===================== } - -procedure FAT32VolInit(fs : PFilesystem); -begin - fat32FilesystemPtr := fs; + kfree(buffer); + countFreeFATClusters := count; end; procedure detect_volumes(disk : PStorage_Device); @@ -338,6 +91,7 @@ var bufSize : uint32; volume : PStorage_Volume; br : PBootRecord; + freeClusters : uint32; begin push_trace('driver.storage.fs.fat32.detect_volumes'); if fat32FilesystemPtr = nil then exit; @@ -354,10 +108,11 @@ begin exit; end; - driver.storage.mgr.storage_read(disk, 0, 1, buffer); + if storage_read(disk, 0, 1, buffer) <> eNone then begin kfree(buffer); exit; end; br := PBootRecord(buffer); - if bootSectorHasSignature(buffer) and (br^.bsignature = $29) then begin + if bootSectorHasSignature(buffer) and (br^.bsignature = $29) and + (FAT32ClusterLimit(br) > 2) and (br^.manySectors <= disk^.maxSectorCount) then begin io.syslog.logln('FAT32', 'detect_volumes: volume found'); volume := PStorage_Volume(kalloc(sizeof(TStorage_Volume))); if volume = nil then begin @@ -370,8 +125,11 @@ begin volume^.sectorSize := br^.sectorSize; volume^.sectorCount := disk^.maxSectorCount; volume^.filesystem := fat32FilesystemPtr; - volume^.freeSectors := countFreeFATClusters(volume, br) * br^.spc; + freeClusters := countFreeFATClusters(volume, br); + if freeClusters = $FFFFFFFF then begin kfree(void(volume)); kfree(buffer); exit; end; + volume^.freeSectors := freeClusters * br^.spc; volume^.isBootDrive := false; + if read_label(volume) <> eNone then begin kfree(void(volume)); kfree(buffer); exit; end; driver.storage.vol.mgr.register_volume(disk, volume); end; @@ -384,6 +142,7 @@ var br : PBootRecord; bufSize : uint32; fatNameOk : boolean; + freeClusters : uint32; spc : uint8; i : uint32; begin @@ -399,7 +158,7 @@ begin if buffer = nil then exit; memset(uint32(buffer), 0, bufSize); - driver.storage.mgr.storage_read(volume^.device, volume^.sectorStart, 1, buffer); + if storage_read(volume^.device, volume^.sectorStart, 1, buffer) <> eNone then begin kfree(buffer); exit; end; br := PBootRecord(buffer); fatNameOk := true; @@ -421,240 +180,97 @@ begin (br^.rsvSectors > 0) and (br^.FATSize > 0) and (br^.numFats > 0) and - (br^.rootCluster >= 2) then begin + (br^.rootCluster >= 2) and (br^.rootCluster < FAT32ClusterLimit(br)) and + (br^.manySectors <= volume^.sectorCount) then begin io.syslog.logln('FAT32', 'identify_volume: FAT32 signature matched'); - identify_volume := true; - volume^.freeSectors := countFreeFATClusters(volume, br) * br^.spc; + freeClusters := countFreeFATClusters(volume, br); + if freeClusters <> $FFFFFFFF then begin + identify_volume := read_label(volume) = eNone; + volume^.freeSectors := freeClusters * br^.spc; + end; end else io.syslog.logln('FAT32', 'identify_volume: not a FAT32 volume'); kfree(buffer); end; -procedure create_volume(volume : PStorage_Volume; sectors : uint32; start : uint32; config : PFSFormatParams); +{ Every write is checked; a valid boot signature is published last. } +function create_volume(volume : PStorage_Volume; sectors, start : uint32; config : PFSFormatParams) : TError; var - buffer : puint32; - zeroBuffer : puint32; - bootRecord : PBootRecord; - dataStart : uint32; - fatStart : uint32; - FATSize : uint32; - batchSize : uint32; - batchPos : uint32; - rootCluster: uint32; - spc : uint32; - disk : PStorage_Device; - status : uint32; + boot, buffer : puint32; + br : PBootRecord; + dir : PDirectory; + spc, fat, fatStart, dataStart, pos, batch, i : uint32; + err : TError; + labelValue : TVolumeLabel; + function writeSectors(lba, count : uint32; data : puint32) : boolean; + begin + if (lba < start) or (lba - start >= sectors) or (count > sectors - (lba - start)) then + err := eInvalidArgument + else err := storage_write(volume^.device, lba, count, data); + writeSectors := err = eNone; + end; begin - push_trace('driver.storage.fs.fat32.create_volume'); - io.syslog.logln('FAT32', 'create_volume (sync): enter'); - - disk := volume^.device; - rootCluster := 2; - spc := fatFormatConfigSPC(config, sectors, disk^.sectorSize); - - buffer := puint32(kalloc(disk^.sectorSize + 512)); - if buffer = nil then exit; - memset(uint32(buffer), 0, disk^.sectorSize); - - bootRecord := PBootRecord(buffer); - FATSize := fatComputeFATSize(sectors, spc, disk^.sectorSize); - - bootRecord^.jmp2boot := $0; - bootRecord^.OEMName[0] := 'A'; - bootRecord^.OEMName[1] := 'S'; - bootRecord^.OEMName[2] := 'U'; - bootRecord^.OEMName[3] := 'R'; - bootRecord^.OEMName[4] := 'O'; - bootRecord^.OEMName[5] := ' '; - bootRecord^.OEMName[6] := 'V'; - bootRecord^.OEMName[7] := '1'; - bootRecord^.sectorSize := disk^.sectorSize; - bootRecord^.spc := uint8(spc); - bootRecord^.rsvSectors := 32; - bootRecord^.numFats := 1; - bootRecord^.mediaDescp := $F8; - bootRecord^.hiddenSectors := start; - bootRecord^.manySectors := sectors; - bootRecord^.FATSize := FATSize; - bootRecord^.rootCluster := rootCluster; - bootRecord^.FSInfoCluster := 0; - bootRecord^.driveNumber := $80; - bootRecord^.volumeID := 62; - bootRecord^.bsignature := $29; - bootRecord^.identString[0] := 'F'; - bootRecord^.identString[1] := 'A'; - bootRecord^.identString[2] := 'T'; - bootRecord^.identString[3] := '3'; - bootRecord^.identString[4] := '2'; - bootRecord^.identString[5] := ' '; - bootRecord^.identString[6] := ' '; - bootRecord^.identString[7] := ' '; - - puint8(uint32(buffer) + 510)^ := $55; - puint8(uint32(buffer) + 511)^ := $AA; - - driver.storage.mgr.storage_write(disk, start, 1, buffer); - io.syslog.logln('FAT32', 'create_volume (sync): boot sector written'); - - fatStart := start + 32; - dataStart := fatStart + FATSize; - - if FATSize > 128 then - batchSize := 128 - else - batchSize := FATSize; - - zeroBuffer := puint32(kalloc(disk^.sectorSize * batchSize)); - if zeroBuffer = nil then begin - kfree(buffer); - exit; + create_volume := validate_format(volume, sectors, start, config); + if create_volume <> eNone then exit; + memset(uint32(@labelValue[0]),0,sizeof(labelValue)); + if (config <> nil) and ((config^.Flags and FS_FORMAT_PARAM_LABEL) <> 0) then + normalize_label(@config^.LabelText[0],labelValue); + spc := formatSPC(config); fat := formatFATSize(sectors, spc); + fatStart := start + 32; dataStart := fatStart + fat; + boot := puint32(kalloc(512)); buffer := puint32(kalloc(128*512)); + if (boot = nil) or (buffer = nil) then begin + if boot <> nil then kfree(boot); + if buffer <> nil then kfree(buffer); + create_volume := eOutOfMemory; exit; end; - memset(uint32(zeroBuffer), 0, disk^.sectorSize * batchSize); - - batchPos := 0; - while batchPos < FATSize do begin - if (FATSize - batchPos) >= batchSize then - driver.storage.mgr.storage_write(disk, fatStart + batchPos, batchSize, zeroBuffer) - else - driver.storage.mgr.storage_write(disk, fatStart + batchPos, FATSize - batchPos, zeroBuffer); - batchPos := batchPos + batchSize; + memset(uint32(boot), 0, 512); memset(uint32(buffer), 0, 128*512); + br := PBootRecord(boot); + br^.sectorSize := 512; br^.spc := uint8(spc); br^.rsvSectors := 32; + br^.numFats := 1; br^.mediaDescp := $F8; br^.hiddenSectors := start; + br^.manySectors := sectors; br^.FATSize := fat; br^.rootCluster := 2; + br^.driveNumber := $80; br^.volumeID := 62; br^.bsignature := $29; + memcpy(uint32(pchar('ASURO ')), uint32(@br^.OEMName[0]), 8); + memcpy(uint32(pchar('FAT32 ')), uint32(@br^.identString[0]), 8); + memcpy(uint32(pchar('NO NAME ')), uint32(@br^.volumeLabel[0]), 11); + if labelValue[0] <> #0 then begin + memset(uint32(@br^.volumeLabel[0]),ord(' '),11); + i := 0; while (i < 11) and (labelValue[i] <> #0) do begin br^.volumeLabel[i] := ord(labelValue[i]); Inc(i); end; end; - - kfree(zeroBuffer); - io.syslog.logln('FAT32', 'create_volume (sync): FAT zeroed'); - - memset(uint32(buffer), 0, disk^.sectorSize); - puint32(buffer)[0] := $0FFFFFF8; { media descriptor / reserved } - puint32(buffer)[1] := $0FFFFFFF; { end-of-chain marker } - puint32(buffer)[2] := $0FFFFFF8; { root cluster 2 — end-of-chain } - - driver.storage.mgr.storage_write(disk, fatStart, 1, buffer); - io.syslog.logln('FAT32', 'create_volume (sync): FAT entries written'); - - { Write root dir: '.' and '..' entries } - memset(uint32(buffer), 0, disk^.sectorSize); - PDirectory(buffer)[0].fileName[0] := '.'; - PDirectory(buffer)[0].fileName[1] := ' '; - PDirectory(buffer)[0].fileName[2] := ' '; - PDirectory(buffer)[0].fileName[3] := ' '; - PDirectory(buffer)[0].fileName[4] := ' '; - PDirectory(buffer)[0].fileName[5] := ' '; - PDirectory(buffer)[0].fileName[6] := ' '; - PDirectory(buffer)[0].fileName[7] := ' '; - PDirectory(buffer)[0].attributes := $08; - PDirectory(buffer)[0].clusterLow := uint16(rootCluster); - PDirectory(buffer)[1].fileName[0] := '.'; - PDirectory(buffer)[1].fileName[1] := '.'; - PDirectory(buffer)[1].fileName[2] := ' '; - PDirectory(buffer)[1].fileName[3] := ' '; - PDirectory(buffer)[1].fileName[4] := ' '; - PDirectory(buffer)[1].fileName[5] := ' '; - PDirectory(buffer)[1].fileName[6] := ' '; - PDirectory(buffer)[1].fileName[7] := ' '; - PDirectory(buffer)[1].attributes := $10; - PDirectory(buffer)[1].clusterLow := uint16(rootCluster); - driver.storage.mgr.storage_write(disk, clusterToLBA(dataStart, spc, rootCluster), 1, buffer); - io.syslog.logln('FAT32', 'create_volume (sync): root dir written'); - - kfree(buffer); - - { Create the SYSTEM directory via the normal FAT32 path } - status := 0; - FAT32CreateDirectory(volume, '', 'SYSTEM', $10, @status); - io.syslog.logln('FAT32', 'create_volume (sync): done'); -end; - -procedure create_volume_async(volume : PStorage_Volume; sectors : uint32; start : uint32; - config : PFSFormatParams; callback : TIOCallback; callbackData : pointer); -var - ctx : PFmtContext; - spc : uint32; -begin - push_trace('driver.storage.fs.fat32.create_volume_async'); - io.syslog.logln('FAT32', 'create_volume_async: enter'); - - ctx := PFmtContext(kalloc(sizeof(TFmtContext))); - if ctx = nil then begin - io.syslog.logln('FAT32', 'create_volume_async: OOM allocating context'); - if callback <> nil then callback(eOutOfMemory, callbackData); - exit; - end; - memset(uint32(ctx), 0, sizeof(TFmtContext)); - - ctx^.Volume := volume; - ctx^.Disk := volume^.device; - ctx^.SectorStart := start; - ctx^.RootCluster := 2; - ctx^.Callback := callback; - ctx^.CallbackData := callbackData; - - spc := fatFormatConfigSPC(config, sectors, ctx^.Disk^.sectorSize); - ctx^.SPC := spc; - ctx^.FATSize := fatComputeFATSize(sectors, spc, ctx^.Disk^.sectorSize); - ctx^.FATStart := start + 32; - ctx^.DataStart := ctx^.FATStart + ctx^.FATSize; - - ctx^.Buffer := puint32(kalloc(ctx^.Disk^.sectorSize + 512)); - if ctx^.Buffer = nil then begin - if callback <> nil then callback(eOutOfMemory, callbackData); - kfree(puint32(ctx)); - exit; - end; - memset(uint32(ctx^.Buffer), 0, ctx^.Disk^.sectorSize); - - { Write boot sector fields } - PBootRecord(ctx^.Buffer)^.jmp2boot := $0; - PBootRecord(ctx^.Buffer)^.OEMName[0] := 'A'; - PBootRecord(ctx^.Buffer)^.OEMName[1] := 'S'; - PBootRecord(ctx^.Buffer)^.OEMName[2] := 'U'; - PBootRecord(ctx^.Buffer)^.OEMName[3] := 'R'; - PBootRecord(ctx^.Buffer)^.OEMName[4] := 'O'; - PBootRecord(ctx^.Buffer)^.OEMName[5] := ' '; - PBootRecord(ctx^.Buffer)^.OEMName[6] := 'V'; - PBootRecord(ctx^.Buffer)^.OEMName[7] := '1'; - PBootRecord(ctx^.Buffer)^.sectorSize := ctx^.Disk^.sectorSize; - PBootRecord(ctx^.Buffer)^.spc := uint8(spc); - PBootRecord(ctx^.Buffer)^.rsvSectors := 32; - PBootRecord(ctx^.Buffer)^.numFats := 1; - PBootRecord(ctx^.Buffer)^.mediaDescp := $F8; - PBootRecord(ctx^.Buffer)^.hiddenSectors := start; - PBootRecord(ctx^.Buffer)^.manySectors := sectors; - PBootRecord(ctx^.Buffer)^.FATSize := ctx^.FATSize; - PBootRecord(ctx^.Buffer)^.rootCluster := ctx^.RootCluster; - PBootRecord(ctx^.Buffer)^.FSInfoCluster := 0; - PBootRecord(ctx^.Buffer)^.driveNumber := $80; - PBootRecord(ctx^.Buffer)^.volumeID := 62; - PBootRecord(ctx^.Buffer)^.bsignature := $29; - PBootRecord(ctx^.Buffer)^.identString[0] := 'F'; - PBootRecord(ctx^.Buffer)^.identString[1] := 'A'; - PBootRecord(ctx^.Buffer)^.identString[2] := 'T'; - PBootRecord(ctx^.Buffer)^.identString[3] := '3'; - PBootRecord(ctx^.Buffer)^.identString[4] := '2'; - PBootRecord(ctx^.Buffer)^.identString[5] := ' '; - PBootRecord(ctx^.Buffer)^.identString[6] := ' '; - PBootRecord(ctx^.Buffer)^.identString[7] := ' '; - puint8(uint32(ctx^.Buffer) + 510)^ := $55; - puint8(uint32(ctx^.Buffer) + 511)^ := $AA; - - if ctx^.FATSize > 128 then - ctx^.BatchSize := 128 - else - ctx^.BatchSize := ctx^.FATSize; - ctx^.BatchPos := 0; - - ctx^.ZeroBuffer := puint32(kalloc(ctx^.Disk^.sectorSize * ctx^.BatchSize)); - if ctx^.ZeroBuffer = nil then begin - kfree(ctx^.Buffer); - if callback <> nil then callback(eOutOfMemory, callbackData); - kfree(puint32(ctx)); - exit; - end; - memset(uint32(ctx^.ZeroBuffer), 0, ctx^.Disk^.sectorSize * ctx^.BatchSize); - - io.syslog.logln('FAT32', 'create_volume_async: kicking off fmtBootSector'); - ctx^.Step := fmtBootSector; - fmt_run_next(ctx); + puint8(uint32(boot)+510)^ := $55; puint8(uint32(boot)+511)^ := $AA; + err := eNone; + repeat + { Invalidate the old boot signature before modifying allocation metadata. } + if not writeSectors(start, 32, buffer) then break; + pos := 0; + while (pos < fat) and (err = eNone) do begin + batch := fat-pos; if batch > 128 then batch := 128; + writeSectors(fatStart+pos, batch, buffer); pos := pos+batch; + end; + if err <> eNone then break; + buffer[0] := $0FFFFFF8; buffer[1] := $0FFFFFFF; + buffer[2] := $0FFFFFFF; buffer[3] := $0FFFFFFF; + if not writeSectors(fatStart, 1, buffer) then break; + memset(uint32(buffer), 0, 128*512); + dir := PDirectory(buffer); + if labelValue[0] <> #0 then begin + memcpy(uint32(@br^.volumeLabel[0]),uint32(@dir^.fileName[0]),11); + dir^.attributes := $08; dir := @PDirectory(buffer)[1]; + end; + memcpy(uint32(pchar('SYSTEM ')), uint32(@dir^.fileName[0]), 11); + dir^.attributes := $10; dir^.clusterLow := 3; + if not writeSectors(dataStart, spc, buffer) then break; + memset(uint32(buffer), 0, 128*512); + dir := PDirectory(buffer); + memcpy(uint32(pchar('. ')), uint32(@dir[0].fileName[0]), 11); + dir[0].attributes := $10; dir[0].clusterLow := 3; + memcpy(uint32(pchar('.. ')), uint32(@dir[1].fileName[0]), 11); + dir[1].attributes := $10; dir[1].clusterLow := 0; { parent is root } + if not writeSectors(dataStart+spc, spc, buffer) then break; + writeSectors(start, 1, boot); + until true; + kfree(buffer); kfree(boot); + create_volume := err; end; end. diff --git a/src/driver/storage/vol/driver.storage.vol.mbr.pas b/src/driver/storage/vol/driver.storage.vol.mbr.pas index 3e033403..65061952 100644 --- a/src/driver/storage/vol/driver.storage.vol.mbr.pas +++ b/src/driver/storage/vol/driver.storage.vol.mbr.pas @@ -86,7 +86,7 @@ procedure set_bootable(partition_table : PPartition_table); begin push_trace('driver.storage.vol.mbr.set_bootable'); //set the bootble bit in attributes - partition_table^.attributes := (partition_table^.attributes and $80); + partition_table^.attributes := (partition_table^.attributes or $80); end; { Check a partitions bootable bit } @@ -105,7 +105,7 @@ begin partition_table^.LBA_start := address; partition_table^.sector_count := sectorSize; partition_table^.CHS_start := LBA_2_CHS(address); - partition_table^.CHS_start := LBA_2_CHS(address + sectorSize); + partition_table^.CHS_end := LBA_2_CHS(address + sectorSize - 1); push_trace('driver.storage.vol.mbr.setup_partition.end'); end; diff --git a/src/driver/storage/vol/driver.storage.vol.mgr.pas b/src/driver/storage/vol/driver.storage.vol.mgr.pas index 9f27918c..c00c417e 100644 --- a/src/driver/storage/vol/driver.storage.vol.mgr.pas +++ b/src/driver/storage/vol/driver.storage.vol.mgr.pas @@ -62,9 +62,9 @@ procedure create_volume_from_partition(device : PStorage_Device; sectorStart : u { --- Partition operations (read/write mbr + update volume list) --- } function get_partition(device : PStorage_Device; index : uint32) : TPartition_table; -procedure add_partition(device : PStorage_Device; slot : uint32; partition : TPartition_table); -procedure remove_partition(device : PStorage_Device; slot : uint32); -procedure init_disk(device : PStorage_Device); +function add_partition(device : PStorage_Device; slot : uint32; partition : TPartition_table) : TError; +function remove_partition(device : PStorage_Device; slot : uint32) : TError; +function init_disk(device : PStorage_Device) : TError; procedure discover_volumes(device : PStorage_Device); function find_free_space(device : PStorage_Device; needed : uint32) : uint32; function find_free_slot(device : PStorage_Device) : sint32; @@ -80,7 +80,12 @@ procedure init_disk_async(device : PStorage_Device; { --- Filesystem operations --- } function format_volume(device : PStorage_Device; volIndex : uint32; filesystemName : pchar; config : PFSFormatParams) : boolean; +{ False: no callback, caller retains callbackData. + True: worker owns completion and invokes the callback exactly once. } function format_volume_async(device : PStorage_Device; volIndex : uint32; filesystemName : pchar; config : PFSFormatParams; callback : TIOCallback; callbackData : pointer) : boolean; +{ eNone transfers completion to a worker; other returns never invoke callback. } +function set_volume_label_async(device : PStorage_Device; volIndex : uint32; text : pchar; + callback : TIOCallback; data : pointer) : TError; procedure delete_volume(volume : PStorage_Volume); implementation @@ -91,6 +96,13 @@ uses proc.types; type + PLabelWork = ^TLabelWork; + TLabelWork = record + Volume : PStorage_Volume; + Value : TVolumeLabel; + Callback : TIOCallback; + Data : pointer; + end; PStorageLifecycleWatch = ^TStorageLifecycleWatch; TStorageLifecycleWatch = record Active : boolean; @@ -116,6 +128,7 @@ const var LifecycleWatches : array[0..MAX_STORAGE_LIFECYCLE_WATCHES - 1] of TStorageLifecycleWatch; NextLifecycleWatchID : uint32; + NextVolumeID : uint32; procedure notify_lifecycle(event : TStorageLifecycleEvent; device : PStorage_Device; volume : PStorage_Volume; error : TError); var @@ -152,7 +165,7 @@ begin end; end; -procedure auto_mount_volume(volume : PStorage_Volume); +function auto_mount_volume(volume : PStorage_Volume) : TError; var volIdx : sint32; volNumStr : pchar; @@ -160,13 +173,14 @@ var prefix : pchar; mountPath : pchar; begin + auto_mount_volume := eDeviceNotReady; if volume = nil then exit; if volume^.filesystem = nil then exit; volIdx := find_volume_index(volume); if volIdx < 0 then exit; - volNumStr := intToString(uint32(volIdx)); + volNumStr := intToString(volume^.id); volName := stringConcat('vol', volNumStr); kfree(void(volNumStr)); prefix := stringNew(6); @@ -178,9 +192,12 @@ begin prefix[5] := '/'; mountPath := stringConcat(prefix, volName); - driver.storage.vfs.mountVolume(mountPath, volume); - if volume^.isBootDrive then - driver.storage.vfs.mountVolume('/boot', volume); + if driver.storage.vfs.mountVolume(mountPath, volume) = pvRegistered then begin + auto_mount_volume := eNone; + if volume^.isBootDrive then + if driver.storage.vfs.mountVolume('/boot', volume) <> pvRegistered then + auto_mount_volume := eDeviceNotReady; + end; kfree(void(volName)); kfree(void(prefix)); @@ -201,137 +218,71 @@ begin end; end; -procedure update_partition_system_id_sync(device : PStorage_Device; sectorStart : uint32; systemId : uint8); -var - srcMbr : PMaster_Boot_Record; - tempMbr : PMaster_Boot_Record; - slot : sint32; - freeSrcMbr : boolean; +function claim_metadata(device : PStorage_Device) : boolean; +var flags : uint32; begin - if device = nil then exit; - - srcMbr := driver.storage.mgr.get_cached_mbr(device); - freeSrcMbr := false; - if srcMbr = nil then begin - srcMbr := driver.storage.mgr.read_mbr(device); - freeSrcMbr := srcMbr <> nil; + claim_metadata := false; + if (device = nil) or device^.removed or (not device^.writable) then exit; + asm pushfd; cli; pop flags end; + if not device^.metadataBusy then begin + device^.metadataBusy := true; claim_metadata := true; end; - if srcMbr = nil then exit; - - slot := find_partition_slot_by_start(srcMbr, sectorStart); - if slot < 0 then begin - if freeSrcMbr then kfree(void(srcMbr)); - exit; - end; - - if srcMbr^.partition[slot].system_id = systemId then begin - if freeSrcMbr then kfree(void(srcMbr)); - exit; - end; - - tempMbr := PMaster_Boot_Record(kalloc(sizeof(TMaster_Boot_Record))); - if tempMbr = nil then begin - if freeSrcMbr then kfree(void(srcMbr)); - exit; - end; - memcpy(uint32(srcMbr), uint32(tempMbr), sizeof(TMaster_Boot_Record)); - tempMbr^.partition[slot].system_id := systemId; - driver.storage.mgr.write_mbr(device, tempMbr); - kfree(void(tempMbr)); - - if freeSrcMbr then kfree(void(srcMbr)); + if (flags and $200) <> 0 then asm sti end; end; -procedure update_partition_system_id_async(device : PStorage_Device; sectorStart : uint32; systemId : uint8); -var - srcMbr : PMaster_Boot_Record; - tempMbr : PMaster_Boot_Record; - slot : sint32; - freeSrcMbr : boolean; +function update_partition_system_id(device : PStorage_Device; sectorStart : uint32; systemId : uint8) : TError; +var src, copyMbr : PMaster_Boot_Record; slot : sint32; begin - if device = nil then exit; + update_partition_system_id := eNone; + src := get_cached_mbr(device); + if src = nil then begin update_partition_system_id := eDeviceNotReady; exit; end; + slot := find_partition_slot_by_start(src, sectorStart); + if slot < 0 then exit; { whole-device filesystem or GPT, no MBR type to update } + if src^.partition[slot].system_id = MBR_TYPE_GPT_PROTECTIVE then exit; + if src^.partition[slot].system_id = systemId then exit; + copyMbr := PMaster_Boot_Record(kalloc(sizeof(TMaster_Boot_Record))); + if copyMbr = nil then begin update_partition_system_id := eOutOfMemory; exit; end; + memcpy(uint32(src), uint32(copyMbr), sizeof(TMaster_Boot_Record)); + copyMbr^.partition[slot].system_id := systemId; + update_partition_system_id := write_mbr(device, copyMbr); + kfree(void(copyMbr)); +end; - srcMbr := driver.storage.mgr.get_cached_mbr(device); - freeSrcMbr := false; - if srcMbr = nil then begin - srcMbr := driver.storage.mgr.read_mbr(device); - freeSrcMbr := srcMbr <> nil; +function perform_format(vol : PStorage_Volume; fs : PFilesystem; config : PFSFormatParams) : TError; +var err : TError; +begin + perform_format := eFileInUse; + if not driver.storage.vfs.InvalidateVolume(vol) then exit; + vol^.filesystem := fs; + memset(uint32(@vol^.labelText[0]),0,sizeof(vol^.labelText)); + err := fs^.createCallback(vol, vol^.sectorCount, vol^.sectorStart, config); + { Do not identify/remount a partially formatted volume after an error. } + vol^.filesystem := nil; + if err = eNone then err := update_partition_system_id(vol^.device, vol^.sectorStart, fs^.system_id); + if err = eNone then begin + driver.storage.fs.mgr.probe_volume(vol); + if vol^.filesystem <> fs then err := eCorruptFilesystem + else err := auto_mount_volume(vol); end; - if srcMbr = nil then exit; - - slot := find_partition_slot_by_start(srcMbr, sectorStart); - if slot < 0 then begin - if freeSrcMbr then kfree(void(srcMbr)); - exit; + if err <> eNone then begin + driver.storage.vfs.InvalidateVolume(vol); + vol^.filesystem := nil; end; - - if srcMbr^.partition[slot].system_id = systemId then begin - if freeSrcMbr then kfree(void(srcMbr)); - exit; - end; - - tempMbr := PMaster_Boot_Record(kalloc(sizeof(TMaster_Boot_Record))); - if tempMbr = nil then begin - if freeSrcMbr then kfree(void(srcMbr)); - exit; - end; - memcpy(uint32(srcMbr), uint32(tempMbr), sizeof(TMaster_Boot_Record)); - tempMbr^.partition[slot].system_id := systemId; - driver.storage.mgr.write_mbr_async(device, tempMbr, nil, nil); - kfree(void(tempMbr)); - - if freeSrcMbr then kfree(void(srcMbr)); + perform_format := err; end; procedure format_volume_worker(pctx : proc.types.PProcessContext); -var - ctx : PFormatVolumeAsyncCtx; - err : TError; +var ctx : PFormatVolumeAsyncCtx; err : TError; begin ctx := PFormatVolumeAsyncCtx(pctx^.Local); - if ctx = nil then begin - proc.mgr.proc_exit(1); - exit; - end; - - err := eNone; - if (ctx^.Volume = nil) or (ctx^.Filesystem = nil) then - err := eInvalidArgument - else if ctx^.Volume^.device = nil then - err := eDeviceNotFound - else if not ctx^.Volume^.device^.writable then - err := eReadOnly - else if ctx^.Filesystem^.createCallback = nil then - err := eInvalidArgument; - - if err = eNone then begin - if not driver.storage.vfs.InvalidateVolume(ctx^.Volume) then - err := eFileInUse - else begin - update_partition_system_id_sync(ctx^.Volume^.device, ctx^.Volume^.sectorStart, ctx^.Filesystem^.system_id); - ctx^.Volume^.filesystem := ctx^.Filesystem; - ctx^.Filesystem^.createCallback(ctx^.Volume, ctx^.Volume^.sectorCount, ctx^.Volume^.sectorStart, ctx^.Config); - - { Re-probe the freshly formatted volume so the caller gets a verified - filesystem assignment and refreshed free-space metadata. } - ctx^.Volume^.filesystem := nil; - driver.storage.fs.mgr.probe_volume(ctx^.Volume); - if ctx^.Volume^.filesystem <> ctx^.Filesystem then - err := eUnsupportedFilesystem - else - auto_mount_volume(ctx^.Volume); - end; - end; - + if ctx = nil then begin proc.mgr.proc_exit(1); exit; end; + err := perform_format(ctx^.Volume, ctx^.Filesystem, ctx^.Config); set_volume_flag(ctx^.Volume, STORAGE_VOLUME_FLAG_FORMATTING, false); + ctx^.Volume^.device^.metadataBusy := false; notify_lifecycle(sleVolumeFormatCompleted, ctx^.Volume^.device, ctx^.Volume, err); - - if ctx^.Config <> nil then - kfree(void(ctx^.Config)); - if ctx^.UserCallback <> nil then - ctx^.UserCallback(err, ctx^.UserData); - kfree(void(ctx)); - proc.mgr.proc_exit(0); + if ctx^.Config <> nil then kfree(void(ctx^.Config)); + if ctx^.UserCallback <> nil then ctx^.UserCallback(err, ctx^.UserData); + kfree(void(ctx)); proc.mgr.proc_exit(0); end; { Create a TStorage_Volume from a partition entry and register it } @@ -370,70 +321,15 @@ begin end; end; -{ Remove from both global and device volume core.ds.lists, then free } -procedure remove_volume_by_start(device : PStorage_Device; sectorStart : uint32); -var - i : uint32; - v : PStorage_Volume; - found : PStorage_Volume; +procedure detach_volume(volume : PStorage_Volume); +var idx : sint32; begin - found := nil; - if device = nil then exit; - - i := 0; - while i < DL_Size(volumes) do begin - v := PStorage_Volume(Void(DL_Get(volumes, i))^); - if (v^.device = device) and (v^.sectorStart = sectorStart) then begin - found := v; - break; - end else - i := i + 1; - end; - - if found <> nil then begin - set_volume_flag(found, STORAGE_VOLUME_FLAG_INVALIDATING, true); - notify_lifecycle(sleVolumeInvalidating, device, found, eNone); - if not driver.storage.vfs.InvalidateVolume(found) then begin - set_volume_flag(found, STORAGE_VOLUME_FLAG_INVALIDATING, false); - exit; - end; - set_volume_flag(found, STORAGE_VOLUME_FLAG_INVALIDATING, false); - notify_lifecycle(sleVolumeInvalidated, device, found, eNone); - set_volume_flag(found, STORAGE_VOLUME_FLAG_REMOVED, true); - notify_lifecycle(sleVolumeRemoved, device, found, eNone); - remove_volume_from_device_list(device, found); - DL_Delete(volumes, i); - kfree(puint32(found)); - end; -end; - -{ Remove all volumes belonging to a device } -procedure remove_all_volumes_for_device(device : PStorage_Device); -var - i : uint32; - v : PStorage_Volume; -begin - i := 0; - while i < DL_Size(volumes) do begin - v := PStorage_Volume(Void(DL_Get(volumes, i))^); - if v^.device = device then begin - set_volume_flag(v, STORAGE_VOLUME_FLAG_INVALIDATING, true); - notify_lifecycle(sleVolumeInvalidating, device, v, eNone); - if not driver.storage.vfs.InvalidateVolume(v) then begin - set_volume_flag(v, STORAGE_VOLUME_FLAG_INVALIDATING, false); - i := i + 1; - continue; - end; - set_volume_flag(v, STORAGE_VOLUME_FLAG_INVALIDATING, false); - notify_lifecycle(sleVolumeInvalidated, device, v, eNone); - set_volume_flag(v, STORAGE_VOLUME_FLAG_REMOVED, true); - notify_lifecycle(sleVolumeRemoved, device, v, eNone); - DL_Delete(volumes, i); - remove_volume_from_device_list(device, v); - kfree(puint32(v)); - end else - i := i + 1; - end; + idx := find_volume_index(volume); + if idx < 0 then exit; + notify_lifecycle(sleVolumeRemoved, volume^.device, volume, eNone); + remove_volume_from_device_list(volume^.device, volume); + DL_Delete(volumes, uint32(idx)); + kfree(void(volume)); end; { ===================== Lifecycle ===================== } @@ -444,6 +340,7 @@ begin volumes := DL_New(sizeof(Pointer)); memset(uint32(@LifecycleWatches[0]), 0, sizeof(LifecycleWatches)); NextLifecycleWatchID := 1; + NextVolumeID := 0; end; function watch_lifecycle(callback : TStorageLifecycleCallback; userdata : pointer) : uint32; @@ -522,6 +419,7 @@ var begin push_trace('driver.storage.vol.mgr.register_volume'); if device = nil then exit; + volume^.id := NextVolumeID; Inc(NextVolumeID); volPtr := volume; DL_Add(volumes); DL_Set(volumes, DL_Size(volumes) - 1, puint32(@volPtr)); @@ -571,71 +469,128 @@ begin get_partition := mbr^.partition[index]; end; -{ Write a partition entry to the mbr and create a matching volume } -procedure add_partition(device : PStorage_Device; slot : uint32; partition : TPartition_table); +{ kind: 0=add, 1=remove, 2=initialize. Caller owns metadataBusy. } +function partition_operation(device : PStorage_Device; kind, slot : uint32; const part : TPartition_table) : TError; var - bootrecord : PMaster_Boot_Record; + mbr, src : PMaster_Boot_Record; + newVol, victim, vol : PStorage_Volume; + i, j : uint32; + err : TError; + partEnd, otherEnd : uint64; begin - push_trace('driver.storage.vol.mgr.add_partition'); - if (device = nil) or (slot > 3) or (not device^.writable) then exit; - - { Write to mbr } - bootrecord := driver.storage.mgr.read_mbr(device); - if bootrecord = nil then exit; - bootrecord^.partition[slot] := partition; - driver.storage.mgr.write_mbr(device, bootrecord); - kfree(puint32(bootrecord)); - - { Create volume in memory } - if (partition.LBA_start <> 0) and (partition.sector_count <> 0) then - create_volume_from_partition(device, partition.LBA_start, partition.sector_count); + partition_operation := eInvalidArgument; + if (device = nil) or (not device^.writable) or device^.removed or (slot > 3) then exit; + src := get_cached_mbr(device); + if src = nil then begin partition_operation := eDeviceNotReady; exit; end; + { GPT partition editing is not implemented; never mutate its protective MBR. } + for i := 0 to 3 do + if src^.partition[i].system_id = MBR_TYPE_GPT_PROTECTIVE then begin + partition_operation := eNotSupported; exit; + end; + newVol := nil; victim := nil; + if kind = 0 then begin + if (part.LBA_start = 0) or (part.sector_count = 0) or + (part.LBA_start >= device^.maxSectorCount) or + (part.sector_count > device^.maxSectorCount-part.LBA_start) then exit; + if src^.boot_sector = $AA55 then begin + if (src^.partition[slot].LBA_start <> 0) or (src^.partition[slot].sector_count <> 0) then begin + partition_operation := eAlreadyExists; exit; + end; + partEnd := uint64(part.LBA_start) + part.sector_count; + for i := 0 to 3 do begin + otherEnd := uint64(src^.partition[i].LBA_start) + src^.partition[i].sector_count; + if (src^.partition[i].sector_count <> 0) and + (part.LBA_start < otherEnd) and (src^.partition[i].LBA_start < partEnd) then exit; + end; + end else if (device^.volumes <> nil) and (DL_Size(device^.volumes) <> 0) then begin + partition_operation := eFileInUse; exit; + end; + newVol := PStorage_Volume(kalloc(sizeof(TStorage_Volume))); + if newVol = nil then begin partition_operation := eOutOfMemory; exit; end; + memset(uint32(newVol), 0, sizeof(TStorage_Volume)); + newVol^.device := device; newVol^.sectorStart := part.LBA_start; + newVol^.sectorCount := part.sector_count; newVol^.sectorSize := device^.sectorSize; + end; + mbr := PMaster_Boot_Record(kalloc(sizeof(TMaster_Boot_Record))); + if mbr = nil then begin + if newVol <> nil then kfree(void(newVol)); + partition_operation := eOutOfMemory; exit; + end; + memcpy(uint32(src), uint32(mbr), sizeof(TMaster_Boot_Record)); + if (kind = 2) or (mbr^.boot_sector <> $AA55) then begin + memset(uint32(mbr), 0, sizeof(TMaster_Boot_Record)); mbr^.boot_sector := $AA55; + end; + err := eNone; + if kind = 0 then mbr^.partition[slot] := part + else begin + { Refuse teardown before touching the partition entry. metadataBusy + prevents another format/delete from taking ownership of these objects. } + i := 0; + while i < DL_Size(volumes) do begin + vol := get_volume(i); + if (vol^.device = device) and ((kind = 2) or + (vol^.sectorStart = src^.partition[slot].LBA_start)) then begin + if volume_is_busy(vol) then begin err := eFileInUse; break; end; + if not driver.storage.vfs.InvalidateVolume(vol) then begin err := eFileInUse; break; end; + if kind = 1 then victim := vol; + end; + Inc(i); + end; + if (kind = 1) and (err = eNone) then + memset(uint32(@mbr^.partition[slot]), 0, sizeof(TPartition_table)); + end; + if err = eNone then err := write_mbr(device, mbr); + kfree(void(mbr)); + if err = eNone then begin + if kind = 0 then register_volume(device, newVol) + else if kind = 1 then begin if victim <> nil then detach_volume(victim); end + else begin + i := 0; + while i < DL_Size(volumes) do begin + vol := get_volume(i); + if vol^.device = device then detach_volume(vol) else Inc(i); + end; + end; + end else begin + if newVol <> nil then kfree(void(newVol)); + { Keep objects/cache on failure; remount anything invalidated above. } + i := 0; + while i < DL_Size(volumes) do begin + vol := get_volume(i); + if (vol^.device = device) and (vol^.filesystem <> nil) and + ((vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_MOUNTED) = 0) then auto_mount_volume(vol); + Inc(i); + end; + end; + partition_operation := err; end; -{ Zero a partition entry in the mbr and remove the matching volume } -procedure remove_partition(device : PStorage_Device; slot : uint32); -var - bootrecord : PMaster_Boot_Record; - oldPart : TPartition_table; - emptyPart : TPartition_table; +function add_partition(device : PStorage_Device; slot : uint32; partition : TPartition_table) : TError; begin - push_trace('driver.storage.vol.mgr.remove_partition'); - if (device = nil) or (slot > 3) or (not device^.writable) then exit; - - { Read old entry so we know which volume to remove } - bootrecord := driver.storage.mgr.read_mbr(device); - if bootrecord = nil then exit; - oldPart := bootrecord^.partition[slot]; - - { Zero the slot } - memset(uint32(@emptyPart), 0, sizeof(TPartition_table)); - bootrecord^.partition[slot] := emptyPart; - driver.storage.mgr.write_mbr(device, bootrecord); - kfree(puint32(bootrecord)); - - { Remove the corresponding volume } - if (oldPart.LBA_start <> 0) and (oldPart.sector_count <> 0) then - remove_volume_by_start(device, oldPart.LBA_start); + add_partition := eFileInUse; + if not claim_metadata(device) then exit; + add_partition := partition_operation(device, 0, slot, partition); + device^.metadataBusy := false; end; -{ Wipe mbr (fresh signature) and remove all volumes for this device } -procedure init_disk(device : PStorage_Device); -var - bootrecord : PMaster_Boot_Record; +function remove_partition(device : PStorage_Device; slot : uint32) : TError; +var part : TPartition_table; begin - push_trace('driver.storage.vol.mgr.init_disk'); - if device = nil then exit; - if not device^.writable then exit; + remove_partition := eFileInUse; + if not claim_metadata(device) then exit; + memset(uint32(@part), 0, sizeof(part)); + remove_partition := partition_operation(device, 1, slot, part); + device^.metadataBusy := false; +end; - { Remove all volumes first } - remove_all_volumes_for_device(device); - - { Write clean mbr } - bootrecord := PMaster_Boot_Record(kalloc(sizeof(TMaster_Boot_Record))); - if bootrecord = nil then exit; - memset(uint32(bootrecord), 0, sizeof(TMaster_Boot_Record)); - bootrecord^.boot_sector := $AA55; - driver.storage.mgr.write_mbr(device, bootrecord); - kfree(puint32(bootrecord)); +function init_disk(device : PStorage_Device) : TError; +var part : TPartition_table; +begin + init_disk := eFileInUse; + if not claim_metadata(device) then exit; + memset(uint32(@part), 0, sizeof(part)); + init_disk := partition_operation(device, 2, 0, part); + device^.metadataBusy := false; end; { Read mbr and create volumes for all non-empty partitions. @@ -779,17 +734,24 @@ begin mbr := driver.storage.mgr.get_cached_mbr(device); if mbr = nil then exit; + if device^.maxSectorCount <= 1 then exit; + if mbr^.boot_sector <> $AA55 then begin + if needed <= device^.maxSectorCount-1 then find_free_space := 1; + exit; + end; nextLBA := 1; for i := 0 to 3 do begin if mbr^.partition[i].LBA_start <> 0 then begin + if (mbr^.partition[i].LBA_start >= device^.maxSectorCount) or + (mbr^.partition[i].sector_count > device^.maxSectorCount-mbr^.partition[i].LBA_start) then exit; partEnd := mbr^.partition[i].LBA_start + mbr^.partition[i].sector_count; if partEnd > nextLBA then nextLBA := partEnd; end; end; - if (nextLBA + needed) <= device^.maxSectorCount then + if (nextLBA <= device^.maxSectorCount) and (needed <= device^.maxSectorCount-nextLBA) then find_free_space := nextLBA; end; @@ -804,6 +766,7 @@ begin mbr := driver.storage.mgr.get_cached_mbr(device); if mbr = nil then exit; + if mbr^.boot_sector <> $AA55 then begin find_free_slot := 0; exit; end; for i := 0 to 3 do begin if (mbr^.partition[i].LBA_start = 0) and @@ -831,225 +794,183 @@ end; { ===================== Async partition operations ===================== } -{ Modify cached mbr in-place, create volume in memory, fire-and-forget write. - All in-memory changes happen synchronously so the caller can refresh UI - immediately. The disk write completes asynchronously. } +type + PPartitionWork = ^TPartitionWork; + TPartitionWork = record + Device : PStorage_Device; + Kind, Slot : uint32; + Part : TPartition_table; + Callback : TIOCallback; + Data : pointer; + end; + +procedure partition_worker(pctx : proc.types.PProcessContext); +var ctx : PPartitionWork; err : TError; +begin + ctx := PPartitionWork(pctx^.Local); + err := partition_operation(ctx^.Device, ctx^.Kind, ctx^.Slot, ctx^.Part); + ctx^.Device^.metadataBusy := false; + if ctx^.Callback <> nil then ctx^.Callback(err, ctx^.Data); + kfree(void(ctx)); proc.mgr.proc_exit(0); +end; + +procedure submit_partition(device : PStorage_Device; kind, slot : uint32; const part : TPartition_table; + callback : TIOCallback; data : pointer); +var ctx : PPartitionWork; +begin + if not claim_metadata(device) then begin + if callback <> nil then callback(eFileInUse, data); exit; + end; + ctx := PPartitionWork(kalloc(sizeof(TPartitionWork))); + if ctx <> nil then begin + ctx^.Device := device; ctx^.Kind := kind; ctx^.Slot := slot; + ctx^.Part := part; ctx^.Callback := callback; ctx^.Data := data; + if proc.mgr.create('vol.part', @partition_worker, void(ctx), 1) <> nil then exit; + kfree(void(ctx)); + end; + device^.metadataBusy := false; + if callback <> nil then callback(eOutOfMemory, data); +end; + procedure add_partition_async(device : PStorage_Device; slot : uint32; partition : TPartition_table; callback : TIOCallback; callbackData : pointer); -var - mbr : PMaster_Boot_Record; - vol : PStorage_Volume; -begin - push_trace('driver.storage.vol.mgr.add_partition_async'); - if (device = nil) or (slot > 3) or (not device^.writable) then begin - if callback <> nil then callback(eInvalidArgument, callbackData); - exit; - end; - - mbr := driver.storage.mgr.get_cached_mbr(device); - if mbr = nil then begin - if callback <> nil then callback(eDeviceNotReady, callbackData); - exit; - end; - - { Modify cache in-place } - mbr^.partition[slot] := partition; - - { Create volume in memory — skip probe_volume because - (a) the partition is freshly created / unformatted, and - (b) probe_volume does blocking I/O which is unsafe here. } - if (partition.LBA_start <> 0) and (partition.sector_count <> 0) then begin - vol := PStorage_Volume(kalloc(sizeof(TStorage_Volume))); - if vol <> nil then begin - vol^.device := device; - vol^.sectorStart := partition.LBA_start; - vol^.sectorCount := partition.sector_count; - vol^.sectorSize := device^.sectorSize; - vol^.freeSectors := 0; - vol^.filesystem := nil; - vol^.isBootDrive := false; - register_volume(device, vol); - end; - end; - - { Write cached mbr to disk through the storage manager's staged MBR path. } - driver.storage.mgr.write_mbr_async(device, mbr, callback, callbackData); -end; +begin submit_partition(device, 0, slot, partition, callback, callbackData); end; procedure remove_partition_async(device : PStorage_Device; slot : uint32; callback : TIOCallback; callbackData : pointer); -var - mbr : PMaster_Boot_Record; - oldPart : TPartition_table; - emptyPart : TPartition_table; +var part : TPartition_table; begin - push_trace('driver.storage.vol.mgr.remove_partition_async'); - if (device = nil) or (slot > 3) or (not device^.writable) then begin - if callback <> nil then callback(eInvalidArgument, callbackData); - exit; - end; - - mbr := driver.storage.mgr.get_cached_mbr(device); - if mbr = nil then begin - if callback <> nil then callback(eDeviceNotReady, callbackData); - exit; - end; - - oldPart := mbr^.partition[slot]; - - { Zero the slot in cache } - memset(uint32(@emptyPart), 0, sizeof(TPartition_table)); - mbr^.partition[slot] := emptyPart; - - { Remove the corresponding volume from memory } - if (oldPart.LBA_start <> 0) and (oldPart.sector_count <> 0) then - remove_volume_by_start(device, oldPart.LBA_start); - - { Write cached mbr to disk through the storage manager's staged MBR path. } - driver.storage.mgr.write_mbr_async(device, mbr, callback, callbackData); + memset(uint32(@part), 0, sizeof(part)); + submit_partition(device, 1, slot, part, callback, callbackData); end; -procedure init_disk_async(device : PStorage_Device; - callback : TIOCallback; callbackData : pointer); -var - cache : puint32; +procedure init_disk_async(device : PStorage_Device; callback : TIOCallback; callbackData : pointer); +var part : TPartition_table; begin - push_trace('driver.storage.vol.mgr.init_disk_async'); - if (device = nil) or (not device^.writable) then begin - if callback <> nil then callback(eInvalidArgument, callbackData); - exit; - end; - - { Remove all volumes first } - remove_all_volumes_for_device(device); - - { Create fresh cached mbr } - if device^.cachedMBR <> nil then - kfree(device^.cachedMBR); - cache := puint32(kalloc(sizeof(TMaster_Boot_Record))); - if cache = nil then begin - device^.cachedMBR := nil; - if callback <> nil then callback(eOutOfMemory, callbackData); - exit; - end; - memset(uint32(cache), 0, sizeof(TMaster_Boot_Record)); - PMaster_Boot_Record(cache)^.boot_sector := $AA55; - device^.cachedMBR := pointer(cache); - - { Write to disk through the storage manager's staged MBR path. } - driver.storage.mgr.write_mbr_async(device, PMaster_Boot_Record(cache), callback, callbackData); + memset(uint32(@part), 0, sizeof(part)); + submit_partition(device, 2, 0, part, callback, callbackData); end; { ===================== Filesystem operations ===================== } -function format_volume(device : PStorage_Device; volIndex : uint32; filesystemName : pchar; config : PFSFormatParams) : boolean; -var - vol : PStorage_Volume; - fs : PFilesystem; +function prepare_format(device : PStorage_Device; volIndex : uint32; name : pchar; config : PFSFormatParams; + var vol : PStorage_Volume; var fs : PFilesystem) : boolean; begin - push_trace('driver.storage.vol.mgr.format_volume'); - format_volume := false; - - vol := get_volume(volIndex); - if vol = nil then exit; - if vol^.device <> device then exit; - - fs := driver.storage.fs.mgr.find_filesystem_by_name(filesystemName); - if fs = nil then exit; - if fs^.createCallback = nil then exit; - if volume_is_busy(vol) then exit; - - set_volume_flag(vol, STORAGE_VOLUME_FLAG_FORMATTING, true); - notify_lifecycle(sleVolumeFormatStarted, device, vol, eNone); - if not driver.storage.vfs.InvalidateVolume(vol) then begin - set_volume_flag(vol, STORAGE_VOLUME_FLAG_FORMATTING, false); - notify_lifecycle(sleVolumeFormatCompleted, device, vol, eFileInUse); - exit; + prepare_format := false; + if not claim_metadata(device) then exit; + vol := get_volume(volIndex); fs := driver.storage.fs.mgr.find_filesystem_by_name(name); + if (vol <> nil) and (vol^.device = device) and (fs <> nil) then begin + if (fs^.createCallback <> nil) and (fs^.validateFormat <> nil) and (not volume_is_busy(vol)) then begin + if fs^.validateFormat(vol, vol^.sectorCount, vol^.sectorStart, config) = eNone then begin + set_volume_flag(vol, STORAGE_VOLUME_FLAG_FORMATTING, true); + prepare_format := true; exit; + end; + end; end; - - update_partition_system_id_sync(device, vol^.sectorStart, fs^.system_id); - vol^.filesystem := fs; - fs^.createCallback(vol, vol^.sectorCount, vol^.sectorStart, config); - vol^.filesystem := nil; - driver.storage.fs.mgr.probe_volume(vol); - format_volume := vol^.filesystem = fs; - if format_volume then - auto_mount_volume(vol); - set_volume_flag(vol, STORAGE_VOLUME_FLAG_FORMATTING, false); - if format_volume then - notify_lifecycle(sleVolumeFormatCompleted, device, vol, eNone) - else - notify_lifecycle(sleVolumeFormatCompleted, device, vol, eUnsupportedFilesystem); + device^.metadataBusy := false; end; +function format_volume(device : PStorage_Device; volIndex : uint32; filesystemName : pchar; config : PFSFormatParams) : boolean; +var vol : PStorage_Volume; fs : PFilesystem; err : TError; +begin + format_volume := false; + if not prepare_format(device, volIndex, filesystemName, config, vol, fs) then exit; + notify_lifecycle(sleVolumeFormatStarted, device, vol, eNone); + err := perform_format(vol, fs, config); + set_volume_flag(vol, STORAGE_VOLUME_FLAG_FORMATTING, false); + device^.metadataBusy := false; + notify_lifecycle(sleVolumeFormatCompleted, device, vol, err); + format_volume := err = eNone; +end; + +{ False: caller still owns callbackData and NO callback has fired. + True: worker owns it and invokes exactly one callback. } function format_volume_async(device : PStorage_Device; volIndex : uint32; filesystemName : pchar; config : PFSFormatParams; callback : TIOCallback; callbackData : pointer) : boolean; -var - vol : PStorage_Volume; - fs : PFilesystem; - ctx : PFormatVolumeAsyncCtx; - cfgCopy : PFSFormatParams; +var vol : PStorage_Volume; fs : PFilesystem; ctx : PFormatVolumeAsyncCtx; begin - push_trace('driver.storage.vol.mgr.format_volume_async'); format_volume_async := false; - - vol := get_volume(volIndex); - if vol = nil then exit; - if vol^.device <> device then exit; - - fs := driver.storage.fs.mgr.find_filesystem_by_name(filesystemName); - if fs = nil then exit; - if fs^.createCallback = nil then exit; - if volume_is_busy(vol) then begin - if callback <> nil then callback(eFileInUse, callbackData); - exit; - end; - - cfgCopy := nil; - if config <> nil then begin - cfgCopy := PFSFormatParams(kalloc(sizeof(TFSFormatParams))); - if cfgCopy = nil then begin - if callback <> nil then callback(eOutOfMemory, callbackData); - exit; - end; - memcpy(uint32(config), uint32(cfgCopy), sizeof(TFSFormatParams)); - end; - + if not prepare_format(device, volIndex, filesystemName, config, vol, fs) then exit; ctx := PFormatVolumeAsyncCtx(kalloc(sizeof(TFormatVolumeAsyncCtx))); - if ctx = nil then begin - if cfgCopy <> nil then kfree(void(cfgCopy)); - if callback <> nil then callback(eOutOfMemory, callbackData); - exit; - end; - ctx^.Volume := vol; - ctx^.Filesystem := fs; - ctx^.Config := cfgCopy; - ctx^.UserCallback := callback; - ctx^.UserData := callbackData; - - set_volume_flag(vol, STORAGE_VOLUME_FLAG_FORMATTING, true); - notify_lifecycle(sleVolumeFormatStarted, device, vol, eNone); - - if proc.mgr.create('vol.fmt', @format_volume_worker, void(ctx), 1) = nil then begin - set_volume_flag(vol, STORAGE_VOLUME_FLAG_FORMATTING, false); - notify_lifecycle(sleVolumeFormatCompleted, device, vol, eOutOfMemory); - if cfgCopy <> nil then kfree(void(cfgCopy)); + if ctx <> nil then begin + ctx^.Volume := vol; ctx^.Filesystem := fs; + ctx^.UserCallback := callback; ctx^.UserData := callbackData; + ctx^.Config := nil; + if config <> nil then begin + ctx^.Config := PFSFormatParams(kalloc(sizeof(TFSFormatParams))); + if ctx^.Config <> nil then memcpy(uint32(config), uint32(ctx^.Config), sizeof(TFSFormatParams)); + end; + if (config = nil) or (ctx^.Config <> nil) then begin + notify_lifecycle(sleVolumeFormatStarted, device, vol, eNone); + if proc.mgr.create('vol.fmt', @format_volume_worker, void(ctx), 1) <> nil then begin + format_volume_async := true; exit; + end; + end; + if ctx^.Config <> nil then kfree(void(ctx^.Config)); kfree(void(ctx)); - if callback <> nil then callback(eOutOfMemory, callbackData); - exit; end; + set_volume_flag(vol, STORAGE_VOLUME_FLAG_FORMATTING, false); + device^.metadataBusy := false; + notify_lifecycle(sleVolumeFormatCompleted, device, vol, eOutOfMemory); +end; - format_volume_async := true; +procedure label_worker(pctx : proc.types.PProcessContext); +var ctx : PLabelWork; vol : PStorage_Volume; err, mountErr : TError; wasMounted : boolean; +begin + ctx := PLabelWork(pctx^.Local); vol := ctx^.Volume; + wasMounted := (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_MOUNTED) <> 0; + err := eFileInUse; + if (not driver.storage.vfs.VolumeHasOpenHandles(vol)) and driver.storage.vfs.InvalidateVolume(vol) then begin + err := vol^.filesystem^.setLabelCallback(vol,@ctx^.Value[0]); + if wasMounted then begin + mountErr := auto_mount_volume(vol); + if err = eNone then err := mountErr; + end; + end; + vol^.device^.metadataBusy := false; + notify_lifecycle(sleVolumeLabelChanged,vol^.device,vol,err); + if ctx^.Callback <> nil then ctx^.Callback(err,ctx^.Data); + kfree(void(ctx)); proc.mgr.proc_exit(0); +end; + +function set_volume_label_async(device : PStorage_Device; volIndex : uint32; text : pchar; + callback : TIOCallback; data : pointer) : TError; +var vol : PStorage_Volume; ctx : PLabelWork; value : TVolumeLabel; +begin + set_volume_label_async := eInvalidArgument; + if device = nil then exit; + if not device^.writable then begin set_volume_label_async := eReadOnly; exit; end; + set_volume_label_async := eFileInUse; + if not claim_metadata(device) then exit; + repeat + vol := get_volume(volIndex); + if (vol = nil) or (vol^.device <> device) then begin set_volume_label_async := eVolumeNotFound; break; end; + if volume_is_busy(vol) then break; + set_volume_label_async := eUnsupportedFilesystem; + if vol^.filesystem = nil then break; + if (vol^.filesystem^.validateLabelCallback = nil) or (vol^.filesystem^.setLabelCallback = nil) then break; + set_volume_label_async := vol^.filesystem^.validateLabelCallback(text,value); + if set_volume_label_async <> eNone then break; + set_volume_label_async := eOutOfMemory; + ctx := PLabelWork(kalloc(sizeof(TLabelWork))); if ctx = nil then break; + ctx^.Volume := vol; ctx^.Value := value; ctx^.Callback := callback; ctx^.Data := data; + if proc.mgr.create('vol.label',@label_worker,void(ctx),1) <> nil then begin + set_volume_label_async := eNone; exit; + end; + kfree(void(ctx)); + until true; + device^.metadataBusy := false; end; procedure delete_volume(volume : PStorage_Volume); -var - device : PStorage_Device; +var device : PStorage_Device; begin - push_trace('driver.storage.vol.mgr.delete_volume'); if volume = nil then exit; device := volume^.device; - remove_volume_by_start(device, volume^.sectorStart); + if not claim_metadata(device) then exit; + if not volume_is_busy(volume) then + if driver.storage.vfs.InvalidateVolume(volume) then detach_volume(volume); + device^.metadataBusy := false; end; initialization diff --git a/src/test/test.pas b/src/test/test.pas index 5b7b787e..09b5d665 100644 --- a/src/test/test.pas +++ b/src/test/test.pas @@ -12,6 +12,7 @@ implementation uses core.strings, + app.diskutil, driver.bus.usb.types, driver.bus.usb.core, driver.bus.usb.uhci, @@ -32,6 +33,7 @@ uses core.ds.prio, driver.storage.vfs, driver.storage.test, + driver.storage.metatest, core.enc.fnv1a, core.enc.djb2, core.ds.bloom, @@ -46,6 +48,7 @@ procedure runAllTests(); begin { Run unit tests } core.strings.UnitTest; + app.diskutil.UnitTest; driver.bus.usb.types.UnitTest; driver.bus.usb.core.UnitTest; driver.bus.usb.uhci.UnitTest; @@ -66,6 +69,7 @@ begin core.ds.prio.UnitTest; driver.storage.vfs.UnitTest; driver.storage.test.UnitTest; + driver.storage.metatest.UnitTest; core.enc.fnv1a.UnitTest; core.enc.djb2.UnitTest; core.ds.bloom.UnitTest;