Index: Doc/library/os.rst =================================================================== --- Doc/library/os.rst (revision 85145) +++ Doc/library/os.rst (working copy) @@ -1062,9 +1062,12 @@ Create a hard link pointing to *source* named *link_name*. - Availability: Unix. + Availability: Unix, Windows. + .. versionchanged:: 3.2 + Added Windows support. + .. function:: listdir(path='.') Return a list containing the names of the entries in the directory given by Index: Lib/test/test_os.py =================================================================== --- Lib/test/test_os.py (revision 85145) +++ Lib/test/test_os.py (working copy) @@ -853,6 +853,33 @@ if hasattr(os, "write"): self.check(os.write, b" ") + +class LinkTests(unittest.TestCase): + def setUp(self): + self.file1 = support.TESTFN + self.file2 = os.path.join(support.TESTFN + "2") + + for file in (self.file1, self.file2): + if os.path.exists(file): + os.unlink(file) + + tearDown = setUp + + def _test_link(self, file1, file2): + with open(file1, "w") as f1: + f1.write("test") + + os.link(file1, file2) + with open(file1, "r") as f1, open(file2, "r") as f2: + self.assertTrue(os.path.sameopenfile(f1.fileno(), f2.fileno())) + + def test_link(self): + self._test_link(self.file1, self.file2) + + def test_link_bytes(self): + self._test_link(bytes(self.file1, sys.getfilesystemencoding()), + bytes(self.file2, sys.getfilesystemencoding())) + if sys.platform != 'win32': class Win32ErrorTests(unittest.TestCase): pass @@ -1231,6 +1258,7 @@ FSEncodingTests, PidTests, LoginTests, + LinkTests, ) if __name__ == "__main__": Index: Modules/posixmodule.c =================================================================== --- Modules/posixmodule.c (revision 85145) +++ Modules/posixmodule.c (working copy) @@ -996,156 +996,69 @@ The _w represent Unicode equivalents of the aformentioned ANSI functions. */ -static int -win32_lstat(const char* path, struct win32_stat *result) +static int +win32_osfstat(HANDLE h, struct win32_stat *result) { - WIN32_FILE_ATTRIBUTE_DATA info; - int code; - char *dot; - WIN32_FIND_DATAA find_data; - HANDLE find_data_handle; - if (!GetFileAttributesExA(path, GetFileExInfoStandard, &info)) { - 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; - } 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; - } - } - } + BY_HANDLE_FILE_INFORMATION info; + int type; - code = attribute_data_to_stat(&info, result); - if (code != 0) - return code; + /* Protocol violation: we explicitly clear errno, instead of + setting it to a POSIX error. Callers should use GetLastError. */ + errno = 0; - /* 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) { - if(find_data.dwReserved0 == 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; - } - FindClose(find_data_handle); - } + if (h == INVALID_HANDLE_VALUE) { + /* This is really a C library error (invalid file handle). + We set the Win32 error to the closes one matching. */ + SetLastError(ERROR_INVALID_HANDLE); + return -1; } + memset(result, 0, sizeof(*result)); - /* Set S_IFEXEC if it is an .exe, .bat, ... */ - dot = strrchr(path, '.'); - if (dot) { - if (stricmp(dot, ".bat") == 0 || stricmp(dot, ".cmd") == 0 || - stricmp(dot, ".exe") == 0 || stricmp(dot, ".com") == 0) - result->st_mode |= 0111; - } - return code; -} - -static int -win32_lstat_w(const wchar_t* path, struct win32_stat *result) -{ - int code; - const wchar_t *dot; - WIN32_FILE_ATTRIBUTE_DATA info; - WIN32_FIND_DATAW find_data; - HANDLE find_data_handle; - if (!GetFileAttributesExW(path, GetFileExInfoStandard, &info)) { - 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; - } 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; - } + type = GetFileType(h); + if (type == FILE_TYPE_UNKNOWN) { + DWORD error = GetLastError(); + if (error != 0) { + return -1; } + /* else: valid but unknown file */ } - code = attribute_data_to_stat(&info, result); - if (code < 0) - return code; - /* 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) { - if(find_data.dwReserved0 == 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; - } - FindClose(find_data_handle); - } + if (type != FILE_TYPE_DISK) { + if (type == FILE_TYPE_CHAR) + result->st_mode = _S_IFCHR; + else if (type == FILE_TYPE_PIPE) + result->st_mode = _S_IFIFO; + return 0; } - /* Set IFEXEC 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; + if (!GetFileInformationByHandle(h, &info)) { + return -1; } - return code; -} -/* 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; + /* similar to stat() */ + result->st_mode = attributes_to_mode(info.dwFileAttributes); + result->st_size = (((__int64)info.nFileSizeHigh)<<32) + info.nFileSizeLow; + FILE_TIME_to_time_t_nsec(&info.ftCreationTime, &result->st_ctime, + &result->st_ctime_nsec); + FILE_TIME_to_time_t_nsec(&info.ftLastWriteTime, &result->st_mtime, + &result->st_mtime_nsec); + FILE_TIME_to_time_t_nsec(&info.ftLastAccessTime, &result->st_atime, + &result->st_atime_nsec); + /* specific to fstat() */ + result->st_nlink = info.nNumberOfLinks; + result->st_ino = (((__int64)info.nFileIndexHigh)<<32) + info.nFileIndexLow; + return 0; } static int -win32_stat(const char* path, struct win32_stat *result) +_win32_stat(const char* path, DWORD flag, struct win32_stat *result) { /* Traverse the symlink to the target using GetFinalPathNameByHandle() */ int code; HANDLE hFile; - int buf_size; - char *target_path; - int result_length; WIN32_FILE_ATTRIBUTE_DATA info; - - if(!check_GetFinalPathNameByHandle()) { - /* if the OS doesn't have GetFinalPathNameByHandle, it doesn't - have symlinks, so just fall back to the traditional behavior - found in lstat. */ - return win32_lstat(path, result); - } hFile = CreateFileA( path, @@ -1154,7 +1067,7 @@ NULL, /* security attributes */ OPEN_EXISTING, /* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */ - FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS|flag, NULL); if(hFile == INVALID_HANDLE_VALUE) { @@ -1178,52 +1091,20 @@ code = attribute_data_to_stat(&info, result); } else { - /* 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 = Py_GetFinalPathNameByHandleA(hFile, 0, 0, VOLUME_NAME_DOS); - if(!buf_size) return -1; - /* Due to a slight discrepancy between GetFinalPathNameByHandleA - and GetFinalPathNameByHandleW, we must allocate one more byte - than reported. */ - target_path = (char *)malloc((buf_size+2)*sizeof(char)); - result_length = Py_GetFinalPathNameByHandleA(hFile, target_path, - buf_size+1, VOLUME_NAME_DOS); - - if(!result_length) { - free(target_path); - return -1; - } - - if(!CloseHandle(hFile)) { - free(target_path); - return -1; - } - - target_path[result_length] = 0; - code = win32_lstat(target_path, result); - free(target_path); + code = win32_osfstat(hFile, result); + CloseHandle(hFile); } return code; } static int -win32_stat_w(const wchar_t* path, struct win32_stat *result) +_win32_stat_w(const wchar_t* path, DWORD flag, struct win32_stat *result) { /* Traverse the symlink to the target using GetFinalPathNameByHandle() */ int code; HANDLE hFile; - int buf_size; - wchar_t *target_path; - int result_length; WIN32_FILE_ATTRIBUTE_DATA info; - - if(!check_GetFinalPathNameByHandle()) { - /* If the OS doesn't have GetFinalPathNameByHandle, it doesn't have - symlinks, so just fall back to the traditional behavior found - in lstat. */ - return win32_lstat_w(path, result); - } hFile = CreateFileW( path, @@ -1232,7 +1113,7 @@ NULL, /* security attributes */ OPEN_EXISTING, /* FILE_FLAG_BACKUP_SEMANTICS is required to open a directory */ - FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS, + FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS|flag, NULL); if(hFile == INVALID_HANDLE_VALUE) { @@ -1256,89 +1137,69 @@ code = attribute_data_to_stat(&info, result); } else { - /* 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 = Py_GetFinalPathNameByHandleW(hFile, 0, 0, VOLUME_NAME_DOS); - if(!buf_size) - return -1; - - target_path = (wchar_t *)malloc((buf_size+1)*sizeof(wchar_t)); - result_length = Py_GetFinalPathNameByHandleW(hFile, target_path, - buf_size, VOLUME_NAME_DOS); - - if(!result_length) { - free(target_path); - return -1; - } - - if(!CloseHandle(hFile)) { - free(target_path); - return -1; - } - - target_path[result_length] = 0; - code = win32_lstat_w(target_path, result); - free(target_path); + code = win32_osfstat(hFile, result); + CloseHandle(hFile); } return code; } +static int +win32_lstat(const char* path, struct win32_stat *result) +{ + return _win32_stat(path, FILE_FLAG_OPEN_REPARSE_POINT, result); +} + static int -win32_fstat(int file_number, struct win32_stat *result) +win32_lstat_w(const wchar_t* path, struct win32_stat *result) { - BY_HANDLE_FILE_INFORMATION info; - HANDLE h; - int type; + return _win32_stat_w(path, FILE_FLAG_OPEN_REPARSE_POINT, result); +} - h = (HANDLE)_get_osfhandle(file_number); - - /* Protocol violation: we explicitly clear errno, instead of - setting it to a POSIX error. Callers should use GetLastError. */ - errno = 0; - - if (h == INVALID_HANDLE_VALUE) { - /* This is really a C library error (invalid file handle). - We set the Win32 error to the closes one matching. */ - SetLastError(ERROR_INVALID_HANDLE); - return -1; +/* 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; } - memset(result, 0, sizeof(*result)); + return has_GetFinalPathNameByHandle; +} - type = GetFileType(h); - if (type == FILE_TYPE_UNKNOWN) { - DWORD error = GetLastError(); - if (error != 0) { - return -1; - } - /* else: valid but unknown file */ - } +static int +win32_stat(const char* path, struct win32_stat *result) +{ + return _win32_stat(path, 0, result); +} - if (type != FILE_TYPE_DISK) { - if (type == FILE_TYPE_CHAR) - result->st_mode = _S_IFCHR; - else if (type == FILE_TYPE_PIPE) - result->st_mode = _S_IFIFO; - return 0; - } +static int +win32_stat_w(const wchar_t* path, struct win32_stat *result) +{ + return _win32_stat_w(path, 0, result); +} - if (!GetFileInformationByHandle(h, &info)) { - return -1; - } +static int +win32_fstat(int file_number, struct win32_stat *result) +{ + HANDLE h; - /* similar to stat() */ - result->st_mode = attributes_to_mode(info.dwFileAttributes); - result->st_size = (((__int64)info.nFileSizeHigh)<<32) + info.nFileSizeLow; - FILE_TIME_to_time_t_nsec(&info.ftCreationTime, &result->st_ctime, - &result->st_ctime_nsec); - FILE_TIME_to_time_t_nsec(&info.ftLastWriteTime, &result->st_mtime, - &result->st_mtime_nsec); - FILE_TIME_to_time_t_nsec(&info.ftLastAccessTime, &result->st_atime, - &result->st_atime_nsec); - /* specific to fstat() */ - result->st_nlink = info.nNumberOfLinks; - result->st_ino = (((__int64)info.nFileIndexHigh)<<32) + info.nFileIndexLow; - return 0; + h = (HANDLE)_get_osfhandle(file_number); + + return win32_osfstat(h, result); } #endif /* MS_WINDOWS */ @@ -2247,7 +2108,37 @@ } #endif /* HAVE_LINK */ +#ifdef MS_WINDOWS +PyDoc_STRVAR(win32_link__doc__, +"link(src, dst)\n\n\ +Create a hard link to a file."); +static PyObject * +win32_link(PyObject *self, PyObject *args) +{ + PyObject *osrc, *odst; + char *src, *dst; + BOOL rslt; + + if (!PyArg_ParseTuple(args, "O&O&:link", PyUnicode_FSConverter, &osrc, + PyUnicode_FSConverter, &odst)) + return NULL; + + src = PyBytes_AsString(osrc); + dst = PyBytes_AsString(odst); + + Py_BEGIN_ALLOW_THREADS + rslt = CreateHardLink(dst, src, NULL); + Py_END_ALLOW_THREADS + + if (rslt == 0) + return win32_error("link", NULL); + + Py_RETURN_NONE; +} +#endif /* MS_WINDOWS */ + + PyDoc_STRVAR(posix_listdir__doc__, "listdir([path]) -> list_of_strings\n\n\ Return a list containing the names of the entries in the directory.\n\ @@ -7796,6 +7687,7 @@ #ifdef MS_WINDOWS {"startfile", win32_startfile, METH_VARARGS, win32_startfile__doc__}, {"kill", win32_kill, METH_VARARGS, win32_kill__doc__}, + {"link", win32_link, METH_VARARGS, win32_link__doc__}, #endif #ifdef HAVE_SETUID {"setuid", posix_setuid, METH_VARARGS, posix_setuid__doc__},