Esempio n. 1
0
class RDisk:
  def __init__(self, rawblk):
    self.rawblk = rawblk
    self.valid = False
    self.rdb = None
    self.parts = []
    self.fs = []
    self.used_blks = []
    self.max_blks = 0

  def open(self):
    # read RDB
    self.rdb = RDBlock(self.rawblk)
    if not self.rdb.read():
      self.valid = False
      return False
    # create used block list
    self.used_blks = [self.rdb.blk_num]
      
    # read partitions
    part_blk = self.rdb.part_list
    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():
        self.valid = False
        return False
      self.parts.append(p)
      # store used block
      self.used_blks.append(p.get_blk_num())
      # next partition
      part_blk = p.get_next_partition_blk()
    
    # read filesystems
    fs_blk = self.rdb.fs_list
    self.fs = []
    num = 0
    while fs_blk != PartitionBlock.no_blk:
      fs = FileSystem(self.rawblk, fs_blk, num)
      num += 1
      if not fs.read():
        self.valid = False
        return False
      self.fs.append(fs)
      # store used blocks
      self.used_blks += fs.get_blk_nums()
      # next partition
      fs_blk = fs.get_next_fs_blk()
      
    # TODO: add bad block blocks
    
    self.valid = True
    self.max_blks = self.rdb.log_drv.rdb_blk_hi + 1
    return True

  def close(self):
    pass
    
  # ----- query -----
  
  def dump(self, hex_dump=False):
    # rdb
    if self.rdb != None:
      self.rdb.dump()
    # partitions
    for p in self.parts:
      p.dump()
    # fs
    for fs in self.fs:
      fs.dump(hex_dump)
  
  def get_info(self):
    res = []
    # physical disk info
    pd = self.rdb.phy_drv
    total_blks = self.get_total_blocks()
    total_bytes = self.get_total_bytes()
    extra="heads=%d sectors=%d" % (pd.heads, pd.secs)
    res.append("PhysicalDisk:        %8d %8d  %10d  %s  %s" \
      % (0, pd.cyls-1, total_blks, ByteSize.to_byte_size_str(total_bytes), extra))
    # logical disk info
    ld = self.rdb.log_drv
    extra="rdb_blks=[%d:%d,#%d] used=[hi=%d,#%d] cyl_blks=%d" % (ld.rdb_blk_lo, ld.rdb_blk_hi, self.max_blks, ld.high_rdsk_blk, len(self.used_blks), ld.cyl_blks)
    logic_blks = self.get_logical_blocks()
    logic_bytes = self.get_logical_bytes()
    res.append("LogicalDisk:         %8d %8d  %10d  %s  %s" \
      % (ld.lo_cyl, ld.hi_cyl, logic_blks, ByteSize.to_byte_size_str(logic_bytes), extra))
    # add partitions
    for p in self.parts:
      res.append(p.get_info(logic_blks))
    # add fileystems
    for f in self.fs:
      res.append(f.get_info())
    return res
    
  def get_block_map(self):
    res = []
    for i in xrange(self.max_blks):
      blk = None
      # check partitions
      if i == 0:
        blk = "RD"
      else:
        for p in self.parts:
          if i == p.get_blk_num():
            blk = 'P%d' % p.num
            break
        if blk == None:
          # check file systems
          for f in self.fs:
            if i in f.get_blk_nums():
              blk = 'F%d' % f.num
              break
        if blk == None:
          blk = '--'
      res.append(blk)
    return res
    
  def get_logical_cylinders(self):
    ld = self.rdb.log_drv
    return ld.hi_cyl - ld.lo_cyl + 1

  def get_logical_blocks(self):
    ld = self.rdb.log_drv
    cyls = ld.hi_cyl - ld.lo_cyl + 1
    return cyls * ld.cyl_blks

  def get_logical_bytes(self, block_bytes=512):
    return self.get_logical_blocks() * block_bytes

  def get_total_blocks(self):
    pd = self.rdb.phy_drv
    return pd.cyls * pd.heads * pd.secs
  
  def get_total_bytes(self, block_bytes=512):
    return self.get_total_blocks() * block_bytes
    
  def get_cylinder_blocks(self):
    ld = self.rdb.log_drv
    return ld.cyl_blks
    
  def get_cylinder_bytes(self, block_bytes=512):
    return self.get_cylinder_blocks() * block_bytes

  def get_num_partitions(self):
    return len(self.parts)
  
  def get_partition(self, num):
    if num < len(self.parts):
      return self.parts[num]
    else:
      return None
  
  def find_partition_by_drive_name(self, name):
    lo_name = name.lower()
    num = 0
    for p in self.parts:
      drv_name = p.get_drive_name().lower()
      if drv_name == lo_name:
        return p
    return None
    
  def find_partition_by_string(self, s):
    p = self.find_partition_by_drive_name(s)
    if p != None:
      return p
    # try partition number
    try:
      num = int(s)
      return self.get_partition(num)
    except ValueError:
      return None
      
  def get_filesystem(self, num):
    if num < len(self.fs):
      return self.fs[num]
    else:
      return None
      
  def get_used_blocks(self):
    return self.used_blks

  def find_filesystem_by_string(self, s):
    # try filesystem number
    try:
      num = int(s)
      return self.get_filesystem(num)
    except ValueError:
      return None    
      
  # ----- edit -----
  
  def create(self, disk_geo, rdb_cyls=1, hi_rdb_blk=0, disk_names=None, ctrl_names=None):
    cyls = disk_geo.cyls
    heads = disk_geo.heads
    secs = disk_geo.secs
    cyl_blks = heads * secs
    rdb_blk_hi = cyl_blks * rdb_cyls - 1
    
    if disk_names != None:
      disk_vendor = disk_names[0]
      disk_product = disk_names[1]
      disk_revision = disk_names[2]
    else:
      disk_vendor = 'RDBTOOL'
      disk_product = 'IMAGE'
      disk_revision = '2012'
      
    if ctrl_names != None:
      ctrl_vendor = ctrl_names[0]
      ctrl_product = ctrl_names[1]
      ctrl_revision = ctrl_names[2]
    else:
      ctrl_vendor = ''
      ctrl_product = ''
      ctrl_revision = ''
    
    flags = 0x7
    if disk_names != None:
      flags |= 0x10
    if ctrl_names != None:
      flags |= 0x20
    
    # create RDB
    phy_drv = RDBPhysicalDrive(cyls, heads, secs)
    log_drv = RDBLogicalDrive(rdb_blk_hi=rdb_blk_hi, lo_cyl=rdb_cyls, hi_cyl=cyls-1, cyl_blks=cyl_blks, high_rdsk_blk=hi_rdb_blk)
    drv_id = RDBDriveID(disk_vendor, disk_product, disk_revision, ctrl_vendor, ctrl_product, ctrl_revision)
    self.rdb = RDBlock(self.rawblk)
    self.rdb.create(phy_drv, log_drv, drv_id, 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 get_cyl_range(self):
    log_drv = self.rdb.log_drv
    return (log_drv.lo_cyl, log_drv.hi_cyl)
  
  def check_cyl_range(self, lo_cyl, hi_cyl):
    if lo_cyl > hi_cyl:
      return False
    (lo,hi) = self.get_cyl_range()
    if not (lo_cyl >= lo and hi_cyl <= hi):
      return False
    # check partitions
    for p in self.parts:
      (lo,hi) = p.get_cyl_range()
      if not ((hi_cyl < lo) or (lo_cyl > hi)):
        return False
    return True
  
  def get_free_cyl_ranges(self):
    lohi = self.get_cyl_range()
    free = [lohi] 
    for p in self.parts:
      pr = p.get_cyl_range()
      new_free = []
      for r in free:
        # partition completely fills range
        if pr[0] == r[0] and pr[1] == r[1]:
          pass
        # partition starts at range
        elif pr[0] == r[0]:
          n = (pr[1]+1, r[1])
          new_free.append(n)
        # partition ends at range
        elif pr[1] == r[1]:
          n = (r[0], pr[0]-1)
          new_free.append(n)
        # partition inside range
        elif pr[0] > r[0] and pr[1] < r[1]:
          new_free.append((r[0], pr[0]-1))
          new_free.append((pr[1]+1,r[1]))
        else:
          new_free.append(r)
      free = new_free
    if len(free) == 0:
      return None
    return free
  
  def find_free_cyl_range_start(self, num_cyls):
    ranges = self.get_free_cyl_ranges()
    if ranges == None:
      return None
    for r in ranges:
      size = r[1] - r[0] + 1
      if num_cyls <= size:
        return r[0]
    return None
  
  # ----- manage rdb blocks -----
  
  def _has_free_rdb_blocks(self, num):
    return (len(self.used_blks) + num) <= self.max_blks
  
  def get_free_blocks(self):
    res = []
    for i in xrange(self.max_blks):
      if i not in self.used_blks:
        res.append(i)
    return res
      
  def _alloc_rdb_blocks(self, num):
    free = self.get_free_blocks()
    n = len(free) 
    if n == num:
      return free
    elif n > num:
      return free[:num]
    else:
      return None
    
  def _next_rdb_block(self):
    free = self.get_free_blocks()
    if len(free) > 0:
      return free[0]
    else:
      return None
  
  def _update_hi_blk(self):
    hi = 0
    for blk_num in self.used_blks:
      if blk_num > hi:
        hi = blk_num
    ld = self.rdb.log_drv
    ld.high_rdsk_blk = hi
  
  # ----- partition handling -----
  
  def add_partition(self, drv_name, cyl_range, dev_flags=0, flags=0, dos_type=DosType.DOS0, boot_pri=0):
    # cyl range is not free anymore or invalid
    if not self.check_cyl_range(*cyl_range):
      return False
    # no space left for partition block
    if not self._has_free_rdb_blocks(1):
      return False
    # allocate block for partition
    blk_num = self._alloc_rdb_blocks(1)[0]
    self.used_blks.append(blk_num)
    self._update_hi_blk()
    # crete a new parttion block
    pb = PartitionBlock(self.rawblk, blk_num)
    heads = self.rdb.phy_drv.heads
    blk_per_trk = self.rdb.phy_drv.secs
    dos_env = PartitionDosEnv(low_cyl=cyl_range[0], high_cyl=cyl_range[1], surfaces=heads, \
                              blk_per_trk=blk_per_trk, dos_type=dos_type, boot_pri=boot_pri)
    pb.create(drv_name, dos_env, flags=flags)
    pb.write()
    # link block
    if len(self.parts) == 0:
      # write into RDB
      self.rdb.part_list = blk_num
    else:
      # write into last partition block
      last_pb = self.parts[-1]
      last_pb.part_blk.next = blk_num
      last_pb.write()
    # always write RDB as allocated block is stored there, too  
    self.rdb.write()
    # create partition object and add to partition list
    p = Partition(self.rawblk, blk_num, len(self.parts), blk_per_trk, self)
    p.read()
    self.parts.append(p)
    return True
  
  def change_partition(self, pid, drv_name=None, dev_flags=None, dos_type=None, flags=None, boot_pri=None):
    # partition not found
    if pid < 0 or pid >= len(self.parts):
      return False
    p = self.parts[pid]
    pb = p.part_blk
    if pb == None:
      return False
    # set flags
    dirty = False
    if flags != None:
      pb.flags = flags
      dirty = True
    if drv_name != None:
      pb.drv_name = drv_name
      dirty = True
    if dev_flags != None:
      pb.dev_flags = dev_flags
      dirty = True
    # set dosenv flags
    if dos_type != None:
      pb.dos_env.dos_type = dos_type
      dirty = True
    if boot_pri != None:
      pb.dos_env.boot_pri = boot_pri
      dirty = True
    # write change
    if dirty:
      pb.write()
    return True
    
  def delete_partition(self, pid):
    # partition not found
    if pid < 0 or pid >= len(self.parts):
      return False
    # unlink partition block
    next = Block.no_blk
    if (pid + 1) < len(self.parts):
      next = self.parts[pid+1].get_blk_num()
    if pid == 0:
      self.rdb.part_list = next
    else:
      last_pb = self.parts[-1]
      last_pb.part_blk.next = next
      last_pb.write()
    # free block
    p = self.parts[pid]
    blk_num = p.get_blk_num()
    self.used_blks.remove(blk_num)
    self._update_hi_blk()    
    # write RDB
    self.rdb.write()
    # remove partition instance
    self.parts.remove(p)
    # relabel remaining parts
    num = 0
    for p in self.parts:
      p.num = num
      num += 1
    # done!
    return True
  
  # ----- file system handling ------
  
  def add_filesystem(self, data, dos_type=DosType.DOS1, version=0):
    # create a file system
    blk_num = self._next_rdb_block()
    fs_num = len(self.fs)
    fs = FileSystem(self.rawblk, blk_num, fs_num)
    # get total number of blocks for fs data
    num_blks = fs.get_total_blocks(data)
    # check if RDB has space left
    if not self._has_free_rdb_blocks(num_blks):
      return False
    # allocate blocks
    blks = self._alloc_rdb_blocks(num_blks)
    self.used_blks += blks
    self._update_hi_blk()
    # create file system
    fs.create(blks[1:], data, version, dos_type)
    fs.write()
    # link fs block
    if len(self.fs) == 0:
      # write into RDB
      self.rdb.fs_list = blk_num
    else:
      # write into last fs block
      last_fs = self.fs[-1]
      last_fs.fshd.next = blk_num
      last_fs.write(only_fshd=True)
    # update rdb: allocated blocks and optional link
    self.rdb.write()
    # add fs to list
    self.fs.append(fs)
    return True

  def delete_filesystem(self, fid):
    # check filesystem id
    if fid < 0 or fid >= len(self.fs):
      return False
    # unlink partition block
    next = Block.no_blk
    if (fid + 1) < len(self.fs):
      next = self.fs[fid+1].blk_num
    if fid == 0:
      self.rdb.fs_list = next
    else:
      last_fs = self.fs[-1]
      last_fs.fshd.next = next
      last_fs.write()
    # free block
    f = self.fs[fid]
    blks = f.get_blk_nums()
    for b in blks:
      self.used_blks.remove(b)
    self._update_hi_blk()    
    # write RDB
    self.rdb.write()
    # remove partition instance
    self.fs.remove(f)
    # relabel remaining parts
    num = 0
    for f in self.fs:
      f.num = num
      num += 1
    # done!
    return True
Esempio n. 2
0
class RDisk:
    def __init__(self, rawblk):
        self.rawblk = rawblk
        self.valid = False
        self.rdb = None
        self.parts = []
        self.fs = []
        self.used_blks = []
        self.max_blks = 0

    def open(self):
        # read RDB
        self.rdb = RDBlock(self.rawblk)
        if not self.rdb.read():
            self.valid = False
            return False
        # create used block list
        self.used_blks = [self.rdb.blk_num]

        # read partitions
        part_blk = self.rdb.part_list
        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():
                self.valid = False
                return False
            self.parts.append(p)
            # store used block
            self.used_blks.append(p.get_blk_num())
            # next partition
            part_blk = p.get_next_partition_blk()

        # read filesystems
        fs_blk = self.rdb.fs_list
        self.fs = []
        num = 0
        while fs_blk != PartitionBlock.no_blk:
            fs = FileSystem(self.rawblk, fs_blk, num)
            num += 1
            if not fs.read():
                self.valid = False
                return False
            self.fs.append(fs)
            # store used blocks
            self.used_blks += fs.get_blk_nums()
            # next partition
            fs_blk = fs.get_next_fs_blk()

        # TODO: add bad block blocks

        self.valid = True
        self.max_blks = self.rdb.log_drv.rdb_blk_hi + 1
        return True

    def close(self):
        pass

    # ----- query -----

    def dump(self, hex_dump=False):
        # rdb
        if self.rdb != None:
            self.rdb.dump()
        # partitions
        for p in self.parts:
            p.dump()
        # fs
        for fs in self.fs:
            fs.dump(hex_dump)

    def get_info(self):
        res = []
        # physical disk info
        pd = self.rdb.phy_drv
        total_blks = self.get_total_blocks()
        total_bytes = self.get_total_bytes()
        extra = "heads=%d sectors=%d" % (pd.heads, pd.secs)
        res.append("PhysicalDisk:        %8d %8d  %10d  %s  %s" \
          % (0, pd.cyls-1, total_blks, ByteSize.to_byte_size_str(total_bytes), extra))
        # logical disk info
        ld = self.rdb.log_drv
        extra = "rdb_blks=[%d:%d,#%d] used=[hi=%d,#%d] cyl_blks=%d" % (
            ld.rdb_blk_lo, ld.rdb_blk_hi, self.max_blks, ld.high_rdsk_blk,
            len(self.used_blks), ld.cyl_blks)
        logic_blks = self.get_logical_blocks()
        logic_bytes = self.get_logical_bytes()
        res.append("LogicalDisk:         %8d %8d  %10d  %s  %s" \
          % (ld.lo_cyl, ld.hi_cyl, logic_blks, ByteSize.to_byte_size_str(logic_bytes), extra))
        # add partitions
        for p in self.parts:
            res.append(p.get_info(logic_blks))
        # add fileystems
        for f in self.fs:
            res.append(f.get_info())
        return res

    def get_block_map(self):
        res = []
        for i in xrange(self.max_blks):
            blk = None
            # check partitions
            if i == 0:
                blk = "RD"
            else:
                for p in self.parts:
                    if i == p.get_blk_num():
                        blk = 'P%d' % p.num
                        break
                if blk == None:
                    # check file systems
                    for f in self.fs:
                        if i in f.get_blk_nums():
                            blk = 'F%d' % f.num
                            break
                if blk == None:
                    blk = '--'
            res.append(blk)
        return res

    def get_logical_cylinders(self):
        ld = self.rdb.log_drv
        return ld.hi_cyl - ld.lo_cyl + 1

    def get_logical_blocks(self):
        ld = self.rdb.log_drv
        cyls = ld.hi_cyl - ld.lo_cyl + 1
        return cyls * ld.cyl_blks

    def get_logical_bytes(self, block_bytes=512):
        return self.get_logical_blocks() * block_bytes

    def get_total_blocks(self):
        pd = self.rdb.phy_drv
        return pd.cyls * pd.heads * pd.secs

    def get_total_bytes(self, block_bytes=512):
        return self.get_total_blocks() * block_bytes

    def get_cylinder_blocks(self):
        ld = self.rdb.log_drv
        return ld.cyl_blks

    def get_cylinder_bytes(self, block_bytes=512):
        return self.get_cylinder_blocks() * block_bytes

    def get_num_partitions(self):
        return len(self.parts)

    def get_partition(self, num):
        if num < len(self.parts):
            return self.parts[num]
        else:
            return None

    def find_partition_by_drive_name(self, name):
        lo_name = name.lower()
        num = 0
        for p in self.parts:
            drv_name = p.get_drive_name().lower()
            if drv_name == lo_name:
                return p
        return None

    def find_partition_by_string(self, s):
        p = self.find_partition_by_drive_name(s)
        if p != None:
            return p
        # try partition number
        try:
            num = int(s)
            return self.get_partition(num)
        except ValueError:
            return None

    def get_filesystem(self, num):
        if num < len(self.fs):
            return self.fs[num]
        else:
            return None

    def get_used_blocks(self):
        return self.used_blks

    def find_filesystem_by_string(self, s):
        # try filesystem number
        try:
            num = int(s)
            return self.get_filesystem(num)
        except ValueError:
            return None

    # ----- edit -----

    def create(self,
               disk_geo,
               rdb_cyls=1,
               hi_rdb_blk=0,
               disk_names=None,
               ctrl_names=None):
        cyls = disk_geo.cyls
        heads = disk_geo.heads
        secs = disk_geo.secs
        cyl_blks = heads * secs
        rdb_blk_hi = cyl_blks * rdb_cyls - 1

        if disk_names != None:
            disk_vendor = disk_names[0]
            disk_product = disk_names[1]
            disk_revision = disk_names[2]
        else:
            disk_vendor = 'RDBTOOL'
            disk_product = 'IMAGE'
            disk_revision = '2012'

        if ctrl_names != None:
            ctrl_vendor = ctrl_names[0]
            ctrl_product = ctrl_names[1]
            ctrl_revision = ctrl_names[2]
        else:
            ctrl_vendor = ''
            ctrl_product = ''
            ctrl_revision = ''

        flags = 0x7
        if disk_names != None:
            flags |= 0x10
        if ctrl_names != None:
            flags |= 0x20

        # create RDB
        phy_drv = RDBPhysicalDrive(cyls, heads, secs)
        log_drv = RDBLogicalDrive(rdb_blk_hi=rdb_blk_hi,
                                  lo_cyl=rdb_cyls,
                                  hi_cyl=cyls - 1,
                                  cyl_blks=cyl_blks,
                                  high_rdsk_blk=hi_rdb_blk)
        drv_id = RDBDriveID(disk_vendor, disk_product, disk_revision,
                            ctrl_vendor, ctrl_product, ctrl_revision)
        self.rdb = RDBlock(self.rawblk)
        self.rdb.create(phy_drv, log_drv, drv_id, 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 get_cyl_range(self):
        log_drv = self.rdb.log_drv
        return (log_drv.lo_cyl, log_drv.hi_cyl)

    def check_cyl_range(self, lo_cyl, hi_cyl):
        if lo_cyl > hi_cyl:
            return False
        (lo, hi) = self.get_cyl_range()
        if not (lo_cyl >= lo and hi_cyl <= hi):
            return False
        # check partitions
        for p in self.parts:
            (lo, hi) = p.get_cyl_range()
            if not ((hi_cyl < lo) or (lo_cyl > hi)):
                return False
        return True

    def get_free_cyl_ranges(self):
        lohi = self.get_cyl_range()
        free = [lohi]
        for p in self.parts:
            pr = p.get_cyl_range()
            new_free = []
            for r in free:
                # partition completely fills range
                if pr[0] == r[0] and pr[1] == r[1]:
                    pass
                # partition starts at range
                elif pr[0] == r[0]:
                    n = (pr[1] + 1, r[1])
                    new_free.append(n)
                # partition ends at range
                elif pr[1] == r[1]:
                    n = (r[0], pr[0] - 1)
                    new_free.append(n)
                # partition inside range
                elif pr[0] > r[0] and pr[1] < r[1]:
                    new_free.append((r[0], pr[0] - 1))
                    new_free.append((pr[1] + 1, r[1]))
                else:
                    new_free.append(r)
            free = new_free
        if len(free) == 0:
            return None
        return free

    def find_free_cyl_range_start(self, num_cyls):
        ranges = self.get_free_cyl_ranges()
        if ranges == None:
            return None
        for r in ranges:
            size = r[1] - r[0] + 1
            if num_cyls <= size:
                return r[0]
        return None

    # ----- manage rdb blocks -----

    def _has_free_rdb_blocks(self, num):
        return (len(self.used_blks) + num) <= self.max_blks

    def get_free_blocks(self):
        res = []
        for i in xrange(self.max_blks):
            if i not in self.used_blks:
                res.append(i)
        return res

    def _alloc_rdb_blocks(self, num):
        free = self.get_free_blocks()
        n = len(free)
        if n == num:
            return free
        elif n > num:
            return free[:num]
        else:
            return None

    def _next_rdb_block(self):
        free = self.get_free_blocks()
        if len(free) > 0:
            return free[0]
        else:
            return None

    def _update_hi_blk(self):
        hi = 0
        for blk_num in self.used_blks:
            if blk_num > hi:
                hi = blk_num
        ld = self.rdb.log_drv
        ld.high_rdsk_blk = hi

    # ----- partition handling -----

    def _adjust_dos_env(self, dos_env, more_dos_env):
        if more_dos_env is None:
            return
        for p in more_dos_env:
            key = p[0]
            if hasattr(dos_env, key):
                setattr(dos_env, key, int(p[1]))

    def add_partition(self,
                      drv_name,
                      cyl_range,
                      dev_flags=0,
                      flags=0,
                      dos_type=DosType.DOS0,
                      boot_pri=0,
                      more_dos_env=None):
        # cyl range is not free anymore or invalid
        if not self.check_cyl_range(*cyl_range):
            return False
        # no space left for partition block
        if not self._has_free_rdb_blocks(1):
            return False
        # allocate block for partition
        blk_num = self._alloc_rdb_blocks(1)[0]
        self.used_blks.append(blk_num)
        self._update_hi_blk()
        # crete a new parttion block
        pb = PartitionBlock(self.rawblk, blk_num)
        # setup dos env
        heads = self.rdb.phy_drv.heads
        blk_per_trk = self.rdb.phy_drv.secs * heads
        dos_env = PartitionDosEnv(low_cyl=cyl_range[0], high_cyl=cyl_range[1], surfaces=heads, \
                                  blk_per_trk=blk_per_trk, dos_type=dos_type, boot_pri=boot_pri)
        self._adjust_dos_env(dos_env, more_dos_env)
        pb.create(drv_name, dos_env, flags=flags)
        pb.write()
        # link block
        if len(self.parts) == 0:
            # write into RDB
            self.rdb.part_list = blk_num
        else:
            # write into last partition block
            last_pb = self.parts[-1]
            last_pb.part_blk.next = blk_num
            last_pb.write()
        # always write RDB as allocated block is stored there, too
        self.rdb.write()
        # flush out all changes before we read again
        self.rawblk.flush()
        # create partition object and add to partition list
        p = Partition(self.rawblk, blk_num, len(self.parts), blk_per_trk, self)
        p.read()
        self.parts.append(p)
        return True

    def change_partition(self,
                         pid,
                         drv_name=None,
                         dev_flags=None,
                         dos_type=None,
                         flags=None,
                         boot_pri=None,
                         more_dos_env=None):
        # partition not found
        if pid < 0 or pid >= len(self.parts):
            return False
        p = self.parts[pid]
        pb = p.part_blk
        if pb == None:
            return False
        # set flags
        dirty = False
        if flags != None:
            pb.flags = flags
            dirty = True
        if drv_name != None:
            pb.drv_name = drv_name
            dirty = True
        if dev_flags != None:
            pb.dev_flags = dev_flags
            dirty = True
        # set dosenv flags
        if dos_type != None:
            pb.dos_env.dos_type = dos_type
            dirty = True
        if boot_pri != None:
            pb.dos_env.boot_pri = boot_pri
            dirty = True
        # update dos env
        if more_dos_env is not None:
            self._adjust_dos_env(pb.dos_env, more_dos_env)
            dirty = True
        # write change
        if dirty:
            pb.write()
        return True

    def delete_partition(self, pid):
        # partition not found
        if pid < 0 or pid >= len(self.parts):
            return False
        # unlink partition block
        next = Block.no_blk
        if (pid + 1) < len(self.parts):
            next = self.parts[pid + 1].get_blk_num()
        if pid == 0:
            self.rdb.part_list = next
        else:
            last_pb = self.parts[-1]
            last_pb.part_blk.next = next
            last_pb.write()
        # free block
        p = self.parts[pid]
        blk_num = p.get_blk_num()
        self.used_blks.remove(blk_num)
        self._update_hi_blk()
        # write RDB
        self.rdb.write()
        # remove partition instance
        self.parts.remove(p)
        # relabel remaining parts
        num = 0
        for p in self.parts:
            p.num = num
            num += 1
        # done!
        return True

    # ----- file system handling ------

    def add_filesystem(self,
                       data,
                       dos_type=DosType.DOS1,
                       version=0,
                       dev_flags=None):
        # create a file system
        blk_num = self._next_rdb_block()
        fs_num = len(self.fs)
        fs = FileSystem(self.rawblk, blk_num, fs_num)
        # get total number of blocks for fs data
        num_blks = fs.get_total_blocks(data)
        # check if RDB has space left
        if not self._has_free_rdb_blocks(num_blks):
            return False
        # allocate blocks
        blks = self._alloc_rdb_blocks(num_blks)
        self.used_blks += blks
        self._update_hi_blk()
        # create file system
        fs.create(blks[1:], data, version, dos_type, dev_flags)
        fs.write()
        # link fs block
        if len(self.fs) == 0:
            # write into RDB
            self.rdb.fs_list = blk_num
        else:
            # write into last fs block
            last_fs = self.fs[-1]
            last_fs.fshd.next = blk_num
            last_fs.write(only_fshd=True)
        # update rdb: allocated blocks and optional link
        self.rdb.write()
        # add fs to list
        self.fs.append(fs)
        return True

    def delete_filesystem(self, fid):
        # check filesystem id
        if fid < 0 or fid >= len(self.fs):
            return False
        # unlink partition block
        next = Block.no_blk
        if (fid + 1) < len(self.fs):
            next = self.fs[fid + 1].blk_num
        if fid == 0:
            self.rdb.fs_list = next
        else:
            last_fs = self.fs[-1]
            last_fs.fshd.next = next
            last_fs.write()
        # free block
        f = self.fs[fid]
        blks = f.get_blk_nums()
        for b in blks:
            self.used_blks.remove(b)
        self._update_hi_blk()
        # write RDB
        self.rdb.write()
        # remove partition instance
        self.fs.remove(f)
        # relabel remaining parts
        num = 0
        for f in self.fs:
            f.num = num
            num += 1
        # done!
        return True
Esempio n. 3
0
class RDisk:
    def __init__(self, rawblk):
        self.rawblk = rawblk
        self.valid = False
        self.rdb = None
        self.parts = []
        self.fs = []
        self.used_blks = []
        self.max_blks = 0
        self.block_bytes = 0

    def peek_block_size(self):
        self.rdb = RDBlock(self.rawblk)
        if not self.rdb.read():
            self.valid = False
            return None
        return self.rdb.block_size

    def open(self):
        # read RDB
        if not self.rdb:
            self.rdb = RDBlock(self.rawblk)
            if not self.rdb.read():
                self.valid = False
                return False

        # check block size of rdb vs. raw block device
        if self.rdb.block_size != self.rawblk.block_bytes:
            raise ValueError("block size mismatch: rdb=%d != device=%d" %
                             (self.rdb.block_size, self.rawblk.block_bytes))
        self.block_bytes = self.rdb.block_size

        # create used block list
        self.used_blks = [self.rdb.blk_num]

        # read partitions
        part_blk = self.rdb.part_list
        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():
                self.valid = False
                return False
            self.parts.append(p)
            # store used block
            self.used_blks.append(p.get_blk_num())
            # next partition
            part_blk = p.get_next_partition_blk()

        # read filesystems
        fs_blk = self.rdb.fs_list
        self.fs = []
        num = 0
        while fs_blk != PartitionBlock.no_blk:
            fs = FileSystem(self.rawblk, fs_blk, num)
            num += 1
            if not fs.read():
                self.valid = False
                return False
            self.fs.append(fs)
            # store used blocks
            self.used_blks += fs.get_blk_nums()
            # next partition
            fs_blk = fs.get_next_fs_blk()

        # TODO: add bad block blocks

        self.valid = True
        self.max_blks = self.rdb.log_drv.rdb_blk_hi + 1
        return True

    def close(self):
        self.rdb = None
        self.valid = False

    # ----- query -----

    def dump(self, hex_dump=False):
        # rdb
        if self.rdb != None:
            self.rdb.dump()
        # partitions
        for p in self.parts:
            p.dump()
        # fs
        for fs in self.fs:
            fs.dump(hex_dump)

    def get_info(self, part_name=None):
        res = []
        part = None
        # only show single partition
        if part_name:
            part = self.find_partition_by_string(part_name)
            if not part:
                res.append("Partition not found: %s!" % part_name)
                return res
        # physical disk info
        if part:
            logic_blks = self.get_logical_blocks()
            res.append(part.get_info(logic_blks))
            extra = part.get_extra_infos()
            for e in extra:
                res.append("%s%s" % (" " * 70, e))
        else:
            pd = self.rdb.phy_drv
            total_blks = self.get_total_blocks()
            total_bytes = self.get_total_bytes()
            bs = self.rdb.block_size
            extra = "heads=%d sectors=%d block_size=%d" % (pd.heads, pd.secs,
                                                           bs)
            res.append("PhysicalDisk:        %8d %8d  %10d  %s  %s" % (
                0,
                pd.cyls - 1,
                total_blks,
                ByteSize.to_byte_size_str(total_bytes),
                extra,
            ))
            # logical disk info
            ld = self.rdb.log_drv
            extra = "rdb_blks=[%d:%d,#%d] used=[hi=%d,#%d] cyl_blks=%d" % (
                ld.rdb_blk_lo,
                ld.rdb_blk_hi,
                self.max_blks,
                ld.high_rdsk_blk,
                len(self.used_blks),
                ld.cyl_blks,
            )
            logic_blks = self.get_logical_blocks()
            logic_bytes = self.get_logical_bytes()
            res.append("LogicalDisk:         %8d %8d  %10d  %s  %s" % (
                ld.lo_cyl,
                ld.hi_cyl,
                logic_blks,
                ByteSize.to_byte_size_str(logic_bytes),
                extra,
            ))
            # add partitions
            for p in self.parts:
                res.append(p.get_info(logic_blks))
                extra = p.get_extra_infos()
                for e in extra:
                    res.append("%s%s" % (" " * 70, e))
            # add fileystems
            for f in self.fs:
                res.append(f.get_info())
        return res

    def get_block_map(self):
        res = []
        for i in range(self.max_blks):
            blk = None
            # check partitions
            if i == 0:
                blk = "RD"
            else:
                for p in self.parts:
                    if i == p.get_blk_num():
                        blk = "P%d" % p.num
                        break
                if blk == None:
                    # check file systems
                    for f in self.fs:
                        if i in f.get_blk_nums():
                            blk = "F%d" % f.num
                            break
                if blk == None:
                    blk = "--"
            res.append(blk)
        return res

    def get_logical_cylinders(self):
        ld = self.rdb.log_drv
        return ld.hi_cyl - ld.lo_cyl + 1

    def get_logical_blocks(self):
        ld = self.rdb.log_drv
        cyls = ld.hi_cyl - ld.lo_cyl + 1
        return cyls * ld.cyl_blks

    def get_logical_bytes(self):
        return self.get_logical_blocks() * self.block_bytes

    def get_cyls_heads_secs(self):
        pd = self.rdb.phy_drv
        return pd.cyls, pd.heads, pd.secs

    def get_total_blocks(self):
        pd = self.rdb.phy_drv
        return pd.cyls * pd.heads * pd.secs

    def get_total_bytes(self):
        return self.get_total_blocks() * self.block_bytes

    def get_cylinder_blocks(self):
        ld = self.rdb.log_drv
        return ld.cyl_blks

    def get_cylinder_bytes(self):
        return self.get_cylinder_blocks() * self.block_bytes

    def get_num_partitions(self):
        return len(self.parts)

    def get_partition(self, num):
        if num < len(self.parts):
            return self.parts[num]
        else:
            return None

    def find_partition_by_drive_name(self, name):
        lo_name = name.lower()
        num = 0
        for p in self.parts:
            drv_name = p.get_drive_name().get_unicode().lower()
            if drv_name == lo_name:
                return p
        return None

    def find_partition_by_string(self, s):
        p = self.find_partition_by_drive_name(s)
        if p != None:
            return p
        # try partition number
        try:
            num = int(s)
            return self.get_partition(num)
        except ValueError:
            return None

    def get_filesystem(self, num):
        if num < len(self.fs):
            return self.fs[num]
        else:
            return None

    def get_used_blocks(self):
        return self.used_blks

    def find_filesystem_by_string(self, s):
        # try filesystem number
        try:
            num = int(s)
            return self.get_filesystem(num)
        except ValueError:
            return None

    # ----- edit -----

    def create(self,
               disk_geo,
               rdb_cyls=1,
               hi_rdb_blk=0,
               disk_names=None,
               ctrl_names=None):
        cyls = disk_geo.cyls
        heads = disk_geo.heads
        secs = disk_geo.secs
        cyl_blks = heads * secs
        rdb_blk_hi = cyl_blks * rdb_cyls - 1

        if disk_names != None:
            disk_vendor = disk_names[0]
            disk_product = disk_names[1]
            disk_revision = disk_names[2]
        else:
            disk_vendor = "RDBTOOL"
            disk_product = "IMAGE"
            disk_revision = "2012"

        if ctrl_names != None:
            ctrl_vendor = ctrl_names[0]
            ctrl_product = ctrl_names[1]
            ctrl_revision = ctrl_names[2]
        else:
            ctrl_vendor = ""
            ctrl_product = ""
            ctrl_revision = ""

        flags = 0x7
        if disk_names != None:
            flags |= 0x10
        if ctrl_names != None:
            flags |= 0x20

        # create RDB
        phy_drv = RDBPhysicalDrive(cyls, heads, secs)
        log_drv = RDBLogicalDrive(
            rdb_blk_hi=rdb_blk_hi,
            lo_cyl=rdb_cyls,
            hi_cyl=cyls - 1,
            cyl_blks=cyl_blks,
            high_rdsk_blk=hi_rdb_blk,
        )
        drv_id = RDBDriveID(
            disk_vendor,
            disk_product,
            disk_revision,
            ctrl_vendor,
            ctrl_product,
            ctrl_revision,
        )
        self.block_bytes = self.rawblk.block_bytes
        self.rdb = RDBlock(self.rawblk)
        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 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
        if not new_lo_cyl:
            new_lo_cyl = self.rdb.log_drv.lo_cyl
        if not new_hi_cyl:
            new_hi_cyl = self.rdb.log_drv.hi_cyl
        # same range? silently ignore and return true
        if (new_lo_cyl == self.rdb.log_drv.lo_cyl
                and new_hi_cyl == self.rdb.log_drv.hi_cyl):
            return
        # invalid range
        if new_lo_cyl > new_hi_cyl:
            raise ValueError("Invalid cylinder range: %d > %d" %
                             (new_lo_cyl, new_hi_cyl))
        # check partitions
        for p in self.parts:
            (lo, hi) = p.get_cyl_range()
            if lo < new_lo_cyl or hi > new_hi_cyl:
                raise ValueError("Partition %d does not fit in new range!" %
                                 p.num)
        # need to adjust phyisical disc?
        if new_hi_cyl != self.rdb.phy_drv.cyls - 1:
            if adjust_physical:
                self.rdb.phy_drv.cyls = new_hi_cyl + 1
            else:
                # not allowed to grow physical disk. abort!
                raise ValueError("Need to adjust physical disk!")
        # adjust logical range
        self.rdb.log_drv.lo_cyl = new_lo_cyl
        self.rdb.log_drv.hi_cyl = new_hi_cyl
        # update block
        self.rdb.write()

    def remap(self, new_heads=None, new_secs=None):
        """Change the RDB structure to use new head and sector values"""
        old_heads = self.rdb.phy_drv.heads
        old_secs = self.rdb.phy_drv.secs
        if not new_heads:
            new_heads = old_heads
        if not new_secs:
            new_secs = old_secs
        # nothing to do?
        if new_heads == old_heads and new_secs == old_secs:
            return
        # validate if remapping is possible
        old_cyl_blocks = old_heads * old_secs
        new_cyl_blocks = new_heads * new_secs
        if old_cyl_blocks > new_cyl_blocks:
            # finer resolution wanted
            # check that factor is integer multiple
            if old_cyl_blocks % new_cyl_blocks != 0:
                raise ValueError("Smaller cylinder blocks are not a multiple!")
            factor = old_cyl_blocks // new_cyl_blocks

            # no further checks required

            # conversion op
            def op(x):
                return x * factor

        else:
            # coarser resolution wanted
            # check that factor is integer multiple
            if new_cyl_blocks & old_cyl_blocks != 0:
                raise ValueError("Larger cylinder blocks are not a multiple!")
            factor = new_cyl_blocks // old_cyl_blocks

            def check(x):
                return x % factor == 0

            # check physical drive range
            pd = self.rdb.phy_drv
            if not check(pd.cyls):
                raise ValueError("Physical Range %d can't be remapped!" %
                                 pd.cyls)
            # check logical drive range
            ld = self.rdb.log_drv
            if not check(ld.lo_cyl) or not check(ld.hi_cyl):
                raise ValueError("Logical Range [%d,%d] can't be remapped!" %
                                 (ld.lo_cyl, ld.hi_cyl))
            # check partition lo/hi cyls
            for p in self.parts:
                lo, hi = p.get_cyl_range()
                if not check(lo) or not check(hi):
                    raise ValueError(
                        "Partition %d [%d,%d] can't be remapped!" %
                        (p.num, lo, hi))

            # conversion op
            def op(x):
                return x // factor

        # new blocks per cylinder
        cyl_blks = new_heads * new_secs
        # process RDB's cyl values with 'op':
        # change physical disk
        pd = self.rdb.phy_drv
        pd.heads = new_heads
        pd.secs = new_secs
        pd.cyls = op(pd.cyls)
        # change logical disk
        ld = self.rdb.log_drv
        ld.lo_cyl = op(ld.lo_cyl)
        ld.hi_cyl = op(ld.hi_cyl)
        ld.cyl_blks = cyl_blks
        # update rdb
        self.rdb.write()
        # change partitions
        for p in self.parts:
            p.cyl_blks = cyl_blks
            de = p.part_blk.dos_env
            de.low_cyl = op(de.low_cyl)
            de.high_cyl = op(de.high_cyl)
            de.blk_per_trk = cyl_blks
            p.part_blk.write()
        # done

    def get_cyl_range(self):
        log_drv = self.rdb.log_drv
        return (log_drv.lo_cyl, log_drv.hi_cyl)

    def check_cyl_range(self, lo_cyl, hi_cyl):
        if lo_cyl > hi_cyl:
            return False
        (lo, hi) = self.get_cyl_range()
        if not (lo_cyl >= lo and hi_cyl <= hi):
            return False
        # check partitions
        for p in self.parts:
            (lo, hi) = p.get_cyl_range()
            if not ((hi_cyl < lo) or (lo_cyl > hi)):
                return False
        return True

    def get_free_cyl_ranges(self):
        lohi = self.get_cyl_range()
        free = [lohi]
        for p in self.parts:
            pr = p.get_cyl_range()
            new_free = []
            for r in free:
                # partition completely fills range
                if pr[0] == r[0] and pr[1] == r[1]:
                    pass
                # partition starts at range
                elif pr[0] == r[0]:
                    n = (pr[1] + 1, r[1])
                    new_free.append(n)
                # partition ends at range
                elif pr[1] == r[1]:
                    n = (r[0], pr[0] - 1)
                    new_free.append(n)
                # partition inside range
                elif pr[0] > r[0] and pr[1] < r[1]:
                    new_free.append((r[0], pr[0] - 1))
                    new_free.append((pr[1] + 1, r[1]))
                else:
                    new_free.append(r)
            free = new_free
        if len(free) == 0:
            return None
        return free

    def find_free_cyl_range_start(self, num_cyls):
        ranges = self.get_free_cyl_ranges()
        if ranges == None:
            return None
        for r in ranges:
            size = r[1] - r[0] + 1
            if num_cyls <= size:
                return r[0]
        return None

    # ----- manage rdb blocks -----

    def _has_free_rdb_blocks(self, num):
        return (len(self.used_blks) + num) <= self.max_blks

    def get_free_blocks(self):
        res = []
        for i in range(self.max_blks):
            if i not in self.used_blks:
                res.append(i)
        return res

    def _alloc_rdb_blocks(self, num):
        free = self.get_free_blocks()
        n = len(free)
        if n == num:
            return free
        elif n > num:
            return free[:num]
        else:
            return None

    def _next_rdb_block(self):
        free = self.get_free_blocks()
        if len(free) > 0:
            return free[0]
        else:
            return None

    def _update_hi_blk(self):
        hi = 0
        for blk_num in self.used_blks:
            if blk_num > hi:
                hi = blk_num
        ld = self.rdb.log_drv
        ld.high_rdsk_blk = hi

    # ----- partition handling -----

    def _adjust_dos_env(self, dos_env, more_dos_env):
        if more_dos_env is None:
            return
        for p in more_dos_env:
            key = p[0]
            if hasattr(dos_env, key):
                setattr(dos_env, key, int(p[1]))

    def add_partition(
        self,
        drv_name,
        cyl_range,
        dev_flags=0,
        flags=0,
        dos_type=DosType.DOS0,
        boot_pri=0,
        more_dos_env=None,
        fs_block_size=None,
    ):
        # cyl range is not free anymore or invalid
        if not self.check_cyl_range(*cyl_range):
            raise ValueError("Partition range is not free!")
        # no space left for partition block
        if not self._has_free_rdb_blocks(1):
            raise ValueError("No space left in RDB for partition block!")
        # allocate block for partition
        blk_num = self._alloc_rdb_blocks(1)[0]
        self.used_blks.append(blk_num)
        self._update_hi_blk()
        # crete a new parttion block
        pb = PartitionBlock(self.rawblk, blk_num)
        # setup fs block size (may be multiple sectors)
        if not fs_block_size:
            fs_block_size = self.block_bytes
        sec_per_blk = int(fs_block_size // self.block_bytes)
        if sec_per_blk < 1 or sec_per_blk > 16:
            raise ValueError("Invalid sec_per_blk: " + sec_per_blk)
        # block size in longs
        bsl = self.block_bytes >> 2
        # setup dos env
        heads = self.rdb.phy_drv.heads
        blk_per_trk = self.rdb.phy_drv.secs
        dos_env = PartitionDosEnv(
            low_cyl=cyl_range[0],
            high_cyl=cyl_range[1],
            surfaces=heads,
            blk_per_trk=blk_per_trk,
            dos_type=dos_type,
            boot_pri=boot_pri,
            block_size=bsl,
            sec_per_blk=sec_per_blk,
        )
        self._adjust_dos_env(dos_env, more_dos_env)
        pb.create(drv_name, dos_env, flags=flags)
        pb.write()
        # link block
        if len(self.parts) == 0:
            # write into RDB
            self.rdb.part_list = blk_num
        else:
            # write into last partition block
            last_pb = self.parts[-1]
            last_pb.part_blk.next = blk_num
            last_pb.write()
        # always write RDB as allocated block is stored there, too
        self.rdb.write()
        # flush out all changes before we read again
        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.read()
        self.parts.append(p)
        return p

    def change_partition(
        self,
        pid,
        drv_name=None,
        dev_flags=None,
        dos_type=None,
        flags=None,
        boot_pri=None,
        more_dos_env=None,
        fs_block_size=None,
    ):
        # partition not found
        if pid < 0 or pid >= len(self.parts):
            return False
        p = self.parts[pid]
        pb = p.part_blk
        if pb == None:
            return False
        # set flags
        dirty = False
        if flags != None:
            pb.flags = flags
            dirty = True
        if drv_name != None:
            pb.drv_name = drv_name
            dirty = True
        if dev_flags != None:
            pb.dev_flags = dev_flags
            dirty = True
        # set dosenv flags
        if dos_type != None:
            pb.dos_env.dos_type = dos_type
            dirty = True
        if boot_pri != None:
            pb.dos_env.boot_pri = boot_pri
            dirty = True
        # change fs block size
        if fs_block_size:
            sec_per_blk = int(fs_block_size // self.block_bytes)
            if sec_per_blk < 1 or sec_per_blk > 16:
                raise ValueError("Invalid sec_per_blk: " + sec_per_blk)
            pb.dos_env.sec_per_blk = sec_per_blk
        # update dos env
        if more_dos_env is not None:
            self._adjust_dos_env(pb.dos_env, more_dos_env)
            dirty = True
        # write change
        if dirty:
            pb.write()
        return True

    def delete_partition(self, pid):
        # partition not found
        if pid < 0 or pid >= len(self.parts):
            return False
        # unlink partition block
        next = Block.no_blk
        if (pid + 1) < len(self.parts):
            next = self.parts[pid + 1].get_blk_num()
        if pid == 0:
            self.rdb.part_list = next
        else:
            last_pb = self.parts[-1]
            last_pb.part_blk.next = next
            last_pb.write()
        # free block
        p = self.parts[pid]
        blk_num = p.get_blk_num()
        self.used_blks.remove(blk_num)
        self._update_hi_blk()
        # write RDB
        self.rdb.write()
        # remove partition instance
        self.parts.remove(p)
        # relabel remaining parts
        num = 0
        for p in self.parts:
            p.num = num
            num += 1
        # done!
        return True

    # ----- file system handling ------

    def add_filesystem(self,
                       data,
                       dos_type=DosType.DOS1,
                       version=0,
                       dev_flags=None):
        # create a file system
        blk_num = self._next_rdb_block()
        fs_num = len(self.fs)
        fs = FileSystem(self.rawblk, blk_num, fs_num)
        # get total number of blocks for fs data
        num_blks = fs.get_total_blocks(data)
        # check if RDB has space left
        if not self._has_free_rdb_blocks(num_blks):
            return False
        # allocate blocks
        blks = self._alloc_rdb_blocks(num_blks)
        self.used_blks += blks
        self._update_hi_blk()
        # create file system
        fs.create(blks[1:], data, version, dos_type, dev_flags)
        fs.write()
        # link fs block
        if len(self.fs) == 0:
            # write into RDB
            self.rdb.fs_list = blk_num
        else:
            # write into last fs block
            last_fs = self.fs[-1]
            last_fs.fshd.next = blk_num
            last_fs.write(only_fshd=True)
        # update rdb: allocated blocks and optional link
        self.rdb.write()
        # add fs to list
        self.fs.append(fs)
        return True

    def delete_filesystem(self, fid):
        # check filesystem id
        if fid < 0 or fid >= len(self.fs):
            return False
        # unlink partition block
        next = Block.no_blk
        if (fid + 1) < len(self.fs):
            next = self.fs[fid + 1].blk_num
        if fid == 0:
            self.rdb.fs_list = next
        else:
            last_fs = self.fs[-1]
            last_fs.fshd.next = next
            last_fs.write()
        # free block
        f = self.fs[fid]
        blks = f.get_blk_nums()
        for b in blks:
            self.used_blks.remove(b)
        self._update_hi_blk()
        # write RDB
        self.rdb.write()
        # remove partition instance
        self.fs.remove(f)
        # relabel remaining parts
        num = 0
        for f in self.fs:
            f.num = num
            num += 1
        # done!
        return True