]> git.tdb.fi Git - libs/core.git/commitdiff
Implement symlink support on Windows
authorMikko Rasa <tdb@tdb.fi>
Sun, 3 Sep 2023 22:44:31 +0000 (01:44 +0300)
committerMikko Rasa <tdb@tdb.fi>
Sun, 3 Sep 2023 23:02:02 +0000 (02:02 +0300)
Also avoid lossage when statting reparse points of the appexeclink type.

source/core/windows/winapi.h
source/fs/stat_private.h
source/fs/windows/stat.cpp
source/fs/windows/utils.cpp

index dca088078f05de073a4b49c3f72b76ca25b51c04..624de0745ac2f1e0f20609d7114ee53f4f8d7a42 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef MSP_CORE_WINAPI_H_
 #define MSP_CORE_WINAPI_H_
 
-#ifndef WIN32_LEAN_AND_MEAN
+#if !defined(WIN32_LEAN_AND_MEAN) && !defined(MSP_FULL_WINAPI)
 #define WIN32_LEAN_AND_MEAN
 #endif
 #ifndef NOMINMAX
index 23019a5b6982c22df324cee04ea3a16de7cd60e9..5806f62a469f9bf6cb7291d0bfbd8bbd533df035 100644 (file)
@@ -16,7 +16,9 @@ struct Stat::Private
        Private(const Private &);
        ~Private();
 
-#ifndef _WIN32
+#ifdef _WIN32
+       static Stat sys_stat(const Path &, bool);
+#else
        /* This is here because it needs access to private members of Stat, but we
        can't expose the system stat struct in the public header */
        static Stat from_struct_stat(const struct stat &);
index ee794767f7eaded5f9f7071e6434d07d4fc64e7a..58c946d8377eff1bbce70aa9fb378937426b3f64 100644 (file)
@@ -1,3 +1,4 @@
+#define MSP_FULL_WINAPI
 #include <msp/core/winapi.h>
 #include <aclapi.h>
 #include <msp/core/systemerror.h>
@@ -37,6 +38,16 @@ string get_account_name(PSID sid)
        return Msp::format("%s/%s", name, domain);
 }
 
+DWORD get_reparse_point_tag(HANDLE handle)
+{
+       char data[1024];
+       DWORD size;
+       if(DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, nullptr, 0, data, sizeof(data), &size, nullptr))
+               return *reinterpret_cast<DWORD *>(data);
+       else
+               return 0;
+}
+
 }
 
 
@@ -56,30 +67,39 @@ Stat::Private::~Private()
                HeapFree(GetProcessHeap(), 0, group_id);
 }
 
-void Stat::Private::fill_owner_info(Stat::OwnerInfo &result)
-{
-       if(owner_id!=INVALID_UID)
-               result.owner = get_account_name(owner_id);
-       else
-               result.owner = "None";
-
-       if(group_id!=INVALID_GID)
-               result.group = get_account_name(group_id);
-       else
-               result.group = "None";
-}
-
-
-Stat Stat::stat(const Path &path)
+Stat Stat::Private::sys_stat(const Path &path, bool follow_symlinks)
 {
        HANDLE handle;
-       handle = CreateFile(path.str().c_str(), READ_CONTROL, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+       DWORD reparse_tag = 0;
+       DWORD flags = FILE_ATTRIBUTE_NORMAL|FILE_FLAG_BACKUP_SEMANTICS;
+       if(!follow_symlinks)
+               flags |= FILE_FLAG_OPEN_REPARSE_POINT;
+       handle = CreateFile(path.str().c_str(), READ_CONTROL, FILE_SHARE_READ, nullptr, OPEN_EXISTING, flags, nullptr);
        if(handle==INVALID_HANDLE_VALUE)
        {
                DWORD err = GetLastError();
                if(err==ERROR_FILE_NOT_FOUND || err==ERROR_PATH_NOT_FOUND)
                        return Stat();
-               else
+               else if(err==ERROR_CANT_ACCESS_FILE && follow_symlinks)
+               {
+                       flags |= FILE_FLAG_OPEN_REPARSE_POINT;
+                       handle = CreateFile(path.str().c_str(), READ_CONTROL, FILE_SHARE_READ, nullptr, OPEN_EXISTING, flags, nullptr);
+                       if(handle!=INVALID_HANDLE_VALUE)
+                       {
+                               reparse_tag = get_reparse_point_tag(handle);
+                               /* App exec links are used for apps installed from the Microsoft
+                               Store, but the apps themselves are stored in a location regular
+                               users aren't permitted to access.  Get information for the reparse
+                               point itself even if following symlinks was requested. */
+                               if(reparse_tag!=IO_REPARSE_TAG_APPEXECLINK)
+                               {
+                                       CloseHandle(handle);
+                                       handle = INVALID_HANDLE_VALUE;
+                               }
+                       }
+               }
+
+               if(handle==INVALID_HANDLE_VALUE)
                        throw system_error(format("CreateFile(%s)", path), err);
        }
 
@@ -95,7 +115,14 @@ Stat Stat::stat(const Path &path)
        if(info.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)
                result.type = DIRECTORY;
        else
-               result.type = REGULAR;
+       {
+               if((info.dwFileAttributes&FILE_ATTRIBUTE_REPARSE_POINT) && !reparse_tag)
+                       reparse_tag = get_reparse_point_tag(handle);
+               if(reparse_tag==IO_REPARSE_TAG_SYMLINK)
+                       result.type = SYMLINK;
+               else
+                       result.type = REGULAR;
+       }
 
        result.size = FileSize(info.nFileSizeHigh)<<32 | info.nFileSizeLow;
        result.alloc_size = (result.size+511)&~511;
@@ -123,9 +150,28 @@ Stat Stat::stat(const Path &path)
        return result;
 }
 
+void Stat::Private::fill_owner_info(Stat::OwnerInfo &result)
+{
+       if(owner_id!=INVALID_UID)
+               result.owner = get_account_name(owner_id);
+       else
+               result.owner = "None";
+
+       if(group_id!=INVALID_GID)
+               result.group = get_account_name(group_id);
+       else
+               result.group = "None";
+}
+
+
+Stat Stat::stat(const Path &path)
+{
+       return Private::sys_stat(path, true);
+}
+
 Stat Stat::lstat(const Path &path)
 {
-       return stat(path);
+       return Private::sys_stat(path, false);
 }
 
 bool exists(const Path &path)
index a1c25136908d11a3248084fdce59f794490a5dcf..53e21f789360c09f671c44079bd0d627c252f04b 100644 (file)
@@ -1,6 +1,9 @@
+#define MSP_FULL_WINAPI
 #include <msp/core/winapi.h>
 #include <msp/core/except.h>
 #include <msp/core/systemerror.h>
+#include <msp/stringcodec/utf16.h>
+#include <msp/stringcodec/utf8.h>
 #include "dir.h"
 #include "utils.h"
 
@@ -11,8 +14,29 @@ namespace FS {
 
 Path readlink(const Path &link)
 {
-       (void)link;
-       throw unsupported("readlink");
+       DWORD attrs = GetFileAttributes(link.str().c_str());
+       if(!(attrs&FILE_ATTRIBUTE_REPARSE_POINT))
+               throw system_error("readlink", ERROR_NOT_A_REPARSE_POINT);
+
+       HANDLE handle = CreateFile(link.str().c_str(), READ_CONTROL, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OPEN_REPARSE_POINT, nullptr);
+       if(handle==INVALID_HANDLE_VALUE)
+               throw system_error("CreateFile");
+
+       char data[1024];
+       DWORD length;
+       bool success = DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, nullptr, 0, data, sizeof(data), &length, nullptr);
+       CloseHandle(handle);
+       if(!success)
+               throw system_error("DeviceIoControl(FSCTL_GET_REPARSE_POINT)");
+
+       DWORD reparse_tag = *reinterpret_cast<DWORD *>(data);
+       if(reparse_tag!=IO_REPARSE_TAG_SYMLINK)
+               throw runtime_error("reparse point is not a symlink");
+
+       // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/b41f1cbf-10df-4a47-98d4-1c52a833d913
+       WORD start = 20+*reinterpret_cast<WORD *>(data+8);
+       WORD end = start+*reinterpret_cast<WORD *>(data+10);
+       return StringCodec::transcode<StringCodec::Utf16Le, StringCodec::Utf8>(string(data+start, data+end));
 }
 
 Path realpath(const Path &path)