-- AlpineInterimBackupImpl.mesa -- Last Edited by -- MBrown on July 22, 1983 4:30 pm -- Kolling on July 29, 1983 4:57 pm DIRECTORY AlpineCmds, CIFS, FileIO, FileLookup, FtpMan, GVBasics, GVSend, IO, MiscServerAdmin, MiscServerRemote, Process, Rope, System, Time, UserCredentials, UserExec; AlpineInterimBackupImpl: CEDAR MONITOR IMPORTS AlpineCmds, CIFS, FileIO, FileLookup, FtpMan, GVSend, IO, MiscServerAdmin, Process, Rope, System, Time, UserCredentials, UserExec = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; BackupProcess: PROC [when: [0..24)] = TRUSTED { lastBackupDay: [0 .. 31] ← 0; unpacked: Time.Unpacked; DO unpacked ← Time.Unpack[System.GetGreenwichMeanTime[]]; IF unpacked.hour = when AND unpacked.day # lastBackupDay THEN { [] ← ForkAlpineBackup[alpineFiles: NIL, messagesTo: NIL]; lastBackupDay ← unpacked.day; }; Process.Pause[Process.SecondsToTicks[60]]; ENDLOOP; }; StartAlpineBackup: PROC [ command: MiscServerRemote.CommandRec, data: REF ANY] RETURNS [ROPE] = { RETURN [ForkAlpineBackup[ alpineFiles: command.arg.first, messagesTo: command.arg.rest.first]]; }; backupInProgress: BOOL ← FALSE; StartBackup: ENTRY PROC [] RETURNS [alreadyInProgress: BOOL] = { alreadyInProgress ← backupInProgress; backupInProgress ← TRUE; RETURN [alreadyInProgress]; }; EndBackup: ENTRY PROC [] = { backupInProgress ← FALSE; }; ForkAlpineBackup: PROC [alpineFiles: ROPE, messagesTo: ROPE] RETURNS [ROPE] = TRUSTED { IF alpineFiles.IsEmpty[] THEN alpineFiles ← "[indigo]<Alpine>Backup>AlpineFilesToBackup.txt↑"; IF messagesTo.IsEmpty[] THEN messagesTo ← "Kolling.pa"; IF StartBackup[].alreadyInProgress THEN RETURN ["Backup already in progress!"]; Process.Detach[FORK DoBackup[alpineFiles, "Ivy", "AlpineBackup", messagesTo]]; RETURN [NIL]; }; DoBackup: PROC [alpineFiles: ROPE, server, directory: ROPE, messagesTo: ROPE] = { -- alpineFiles is either "<full path name>↑", meaning contents of the named file, or -- "<full path name> <full path name> ... <full path name>", 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" -- 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; [name, pwd] ← UserCredentials.GetUserCredentials[]; err ← IO.ROS[]; err.PutF["Starting backup of Alpine files %g to [%g]<%g>\n", IO.rope[alpineFiles], IO.rope[server], IO.rope[directory]]; MiscServerAdmin.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]; CIFS.Reset[alpineFiles ! CIFS.Error => IF code = noSuchFile THEN CONTINUE]; in ← FileIO.Open[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.GetToken[IO.IDProc]; IF to.IsEmpty[] THEN EXIT; toList ← CONS[first: to, rest: toList]; ENDLOOP; s.Close[]; }; DO alpineFile: ROPE; info: BackupFileInfo; alpineFile ← in.GetToken[IO.IDProc]; IF alpineFile.IsEmpty[] THEN EXIT; 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.GetOutputStreamRope[err]]; [] ← SendMessage[ from: "Alpine backup process", sender: name, senderPwd: pwd, returnTo: "MBrown.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[]; MiscServerAdmin.PutLog[format: "Ending backup of Alpine files"]; EndBackup[]; }; 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; result: FileLookup.Result; found: BOOL; alpineCreateTime, ifsCreateTime: System.GreenwichMeanTime; copyOnlyIfCreateTimeChanged: BOOL ← TRUE; IF alpineFile.Fetch[0] = '= THEN { copyOnlyIfCreateTimeChanged ← FALSE; alpineFile ← alpineFile.Substr[1]; }; [longName, shortName] ← BackupFileName[alpineFile, ifsDirectory]; fullName ← Rope.Cat[ifsServer, longName]; { 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]]; MiscServerAdmin.PutLog["Alpine error in examining %g", IO.rope[alpineFile]]; RETURN [alpineError]; } }; IF NOT found THEN { err.PutF["Alpine file %g not found\n", IO.rope[alpineFile]]; MiscServerAdmin.PutLog["Alpine file %g not found", IO.rope[alpineFile]]; RETURN[fileNotFound]; }; [result: result, create: ifsCreateTime] ← FileLookup.LookupFile[ server: server, file: Rope.Cat[longName, "!h"]]; IF result = noResponse OR result = noSuchPort THEN { err.PutF["IFS error in examining %g on %g\n", IO.rope[longName], IO.rope[server]]; MiscServerAdmin.PutLog["IFS error in examining %g on %g", IO.rope[longName], IO.rope[server]]; RETURN[ifsError]; }; IF result = ok AND copyOnlyIfCreateTimeChanged AND alpineCreateTime = ifsCreateTime THEN { err.PutF["File found unchanged %g\n", IO.rope[alpineFile]]; MiscServerAdmin.PutLog["File found unchanged %g", IO.rope[alpineFile]]; RETURN[fileUnchanged]; }; { ENABLE ANY => GOTO alpineError; AlpineCmds.WheelCopy[from: alpineFile, to: "Backup.bigFile$$", enableWheel: TRUE]; EXITS alpineError => { err.PutF["Alpine error in retrieving %g\n", IO.rope[alpineFile]]; MiscServerAdmin.PutLog["Alpine error in retrieving %g", IO.rope[alpineFile]]; RETURN [alpineError]; } }; TRUSTED { ENABLE ANY => GOTO ifsError; source: CIFS.OpenFile = CIFS.Open["Backup.bigFile$$", CIFS.read]; [] ← FtpMan.Store[server, longName, CIFS.GetFC[source]]; source.Close[]; EXITS ifsError => { err.PutF["IFS error in storing %g on %g\n", IO.rope[longName], IO.rope[server]]; MiscServerAdmin.PutLog["IFS error in storing %g on %g", IO.rope[longName], IO.rope[server]]; RETURN [ifsError]; } }; DelVerAndSetNoBackup[server, directory, longName]; err.PutF["File transferred okay %g\n", IO.rope[alpineFile]]; MiscServerAdmin.PutLog["File transferred okay %g", IO.rope[alpineFile]]; RETURN [fileCopied] }; BackupFileName: PROC [alpineFile, ifsDirectory: ROPE] RETURNS [longName, shortName: ROPE] = { -- Transforms "[Luther.alpine]<MBrown.pa>Walnut.DBLog", "<AlpineBackup>" -- into "<AlpineBackup>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 "<AlpineBackup>Luther>MBrown.pa>Walnut.DBLog" ksFile: STREAM = FileIO.Open["Backup.ks$$", overwrite]; ksFile.PutRope[Rope.Cat[ "Connect ", directory, "\n", "Delete ", longName, ",\nKeep 1\nConfirm\n\n"]]; ksFile.PutRope[Rope.Cat[ "Change Attributes ", longName, "\nNo Backup\n\nQuit\n"]]; ksFile.Close[]; UserExec.DoIt[Rope.Cat["Chat ", server, " -l >Backup.ChatLog$$ <Backup.ks$$ -d -s"]]; }; RName: TYPE = GVBasics.RName; SendMessageInfo: TYPE = { ok, invalidRecipient, noValidRecipients, badPwd, badSender, badReturnTo, allDown}; SendMessage: PROC [from: ROPE, sender: RName, senderPwd: ROPE, returnTo: RName, to, cc: LIST OF RName, subject: ROPE, otherHeader: ROPE, body: ROPE, validate, sendIfValidateFails: BOOL] RETURNS [sent: BOOL, info: SendMessageInfo] = { NoticeInvalidRecipient: PROC [rNumber: INT, rName: RName] = { state ← $invalidRecipient; }; h: GVSend.Handle = GVSend.Create[]; state: SendMessageInfo; aborted: BOOL ← FALSE; DO ENABLE GVSend.SendFailed => 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; h.Close[]; 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[]]; }; Init: PROC [] = TRUSTED { Process.Detach[FORK BackupProcess[when: 5]]; MiscServerAdmin.AddCommand[command: [name: $StartAlpineBackup, arg: LIST["alpineFiles: ", "messagesTo: "]], details: [data: NIL, proc: StartAlpineBackup, acl: "AlpineWheels↑.pa"]]; }; Init[]; 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.