From: Elizabeth Figura zfigura@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); }
From: Elizabeth Figura zfigura@codeweavers.com
--- programs/cmd/builtins.c | 62 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-)
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c index aebd1136563..7bbf34df1ec 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,62 @@ 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) { + UNICODE_STRING nt_link, nt_target; + REPARSE_DATA_BUFFER *data; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + unsigned int size; + NTSTATUS status; + HANDLE file; + + TRACE( "link %s, target %s\n", debugstr_w(link), debugstr_w(target) ); + + status = RtlDosPathNameToNtPathName_U_WithStatus( link, &nt_link, NULL, NULL ); + if (status) return FALSE; + + status = RtlDosPathNameToNtPathName_U_WithStatus( target, &nt_target, NULL, NULL ); + if (status) + { + RtlFreeUnicodeString( &nt_link ); + return FALSE; + } + + size = offsetof( REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer ); + size += (nt_target.Length + sizeof(WCHAR)) * 2; + data = malloc( size ); + + 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 = nt_target.Length; + memcpy( data->MountPointReparseBuffer.PathBuffer, + nt_target.Buffer, nt_target.Length + sizeof(WCHAR) ); + memcpy( data->MountPointReparseBuffer.PathBuffer + (nt_target.Length / sizeof(WCHAR)) + 1, + nt_target.Buffer, nt_target.Length + sizeof(WCHAR) ); + RtlFreeUnicodeString( &nt_target ); + + InitializeObjectAttributes( &attr, &nt_link, OBJ_CASE_INSENSITIVE, 0, NULL ); + status = NtCreateFile( &file, GENERIC_WRITE, &attr, &io, NULL, 0, 0, FILE_OPEN_IF, + FILE_OPEN_REPARSE_POINT | FILE_DIRECTORY_FILE, NULL, 0 ); + RtlFreeUnicodeString( &nt_link ); + if (status) + { + free( data ); + return FALSE; + } + + status = NtDeviceIoControlFile( file, NULL, NULL, NULL, &io, FSCTL_SET_REPARSE_POINT, data, size, NULL, 0 ); + free( data ); + NtClose( file ); + return !status; +} + /**************************************************************************** * WCMD_mklink */ @@ -4011,7 +4071,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;
I'm afraid this shall require some tests, esp. for the various return codes in case of failures (setting aside DIR output)
also, native cmd doesn't handle path:s longer than MAX_PATH characters, so this shall be detected and failed upon
is the use of ntdll APIs mandatory? I'd rather stick to kernel* if possible
is the use of ntdll APIs mandatory?
Use of RtlDosPathNameToNtPathName_U_WithStatus() is necessary. NtDeviceIoControlFile() could be replaced with DeviceIoControl(). NtCreateFile() is used because otherwise we would have to unnecessarily create a directory and then open it in two separate steps.
I'd rather stick to kernel* if possible
Why?
Alexandre Julliard (@julliard) commented about programs/cmd/directory.c:
- 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);
This could also use a MAXIMUM_REPARSE_DATA_BUFFER_SIZE buffer.
On Thu Nov 27 07:52:54 2025 +0000, Elizabeth Figura wrote:
is the use of ntdll APIs mandatory?
Use of RtlDosPathNameToNtPathName_U_WithStatus() is necessary. NtDeviceIoControlFile() could be replaced with DeviceIoControl(). NtCreateFile() is used because otherwise we would have to unnecessarily create a directory and then open it in two separate steps.
I'd rather stick to kernel* if possible
Why?
1) to respect the layered architecture
2) to simplify contributors work (not all of them are familiar with ntdll APIs and the differences thereof)
but fine if can limit it to its minimum