~mgiuca/fuse-python-docs/trunk

1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
1
#!/usr/bin/env python
47 by Matt Giuca
Applied MIT License text to the top of all files. (Had some questions about licensing.)
2
3
# -------------------------------------------------------------------
4
# Copyright (c) 2009 Matt Giuca
5
# This software and its accompanying documentation is licensed under the
6
# MIT License.
7
#
8
# Permission is hereby granted, free of charge, to any person
9
# obtaining a copy of this software and associated documentation
10
# files (the "Software"), to deal in the Software without
11
# restriction, including without limitation the rights to use,
12
# copy, modify, merge, publish, distribute, sublicense, and/or sell
13
# copies of the Software, and to permit persons to whom the
14
# Software is furnished to do so, subject to the following
15
# conditions:
16
#
17
# The above copyright notice and this permission notice shall be
18
# included in all copies or substantial portions of the Software.
19
#
20
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27
# OTHER DEALINGS IN THE SOFTWARE.
28
# -------------------------------------------------------------------
29
30 by Matt Giuca
Renamed MattFS to TemplateFS. This will now serve as a template for
30
# TemplateFS - A Simple Python Fuse Example
31
# This file system does absolutely nothing, but it's a complete stubbed-out
32
# file system ready for whatever implementation is desired.
2 by Matt Giuca
mattfs: More comments, and fsinit.
33
#
34
# Fuse lets you easily create your own filesystems which run entirely in user
35
# mode and with normal user permissions.
36
# This example presents a simple file system.
37
# You mount it by directly running this file.
38
#
39
# Usage:
30 by Matt Giuca
Renamed MattFS to TemplateFS. This will now serve as a template for
40
#     templatefs.py MOUNTPOINT
45 by Adrian Panasiuk
* describe command line parsing
41
#     templatefs.py MOUNTPOINT -o xyz=VALUE
42
#     templatefs.py arg1 arg2 .. MOUNTPOINT argn arg(n+1) ...
2 by Matt Giuca
mattfs: More comments, and fsinit.
43
# To unmount:
44
#     fusermount -u MOUNTPOINT
45
#
46
# Use `tail -f LOG` to read the log in real-time (as it is written).
30 by Matt Giuca
Renamed MattFS to TemplateFS. This will now serve as a template for
47
# Also, mount with `./templatefs.py MOUNTPOINT -d` to have FUSE print out its own
2 by Matt Giuca
mattfs: More comments, and fsinit.
48
# debugging messages (which are in some cases more, and some cases less useful
49
# than mine).
19 by Matt Giuca
mattfs: Added a list of as-yet-unstubbed methods at the top.
50
#
31 by Matt Giuca
templatefs: Cleaned up / reorganised comments at the top.
51
# Issues with this template
52
# =========================
19 by Matt Giuca
mattfs: Added a list of as-yet-unstubbed methods at the top.
53
# I have tried to stub out all methods which Fuse will call (including
54
# undocumented ones, some of which are very important).
55
# I have not yet done the following:
56
# - getxattr
57
# - listxattr
58
# - setxattr
59
# - removexattr
60
# - lock
61
# - bmap
45 by Adrian Panasiuk
* describe command line parsing
62
#
63
# Note that some of the data in this document come from experimentation.
64
# There is no guarantee that later versions and implementations of FUSE
65
# will behave the same way as the tested version.
66
#
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
67
31 by Matt Giuca
templatefs: Cleaned up / reorganised comments at the top.
68
# Notes on implementing this template
69
# ===================================
70
# The comments in this file are intended to be instructional, and may end up
71
# becoming proper documentation.
72
#
73
# All methods log their inputs to a file called "LOG" by default (in the
74
# current directory), so you can experiment with the filesystem and
75
# note exactly how all the methods get called.
76
#
77
# Most methods just return -errno.EOPNOTSUPP (Operation not supported). If you
78
# plan to implement a method, you could change the stub to -errno.ENOSYS
79
# (Function not implemented). If your system actually doesn't support an
80
# operation, leave it as -errno.EOPNOTSUPP.
81
82
# Notes on working out all the methods to implement
83
# =================================================
12 by Matt Giuca
mattfs: Notes on working out all the methods to implement.
84
# Python Fuse is fairly poorly documented. The documentation is here:
85
# http://apps.sourceforge.net/mediawiki/fuse/index.php?
86
#   title=FUSE_Python_Reference
87
# But it's very scarce in some cases.
88
#
89
# A better bet is to look at the C reference, but it's very scarce too, and
90
# you have to figure out how it applies to Python (not always obvious):
91
# http://fuse.sourceforge.net/doxygen/structfuse__operations.html
92
#
93
# So finally, I turned to the source code. This is also very difficult to
94
# understand because the functions aren't explicitly defined anywhere. The
95
# only places they are actually defined is in the C wrapping code, which has a
96
# handler for each Fuse C function, and calls a Python function object. From
97
# here, you can tell what it's marshalling between C and Python.
98
# This is found in /fuseparts/_fusemodule.c in the source tree.
99
# Most functions just copy over their arguments into Python, but some are
100
# tricky (such as open).
101
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
102
import fuse
103
8 by Matt Giuca
mattfs: By default, files are now owned by the user who mounted the drive, NOT
104
import os
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
105
import stat
106
import errno
107
import datetime
108
import time
109
import calendar
110
import logging
111
112
LOG_FILENAME = "LOG"
5 by Matt Giuca
mattfs: getattr now prints as a debug-level message, not info, since it shows
113
logging.basicConfig(filename=LOG_FILENAME,level=logging.INFO,)
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
114
115
# FUSE version at the time of writing. Be compatible with this version.
116
fuse.fuse_python_api = (0, 2)
117
118
# First, we define a class deriving from fuse.Stat. This object is used to
119
# describe a file in our virtual file system.
2 by Matt Giuca
mattfs: More comments, and fsinit.
120
# This class is rather large, but the concept is really simple (there's just a
121
# lot of code here to make construction really easy).
122
# All you have to do is present an object with the following fields, all ints:
123
#   st_mode:
124
#       Should be stat.S_IFREG or S_IFDIR OR'd with a normal Unix permission
125
#       flag, such as 644.
126
#   st_ino, st_dev:
127
#       0. Ignored, but required.
128
#   st_nlink:
129
#       Number of hard links to this file. For files, usually 1. For
130
#       directories, usually 2 + number of immediate subdirs (one for parent,
131
#       one for self, one for each child).
132
#   st_uid, st_gid:
133
#       uid/gid of file owner.
134
#   st_size:
135
#       File size in bytes.
136
#   st_atime, st_mtime, st_ctime:
137
#       File access times, in seconds since the epoch, UTC time. Last access
138
#       time, modification time, stat change time, respectively.
3 by Matt Giuca
mattfs: Re-commented the methods using docstrings, in a way describing what
139
class Stat(fuse.Stat):
140
    """
141
    A Stat object. Describes the attributes of a file or directory.
34 by Matt Giuca
templatefs: Changed Stat so it presents a 'dt_*time' attribute (as a
142
    Has all the st_* attributes, as well as dt_atime, dt_mtime and dt_ctime,
143
    which are datetime.datetime versions of st_*time. The st_*time versions
144
    are in epoch time.
3 by Matt Giuca
mattfs: Re-commented the methods using docstrings, in a way describing what
145
    """
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
146
    # Filesize of directories, in bytes.
147
    DIRSIZE = 4096
148
149
    # We can define __init__ however we like, because it's only called by us.
150
    # But it has to have certain fields.
8 by Matt Giuca
mattfs: By default, files are now owned by the user who mounted the drive, NOT
151
    def __init__(self, st_mode, st_size, st_nlink=1, st_uid=None, st_gid=None,
34 by Matt Giuca
templatefs: Changed Stat so it presents a 'dt_*time' attribute (as a
152
            dt_atime=None, dt_mtime=None, dt_ctime=None):
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
153
        """
154
        Creates a Stat object.
155
        st_mode: Required. Should be stat.S_IFREG or stat.S_IFDIR ORed with a
2 by Matt Giuca
mattfs: More comments, and fsinit.
156
            regular Unix permission value like 0644.
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
157
        st_size: Required. Size of file in bytes. For a directory, should be
3 by Matt Giuca
mattfs: Re-commented the methods using docstrings, in a way describing what
158
            Stat.DIRSIZE.
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
159
        st_nlink: Number of hard-links to the file. Regular files should
160
            usually be 1 (default). Directories should usually be 2 + number
161
            of immediate subdirs (one from the parent, one from self, one from
162
            each child).
8 by Matt Giuca
mattfs: By default, files are now owned by the user who mounted the drive, NOT
163
        st_uid, st_gid: uid/gid of file owner. Defaults to the user who
164
            mounted the file system.
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
165
        st_atime, st_mtime, st_ctime: atime/mtime/ctime of file.
166
            (Access time, modification time, stat change time).
167
            These must be datetime.datetime objects, in UTC time.
168
            All three values default to the current time.
169
        """
170
        self.st_mode = st_mode
171
        self.st_ino = 0         # Ignored, but required
172
        self.st_dev = 0         # Ignored, but required
173
        # Note: Wiki says st_blksize is required (like st_dev, ignored but
174
        # required). However, this breaks things and another tutorial I found
175
        # did not have this field.
176
        self.st_nlink = st_nlink
177
        if st_uid is None:
8 by Matt Giuca
mattfs: By default, files are now owned by the user who mounted the drive, NOT
178
            st_uid = os.getuid()
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
179
        self.st_uid = st_uid
180
        if st_gid is None:
8 by Matt Giuca
mattfs: By default, files are now owned by the user who mounted the drive, NOT
181
            st_gid = os.getgid()
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
182
        self.st_gid = st_gid
183
        self.st_size = st_size
34 by Matt Giuca
templatefs: Changed Stat so it presents a 'dt_*time' attribute (as a
184
        now = datetime.datetime.utcnow()
185
        self.dt_atime = dt_atime or now
186
        self.dt_mtime = dt_mtime or now
187
        self.dt_ctime = dt_ctime or now
188
189
    def _get_dt_atime(self):
190
        return self.epoch_datetime(self.st_atime)
191
    def _set_dt_atime(self, value):
192
        self.st_atime = self.datetime_epoch(value)
193
    dt_atime = property(_get_dt_atime, _set_dt_atime)
194
195
    def _get_dt_mtime(self):
196
        return self.epoch_datetime(self.st_mtime)
197
    def _set_dt_mtime(self, value):
198
        self.st_mtime = self.datetime_epoch(value)
199
    dt_mtime = property(_get_dt_mtime, _set_dt_mtime)
200
201
    def _get_dt_ctime(self):
202
        return self.epoch_datetime(self.st_ctime)
203
    def _set_dt_ctime(self, value):
204
        self.st_ctime = self.datetime_epoch(value)
205
    dt_ctime = property(_get_dt_ctime, _set_dt_ctime)
3 by Matt Giuca
mattfs: Re-commented the methods using docstrings, in a way describing what
206
207
    @staticmethod
208
    def datetime_epoch(dt):
209
        """
210
        Converts a datetime.datetime object which is in UTC time
211
        (as returned by datetime.datetime.utcnow()) into an int, which represents
212
        the number of seconds since the system epoch (also in UTC time).
213
        """
214
        # datetime.datetime.timetuple converts a datetime into a time.struct_time.
215
        # calendar.timegm converts a time.struct_time into epoch time, without
216
        # modifying for time zone (so UTC time stays in UTC time, unlike
217
        # time.mktime).
218
        return calendar.timegm(dt.timetuple())
34 by Matt Giuca
templatefs: Changed Stat so it presents a 'dt_*time' attribute (as a
219
    @staticmethod
220
    def epoch_datetime(seconds):
221
        """
222
        Converts an int, the number of seconds since the system epoch in UTC
223
        time, into a datetime.datetime object, also in UTC time.
224
        """
225
        return datetime.datetime.utcfromtimestamp(seconds)
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
226
227
# Almost all that is required is the definition of a class deriving from
228
# fuse.Fuse, and implementation of a bunch of methods.
30 by Matt Giuca
Renamed MattFS to TemplateFS. This will now serve as a template for
229
class TemplateFS(fuse.Fuse):
3 by Matt Giuca
mattfs: Re-commented the methods using docstrings, in a way describing what
230
    """
231
    A Fuse filesystem object. Implements methods which are called by the Fuse
232
    system as a result of the operating system requesting filesystem
233
    operations on places where this file system is mounted.
4 by Matt Giuca
mattfs: Better commenting.
234
235
    Unless otherwise documented, all of these methods return an int.
236
    This should be 0 on success, or the NEGATIVE of an errno value on failure.
237
    For example, to report "no such file or directory", methods return
238
    -errno.ENOENT. See the errno manpage for a list of errno values. (Though
239
    note that Python's errno is slightly different; see help(errno)).
240
    Methods should return errno.EOPNOTSUPP (operation not supported) if they
241
    are deliberately not supported, or errno.ENOSYS (function not implemented)
242
    if they have not yet been implemented.
243
244
    Unless otherwise documented, all paths should begin with a '/' and be
245
    "absolute paths", where "absolute" means relative to the root of the
246
    mounted filesystem. There are no references to files outside the
247
    filesystem.
3 by Matt Giuca
mattfs: Re-commented the methods using docstrings, in a way describing what
248
    """
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
249
    def __init__(self, *args, **kwargs):
3 by Matt Giuca
mattfs: Re-commented the methods using docstrings, in a way describing what
250
        """
45 by Adrian Panasiuk
* describe command line parsing
251
        Creates a new TemplateFS object. Needs to call fuse.Fuse.__init__ with
252
        the args (just forward them along). Note that options passed to the
253
        filesystem through the command line are not available during the
254
        execution of this method.
255
256
        If parsing the command line argument fails, fsdestroy is called
257
        without prior calling fsinit.
3 by Matt Giuca
mattfs: Re-commented the methods using docstrings, in a way describing what
258
        """
45 by Adrian Panasiuk
* describe command line parsing
259
        logging.info("Preparing to mount file system")
28 by Matt Giuca
mattfs: Added File class, and extensive documentation. But it's disabled by
260
        #self.file_class = File
30 by Matt Giuca
Renamed MattFS to TemplateFS. This will now serve as a template for
261
        super(TemplateFS, self).__init__(*args, **kwargs)
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
262
45 by Adrian Panasiuk
* describe command line parsing
263
        """
264
        After calling the superconstructor, we may optionally register
265
        options to be parsed from the command line arguments. To pass an
266
        option, use
267
            templatefs.py MOUNTPOINT -o xyz=VALUE -o pqr=
268
        which sets xyz to "VALUE" and pqr to "" (empty string).
269
270
        Not offering the equals sign is (eg. templatefs.py MOUNTPOINT -o
271
        enable_feature) is equivallent to not passing the option at all.
272
273
        If the same option is passed multiple times, the last one takes
274
        takes priority:
275
            templatefs.py -o xyz=a,xyz=b MOUNTPOINT -o xyz=c -o xyz=d,def=0,xyz=e
276
277
        Note that the parser will error out when a not registered option
278
        is passed on the command line.
279
280
281
        Apart from options, we can also get non-option arguments from the
282
        command-line:
283
            templatefs.py arg1 arg2 arg3 -o xyz=pqr,abc=def MOUNTPOINT
284
        These may be used, for example, to specify the device to be mounted.
285
        The mountpoint is the last non-option argument.
286
287
288
        You may further customize command line argument parsing setting
289
        the parser_class argument in __init__.
290
        """
46 by Matt Giuca
templatefs.py: Wrapped too-long line.
291
        self.parser.add_option(mountopt="xyz",
292
                   help="description which shows up with templatefs.py -h")
45 by Adrian Panasiuk
* describe command line parsing
293
4 by Matt Giuca
mattfs: Better commenting.
294
    def fsinit(self):
3 by Matt Giuca
mattfs: Re-commented the methods using docstrings, in a way describing what
295
        """
45 by Adrian Panasiuk
* describe command line parsing
296
        Will be called after the command line arguments are successfully
297
        parsed. It doesn't have to exist or do anything, but as options to the
298
        filesystem are not available in __init__, fsinit is more suitable for
299
        the mounting logic than __init__.
300
301
        To access the command line passed options and nonoption arguments, use
302
        cmdline.
303
304
        The mountpoint is not stored in cmdline.
3 by Matt Giuca
mattfs: Re-commented the methods using docstrings, in a way describing what
305
        """
45 by Adrian Panasiuk
* describe command line parsing
306
        logging.info("Nonoption arguments: " + str(self.cmdline[1]))
307
308
309
        self.xyz = self.cmdline[0].xyz
310
        if self.xyz != None:
311
            logging.info("xyz set to '" + self.xyz + "'")
312
        else:
313
            logging.info("xyz not set")
314
315
        logging.info("Filesystem mounted")
2 by Matt Giuca
mattfs: More comments, and fsinit.
316
18 by Matt Giuca
mattfs: Added fsdestroy.
317
    def fsdestroy(self):
318
        """
319
        Will be called when the file system is about to be unmounted.
320
        It doesn't have to exist, or do anything.
321
        """
322
        logging.info("Unmounting file system")
323
20 by Matt Giuca
mattfs.py: Added statfs.
324
    def statfs(self):
325
        """
326
        Retrieves information about the mounted filesystem.
327
        Returns a fuse.StatVfs object containing the details.
328
        This is optional. If omitted, Fuse will simply report a bunch of 0s.
329
330
        The StatVfs should have the same fields as described in man 2 statfs
331
        (Linux Programmer's Manual), except for f_type.
332
        This includes the following:
333
            f_bsize     (optimal transfer block size)
334
            f_blocks    (number of blocks total)
335
            f_bfree     (number of free blocks)
336
            f_bavail    (number of free blocks available to non-root)
337
            f_files     (number of file nodes in system)
338
            f_ffree     (number of free file nodes)
339
            f_namemax   (max length of filenames)
340
341
        Note f_type, f_frsize, f_favail, f_fsid and f_flag are ignored.
342
        """
343
        logging.info("statfs")
344
        stats = fuse.StatVfs()
345
        # Fill it in here. All fields take on a default value of 0.
346
        return stats
347
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
348
    def getattr(self, path):
3 by Matt Giuca
mattfs: Re-commented the methods using docstrings, in a way describing what
349
        """
350
        Retrieves information about a file (the "stat" of a file).
351
        Returns a fuse.Stat object containing details about the file or
352
        directory.
353
        Returns -errno.ENOENT if the file is not found, or another negative
354
        errno code if another error occurs.
355
        """
5 by Matt Giuca
mattfs: getattr now prints as a debug-level message, not info, since it shows
356
        logging.debug("getattr: %s" % path)
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
357
        if path == "/":
358
            mode = stat.S_IFDIR | 0755
8 by Matt Giuca
mattfs: By default, files are now owned by the user who mounted the drive, NOT
359
            st = Stat(st_mode=mode, st_size=Stat.DIRSIZE, st_nlink=2)
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
360
        # An example of a regular file:
361
        #    mode = stat.S_IFREG | 0644
362
        #    st = Stat(st_mode=mode, st_size=14)
363
        # An example of a symlink (note that size is the size of the link's
364
        # target path string):
365
        #    mode = stat.S_IFLNK | 0777
366
        #    st = Stat(st_mode=mode, st_size=7)
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
367
        else:
368
            return -errno.ENOENT
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
369
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
370
        return st
371
17 by Matt Giuca
mattfs: Added utime and utimens.
372
    # Note: utime is deprecated in favour of utimens.
373
    # utimens takes precedence over utime, so having this here does nothing
374
    # unless you delete utimens.
375
    def utime(self, path, times):
376
        """
377
        Sets the access and modification times on a file.
378
        times: (atime, mtime) pair. Both ints, in seconds since epoch.
379
        Deprecated in favour of utimens.
380
        """
381
        atime, mtime = times
382
        logging.info("utime: %s (atime %s, mtime %s)" % (path, atime, mtime))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
383
        return -errno.EOPNOTSUPP
17 by Matt Giuca
mattfs: Added utime and utimens.
384
385
    def utimens(self, path, atime, mtime):
386
        """
387
        Sets the access and modification times on a file, in nanoseconds.
388
        atime, mtime: Both fuse.TimeSpec objects, with 'tv_sec' and 'tv_nsec'
389
            attributes, which are the seconds and nanoseconds parts,
390
            respectively.
391
        """
392
        logging.info("utime: %s (atime %s:%s, mtime %s:%s)"
393
            % (path,atime.tv_sec,atime.tv_nsec,mtime.tv_sec,mtime.tv_nsec))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
394
        return -errno.EOPNOTSUPP
17 by Matt Giuca
mattfs: Added utime and utimens.
395
10 by Matt Giuca
mattfs: Added access, open and opendir methods.
396
    def access(self, path, flags):
397
        """
398
        Checks permissions for accessing a file or directory.
399
        flags: As described in man 2 access (Linux Programmer's Manual).
400
            Either os.F_OK (test for existence of file), or ORing of
401
            os.R_OK, os.W_OK, os.X_OK (test if file is readable, writable and
402
            executable, respectively. Must pass all tests).
403
        Should return 0 for "allowed", or -errno.EACCES if disallowed.
404
        May not always be called. For example, when opening a file, open may
405
        be called and access avoided.
406
        """
407
        logging.info("access: %s (flags %s)" % (path, oct(flags)))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
408
        if path == "/":
409
            return 0
410
        else:
411
            return -errno.EACCES
10 by Matt Giuca
mattfs: Added access, open and opendir methods.
412
13 by Matt Giuca
mattfs: Added readlink method, and new dummy file "ls" which is a symlink to
413
    def readlink(self, path):
414
        """
415
        Get the target of a symlink.
416
        Returns a bytestring with the contents of a symlink (its target).
417
        May also return an int error code.
418
        """
419
        logging.info("readlink: %s" % path)
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
420
        return -errno.EOPNOTSUPP
13 by Matt Giuca
mattfs: Added readlink method, and new dummy file "ls" which is a symlink to
421
6 by Matt Giuca
mattfs: Added in stubs for all of the other directory operations
422
    def mknod(self, path, mode, rdev):
423
        """
424
        Creates a non-directory file (or a device node).
425
        mode: Unix file mode flags for the file being created.
426
        rdev: Special properties for creation of character or block special
427
            devices (I've never gotten this to work).
428
            Always 0 for regular files or FIFO buffers.
429
        """
430
        # Note: mode & 0770000 gives you the non-permission bits.
431
        # Common ones:
11 by Matt Giuca
mattfs: Implemented "read" function. Can now print out text for some files.
432
        # S_IFREG:  0100000 (A regular file)
6 by Matt Giuca
mattfs: Added in stubs for all of the other directory operations
433
        # S_IFIFO:  010000  (A fifo buffer, created with mkfifo)
434
435
        # Potential ones (I have never seen them):
436
        # Note that these could be made by copying special devices or sockets
437
        # or using mknod, but I've never gotten FUSE to pass such a request
438
        # along.
439
        # S_IFCHR:  020000  (A character special device, created with mknod)
440
        # S_IFBLK:  060000  (A block special device, created with mknod)
441
        # S_IFSOCK: 0140000 (A socket, created with mkfifo)
8 by Matt Giuca
mattfs: By default, files are now owned by the user who mounted the drive, NOT
442
443
        # Also note: You can use self.GetContext() to get a dictionary
444
        #   {'uid': ?, 'gid': ?}, which tells you the uid/gid of the user
445
        #   executing the current syscall. This should be handy when creating
446
        #   new files and directories, because they should be owned by this
447
        #   user/group.
6 by Matt Giuca
mattfs: Added in stubs for all of the other directory operations
448
        logging.info("mknod: %s (mode %s, rdev %s)" % (path, oct(mode), rdev))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
449
        return -errno.EOPNOTSUPP
6 by Matt Giuca
mattfs: Added in stubs for all of the other directory operations
450
451
    def mkdir(self, path, mode):
452
        """
453
        Creates a directory.
454
        mode: Unix file mode flags for the directory being created.
455
        """
456
        # Note: mode & 0770000 gives you the non-permission bits.
457
        # Should be S_IDIR (040000); I guess you can assume this.
8 by Matt Giuca
mattfs: By default, files are now owned by the user who mounted the drive, NOT
458
        # Also see note about self.GetContext() in mknod.
6 by Matt Giuca
mattfs: Added in stubs for all of the other directory operations
459
        logging.info("mkdir: %s (mode %s)" % (path, oct(mode)))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
460
        return -errno.EOPNOTSUPP
6 by Matt Giuca
mattfs: Added in stubs for all of the other directory operations
461
462
    def unlink(self, path):
463
        """Deletes a file."""
464
        logging.info("unlink: %s" % path)
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
465
        return -errno.EOPNOTSUPP
6 by Matt Giuca
mattfs: Added in stubs for all of the other directory operations
466
44 by Matt Giuca
templatefs: Added rmdir (previously forgotten operation).
467
    def rmdir(self, path):
468
        """Deletes a directory."""
469
        logging.info("rmdir: %s" % path)
470
        return -errno.EOPNOTSUPP
471
6 by Matt Giuca
mattfs: Added in stubs for all of the other directory operations
472
    def symlink(self, target, name):
473
        """
474
        Creates a symbolic link from path to target.
475
476
        The 'name' is a regular path like any other method (absolute, but
477
        relative to the filesystem root).
478
        The 'target' is special - it works just like any symlink target. It
479
        may be absolute, in which case it is absolute on the user's system,
480
        NOT the mounted filesystem, or it may be relative. It should be
481
        treated as an opaque string - the filesystem implementation should not
482
        ever need to follow it (that is handled by the OS).
483
484
        Hence, if the operating system creates a link FROM this system TO
485
        another system, it will call this method with a target pointing
486
        outside the filesystem.
487
        If the operating system creates a link FROM some other system TO this
488
        system, it will not touch this system at all (symlinks do not depend
489
        on the target system unless followed).
490
        """
491
        logging.info("symlink: target %s, name: %s" % (target, name))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
492
        return -errno.EOPNOTSUPP
6 by Matt Giuca
mattfs: Added in stubs for all of the other directory operations
493
494
    def link(self, target, name):
495
        """
496
        Creates a hard link from name to target. Note that both paths are
497
        relative to the mounted file system. Hard-links across systems are not
498
        supported.
499
        """
500
        logging.info("link: target %s, name: %s" % (target, name))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
501
        return -errno.EOPNOTSUPP
6 by Matt Giuca
mattfs: Added in stubs for all of the other directory operations
502
503
    def rename(self, old, new):
504
        """
505
        Moves a file from old to new. (old and new are both full paths, and
506
        may not be in the same directory).
507
        
508
        Note that both paths are relative to the mounted file system.
509
        If the operating system needs to move files across systems, it will
510
        manually copy and delete the file, and this method will not be called.
511
        """
512
        logging.info("rename: target %s, name: %s" % (old, new))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
513
        return -errno.EOPNOTSUPP
6 by Matt Giuca
mattfs: Added in stubs for all of the other directory operations
514
15 by Matt Giuca
mattfs: Added chmod and chown (stubs).
515
    def chmod(self, path, mode):
516
        """Changes the mode of a file or directory."""
517
        logging.info("chmod: %s (mode %s)" % (path, oct(mode)))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
518
        return -errno.EOPNOTSUPP
15 by Matt Giuca
mattfs: Added chmod and chown (stubs).
519
520
    def chown(self, path, uid, gid):
521
        """Changes the owner of a file or directory."""
522
        logging.info("chown: %s (uid %s, gid %s)" % (path, uid, gid))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
523
        return -errno.EOPNOTSUPP
15 by Matt Giuca
mattfs: Added chmod and chown (stubs).
524
23 by Matt Giuca
mattfs.py: Added fgetattr and ftruncate methods. I discovered these are
525
    def truncate(self, path, size):
526
        """
527
        Shrink or expand a file to a given size.
528
        If 'size' is smaller than the existing file size, truncate it from the
529
        end.
530
        If 'size' if larger than the existing file size, extend it with null
531
        bytes.
532
        """
533
        logging.info("truncate: %s (size %s)" % (path, size))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
534
        return -errno.EOPNOTSUPP
23 by Matt Giuca
mattfs.py: Added fgetattr and ftruncate methods. I discovered these are
535
26 by Matt Giuca
mattfs: Added section "directory operation methods", commented it, and moved
536
    ### DIRECTORY OPERATION METHODS ###
537
    # Methods in this section are operations for opening directories and
538
    # working on open directories.
539
    # "opendir" is the method for opening directories. It *may* return an
540
    # arbitrary Python object (not None or int), which is used as a dir
541
    # handle by the methods for working on directories.
542
    # All the other methods (readdir, fsyncdir, releasedir) are methods for
543
    # working on directories. They should all be prepared to accept an
544
    # optional dir-handle argument, which is whatever object "opendir"
545
    # returned.
546
547
    def opendir(self, path):
548
        """
549
        Checks permissions for listing a directory.
550
        This should check the 'r' (read) permission on the directory.
27 by Matt Giuca
mattfs: Added dir-handle arguments to all the directory operations, and more
551
552
        On success, *may* return an arbitrary Python object, which will be
553
        used as the "fh" argument to all the directory operation methods on
554
        the directory. Or, may just return None on success.
555
        On failure, should return a negative errno code.
556
        Should return -errno.EACCES if disallowed.
26 by Matt Giuca
mattfs: Added section "directory operation methods", commented it, and moved
557
        """
558
        logging.info("opendir: %s" % path)
559
        if path == "/":
560
            return 0
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
561
        else:
26 by Matt Giuca
mattfs: Added section "directory operation methods", commented it, and moved
562
            return -errno.EACCES
563
27 by Matt Giuca
mattfs: Added dir-handle arguments to all the directory operations, and more
564
    def releasedir(self, path, dh=None):
26 by Matt Giuca
mattfs: Added section "directory operation methods", commented it, and moved
565
        """
566
        Closes an open directory. Allows filesystem to clean up.
567
        """
27 by Matt Giuca
mattfs: Added dir-handle arguments to all the directory operations, and more
568
        logging.info("releasedir: %s (dh %s)" % (path, dh))
26 by Matt Giuca
mattfs: Added section "directory operation methods", commented it, and moved
569
27 by Matt Giuca
mattfs: Added dir-handle arguments to all the directory operations, and more
570
    def fsyncdir(self, path, datasync, dh=None):
26 by Matt Giuca
mattfs: Added section "directory operation methods", commented it, and moved
571
        """
572
        Synchronises an open directory.
573
        datasync: If True, only flush user data, not metadata.
574
        """
27 by Matt Giuca
mattfs: Added dir-handle arguments to all the directory operations, and more
575
        logging.info("fsyncdir: %s (datasync %s, dh %s)"
576
            % (path, datasync, dh))
26 by Matt Giuca
mattfs: Added section "directory operation methods", commented it, and moved
577
27 by Matt Giuca
mattfs: Added dir-handle arguments to all the directory operations, and more
578
    def readdir(self, path, offset, dh=None):
26 by Matt Giuca
mattfs: Added section "directory operation methods", commented it, and moved
579
        """
580
        Generator function. Produces a directory listing.
581
        Yields individual fuse.Direntry objects, one per file in the
582
        directory. Should always yield at least "." and "..".
583
        Should yield nothing if the file is not a directory or does not exist.
584
        (Does not need to raise an error).
585
586
        offset: I don't know what this does, but I think it allows the OS to
587
        request starting the listing partway through (which I clearly don't
588
        yet support). Seems to always be 0 anyway.
589
        """
27 by Matt Giuca
mattfs: Added dir-handle arguments to all the directory operations, and more
590
        logging.info("readdir: %s (offset %s, dh %s)" % (path, offset, dh))
26 by Matt Giuca
mattfs: Added section "directory operation methods", commented it, and moved
591
        if path == "/":
592
            yield fuse.Direntry(".")
593
            yield fuse.Direntry("..")
594
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
595
    ### FILE OPERATION METHODS ###
26 by Matt Giuca
mattfs: Added section "directory operation methods", commented it, and moved
596
    # Methods in this section are operations for opening files and working on
597
    # open files.
598
    # "open" and "create" are methods for opening files. They *may* return an
599
    # arbitrary Python object (not None or int), which is used as a file
600
    # handle by the methods for working on files.
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
601
    # All the other methods (fgetattr, release, read, write, fsync, flush,
26 by Matt Giuca
mattfs: Added section "directory operation methods", commented it, and moved
602
    # ftruncate and lock) are methods for working on files. They should all be
603
    # prepared to accept an optional file-handle argument, which is whatever
604
    # object "open" or "create" returned.
10 by Matt Giuca
mattfs: Added access, open and opendir methods.
605
606
    def open(self, path, flags):
607
        """
608
        Open a file for reading/writing, and check permissions.
609
        flags: As described in man 2 open (Linux Programmer's Manual).
610
            ORing of several access flags, including one of os.O_RDONLY,
611
            os.O_WRONLY or os.O_RDWR. All other flags are in os as well.
27 by Matt Giuca
mattfs: Added dir-handle arguments to all the directory operations, and more
612
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
613
        On success, *may* return an arbitrary Python object, which will be
614
        used as the "fh" argument to all the file operation methods on the
615
        file. Or, may just return None on success.
616
        On failure, should return a negative errno code.
617
        Should return -errno.EACCES if disallowed.
10 by Matt Giuca
mattfs: Added access, open and opendir methods.
618
        """
619
        logging.info("open: %s (flags %s)" % (path, oct(flags)))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
620
        return -errno.EOPNOTSUPP
10 by Matt Giuca
mattfs: Added access, open and opendir methods.
621
24 by Matt Giuca
mattfs.py: Minor (moved create near open).
622
    def create(self, path, mode, rdev):
623
        """
624
        Creates a file and opens it for writing.
625
        Will be called in favour of mknod+open, but it's optional (OS will
626
        fall back on that sequence).
627
        mode: Unix file mode flags for the file being created.
628
        rdev: Special properties for creation of character or block special
629
            devices (I've never gotten this to work).
630
            Always 0 for regular files or FIFO buffers.
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
631
        See "open" for return value.
24 by Matt Giuca
mattfs.py: Minor (moved create near open).
632
        """
633
        logging.info("create: %s (mode %s, rdev %s)" % (path,oct(mode),rdev))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
634
        return -errno.EOPNOTSUPP
24 by Matt Giuca
mattfs.py: Minor (moved create near open).
635
23 by Matt Giuca
mattfs.py: Added fgetattr and ftruncate methods. I discovered these are
636
    def fgetattr(self, path, fh=None):
637
        """
638
        Retrieves information about a file (the "stat" of a file).
639
        Same as Fuse.getattr, but may be given a file handle to an open file,
640
        so it can use that instead of having to look up the path.
641
        """
642
        logging.debug("fgetattr: %s (fh %s)" % (path, fh))
643
        # We could use fh for a more efficient lookup. Here we just call the
644
        # non-file-handle version, getattr.
645
        return self.getattr(path)
646
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
647
    def release(self, path, flags, fh=None):
14 by Matt Giuca
Added releasedir, release, write and truncate methods (stubs).
648
        """
649
        Closes an open file. Allows filesystem to clean up.
650
        flags: The same flags the file was opened with (see open).
651
        """
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
652
        logging.info("release: %s (flags %s, fh %s)" % (path, oct(flags), fh))
14 by Matt Giuca
Added releasedir, release, write and truncate methods (stubs).
653
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
654
    def fsync(self, path, datasync, fh=None):
22 by Matt Giuca
mattfs: Added fsyncdir, fsync, flush.
655
        """
656
        Synchronises an open file.
657
        datasync: If True, only flush user data, not metadata.
658
        """
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
659
        logging.info("fsync: %s (datasync %s, fh %s)" % (path, datasync, fh))
22 by Matt Giuca
mattfs: Added fsyncdir, fsync, flush.
660
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
661
    def flush(self, path, fh=None):
22 by Matt Giuca
mattfs: Added fsyncdir, fsync, flush.
662
        """
663
        Flush cached data to the file system.
664
        This is NOT an fsync (I think the difference is fsync goes both ways,
665
        while flush is just one-way).
666
        """
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
667
        logging.info("flush: %s (fh %s)" % (path, fh))
22 by Matt Giuca
mattfs: Added fsyncdir, fsync, flush.
668
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
669
    def read(self, path, size, offset, fh=None):
11 by Matt Giuca
mattfs: Implemented "read" function. Can now print out text for some files.
670
        """
671
        Get all or part of the contents of a file.
672
        size: Size in bytes to read.
673
        offset: Offset in bytes from the start of the file to read from.
674
        Does not need to check access rights (operating system will always
675
        call access or open first).
676
        Returns a byte string with the contents of the file, with a length no
677
        greater than 'size'. May also return an int error code.
678
679
        If the length of the returned string is 0, it indicates the end of the
680
        file, and the OS will not request any more. If the length is nonzero,
681
        the OS may request more bytes later.
682
        To signal that it is NOT the end of file, but no bytes are presently
683
        available (and it is a non-blocking read), return -errno.EAGAIN.
684
        If it is a blocking read, just block until ready.
685
        """
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
686
        logging.info("read: %s (size %s, offset %s, fh %s)"
687
            % (path, size, offset, fh))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
688
        return -errno.EOPNOTSUPP
11 by Matt Giuca
mattfs: Implemented "read" function. Can now print out text for some files.
689
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
690
    def write(self, path, buf, offset, fh=None):
14 by Matt Giuca
Added releasedir, release, write and truncate methods (stubs).
691
        """
692
        Write over part of a file.
693
        buf: Byte string containing the text to write.
694
        offset: Offset in bytes from the start of the file to write to.
695
        Does not need to check access rights (operating system will always
696
        call access or open first).
697
        Should only overwrite the part of the file from offset to
698
        offset+len(buf).
699
700
        Must return an int: the number of bytes successfully written (should
701
        be equal to len(buf) unless an error occured). May also be a negative
702
        int, which is an errno code.
703
        """
25 by Matt Giuca
mattfs.py: Added filehandle arguments to all file access methods, and added
704
        logging.info("write: %s (offset %s, fh %s)" % (path, offset, fh))
16 by Matt Giuca
mattfs: Write and truncate now succeed, pretending they were successful,
705
        logging.debug("  buf: %r" % buf)
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
706
        return -errno.EOPNOTSUPP
14 by Matt Giuca
Added releasedir, release, write and truncate methods (stubs).
707
23 by Matt Giuca
mattfs.py: Added fgetattr and ftruncate methods. I discovered these are
708
    def ftruncate(self, path, size, fh=None):
14 by Matt Giuca
Added releasedir, release, write and truncate methods (stubs).
709
        """
710
        Shrink or expand a file to a given size.
23 by Matt Giuca
mattfs.py: Added fgetattr and ftruncate methods. I discovered these are
711
        Same as Fuse.truncate, but may be given a file handle to an open file,
712
        so it can use that instead of having to look up the path.
14 by Matt Giuca
Added releasedir, release, write and truncate methods (stubs).
713
        """
23 by Matt Giuca
mattfs.py: Added fgetattr and ftruncate methods. I discovered these are
714
        logging.info("ftruncate: %s (size %s, fh %s)" % (path, size, fh))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
715
        return -errno.EOPNOTSUPP
14 by Matt Giuca
Added releasedir, release, write and truncate methods (stubs).
716
28 by Matt Giuca
mattfs: Added File class, and extensive documentation. But it's disabled by
717
class File(object):
718
    """
719
    An open File handle. Like Fuse objects, supports a number of
720
    specially-named methods which are called when the filesystem needs to
721
    access the file in question.
722
723
    Note: Implementing this class is an *alternative* to implementing the
724
    above "file operation methods" in Fuse itself. If this class is
725
    implemented, the following line should appear in Fuse.__init__:
726
727
    self.file_class = File
728
729
    Also, all of the "file operation methods" should be removed from Fuse, as
730
    they take priority over the File class.
731
732
    The File class is simply a more object oriented way to implement the same
733
    methods.
734
    """
735
    def __init__(self, path, flags, mode=None):
736
        """
737
        File-class version of "open" and "create" combined.
738
        Opens a file (possibly creating it, if mode is supplied).
739
740
        Note that you have no way to see the Fuse object which created it.
741
        The documentation suggests a workaround (search for
742
        "wrapped_file_class"). Note that this class is supposed to go inside
743
        Fuse.__init__.
744
        http://apps.sourceforge.net/mediawiki/fuse/index.php
745
            ?title=FUSE_Python_Reference
746
        This alone might be a reason to avoid using the File class, and
747
        instead go with the more direct approach.
748
        """
749
        logging.info("File.__init__: %s (flags %s, mode %s)"
750
            % (path, oct(flags), None if mode is None else oct(mode)))
751
        self.path = path
752
753
    def __repr__(self):
754
        return "<File %r>" % self.path
755
756
    def fgetattr(self):
757
        """
758
        File-class version of "getattr".
759
        Retrieves information about a file (the "stat" of a file).
760
        """
761
        logging.info("%r.fgetattr" % self)
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
762
        return -errno.EOPNOTSUPP
28 by Matt Giuca
mattfs: Added File class, and extensive documentation. But it's disabled by
763
764
    def release(self, flags):
765
        """
766
        File-class version of "release".
767
        Closes an open file. Allows filesystem to clean up.
768
        """
769
        logging.info("%r.release (flags %s)" % (self, oct(flags)))
770
771
    def fsync(self, datasync):
772
        """
773
        File-class version of "fsync".
774
        Synchronises an open file.
775
        """
776
        logging.info("%r.fsync (datasync %s)" % (self, datasync))
777
778
    def flush(self):
779
        """
780
        File-class version of "flush".
781
        Flush cached data to the file system.
782
        """
783
        logging.info("%r.flush" % self)
784
785
    def read(self, size, offset):
786
        """
787
        File-class version of "read".
788
        Get all or part of the contents of a file.
789
        """
790
        logging.info("%r.read (size %s, offset %s)" % (self, size, offset))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
791
        return -errno.EOPNOTSUPP
28 by Matt Giuca
mattfs: Added File class, and extensive documentation. But it's disabled by
792
793
    def write(self, buf, offset):
794
        """
795
        File-class version of "write".
796
        Write over part of a file.
797
        """
798
        logging.info("%r.write (offset %s)" % (self, offset))
799
        logging.debug("  buf: %r" % buf)
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
800
        return -errno.EOPNOTSUPP
28 by Matt Giuca
mattfs: Added File class, and extensive documentation. But it's disabled by
801
802
    def ftruncate(self, size):
803
        """
804
        File-class version of "ftruncate".
805
        Shrink or expand a file to a given size.
806
        """
807
        logging.info("%r.ftruncate (size %s)" % (self, size))
32 by Matt Giuca
templatefs: Rewrote many of the methods' implementations, so most now simply
808
        return -errno.EOPNOTSUPP
28 by Matt Giuca
mattfs: Added File class, and extensive documentation. But it's disabled by
809
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
810
# Now all we need is a main function.
811
# Fuse modules are actual Python scripts, which are user-executable. The main
812
# function needs to tell Fuse to mount themselves.
813
def main():
814
    # Our custom usage message
815
    usage = """
30 by Matt Giuca
Renamed MattFS to TemplateFS. This will now serve as a template for
816
    TemplateFS: A demo FUSE file system.
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
817
    """ + fuse.Fuse.fusage
30 by Matt Giuca
Renamed MattFS to TemplateFS. This will now serve as a template for
818
    server = TemplateFS(version="%prog " + fuse.__version__,
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
819
        usage=usage, dash_s_do='setsingle')
820
    server.parse(errex=1)
36 by Matt Giuca
templatefs: Added server.multithreaded = 0.
821
    # multithreaded = 0 appears to be very important.
822
    # I've had more complex filesystems freeze up without this.
823
    server.multithreaded = 0
1 by Matt Giuca
mattfs.py: A first cut at a simple fuse example (hello in a directory).
824
    try:
825
        server.main()
826
    except fuse.FuseError, e:
827
        print str(e)
828
829
if __name__ == '__main__':
830
    main()
831
18 by Matt Giuca
mattfs: Added fsdestroy.
832
logging.info("File system unmounted")