<> <> <> <<>> <> DIRECTORY AlpineCmds USING [Copy], AlpineEnvironment USING [AccessList], AlpineFS USING [OpenFile, Open, OpenOrCreate, StreamFromOpenFile, WriteProperties], AlpFile USING [PropertyValuePair], AlpInstance USING [Create], AlpTransaction USING [Handle, AssertAlpineWheel, Create, Finish], DB USING [Segment, TransactionHandle, CloseTransaction, DeclareSegment, EraseSegment, MakeTransHandle], Commander USING [Handle, Register, CommandProc], CommandTool USING [ArgumentVector, FileWithSearchRules, NextArgument, Parse], Convert USING [IntFromRope], DefaultRemoteNames USING [Get], FS USING [ComponentPositions, Error, OpenFile, nullOpenFile, Close, Create, ExpandName, GetInfo, PagesForBytes, SetByteCountAndCreatedTime, SetPageCount, StreamFromOpenFile, StreamOpen], IO, RefText USING [line, Fetch, Length, TrustTextAsRope], Rope, UserCredentials USING [Get], WalnutDefs USING [Segment], WalnutKernelDefs USING [LogInfoFromRoot, RootEntry], WalnutRegistry USING [CurrentWalnutState], WalnutSchema USING [SetSchemaVersion], WalnutStream USING [logFileInfo, WriteEntry]; NewWalnutUser: CEDAR PROGRAM IMPORTS AlpineCmds, AlpineFS, AlpInstance, AlpTransaction, Commander, CommandTool, Convert, DB, DefaultRemoteNames, FS, IO, RefText, Rope, UserCredentials, WalnutRegistry, WalnutSchema, WalnutStream = BEGIN <> STREAM: TYPE = IO.STREAM; ROPE: TYPE = Rope.ROPE; CmdHandle: TYPE = Commander.Handle; ArgumentVector: TYPE = CommandTool.ArgumentVector; defaultServer: ROPE = "Luther.alpine"; serverRope: ROPE = "\001ServerAndDirectory\002"; serverLen: NAT = Rope.Length[serverRope]; RootEntry: TYPE = WalnutKernelDefs.RootEntry; LogInfoFromRoot: TYPE = WalnutKernelDefs.LogInfoFromRoot; field1: REF TEXT = NEW[TEXT[RefText.line]]; <> InitWalnutFiles: Commander.CommandProc = { argV: ArgumentVector = CommandTool.Parse[cmd]; userKey, alpineServer: ROPE; userRName: ROPE = UserCredentials.Get[].name; IF IsActive[cmd, "InitWalnutFiles"] THEN RETURN; userKey _ CommandTool.NextArgument[cmd]; IF userKey = NIL THEN userKey _ IO.PutFR["%g's Mail Database", IO.rope[userRName]]; alpineServer _ CommandTool.NextArgument[cmd]; DoMailInit[cmd, userRName, userKey, alpineServer, FALSE]; }; InitForOtherUser: Commander.CommandProc = { argV: ArgumentVector = CommandTool.Parse[cmd]; userKey, alpineServer, userRName: ROPE; IF IsActive[cmd, "NewWalnutUser"] THEN RETURN; userRName _ CommandTool.NextArgument[cmd]; IF userRName = NIL THEN { cmd.out.PutRope["\n You must supply a userRName - quitting\n"]; RETURN }; IF userRName.Find["."] = -1 THEN -- need a registry userRName _ userRName.Cat[".", DefaultRemoteNames.Get[].registry]; userKey _ IO.PutFR["%g's Mail Database", IO.rope[userRName]]; alpineServer _ CommandTool.NextArgument[cmd]; DoMailInit[cmd, userRName, userKey, alpineServer, TRUE]; }; JustDoFiles: Commander.CommandProc = { rootFile: ROPE; argV: ArgumentVector = CommandTool.Parse[cmd]; IF IsActive[cmd, "JustDoFiles"] THEN RETURN; rootFile _ CommandTool.NextArgument[cmd]; IF rootFile = NIL THEN RETURN; [] _ InitFiles[rootFile, cmd, FALSE]; }; IsActive: PROC[cmd: Commander.Handle, which: ROPE] RETURNS[is: BOOL] = { IF WalnutRegistry.CurrentWalnutState[] # active THEN RETURN[FALSE]; cmd.err.PutF["\n*** You must destroy the Walnut Control window before doing %g - quitting\n", IO.rope[which]]; RETURN[TRUE]; }; DoMailInit: PROC[ cmd: CmdHandle, userRName, userKey, alpineServer: ROPE, masquerade: BOOL] = { <<>> <> localName, remoteName: ROPE; serverAndDirectory: ROPE; outcome: ROPE; IF alpineServer = NIL THEN alpineServer _ defaultServer; IF alpineServer.Find["."] = -1 THEN alpineServer _ alpineServer.Concat[".alpine"]; serverAndDirectory _ IO.PutFR["[%g]<%g>", IO.rope[alpineServer], IO.rope[userRName] ]; localName _ IO.PutFR["///Users/%g/Walnut.Root", IO.rope[userRName] ]; IF ~CreateRootFile[cmd, masquerade, userKey, localName, serverAndDirectory, userRName] THEN RETURN; remoteName _ Rope.Concat[serverAndDirectory, "Walnut.Root"]; outcome _ CopyLocalToAlpineFile[ alpineFile: remoteName, from: localName, masquerade: masquerade]; cmd.out.PutRope[outcome]; [] _ InitFiles[remoteName, cmd, masquerade]; }; DoOtherInit: Commander.CommandProc = { <> <> argV: ArgumentVector = CommandTool.Parse[cmd]; userKey, localName, remoteName, baseName: ROPE; serverAndDirectory: ROPE; userRName: ROPE = UserCredentials.Get[].name; IF cmd = NIL THEN RETURN; IF IsActive[cmd, "InitOtherWalnutFiles"] THEN RETURN; baseName _ CommandTool.NextArgument[cmd]; IF baseName = NIL THEN { cmd.out.PutRope["No base name for database given - quitting\n"]; RETURN; }; IF baseName.Fetch[0] = '[ THEN serverAndDirectory _ baseName ELSE IF baseName.Fetch[0] = '/ THEN serverAndDirectory _ FS.ExpandName[baseName].fullFName ELSE serverAndDirectory _ IO.PutFR["[%g]<%g>", IO.rope[defaultServer], IO.rope[baseName]]; userKey _ CommandTool.NextArgument[cmd]; IF userKey = NIL THEN userKey _ IO.PutFR["%g's Walnut Database", IO.rope[baseName]]; localName _ IO.PutFR["///Users/%g/Walnut.Root", IO.rope[userRName] ]; IF ~CreateRootFile[cmd, FALSE, userKey, localName, serverAndDirectory, baseName] THEN RETURN; remoteName _ Rope.Concat[serverAndDirectory, "Walnut.Root"]; AlpineCmds.Copy[to: remoteName, from: localName]; [] _ InitFiles[remoteName, cmd, FALSE]; }; Confirm: PROC[cmd: CmdHandle] RETURNS[continue: BOOL] = { char: CHAR; line: ROPE; cmd.err.PutRope["\nType y(CR) or Y(CR) or CR to continue anyway. "]; line _ cmd.in.GetLineRope[]; IF line.Length[] = 0 THEN RETURN[TRUE]; -- only CR typed continue _ (char _ line.Fetch[0]) = 'y OR char = 'Y OR char = '\n; }; CreateRootFile: PROC[ cmd: CmdHandle, masquerade: BOOL, userKey, localName, serverAndDirectory, mailForName: ROPE] RETURNS[BOOL] = { template, rootStream: STREAM; rootOF: FS.OpenFile; templateRoot: ROPE; BEGIN ENABLE UNWIND => { IF template # NIL THEN template.Close[]; IF rootStream # NIL THEN rootStream.Close[]; IF rootOF # FS.nullOpenFile THEN FS.Close[rootOF]; }; templateRoot _ CommandTool.FileWithSearchRules["Template-Walnut", ".root", cmd, FALSE]; IF templateRoot = NIL THEN { cmd.out.PutRope["Couldn't find Template-Walnut.root"]; RETURN[FALSE] }; template _ FS.StreamOpen[templateRoot ! FS.Error => { cmd.out.PutRope[error.explanation]; template _ NIL; CONTINUE}]; IF template = NIL THEN RETURN[FALSE]; rootOF _ FS.Create[name: localName, setKeep: TRUE, keep: 2 ! FS.Error => { cmd.out.PutRope[error.explanation]; rootOF _ FS.nullOpenFile; template.Close[]; CONTINUE}]; IF rootOF = FS.nullOpenFile THEN RETURN[FALSE]; rootStream _ FS.StreamFromOpenFile[openFile: rootOF, accessRights: $write ! FS.Error => { cmd.out.PutRope[error.explanation]; template.Close[]; FS.Close[rootOF]; rootStream _ NIL; CONTINUE}]; IF rootStream = NIL THEN RETURN[FALSE]; DO BEGIN line: REF TEXT; pos: INTEGER; IF template.EndOf[] THEN EXIT; line _ template.GetLine[field1 ! IO.EndOfStream => GOTO done]; IF Rope.Find[RefText.TrustTextAsRope[line], "UserSuppliedKey"] = 0 THEN { rootStream.PutRope[userKey]; rootStream.PutChar['\n]; LOOP }; IF Rope.Find[RefText.TrustTextAsRope[line], "UserRName"] = 0 THEN { rootStream.PutRope[mailForName]; rootStream.PutChar['\n]; LOOP }; IF (pos_ Rope.Find[RefText.TrustTextAsRope[line], serverRope]) = -1 THEN { rootStream.PutText[line]; rootStream.PutChar['\n]; LOOP }; FOR i: NAT IN [0 .. pos) DO rootStream.PutChar[RefText.Fetch[line, i]]; ENDLOOP; rootStream.PutRope[serverAndDirectory]; FOR i: NAT IN [pos+serverLen .. RefText.Length[line]) DO rootStream.PutChar[RefText.Fetch[line, i]]; ENDLOOP; rootStream.PutChar['\n]; EXITS done => EXIT; END; ENDLOOP; template.Close[]; rootStream.Flush[]; rootStream.Close[]; RETURN[TRUE]; END; }; InitFiles: PROC[rootFile: ROPE, cmd: CmdHandle, masquerade: BOOL] RETURNS[BOOL]= { rootEntry: RootEntry; logInfoList: LIST OF LogInfoFromRoot; logInfoCount: INT _ 0; keyValue, mailFor, dbName, newMailLog, readArchiveLog: ROPE; fileFailure: ROPE = "Couldn't initialize file %g\n"; rootStream: STREAM; finished: BOOL _ FALSE; fileStore: ROPE = rootFile.Substr[start: 1, len: rootFile.SkipTo[1, "]"] - 1]; openFile: AlpineFS.OpenFile; transHandle: AlpTransaction.Handle; IF rootFile = NIL THEN { cmd.out.PutRope["\nNo file specified\n"]; RETURN[FALSE] }; transHandle _ GetTransHandle[fileStore]; IF masquerade THEN AlpTransaction.AssertAlpineWheel[transHandle, TRUE]; openFile _ AlpineFS.Open[name: rootFile, access: $read, transHandle: transHandle ! FS.Error => CONTINUE]; IF openFile = FS.nullOpenFile THEN { cmd.out.PutF["Can't open rootFile: %g\n", IO.rope[rootFile] ]; -- couldn't open RETURN[FALSE] }; rootStream _ AlpineFS.StreamFromOpenFile[openFile]; BEGIN ENABLE IO.EndOfStream, IO.Error => GOTO cantParse; rootStream.SetIndex[0]; DO curPos: INT _ rootStream.GetIndex[]; rootEntry_ NextRootEntry[rootStream]; DO IF rootEntry.ident.Equal["Key", FALSE] THEN { keyValue_ rootEntry.value; EXIT }; IF rootEntry.ident.Equal["MailFor", FALSE] THEN { mailFor_ rootEntry.value; EXIT }; IF rootEntry.ident.Equal["Database", FALSE] THEN { dbName_ rootEntry.value; EXIT }; IF rootEntry.ident.Equal["NewMailLog", FALSE] THEN { newMailLog_ rootEntry.value; EXIT }; IF rootEntry.ident.Equal["ReadArchiveLog", FALSE] THEN { readArchiveLog_ rootEntry.value; EXIT }; IF rootEntry.ident.Equal["LogInfo", FALSE] THEN { logInfoList _ CONS[rootEntry.info, logInfoList]; logInfoCount _ logInfoCount + 1; EXIT }; IF rootEntry.ident.Equal["End", FALSE] THEN { finished_ TRUE; EXIT }; rootStream.Close[ ! IO.Error, FS.Error => CONTINUE]; cmd.out.PutF["Unknown entry in RootFile at pos %g\n", IO.int[curPos]]; RETURN[FALSE] ENDLOOP; IF finished THEN EXIT; ENDLOOP; rootStream.Close[ ! IO.Error, FS.Error => CONTINUE]; EXITS cantParse => { rootStream.Close[ ! IO.Error, FS.Error => CONTINUE]; cmd.out.PutRope["Couldn't parse root file\n"]; RETURN[FALSE] }; END; IF keyValue.Length[] = 0 OR dbName.Length[] = 0 OR mailFor.Length[] = 0 OR newMailLog.Length[] = 0 OR readArchiveLog.Length[] = 0 OR logInfoCount # 2 THEN { cmd.out.PutRope["Incomplete RootFile - missing entries\n"]; RETURN[FALSE] }; <<>> <> BEGIN cp: FS.ComponentPositions; fn: ROPE; oServer: ROPE; CheckServer: PROC[name: ROPE] RETURNS[ok: BOOL] = { [fn, cp, ] _ FS.ExpandName[name]; oServer _ Rope.Substr[fn, cp.server.start, cp.server.length]; IF ~(ok _ Rope.Equal[fileStore, oServer, FALSE]) THEN cmd.out.PutF["\nserver for file %g does not agree with that for %g\n", IO.rope[name], IO.rope[rootFile]]; }; IF ~CheckServer[dbName] THEN RETURN[FALSE]; IF ~CheckServer[newMailLog] THEN RETURN[FALSE]; IF ~CheckServer[readArchiveLog] THEN RETURN[FALSE]; IF ~CheckServer[logInfoList.first.fileName] THEN RETURN[FALSE]; IF ~CheckServer[logInfoList.rest.first.fileName] THEN RETURN[FALSE]; END; cmd.out.PutRope["\n Initializing log files\n"]; IF ~EnsureEmptyFile[cmd, newMailLog, fileStore, masquerade, 200] THEN RETURN[FALSE]; IF ~EnsureEmptyFile[cmd, readArchiveLog, fileStore, masquerade, 0] THEN RETURN[FALSE]; FOR lil: LIST OF LogInfoFromRoot _ logInfoList, lil.rest UNTIL lil = NIL DO seqNo: ROPE _ lil.first.logSeqNo; fileName: ROPE = lil.first.fileName; openFile: AlpineFS.OpenFile; IF seqNo.Fetch[0] # 'E THEN { -- write the LogFileInfo entry in the file strm: STREAM; realSeqNo: INT; transHandle: AlpTransaction.Handle; IF ~EnsureEmptyFile[cmd, fileName, fileStore, masquerade, 200] THEN RETURN[FALSE]; transHandle _ GetTransHandle[fileStore]; IF masquerade THEN AlpTransaction.AssertAlpineWheel[transHandle, TRUE]; openFile _ AlpineFS.Open[name: fileName, access: $write, transHandle: transHandle]; strm _ AlpineFS.StreamFromOpenFile[openFile, $write]; realSeqNo _ Convert.IntFromRope[seqNo]; WalnutStream.logFileInfo.key _ keyValue.ToRefText[]; WalnutStream.logFileInfo.internalFileID _ lil.first.internalFileID; WalnutStream.logFileInfo.logSeqNo _ realSeqNo; []_ WalnutStream.WriteEntry[strm, WalnutStream.logFileInfo]; strm.Close[]; } ELSE IF ~EnsureEmptyFile[cmd, fileName, fileStore, masquerade, 0] THEN RETURN[FALSE]; ENDLOOP; BEGIN inaccessible: BOOL _ FALSE; cmd.out.PutRope[" Initializing segment file\n"]; BEGIN segment: DB.Segment = $Walnut; tHandle: AlpTransaction.Handle = GetTransHandle[fileStore]; transHandle: DB.TransactionHandle = DB.MakeTransHandle[tHandle]; IF masquerade THEN AlpTransaction.AssertAlpineWheel[tHandle, TRUE]; DB.DeclareSegment[ filePath: dbName, segment: segment, readonly: FALSE, nPagesInitial: 256, nPagesPerExtent: 256]; IF masquerade THEN AlpTransaction.AssertAlpineWheel[tHandle, TRUE]; [] _ DB.EraseSegment[segment, transHandle]; -- leaves trans open IF masquerade THEN AlpTransaction.AssertAlpineWheel[tHandle, TRUE]; WalnutSchema.SetSchemaVersion[segment ! ABORTED => GOTO cant]; DB.CloseTransaction[transHandle]; EXITS cant => { cmd.out.PutF["Couldn't initialize database %g\n", IO.rope[dbName] ]; RETURN[FALSE] }; END; END; cmd.out.PutF[" Walnut files have been properly intialized\n"]; RETURN[TRUE]; }; NextRootEntry: PROC[strm: STREAM] RETURNS[rte: RootEntry] = { []_ strm.SkipWhitespace[flushComments: TRUE]; rte.ident _ strm.GetTokenRope[].token; IF rte.ident.Equal["End", FALSE] THEN RETURN; []_ strm.SkipWhitespace[flushComments: TRUE]; IF rte.ident.Equal["LogInfo", FALSE] THEN { lif: LogInfoFromRoot; lif.fileName _ strm.GetTokenRope[IO.IDProc].token; []_ strm.SkipWhitespace[flushComments: TRUE]; lif.internalFileID _ strm.GetInt[]; []_ strm.SkipWhitespace[flushComments: TRUE]; lif.seqNoPos _ strm.GetIndex[]; lif.logSeqNo _ strm.GetTokenRope[].token; []_ strm.SkipWhitespace[flushComments: TRUE]; rte.info _ lif; } ELSE IF rte.ident.Equal["Key", FALSE] THEN rte.value _ strm.GetLineRope[] ELSE { rte.value _ strm.GetTokenRope[IO.IDProc].token; []_ strm.SkipWhitespace[flushComments: TRUE]; }; RETURN[rte]; }; nonZeroLen: ROPE = "File %g has nonZero (%g bytes) length\n"; EnsureEmptyFile: PROC[ cmd: CmdHandle, fileName, fileStore: ROPE, masquerade: BOOL, pagesWanted: INT _ 2] RETURNS[ok: BOOL] = { strm: STREAM; openFile: AlpineFS.OpenFile; numPages, length: INT; transHandle: AlpTransaction.Handle = GetTransHandle[fileStore]; prop: highWaterMark AlpFile.PropertyValuePair = [highWaterMark[0]]; IF masquerade THEN AlpTransaction.AssertAlpineWheel[transHandle, TRUE]; openFile _ AlpineFS.OpenOrCreate[name: fileName, pages: pagesWanted, transHandle: transHandle]; strm _ AlpineFS.StreamFromOpenFile[openFile, $write]; length _ strm.GetLength[]; IF length # 0 THEN { continue: BOOL _ FALSE; cmd.out.PutRope[IO.PutFR[nonZeroLen, IO.rope[fileName], IO.int[length]]]; continue _ Confirm[cmd]; IF ~continue THEN { strm.Close[]; RETURN[FALSE] }; }; numPages_ FS.GetInfo[openFile].pages; IF pagesWanted > numPages THEN FS.SetPageCount[openFile, pagesWanted]; strm.SetIndex[0]; strm.SetLength[0]; strm.Flush[]; -- make it notice the SetLength IF masquerade THEN AlpTransaction.AssertAlpineWheel[transHandle, TRUE]; AlpineFS.WriteProperties[openFile, LIST[prop]]; strm.Close[]; RETURN[TRUE]; }; CopyLocalToAlpineFile: PROC[ alpineFile: Rope.ROPE, from: Rope.ROPE, masquerade: BOOL] RETURNS[Rope.ROPE] = { openFile: AlpineFS.OpenFile; localStrm, alpStrm: STREAM; transHandle: AlpTransaction.Handle; pages, bytes: INT; fileStore: ROPE = alpineFile.Substr[start: 1, len: alpineFile.SkipTo[1, "]"] - 1]; localStrm _ FS.StreamOpen[from]; pages _ FS.PagesForBytes[bytes _ localStrm.GetLength[]]; transHandle _ GetTransHandle[fileStore]; IF masquerade THEN AlpTransaction.AssertAlpineWheel[transHandle, TRUE]; openFile _ GetAlpineFile[alpineFile, pages, transHandle]; alpStrm _ AlpineFS.StreamFromOpenFile[openFile, $write]; alpStrm.SetIndex[0]; localStrm.SetIndex[0]; CopyStreams[from: localStrm, to: alpStrm, transHandle: transHandle, masquerade: masquerade]; alpStrm.Flush[]; IF masquerade THEN AlpTransaction.AssertAlpineWheel[transHandle, TRUE]; openFile.SetByteCountAndCreatedTime[ bytes: bytes ]; IF AlpTransaction.Finish[transHandle, commit] # commit THEN { FS.Close[openFile ! FS.Error => CONTINUE]; localStrm.Close[]; RETURN[IO.PutFR[" commit failed for %g", IO.rope[alpineFile]]]; }; FS.Close[openFile ! FS.Error => CONTINUE]; localStrm.Close[]; RETURN[IO.PutFR["\n\n****%g created, copied from %g", IO.rope[alpineFile], IO.rope[from]]]; }; GetTransHandle: PROC[fileStore: ROPE] RETURNS[AlpTransaction.Handle] = { RETURN[ AlpTransaction.Create[instHandle:AlpInstance.Create[fileStore: fileStore]] ] }; GetAlpineFile: PROC[alpineFile: Rope.ROPE, pages: INT, transHandle: AlpTransaction.Handle] RETURNS[openFile: AlpineFS.OpenFile] = { initialAccess: AlpineEnvironment.AccessList = LIST[ "owner" ]; openFile _ AlpineFS.OpenOrCreate[ name: alpineFile, pages: pages, transHandle: transHandle ]; AlpineFS.WriteProperties[ file: openFile, properties: LIST[ [modifyAccess[initialAccess]], [readAccess[initialAccess]] ] ]; }; copyBuffer: REF TEXT _ NEW[TEXT[8*512]]; CopyStreams: PROC[ from, to: STREAM, transHandle: AlpTransaction.Handle, masquerade: BOOL] = { count: INT _ 0; DO IF from.GetBlock[copyBuffer, 0, 8*512] = 0 THEN EXIT; to.PutBlock[copyBuffer]; IF (count _ count + 1) >= 10 THEN { to.Flush[]; IF masquerade THEN AlpTransaction.AssertAlpineWheel[transHandle, TRUE]; count _ 0; }; ENDLOOP }; <<* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * >> Commander.Register["InitWalnutFiles", InitWalnutFiles, "Create the files for a Walnut Mail Databse"]; Commander.Register["NewWalnutUser", InitForOtherUser, "Create the files for a Walnut Mail Databse for someone else - AlpineWheels only"]; Commander.Register["InitOtherWalnutFiles", DoOtherInit, "Create the files for walnut"]; Commander.Register["JustDoFiles", JustDoFiles, "Create the files using an existing rootFile"]; END.