xattr.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. """A basic extended attributes (xattr) implementation for Linux and MacOS X
  2. """
  3. import errno
  4. import os
  5. import re
  6. import subprocess
  7. import sys
  8. import tempfile
  9. import threading
  10. from ctypes import CDLL, create_string_buffer, c_ssize_t, c_size_t, c_char_p, c_int, c_uint32, get_errno
  11. from ctypes.util import find_library
  12. from distutils.version import LooseVersion
  13. from .logger import create_logger
  14. logger = create_logger()
  15. def get_buffer(size=None, init=False):
  16. if size is not None:
  17. size = int(size)
  18. assert size < 2 ** 24
  19. if init or len(thread_local.buffer) < size:
  20. thread_local.buffer = create_string_buffer(size)
  21. return thread_local.buffer
  22. thread_local = threading.local()
  23. get_buffer(size=4096, init=True)
  24. def is_enabled(path=None):
  25. """Determine if xattr is enabled on the filesystem
  26. """
  27. with tempfile.NamedTemporaryFile(dir=path, prefix='borg-tmp') as fd:
  28. try:
  29. setxattr(fd.fileno(), 'user.name', b'value')
  30. except OSError:
  31. return False
  32. return getxattr(fd.fileno(), 'user.name') == b'value'
  33. def get_all(path, follow_symlinks=True):
  34. try:
  35. return dict((name, getxattr(path, name, follow_symlinks=follow_symlinks))
  36. for name in listxattr(path, follow_symlinks=follow_symlinks))
  37. except OSError as e:
  38. if e.errno in (errno.ENOTSUP, errno.EPERM):
  39. return {}
  40. libc_name = find_library('c')
  41. if libc_name is None:
  42. # find_library didn't work, maybe we are on some minimal system that misses essential
  43. # tools used by find_library, like ldconfig, gcc/cc, objdump.
  44. # so we can only try some "usual" names for the C library:
  45. if sys.platform.startswith('linux'):
  46. libc_name = 'libc.so.6'
  47. elif sys.platform.startswith(('freebsd', 'netbsd')):
  48. libc_name = 'libc.so'
  49. elif sys.platform == 'darwin':
  50. libc_name = 'libc.dylib'
  51. else:
  52. msg = "Can't find C library. No fallback known. Try installing ldconfig, gcc/cc or objdump."
  53. logger.error(msg)
  54. raise Exception(msg)
  55. # If we are running with fakeroot on Linux, then use the xattr functions of fakeroot. This is needed by
  56. # the 'test_extract_capabilities' test, but also allows xattrs to work with fakeroot on Linux in normal use.
  57. # TODO: Check whether fakeroot supports xattrs on all platforms supported below.
  58. # TODO: If that's the case then we can make Borg fakeroot-xattr-compatible on these as well.
  59. try:
  60. XATTR_FAKEROOT = False
  61. if sys.platform.startswith('linux'):
  62. LD_PRELOAD = os.environ.get('LD_PRELOAD', '')
  63. preloads = re.split("[ :]", LD_PRELOAD)
  64. for preload in preloads:
  65. if preload.startswith("libfakeroot"):
  66. fakeroot_version = LooseVersion(subprocess.check_output(['fakeroot', '-v']).decode('ascii').split()[-1])
  67. if fakeroot_version >= LooseVersion("1.20.2"):
  68. # 1.20.2 has been confirmed to have xattr support
  69. # 1.18.2 has been confirmed not to have xattr support
  70. # Versions in-between are unknown
  71. libc_name = preload
  72. XATTR_FAKEROOT = True
  73. break
  74. except:
  75. pass
  76. try:
  77. libc = CDLL(libc_name, use_errno=True)
  78. except OSError as e:
  79. msg = "Can't find C library [%s]. Try installing ldconfig, gcc/cc or objdump." % e
  80. raise Exception(msg)
  81. class BufferTooSmallError(Exception):
  82. """the buffer given to an xattr function was too small for the result"""
  83. def _check(rv, path=None, detect_buffer_too_small=False):
  84. if rv < 0:
  85. e = get_errno()
  86. if detect_buffer_too_small and e == errno.ERANGE:
  87. # listxattr and getxattr signal with ERANGE that they need a bigger result buffer.
  88. # setxattr signals this way that e.g. a xattr key name is too long / inacceptable.
  89. raise BufferTooSmallError
  90. else:
  91. try:
  92. msg = os.strerror(e)
  93. except ValueError:
  94. msg = ''
  95. if isinstance(path, int):
  96. path = '<FD %d>' % path
  97. raise OSError(e, msg, path)
  98. return rv
  99. def _listxattr_inner(func, path):
  100. if isinstance(path, str):
  101. path = os.fsencode(path)
  102. size = len(get_buffer())
  103. while True:
  104. buf = get_buffer(size)
  105. try:
  106. n = _check(func(path, buf, size), path, detect_buffer_too_small=True)
  107. except BufferTooSmallError:
  108. size *= 2
  109. else:
  110. return n, buf.raw
  111. def _getxattr_inner(func, path, name):
  112. if isinstance(path, str):
  113. path = os.fsencode(path)
  114. name = os.fsencode(name)
  115. size = len(get_buffer())
  116. while True:
  117. buf = get_buffer(size)
  118. try:
  119. n = _check(func(path, name, buf, size), path, detect_buffer_too_small=True)
  120. except BufferTooSmallError:
  121. size *= 2
  122. else:
  123. return n, buf.raw
  124. def _setxattr_inner(func, path, name, value):
  125. if isinstance(path, str):
  126. path = os.fsencode(path)
  127. name = os.fsencode(name)
  128. value = value and os.fsencode(value)
  129. size = len(value) if value else 0
  130. _check(func(path, name, value, size), path, detect_buffer_too_small=False)
  131. if sys.platform.startswith('linux'): # pragma: linux only
  132. libc.llistxattr.argtypes = (c_char_p, c_char_p, c_size_t)
  133. libc.llistxattr.restype = c_ssize_t
  134. libc.flistxattr.argtypes = (c_int, c_char_p, c_size_t)
  135. libc.flistxattr.restype = c_ssize_t
  136. libc.lsetxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_int)
  137. libc.lsetxattr.restype = c_int
  138. libc.fsetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_int)
  139. libc.fsetxattr.restype = c_int
  140. libc.lgetxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t)
  141. libc.lgetxattr.restype = c_ssize_t
  142. libc.fgetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t)
  143. libc.fgetxattr.restype = c_ssize_t
  144. def listxattr(path, *, follow_symlinks=True):
  145. def func(path, buf, size):
  146. if isinstance(path, int):
  147. return libc.flistxattr(path, buf, size)
  148. else:
  149. if follow_symlinks:
  150. return libc.listxattr(path, buf, size)
  151. else:
  152. return libc.llistxattr(path, buf, size)
  153. n, buf = _listxattr_inner(func, path)
  154. if n == 0:
  155. return []
  156. names = buf[:n].split(b'\0')[:-1]
  157. return [os.fsdecode(name) for name in names
  158. if not name.startswith(b'system.posix_acl_')]
  159. def getxattr(path, name, *, follow_symlinks=True):
  160. def func(path, name, buf, size):
  161. if isinstance(path, int):
  162. return libc.fgetxattr(path, name, buf, size)
  163. else:
  164. if follow_symlinks:
  165. return libc.getxattr(path, name, buf, size)
  166. else:
  167. return libc.lgetxattr(path, name, buf, size)
  168. n, buf = _getxattr_inner(func, path, name)
  169. if n == 0:
  170. return
  171. return buf[:n]
  172. def setxattr(path, name, value, *, follow_symlinks=True):
  173. def func(path, name, value, size):
  174. flags = 0
  175. if isinstance(path, int):
  176. return libc.fsetxattr(path, name, value, size, flags)
  177. else:
  178. if follow_symlinks:
  179. return libc.setxattr(path, name, value, size, flags)
  180. else:
  181. return libc.lsetxattr(path, name, value, size, flags)
  182. _setxattr_inner(func, path, name, value)
  183. elif sys.platform == 'darwin': # pragma: darwin only
  184. libc.listxattr.argtypes = (c_char_p, c_char_p, c_size_t, c_int)
  185. libc.listxattr.restype = c_ssize_t
  186. libc.flistxattr.argtypes = (c_int, c_char_p, c_size_t)
  187. libc.flistxattr.restype = c_ssize_t
  188. libc.setxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
  189. libc.setxattr.restype = c_int
  190. libc.fsetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
  191. libc.fsetxattr.restype = c_int
  192. libc.getxattr.argtypes = (c_char_p, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
  193. libc.getxattr.restype = c_ssize_t
  194. libc.fgetxattr.argtypes = (c_int, c_char_p, c_char_p, c_size_t, c_uint32, c_int)
  195. libc.fgetxattr.restype = c_ssize_t
  196. XATTR_NOFLAGS = 0x0000
  197. XATTR_NOFOLLOW = 0x0001
  198. def listxattr(path, *, follow_symlinks=True):
  199. def func(path, buf, size):
  200. if isinstance(path, int):
  201. return libc.flistxattr(path, buf, size, XATTR_NOFLAGS)
  202. else:
  203. if follow_symlinks:
  204. return libc.listxattr(path, buf, size, XATTR_NOFLAGS)
  205. else:
  206. return libc.listxattr(path, buf, size, XATTR_NOFOLLOW)
  207. n, buf = _listxattr_inner(func, path)
  208. if n == 0:
  209. return []
  210. names = buf[:n].split(b'\0')[:-1]
  211. return [os.fsdecode(name) for name in names]
  212. def getxattr(path, name, *, follow_symlinks=True):
  213. def func(path, name, buf, size):
  214. if isinstance(path, int):
  215. return libc.fgetxattr(path, name, buf, size, 0, XATTR_NOFLAGS)
  216. else:
  217. if follow_symlinks:
  218. return libc.getxattr(path, name, buf, size, 0, XATTR_NOFLAGS)
  219. else:
  220. return libc.getxattr(path, name, buf, size, 0, XATTR_NOFOLLOW)
  221. n, buf = _getxattr_inner(func, path, name)
  222. if n == 0:
  223. return
  224. return buf[:n]
  225. def setxattr(path, name, value, *, follow_symlinks=True):
  226. def func(path, name, value, size):
  227. if isinstance(path, int):
  228. return libc.fsetxattr(path, name, value, size, 0, XATTR_NOFLAGS)
  229. else:
  230. if follow_symlinks:
  231. return libc.setxattr(path, name, value, size, 0, XATTR_NOFLAGS)
  232. else:
  233. return libc.setxattr(path, name, value, size, 0, XATTR_NOFOLLOW)
  234. _setxattr_inner(func, path, name, value)
  235. elif sys.platform.startswith('freebsd'): # pragma: freebsd only
  236. libc.extattr_list_fd.argtypes = (c_int, c_int, c_char_p, c_size_t)
  237. libc.extattr_list_fd.restype = c_ssize_t
  238. libc.extattr_list_link.argtypes = (c_char_p, c_int, c_char_p, c_size_t)
  239. libc.extattr_list_link.restype = c_ssize_t
  240. libc.extattr_list_file.argtypes = (c_char_p, c_int, c_char_p, c_size_t)
  241. libc.extattr_list_file.restype = c_ssize_t
  242. libc.extattr_get_fd.argtypes = (c_int, c_int, c_char_p, c_char_p, c_size_t)
  243. libc.extattr_get_fd.restype = c_ssize_t
  244. libc.extattr_get_link.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
  245. libc.extattr_get_link.restype = c_ssize_t
  246. libc.extattr_get_file.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
  247. libc.extattr_get_file.restype = c_ssize_t
  248. libc.extattr_set_fd.argtypes = (c_int, c_int, c_char_p, c_char_p, c_size_t)
  249. libc.extattr_set_fd.restype = c_int
  250. libc.extattr_set_link.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
  251. libc.extattr_set_link.restype = c_int
  252. libc.extattr_set_file.argtypes = (c_char_p, c_int, c_char_p, c_char_p, c_size_t)
  253. libc.extattr_set_file.restype = c_int
  254. ns = EXTATTR_NAMESPACE_USER = 0x0001
  255. def listxattr(path, *, follow_symlinks=True):
  256. def func(path, buf, size):
  257. if isinstance(path, int):
  258. return libc.extattr_list_fd(path, ns, buf, size)
  259. else:
  260. if follow_symlinks:
  261. return libc.extattr_list_file(path, ns, buf, size)
  262. else:
  263. return libc.extattr_list_link(path, ns, buf, size)
  264. n, buf = _listxattr_inner(func, path)
  265. if n == 0:
  266. return []
  267. names = []
  268. mv = memoryview(buf)
  269. while mv:
  270. length = mv[0]
  271. names.append(os.fsdecode(bytes(mv[1:1 + length])))
  272. mv = mv[1 + length:]
  273. return names
  274. def getxattr(path, name, *, follow_symlinks=True):
  275. def func(path, name, buf, size):
  276. if isinstance(path, int):
  277. return libc.extattr_get_fd(path, ns, name, buf, size)
  278. else:
  279. if follow_symlinks:
  280. return libc.extattr_get_file(path, ns, name, buf, size)
  281. else:
  282. return libc.extattr_get_link(path, ns, name, buf, size)
  283. n, buf = _getxattr_inner(func, path, name)
  284. if n == 0:
  285. return
  286. return buf[:n]
  287. def setxattr(path, name, value, *, follow_symlinks=True):
  288. def func(path, name, value, size):
  289. if isinstance(path, int):
  290. return libc.extattr_set_fd(path, ns, name, value, size)
  291. else:
  292. if follow_symlinks:
  293. return libc.extattr_set_file(path, ns, name, value, size)
  294. else:
  295. return libc.extattr_set_link(path, ns, name, value, size)
  296. _setxattr_inner(func, path, name, value)
  297. else: # pragma: unknown platform only
  298. def listxattr(path, *, follow_symlinks=True):
  299. return []
  300. def getxattr(path, name, *, follow_symlinks=True):
  301. pass
  302. def setxattr(path, name, value, *, follow_symlinks=True):
  303. pass