unit ubixfs;
interface
uses device, lists;
const INODE_NO_FLAG = $00000000;
const INODE_IN_USE = $00000001;
const ATTR_INODE = $00000004;
const INODE_LOGGED = $00000008;
const INODE_DELETED = $00000010;
const PERMANENT_FLAGS = $0000ffff;
const INODE_NO_CACHE = $00010000;
const INODE_WAS_WRITTEN = $00020000;
const S_IFDIR = $00040000; // POSIX
const NO_TRANSACTION = $00040000;
const NUM_DIRECT_BLOCKS = 16;
const UBIXFS_MAGIC_1 = $A0A0A0A;
const UBIXFS_MAGIC_2 = $B0B0B0B;
const UBIXFS_MAGIC_3 = $C0C0C0C;
const UBIXFS_INODE_MAGIC = $3BBE0AD9;
const UBIXFS_CLEAN = $434C454E;
const UBIXFS_DIRTY = $44495254;
const SECTOR_SHIFT = 9;
const SECTOR_SIZE = 1 shl SECTOR_SHIFT;
type
int8 = shortInt;
int16 = smallInt;
int32 = longInt;
uInt8 = byte;
uInt16 = word;
uInt32 = dWord;
{ Common Integer array definitions }
type
int8ArrayPtr = ^int8Array;
int8Array = array[ 0..0 ] of int8;
int16ArrayPtr = ^int16Array;
int16Array = array[ 0..0 ] of int16;
int32ArrayPtr = ^int32Array;
int32Array = array[ 0..0 ] of int32;
{ Common Unsigned Integer array definitions }
type
uInt8ArrayPtr = ^uInt8Array;
uInt8Array = array[ 0..0 ] of uInt8;
uInt16ArrayPtr= ^uInt16Array;
uInt16Array = array[ 0..0 ] of uInt16;
uInt32ArrayPtr= ^uInt32Array;
uInt32Array = array[ 0..0 ] of uInt32;
type
(* TBlockRun = packed record
AG : int32;
start : uInt16;
len : uInt16;
end;
inodeAddr = TblockRun;
uPtr = packed record
case integer of
0: (i : int32);
1: (u : uInt32);
2: (s : single);
3: (d : double);
4: (p : pointer);
5: (offset : int64);
6: (iAddr : inodeAddr);
end;
*)
PdiskSuperBlock = ^diskSuperBlock;
diskSuperBlock = packed record
name : array[0..31] of char;
magic1 : int32;
fsByteOrder : int32;
// blockSize on disk
blockSize : uInt32;
// number of bits needed to shift a block number to get a byte address
blockShift : uInt32;
numBlocks : int64;
usedBlocks : int64;
inodeSize : uInt32;
magic2 : uInt32;
blocksPerAG : uInt32;
AGShift : uInt32;
numAGs : uInt32;
flags : uInt32;
logBlocks : TBlockRun;
logStart : int64;
logEnd : int64;
magic3 : int32;
BAT : inodeAddr;
rootDir : inodeAddr;
indicies : inodeAddr;
pad : array[0..371] of byte;
end; // diskSuperBlock
PDataStream = ^TDataStream;
TDataStream = packed record
direct : array[0..NUM_DIRECT_BLOCKS-1] of TBlockRun;
maxDirect : uInt32;
maxDirectU : uInt32; // upper 32 bits
indirect : TBlockRun;
maxIndirect : uInt32;
maxIndirectU : uInt32; // upper 32 bits
doubleIndirect: TBlockrun;
maxDIndirect : uInt32;
maxDIndirectU : uInt32; // upper 32 bits
size : uInt32;
sizeU : uInt32; // upper 32 bits
end; // TDataStream
PubixfsInode = ^ubixfsInode;
ubixfsInode = packed record
magic : int32;
inodeNum : inodeAddr;
uid : uInt32;
gid : uInt32;
mode : int32;
flags : int32;
createTime : int64;
modifiedTime : int64;
parent : inodeAddr;
attributes : inodeAddr;
iType : uInt32;
inodeSize : uInt32;
data : uPtr; {this was the etc field in bfs}
refCount : uInt32;
blocks : TDataStream;
blockCount : uInt32;
smallData : array[0..0] of byte;
end; // ubixfsInode
PfileDescriptor = ^TfileDescriptor;
TfileDescriptor = packed record
magic : uInt32;
inode : pointer;
offset : int64;
size : int64;
end; // fileDescriptor
vfs_abstract = object
private
prev : ^vfs_abstract;
next : ^vfs_abstract;
dev : PDevice;
public
constructor init;
destructor done; virtual;
end; // vfs_abstract
PUbixFS = ^TUbixFS;
TUbixFS = object(vfs_abstract)
private
superBlock : ^diskSuperBlock;
freeBlockList : PfileDescriptor;
InodeCache : PbTree;
procedure assert(x:boolean);
function allocBRun(blockRun:TBlockRun):TBlockRun;
function allocInode(parentIAddr:inodeAddr):PUbixfsInode;
function extendFile(inode:PubixfsInode; numBlocks:uInt32):integer;
function findBRun(dataStream:PDataStream; blockNum:uInt32):TBlockRun;
function findBRunDirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun;
function findBRunIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun;
function findBRunDIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun;
function freeUsedBlocks(blockRun:TBlockRun):integer;
function getFileSize(inode:PUbixfsInode):uInt32; // limits to 4GB
function getFileSpace(inode:PUbixfsInode):uInt32; // limits to 4GB
function iAddrToStr(iAddr:inodeAddr):string;
function isValidIAddr(iAddr:inodeAddr):boolean;
function loadInode(iaddr:inodeAddr):PUbixfsInode;
function markUsedBlocks(blockRun:TBlockRun):integer;
function nextAG(AG:uInt32):uInt32;
function pathToIAddr(path:PChar):inodeAddr;
function readDataStream(inode:PubixfsInode; position:uInt32; buf:pointer; length:uInt32):integer; // position limits files to 4GB
function saveInode(inode:PUbixfsInode):integer;
procedure setBlockRun(var blockRun:TBlockRun; AG:uInt32; start, len:uInt16);
procedure setIAddr(var inode:inodeAddr; AG:uInt32; start, len:uInt16);
function strToIAddr(s:string):inodeAddr;
function unloadInode(inode:PubixfsInode):integer;
function writeDataStream(inode:PubixfsInode; position:uInt32; buf:pointer; length:uInt32):integer; // position limits files to 4GB
public
constructor init(device:Pdevice);
procedure printSuperBlock;
function vfs_fCreate(filename:PChar):int32;
function vfs_fExist(path:PChar):integer;
function vfs_format(device:PDevice; fsBlockSize:uInt32; quick:boolean):integer;
function vfs_init:integer;
function vfs_mkdir(path:PChar):integer;
function vfs_read(filedes:int64; position:uInt32; buffer:pointer; size:uInt32):uInt32; // limits to 4GB
function vfs_write(filedes:int64; position:uInt32; buffer:pointer; size:uInt32):uInt32; // limits to 4GB
procedure examine;
destructor done; virtual;
end; // TUbixFS
implementation
uses strings, dos, math, lists;
type
PUbixBTreeVFS = ^TUbixBTreeVFS;
TUbixBTreeVFS = object(TBTreeVFS)
private
fs : PUbixFS;
inode : PUbixFSInode;
position : uInt32; // limits files to 4GB
inodeSize : uInt32;
public
constructor init(_fs:PUbixFS);
function fClose:boolean; virtual;
function fCreate(const filename:string):boolean; virtual;
function fExist(const filename:string):boolean; virtual;
function fOpen(const filename:string):boolean; virtual;
function fRead(var buf; size:uInt32):boolean; virtual;
function fSeek(offset:uInt32):boolean; virtual;
function fWrite(var buf; size:uInt32):boolean; virtual;
destructor done; virtual;
end; // TUbixBTreeVFS
constructor TUbixBTreeVFS.init;
begin
// writeln('TUbixBTreeVFS.init()');
fs := _fs;
inode := NIL;
inodeSize := 0;
position := 0;
end; // TUbixBTreeVFS.init;
function TUbixBTreeVFS.fClose;
begin
if (fs = NIL) or (inode = NIL) then exit;
fs^.unloadInode(inode);
inode := NIL;
inodeSize := 0;
position := 0;
end; // TUbixBTreeVFS.fClose
function TUbixBTreeVFS.fCreate;
begin
// I *think* that this code is only called for the
// B^Tree. Therefore, the file technically exists, but just
// has nothing in it when we call fCreate.
result := fOpen(filename);
end; // TUbixBTreeVFS.fCreate
function TUbixBTreeVFS.fExist;
var
tmpIAddr:inodeAddr;
tmpInode:PUbixfsInode;
begin
result := FALSE;
assert(fs <> NIL);
if (fs = NIL) then exit;
assert(fs^.superBlock <> NIL);
if (fs^.superBlock = NIL) then exit;
tmpIAddr := fs^.strToIAddr(filename);
if not fs^.isValidIAddr(tmpIAddr) then exit;
// If we've created an inode, but haven't written any data to it, then
// the B^Tree hasn't written out a superblock, so technically the file doesn't
// exist, and this is a really long run-on sentence.
tmpInode := fs^.loadInode(tmpIAddr);
result := (tmpInode <> NIL) and (tmpInode^.blocks.size <> 0);
fs^.unloadInode(tmpInode);
end; // TUbixBTreeVFS.fExist
function TUbixBTreeVFS.fOpen;
var
iAddr : inodeAddr;
begin
result := FALSE;
if (fs = NIL) then exit;
if (fs^.superBlock = NIL) then exit;
iAddr := fs^.strToIAddr(filename);
position := 0;
inode := fs^.loadInode(iAddr);
result := (inode <> NIL);
end; // TUbixBTreeVFS.fOpen
function TUbixBTreeVFS.fRead;
begin
result := FALSE;
if (fs = NIL) or (inode = NIL) then exit;
// with inode^.inodeNum do
// write('fRead(', AG, ':', start, ':', len, ', ');
// write(position, ', ');
// write('@buf, ');
result := (fs^.readDataStream(inode, position, @buf, size) = 0);
inc(position, size);
end; // TUbixBTreeVFS.fRead
function TUbixBTreeVFS.fSeek;
begin
// uhm.. if the desired offset is beyond the end of the file,
// should we not extend out the file? Or does that occur in writeDataStream ...
position := offset;
result := TRUE;
end; // TUbixBTreeVFS.fSeek
function TUbixBTreeVFS.fWrite;
begin
result := FALSE;
if (fs = NIL) or (inode = NIL) then exit;
// with inode^.inodeNum do
// write('fWrite(', AG, ':', start, ':', len, ', ');
// write(position, ', ');
// write('@buf, ');
result := (fs^.writeDataStream(inode, position, @buf, size) = 0);
inc(position, size);
end; // TUbixBTreeVFS.fWrite;
destructor TUbixBTreeVFS.done;
begin
if ((inode <> NIL) and (inodeSize <> 0)) then FreeMem(inode, inodeSize);
inodeSize := 0;
inode := NIL;
fs := NIL; // don't destruct it since we didn't allocate it
end; // TUbixBTreeVFS.done
constructor vfs_abstract.init;
begin
next := NIL;
prev := NIL;
dev := NIL;
end; // vfs_abstract.init
destructor vfs_abstract.done;
begin
end; // vfs_abstract.done
constructor TUbixFS.init;
begin
inherited init;
freeBlockList := NIL;
inodeCache := NIL;
superBlock := NIL;
dev := device;
end; // TUbixFS.init
procedure TUbixFS.assert(x:boolean);
begin
if not x then runerror(1000);
end; // TUbixFS.assert
function TUbixFS.allocBRun;
var
inode:PUbixfsInode;
FBL:int8ArrayPtr;
FBLSize:uInt32;
bitOffset:uInt32;
byteOffset:uInt32;
curBlock, baseBlock:uInt32;
freeCount:uInt32;
begin
setBlockRun(result, 0, 0, 0);
if (superBlock = NIL) then exit;
if (blockRun.AG > superBlock^.numAGs) then exit;
if (superBlock^.usedBlocks + blockRun.len > superBlock^.numBlocks) then exit; // should probably give a louder warning
inode := loadInode(superBlock^.BAT);
assert(inode <> NIL); // should probably check for failure
// now that the BAT inode is loaded, we can scan through it looking for
// a free bit
// we read in one block at a time
FBLSize := superBlock^.blockSize;
GetMem(FBL, FBLSize);
assert(FBL <> NIL);
fillchar(FBL^, FBLSize, 0); // better safe than sorry
assert(readDataStream(inode, blockRun.AG shl (superblock^.AGShift shr 3), FBL, FBLSize) = 0);
byteOffset := blockRun.start shr 3;
bitOffset := 7-(blockRun.start and 7);
baseBlock := (blockRun.AG shl superBlock^.AGShift) + blockRun.start;
curBlock := baseBlock;
freeCount := 0;
// an optimization would be to check to see if the byte is -1, thus skipping 8 at a time
// This will need to be implemented (but can be later)
repeat
inc(curBlock);
if (FBL^[byteOffset] and (1 shl bitOffset) = 0) then
inc(freeCount)
else
freeCount := 0;
if (freeCount = blockRun.len) then break;
if (bitOffset = 0) then
begin
bitOffset := 7;
inc(byteOffset);
end
else dec(bitOffset);
if (byteOffset = superBlock^.blocksPerAG) then
begin
byteOffset := 0;
curBlock := 0;
inc(blockRun.AG);
if (blockRun.AG = superBlock^.numAGs) then blockRun.AG := 0;
assert(readDataStream(inode, blockRun.AG shl (superblock^.AGShift shr 3), FBL, FBLSize) = 0);
bitOffset := 7;
end;
until (curBlock = baseBlock);
if (freeCount = blockRun.len) then // we found a run
begin
blockRun.AG := (curBlock-blockRun.len) shr superBlock^.AGShift;
blockRun.start := (curBlock-blockRun.len) and (superBlock^.blocksPerAG-1);
markUsedBlocks(blockRun);
result := blockRun;
end
else writeln('Failure in allocBRun()');
FreeMem(FBL, FBLSize);
assert(unloadInode(inode) = 0);
end; // TUbixFS.allocBRun
function TUbixFS.allocInode;
var
inode:PUbixfsInode;
inodeSize:uInt32;
blockRun:TBlockRun;
begin
// XXX ToDo:
// need to fill in the rest of the fields in the inode record
result := NIL;
if (superBlock = NIL) then exit;
assert(parentIAddr.AG < superBlock^.numAGs);
if (parentIAddr.AG >= superBlock^.numAGs) then exit; // parent addr is outside out block count?
// ask for a single block somewhere around the beginning of the requested AG
setBlockRun(blockRun, parentIAddr.AG, 0, 1);
blockRun := allocBRun(blockRun);
assert(blockRun.len <> 0);
if (blockRun.len = 0) then exit; // this would be bad
inodeSize := superBlock^.inodeSize;
GetMem(inode, inodeSize);
assert(inode <> NIL);
if (inode = NIL) then exit;
fillchar(inode^, inodeSize, 0);
with inode^ do
begin
magic := UBIXFS_INODE_MAGIC;
// inodes point to themselves
inodeNum := blockRun;
uid := 0; // this doesn't mean much here
gid := 0;
// mode := ??
flags := INODE_NO_FLAG;
// createTime :=
// modifiedTime :=
parent := parentIAddr;
setIAddr(attributes, 0, 0, 0);
inodeSize := superBlock^.inodeSize;
// data := 0;
refCount := 0; // maybe 1?
// there's no data yet in the file, so don't
// set blocks to anything (cleared above)
blockCount := 0;
end; // with
result := inode;
end; // TUbixFS.allocInode
function TUbixFS.extendFile;
var
blockRun, exBlockRun:TBlockRun;
i:integer;
found:boolean;
begin
// XXX ToDo:
// Need to add support for indirect and double-indirect blocks
// Fix 4GB issue
// Check to see if the block extension is the last one in direct blocks
result := -1; // failure by default
// basic sanity checks
if (inode = NIL) or (superBlock = NIL) then exit;
// if the file size is 0 then this is the first extension.
if (inode^.blocks.size = 0) then // first extension
begin
// a file's data is stored in the next allocation group from it's parent
// inode. This will help distribute the data across the disk.
setBlockRun(exBlockRun, nextAG(inode^.inodeNum.AG), 0, numBlocks);
exBlockRun := allocBRun(exBlockRun); // try to allocate a blockrun
assert(exBlockRun.len <> 0);
if (exBlockRun.len = 0) then exit; // erp. Couldn't extend the file
inode^.blocks.direct[0] := exBlockRun; // Set the first direct run
// The space allocated for the file can and may be different than the
// actual size. Set the maxDirect to the size (in bytes) of how much space
// we allocated
inode^.blocks.maxDirect := numBlocks shl superBlock^.blockShift;
end // size = 0
else
begin
// the size of the file wasn't 0 (handled above). We need to extend the
// file out. Find where the last blocks of the file are
// inode^.blocks.size is now a 32-bit integer because the compiler doesn't
// properly support 64-bit ints. Therefore, we are limited to 4GB
blockRun := findBRun(@inode^.blocks, inode^.blocks.size shr superBlock^.blockShift);
assert(blockRun.len <> 0); // hm.. was exBlockRun
if (blockRun.len = 0) then exit; // This would be odd
exBlockRun := blockRun; // copy the last run to exBlockRun
exBlockRun.len := numBlocks; // set the requested number of blocks
exBlockRun := allocBRun(exBlockRun); // allocate a block run near the previous one
if (exBlockRun.len = 0) then exit; // should probably yell louder about this
// blockRun points to the last blockRun structure associated with this inode
if (exBlockRun.AG = blockRun.AG) and (exBlockRun.start = blockRun.start+1) then
begin
// the extended block run continues a previous run.
// scan through to find what the last run was
with inode^, blocks do
begin
// XXX only works for direct block runs for now
// I'm going to have to put a special check in to see if
// we're the last entry in the direct field
if (direct[high(direct)].len <> 0) then assert(false);
for i := low(direct) to high(direct) do
if (direct[i].len = 0) then break;
assert(i <> low(direct)); // big problemo
// make sure we don't overflow the length
if (dword(direct[i-1].len) + numBlocks < 65535) then
begin
inc(direct[i-1].len, numBlocks); // update the previous entry
end
else // oops, we overflowed the length
begin
// need to check to see if we were the last direct entry. If we were
// then we need to allocate an indirect block and store it in there
if (i = high(direct)) then assert(false);
direct[i] := exBlockRun;
end; // else
end; // with
end
else // extended blockRun isn't contiguous to the previous one
begin
with inode^, blocks do
begin
// XXX only works for direct block runs for now
// I'm going to have to put a special check in to see if
// we're the last entry in the direct field
if (direct[high(direct)].len <> 0) then assert(false);
for i := low(direct) to high(direct) do
if (direct[i].len = 0) then break;
// i points to the entry in the direct[] block runs to the first free slot
direct[i] := exBlockRun; // set this entry to the extended block run
// now update the maxDirect size
maxDirect := maxDirect + numBlocks shl superBlock^.blockShift;
end; // with
end; // else
end; // else size <> 0
inc(inode^.blockCount, numBlocks); // update the blockCount in the inode
// no matter where we stored the extended blockRun, we have updated the inode
assert(saveInode(inode) = 0); // 0 result means success
result := 0; // no error
end; // TUbixFS.extendFile
function TUbixFS.findBRun;
var
position:uInt32; // limits filesize to 4gb
begin
setBlockRun(result, 0, 0, 0);
if (dataStream = NIL) then exit;
position := blockNum shl superBlock^.blockShift;
if (position < dataStream^.maxDirect) then
result := findBRunDirect(dataStream, blockNum)
else if (position < dataStream^.maxIndirect) then
result := findBRunIndirect(dataStream, blockNum)
else if (position < dataStream^.maxDIndirect) then
result := findBRunDIndirect(dataStream, blockNum)
else writeln('Block position is not mapped by dataStream');
end; // TUbixFS.findBRun
function TUbixFS.findBRunDirect;
var
maxBlock:uInt32;
sum, i, offset:uInt32;
begin
// set the result to a nil blockrun
setBlockRun(result, 0, 0, 0);
// maxDirect is in bytes, so shift it down to blocks
maxBlock := dword(dataStream^.maxDirect) shr superblock^.blockShift;
if (blockNum > maxBlock) then
begin
writeln('Requested block outside direct block run range');
exit;
end;
sum := 0;
with dataStream^ do
for i := low(direct) to high(direct) do
with direct[i] do
begin
if (blockNum < sum + len) then // found it
begin
offset := blockNum - sum;
setBlockRun(result, AG, start + offset, len - offset);
exit
end;
inc(sum, len);
end; // for
end; // TUbixFS.findBRunDirect
function TUbixFS.findBRunIndirect;
begin
// XXX ToDo:
// load and scan the indirect block for the corresponding block run
setBlockRun(result, 0, 0, 0);
end; // TUbixFS.findBRunIndirect
function TUbixFS.findBRunDIndirect;
begin
// XXX ToDo:
// find the position in the double-indirect range
// load the appropiate double-indirect block and get the blockRun
setBlockRun(result, 0, 0, 0);
end; // TUbixFS.findBRunDIndirect
function TUbixFS.freeUsedBlocks;
var
inode:PUbixfsInode;
FBL:int8ArrayPtr;
FBLSize:uInt32;
startByte, endByte:uInt32;
bitCount, bitOffset:uInt32;
startBlock:uInt32;
byteOffset:uInt32;
begin
result := -1;
// basic sanity checks
if ((superBlock = NIL) or (blockRun.len = 0)) then exit;
// first thing to do is load the BAT inode
inode := loadInode(superBlock^.BAT);
assert(inode <> NIL);
// Okay.. inode is the BAT inode. We need this to read from/write to the BAT
// Compute how much of the BAT we have to read in.
with blockRun, superBlock^ do
begin
startBlock := (AG shl AGShift) + start;
startByte := startBlock shr 3; // divide by 8 since it's a bitfield
endByte := (startBlock + len) shr 3; // divide by 8 since it's a bitfield
FBLSize := endByte - startByte + 1;
GetMem(FBL, FBLSize);
assert(readDataStream(inode, startByte, FBL, FBLSize) = 0);
bitOffset := 7-(startBlock and 7);
byteOffset := 0;
for bitCount := 1 to len do
begin
FBL^[byteOffset] := FBL^[byteOffset] and not (1 shl bitOffset);
if (bitOffset = 0) then
begin
bitOffset := 7;
inc(byteOffset);
end
else dec(bitOffset);
end; // for bitCount
// save back out the modified block
assert(writeDataStream(inode, startByte, FBL, FBLSize) = 0);
FreeMem(FBL, FBLSize);
assert(unloadInode(inode) = 0);
usedBlocks := usedBlocks - len;
end; // with
result := 0;
end; // TUbixFS.freeUsedBlocks
function TUbixFS.getFileSize;
begin
// XXX ToDo:
// Fix to support sizes > 4GB
result := 0;
if (inode = NIL) then exit;
// inode^.blocks.size is now a 32-bit integer.
// This limits the filesize to 4GB
result := inode^.blocks.size;
end; // TUbixFS.getFileSize
function TUbixFS.getFileSpace;
begin
// getFileSpace() will return the size of the file's allocated space
// XXX ToDo:
// Fix to support sizes > 4GB
result := 0;
if (inode = NIL) or (superBlock = NIL) then exit;
result := inode^.blockCount shl superBlock^.blockShift;
end; // TUbixFS.getFileSpace
function TUbixFS.iAddrToStr;
var s:string;
begin
// Note: this won't work in C/C++ because strings are really null
// terminated character arrays. So, the B^Tree won't hold a string
// with nulls in it properly
length(s) := sizeof(iAddr);
move(iAddr, s[1], sizeof(iAddr));
result := s;
end; // TUbixFS.iAddrToStr;
function TUbixFS.loadInode;
var
u:uPtr;
inode:PUbixfsInode;
begin
// XXX ToDo:
// Read through file cache
// Do more sanity checks on the inode
result := NIL;
if (superBlock = NIL) or (dev = NIL) or (inodeCache = NIL) then exit;
if inodeCache^.Find(@iAddr, u) then // inode was in cache
begin
inode := u.p;
assert(inode <> NIL);
end
else
begin
// allocate space for the inode
GetMem(inode, superBlock^.inodeSize);
assert(inode <> NIL);
// clear it (this isn't absolutely necessary, but better
// safe than sorry). Also, it's only done the first time
// the inode is loaded, so speed isn't as much a concern
fillchar(inode^, superBlock^.inodeSize, 0);
with iAddr do
writeln('inode: ', AG, ':', start, ':', len);
with iAddr, superBlock^ do
dev^.read(dev,
inode,
((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT),
inodeSize shr SECTOR_SHIFT
); // dev^.read
// should probably check more than this
if (inode^.magic <> UBIXFS_INODE_MAGIC) then
begin
FreeMem(inode, superBlock^.inodeSize);
writeln('TUbixFS.loadInode() failed to load an inode!');
exit;
end;
// now insert the pointer into the InodeCache
u.p := inode;
assert(inodeCache^.Insert(@iAddr, u));
end;
inc(inode^.refCount); // increment the reference count
result := inode;
end; // TUbixFS.loadInode
function TUbixFS.markUsedBlocks;
var
inode:PUbixfsInode;
FBL:uInt8ArrayPtr;
FBLSize:uInt32;
startByte, endByte:uInt32;
bitCount, bitOffset:uInt32;
startBlock:uInt32;
byteOffset:uInt32;
begin
result := -1;
// basic sanity checks
if ((superBlock = NIL) or (blockRun.len = 0)) then exit;
with blockRun do
writeln('---- marking used blocks: ', AG, ':', start, ':', len);
// first thing to do is load the BAT inode
inode := loadInode(superBlock^.BAT);
assert(inode <> NIL);
// Okay.. inode is the BAT inode. We need this to read from/write to the BAT
// Compute how much of the BAT we have to read in.
with blockRun, superBlock^ do
begin
startBlock := (AG shl AGShift) + start;
startByte := startBlock shr 3;
endByte := (startBlock + len) shr 3;
FBLSize := endByte - startByte + 1;
GetMem(FBL, FBLSize);
assert(readDataStream(inode, startByte, FBL, FBLSize) = 0);
fillchar(FBL^, FBLSize, 0); // better safe than sorry
bitOffset := 7-(startBlock and 7);
byteOffset := 0;
for bitCount := 1 to len do
begin
FBL^[byteOffset] := FBL^[byteOffset] or (1 shl bitOffset);
if (bitOffset = 0) then
begin
bitOffset := 7;
inc(byteOffset);
end
else dec(bitOffset);
end; // for bitCount
assert(writeDataStream(inode, startByte, FBL, FBLSize) = 0);
FreeMem(FBL, FBLSize);
assert(unloadInode(inode) = 0);
usedBlocks := usedBlocks + len;
end; // with
result := 0;
end; // TUbixFS.markUsedBlocks
function TUbixFS.nextAG;
begin
// there's no error we can throw from here if superblock is NIL,
// so don't bother to check. Just hope whomever calls this
// function has done some basic sanity checks
result := (AG+1) mod superBlock^.numAGs
end; // TUbixFS.nextAG
function TUbixFS.pathToIAddr;
function recursePath(path:PChar; len:integer; iAddr:inodeAddr):inodeAddr;
var
dirTree:PBTree;
u:uPtr;
nameSize:integer;
begin
// if the string length is 0 then we have found the inode in the previous
// recurse, so return what we found.
if (len = 0) then
begin
result := iAddr;
exit;
end
else
setIAddr(result, 0, 0, 0);
// hm. If we're looking for something like ``/.'' then the ``/'' is stripped
// off in the main call leaving only ``.'', which means that this malformed
// path check would trigger. Same with /foo/bar if bar is a dir.
//if uInt8ArrayPtr(path)^[0] = ord('/') then exit; // malformed path
if not (isValidIAddr(iAddr)) then exit; // sanity check
nameSize := 0;
while (len > 0) and (uInt8ArrayPtr(path)^[nameSize] <> ord('/')) do
begin
inc(nameSize);
dec(len);
end;
uInt8ArrayPtr(path)^[nameSize+1] := 0;
if (len <> 0) then dec(len);
new(dirTree, open(IAddrToStr(iAddr), new(PUbixBTreeVFS, init(@self))));
if (dirTree = NIL) then exit; // hm.. what happens to the PUbixBTreeVFS is this is NIL?
// note that the dirTree is a tree of BT_PCHAR, and searches are case sensitive.
// if the tree doesn't exist, Find() should return false.
if dirTree^.Find(path, u) then result := recursePath(path + nameSize + 1, len, u.iAddr);
dispose(dirTree, done);
end; // recursePath
var len:integer;
begin
setIAddr(result, 0, 0, 0);
if (path = NIL) or (superBlock = NIL) then exit;
len := strlen(path);
if (len = 0) then exit; // empty string
if uInt8ArrayPtr(path)^[0] <> ord('/') then exit; // malformed path
result := recursePath(path+1, len-1, superBlock^.rootDir);
end; // TUbixFS.pathToIAddr
procedure TUbixFS.printSuperBlock;
begin
if (superBlock = NIL) then exit;
with superBlock^ do
begin
writeln('superBlock^.name........... ', name);
writeln('superBlock^.magic1......... ', hex(magic1));
writeln('superBlock^.fsByteOrder.... ', fsByteOrder);
writeln('superBlock^.blockSize...... ', blockSize);
writeln('superBlock^.blockShift..... ', blockShift);
writeln('superBlock^.numBlocks...... ', numBlocks);
writeln('superBlock^.usedBlocks..... ', usedBlocks);
writeln('superBlock^.magic2......... ', hex(magic2));
writeln('superBlock^.blocksPerAG.... ', blocksPerAG);
writeln('superBlock^.AGShift........ ', AGShift);
writeln('superBlock^.numAGs......... ', numAGs);
writeln('superBlock^.flags.......... ', hex(flags));
writeln('superBlock^.magic3......... ', hex(magic3));
with superBlock^.BAT do
writeln('superBlock^.BAT............ ', AG, ':', start, ':', len);
with superBlock^.rootDir do
writeln('superBlock^.rootDir........ ', AG, ':', start, ':', len);
end;
end; // TUbixFS.printSuperBlock
function TUbixFS.saveInode;
begin
// XXX ToDo:
// Write through cache
result := -1;
if (superBlock = NIL) or (dev = NIL) or (inode = NIL) then exit;
with inode^.inodeNum, superBlock^ do
dev^.write(dev,
inode,
((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT),
inodeSize shr SECTOR_SHIFT
); // device^.write
result := 0;
end; // TUbixFS.saveInode
function TUbixFS.isValidIAddr;
begin
result := (iAddr.len <> 0) and (iAddr.AG < superBlock^.numAGs);
end; // TUbixFS.isValidIAddr
function TUbixFS.readDataStream;
var
tmpBlock:uInt8ArrayPtr;
blockRun:TBlockRun;
size, remainder:uInt32;
blockStart:uInt32;
blocksToRead:uInt32;
bytesToRead:uInt32;
begin
// XXX ToDo:
// Read from cache
result := -1;
if ((inode = NIL) or (buf = NIL)) then exit;
tmpBlock := NIL;
size := length;
remainder := position mod superBlock^.blockSize;
if (remainder <> 0) then
begin
// the position we've been asked to read from doesn't align to
// a block size. We need to read in the previous block and pad
// the data
blockStart := position shr superBlock^.blockShift;
blockRun := findBRun(@inode^.blocks, blockStart);
assert(blockRun.len > 0);
GetMem(tmpBlock, superBlock^.blockSize);
assert(tmpBlock <> NIL);
if (tmpBlock = NIL) then exit;
with superBlock^, blockRun do
dev^.read(dev,
tmpBlock,
((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
blockSize shr SECTOR_SHIFT
); // dev^.write
bytesToRead := min(size, (superBlock^.blockSize - remainder));
move(tmpBlock^[remainder], buf^, bytesToRead);
inc(buf, bytesToRead);
dec(size, bytesToRead);
inc(position, bytesToRead);
end; // if remainder != 0
remainder := size mod superBlock^.blockSize;
dec(size, remainder);
// Size is currently in bytes. The remainder variable above holds the number
// of remaining bytes (if any) outside full blocks we have to write.
// convert the size into blocks
size := size shr superBlock^.blockShift;
while (size > 0) do
begin
blockRun := findBRun(@inode^.blocks, position shr superBlock^.blockShift);
assert(blockRun.len > 0);
blocksToRead := min(blockRun.len, size);
with inode^, superBlock^, blockRun do
dev^.read(dev,
buf,
((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
blockSize shr SECTOR_SHIFT
); // dev^.read
dec(size, blocksToRead);
inc(buf, blocksToRead shl superBlock^.blockShift);
end; // while
if (remainder <> 0) then
begin
// There is less than a block of data left to read.
blockStart := (position+length-remainder) shr superBlock^.blockShift;
blockRun := findBRun(@inode^.blocks, blockStart);
assert(blockRun.len > 0);
if (tmpBlock = NIL) then GetMem(tmpBlock, superBlock^.blockSize);
assert(tmpBlock <> NIL);
with inode^, superBlock^, blockRun do
dev^.read(dev,
tmpBlock,
((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
blockSize shr SECTOR_SHIFT
); // dev^.read
move(tmpBlock^, buf^, remainder);
end;
if (tmpBlock <> NIL) then FreeMem(tmpBlock, superBlock^.blockSize);
result := 0;
end; // TUbixFS.readDataStream
procedure TUbixFS.setBlockRun;
begin
blockRun.AG := AG;
blockRun.start := start;
blockRun.len := len;
end; // TUbixFS.setBlockRun
procedure TUbixFS.setIAddr;
begin
inode.AG := AG;
inode.start := start;
inode.len := len;
end; // TUbixFS.setIAddr
function TUbixFS.strToIAddr;
begin
// Note: this won't work in C/C++ because strings are really null
// terminated character arrays. So, the B^Tree won't hold a string
// with nulls in it properly
// begin fiendishly clever hack
move(s[1], result, sizeof(inodeAddr));
// end fiendishly clever hack
end; // TUbixFS.strToIAddr
function TUbixFS.unloadInode;
var
u:uPtr;
begin
// XXX ToDo:
// I guess this would be a good place to determine if the
// file has been deleted, and free all resources associated
// with it after the ref count drops to 0
result := -1;
if (inode = NIL) or (inodeCache = NIL) then exit;
dec(inode^.refCount); // decrese the reference count
if (inode^.refCount = 0) then
begin
// the last handle to the inode was closed, so de-allocate
// the memory for the inode and delete it out of the cache
u.p := inode;
inodeCache^.Delete(@inode^.inodeNum, u);
// we have inodes in the cache, but they might have been modified
// The proper solution to this is to not have an inode cache and
// instead use a real block cache and write out modified blocks.
// For the time being, save out this inode to the device
saveInode(inode);
{ if (dev <> NIL) then
with inode^.inodeNum, superBlock^ do
dev^.write(dev,
inode,
((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT),
inodeSize shr SECTOR_SHIFT
); // device^.write }
FreeMem(inode, inode^.inodeSize);
end;
result := 0;
end; // TUbixFS.unloadInode
function TUbixFS.vfs_fCreate;
var
inode:PUbixFSInode;
dirTree:PBTree;
dirIAddr, fileIAddr:inodeAddr;
filedes:int64;
u:uPtr;
searchRec:BTreeSearchRec;
len : integer;
path:PChar;
filenamePtr:PChar;
curPos:integer;
begin
result := -1;
if (filename = NIL) or (superBlock = NIL) then exit;
// we need to split the filename into it's component path and filename pieces.
// From there we need to see if the file exists. If it does then I guess we
// should truncate the file and load the inode. Otherwise allocate a new inode
len := strlen(filename);
if (len = 0) then exit;
if uInt8ArrayPtr(filename)^[0] <> ord('/') then exit; // malformed path
GetMem(path, len+1);
strCopy(path, filename);
curPos := len;
setIAddr(dirIAddr, 0, 0, 0); // zero out the inode address for the directory
setIAddr(fileIAddr, 0, 0, 0); // zero out the inode address for the file
while (curPos > 0) and (uInt8ArrayPtr(path)^[curPos] <> ord('/')) do dec(curPos);
fileNamePtr := path + curPos + 1;
if (curPos = 0) then
begin
// file is in the root dir
writeln('path: /');
writeln('filename: ', fileNamePtr);
dirIAddr := pathToIAddr('/');
end
else
begin
uInt8ArrayPtr(path)^[curPos] := 0; // null out the separator
writeln('Path: ', path);
writeln('Filename: ', fileNamePtr);
dirIAddr := pathToIAddr(path);
end;
with dirIAddr do
writeln('iaddr to path: ', AG, ':', start, ':', len);
assert(isValidIAddr(dirIAddr));
if not isValidIAddr(dirIAddr) then exit;
// should probably check to make sure that we're opening a directory and not a file
write('creating new dir tree...');
new(dirTree, open(IAddrToStr(dirIAddr), new(PUbixBTreeVFS, init(@self))));
writeln('done');
if (dirTree = NIL) then exit;
if dirTree^.Find(fileNamePtr, u) then
begin
writeln(fileNamePtr, ' already exists');
end
else
begin
writeln('creating new file: ', fileNamePtr);
writeln('allocating new inode');
inode := allocInode(dirIAddr); // alloc a new inode (pass in the parent iaddr)
assert(inode <> NIL); // make sure it isn't nil
if (inode = NIL) then exit;
saveInode(inode); // save the new inode
u.iAddr := inode^.inodeNum; // copy the inode addr to a uPtr
// now add the file to the directory
writeln('Inserting ', fileNamePtr, ' into directory returned ', dirTree^.Insert(fileNamePtr, u));
end;
dispose(dirTree, done);
FreeMem(path, len+1);
result := 0;
end; // TUbixFS.vfs_fCreate
function TUbixFS.vfs_fExist;
begin
// XXX ToDo:
// Find out why fExist('/.') fails
result := -1;
if ((superBlock = NIL) or (path = NIL)) then exit;
if (not isValidIAddr(superBlock^.rootDir)) then exit;
if isValidIAddr(pathToIAddr(path)) then result := 0;
end; // TUbixFS.vfs_fExist
function TUbixFS.vfs_format;
var
sector:array[0..511] of byte;
fs:PUbixFS;
logicalBlocks, fsBATSize, fsBATBlocks:uInt32;
sb:PdiskSuperBlock;
inode:PubixfsInode;
i, fsBlockShift, lBlockShift, nodeSize, keyLength:integer;
blockRun:TBlockRun;
remainderBlocks:uInt32;
blockBit:shortint;
begin
result := -1;
if (device = NIL) then exit;
if (fsBlockSize < 1024) then
begin
writeln('Blocksize must be >= 1024');
exit;
end;
// zero out the sector
fillchar(sector, sizeof(sector), 0);
writeln('device^.sectors: ', device^.sectors);
if not quick then
begin
system.write('clearing device... ');
for i := 0 to device^.sectors-1 do
device^.write(device, @sector, i, 1);
writeln('done');
end; // if !quick
fsBlockShift := 10; {minimum is 1024}
while (1 shl fsBlockShift < fsBlockSize) do inc(fsBlockShift);
// allocate a new superBlock and clear it
new(sb);
if (sb = NIL) then exit;
fillchar(sb^, sizeof(diskSuperBlock), 0);
// device^.sectors is the number of 512 byte sectors
lBlockShift := fsBlockSize shr 9;
writeln('lBlockShift: ', lBlockShift);
logicalBlocks := device^.sectors div lBlockShift; // logical blocks
writeln('logical blocks: ', logicalBlocks);
with sb^ do
begin
strcopy(name, 'UbixFS');
magic1 := UBIXFS_MAGIC_1;
fsByteOrder := 0;
blockSize := fsBlockSize;
blockShift := fsBlockShift;
numBlocks := logicalBlocks;
usedBlocks := 0;
inodeSize := fsBlockSize;
magic2 := UBIXFS_MAGIC_2;
blocksPerAG := min(65536, blockSize shl 3);
AGShift := 10; // realistically it should be a min of 13
while (1 shl AGShift <> blocksPerAG) do inc(AGShift);
numAGs := (dword(numBlocks)+(blocksPerAG-1)) div blocksPerAG;
flags := UBIXFS_CLEAN;
setBlockRun(logBlocks, 0, 0, 0);
logStart := 0;
logEnd := 0;
magic3 := UBIXFS_MAGIC_3;
setIAddr(indicies, 0, 0, 0);
setIAddr(rootDir, 0, 0, 0);
end; // with
// create the free block list inode
getmem(inode, fsBlockSize); // make the inode the same size as a block
if (inode = NIL) then exit;
fillchar(inode^, sb^.inodeSize, 0);
with inode^ do
begin
magic := UBIXFS_INODE_MAGIC;
// inodes point to themselves
setIAddr(inodeNum, 0, sb^.logBlocks.len+1, 1); // the +1 for the start might be wrong
setIAddr(parent, 0, 0, 0);
// If the partition is larger than 550GB we will need to allocate more than
// one blockRun for the BAT.
fsBATSize := logicalBlocks shr 3; // compute how many bytes the BAT takes up
fsBATBlocks := (fsBATSize + fsBlockSize-1) div fsBlockSize;
writeln('fsBATSize: ', fsBATSize);
writeln('fsBATBlocks: ', fsBATBlocks);
blockCount := fsBATBlocks;
setBlockRun(blocks.direct[0], 0, inodeNum.start+1, fsBATBlocks);
blocks.size := (fsBATBlocks * fsBlockSize); // round the file size up to the nearest block size
blocks.maxDirect := blocks.size-1;
uid := 0;
gid := 0;
mode := S_IFDIR;
flags := INODE_NO_FLAG;
setIAddr(attributes, 0, 0, 0);
inodeSize := sb^.inodeSize;
end; {with}
sb^.BAT := inode^.inodeNum;
writeln('writing out superblock to sector 1');
device^.write(device, sb, 1, 1);
// note that the superblock does not contain proper inode addresses for the
// BAT and rootDir. Those are sync'd to disk in the destructor after they've been
// created below.
system.write('Writing out BAT inode... ');
// write out the Block Allocation Table inode
with inode^.inodeNum, sb^ do
device^.write(device,
inode,
((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT),
inodeSize shr SECTOR_SHIFT
); // device^.write
writeln('done');
// Okay, the superblock is the only piece of information needed
// to mount an UbixFS partition
new(fs, init(device));
assert(fs <> NIL);
assert(fs^.vfs_init() = 0);
// The freshly formatted parition is now mounted.
// From here we need to create:
// free block list file contents
// journal
// root dir
// inode still points to the BAT. Clear out the file in case people decided to
// use quick formatting
for i := 0 to (dword(inode^.blocks.size) div sizeof(sector))-1 do
fs^.writeDataStream(inode, i*sizeof(sector), @sector, sizeof(sector));
setBlockRun(blockRun, 0, 0, 1);
fs^.markusedBlocks(blockRun); // mark superBlock block as used
// mark the journal as used here
fs^.markUsedBlocks(inode^.inodeNum); // mark the BAT inode as used
// now mark the BAT file blocks as used. Note that if the BAT takes up
// more than one blockRun (unlikely, but not impossible) then this code
// will have to check for that.
writeln('BAT inode blocks.direct[0].len: ', inode^.blocks.direct[0].len);
fs^.markUsedBlocks(inode^.blocks.direct[0]);
// If the number of blocksPerAG doesn't divide evenly into the
// number of logical blocks, mark out all the "remainder" blocks
// We need to mark the blocks as used in the FBL manually because
// if we do it through markUsedBlocks() it will change the usedBlocks
// count.
remainderBlocks := (fsBATBlocks shl sb^.blockShift) - fsBATSize;
writeln('RemainderBlocks: ', remainderBlocks);
if (remainderBlocks <> 0) then
begin
// check if the remainder blocks are evenly divisible by 8
if (remainderBlocks and 7 <> 0) then
begin
blockBit := uInt8(1 shl (remainderBlocks and 7)) -1;
fs^.writeDataStream(inode, fsBatSize, @blockBit, 1);
blockBit := -1;
for i := fsBatSize+1 to (fsBATBlocks shl sb^.blockShift)-1 do
fs^.writeDataStream(inode, i, @blockBit, 1);
end
else
begin
blockBit := -1;
for i := fsBatSize to (fsBATBlocks shl sb^.blockShift)-1 do
fs^.writeDataStream(inode, i, @blockBit, 1);
end;
end;
fs^.vfs_mkdir('/');
//writeln(fs^.vfs_fExist('/.')); // <- this blows up!?
fs^.printSuperBlock;
dispose(fs, done);
writeln('Format complete!');
dispose(sb);
freemem(inode, fsBlockSize);
end; // TUbixfS.vfs_format
function TUbixFS.vfs_init;
begin
// This function should probably not be called more than once without
// calling the appropiate shutdown code
writeln('TUbixFS.vfs_init()');
result := -1;
if (dev = NIL) then exit;
new(superBlock);
assert(superBlock <> NIL);
if (superBlock = NIL) then exit;
fillchar(superBlock^, sizeof(superBlock^), 0); // better safe than sorry
// read in the superBlock. It's always the 1st block on the partition (counting from 0)
system.write('reading in superBlock... ');
dev^.read(dev, superBlock, 1, 1);
writeln('done');
assert(superBlock^.magic1 = UBIXFS_MAGIC_1);
assert(superBlock^.magic2 = UBIXFS_MAGIC_2);
assert(superBlock^.magic3 = UBIXFS_MAGIC_3);
assert(strcomp(superBlock^.name, 'UbixFS') = 0);
assert((1 shl superBlock^.blockShift) = superBlock^.blockSize);
assert((1 shl superBlock^.AGShift) = superBlock^.blocksPerAG);
assert(superBlock^.flags = UBIXFS_CLEAN);
result := 0; // success
// create the file decriptor tree
new(inodeCache, init('', 256, BT_INT64, NIL));
end; // TUbixFS.vfs_init
function TUbixFS.vfs_mkdir;
const
dot:array[0..1] of char = '.'#0;
dotdot:array[0..2] of char = '..'#0;
var
inode:PUbixFSInode;
iAddr:inodeAddr;
bRun:TBlockRun;
len:integer;
dirTree:PbTree;
u:uPtr;
begin
// XXX ToDo:
// Ability to add sub directories
result := -1;
assert(path <> NIL);
assert(superBlock <> NIL);
if (path = NIL) or (superBlock = NIL) then exit;
len := strlen(path);
assert(len <> 0);
if (len = 0) then exit;
if (len = 1) and (uInt8ArrayPtr(path)^[0] = ord('/')) then
begin
writeln('Creating root directory');
// wanting to create the root. Check to see if the root dir inode is empty
assert(not isValidIAddr(superBlock^.rootDir));
if isValidIAddr(superBlock^.rootDir) then exit; // should set a proper error code here
setIAddr(iAddr, 0, 0, 0); // technically the "parent" iaddr
inode := allocInode(iAddr); // allocate an inode (parent points to la-la land)
assert(inode <> NIL); // make sure it isn't nil
if (inode = NIL) then exit;
superBlock^.rootDir := inode^.inodeNum; // assign the root dir
saveInode(inode); // save the inode
u.iAddr := inode^.inodeNum; // copy the inode addr to a uPtr
// open the root dir
with superBlock^.rootDir do
writeln('Root dir inode: ', AG, ':', start, ':', len);
writeln('Opening root dir tree');
new(dirTree, init(iAddrToStr(superBlock^.rootDir), 1024, BT_PCHAR, new(PUbixBTreeVFS, init(@self))));
writeln('Inserting ''.''');
// Get ready to insert the '.' :: inode^.inodeNum key :: value pair
writeln('dirTree^.Insert(@dot, u) returned ', dirTree^.Insert(@dot, u)); // '.' points to ourself
dispose(dirTree, done); // close and save the directory
// saveInode(inode); // save the inode
freemem(inode, inode^.inodeSize); // free the memory associate with the inode (saved above)
end
else
begin
// subdir support goes here
end;
result := 0;
end; // TUbixFS.vfs_mkdir
function TUbixFS.vfs_read;
var
inode:PUbixFSInode;
u:uPtr;
begin
result := -1;
if (buffer = NIL) or (superBlock = NIL) or (inodeCache = NIL) then exit;
if not inodeCache^.Find(@filedes, u) then exit;
inode := u.p;
if (inode = NIL) then exit;
assert(readDataStream(inode, position, buffer, size) = 0);
result := size;
end; // TUbixFS.vfs_read
function TUbixFS.vfs_write;
var
inode:PUbixFSInode;
u:uPtr;
begin
result := -1;
if (buffer = NIL) or (inodeCache = NIL) then exit;
if not inodeCache^.Find(@filedes, u) then exit;
inode := u.p;
if (inode = NIL) then exit;
assert(writeDataStream(inode, position, buffer, size) = 0);
result := size;
end; // TUbixFS.vfs_write
function TUbixFS.writeDataStream;
var
tmpBlock:uInt8ArrayPtr;
blockRun:TBlockRun;
size, remainder:uInt32;
blockStart:uInt32;
blocksToWrite:uInt32;
bytesToWrite:uInt32;
inodeChanged:boolean;
begin
// XXX ToDo:
// Read/Write through cache
// fix block extension size
result := -1;
if ((inode = NIL) or (buf = NIL) or (superBlock = NIL)) then exit;
tmpBlock := NIL;
inodeChanged := FALSE;
if (position + length > getFileSpace(inode)) then
begin
// the next line is slightly wrong. We extend 4 blocks,
// but we may be writing out more than 4 blocks worth of
// data.
extendFile(inode, 4); // XXX pick a reasonable amount to extend by
inode^.blocks.size := position+length; // update the blocks
writeln('inode^.blocks.size = ', inode^.blocks.size);
inodeChanged := TRUE;
end
else
if (position + length > inode^.blocks.size) then
begin
// The above case checks to see if we're writing out past
// the end of the allocated space on the device. If we do, we extend
// out the file. However, if we're just writing past the end of
// the file, we have to update the size.
inodeChanged := TRUE;
inode^.blocks.size := position+length;
end;
size := length;
remainder := position mod superBlock^.blockSize;
if (remainder <> 0) then
begin
// the position we've been asked to write to doesn't align to
// a block size. We need to read in the previous block and pad
// the data
blockStart := position shr superBlock^.blockShift;
blockRun := findBRun(@inode^.blocks, blockStart);
assert(blockRun.len > 0);
GetMem(tmpBlock, superBlock^.blockSize);
assert(tmpBlock <> NIL);
if (tmpBlock = NIL) then exit;
// read in the block
readDataStream(inode, blockStart, tmpBlock, superBlock^.blockSize);
bytesToWrite := min(size, (superBlock^.blockSize - remainder));
move(buf^, tmpBlock^[remainder], bytesToWrite);
with inode^, superBlock^, blockRun do
dev^.write(dev,
tmpBlock,
((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
blockSize shr SECTOR_SHIFT
); // dev^.write
inc(buf, bytesToWrite);
dec(size, bytesToWrite);
inc(position, bytesToWrite);
end; // if remainder != 0
remainder := size mod superBlock^.blockSize;
dec(size, remainder);
// Size is currently in bytes. The remainder variable above holds the number
// of remaining bytes (if any) outside full blocks we have to write.
// convert the size into blocks
size := size shr superBlock^.blockShift;
while (size > 0) do
begin
blockRun := findBRun(@inode^.blocks, position shr superBlock^.blockShift);
assert(blockRun.len > 0);
blocksToWrite := min(blockRun.len, size);
with inode^, superBlock^, blockRun do
dev^.write(dev,
buf,
((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
blockSize shr SECTOR_SHIFT
); // dev^.write
dec(size, blocksToWrite);
inc(buf, blocksToWrite shl superBlock^.blockShift);
end; // while
if (remainder <> 0) then
begin
// There is less than a block of data left to write. Read in the old block
blockStart := (position+length-remainder) shr superBlock^.blockShift;
blockRun := findBRun(@inode^.blocks, blockStart);
assert(blockRun.len > 0);
if (tmpBlock = NIL) then GetMem(tmpBlock, superBlock^.blockSize);
assert(tmpBlock <> NIL);
if (tmpBlock = NIL) then exit;
// read in the block
readDataStream(inode, blockStart, tmpBlock, superBlock^.blockSize);
move(buf^, tmpBlock^[0], remainder);
with inode^, superBlock^, blockRun do
dev^.write(dev,
tmpBlock,
((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
blockSize shr SECTOR_SHIFT
); // dev^.write
end;
if (tmpBlock <> NIL) then FreeMem(tmpBlock, superBlock^.blockSize);
if (inodeChanged) then saveInode(inode);
result := 0;
end; // TUbixFS.writeDataStream
destructor TUbixFS.done;
var
searchRec:BTreeSearchRec;
inode:PUbixfsInode;
begin
inherited done;
if (InodeCache <> NIL) then
begin
if (InodeCache^.getFirstKey(searchRec)) then
repeat
inode := searchRec.value.p;
if (inode <> NIL) then unloadInode(inode);
//if (inode <> NIL) then freemem(inode, inode^.inodeSize);
until not InodeCache^.findNext(searchRec);
writeln('inodes in InodeCache: ', InodeCache^.GetKeyCount());
dispose(InodeCache, done);
InodeCache := NIL;
end;
// we need to sync the superblock back out to disk
// Doing this should probably be done in sync() instead of here.
// We should also probably honour the clean/dirty flags and only
// write it out when needed.
if (dev <> NIL) then dev^.write(dev, superBlock, 1, 1);
if (superBlock <> NIL) then dispose(superBlock);
end; // TUbixFS.done
procedure TUbixFS.examine;
var
dirTree:PBTree;
searchRec:BTreeSearchRec;
u:uPtr;
begin
writeln('------------- examining FS -----------------');
write('Opening root dir...');
new(dirTree, open(IAddrToStr(superblock^.rootDir), new(PUbixBTreeVFS, init(@self))));
//dirTree^.PrintList('rootDir.txt');
writeln('done');
writeln('Root directory has ', dirTree^.getKeyCount(), ' entrie(s)');
write('Getting first key...');
dirTree^.getFirstKey(searchRec);
writeln('done');
writeln('key: ', pchar(searchRec.key)^);
with searchRec.value.iAddr do
writeln('value: ', AG, ':', start, ':', len);
dispose(dirTree, done);
writeln('------------------ done --------------------');
end;
end.