mirror of https://github.com/vinc/moros.git
Improve FUSE driver with write and delete (#292)
* Add write to FUSE driver * Update code * Fix file creation * Remove debug print * Remove more print statements * Remove unsupported function * Fix entries * Allocate space for new dir entry * Fix dir block size computation * Add unlink * Refactor private methods * Remove debug operation
This commit is contained in:
parent
512b60564b
commit
407d20c75c
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from time import time
|
||||||
from errno import ENOENT
|
from errno import ENOENT
|
||||||
from fuse import FUSE, FuseOSError, Operations, LoggingMixIn
|
from fuse import FUSE, FuseOSError, Operations, LoggingMixIn
|
||||||
from stat import S_IFDIR, S_IFREG
|
from stat import S_IFDIR, S_IFREG
|
||||||
|
@ -9,24 +10,22 @@ from stat import S_IFDIR, S_IFREG
|
||||||
BLOCK_SIZE = 512
|
BLOCK_SIZE = 512
|
||||||
SUPERBLOCK_ADDR = 4096 * BLOCK_SIZE
|
SUPERBLOCK_ADDR = 4096 * BLOCK_SIZE
|
||||||
BITMAP_ADDR = SUPERBLOCK_ADDR + 2 * BLOCK_SIZE
|
BITMAP_ADDR = SUPERBLOCK_ADDR + 2 * BLOCK_SIZE
|
||||||
|
ENTRY_SIZE = (1 + 4 + 4 + 8 + 1)
|
||||||
|
|
||||||
class MorosFuse(LoggingMixIn, Operations):
|
class MorosFuse(LoggingMixIn, Operations):
|
||||||
chmod = None
|
chmod = None
|
||||||
chown = None
|
chown = None
|
||||||
create = None
|
|
||||||
mkdir = None
|
|
||||||
readlink = None
|
readlink = None
|
||||||
rename = None
|
rename = None
|
||||||
rmdir = None
|
rmdir = None
|
||||||
symlink = None
|
symlink = None
|
||||||
truncate = None
|
truncate = None
|
||||||
unlink = None
|
|
||||||
utimens = None
|
utimens = None
|
||||||
write = None
|
getxattr = None
|
||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self.block_size = BLOCK_SIZE
|
self.block_size = BLOCK_SIZE
|
||||||
self.image = open(path, "rb")
|
self.image = open(path, "r+b")
|
||||||
self.image.seek(SUPERBLOCK_ADDR)
|
self.image.seek(SUPERBLOCK_ADDR)
|
||||||
superblock = self.image.read(self.block_size)
|
superblock = self.image.read(self.block_size)
|
||||||
assert superblock[0:8] == b"MOROS FS" # Signature
|
assert superblock[0:8] == b"MOROS FS" # Signature
|
||||||
|
@ -40,39 +39,54 @@ class MorosFuse(LoggingMixIn, Operations):
|
||||||
rest = (total - (BITMAP_ADDR // self.block_size)) * bs // (bs + 1)
|
rest = (total - (BITMAP_ADDR // self.block_size)) * bs // (bs + 1)
|
||||||
self.data_addr = BITMAP_ADDR + (rest // bs) * self.block_size
|
self.data_addr = BITMAP_ADDR + (rest // bs) * self.block_size
|
||||||
|
|
||||||
def destroy(self, path):
|
def __next_free_addr(self):
|
||||||
self.image.close()
|
for bitmap_addr in range(BITMAP_ADDR, self.data_addr):
|
||||||
return
|
self.image.seek(bitmap_addr)
|
||||||
|
bitmap = self.image.read(self.block_size)
|
||||||
|
for i in range(self.block_size):
|
||||||
|
byte = bitmap[i]
|
||||||
|
for bit in range(0, 8):
|
||||||
|
if (byte >> bit & 1) == 0:
|
||||||
|
block = (bitmap_addr - BITMAP_ADDR) * self.block_size * 8 + i * 8 + bit
|
||||||
|
return self.data_addr + block * self.block_size
|
||||||
|
|
||||||
def getattr(self, path, fh=None):
|
def __is_alloc(self, addr):
|
||||||
(kind, addr, size, time, name) = self.__scan(path)
|
block = (addr - self.data_addr) // self.block_size
|
||||||
if addr == 0:
|
bitmap_addr = BITMAP_ADDR + (block // (self.block_size * 8))
|
||||||
raise FuseOSError(ENOENT)
|
pos = bitmap_addr + (block // 8)
|
||||||
mode = S_IFDIR | 0o755 if kind == 0 else S_IFREG | 0o644
|
self.image.seek(pos)
|
||||||
return { "st_atime": 0, "st_mtime": time, "st_uid": 0, "st_gid": 0, "st_mode": mode, "st_size": size }
|
byte = int.from_bytes(self.image.read(1), "big")
|
||||||
|
bit = block % 8
|
||||||
|
return (byte >> bit & 1) == 1
|
||||||
|
|
||||||
def read(self, path, size, offset, fh):
|
def __alloc(self, addr):
|
||||||
(kind, next_block_addr, size, time, name) = self.__scan(path)
|
block = (addr - self.data_addr) // self.block_size
|
||||||
res = b""
|
bitmap_addr = BITMAP_ADDR + (block // (self.block_size * 8))
|
||||||
while next_block_addr != 0:
|
self.image.seek(bitmap_addr + (block // 8))
|
||||||
self.image.seek(next_block_addr)
|
byte = int.from_bytes(self.image.read(1), "big")
|
||||||
next_block_addr = int.from_bytes(self.image.read(4), "big") * self.block_size
|
self.image.seek(-1, 1)
|
||||||
if offset < self.block_size - 4:
|
bit = block % 8
|
||||||
buf = self.image.read(min(self.block_size - 4, size))
|
byte |= (1 << bit)
|
||||||
res = b"".join([res, buf[offset:]])
|
self.image.write(bytes([byte]))
|
||||||
offset = 0
|
|
||||||
else:
|
|
||||||
offset -= self.block_size - 4
|
|
||||||
size -= self.block_size - 4
|
|
||||||
return res
|
|
||||||
|
|
||||||
def readdir(self, path, fh):
|
def __free(self, addr):
|
||||||
files = [".", ".."]
|
block = (addr - self.data_addr) // self.block_size
|
||||||
(_, next_block_addr, _, _, _) = self.__scan(path)
|
bitmap_addr = BITMAP_ADDR + (block // (self.block_size * 8))
|
||||||
for (kind, addr, size, time, name) in self.__read(next_block_addr):
|
self.image.seek(bitmap_addr + (block // 8))
|
||||||
files.append(name)
|
byte = int.from_bytes(self.image.read(1), "big")
|
||||||
return files
|
self.image.seek(-1, 1)
|
||||||
|
bit = block % 8
|
||||||
|
byte &= ~(1 << bit)
|
||||||
|
self.image.write(bytes([byte]))
|
||||||
|
|
||||||
|
def __update_dir_size(self, path, entries):
|
||||||
|
entries.remove(".")
|
||||||
|
entries.remove("..")
|
||||||
|
(_, parent_addr, parent_size, _, parent_name) = self.__scan(path)
|
||||||
|
parent_size = ENTRY_SIZE * len(entries) + len("".join(entries))
|
||||||
|
self.image.seek(-(4 + 8 + 1 + len(parent_name)), 1)
|
||||||
|
self.image.write(parent_size.to_bytes(4, "big"))
|
||||||
|
return parent_addr
|
||||||
|
|
||||||
def __scan(self, path):
|
def __scan(self, path):
|
||||||
next_block_addr = self.data_addr
|
next_block_addr = self.data_addr
|
||||||
|
@ -107,6 +121,133 @@ class MorosFuse(LoggingMixIn, Operations):
|
||||||
if addr > 0:
|
if addr > 0:
|
||||||
yield (kind, addr, size, time, name)
|
yield (kind, addr, size, time, name)
|
||||||
|
|
||||||
|
def destroy(self, path):
|
||||||
|
self.image.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
def getattr(self, path, fh=None):
|
||||||
|
(kind, addr, size, time, name) = self.__scan(path)
|
||||||
|
if addr == 0:
|
||||||
|
raise FuseOSError(ENOENT)
|
||||||
|
mode = S_IFDIR | 0o755 if kind == 0 else S_IFREG | 0o644
|
||||||
|
return { "st_atime": 0, "st_mtime": time, "st_uid": 0, "st_gid": 0, "st_mode": mode, "st_size": size }
|
||||||
|
|
||||||
|
def read(self, path, size, offset, fh):
|
||||||
|
(kind, next_block_addr, size, time, name) = self.__scan(path)
|
||||||
|
res = b""
|
||||||
|
while next_block_addr != 0 and size > 0:
|
||||||
|
self.image.seek(next_block_addr)
|
||||||
|
next_block_addr = int.from_bytes(self.image.read(4), "big") * self.block_size
|
||||||
|
if offset < self.block_size - 4:
|
||||||
|
buf = self.image.read(max(0, min(self.block_size - 4, size)))
|
||||||
|
res = b"".join([res, buf[offset:]])
|
||||||
|
offset = 0
|
||||||
|
else:
|
||||||
|
offset -= self.block_size - 4
|
||||||
|
size -= self.block_size - 4
|
||||||
|
return res
|
||||||
|
|
||||||
|
def readdir(self, path, fh):
|
||||||
|
files = [".", ".."]
|
||||||
|
(_, next_block_addr, _, _, _) = self.__scan(path)
|
||||||
|
for (kind, addr, size, time, name) in self.__read(next_block_addr):
|
||||||
|
files.append(name)
|
||||||
|
return files
|
||||||
|
|
||||||
|
def mkdir(self, path, mode):
|
||||||
|
self.create(path, S_IFDIR | mode)
|
||||||
|
|
||||||
|
def create(self, path, mode):
|
||||||
|
(path, _, name) = path.rpartition("/")
|
||||||
|
entries = self.readdir(path + "/", 0)
|
||||||
|
entries.append(name)
|
||||||
|
pos = self.image.tell()
|
||||||
|
parent_addr = self.__update_dir_size(path, entries)
|
||||||
|
|
||||||
|
# Allocate space for the new dir entry if needed
|
||||||
|
blocks = 0
|
||||||
|
addr = parent_addr
|
||||||
|
next_addr = parent_addr
|
||||||
|
while next_addr != 0:
|
||||||
|
addr = next_addr
|
||||||
|
blocks += 1
|
||||||
|
self.image.seek(addr)
|
||||||
|
next_addr = int.from_bytes(self.image.read(4), "big") * self.block_size
|
||||||
|
free_size = (self.block_size - 4) - (pos - addr)
|
||||||
|
if free_size < ENTRY_SIZE + len(name):
|
||||||
|
pos = self.image.tell() - 4
|
||||||
|
addr = self.__next_free_addr()
|
||||||
|
self.__alloc(addr)
|
||||||
|
self.image.seek(pos)
|
||||||
|
self.image.write((addr // self.block_size).to_bytes(4, "big"))
|
||||||
|
pos = addr + 4
|
||||||
|
|
||||||
|
# Allocate space for the new file
|
||||||
|
kind = int((mode & S_IFDIR) != S_IFDIR)
|
||||||
|
size = 0
|
||||||
|
addr = self.__next_free_addr()
|
||||||
|
self.__alloc(addr)
|
||||||
|
|
||||||
|
# Add dir entry
|
||||||
|
self.image.seek(pos)
|
||||||
|
self.image.write(kind.to_bytes(1, "big"))
|
||||||
|
self.image.write((addr // self.block_size).to_bytes(4, "big"))
|
||||||
|
self.image.write(size.to_bytes(4, "big"))
|
||||||
|
self.image.write(int(time()).to_bytes(8, "big"))
|
||||||
|
self.image.write(len(name).to_bytes(1, "big"))
|
||||||
|
self.image.write(name.encode("utf-8"))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def write(self, path, data, offset, fh):
|
||||||
|
(_, addr, size, _, name) = self.__scan(path)
|
||||||
|
n = self.block_size - 4 # Space available for data in blocks
|
||||||
|
j = size % n # Start of space available in last block
|
||||||
|
|
||||||
|
# Update file size
|
||||||
|
self.image.seek(-(4 + 8 + 1 + len(name)), 1)
|
||||||
|
size = max(size, offset + len(data))
|
||||||
|
self.image.write(size.to_bytes(4, "big"))
|
||||||
|
|
||||||
|
for i in range(0, offset, n):
|
||||||
|
self.image.seek(addr)
|
||||||
|
next_addr = int.from_bytes(self.image.read(4), "big") * self.block_size
|
||||||
|
if i + n >= offset:
|
||||||
|
self.image.seek(addr + 4 + j)
|
||||||
|
self.image.write(data[0:(n - j)])
|
||||||
|
if next_addr == 0:
|
||||||
|
next_addr = self.__next_free_addr()
|
||||||
|
self.__alloc(next_addr)
|
||||||
|
self.image.seek(addr)
|
||||||
|
self.image.write((next_addr // self.block_size).to_bytes(4, "big"))
|
||||||
|
addr = next_addr
|
||||||
|
|
||||||
|
for i in range(n - j if j > 0 else 0, len(data), n):
|
||||||
|
next_addr = 0
|
||||||
|
if i + n < len(data): # TODO: check for off by one error
|
||||||
|
next_addr = self.__next_free_addr()
|
||||||
|
self.__alloc(next_addr)
|
||||||
|
self.image.seek(addr)
|
||||||
|
self.image.write((next_addr // self.block_size).to_bytes(4, "big"))
|
||||||
|
self.image.write(data[i:min(i + n, len(data))])
|
||||||
|
addr = next_addr
|
||||||
|
|
||||||
|
return len(data)
|
||||||
|
|
||||||
|
def unlink(self, path):
|
||||||
|
# Unlink and free blocs
|
||||||
|
(_, addr, size, _, name) = self.__scan(path)
|
||||||
|
self.image.seek(-(4 + 4 + 8 + 1 + len(name)), 1)
|
||||||
|
self.image.write((0).to_bytes(4, "big"))
|
||||||
|
while addr != 0:
|
||||||
|
self.__free(addr)
|
||||||
|
self.image.seek(addr)
|
||||||
|
addr = int.from_bytes(self.image.read(4), "big") * self.block_size
|
||||||
|
|
||||||
|
# Update parent dir size
|
||||||
|
(path, _, _) = path.rpartition("/")
|
||||||
|
entries = self.readdir(path + "/", 0)
|
||||||
|
self.__update_dir_size(path, entries)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import argparse
|
import argparse
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
@ -114,4 +255,4 @@ if __name__ == '__main__':
|
||||||
parser.add_argument('mount')
|
parser.add_argument('mount')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
#logging.basicConfig(level=logging.DEBUG)
|
#logging.basicConfig(level=logging.DEBUG)
|
||||||
fuse = FUSE(MorosFuse(args.image), args.mount, ro=True, foreground=True, allow_other=True)
|
fuse = FUSE(MorosFuse(args.image), args.mount, ro=False, foreground=True, allow_other=True)
|
||||||
|
|
Loading…
Reference in New Issue