From 489209a22aac2577e22197e373ba52b7d8c316e7 Mon Sep 17 00:00:00 2001 From: Iustin Pop Date: Thu, 14 Nov 2019 12:24:49 +0100 Subject: [PATCH] New upstream version 0.5.4 --- MANIFEST.in | 2 +- Makefile | 39 ++-- NEWS | 31 ++- PKG-INFO | 2 +- README => README.rst | 37 +++- acl.c | 104 ++++++---- doc/conf.py | 4 +- doc/index.rst | 2 +- doc/news.rst | 143 +++++++++++++- pylibacl.egg-info/PKG-INFO | 2 +- pylibacl.egg-info/SOURCES.txt | 2 +- setup.cfg | 1 - setup.py | 4 +- test/test_acls.py | 351 ++++++++++++++++++++++++++++++---- 14 files changed, 620 insertions(+), 104 deletions(-) rename README => README.rst (54%) mode change 120000 => 100644 doc/news.rst diff --git a/MANIFEST.in b/MANIFEST.in index f570711..f173282 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,7 @@ include COPYING include Makefile include NEWS -include README +include README.rst include acl.c include setup.cfg include doc/conf.py diff --git a/Makefile b/Makefile index 64e8747..3e76944 100644 --- a/Makefile +++ b/Makefile @@ -1,38 +1,49 @@ +PYTHON = python3 SPHINXOPTS = -W -SPHINXBUILD = sphinx-build +SPHINXBUILD = $(PYTHON) -m sphinx 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 +RSTFILES = doc/index.rst doc/module.rst NEWS README.rst doc/conf.py all: doc test $(MODNAME): acl.c - ./setup.py build_ext --inplace + $(PYTHON) ./setup.py build_ext --inplace -$(DOCHTML)/index.html: $(MODNAME) $(RSTFILES) +$(DOCHTML)/index.html: $(MODNAME) $(RSTFILES) acl.c $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(DOCHTML) touch $@ doc: $(DOCHTML)/index.html dist: - fakeroot ./setup.py sdist + fakeroot $(PYTHON) ./setup.py sdist test: - @for ver in 2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3 3.4; do \ - if type python$$ver >/dev/null; then \ - echo Testing with python$$ver; \ - python$$ver ./setup.py test -q; \ + @for ver in 2.7 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7; do \ + for flavour in "" "-dbg"; do \ + if type python$$ver$$flavour >/dev/null; then \ + echo Testing with python$$ver$$flavour; \ + python$$ver$$flavour ./setup.py test -q; \ + fi; \ + done; \ + done; \ + for pp in pypy pypy3; do \ + if type $$pp >/dev/null; then \ + echo Testing with $$pp; \ + $$pp ./setup.py test -q; \ fi; \ done - @if type pypy >/dev/null; then \ - echo Testing with pypy; \ - pypy ./setup.py test -q; \ - fi + +coverage: + $(MAKE) clean + $(MAKE) test CFLAGS="-coverage" + lcov --capture --directory . --output-file coverage.info + genhtml coverage.info --output-directory out clean: rm -rf $(DOCHTML) $(DOCTREES) @@ -40,4 +51,4 @@ clean: rm -f *.so rm -rf build -.PHONY: doc test clean dist +.PHONY: doc test clean dist coverage diff --git a/NEWS b/NEWS index 7f9108f..1567ec1 100644 --- a/NEWS +++ b/NEWS @@ -1,9 +1,28 @@ News ==== +Version 0.5.4 +------------- + +*released Thu, 14 Nov 2019* + +Maintenance release: + +- Switch build system to Python 3 by default (can be overridden if + needed). +- Internal improvements for better cpychecker support. +- Fix compatibility with PyPy. +- Test improvements (both local and on Travis), testing more variations + (debug, PyPy). +- Improve test coverage, and allow gathering test coverage results. +- Drop support (well, drop testing) for Python lower than 2.7. +- Minor documentation improvements (closes #9, #12). + Version 0.5.3 ------------- +*released Thu, 30 Apr 2015* + FreeBSD fixes: - Enable all FreeBSD versions after 7.x at level 2 (thanks to Garrett @@ -15,11 +34,15 @@ FreeBSD fixes: Version 0.5.2 ------------- +*released Sat, 24 May 2014* + No visible changes release: just fix tests when running under pypy. Version 0.5.1 ------------- +*released Sun, 13 May 2012* + 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 @@ -36,16 +59,20 @@ SourceForge to GitHub. Version 0.5 ----------- +*released Sun, 27 Dec 2009* + Added support for Python 3.x and improved support for Unicode filenames. Version 0.4 ----------- +*released Sat, 28 Jun 2008* + License ~~~~~~~ Starting with this version, pylibacl is licensed under LGPL 2.1, -Febryary 1999 or any later versions (see README and COPYING). +Febryary 1999 or any later versions (see README.rst and COPYING). Linux support ~~~~~~~~~~~~~ @@ -97,6 +124,8 @@ Many functions have now unittests, which is a good thing. Version 0.3 ----------- +*released Sun, 21 Oct 2007* + Linux support ~~~~~~~~~~~~~ diff --git a/PKG-INFO b/PKG-INFO index 15fd781..8781e00 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: pylibacl -Version: 0.5.3 +Version: 0.5.4 Summary: POSIX.1e ACLs for python Home-page: http://pylibacl.k1024.org/ Author: Iustin Pop diff --git a/README b/README.rst similarity index 54% rename from README rename to README.rst index 7d1934f..53c09b7 100644 --- a/README +++ b/README.rst @@ -1,14 +1,14 @@ pylibacl ======== -This is a Python 2.4+ extension module allows you to manipulate the +This is a Python 2.7+ extension module allows you to manipulate the POSIX.1e Access Control Lists present in some OS/file-systems combinations. -Downloads: go to http://pylibacl.k1024.org/downloads. Latest -version is 0.5.3. The source repository is either -at ``_ or -at https://github.com/iustin/pylibacl. +Downloads: go to http://pylibacl.k1024.org/downloads. Latest version +is 0.5.4. The source repository is either at +https://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. @@ -22,13 +22,32 @@ 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. -- Python 2.4 or newer -- operating system: +- Python 2.7 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 + ACLs turned on, either as a compile or mount option. + - FreeBSD 7.0 or newer. +- The sphinx python module, for your python version, if building the + documentation. + +Note: to build from source, by default, Python 3 is needed. It can +still be built with Python 2, by calling `make PYTHON=python2`. + +FreeBSD ++++++++ + +Note that on FreeBSD, ACLs are not enabled by default (at least on UFS +file systems). To enable them, run `tunefs -a enabled` on the file +system in question (after mounting it read-only). Then install: + +- pkg install py36-setuptools py36-sphinx + +or: + +- pkg install py37-setuptools + License ------- diff --git a/acl.c b/acl.c index 0aba1a7..f13a5c1 100644 --- a/acl.c +++ b/acl.c @@ -1,7 +1,7 @@ /* posix1e - a python module exposing the posix acl functions - Copyright (C) 2002-2009, 2012, 2014 Iustin Pop + Copyright (C) 2002-2009, 2012, 2014, 2015 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,7 +45,6 @@ #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 @@ -55,19 +54,34 @@ #define PyBytes_FromStringAndSize PyString_FromStringAndSize #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 +#endif -/* Python 2.6 already defines Py_TYPE */ -#ifndef Py_TYPE -#define Py_TYPE(o) (((PyObject*)(o))->ob_type) +/* Used for cpychecker: */ +/* The checker automatically defines this preprocessor name when creating + the custom attribute: */ +#if defined(WITH_CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF_ATTRIBUTE) +#define CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF(typename) \ + __attribute__((cpychecker_type_object_for_typedef(typename))) +#else +/* This handles the case where we're compiling with a "vanilla" + compiler that doesn't supply this attribute: */ +#define CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF(typename) #endif + +/* The checker automatically defines this preprocessor name when creating + the custom attribute: */ +#if defined(WITH_CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION_ATTRIBUTE) + #define CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION \ +__attribute__((cpychecker_negative_result_sets_exception)) + #else + #define CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION #endif -static PyTypeObject ACL_Type; +static PyTypeObject ACL_Type + CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF("ACL_Object"); static PyObject* ACL_applyto(PyObject* obj, PyObject* args); static PyObject* ACL_valid(PyObject* obj, PyObject* args); @@ -77,8 +91,10 @@ static PyObject* ACL_set_state(PyObject *obj, PyObject* args); #endif #ifdef HAVE_LEVEL2 -static PyTypeObject Entry_Type; -static PyTypeObject Permset_Type; +static PyTypeObject Entry_Type + CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF("Entry_Object"); +static PyTypeObject Permset_Type + CPYCHECKER_TYPE_OBJECT_FOR_TYPEDEF("Permset_Object"); static PyObject* Permset_new(PyTypeObject* type, PyObject* args, PyObject *keywds); #endif @@ -134,8 +150,8 @@ static int ACL_init(PyObject* obj, PyObject* args, PyObject *keywds) { #ifdef HAVE_LINUX static char *kwlist[] = { "file", "fd", "text", "acl", "filedef", "mode", NULL }; - char *format = "|etisO!sH"; - mode_t mode = 0; + char *format = "|etisO!si"; + int mode = -1; #else static char *kwlist[] = { "file", "fd", "text", "acl", "filedef", NULL }; char *format = "|etisO!s"; @@ -177,7 +193,7 @@ static int ACL_init(PyObject* obj, PyObject* args, PyObject *keywds) { else if(filedef != NULL) self->acl = acl_get_file(filedef, ACL_TYPE_DEFAULT); #ifdef HAVE_LINUX - else if(PyMapping_HasKeyString(keywds, kwlist[5])) + else if(mode != -1) self->acl = acl_from_mode(mode); #endif else @@ -343,7 +359,8 @@ static PyObject* ACL_richcompare(PyObject* o1, PyObject* o2, int op) { ret = n == 1 ? Py_True : Py_False; break; default: - ret = Py_NotImplemented; + PyErr_SetString(PyExc_TypeError, "ACLs are not orderable"); + return NULL; } Py_INCREF(ret); return ret; @@ -371,12 +388,14 @@ static PyObject* ACL_equiv_mode(PyObject* obj, PyObject* args) { } #endif +#ifndef IS_PY3K /* Implementation of the compare for ACLs */ static int ACL_nocmp(PyObject* o1, PyObject* o2) { PyErr_SetString(PyExc_TypeError, "cannot compare ACLs using cmp()"); return -1; } +#endif /* Custom methods */ static char __applyto_doc__[] = @@ -680,27 +699,31 @@ typedef struct { }; } tag_qual; +/* Pre-declaring the function is more friendly to cpychecker, sigh. */ +static int get_tag_qualifier(acl_entry_t entry, tag_qual *tq) + CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; + /* Helper function to get the tag and qualifier of an Entry at the same time. This is "needed" because the acl_get_qualifier function returns a pointer to different types, based on the tag value, and thus it's not straightforward to get the right type. - It sets a Python exception if an error occurs, and return 0 in this - case. If successful, the tag is set to the tag type, and the + It sets a Python exception if an error occurs, and returns -1 in + this case. If successful, the tag is set to the tag type, the qualifier (if any) to either the uid or the gid entry in the - tag_qual structure. + tag_qual structure, and the return value is 0. */ -int get_tag_qualifier(acl_entry_t entry, tag_qual *tq) { +static int get_tag_qualifier(acl_entry_t entry, tag_qual *tq) { void *p; if(acl_get_tag_type(entry, &tq->tag) == -1) { PyErr_SetFromErrno(PyExc_IOError); - return 0; + return -1; } if (tq->tag == ACL_USER || tq->tag == ACL_GROUP) { if((p = acl_get_qualifier(entry)) == NULL) { PyErr_SetFromErrno(PyExc_IOError); - return 0; + return -1; } if (tq->tag == ACL_USER) { tq->uid = *(uid_t*)p; @@ -709,7 +732,7 @@ int get_tag_qualifier(acl_entry_t entry, tag_qual *tq) { } acl_free(p); } - return 1; + return 0; } /* Creation of a new Entry instance */ @@ -769,7 +792,7 @@ static PyObject* Entry_str(PyObject *obj) { Entry_Object *self = (Entry_Object*) obj; tag_qual tq; - if(!get_tag_qualifier(self->entry, &tq)) { + if(get_tag_qualifier(self->entry, &tq) < 0) { return NULL; } @@ -810,8 +833,15 @@ static PyObject* Entry_str(PyObject *obj) { Py_DECREF(format); return NULL; } - MyString_ConcatAndDel(&format, kind); +#ifdef IS_PY3K + PyObject *ret = PyUnicode_Concat(format, kind); + Py_DECREF(format); + Py_DECREF(kind); + return ret; +#else + PyString_ConcatAndDel(&format, kind); return format; +#endif } /* Sets the tag type of the entry */ @@ -930,7 +960,7 @@ static PyObject* Entry_get_qualifier(PyObject *obj, void* arg) { PyErr_SetString(PyExc_AttributeError, "entry attribute"); return NULL; } - if(!get_tag_qualifier(self->entry, &tq)) { + if(get_tag_qualifier(self->entry, &tq) < 0) { return NULL; } if (tq.tag == ACL_USER) { @@ -1116,8 +1146,8 @@ static int Permset_set_right(PyObject* obj, PyObject* value, void* arg) { int nerr; if(!PyInt_Check(value)) { - PyErr_SetString(PyExc_ValueError, "a maximum of one argument must" - " be passed"); + PyErr_SetString(PyExc_ValueError, "invalid argument, an integer" + " is expected"); return -1; } on = PyInt_AsLong(value); @@ -1230,17 +1260,20 @@ static char __ACL_Type_doc__[] = ".. 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" + " the access ACL of the specified file.\n" ":param string filedef: creates an ACL representing\n" - " the default ACL of the given directory\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" + " the access ACL of the given file descriptor.\n" ":param string text: creates an ACL from a \n" - " textual description\n" - ":param ACL acl: creates a copy of an existing ACL instance\n" + " textual description; note the ACL must be valid, which\n" + " means including a mask for extended ACLs, similar to\n" + " ``setfacl --no-mask``\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" + " (e.g. ``mode=0644``); this is valid only when the C library\n" + " provides the ``acl_from_mode call``, and\n" + " note that no validation is done on the given value.\n" "\n" "If no parameters are passed, an empty ACL will be created; this\n" "makes sense only when your OS supports ACL modification\n" @@ -1288,7 +1321,12 @@ static PyTypeObject ACL_Type = { 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ +#ifdef IS_PY3K + 0, /* formerly tp_compare, in 3.0 deprecated, in + 3.5 tp_as_async */ +#else ACL_nocmp, /* tp_compare */ +#endif 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ diff --git a/doc/conf.py b/doc/conf.py index 5861a43..5083af5 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,9 +48,9 @@ copyright = u'2002-2009, 2012, 2014, 2015, Iustin Pop' # built documents. # # The short X.Y version. -version = '0.5.3' +version = '0.5.4' # The full version, including alpha/beta/rc tags. -release = '0.5.3' +release = '0.5.4' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/index.rst b/doc/index.rst index 8eb5db6..5581815 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -2,7 +2,7 @@ Welcome to pylibacl's documentation! ====================================== -.. include:: ../README +.. include:: ../README.rst :start-line: 2 Contents diff --git a/doc/news.rst b/doc/news.rst deleted file mode 120000 index 0fae0f8..0000000 --- a/doc/news.rst +++ /dev/null @@ -1 +0,0 @@ -../NEWS \ No newline at end of file diff --git a/doc/news.rst b/doc/news.rst new file mode 100644 index 0000000..1567ec1 --- /dev/null +++ b/doc/news.rst @@ -0,0 +1,142 @@ +News +==== + +Version 0.5.4 +------------- + +*released Thu, 14 Nov 2019* + +Maintenance release: + +- Switch build system to Python 3 by default (can be overridden if + needed). +- Internal improvements for better cpychecker support. +- Fix compatibility with PyPy. +- Test improvements (both local and on Travis), testing more variations + (debug, PyPy). +- Improve test coverage, and allow gathering test coverage results. +- Drop support (well, drop testing) for Python lower than 2.7. +- Minor documentation improvements (closes #9, #12). + +Version 0.5.3 +------------- + +*released Thu, 30 Apr 2015* + +FreeBSD fixes: + +- Enable all FreeBSD versions after 7.x at level 2 (thanks to Garrett + Cooper). +- Make test suite pass under FreeBSD, which has a stricter behaviour + with regards to invalid ACLs (which we do exercise in the test suite), + thanks again to Garret for the bug reports. + +Version 0.5.2 +------------- + +*released Sat, 24 May 2014* + +No visible changes release: just fix tests when running under pypy. + +Version 0.5.1 +------------- + +*released Sun, 13 May 2012* + +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 +----------- + +*released Sun, 27 Dec 2009* + +Added support for Python 3.x and improved support for Unicode filenames. + +Version 0.4 +----------- + +*released Sat, 28 Jun 2008* + +License +~~~~~~~ + +Starting with this version, pylibacl is licensed under LGPL 2.1, +Febryary 1999 or any later versions (see README.rst and COPYING). + +Linux support +~~~~~~~~~~~~~ + +A few more Linux-specific functions: + +- add the ACL.equiv_mode() method, which will return the equivalent + octal mode if this is a basic ACL and raise an IOError exception + otherwise + +- add the acl_extended(...) function, which will check if an fd or path + 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 +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: + +- HAS_ACL_FROM_MODE, denoting whether the ACL constructor supports the + mode=0xxx parameter + +- HAS_ACL_CHECK, denoting whether ACL instances support the check() + method + +- HAS_ACL_ENTRY, denoting whether ACL manipulation is possible and the + Entry and Permset classes are available + +- HAS_EXTENEDED_CHECK, denoting whether the acl_extended function is + supported + +- HAS_EQUIV_MODE, denoting whether ACL instances support the + equiv_mode() method + +Internals +~~~~~~~~~ + +Many functions have now unittests, which is a good thing. + + +Version 0.3 +----------- + +*released Sun, 21 Oct 2007* + +Linux support +~~~~~~~~~~~~~ + +Under Linux, implement more functions from libacl: + +- add ACL(mode=...), implementing acl_from_mode +- 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/pylibacl.egg-info/PKG-INFO b/pylibacl.egg-info/PKG-INFO index 15fd781..8781e00 100644 --- a/pylibacl.egg-info/PKG-INFO +++ b/pylibacl.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: pylibacl -Version: 0.5.3 +Version: 0.5.4 Summary: POSIX.1e ACLs for python Home-page: http://pylibacl.k1024.org/ Author: Iustin Pop diff --git a/pylibacl.egg-info/SOURCES.txt b/pylibacl.egg-info/SOURCES.txt index b73697b..9f01a8e 100644 --- a/pylibacl.egg-info/SOURCES.txt +++ b/pylibacl.egg-info/SOURCES.txt @@ -2,7 +2,7 @@ COPYING MANIFEST.in Makefile NEWS -README +README.rst acl.c setup.cfg setup.py diff --git a/setup.cfg b/setup.cfg index cacd252..f078241 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,5 +5,4 @@ requires = libacl [egg_info] tag_build = tag_date = 0 -tag_svn_revision = 0 diff --git a/setup.py b/setup.py index aaaee63..bdc5c2d 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import os from setuptools import setup, Extension @@ -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.3" +version = "0.5.4" setup(name="pylibacl", version=version, diff --git a/test/test_acls.py b/test/test_acls.py index 8eb3e4d..c86477f 100644 --- a/test/test_acls.py +++ b/test/test_acls.py @@ -28,10 +28,16 @@ import sys import platform import re import errno +import operator import posix1e from posix1e import * +try: + import __pypy__ +except ImportError: + __pypy__ = None + TEST_DIR = os.environ.get("TEST_DIR", ".") BASIC_ACL_TEXT = "u::rw,g::r,o::-" @@ -42,22 +48,16 @@ M0500 = 320 # octal 0500 M0644 = 420 # octal 0644 M0755 = 493 # octal 755 -# Check if running under Python 3 -IS_PY_3K = sys.hexversion >= 0x03000000 - -def _skip_test(fn): - """Wrapper to skip a test""" - new_fn = lambda x: None - new_fn.__doc__ = "SKIPPED %s" % fn.__doc__ - return new_fn +# Permset permission information +PERMSETS = { + posix1e.ACL_READ: ("read", posix1e.Permset.read), + posix1e.ACL_WRITE: ("write", posix1e.Permset.write), + posix1e.ACL_EXECUTE: ("execute", posix1e.Permset.execute), + } -def has_ext(extension): - """Decorator to skip tests based on platform support""" - if not extension: - return _skip_test - else: - return lambda x: x +# Check if running under Python 3 +IS_PY_3K = sys.hexversion >= 0x03000000 def ignore_ioerror(errnum, fn, *args, **kwargs): """Call a function while ignoring some IOErrors. @@ -74,6 +74,14 @@ def ignore_ioerror(errnum, fn, *args, **kwargs): return raise +def encode(s): + """Encode a string if needed (under Python 3)""" + if IS_PY_3K: + return s.encode() + else: + return s + + class aclTest: """Support functions ACLs""" @@ -145,17 +153,38 @@ class LoadTests(aclTest, unittest.TestCase): self.assertTrue(acl1.valid(), "ACL based on standard description should be valid") + def testFromACL(self): + """Test creating an ACL from an existing ACL""" + acl1 = posix1e.ACL() + acl2 = posix1e.ACL(acl=acl1) + + def testInvalidCreationParams(self): + """Test that creating an ACL from multiple objects fails""" + fd, _ = self._getfile() + self.assertRaises(ValueError, posix1e.ACL, text=BASIC_ACL_TEXT, fd=fd) + + def testInvalidValueCreation(self): + """Test that creating an ACL from wrong specification fails""" + self.assertRaises(EnvironmentError, posix1e.ACL, text="foobar") + self.assertRaises(TypeError, posix1e.ACL, foo="bar") + + def testDoubleInit(self): + acl1 = posix1e.ACL(text=BASIC_ACL_TEXT) + self.assertTrue(acl1.valid()) + acl1.__init__(text=BASIC_ACL_TEXT) + self.assertTrue(acl1.valid()) + class AclExtensions(aclTest, unittest.TestCase): """ACL extensions checks""" - @has_ext(HAS_ACL_FROM_MODE) + @unittest.skipUnless(HAS_ACL_FROM_MODE, "Missing 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) + @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported") def testAclCheck(self): """Test the acl_check method""" acl1 = posix1e.ACL(text=BASIC_ACL_TEXT) @@ -163,7 +192,7 @@ class AclExtensions(aclTest, unittest.TestCase): acl2 = posix1e.ACL() self.assertTrue(acl2.check(), "Empty ACL should not be valid") - @has_ext(HAS_EXTENDED_CHECK) + @unittest.skipUnless(HAS_EXTENDED_CHECK, "Extended ACL check not supported") def testExtended(self): """Test the acl_extended function""" fd, fname = self._getfile() @@ -180,7 +209,12 @@ class AclExtensions(aclTest, unittest.TestCase): self.assertTrue(has_extended(item), "An extended ACL should be reported as such") - @has_ext(HAS_EQUIV_MODE) + @unittest.skipUnless(HAS_EXTENDED_CHECK, "Extended ACL check not supported") + def testExtendedArgHandling(self): + self.assertRaises(TypeError, has_extended) + self.assertRaises(TypeError, has_extended, object()) + + @unittest.skipUnless(HAS_EQUIV_MODE, "equiv_mode not supported") def testEquivMode(self): """Test the equiv_mode function""" if HAS_ACL_FROM_MODE: @@ -192,6 +226,51 @@ class AclExtensions(aclTest, unittest.TestCase): acl = posix1e.ACL(text="u::rx,g::-,o::-") self.assertEqual(acl.equiv_mode(), M0500) + @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported") + def testToAnyText(self): + acl = posix1e.ACL(text=BASIC_ACL_TEXT) + self.assertIn(encode("u::"), + acl.to_any_text(options=posix1e.TEXT_ABBREVIATE)) + self.assertIn(encode("user::"), acl.to_any_text()) + + @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported") + def testToAnyTextWrongArgs(self): + acl = posix1e.ACL(text=BASIC_ACL_TEXT) + self.assertRaises(TypeError, acl.to_any_text, foo="bar") + + + @unittest.skipUnless(HAS_ACL_CHECK, "ACL check not supported") + def testRichCompare(self): + acl1 = posix1e.ACL(text="u::rw,g::r,o::r") + acl2 = posix1e.ACL(acl=acl1) + acl3 = posix1e.ACL(text="u::rw,g::rw,o::r") + self.assertEqual(acl1, acl2) + self.assertNotEqual(acl1, acl3) + self.assertRaises(TypeError, operator.lt, acl1, acl2) + self.assertRaises(TypeError, operator.ge, acl1, acl3) + self.assertTrue(acl1 != True) + self.assertFalse(acl1 == 1) + self.assertRaises(TypeError, operator.gt, acl1, True) + + @unittest.skipUnless(hasattr(posix1e.ACL, "__cmp__"), "__cmp__ is missing") + @unittest.skipUnless(__pypy__ is None, "Disabled under pypy") + def testCmp(self): + acl1 = posix1e.ACL() + self.assertRaises(TypeError, acl1.__cmp__, acl1) + + def testApplyToWithWrongObject(self): + acl1 = posix1e.ACL(text=BASIC_ACL_TEXT) + self.assertTrue(acl1.valid()) + self.assertRaises(TypeError, acl1.applyto, object()) + self.assertRaises(TypeError, acl1.applyto, object(), object()) + + @unittest.skipUnless(HAS_ACL_ENTRY, "ACL entries not supported") + def testAclIterator(self): + acl = posix1e.ACL(text=BASIC_ACL_TEXT) + #self.assertEqual(len(acl), 3) + for entry in acl: + self.assertIs(entry.parent, acl) + class WriteTests(aclTest, unittest.TestCase): """Write tests""" @@ -201,6 +280,10 @@ class WriteTests(aclTest, unittest.TestCase): dname = self._getdir() posix1e.delete_default(dname) + @unittest.skipUnless(__pypy__ is None, "Disabled under pypy") + def testDeleteDefaultWrongArg(self): + self.assertRaises(TypeError, posix1e.delete_default, object()) + def testReapply(self): """Test re-applying an ACL""" fd, fname = self._getfile() @@ -212,6 +295,7 @@ class WriteTests(aclTest, unittest.TestCase): acl2.applyto(dname) +@unittest.skipUnless(HAS_ACL_ENTRY, "ACL entries not supported") class ModificationTests(aclTest, unittest.TestCase): """ACL modification tests""" @@ -233,7 +317,6 @@ class ModificationTests(aclTest, unittest.TestCase): 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() @@ -242,8 +325,27 @@ class ModificationTests(aclTest, unittest.TestCase): ignore_ioerror(errno.EINVAL, acl.calc_mask) str_format = str(e) self.checkRef(str_format) + e2 = acl.append(e) + ignore_ioerror(errno.EINVAL, acl.calc_mask) + self.assertFalse(acl.valid()) + + def testWrongAppend(self): + """Test append a new Entry to the ACL based on wrong object type""" + acl = posix1e.ACL() + self.assertRaises(TypeError, acl.append, object()) + + def testEntryCreation(self): + acl = posix1e.ACL() + e = posix1e.Entry(acl) + ignore_ioerror(errno.EINVAL, acl.calc_mask) + str_format = str(e) + self.checkRef(str_format) + + def testEntryFailedCreation(self): + # Checks for partial initialisation and deletion on error + # path. + self.assertRaises(TypeError, posix1e.Entry, object()) - @has_ext(HAS_ACL_ENTRY) def testDelete(self): """Test delete Entry from the ACL""" acl = posix1e.ACL() @@ -253,7 +355,36 @@ class ModificationTests(aclTest, unittest.TestCase): acl.delete_entry(e) ignore_ioerror(errno.EINVAL, acl.calc_mask) - @has_ext(HAS_ACL_ENTRY) + def testDoubleDelete(self): + """Test delete Entry from the ACL""" + # This is not entirely valid/correct, since the entry object + # itself is invalid after the first deletion, so we're + # actually testing deleting an invalid object, not a + # non-existing entry... + acl = posix1e.ACL() + e = acl.append() + e.tag_type = posix1e.ACL_OTHER + ignore_ioerror(errno.EINVAL, acl.calc_mask) + acl.delete_entry(e) + ignore_ioerror(errno.EINVAL, acl.calc_mask) + self.assertRaises(EnvironmentError, acl.delete_entry, e) + + # This currently fails as this deletion seems to be accepted :/ + @unittest.skip("Entry deletion is unreliable") + def testDeleteInvalidEntry(self): + """Test delete foreign Entry from the ACL""" + acl1 = posix1e.ACL() + acl2 = posix1e.ACL() + e = acl1.append() + e.tag_type = posix1e.ACL_OTHER + ignore_ioerror(errno.EINVAL, acl1.calc_mask) + self.assertRaises(EnvironmentError, acl2.delete_entry, e) + + def testDeleteInvalidObject(self): + """Test delete a non-Entry from the ACL""" + acl = posix1e.ACL() + self.assertRaises(TypeError, acl.delete_entry, object()) + def testDoubleEntries(self): """Test double entries""" acl = posix1e.ACL(text=BASIC_ACL_TEXT) @@ -268,7 +399,6 @@ class ModificationTests(aclTest, unittest.TestCase): " 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) @@ -285,7 +415,6 @@ class ModificationTests(aclTest, unittest.TestCase): "ACL should be able to hold multiple" " user/group entries") - @has_ext(HAS_ACL_ENTRY) def testMultipleBadEntries(self): """Test multiple invalid entries""" for tag_type in (posix1e.ACL_USER, @@ -312,7 +441,64 @@ class ModificationTests(aclTest, unittest.TestCase): # entry, even though it still exists. ignore_ioerror(errno.EINVAL, acl.delete_entry, e2) - @has_ext(HAS_ACL_ENTRY) + def testCopy(self): + acl = ACL() + e1 = acl.append() + e1.tag_type = ACL_USER + p1 = e1.permset + p1.clear() + p1.read = True + p1.write = True + e2 = acl.append() + e2.tag_type = ACL_GROUP + p2 = e2.permset + p2.clear() + p2.read = True + self.assertFalse(p2.write) + e2.copy(e1) + self.assertTrue(p2.write) + self.assertEqual(e1.tag_type, e2.tag_type) + + def testCopyWrongArg(self): + acl = ACL() + e = acl.append() + self.assertRaises(TypeError, e.copy, object()) + + def testSetPermset(self): + acl = ACL() + e1 = acl.append() + e1.tag_type = ACL_USER + p1 = e1.permset + p1.clear() + p1.read = True + p1.write = True + e2 = acl.append() + e2.tag_type = ACL_GROUP + p2 = e2.permset + p2.clear() + p2.read = True + self.assertFalse(p2.write) + e2.permset = p1 + self.assertTrue(e2.permset.write) + self.assertEqual(e2.tag_type, ACL_GROUP) + + def testSetPermsetWrongArg(self): + acl = ACL() + e = acl.append() + def setter(v): + e.permset = v + self.assertRaises(TypeError, setter, object()) + + def testPermsetCreation(self): + acl = ACL() + e = acl.append() + p1 = e.permset + p2 = Permset(e) + #self.assertEqual(p1, p2) + + def testPermsetCreationWrongArg(self): + self.assertRaises(TypeError, Permset, object()) + def testPermset(self): """Test permissions""" acl = posix1e.ACL() @@ -321,27 +507,65 @@ class ModificationTests(aclTest, unittest.TestCase): 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: + for perm in PERMSETS: str_ps = str(ps) + txt = PERMSETS[perm][0] self.checkRef(str_ps) self.assertFalse(ps.test(perm), "Empty permission set should not" - " have permission '%s'" % pmap[perm]) + " have permission '%s'" % txt) ps.add(perm) self.assertTrue(ps.test(perm), "Permission '%s' should exist" - " after addition" % pmap[perm]) + " after addition" % txt) 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]) + " after deletion" % txt) + def testPermsetViaAccessors(self): + """Test permissions""" + acl = posix1e.ACL() + e = acl.append() + ps = e.permset + ps.clear() + str_ps = str(ps) + self.checkRef(str_ps) + def getter(perm): + return PERMSETS[perm][1].__get__(ps) + def setter(parm, value): + return PERMSETS[perm][1].__set__(ps, value) + for perm in PERMSETS: + str_ps = str(ps) + self.checkRef(str_ps) + txt = PERMSETS[perm][0] + self.assertFalse(getter(perm), "Empty permission set should not" + " have permission '%s'" % txt) + setter(perm, True) + self.assertTrue(ps.test(perm), "Permission '%s' should exist" + " after addition" % txt) + self.assertTrue(getter(perm), "Permission '%s' should exist" + " after addition" % txt) + str_ps = str(ps) + self.checkRef(str_ps) + setter(perm, False) + self.assertFalse(ps.test(perm), "Permission '%s' should not exist" + " after deletion" % txt) + self.assertFalse(getter(perm), "Permission '%s' should not exist" + " after deletion" % txt) - @has_ext(HAS_ACL_ENTRY and IS_PY_3K) + def testPermsetInvalidType(self): + acl = posix1e.ACL() + e = acl.append() + ps = e.permset + ps.clear() + def setter(): + ps.write = object() + self.assertRaises(TypeError, ps.add, "foobar") + self.assertRaises(TypeError, ps.delete, "foobar") + self.assertRaises(TypeError, ps.test, "foobar") + self.assertRaises(ValueError, setter) + + @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3") def testQualifierValues(self): """Tests qualifier correct store/retrieval""" acl = posix1e.ACL() @@ -368,7 +592,7 @@ class ModificationTests(aclTest, unittest.TestCase): fn(str(e), regex) qualifier *= 2 - @has_ext(HAS_ACL_ENTRY and IS_PY_3K) + @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3") def testQualifierOverflow(self): """Tests qualifier overflow handling""" acl = posix1e.ACL() @@ -379,7 +603,7 @@ class ModificationTests(aclTest, unittest.TestCase): with self.assertRaises(OverflowError): e.qualifier = qualifier - @has_ext(HAS_ACL_ENTRY and IS_PY_3K) + @unittest.skipUnless(IS_PY_3K, "Only supported under Python 3") def testNegativeQualifier(self): """Tests negative qualifier handling""" # Note: this presumes that uid_t/gid_t in C are unsigned... @@ -391,6 +615,61 @@ class ModificationTests(aclTest, unittest.TestCase): with self.assertRaises(OverflowError): e.qualifier = qualifier + def testInvalidQualifier(self): + """Tests invalid qualifier handling""" + acl = posix1e.ACL() + e = acl.append() + def set_qual(x): + e.qualifier = x + def del_qual(): + del e.qualifier + self.assertRaises(TypeError, set_qual, object()) + self.assertRaises((TypeError, AttributeError), del_qual) + + def testQualifierOnWrongTag(self): + """Tests qualifier setting on wrong tag""" + acl = posix1e.ACL() + e = acl.append() + e.tag_type = posix1e.ACL_OTHER + def set_qual(x): + e.qualifier = x + def get_qual(): + return e.qualifier + self.assertRaises(TypeError, set_qual, 1) + self.assertRaises(TypeError, get_qual) + + + def testTagTypes(self): + """Tests tag type correct set/get""" + acl = posix1e.ACL() + e = acl.append() + for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP, posix1e.ACL_USER_OBJ, + posix1e.ACL_GROUP_OBJ, posix1e.ACL_MASK, + posix1e.ACL_OTHER]: + e.tag_type = tag + self.assertEqual(e.tag_type, tag) + # check we can show all tag types without breaking + self.assertTrue(str(e)) + + def testInvalidTags(self): + """Tests tag type incorrect set/get""" + acl = posix1e.ACL() + e = acl.append() + def set_tag(x): + e.tag_type = x + self.assertRaises(TypeError, set_tag, object()) + def delete_tag(): + del e.tag_type + # For some reason, PyPy raises AttributeError. Strange... + self.assertRaises((TypeError, AttributeError), delete_tag) + + e.tag_type = posix1e.ACL_USER_OBJ + tag = max([posix1e.ACL_USER, posix1e.ACL_GROUP, posix1e.ACL_USER_OBJ, + posix1e.ACL_GROUP_OBJ, posix1e.ACL_MASK, + posix1e.ACL_OTHER]) + 1 + self.assertRaises(EnvironmentError, set_tag, tag) + # Check tag is still valid. + self.assertEqual(e.tag_type, posix1e.ACL_USER_OBJ) if __name__ == "__main__": unittest.main() -- 2.39.2