diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1495,6 +1495,9 @@ {"st_atime", "time of last access"}, {"st_mtime", "time of last modification"}, {"st_ctime", "time of last change"}, + {"atime", "time of last access as Decimal"}, + {"mtime", "time of last modification as Decimal"}, + {"ctime", "time of last change as Decimal"}, #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE {"st_blksize", "blocksize for filesystem I/O"}, #endif @@ -1517,9 +1520,9 @@ }; #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE -#define ST_BLKSIZE_IDX 13 -#else -#define ST_BLKSIZE_IDX 12 +#define ST_BLKSIZE_IDX 16 +#else +#define ST_BLKSIZE_IDX 15 #endif #ifdef HAVE_STRUCT_STAT_ST_BLOCKS @@ -1647,6 +1650,42 @@ +#if SIZEOF_TIME_T > SIZEOF_LONG +#define PYLONG_FROM_TIME_T(x) PyLong_FromLongLong((PY_LONG_LONG)(x)) +#define PYLONG_AS_TIME_T PyLong_AsUnsignedLongLongMask +#define TIME_T_FMT "lld" +#else +#define PYLONG_FROM_TIME_T(x) PyLong_FromLong((long)(x)) +#define PYLONG_AS_TIME_T PyLong_AsLong +#define TIME_T_FMT "ld" +#endif + +/* + * Classic POSIX utime functions supported microseconds (1m/sec). + * Newer POSIX functions support nanoseconds (1 billion per sec). + * posixmodule now uses the new functions where possible. + * This improves accuracy in many situations, for example shutil.copy2(). + * + * The implementation isn't currently sophisticated enough to handle + * a platform where HAVE_UTIMENSAT is true but HAVE_FUTIMENS is false. + * Specifically, posix_futimes() would break. + * + * Supporting such a platform wouldn't be impossible; you'd need two + * extract_time() functions, or make its precision a parameter. + * Since such a platform seems unlikely we haven't bothered. + */ +#if defined(HAVE_UTIMENSAT) && !defined(HAVE_FUTIMENS) +#error You HAVE_UTIMENSAT but not HAVE_FUTIMENS... please see accompanying comment. +#endif + +/* + * Currently ctime/mtime/utime is computed to nine decimal places, + * aka billionths of a second precision. + */ +#define DECIMAL_TIME_WIDTH "9" +#define EXTRACT_TIME_PRECISION (1e9) + + /* If true, st_?time is float. */ static int _stat_float_times = 1; @@ -1671,25 +1710,78 @@ return Py_None; } +static PyObject * +decimal_class(void) +{ + PyObject *klass = NULL; + PyObject *module = PyImport_ImportModule("decimal"); + if (module) + { + klass = PyObject_GetAttrString(module, "Decimal"); + Py_DECREF(module); + } else { + /* it's okay if this fails */ + PyErr_Clear(); + } + return klass; +} + +static PyObject * +build_decimal(PyObject *dc, time_t seconds, unsigned long fraction) +{ + char buffer[64]; + PyObject *string; + PyObject *return_value = NULL; + char *trace = buffer; + + trace += sprintf(buffer, + "%" TIME_T_FMT ".%0" DECIMAL_TIME_WIDTH "ld", + seconds, fraction) - 1; + /* Keep Python Tidy - Remove Trailing Zeroes */ + while (*trace == '0') + *trace-- = 0; + if (*trace == '.') + *trace = 0; + + string = PyUnicode_FromString(buffer); + if (string) + { + PyObject *arguments = Py_BuildValue("(O)", string); + if (arguments) + { + return_value = PyObject_CallObject(dc, arguments); + Py_DECREF(arguments); + } + Py_DECREF(string); + } + + return return_value; +} + static void -fill_time(PyObject *v, int index, time_t sec, unsigned long nsec) -{ - PyObject *fval,*ival; -#if SIZEOF_TIME_T > SIZEOF_LONG - ival = PyLong_FromLongLong((PY_LONG_LONG)sec); -#else - ival = PyLong_FromLong((long)sec); -#endif +fill_time(PyObject *v, int index, time_t sec, unsigned long nsec, PyObject *dc) +{ + PyObject *fval, *ival, *dval; + ival = PYLONG_FROM_TIME_T(sec); if (!ival) return; + if (_stat_float_times) { fval = PyFloat_FromDouble(sec + 1e-9*nsec); } else { fval = ival; Py_INCREF(fval); } + + dval = (dc) ? build_decimal(dc, sec, nsec) : NULL; + if (!dval) { + dval = Py_None; + Py_INCREF(Py_None); + } + PyStructSequence_SET_ITEM(v, index, ival); PyStructSequence_SET_ITEM(v, index+3, fval); + PyStructSequence_SET_ITEM(v, index+6, dval); } /* pack a system stat C structure into the Python stat tuple @@ -1698,6 +1790,7 @@ _pystat_fromstructstat(STRUCT_STAT *st) { unsigned long ansec, mnsec, cnsec; + PyObject *dc; /* decimal constructor */ PyObject *v = PyStructSequence_New(&StatResultType); if (v == NULL) return NULL; @@ -1740,9 +1833,11 @@ #else ansec = mnsec = cnsec = 0; #endif - fill_time(v, 7, st->st_atime, ansec); - fill_time(v, 8, st->st_mtime, mnsec); - fill_time(v, 9, st->st_ctime, cnsec); + dc = decimal_class(); + fill_time(v, 7, st->st_atime, ansec, dc); + fill_time(v, 8, st->st_mtime, mnsec, dc); + fill_time(v, 9, st->st_ctime, cnsec, dc); + Py_XDECREF(dc); #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE PyStructSequence_SET_ITEM(v, ST_BLKSIZE_IDX, @@ -2969,7 +3064,7 @@ NULL); if(hFile == INVALID_HANDLE_VALUE) { - return win32_error_unicode("GetFinalPathNamyByHandle", path); + return win32_error_unicode("GetFinalPathNameByHandle", path); return PyErr_Format(PyExc_RuntimeError, "Could not get a handle to file."); } @@ -3425,43 +3520,82 @@ #endif /* HAVE_UNAME */ -/* - * Classic POSIX utime functions supported microseconds (1m/sec). - * Newer POSIX functions support nanoseconds (1 billion per sec). - * posixmodule now uses the new functions where possible. - * This improves accuracy in many situations, for example shutil.copy2(). - * - * The implementation isn't currently sophisticated enough to handle - * a platform where HAVE_UTIMENSAT is true but HAVE_FUTIMENS is false. - * Specifically, posix_futimes() would break. - * - * Supporting such a platform wouldn't be impossible; you'd need two - * extract_time() functions, or make its precision a parameter. - * Since such a platform seems unlikely we haven't bothered. - */ -#if defined(HAVE_UTIMENSAT) -#define EXTRACT_TIME_PRECISION (1e9) -#if !defined(HAVE_FUTIMENS) -#error You HAVE_UTIMENSAT but not HAVE_FUTIMENS... please see accompanying comment. -#endif -#else -#define EXTRACT_TIME_PRECISION (1e6) -#endif +static PyObject * +call_method(PyObject *o, const char *method_name, PyObject *argument) +{ + PyObject *method; + PyObject *arguments; + PyObject *result; + + if (argument) + { + arguments = Py_BuildValue("(O)", argument); + if (!arguments) + return NULL; + } else { + arguments = NULL; + } + + method = PyObject_GetAttrString(o, method_name); + if (method) + { + result = PyObject_CallObject(method, arguments); + Py_DECREF(method); + } + + Py_XDECREF(arguments); + return result; +} static int -extract_time(PyObject *t, time_t* sec, long* usec) +extract_time(PyObject *t, time_t* sec, long* usec, PyObject *dc) { time_t intval; + if (dc && PyObject_TypeCheck(t, (PyTypeObject *)dc)) + { + /* Decimal time */ + PyObject *seconds = NULL; + PyObject *multiplier = NULL; + PyObject *fraction = NULL; + PyObject *shifted = NULL; + PyObject *nseconds = NULL; + int status = -1; + + /* sec = int(t) */ + seconds = call_method(t, "__int__", NULL); + if (!seconds) + goto EXIT; + + /* nsec = int( (t - sec) * EXTRACT_TIME_PRECISION) */ + multiplier = PyLong_FromLong(EXTRACT_TIME_PRECISION); + fraction = call_method(t, "__sub__", seconds); + if (!(fraction && multiplier)) + goto EXIT; + shifted = call_method(fraction, "__mul__", multiplier); + if (!shifted) + goto EXIT; + nseconds = call_method(shifted, "__int__", NULL); + if (!nseconds) + goto EXIT; + + *sec = PYLONG_AS_TIME_T(seconds); + *usec = PyLong_AsLong(nseconds); + status = 0; +EXIT: + Py_XDECREF(nseconds); + Py_XDECREF(shifted); + Py_XDECREF(multiplier); + Py_XDECREF(fraction); + Py_XDECREF(seconds); + return status; + } + if (PyFloat_Check(t)) { double tval = PyFloat_AsDouble(t); PyObject *intobj = PyNumber_Long(t); if (!intobj) return -1; -#if SIZEOF_TIME_T > SIZEOF_LONG - intval = PyLong_AsUnsignedLongLongMask(intobj); -#else - intval = PyLong_AsLong(intobj); -#endif + intval = PYLONG_AS_TIME_T(intobj); Py_DECREF(intobj); if (intval == -1 && PyErr_Occurred()) return -1; @@ -3474,11 +3608,8 @@ *usec = 0; return 0; } -#if SIZEOF_TIME_T > SIZEOF_LONG - intval = PyLong_AsUnsignedLongLongMask(t); -#else - intval = PyLong_AsLong(t); -#endif + + intval = PYLONG_AS_TIME_T(t); if (intval == -1 && PyErr_Occurred()) return -1; *sec = intval; @@ -3506,6 +3637,7 @@ long ausec, musec; FILETIME atime, mtime; PyObject *result = NULL; + PyObject *dc = NULL; if (PyArg_ParseTuple(args, "UO|:utime", &obwpath, &arg)) { wpath = PyUnicode_AS_UNICODE(obwpath); @@ -3514,8 +3646,12 @@ NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); Py_END_ALLOW_THREADS - if (hFile == INVALID_HANDLE_VALUE) - return win32_error_unicode("utime", wpath); + if (hFile == INVALID_HANDLE_VALUE) { + result = win32_error_unicode("utime", wpath); + Py_DECREF(obwpath); + goto done; + } + Py_DECREF(obwpath); } else /* Drop the argument parsing error as narrow strings are also valid. */ @@ -3532,9 +3668,9 @@ FILE_FLAG_BACKUP_SEMANTICS, NULL); Py_END_ALLOW_THREADS if (hFile == INVALID_HANDLE_VALUE) { - win32_error("utime", apath); + result = win32_error("utime", apath); Py_DECREF(oapath); - return NULL; + goto done; } Py_DECREF(oapath); } @@ -3544,7 +3680,7 @@ GetSystemTime(&now); if (!SystemTimeToFileTime(&now, &mtime) || !SystemTimeToFileTime(&now, &atime)) { - win32_error("utime", NULL); + result = win32_error("utime", NULL); goto done; } } @@ -3554,40 +3690,43 @@ goto done; } else { - if (extract_time(PyTuple_GET_ITEM(arg, 0), - &atimesec, &ausec) == -1) + dc = decimal_class(); + if ((extract_time(PyTuple_GET_ITEM(arg, 0), + &atimesec, &ausec, dc) == -1) + || (extract_time(PyTuple_GET_ITEM(arg, 1), + &mtimesec, &musec, dc) == -1)) goto done; - time_t_to_FILE_TIME(atimesec, 1000*ausec, &atime); - if (extract_time(PyTuple_GET_ITEM(arg, 1), - &mtimesec, &musec) == -1) - goto done; - time_t_to_FILE_TIME(mtimesec, 1000*musec, &mtime); + time_t_to_FILE_TIME(atimesec, ausec, &atime); + time_t_to_FILE_TIME(mtimesec, musec, &mtime); } if (!SetFileTime(hFile, NULL, &atime, &mtime)) { /* Avoid putting the file name into the error here, as that may confuse the user into believing that something is wrong with the file, when it also could be the time stamp that gives a problem. */ - win32_error("utime", NULL); + result = win32_error("utime", NULL); goto done; } Py_INCREF(Py_None); result = Py_None; done: + Py_XDECREF(dc); CloseHandle(hFile); return result; #else /* MS_WINDOWS */ - PyObject *opath; char *path; time_t atime, mtime; long ausec, musec; int res; PyObject* arg; + PyObject *opath = NULL; + PyObject *dc = NULL; + PyObject *result = NULL; if (!PyArg_ParseTuple(args, "O&O:utime", PyUnicode_FSConverter, &opath, &arg)) - return NULL; + goto done; path = PyBytes_AsString(opath); if (arg == Py_None) { /* optional time values not given */ @@ -3598,19 +3737,15 @@ 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; + goto done; } else { - if (extract_time(PyTuple_GET_ITEM(arg, 0), - &atime, &ausec) == -1) { - Py_DECREF(opath); - return NULL; - } - if (extract_time(PyTuple_GET_ITEM(arg, 1), - &mtime, &musec) == -1) { - Py_DECREF(opath); - return NULL; + dc = decimal_class(); + if ((extract_time(PyTuple_GET_ITEM(arg, 0), + &atime, &ausec, dc) == -1) + || (extract_time(PyTuple_GET_ITEM(arg, 1), + &mtime, &musec, dc) == -1)) { + goto done; } Py_BEGIN_ALLOW_THREADS @@ -3645,12 +3780,16 @@ Py_END_ALLOW_THREADS } if (res < 0) { - return posix_error_with_allocated_filename(opath); - } - Py_DECREF(opath); - Py_INCREF(Py_None); - return Py_None; -#undef UTIME_EXTRACT + result = posix_error_with_allocated_filename(opath); + opath = NULL; /* posix_error_... frees it for us! */ + } else { + Py_INCREF(Py_None); + result = Py_None; + } +done: + Py_XDECREF(dc); + Py_XDECREF(opath); + return result; #endif /* MS_WINDOWS */ } @@ -3685,14 +3824,16 @@ return NULL; } else { - if (extract_time(PyTuple_GET_ITEM(arg, 0), - &atime, &ausec) == -1) { + PyObject *dc = decimal_class(); + int failed = ( + (extract_time(PyTuple_GET_ITEM(arg, 0), + &atime, &ausec, dc) == -1) + || (extract_time(PyTuple_GET_ITEM(arg, 1), + &mtime, &musec, dc) == -1) ); + Py_XDECREF(dc); + if (failed) return NULL; - } - if (extract_time(PyTuple_GET_ITEM(arg, 1), - &mtime, &musec) == -1) { - return NULL; - } + Py_BEGIN_ALLOW_THREADS { #ifdef HAVE_FUTIMENS @@ -3733,6 +3874,8 @@ int res; time_t atime, mtime; long ausec, musec; + PyObject *dc = NULL; + PyObject *result = NULL; if (!PyArg_ParseTuple(args, "O&O:lutimes", PyUnicode_FSConverter, &opath, &arg)) @@ -3747,19 +3890,15 @@ 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; + goto done; } else { - if (extract_time(PyTuple_GET_ITEM(arg, 0), - &atime, &ausec) == -1) { - Py_DECREF(opath); - return NULL; - } - if (extract_time(PyTuple_GET_ITEM(arg, 1), - &mtime, &musec) == -1) { - Py_DECREF(opath); - return NULL; + dc = decimal_class(); + if ((extract_time(PyTuple_GET_ITEM(arg, 0), + &atime, &ausec, dc) == -1) + || (extract_time(PyTuple_GET_ITEM(arg, 1), + &mtime, &musec, dc) == -1)) { + goto done; } Py_BEGIN_ALLOW_THREADS { @@ -3781,10 +3920,16 @@ } Py_END_ALLOW_THREADS } + if (res < 0) + result = posix_error(); + else { + Py_INCREF(Py_None); + result = Py_None; + } +done: + Py_XDECREF(dc); Py_DECREF(opath); - if (res < 0) - return posix_error(); - Py_RETURN_NONE; + return result; } #endif @@ -9628,6 +9773,8 @@ PyObject* arg; time_t atime, mtime; long ausec, musec; + PyObject *dc = NULL; + PyObject *result = NULL; if (!PyArg_ParseTuple(args, "iO&O:futimesat", &dirfd, PyUnicode_FSConverter, &opath, &arg)) @@ -9642,19 +9789,15 @@ else if (!PyTuple_Check(arg) || PyTuple_Size(arg) != 2) { PyErr_SetString(PyExc_TypeError, "futimesat() arg 3 must be a tuple (atime, mtime)"); - Py_DECREF(opath); - return NULL; + goto done; } else { - if (extract_time(PyTuple_GET_ITEM(arg, 0), - &atime, &ausec) == -1) { - Py_DECREF(opath); - return NULL; - } - if (extract_time(PyTuple_GET_ITEM(arg, 1), - &mtime, &musec) == -1) { - Py_DECREF(opath); - return NULL; + dc = decimal_class(); + if ((extract_time(PyTuple_GET_ITEM(arg, 0), + &atime, &ausec, dc) == -1) + || (extract_time(PyTuple_GET_ITEM(arg, 1), + &mtime, &musec, dc) == -1)) { + goto done; } Py_BEGIN_ALLOW_THREADS @@ -9677,11 +9820,16 @@ } Py_END_ALLOW_THREADS } + if (res < 0) { + result = posix_error(); + } else { + result = Py_None; + Py_INCREF(Py_None); + } +done: + Py_XDECREF(dc); Py_DECREF(opath); - if (res < 0) { - return posix_error(); - } - Py_RETURN_NONE; + return result; } #endif