Tridora-CPU/progs/partmgr.pas
2024-09-19 14:10:33 +02:00

742 lines
18 KiB
ObjectPascal

(* Copyright 2021-2024 Sebastian Lederer. See the file LICENSE.md for details *)
program partmgr;
const MaxPartitions = 32;
LastPartBlock = 7;
PartsPerBlock = 8;
var partTable:array[0..LastPartBlock] of PartitionTableBlock;
changed:array[0..LastPartBlock] of boolean;
detectedCardSize:integer;
cmd:char;
done:boolean;
lastPartNo:integer;
function flags2str(flags:PartFlags):string;
begin
flags2str := '';
if PartEnabled in flags then flags2str := flags2str + 'E ';
if PartBoot in flags then flags2str := flags2str + 'B ';
if PartLast in flags then flags2str := flags2str + 'L ';
if PartPhysical in flags then flags2str := flags2str + 'P ';
if PartDefault in flags then flags2str := flags2str + 'D ';
end;
function str2flags(var s:string):PartFlags;
begin
str2flags := [];
if 'E' in s then str2flags := str2flags + [PartEnabled];
if 'B' in s then str2flags := str2flags + [PartBoot];
if 'L' in s then str2flags := str2flags + [PartLast];
if 'P' in s then str2flags := str2flags + [PartPhysical];
if 'D' in s then str2flags := str2flags + [PartDefault];
end;
function sanitizeName(var name:string):string;
begin
if (length(name) <= 32) and (maxlength(name) = 32) then
sanitizeName := name
else
sanitizeName := '<invalid>';
end;
procedure changeNumber(prompt:string; var num:integer);
var buf:string;
err:integer;
digits:string;
begin
str(num, digits);
buf := prompt + ' [' + digits + ']> ';
write(buf:30);
readln(buf);
val(buf,num,err);
end;
procedure readPartTable;
var done:boolean;
curblk:integer;
error:integer;
devid:integer;
begin
done := false;
curblk := 0;
devid := 0; (* we only support one device *)
while not done do
begin
changed[curBlk] := false;
readPartBlk(curblk, partTable[curblk], error, devid);
if error <> 0 then
begin
done := true;
writeln('Error ', error,
' reading partition block ', curblk);
end
else
curblk := curblk + 1;
if curBlk > LastPartBlock then
done := true;
end;
end;
procedure writePartBlock(no:integer);
var error:integer;
devid:integer;
begin
devid := 0;
writePartBlk(no, partTable[no], error, devid);
if error <> 0 then
writeln('Error ', error,
' reading partition block ', no);
end;
procedure writePartitions;
var blkNo:integer;
begin
for blkNo := 0 to LastPartBlock do
begin
if changed[blkNo] then
begin
writeln('Writing back partition block ',blkNo);
writePartBlock(blkNo);
end;
end;
end;
function getPartition(partNo:integer):Partition;
var blkNo:integer;
begin
blkNo := partNo div PartsPerBlock;
if (blkNo < 0) or (blkNo > LastPartBlock) then
writeln('internal error: invalid part no in getPartition')
else
getPartition := partTable[blkNo][partNo mod PartsPerBlock];
{ writeln('** getPartition: ', blkNo, ' ', partNo mod PartsPerBlock); }
end;
procedure putPartition(var part:Partition; partNo:integer);
var blkNo:integer;
begin
blkNo := partNo div PartsPerBlock;
{ writeln('** putPartition: ', blkNo, ' ', partNo mod PartsPerBlock); }
if (blkNo < 0) or (blkNo > LastPartBlock) then
writeln('internal error: invalid part no in getPartition')
else
begin
partTable[blkNo][partNo mod PartsPerBlock] := part;
changed[blkNo] := true;
end;
end;
function isEmptyPart(var part:Partition):boolean;
begin
isEmptyPart := (part.startBlock = 0) and (part.blocks = 0);
end;
procedure printPartTable;
var blkNo, partNo:integer;
partBlk:PartitionTableBlock;
part:Partition;
totalPartNo:integer;
begin
totalPartNo := 0;
writeln('Partition Table:');
writeln('No. ', 'Flags':11, 'Name':32, 'Start':10, 'Size':10);
for blkNo := 0 to LastPartBlock do
begin
partBlk := partTable[blkNo];
for partNo := 0 to PartsPerBlock - 1 do
begin
part := partBlk[partNo];
if not isEmptyPart(part) then
begin
write(totalPartNo:3, ': ', flags2Str(part.flags):11,
sanitizeName(part.name):32,
part.startBlock:10,
part.blocks:10);
if PartBoot in part.flags then write(' ', part.bootBlocks);
writeln;
lastPartNo := totalPartNo;
end;
totalPartNo := totalPartNo + 1;
end;
end;
writeln('Flags: P=Physical B=Boot E=Enabled L=Last D=Default');
end;
function askPartNo:integer;
var i:integer;
s:string;
begin
askPartNo := -1;
write('Enter partition number (0-', lastPartNo, ')> ');
readln(s);
if length(s) > 0 then
begin
val(s,askPartNo,i);
if i > 0 then
writeln('Invalid partition number');
end;
end;
function askConfirmPartNo:integer;
var partNo:integer;
part:Partition;
answer:char;
begin
askConfirmPartNo := -1;
partNo := askPartNo;
if partNo >= 0 then
begin
part := getPartition(partNo);
write('Any data on partition ', partNo,
' (', sanitizeName(part.name), ') ',
'will be destroyed. Sure [y/n]? ');
readln(answer);
if upcase(answer) = 'Y' then
askConfirmPartNo := partNo;
end;
end;
function guessExtentSize(blocks:integer):integer;
begin
if blocks >= 4194304 then (* 2 GB *)
guessExtentSize := 1048576 (* use 1MB extents *)
else
if blocks >= 1048576 then (* 512 MB *)
guessExtentSize := 524288 (* use 512K extents *)
else
if blocks >= 524288 then (* 256 MB *)
guessExtentSize := 131072
else
if blocks >= 262144 then (* 128 MB *)
guessExtentSize := 65536
else
if blocks >= 32768 then (* 16 MB *)
guessExtentSize := 16384
else
guessExtentSize := 8192;
end;
function getDirSize(extentSize,blocks:integer):integer;
begin
getDirSize := blocks div (extentSize div 512);
end;
procedure createFilesystem(partNo:integer); forward;
procedure addVolume;
var nextFreeBlock:integer;
nextBlock:integer;
extentSize:integer;
size:integer;
i:integer;
part:Partition;
maxBlocks:integer;
freeBlocks:integer;
newPartNo:integer;
newPart:Partition;
begin
part := getPartition(0);
maxBlocks := part.blocks;
nextFreeBlock := 0;
(* read all partitions *)
for i := 1 to lastPartNo do
begin
part := getPartition(i);
nextBlock := part.startBlock + part.blocks;
if nextBlock > nextFreeBlock then
nextFreeBlock := nextBlock;
end;
(* remember last used block *)
writeln('next free partition: ', lastPartNo + 1,
' next free block: ', nextFreeBlock);
freeBlocks := maxBlocks - nextFreeBlock;
if freeBlocks < 1 then
writeln('Cannot add partition - no free blocks after last partition.')
else
begin
newPartNo := lastPartNo + 1;
(* remove last partition flag on previous last partition *)
part.flags := part.flags - [PartLast];
putPartition(part, lastPartNo);
(* create new partition *)
size := freeBlocks;
changeNumber('Size (blocks)', size);
write('Name> ':30);
readln(newPart.name);
newPart.startBlock := nextFreeBlock;
newPart.blocks := size;
newPart.extentSize := guessExtentSize(size);
newPart.dirSize := getDirSize(newPart.extentSize, size);
newPart.bootBlocks := 0;
(* mark new partition as last partition *)
newPart.flags := [ PartEnabled, PartLast ];
putPartition(newPart, newPartNo);
writeln('Partition ', newPartNo, ' created, extent size:', newPart.extentSize,
' directory size: ', newPart.dirSize);
createFilesystem(newPartNo);
lastPartNo := lastPartNo + 1;
end;
end;
procedure renameVolume;
var partNo:integer;
newName:string;
part:Partition;
begin
partNo := askPartNo;
if partNo >= 0 then
begin
part := getPartition(partNo);
writeln('Old partition/volume name: ', sanitizeName(part.name));
write('New partion/volume name: ');
readln(newName);
if length(newName) > 0 then
begin
part.name := newName;
putPartition(part, partNo);
end;
end;
end;
procedure toggleDefaultFlag;
var partNo:integer;
part:Partition;
begin
partNo := askPartNo;
if partNo >= 0 then
begin
part := getPartition(partNo);
write('Default flag ');
if PartDefault in part.flags then
begin
part.flags := part.flags - [PartDefault];
write('cleared');
end
else
begin
part.flags := part.flags + [PartDefault];
write('set');
end;
writeln(' on partition ', partNo, ' (', sanitizeName(part.name), ').');
putPartition(part, partNo);
end;
end;
procedure deleteVolume;
var partNo:integer;
part:Partition;
begin
partNo := askConfirmPartNo;
if partNo >= 0 then
begin
part := getPartition(partNo);
part.flags := [];
part.name := '';
part.startBlock := 0;
part.blocks := 0;
part.extentSize := 0;
part.dirSize := 0;
part.bootBlocks := 0;
putPartition(part, partNo);
writeln('Partition ', partNo, ' deleted.');
(* try to fix last partition flag *)
(* only works if the previous entry has
a valid partition *)
if partNo = lastPartNo then
begin
lastPartNo := lastPartNo - 1;
part := getPartition(lastPartNo);
part.flags := part.flags + [PartLast];
putPartition(part, lastPartNo);
end;
end;
end;
procedure validatePartTable;
var partNo:integer;
phys:Partition;
part,part2:Partition;
answer:char;
p,p2:integer;
valid:boolean;
begin
valid := true;
phys := getPartition(0);
if not (PartPhysical in phys.flags) then
begin
writeln('PHYS partition missing, initialize card first!');
exit;
end;
if phys.blocks <> detectedCardSize then
begin
write('PHYS partition size does not match detected card size, fix? [y/n]');
readln(answer);
if upcase(answer) = 'Y' then
begin
phys.blocks := detectedCardSize;
putPartition(phys,0);
end
else
valid := false;
end;
for p := 1 to lastPartNo do
begin
part := getPartition(p);
if (part.startBlock < 0) or (part.startBlock + part.blocks > phys.blocks) then
begin
writeln('Partition ', p, ' outside of physical bounds.');
valid := false;
end;
if PartEnabled in part.flags then
if part.dirSize <> getDirSize(part.extentSize, part.blocks) then
begin
write('Partition ', p, ' has an invalid directory size (is ');
writeln(part.dirSize, ', should be ',
getDirSize(part.extentSize, part.blocks), ').');
valid := false;
end;
for p2 := 1 to lastPartNo do
begin
part2 := getPartition(p2);
if (p <> p2) then
begin
if ((part.startBlock >= part2.startBlock) and
(part.startBlock < part2.startBlock + part2.blocks)) or
((part2.startBlock > part.startBlock) and
(part2.startBlock < part.startBlock + part.blocks))
then
begin
writeln('Partition ',p ,' overlaps with partition ', p2);
valid := false;
end;
if (part.name = part2.name) and (p > p2) then
begin
writeln('Duplicate volume name ', part.name);
valid := false;
end;
end;
end;
end;
write('Partition table is ');
if not valid then write('in');
writeln('valid.');
end;
procedure checkNewCard; forward;
procedure initializeCard;
var part:Partition;
answer:char;
p:integer;
begin
writeln('Initializing a card will create an empty partition table with');
writeln('the standard PHYS and BOOT partitions.');
write('This will likely destroy any data on the card - sure? [y/n] ');
readln(answer);
if upcase(answer) <> 'Y' then exit;
(* create PHYS partition using detectedcardblocks *)
part.name := 'PHYS';
part.startBlock := 0;
part.blocks := detectedCardSize;
part.flags := [PartPhysical];
part.extentSize := 0;
part.dirSize := 0;
part.bootBlocks := 0;
putPartition(part,0);
(* create BOOT partition without PartBoot flag *)
part.name := 'BOOT';
part.startBlock := 16; (* 16 possible partition blocks with 8 partitions each *)
part.blocks := 8192 - 16; (* align first volume to 4MB *)
part.flags := [PartBoot, PartLast];
putPartition(part,1);
part.name := '';
part.startBlock := 0;
part.blocks := 0;
part.flags := [];
for p := 2 to 7 do
putPartition(part,p);
writeln('Empty partition table created.');
end;
procedure createFilesystem(partNo:integer);
var firstDirBlock:integer;
slot:DirectorySlot;
dirblk:DirBlock;
dirblockCount:integer;
metaSlotsCount:integer;
dirSlotsPerBlock:integer;
dirSlotsPerExtent:integer;
part:Partition;
ts:Timestamp;
i,b:integer;
error,devid:integer;
begin
devid := 0;
ts := 0;
part := getPartition(partNo);
firstDirBlock := part.startBlock;
dirSlotsPerBlock := 512 div 64;
dirSlotsPerExtent := part.extentSize div 64;
dirblockCount := (part.dirSize - 1) div dirSlotsPerBlock + 1;
metaSlotsCount := (part.dirSize - 1) div dirSlotsPerExtent + 1;
writeln('partition size: ', part.blocks);
writeln('extent size: ', part.extentSize);
writeln('directory size: ', part.dirSize);
{ writeln('dirslots per extent:', dirSlotsPerExtent);
writeln('dirblocks: ', dirblockCount);
writeln('metaslots: ', metaSlotsCount);
writeln('first dir block: ', firstDirBlock);
}
for b := firstDirBlock to firstDirBlock + dirblockCount - 1 do
begin
for i := 0 to dirSlotsPerBlock - 1 do
begin
if metaSlotsCount > 0 then
begin
(* write DIR/Reserved directory slots *)
slot.name := 'DIR';
slot.flags := [ SlotReserved ];
metaSlotsCount := metaSlotsCount - 1;
end
else
begin
(* write Free + EndScan directory slots *)
slot.name := '';
slot.flags := [ SlotFree , SlotEndScan ];
end;
slot.sizeBytes := 0;
slot.createTime := ts;
slot.modTime := ts;
slot.generation := 0;
slot.owner := 0;
dirBlk[i] := slot;
end;
writedirblk(b, dirBlk, error, devid);
if error > 0 then
writeln('error writing block ', b, ': ', error);
end;
writeln('Volume ', part.name, ' initialized.');
end;
procedure initializeVolume;
var partNo:integer;
part:Partition;
begin
partNo := askConfirmPartNo;
if partNo >= 0 then
begin
part := getPartition(partNo);
if not (PartEnabled in part.flags) then
writeln('Wrong partition flags (must be Enabled)')
else
createFilesystem(partNo);
end;
end;
procedure rawEdit;
var partNo:integer;
newName:string;
part:Partition;
buf:string;
begin
writeln('Raw editing partition entry - use caution!');
partNo := askPartNo;
if partNo >= 0 then
begin
part := getPartition(partNo);
writeln('Volume name: ', sanitizeName(part.name));
write('Flags> ':30);
readln(buf);
if length(buf) > 0 then
part.flags := str2flags(buf);
changeNumber('Start block', part.startBlock);
changeNumber('Size (blocks)', part.blocks);
changeNumber('Extent size (blocks)', part.extentSize);
changeNumber('Dir size (slots)', part.dirSize);
changeNumber('Boot blocks', part.bootBlocks);
putPartition(part, partNo);
end;
end;
procedure installBoot;
var bootfile:file;
name:string;
part:Partition;
partNo:integer;
buf:IOBlock;
b,blkCount:integer;
devId:integer;
error:integer;
procedure readWordsIntoBuf;
var i:integer;
w:integer;
c1,c2,c3,c4:char;
begin
for i := 0 to 127 do
begin
if not eof(bootfile) then
begin
read(bootfile, c1, c2, c3, c4);
w := (ord(c1) shl 24) or
(ord(c2) shl 16) or
(ord(c3) shl 8) or
ord(c4);
end
else
w := 0;
buf[i] := w;
end;
end;
begin
devId := 0; (* only one device supported *)
partNo := 1; (* BOOT partition is always at position 1 *)
part := getPartition(partNo);
if part.name <> 'BOOT' then
begin
writeln('No BOOT partition at position 1.');
exit;
end;
write('Boot file name> ');
readln(name);
if length(name) > 0 then
begin
open(bootfile, name, ModeReadonly);
if IOResult(bootfile) <> 0 then
writeln('Error opening file: ', ErrorStr(IOResult(bootfile)))
else
begin
blkCount := filesize(bootfile) div 512 + 1;
if blkCount > part.blocks then
writeln('Boot partition too small, need ', blkCount)
else
begin
part.bootBlocks := blkCount;
if not (PartBoot in part.flags) then
begin
write('Boot flag set');
writeln(' on partition ', partNo,
' (', sanitizeName(part.name), ').');
part.flags := part.flags + [PartBoot];
end;
putPartition(part, partNo);
for b := 0 to blkCount - 1 do
begin
readWordsIntoBuf;
writeblock(part.startBlock + b, buf, error, devId);
if error <> 0 then
writeln('Error in writeblock ', b, ': ', error);
end;
writeln(blkCount, ' boot blocks written.');
end;
close(bootfile);
end;
end;
end;
procedure showMenu;
begin
writeln;
writeln('L)ist partitions V)alidate partition table T)oggle default volume flag');
writeln('A)dd volume R)ename volume D)elete volume I)nitialize volume');
writeln('Read N)ew card Initialize C)ard');
writeln('E)dit partition Install B)oot file eX)it');
write('> ');
end;
procedure invalidCommand;
begin
writeln('Invalid command.');
end;
procedure command(cmd:char; var done:boolean);
begin
case cmd of
'L': printPartTable;
'A': addVolume;
'R': renameVolume;
'D': deleteVolume;
'V': validatePartTable;
'T': toggleDefaultFlag;
'I': initializeVolume;
'N': checkNewCard;
'C': initializeCard;
'B': installBoot;
'E': rawEdit;
'X',#24: done := true;
else invalidCommand;
end;
end;
function changesPending:boolean;
var i:integer;
begin
changesPending := false;
for i := 0 to LastPartBlock do
if changed[i] then changesPending := true;
end;
procedure checkNewCard;
begin
if changesPending then
writeln('WARNING: Discarding partition table changes.');
initDevices;
detectedCardSize := cardsize;
writeln('Detected card size: ', detectedCardSize);
readPartTable;
printPartTable;
end;
begin
checkNewCard;
repeat
showMenu;
read(cmd);
writeln;
command(Upcase(cmd), done);
until done;
writePartitions;
end.