-- HackedAlpineBackupImpl.mesa -- Last Edited by -- MBrown on July 22, 1983 4:30 pm -- Kolling on January 4, 1984 3:21 pm -- Bob Hagmann October 5, 1984 10:19:56 am PDT DIRECTORY AlpineCmds USING[WheelGetCreateTime], AlpineFS USING[StreamOpen], AlpInstance USING[Create], AlpTransaction USING[AssertAlpineWheel, Create, Finish, Handle], BasicTime USING[GMT], Commander USING[CommandProc, Handle, Register], CommandTool USING[DoCommand], FS USING[Error, FileInfo, StreamOpen], FSRemoteFile USING[ConfirmProc, Store], GVBasics, GVSend, IO, Process, Rope, UserCredentials USING[Get], ViewerIO; HackedAlpineBackupImpl: CEDAR MONITOR IMPORTS AlpineCmds, AlpineFS, AlpI: AlpInstance, AlpT: AlpTransaction, Commander, CommandTool, FS, FSRemoteFile, GVSend, IO, Rope, UserCredentials, ViewerIO = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; typescriptStream: IO.STREAM _ NIL; PutLog: PROCEDURE[format: Rope.ROPE _ NIL, v1, v2, v3, v4, v5: IO.Value _ [null[]]] = BEGIN typescriptStream.PutF[format, v1, v2, v3, v4, v5]; typescriptStream.PutRope[" "]; typescriptStream.Flush[]; END; OpenLog: PROCEDURE = BEGIN typescriptStream _ ViewerIO.CreateViewerStreams["AlpineBackup.log"].out; END; CloseLog: PROCEDURE = BEGIN typescriptStream.Close[]; END; crockCmd: Commander.Handle _ NIL; AlpineBackupProc: Commander.CommandProc = BEGIN crockCmd _ cmd; DoAlpineBackup[NIL, NIL]; END; DoAlpineBackup: PROC [alpineFiles: ROPE, messagesTo: ROPE] = TRUSTED { IF alpineFiles.IsEmpty[] THEN alpineFiles _ "[indigo]Backup>AlpineFilesToBackup.txt^"; IF messagesTo.IsEmpty[] THEN messagesTo _ "Hagmann.pa, Donahue.pa"; DoBackup[alpineFiles, "Cyan", "AlpineBackup", messagesTo]; }; DoBackup: PROC [alpineFiles: ROPE, server, directory: ROPE, messagesTo: ROPE] = { -- alpineFiles is either "^", meaning contents of the named file, or -- " ... ", meaning itself. -- if a full path name is prefixed with '= then file is backed up even if create dates agree -- server is of form "Ivy" or "Cyan" -- directory is of form "AlpineBackup" -- messagesTo is of form "RWeaver.pa, Kolling.pa, MBrown.pa" in, err: STREAM; toList: LIST OF RName _ NIL; counts: ARRAY BackupFileInfo OF INT _ ALL[0]; name, pwd: ROPE; OpenLog[]; [name, pwd] _ UserCredentials.Get[]; err _ IO.ROS[]; err.PutF["Starting backup of Alpine files %g to [%g]<%g>\n", IO.rope[alpineFiles], IO.rope[server], IO.rope[directory]]; PutLog["Starting backup of Alpine files %g to [%g]<%g>", IO.rope[alpineFiles], IO.rope[server], IO.rope[directory]]; IF alpineFiles.Fetch[alpineFiles.Length[]-1] = '^ THEN { alpineFiles _ alpineFiles.Substr[len: alpineFiles.Length[]-1]; in _ FS.StreamOpen[alpineFiles]; } ELSE in _ IO.RIS[alpineFiles]; { -- Parse the messagesTo ROPE into a LIST OF RName. s: STREAM = IO.RIS[messagesTo]; to: RName; DO to _ s.GetTokenRope[IO.IDProc! IO.EndOfStream => EXIT].token; toList _ CONS[first: to, rest: toList]; ENDLOOP; s.Close[]; }; DO alpineFile: ROPE; info: BackupFileInfo; alpineFile _ in.GetTokenRope[IO.IDProc! IO.EndOfStream => EXIT].token; info _ BackupFile[alpineFile, server, directory, err]; IF info = alpineError THEN { -- retry once err.PutRope["Retrying ...\n"]; info _ BackupFile[alpineFile, server, directory, err]; }; counts[info] _ counts[info] + 1; ENDLOOP; { body: ROPE; body _ IO.PutFR["Summary:\n%g file(s) copied\n%g file(s) found unchanged\n%g file(s) not found\n%g error(s) in IFS operations\n%g error(s) in Alpine operations\n\nLog:\n\n", IO.int[counts[fileCopied]], IO.int[counts[fileUnchanged]], IO.int[counts[fileNotFound]], IO.int[counts[ifsError]], IO.int[counts[alpineError]]]; body _ body.Concat[IO.RopeFromROS[err]]; [] _ SendMessage[ from: "Alpine backup process", sender: name, senderPwd: pwd, returnTo: "Hagmann.pa", to: toList, cc: NIL, subject: IF (counts[ifsError] + counts[alpineError]) # 0 THEN "Ending backup (file server errors)" ELSE IF counts[fileNotFound] # 0 THEN "Ending backup (files not found)" ELSE "Ending backup", otherHeader: NIL, body: body, validate: FALSE, sendIfValidateFails: TRUE]; }; err.Close[]; in.Close[]; PutLog[format: "Ending backup of Alpine files"]; CloseLog[]; }; BackupFileInfo: TYPE = { fileNotFound, ifsError, alpineError, fileUnchanged, fileCopied }; BackupFile: PROC [alpineFile: ROPE, server, directory: ROPE, err: STREAM] RETURNS [BackupFileInfo] = { ifsServer: ROPE = Rope.Cat["[", server, "]"]; ifsDirectory: ROPE = Rope.Cat["<", directory, ">"]; fullName, longName, shortName: ROPE; errorCode: ATOM _ NIL; found: BOOL; alpineCreateTime, ifsCreateTime: BasicTime.GMT; copyOnlyIfCreateTimeChanged: BOOL _ TRUE; IF alpineFile.Fetch[0] = '= THEN { copyOnlyIfCreateTimeChanged _ FALSE; alpineFile _ alpineFile.Substr[1]; }; [longName, shortName] _ BackupFileName[alpineFile, ifsDirectory]; fullName _ Rope.Cat[ifsServer, longName]; BEGIN ENABLE ANY => GOTO alpineError; [found, alpineCreateTime] _ AlpineCmds.WheelGetCreateTime[ file: alpineFile, enableWheel: TRUE]; EXITS alpineError => { err.PutF["Alpine error in examining %g\n", IO.rope[alpineFile]]; PutLog["Alpine error in examining %g", IO.rope[alpineFile]]; RETURN [alpineError]; } END; IF NOT found THEN BEGIN err.PutF["Alpine file %g not found\n", IO.rope[alpineFile]]; PutLog["Alpine file %g not found", IO.rope[alpineFile]]; RETURN[fileNotFound]; END; BEGIN ifsCreateTime _ FS.FileInfo[name: Rope.Cat["[", server, "]", longName, "!h"], wantedCreatedTime: , remoteCheck: , wDir: ! FS.Error => BEGIN errorCode _ error.code; GOTO errorFound; END;].created; errorCode _ NIL; EXITS errorFound => NULL; END; IF ((errorCode = $serverInaccessible) OR (errorCode = $connectionRejected)) THEN BEGIN err.PutF["IFS error in examining %g on %g\n", IO.rope[longName], IO.rope[server]]; PutLog["IFS error in examining %g on %g", IO.rope[longName], IO.rope[server]]; RETURN[ifsError]; END; IF errorCode = NIL AND copyOnlyIfCreateTimeChanged AND alpineCreateTime = ifsCreateTime THEN BEGIN err.PutF["File found unchanged %g\n", IO.rope[alpineFile]]; PutLog["File found unchanged %g", IO.rope[alpineFile]]; RETURN[fileUnchanged]; END; BEGIN transHandle: AlpT.Handle _ NIL; BEGIN ENABLE ANY => GOTO alpineError; confirmProc: FSRemoteFile.ConfirmProc = BEGIN RETURN[TRUE]; END; alpineStream: STREAM; transHandle _ AlpT.Create[instHandle: AlpI.Create[fileStore: alpineFile.Substr[start: 1, len: alpineFile.SkipTo[1, "]"] - 1]], createLocalWorker: TRUE]; transHandle.AssertAlpineWheel[TRUE]; alpineStream _ AlpineFS.StreamOpen[name: alpineFile, accessOptions: $read, streamOptions: [tiogaRead: FALSE], keep: , createByteCount: , streamBufferParms: , extendFileProc: NIL, wDir: NIL, transHandle: transHandle]; FSRemoteFile.Store[server: server, file: longName, str: alpineStream, created: alpineCreateTime, proc: confirmProc ! FS.Error => GOTO ifsError]; alpineStream.Close[! ANY => CONTINUE]; [] _ transHandle.Finish[requestedOutcome: commit, continue: FALSE]; EXITS alpineError => BEGIN err.PutF["Alpine error in retrieving %g\n", IO.rope[alpineFile]]; PutLog["Alpine error in retrieving %g", IO.rope[alpineFile]]; IF transHandle # NIL THEN [] _ transHandle.Finish[requestedOutcome: commit, continue: FALSE]; RETURN [alpineError]; END; ifsError => BEGIN err.PutF["IFS error in storing %g on %g\n", IO.rope[longName], IO.rope[server]]; PutLog["IFS error in storing %g on %g", IO.rope[longName], IO.rope[server]]; IF transHandle # NIL THEN [] _ transHandle.Finish[requestedOutcome: commit, continue: FALSE]; RETURN [ifsError]; END; END; END; DelVerAndSetNoBackup[server, directory, longName]; err.PutF["File transferred okay %g\n", IO.rope[alpineFile]]; PutLog["File transferred okay %g", IO.rope[alpineFile]]; RETURN [fileCopied] }; BackupFileName: PROC [alpineFile, ifsDirectory: ROPE] RETURNS [longName, shortName: ROPE] = { -- Transforms "[Luther.alpine]Walnut.DBLog", "" -- into "Luther>MBrown.pa>Walnut.DBLog", "Walnut.DBLog". IF NOT Rope.Match[pattern: "[*.alpine]<*>*", object: alpineFile, case: FALSE] THEN ERROR; { -- derive longName from alpineFile and ifsDirectory longName _ alpineFile.Replace[ start: alpineFile.Find[s2: ".alpine]<", case: FALSE], len: 9, with: ">"]; longName _ longName.Replace[start: 0, len: 1, with: ifsDirectory]; }; { -- derive shortName from longName pos1: INT _ 0; pos2: INT; UNTIL (pos2 _ longName.Find[s2: ">", pos1: pos1+1]) = -1 DO pos1 _ pos2; ENDLOOP; shortName _ longName.Substr[start: pos1+1]; }; RETURN [longName, shortName]; }; DelVerAndSetNoBackup: PROC [server, directory, longName: ROPE] = { -- server is like "Ivy" -- directory is like "AlpineBackup" -- longName is like "Luther>MBrown.pa>Walnut.DBLog" ksFile: STREAM = FS.StreamOpen["Backup.ks", $create]; ksFile.PutRope[Rope.Cat[ "Connect ", directory, "\n", "Delete ", longName, ",\nKeep 2\nConfirm\n\n"]]; ksFile.PutRope[Rope.Cat[ "Change Attributes ", longName, "\nNo Backup\n\nQuit\n"]]; ksFile.Close[]; [] _ CommandTool.DoCommand[Rope.Cat["Chat ", server, " -l >Backup.ChatLog IF notDelivered THEN LOOP ELSE EXIT; SELECT h.StartSend[senderPwd, sender, returnTo, validate] FROM ok => state _ $ok; badPwd => { state _ $badPwd; EXIT }; badSender => { state _ $badSender; EXIT }; badReturnTo => { state _ $badReturnTo; EXIT }; allDown => { state _ $allDown; EXIT }; ENDCASE => ERROR; AddRecipients[h, to]; AddRecipients[h, cc]; IF validate THEN { IF h.CheckValidity[NoticeInvalidRecipient] = 0 THEN state _ $noValidRecipients; IF NOT sendIfValidateFails AND state # $ok THEN { h.Abort[]; aborted _ TRUE; EXIT; }; }; h.StartItem[Text]; h.AddToItem[ConsMessage[sender, subject, from, to, cc, otherHeader, body]]; h.Send[]; EXIT; ENDLOOP; RETURN [state IN [$ok .. $noValidRecipients] AND NOT aborted, state] }; AddRecipients: PROC [h: GVSend.Handle, recipients: LIST OF RName] = { FOR r: LIST OF RName _ recipients, r.rest UNTIL r = NIL DO h.AddRecipient[recipient: r.first]; ENDLOOP; }; ConsMessage: PROC [ sender: RName, subject, from: ROPE, to, cc: LIST OF RName, otherHeader, body: ROPE] RETURNS [ROPE] = { dateRope: ROPE = ConsField["Date", IO.PutFR["%g", IO.time[]]]; senderRope: ROPE = ConsField["Sender", sender]; subjectRope: ROPE = ConsField["Subject", subject]; fromRope: ROPE = ConsField["From", from]; toRope: ROPE = ConsListField["To", to]; ccRope: ROPE = ConsListField["cc", cc]; header: ROPE = Rope.Cat[dateRope, senderRope, subjectRope, fromRope, toRope, ccRope]; RETURN[Rope.Cat[header, otherHeader, "\n", body]]; }; ConsField: PROC [fieldName, fieldContents: ROPE] RETURNS [ROPE] = { RETURN [IF fieldContents.IsEmpty[] THEN NIL ELSE IO.PutFR["%g: %g\n", IO.rope[fieldName], IO.rope[fieldContents]]]; }; ConsListField: PROC [fieldName: ROPE, fieldContents: LIST OF ROPE] RETURNS [ROPE]= { result: ROPE _ NIL; IF fieldContents # NIL THEN { result _ fieldName.Concat[": "]; DO result _ result.Concat[fieldContents.first]; fieldContents _ fieldContents.rest; IF fieldContents = NIL THEN EXIT; result _ result.Concat[", "]; ENDLOOP; result _ result.Concat["\n"]; }; RETURN [result.Flatten[]]; }; Commander.Register[key: "DoAlpineBackup", proc: AlpineBackupProc, doc: NIL, clientData: ]; END. CHANGE LOG Created by MBrown on May 28, 1983 9:20 pm Changed by MBrown on July 8, 1983 11:53 am -- Added code for server operation (periodic wakeup, grapevine message sending.) Changed by MBrown on July 19, 1983 8:35 am -- Added code for triggering backup from MiscServerTool Changed by MBrown on July 22, 1983 4:30 pm -- Added code for passing "alpineFiles" as a value as well as a file reference; added --"messagesTo" button to aid in debugging. Changed by Bob Hagmann on June 1, 1984 7:47:40 am PDT -- Changed keep from 1 to 2 so that backup will be more meaningful. Changed by Bob Hagmann on October 5, 1984 10:20:14 am PDT -- Moved backup to Cyan from Ivy, and added Donahue to message list. Êb˜Jš–Ïc¯œÏk œžœ*žœ%žœ$žœBžœžœ8žœžœ7žœ4žœ4žœÐblœžœžœžœwžœ+žœžœžœžœžœžœžœžœžœžœžœ Ïnœž œžœžœžœžœžœ œž œžœTžœ œž œžœ%žœ  œžœ œžœ&žœžœžœ œžœžœžœžœžœžœLžœžœq œžœžœžœžœ UœMœ]œÐci Ïi¡œ'œ=œ žœžœžœ žœžœžœžœžœžœDžœžœKžœžœžœPžœžœžœžœ0žœ{žœžœžœ3œ žœžœžœ%žœžœ Ïrœžœžœ#žœ žœžœAžœ £œžœLžœžœ œžžœžœ žœ­žœžœžœ$žœžœ3žœ§žœžœ.žœ8žœžœžœ5žœ%žœ$žœžœŠžœN  œžœžœžœžœžœ%žœ1žœEžœžœžœ žœWžœžœžœžœ'žœ¬žœžœžœžœyž œCžœAžœžœžœžœžœžœžœ.žœ=žœžœžœžœ›žœ žœ žœ žœžœžœžœžœ žœžœž œ$žœ$žœžœ=žœžœ@žœžœžœžœžœ žœžœ#žœ"žœžœ-žœ<žœžœžœžœžœžœžœ žœ,žœžœžœžœžœŸžœ%žœtžœJžœžœžœcž œ(žœ%žœžœžœ+žœž œžœ?žœNžœžœžœžœžœ+žœžœž!œ žœGžœžœJžœžœžœžœžœžœ+žœžœž$œxžœ;žœžœ œžœžœžœžœ IœGœžœžœAžœžœžœ4œ[žœo"œ žœžœžœ4žœžœAžœ" œžœžœ œ$œBœ žœžœ'žœ_  œžœžœžœžœžœžœžœžœ%žœžœžœ! œžœ žœžœžœžœžœžœžœžœžœžœžœ4žœBžœ0žœ4žœ+žœ žœžœ@žœ žœ žœ-žœ%žœžœžœ žœ#žœžœ›žœžœ žœžœžœžœ  œžœ žœžœžœžœžœžœžœžœ1žœ   œžœ%žœ žœžœžœžœžœžœžœ žœžœ1žœ0žœ(žœ(žœ(žœNžœ7  œžœžœžœžœ žœžœžœžœžœžœžœžœ"  œžœ žœžœžœžœžœžœžœžœžœžœžœ0žœjžœžœžœžœ0žœ3žœžœGžœžœžœžœXQœ,8œ,‚œ6Dœ;E˜ýo—…—8>h