]> arthur.barton.de Git - bup.git/commitdiff
Add (sec, ns) timestamps and extended stat, lstat, utime, and lutime.
authorRob Browning <rlb@defaultvalue.org>
Fri, 24 Sep 2010 03:00:07 +0000 (22:00 -0500)
committerRob Browning <rlb@defaultvalue.org>
Fri, 24 Sep 2010 03:00:07 +0000 (22:00 -0500)
Replace py_lutimes() with bup_utimensat() and when available, use it
from helpers to provide high resolution lutime() and utime().  Also
provide the utimensat AT_FDCWD and AT_SYMLINK_NOFOLLOW flags from
_helpers.

Add bup_lstat(), and when available, use it from helpers to provide an
extended lstat(), which for now just adds full timestamp resolution.

Change the timestamp encoding to a signed pair of (seconds,
nanoseconds) both internally, and in the archive.

lib/bup/_helpers.c
lib/bup/helpers.py
lib/bup/metadata.py

index 0c9aae3c2bfdec975d42c1c70bdac91a1e457f55..973de8a721cc4eee40f472958eaac5cc1a17b3e0 100644 (file)
@@ -253,56 +253,119 @@ static PyObject *py_set_linux_file_attr(PyObject *self, PyObject *args)
 }
 
 
-static PyObject *py_lutimes(PyObject *self, PyObject *args)
-{
-    int rc;
-    char *filename;
-    double access, modification;
+#if _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L
+#define HAVE_BUP_UTIMENSAT 1
 
-    if (!PyArg_ParseTuple(args, "s(dd)", &filename, &access, &modification))
+static PyObject *bup_utimensat(PyObject *self, PyObject *args)
+{
+    int rc, dirfd, flags;
+    char *path;
+    long access, access_ns, modification, modification_ns;
+    struct timespec ts[2];
+
+    if (!PyArg_ParseTuple(args, "is((ll)(ll))i",
+                          &dirfd,
+                          &path,
+                          &access, &access_ns,
+                          &modification, &modification_ns,
+                          &flags))
         return NULL;
 
-    if(isnan(access))
+    if (isnan(access))
     {
         PyErr_SetString(PyExc_ValueError, "access time is NaN");
         return NULL;
     }
-    else if(isinf(access))
+    else if (isinf(access))
     {
         PyErr_SetString(PyExc_ValueError, "access time is infinite");
         return NULL;
     }
-    else if(isnan(modification))
+    else if (isnan(modification))
     {
         PyErr_SetString(PyExc_ValueError, "modification time is NaN");
         return NULL;
     }
-    else if(isinf(modification))
+    else if (isinf(modification))
     {
         PyErr_SetString(PyExc_ValueError, "modification time is infinite");
         return NULL;
     }
 
-    struct timeval tv[2];
+    if (isnan(access_ns))
+    {
+        PyErr_SetString(PyExc_ValueError, "access time ns is NaN");
+        return NULL;
+    }
+    else if (isinf(access_ns))
+    {
+        PyErr_SetString(PyExc_ValueError, "access time ns is infinite");
+        return NULL;
+    }
+    else if (isnan(modification_ns))
+    {
+        PyErr_SetString(PyExc_ValueError, "modification time ns is NaN");
+        return NULL;
+    }
+    else if (isinf(modification_ns))
+    {
+        PyErr_SetString(PyExc_ValueError, "modification time ns is infinite");
+        return NULL;
+    }
+
+    ts[0].tv_sec = access;
+    ts[0].tv_nsec = access_ns;
+    ts[1].tv_sec = modification;
+    ts[1].tv_nsec = modification_ns;
 
-    double integral_part;
-    double fractional_part;
+    rc = utimensat(dirfd, path, ts, flags);
+    if (rc != 0)
+        return PyErr_SetFromErrnoWithFilename(PyExc_IOError, path);
+
+    Py_RETURN_TRUE;
+}
+
+#endif /* _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L */
 
-    fractional_part = modf(access, &integral_part);
-    tv[0].tv_sec = integral_part;
-    tv[0].tv_usec = fractional_part * 1000000;
 
-    fractional_part = modf(modification, &integral_part);
-    tv[1].tv_sec = modification;
-    tv[1].tv_usec = fmod(modification, 1000000);
+#ifdef linux /* and likely others */
+#define HAVE_BUP_LSTAT 1
+
+static PyObject *bup_lstat(PyObject *self, PyObject *args)
+{
+    int rc;
+    char *filename;
+
+    if (!PyArg_ParseTuple(args, "s", &filename))
+        return NULL;
 
-    rc = lutimes(filename, tv);
-    if(rc != 0)
+    struct stat st;
+    rc = lstat(filename, &st);
+    if (rc != 0)
         return PyErr_SetFromErrnoWithFilename(PyExc_IOError, filename);
 
-    Py_RETURN_TRUE;
+    return Py_BuildValue("kkkkkkkk"
+                         "(ll)"
+                         "(ll)"
+                         "(ll)",
+                         (unsigned long) st.st_mode,
+                         (unsigned long) st.st_ino,
+                         (unsigned long) st.st_dev,
+                         (unsigned long) st.st_nlink,
+                         (unsigned long) st.st_uid,
+                         (unsigned long) st.st_gid,
+                         (unsigned long) st.st_rdev,
+                         (unsigned long) st.st_size,
+                         (long) st.st_atime,
+                         (long) st.st_atim.tv_nsec,
+                         (long) st.st_mtime,
+                         (long) st.st_mtim.tv_nsec,
+                         (long) st.st_ctime,
+                         (long) st.st_ctim.tv_nsec);
 }
 
+#endif /* def linux */
+
 
 static PyMethodDef helper_methods[] = {
     { "selftest", selftest, METH_VARARGS,
@@ -327,12 +390,26 @@ static PyMethodDef helper_methods[] = {
       "Return the Linux attributes for the given file." },
     { "set_linux_file_attr", py_set_linux_file_attr, METH_VARARGS,
       "Set the Linux attributes for the given file." },
-    { "lutimes", py_lutimes, METH_VARARGS,
-      "Set the access and modification times for the given file or symlink." },
+#ifdef HAVE_BUP_UTIMENSAT
+    { "utimensat", bup_utimensat, METH_VARARGS,
+      "Change file timestamps with nanosecond precision." },
+#endif
+#ifdef HAVE_BUP_LSTAT
+    { "lstat", bup_lstat, METH_VARARGS,
+      "Extended version of lstat." },
+#endif
     { NULL, NULL, 0, NULL },  // sentinel
 };
 
+
 PyMODINIT_FUNC init_helpers(void)
 {
-    Py_InitModule("_helpers", helper_methods);
+    PyObject *m = Py_InitModule("_helpers", helper_methods);
+    if (m == NULL)
+        return;
+#ifdef HAVE_BUP_UTIMENSAT
+    PyModule_AddObject(m, "AT_FDCWD", Py_BuildValue("i", AT_FDCWD));
+    PyModule_AddObject(m, "AT_SYMLINK_NOFOLLOW",
+                       Py_BuildValue("i", AT_SYMLINK_NOFOLLOW));
+#endif
 }
index bf431455921de2b0d558de67516e2f2201b77198..606c1351ccdf619e875ce1c2bf072e030d8811cd 100644 (file)
@@ -1,6 +1,7 @@
 """Helper functions and classes for bup."""
 import sys, os, pwd, subprocess, errno, socket, select, mmap, stat, re
 from bup import _version
+import bup._helpers as _helpers
 
 # This function should really be in helpers, not in bup.options.  But we
 # want options.py to be standalone so people can include it in other projects.
@@ -407,6 +408,65 @@ def parse_date_or_fatal(str, fatal):
         return date
 
 
+def lutime(path, times):
+    if _helpers.utimensat:
+        atime = times[0]
+        mtime = times[1]
+        return _helpers.utimensat(_helpers.AT_FDCWD, path, (atime, mtime),
+                                  _helpers.AT_SYMLINK_NOFOLLOW)
+    else:
+        return None
+
+
+def utime(path, times):
+    atime = times[0]
+    mtime = times[1]
+    if _helpers.utimensat:
+        return _helpers.utimensat(_helpers.AT_FDCWD, path, (atime, mtime),
+                                  0)
+    else:
+        os.utime(path, (atime[0] + atime[1] / 10e9,
+                        mtime[0] + mtime[1] / 10e9))
+
+
+class stat_result():
+    pass
+
+
+def lstat(path):
+    result = stat_result()
+    if _helpers.lstat:
+        st = _helpers.lstat(path)
+        (result.st_mode,
+         result.st_ino,
+         result.st_dev,
+         result.st_nlink,
+         result.st_uid,
+         result.st_gid,
+         result.st_rdev,
+         result.st_size,
+         result.st_atime,
+         result.st_mtime,
+         result.st_ctime) = st
+    else:
+        st = os.lstat(path)
+        result.st_mode = st.st_mode
+        result.st_ino = st.st_ino
+        result.st_dev = st.st_dev
+        result.st_nlink = st.st_nlink
+        result.st_uid = st.st_uid
+        result.st_gid = st.st_gid
+        result.st_rdev = st.st_rdev
+        result.st_size = st.st_size
+        result.st_atime = (math.trunc(st.st_atime),
+                           math.trunc(math.fmod(st.st_atime, 1) * 10**9))
+        result.st_mtime = (math.trunc(st.st_mtime),
+                           math.trunc(math.fmod(st.st_mtime, 1) * 10**9))
+        result.st_ctime = (math.trunc(st.st_ctime),
+                           math.trunc(math.fmod(st.st_ctime, 1) * 10**9))
+    return result
+
+
 # hashlib is only available in python 2.5 or higher, but the 'sha' module
 # produces a DeprecationWarning in python 2.6 or higher.  We want to support
 # python 2.4 and above without any stupid warnings, so let's try using hashlib
index f6d12acaccc517291b6614012d547d49ba13a4a3..cca82234d65fd6c70e463628e5151cecd8a851b1 100644 (file)
@@ -9,8 +9,8 @@ import errno, os, sys, stat, pwd, grp, struct, xattr, posix1e, re
 
 from cStringIO import StringIO
 from bup import vint
-from bup.helpers import mkdirp, log
-from bup._helpers import get_linux_file_attr, set_linux_file_attr, lutimes
+from bup.helpers import mkdirp, log, utime, lutime, lstat
+from bup._helpers import get_linux_file_attr, set_linux_file_attr
 
 # WARNING: the metadata encoding is *not* stable yet.  Caveat emptor!
 
@@ -152,27 +152,27 @@ class Metadata:
         self.mode = st.st_mode
         self.uid = st.st_uid
         self.gid = st.st_gid
+        self.rdev = st.st_rdev
         self.atime = st.st_atime
         self.mtime = st.st_mtime
         self.ctime = st.st_ctime
-        self.rdev = st.st_rdev
         self.owner = pwd.getpwuid(st.st_uid)[0]
         self.group = grp.getgrgid(st.st_gid)[0]
 
     def _encode_common(self):
-        result = vint.pack('VVsVsVVVVVVV',
+        result = vint.pack('VVsVsVvvvvvv',
                            self.mode,
                            self.uid,
                            self.owner,
                            self.gid,
                            self.group,
-                           int(self.atime),
-                           int(self.mtime),
-                           int(self.ctime),
-                           int(self.atime * 1e9) % 1000000000,
-                           int(self.mtime * 1e9) % 1000000000,
-                           int(self.ctime * 1e9) % 1000000000,
-                           self.rdev)
+                           self.rdev,
+                           self.atime[0],
+                           self.atime[1],
+                           self.mtime[0],
+                           self.mtime[1],
+                           self.ctime[0],
+                           self.ctime[1])
         return result
 
     def _load_common_rec(self, port):
@@ -182,16 +182,16 @@ class Metadata:
          self.owner,
          self.gid,
          self.group,
-         atime_s,
-         mtime_s,
-         ctime_s,
+         self.rdev,
+         self.atime,
          atime_ns,
+         self.mtime,
          mtime_ns,
-         ctime_ns,
-         self.rdev) = vint.unpack('VVsVsVVVVVVV', data)
-        self.atime = atime_s + (atime_ns / 1e9)
-        self.mtime = mtime_s + (mtime_ns / 1e9)
-        self.ctime = ctime_s + (ctime_ns / 1e9)
+         self.ctime,
+         ctime_ns) = vint.unpack('VVsVsVvvvvvv', data)
+        self.atime = (self.atime, atime_ns)
+        self.mtime = (self.mtime, mtime_ns)
+        self.ctime = (self.ctime, ctime_ns)
 
     def _create_via_common_rec(self, path, create_symlinks=True):
         if stat.S_ISREG(self.mode):
@@ -213,9 +213,9 @@ class Metadata:
     def _apply_common_rec(self, path, restore_numeric_ids=False):
         # FIXME: S_ISDOOR, S_IFMPB, S_IFCMP, S_IFNWK, ... see stat(2).
         if stat.S_ISLNK(self.mode):
-            lutimes(path, (self.atime, self.mtime))
+            lutime(path, (self.atime, self.mtime))
         else:
-            os.utime(path, (self.atime, self.mtime))
+            utime(path, (self.atime, self.mtime))
         if stat.S_ISREG(self.mode) \
                 | stat.S_ISDIR(self.mode) \
                 | stat.S_ISCHR(self.mode) \
@@ -457,7 +457,7 @@ class Metadata:
 def from_path(path, archive_path=None, save_symlinks=True):
     result = Metadata()
     result.path = archive_path
-    st = os.lstat(path)
+    st = lstat(path)
     result._add_common(path, st)
     if(save_symlinks):
         result._add_symlink_target(path, st)