Адаптировал к новому API llfuse

master
inpos 2017-02-19 16:58:52 +03:00
parent 7753267d07
commit 1a69027a4b
2 changed files with 66 additions and 62 deletions

View File

@ -4,6 +4,8 @@
Приложение работает на python3. Приложение работает на python3.
Зависит от модуля `llfuse`. Зависит от модуля `llfuse`.
В одной базе данных может храниться неограниченное количество изолированных дург от друга файловых систем.
База данных и пользователь с полным доступом к ней (если их ещё нет) должны быть созданы на сервере: База данных и пользователь с полным доступом к ней (если их ещё нет) должны быть созданы на сервере:
``` ```
su - postrges su - postrges
@ -21,12 +23,13 @@ chmod +x /usr/local/bin/udavfs3
`udavfs3 "host=srv_hostname dbname=fs_db user=fs_user password=fs_user_password" /mount/point/path [-o mount_options]` `udavfs3 "host=srv_hostname dbname=fs_db user=fs_user password=fs_user_password" /mount/point/path [-o mount_options]`
mount_options имеют смысл только для вновь созданной базы данных. mount_options (кроме fsname) имеют смысл только для несуществующей в базе ФС. fsname - обязателен, т.к. выбирает
Если мы монтируем уже инициализированную файловую систему, mount_options игнорируются. конкретную файловую систему из базы.
Если мы монтируем уже созданную файловую систему, mount_options игнорируются.
Список опций mount_options: Список опций mount_options:
* fsid - уникальная (в рамках конкретного сервера) строка без пробелов. Из данной строки сформируется sha1-хэш, который и будет * fsname - уникальная (в рамках конкретного сервера) строка без пробелов. Из данной строки сформируется sha1-хэш, который и будет
уникальным идентификатором ФС для операционной системы. уникальным идентификатором ФС для операционной системы.
* blocksize - размер блока ФС в байтах. Обычно используется 4096 (4 КБ). * blocksize - размер блока ФС в байтах. Обычно используется 4096 (4 КБ).
* fssize - размер файловой системы. Можно использовать окончание еденицы измерения (k,m,g,t). Минимальный размер - 4 МБ. * fssize - размер файловой системы. Можно использовать окончание еденицы измерения (k,m,g,t). Минимальный размер - 4 МБ.

121
udavfs3
View File

@ -66,9 +66,9 @@ class Operations(llfuse.Operations):
uid INT NOT NULL, uid INT NOT NULL,
gid INT NOT NULL, gid INT NOT NULL,
mode INT NOT NULL, mode INT NOT NULL,
mtime FLOAT NOT NULL, mtime BIGINT NOT NULL,
atime FLOAT NOT NULL, atime BIGINT NOT NULL,
ctime FLOAT NOT NULL, ctime BIGINT NOT NULL,
target BYTEA, target BYTEA,
size BIGINT NOT NULL DEFAULT 0, size BIGINT NOT NULL DEFAULT 0,
rdev INT NOT NULL DEFAULT 0, rdev INT NOT NULL DEFAULT 0,
@ -111,8 +111,8 @@ class Operations(llfuse.Operations):
"VALUES (%s,%s,%s,%s,%s,%s,%s,%s)", "VALUES (%s,%s,%s,%s,%s,%s,%s,%s)",
(self.fsid, llfuse.ROOT_INODE, stat.S_IFDIR | stat.S_IRUSR | stat.S_IWUSR (self.fsid, llfuse.ROOT_INODE, stat.S_IFDIR | stat.S_IRUSR | stat.S_IWUSR
| stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH
| stat.S_IXOTH, os.getuid(), os.getgid(), time(), | stat.S_IXOTH, os.getuid(), os.getgid(), int(time() * 1e9),
time(), time())) int(time() * 1e9), int(time() * 1e9)))
self.cursor.execute("SELECT setval('inodes_inode_id_seq', %s);", (llfuse.ROOT_INODE + 1,)) self.cursor.execute("SELECT setval('inodes_inode_id_seq', %s);", (llfuse.ROOT_INODE + 1,))
self.cursor.execute("INSERT INTO contents (fsid, name, parent_inode, inode_id) VALUES (%s,%s,%s,%s)", self.cursor.execute("INSERT INTO contents (fsid, name, parent_inode, inode_id) VALUES (%s,%s,%s,%s)",
(self.fsid, b'..', llfuse.ROOT_INODE, llfuse.ROOT_INODE)) (self.fsid, b'..', llfuse.ROOT_INODE, llfuse.ROOT_INODE))
@ -129,7 +129,7 @@ class Operations(llfuse.Operations):
return row return row
def lookup(self, inode_p, name): def lookup(self, inode_p, name, ctx):
if name == b'.': if name == b'.':
inode = inode_p inode = inode_p
elif name == b'..': elif name == b'..':
@ -142,9 +142,9 @@ class Operations(llfuse.Operations):
except NoSuchRowError: except NoSuchRowError:
raise(llfuse.FUSEError(errno.ENOENT)) raise(llfuse.FUSEError(errno.ENOENT))
return self.getattr(inode) return self.getattr(inode, ctx)
def getattr(self, inode): def getattr(self, inode, ctx = None):
cur = self.db.cursor(cursor_factory=psycopg2.extras.DictCursor) cur = self.db.cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute('SELECT * FROM inodes WHERE inode_id=%s AND fsid=%s', (inode, self.fsid)) cur.execute('SELECT * FROM inodes WHERE inode_id=%s AND fsid=%s', (inode, self.fsid))
row = cur.fetchone() row = cur.fetchone()
@ -164,16 +164,16 @@ class Operations(llfuse.Operations):
entry.st_blksize = self.blocksize entry.st_blksize = self.blocksize
entry.st_blocks = self.get_row("SELECT COUNT(1) FROM body WHERE inode_id=%s AND fsid=%s", entry.st_blocks = self.get_row("SELECT COUNT(1) FROM body WHERE inode_id=%s AND fsid=%s",
(inode, self.fsid))[0] (inode, self.fsid))[0]
entry.st_atime = row['atime'] entry.st_atime_ns = row['atime']
entry.st_mtime = row['mtime'] entry.st_mtime_ns = row['mtime']
entry.st_ctime = row['ctime'] entry.st_ctime_ns = row['ctime']
return entry return entry
def readlink(self, inode): def readlink(self, inode, ctx):
return bytes(self.get_row('SELECT target FROM inodes WHERE inode_id=%s AND fsid=%s', (inode, self.fsid))[0]) return bytes(self.get_row('SELECT target FROM inodes WHERE inode_id=%s AND fsid=%s', (inode, self.fsid))[0])
def opendir(self, inode): def opendir(self, inode, ctx):
return inode return inode
def readdir(self, inode, off): def readdir(self, inode, off):
@ -187,16 +187,16 @@ class Operations(llfuse.Operations):
for row in cursor2.fetchall(): for row in cursor2.fetchall():
yield (bytes(row['name']), self.getattr(row['inode_id']), row['rowid']) yield (bytes(row['name']), self.getattr(row['inode_id']), row['rowid'])
def unlink(self, inode_p, name): def unlink(self, inode_p, name, ctx):
entry = self.lookup(inode_p, name) entry = self.lookup(inode_p, name, ctx)
if stat.S_ISDIR(entry.st_mode): if stat.S_ISDIR(entry.st_mode):
raise llfuse.FUSEError(errno.EISDIR) raise llfuse.FUSEError(errno.EISDIR)
self._remove(inode_p, name, entry) self._remove(inode_p, name, entry)
def rmdir(self, inode_p, name): def rmdir(self, inode_p, name, ctx):
entry = self.lookup(inode_p, name) entry = self.lookup(inode_p, name, ctx)
if not stat.S_ISDIR(entry.st_mode): if not stat.S_ISDIR(entry.st_mode):
raise llfuse.FUSEError(errno.ENOTDIR) raise llfuse.FUSEError(errno.ENOTDIR)
@ -221,11 +221,11 @@ class Operations(llfuse.Operations):
stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH)
return self._create(inode_p, name, mode, ctx, target=target) return self._create(inode_p, name, mode, ctx, target=target)
def rename(self, inode_p_old, name_old, inode_p_new, name_new): def rename(self, inode_p_old, name_old, inode_p_new, name_new, ctx):
entry_old = self.lookup(inode_p_old, name_old) entry_old = self.lookup(inode_p_old, name_old, ctx)
try: try:
entry_new = self.lookup(inode_p_new, name_new) entry_new = self.lookup(inode_p_new, name_new, ctx)
except llfuse.FUSEError as exc: except llfuse.FUSEError as exc:
if exc.errno != errno.ENOENT: if exc.errno != errno.ENOENT:
raise raise
@ -257,21 +257,21 @@ class Operations(llfuse.Operations):
self.cursor.execute("DELETE FROM inodes WHERE inode_id=%s AND fsid=%s", (entry_new.st_ino, self.fsid)) self.cursor.execute("DELETE FROM inodes WHERE inode_id=%s AND fsid=%s", (entry_new.st_ino, self.fsid))
def link(self, inode, new_inode_p, new_name): def link(self, inode, new_inode_p, new_name, ctx):
entry_p = self.getattr(new_inode_p) entry_p = self.getattr(new_inode_p, ctx)
if entry_p.st_nlink == 0: if entry_p.st_nlink == 0:
raise FUSEError(errno.EINVAL) raise FUSEError(errno.EINVAL)
self.cursor.execute("INSERT INTO contents (name, inode_id, parent_inode, fsid) VALUES(%s,%s,%s,%s)", self.cursor.execute("INSERT INTO contents (name, inode_id, parent_inode, fsid) VALUES(%s,%s,%s,%s)",
(new_name, inode, new_inode_p, self.fsid)) (new_name, inode, new_inode_p, self.fsid))
return self.getattr(inode) return self.getattr(inode, ctx)
def del_block(self, inode, num): def del_block(self, inode, num):
self.cursor.execute("DELETE FROM body WHERE inode_id=%s AND block_no=%s AND fsid=%s", (inode, num, self.fsid)) self.cursor.execute("DELETE FROM body WHERE inode_id=%s AND block_no=%s AND fsid=%s", (inode, num, self.fsid))
def setattr(self, inode, attr): def setattr(self, inode, attr, fields, fh, ctx):
if attr.st_size is not None: if fields.update_size:
size = self.get_row('SELECT size FROM inodes WHERE inode_id=%s AND fsid=%s', (inode, self.fsid))[0] size = self.get_row('SELECT size FROM inodes WHERE inode_id=%s AND fsid=%s', (inode, self.fsid))[0]
if size is None: if size is None:
size = 0 size = 0
@ -314,15 +314,15 @@ class Operations(llfuse.Operations):
else: else:
self.cursor.execute("UPDATE inodes SET size=%s WHERE inode_id=%s AND fsid=%s", self.cursor.execute("UPDATE inodes SET size=%s WHERE inode_id=%s AND fsid=%s",
(attr.st_size, inode, self.fsid)) (attr.st_size, inode, self.fsid))
if attr.st_mode is not None: if fields.update_mode:
self.cursor.execute('UPDATE inodes SET mode=%s WHERE inode_id=%s AND fsid=%s', self.cursor.execute('UPDATE inodes SET mode=%s WHERE inode_id=%s AND fsid=%s',
(attr.st_mode, inode, self.fsid)) (attr.st_mode, inode, self.fsid))
if attr.st_uid is not None: if fields.update_uid:
self.cursor.execute('UPDATE inodes SET uid=%s WHERE inode_id=%s AND fsid=%s', self.cursor.execute('UPDATE inodes SET uid=%s WHERE inode_id=%s AND fsid=%s',
(attr.st_uid, inode, self.fsid)) (attr.st_uid, inode, self.fsid))
if attr.st_gid is not None: if fields.update_gid:
self.cursor.execute('UPDATE inodes SET gid=%s WHERE inode_id=%s AND fsid=%s', self.cursor.execute('UPDATE inodes SET gid=%s WHERE inode_id=%s AND fsid=%s',
(attr.st_gid, inode, self.fsid)) (attr.st_gid, inode, self.fsid))
@ -330,19 +330,19 @@ class Operations(llfuse.Operations):
self.cursor.execute('UPDATE inodes SET rdev=%s WHERE inode_id=%s AND fsid=%s', self.cursor.execute('UPDATE inodes SET rdev=%s WHERE inode_id=%s AND fsid=%s',
(attr.st_rdev, inode, self.fsid)) (attr.st_rdev, inode, self.fsid))
if attr.st_atime is not None: if fields.update_atime:
self.cursor.execute('UPDATE inodes SET atime=%s WHERE inode_id=%s AND fsid=%s', self.cursor.execute('UPDATE inodes SET atime=%s WHERE inode_id=%s AND fsid=%s',
(attr.st_atime, inode, self.fsid)) (attr.st_atime_ns, inode, self.fsid))
if attr.st_mtime is not None: if fields.update_mtime:
self.cursor.execute('UPDATE inodes SET mtime=%s WHERE inode_id=%s AND fsid=%s', self.cursor.execute('UPDATE inodes SET mtime=%s WHERE inode_id=%s AND fsid=%s',
(attr.st_mtime, inode, self.fsid)) (attr.st_mtime_ns, inode, self.fsid))
if attr.st_ctime is not None: if attr.st_ctime_ns is not None:
self.cursor.execute('UPDATE inodes SET ctime=%s WHERE inode_id=%s AND fsid=%s', self.cursor.execute('UPDATE inodes SET ctime=%s WHERE inode_id=%s AND fsid=%s',
(attr.st_ctime, inode, self.fsid)) (attr.st_ctime_ns, inode, self.fsid))
return self.getattr(inode) return self.getattr(inode, ctx)
def mknod(self, inode_p, name, mode, rdev, ctx): def mknod(self, inode_p, name, mode, rdev, ctx):
return self._create(inode_p, name, mode, ctx, rdev=rdev) return self._create(inode_p, name, mode, ctx, rdev=rdev)
@ -350,7 +350,7 @@ class Operations(llfuse.Operations):
def mkdir(self, inode_p, name, mode, ctx): def mkdir(self, inode_p, name, mode, ctx):
return self._create(inode_p, name, mode, ctx) return self._create(inode_p, name, mode, ctx)
def statfs(self): def statfs(self, ctx):
stat_ = llfuse.StatvfsData() stat_ = llfuse.StatvfsData()
stat_.f_bsize = self.blocksize stat_.f_bsize = self.blocksize
@ -368,11 +368,11 @@ class Operations(llfuse.Operations):
return stat_ return stat_
def open(self, inode, flags): def open(self, inode, flags, ctx):
self.inode_open_count[inode] += 1 self.inode_open_count[inode] += 1
return inode return inode
def access(self, inode, mode, ctx): def access(self, inode, mode, ctx=None):
if mode != os.F_OK and not self.__access(inode, mode, ctx): if mode != os.F_OK and not self.__access(inode, mode, ctx):
return False return False
return True return True
@ -396,18 +396,18 @@ class Operations(llfuse.Operations):
return (entry.st_ino, entry) return (entry.st_ino, entry)
def _create(self, inode_p, name, mode, ctx, rdev=0, target=None): def _create(self, inode_p, name, mode, ctx, rdev=0, target=None):
if self.getattr(inode_p).st_nlink == 0: if self.getattr(inode_p, ctx).st_nlink == 0:
raise FUSEError(errno.EINVAL) raise FUSEError(errno.EINVAL)
self.cursor.execute('INSERT INTO inodes (uid, gid, mode, mtime, atime, ' self.cursor.execute('INSERT INTO inodes (uid, gid, mode, mtime, atime, '
'ctime, target, rdev, fsid) VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING inode_id;', 'ctime, target, rdev, fsid) VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s) RETURNING inode_id;',
(ctx.uid, ctx.gid, mode, time(), time(), time(), target, rdev, self.fsid)) (ctx.uid, ctx.gid, mode, int(time() * 1e9), int(time() * 1e9), int(time() * 1e9), target, rdev, self.fsid))
inode = self.cursor.fetchone()[0] inode = self.cursor.fetchone()[0]
self.cursor.execute("INSERT INTO contents(name, inode_id, parent_inode, fsid) VALUES(%s,%s,%s,%s);", self.cursor.execute("INSERT INTO contents(name, inode_id, parent_inode, fsid) VALUES(%s,%s,%s,%s);",
(name, inode, inode_p, self.fsid)) (name, inode, inode_p, self.fsid))
return self.getattr(inode) return self.getattr(inode, ctx)
def block_info(self, offset, length): def block_info(self, offset, length):
info = {} info = {}
@ -502,38 +502,40 @@ class NoSuchRowError(Exception):
def usage(): def usage():
raise SystemExit('''\ raise SystemExit('''\
Usage: %s "host=dbhost dbname=database user=dbuser password=dbpass" <mountpoint> [-o mount_options] Usage: %s "host=dbhost dbname=database user=dbuser password=dbpass" <mountpoint> -o mount_options
UdavFS mount_option: UdavFS mount_option:
fsid=<uniq_string> fsid=<uniq_string>
blocksize=<bytes> [locksize=<bytes>
fssize=<size>[k|m|g|t] fssize=<size>[k|m|g|t] ]
Option 'user_allow_other' MUST be set in /etc/fuse.conf''' % sys.argv[0]) Option 'user_allow_other' MUST be set in /etc/fuse.conf''' % sys.argv[0])
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) < 3 or len(sys.argv) > 3 and len(sys.argv) < 5 or len(sys.argv) > 5: if len(sys.argv) != 5:
usage() usage()
if sys.argv[3] != '-o' and len(sys.argv) != 5: if sys.argv[3] != '-o':
usage() usage()
conn_str = sys.argv[1] conn_str = sys.argv[1] + ' sslmode=\'require\''
options = {x.split('=')[0].strip(): len(x.split('=')) > 1 and x.split('=')[1].strip() or True for x in sys.argv[4].split(',') } options = {x.split('=')[0].strip(): len(x.split('=')) > 1 and x.split('=')[1].strip() or True for x in sys.argv[4].split(',') }
db = psycopg2.connect(conn_str) db = psycopg2.connect(conn_str)
db.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT) db.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
cursor = db.cursor() cursor = db.cursor()
try: if 'fsname' not in options.keys():
cursor.execute('SELECT fsid, bs, size FROM fsinfo WHERE fsid=%s', (fsid,)) print('fsname MUST be in mountoptions\n')
fsid, blocksize, fssize = cursor.fetchone() usage()
except: fsname = options['fsname']
if 'fsid' not in options.keys(): del options['fsname']
print('fsid MUST be in mountoptions\n') fsid = sha1(bytes(fsname.strip(), 'UTF-8')).hexdigest()
usage()
fsname = options['fsid']
del options['fsid']
fsid = sha1(bytes(fsname.strip(), 'UTF-8')).hexdigest()
try:
cursor.execute('SELECT bs, size FROM fsinfo WHERE fsid=%s', (fsid,))
blocksize, fssize = cursor.fetchone()
if options['fssize']: del options['fssize']
if options['blocksize']: del options['blocksize']
except:
if 'blocksize' not in options.keys(): if 'blocksize' not in options.keys():
print('blocksize mountoption MUST be specified\n') print('blocksize mountoption MUST be specified\n')
usage() usage()
@ -593,12 +595,11 @@ if __name__ == '__main__':
operations = Operations(conn_str, fsid, blocksize, fssize) operations = Operations(conn_str, fsid, blocksize, fssize)
llfuse.init(operations, mountpoint, llfuse.init(operations, mountpoint, m_params)
m_params)
try: try:
llfuse.main(single=False) llfuse.main(workers=None)
except: except:
llfuse.close(unmount=False) llfuse.close(unmount=False)
raise raise