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

Writing An Entry

Introduction

create wrote a whole new image. Most writing is smaller than that: change a few bytes of an image that already has files on it, and leave every other byte exactly as it was. This section builds the pieces for that - writing a sector, writing the directory, checking a name, packing an entry - and uses them for the simplest change there is: renaming a file.

Writing A Sector

Reading a sector was disc[start:start + 512]. Writing one is the same slice on the other side of =:

disc[start:start + 512] = data

That only works on a bytearray - a bytes cannot be changed (S5) - so writing commands turn the image into a bytearray first. And it must be exactly 512 bytes, or every byte after it in the image moves (S5 showed what MAME thinks of that).

write_directory writes the directory's 2,048 bytes back into track 2's sectors 0 to 3: read_directory in reverse.

Names, The Disc's Way

A name typed on your computer has to fit the disc's rules before it can go in an entry. split_name turns story.txt into the two parts the entry holds, STORY and TXT, and refuses anything that will not fit:

  • Capitals. The Einstein writes names in capitals, and so does edisc.py. It matches names without caring about case - so it must not create story.txt beside STORY.TXT, which would be two files the Einstein cannot tell apart.
  • At most one dot, with 1 to 8 characters before it and up to 3 after.
  • Printable characters only, and not *, ? or : - characters the Einstein's own commands use for other things (: after a drive number, as in DIR 1:).

split(".") cuts text at each dot into a list: "STORY.TXT".split(".") is ["STORY", "TXT"]. And a function can give back two values at once, as a tuple: return base, ext, received as base, ext = split_name(name).

{ch!r} in an f-string shows a value the way Python would write it, in quotes, so a space in an error message is visible as ' '.

Packing An Entry

make_entry builds a 32-byte entry from its parts - S13's table, written instead of read. encode("ascii") turns text into bytes, and ljust(8) pads it with spaces to eight. Every byte the function does not set stays 0, as an in-use entry needs. rename does not use it; add, in S22, will.

Locked Files

A locked file (S13) should not be changed by accident. XtalDOS's own rename asks first - File Lock,DRIVE 0: / Unlock (Y/N)?. edisc.py is simpler: check_unlocked refuses to change a locked file at all.

The code

After free_slots:

def write_sector(disc, track, sector_id, data):
    start = find_sector(disc, track, sector_id)
    disc[start:start + 512] = data


def write_directory(disc, directory):
    for sector_id in range(4):
        write_sector(disc, 2, sector_id, directory[sector_id * 512:sector_id * 512 + 512])


def split_name(name):
    name = name.upper()
    parts = name.split(".")
    if len(parts) == 1:
        parts.append("")
    if len(parts) != 2:
        sys.exit(f"{name}: a disc name has at most one dot")
    base, ext = parts
    if len(base) < 1 or len(base) > 8 or len(ext) > 3:
        sys.exit(f"{name}: a disc name is 1 to 8 characters, a dot, and up to 3 more")
    for ch in base + ext:
        if ch <= " " or ch > "~" or ch in "*?:":
            sys.exit(f"{name}: the character {ch!r} cannot be used in a disc name")
    return base, ext


def make_entry(base, ext, extent, records, blocks):
    entry = bytearray(32)
    entry[0] = 0
    entry[1:9] = base.encode("ascii").ljust(8)
    entry[9:12] = ext.encode("ascii").ljust(3)
    entry[12] = extent
    entry[15] = records
    for i in range(len(blocks)):
        entry[16 + i * 2:18 + i * 2] = blocks[i].to_bytes(2, "little")
    return entry


def check_unlocked(disc, name):
    for entry in find_file(disc, name):
        if entry[9] & 0x80:
            sys.exit(f"{name.upper()} is locked: edisc will not change it")


def name_in_use(disc, name):
    for entry in live_entries(disc):
        if entry_name(entry).upper() == name.upper():
            return True
    return False

Two new words join conditions together. or is true if either side is true: len(base) < 1 or len(base) > 8 is true for a name that is too short or too long. and is true only if both sides are, as in rename_file below: the slot must be in use and have the old name.

Text compares by character code, so ch <= " " or ch > "~" catches everything that is not a printable character (S6: 32 to 126). After show_free, the command:

def rename_file(disc, old, new):
    check_unlocked(disc, old)
    base, ext = split_name(new)
    if name_in_use(disc, base + "." + ext):
        sys.exit(f"{base}.{ext} is already on this disc")
    directory = bytearray(read_directory(disc))
    for slot in range(64):
        entry = directory[slot * 32:slot * 32 + 32]
        if entry[0] != 0xE5 and entry_name(entry).upper() == old.upper():
            directory[slot * 32 + 1:slot * 32 + 9] = base.encode("ascii").ljust(8)
            directory[slot * 32 + 9:slot * 32 + 12] = ext.encode("ascii").ljust(3)
    write_directory(disc, directory)
    print(f"Renamed {old.upper()} to {base}.{ext}")

It renames every entry with the old name, so a big file's extents all move together. And before the else::

elif command == "rename":
    disc = bytearray(disc)
    rename_file(disc, sys.argv[3], sys.argv[4])
    save_image(filename, disc)

The image is read, changed in memory, and only then saved - safely, by S19's save_image. If anything stops the program part way, nothing has been written.

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

Run it

Work on a copy, so FILES.DSK stays as it is for later sections: copy FILES.DSK to FILES2.DSK (in your file manager, or with cp on macOS and Linux, copy on Windows). Then:

python3 edisc.py rename FILES2.DSK README.TXT INFO.TXT
python3 edisc.py rename FILES2.DSK story.txt TALE.TXT
python3 edisc.py entries FILES2.DSK

What you should see

README.TXT is locked: edisc will not change it
Renamed STORY.TXT to TALE.TXT
Slot  0: README.TXT    status 00  extent 0  records   1  blocks [1]  locked
Slot  1: TALE.TXT      status 00  extent 0  records 128  blocks [2, 3, 4, 5, 6, 7, 8, 9]
Slot  2: TALE.TXT      status 00  extent 1  records  29  blocks [10, 11]
Slot  3: NUMBERS.BIN   status 00  extent 0  records  24  blocks [12, 13]

Both of STORY's entries renamed, nothing else touched. And in MAME, with FILES2.DSK in drive 1:

0:DIR 1:
1:*README  .TXT : TALE    .TXT
1: NUMBERS .BIN
26k Size, 162k Free, 190k Total

XtalDOS listing the renamed file

Change one thing

Try each of these on FILES2.DSK, and predict the message first:

  • rename FILES2.DSK TALE.TXT NUMBERS.BIN
  • rename FILES2.DSK TALE.TXT TOOLONGNAME.TXT
  • rename FILES2.DSK TALE.TXT "A*B.TXT" (with the quotes: without them, the terminal on a Mac or Linux tries to treat * as a wildcard itself)
  • rename FILES2.DSK TALE.TXT A.B.C

Then: in rename_file, take out the right-hand .upper() - the one after old - in entry_name(entry).upper() == old.upper(), and run rename FILES2.DSK tale.txt STORY.TXT. What does the tool print, and what has it actually done? (Run entries.) What would be a better thing for it to say?

When it goes wrong

SymptomCause
README.TXT is locked: edisc will not change itThe file's lock bit is set (S13)
NUMBERS.BIN is already on this discTwo files cannot share a name
TOOLONGNAME.TXT: a disc name is 1 to 8 characters, a dot, and up to 3 moreThe name does not fit an entry
A*B.TXT: the character '*' cannot be used in a disc name*, ? and : are refused
A.B.C: a disc name has at most one dotOnly one dot, between the name and the extension

Summary

Writing is reading in reverse: slices of a bytearray on the left of =. write_sector and write_directory put bytes back exactly where read_sector and read_directory found them. split_name makes a typed name fit the disc - capitals, 8 and 3, one dot, no awkward characters - and make_entry packs an entry's 32 bytes. rename changes the name in every one of a file's entries, refuses locked files and names already in use, and the Einstein agrees with the result.

Next

S22, add - choosing blocks and a slot, writing a file's data, and its entry: copying a file from your computer onto an Einstein disc.

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.