diff -r f39895c55699 Lib/test/test_os.py --- a/Lib/test/test_os.py Fri Jul 20 19:50:41 2012 -0500 +++ b/Lib/test/test_os.py Sat Jul 21 15:43:36 2012 +0900 @@ -1533,6 +1533,31 @@ os.remove(file1) shutil.rmtree(level1) + def test_chmod_link(self): + TESTFN2 = support.TESTFN+"_link" + + def cleanup(): + if os.path.exists(TESTFN2): + os.chmod(TESTFN2, 0o100666) + os.unlink(TESTFN2) + + if os.path.exists(support.TESTFN): + os.chmod(support.TESTFN, 0o666) + os.unlink(support.TESTFN) + + cleanup() + + open(support.TESTFN, "w").close() + try: + os.symlink(support.TESTFN, TESTFN2) + os.chmod(TESTFN2, 0o444) + self.assertEqual(os.stat(TESTFN2).st_mode & 0o777, 0o444) + + os.chmod(TESTFN2, 0o666) + self.assertEqual(os.stat(support.TESTFN).st_mode & 0o777, 0o666) + finally: + cleanup() + class FSEncodingTests(unittest.TestCase): def test_nop(self): diff -r f39895c55699 Modules/posixmodule.c --- a/Modules/posixmodule.c Fri Jul 20 19:50:41 2012 -0500 +++ b/Modules/posixmodule.c Sat Jul 21 15:43:36 2012 +0900 @@ -1396,6 +1396,7 @@ int st_mtime_nsec; time_t st_ctime; int st_ctime_nsec; + DWORD file_attribute; }; static __int64 secs_between_epochs = 11644473600; /* Seconds between 1.1.1601 and 1.1.1970 */ @@ -1445,6 +1446,7 @@ attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *info, ULONG reparse_tag, struct win32_stat *result) { memset(result, 0, sizeof(*result)); + result->file_attribute = info->dwFileAttributes; 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); @@ -1573,7 +1575,7 @@ static int win32_xstat_impl_w(const wchar_t *path, struct win32_stat *result, - BOOL traverse); + wchar_t **finalpath, BOOL traverse); static int win32_xstat_impl(const char *path, struct win32_stat *result, BOOL traverse) @@ -1582,7 +1584,7 @@ HANDLE hFile, hFile2; BY_HANDLE_FILE_INFORMATION info; ULONG reparse_tag = 0; - wchar_t *target_path; + wchar_t *target_path, *finalpath = NULL; const char *dot; if(!check_GetFinalPathNameByHandle()) { @@ -1651,8 +1653,11 @@ if (!get_target_path(hFile2, &target_path)) return -1; - code = win32_xstat_impl_w(target_path, result, FALSE); + finalpath = NULL; + code = win32_xstat_impl_w(target_path, result, &finalpath, FALSE); free(target_path); + free(finalpath); + return code; } } else @@ -1672,7 +1677,7 @@ static int win32_xstat_impl_w(const wchar_t *path, struct win32_stat *result, - BOOL traverse) + wchar_t **finalpath, BOOL traverse) { int code; HANDLE hFile, hFile2; @@ -1681,6 +1686,7 @@ wchar_t *target_path; const wchar_t *dot; + *finalpath = NULL; if(!check_GetFinalPathNameByHandle()) { /* If the OS doesn't have GetFinalPathNameByHandle, don't traverse reparse point. */ @@ -1747,7 +1753,7 @@ if (!get_target_path(hFile2, &target_path)) return -1; - code = win32_xstat_impl_w(target_path, result, FALSE); + code = win32_xstat_impl_w(target_path, result, finalpath, FALSE); free(target_path); return code; } @@ -1763,6 +1769,7 @@ _wcsicmp(dot, L".exe") == 0 || _wcsicmp(dot, L".com") == 0) result->st_mode |= 0111; } + *finalpath = wcsdup(path); return 0; } @@ -1779,9 +1786,11 @@ static int win32_xstat_w(const wchar_t *path, struct win32_stat *result, BOOL traverse) { + wchar_t *finalpath = NULL; /* 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); + int code = win32_xstat_impl_w(path, result, &finalpath, traverse); + free(finalpath); errno = 0; return code; } @@ -2625,7 +2634,8 @@ int mode; int dir_fd = DEFAULT_DIR_FD; int follow_symlinks = 1; - int result; + int result, xstat_result; + wchar_t *finalpath = NULL; PyObject *return_value = NULL; static char *keywords[] = {"path", "mode", "dir_fd", "follow_symlinks", NULL}; @@ -2659,24 +2669,54 @@ #endif #ifdef MS_WINDOWS - Py_BEGIN_ALLOW_THREADS - if (path.wide) - attr = GetFileAttributesW(path.wide); - else - attr = GetFileAttributesA(path.narrow); - if (attr == 0xFFFFFFFF) - result = 0; + if (path.wide && follow_symlinks) { + struct win32_stat st; + + Py_BEGIN_ALLOW_THREADS + + xstat_result = win32_xstat_impl_w(path.wide, &st, + &finalpath, TRUE); + if (!xstat_result) { + attr = st.file_attribute; + if (attr == 0xFFFFFFFF) + result = 0; + else { + if (mode & _S_IWRITE) + attr &= ~FILE_ATTRIBUTE_READONLY; + else + attr |= FILE_ATTRIBUTE_READONLY; + result = SetFileAttributesW(finalpath, attr); + } + free(finalpath); + } + Py_END_ALLOW_THREADS + + if (xstat_result) { + return_value = win32_error_object("chmod", path.object); + goto exit; + } + } else { - if (mode & _S_IWRITE) - attr &= ~FILE_ATTRIBUTE_READONLY; + Py_BEGIN_ALLOW_THREADS + + if (path.wide) + attr = GetFileAttributesW(path.wide); else - attr |= FILE_ATTRIBUTE_READONLY; - if (path.wide) - result = SetFileAttributesW(path.wide, attr); - else - result = SetFileAttributesA(path.narrow, attr); - } - Py_END_ALLOW_THREADS + attr = GetFileAttributesA(path.narrow); + if (attr == 0xFFFFFFFF) + result = 0; + else { + if (mode & _S_IWRITE) + attr &= ~FILE_ATTRIBUTE_READONLY; + else + attr |= FILE_ATTRIBUTE_READONLY; + if (path.wide) + result = SetFileAttributesW(path.wide, attr); + else + result = SetFileAttributesA(path.narrow, attr); + } + Py_END_ALLOW_THREADS + } if (!result) { return_value = win32_error_object("chmod", path.object);