From fc49ffd227b06495b30674eff9897a34f9fd60be Mon Sep 17 00:00:00 2001 From: Venky Shankar Date: Thu, 6 Feb 2020 05:34:24 -0500 Subject: [PATCH] pybind/cephfs: pybind calls for changing inode timestamps Signed-off-by: Venky Shankar --- src/pybind/cephfs/cephfs.pyx | 201 +++++++++++++++++++++++++++++++++ src/test/pybind/test_cephfs.py | 173 +++++++++++++++++++++++++++- 2 files changed, 373 insertions(+), 1 deletion(-) diff --git a/src/pybind/cephfs/cephfs.pyx b/src/pybind/cephfs/cephfs.pyx index de17d6ecf52..904ff177481 100644 --- a/src/pybind/cephfs/cephfs.pyx +++ b/src/pybind/cephfs/cephfs.pyx @@ -14,6 +14,7 @@ from datetime import datetime import errno import os import sys +import time # Are we running Python 2.x if sys.version_info[0] < 3: @@ -95,6 +96,16 @@ cdef extern from "time.h": cdef extern from "sys/types.h": ctypedef unsigned long mode_t +cdef extern from "": + cdef struct utimbuf: + time_t actime + time_t modtime + +cdef extern from "sys/time.h": + cdef struct timeval: + long tv_sec + long tv_usec + cdef extern from "cephfs/ceph_statx.h": cdef struct statx "ceph_statx": uint32_t stx_mask @@ -182,6 +193,12 @@ cdef extern from "cephfs/libcephfs.h" nogil: int64_t ceph_lseek(ceph_mount_info *cmount, int fd, int64_t offset, int whence) void ceph_buffer_free(char *buf) mode_t ceph_umask(ceph_mount_info *cmount, mode_t mode) + int ceph_utime(ceph_mount_info *cmount, const char *path, utimbuf *buf) + int ceph_futime(ceph_mount_info *cmount, int fd, utimbuf *buf) + int ceph_utimes(ceph_mount_info *cmount, const char *path, timeval times[2]) + int ceph_lutimes(ceph_mount_info *cmount, const char *path, timeval times[2]) + int ceph_futimes(ceph_mount_info *cmount, int fd, timeval times[2]) + int ceph_futimens(ceph_mount_info *cmount, int fd, timespec times[2]) class Error(Exception): @@ -415,6 +432,21 @@ def decode_cstr(val, encoding="utf-8"): return val.decode(encoding) +cdef timeval to_timeval(t): + """ + return timeval equivalent from time + """ + tt = int(t) + cdef timeval buf = timeval(tt, (t - tt) * 1000000) + return buf + +cdef timespec to_timespec(t): + """ + return timespec equivalent from time + """ + tt = int(t) + cdef timespec buf = timespec(tt, (t - tt) * 1000000000) + return buf cdef char* opt_str(s) except? NULL: if s is None: @@ -1557,3 +1589,172 @@ cdef class LibCephFS(object): raise make_ex(ret, "error in lseek") return ret + + def utime(self, path, times=None): + """ + Set access and modification time for path + + :param path: file path for which timestamps have to be changed + :param times: if times is not None, it must be a tuple (atime, mtime) + """ + + self.require_state("mounted") + path = cstr(path, 'path') + if times: + if not isinstance(times, tuple): + raise TypeError('times must be a tuple') + if not isinstance(times[0], int): + raise TypeError('atime must be an int') + if not isinstance(times[1], int): + raise TypeError('mtime must be an int') + actime = modtime = int(time.time()) + if times: + actime = times[0] + modtime = times[1] + + cdef: + char *pth = path + utimbuf buf = utimbuf(actime, modtime) + with nogil: + ret = ceph_utime(self.cluster, pth, &buf) + if ret < 0: + raise make_ex(ret, "error in utime {}".format(path.decode('utf-8'))) + + def futime(self, fd, times=None): + """ + Set access and modification time for a file pointed by descriptor + + :param fd: file descriptor of the open file + :param times: if times is not None, it must be a tuple (atime, mtime) + """ + + self.require_state("mounted") + if not isinstance(fd, int): + raise TypeError('fd must be an int') + if times: + if not isinstance(times, tuple): + raise TypeError('times must be a tuple') + if not isinstance(times[0], int): + raise TypeError('atime must be an int') + if not isinstance(times[1], int): + raise TypeError('mtime must be an int') + actime = modtime = int(time.time()) + if times: + actime = times[0] + modtime = times[1] + + cdef: + int _fd = fd + utimbuf buf = utimbuf(actime, modtime) + with nogil: + ret = ceph_futime(self.cluster, _fd, &buf) + if ret < 0: + raise make_ex(ret, "error in futime") + + def utimes(self, path, times=None, follow_symlink=True): + """ + Set access and modification time for path + + :param path: file path for which timestamps have to be changed + :param times: if times is not None, it must be a tuple (atime, mtime) + :param follow_symlink: perform the operation on the target file if @path + is a symbolic link (default) + """ + + self.require_state("mounted") + path = cstr(path, 'path') + if times: + if not isinstance(times, tuple): + raise TypeError('times must be a tuple') + if not isinstance(times[0], (int, float)): + raise TypeError('atime must be an int or a float') + if not isinstance(times[1], (int, float)): + raise TypeError('mtime must be an int or a float') + actime = modtime = time.time() + if times: + actime = float(times[0]) + modtime = float(times[1]) + + cdef: + char *pth = path + timeval *buf = [to_timeval(actime), to_timeval(modtime)] + if follow_symlink: + with nogil: + ret = ceph_utimes(self.cluster, pth, buf) + else: + with nogil: + ret = ceph_lutimes(self.cluster, pth, buf) + if ret < 0: + raise make_ex(ret, "error in utimes {}".format(path.decode('utf-8'))) + + def lutimes(self, path, times=None): + """ + Set access and modification time for a file. If the file is a symbolic + link do not follow to the target. + + :param path: file path for which timestamps have to be changed + :param times: if times is not None, it must be a tuple (atime, mtime) + """ + self.utimes(path, times=times, follow_symlink=False) + + def futimes(self, fd, times=None): + """ + Set access and modification time for a file pointer by descriptor + + :param fd: file descriptor of the open file + :param times: if times is not None, it must be a tuple (atime, mtime) + """ + + self.require_state("mounted") + if not isinstance(fd, int): + raise TypeError('fd must be an int') + if times: + if not isinstance(times, tuple): + raise TypeError('times must be a tuple') + if not isinstance(times[0], (int, float)): + raise TypeError('atime must be an int or a float') + if not isinstance(times[1], (int, float)): + raise TypeError('mtime must be an int or a float') + actime = modtime = time.time() + if times: + actime = float(times[0]) + modtime = float(times[1]) + + cdef: + int _fd = fd + timeval *buf = [to_timeval(actime), to_timeval(modtime)] + with nogil: + ret = ceph_futimes(self.cluster, _fd, buf) + if ret < 0: + raise make_ex(ret, "error in futimes") + + def futimens(self, fd, times=None): + """ + Set access and modification time for a file pointer by descriptor + + :param fd: file descriptor of the open file + :param times: if times is not None, it must be a tuple (atime, mtime) + """ + + self.require_state("mounted") + if not isinstance(fd, int): + raise TypeError('fd must be an int') + if times: + if not isinstance(times, tuple): + raise TypeError('times must be a tuple') + if not isinstance(times[0], (int, float)): + raise TypeError('atime must be an int or a float') + if not isinstance(times[1], (int, float)): + raise TypeError('mtime must be an int or a float') + actime = modtime = time.time() + if times: + actime = float(times[0]) + modtime = float(times[1]) + + cdef: + int _fd = fd + timespec *buf = [to_timespec(actime), to_timespec(modtime)] + with nogil: + ret = ceph_futimens(self.cluster, _fd, buf) + if ret < 0: + raise make_ex(ret, "error in futimens") diff --git a/src/test/pybind/test_cephfs.py b/src/test/pybind/test_cephfs.py index c6d76fd3e3e..122c01258dc 100644 --- a/src/test/pybind/test_cephfs.py +++ b/src/test/pybind/test_cephfs.py @@ -1,8 +1,10 @@ # vim: expandtab smarttab shiftwidth=4 softtabstop=4 -from nose.tools import assert_raises, assert_equal, with_setup +from nose.tools import assert_raises, assert_equal, assert_greater, with_setup import cephfs as libcephfs import fcntl import os +import time +from datetime import datetime cephfs = None @@ -259,3 +261,172 @@ def test_mount_root(): assert_raises(libcephfs.Error, cephfs.mount, mount_root = b"/nowhere") +@with_setup(setup_test) +def test_utime(): + fd = cephfs.open(b'/file-1', 'w', 0o755) + cephfs.write(fd, b'0000', 0) + cephfs.close(fd) + + stx_pre = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + time.sleep(1) + cephfs.utime(b'/file-1') + + stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + assert_greater(stx_post['atime'], stx_pre['atime']) + assert_greater(stx_post['mtime'], stx_pre['mtime']) + + atime_pre = int(time.mktime(stx_pre['atime'].timetuple())) + mtime_pre = int(time.mktime(stx_pre['mtime'].timetuple())) + + cephfs.utime(b'/file-1', (atime_pre, mtime_pre)) + stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + assert_equal(stx_post['atime'], stx_pre['atime']) + assert_equal(stx_post['mtime'], stx_pre['mtime']) + + cephfs.unlink(b'/file-1') + +@with_setup(setup_test) +def test_futime(): + fd = cephfs.open(b'/file-1', 'w', 0o755) + cephfs.write(fd, b'0000', 0) + + stx_pre = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + time.sleep(1) + cephfs.futime(fd) + + stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + assert_greater(stx_post['atime'], stx_pre['atime']) + assert_greater(stx_post['mtime'], stx_pre['mtime']) + + atime_pre = int(time.mktime(stx_pre['atime'].timetuple())) + mtime_pre = int(time.mktime(stx_pre['mtime'].timetuple())) + + cephfs.futime(fd, (atime_pre, mtime_pre)) + stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + assert_equal(stx_post['atime'], stx_pre['atime']) + assert_equal(stx_post['mtime'], stx_pre['mtime']) + + cephfs.close(fd) + cephfs.unlink(b'/file-1') + +@with_setup(setup_test) +def test_utimes(): + fd = cephfs.open(b'/file-1', 'w', 0o755) + cephfs.write(fd, b'0000', 0) + cephfs.close(fd) + + stx_pre = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + time.sleep(1) + cephfs.utimes(b'/file-1') + + stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + assert_greater(stx_post['atime'], stx_pre['atime']) + assert_greater(stx_post['mtime'], stx_pre['mtime']) + + atime_pre = time.mktime(stx_pre['atime'].timetuple()) + mtime_pre = time.mktime(stx_pre['mtime'].timetuple()) + + cephfs.utimes(b'/file-1', (atime_pre, mtime_pre)) + stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + assert_equal(stx_post['atime'], stx_pre['atime']) + assert_equal(stx_post['mtime'], stx_pre['mtime']) + + cephfs.unlink(b'/file-1') + +@with_setup(setup_test) +def test_lutimes(): + 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_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + stx_pre_s = cephfs.statx(b'/file-2', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, libcephfs.AT_SYMLINK_NOFOLLOW) + + time.sleep(1) + cephfs.lutimes(b'/file-2') + + stx_post_t = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + stx_post_s = cephfs.statx(b'/file-2', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, libcephfs.AT_SYMLINK_NOFOLLOW) + + assert_equal(stx_post_t['atime'], stx_pre_t['atime']) + assert_equal(stx_post_t['mtime'], stx_pre_t['mtime']) + + assert_greater(stx_post_s['atime'], stx_pre_s['atime']) + assert_greater(stx_post_s['mtime'], stx_pre_s['mtime']) + + atime_pre = time.mktime(stx_pre_s['atime'].timetuple()) + mtime_pre = time.mktime(stx_pre_s['mtime'].timetuple()) + + cephfs.lutimes(b'/file-2', (atime_pre, mtime_pre)) + stx_post_s = cephfs.statx(b'/file-2', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, libcephfs.AT_SYMLINK_NOFOLLOW) + + assert_equal(stx_post_s['atime'], stx_pre_s['atime']) + assert_equal(stx_post_s['mtime'], stx_pre_s['mtime']) + + cephfs.unlink(b'/file-2') + cephfs.unlink(b'/file-1') + +@with_setup(setup_test) +def test_futimes(): + fd = cephfs.open(b'/file-1', 'w', 0o755) + cephfs.write(fd, b'0000', 0) + + stx_pre = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + time.sleep(1) + cephfs.futimes(fd) + + stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + assert_greater(stx_post['atime'], stx_pre['atime']) + assert_greater(stx_post['mtime'], stx_pre['mtime']) + + atime_pre = time.mktime(stx_pre['atime'].timetuple()) + mtime_pre = time.mktime(stx_pre['mtime'].timetuple()) + + cephfs.futimes(fd, (atime_pre, mtime_pre)) + stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + assert_equal(stx_post['atime'], stx_pre['atime']) + assert_equal(stx_post['mtime'], stx_pre['mtime']) + + cephfs.close(fd) + cephfs.unlink(b'/file-1') + +@with_setup(setup_test) +def test_futimens(): + fd = cephfs.open(b'/file-1', 'w', 0o755) + cephfs.write(fd, b'0000', 0) + + stx_pre = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + time.sleep(1) + cephfs.futimens(fd) + + stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + assert_greater(stx_post['atime'], stx_pre['atime']) + assert_greater(stx_post['mtime'], stx_pre['mtime']) + + atime_pre = time.mktime(stx_pre['atime'].timetuple()) + mtime_pre = time.mktime(stx_pre['mtime'].timetuple()) + + cephfs.futimens(fd, (atime_pre, mtime_pre)) + stx_post = cephfs.statx(b'/file-1', libcephfs.CEPH_STATX_ATIME | libcephfs.CEPH_STATX_MTIME, 0) + + assert_equal(stx_post['atime'], stx_pre['atime']) + assert_equal(stx_post['mtime'], stx_pre['mtime']) + + cephfs.close(fd) + cephfs.unlink(b'/file-1') -- 2.39.5