freebsd.pyx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import os
  2. import stat
  3. from libc cimport errno
  4. from .posix import posix_acl_use_stored_uid_gid
  5. from ..helpers import safe_encode, safe_decode
  6. from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, split_lstring
  7. API_VERSION = '1.2_05'
  8. cdef extern from "sys/extattr.h":
  9. ssize_t c_extattr_list_file "extattr_list_file" (const char *path, int attrnamespace, void *data, size_t nbytes)
  10. ssize_t c_extattr_list_link "extattr_list_link" (const char *path, int attrnamespace, void *data, size_t nbytes)
  11. ssize_t c_extattr_list_fd "extattr_list_fd" (int fd, int attrnamespace, void *data, size_t nbytes)
  12. ssize_t c_extattr_get_file "extattr_get_file" (const char *path, int attrnamespace, const char *attrname, void *data, size_t nbytes)
  13. ssize_t c_extattr_get_link "extattr_get_link" (const char *path, int attrnamespace, const char *attrname, void *data, size_t nbytes)
  14. ssize_t c_extattr_get_fd "extattr_get_fd" (int fd, int attrnamespace, const char *attrname, void *data, size_t nbytes)
  15. int c_extattr_set_file "extattr_set_file" (const char *path, int attrnamespace, const char *attrname, const void *data, size_t nbytes)
  16. int c_extattr_set_link "extattr_set_link" (const char *path, int attrnamespace, const char *attrname, const void *data, size_t nbytes)
  17. int c_extattr_set_fd "extattr_set_fd" (int fd, int attrnamespace, const char *attrname, const void *data, size_t nbytes)
  18. int EXTATTR_NAMESPACE_USER
  19. cdef extern from "sys/types.h":
  20. int ACL_TYPE_ACCESS
  21. int ACL_TYPE_DEFAULT
  22. int ACL_TYPE_NFS4
  23. cdef extern from "sys/acl.h":
  24. ctypedef struct _acl_t:
  25. pass
  26. ctypedef _acl_t *acl_t
  27. int acl_free(void *obj)
  28. acl_t acl_get_link_np(const char *path, int type)
  29. acl_t acl_get_fd_np(int fd, int type)
  30. int acl_set_link_np(const char *path, int type, acl_t acl)
  31. int acl_set_fd_np(int fd, acl_t acl, int type)
  32. acl_t acl_from_text(const char *buf)
  33. char *acl_to_text_np(acl_t acl, ssize_t *len, int flags)
  34. int ACL_TEXT_NUMERIC_IDS
  35. int ACL_TEXT_APPEND_ID
  36. int acl_extended_link_np(const char * path) # check also: acl_is_trivial_np
  37. cdef extern from "unistd.h":
  38. long lpathconf(const char *path, int name)
  39. int _PC_ACL_NFS4
  40. # On FreeBSD, borg currently only deals with the USER namespace as it is unclear
  41. # whether (and if so, how exactly) it should deal with the SYSTEM namespace.
  42. NS_ID_MAP = {b"user": EXTATTR_NAMESPACE_USER, }
  43. def split_ns(ns_name, default_ns):
  44. # split ns_name (which is in the form of b"namespace.name") into namespace and name.
  45. # if there is no namespace given in ns_name, default to default_ns.
  46. # note:
  47. # borg < 1.1.10 on FreeBSD did not prefix the namespace to the names, see #3952.
  48. # we also need to deal with "unexpected" namespaces here, they could come
  49. # from borg archives made on other operating systems.
  50. ns_name_tuple = ns_name.split(b".", 1)
  51. if len(ns_name_tuple) == 2:
  52. # we have a namespace prefix in the given name
  53. ns, name = ns_name_tuple
  54. else:
  55. # no namespace given in ns_name (== no dot found), maybe data coming from an old borg archive.
  56. ns, name = default_ns, ns_name
  57. return ns, name
  58. def listxattr(path, *, follow_symlinks=False):
  59. def func(path, buf, size):
  60. if isinstance(path, int):
  61. return c_extattr_list_fd(path, ns_id, <char *> buf, size)
  62. else:
  63. if follow_symlinks:
  64. return c_extattr_list_file(path, ns_id, <char *> buf, size)
  65. else:
  66. return c_extattr_list_link(path, ns_id, <char *> buf, size)
  67. ns = b"user"
  68. ns_id = NS_ID_MAP[ns]
  69. n, buf = _listxattr_inner(func, path)
  70. return [ns + b"." + name for name in split_lstring(buf[:n]) if name]
  71. def getxattr(path, name, *, follow_symlinks=False):
  72. def func(path, name, buf, size):
  73. if isinstance(path, int):
  74. return c_extattr_get_fd(path, ns_id, name, <char *> buf, size)
  75. else:
  76. if follow_symlinks:
  77. return c_extattr_get_file(path, ns_id, name, <char *> buf, size)
  78. else:
  79. return c_extattr_get_link(path, ns_id, name, <char *> buf, size)
  80. ns, name = split_ns(name, b"user")
  81. ns_id = NS_ID_MAP[ns] # this will raise a KeyError it the namespace is unsupported
  82. n, buf = _getxattr_inner(func, path, name)
  83. return bytes(buf[:n])
  84. def setxattr(path, name, value, *, follow_symlinks=False):
  85. def func(path, name, value, size):
  86. if isinstance(path, int):
  87. return c_extattr_set_fd(path, ns_id, name, <char *> value, size)
  88. else:
  89. if follow_symlinks:
  90. return c_extattr_set_file(path, ns_id, name, <char *> value, size)
  91. else:
  92. return c_extattr_set_link(path, ns_id, name, <char *> value, size)
  93. ns, name = split_ns(name, b"user")
  94. try:
  95. ns_id = NS_ID_MAP[ns] # this will raise a KeyError it the namespace is unsupported
  96. except KeyError:
  97. pass
  98. else:
  99. _setxattr_inner(func, path, name, value)
  100. cdef _get_acl(p, type, item, attribute, flags, fd=None):
  101. cdef acl_t acl
  102. cdef char *text
  103. if fd is not None:
  104. acl = acl_get_fd_np(fd, type)
  105. else:
  106. acl = acl_get_link_np(p, type)
  107. if acl == NULL:
  108. raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(p))
  109. text = acl_to_text_np(acl, NULL, flags)
  110. if text == NULL:
  111. acl_free(acl)
  112. raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(p))
  113. item[attribute] = text
  114. acl_free(text)
  115. acl_free(acl)
  116. def acl_get(path, item, st, numeric_ids=False, fd=None):
  117. """Saves ACL Entries
  118. If `numeric_ids` is True the user/group field is not preserved only uid/gid
  119. """
  120. cdef int flags = ACL_TEXT_APPEND_ID
  121. flags |= ACL_TEXT_NUMERIC_IDS if numeric_ids else 0
  122. if isinstance(path, str):
  123. path = os.fsencode(path)
  124. ret = acl_extended_link_np(path)
  125. if ret < 0:
  126. raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
  127. if ret == 0:
  128. # there is no ACL defining permissions other than those defined by the traditional file permission bits.
  129. return
  130. ret = lpathconf(path, _PC_ACL_NFS4)
  131. if ret < 0:
  132. raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
  133. nfs4_acl = ret == 1
  134. if nfs4_acl:
  135. _get_acl(path, ACL_TYPE_NFS4, item, 'acl_nfs4', flags, fd=fd)
  136. else:
  137. _get_acl(path, ACL_TYPE_ACCESS, item, 'acl_access', flags, fd=fd)
  138. if stat.S_ISDIR(st.st_mode):
  139. _get_acl(path, ACL_TYPE_DEFAULT, item, 'acl_default', flags, fd=fd)
  140. cdef _set_acl(path, type, item, attribute, numeric_ids=False, fd=None):
  141. cdef acl_t acl = NULL
  142. text = item.get(attribute)
  143. if text is not None:
  144. if numeric_ids and type == ACL_TYPE_NFS4:
  145. text = _nfs4_use_stored_uid_gid(text)
  146. elif numeric_ids and type in (ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT):
  147. text = posix_acl_use_stored_uid_gid(text)
  148. acl = acl_from_text(<bytes>text)
  149. if acl == NULL:
  150. raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
  151. try:
  152. if fd is not None:
  153. if acl_set_fd_np(fd, acl, type) == -1:
  154. raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
  155. else:
  156. if acl_set_link_np(path, type, acl) == -1:
  157. raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
  158. finally:
  159. acl_free(acl)
  160. cdef _nfs4_use_stored_uid_gid(acl):
  161. """Replace the user/group field with the stored uid/gid
  162. """
  163. assert isinstance(acl, bytes)
  164. entries = []
  165. for entry in safe_decode(acl).split('\n'):
  166. if entry:
  167. if entry.startswith('user:') or entry.startswith('group:'):
  168. fields = entry.split(':')
  169. entries.append(':'.join(fields[0], fields[5], *fields[2:-1]))
  170. else:
  171. entries.append(entry)
  172. return safe_encode('\n'.join(entries))
  173. def acl_set(path, item, numeric_ids=False, fd=None):
  174. """Restore ACL Entries
  175. If `numeric_ids` is True the stored uid/gid is used instead
  176. of the user/group names
  177. """
  178. if isinstance(path, str):
  179. path = os.fsencode(path)
  180. _set_acl(path, ACL_TYPE_NFS4, item, 'acl_nfs4', numeric_ids, fd=fd)
  181. _set_acl(path, ACL_TYPE_ACCESS, item, 'acl_access', numeric_ids, fd=fd)
  182. _set_acl(path, ACL_TYPE_DEFAULT, item, 'acl_default', numeric_ids, fd=fd)