diff options
author | Larry Hastings <larry@hastings.org> | 2012-05-03 07:30:07 (GMT) |
---|---|---|
committer | Larry Hastings <larry@hastings.org> | 2012-05-03 07:30:07 (GMT) |
commit | 76ad59b7e826691e0eb19f04cb647e07cdbde76a (patch) | |
tree | 3c775b67065ab0424b23367462d324648add4810 /Modules/posixmodule.c | |
parent | 3a7f7977f1ad3e5afe79254eef5057c0288613db (diff) | |
download | cpython-76ad59b7e826691e0eb19f04cb647e07cdbde76a.zip cpython-76ad59b7e826691e0eb19f04cb647e07cdbde76a.tar.gz cpython-76ad59b7e826691e0eb19f04cb647e07cdbde76a.tar.bz2 |
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!
Diffstat (limited to 'Modules/posixmodule.c')
-rw-r--r-- | Modules/posixmodule.c | 437 |
1 files changed, 233 insertions, 204 deletions
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 92a6277..09c430f 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__}, |