-- DirectoryFilesImpl.mesa (last edited by: Keith on: January 14, 1981 10:38 PM) --
-- Schmidt March 16, 1983 2:15 pm

DIRECTORY
Ascii USING [BEL],
CommonSoftwareFileTypes USING [tDirectory],
Directory,
DirectoryContexts,
DirectoryFiles,
DirectoryInternal,
DirectoryProps,
DirectoryTrees,
Environment USING [Byte, wordsPerPage],
File USING [
Capability, Create, Delete, GetAttributes, GetSize, LimitPermissions,
MakePermanent, nullCapability, PageCount, Permissions, Type, Unknown],
Inline USING [LowHalf],
LongString USING[EquivalentString],
Space USING [
Create, CreateUniformSwapUnits, Delete, ForceOut, Handle, LongPointer, Map, PageCount, Unmap,
virtualMemory],
Volume USING [
ID, GetAttributes, GetLabelStr
ing, GetNext, GetStatus, nullID, Unknown];

DirectoryFilesImpl: MONITOR LOCKS DirectoryLock
IMPORTS
Directory, DirectoryContexts, DirectoryProps, DirectoryTrees, File, Inline, LongString,
Space, Volume
EXPORTS Directory, DirectoryInternal, Direc
toryFiles
S
HARES Directory, File =
BE
GIN

Direc
toryLock: PUBLIC MONITORLOCK;

-- Volume Table
-- This table
is a cache of the information needed for volumes

VolumeTable: TYPE = ARRAY [0..DirectoryFiles.maxDVolumes) OF DirectoryFiles.VTEntry ←
ALL[[volName: [length: 0, maxlength: 40, text:], filler: ALL[’ ]
, volID: Volume.nullID, pDT: NIL, dtSpace:]];
vo
lumeTable: PUBLIC LONG POINTER TO VolumeTable;

-- Directory Cache
-- This cache contains the informa
tion on all of the directories currently in use

DirectoryCache: TYPE = ARRAY [1..DirectoryFiles.maxDCache] OF DirectoryFiles.DCEntry ← ALL[
[File.nullCapability, 0, 0, DirectoryInternal.pDTnil,
[base: NIL, top: Director
yInternal.nilPagePointer, size: 0, space:]]];
directoryCach
e: PUBLIC LONG POINTER TO DirectoryCache;

-- Contexts

Context: PUBLIC TYPE = DirectoryInternal.Context;
Handle: TYPE = LONG POINTER TO Context;

-- miscellanous type and variable definitions

Error: PUB
LIC ERROR [type: Directory.ErrorType] = CODE;
OutOfBounds: PUBLIC SIGNAL = CODE;
VolumeError: PUBLIC ERROR [type: Directory.VolumeErrorType, volume: Volume.ID] = CODE;
FieldType: TYP
E = {File, Volume, Directory, Empty, Funny}; -- result from Break
S
earchType: TYPE = {UseWD, UseSP, MustBeQualified}; -- parameter to XlateName

-- Val
id Character Table

ValidChars: TYPE = PACKED ARRAY [0.. 256) OF BOOLEAN;
pValidChars: LONG POINTER TO ValidChars;

-- GetNext cached directory d
escriptor

gnLastPath: LONG STRING ← NIL;

gnLastNameFromPath: LONG STRING ← NIL;
gnLastNextName: LONG STRING ← NIL;
gnLastNameOfNext: LONG STRING ← NIL;
gnLastDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;

-- These variables are used by
the entry routines. If the monitor is changed to multiple monitors, beware!

nudeName: LONG STRING ← NIL; -
- used by entry procs to store unqual name
eType: Directory.ErrorType; --
used by entry procedures to save the type of error for return

-- entry procedures

CreateFi
le: PUBLIC ENTRY PROC [
fileName: LONG STRING, fileType: File.Type, size: File.PageCount,
context: Handle] RETURNS [file: File.Capability] =
-- Create and enter a file i
nto the directory
BEGIN
ENABLE UNWIND => NU
LL;
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
wc, ok, noRoom: BOOLEAN;
cardSize: LONG CARDINAL ← size;
actualSize: File.PageCount ← cardSize+DirectoryInternal.leaderPageSize;
pE: DirectoryInternal.DirectoryEntryHandle;
BEGIN
ENABLE Directory.Error => {eType ← type; GOTO AnError};
context ← DirectoryContexts.ValidateContext[context];
[pDD: pDD, wc: wc] ← XlateName[fileName, context, nudeName, UseWD];
IF wc OR (nudeName.length = 0) THEN {eType ← invalidFileName; GOTO AnError};
[ok: ok, noRoom: noRoom, ent: pE] ← DirectoryTrees.BTreeInsert[pDD, nudeName, File.nullCapability];
IF noRoom THEN {eType ← notImplemented; GOTO AnError};
IF ~ ok THEN {eType ← fileAlreadyExists; GOTO AnError};
pE.cap ← file ← File.Create[
volume: volumeTable[directoryCache[PDCFromDD[pDD]].pVT].volID,
initialSize: actualSize, type: fileType];
Space.ForceOut[pDD.space];
File.MakePermanent[file];
DirectoryProps.SetCreateDate[file, nudeName, directoryCache[PDCFromDD[pDD]].cap];
DoneWith[pDD];
RETURN
EXITS AnError => {DoneWith[pDD]; RETURN WITH ERROR Directory.Error[eType]};
END;
END;

CreateSubdirectory: PUBLIC ENTRY
PROC [
parentP
athName, subDirectory: LONG STRING] =
-- Create a directory in the
parentPathName
BEGIN
ENABLE UNWIND => NU
LL;
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
pPage: DirectoryInternal.DirectoryPageHandle;
dirCap: File.Capability ←File.nullCapability;
pE: DirectoryInternal.DirectoryEntryHandle;
dirName: STRING ← [Directory.maxDirectoryNameLength + 1];
-- name with trailing BEL to
indicate dir
space: Space.Handle;
pDC:
CARDINAL;
ok, noRoom: BOOLEAN;
BEGIN
ENABLE Directory.Error => {eType ← type; GOTO AnError};
[] ← DirectoryContexts.ValidateContext[Directory.defaultContext];
IF subDirectory = NIL THEN RETURN WITH ERROR Error[invalidFileName];
IF subDirectory.length = 0 THEN RETURN WITH ERROR Error[invalidFileName];
DirectoryTrees.MoveLongString[to: dirName, from: subDirectory];
dirName[dirName.length] ← Ascii.BEL;
dirName.length ← dirName.length + 1;
FOR i: CARDINAL IN [0..subDirectory.length) DO
IF ~CheckChar[subDirectory[i]] THEN
RETURN WITH ERROR Error[invalidFileName];
ENDLOOP;
pDD ← XlateName[parentPathName, NIL, nudeName, MustBeQualified].pDD;
IF nudeName.length # 0 THEN {eType ← invalidPathName; GOTO AnError};
[ok: ok, noRoom: noRoom, ent: pE] ← DirectoryTrees.BTreeInsert[pDD, dirName, dirCap];
IF noRoom THEN {eType ← notImplemented; GOTO AnError};
IF ~ ok THEN {eType ← fileAlreadyExists; GOTO AnError};
pDC ← PDCFromDD[pDD];
pE.cap ← dirCap ← File.Create[
volumeTable[directoryCache[pDC].pVT].volID, DirectoryInternal.initDirSize,
CommonSoftwareFileTypes.tDirectory];
File.MakePermanent[dirCap];
Space.ForceOut[pDD.space];
space ← Space.Create[DirectoryInternal.initDirSize, Space.virtualMemory];
Space.Map[space, [dirCap, DirectoryInternal.leaderPageSize]];
pPage ← Space.LongPointer[space]; -- Initialize the first page
pPage.parent ← pPage.lastPointer ← DirectoryInternal.nilPagePointer;
pPage.size ← DirectoryInternal.emptySize;
pPage.free ← FALSE;
pPage.top ← FIRST[DirectoryInternal.PagePointer];
pPage ← pPage + Environment.wordsPerPage;
-- mark the rest of the page
s as free
FOR x: File.PageCount ← Direc
toryInternal.leaderPageSize+1, x + 1 UNTIL x = DirectoryInternal.initDirSize DO
pPage.free ← TRUE; pPage ← pPage + Environment.wordsPerPage; ENDLOOP;
Space.Unmap[space];
Space.Delete[space];
DirectoryProps.SetCreateDate[dirCap, subDirectory, directoryCache[pDC].cap];
IF ~DirectoryTrees.DTInsert[
volumeTable[directoryCache[pDC].pVT].pDT, directoryCache[pDC].pDTNode,
subDirectory, dirCap].ok THEN
RETURN WITH ERROR Directory.VolumeError[directoryNeedsScavenging, volumeTable[directoryCache[pDC].pVT].volID];
Space.ForceOut[volumeTable[directoryCache[pDC].pVT].dtSpace];
DoneWith[pDD];
RETURN
EXITS AnError => {DoneWith[pDD]; RETURN WITH ERROR Directory.Error[eType]};
END
END;

DeleteFile: PUBLIC ENTRY PROC [fi
leName: LONG STRING, context: Handle] =
-- Delete and remove a file
from the directory
BEGIN
ENABLE UNWIND => NU
LL;
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
file: File.Capability;
exists, sd, wc: BOOLEAN;
BEGIN
ENABLE Directory.Error => {eType ← type; GOTO AnError};
context ← DirectoryContexts.ValidateContext[context];
[pDD: pDD, wc: wc] ← XlateName[fileName, context, nudeName, UseWD];
IF wc OR (nudeName.length = 0) THEN {eType ← invalidFileName; GOTO AnError};
[ok: exists, cap: file, isSD: sd] ← DirectoryTrees.BTreeFind[pDD, nudeName];
IF ~exists THEN {eType ← fileNotFound; GOTO AnError};
IF sd THEN {eType ← fileIsSD; GOTO AnError};
File.Delete[file];
[] ← DirectoryTrees.BTreeDelete[pDD, nudeName, FALSE];
Space.ForceOut[pDD.space];
DoneWith[pDD];
RETURN
EXITS AnError => {DoneWith[pDD]; RETURN WITH ERROR Directory.Error[eType]};
END;
END;

DeleteSubdirectory: PUBLIC ENTRY
PROC [
parentP
athName, subDirectory: LONG STRING] =
-- Delete an empty directory
and remove from D-Tree. Flush cache entry if one exists
BEGIN ENABLE UNWIND => NULL;
parentDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
sdDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
sdWithBEL: STRING ← [Directory.maxDirectoryNameLength+1];
sdDC, parentDC: CARDINAL;
sdCap: File.Capability;
ok, isSD: BOOLEAN;
BEGIN ENABLE Directory.Error => {eType ← type; GOTO AnError};
IF subDirectory = NIL THEN {eType ← invalidFileName; GOTO AnError};
parentDD ← XlateName[parentPathName, NIL, nudeName, MustBeQualified].pDD;
IF nudeName.length # 0 THEN {eType ← invalidPathName; GOTO AnError};
parentDC ← PDCFromDD[parentDD];
[ok: ok, cap: sdCap, isSD: isSD] ← DirectoryTrees.BTreeFind[parentDD, subDirectory];
IF ~ ok THEN {eType ← fileNotFound; GOTO AnError};
IF ~ isSD THEN {eType ← fileNotSD; GOTO AnError};
sdDC ← GetDirectoryCache[sdCap];
sdDD ← @directoryCache[sdDC].dir;
IF ~ DirectoryTrees.BTreeEmpty[sdDD] THEN {eType ← directoryNotEmpty; GOTO AnError};
DirectoryTrees.MoveLongString[to: sdWithBEL, from: subDirectory];
sdWithBEL[sdWithBEL.length] ← Ascii.BEL; sdWithBEL.length ← sdWithBEL.length+1;
[] ← DirectoryTrees.BTreeDelete[parentDD, sdWithBEL, TRUE];
Space.ForceOut[parentDD.space];
ok ← DirectoryTrees.DTDelete[volumeTable[directoryCache[parentDC].pVT].pDT,
directoryCache[parentDC].pDTNode,
subDirectory];
IF ~ ok THEN {
DoneWith[parentDD]; DoneWith[sdDD];
RETURN WITH ERROR Directory.VolumeError[
directoryNeedsScavenging, volumeTable[directoryCache[parentDC].pVT].volID]};
Space.ForceOut[volumeTable[directoryCache[parentDC].pVT].dtSpace];
IF directoryCache[sdDC].refCount > 1 THEN {
-- this directory is referenced some
where in contexts... set bad
DirectoryContexts.Invalidat
eContext[sdDC];
directoryCache[sdDC].refCount ← 1};
DoneWith[sdDD];
File.Delete[sdCap];
DoneWith[parentDD];
RETURN
EXITS AnError => {DoneWith[parentDD]; DoneWith[sdDD]; RETURN WITH ERROR Directory.Error[eType]};
END;
END;

GetNext: PUBLIC ENTRY PROC [pathN
ame, currentName, nextName: LONG STRING]
RETURNS [file: File.Capability] = -- Stateless Directory Enumerat
or
BEGIN
ENABLE UNWIND => NU
LL;
nameFromPath: STRING ← [Directory.maxDirectoryNameLength];
nameFromCurrent: STRING ← [Directory.maxDirectoryNameLength];
nameOfNext: STRING ← [Directory.maxDirectoryNameLength];
dirOfCurrent: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
dirOfPath: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
i, j, k: CARDINAL;
wcCurrent: BOOLEAN;
context: Handle;
BEGIN
ENABLE Directory.Error => {eType ← type; GOTO AnError};
IF (currentName = NIL) OR (nextName = NIL)
THEN RETURN WITH ERROR Directory.Error[invalidFileName];
IF pathName = NIL THEN RETURN WITH ERROR Directory.Error[invalidPathName];
IF (gnLastDD # NIL) AND (LongString.EquivalentString[pathName, gnLastPath] ) AND
(LongString.EquivalentString[currentName, gnLastNextName]) THEN {
-- use cached info fr
om last time
dirOfCurrent ← NIL;
dirOfPath ← gnLastDD;
i ← PDCFromDD[dirOfPath];
directoryCache[i].refCount ← directoryCache[i].refCount + 1;
DirectoryTrees.MoveLongString[to: nameFromPath, from: gnLastNameFromPath];
DirectoryTrees.MoveLongString[to: nameFromCurrent, from: gnLastNameOfNext];
IF nameFromCurrent[nameFromCurrent.length-1] = ’>
THEN nameFromCurrent[nameFromCurrent.length-1] ← Ascii.BEL}
ELSE {
-- must translate the
names and do some checking
context ← DirectoryCon
texts.ValidateContext[Directory.defaultContext];
IF pathName.length = 0 THEN
dirOfPath ← XlateName["*", context, nameFromPath, UseWD].pDD
ELSE dirOfPath ← XlateName[pathName, context, nameFromPath, UseWD].pDD;
IF currentName.length # 0 THEN {
IF currentName[currentName.length - 1] = ’> THEN
currentName[currentName.length - 1] ← Ascii.BEL;
[pDD: dirOfCurrent, wc: wcCurrent] ← XlateName[
currentName, context, nameFromCurrent, UseWD]}
ELSE {
wcCurrent ← FALSE;
dirOfCurrent ← dirOfPath;
i ← PDCFromDD[dirOfPath];
directoryCache[i].refCount ← directoryCache[i].refCount + 1};
IF wcCurrent THEN {eType ← invalidFileName; GOTO AnError};
IF dirOfCurrent.base # dirOfPath.base THEN GOTO LastExit};
file ← DirectoryTrees.BTreeGetNext[dirOfPath, nameFromCurrent, nameOfNext, nameFromPath];
IF nameOfNext.length = 0 THEN GOTO LastExit;
IF nameOfNext[nameOfNext.length - 1] = Ascii.BEL THEN
nameOfNext[nameOfNext.length - 1] ← ’>;
IF (pathName[0] = ’<) AND (pathName.length # 0) THEN {
-- name can be qualified by supplied path name
FOR j DECREASING IN [0..pathName.length) DO
IF pathName[j] = ’> THEN EXIT ENDLOOP;
FOR i IN [0..j] DO
IF i = nextName.maxlength THEN {eType ← stringTooShort; GOTO AnError};
nextName[i] ← pathName[i];
ENDLOOP;
j ← j + 1;
FOR i IN [0..nameOfNext.length) DO
IF i + j = nextName.maxlength THEN {eType ← stringTooShort; GOTO AnError};
nextName[i + j] ← nameOfNext[i];
ENDLOOP;
nextName.length ← j + nameOfNext.length}
ELSE {
-- name must be quali
fied by default working directory name
DirectoryTrees.MoveLon
gString[
to: nextName, from: @DirectoryContexts.contextTable[0].wdName];
FOR i IN [0..pathName.length) DO IF pathName[i] = ’> THEN EXIT ENDLOOP;
i ← i + 1;
FOR k DECREASING IN [0..pathName.length) DO
IF pathName[k] = ’> THEN EXIT ENDLOOP;
FOR j IN [nextName.length..nextName.maxlength) DO
IF i > k THEN EXIT;
nextName[j] ← pathName[i];
i ← i + 1
REPEAT FINISHED => {eType ← stringTooShort; GOTO AnError}
ENDLOOP;
FOR i IN [j..nextName.maxlength) DO
IF i - j = nameOfNext.length THEN EXIT;
nextName[i] ← nameOfNext[i - j];
REPEAT FINISHED => {eType ← stringTooShort; GOTO AnError}
ENDLOOP;
nextName.length ← i;
}; -- cache some state informat
ion for next time around
DirectoryTrees.MoveLongS
tring[to: gnLastNameOfNext, from: nameOfNext];
DirectoryTrees.MoveLongString[to: gnLastPath, from: pathName];
DirectoryTrees.MoveLongString[to: gnLastNameFromPath, from: nameFromPath];
DirectoryTrees.MoveLongString[to: gnLastNextName, from: nextName];
IF gnLastDD # dirOfPath THEN {
DoneWith[gnLastDD];
i ← PDCFromDD[dirOfPath];
directoryCache[i].refCount ← directoryCache[i].refCount+1;
gnLastDD ← dirOfPath};
DoneWith[dirOfPath];
DoneWith[dirOfCurrent];
RETURN
EXITS
AnError => {
DoneWith[gnLastDD];
gnLastDD ← NIL;
DoneWith[dirOfPath];
DoneWith[dirOfCurrent];
RETURN WITH ERROR Directory.Error[eType]};
LastExit => {
DoneWith[gnLastDD];
gnLastDD ← NIL;
DoneWith[dirOfPath];
DoneWith[dirOfCurrent];
nextName.length ← 0;
RETURN[File.nullCapability]};
END;
END;

InsertFile: PUBLIC ENTRY PRO
C [
fi
leName: LONG STRING, file: File.Capability, context: Handle] =
-- Insert capability/fi
le name pair into directory
BEGIN
ENABLE UNWIND
=> NULL;
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
wc, noRoom ,ok: BOOLEAN;
BEGIN
ENABLE Directory.Error => {eType ← type; GOTO AnError};
context ← DirectoryContexts.ValidateContext[context];
[pDD: pDD, wc: wc] ← XlateName[fileName, context, nudeName, UseWD];
IF wc OR (nudeName.length = 0) THEN {eType ← invalidFileName; GOTO AnError};
[ok: ok, noRoom: noRoom] ← DirectoryTrees.BTreeInsert[pDD, nudeName, file];
IF noRoom THEN {eType ← notImplemented; GOTO AnError};
IF ~ ok THEN {eType ← fileAlreadyExists; GOTO AnError};
Space.ForceOut[pDD.space];
DoneWith[pDD];
RETURN
EXITS AnError => {DoneWith[pDD]; RETURN WITH ERROR Directory.Error[eType]};
END;
END;

LimitPermissions: PUBLIC ENT
RY PROC [
fi
leName: LONG STRING, permissions: File.Permissions, context: Handle] =
-- Limit the capability
in the directory with the passed permissions
BEGIN
ENABLE UNWIND
=> NULL;
ok, sd, wc: BOOLEAN;
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
pDE: DirectoryInternal.DirectoryEntryHandle;
file: File.Capability;
BEGIN
ENABLE Directory.Error => {eType ← type; GOTO AnError};
context ← DirectoryContexts.ValidateContext[context];
[pDD: pDD, wc: wc] ← XlateName[fileName, context, nudeName, UseWD];
IF wc THEN {eType ← invalidFileName; GOTO AnError};
[ok: ok, cap: file, isSD: sd, ent: pDE] ← DirectoryTrees.BTreeFind[
pDD, nudeName];
IF sd THEN {eType ← fileIsSD; GOTO AnError};
IF ~ok THEN {eType ← fileNotFound; GOTO AnError};
file ← File.LimitPermissions[file, permissions];
pDE.cap ← file;
Space.ForceOut[pDD.space];
DoneWith[pDD];
RETURN
EXITS AnError => {DoneWith[pDD]; RETURN WITH ERROR Directory.Error[eType]};
END;
END;


Lookup: PUBLIC ENTRY PROC [
fileName: LONG STRING, context: Handle, permissions: File.Permissions]
RETURNS [file: File.Capability] =
-- Return a file’s capab
ility given its name. Update the dates as necessary after limiting the permissions.
BEGIN
ENABLE UNWIND
=> NULL;
ok, sd, wc: BOOLEAN;
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
pSP: LONG POINTER TO DirectoryInternal.SPRecord ← NIL;
badVol: Volume.ID;
BEGIN
ENABLE Directory.Error => {eType ← type; GOTO AnError};
context ← DirectoryContexts.ValidateContext[context];
DO
[pDD: pDD, next: pSP, wc: wc] ← XlateName[
fileName, context, nudeName, UseSP, pSP];
IF wc OR (nudeName.length = 0) THEN {eType ← invalidFileName; GOTO AnError};
[ok: ok, cap: file, isSD: sd] ← DirectoryTrees.BTreeFind[pDD, nudeName];
IF sd THEN {eType ← fileIsSD; GOTO AnError};
IF ok OR (pSP = NIL) THEN EXIT;
DoneWith[pDD];
ENDLOOP;
IF ~ok THEN {eType ← fileNotFound; GOTO AnError};
IF permissions # Directory.ignore THEN {
file ← File.LimitPermissions[file, permissions];
DirectoryProps.SetTimes[file ! Directory.Error => IF type = fileNotFound THEN {
badVol ← volumeTable[directoryCache[PDCFromDD[pDD]].pVT].volID;
DoneWith[pDD];
GOTO AVolumeError}
]};
DoneWith[pDD];
RETURN
EXITS
AnError => {DoneWith[pDD]; RETURN WITH ERROR Directory.Error[eType]};
AVolumeError => RETURN WITH ERROR Directory.VolumeError[directoryNeedsScavenging, badVol];
END;
END;

LookupUnlimited: PUBLIC ENTR
Y PROC [fileName: LONG STRING, context: Handle]
RETURNS [file: File.Capability] =
-- Return a file’s capab
ility given its name. Update the dates as necessary.
BEGIN
ENABLE UNWIND
=> NULL;
ok, sd, wc: BOOLEAN;
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
pSP: LONG POINTER TO DirectoryInternal.SPRecord ← NIL;
badVol: Volume.ID;
BEGIN
ENABLE Directory.Error => {eType ← type; GOTO AnError};
context ← DirectoryContexts.ValidateContext[context];
DO
[pDD: pDD, next: pSP, wc: wc] ← XlateName[
fileName, context, nudeName, UseSP, pSP];
IF wc OR (nudeName.length = 0) THEN {eType ← invalidFileName; GOTO AnError};
[ok: ok, cap: file, isSD: sd] ← DirectoryTrees.BTreeFind[pDD, nudeName];
IF sd THEN {eType ← fileIsSD; GOTO AnError};
IF ok OR (pSP = NIL) THEN EXIT;
DoneWith[pDD];
ENDLOOP;
IF ~ok THEN {eType ← fileNotFound; GOTO AnError};
DirectoryProps.SetTimes[file ! Directory.Error => IF type = fileNotFound THEN {
badVol ← volumeTable[directoryCache[PDCFromDD[pDD]].pVT].volID;
DoneWith[pDD];
GOTO AVolumeError}
];
DoneWith[pDD];
RETURN
EXITS
AnError => {DoneWith[pDD]; RETURN WITH ERROR Directory.Error[eType]};
AVolumeError => RETURN WITH ERROR Directory.VolumeError[directoryNeedsScavenging, badVol];
END;
END;

ModifyContext: PUBLIC ENTRY
PROC [
co
ntext: Handle, pathName: LONG STRING, sP: Directory.SearchPath] =
-- Changes the values o
f a context
BEGIN
ENABLE { UNWIN
D => NULL; Directory.Error => {eType ← type; GOTO AnError}};
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor;
wc: BOOLEAN;
emptyName: STRING ← [Directory.maxDirectoryNameLength];
pSP, nextSP: LONG POINTER TO DirectoryInternal.SPRecord;
IF context = Directory.defaultContext THEN context ← DirectoryContexts.contextTable[0];
[] ← DirectoryContexts.ValidateContext[context ! Directory.Error => IF type = fileNotFound THEN CONTINUE];
IF pathName # NIL THEN {
-- change working dir
ectory
[pDD: pDD, wc: wc] ← X
lateName[pathName, NIL, emptyName, MustBeQualified];
IF wc OR (emptyName.length # 0) THEN {
DoneWith[pDD]; eType ← invalidFileName; GOTO AnError};
DirectoryTrees.MoveLongString[from: pathName, to: @context.wdName];
DoneWith[gnLastDD]; -- invalidate cached info
rmation for GetNext
gnLastDD ← NIL;
IF context.valid THEN FlushCache[context.pDC];
context.valid ← TRUE;
context.pDC ← PDCFromDD[pDD]};
IF sP # NIL THEN {
-- change search path

IF LENGTH[sP] = 0 THEN
RETURN WITH ERROR Directory.Error[invalidSearchPath];
IF sP[0].length = 0 THEN
RETURN WITH ERROR Directory.Error[invalidSearchPath];
pSP ← context.pSP;
WHILE pSP # NIL DO
nextSP ← pSP.pSP;
IF pSP.defined THEN FlushCache[pSP.pDC];
DirectoryContexts.ContextSpace.FREE[@pSP];
pSP ← nextSP;
ENDLOOP;
FOR i: CARDINAL IN [0..LENGTH[sP]) DO
IF sP[i].length = 0 THEN EXIT;
SELECT Break[sP[i], emptyName, 0].field FROM
= Directory =>
nextSP ← DirectoryContexts.ContextSpace.NEW[
withWD DirectoryInternal.SPRecord];
ENDCASE =>
nextSP ← DirectoryContexts.ContextSpace.NEW[
noWD DirectoryInternal.SPRecord];
WITH sp: nextSP↑ SELECT FROM
noWD => NULL;
withWD => {sp.pWD ← context; sp.cap ← File.nullCapability};
ENDCASE;
nextSP.spName ←
[length: 0, maxlength: Directory.maxPathNameLength, text:];
DirectoryTrees.MoveLongString[to: @nextSP.spName, from: sP[i]];
nextSP.pSP ← NIL;
nextSP.pDC ← 0;
nextSP.defined ← FALSE;
IF i = 0 THEN context.pSP ← nextSP ELSE pSP.pSP ← nextSP;
pSP ← nextSP
ENDLOOP};
EXITS AnError => RETURN WITH ERROR Directory.Error[eType];
END;

RemoveFile: PUBLIC ENTRY PRO
C [
fi
leName: LONG STRING, file: File.Capability, context: Directory.Handle] =
-- Removes a file/capab
ility pair from the directory
BEGIN
ENABLE UNWIND
=> NULL;
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
actualFile: File.Capability;
exists, sd, wc: BOOLEAN;
BEGIN
ENABLE Directory.Error => {eType ← type; GOTO AnError};
context ← DirectoryContexts.ValidateContext[context];
[pDD: pDD, wc: wc] ← XlateName[fileName, context, nudeName, UseWD];
IF wc OR (nudeName.length = 0) THEN {eType ← invalidFileName; GOTO AnError};
[ok: exists, cap: actualFile, isSD: sd] ← DirectoryTrees.BTreeFind[pDD, nudeName];
IF ~exists OR (file # actualFile) THEN {eType ← fileNotFound; GOTO AnError};
IF sd THEN {eType ← fileIsSD; GOTO AnError};
[] ← DirectoryTrees.BTreeDelete[pDD, nudeName, FALSE];
Space.ForceOut[pDD.space];
DoneWith[pDD];
RETURN
EXITS AnError => {DoneWith[pDD]; RETURN WITH ERROR Directory.Error[eType]};
END;
END;

Rename: PUBLIC ENTRY PROC [
oldName, newName: LONG STRING, context: Directory.Handle] =
-- Renames a file leavi
ng the capability unchanged
BEGIN
ENABLE UNWIND
=> NULL;
oldDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
newDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
newNudeName: STRING ← [Directory.maxDirectoryNameLength+1];
cap: File.Capability;
wc, ok: BOOLEAN;
pDCOld, pDCNew: CARDINAL;
pDT: DirectoryInternal.PDT;
pDTNode: DirectoryInternal.PDTNode;
BEGIN
ENABLE Directory.Error => {eType ← type; GOTO AnError};
context ← DirectoryContexts.ValidateContext[context];
[pDD: oldDD, wc: wc] ← XlateName[oldName, context, nudeName, UseWD];
IF wc OR (nudeName.length = 0) THEN {eType ← invalidFileName; GOTO AnError};
[pDD: newDD, wc: wc] ← XlateName[newName, context, newNudeName, UseWD];
IF wc OR (newNudeName.length = 0) THEN {eType ← invalidFileName; GOTO AnError};
IF DirectoryTrees.BTreeFind[newDD, newNudeName].ok
THEN {eType ← fileAlreadyExists; GOTO AnError};
[ok: ok, cap: cap] ← DirectoryTrees.BTreeDelete[oldDD, nudeName, FALSE];
IF ok THEN
-- renaming a normal file
[] ← DirectoryTrees.BTreeInsert[newDD, newNudeName, cap]
ELSE {
-- renaming a subdirectory
IF ~ DirectoryTrees.BTreeFind[oldDD, nudeName].ok THEN {eType ← fileNotFound; GOTO AnError};
pDCOld ← PDCFromDD[oldDD];
pDCNew ← PDCFromDD[newDD];
pDT ← volumeTable[directoryCache[pDCOld].pVT].pDT;
IF pDT # volumeTable[directoryCache[pDCNew].pVT].pDT
THEN {eType ← invalidPathName; GOTO AnError};
pDTNode ← DirectoryTrees.DTFind[pDT, directoryCache[pDCOld].pDTNode, nudeName];
DirectoryTrees.DTMoveNode[
pDT: pDT,
pDTNode1: pDTNode,
pDTNode2: directoryCache[pDCNew].pDTNode];
DirectoryTrees.MoveLongString[from: newNudeName, to: @pDT[pDTNode].name];
nudeName[nudeName.length] ← Ascii.BEL;
nudeName.length ← nudeName.length+1;
newNudeName[newNudeName.length] ← Ascii.BEL;
newNudeName.length ← newNudeName.length+1;
[cap: cap] ← DirectoryTrees.BTreeDelete[oldDD, nudeName, TRUE];
[] ← DirectoryTrees.BTreeInsert[newDD, newNudeName, cap];
Space.ForceOut[volumeTable[directoryCache[pDCOld].pVT].dtSpace];
newNudeName.length ← newNudeName.length-1};
DirectoryProps.SetNameAndCap[cap, newNudeName, directoryCache[PDCFromDD[newDD]].cap];
Space.ForceOut[oldDD.space];
IF oldDD.base # newDD.base THEN Space.ForceOut[newDD.space];
DoneWith[oldDD];
DoneWith[newDD];
RETURN
EXITS AnError => {DoneWith[oldDD]; DoneWith[newDD]; RETURN WITH ERROR Directory.Error[eType]};
END;
END;

SetPermissions: PUBLIC ENTRY
PROC [
fi
leName: LONG STRING, file: File.Capability, context: Directory.Handle] =
-- Sets the permissions
in the capability in the directory
BEGIN
ENABLE UNWIND
=> NULL;
ok, sd, wc: BOOLEAN;
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor ← NIL;
pDE: DirectoryInternal.DirectoryEntryHandle;
BEGIN
ENABLE Directory.Error => {eType ← type; GOTO AnError};
context ← DirectoryContexts.ValidateContext[context];
[pDD: pDD, wc: wc] ← XlateName[fileName, context, nudeName, UseWD];
IF wc THEN {eType ← invalidFileName; GOTO AnError};
[ok: ok, isSD: sd, ent: pDE] ← DirectoryTrees.BTreeFind[pDD, nudeName];
IF sd THEN {eType ← fileIsSD; GOTO AnError};
IF ~ok OR (file.fID # pDE.cap.fID) THEN {eType ← fileNotFound; GOTO AnError};
pDE.cap.permissions ← file.permissions;
Space.ForceOut[pDD.space];
DoneWith[pDD];
RETURN
EXITS AnError => {DoneWith[pDD]; RETURN WITH ERROR Directory.Error[eType]};
END;
END;

-- internal procedures

Break: INTERNAL PROC [ls, s: LONG STRING, position: CARDINAL]
RETURNS [new: CARDINAL, field: FieldType, wildCard: BOOLEAN] =
-- Breaks a file name in
to left component starting at position
BEGIN
wildCard ← FAL
SE;
s.length ← 0;
IF ls.length <= position THEN RETURN[position, Empty, wildCard];
IF ls[position] = ’< THEN {
FOR i: CARDINAL IN [0..ls.length - position - 1) DO
IF ls[i + position + 1] = ’> THEN {
position ← position + i + 2; field ← Volume; EXIT}
ELSE {
IF WildCard[ls[i + position + 1]] THEN wildCard ← TRUE
ELSE IF ~CheckChar[ls[i + position + 1]] THEN GOTO BadChar;
s[i] ← ls[i + position + 1];
s.length ← s.length + 1};
REPEAT
BadChar => {field ← Funny; position ← ls.length};
FINISHED => {field ← Funny; position ← ls.length};
ENDLOOP;
RETURN[position, field, wildCard]}
ELSE {
FOR i: CARDINAL IN [0..ls.length - position) DO
IF ls[i + position] = ’> THEN {
position ← position + i + 1; field ← Directory; EXIT}
ELSE {
IF WildCard[ls[i + position]] THEN wildCard ← TRUE
ELSE IF ~CheckChar[ls[i + position]] THEN GOTO BadChar;
s[i] ← ls[i + position];
s.length ← s.length + 1};
REPEAT
BadChar => {field ← Funny; position ← ls.length};
FINISHED => {field ← File; position ← ls.length};
ENDLOOP;
RETURN[position, field, wildCard]};
END;

CheckChar: INTERNAL PROC [c: CHARACTER] RETURNS [BOOLEAN] =
-- test if a character
is valid for inclusion in a file name
INLINE {RETURN[pValidCha
rs[LOOPHOLE[c]]]};

DoneWith: PUBLIC INTERNAL PROC [
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor] =
-- flush the directory
cache entry of a directory descriptor
BEGIN IF pDD # NIL THEN
FlushCache[PDCFromDD[pDD]]; RETURN END;

FlushCache: INTERNAL PROC [pDC: CARDINAL] =
-- Decrement the refere
nce count on the cache entry. If zero, remove entry
BEGIN
space: Space.H
andle ← directoryCache[pDC].dir.space;
directoryCache[pDC].refCount ← directoryCache[pDC].refCount - 1;
IF directoryCache[pDC].refCount > 0 THEN RETURN;
Space.Unmap[space];
Space.Delete[space];
END;

GetDirectoryCache: PUBLIC INTERNAL PROC [cap: File.Capability]
RETURNS [pDC: CARDINAL] =
-- returns a "pointer" t
o the cache entry of a capability, doing the load if necessary
BEGIN
dirSize: Space
.PageCount;
dirSpace: Space.Handle;
dirPage: DirectoryInternal.DirectoryPageHandle;
pFreeDC: CARDINAL ← DirectoryFiles.maxDCache + 1;
FOR pDC IN (0..DirectoryFiles.maxDCache] DO
IF directoryCache[pDC].refCount = 0 THEN pFreeDC ← pDC
ELSE
IF directoryCache[pDC].cap = cap THEN {
directoryCache[pDC].refCount ← directoryCache[pDC].refCount + 1;
RETURN[pDC]};
ENDLOOP;
IF pFreeDC = DirectoryFiles.maxDCache + 1 THEN ERROR;
IF ~ DirectoryProps.ValidLP[cap] THEN {
vol: Volume.ID;
{ENABLE File.Unknown => {vol ← Volume.nullID; CONTINUE};
vol ← File.GetAttributes[cap].volume;
ERROR Directory.VolumeError[directoryNeedsScavenging, vol]}};
pDC ← pFreeDC;
directoryCache[pDC].cap ← cap;
directoryCache[pDC].refCount ← 1;
directoryCache[pDC].pVT ← GetVTEntryFromID[File.GetAttributes[cap].volume];
directoryCache[pDC].pDTNode ← GetPDTNode[
volumeTable[directoryCache[pDC].pVT].pDT, cap];
dirSize ← Inline.LowHalf[File.GetSize[cap]];
dirSpace ← Space.Create[DirectoryInternal.maxDirSize, Space.virtualMemory];
Space.CreateUniformSwapUnits[DirectoryInternal.swapUnitSize, dirSpace];
Space.Map[dirSpace, [cap, DirectoryInternal.leaderPageSize]];
dirPage ← Space.LongPointer[dirSpace];
directoryCache[pDC].dir ←
[base: dirPage, top: dirPage.top, size: dirSize - DirectoryInternal.leaderPageSize, space: dirSpace];
RETURN[pDC];
END;

GetPDTNode: INTERNAL PROC [pDT: DirectoryInternal.PDT, cap: File.Capability]
RETURNS [pDTNode: DirectoryInternal.PDTNode] =
-- returns a pointer to
the Directory Tree entry of a directory capability
BEGIN
pDTNode ← pDT.
son;
IF cap = pDT[pDTNode].cap THEN RETURN[pDTNode];
UNTIL pDTNode = DirectoryInternal.pDTnil DO
pDTNode ← DirectoryTrees.PDTNext[pDT, pDTNode];
IF pDT[pDTNode].cap = cap THEN RETURN[pDTNode];
IF pDT[pDTNode].level = 0 THEN
ERROR Directory.VolumeError[directoryNeedsScavenging, File.GetAttributes[cap].volume];
ENDLOOP;
END;

GetVTEntry: INTERNAL PROC [name: LONG STRING] RETURNS [pVT: CARDINAL] =
-- returns a "pointer" t
o the volume table entry of a volume label string, doing the load if necessary
BEGIN
vol: Volume.ID
← Volume.nullID;
volLabel: STRING ← [40];
pFreeVT: CARDINAL ← DirectoryFiles.maxDVolumes;
treeCap: File.Capability;
isTree: BOOLEAN;
dirSize, treeSize: Space.PageCount;
dirSpace, treeSpace: Space.Handle;
dirPage: DirectoryInternal.DirectoryPageHandle;
pDC: CARDINAL ← DirectoryFiles.maxDCache;
-- get cached entry
FOR pVT IN [0..DirectoryFiles.maxDVolumes) DO
IF LongString.EquivalentString[name, @volumeTable[pVT].volName] THEN
RETURN[pVT];
IF volumeTable[pVT].pDT = NIL THEN pFreeVT ← pVT;
ENDLOOP;
-- not in table: do l
oad
DO
vol ← Volume
.GetNext[vol, [TRUE, TRUE, TRUE, FALSE]];
IF vol = Volume.nullID THEN ERROR Directory.Error[volumeNotFound];
Volume.GetLabelString[vol, volLabel];
IF LongString.EquivalentString[name, volLabel] THEN EXIT;
ENDLOOP;
IF pFreeVT = DirectoryFiles.maxDVolumes THEN ERROR;
pVT ← pFreeVT;
volumeTable[pVT].volID ← vol;
DirectoryTrees.MoveLongString[to: @volumeTable[pVT].volName, from: volLabel];
-- load the directory c
ache with the root directory of this volume
-- cannot use GetDirectoryCache here since it assumes a valid volume table entry
FOR pDC IN (0..Dire
ctoryFiles.maxDCache] DO
IF directoryCache[pDC].refCount = 0 THEN EXIT;
REPEAT FINISHED => ERROR;
ENDLOOP;
directoryCache[pDC].cap ← Volume.GetAttributes[vol].rootFile;
IF directoryCache[pDC].cap = File.nullCapability THEN
{volumeTable[pVT].volName.length ← 0; ERROR Directory.VolumeError[rootNotFound, vol]};
IF Volume.GetStatus[vol] # open THEN
{volumeTable[pVT].volName.length ← 0; ERROR Directory.VolumeError[volumeNotOpen, vol]};
IF ~ DirectoryProps.ValidLP[directoryCache[pDC].cap] THEN
{volumeTable[pVT].volName.length ← 0; ERROR Directory.VolumeError[directoryNeedsScavenging, vol]};
directoryCache[pDC].pVT ← pVT;
dirSize ← Inline.LowHalf[File.GetSize[directoryCache[pDC].cap]];
dirSpace ← Space.Create[DirectoryInternal.maxDirSize, Space.virtualMemory];
Space.CreateUniformSwapUnits[DirectoryInternal.swapUnitSize, dirSpace];
Space.Map[dirSpace, [directoryCache[pDC].cap, DirectoryInternal.leaderPageSize]];
directoryCache[pDC].refCount ← 1;
dirPage ← Space.LongPointer[dirSpace];
directoryCache[pDC].dir ←
[base: dirPage, top: dirPage.top, size: dirSize - DirectoryInternal.leaderPageSize, space: dirSpace];
[ok: isTree, cap: treeCap] ← DirectoryTrees.BTreeFind[
@directoryCache[pDC].dir, "Volume.DirectoryTree"L];
IF ~isTree THEN {
Space.Unmap[dirSpace]; Space.Delete[dirSpace];
volumeTable[pVT].volName.length ← 0;
ERROR Directory.VolumeError[directoryNeedsScavenging, vol]};
treeSize ← Inline.LowHalf[File.GetSize[treeCap]];
volumeTable[pVT].dtSpace ← treeSpace ← Space.Create[DirectoryInternal.maxDirSize, Space.virtualMemory];
treeCap.permissions ← Directory.fileMaxPermissions;
Space.Map[treeSpace, [treeCap, DirectoryInternal.leaderPageSize]];
volumeTable[pVT].pDT ← Space.LongPointer[treeSpace];
directoryCache[pDC].pDTNode ← DirectoryTrees.DTFirst[volumeTable[pVT].pDT];
RETURN[pVT];
END;

GetVTEntryFromID: PUBLIC INTERNAL PROC [vol: Volume.ID]
RETURNS [pVT: CARDINAL] =
-- returns a "pointe
r" to the volume table entry of a volume ID, doing the load if necessary
-- this is a code-saving slow way to do this, but it is only used rarely
BEGIN
name: STRI
NG ← [40];
Volume.GetLabelString[
vol, name ! Volume.Unknown => ERROR Error[volumeNotFound]];
RETURN[GetVTEntry[name]];
END;

PDCFromDD: PUBLIC INTERNAL PROC [pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor]
RETURNS
[pDC: CARDINAL] =
-- returns the directory
cache entry of a directory descriptor
-- note the reference count is unchanged since to get the descriptor we went to the cache!
BEGIN
FOR pDC DECREASING IN (0..DirectoryFiles.maxDCache] DO
IF (directoryCache[pDC].refCount > 0) AND
(directoryCache[pDC].dir.base = pDD.base) THEN RETURN[pDC]
ENDLOOP;
ERROR;
END;

ValidateSP: INTERNAL PROC [pSP: LONG POINTER TO DirectoryInternal.SPRecord]
RETURNS [LONG POINTER TO Directory
Internal.SPRecord] =
-- ensures that the search path entry is current with the working directory
BEGIN
emptyName: STRING ← [Directory.maxDirectoryNameLe
ngth];
WITH sp: pSP↑ SELECT FROM
noWD =>
IF sp.defined THEN RETURN[pSP]
ELSE {
sp.pDC ← PDCFromDD[
XlateName[@sp.spName, NIL, emptyName, MustBeQualified].pDD];
IF emptyName.length # 0 THEN ERROR Directory.Error[invalidSearchPath];
sp.defined ← TRUE;
RETURN[pSP]};
withWD =>
IF sp.defined AND (sp.cap = directoryCache[sp.pWD↑.pDC].cap) THEN
RETURN[pSP]
ELSE {
IF sp.defined THEN FlushCache[sp.pDC];
sp.pDC ← PDCFromDD[XlateName[@sp.spName, sp.pWD, emptyName, UseWD].pDD];
IF emptyName.length # 0 THEN ERROR Directory.Error[invalidSearchPath];
sp.cap ← directoryCache[sp.pWD↑.pDC].cap;
sp.defined ← TRUE;
RETURN[pSP]};
ENDCASE;
ERROR; -- this is an impossible place to get to!

END;

WildCard: INTERNAL PROC [c: CHARACTER] RETURNS [BOOLEAN] =
-- Returns boolean indicating whether the passed character is a wildcard or not
INLINE {RETURN[c = ’* OR c = ’#]};

XlateName: INTERNAL PROC [
fullName: LONG STRING, context: Handle, name: LONG STRING, search: SearchType,
at: LONG POINTER TO DirectoryInternal.SPRecord ← NIL]
RETURNS [
pDD: LONG POINTER TO DirectoryInternal.DirectoryDescriptor,
next: LONG POINTER TO DirectoryInternal.SPRecord, wc: BOOLEAN] =
-- This is the guts of the name translation
. The file name fullName is broken down to the unqualified
-- name and a pointer
to a directory descriptor. IF search useWD, use the working directory; if it is
-- UseWD, use the search path at element at (NIL to start); otherwise, the name must be qualified.
-- The pointer next is non-NIL if there is another search path element to consult (for this fullName must
-- not be qualified). The boolean wc indicates whether the
returned file name includes any wildcards.
BEGIN
posn: CARDINAL ← 0;
ft: FieldType;
qualified: BOOLEAN;
pDT: DirectoryInternal.PDT; -- the directory tree we are using
pDTNode: DirectoryInternal.PDTNode; -- the current node in the directory tree
pDC: CARDINAL;
pDCEntry: LONG POINTER TO DirectoryFiles.DCEntry;
IF fullName = NIL THEN ERROR Directory.Error[invalidFileName];
[posn, ft, wc] ← Break[fullName, name, posn];
SELECT ft FROM -- set up starting values in tree from first identifie
r

= File =>
IF search = UseWD THEN {
pDCEntry ← @directoryCache[context.pDC];
pDD ← @pDCEntry.dir;
pDCEntry.refCount ← pDCEntry.refCount + 1;
next ← NIL;
RETURN}
ELSE
IF search = UseSP THEN {
IF at = NIL THEN at ← context.pSP;
pDCEntry ← @directoryCache[ValidateSP[at].pDC];
pDD ← @pDCEntry.dir;
pDCEntry.refCount ← pDCEntry.refCount + 1;
next ← at.pSP;
RETURN}
ELSE ERROR Error[invalidPathName];
= Volume => {
IF wc THEN ERROR Error[notImplemented];
qualified ← TRUE;
pDT ← volumeTable[GetVTEntry[name]].pDT;
pDTNode ← DirectoryTrees.DTFirst[pDT]};
= Directory => {
IF wc THEN ERROR Error[notImplemented];
IF LongString.EquivalentString["workdir"L, name] THEN {
-- really is a name qualified with the context’s working directory (or default)
qualified ← TRUE;
IF search = UseWD THEN {
IF ~context.valid THEN ERROR Error[fileNotFound] ELSE pDC ← context.pDC}
ELSE {
IF ~DirectoryContexts.contextTable[0].valid THEN ERROR Error[fileNotFound]
ELSE pDC ← DirectoryContexts.contextTable[0].pDC};
pDT ← volumeTable[directoryCache[pDC].pVT].pDT;
pDTNode ← directoryCache[pDC].pDTNode}
ELSE {
-- this is really a directory which needs to be qualified with the working directory
IF search = MustBeQualified THEN ERROR Error[invalidPathName];
qualified ← FALSE;
IF search = UseWD THEN {
pDT ← volumeTable[directoryCache[context.pDC].pVT].pDT;
pDTNode ← directoryCache[context.pDC].pDTNode}
ELSE {
IF at = NIL THEN at ← context.pSP;
pDT ← volumeTable[directoryCache[ValidateSP[at].pDC].pVT].pDT;
pDTNode ← directoryCache[at.pDC].pDTNode};
pDTNode ← DirectoryTrees.DTFind[pDT, pDTNode, name];
IF pDTNode = DirectoryInternal.pDTnil THEN ERROR Error[fileNotFound]}};
ENDCASE => ERROR Directory.Error[invalidFileName];
DO
-- trace down the tree according to the subdirectories in the name
[posn, ft, wc] ← Break[fullName, name, posn];
SELECT ft FROM
= File, Empty => EXIT;
= Directory => {
IF wc THEN ERROR Error[notImplemented];
pDTNode ← DirectoryTrees.DTFind[pDT, pDTNode, name];
IF pDTNode = DirectoryInternal.pDTnil THEN ERROR Error[fileNotFound]};
ENDCASE => ERROR Directory.Error[invalidFileName];
ENDLOOP;
pDD ← @directoryCache[GetDirectoryCache[pDT[pDTNode].cap]].
dir;
next ← IF qualified OR (search # UseSP) THEN NIL ELSE at.pSP;
RETURN
END;

-- Allocate the needed tables and strings

volumeTable ← DirectoryContexts.ContextSpace.NEW[VolumeTable ← ALL[]];
FOR i: CARDINAL IN [0.. DirectoryFiles.maxDVolumes) DO
volumeTable[i] ←
[volName: [length: 0, maxlength: 40, text:], filler: ALL[’ ], volID: Volume.nullID, pDT: NIL, dtSpace:]
ENDLOOP;
directoryCache ← DirectoryContexts.ContextSpace.NEW[DirectoryCache ← ALL[]];
FOR i: CARDINAL IN [1.. DirectoryFiles.maxDCache] DO
directoryCache[i] ←
[File.nul
lCapability, 0, 0, DirectoryInternal.pDTnil,
[base: NIL, top: DirectoryInternal.nilPagePointer, size: 0, space:]]
ENDLOOP;
gnLastPath ← DirectoryContexts.ContextSpace.NEW[StringBody[Directory.maxPathNameLength]];
gnLastNameFromPath ← DirectoryContexts.ContextSpace.NEW[StringBody[Directory.maxDirectoryNameLength]];
gnLastNextName ← DirectoryContexts.ContextSpace.NEW[StringBody[Directory.maxPathNameLength]];
gnLastNameOfNext ← DirectoryContexts.ContextSpace.NEW[StringBody[Directory.maxDirectoryNameLength]];
nudeName ← DirectoryContexts.ContextSpace.NEW[StringBody[Directory.maxDirectoryNameLength+1]];
pValidChars ← DirectoryContexts.ContextSpace.NEW[ValidChars ← ALL[FALSE]];
FOR i: Environment.Byte IN [0.. 256) DO {
c: CHARACTER ← LOOPHOLE[i];
SELECT c FROM
IN [’+..’;] => pValidChars[i] ← TRUE; -- includes the numbers

IN [’@..’←] => pValidChars[i] ← TRUE; -- includes the upper case letters

IN [’a..’~] => pValidChars[i] ← TRUE; -- includes the lower case letters

= ’,, = ’?, = ’$, = ’%, = ’’, = ’", = ’!, = ’&, = ’(, = ’) => pValidChars[i] ← TRUE;
-- misc stuff which is ok

= Ascii.BEL => pValidChars[i] ← TRUE; -- for directory names

ENDCASE};
ENDLOOP;

END.




LOG

Time: August 28, 1980 11:42 AM By: Keith Action: Created Fil
e
Time: September 19,
1980 2:06 PM By: Keith Action: Corrected errors returned from CreateFile and CreateSubdirectory
Time: October 8, 1980
3:28 PM By: Keith Action: Fixed easy bugs (ARs 5373, 6075, 6115, 6123, 6180, 6184); fixed Remove so DoneWith is performed before exit; fixed Insert and Remove to return correct Booleans.
Time: Octo
ber 12, 1980 4:39 PM By: Fay Action: Changes for merge of DirectoryExtras into Directory.
Time: January 13, 1981 11:35 AM By: Keith Action: Fixed ARs, added masking to GetNext, changed CheckChar to use a table a
nd reduced global frame size.