From 98e3144bc811fba0099026d0cb31af36e73e6297 Mon Sep 17 00:00:00 2001
From: Iustin Pop <iusty@k1024.org>
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 `<git://git.k1024.org/pylibacl.git>`_ 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, <iusty@k1024.org>
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 <iusty@k1024.org>
+    Copyright (C) 2002-2009, 2012 Iustin Pop <iusty@k1024.org>
 
     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=<int>, 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=<ACL instance>, meaning create a copy\n"
-    "    of an existing ACL instance\n"
-    "  - mode=<int>, 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
+# "<project> v<release> 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 <link> 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 <iusty@k1024.org>
+#
+#  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.5