From 98e3144bc811fba0099026d0cb31af36e73e6297 Mon Sep 17 00:00:00 2001 From: Iustin Pop Date: Sun, 13 May 2012 21:25:31 +0200 Subject: [PATCH] Imported Upstream version 0.5.1 --- .gitignore | 10 + Makefile | 33 +- NEWS | 42 ++- PKG-INFO | 12 - PLATFORMS | 30 -- README | 40 ++- acl.c | 422 ++++++++++++++--------- doc/conf.py | 250 ++++++++++++++ IMPLEMENTATION => doc/implementation.rst | 60 ++++ doc/index.rst | 18 + doc/module.rst | 4 + doc/news.rst | 1 + pylibacl.egg-info/PKG-INFO | 12 - pylibacl.egg-info/SOURCES.txt | 14 - pylibacl.egg-info/dependency_links.txt | 1 - pylibacl.egg-info/top_level.txt | 1 - setup.cfg | 7 +- setup.py | 2 +- tests/__init__.py | 0 tests/test_acls.py | 320 +++++++++++++++++ 20 files changed, 1004 insertions(+), 275 deletions(-) create mode 100644 .gitignore delete mode 100644 PKG-INFO delete mode 100644 PLATFORMS create mode 100644 doc/conf.py rename IMPLEMENTATION => doc/implementation.rst (51%) create mode 100644 doc/index.rst create mode 100644 doc/module.rst create mode 120000 doc/news.rst delete mode 100644 pylibacl.egg-info/PKG-INFO delete mode 100644 pylibacl.egg-info/SOURCES.txt delete mode 100644 pylibacl.egg-info/dependency_links.txt delete mode 100644 pylibacl.egg-info/top_level.txt create mode 100644 tests/__init__.py create mode 100644 tests/test_acls.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e108039 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/MANIFEST +/build +/dist +/doc/doctrees +/doc/html +/posix1e.html +/posix1e.so +/posix1e.txt +/pylibacl.egg-info +*.py[co] diff --git a/Makefile b/Makefile index 20029f4..0ba620d 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,35 @@ -.PHONY: doc test +SPHINXOPTS = -W +SPHINXBUILD = sphinx-build +DOCDIR = doc +DOCHTML = $(DOCDIR)/html +DOCTREES = $(DOCDIR)/doctrees +ALLSPHINXOPTS = -d $(DOCTREES) $(SPHINXOPTS) $(DOCDIR) + +MODNAME = posix1e.so +RSTFILES = doc/index.rst doc/module.rst NEWS README doc/conf.py all: doc test -posix1e.so: acl.c +$(MODNAME): acl.c ./setup.py build_ext --inplace -doc: posix1e.so - epydoc -q -o html --name pylibacl \ - --url http://pylibacl.sourceforge.net/ \ - --show-frames \ - --docformat epytext \ - --no-sourcecode \ - posix1e +$(DOCHTML)/index.html: $(MODNAME) $(RSTFILES) + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(DOCHTML) + touch $@ + +doc: $(DOCHTML)/index.html test: - for ver in 2.4 2.5 2.6 3.0 3.1; do \ + for ver in 2.4 2.5 2.6 2.7 3.0 3.1 3.2; do \ if type python$$ver >/dev/null; then \ echo Testing with python$$ver; \ python$$ver ./setup.py test; \ fi; \ done + +clean: + rm -rf $(DOCHTML) $(DOCTREES) + rm -f $(MODNAME) + rm -rf build + +.PHONY: doc test clean diff --git a/NEWS b/NEWS index b14b96c..a84e674 100644 --- a/NEWS +++ b/NEWS @@ -1,19 +1,38 @@ +News +==== + +Version 0.5.1 +------------- + +A bug-fix only release. Critical bugs (memory leaks and possible +segmentation faults) have been fixed thanks to Dave Malcolm and his +``cpychecker`` tool. Additionally, some compatibility issues with Python +3.x have been fixed (str() methods returning bytes). + +The documentation has been improved and changed from epydoc to sphinx; +note however that the documentation is still auto-generated from the +docstrings. + +Project reorganisation: the project home page has been moved from +SourceForge to GitHub. + + Version 0.5 -=========== +----------- Added support for Python 3.x and improved support for Unicode filenames. Version 0.4 -=========== +----------- License -------- +~~~~~~~ Starting with this version, pylibacl is licensed under LGPL 2.1, Febryary 1999 or any later versions (see README and COPYING). Linux support -------------- +~~~~~~~~~~~~~ A few more Linux-specific functions: @@ -25,7 +44,7 @@ A few more Linux-specific functions: has an extended ACL FreeBSD support ---------------- +~~~~~~~~~~~~~~~ FreeBSD 7.x will have almost all the acl manipulation functions that Linux has, with the exception of __getstate__/__setstate__. As a @@ -33,7 +52,7 @@ workaround, use the str() and ACL(text=...) methods to pass around textual representations. Interface ---------- +~~~~~~~~~ At module level there are now a few constants exported for easy-checking at runtime what features have been compiled in: @@ -54,16 +73,16 @@ at runtime what features have been compiled in: equiv_mode() method Internals ---------- +~~~~~~~~~ Many functions have now unittests, which is a good thing. Version 0.3 -=========== +----------- Linux support -------------- +~~~~~~~~~~~~~ Under Linux, implement more functions from libacl: @@ -71,3 +90,8 @@ Under Linux, implement more functions from libacl: - add ACL().to_any_text, implementing acl_to_any_text - add ACL comparison, using acl_cmp - add ACL().check, which is a more descriptive function than validate + +.. Local Variables: +.. mode: rst +.. fill-column: 72 +.. End: diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index 4ba298e..0000000 --- a/PKG-INFO +++ /dev/null @@ -1,12 +0,0 @@ -Metadata-Version: 1.0 -Name: pylibacl -Version: 0.5.0 -Summary: POSIX.1e ACLs for python -Home-page: http://pylibacl.sourceforge.net/ -Author: Iustin Pop -Author-email: iusty@k1024.org -License: LGPL -Description: This is a C extension module for Python which - implements POSIX ACLs manipulation. It is a wrapper on top - of the systems's acl C library - see acl(5). -Platform: UNKNOWN diff --git a/PLATFORMS b/PLATFORMS deleted file mode 100644 index 6c05d1d..0000000 --- a/PLATFORMS +++ /dev/null @@ -1,30 +0,0 @@ -Current supported platforms -=========================== - -Linux ------ - -It needs kernel 2.4 or higher and the libacl library installed (with -development headers, if installing from rpm). This library is available -on all modern distributions. - -The level of compliance is level 2 (see IMPLEMENTATION), plus some extra -functions; and as my development is done on Linux, I try to implement -these extensions when it makes sense. - - -FreeBSD -------- - -The current tested version is 7.0. FreeBSD supports all the standards -functions, but 7.0-RELEASE seems to have some issues regarding the -acl_valid() function when the qualifier of an ACL_USER or ACL_GROUP -entry is the same as the current uid. By my interpretation, this should -be a valid ACL, but FreeBSD declares the ACL invalid. As such, some -unittests fail on FreeBSD. - - -Other platforms ---------------- - -For any other platforms, volunteers are welcome - read the PORTING file. diff --git a/README b/README index 7bba91e..7b42b08 100644 --- a/README +++ b/README @@ -1,23 +1,41 @@ pylibacl ======== -About ------ - -This is an extension for Python which implements POSIX ACLs (POSIX.1e). - -The supported platforms are detailed in the file PLATFORMS. - -A few internal details are in the file IMPLEMENTATION. +This is a Python 2.4+ extension module allows you to manipulate the +POSIX.1e Access Control Lists present in some OS/file-systems +combinations. + +Downloads: go to https://github.com/iustin/pylibacl/downloads. Latest +version is 0.5.1. The source repository is either +at ``_ or +at https://github.com/iustin/pylibacl. + +For any issues, please file bugs at +https://github.com/iustin/pylibacl/issues. + +Requirements +------------ + +pylibacl has been written and tested on Linux, kernel v2.4 or newer, +with XFS filesystems; ext2/ext3 should also work. Since release 0.4.0, +FreeBSD 7 also has quite good support. If any other platform +implements the POSIX.1e draft, pylibacl can be used. I heard that +Solaris does, but I can't test it yet. + +- Python 2.4 or newer +- operating system: + - Linux, kernel v2.4 or newer, and the libacl library and + development packages (all modern distributions should have this, + under various names); also the file-systems you use must have + ACLs turned on, either as a compile or mount option + - FreeBSD 7.0 or newer License ------- -pylibacl is Copyright (C) 2002-2009 Iustin Pop. +pylibacl is Copyright (C) 2002-2009, 2012 Iustin Pop. pylibacl is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. See the COPYING file for the full license terms. - -Iustin Pop, diff --git a/acl.c b/acl.c index d8bbee5..6c0851b 100644 --- a/acl.c +++ b/acl.c @@ -1,7 +1,7 @@ /* posix1e - a python module exposing the posix acl functions - Copyright (C) 2002-2009 Iustin Pop + Copyright (C) 2002-2009, 2012 Iustin Pop This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -45,6 +45,10 @@ #define PyInt_AsUnsignedLongMask PyLong_AsUnsignedLongMask #define PyInt_AsUnsignedLongLongMask PyLong_AsUnsignedLongLongMask #define PyInt_AS_LONG PyLong_AS_LONG +#define MyString_ConcatAndDel PyUnicode_AppendAndDel +#define MyString_FromFormat PyUnicode_FromFormat +#define MyString_FromString PyUnicode_FromString +#define MyString_FromStringAndSize PyUnicode_FromStringAndSize #else #define PyBytes_Check PyString_Check #define PyBytes_AS_STRING PyString_AS_STRING @@ -52,6 +56,10 @@ #define PyBytes_FromString PyString_FromString #define PyBytes_FromFormat PyString_FromFormat #define PyBytes_ConcatAndDel PyString_ConcatAndDel +#define MyString_ConcatAndDel PyBytes_ConcatAndDel +#define MyString_FromFormat PyBytes_FromFormat +#define MyString_FromString PyBytes_FromString +#define MyString_FromStringAndSize PyBytes_FromStringAndSize /* Python 2.6 already defines Py_TYPE */ #ifndef Py_TYPE @@ -208,9 +216,9 @@ static PyObject* ACL_str(PyObject *obj) { if(text == NULL) { return PyErr_SetFromErrno(PyExc_IOError); } - ret = PyBytes_FromString(text); + ret = MyString_FromString(text); if(acl_free(text) != 0) { - Py_DECREF(ret); + Py_XDECREF(ret); return PyErr_SetFromErrno(PyExc_IOError); } return ret; @@ -218,29 +226,30 @@ static PyObject* ACL_str(PyObject *obj) { #ifdef HAVE_LINUX static char __to_any_text_doc__[] = + "to_any_text([prefix='', separator='n', options=0])\n" "Convert the ACL to a custom text format.\n" "\n" - "This method encapsulates the acl_to_any_text function. It allows a \n" - "customized text format to be generated for the ACL. See\n" - "acl_to_any_text(3) for more details.\n" + "This method encapsulates the ``acl_to_any_text()`` function.\n" + "It allows a customized text format to be generated for the ACL. See\n" + ":manpage:`acl_to_any_text(3)` for more details.\n" "\n" - "Parameters:\n" - " - prefix: if given, this string will be prepended to all lines\n" - " - separator: a single character (defaults to '\\n'); this will be\n" - " user to separate the entries in the ACL\n" - " - options: a bitwise combination of:\n" - " - TEXT_ABBREVIATE: use 'u' instead of 'user', 'g' instead of \n" - " 'group', etc.\n" - " - TEXT_NUMERIC_IDS: User and group IDs are included as decimal\n" - " numbers instead of names\n" - " - TEXT_SOME_EFFECTIVE: Include comments denoting the effective\n" - " permissions when some are masked\n" - " - TEXT_ALL_EFFECTIVE: Include comments after all ACL entries\n" - " affected by an ACL_MASK entry\n" - " - TEXT_SMART_INDENT: Used in combination with the _EFFECTIVE\n" - " options, this will ensure that comments \n" - " are alligned to the fourth tab position\n" - " (assuming one tab equals eight spaces)\n" + ":param string prefix: if given, this string will be pre-pended to\n" + " all lines\n" + ":param string separator: a single character (defaults to '\\n'); this will" + " be used to separate the entries in the ACL\n" + ":param options: a bitwise combination of:\n\n" + " - :py:data:`TEXT_ABBREVIATE`: use 'u' instead of 'user', 'g' \n" + " instead of 'group', etc.\n" + " - :py:data:`TEXT_NUMERIC_IDS`: User and group IDs are included as\n" + " decimal numbers instead of names\n" + " - :py:data:`TEXT_SOME_EFFECTIVE`: Include comments denoting the\n" + " effective permissions when some are masked\n" + " - :py:data:`TEXT_ALL_EFFECTIVE`: Include comments after all ACL\n" + " entries affected by an ACL_MASK entry\n" + " - :py:data:`TEXT_SMART_INDENT`: Used in combination with the\n" + " _EFFECTIVE options, this will ensure that comments are aligned\n" + " to the fourth tab position (assuming one tab equals eight spaces)\n" + ":rtype: string\n" ; /* Converts the acl to a custom text format */ @@ -249,7 +258,7 @@ static PyObject* ACL_to_any_text(PyObject *obj, PyObject *args, char *text; ACL_Object *self = (ACL_Object*) obj; PyObject *ret; - char *arg_prefix = NULL; + const char *arg_prefix = NULL; char arg_separator = '\n'; int arg_options = 0; static char *kwlist[] = {"prefix", "separator", "options", NULL}; @@ -264,7 +273,7 @@ static PyObject* ACL_to_any_text(PyObject *obj, PyObject *args, } ret = PyBytes_FromString(text); if(acl_free(text) != 0) { - Py_DECREF(ret); + Py_XDECREF(ret); return PyErr_SetFromErrno(PyExc_IOError); } return ret; @@ -274,18 +283,20 @@ static char __check_doc__[] = "Check the ACL validity.\n" "\n" "This is a non-portable, Linux specific extension that allow more\n" - "information to be retrieved in case an ACL is not valid than the\n" - "validate() method.\n" + "information to be retrieved in case an ACL is not valid than via the\n" + ":py:func:`valid` method.\n" "\n" "This method will return either False (the ACL is valid), or a tuple\n" "with two elements. The first element is one of the following\n" - "constants:\n" - " - ACL_MULTI_ERROR: The ACL contains multiple entries that have a\n" - " tag type that may occur at most once\n" - " - ACL_DUPLICATE_ERROR: The ACL contains multiple ACL_USER or \n" - " ACL_GROUP entries with the same ID\n" - " - ACL_MISS_ERROR: A required entry is missing\n" - " - ACL_ENTRY_ERROR: The ACL contains an invalid entry tag type\n" + "constants:\n\n" + " - :py:data:`ACL_MULTI_ERROR`: The ACL contains multiple entries that\n" + " have a tag type that may occur at most once\n" + " - :py:data:`ACL_DUPLICATE_ERROR`: The ACL contains multiple \n" + " :py:data:`ACL_USER` or :py:data:`ACL_GROUP` entries with the\n" + " same ID\n" + " - :py:data:`ACL_MISS_ERROR`: A required entry is missing\n" + " - :py:data:`ACL_ENTRY_ERROR`: The ACL contains an invalid entry\n" + " tag type\n" "\n" "The second element of the tuple is the index of the entry that is\n" "invalid (in the same order as by iterating over the ACL entry)\n" @@ -300,10 +311,9 @@ static PyObject* ACL_check(PyObject* obj, PyObject* args) { if((result = acl_check(self->acl, &eindex)) == -1) return PyErr_SetFromErrno(PyExc_IOError); if(result == 0) { - Py_INCREF(Py_False); - return Py_False; + Py_RETURN_FALSE; } - return PyTuple_Pack(2, PyInt_FromLong(result), PyInt_FromLong(eindex)); + return Py_BuildValue("(ii)", result, eindex); } /* Implementation of the rich compare for ACLs */ @@ -345,7 +355,9 @@ static char __equiv_mode_doc__[] = "This is a non-portable, Linux specific extension that checks\n" "if the ACL is a basic ACL and returns the corresponding mode.\n" "\n" - "An IOerror exception will be raised if the ACL is an extended ACL\n" + ":rtype: integer\n" + ":raise IOError: An IOerror exception will be raised if the ACL is\n" + " an extended ACL.\n" ; /* The acl_equiv_mode method */ @@ -368,16 +380,16 @@ static int ACL_nocmp(PyObject* o1, PyObject* o2) { /* Custom methods */ static char __applyto_doc__[] = + "applyto(item[, flag=ACL_TYPE_ACCESS])\n" "Apply the ACL to a file or filehandle.\n" "\n" - "Parameters:\n" - " - either a filename or a file-like object or an integer; this\n" - " represents the filesystem object on which to act\n" - " - optional flag representing the type of ACL to set, either\n" - " ACL_TYPE_ACCESS (default) or ACL_TYPE_DEFAULT\n" + ":param item: either a filename or a file-like object or an integer;\n" + " this represents the filesystem object on which to act\n" + ":param flag: optional flag representing the type of ACL to set, either\n" + " :py:data:`ACL_TYPE_ACCESS` (default) or :py:data:`ACL_TYPE_DEFAULT`\n" ; -/* Applyes the ACL to a file */ +/* Applies the ACL to a file */ static PyObject* ACL_applyto(PyObject* obj, PyObject* args) { ACL_Object *self = (ACL_Object*) obj; PyObject *myarg; @@ -385,7 +397,7 @@ static PyObject* ACL_applyto(PyObject* obj, PyObject* args) { int nret; int fd; - if (!PyArg_ParseTuple(args, "O|i", &myarg, &type)) + if (!PyArg_ParseTuple(args, "O|I", &myarg, &type)) return NULL; if(PyBytes_Check(myarg)) { @@ -420,25 +432,32 @@ static char __valid_doc__[] = "Test the ACL for validity.\n" "\n" "This method tests the ACL to see if it is a valid ACL\n" - "in terms of the filesystem. More precisely, it checks that:\n" + "in terms of the file-system. More precisely, it checks that:\n" "\n" "The ACL contains exactly one entry with each of the\n" - "ACL_USER_OBJ, ACL_GROUP_OBJ, and ACL_OTHER tag types. Entries\n" - "with ACL_USER and ACL_GROUP tag types may appear zero or more\n" - "times in an ACL. An ACL that contains entries of ACL_USER or\n" - "ACL_GROUP tag types must contain exactly one entry of the \n" - "ACL_MASK tag type. If an ACL contains no entries of\n" - "ACL_USER or ACL_GROUP tag types, the ACL_MASK entry is optional.\n" + ":py:data:`ACL_USER_OBJ`, :py:data:`ACL_GROUP_OBJ`, and \n" + ":py:data:`ACL_OTHER` tag types. Entries\n" + "with :py:data:`ACL_USER` and :py:data:`ACL_GROUP` tag types may\n" + "appear zero or more\n" + "times in an ACL. An ACL that contains entries of :py:data:`ACL_USER` or\n" + ":py:data:`ACL_GROUP` tag types must contain exactly one entry of the \n" + ":py:data:`ACL_MASK` tag type. If an ACL contains no entries of\n" + ":py:data:`ACL_USER` or :py:data:`ACL_GROUP` tag types, the\n" + ":py:data:`ACL_MASK` entry is optional.\n" "\n" "All user ID qualifiers must be unique among all entries of\n" - "the ACL_USER tag type, and all group IDs must be unique among all\n" - "entries of ACL_GROUP tag type.\n" + "the :py:data:`ACL_USER` tag type, and all group IDs must be unique\n" + "among all entries of :py:data:`ACL_GROUP` tag type.\n" "\n" "The method will return 1 for a valid ACL and 0 for an invalid one.\n" - "This has been chosen because the specification for acl_valid in\n" - "the POSIX.1e standard documents only one possible value for errno\n" + "This has been chosen because the specification for\n" + ":manpage:`acl_valid(3)`\n" + "in the POSIX.1e standard documents only one possible value for errno\n" "in case of an invalid ACL, so we can't differentiate between\n" "classes of errors. Other suggestions are welcome.\n" + "\n" + ":return: 0 or 1\n" + ":rtype: integer\n" ; /* Checks the ACL for validity */ @@ -446,11 +465,9 @@ static PyObject* ACL_valid(PyObject* obj, PyObject* args) { ACL_Object *self = (ACL_Object*) obj; if(acl_valid(self->acl) == -1) { - Py_INCREF(Py_False); - return Py_False; + Py_RETURN_FALSE; } else { - Py_INCREF(Py_True); - return Py_True; + Py_RETURN_TRUE; } } @@ -547,11 +564,12 @@ static PyObject* ACL_iternext(PyObject *obj) { } static char __ACL_delete_entry_doc__[] = + "delete_entry(entry)\n" "Deletes an entry from the ACL.\n" "\n" - "Note: Only with level 2\n" - "Parameters:\n" - " - the Entry object which should be deleted; note that after\n" + ".. note:: Only available with level 2.\n" + "\n" + ":param entry: the Entry object which should be deleted; note that after\n" " this function is called, that object is unusable any longer\n" " and should be deleted\n" ; @@ -576,12 +594,14 @@ static char __ACL_calc_mask_doc__[] = "Compute the file group class mask.\n" "\n" "The calc_mask() method calculates and sets the permissions \n" - "associated with the ACL_MASK Entry of the ACL.\n" + "associated with the :py:data:`ACL_MASK` Entry of the ACL.\n" "The value of the new permissions is the union of the permissions \n" - "granted by all entries of tag type ACL_GROUP, ACL_GROUP_OBJ, or \n" - "ACL_USER. If the ACL already contains an ACL_MASK entry, its \n" - "permissions are overwritten; if it does not contain an ACL_MASK \n" - "Entry, one is added.\n" + "granted by all entries of tag type :py:data:`ACL_GROUP`, \n" + ":py:data:`ACL_GROUP_OBJ`, or \n" + ":py:data:`ACL_USER`. If the ACL already contains an :py:data:`ACL_MASK`\n" + "entry, its \n" + "permissions are overwritten; if it does not contain an \n" + ":py:data:`ACL_MASK` Entry, one is added.\n" "\n" "The order of existing entries in the ACL is undefined after this \n" "function.\n" @@ -600,13 +620,17 @@ static PyObject* ACL_calc_mask(PyObject *obj, PyObject *args) { } static char __ACL_append_doc__[] = + "append([entry])\n" "Append a new Entry to the ACL and return it.\n" "\n" "This is a convenience function to create a new Entry \n" "and append it to the ACL.\n" "If a parameter of type Entry instance is given, the \n" "entry will be a copy of that one (as if copied with \n" - "Entry.copy()), otherwise, the new entry will be empty.\n" + ":py:func:`Entry.copy`), otherwise, the new entry will be empty.\n" + "\n" + ":rtype: :py:class:`Entry`\n" + ":returns: the newly created entry\n" ; /* Convenience method to create a new Entry */ @@ -621,8 +645,10 @@ static PyObject* ACL_append(PyObject *obj, PyObject *args) { return NULL; } - if (!PyArg_ParseTuple(args, "|O!", &Entry_Type, &oldentry)) + if (!PyArg_ParseTuple(args, "|O!", &Entry_Type, &oldentry)) { + Py_DECREF(newentry); return NULL; + } nret = acl_create_entry(&self->acl, &newentry->entry); if(nret == -1) { @@ -720,30 +746,40 @@ static PyObject* Entry_str(PyObject *obj) { qualifier = 0; } - format = PyBytes_FromString("ACL entry for "); + format = MyString_FromString("ACL entry for "); if(format == NULL) return NULL; - if(tag == ACL_UNDEFINED_TAG) { - kind = PyBytes_FromString("undefined type"); - } else if(tag == ACL_USER_OBJ) { - kind = PyBytes_FromString("the owner"); - } else if(tag == ACL_GROUP_OBJ) { - kind = PyBytes_FromString("the group"); - } else if(tag == ACL_OTHER) { - kind = PyBytes_FromString("the others"); - } else if(tag == ACL_USER) { - kind = PyBytes_FromFormat("user with uid %d", qualifier); - } else if(tag == ACL_GROUP) { - kind = PyBytes_FromFormat("group with gid %d", qualifier); - } else if(tag == ACL_MASK) { - kind = PyBytes_FromString("the mask"); - } else { - kind = PyBytes_FromString("UNKNOWN_TAG_TYPE!"); + switch(tag) { + case ACL_UNDEFINED_TAG: + kind = MyString_FromString("undefined type"); + break; + case ACL_USER_OBJ: + kind = MyString_FromString("the owner"); + break; + case ACL_GROUP_OBJ: + kind = MyString_FromString("the group"); + break; + case ACL_OTHER: + kind = MyString_FromString("the others"); + break; + case ACL_USER: + kind = MyString_FromFormat("user with uid %d", qualifier); + break; + case ACL_GROUP: + kind = MyString_FromFormat("group with gid %d", qualifier); + break; + case ACL_MASK: + kind = MyString_FromString("the mask"); + break; + default: + kind = MyString_FromString("UNKNOWN_TAG_TYPE!"); + break; } - if (kind == NULL) + if (kind == NULL) { + Py_DECREF(format); return NULL; - PyBytes_ConcatAndDel(&format, kind); - Py_DECREF(format); + } + MyString_ConcatAndDel(&format, kind); return format; } @@ -857,6 +893,7 @@ static PyObject* Entry_get_permset(PyObject *obj, void* arg) { ps = (Permset_Object*)p; if(acl_get_permset(self->entry, &ps->permset) == -1) { PyErr_SetFromErrno(PyExc_IOError); + Py_DECREF(p); return NULL; } ps->parent_entry = obj; @@ -883,15 +920,16 @@ static int Entry_set_permset(PyObject* obj, PyObject* value, void* arg) { } static char __Entry_copy_doc__[] = - "Copy an ACL entry.\n" + "copy(src)\n" + "Copies an ACL entry.\n" "\n" "This method sets all the parameters to those of another\n" - "entry, even one of another's ACL\n" - "Parameters:\n" - " - src, instance of type Entry\n" + "entry (either of the same ACL or belonging to another ACL).\n" + "\n" + ":param Entry src: instance of type Entry\n" ; -/* Sets all the entry parameters to another's entry */ +/* Sets all the entry parameters to another entry */ static PyObject* Entry_copy(PyObject *obj, PyObject *args) { Entry_Object *self = (Entry_Object*)obj; Entry_Object *other; @@ -967,11 +1005,11 @@ static PyObject* Permset_str(PyObject *obj) { pstr[0] = get_perm(self->permset, ACL_READ) ? 'r' : '-'; pstr[1] = get_perm(self->permset, ACL_WRITE) ? 'w' : '-'; pstr[2] = get_perm(self->permset, ACL_EXECUTE) ? 'x' : '-'; - return PyBytes_FromStringAndSize(pstr, 3); + return MyString_FromStringAndSize(pstr, 3); } static char __Permset_clear_doc__[] = - "Clear all permissions from the permission set.\n" + "Clears all permissions from the permission set.\n" ; /* Clears all permissions from the permset */ @@ -990,11 +1028,9 @@ static PyObject* Permset_get_right(PyObject *obj, void* arg) { Permset_Object *self = (Permset_Object*) obj; if(get_perm(self->permset, *(acl_perm_t*)arg)) { - Py_INCREF(Py_True); - return Py_True; + Py_RETURN_TRUE; } else { - Py_INCREF(Py_False); - return Py_False; + Py_RETURN_FALSE; } } @@ -1021,19 +1057,17 @@ static int Permset_set_right(PyObject* obj, PyObject* value, void* arg) { } static char __Permset_add_doc__[] = + "add(perm)\n" "Add a permission to the permission set.\n" "\n" - "The add() function adds the permission contained in \n" + "This function adds the permission contained in \n" "the argument perm to the permission set. An attempt \n" "to add a permission that is already contained in the \n" "permission set is not considered an error.\n" "\n" - "Parameters:\n\n" - " - perm: a permission (ACL_WRITE, ACL_READ, ACL_EXECUTE, ...)\n" - "\n" - "Return value: None\n" - "\n" - "Can raise: IOError\n" + ":param perm: a permission (:py:data:`ACL_WRITE`, :py:data:`ACL_READ`,\n" + " :py:data:`ACL_EXECUTE`, ...)\n" + ":raises IOError: in case the argument is not a valid descriptor\n" ; static PyObject* Permset_add(PyObject* obj, PyObject* args) { @@ -1052,17 +1086,17 @@ static PyObject* Permset_add(PyObject* obj, PyObject* args) { } static char __Permset_delete_doc__[] = + "delete(perm)\n" "Delete a permission from the permission set.\n" "\n" - "The delete() function deletes the permission contained in \n" - "the argument perm from the permission set. An attempt \n" + "This function deletes the permission contained in \n" + "the argument perm from the permission set. An attempt \n" "to delete a permission that is not contained in the \n" "permission set is not considered an error.\n" - "Parameters:\n\n" - " - perm a permission (ACL_WRITE, ACL_READ, ACL_EXECUTE, ...)\n" - "Return value: None\n" "\n" - "Can raise: IOError\n" + ":param perm: a permission (:py:data:`ACL_WRITE`, :py:data:`ACL_READ`,\n" + " :py:data:`ACL_EXECUTE`, ...)\n" + ":raises IOError: in case the argument is not a valid descriptor\n" ; static PyObject* Permset_delete(PyObject* obj, PyObject* args) { @@ -1081,15 +1115,16 @@ static PyObject* Permset_delete(PyObject* obj, PyObject* args) { } static char __Permset_test_doc__[] = + "test(perm)\n" "Test if a permission exists in the permission set.\n" "\n" - "The test() function tests if the permission contained in \n" - "the argument perm exits the permission set.\n" - "Parameters:\n\n" - " - perm a permission (ACL_WRITE, ACL_READ, ACL_EXECUTE, ...)\n" - "Return value: Boolean\n" + "The test() function tests if the permission represented by\n" + "the argument perm exists in the permission set.\n" "\n" - "Can raise: IOError\n" + ":param perm: a permission (:py:data:`ACL_WRITE`, :py:data:`ACL_READ`,\n" + " :py:data:`ACL_EXECUTE`, ...)\n" + ":rtype: Boolean\n" + ":raises IOError: in case the argument is not a valid descriptor\n" ; static PyObject* Permset_test(PyObject* obj, PyObject* args) { @@ -1105,11 +1140,9 @@ static PyObject* Permset_test(PyObject* obj, PyObject* args) { return PyErr_SetFromErrno(PyExc_IOError); if(ret) { - Py_INCREF(Py_True); - return Py_True; + Py_RETURN_TRUE; } else { - Py_INCREF(Py_False); - return Py_False; + Py_RETURN_FALSE; } } @@ -1118,24 +1151,25 @@ static PyObject* Permset_test(PyObject* obj, PyObject* args) { static char __ACL_Type_doc__[] = "Type which represents a POSIX ACL\n" "\n" - "Parameters (only one keword parameter should be provided):\n" - " - file=\"...\", meaning create ACL representing\n" - " the access ACL of that file\n" - " - filedef=\"...\", meaning create ACL representing\n" - " the default ACL of that directory\n" - " - fd=, meaning create ACL representing\n" - " the access ACL of that file descriptor\n" - " - text=\"...\", meaning create ACL from a \n" + ".. note:: only one keyword parameter should be provided\n" + "\n" + ":param string file: creates an ACL representing\n" + " the access ACL of the specified file\n" + ":param string filedef: creates an ACL representing\n" + " the default ACL of the given directory\n" + ":param int fd: creates an ACL representing\n" + " the access ACL of the given file descriptor\n" + ":param string text: creates an ACL from a \n" " textual description\n" - " - acl=, meaning create a copy\n" - " of an existing ACL instance\n" - " - mode=, meaning create an ACL from a numeric mode\n" + ":param ACL acl: creates a copy of an existing ACL instance\n" + ":param int mode: creates an ACL from a numeric mode\n" " (e.g. mode=0644) (this is valid only when the C library\n" " provides the acl_from_mode call)\n" "\n" - "If no parameters are passed, create an empty ACL; this\n" + "If no parameters are passed, an empty ACL will be created; this\n" "makes sense only when your OS supports ACL modification\n" - "(i.e. it implements full POSIX.1e support)\n" + "(i.e. it implements full POSIX.1e support), otherwise the ACL won't\n" + "be useful.\n" ; /* ACL type methods */ @@ -1231,21 +1265,21 @@ static char __Entry_tagtype_doc__[] = "The tag type of the current entry\n" "\n" "This is one of:\n" - " - ACL_UNDEFINED_TAG\n" - " - ACL_USER_OBJ\n" - " - ACL_USER\n" - " - ACL_GROUP_OBJ\n" - " - ACL_GROUP\n" - " - ACL_MASK\n" - " - ACL_OTHER\n" + " - :py:data:`ACL_UNDEFINED_TAG`\n" + " - :py:data:`ACL_USER_OBJ`\n" + " - :py:data:`ACL_USER`\n" + " - :py:data:`ACL_GROUP_OBJ`\n" + " - :py:data:`ACL_GROUP`\n" + " - :py:data:`ACL_MASK`\n" + " - :py:data:`ACL_OTHER`\n" ; static char __Entry_qualifier_doc__[] = "The qualifier of the current entry\n" "\n" - "If the tag type is ACL_USER, this should be a user id.\n" - "If the tag type if ACL_GROUP, this should be a group id.\n" - "Else, it doesn't matter.\n" + "If the tag type is :py:data:`ACL_USER`, this should be a user id.\n" + "If the tag type if :py:data:`ACL_GROUP`, this should be a group id.\n" + "Else it doesn't matter.\n" ; static char __Entry_parent_doc__[] = @@ -1277,6 +1311,7 @@ static char __Entry_Type_doc__[] = " >>> e = myACL.append() # another way for doing the same thing\n" "\n" "or by:\n" + "\n" " >>> for entry in myACL:\n" " ... print entry\n" "\n" @@ -1341,27 +1376,30 @@ static PyMethodDef Permset_methods[] = { }; static char __Permset_execute_doc__[] = - "Execute permsission\n" + "Execute permission property\n" "\n" - "This is a convenience method of access; the \n" + "This is a convenience method of retrieving and setting the execute\n" + "permission in the permission set; the \n" "same effect can be achieved using the functions\n" "add(), test(), delete(), and those can take any \n" "permission defined by your platform.\n" ; static char __Permset_read_doc__[] = - "Read permsission\n" + "Read permission property\n" "\n" - "This is a convenience method of access; the \n" + "This is a convenience method of retrieving and setting the read\n" + "permission in the permission set; the \n" "same effect can be achieved using the functions\n" "add(), test(), delete(), and those can take any \n" "permission defined by your platform.\n" ; static char __Permset_write_doc__[] = - "Write permsission\n" + "Write permission property\n" "\n" - "This is a convenience method of access; the \n" + "This is a convenience method of retrieving and setting the write\n" + "permission in the permission set; the \n" "same effect can be achieved using the functions\n" "add(), test(), delete(), and those can take any \n" "permission defined by your platform.\n" @@ -1445,14 +1483,14 @@ static PyTypeObject Permset_Type = { /* Module methods */ static char __deletedef_doc__[] = + "delete_default(path)\n" "Delete the default ACL from a directory.\n" "\n" - "This function deletes the default ACL associated with \n" + "This function deletes the default ACL associated with\n" "a directory (the ACL which will be ANDed with the mode\n" "parameter to the open, creat functions).\n" - "Parameters:\n" - " - a string representing the directory whose default ACL\n" - " should be deleted\n" + "\n" + ":param string path: the directory whose default ACL should be deleted\n" ; /* Deletes the default ACL from a directory */ @@ -1474,11 +1512,11 @@ static PyObject* aclmodule_delete_default(PyObject* obj, PyObject* args) { #ifdef HAVE_LINUX static char __has_extended_doc__[] = - "Check if a file or filehandle has an extended ACL.\n" + "has_extended(item)\n" + "Check if a file or file handle has an extended ACL.\n" "\n" - "Parameter:\n" - " - either a filename or a file-like object or an integer; this\n" - " represents the filesystem object on which to act\n" + ":param item: either a file name or a file-like object or an integer;\n" + " it represents the file-system object on which to act\n" ; /* Check for extended ACL a file or fd */ @@ -1531,11 +1569,12 @@ static PyMethodDef aclmodule_methods[] = { static char __posix1e_doc__[] = "POSIX.1e ACLs manipulation\n" + "==========================\n" "\n" "This module provides support for manipulating POSIX.1e ACLS\n" "\n" "Depending on the operating system support for POSIX.1e, \n" - "the ACL type will have more or less capabilities:\n" + "the ACL type will have more or less capabilities:\n\n" " - level 1, only basic support, you can create\n" " ACLs from files and text descriptions;\n" " once created, the type is immutable\n" @@ -1546,12 +1585,13 @@ static char __posix1e_doc__[] = "to acl_entry_t (the Entry type), acl_permset_t (the Permset type).\n" "\n" "The existence of level 2 support and other extensions can be\n" - "checked by the constants:\n" - " - HAS_ACL_ENTRY for level 2 and the Entry/Permset classes\n" - " - HAS_ACL_FROM_MODE for ACL(mode=...) usage\n" - " - HAS_ACL_CHECK for the ACL().check function\n" - " - HAS_EXTENDED_CHECK for the module-level has_extended function\n" - " - HAS_EQUIV_MODE for the ACL().equiv_mode method\n" + "checked by the constants:\n\n" + " - :py:data:`HAS_ACL_ENTRY` for level 2 and the Entry/Permset classes\n" + " - :py:data:`HAS_ACL_FROM_MODE` for ``ACL(mode=...)`` usage\n" + " - :py:data:`HAS_ACL_CHECK` for the :py:func:`ACL.check` function\n" + " - :py:data:`HAS_EXTENDED_CHECK` for the module-level\n" + " :py:func:`has_extended` function\n" + " - :py:data:`HAS_EQUIV_MODE` for the :py:func:`ACL.equiv_mode` method\n" "\n" "Example:\n" "\n" @@ -1575,6 +1615,52 @@ static char __posix1e_doc__[] = "other::---\n" ">>>\n" "\n" + ".. py:data:: ACL_USER\n\n" + " Denotes a specific user entry in an ACL.\n" + "\n" + ".. py:data:: ACL_USER_OBJ\n\n" + " Denotes the user owner entry in an ACL.\n" + "\n" + ".. py:data:: ACL_GROUP\n\n" + " Denotes the a group entry in an ACL.\n" + "\n" + ".. py:data:: ACL_GROUP_OBJ\n\n" + " Denotes the group owner entry in an ACL.\n" + "\n" + ".. py:data:: ACL_OTHER\n\n" + " Denotes the 'others' entry in an ACL.\n" + "\n" + ".. py:data:: ACL_MASK\n\n" + " Denotes the mask entry in an ACL, representing the maximum\n" + " access granted other users, the owner group and other groups.\n" + "\n" + ".. py:data:: ACL_UNDEFINED_TAG\n\n" + " An undefined tag in an ACL.\n" + "\n" + ".. py:data:: ACL_READ\n\n" + " Read permission in a permission set.\n" + "\n" + ".. py:data:: ACL_WRITE\n\n" + " Write permission in a permission set.\n" + "\n" + ".. py:data:: ACL_EXECUTE\n\n" + " Execute permission in a permission set.\n" + "\n" + ".. py:data:: HAS_ACL_ENTRY\n\n" + " denotes support for level 2 and the Entry/Permset classes\n" + "\n" + ".. py:data:: HAS_ACL_FROM_MODE\n\n" + " denotes support for building an ACL from an octal mode\n" + "\n" + ".. py:data:: HAS_ACL_CHECK\n\n" + " denotes support for extended checks of an ACL's validity\n" + "\n" + ".. py:data:: HAS_EXTENDED_CHECK\n\n" + " denotes support for checking whether an ACL is basic or extended\n" + "\n" + ".. py:data:: HAS_EQUIV_MODE\n\n" + " denotes support for the equiv_mode function\n" + "\n" ; #ifdef IS_PY3K diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..3b2d215 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,250 @@ +# -*- coding: utf-8 -*- +# +# pylibacl documentation build configuration file, created by +# sphinx-quickstart on Sun May 13 01:05:18 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('../')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'pylibacl' +copyright = u'2002-2009, 2012, Iustin Pop' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.5.1' +# The full version, including alpha/beta/rc tags. +release = '0.5.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build', 'html'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +keep_warnings = True + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] +html_static_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True +html_domain_indices = False + +# If false, no index is generated. +#html_use_index = True +html_use_index = False + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True +html_show_sourcelink = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pylibacldoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'pylibacl.tex', u'pylibacl Documentation', + u'Iustin Pop', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'pylibacl', u'pylibacl Documentation', + [u'Iustin Pop'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'pylibacl', u'pylibacl Documentation', + u'Iustin Pop', 'pylibacl', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +autodoc_member_order = 'alphabetical' diff --git a/IMPLEMENTATION b/doc/implementation.rst similarity index 51% rename from IMPLEMENTATION rename to doc/implementation.rst index e065241..54977e4 100644 --- a/IMPLEMENTATION +++ b/doc/implementation.rst @@ -1,3 +1,6 @@ +Implementation details +====================== + Functionality level ------------------- @@ -47,3 +50,60 @@ The POSIX draft has the following stuff (correct me if I'm wrong): - the acl_permset_t can contain acl_perm_t value (ACL_READ, ACL_WRITE, ACL_EXECUTE, ACL_ADD, ACL_DELETE, ...) - functions to manipulate all these, and functions to manipulate files + +Currently supported platforms +----------------------------- + +For any other platforms, volunteers are welcome. + +Linux +~~~~~ + +It needs kernel 2.4 or higher and the libacl library installed (with +development headers, if installing from rpm). This library is available +on all modern distributions. + +The level of compliance is level 2 (see IMPLEMENTATION), plus some extra +functions; and as my development is done on Linux, I try to implement +these extensions when it makes sense. + + +FreeBSD +~~~~~~~ + +The current tested version is 7.0. FreeBSD supports all the standards +functions, but 7.0-RELEASE seems to have some issues regarding the +acl_valid() function when the qualifier of an ACL_USER or ACL_GROUP +entry is the same as the current uid. By my interpretation, this should +be a valid ACL, but FreeBSD declares the ACL invalid. As such, some +unittests fail on FreeBSD. + +Porting to other platforms +-------------------------- + +First, determine if your OS supports the full 28 functions of the +POSIX.1e draft (if so, define HAVE_LEVEL2) or only the first 11 +functions (most common case, meaning only HAVE_LEVEL1). + +If your OS supports only LEVEL1, modify ``setup.py`` as appropriately; +unfortunately, the functionality of the module is quite low. + +If your OS supports LEVEL2, there is a function which you must define: +testing if an acl_permset_t contains a given permission. For example, +under Linux, the acl library defines:: + + int acl_get_perm(acl_permset_t permset_d, acl_perm_t perm); + +under FreeBSD, the library defines ``acl_get_perm_np`` with a similar +syntax. So just see how this is implemented in your platform and either +define a simple macro or a full function with the syntax:: + + static int get_perm(acl_permset_t permset_d, acl_perm_t perm); + +which must return 1 if the permset contains perm and 0 otherwise. + + +.. Local Variables: +.. mode: rst +.. fill-column: 72 +.. End: diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..8eb5db6 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,18 @@ +====================================== + Welcome to pylibacl's documentation! +====================================== + +.. include:: ../README + :start-line: 2 + +Contents +-------- + +.. toctree:: + :maxdepth: 2 + + module.rst + implementation.rst + news.rst + +Also see the :ref:`search`. diff --git a/doc/module.rst b/doc/module.rst new file mode 100644 index 0000000..b5fa851 --- /dev/null +++ b/doc/module.rst @@ -0,0 +1,4 @@ +.. automodule:: posix1e + :members: + :undoc-members: + diff --git a/doc/news.rst b/doc/news.rst new file mode 120000 index 0000000..0fae0f8 --- /dev/null +++ b/doc/news.rst @@ -0,0 +1 @@ +../NEWS \ No newline at end of file diff --git a/pylibacl.egg-info/PKG-INFO b/pylibacl.egg-info/PKG-INFO deleted file mode 100644 index 4ba298e..0000000 --- a/pylibacl.egg-info/PKG-INFO +++ /dev/null @@ -1,12 +0,0 @@ -Metadata-Version: 1.0 -Name: pylibacl -Version: 0.5.0 -Summary: POSIX.1e ACLs for python -Home-page: http://pylibacl.sourceforge.net/ -Author: Iustin Pop -Author-email: iusty@k1024.org -License: LGPL -Description: This is a C extension module for Python which - implements POSIX ACLs manipulation. It is a wrapper on top - of the systems's acl C library - see acl(5). -Platform: UNKNOWN diff --git a/pylibacl.egg-info/SOURCES.txt b/pylibacl.egg-info/SOURCES.txt deleted file mode 100644 index 7d46ac4..0000000 --- a/pylibacl.egg-info/SOURCES.txt +++ /dev/null @@ -1,14 +0,0 @@ -COPYING -IMPLEMENTATION -MANIFEST.in -Makefile -NEWS -PLATFORMS -README -acl.c -setup.cfg -setup.py -pylibacl.egg-info/PKG-INFO -pylibacl.egg-info/SOURCES.txt -pylibacl.egg-info/dependency_links.txt -pylibacl.egg-info/top_level.txt \ No newline at end of file diff --git a/pylibacl.egg-info/dependency_links.txt b/pylibacl.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/pylibacl.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/pylibacl.egg-info/top_level.txt b/pylibacl.egg-info/top_level.txt deleted file mode 100644 index 78cf476..0000000 --- a/pylibacl.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -posix1e diff --git a/setup.cfg b/setup.cfg index cacd252..f8c1ed7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,9 +1,4 @@ [bdist_rpm] release = 1 requires = libacl - -[egg_info] -tag_build = -tag_date = 0 -tag_svn_revision = 0 - +;build_requires = libacl libacl-devel diff --git a/setup.py b/setup.py index 8cf37a2..dbb5f56 100755 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ long_desc = """This is a C extension module for Python which implements POSIX ACLs manipulation. It is a wrapper on top of the systems's acl C library - see acl(5).""" -version = "0.5.0" +version = "0.5.1" setup(name="pylibacl", version=version, diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_acls.py b/tests/test_acls.py new file mode 100644 index 0000000..45cd0ed --- /dev/null +++ b/tests/test_acls.py @@ -0,0 +1,320 @@ +# +# + +"""Unittests for the posix1e module""" + +# Copyright (C) 2002-2009, 2012 Iustin Pop +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + + +import unittest +import os +import tempfile +import sys + +import posix1e +from posix1e import * + +TEST_DIR = os.environ.get("TESTDIR", ".") + +BASIC_ACL_TEXT = "u::rw,g::r,o::-" + +# This is to workaround python 2/3 differences at syntactic level +# (which can't be worked around via if's) +M0500 = 320 # octal 0500 +M0644 = 420 # octal 0644 +M0755 = 493 # octal 755 + +def _skip_test(fn): + """Wrapper to skip a test""" + new_fn = lambda x: None + new_fn.__doc__ = "SKIPPED %s" % fn.__doc__ + return new_fn + + +def has_ext(extension): + """Decorator to skip tests based on platform support""" + if not extension: + return _skip_test + else: + return lambda x: x + + +class aclTest: + """Support functions ACLs""" + + def setUp(self): + """set up function""" + self.rmfiles = [] + self.rmdirs = [] + + def tearDown(self): + """tear down function""" + for fname in self.rmfiles: + os.unlink(fname) + for dname in self.rmdirs: + os.rmdir(dname) + + def _getfile(self): + """create a temp file""" + fh, fname = tempfile.mkstemp(".test", "xattr-", TEST_DIR) + self.rmfiles.append(fname) + return fh, fname + + def _getdir(self): + """create a temp dir""" + dname = tempfile.mkdtemp(".test", "xattr-", TEST_DIR) + self.rmdirs.append(dname) + return dname + + def _getsymlink(self): + """create a symlink""" + fh, fname = self._getfile() + os.close(fh) + os.unlink(fname) + os.symlink(fname + ".non-existent", fname) + return fname + + +class LoadTests(aclTest, unittest.TestCase): + """Load/create tests""" + def testFromFile(self): + """Test loading ACLs from a file""" + _, fname = self._getfile() + acl1 = posix1e.ACL(file=fname) + self.assertTrue(acl1.valid(), "ACL read from file should be valid") + + def testFromDir(self): + """Test loading ACLs from a directory""" + dname = self._getdir() + acl1 = posix1e.ACL(file=dname) + acl2 = posix1e.ACL(filedef=dname) + self.assertTrue(acl1.valid(), + "ACL read from directory should be valid") + # default ACLs might or might not be valid; missing ones are + # not valid, so we don't test acl2 for validity + + def testFromFd(self): + """Test loading ACLs from a file descriptor""" + fd, _ = self._getfile() + acl1 = posix1e.ACL(fd=fd) + self.assertTrue(acl1.valid(), "ACL read from fd should be valid") + + def testFromEmpty(self): + """Test creating an empty ACL""" + acl1 = posix1e.ACL() + self.assertFalse(acl1.valid(), "Empty ACL should not be valid") + + def testFromText(self): + """Test creating an ACL from text""" + acl1 = posix1e.ACL(text=BASIC_ACL_TEXT) + self.assertTrue(acl1.valid(), + "ACL based on standard description should be valid") + +class AclExtensions(aclTest, unittest.TestCase): + """ACL extensions checks""" + + @has_ext(HAS_ACL_FROM_MODE) + def testFromMode(self): + """Test loading ACLs from an octal mode""" + acl1 = posix1e.ACL(mode=M0644) + self.assertTrue(acl1.valid(), + "ACL created via octal mode shoule be valid") + + @has_ext(HAS_ACL_CHECK) + def testAclCheck(self): + """Test the acl_check method""" + acl1 = posix1e.ACL(text=BASIC_ACL_TEXT) + self.assertFalse(acl1.check(), "ACL is not valid") + acl2 = posix1e.ACL() + self.assertTrue(acl2.check(), "Empty ACL should not be valid") + + @has_ext(HAS_EXTENDED_CHECK) + def testExtended(self): + """Test the acl_extended function""" + fd, fname = self._getfile() + basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT) + basic_acl.applyto(fd) + for item in fd, fname: + self.assertFalse(has_extended(item), + "A simple ACL should not be reported as extended") + enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r") + self.assertTrue(enhanced_acl.valid(), + "Failure to build an extended ACL") + enhanced_acl.applyto(fd) + for item in fd, fname: + self.assertTrue(has_extended(item), + "An extended ACL should be reported as such") + + @has_ext(HAS_EQUIV_MODE) + def testEquivMode(self): + """Test the equiv_mode function""" + if HAS_ACL_FROM_MODE: + for mode in M0644, M0755: + acl = posix1e.ACL(mode=mode) + self.assertEqual(acl.equiv_mode(), mode) + acl = posix1e.ACL(text="u::rw,g::r,o::r") + self.assertEqual(acl.equiv_mode(), M0644) + acl = posix1e.ACL(text="u::rx,g::-,o::-") + self.assertEqual(acl.equiv_mode(), M0500) + + +class WriteTests(aclTest, unittest.TestCase): + """Write tests""" + + def testDeleteDefault(self): + """Test removing the default ACL""" + dname = self._getdir() + posix1e.delete_default(dname) + + def testReapply(self): + """Test re-applying an ACL""" + fd, fname = self._getfile() + acl1 = posix1e.ACL(fd=fd) + acl1.applyto(fd) + acl1.applyto(fname) + dname = self._getdir() + acl2 = posix1e.ACL(file=fname) + acl2.applyto(dname) + + +class ModificationTests(aclTest, unittest.TestCase): + """ACL modification tests""" + + def checkRef(self, obj): + """Checks if a given obj has a 'sane' refcount""" + ref_cnt = sys.getrefcount(obj) + # FIXME: hardcoded value for the max ref count... but I've + # seen it overflow on bad reference counting, so it's better + # to be safe + if ref_cnt < 2 or ref_cnt > 1024: + self.fail("Wrong reference count, expected 2-1024 and got %d" % + ref_cnt) + + def testStr(self): + """Test str() of an ACL.""" + acl = posix1e.ACL() + str_acl = str(acl) + self.checkRef(str_acl) + + @has_ext(HAS_ACL_ENTRY) + def testAppend(self): + """Test append a new Entry to the ACL""" + acl = posix1e.ACL() + e = acl.append() + e.tag_type = posix1e.ACL_OTHER + acl.calc_mask() + str_format = str(e) + self.checkRef(str_format) + + @has_ext(HAS_ACL_ENTRY) + def testDelete(self): + """Test delete Entry from the ACL""" + acl = posix1e.ACL() + e = acl.append() + e.tag_type = posix1e.ACL_OTHER + acl.calc_mask() + acl.delete_entry(e) + acl.calc_mask() + + @has_ext(HAS_ACL_ENTRY) + def testDoubleEntries(self): + """Test double entries""" + acl = posix1e.ACL(text=BASIC_ACL_TEXT) + self.assertTrue(acl.valid(), "ACL is not valid") + for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ, + posix1e.ACL_OTHER): + e = acl.append() + e.tag_type = tag_type + e.permset.clear() + self.assertFalse(acl.valid(), + "ACL containing duplicate entries should not be valid") + acl.delete_entry(e) + + @has_ext(HAS_ACL_ENTRY) + def testMultipleGoodEntries(self): + """Test multiple valid entries""" + acl = posix1e.ACL(text=BASIC_ACL_TEXT) + self.assertTrue(acl.valid(), "ACL is not valid") + for tag_type in (posix1e.ACL_USER, + posix1e.ACL_GROUP): + for obj_id in range(5): + e = acl.append() + e.tag_type = tag_type + e.qualifier = obj_id + e.permset.clear() + acl.calc_mask() + self.assertTrue(acl.valid(), + "ACL should be able to hold multiple" + " user/group entries") + + @has_ext(HAS_ACL_ENTRY) + def testMultipleBadEntries(self): + """Test multiple invalid entries""" + acl = posix1e.ACL(text=BASIC_ACL_TEXT) + self.assertTrue(acl.valid(), "ACL built from standard description" + " should be valid") + for tag_type in (posix1e.ACL_USER, + posix1e.ACL_GROUP): + e1 = acl.append() + e1.tag_type = tag_type + e1.qualifier = 0 + e1.permset.clear() + acl.calc_mask() + self.assertTrue(acl.valid(), "ACL should be able to add a" + " user/group entry") + e2 = acl.append() + e2.tag_type = tag_type + e2.qualifier = 0 + e2.permset.clear() + acl.calc_mask() + self.assertFalse(acl.valid(), "ACL should not validate when" + " containing two duplicate entries") + acl.delete_entry(e1) + acl.delete_entry(e2) + + @has_ext(HAS_ACL_ENTRY) + def testPermset(self): + """Test permissions""" + acl = posix1e.ACL() + e = acl.append() + ps = e.permset + ps.clear() + str_ps = str(ps) + self.checkRef(str_ps) + pmap = { + posix1e.ACL_READ: "read", + posix1e.ACL_WRITE: "write", + posix1e.ACL_EXECUTE: "execute", + } + for perm in pmap: + str_ps = str(ps) + self.checkRef(str_ps) + self.assertFalse(ps.test(perm), "Empty permission set should not" + " have permission '%s'" % pmap[perm]) + ps.add(perm) + self.assertTrue(ps.test(perm), "Permission '%s' should exist" + " after addition" % pmap[perm]) + str_ps = str(ps) + self.checkRef(str_ps) + ps.delete(perm) + self.assertFalse(ps.test(perm), "Permission '%s' should not exist" + " after deletion" % pmap[perm]) + + +if __name__ == "__main__": + unittest.main() -- 2.39.2