-- CIFSCommands.mesa -- Commands for the Cedar Interim File System -- August 18, 1982 8:51 am by Levin -- October 5, 1982 1:26 pm DIRECTORY CIFS, CIFSPrivate: TYPE USING [SetHandleError, HandleErrorProc], Convert: TYPE USING [IntFromRope], ConvertUnsafe: TYPE USING [ToRope], IO: TYPE USING [PutF, rope, string, text, time, int, Handle, DeliverWhenProc, SP, CR, CreateEditedStream, char, SetEcho, Put, Close, Signal, STREAM, GetToken, IDProc], LSD: TYPE USING [Entry, GetDirty, Enumerate, EnumerateLocked, Unlock, UnlockAll, EProc], Rope: TYPE USING [ROPE, Index, Substr, Size, Fetch, Cat, Compare], UECP: TYPE USING [Parse, Argv], UserExec: TYPE USING [RegisterCommand, UserAbort, ResetUserAbort, CommandProc, GetNameAndPassword, SetNameAndPassword, GetStreams], ViewerIO: TYPE USING [CreateViewerStreams]; CIFSCommands: MONITOR IMPORTS CIFS, CIFSPrivate, Convert, ConvertUnsafe, IO, LSD, Rope, UECP, UserExec, ViewerIO = { -- Directory delimiter dirDelim: CHARACTER = '/; dirD: Rope.ROPE = "/"; -- Add Search Rule command asrC: Rope.ROPE = "Add Search Rule Usage: asr path The path specified will be added to the beginning of the search rules"; ASR: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc=1 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; CIFS.AddSearchRule[argv[1], TRUE]; }}; -- Create Directory command cdC: Rope.ROPE = "Make Directory Usage: mkdir path The diretory path will be created"; CD: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc=1 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; CIFS.CreateDir[argv[1]]; }}; -- Cwd command cwdC: Rope.ROPE = "Change working directory usage: cd path The working directory will be chagned to path. If path is not specified, /local is assumed"; CWD: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; CIFS.SetWDir[IF argv.argc=1 THEN "/local" ELSE argv[1]]; }}; -- Backup command backupC: Rope.ROPE = "Backup usage: backup Dirty files on the local machine will be copied to their remote homes"; Backup: UserExec.CommandProc = TRUSTED { -- Enumerates all of the dirty files in the LSD and dumps them -- Code for dealing with exceptional conditions maxErr: CARDINAL = 50; ErrorList: TYPE = RECORD [ length: CARDINAL _ 0, list: SEQUENCE maxSize: [1..LAST[CARDINAL]] OF LSD.Entry ]; errorList: REF ErrorList _ NEW[ErrorList[maxErr]]; PushError: PROC [entry: LSD.Entry] = { -- There has been an error with a file IF errorList.length >= maxErr THEN RETURN; errorList.length _ errorList.length + 1; errorList[errorList.length] _ entry; }; PopError: PROC RETURNS [entry: LSD.Entry] = { IF errorList.length=0 THEN RETURN[NIL]; entry _ errorList[errorList.length]; errorList.length _ errorList.length - 1; }; proc: LSD.EProc = { -- called for each locked file IF needBusyHerald THEN { exec.GetStreams[].out.PutF["\n\nThe following files are locked and were not examined:\n\n"]; needBusyHerald _ FALSE; }; exec.GetStreams[].out.PutF["%g\n", IO.string[@entry.name]]; RETURN[UserExec.UserAbort[exec]]; }; -- Backup main body aborted: BOOLEAN _ FALSE; needBusyHerald: BOOLEAN _ TRUE; ropeName, wdir: Rope.ROPE; searchRules: LIST OF Rope.ROPE _ NIL; entry: LSD.Entry; version: INT; exec.GetStreams[].out.PutF["\nBackup: Version of July 1, 1982 5:13 pm\n\n"]; -- first, point working directory at a harmless spot wdir _ CIFS.GetWDir[]; searchRules _ CIFS.GetSearchRules[]; CIFS.DeleteContext[]; -- Enumerate the dirty files and store them. -- If there is an error, we push the entry on a stack. -- After we have processed all other entries, we -- unlock these problem entries. FOR entry _ LSD.GetDirty[], LSD.GetDirty[] UNTIL entry=NIL DO -- Have a file to move back -- If an error occurs, skip the file {ENABLE CIFS.Error => { IF CIFS.HandleError[code, error] THEN RESUME; exec.GetStreams[].out.PutF[" not stored. Error: %g\n", IO.rope[error]]; PushError[entry]; CONTINUE; }; -- if we get an abort, quit IF UserExec.UserAbort[exec] THEN { aborted _ TRUE; EXIT; }; -- Get the file name from the entry ropeName _ ConvertUnsafe.ToRope[@entry.name]; exec.GetStreams[].out.PutF["%g", IO.rope[ropeName]]; LSD.Unlock[entry]; version _ CIFS.ExplicitBackup[ropeName]; SELECT version FROM > 0 => { -- local copy more recent exec.GetStreams[].out.PutF[" stored as version %g\n", IO.int[version]]; }; = 0 => exec.GetStreams[].out.PutF[" not stored. Remote copy current.\n"]; < 0 => exec.GetStreams[].out.PutF[" not stored. Remote copy more recent.\n"]; ENDCASE; }; ENDLOOP; -- Have stored all unlocked files. -- Now unlock the problem entries FOR entry _ PopError[], PopError[] UNTIL entry=NIL DO LSD.Unlock[entry]; ENDLOOP; -- Now tell the user what files have not been stored. IF NOT aborted THEN LSD.EnumerateLocked[proc]; -- Now reset context back to what it was CIFS.SetWDir[wdir]; WHILE searchRules#NIL DO CIFS.AddSearchRule[path: searchRules.first, before: FALSE]; searchRules _ searchRules.rest; ENDLOOP; UserExec.ResetUserAbort[exec]; }; -- Comment Command commentC: Rope.ROPE = "Comment usage: comment path \"comment string\" Sets the comment on path to the supplied string"; Comment: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc < 2 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; CIFS.SetComment[argv[1], IF argv.argc#3 THEN NIL ELSE argv[2]]; }}; -- Delete Command deleteC: Rope.ROPE = "Delete usage: CIFSDelete path1 ... pathn Deletes the list of files supplied"; Delete: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; FOR i: INT IN [1..argv.argc) DO CIFS.Delete[argv[i]]; IF UserExec.UserAbort[exec] THEN EXIT; ENDLOOP; UserExec.ResetUserAbort[exec]; }}; -- Fetch Command. fetchC: Rope.ROPE = "Fetch usage: fetch path Fetches the file specified to the local disk"; Fetch: UserExec.CommandProc = TRUSTED { of: CIFS.OpenFile; argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc=1 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; of _ CIFS.Open[argv[1], CIFS.read]; CIFS.Close[of]; }}; -- Link Command linkC: Rope.ROPE = "Link usage: Link linkpath targetpath Causes linkpath to point at targetpath"; Link: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc#3 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; CIFS.CreateLink[argv[1], argv[2]]; }}; -- LS Command. lsC: Rope.ROPE = "List Directory Usage: ls [-pat pattern] [-dir path] [pattern] ls lists the contents of a directory. The directory can be specified with -dir. The pattern can be specified with -pat, and can include * and #"; LS: UserExec.CommandProc = TRUSTED { pattern: Rope.ROPE _ "*"; directory: Rope.ROPE _ NIL; i: INT _ 1; argv: UECP.Argv _ UECP.Parse[event.commandLine]; -- pick arguments off command line WHILE i < argv.argc DO -- check for switches SELECT TRUE FROM Rope.Compare[argv[i], "-dir", FALSE]=equal => { i _ i + 1; IF i < argv.argc THEN directory _ argv[i]; }; Rope.Compare[argv[i], "-pat", FALSE]=equal => { i _ i + 1; IF i < argv.argc THEN pattern _ argv[i]; }; ENDCASE => pattern _ argv[i]; -- go to next token i _ i + 1; ENDLOOP; -- okay, print out the results {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; p: CIFS.EProc = CHECKED { exec.GetStreams[].out.PutF["%g", IO.text[name]]; IF link.length#0 THEN { exec.GetStreams[].out.PutF[" %g", IO.text[link]]; }; IF comment.length#0 THEN { exec.GetStreams[].out.PutF["\n %g", IO.text[comment]]; }; exec.GetStreams[].out.PutF["\n"]; RETURN[UserExec.UserAbort[exec]]; }; CIFS.Enumerate[dir: directory, pattern: pattern, p: p]; UserExec.ResetUserAbort[exec]; }}; -- lsd Command. lsdC: Rope.ROPE = "List LSD (Local System Directory) Usage: lsd The contents of the LSD will be output in the following form: path (create date) [{maybe dirty}] The dirty indicator will be output if path is possibly dirty or busy"; CLSD: UserExec.CommandProc = TRUSTED { -- list the contents of the lsd dString: Rope.ROPE = "{maybe dirty}"; tString: Rope.ROPE = "**date not known**"; cString: Rope.ROPE = ""; proc: LSD.EProc = { -- called for each file exec.GetStreams[].out.PutF["%g (%g) %g\n", IO.string[@entry.name], IF entry.create = 0 THEN IO.rope[tString] ELSE IO.time[LOOPHOLE[entry.create]], IO.rope[IF entry.dirtyF THEN dString ELSE cString]]; RETURN[UserExec.UserAbort[exec]]; }; exec.GetStreams[].out.PutF["\nThe LSD contains:\n\n"]; LSD.Enumerate[proc]; UserExec.ResetUserAbort[exec]; }; -- Pwd Command pwdC: Rope.ROPE = "Print Working Directory Usage: pwd Prints the working directory"; PWD: UserExec.CommandProc = TRUSTED { exec.GetStreams[].out.PutF["%g", IO.rope[CIFS.GetWDir[]]]; }; -- Rename Command renameC: Rope.ROPE = "Rename Usage: CIFSRename frompath topath frompath will be renamed to topath"; Rename: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc#3 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; CIFS.Rename[from: argv[1], to: argv[2]]; }}; -- Reset Command resetC: Rope.ROPE = "Reset Command Usage: reset abspath Erase ONLY the local copy of the file specified. The name specified must be absolute"; Reset: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc=1 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; CIFS.Reset[argv[1]]; }}; -- Set Base Free Space sbfC: Rope.ROPE = "Set Base Free Space Usage: sbf pages Set number of pages that should be kept free at all times. If pages is a large number, then CIFS will not cache files on the local disk."; SetBaseFree: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc=1 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; CIFS.SetBaseFreeSpace[Convert.IntFromRope[argv[1]]]; }}; -- Swap Command swapC: Rope.ROPE = "Swap files Usage: swap patha pathb Swaps the contents of patha and pathb"; Swap: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc#3 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; CIFS.Swap[argv[1], argv[2]]; }}; -- Delete Directory Command ddC: Rope.ROPE = "Delete Directory Usage: dd path Deletes the directory named path. The directory must be empty."; DD: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc#2 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; CIFS.DeleteDir[argv[1]]; }}; -- Delete Search Rule Command dsrC: Rope.ROPE = "Delete Search Rule Usage: dsr path Removes the path specifed from the search rules"; DSR: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc=1 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; CIFS.DeleteSearchRule[argv[1]]; }}; -- List Search Rules Command lsrC: Rope.ROPE = "List Search Rules Usage: lsr Lists the search rules"; LSR: UserExec.CommandProc = TRUSTED { x: LIST OF Rope.ROPE; FOR x _ CIFS.GetSearchRules[], x.rest UNTIL x=NIL DO exec.GetStreams[].out.PutF["%g\n", IO.rope[x.first]]; ENDLOOP; }; -- Unlock command unlockC: Rope.ROPE = "Unlock Usage: unlock Unlock all files, regarless of anything. The sledge-hammer for the frustrated user. (Requested by L. Stewart)"; CUnlock: UserExec.CommandProc = TRUSTED { CIFS.DeleteContext[]; LSD.UnlockAll[]; }; -- Where Command whereC: Rope.ROPE = "Where Usage: where path Shows the full path name of path"; Where: UserExec.CommandProc = TRUSTED { argv: UECP.Argv _ UECP.Parse[event.commandLine]; IF argv.argc=1 THEN RETURN; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; exec.GetStreams[].out.PutF["%g", IO.rope[CIFS.Expand[argv[1]]]]; }}; -- Print Base Free Space pbfC: Rope.ROPE = "Print Base Free Space Usage: pbf Prints the number of pages that CIFS will try to keep free on the local disk. The value can be changed with sbf."; PrintBaseFree: UserExec.CommandProc = TRUSTED { fs: INT _ CIFS.GetBaseFreeSpace[]; {ENABLE CIFS.Error => { exec.GetStreams[].out.PutF["%g\n", IO.rope[error]]; CONTINUE; }; exec.GetStreams[].out.PutF["%g pages.\n", IO.int[fs]]; }}; -- Internal Procedures BreakName: PROC[name: Rope.ROPE] RETURNS[server, file: Rope.ROPE] = { -- returns a server name and file name from a path name fs: LONG INTEGER _ Rope.Index[name, 1, dirD]; IF ((fs+1)>=Rope.Size[name]) OR (Rope.Fetch[name, 0]#dirDelim) THEN ERROR CIFS.Error[ CIFS.ErrorCode[illegalFileName], Rope.Cat[name, " is an illegal name"]] ELSE { server _ Rope.Substr[name, 1, fs-1]; file _ Rope.Substr[name, fs + 1, Rope.Size[name]-fs-1]}; }; -- this is the error handling procedure that gets plugged into CIFS OnSPorCR: IO.DeliverWhenProc = TRUSTED { RETURN[(char=IO.SP) OR (char=IO.CR)]; }; HandleError: CIFSPrivate.HandleErrorProc = TRUSTED { -- Attempts to handle a credential error. The usage of this procedure is: -- CIFS.Operation[ args ! -- CIFS.Error => {IF CIFS.HandleError[code, error, reply] THEN RESUME}]; oldEcho, in, out: IO.Handle; name, password: Rope.ROPE; connect: BOOLEAN _ FALSE; -- handle credential errors IF NOT (code IN CIFS.CredentialsErrors) THEN RETURN[FALSE]; -- okay, here goes -- see if we need connect credentials or not connect _ (code=CIFS.ErrorCode[illegalConnectName]) OR (code=CIFS.ErrorCode[illegalConnectPassword]) OR (code=CIFS.ErrorCode[accessDenied]); -- Shazam! A new window! [in, out] _ ViewerIO.CreateViewerStreams["Credentials Please"]; in _ IO.CreateEditedStream[in, out, OnSPorCR]; -- if user hits DEL, then don't resume (give up) {ENABLE IO.Signal => TRUSTED {GOTO BailOut}; resume _ TRUE; out.PutF["CIFS Error: %s\n", IO.rope[error]]; out.PutF["Hit DEL to abort request or enter name and password\n"]; IF connect THEN out.PutF["Connect "] ELSE out.PutF["User "]; out.PutF["Name: "]; name _ in.GetToken[IO.IDProc]; out.Put[IO.char[IO.CR], IO.rope["Password: "]]; oldEcho _ in.SetEcho[NIL]; password _ in.GetToken[IO.IDProc]; [] _ in.SetEcho[oldEcho]; out.Put[IO.char[IO.CR], IO.rope["Thanks!"]]; -- set credentials IF connect THEN CIFS.Connect[name, password] ELSE { -- set login credentials UserExec.SetNameAndPassword[name, password]; CIFS.Login[name, password]; }; EXITS BailOut => resume _ FALSE; }; in.Close[]; out.Close[]; RETURN[resume]; }; Init: PROC = { name, password: Rope.ROPE; -- set up name and password [name, password] _ UserExec.GetNameAndPassword[]; CIFS.Login[name, password]; -- register commands UserExec.RegisterCommand["Backup.~", Backup, "Backup Dirty Files", backupC]; UserExec.RegisterCommand["Fetch.~", Fetch, "Fetch File to Local Disk", fetchC]; UserExec.RegisterCommand["CIFSRename.~", Rename, "Rename file", renameC]; UserExec.RegisterCommand["CIFSDelete.~", Delete, "Delete File", deleteC]; UserExec.RegisterCommand["LSD.~",CLSD, "List Local System Directory", lsdC]; UserExec.RegisterCommand["Swap.~", Swap, "Swap File Contents", swapC]; UserExec.RegisterCommand["Reset.~", Reset, "Reset Local Copy", resetC]; UserExec.RegisterCommand["Ls.~", LS, "List Directory", lsC]; UserExec.RegisterCommand["Cwd.~", CWD, "Change Working Directory", cwdC]; UserExec.RegisterCommand["Cd.~", CWD, "Change Working Directory", cwdC]; UserExec.RegisterCommand["Pwd.~", PWD, "Print Working Directory", pwdC]; UserExec.RegisterCommand["Lsr.~", LSR, "List Search Rules", lsrC]; UserExec.RegisterCommand["Asr.~", ASR, "Add Search Rule", asrC]; UserExec.RegisterCommand["Dd.~", DD, "Delete Directory", ddC]; UserExec.RegisterCommand["MkDir.~", CD, "Make Directory", cdC]; UserExec.RegisterCommand["Dsr.~", DSR, "Delete Search Rule", dsrC]; UserExec.RegisterCommand["Where.~", Where, "Where Is File", whereC]; UserExec.RegisterCommand["Link.~", Link, "Create Link", linkC]; UserExec.RegisterCommand["Comment.~", Comment, "Set Comment", commentC]; UserExec.RegisterCommand["Unlock.~", CUnlock, "Unlock All Files", unlockC]; UserExec.RegisterCommand["Sbf.~", SetBaseFree, "Set Base Number of Free Local Disk Pages", sbfC]; UserExec.RegisterCommand["Pbf.~", PrintBaseFree, "Print Base Free Pages", pbfC]; -- bind our handle error proc to the low level system CIFSPrivate.SetHandleError[HandleError]; }; -- Start trap intializes Init[]; }.