Issue18314 Allow unlink to remove junctions. Includes support for creating junctions. Patch by Kim Gräsman

This commit is contained in:
Tim Golden 2014-05-05 19:46:17 +01:00
parent a4790965f4
commit 0321cf2550
4 changed files with 235 additions and 36 deletions

View File

@ -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__":

View File

@ -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) \
@ -400,6 +401,140 @@ winapi_CreateFile(PyObject *self, PyObject *args)
return Py_BuildValue(F_HANDLE, handle);
}
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)
{
@ -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,

View File

@ -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);
}
}

53
Modules/winreparse.h Normal file
View File

@ -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 */