600 lines
16 KiB
ObjectPascal
600 lines
16 KiB
ObjectPascal
{
|
|
Prog->PartCmd - Command-line tools for partition table management.
|
|
|
|
Shell command: PART [subcommand]
|
|
Subcommands:
|
|
list <disk> - Show partition table
|
|
add <disk> [size] - Add a partition (auto-inits mbr;
|
|
uses all remaining space if size
|
|
omitted; accepts B, KB, MB, GB)
|
|
rm <disk> <slot> - Remove a partition by slot number
|
|
format <disk> <slot> <fsname> - Format a partition with a filesystem
|
|
|
|
@author(Aaron Hance <[email protected]>)
|
|
}
|
|
unit app.partcmd;
|
|
|
|
interface
|
|
|
|
procedure init();
|
|
|
|
implementation
|
|
|
|
uses
|
|
driver.storage.fs.mgr,
|
|
core.ds.lists,
|
|
memory.heap,
|
|
driver.storage.vol.mbr,
|
|
io.stdio,
|
|
driver.storage.mgr,
|
|
driver.storage.types,
|
|
core.strings,
|
|
debug.tracer,
|
|
driver.storage.vol.mgr;
|
|
|
|
var
|
|
g_out : POutBuf;
|
|
|
|
{ ---------- helpers ---------- }
|
|
|
|
function wnd : uint32;
|
|
begin
|
|
wnd := 0;
|
|
end;
|
|
|
|
function str2int(s : pchar) : uint32;
|
|
begin
|
|
str2int := stringToInt(s);
|
|
end;
|
|
|
|
function isNumeric(s : pchar) : boolean;
|
|
var
|
|
i : uint32;
|
|
len : uint32;
|
|
begin
|
|
isNumeric := false;
|
|
len := stringSize(s);
|
|
if len = 0 then exit;
|
|
for i := 0 to len - 1 do begin
|
|
if (s[i] < '0') or (s[i] > '9') then exit;
|
|
end;
|
|
isNumeric := true;
|
|
end;
|
|
|
|
procedure print(s : pchar);
|
|
begin
|
|
io.stdio.bufWriteStr(g_out, s);
|
|
end;
|
|
|
|
procedure println(s : pchar);
|
|
begin
|
|
io.stdio.bufWriteStrLn(g_out, s);
|
|
end;
|
|
|
|
procedure printint(v : uint32);
|
|
begin
|
|
io.stdio.bufWriteInt(g_out, integer(v));
|
|
end;
|
|
|
|
procedure printhex(v : uint32);
|
|
begin
|
|
io.stdio.bufWriteHex(g_out, v);
|
|
end;
|
|
|
|
procedure printsize(bytes : uint32);
|
|
begin
|
|
if bytes >= 1073741824 then begin
|
|
printint(bytes div 1073741824);
|
|
print(' GB');
|
|
end else if bytes >= 1048576 then begin
|
|
printint(bytes div 1048576);
|
|
print(' MB');
|
|
end else if bytes >= 1024 then begin
|
|
printint(bytes div 1024);
|
|
print(' KB');
|
|
end else begin
|
|
printint(bytes);
|
|
print(' B');
|
|
end;
|
|
end;
|
|
|
|
{ Parse a size string (e.g. '500MB', '2GB', '1024KB', '4096B', '1024')
|
|
into a sector count. Plain numbers are treated as bytes.
|
|
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;
|
|
begin
|
|
parsesize := 0;
|
|
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;
|
|
value := stringToInt(s);
|
|
s[numEnd] := prev;
|
|
bytes := value * 1073741824;
|
|
end else if (prev = 'M') or (prev = 'm') then begin
|
|
numEnd := len - 2;
|
|
s[numEnd] := #0;
|
|
value := stringToInt(s);
|
|
s[numEnd] := prev;
|
|
bytes := value * 1048576;
|
|
end else if (prev = 'K') or (prev = 'k') then begin
|
|
numEnd := len - 2;
|
|
s[numEnd] := #0;
|
|
value := stringToInt(s);
|
|
s[numEnd] := prev;
|
|
bytes := value * 1024;
|
|
end else begin
|
|
numEnd := len - 1;
|
|
s[numEnd] := #0;
|
|
value := stringToInt(s);
|
|
s[numEnd] := last;
|
|
bytes := value;
|
|
end;
|
|
end else begin
|
|
bytes := stringToInt(s);
|
|
end;
|
|
|
|
if sectorSize = 0 then exit;
|
|
parsesize := bytes div sectorSize;
|
|
end;
|
|
|
|
{ ---------- PART LIST ---------- }
|
|
|
|
procedure cmd_list(params : PParamList);
|
|
var
|
|
idx : uint32;
|
|
device : PStorage_Device;
|
|
bootrecord : PMaster_Boot_Record;
|
|
i : uint32;
|
|
part : TPartition_table;
|
|
hasAny : boolean;
|
|
begin
|
|
if paramCount(params) < 2 then begin
|
|
println('Usage: PART list <disk>');
|
|
exit;
|
|
end;
|
|
|
|
if not isNumeric(getParam(1, params)) then begin
|
|
println('Error: disk index must be a number.');
|
|
exit;
|
|
end;
|
|
|
|
idx := str2int(getParam(1, params));
|
|
device := driver.storage.mgr.get_device(idx);
|
|
|
|
if device = nil then begin
|
|
print('Error: device ');
|
|
printint(idx);
|
|
print(' not found (');
|
|
printint(driver.storage.mgr.get_device_count());
|
|
println(' devices registered).');
|
|
exit;
|
|
end;
|
|
|
|
if device^.dispatchRead = nil then begin
|
|
println('Error: device has no read support.');
|
|
exit;
|
|
end;
|
|
|
|
bootrecord := driver.storage.mgr.read_mbr(device);
|
|
|
|
if bootrecord = nil then begin
|
|
println('Error: failed to read mbr (memory allocation failed).');
|
|
exit;
|
|
end;
|
|
|
|
print('mbr Signature: '); printhex(bootrecord^.signature); println('');
|
|
print('Boot Marker: '); printhex(bootrecord^.boot_sector); println('');
|
|
|
|
if bootrecord^.boot_sector <> $AA55 then
|
|
println('Warning: invalid boot marker (expected 0xAA55).');
|
|
|
|
println('');
|
|
println('SLOT SYS_ID START SIZE BOOT');
|
|
println('---- ------ ----- ---- ----');
|
|
|
|
hasAny := false;
|
|
for i := 0 to 3 do begin
|
|
part := bootrecord^.partition[i];
|
|
if (part.LBA_start <> 0) or (part.sector_count <> 0) then
|
|
hasAny := true;
|
|
print(' ');
|
|
printint(i);
|
|
print(' ');
|
|
printhex(part.system_id);
|
|
print(' ');
|
|
printint(part.LBA_start);
|
|
print(' ');
|
|
printsize(part.sector_count * device^.sectorSize);
|
|
print(' ');
|
|
if driver.storage.vol.mbr.get_bootable(@part) then
|
|
println('yes')
|
|
else
|
|
println('no');
|
|
end;
|
|
|
|
if not hasAny then
|
|
println('(all partition slots are empty)');
|
|
|
|
kfree(puint32(bootrecord));
|
|
end;
|
|
|
|
{ ---------- PART ADD ---------- }
|
|
|
|
procedure cmd_add(params : PParamList);
|
|
var
|
|
idx : uint32;
|
|
slot : sint32;
|
|
lba : uint32;
|
|
sectors : uint32;
|
|
freeSec : uint32;
|
|
device : PStorage_Device;
|
|
part : TPartition_table;
|
|
bootrecord : PMaster_Boot_Record;
|
|
begin
|
|
if paramCount(params) < 2 then begin
|
|
println('Usage: PART add <disk> [size]');
|
|
println(' size examples: 500MB, 2GB, 1024KB, 4096B');
|
|
println(' omit size to use all remaining space.');
|
|
exit;
|
|
end;
|
|
|
|
if not isNumeric(getParam(1, params)) then begin
|
|
println('Error: disk index must be a number.');
|
|
exit;
|
|
end;
|
|
|
|
idx := str2int(getParam(1, params));
|
|
device := driver.storage.mgr.get_device(idx);
|
|
|
|
if device = nil then begin
|
|
print('Error: device ');
|
|
printint(idx);
|
|
print(' not found (');
|
|
printint(driver.storage.mgr.get_device_count());
|
|
println(' devices registered).');
|
|
exit;
|
|
end;
|
|
|
|
if not device^.writable then begin
|
|
println('Error: device is not writable.');
|
|
exit;
|
|
end;
|
|
|
|
if device^.dispatchWrite = nil then begin
|
|
println('Error: device has no write support.');
|
|
exit;
|
|
end;
|
|
|
|
{ Auto-init: create fresh mbr if none present }
|
|
bootrecord := driver.storage.mgr.read_mbr(device);
|
|
if bootrecord <> nil then begin
|
|
if bootrecord^.boot_sector <> $AA55 then begin
|
|
println('No valid mbr found, initialising disk...');
|
|
if driver.storage.vol.mgr.init_disk(device) <> eNone then begin
|
|
println('Error: disk initialization failed.'); exit;
|
|
end;
|
|
end;
|
|
kfree(puint32(bootrecord));
|
|
end;
|
|
|
|
slot := driver.storage.vol.mgr.find_free_slot(device);
|
|
if slot < 0 then begin
|
|
println('Error: no free partition slots (max 4).');
|
|
exit;
|
|
end;
|
|
|
|
freeSec := driver.storage.vol.mgr.get_free_sector_count(device);
|
|
|
|
if freeSec = 0 then begin
|
|
println('Error: no free space on device.');
|
|
exit;
|
|
end;
|
|
|
|
if paramCount(params) >= 3 then begin
|
|
sectors := parsesize(getParam(2, params), device^.sectorSize);
|
|
if sectors = 0 then begin
|
|
print('Error: invalid size "');
|
|
print(getParam(2, params));
|
|
println('". Use e.g. 500MB, 2GB, 1024KB, 4096B.');
|
|
exit;
|
|
end;
|
|
if sectors > freeSec then begin
|
|
print('Error: requested ');
|
|
printsize(sectors * device^.sectorSize);
|
|
print(' but only ');
|
|
printsize(freeSec * device^.sectorSize);
|
|
println(' available.');
|
|
exit;
|
|
end;
|
|
end else
|
|
sectors := freeSec;
|
|
|
|
lba := driver.storage.vol.mgr.find_free_space(device, sectors);
|
|
if lba = 0 then begin
|
|
print('Error: cannot find ');
|
|
printsize(sectors * device^.sectorSize);
|
|
println(' of contiguous space.');
|
|
exit;
|
|
end;
|
|
|
|
driver.storage.vol.mbr.setup_partition(@part, lba, sectors);
|
|
part.system_id := 0;
|
|
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);
|
|
print(': LBA ');
|
|
printint(lba);
|
|
print(', ');
|
|
printsize(sectors * device^.sectorSize);
|
|
println('.');
|
|
end;
|
|
|
|
{ ---------- PART RM ---------- }
|
|
|
|
procedure cmd_rm(params : PParamList);
|
|
var
|
|
idx : uint32;
|
|
slot : uint32;
|
|
device : PStorage_Device;
|
|
old : TPartition_table;
|
|
begin
|
|
if paramCount(params) < 3 then begin
|
|
println('Usage: PART rm <disk> <slot>');
|
|
exit;
|
|
end;
|
|
|
|
if not isNumeric(getParam(1, params)) then begin
|
|
println('Error: disk index must be a number.');
|
|
exit;
|
|
end;
|
|
|
|
if not isNumeric(getParam(2, params)) then begin
|
|
println('Error: slot must be a number (0-3).');
|
|
exit;
|
|
end;
|
|
|
|
idx := str2int(getParam(1, params));
|
|
slot := str2int(getParam(2, params));
|
|
|
|
device := driver.storage.mgr.get_device(idx);
|
|
|
|
if device = nil then begin
|
|
print('Error: device ');
|
|
printint(idx);
|
|
print(' not found (');
|
|
printint(driver.storage.mgr.get_device_count());
|
|
println(' devices registered).');
|
|
exit;
|
|
end;
|
|
|
|
if slot > 3 then begin
|
|
println('Error: slot must be 0-3.');
|
|
exit;
|
|
end;
|
|
|
|
if not device^.writable then begin
|
|
println('Error: device is not writable.');
|
|
exit;
|
|
end;
|
|
|
|
if device^.dispatchWrite = nil then begin
|
|
println('Error: device has no write support.');
|
|
exit;
|
|
end;
|
|
|
|
old := driver.storage.vol.mgr.get_partition(device, slot);
|
|
if (old.LBA_start = 0) and (old.sector_count = 0) then begin
|
|
print('Slot ');
|
|
printint(slot);
|
|
println(' is already empty.');
|
|
exit;
|
|
end;
|
|
|
|
if driver.storage.vol.mgr.remove_partition(device, slot) <> eNone then begin
|
|
println('Error: partition removal failed.'); exit;
|
|
end;
|
|
|
|
print('Partition slot ');
|
|
printint(slot);
|
|
print(' removed (was LBA ');
|
|
printint(old.LBA_start);
|
|
print(', ');
|
|
printsize(old.sector_count * device^.sectorSize);
|
|
println(').');
|
|
end;
|
|
|
|
{ ---------- PART FORMAT ---------- }
|
|
|
|
procedure cmd_format(params : PParamList);
|
|
var
|
|
idx : uint32;
|
|
slot : uint32;
|
|
fsName : pchar;
|
|
device : PStorage_Device;
|
|
part : TPartition_table;
|
|
vol : PStorage_Volume;
|
|
fs : PFilesystem;
|
|
i : uint32;
|
|
begin
|
|
if paramCount(params) < 4 then begin
|
|
println('Usage: PART format <disk> <slot> <fsname>');
|
|
exit;
|
|
end;
|
|
|
|
if not isNumeric(getParam(1, params)) then begin
|
|
println('Error: disk index must be a number.');
|
|
exit;
|
|
end;
|
|
|
|
if not isNumeric(getParam(2, params)) then begin
|
|
println('Error: slot must be a number (0-3).');
|
|
exit;
|
|
end;
|
|
|
|
idx := str2int(getParam(1, params));
|
|
slot := str2int(getParam(2, params));
|
|
fsName := getParam(3, params);
|
|
|
|
device := driver.storage.mgr.get_device(idx);
|
|
if device = nil then begin
|
|
print('Error: device ');
|
|
printint(idx);
|
|
print(' not found (');
|
|
printint(driver.storage.mgr.get_device_count());
|
|
println(' devices registered).');
|
|
exit;
|
|
end;
|
|
|
|
if slot > 3 then begin
|
|
println('Error: slot must be 0-3.');
|
|
exit;
|
|
end;
|
|
|
|
if not device^.writable then begin
|
|
println('Error: device is not writable.');
|
|
exit;
|
|
end;
|
|
|
|
part := driver.storage.vol.mgr.get_partition(device, slot);
|
|
if (part.LBA_start = 0) and (part.sector_count = 0) then begin
|
|
print('Error: partition slot ');
|
|
printint(slot);
|
|
println(' is empty.');
|
|
exit;
|
|
end;
|
|
|
|
{ Find the corresponding volume, or create it }
|
|
vol := nil;
|
|
for i := 0 to driver.storage.vol.mgr.get_volume_count() - 1 do begin
|
|
vol := driver.storage.vol.mgr.get_volume(i);
|
|
if (vol^.device = device) and (vol^.sectorStart = part.LBA_start) then
|
|
break;
|
|
vol := nil;
|
|
end;
|
|
|
|
if vol = nil then begin
|
|
driver.storage.vol.mgr.create_volume_from_partition(device, part.LBA_start, part.sector_count);
|
|
vol := driver.storage.vol.mgr.get_volume(driver.storage.vol.mgr.get_volume_count() - 1);
|
|
end;
|
|
|
|
if vol^.filesystem <> nil then begin
|
|
print('Warning: already has filesystem "');
|
|
print(vol^.filesystem^.sName);
|
|
println('", overwriting.');
|
|
end;
|
|
|
|
fs := driver.storage.fs.mgr.find_filesystem_by_name(fsName);
|
|
if fs = nil then begin
|
|
print('Error: filesystem "');
|
|
print(fsName);
|
|
println('" not registered.');
|
|
print('Registered: ');
|
|
if driver.storage.fs.mgr.get_filesystem_count() = 0 then
|
|
println('(none)')
|
|
else begin
|
|
for i := 0 to driver.storage.fs.mgr.get_filesystem_count() - 1 do begin
|
|
if i > 0 then print(', ');
|
|
print(driver.storage.fs.mgr.get_filesystem(i)^.sName);
|
|
end;
|
|
println('');
|
|
end;
|
|
exit;
|
|
end;
|
|
|
|
if fs^.createCallback = nil then begin
|
|
print('Error: filesystem "');
|
|
print(fsName);
|
|
println('" does not support formatting.');
|
|
exit;
|
|
end;
|
|
|
|
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);
|
|
print(' on disk ');
|
|
printint(idx);
|
|
print(' formatted with ');
|
|
print(fsName);
|
|
print(' (');
|
|
printsize(vol^.sectorCount * vol^.sectorSize);
|
|
println(').');
|
|
end;
|
|
|
|
{ ---------- Main dispatcher ---------- }
|
|
|
|
procedure command_part(params : PParamList; stdin_buf, stdout_buf, stderr_buf: POutBuf);
|
|
var
|
|
subcmd : pchar;
|
|
begin
|
|
g_out := stdout_buf;
|
|
push_trace('PartCmd.command_part');
|
|
|
|
if paramCount(params) = 0 then begin
|
|
println('Usage: PART <list|add|rm|format>');
|
|
exit;
|
|
end;
|
|
|
|
subcmd := getParam(0, params);
|
|
|
|
if stringEquals(subcmd, 'list') then begin
|
|
cmd_list(params);
|
|
exit;
|
|
end;
|
|
|
|
if stringEquals(subcmd, 'add') then begin
|
|
cmd_add(params);
|
|
exit;
|
|
end;
|
|
|
|
if stringEquals(subcmd, 'rm') then begin
|
|
cmd_rm(params);
|
|
exit;
|
|
end;
|
|
|
|
if stringEquals(subcmd, 'format') then begin
|
|
cmd_format(params);
|
|
exit;
|
|
end;
|
|
|
|
print('Unknown subcommand: ');
|
|
println(subcmd);
|
|
end;
|
|
|
|
{ ---------- Init ---------- }
|
|
|
|
procedure init();
|
|
begin
|
|
io.stdio.registerCommand('PART', @command_part, 'Partition management.');
|
|
end;
|
|
|
|
end.
|