HWRF  trunk@4391
acl.py
1 """!Manipulates Access Control Lists (ACL)
2 
3 This module is a wrapper around the C libacl library, which provides
4 support for POSIX Access Control Lists, as defined by the abandoned
5 draft standard "IEEE 1003.1e draft 17". Only the widely-supported
6 features are implemented. It is intended to be used with the Linux
7 libacl, but might be portable to other versions if the module-scope
8 acl_library variable is changed to the name of your "dll" or "so" file
9 for libacl and values of ACL_TYPE_ACCESS and ACL_TYPE_DEFAULT are
10 changed. In addition, one must change the means by which errno is
11 accessed if switching from glibc to another C library."""
12 
13 import ctypes, os, stat
14 
15 class ACLError(EnvironmentError):
16  """!Superclass of any ACL errors"""
17  def __init__(self,message,errno):
18  """!ACLError constructor
19  @param message the description of the error
20  @param errno the system errno from the error"""
21  super(ACLError,self).__init__(message)
22  self.errno=errno
23  ##@var errno
24  # The errno value when the error happened.
25 
27  """!Raised when the libacl library could not be loaded."""
28 class ACLMissingError(ACLError):
29  """!Raised when a function that requires an ACL object received
30  None, or an invalid ACL."""
32  """!Raised when libacl cannot convert an ACL to text."""
34  """!Raised when the libacl library could not get a file's ACL."""
36  """!Raised when the libacl library could not set a file's ACL."""
37 
38 ##@var libacl
39 # The loaded libacl library from ctypes.cdll.LoadLibrary.
40 libacl=None
41 
42 ##@var libc
43 # The loaded libc library from ctypes.cdll.LoadLibrary.
44 libc=None
45 
46 ##@var ACL_TYPE_ACCESS
47 # The ACL_TYPE for Access Control Lists, defined in the libacl header
48 # files. This must match the value in the header.
49 ACL_TYPE_ACCESS=32768
50 
51 ##@var ACL_TYPE_DEFAULT
52 # The ACL_TYPE for Default Access Control Lists defined in the libacl
53 # header files. This must match the value in the header.
54 ACL_TYPE_DEFAULT=16384
55 
56 ##@var acl_library
57 # The ACL library name or path for input to ctypes.cdll.LoadLibrary.
58 # This is intended to be modified externally from this module if needed
59 # before using the produtil.acl module.
60 acl_library='libacl.so.1'
61 
62 ##@var c_library
63 # The C library name for input to ctypes.cdll.LoadLibrary. This is
64 # intended to be modified externally from this module if needed before
65 # using the produtil.acl module.
66 c_library='libc.so.6'
67 
68 ##@var get_errno
69 # Function that returns the value of errno. Used for testing for
70 # errors in libacl routines.
71 get_errno=None
72 
73 ########################################################################
74 # Library loading routine:
75 
76 def load_libc():
77  """!Loads the libc library.
78 
79  Loads the standard C library, which is needed to test the value of
80  errno in order to report errors. This function is called
81  automatically when needed; you should never need to call it
82  directly."""
83  global libc,get_errno
84  try:
85  libc=ctypes.cdll.LoadLibrary(c_library)
86  get_errno_loc = libc.__errno_location
87  get_errno_loc.restype = ctypes.POINTER(ctypes.c_int)
88  except EnvironmentError as e:
89  raise ACLLibraryError('Cannot load libc: '+str(e),e.errno)
90  if libc is None:
91  raise ACLLibraryError(
92  'Cannot find libc. The ctypes.cdll.LoadLibrary(%s) returned None.'
93  %(repr(c_library),))
94 
95  get_errno=lambda: get_errno_loc()[0]
96 
98  """!Loads the libacl library.
99 
100  Loads the libacl library whose name is specified in the module
101  scope acl_library variable. This function is called automatically
102  when needed; you should never need to call it directly."""
103  global libacl, libc
104  if libc is None:
105  load_libc()
106  # Load the library:
107  try:
108  libacl=ctypes.cdll.LoadLibrary(acl_library)
109  except EnvironmentError as e:
110  raise ACLLibraryError('Cannot load libacl: '+str(e),e.errno)
111  if libacl is None:
112  raise ACLLibraryError('Cannot find libacl. The ctypes.cdll.LoadLibrary(%s) returned None.'%(repr(acl_library),))
113 
114  # Set the function prototypes:
115  libacl.acl_get_fd.argtypes=[ ctypes.c_int ]
116  libacl.acl_get_fd.restype=ctypes.c_void_p
117 
118  libacl.acl_get_file.argtypes=[ ctypes.c_char_p, ctypes.c_int ]
119  libacl.acl_get_file.restype=ctypes.c_void_p
120 
121  libacl.acl_from_text.argtypes=[ ctypes.c_char_p ]
122  libacl.acl_from_text.restype=ctypes.c_void_p
123 
124  libacl.acl_to_text.argtypes=[ ctypes.c_int, ctypes.c_void_p ]
125  libacl.acl_to_text.restype=ctypes.c_char_p
126 
127  libacl.acl_set_fd.argtypes=[ ctypes.c_int, ctypes.c_void_p ]
128  libacl.acl_set_fd.restype=ctypes.c_int
129 
130  libacl.acl_set_file.argtypes=[ ctypes.c_char_p, ctypes.c_int, ctypes.c_void_p ]
131  libacl.acl_set_file.restype=ctypes.c_int
132 
133  libacl.acl_free.argtypes=[ ctypes.c_void_p ]
134  libacl.acl_free.restype=ctypes.c_int
135 
136 ########################################################################
137 # ACL class wrapped around the libacl library:
138 
139 class ACL(object):
140  """!Inquire and manipulate access control lists (ACLs).
141 
142  Represents a POSIX Access Control List (ACL). This is a wrapper
143  around the libacl library, and implements only widely-supported
144  ACL features. Data is stored internally in C structures, which
145  are allocated and freed automatically as needed."""
146  def __init__(self):
147  """!Create a blank, invalid, ACL. You should use the various
148  from_* routines to fill it with valid data."""
149  if libacl is None: load_libacl()
150  self.__libacl=libacl # to ensure libacl is not freed before the ACL
151  self.__acl=None
152  def __del__(self):
153  """!Free the memory used by the ACL in libacl."""
154  self.free()
155  def free(self):
156  """!Frees resources used by the libacl library to store this
157  ACL's underlying C structures."""
158  if self.__acl is not None and self.__acl!=0:
159  # We use self.__libacl here because we know it has not
160  # been freed.
161  self.__libacl.acl_free(self.__acl)
162  self.__acl=None
163  return self
164  def have_acl(self):
165  """!Returns True if this ACL has data, and False otherwise."""
166  return self.__acl is not None and self.__acl!=0
167  def from_text(self,acl):
168  """!Attempts to convert the given string to an ACL, storing the
169  result in this object. Any prior ACL information in this
170  object will be freed.
171  @param acl the access control list description"""
172  if self.__acl is not None: self.free()
173  sacl=str(acl)
174  cacl=ctypes.c_char_p(sacl)
175  self.__acl=self.__libacl.acl_from_text(cacl)
176  errno=get_errno()
177  if self.__acl is None or self.__acl==0:
178  self.__acl=None
179  shortacl=sacl
180  if len(sacl)>20:
181  shortacl=sacl[0:17]+'...'
182  raise ACLCannotGet('Cannot convert to acl: %s. String: %s'
183  %(os.strerror(errno),repr(shortacl)),errno)
184  return self
185  def from_file(self,filename,which=ACL_TYPE_ACCESS):
186  """!Copies the files's ACL into this object.
187 
188  Specify which type of access control list via the second
189  argument: ACL_TYPE_ACCESS or ACL_TYPE_DEFAULT. Any prior ACL
190  information in this object will be freed.
191  @param filename the name of the file whose ACL is desired
192  @param which which access control list is desired;
193  ACL_TYPE_ACCESS or ACL_TYPE_DEFAULT.
194  @returns self"""
195  if self.__acl is not None: self.free()
196  sfilename=str(filename)
197  cfilename=ctypes.c_char_p(sfilename)
198  self.__acl=self.__libacl.acl_get_file(filename,which)
199  errno=get_errno()
200  if self.__acl is None or self.__acl==0:
201  self.__acl=None
202  raise ACLCannotGet('%s: cannot get acl: %s'
203  %(sfilename,os.strerror(errno)),errno)
204  return self
205  def from_fd(self,fd):
206  """!Get an access control list from a file descriptor.
207 
208  Obtains an Access Control List from the specified file object
209  or file descriptor number. You can also pass any object that
210  has a "fileno()" member function. Any prior ACL information
211  in this object will be freed.
212  @param fd an integer file descriptor or a file object.
213  @returns self"""
214  if hasattr(fd,'fileno'): fd=fd.fileno()
215  ifd=int(fd)
216  cfd=ctypes.c_int(ifd)
217  self.__acl=self.__libacl.acl_get_fd(cfd)
218  errno=get_errno()
219  if self.__acl is None or self.__acl==0:
220  self.__acl=None
221  raise ACLCannotGet('file descriptor %d: cannot get acl: %s'
222  %(ifd,os.strerror(errno)),errno)
223  return self
224  def to_fd(self,fd):
225  """!Updates a file's file descriptor.
226 
227  Sets the ACL for the specified file descriptor to the ACL
228  stored in this object. Raises ACLMissingError if this object
229  has no ACL information.
230  @param fd an integer file descriptor or open file object"""
231  if self.__acl is None or self.__acl==0:
232  raise ACLMissingError(
233  "to_fd: caller tried to set a file descriptor's ACL to an invalid ACL: %s"
234  %(os.strerror(errno),),errno)
235  if hasattr(fd,'fileno'):
236  fd=fd.fileno()
237  ifd=int(fd)
238  cfd=ctypes.c_int(ifd)
239  r=self.__libacl.acl_set_fd(cfd,self.__acl)
240  errno=get_errno()
241  if r is not None and r!=0:
242  raise ACLCannotSet('file descriptor %d: cannot set acl: %s'
243  %(ifd,os.strerror(errno)),errno)
244  return self
245  def to_file(self,filename,access=ACL_TYPE_ACCESS):
246  """!Updates a file's access control list.
247 
248  Sets the ACL for the specified file to the ACL stored in this
249  object. Specify access=ACL_TYPE_DEFAULT to obtain the default
250  access control list (Default ACL) or ACL_TYPE_ACCESS for the
251  access control list. Raises ACLMissingError if this object
252  has no ACL information.
253 
254  @param filename the name of the file whose ACL is to be updated
255  @param access ACL_TYPE_ACCESS or ACL_TYPE_DEFAULT"""
256  if self.__acl is None or self.__acl==0:
257  raise ACLMissingError(
258  "Tried to set a file's ACL, while providing an invalid ACL.",
259  errno)
260  sfn=str(filename)
261  cfn=ctypes.c_char_p(sfn)
262  r=self.__libacl.acl_set_file(cfn,access,self.__acl)
263  errno=get_errno()
264  if r is not None and r!=0:
265  raise ACLCannotSet('%s: cannot set acl: %s'
266  %(sfn,os.strerror(errno)),errno)
267  return self
268  def to_text(self):
269  """!Converts an ACL to text.
270 
271  Returns a string representation of this ACL from acl_to_text.
272  Returns the empty string ('') if this ACL has no data."""
273  if self.__acl is None or self.__acl==0: return ''
274  size=ctypes.c_ulonglong(0)
275  textified=self.__libacl.acl_to_text(self.__acl,ctypes.byref(size))
276  errno=get_errno()
277  if textified==0 or textified is None:
278  raise ACLCannotStringify(
279  'Cannot convert ACL to a string: acl_to_text returned NULL: %s'
280  %(os.strerror(errno),), errno)
281  if size<=0:
282  raise ACLCannotStringify(
283  ('Cannot convert ACL to a string: acl_to_text size was %d'
284  ' (but return value was non-NULL): %s')
285  %(int(size),os.strerror(errno)), errno)
286  retstr=ctypes.string_at(textified)
287  self.__libacl.acl_free(textified)
288  return retstr
289 
290  def __str__(self):
291  """! => self.to_text() """
292  return self.to_text()
293 
294 ########################################################################
295 # Pythonic wrapper functions that mimic the C routines:
296 
297 def acl_to_text(acl):
298  """!Returns a string representation of the given access control
299  list object.
300  @param acl an ACL object
301  @returns the string equivalent"""
302  return acl.to_text()
303 
304 def acl_get_file(filename,access=ACL_TYPE_ACCESS):
305  """!Returns an object that represents the access control list for
306  the specified file.
307  @param filename the name of the file of interest
308  @param access ACL_TYPE_ACCESS or ACL_TYPE_DEFAULT
309  @return a new ACL"""
310  return ACL().from_file(filename,access)
311 
312 def acl_get_fd(fd):
313  """!Returns an object that represents the access control list for
314  an open file descriptor.
315  @param fd the integer file descriptor or open file object
316  @returns a new ACL object"""
317  return ACL().from_fd(fd)
318 
319 def acl_set_file(filename,acl,access=ACL_TYPE_ACCESS):
320  """!Sets the named file's access control list.
321  @param filename the name of the file of interest
322  @param acl the destination ACL object
323  @param access ACL_TYPE_ACCESS or ACL_TYPE_DEFAULT
324  @returns acl"""
325  return acl.to_file(filename)
326 
327 def acl_set_fd(fd,acl):
328  """!Given an open file descriptor, sets the corresponding file's
329  access control list.
330  @param fd the file descriptor or file object
331  @param acl the ACL object to change
332  @returns acl"""
333  return acl.to_fd(fd)
334 
335 def acl_from_text(txt):
336  """!Converts text to an access control list.
337  @param txt a text access control list
338  @returns a new ACL object"""
339  return ACL().from_text(txt)
340 
341 ########################################################################
342 # Simplified wrappers that perform common tasks:
343 
344 def copy_acl_fd(fromfd,tofd):
345  """!Copy an access control list from one object to another
346 
347  Copies a POSIX Access Control List (ACL) from one open file to
348  another. The arguments should be either UNIX file descriptors, or
349  the return values from open(). This routine is quicker than using
350  the ACL() object due to avoidance of creating unnecessary Python
351  objects. However, the access control list information is
352  discarded in this routine, so it can only be used when the sole
353  need is to copy the information from one file to another.
354  @param fromfd the source file descriptor
355  @param tofd the target file descriptor"""
356  if hasattr(fromfd,'fileno'): fromfd=fromfd.fileno()
357  if hasattr(tofd,'fileno'): tofd=tofd.fileno()
358  if libacl is None: load_libacl()
359  acl=None
360  try:
361  cfrom=ctypes.c_int(int(fromfd))
362  cdto=ctypes.c_int(int(tofd))
363  acl=libacl.acl_get_fd(cfrom)
364  errno=get_errno()
365  if acl is None or acl==0:
366  raise ACLCannotGet(
367  'Cannot read acl from descriptor %s: acl_get_fd returned NULL: %s'
368  %(repr(fromfd),os.strerror(errno)), errno)
369  res=libacl.acl_set_fd(cto,acl)
370  errno=get_errno()
371  if res is None or res!=0:
372  raise ACLCannotSet('Cannot set acl to descriptor %s: %s'
373  %(repr(getfd),os.strerror(errno)), errno)
374  finally:
375  if acl is not None and acl!=0:
376  libacl.acl_free(acl)
377  acl=None
Superclass of any ACL errors.
Definition: acl.py:15
def acl_set_file
Sets the named file's access control list.
Definition: acl.py:319
def acl_set_fd(fd, acl)
Given an open file descriptor, sets the corresponding file's access control list. ...
Definition: acl.py:327
def to_fd(self, fd)
Updates a file's file descriptor.
Definition: acl.py:224
def __init__(self, message, errno)
ACLError constructor.
Definition: acl.py:17
def acl_to_text(acl)
Returns a string representation of the given access control list object.
Definition: acl.py:297
def have_acl(self)
Returns True if this ACL has data, and False otherwise.
Definition: acl.py:164
def load_libc()
Library loading routine:
Definition: acl.py:76
def __del__(self)
Free the memory used by the ACL in libacl.
Definition: acl.py:152
Raised when the libacl library could not get a file's ACL.
Definition: acl.py:33
Raised when the libacl library could not set a file's ACL.
Definition: acl.py:35
def from_file
Copies the files's ACL into this object.
Definition: acl.py:185
Raised when a function that requires an ACL object received None, or an invalid ACL.
Definition: acl.py:28
def copy_acl_fd(fromfd, tofd)
Simplified wrappers that perform common tasks:
Definition: acl.py:344
Raised when the libacl library could not be loaded.
Definition: acl.py:26
def to_file
Updates a file's access control list.
Definition: acl.py:245
def from_fd(self, fd)
Get an access control list from a file descriptor.
Definition: acl.py:205
errno
The errno value when the error happened.
Definition: acl.py:22
def __str__(self)
=> self.to_text()
Definition: acl.py:290
def acl_get_fd(fd)
Returns an object that represents the access control list for an open file descriptor.
Definition: acl.py:312
get_errno
Function that returns the value of errno.
Definition: acl.py:71
def to_text(self)
Converts an ACL to text.
Definition: acl.py:268
def __init__(self)
Create a blank, invalid, ACL.
Definition: acl.py:146
def from_text(self, acl)
Attempts to convert the given string to an ACL, storing the result in this object.
Definition: acl.py:167
def free(self)
Frees resources used by the libacl library to store this ACL's underlying C structures.
Definition: acl.py:155
def acl_get_file
Returns an object that represents the access control list for the specified file. ...
Definition: acl.py:304
def load_libacl()
Loads the libacl library.
Definition: acl.py:97
def acl_from_text(txt)
Converts text to an access control list.
Definition: acl.py:335
ACL class wrapped around the libacl library:
Definition: acl.py:139
Raised when libacl cannot convert an ACL to text.
Definition: acl.py:31