summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Golden <mail@timgolden.me.uk>2014-05-05 19:46:17 +0100
committerTim Golden <mail@timgolden.me.uk>2014-05-05 19:46:17 +0100
commitcc45cc84efd64ba56840459a59702f56bb9b469c (patch)
tree55bb3f4f199cf454f85ef75694e15522d61886d9
parent0cf9ffae404804a7567517a67ccc7591c9b77319 (diff)
downloadcpython-cc45cc84efd64ba56840459a59702f56bb9b469c.tar.gz
Issue18314 Allow unlink to remove junctions. Includes support for creating junctions. Patch by Kim Gr?sman
-rw-r--r--Lib/test/test_os.py36
-rw-r--r--Modules/_winapi.c137
-rw-r--r--Modules/posixmodule.c45
-rw-r--r--Modules/winreparse.h53
4 files changed, 235 insertions, 36 deletions
diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py
index e129beff54..7d5ee69c45 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__":
diff --git a/Modules/_winapi.c b/Modules/_winapi.c
index b755178427..f118436f91 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,140 @@ 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 (src_path == NULL || dst_path == NULL)
+ return PyErr_SetFromWindowsErr(ERROR_INVALID_PARAMETER);
+
+ if (wcsncmp(src_path, L"\\??\\", prefix_len) == 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;
+
+ /* Store the absolute link target path length in print_len. */
+ 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;
+
+ /* REPARSE_DATA_BUFFER usage is heavily under-documented, especially for
+ junction points. Here's what I've learned along the way:
+ - A junction point has two components: a print name and a substitute
+ name. They both describe the link target, but the substitute name is
+ the physical target and the print name is shown in directory listings.
+ - The print name must be a native name, prefixed with "\??\".
+ - Both names are stored after each other in the same buffer (the
+ PathBuffer) and both must be NUL-terminated.
+ - There are four members defining their respective offset and length
+ inside PathBuffer: SubstituteNameOffset, SubstituteNameLength,
+ PrintNameOffset and PrintNameLength.
+ - The total size we need to allocate for the REPARSE_DATA_BUFFER, thus,
+ is the sum of:
+ - the fixed header size (REPARSE_DATA_BUFFER_HEADER_SIZE)
+ - the size of the MountPointReparseBuffer member without the PathBuffer
+ - the size of the prefix ("\??\") in bytes
+ - the size of the print name in bytes
+ - the size of the substitute name in bytes
+ - the size of two NUL terminators in bytes */
+ 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);
+ if (rdb == NULL)
+ goto cleanup;
+
+ memset(rdb, 0, rdb_size);
+ 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);
+
+ /* Store the full native path of link target at the substitute name
+ offset (0). */
+ wcscpy(rdb->MountPointReparseBuffer.PathBuffer, L"\\??\\");
+ if (GetFullPathNameW(src_path, print_len + 1,
+ rdb->MountPointReparseBuffer.PathBuffer + prefix_len,
+ NULL) == 0)
+ goto cleanup;
+
+ /* Copy everything but the native prefix to the print name offset. */
+ wcscpy(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);
+ PyMem_RawFree(rdb);
+
+ if (ret != 0)
+ return PyErr_SetFromWindowsErr(ret);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *
winapi_CreateNamedPipe(PyObject *self, PyObject *args)
{
LPCTSTR lpName;
@@ -1225,6 +1360,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 8cd5485ceb..916be81e43 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 0000000000..66f7775dd2
--- /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 */