Skip to content

Commit c9b31fb

Browse files
committed
mex: add read_symlink()
1 parent 8e54c98 commit c9b31fb

File tree

10 files changed

+175
-15
lines changed

10 files changed

+175
-15
lines changed

private/get_mex_sources.m

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@
33
top (1,1) string % package directory
44
end
55

6-
win = string.empty;
6+
limits = fullfile(top, "src/limits_fs.cpp");
7+
8+
win = limits;
79
if ispc
8-
win = fullfile(top, "src/windows.cpp");
10+
win(end+1) = fullfile(top, "src/windows.cpp");
911
end
1012

1113
mac = fullfile(top, "src/macos.cpp");
1214

1315
sym = fullfile(top, "src/symlink_fs.cpp");
1416

17+
1518
srcs = {fullfile(top, "src/is_char_device.cpp"), ...
1619
fullfile(top, "src/set_permissions.cpp"), ...
1720
[fullfile(top, "src/is_rosetta.cpp"), mac], ...
18-
[fullfile(top, "src/windows_shortname.cpp"), win]
21+
[fullfile(top, "src/windows_shortname.cpp"), win], ...
1922
};
2023

2124
%% new in R2024b
2225
if isMATLABReleaseOlderThan("R2024b")
2326
srcs{end+1} = [fullfile(top, "src/is_symlink.cpp"), win, sym];
2427
srcs{end+1} = [fullfile(top, "src/create_symlink.cpp"), win, sym];
28+
srcs{end+1} = [fullfile(top, "src/read_symlink.cpp"), win, sym];
2529
end
2630

2731
end

src/create_symlink.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
#include "mexAdapter.hpp"
33

44
#include <string>
5-
// note: <string_view> causes compile failures with MSVC at least
65

76
#include <vector>
87
#include <memory>

src/limits_fs.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <cstdlib>
2+
3+
#if defined(__APPLE__) && defined(__MACH__)
4+
#include <sys/syslimits.h>
5+
#endif
6+
7+
#if __has_include(<limits.h>)
8+
#include <limits.h>
9+
#endif
10+
11+
#include "limits_fs.h"
12+
13+
14+
std::size_t fs_get_max_path()
15+
{
16+
#if defined(PATH_MAX)
17+
return PATH_MAX;
18+
#elif defined(_WIN32)
19+
return _MAX_PATH;
20+
#else
21+
return 1024;
22+
#endif
23+
}

src/limits_fs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include <cstddef>
2+
3+
std::size_t fs_get_max_path();

src/read_symlink.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// C++ Matlab MEX function using C++17 <filesystem>
2+
// https://www.mathworks.com/help/matlab/matlab_external/data-types-for-passing-mex-function-data-1.html
3+
4+
#include "mex.hpp"
5+
#include "mexAdapter.hpp"
6+
7+
#include <string>
8+
9+
#include <vector>
10+
#include <memory>
11+
12+
#include "symlink_fs.h"
13+
14+
class MexFunction : public matlab::mex::Function {
15+
public:
16+
void operator()(matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) {
17+
// boilerplate engine & ArrayFactory setup
18+
std::shared_ptr<matlab::engine::MATLABEngine> matlabEng = getEngine();
19+
20+
matlab::data::ArrayFactory factory;
21+
// wrangle inputs
22+
std::string in;
23+
24+
if (inputs.size() != 1) {
25+
matlabEng->feval(u"error", 0,
26+
std::vector<matlab::data::Array>({ factory.createScalar("One input required") }));
27+
}
28+
if ((inputs[0].getType() == matlab::data::ArrayType::MATLAB_STRING && inputs[0].getNumberOfElements() == 1)){
29+
matlab::data::TypedArray<matlab::data::MATLABString> stringArr = inputs[0];
30+
in = stringArr[0];
31+
} else if (inputs[0].getType() == matlab::data::ArrayType::CHAR){
32+
matlab::data::CharArray charArr = inputs[0];
33+
in.assign(charArr.begin(), charArr.end());
34+
} else {
35+
matlabEng->feval(u"error", 0,
36+
std::vector<matlab::data::Array>({ factory.createScalar("Mex: First input must be a scalar string or char vector") }));
37+
}
38+
39+
// actual function algorithm / computation
40+
std::string out = fs_read_symlink(in);
41+
42+
// convert to Matlab output
43+
outputs[0] = factory.createScalar(out);
44+
}
45+
};

src/symlink_fs.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#if defined(_WIN32)
22
# include "win32_fs.h"
33
#else
4-
# include "unistd.h" // for symlink()
4+
# include "unistd.h" // for symlink(), readlink()
55
#endif
66

77
#include <string>
@@ -14,9 +14,11 @@
1414
#include <sys/types.h>
1515
#include <sys/stat.h>
1616

17+
#include "limits_fs.h"
1718
#include "symlink_fs.h"
1819

1920

21+
2022
bool fs_create_symlink(std::string target, std::string link)
2123
{
2224
// confusing program errors if target is "" -- we'd never make such a symlink in real use.
@@ -77,3 +79,32 @@ bool fs_is_symlink(std::string path)
7779
return lstat(path.data(), &s) == 0 && S_ISLNK(s.st_mode);
7880
#endif
7981
}
82+
83+
84+
std::string fs_read_symlink(std::string path)
85+
{
86+
87+
if(!fs_is_symlink(path))
88+
return {};
89+
90+
std::error_code ec;
91+
92+
#if defined(__MINGW32__) || (defined(_WIN32) && !defined(__cpp_lib_filesystem))
93+
return fs_win32_final_path(path);
94+
#elif defined(__cpp_lib_filesystem)
95+
if(auto p = std::filesystem::read_symlink(path, ec); !ec)
96+
return p.generic_string();
97+
#else
98+
// https://www.man7.org/linux/man-pages/man2/readlink.2.html
99+
std::string r(fs_get_max_path(), '\0');
100+
101+
const ssize_t Lr = readlink(path.data(), r.data(), r.size());
102+
if (Lr > 0){
103+
// readlink() does not null-terminate the result
104+
r.resize(Lr);
105+
return r;
106+
}
107+
#endif
108+
109+
return {};
110+
}

src/symlink_fs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22

33
bool fs_is_symlink(std::string);
44
bool fs_create_symlink(std::string, std::string);
5+
std::string fs_read_symlink(std::string);

src/win32_fs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
bool fs_win32_is_symlink(std::string);
44

55
std::string fs_shortname(std::string);
6+
7+
std::string fs_as_posix(std::string);

src/windows.cpp

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#include <string>
22
#include <cstddef>
3-
#include <cstdlib> // for _MAX_PATH
3+
#include <cstdlib>
4+
#include <algorithm> // for std::replace
45

56
#define WIN32_LEAN_AND_MEAN
67
#include <windows.h>
78
#include <winioctl.h>
89

10+
#include "limits_fs.h"
911
#include "win32_fs.h"
1012

1113

@@ -55,6 +57,18 @@ typedef struct _REPARSE_DATA_BUFFER
5557
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
5658

5759

60+
std::string fs_as_posix(std::string path)
61+
{
62+
std::string s(path);
63+
64+
#if defined(_WIN32)
65+
std::replace(s.begin(), s.end(), '\\', '/');
66+
#endif
67+
68+
return s;
69+
}
70+
71+
5872
static bool fs_win32_get_reparse_buffer(std::string path, std::byte* buffer)
5973
{
6074
// this function is adapted from
@@ -97,6 +111,48 @@ static bool fs_win32_get_reparse_buffer(std::string path, std::byte* buffer)
97111
}
98112

99113

114+
std::string fs_win32_final_path(std::string path)
115+
{
116+
// resolves Windows symbolic links (reparse points and junctions)
117+
// it also resolves the case insensitivity of Windows paths to the disk case
118+
// PATH MUST EXIST
119+
//
120+
// References:
121+
// https://stackoverflow.com/a/50182947
122+
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
123+
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlea
124+
125+
#if defined(_WIN32)
126+
// dwDesiredAccess=0 to allow getting parameters even without read permission
127+
// FILE_FLAG_BACKUP_SEMANTICS required to open a directory
128+
HANDLE h = CreateFileA(path.data(), GENERIC_READ, FILE_SHARE_READ, nullptr,
129+
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
130+
if(h == INVALID_HANDLE_VALUE)
131+
return {};
132+
133+
std::string r(fs_get_max_path(), '\0');
134+
135+
const DWORD L = GetFinalPathNameByHandleA(h, r.data(), static_cast<DWORD>(r.size()), FILE_NAME_NORMALIZED);
136+
CloseHandle(h);
137+
if(L == 0)
138+
return {};
139+
140+
r.resize(L);
141+
142+
#ifdef __cpp_lib_starts_ends_with // C++20
143+
if (r.starts_with("\\\\?\\"))
144+
#else // C++98
145+
if (r.substr(0, 4) == "\\\\?\\")
146+
#endif
147+
r = r.substr(4);
148+
149+
return fs_as_posix(r);
150+
#else
151+
return std::string(path);
152+
#endif
153+
}
154+
155+
100156
bool fs_win32_is_symlink(std::string path)
101157
{
102158
// distinguish between Windows symbolic links and reparse points as
@@ -126,7 +182,7 @@ std::string fs_shortname(std::string in)
126182
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getshortpathnamew
127183
// the path must exist
128184

129-
std::string out(_MAX_PATH, '\0');
185+
std::string out(fs_get_max_path(), '\0');
130186
// size does not include null terminator
131187
if(auto L = GetShortPathNameA(in.data(), out.data(), static_cast<DWORD>(out.size()));
132188
L > 0 && L < out.length()){

src/windows_shortname.cpp

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,12 @@ class MexFunction : public matlab::mex::Function {
4242
}
4343

4444
// actual function algorithm / computation
45+
std::string out = "";
4546
#if defined(_WIN32)
46-
std::string s = fs_shortname(in);
47-
#else
48-
std::string s = "";
47+
out = fs_shortname(in);
4948
#endif
5049

51-
// convert to Matlab output -- even scalars are arrays in Matlab
52-
// https://www.mathworks.com/help/matlab/matlab_external/create-matlab-array-with-matlab-data-cpp-api.html
53-
// https://www.mathworks.com/help/matlab/apiref/matlab.data.arrayfactory.html
54-
55-
outputs[0] = factory.createScalar(s);
50+
// convert to Matlab output
51+
outputs[0] = factory.createScalar(out);
5652
}
5753
};

0 commit comments

Comments
 (0)