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
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
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.
27 libc
=ctypes
.cdll
.LoadLibrary('')
29 libc
=ctypes
.cdll
.LoadLibrary('libc.so')
32 # No ctypes or can't find libc
35 class NoXAttr(OSError):
36 """Exception thrown when xattr support is requested but is not
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'):
46 libc_errno
=ctypes
.c_int
.in_dll(libc
, 'errno')
48 return libc_errno
.value
49 def _error_check(res
, path
):
51 raise OSError(_get_errno(), os
.strerror(_get_errno()), path
)
54 _PC_XATTR_ENABLED
=os
.pathconf_names
['PC_XATTR_ENABLED']
56 _PC_XATTR_ENABLED
=100 # Solaris 9
59 _PC_XATTR_EXISTS
=os
.pathconf_names
['PC_XATTR_EXISTS']
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."""
75 return os
.pathconf(path
, _PC_XATTR_ENABLED
)
78 """Return True if extended attributes exist on the named file."""
80 return os
.pathconf(path
, _PC_XATTR_EXISTS
)>0
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:
97 fd
=libc
.attropen(path
, attr
, os
.O_RDONLY
, 0)
103 buf
=os
.read(fd
, 1024)
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:
124 fd
=libc
.attropen(path
, '.', os
.O_RDONLY
, 0)
130 attrs
=os
.listdir('.')
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
)
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)
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)."""
180 elif libc
and hasattr(libc
, 'getxattr'):
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')
193 libc_errno
=ctypes
.c_int(errno
.EOPNOTSUP
)
196 return libc_errno
.value
197 def _error_check(res
, path
):
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."""
213 if not os
.access(path
, os
.F_OK
):
214 raise OSError(errno
.ENOENT
, 'No such file or directory', 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)
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)
245 buf
=ctypes
.c_buffer(size
+1)
246 libc
.getxattr(path
, attr
, ctypes
.byref(buf
), size
)
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)
262 buf
=ctypes
.create_string_buffer(size
)
263 n
=libc
.listxattr(path
, ctypes
.byref(buf
), size
)
264 names
=buf
.raw
[:-1].split('\0')
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.'):
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)."""
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."""
327 """Return True if extended attributes exist on the named file."""
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
)
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
)
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."""
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."""
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."""
383 def binary_value_supported():
384 """Returns True if the value of an extended attribute may contain
385 embedded NUL characters (ASCII 0)."""
389 if __name__
=='__main__':
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
)