diff --git a/amitools/fs/block/Block.py b/amitools/fs/block/Block.py index 391af05d..1d385181 100644 --- a/amitools/fs/block/Block.py +++ b/amitools/fs/block/Block.py @@ -25,6 +25,8 @@ class Block: ST_USERDIR = 2 ST_FILE = -3 & 0xFFFFFFFF + ignore_errors = False + def __init__(self, blkdev, blk_num, is_type=0, is_sub_type=0, chk_loc=5): self.valid = False self.blkdev = blkdev @@ -36,6 +38,7 @@ def __init__(self, blkdev, blk_num, is_type=0, is_sub_type=0, chk_loc=5): self.is_type = is_type self.is_sub_type = is_sub_type self.chk_loc = chk_loc + self.errors = [] def __str__(self): return "%s:@%d" % (self.__class__.__name__, self.blk_num) @@ -70,7 +73,7 @@ def read(self): self._read_data() self._get_types() self._get_chksum() - self.valid = self.valid_types and self.valid_chksum + self.valid = self.valid_types and (self.valid_chksum or Block.ignore_errors) def write(self): if self.data == None: @@ -129,9 +132,11 @@ def _get_types(self): self.valid_types = True if self.is_type != 0: if self.type != self.is_type: + self.errors.append("Block #%d type mismatch (is %d but expected %d)" % (self.blk_num, self.type, self.is_type)) self.valid_types = False if self.is_sub_type != 0: if self.sub_type != self.is_sub_type: + self.errors.append("Block #%d subtype mismatch (is %d but expected %d)" % (self.blk_num, self.sub_type, self.is_sub_type)) self.valid_types = False def _put_types(self): @@ -144,6 +149,8 @@ def _get_chksum(self): self.got_chksum = self._get_long(self.chk_loc) self.calc_chksum = self._calc_chksum() self.valid_chksum = self.got_chksum == self.calc_chksum + if not self.valid_chksum: + self.errors.append("Block #%d checksum is invalid" % (self.blk_num)) def _put_chksum(self): self.calc_chksum = self._calc_chksum() diff --git a/amitools/fs/rdb/FileSystem.py b/amitools/fs/rdb/FileSystem.py index 8ba6f7d8..d08d9ec5 100644 --- a/amitools/fs/rdb/FileSystem.py +++ b/amitools/fs/rdb/FileSystem.py @@ -2,6 +2,7 @@ from amitools.fs.block.rdb.LoadSegBlock import * from amitools.util.HexDump import * import amitools.fs.DosType as DosType +import logging class FileSystem: @@ -153,6 +154,12 @@ def get_desc(self): "dev_node": dev_node, } + def log_errors(self): + for i in self.lsegs: + for e in i.errors: + logging.warning(e) + + # ----- edit ----- def clear_flags(self): diff --git a/amitools/fs/rdb/Partition.py b/amitools/fs/rdb/Partition.py index 1a2e8777..d816d8ce 100644 --- a/amitools/fs/rdb/Partition.py +++ b/amitools/fs/rdb/Partition.py @@ -3,16 +3,17 @@ from amitools.fs.blkdev.PartBlockDevice import PartBlockDevice import amitools.util.ByteSize as ByteSize import amitools.fs.DosType as DosType +import logging class Partition: - def __init__(self, blkdev, blk_num, num, cyl_blks, rdisk): + def __init__(self, blkdev, blk_num, num, cyl_blks, block_bytes, rdisk=None): self.blkdev = blkdev self.blk_num = blk_num self.num = num self.cyl_blks = cyl_blks + self.block_bytes = block_bytes self.rdisk = rdisk - self.block_bytes = rdisk.block_bytes self.part_blk = None def get_next_partition_blk(self): @@ -156,6 +157,11 @@ def get_desc(self): "automount": automount, } + def log_errors(self): + for e in self.part_blk.errors: + logging.warning(e) + + # ----- Import/Export ----- def export_data(self, file_name): diff --git a/amitools/fs/rdb/RDisk.py b/amitools/fs/rdb/RDisk.py index fb0b2d3b..f387353c 100644 --- a/amitools/fs/rdb/RDisk.py +++ b/amitools/fs/rdb/RDisk.py @@ -48,9 +48,10 @@ def open(self): self.parts = [] num = 0 while part_blk != Block.no_blk: - p = Partition(self.rawblk, part_blk, num, self.rdb.log_drv.cyl_blks, self) - num += 1 - if not p.read(): + p = Partition(self.rawblk, part_blk, num, self.rdb.log_drv.cyl_blks, self.block_bytes, self) + ok = p.read() + p.log_errors() + if not ok: self.valid = False return False self.parts.append(p) @@ -58,6 +59,7 @@ def open(self): self.used_blks.append(p.get_blk_num()) # next partition part_blk = p.get_next_partition_blk() + num += 1 # read filesystems fs_blk = self.rdb.fs_list @@ -65,8 +67,9 @@ def open(self): num = 0 while fs_blk != PartitionBlock.no_blk: fs = FileSystem(self.rawblk, fs_blk, num) - num += 1 - if not fs.read(): + ok = fs.read() + fs.log_errors() + if not ok: self.valid = False return False self.fs.append(fs) @@ -74,6 +77,7 @@ def open(self): self.used_blks += fs.get_blk_nums() # next partition fs_blk = fs.get_next_fs_blk() + num += 1 # TODO: add bad block blocks @@ -286,7 +290,7 @@ def find_filesystem_by_string(self, s): # ----- edit ----- def create( - self, disk_geo, rdb_cyls=1, hi_rdb_blk=0, disk_names=None, ctrl_names=None + self, disk_geo, rdb_cyls=1, hi_rdb_blk=0, disk_names=None, ctrl_names=None, blk_num=0 ): cyls = disk_geo.cyls heads = disk_geo.heads @@ -336,16 +340,17 @@ def create( ctrl_revision, ) self.block_bytes = self.rawblk.block_bytes - self.rdb = RDBlock(self.rawblk) + self.rdb = RDBlock(self.rawblk, blk_num) self.rdb.create( phy_drv, log_drv, drv_id, block_size=self.block_bytes, flags=flags ) - self.rdb.write() - self.used_blks = [self.rdb.blk_num] self.max_blks = self.rdb.log_drv.rdb_blk_hi + 1 self.valid = True + def write(): + self.rdb.write() + def resize(self, new_lo_cyl=None, new_hi_cyl=None, adjust_physical=False): # if the rdb region has to be minimized then check if no partition # is in the way @@ -630,7 +635,7 @@ def add_partition( self.rawblk.flush() # create partition object and add to partition list blk_per_cyl = blk_per_trk * heads - p = Partition(self.rawblk, blk_num, len(self.parts), blk_per_cyl, self) + p = Partition(self.rawblk, blk_num, len(self.parts), blk_per_cyl, self.block_bytes, self) p.read() self.parts.append(p) return p diff --git a/amitools/fs/rdb/Recovery.py b/amitools/fs/rdb/Recovery.py new file mode 100644 index 00000000..8269f64d --- /dev/null +++ b/amitools/fs/rdb/Recovery.py @@ -0,0 +1,67 @@ +from amitools.fs.block.rdb.RDBlock import * +from .FileSystem import FileSystem +from .Partition import Partition +from .RDisk import RDisk + +class Recovery: + def __init__(self, rawblk, block_bytes, cyl_blks, reserved_cyls): + self.rawblk = rawblk + self.max_blks = cyl_blks * reserved_cyls + self.block_bytes = block_bytes + self.cyl_blks = cyl_blks + self.pnum = 0 + self.fnum = 0 + + def read(self): + res = [] + for i in range(self.max_blks): + blk = self.try_block_read(i) + if blk is not None: + res.append(blk) + self.block_map = res + + def try_block_read(self, i): + p = Partition(self.rawblk, i, self.pnum, self.cyl_blks, self.block_bytes) + if p.read(): + self.pnum += 1 + return p + f = FileSystem(self.rawblk, i, self.fnum) + if f.read(): + self.fnum += 1 + return f + r = RDBlock(self.rawblk, i) + if r.read(): + return r + return None + + def dump(self): + for i in self.block_map: + print(i.dump()) + + def build_rdisk(self): + blkdev = self.rawblk + rdb = None + parts = [] + fs = [] + for i in self.block_map: + t = type(i) + if t is RDBlock: + rdb = i + if t is Partition: + parts.append(i) + if t is FileSystem: + fs.append(i) + pass + + if rdb == None: + rdb = RDBlock(blkdev) + + rdisk = RDisk(blkdev) + rdisk.create(blkdev.geo, rdb_cyls=self.cyl_blks, blk_num=rdb.blk_num) + + for i in parts: + rdisk.parts.append(i) + for i in fs: + rdisk.fs.append(i) + + return rdisk diff --git a/amitools/tools/rdbtool.py b/amitools/tools/rdbtool.py index c6323565..74688571 100755 --- a/amitools/tools/rdbtool.py +++ b/amitools/tools/rdbtool.py @@ -7,7 +7,9 @@ import argparse import os.path import json +import logging +from amitools.util.Logging import setup_logging, add_logging_options from amitools.util.HexDump import get_hex_line from amitools.util.CommandQueue import CommandQueue from amitools.fs.FSError import FSError @@ -23,6 +25,8 @@ import amitools.util.KeyValue as KeyValue import amitools.util.ByteSize as ByteSize import amitools.util.VerTag as VerTag +from amitools.fs.rdb.Recovery import Recovery +from amitools.fs.block.Block import Block # ----- commands ----- @@ -100,13 +104,11 @@ def run(self): # close rdisk if self.rdisk != None: self.rdisk.close() - if self.args.verbose: - print("closing rdisk:", self.img) + logging.info("closing rdisk: %s", self.img) # close blkdev if self.blkdev != None: self.blkdev.close() - if self.args.verbose: - print("closing image:", self.img) + logging.info("closing image: %s", self.img) return exit_code def create_cmd(self, cclass, name, opts): @@ -115,8 +117,7 @@ def create_cmd(self, cclass, name, opts): def _open_rdisk(self): if self.rdisk == None: self.rdisk = RDisk(self.blkdev) - if self.args.verbose: - print("opening rdisk:", self.img) + logging.info("opening rdisk: %s", self.img) return self.rdisk.open() else: return True @@ -128,11 +129,9 @@ def run_first(self, cmd_line, cmd): if not cmd.has_init_blkdev(): # auto add 'open' command pre_cmd = OpenCommand(self.args, []) - if self.args.verbose: - print("auto open command:", self.cmd_line) + logging.info("auto open command: %s", self.cmd_line) exit_code = pre_cmd.run(self.blkdev, self.rdisk) - if self.args.verbose: - print("auto open exit_code:", exit_code) + logging.info("auto open exit_code: %d", exit_code) if exit_code != 0: return exit_code self.blkdev = pre_cmd.blkdev @@ -142,8 +141,7 @@ def run_first(self, cmd_line, cmd): raise IOError("No RDB Disk?") # run first command - if self.args.verbose: - print("command:", self.cmd_line) + logging.info("command: %s", self.cmd_line) if cmd.edit and self.args.read_only: raise IOError("Edit commands not allowed in read-only mode") @@ -159,14 +157,12 @@ def run_first(self, cmd_line, cmd): self.rdisk = cmd.rdisk # final exit code - if self.args.verbose: - print("exit_code:", exit_code) + logging.info("exit_code: %d", exit_code) return exit_code def run_next(self, cmd_line, cmd): self.cmd_line = cmd_line - if self.args.verbose: - print("command:", self.cmd_line) + logging.info("command: %s", self.cmd_line) # verify command if cmd.edit and self.args.read_only: raise IOError("Edit commands not allowed in read-only mode") @@ -180,8 +176,7 @@ def run_next(self, cmd_line, cmd): self.blkdev = cmd.blkdev if cmd.rdisk != None: self.rdisk = cmd.rdisk - if self.args.verbose: - print("exit_code:", exit_code) + logging.info("exit_code: %d", exit_code) return exit_code @@ -317,6 +312,7 @@ def init_rdisk(self, blkdev): rdb_cyls = 1 rdisk = RDisk(blkdev) rdisk.create(blkdev.geo, rdb_cyls=rdb_cyls) + rdisk.write() return rdisk @@ -494,6 +490,36 @@ def handle_rdisk(self, rdisk): print("") return 0 +class DiscoverCommand(Command): + def need_rdisk(self): + return False + + usage = "Usage: discover bpc= [bs=] [cyls=]" + + def handle_blkdev(self, blkdev): + self.popts = KeyValue.parse_key_value_strings(self.opts) + if "bs" in self.popts: + block_bytes = int(self.popts["bs"]) + else: + block_bytes = 512 + if "bpc" in self.popts: + cyl_blks = int(self.popts["bpc"]) + else: + print(DiscoverCommand.usage) + return -1 + if "cyls" in self.popts: + reserved_cyls = int(self.popts["cyls"]) + else: + reserved_cyls = 2 + + recovery = Recovery(blkdev, block_bytes, cyl_blks, reserved_cyls) + recovery.read() + recovery.dump() + + self.rdisk = recovery.build_rdisk() + + return 0 + # --- Free Partition Ranges @@ -1055,6 +1081,7 @@ def main(args=None, defaults=None): "change": ChangeCommand, "export": ExportCommand, "import": ImportCommand, + "discover": DiscoverCommand, } parser = argparse.ArgumentParser() @@ -1062,9 +1089,7 @@ def main(args=None, defaults=None): parser.add_argument( "command_list", nargs="+", help="command: " + ",".join(list(cmd_map.keys())) ) - parser.add_argument( - "-v", "--verbose", action="store_true", default=False, help="be more verbose" - ) + add_logging_options(parser) parser.add_argument( "-s", "--seperator", default="+", help="set the command separator char sequence" ) @@ -1091,13 +1116,25 @@ def main(args=None, defaults=None): parser.add_argument( "-t", "--dostype", default="ffs+intl", help="set default dos type" ) + parser.add_argument( + "--ignore-block-errors", + action="store_true", + default=False, + help="ignore block checksum errors", + ) + if defaults: parser.set_defaults(defaults) - args = parser.parse_args(args) + opts = parser.parse_args(args) + opts.log_format = "%(message)s" + setup_logging(opts) + + if opts.ignore_block_errors: + Block.ignore_errors = True - cmd_list = args.command_list - sep = args.seperator - queue = FSCommandQueue(args, cmd_list, sep, cmd_map) + cmd_list = opts.command_list + sep = opts.seperator + queue = FSCommandQueue(opts, cmd_list, sep, cmd_map) code = queue.run() return code diff --git a/amitools/util/Logging.py b/amitools/util/Logging.py index 94b1a0b2..36d955a1 100644 --- a/amitools/util/Logging.py +++ b/amitools/util/Logging.py @@ -34,5 +34,8 @@ def setup_logging(opts): level = logging.INFO else: level = logging.DEBUG + format = FORMAT + if opts.log_format is not None: + format = opts.log_format # setup logging - logging.basicConfig(format=FORMAT, filename=opts.log_file, level=level) + logging.basicConfig(format=format, filename=opts.log_file, level=level)