Skip to content

Commit 4ecb5f5

Browse files
committed
mex: create_symlink()
1 parent 364ded2 commit 4ecb5f5

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed

private/get_mex_sources.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
srcs = {fullfile(top, "src/is_char_device.cpp"), ...
1414
fullfile(top, "src/set_permissions.cpp"), ...
15+
fullfile(top, "src/create_symlink.cpp"), ...
1516
[fullfile(top, "src/is_rosetta.cpp"), mac], ...
1617
[fullfile(top, "src/windows_shortname.cpp"), win]
1718
};

src/create_symlink.cpp

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#include "mex.hpp"
2+
#include "mexAdapter.hpp"
3+
4+
#include <string>
5+
// note: <string_view> causes compile failures with MSVC at least
6+
7+
#include <vector>
8+
#include <memory>
9+
10+
#if defined(_WIN32)
11+
# define WIN32_LEAN_AND_MEAN
12+
# include <windows.h>
13+
#else
14+
# include <unistd.h> // for symlink()
15+
#endif
16+
17+
#if __has_include(<filesystem>)
18+
# include <filesystem>
19+
# include <system_error>
20+
#endif
21+
22+
23+
bool fs_create_symlink(std::string target, std::string link)
24+
{
25+
// confusing program errors if target is "" -- we'd never make such a symlink in real use.
26+
if(target.empty())
27+
return false;
28+
29+
// macOS needs empty check to avoid SIGABRT
30+
if(link.empty())
31+
return false;
32+
33+
#if defined(__MINGW32__) || (defined(_WIN32) && !defined(__cpp_lib_filesystem))
34+
35+
const DWORD attr = GetFileAttributesA(target.data());
36+
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfileattributesa
37+
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
38+
39+
if (attr == INVALID_FILE_ATTRIBUTES)
40+
return false;
41+
42+
DWORD p = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
43+
if(attr & FILE_ATTRIBUTE_DIRECTORY)
44+
p |= SYMBOLIC_LINK_FLAG_DIRECTORY;
45+
46+
return CreateSymbolicLinkA(link.data(), target.data(), p);
47+
#elif defined(__cpp_lib_filesystem)
48+
std::error_code ec;
49+
std::filesystem::path t(target);
50+
51+
bool isdir = std::filesystem::is_directory(t, ec);
52+
if (ec)
53+
return false;
54+
55+
isdir
56+
? std::filesystem::create_directory_symlink(t, link, ec)
57+
: std::filesystem::create_symlink(t, link, ec);
58+
59+
return !ec;
60+
#else
61+
// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/symlink.2.html
62+
// https://linux.die.net/man/3/symlink
63+
return symlink(target.data(), link.data()) == 0;
64+
#endif
65+
66+
}
67+
68+
69+
class MexFunction : public matlab::mex::Function {
70+
public:
71+
void operator()(matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) {
72+
// boilerplate engine & ArrayFactory setup
73+
std::shared_ptr<matlab::engine::MATLABEngine> matlabEng = getEngine();
74+
75+
matlab::data::ArrayFactory factory;
76+
// wrangle inputs
77+
std::string target, link;
78+
79+
if (inputs.size() != 2) {
80+
matlabEng->feval(u"error", 0,
81+
std::vector<matlab::data::Array>({ factory.createScalar("Two inputs required") }));
82+
}
83+
84+
if ((inputs[0].getType() == matlab::data::ArrayType::MATLAB_STRING && inputs[0].getNumberOfElements() == 1)){
85+
matlab::data::TypedArray<matlab::data::MATLABString> stringArr = inputs[0];
86+
target = stringArr[0];
87+
} else if (inputs[0].getType() == matlab::data::ArrayType::CHAR){
88+
matlab::data::CharArray charArr = inputs[0];
89+
target.assign(charArr.begin(), charArr.end());
90+
} else {
91+
matlabEng->feval(u"error", 0,
92+
std::vector<matlab::data::Array>({ factory.createScalar("Mex: First input must be a scalar string or char vector") }));
93+
}
94+
95+
if ((inputs[1].getType() == matlab::data::ArrayType::MATLAB_STRING && inputs[1].getNumberOfElements() == 1)){
96+
matlab::data::TypedArray<matlab::data::MATLABString> stringArr = inputs[1];
97+
link = stringArr[0];
98+
} else if (inputs[1].getType() == matlab::data::ArrayType::CHAR){
99+
matlab::data::CharArray charArr = inputs[1];
100+
link.assign(charArr.begin(), charArr.end());
101+
} else {
102+
matlabEng->feval(u"error", 0,
103+
std::vector<matlab::data::Array>({ factory.createScalar("Mex: Second input must be a scalar string or char vector") }));
104+
}
105+
106+
// actual function algorithm / computation
107+
bool y = fs_create_symlink(target, link);
108+
109+
// convert to Matlab output
110+
// https://www.mathworks.com/help/matlab/matlab_external/create-matlab-array-with-matlab-data-cpp-api.html
111+
// https://www.mathworks.com/help/matlab/apiref/matlab.data.arrayfactory.html
112+
113+
outputs[0] = factory.createScalar<bool>(y);
114+
}
115+
};

0 commit comments

Comments
 (0)