-- CIFSImpl.mesa
-- Implementation of the Cedar Interim File System.
-- Coded October, 1981 by D. Gifford
-- Changed, October 4, 1982 2:46 pm, by Mike Schroeder

DIRECTORY
Ascii: TYPE USING [NUL],
List: TYPE USING [Append],
CedarSnapshot: TYPE USING [Register, CheckpointProc, RollbackProc],
CIFS,
CIFSPrivate,
ConvertUnsafe: TYPE USING [AppendRope],
Directory: TYPE USING [GetNext, Rename, Error, ErrorType],
DirMan: TYPE USING [Dir, Open, Close, EProc, Enumerate, Delete,
Insert, Destroy],
File: TYPE USING [Capability],
FT: TYPE USING [Close, Open, Delete, ExplicitBackup, Login, FileObject,
Rename, Reset, Swap, SetBaseFreeSpace, GetBaseFreeSpace, Connect],
Inline: TYPE USING [BITAND, BITOR],
Rope: TYPE USING [ROPE, Cat, Compare, Size, Index, Concat, Equal,
Fetch, Substr, FromRefText],
Runtime: TYPE USING [BoundsFault];

CIFSImpl: PROGRAM
IMPORTS List, CedarSnapshot, ConvertUnsafe,
Directory, DirMan, FT, Inline, Rope, Runtime
EXPORTS CIFS, CIFSPrivate = {

-- Types

global: REF Global ← NIL;
-- Save on MDS space

Global: TYPE = RECORD [
-- state for checkpoint/rollback
wdir: Rope.ROPENIL,
searchRules: LIST OF Rope.ROPENIL
];

OpenFile: TYPE = REF FileObject;

FileObject: PUBLIC TYPE = FT.FileObject;

Context: TYPE = REF ContextObj;

Default: Context ← CreateContext[];

ContextObj: PUBLIC TYPE = RECORD [
wdir: SREntry,
searchRules: LIST OF REF SREntry
];

SREntry: TYPE = RECORD [
path: Rope.ROPE,
d: DirMan.Dir
];


-- Public Procedures

AddSearchRule: PUBLIC SAFE PROC [path: Rope.ROPE, before: BOOLEAN,
c: Context ← NIL] = TRUSTED {
-- Add a search rule
-- If before is T it will add it before the other search rules
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
sre: REF SREntry ← NEW[SREntry];
IF c=NIL THEN c ← Default;
path ← ConvertName[path]; -- convert pathname if necessary
sre.path ← MakeAbsolute[path, c];
sre.d ← NIL;

-- HACK HACK
-- if dir is /local don't open directory
IF Rope.Compare[sre.path, "/local", FALSE] # equal THEN {
sre.d ← DirMan.Open[sre.path];
};
c.searchRules ← LOOPHOLE[IF before THEN
List.Append[LIST[sre], LOOPHOLE[c.searchRules]]
ELSE
List.Append[LOOPHOLE[c.searchRules], LIST[sre]]];
}};

Close: PUBLIC SAFE PROC [fh: CIFS.OpenFile] = TRUSTED {
-- Close a file
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
FT.Close[fh];
}};

Connect: PUBLIC SAFE PROC [name, password: Rope.ROPE] = TRUSTED {
-- Set credentials
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
FT.Connect[name, password];
}};

CopyContext: PUBLIC SAFE PROC [from: Context ← NIL]
RETURNS [c: Context] = TRUSTED {
-- Copy a context
-- If from is omitted, CopyContext will return a copy of the default
-- context.
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
x: LIST OF REF SREntry;
ex: REF SREntry ← NEW[SREntry];
-- make a new context and set up wdir
IF from=NIL THEN from ← Default;
c ← CreateContext[];
c.wdir.path ← from.wdir.path;
c.wdir.d ← IF from.wdir.d#NIL THEN DirMan.Open[c.wdir.path] ELSE NIL;
-- now copy the search rules
c.searchRules ← NIL;
FOR x ← from.searchRules, x.rest
UNTIL x=NIL DO
ex.path ← x.first.path;
ex.d ← IF x.first.d#NIL THEN DirMan.Open[x.first.path] ELSE NIL;
c.searchRules ← LOOPHOLE[
List.Append[LOOPHOLE[c.searchRules], LIST[ex]]
];
ENDLOOP;
}};

CreateContext: PUBLIC SAFE PROC
RETURNS [c: Context] = TRUSTED {
-- Create a new context for the interpretation of names
-- The "Jerry Brown" Operation
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
c ← NEW[ContextObj];
-- set defaults
c.wdir.d ← NIL;
c.wdir.path ← "/local";
c.searchRules ← NIL;
RETURN[c];
}};

CreateDir: PUBLIC SAFE PROC [name: Rope.ROPE, c: Context] = TRUSTED {
-- Create a directory
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
d: DirMan.Dir;
dir, entry: Rope.ROPE;
notThere: BOOLEANTRUE;
ip: DirMan.EProc = {notThere ← FALSE; RETURN[TRUE]};
IF c=NIL THEN c ← Default;
name ← ConvertName[name];
[dir, entry] ← Parse[name ← MakeAbsolute[name, c]];
-- place name in directory system
d ← DirMan.Open[dir];
entry ← Rope.Concat[entry, "/"];
d.Enumerate[entry, ip];
IF notThere THEN d.Insert[entry];
d.Close[];
-- now create directory
d ← NIL;
d ← DirMan.Open[name ! Error => CHECKED {CONTINUE}];
-- if directory is not there, then create it
IF d=NIL THEN d ← DirMan.Open[name, TRUE];
d.Close[];
}};

CreateLink: PUBLIC SAFE PROC [path, targetPath: Rope.ROPE, c: Context] = TRUSTED {
-- causes path to point to targetPath
-- whenever path is used as a file name, it will be resolved to
-- targetPath
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
d: DirMan.Dir;
expandedT, dir, entry: Rope.ROPE;
found: BOOLEANFALSE;
ip: DirMan.EProc = {
found ← TRUE; RETURN[TRUE];
};
IF c=NIL THEN c ← Default;
path ← ConvertName[path];
targetPath ← ConvertName[targetPath];
expandedT ← Expand[targetPath, c];
[dir, entry] ← Parse[path ← MakeAbsolute[path, c]];
d ← DirMan.Open[dir];
d.Enumerate[entry, ip];
IF found THEN {
d.Close[];
Error[CIFS.ErrorCode[linkAlreadyExists],
Rope.Concat[path, " link already exists."]];
};
d.Insert[name: entry, link: expandedT];
d.Close[];
}};

Delete: PUBLIC SAFE PROC [name: Rope.ROPE, c: Context] = TRUSTED {
-- Delete a file
-- Files are created by Open
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
d: DirMan.Dir ← NIL;
dir, entry, path, entryPath: Rope.ROPE;
IF c=NIL THEN c ← Default;
name ← ConvertName[name];
[path, entryPath] ← InternalExpand[name, c];
-- remove name from directory system
[dir, entry] ← Parse[entryPath];
IF Rope.Compare[dir, "/local", FALSE] # equal THEN {
{ENABLE Error => TRUSTED {IF d#NIL THEN d.Close; CONTINUE};
d ← DirMan.Open[dir];
d.Delete[entry];
d.Close[];
}};
-- if this is not a link, delete file
IF Rope.Compare[path, entryPath, FALSE] = equal THEN FT.Delete[path];
}};

DeleteContext: PUBLIC SAFE PROC [c: Context ← NIL] = TRUSTED {
-- Destroy a context for the interpretation of names.
-- The "Edward Kennedy" Operation
-- close all open directories
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
IF c=NIL THEN c ← Default;
FOR x: LIST OF REF SREntry ← c.searchRules, x.rest
UNTIL x=NIL DO
x.first.path ← NIL;
x.first.d.Close[];
ENDLOOP;
c.searchRules ← NIL;
-- if wdir has open directory, close it
IF c.wdir.d#NIL THEN c.wdir.d.Close[];
c.wdir.path ← "/local";
}};

DeleteDir: PUBLIC SAFE PROC [name: Rope.ROPE, c: Context] = TRUSTED {
-- Delete a directory
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
d: DirMan.Dir;
dirName, dir, entry: Rope.ROPE;
notEmpty: BOOLEANFALSE;
ip: DirMan.EProc = {notEmpty ← TRUE; RETURN[TRUE]};
IF c=NIL THEN c ← Default;
name ← ConvertName[name];
dirName ← MakeAbsolute[name, c];
[dir, entry] ← Parse[dirName];
-- ensure directory empty
d ← DirMan.Open[dirName];
d.Enumerate["*", ip];
IF notEmpty THEN {
d.Close[];
Error[CIFS.ErrorCode[directoryNotEmpty],
Rope.Concat[name, " not empty."]];
};
-- delete directory
d.Destroy[];
d ← DirMan.Open[dir];
d.Delete[Rope.Concat[entry, "/"]];
d.Close[];
}};

DeleteSearchRule: PUBLIC SAFE PROC [path: Rope.ROPE, c: Context] = TRUSTED {
-- Delete a search rule
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
x: LIST OF REF SREntry;
newSR: LIST OF REF SREntry ← NIL;
IF c=NIL THEN c ← Default;
path ← ConvertName[path];
path ← MakeAbsolute[path, c];
-- First, see if we can find it
FOR x ← c.searchRules, x.rest
UNTIL x=NIL DO
-- if match, delete
IF Rope.Compare[path, x.first.path, FALSE] = equal THEN {
-- close directory
IF x.first.d#NIL THEN x.first.d.Close[];
} ELSE {
newSR ← LOOPHOLE[List.Append[LOOPHOLE[newSR], LIST[x.first]]];
};
ENDLOOP;
c.searchRules ← newSR;
}};

Enumerate: PUBLIC SAFE PROC [dir: Rope.ROPE, pattern: Rope.ROPE,
p: CIFS.EProc, c: Context] = TRUSTED {
-- Enumerate the contents of a directory
-- pattern can contain "*" and "#"
-- a pattern of "*" enumerates the entire directory.
-- If dir = NIL or dir = "" Enumerate assumes the wdir
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
ent: REF TEXTNEW[TEXT[CIFS.maxPath]];
lnk: REF TEXTNEW[TEXT[CIFS.maxPath]];
com: REF TEXTNEW[TEXT[CIFS.maxComment]];
d: DirMan.Dir;
IF c=NIL THEN c ← Default;
dir ← ConvertName[dir];
IF (dir=NIL) OR (Rope.Equal[dir, ""]) THEN
dir ← c.wdir.path
ELSE
dir ← MakeAbsolute[dir, c];

-- HACK HACK
-- Enumerate local FS
IF Rope.Compare[dir, "/local", FALSE] = equal THEN {
current: STRING ← [100];
temp: STRING;
next: STRING ← [100];
i: CARDINAL;
current.length ← 0;
ent.length ← lnk.length ← com.length ← 0;
[] ← Directory.GetNext["*"L, current, next];
WHILE next.length#0 DO
-- copy string
FOR i IN [0..next.length) DO
ent[i] ← next[i];
ENDLOOP;
ent.length ← next.length;
-- call user procedure
IF p[ent, lnk, com] THEN RETURN;
-- advance to next name
temp ← current; current ← next; next ← temp;
[] ← Directory.GetNext["*"L, current, next];
ENDLOOP;
RETURN;
};
-- END HACK HACK

-- open directory and enumerate
d ← DirMan.Open[dir];
d.Enumerate[pattern, LOOPHOLE[p, DirMan.EProc] ! UNWIND => d.Close];
d.Close[];
}};

Error: PUBLIC SAFE SIGNAL[code: CIFS.ErrorCode, error: Rope.ROPE,
reply: CHARACTER ← Ascii.NUL] = CODE;

Expand: PUBLIC SAFE PROC [name: Rope.ROPE, c: Context]
RETURNS [path: Rope.ROPE] = TRUSTED {
-- Expands a name into a full path name
-- Can raise CIFS.Error[fileNotFound]
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
RETURN[InternalExpand[ConvertName[name], c].path];
}};

ExplicitBackup: PUBLIC SAFE PROC [name: Rope.ROPE, c: Context ← NIL]
RETURNS[version: INT] = TRUSTED {
-- Explict backup of a file
-- Returns 0 if the file is current on the server
path: Rope.ROPE ← Expand[name, c];
RETURN[FT.ExplicitBackup[path]];
};

GetBaseFreeSpace: PUBLIC SAFE PROC
RETURNS[pages: INT] = TRUSTED {
-- Gets the number of free pages that should be maintained
-- on the local disk
RETURN[FT.GetBaseFreeSpace[]];
};

GetSearchRules: PUBLIC SAFE PROC [c: Context ← NIL]
RETURNS [sr: LIST OF Rope.ROPE] = TRUSTED {
-- Returns the list of paths in the search rules
x: LIST OF REF SREntry;
IF c=NIL THEN c ← Default;
sr ← NIL;
FOR x ← c.searchRules, x.rest
UNTIL x=NIL DO
sr ← LOOPHOLE[List.Append[LOOPHOLE[sr], LIST[x.first.path]]];
ENDLOOP;
RETURN[sr];
};

GetWDir: PUBLIC SAFE PROC [c: Context ← NIL]
RETURNS [path: Rope.ROPE] = TRUSTED {
-- Return the path of the working directory
IF c=NIL THEN c ← Default;
RETURN[c.wdir.path];
};

GetFC: PUBLIC SAFE PROC [fh: OpenFile] RETURNS [fc: File.Capability] = TRUSTED
{RETURN [fh.fc]};

GetPathname: PUBLIC SAFE PROC [fh: OpenFile] RETURNS [path: Rope.ROPE] = TRUSTED
{RETURN [fh.name]};

importedHandleError: CIFSPrivate.HandleErrorProc;

HandleError: PUBLIC CIFSPrivate.HandleErrorProc = CHECKED {
RETURN[importedHandleError[code, error, reply]];
};

Login: PUBLIC SAFE PROC [name, password: Rope.ROPE] = TRUSTED {
-- Set credentials
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
FT.Login[name, password];
}};

Open: PUBLIC SAFE PROC [name: Rope.ROPE, mode: CIFS.Mode, c: Context]
RETURNS [fh: CIFS.OpenFile] = TRUSTED {
-- Open a file
-- Include default error handler
ENABLE Error => IF HandleError[code, error] THEN RESUME;
RETURN[OpenButDontHandleError[name, mode, c]];
};

OpenButDontHandleError: PUBLIC SAFE PROC [name: Rope.ROPE, mode: CIFS.Mode, c: Context]
RETURNS [fh: CIFS.OpenFile] = TRUSTED {
-- Open a file
-- Same as Open, except that it will not automatically handle
-- a credential error.
target, dir, entry: Rope.ROPE;
d: DirMan.Dir;
create: BOOLEANFALSE;
IF c=NIL THEN c ← Default;
name ← ConvertName[name];
-- See if we can establish a long name for the name the
-- user provided. If the name is not found, and we are prepared
-- to create the file, then set create to be TRUE
[target, name] ← InternalExpand[name, c
! Error => TRUSTED {
IF (code=CIFS.ErrorCode[noSuchFile]) AND
(Inline.BITAND[mode, CIFS.create+CIFS.replace]#0) THEN {
create ← TRUE;
CONTINUE}
ELSE REJECT
}];
-- if the file is opened in replace mode, ensure that we will not
-- overwrite the target of a link
IF Inline.BITAND[mode, CIFS.replace]#0 THEN {
-- file is to be replaced. If name points to a link, delete the
-- link so it won't be followed
-- (This is so Bringover and SModel will work properly)
-- First, include write with replace as a convenience to our users
mode ← Inline.BITOR[mode, CIFS.write];
-- Now see if name is a link
IF Rope.Compare[target, name, FALSE] # equal THEN {
-- name is a link. Get rid of it.
-- By setting create to be TRUE, we cause the dir entry
-- to be overwritten below by d.Insert, which erases the
-- link
create ← TRUE;
};
} ELSE name ← target;
-- if we have to create the file, then put an entry in the dir
-- system
-- create is never true for Com Soft Dir System files, becuase
-- Internal expand will always return "/local/foo" even if
-- foo does not exist.
IF create THEN {
-- file was not found in the directory system
-- expand name with simple mechanism
-- and put name in directory system
name ← MakeAbsolute[name, c];
[dir, entry] ← Parse[name];
d ← DirMan.Open[dir];
d.Insert[entry];
d.Close[];
};
RETURN[FT.Open[name, LOOPHOLE[mode]]];
};

Rename: PUBLIC SAFE PROC [from: Rope.ROPE, to: Rope.ROPE, c: Context] = TRUSTED {
-- Rename a file
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
fromDir, toDir, fromEntry, toEntry: Rope.ROPE;
dfrom, dto: DirMan.Dir;
IF c=NIL THEN c ← Default;
from ← ConvertName[from];
to ← ConvertName[to];
from ← Expand[from, c];
[fromDir, fromEntry] ← Parse[from];
to ← MakeAbsolute[to, c];
[toDir, toEntry] ← Parse[to];

-- HACK for com soft dir system
IF Rope.Compare[fromDir, "/local", FALSE] = equal THEN {
-- from is on the local disk
IF Rope.Compare[toDir, "/local", FALSE] # equal THEN
ERROR Error[CIFS.ErrorCode[noSuchDirectory],
Rope.Concat[to, " is not local. Can't perform rename."]];
{fromString: STRING ← [100];
toString: STRING ← [100];
fromString.length ← toString.length ← 0;
ConvertUnsafe.AppendRope[to: toString, from: toEntry
! Runtime.BoundsFault => CONTINUE];
ConvertUnsafe.AppendRope[to: fromString, from: fromEntry
! Runtime.BoundsFault => CONTINUE];
Directory.Rename[oldName: fromString, newName: toString
! Directory.Error => SELECT type FROM
Directory.ErrorType[fileNotFound] => ERROR
Error[CIFS.ErrorCode[noSuchFile], Rope.Concat[from, " not found."]];
Directory.ErrorType[fileAlreadyExists] => ERROR
Error[CIFS.ErrorCode[fileAlreadyExists], Rope.Concat[to, " already exists."]];
ENDCASE => REJECT;
];
RETURN;
}};

IF Rope.Compare[toDir, "/local", FALSE] = equal THEN
ERROR Error[CIFS.ErrorCode[noSuchDirectory],
Rope.Concat[to, " is local. Can't perform rename."]];
-- do the rename
-- make sure that to file does not exist
IF LookupEntry[toDir, toEntry].path#NIL THEN
ERROR Error[CIFS.ErrorCode[fileAlreadyExists],
Rope.Concat[to, " already exists."]];
-- first change the directory entries
dfrom ← DirMan.Open[fromDir];
dto ← DirMan.Open[toDir];
dfrom.Delete[fromEntry];
dto.Insert[toEntry];
dfrom.Close[];
dto.Close[];
-- now rename the remote files
FT.Rename[from, to];
}};

Reset: PUBLIC SAFE PROC[name: Rope.ROPE, c: Context] = TRUSTED {
-- Reset a file from its backing store
name ← ConvertName[name];
FT.Reset[name];
};

SetBaseFreeSpace: PUBLIC SAFE PROC[pages: INT ← 0] = TRUSTED {
-- Sets the number of free pages that should be maintained
-- on the local disk
-- If pages is omitted, CIFS will just ensure that there are
-- enough free pages according to the current setting
FT.SetBaseFreeSpace[pages];
};

SetComment: PUBLIC SAFE PROC [path, comment: Rope.ROPE, c: Context] = TRUSTED {
-- Sets the comment on the specified path to be comment
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
d: DirMan.Dir;
found: BOOLEANFALSE;
ent: REF TEXTNEW[TEXT[CIFS.maxPath]];
lnk: REF TEXTNEW[TEXT[CIFS.maxPath]];
dir, entry: Rope.ROPE;
ip: DirMan.EProc = {
found ← TRUE;
ent ← name; lnk ← link;
RETURN[TRUE];
};
IF c=NIL THEN c ← Default;
path ← ConvertName[path];
-- find out what directory the entry is in
path ← MakeAbsolute[path, c];
[dir, entry] ← Parse[path];
d ← DirMan.Open[dir];
-- get the old entry and link
d.Enumerate[entry, ip];
-- if failed, try with version wild card
IF NOT found THEN {
d.Enumerate[Rope.Concat[entry, "!*"], ip];
};
-- if the entry exists, delete the comment
IF NOT found THEN {
d.Close[];
Error[CIFS.ErrorCode[noSuchFile],
Rope.Concat[path, " not found."]];
};
-- now replace the old entry
entry ← Rope.FromRefText[ent];
d.Insert[
name: entry,
link: IF lnk.length=0 THEN NIL ELSE Rope.FromRefText[lnk],
comment: comment];
d.Close[];
}};

SetDefaultContext: PUBLIC SAFE PROC [c: Context] = CHECKED {
-- set the default context
Default ← c;
};

SetWDir: PUBLIC SAFE PROC [path: Rope.ROPE, c: Context ← NIL] = TRUSTED {
-- Set working directory
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
d: DirMan.Dir ← NIL;
IF c=NIL THEN c ← Default;
path ← ConvertName[path];
path ← MakeAbsolute[path, c];

-- HACK HACK
-- if path is /local don't open directory
IF Rope.Compare[path, "/local", FALSE] # equal THEN {
d ← DirMan.Open[path
! Error => CHECKED {CONTINUE}];
};

-- close old one
IF c.wdir.d#NIL THEN c.wdir.d.Close[];
-- set new one
c.wdir.d ← d;
c.wdir.path ← path;
}};

Swap: PUBLIC SAFE PROC [filea: Rope.ROPE, fileb: Rope.ROPE, c: Context] = TRUSTED {
-- Swap the contents of filea and fileb
-- Include default error handler
{ENABLE Error => IF HandleError[code, error] THEN RESUME;
IF c=NIL THEN c ← Default;
filea ← ConvertName[filea];
fileb ← ConvertName[fileb];
filea ← Expand[filea, c];
fileb ← Expand[fileb, c];
IF Rope.Compare[Parse[filea].dir, "/local", FALSE] = equal THEN
ERROR Error[CIFS.ErrorCode[noSuchDirectory],
Rope.Concat[filea, " is local. Can't perform swap."]];
IF Rope.Compare[Parse[fileb].dir, "/local", FALSE] = equal THEN
ERROR Error[CIFS.ErrorCode[noSuchDirectory],
Rope.Concat[fileb, " is local. Can't perform swap."]];
FT.Swap[filea, fileb];
}};


-- Procedures for CIFSPrivate

DefaultHandleError: CIFSPrivate.HandleErrorProc = CHECKED {
RETURN[FALSE];
};

SetHandleError: PUBLIC SAFE PROC [p: CIFSPrivate.HandleErrorProc] = CHECKED {
importedHandleError ← p;
};

-- Internal procedures

ConvertName: PROC [patha: Rope.ROPE]
RETURNS [pathb: Rope.ROPE] = {
-- converts old style file names to CIFS file style
i: INT ← 0;
length: INT;
-- most of the time, we don't need this
IF (patha=NIL) OR ((length←Rope.Size[patha])=0) OR (Rope.Fetch[patha, 0]#'[)
THEN RETURN[patha];
pathb ← patha;
WHILE i < length DO
SELECT Rope.Fetch[pathb, i] FROM
= '[, = '> => {pathb ← Rope.Cat[
Rope.Substr[pathb, 0, i],
"/",
Rope.Substr[pathb, i+1]]; -- skip [ or >
};
= '] => {pathb ← Rope.Cat[
Rope.Substr[pathb, 0, i],
"/",
Rope.Substr[pathb, i+2]]; -- skip ]>
length ← length - 1;
};
ENDCASE;
i ← i + 1;
ENDLOOP;
-- name is now converted.
RETURN[pathb];
};

CopyString: PROC [string: REF TEXT]
RETURNS [copy: REF TEXT] = {
-- copies string
i: CARDINAL;
copy ← NEW[TEXT[string.length]];
FOR i IN [0..string.length) DO
copy[i] ← string[i];
ENDLOOP;
copy.length ← string.length;
};

HandleBackslash: PROC [name: Rope.ROPE]
RETURNS [new: Rope.ROPE] = {
-- removes "\" from a path name by collapsing the name
length: LONG INTEGER ← Rope.Size[name];
newSlash, slash, i: LONG INTEGER ← 0;
FOR i IN [0..length) DO
SELECT Rope.Fetch[name, i] FROM
= '/ => newSlash ← i;
= '\\ => RETURN[HandleBackslash[
Rope.Concat[
Rope.Substr[name, 0, slash+1],
Rope.Substr[name, i+1]]]];
ENDCASE => slash ← newSlash;
ENDLOOP;
-- remove trailing /
RETURN[IF Rope.Fetch[name, length-1]='/ THEN
Rope.Substr[name, 0, length-1]
ELSE name];
};

InternalExpand: PROC [name: Rope.ROPE, c: Context]
RETURNS [path: Rope.ROPE, entryPath: Rope.ROPE] = {
-- Expands a name into a full path name
-- Also returns the path name of the entry before a link is followed
dir, entry: Rope.ROPE;
IF c=NIL THEN c ← Default;
[dir, entry] ← Parse[name];
IF dir#NIL THEN {
-- see if there is a version number
IF Rope.Index[entry, 0, "!"] < Rope.Size[entry] THEN {
-- there is a version number. don't look up the file
path ← entryPath ← MakeAbsolute[name, c];
RETURN[path, entryPath];
};
-- no version number
-- if the directory does not exist, then don't try to add one
{ENABLE Error => CHECKED {IF code=CIFS.ErrorCode[noSuchDirectory]
THEN GOTO BailOut ELSE REJECT};
dir ← MakeAbsolute[dir, c];
[path, entryPath] ← LookupEntry[dir, entry];
EXITS
BailOut => {path ← entryPath ← MakeAbsolute[name, c]};
};
} ELSE {
-- there is no directory. Lookup the name
[path, entryPath] ← LookupEntry[c.wdir.path, entry, c.wdir.d];
-- try search rules
FOR x: LIST OF REF SREntry ← c.searchRules, x.rest
UNTIL (x=NIL) OR (path#NIL) DO
[path, entryPath] ← LookupEntry[x.first.path, entry, x.first.d];
ENDLOOP;
};
-- not found.
IF path=NIL THEN
ERROR Error[CIFS.ErrorCode[noSuchFile], Rope.Concat[name, " not found."]];
RETURN[path, entryPath];
};

LookupEntry: PROC[dir: Rope.ROPE, entry: Rope.ROPE, sd: DirMan.Dir ← NIL]
RETURNS [path: Rope.ROPE, entryPath: Rope.ROPE] = {
-- if entry is in directory, return full path name
-- otherwise, return NIL
-- an opened version of dir can be supplied to improve performance
length: LONG INTEGER;
link: Rope.ROPE;
ent: REF TEXTNEW[TEXT[CIFS.maxPath]];
lnk: REF TEXTNEW[TEXT[CIFS.maxPath]];
d: DirMan.Dir;
max, maxLnk: REF READONLY TEXTNIL;
ip: DirMan.EProc = {
IF (max=NIL)
OR (Rope.Compare[Rope.FromRefText[max], Rope.FromRefText[name], FALSE] = less)
THEN {
max ← CopyString[name];
maxLnk ← CopyString[link];
};
RETURN[FALSE];
};

-- HACK HACK
-- if dir is /local then just return path name
-- this is for compatibility with common software dir system
IF Rope.Compare[dir, "/local", FALSE] = equal THEN {
path ← entryPath ← Rope.Concat[dir, Rope.Concat["/", entry]];
RETURN[path, entryPath];
};
-- END HACK HACK

{ENABLE Error => CHECKED {GOTO BailOut};
-- see if the client supplied an open directory
d ← IF sd#NIL THEN sd ELSE DirMan.Open[dir];
-- see if in the directory
d.Enumerate[entry, ip];
-- if not found, then try to look up entry with version number
length ← Rope.Size[entry];
IF (max=NIL) AND (Rope.Index[entry, 0, "!"] = length) AND
(Rope.Fetch[entry, length-1]#'/) THEN {
-- entry not found, no version number on entry, and entry
-- is not a directory
d.Enumerate[Rope.Concat[entry, "!*"], ip];
};
-- close directory if we opened it
IF sd=NIL THEN d.Close[];
-- if not found, return
IF max=NIL THEN RETURN[NIL, NIL];
-- now return the path name
link ← Rope.FromRefText[maxLnk];
entry ← Rope.FromRefText[max];
entryPath ← Rope.Concat[dir, Rope.Concat["/", entry]];
IF Rope.Equal[link, ""] THEN RETURN[entryPath, entryPath];
RETURN[link, entryPath];

EXITS
-- directory was not found. Assume that the file is there
BailOut => {entry ← Rope.Cat[dir, "/", entry]; RETURN[entry, entry];};
};
};

MakeAbsolute: PROC [name: Rope.ROPE, c: Context]
RETURNS [path: Rope.ROPE] = {
-- expands name to be a full path name by assuming it is relative
-- to the working
IF name=NIL THEN RETURN[c.wdir.path];
IF Rope.Fetch[name, 0]='/ THEN RETURN[HandleBackslash[name]];
RETURN[HandleBackslash[Rope.Concat[Rope.Concat[c.wdir.path, "/"], name]]];
};

Parse: PROC [path: Rope.ROPE]
RETURNS [dir, entry: Rope.ROPE] = {
-- splits a path name into a directory and an entry component
-- if path consists only of an entry name, dir will be NIL
i: LONG INTEGER;
dirLength: LONG INTEGER ← -1;
entryStart: LONG INTEGER ← 0;
-- don't treat trailing / as a directory delimiter
-- that is "/a/b/" is passed back as "/a" "b/"
FOR i IN [0..Rope.Size[path]-1) DO
SELECT Rope.Fetch[path, i] FROM
= '/ => {
dirLength ← i;
entryStart ← i + 1;
};
= '\\ => {
dirLength ← i + 1;
entryStart ← i + 1;
};
ENDCASE;
ENDLOOP;
dir ← IF dirLength < 1 THEN NIL ELSE Rope.Substr[path, 0, dirLength];
entry ← Rope.Substr[path, entryStart];
};

-- Checkpoint and Rollback
-- We assume that Checkpoint is done when:
-- (1) There are no enumerations in progress
-- (2) Anyone who uses a context other than the Default context
-- has destroyed it.
-- (3) Catalog is not running.

CheckpointP: CedarSnapshot.CheckpointProc = {
-- point the working directory at a harmless spot
global.wdir ← GetWDir[];
global.searchRules ← GetSearchRules[];
DeleteContext[];
};

RollbackP: CedarSnapshot.RollbackProc = {
-- recover where we were
searchRules: LIST OF Rope.ROPE ← global.searchRules;
SetWDir[global.wdir];
WHILE searchRules#NIL DO
AddSearchRule[path: searchRules.first, before: FALSE];
searchRules ← searchRules.rest;
ENDLOOP;
};

-- Initialization

Init: PROC = {
-- set up handle error proc
importedHandleError ← DefaultHandleError;
-- Set up procedures to handle checkpoint and rollback
global ← NEW[Global];
CedarSnapshot.Register[CheckpointP, RollbackP];
};

Init[];

}..