mirror of
https://github.com/python/cpython.git
synced 2025-11-01 18:51:43 +00:00
bpo-37834: Normalise handling of reparse points on Windows (GH-15231)
bpo-37834: Normalise handling of reparse points on Windows
* ntpath.realpath() and nt.stat() will traverse all supported reparse points (previously was mixed)
* nt.lstat() will let the OS traverse reparse points that are not name surrogates (previously would not traverse any reparse point)
* nt.[l]stat() will only set S_IFLNK for symlinks (previous behaviour)
* nt.readlink() will read destinations for symlinks and junction points only
bpo-1311: os.path.exists('nul') now returns True on Windows
* nt.stat('nul').st_mode is now S_IFCHR (previously was an error)
This commit is contained in:
parent
bcc446f525
commit
df2d4a6f3d
16 changed files with 477 additions and 240 deletions
|
|
@ -1625,6 +1625,7 @@ win32_wchdir(LPCWSTR path)
|
|||
*/
|
||||
#define HAVE_STAT_NSEC 1
|
||||
#define HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES 1
|
||||
#define HAVE_STRUCT_STAT_ST_REPARSE_TAG 1
|
||||
|
||||
static void
|
||||
find_data_to_file_info(WIN32_FIND_DATAW *pFileData,
|
||||
|
|
@ -1658,136 +1659,178 @@ attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *re
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL
|
||||
get_target_path(HANDLE hdl, wchar_t **target_path)
|
||||
{
|
||||
int buf_size, result_length;
|
||||
wchar_t *buf;
|
||||
|
||||
/* We have a good handle to the target, use it to determine
|
||||
the target path name (then we'll call lstat on it). */
|
||||
buf_size = GetFinalPathNameByHandleW(hdl, 0, 0,
|
||||
VOLUME_NAME_DOS);
|
||||
if(!buf_size)
|
||||
return FALSE;
|
||||
|
||||
buf = (wchar_t *)PyMem_RawMalloc((buf_size + 1) * sizeof(wchar_t));
|
||||
if (!buf) {
|
||||
SetLastError(ERROR_OUTOFMEMORY);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
result_length = GetFinalPathNameByHandleW(hdl,
|
||||
buf, buf_size, VOLUME_NAME_DOS);
|
||||
|
||||
if(!result_length) {
|
||||
PyMem_RawFree(buf);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
buf[result_length] = 0;
|
||||
|
||||
*target_path = buf;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static int
|
||||
win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result,
|
||||
BOOL traverse)
|
||||
{
|
||||
int code;
|
||||
HANDLE hFile, hFile2;
|
||||
BY_HANDLE_FILE_INFORMATION info;
|
||||
ULONG reparse_tag = 0;
|
||||
wchar_t *target_path;
|
||||
const wchar_t *dot;
|
||||
HANDLE hFile;
|
||||
BY_HANDLE_FILE_INFORMATION fileInfo;
|
||||
FILE_ATTRIBUTE_TAG_INFO tagInfo = { 0 };
|
||||
DWORD fileType, error;
|
||||
BOOL isUnhandledTag = FALSE;
|
||||
int retval = 0;
|
||||
|
||||
hFile = CreateFileW(
|
||||
path,
|
||||
FILE_READ_ATTRIBUTES, /* desired access */
|
||||
0, /* share mode */
|
||||
NULL, /* security attributes */
|
||||
OPEN_EXISTING,
|
||||
/* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */
|
||||
/* FILE_FLAG_OPEN_REPARSE_POINT does not follow the symlink.
|
||||
Because of this, calls like GetFinalPathNameByHandle will return
|
||||
the symlink path again and not the actual final path. */
|
||||
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS|
|
||||
FILE_FLAG_OPEN_REPARSE_POINT,
|
||||
NULL);
|
||||
DWORD access = FILE_READ_ATTRIBUTES;
|
||||
DWORD flags = FILE_FLAG_BACKUP_SEMANTICS; /* Allow opening directories. */
|
||||
if (!traverse) {
|
||||
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
|
||||
}
|
||||
|
||||
hFile = CreateFileW(path, access, 0, NULL, OPEN_EXISTING, flags, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
/* Either the target doesn't exist, or we don't have access to
|
||||
get a handle to it. If the former, we need to return an error.
|
||||
If the latter, we can use attributes_from_dir. */
|
||||
DWORD lastError = GetLastError();
|
||||
if (lastError != ERROR_ACCESS_DENIED &&
|
||||
lastError != ERROR_SHARING_VIOLATION)
|
||||
return -1;
|
||||
/* Could not get attributes on open file. Fall back to
|
||||
reading the directory. */
|
||||
if (!attributes_from_dir(path, &info, &reparse_tag))
|
||||
/* Very strange. This should not fail now */
|
||||
return -1;
|
||||
if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
if (traverse) {
|
||||
/* Should traverse, but could not open reparse point handle */
|
||||
SetLastError(lastError);
|
||||
/* Either the path doesn't exist, or the caller lacks access. */
|
||||
error = GetLastError();
|
||||
switch (error) {
|
||||
case ERROR_ACCESS_DENIED: /* Cannot sync or read attributes. */
|
||||
case ERROR_SHARING_VIOLATION: /* It's a paging file. */
|
||||
/* Try reading the parent directory. */
|
||||
if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) {
|
||||
/* Cannot read the parent directory. */
|
||||
SetLastError(error);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!GetFileInformationByHandle(hFile, &info)) {
|
||||
CloseHandle(hFile);
|
||||
return -1;
|
||||
}
|
||||
if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
if (!win32_get_reparse_tag(hFile, &reparse_tag)) {
|
||||
CloseHandle(hFile);
|
||||
return -1;
|
||||
}
|
||||
/* Close the outer open file handle now that we're about to
|
||||
reopen it with different flags. */
|
||||
if (!CloseHandle(hFile))
|
||||
return -1;
|
||||
|
||||
if (traverse) {
|
||||
/* In order to call GetFinalPathNameByHandle we need to open
|
||||
the file without the reparse handling flag set. */
|
||||
hFile2 = CreateFileW(
|
||||
path, FILE_READ_ATTRIBUTES, FILE_SHARE_READ,
|
||||
NULL, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS,
|
||||
NULL);
|
||||
if (hFile2 == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
if (!get_target_path(hFile2, &target_path)) {
|
||||
CloseHandle(hFile2);
|
||||
if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
if (traverse ||
|
||||
!IsReparseTagNameSurrogate(tagInfo.ReparseTag)) {
|
||||
/* The stat call has to traverse but cannot, so fail. */
|
||||
SetLastError(error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!CloseHandle(hFile2)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
code = win32_xstat_impl(target_path, result, FALSE);
|
||||
PyMem_RawFree(target_path);
|
||||
return code;
|
||||
}
|
||||
} else
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
_Py_attribute_data_to_stat(&info, reparse_tag, result);
|
||||
break;
|
||||
|
||||
/* Set S_IEXEC if it is an .exe, .bat, ... */
|
||||
dot = wcsrchr(path, '.');
|
||||
if (dot) {
|
||||
if (_wcsicmp(dot, L".bat") == 0 || _wcsicmp(dot, L".cmd") == 0 ||
|
||||
_wcsicmp(dot, L".exe") == 0 || _wcsicmp(dot, L".com") == 0)
|
||||
result->st_mode |= 0111;
|
||||
case ERROR_INVALID_PARAMETER:
|
||||
/* \\.\con requires read or write access. */
|
||||
hFile = CreateFileW(path, access | GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
|
||||
OPEN_EXISTING, flags, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
SetLastError(error);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERROR_CANT_ACCESS_FILE:
|
||||
/* bpo37834: open unhandled reparse points if traverse fails. */
|
||||
if (traverse) {
|
||||
traverse = FALSE;
|
||||
isUnhandledTag = TRUE;
|
||||
hFile = CreateFileW(path, access, 0, NULL, OPEN_EXISTING,
|
||||
flags | FILE_FLAG_OPEN_REPARSE_POINT, NULL);
|
||||
}
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
SetLastError(error);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
/* Handle types other than files on disk. */
|
||||
fileType = GetFileType(hFile);
|
||||
if (fileType != FILE_TYPE_DISK) {
|
||||
if (fileType == FILE_TYPE_UNKNOWN && GetLastError() != 0) {
|
||||
retval = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
DWORD fileAttributes = GetFileAttributesW(path);
|
||||
memset(result, 0, sizeof(*result));
|
||||
if (fileAttributes != INVALID_FILE_ATTRIBUTES &&
|
||||
fileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||
/* \\.\pipe\ or \\.\mailslot\ */
|
||||
result->st_mode = _S_IFDIR;
|
||||
} else if (fileType == FILE_TYPE_CHAR) {
|
||||
/* \\.\nul */
|
||||
result->st_mode = _S_IFCHR;
|
||||
} else if (fileType == FILE_TYPE_PIPE) {
|
||||
/* \\.\pipe\spam */
|
||||
result->st_mode = _S_IFIFO;
|
||||
}
|
||||
/* FILE_TYPE_UNKNOWN, e.g. \\.\mailslot\waitfor.exe\spam */
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Query the reparse tag, and traverse a non-link. */
|
||||
if (!traverse) {
|
||||
if (!GetFileInformationByHandleEx(hFile, FileAttributeTagInfo,
|
||||
&tagInfo, sizeof(tagInfo))) {
|
||||
/* Allow devices that do not support FileAttributeTagInfo. */
|
||||
switch (GetLastError()) {
|
||||
case ERROR_INVALID_PARAMETER:
|
||||
case ERROR_INVALID_FUNCTION:
|
||||
case ERROR_NOT_SUPPORTED:
|
||||
tagInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
||||
tagInfo.ReparseTag = 0;
|
||||
break;
|
||||
default:
|
||||
retval = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
} else if (tagInfo.FileAttributes &
|
||||
FILE_ATTRIBUTE_REPARSE_POINT) {
|
||||
if (IsReparseTagNameSurrogate(tagInfo.ReparseTag)) {
|
||||
if (isUnhandledTag) {
|
||||
/* Traversing previously failed for either this link
|
||||
or its target. */
|
||||
SetLastError(ERROR_CANT_ACCESS_FILE);
|
||||
retval = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
/* Traverse a non-link, but not if traversing already failed
|
||||
for an unhandled tag. */
|
||||
} else if (!isUnhandledTag) {
|
||||
CloseHandle(hFile);
|
||||
return win32_xstat_impl(path, result, TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!GetFileInformationByHandle(hFile, &fileInfo)) {
|
||||
switch (GetLastError()) {
|
||||
case ERROR_INVALID_PARAMETER:
|
||||
case ERROR_INVALID_FUNCTION:
|
||||
case ERROR_NOT_SUPPORTED:
|
||||
retval = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
/* Volumes and physical disks are block devices, e.g.
|
||||
\\.\C: and \\.\PhysicalDrive0. */
|
||||
memset(result, 0, sizeof(*result));
|
||||
result->st_mode = 0x6000; /* S_IFBLK */
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
_Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, result);
|
||||
|
||||
if (!(fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
/* Fix the file execute permissions. This hack sets S_IEXEC if
|
||||
the filename has an extension that is commonly used by files
|
||||
that CreateProcessW can execute. A real implementation calls
|
||||
GetSecurityInfo, OpenThreadToken/OpenProcessToken, and
|
||||
AccessCheck to check for generic read, write, and execute
|
||||
access. */
|
||||
const wchar_t *fileExtension = wcsrchr(path, '.');
|
||||
if (fileExtension) {
|
||||
if (_wcsicmp(fileExtension, L".exe") == 0 ||
|
||||
_wcsicmp(fileExtension, L".bat") == 0 ||
|
||||
_wcsicmp(fileExtension, L".cmd") == 0 ||
|
||||
_wcsicmp(fileExtension, L".com") == 0) {
|
||||
result->st_mode |= 0111;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int
|
||||
|
|
@ -1806,9 +1849,8 @@ win32_xstat(const wchar_t *path, struct _Py_stat_struct *result, BOOL traverse)
|
|||
default does not traverse symlinks and instead returns attributes for
|
||||
the symlink.
|
||||
|
||||
Therefore, win32_lstat will get the attributes traditionally, and
|
||||
win32_stat will first explicitly resolve the symlink target and then will
|
||||
call win32_lstat on that result. */
|
||||
Instead, we will open the file (which *does* traverse symlinks by default)
|
||||
and GetFileInformationByHandle(). */
|
||||
|
||||
static int
|
||||
win32_lstat(const wchar_t* path, struct _Py_stat_struct *result)
|
||||
|
|
@ -1876,6 +1918,9 @@ static PyStructSequence_Field stat_result_fields[] = {
|
|||
#endif
|
||||
#ifdef HAVE_STRUCT_STAT_ST_FSTYPE
|
||||
{"st_fstype", "Type of filesystem"},
|
||||
#endif
|
||||
#ifdef HAVE_STRUCT_STAT_ST_REPARSE_TAG
|
||||
{"st_reparse_tag", "Windows reparse tag"},
|
||||
#endif
|
||||
{0}
|
||||
};
|
||||
|
|
@ -1928,6 +1973,12 @@ static PyStructSequence_Field stat_result_fields[] = {
|
|||
#define ST_FSTYPE_IDX ST_FILE_ATTRIBUTES_IDX
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STRUCT_STAT_ST_REPARSE_TAG
|
||||
#define ST_REPARSE_TAG_IDX (ST_FSTYPE_IDX+1)
|
||||
#else
|
||||
#define ST_REPARSE_TAG_IDX ST_FSTYPE_IDX
|
||||
#endif
|
||||
|
||||
static PyStructSequence_Desc stat_result_desc = {
|
||||
"stat_result", /* name */
|
||||
stat_result__doc__, /* doc */
|
||||
|
|
@ -2155,6 +2206,10 @@ _pystat_fromstructstat(STRUCT_STAT *st)
|
|||
PyStructSequence_SET_ITEM(v, ST_FSTYPE_IDX,
|
||||
PyUnicode_FromString(st->st_fstype));
|
||||
#endif
|
||||
#ifdef HAVE_STRUCT_STAT_ST_REPARSE_TAG
|
||||
PyStructSequence_SET_ITEM(v, ST_REPARSE_TAG_IDX,
|
||||
PyLong_FromUnsignedLong(st->st_reparse_tag));
|
||||
#endif
|
||||
|
||||
if (PyErr_Occurred()) {
|
||||
Py_DECREF(v);
|
||||
|
|
@ -3877,8 +3932,9 @@ os__getfinalpathname_impl(PyObject *module, path_t *path)
|
|||
}
|
||||
|
||||
result = PyUnicode_FromWideChar(target_path, result_length);
|
||||
if (path->narrow)
|
||||
if (result && path->narrow) {
|
||||
Py_SETREF(result, PyUnicode_EncodeFSDefault(result));
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (target_path != buf) {
|
||||
|
|
@ -3888,44 +3944,6 @@ cleanup:
|
|||
return result;
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
os._isdir
|
||||
|
||||
path as arg: object
|
||||
/
|
||||
|
||||
Return true if the pathname refers to an existing directory.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
os__isdir(PyObject *module, PyObject *arg)
|
||||
/*[clinic end generated code: output=404f334d85d4bf25 input=36cb6785874d479e]*/
|
||||
{
|
||||
DWORD attributes;
|
||||
path_t path = PATH_T_INITIALIZE("_isdir", "path", 0, 0);
|
||||
|
||||
if (!path_converter(arg, &path)) {
|
||||
if (PyErr_ExceptionMatches(PyExc_ValueError)) {
|
||||
PyErr_Clear();
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
attributes = GetFileAttributesW(path.wide);
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
path_cleanup(&path);
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES)
|
||||
Py_RETURN_FALSE;
|
||||
|
||||
if (attributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
Py_RETURN_TRUE;
|
||||
else
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
|
||||
/*[clinic input]
|
||||
os._getvolumepathname
|
||||
|
|
@ -7796,11 +7814,10 @@ os_readlink_impl(PyObject *module, path_t *path, int dir_fd)
|
|||
return PyBytes_FromStringAndSize(buffer, length);
|
||||
#elif defined(MS_WINDOWS)
|
||||
DWORD n_bytes_returned;
|
||||
DWORD io_result;
|
||||
DWORD io_result = 0;
|
||||
HANDLE reparse_point_handle;
|
||||
char target_buffer[_Py_MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
|
||||
_Py_REPARSE_DATA_BUFFER *rdb = (_Py_REPARSE_DATA_BUFFER *)target_buffer;
|
||||
const wchar_t *print_name;
|
||||
PyObject *result;
|
||||
|
||||
/* First get a handle to the reparse point */
|
||||
|
|
@ -7813,42 +7830,51 @@ os_readlink_impl(PyObject *module, path_t *path, int dir_fd)
|
|||
OPEN_EXISTING,
|
||||
FILE_FLAG_OPEN_REPARSE_POINT|FILE_FLAG_BACKUP_SEMANTICS,
|
||||
0);
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (reparse_point_handle == INVALID_HANDLE_VALUE) {
|
||||
return path_error(path);
|
||||
if (reparse_point_handle != INVALID_HANDLE_VALUE) {
|
||||
/* New call DeviceIoControl to read the reparse point */
|
||||
io_result = DeviceIoControl(
|
||||
reparse_point_handle,
|
||||
FSCTL_GET_REPARSE_POINT,
|
||||
0, 0, /* in buffer */
|
||||
target_buffer, sizeof(target_buffer),
|
||||
&n_bytes_returned,
|
||||
0 /* we're not using OVERLAPPED_IO */
|
||||
);
|
||||
CloseHandle(reparse_point_handle);
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
/* New call DeviceIoControl to read the reparse point */
|
||||
io_result = DeviceIoControl(
|
||||
reparse_point_handle,
|
||||
FSCTL_GET_REPARSE_POINT,
|
||||
0, 0, /* in buffer */
|
||||
target_buffer, sizeof(target_buffer),
|
||||
&n_bytes_returned,
|
||||
0 /* we're not using OVERLAPPED_IO */
|
||||
);
|
||||
CloseHandle(reparse_point_handle);
|
||||
Py_END_ALLOW_THREADS
|
||||
|
||||
if (io_result == 0) {
|
||||
return path_error(path);
|
||||
}
|
||||
|
||||
if (rdb->ReparseTag != IO_REPARSE_TAG_SYMLINK)
|
||||
wchar_t *name = NULL;
|
||||
Py_ssize_t nameLen = 0;
|
||||
if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK)
|
||||
{
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"not a symbolic link");
|
||||
return NULL;
|
||||
name = (wchar_t *)((char*)rdb->SymbolicLinkReparseBuffer.PathBuffer +
|
||||
rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset);
|
||||
nameLen = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(wchar_t);
|
||||
}
|
||||
print_name = (wchar_t *)((char*)rdb->SymbolicLinkReparseBuffer.PathBuffer +
|
||||
rdb->SymbolicLinkReparseBuffer.PrintNameOffset);
|
||||
|
||||
result = PyUnicode_FromWideChar(print_name,
|
||||
rdb->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(wchar_t));
|
||||
if (path->narrow) {
|
||||
Py_SETREF(result, PyUnicode_EncodeFSDefault(result));
|
||||
else if (rdb->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)
|
||||
{
|
||||
name = (wchar_t *)((char*)rdb->MountPointReparseBuffer.PathBuffer +
|
||||
rdb->MountPointReparseBuffer.SubstituteNameOffset);
|
||||
nameLen = rdb->MountPointReparseBuffer.SubstituteNameLength / sizeof(wchar_t);
|
||||
}
|
||||
else
|
||||
{
|
||||
PyErr_SetString(PyExc_ValueError, "not a symbolic link");
|
||||
}
|
||||
if (name) {
|
||||
if (nameLen > 4 && wcsncmp(name, L"\\??\\", 4) == 0) {
|
||||
/* Our buffer is mutable, so this is okay */
|
||||
name[1] = L'\\';
|
||||
}
|
||||
result = PyUnicode_FromWideChar(name, nameLen);
|
||||
if (path->narrow) {
|
||||
Py_SETREF(result, PyUnicode_EncodeFSDefault(result));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
#endif
|
||||
|
|
@ -13647,7 +13673,6 @@ static PyMethodDef posix_methods[] = {
|
|||
OS_PATHCONF_METHODDEF
|
||||
OS_ABORT_METHODDEF
|
||||
OS__GETFULLPATHNAME_METHODDEF
|
||||
OS__ISDIR_METHODDEF
|
||||
OS__GETDISKUSAGE_METHODDEF
|
||||
OS__GETFINALPATHNAME_METHODDEF
|
||||
OS__GETVOLUMEPATHNAME_METHODDEF
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue