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");
107#ifdef LINE_LDES_BUILD_DIR
109 out.push_back(LINE_LDES_BUILD_DIR);
115inline std::string find_engine_dir_uncached() {
116 const std::vector<std::string> cand = engine_dirs();
117 for (std::size_t i = 0; i < cand.size(); ++i)
118 if (is_file(cand[i] +
"/ldes.jar") || is_file(cand[i] +
"/ldes"))
return cand[i];
119 return std::string();
123static const int EM_386_ID = 0x03;
124static const int EM_ARM_ID = 0x28;
125static const int EM_X86_64_ID = 0x3E;
126static const int EM_AARCH64_ID = 0xB7;
129inline int elf_machine(
const std::string& path) {
130 std::ifstream
in(path.c_str(), std::ios::binary);
133 in.read(
reinterpret_cast<char*
>(h), 20);
134 if (
in.gcount() < 20)
return 0;
135 if (!(h[0] == 0x7F && h[1] ==
'E' && h[2] ==
'L' && h[3] ==
'F'))
return 0;
136 return h[5] == 1 ? (h[18] | (h[19] << 8)) : ((h[18] << 8) | h[19]);
140inline int host_elf_machine() {
142 if (::uname(&u) != 0)
return 0;
143 const std::string m(u.machine);
144 if (m ==
"x86_64" || m ==
"amd64")
return EM_X86_64_ID;
145 if (m ==
"aarch64" || m ==
"arm64")
return EM_AARCH64_ID;
146 if (m ==
"i386" || m ==
"i486" || m ==
"i586" || m ==
"i686" || m ==
"x86")
return EM_386_ID;
147 if (m.compare(0, 3,
"arm") == 0)
return EM_ARM_ID;
157inline std::string native_ldes_path(
const std::string& dir) {
158 if (dir.empty())
return std::string();
159 if (!env_or_empty(
"LINE_LDES_FORCE_JAR").empty())
return std::string();
160 const std::string cand = dir +
"/ldes";
161 if (!is_exec(cand))
return std::string();
162 const int host = host_elf_machine();
163 const int bin = elf_machine(cand);
164 if (host != 0 && bin != 0 && host != bin)
return std::string();
169inline bool incompatible_native(
const std::string& dir) {
170 if (dir.empty())
return false;
171 const std::string cand = dir +
"/ldes";
172 if (!is_file(cand))
return false;
173 const int host = host_elf_machine();
174 const int bin = elf_machine(cand);
175 return host != 0 && bin != 0 && host != bin;
186inline std::string find_java() {
187 const std::string forced = env_or_empty(
"LINE_JAVA");
188 if (!forced.empty())
return is_exec(forced) ? forced : std::string();
189 const std::string home = env_or_empty(
"JAVA_HOME");
190 if (!home.empty() && is_exec(home +
"/bin/java"))
return home +
"/bin/java";
191 const std::string path = env_or_empty(
"PATH");
193 while (b <= path.size()) {
194 const std::size_t e = path.find(
':', b);
195 const std::string dir = path.substr(b, e == std::string::npos ? std::string::npos : e - b);
196 if (!dir.empty() && is_exec(dir +
"/java"))
return dir +
"/java";
197 if (e == std::string::npos)
break;
200 return std::string();
211inline std::string no_backend_message(
const std::string& dir) {
213 const std::string host =
214 ::uname(&u) == 0 ? std::string(u.sysname) +
"/" + u.machine : std::string(
"this host");
215 const bool have_java = !find_java().empty();
218 const std::string forced = env_or_empty(
"LINE_LDES_DIR");
220 ?
"no LDES engine was found beside this executable or under the working "
221 "directory (looking for `ldes` or `ldes.jar` in each ancestor and its "
223 :
"$LINE_LDES_DIR names '" + forced +
224 "', which holds neither `ldes` nor `ldes.jar`";
225 }
else if (incompatible_native(dir)) {
226 why =
"the native LDES binary in '" + dir +
227 "' was built for a different CPU architecture than this host (" + host +
")" +
228 (have_java ?
", and there is no ldes.jar beside it to run instead"
229 :
", and no Java runtime was found to run ldes.jar");
231 why =
"'" + dir +
"' holds no runnable engine for this host (" + host +
")" +
232 (have_java ?
"" :
" and no Java runtime was found to run ldes.jar");
234 if (dir.empty() || !have_java)
235 return "cannot run SolverLDES: " + why +
236 ". Build the native engine (`cmake --build <build-dir> --target ldes`) and point "
237 "$LINE_LDES_DIR at the directory holding `ldes` when it is not beside the executable; "
238 "to run `ldes.jar` instead, install a JRE/JDK 8 or newer and put `java` on PATH, or set "
239 "$LINE_JAVA or $JAVA_HOME";
240 return "cannot run SolverLDES: " + why +
241 ". Point $LINE_LDES_DIR at a directory holding an `ldes` built for this host or an `ldes.jar`";
248 static const std::string dir = detail::find_engine_dir_uncached();
256 (!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.
bool in(const std::string &s, std::initializer_list< const char * > set)
std::vector< double > stat(const Matrix< double > &A)
Stationary distribution of a stochastic matrix: the left eigenvector for eigenvalue 1,...
Conservation laws of a layered queueing network, enumerated from its structure.