Compare commits

...

24 Commits

Author SHA1 Message Date
Ivan Shapovalov e3278d0556
Merge aee25c3564 into 0c1df415d7 2024-04-19 21:39:37 -07:00
Vladimir Malinovskii 0c1df415d7
changed insufficiently reserved length for log message (#8152)
changed log message reserved length
2024-04-06 20:24:10 +02:00
TW 411c763fb8
Merge pull request #8182 from ThomasWaldmann/fix-test-ht-master
format_timedelta: use 3 decimal digits (ms)
2024-04-04 13:31:58 +02:00
Thomas Waldmann 54a85bf56d
format_timedelta: use 3 decimal digits (ms)
maybe this fixes the frequently failing test.
also, giving ms makes more sense than 10ms granularity.
2024-04-04 12:45:28 +02:00
TW 4d2eb0cb1b
Merge pull request #8181 from ThomasWaldmann/github-actions-update-master
update github actions
2024-04-03 19:33:35 +02:00
Thomas Waldmann d893b899fc
update github actions
(avoid deprecation warnings)
2024-04-03 18:26:35 +02:00
TW fb4b4cfeb8
Merge pull request #8180 from ThomasWaldmann/update-requirements-master
update development.lock.txt
2024-04-03 17:52:24 +02:00
Thomas Waldmann bb50246bc4
update development.lock.txt 2024-04-03 17:13:18 +02:00
TW c5abfe1ee9
Merge pull request #8178 from ThomasWaldmann/acl-error-handling-master
improve acl_get / acl_set error handling (master)
2024-04-03 17:02:14 +02:00
Thomas Waldmann 4e5bf28473
Linux: refactor acl_get 2024-04-02 01:38:31 +02:00
Thomas Waldmann 64b7b5fdd4
FreeBSD: check first if kind of ACL can be set on a file 2024-04-02 01:38:30 +02:00
Thomas Waldmann 4ebb5cdf3c
FreeBSD: simplify numeric_ids check 2024-04-02 01:38:28 +02:00
Thomas Waldmann 7df170c946
FreeBSD: added tests, only get default ACL from dirs 2024-04-02 01:38:27 +02:00
Thomas Waldmann d3694271eb
FreeBSD: acl_get: raise OSError if lpathconf fails
Previously:
- acl_get just returned for lpathconf returning EINVAL
- acl_get silently ignored all other lpathconf errors and
  implied it is not a NFS4 acl

Now:
- not sure why the EINVAL silent return was done, but it seems
  wrong. guess it could be the system not implementing a check
  for nfs4. but in that case guess we still would like to get
  the default and access ACL!? Thus, I removed the silent return.
- raise OSError for all lpathconf errors

Cosmetic: add a nfs4_acl boolean, so the code reads better.
2024-04-02 01:38:26 +02:00
Thomas Waldmann 30f4518058
FreeBSD: acl_get: add an acl_extended_* call
... to implement same semantics as on linux (only store ACL
if it defines permissions other than those defined by the
traditional file permissions).

Looks like there is no call working with an fd on FreeBSD.
2024-04-02 01:38:24 +02:00
Thomas Waldmann 4cc4516c59
Linux: acl_set bug fix: always fsencode path
We use path when raising OSErrors, even if we have an fd.
2024-04-02 01:38:23 +02:00
Thomas Waldmann 96cac5f381
Linux: acl_get: use "nofollow" variant of acl_extended_file call
This is NOT a bug fix, because the previous code contained a
check for symlinks before that line - because symlinks can not
have ACLs under Linux.

Now, this "is it a symlink" check is removed to simplify the
code and the "nofollow" variant of acl_extended_file* is used
to look at the symlink fs object (in the symlink case).

It then should tell us that this does NOT have an extended ACL
(because symlinks can't have ACLs) and so we return there.

Overall the code gets simpler and looks less suspect.
2024-04-02 01:38:21 +02:00
Thomas Waldmann beac2fa9ae
Linux: acl_get: raise OSError for errors in acl_extended_* call
Previously, these conditions were handled the same (just return):
- no extended acl here
- some error happened (e.g. ACLs unsupported, bad file descriptor, file not found, permission error, ...)

Now there will be OSErrors for the error cases.
2024-04-02 01:38:20 +02:00
Thomas Waldmann 1269c852bf
create/extract: ignore OSError if ACLs are not supported (ENOTSUP)
but do not silence other OSErrors.
2024-04-02 01:38:18 +02:00
Thomas Waldmann bafea3b5de
platform tests: misc. minor cleanups
- remove unused global / import
- use is_linux and is_darwin
- rename darwin acl test method
2024-04-02 01:38:17 +02:00
Thomas Waldmann d5396feebd
improve are_acls_working function
- ACLs are not working, if ENOTSUP ("Operation not supported") happens
- fix check for macOS
  On macOS borg uses "acl_extended", not "acl_access" and
  also the ACL text format is a bit different.
2024-04-02 01:38:15 +02:00
Thomas Waldmann b3554cdc0f
raise OSError if acl_to_text / acl_from_text returns NULL
Also did a small structural refactors there.
2024-04-02 01:38:14 +02:00
Thomas Waldmann a75945ed0d
improve acl_get / acl_set error handling, see #4049 2024-04-02 01:38:12 +02:00
Ivan Shapovalov aee25c3564 extract_item: do not delete an existing directory if possible
A pre-existing directory might be a Btrfs subvolume that was created by
the user ahead of time when restoring several nested subvolumes from a
single archive.
2023-10-10 23:42:39 +04:00
14 changed files with 194 additions and 125 deletions

View File

@ -9,7 +9,7 @@ jobs:
lint:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: psf/black@stable
with:
version: "~= 23.0"

View File

@ -78,11 +78,11 @@ jobs:
# just fetching 1 commit is not enough for setuptools-scm, so we fetch all
fetch-depth: 0
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.d/development.txt') }}
@ -114,7 +114,7 @@ jobs:
#sudo -E bash -c "tox -e py"
tox --skip-missing-interpreters
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
env:
OS: ${{ runner.os }}
python: ${{ matrix.python-version }}

View File

@ -1,14 +1,14 @@
setuptools==68.2.2
setuptools==69.2.0
setuptools-scm==8.0.4
pip==23.3.2
wheel==0.41.3
virtualenv==20.25.0
build==1.0.3
pip==24.0
wheel==0.43.0
virtualenv==20.25.1
build==1.2.1
pkgconfig==1.5.5
tox==4.11.3
pytest==7.4.3
pytest-xdist==3.3.1
pytest-cov==4.1.0
tox==4.14.2
pytest==8.1.1
pytest-xdist==3.5.0
pytest-cov==5.0.0
pytest-benchmark==4.0.0
Cython==3.0.5
pre-commit==3.5.0
Cython==3.0.10
pre-commit==3.7.0

View File

@ -844,10 +844,13 @@ Duration: {0.duration}
st = os.stat(path, follow_symlinks=False)
if continue_extraction and same_item(item, st):
return # done! we already have fully extracted this file in a previous run.
elif stat.S_ISDIR(st.st_mode):
os.rmdir(path)
else:
# remove anything that is not a directory
if not stat.S_ISDIR(st.st_mode):
os.unlink(path)
# only remove a directory if it is conflicting
# preserve existing directories because they might be subvolumes
elif not stat.S_ISDIR(item.mode):
os.rmdir(path)
except UnicodeEncodeError:
raise self.IncompatibleFilesystemEncodingError(path, sys.getfilesystemencoding()) from None
except OSError:
@ -964,7 +967,11 @@ Duration: {0.duration}
if not symlink:
os.chmod(path, item.mode)
if not self.noacls:
acl_set(path, item, self.numeric_ids, fd=fd)
try:
acl_set(path, item, self.numeric_ids, fd=fd)
except OSError as e:
if e.errno not in (errno.ENOTSUP,):
raise
if not self.noxattrs and "xattrs" in item:
# chown removes Linux capabilities, so set the extended attributes at the end, after chown,
# since they include the Linux capabilities in the "security.capability" attribute.
@ -1210,7 +1217,11 @@ class MetadataCollector:
attrs["xattrs"] = StableDict(xattrs)
if not self.noacls:
with backup_io("extended stat (ACLs)"):
acl_get(path, attrs, st, self.numeric_ids, fd=fd)
try:
acl_get(path, attrs, st, self.numeric_ids, fd=fd)
except OSError as e:
if e.errno not in (errno.ENOTSUP,):
raise
return attrs
def stat_attrs(self, st, path, fd=None):

View File

@ -169,7 +169,7 @@ class PruneMixIn:
or (args.list_pruned and archive in to_delete)
or (args.list_kept and archive not in to_delete)
):
list_logger.info(f"{log_message:<40} {formatter.format_item(archive, jsonline=False)}")
list_logger.info(f"{log_message:<44} {formatter.format_item(archive, jsonline=False)}")
pi.finish()
if sig_int:
# Ctrl-C / SIGINT: do not checkpoint (commit) again, we already have a checkpoint in this case.

View File

@ -100,7 +100,7 @@ def format_timedelta(td):
s = ts % 60
m = int(ts / 60) % 60
h = int(ts / 3600) % 24
txt = "%.2f seconds" % s
txt = "%.3f seconds" % s
if m:
txt = "%d minutes %s" % (m, txt)
if h:

View File

@ -1,6 +1,7 @@
import os
from libc.stdint cimport uint32_t
from libc cimport errno
from .posix import user2uid, group2gid
from ..helpers import safe_decode, safe_encode
@ -115,20 +116,25 @@ def _remove_non_numeric_identifier(acl):
def acl_get(path, item, st, numeric_ids=False, fd=None):
cdef acl_t acl = NULL
cdef char *text = NULL
if isinstance(path, str):
path = os.fsencode(path)
try:
if fd is not None:
acl = acl_get_fd_np(fd, ACL_TYPE_EXTENDED)
else:
if isinstance(path, str):
path = os.fsencode(path)
acl = acl_get_link_np(path, ACL_TYPE_EXTENDED)
if acl is not NULL:
text = acl_to_text(acl, NULL)
if text is not NULL:
if numeric_ids:
item['acl_extended'] = _remove_non_numeric_identifier(text)
else:
item['acl_extended'] = text
if acl == NULL:
if errno.errno == errno.ENOENT:
# macOS weirdness: if a file has no ACLs, it sets errno to ENOENT. :-(
return
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
text = acl_to_text(acl, NULL)
if text == NULL:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
if numeric_ids:
item['acl_extended'] = _remove_non_numeric_identifier(text)
else:
item['acl_extended'] = text
finally:
acl_free(text)
acl_free(acl)
@ -139,16 +145,19 @@ def acl_set(path, item, numeric_ids=False, fd=None):
acl_text = item.get('acl_extended')
if acl_text is not None:
try:
if isinstance(path, str):
path = os.fsencode(path)
if numeric_ids:
acl = acl_from_text(acl_text)
else:
acl = acl_from_text(<bytes>_remove_numeric_id_if_possible(acl_text))
if acl is not NULL:
if fd is not None:
acl_set_fd_np(fd, acl, ACL_TYPE_EXTENDED)
else:
if isinstance(path, str):
path = os.fsencode(path)
acl_set_link_np(path, ACL_TYPE_EXTENDED, acl)
if acl == NULL:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
if fd is not None:
if acl_set_fd_np(fd, acl, ACL_TYPE_EXTENDED) == -1:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
else:
if acl_set_link_np(path, ACL_TYPE_EXTENDED, acl) == -1:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
finally:
acl_free(acl)

View File

@ -1,4 +1,7 @@
import os
import stat
from libc cimport errno
from .posix import posix_acl_use_stored_uid_gid
from ..helpers import safe_encode, safe_decode
@ -6,10 +9,6 @@ from .xattr import _listxattr_inner, _getxattr_inner, _setxattr_inner, split_lst
API_VERSION = '1.2_05'
cdef extern from "errno.h":
int errno
int EINVAL
cdef extern from "sys/extattr.h":
ssize_t c_extattr_list_file "extattr_list_file" (const char *path, int attrnamespace, void *data, size_t nbytes)
ssize_t c_extattr_list_link "extattr_list_link" (const char *path, int attrnamespace, void *data, size_t nbytes)
@ -44,10 +43,12 @@ cdef extern from "sys/acl.h":
char *acl_to_text_np(acl_t acl, ssize_t *len, int flags)
int ACL_TEXT_NUMERIC_IDS
int ACL_TEXT_APPEND_ID
int acl_extended_link_np(const char * path) # check also: acl_is_trivial_np
cdef extern from "unistd.h":
long lpathconf(const char *path, int name)
int _PC_ACL_NFS4
int _PC_ACL_EXTENDED
# On FreeBSD, borg currently only deals with the USER namespace as it is unclear
@ -124,21 +125,21 @@ def setxattr(path, name, value, *, follow_symlinks=False):
cdef _get_acl(p, type, item, attribute, flags, fd=None):
cdef acl_t acl = NULL
cdef char *text = NULL
try:
if fd is not None:
acl = acl_get_fd_np(fd, type)
else:
acl = acl_get_link_np(p, type)
if acl is not NULL:
text = acl_to_text_np(acl, NULL, flags)
if text is not NULL:
item[attribute] = text
finally:
acl_free(text)
cdef acl_t acl
cdef char *text
if fd is not None:
acl = acl_get_fd_np(fd, type)
else:
acl = acl_get_link_np(p, type)
if acl == NULL:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(p))
text = acl_to_text_np(acl, NULL, flags)
if text == NULL:
acl_free(acl)
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(p))
item[attribute] = text
acl_free(text)
acl_free(acl)
def acl_get(path, item, st, numeric_ids=False, fd=None):
"""Saves ACL Entries
@ -146,34 +147,46 @@ def acl_get(path, item, st, numeric_ids=False, fd=None):
If `numeric_ids` is True the user/group field is not preserved only uid/gid
"""
cdef int flags = ACL_TEXT_APPEND_ID
flags |= ACL_TEXT_NUMERIC_IDS if numeric_ids else 0
if isinstance(path, str):
path = os.fsencode(path)
ret = lpathconf(path, _PC_ACL_NFS4)
if ret < 0 and errno == EINVAL:
ret = acl_extended_link_np(path)
if ret < 0:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
if ret == 0:
# there is no ACL defining permissions other than those defined by the traditional file permission bits.
return
flags |= ACL_TEXT_NUMERIC_IDS if numeric_ids else 0
if ret > 0:
ret = lpathconf(path, _PC_ACL_NFS4)
if ret < 0:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
nfs4_acl = ret == 1
if nfs4_acl:
_get_acl(path, ACL_TYPE_NFS4, item, 'acl_nfs4', flags, fd=fd)
else:
_get_acl(path, ACL_TYPE_ACCESS, item, 'acl_access', flags, fd=fd)
_get_acl(path, ACL_TYPE_DEFAULT, item, 'acl_default', flags, fd=fd)
if stat.S_ISDIR(st.st_mode):
_get_acl(path, ACL_TYPE_DEFAULT, item, 'acl_default', flags, fd=fd)
cdef _set_acl(path, type, item, attribute, numeric_ids=False, fd=None):
cdef acl_t acl = NULL
text = item.get(attribute)
if text is not None:
if numeric_ids and type == ACL_TYPE_NFS4:
text = _nfs4_use_stored_uid_gid(text)
elif numeric_ids and type in (ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT):
text = posix_acl_use_stored_uid_gid(text)
if text:
if numeric_ids:
if type == ACL_TYPE_NFS4:
text = _nfs4_use_stored_uid_gid(text)
elif type in (ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT):
text = posix_acl_use_stored_uid_gid(text)
acl = acl_from_text(<bytes>text)
if acl == NULL:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
try:
acl = acl_from_text(<bytes> text)
if acl is not NULL:
if fd is not None:
acl_set_fd_np(fd, acl, type)
else:
acl_set_link_np(path, type, acl)
if fd is not None:
if acl_set_fd_np(fd, acl, type) == -1:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
else:
if acl_set_link_np(path, type, acl) == -1:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
finally:
acl_free(acl)
@ -201,6 +214,14 @@ def acl_set(path, item, numeric_ids=False, fd=None):
"""
if isinstance(path, str):
path = os.fsencode(path)
_set_acl(path, ACL_TYPE_NFS4, item, 'acl_nfs4', numeric_ids, fd=fd)
_set_acl(path, ACL_TYPE_ACCESS, item, 'acl_access', numeric_ids, fd=fd)
_set_acl(path, ACL_TYPE_DEFAULT, item, 'acl_default', numeric_ids, fd=fd)
ret = lpathconf(path, _PC_ACL_NFS4)
if ret < 0:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
if ret == 1:
_set_acl(path, ACL_TYPE_NFS4, item, 'acl_nfs4', numeric_ids, fd=fd)
ret = lpathconf(path, _PC_ACL_EXTENDED)
if ret < 0:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
if ret == 1:
_set_acl(path, ACL_TYPE_ACCESS, item, 'acl_access', numeric_ids, fd=fd)
_set_acl(path, ACL_TYPE_DEFAULT, item, 'acl_default', numeric_ids, fd=fd)

View File

@ -50,7 +50,7 @@ cdef extern from "sys/acl.h":
char *acl_to_text(acl_t acl, ssize_t *len)
cdef extern from "acl/libacl.h":
int acl_extended_file(const char *path)
int acl_extended_file_nofollow(const char *path)
int acl_extended_fd(int fd)
cdef extern from "linux/fs.h":
@ -233,15 +233,19 @@ def acl_get(path, item, st, numeric_ids=False, fd=None):
cdef acl_t access_acl = NULL
cdef char *default_text = NULL
cdef char *access_text = NULL
cdef int ret = 0
if stat.S_ISLNK(st.st_mode):
# symlinks can not have ACLs
return
if isinstance(path, str):
path = os.fsencode(path)
if (fd is not None and acl_extended_fd(fd) <= 0
or
fd is None and acl_extended_file(path) <= 0):
if fd is not None:
ret = acl_extended_fd(fd)
else:
ret = acl_extended_file_nofollow(path)
if ret < 0:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
if ret == 0:
# there is no ACL defining permissions other than those defined by the traditional file permission bits.
# note: this should also be the case for symlink fs objects, as they can not have ACLs.
return
if numeric_ids:
converter = acl_numeric_ids
@ -252,25 +256,28 @@ def acl_get(path, item, st, numeric_ids=False, fd=None):
access_acl = acl_get_fd(fd)
else:
access_acl = acl_get_file(path, ACL_TYPE_ACCESS)
if access_acl is not NULL:
access_text = acl_to_text(access_acl, NULL)
if access_text is not NULL:
item['acl_access'] = converter(access_text)
if access_acl == NULL:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
access_text = acl_to_text(access_acl, NULL)
if access_text == NULL:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
item['acl_access'] = converter(access_text)
finally:
acl_free(access_text)
acl_free(access_acl)
try:
if stat.S_ISDIR(st.st_mode):
# only directories can have a default ACL. there is no fd-based api to get it.
if stat.S_ISDIR(st.st_mode):
# only directories can have a default ACL. there is no fd-based api to get it.
try:
default_acl = acl_get_file(path, ACL_TYPE_DEFAULT)
if default_acl is not NULL:
default_text = acl_to_text(default_acl, NULL)
if default_text is not NULL:
item['acl_default'] = converter(default_text)
finally:
acl_free(default_text)
acl_free(default_acl)
if default_acl == NULL:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
default_text = acl_to_text(default_acl, NULL)
if default_text == NULL:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
item['acl_default'] = converter(default_text)
finally:
acl_free(default_text)
acl_free(default_acl)
def acl_set(path, item, numeric_ids=False, fd=None):
@ -281,7 +288,7 @@ def acl_set(path, item, numeric_ids=False, fd=None):
# Linux does not support setting ACLs on symlinks
return
if fd is None and isinstance(path, str):
if isinstance(path, str):
path = os.fsencode(path)
if numeric_ids:
converter = posix_acl_use_stored_uid_gid
@ -290,21 +297,26 @@ def acl_set(path, item, numeric_ids=False, fd=None):
access_text = item.get('acl_access')
if access_text is not None:
try:
access_acl = acl_from_text(<bytes> converter(access_text))
if access_acl is not NULL:
if fd is not None:
acl_set_fd(fd, access_acl)
else:
acl_set_file(path, ACL_TYPE_ACCESS, access_acl)
access_acl = acl_from_text(<bytes>converter(access_text))
if access_acl == NULL:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
if fd is not None:
if acl_set_fd(fd, access_acl) == -1:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
else:
if acl_set_file(path, ACL_TYPE_ACCESS, access_acl) == -1:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
finally:
acl_free(access_acl)
default_text = item.get('acl_default')
if default_text is not None:
try:
default_acl = acl_from_text(<bytes> converter(default_text))
if default_acl is not NULL:
# only directories can get a default ACL. there is no fd-based api to set it.
acl_set_file(path, ACL_TYPE_DEFAULT, default_acl)
default_acl = acl_from_text(<bytes>converter(default_text))
if default_acl == NULL:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
# only directories can get a default ACL. there is no fd-based api to set it.
if acl_set_file(path, ACL_TYPE_DEFAULT, default_acl) == -1:
raise OSError(errno.errno, os.strerror(errno.errno), os.fsdecode(path))
finally:
acl_free(default_acl)

View File

@ -61,8 +61,8 @@ def test_stats_format(stats):
Number of files: 1
Original size: 20 B
Deduplicated size: 20 B
Time spent in hashing: 0.00 seconds
Time spent in chunking: 0.00 seconds
Time spent in hashing: 0.000 seconds
Time spent in chunking: 0.000 seconds
Added files: 0
Unchanged files: 0
Modified files: 0

View File

@ -368,7 +368,7 @@ def test_text_invalid(text):
def test_format_timedelta():
t0 = datetime(2001, 1, 1, 10, 20, 3, 0)
t1 = datetime(2001, 1, 1, 12, 20, 4, 100000)
assert format_timedelta(t1 - t0) == "2 hours 1.10 seconds"
assert format_timedelta(t1 - t0) == "2 hours 1.100 seconds"
@pytest.mark.parametrize(

View File

@ -1,3 +1,4 @@
import errno
import functools
import os
@ -31,25 +32,38 @@ def are_acls_working():
with unopened_tempfile() as filepath:
open(filepath, "w").close()
try:
if is_freebsd:
access = b"user::rw-\ngroup::r--\nmask::rw-\nother::---\nuser:root:rw-\n"
contained = b"user:root:rw-"
if is_darwin:
acl_key = "acl_extended"
acl_value = b"!#acl 1\nuser:FFFFEEEE-DDDD-CCCC-BBBB-AAAA00000000:root:0:allow:read\n"
elif is_linux:
access = b"user::rw-\ngroup::r--\nmask::rw-\nother::---\nuser:root:rw-:0\n"
contained = b"user:root:rw-:0"
elif is_darwin:
return True # improve?
acl_key = "acl_access"
acl_value = b"user::rw-\ngroup::r--\nmask::rw-\nother::---\nuser:root:rw-:9999\ngroup:root:rw-:9999\n"
elif is_freebsd:
acl_key = "acl_access"
acl_value = b"user::rw-\ngroup::r--\nmask::rw-\nother::---\nuser:root:rw-\ngroup:wheel:rw-\n"
else:
return False # unsupported platform
acl = {"acl_access": access}
acl_set(filepath, acl)
return False # ACLs unsupported on this platform.
write_acl = {acl_key: acl_value}
acl_set(filepath, write_acl)
read_acl = {}
acl_get(filepath, read_acl, os.stat(filepath))
read_acl_access = read_acl.get("acl_access", None)
if read_acl_access and contained in read_acl_access:
return True
acl = read_acl.get(acl_key, None)
if acl is not None:
if is_darwin:
check_for = b"root:0:allow:read"
elif is_linux:
check_for = b"user::rw-"
elif is_freebsd:
check_for = b"user::rw-"
else:
return False # ACLs unsupported on this platform.
if check_for in acl:
return True
except PermissionError:
pass
except OSError as e:
if e.errno not in (errno.ENOTSUP,):
raise
return False

View File

@ -20,7 +20,7 @@ def set_acl(path, acl, numeric_ids=False):
@skipif_acls_not_working
def test_access_acl():
def test_extended_acl():
file = tempfile.NamedTemporaryFile()
assert get_acl(file.name) == {}
set_acl(

View File

@ -49,6 +49,7 @@ def set_acl(path, access=None, default=None, nfs4=None, numeric_ids=False):
@skipif_acls_not_working
def test_access_acl():
file1 = tempfile.NamedTemporaryFile()
assert get_acl(file1.name) == {}
set_acl(
file1.name,
access=b"user::rw-\ngroup::r--\nmask::rw-\nother::---\nuser:root:rw-\ngroup:wheel:rw-\n",
@ -86,6 +87,7 @@ def test_access_acl():
@skipif_acls_not_working
def test_default_acl():
tmpdir = tempfile.mkdtemp()
assert get_acl(tmpdir) == {}
set_acl(tmpdir, access=ACCESS_ACL, default=DEFAULT_ACL)
assert get_acl(tmpdir)["acl_access"] == ACCESS_ACL
assert get_acl(tmpdir)["acl_default"] == DEFAULT_ACL