--- filemanip.cc | 27 ++++++++++++ filemanip.h | 1 + mklink2.cc | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+)
diff --git a/filemanip.cc b/filemanip.cc index 48f5117..ca5e4ac 100644 --- a/filemanip.cc +++ b/filemanip.cc @@ -247,6 +247,33 @@ mklongpath (wchar_t *tgt, const char *src, size_t len) return 0; } +int +mklongrelpath (wchar_t *tgt, const char *src, size_t len) +{ + wchar_t *tp; + size_t n; + mbstate_t mb; + + tp = tgt; + memset (&mb, 0, sizeof mb); + + while (len > 0) + { + n = mbrtowc (tp, src, 6, &mb); + if (n == (size_t) -1 || n == (size_t) -2) + return -1; + if (n == 0) + break; + src += n; + /* Transform char according to Cygwin rules. */ + if (*tp < 128) + *tp = tfx_chars[*tp]; + ++tp; + --len; + } + return 0; +} + /* Replacement functions for Win32 API functions. The joke here is that the replacement functions always use the FILE_OPEN_FOR_BACKUP_INTENT flag. */ diff --git a/filemanip.h b/filemanip.h index 451211f..e83b8e9 100644 --- a/filemanip.h +++ b/filemanip.h @@ -34,6 +34,7 @@ size_t get_file_size (const std::string& ); std::string backslash (const std::string& s); const char * trail (const char *, const char *); int mklongpath (wchar_t *tgt, const char *src, size_t len); +int mklongrelpath (wchar_t *tgt, const char *src, size_t len); FILE *nt_wfopen (const wchar_t *wpath, const char *mode, mode_t perms); FILE *nt_fopen (const char *path, const char *mode, mode_t perms = 0644); diff --git a/mklink2.cc b/mklink2.cc index 3fe5b15..6e7a002 100644 --- a/mklink2.cc +++ b/mklink2.cc @@ -196,6 +196,113 @@ mkwslsymlink (const char *from, const char *to) return NT_SUCCESS (status) ? 0 : 1; } +static int +mknativesymlink (const char *from, const char *to) +{ + /* Construct the absolute Windows path of 'to' ... */ + std::string absto; + if (to[0] == '/') + { + absto = get_root_dir(); + absto.append(to); + } + else + { + /* 'from' is already absolute */ + absto.append(from); + /* remove the last pathname component */ + size_t i = absto.rfind('/'); + if (i != std::string::npos) + absto.resize(i); + /* ... and add relative path 'to'. */ + absto.append("/"); + absto.append(to); + } + + /* ... so we can discover if it's a file or directory (if it already exists) */ + size_t abstlen = strlen (absto.c_str()) + 7; + wchar_t wabsto[abstlen]; + mklongpath (wabsto, absto.c_str(), abstlen); + wabsto[1] = '?'; + + bool isdir = FALSE; + bool isdir_known = FALSE; + HANDLE fh; + NTSTATUS status; + UNICODE_STRING uto; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + RtlInitUnicodeString (&uto, wabsto); + InitializeObjectAttributes (&attr, &uto, OBJ_CASE_INSENSITIVE, NULL, NULL); + status = NtOpenFile (&fh, FILE_READ_ATTRIBUTES, &attr, &io, FILE_SHARE_VALID_FLAGS, + FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT); + if (NT_SUCCESS (status)) + { + FILE_BASIC_INFORMATION fi; + status = NtQueryInformationFile(fh, &io, &fi, sizeof(fi), FileBasicInformation); + if (!NT_SUCCESS (status)) + Log (LOG_BABBLE) << "Querying " << absto << " failed " << std::hex << status << endLog; + else + { + isdir = fi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY; + isdir_known = TRUE; + Log (LOG_BABBLE) << "Querying " << absto << " isdir is " << isdir << endLog; + } + NtClose(fh); + } + else + { + Log (LOG_BABBLE) << "Opening " << absto << " failed " << std::hex << status << endLog; + } + + /* + Fail, if we failed to determine if the symlink target is a directory + (probably because it doesn't exist (yet)) + + (We could guess that it's a file, since that works for Cygwin (and WSL), + which don't care if the directory flag in the symlink is wrong (when the + target comes into existence), but native tools will fail. + */ + + if (!isdir_known) + return 1; + + /* Try to create the native symlink. */ + const size_t flen = strlen (from) + 7; + WCHAR wfrom[flen]; + mklongpath (wfrom, from, flen); + wfrom[1] = '?'; + + size_t tlen = strlen (to) + 7; + wchar_t wto[tlen]; + if (to[0] == '/') + { + absto = get_root_dir(); + absto.append(to); + mklongpath (wto, to, tlen); + wto[1] = '?'; + } + else + { + mklongrelpath (wto, to, tlen); + } + + DWORD flags = isdir ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0; + /* Windows 10 1703 and later allow unprivileged symlink creation when + 'Developer Mode' is on.*/ + VersionInfo v = GetVer(); + if ((v.major() > 10) || + ((v.major() == 10) && (v.buildNumber() >= 15063))) + flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + + status = CreateSymbolicLinkW (wfrom, wto, flags); + + if (!status) + Log (LOG_PLAIN) << "Linking " << from << " to " << to << " failed " << std::hex << GetLastError() << endLog; + + return !status; +} + int mkcygsymlink (const char *from, const char *to) { @@ -205,6 +312,12 @@ mkcygsymlink (const char *from, const char *to) return 0; } + if (symlinkType == SymlinkTypeNative) + { + if (!mknativesymlink (from, to)) + return 0; + } + /* fall back to magic symlink, if selected method fails */ return mkmagiccygsymlink(from, to); } -- 2.32.0