DIRECTORY Ascii USING [Lower], Basics, BasicTime USING [GMT, Now, nullGMT], Commander USING [CommandProc, Handle, Register], CommandTool USING [ArgumentVector, Failed, Parse], Convert, DFUtilities USING [DirectoryItem, FileItem, Filter, ImportsItem, IncludeItem, ParseFromStream, ProcessItemProc, UsingList], FileNames, FS USING [ComponentPositions, ExpandName, EnumerateForInfo, Error, FileInfo, InfoProc, StreamOpen, tDirectory], FSBackdoor, IO, List USING [CompareProc], Process USING [CheckForAbort], RedBlackTree USING [Compare, Create, Delete, EachNode, EnumerateIncreasing, GetKey, Insert, Lookup, Table], RefText, Rope, SunNFSRemoteFile, Tar, TarFileFormat, TarPrivate, UnixRemoteFile, UXTime USING [DOWN, FromGMT]; TrickleChargeToTarImpl: CEDAR MONITOR IMPORTS Ascii, BasicTime, Commander, CommandTool, Convert, DFUtilities, FileNames, FS, IO, Process, RedBlackTree, Rope, RefText, SunNFSRemoteFile, TarPrivate, UnixRemoteFile, UXTime = BEGIN GMT: TYPE = BasicTime.GMT; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; TBLOCK: NAT ~ TarFileFormat.TBLOCK; Switches: TYPE = PACKED ARRAY CHAR['a..'z] OF BOOL; TCType: TYPE = {fullDirectory, oneDF, allDFs}; TCInfo: TYPE = REF TCInfoRec; TCInfoRec: TYPE = RECORD[ pseudoServer, translation: ROPE, -- pseudoServer and its translation, for source tarFileName: ROPE, server: ROPE, srcPrefixToSurpress: ROPE, -- prefix path for source to supress restOfSrcPrefix: ROPE, srcPrefix: ROPE, otherArg: ROPE, -- for oneDF and allDFs tcType: TCType _ fullDirectory ]; FileEntry: TYPE = REF FileEntryRep; FileEntryRep: TYPE = RECORD [ name: ROPE, short: ROPE, fullUnix: ROPE, relative: ROPE, caseFileName: ROPE, relativeCaseName: ROPE, date: GMT, len: INT, state: FileEntryState ]; FileEntryState: TYPE = {init, doingHeader, copying, moved}; dirDoc: ROPE = " tarFile srcDir moves files from srcDir to tarFile "; allDfsDoc: ROPE = "{ { pseudoServerName translation } } tarFile srcDirToBeSupressed subDir {subdirForDfs} moves files from srcDirToBeSupressed/srcDir{/subdirForDfs} to tarFile "; oneDfDoc: ROPE = "{ { pseudoServerName translation } } tarFile srcDirToBeSupressed subDir subDirAndDfName moves files from srcDirToBeSupressed/srcDir/subDirAndDfName to tarFile "; maxRetries: NAT _ 10; retrySeconds: NAT _ 20; nameText: REF TEXT = NEW[TEXT[RefText.line]]; DoIt: ENTRY PROC [tcInfo: TCInfo, out: STREAM, blocking: NAT _ 20, bestEfforts: BOOL _ TRUE] = { ENABLE UNWIND => NULL; enumerateForDfFiles: BOOL _ tcInfo.tcType = allDFs; errorsEncountered: BOOL _ FALSE; filter: DFUtilities.Filter _ [FALSE, all, all, defining]; table: RedBlackTree.Table _ RedBlackTree.Create[getKey: GetKey, compare: Compare]; tarFile: STREAM; unixH, tarUnixH: UnixRemoteFile.UnixServerHandle; path: UnixRemoteFile.Path; currentFullDirPath: ROPE _ NIL; EachEntry: RedBlackTree.EachNode = { WITH data SELECT FROM entry: FileEntry => { IF entry.state # moved THEN CopyFileToTar[entry]; RETURN; }; ENDCASE => ERROR; }; CopyFileToTar: PROC [entry: FileEntry] = { srcStream, caseStream: STREAM _ NIL; retriedCount: NAT _ 0; fileInfo: Tar.FileInfo; mTime: BasicTime.GMT; thisDirPath: ROPE _ GetFullDirPath[entry.fullUnix]; IF NOT thisDirPath.Equal[currentFullDirPath] THEN { thisPath: UnixRemoteFile.Path _ [thisDirPath, NIL]; UnixRemoteFile.Close[unixH, path.dirFD]; path.dirFD _ UnixRemoteFile.Open[unixH, [currentFullDirPath _ thisDirPath, NIL]]; }; path.path _ entry.fullUnix; entry.state _ doingHeader; srcStream _ UnixRemoteFile.OpenReadStream[unixH, path]; [ , fileInfo.mode, , fileInfo.uid, fileInfo.gid, fileInfo.size, , , , , , , mTime, ] _ UnixRemoteFile.GetInfoFromStream[srcStream]; fileInfo.mtime _ UXTime.DOWN[UXTime.FromGMT[mTime]]; fileInfo.name _ entry.relative; WriteHeaderToTarFile[tarFile, TarPrivate.HeaderFromFileInfo[fileInfo]]; entry.state _ copying; StreamToStreamCopy[to: tarFile, from: srcStream]; -- pads to TBLOCK srcStream.Close[]; path.path _ entry.caseFileName; caseStream _ UnixRemoteFile.OpenReadStream[unixH, path ! UnixRemoteFile.Error => IF code = $noent THEN CONTINUE ELSE REJECT]; IF caseStream = NIL THEN { fileInfo.size _ 0; -- no case file, use info from regular file } ELSE { [ , fileInfo.mode, , fileInfo.uid, fileInfo.gid, fileInfo.size, , , , , , , mTime, ] _ UnixRemoteFile.GetInfoFromStream[caseStream]; fileInfo.mtime _ UXTime.DOWN[UXTime.FromGMT[mTime]]; caseStream.Close[]; }; fileInfo.name _ entry.relativeCaseName; WriteHeaderToTarFile[tarFile, TarPrivate.HeaderFromFileInfo[fileInfo]]; tarFile.Flush[]; entry.state _ moved; filesMoved _ CheckCount[filesMoved, out]; }; VisitEntry: PROC [name: ROPE, date: GMT] = { newE: FileEntry _ NIL; bytes: INT _ 0; short: ROPE _ NIL; Process.CheckForAbort[]; WITH RedBlackTree.Lookup[table, name] SELECT FROM entry: FileEntry => IF entry.date = date THEN RETURN; ENDCASE; [fullFName: name, bytes: bytes, created: date] _ FS.FileInfo[name: name, wantedCreatedTime: date ! FS.Error => IF error.code = $unknownFile OR error.code = $unknownCreatedTime OR (error.code = $unknownServer AND bestEfforts) THEN { out.PutF1["\tFS.Error[%g]\n", [rope[error.explanation]]]; name _ NIL; CONTINUE; } ELSE REJECT]; short _ ReduceToShort[tcInfo, name]; IF short = NIL THEN RETURN; WITH RedBlackTree.Lookup[table, name] SELECT FROM entry: FileEntry => { IF entry.date = date THEN RETURN; [] _ RedBlackTree.Delete[table, name]; }; ENDCASE; newE _ NEW[FileEntryRep _ [ name: name, short: short, date: date, len: bytes, state: init]]; UnixNames[tcInfo, newE]; RedBlackTree.Insert[table, newE, name]; filesSeenDuringEnumeration _ CheckCount[filesSeenDuringEnumeration, out]; bytesSeenDuringEnumeration _ bytesSeenDuringEnumeration + bytes; }; VisitClosure: PROC [tcInfo: TCInfo, dfName: ROPE, date: GMT, visitor: PROC [name: ROPE, date: GMT], usingList: REF DFUtilities.UsingList _ NIL] = { EachItem: DFUtilities.ProcessItemProc = { WITH item SELECT FROM dir: REF DFUtilities.DirectoryItem => prefix _ TranslateHost[tcInfo, dir.path1]; file: REF DFUtilities.FileItem => { name: ROPE = Rope.Concat[prefix, file.name]; xName: ROPE _ IF prefix = NIL THEN TranslateHost[tcInfo, name] ELSE name; IF xName = NIL THEN xName _ name; visitor[xName, file.date.gmt]; }; incl: REF DFUtilities.IncludeItem => { file: ROPE _ TranslateHost[tcInfo, incl.path1]; visitor[file, incl.date.gmt]; VisitClosure[tcInfo, file, incl.date.gmt, visitor]; }; imports: REF DFUtilities.ImportsItem => { -- this stuff is for me - (bj) }; ENDCASE => { i: INT _ 0; }; -- hany for setting breakpoints - (bj) }; prefix: ROPE _ NIL; in: STREAM; xName: ROPE = TranslateHost[tcInfo, dfName]; in _ FS.StreamOpen[ fileName: xName, wantedCreatedTime: date ! FS.Error => IF error.code = $unknownServer AND bestEfforts THEN { out.PutF1["FS.Error[%g] - best efforts requested so continuing\n", [rope[error.explanation]]]; in _ NIL; CONTINUE; } ELSE REJECT ]; IF in#NIL THEN { filter.list _ usingList; DFUtilities.ParseFromStream[in, EachItem, filter -- global variable, hack for now! - (bj) ! UNWIND => in.Close[]]; in.Close[] }; }; filesMoved: INT _ 0; tarBytes: INT _ 0; filesSeenDuringEnumeration: INT _ 0; bytesSeenDuringEnumeration: INT _ 0; out.PutF["\n\tTar'ing files from %g%g to %g\n", [rope[tcInfo.srcPrefix]], [rope[tcInfo.otherArg]], [rope[tcInfo.tarFileName]] ]; out.PutF1["\n***** Building file table at %g\n", [time[BasicTime.Now[]]] ]; SELECT tcInfo.tcType FROM oneDF => { VisitClosure[tcInfo, tcInfo.srcPrefix.Cat[tcInfo.otherArg, "!H"], BasicTime.nullGMT, VisitEntry]; }; allDFs => { srcPrefix: ROPE = tcInfo.srcPrefix; srcPrefixLen: INT = tcInfo.srcPrefix.Length[]; EachDfFile: FS.InfoProc = { Process.CheckForAbort[]; continue _ TRUE; IF NOT Rope.IsPrefix[srcPrefix, fullFName, FALSE] THEN { out.PutF["\t***%g is not a prefix of %g - ignoring\n", [rope[srcPrefix]], [rope[fullFName]] ]; RETURN; }; VisitClosure[tcInfo: tcInfo, dfName: fullFName, date: BasicTime.nullGMT, visitor: VisitEntry]; }; FS.EnumerateForInfo[srcPrefix.Concat["*.df!H"], EachDfFile]; }; fullDirectory => { -- Trickling a whole directory. EachFile: FS.InfoProc = { continue _ TRUE; Process.CheckForAbort[]; IF fileType=FS.tDirectory THEN RETURN; IF Rope.IsPrefix[tcInfo.srcPrefix, fullFName, FALSE] THEN { new: FileEntry _ NIL; short: ROPE _ NIL; WITH RedBlackTree.Lookup[table, fullFName] SELECT FROM entry: FileEntry => { IF entry.date = created THEN RETURN; [] _ RedBlackTree.Delete[table, fullFName]; }; ENDCASE; short _ Rope.Substr[fullFName, tcInfo.srcPrefix.Length[]]; IF Rope.Match["!*", short] THEN RETURN; new _ NEW[FileEntryRep _ [ name: fullFName, short: short, date: created, len: bytes, state: init]]; RedBlackTree.Insert[table, new, fullFName]; filesSeenDuringEnumeration _ CheckCount[filesSeenDuringEnumeration, out]; bytesSeenDuringEnumeration _ bytesSeenDuringEnumeration + bytes; }; }; FS.EnumerateForInfo[tcInfo.srcPrefix.Concat["*!h"], EachFile]; }; ENDCASE => ERROR; out.PutF["\nEnumerated files: %g, bytes: %g\n", [integer[filesSeenDuringEnumeration]], [integer[bytesSeenDuringEnumeration]] ]; IF filesSeenDuringEnumeration = 0 THEN RETURN; -- nothing to do tarFile _ FS.StreamOpen[tcInfo.tarFileName, $create ! FS.Error => IF error.code = $globalCreation THEN CONTINUE ELSE REJECT]; IF tarFile = NIL THEN { -- need to open a remote file, assumed on a sun accessMode: UnixRemoteFile.Mode ~ 774B; -- world read access tarPath: UnixRemoteFile.Path; full, server, foo: ROPE; cp: FS.ComponentPositions; [full, cp, ] _ FS.ExpandName[tcInfo.tarFileName]; server _ full.Substr[cp.server.start, cp.server.length]; foo _ FileNames.ConvertToSlashFormat[full]; tarUnixH _ UnixRemoteFile.CreateHandle[server]; foo _ foo.Substr[ foo.SkipTo[1, "/"] ]; tarPath.path _ foo.Substr[ 0, foo.FindBackward["/"] ]; tarPath.dirFD _ UnixRemoteFile.Open[tarUnixH, tarPath]; tarPath.path _ foo; tarFile _ UnixRemoteFile.OpenWriteStream[tarUnixH, tarPath, accessMode]; }; unixH _ UnixRemoteFile.CreateHandle[tcInfo.server]; out.PutF1["\n***** Copying files at %g\n", [time[BasicTime.Now[]]] ]; RedBlackTree.EnumerateIncreasing[table, EachEntry]; FinishAndPadTarFileToBlocking[tarFile, blocking]; tarBytes _ tarFile.GetLength[]; tarFile.Close[]; -- get it out there out.PutF["\n Moved %g files, resulting tarFile is %g bytes\n", [integer[filesMoved]], [integer[tarBytes]] ]; out.PutF1["\n{Done at %g}\n", [time[BasicTime.Now[]]] ]; }; CheckCount: PROC[num: INT, out: STREAM] RETURNS[res: INT] = { IF ( res _ num + 1 ) MOD 10 = 0 THEN IF res MOD 100 = 0 THEN out.PutF["(%g) ", [integer[res]] ] ELSE out.PutChar['.]; }; ReduceToShort: PROC [tcInfo: TCInfo, name: ROPE] RETURNS [short: ROPE] = { IF NOT Rope.IsPrefix[tcInfo.srcPrefix, name, FALSE] THEN RETURN[NIL]; short _ name.Substr[tcInfo.srcPrefix.Length[]]; }; UnixNames: PROC [tcInfo: TCInfo, entry: FileEntry] = { foo, foo2, foo3, full: ROPE; cp: FS.ComponentPositions; version: FSBackdoor.Version _ FSBackdoor.noVersion; versionCard: CARD _ 0; lastSlash: INT _ 0; [full, cp, ] _ FS.ExpandName[entry.name]; versionCard _ Convert.CardFromRope[full.Substr[cp.ver.start, cp.ver.length] ! Convert.Error => CONTINUE]; IF versionCard # 0 THEN version _ [versionCard]; foo _ FileNames.ConvertToSlashFormat[FileNames.StripVersionNumber[entry.name]]; foo2 _ foo.Substr[ foo.SkipTo[1, "/"] ]; foo3 _ SunNFSRemoteFile.EncodeVersionInNFSName[foo2, version]; nameText.length _ 0; FOR i: INT IN [0 .. foo3.Length[]) DO [] _ RefText.InlineAppendChar[nameText, Ascii.Lower[foo3.Fetch[i]]]; ENDLOOP; entry.fullUnix _ Rope.FromRefText[nameText]; entry.relative _ Rope.Concat["./", entry.fullUnix.Substr[tcInfo.srcPrefixToSurpress.Length[]] ]; lastSlash _ foo2.FindBackward["/"]; nameText.length _ 0; FOR i: INT IN [0 .. lastSlash+1) DO [] _ RefText.InlineAppendChar[nameText, Ascii.Lower[foo2.Fetch[i]]]; ENDLOOP; [] _ RefText.AppendRope[nameText, SunNFSRemoteFile.caseFileNamePrefix]; FOR i: INT IN [lastSlash+1 .. foo2.Length[]) DO [] _ RefText.InlineAppendChar[nameText, foo2.Fetch[i]]; ENDLOOP; entry.caseFileName _ Rope.FromRefText[nameText]; entry.relativeCaseName _ Rope.Concat["./", entry.caseFileName.Substr[tcInfo.srcPrefixToSurpress.Length[]] ]; }; GetFullDirPath: PROC[ r: ROPE ] RETURNS [fullDirPath: ROPE ] = { pos: INT _ r.FindBackward["/"]; RETURN[ r.Substr[0, pos] ]; }; copyBuffer: REF TEXT _ NEW[TEXT[TBLOCK]]; nullBuffer: REF TEXT _ NEW[TEXT[TBLOCK]]; StreamToStreamCopy: PROC[to, from: STREAM] = { bytes: NAT; DO IF ( ( bytes _ from.GetBlock[copyBuffer, 0, TBLOCK] ) = 0 ) THEN EXIT; to.PutBlock[copyBuffer]; IF bytes < TBLOCK THEN { to.PutBlock[block: nullBuffer, startIndex: bytes]; EXIT; }; ENDLOOP }; WriteHeaderToTarFile: PROC[ tarFile: STREAM, h: TarFileFormat.Header ] = TRUSTED { unsafe: Basics.UnsafeBlock ~ [LOOPHOLE[LONG[@h]], 0, TarFileFormat.headerBytes]; tarFile.UnsafePutBlock[unsafe]; tarFile.PutBlock[block: nullBuffer, startIndex: TarFileFormat.headerBytes]; -- pad to TBLOCK }; FinishAndPadTarFileToBlocking: PROC[tarFile: STREAM, blocking: NAT] = { nBlocks, needed: INT; len: INT ~ tarFile.GetIndex[]; IF ( len MOD TBLOCK ) # 0 THEN { tarFile.Close[]; -- so we can examine the remains ERROR; }; -- someone messed up tarFile.PutBlock[nullBuffer]; tarFile.PutBlock[nullBuffer]; -- 2 nullBuffer's for EOF nBlocks _ tarFile.GetIndex[] / TBLOCK; needed _ nBlocks MOD blocking; FOR i: INT IN [0..needed) DO tarFile.PutBlock[nullBuffer]; ENDLOOP; }; TranslateHost: PROC [tcInfo: TCInfo, path: ROPE] RETURNS [ROPE] = { IF Rope.Match["[*]*", path] THEN { rPos: INT _ Rope.SkipTo[path, 1, "]"]; host: ROPE _ Rope.Substr[path, 1, rPos-1]; rest: ROPE _ Rope.Substr[path, rPos+1]; sourceHost: ROPE _ TranslateForSource[tcInfo, host]; IF sourceHost = NIL THEN RETURN[path]; IF NOT Rope.Equal[host, sourceHost, FALSE] THEN { IF NOT Rope.IsPrefix["[", sourceHost] THEN sourceHost _ Rope.Cat["[", sourceHost, "]"]; IF (Rope.Fetch[sourceHost, Rope.Length[sourceHost]-1] = '>) AND Rope.IsPrefix["<", rest] THEN rest _ Rope.Substr[rest, 1]; RETURN [Rope.Concat[sourceHost, rest]]; }; }; RETURN [path]; }; TranslateForSource: PROC [tcInfo: TCInfo, server: ROPE] RETURNS [ROPE] = { IF Rope.Equal[tcInfo.pseudoServer, server, FALSE] THEN RETURN [tcInfo.translation]; RETURN[NIL]; }; GetKey: RedBlackTree.GetKey = { RETURN [data]; }; Compare: RedBlackTree.Compare = { key: ROPE _ NIL; WITH k SELECT FROM ent: FileEntry => key _ ent.name; rope: ROPE => key _ rope; ENDCASE => ERROR; WITH data SELECT FROM ent: FileEntry => RETURN [Rope.Compare[key, ent.name, FALSE]]; ENDCASE; ERROR; }; CompareEntries: List.CompareProc = { WITH ref1 SELECT FROM ent1: FileEntry => WITH ref2 SELECT FROM ent2: FileEntry => RETURN [Rope.Compare[ent1.name, ent2.name, FALSE]]; ENDCASE; ENDCASE; ERROR; }; AllDfsCmdProc: Commander.CommandProc = { [result, msg] _ Common[allDFs, cmd] }; OneDfCmdProc: Commander.CommandProc = { [result, msg] _ Common[oneDF, cmd] }; DirCmdProc: Commander.CommandProc = { [result, msg] _ Common[fullDirectory, cmd] }; Common: PROC[tcType: TCType, cmd: Commander.Handle] RETURNS[result: REF, msg: ROPE] = { out: STREAM = cmd.out; argv: CommandTool.ArgumentVector _ CommandTool.Parse[cmd: cmd, starExpand: FALSE ! CommandTool.Failed => {msg _ errorMsg; GO TO failed}]; this: TCInfo _ NEW[TCInfoRec _ [ tcType: tcType ] ]; i: NAT _ 1; WHILE i < argv.argc DO arg: ROPE _ argv[i]; Process.CheckForAbort[]; IF arg.Length[] = 0 THEN { i _ i + 1; LOOP }; SELECT arg.Fetch[0] FROM '{ => { this.pseudoServer _ argv[i _ i + 1]; this.translation _ argv[i _ i + 1]; arg _ argv[i _ i + 1]; IF arg.Fetch[0] # '} THEN GOTO failed; i _ i + 1; LOOP; }; '$ => EXIT; -- end of current set of instructions ENDCASE => {}; SELECT TRUE FROM ( this.tarFileName = NIL ) => this.tarFileName _ arg; ( this.server = NIL ) => this.server _ arg; ( this.srcPrefixToSurpress = NIL ) => { IF NOT Rope.Match["*>", arg] THEN { msg _ IO.PutFR1["Source not a directory (%g)", [rope[arg]] ]; GO TO failed; }; this.srcPrefixToSurpress _ arg; }; ( this.restOfSrcPrefix = NIL ) => { IF NOT Rope.Match["*>", arg] THEN { msg _ IO.PutFR1["restOfSrcPrefix not a directory (%g)", [rope[arg]] ]; GO TO failed; }; this.restOfSrcPrefix _ arg; }; ( this.otherArg # NIL) => { msg _ IO.PutFR1["Extra argument (%g)", [rope[arg]] ]; GO TO failed; }; ENDCASE => this.otherArg _ arg; i _ i + 1; ENDLOOP; this.srcPrefix _ IO.PutFR["[%g]%g%g", [rope[this.server]], [rope[this.srcPrefixToSurpress]], [rope[this.restOfSrcPrefix]] ]; this.srcPrefixToSurpress _ FileNames.ConvertToSlashFormat[this.srcPrefixToSurpress]; this.restOfSrcPrefix _ FileNames.ConvertToSlashFormat[this.restOfSrcPrefix]; DoIt[this, out ! FS.Error => { out.PutF["FS.Error[%g], stopping (at %g)\n\n", [rope[error.explanation]], [time[BasicTime.Now[]]] ]; CONTINUE; } ]; EXITS failed => {result _ $Failure}; }; Commander.Register[ key: "TarChargeOneDF", proc: OneDfCmdProc, doc: oneDfDoc, interpreted: TRUE]; Commander.Register[ key: "TarChargeAllDFs", proc: AllDfsCmdProc, doc: allDfsDoc, interpreted: TRUE]; Commander.Register[ key: "TarChargeDirectory", proc: DirCmdProc, doc: dirDoc, interpreted: TRUE]; Commander.Register[ key: "TrOne", proc: OneDfCmdProc, doc: oneDfDoc, interpreted: TRUE]; Commander.Register[ key: "TrAll", proc: AllDfsCmdProc, doc: allDfsDoc, interpreted: TRUE]; Commander.Register[ key: "TrDir", proc: DirCmdProc, doc: dirDoc, interpreted: TRUE]; { FOR i: NAT IN [0..TBLOCK) DO nullBuffer[i] _ '\000; ENDLOOP; nullBuffer.length _ TBLOCK; }; END. lTrickleChargeToTarImpl.mesa Copyright Σ 1989 by Xerox Corporation. All rights reserved. Willie-Sue, August 3, 1989 5:03:58 pm PDT TrickleChargeToTar (switches) srcDir dstFile moves files from remote directory to a tar file. Types includes version # (translated - with real server) - fs format Does not include the srcPrefix, does include version # - fs format full unix name except for server, includes version #, unix format Does not include the srcPrefixToSurpress, does include version #, unix format; has "./" prepended (to put into tar file header) create date of the file byte count of the file (useful redundancy) indicates the state of the file (obvious) Documentation Option variables # of times to retry connectionRejected from STP # of seconds between retry attempts Command Procedures [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE] called for each item in the table to do the moving. now get the .~case~ file for this name, from short w/o version This procedure is used to visit each file in a simple DF closure, where the imports are NOT followed, but the inclusions ARE followed. The mainline of DoIt Phase1, build up data base. Don't move any files. Trickling a df file together with the files it controls Trickling df files in a directory together with the files controlled by those df files. [fullFName: ROPE, attachedTo: ROPE, created: GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOL] [fullFName: ROPE, attachedTo: ROPE, created: BasicTime.GMT, bytes: INT, keep: CARDINAL, fileType: FileType] RETURNS [continue: BOOL] This is likely to be the controlling file entry for an IFS (or it could just be a bogus file to be ignored) Phase2, copy files. Don't change the entries (except for the 'moved' field). all names in unix format form all names in unix format form must map to all lower case now make up case file name, starting from foo2 assumes in slash format, lower case already pads out to a Tar.TBLOCK boundary Translates the server name into the server name specified for the source. If no such translation is present, returns NIL. [data: RedBlackTree.UserData] RETURNS [RedBlackTree.Key] [k: RedBlackTree.Key, data: RedBlackTree.UserData] RETURNS [Basics.Comparison] [ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison] [cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL] CommandObject = [in, out, err: STREAM, commandLine, command: ROPE, ...] When parsing the command line, be prepared for failure. The error is reported to the user Each argument is an argument to be processed. The first argument (argv[0]) is not examined, because by convention it is the name of the command as given by the user. It is a good idea to periodically check for a process abort request. Ignore null arguments (it is not easy to generate them, even). next two args are a pseudoServer and its translation Initialization fill the nullBuffer with null's, for later use Κ9˜codešœ™Kšœ<™™>—šœœ˜ KšœB™B—šœ œ˜KšœA™A—šœ œ˜Kšœ™—Kšœœ˜Kšœœ˜šœœ˜ K™—šœœ˜ K™*—šœ˜K™)—K˜—Kšœœ'˜;—™ Kšœœ;˜GKšœ œ₯˜΄Kšœ œ§˜΅—šœ™šœ œ˜Kšœ/™/—šœœ˜Kšœ#™#—Kš œ œœœœ˜.K™—™š Οnœ œœ œœœ˜`Kšœœœ˜Kšœœ˜3Kšœœœ˜ Kšœœ˜9K•StartOfExpansion@[getKey: RedBlackTree.GetKey, compare: RedBlackTree.Compare]šœR˜RKšœ œ˜Kšœ1˜1Kšœ˜šœœœ˜K™—–> -- [data: RedBlackTree.UserData] RETURNS [stop: BOOL _ FALSE]šΟb œ˜$KšΠck:™:K™4šœœ˜šœ˜Kšœœ˜1Kšœ˜K˜—Kšœœ˜—K˜K˜—š  œœ˜*Kšœœœ˜$Kšœœ˜Kšœ˜Kšœœ˜Kšœ œ"˜3šœœ'œ˜3Kšœ.œ˜4Kšœ(˜(KšœKœ˜QK˜—Kšœ˜K˜Kšœ˜Kšœ7˜7Kšœƒ˜ƒKšœœ˜4Kšœ˜KšœG˜GKšœ˜Kšœ3Ÿ˜DKšœ˜Kšœ>™>Kšœ˜Kš œQœœœœœ˜}šœœœ˜KšœŸ+˜?K˜šœ˜Kšœ„˜„Kšœœ˜4Kšœ˜K˜——Kšœ'˜'KšœG˜GKšœ˜Kšœ˜K˜)K˜K˜—š  œœœœ˜,KšœXΟsœ£œ ™†Kšœœ˜Kšœœ˜Kšœœœ˜Kšœ˜šœ"œ˜1Kšœœœœ˜5Kšœ˜—šœ1œ0˜cš œ œœ"œœœ˜„Kšœ9˜9Kšœœ˜ Kšœ˜ K˜Kšœœ˜ ——Kšœ$˜$Kšœ œœœ˜šœ"œ˜1šœ˜Kšœœœ˜!Kšœ&˜&K˜—Kšœ˜—šœœ˜Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ˜—Kšœ˜Kšœ'˜'KšœI˜IKšœ@˜@K˜K˜—š  œœœœ œœœœœ˜“šΠbnœ!˜)šœœ˜KšœœH˜Pšœœ˜#Kšœœ"˜,Kš œœœ œœœ˜IKšœ œœ˜"Kšœ˜K˜—šœœ˜&Kšœœ%˜/Kšœ˜Kšœ3˜3K˜—šœ œŸ˜HK˜—Kšœ œ Ÿ&˜B—K˜K˜—Kšœœœ˜Kšœ˜ Kšœœ!˜,šœœ ˜Kšœ˜šœ˜š œœ œœ œ˜CKšœ^˜^Kšœ˜ Kšœ˜ K˜——Kšœ˜ Kšœ˜—šœœœ˜Kšœ˜šœ1Ÿ(˜YKšœœ˜—Kšœ ˜ Kšœ˜—K˜K˜—K™K˜Kšœ œ˜Kšœ œ˜K˜Kšœœ˜$Kšœœ˜$K˜šœ˜K˜—Kšœ2™2KšœK˜Kšœ˜šœ ˜ K™7Kšœa˜aK˜—šœ ˜ KšŸY™YK–j -- [fullFName: ROPE, attachedTo: ROPE, created: GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOL]šœ œ˜#Kšœœ˜.š‘ œœ ˜Kš’f™fKšœ˜Kšœ œ˜–O[s1: ROPE, pos1: INT _ 0, s2: ROPE, pos2: INT _ 0, case: BOOL _ TRUE]šœœ%œœ˜8Kšœ^˜^Kšœ˜K˜—Kšœ^˜^K˜—Kšœ:˜K˜—šœœ˜K˜——Kšœ˜Kšœ œœŸ˜?K˜Kšœ œ*œ œœœœœ˜}šœ œœŸ/˜GKšœ)Ÿ˜=Kšœ˜Kšœœ˜Kšœœ˜Kšœœ ˜1Kšœ8˜8Kšœ+˜+Kšœ/˜/Kšœ'˜'Kšœ6˜6Kšœ7˜7Kšœ˜KšœH˜HK˜—Kšœ3˜3K˜KšœM™MKšœE˜EKšœ3˜3K™K˜1Kšœ˜KšœŸ˜%Kšœl˜lKšœ8˜8K˜K˜—š   œœœœœœ˜=Kšœœœœœ œ$œ˜uK˜K˜—š   œœœœ œ˜JK™Kš œœ'œœœœ˜EKšœ/˜/K˜K˜—š  œœ'˜6K™Kšœœ˜Kšœœ˜K˜3Kšœ œ˜Kšœ œ˜Kšœœ˜)Kšœ_œ˜iKšœœ˜0KšœO˜OKšœ(˜(Kšœ>˜>Kšœ™K˜šœœœ˜%KšœD˜DKšœ˜—Kšœ,˜,Kšœ`˜`K™.Kšœ#˜#K˜šœœœ˜#KšœD˜DKšœ˜—KšœG˜Gšœœœ ˜/Kšœ7˜7Kšœ˜—Kšœ0˜0Kšœl˜lK˜K˜—š  œœœœœ˜@K™+Kšœœ˜Kšœ˜K˜K˜—Kš œ œœœœœ˜)Kš œ œœœœœ˜)K˜š œœ œ˜.Kšœœ ™!Kšœœ˜ š˜Kšœ*œ œœ˜FKšœ˜šœ œœ˜Kšœ2˜2Kšœ˜K˜—Kš˜—K˜—K˜š œœ œœ˜RKšœœœ%˜PKšœ˜KšœLŸ˜\K˜K˜—š œœ œ œ˜GKšœœ˜Kšœœ˜šœœœœ˜ KšœŸ ˜2Kšœ˜KšœŸ˜—Kšœ<Ÿ˜UKšœœ˜&Kšœœ ˜Kš œœœ œœ˜CK˜K˜—š   œœœœœ˜Cšœœ˜"Kšœœ˜&Kšœœ ˜*Kšœœ˜'Kšœ œ$˜4Kšœœœœ˜&šœœœœ˜1Kšœœ œ-˜Wšœ:œ˜XKšœ˜!—Kšœ!˜'K˜—K˜—Kšœ˜K˜K˜—š  œœœœœ˜JKšœz™zKšœ)œœœ˜SKšœœ˜ K˜K˜—–< -- [data: RedBlackTree.UserData] RETURNS [RedBlackTree.Key]š œ˜Kš’8™8Kšœ˜K˜K˜—–R -- [k: RedBlackTree.Key, data: RedBlackTree.UserData] RETURNS [Basics.Comparison]š œ˜!Kš’N™NKšœœœ˜šœœ˜Kšœ!˜!Kšœœ˜Kšœœ˜—šœœ˜Kšœœœ˜>Kšœ˜—Kšœ˜K˜K˜—–> -- [ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]š œ˜$Kš’:™:šœœ˜šœ˜šœœ˜šœ˜Kšœ%œ˜3—Kšœ˜——Kšœ˜—Kšœ˜K˜K˜—š  œB˜OK˜—š  œA˜MK˜—š  œI˜SK˜—š  œœ(œ œœ˜Wš œœ œœœœ™:Kšœœœ™G—Kšœœ ˜–I[cmd: Commander.Handle, starExpand: BOOL _ FALSE, switchChar: CHAR]šœK˜PKšœ)œœ ˜8KšœZ™ZK˜—Kšœœ"˜4Kšœœ˜ K˜šœ˜Kšœ¦™¦Kšœœ ˜˜KšœD™D—šœœœ˜-Kšœ>™>—šœ˜˜K™4Kšœ$˜$Kšœ#˜#Kšœ˜Kšœœœ˜&Kšœ ˜ Kšœ˜K˜—KšœœŸ%˜1šœ˜K˜——šœœ˜Kšœœ˜5Kšœœ˜+šœœ˜'šœœœ˜#Kšœœ5˜=Kšœœ˜ K˜—Kšœ˜K˜—šœœ˜#šœœœ˜#Kšœœ>˜FKšœœ˜ K˜—Kšœ˜K˜—šœœ˜Kšœœ-˜5Kšœœ˜ K˜—Kšœ˜K˜—K˜ K˜Kšœ˜K˜—Kšœœi˜|KšœT˜TKšœL˜Lšœœ ˜Kšœd˜dKšœ˜ K˜—K˜š˜Kšœ˜—K˜K˜——–x[key: ROPE, proc: Commander.CommandProc, doc: ROPE _ NIL, clientData: REF ANY _ NIL, interpreted: BOOL _ TRUE]™šœ[œ˜aK˜—šœ^œ˜dK˜—Kšœ[œ˜aK˜šœRœ˜XK˜—šœTœ˜ZK˜—šœNœ˜TK˜—Kšœ.™.šœ˜Kš œœœœœœ˜