-- 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: BOOLEANFALSE;
needBusyHerald: BOOLEANTRUE;
ropeName, wdir: Rope.ROPE;
searchRules: LIST OF Rope.ROPENIL;
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.ROPENIL;
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: INTCIFS.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: BOOLEANFALSE;
-- 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[];

}.