NOS Disk I/O (nosio)
Module for NOS device I/O
Copyright (c) 2021-2024 Steve Zoppi
This is licensed software a copy of the license should have been included in the source tree for this module. You may find a copy of the license here: https://www.gnu.org/licenses/gpl-3.0.en.html
This program operates on PLATO packs and master files. Packs are accessed as DtCyber disk images. Master files (CDC style virtual packs) can be accessed directly (if you have a master file image copy available) or as NOS files contained in a NOS disk image.
<Documentation_reference>
When accessing PLATO file structures, the host pack object needs to be instantiated and then the PlatoFile class may be used to access the underlying structure.
- class nosio.BasicFileDevice(fn, mode='rb')[source]
Bases:
objectBase class for NOS devices.
This class encapsulates a host-based file and presents it as a NOS-type device for additional decoration by classes deriving from this one.
- Parameters:
fn (string) – File Name
mode (string) – IO Mode, permitted values ‘rb’ ‘r+b’
- f
Container File Object.
- Type:
object
- fs
File Status Structure Object of Container file upon opening.
- Type:
object
- mode
The device’s IO mode as requested upon instantiation
- Type:
str
- class nosio.ControlBlock(cb_format: tuple, dblock: list)[source]
Bases:
objectParse Any Control Block into Named Properties
The decomposition of the control block enables bidirectional (get/set) manipulation of properties of a control block by name. In effect, this enables a dynamic “class-like” implementation without having to write explicit getters and setters for each property.
Warning
Control Block definitions required that ALL property names be unique. This is checked during class construction.
Structure:
The primary structure is a tuple composed of an integer and a dictionary object. tuple: ( min_length, { structure_descriptor, ... } )
Structure Descriptor:
Structure Descriptors are one of three formats Level One: (Whole-word descriptor) "property_name": word_offset(,) or "property_name": word_offset, word_count(,) Level Two: (Bit-segment descriptor) "property_name": word_offset, { <bit_dictionary> })(,) <bit_dictionary> := "property_name": bit_count_of_property(,)
Note
bit_count_of_propertycounts bits from the left.- dblock
Data Block
- Type:
list
- min_words
Minimum Number of words in Data Block list.
- Type:
int
- prop_name_maxlength
Maximum character count of property name.
- Type:
int
- properties
Dictionary of descriptors.
- Type:
dict
- proplist
List of all properties in this control block.
- Type:
list
- title
Default Title String for this control block.
- Type:
str
- Parameters
- cb_format (tuple):
Format specification for the data words supplied in
dblock- dblock (list):
List of words that are described by
cb_format
- get_propertybag(itemlist=None) dict[source]
Manipulates the property bag of the stored control block.
If
itemlistis not specified, all properties are returned in a dictionary object with a key and value of the following types:If the value of the item is a whole word or more than one whole ``cmword``, the value returned is a list object containing one or more ``cmwords``. If the value of the item to be returned is a ``bit`` value, a single integer value is returned as the key's value. If ``itemlist`` is specified, ONLY the items named in the ``itemlist`` are returned in the key/value dictionary.
- set_propertybag(valuedict: dict) None[source]
Modifies the contents of a control block by property name.
- Parameters:
valuedict (dict) – Dictionary of “key/value” pairs.
The control block’s keys are searched using the mapping provided in the resident control block. Once a key is found, the transform is completed in the locally cached image of the control block.
- class nosio.DiskDevice(fn, mode='rb')[source]
Bases:
BasicFileDeviceBasic disk.
- cw1
Control Word (12-bit value)
- Type:
int
- cw2
Control Word (12-bit value)
- Type:
int
- wordstyle
- Type:
int
- ddevtype
2-character identifier of device type (detected)
- Type:
str
- ddevmodel
Descriptor of the detected device model
- Type:
str
- GEOM_BY_SIZE = {95956992: ['di', 0, 'dd844-2(1) packed/new', 512, 64], 120695904: ['di', 1, 'dd844-2(1) unpacked/classic', 644, 64], 192147456: ['dj', 0, 'dd844-4(1/4) packed/new', 512, 64], 241685472: ['dj', 1, 'dd844-4(1/4) unpacked/classic', 644, 64], 552468480: ['dm', 0, 'dd885-(11/12) packed/new (dm or dq)', 512, 64], 554626560: ['db', 2, 'dd885-42 unpacked (db)', 2056, 256], 694901760: ['dm', 1, 'dd885-(11/12) unpacked/classic (dm or dq)', 644, 64]}
- SECTOR_PAD = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
- SECTOR_SIZE_PACKED = 512
- SECTOR_SIZE_UNPACKED = 644
- SECTOR_SIZE_UNPACKED_DB = 2056
- WORD_STYLE_PACKED = 0
- WORD_STYLE_Q64 = 2
- WORD_STYLE_UNPACKED = 1
- phy_sector_read(sec) tuple[source]
Read data from a disk image at the given sector number.
- Returns:
list
- List Contents::
A full-sector (depending on the device) of 60-bit words plus the two (or four) control words (from the start of the sector) which are 12 bit values.
- Generally:
Each sector is 512 bytes * 8 bits per byte = 4096 bits
4096 bits = 68 CM words (60-bits) with a remainder of 16 bit (2 bytes).
However, the used sector format is:
[12-bits] cw1 12 + [12-bits] cw2 12 = 24 bits or 3 8-bit bytes [64 CM Words @ 60-bits each] 3840 (same as an NOS-I Block) ---------------------------------- 3864 232 remaining (29 8-bit bytes) SECTOR_PAD is exactly 29 bytes
On “db” type devices:
One CM word is stored per 8 bytes (4 bits unused) Each disk sector contains 4, 64-word entries (logical sector) Each disk sector is 256 * 8 bytes = 2048 bytes
PLUS 2, 2-byte PPWords containing Control Bytes PLUS 2, 2-byte PPWords containing Extended Mem Address = 2048 + 8 = 2056 bytes per cluster of old sectors
- class nosio.NosCatEntry(fn: str, ui: int, ent: tuple, flen: int, ftrk: int, fsec: int, daf: str, devnum: int)[source]
Bases:
object
- class nosio.NosDiskDevice(fn: Path | str, mode: str = 'rb')[source]
Bases:
DiskDeviceNOS file structured disk.
DISK SECTOR
Every sector, from a user standpoint, contains up to 64 (100B) CM words. However, the system always prefixes the sector with two header bytes (24 bits). These two header bytes contain file Linkage and other information. The general format of a disk sector is as follows.
- header
byte 1
- header
byte 2
- from 0 —1008 CM
words of data
There are five types of sectors known to the system and labeled via the header bytes. These are:
End—of—record (EOR) sector
End—of—file (EOF) sector
End—of—information (EOI) sector
System sector
Full sector
Header byte 2 contains a word count of the number of CM words within the sector as written by the user. The word count (WC) is in the range 0 to 1008. If the word count equals 1008, the sector is full. If the word count is Less than 1006, the sector is called a short PRU and indicates an EOR. Table 7-2 shows the relationship between the sector types and the contents of the header bytes.
- Sector Type
- Header Byte 1
- Header Byte 2
Comment
- EOR
- Next Sector/Track
0 < WC < 100B May contain data
- EOF
- Next Sector/Track
No data
- EOI
- 0
- 0
No data
- System
- 3777B
- 77B
System data only
- Full
- Next Sector/Track
- WC=100B
Full sector
A full sector differs from an EOR sector by WC=100B rather than WC<100B as for the EOR sector.
To differentiate between a link to another track rather than to the next sector in header byte 1, the upper bit (bit 11) is set. The PP common decks that read/write mass storage perform the reading and writing of the header bytes. Also, CIO reads/writes the header bytes for disk I/O.
In the above table, the system sector for a file is indicated by special header byte values. This is done to prevent accidental reading through the system sector itself. The system sector is always sector 0 of the first track of a file.
- cat_byname
Entries by Filename (key=filename, returns UI list)
- Type:
dict
- cat_byui
Entries by UI (key=userindex, returns filename list)
- Type:
dict
- cat_entries
Catalog Entries
- Type:
list
- cat_index
Entries by (key=(Filename, UI))
- Type:
dict
- catalog_tks
List of Catalog Tracks
- Type:
list
- cw1
Control Word 1
- Type:
int
- cw2
Control Word 2
- Type:
int
- emw1
Extended Memory Address Word 1
- Type:
int
- emw2
Extended Memory Address Word 2
- Type:
int
- devmask
Device Mask
- Type:
int
- devnum_list
List of device numbers referenced by this disk’s catalog (0 = This device)
- Type:
list
- devtype
Device Type ‘di’ ‘dj’ ‘dm’ ‘dq’ ‘db’
- Type:
str
- _next_free_trk
Free Tracks
- Type:
int
- label_sector
Label Sector
- Type:
list
- level
NOS Pack Format Level (0 <= NOS1.3; 1 >= NOS 1.4; 2 >= NOS 2+)
- Type:
int
- dsector_to_container
Device LOGICAL Sector to Host PHYSICAL Sector Conversion routine
- Type:
function
- lsec
Current Logical Sector
- Type:
int
- seclimit
Sector Limits for the selected device
- Type:
int
- secmask
Secondary Mask
- Type:
int
- current_track
Current Track
- Type:
int
- trt
Track Reservation Table
- Type:
list
- converters = {'db': <function dsect_to_csect_db>, 'di': <function dsect_to_csect_di>, 'dj': <function dsect_to_csect_dj>, 'dk': <function dsect_to_csect_dk>, 'dl': <function dsect_to_csect_dl>, 'dm': <function dsect_to_csect_dm>, 'dq': <function dsect_to_csect_dq>}
- dump_catalog(ordinal: bool = True, byfilename: bool = True, byuserindex: bool = True) None[source]
Print the contents of the Disk Catalog
- formats catalog entry for display which includes
file(record)name userindex size ‘d’irect or ‘i’ndirect access track / sector start
- get_nosfile(fn: str, ui: int) NosFile[source]
Open a NOS disk file
Iterates through the catalog entries and looks for a matching entry for a given name and userindex.
- Raises:
FileNotFoundError –
- property logsec: int
- property physec: int
- seclimits = {'db': 640, 'di': 107, 'dj': 227, 'dk': 112, 'dl': 227, 'dm': 640, 'dq': 640}
- sector_io(track=None, lsec=None, syssec=False, new_data=None) tuple[source]
Read (or write) a sector from the specified NOS logical track and sector address.
End of record indication is:
None (not EOR/EOF/EOI) or the level number (EOR = 0, EOF > 0, EOI = -1), (same as NOS tape read).
The next track/sector are saved in the object state, and are used as default position for the next read.
By default, the system sector is skipped; if syssec is True, it will be returned.
- Returns:
the block data as a list of words, the end of record indication.
- property subsector
- trklimits = {'db': 1682, 'di': 1632, 'dj': 1640, 'dk': 1632, 'dl': 1640, 'dm': 1682, 'dq': 1682}
- class nosio.NosFile(disk: any, ent_obj: NosCatEntry)[source]
Bases:
objectEncapsulation of the NOS file
- daf
TRUE=Direct Access FALSE=Indirect
- Type:
bool
- _disk
Placeholder for the underlying disk device
- Type:
any
- ent_obj
Catalog Entry Object
- Type:
- fsec
First Sector
- Type:
int
- ftrk
First Track
- Type:
int
- len
Length of the file
- Type:
int
- pack_struct
pack structure
- Type:
str
- tracks
Tracks associated with this file
- Type:
None|list
- property disk: any
This property changes the underlying disk containing this file.
When this occurs, the track table needs to be re-read to correct this file’s in-memory track list.
- dump_nosfile(dformat='dump') None[source]
Dump the contents of a permanent file
https://www.bitsavers.org/pdf/cdc/Tom_Hunter_Scans//NOS_2_Analysis_Handbook_Ref_60459300M_Dec88_Part_1.pdf P 17-9 https://www.bitsavers.org/pdf/cdc/Tom_Hunter_Scans//NOS_2_Analysis_Handbook_Ref_60459300M_Dec88_Part_2.pdf
- get_relative_sector(logical_sector: int, mfoffset: bool = True) list[source]
Read a relative sector (by number) from this file.
When Plato Master files are read, the system sector is ignored in the sector count (PLATO files don’t take that sector into account) so adding ONE to the sector number will always cause the system sector to be skipped when the track number is resolved for plato files.
This is the default case for get_relative_sector. We turn it off while we’re dumping files.
- class nosio.PlatoDiskDevice(fn, mode='rb')[source]
Bases:
DiskDevicePlato disk (not NOS file structured).