Issue18314 Allow unlink to remove junctions. Includes support for creating junctions. Patch by Kim Gräsman
This commit is contained in:
parent
a4790965f4
commit
0321cf2550
|
@ -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__":
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 */
|
Loading…
Reference in New Issue