Merge branch 'master' into 2221-complete-docker
[arvados.git] / sdk / python / bin / arv-mount
index 667f36e81516b3b24abd50b80ad5b53140a6549d..ac9cd9bcf6088cbc54751ec340d143b66c423154 100755 (executable)
 #!/usr/bin/env python
 
-import argparse
-import hashlib
-import os
-import re
-import string
-import sys
-import logging
-import fuse
-import errno
-import stat
+from arvados.fuse import * 
 import arvados
-import time
-
-class KeepMount(fuse.LoggingMixIn, fuse.Operations):
-    'Read-only Keep mount.'
-
-    def __init__(self):
-        self.arv = arvados.api('v1')
-        self.reader = None
-        self.collections = {}
-        self.audited = dict(read={})
+import subprocess
+import argparse
 
-    def load_collection(self, uuid):
-        if uuid in self.collections:
-            return
-        now = time.time()
-        reader = arvados.CollectionReader(uuid)
-        files = {}
-        files[''] = dict(
-            stat=dict(
-                st_mode=(stat.S_IFDIR | 0755), st_ctime=now,
-                st_mtime=now, st_atime=now, st_nlink=2))
-        try:
-            for s in reader.all_streams():
-                for f in s.all_files():
-                    path = re.sub(r'^\./', '', os.path.join(s.name(), f.name()))
-                    files[path] = dict(
-                        stat=dict(
-                            st_mode=(stat.S_IFREG | 0444),
-                            st_size=f.size(), st_nlink=1,
-                            st_ctime=now, st_mtime=now, st_atime=now),
-                        arv_file=f)
-                    logger.debug("collection.load: %s: %s" % (uuid, path))
-        except:
-            # TODO: propagate real error, don't assume ENOENT
-            raise fuse.FuseOSError(errno.ENOENT)
-        self.collections[uuid] = dict(reader=reader, files=files)
-        logger.info("collection.load %s" % uuid)
+if __name__ == '__main__':
+    # Handle command line parameters
+    parser = argparse.ArgumentParser(
+        description='Mount Keep data under the local filesystem.',
+        epilog="""
+Note: When using the --exec feature, you must either specify the
+mountpoint before --exec, or mark the end of your --exec arguments
+with "--".
+""")
+    parser.add_argument('mountpoint', type=str, help="""Mount point.""")
+    parser.add_argument('--collection', type=str, help="""Collection locator""")
+    parser.add_argument('--debug', action='store_true', help="""Debug mode""")
+    parser.add_argument('--exec', type=str, nargs=argparse.REMAINDER,
+                        dest="exec_args", metavar=('command', 'args', '...', '--'),
+                        help="""Mount, run a command, then unmount and exit""")
 
-    def setup_reader(self, path):
-        logger.debug("%s", path.split('/'))
-        return True
+    args = parser.parse_args()
 
-    def set_args(self, args):
-        self.args = args
+    # Create the request handler
+    operations = Operations(os.getuid(), os.getgid())
 
-    def parse_and_load(self, path):
-        parts = path.split(os.path.sep, 2)
-        while len(parts) < 3:
-            parts += ['']
-        if not re.match(r'[0-9a-f]{32,}(\+\S+?)*', parts[1]):
-            raise fuse.FuseOSError(errno.ENOENT)
-        if self.args.collection != []:
-            if parts[1] not in self.args.collection:
-                raise fuse.FuseOSError(errno.EPERM)
-        self.load_collection(parts[1])
-        return parts[0:3]
+    if args.collection != None:
+        # Set up the request handler with the collection at the root
+        e = operations.inodes.add_entry(Directory(llfuse.ROOT_INODE))
+        operations.inodes.load_collection(e, arvados.CollectionReader(arvados.Keep.get(args.collection)))
+    else:
+        # Set up the request handler with the 'magic directory' at the root
+        operations.inodes.add_entry(MagicDirectory(llfuse.ROOT_INODE, operations.inodes))
 
-    def audit_read(self, uuid):
-        if self.args.audit and uuid not in self.audited['read']:
-            self.audited['read'][uuid] = True
-            logger.info("collection.read %s" % uuid)
+    # FUSE options, see mount.fuse(8)
+    opts = []
 
-    def read(self, path, size, offset, fh):
-        _, uuid, target = self.parse_and_load(path)
-        if (uuid not in self.collections or
-            target not in self.collections[uuid]['files']):
-            raise fuse.FuseOSError(errno.ENOENT)
-        self.audit_read(uuid)
-        f = self.collections[uuid]['files'][target]['arv_file']
-        f.seek(offset)
-        return f.read(size)
+    # Enable FUSE debugging (logs each FUSE request)
+    if args.debug:
+        opts += ['debug']    
+    
+    # Initialize the fuse connection
+    llfuse.init(operations, args.mountpoint, opts)
 
-    def readdir(self, path, fh):
-        if path == '/':
-            raise fuse.FuseOSError(errno.EPERM)
-        _, uuid, target = self.parse_and_load(path)
-        if uuid not in self.collections:
-            raise fuse.FuseOSError(errno.ENOENT)
-        if target != '' and target[-1] != os.path.sep:
-            target += os.path.sep
-        dirs = {}
-        for filepath in self.collections[uuid]['files']:
-            if filepath != '':
-                logger.debug(filepath)
-                if target == '' or 0 == string.find(filepath, target):
-                    dirs[filepath[len(target):].split(os.path.sep)[0]] = True
-        return ['.', '..'] + dirs.keys()
+    if args.exec_args:
+        t = threading.Thread(None, lambda: llfuse.main())
+        t.start()
 
-    def getattr(self, path, fh=None):
-        if path == '/':
-            now = time.time()
-            return dict(st_mode=(stat.S_IFDIR | 0111), st_ctime=now,
-                        st_mtime=now, st_atime=now, st_nlink=2)
-        _, uuid, target = self.parse_and_load(path)
-        if uuid not in self.collections:
-            raise fuse.FuseOSError(errno.ENOENT)
-        if target in self.collections[uuid]['files']:
-            return self.collections[uuid]['files'][target]['stat']
-        for filepath in self.collections[uuid]['files']:
-            if filepath != '':
-                if target == '' or 0 == string.find(filepath, target + '/'):
-                    return self.collections[uuid]['files']['']['stat']
-        raise fuse.FuseOSError(errno.ENOENT)
+        # wait until the driver is finished initializing
+        operations.initlock.wait()
 
-def parse_args():
-    parser = argparse.ArgumentParser(
-        description='Mount Keep data under the local filesystem.')
-    parser.add_argument('mountpoint', type=str,
-                        help="""
-Mount point.
-""")
-    parser.add_argument('--collection', type=str, action='append', default=[],
-                        help="""
-Collection locator. If none supplied, provide access to all readable
-manifests.
-""")
-    parser.add_argument('--audit', action='store_true',
-                        help="""
-Print the collection uuid on stderr the first time a given collection
-is read.
-""")
-    parser.add_argument('--debug', action='store_true',
-                        help="""
-Print debug messages.
-""")
-    parser.add_argument('--foreground', action='store_true',
-                        help="""
-Run in foreground, instead of detaching and running as a daemon.
-""")
-    args = parser.parse_args()
-    return args
+        try:
+            rc = subprocess.call(args.exec_args, shell=False)
+        except:
+            rc = 255
+        finally:
+            subprocess.call(["fusermount", "-u", "-z", args.mountpoint])
 
-if __name__ == '__main__':
-    args = parse_args()
-    logger = logging.getLogger(os.path.basename(sys.argv[0]))
-    if args.audit:
-        logging.basicConfig(level=logging.INFO)
-    if args.debug:
-        logging.basicConfig(level=logging.DEBUG)
-    mounter = KeepMount()
-    mounter.set_args(args)
-    fuse = fuse.FUSE(mounter,
-                     args.mountpoint,
-                     foreground=args.foreground,
-                     fsname='arv-mount')
+        exit(rc)
+    else:
+        llfuse.main()