#!/usr/bin/python3 # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 # Copyright 2021-2024 Sebastian Lederer. See the file LICENSE.md for details import struct import sys from collections import namedtuple import os MaxPartitions = 8 SlotFree = 1 SlotReserved = 2 SlotDeleted = 4 SlotEndScan = 8 SlotFirst = 16 SlotExtent = 32 SlotReadonly = 64 PartEnabled = 1 PartBoot = 2 PartLast = 4 PartPhysical = 8 PartDefault = 16 part_fmt = ">ii32siiiiii" dirslot_fmt = ">ii32siiiiii" PartSlot = namedtuple("PartSlot", "namelength maxlength name flags startBlock blocks extentSize dirSize bootBlocks") DirSlot = namedtuple("DirSlot", "namelength maxlength name flags sizeBytes createTime modTime generation owner") def createpart(name, flags, start_block, blocks, extent_size, dir_size, boot_blocks=0): b = struct.pack(part_fmt, len(name), 32, bytes(name, 'utf8'), flags, start_block, blocks, extent_size, dir_size, boot_blocks) return b def decodepart(data): return struct.unpack(part_fmt, data) def getpartslot(img, partno): img.seek(partno * 64) fields = decodepart(img.read(64)) name = getname(fields) fields = list(fields) fields[2] = name return PartSlot._make(fields) def createdirslot(name, flags, size, create_time, mod_time, generation, owner): return struct.pack(dirslot_fmt, len(name), 32, bytes(name, 'utf8'), flags, size, create_time, mod_time, generation, owner) def decodedirslot(data): return struct.unpack(dirslot_fmt, data) def putdirslot(img, partstart, slotno, slotdata): img.seek(partstart * 512 + slotno * 64) img.write(slotdata) def getdirslot(img, part, slotno): partstart = part.startBlock img.seek(partstart * 512 + slotno * 64) fields = decodedirslot(img.read(64)) name = getname(fields) fields = list(fields) fields[2] = name return DirSlot._make(fields) def writefile(img, part, slotno, databytes): partstart = part.startBlock extent_size = part.extentSize pos = partstart * 512 + slotno * extent_size print("writefile: slot", slotno, len(databytes), "bytes at block", partstart + slotno * extent_size // 512, " pos", pos) img.seek(pos) img.write(databytes) extendfile(img, part, slotno, len(databytes)) def write_bootimage(img, startblock, data): pos = startblock * 512 img.seek(pos) img.write(data) def extendfile(img, part, slotno, newSize): dirslot = getdirslot(img, part, slotno) extent_size = part.extentSize old_extents = dirslot.sizeBytes // extent_size + 1 new_extents = newSize // extent_size + 1 old_last_slot = slotno + 1 new_last_slot = slotno + new_extents - 1 print("extendfile old_last_slot {} new_last_slot {} old_ext {} new_ext {} ".format( old_last_slot, new_last_slot, old_extents, new_extents), end="") for i in range(old_last_slot, new_last_slot + 1): d = getdirslot(img, part, i) if d.flags & SlotFree: print(i," ", sep="", end="") d = createdirslot("", SlotExtent, 0, 0, 0, 0, 0) putdirslot(img, part.startBlock, i, d) else: print("Cannot extend file at dirslot",i, "- wanted size:", newSize) listdir(img, part) sys.exit(3) print() if old_extents != new_extents: print("file at dirslot",slotno, "extended to", new_extents, "extents") firstslot = createdirslot(dirslot.name, dirslot.flags, newSize, dirslot.createTime, dirslot.modTime, dirslot.generation, dirslot.owner) putdirslot(img, part.startBlock, slotno, firstslot) def findslots(img, part, size): size_in_extents = (size + part.extentSize - 1) // part.extentSize last_slot = part.dirSize - 1 found = False firstslot = 0 slotno = 0 while slotno <= last_slot and not found: dirslot = getdirslot(img, part, slotno) if dirslot.flags & SlotFree: found = True firstslot = slotno print("found free slot at",slotno, ":", dirslot) if size_in_extents > 1: print("checking slot ", end='') for s in range(1, size_in_extents): slotno += 1 print(slotno, " ", end='') dirslot = getdirslot(img, part, slotno) if not (dirslot.flags & SlotFree): print("wanted slot",slotno,"not free") found = False break print() else: slotno += 1 if found: return firstslot else: return 0 def putfile(infilename, filename, img, part, partstart, slotnr): if filename is None: filename = os.path.basename(infilename) try: extent_size = part.extentSize with open(infilename,"rb") as infile: print("creating file", filename, "at slot", slotnr) content = infile.read() d = createdirslot(filename, SlotFirst, len(content), 0, 0, 0, 0) putdirslot(img, partstart, slotnr, d) writefile(img, part, slotnr, content) slotnr += len(content) // extent_size + 1 except Exception as e: print("error reading file", infilename, "skipping", e) return slotnr def getname(data): length = data[0] name = data[2][:length].decode('utf8') return name def flags2str(flags): result = "" if flags & SlotFree: result += "F" if flags & SlotReserved: result += "R" if flags & SlotDeleted: result += "D" if flags & SlotEndScan: result += "E" if flags & SlotFirst: result += "1" if flags & SlotExtent: result += "+" if flags & SlotReadonly: result += "o" return result def findvolume(img, volname): part = None partno = 0 while True: part = getpartslot(img, partno) if part.flags & PartEnabled: if part.name == volname: break partno += 1 if (part.flags & PartLast) or partno >= MaxPartitions: part = None break return part def listvolumes(img): firstvolume = None partno = 0 done = False while not done: part = getpartslot(img, partno) print(part) if part.flags & PartEnabled: print("part", partno, " enabled") print("\tvolume name\t", part.name) print("\tstart block\t", part.startBlock) print("\tblocks\t\t", part.blocks) print("\textentSize\t", part.extentSize) print("\tdirSize\t\t", part.dirSize) if firstvolume is None : firstvolume = part partno += 1 if (part.flags & PartLast) or partno >= MaxPartitions: done = True return firstvolume def listdir(img, part, verbose=False): print("Directory of {}:".format(part.name)) slotno = 0 done = False while not done: slot = getdirslot(img, part, slotno) if (slot.flags & SlotFirst): print(slot.name, slot.sizeBytes, slotno) else: if verbose: print(flags2str(slot.flags)) slotno += 1 #if (slot.flags & SlotEndScan) or (slotno >= part.dirSize): # done = True if (slotno >= part.dirSize): done = True def findfile(img, part, name): slotno = 0 done = False while not done: slot = getdirslot(img, part, slotno) if (slot.flags & SlotFirst) and not (slot.flags & SlotDeleted): if slot.name == name: return slotno slotno += 1 if (slot.flags & SlotEndScan) or (slotno >= part.dirSize): done = True return None def readfile(img, part, slotno): pos = part.startBlock * 512 + slotno * part.extentSize dirslot = getdirslot(img, part, slotno) size = dirslot.sizeBytes print("readfile", dirslot.name, size,"bytes from",pos) img.seek(pos) return img.read(size) def parsepath(img, pathname): volname = "SYSTEM" if pathname.startswith("#"): volname, filename = pathname.split(':') volname = volname[1:] vol = findvolume(img, volname) if vol is None: print("Volume {} not found".format(volname)) return (None, None) else: filename = pathname vol = listvolumes(img) return (vol, filename) def readfromimg(img, pathname,outfilepath): vol, filename = parsepath(img, pathname) if vol is None: return listdir(img, vol) slotno = findfile(img, vol, filename) if slotno is None: print("File", filename,"not found") return data = readfile(img, vol, slotno) with open(outfilepath, "wb") as f: f.write(data) def writetoimg(img, pathname, infilepath): vol, filename = parsepath(img, pathname) if vol is None: return existing_slot = findfile(img, vol, filename) if existing_slot is not None: print("Filename", filename, "already exists on", vol.name) return filesize = os.path.getsize(infilepath) slotno = findslots(img, vol, filesize) if slotno < 1: print("No space on volume", vol.name) return putfile(infilepath, filename, img, vol, vol.startBlock, slotno) def create_image_with_stuff(): imgfile = "sdcard.img" bootimage = "../lib/coreloader.prog" dir_slots = 256 extent_size = 8192 slots_per_extent = extent_size // 64 reserved_slots = dir_slots // slots_per_extent f = open(imgfile,"w+b") b = createpart("PHYS", PartPhysical, 0, 12288, 4096, 0, 0) #print(b) f.write(b) with open(bootimage, "rb") as bf: bootdata = bf.read() bootBlocks = len(bootdata) // 512 + 1 b = createpart("BOOT", PartBoot, 16, 112, 0, 0, bootBlocks) f.write(b) b = createpart("Testvolume 1", PartEnabled, 128, 3968, 8192, 248) f.write(b) b = createpart("SYSTEM", PartEnabled, 4096, 4096, 8192, 256) f.write(b) b = createpart("Examples", PartEnabled + PartLast, 8192, 4096, 8192, 256) f.write(b) part = getpartslot(f, 2) partstart = part.startBlock dir_slots = part.dirSize print("creating",reserved_slots, "reserved directory slots") for a in range(0,reserved_slots): d = createdirslot("DIR", SlotReserved, 0, 0, 0, 0, 0) putdirslot(f, partstart, a, d) print("creating", dir_slots - reserved_slots, "free slots") for a in range(reserved_slots, dir_slots): d = createdirslot("", SlotFree, 0, 0, 0, 0, 0) putdirslot(f, partstart, a, d) #d = createdirslot("obstacle", SlotFirst , 0, 0, 0, 0, 0) #putdirslot(f, partstart, reserved_slots + 2, d) slotnr = reserved_slots if True: data = bytes("ABCDEFGHIJKLMNOPQRST", "ASCII") * 410 + bytes('1234','ASCII') d = createdirslot("A Testfile.text", SlotFirst, 0, 0, 0, 0, 0) putdirslot(f, partstart, slotnr, d) writefile(f, part, slotnr, data) slotnr += len(data) // extent_size + 1 d = createdirslot("", SlotFree, 0, 0, 0, 0, 0) putdirslot(f, partstart, slotnr, d) slotnr += 1 d = createdirslot("Another_file.text", SlotFirst, 20, 0, 0, 0, 0) putdirslot(f, partstart, slotnr, d) slotnr += 1 for a in range(0,20): d = createdirslot("", SlotFree, 0, 0, 0, 0, 0) putdirslot(f, partstart, slotnr, d) slotnr += 1 if True: d = createdirslot("test3.text", SlotFirst, 20, 0, 0, 0, 0) putdirslot(f, partstart, slotnr, d) slotnr += 1 d = createdirslot("test1.text", SlotFirst, 20, 0, 0, 0, 0) putdirslot(f, partstart, slotnr, d) slotnr += 1 d = createdirslot("test2.text", SlotFirst, 20, 0, 0, 0, 0) putdirslot(f, partstart, slotnr, d) slotnr += 1 slotnr = putfile("../examples/test.txt", "sometext.text" , f, part, partstart, slotnr) # slotnr = putfile("chase.prog", None , f, part, partstart, slotnr) slotnr = putfile("../examples/chase.pas", None , f, part, partstart, slotnr) slotnr = putfile("../examples/sine.pas", None , f, part, partstart, slotnr) slotnr = putfile("../examples/graph2.pas", None , f, part, partstart, slotnr) while slotnr < dir_slots: d = createdirslot("", SlotFree + SlotEndScan , 0, 0, 0, 0, 0) putdirslot(f, partstart, slotnr, d) slotnr += 1 # second partition (SYSTEM) part = getpartslot(f, 3) partstart = part.startBlock dir_slots = part.dirSize print() print("Partition {} at {}".format(part.name, part.startBlock)) print("creating",reserved_slots, "reserved directory slots") for a in range(0,reserved_slots): d = createdirslot("DIR", SlotReserved, 0, 0, 0, 0, 0) putdirslot(f, partstart, a, d) print("creating", dir_slots - reserved_slots, "free slots") for a in range(reserved_slots, dir_slots): d = createdirslot("", SlotFree + SlotEndScan, 0, 0, 0, 0, 0) putdirslot(f, partstart, a, d) slotnr = reserved_slots slotnr = putfile("../progs/shell.prog", "shell.prog", f, part, partstart, slotnr) slotnr = putfile("../lib/coreloader.lsym", "coreloader.lsym", f, part, partstart, slotnr) slotnr = putfile("../lib/coreloader.prog", "coreloader.prog", f, part, partstart, slotnr) slotnr = putfile("../lib/float32.lib", "float32.lib", f, part, partstart, slotnr) slotnr = putfile("../lib/runtime.lib", "runtime.lib", f, part, partstart, slotnr) slotnr = putfile("../lib/stdlib.lib", None, f, part, partstart, slotnr) slotnr = putfile("../lib/stdlib.inc", None, f, part, partstart, slotnr) slotnr = putfile("../pcomp/sasm.prog", None , f, part, partstart, slotnr) slotnr = putfile("../pcomp/pcomp.prog", None , f, part, partstart, slotnr) slotnr = putfile("../pcomp/lsymgen.prog", None , f, part, partstart, slotnr) slotnr = putfile("../pcomp/libgen.prog", None , f, part, partstart, slotnr) slotnr = putfile("../progs/reclaim.prog", None , f, part, partstart, slotnr) slotnr = putfile("../progs/dumpdir.prog", None , f, part, partstart, slotnr) slotnr = putfile("../progs/partmgr.prog", None , f, part, partstart, slotnr) slotnr = putfile("../progs/editor.pas", None , f, part, partstart, slotnr) slotnr = putfile("../progs/editor.prog", None , f, part, partstart, slotnr) slotnr = putfile("../progs/xfer.prog", None , f, part, partstart, slotnr) listdir(f, part) # third partition part = getpartslot(f, 4) partstart = part.startBlock dir_slots = part.dirSize print() print("Partition {} at {}".format(part.name, part.startBlock)) print("creating",reserved_slots, "reserved directory slots") for a in range(0,reserved_slots): d = createdirslot("DIR", SlotReserved, 0, 0, 0, 0, 0) putdirslot(f, partstart, a, d) slotnr = reserved_slots print("creating", dir_slots - reserved_slots, "free slots") for a in range(reserved_slots, dir_slots): d = createdirslot("", SlotFree + SlotEndScan, 0, 0, 0, 0, 0) putdirslot(f, partstart, a, d) slotnr = putfile("../examples/helloasm.s", None, f, part, partstart, slotnr) # slotnr = putfile("helloasm.prog", "helloasm.prog", f, part, partstart, slotnr) # slotnr = putfile("hello.prog", None , f, part, partstart, slotnr) # slotnr = putfile("hellop.s", None , f, part, partstart, slotnr) slotnr = putfile("../examples/hellop.pas", None , f, part, partstart, slotnr) slotnr = putfile("../tests/timetest.pas", None , f, part, partstart, slotnr) # slotnr = putfile("../tests/timetest.prog", None , f, part, partstart, slotnr) slotnr = putfile("../tests/readtest.pas", None , f, part, partstart, slotnr) slotnr = putfile("../tests/readtest.prog", None , f, part, partstart, slotnr) slotnr = putfile("../tests/readchartest.pas", None , f, part, partstart, slotnr) slotnr = putfile("../tests/readchartest.prog", None , f, part, partstart, slotnr) # slotnr = putfile("cchangetest.pas", None , f, part, partstart, slotnr) # slotnr = putfile("cchangetest.prog", None , f, part, partstart, slotnr) slotnr = putfile("../tests/test109.pas", None , f, part, partstart, slotnr) slotnr = putfile("../tests/test133.pas", None , f, part, partstart, slotnr) # slotnr = putfile("../tests/test133.prog", None , f, part, partstart, slotnr) slotnr = putfile("../tests/test159.pas", None , f, part, partstart, slotnr) # slotnr = putfile("../tests/test159.prog", None , f, part, partstart, slotnr) slotnr = putfile("../tests/umlaut.pas", None , f, part, partstart, slotnr) slotnr = putfile("../examples/rtpair.pas", None , f, part, partstart, slotnr) slotnr = putfile("../examples/5cubes.pas", None , f, part, partstart, slotnr) # slotnr = putfile("../examples/5cubes.prog", None , f, part, partstart, slotnr) slotnr = putfile("../examples/3dcube.pas", None , f, part, partstart, slotnr) slotnr = putfile("../examples/conway.pas", None , f, part, partstart, slotnr) slotnr = putfile("../examples/mandelbrot.pas", None , f, part, partstart, slotnr) slotnr = putfile("../examples/lines.pas", None , f, part, partstart, slotnr) slotnr = putfile("../examples/viewpict.pas", None , f, part, partstart, slotnr) # slotnr = putfile("viewpict.prog", None , f, part, partstart, slotnr) slotnr = putfile("../examples/ara.pict", "ara.pict" , f, part, partstart, slotnr) slotnr = putfile("../examples/shinkansen.pict", "shinkansen.pict" , f, part, partstart, slotnr) slotnr = putfile("../examples/snow_leopard.pict", "snow_leopard.pict" , f, part, partstart, slotnr) listdir(f, part) write_bootimage(f, 16, bootdata) f.close() if __name__ == "__main__": if len(sys.argv) > 1: if sys.argv[1] == "get": f = open(sys.argv[2], "rb") readfromimg(f, sys.argv[3], sys.argv[4]) elif sys.argv[1] == "put": imgfile = open(sys.argv[2], "r+b") infilepath = sys.argv[3] destfilename = sys.argv[4] writetoimg(imgfile, destfilename, infilepath) elif sys.argv[1] == "createimg": create_image_with_stuff() sys.exit(0)