Merge branch 'acl'
authorEvan Broder <broder@mit.edu>
Thu, 9 Jul 2009 02:32:01 +0000 (19:32 -0700)
committerEvan Broder <broder@mit.edu>
Thu, 9 Jul 2009 02:32:01 +0000 (19:32 -0700)
afs/_acl.pyx [new file with mode: 0644]
afs/acl.py [new file with mode: 0644]
afs/afs.pxd
afs/afs.pyx
afs/tests/test_acl.py [new file with mode: 0644]
setup.py

diff --git a/afs/_acl.pyx b/afs/_acl.pyx
new file mode 100644 (file)
index 0000000..ef65388
--- /dev/null
@@ -0,0 +1,45 @@
+from afs cimport *
+from afs import pyafs_error
+
+cdef import from "afs/prs_fs.h":
+    enum:
+        PRSFS_READ, PRSFS_WRITE, PRSFS_INSERT, PRSFS_LOOKUP,
+        PRSFS_DELETE, PRSFS_LOCK, PRSFS_ADMINISTER,
+        PRSFS_USR0, PRSFS_USR1, PRSFS_USR2, PRSFS_USR2, PRSFS_USR3,
+        PRSFS_USR4, PRSFS_USR5, PRSFS_USR6, PRSFS_USR7
+
+# This is defined in afs/afs.h, but I can't figure how to include the
+# header. Also, venus/fs.c redefines the struct, so why not!
+cdef struct vcxstat2:
+    afs_int32 callerAccess
+    afs_int32 cbExpires
+    afs_int32 anyAccess
+    char mvstat
+
+READ = PRSFS_READ
+WRITE = PRSFS_WRITE
+INSERT = PRSFS_INSERT
+LOOKUP = PRSFS_LOOKUP
+DELETE = PRSFS_DELETE
+LOCK = PRSFS_LOCK
+ADMINISTER = PRSFS_ADMINISTER
+USR0 = PRSFS_USR0
+USR1 = PRSFS_USR1
+USR2 = PRSFS_USR2
+USR3 = PRSFS_USR3
+USR4 = PRSFS_USR4
+USR5 = PRSFS_USR5
+USR6 = PRSFS_USR6
+USR7 = PRSFS_USR7
+
+DEF MAXSIZE = 2048
+
+def getAcl(char* dir, int follow=1):
+    cdef char space[MAXSIZE]
+    pioctl_read(dir, VIOCGETAL, space, MAXSIZE, follow)
+    return space
+
+def getCallerAccess(char *dir, int follow=1):
+    cdef vcxstat2 stat
+    pioctl_read(dir, VIOC_GETVCXSTATUS2, <void*>&stat, sizeof(vcxstat2), follow)
+    return stat.callerAccess
diff --git a/afs/acl.py b/afs/acl.py
new file mode 100644 (file)
index 0000000..da0821f
--- /dev/null
@@ -0,0 +1,88 @@
+import _acl
+from _acl import READ, WRITE, INSERT, LOOKUP, DELETE, LOCK, ADMINISTER, \
+    USR0, USR1, USR2, USR3, USR4, USR5, USR6, USR7
+from _acl import getCallerAccess
+
+_canonical = {
+    "read": "rl",
+    "write": "rwlidwk",
+    "all": "rwlidwka",
+    "mail": "lik",
+    "none": "",
+}
+
+_charBitAssoc = [
+    ('r', READ),
+    ('w', WRITE),
+    ('i', INSERT),
+    ('l', LOOKUP),
+    ('d', DELETE),
+    ('k', LOCK),
+    ('a', ADMINISTER),
+    ('A', USR0),
+    ('B', USR1),
+    ('C', USR2),
+    ('D', USR3),
+    ('E', USR4),
+    ('F', USR5),
+    ('G', USR6),
+    ('H', USR7),
+]
+
+_char2bit = dict(_charBitAssoc)
+
+
+def readRights(s):
+    """Canonicalizes string rights to bitmask"""
+    if s in _canonical: s = _canonical[s]
+    return _parseRights(s)
+
+def showRights(r):
+    """Takes a bitmask and returns a rwlidka string"""
+    s = ""
+    for char,mask in _charBitAssoc:
+        if r & mask == mask: s += char
+    return s
+
+def _parseRights(s):
+    """Parses a rwlid... rights tring to bitmask"""
+    r = 0
+    try:
+        for c in s:
+            r = r | _char2bit[c]
+    except KeyError:
+        raise ValueError
+    return r
+
+def _parseAcl(inp):
+    lines = inp.split("\n")
+    npos = int(lines[0].split(" ")[0])
+    pos = {}
+    neg = {}
+    for l in lines[2:]:
+        if l == "": continue
+        name, acl = l.split()
+        if npos:
+            npos -= 1
+            pos[name] = int(acl)
+        else:
+            # negative acl
+            neg[name] = int(acl)
+    return (pos, neg)
+
+class ACL(object):
+    def __init__(self, pos, neg):
+        """
+        ``pos``
+            Dictionary of usernames to positive ACL bitmasks
+        ``neg``
+            Dictionary of usernames to negative ACL bitmasks
+        """
+        self.pos = pos
+        self.neg = neg
+    @staticmethod
+    def retrieve(dir, follow=1):
+        """Retrieve the ACL for an AFS directory"""
+        pos, neg = _parseAcl(_acl.getAcl(dir, follow))
+        return ACL(pos, neg)
+
index d165921..02518e2 100644 (file)
@@ -146,3 +146,20 @@ cdef extern from "rx/rxkad.h":
     void initialize_RXK_error_table()
 cdef extern from "ubik.h":
     void initialize_U_error_table()
+
+cdef extern from "afs/vice.h":
+    struct ViceIoctl:
+        void *cin "in"
+        void *out
+        unsigned short out_size
+        unsigned short in_size
+
+cdef import from "afs/venus.h":
+    enum:
+        # PIOCTLS to Venus that we use
+        VIOCGETAL, VIOC_GETVCXSTATUS2
+
+# pioctl doesn't actually have a header, so we have to define it here
+cdef extern int pioctl(char *, afs_int32, ViceIoctl *, afs_int32)
+cdef int pioctl_read(char *, afs_int32, void *, unsigned short, afs_int32) except -1
+
index 8f51c16..29a2442 100644 (file)
@@ -4,8 +4,30 @@ General PyAFS utilities, such as error handling
 
 import sys
 
+# otherwise certain headers are unhappy
+cdef import from "netinet/in.h": pass
+cdef import from "afs/vice.h": pass
+
 cdef int _init = 0
 
+# pioctl convenience wrappers
+
+cdef extern int pioctl_read(char *dir, afs_int32 op, void *buffer, unsigned short size, afs_int32 follow) except -1:
+    cdef ViceIoctl blob
+    cdef afs_int32 code
+    blob.in_size  = 0
+    blob.out_size = size
+    blob.out = buffer
+    code = pioctl(dir, op, &blob, follow)
+    # This might work with the rest of OpenAFS, but I'm not convinced
+    # the rest of it is consistent
+    if code == -1:
+        raise OSError(errno, strerror(errno))
+    pyafs_error(code)
+    return code
+
+# Error handling
+
 class AFSException(Exception):
     def __init__(self, errno):
         self.errno = errno
diff --git a/afs/tests/test_acl.py b/afs/tests/test_acl.py
new file mode 100644 (file)
index 0000000..c6a7338
--- /dev/null
@@ -0,0 +1,21 @@
+import nose
+import afs.acl as acl
+
+def test_showRights():
+    assert acl.showRights(acl.READ | acl.WRITE) == "rw"
+
+def test_crights():
+    assert acl.crights('read') & acl.READ
+    assert acl.crights('read') & acl.LOOKUP
+    assert not acl.crights('read') & acl.WRITE
+
+def test_retrieve():
+    assert acl.ACL.retrieve('/afs/athena.mit.edu/contrib/bitbucket2').pos['system:anyuser'] & acl.WRITE
+    assert acl.ACL.retrieve('/afs/athena.mit.edu/user/t/a/tabbott').neg['yuranlu'] & acl.USR0
+
+def test_getCallerAccess():
+    assert acl.getCallerAccess('/afs/athena.mit.edu/contrib/bitbucket2') & acl.WRITE
+
+if __name__ == '__main__':
+    nose.main()
+
index e3a5133..912231c 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -40,6 +40,7 @@ setup(
     ext_modules=[
         PyAFSExtension("afs.afs"),
         PyAFSExtension("afs._pts"),
+        PyAFSExtension("afs._acl"),
         ],
     cmdclass= {"build_ext": build_ext}
 )