From 9a7d3d84969173b1a53e0e73fdc22f8eb6d52c34 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 25 Jan 2023 11:36:30 -0500 Subject: [PATCH 01/29] Make isdir/isfile/exists faster on Windows --- Lib/ntpath.py | 9 +- Lib/test/test_ntpath.py | 13 ++ ...-01-25-11-33-54.gh-issue-101196.wAX_2g.rst | 2 + Modules/clinic/posixmodule.c.h | 201 +++++++++++++++++- Modules/posixmodule.c | 192 +++++++++++++++++ 5 files changed, 412 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst diff --git a/Lib/ntpath.py b/Lib/ntpath.py index cd7fb58a88de67..5fb4e761bbabd2 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -852,11 +852,12 @@ def commonpath(paths): try: - # The genericpath.isdir implementation uses os.stat and checks the mode - # attribute to tell whether or not the path is a directory. - # This is overkill on Windows - just pass the path to GetFileAttributes + # The genericpath's isdir and isfile implementations uses os.stat internally. + # This is overkill on Windows - just pass the path to GetFileAttributesW # and check the attribute from there. from nt import _isdir as isdir + from nt import _isfile as isfile + from nt import _exists as exists except ImportError: - # Use genericpath.isdir as imported above. + # Use genericpath.* as imported above pass diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index f56de0be772105..a74f138e1cec5c 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -1,3 +1,4 @@ +import inspect import ntpath import os import sys @@ -889,6 +890,18 @@ def test_isjunction(self): self.assertFalse(ntpath.isjunction('tmpdir')) self.assertPathEqual(ntpath.realpath('testjunc'), ntpath.realpath('tmpdir')) + @unittest.skipIf(sys.platform != 'win32', "Fast paths are only for win32") + def test_fast_paths_in_use(self): + # There are fast paths of these functions implemented in posixmodule.c. + # Confirm that they are being used, and not the Python fallbacks in + # genericpath.py. + self.assertTrue(os.path.isdir is nt._isdir) + self.assertFalse(inspect.isfunction(os.path.isdir)) + self.assertTrue(os.path.isfile is nt._isfile) + self.assertFalse(inspect.isfunction(os.path.isfile)) + self.assertTrue(os.path.exists is nt._exists) + self.assertFalse(inspect.isfunction(os.path.exists)) + class NtCommonTest(test_genericpath.CommonTest, unittest.TestCase): pathmodule = ntpath diff --git a/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst b/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst new file mode 100644 index 00000000000000..ff1536ef60dfb5 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst @@ -0,0 +1,2 @@ +The functions `os.path.isdir`, `os.path.isfile` and `os.path.exists` are now +13% to 28% faster on Windows, by making fewer win32 API calls. diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index d4722cc533cbab..3c7b6ac038452a 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -10965,6 +10965,193 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #endif /* (defined(WIFEXITED) || defined(MS_WINDOWS)) */ +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__isdir__doc__, +"_isdir($module, /, path)\n" +"--\n" +"\n" +"Return True if path is an existing directory.\n" +"\n" +"This follows symbolic links, so both islink() and isdir() can be true for the\n" +"same path."); + +#define OS__ISDIR_METHODDEF \ + {"_isdir", _PyCFunction_CAST(os__isdir), METH_FASTCALL|METH_KEYWORDS, os__isdir__doc__}, + +static PyObject * +os__isdir_impl(PyObject *module, PyObject *path); + +static PyObject * +os__isdir(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(path), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"path", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_isdir", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *path; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + path = args[0]; + return_value = os__isdir_impl(module, path); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__isfile__doc__, +"_isfile($module, /, path)\n" +"--\n" +"\n" +"Return True if path is an existing file.\n" +"\n" +"This follows symbolic links, so both islink() and isfile() can be true for the\n" +"same path."); + +#define OS__ISFILE_METHODDEF \ + {"_isfile", _PyCFunction_CAST(os__isfile), METH_FASTCALL|METH_KEYWORDS, os__isfile__doc__}, + +static PyObject * +os__isfile_impl(PyObject *module, PyObject *path); + +static PyObject * +os__isfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(path), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"path", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_isfile", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *path; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + path = args[0]; + return_value = os__isfile_impl(module, path); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__exists__doc__, +"_exists($module, /, path)\n" +"--\n" +"\n" +"Return True if path refers to an existing path or an open file descriptor.\n" +"\n" +"Returns False for broken symbolic links. On some platforms, this function may\n" +"return False if permission is not granted to execute os.stat() on the requested\n" +"file, even if the path physically exists."); + +#define OS__EXISTS_METHODDEF \ + {"_exists", _PyCFunction_CAST(os__exists), METH_FASTCALL|METH_KEYWORDS, os__exists__doc__}, + +static PyObject * +os__exists_impl(PyObject *module, PyObject *path); + +static PyObject * +os__exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(path), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"path", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_exists", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *path; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + path = args[0]; + return_value = os__exists_impl(module, path); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + #ifndef OS_TTYNAME_METHODDEF #define OS_TTYNAME_METHODDEF #endif /* !defined(OS_TTYNAME_METHODDEF) */ @@ -11560,4 +11747,16 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ -/*[clinic end generated code: output=41eab6c3523792a9 input=a9049054013a1b77]*/ + +#ifndef OS__ISDIR_METHODDEF + #define OS__ISDIR_METHODDEF +#endif /* !defined(OS__ISDIR_METHODDEF) */ + +#ifndef OS__ISFILE_METHODDEF + #define OS__ISFILE_METHODDEF +#endif /* !defined(OS__ISFILE_METHODDEF) */ + +#ifndef OS__EXISTS_METHODDEF + #define OS__EXISTS_METHODDEF +#endif /* !defined(OS__EXISTS_METHODDEF) */ +/*[clinic end generated code: output=378f9b4d00998b10 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index b84fb0d280f4e3..05854ca9a62a51 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -14962,6 +14962,194 @@ os_waitstatus_to_exitcode_impl(PyObject *module, PyObject *status_obj) #endif +#if defined(MS_WINDOWS) +/*[clinic input] +os._isdir + + path: 'O' + +Return True if path is an existing directory. + +This follows symbolic links, so both islink() and isdir() can be true for the +same path. + +[clinic start generated code]*/ + +static PyObject * +os__isdir_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=e87264f004b1aa44 input=2b77ce7f601eb6bc]*/ +{ + HANDLE hfile; + BOOL close_file = TRUE; + FILE_BASIC_INFO info = { 0 }; + path_t _path = PATH_T_INITIALIZE("isdir", "path", 0, 1); + int result; + + if (!path_converter(path, &_path)) { + path_cleanup(&_path); + if (PyErr_ExceptionMatches(PyExc_ValueError)) { + PyErr_Clear(); + Py_RETURN_FALSE; + } else { + return NULL; + } + } + + Py_BEGIN_ALLOW_THREADS + if (_path.fd != -1) { + hfile = _Py_get_osfhandle_noraise(_path.fd); + close_file = FALSE; + } else { + hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + } + if (hfile != INVALID_HANDLE_VALUE) { + GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, sizeof(info)); + if (close_file) { + CloseHandle(hfile); + } + result = info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY; + } else { + result = 0; + } + Py_END_ALLOW_THREADS + + path_cleanup(&_path); + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + + +/*[clinic input] +os._isfile + + path: 'O' + +Return True if path is an existing file. + +This follows symbolic links, so both islink() and isfile() can be true for the +same path. + +[clinic start generated code]*/ + +static PyObject * +os__isfile_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=45efe2f636a0226a input=e4026a0f974ee867]*/ +{ + HANDLE hfile; + BOOL close_file = TRUE; + FILE_BASIC_INFO info = { 0 }; + DWORD fileType; + path_t _path = PATH_T_INITIALIZE("isfile", "path", 0, 1); + int result; + + if (!path_converter(path, &_path)) { + path_cleanup(&_path); + if (PyErr_ExceptionMatches(PyExc_ValueError)) { + PyErr_Clear(); + Py_RETURN_FALSE; + } else { + return NULL; + } + } + + Py_BEGIN_ALLOW_THREADS + if (_path.fd != -1) { + hfile = _Py_get_osfhandle_noraise(_path.fd); + close_file = FALSE; + } else { + hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + } + if (hfile != INVALID_HANDLE_VALUE) { + GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, sizeof(info)); + result = !(info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY); + if (result) { + fileType = GetFileType(hfile); + if (fileType != FILE_TYPE_DISK) { + result = 0; + } + } + if (close_file) { + CloseHandle(hfile); + } + } else { + result = 0; + } + Py_END_ALLOW_THREADS + + path_cleanup(&_path); + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} + + +/*[clinic input] +os._exists + + path: 'O' + +Return True if path refers to an existing path or an open file descriptor. + +Returns False for broken symbolic links. On some platforms, this function may +return False if permission is not granted to execute os.stat() on the requested +file, even if the path physically exists. + +[clinic start generated code]*/ + +static PyObject * +os__exists_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=0f0ba589f386f816 input=a6e67a9b92711692]*/ +{ + HANDLE hfile; + BOOL close_file = TRUE; + path_t _path = PATH_T_INITIALIZE("exists", "path", 0, 1); + int result; + + if (!path_converter(path, &_path)) { + path_cleanup(&_path); + if (PyErr_ExceptionMatches(PyExc_ValueError)) { + PyErr_Clear(); + Py_RETURN_FALSE; + } else { + return NULL; + } + } + + Py_BEGIN_ALLOW_THREADS + if (_path.fd != -1) { + hfile = _Py_get_osfhandle_noraise(_path.fd); + close_file = FALSE; + } else { + hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + } + if (hfile != INVALID_HANDLE_VALUE) { + result = 1; + if (close_file) { + CloseHandle(hfile); + } + } else { + result = 0; + } + Py_END_ALLOW_THREADS + + path_cleanup(&_path); + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} +#endif + + static PyMethodDef posix_methods[] = { OS_STAT_METHODDEF @@ -15150,6 +15338,10 @@ static PyMethodDef posix_methods[] = { OS_WAITSTATUS_TO_EXITCODE_METHODDEF OS_SETNS_METHODDEF OS_UNSHARE_METHODDEF + + OS__ISFILE_METHODDEF + OS__ISDIR_METHODDEF + OS__EXISTS_METHODDEF {NULL, NULL} /* Sentinel */ }; From a07f1e796530c15402d957f2ba3f2a2433440d11 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 25 Jan 2023 11:48:00 -0500 Subject: [PATCH 02/29] Fix doc syntax --- .../next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst b/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst index ff1536ef60dfb5..f72b18eb86e766 100644 --- a/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst +++ b/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst @@ -1,2 +1,2 @@ -The functions `os.path.isdir`, `os.path.isfile` and `os.path.exists` are now +The functions ``os.path.isdir``, ``os.path.isfile`` and `os.path.exists` are now 13% to 28% faster on Windows, by making fewer win32 API calls. From 684d6833ae727600a60f1ffe9dce551461c0180e Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 25 Jan 2023 12:10:42 -0500 Subject: [PATCH 03/29] Fix doc syntax --- .../next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst b/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst index f72b18eb86e766..2a00ad6cf7dd0c 100644 --- a/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst +++ b/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst @@ -1,2 +1,2 @@ -The functions ``os.path.isdir``, ``os.path.isfile`` and `os.path.exists` are now +The functions ``os.path.isdir``, ``os.path.isfile`` and ``os.path.exists`` are now 13% to 28% faster on Windows, by making fewer win32 API calls. From 781fa07982dd3f5f52bd96e289033c317d01f508 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 25 Jan 2023 16:05:00 -0500 Subject: [PATCH 04/29] Update Lib/ntpath.py Co-authored-by: Eryk Sun --- Lib/ntpath.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 5fb4e761bbabd2..501ba0c84a111f 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -852,9 +852,9 @@ def commonpath(paths): try: - # The genericpath's isdir and isfile implementations uses os.stat internally. - # This is overkill on Windows - just pass the path to GetFileAttributesW - # and check the attribute from there. + # The isdir(), isfile(), and exists() implementations in genericpath use + # os.stat(). This is overkill on Windows. Use simpler builtin functions + # if they are available. from nt import _isdir as isdir from nt import _isfile as isfile from nt import _exists as exists From dda7be7f3759dec7a68e63c785adc1f80674d358 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 25 Jan 2023 16:39:25 -0500 Subject: [PATCH 05/29] Mark test as CPython-only --- Lib/test/test_ntpath.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index a74f138e1cec5c..1a3a90e5b08f26 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -4,7 +4,7 @@ import sys import unittest import warnings -from test.support import os_helper +from test.support import cpython_only, os_helper from test.support import TestFailed, is_emscripten from test.support.os_helper import FakePath from test import test_genericpath @@ -891,6 +891,7 @@ def test_isjunction(self): self.assertPathEqual(ntpath.realpath('testjunc'), ntpath.realpath('tmpdir')) @unittest.skipIf(sys.platform != 'win32', "Fast paths are only for win32") + @cpython_only def test_fast_paths_in_use(self): # There are fast paths of these functions implemented in posixmodule.c. # Confirm that they are being used, and not the Python fallbacks in From 565c2e1583d34a943bbc97ba4edcd04a3dc32aca Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 25 Jan 2023 16:43:00 -0500 Subject: [PATCH 06/29] Handle files correctly --- Lib/test/test_ntpath.py | 5 +++++ Modules/posixmodule.c | 11 ++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 1a3a90e5b08f26..c5af67881e65f3 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -890,6 +890,11 @@ def test_isjunction(self): self.assertFalse(ntpath.isjunction('tmpdir')) self.assertPathEqual(ntpath.realpath('testjunc'), ntpath.realpath('tmpdir')) + @unittest.skipIf(sys.platform != 'win32', "drive letters are a windows concept") + def test_isfile_driveletter(self): + current_drive = os.path.splitdrive(os.path.abspath(__file__))[0] + "\\" + self.assertFalse(os.path.isfile(current_drive)) + @unittest.skipIf(sys.platform != 'win32', "Fast paths are only for win32") @cpython_only def test_fast_paths_in_use(self): diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 05854ca9a62a51..5a615022f1c95b 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -15065,13 +15065,10 @@ os__isfile_impl(PyObject *module, PyObject *path) OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); } if (hfile != INVALID_HANDLE_VALUE) { - GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, sizeof(info)); - result = !(info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY); - if (result) { - fileType = GetFileType(hfile); - if (fileType != FILE_TYPE_DISK) { - result = 0; - } + if (GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, sizeof(info))) { + result = !(info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } else { + result = 0; } if (close_file) { CloseHandle(hfile); From 53b932b56ac987f5b292f2877eaa8919f14d8e54 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 25 Jan 2023 16:54:35 -0500 Subject: [PATCH 07/29] Remove unused variable --- Modules/posixmodule.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 5a615022f1c95b..0ea1cf69c5247b 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -15042,7 +15042,6 @@ os__isfile_impl(PyObject *module, PyObject *path) HANDLE hfile; BOOL close_file = TRUE; FILE_BASIC_INFO info = { 0 }; - DWORD fileType; path_t _path = PATH_T_INITIALIZE("isfile", "path", 0, 1); int result; From f6ce580b189d75978bf049409499aad98bcae667 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 25 Jan 2023 17:55:00 -0500 Subject: [PATCH 08/29] Add islink --- Lib/ntpath.py | 7 ++-- Lib/test/test_ntpath.py | 2 + Modules/clinic/posixmodule.c.h | 67 +++++++++++++++++++++++++++++++++- Modules/posixmodule.c | 67 +++++++++++++++++++++++++++++++++- 4 files changed, 138 insertions(+), 5 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 501ba0c84a111f..8d5916c2203046 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -852,11 +852,12 @@ def commonpath(paths): try: - # The isdir(), isfile(), and exists() implementations in genericpath use - # os.stat(). This is overkill on Windows. Use simpler builtin functions - # if they are available. + # The isdir(), isfile(), islink() and exists() implementations in + # genericpath use os.stat(). This is overkill on Windows. Use simpler + # builtin functions if they are available. from nt import _isdir as isdir from nt import _isfile as isfile + from nt import _islink as islink from nt import _exists as exists except ImportError: # Use genericpath.* as imported above diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index c5af67881e65f3..aad3751153d47a 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -905,6 +905,8 @@ def test_fast_paths_in_use(self): self.assertFalse(inspect.isfunction(os.path.isdir)) self.assertTrue(os.path.isfile is nt._isfile) self.assertFalse(inspect.isfunction(os.path.isfile)) + self.assertTrue(os.path.islink is nt._islink) + self.assertFalse(inspect.isfunction(os.path.islink)) self.assertTrue(os.path.exists is nt._exists) self.assertFalse(inspect.isfunction(os.path.exists)) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 3c7b6ac038452a..13683a7f4351f0 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -11152,6 +11152,67 @@ os__exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * #endif /* defined(MS_WINDOWS) */ +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__islink__doc__, +"_islink($module, /, path)\n" +"--\n" +"\n" +"Return True if path refers to an existing directory entry that is a symbolic link.\n" +"\n" +"Always False if symbolic links are not supported by the Python runtime."); + +#define OS__ISLINK_METHODDEF \ + {"_islink", _PyCFunction_CAST(os__islink), METH_FASTCALL|METH_KEYWORDS, os__islink__doc__}, + +static PyObject * +os__islink_impl(PyObject *module, PyObject *path); + +static PyObject * +os__islink(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(path), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"path", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_islink", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *path; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + path = args[0]; + return_value = os__islink_impl(module, path); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + #ifndef OS_TTYNAME_METHODDEF #define OS_TTYNAME_METHODDEF #endif /* !defined(OS_TTYNAME_METHODDEF) */ @@ -11759,4 +11820,8 @@ os__exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * #ifndef OS__EXISTS_METHODDEF #define OS__EXISTS_METHODDEF #endif /* !defined(OS__EXISTS_METHODDEF) */ -/*[clinic end generated code: output=378f9b4d00998b10 input=a9049054013a1b77]*/ + +#ifndef OS__ISLINK_METHODDEF + #define OS__ISLINK_METHODDEF +#endif /* !defined(OS__ISLINK_METHODDEF) */ +/*[clinic end generated code: output=bd2677b545dabe35 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 0ea1cf69c5247b..4d12bbd2b371a7 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -15143,6 +15143,70 @@ os__exists_impl(PyObject *module, PyObject *path) Py_RETURN_FALSE; } } + + +/*[clinic input] +os._islink + + path: 'O' + +Return True if path refers to an existing directory entry that is a symbolic link. + +Always False if symbolic links are not supported by the Python runtime. + +[clinic start generated code]*/ + +static PyObject * +os__islink_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=597174066981ca21 input=df8e74a37ac3c6dc]*/ +{ + HANDLE hfile; + BOOL close_file = TRUE; + FILE_ATTRIBUTE_TAG_INFO info = { 0 }; + path_t _path = PATH_T_INITIALIZE("islink", "path", 0, 1); + int result; + + if (!path_converter(path, &_path)) { + path_cleanup(&_path); + if (PyErr_ExceptionMatches(PyExc_ValueError)) { + PyErr_Clear(); + Py_RETURN_FALSE; + } else { + return NULL; + } + } + + Py_BEGIN_ALLOW_THREADS + if (_path.fd != -1) { + hfile = _Py_get_osfhandle_noraise(_path.fd); + close_file = FALSE; + } else { + hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + NULL); + } + if (hfile != INVALID_HANDLE_VALUE) { + if (GetFileInformationByHandleEx(hfile, FileAttributeTagInfo, &info, sizeof(info))) { + result = (info.ReparseTag == IO_REPARSE_TAG_SYMLINK); + } else { + result = 0; + } + if (close_file) { + CloseHandle(hfile); + } + } else { + result = 0; + } + Py_END_ALLOW_THREADS + + path_cleanup(&_path); + if (result) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } +} #endif @@ -15335,8 +15399,9 @@ static PyMethodDef posix_methods[] = { OS_SETNS_METHODDEF OS_UNSHARE_METHODDEF - OS__ISFILE_METHODDEF OS__ISDIR_METHODDEF + OS__ISFILE_METHODDEF + OS__ISLINK_METHODDEF OS__EXISTS_METHODDEF {NULL, NULL} /* Sentinel */ }; From 88c8b25f31b6e8b6fb806c1fcc43cffe5131624d Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 25 Jan 2023 17:59:44 -0500 Subject: [PATCH 09/29] Update CHANGELOG entry --- .../Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst b/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst index 2a00ad6cf7dd0c..a4c58ff2546d80 100644 --- a/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst +++ b/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst @@ -1,2 +1,3 @@ -The functions ``os.path.isdir``, ``os.path.isfile`` and ``os.path.exists`` are now -13% to 28% faster on Windows, by making fewer win32 API calls. +The functions ``os.path.isdir``, ``os.path.isfile``, ``os.path.islink`` and +``os.path.exists`` are now 13% to 28% faster on Windows, by making fewer win32 +API calls. From 39beb86d98725e74f663d94b5d348c9f10c0c18d Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Thu, 26 Jan 2023 09:51:00 -0500 Subject: [PATCH 10/29] Handle uncommon error cases --- Include/pyport.h | 4 +++ Lib/test/test_os.py | 1 + Modules/posixmodule.c | 72 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/Include/pyport.h b/Include/pyport.h index b1b2a74779691d..65c1e7285b59d9 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -247,6 +247,10 @@ typedef Py_ssize_t Py_ssize_clean_t; #define S_ISCHR(x) (((x) & S_IFMT) == S_IFCHR) #endif +#ifndef S_ISLNK +#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK) +#endif + #ifdef __cplusplus /* Move this down here since some C++ #include's don't like to be included inside an extern "C" */ diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 58e04dd1348fd1..bc478f4a6460fb 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -742,6 +742,7 @@ def test_access_denied(self): ) result = os.stat(fname) self.assertNotEqual(result.st_size, 0) + self.assertTrue(os.path.isfile(fname)) @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") def test_stat_block_device(self): diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 4d12bbd2b371a7..9595bf10adcb2a 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -14983,6 +14983,8 @@ os__isdir_impl(PyObject *module, PyObject *path) BOOL close_file = TRUE; FILE_BASIC_INFO info = { 0 }; path_t _path = PATH_T_INITIALIZE("isdir", "path", 0, 1); + STRUCT_STAT st; + DWORD error; int result; if (!path_converter(path, &_path)) { @@ -15010,7 +15012,21 @@ os__isdir_impl(PyObject *module, PyObject *path) } result = info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY; } else { - result = 0; + error = GetLastError(); + switch (error) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_CANT_ACCESS_FILE: + case ERROR_INVALID_PARAMETER: + if (win32_stat(_path.wide, &st)) { + result = 0; + } else { + result = S_ISDIR(st.st_mode); + } + break; + default: + result = 0; + } } Py_END_ALLOW_THREADS @@ -15043,6 +15059,8 @@ os__isfile_impl(PyObject *module, PyObject *path) BOOL close_file = TRUE; FILE_BASIC_INFO info = { 0 }; path_t _path = PATH_T_INITIALIZE("isfile", "path", 0, 1); + STRUCT_STAT st; + DWORD error; int result; if (!path_converter(path, &_path)) { @@ -15073,7 +15091,21 @@ os__isfile_impl(PyObject *module, PyObject *path) CloseHandle(hfile); } } else { - result = 0; + error = GetLastError(); + switch (error) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_CANT_ACCESS_FILE: + case ERROR_INVALID_PARAMETER: + if (win32_stat(_path.wide, &st)) { + result = 0; + } else { + result = S_ISREG(st.st_mode); + } + break; + default: + result = 0; + } } Py_END_ALLOW_THREADS @@ -15106,6 +15138,8 @@ os__exists_impl(PyObject *module, PyObject *path) HANDLE hfile; BOOL close_file = TRUE; path_t _path = PATH_T_INITIALIZE("exists", "path", 0, 1); + STRUCT_STAT st; + DWORD error; int result; if (!path_converter(path, &_path)) { @@ -15132,7 +15166,21 @@ os__exists_impl(PyObject *module, PyObject *path) CloseHandle(hfile); } } else { - result = 0; + error = GetLastError(); + switch (error) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_CANT_ACCESS_FILE: + case ERROR_INVALID_PARAMETER: + if (win32_stat(_path.wide, &st)) { + result = 0; + } else { + result = 1; + } + break; + default: + result = 0; + } } Py_END_ALLOW_THREADS @@ -15164,6 +15212,8 @@ os__islink_impl(PyObject *module, PyObject *path) BOOL close_file = TRUE; FILE_ATTRIBUTE_TAG_INFO info = { 0 }; path_t _path = PATH_T_INITIALIZE("islink", "path", 0, 1); + STRUCT_STAT st; + DWORD error; int result; if (!path_converter(path, &_path)) { @@ -15196,7 +15246,21 @@ os__islink_impl(PyObject *module, PyObject *path) CloseHandle(hfile); } } else { - result = 0; + error = GetLastError(); + switch (error) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_CANT_ACCESS_FILE: + case ERROR_INVALID_PARAMETER: + if (win32_stat(_path.wide, &st)) { + result = 0; + } else { + result = S_ISLNK(st.st_mode); + } + break; + default: + result = 0; + } } Py_END_ALLOW_THREADS From 9d4af5a134ca6dbe24ca90aa6a58513830ce741b Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Thu, 26 Jan 2023 10:07:32 -0500 Subject: [PATCH 11/29] Fix drive test --- Lib/test/test_ntpath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index aad3751153d47a..b56702f661eaaf 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -892,7 +892,7 @@ def test_isjunction(self): @unittest.skipIf(sys.platform != 'win32', "drive letters are a windows concept") def test_isfile_driveletter(self): - current_drive = os.path.splitdrive(os.path.abspath(__file__))[0] + "\\" + current_drive = "\\\\.\\" + os.path.splitdrive(os.path.abspath(__file__))[0] self.assertFalse(os.path.isfile(current_drive)) @unittest.skipIf(sys.platform != 'win32', "Fast paths are only for win32") From 1030d8a534d640808440f3e8f7a81f99927f267d Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Thu, 26 Jan 2023 13:04:00 -0500 Subject: [PATCH 12/29] Update Lib/test/test_ntpath.py Co-authored-by: Eryk Sun --- Lib/test/test_ntpath.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index b56702f661eaaf..05d94df1ae626c 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -892,8 +892,10 @@ def test_isjunction(self): @unittest.skipIf(sys.platform != 'win32', "drive letters are a windows concept") def test_isfile_driveletter(self): - current_drive = "\\\\.\\" + os.path.splitdrive(os.path.abspath(__file__))[0] - self.assertFalse(os.path.isfile(current_drive)) + drive = os.environ.get('SystemDrive') + if drive is None or len(drive) != 2 or drive[1] != ':': + raise unittest.SkipTest('SystemDrive is not defined or malformed') + self.assertFalse(os.path.isfile('\\\\.\\' + drive)) @unittest.skipIf(sys.platform != 'win32', "Fast paths are only for win32") @cpython_only From be6b592d72aa25a49bfac6125265284c136bb5b6 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Thu, 26 Jan 2023 13:04:40 -0500 Subject: [PATCH 13/29] Use STAT and LSTAT macros Co-authored-by: Eryk Sun --- Modules/posixmodule.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 9595bf10adcb2a..5b2ae0ea524f22 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -15018,7 +15018,7 @@ os__isdir_impl(PyObject *module, PyObject *path) case ERROR_SHARING_VIOLATION: case ERROR_CANT_ACCESS_FILE: case ERROR_INVALID_PARAMETER: - if (win32_stat(_path.wide, &st)) { + if (STAT(_path.wide, &st)) { result = 0; } else { result = S_ISDIR(st.st_mode); @@ -15097,7 +15097,7 @@ os__isfile_impl(PyObject *module, PyObject *path) case ERROR_SHARING_VIOLATION: case ERROR_CANT_ACCESS_FILE: case ERROR_INVALID_PARAMETER: - if (win32_stat(_path.wide, &st)) { + if (STAT(_path.wide, &st)) { result = 0; } else { result = S_ISREG(st.st_mode); @@ -15172,7 +15172,7 @@ os__exists_impl(PyObject *module, PyObject *path) case ERROR_SHARING_VIOLATION: case ERROR_CANT_ACCESS_FILE: case ERROR_INVALID_PARAMETER: - if (win32_stat(_path.wide, &st)) { + if (STAT(_path.wide, &st)) { result = 0; } else { result = 1; @@ -15252,7 +15252,7 @@ os__islink_impl(PyObject *module, PyObject *path) case ERROR_SHARING_VIOLATION: case ERROR_CANT_ACCESS_FILE: case ERROR_INVALID_PARAMETER: - if (win32_stat(_path.wide, &st)) { + if (LSTAT(_path.wide, &st)) { result = 0; } else { result = S_ISLNK(st.st_mode); From 0e465dc0daf8db601c4ba38ee6f4387dbb4fd153 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Thu, 26 Jan 2023 17:47:15 -0500 Subject: [PATCH 14/29] Fix and add more tests --- Lib/test/test_ntpath.py | 13 +++++++++++-- Lib/test/test_os.py | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index b56702f661eaaf..c57f69c583408c 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -892,8 +892,17 @@ def test_isjunction(self): @unittest.skipIf(sys.platform != 'win32', "drive letters are a windows concept") def test_isfile_driveletter(self): - current_drive = "\\\\.\\" + os.path.splitdrive(os.path.abspath(__file__))[0] - self.assertFalse(os.path.isfile(current_drive)) + drive = os.environ.get('SystemDrive') + if drive is None or len(drive) != 2 or drive[1] != ':': + raise unittest.SkipTest('SystemDrive is not defined or malformed') + self.assertFalse(os.path.isfile('\\\\.\\' + drive)) + + @unittest.skipIf(sys.platform != 'win32', "windows only") + def test_con_device(self): + self.assertFalse(os.path.isfile(r"\\.\CON")) + self.assertFalse(os.path.isdir(r"\\.\CON")) + self.assertFalse(os.path.islink(r"\\.\CON")) + self.assertTrue(os.path.exists(r"\\.\CON")) @unittest.skipIf(sys.platform != 'win32', "Fast paths are only for win32") @cpython_only diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index bc478f4a6460fb..387d2581c06fc6 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -2861,6 +2861,7 @@ def test_appexeclink(self): self.assertEqual(st, os.stat(alias)) self.assertFalse(stat.S_ISLNK(st.st_mode)) self.assertEqual(st.st_reparse_tag, stat.IO_REPARSE_TAG_APPEXECLINK) + self.assertTrue(os.path.isfile(alias)) # testing the first one we see is sufficient break else: From dcb951327dc2239d9df216d888ea3d6a1581edda Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 31 Jan 2023 12:03:10 -0500 Subject: [PATCH 15/29] Don't unnecessarily zero-out info --- Modules/posixmodule.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 5b2ae0ea524f22..542ce995b6caba 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -14981,7 +14981,7 @@ os__isdir_impl(PyObject *module, PyObject *path) { HANDLE hfile; BOOL close_file = TRUE; - FILE_BASIC_INFO info = { 0 }; + FILE_BASIC_INFO info; path_t _path = PATH_T_INITIALIZE("isdir", "path", 0, 1); STRUCT_STAT st; DWORD error; @@ -15006,11 +15006,14 @@ os__isdir_impl(PyObject *module, PyObject *path) OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); } if (hfile != INVALID_HANDLE_VALUE) { - GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, sizeof(info)); + if (GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, sizeof(info))) { + result = info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY; + } else { + result = 0; + } if (close_file) { CloseHandle(hfile); } - result = info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY; } else { error = GetLastError(); switch (error) { @@ -15057,7 +15060,7 @@ os__isfile_impl(PyObject *module, PyObject *path) { HANDLE hfile; BOOL close_file = TRUE; - FILE_BASIC_INFO info = { 0 }; + FILE_BASIC_INFO info; path_t _path = PATH_T_INITIALIZE("isfile", "path", 0, 1); STRUCT_STAT st; DWORD error; @@ -15210,7 +15213,7 @@ os__islink_impl(PyObject *module, PyObject *path) { HANDLE hfile; BOOL close_file = TRUE; - FILE_ATTRIBUTE_TAG_INFO info = { 0 }; + FILE_ATTRIBUTE_TAG_INFO info; path_t _path = PATH_T_INITIALIZE("islink", "path", 0, 1); STRUCT_STAT st; DWORD error; From 0d2985d029c87e0f85c9b5a16018a219e0cf91c6 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 31 Jan 2023 12:06:31 -0500 Subject: [PATCH 16/29] Fix spelling of Win32 --- .../next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst b/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst index a4c58ff2546d80..c61e9b90fb5373 100644 --- a/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst +++ b/Misc/NEWS.d/next/Windows/2023-01-25-11-33-54.gh-issue-101196.wAX_2g.rst @@ -1,3 +1,3 @@ The functions ``os.path.isdir``, ``os.path.isfile``, ``os.path.islink`` and -``os.path.exists`` are now 13% to 28% faster on Windows, by making fewer win32 +``os.path.exists`` are now 13% to 28% faster on Windows, by making fewer Win32 API calls. From 19018dc41651bbe314f1fe090e4bf1b4cd59bb4c Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 31 Jan 2023 13:42:19 -0500 Subject: [PATCH 17/29] PEP7 --- Modules/posixmodule.c | 78 +++++++++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 542ce995b6caba..874a0cf9e9bb1d 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -14992,7 +14992,8 @@ os__isdir_impl(PyObject *module, PyObject *path) if (PyErr_ExceptionMatches(PyExc_ValueError)) { PyErr_Clear(); Py_RETURN_FALSE; - } else { + } + else { return NULL; } } @@ -15001,20 +15002,25 @@ os__isdir_impl(PyObject *module, PyObject *path) if (_path.fd != -1) { hfile = _Py_get_osfhandle_noraise(_path.fd); close_file = FALSE; - } else { + } + else { hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); } if (hfile != INVALID_HANDLE_VALUE) { - if (GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, sizeof(info))) { + if (GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, + sizeof(info))) + { result = info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY; - } else { + } + else { result = 0; } if (close_file) { CloseHandle(hfile); } - } else { + } + else { error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: @@ -15023,7 +15029,8 @@ os__isdir_impl(PyObject *module, PyObject *path) case ERROR_INVALID_PARAMETER: if (STAT(_path.wide, &st)) { result = 0; - } else { + } + else { result = S_ISDIR(st.st_mode); } break; @@ -15036,7 +15043,8 @@ os__isdir_impl(PyObject *module, PyObject *path) path_cleanup(&_path); if (result) { Py_RETURN_TRUE; - } else { + } + else { Py_RETURN_FALSE; } } @@ -15071,7 +15079,8 @@ os__isfile_impl(PyObject *module, PyObject *path) if (PyErr_ExceptionMatches(PyExc_ValueError)) { PyErr_Clear(); Py_RETURN_FALSE; - } else { + } + else { return NULL; } } @@ -15080,20 +15089,25 @@ os__isfile_impl(PyObject *module, PyObject *path) if (_path.fd != -1) { hfile = _Py_get_osfhandle_noraise(_path.fd); close_file = FALSE; - } else { + } + else { hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); } if (hfile != INVALID_HANDLE_VALUE) { - if (GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, sizeof(info))) { + if (GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, + sizeof(info))) + { result = !(info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY); - } else { + } + else { result = 0; } if (close_file) { CloseHandle(hfile); } - } else { + } + else { error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: @@ -15102,7 +15116,8 @@ os__isfile_impl(PyObject *module, PyObject *path) case ERROR_INVALID_PARAMETER: if (STAT(_path.wide, &st)) { result = 0; - } else { + } + else { result = S_ISREG(st.st_mode); } break; @@ -15115,7 +15130,8 @@ os__isfile_impl(PyObject *module, PyObject *path) path_cleanup(&_path); if (result) { Py_RETURN_TRUE; - } else { + } + else { Py_RETURN_FALSE; } } @@ -15150,7 +15166,8 @@ os__exists_impl(PyObject *module, PyObject *path) if (PyErr_ExceptionMatches(PyExc_ValueError)) { PyErr_Clear(); Py_RETURN_FALSE; - } else { + } + else { return NULL; } } @@ -15159,7 +15176,8 @@ os__exists_impl(PyObject *module, PyObject *path) if (_path.fd != -1) { hfile = _Py_get_osfhandle_noraise(_path.fd); close_file = FALSE; - } else { + } + else { hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); } @@ -15168,7 +15186,8 @@ os__exists_impl(PyObject *module, PyObject *path) if (close_file) { CloseHandle(hfile); } - } else { + } + else { error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: @@ -15190,7 +15209,8 @@ os__exists_impl(PyObject *module, PyObject *path) path_cleanup(&_path); if (result) { Py_RETURN_TRUE; - } else { + } + else { Py_RETURN_FALSE; } } @@ -15224,7 +15244,8 @@ os__islink_impl(PyObject *module, PyObject *path) if (PyErr_ExceptionMatches(PyExc_ValueError)) { PyErr_Clear(); Py_RETURN_FALSE; - } else { + } + else { return NULL; } } @@ -15233,22 +15254,27 @@ os__islink_impl(PyObject *module, PyObject *path) if (_path.fd != -1) { hfile = _Py_get_osfhandle_noraise(_path.fd); close_file = FALSE; - } else { + } + else { hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); } if (hfile != INVALID_HANDLE_VALUE) { - if (GetFileInformationByHandleEx(hfile, FileAttributeTagInfo, &info, sizeof(info))) { + if (GetFileInformationByHandleEx(hfile, FileAttributeTagInfo, &info, + sizeof(info))) + { result = (info.ReparseTag == IO_REPARSE_TAG_SYMLINK); - } else { + } + else { result = 0; } if (close_file) { CloseHandle(hfile); } - } else { + } + else { error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: @@ -15257,7 +15283,8 @@ os__islink_impl(PyObject *module, PyObject *path) case ERROR_INVALID_PARAMETER: if (LSTAT(_path.wide, &st)) { result = 0; - } else { + } + else { result = S_ISLNK(st.st_mode); } break; @@ -15270,7 +15297,8 @@ os__islink_impl(PyObject *module, PyObject *path) path_cleanup(&_path); if (result) { Py_RETURN_TRUE; - } else { + } + else { Py_RETURN_FALSE; } } From 7583a1d96296fec658b02ea09c1913418b0d3d2f Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 31 Jan 2023 15:44:04 -0500 Subject: [PATCH 18/29] Reduce use of else --- Modules/posixmodule.c | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 874a0cf9e9bb1d..4b69841f8850ff 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -14993,9 +14993,7 @@ os__isdir_impl(PyObject *module, PyObject *path) PyErr_Clear(); Py_RETURN_FALSE; } - else { - return NULL; - } + return NULL; } Py_BEGIN_ALLOW_THREADS @@ -15044,9 +15042,7 @@ os__isdir_impl(PyObject *module, PyObject *path) if (result) { Py_RETURN_TRUE; } - else { - Py_RETURN_FALSE; - } + Py_RETURN_FALSE; } @@ -15080,9 +15076,7 @@ os__isfile_impl(PyObject *module, PyObject *path) PyErr_Clear(); Py_RETURN_FALSE; } - else { - return NULL; - } + return NULL; } Py_BEGIN_ALLOW_THREADS @@ -15131,9 +15125,7 @@ os__isfile_impl(PyObject *module, PyObject *path) if (result) { Py_RETURN_TRUE; } - else { - Py_RETURN_FALSE; - } + Py_RETURN_FALSE; } @@ -15167,9 +15159,7 @@ os__exists_impl(PyObject *module, PyObject *path) PyErr_Clear(); Py_RETURN_FALSE; } - else { - return NULL; - } + return NULL; } Py_BEGIN_ALLOW_THREADS @@ -15210,9 +15200,7 @@ os__exists_impl(PyObject *module, PyObject *path) if (result) { Py_RETURN_TRUE; } - else { - Py_RETURN_FALSE; - } + Py_RETURN_FALSE; } @@ -15245,9 +15233,7 @@ os__islink_impl(PyObject *module, PyObject *path) PyErr_Clear(); Py_RETURN_FALSE; } - else { - return NULL; - } + return NULL; } Py_BEGIN_ALLOW_THREADS @@ -15298,9 +15284,7 @@ os__islink_impl(PyObject *module, PyObject *path) if (result) { Py_RETURN_TRUE; } - else { - Py_RETURN_FALSE; - } + Py_RETURN_FALSE; } #endif From 30cf75420126016f6639eaaef6c5ec2f44394c93 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 31 Jan 2023 15:48:14 -0500 Subject: [PATCH 19/29] Remove variable declarations --- Modules/posixmodule.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 4b69841f8850ff..d30ddfbfee248e 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -14983,8 +14983,6 @@ os__isdir_impl(PyObject *module, PyObject *path) BOOL close_file = TRUE; FILE_BASIC_INFO info; path_t _path = PATH_T_INITIALIZE("isdir", "path", 0, 1); - STRUCT_STAT st; - DWORD error; int result; if (!path_converter(path, &_path)) { @@ -15019,6 +15017,8 @@ os__isdir_impl(PyObject *module, PyObject *path) } } else { + STRUCT_STAT st; + DWORD error; error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: @@ -15066,8 +15066,6 @@ os__isfile_impl(PyObject *module, PyObject *path) BOOL close_file = TRUE; FILE_BASIC_INFO info; path_t _path = PATH_T_INITIALIZE("isfile", "path", 0, 1); - STRUCT_STAT st; - DWORD error; int result; if (!path_converter(path, &_path)) { @@ -15102,6 +15100,8 @@ os__isfile_impl(PyObject *module, PyObject *path) } } else { + STRUCT_STAT st; + DWORD error; error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: @@ -15149,8 +15149,6 @@ os__exists_impl(PyObject *module, PyObject *path) HANDLE hfile; BOOL close_file = TRUE; path_t _path = PATH_T_INITIALIZE("exists", "path", 0, 1); - STRUCT_STAT st; - DWORD error; int result; if (!path_converter(path, &_path)) { @@ -15178,6 +15176,8 @@ os__exists_impl(PyObject *module, PyObject *path) } } else { + STRUCT_STAT st; + DWORD error; error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: @@ -15223,8 +15223,6 @@ os__islink_impl(PyObject *module, PyObject *path) BOOL close_file = TRUE; FILE_ATTRIBUTE_TAG_INFO info; path_t _path = PATH_T_INITIALIZE("islink", "path", 0, 1); - STRUCT_STAT st; - DWORD error; int result; if (!path_converter(path, &_path)) { @@ -15261,6 +15259,8 @@ os__islink_impl(PyObject *module, PyObject *path) } } else { + STRUCT_STAT st; + DWORD error; error = GetLastError(); switch (error) { case ERROR_ACCESS_DENIED: From c0991eca701465ec9cf5c033236195c72934471d Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 31 Jan 2023 15:54:56 -0500 Subject: [PATCH 20/29] Docstring improvements --- Modules/posixmodule.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index d30ddfbfee248e..dd298fcb77a733 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -15136,9 +15136,9 @@ os._exists Return True if path refers to an existing path or an open file descriptor. -Returns False for broken symbolic links. On some platforms, this function may +Return False for broken symbolic links. On some platforms, this function may return False if permission is not granted to execute os.stat() on the requested -file, even if the path physically exists. +file, even if the path exists. [clinic start generated code]*/ @@ -15211,7 +15211,7 @@ os._islink Return True if path refers to an existing directory entry that is a symbolic link. -Always False if symbolic links are not supported by the Python runtime. +Return False if symbolic links are not supported by the Python runtime. [clinic start generated code]*/ From 3400f0764b95a8425a23ffbba5cd49ed008b23cb Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 31 Jan 2023 15:55:16 -0500 Subject: [PATCH 21/29] No need for 'error' local variable --- Modules/posixmodule.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index dd298fcb77a733..4b8918d7aa001e 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -15018,9 +15018,7 @@ os__isdir_impl(PyObject *module, PyObject *path) } else { STRUCT_STAT st; - DWORD error; - error = GetLastError(); - switch (error) { + switch (GetLastError()) { case ERROR_ACCESS_DENIED: case ERROR_SHARING_VIOLATION: case ERROR_CANT_ACCESS_FILE: @@ -15101,9 +15099,7 @@ os__isfile_impl(PyObject *module, PyObject *path) } else { STRUCT_STAT st; - DWORD error; - error = GetLastError(); - switch (error) { + switch (GetLastError()) { case ERROR_ACCESS_DENIED: case ERROR_SHARING_VIOLATION: case ERROR_CANT_ACCESS_FILE: @@ -15177,9 +15173,7 @@ os__exists_impl(PyObject *module, PyObject *path) } else { STRUCT_STAT st; - DWORD error; - error = GetLastError(); - switch (error) { + switch (GetLastError()) { case ERROR_ACCESS_DENIED: case ERROR_SHARING_VIOLATION: case ERROR_CANT_ACCESS_FILE: @@ -15260,9 +15254,7 @@ os__islink_impl(PyObject *module, PyObject *path) } else { STRUCT_STAT st; - DWORD error; - error = GetLastError(); - switch (error) { + switch (GetLastError()) { case ERROR_ACCESS_DENIED: case ERROR_SHARING_VIOLATION: case ERROR_CANT_ACCESS_FILE: From cb7cea349d822ad659d5f840101462289b57d2a7 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 31 Jan 2023 17:20:39 -0500 Subject: [PATCH 22/29] Update generated code --- Modules/clinic/posixmodule.c.h | 8 ++++---- Modules/posixmodule.c | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 13683a7f4351f0..f52e10054f5792 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -11097,9 +11097,9 @@ PyDoc_STRVAR(os__exists__doc__, "\n" "Return True if path refers to an existing path or an open file descriptor.\n" "\n" -"Returns False for broken symbolic links. On some platforms, this function may\n" +"Return False for broken symbolic links. On some platforms, this function may\n" "return False if permission is not granted to execute os.stat() on the requested\n" -"file, even if the path physically exists."); +"file, even if the path exists."); #define OS__EXISTS_METHODDEF \ {"_exists", _PyCFunction_CAST(os__exists), METH_FASTCALL|METH_KEYWORDS, os__exists__doc__}, @@ -11160,7 +11160,7 @@ PyDoc_STRVAR(os__islink__doc__, "\n" "Return True if path refers to an existing directory entry that is a symbolic link.\n" "\n" -"Always False if symbolic links are not supported by the Python runtime."); +"Return False if symbolic links are not supported by the Python runtime."); #define OS__ISLINK_METHODDEF \ {"_islink", _PyCFunction_CAST(os__islink), METH_FASTCALL|METH_KEYWORDS, os__islink__doc__}, @@ -11824,4 +11824,4 @@ os__islink(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * #ifndef OS__ISLINK_METHODDEF #define OS__ISLINK_METHODDEF #endif /* !defined(OS__ISLINK_METHODDEF) */ -/*[clinic end generated code: output=bd2677b545dabe35 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=ad817f73e9707b32 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 4b8918d7aa001e..3ec1ade69b6617 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -15140,7 +15140,7 @@ file, even if the path exists. static PyObject * os__exists_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=0f0ba589f386f816 input=a6e67a9b92711692]*/ +/*[clinic end generated code: output=0f0ba589f386f816 input=4fb71ae480a2082c]*/ { HANDLE hfile; BOOL close_file = TRUE; @@ -15211,7 +15211,7 @@ Return False if symbolic links are not supported by the Python runtime. static PyObject * os__islink_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=597174066981ca21 input=df8e74a37ac3c6dc]*/ +/*[clinic end generated code: output=597174066981ca21 input=78c88fc9c0b64d3b]*/ { HANDLE hfile; BOOL close_file = TRUE; From 636886e0e9e8ac8c8ca911ff2cd8e247c4c89dc9 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 31 Jan 2023 17:20:46 -0500 Subject: [PATCH 23/29] PEP7 --- Modules/posixmodule.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 3ec1ade69b6617..150d3fc8384b71 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -15180,7 +15180,8 @@ os__exists_impl(PyObject *module, PyObject *path) case ERROR_INVALID_PARAMETER: if (STAT(_path.wide, &st)) { result = 0; - } else { + } + else { result = 1; } break; From 05c91651223d215e74dcb5a33a12528f299510f9 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Tue, 31 Jan 2023 17:20:51 -0500 Subject: [PATCH 24/29] Make docs consistent --- Doc/library/os.path.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst index 42bbe24830e6c1..4fe439a92cc8ab 100644 --- a/Doc/library/os.path.rst +++ b/Doc/library/os.path.rst @@ -129,10 +129,10 @@ the :mod:`glob` module.) .. function:: exists(path) Return ``True`` if *path* refers to an existing path or an open - file descriptor. Returns ``False`` for broken symbolic links. On + file descriptor. Return ``False`` for broken symbolic links. On some platforms, this function may return ``False`` if permission is not granted to execute :func:`os.stat` on the requested file, even - if the *path* physically exists. + if the *path* exists. .. versionchanged:: 3.3 *path* can now be an integer: ``True`` is returned if it is an @@ -278,7 +278,7 @@ the :mod:`glob` module.) .. function:: islink(path) Return ``True`` if *path* refers to an :func:`existing ` directory - entry that is a symbolic link. Always ``False`` if symbolic links are not + entry that is a symbolic link. Return ``False`` if symbolic links are not supported by the Python runtime. .. versionchanged:: 3.6 From ff6bca90d9cc1926fd3176b52790ee119ff6417f Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 1 Feb 2023 15:55:33 -0500 Subject: [PATCH 25/29] Revert docstrings to the equivalent Python ones Co-authored-by: Eryk Sun --- Modules/posixmodule.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 150d3fc8384b71..fa1770cd393d02 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -14968,10 +14968,7 @@ os._isdir path: 'O' -Return True if path is an existing directory. - -This follows symbolic links, so both islink() and isdir() can be true for the -same path. +Return true if the pathname refers to an existing directory. [clinic start generated code]*/ @@ -15049,10 +15046,7 @@ os._isfile path: 'O' -Return True if path is an existing file. - -This follows symbolic links, so both islink() and isfile() can be true for the -same path. +Test whether a path is a regular file [clinic start generated code]*/ @@ -15130,11 +15124,7 @@ os._exists path: 'O' -Return True if path refers to an existing path or an open file descriptor. - -Return False for broken symbolic links. On some platforms, this function may -return False if permission is not granted to execute os.stat() on the requested -file, even if the path exists. +Test whether a path exists. Returns False for broken symbolic links [clinic start generated code]*/ @@ -15204,9 +15194,7 @@ os._islink path: 'O' -Return True if path refers to an existing directory entry that is a symbolic link. - -Return False if symbolic links are not supported by the Python runtime. +Test whether a path is a symbolic link [clinic start generated code]*/ From 6d48808f97db559126ee1c56a1c3103f2f625ef6 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 1 Feb 2023 15:56:05 -0500 Subject: [PATCH 26/29] Revert docs changes --- Doc/library/os.path.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/os.path.rst b/Doc/library/os.path.rst index 66b24b590593b1..96bcb48ad7d126 100644 --- a/Doc/library/os.path.rst +++ b/Doc/library/os.path.rst @@ -129,10 +129,10 @@ the :mod:`glob` module.) .. function:: exists(path) Return ``True`` if *path* refers to an existing path or an open - file descriptor. Return ``False`` for broken symbolic links. On + file descriptor. Returns ``False`` for broken symbolic links. On some platforms, this function may return ``False`` if permission is not granted to execute :func:`os.stat` on the requested file, even - if the *path* exists. + if the *path* physically exists. .. versionchanged:: 3.3 *path* can now be an integer: ``True`` is returned if it is an @@ -278,7 +278,7 @@ the :mod:`glob` module.) .. function:: islink(path) Return ``True`` if *path* refers to an :func:`existing ` directory - entry that is a symbolic link. Return ``False`` if symbolic links are not + entry that is a symbolic link. Always ``False`` if symbolic links are not supported by the Python runtime. .. versionchanged:: 3.6 From c7128bc99146c4bb5f2780589838367df56d7b68 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 1 Feb 2023 16:10:22 -0500 Subject: [PATCH 27/29] Move islink to genericpath.py --- Lib/genericpath.py | 14 +++++++++++++- Lib/ntpath.py | 13 ------------- Lib/posixpath.py | 12 ------------ 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/Lib/genericpath.py b/Lib/genericpath.py index ce36451a3af01c..1bd5b3897c3af9 100644 --- a/Lib/genericpath.py +++ b/Lib/genericpath.py @@ -7,7 +7,7 @@ import stat __all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime', - 'getsize', 'isdir', 'isfile', 'samefile', 'sameopenfile', + 'getsize', 'isdir', 'isfile', 'islink', 'samefile', 'sameopenfile', 'samestat'] @@ -45,6 +45,18 @@ def isdir(s): return stat.S_ISDIR(st.st_mode) +# Is a path a symbolic link? +# This will always return false on systems where os.lstat doesn't exist. + +def islink(path): + """Test whether a path is a symbolic link""" + try: + st = os.lstat(path) + except (OSError, ValueError, AttributeError): + return False + return stat.S_ISLNK(st.st_mode) + + def getsize(filename): """Return the size of a file, reported by os.stat().""" return os.stat(filename).st_size diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 6257e5061ff2e0..56a8cfcaf96204 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -276,19 +276,6 @@ def dirname(p): """Returns the directory component of a pathname""" return split(p)[0] -# Is a path a symbolic link? -# This will always return false on systems where os.lstat doesn't exist. - -def islink(path): - """Test whether a path is a symbolic link. - This will always return false for Windows prior to 6.0. - """ - try: - st = os.lstat(path) - except (OSError, ValueError, AttributeError): - return False - return stat.S_ISLNK(st.st_mode) - # Is a path a junction? diff --git a/Lib/posixpath.py b/Lib/posixpath.py index 32b5d6e105dde9..e4f155e41a3221 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -187,18 +187,6 @@ def dirname(p): return head -# Is a path a symbolic link? -# This will always return false on systems where os.lstat doesn't exist. - -def islink(path): - """Test whether a path is a symbolic link""" - try: - st = os.lstat(path) - except (OSError, ValueError, AttributeError): - return False - return stat.S_ISLNK(st.st_mode) - - # Is a path a junction? def isjunction(path): From a72aba055dcc3bf1f2783a74f128b23d3843545a Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 1 Feb 2023 16:20:54 -0500 Subject: [PATCH 28/29] Regenerate clinic --- Modules/clinic/posixmodule.c.h | 22 +++++----------------- Modules/posixmodule.c | 8 ++++---- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index f52e10054f5792..3a32abfafe6058 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -10971,10 +10971,7 @@ PyDoc_STRVAR(os__isdir__doc__, "_isdir($module, /, path)\n" "--\n" "\n" -"Return True if path is an existing directory.\n" -"\n" -"This follows symbolic links, so both islink() and isdir() can be true for the\n" -"same path."); +"Return true if the pathname refers to an existing directory."); #define OS__ISDIR_METHODDEF \ {"_isdir", _PyCFunction_CAST(os__isdir), METH_FASTCALL|METH_KEYWORDS, os__isdir__doc__}, @@ -11033,10 +11030,7 @@ PyDoc_STRVAR(os__isfile__doc__, "_isfile($module, /, path)\n" "--\n" "\n" -"Return True if path is an existing file.\n" -"\n" -"This follows symbolic links, so both islink() and isfile() can be true for the\n" -"same path."); +"Test whether a path is a regular file"); #define OS__ISFILE_METHODDEF \ {"_isfile", _PyCFunction_CAST(os__isfile), METH_FASTCALL|METH_KEYWORDS, os__isfile__doc__}, @@ -11095,11 +11089,7 @@ PyDoc_STRVAR(os__exists__doc__, "_exists($module, /, path)\n" "--\n" "\n" -"Return True if path refers to an existing path or an open file descriptor.\n" -"\n" -"Return False for broken symbolic links. On some platforms, this function may\n" -"return False if permission is not granted to execute os.stat() on the requested\n" -"file, even if the path exists."); +"Test whether a path exists. Returns False for broken symbolic links"); #define OS__EXISTS_METHODDEF \ {"_exists", _PyCFunction_CAST(os__exists), METH_FASTCALL|METH_KEYWORDS, os__exists__doc__}, @@ -11158,9 +11148,7 @@ PyDoc_STRVAR(os__islink__doc__, "_islink($module, /, path)\n" "--\n" "\n" -"Return True if path refers to an existing directory entry that is a symbolic link.\n" -"\n" -"Return False if symbolic links are not supported by the Python runtime."); +"Test whether a path is a symbolic link"); #define OS__ISLINK_METHODDEF \ {"_islink", _PyCFunction_CAST(os__islink), METH_FASTCALL|METH_KEYWORDS, os__islink__doc__}, @@ -11824,4 +11812,4 @@ os__islink(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * #ifndef OS__ISLINK_METHODDEF #define OS__ISLINK_METHODDEF #endif /* !defined(OS__ISLINK_METHODDEF) */ -/*[clinic end generated code: output=ad817f73e9707b32 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=804a3fa6b5e7a575 input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index fa1770cd393d02..9f9ffda1bf4273 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -14974,7 +14974,7 @@ Return true if the pathname refers to an existing directory. static PyObject * os__isdir_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=e87264f004b1aa44 input=2b77ce7f601eb6bc]*/ +/*[clinic end generated code: output=e87264f004b1aa44 input=c5c5c56fe1d287b7]*/ { HANDLE hfile; BOOL close_file = TRUE; @@ -15052,7 +15052,7 @@ Test whether a path is a regular file static PyObject * os__isfile_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=45efe2f636a0226a input=e4026a0f974ee867]*/ +/*[clinic end generated code: output=45efe2f636a0226a input=93d9523950b7585f]*/ { HANDLE hfile; BOOL close_file = TRUE; @@ -15130,7 +15130,7 @@ Test whether a path exists. Returns False for broken symbolic links static PyObject * os__exists_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=0f0ba589f386f816 input=4fb71ae480a2082c]*/ +/*[clinic end generated code: output=0f0ba589f386f816 input=6aa7a07262d888f4]*/ { HANDLE hfile; BOOL close_file = TRUE; @@ -15200,7 +15200,7 @@ Test whether a path is a symbolic link static PyObject * os__islink_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=597174066981ca21 input=78c88fc9c0b64d3b]*/ +/*[clinic end generated code: output=597174066981ca21 input=e90f4913a45c8098]*/ { HANDLE hfile; BOOL close_file = TRUE; From aac93e4adcd67d66bd9679a74a811fee105a42da Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Fri, 3 Feb 2023 12:40:47 -0500 Subject: [PATCH 29/29] Rename and reorganize - _isdir -> _path_isdir etc. --- Lib/ntpath.py | 8 +- Lib/test/test_ntpath.py | 8 +- Modules/clinic/posixmodule.c.h | 506 ++++++++-------- Modules/posixmodule.c | 1029 ++++++++++++++++---------------- 4 files changed, 774 insertions(+), 777 deletions(-) diff --git a/Lib/ntpath.py b/Lib/ntpath.py index 56a8cfcaf96204..e93a5e69600973 100644 --- a/Lib/ntpath.py +++ b/Lib/ntpath.py @@ -860,10 +860,10 @@ def commonpath(paths): # The isdir(), isfile(), islink() and exists() implementations in # genericpath use os.stat(). This is overkill on Windows. Use simpler # builtin functions if they are available. - from nt import _isdir as isdir - from nt import _isfile as isfile - from nt import _islink as islink - from nt import _exists as exists + from nt import _path_isdir as isdir + from nt import _path_isfile as isfile + from nt import _path_islink as islink + from nt import _path_exists as exists except ImportError: # Use genericpath.* as imported above pass diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index 34dad9128d46a6..b32900697874b1 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -959,13 +959,13 @@ def test_fast_paths_in_use(self): # There are fast paths of these functions implemented in posixmodule.c. # Confirm that they are being used, and not the Python fallbacks in # genericpath.py. - self.assertTrue(os.path.isdir is nt._isdir) + self.assertTrue(os.path.isdir is nt._path_isdir) self.assertFalse(inspect.isfunction(os.path.isdir)) - self.assertTrue(os.path.isfile is nt._isfile) + self.assertTrue(os.path.isfile is nt._path_isfile) self.assertFalse(inspect.isfunction(os.path.isfile)) - self.assertTrue(os.path.islink is nt._islink) + self.assertTrue(os.path.islink is nt._path_islink) self.assertFalse(inspect.isfunction(os.path.islink)) - self.assertTrue(os.path.exists is nt._exists) + self.assertTrue(os.path.exists is nt._path_exists) self.assertFalse(inspect.isfunction(os.path.exists)) diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h index 3a32abfafe6058..5e04507ddd6917 100644 --- a/Modules/clinic/posixmodule.c.h +++ b/Modules/clinic/posixmodule.c.h @@ -1794,6 +1794,242 @@ os__path_splitroot(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py #endif /* defined(MS_WINDOWS) */ +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__path_isdir__doc__, +"_path_isdir($module, /, path)\n" +"--\n" +"\n" +"Return true if the pathname refers to an existing directory."); + +#define OS__PATH_ISDIR_METHODDEF \ + {"_path_isdir", _PyCFunction_CAST(os__path_isdir), METH_FASTCALL|METH_KEYWORDS, os__path_isdir__doc__}, + +static PyObject * +os__path_isdir_impl(PyObject *module, PyObject *path); + +static PyObject * +os__path_isdir(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(path), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"path", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_path_isdir", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *path; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + path = args[0]; + return_value = os__path_isdir_impl(module, path); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__path_isfile__doc__, +"_path_isfile($module, /, path)\n" +"--\n" +"\n" +"Test whether a path is a regular file"); + +#define OS__PATH_ISFILE_METHODDEF \ + {"_path_isfile", _PyCFunction_CAST(os__path_isfile), METH_FASTCALL|METH_KEYWORDS, os__path_isfile__doc__}, + +static PyObject * +os__path_isfile_impl(PyObject *module, PyObject *path); + +static PyObject * +os__path_isfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(path), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"path", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_path_isfile", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *path; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + path = args[0]; + return_value = os__path_isfile_impl(module, path); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__path_exists__doc__, +"_path_exists($module, /, path)\n" +"--\n" +"\n" +"Test whether a path exists. Returns False for broken symbolic links"); + +#define OS__PATH_EXISTS_METHODDEF \ + {"_path_exists", _PyCFunction_CAST(os__path_exists), METH_FASTCALL|METH_KEYWORDS, os__path_exists__doc__}, + +static PyObject * +os__path_exists_impl(PyObject *module, PyObject *path); + +static PyObject * +os__path_exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(path), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"path", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_path_exists", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *path; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + path = args[0]; + return_value = os__path_exists_impl(module, path); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + +#if defined(MS_WINDOWS) + +PyDoc_STRVAR(os__path_islink__doc__, +"_path_islink($module, /, path)\n" +"--\n" +"\n" +"Test whether a path is a symbolic link"); + +#define OS__PATH_ISLINK_METHODDEF \ + {"_path_islink", _PyCFunction_CAST(os__path_islink), METH_FASTCALL|METH_KEYWORDS, os__path_islink__doc__}, + +static PyObject * +os__path_islink_impl(PyObject *module, PyObject *path); + +static PyObject * +os__path_islink(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) + + #define NUM_KEYWORDS 1 + static struct { + PyGC_Head _this_is_not_used; + PyObject_VAR_HEAD + PyObject *ob_item[NUM_KEYWORDS]; + } _kwtuple = { + .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) + .ob_item = { &_Py_ID(path), }, + }; + #undef NUM_KEYWORDS + #define KWTUPLE (&_kwtuple.ob_base.ob_base) + + #else // !Py_BUILD_CORE + # define KWTUPLE NULL + #endif // !Py_BUILD_CORE + + static const char * const _keywords[] = {"path", NULL}; + static _PyArg_Parser _parser = { + .keywords = _keywords, + .fname = "_path_islink", + .kwtuple = KWTUPLE, + }; + #undef KWTUPLE + PyObject *argsbuf[1]; + PyObject *path; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); + if (!args) { + goto exit; + } + path = args[0]; + return_value = os__path_islink_impl(module, path); + +exit: + return return_value; +} + +#endif /* defined(MS_WINDOWS) */ + PyDoc_STRVAR(os__path_normpath__doc__, "_path_normpath($module, /, path)\n" "--\n" @@ -10965,242 +11201,6 @@ os_waitstatus_to_exitcode(PyObject *module, PyObject *const *args, Py_ssize_t na #endif /* (defined(WIFEXITED) || defined(MS_WINDOWS)) */ -#if defined(MS_WINDOWS) - -PyDoc_STRVAR(os__isdir__doc__, -"_isdir($module, /, path)\n" -"--\n" -"\n" -"Return true if the pathname refers to an existing directory."); - -#define OS__ISDIR_METHODDEF \ - {"_isdir", _PyCFunction_CAST(os__isdir), METH_FASTCALL|METH_KEYWORDS, os__isdir__doc__}, - -static PyObject * -os__isdir_impl(PyObject *module, PyObject *path); - -static PyObject * -os__isdir(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - - #define NUM_KEYWORDS 1 - static struct { - PyGC_Head _this_is_not_used; - PyObject_VAR_HEAD - PyObject *ob_item[NUM_KEYWORDS]; - } _kwtuple = { - .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(path), }, - }; - #undef NUM_KEYWORDS - #define KWTUPLE (&_kwtuple.ob_base.ob_base) - - #else // !Py_BUILD_CORE - # define KWTUPLE NULL - #endif // !Py_BUILD_CORE - - static const char * const _keywords[] = {"path", NULL}; - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "_isdir", - .kwtuple = KWTUPLE, - }; - #undef KWTUPLE - PyObject *argsbuf[1]; - PyObject *path; - - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); - if (!args) { - goto exit; - } - path = args[0]; - return_value = os__isdir_impl(module, path); - -exit: - return return_value; -} - -#endif /* defined(MS_WINDOWS) */ - -#if defined(MS_WINDOWS) - -PyDoc_STRVAR(os__isfile__doc__, -"_isfile($module, /, path)\n" -"--\n" -"\n" -"Test whether a path is a regular file"); - -#define OS__ISFILE_METHODDEF \ - {"_isfile", _PyCFunction_CAST(os__isfile), METH_FASTCALL|METH_KEYWORDS, os__isfile__doc__}, - -static PyObject * -os__isfile_impl(PyObject *module, PyObject *path); - -static PyObject * -os__isfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - - #define NUM_KEYWORDS 1 - static struct { - PyGC_Head _this_is_not_used; - PyObject_VAR_HEAD - PyObject *ob_item[NUM_KEYWORDS]; - } _kwtuple = { - .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(path), }, - }; - #undef NUM_KEYWORDS - #define KWTUPLE (&_kwtuple.ob_base.ob_base) - - #else // !Py_BUILD_CORE - # define KWTUPLE NULL - #endif // !Py_BUILD_CORE - - static const char * const _keywords[] = {"path", NULL}; - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "_isfile", - .kwtuple = KWTUPLE, - }; - #undef KWTUPLE - PyObject *argsbuf[1]; - PyObject *path; - - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); - if (!args) { - goto exit; - } - path = args[0]; - return_value = os__isfile_impl(module, path); - -exit: - return return_value; -} - -#endif /* defined(MS_WINDOWS) */ - -#if defined(MS_WINDOWS) - -PyDoc_STRVAR(os__exists__doc__, -"_exists($module, /, path)\n" -"--\n" -"\n" -"Test whether a path exists. Returns False for broken symbolic links"); - -#define OS__EXISTS_METHODDEF \ - {"_exists", _PyCFunction_CAST(os__exists), METH_FASTCALL|METH_KEYWORDS, os__exists__doc__}, - -static PyObject * -os__exists_impl(PyObject *module, PyObject *path); - -static PyObject * -os__exists(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - - #define NUM_KEYWORDS 1 - static struct { - PyGC_Head _this_is_not_used; - PyObject_VAR_HEAD - PyObject *ob_item[NUM_KEYWORDS]; - } _kwtuple = { - .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(path), }, - }; - #undef NUM_KEYWORDS - #define KWTUPLE (&_kwtuple.ob_base.ob_base) - - #else // !Py_BUILD_CORE - # define KWTUPLE NULL - #endif // !Py_BUILD_CORE - - static const char * const _keywords[] = {"path", NULL}; - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "_exists", - .kwtuple = KWTUPLE, - }; - #undef KWTUPLE - PyObject *argsbuf[1]; - PyObject *path; - - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); - if (!args) { - goto exit; - } - path = args[0]; - return_value = os__exists_impl(module, path); - -exit: - return return_value; -} - -#endif /* defined(MS_WINDOWS) */ - -#if defined(MS_WINDOWS) - -PyDoc_STRVAR(os__islink__doc__, -"_islink($module, /, path)\n" -"--\n" -"\n" -"Test whether a path is a symbolic link"); - -#define OS__ISLINK_METHODDEF \ - {"_islink", _PyCFunction_CAST(os__islink), METH_FASTCALL|METH_KEYWORDS, os__islink__doc__}, - -static PyObject * -os__islink_impl(PyObject *module, PyObject *path); - -static PyObject * -os__islink(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) -{ - PyObject *return_value = NULL; - #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) - - #define NUM_KEYWORDS 1 - static struct { - PyGC_Head _this_is_not_used; - PyObject_VAR_HEAD - PyObject *ob_item[NUM_KEYWORDS]; - } _kwtuple = { - .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) - .ob_item = { &_Py_ID(path), }, - }; - #undef NUM_KEYWORDS - #define KWTUPLE (&_kwtuple.ob_base.ob_base) - - #else // !Py_BUILD_CORE - # define KWTUPLE NULL - #endif // !Py_BUILD_CORE - - static const char * const _keywords[] = {"path", NULL}; - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "_islink", - .kwtuple = KWTUPLE, - }; - #undef KWTUPLE - PyObject *argsbuf[1]; - PyObject *path; - - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf); - if (!args) { - goto exit; - } - path = args[0]; - return_value = os__islink_impl(module, path); - -exit: - return return_value; -} - -#endif /* defined(MS_WINDOWS) */ - #ifndef OS_TTYNAME_METHODDEF #define OS_TTYNAME_METHODDEF #endif /* !defined(OS_TTYNAME_METHODDEF) */ @@ -11277,6 +11277,22 @@ os__islink(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * #define OS__PATH_SPLITROOT_METHODDEF #endif /* !defined(OS__PATH_SPLITROOT_METHODDEF) */ +#ifndef OS__PATH_ISDIR_METHODDEF + #define OS__PATH_ISDIR_METHODDEF +#endif /* !defined(OS__PATH_ISDIR_METHODDEF) */ + +#ifndef OS__PATH_ISFILE_METHODDEF + #define OS__PATH_ISFILE_METHODDEF +#endif /* !defined(OS__PATH_ISFILE_METHODDEF) */ + +#ifndef OS__PATH_EXISTS_METHODDEF + #define OS__PATH_EXISTS_METHODDEF +#endif /* !defined(OS__PATH_EXISTS_METHODDEF) */ + +#ifndef OS__PATH_ISLINK_METHODDEF + #define OS__PATH_ISLINK_METHODDEF +#endif /* !defined(OS__PATH_ISLINK_METHODDEF) */ + #ifndef OS_NICE_METHODDEF #define OS_NICE_METHODDEF #endif /* !defined(OS_NICE_METHODDEF) */ @@ -11796,20 +11812,4 @@ os__islink(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject * #ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF #define OS_WAITSTATUS_TO_EXITCODE_METHODDEF #endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */ - -#ifndef OS__ISDIR_METHODDEF - #define OS__ISDIR_METHODDEF -#endif /* !defined(OS__ISDIR_METHODDEF) */ - -#ifndef OS__ISFILE_METHODDEF - #define OS__ISFILE_METHODDEF -#endif /* !defined(OS__ISFILE_METHODDEF) */ - -#ifndef OS__EXISTS_METHODDEF - #define OS__EXISTS_METHODDEF -#endif /* !defined(OS__EXISTS_METHODDEF) */ - -#ifndef OS__ISLINK_METHODDEF - #define OS__ISLINK_METHODDEF -#endif /* !defined(OS__ISLINK_METHODDEF) */ -/*[clinic end generated code: output=804a3fa6b5e7a575 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=a3f76228b549e8ec input=a9049054013a1b77]*/ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 9f9ffda1bf4273..cba6cea48b77e1 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -4490,6 +4490,311 @@ os__path_splitroot_impl(PyObject *module, path_t *path) } +/*[clinic input] +os._path_isdir + + path: 'O' + +Return true if the pathname refers to an existing directory. + +[clinic start generated code]*/ + +static PyObject * +os__path_isdir_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=00faea0af309669d input=b1d2571cf7291aaf]*/ +{ + HANDLE hfile; + BOOL close_file = TRUE; + FILE_BASIC_INFO info; + path_t _path = PATH_T_INITIALIZE("isdir", "path", 0, 1); + int result; + + if (!path_converter(path, &_path)) { + path_cleanup(&_path); + if (PyErr_ExceptionMatches(PyExc_ValueError)) { + PyErr_Clear(); + Py_RETURN_FALSE; + } + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + if (_path.fd != -1) { + hfile = _Py_get_osfhandle_noraise(_path.fd); + close_file = FALSE; + } + else { + hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + } + if (hfile != INVALID_HANDLE_VALUE) { + if (GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, + sizeof(info))) + { + result = info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY; + } + else { + result = 0; + } + if (close_file) { + CloseHandle(hfile); + } + } + else { + STRUCT_STAT st; + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_CANT_ACCESS_FILE: + case ERROR_INVALID_PARAMETER: + if (STAT(_path.wide, &st)) { + result = 0; + } + else { + result = S_ISDIR(st.st_mode); + } + break; + default: + result = 0; + } + } + Py_END_ALLOW_THREADS + + path_cleanup(&_path); + if (result) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + + +/*[clinic input] +os._path_isfile + + path: 'O' + +Test whether a path is a regular file + +[clinic start generated code]*/ + +static PyObject * +os__path_isfile_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=2394ed7c4b5cfd85 input=de22d74960ade365]*/ +{ + HANDLE hfile; + BOOL close_file = TRUE; + FILE_BASIC_INFO info; + path_t _path = PATH_T_INITIALIZE("isfile", "path", 0, 1); + int result; + + if (!path_converter(path, &_path)) { + path_cleanup(&_path); + if (PyErr_ExceptionMatches(PyExc_ValueError)) { + PyErr_Clear(); + Py_RETURN_FALSE; + } + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + if (_path.fd != -1) { + hfile = _Py_get_osfhandle_noraise(_path.fd); + close_file = FALSE; + } + else { + hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + } + if (hfile != INVALID_HANDLE_VALUE) { + if (GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, + sizeof(info))) + { + result = !(info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } + else { + result = 0; + } + if (close_file) { + CloseHandle(hfile); + } + } + else { + STRUCT_STAT st; + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_CANT_ACCESS_FILE: + case ERROR_INVALID_PARAMETER: + if (STAT(_path.wide, &st)) { + result = 0; + } + else { + result = S_ISREG(st.st_mode); + } + break; + default: + result = 0; + } + } + Py_END_ALLOW_THREADS + + path_cleanup(&_path); + if (result) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + + +/*[clinic input] +os._path_exists + + path: 'O' + +Test whether a path exists. Returns False for broken symbolic links + +[clinic start generated code]*/ + +static PyObject * +os__path_exists_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=f508c3b35e13a249 input=380f77cdfa0f7ae8]*/ +{ + HANDLE hfile; + BOOL close_file = TRUE; + path_t _path = PATH_T_INITIALIZE("exists", "path", 0, 1); + int result; + + if (!path_converter(path, &_path)) { + path_cleanup(&_path); + if (PyErr_ExceptionMatches(PyExc_ValueError)) { + PyErr_Clear(); + Py_RETURN_FALSE; + } + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + if (_path.fd != -1) { + hfile = _Py_get_osfhandle_noraise(_path.fd); + close_file = FALSE; + } + else { + hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + } + if (hfile != INVALID_HANDLE_VALUE) { + result = 1; + if (close_file) { + CloseHandle(hfile); + } + } + else { + STRUCT_STAT st; + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_CANT_ACCESS_FILE: + case ERROR_INVALID_PARAMETER: + if (STAT(_path.wide, &st)) { + result = 0; + } + else { + result = 1; + } + break; + default: + result = 0; + } + } + Py_END_ALLOW_THREADS + + path_cleanup(&_path); + if (result) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + + +/*[clinic input] +os._path_islink + + path: 'O' + +Test whether a path is a symbolic link + +[clinic start generated code]*/ + +static PyObject * +os__path_islink_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=6d8640b1a390c054 input=38a3cb937ccf59bf]*/ +{ + HANDLE hfile; + BOOL close_file = TRUE; + FILE_ATTRIBUTE_TAG_INFO info; + path_t _path = PATH_T_INITIALIZE("islink", "path", 0, 1); + int result; + + if (!path_converter(path, &_path)) { + path_cleanup(&_path); + if (PyErr_ExceptionMatches(PyExc_ValueError)) { + PyErr_Clear(); + Py_RETURN_FALSE; + } + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + if (_path.fd != -1) { + hfile = _Py_get_osfhandle_noraise(_path.fd); + close_file = FALSE; + } + else { + hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + NULL); + } + if (hfile != INVALID_HANDLE_VALUE) { + if (GetFileInformationByHandleEx(hfile, FileAttributeTagInfo, &info, + sizeof(info))) + { + result = (info.ReparseTag == IO_REPARSE_TAG_SYMLINK); + } + else { + result = 0; + } + if (close_file) { + CloseHandle(hfile); + } + } + else { + STRUCT_STAT st; + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_CANT_ACCESS_FILE: + case ERROR_INVALID_PARAMETER: + if (LSTAT(_path.wide, &st)) { + result = 0; + } + else { + result = S_ISLNK(st.st_mode); + } + break; + default: + result = 0; + } + } + Py_END_ALLOW_THREADS + + path_cleanup(&_path); + if (result) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + #endif /* MS_WINDOWS */ @@ -14668,604 +14973,296 @@ PyOS_FSPath(PyObject *path) PyErr_Format(PyExc_TypeError, "expected %.200s.__fspath__() to return str or bytes, " "not %.200s", _PyType_Name(Py_TYPE(path)), - _PyType_Name(Py_TYPE(path_repr))); - Py_DECREF(path_repr); - return NULL; - } - - return path_repr; -} - -/*[clinic input] -os.fspath - - path: object - -Return the file system path representation of the object. - -If the object is str or bytes, then allow it to pass through as-is. If the -object defines __fspath__(), then return the result of that method. All other -types raise a TypeError. -[clinic start generated code]*/ - -static PyObject * -os_fspath_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=c3c3b78ecff2914f input=e357165f7b22490f]*/ -{ - return PyOS_FSPath(path); -} - -#ifdef HAVE_GETRANDOM_SYSCALL -/*[clinic input] -os.getrandom - - size: Py_ssize_t - flags: int=0 - -Obtain a series of random bytes. -[clinic start generated code]*/ - -static PyObject * -os_getrandom_impl(PyObject *module, Py_ssize_t size, int flags) -/*[clinic end generated code: output=b3a618196a61409c input=59bafac39c594947]*/ -{ - PyObject *bytes; - Py_ssize_t n; - - if (size < 0) { - errno = EINVAL; - return posix_error(); - } - - bytes = PyBytes_FromStringAndSize(NULL, size); - if (bytes == NULL) { - PyErr_NoMemory(); - return NULL; - } - - while (1) { - n = syscall(SYS_getrandom, - PyBytes_AS_STRING(bytes), - PyBytes_GET_SIZE(bytes), - flags); - if (n < 0 && errno == EINTR) { - if (PyErr_CheckSignals() < 0) { - goto error; - } - - /* getrandom() was interrupted by a signal: retry */ - continue; - } - break; - } - - if (n < 0) { - PyErr_SetFromErrno(PyExc_OSError); - goto error; - } - - if (n != size) { - _PyBytes_Resize(&bytes, n); - } - - return bytes; - -error: - Py_DECREF(bytes); - return NULL; -} -#endif /* HAVE_GETRANDOM_SYSCALL */ - -#ifdef MS_WINDOWS -/* bpo-36085: Helper functions for managing DLL search directories - * on win32 - */ - -typedef DLL_DIRECTORY_COOKIE (WINAPI *PAddDllDirectory)(PCWSTR newDirectory); -typedef BOOL (WINAPI *PRemoveDllDirectory)(DLL_DIRECTORY_COOKIE cookie); - -/*[clinic input] -os._add_dll_directory - - path: path_t - -Add a path to the DLL search path. - -This search path is used when resolving dependencies for imported -extension modules (the module itself is resolved through sys.path), -and also by ctypes. - -Returns an opaque value that may be passed to os.remove_dll_directory -to remove this directory from the search path. -[clinic start generated code]*/ - -static PyObject * -os__add_dll_directory_impl(PyObject *module, path_t *path) -/*[clinic end generated code: output=80b025daebb5d683 input=1de3e6c13a5808c8]*/ -{ - HMODULE hKernel32; - PAddDllDirectory AddDllDirectory; - DLL_DIRECTORY_COOKIE cookie = 0; - DWORD err = 0; - - if (PySys_Audit("os.add_dll_directory", "(O)", path->object) < 0) { - return NULL; - } - - /* For Windows 7, we have to load this. As this will be a fairly - infrequent operation, just do it each time. Kernel32 is always - loaded. */ - Py_BEGIN_ALLOW_THREADS - if (!(hKernel32 = GetModuleHandleW(L"kernel32")) || - !(AddDllDirectory = (PAddDllDirectory)GetProcAddress( - hKernel32, "AddDllDirectory")) || - !(cookie = (*AddDllDirectory)(path->wide))) { - err = GetLastError(); - } - Py_END_ALLOW_THREADS - - if (err) { - return win32_error_object_err("add_dll_directory", - path->object, err); - } - - return PyCapsule_New(cookie, "DLL directory cookie", NULL); -} - -/*[clinic input] -os._remove_dll_directory - - cookie: object - -Removes a path from the DLL search path. - -The parameter is an opaque value that was returned from -os.add_dll_directory. You can only remove directories that you added -yourself. -[clinic start generated code]*/ - -static PyObject * -os__remove_dll_directory_impl(PyObject *module, PyObject *cookie) -/*[clinic end generated code: output=594350433ae535bc input=c1d16a7e7d9dc5dc]*/ -{ - HMODULE hKernel32; - PRemoveDllDirectory RemoveDllDirectory; - DLL_DIRECTORY_COOKIE cookieValue; - DWORD err = 0; - - if (!PyCapsule_IsValid(cookie, "DLL directory cookie")) { - PyErr_SetString(PyExc_TypeError, - "Provided cookie was not returned from os.add_dll_directory"); - return NULL; - } - - cookieValue = (DLL_DIRECTORY_COOKIE)PyCapsule_GetPointer( - cookie, "DLL directory cookie"); - - /* For Windows 7, we have to load this. As this will be a fairly - infrequent operation, just do it each time. Kernel32 is always - loaded. */ - Py_BEGIN_ALLOW_THREADS - if (!(hKernel32 = GetModuleHandleW(L"kernel32")) || - !(RemoveDllDirectory = (PRemoveDllDirectory)GetProcAddress( - hKernel32, "RemoveDllDirectory")) || - !(*RemoveDllDirectory)(cookieValue)) { - err = GetLastError(); - } - Py_END_ALLOW_THREADS - - if (err) { - return win32_error_object_err("remove_dll_directory", - NULL, err); - } - - if (PyCapsule_SetName(cookie, NULL)) { + _PyType_Name(Py_TYPE(path_repr))); + Py_DECREF(path_repr); return NULL; } - Py_RETURN_NONE; + return path_repr; } -#endif - - -/* Only check if WIFEXITED is available: expect that it comes - with WEXITSTATUS, WIFSIGNALED, etc. - - os.waitstatus_to_exitcode() is implemented in C and not in Python, so - subprocess can safely call it during late Python finalization without - risking that used os attributes were set to None by finalize_modules(). */ -#if defined(WIFEXITED) || defined(MS_WINDOWS) /*[clinic input] -os.waitstatus_to_exitcode - - status as status_obj: object - -Convert a wait status to an exit code. - -On Unix: +os.fspath -* If WIFEXITED(status) is true, return WEXITSTATUS(status). -* If WIFSIGNALED(status) is true, return -WTERMSIG(status). -* Otherwise, raise a ValueError. + path: object -On Windows, return status shifted right by 8 bits. +Return the file system path representation of the object. -On Unix, if the process is being traced or if waitpid() was called with -WUNTRACED option, the caller must first check if WIFSTOPPED(status) is true. -This function must not be called if WIFSTOPPED(status) is true. +If the object is str or bytes, then allow it to pass through as-is. If the +object defines __fspath__(), then return the result of that method. All other +types raise a TypeError. [clinic start generated code]*/ static PyObject * -os_waitstatus_to_exitcode_impl(PyObject *module, PyObject *status_obj) -/*[clinic end generated code: output=db50b1b0ba3c7153 input=7fe2d7fdaea3db42]*/ +os_fspath_impl(PyObject *module, PyObject *path) +/*[clinic end generated code: output=c3c3b78ecff2914f input=e357165f7b22490f]*/ { -#ifndef MS_WINDOWS - int status = _PyLong_AsInt(status_obj); - if (status == -1 && PyErr_Occurred()) { - return NULL; - } - - WAIT_TYPE wait_status; - WAIT_STATUS_INT(wait_status) = status; - int exitcode; - if (WIFEXITED(wait_status)) { - exitcode = WEXITSTATUS(wait_status); - /* Sanity check to provide warranty on the function behavior. - It should not occur in practice */ - if (exitcode < 0) { - PyErr_Format(PyExc_ValueError, "invalid WEXITSTATUS: %i", exitcode); - return NULL; - } - } - else if (WIFSIGNALED(wait_status)) { - int signum = WTERMSIG(wait_status); - /* Sanity check to provide warranty on the function behavior. - It should not occurs in practice */ - if (signum <= 0) { - PyErr_Format(PyExc_ValueError, "invalid WTERMSIG: %i", signum); - return NULL; - } - exitcode = -signum; - } else if (WIFSTOPPED(wait_status)) { - /* Status only received if the process is being traced - or if waitpid() was called with WUNTRACED option. */ - int signum = WSTOPSIG(wait_status); - PyErr_Format(PyExc_ValueError, - "process stopped by delivery of signal %i", - signum); - return NULL; - } - else { - PyErr_Format(PyExc_ValueError, "invalid wait status: %i", status); - return NULL; - } - return PyLong_FromLong(exitcode); -#else - /* Windows implementation: see os.waitpid() implementation - which uses _cwait(). */ - unsigned long long status = PyLong_AsUnsignedLongLong(status_obj); - if (status == (unsigned long long)-1 && PyErr_Occurred()) { - return NULL; - } - - unsigned long long exitcode = (status >> 8); - /* ExitProcess() accepts an UINT type: - reject exit code which doesn't fit in an UINT */ - if (exitcode > UINT_MAX) { - PyErr_Format(PyExc_ValueError, "invalid exit code: %llu", exitcode); - return NULL; - } - return PyLong_FromUnsignedLong((unsigned long)exitcode); -#endif + return PyOS_FSPath(path); } -#endif - -#if defined(MS_WINDOWS) +#ifdef HAVE_GETRANDOM_SYSCALL /*[clinic input] -os._isdir - - path: 'O' +os.getrandom -Return true if the pathname refers to an existing directory. + size: Py_ssize_t + flags: int=0 +Obtain a series of random bytes. [clinic start generated code]*/ static PyObject * -os__isdir_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=e87264f004b1aa44 input=c5c5c56fe1d287b7]*/ +os_getrandom_impl(PyObject *module, Py_ssize_t size, int flags) +/*[clinic end generated code: output=b3a618196a61409c input=59bafac39c594947]*/ { - HANDLE hfile; - BOOL close_file = TRUE; - FILE_BASIC_INFO info; - path_t _path = PATH_T_INITIALIZE("isdir", "path", 0, 1); - int result; + PyObject *bytes; + Py_ssize_t n; - if (!path_converter(path, &_path)) { - path_cleanup(&_path); - if (PyErr_ExceptionMatches(PyExc_ValueError)) { - PyErr_Clear(); - Py_RETURN_FALSE; - } - return NULL; + if (size < 0) { + errno = EINVAL; + return posix_error(); } - Py_BEGIN_ALLOW_THREADS - if (_path.fd != -1) { - hfile = _Py_get_osfhandle_noraise(_path.fd); - close_file = FALSE; - } - else { - hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - } - if (hfile != INVALID_HANDLE_VALUE) { - if (GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, - sizeof(info))) - { - result = info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY; - } - else { - result = 0; - } - if (close_file) { - CloseHandle(hfile); - } + bytes = PyBytes_FromStringAndSize(NULL, size); + if (bytes == NULL) { + PyErr_NoMemory(); + return NULL; } - else { - STRUCT_STAT st; - switch (GetLastError()) { - case ERROR_ACCESS_DENIED: - case ERROR_SHARING_VIOLATION: - case ERROR_CANT_ACCESS_FILE: - case ERROR_INVALID_PARAMETER: - if (STAT(_path.wide, &st)) { - result = 0; - } - else { - result = S_ISDIR(st.st_mode); + + while (1) { + n = syscall(SYS_getrandom, + PyBytes_AS_STRING(bytes), + PyBytes_GET_SIZE(bytes), + flags); + if (n < 0 && errno == EINTR) { + if (PyErr_CheckSignals() < 0) { + goto error; } - break; - default: - result = 0; + + /* getrandom() was interrupted by a signal: retry */ + continue; } + break; } - Py_END_ALLOW_THREADS - path_cleanup(&_path); - if (result) { - Py_RETURN_TRUE; + if (n < 0) { + PyErr_SetFromErrno(PyExc_OSError); + goto error; } - Py_RETURN_FALSE; + + if (n != size) { + _PyBytes_Resize(&bytes, n); + } + + return bytes; + +error: + Py_DECREF(bytes); + return NULL; } +#endif /* HAVE_GETRANDOM_SYSCALL */ + +#ifdef MS_WINDOWS +/* bpo-36085: Helper functions for managing DLL search directories + * on win32 + */ +typedef DLL_DIRECTORY_COOKIE (WINAPI *PAddDllDirectory)(PCWSTR newDirectory); +typedef BOOL (WINAPI *PRemoveDllDirectory)(DLL_DIRECTORY_COOKIE cookie); /*[clinic input] -os._isfile +os._add_dll_directory - path: 'O' + path: path_t -Test whether a path is a regular file +Add a path to the DLL search path. + +This search path is used when resolving dependencies for imported +extension modules (the module itself is resolved through sys.path), +and also by ctypes. +Returns an opaque value that may be passed to os.remove_dll_directory +to remove this directory from the search path. [clinic start generated code]*/ static PyObject * -os__isfile_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=45efe2f636a0226a input=93d9523950b7585f]*/ -{ - HANDLE hfile; - BOOL close_file = TRUE; - FILE_BASIC_INFO info; - path_t _path = PATH_T_INITIALIZE("isfile", "path", 0, 1); - int result; - - if (!path_converter(path, &_path)) { - path_cleanup(&_path); - if (PyErr_ExceptionMatches(PyExc_ValueError)) { - PyErr_Clear(); - Py_RETURN_FALSE; - } - return NULL; - } - - Py_BEGIN_ALLOW_THREADS - if (_path.fd != -1) { - hfile = _Py_get_osfhandle_noraise(_path.fd); - close_file = FALSE; - } - else { - hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - } - if (hfile != INVALID_HANDLE_VALUE) { - if (GetFileInformationByHandleEx(hfile, FileBasicInfo, &info, - sizeof(info))) - { - result = !(info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY); - } - else { - result = 0; - } - if (close_file) { - CloseHandle(hfile); - } +os__add_dll_directory_impl(PyObject *module, path_t *path) +/*[clinic end generated code: output=80b025daebb5d683 input=1de3e6c13a5808c8]*/ +{ + HMODULE hKernel32; + PAddDllDirectory AddDllDirectory; + DLL_DIRECTORY_COOKIE cookie = 0; + DWORD err = 0; + + if (PySys_Audit("os.add_dll_directory", "(O)", path->object) < 0) { + return NULL; } - else { - STRUCT_STAT st; - switch (GetLastError()) { - case ERROR_ACCESS_DENIED: - case ERROR_SHARING_VIOLATION: - case ERROR_CANT_ACCESS_FILE: - case ERROR_INVALID_PARAMETER: - if (STAT(_path.wide, &st)) { - result = 0; - } - else { - result = S_ISREG(st.st_mode); - } - break; - default: - result = 0; - } + + /* For Windows 7, we have to load this. As this will be a fairly + infrequent operation, just do it each time. Kernel32 is always + loaded. */ + Py_BEGIN_ALLOW_THREADS + if (!(hKernel32 = GetModuleHandleW(L"kernel32")) || + !(AddDllDirectory = (PAddDllDirectory)GetProcAddress( + hKernel32, "AddDllDirectory")) || + !(cookie = (*AddDllDirectory)(path->wide))) { + err = GetLastError(); } Py_END_ALLOW_THREADS - path_cleanup(&_path); - if (result) { - Py_RETURN_TRUE; + if (err) { + return win32_error_object_err("add_dll_directory", + path->object, err); } - Py_RETURN_FALSE; -} + return PyCapsule_New(cookie, "DLL directory cookie", NULL); +} /*[clinic input] -os._exists +os._remove_dll_directory - path: 'O' + cookie: object -Test whether a path exists. Returns False for broken symbolic links +Removes a path from the DLL search path. +The parameter is an opaque value that was returned from +os.add_dll_directory. You can only remove directories that you added +yourself. [clinic start generated code]*/ static PyObject * -os__exists_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=0f0ba589f386f816 input=6aa7a07262d888f4]*/ +os__remove_dll_directory_impl(PyObject *module, PyObject *cookie) +/*[clinic end generated code: output=594350433ae535bc input=c1d16a7e7d9dc5dc]*/ { - HANDLE hfile; - BOOL close_file = TRUE; - path_t _path = PATH_T_INITIALIZE("exists", "path", 0, 1); - int result; + HMODULE hKernel32; + PRemoveDllDirectory RemoveDllDirectory; + DLL_DIRECTORY_COOKIE cookieValue; + DWORD err = 0; - if (!path_converter(path, &_path)) { - path_cleanup(&_path); - if (PyErr_ExceptionMatches(PyExc_ValueError)) { - PyErr_Clear(); - Py_RETURN_FALSE; - } + if (!PyCapsule_IsValid(cookie, "DLL directory cookie")) { + PyErr_SetString(PyExc_TypeError, + "Provided cookie was not returned from os.add_dll_directory"); return NULL; } + cookieValue = (DLL_DIRECTORY_COOKIE)PyCapsule_GetPointer( + cookie, "DLL directory cookie"); + + /* For Windows 7, we have to load this. As this will be a fairly + infrequent operation, just do it each time. Kernel32 is always + loaded. */ Py_BEGIN_ALLOW_THREADS - if (_path.fd != -1) { - hfile = _Py_get_osfhandle_noraise(_path.fd); - close_file = FALSE; - } - else { - hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - } - if (hfile != INVALID_HANDLE_VALUE) { - result = 1; - if (close_file) { - CloseHandle(hfile); - } - } - else { - STRUCT_STAT st; - switch (GetLastError()) { - case ERROR_ACCESS_DENIED: - case ERROR_SHARING_VIOLATION: - case ERROR_CANT_ACCESS_FILE: - case ERROR_INVALID_PARAMETER: - if (STAT(_path.wide, &st)) { - result = 0; - } - else { - result = 1; - } - break; - default: - result = 0; - } + if (!(hKernel32 = GetModuleHandleW(L"kernel32")) || + !(RemoveDllDirectory = (PRemoveDllDirectory)GetProcAddress( + hKernel32, "RemoveDllDirectory")) || + !(*RemoveDllDirectory)(cookieValue)) { + err = GetLastError(); } Py_END_ALLOW_THREADS - path_cleanup(&_path); - if (result) { - Py_RETURN_TRUE; + if (err) { + return win32_error_object_err("remove_dll_directory", + NULL, err); } - Py_RETURN_FALSE; + + if (PyCapsule_SetName(cookie, NULL)) { + return NULL; + } + + Py_RETURN_NONE; } +#endif + + +/* Only check if WIFEXITED is available: expect that it comes + with WEXITSTATUS, WIFSIGNALED, etc. + os.waitstatus_to_exitcode() is implemented in C and not in Python, so + subprocess can safely call it during late Python finalization without + risking that used os attributes were set to None by finalize_modules(). */ +#if defined(WIFEXITED) || defined(MS_WINDOWS) /*[clinic input] -os._islink +os.waitstatus_to_exitcode - path: 'O' + status as status_obj: object -Test whether a path is a symbolic link +Convert a wait status to an exit code. + +On Unix: + +* If WIFEXITED(status) is true, return WEXITSTATUS(status). +* If WIFSIGNALED(status) is true, return -WTERMSIG(status). +* Otherwise, raise a ValueError. + +On Windows, return status shifted right by 8 bits. +On Unix, if the process is being traced or if waitpid() was called with +WUNTRACED option, the caller must first check if WIFSTOPPED(status) is true. +This function must not be called if WIFSTOPPED(status) is true. [clinic start generated code]*/ static PyObject * -os__islink_impl(PyObject *module, PyObject *path) -/*[clinic end generated code: output=597174066981ca21 input=e90f4913a45c8098]*/ +os_waitstatus_to_exitcode_impl(PyObject *module, PyObject *status_obj) +/*[clinic end generated code: output=db50b1b0ba3c7153 input=7fe2d7fdaea3db42]*/ { - HANDLE hfile; - BOOL close_file = TRUE; - FILE_ATTRIBUTE_TAG_INFO info; - path_t _path = PATH_T_INITIALIZE("islink", "path", 0, 1); - int result; - - if (!path_converter(path, &_path)) { - path_cleanup(&_path); - if (PyErr_ExceptionMatches(PyExc_ValueError)) { - PyErr_Clear(); - Py_RETURN_FALSE; - } +#ifndef MS_WINDOWS + int status = _PyLong_AsInt(status_obj); + if (status == -1 && PyErr_Occurred()) { return NULL; } - Py_BEGIN_ALLOW_THREADS - if (_path.fd != -1) { - hfile = _Py_get_osfhandle_noraise(_path.fd); - close_file = FALSE; - } - else { - hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL, - OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, - NULL); - } - if (hfile != INVALID_HANDLE_VALUE) { - if (GetFileInformationByHandleEx(hfile, FileAttributeTagInfo, &info, - sizeof(info))) - { - result = (info.ReparseTag == IO_REPARSE_TAG_SYMLINK); - } - else { - result = 0; + WAIT_TYPE wait_status; + WAIT_STATUS_INT(wait_status) = status; + int exitcode; + if (WIFEXITED(wait_status)) { + exitcode = WEXITSTATUS(wait_status); + /* Sanity check to provide warranty on the function behavior. + It should not occur in practice */ + if (exitcode < 0) { + PyErr_Format(PyExc_ValueError, "invalid WEXITSTATUS: %i", exitcode); + return NULL; } - if (close_file) { - CloseHandle(hfile); + } + else if (WIFSIGNALED(wait_status)) { + int signum = WTERMSIG(wait_status); + /* Sanity check to provide warranty on the function behavior. + It should not occurs in practice */ + if (signum <= 0) { + PyErr_Format(PyExc_ValueError, "invalid WTERMSIG: %i", signum); + return NULL; } + exitcode = -signum; + } else if (WIFSTOPPED(wait_status)) { + /* Status only received if the process is being traced + or if waitpid() was called with WUNTRACED option. */ + int signum = WSTOPSIG(wait_status); + PyErr_Format(PyExc_ValueError, + "process stopped by delivery of signal %i", + signum); + return NULL; } else { - STRUCT_STAT st; - switch (GetLastError()) { - case ERROR_ACCESS_DENIED: - case ERROR_SHARING_VIOLATION: - case ERROR_CANT_ACCESS_FILE: - case ERROR_INVALID_PARAMETER: - if (LSTAT(_path.wide, &st)) { - result = 0; - } - else { - result = S_ISLNK(st.st_mode); - } - break; - default: - result = 0; - } + PyErr_Format(PyExc_ValueError, "invalid wait status: %i", status); + return NULL; + } + return PyLong_FromLong(exitcode); +#else + /* Windows implementation: see os.waitpid() implementation + which uses _cwait(). */ + unsigned long long status = PyLong_AsUnsignedLongLong(status_obj); + if (status == (unsigned long long)-1 && PyErr_Occurred()) { + return NULL; } - Py_END_ALLOW_THREADS - path_cleanup(&_path); - if (result) { - Py_RETURN_TRUE; + unsigned long long exitcode = (status >> 8); + /* ExitProcess() accepts an UINT type: + reject exit code which doesn't fit in an UINT */ + if (exitcode > UINT_MAX) { + PyErr_Format(PyExc_ValueError, "invalid exit code: %llu", exitcode); + return NULL; } - Py_RETURN_FALSE; + return PyLong_FromUnsignedLong((unsigned long)exitcode); +#endif } #endif @@ -15459,10 +15456,10 @@ static PyMethodDef posix_methods[] = { OS_SETNS_METHODDEF OS_UNSHARE_METHODDEF - OS__ISDIR_METHODDEF - OS__ISFILE_METHODDEF - OS__ISLINK_METHODDEF - OS__EXISTS_METHODDEF + OS__PATH_ISDIR_METHODDEF + OS__PATH_ISFILE_METHODDEF + OS__PATH_ISLINK_METHODDEF + OS__PATH_EXISTS_METHODDEF {NULL, NULL} /* Sentinel */ };