LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
tempdir.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_UTIL_TEMPDIR_H
6#define LINE_UTIL_TEMPDIR_H
7
8/**
9 * @file
10 * @ingroup line_util
11 * A scratch directory for the subprocess wrappers, the port's `lineTempName`.
12 *
13 * The wrappers hand an external binary a FILE, not a stream, so they need a
14 * private directory to put it in and to collect the tool's output from. The
15 * name comes from mkdtemp rather than from a counter or a PID: two solves of the
16 * same model running side by side (a parity sweep does exactly that) would
17 * otherwise write the same `model.jmva` and read each other's results.
18 *
19 * The directory is REMOVED IN THE DESTRUCTOR, including on the exception path,
20 * because the wrappers throw on a tool failure and a leaked scratch directory
21 * per failed solve accumulates silently. `keep()` suppresses the removal for
22 * the case where the caller wants to inspect what was sent.
23 */
24
25#include <cctype>
26#include <cerrno>
27#include <cstdlib>
28#include <string>
29#include <vector>
30
31#include <dirent.h>
32#include <sys/stat.h>
33#include <unistd.h>
34
35#include "line/util/error.h"
36
37namespace line {
38namespace util {
39
40namespace detail {
41
42/** The string without its surrounding whitespace. */
43inline std::string trim_ws(const std::string& s) {
44 std::size_t b = 0, e = s.size();
45 while (b < e && std::isspace(static_cast<unsigned char>(s[b]))) ++b;
46 while (e > b && std::isspace(static_cast<unsigned char>(s[e - 1]))) --e;
47 return s.substr(b, e - b);
48}
49
50/**
51 * Create a directory and every missing parent, as MATLAB's `mkdir` does.
52 *
53 * "It is already there" is success: two solves staging side by side race on the
54 * shared workspace directory, and the loser of that race has what it asked for.
55 */
56inline void mkdir_p(const std::string& dir) {
57 for (std::size_t at = dir.find('/', 1); ; at = dir.find('/', at + 1)) {
58 const std::string part = (at == std::string::npos) ? dir : dir.substr(0, at);
59 if (::mkdir(part.c_str(), 0700) != 0 && errno != EEXIST)
60 throw InputError("cannot create the staging directory '" + part + "'");
61 if (at == std::string::npos) break;
62 }
63}
64
65} // namespace detail
66
67/**
68 * Create a private scratch directory named after its caller.
69 *
70 * LINE_WORKSPACE_ROOT RELOCATES EVERY STAGED MODEL, exactly as it does in
71 * `lineTempName.m`, `SysUtils.lineTempName` and the native-Python wrappers.
72 * run-tests.sh sets it when it wraps lqns, lqsim or qnsolver in a container: the
73 * shim bind-mounts that root and nothing else, so a model left under TMPDIR is
74 * invisible to the binary, which then writes no result and is reported as having
75 * rejected the model. No wrapper here needs to know a container is involved.
76 *
77 * `mountable` is the second argument of `lineTempName`: a snap-confined Docker
78 * daemon bind-mounts neither the system temp dir nor a dot-directory, so a
79 * caller that hands the directory to a container asks for a HOME-rooted one.
80 *
81 * @param prefix a short tag naming the caller, as `lineTempName('qns')` does
82 * @param mountable root under $HOME/.line rather than TMPDIR
83 */
84inline std::string make_temp_dir(const std::string& prefix, bool mountable = false) {
85 std::string root;
86 const char* ws = std::getenv("LINE_WORKSPACE_ROOT");
87 if (ws != nullptr) root = detail::trim_ws(std::string(ws));
88 if (root.empty() && mountable) {
89 const char* home = std::getenv("HOME");
90 if (home != nullptr && *home) root = std::string(home) + "/.line";
91 }
92 if (!root.empty()) {
93 root += "/line_workspace/" + prefix;
94 detail::mkdir_p(root);
95 } else {
96 const char* base = std::getenv("TMPDIR");
97 root = (base && *base ? std::string(base) : std::string("/tmp"));
98 }
99 const std::string tmpl = root + "/line_" + prefix + "_XXXXXX";
100 std::vector<char> buf(tmpl.begin(), tmpl.end());
101 buf.push_back('\0');
102 if (::mkdtemp(&buf[0]) == nullptr)
103 throw InputError("cannot create a temporary directory under '" + tmpl + "'");
104 return std::string(&buf[0]);
105}
106
107class TempDir {
108public:
109 /**
110 * @param prefix a short tag naming the caller, as `lineTempName('qns')` does
111 * @param mountable root it where a confined Docker daemon can bind-mount it
112 */
113 explicit TempDir(const std::string& prefix, bool mountable = false)
114 : path_(make_temp_dir(prefix, mountable)) {}
115
117 if (!keep_ && !path_.empty()) remove_tree(path_);
118 }
119
120 TempDir(const TempDir&) = delete;
121 TempDir& operator=(const TempDir&) = delete;
122
123 /** The directory itself, with no trailing separator. */
124 const std::string& path() const { return path_; }
125
126 /** A file inside it. */
127 std::string file(const std::string& name) const { return path_ + "/" + name; }
128
129 /** Leave the directory in place, for a caller that wants to inspect it. */
130 void keep() { keep_ = true; }
131
132private:
133 static void remove_tree(const std::string& dir) {
134 DIR* d = ::opendir(dir.c_str());
135 if (d != nullptr) {
136 struct dirent* e;
137 while ((e = ::readdir(d)) != nullptr) {
138 const std::string n(e->d_name);
139 if (n == "." || n == "..") continue;
140 const std::string child = dir + "/" + n;
141 struct stat st;
142 if (::lstat(child.c_str(), &st) == 0 && S_ISDIR(st.st_mode))
143 remove_tree(child);
144 else
145 ::unlink(child.c_str());
146 }
147 ::closedir(d);
148 }
149 ::rmdir(dir.c_str());
150 }
151
152 std::string path_;
153 bool keep_ = false;
154};
155
156} // namespace util
157} // namespace line
158
159#endif // LINE_UTIL_TEMPDIR_H
InputError(const std::string &what)
Definition error.h:39
TempDir(const std::string &prefix, bool mountable=false)
Definition tempdir.h:113
std::string file(const std::string &name) const
A file inside it.
Definition tempdir.h:127
TempDir(const TempDir &)=delete
TempDir & operator=(const TempDir &)=delete
void keep()
Leave the directory in place, for a caller that wants to inspect it.
Definition tempdir.h:130
const std::string & path() const
The directory itself, with no trailing separator.
Definition tempdir.h:124
The exception types the port throws.
std::string make_temp_dir(const std::string &prefix, bool mountable=false)
Create a private scratch directory named after its caller.
Definition tempdir.h:84