[Python-checkins] cpython: Issue #14127: Add ns= parameter to utime, futimes, and lutimes.

larry.hastings python-checkins at python.org
Thu May 3 09:31:05 CEST 2012


http://hg.python.org/cpython/rev/bba131e48852
changeset:   76725:bba131e48852
user:        Larry Hastings <larry at hastings.org>
date:        Thu May 03 00:30:07 2012 -0700
summary:
  Issue #14127: Add ns= parameter to utime, futimes, and lutimes.
Removed futimens as it is now redundant.
Changed shutil.copystat to use st_atime_ns and st_mtime_ns from os.stat
and ns= parameter to utime--it once again preserves exact metadata on Linux!

files:
  Doc/library/os.rst    |   54 ++-
  Include/pytime.h      |    4 +
  Lib/shutil.py         |    2 +-
  Lib/test/test_os.py   |   86 ++++-
  Modules/posixmodule.c |  457 +++++++++++++++--------------
  Python/pytime.c       |    2 +-
  6 files changed, 357 insertions(+), 248 deletions(-)


diff --git a/Doc/library/os.rst b/Doc/library/os.rst
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -934,13 +934,11 @@
    .. versionadded:: 3.3
 
 
-.. function:: futimes(fd[, times])
+.. function:: futimes(fd[, times, *, ns=times])
 
    Set the access and modified time of the file specified by the file
-   descriptor *fd* to the given values.  *atimes* must be a 2-tuple of numbers,
-   of the form ``(atime, mtime)``, or None.  If no second argument is used,
-   set the access and modified times to the current time.
-
+   descriptor *fd* to the given values.  See :func:`utime` for proper
+   use of the *times* and *ns* arguments.
    Availability: Unix.
 
    .. versionadded:: 3.3
@@ -1762,12 +1760,11 @@
       Added support for Windows 6.0 (Vista) symbolic links.
 
 
-.. function:: lutimes(path[, times])
+.. function:: lutimes(path[, times, *, ns=times])
 
    Like :func:`utime`, but if *path* is a symbolic link, it is not
-   dereferenced.  *times* must be a 2-tuple of numbers, of the form
-   ``(atime, mtime)``, or None.
-
+   dereferenced.  See :func:`utime` for proper use of the
+   *times* and *ns* arguments.
 
    Availability: Unix.
 
@@ -2226,22 +2223,43 @@
    Availability: Unix, Windows.
 
 
-.. function:: utime(path[, times])
-
-   Set the access and modified times of the file specified by *path*. If *times*
-   is ``None`` or not specified, then the file's access and modified times are
-   set to the current time. (The effect is similar to running the Unix program
-   :program:`touch` on the path.)  Otherwise, *times* must be a 2-tuple of
-   numbers, of the form ``(atime, mtime)`` which is used to set the access and
-   modified times, respectively. Whether a directory can be given for *path*
+.. function:: utime(path[, times, *, ns=(atime_ns, mtime_ns)])
+
+   Set the access and modified times of the file specified by *path*.
+
+   :func:`utime` takes two optional parameters, *times* and *ns*.
+   These specify the times set on *path* and are used as follows:
+
+   - If *ns* is specified,
+     it must be a 2-tuple of the form ``(atime_ns, mtime_ns)``
+     where each member is an int expressing nanoseconds.
+   - If *times* is specified and is not ``None``,
+     it must be a 2-tuple of the form ``(atime, mtime)``
+     where each member is an int or float expressing seconds.
+   - If *times* is specified as ``None``,
+     this is equivalent to specifying an ``(atime, mtime)``
+     where both times are the current time.
+     (The effect is similar to running the Unix program
+     :program:`touch` on *path*.)
+   - If neither *ns* nor *times* is specified, this is
+     equivalent to specifying *times* as ``None``.
+
+   Specifying both *times* and *ns* simultaneously is an error.
+
+   Whether a directory can be given for *path*
    depends on whether the operating system implements directories as files
    (for example, Windows does not).  Note that the exact times you set here may
    not be returned by a subsequent :func:`~os.stat` call, depending on the
    resolution with which your operating system records access and modification
-   times; see :func:`~os.stat`.
+   times; see :func:`~os.stat`.  The best way to preserve exact times is to
+   use the *st_atime_ns* and *st_mtime_ns* fields from the :func:`os.stat`
+   result object with the *ns* parameter to `utime`.
 
    Availability: Unix, Windows.
 
+   .. versionadded:: 3.3
+      The :attr:`ns` keyword parameter.
+
 
 .. function:: walk(top, topdown=True, onerror=None, followlinks=False)
 
diff --git a/Include/pytime.h b/Include/pytime.h
--- a/Include/pytime.h
+++ b/Include/pytime.h
@@ -62,6 +62,10 @@
 PyAPI_FUNC(PyObject *) _PyLong_FromTime_t(
     time_t sec);
 
+/* Convert a PyLong to a time_t. */
+PyAPI_FUNC(time_t) _PyLong_AsTime_t(
+    PyObject *obj);
+
 /* Convert a number of seconds, int or float, to a timeval structure.
    usec is in the range [0; 999999] and rounded towards zero.
    For example, -1.2 is converted to (-2, 800000). */
diff --git a/Lib/shutil.py b/Lib/shutil.py
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -154,7 +154,7 @@
 
     st = stat_func(src)
     mode = stat.S_IMODE(st.st_mode)
-    utime_func(dst, (st.st_atime, st.st_mtime))
+    utime_func(dst, ns=(st.st_atime_ns, st.st_mtime_ns))
     chmod_func(dst, mode)
     if hasattr(st, 'st_flags'):
         try:
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -192,11 +192,11 @@
                 self.assertIn(attr, members)
 
         # Make sure that the st_?time and st_?time_ns fields roughly agree
-        # (they should always agree up to the tens-of-microseconds magnitude)
+        # (they should always agree up to around tens-of-microseconds)
         for name in 'st_atime st_mtime st_ctime'.split():
             floaty = int(getattr(result, name) * 100000)
             nanosecondy = getattr(result, name + "_ns") // 10000
-            self.assertEqual(floaty, nanosecondy)
+            self.assertAlmostEqual(floaty, nanosecondy, delta=2)
 
         try:
             result[200]
@@ -303,20 +303,80 @@
         st2 = os.stat(support.TESTFN)
         self.assertEqual(st2.st_mtime, int(st.st_mtime-delta))
 
-    def test_utime_noargs(self):
+    def _test_utime(self, filename, attr, utime, delta):
         # Issue #13327 removed the requirement to pass None as the
         # second argument. Check that the previous methods of passing
         # a time tuple or None work in addition to no argument.
-        st = os.stat(support.TESTFN)
+        st0 = os.stat(filename)
         # Doesn't set anything new, but sets the time tuple way
-        os.utime(support.TESTFN, (st.st_atime, st.st_mtime))
+        utime(filename, (attr(st0, "st_atime"), attr(st0, "st_mtime")))
+        # Setting the time to the time you just read, then reading again,
+        # should always return exactly the same times.
+        st1 = os.stat(filename)
+        self.assertEqual(attr(st0, "st_mtime"), attr(st1, "st_mtime"))
+        self.assertEqual(attr(st0, "st_atime"), attr(st1, "st_atime"))
         # Set to the current time in the old explicit way.
-        os.utime(support.TESTFN, None)
-        st1 = os.stat(support.TESTFN)
+        os.utime(filename, None)
+        st2 = os.stat(support.TESTFN)
         # Set to the current time in the new way
-        os.utime(support.TESTFN)
-        st2 = os.stat(support.TESTFN)
-        self.assertAlmostEqual(st1.st_mtime, st2.st_mtime, delta=10)
+        os.utime(filename)
+        st3 = os.stat(filename)
+        self.assertAlmostEqual(attr(st2, "st_mtime"), attr(st3, "st_mtime"), delta=delta)
+
+    def test_utime(self):
+        def utime(file, times):
+            return os.utime(file, times)
+        self._test_utime(self.fname, getattr, utime, 10)
+        self._test_utime(support.TESTFN, getattr, utime, 10)
+
+
+    def _test_utime_ns(self, set_times_ns, test_dir=True):
+        def getattr_ns(o, attr):
+            return getattr(o, attr + "_ns")
+        ten_s = 10 * 1000 * 1000 * 1000
+        self._test_utime(self.fname, getattr_ns, set_times_ns, ten_s)
+        if test_dir:
+            self._test_utime(support.TESTFN, getattr_ns, set_times_ns, ten_s)
+
+    def test_utime_ns(self):
+        def utime_ns(file, times):
+            return os.utime(file, ns=times)
+        self._test_utime_ns(utime_ns)
+
+    requires_lutimes = unittest.skipUnless(hasattr(os, 'lutimes'),
+                            "os.lutimes required for this test.")
+    requires_futimes = unittest.skipUnless(hasattr(os, 'futimes'),
+                            "os.futimes required for this test.")
+
+    @requires_lutimes
+    def test_lutimes_ns(self):
+        def lutimes_ns(file, times):
+            return os.lutimes(file, ns=times)
+        self._test_utime_ns(lutimes_ns)
+
+    @requires_futimes
+    def test_futimes_ns(self):
+        def futimes_ns(file, times):
+            with open(file, "wb") as f:
+                os.futimes(f.fileno(), ns=times)
+        self._test_utime_ns(futimes_ns, test_dir=False)
+
+    def _utime_invalid_arguments(self, name, arg):
+        with self.assertRaises(RuntimeError):
+            getattr(os, name)(arg, (5, 5), ns=(5, 5))
+
+    def test_utime_invalid_arguments(self):
+        self._utime_invalid_arguments('utime', self.fname)
+
+    @requires_lutimes
+    def test_lutimes_invalid_arguments(self):
+        self._utime_invalid_arguments('lutimes', self.fname)
+
+    @requires_futimes
+    def test_futimes_invalid_arguments(self):
+        with open(self.fname, "wb") as f:
+            self._utime_invalid_arguments('futimes', f.fileno())
+
 
     @unittest.skipUnless(stat_supports_subsecond,
                          "os.stat() doesn't has a subsecond resolution")
@@ -338,8 +398,7 @@
             os.utime(filename, (atime, mtime))
         self._test_utime_subsecond(set_time)
 
-    @unittest.skipUnless(hasattr(os, 'futimes'),
-                         "os.futimes required for this test.")
+    @requires_futimes
     def test_futimes_subsecond(self):
         def set_time(filename, atime, mtime):
             with open(filename, "wb") as f:
@@ -375,8 +434,7 @@
                 os.close(dirfd)
         self._test_utime_subsecond(set_time)
 
-    @unittest.skipUnless(hasattr(os, 'lutimes'),
-                         "os.lutimes required for this test.")
+    @requires_lutimes
     def test_lutimes_subsecond(self):
         def set_time(filename, atime, mtime):
             os.lutimes(filename, (atime, mtime))
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -3572,28 +3572,194 @@
 #endif /* HAVE_UNAME */
 
 
+static int
+split_py_long_to_s_and_ns(PyObject *py_long, time_t *s, long *ns)
+{
+    int result = 0;
+    PyObject *divmod;
+    divmod = PyNumber_Divmod(py_long, billion);
+    if (!divmod)
+        goto exit;
+    *s = _PyLong_AsTime_t(PyTuple_GET_ITEM(divmod, 0));
+    if ((*s == -1) && PyErr_Occurred())
+        goto exit;
+    *ns = PyLong_AsLong(PyTuple_GET_ITEM(divmod, 1));
+    if ((*s == -1) && PyErr_Occurred())
+        goto exit;
+
+    result = 1;
+exit:
+    Py_XDECREF(divmod);
+    return result;
+}
+
+
+typedef int (*parameter_converter_t)(PyObject *, void *);
+
+typedef struct {
+    /* input only */
+    char path_format;
+    parameter_converter_t converter;
+    char *function_name;
+    char *first_argument_name;
+    PyObject *args;
+    PyObject *kwargs;
+
+    /* input/output */    
+    PyObject **path;
+
+    /* output only */
+    int now;
+    time_t atime_s;
+    long   atime_ns;
+    time_t mtime_s;
+    long   mtime_ns;
+} utime_arguments;
+
+#define DECLARE_UA(ua, fname) \
+    utime_arguments ua; \
+    memset(&ua, 0, sizeof(ua)); \
+    ua.function_name = fname; \
+    ua.args = args; \
+    ua.kwargs = kwargs; \
+    ua.first_argument_name = "path"; \
+
+/* UA_TO_FILETIME doesn't declare atime and mtime for you */
+#define UA_TO_FILETIME(ua, atime, mtime) \
+    time_t_to_FILE_TIME(ua.atime_s, ua.atime_ns, &atime); \
+    time_t_to_FILE_TIME(ua.mtime_s, ua.mtime_ns, &mtime)
+
+/* the rest of these macros declare the output variable for you */
+#define UA_TO_TIMESPEC(ua, ts) \
+    struct timespec ts[2]; \
+    ts[0].tv_sec = ua.atime_s; \
+    ts[0].tv_nsec = ua.atime_ns; \
+    ts[1].tv_sec = ua.mtime_s; \
+    ts[1].tv_nsec = ua.mtime_ns
+
+#define UA_TO_TIMEVAL(ua, tv) \
+    struct timeval tv[2]; \
+    tv[0].tv_sec = ua.atime_s; \
+    tv[0].tv_usec = ua.atime_ns / 1000; \
+    tv[1].tv_sec = ua.mtime_s; \
+    tv[1].tv_usec = ua.mtime_ns / 1000
+
+#define UA_TO_UTIMBUF(ua, u) \
+    struct utimbuf u; \
+    utimbuf.actime = ua.atime_s; \
+    utimbuf.modtime = ua.mtime_s
+
+#define UA_TO_TIME_T(ua, timet) \
+    time_t timet[2]; \
+    timet[0] = ua.atime_s; \
+    timet[1] = ua.mtime_s
+
+
+/* 
+ * utime_read_time_arguments() processes arguments for the utime
+ * family of functions.
+ * returns zero on failure.
+ */
+static int
+utime_read_time_arguments(utime_arguments *ua)
+{
+    PyObject *times = NULL;
+    PyObject *ns = NULL;
+    char format[24];
+    char *kwlist[4];
+    char **kw = kwlist;
+    int return_value;
+
+    *kw++ = ua->first_argument_name;
+    *kw++ = "times";
+    *kw++ = "ns";
+    *kw = NULL;
+
+    sprintf(format, "%c%s|O$O:%s",
+            ua->path_format,
+            ua->converter ? "&" : "",
+            ua->function_name);
+
+    if (ua->converter)
+        return_value = PyArg_ParseTupleAndKeywords(ua->args, ua->kwargs,
+            format, kwlist, ua->converter, ua->path, &times, &ns);
+    else
+        return_value = PyArg_ParseTupleAndKeywords(ua->args, ua->kwargs,
+            format, kwlist, ua->path, &times, &ns);
+
+    if (!return_value)
+        return 0;
+
+    if (times && ns) {
+        PyErr_Format(PyExc_RuntimeError,
+                     "%s: you may specify either 'times'"
+                     " or 'ns' but not both",
+                     ua->function_name);
+        return 0;
+    }
+
+    if (times && (times != Py_None)) {
+        if (!PyTuple_CheckExact(times) || (PyTuple_Size(times) != 2)) {
+            PyErr_Format(PyExc_TypeError,
+                         "%s: 'time' must be either"
+                         " a valid tuple of two ints or None",
+                         ua->function_name);
+            return 0;
+        }
+        ua->now = 0;
+        return (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 0),
+                    &(ua->atime_s), &(ua->atime_ns)) != -1)
+            && (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(times, 1),
+                    &(ua->mtime_s), &(ua->mtime_ns)) != -1);
+    }
+
+    if (ns) {
+        if (!PyTuple_CheckExact(ns) || (PyTuple_Size(ns) != 2)) {
+            PyErr_Format(PyExc_TypeError,
+                         "%s: 'ns' must be a valid tuple of two ints",
+                         ua->function_name);
+            return 0;
+        }
+        ua->now = 0;
+        return (split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 0),
+                    &(ua->atime_s), &(ua->atime_ns)))
+            && (split_py_long_to_s_and_ns(PyTuple_GET_ITEM(ns, 1),
+                    &(ua->mtime_s), &(ua->mtime_ns)));
+    }
+
+    /* either times=None, or neither times nor ns was specified. use "now". */
+    ua->now = 1;
+    return 1;
+}
+
+
 PyDoc_STRVAR(posix_utime__doc__,
-"utime(path[, (atime, mtime)])\n\
-Set the access and modified time of the file to the given values.\n\
-If no second argument is used, set the access and modified times to\n\
-the current time.");
-
-static PyObject *
-posix_utime(PyObject *self, PyObject *args)
+"utime(path[, times=(atime, mtime), *, ns=(atime_ns, mtime_ns)])\n\
+Set the access and modified time of the file.\n\
+If the second argument ('times') is specified,\n\
+    the values should be expressed as float seconds since the epoch.\n\
+If the keyword argument 'ns' is specified,\n\
+    the values should be expressed as integer nanoseconds since the epoch.\n\
+If neither the second nor the 'ns' argument is specified,\n\
+    utime uses the current time.\n\
+Specifying both 'times' and 'ns' is an error.");
+
+static PyObject *
+posix_utime(PyObject *self, PyObject *args, PyObject *kwargs)
 {
 #ifdef MS_WINDOWS
-    PyObject *arg = Py_None;
-    PyObject *obwpath;
-    wchar_t *wpath = NULL;
-    const char *apath;
+    PyObject *upath;
     HANDLE hFile;
-    time_t atimesec, mtimesec;
-    long ansec, mnsec;
+    PyObject *result = NULL;
     FILETIME atime, mtime;
-    PyObject *result = NULL;
-
-    if (PyArg_ParseTuple(args, "U|O:utime", &obwpath, &arg)) {
-        wpath = PyUnicode_AsUnicode(obwpath);
+
+    DECLARE_UA(ua, "utime");
+
+    ua.path_format = 'U';
+    ua.path = &upath;
+
+    if (!utime_read_time_arguments(&ua)) {
+        wchar_t *wpath = PyUnicode_AsUnicode(upath);
         if (wpath == NULL)
             return NULL;
         Py_BEGIN_ALLOW_THREADS
@@ -3602,14 +3768,17 @@
                             FILE_FLAG_BACKUP_SEMANTICS, NULL);
         Py_END_ALLOW_THREADS
         if (hFile == INVALID_HANDLE_VALUE)
-            return win32_error_object("utime", obwpath);
+            return win32_error_object("utime", upath);
     }
     else {
+        const char *apath;
         /* Drop the argument parsing error as narrow strings
            are also valid. */
         PyErr_Clear();
 
-        if (!PyArg_ParseTuple(args, "y|O:utime", &apath, &arg))
+        ua.path_format = 'y';
+        ua.path = (PyObject **)&apath;
+        if (!utime_read_time_arguments(&ua))
             return NULL;
         if (win32_warn_bytes_api())
             return NULL;
@@ -3625,7 +3794,7 @@
         }
     }
 
-    if (arg == Py_None) {
+    if (ua.now) {
         SYSTEMTIME now;
         GetSystemTime(&now);
         if (!SystemTimeToFileTime(&now, &mtime) ||
@@ -3634,20 +3803,8 @@
             goto done;
         }
     }
-    else if (!PyTuple_Check(arg) || PyTuple_Size(arg) != 2) {
-        PyErr_SetString(PyExc_TypeError,
-                        "utime() arg 2 must be a tuple (atime, mtime)");
-        goto done;
-    }
     else {
-        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 0),
-                                     &atimesec, &ansec) == -1)
-            goto done;
-        time_t_to_FILE_TIME(atimesec, ansec, &atime);
-        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 1),
-                                     &mtimesec, &mnsec) == -1)
-            goto done;
-        time_t_to_FILE_TIME(mtimesec, mnsec, &mtime);
+        UA_TO_FILETIME(ua, atime, mtime);
     }
     if (!SetFileTime(hFile, NULL, &atime, &mtime)) {
         /* Avoid putting the file name into the error here,
@@ -3663,136 +3820,85 @@
     CloseHandle(hFile);
     return result;
 #else /* MS_WINDOWS */
-
     PyObject *opath;
     char *path;
-    time_t atime, mtime;
-    long ansec, mnsec;
     int res;
-    PyObject* arg = Py_None;
-
-    if (!PyArg_ParseTuple(args, "O&|O:utime",
-                          PyUnicode_FSConverter, &opath, &arg))
+
+    DECLARE_UA(ua, "utime");
+
+    ua.path_format = 'O';
+    ua.path = &opath;
+    ua.converter = PyUnicode_FSConverter;
+
+    if (!utime_read_time_arguments(&ua))
         return NULL;
     path = PyBytes_AsString(opath);
-    if (arg == Py_None) {
-        /* optional time values not given */
+    if (ua.now) {
         Py_BEGIN_ALLOW_THREADS
         res = utime(path, NULL);
         Py_END_ALLOW_THREADS
     }
-    else if (!PyTuple_Check(arg) || PyTuple_Size(arg) != 2) {
-        PyErr_SetString(PyExc_TypeError,
-                        "utime() arg 2 must be a tuple (atime, mtime)");
-        Py_DECREF(opath);
-        return NULL;
-    }
     else {
-        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 0),
-                                     &atime, &ansec) == -1) {
-            Py_DECREF(opath);
-            return NULL;
-        }
-        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 1),
-                                     &mtime, &mnsec) == -1) {
-            Py_DECREF(opath);
-            return NULL;
-        }
-
         Py_BEGIN_ALLOW_THREADS
-        {
 #ifdef HAVE_UTIMENSAT
-        struct timespec buf[2];
-        buf[0].tv_sec = atime;
-        buf[0].tv_nsec = ansec;
-        buf[1].tv_sec = mtime;
-        buf[1].tv_nsec = mnsec;
+        UA_TO_TIMESPEC(ua, buf);
         res = utimensat(AT_FDCWD, path, buf, 0);
 #elif defined(HAVE_UTIMES)
-        struct timeval buf[2];
-        buf[0].tv_sec = atime;
-        buf[0].tv_usec = ansec / 1000;
-        buf[1].tv_sec = mtime;
-        buf[1].tv_usec = mnsec / 1000;
+        UA_TO_TIMEVAL(ua, buf);
         res = utimes(path, buf);
 #elif defined(HAVE_UTIME_H)
         /* XXX should define struct utimbuf instead, above */
-        struct utimbuf buf;
-        buf.actime = atime;
-        buf.modtime = mtime;
+        UA_TO_UTIMBUF(ua, buf);
         res = utime(path, &buf);
 #else
-        time_t buf[2];
-        buf[0] = atime;
-        buf[1] = mtime;
+        UA_TO_TIME_T(ua, buf);
         res = utime(path, buf);
 #endif
-        }
         Py_END_ALLOW_THREADS
     }
+
     if (res < 0) {
         return posix_error_with_allocated_filename(opath);
     }
     Py_DECREF(opath);
-    Py_INCREF(Py_None);
-    return Py_None;
+    Py_RETURN_NONE;
 #undef UTIME_EXTRACT
 #endif /* MS_WINDOWS */
 }
 
 #ifdef HAVE_FUTIMES
 PyDoc_STRVAR(posix_futimes__doc__,
-"futimes(fd[, (atime, mtime)])\n\
+"futimes(fd[, times=(atime, mtime), *, ns=(atime_ns, mtime_ns)])\n\
 Set the access and modified time of the file specified by the file\n\
-descriptor fd to the given values. If no second argument is used, set the\n\
-access and modified times to the current time.");
-
-static PyObject *
-posix_futimes(PyObject *self, PyObject *args)
+descriptor fd.  See utime for the semantics of the times and ns parameters.");
+
+static PyObject *
+posix_futimes(PyObject *self, PyObject *args, PyObject *kwargs)
 {
     int res, fd;
-    PyObject* arg = Py_None;
-    time_t atime, mtime;
-    long ansec, mnsec;
-
-    if (!PyArg_ParseTuple(args, "i|O:futimes", &fd, &arg))
-        return NULL;
-
-    if (arg == Py_None) {
-        /* optional time values not given */
+
+    DECLARE_UA(ua, "futimes");
+
+    ua.path_format = 'i';
+    ua.path = (PyObject **)&fd;
+    ua.first_argument_name = "fd";
+
+    if (!utime_read_time_arguments(&ua))
+        return NULL;
+
+    if (ua.now) {
         Py_BEGIN_ALLOW_THREADS
         res = futimes(fd, NULL);
         Py_END_ALLOW_THREADS
     }
-    else if (!PyTuple_Check(arg) || PyTuple_Size(arg) != 2) {
-        PyErr_SetString(PyExc_TypeError,
-                "futimes() arg 2 must be a tuple (atime, mtime)");
-        return NULL;
-    }
     else {
-        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 0),
-                                     &atime, &ansec) == -1) {
-            return NULL;
-        }
-        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 1),
-                                     &mtime, &mnsec) == -1) {
-            return NULL;
-        }
         Py_BEGIN_ALLOW_THREADS
         {
 #ifdef HAVE_FUTIMENS
-        struct timespec buf[2];
-        buf[0].tv_sec = atime;
-        buf[0].tv_nsec = ansec;
-        buf[1].tv_sec = mtime;
-        buf[1].tv_nsec = mnsec;
+        UA_TO_TIMESPEC(ua, buf);
         res = futimens(fd, buf);
 #else
-        struct timeval buf[2];
-        buf[0].tv_sec = atime;
-        buf[0].tv_usec = ansec / 1000;
-        buf[1].tv_sec = mtime;
-        buf[1].tv_usec = mnsec / 1000;
+        UA_TO_TIMEVAL(ua, buf);
         res = futimes(fd, buf);
 #endif
         }
@@ -3806,61 +3912,40 @@
 
 #ifdef HAVE_LUTIMES
 PyDoc_STRVAR(posix_lutimes__doc__,
-"lutimes(path[, (atime, mtime)])\n\
+"lutimes(path[, times=(atime, mtime), *, ns=(atime_ns, mtime_ns)])\n\
 Like utime(), but if path is a symbolic link, it is not dereferenced.");
 
 static PyObject *
-posix_lutimes(PyObject *self, PyObject *args)
+posix_lutimes(PyObject *self, PyObject *args, PyObject *kwargs)
 {
     PyObject *opath;
-    PyObject *arg = Py_None;
     const char *path;
     int res;
-    time_t atime, mtime;
-    long ansec, mnsec;
-
-    if (!PyArg_ParseTuple(args, "O&|O:lutimes",
-            PyUnicode_FSConverter, &opath, &arg))
+
+    DECLARE_UA(ua, "lutimes");
+
+    ua.path_format = 'O';
+    ua.path = &opath;
+    ua.converter = PyUnicode_FSConverter;
+
+    if (!utime_read_time_arguments(&ua))
         return NULL;
     path = PyBytes_AsString(opath);
-    if (arg == Py_None) {
+
+    if (ua.now) {
         /* optional time values not given */
         Py_BEGIN_ALLOW_THREADS
         res = lutimes(path, NULL);
         Py_END_ALLOW_THREADS
     }
-    else if (!PyTuple_Check(arg) || PyTuple_Size(arg) != 2) {
-        PyErr_SetString(PyExc_TypeError,
-            "lutimes() arg 2 must be a tuple (atime, mtime)");
-        Py_DECREF(opath);
-        return NULL;
-    }
     else {
-        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 0),
-                                     &atime, &ansec) == -1) {
-            Py_DECREF(opath);
-            return NULL;
-        }
-        if (_PyTime_ObjectToTimespec(PyTuple_GET_ITEM(arg, 1),
-                                     &mtime, &mnsec) == -1) {
-            Py_DECREF(opath);
-            return NULL;
-        }
         Py_BEGIN_ALLOW_THREADS
         {
 #ifdef HAVE_UTIMENSAT
-        struct timespec buf[2];
-        buf[0].tv_sec = atime;
-        buf[0].tv_nsec = ansec;
-        buf[1].tv_sec = mtime;
-        buf[1].tv_nsec = mnsec;
+        UA_TO_TIMESPEC(ua, buf);
         res = utimensat(AT_FDCWD, path, buf, AT_SYMLINK_NOFOLLOW);
 #else
-        struct timeval buf[2];
-        buf[0].tv_sec = atime;
-        buf[0].tv_usec = ansec / 1000;
-        buf[1].tv_sec = mtime;
-        buf[1].tv_usec = mnsec / 1000;
+        UA_TO_TIMEVAL(ua, buf);
         res = lutimes(path, buf);
 #endif
         }
@@ -3873,62 +3958,6 @@
 }
 #endif
 
-#ifdef HAVE_FUTIMENS
-PyDoc_STRVAR(posix_futimens__doc__,
-"futimens(fd[, (atime_sec, atime_nsec), (mtime_sec, mtime_nsec)])\n\
-Updates the timestamps of a file specified by the file descriptor fd, with\n\
-nanosecond precision.\n\
-If no second argument is given, set atime and mtime to the current time.\n\
-If *_nsec is specified as UTIME_NOW, the timestamp is updated to the\n\
-current time.\n\
-If *_nsec is specified as UTIME_OMIT, the timestamp is not updated.");
-
-static PyObject *
-posix_futimens(PyObject *self, PyObject *args)
-{
-    int res, fd;
-    PyObject *atime = Py_None;
-    PyObject *mtime = Py_None;
-    struct timespec buf[2];
-
-    if (!PyArg_ParseTuple(args, "i|OO:futimens",
-            &fd, &atime, &mtime))
-        return NULL;
-    if (atime == Py_None && mtime == Py_None) {
-        /* optional time values not given */
-        Py_BEGIN_ALLOW_THREADS
-        res = futimens(fd, NULL);
-        Py_END_ALLOW_THREADS
-    }
-    else if (!PyTuple_Check(atime) || PyTuple_Size(atime) != 2) {
-        PyErr_SetString(PyExc_TypeError,
-            "futimens() arg 2 must be a tuple (atime_sec, atime_nsec)");
-        return NULL;
-    }
-    else if (!PyTuple_Check(mtime) || PyTuple_Size(mtime) != 2) {
-        PyErr_SetString(PyExc_TypeError,
-            "futimens() arg 3 must be a tuple (mtime_sec, mtime_nsec)");
-        return NULL;
-    }
-    else {
-        if (!PyArg_ParseTuple(atime, "ll:futimens",
-                &(buf[0].tv_sec), &(buf[0].tv_nsec))) {
-            return NULL;
-        }
-        if (!PyArg_ParseTuple(mtime, "ll:futimens",
-                &(buf[1].tv_sec), &(buf[1].tv_nsec))) {
-            return NULL;
-        }
-        Py_BEGIN_ALLOW_THREADS
-        res = futimens(fd, buf);
-        Py_END_ALLOW_THREADS
-    }
-    if (res < 0)
-        return posix_error();
-    Py_RETURN_NONE;
-}
-#endif
-
 /* Process operations */
 
 PyDoc_STRVAR(posix__exit__doc__,
@@ -10619,15 +10648,15 @@
 #endif /* HAVE_UNAME */
     {"unlink",          posix_unlink, METH_VARARGS, posix_unlink__doc__},
     {"remove",          posix_unlink, METH_VARARGS, posix_remove__doc__},
-    {"utime",           posix_utime, METH_VARARGS, posix_utime__doc__},
+    {"utime",           (PyCFunction)posix_utime,
+                        METH_VARARGS | METH_KEYWORDS, posix_utime__doc__},
 #ifdef HAVE_FUTIMES
-    {"futimes",         posix_futimes, METH_VARARGS, posix_futimes__doc__},
+    {"futimes",         (PyCFunction)posix_futimes,
+                        METH_VARARGS | METH_KEYWORDS, posix_futimes__doc__},
 #endif
 #ifdef HAVE_LUTIMES
-    {"lutimes",         posix_lutimes, METH_VARARGS, posix_lutimes__doc__},
-#endif
-#ifdef HAVE_FUTIMENS
-    {"futimens",        posix_futimens, METH_VARARGS, posix_futimens__doc__},
+    {"lutimes",         (PyCFunction)posix_lutimes,
+                        METH_VARARGS | METH_KEYWORDS, posix_lutimes__doc__},
 #endif
 #ifdef HAVE_TIMES
     {"times",           posix_times, METH_NOARGS, posix_times__doc__},
diff --git a/Python/pytime.c b/Python/pytime.c
--- a/Python/pytime.c
+++ b/Python/pytime.c
@@ -123,7 +123,7 @@
                     "timestamp out of range for platform time_t");
 }
 
-static time_t
+time_t
 _PyLong_AsTime_t(PyObject *obj)
 {
 #if defined(HAVE_LONG_LONG) && SIZEOF_TIME_T == SIZEOF_LONG_LONG

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list