From 06d20b20c04656aabbec2ae1b1aea5182514503e Mon Sep 17 00:00:00 2001 From: hogliux Date: Mon, 9 Apr 2018 14:02:53 +0100 Subject: [PATCH] Files: Added support for creating and reading relative or special path symbolic link files --- modules/juce_core/files/juce_File.cpp | 27 ++- modules/juce_core/files/juce_File.h | 15 ++ .../native/juce_BasicNativeHeaders.h | 1 + .../native/juce_linux_CommonFile.cpp | 20 +-- modules/juce_core/native/juce_mac_Files.mm | 6 +- modules/juce_core/native/juce_win32_Files.cpp | 164 +++++++++++++++--- 6 files changed, 185 insertions(+), 48 deletions(-) diff --git a/modules/juce_core/files/juce_File.cpp b/modules/juce_core/files/juce_File.cpp index d7ded1c212..358edd2749 100644 --- a/modules/juce_core/files/juce_File.cpp +++ b/modules/juce_core/files/juce_File.cpp @@ -941,7 +941,9 @@ File File::createTempFile (StringRef fileNameEnding) return tempFile; } -bool File::createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const +bool File::createSymbolicLink (const File& linkFileToCreate, + const String& nativePathOfTarget, + bool overwriteExisting) { if (linkFileToCreate.exists()) { @@ -959,7 +961,7 @@ bool File::createSymbolicLink (const File& linkFileToCreate, bool overwriteExist #if JUCE_MAC || JUCE_LINUX // one common reason for getting an error here is that the file already exists - if (symlink (fullPath.toRawUTF8(), linkFileToCreate.getFullPathName().toRawUTF8()) == -1) + if (symlink (nativePathOfTarget.toRawUTF8(), linkFileToCreate.getFullPathName().toRawUTF8()) == -1) { jassertfalse; return false; @@ -967,15 +969,32 @@ bool File::createSymbolicLink (const File& linkFileToCreate, bool overwriteExist return true; #elif JUCE_MSVC + File targetFile (linkFileToCreate.getSiblingFile (nativePathOfTarget)); + return CreateSymbolicLink (linkFileToCreate.getFullPathName().toWideCharPointer(), - fullPath.toWideCharPointer(), - isDirectory() ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) != FALSE; + nativePathOfTarget.toWideCharPointer(), + targetFile.isDirectory() ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) != FALSE; #else jassertfalse; // symbolic links not supported on this platform! return false; #endif } +bool File::createSymbolicLink (const File& linkFileToCreate, bool overwriteExisting) const +{ + return createSymbolicLink (linkFileToCreate, getFullPathName(), overwriteExisting); +} + +#if ! JUCE_WINDOWS +File File::getLinkedTarget() const +{ + if (isSymbolicLink()) + return getSiblingFile (getNativeLinkedTarget()); + + return *this; +} +#endif + //============================================================================== MemoryMappedFile::MemoryMappedFile (const File& file, MemoryMappedFile::AccessMode mode, bool exclusive) : range (0, file.getSize()) diff --git a/modules/juce_core/files/juce_File.h b/modules/juce_core/files/juce_File.h index fd52a39d30..5f7d2d1b29 100644 --- a/modules/juce_core/files/juce_File.h +++ b/modules/juce_core/files/juce_File.h @@ -987,12 +987,27 @@ public: */ File getLinkedTarget() const; + /** Create a symbolic link to a native path and return a boolean to indicate success. + + Use this method if you want to create a link to a relative path or a special native + file path (such as a device file on Windows). + */ + static bool createSymbolicLink (const File& linkFileToCreate, + const String& nativePathOfTarget, + bool overwriteExisting); + + /** This returns the native path that the symbolic link points to. The returned path + is a native path of the current OS and can be a relative, absolute or special path. */ + String getNativeLinkedTarget() const; + #if JUCE_WINDOWS || DOXYGEN /** Windows ONLY - Creates a win32 .LNK shortcut file that links to this file. */ bool createShortcut (const String& description, const File& linkFileToCreate) const; /** Windows ONLY - Returns true if this is a win32 .LNK file. */ bool isShortcut() const; + #else + #endif //============================================================================== diff --git a/modules/juce_core/native/juce_BasicNativeHeaders.h b/modules/juce_core/native/juce_BasicNativeHeaders.h index 15f1c21b8e..7ff4e625bc 100644 --- a/modules/juce_core/native/juce_BasicNativeHeaders.h +++ b/modules/juce_core/native/juce_BasicNativeHeaders.h @@ -143,6 +143,7 @@ #include #include #include + #include #if JUCE_MINGW #include diff --git a/modules/juce_core/native/juce_linux_CommonFile.cpp b/modules/juce_core/native/juce_linux_CommonFile.cpp index c22f64f779..d2d75e267c 100644 --- a/modules/juce_core/native/juce_linux_CommonFile.cpp +++ b/modules/juce_core/native/juce_linux_CommonFile.cpp @@ -55,26 +55,16 @@ bool File::isHidden() const return getFileName().startsWithChar ('.'); } -static String getLinkedFile (const String& file) -{ - HeapBlock buffer (8194); - const int numBytes = (int) readlink (file.toRawUTF8(), buffer, 8192); - return String::fromUTF8 (buffer, jmax (0, numBytes)); -} - bool File::isSymbolicLink() const { - return getLinkedFile (getFullPathName()).isNotEmpty(); + return getNativeLinkedTarget().isNotEmpty(); } -File File::getLinkedTarget() const +String File::getNativeLinkedTarget() const { - String f (getLinkedFile (getFullPathName())); - - if (f.isNotEmpty()) - return getSiblingFile (f); - - return *this; + HeapBlock buffer (8194); + const int numBytes = (int) readlink (getFullPathName().toRawUTF8(), buffer, 8192); + return String::fromUTF8 (buffer, jmax (0, numBytes)); } //============================================================================== diff --git a/modules/juce_core/native/juce_mac_Files.mm b/modules/juce_core/native/juce_mac_Files.mm index c95862bb85..c055621e03 100644 --- a/modules/juce_core/native/juce_mac_Files.mm +++ b/modules/juce_core/native/juce_mac_Files.mm @@ -281,12 +281,12 @@ bool File::isSymbolicLink() const return getFileLink (fullPath) != nil; } -File File::getLinkedTarget() const +String File::getNativeLinkedTarget() const { if (NSString* dest = getFileLink (fullPath)) - return getSiblingFile (nsStringToJuce (dest)); + return nsStringToJuce (dest); - return *this; + return {}; } //============================================================================== diff --git a/modules/juce_core/native/juce_win32_Files.cpp b/modules/juce_core/native/juce_win32_Files.cpp index 35bb189abd..8287edaa41 100644 --- a/modules/juce_core/native/juce_win32_Files.cpp +++ b/modules/juce_core/native/juce_win32_Files.cpp @@ -30,6 +30,36 @@ namespace juce //============================================================================== namespace WindowsFileHelpers { + //============================================================================== + #if JUCE_WINDOWS + 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; + } DUMMYUNIONNAME; + } *PREPARSE_DATA_BUFFER, REPARSE_DATA_BUFFER; + #endif + + //============================================================================== DWORD getAtts (const String& path) noexcept { return GetFileAttributes (path.toWideCharPointer()); @@ -666,9 +696,104 @@ bool File::isShortcut() const return hasFileExtension (".lnk"); } -File File::getLinkedTarget() const +static String readWindowsLnkFile (File lnkFile, bool wantsAbsolutePath) +{ + if (! lnkFile.exists()) + lnkFile = File (lnkFile.getFullPathName() + ".lnk"); + + if (lnkFile.exists()) + { + ComSmartPtr shellLink; + ComSmartPtr persistFile; + + if (SUCCEEDED (shellLink.CoCreateInstance (CLSID_ShellLink)) + && SUCCEEDED (shellLink.QueryInterface (persistFile)) + && SUCCEEDED (persistFile->Load (lnkFile.getFullPathName().toWideCharPointer(), STGM_READ)) + && (! wantsAbsolutePath || SUCCEEDED (shellLink->Resolve (0, SLR_ANY_MATCH | SLR_NO_UI)))) + { + WIN32_FIND_DATA winFindData; + WCHAR resolvedPath [MAX_PATH]; + + DWORD flags = SLGP_UNCPRIORITY; + + if (! wantsAbsolutePath) + flags |= SLGP_RAWPATH; + + if (SUCCEEDED (shellLink->GetPath (resolvedPath, MAX_PATH, &winFindData, flags))) + return resolvedPath; + } + } + + return {}; +} + +static String readWindowsShortcutOrLink (const File& shortcut, bool wantsAbsolutePath) { #if JUCE_WINDOWS + if (! wantsAbsolutePath) + { + HANDLE h = CreateFile (shortcut.getFullPathName().toWideCharPointer(), + GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + 0); + + if (h != INVALID_HANDLE_VALUE) + { + HeapBlock reparseData; + + reparseData.calloc (1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE); + DWORD bytesReturned = 0; + + bool success = DeviceIoControl (h, FSCTL_GET_REPARSE_POINT, nullptr, 0, + reparseData.getData(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, + &bytesReturned, nullptr) != 0; + CloseHandle (h); + + if (success) + { + if (IsReparseTagMicrosoft (reparseData->ReparseTag)) + { + String targetPath; + + switch (reparseData->ReparseTag) + { + case IO_REPARSE_TAG_SYMLINK: + { + auto& symlinkData = reparseData->SymbolicLinkReparseBuffer; + targetPath = {symlinkData.PathBuffer + (symlinkData.SubstituteNameOffset / sizeof (WCHAR)), + symlinkData.SubstituteNameLength / sizeof (WCHAR)}; + } + break; + + case IO_REPARSE_TAG_MOUNT_POINT: + { + auto& mountData = reparseData->MountPointReparseBuffer; + targetPath = {mountData.PathBuffer + (mountData.SubstituteNameOffset / sizeof (WCHAR)), + mountData.SubstituteNameLength / sizeof (WCHAR)}; + } + break; + + default: + break; + } + + if (targetPath.isNotEmpty()) + { + const StringRef prefix ("\\??\\"); + + if (targetPath.startsWith (prefix)) + targetPath = targetPath.substring (prefix.length()); + + return targetPath; + } + } + } + } + } + + if (! wantsAbsolutePath) + return readWindowsLnkFile (shortcut, false); + typedef DWORD (WINAPI* GetFinalPathNameByHandleFunc) (HANDLE, LPTSTR, DWORD, DWORD); static GetFinalPathNameByHandleFunc getFinalPathNameByHandle @@ -676,7 +801,7 @@ File File::getLinkedTarget() const if (getFinalPathNameByHandle != nullptr) { - HANDLE h = CreateFile (getFullPathName().toWideCharPointer(), + HANDLE h = CreateFile (shortcut.getFullPathName().toWideCharPointer(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); @@ -695,8 +820,7 @@ File File::getLinkedTarget() const // It turns out that GetFinalPathNameByHandleW prepends \\?\ to the path. // This is not a bug, it's feature. See MSDN for more information. - return File (path.startsWith (prefix) ? path.substring (prefix.length()) - : path); + return path.startsWith (prefix) ? path.substring (prefix.length()) : path; } } @@ -705,30 +829,18 @@ File File::getLinkedTarget() const } #endif - File result (*this); - String p (getFullPathName()); + // as last resort try the resolve method of the ShellLink + return readWindowsLnkFile (shortcut, true); +} - if (! exists()) - p += ".lnk"; - else if (! hasFileExtension (".lnk")) - return result; +String File::getNativeLinkedTarget() const +{ + return readWindowsShortcutOrLink (*this, false); +} - ComSmartPtr shellLink; - ComSmartPtr persistFile; - - if (SUCCEEDED (shellLink.CoCreateInstance (CLSID_ShellLink)) - && SUCCEEDED (shellLink.QueryInterface (persistFile)) - && SUCCEEDED (persistFile->Load (p.toWideCharPointer(), STGM_READ)) - && SUCCEEDED (shellLink->Resolve (0, SLR_ANY_MATCH | SLR_NO_UI))) - { - WIN32_FIND_DATA winFindData; - WCHAR resolvedPath [MAX_PATH]; - - if (SUCCEEDED (shellLink->GetPath (resolvedPath, MAX_PATH, &winFindData, SLGP_UNCPRIORITY))) - result = File (resolvedPath); - } - - return result; +File File::getLinkedTarget() const +{ + return readWindowsShortcutOrLink (*this, true); } bool File::createShortcut (const String& description, const File& linkFileToCreate) const