aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authornthnluu <nate1299@me.com>2024-01-28 21:20:27 -0500
committernthnluu <nate1299@me.com>2024-01-28 21:20:27 -0500
commitc63f340d90800895f007de64b7d2d14624263331 (patch)
tree2c0849fa597dd6da831c8707b6f2603403778d7b /tools
Created student weenix repository
Diffstat (limited to 'tools')
-rw-r--r--tools/fsmaker/.gitignore6
-rw-r--r--tools/fsmaker/api.py653
-rw-r--r--tools/fsmaker/sh.py720
3 files changed, 1379 insertions, 0 deletions
diff --git a/tools/fsmaker/.gitignore b/tools/fsmaker/.gitignore
new file mode 100644
index 0000000..2a83cb5
--- /dev/null
+++ b/tools/fsmaker/.gitignore
@@ -0,0 +1,6 @@
+cscope.files
+cscope.out
+cscope.po.out
+cscope.in.out
+
+fsmaker
diff --git a/tools/fsmaker/api.py b/tools/fsmaker/api.py
new file mode 100644
index 0000000..07e6440
--- /dev/null
+++ b/tools/fsmaker/api.py
@@ -0,0 +1,653 @@
+import os
+import math
+import struct
+
+S5_MAGIC = 0x727f
+S5_CURRENT_VERSION = 3
+S5_BLOCK_SIZE = 4096
+
+S5_NBLKS_PER_FNODE = 30
+S5_NDIRECT_BLOCKS = 28
+S5_MAX_FILE_BLOCKS = S5_NDIRECT_BLOCKS + math.floor(S5_BLOCK_SIZE / 4)
+S5_MAX_FILE_SIZE = S5_MAX_FILE_BLOCKS * S5_BLOCK_SIZE
+
+S5_NAME_LEN = 28
+S5_DIRENT_SIZE = S5_NAME_LEN + 4
+
+S5_INODE_SIZE = 16 + S5_NDIRECT_BLOCKS * 4
+S5_INODES_PER_BLOCK = S5_BLOCK_SIZE / S5_INODE_SIZE
+
+S5_TYPE_FREE = 0x0
+S5_TYPE_DATA = 0x1
+S5_TYPE_DIR = 0x2
+S5_TYPE_CHR = 0x4
+S5_TYPE_BLK = 0x8
+S5_TYPES = set([ S5_TYPE_FREE, S5_TYPE_DATA, S5_TYPE_DIR, S5_TYPE_CHR, S5_TYPE_BLK ])
+
+class S5fsException(Exception):
+
+ def __init__(self, msg):
+ self._msg = msg
+
+ def __str__(self):
+ return self._msg
+
+class S5fsDiskSpaceException(S5fsException):
+
+ def __init__(self):
+ S5fsException.__init__(self, "out of disk space")
+
+class Block:
+
+ def __init__(self, simdisk, offset, blockno):
+ self._simdisk = simdisk
+ self._offset = offset
+ self._blockno = blockno
+
+ def get_blockno(self):
+ return self._blockno
+
+ def read(self, offset=0, size=None):
+ if (size == None):
+ size = S5_BLOCK_SIZE - offset
+ if (offset + size > S5_BLOCK_SIZE):
+ raise S5fsException("cannot read to {0}, exceeds block size of {1}".format(offset + size, S5_BLOCK_SIZE))
+ self._simdisk._simfile.seek(int(self._offset + offset))
+ res = self._simdisk._simfile.read(size)
+ return res
+
+ def write(self, offset, data):
+ if (offset + len(data) > S5_BLOCK_SIZE):
+ raise S5fsException("cannot write to {0}, exceeds block size of {1}".format(offset + len(data), S5_BLOCK_SIZE))
+ self._simdisk._simfile.seek(int(self._offset + offset))
+ res = self._simdisk._simfile.write(data)
+
+ def zero(self):
+ self._simdisk._simfile.seek(self._offset)
+ for i in xrange(S5_BLOCK_SIZE):
+ self._simdisk._simfile.write('\0')
+
+ def free(self):
+ if (self._simdisk.get_nfree() < S5_NBLKS_PER_FNODE - 1):
+ self._simdisk.set_free_block(self._simdisk.get_nfree(), self._blockno)
+ self._simdisk.set_nfree(self._simdisk.get_nfree() + 1)
+ else:
+ for i in xrange(S5_NBLKS_PER_FNODE - 1):
+ self.write(i * 4, struct.pack("I", self._simdisk.get_free_block(i)))
+ self.write((S5_NBLKS_PER_FNODE - 1) * 4, struct.pack("I", self._simdisk.get_last_free_block()))
+ self._simdisk.set_last_free_block(self._blockno)
+ self._simdisk.set_nfree(0)
+
+class Dirent:
+
+ def __init__(self, parent, inode, name, offset):
+ self._parent = parent
+ self.inode = inode
+ self.name = name
+ self._offset = offset
+
+ def remove(self):
+ self._parent.write(self._offset + 4, '\0')
+
+class Inode:
+
+ def __init__(self, simdisk, number, offset):
+ self._simdisk = simdisk
+ self._simfile = simdisk._simfile
+ self._number = number
+ self._offset = offset
+
+ def get_next_free(self):
+ return self.get_size()
+
+ def set_next_free(self, val):
+ return self.set_size(val)
+
+ def get_size(self):
+ self._simfile.seek(int(self._offset))
+ return struct.unpack("I", self._simfile.read(4))[0]
+
+ def set_size(self, val):
+ self._simfile.seek(int(self._offset))
+ self._simfile.write(struct.pack("I", val))
+
+ def get_number(self):
+ self._simfile.seek(int(self._offset + 4))
+ return struct.unpack("I", self._simfile.read(4))[0]
+
+ def set_number(self, val):
+ self._simfile.seek(int(self._offset + 4))
+ self._simfile.write(struct.pack("I", val))
+
+ def get_type(self):
+ self._simfile.seek(int(self._offset + 8))
+ return struct.unpack("H", self._simfile.read(2))[0]
+
+ def set_type(self, val):
+ self._simfile.seek(int(self._offset + 8))
+ self._simfile.write(struct.pack("H", val))
+
+ def get_link_count(self):
+ self._simfile.seek(int(self._offset + 10))
+ return struct.unpack("h", self._simfile.read(2))[0]
+
+ def set_link_count(self, val):
+ self._simfile.seek(int(self._offset + 10))
+ self._simfile.write(struct.pack("h", val))
+
+ def get_direct_blockno(self, index):
+ if (index < S5_NDIRECT_BLOCKS):
+ self._simfile.seek(int(self._offset + 12 + index * 4))
+ return struct.unpack("I", self._simfile.read(4))[0]
+ else:
+ raise S5fsException("direct block index {0} greater than max {1}".format(index, S5_NDIRECT_BLOCKS))
+
+ def set_direct_blockno(self, index, val):
+ if (index < S5_NDIRECT_BLOCKS):
+ self._simfile.seek(int(self._offset + 12 + index * 4))
+ self._simfile.write(struct.pack("I", val))
+ else:
+ raise S5fsException("direct block index {0} greater than max {1}".format(index, S5_NDIRECT_BLOCKS))
+
+ def get_indirect_blockno(self):
+ self._simfile.seek(int(self._offset + 12 + 4 * S5_NDIRECT_BLOCKS))
+ return struct.unpack("I", self._simfile.read(4))[0]
+
+ def set_indirect_blockno(self, val):
+ self._simfile.seek(int(self._offset + 12 + 4 * S5_NDIRECT_BLOCKS))
+ self._simfile.write(struct.pack("I", val))
+
+ def get_type_str(self, short=False):
+ t = self.get_type()
+ name = "INV" if short else "INVALID"
+ if (t == S5_TYPE_FREE):
+ name = "---" if short else "S5_TYPE_FREE"
+ elif (t == S5_TYPE_DATA):
+ name = "dat" if short else "S5_TYPE_DATA"
+ elif (t == S5_TYPE_DIR):
+ name = "dir" if short else "S5_TYPE_DIR"
+ elif (t == S5_TYPE_BLK):
+ name = "blk" if short else "S5_TYPE_BLK"
+ elif (t == S5_TYPE_CHR):
+ name = "chr" if short else "S5_TYPE_CHR"
+ return name if short else "{0} (0x{1:02x})".format(name, t)
+
+ def get_summary(self):
+ res = ""
+ res += "num: {0}{1}\n".format(self.get_number(), "" if self.get_number() == self._number else " (INVALID, should be {0})".format(self.get_number()))
+ res += "type: {0}\n".format(self.get_type_str())
+ if (self.get_type() != S5_TYPE_FREE):
+ res += "links: {0}\n".format(self.get_link_count())
+ if (self.get_type() in set([ S5_TYPE_DATA, S5_TYPE_DIR ])):
+ res += "size: {0} bytes".format(self.get_size())
+ if (self.get_size() > S5_MAX_FILE_SIZE):
+ res += " (INVALID, max file size is {0})".format(S5_MAX_FILE_SIZE)
+ elif (self.get_type() == S5_TYPE_DIR and self.get_size() % S5_DIRENT_SIZE != 0):
+ res += " (INVALID, directory size must be multiple of dirent size ({0}))".format(S5_DIRENT_SIZE)
+ elif (self.get_type() == S5_TYPE_DIR):
+ res += " ({0} dirents)".format(self.get_size() / S5_DIRENT_SIZE)
+ res += "\n"
+ res += "direct blocks ({0}):\n".format(S5_NDIRECT_BLOCKS)
+ for i in xrange(S5_NDIRECT_BLOCKS):
+ res += " {0:5}".format(self.get_direct_blockno(i))
+ if ((i + 1) % 4 == 0):
+ res += "\n"
+ if (res[-1] != "\n"):
+ res += "\n"
+ res += "indirect block: {0}\n".format(self.get_indirect_blockno())
+ elif (self.get_type() == S5_TYPE_FREE):
+ res += "next free: {0}\n".format(self.get_next_free())
+ res = res[:-1]
+ return res
+
+ def read(self, offset=0, size=None):
+ if (size == None):
+ size = self.get_size()
+ if (self.get_type() not in set([ S5_TYPE_DATA, S5_TYPE_DIR ])):
+ raise S5fsException("cannot read from inode of type " + self.get_type_str())
+ size = min(size, min(S5_MAX_FILE_SIZE, self.get_size()) - offset)
+ res = ""
+ while (size > 0):
+ blockno = math.floor(offset / S5_BLOCK_SIZE)
+ blockoff = offset % S5_BLOCK_SIZE
+ ammount = min(S5_BLOCK_SIZE - blockoff, size)
+ if (blockno < S5_NDIRECT_BLOCKS):
+ blockno = self.get_direct_blockno(blockno)
+ else:
+ if (self.get_indirect_blockno() == 0):
+ blockno = 0
+ else:
+ indirect = self._simdisk.get_block(self.get_indirect_blockno())
+ blockno -= S5_NDIRECT_BLOCKS
+ blockno = struct.unpack("I", indirect.read(blockno * 4, 4))[0]
+ if (blockno == 0):
+ for i in xrange(ammount):
+ res += '\0'
+ else:
+ res += self._simdisk.get_block(blockno).read(blockoff, ammount)
+ offset += ammount
+ size -= ammount
+ return res
+
+ def write(self, offset, data):
+ if (self.get_type() not in set([ S5_TYPE_DATA, S5_TYPE_DIR ])):
+ raise S5fsException("cannot write to inode of type " + self.get_type_str())
+ if (offset + len(data) > S5_MAX_FILE_SIZE):
+ raise S5fsException("cannot write up to byte {0}, max file size is {1}".format(offset + len(data), S5_MAX_FILE_SIZE))
+ remaining = len(data)
+ while (remaining > 0):
+ blockloc = math.floor(offset / S5_BLOCK_SIZE)
+ blockoff = offset % S5_BLOCK_SIZE
+ ammount = min(S5_BLOCK_SIZE - blockoff, remaining)
+ if (blockloc < S5_NDIRECT_BLOCKS):
+ blockno = self.get_direct_blockno(blockloc)
+ else:
+ if (self.get_indirect_blockno() == 0):
+ indirect = self._simdisk.alloc_block()
+ indirect.zero()
+ self.set_indirect_blockno(indirect.get_blockno())
+ blockno = 0
+ else:
+ indirect = self._simdisk.get_block(self.get_indirect_blockno())
+ blockno = struct.unpack("I", indirect.read((blockloc - S5_NDIRECT_BLOCKS) * 4, 4))[0]
+ if (blockno == 0):
+ block = self._simdisk.alloc_block()
+ block.zero()
+ if (blockloc < S5_NDIRECT_BLOCKS):
+ self.set_direct_blockno(blockloc, block.get_blockno())
+ else:
+ indirect = self._simdisk.get_block(self.get_indirect_blockno())
+ indirect.write((blockloc - S5_NDIRECT_BLOCKS) * 4, struct.pack("I", block.get_blockno()))
+ else:
+ block = self._simdisk.get_block(blockno)
+ if (remaining == ammount):
+ block.write(blockoff, data[-remaining:])
+ else:
+ block.write(blockoff, data[-remaining:-remaining+ammount])
+ remaining -= ammount
+ offset += ammount
+ if (offset > self.get_size()):
+ self.set_size(offset)
+
+ def truncate(self, size=0):
+ target = math.floor((size - 1) / S5_BLOCK_SIZE)
+ curr = math.floor(self.get_size() / S5_BLOCK_SIZE)
+ while (curr > target):
+ if (curr < S5_NDIRECT_BLOCKS):
+ blockno = self.get_direct_blockno(curr)
+ else:
+ if (self.get_indirect_blockno() == 0):
+ blockno = 0
+ else:
+ indirect = self._simdisk.get_block(self.get_indirect_blockno())
+ blockno = struct.unpack("I", indirect.read((curr - S5_NDIRECT_BLOCKS) * 4, 4))[0]
+ if (blockno > 0):
+ block = self._simdisk.get_block(blockno)
+ block.free()
+ if (curr < S5_NDIRECT_BLOCKS):
+ self.set_direct_blockno(curr, 0)
+ else:
+ indirect = self._simdisk.get_block(self.get_indirect_blockno())
+ indirect.write((curr - S5_NDIRECT_BLOCKS) * 4, struct.pack("I", 0))
+ if (curr == S5_NDIRECT_BLOCKS):
+ indirect.free()
+ self.set_indirect_blockno(0)
+ curr -= 1
+ if (curr < target):
+ if (curr < S5_NDIRECT_BLOCKS):
+ self.set_indirect_blockno(0)
+ else:
+ if (self.get_indirect_blockno() != 0):
+ indirect = self._simdisk.get_block(self.get_indirect_blockno())
+ indirect.write((curr - S5_NDIRECT_BLOCKS) * 4, struct.pack("I", 0))
+ self.set_size(size)
+
+ def _find_dirent(self, name, types=S5_TYPES):
+ if (self.get_type() != S5_TYPE_DIR):
+ raise S5fsException("cannot remove directory entry in non-directory inode of type " + self.get_type_str())
+ if (self.get_size() % S5_DIRENT_SIZE != 0):
+ raise S5fsException("cannot remove directory entry, inode has size {0} not a multiple of dirent size {1}".format(self.get_size(), S5_DIRENT_SIZE))
+ if (len(name) >= S5_NAME_LEN):
+ raise S5fsException("directroy entry name '{0}' too long, limit is {1} characters".format(name, S5_NAME_LEN - 1))
+ for i in xrange(0, self.get_size(), S5_DIRENT_SIZE):
+ inode = struct.unpack("I", self.read(i, 4))[0]
+ parts = self.read(i + 4, S5_NAME_LEN).split('\0', 1)
+ if (len(parts) == 1):
+ raise S5fsException("directory entry name '{0}' does not contain a null character".format(name))
+ if (name == parts[0]):
+ return Dirent(self, inode, name, i)
+ return None
+
+ def unlink(self, name):
+ dirent = self._find_dirent(name)
+ if (dirent == None):
+ raise S5fsException("no such file or directory: {0}".format(name))
+ inode = self._simdisk.get_inode(dirent.inode)
+ if (inode.get_type() == S5_TYPE_DIR):
+ raise S5fsException("cannot unlink directory: {0}".format(name))
+ if (inode.get_link_count() < 1):
+ raise S5fsException("inode being unlinked has invalid link count of {1}: {0}".format(name, inode.get_link_count()))
+ dirent.remove()
+ if (inode.get_link_count() == 1):
+ inode.set_link_count(0)
+ inode.free()
+ else:
+ inode.set_link_count(inode.get_link_count() - 1)
+
+ def rmdir(self, name):
+ if (name == "." or name == ".."):
+ raise S5fsException("cannot rmdir special directory: {0}".format(name))
+ dirent = self._find_dirent(name)
+ if (dirent == None):
+ raise S5fsException("no such file or directory: {0}".format(name))
+ inode = self._simdisk.get_inode(dirent.inode)
+ if (inode.get_type() != S5_TYPE_DIR):
+ raise S5fsException("cannot rmdir non-directory: {0}".format(name))
+ if (not inode.is_empty()):
+ raise S5fsException("cannot rmdir non-empty directory: {0}".format(name))
+ if (inode.get_link_count() != 2):
+ # the link count should be 2 because the parent still refers
+ # to the directory being removed and the directory refers to itself
+ print("link count" + str(inode.get_link_count()))
+ raise S5fsException("empty directory has link count of {1}: {0}".format(name, inode.get_link_count()))
+ dirent.remove()
+ self.set_link_count(self.get_link_count() - 1)
+ inode.set_link_count(0)
+ inode.free()
+
+ def _make_dirent(self, inode, name):
+ if (self.get_type() != S5_TYPE_DIR):
+ raise S5fsException("cannot create directory entry in non-directory inode of type " + self.get_type_str())
+ if (self.get_size() % S5_DIRENT_SIZE != 0):
+ raise S5fsException("cannot create directory entry, inode has size {0} not a multiple of dirent size {1}".format(self.get_size(), S5_DIRENT_SIZE))
+ if (len(name) >= S5_NAME_LEN):
+ raise S5fsException("directroy entry name '{0}' too long, limit is {1} characters".format(name, S5_NAME_LEN - 1))
+ empty = -1
+ for i in xrange(0, self.get_size(), S5_DIRENT_SIZE):
+ direntname = self.read(i + 4, S5_NAME_LEN).split('\0', 1)[0]
+ if (direntname == name):
+ raise S5fsException("directory already has entry with same name: {0}".format(name))
+ if (len(name) == 0):
+ empty = i
+ if (empty >= 0):
+ self.write(empty, struct.pack("I", inode))
+ self.write(empty + 4, name.ljust(S5_NAME_LEN, '\0'))
+ else:
+ self.write(self.get_size(), struct.pack("I", inode))
+ self.write(self.get_size(), name.ljust(S5_NAME_LEN, '\0'))
+
+ def create(self, name):
+ inode = self._simdisk.alloc_inode()
+ try:
+ inode.set_type(S5_TYPE_DATA)
+ inode.set_size(0)
+ inode.set_link_count(1)
+ for i in xrange(S5_NDIRECT_BLOCKS):
+ inode.set_direct_blockno(i, 0)
+ inode.set_indirect_blockno(0)
+ self._make_dirent(inode.get_number(), name)
+ return inode
+ except S5fsException as e:
+ inode.free()
+ raise e
+
+ def mkdir(self, name):
+ inode = self._simdisk.alloc_inode()
+ try:
+ inode.set_type(S5_TYPE_DIR)
+ inode.set_size(0)
+ inode.set_link_count(2)
+ for i in xrange(S5_NDIRECT_BLOCKS):
+ inode.set_direct_blockno(i, 0)
+ inode.set_indirect_blockno(0)
+ inode._make_dirent(inode.get_number(), ".")
+ inode._make_dirent(self.get_number(), "..")
+ self.set_link_count(self.get_link_count() + 1)
+ self._make_dirent(inode.get_number(), name)
+ return inode
+ except S5fsException as e:
+ inode.free()
+ raise e
+
+ def getdents(self):
+ if (self.get_type() != S5_TYPE_DIR):
+ raise S5fsException("cannot get dirents from inode of type " + self.get_type_str())
+ if (self.get_size() % S5_DIRENT_SIZE != 0):
+ raise S5fsException("cannot get dirents, inode has size {0} not a multiple of dirent size {1}".format(self.get_size(), S5_DIRENT_SIZE))
+ for i in xrange(0, self.get_size(), S5_DIRENT_SIZE):
+ inode = struct.unpack("I", self.read(i, 4))[0]
+ name = self.read(i + 4, S5_NAME_LEN)
+ parts = name.split('\0', 1)
+ if (len(parts) == 1):
+ raise S5fsException("directory entry {0} in inode {1} does not contain a null character".format(i / S5_DIRENT_SIZE, self._number))
+ name = parts[0]
+ if (len(name) > 0):
+ yield Dirent(self, inode, name, i)
+
+ def is_empty(self):
+ for dirent in self.getdents():
+ if (dirent.name != "." and dirent.name != ".."):
+ return False
+ return True
+
+ def lookup(self, name):
+ if (len(name) >= S5_NAME_LEN):
+ raise S5fsException("directroy entry name '{0}' too long, limit is {1} characters".format(name, S5_NAME_LEN - 1))
+ for dirent in self.getdents():
+ if (dirent.name == name):
+ try:
+ return self._simdisk.get_inode(dirent.inode)
+ except S5fsException as e:
+ raise S5fsException("error looking up '{0}': {1}".format(name, str(e)))
+ return None
+
+ def open(self, path, create=False):
+ opath = os.path.normpath(path)
+ path = opath.split("/")
+ curr = self
+ for seg in path[:-1]:
+ if (len(seg) == 0):
+ continue
+ curr = curr.lookup(seg)
+ if (curr == None):
+ if (not create):
+ return None
+ else:
+ raise S5fsException("segment '{0}' in path '{1}' does not exist".format(seg, opath))
+ if (len(path[-1]) == 0):
+ return curr
+ last = curr.lookup(path[-1])
+ if (None == last and create):
+ return curr.create(path[-1])
+ return last
+
+ def free(self):
+ if (self.get_size() != 0):
+ self.truncate()
+ self.set_type(S5_TYPE_FREE)
+ self.set_next_free(self._simdisk.get_free_inode())
+ self._simdisk.set_free_inode(self._number)
+
+class Simdisk:
+
+ def __init__(self, simfile):
+ self._simfile = simfile
+
+ def get_magic(self):
+ self._simfile.seek(0)
+ return struct.unpack("I", self._simfile.read(4))[0]
+
+ def set_magic(self, val):
+ self._simfile.seek(0)
+ self._simfile.write(struct.pack("I", val))
+
+ def get_free_inode(self):
+ self._simfile.seek(4)
+ return struct.unpack("I", self._simfile.read(4))[0]
+
+ def set_free_inode(self, val):
+ self._simfile.seek(4)
+ self._simfile.write(struct.pack("I", val))
+
+ def get_nfree(self):
+ self._simfile.seek(8)
+ return struct.unpack("I", self._simfile.read(4))[0]
+
+ def set_nfree(self, val):
+ self._simfile.seek(8)
+ self._simfile.write(struct.pack("I", val))
+
+ def get_free_block(self, index):
+ if (index < S5_NBLKS_PER_FNODE - 1):
+ self._simfile.seek(12 + 4 * index)
+ return struct.unpack("I", self._simfile.read(4))[0]
+ else:
+ raise S5fsException("free block index {0} greater than max {1}".format(index, S5_NBLKS_PER_FNODE - 2))
+
+ def set_free_block(self, index, val):
+ if (index < S5_NBLKS_PER_FNODE - 1):
+ self._simfile.seek(12 + 4 * index)
+ self._simfile.write(struct.pack("I", val))
+ else:
+ raise S5fsException("free block index {0} greater than max {1}".format(index, S5_NBLKS_PER_FNODE - 2))
+
+ def get_last_free_block(self):
+ self._simfile.seek(12 + 4 * (S5_NBLKS_PER_FNODE - 1))
+ return struct.unpack("I", self._simfile.read(4))[0]
+
+ def set_last_free_block(self, val):
+ self._simfile.seek(12 + 4 * (S5_NBLKS_PER_FNODE - 1))
+ self._simfile.write(struct.pack("I", val))
+
+ def get_root_inode(self):
+ self._simfile.seek(12 + 4 * S5_NBLKS_PER_FNODE)
+ return struct.unpack("I", self._simfile.read(4))[0]
+
+ def get_num_inodes(self):
+ self._simfile.seek(16 + 4 * S5_NBLKS_PER_FNODE)
+ return struct.unpack("I", self._simfile.read(4))[0]
+
+ def set_num_inodes(self, val):
+ self._simfile.seek(16 + 4 * S5_NBLKS_PER_FNODE)
+ self._simfile.write(struct.pack("I", val))
+
+ def get_version(self):
+ self._simfile.seek(20 + 4 * S5_NBLKS_PER_FNODE)
+ return struct.unpack("I", self._simfile.read(4))[0]
+
+ def set_version(self, val):
+ self._simfile.seek(20 + 4 * S5_NBLKS_PER_FNODE)
+ self._simfile.write(struct.pack("I", val))
+
+ def get_super_block_summary(self):
+ res = ""
+ res += "magic: 0x{0:04x} ({1})\n".format(self.get_magic(), "VALID" if self.get_magic() == S5_MAGIC else "INVALID")
+ res += "version: 0x{0:04x}{1}\n".format(self.get_version(), "" if self.get_version() == S5_CURRENT_VERSION else " (INVALID)")
+ res += "num inodes: {0}\n".format(self.get_num_inodes())
+ res += "free inode: {0}{1}\n".format(self.get_free_inode(), "" if self.get_free_inode() < self.get_num_inodes() else " (INVALID)")
+ res += "root inode: {0}{1}\n".format(self.get_root_inode(), "" if self.get_root_inode() < self.get_num_inodes() else " (INVALID)")
+ res += "free blocks ({0}{1}):\n".format(self.get_nfree(), "" if self.get_nfree() <= S5_NBLKS_PER_FNODE else (", too large shouldn't exceed " + str(S5_NBLKS_PER_FNODE)))
+ for i in xrange(min(self.get_nfree(), S5_NBLKS_PER_FNODE - 1)):
+ res += " {0}".format(self.get_free_block(i))
+ if ((i + 1) % 10 == 0):
+ res += "\n"
+ if (res[-1] != "\n"):
+ res += "\n"
+ res += " last free block: {0}\n".format(self.get_last_free_block())
+ return res
+
+ def format(self, inodes, size):
+ if (inodes < 1):
+ raise S5fsException("cannot format disk with {0} inodes, must have at least one".format(inodes))
+ if (size % S5_BLOCK_SIZE != 0):
+ raise S5fsException("cannot format disk to size {0} which is not a multiple of the block size {1}".format(size, S5_BLOCK_SIZE))
+ blocks = int(size / S5_BLOCK_SIZE)
+ iblocks = int(math.floor((inodes - 1) / S5_INODES_PER_BLOCK) + 1)
+ if (iblocks + 1 >= blocks):
+ raise S5fsException("cannot format disk of size {0} with {1} inodes, the inodes require at least {2} bytes of space".format(size, inodes, (1 + iblocks) * S5_BLOCK_SIZE))
+ self._simfile.truncate()
+ self._simfile.seek(size)
+ self._simfile.write("")
+
+ self.set_magic(S5_MAGIC)
+ self.set_version(S5_CURRENT_VERSION)
+ self.set_num_inodes(inodes)
+ for i in xrange(inodes):
+ inode = self.get_inode(i)
+ inode.set_number(i)
+ inode.set_type(S5_TYPE_FREE)
+ inode.set_next_free(i + 1)
+ inode.set_next_free(0xffffffff)
+ self.set_free_inode(0)
+
+ self.set_last_free_block(0xffffffff)
+ i = 0
+ for num in xrange(iblocks+1, blocks):
+ if (i == S5_NBLKS_PER_FNODE - 1):
+ block = self.get_block(num)
+ for j in xrange(S5_NBLKS_PER_FNODE - 1):
+ block.write(j * 4, struct.pack("I", self.get_free_block(j)))
+ block.write((S5_NBLKS_PER_FNODE - 1) * 4, struct.pack("I", self.get_last_free_block()))
+ self.set_last_free_block(num)
+ i = 0
+ else:
+ self.set_free_block(i, num)
+ i += 1
+ self.set_nfree(i)
+
+ root = self.alloc_inode()
+ for i in xrange(S5_NDIRECT_BLOCKS):
+ root.set_direct_blockno(i, 0)
+ root.set_indirect_blockno(0)
+ root.set_type(S5_TYPE_DIR)
+ root.set_size(0)
+ root.set_link_count(2)
+ root._make_dirent(root.get_number(), ".")
+ root._make_dirent(root.get_number(), "..")
+
+ def free_inodes(self):
+ inext = self.get_free_inode()
+ while (inext != 0xffffffff):
+ try:
+ curr = self.get_inode(inext)
+ yield inext
+ inext = curr.get_free_inode()
+ except S5fsException as e:
+ raise S5fsException("error encountered while iterating free inodes: {0}".format(str(e)))
+
+ def get_inode(self, index):
+ offset = S5_BLOCK_SIZE * (1 + math.floor(index / S5_INODES_PER_BLOCK)) + S5_INODE_SIZE * (index % S5_INODES_PER_BLOCK)
+ if (index >= self.get_num_inodes()):
+ raise S5fsException("cannot get inode {0}, there are only {1} inodes on disk".format(index, self.get_num_inodes()))
+ return Inode(self, index, offset)
+
+ def alloc_inode(self):
+ if (self.get_free_inode() == 0xffffffff):
+ raise S5fsException("disk is out of inodes")
+ inode = self.get_inode(self.get_free_inode())
+ self.set_free_inode(inode.get_next_free())
+ return inode
+
+ def get_block(self, index):
+ offset = S5_BLOCK_SIZE * index
+ return Block(self, offset, index)
+
+ def alloc_block(self):
+ if (self.get_nfree() > S5_NBLKS_PER_FNODE - 1):
+ raise S5fsException("nfree {0} is invalid, maximum value is {1}".format(self.get_nfree(), S5_NBLKS_PER_FNODE - 1))
+ if (self.get_nfree() == 0):
+ if (self.get_last_free_block() == 0xffffffff):
+ raise S5fsDiskSpaceException()
+ else:
+ block = self.get_block(self.get_last_free_block())
+ for i in xrange(S5_NBLKS_PER_FNODE - 1):
+ self.set_free_block(i, struct.unpack("I", block.read(i * 4, 4))[0])
+ self.set_last_free_block(struct.unpack("I", block.read((S5_NBLKS_PER_FNODE - 1) * 4, 4))[0])
+ self.set_nfree(S5_NBLKS_PER_FNODE - 1)
+ return block
+ else:
+ self.set_nfree(self.get_nfree() - 1)
+ return self.get_block(self.get_free_block(self.get_nfree()))
+
+ def open(self, path, create=False):
+ return self.get_inode(self.get_root_inode()).open(path, create=create)
diff --git a/tools/fsmaker/sh.py b/tools/fsmaker/sh.py
new file mode 100644
index 0000000..3eee4f0
--- /dev/null
+++ b/tools/fsmaker/sh.py
@@ -0,0 +1,720 @@
+import os
+import sys
+import cmd
+import api
+
+import math
+import stat
+import errno
+import shlex
+import Queue
+import struct
+import string
+import optparse
+import tempfile
+
+import curses.ascii
+
+class OptionParser(optparse.OptionParser):
+
+ def __init__(self, **args):
+ optparse.OptionParser.__init__(self, **args)
+
+ def exit(self, code=0, msg=""):
+ sys.stderr.write(msg)
+
+class FsmakerShell(cmd.Cmd):
+
+ def __init__(self, simdisk):
+ self._simdisk = simdisk
+ self._curdir = "/"
+ self.prompt = "{0} > ".format(self._curdir)
+ cmd.Cmd.__init__(self)
+
+ self._parse_superblock = OptionParser(usage="usage: %prog", prog="superblock", description="prints a summary of the superblock's contents")
+
+ self._parse_inode = OptionParser(usage="usage: %prog <nums...>", prog="inode", description="prints a summary of the specified inode's contents")
+ self._parse_inode.add_option("-i", "--indirect", action="store_true", default=False,
+ help="if the inode is a directory or data file print the indirect block contents")
+ self._parse_inode.add_option("-c", "--contents", action="store_true", default=False,
+ help="if the inode is a data file or directory this prints the contents of the file as part of the summary")
+ self._parse_inode.add_option("-l", "--list", action="store_true", default=False,
+ help="if the inode is a directory this prints a directory listing as part of the summary")
+
+ self._parse_block = OptionParser(usage="usage: %prog", prog="block", description="prints the data from a given block")
+ self._parse_ls = OptionParser(usage="usage: %prog <dirs...>", prog="ls", description="prints a directory listing")
+ self._parse_cat = OptionParser(usage="usage: %prog <files...>", prog="cat", description="prints the contents of a file")
+ self._parse_cd = OptionParser(usage="usage: %prog <dir>", prog="cd", description="changes the current working directory")
+
+ self._parse_trunc = OptionParser(usage="usage: %prog <files...>", prog="truncate", description="changes the size of a file")
+ self._parse_trunc.add_option("-s", "--size", action="store", type="int", default=0,
+ help="the new file size (defaults to %default)")
+
+ self._parse_rm = OptionParser(usage="usage: %prog <files...>", prog="rm", description="removes a file from the disk")
+ self._parse_rm.add_option("-r", "--recursive", action="store_true", default=False,
+ help="recrsively destroy all subdirectories and files of any directory arguments")
+ self._parse_rmdir = OptionParser(usage="usage: %prog <dirs...>", prog="rmdir", description="removes an empty directory from the disk")
+
+ self._parse_touch = OptionParser(usage="usage: %prog <dirs...>", prog="touch", description="creates a plain data file")
+ self._parse_mkdir = OptionParser(usage="usage: %prog <dirs...>", prog="mkdir", description="creates an empty directory")
+
+ self._parse_getfile = OptionParser(usage="usage: %prog <source> <dest>", prog="getfile", description="gets a file from the real disk and puts it on the simdisk")
+ self._parse_putfile = OptionParser(usage="usage: %prog <source> <dest>", prog="putfile", description="puts a file from the simdisk onto the real disk")
+
+ self._parse_format = OptionParser(usage="usage: %prog -i <inode count> [-s <size>|-b <blocks>]", prog="format", description="formats the simdisk to an empty file system")
+ self._parse_format.add_option("-s", "--size", action="store", type="int", default=None,
+ help="size for the new file system in bytes, must specify either this option or -b but not both")
+ self._parse_format.add_option("-b", "--blocks", action="store", type="int", default=None,
+ help="size for the new file system in blocks, must specify either this option or -s but not both")
+ self._parse_format.add_option("-i", "--inodes", action="store", type="int", default=None,
+ help="number of inodes to put on the disk, this must be specified and be compatible with the size of the disk (there must be enough space for the inodes)")
+ self._parse_format.add_option("-d", "--directory", action="store", type="str", default=None,
+ help="initializes the disk with the contents of the specified directory")
+
+ def open(self, path, create=False):
+ if (path.startswith("/")):
+ return self._simdisk.open(path, create=create)
+ else:
+ return self._simdisk.open(self._curdir + "/" + path, create=create)
+
+ def filepath_completion(self, text, line, begin, end, types=api.S5_TYPES):
+ res = []
+ filename = text
+
+ bline = line[:begin]
+ try:
+ parts = shlex.split(bline)
+ if (bline.endswith('"')):
+ return []
+ if (bline.endswith(" ") and not bline.endswith("\\ ")):
+ dirpath = self._curdir
+ else:
+ dirpath = parts[-1]
+ except ValueError:
+ dirpath = bline.rsplit('"', 1)[-1]
+ if (len(dirpath.strip()) == 0):
+ dirpath = self._curdir
+
+ try:
+ dirinode = self.open(dirpath)
+ if (None != dirinode):
+ for dirent in dirinode.getdents():
+ if (dirent.name.startswith(filename) and dirent.name != "." and dirent.name != ".."):
+ t = self._simdisk.get_inode(dirent.inode).get_type()
+ if (t in types or t == api.S5_TYPE_DIR):
+ if (t == api.S5_TYPE_DIR):
+ res.append(dirent.name + "/")
+ else:
+ res.append(dirent.name)
+ return res
+ else:
+ return []
+ except api.S5fsException as e:
+ return []
+
+ def real_filepath_completion(self, text, line, begin, end):
+ res = []
+ filename = text
+
+ bline = line[:begin]
+ try:
+ parts = shlex.split(bline)
+ if (bline.endswith('"')):
+ return []
+ if (bline.endswith(" ") and not bline.endswith("\\ ")):
+ dirpath = os.curdir
+ else:
+ dirpath = parts[-1]
+ except ValueError:
+ dirpath = bline.rsplit('"', 1)[-1]
+ if (len(dirpath.strip()) == 0):
+ dirpath = os.curdir
+
+ try:
+ for dirent in os.listdir(dirpath):
+ if (dirent.startswith(filename)):
+ if (os.path.isdir(os.path.join(dirpath, dirent))):
+ res.append(dirent + "/")
+ else:
+ res.append(dirent)
+ return res
+ except api.S5fsException as e:
+ return []
+
+ def completion_argnum(self, text, line, begin, end):
+ bline = line[:begin]
+ try:
+ parts = [x for x in shlex.split(bline) if not x.startswith("-")]
+ if (bline.endswith(" ") and not bline.endswith("\\ ")):
+ return len(parts)
+ else:
+ return len(parts) - 1
+ except ValueError:
+ parts = [x for x in shlex.split(bline.rsplit('"', 1)[0]) if not x.startswith("-")]
+ return len(parts)
+
+ def get_parentdir(self, path):
+ path = os.path.normpath(path).rsplit('/', 1)
+ if (len(path) == 1):
+ res = (self.open(self._curdir), path[0])
+ else:
+ if (len(path[1]) == 0):
+ res = (self.open(path[0]), '.')
+ else:
+ res = (self.open(path[0]), path[1])
+ if (res[0] == None):
+ raise api.S5fsException("no such directory: {0}".format(path[0]))
+ return res
+
+ def binary_print(self, data, prefix=""):
+ binary = ""
+ line = ""
+ for i in xrange(int(math.floor(len(data) / 2))):
+ unpacked = struct.unpack("BB", data[i * 2:(i + 1) * 2])
+ binary += "{0:02x}{1:02x} ".format(*unpacked)
+ for d in unpacked:
+ if (curses.ascii.isprint(d)):
+ line += chr(d)
+ else:
+ line += '.'
+ if ((i + 1) % 10 == 0):
+ print("{2}{0:<50} {1}".format(binary, line, prefix))
+ binary = ""
+ line = ""
+ if (len(data) % 2 != 0):
+ d = struct.unpack("B", data[-1])[0]
+ binary += "{0:02x}".format(d)
+ if (curses.ascii.isprint(d)):
+ line += chr(d)
+ else:
+ line += '.'
+ if (binary != ""):
+ print("{2}{0:<50} {1}".format(binary, line, prefix))
+
+ def dirents_print(self, dirents, prefix=""):
+ direntlist = []
+ maxlen = 0
+ maxinode = 0
+ for dirent in dirents:
+ maxlen = max(maxlen, len(dirent.name))
+ maxinode = max(maxinode, dirent.inode)
+ direntlist.append(dirent)
+ if (len(direntlist) == 0):
+ print ("{0}<empty directory>".format(prefix))
+ else:
+ for dirent in direntlist:
+ try:
+ inode = self._simdisk.get_inode(dirent.inode)
+ itype = inode.get_type()
+ itypestr = inode.get_type_str(short=True)
+ isize = inode.get_size()
+ except api.S5fsException as e:
+ inode = None
+ errmsg = str(e)
+ if (inode == None):
+ msg = "<{0}>".format(errmsg)
+ else:
+ if (itype in set([ api.S5_TYPE_DATA, api.S5_TYPE_DIR ])):
+ msg = "{0} {1} bytes".format(itypestr, isize)
+ elif (itype in set([ api.S5_TYPE_BLK, api.S5_TYPE_CHR ])):
+ msg = "{0}".format(itypestr)
+ else:
+ msg = "{0} (INVALID, free inode)".format(itypestr)
+ print("{4}{0:<{1}} {2:>{3}} {5}".format(dirent.name, maxlen, dirent.inode, len(str(maxinode)), prefix, msg))
+
+ def help_help(self):
+ print("prints help information about a command")
+
+ def do_cd(self, args):
+ try:
+ (options, args) = self._parse_cd.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_cd.error(str(e))
+ return
+
+ if (len(args) != 1):
+ self._parse_cd.error("command takes exactly one argument")
+ else:
+ f = self.open(args[0])
+ if (None == f):
+ self._parse_cd.error("no such file or directory: {0}".format(args[0]))
+ elif (api.S5_TYPE_DIR != f.get_type()):
+ self._parse_cd.error("not a directory: {0}".format(args[0]))
+ else:
+ self._curdir = os.path.normpath(self._curdir + ("" if self._curdir.endswith("/") else "/") + args[0])
+ self.prompt = "{0} > ".format(self._curdir)
+
+ def help_cd(self):
+ self._parse_cd.print_help()
+
+ def complete_cd(self, text, line, begidx, endidx):
+ if (self.completion_argnum(text, line, begidx, endidx) == 1):
+ return self.filepath_completion(text, line, begidx, endidx, types=set([ api.S5_TYPE_DIR ]))
+ else:
+ return []
+
+ def do_superblock(self, args):
+ try:
+ (options, args) = self._parse_superblock.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_superblock.error(str(e))
+ return
+
+ if (len(args) != 0):
+ self._parse_superblock.error("command does not take arguments")
+ else:
+ print(self._simdisk.get_super_block_summary())
+
+ def help_superblock(self):
+ self._parse_superblock.print_help()
+
+ def complete_superblock(self, text, line, begidx, endidx):
+ return []
+
+ def do_inode(self, args):
+ try:
+ (options, args) = self._parse_inode.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_inode.error(str(e))
+ return
+
+ if (len(args) == 0):
+ self._parse_inode.error("command requires at least one argument")
+ else:
+ for arg in args:
+ try:
+ inode = self._simdisk.get_inode(string.atoi(arg))
+ except ValueError as e:
+ inode = self.open(arg)
+ except api.S5fsException as e:
+ inode = self.open(arg)
+
+ if (None == inode):
+ self._parse_inode.error(arg + " is not a valid inode number or path")
+ else:
+ try:
+ print(inode.get_summary())
+ if (options.indirect and inode.get_type() in set([ api.S5_TYPE_DATA, api.S5_TYPE_DIR ]) and inode.get_indirect_blockno() != 0):
+ try:
+ iblock = self._simdisk.get_block(inode.get_indirect_blockno())
+ for i in xrange(api.S5_BLOCK_SIZE / 4):
+ num = struct.unpack("I", iblock.read(i * 4, 4))[0]
+ sys.stdout.write(" {0:5}".format(num))
+ if ((i + 1) % 8 == 0):
+ sys.stdout.write("\n")
+ if ((i + 1) % 8 != 0):
+ sys.stdout.write("\n")
+ except api.S5fsException as e:
+ self._parse_inode.error(str(e))
+ if (options.contents and inode.get_type() in set([ api.S5_TYPE_DATA, api.S5_TYPE_DIR ])):
+ print("contents:")
+ self.binary_print(inode.read(),prefix=" ")
+ if (options.list and inode.get_type() == api.S5_TYPE_DIR):
+ print("directory listing:")
+ self.dirents_print(inode.getdents(),prefix=" ")
+ except api.S5fsException as e:
+ self._parse_inode.error(str(e))
+ print("- - - - -")
+
+ def help_inode(self):
+ self._parse_inode.print_help()
+
+ def complete_inode(self, text, line, begidx, endidx):
+ res = []
+ try:
+ string.atoi(text)
+ res += [str(x) for x in xrange(self._simdisk.get_num_inodes()) if str(x).startswith(text)]
+ except ValueError:
+ None
+ res += self.filepath_completion(text, line, begidx, endidx)
+ return res
+
+ def do_block(self, args):
+ try:
+ (options, args) = self._parse_block.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_block.error(str(e))
+ return
+
+ if (len(args) == 0):
+ self._parse_block.error("command requires at least one argument")
+ else:
+ for arg in args:
+ try:
+ blockno = string.atoi(arg)
+ print("block {0}:".format(blockno))
+ block = self._simdisk.get_block(blockno)
+ self.binary_print(block.read())
+ except api.S5fsException as e:
+ self._parse_block.error(str(e))
+ except ValueError as e:
+ self._parse_block.error(arg + " is not a valid block number")
+ print("- - - - -")
+
+ def help_block(self):
+ self._parse_block.print_help()
+
+ def do_ls(self, args):
+ try:
+ (options, args) = self._parse_ls.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_ls.error(str(e))
+ return
+
+ for arg in (args if len(args) > 0 else [self._curdir]):
+ try:
+ f = self.open(arg)
+ if (f == None):
+ self._parse_ls.error("no such file or directory: {0}".format(arg))
+ elif (f.get_type() != api.S5_TYPE_DIR):
+ self._parse_ls.error("not a directory: {0}".format(arg))
+ else:
+ self.dirents_print(f.getdents())
+ except api.S5fsException as e:
+ self._parse_ls.error(str(e))
+ print("- - - - -")
+
+ def help_ls(self):
+ self._parse_ls.print_help()
+
+ def complete_ls(self, text, line, begidx, endidx):
+ return self.filepath_completion(text, line, begidx, endidx, types=set([ api.S5_TYPE_DIR ]))
+
+ def do_cat(self, args):
+ try:
+ (options, args) = self._parse_cat.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_cat.error(str(e))
+ return
+
+ if (len(args) == 0):
+ self._parse_cat.error("command requires at least one argument")
+ else:
+ for arg in args:
+ try:
+ f = self.open(arg)
+ if (f == None):
+ self._parse_cat.error("no such file or directory: {0}".format(arg))
+ elif (f.get_type() != api.S5_TYPE_DATA):
+ self._parse_cat.error("not a plain data file: {0}".format(arg))
+ else:
+ print(f.read())
+ except api.S5fsException as e:
+ self._parse_cat.error(str(e))
+ print("- - - - -")
+
+ def help_cat(self):
+ self._parse_cat.print_help()
+
+ def complete_cat(self, text, line, begidx, endidx):
+ return self.filepath_completion(text, line, begidx, endidx, types=set([ api.S5_TYPE_DATA ]))
+
+ def do_truncate(self, args):
+ try:
+ (options, args) = self._parse_trunc.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_trunc.error(str(e))
+ return
+
+ if (len(args) == 0):
+ self._parse_trunc.error("command requires at least one argument")
+ else:
+ for arg in args:
+ try:
+ f = self.open(arg)
+ if (f == None):
+ self._parse_trunc.error("no such file or directory: {0}".format(arg))
+ elif (f.get_type() != api.S5_TYPE_DATA):
+ self._parse_trunc.error("not a plain data file: {0}".format(arg))
+ else:
+ f.truncate(size=options.size)
+ except api.S5fsException as e:
+ self._parse_trunc.error(str(e))
+
+ def help_truncate(self):
+ self._parse_trunc.print_help()
+
+ def complete_truncate(self, text, line, begidx, endidx):
+ return self.filepath_completion(text, line, begidx, endidx, types=set([ api.S5_TYPE_DATA ]))
+
+ def _dir_clear(self, directory):
+ for dirent in directory.getdents():
+ if (dirent.name != "." and dirent.name != ".."):
+ inode = self._simdisk.get_inode(dirent.inode)
+ if (inode.get_type() == api.S5_TYPE_DIR):
+ self._dir_clear(inode)
+ directory.rmdir(dirent.name)
+ else:
+ directory.unlink(dirent.name)
+
+ def do_rm(self, args):
+ try:
+ (options, args) = self._parse_rm.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_rm.error(str(e))
+ return
+
+ if (len(args) == 0):
+ self._parse_rm.error("command requires at least one argument")
+ else:
+ for arg in args:
+ try:
+ parentdir, name = self.get_parentdir(arg)
+ inode = parentdir.open(name)
+ if (options.recursive and inode.get_type() == api.S5_TYPE_DIR):
+ self._dir_clear(inode)
+ parentdir.rmdir(name)
+ else:
+ parentdir.unlink(name)
+ except api.S5fsException as e:
+ self._parse_rm.error(str(e))
+
+ def help_rm(self):
+ self._parse_rm.print_help()
+
+ def complete_rm(self, text, line, begin, end):
+ return self.filepath_completion(text, line, begin, end)
+
+ def do_rmdir(self, args):
+ try:
+ (options, args) = self._parse_rmdir.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_rmdir.error(str(e))
+ return
+
+ if (len(args) == 0):
+ self._parse_rmdir.error("command requires at least one argument")
+ else:
+ for arg in args:
+ try:
+ parentdir, name = self.get_parentdir(arg)
+ parentdir.rmdir(name)
+ except api.S5fsException as e:
+ self._parse_rmdir.error(str(e))
+
+ def help_rmdir(self):
+ self._parse_rmdir.print_help()
+
+ def complete_rmdir(self, text, line, begin, end):
+ return self.filepath_completion(text, line, begin, end, types=set([ api.S5_TYPE_DIR ]))
+
+ def do_touch(self, args):
+ try:
+ (options, args) = self._parse_touch.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_touch.error(str(e))
+ return
+
+ if (len(args) == 0):
+ self._parse_touch.error("command requires at least one argument")
+ else:
+ for arg in args:
+ try:
+ parentdir, name = self.get_parentdir(arg)
+ if (parentdir.open(name) == None):
+ parentdir.create(name)
+ except api.S5fsException as e:
+ self._parse_touch.error(str(e))
+
+ def help_touch(self):
+ self._parse_touch.print_help()
+
+ def complete_touch(self, text, line, begin, end):
+ return self.filepath_completion(text, line, begin, end)
+
+ def do_mkdir(self, args):
+ try:
+ (options, args) = self._parse_mkdir.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_mkdir.error(str(e))
+ return
+
+ if (len(args) == 0):
+ self._parse_mkdir.error("command requires at least one argument")
+ else:
+ for arg in args:
+ try:
+ parentdir, name = self.get_parentdir(arg)
+ parentdir.mkdir(name)
+ except api.S5fsException as e:
+ self._parse_mkdir.error(str(e))
+
+ def help_mkdir(self):
+ self._parse_mkdir.print_help()
+
+ def complete_mkdir(self, text, line, begin, end):
+ return self.filepath_completion(text, line, begin, end)
+
+ def getfile(self, source, dest):
+ dest.truncate()
+
+ loc = 0
+ data = source.read(20000)
+ while (len(data) != 0):
+ dest.write(loc, data)
+ loc += len(data)
+ data = source.read(20000)
+ source.close()
+
+ def do_getfile(self, args):
+ try:
+ (options, args) = self._parse_getfile.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_getfile.error(str(e))
+ return
+
+ if (len(args) != 2):
+ self._parse_getfile.error("command requires source and destination paths")
+ else:
+ try:
+ source = open(args[0], 'r')
+ dest = self.open(args[1], create=True)
+ self.getfile(source, dest)
+ except api.S5fsException as e:
+ self._parse_getfile.error(str(e))
+ except IOError as e:
+ self._parse_getfile.error(str(e))
+
+ def help_getfile(self):
+ self._parse_getfile.print_help()
+
+ def complete_getfile(self, text, line, begin, end):
+ argnum = self.completion_argnum(text, line, begin, end)
+ if (argnum == 1):
+ try:
+ return self.real_filepath_completion(text, line, begin, end)
+ except Exception as e:
+ print str(e)
+ elif (argnum == 2):
+ return self.filepath_completion(text, line, begin, end, types=set([ api.S5_TYPE_DATA ]))
+ else:
+ return []
+
+ def do_putfile(self, args):
+ try:
+ (options, args) = self._parse_putfile.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_putfile.error(str(e))
+ return
+
+ if (len(args) != 2):
+ self._parse_putfile.error("command requires a source and destination")
+ else:
+ try:
+ source = self.open(args[0])
+ if (None == source):
+ self._parse_putfile.error("no such file: {0}".format(args[0]))
+ dest = open(args[1], 'w+')
+
+ loc = 0
+ data = source.read(loc, 20000)
+ while (len(data) != 0):
+ dest.write(data)
+ loc += len(data)
+ data = source.read(loc, 20000)
+ dest.close()
+ except api.S5fsException as e:
+ self._parse_getfile.error(str(e))
+ except IOError as e:
+ self._parse_getfile.error(str(e))
+
+ def help_putfile(self):
+ self._parse_putfile.print_help()
+
+ def complete_putfile(self, text, line, begin, end):
+ argnum = self.completion_argnum(text, line, begin, end)
+ if (argnum == 1):
+ return self.filepath_completion(text, line, begin, end, types=set([ api.S5_TYPE_DATA ]))
+ elif (argnum == 2):
+ return self.real_filepath_completion(text, line, begin, end)
+ else:
+ return []
+
+ def do_format(self, args):
+ try:
+ (options, args) = self._parse_format.parse_args(shlex.split(args))
+ except ValueError as e:
+ self._parse_format.error(str(e))
+ return
+
+ if (options.size == None and options.blocks == None):
+ self._parse_format.error("must specify either -s or -b option to give size of formatted disk")
+ elif (options.size != None and options.blocks != None and options.size / api.S5_BLOCK_SIZE != options.blocks):
+ self._parse_format.error("specified conflicting -s and -b options, please only specify one")
+ elif (options.inodes == None):
+ self._parse_format.error("must specify -i option to give number of inodes to reserve on disk")
+ else:
+ if (options.size != None):
+ size = options.size
+ else:
+ size = options.blocks * api.S5_BLOCK_SIZE
+ self._simdisk.format(options.inodes, size)
+
+ if (options.directory):
+ q = Queue.Queue()
+ q.put(".")
+ while(not q.empty()):
+ curr = q.get()
+ real = os.path.join(options.directory, curr)
+ mode = os.stat(real).st_mode
+ if (stat.S_ISDIR(mode)):
+ for path in os.listdir(real):
+ q.put(os.path.join(curr, path))
+ parent, name = self.get_parentdir(os.path.join("/", curr))
+ if ("." != name):
+ parent.mkdir(name)
+ else:
+ source = open(real, 'r')
+ dest = self.open(os.path.join("/", curr), create=True)
+ self.getfile(source, dest)
+
+ def default(self, line):
+ if (line.strip() == "EOF"):
+ print("\n")
+ return True
+ else:
+ print(line.split()[0] + " is not a valid command")
+ return False
+
+_parser = optparse.OptionParser(usage="usage: %prog <simdisk> [options]", add_help_option=False,
+ description="command line tool to manipulate S5FS simdisks for Weenix")
+_parser.add_option("-e", "--execute", action="append", default=[], metavar="COMMAND",
+ help="Executes the given command on the simdisk. This flag can be used multiple times, the commands will be executed "
+ "in the order they appear on the command line. To see a list of commands use the -h flag. For information on a "
+ "specific command use the -c <command> flag.")
+_parser.add_option("-h", "--help", action="store_true", default=False,
+ help="Prints help information and a list of fsmaker commands.")
+_parser.add_option("-c", "--command", action="append", default=[],
+ help="Prints usage information about the specified command.")
+_parser.add_option("-i", "--interactive", action="store_true", default=False,
+ help="Causes fsmaker to open an interactive shell after executing all commands specified with the -e option.")
+(options, args) = _parser.parse_args()
+
+try:
+ if (len(args) > 1):
+ _parser.error("command takes at most one positional argument, but {0} were given".format(len(args)))
+ if (len(args) < 1 and not options.help and len(options.command) == 0):
+ _parser.error("command requires a simdisk file path")
+
+ if (len(args) < 1):
+ fs = FsmakerShell(api.Simdisk(tempfile.TemporaryFile()))
+ else:
+ try:
+ fs = FsmakerShell(api.Simdisk(open(args[0], 'rb+')))
+ except IOError as e:
+ if (e.errno == errno.ENOENT):
+ fs = FsmakerShell(api.Simdisk(open(args[0], 'wb+')))
+ else:
+ raise e
+
+ if (options.help):
+ _parser.print_help()
+ fs.onecmd("help")
+ for command in options.command:
+ fs.onecmd("help {0}".format(command))
+ for command in options.execute:
+ fs.onecmd(command)
+ if (options.interactive):
+ fs.cmdloop()
+except KeyboardInterrupt:
+ print("\n")