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!
This commit is contained in:
Larry Hastings 2012-05-03 00:30:07 -07:00
parent 3a7f7977f1
commit 76ad59b7e8
6 changed files with 346 additions and 237 deletions

View File

@ -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)

View File

@ -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). */

View File

@ -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:

View File

@ -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))

View File

@ -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, &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.");
"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__},

View File

@ -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