pax_global_header00006660000000000000000000000064117536325010014515gustar00rootroot0000000000000052 comment=d87d73ff86da77a453afbba4307827f3af6a0d58 pylibacl-0.5.1/000077500000000000000000000000001175363250100133175ustar00rootroot00000000000000pylibacl-0.5.1/.gitignore000066400000000000000000000001621175363250100153060ustar00rootroot00000000000000/MANIFEST /build /dist /doc/doctrees /doc/html /posix1e.html /posix1e.so /posix1e.txt /pylibacl.egg-info *.py[co] pylibacl-0.5.1/COPYING000066400000000000000000000635041175363250100143620ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! pylibacl-0.5.1/MANIFEST.in000066400000000000000000000002271175363250100150560ustar00rootroot00000000000000include COPYING include IMPLEMENTATION include MANIFEST include Makefile include NEWS include PLATFORMS include README include acl.c include setup.cfg pylibacl-0.5.1/Makefile000066400000000000000000000013741175363250100147640ustar00rootroot00000000000000SPHINXOPTS = -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 $(MODNAME): acl.c ./setup.py build_ext --inplace $(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 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 pylibacl-0.5.1/NEWS000066400000000000000000000046021175363250100140200ustar00rootroot00000000000000News ==== 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: - 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 ----------- 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: pylibacl-0.5.1/README000066400000000000000000000027051175363250100142030ustar00rootroot00000000000000pylibacl ======== This is a Python 2.4+ extension module allows you to manipulate the POSIX.1e Access Control Lists present in some OS/file-systems combinations. Downloads: go to https://github.com/iustin/pylibacl/downloads. Latest version is 0.5.1. The source repository is either at ``_ or at https://github.com/iustin/pylibacl. For any issues, please file bugs at https://github.com/iustin/pylibacl/issues. Requirements ------------ pylibacl has been written and tested on Linux, kernel v2.4 or newer, with XFS filesystems; ext2/ext3 should also work. Since release 0.4.0, FreeBSD 7 also has quite good support. If any other platform implements the POSIX.1e draft, pylibacl can be used. I heard that Solaris does, but I can't test it yet. - Python 2.4 or newer - operating system: - Linux, kernel v2.4 or newer, and the libacl library and development packages (all modern distributions should have this, under various names); also the file-systems you use must have ACLs turned on, either as a compile or mount option - FreeBSD 7.0 or newer License ------- pylibacl is Copyright (C) 2002-2009, 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. pylibacl-0.5.1/acl.c000066400000000000000000001547271175363250100142420ustar00rootroot00000000000000/* posix1e - a python module exposing the posix acl functions Copyright (C) 2002-2009, 2012 Iustin Pop This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #ifdef HAVE_LINUX #include #define get_perm acl_get_perm #elif HAVE_FREEBSD #define get_perm acl_get_perm_np #endif #if PY_MAJOR_VERSION >= 3 #define IS_PY3K #define PyInt_Check(op) PyLong_Check(op) #define PyInt_FromString PyLong_FromString #define PyInt_FromUnicode PyLong_FromUnicode #define PyInt_FromLong PyLong_FromLong #define PyInt_FromSize_t PyLong_FromSize_t #define PyInt_FromSsize_t PyLong_FromSsize_t #define PyInt_AsLong PyLong_AsLong #define PyInt_AsSsize_t PyLong_AsSsize_t #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 #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 /* Python 2.6 already defines Py_TYPE */ #ifndef Py_TYPE #define Py_TYPE(o) (((PyObject*)(o))->ob_type) #endif #endif static PyTypeObject ACL_Type; static PyObject* ACL_applyto(PyObject* obj, PyObject* args); static PyObject* ACL_valid(PyObject* obj, PyObject* args); #ifdef HAVE_ACL_COPY_EXT static PyObject* ACL_get_state(PyObject *obj, PyObject* args); static PyObject* ACL_set_state(PyObject *obj, PyObject* args); #endif #ifdef HAVE_LEVEL2 static PyTypeObject Entry_Type; static PyTypeObject Permset_Type; static PyObject* Permset_new(PyTypeObject* type, PyObject* args, PyObject *keywds); #endif static acl_perm_t holder_ACL_EXECUTE = ACL_EXECUTE; static acl_perm_t holder_ACL_READ = ACL_READ; static acl_perm_t holder_ACL_WRITE = ACL_WRITE; typedef struct { PyObject_HEAD acl_t acl; #ifdef HAVE_LEVEL2 int entry_id; #endif } ACL_Object; #ifdef HAVE_LEVEL2 typedef struct { PyObject_HEAD PyObject *parent_acl; /* The parent acl, so it won't run out on us */ acl_entry_t entry; } Entry_Object; typedef struct { PyObject_HEAD PyObject *parent_entry; /* The parent entry, so it won't run out on us */ acl_permset_t permset; } Permset_Object; #endif /* Creation of a new ACL instance */ static PyObject* ACL_new(PyTypeObject* type, PyObject* args, PyObject *keywds) { PyObject* newacl; newacl = type->tp_alloc(type, 0); if(newacl != NULL) { ((ACL_Object*)newacl)->acl = NULL; #ifdef HAVEL_LEVEL2 ((ACL_Object*)newacl)->entry_id = ACL_FIRST_ENTRY; #endif } return newacl; } /* Initialization of a new ACL instance */ static int ACL_init(PyObject* obj, PyObject* args, PyObject *keywds) { ACL_Object* self = (ACL_Object*) obj; #ifdef HAVE_LINUX static char *kwlist[] = { "file", "fd", "text", "acl", "filedef", "mode", NULL }; char *format = "|etisO!sH"; mode_t mode = 0; #else static char *kwlist[] = { "file", "fd", "text", "acl", "filedef", NULL }; char *format = "|etisO!s"; #endif char *file = NULL; char *filedef = NULL; char *text = NULL; int fd = -1; ACL_Object* thesrc = NULL; if(!PyTuple_Check(args) || PyTuple_Size(args) != 0 || (keywds != NULL && PyDict_Check(keywds) && PyDict_Size(keywds) > 1)) { PyErr_SetString(PyExc_ValueError, "a max of one keyword argument" " must be passed"); return -1; } if(!PyArg_ParseTupleAndKeywords(args, keywds, format, kwlist, NULL, &file, &fd, &text, &ACL_Type, &thesrc, &filedef #ifdef HAVE_LINUX , &mode #endif )) return -1; /* Free the old acl_t without checking for error, we don't * care right now */ if(self->acl != NULL) acl_free(self->acl); if(file != NULL) self->acl = acl_get_file(file, ACL_TYPE_ACCESS); else if(text != NULL) self->acl = acl_from_text(text); else if(fd != -1) self->acl = acl_get_fd(fd); else if(thesrc != NULL) self->acl = acl_dup(thesrc->acl); else if(filedef != NULL) self->acl = acl_get_file(filedef, ACL_TYPE_DEFAULT); #ifdef HAVE_LINUX else if(PyMapping_HasKeyString(keywds, kwlist[5])) self->acl = acl_from_mode(mode); #endif else self->acl = acl_init(0); if(self->acl == NULL) { PyErr_SetFromErrno(PyExc_IOError); return -1; } return 0; } /* Standard type functions */ static void ACL_dealloc(PyObject* obj) { ACL_Object *self = (ACL_Object*) obj; PyObject *err_type, *err_value, *err_traceback; int have_error = PyErr_Occurred() ? 1 : 0; if (have_error) PyErr_Fetch(&err_type, &err_value, &err_traceback); if(self->acl != NULL && acl_free(self->acl) != 0) PyErr_WriteUnraisable(obj); if (have_error) PyErr_Restore(err_type, err_value, err_traceback); PyObject_DEL(self); } /* Converts the acl to a text format */ static PyObject* ACL_str(PyObject *obj) { char *text; ACL_Object *self = (ACL_Object*) obj; PyObject *ret; text = acl_to_text(self->acl, NULL); if(text == NULL) { return PyErr_SetFromErrno(PyExc_IOError); } ret = MyString_FromString(text); if(acl_free(text) != 0) { Py_XDECREF(ret); return PyErr_SetFromErrno(PyExc_IOError); } return ret; } #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.\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" ":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 */ static PyObject* ACL_to_any_text(PyObject *obj, PyObject *args, PyObject *kwds) { char *text; ACL_Object *self = (ACL_Object*) obj; PyObject *ret; const char *arg_prefix = NULL; char arg_separator = '\n'; int arg_options = 0; static char *kwlist[] = {"prefix", "separator", "options", NULL}; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|sci", kwlist, &arg_prefix, &arg_separator, &arg_options)) return NULL; text = acl_to_any_text(self->acl, arg_prefix, arg_separator, arg_options); if(text == NULL) { return PyErr_SetFromErrno(PyExc_IOError); } ret = PyBytes_FromString(text); if(acl_free(text) != 0) { Py_XDECREF(ret); return PyErr_SetFromErrno(PyExc_IOError); } return ret; } 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 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\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" ; /* The acl_check method */ static PyObject* ACL_check(PyObject* obj, PyObject* args) { ACL_Object *self = (ACL_Object*) obj; int result; int eindex; if((result = acl_check(self->acl, &eindex)) == -1) return PyErr_SetFromErrno(PyExc_IOError); if(result == 0) { Py_RETURN_FALSE; } return Py_BuildValue("(ii)", result, eindex); } /* Implementation of the rich compare for ACLs */ static PyObject* ACL_richcompare(PyObject* o1, PyObject* o2, int op) { ACL_Object *acl1, *acl2; int n; PyObject *ret; if(!PyObject_IsInstance(o2, (PyObject*)&ACL_Type)) { if(op == Py_EQ) Py_RETURN_FALSE; if(op == Py_NE) Py_RETURN_TRUE; PyErr_SetString(PyExc_TypeError, "can only compare to an ACL"); return NULL; } acl1 = (ACL_Object*)o1; acl2 = (ACL_Object*)o2; if((n=acl_cmp(acl1->acl, acl2->acl))==-1) return PyErr_SetFromErrno(PyExc_IOError); switch(op) { case Py_EQ: ret = n == 0 ? Py_True : Py_False; break; case Py_NE: ret = n == 1 ? Py_True : Py_False; break; default: ret = Py_NotImplemented; } Py_INCREF(ret); return ret; } static char __equiv_mode_doc__[] = "Return the octal mode the ACL is equivalent to.\n" "\n" "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" ":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 */ static PyObject* ACL_equiv_mode(PyObject* obj, PyObject* args) { ACL_Object *self = (ACL_Object*) obj; mode_t mode; if(acl_equiv_mode(self->acl, &mode) == -1) return PyErr_SetFromErrno(PyExc_IOError); return PyInt_FromLong(mode); } #endif /* 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; } /* Custom methods */ static char __applyto_doc__[] = "applyto(item[, flag=ACL_TYPE_ACCESS])\n" "Apply the ACL to a file or filehandle.\n" "\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" ; /* Applies the ACL to a file */ static PyObject* ACL_applyto(PyObject* obj, PyObject* args) { ACL_Object *self = (ACL_Object*) obj; PyObject *myarg; acl_type_t type = ACL_TYPE_ACCESS; int nret; int fd; if (!PyArg_ParseTuple(args, "O|I", &myarg, &type)) return NULL; if(PyBytes_Check(myarg)) { char *filename = PyBytes_AS_STRING(myarg); nret = acl_set_file(filename, type, self->acl); } else if (PyUnicode_Check(myarg)) { PyObject *o = PyUnicode_AsEncodedString(myarg, Py_FileSystemDefaultEncoding, "strict"); if (o == NULL) return NULL; const char *filename = PyBytes_AS_STRING(o); nret = acl_set_file(filename, type, self->acl); Py_DECREF(o); } else if((fd = PyObject_AsFileDescriptor(myarg)) != -1) { nret = acl_set_fd(fd, self->acl); } else { PyErr_SetString(PyExc_TypeError, "argument 1 must be string, int," " or file-like object"); return 0; } if(nret == -1) { return PyErr_SetFromErrno(PyExc_IOError); } /* Return the result */ Py_INCREF(Py_None); return Py_None; } 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 file-system. More precisely, it checks that:\n" "\n" "The ACL contains exactly one entry with each of the\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 :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\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 */ static PyObject* ACL_valid(PyObject* obj, PyObject* args) { ACL_Object *self = (ACL_Object*) obj; if(acl_valid(self->acl) == -1) { Py_RETURN_FALSE; } else { Py_RETURN_TRUE; } } #ifdef HAVE_ACL_COPY_EXT static PyObject* ACL_get_state(PyObject *obj, PyObject* args) { ACL_Object *self = (ACL_Object*) obj; PyObject *ret; ssize_t size, nsize; char *buf; size = acl_size(self->acl); if(size == -1) return PyErr_SetFromErrno(PyExc_IOError); if((ret = PyBytes_FromStringAndSize(NULL, size)) == NULL) return NULL; buf = PyBytes_AsString(ret); if((nsize = acl_copy_ext(buf, self->acl, size)) == -1) { Py_DECREF(ret); return PyErr_SetFromErrno(PyExc_IOError); } return ret; } static PyObject* ACL_set_state(PyObject *obj, PyObject* args) { ACL_Object *self = (ACL_Object*) obj; const void *buf; int bufsize; acl_t ptr; /* Parse the argument */ if (!PyArg_ParseTuple(args, "s#", &buf, &bufsize)) return NULL; /* Try to import the external representation */ if((ptr = acl_copy_int(buf)) == NULL) return PyErr_SetFromErrno(PyExc_IOError); /* Free the old acl. Should we ignore errors here? */ if(self->acl != NULL) { if(acl_free(self->acl) == -1) return PyErr_SetFromErrno(PyExc_IOError); } self->acl = ptr; /* Return the result */ Py_INCREF(Py_None); return Py_None; } #endif #ifdef HAVE_LEVEL2 /* tp_iter for the ACL type; since it can be iterated only * destructively, the type is its iterator */ static PyObject* ACL_iter(PyObject *obj) { ACL_Object *self = (ACL_Object*)obj; self->entry_id = ACL_FIRST_ENTRY; Py_INCREF(obj); return obj; } /* the tp_iternext function for the ACL type */ static PyObject* ACL_iternext(PyObject *obj) { ACL_Object *self = (ACL_Object*)obj; acl_entry_t the_entry_t; Entry_Object *the_entry_obj; int nerr; nerr = acl_get_entry(self->acl, self->entry_id, &the_entry_t); self->entry_id = ACL_NEXT_ENTRY; if(nerr == -1) return PyErr_SetFromErrno(PyExc_IOError); else if(nerr == 0) { /* Docs says this is not needed */ /*PyErr_SetObject(PyExc_StopIteration, Py_None);*/ return NULL; } the_entry_obj = (Entry_Object*) PyType_GenericNew(&Entry_Type, NULL, NULL); if(the_entry_obj == NULL) return NULL; the_entry_obj->entry = the_entry_t; the_entry_obj->parent_acl = obj; Py_INCREF(obj); /* For the reference we have in entry->parent */ return (PyObject*)the_entry_obj; } static char __ACL_delete_entry_doc__[] = "delete_entry(entry)\n" "Deletes an entry from the ACL.\n" "\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" ; /* Deletes an entry from the ACL */ static PyObject* ACL_delete_entry(PyObject *obj, PyObject *args) { ACL_Object *self = (ACL_Object*)obj; Entry_Object *e; if (!PyArg_ParseTuple(args, "O!", &Entry_Type, &e)) return NULL; if(acl_delete_entry(self->acl, e->entry) == -1) return PyErr_SetFromErrno(PyExc_IOError); /* Return the result */ Py_INCREF(Py_None); return Py_None; } 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 :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 :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" ; /* Updates the mask entry in the ACL */ static PyObject* ACL_calc_mask(PyObject *obj, PyObject *args) { ACL_Object *self = (ACL_Object*)obj; if(acl_calc_mask(&self->acl) == -1) return PyErr_SetFromErrno(PyExc_IOError); /* Return the result */ Py_INCREF(Py_None); return Py_None; } 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" ":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 */ static PyObject* ACL_append(PyObject *obj, PyObject *args) { ACL_Object* self = (ACL_Object*) obj; Entry_Object* newentry; Entry_Object* oldentry = NULL; int nret; newentry = (Entry_Object*)PyType_GenericNew(&Entry_Type, NULL, NULL); if(newentry == NULL) { return NULL; } if (!PyArg_ParseTuple(args, "|O!", &Entry_Type, &oldentry)) { Py_DECREF(newentry); return NULL; } nret = acl_create_entry(&self->acl, &newentry->entry); if(nret == -1) { Py_DECREF(newentry); return PyErr_SetFromErrno(PyExc_IOError); } if(oldentry != NULL) { nret = acl_copy_entry(newentry->entry, oldentry->entry); if(nret == -1) { Py_DECREF(newentry); return PyErr_SetFromErrno(PyExc_IOError); } } newentry->parent_acl = obj; Py_INCREF(obj); return (PyObject*)newentry; } /***** Entry type *****/ /* Creation of a new Entry instance */ static PyObject* Entry_new(PyTypeObject* type, PyObject* args, PyObject *keywds) { PyObject* newentry; newentry = PyType_GenericNew(type, args, keywds); if(newentry != NULL) { ((Entry_Object*)newentry)->entry = NULL; ((Entry_Object*)newentry)->parent_acl = NULL; } return newentry; } /* Initialization of a new Entry instance */ static int Entry_init(PyObject* obj, PyObject* args, PyObject *keywds) { Entry_Object* self = (Entry_Object*) obj; ACL_Object* parent = NULL; if (!PyArg_ParseTuple(args, "O!", &ACL_Type, &parent)) return -1; if(acl_create_entry(&parent->acl, &self->entry) == -1) { PyErr_SetFromErrno(PyExc_IOError); return -1; } self->parent_acl = (PyObject*)parent; Py_INCREF(parent); return 0; } /* Free the Entry instance */ static void Entry_dealloc(PyObject* obj) { Entry_Object *self = (Entry_Object*) obj; PyObject *err_type, *err_value, *err_traceback; int have_error = PyErr_Occurred() ? 1 : 0; if (have_error) PyErr_Fetch(&err_type, &err_value, &err_traceback); if(self->parent_acl != NULL) { Py_DECREF(self->parent_acl); self->parent_acl = NULL; } if (have_error) PyErr_Restore(err_type, err_value, err_traceback); PyObject_DEL(self); } /* Converts the entry to a text format */ static PyObject* Entry_str(PyObject *obj) { acl_tag_t tag; uid_t qualifier; void *p; PyObject *format, *kind; Entry_Object *self = (Entry_Object*) obj; if(acl_get_tag_type(self->entry, &tag) == -1) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } if(tag == ACL_USER || tag == ACL_GROUP) { if((p = acl_get_qualifier(self->entry)) == NULL) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } qualifier = *(uid_t*)p; acl_free(p); } else { qualifier = 0; } format = MyString_FromString("ACL entry for "); if(format == NULL) return NULL; 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) { Py_DECREF(format); return NULL; } MyString_ConcatAndDel(&format, kind); return format; } /* Sets the tag type of the entry */ static int Entry_set_tag_type(PyObject* obj, PyObject* value, void* arg) { Entry_Object *self = (Entry_Object*) obj; if(value == NULL) { PyErr_SetString(PyExc_TypeError, "tag type deletion is not supported"); return -1; } if(!PyInt_Check(value)) { PyErr_SetString(PyExc_TypeError, "tag type must be integer"); return -1; } if(acl_set_tag_type(self->entry, (acl_tag_t)PyInt_AsLong(value)) == -1) { PyErr_SetFromErrno(PyExc_IOError); return -1; } return 0; } /* Returns the tag type of the entry */ static PyObject* Entry_get_tag_type(PyObject *obj, void* arg) { Entry_Object *self = (Entry_Object*) obj; acl_tag_t value; if (self->entry == NULL) { PyErr_SetString(PyExc_AttributeError, "entry attribute"); return NULL; } if(acl_get_tag_type(self->entry, &value) == -1) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } return PyInt_FromLong(value); } /* Sets the qualifier (either uid_t or gid_t) for the entry, * usable only if the tag type if ACL_USER or ACL_GROUP */ static int Entry_set_qualifier(PyObject* obj, PyObject* value, void* arg) { Entry_Object *self = (Entry_Object*) obj; int uidgid; if(value == NULL) { PyErr_SetString(PyExc_TypeError, "qualifier deletion is not supported"); return -1; } if(!PyInt_Check(value)) { PyErr_SetString(PyExc_TypeError, "tag type must be integer"); return -1; } uidgid = PyInt_AsLong(value); if(acl_set_qualifier(self->entry, (void*)&uidgid) == -1) { PyErr_SetFromErrno(PyExc_IOError); return -1; } return 0; } /* Returns the qualifier of the entry */ static PyObject* Entry_get_qualifier(PyObject *obj, void* arg) { Entry_Object *self = (Entry_Object*) obj; void *p; int value; if (self->entry == NULL) { PyErr_SetString(PyExc_AttributeError, "entry attribute"); return NULL; } if((p = acl_get_qualifier(self->entry)) == NULL) { PyErr_SetFromErrno(PyExc_IOError); return NULL; } value = *(uid_t*)p; acl_free(p); return PyInt_FromLong(value); } /* Returns the parent ACL of the entry */ static PyObject* Entry_get_parent(PyObject *obj, void* arg) { Entry_Object *self = (Entry_Object*) obj; Py_INCREF(self->parent_acl); return self->parent_acl; } /* Returns the a new Permset representing the permset of the entry * FIXME: Should return a new reference to the same object, which * should be created at init time! */ static PyObject* Entry_get_permset(PyObject *obj, void* arg) { Entry_Object *self = (Entry_Object*)obj; PyObject *p; Permset_Object *ps; p = Permset_new(&Permset_Type, NULL, NULL); if(p == NULL) return NULL; 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; Py_INCREF(obj); return (PyObject*)p; } /* Sets the permset of the entry to the passed Permset */ static int Entry_set_permset(PyObject* obj, PyObject* value, void* arg) { Entry_Object *self = (Entry_Object*)obj; Permset_Object *p; if(!PyObject_IsInstance(value, (PyObject*)&Permset_Type)) { PyErr_SetString(PyExc_TypeError, "argument 1 must be posix1e.Permset"); return -1; } p = (Permset_Object*)value; if(acl_set_permset(self->entry, p->permset) == -1) { PyErr_SetFromErrno(PyExc_IOError); return -1; } return 0; } static char __Entry_copy_doc__[] = "copy(src)\n" "Copies an ACL entry.\n" "\n" "This method sets all the parameters to those of another\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 entry */ static PyObject* Entry_copy(PyObject *obj, PyObject *args) { Entry_Object *self = (Entry_Object*)obj; Entry_Object *other; if(!PyArg_ParseTuple(args, "O!", &Entry_Type, &other)) return NULL; if(acl_copy_entry(self->entry, other->entry) == -1) return PyErr_SetFromErrno(PyExc_IOError); Py_INCREF(Py_None); return Py_None; } /**** Permset type *****/ /* Creation of a new Permset instance */ static PyObject* Permset_new(PyTypeObject* type, PyObject* args, PyObject *keywds) { PyObject* newpermset; newpermset = PyType_GenericNew(type, args, keywds); if(newpermset != NULL) { ((Permset_Object*)newpermset)->permset = NULL; ((Permset_Object*)newpermset)->parent_entry = NULL; } return newpermset; } /* Initialization of a new Permset instance */ static int Permset_init(PyObject* obj, PyObject* args, PyObject *keywds) { Permset_Object* self = (Permset_Object*) obj; Entry_Object* parent = NULL; if (!PyArg_ParseTuple(args, "O!", &Entry_Type, &parent)) return -1; if(acl_get_permset(parent->entry, &self->permset) == -1) { PyErr_SetFromErrno(PyExc_IOError); return -1; } self->parent_entry = (PyObject*)parent; Py_INCREF(parent); return 0; } /* Free the Permset instance */ static void Permset_dealloc(PyObject* obj) { Permset_Object *self = (Permset_Object*) obj; PyObject *err_type, *err_value, *err_traceback; int have_error = PyErr_Occurred() ? 1 : 0; if (have_error) PyErr_Fetch(&err_type, &err_value, &err_traceback); if(self->parent_entry != NULL) { Py_DECREF(self->parent_entry); self->parent_entry = NULL; } if (have_error) PyErr_Restore(err_type, err_value, err_traceback); PyObject_DEL(self); } /* Permset string representation */ static PyObject* Permset_str(PyObject *obj) { Permset_Object *self = (Permset_Object*) obj; char pstr[3]; 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 MyString_FromStringAndSize(pstr, 3); } static char __Permset_clear_doc__[] = "Clears all permissions from the permission set.\n" ; /* Clears all permissions from the permset */ static PyObject* Permset_clear(PyObject* obj, PyObject* args) { Permset_Object *self = (Permset_Object*) obj; if(acl_clear_perms(self->permset) == -1) return PyErr_SetFromErrno(PyExc_IOError); /* Return the result */ Py_INCREF(Py_None); return Py_None; } 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_RETURN_TRUE; } else { Py_RETURN_FALSE; } } static int Permset_set_right(PyObject* obj, PyObject* value, void* arg) { Permset_Object *self = (Permset_Object*) obj; int on; int nerr; if(!PyInt_Check(value)) { PyErr_SetString(PyExc_ValueError, "a maximum of one argument must" " be passed"); return -1; } on = PyInt_AsLong(value); if(on) nerr = acl_add_perm(self->permset, *(acl_perm_t*)arg); else nerr = acl_delete_perm(self->permset, *(acl_perm_t*)arg); if(nerr == -1) { PyErr_SetFromErrno(PyExc_IOError); return -1; } return 0; } static char __Permset_add_doc__[] = "add(perm)\n" "Add a permission to the permission set.\n" "\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" ":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) { Permset_Object *self = (Permset_Object*) obj; int right; if (!PyArg_ParseTuple(args, "i", &right)) return NULL; if(acl_add_perm(self->permset, (acl_perm_t) right) == -1) return PyErr_SetFromErrno(PyExc_IOError); /* Return the result */ Py_INCREF(Py_None); return Py_None; } static char __Permset_delete_doc__[] = "delete(perm)\n" "Delete a permission from the permission set.\n" "\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" "\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) { Permset_Object *self = (Permset_Object*) obj; int right; if (!PyArg_ParseTuple(args, "i", &right)) return NULL; if(acl_delete_perm(self->permset, (acl_perm_t) right) == -1) return PyErr_SetFromErrno(PyExc_IOError); /* Return the result */ Py_INCREF(Py_None); return Py_None; } 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 represented by\n" "the argument perm exists in the permission set.\n" "\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) { Permset_Object *self = (Permset_Object*) obj; int right; int ret; if (!PyArg_ParseTuple(args, "i", &right)) return NULL; ret = get_perm(self->permset, (acl_perm_t) right); if(ret == -1) return PyErr_SetFromErrno(PyExc_IOError); if(ret) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } #endif static char __ACL_Type_doc__[] = "Type which represents a POSIX ACL\n" "\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" ":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, 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), otherwise the ACL won't\n" "be useful.\n" ; /* ACL type methods */ static PyMethodDef ACL_methods[] = { {"applyto", ACL_applyto, METH_VARARGS, __applyto_doc__}, {"valid", ACL_valid, METH_NOARGS, __valid_doc__}, #ifdef HAVE_LINUX {"to_any_text", (PyCFunction)ACL_to_any_text, METH_VARARGS | METH_KEYWORDS, __to_any_text_doc__}, {"check", ACL_check, METH_NOARGS, __check_doc__}, {"equiv_mode", ACL_equiv_mode, METH_NOARGS, __equiv_mode_doc__}, #endif #ifdef HAVE_ACL_COPYEXT {"__getstate__", ACL_get_state, METH_NOARGS, "Dumps the ACL to an external format."}, {"__setstate__", ACL_set_state, METH_VARARGS, "Loads the ACL from an external format."}, #endif #ifdef HAVE_LEVEL2 {"delete_entry", ACL_delete_entry, METH_VARARGS, __ACL_delete_entry_doc__}, {"calc_mask", ACL_calc_mask, METH_NOARGS, __ACL_calc_mask_doc__}, {"append", ACL_append, METH_VARARGS, __ACL_append_doc__}, #endif {NULL, NULL, 0, NULL} }; /* The definition of the ACL Type */ static PyTypeObject ACL_Type = { #ifdef IS_PY3K PyVarObject_HEAD_INIT(NULL, 0) #else PyObject_HEAD_INIT(NULL) 0, #endif "posix1e.ACL", sizeof(ACL_Object), 0, ACL_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ ACL_nocmp, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ ACL_str, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ __ACL_Type_doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ #ifdef HAVE_LINUX ACL_richcompare, /* tp_richcompare */ #else 0, /* tp_richcompare */ #endif 0, /* tp_weaklistoffset */ #ifdef HAVE_LEVEL2 ACL_iter, ACL_iternext, #else 0, /* tp_iter */ 0, /* tp_iternext */ #endif ACL_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ ACL_init, /* tp_init */ 0, /* tp_alloc */ ACL_new, /* tp_new */ }; #ifdef HAVE_LEVEL2 /* Entry type methods */ static PyMethodDef Entry_methods[] = { {"copy", Entry_copy, METH_VARARGS, __Entry_copy_doc__}, {NULL, NULL, 0, NULL} }; static char __Entry_tagtype_doc__[] = "The tag type of the current entry\n" "\n" "This is one of:\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 :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__[] = "The parent ACL of this entry\n" ; static char __Entry_permset_doc__[] = "The permission set of this ACL entry\n" ; /* Entry getset */ static PyGetSetDef Entry_getsets[] = { {"tag_type", Entry_get_tag_type, Entry_set_tag_type, __Entry_tagtype_doc__}, {"qualifier", Entry_get_qualifier, Entry_set_qualifier, __Entry_qualifier_doc__}, {"parent", Entry_get_parent, NULL, __Entry_parent_doc__}, {"permset", Entry_get_permset, Entry_set_permset, __Entry_permset_doc__}, {NULL} }; static char __Entry_Type_doc__[] = "Type which represents an entry in an ACL.\n" "\n" "The type exists only if the OS has full support for POSIX.1e\n" "Can be created either by:\n" "\n" " >>> e = posix1e.Entry(myACL) # this creates a new entry in the ACL\n" " >>> 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" "Note that the Entry keeps a reference to its ACL, so even if \n" "you delete the ACL, it won't be cleaned up and will continue to \n" "exist until its Entry(ies) will be deleted.\n" ; /* The definition of the Entry Type */ static PyTypeObject Entry_Type = { #ifdef IS_PY3K PyVarObject_HEAD_INIT(NULL, 0) #else PyObject_HEAD_INIT(NULL) 0, #endif "posix1e.Entry", sizeof(Entry_Object), 0, Entry_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ Entry_str, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ __Entry_Type_doc__, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Entry_methods, /* tp_methods */ 0, /* tp_members */ Entry_getsets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ Entry_init, /* tp_init */ 0, /* tp_alloc */ Entry_new, /* tp_new */ }; /* Permset type methods */ static PyMethodDef Permset_methods[] = { {"clear", Permset_clear, METH_NOARGS, __Permset_clear_doc__, }, {"add", Permset_add, METH_VARARGS, __Permset_add_doc__, }, {"delete", Permset_delete, METH_VARARGS, __Permset_delete_doc__, }, {"test", Permset_test, METH_VARARGS, __Permset_test_doc__, }, {NULL, NULL, 0, NULL} }; static char __Permset_execute_doc__[] = "Execute permission property\n" "\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 permission property\n" "\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 permission property\n" "\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" ; /* Permset getset */ static PyGetSetDef Permset_getsets[] = { {"execute", Permset_get_right, Permset_set_right, __Permset_execute_doc__, &holder_ACL_EXECUTE}, {"read", Permset_get_right, Permset_set_right, __Permset_read_doc__, &holder_ACL_READ}, {"write", Permset_get_right, Permset_set_right, __Permset_write_doc__, &holder_ACL_WRITE}, {NULL} }; static char __Permset_Type_doc__[] = "Type which represents the permission set in an ACL entry\n" "\n" "The type exists only if the OS has full support for POSIX.1e\n" "Can be retrieved either by:\n\n" ">>> perms = myEntry.permset\n" "\n" "or by:\n\n" ">>> perms = posix1e.Permset(myEntry)\n" "\n" "Note that the Permset keeps a reference to its Entry, so even if \n" "you delete the entry, it won't be cleaned up and will continue to \n" "exist until its Permset will be deleted.\n" ; /* The definition of the Permset Type */ static PyTypeObject Permset_Type = { #ifdef IS_PY3K PyVarObject_HEAD_INIT(NULL, 0) #else PyObject_HEAD_INIT(NULL) 0, #endif "posix1e.Permset", sizeof(Permset_Object), 0, Permset_dealloc, /* tp_dealloc */ 0, /* tp_print */ 0, /* tp_getattr */ 0, /* tp_setattr */ 0, /* tp_compare */ 0, /* tp_repr */ 0, /* tp_as_number */ 0, /* tp_as_sequence */ 0, /* tp_as_mapping */ 0, /* tp_hash */ 0, /* tp_call */ Permset_str, /* tp_str */ 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT, /* tp_flags */ __Permset_Type_doc__,/* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ Permset_methods, /* tp_methods */ 0, /* tp_members */ Permset_getsets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ Permset_init, /* tp_init */ 0, /* tp_alloc */ Permset_new, /* tp_new */ }; #endif /* 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" "a directory (the ACL which will be ANDed with the mode\n" "parameter to the open, creat functions).\n" "\n" ":param string path: the directory whose default ACL should be deleted\n" ; /* Deletes the default ACL from a directory */ static PyObject* aclmodule_delete_default(PyObject* obj, PyObject* args) { char *filename; /* Parse the arguments */ if (!PyArg_ParseTuple(args, "et", NULL, &filename)) return NULL; if(acl_delete_def_file(filename) == -1) { return PyErr_SetFromErrno(PyExc_IOError); } /* Return the result */ Py_INCREF(Py_None); return Py_None; } #ifdef HAVE_LINUX static char __has_extended_doc__[] = "has_extended(item)\n" "Check if a file or file handle has an extended ACL.\n" "\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 */ static PyObject* aclmodule_has_extended(PyObject* obj, PyObject* args) { PyObject *myarg; int nret; int fd; if (!PyArg_ParseTuple(args, "O", &myarg)) return NULL; if(PyBytes_Check(myarg)) { const char *filename = PyBytes_AS_STRING(myarg); nret = acl_extended_file(filename); } else if (PyUnicode_Check(myarg)) { PyObject *o = PyUnicode_AsEncodedString(myarg, Py_FileSystemDefaultEncoding, "strict"); if (o == NULL) return NULL; const char *filename = PyBytes_AS_STRING(o); nret = acl_extended_file(filename); Py_DECREF(o); } else if((fd = PyObject_AsFileDescriptor(myarg)) != -1) { nret = acl_extended_fd(fd); } else { PyErr_SetString(PyExc_TypeError, "argument 1 must be string, int," " or file-like object"); return 0; } if(nret == -1) { return PyErr_SetFromErrno(PyExc_IOError); } /* Return the result */ return PyBool_FromLong(nret); } #endif /* The module methods */ static PyMethodDef aclmodule_methods[] = { {"delete_default", aclmodule_delete_default, METH_VARARGS, __deletedef_doc__}, #ifdef HAVE_LINUX {"has_extended", aclmodule_has_extended, METH_VARARGS, __has_extended_doc__}, #endif {NULL, NULL, 0, NULL} }; 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\n" " - level 1, only basic support, you can create\n" " ACLs from files and text descriptions;\n" " once created, the type is immutable\n" " - level 2, complete support, you can alter\n" " the ACL once it is created\n" "\n" "Also, in level 2, more types are available, corresponding\n" "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\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" ">>> import posix1e\n" ">>> acl1 = posix1e.ACL(file=\"file.txt\") \n" ">>> print acl1\n" "user::rw-\n" "group::rw-\n" "other::r--\n" ">>>\n" ">>> b = posix1e.ACL(text=\"u::rx,g::-,o::-\")\n" ">>> print b\n" "user::r-x\n" "group::---\n" "other::---\n" ">>>\n" ">>> b.applyto(\"file.txt\")\n" ">>> print posix1e.ACL(file=\"file.txt\")\n" "user::r-x\n" "group::---\n" "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 static struct PyModuleDef posix1emodule = { PyModuleDef_HEAD_INIT, "posix1e", __posix1e_doc__, 0, aclmodule_methods, }; #define INITERROR return NULL PyMODINIT_FUNC PyInit_posix1e(void) #else #define INITERROR return void initposix1e(void) #endif { PyObject *m, *d; Py_TYPE(&ACL_Type) = &PyType_Type; if(PyType_Ready(&ACL_Type) < 0) INITERROR; #ifdef HAVE_LEVEL2 Py_TYPE(&Entry_Type) = &PyType_Type; if(PyType_Ready(&Entry_Type) < 0) INITERROR; Py_TYPE(&Permset_Type) = &PyType_Type; if(PyType_Ready(&Permset_Type) < 0) INITERROR; #endif #ifdef IS_PY3K m = PyModule_Create(&posix1emodule); #else m = Py_InitModule3("posix1e", aclmodule_methods, __posix1e_doc__); #endif if (m==NULL) INITERROR; d = PyModule_GetDict(m); if (d == NULL) INITERROR; Py_INCREF(&ACL_Type); if (PyDict_SetItemString(d, "ACL", (PyObject *) &ACL_Type) < 0) INITERROR; /* 23.3.6 acl_type_t values */ PyModule_AddIntConstant(m, "ACL_TYPE_ACCESS", ACL_TYPE_ACCESS); PyModule_AddIntConstant(m, "ACL_TYPE_DEFAULT", ACL_TYPE_DEFAULT); #ifdef HAVE_LEVEL2 Py_INCREF(&Entry_Type); if (PyDict_SetItemString(d, "Entry", (PyObject *) &Entry_Type) < 0) INITERROR; Py_INCREF(&Permset_Type); if (PyDict_SetItemString(d, "Permset", (PyObject *) &Permset_Type) < 0) INITERROR; /* 23.2.2 acl_perm_t values */ PyModule_AddIntConstant(m, "ACL_READ", ACL_READ); PyModule_AddIntConstant(m, "ACL_WRITE", ACL_WRITE); PyModule_AddIntConstant(m, "ACL_EXECUTE", ACL_EXECUTE); /* 23.2.5 acl_tag_t values */ PyModule_AddIntConstant(m, "ACL_UNDEFINED_TAG", ACL_UNDEFINED_TAG); PyModule_AddIntConstant(m, "ACL_USER_OBJ", ACL_USER_OBJ); PyModule_AddIntConstant(m, "ACL_USER", ACL_USER); PyModule_AddIntConstant(m, "ACL_GROUP_OBJ", ACL_GROUP_OBJ); PyModule_AddIntConstant(m, "ACL_GROUP", ACL_GROUP); PyModule_AddIntConstant(m, "ACL_MASK", ACL_MASK); PyModule_AddIntConstant(m, "ACL_OTHER", ACL_OTHER); /* Document extended functionality via easy-to-use constants */ PyModule_AddIntConstant(m, "HAS_ACL_ENTRY", 1); #else PyModule_AddIntConstant(m, "HAS_ACL_ENTRY", 0); #endif #ifdef HAVE_LINUX /* Linux libacl specific acl_to_any_text constants */ PyModule_AddIntConstant(m, "TEXT_ABBREVIATE", TEXT_ABBREVIATE); PyModule_AddIntConstant(m, "TEXT_NUMERIC_IDS", TEXT_NUMERIC_IDS); PyModule_AddIntConstant(m, "TEXT_SOME_EFFECTIVE", TEXT_SOME_EFFECTIVE); PyModule_AddIntConstant(m, "TEXT_ALL_EFFECTIVE", TEXT_ALL_EFFECTIVE); PyModule_AddIntConstant(m, "TEXT_SMART_INDENT", TEXT_SMART_INDENT); /* Linux libacl specific acl_check constants */ PyModule_AddIntConstant(m, "ACL_MULTI_ERROR", ACL_MULTI_ERROR); PyModule_AddIntConstant(m, "ACL_DUPLICATE_ERROR", ACL_DUPLICATE_ERROR); PyModule_AddIntConstant(m, "ACL_MISS_ERROR", ACL_MISS_ERROR); PyModule_AddIntConstant(m, "ACL_ENTRY_ERROR", ACL_ENTRY_ERROR); /* declare the Linux extensions */ PyModule_AddIntConstant(m, "HAS_ACL_FROM_MODE", 1); PyModule_AddIntConstant(m, "HAS_ACL_CHECK", 1); PyModule_AddIntConstant(m, "HAS_EXTENDED_CHECK", 1); PyModule_AddIntConstant(m, "HAS_EQUIV_MODE", 1); #else PyModule_AddIntConstant(m, "HAS_ACL_FROM_MODE", 0); PyModule_AddIntConstant(m, "HAS_ACL_CHECK", 0); PyModule_AddIntConstant(m, "HAS_EXTENDED_CHECK", 0); PyModule_AddIntConstant(m, "HAS_EQUIV_MODE", 0); #endif #ifdef IS_PY3K return m; #endif } pylibacl-0.5.1/doc/000077500000000000000000000000001175363250100140645ustar00rootroot00000000000000pylibacl-0.5.1/doc/conf.py000066400000000000000000000174171175363250100153750ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # pylibacl documentation build configuration file, created by # sphinx-quickstart on Sun May 13 01:05:18 2012. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('../')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'pylibacl' copyright = u'2002-2009, 2012, Iustin Pop' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.5.1' # The full version, including alpha/beta/rc tags. release = '0.5.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build', 'html'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] keep_warnings = True # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". #html_static_path = ['_static'] html_static_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True html_domain_indices = False # If false, no index is generated. #html_use_index = True html_use_index = False # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'pylibacldoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'pylibacl.tex', u'pylibacl Documentation', u'Iustin Pop', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pylibacl', u'pylibacl Documentation', [u'Iustin Pop'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'pylibacl', u'pylibacl Documentation', u'Iustin Pop', 'pylibacl', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' autodoc_member_order = 'alphabetical' pylibacl-0.5.1/doc/implementation.rst000066400000000000000000000076211175363250100176510ustar00rootroot00000000000000Implementation details ====================== Functionality level ------------------- The IEEE 1003.1e draft 17 ("POSIX.1e") describes a set of 28 functions. These are grouped into three groups, based on their portability: - first group, the most portable one. All systems which claim to support POSIX.1e should implement these: acl_delete_def_file(3), acl_dup(3), acl_free(3), acl_from_text(3), acl_get_fd(3), acl_get_file(3), acl_init(3), acl_set_fd(3), acl_set_file(3), acl_to_text(3), acl_valid(3) - second group, containing the rest of the POSIX ACL functions. Systems which claim to fully implement POSIX.1e should implement these: acl_add_perm(3), acl_calc_mask(3), acl_clear_perms(3), acl_copy_entry(3), acl_copy_ext(3), acl_copy_int(3), acl_create_entry(3), acl_delete_entry(3), acl_delete_perm(3), acl_get_entry(3), acl_get_permset(3), acl_get_qualifier(3), acl_get_tag_type(3), acl_set_permset(3), acl_set_qualifier(3), acl_set_tag_type(3), acl_size(3) - third group, containing extra functions implemented by each OS. These are non-portable version. Both Linux and FreeBSD implement some extra functions. Thus we have the level of compliance. Depending on whether the system library support the second group, you get some extra methods for the ACL object. The implementation of the second group of function can be tested by checking the module-level constant HAS_ACL_ENTRY. The extra functionality available on Linux can be tested by additional HAS_* constants. Internal structure ------------------ The POSIX draft has the following stuff (correct me if I'm wrong): - an ACL is denoted by acl_t - an ACL contains many acl_entry_t, these are the individual entries in the list; they always(!) belong to an acl_t - each entry_t has a qualifier (think uid_t or gid_t), whose type is denoted by the acl_tag_t type, and an acl_permset_t - 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: pylibacl-0.5.1/doc/index.rst000066400000000000000000000004351175363250100157270ustar00rootroot00000000000000====================================== 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`. pylibacl-0.5.1/doc/module.rst000066400000000000000000000000711175363250100161010ustar00rootroot00000000000000.. automodule:: posix1e :members: :undoc-members: pylibacl-0.5.1/doc/news.rst000077700000000000000000000000001175363250100164772../NEWSustar00rootroot00000000000000pylibacl-0.5.1/setup.cfg000066400000000000000000000001201175363250100151310ustar00rootroot00000000000000[bdist_rpm] release = 1 requires = libacl ;build_requires = libacl libacl-devel pylibacl-0.5.1/setup.py000077500000000000000000000027001175363250100150330ustar00rootroot00000000000000#!/usr/bin/env python import os from setuptools import setup, Extension (u_sysname, u_nodename, u_release, u_version, u_machine) = os.uname() macros = [] libs = [] if u_sysname == "Linux": macros.append(("HAVE_LINUX", None)) macros.append(("HAVE_LEVEL2", None)) libs.append("acl") elif u_sysname == "GNU/kFreeBSD": macros.append(("HAVE_LINUX", None)) macros.append(("HAVE_LEVEL2", None)) macros.append(("HAVE_ACL_COPY_EXT", None)) libs.append("acl") elif u_sysname == "FreeBSD": macros.append(("HAVE_FREEBSD", None)) if u_release.startswith("7."): macros.append(("HAVE_LEVEL2", None)) elif u_sysname == "Darwin": libs.append("pthread") else: raise ValueError("I don't know your system '%s'." " Please contact the author" % u_sysname) 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.1" setup(name="pylibacl", version=version, description="POSIX.1e ACLs for python", long_description=long_desc, author="Iustin Pop", author_email="iusty@k1024.org", url="http://pylibacl.sourceforge.net/", license="LGPL", ext_modules=[Extension("posix1e", ["acl.c"], libraries=libs, define_macros=macros, )], test_suite="tests", ) pylibacl-0.5.1/tests/000077500000000000000000000000001175363250100144615ustar00rootroot00000000000000pylibacl-0.5.1/tests/__init__.py000066400000000000000000000000001175363250100165600ustar00rootroot00000000000000pylibacl-0.5.1/tests/test_acls.py000066400000000000000000000246221175363250100170220ustar00rootroot00000000000000# # """Unittests for the posix1e module""" # Copyright (C) 2002-2009, 2012 Iustin Pop # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA import unittest import os import tempfile import sys import posix1e from posix1e import * TEST_DIR = os.environ.get("TESTDIR", ".") BASIC_ACL_TEXT = "u::rw,g::r,o::-" # This is to workaround python 2/3 differences at syntactic level # (which can't be worked around via if's) M0500 = 320 # octal 0500 M0644 = 420 # octal 0644 M0755 = 493 # octal 755 def _skip_test(fn): """Wrapper to skip a test""" new_fn = lambda x: None new_fn.__doc__ = "SKIPPED %s" % fn.__doc__ return new_fn def has_ext(extension): """Decorator to skip tests based on platform support""" if not extension: return _skip_test else: return lambda x: x class aclTest: """Support functions ACLs""" def setUp(self): """set up function""" self.rmfiles = [] self.rmdirs = [] def tearDown(self): """tear down function""" for fname in self.rmfiles: os.unlink(fname) for dname in self.rmdirs: os.rmdir(dname) def _getfile(self): """create a temp file""" fh, fname = tempfile.mkstemp(".test", "xattr-", TEST_DIR) self.rmfiles.append(fname) return fh, fname def _getdir(self): """create a temp dir""" dname = tempfile.mkdtemp(".test", "xattr-", TEST_DIR) self.rmdirs.append(dname) return dname def _getsymlink(self): """create a symlink""" fh, fname = self._getfile() os.close(fh) os.unlink(fname) os.symlink(fname + ".non-existent", fname) return fname class LoadTests(aclTest, unittest.TestCase): """Load/create tests""" def testFromFile(self): """Test loading ACLs from a file""" _, fname = self._getfile() acl1 = posix1e.ACL(file=fname) self.assertTrue(acl1.valid(), "ACL read from file should be valid") def testFromDir(self): """Test loading ACLs from a directory""" dname = self._getdir() acl1 = posix1e.ACL(file=dname) acl2 = posix1e.ACL(filedef=dname) self.assertTrue(acl1.valid(), "ACL read from directory should be valid") # default ACLs might or might not be valid; missing ones are # not valid, so we don't test acl2 for validity def testFromFd(self): """Test loading ACLs from a file descriptor""" fd, _ = self._getfile() acl1 = posix1e.ACL(fd=fd) self.assertTrue(acl1.valid(), "ACL read from fd should be valid") def testFromEmpty(self): """Test creating an empty ACL""" acl1 = posix1e.ACL() self.assertFalse(acl1.valid(), "Empty ACL should not be valid") def testFromText(self): """Test creating an ACL from text""" acl1 = posix1e.ACL(text=BASIC_ACL_TEXT) self.assertTrue(acl1.valid(), "ACL based on standard description should be valid") class AclExtensions(aclTest, unittest.TestCase): """ACL extensions checks""" @has_ext(HAS_ACL_FROM_MODE) def testFromMode(self): """Test loading ACLs from an octal mode""" acl1 = posix1e.ACL(mode=M0644) self.assertTrue(acl1.valid(), "ACL created via octal mode shoule be valid") @has_ext(HAS_ACL_CHECK) def testAclCheck(self): """Test the acl_check method""" acl1 = posix1e.ACL(text=BASIC_ACL_TEXT) self.assertFalse(acl1.check(), "ACL is not valid") acl2 = posix1e.ACL() self.assertTrue(acl2.check(), "Empty ACL should not be valid") @has_ext(HAS_EXTENDED_CHECK) def testExtended(self): """Test the acl_extended function""" fd, fname = self._getfile() basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT) basic_acl.applyto(fd) for item in fd, fname: self.assertFalse(has_extended(item), "A simple ACL should not be reported as extended") enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r") self.assertTrue(enhanced_acl.valid(), "Failure to build an extended ACL") enhanced_acl.applyto(fd) for item in fd, fname: self.assertTrue(has_extended(item), "An extended ACL should be reported as such") @has_ext(HAS_EQUIV_MODE) def testEquivMode(self): """Test the equiv_mode function""" if HAS_ACL_FROM_MODE: for mode in M0644, M0755: acl = posix1e.ACL(mode=mode) self.assertEqual(acl.equiv_mode(), mode) acl = posix1e.ACL(text="u::rw,g::r,o::r") self.assertEqual(acl.equiv_mode(), M0644) acl = posix1e.ACL(text="u::rx,g::-,o::-") self.assertEqual(acl.equiv_mode(), M0500) class WriteTests(aclTest, unittest.TestCase): """Write tests""" def testDeleteDefault(self): """Test removing the default ACL""" dname = self._getdir() posix1e.delete_default(dname) def testReapply(self): """Test re-applying an ACL""" fd, fname = self._getfile() acl1 = posix1e.ACL(fd=fd) acl1.applyto(fd) acl1.applyto(fname) dname = self._getdir() acl2 = posix1e.ACL(file=fname) acl2.applyto(dname) class ModificationTests(aclTest, unittest.TestCase): """ACL modification tests""" def checkRef(self, obj): """Checks if a given obj has a 'sane' refcount""" ref_cnt = sys.getrefcount(obj) # FIXME: hardcoded value for the max ref count... but I've # seen it overflow on bad reference counting, so it's better # to be safe if ref_cnt < 2 or ref_cnt > 1024: self.fail("Wrong reference count, expected 2-1024 and got %d" % ref_cnt) def testStr(self): """Test str() of an ACL.""" acl = posix1e.ACL() str_acl = str(acl) self.checkRef(str_acl) @has_ext(HAS_ACL_ENTRY) def testAppend(self): """Test append a new Entry to the ACL""" acl = posix1e.ACL() e = acl.append() e.tag_type = posix1e.ACL_OTHER acl.calc_mask() str_format = str(e) self.checkRef(str_format) @has_ext(HAS_ACL_ENTRY) def testDelete(self): """Test delete Entry from the ACL""" acl = posix1e.ACL() e = acl.append() e.tag_type = posix1e.ACL_OTHER acl.calc_mask() acl.delete_entry(e) acl.calc_mask() @has_ext(HAS_ACL_ENTRY) def testDoubleEntries(self): """Test double entries""" acl = posix1e.ACL(text=BASIC_ACL_TEXT) self.assertTrue(acl.valid(), "ACL is not valid") for tag_type in (posix1e.ACL_USER_OBJ, posix1e.ACL_GROUP_OBJ, posix1e.ACL_OTHER): e = acl.append() e.tag_type = tag_type e.permset.clear() self.assertFalse(acl.valid(), "ACL containing duplicate entries should not be valid") acl.delete_entry(e) @has_ext(HAS_ACL_ENTRY) def testMultipleGoodEntries(self): """Test multiple valid entries""" acl = posix1e.ACL(text=BASIC_ACL_TEXT) self.assertTrue(acl.valid(), "ACL is not valid") for tag_type in (posix1e.ACL_USER, posix1e.ACL_GROUP): for obj_id in range(5): e = acl.append() e.tag_type = tag_type e.qualifier = obj_id e.permset.clear() acl.calc_mask() self.assertTrue(acl.valid(), "ACL should be able to hold multiple" " user/group entries") @has_ext(HAS_ACL_ENTRY) def testMultipleBadEntries(self): """Test multiple invalid entries""" acl = posix1e.ACL(text=BASIC_ACL_TEXT) self.assertTrue(acl.valid(), "ACL built from standard description" " should be valid") for tag_type in (posix1e.ACL_USER, posix1e.ACL_GROUP): e1 = acl.append() e1.tag_type = tag_type e1.qualifier = 0 e1.permset.clear() acl.calc_mask() self.assertTrue(acl.valid(), "ACL should be able to add a" " user/group entry") e2 = acl.append() e2.tag_type = tag_type e2.qualifier = 0 e2.permset.clear() acl.calc_mask() self.assertFalse(acl.valid(), "ACL should not validate when" " containing two duplicate entries") acl.delete_entry(e1) acl.delete_entry(e2) @has_ext(HAS_ACL_ENTRY) def testPermset(self): """Test permissions""" acl = posix1e.ACL() e = acl.append() ps = e.permset ps.clear() str_ps = str(ps) self.checkRef(str_ps) pmap = { posix1e.ACL_READ: "read", posix1e.ACL_WRITE: "write", posix1e.ACL_EXECUTE: "execute", } for perm in pmap: str_ps = str(ps) self.checkRef(str_ps) self.assertFalse(ps.test(perm), "Empty permission set should not" " have permission '%s'" % pmap[perm]) ps.add(perm) self.assertTrue(ps.test(perm), "Permission '%s' should exist" " after addition" % pmap[perm]) str_ps = str(ps) self.checkRef(str_ps) ps.delete(perm) self.assertFalse(ps.test(perm), "Permission '%s' should not exist" " after deletion" % pmap[perm]) if __name__ == "__main__": unittest.main()