1
0
mirror of https://github.com/pkimpel/retro-b5500.git synced 2026-02-12 03:07:30 +00:00
Files
pkimpel.retro-b5500/tools/B5500LibMaintDir.html
paul.kimpel@digm.com ef566d4447 1. Release emulator version 0.09.
2. Implement differential read vs. write memory cycle timing (2us vs 4us).
3. Implement setImmediate() instead of SetTimeout() for I/O forking in IOUnit.
4. Time I/Os from initiation in IOUnit instead of peripheral device driver.
5. Accumulate Control and Normal state clocks separately for use in UI displays.
6. Rework Processor.schedule() delay management for use with setImmediate()
7. Accumulate average processor delay delta for use in UI displays.
8. Enable third I/O unit and all eight memory modules in B5500SystemConfiguration.
9. Clone B5500LibMaintDir.html from B5500LibMaintMapper.html to list files on a tape image.
10. Implement multiple-file selection in B5500CardReader; fix markup for Firefox 22.
11. Implement more granular Control/Normal State light intensity and PA Delay delta in B5500Console.
12. Attempt (again) to fix creation of SYSTEM/LOG file in B5500ColdLoader.
13. Create tools/LOG-MAKER.job deck to create SYSTEM/LOG on a running emulator instance.
2013-07-08 04:27:51 +00:00

617 lines
22 KiB
HTML

<!DOCTYPE html>
<head>
<title>B5500 LibMaint Directory</title>
<meta name="Author" content="Paul Kimpel">
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<meta http-equiv="Content-Style-Type" content="text/css">
<script>
/***********************************************************************
* retro-b5500/tools B5500LibMaintDir.html
************************************************************************
* Copyright (c) 2013, Paul Kimpel.
* Licensed under the MIT License,
* see http://www.opensource.org/licenses/mit-license.php
************************************************************************
* B5500 Library Maintenance tape file direectory list.
*
* This script reads a Burroughs B5500 Library/Maintenance tape as one
* large blob and outputs directory information for all files.
*
* The blob is assumed to be in the so-called ".bcd" format. Each 7-bit frame
* from the tape is represented as one 8-bit unsigned byte. The low-order six
* bits (mask 0x3F) contain the character value. The next bit (mask 0x40) is
* the parity bit, and the high-order bit (mask 0x80) indicates the byte is
* at the start of a physical tape block. Tape marks (EOF) are indicated by a
* block containing a single 0x8F byte.
*
* The mapping process is driven by the tape directory at the beginning of
* the tape volume. Continuation "reels" are not currently supported.
*
* To use, select the .bcd file using the file selection control on the page.
* The script writes a log of activity to the web page.
************************************************************************
* 2013-06-06 P.Kimpel
* Original version, from B5500LibMaintMapper.html and B5500DiskDirList.html.
***********************************************************************/
"use strict";
window.onload = function() {
var panel = document.getElementById("TextPanel");
var tapeMark = 0x8F;
var tapeDir = [];
var tapeCtl = {
data: null,
offset: 0,
dataLength: -1,
eof: false,
eot: false,
blockCount: 0,
blockLength: 0};
var BICtoANSI = [
"0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", "#", "@", "?", ":", ">", "}",
"+", "A", "B", "C", "D", "E", "F", "G",
"H", "I", ".", "[", "&", "(", "<", "~",
"|", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "$", "*", "-", ")", ";", "{",
" ", "/", "S", "T", "U", "V", "W", "X",
"Y", "Z", ",", "%", "!", "=", "]", "\""];
var pow2 = [ // powers of 2 from 0 to 52
0x1, 0x2, 0x4, 0x8,
0x10, 0x20, 0x40, 0x80,
0x100, 0x200, 0x400, 0x800,
0x1000, 0x2000, 0x4000, 0x8000,
0x10000, 0x20000, 0x40000, 0x80000,
0x100000, 0x200000, 0x400000, 0x800000,
0x1000000, 0x2000000, 0x4000000, 0x8000000,
0x10000000, 0x20000000, 0x40000000, 0x80000000,
0x100000000, 0x200000000, 0x400000000, 0x800000000,
0x1000000000, 0x2000000000, 0x4000000000, 0x8000000000,
0x10000000000, 0x20000000000, 0x40000000000, 0x80000000000,
0x100000000000, 0x200000000000, 0x400000000000, 0x800000000000,
0x1000000000000, 0x2000000000000, 0x4000000000000, 0x8000000000000,
0x10000000000000];
function $$(id) {
return document.getElementById(id);
}
function bit(word, bit) {
/* Extracts and returns the specified bit from the word */
var e = 47-bit; // word lower power exponent
var p; // bottom portion of word power of 2
if (e > 0) {
return ((word - word % (p = pow2[e]))/p) % 2;
} else {
return word % 2;
}
};
function fieldIsolate(word, start, width) {
/* Extracts a bit field [start:width] from word and returns the field */
var le = 48-start-width; // lower power exponent
var p; // bottom portion of word power of 2
return (le == 0 ? word : (word - word % (p = pow2[le]))/p) % pow2[width];
};
function spout(text) {
/* Appends "text"+NL as a new text node to the panel DOM element */
var e = document.createTextNode(text + "\n");
panel.appendChild(e);
}
function clearPanel() {
/* Clears the text panel */
var kid;
while (kid = panel.firstChild) {
panel.removeChild(kid);
}
}
function parseNumber(s) {
/* Parses the string "s" as a base-10 number. Returns 0 if it is not a number */
var n = parseInt(s, 10);
return (isNaN(n) ? 0 : n);
}
function rtrim(s) {
/* Trims trailing spaces from "s" and returns the resulting string */
var m = s.match(/^(.*?) *$/);
return m[1];
}
function readTextBlock(ctl) {
/* Reads the next block from the tape, translating the character frames to ANSI
character codes and returning the data as a string. A block is terminated when
the next frame has its high-order bit set, or the end of the data is reached.
The string returned is always at least one character in length, unless the block
is a tapeMark (in which case the "eof" property is set) or the end of the data
has been reached (in which case the "eof" and "eot" properties are set) */
var c;
var data = ctl.data;
var limit = ctl.dataLength;
var text = "";
var x = ctl.offset;
if (x >= limit) {
ctl.eof = true;
ctl.eot = true;
ctl.blockLength = 0;
} else {
c = data.getUint8(x);
if (c == tapeMark) {
ctl.eof = true;
ctl.offset = x+1;
ctl.blockLength = 0;
} else {
do {
text += BICtoANSI[c & 0x3F];
if (++x < limit) {
c = data.getUint8(x);
} else {
c = tapeMark; // to kill the loop
}
} while (c < 128);
ctl.eof = false;
ctl.blockLength = x - ctl.offset;
ctl.offset = x;
ctl.blockCount++;
}
}
return text;
}
function readWordBlock(ctl) {
/* Reads the next block from the tape, translating the character frames to an array
of B5500 binary words and returning the array. A block is terminated when
the next frame has its high-order bit set, or the end of the data is reached.
The array returned is always at least one element in length, unless the block
is a tapeMark (in which case the "eof" property is set) or the end of the data
has been reached (in which case the "eof" and "eot" properties are set) */
var c;
var data = ctl.data;
var limit = ctl.dataLength;
var w = 0;
var words = [];
var wx = 0;
var x = ctl.offset;
if (x >= limit) {
ctl.eof = true;
ctl.eot = true;
ctl.blockLength = 0;
} else {
c = data.getUint8(x);
if (c == tapeMark) {
ctl.eof = true;
ctl.offset = x+1;
ctl.blockLength = 0;
} else {
do {
if (wx < 8) {
w = w*64 + (c & 0x3F);
wx++;
} else {
words.push(w);
w = c & 0x3F;
wx = 1;
}
if (++x < limit) {
c = data.getUint8(x);
} else {
c = tapeMark; // to kill the loop
}
} while (c < 128);
// Right-justify the last word as necessary
while (wx++ < 8) {
w *= 64;
}
words.push(w);
ctl.eof = false;
ctl.blockLength = x - ctl.offset;
ctl.offset = x;
ctl.blockCount++;
}
}
return words;
}
function readTapeLabel(ctl) {
/* Reads the next block from the tape and determines if it is a B5500 tape label.
If so, decodes the label into a label object and returns the object */
var rec;
var s;
var lab = {
isLabel: false,
text: "",
heading: "",
mfid: "",
fid: "",
reel: 0,
dateWritten:0,
cycle: 0,
datePurge: 0,
sentinel: 0,
blockCount: 0,
recordCount:0,
memdumpKey: 0,
tapeNumber: ""};
rec = readTextBlock(ctl);
if (!ctl.eof) {
lab.text = rec;
if (ctl.blockLength == 80 && (s = rec.substring(0, 8)) == " LABEL ") {
lab.isLabel = true;
lab.heading = s;
lab.mfid = rec.substring(9, 16);
lab.fid = rec.substring(17, 24);
lab.reel = parseNumber(rec.substring(24, 27));
lab.dateWritten = parseNumber(rec.substring(27, 32));
lab.cycle = parseNumber(rec.substring(32, 34));
lab.datePurge = parseNumber(rec.substring(34, 39));
lab.sentinel = parseNumber(rec.substring(39, 40));
lab.blockCount = parseNumber(rec.substring(40, 45));
lab.recordCount = parseNumber(rec.substring(45, 52));
lab.memdumpKey = parseNumber(rec.substring(52, 53));
lab.tapeNumber = rec.substring(53, 58);
}
}
return lab;
}
function readTapeDirectory(ctl) {
/* Reads the Lib/Maint tape directory and returns and array of file names, indexed
starting at 1. If the directory is invalid, returns an empty array */
var dir = [];
var done;
var fid;
var lab;
var lab2;
var mfid;
var rec;
var w;
var x;
lab = readTapeLabel(ctl);
if (ctl.eof) {
spout("TapeDir: EOF encountered when tape label expected, block=" + ctl.blockCount);
} else if (!lab.isLabel) {
spout(lab.text);
spout("TapeDir: Above block encountered when a tape label was expected, block=" + ctl.blockCount);
} else {
dir.push(rtrim(lab.mfid) + "/" + rtrim(lab.fid)); // store the tape name in dir[0]
rec = readTextBlock(ctl);
if (!ctl.eof) {
spout("TapeDir: EOF expected after starting label, block=" + ctl.blockCount);
}
do {
rec = readTextBlock(ctl);
if (!ctl.eof) {
x = 0;
done = false;
do {
if (x+8 > rec.length) {
spout("TapeDir: No terminating entry, block=" + ctl.blockCount + ", x=" + x);
done = true;
} else if (rec.substring(x, x+8) == "0000000?") {
done = true;
} else if (x+16 > rec.length) {
spout("TapeDir: Truncated directory entry, block=" + ctl.blockCount + ", x=" + x);
done = true;
} else {
mfid = rec.substring(x+1, x+8);
fid = rec.substring(x+9, x+16);
dir.push(rtrim(mfid) + "/" + rtrim(fid));
x += 16;
}
} while (!done);
}
} while (!ctl.eof);
lab2 = readTapeLabel(ctl);
if (!lab2.isLabel) {
spout("TapeDir: Tape label expected after directory, block=" + ctl.blockCount);
} else if (lab2.mfid != lab.mfid || lab2.fid != lab.fid) {
spout("TapeDir: Directory ending label mismatch, block=" + ctl.blockCount);
}
}
return dir;
}
function readDiskHeader(ctl) {
/* Reads the next block from the tape blob and (partially) decodes it as a B5500
disk header, returning the header object */
var block;
var header = {
recordLength: 0,
blockLength: 0,
recordsPerBlock: 0,
segmentsPerBlock: 0,
logCreationDate: 0,
logCreationTime: 0,
lastAccessDate: 0,
creationDate: 0,
fileClass: 0,
fileType: 0,
recordCount: 0,
segmentsPerRow: 0,
maxRows: 0,
rowAddress: []};
block = readWordBlock(ctl);
if (ctl.eof) {
spout("DiskHeader: EOF encountered reading header, block=" + ctl.blockCount);
} else if (block.length < 11) {
spout("DiskHeader: header too short, got " + block.length + ", block=" + ctl.blockCount);
} else {
header.recordLength = fieldIsolate(block[0], 0, 15);
header.blockLength = fieldIsolate(block[0], 15, 15);
header.recordsPerBlock = fieldIsolate(block[0], 30, 12);
header.segmentsPerBlock = fieldIsolate(block[0], 42, 6);
header.logCreationDate = fieldIsolate(block[1], 6, 18);
header.logCreationTime = fieldIsolate(block[1], 25, 23);
header.newFormat = fieldIsolate(block[3], 1, 1);
header.saveFactor = fieldIsolate(block[3], 2, 10);
header.lastAccessDate = fieldIsolate(block[3], 12, 18);
header.creationDate = fieldIsolate(block[3], 30, 18);
header.fileClass = fieldIsolate(block[4], 9, 2);
header.fileType = fieldIsolate(block[4], 36, 6);
header.recordCount = block[7];
header.segmentsPerRow = block[8];
header.maxRows = fieldIsolate(block[9], 43, 5);
header.rowAddress = block.slice(10);
}
return header;
}
function formatJulianDate(d) {
/* Formats an integer Julian date as 19YY-MM-DD */
var dd = d % 1000;
var yy = (d-dd)/1000;
var dt = new Date(yy+1900, 0, 1);
dt.setTime(dt.getTime() + (dd-1)*86400000);
return dt.getFullYear().toString() + "-" +
(dt.getMonth()+101).toString().substring(1) + "-" +
(dt.getDate()+100).toString().substring(1);
}
function formatB5500Time(t) {
/* Formats a time in 1/60 seconds as HH:MM:SS */
var hh;
var mm;
var ss;
ss = t % 3600;
mm = ((t-ss)/3600) % 60;
hh = (t - mm*3600 - ss)/3600/60;
return hh.toString() + ":" + (mm+100).toString().substring(1) + ":" +
(ss/60+100).toFixed(0).substring(1);
}
function formatDirectoryEntry(body, lmFile, fileName, header) {
/* Formats the disk header for a file */
var row = document.createElement("tr");
var rx;
var text;
function appendCell(row, v, className) {
var cell = document.createElement("td");
if (className && className.search(/\S/) >= 0) {
cell.className = className;
}
cell.appendChild(document.createTextNode(v.toString()));
row.appendChild(cell);
}
appendCell(row, lmFile, "data");
appendCell(row, fileName, "data");
appendCell(row, header.fileClass, "rj");
appendCell(row, header.fileType, "rj");
appendCell(row, header.recordLength, "rj");
appendCell(row, header.blockLength, "rj");
appendCell(row, header.recordsPerBlock, "rj");
appendCell(row, header.segmentsPerBlock, "rj");
appendCell(row, header.segmentsPerRow, "rj");
appendCell(row, header.maxRows, "rj");
appendCell(row, header.recordCount, "rj");
appendCell(row, header.saveFactor, "rj");
appendCell(row, formatJulianDate(header.creationDate), "rj");
appendCell(row, formatJulianDate(header.lastAccessDate), "rj");
appendCell(row, formatJulianDate(header.logCreationDate), "rj");
appendCell(row, formatB5500Time(header.logCreationTime), "rj");
text = "";
for (rx=header.maxRows-1; rx>=0; rx--) {
if (text || header.rowAddress[rx]) {
text = header.rowAddress[rx].toFixed(0) + " " + text;
}
}
appendCell(row, text);
body.appendChild(row);
}
function extractFile(ctl, fileNr, fileName) {
/* Extracts the next file in sequence from the tape blob, converts the data
from BIC to ASCII, and writes it to a new window object within the browser.
Returns true if no more files should be converted */
var block;
var body = $$("DirListBody");
var box;
var header;
var lab;
var lab2;
var recs = 0;
var result = false;
var rowCount = 0;
var text;
var win;
var x;
//spout(" ");
//spout("File #" + fileNr + ": " + fileName);
lab = readTapeLabel(ctl);
if (ctl.eof) {
spout("Extract: EOF encountered when tape label expected, block=" + ctl.blockCount);
} else if (!lab.isLabel) {
spout(lab.text);
spout("Extract: Above block encountered when a tape label was expected, block=" + ctl.blockCount);
} else {
block = readWordBlock(ctl);
if (!ctl.eof) {
spout("Extract: EOF expected after starting label, block=" + ctl.blockCount);
}
header = readDiskHeader(ctl);
formatDirectoryEntry(body, lab.fid, fileName, header);
while (!ctl.eof) {
text = readTextBlock(ctl);
}
lab2 = readTapeLabel(ctl);
if (!lab2.isLabel) {
spout("Extract: Tape label expected after file data, block=" + ctl.blockCount);
} else if (lab2.mfid != lab.mfid || lab2.fid != lab.fid) {
spout("Extract: File ending label mismatch, block=" + ctl.blockCount);
}
}
return result;
}
function fileLoader_onLoad(ev) {
/* Handle the onload event for an ArrayBuffer FileReader */
var buf = ev.target.result;
var data = new DataView(buf); // use DataView() to avoid problems with littleendians.
var tapeDir;
var text = "";
var x = 0;
clearPanel();
tapeCtl.data = data;
tapeCtl.offset = 0;
tapeCtl.dataLength = buf.byteLength;
tapeCtl.eof = false;
tapeCtl.eot = false;
tapeCtl.blockCount = 0;
tapeDir = readTapeDirectory(tapeCtl);
spout("Files on tape: " + tapeDir[0]);
spout("");
for (x=1; x<tapeDir.length; x++) {
if (extractFile(tapeCtl, x, tapeDir[x])) {
break;
}
}
}
function fileSelector_onChange(ev) {
/* Handle the <input type=file> onchange event when a file is selected */
var f = ev.target.files[0];
var reader = new FileReader();
//alert("File selected: " + f.name +
// "\nModified " + f.lastModifiedDate +
// "\nType=" + f.type + ", Size=" + f.size + " octets");
reader.onload = fileLoader_onLoad;
reader.readAsArrayBuffer(f);
}
function checkBrowser() {
/* Checks whether this browser can support the necessary stuff */
var missing = "";
if (!window.File) {missing += ", File"}
if (!window.FileReader) {missing += ", FileReader"}
if (!window.FileList) {missing += ", FileList"}
if (!window.Blob) {missing += ", Blob"}
if (!window.ArrayBuffer) {missing += ", ArrayBuffer"}
if (!window.DataView) {missing += ", DataView"}
if (missing.length == 0) {
return false;
} else {
alert("No can do... your browser does not support the following features:\n" + missing.substring(2));
return true;
}
}
/* Start of window.onload() */
if (checkBrowser()) {
return;
}
document.getElementById("FileSelector").addEventListener("change", fileSelector_onChange, false);
}
</script>
<style>
TR {
vertical-align: top}
TR TH {
vertical-align: bottom}
.data {
font-family: Courier New, Courier, monospace;
white-space: nowrap}
.rj {
text-align: right}
</style>
</head>
<body>
<div style="position:relative; width:100%; height:3em">
<div style="position:absolute; left:0; top:0; width:auto">
retro-B5500 LibMaint Tape Extract Utility
</div>
<div style="position:absolute; top:0; right:0; width:auto">
<input id=FileSelector type=file size=60>
</div>
</div>
<pre id=TextPanel>
</pre>
<table id=DirListTable border=1 cellspacing=0 cellpadding=1>
<thead>
<tr>
<th>Tape File
<th>Disk Name
<th>Class
<th>Type
<th>Words<br>/Rec
<th>Words<br>/Block
<th>Rec/<br>Block
<th>Seg/<br>Block
<th>Seg/<br>Row
<th>Max Rows
<th>Records
<th>Save Factor
<th>Create Date
<th>Last Access
<th>Log Date
<th>Log Time
<th>Row Addresses
<tbody id=DirListBody>
</table>
</body>
</html>