Skip to content

Commit

Permalink
chmod +w is not necessary anymore.
Browse files Browse the repository at this point in the history
  • Loading branch information
chmduquesne committed Mar 20, 2011
1 parent 327d7ef commit ab38430
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 52 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.pyc
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
test: dirs
python sharebox.py test/local/mnt -o gitdir=test/local/git

unmount:
fusermount -u test/local/mnt
fusermount -u test/remote/mnt

dirs:
mkdir -p test/local/mnt
mkdir -p test/local/git
mkdir -p test/remote/mnt
mkdir -p test/remote/git

clean:
rm -rf *.pyc
187 changes: 135 additions & 52 deletions sharebox.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
#!/usr/bin/env python

"""
Filesystem based on git-annex.
Usage
About git-annex: git-annex allows to keep only links to files and to keep
their content out of git. Once a file is added to git annex, it is
replaced by a symbolic link to the content of the file. The content of the
file is made read-only so that you don't modify it by mistake.
What does this file system:
- It automatically adds new files to git-annex.
- It resolves git-annex symlinks so that you see them as regular writable
files.
- If the content of a file is not present on the file system, it is
requested on the fly from one of the replicated copies.
- When you access a file, it does copy on write: if you don't modify it,
you read the git-annex copy. However, if you change it, the copy is
unlocked on the fly and commited to git-annex when closed. Depending on
the mount option, the previous copy can be kept in git-annex.
- It pulls at regular intervals the other replicated copies and launches a
merge program if there are conflicts.
"""
from __future__ import with_statement

from errno import EACCES
from os.path import realpath
from sys import argv, exit
import threading

import os
import os.path

from fuse import FUSE, FuseOSError, Operations, LoggingMixIn

import shlex
import subprocess
import logging
import time
import sys
import getopt

def ignored(path):
"""
Expand Down Expand Up @@ -45,7 +67,6 @@ def shell_do(cmd):
"""
calls the given shell command
"""
logging.debug(cmd)
p = subprocess.Popen(shlex.split(cmd))
p.wait()

Expand All @@ -71,7 +92,6 @@ def __enter__(self):
def __exit__(self, type, value, traceback):
if not self.ignored:
shell_do('git annex add %s' % self.path)
shell_do('chmod +w %s' % os.readlink(self.path))
shell_do('git commit -m "changed %s"' % self.path)

class CopyOnWrite:
Expand Down Expand Up @@ -118,27 +138,28 @@ def __enter__(self):
if self.opened_copies.get(self.fh, None) == None:
if not self.ignored:
shell_do('git annex unlock %s' % self.path)
self.opened_copies[self.fh] = os.open(self.path,
os.O_WRONLY | os.O_CREAT)
self.opened_copies[self.fh] = os.open(self.path,
os.O_WRONLY | os.O_CREAT)
return self.opened_copies.get(self.fh, self.fh)

def __exit__(self, type, value, traceback):
if self.commit:
if not self.ignored:
try:
os.close(self.opened_copies[self.fh])
del self.opened_copies[self.fh]
except KeyError:
pass
shell_do('git annex add %s' % self.path)
shell_do('chmod +w %s' % os.readlink(self.path))
shell_do('git commit -m "changed %s"' % self.path)
if self.opened_copies.get(self.fh, None) != None:
if not self.ignored:
try:
os.close(self.opened_copies[self.fh])
del self.opened_copies[self.fh]
except KeyError:
pass
shell_do('git annex add %s' % self.path)
shell_do('git commit -m "changed %s"' % self.path)

class ShareBox(LoggingMixIn, Operations):
"""
Assumes operating from the root of the managed git directory
"""
def __init__(self):
def __init__(self, gitdir):
self.gitdir = gitdir
self.rwlock = threading.Lock()
self.opened_copies = {}
with self.rwlock:
Expand All @@ -153,17 +174,24 @@ def __call__(self, op, path, *args):
"""
redirects self.op('/foo', ...) to self.op('./foo', ...)
"""
if os.path.realpath(os.curdir) != self.gitdir:
os.chdir(self.gitdir)
return super(ShareBox, self).__call__(op, "." + path, *args)

getxattr = None
listxattr = None
link = os.link
mknod = os.mknod
mkdir = os.mkdir
open = os.open
readlink = os.readlink
rmdir = os.rmdir

def statfs(self, path):
stv = os.statvfs(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 create(self, path, mode):
return os.open(path, os.O_WRONLY | os.O_CREAT, mode)

Expand All @@ -174,32 +202,52 @@ def readdir(self, path, fh):
return ['.', '..'] + os.listdir(path)

def access(self, path, mode):
if not os.access(path, mode):
raise FuseOSError(EACCES)

def getattr(self, path, fh=None):
if annexed(path):
if not os.path.exists(path):
raise FuseOSError(EACCES)
else:
if not os.access(path, mode):
raise FuseOSError(EACCES)

def open(self, path, flags):
"""
symlinks to annexed files are dereferenced
When an annexed file is requested, if it is not present on the
system we first try to get it. If it fails, we refuse the access.
Since we do copy on write, we do not need to try to open in write
mode annexed files.
"""
path_ = path
if annexed(path) and os.path.exists(path):
path_ = os.readlink(path)
st = os.lstat(path_)
return dict((key, getattr(st, key)) for key in ('st_atime',
'st_ctime', 'st_gid', 'st_mode', 'st_mtime', 'st_nlink',
'st_size', 'st_uid'))
res = None
if annexed(path):
if not os.path.exists(path):
shell_do('git annex get %s' % path)
if not os.path.exists(path):
raise FuseOSError(EACCES)
res = os.open(path, 32768) # magic to open read only
else:
res = os.open(path, flags)
return res

def statfs(self, path):
def getattr(self, path, fh=None):
"""
symlinks to annexed files are dereferenced
When an annexed file is requested, we fake some of its attributes,
making it look like a conventional file (of size 0 if if is not
present on the system).
"""
path_ = path
if annexed(path) and os.path.exists(path):
path_ = os.readlink(path)
stv = os.statvfs(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'))
faked_attr = {}
if annexed(path):
faked_attr ['st_mode'] = 33188 # we fake a 644 regular file
if os.path.exists(path):
path_ = os.readlink(path)
else:
faked_attr ['st_size'] = 0
st = os.lstat(path_)
res = dict((key, getattr(st, key)) for key in ('st_atime',
'st_ctime', 'st_gid', 'st_mode', 'st_mtime',
'st_nlink', 'st_size', 'st_uid'))
for attr, value in faked_attr.items():
res [attr] = value
return res

def chmod(self, path, mode):
with self.rwlock:
Expand Down Expand Up @@ -253,8 +301,8 @@ def release(self, path, fh):
os.close(fh)

def rename(self, old, new):
# Make sure to lock the file (and to annex it if it was not)
with self.rwlock:
# Make sure to lock the file (and to annex it if it was not)
if not ignored(old):
shell_do('git annex add %s' % old)
os.rename(old, '.' + new)
Expand Down Expand Up @@ -292,19 +340,54 @@ def synchronize():
"""
i = 0
while 1:
logging.debug("synchronizing %s" % i)
time.sleep(1)
i += 1

if __name__ == "__main__":
if len(argv) != 3:
print 'usage: %s <gitdir> <mountpoint>' % argv[0]
exit(1)
logging.basicConfig(filename="sharebox.log", level=logging.DEBUG)
t = threading.Thread(target=synchronize)
t.daemon = True
#t.start()
mountpoint = realpath(argv[2])
gitdir = realpath(argv[1])
os.chdir(gitdir)
fuse = FUSE(ShareBox(), mountpoint, foreground=True)
try:
opts, args = getopt.gnu_getopt(sys.argv[1:], "ho:", ["help"])
except getopt.GetoptError, err:
print str(err)
print 'usage: %s <mountpoint> [-o <option>]' % sys.argv[0]
sys.exit(1)

gitdir = None
logfile = 'sharebox.log'
foreground = False
sync = 0

for opt, arg in opts:
if opt in ("-h", "--help"):
print 'usage: %s <mountpoint> [-o <option>]' % sys.argv[0]
sys.exit(0)
if opt == "-o":
if '=' in arg:
option = arg.split('=')[0]
value = arg.replace( option + '=', '', 1)
if option == 'gitdir':
gitdir = value
if option == 'logfile':
logfile = value
if option == 'sync':
sync = value
else:
if arg == 'foreground':
foreground=True

if not gitdir:
print 'missing the gitdir option'
sys.exit(1)

mountpoint = "".join(args)
if mountpoint == "":
print 'invalid mountpoint'
sys.exit(1)

if sync:
t = threading.Thread(target=synchronize)
t.daemon = True
t.start()

mountpoint = os.path.realpath(mountpoint)
gitdir = os.path.realpath(gitdir)
fuse = FUSE(ShareBox(gitdir), mountpoint, foreground=foreground)

0 comments on commit ab38430

Please sign in to comment.