diff options
author | Tim Golden <mail@timgolden.me.uk> | 2014-04-27 17:00:10 (GMT) |
---|---|---|
committer | Tim Golden <mail@timgolden.me.uk> | 2014-04-27 17:00:10 (GMT) |
commit | 4675d798bfdef3ad916d1eef6d5a4d0529ccfda7 (patch) | |
tree | 628685ce1f8db9455bb95819f876faa7c56bf020 | |
parent | dd41f246875494c9acc6ebd4338f8df3ec6c28f0 (diff) | |
download | cpython-4675d798bfdef3ad916d1eef6d5a4d0529ccfda7.zip cpython-4675d798bfdef3ad916d1eef6d5a4d0529ccfda7.tar.gz cpython-4675d798bfdef3ad916d1eef6d5a4d0529ccfda7.tar.bz2 |
Issue #18314 os.unlink will now remove junction points on Windows. Patch by Kim Gräsman.
-rw-r--r-- | Lib/test/test_os.py | 36 | ||||
-rw-r--r-- | Misc/ACKS | 1 | ||||
-rw-r--r-- | Modules/_winapi.c | 109 | ||||
-rw-r--r-- | Modules/posixmodule.c | 45 | ||||
-rw-r--r-- | Modules/winreparse.h | 53 |
5 files changed, 208 insertions, 36 deletions
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index e129bef..7d5ee69 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -39,6 +39,10 @@ try: import fcntl except ImportError: fcntl = None +try: + import _winapi +except ImportError: + _winapi = None from test.script_helper import assert_python_ok @@ -1773,6 +1777,37 @@ class Win32SymlinkTests(unittest.TestCase): shutil.rmtree(level1) +@unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") +class Win32JunctionTests(unittest.TestCase): + junction = 'junctiontest' + junction_target = os.path.dirname(os.path.abspath(__file__)) + + def setUp(self): + assert os.path.exists(self.junction_target) + assert not os.path.exists(self.junction) + + def tearDown(self): + if os.path.exists(self.junction): + # os.rmdir delegates to Windows' RemoveDirectoryW, + # which removes junction points safely. + os.rmdir(self.junction) + + def test_create_junction(self): + _winapi.CreateJunction(self.junction_target, self.junction) + self.assertTrue(os.path.exists(self.junction)) + self.assertTrue(os.path.isdir(self.junction)) + + # Junctions are not recognized as links. + self.assertFalse(os.path.islink(self.junction)) + + def test_unlink_removes_junction(self): + _winapi.CreateJunction(self.junction_target, self.junction) + self.assertTrue(os.path.exists(self.junction)) + + os.unlink(self.junction) + self.assertFalse(os.path.exists(self.junction)) + + @support.skip_unless_symlink class NonLocalSymlinkTests(unittest.TestCase): @@ -2544,6 +2579,7 @@ def test_main(): RemoveDirsTests, CPUCountTests, FDInheritanceTests, + Win32JunctionTests, ) if __name__ == "__main__": @@ -468,6 +468,7 @@ Chris Gonnerman Shelley Gooch David Goodger Hans de Graaff +Kim Gräsman Nathaniel Gray Eddy De Greef Grant Griffin diff --git a/Modules/_winapi.c b/Modules/_winapi.c index b755178..80c1e92 100644 --- a/Modules/_winapi.c +++ b/Modules/_winapi.c @@ -40,6 +40,7 @@ #define WINDOWS_LEAN_AND_MEAN #include "windows.h" #include <crtdbg.h> +#include "winreparse.h" #if defined(MS_WIN32) && !defined(MS_WIN64) #define HANDLE_TO_PYNUM(handle) \ @@ -401,6 +402,112 @@ winapi_CreateFile(PyObject *self, PyObject *args) } static PyObject * +winapi_CreateJunction(PyObject *self, PyObject *args) +{ + /* Input arguments */ + LPWSTR src_path = NULL; + LPWSTR dst_path = NULL; + + /* Privilege adjustment */ + HANDLE token = NULL; + TOKEN_PRIVILEGES tp; + + /* Reparse data buffer */ + const USHORT prefix_len = 4; + USHORT print_len = 0; + USHORT rdb_size = 0; + PREPARSE_DATA_BUFFER rdb = NULL; + + /* Junction point creation */ + HANDLE junction = NULL; + DWORD ret = 0; + + if (!PyArg_ParseTuple(args, "uu", + &src_path, &dst_path)) + return NULL; + + if (memcmp(src_path, L"\\??\\", prefix_len * sizeof(WCHAR)) == 0) + return PyErr_SetFromWindowsErr(ERROR_INVALID_PARAMETER); + + /* Adjust privileges to allow rewriting directory entry as a + junction point. */ + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)) + goto cleanup; + + if (!LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &tp.Privileges[0].Luid)) + goto cleanup; + + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + if (!AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), + NULL, NULL)) + goto cleanup; + + if (GetFileAttributesW(src_path) == INVALID_FILE_ATTRIBUTES) + goto cleanup; + + print_len = (USHORT)GetFullPathNameW(src_path, 0, NULL, NULL); + if (print_len == 0) + goto cleanup; + + /* NUL terminator should not be part of print_len */ + --print_len; + + rdb_size = REPARSE_DATA_BUFFER_HEADER_SIZE + + sizeof(rdb->MountPointReparseBuffer) - + sizeof(rdb->MountPointReparseBuffer.PathBuffer) + + /* Two +1's for NUL terminators. */ + (prefix_len + print_len + 1 + print_len + 1) * sizeof(WCHAR); + rdb = (PREPARSE_DATA_BUFFER)PyMem_RawMalloc(rdb_size, 1); + + rdb->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + rdb->ReparseDataLength = rdb_size - REPARSE_DATA_BUFFER_HEADER_SIZE; + rdb->MountPointReparseBuffer.SubstituteNameOffset = 0; + rdb->MountPointReparseBuffer.SubstituteNameLength = + (prefix_len + print_len) * sizeof(WCHAR); + rdb->MountPointReparseBuffer.PrintNameOffset = + rdb->MountPointReparseBuffer.SubstituteNameLength + sizeof(WCHAR); + rdb->MountPointReparseBuffer.PrintNameLength = print_len * sizeof(WCHAR); + + lstrcpyW(rdb->MountPointReparseBuffer.PathBuffer, L"\\??\\"); + if (GetFullPathNameW(src_path, print_len + 1, + rdb->MountPointReparseBuffer.PathBuffer + prefix_len, + NULL) == 0) + goto cleanup; + + lstrcpyW(rdb->MountPointReparseBuffer.PathBuffer + + prefix_len + print_len + 1, + rdb->MountPointReparseBuffer.PathBuffer + prefix_len); + + /* Create a directory for the junction point. */ + if (!CreateDirectoryW(dst_path, NULL)) + goto cleanup; + + junction = CreateFileW(dst_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (junction == INVALID_HANDLE_VALUE) + goto cleanup; + + /* Make the directory entry a junction point. */ + if (!DeviceIoControl(junction, FSCTL_SET_REPARSE_POINT, rdb, rdb_size, + NULL, 0, &ret, NULL)) + goto cleanup; + +cleanup: + ret = GetLastError(); + + CloseHandle(token); + CloseHandle(junction); + free(rdb); + + if (ret != 0) + return PyErr_SetFromWindowsErr(ret); + + Py_RETURN_NONE; +} + +static PyObject * winapi_CreateNamedPipe(PyObject *self, PyObject *args) { LPCTSTR lpName; @@ -1225,6 +1332,8 @@ static PyMethodDef winapi_functions[] = { METH_VARARGS | METH_KEYWORDS, ""}, {"CreateFile", winapi_CreateFile, METH_VARARGS, ""}, + {"CreateJunction", winapi_CreateJunction, METH_VARARGS, + ""}, {"CreateNamedPipe", winapi_CreateNamedPipe, METH_VARARGS, ""}, {"CreatePipe", winapi_CreatePipe, METH_VARARGS, diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 8cd5485..916be81 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -27,6 +27,8 @@ #include "Python.h" #ifndef MS_WINDOWS #include "posixmodule.h" +#else +#include "winreparse.h" #endif #ifdef __cplusplus @@ -301,6 +303,9 @@ extern int lstat(const char *, struct stat *); #ifndef IO_REPARSE_TAG_SYMLINK #define IO_REPARSE_TAG_SYMLINK (0xA000000CL) #endif +#ifndef IO_REPARSE_TAG_MOUNT_POINT +#define IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) +#endif #include "osdefs.h" #include <malloc.h> #include <windows.h> @@ -1109,41 +1114,6 @@ _PyVerify_fd_dup2(int fd1, int fd2) #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_get_reparse_tag(HANDLE reparse_point_handle, ULONG *reparse_tag) @@ -4492,7 +4462,10 @@ BOOL WINAPI Py_DeleteFileW(LPCWSTR lpFileName) find_data_handle = FindFirstFileW(lpFileName, &find_data); if(find_data_handle != INVALID_HANDLE_VALUE) { - is_link = find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK; + /* IO_REPARSE_TAG_SYMLINK if it is a symlink and + IO_REPARSE_TAG_MOUNT_POINT if it is a junction point. */ + is_link = find_data.dwReserved0 == IO_REPARSE_TAG_SYMLINK || + find_data.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT; FindClose(find_data_handle); } } diff --git a/Modules/winreparse.h b/Modules/winreparse.h new file mode 100644 index 0000000..66f7775 --- /dev/null +++ b/Modules/winreparse.h @@ -0,0 +1,53 @@ +#ifndef Py_WINREPARSE_H +#define Py_WINREPARSE_H + +#ifdef MS_WINDOWS +#include <Windows.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* The following structure was copied from + http://msdn.microsoft.com/en-us/library/ff552012.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 ) + +#ifdef __cplusplus +} +#endif + +#endif /* MS_WINDOWS */ + +#endif /* !Py_WINREPARSE_H */ |