]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
pybind/cephfs: pybind calls for changing inode timestamps
authorVenky Shankar <vshankar@redhat.com>
Thu, 6 Feb 2020 10:34:24 +0000 (05:34 -0500)
committerRamana Raja <rraja@redhat.com>
Thu, 12 Mar 2020 07:20:16 +0000 (12:50 +0530)
Signed-off-by: Venky Shankar <vshankar@redhat.com>
(cherry picked from commit fc49ffd227b06495b30674eff9897a34f9fd60be)

src/pybind/cephfs/cephfs.pyx
src/test/pybind/test_cephfs.py

index aa5390ef3e121d096ad7bae3bf5f3b9afe81883a..efeeaa0a1cddadb8af0f5fb76423e4c948e8f939 100644 (file)
@@ -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:
@@ -78,6 +79,16 @@ cdef extern from "time.h":
 cdef extern from "sys/types.h":
     ctypedef unsigned long mode_t
 
+cdef extern from "<utime.h>":
+    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
@@ -162,6 +173,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):
@@ -388,6 +405,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:
@@ -1402,3 +1434,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")
index 584d150eaeddd2745bc038f0e8ae2f965fdb4e8e..164e288a50c138c908b3a04a6583a5a0550a363b 100644 (file)
@@ -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
 
@@ -234,3 +236,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')