Cedar Nucleus (Files): per-file operations, locking file header data structures
FileHeaderImpl.mesa
Andrew Birrell May 2, 1983 9:59 am
Last Edited by: Levin, May 20, 1983 5:19 pm
DIRECTORY
Disk USING[ Channel, DoIO, GetBootChainLink, invalid, Label, labelCheck, ok, PageNumber, PageCount, Request, SameDrive, Status ],
DiskFace USING[ wordsPerPage ],
NewFile USING[ Error, FileID, FP, GetVolumeID, PageCount, PageNumber, PagesForWords, PropertyStorage, propertyWords, RC, Volume, VolumeFile, VolumeID ],
FileInternal,
VolumeFormat USING[ AbsID, Attributes, lastLogicalRun, LogicalPage, LogicalPageCount, LogicalRun, LogicalRunObject, RelID, RunPageCount ],
VM USING[ AddressToPageNumber, Allocate, Free, Interval, PageCount, PageNumber, PageNumberToAddress, SwapIn, Unpin, WordsToPages],
VMSideDoor USING[ Run, RunTableIndex, RunTableObject, RunTablePageNumber ];
FileHeaderImpl: CEDAR MONITOR LOCKS file USING file: Handle
IMPORTS Disk, NewFile, FileInternal, VM
EXPORTS DiskFace--RelID,AbsID,Attributes--, NewFile, FileInternal
SHARES NewFile =
BEGIN
OPEN File: NewFile;
-- ******** Data Types and minor subroutines ******** --
--DiskFace.--Attributes: PUBLIC TYPE = VolumeFormat.Attributes;
--DiskFace.--AbsID: PUBLIC TYPE = VolumeFormat.AbsID;
--DiskFace.--RelID: PUBLIC TYPE = VolumeFormat.RelID;
--File.--DA: PUBLIC TYPE = VolumeFormat.LogicalPage;
Handle: TYPE = REF Object;
--File.--Object: PUBLIC TYPE = FileInternal.Object;
RunTable: TYPE = FileInternal.RunTable;
RunTableObject: TYPE = VMSideDoor.RunTableObject;
RunTableIndex: TYPE = VMSideDoor.RunTableIndex;
PhysicalRun: TYPE = FileInternal.PhysicalRun;
lastRun: VMSideDoor.RunTablePageNumber = LAST[INT]; -- end marker in runTable --
initRuns: CARDINAL = (DiskFace.wordsPerPage-SIZE[VolumeFormat.LogicalRunObject[0]]) /
SIZE[VolumeFormat.LogicalRun];
DoPinnedIO: PROC[channel: Disk.Channel, label: REF Disk.Label, req: POINTER TO Disk.Request]
RETURNS[ status: Disk.Status, countDone: Disk.PageCount] = TRUSTED
BEGIN
interval: VM.Interval = [
page: VM.AddressToPageNumber[req.data],
count: VM.WordsToPages[
(IF req.incrementDataPtr THEN req.count ELSE 1)*DiskFace.wordsPerPage] ];
VM.SwapIn[interval: interval, kill: req.command.data=read, pin: TRUE];
[status, countDone] ← Disk.DoIO[channel, label, req ! UNWIND => VM.Unpin[interval] ];
VM.Unpin[interval];
END;
CheckStatus: PROC[status: Disk.Status] =
BEGIN
why: File.RC = FileInternal.TranslateStatus[status];
IF why # ok THEN ERROR File.Error[why];
END;
-- ******** Header and Run-table management ******** --
--FileInternal.--GetHeaderVM: PUBLIC PROC[file: Handle, runs: CARDINAL] = TRUSTED
BEGIN
words: INT = File.propertyWords + SIZE[VolumeFormat.LogicalRunObject[runs]];
file.headerVM ← VM.PageNumberToAddress[VM.Allocate[VM.WordsToPages[words]].page];
file.logicalRunTable ← LOOPHOLE[file.headerVM + File.propertyWords];
file.logicalRunTable.headerPages ← File.PagesForWords[words];
file.logicalRunTable.maxRuns ← runs;
END;
initHeaderPages: File.PageCount =
File.PagesForWords[File.propertyWords + SIZE[VolumeFormat.LogicalRunObject[initRuns]]];
--FileInternal.--TranslateLogicalRunTable: PUBLIC PROC[file: Handle] RETURNS[ File.PageCount ] = TRUSTED
BEGIN
nRuns: CARDINAL;
filePage: File.PageNumber ← [-file.logicalRunTable.headerPages];
file.runTable ← NEW[RunTableObject[file.logicalRunTable.maxRuns]];
FOR nRuns IN [0..file.logicalRunTable.maxRuns)
DO
IF file.logicalRunTable[nRuns].first = VolumeFormat.lastLogicalRun THEN EXIT;
file.runTable[nRuns] ←
FileInternal.TranslateLogicalRun[file.logicalRunTable[nRuns], file.volume, filePage];
filePage ← [filePage + file.logicalRunTable[nRuns].size];
REPEAT FINISHED => ERROR File.Error[inconsistent]
ENDLOOP;
file.runTable.nDataPages ← filePage;
file.runTable.nRuns ← nRuns;
file.runTable[file.runTable.nRuns].filePage ← lastRun;
RETURN[ file.runTable.nDataPages ]
END;
--FileInternal.--CreateEmptyRunTable: PUBLIC PROC[file: Handle] RETURNS[ File.PageCount ] = TRUSTED
BEGIN
GetHeaderVM[file, initRuns];
file.logicalRunTable[0].first ← VolumeFormat.lastLogicalRun;
RETURN[ TranslateLogicalRunTable[file] ];
END;
--FileInternal.--AddRun: PUBLIC PROC[file: Handle,
run: POINTER TO PhysicalRun,
logicalPage: VolumeFormat.LogicalPage,
okPages: INT] = TRUSTED
BEGIN
file.runTable.nDataPages ← file.size + okPages;
file.runTable[file.runTable.nRuns] ← run^;
file.logicalRunTable[file.runTable.nRuns].first ← logicalPage;
file.logicalRunTable[file.runTable.nRuns].size ← okPages;
file.runTable.nRuns ← file.runTable.nRuns + 1;
file.runTable[file.runTable.nRuns].filePage ← lastRun;
file.logicalRunTable[file.runTable.nRuns].first ← VolumeFormat.lastLogicalRun;
END;
--FileInternal.--LastLogicalPage: PUBLIC PROC[file: Handle] RETURNS [VolumeFormat.LogicalPage] = TRUSTED
BEGIN
Assumes file.runTable.nRuns > 0
lastRun: VolumeFormat.LogicalRun = file.logicalRunTable[file.runTable.nRuns-1];
RETURN[ [lastRun.first + lastRun.size-1] ]
END;
--FileInternal.--RemoveFromRunTable: PUBLIC PROC[file: Handle, remove: INT] = TRUSTED
BEGIN
file.runTable.nDataPages ← file.size;
WHILE remove > 0
DO runSize: INT = file.logicalRunTable[file.runTable.nRuns-1].size;
amount: INT = IF remove > runSize THEN runSize ELSE remove;
file.logicalRunTable[file.runTable.nRuns-1].size ← runSize - amount;
IF runSize - amount = 0
THEN BEGIN
-- Remove the new run table entry entirely! --
IF file.runTable.nRuns = 1 THEN ERROR File.Error[inconsistent];
file.runTable.nRuns ← file.runTable.nRuns - 1;
file.runTable[file.runTable.nRuns].filePage ← lastRun;
file.logicalRunTable[file.runTable.nRuns].first ← VolumeFormat.lastLogicalRun;
END;
remove ← remove - amount;
ENDLOOP;
END;
--FileInternal.--FindRun: PUBLIC PROC[start: File.PageNumber, nPages: File.PageCount, runTable: RunTable]
RETURNS[diskPage: Disk.PageNumber, size: Disk.PageCount, channel: Disk.Channel] =
BEGIN
probe: RunTableIndex ← runTable.nRuns / 2; -- NB: round down --
increment: CARDINAL ← probe;
IF runTable.nRuns = 0 THEN ERROR File.Error[inconsistent];
IF start + nPages > runTable.nDataPages THEN ERROR File.Error[unknownPage];
IF start < runTable[0].filePage THEN ERROR File.Error[unknownPage];
DO increment ← (increment+1)/2;
SELECT TRUE FROM
runTable[probe].filePage > start =>
probe ← IF probe < increment THEN 0 ELSE probe-increment;
runTable[probe+1].filePage <= start =>
probe ← MIN[probe + increment, runTable.nRuns-1];
ENDCASE =>
RETURN[
diskPage: [runTable[probe].diskPage + (start-runTable[probe].filePage)],
size: IF start + nPages <= runTable[probe+1].filePage
THEN nPages
ELSE runTable[probe+1].filePage - start,
channel: runTable[probe].channel ]
ENDLOOP;
END;
-- ******** Subroutines for access to file pages ******** --
HeaderLabel: PROC[id: File.FileID] RETURNS[REF Disk.Label] =
BEGIN
RETURN[NEW[Disk.Label ← [
fileID: [rel[RelID[id]]],
filePage: 0,
attributes: Attributes[header],
dontCare: LOOPHOLE[LONG[0]]
] ] ]
END;
DataLabel: PROC[id: File.FileID] RETURNS[REF Disk.Label] =
BEGIN
RETURN[NEW[Disk.Label ← [
fileID: [rel[RelID[id]]],
filePage: 0,
attributes: Attributes[data],
dontCare: LOOPHOLE[LONG[0]]
] ] ]
END;
FreeLabel: PROC[volume: File.Volume] RETURNS[REF Disk.Label] =
BEGIN
RETURN[NEW[Disk.Label ← [
fileID: [abs[AbsID[File.GetVolumeID[volume]]]],
filePage: 0,
attributes: Attributes[freePage],
dontCare: LOOPHOLE[LONG[0]]
] ] ]
END;
WriteLabels: PROC[channel: Disk.Channel, diskPage: Disk.PageNumber, count: Disk.PageCount, data: LONG POINTER, label: REF Disk.Label]
RETURNS[ status: Disk.Status, countDone: Disk.PageCount] = TRUSTED
BEGIN
req: Disk.Request ← [
diskPage: diskPage,
data: IF data = NIL THEN freePageData ELSE data,
incrementDataPtr: data # NIL,
command: [header: verify, label: write, data: write],
count: count ];
[status, countDone] ← DoPinnedIO[channel, label, @req];
END;
GetScratchPage: PROC RETURNS[data: LONG POINTER] = TRUSTED
BEGIN
temp: LONG POINTER TO ARRAY [0..DiskFace.wordsPerPage) OF WORD;
data ← VM.PageNumberToAddress[VM.Allocate[VM.WordsToPages[DiskFace.wordsPerPage]].page];
temp ← LOOPHOLE[data];
temp^ ← ALL[0];
END;
FreeScratchPage: PROC[data: LONG POINTER] = TRUSTED
BEGIN
VM.Free[
[ page: VM.AddressToPageNumber[data],
count: VM.WordsToPages[DiskFace.wordsPerPage] ]
];
END;
freePageData: LONG POINTER = GetScratchPage[]; -- buffer for reading/writing freepages
--FileInternal.--FreeRun: PUBLIC PROC[logicalRun: VolumeFormat.LogicalRun, volume: File.Volume] =
BEGIN
-- Write page labels as "free" and mark as unused in VAM --
label: REF Disk.Label ← FreeLabel[volume];
FileInternal.Free[volume, logicalRun];
WHILE logicalRun.size > 0
DO run: PhysicalRun ← FileInternal.TranslateLogicalRun[logicalRun, volume, [0]];
status: Disk.Status;
countDone: Disk.PageCount;
label.filePage ← logicalRun.first;
TRUSTED{[status, countDone] ← WriteLabels[run.channel,
run.diskPage, logicalRun.size, NIL, label]};
SELECT status FROM
Disk.ok => NULL;
Disk.invalid => ERROR File.Error[wentOffline];
ENDCASE =>
BEGIN
-- Couldn't write label, so assume page is bad --
countDone ← countDone + 1;
END;
logicalRun.first ← [logicalRun.first + countDone];
logicalRun.size ← logicalRun.size - countDone;
ENDLOOP;
END--FreeRun--;
Extend: PROC[file: Handle, amount: INT, create: BOOL] =
BEGIN
volume: File.Volume = file.volume;
prev: VolumeFormat.LogicalPage ← IF create THEN [0] ELSE FileInternal.LastLogicalPage[file];
freeLabel: REF Disk.Label = FreeLabel[volume];
freeReq: Disk.Request ← [
diskPage:,
data: freePageData,
incrementDataPtr: FALSE,
command: [header: verify, label: verify, data: read],
count: ];
label: REF Disk.Label;
IF NOT create
THEN { label ← DataLabel[file.fp.id]; label.filePage ← file.size };
The structure of the following loops is:
FOR each disk run allocated
DO FOR each part of the run that has genuinely free pages
DO Write header; IF header is now ok THEN Write data labels;
ENDLOOP;
ENDLOOP;
WHILE amount > 0
-- Loop for each allocated disk run --
DO
logicalRun: VolumeFormat.LogicalRun ←
FileInternal.Alloc[volume: volume, first: prev, size: amount];
IF create AND logicalRun.size < initHeaderPages THEN ERROR File.Error[volumeFull];
WHILE logicalRun.size > 0
-- Loop for each available fragment of disk run --
DO run: PhysicalRun ← FileInternal.TranslateLogicalRun[logicalRun, volume,
IF create THEN [-initHeaderPages] ELSE [file.size]];
labelsOK: Disk.PageCount; -- count of labels that are genuinely free pages --
status: Disk.Status;
freeReq.diskPage ← run.diskPage;
freeReq.count ← logicalRun.size;
freeLabel.filePage ← logicalRun.first;
TRUSTED{ [status, labelsOK] ←
DoPinnedIO[run.channel, freeLabel, @freeReq] -- verify free --};
IF create AND labelsOK < initHeaderPages THEN labelsOK ← 0; -- may lose a page --
IF labelsOK > 0
THEN BEGIN
labelsWritten: Disk.PageCount ← 0; -- total labels actually written --
labelsThisTime: Disk.PageCount ← 0; -- labels written in this transfer --
Consume: PROC =
BEGIN
labelsWritten ← labelsWritten + labelsThisTime;
amount ← amount - labelsThisTime;
logicalRun.first ← [logicalRun.first+labelsThisTime];
logicalRun.size ← logicalRun.size - labelsThisTime;
END;
-- ensure run-table is superset of allocated pages --
TRUSTED{FileInternal.AddRun[file, @run, logicalRun.first, labelsOK]};
IF create
THEN BEGIN
-- write labels and data for header area --
file.fp ← [id: FileInternal.NewID[volume], da: logicalRun.first];
TRUSTED{[status, labelsThisTime] ← WriteLabels[run.channel,
run.diskPage, initHeaderPages, file.headerVM, HeaderLabel[file.fp.id]]};
IF labelsThisTime < initHeaderPages
THEN labelsThisTime ← 0 -- incorrigable page; may lose preceding page --
ELSE BEGIN
create ← FALSE;
file.size ← file.size + labelsThisTime;
label ← DataLabel[file.fp.id];
label.filePage ← file.size;
END;
Consume[];
END
ELSE WriteHeader[file];
IF labelsOK > labelsWritten
AND NOT create -- i.e. if there's still pages free and creation succeeded
THEN BEGIN
TRUSTED{[status, labelsThisTime] ← WriteLabels[run.channel,
[run.diskPage+labelsThisTime], labelsOK-labelsWritten, NIL, label]};
file.size ← file.size + labelsThisTime;
Consume[];
END;
-- correct run-table to number of pages successfully written --
IF labelsOK > labelsWritten
THEN FileInternal.RemoveFromRunTable[file, labelsOK-labelsWritten];
END;
SELECT status FROM
Disk.ok => NULL;
Disk.invalid => ERROR File.Error[wentOffline];
ENDCASE => -- skip bad/in-use page --
{ logicalRun.first ← [logicalRun.first+1]; logicalRun.size ← logicalRun.size-1 };
ENDLOOP-- Loop for each available fragment of disk run --;
ENDLOOP-- Loop for each allocated disk run --;
END--Extend--;
maxTransferRun: File.PageCount ← 100;
Largest number of disk pages to transfer in single request. This much VM will be pinned during the transfer. Variable, not constant, to allow patching.
Transfer: PROC[file: Handle, data: LONG POINTER, filePage: File.PageNumber, nPages: File.PageCount, action: {write, read}, where: {header, data} ] =
BEGIN
label: REF Disk.Label ←
IF where = header THEN HeaderLabel[file.fp.id] ELSE DataLabel[file.fp.id];
IF where = data THEN label.filePage ← filePage;
WHILE nPages > 0
DO status: Disk.Status;
countDone: Disk.PageCount;
channel: Disk.Channel;
req: Disk.Request;
req.data ← data;
req.incrementDataPtr ← TRUE;
req.command ← IF action = read
THEN [header: verify, label: verify, data: read]
ELSE [header: verify, label: verify, data: write];
[diskPage: req.diskPage, size: req.count, channel: channel] ←
FileInternal.FindRun[start: filePage, nPages: MIN[nPages,maxTransferRun], runTable: file.runTable];
TRUSTED{[status, countDone] ← DoPinnedIO[channel, label, @req]};
CheckStatus[status];
TRUSTED{data ← data + countDone * DiskFace.wordsPerPage};
nPages ← nPages - countDone;
filePage ← [filePage + countDone];
ENDLOOP;
END;
MakeBootable: PROC[file: Handle, firstPage: File.PageNumber] =
BEGIN
data: LONG POINTER;
status: Disk.Status;
label: REF Disk.Label ← DataLabel[file.fp.id];
countDone: Disk.PageCount;
filePage: File.PageNumber ← firstPage;
thisDiskPage: Disk.PageNumber;
thisSize: Disk.PageCount;
channel: Disk.Channel;
IF file.size > firstPage
THEN [diskPage: thisDiskPage, size: thisSize, channel: channel] ←
FileInternal.FindRun[start: filePage, nPages: file.size-filePage, runTable: file.runTable];
data ← GetScratchPage[];
WHILE filePage + thisSize < file.size
DO BEGIN
ENABLE UNWIND => FreeScratchPage[data];
nextDiskPage: Disk.PageNumber;
nextSize: Disk.PageCount;
nextChannel: Disk.Channel;
req: Disk.Request;
filePage ← [filePage+thisSize]; -- file page number of start of next run
thisDiskPage ← [thisDiskPage + thisSize - 1]; -- disk page number of last page of this run
[diskPage: nextDiskPage, size: nextSize, channel: nextChannel] ←
FileInternal.FindRun[start: filePage, nPages: file.size-filePage, runTable: file.runTable];
IF NOT Disk.SameDrive[nextChannel, channel] THEN ERROR File.Error[mixedDevices];
label.filePage ← filePage-1;
req ← [
diskPage: thisDiskPage,
data: data,
incrementDataPtr: TRUE,
command: [header: verify, label: verify, data: read],
count: 1 ];
TRUSTED{[status, countDone] ← DoPinnedIO[channel, label, @req]};
CheckStatus[status];
label.dontCare ← Disk.GetBootChainLink[channel, nextDiskPage];
label.filePage ← filePage-1;
TRUSTED{[status, countDone] ← WriteLabels[channel, thisDiskPage, 1, data, label]};
CheckStatus[status];
thisDiskPage ← nextDiskPage; thisSize ← nextSize;
END
ENDLOOP;
FreeScratchPage[data];
END;
LockMode: TYPE = { shared, exclusive };
unlocked: CONDITION;
Lock: ENTRY PROC[file: Handle, mode: LockMode] =
BEGIN
ENABLE UNWIND => NULL;
IF file = NIL THEN RETURN WITH ERROR File.Error[unknownFile];
IF mode = shared
THEN BEGIN
WHILE file.users < 0 --writers-- DO WAIT unlocked ENDLOOP;
file.users ← file.users + 1;
END
ELSE BEGIN
WHILE file.users # 0 --anyone-- DO WAIT unlocked ENDLOOP;
file.users ← file.users - 1;
END;
END;
Unlock: ENTRY PROC[file: Handle] =
BEGIN
SELECT file.users FROM
< 0 => file.users ← file.users + 1;
> 0 => file.users ← file.users - 1;
ENDCASE => NULL;
BROADCAST unlocked;
END;
-- ******** Top-level procedures ******** --
--File.--Create: PUBLIC PROC[volume: File.Volume, size: File.PageCount]
RETURNS[file: Handle] =
BEGIN
file ← NEW[Object ← [volume: volume] ];
file.size ← FileInternal.CreateEmptyRunTable[file];
Extend[file, initHeaderPages+size, TRUE];
END;
--File.--Open: PUBLIC PROC[volume: File.Volume, fp: File.FP] RETURNS[Handle] =
BEGIN
file: Handle = NEW[Object ← [volume: volume, fp: fp] ];
logicalRun: VolumeFormat.LogicalRun ← [first: fp.da, size: initHeaderPages];
run: PhysicalRun ← FileInternal.TranslateLogicalRun[logicalRun, volume, [-initHeaderPages]];
label: REF Disk.Label ← HeaderLabel[fp.id];
req: Disk.Request;
status: Disk.Status;
countDone: Disk.PageCount;
FileInternal.GetHeaderVM[file, initRuns];
req ← [
diskPage: run.diskPage,
data: file.headerVM,
incrementDataPtr: TRUE,
command: [header: verify, label: verify, data: read],
count: initHeaderPages ];
TRUSTED{[status, countDone] ← DoPinnedIO[run.channel, label, @req]};
IF countDone = 0 AND status = Disk.labelCheck THEN ERROR File.Error[unknownFile];
CheckStatus[status];
TRUSTED{ IF file.logicalRunTable.headerPages # initHeaderPages
THEN ERROR File.Error[inconsistent] }; --Run table has been extended; tedious--
file.size ← FileInternal.TranslateLogicalRunTable[file];
RETURN[file]
END;
--File.--Delete: PUBLIC PROC[file: Handle] =
BEGIN
Lock[file, exclusive];
BEGIN
ENABLE File.Error => Unlock[file];
NULL;
END;
Unlock[file];
END;
--File.--SetSize: PUBLIC PROC[file: Handle, size: File.PageCount] =
BEGIN
Lock[file, exclusive];
BEGIN
ENABLE File.Error => Unlock[file];
SELECT TRUE FROM
size > file.size => Extend[file, size-file.size, FALSE];
size < file.size => ERROR;
ENDCASE => NULL;
END;
Unlock[file];
END;
--File.--Info: PUBLIC PROC[file: Handle]
RETURNS[volume: File.Volume, fp: File.FP, size: File.PageCount] =
BEGIN
Lock[file, shared];
BEGIN
ENABLE File.Error => Unlock[file];
volume ← file.volume; fp ← file.fp; size ← file.size;
END;
Unlock[file];
END;
--File.--SetRoot: PUBLIC PROC[root: File.VolumeFile, file: Handle, page: File.PageNumber ← [0]] = TRUSTED
BEGIN
diskPage: Disk.PageNumber;
channel: Disk.Channel;
Lock[file, shared];
BEGIN
ENABLE File.Error => Unlock[file];
MakeBootable[file, page];
[diskPage: diskPage, channel: channel] ←
FileInternal.FindRun[start: page, nPages: 1, runTable: file.runTable];
FileInternal.RecordRootFile[file.volume, root, file.fp, RelID[file.fp.id],
Disk.GetBootChainLink[channel, diskPage], channel];
END;
Unlock[file];
END;
WriteHeader: PROC[file: Handle] =
BEGIN
hPages: File.PageCount = -file.runTable[0].filePage;
Transfer[file: file, data: file.headerVM, filePage: [-hPages],
nPages: hPages, action: write, where: header]
END;
--File.--Read: PUBLIC UNSAFE PROC[file: Handle,
from: File.PageNumber,
nPages: File.PageCount,
to: LONG POINTER] =
BEGIN
IF from < 0 THEN ERROR File.Error[unknownPage];
Lock[file, shared];
BEGIN
ENABLE File.Error => Unlock[file];
Transfer[file: file, data: to, filePage: from, nPages: nPages, action: read, where: data];
END;
Unlock[file];
END;
--File.--Write: PUBLIC PROC[file: Handle,
to: File.PageNumber,
nPages: File.PageCount,
from: LONG POINTER] =
BEGIN
IF to < 0 THEN ERROR File.Error[unknownPage];
Lock[file, shared];
BEGIN
ENABLE File.Error => Unlock[file];
Transfer[file: file, data: from, filePage: to, nPages: nPages, action: write, where: data];
END;
Unlock[file];
END;
--File.--GetProperties: PUBLIC PROC[file: Handle] RETURNS[File.PropertyStorage] =
BEGIN
RETURN[LOOPHOLE[file.headerVM]]
END;
--File.--WriteProperties: PUBLIC PROC[file: Handle] =
BEGIN
Lock[file, shared];
BEGIN
ENABLE File.Error => Unlock[file];
hPages: File.PageCount = -file.runTable[0].filePage;
Transfer[file: file, data: file.headerVM, filePage: [-hPages],
nPages: File.PagesForWords[File.propertyWords], action: write, where: header]
END;
Unlock[file];
END;
END.