Skip to content

Commit a10c1f2

Browse files
zoobaFidget-Spinner
authored andcommitted
pythongh-99726: Improves correctness of stat results for Windows, and uses faster API when available (pythonGH-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.
1 parent ba65c48 commit a10c1f2

File tree

10 files changed

+446
-82
lines changed

10 files changed

+446
-82
lines changed

Doc/library/os.rst

+65-23
Original file line numberDiff line numberDiff line change
@@ -2858,6 +2858,12 @@ features:
28582858
Added support for the :class:`~os.PathLike` interface. Added support
28592859
for :class:`bytes` paths on Windows.
28602860

2861+
.. versionchanged:: 3.12
2862+
The ``st_ctime`` attribute of a stat result is deprecated on Windows.
2863+
The file creation time is properly available as ``st_birthtime``, and
2864+
in the future ``st_ctime`` may be changed to return zero or the
2865+
metadata change time, if available.
2866+
28612867

28622868
.. function:: stat(path, *, dir_fd=None, follow_symlinks=True)
28632869

@@ -2973,10 +2979,12 @@ features:
29732979

29742980
.. attribute:: st_ctime
29752981

2976-
Platform dependent:
2982+
Time of most recent metadata change expressed in seconds.
29772983

2978-
* the time of most recent metadata change on Unix,
2979-
* the time of creation on Windows, expressed in seconds.
2984+
.. versionchanged:: 3.12
2985+
``st_ctime`` is deprecated on Windows. Use ``st_birthtime`` for
2986+
the file creation time. In the future, ``st_ctime`` will contain
2987+
the time of the most recent metadata change, as for other platforms.
29802988

29812989
.. attribute:: st_atime_ns
29822990

@@ -2989,29 +2997,48 @@ features:
29892997

29902998
.. attribute:: st_ctime_ns
29912999

2992-
Platform dependent:
3000+
Time of most recent metadata change expressed in nanoseconds as an
3001+
integer.
3002+
3003+
.. versionchanged:: 3.12
3004+
``st_ctime_ns`` is deprecated on Windows. Use ``st_birthtime_ns``
3005+
for the file creation time. In the future, ``st_ctime`` will contain
3006+
the time of the most recent metadata change, as for other platforms.
3007+
3008+
.. attribute:: st_birthtime
3009+
3010+
Time of file creation expressed in seconds. This attribute is not
3011+
always available, and may raise :exc:`AttributeError`.
3012+
3013+
.. versionchanged:: 3.12
3014+
``st_birthtime`` is now available on Windows.
3015+
3016+
.. attribute:: st_birthtime_ns
29933017

2994-
* the time of most recent metadata change on Unix,
2995-
* the time of creation on Windows, expressed in nanoseconds as an
2996-
integer.
3018+
Time of file creation expressed in nanoseconds as an integer.
3019+
This attribute is not always available, and may raise
3020+
:exc:`AttributeError`.
3021+
3022+
.. versionadded:: 3.12
29973023

29983024
.. note::
29993025

30003026
The exact meaning and resolution of the :attr:`st_atime`,
3001-
:attr:`st_mtime`, and :attr:`st_ctime` attributes depend on the operating
3002-
system and the file system. For example, on Windows systems using the FAT
3003-
or FAT32 file systems, :attr:`st_mtime` has 2-second resolution, and
3004-
:attr:`st_atime` has only 1-day resolution. See your operating system
3005-
documentation for details.
3027+
:attr:`st_mtime`, :attr:`st_ctime` and :attr:`st_birthtime` attributes
3028+
depend on the operating system and the file system. For example, on
3029+
Windows systems using the FAT32 file systems, :attr:`st_mtime` has
3030+
2-second resolution, and :attr:`st_atime` has only 1-day resolution.
3031+
See your operating system documentation for details.
30063032

30073033
Similarly, although :attr:`st_atime_ns`, :attr:`st_mtime_ns`,
3008-
and :attr:`st_ctime_ns` are always expressed in nanoseconds, many
3009-
systems do not provide nanosecond precision. On systems that do
3010-
provide nanosecond precision, the floating-point object used to
3011-
store :attr:`st_atime`, :attr:`st_mtime`, and :attr:`st_ctime`
3012-
cannot preserve all of it, and as such will be slightly inexact.
3013-
If you need the exact timestamps you should always use
3014-
:attr:`st_atime_ns`, :attr:`st_mtime_ns`, and :attr:`st_ctime_ns`.
3034+
:attr:`st_ctime_ns` and :attr:`st_birthtime_ns` are always expressed in
3035+
nanoseconds, many systems do not provide nanosecond precision. On
3036+
systems that do provide nanosecond precision, the floating-point object
3037+
used to store :attr:`st_atime`, :attr:`st_mtime`, :attr:`st_ctime` and
3038+
:attr:`st_birthtime` cannot preserve all of it, and as such will be
3039+
slightly inexact. If you need the exact timestamps you should always use
3040+
:attr:`st_atime_ns`, :attr:`st_mtime_ns`, :attr:`st_ctime_ns` and
3041+
:attr:`st_birthtime_ns`.
30153042

30163043
On some Unix systems (such as Linux), the following attributes may also be
30173044
available:
@@ -3041,10 +3068,6 @@ features:
30413068

30423069
File generation number.
30433070

3044-
.. attribute:: st_birthtime
3045-
3046-
Time of file creation.
3047-
30483071
On Solaris and derivatives, the following attributes may also be
30493072
available:
30503073

@@ -3117,6 +3140,25 @@ features:
31173140
files as :const:`S_IFCHR`, :const:`S_IFIFO` or :const:`S_IFBLK`
31183141
as appropriate.
31193142

3143+
.. versionchanged:: 3.12
3144+
On Windows, :attr:`st_ctime` is deprecated. Eventually, it will
3145+
contain the last metadata change time, for consistency with other
3146+
platforms, but for now still contains creation time.
3147+
Use :attr:`st_birthtime` for the creation time.
3148+
3149+
.. versionchanged:: 3.12
3150+
On Windows, :attr:`st_ino` may now be up to 128 bits, depending
3151+
on the file system. Previously it would not be above 64 bits, and
3152+
larger file identifiers would be arbitrarily packed.
3153+
3154+
.. versionchanged:: 3.12
3155+
On Windows, :attr:`st_rdev` no longer returns a value. Previously
3156+
it would contain the same as :attr:`st_dev`, which was incorrect.
3157+
3158+
.. versionadded:: 3.12
3159+
Added the :attr:`st_birthtime` member on Windows.
3160+
3161+
31203162
.. function:: statvfs(path)
31213163

31223164
Perform a :c:func:`statvfs` system call on the given path. The return value is

Doc/whatsnew/3.12.rst

+16
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,16 @@ os
306306
functions on Windows for enumerating drives, volumes and mount points.
307307
(Contributed by Steve Dower in :gh:`102519`.)
308308

309+
* :func:`os.stat` and :func:`os.lstat` are now more accurate on Windows.
310+
The ``st_birthtime`` field will now be filled with the creation time
311+
of the file, and ``st_ctime`` is deprecated but still contains the
312+
creation time (but in the future will return the last metadata change,
313+
for consistency with other platforms). ``st_dev`` may be up to 64 bits
314+
and ``st_ino`` up to 128 bits depending on your file system, and
315+
``st_rdev`` is always set to zero rather than incorrect values.
316+
Both functions may be significantly faster on newer releases of
317+
Windows. (Contributed by Steve Dower in :gh:`99726`.)
318+
309319
os.path
310320
-------
311321

@@ -469,6 +479,12 @@ Deprecated
469479
warning at compile time. This field will be removed in Python 3.14.
470480
(Contributed by Ramvikrams and Kumar Aditya in :gh:`101193`. PEP by Ken Jin.)
471481

482+
* The ``st_ctime`` fields return by :func:`os.stat` and :func:`os.lstat` on
483+
Windows are deprecated. In a future release, they will contain the last
484+
metadata change time, consistent with other platforms. For now, they still
485+
contain the creation time, which is also available in the new ``st_birthtime``
486+
field. (Contributed by Steve Dower in :gh:`99726`.)
487+
472488
Pending Removal in Python 3.13
473489
------------------------------
474490

Include/internal/pycore_fileutils.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ PyAPI_FUNC(PyObject *) _Py_device_encoding(int);
6666

6767
#ifdef MS_WINDOWS
6868
struct _Py_stat_struct {
69-
unsigned long st_dev;
69+
uint64_t st_dev;
7070
uint64_t st_ino;
7171
unsigned short st_mode;
7272
int st_nlink;
@@ -80,8 +80,11 @@ struct _Py_stat_struct {
8080
int st_mtime_nsec;
8181
time_t st_ctime;
8282
int st_ctime_nsec;
83+
time_t st_birthtime;
84+
int st_birthtime_nsec;
8385
unsigned long st_file_attributes;
8486
unsigned long st_reparse_tag;
87+
uint64_t st_ino_high;
8588
};
8689
#else
8790
# define _Py_stat_struct stat
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#ifndef Py_INTERNAL_FILEUTILS_WINDOWS_H
2+
#define Py_INTERNAL_FILEUTILS_WINDOWS_H
3+
#ifdef __cplusplus
4+
extern "C" {
5+
#endif
6+
7+
#ifndef Py_BUILD_CORE
8+
# error "Py_BUILD_CORE must be defined to include this header"
9+
#endif
10+
11+
#ifdef MS_WINDOWS
12+
13+
#if !defined(NTDDI_WIN10_NI) || !(NTDDI_VERSION >= NTDDI_WIN10_NI)
14+
typedef struct _FILE_STAT_BASIC_INFORMATION {
15+
LARGE_INTEGER FileId;
16+
LARGE_INTEGER CreationTime;
17+
LARGE_INTEGER LastAccessTime;
18+
LARGE_INTEGER LastWriteTime;
19+
LARGE_INTEGER ChangeTime;
20+
LARGE_INTEGER AllocationSize;
21+
LARGE_INTEGER EndOfFile;
22+
ULONG FileAttributes;
23+
ULONG ReparseTag;
24+
ULONG NumberOfLinks;
25+
ULONG DeviceType;
26+
ULONG DeviceCharacteristics;
27+
ULONG Reserved;
28+
FILE_ID_128 FileId128;
29+
LARGE_INTEGER VolumeSerialNumber;
30+
} FILE_STAT_BASIC_INFORMATION;
31+
32+
typedef enum _FILE_INFO_BY_NAME_CLASS {
33+
FileStatByNameInfo,
34+
FileStatLxByNameInfo,
35+
FileCaseSensitiveByNameInfo,
36+
FileStatBasicByNameInfo,
37+
MaximumFileInfoByNameClass
38+
} FILE_INFO_BY_NAME_CLASS;
39+
#endif
40+
41+
typedef BOOL (WINAPI *PGetFileInformationByName)(
42+
PCWSTR FileName,
43+
FILE_INFO_BY_NAME_CLASS FileInformationClass,
44+
PVOID FileInfoBuffer,
45+
ULONG FileInfoBufferSize
46+
);
47+
48+
static inline BOOL _Py_GetFileInformationByName(
49+
PCWSTR FileName,
50+
FILE_INFO_BY_NAME_CLASS FileInformationClass,
51+
PVOID FileInfoBuffer,
52+
ULONG FileInfoBufferSize
53+
) {
54+
static PGetFileInformationByName GetFileInformationByName = NULL;
55+
static int GetFileInformationByName_init = -1;
56+
57+
if (GetFileInformationByName_init < 0) {
58+
HMODULE hMod = LoadLibraryW(L"api-ms-win-core-file-l2-1-4");
59+
GetFileInformationByName_init = 0;
60+
if (hMod) {
61+
GetFileInformationByName = (PGetFileInformationByName)GetProcAddress(
62+
hMod, "GetFileInformationByName");
63+
if (GetFileInformationByName) {
64+
GetFileInformationByName_init = 1;
65+
} else {
66+
FreeLibrary(hMod);
67+
}
68+
}
69+
}
70+
71+
if (GetFileInformationByName_init <= 0) {
72+
SetLastError(ERROR_NOT_SUPPORTED);
73+
return FALSE;
74+
}
75+
return GetFileInformationByName(FileName, FileInformationClass, FileInfoBuffer, FileInfoBufferSize);
76+
}
77+
78+
#endif
79+
80+
#endif

Lib/test/test_os.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,15 @@ def trunc(x): return x
556556
nanosecondy = getattr(result, name + "_ns") // 10000
557557
self.assertAlmostEqual(floaty, nanosecondy, delta=2)
558558

559+
# Ensure both birthtime and birthtime_ns roughly agree, if present
560+
try:
561+
floaty = int(result.st_birthtime * 100000)
562+
nanosecondy = result.st_birthtime_ns // 10000
563+
except AttributeError:
564+
pass
565+
else:
566+
self.assertAlmostEqual(floaty, nanosecondy, delta=2)
567+
559568
try:
560569
result[200]
561570
self.fail("No exception raised")
@@ -4234,7 +4243,8 @@ def assert_stat_equal(self, stat1, stat2, skip_fields):
42344243
for attr in dir(stat1):
42354244
if not attr.startswith("st_"):
42364245
continue
4237-
if attr in ("st_dev", "st_ino", "st_nlink"):
4246+
if attr in ("st_dev", "st_ino", "st_nlink", "st_ctime",
4247+
"st_ctime_ns"):
42384248
continue
42394249
self.assertEqual(getattr(stat1, attr),
42404250
getattr(stat2, attr),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improves correctness of stat results for Windows, and uses faster API when
2+
available

0 commit comments

Comments
 (0)