5#ifndef LINE_SOLVERS_WRAPPERS_LDES_LDES_PROBE_H
6#define LINE_SOLVERS_WRAPPERS_LDES_LDES_PROBE_H
38#include <sys/utsname.h>
46inline bool is_file(
const std::string& p) {
48 return !p.empty() && ::stat(p.c_str(), &st) == 0 && S_ISREG(st.st_mode);
51inline bool is_exec(
const std::string& p) {
52 return is_file(p) && ::access(p.c_str(), X_OK) == 0;
55inline std::string env_or_empty(
const char* name) {
56 const char* v = std::getenv(name);
57 return v !=
nullptr ? std::string(v) : std::string();
61inline std::string exe_dir() {
63 const ssize_t n = ::readlink(
"/proc/self/exe", buf,
sizeof(buf) - 1);
64 if (n <= 0)
return std::string();
65 const std::string p(buf,
static_cast<std::size_t
>(n));
66 const std::size_t s = p.find_last_of(
'/');
67 return s == std::string::npos ? std::string() : p.substr(0, s);
70inline std::string cwd_path() {
72 return ::getcwd(buf,
sizeof(buf)) !=
nullptr ? std::string(buf) : std::string();
85inline std::vector<std::string> engine_dirs() {
86 std::vector<std::string> out;
92 const std::string forced = env_or_empty(
"LINE_LDES_DIR");
93 if (!forced.empty()) {
94 out.push_back(forced);
97 const std::string roots[2] = {exe_dir(), cwd_path()};
98 for (
int r = 0; r < 2; ++r) {
99 if (roots[r].empty())
continue;
100 std::string dir = roots[r];
101 for (
int k = 0; k < 7; ++k) {
103 out.push_back(dir +
"/common");
111inline std::string find_engine_dir_uncached() {
112 const std::vector<std::string> cand = engine_dirs();
113 for (std::size_t i = 0; i < cand.size(); ++i)
114 if (is_file(cand[i] +
"/ldes.jar") || is_file(cand[i] +
"/ldes"))
return cand[i];
115 return std::string();
119static const int EM_386_ID = 0x03;
120static const int EM_ARM_ID = 0x28;
121static const int EM_X86_64_ID = 0x3E;
122static const int EM_AARCH64_ID = 0xB7;
125inline int elf_machine(
const std::string& path) {
126 std::ifstream in(path.c_str(), std::ios::binary);
129 in.read(
reinterpret_cast<char*
>(h), 20);
130 if (in.gcount() < 20)
return 0;
131 if (!(h[0] == 0x7F && h[1] ==
'E' && h[2] ==
'L' && h[3] ==
'F'))
return 0;
132 return h[5] == 1 ? (h[18] | (h[19] << 8)) : ((h[18] << 8) | h[19]);
136inline int host_elf_machine() {
138 if (::uname(&u) != 0)
return 0;
139 const std::string m(u.machine);
140 if (m ==
"x86_64" || m ==
"amd64")
return EM_X86_64_ID;
141 if (m ==
"aarch64" || m ==
"arm64")
return EM_AARCH64_ID;
142 if (m ==
"i386" || m ==
"i486" || m ==
"i586" || m ==
"i686" || m ==
"x86")
return EM_386_ID;
143 if (m.compare(0, 3,
"arm") == 0)
return EM_ARM_ID;
153inline std::string native_ldes_path(
const std::string& dir) {
154 if (dir.empty())
return std::string();
155 if (!env_or_empty(
"LINE_LDES_FORCE_JAR").empty())
return std::string();
156 const std::string cand = dir +
"/ldes";
157 if (!is_exec(cand))
return std::string();
158 const int host = host_elf_machine();
159 const int bin = elf_machine(cand);
160 if (host != 0 && bin != 0 && host != bin)
return std::string();
165inline bool incompatible_native(
const std::string& dir) {
166 if (dir.empty())
return false;
167 const std::string cand = dir +
"/ldes";
168 if (!is_file(cand))
return false;
169 const int host = host_elf_machine();
170 const int bin = elf_machine(cand);
171 return host != 0 && bin != 0 && host != bin;
182inline std::string find_java() {
183 const std::string forced = env_or_empty(
"LINE_JAVA");
184 if (!forced.empty())
return is_exec(forced) ? forced : std::string();
185 const std::string home = env_or_empty(
"JAVA_HOME");
186 if (!home.empty() && is_exec(home +
"/bin/java"))
return home +
"/bin/java";
187 const std::string path = env_or_empty(
"PATH");
189 while (b <= path.size()) {
190 const std::size_t e = path.find(
':', b);
191 const std::string dir = path.substr(b, e == std::string::npos ? std::string::npos : e - b);
192 if (!dir.empty() && is_exec(dir +
"/java"))
return dir +
"/java";
193 if (e == std::string::npos)
break;
196 return std::string();
207inline std::string no_backend_message(
const std::string& dir) {
209 const std::string host =
210 ::uname(&u) == 0 ? std::string(u.sysname) +
"/" + u.machine : std::string(
"this host");
211 const bool have_java = !find_java().empty();
214 const std::string forced = env_or_empty(
"LINE_LDES_DIR");
216 ?
"no LDES engine was found beside this executable or under the working "
217 "directory (looking for `ldes` or `ldes.jar` in each ancestor and its "
219 :
"$LINE_LDES_DIR names '" + forced +
220 "', which holds neither `ldes` nor `ldes.jar`";
221 }
else if (incompatible_native(dir)) {
222 why =
"the native LDES binary in '" + dir +
223 "' was built for a different CPU architecture than this host (" + host +
")" +
224 (have_java ?
", and there is no ldes.jar beside it to run instead"
225 :
", and no Java runtime was found to run ldes.jar");
227 why =
"'" + dir +
"' holds no runnable engine for this host (" + host +
")" +
228 (have_java ?
"" :
" and no Java runtime was found to run ldes.jar");
230 return "cannot run SolverLDES: " + why +
231 ". Install a JRE/JDK 8 or newer and put `java` on PATH, or set $LINE_JAVA or "
232 "$JAVA_HOME; point $LINE_LDES_DIR at the directory holding ldes/ldes.jar when it is "
233 "not beside the executable";
240 static const std::string dir = detail::find_engine_dir_uncached();
248 (!detail::find_java().empty() &&
bool ldes_is_available()
True when this machine can run the engine at all, by either image.
const std::string & ldes_engine_dir()
The directory holding the engine, or empty when there is none.
std::vector< double > stat(const Matrix< double > &A)
Stationary distribution of a stochastic matrix: the left eigenvector for eigenvalue 1,...