<reponame>aggregate1166877/glassfs
#!/usr/bin/env python

# Usage example:
# python glass_fs.py /your/dir /mnt/point

# See also:
# https://www.thepythoncorner.com/2017/02/writing-a-fuse-filesystem-in-python/?doing_wp_cron=1562559777.6591670513153076171875

from __future__ import with_statement

import os
import sys
import getpass
import stat
import signal
import errno
import time
import glob
import json
import hashlib

# https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp
# https://stackoverflow.com/questions/8577137/how-can-i-create-a-tmp-file-in-python
# import tempfile

from fuse import FUSE, FuseOSError, Operations

if os.geteuid() == 0:
    print(
        'This application does not currently support being run as root. '
        'For such requirements, it may be better to use aufs or overlayfs instead.'
    )
    sys.exit(1)

# Global vars
version = '0.0.9 beta'
motd = 'GlassFS version ' + version
home_dir = os.path.expanduser('~')
config_dir = home_dir + '/.glassfs'
run_dir = config_dir + '/meta'
start_time = time.time()
gid = os.getgid()
hide_glass_meta_files = True

# Init
print(motd)
print(' * Run dir:', run_dir)
os.makedirs(run_dir, exist_ok=True)
if not os.path.isdir(run_dir) or os.path.abspath(run_dir) == '/':
    # We batch remove stuff from the run dir, so make sure it's semi-safe.
    raise Exception('Run dir not valid:', run_dir)

if hide_glass_meta_files:
    print(' * Glass meta files will be hidden, but still readable as .__GlassFS__.info')
else:
    print(' * Not hiding glass meta files: .__GlassFS__.info')


# Remove all meta files on start
def clean_meta():
    global run_dir
    run_dir_files = glob.glob(run_dir + '/*')
    print(' * Deleting meta files:', run_dir + '/*')
    for f in run_dir_files:
        if os.path.isfile(f):
            os.remove(f)


clean_meta()


def sha256(plaintext):
    return hashlib.sha256(plaintext.encode('utf-8')).hexdigest()


def get_glass_info_meta():
    global start_time, gid
    return {
        'st_atime': start_time,
        'st_ctime': start_time,
        'st_gid': gid,
        'st_mode': 33252,  # 744
        'st_mtime': start_time,
        'st_nlink': 2,
        'st_size': 0,
        'st_uid': 0
    }


def glass_template(parent, meta, meta_path, glass_roots):
    return {
        'daemon': os.getpid(),
        'effective_parent': parent,
        'effective_meta': meta,
        'meta_path': meta_path,
        'glass_roots': glass_roots,
    }


class GlassFS(Operations):
    glass_roots = []
    glass_index = 0

    def __init__(self, root_dirs):
        self.glass_roots = root_dirs.split(':')
        # self.glass_roots.reverse()
        print(' * root dirs:', self.glass_roots)
        self.root = self.get_root()

    # Helpers
    # =======

    def _full_path(self, partial, get_multi=False):
        global run_dir
        if partial.startswith("/"):
            partial = partial[1:]

        path_list = []

        for root in self.glass_roots:
            # Check if the user has access to this root:
            if not os.access(root, os.X_OK):
                continue

            path = os.path.join(root, partial)

            if partial.endswith('.__GlassFS__.info'):
                meta_path = run_dir + '/' + sha256(path)
                # short_parent = os.path.basename(os.path.normpath(path.replace('/' + partial, '')))
                parent = os.path.normpath(path.replace('/' + partial, ''))
                self.create_meta_file(parent, path, meta_path)
                path = meta_path

            # print('path:', path)

            if get_multi:
                path_list.append(path)
            else:
                if os.path.exists(path):
                    break

        if get_multi:
            return path_list
        else:
            return path

    def get_root(self):
        return self.glass_roots[self.glass_index]

    def cycle_roots(self, sig=None, frame=None):
        """Moves the first glass root to the end of the list."""
        self.glass_roots.append(self.glass_roots[0])
        del self.glass_roots[0]
        clean_meta()

    def uncycle_roots(self, sig=None, frame=None):
        """Moves the bottom glass root to the top of the list."""
        length = len(self.glass_roots)
        self.glass_roots.insert(0, self.glass_roots[length - 1])
        del self.glass_roots[length]
        clean_meta()

    def create_meta_file(self, parent, path, meta_path):
        if not os.path.exists(meta_path):
            with open(meta_path, 'w') as file:
                file.write(
                    json.dumps(
                        glass_template(parent, path, meta_path, self.glass_roots),
                        indent=4,
                        sort_keys=True,
                    )
                )

    # Filesystem methods
    # ==================

    # def ioctl(self, path, cmd, arg, fip, flags, data):
    #     print('ioctl not yet implemented - please assist if you know how.')

    def access(self, path, mode):
        # print('[access]', path)
        full_path = self._full_path(path)
        if not os.access(full_path, mode):
            raise FuseOSError(errno.EACCES)

    def chmod(self, path, mode):
        full_path = self._full_path(path)
        return os.chmod(full_path, mode)

    def chown(self, path, uid, gid):
        full_path = self._full_path(path)
        return os.chown(full_path, uid, gid)

    def getattr(self, path, fh=None):
        full_path = self._full_path(path)
        st = os.lstat(full_path)
        result = dict((key, getattr(st, key)) for key in ('st_atime', 'st_ctime',
                         'st_gid', 'st_mode', 'st_mtime', 'st_nlink', 'st_size', 'st_uid'))
        return result

    def readdir(self, path, fh):
        global hide_glass_meta_files
        full_paths = self._full_path(path, True)

        # print('[readdir path]', path)
        # print('[readdir full_path]', full_path)

        dirents = ['.', '..']
        if not hide_glass_meta_files:
            dirents.append('.__GlassFS__.info')

        for full_path in full_paths:
            if os.path.isdir(full_path):
                dirents.extend(os.listdir(full_path))

        # Remove dups as a result of mergers.
        dirents = set(dirents)

        for r in dirents:
            yield r

    def readlink(self, path):
        pathname = os.readlink(self._full_path(path))
        if pathname.startswith("/"):
            # Path name is absolute, sanitize it.
            return os.path.relpath(pathname, self.root)
        else:
            return pathname

    def mknod(self, path, mode, dev):
        return os.mknod(self._full_path(path), mode, dev)

    def rmdir(self, path):
        full_path = self._full_path(path)
        return os.rmdir(full_path)

    def mkdir(self, path, mode):
        return os.mkdir(self._full_path(path), mode)

    def statfs(self, path):
        full_path = self._full_path(path)
        stv = os.statvfs(full_path)
        return dict((key, getattr(stv, key)) for key in ('f_bavail', 'f_bfree',
            'f_blocks', 'f_bsize', 'f_favail', 'f_ffree', 'f_files', 'f_flag',
            'f_frsize', 'f_namemax'))

    def unlink(self, path):
        return os.unlink(self._full_path(path))

    def symlink(self, name, target):
        return os.symlink(target, self._full_path(name))

    def rename(self, old, new):
        return os.rename(self._full_path(old), self._full_path(new))

    def link(self, target, name):
        return os.link(self._full_path(name), self._full_path(target))

    def utimens(self, path, times=None):
        return os.utime(self._full_path(path), times)

    # File methods
    # ============

    def open(self, path, flags):
        full_path = self._full_path(path)
        # print('[open]', path)
        return os.open(full_path, flags)

    def create(self, path, mode, fi=None):
        # print('[create]', path)
        full_path = self._full_path(path)
        return os.open(full_path, os.O_WRONLY | os.O_CREAT, mode)

    def read(self, path, length, offset, fh):
        # print('[read]', path)
        os.lseek(fh, offset, os.SEEK_SET)
        return os.read(fh, length)

    def write(self, path, buf, offset, fh):
        write_mode = 'rw'  # rw, ro, fake - TODO: implement me.

        if write_mode == 'rw':
            os.lseek(fh, offset, os.SEEK_SET)
            return os.write(fh, buf)
        elif write_mode == 'ro':
            return
        # print('[write]', path)

    def truncate(self, path, length, fh=None):
        full_path = self._full_path(path)
        with open(full_path, 'r+') as f:
            f.truncate(length)

    def flush(self, path, fh):
        return os.fsync(fh)

    def release(self, path, fh):
        # print('[release]', path)
        return os.close(fh)

    def fsync(self, path, fdatasync, fh):
        return self.flush(path, fh)


def main(mountpoint, root):
    glass = GlassFS(root)

    signal.signal(signal.SIGUSR1, glass.cycle_roots)
    signal.signal(signal.SIGUSR2, glass.uncycle_roots)

    FUSE(glass, mountpoint, nothreads=True, foreground=True, **{'allow_other': True})


if __name__ == '__main__':
    main(sys.argv[2], sys.argv[1])

#!/usr/bin/env python

# Usage example:
# python glass_fs.py /your/dir /mnt/point

# See also:
# https://www.thepythoncorner.com/2017/02/writing-a-fuse-filesystem-in-python/?doing_wp_cron=1562559777.6591670513153076171875

from __future__ import with_statement

import os
import sys
import getpass
import stat
import signal
import errno
import time
import glob
import json
import hashlib

# https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp
# https://stackoverflow.com/questions/8577137/how-can-i-create-a-tmp-file-in-python
# import tempfile

from fuse import FUSE, FuseOSError, Operations

if os.geteuid() == 0:
    print(
        'This application does not currently support being run as root. '
        'For such requirements, it may be better to use aufs or overlayfs instead.'
    )
    sys.exit(1)

# Global vars
version = '0.0.9 beta'
motd = 'GlassFS version ' + version
home_dir = os.path.expanduser('~')
config_dir = home_dir + '/.glassfs'
run_dir = config_dir + '/meta'
start_time = time.time()
gid = os.getgid()
hide_glass_meta_files = True

# Init
print(motd)
print(' * Run dir:', run_dir)
os.makedirs(run_dir, exist_ok=True)
if not os.path.isdir(run_dir) or os.path.abspath(run_dir) == '/':
    # We batch remove stuff from the run dir, so make sure it's semi-safe.
    raise Exception('Run dir not valid:', run_dir)

if hide_glass_meta_files:
    print(' * Glass meta files will be hidden, but still readable as .__GlassFS__.info')
else:
    print(' * Not hiding glass meta files: .__GlassFS__.info')


# Remove all meta files on start
def clean_meta():
    global run_dir
    run_dir_files = glob.glob(run_dir + '/*')
    print(' * Deleting meta files:', run_dir + '/*')
    for f in run_dir_files:
        if os.path.isfile(f):
            os.remove(f)


clean_meta()


def sha256(plaintext):
    return hashlib.sha256(plaintext.encode('utf-8')).hexdigest()


def get_glass_info_meta():
    global start_time, gid
    return {
        'st_atime': start_time,
        'st_ctime': start_time,
        'st_gid': gid,
        'st_mode': 33252,  # 744
        'st_mtime': start_time,
        'st_nlink': 2,
        'st_size': 0,
        'st_uid': 0
    }


def glass_template(parent, meta, meta_path, glass_roots):
    return {
        'daemon': os.getpid(),
        'effective_parent': parent,
        'effective_meta': meta,
        'meta_path': meta_path,
        'glass_roots': glass_roots,
    }


class GlassFS(Operations):
    glass_roots = []
    glass_index = 0

    def __init__(self, root_dirs):
        self.glass_roots = root_dirs.split(':')
        # self.glass_roots.reverse()
        print(' * root dirs:', self.glass_roots)
        self.root = self.get_root()

    # Helpers
    # =======

    def _full_path(self, partial, get_multi=False):
        global run_dir
        if partial.startswith("/"):
            partial = partial[1:]

        path_list = []

        for root in self.glass_roots:
            # Check if the user has access to this root:
            if not os.access(root, os.X_OK):
                continue

            path = os.path.join(root, partial)

            if partial.endswith('.__GlassFS__.info'):
                meta_path = run_dir + '/' + sha256(path)
                # short_parent = os.path.basename(os.path.normpath(path.replace('/' + partial, '')))
                parent = os.path.normpath(path.replace('/' + partial, ''))
                self.create_meta_file(parent, path, meta_path)
                path = meta_path

            # print('path:', path)

            if get_multi:
                path_list.append(path)
            else:
                if os.path.exists(path):
                    break

        if get_multi:
            return path_list
        else:
            return path

    def get_root(self):
        return self.glass_roots[self.glass_index]

    def cycle_roots(self, sig=None, frame=None):
        """Moves the first glass root to the end of the list."""
        self.glass_roots.append(self.glass_roots[0])
        del self.glass_roots[0]
        clean_meta()

    def uncycle_roots(self, sig=None, frame=None):
        """Moves the bottom glass root to the top of the list."""
        length = len(self.glass_roots)
        self.glass_roots.insert(0, self.glass_roots[length - 1])
        del self.glass_roots[length]
        clean_meta()

    def create_meta_file(self, parent, path, meta_path):
        if not os.path.exists(meta_path):
            with open(meta_path, 'w') as file:
                file.write(
                    json.dumps(
                        glass_template(parent, path, meta_path, self.glass_roots),
                        indent=4,
                        sort_keys=True,
                    )
                )

    # Filesystem methods
    # ==================

    # def ioctl(self, path, cmd, arg, fip, flags, data):
    #     print('ioctl not yet implemented - please assist if you know how.')

    def access(self, path, mode):
        # print('[access]', path)
        full_path = self._full_path(path)
        if not os.access(full_path, mode):
            raise FuseOSError(errno.EACCES)

    def chmod(self, path, mode):
        full_path = self._full_path(path)
        return os.chmod(full_path, mode)

    def chown(self, path, uid, gid):
        full_path = self._full_path(path)
        return os.chown(full_path, uid, gid)

    def getattr(self, path, fh=None):
        full_path = self._full_path(path)
        st = os.lstat(full_path)
        result = dict((key, getattr(st, key)) for key in ('st_atime', 'st_ctime',
                         'st_gid', 'st_mode', 'st_mtime', 'st_nlink', 'st_size', 'st_uid'))
        return result

    def readdir(self, path, fh):
        global hide_glass_meta_files
        full_paths = self._full_path(path, True)

        # print('[readdir path]', path)
        # print('[readdir full_path]', full_path)

        dirents = ['.', '..']
        if not hide_glass_meta_files:
            dirents.append('.__GlassFS__.info')

        for full_path in full_paths:
            if os.path.isdir(full_path):
                dirents.extend(os.listdir(full_path))

        # Remove dups as a result of mergers.
        dirents = set(dirents)

        for r in dirents:
            yield r

    def readlink(self, path):
        pathname = os.readlink(self._full_path(path))
        if pathname.startswith("/"):
            # Path name is absolute, sanitize it.
            return os.path.relpath(pathname, self.root)
        else:
            return pathname

    def mknod(self, path, mode, dev):
        return os.mknod(self._full_path(path), mode, dev)

    def rmdir(self, path):
        full_path = self._full_path(path)
        return os.rmdir(full_path)

    def mkdir(self, path, mode):
        return os.mkdir(self._full_path(path), mode)

    def statfs(self, path):
        full_path = self._full_path(path)
        stv = os.statvfs(full_path)
        return dict((key, getattr(stv, key)) for key in ('f_bavail', 'f_bfree',
            'f_blocks', 'f_bsize', 'f_favail', 'f_ffree', 'f_files', 'f_flag',
            'f_frsize', 'f_namemax'))

    def unlink(self, path):
        return os.unlink(self._full_path(path))

    def symlink(self, name, target):
        return os.symlink(target, self._full_path(name))

    def rename(self, old, new):
        return os.rename(self._full_path(old), self._full_path(new))

    def link(self, target, name):
        return os.link(self._full_path(name), self._full_path(target))

    def utimens(self, path, times=None):
        return os.utime(self._full_path(path), times)

    # File methods
    # ============

    def open(self, path, flags):
        full_path = self._full_path(path)
        # print('[open]', path)
        return os.open(full_path, flags)

    def create(self, path, mode, fi=None):
        # print('[create]', path)
        full_path = self._full_path(path)
        return os.open(full_path, os.O_WRONLY | os.O_CREAT, mode)

    def read(self, path, length, offset, fh):
        # print('[read]', path)
        os.lseek(fh, offset, os.SEEK_SET)
        return os.read(fh, length)

    def write(self, path, buf, offset, fh):
        write_mode = 'rw'  # rw, ro, fake - TODO: implement me.

        if write_mode == 'rw':
            os.lseek(fh, offset, os.SEEK_SET)
            return os.write(fh, buf)
        elif write_mode == 'ro':
            return
        # print('[write]', path)

    def truncate(self, path, length, fh=None):
        full_path = self._full_path(path)
        with open(full_path, 'r+') as f:
            f.truncate(length)

    def flush(self, path, fh):
        return os.fsync(fh)

    def release(self, path, fh):
        # print('[release]', path)
        return os.close(fh)

    def fsync(self, path, fdatasync, fh):
        return self.flush(path, fh)


def main(mountpoint, root):
    glass = GlassFS(root)

    signal.signal(signal.SIGUSR1, glass.cycle_roots)
    signal.signal(signal.SIGUSR2, glass.uncycle_roots)

    FUSE(glass, mountpoint, nothreads=True, foreground=True, **{'allow_other': True})


if __name__ == '__main__':
    main(sys.argv[2], sys.argv[1])
#!/usr/bin/env python # Usage example: # python glass_fs.py /your/dir /mnt/point # See also: # https://www.thepythoncorner.com/2017/02/writing-a-fuse-filesystem-in-python/?doing_wp_cron=1562559777.6591670513153076171875 # https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp # https://stackoverflow.com/questions/8577137/how-can-i-create-a-tmp-file-in-python # import tempfile # Global vars # Init # We batch remove stuff from the run dir, so make sure it's semi-safe. # Remove all meta files on start # 744 # self.glass_roots.reverse() # Helpers # ======= # Check if the user has access to this root: # short_parent = os.path.basename(os.path.normpath(path.replace('/' + partial, ''))) # print('path:', path) Moves the first glass root to the end of the list. Moves the bottom glass root to the top of the list. # Filesystem methods # ================== # def ioctl(self, path, cmd, arg, fip, flags, data): #     print('ioctl not yet implemented - please assist if you know how.') # print('[access]', path) # print('[readdir path]', path) # print('[readdir full_path]', full_path) # Remove dups as a result of mergers. # Path name is absolute, sanitize it. # File methods # ============ # print('[open]', path) # print('[create]', path) # print('[read]', path) # rw, ro, fake - TODO: implement me. # print('[write]', path) # print('[release]', path)