mirror of https://github.com/python/cpython
gh-99726: Improves correctness of stat results for Windows, and uses faster API when available (GH-102149)
This deprecates `st_ctime` fields on Windows, with the intent to change them to contain the correct value in 3.14. For now, they should keep returning the creation time as they always have.
This commit is contained in:
parent
e108af6eca
commit
0f175766e2
|
@ -2858,6 +2858,12 @@ features:
|
|||
Added support for the :class:`~os.PathLike` interface. Added support
|
||||
for :class:`bytes` paths on Windows.
|
||||
|
||||
.. versionchanged:: 3.12
|
||||
The ``st_ctime`` attribute of a stat result is deprecated on Windows.
|
||||
The file creation time is properly available as ``st_birthtime``, and
|
||||
in the future ``st_ctime`` may be changed to return zero or the
|
||||
metadata change time, if available.
|
||||
|
||||
|
||||
.. function:: stat(path, *, dir_fd=None, follow_symlinks=True)
|
||||
|
||||
|
@ -2973,10 +2979,12 @@ features:
|
|||
|
||||
.. attribute:: st_ctime
|
||||
|
||||
Platform dependent:
|
||||
Time of most recent metadata change expressed in seconds.
|
||||
|
||||
* the time of most recent metadata change on Unix,
|
||||
* the time of creation on Windows, expressed in seconds.
|
||||
.. versionchanged:: 3.12
|
||||
``st_ctime`` is deprecated on Windows. Use ``st_birthtime`` for
|
||||
the file creation time. In the future, ``st_ctime`` will contain
|
||||
the time of the most recent metadata change, as for other platforms.
|
||||
|
||||
.. attribute:: st_atime_ns
|
||||
|
||||
|
@ -2989,29 +2997,48 @@ features:
|
|||
|
||||
.. attribute:: st_ctime_ns
|
||||
|
||||
Platform dependent:
|
||||
|
||||
* the time of most recent metadata change on Unix,
|
||||
* the time of creation on Windows, expressed in nanoseconds as an
|
||||
Time of most recent metadata change expressed in nanoseconds as an
|
||||
integer.
|
||||
|
||||
.. versionchanged:: 3.12
|
||||
``st_ctime_ns`` is deprecated on Windows. Use ``st_birthtime_ns``
|
||||
for the file creation time. In the future, ``st_ctime`` will contain
|
||||
the time of the most recent metadata change, as for other platforms.
|
||||
|
||||
.. attribute:: st_birthtime
|
||||
|
||||
Time of file creation expressed in seconds. This attribute is not
|
||||
always available, and may raise :exc:`AttributeError`.
|
||||
|
||||
.. versionchanged:: 3.12
|
||||
``st_birthtime`` is now available on Windows.
|
||||
|
||||
.. attribute:: st_birthtime_ns
|
||||
|
||||
Time of file creation expressed in nanoseconds as an integer.
|
||||
This attribute is not always available, and may raise
|
||||
:exc:`AttributeError`.
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
.. note::
|
||||
|
||||
The exact meaning and resolution of the :attr:`st_atime`,
|
||||
:attr:`st_mtime`, and :attr:`st_ctime` attributes depend on the operating
|
||||
system and the file system. For example, on Windows systems using the FAT
|
||||
or FAT32 file systems, :attr:`st_mtime` has 2-second resolution, and
|
||||
:attr:`st_atime` has only 1-day resolution. See your operating system
|
||||
documentation for details.
|
||||
:attr:`st_mtime`, :attr:`st_ctime` and :attr:`st_birthtime` attributes
|
||||
depend on the operating system and the file system. For example, on
|
||||
Windows systems using the FAT32 file systems, :attr:`st_mtime` has
|
||||
2-second resolution, and :attr:`st_atime` has only 1-day resolution.
|
||||
See your operating system documentation for details.
|
||||
|
||||
Similarly, although :attr:`st_atime_ns`, :attr:`st_mtime_ns`,
|
||||
and :attr:`st_ctime_ns` are always expressed in nanoseconds, many
|
||||
systems do not provide nanosecond precision. On systems that do
|
||||
provide nanosecond precision, the floating-point object used to
|
||||
store :attr:`st_atime`, :attr:`st_mtime`, and :attr:`st_ctime`
|
||||
cannot preserve all of it, and as such will be slightly inexact.
|
||||
If you need the exact timestamps you should always use
|
||||
:attr:`st_atime_ns`, :attr:`st_mtime_ns`, and :attr:`st_ctime_ns`.
|
||||
:attr:`st_ctime_ns` and :attr:`st_birthtime_ns` are always expressed in
|
||||
nanoseconds, many systems do not provide nanosecond precision. On
|
||||
systems that do provide nanosecond precision, the floating-point object
|
||||
used to store :attr:`st_atime`, :attr:`st_mtime`, :attr:`st_ctime` and
|
||||
:attr:`st_birthtime` cannot preserve all of it, and as such will be
|
||||
slightly inexact. If you need the exact timestamps you should always use
|
||||
:attr:`st_atime_ns`, :attr:`st_mtime_ns`, :attr:`st_ctime_ns` and
|
||||
:attr:`st_birthtime_ns`.
|
||||
|
||||
On some Unix systems (such as Linux), the following attributes may also be
|
||||
available:
|
||||
|
@ -3041,10 +3068,6 @@ features:
|
|||
|
||||
File generation number.
|
||||
|
||||
.. attribute:: st_birthtime
|
||||
|
||||
Time of file creation.
|
||||
|
||||
On Solaris and derivatives, the following attributes may also be
|
||||
available:
|
||||
|
||||
|
@ -3117,6 +3140,25 @@ features:
|
|||
files as :const:`S_IFCHR`, :const:`S_IFIFO` or :const:`S_IFBLK`
|
||||
as appropriate.
|
||||
|
||||
.. versionchanged:: 3.12
|
||||
On Windows, :attr:`st_ctime` is deprecated. Eventually, it will
|
||||
contain the last metadata change time, for consistency with other
|
||||
platforms, but for now still contains creation time.
|
||||
Use :attr:`st_birthtime` for the creation time.
|
||||
|
||||
.. versionchanged:: 3.12
|
||||
On Windows, :attr:`st_ino` may now be up to 128 bits, depending
|
||||
on the file system. Previously it would not be above 64 bits, and
|
||||
larger file identifiers would be arbitrarily packed.
|
||||
|
||||
.. versionchanged:: 3.12
|
||||
On Windows, :attr:`st_rdev` no longer returns a value. Previously
|
||||
it would contain the same as :attr:`st_dev`, which was incorrect.
|
||||
|
||||
.. versionadded:: 3.12
|
||||
Added the :attr:`st_birthtime` member on Windows.
|
||||
|
||||
|
||||
.. function:: statvfs(path)
|
||||
|
||||
Perform a :c:func:`statvfs` system call on the given path. The return value is
|
||||
|
|
|
@ -306,6 +306,16 @@ os
|
|||
functions on Windows for enumerating drives, volumes and mount points.
|
||||
(Contributed by Steve Dower in :gh:`102519`.)
|
||||
|
||||
* :func:`os.stat` and :func:`os.lstat` are now more accurate on Windows.
|
||||
The ``st_birthtime`` field will now be filled with the creation time
|
||||
of the file, and ``st_ctime`` is deprecated but still contains the
|
||||
creation time (but in the future will return the last metadata change,
|
||||
for consistency with other platforms). ``st_dev`` may be up to 64 bits
|
||||
and ``st_ino`` up to 128 bits depending on your file system, and
|
||||
``st_rdev`` is always set to zero rather than incorrect values.
|
||||
Both functions may be significantly faster on newer releases of
|
||||
Windows. (Contributed by Steve Dower in :gh:`99726`.)
|
||||
|
||||
os.path
|
||||
-------
|
||||
|
||||
|
@ -469,6 +479,12 @@ Deprecated
|
|||
warning at compile time. This field will be removed in Python 3.14.
|
||||
(Contributed by Ramvikrams and Kumar Aditya in :gh:`101193`. PEP by Ken Jin.)
|
||||
|
||||
* The ``st_ctime`` fields return by :func:`os.stat` and :func:`os.lstat` on
|
||||
Windows are deprecated. In a future release, they will contain the last
|
||||
metadata change time, consistent with other platforms. For now, they still
|
||||
contain the creation time, which is also available in the new ``st_birthtime``
|
||||
field. (Contributed by Steve Dower in :gh:`99726`.)
|
||||
|
||||
Pending Removal in Python 3.13
|
||||
------------------------------
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ PyAPI_FUNC(PyObject *) _Py_device_encoding(int);
|
|||
|
||||
#ifdef MS_WINDOWS
|
||||
struct _Py_stat_struct {
|
||||
unsigned long st_dev;
|
||||
uint64_t st_dev;
|
||||
uint64_t st_ino;
|
||||
unsigned short st_mode;
|
||||
int st_nlink;
|
||||
|
@ -80,8 +80,11 @@ struct _Py_stat_struct {
|
|||
int st_mtime_nsec;
|
||||
time_t st_ctime;
|
||||
int st_ctime_nsec;
|
||||
time_t st_birthtime;
|
||||
int st_birthtime_nsec;
|
||||
unsigned long st_file_attributes;
|
||||
unsigned long st_reparse_tag;
|
||||
uint64_t st_ino_high;
|
||||
};
|
||||
#else
|
||||
# define _Py_stat_struct stat
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
#ifndef Py_INTERNAL_FILEUTILS_WINDOWS_H
|
||||
#define Py_INTERNAL_FILEUTILS_WINDOWS_H
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef Py_BUILD_CORE
|
||||
# error "Py_BUILD_CORE must be defined to include this header"
|
||||
#endif
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
|
||||
#if !defined(NTDDI_WIN10_NI) || !(NTDDI_VERSION >= NTDDI_WIN10_NI)
|
||||
typedef struct _FILE_STAT_BASIC_INFORMATION {
|
||||
LARGE_INTEGER FileId;
|
||||
LARGE_INTEGER CreationTime;
|
||||
LARGE_INTEGER LastAccessTime;
|
||||
LARGE_INTEGER LastWriteTime;
|
||||
LARGE_INTEGER ChangeTime;
|
||||
LARGE_INTEGER AllocationSize;
|
||||
LARGE_INTEGER EndOfFile;
|
||||
ULONG FileAttributes;
|
||||
ULONG ReparseTag;
|
||||
ULONG NumberOfLinks;
|
||||
ULONG DeviceType;
|
||||
ULONG DeviceCharacteristics;
|
||||
ULONG Reserved;
|
||||
FILE_ID_128 FileId128;
|
||||
LARGE_INTEGER VolumeSerialNumber;
|
||||
} FILE_STAT_BASIC_INFORMATION;
|
||||
|
||||
typedef enum _FILE_INFO_BY_NAME_CLASS {
|
||||
FileStatByNameInfo,
|
||||
FileStatLxByNameInfo,
|
||||
FileCaseSensitiveByNameInfo,
|
||||
FileStatBasicByNameInfo,
|
||||
MaximumFileInfoByNameClass
|
||||
} FILE_INFO_BY_NAME_CLASS;
|
||||
#endif
|
||||
|
||||
typedef BOOL (WINAPI *PGetFileInformationByName)(
|
||||
PCWSTR FileName,
|
||||
FILE_INFO_BY_NAME_CLASS FileInformationClass,
|
||||
PVOID FileInfoBuffer,
|
||||
ULONG FileInfoBufferSize
|
||||
);
|
||||
|
||||
static inline BOOL _Py_GetFileInformationByName(
|
||||
PCWSTR FileName,
|
||||
FILE_INFO_BY_NAME_CLASS FileInformationClass,
|
||||
PVOID FileInfoBuffer,
|
||||
ULONG FileInfoBufferSize
|
||||
) {
|
||||
static PGetFileInformationByName GetFileInformationByName = NULL;
|
||||
static int GetFileInformationByName_init = -1;
|
||||
|
||||
if (GetFileInformationByName_init < 0) {
|
||||
HMODULE hMod = LoadLibraryW(L"api-ms-win-core-file-l2-1-4");
|
||||
GetFileInformationByName_init = 0;
|
||||
if (hMod) {
|
||||
GetFileInformationByName = (PGetFileInformationByName)GetProcAddress(
|
||||
hMod, "GetFileInformationByName");
|
||||
if (GetFileInformationByName) {
|
||||
GetFileInformationByName_init = 1;
|
||||
} else {
|
||||
FreeLibrary(hMod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GetFileInformationByName_init <= 0) {
|
||||
SetLastError(ERROR_NOT_SUPPORTED);
|
||||
return FALSE;
|
||||
}
|
||||
return GetFileInformationByName(FileName, FileInformationClass, FileInfoBuffer, FileInfoBufferSize);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -556,6 +556,15 @@ class StatAttributeTests(unittest.TestCase):
|
|||
nanosecondy = getattr(result, name + "_ns") // 10000
|
||||
self.assertAlmostEqual(floaty, nanosecondy, delta=2)
|
||||
|
||||
# Ensure both birthtime and birthtime_ns roughly agree, if present
|
||||
try:
|
||||
floaty = int(result.st_birthtime * 100000)
|
||||
nanosecondy = result.st_birthtime_ns // 10000
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.assertAlmostEqual(floaty, nanosecondy, delta=2)
|
||||
|
||||
try:
|
||||
result[200]
|
||||
self.fail("No exception raised")
|
||||
|
@ -4234,7 +4243,8 @@ class TestScandir(unittest.TestCase):
|
|||
for attr in dir(stat1):
|
||||
if not attr.startswith("st_"):
|
||||
continue
|
||||
if attr in ("st_dev", "st_ino", "st_nlink"):
|
||||
if attr in ("st_dev", "st_ino", "st_nlink", "st_ctime",
|
||||
"st_ctime_ns"):
|
||||
continue
|
||||
self.assertEqual(getattr(stat1, attr),
|
||||
getattr(stat2, attr),
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Improves correctness of stat results for Windows, and uses faster API when
|
||||
available
|
|
@ -41,6 +41,7 @@
|
|||
#ifndef MS_WINDOWS
|
||||
# include "posixmodule.h"
|
||||
#else
|
||||
# include "pycore_fileutils_windows.h"
|
||||
# include "winreparse.h"
|
||||
#endif
|
||||
|
||||
|
@ -668,8 +669,11 @@ PyOS_AfterFork(void)
|
|||
#ifdef MS_WINDOWS
|
||||
/* defined in fileutils.c */
|
||||
void _Py_time_t_to_FILE_TIME(time_t, int, FILETIME *);
|
||||
void _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *,
|
||||
ULONG, struct _Py_stat_struct *);
|
||||
void _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *, ULONG,
|
||||
FILE_BASIC_INFO *, FILE_ID_INFO *,
|
||||
struct _Py_stat_struct *);
|
||||
void _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *,
|
||||
struct _Py_stat_struct *);
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -1819,12 +1823,39 @@ attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *re
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
update_st_mode_from_path(const wchar_t *path, DWORD attr,
|
||||
struct _Py_stat_struct *result)
|
||||
{
|
||||
if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
/* Fix the file execute permissions. This hack sets S_IEXEC if
|
||||
the filename has an extension that is commonly used by files
|
||||
that CreateProcessW can execute. A real implementation calls
|
||||
GetSecurityInfo, OpenThreadToken/OpenProcessToken, and
|
||||
AccessCheck to check for generic read, write, and execute
|
||||
access. */
|
||||
const wchar_t *fileExtension = wcsrchr(path, '.');
|
||||
if (fileExtension) {
|
||||
if (_wcsicmp(fileExtension, L".exe") == 0 ||
|
||||
_wcsicmp(fileExtension, L".bat") == 0 ||
|
||||
_wcsicmp(fileExtension, L".cmd") == 0 ||
|
||||
_wcsicmp(fileExtension, L".com") == 0) {
|
||||
result->st_mode |= 0111;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
|
||||
win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result,
|
||||
BOOL traverse)
|
||||
{
|
||||
HANDLE hFile;
|
||||
BY_HANDLE_FILE_INFORMATION fileInfo;
|
||||
FILE_BASIC_INFO basicInfo;
|
||||
FILE_ID_INFO idInfo;
|
||||
FILE_ATTRIBUTE_TAG_INFO tagInfo = { 0 };
|
||||
DWORD fileType, error;
|
||||
BOOL isUnhandledTag = FALSE;
|
||||
|
@ -1954,12 +1985,16 @@ win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
|
|||
for an unhandled tag. */
|
||||
} else if (!isUnhandledTag) {
|
||||
CloseHandle(hFile);
|
||||
return win32_xstat_impl(path, result, TRUE);
|
||||
return win32_xstat_slow_impl(path, result, TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!GetFileInformationByHandle(hFile, &fileInfo)) {
|
||||
if (!GetFileInformationByHandle(hFile, &fileInfo) ||
|
||||
!GetFileInformationByHandleEx(hFile, FileBasicInfo,
|
||||
&basicInfo, sizeof(basicInfo)) ||
|
||||
!GetFileInformationByHandleEx(hFile, FileIdInfo,
|
||||
&idInfo, sizeof(idInfo))) {
|
||||
switch (GetLastError()) {
|
||||
case ERROR_INVALID_PARAMETER:
|
||||
case ERROR_INVALID_FUNCTION:
|
||||
|
@ -1975,25 +2010,8 @@ win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
|
|||
}
|
||||
}
|
||||
|
||||
_Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, result);
|
||||
|
||||
if (!(fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
/* Fix the file execute permissions. This hack sets S_IEXEC if
|
||||
the filename has an extension that is commonly used by files
|
||||
that CreateProcessW can execute. A real implementation calls
|
||||
GetSecurityInfo, OpenThreadToken/OpenProcessToken, and
|
||||
AccessCheck to check for generic read, write, and execute
|
||||
access. */
|
||||
const wchar_t *fileExtension = wcsrchr(path, '.');
|
||||
if (fileExtension) {
|
||||
if (_wcsicmp(fileExtension, L".exe") == 0 ||
|
||||
_wcsicmp(fileExtension, L".bat") == 0 ||
|
||||
_wcsicmp(fileExtension, L".cmd") == 0 ||
|
||||
_wcsicmp(fileExtension, L".com") == 0) {
|
||||
result->st_mode |= 0111;
|
||||
}
|
||||
}
|
||||
}
|
||||
_Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, &basicInfo, &idInfo, result);
|
||||
update_st_mode_from_path(path, fileInfo.dwFileAttributes, result);
|
||||
|
||||
cleanup:
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
|
@ -2010,6 +2028,39 @@ cleanup:
|
|||
return retval;
|
||||
}
|
||||
|
||||
static int
|
||||
win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
|
||||
BOOL traverse)
|
||||
{
|
||||
FILE_STAT_BASIC_INFORMATION statInfo;
|
||||
if (_Py_GetFileInformationByName(path, FileStatBasicByNameInfo,
|
||||
&statInfo, sizeof(statInfo))) {
|
||||
if (// Cannot use fast path for reparse points ...
|
||||
!(statInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
// ... unless it's a name surrogate (symlink) and we're not following
|
||||
|| (!traverse && IsReparseTagNameSurrogate(statInfo.ReparseTag))
|
||||
) {
|
||||
_Py_stat_basic_info_to_stat(&statInfo, result);
|
||||
update_st_mode_from_path(path, statInfo.FileAttributes, result);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
switch(GetLastError()) {
|
||||
case ERROR_FILE_NOT_FOUND:
|
||||
case ERROR_PATH_NOT_FOUND:
|
||||
case ERROR_NOT_READY:
|
||||
case ERROR_BAD_NET_NAME:
|
||||
/* These errors aren't worth retrying with the slow path */
|
||||
return -1;
|
||||
case ERROR_NOT_SUPPORTED:
|
||||
/* indicates the API couldn't be loaded */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return win32_xstat_slow_impl(path, result, traverse);
|
||||
}
|
||||
|
||||
static int
|
||||
win32_xstat(const wchar_t *path, struct _Py_stat_struct *result, BOOL traverse)
|
||||
{
|
||||
|
@ -2017,6 +2068,10 @@ win32_xstat(const wchar_t *path, struct _Py_stat_struct *result, BOOL traverse)
|
|||
setting it to a POSIX error. Callers should use GetLastError. */
|
||||
int code = win32_xstat_impl(path, result, traverse);
|
||||
errno = 0;
|
||||
|
||||
/* ctime is only deprecated from 3.12, so we copy birthtime across */
|
||||
result->st_ctime = result->st_birthtime;
|
||||
result->st_ctime_nsec = result->st_birthtime_nsec;
|
||||
return code;
|
||||
}
|
||||
/* About the following functions: win32_lstat_w, win32_stat, win32_stat_w
|
||||
|
@ -2087,9 +2142,12 @@ static PyStructSequence_Field stat_result_fields[] = {
|
|||
#ifdef HAVE_STRUCT_STAT_ST_GEN
|
||||
{"st_gen", "generation number"},
|
||||
#endif
|
||||
#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME
|
||||
#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(MS_WINDOWS)
|
||||
{"st_birthtime", "time of creation"},
|
||||
#endif
|
||||
#ifdef MS_WINDOWS
|
||||
{"st_birthtime_ns", "time of creation in nanoseconds"},
|
||||
#endif
|
||||
#ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES
|
||||
{"st_file_attributes", "Windows file attribute bits"},
|
||||
#endif
|
||||
|
@ -2132,16 +2190,22 @@ static PyStructSequence_Field stat_result_fields[] = {
|
|||
#define ST_GEN_IDX ST_FLAGS_IDX
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME
|
||||
#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(MS_WINDOWS)
|
||||
#define ST_BIRTHTIME_IDX (ST_GEN_IDX+1)
|
||||
#else
|
||||
#define ST_BIRTHTIME_IDX ST_GEN_IDX
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES
|
||||
#define ST_FILE_ATTRIBUTES_IDX (ST_BIRTHTIME_IDX+1)
|
||||
#ifdef MS_WINDOWS
|
||||
#define ST_BIRTHTIME_NS_IDX (ST_BIRTHTIME_IDX+1)
|
||||
#else
|
||||
#define ST_FILE_ATTRIBUTES_IDX ST_BIRTHTIME_IDX
|
||||
#define ST_BIRTHTIME_NS_IDX ST_BIRTHTIME_IDX
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES) || defined(MS_WINDOWS)
|
||||
#define ST_FILE_ATTRIBUTES_IDX (ST_BIRTHTIME_NS_IDX+1)
|
||||
#else
|
||||
#define ST_FILE_ATTRIBUTES_IDX ST_BIRTHTIME_NS_IDX
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STRUCT_STAT_ST_FSTYPE
|
||||
|
@ -2310,7 +2374,7 @@ _posix_free(void *module)
|
|||
}
|
||||
|
||||
static void
|
||||
fill_time(PyObject *module, PyObject *v, int index, time_t sec, unsigned long nsec)
|
||||
fill_time(PyObject *module, PyObject *v, int s_index, int f_index, int ns_index, time_t sec, unsigned long nsec)
|
||||
{
|
||||
PyObject *s = _PyLong_FromTime_t(sec);
|
||||
PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec);
|
||||
|
@ -2334,12 +2398,18 @@ fill_time(PyObject *module, PyObject *v, int index, time_t sec, unsigned long ns
|
|||
goto exit;
|
||||
}
|
||||
|
||||
PyStructSequence_SET_ITEM(v, index, s);
|
||||
PyStructSequence_SET_ITEM(v, index+3, float_s);
|
||||
PyStructSequence_SET_ITEM(v, index+6, ns_total);
|
||||
if (s_index >= 0) {
|
||||
PyStructSequence_SET_ITEM(v, s_index, s);
|
||||
s = NULL;
|
||||
}
|
||||
if (f_index >= 0) {
|
||||
PyStructSequence_SET_ITEM(v, f_index, float_s);
|
||||
float_s = NULL;
|
||||
}
|
||||
if (ns_index >= 0) {
|
||||
PyStructSequence_SET_ITEM(v, ns_index, ns_total);
|
||||
ns_total = NULL;
|
||||
}
|
||||
exit:
|
||||
Py_XDECREF(s);
|
||||
Py_XDECREF(ns_fractional);
|
||||
|
@ -2348,6 +2418,33 @@ exit:
|
|||
Py_XDECREF(float_s);
|
||||
}
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
static PyObject*
|
||||
_pystat_l128_from_l64_l64(uint64_t low, uint64_t high)
|
||||
{
|
||||
PyObject *o_low = PyLong_FromUnsignedLongLong(low);
|
||||
if (!o_low || !high) {
|
||||
return o_low;
|
||||
}
|
||||
PyObject *o_high = PyLong_FromUnsignedLongLong(high);
|
||||
PyObject *l64 = o_high ? PyLong_FromLong(64) : NULL;
|
||||
if (!l64) {
|
||||
Py_XDECREF(o_high);
|
||||
Py_DECREF(o_low);
|
||||
return NULL;
|
||||
}
|
||||
Py_SETREF(o_high, PyNumber_Lshift(o_high, l64));
|
||||
Py_DECREF(l64);
|
||||
if (!o_high) {
|
||||
Py_DECREF(o_low);
|
||||
return NULL;
|
||||
}
|
||||
Py_SETREF(o_low, PyNumber_Add(o_low, o_high));
|
||||
Py_DECREF(o_high);
|
||||
return o_low;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* pack a system stat C structure into the Python stat tuple
|
||||
(used by posix_stat() and posix_fstat()) */
|
||||
static PyObject*
|
||||
|
@ -2360,12 +2457,13 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)
|
|||
return NULL;
|
||||
|
||||
PyStructSequence_SET_ITEM(v, 0, PyLong_FromLong((long)st->st_mode));
|
||||
#ifdef MS_WINDOWS
|
||||
PyStructSequence_SET_ITEM(v, 1, _pystat_l128_from_l64_l64(st->st_ino, st->st_ino_high));
|
||||
PyStructSequence_SET_ITEM(v, 2, PyLong_FromUnsignedLongLong(st->st_dev));
|
||||
#else
|
||||
static_assert(sizeof(unsigned long long) >= sizeof(st->st_ino),
|
||||
"stat.st_ino is larger than unsigned long long");
|
||||
PyStructSequence_SET_ITEM(v, 1, PyLong_FromUnsignedLongLong(st->st_ino));
|
||||
#ifdef MS_WINDOWS
|
||||
PyStructSequence_SET_ITEM(v, 2, PyLong_FromUnsignedLong(st->st_dev));
|
||||
#else
|
||||
PyStructSequence_SET_ITEM(v, 2, _PyLong_FromDev(st->st_dev));
|
||||
#endif
|
||||
PyStructSequence_SET_ITEM(v, 3, PyLong_FromLong((long)st->st_nlink));
|
||||
|
@ -2395,9 +2493,9 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)
|
|||
#else
|
||||
ansec = mnsec = cnsec = 0;
|
||||
#endif
|
||||
fill_time(module, v, 7, st->st_atime, ansec);
|
||||
fill_time(module, v, 8, st->st_mtime, mnsec);
|
||||
fill_time(module, v, 9, st->st_ctime, cnsec);
|
||||
fill_time(module, v, 7, 10, 13, st->st_atime, ansec);
|
||||
fill_time(module, v, 8, 11, 14, st->st_mtime, mnsec);
|
||||
fill_time(module, v, 9, 12, 15, st->st_ctime, cnsec);
|
||||
|
||||
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
|
||||
PyStructSequence_SET_ITEM(v, ST_BLKSIZE_IDX,
|
||||
|
@ -2415,7 +2513,7 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)
|
|||
PyStructSequence_SET_ITEM(v, ST_GEN_IDX,
|
||||
PyLong_FromLong((long)st->st_gen));
|
||||
#endif
|
||||
#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME
|
||||
#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME)
|
||||
{
|
||||
PyObject *val;
|
||||
unsigned long bsec,bnsec;
|
||||
|
@ -2429,6 +2527,9 @@ _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st)
|
|||
PyStructSequence_SET_ITEM(v, ST_BIRTHTIME_IDX,
|
||||
val);
|
||||
}
|
||||
#elif defined(MS_WINDOWS)
|
||||
fill_time(module, v, -1, ST_BIRTHTIME_IDX, ST_BIRTHTIME_NS_IDX,
|
||||
st->st_birthtime, st->st_birthtime_nsec);
|
||||
#endif
|
||||
#ifdef HAVE_STRUCT_STAT_ST_FLAGS
|
||||
PyStructSequence_SET_ITEM(v, ST_FLAGS_IDX,
|
||||
|
@ -14639,7 +14740,7 @@ DirEntry_from_find_data(PyObject *module, path_t *path, WIN32_FIND_DATAW *dataW)
|
|||
}
|
||||
|
||||
find_data_to_file_info(dataW, &file_info, &reparse_tag);
|
||||
_Py_attribute_data_to_stat(&file_info, reparse_tag, &entry->win32_lstat);
|
||||
_Py_attribute_data_to_stat(&file_info, reparse_tag, NULL, NULL, &entry->win32_lstat);
|
||||
|
||||
return (PyObject *)entry;
|
||||
|
||||
|
|
|
@ -216,6 +216,7 @@
|
|||
<ClInclude Include="..\Include\internal\pycore_exceptions.h" />
|
||||
<ClInclude Include="..\Include\internal\pycore_faulthandler.h" />
|
||||
<ClInclude Include="..\Include\internal\pycore_fileutils.h" />
|
||||
<ClInclude Include="..\Include\internal\pycore_fileutils_windows.h" />
|
||||
<ClInclude Include="..\Include\internal\pycore_floatobject.h" />
|
||||
<ClInclude Include="..\Include\internal\pycore_format.h" />
|
||||
<ClInclude Include="..\Include\internal\pycore_frame.h" />
|
||||
|
|
|
@ -555,6 +555,9 @@
|
|||
<ClInclude Include="..\Include\internal\pycore_fileutils.h">
|
||||
<Filter>Include\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\internal\pycore_fileutils_windows.h">
|
||||
<Filter>Include\internal</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\Include\internal\pycore_floatobject.h">
|
||||
<Filter>Include\internal</Filter>
|
||||
</ClInclude>
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#ifdef MS_WINDOWS
|
||||
# include <malloc.h>
|
||||
# include <windows.h>
|
||||
# include <winioctl.h> // FILE_DEVICE_* constants
|
||||
# include "pycore_fileutils_windows.h" // FILE_STAT_BASIC_INFORMATION
|
||||
# if defined(MS_WINDOWS_GAMES) && !defined(MS_WINDOWS_DESKTOP)
|
||||
# define PATHCCH_ALLOW_LONG_PATHS 0x01
|
||||
# else
|
||||
|
@ -1056,6 +1058,13 @@ FILE_TIME_to_time_t_nsec(FILETIME *in_ptr, time_t *time_out, int* nsec_out)
|
|||
*time_out = Py_SAFE_DOWNCAST((in / 10000000) - secs_between_epochs, __int64, time_t);
|
||||
}
|
||||
|
||||
static void
|
||||
LARGE_INTEGER_to_time_t_nsec(LARGE_INTEGER *in_ptr, time_t *time_out, int* nsec_out)
|
||||
{
|
||||
*nsec_out = (int)(in_ptr->QuadPart % 10000000) * 100; /* FILETIME is in units of 100 nsec. */
|
||||
*time_out = Py_SAFE_DOWNCAST((in_ptr->QuadPart / 10000000) - secs_between_epochs, __int64, time_t);
|
||||
}
|
||||
|
||||
void
|
||||
_Py_time_t_to_FILE_TIME(time_t time_in, int nsec_in, FILETIME *out_ptr)
|
||||
{
|
||||
|
@ -1085,33 +1094,126 @@ attributes_to_mode(DWORD attr)
|
|||
return m;
|
||||
}
|
||||
|
||||
|
||||
typedef union {
|
||||
FILE_ID_128 id;
|
||||
struct {
|
||||
uint64_t st_ino;
|
||||
uint64_t st_ino_high;
|
||||
};
|
||||
} id_128_to_ino;
|
||||
|
||||
|
||||
void
|
||||
_Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag,
|
||||
FILE_BASIC_INFO *basic_info, FILE_ID_INFO *id_info,
|
||||
struct _Py_stat_struct *result)
|
||||
{
|
||||
memset(result, 0, sizeof(*result));
|
||||
result->st_mode = attributes_to_mode(info->dwFileAttributes);
|
||||
result->st_size = (((__int64)info->nFileSizeHigh)<<32) + info->nFileSizeLow;
|
||||
result->st_dev = info->dwVolumeSerialNumber;
|
||||
result->st_rdev = result->st_dev;
|
||||
FILE_TIME_to_time_t_nsec(&info->ftCreationTime, &result->st_ctime, &result->st_ctime_nsec);
|
||||
result->st_dev = id_info ? id_info->VolumeSerialNumber : info->dwVolumeSerialNumber;
|
||||
result->st_rdev = 0;
|
||||
/* st_ctime is deprecated, but we preserve the legacy value in our caller, not here */
|
||||
if (basic_info) {
|
||||
LARGE_INTEGER_to_time_t_nsec(&basic_info->CreationTime, &result->st_birthtime, &result->st_birthtime_nsec);
|
||||
LARGE_INTEGER_to_time_t_nsec(&basic_info->ChangeTime, &result->st_ctime, &result->st_ctime_nsec);
|
||||
LARGE_INTEGER_to_time_t_nsec(&basic_info->LastWriteTime, &result->st_mtime, &result->st_mtime_nsec);
|
||||
LARGE_INTEGER_to_time_t_nsec(&basic_info->LastAccessTime, &result->st_atime, &result->st_atime_nsec);
|
||||
} else {
|
||||
FILE_TIME_to_time_t_nsec(&info->ftCreationTime, &result->st_birthtime, &result->st_birthtime_nsec);
|
||||
FILE_TIME_to_time_t_nsec(&info->ftLastWriteTime, &result->st_mtime, &result->st_mtime_nsec);
|
||||
FILE_TIME_to_time_t_nsec(&info->ftLastAccessTime, &result->st_atime, &result->st_atime_nsec);
|
||||
}
|
||||
result->st_nlink = info->nNumberOfLinks;
|
||||
|
||||
if (id_info) {
|
||||
id_128_to_ino file_id;
|
||||
file_id.id = id_info->FileId;
|
||||
result->st_ino = file_id.st_ino;
|
||||
result->st_ino_high = file_id.st_ino_high;
|
||||
} else {
|
||||
/* should only occur for DirEntry_from_find_data, in which case the
|
||||
index is likely to be zero anyway. */
|
||||
result->st_ino = (((uint64_t)info->nFileIndexHigh) << 32) + info->nFileIndexLow;
|
||||
}
|
||||
|
||||
/* bpo-37834: Only actual symlinks set the S_IFLNK flag. But lstat() will
|
||||
open other name surrogate reparse points without traversing them. To
|
||||
detect/handle these, check st_file_attributes and st_reparse_tag. */
|
||||
result->st_reparse_tag = reparse_tag;
|
||||
if (info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
|
||||
reparse_tag == IO_REPARSE_TAG_SYMLINK) {
|
||||
/* first clear the S_IFMT bits */
|
||||
result->st_mode ^= (result->st_mode & S_IFMT);
|
||||
/* now set the bits that make this a symlink */
|
||||
result->st_mode |= S_IFLNK;
|
||||
/* set the bits that make this a symlink */
|
||||
result->st_mode = (result->st_mode & ~S_IFMT) | S_IFLNK;
|
||||
}
|
||||
result->st_file_attributes = info->dwFileAttributes;
|
||||
}
|
||||
|
||||
void
|
||||
_Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *info,
|
||||
struct _Py_stat_struct *result)
|
||||
{
|
||||
memset(result, 0, sizeof(*result));
|
||||
result->st_mode = attributes_to_mode(info->FileAttributes);
|
||||
result->st_size = info->EndOfFile.QuadPart;
|
||||
LARGE_INTEGER_to_time_t_nsec(&info->CreationTime, &result->st_birthtime, &result->st_birthtime_nsec);
|
||||
LARGE_INTEGER_to_time_t_nsec(&info->ChangeTime, &result->st_ctime, &result->st_ctime_nsec);
|
||||
LARGE_INTEGER_to_time_t_nsec(&info->LastWriteTime, &result->st_mtime, &result->st_mtime_nsec);
|
||||
LARGE_INTEGER_to_time_t_nsec(&info->LastAccessTime, &result->st_atime, &result->st_atime_nsec);
|
||||
result->st_nlink = info->NumberOfLinks;
|
||||
result->st_dev = info->VolumeSerialNumber.QuadPart;
|
||||
/* File systems with less than 128-bits zero pad into this field */
|
||||
id_128_to_ino file_id;
|
||||
file_id.id = info->FileId128;
|
||||
result->st_ino = file_id.st_ino;
|
||||
result->st_ino_high = file_id.st_ino_high;
|
||||
/* bpo-37834: Only actual symlinks set the S_IFLNK flag. But lstat() will
|
||||
open other name surrogate reparse points without traversing them. To
|
||||
detect/handle these, check st_file_attributes and st_reparse_tag. */
|
||||
result->st_reparse_tag = info->ReparseTag;
|
||||
if (info->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT &&
|
||||
info->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
|
||||
/* set the bits that make this a symlink */
|
||||
result->st_mode = (result->st_mode & ~S_IFMT) | S_IFLNK;
|
||||
}
|
||||
result->st_file_attributes = info->FileAttributes;
|
||||
switch (info->DeviceType) {
|
||||
case FILE_DEVICE_DISK:
|
||||
case FILE_DEVICE_VIRTUAL_DISK:
|
||||
case FILE_DEVICE_DFS:
|
||||
case FILE_DEVICE_CD_ROM:
|
||||
case FILE_DEVICE_CONTROLLER:
|
||||
case FILE_DEVICE_DATALINK:
|
||||
break;
|
||||
case FILE_DEVICE_DISK_FILE_SYSTEM:
|
||||
case FILE_DEVICE_CD_ROM_FILE_SYSTEM:
|
||||
case FILE_DEVICE_NETWORK_FILE_SYSTEM:
|
||||
result->st_mode = (result->st_mode & ~S_IFMT) | 0x6000; /* _S_IFBLK */
|
||||
break;
|
||||
case FILE_DEVICE_CONSOLE:
|
||||
case FILE_DEVICE_NULL:
|
||||
case FILE_DEVICE_KEYBOARD:
|
||||
case FILE_DEVICE_MODEM:
|
||||
case FILE_DEVICE_MOUSE:
|
||||
case FILE_DEVICE_PARALLEL_PORT:
|
||||
case FILE_DEVICE_PRINTER:
|
||||
case FILE_DEVICE_SCREEN:
|
||||
case FILE_DEVICE_SERIAL_PORT:
|
||||
case FILE_DEVICE_SOUND:
|
||||
result->st_mode = (result->st_mode & ~S_IFMT) | _S_IFCHR;
|
||||
break;
|
||||
case FILE_DEVICE_NAMED_PIPE:
|
||||
result->st_mode = (result->st_mode & ~S_IFMT) | _S_IFIFO;
|
||||
break;
|
||||
default:
|
||||
if (info->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
result->st_mode = (result->st_mode & ~S_IFMT) | _S_IFDIR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* Return information about a file.
|
||||
|
@ -1131,6 +1233,8 @@ _Py_fstat_noraise(int fd, struct _Py_stat_struct *status)
|
|||
{
|
||||
#ifdef MS_WINDOWS
|
||||
BY_HANDLE_FILE_INFORMATION info;
|
||||
FILE_BASIC_INFO basicInfo;
|
||||
FILE_ID_INFO idInfo;
|
||||
HANDLE h;
|
||||
int type;
|
||||
|
||||
|
@ -1162,14 +1266,16 @@ _Py_fstat_noraise(int fd, struct _Py_stat_struct *status)
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (!GetFileInformationByHandle(h, &info)) {
|
||||
if (!GetFileInformationByHandle(h, &info) ||
|
||||
!GetFileInformationByHandleEx(h, FileBasicInfo, &basicInfo, sizeof(basicInfo)) ||
|
||||
!GetFileInformationByHandleEx(h, FileIdInfo, &idInfo, sizeof(idInfo))) {
|
||||
/* The Win32 error is already set, but we also set errno for
|
||||
callers who expect it */
|
||||
errno = winerror_to_errno(GetLastError());
|
||||
return -1;
|
||||
}
|
||||
|
||||
_Py_attribute_data_to_stat(&info, 0, status);
|
||||
_Py_attribute_data_to_stat(&info, 0, &basicInfo, &idInfo, status);
|
||||
return 0;
|
||||
#else
|
||||
return fstat(fd, status);
|
||||
|
|
Loading…
Reference in New Issue