Moved into a sub-dir, so that a svn checkout has the same structure as
[rox-lib.git] / ROX-Lib2 / python / rox / xattr.py
blobba16ba631fc48c97fd0d0a5d4d6cb23207d7cd72
1 """
2 Extended attribute support.
4 Two versions of the extended attribute API are recognized. If the 'getxattr'
5 function is detected then the functions used are
6 getxattr/setxattr/listxattr/removexattr. This is the API found on Linux
7 and some others.
9 If the 'attropen' function is found then the functions used are
10 attropen and unlinkat. This is the API found on Solaris.
12 If neither version is detected then a set of dummy functions are generated
13 which have no affect.
15 You should check the return value of the function supported(). Without
16 an argument this returns whether extended attribute support is available.
18 This module requires the ctypes module. This is part of Python 2.5 and
19 available as an extension for earlier versions of Python.
20 """
22 import os, sys, errno
24 try:
25 import ctypes
26 try:
27 libc=ctypes.cdll.LoadLibrary('')
28 except:
29 libc=ctypes.cdll.LoadLibrary('libc.so')
31 except:
32 # No ctypes or can't find libc
33 libc=None
35 class NoXAttr(OSError):
36 """Exception thrown when xattr support is requested but is not
37 available."""
38 def __init__(self, path):
39 OSError.__init__(self, errno.EOPNOTSUP, 'No xattr support',path)
41 # Well known extended attribute names.
42 USER_MIME_TYPE = 'user.mime_type'
44 if libc and hasattr(libc, 'attropen'):
45 # Solaris style
46 libc_errno=ctypes.c_int.in_dll(libc, 'errno')
47 def _get_errno():
48 return libc_errno.value
49 def _error_check(res, path):
50 if res<0:
51 raise OSError(_get_errno(), os.strerror(_get_errno()), path)
53 try:
54 _PC_XATTR_ENABLED=os.pathconf_names['PC_XATTR_ENABLED']
55 except:
56 _PC_XATTR_ENABLED=100 # Solaris 9
58 try:
59 _PC_XATTR_EXISTS=os.pathconf_names['PC_XATTR_EXISTS']
60 except:
61 _PC_XATTR_EXISTS=101 # Solaris 9
63 def supported(path=None):
64 """Detect whether extended attribute support is available.
66 path - path to file to check for extended attribute support.
67 Availablity can vary from one file system to another.
69 If path is None then return True if the system in general supports
70 extended attributes."""
72 if not path:
73 return True
75 return os.pathconf(path, _PC_XATTR_ENABLED)
77 def present(path):
78 """Return True if extended attributes exist on the named file."""
80 return os.pathconf(path, _PC_XATTR_EXISTS)>0
82 def get(path, attr):
83 """Get an extended attribute on a specified file.
85 path - path name of file to check
86 attr - name of attribute to get
88 None is returned if the named attribute does not exist. OSError
89 is raised if path does not exist or is not readable."""
91 if not os.access(path, os.F_OK):
92 raise OSError(errno.ENOENT, 'No such file or directory', path)
94 if os.pathconf(path, _PC_XATTR_EXISTS)<=0:
95 return
97 fd=libc.attropen(path, attr, os.O_RDONLY, 0)
98 if fd<0:
99 return
101 v=''
102 while True:
103 buf=os.read(fd, 1024)
104 if len(buf)<1:
105 break
106 v+=buf
108 libc.close(fd)
110 return v
112 def listx(path):
113 """Return a list of extended attributes set on the named file.
115 path - path name of file to check
117 OSError is raised if path does not exist or is not readable."""
118 if not os.access(path, os.F_OK):
119 raise OSError(errno.ENOENT, 'No such file or directory', path)
121 if os.pathconf(path, _PC_XATTR_EXISTS)<=0:
122 return []
124 fd=libc.attropen(path, '.', os.O_RDONLY, 0)
125 if fd<0:
126 return []
128 odir=os.getcwd()
129 os.fchdir(fd)
130 attrs=os.listdir('.')
131 os.chdir(odir)
132 libc.close(fd)
134 return attrs
136 def set(path, attr, value):
137 """Set an extended attribute on a specified file.
139 path - path name of file to check
140 attr - name of attribute to set
141 value - value of attribute to set
143 OSError is raised if path does not exist or is not writable."""
145 fd=libc.attropen(path, attr, os.O_WRONLY|os.O_CREAT, 0644)
146 _error_check(fd, path)
148 res=os.write(fd, value)
149 libc.close(fd)
150 _error_check(res, path)
152 def delete(path, attr):
153 """Delete an extended attribute from a specified file.
155 path - path name of file to check
156 attr - name of attribute to delete
158 OSError is raised if an error occurs."""
160 fd=libc.attropen(path, '.', os.O_RDONLY, 0)
161 _error_check(fd, path)
163 res=libc.unlinkat(fd, attr, 0)
164 libc.close(fd)
165 _error_check(res, path)
167 name_invalid_chars='/\0'
168 def name_valid(name):
169 """Check that name is a valid name for an extended attibute.
170 False is returned if the name should not be used."""
172 return name_invalid_chars not in name
174 def binary_value_supported():
175 """Returns True if the value of an extended attribute may contain
176 embedded NUL characters (ASCII 0)."""
178 return True
180 elif libc and hasattr(libc, 'getxattr'):
181 # Linux style
183 # Find out how to access errno. The wrong way may cause SIGSEGV!
184 if hasattr(libc, '__errno_location'):
185 libc.__errno_location.restype=ctypes.c_int
186 errno_loc=libc.__errno_location()
187 libc_errno=ctypes.c_int.from_address(errno_loc)
189 elif hasattr(libc, 'errno'):
190 libc_errno=ctypes.c_int.in_dll(lib, 'errno')
192 else:
193 libc_errno=ctypes.c_int(errno.EOPNOTSUP)
195 def _get_errno():
196 return libc_errno.value
197 def _error_check(res, path):
198 if res<0:
199 raise OSError(_get_errno(), os.strerror(_get_errno()), path)
201 def supported(path=None):
202 """Detect whether extended attribute support is available.
204 path - path to file to check for extended attribute support.
205 Availablity can vary from one file system to another.
207 If path is None then return True if the system in general supports
208 extended attributes."""
210 if not path:
211 return False
213 if not os.access(path, os.F_OK):
214 raise OSError(errno.ENOENT, 'No such file or directory', path)
216 return True
218 def present(path):
219 """Return True if extended attributes exist on the named file."""
221 if not os.access(path, os.F_OK):
222 raise OSError(errno.ENOENT, 'No such file or directory', path)
224 buf=ctypes.c_buffer(1024)
225 n=libc.listxattr(path, ctypes.byref(buf), 1024)
227 return n>0
229 def get(path, attr):
230 """Get an extended attribute on a specified file.
232 path - path name of file to check
233 attr - name of attribute to get
235 None is returned if the named attribute does not exist. OSError
236 is raised if path does not exist or is not readable."""
238 if not os.access(path, os.F_OK):
239 raise OSError(errno.ENOENT, 'No such file or directory', path)
241 size=libc.getxattr(path, attr, '', 0)
242 if size<0:
243 return
245 buf=ctypes.c_buffer(size+1)
246 libc.getxattr(path, attr, ctypes.byref(buf), size)
247 return buf.value
249 def listx(path):
250 """Return a list of extended attributes set on the named file.
252 path - path name of file to check
254 OSError is raised if path does not exist or is not readable."""
255 if not os.access(path, os.F_OK):
256 raise OSError(errno.ENOENT, 'No such file or directory', path)
258 size=libc.listxattr(path, None, 0)
260 if size<1:
261 return []
262 buf=ctypes.create_string_buffer(size)
263 n=libc.listxattr(path, ctypes.byref(buf), size)
264 names=buf.raw[:-1].split('\0')
265 return names
267 def set(path, attr, value):
268 """Set an extended attribute on a specified file.
270 path - path name of file to check
271 attr - name of attribute to set
272 value - value of attribute to set
274 OSError is raised if path does not exist or is not writable."""
276 if not os.access(path, os.F_OK):
277 raise OSError(errno.ENOENT, 'No such file or directory', path)
279 res=libc.setxattr(path, attr, value, len(value), 0)
280 _error_check(res, path)
283 def delete(path, attr):
284 """Delete an extended attribute from a specified file.
286 path - path name of file to check
287 attr - name of attribute to delete
289 OSError is raised if an error occurs."""
291 if not os.access(path, os.F_OK):
292 raise OSError(errno.ENOENT, 'No such file or directory', path)
294 res=libc.removexattr(path, attr)
295 _error_check(res, path)
297 name_invalid_chars='\0'
298 def name_valid(name):
299 """Check that name is a valid name for an extended attibute.
300 False is returned if the name should not be used."""
302 if not name.startswith('user.'):
303 return False
304 return name_invalid_chars not in name
306 def binary_value_supported():
307 """Returns True if the value of an extended attribute may contain
308 embedded NUL characters (ASCII 0)."""
310 return False
312 else:
313 # No available xattr support
315 def supported(path=None):
316 """Detect whether extended attribute support is available.
318 path - path to file to check for extended attribute support.
319 Availablity can vary from one file system to another.
321 If path is None then return True if the system in general supports
322 extended attributes."""
324 return False
326 def present(path):
327 """Return True if extended attributes exist on the named file."""
329 return False
331 def get(path, attr):
332 """Get an extended attribute on a specified file.
334 path - path name of file to check
335 attr - name of attribute to get
337 None is returned if the named attribute does not exist. OSError
338 is raised if path does not exist or is not readable."""
340 if not os.access(path, os.F_OK):
341 raise OSError(errno.ENOENT, 'No such file or directory', path)
343 return
345 def listx(path):
346 """Return a list of extended attributes set on the named file.
348 path - path name of file to check
350 OSError is raised if path does not exist or is not readable."""
351 if not os.access(path, os.F_OK):
352 raise OSError(errno.ENOENT, 'No such file or directory', path)
354 return []
356 def set(path, attr, value):
357 """Set an extended attribute on a specified file.
359 path - path name of file to check
360 attr - name of attribute to set
361 value - value of attribute to set
363 OSError is raised if path does not exist or is not writable."""
365 raise NoXAttr(path)
367 def delete(path, attr):
368 """Delete an extended attribute from a specified file.
370 path - path name of file to check
371 attr - name of attribute to delete
373 OSError is raised if an error occurs."""
375 raise NoXAttr(path)
377 def name_valid(name):
378 """Check that name is a valid name for an extended attibute.
379 False is returned if the name should not be used."""
381 return False
383 def binary_value_supported():
384 """Returns True if the value of an extended attribute may contain
385 embedded NUL characters (ASCII 0)."""
387 return False
389 if __name__=='__main__':
390 # Run some tests.
392 if len(sys.argv)>1:
393 path=sys.argv[1]
394 else:
395 path='/tmp'
397 print path, supported(path)
398 print path, present(path)
399 print path, get(path, 'user.mime_type')
400 print path, listx(path)
402 set(path, 'user.test', 'this is a test')
403 print path, listx(path)
404 print path, get(path, 'user.test')
405 delete(path, 'user.test')
406 print path, listx(path)