xattr.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. """A basic extended attributes (xattr) implementation for Linux and MacOS X
  2. """
  3. import errno
  4. import os
  5. import sys
  6. import tempfile
  7. from ctypes import CDLL, create_string_buffer, c_ssize_t, c_size_t, c_char_p, c_int, c_uint32, get_errno
  8. from ctypes.util import find_library
  9. from .logger import create_logger
  10. logger = create_logger()
  11. def is_enabled(path=None):
  12. """Determine if xattr is enabled on the filesystem
  13. """
  14. with tempfile.NamedTemporaryFile(dir=path, prefix='borg-tmp') as fd:
  15. try:
  16. setxattr(fd.fileno(), 'user.name', b'value')
  17. except OSError:
  18. return False
  19. return getxattr(fd.fileno(), 'user.name') == b'value'
  20. def get_all(path, follow_symlinks=True):
  21. try:
  22. return dict((name, getxattr(path, name, follow_symlinks=follow_symlinks))
  23. for name in listxattr(path, follow_symlinks=follow_symlinks))
  24. except OSError as e:
  25. if e.errno in (errno.ENOTSUP, errno.EPERM):
  26. return {}
  27. libc_name = find_library('c')
  28. if libc_name is None:
  29. # find_library didn't work, maybe we are on some minimal system that misses essential
  30. # tools used by find_library, like ldconfig, gcc/cc, objdump.
  31. # so we can only try some "usual" names for the C library:
  32. if sys.platform.startswith('linux'):
  33. libc_name = 'libc.so.6'
  34. elif sys.platform.startswith(('freebsd', 'netbsd')):
  35. libc_name = 'libc.so'
  36. elif sys.platform == 'darwin':
  37. libc_name = 'libc.dylib'
  38. else:
  39. msg = "Can't find C library. No fallback known. Try installing ldconfig, gcc/cc or objdump."
  40. logger.error(msg)
  41. raise Exception(msg)
  42. try:
  43. libc = CDLL(libc_name, use_errno=True)
  44. except OSError as e:
  45. msg = "Can't find C library [%s]. Try installing ldconfig, gcc/cc or objdump." % e
  46. logger.error(msg)
  47. raise Exception(msg)
  48. def _check(rv, path=None):
  49. if rv < 0:
  50. raise OSError(get_errno(), path)
  51. return rv
  52. if sys.platform.startswith('linux'): # pragma: linux only
  53. libc.llistxattr.argtypes = (c_char_p, c_char_p, c_size_t)
  54. libc.llistxattr.restype = c_ssize_t
  55. libc.flistxattr.argtypes = (c_int, c_char_p, c_size_t)
  56. libc.flistxattr.restype = c_ssize_t
  57. libc.lsetxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_int)
  58. libc.lsetxattr.restype = c_int
  59. libc.fsetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_int)
  60. libc.fsetxattr.restype = c_int
  61. libc.lgetxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t)
  62. libc.lgetxattr.restype = c_ssize_t
  63. libc.fgetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t)
  64. libc.fgetxattr.restype = c_ssize_t
  65. def listxattr(path, *, follow_symlinks=True):
  66. if isinstance(path, str):
  67. path = os.fsencode(path)
  68. if isinstance(path, int):
  69. func = libc.flistxattr
  70. elif follow_symlinks:
  71. func = libc.listxattr
  72. else:
  73. func = libc.llistxattr
  74. n = _check(func(path, None, 0), path)
  75. if n == 0:
  76. return []
  77. namebuf = create_string_buffer(n)
  78. n2 = _check(func(path, namebuf, n), path)
  79. if n2 != n:
  80. raise Exception('listxattr failed')
  81. return [os.fsdecode(name) for name in namebuf.raw.split(b'\0')[:-1] if not name.startswith(b'system.posix_acl_')]
  82. def getxattr(path, name, *, follow_symlinks=True):
  83. name = os.fsencode(name)
  84. if isinstance(path, str):
  85. path = os.fsencode(path)
  86. if isinstance(path, int):
  87. func = libc.fgetxattr
  88. elif follow_symlinks:
  89. func = libc.getxattr
  90. else:
  91. func = libc.lgetxattr
  92. n = _check(func(path, name, None, 0))
  93. if n == 0:
  94. return
  95. valuebuf = create_string_buffer(n)
  96. n2 = _check(func(path, name, valuebuf, n), path)
  97. if n2 != n:
  98. raise Exception('getxattr failed')
  99. return valuebuf.raw
  100. def setxattr(path, name, value, *, follow_symlinks=True):
  101. name = os.fsencode(name)
  102. value = value and os.fsencode(value)
  103. if isinstance(path, str):
  104. path = os.fsencode(path)
  105. if isinstance(path, int):
  106. func = libc.fsetxattr
  107. elif follow_symlinks:
  108. func = libc.setxattr
  109. else:
  110. func = libc.lsetxattr
  111. _check(func(path, name, value, len(value) if value else 0, 0), path)
  112. elif sys.platform == 'darwin': # pragma: darwin only
  113. libc.listxattr.argtypes = (c_char_p, c_char_p, c_size_t, c_int)
  114. libc.listxattr.restype = c_ssize_t
  115. libc.flistxattr.argtypes = (c_int, c_char_p, c_size_t)
  116. libc.flistxattr.restype = c_ssize_t
  117. libc.setxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
  118. libc.setxattr.restype = c_int
  119. libc.fsetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
  120. libc.fsetxattr.restype = c_int
  121. libc.getxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
  122. libc.getxattr.restype = c_ssize_t
  123. libc.fgetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
  124. libc.fgetxattr.restype = c_ssize_t
  125. XATTR_NOFOLLOW = 0x0001
  126. def listxattr(path, *, follow_symlinks=True):
  127. func = libc.listxattr
  128. flags = 0
  129. if isinstance(path, str):
  130. path = os.fsencode(path)
  131. if isinstance(path, int):
  132. func = libc.flistxattr
  133. elif not follow_symlinks:
  134. flags = XATTR_NOFOLLOW
  135. n = _check(func(path, None, 0, flags), path)
  136. if n == 0:
  137. return []
  138. namebuf = create_string_buffer(n)
  139. n2 = _check(func(path, namebuf, n, flags), path)
  140. if n2 != n:
  141. raise Exception('listxattr failed')
  142. return [os.fsdecode(name) for name in namebuf.raw.split(b'\0')[:-1]]
  143. def getxattr(path, name, *, follow_symlinks=True):
  144. name = os.fsencode(name)
  145. func = libc.getxattr
  146. flags = 0
  147. if isinstance(path, str):
  148. path = os.fsencode(path)
  149. if isinstance(path, int):
  150. func = libc.fgetxattr
  151. elif not follow_symlinks:
  152. flags = XATTR_NOFOLLOW
  153. n = _check(func(path, name, None, 0, 0, flags))
  154. if n == 0:
  155. return
  156. valuebuf = create_string_buffer(n)
  157. n2 = _check(func(path, name, valuebuf, n, 0, flags), path)
  158. if n2 != n:
  159. raise Exception('getxattr failed')
  160. return valuebuf.raw
  161. def setxattr(path, name, value, *, follow_symlinks=True):
  162. name = os.fsencode(name)
  163. value = value and os.fsencode(value)
  164. func = libc.setxattr
  165. flags = 0
  166. if isinstance(path, str):
  167. path = os.fsencode(path)
  168. if isinstance(path, int):
  169. func = libc.fsetxattr
  170. elif not follow_symlinks:
  171. flags = XATTR_NOFOLLOW
  172. _check(func(path, name, value, len(value) if value else 0, 0, flags), path)
  173. elif sys.platform.startswith('freebsd'): # pragma: freebsd only
  174. EXTATTR_NAMESPACE_USER = 0x0001
  175. libc.extattr_list_fd.argtypes = (c_int, c_int, c_char_p, c_size_t)
  176. libc.extattr_list_fd.restype = c_ssize_t
  177. libc.extattr_list_link.argtypes = (c_char_p, c_int, c_char_p, c_size_t)
  178. libc.extattr_list_link.restype = c_ssize_t
  179. libc.extattr_list_file.argtypes = (c_char_p, c_int, c_char_p, c_size_t)
  180. libc.extattr_list_file.restype = c_ssize_t
  181. libc.extattr_get_fd.argtypes = (c_int, c_int, c_char_p, c_char_p, c_size_t)
  182. libc.extattr_get_fd.restype = c_ssize_t
  183. libc.extattr_get_link.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
  184. libc.extattr_get_link.restype = c_ssize_t
  185. libc.extattr_get_file.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
  186. libc.extattr_get_file.restype = c_ssize_t
  187. libc.extattr_set_fd.argtypes = (c_int, c_int, c_char_p, c_char_p, c_size_t)
  188. libc.extattr_set_fd.restype = c_int
  189. libc.extattr_set_link.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
  190. libc.extattr_set_link.restype = c_int
  191. libc.extattr_set_file.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
  192. libc.extattr_set_file.restype = c_int
  193. def listxattr(path, *, follow_symlinks=True):
  194. ns = EXTATTR_NAMESPACE_USER
  195. if isinstance(path, str):
  196. path = os.fsencode(path)
  197. if isinstance(path, int):
  198. func = libc.extattr_list_fd
  199. elif follow_symlinks:
  200. func = libc.extattr_list_file
  201. else:
  202. func = libc.extattr_list_link
  203. n = _check(func(path, ns, None, 0), path)
  204. if n == 0:
  205. return []
  206. namebuf = create_string_buffer(n)
  207. n2 = _check(func(path, ns, namebuf, n), path)
  208. if n2 != n:
  209. raise Exception('listxattr failed')
  210. names = []
  211. mv = memoryview(namebuf.raw)
  212. while mv:
  213. length = mv[0]
  214. names.append(os.fsdecode(bytes(mv[1:1+length])))
  215. mv = mv[1+length:]
  216. return names
  217. def getxattr(path, name, *, follow_symlinks=True):
  218. name = os.fsencode(name)
  219. if isinstance(path, str):
  220. path = os.fsencode(path)
  221. if isinstance(path, int):
  222. func = libc.extattr_get_fd
  223. elif follow_symlinks:
  224. func = libc.extattr_get_file
  225. else:
  226. func = libc.extattr_get_link
  227. n = _check(func(path, EXTATTR_NAMESPACE_USER, name, None, 0))
  228. if n == 0:
  229. return
  230. valuebuf = create_string_buffer(n)
  231. n2 = _check(func(path, EXTATTR_NAMESPACE_USER, name, valuebuf, n), path)
  232. if n2 != n:
  233. raise Exception('getxattr failed')
  234. return valuebuf.raw
  235. def setxattr(path, name, value, *, follow_symlinks=True):
  236. name = os.fsencode(name)
  237. value = value and os.fsencode(value)
  238. if isinstance(path, str):
  239. path = os.fsencode(path)
  240. if isinstance(path, int):
  241. func = libc.extattr_set_fd
  242. elif follow_symlinks:
  243. func = libc.extattr_set_file
  244. else:
  245. func = libc.extattr_set_link
  246. _check(func(path, EXTATTR_NAMESPACE_USER, name, value, len(value) if value else 0), path)
  247. else: # pragma: unknown platform only
  248. def listxattr(path, *, follow_symlinks=True):
  249. return []
  250. def getxattr(path, name, *, follow_symlinks=True):
  251. pass
  252. def setxattr(path, name, value, *, follow_symlinks=True):
  253. pass