Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

adding basic FUSE handlers (does not read the file)

  • Loading branch information...
commit 36b0518d2243d2f50d4e229979a62339cd1372db 1 parent c2332f5
Terence Honles authored April 23, 2012

Showing 1 changed file with 201 additions and 8 deletions. Show diff stats Hide diff stats

  1. 209  fs.py
209  fs.py
... ...
@@ -1,7 +1,16 @@
1 1
 #!/usr/bin/env python
2 2
 # vim: set fileencoding=utf-8 :
3 3
 
  4
+import errno
  5
+import fuse
  6
+import os
  7
+import stat
  8
+import time
  9
+
4 10
 from binascii import b2a_hex
  11
+from collections import namedtuple
  12
+from fuse import FUSE, FuseOSError, LoggingMixIn, Operations
  13
+from glob import iglob
5 14
 from io import BytesIO
6 15
 from os import path
7 16
 from struct import Struct
@@ -57,12 +66,197 @@ def find(self, offset):
57 66
             return self.right.find(offset)
58 67
 
59 68
 
60  
-def _read_jump_file(file):
61  
-    while True:
62  
-        item = file.read(JUMP_ITEM.size)
63  
-        if not item: break
  69
+def _unpack_stream(stream, struct):
  70
+    return (struct.unpack(item)
  71
+            for item in iter(lambda: stream.read(struct.size), b''))
  72
+
  73
+ExplodedInfo = namedtuple('ExplodedInfo',
  74
+                          'filesize directory_offset jump_tree')
  75
+
  76
+class ExplodedZip(Operations):
  77
+    def __init__(self):
  78
+        self._load_time = time.time()
  79
+        self.__exploded_info = {}
  80
+
  81
+    def _exploded_info(self, path):
  82
+        if path in self.__exploded_info: return self.__exploded_info[path]
  83
+
  84
+        jump_name = os.path.join('meta', os.path.basename(path) + '.jump')
  85
+        with open(jump_name, 'rb') as jump:
  86
+            filesize, dir_offset = JUMP_ITEM.unpack(jump.read(JUMP_ITEM.size))
  87
+            tree = SeekTree.load(_unpack_stream(jump, JUMP_ITEM))
  88
+
  89
+            info = self.__exploded_info[path] = ExplodedInfo(filesize,
  90
+                                                             dir_offset, tree)
  91
+
  92
+            return info
  93
+
  94
+    @staticmethod
  95
+    def _metafiles(path):
  96
+        meta = os.path.join('meta', os.path.basename(path))
  97
+        return [meta + suffix for suffix in ('.dir', '.stream', '.jump')]
  98
+
  99
+    @staticmethod
  100
+    def _not_supported(*args, **kargs):
  101
+        raise FuseOSError(fuse.ENOTSUP)
  102
+
  103
+    def access(self, path, amode):
  104
+        # this is a read only file system
  105
+        if amode & os.W_OK: return -errno.EACCES
  106
+        if path == '/': return 0
  107
+
  108
+        # as long as the user is able to access all of the meta files it's ok
  109
+        if all(os.access(f, amode) for f in self._metafiles(path)):
  110
+            return 0
  111
+        else:
  112
+            return -errno.EACCES
  113
+
  114
+    def chmod(self, path, mode):
  115
+        if path == '/': return -errno.EACCES
  116
+
  117
+        file_info = [(f, os.stat(f).st_mode) for f in self._metafiles(path)]
  118
+
  119
+        try:
  120
+            for filename, current_mode in file_info:
  121
+                if current_mode != mode:
  122
+                    os.chmod(filename, mode)
  123
+        except:
  124
+            try:
  125
+                for filename, previous_mode in file_info:
  126
+                    if os.stat(filename).st_mode != previous_mode:
  127
+                        os.chmod(filename, previous_mode)
  128
+            except:
  129
+                # there's not really anything we can do at this point
  130
+                pass
  131
+
  132
+            return -errno.EACCES
  133
+        return 0
  134
+
  135
+    def chown(self, path, gid, uid):
  136
+        if path == '/': return -errno.EACCES
  137
+
  138
+        file_info = [(f, os.stat(f)) for f in self._metafiles(path)]
  139
+
  140
+        try:
  141
+            for filename, stat in file_info:
  142
+                if gid != stat.st_gid or uid != stat.st_uid:
  143
+                    os.chown(filename, gid, uid)
  144
+        except:
  145
+            try:
  146
+                for filename, previous in file_info:
  147
+                    current = os.stat(filename)
  148
+
  149
+                    if (current.st_gid != previous.st_gid or
  150
+                        current_mode.st_uid != previous.st_uid):
  151
+
  152
+                        os.chown(filename, previous.st_gid, previous.st_uid)
  153
+
  154
+            except:
  155
+                # there's not really anything we can do at this point
  156
+                pass
  157
+
  158
+            return -errno.EACCES
  159
+        return 0
  160
+
  161
+    create = _not_supported
  162
+
  163
+    def destroy(self, path):
  164
+        self.__exploded_info = {}
  165
+
  166
+    def getattr(self, path, fh=None):
  167
+        if path == '/':
  168
+            uid, gid, pid = fuse.fuse_get_context()
  169
+
  170
+            return {
  171
+                'st_uid': uid,
  172
+                'st_gid': gid,
  173
+                'st_mode': stat.S_IFDIR | 0555,
  174
+                'st_nlink': 2,
  175
+
  176
+                'st_atime': self._load_time,
  177
+                'st_mtime': self._load_time,
  178
+                'st_ctime': self._load_time,
  179
+            }
  180
+        else:
  181
+            stats = [os.stat(f) for f in self._metafiles(path)]
  182
+
  183
+            # bitwise OR of all the modes
  184
+            mode = reduce(lambda a, b: a | (b.st_mode & 0777), stats, 0)
  185
+
  186
+            return {
  187
+                'st_uid': stats[0].st_uid,
  188
+                'st_gid': stats[0].st_gid,
  189
+                'st_mode': stat.S_IFREG |  mode,
  190
+                'st_size': self._exploded_info(path).filesize,
  191
+                'st_nlink': min(i.st_nlink for i in stats),
  192
+
  193
+                'st_atime': max(i.st_atime for i in stats),
  194
+                'st_mtime': max(i.st_mtime for i in stats),
  195
+                'st_ctime': max(i.st_ctime for i in stats),
  196
+            }
  197
+
  198
+
  199
+    def link(self, target, source):
  200
+        for t, s in zip(self._metafiles(target), self._metafiles(source)):
  201
+            if not path.isfile(t) or not path.samefile(s, t):
  202
+                os.link(s, t)
  203
+
  204
+    listxattr = _not_supported
  205
+    mkdir = _not_supported
  206
+    mknod = _not_supported
  207
+
  208
+    # TODO: open
  209
+    # TODO: read
  210
+    def readdir(self, path, fh):
  211
+        if path != '/':
  212
+            raise FuseOSError(errno.ENOTDIR)
  213
+
  214
+        yield '.'
  215
+        yield '..'
  216
+        for entry in iglob('meta/*.dir'):
  217
+            yield os.path.basename(entry[:-4])
  218
+
  219
+    def readlink(self, path):
  220
+        for meta in self._metafiles(path):
  221
+            link = os.readlink(meta)
  222
+
  223
+            try:
  224
+                name, ext = link.rsplit('.', 1)
  225
+
  226
+                if meta.rsplit('.', 1)[1] == ext:
  227
+                    return name
  228
+            except ValueError:
  229
+                continue
  230
+
  231
+        return -errno.EINVAL
  232
+
  233
+    # TODO: release (close?)
  234
+
  235
+    removexattr = _not_supported
  236
+    rename = _not_supported
  237
+    rmdir = _not_supported
  238
+
  239
+    def statfs(self, path):
  240
+        # TODO: report better information
  241
+        stat = os.statvfs('meta')
  242
+        return dict((key, getattr(stat, key)) for key in
  243
+                    ('f_bavail', 'f_bfree', 'f_blocks', 'f_bsize'))
  244
+
  245
+    def symlink(self, target, source):
  246
+        for t, s in zip(self._metafiles(target), self._metafiles(source)):
  247
+            if not path.islink(t) or os.readlink(t) != s:
  248
+                os.symlink(s, t)
  249
+
  250
+    truncate = _not_supported
  251
+    unlink = _not_supported
  252
+
  253
+    def utimens(self, path, time=None):
  254
+        for filename in self._metafiles(path):
  255
+            os.utime(filename, time)
  256
+
  257
+    write = _not_supported
  258
+
64 259
 
65  
-        yield JUMP_ITEM.unpack(item)
66 260
 
67 261
 def _read_stream(stream, offset=0, count=-1):
68 262
     if offset < 0:
@@ -137,7 +331,6 @@ def _read_stream(stream, offset=0, count=-1):
137 331
 
138 332
 def read(filename, offset=0, count=-1, begining=True):
139 333
     meta = path.join('meta', filename)
140  
-    data = path.join('data', filename)
141 334
 
142 335
     with open(meta + '.jump', 'rb') as jump:
143 336
         with open(meta + '.dir', 'rb') as dir:
@@ -145,7 +338,7 @@ def read(filename, offset=0, count=-1, begining=True):
145 338
                 header = JUMP_ITEM.unpack(jump.read(JUMP_ITEM.size))
146 339
                 filesize, directory_offset = header
147 340
 
148  
-                tree = SeekTree.load(_read_jump_file(jump))
  341
+                tree = SeekTree.load(_unpack_stream(jump, JUMP_ITEM))
149 342
 
150 343
                 if not begining:
151 344
                     offset += filesize
@@ -178,4 +371,4 @@ def read(filename, offset=0, count=-1, begining=True):
178 371
 if __name__ == '__main__':
179 372
     import sys
180 373
 
181  
-    sys.stdout.write(read(sys.argv[1]))
  374
+    fuse = FUSE(ExplodedZip(), sys.argv[1], foreground=True, ro=True)

0 notes on commit 36b0518

Please sign in to comment.
Something went wrong with that request. Please try again.