From 76ad59b7e826691e0eb19f04cb647e07cdbde76a Mon Sep 17 00:00:00 2001 From: Larry Hastings Date: Thu, 3 May 2012 00:30:07 -0700 Subject: [PATCH] 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! --- Doc/library/os.rst | 52 +++-- Include/pytime.h | 4 + Lib/shutil.py | 2 +- Lib/test/test_os.py | 86 +++++++-- Modules/posixmodule.c | 437 ++++++++++++++++++++++-------------------- Python/pytime.c | 2 +- 6 files changed, 346 insertions(+), 237 deletions(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 461092dc71a..f417c34929f 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -934,13 +934,11 @@ as internal buffering of data. .. 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 @@ Files and Directories 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 @@ Files and Directories Availability: Unix, Windows. -.. function:: utime(path[, times]) +.. function:: utime(path[, times, *, ns=(atime_ns, mtime_ns)]) - 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* + 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 index 7442c6fbb78..dd4cc6922bd 100644 --- a/Include/pytime.h +++ b/Include/pytime.h @@ -62,6 +62,10 @@ PyAPI_FUNC(int) _PyTime_ObjectToTime_t( 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 index 0ac7a49c458..6df492425c9 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -154,7 +154,7 @@ def copystat(src, dst, symlinks=False): 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 index aa619a8dd7d..0c15f22e8d1 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -192,11 +192,11 @@ class StatAttributeTests(unittest.TestCase): 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 @@ class StatAttributeTests(unittest.TestCase): 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) - # Set to the current time in the new way - os.utime(support.TESTFN) + os.utime(filename, None) st2 = os.stat(support.TESTFN) - self.assertAlmostEqual(st1.st_mtime, st2.st_mtime, delta=10) + # Set to the current time in the new way + 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 @@ class StatAttributeTests(unittest.TestCase): 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 @@ class StatAttributeTests(unittest.TestCase): 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 index 92a62774585..09c430fd708 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3572,28 +3572,194 @@ posix_uname(PyObject *self, PyObject *noargs) #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, ×, &ns); + else + return_value = PyArg_ParseTupleAndKeywords(ua->args, ua->kwargs, + format, kwlist, ua->path, ×, &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."); +"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) +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; - FILETIME atime, mtime; PyObject *result = NULL; + FILETIME atime, mtime; - 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 @@ posix_utime(PyObject *self, PyObject *args) 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 @@ posix_utime(PyObject *self, PyObject *args) } } - if (arg == Py_None) { + if (ua.now) { SYSTEMTIME now; GetSystemTime(&now); if (!SystemTimeToFileTime(&now, &mtime) || @@ -3634,20 +3803,8 @@ posix_utime(PyObject *self, PyObject *args) 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 @@ done: 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."); +descriptor fd. See utime for the semantics of the times and ns parameters."); static PyObject * -posix_futimes(PyObject *self, PyObject *args) +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)) + 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 (arg == Py_None) { - /* optional time values not given */ + 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 @@ posix_futimes(PyObject *self, PyObject *args) #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 @@ posix_lutimes(PyObject *self, PyObject *args) } #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 @@ static PyMethodDef posix_methods[] = { #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 index 0ffe799b32a..eb5685b9877 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -123,7 +123,7 @@ error_time_t_overflow(void) "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