Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Lib/ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,13 +892,14 @@ def commonpath(paths):


try:
# The isdir(), isfile(), islink() and exists() implementations in
# genericpath use os.stat(). This is overkill on Windows. Use simpler
# The isdir(), isfile(), islink(), exists() and lexists() implementations
# in genericpath use os.stat(). This is overkill on Windows. Use simpler
# builtin functions if they are available.
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
from nt import _path_lexists as lexists
except ImportError:
# Use genericpath.* as imported above
pass
Expand Down
33 changes: 18 additions & 15 deletions Lib/test/test_genericpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,31 +128,34 @@ def test_filetime(self):
)

def test_exists(self):
def check_exists(filename, expected):
Comment thread
nineteendo marked this conversation as resolved.
Resolved
bfilename = os.fsencode(filename)
self.assertIs(self.pathmodule.exists(filename), expected)
self.assertIs(self.pathmodule.exists(bfilename), expected)

def check_lexists(filename, expected):
bfilename = os.fsencode(filename)
self.assertIs(self.pathmodule.lexists(filename), expected)
self.assertIs(self.pathmodule.lexists(bfilename), expected)

filename = os_helper.TESTFN
bfilename = os.fsencode(filename)
self.addCleanup(os_helper.unlink, filename)

self.assertIs(self.pathmodule.exists(filename), False)
self.assertIs(self.pathmodule.exists(bfilename), False)
check_exists(filename, False)
check_lexists(filename, False)

create_file(filename)

self.assertIs(self.pathmodule.exists(filename), True)
self.assertIs(self.pathmodule.exists(bfilename), True)

check_exists(filename, True)
self.assertIs(self.pathmodule.exists(filename + '\udfff'), False)
self.assertIs(self.pathmodule.exists(bfilename + b'\xff'), False)
self.assertIs(self.pathmodule.exists(filename + '\x00'), False)
self.assertIs(self.pathmodule.exists(bfilename + b'\x00'), False)

if self.pathmodule is not genericpath:
self.assertIs(self.pathmodule.lexists(filename), True)
self.assertIs(self.pathmodule.lexists(bfilename), True)
check_exists(filename + '\x00', False)

self.assertIs(self.pathmodule.lexists(filename + '\udfff'), False)
self.assertIs(self.pathmodule.lexists(bfilename + b'\xff'), False)
self.assertIs(self.pathmodule.lexists(filename + '\x00'), False)
self.assertIs(self.pathmodule.lexists(bfilename + b'\x00'), False)
check_lexists(filename, True)
self.assertIs(self.pathmodule.lexists(filename + '\udfff'), False)
self.assertIs(self.pathmodule.lexists(bfilename + b'\xff'), False)
check_lexists(filename + '\x00', False)

@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
@unittest.skipIf(is_emscripten, "Emscripten pipe fds have no stat")
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_ntpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,8 @@ def test_fast_paths_in_use(self):
self.assertFalse(inspect.isfunction(os.path.islink))
self.assertTrue(os.path.exists is nt._path_exists)
self.assertFalse(inspect.isfunction(os.path.exists))
self.assertTrue(os.path.lexists is nt._path_lexists)
self.assertFalse(inspect.isfunction(os.path.lexists))

@unittest.skipIf(os.name != 'nt', "Dev Drives only exist on Win32")
def test_isdevdrive(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Speedup :func:`os.path.lexists` on Windows.
65 changes: 64 additions & 1 deletion Modules/clinic/posixmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

167 changes: 118 additions & 49 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -5280,25 +5280,13 @@ os__path_isfile_impl(PyObject *module, PyObject *path)
}


/*[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]*/
nt_exists(PyObject *path, int follow_symlinks)
{
HANDLE hfile;
BOOL close_file = TRUE;
path_t _path = PATH_T_INITIALIZE("exists", "path", 0, 1);
int result;
BOOL slow_path = TRUE;
FILE_STAT_BASIC_INFORMATION statInfo;
HANDLE hfile;
BOOL traverse = follow_symlinks;
int result = 0;

if (!path_converter(path, &_path)) {
path_cleanup(&_path);
Expand All @@ -5310,49 +5298,95 @@ os__path_exists_impl(PyObject *module, PyObject *path)
}

Py_BEGIN_ALLOW_THREADS
if (_path.wide) {
if (_path.fd != -1) {
hfile = _Py_get_osfhandle_noraise(_path.fd);
if (hfile != INVALID_HANDLE_VALUE) {
result = 1;
}
}
else if (_path.wide) {
BOOL slow_path = TRUE;
FILE_STAT_BASIC_INFORMATION statInfo;
if (_Py_GetFileInformationByName(_path.wide, FileStatBasicByNameInfo,
&statInfo, sizeof(statInfo))) {
if (!(statInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
&statInfo, sizeof(statInfo)))
{
if (!(statInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
!follow_symlinks &&
IsReparseTagNameSurrogate(statInfo.ReparseTag))
{
slow_path = FALSE;
result = 1;
}
} else if (_Py_GetFileInformationByName_ErrorIsTrustworthy(GetLastError())) {
slow_path = FALSE;
result = 0;
}
}
if (slow_path) {
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 {
// reparse point but not name-surrogate
traverse = TRUE;
}
}
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 if (_Py_GetFileInformationByName_ErrorIsTrustworthy(
GetLastError()))
{
slow_path = FALSE;
}
if (slow_path) {
BOOL traverse = follow_symlinks;
if (!traverse) {
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) {
FILE_ATTRIBUTE_TAG_INFO info;
if (GetFileInformationByHandleEx(hfile,
FileAttributeTagInfo, &info, sizeof(info)))
{
if (!(info.FileAttributes &
FILE_ATTRIBUTE_REPARSE_POINT) ||
IsReparseTagNameSurrogate(info.ReparseTag))
{
result = 1;
}
else {
// reparse point but not name-surrogate
traverse = TRUE;
}
}
else {
// device or legacy filesystem
result = 1;
}
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 = 1;
}
}
}
}
if (traverse) {
hfile = CreateFileW(_path.wide, FILE_READ_ATTRIBUTES, 0, NULL,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
if (hfile != INVALID_HANDLE_VALUE) {
CloseHandle(hfile);
result = 1;
}
break;
default:
result = 0;
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 = 1;
}
}
}
}
}
}
Expand All @@ -5366,6 +5400,40 @@ os__path_exists_impl(PyObject *module, PyObject *path)
}


/*[clinic input]
os._path_exists

path: 'O'
Comment thread
nineteendo marked this conversation as resolved.
Resolved

Test whether a path exists. Returns False for broken symbolic links
Comment thread
nineteendo marked this conversation as resolved.
Resolved

[clinic start generated code]*/

static PyObject *
os__path_exists_impl(PyObject *module, PyObject *path)
/*[clinic end generated code: output=f508c3b35e13a249 input=380f77cdfa0f7ae8]*/
{
return nt_exists(path, 1);
}


/*[clinic input]
os._path_lexists

path: 'O'
Comment thread
nineteendo marked this conversation as resolved.
Resolved

Test whether a path exists. Returns True for broken symbolic links
Comment thread
nineteendo marked this conversation as resolved.
Resolved

[clinic start generated code]*/

static PyObject *
os__path_lexists_impl(PyObject *module, PyObject *path)
/*[clinic end generated code: output=b9a42a50b1df6651 input=5f14942c64196ba2]*/
{
return nt_exists(path, 0);
}


/*[clinic input]
os._path_islink

Expand Down Expand Up @@ -16843,6 +16911,7 @@ static PyMethodDef posix_methods[] = {
OS__PATH_ISFILE_METHODDEF
OS__PATH_ISLINK_METHODDEF
OS__PATH_EXISTS_METHODDEF
OS__PATH_LEXISTS_METHODDEF

OS__SUPPORTS_VIRTUAL_TERMINAL_METHODDEF
{NULL, NULL} /* Sentinel */
Expand Down