|
40 | 40 | #define WINDOWS_LEAN_AND_MEAN |
41 | 41 | #include "windows.h" |
42 | 42 | #include <crtdbg.h> |
| 43 | +#include "winreparse.h" |
43 | 44 |
|
44 | 45 | #if defined(MS_WIN32) && !defined(MS_WIN64) |
45 | 46 | #define HANDLE_TO_PYNUM(handle) \ |
@@ -400,6 +401,140 @@ winapi_CreateFile(PyObject *self, PyObject *args) |
400 | 401 | return Py_BuildValue(F_HANDLE, handle); |
401 | 402 | } |
402 | 403 |
|
| 404 | +static PyObject * |
| 405 | +winapi_CreateJunction(PyObject *self, PyObject *args) |
| 406 | +{ |
| 407 | + /* Input arguments */ |
| 408 | + LPWSTR src_path = NULL; |
| 409 | + LPWSTR dst_path = NULL; |
| 410 | + |
| 411 | + /* Privilege adjustment */ |
| 412 | + HANDLE token = NULL; |
| 413 | + TOKEN_PRIVILEGES tp; |
| 414 | + |
| 415 | + /* Reparse data buffer */ |
| 416 | + const USHORT prefix_len = 4; |
| 417 | + USHORT print_len = 0; |
| 418 | + USHORT rdb_size = 0; |
| 419 | + PREPARSE_DATA_BUFFER rdb = NULL; |
| 420 | + |
| 421 | + /* Junction point creation */ |
| 422 | + HANDLE junction = NULL; |
| 423 | + DWORD ret = 0; |
| 424 | + |
| 425 | + if (!PyArg_ParseTuple(args, "uu", &src_path, &dst_path)) |
| 426 | + return NULL; |
| 427 | + |
| 428 | + if (src_path == NULL || dst_path == NULL) |
| 429 | + return PyErr_SetFromWindowsErr(ERROR_INVALID_PARAMETER); |
| 430 | + |
| 431 | + if (wcsncmp(src_path, L"\\??\\", prefix_len) == 0) |
| 432 | + return PyErr_SetFromWindowsErr(ERROR_INVALID_PARAMETER); |
| 433 | + |
| 434 | + /* Adjust privileges to allow rewriting directory entry as a |
| 435 | + junction point. */ |
| 436 | + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)) |
| 437 | + goto cleanup; |
| 438 | + |
| 439 | + if (!LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &tp.Privileges[0].Luid)) |
| 440 | + goto cleanup; |
| 441 | + |
| 442 | + tp.PrivilegeCount = 1; |
| 443 | + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; |
| 444 | + if (!AdjustTokenPrivileges(token, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), |
| 445 | + NULL, NULL)) |
| 446 | + goto cleanup; |
| 447 | + |
| 448 | + if (GetFileAttributesW(src_path) == INVALID_FILE_ATTRIBUTES) |
| 449 | + goto cleanup; |
| 450 | + |
| 451 | + /* Store the absolute link target path length in print_len. */ |
| 452 | + print_len = (USHORT)GetFullPathNameW(src_path, 0, NULL, NULL); |
| 453 | + if (print_len == 0) |
| 454 | + goto cleanup; |
| 455 | + |
| 456 | + /* NUL terminator should not be part of print_len. */ |
| 457 | + --print_len; |
| 458 | + |
| 459 | + /* REPARSE_DATA_BUFFER usage is heavily under-documented, especially for |
| 460 | + junction points. Here's what I've learned along the way: |
| 461 | + - A junction point has two components: a print name and a substitute |
| 462 | + name. They both describe the link target, but the substitute name is |
| 463 | + the physical target and the print name is shown in directory listings. |
| 464 | + - The print name must be a native name, prefixed with "\??\". |
| 465 | + - Both names are stored after each other in the same buffer (the |
| 466 | + PathBuffer) and both must be NUL-terminated. |
| 467 | + - There are four members defining their respective offset and length |
| 468 | + inside PathBuffer: SubstituteNameOffset, SubstituteNameLength, |
| 469 | + PrintNameOffset and PrintNameLength. |
| 470 | + - The total size we need to allocate for the REPARSE_DATA_BUFFER, thus, |
| 471 | + is the sum of: |
| 472 | + - the fixed header size (REPARSE_DATA_BUFFER_HEADER_SIZE) |
| 473 | + - the size of the MountPointReparseBuffer member without the PathBuffer |
| 474 | + - the size of the prefix ("\??\") in bytes |
| 475 | + - the size of the print name in bytes |
| 476 | + - the size of the substitute name in bytes |
| 477 | + - the size of two NUL terminators in bytes */ |
| 478 | + rdb_size = REPARSE_DATA_BUFFER_HEADER_SIZE + |
| 479 | + sizeof(rdb->MountPointReparseBuffer) - |
| 480 | + sizeof(rdb->MountPointReparseBuffer.PathBuffer) + |
| 481 | + /* Two +1's for NUL terminators. */ |
| 482 | + (prefix_len + print_len + 1 + print_len + 1) * sizeof(WCHAR); |
| 483 | + rdb = (PREPARSE_DATA_BUFFER)PyMem_RawMalloc(rdb_size); |
| 484 | + if (rdb == NULL) |
| 485 | + goto cleanup; |
| 486 | + |
| 487 | + memset(rdb, 0, rdb_size); |
| 488 | + rdb->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; |
| 489 | + rdb->ReparseDataLength = rdb_size - REPARSE_DATA_BUFFER_HEADER_SIZE; |
| 490 | + rdb->MountPointReparseBuffer.SubstituteNameOffset = 0; |
| 491 | + rdb->MountPointReparseBuffer.SubstituteNameLength = |
| 492 | + (prefix_len + print_len) * sizeof(WCHAR); |
| 493 | + rdb->MountPointReparseBuffer.PrintNameOffset = |
| 494 | + rdb->MountPointReparseBuffer.SubstituteNameLength + sizeof(WCHAR); |
| 495 | + rdb->MountPointReparseBuffer.PrintNameLength = print_len * sizeof(WCHAR); |
| 496 | + |
| 497 | + /* Store the full native path of link target at the substitute name |
| 498 | + offset (0). */ |
| 499 | + wcscpy(rdb->MountPointReparseBuffer.PathBuffer, L"\\??\\"); |
| 500 | + if (GetFullPathNameW(src_path, print_len + 1, |
| 501 | + rdb->MountPointReparseBuffer.PathBuffer + prefix_len, |
| 502 | + NULL) == 0) |
| 503 | + goto cleanup; |
| 504 | + |
| 505 | + /* Copy everything but the native prefix to the print name offset. */ |
| 506 | + wcscpy(rdb->MountPointReparseBuffer.PathBuffer + |
| 507 | + prefix_len + print_len + 1, |
| 508 | + rdb->MountPointReparseBuffer.PathBuffer + prefix_len); |
| 509 | + |
| 510 | + /* Create a directory for the junction point. */ |
| 511 | + if (!CreateDirectoryW(dst_path, NULL)) |
| 512 | + goto cleanup; |
| 513 | + |
| 514 | + junction = CreateFileW(dst_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, |
| 515 | + OPEN_EXISTING, |
| 516 | + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL); |
| 517 | + if (junction == INVALID_HANDLE_VALUE) |
| 518 | + goto cleanup; |
| 519 | + |
| 520 | + /* Make the directory entry a junction point. */ |
| 521 | + if (!DeviceIoControl(junction, FSCTL_SET_REPARSE_POINT, rdb, rdb_size, |
| 522 | + NULL, 0, &ret, NULL)) |
| 523 | + goto cleanup; |
| 524 | + |
| 525 | +cleanup: |
| 526 | + ret = GetLastError(); |
| 527 | + |
| 528 | + CloseHandle(token); |
| 529 | + CloseHandle(junction); |
| 530 | + PyMem_RawFree(rdb); |
| 531 | + |
| 532 | + if (ret != 0) |
| 533 | + return PyErr_SetFromWindowsErr(ret); |
| 534 | + |
| 535 | + Py_RETURN_NONE; |
| 536 | +} |
| 537 | + |
403 | 538 | static PyObject * |
404 | 539 | winapi_CreateNamedPipe(PyObject *self, PyObject *args) |
405 | 540 | { |
@@ -1225,6 +1360,8 @@ static PyMethodDef winapi_functions[] = { |
1225 | 1360 | METH_VARARGS | METH_KEYWORDS, ""}, |
1226 | 1361 | {"CreateFile", winapi_CreateFile, METH_VARARGS, |
1227 | 1362 | ""}, |
| 1363 | + {"CreateJunction", winapi_CreateJunction, METH_VARARGS, |
| 1364 | + ""}, |
1228 | 1365 | {"CreateNamedPipe", winapi_CreateNamedPipe, METH_VARARGS, |
1229 | 1366 | ""}, |
1230 | 1367 | {"CreatePipe", winapi_CreatePipe, METH_VARARGS, |
|
0 commit comments