-- Copyright (C) 1984 by Xerox Corporation. All rights reserved. -- HeapScanTool.mesa -- HGM, 19-Nov-84 16:10:19 -- Brenda Hankins 12-Sep-84 17:12:43 -- might try adding ability to specify ObjectDirFile (or rebuild it if have chain) but do this later. DIRECTORY File USING [File, nullFile], FormSW USING [ AllocateItemDescriptor, BooleanItem, ClientItemsProcType, CommandItem, DisplayItem, newLine, nextPlace, NumberItem, ProcType, StringItem], Heap USING [systemZone], HeapFile USING [ChainBlock, headerSize, noSegment, segmentsPerPage, segmentSize, SerialAndHead], HeapXDefs USING [ObjectHeader, PageHeader], Inline USING [LongCOPY, LowHalf], MFile USING [Error, GetLength, Handle, ReadOnly, Release], ObjectDirDefs USING [ObjectNumber, ObjectType], Put USING [CR, Decimal, Line, LongDecimal, Text], Space USING [InsufficientSpace, Interval, Kill, Map, nullInterval], SpecialMFile USING [GetCapaWithAccess], Tool USING [ Create, MakeFileSW, MakeFormSW, MakeMsgSW, MakeSWsProc, UnusedLogName], ToolWindow USING [TransitionProcType], VMDefs USING [PageIndex,PageNumber, pageSize], Window USING [Box, Handle]; HeapScanTool: PROGRAM IMPORTS FormSW, Heap, Inline, MFile, Put, Space, SpecialMFile, Tool SHARES HeapFile, ObjectDirDefs = BEGIN -- TYPES: CompactPageFormat: TYPE = RECORD[ offset: LONG CARDINAL, hdrs: SEQUENCE length: [0..VMDefs.pageSize] OF HeapXDefs.ObjectHeader ]; CompactHeapFormat: TYPE = RECORD[ pages: SEQUENCE length: [1..LAST[CARDINAL]] OF LONG POINTER TO CompactPageFormat]; --VARIABLES: toolData: MACHINE DEPENDENT RECORD [ -- tool window stuff: msgSW(0): Window.Handle ← NIL, fileSW(2): Window.Handle ← NIL, formSW(4): Window.Handle ← NIL, -- vars: heapFileName(6): LONG STRING, chainFileName(8): LONG STRING, segmentNo(10): CARDINAL, logicalSegNo(11): CARDINAL, segmentBound(12): CARDINAL, -- last valid real segment no. logicalVsReal(13): BOOLEAN, chain(14): LONG POINTER TO HeapFile.ChainBlock, chainSpace(16): Space.Interval, objNoPage(20): CARDINAL, objNoType(21): CARDINAL, objNoIndex(22): CARDINAL, compactHeapFormat(23): LONG POINTER TO CompactHeapFormat]; --**************************************************************************** OpenFiles: FormSW.ProcType = BEGIN heapFileHandle: MFile.Handle ← NIL; BEGIN ENABLE BEGIN MFile.Error => CONTINUE; Space.InsufficientSpace => BEGIN Put.Line[toolData.msgSW, "Space.InsufficientSpace (need 6 pages)."L]; IF heapFileHandle # NIL THEN MFile.Release[heapFileHandle]; CONTINUE; END; END; --looks for the files in Mesa File System. heapFile: File.File; heapSize, chainSize: CARDINAL; IF toolData.heapFileName # NIL THEN BEGIN heapFileHandle ← MFile.ReadOnly[toolData.heapFileName, [NIL, NIL] ! MFile.Error => Put.Line[toolData.msgSW, "MFile error on open of data file."L] ]; heapFile ← SpecialMFile.GetCapaWithAccess[heapFileHandle]; Put.Line[toolData.fileSW, "Data file opened."L]; heapSize ← Inline.LowHalf[ MFile.GetLength[heapFileHandle]/512--bytes/pg--]; chainSize ← ((heapSize/HeapFile.segmentSize) + HeapFile.segmentsPerPage-1) / HeapFile.segmentsPerPage; toolData.segmentBound ← (heapSize/HeapFile.segmentSize) + chainSize*HeapFile.headerSize - 1; CreateDataStructure[heapSize, heapFile]; END ELSE Put.Line[toolData.msgSW, "No data file specified."L]; IF heapFileHandle # NIL AND toolData.chainFileName # NIL THEN BEGIN tempSize: CARDINAL ← 0; chainFileHandle: MFile.Handle ← MFile.ReadOnly[ toolData.chainFileName, [NIL, NIL] ! MFile.Error => Put.Line[toolData.msgSW, "MFile error on open of chain file."L] ]; chainFile: File.File ← SpecialMFile.GetCapaWithAccess[chainFileHandle]; Put.Line[toolData.fileSW, "Chain file opened."L]; IF heapSize > chainSize*2 THEN Put.Line[toolData.msgSW, "Warning: size of chainfile is larger than that of Heap."L]; IF heapSize < chainSize*2 THEN Put.Line[toolData.msgSW, -- must stop "Size of chainfile is smaller than that of Heap - you should quit."L]; -- build chain data structure: toolData.chainSpace ← Space.Map[ window: [file: File.nullFile, base: 0, count: chainSize]]; toolData.chain ← toolData.chainSpace.pointer; FOR index: CARDINAL IN [0..chainSize) DO tempSpace: Space.Interval; chain0, chain1, chainChoice: LONG POINTER TO HeapFile.SerialAndHead; -- look at the chain in duplicate page pairs and use the 'correct' one. tempSpace ← LOOPHOLE[Space.Map[ window: [chainFile, index*2+1, 2], access: readOnly]]; -- remember to skip over leader page chain0 ← LOOPHOLE[tempSpace.pointer]; chain1 ← LOOPHOLE[chain0+VMDefs.pageSize]; chainChoice ← IF chain1.serialNumber > chain0.serialNumber THEN chain1 ELSE chain0; Inline.LongCOPY[from: chainChoice, nwords: VMDefs.pageSize, to: toolData.chain + index*VMDefs.pageSize]; Space.Kill[tempSpace]; ENDLOOP; IF toolData.chain.header[0].chainHead = HeapFile.noSegment THEN Put.Line[toolData.msgSW, "There are no segments in chain (head pts to end)."L]; FOR s: CARDINAL ← toolData.chain.header[0].chainHead, toolData.chain.next[s] UNTIL s = HeapFile.noSegment DO IF tempSize > toolData.segmentBound THEN { Put.Line[toolData.fileSW, "Chain has a circularity, fix the file with the ChainCruiser tool prior to attempting to do any logical operations."L]; EXIT }; tempSize ← tempSize+1; ENDLOOP; IF chainFileHandle # NIL THEN MFile.Release[chainFileHandle]; END ELSE Put.Line[toolData.msgSW, "No chain file specified."L]; -- cleanup: IF heapFileHandle # NIL THEN MFile.Release[heapFileHandle]; END; -- enabled END; -- proc. OpenFiles CreateDataStructure: PROCEDURE[heapSize: CARDINAL, heapFile: File.File] = BEGIN segmentSpace: Space.Interval ← Space.nullInterval; objHdrSize: CARDINAL ← SIZE[HeapXDefs.ObjectHeader]; toolData.compactHeapFormat ← Heap.systemZone.NEW[CompactHeapFormat[heapSize]]; FOR segment: CARDINAL ← 3 -- first real segment --, segment+1 UNTIL segment > toolData.segmentBound DO position: CARDINAL; toolData.segmentNo ← segment; position ← Position[]; -- actual file page (1..length) IF position = HeapFile.noSegment THEN LOOP; segmentSpace ← Space.Map[window: [heapFile, position, 6], access: readOnly]; FOR page: CARDINAL ← 0, page+1 UNTIL page = 6 DO noOfHdrsOnThisPage, hdrCounter: CARDINAL ← 0; pagePtr: LONG POINTER ← (segmentSpace.pointer+page*256); currentPos: VMDefs.PageIndex ← SIZE[HeapXDefs.PageHeader]; offset: LONG POINTER TO LONG CARDINAL ← pagePtr; hdr: LONG POINTER TO HeapXDefs.ObjectHeader ← pagePtr + currentPos; DO -- UNTIL end of page noOfHdrsOnThisPage ← noOfHdrsOnThisPage+1; currentPos ← currentPos + objHdrSize; IF currentPos+hdr.size+objHdrSize > LAST[VMDefs.PageIndex] THEN EXIT ELSE currentPos ← currentPos + hdr.size; hdr ← pagePtr + currentPos; ENDLOOP; toolData.compactHeapFormat[position+page] ← Heap.systemZone.NEW[ CompactPageFormat[noOfHdrsOnThisPage] ]; -- reset values to loop again: pagePtr ← (segmentSpace.pointer+page*256); currentPos ← SIZE[HeapXDefs.PageHeader]; hdr ← pagePtr + currentPos; toolData.compactHeapFormat[position+page].offset ← offset↑; DO -- UNTIL end of page toolData.compactHeapFormat[position+page].hdrs[hdrCounter] ← hdr↑; currentPos ← currentPos + objHdrSize; IF currentPos+hdr.size+objHdrSize > LAST[VMDefs.PageIndex] THEN EXIT ELSE currentPos ← currentPos + hdr.size; hdr ← pagePtr + currentPos; hdrCounter ← hdrCounter+1; ENDLOOP; ENDLOOP; Space.Kill[segmentSpace]; ENDLOOP; END; -- proc. CreateDataStructure --**************************************************************************** ShowObjects: FormSW.ProcType = BEGIN position: CARDINAL ← Position[]; IF position = HeapFile.noSegment THEN RETURN; -- Search the pages for (and print out) all object headers in segment. FOR page: CARDINAL ← 0, page+1 UNTIL page = 6 DO Put.Text[toolData.fileSW, "Page Number "L]; Put.Decimal[toolData.fileSW, position+page]; -- ignore leader page Put.Text[toolData.fileSW, " (in file)"L]; Put.CR[toolData.fileSW]; FOR i: CARDINAL IN [0..toolData.compactHeapFormat[position+page].length) DO PrintObjectHdr[toolData.compactHeapFormat[position+page].hdrs[i]]; IF i = 0 THEN -- first obj. { Put.Text[toolData.fileSW, " offset: "L]; Put.LongDecimal[toolData.fileSW, toolData.compactHeapFormat[position+page].offset] }; Put.CR[toolData.fileSW]; ENDLOOP; ENDLOOP; END; --~~~~~~~~ CheckSegmentNumber: PROCEDURE [of: CARDINAL] RETURNS [ok: BOOLEAN ← TRUE] = BEGIN -- checks to see that it's a valid segment number. SELECT of MOD VMDefs.pageSize FROM 0, 1, 2 => ok ← FALSE; ENDCASE; IF ok THEN IF of > toolData.segmentBound THEN ok ← FALSE; IF NOT ok THEN Put.Line[toolData.msgSW, "Invalid Segment Number"L]; END; Position: PROCEDURE RETURNS [CARDINAL] = BEGIN -- this must take into account diff in size of page numbers. IF toolData.logicalVsReal THEN { toolData.segmentNo ← LogicalToReal[]; FormSW.DisplayItem[toolData.formSW, tIndex.segmentNo.ORD] }; IF toolData.segmentNo = HeapFile.noSegment OR (~toolData.logicalVsReal AND ~CheckSegmentNumber[toolData.segmentNo]) THEN RETURN[HeapFile.noSegment]; RETURN[ ( ( toolData.segmentNo - ( (toolData.segmentNo+VMDefs.pageSize)/VMDefs.pageSize) * (HeapFile.headerSize)) * HeapFile.segmentSize) + 1 -- skip leader page --]; END; LogicalToReal: PROC RETURNS [real: CARDINAL ← HeapFile.noSegment] = BEGIN -- head of chain is logical 1. counter: CARDINAL ← 1; FOR s: CARDINAL ← toolData.chain.header[0].chainHead, toolData.chain.next[s] UNTIL s = HeapFile.noSegment DO IF counter > toolData.segmentBound THEN { Put.Line[toolData.fileSW, "Error, chain needs to be fixed."L]; EXIT }; IF counter = toolData.logicalSegNo THEN { real ← s; EXIT }; counter ← counter+1; ENDLOOP; END; --~~~~~~~~ PrintObjectHdr: PROCEDURE[objectHdr: HeapXDefs.ObjectHeader] = BEGIN -- [ size: y no: [page no: a type: b pageIndex: c] ] Put.Text[toolData.fileSW, " [ size: "L]; Put.Decimal[toolData.fileSW, objectHdr.size]; Put.Text[toolData.fileSW, " no: [page no: "L]; Put.Decimal[toolData.fileSW, objectHdr.number.page]; Put.Text[toolData.fileSW, " type: "L]; Put.Decimal[toolData.fileSW, objectHdr.number.type.ORD]; Put.Text[toolData.fileSW, " pageIndex: "L]; Put.Decimal[toolData.fileSW, objectHdr.number.index]; Put.Text[toolData.fileSW, "] ]"L]; END; --**************************************************************************** NextSegment: FormSW.ProcType = BEGIN IF toolData.logicalVsReal THEN { toolData.segmentNo ← toolData.chain.next[toolData.segmentNo]; toolData.logicalSegNo ← toolData.logicalSegNo+1; FormSW.DisplayItem[toolData.formSW, tIndex.logicalSegNo.ORD] } ELSE BEGIN toolData.segmentNo ← toolData.segmentNo+1; SELECT toolData.segmentNo MOD VMDefs.pageSize FROM 0 => toolData.segmentNo ← toolData.segmentNo+3; 1 => toolData.segmentNo ← toolData.segmentNo+2; 2 => toolData.segmentNo ← toolData.segmentNo+1; ENDCASE; END; FormSW.DisplayItem[toolData.formSW, tIndex.segmentNo.ORD]; IF toolData.segmentNo = HeapFile.noSegment OR toolData.segmentNo > toolData.segmentBound THEN Put.Line[toolData.msgSW, "No more segments."L] ELSE ShowObjects[]; END; --**************************************************************************** ShowSegmentContents: FormSW.ProcType = BEGIN position: LONG CARDINAL ← Position[]*256; IF position = HeapFile.noSegment THEN RETURN; -- bad segmentNo. Put.Text[toolData.fileSW, "This cmmd should show the actual bits in the segment but is unimplemented, use the OctalReadTool to look at the six pages at word "L]; Put.LongDecimal[toolData.fileSW, position]; -- word position in file Put.CR[toolData.fileSW]; -- might later want to display bits myself. END; --**************************************************************************** FindObjHdr: FormSW.ProcType = BEGIN -- take obj hdr and search thru all segments for instances of it. ENABLE Space.InsufficientSpace => { Put.Line[toolData.msgSW, "Space.InsufficientSpace (need 6 pages)."L]; CONTINUE }; objHdrSize, tempID: CARDINAL; match: ObjectDirDefs.ObjectNumber; SearchSegment: PROCEDURE = BEGIN position: CARDINAL; toolData.segmentNo ← tempID; FormSW.DisplayItem[toolData.formSW, tIndex.segmentNo.ORD]; position ← Position[]; IF position = HeapFile.noSegment THEN RETURN; -- bad SegmentNo. FOR page: CARDINAL ← 0, page+1 UNTIL page = 6 DO FOR i: CARDINAL IN [0..toolData.compactHeapFormat[position+page].length) DO IF toolData.compactHeapFormat[position+page].hdrs[i].number = match THEN BEGIN Put.Text[toolData.fileSW, "Page Number "L]; Put.Decimal[toolData.fileSW, position+page]; -- ignore leader page Put.CR[toolData.fileSW]; PrintObjectHdr[toolData.compactHeapFormat[position+page].hdrs[i]]; IF i = 0 THEN -- first obj. { Put.Text[toolData.fileSW, " offset: "L]; Put.LongDecimal[toolData.fileSW, toolData.compactHeapFormat[position+page].offset] }; Put.CR[toolData.fileSW]; END; ENDLOOP; ENDLOOP; END; -- proc. SearchSegment IF toolData.objNoIndex > VMDefs.PageNumber.LAST OR toolData.objNoType > ObjectDirDefs.ObjectType.LAST.ORD THEN { Put.Line[toolData.msgSW, "Illegal Obj number."L]; RETURN }; objHdrSize ← SIZE[HeapXDefs.ObjectHeader]; match ← [toolData.objNoPage, 0, LOOPHOLE[toolData.objNoType], toolData.objNoIndex]; IF toolData.logicalVsReal THEN -- search in chain order BEGIN toolData.segmentNo ← LogicalToReal[]; FormSW.DisplayItem[toolData.formSW, tIndex.segmentNo.ORD]; toolData.logicalSegNo ← toolData.logicalSegNo-1; --so display will be correct FOR tempID ← toolData.segmentNo, toolData.chain.next[tempID] UNTIL tempID = HeapFile.noSegment DO toolData.logicalSegNo ← toolData.logicalSegNo+1; FormSW.DisplayItem[toolData.formSW, tIndex.logicalSegNo.ORD]; SearchSegment[]; ENDLOOP END ELSE -- search thru file in order of pages: FOR tempID ← toolData.segmentNo, tempID+1 UNTIL tempID > toolData.segmentBound DO SearchSegment[]; ENDLOOP; END; -- proc. FindObjHdrs --**************************************************************************** CheckForLogicalAllowed: FormSW.ProcType = BEGIN IF toolData.chain # NIL THEN RETURN; toolData.logicalVsReal ← FALSE; FormSW.DisplayItem[sw, index]; Put.Line[toolData.msgSW, "Logical values not allowed w/o Chain File."L]; END; --************************************************************************** HelpProc: FormSW.ProcType = BEGIN Put.CR[toolData.fileSW]; Put.Line[toolData.fileSW, "Specify file name(s) and bug OpenFiles to set up session. (There is no corresponding CloseFiles as deactivating the tool will clean up everything)"L]; Put.CR[toolData.fileSW]; Put.Line[toolData.fileSW, "You need not have a chain file. But if you do, then can specify logical segment indices as well as real (so can step thru only what's on chain when searching and in chain order) 1 is logical head of chain (corresponding real segment indices will be displayed in Real Segments field)."L]; Put.CR[toolData.fileSW]; Put.Line[toolData.fileSW, "This tool claims to work on only Grapevine databases."L]; Put.CR[toolData.fileSW]; Put.Line[toolData.fileSW, "SegmentNo identifies the place in the file that any operation will start. IF LogicalvsReal is true, the LogicalSegmentNo will be used to find the value that corresponds to that logical position in the chain (clearly must have a chain file to use this) and the corresponding real segment no. will be displayed in RealSegmentNo field. If it is not true, then RealSegmentNo will be used. WARNING: it's not clear that use of and updating of these two fields (logical and real) is completely correct at moment so pay careful attention to what you are doing and inspect all results. Notify me (BLH) if you notice any problems."L]; Put.CR[toolData.fileSW]; Put.Line[toolData.fileSW, "ShowObjects will display all the object headers in the six pages of the given segment number. A brief explanation of the format follows this description."L]; Put.CR[toolData.fileSW]; Put.Line[toolData.fileSW, "NextSegment will increment the segment number (either next in file or next on chain) and do a ShowObjects."L]; Put.CR[toolData.fileSW]; Put.Line[toolData.fileSW, "ShowSegmentContents will show the actual bits in the given segments pages."L]; Put.CR[toolData.fileSW]; Put.Line[toolData.fileSW, "FindObjectHdrs will search (from given segment number) for all object headers which match the specified values (the following three fields in the formSW) and print out their location in file."L]; Put.CR[toolData.fileSW]; Put.CR[toolData.fileSW]; Put.Line[toolData.fileSW, "Object Header Format:"L]; Put.Line[toolData.fileSW, " offset: the word offset into the object that the first word"L]; Put.Line[toolData.fileSW, " of this sub object represents (0 is objectStart)."L]; Put.Line[toolData.fileSW, " size: number of words (of this object) to follow"L]; Put.Line[toolData.fileSW, " in this same page (determines length of subObj and if"L]; Put.Line[toolData.fileSW, " it doesn't fill up the page, another subObj will follow)."L]; Put.Line[toolData.fileSW, " objNo:"L]; Put.Line[toolData.fileSW, " pageNo: page in ObjDir table."L]; Put.Line[toolData.fileSW, " type: type of object, check an interface for def'n of types."L]; Put.Line[toolData.fileSW, " (MS 18-22, CHS 6-12)"L]; Put.Line[toolData.fileSW, " pageIndex: index into page in ObjDir table."L]; END; -- proc. HelpProc --************************************************************************** -- Tool needed routines: ClientTransition: ToolWindow.TransitionProcType = BEGIN SELECT TRUE FROM new = active => BEGIN toolData.heapFileName ← NIL; toolData.chainFileName ← NIL; toolData.segmentNo ← 3; -- real seg which corresponds to 1st data page toolData.logicalSegNo ← 1; -- head of chain. toolData.logicalVsReal ← FALSE; toolData.chainSpace ← Space.nullInterval; toolData.chain ← NIL; toolData.objNoPage ← 0; toolData.objNoType ← 0; toolData.objNoIndex ← 0; toolData.compactHeapFormat ← NIL; END; old = active => BEGIN IF toolData.chainSpace # Space.nullInterval THEN Space.Kill[toolData.chainSpace]; IF toolData.compactHeapFormat # NIL THEN BEGIN FOR i: CARDINAL IN [1..toolData.compactHeapFormat.length] DO IF toolData.compactHeapFormat.pages[i] # NIL THEN Heap.systemZone.FREE[@toolData.compactHeapFormat.pages[i]]; ENDLOOP; Heap.systemZone.FREE[@toolData.compactHeapFormat]; END; END; ENDCASE; END; -- proc. ClientTransition --*************************************************************************** MakeTool: PROCEDURE [initialBox: Window.Box] = BEGIN window: Window.Handle ← Tool.Create[ makeSWsProc: MakeSWs, initialState: default, initialBox: initialBox, clientTransition: ClientTransition, name: "HeapScanTool"L, tinyName1: "Heap"L, tinyName2: "ScanTool"L]; END; -- proc. MakeTool --****************************************************************************** tIndex: TYPE = {heapFileName, chainFileName, openFiles, segmentNo, logicalSegNo, logicalVsReal, showObjects, nextSegment, showSegmentContents, help, findObjHdr, objNoPage, objNoType, objNoIndex}; noToolIndices: CARDINAL = tIndex.LAST.ORD+1; MakeCommon: FormSW.ClientItemsProcType = BEGIN OPEN FormSW; items ← AllocateItemDescriptor[noToolIndices]; items[tIndex.heapFileName.ORD] ← StringItem [tag: "Data File Name"L, place: newLine, string: @toolData.heapFileName, inHeap: TRUE]; items[tIndex.chainFileName.ORD] ← StringItem [tag: "Chain File Name"L, place: newLine, string: @toolData.chainFileName, inHeap: TRUE]; items[tIndex.openFiles.ORD] ← CommandItem [tag: "OpenFiles"L, proc: OpenFiles, place: newLine]; items[tIndex.segmentNo.ORD] ← NumberItem[tag: "Real Segment No"L, value: @toolData.segmentNo, radix: decimal, place: newLine]; items[tIndex.logicalSegNo.ORD] ← NumberItem[tag: "Logical Segment No"L, value: @toolData.logicalSegNo, radix: decimal, place: nextPlace]; items[tIndex.logicalVsReal.ORD] ← BooleanItem [tag: "Logical vs Real"L, switch: @toolData.logicalVsReal, proc: CheckForLogicalAllowed, place: nextPlace]; items[tIndex.showObjects.ORD] ← CommandItem [tag: "Show Objects"L, proc: ShowObjects, place: newLine]; items[tIndex.nextSegment.ORD] ← CommandItem [tag: "Next Segment"L, proc: NextSegment, place: nextPlace]; items[tIndex.showSegmentContents.ORD] ← CommandItem [tag: "Show Segment Contents"L, proc: ShowSegmentContents, place: nextPlace]; items[tIndex.help.ORD] ← CommandItem [tag: "Help"L, proc: HelpProc, place: nextPlace]; items[tIndex.findObjHdr.ORD] ← CommandItem [tag: "FindObjHdr"L, proc: FindObjHdr, place: newLine]; items[tIndex.objNoPage.ORD] ← NumberItem[tag: "ObjNoPage"L, value: @toolData.objNoPage, radix: decimal, place: nextPlace]; items[tIndex.objNoType.ORD] ← NumberItem[tag: "ObjNoType"L, value: @toolData.objNoType, radix: decimal, place: nextPlace]; items[tIndex.objNoIndex.ORD] ← NumberItem[tag: "ObjNoIndex"L, value: @toolData.objNoIndex, radix: decimal, place: nextPlace]; RETURN[items: items, freeDesc: TRUE]; END; -- proc. MakeCommon. --**************************************************************************** MakeSWs: Tool.MakeSWsProc = BEGIN logName: STRING ← [40]; Tool.UnusedLogName[unused: logName, root: "HeapScanTool.log"L]; toolData.msgSW ← Tool.MakeMsgSW[window: window, h: 32]; toolData.formSW ← Tool.MakeFormSW[window: window, formProc: MakeCommon, h: 96]; toolData.fileSW ← Tool.MakeFileSW[window: window, name: logName, h:350]; END; --************************************************************************ -- mainline code: MakeTool[[[0, 35],[482,650]]]; -- change size END. LOG: 4-Sep-84 12:31:29 converted from MSScanTool