xattr.py 14 KB

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