From 63f277b6944d583596675970666bbf4152b83349 Mon Sep 17 00:00:00 2001 From: Zachary Ware Date: Thu, 19 Jun 2014 09:46:37 -0500 Subject: [PATCH] Issue #21741: Add st_file_attributes to os.stat_result on Windows. Patch by Ben Hoyt. --- Doc/library/os.rst | 8 ++++++++ Doc/library/stat.rst | 28 +++++++++++++++++++++++++++- Doc/whatsnew/3.5.rst | 9 +++++++++ Lib/stat.py | 23 +++++++++++++++++++++++ Lib/test/test_os.py | 22 ++++++++++++++++++++++ Lib/test/test_stat.py | 29 +++++++++++++++++++++++++++++ Misc/ACKS | 1 + Misc/NEWS | 3 +++ Modules/_stat.c | 36 ++++++++++++++++++++++++++++++++++++ Modules/posixmodule.c | 16 ++++++++++++++++ 10 files changed, 174 insertions(+), 1 deletion(-) diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 27f6f2a9732..7575b3d2588 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -1905,6 +1905,11 @@ features: * :attr:`st_creator` * :attr:`st_type` + On Windows systems, the following attribute is also available: + + * :attr:`st_file_attributes` - Windows file attribute bits (see the + ``FILE_ATTRIBUTE_*`` constants in the :mod:`stat` module) + .. note:: The exact meaning and resolution of the :attr:`st_atime`, @@ -1958,6 +1963,9 @@ features: and the :attr:`st_atime_ns`, :attr:`st_mtime_ns`, and :attr:`st_ctime_ns` members. + .. versionadded:: 3.5 + Added the :attr:`st_file_attributes` member on Windows. + .. function:: stat_float_times([newvalue]) diff --git a/Doc/library/stat.rst b/Doc/library/stat.rst index 24769f68932..845b2ef7dab 100644 --- a/Doc/library/stat.rst +++ b/Doc/library/stat.rst @@ -126,7 +126,7 @@ Example:: if __name__ == '__main__': walktree(sys.argv[1], visitfile) -An additional utility function is provided to covert a file's mode in a human +An additional utility function is provided to convert a file's mode in a human readable string: .. function:: filemode(mode) @@ -399,3 +399,29 @@ The following flags can be used in the *flags* argument of :func:`os.chflags`: The file is a snapshot file. See the \*BSD or Mac OS systems man page :manpage:`chflags(2)` for more information. + +On Windows, the following file attribute constants are available for use when +testing bits in the ``st_file_attributes`` member returned by :func:`os.stat`. +See the `Windows API documentation +`_ +for more detail on the meaning of these constants. + +.. data:: FILE_ATTRIBUTE_ARCHIVE + FILE_ATTRIBUTE_COMPRESSED + FILE_ATTRIBUTE_DEVICE + FILE_ATTRIBUTE_DIRECTORY + FILE_ATTRIBUTE_ENCRYPTED + FILE_ATTRIBUTE_HIDDEN + FILE_ATTRIBUTE_INTEGRITY_STREAM + FILE_ATTRIBUTE_NORMAL + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED + FILE_ATTRIBUTE_NO_SCRUB_DATA + FILE_ATTRIBUTE_OFFLINE + FILE_ATTRIBUTE_READONLY + FILE_ATTRIBUTE_REPARSE_POINT + FILE_ATTRIBUTE_SPARSE_FILE + FILE_ATTRIBUTE_SYSTEM + FILE_ATTRIBUTE_TEMPORARY + FILE_ATTRIBUTE_VIRTUAL + + .. versionadded:: 3.5 diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index 17b703adeb7..1b4253ce728 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -176,6 +176,15 @@ ipaddress network objects from existing addresses (contributed by Peter Moody and Antoine Pitrou in :issue:`16531`). +os +-- + +* :class:`os.stat_result` now has a ``st_file_attributes`` field on Windows, + containing the ``dwFileAttributes`` member of the + ``BY_HANDLE_FILE_INFORMATION`` structure returned by + ``GetFileInformationByHandle()`` (contributed by Ben Hoyt in + :issue:`21719`). + shutil ------ diff --git a/Lib/stat.py b/Lib/stat.py index 3eecc3e0d34..46837c06dac 100644 --- a/Lib/stat.py +++ b/Lib/stat.py @@ -148,6 +148,29 @@ def filemode(mode): perm.append("-") return "".join(perm) + +# Windows FILE_ATTRIBUTE constants for interpreting os.stat()'s +# "st_file_attributes" member + +FILE_ATTRIBUTE_ARCHIVE = 32 +FILE_ATTRIBUTE_COMPRESSED = 2048 +FILE_ATTRIBUTE_DEVICE = 64 +FILE_ATTRIBUTE_DIRECTORY = 16 +FILE_ATTRIBUTE_ENCRYPTED = 16384 +FILE_ATTRIBUTE_HIDDEN = 2 +FILE_ATTRIBUTE_INTEGRITY_STREAM = 32768 +FILE_ATTRIBUTE_NORMAL = 128 +FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 8192 +FILE_ATTRIBUTE_NO_SCRUB_DATA = 131072 +FILE_ATTRIBUTE_OFFLINE = 4096 +FILE_ATTRIBUTE_READONLY = 1 +FILE_ATTRIBUTE_REPARSE_POINT = 1024 +FILE_ATTRIBUTE_SPARSE_FILE = 512 +FILE_ATTRIBUTE_SYSTEM = 4 +FILE_ATTRIBUTE_TEMPORARY = 256 +FILE_ATTRIBUTE_VIRTUAL = 65536 + + # If available, use C implementation try: from _stat import * diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 7d5ee69c45e..f559841ccf6 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -530,6 +530,28 @@ class StatAttributeTests(unittest.TestCase): os.stat(r) self.assertEqual(ctx.exception.errno, errno.EBADF) + def check_file_attributes(self, result): + self.assertTrue(hasattr(result, 'st_file_attributes')) + self.assertTrue(isinstance(result.st_file_attributes, int)) + self.assertTrue(0 <= result.st_file_attributes <= 0xFFFFFFFF) + + @unittest.skipUnless(sys.platform == "win32", + "st_file_attributes is Win32 specific") + def test_file_attributes(self): + # test file st_file_attributes (FILE_ATTRIBUTE_DIRECTORY not set) + result = os.stat(self.fname) + self.check_file_attributes(result) + self.assertEqual( + result.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY, + 0) + + # test directory st_file_attributes (FILE_ATTRIBUTE_DIRECTORY set) + result = os.stat(support.TESTFN) + self.check_file_attributes(result) + self.assertEqual( + result.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY, + stat.FILE_ATTRIBUTE_DIRECTORY) + from test import mapping_tests class EnvironTests(mapping_tests.BasicTestMappingProtocol): diff --git a/Lib/test/test_stat.py b/Lib/test/test_stat.py index af6ced42040..f1a5938a39f 100644 --- a/Lib/test/test_stat.py +++ b/Lib/test/test_stat.py @@ -1,5 +1,6 @@ import unittest import os +import sys from test.support import TESTFN, import_fresh_module c_stat = import_fresh_module('stat', fresh=['_stat']) @@ -52,6 +53,26 @@ class TestFilemode: 'S_IWOTH': 0o002, 'S_IXOTH': 0o001} + # defined by the Windows API documentation + file_attributes = { + 'FILE_ATTRIBUTE_ARCHIVE': 32, + 'FILE_ATTRIBUTE_COMPRESSED': 2048, + 'FILE_ATTRIBUTE_DEVICE': 64, + 'FILE_ATTRIBUTE_DIRECTORY': 16, + 'FILE_ATTRIBUTE_ENCRYPTED': 16384, + 'FILE_ATTRIBUTE_HIDDEN': 2, + 'FILE_ATTRIBUTE_INTEGRITY_STREAM': 32768, + 'FILE_ATTRIBUTE_NORMAL': 128, + 'FILE_ATTRIBUTE_NOT_CONTENT_INDEXED': 8192, + 'FILE_ATTRIBUTE_NO_SCRUB_DATA': 131072, + 'FILE_ATTRIBUTE_OFFLINE': 4096, + 'FILE_ATTRIBUTE_READONLY': 1, + 'FILE_ATTRIBUTE_REPARSE_POINT': 1024, + 'FILE_ATTRIBUTE_SPARSE_FILE': 512, + 'FILE_ATTRIBUTE_SYSTEM': 4, + 'FILE_ATTRIBUTE_TEMPORARY': 256, + 'FILE_ATTRIBUTE_VIRTUAL': 65536} + def setUp(self): try: os.remove(TESTFN) @@ -185,6 +206,14 @@ class TestFilemode: self.assertTrue(callable(func)) self.assertEqual(func(0), 0) + @unittest.skipUnless(sys.platform == "win32", + "FILE_ATTRIBUTE_* constants are Win32 specific") + def test_file_attribute_constants(self): + for key, value in sorted(self.file_attributes.items()): + self.assertTrue(hasattr(self.statmod, key), key) + modvalue = getattr(self.statmod, key) + self.assertEqual(value, modvalue, key) + class TestFilemodeCStat(TestFilemode, unittest.TestCase): statmod = c_stat diff --git a/Misc/ACKS b/Misc/ACKS index 52c00f86aed..6fff1e114e1 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -580,6 +580,7 @@ Alan Hourihane Ken Howard Brad Howes Mike Hoy +Ben Hoyt Chih-Hao Huang Christian Hudon Lawrence Hudson diff --git a/Misc/NEWS b/Misc/NEWS index 0ed980a1ebd..f6491340d33 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -103,6 +103,9 @@ Core and Builtins Library ------- +- Issue #21719: Added the ``st_file_attributes`` field to os.stat_result on + Windows. + - Issue #21722: The distutils "upload" command now exits with a non-zero return code when uploading fails. Patch by Martin Dengler. diff --git a/Modules/_stat.c b/Modules/_stat.c index a301fa8840c..f6cb303500c 100644 --- a/Modules/_stat.c +++ b/Modules/_stat.c @@ -27,9 +27,21 @@ extern "C" { #endif /* HAVE_SYS_STAT_H */ #ifdef MS_WINDOWS +#include typedef unsigned short mode_t; + +/* FILE_ATTRIBUTE_INTEGRITY_STREAM and FILE_ATTRIBUTE_NO_SCRUB_DATA + are not present in VC2010, so define them manually */ +#ifndef FILE_ATTRIBUTE_INTEGRITY_STREAM +# define FILE_ATTRIBUTE_INTEGRITY_STREAM 0x8000 #endif +#ifndef FILE_ATTRIBUTE_NO_SCRUB_DATA +# define FILE_ATTRIBUTE_NO_SCRUB_DATA 0x20000 +#endif + +#endif /* MS_WINDOWS */ + /* From Python's stat.py */ #ifndef S_IMODE # define S_IMODE 07777 @@ -473,6 +485,10 @@ ST_SIZE\n\ ST_ATIME\n\ ST_MTIME\n\ ST_CTIME\n\ +\n" + +"FILE_ATTRIBUTE_*: Windows file attribute constants\n\ + (only present on Windows)\n\ "); @@ -555,6 +571,26 @@ PyInit__stat(void) if (PyModule_AddIntConstant(m, "ST_MTIME", 8)) return NULL; if (PyModule_AddIntConstant(m, "ST_CTIME", 9)) return NULL; +#ifdef MS_WINDOWS + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_ARCHIVE)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_COMPRESSED)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_DEVICE)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_DIRECTORY)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_ENCRYPTED)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_HIDDEN)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_INTEGRITY_STREAM)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_NORMAL)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_NO_SCRUB_DATA)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_OFFLINE)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_READONLY)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_REPARSE_POINT)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_SPARSE_FILE)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_SYSTEM)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_TEMPORARY)) return NULL; + if (PyModule_AddIntMacro(m, FILE_ATTRIBUTE_VIRTUAL)) return NULL; +#endif + return m; } diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 916be81e431..899618f2665 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -1417,6 +1417,7 @@ win32_wchdir(LPCWSTR path) Therefore, we implement our own stat, based on the Win32 API directly. */ #define HAVE_STAT_NSEC 1 +#define HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES 1 struct win32_stat{ unsigned long st_dev; @@ -1433,6 +1434,7 @@ struct win32_stat{ int st_mtime_nsec; time_t st_ctime; int st_ctime_nsec; + unsigned long st_file_attributes; }; static __int64 secs_between_epochs = 11644473600; /* Seconds between 1.1.1601 and 1.1.1970 */ @@ -1497,6 +1499,7 @@ attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag, stru /* now set the bits that make this a symlink */ result->st_mode |= S_IFLNK; } + result->st_file_attributes = info->dwFileAttributes; return 0; } @@ -1960,6 +1963,9 @@ static PyStructSequence_Field stat_result_fields[] = { #endif #ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME {"st_birthtime", "time of creation"}, +#endif +#ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES + {"st_file_attributes", "Windows file attribute bits"}, #endif {0} }; @@ -2000,6 +2006,12 @@ static PyStructSequence_Field stat_result_fields[] = { #define ST_BIRTHTIME_IDX ST_GEN_IDX #endif +#ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES +#define ST_FILE_ATTRIBUTES_IDX (ST_BIRTHTIME_IDX+1) +#else +#define ST_FILE_ATTRIBUTES_IDX ST_BIRTHTIME_IDX +#endif + static PyStructSequence_Desc stat_result_desc = { "stat_result", /* name */ stat_result__doc__, /* doc */ @@ -2267,6 +2279,10 @@ _pystat_fromstructstat(STRUCT_STAT *st) PyStructSequence_SET_ITEM(v, ST_FLAGS_IDX, PyLong_FromLong((long)st->st_flags)); #endif +#ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES + PyStructSequence_SET_ITEM(v, ST_FILE_ATTRIBUTES_IDX, + PyLong_FromUnsignedLong(st->st_file_attributes)); +#endif if (PyErr_Occurred()) { Py_DECREF(v);