-- 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.ROPE _ NIL, searchRules: LIST OF Rope.ROPE _ NIL ]; 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: BOOLEAN _ TRUE; 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: BOOLEAN _ FALSE; 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: BOOLEAN _ FALSE; 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 TEXT _ NEW[TEXT[CIFS.maxPath]]; lnk: REF TEXT _ NEW[TEXT[CIFS.maxPath]]; com: REF TEXT _ NEW[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: BOOLEAN _ FALSE; 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: BOOLEAN _ FALSE; ent: REF TEXT _ NEW[TEXT[CIFS.maxPath]]; lnk: REF TEXT _ NEW[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 TEXT _ NEW[TEXT[CIFS.maxPath]]; lnk: REF TEXT _ NEW[TEXT[CIFS.maxPath]]; d: DirMan.Dir; max, maxLnk: REF READONLY TEXT _ NIL; 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[]; }..