Inside the Einstein: The Disc, a TatungBytes course: XtalDOS listing the first practice disc image, on a rainbow sunburst
← Back to Modules
module
13

A Directory Entry, Byte By Byte

Introduction

S12 showed you the directory's slots: 32 bytes each, a name you could read, and a lot of numbers you could not. Everything the Einstein knows about a file is in those 32 bytes - there is nowhere else for it to be. This section reads every one of them, and teaches edisc.py to do the same.

The 32 Bytes

STORY.TXT's first directory entry, byte by byte

ByteWhat it holds
0Status: 00 for a slot in use, E5 for a slot that is free
1-8Name: up to eight characters, padded with spaces (20)
9-11Extension: up to three characters, padded with spaces. The top bit of byte 9 has a second job - see Locked files
12Extent: which piece of the file this entry describes - 0 for the first
13-14Not used
15Records: how many 128-byte pieces of the file this entry holds - at most 128, which is 16K
16-31Block numbers: where on the disc the file's data is

Names are stored in capitals by convention, and a name shorter than eight letters is filled out with spaces, which is why the dump shows STORY and then three 20s. There is no dot on the disc: the dot is something you type, and something edisc.py puts back when it shows a name.

Extent and records only make full sense with files bigger than 16K, which need more than one entry. STORY.TXT is one: its first entry (slot 1) has extent 0 and 128 records - 16K, a full entry - and its second (slot 2) has extent 1 and 1D, 29 records, for the rest. S17 puts them together.

Block numbers say where the data is. The disc's file space is handed out in blocks of 2K, numbered from 1; S15 explains how a block number becomes four sectors. An entry has room for eight block numbers, two bytes each, low byte first - so 02 00 03 00 04 00 ... means blocks 2, 3, 4, and so on. 00 00 means "no block here". Eight blocks of 2K is 16K: that is why one entry holds at most 16K.

Locked Files

A file can be locked, so the Einstein will not let it be changed or deleted by accident. The lock is a single bit: the top bit (the one worth 128, 0x80) of byte 9, the first letter of the extension. README on FILES.DSK is locked, so its byte 9 is not 54 (T) but D4 - 54 with the top bit set. That is why S12's dump showed .XT. XtalDOS shows a locked file with a *:

1:*README  .TXT

Python's & works on the bits of two numbers, keeping only the bits set in both. So:

  • entry[9] & 0x80 is 128 if the lock bit is set, and 0 if not - a test.
  • entry[9] & 0x7F keeps the other seven bits and clears the top one: 0xD4 & 0x7F is 0x54, T again.

Two Ways To Store A Block Number

The Einstein's own DOS stores every block number in two bytes, as above. But a few real Einstein discs - written by something else - store each block number in one byte: sixteen of them, 01 02 03 00 00 .... A tool that reads all discs must handle both, and it can tell which it has, entry by entry: no block number on an Einstein disc is ever bigger than 255, so in two-byte form the second byte of every pair - bytes 17, 19, 21 and so on - is always 00. If every one of those is zero, the entry is two-byte; if any is not, it is one-byte.

The course download has a disc whose one file is stored the one-byte way, EIGHTBIT.DSK. Look at it in XtalDOS:

0:DIR 1:
1: EIGHT   .TXT
4k Size, 186k Free, 190k Total

The file holds 5,000 bytes in three blocks, 6K - but XtalDOS reads its 01 02 03 00 as the two-byte numbers 0201 and 0003: block 513, which does not exist, and block 3. Ask it to DISP 1:EIGHT.TXT and it fails with Not Ready 1: - it is looking for block 513. (Answer N to Try Again(Y/N)?.) The Einstein's own DOS only understands two-byte numbers; your tool will understand both.

The code

Three helpers, after read_directory. entry_text turns bytes into printable text (using show_byte from S6). entry_name builds NAME.EXT from bytes 1-11, clearing the lock bit, and rstrip() removes the padding spaces from the end of a piece of text. entry_blocks reads the block numbers whichever way they are stored:

def entry_text(raw):
    text = ""
    for b in raw:
        text = text + show_byte(b)
    return text


def entry_name(entry):
    name = entry_text(entry[1:9]).rstrip()
    ext = entry_text(bytes([entry[9] & 0x7F]) + entry[10:12]).rstrip()
    return name + "." + ext


def entry_blocks(entry):
    field = entry[16:32]
    sixteen_bit = True
    for i in range(1, 16, 2):
        if field[i] != 0:
            sixteen_bit = False
    blocks = []
    if sixteen_bit:
        for i in range(0, 16, 2):
            block = int.from_bytes(field[i:i + 2], "little")
            if block != 0:
                blocks.append(block)
    else:
        for block in field:
            if block != 0:
                blocks.append(block)
    return blocks

range(1, 16, 2) is 1, 3, 5 ... 15: the second byte of each pair.

Then the command, after show_directory. continue inside a loop means "skip the rest of the body and go on to the next one":

def show_entries(disc):
    directory = read_directory(disc)
    for slot in range(64):
        entry = directory[slot * 32:slot * 32 + 32]
        if entry[0] == 0xE5:
            continue
        locked = ""
        if entry[9] & 0x80:
            locked = "  locked"
        print(f"Slot {slot:2}: {entry_name(entry):12}  status {entry[0]:02X}"
              f"  extent {entry[12]}  records {entry[15]:3}  blocks {entry_blocks(entry)}{locked}")

if entry[9] & 0x80: works because Python treats 0 as false and any other number as true. And one more command line, before the else::

elif command == "entries":
    show_entries(disc)

(The whole program: s13-edisc.py.)

Run it

python3 edisc.py entries FILES.DSK
python3 edisc.py entries EIGHTBIT.DSK

What you should see

Slot  0: README.TXT    status 00  extent 0  records   1  blocks [1]  locked
Slot  1: STORY.TXT     status 00  extent 0  records 128  blocks [2, 3, 4, 5, 6, 7, 8, 9]
Slot  2: STORY.TXT     status 00  extent 1  records  29  blocks [10, 11]
Slot  3: NUMBERS.BIN   status 00  extent 0  records  24  blocks [12, 13]
Slot  0: EIGHT.TXT     status 00  extent 0  records  40  blocks [1, 2, 3]

Every byte accounted for. README is locked, in block 1. STORY.TXT is two entries and ten blocks. And EIGHT.TXT is three blocks, 1, 2 and 3 - which your tool read correctly and the Einstein's DOS did not.

Change one thing

  • In entry_name, take out & 0x7F. What does entries FILES.DSK show for README now?
  • Make entry_blocks always use the two-byte branch (set sixteen_bit = True after the loop). What does it give for EIGHT.TXT - and which number from XtalDOS's DIR does that explain?
  • Add print(len(entry_blocks(entry)) * 2) as the last line inside show_entries' loop. Add up the numbers it prints for FILES.DSK. Where have you seen the total?

Summary

A directory entry is status (0 in use, E5 free), an eight-character name and three-character extension padded with spaces, an extent number, a count of 128-byte records, and up to eight block numbers - two bytes each as the Einstein's DOS writes them, one byte each on a few discs written by other things, told apart by whether the pairs' second bytes are all zero. The top bit of byte 9 is the lock. edisc.py entries decodes them all.

Next

S14, list - turning entries into a list of files, the way DIR does, with sizes worked out and the free space at the bottom.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.