]> arthur.barton.de Git - bup.git/commitdiff
Always publish utimensat in helpers when available and fix type conversions.
authorRob Browning <rlb@defaultvalue.org>
Wed, 18 Dec 2013 02:56:52 +0000 (20:56 -0600)
committerRob Browning <rlb@defaultvalue.org>
Sat, 21 Dec 2013 17:53:03 +0000 (11:53 -0600)
Previously, bup assumed a timespec was (long, long), which agrees with
glibc.info, but utimensat(2) and POSIX say it's (time_t, long).

Since the types vary (and even if they didn't, Python doesn't provide
safe "time_t" conversions), add ASSIGN_PYLONG_TO_INTEGRAL(), which
should handle safely assigning a PyLong value to any given C integral
destination, whenever it will fit -- then use that macro to handle the
utimensat arguments.

Whenever it's available, publish utimensat independently in helpers.
For now, this allows us to test utimensat directly, and in the future,
it may allow us to test bup's behavior with either utimensat or
utimes/lutimes without recompilation, whenever both are available.

Signed-off-by: Rob Browning <rlb@defaultvalue.org>
lib/bup/_helpers.c
lib/bup/t/txstat.py
lib/bup/xstat.py

index 9634b1e6d0643fcec76ab128023c75a5626f0f5b..d9dcbed64451a9aada0ddea07d6aa238523a601f 100644 (file)
@@ -837,49 +837,89 @@ static int bup_parse_xutime_args(char **path,
           || defined(HAVE_LUTIMES) */
 
 
+#define INTEGRAL_ASSIGNMENT_FITS(dest, src)                             \
+    ({                                                                  \
+        *(dest) = (src);                                                \
+        *(dest) == (src) && (*(dest) < 1) == ((src) < 1);               \
+    })
+
+
+#define ASSIGN_PYLONG_TO_INTEGRAL(dest, pylong, overflow) \
+    ({                                                     \
+        int result = 0;                                                 \
+        *(overflow) = 0;                                                \
+        const long long ltmp = PyLong_AsLongLong(pylong);               \
+        if (ltmp == -1 && PyErr_Occurred())                             \
+        {                                                               \
+            if (PyErr_ExceptionMatches(PyExc_OverflowError))            \
+            {                                                           \
+                const unsigned long ultmp = PyLong_AsUnsignedLongLong(pylong); \
+                if (ultmp == (unsigned long long) -1 && PyErr_Occurred()) \
+                {                                                       \
+                    if (PyErr_ExceptionMatches(PyExc_OverflowError))    \
+                    {                                                   \
+                        PyErr_Clear();                                  \
+                        *(overflow) = 1;                                \
+                    }                                                   \
+                }                                                       \
+                if (INTEGRAL_ASSIGNMENT_FITS((dest), ultmp))            \
+                    result = 1;                                         \
+                else                                                    \
+                    *(overflow) = 1;                                    \
+            }                                                           \
+        }                                                               \
+        else                                                            \
+        {                                                               \
+            if (INTEGRAL_ASSIGNMENT_FITS((dest), ltmp))                 \
+                result = 1;                                             \
+            else                                                        \
+                *(overflow) = 1;                                        \
+        }                                                               \
+        result;                                                         \
+        })
+
+
 #ifdef HAVE_UTIMENSAT
 
-static PyObject *bup_xutime_ns(PyObject *self, PyObject *args,
-                               int follow_symlinks)
+static PyObject *bup_utimensat(PyObject *self, PyObject *args)
 {
     int rc;
+    int fd, flag;
     char *path;
-    long access, access_ns, modification, modification_ns;
+    PyObject *access_py, *modification_py;
     struct timespec ts[2];
 
-    if (!bup_parse_xutime_args(&path, &access, &access_ns,
-                               &modification, &modification_ns,
-                               self, args))
-       return NULL;
+    if (!PyArg_ParseTuple(args, "is((Ol)(Ol))i",
+                          &fd,
+                          &path,
+                          &access_py, &(ts[0].tv_nsec),
+                          &modification_py, &(ts[1].tv_nsec),
+                          &flag))
+        return NULL;
 
-    ts[0].tv_sec = access;
-    ts[0].tv_nsec = access_ns;
-    ts[1].tv_sec = modification;
-    ts[1].tv_nsec = modification_ns;
-    rc = utimensat(AT_FDCWD, path, ts,
-                   follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW);
+    int overflow;
+    if (!ASSIGN_PYLONG_TO_INTEGRAL(&(ts[0].tv_sec), access_py, &overflow))
+    {
+        if (overflow)
+            PyErr_SetString(PyExc_ValueError,
+                            "unable to convert access time seconds for utimensat");
+        return NULL;
+    }
+    if (!ASSIGN_PYLONG_TO_INTEGRAL(&(ts[1].tv_sec), modification_py, &overflow))
+    {
+        if (overflow)
+            PyErr_SetString(PyExc_ValueError,
+                            "unable to convert modification time seconds for utimensat");
+        return NULL;
+    }
+    rc = utimensat(fd, path, ts, flag);
     if (rc != 0)
         return PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
 
     return Py_BuildValue("O", Py_None);
 }
 
-
-#define BUP_HAVE_BUP_UTIME_NS 1
-static PyObject *bup_utime_ns(PyObject *self, PyObject *args)
-{
-    return bup_xutime_ns(self, args, 1);
-}
-
-
-#define BUP_HAVE_BUP_LUTIME_NS 1
-static PyObject *bup_lutime_ns(PyObject *self, PyObject *args)
-{
-    return bup_xutime_ns(self, args, 0);
-}
-
-
-#else /* not defined(HAVE_UTIMENSAT) */
+#endif /* def HAVE_UTIMENSAT */
 
 
 #ifdef HAVE_UTIMES
@@ -936,9 +976,6 @@ static PyObject *bup_lutime_ns(PyObject *self, PyObject *args)
 #endif /* def HAVE_LUTIMES */
 
 
-#endif /* not defined(HAVE_UTIMENSAT) */
-
-
 #ifdef HAVE_STAT_ST_ATIM
 # define BUP_STAT_ATIME_NS(st) (st)->st_atim.tv_nsec
 # define BUP_STAT_MTIME_NS(st) (st)->st_mtim.tv_nsec
@@ -1123,6 +1160,10 @@ static PyMethodDef helper_methods[] = {
     { "set_linux_file_attr", bup_set_linux_file_attr, METH_VARARGS,
       "Set the Linux attributes for the given file." },
 #endif
+#ifdef HAVE_UTIMENSAT
+    { "bup_utimensat", bup_utimensat, METH_VARARGS,
+      "Change path timestamps with nanosecond precision (POSIX)." },
+#endif
 #ifdef BUP_HAVE_BUP_UTIME_NS
     { "bup_utime_ns", bup_utime_ns, METH_VARARGS,
       "Change path timestamps with up to nanosecond precision." },
@@ -1158,6 +1199,22 @@ PyMODINIT_FUNC init_helpers(void)
     PyObject *m = Py_InitModule("_helpers", helper_methods);
     if (m == NULL)
         return;
+
+#ifdef HAVE_UTIMENSAT
+    {
+        PyObject *value;
+        value = INTEGER_TO_PY(AT_FDCWD);
+        PyObject_SetAttrString(m, "AT_FDCWD", value);
+        Py_DECREF(value);
+        value = INTEGER_TO_PY(AT_SYMLINK_NOFOLLOW);
+        PyObject_SetAttrString(m, "AT_SYMLINK_NOFOLLOW", value);
+        Py_DECREF(value);
+        value = INTEGER_TO_PY(UTIME_NOW);
+        PyObject_SetAttrString(m, "UTIME_NOW", value);
+        Py_DECREF(value);
+    }
+#endif
+
     e = getenv("BUP_FORCE_TTY");
     istty2 = isatty(2) || (atoi(e ? e : "0") & 2);
     unpythonize_argv();
index d732636a2a9cd7ca7daa25990bf0df4718e1550d..7cb144b911bbccc39c8a50408a2995dc77d05d3b 100644 (file)
@@ -39,6 +39,27 @@ def test_fstime():
     WVPASSEQ(type(xstat.fstime_floor_secs(-10**9 / 2)), type(0))
 
 
+@wvtest
+def test_bup_utimensat():
+    if not xstat._bup_utimensat:
+        return
+    tmpdir = tempfile.mkdtemp(prefix='bup-tmetadata-')
+    try:
+        path = tmpdir + '/foo'
+        open(path, 'w').close()
+        frac_ts = (0, 10**9 / 2)
+        xstat._bup_utimensat(_helpers.AT_FDCWD, path, (frac_ts, frac_ts), 0)
+        st = _helpers.stat(path)
+        atime_ts = st[8]
+        mtime_ts = st[9]
+        WVPASSEQ(atime_ts[0], 0)
+        WVPASS(atime_ts[1] == 0 or atime_ts[1] == frac_ts[1])
+        WVPASSEQ(mtime_ts[0], 0)
+        WVPASS(mtime_ts[1] == 0 or mtime_ts[1] == frac_ts[1])
+    finally:
+        subprocess.call(['rm', '-rf', tmpdir])
+
+
 try:
     _have_bup_utime_ns = _helpers.bup_utime_ns
 except AttributeError, e:
index 54dfbae4a4a5001e6fafb26ee4100c69d1f04f44..f715d1fb4c2d1af5bedaa982b9bc892784294613 100644 (file)
@@ -3,6 +3,10 @@ import os
 import stat as pystat
 from bup import _helpers
 
+try:
+    _bup_utimensat = _helpers.bup_utimensat
+except AttributeError, e:
+    _bup_utimensat = False
 
 def timespec_to_nsecs((ts_s, ts_ns)):
     # c.f. _helpers.c: timespec_vals_to_py_ns()
@@ -36,19 +40,29 @@ def fstime_to_sec_str(fstime):
     else:
         return '%d.%09d' % (s, ns)
 
-
-def utime(path, times):
-    """Times must be provided as (atime_ns, mtime_ns)."""
-    atime = nsecs_to_timespec(times[0])
-    mtime = nsecs_to_timespec(times[1])
-    _helpers.bup_utime_ns(path, (atime, mtime))
-
-
-def lutime(path, times):
-    """Times must be provided as (atime_ns, mtime_ns)."""
-    atime = nsecs_to_timespec(times[0])
-    mtime = nsecs_to_timespec(times[1])
-    _helpers.bup_lutime_ns(path, (atime, mtime))
+if _bup_utimensat:
+    def utime(path, times):
+        """Times must be provided as (atime_ns, mtime_ns)."""
+        atime = nsecs_to_timespec(times[0])
+        mtime = nsecs_to_timespec(times[1])
+        _bup_utimensat(_helpers.AT_FDCWD, path, (atime, mtime), 0)
+    def lutime(path, times):
+        """Times must be provided as (atime_ns, mtime_ns)."""
+        atime = nsecs_to_timespec(times[0])
+        mtime = nsecs_to_timespec(times[1])
+        _bup_utimensat(_helpers.AT_FDCWD, path, (atime, mtime),
+                       _helpers.AT_SYMLINK_NOFOLLOW)
+else:
+    def utime(path, times):
+        """Times must be provided as (atime_ns, mtime_ns)."""
+        atime = nsecs_to_timespec(times[0])
+        mtime = nsecs_to_timespec(times[1])
+        _helpers.bup_utime_ns(path, (atime, mtime))
+    def lutime(path, times):
+        """Times must be provided as (atime_ns, mtime_ns)."""
+        atime = nsecs_to_timespec(times[0])
+        mtime = nsecs_to_timespec(times[1])
+        _helpers.bup_lutime_ns(path, (atime, mtime))
 
 
 class stat_result: