[PATCH v3 0/2] MR9595: cmd: Reparse support.
-- v3: cmd: Implement mklink /j. cmd: Print reparse points in directory listings. https://gitlab.winehq.org/wine/wine/-/merge_requests/9595
From: Elizabeth Figura <zfigura(a)codeweavers.com> Based on patches by Erich E. Hoover. --- programs/cmd/Makefile.in | 2 +- programs/cmd/directory.c | 75 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/programs/cmd/Makefile.in b/programs/cmd/Makefile.in index cca40b84011..cc913733c8b 100644 --- a/programs/cmd/Makefile.in +++ b/programs/cmd/Makefile.in @@ -1,5 +1,5 @@ MODULE = cmd.exe -IMPORTS = shell32 user32 advapi32 +IMPORTS = shell32 user32 advapi32 kernelbase EXTRADLLFLAGS = -mconsole -municode diff --git a/programs/cmd/directory.c b/programs/cmd/directory.c index 985a9899cec..e95d57e9d19 100644 --- a/programs/cmd/directory.c +++ b/programs/cmd/directory.c @@ -22,6 +22,10 @@ #define WIN32_LEAN_AND_MEAN #include "wcmd.h" +#define WIN32_NO_STATUS +#include <pathcch.h> +#include <winioctl.h> +#include <ddk/ntifs.h> #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(cmd); @@ -232,6 +236,56 @@ static void WCMD_getfileowner(WCHAR *filename, WCHAR *owner, int ownerlen) { return; } +static void output_reparse_target(const WCHAR *dir, const WCHAR *filename) +{ + REPARSE_DATA_BUFFER *data; + HANDLE file; + WCHAR *path; + DWORD size; + BOOL ret; + + PathAllocCombine(dir, filename, PATHCCH_ALLOW_LONG_PATHS, &path); + file = CreateFileW(path, GENERIC_READ, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if (file == INVALID_HANDLE_VALUE) + { + WARN("failed to open %s, error %lu\n", debugstr_w(path), GetLastError()); + LocalFree(path); + return; + } + + data = malloc(1024); + ret = DeviceIoControl(file, FSCTL_GET_REPARSE_POINT, NULL, 0, data, 1024, &size, NULL); + if (!ret && GetLastError() == ERROR_MORE_DATA) + { + size = sizeof(REPARSE_DATA_BUFFER) + data->ReparseDataLength; + data = realloc(data, size); + ret = DeviceIoControl(file, FSCTL_GET_REPARSE_POINT, NULL, 0, data, size, &size, NULL); + } + + if (ret) + { + if (data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) + { + size_t offset = data->MountPointReparseBuffer.PrintNameOffset / sizeof(WCHAR); + WCMD_output(L" [%1]", data->MountPointReparseBuffer.PathBuffer + offset); + } + else if (data->ReparseTag == IO_REPARSE_TAG_SYMLINK) + { + size_t offset = data->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR); + WCMD_output(L" [%1]", data->SymbolicLinkReparseBuffer.PathBuffer + offset); + } + } + else + { + WARN("failed to get reparse point from %s, error %lu\n", debugstr_w(path), GetLastError()); + } + + free(data); + LocalFree(path); + CloseHandle(file); +} + /***************************************************************************** * WCMD_list_directory * @@ -404,10 +458,19 @@ static RETURN_CODE WCMD_list_directory (DIRECTORY_STACK *inputparms, int level, dir_count++; if (!bare) { - WCMD_output (L"%1 %2 <DIR> ", datestring, timestring); + if ((fd[i].dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + && (fd[i].dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT)) + WCMD_output(L"%1 %2 <JUNCTION> ", datestring, timestring); + else if ((fd[i].dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + && (fd[i].dwReserved0 == IO_REPARSE_TAG_SYMLINK)) + WCMD_output(L"%1 %2 <SYMLINKD> ", datestring, timestring); + else + WCMD_output(L"%1 %2 <DIR> ", datestring, timestring); if (shortname) WCMD_output(L"%1!-13s!", fd[i].cAlternateFileName); if (usernames) WCMD_output(L"%1!-23s!", username); WCMD_output(L"%1",fd[i].cFileName); + if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + output_reparse_target(inputparms->dirName, fd[i].cFileName); } else { if (!((lstrcmpW(fd[i].cFileName, L".") == 0) || (lstrcmpW(fd[i].cFileName, L"..") == 0))) { @@ -423,11 +486,17 @@ static RETURN_CODE WCMD_list_directory (DIRECTORY_STACK *inputparms, int level, file_size.u.HighPart = fd[i].nFileSizeHigh; byte_count.QuadPart += file_size.QuadPart; if (!bare) { - WCMD_output (L"%1 %2 %3!14s! ", datestring, timestring, - WCMD_filesize64(file_size.QuadPart)); + if ((fd[i].dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + && (fd[i].dwReserved0 == IO_REPARSE_TAG_SYMLINK)) + WCMD_output(L"%1 %2 <SYMLINK> ", datestring, timestring); + else + WCMD_output(L"%1 %2 %3!14s! ", datestring, timestring, + WCMD_filesize64(file_size.QuadPart)); if (shortname) WCMD_output(L"%1!-13s!", fd[i].cAlternateFileName); if (usernames) WCMD_output(L"%1!-23s!", username); WCMD_output(L"%1",fd[i].cFileName); + if (fd[i].dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + output_reparse_target(inputparms->dirName, fd[i].cFileName); } else { WCMD_output(L"%1%2", recurse ? inputparms->dirName : L"", fd[i].cFileName); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9595
From: Elizabeth Figura <zfigura(a)codeweavers.com> --- programs/cmd/builtins.c | 81 +++++++++++++++++++++++- programs/cmd/tests/test_builtins.bat | 3 + programs/cmd/tests/test_builtins.bat.exp | 2 + programs/cmd/tests/test_builtins.cmd | 3 + programs/cmd/tests/test_builtins.cmd.exp | 2 + 5 files changed, 90 insertions(+), 1 deletion(-) diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index aebd1136563..7d1ad5c1e15 100644 --- a/programs/cmd/builtins.c +++ b/programs/cmd/builtins.c @@ -30,6 +30,10 @@ #include "wcmd.h" #include <shellapi.h> +#define WIN32_NO_STATUS +#include "winternl.h" +#include "winioctl.h" +#include "ddk/ntifs.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(cmd); @@ -3961,6 +3965,81 @@ RETURN_CODE WCMD_color(void) return errorlevel = return_code; } +/* We cannot use SetVolumeMountPoint(), because that function forbids setting + * arbitrary directories as mount points, whereas mklink /j allows it. */ +BOOL create_mount_point(const WCHAR *link, const WCHAR *target) { + char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + REPARSE_DATA_BUFFER *data = (void *)buffer; + WCHAR full_link[MAX_PATH], *full_target; + UNICODE_STRING nt_link, nt_target; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + NTSTATUS status; + HANDLE file; + DWORD size; + BOOL ret; + + TRACE( "link %s, target %s\n", debugstr_w(link), debugstr_w(target) ); + + if (!WCMD_get_fullpath(link, ARRAY_SIZE(full_link), full_link, NULL)) + return FALSE; + + if (!(size = GetFullPathNameW(target, 0, NULL, NULL))) + return FALSE; + full_target = malloc(size * sizeof(WCHAR)); + GetFullPathNameW(target, size, full_target, NULL); + + status = RtlDosPathNameToNtPathName_U_WithStatus(full_link, &nt_link, NULL, NULL); + if (status) + { + free(full_target); + return FALSE; + } + + status = RtlDosPathNameToNtPathName_U_WithStatus(full_target, &nt_target, NULL, NULL); + if (status) + { + free(full_target); + RtlFreeUnicodeString(&nt_link); + return FALSE; + } + + size = offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer); + size += nt_target.Length + sizeof(WCHAR) + (wcslen(full_target) + 1) * sizeof(WCHAR); + if (size > sizeof(buffer)) + { + free(full_target); + RtlFreeUnicodeString(&nt_link); + RtlFreeUnicodeString(&nt_target); + return FALSE; + } + + data->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + data->ReparseDataLength = size - offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer); + data->Reserved = 0; + data->MountPointReparseBuffer.SubstituteNameOffset = 0; + data->MountPointReparseBuffer.SubstituteNameLength = nt_target.Length; + data->MountPointReparseBuffer.PrintNameOffset = nt_target.Length + sizeof(WCHAR); + data->MountPointReparseBuffer.PrintNameLength = wcslen(full_target) * sizeof(WCHAR); + memcpy(data->MountPointReparseBuffer.PathBuffer, + nt_target.Buffer, nt_target.Length + sizeof(WCHAR)); + memcpy(data->MountPointReparseBuffer.PathBuffer + (nt_target.Length / sizeof(WCHAR)) + 1, + full_target, (wcslen(full_target) + 1) * sizeof(WCHAR)); + RtlFreeUnicodeString(&nt_target); + free(full_target); + + InitializeObjectAttributes(&attr, &nt_link, OBJ_CASE_INSENSITIVE, 0, NULL); + status = NtCreateFile(&file, GENERIC_WRITE, &attr, &io, NULL, 0, 0, FILE_CREATE, + FILE_OPEN_REPARSE_POINT | FILE_DIRECTORY_FILE, NULL, 0); + RtlFreeUnicodeString(&nt_link); + if (status) + return FALSE; + + ret = DeviceIoControl(file, FSCTL_SET_REPARSE_POINT, data, size, NULL, 0, &size, NULL); + CloseHandle(file); + return ret; +} + /**************************************************************************** * WCMD_mklink */ @@ -4011,7 +4090,7 @@ RETURN_CODE WCMD_mklink(WCHAR *args) else if(!junction) ret = CreateSymbolicLinkW(file1, file2, isdir); else - TRACE("Junction links currently not supported.\n"); + ret = create_mount_point(file1, file2); } if (ret) return errorlevel = NO_ERROR; diff --git a/programs/cmd/tests/test_builtins.bat b/programs/cmd/tests/test_builtins.bat index e36cc742053..1bd3de55292 100644 --- a/programs/cmd/tests/test_builtins.bat +++ b/programs/cmd/tests/test_builtins.bat @@ -236,6 +236,9 @@ call :setError 666 & (mklink &&echo SUCCESS !errorlevel!||echo FAILURE !errorlev call :setError 666 & (mklink /h foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) call :setError 666 & (mklink /h foo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) call :setError 666 & (mklink /z foo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mklink /j foo foo >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mklink /j foo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rmdir foo echo bar > foo call :setError 666 & (mklink /h foo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) call :setError 666 & (mklink /h bar foo >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) diff --git a/programs/cmd/tests/test_builtins.bat.exp b/programs/cmd/tests/test_builtins.bat.exp index 220ba55c857..397b720c3bf 100644 --- a/programs/cmd/tests/test_builtins.bat.exp +++ b/programs/cmd/tests/test_builtins.bat.exp @@ -149,6 +149,8 @@ FAILURE 1 FAILURE 1 FAILURE 1 FAILURE 1 +SUCCESS 0 +FAILURE 1 FAILURE 1 SUCCESS 0 FAILURE 1 diff --git a/programs/cmd/tests/test_builtins.cmd b/programs/cmd/tests/test_builtins.cmd index a4134fe32c1..36b36699772 100644 --- a/programs/cmd/tests/test_builtins.cmd +++ b/programs/cmd/tests/test_builtins.cmd @@ -819,6 +819,9 @@ call :setError 666 & (mklink &&echo SUCCESS !errorlevel!||echo FAILURE !errorlev call :setError 666 & (mklink /h foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) call :setError 666 & (mklink /h foo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) call :setError 666 & (mklink /z foo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mklink /j foo foo >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +call :setError 666 & (mklink /j foo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) +rmdir foo echo bar > foo call :setError 666 & (mklink /h foo foo &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) call :setError 666 & (mklink /h bar foo >NUL &&echo SUCCESS !errorlevel!||echo FAILURE !errorlevel!) diff --git a/programs/cmd/tests/test_builtins.cmd.exp b/programs/cmd/tests/test_builtins.cmd.exp index 5409c13f662..d169066a167 100644 --- a/programs/cmd/tests/test_builtins.cmd.exp +++ b/programs/cmd/tests/test_builtins.cmd.exp @@ -668,6 +668,8 @@ FAILURE 1 FAILURE 1 FAILURE 1 FAILURE 1 +SUCCESS 0 +FAILURE 1 FAILURE 1 SUCCESS 0 FAILURE 1 -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9595
I've pushed a new version that correctly handles long paths. I've also fixed the print name, which was wrong—it's supposed to be the full DOS path. I'm not sure where that overflow is coming from, but I'm pretty sure it's not from create_mount_point(). There's only one fixed-length buffer in there and it's checked. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9595#note_124772
thanks for the update I'll send another patch later on for the overflow (it existed before this MR) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9595#note_124866
This merge request was approved by eric pouech. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9595
This could also use a MAXIMUM_REPARSE_DATA_BUFFER_SIZE buffer.
Doh, yet again. Fixed.
It's still not fixed. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/9595#note_124981
participants (4)
-
Alexandre Julliard (@julliard) -
Elizabeth Figura -
Elizabeth Figura (@zfigura) -
eric pouech (@epo)