[Python-checkins] cpython: Issue #18314 os.unlink will now remove junction points on Windows. Patch by Kim

tim.golden python-checkins at python.org
Sun Apr 27 19:00:30 CEST 2014


http://hg.python.org/cpython/rev/17df50df62c7
changeset:   90474:17df50df62c7
parent:      90470:462470859e57
user:        Tim Golden <mail at timgolden.me.uk>
date:        Sun Apr 27 18:00:10 2014 +0100
summary:
  Issue #18314 os.unlink will now remove junction points on Windows. Patch by Kim Gräsman.

files:
  Lib/test/test_os.py   |   36 +++++++++
  Misc/ACKS             |    1 +
  Modules/_winapi.c     |  109 ++++++++++++++++++++++++++++++
  Modules/posixmodule.c |   45 ++---------
  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
--- a/Lib/test/test_os.py
+++ b/Lib/test/test_os.py
@@ -39,6 +39,10 @@
     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 @@
             shutil.rmtree(level1)
 
 
+ at 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 @@
         RemoveDirsTests,
         CPUCountTests,
         FDInheritanceTests,
+        Win32JunctionTests,
     )
 
 if __name__ == "__main__":
diff --git a/Misc/ACKS b/Misc/ACKS
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -468,6 +468,7 @@
 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
--- 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 @@
 }
 
 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 @@
      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
--- 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 @@
 #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 @@
 #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 @@
             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
--- /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 */

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list