diff --git a/src/pybind/cephfs/c_cephfs.pxd b/src/pybind/cephfs/c_cephfs.pxd index b4b2c625144..cb266ecf910 100644 --- a/src/pybind/cephfs/c_cephfs.pxd +++ b/src/pybind/cephfs/c_cephfs.pxd @@ -118,6 +118,7 @@ cdef extern from "cephfs/libcephfs.h" nogil: int ceph_lazyio_synchronize(ceph_mount_info *cmount, int fd, int64_t offset, size_t count) int ceph_fallocate(ceph_mount_info *cmount, int fd, int mode, int64_t offset, int64_t length) int ceph_chmod(ceph_mount_info *cmount, const char *path, mode_t mode) + int ceph_lchmod(ceph_mount_info *cmount, const char *path, mode_t mode) int ceph_fchmod(ceph_mount_info *cmount, int fd, mode_t mode) int ceph_chown(ceph_mount_info *cmount, const char *path, int uid, int gid) int ceph_lchown(ceph_mount_info *cmount, const char *path, int uid, int gid) diff --git a/src/pybind/cephfs/cephfs.pyx b/src/pybind/cephfs/cephfs.pyx index c93313cfa56..e536fcbe5eb 100644 --- a/src/pybind/cephfs/cephfs.pyx +++ b/src/pybind/cephfs/cephfs.pyx @@ -1102,6 +1102,26 @@ cdef class LibCephFS(object): if ret < 0: raise make_ex(ret, "error in chmod {}".format(path.decode('utf-8'))) + def lchmod(self, path, mode) -> None: + """ + Change file mode. If the path is a symbolic link, it won't be dereferenced. + + :param path: the path of the file. This must be either an absolute path or + a relative path off of the current working directory. + :param mode: the permissions to be set . + """ + self.require_state("mounted") + path = cstr(path, 'path') + if not isinstance(mode, int): + raise TypeError('mode must be an int') + cdef: + char* _path = path + int _mode = mode + with nogil: + ret = ceph_lchmod(self.cluster, _path, _mode) + if ret < 0: + raise make_ex(ret, "error in chmod {}".format(path.decode('utf-8'))) + def fchmod(self, fd, mode) : """ Change file mode based on fd. diff --git a/src/pybind/cephfs/mock_cephfs.pxi b/src/pybind/cephfs/mock_cephfs.pxi index f4ff5b7360c..c871b8b353b 100644 --- a/src/pybind/cephfs/mock_cephfs.pxi +++ b/src/pybind/cephfs/mock_cephfs.pxi @@ -189,6 +189,8 @@ cdef nogil: pass int ceph_chmod(ceph_mount_info *cmount, const char *path, mode_t mode): pass + int ceph_lchmod(ceph_mount_info *cmount, const char *path, mode_t mode): + pass int ceph_fchmod(ceph_mount_info *cmount, int fd, mode_t mode): pass int ceph_chown(ceph_mount_info *cmount, const char *path, int uid, int gid): diff --git a/src/test/pybind/test_cephfs.py b/src/test/pybind/test_cephfs.py index bdb8332ffe7..a3b1d494e6a 100644 --- a/src/test/pybind/test_cephfs.py +++ b/src/test/pybind/test_cephfs.py @@ -1,5 +1,5 @@ # vim: expandtab smarttab shiftwidth=4 softtabstop=4 -from nose.tools import assert_raises, assert_equal, assert_greater, with_setup +from nose.tools import assert_raises, assert_equal, assert_not_equal, assert_greater, with_setup import cephfs as libcephfs import fcntl import os @@ -540,6 +540,31 @@ def test_futimens(): cephfs.close(fd) cephfs.unlink(b'/file-1') +@with_setup(setup_test) +def test_lchmod(): + fd = cephfs.open(b'/file-1', 'w', 0o755) + cephfs.write(fd, b'0000', 0) + cephfs.close(fd) + + cephfs.symlink(b'/file-1', b'/file-2') + + stx_pre_t = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_MODE, 0) + stx_pre_s = cephfs.statx(b'/file-2', libcephfs.CEPH_STATX_MODE, libcephfs.AT_SYMLINK_NOFOLLOW) + + time.sleep(1) + cephfs.lchmod(b'/file-2', 0o400) + + stx_post_t = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_MODE, 0) + stx_post_s = cephfs.statx(b'/file-2', libcephfs.CEPH_STATX_MODE, libcephfs.AT_SYMLINK_NOFOLLOW) + + assert_equal(stx_post_t['mode'], stx_pre_t['mode']) + assert_not_equal(stx_post_s['mode'], stx_pre_s['mode']) + stx_post_s_perm_bits = stx_post_s['mode'] & ~stat.S_IFMT(stx_post_s["mode"]) + assert_equal(stx_post_s_perm_bits, 0o400) + + cephfs.unlink(b'/file-2') + cephfs.unlink(b'/file-1') + @with_setup(setup_test) def test_fchmod(): fd = cephfs.open(b'/file-fchmod', 'w', 0o655)