diff --git a/Lib/test/support.py b/Lib/test/support.py --- a/Lib/test/support.py +++ b/Lib/test/support.py @@ -1462,7 +1462,7 @@ try: os.symlink(TESTFN, TESTFN + "can_symlink") can = True - except (OSError, NotImplementedError): + except (OSError, NotImplementedError, AttributeError): can = False _can_symlink = can return can diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -1244,6 +1244,34 @@ self.assertNotEqual(os.lstat(link), os.stat(link)) +@support.skip_unless_symlink +class SymlinkTests(unittest.TestCase): + def test_relative_symlink(self): + os.mkdir(support.TESTFN) + try: + level1 = support.TESTFN + level2 = os.path.join(level1, "deeper") + os.mkdir(level2) + src1 = os.path.join(level1, "src") + src2 = os.path.join(level2, "src") + open(src1, "w").write("x") + os.mkdir(src2) + old = os.getcwd() + try: + lnk1 = os.path.join(level1, "lnk") + os.symlink("./src", lnk1) # relative symlink to src1 + os.chdir(level1) + self.assertEqual(os.stat(src1), os.stat(lnk1)) + self.assertNotEqual(os.stat(src2), os.stat(lnk1)) + os.chdir(level2) + self.assertEqual(os.stat(src1), os.stat(lnk1)) + self.assertNotEqual(os.stat(src2), os.stat(lnk1)) + finally: + os.chdir(old) + finally: + shutil.rmtree(support.TESTFN) + + class FSEncodingTests(unittest.TestCase): def test_nop(self): self.assertEqual(os.fsencode(b'abc\xff'), b'abc\xff') @@ -1296,6 +1324,7 @@ Pep383Tests, Win32KillTests, Win32SymlinkTests, + SymlinkTests, FSEncodingTests, PidTests, LoginTests, diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -442,98 +442,6 @@ #define _PyVerify_fd_dup2(A, B) (1) #endif -#ifdef MS_WINDOWS -/* The following structure was copied from - http://msdn.microsoft.com/en-us/library/ms791514.aspx as the required - include doesn't seem to be present in the Windows SDK (at least as included - with Visual Studio Express). */ -typedef struct _REPARSE_DATA_BUFFER { - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - union { - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - ULONG Flags; - WCHAR PathBuffer[1]; - } SymbolicLinkReparseBuffer; - - struct { - USHORT SubstituteNameOffset; - USHORT SubstituteNameLength; - USHORT PrintNameOffset; - USHORT PrintNameLength; - WCHAR PathBuffer[1]; - } MountPointReparseBuffer; - - struct { - UCHAR DataBuffer[1]; - } GenericReparseBuffer; - }; -} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; - -#define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER,\ - GenericReparseBuffer) -#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 ) - -static int -win32_read_link(HANDLE reparse_point_handle, ULONG *reparse_tag, wchar_t **target_path) -{ - char target_buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - REPARSE_DATA_BUFFER *rdb = (REPARSE_DATA_BUFFER *)target_buffer; - DWORD n_bytes_returned; - const wchar_t *ptr; - wchar_t *buf; - size_t len; - - if (0 == DeviceIoControl( - reparse_point_handle, - FSCTL_GET_REPARSE_POINT, - NULL, 0, /* in buffer */ - target_buffer, sizeof(target_buffer), - &n_bytes_returned, - NULL)) /* we're not using OVERLAPPED_IO */ - return -1; - - if (reparse_tag) - *reparse_tag = rdb->ReparseTag; - - if (target_path) { - switch (rdb->ReparseTag) { - case IO_REPARSE_TAG_SYMLINK: - /* XXX: Maybe should use SubstituteName? */ - ptr = rdb->SymbolicLinkReparseBuffer.PathBuffer + - rdb->SymbolicLinkReparseBuffer.PrintNameOffset/sizeof(WCHAR); - len = rdb->SymbolicLinkReparseBuffer.PrintNameLength/sizeof(WCHAR); - break; - case IO_REPARSE_TAG_MOUNT_POINT: - ptr = rdb->MountPointReparseBuffer.PathBuffer + - rdb->MountPointReparseBuffer.SubstituteNameOffset/sizeof(WCHAR); - len = rdb->MountPointReparseBuffer.SubstituteNameLength/sizeof(WCHAR); - break; - default: - SetLastError(ERROR_REPARSE_TAG_MISMATCH); /* XXX: Proper error code? */ - return -1; - } - buf = (wchar_t *)malloc(sizeof(wchar_t)*(len+1)); - if (!buf) { - SetLastError(ERROR_OUTOFMEMORY); - return -1; - } - wcsncpy(buf, ptr, len); - buf[len] = L'\0'; - if (wcsncmp(buf, L"\\??\\", 4) == 0) - buf[1] = L'\\'; - *target_path = buf; - } - - return 0; -} -#endif /* MS_WINDOWS */ - /* Return a dictionary corresponding to the POSIX environment table */ #ifdef WITH_NEXT_FRAMEWORK /* On Darwin/MacOSX a shared library or framework has no access to @@ -1030,7 +938,7 @@ } static int -attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag, struct win32_stat *result) +attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, struct win32_stat *result) { memset(result, 0, sizeof(*result)); result->st_mode = attributes_to_mode(info->dwFileAttributes); @@ -1040,18 +948,12 @@ FILE_TIME_to_time_t_nsec(&info->ftLastAccessTime, &result->st_atime, &result->st_atime_nsec); result->st_nlink = info->nNumberOfLinks; result->st_ino = (((__int64)info->nFileIndexHigh)<<32) + info->nFileIndexLow; - if (reparse_tag == IO_REPARSE_TAG_SYMLINK) { - /* first clear the S_IFMT bits */ - result->st_mode ^= (result->st_mode & 0170000); - /* now set the bits that make this a symlink */ - result->st_mode |= 0120000; - } return 0; } static BOOL -attributes_from_dir(LPCSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *reparse_tag) +attributes_from_dir(LPCSTR pszFile, BY_HANDLE_FILE_INFORMATION *info) { HANDLE hFindFile; WIN32_FIND_DATAA FileData; @@ -1060,7 +962,6 @@ return FALSE; FindClose(hFindFile); memset(info, 0, sizeof(*info)); - *reparse_tag = 0; info->dwFileAttributes = FileData.dwFileAttributes; info->ftCreationTime = FileData.ftCreationTime; info->ftLastAccessTime = FileData.ftLastAccessTime; @@ -1068,13 +969,11 @@ info->nFileSizeHigh = FileData.nFileSizeHigh; info->nFileSizeLow = FileData.nFileSizeLow; /* info->nNumberOfLinks = 1; */ - if (FileData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) - *reparse_tag = FileData.dwReserved0; return TRUE; } static BOOL -attributes_from_dir_w(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *reparse_tag) +attributes_from_dir_w(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info) { HANDLE hFindFile; WIN32_FIND_DATAW FileData; @@ -1083,7 +982,6 @@ return FALSE; FindClose(hFindFile); memset(info, 0, sizeof(*info)); - *reparse_tag = 0; info->dwFileAttributes = FileData.dwFileAttributes; info->ftCreationTime = FileData.ftCreationTime; info->ftLastAccessTime = FileData.ftLastAccessTime; @@ -1091,32 +989,121 @@ info->nFileSizeHigh = FileData.nFileSizeHigh; info->nFileSizeLow = FileData.nFileSizeLow; /* info->nNumberOfLinks = 1; */ - if (FileData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) - *reparse_tag = FileData.dwReserved0; return TRUE; } -#ifndef SYMLOOP_MAX -#define SYMLOOP_MAX ( 88 ) -#endif - +/* About the following functions: win32_lstat, win32_lstat_w, win32_stat, + win32_stat_w + + In Posix, stat automatically traverses symlinks and returns the stat + structure for the target. In Windows, the equivalent GetFileAttributes by + 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. + + The _w represent Unicode equivalents of the aformentioned ANSI functions. */ + +/* Grab GetFinalPathNameByHandle dynamically from kernel32 */ +static int has_GetFinalPathNameByHandle = 0; +static DWORD (CALLBACK *Py_GetFinalPathNameByHandleA)(HANDLE, LPSTR, DWORD, + DWORD); +static DWORD (CALLBACK *Py_GetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, + DWORD); static int -win32_xstat_impl_w(const wchar_t *path, struct win32_stat *result, BOOL traverse, int depth); +check_GetFinalPathNameByHandle() +{ + HINSTANCE hKernel32; + /* only recheck */ + if (!has_GetFinalPathNameByHandle) + { + hKernel32 = GetModuleHandle("KERNEL32"); + *(FARPROC*)&Py_GetFinalPathNameByHandleA = GetProcAddress(hKernel32, + "GetFinalPathNameByHandleA"); + *(FARPROC*)&Py_GetFinalPathNameByHandleW = GetProcAddress(hKernel32, + "GetFinalPathNameByHandleW"); + has_GetFinalPathNameByHandle = Py_GetFinalPathNameByHandleA && + Py_GetFinalPathNameByHandleW; + } + return has_GetFinalPathNameByHandle; +} + +static wchar_t * +getfinalpathname_w(HANDLE hFile) +{ + DWORD buf_size; + wchar_t *target_path; + DWORD result_length; + + if (!check_GetFinalPathNameByHandle()) { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return NULL; + } + + buf_size = Py_GetFinalPathNameByHandleW(hFile, 0, 0, VOLUME_NAME_DOS); + if (!buf_size) + return NULL; + /* On some platforms, GetFinalPathNameByHandle may return not the + required buffer size but the length of string. So we need to + allocate one more room for null terminator. */ + buf_size++; + + target_path = (wchar_t *)malloc(buf_size * sizeof(wchar_t)); + if (!target_path) { + SetLastError(ERROR_OUTOFMEMORY); + return NULL; + } + + result_length = Py_GetFinalPathNameByHandleW(hFile, target_path, + buf_size, VOLUME_NAME_DOS); + if (!result_length) { + free(target_path); + return NULL; + } + +/* target_path[result_length] = 0; */ /* XXX: Is this needed? */ + return target_path; +} + +static int win32_xstat_w(const wchar_t* path, struct win32_stat *result, BOOL traverse); static int -win32_xstat_impl(const char *path, struct win32_stat *result, BOOL traverse, int depth) -{ +process_symlink(HANDLE hFile, struct win32_stat *result, BOOL traverse) +{ + wchar_t *target_path; int code; + + if (!traverse) { + /* first clear the S_IFMT bits */ + result->st_mode ^= (result->st_mode & 0170000); + /* now set the bits that make this a symlink */ + result->st_mode |= 0120000; + return 0; + } else if (hFile != INVALID_HANDLE_VALUE) { + target_path = getfinalpathname_w(hFile); + if (!target_path) + return -1; + code = win32_xstat_w(target_path, result, FALSE); + free(target_path); + return code; + } else { + /* Should walk in, but cannot */ + SetLastError(ERROR_SHARING_VIOLATION); + return -1; + } +} + +static int +win32_xstat(const char* path, struct win32_stat *result, BOOL traverse) +{ + int code = -1; HANDLE hFile; BY_HANDLE_FILE_INFORMATION info; - ULONG reparse_tag = 0; - wchar_t *target_path; const char *dot; - - if (depth > SYMLOOP_MAX) { - SetLastError(ERROR_CANT_RESOLVE_FILENAME); /* XXX: ELOOP? */ - return -1; - } + WIN32_FIND_DATAA find_data; + HANDLE find_data_handle; hFile = CreateFileA( path, @@ -1132,39 +1119,42 @@ /* 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. */ - if (GetLastError() != ERROR_SHARING_VIOLATION) + if (GetLastError() != ERROR_SHARING_VIOLATION) { + /* Protocol violation: we explicitly clear errno, instead of + setting it to a POSIX error. Callers should use GetLastError. */ + errno = 0; 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(ERROR_SHARING_VIOLATION); + } else { + /* Could not get attributes on open file. Fall back to + reading the directory. */ + if (!attributes_from_dir(path, &info)) { + /* Very strange. This should not fail now */ + errno = 0; return -1; } } - } else { - if (!GetFileInformationByHandle(hFile, &info)) { - CloseHandle(hFile); - return -1;; - } - if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { - code = win32_read_link(hFile, &reparse_tag, traverse ? &target_path : NULL); - CloseHandle(hFile); - if (code < 0) - return code; - if (traverse) { - code = win32_xstat_impl_w(target_path, result, traverse, depth + 1); - free(target_path); + } + else { + if (!GetFileInformationByHandle(hFile, &info)) + goto error; + } + code = attribute_data_to_stat(&info, result); + if (code != 0) + goto error; + + /* Get WIN32_FIND_DATA structure for the path to determine if + it is a symlink */ + if(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + find_data_handle = FindFirstFileA(path, &find_data); + if(find_data_handle != INVALID_HANDLE_VALUE) { + FindClose(find_data_handle); + if(find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK) { + code = process_symlink(hFile, result, traverse); + CloseHandle(hFile); return code; } - } else - CloseHandle(hFile); - } - attribute_data_to_stat(&info, reparse_tag, result); + } + } /* Set S_IEXEC if it is an .exe, .bat, ... */ dot = strrchr(path, '.'); @@ -1173,23 +1163,21 @@ stricmp(dot, ".exe") == 0 || stricmp(dot, ".com") == 0) result->st_mode |= 0111; } - return 0; +error: + if (hFile != INVALID_HANDLE_VALUE) + CloseHandle(hFile); + return code; } static int -win32_xstat_impl_w(const wchar_t *path, struct win32_stat *result, BOOL traverse, int depth) -{ - int code; +win32_xstat_w(const wchar_t* path, struct win32_stat *result, BOOL traverse) +{ + int code = -1; HANDLE hFile; BY_HANDLE_FILE_INFORMATION info; - ULONG reparse_tag = 0; - wchar_t *target_path; const wchar_t *dot; - - if (depth > SYMLOOP_MAX) { - SetLastError(ERROR_CANT_RESOLVE_FILENAME); /* XXX: ELOOP? */ - return -1; - } + WIN32_FIND_DATAW find_data; + HANDLE find_data_handle; hFile = CreateFileW( path, @@ -1205,39 +1193,42 @@ /* 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. */ - if (GetLastError() != ERROR_SHARING_VIOLATION) + if (GetLastError() != ERROR_SHARING_VIOLATION) { + /* Protocol violation: we explicitly clear errno, instead of + setting it to a POSIX error. Callers should use GetLastError. */ + errno = 0; return -1; - /* Could not get attributes on open file. Fall back to - reading the directory. */ - if (!attributes_from_dir_w(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(ERROR_SHARING_VIOLATION); + } else { + /* Could not get attributes on open file. Fall back to + reading the directory. */ + if (!attributes_from_dir_w(path, &info)) { + /* Very strange. This should not fail now */ + errno = 0; return -1; } } - } else { - if (!GetFileInformationByHandle(hFile, &info)) { - CloseHandle(hFile); - return -1;; - } - if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { - code = win32_read_link(hFile, &reparse_tag, traverse ? &target_path : NULL); - CloseHandle(hFile); - if (code < 0) - return code; - if (traverse) { - code = win32_xstat_impl_w(target_path, result, traverse, depth + 1); - free(target_path); + } + else { + if (!GetFileInformationByHandle(hFile, &info)) + goto error; + } + code = attribute_data_to_stat(&info, result); + if (code < 0) + goto error; + + /* Get WIN32_FIND_DATA structure for the path to determine if + it is a symlink */ + if(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + find_data_handle = FindFirstFileW(path, &find_data); + if(find_data_handle != INVALID_HANDLE_VALUE) { + FindClose(find_data_handle); + if(find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK) { + code = process_symlink(hFile, result, traverse); + CloseHandle(hFile); return code; } - } else - CloseHandle(hFile); - } - attribute_data_to_stat(&info, reparse_tag, result); + } + } /* Set S_IEXEC if it is an .exe, .bat, ... */ dot = wcsrchr(path, '.'); @@ -1246,43 +1237,12 @@ _wcsicmp(dot, L".exe") == 0 || _wcsicmp(dot, L".com") == 0) result->st_mode |= 0111; } - return 0; -} - -static int -win32_xstat(const char *path, struct win32_stat *result, BOOL traverse) -{ - /* Protocol violation: we explicitly clear errno, instead of - setting it to a POSIX error. Callers should use GetLastError. */ - int code = win32_xstat_impl(path, result, traverse, 0); - errno = 0; +error: + if (hFile != INVALID_HANDLE_VALUE) + CloseHandle(hFile); return code; } -static int -win32_xstat_w(const wchar_t *path, struct win32_stat *result, BOOL traverse) -{ - /* Protocol violation: we explicitly clear errno, instead of - setting it to a POSIX error. Callers should use GetLastError. */ - int code = win32_xstat_impl_w(path, result, traverse, 0); - errno = 0; - return code; -} - -/* About the following functions: win32_lstat, win32_lstat_w, win32_stat, - win32_stat_w - - In Posix, stat automatically traverses symlinks and returns the stat - structure for the target. In Windows, the equivalent GetFileAttributes by - 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. - - The _w represent Unicode equivalents of the aforementioned ANSI functions. */ - static int win32_lstat(const char* path, struct win32_stat *result) { @@ -1349,7 +1309,7 @@ return -1; } - attribute_data_to_stat(&info, 0, result); + attribute_data_to_stat(&info, result); /* specific to fstat() */ result->st_ino = (((__int64)info.nFileIndexHigh)<<32) + info.nFileIndexLow; return 0; @@ -2707,38 +2667,12 @@ return PyBytes_FromString(outbuf); } /* end of posix__getfullpathname */ -/* Grab GetFinalPathNameByHandle dynamically from kernel32 */ -static int has_GetFinalPathNameByHandle = 0; -static DWORD (CALLBACK *Py_GetFinalPathNameByHandleA)(HANDLE, LPSTR, DWORD, - DWORD); -static DWORD (CALLBACK *Py_GetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, - DWORD); -static int -check_GetFinalPathNameByHandle() -{ - HINSTANCE hKernel32; - /* only recheck */ - if (!has_GetFinalPathNameByHandle) - { - hKernel32 = GetModuleHandle("KERNEL32"); - *(FARPROC*)&Py_GetFinalPathNameByHandleA = GetProcAddress(hKernel32, - "GetFinalPathNameByHandleA"); - *(FARPROC*)&Py_GetFinalPathNameByHandleW = GetProcAddress(hKernel32, - "GetFinalPathNameByHandleW"); - has_GetFinalPathNameByHandle = Py_GetFinalPathNameByHandleA && - Py_GetFinalPathNameByHandleW; - } - return has_GetFinalPathNameByHandle; -} - /* A helper function for samepath on windows */ static PyObject * posix__getfinalpathname(PyObject *self, PyObject *args) { HANDLE hFile; - int buf_size; wchar_t *target_path; - int result_length; PyObject *result; wchar_t *path; @@ -2765,31 +2699,16 @@ if(hFile == INVALID_HANDLE_VALUE) { return win32_error_unicode("GetFinalPathNamyByHandle", path); - return PyErr_Format(PyExc_RuntimeError, - "Could not get a handle to file."); } /* We have a good handle to the target, use it to determine the target path name. */ - buf_size = Py_GetFinalPathNameByHandleW(hFile, 0, 0, VOLUME_NAME_NT); - - if(!buf_size) - return win32_error_unicode("GetFinalPathNameByHandle", path); - - target_path = (wchar_t *)malloc((buf_size+1)*sizeof(wchar_t)); + target_path = getfinalpathname_w(hFile); + CloseHandle(hFile); if(!target_path) - return PyErr_NoMemory(); - - result_length = Py_GetFinalPathNameByHandleW(hFile, target_path, - buf_size, VOLUME_NAME_DOS); - if(!result_length) return win32_error_unicode("GetFinalPathNamyByHandle", path); - if(!CloseHandle(hFile)) - return win32_error_unicode("GetFinalPathNameByHandle", path); - - target_path[result_length] = 0; - result = PyUnicode_FromUnicode(target_path, result_length); + result = PyUnicode_FromUnicode(target_path, wcslen(target_path)); free(target_path); return result; @@ -5140,6 +5059,43 @@ "readlink(path) -> path\n\n\ Return a string representing the path to which the symbolic link points."); +/* The following structure was copied from + http://msdn.microsoft.com/en-us/library/ms791514.aspx as the required + include doesn't seem to be present in the Windows SDK (at least as included + with Visual Studio Express). */ +typedef struct _REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; + +#define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER,\ + GenericReparseBuffer) + +#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 ) + /* Windows readlink implementation */ static PyObject * win_readlink(PyObject *self, PyObject *args)