LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
docker_image.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_IO_DOCKER_IMAGE_H
6#define LINE_IO_DOCKER_IMAGE_H
7
8/**
9 * @file
10 * @ingroup line_io
11 * Docker primitives for the backends that legitimately ship an image.
12 *
13 * Port of jar/src/main/java/jline/io/DockerImage.java. In the JAR these are
14 * shared by the JMT backend (imperialqore/jmt-rest) and the Sage symbolic
15 * engine (imperialqore/line-sage-rest), and this port carries both: the JMT
16 * client's Docker arm is `jmt_run_docker` in `solvers/wrappers/jmt/`.
17 * Each backend owns its image name; this file only answers whether the daemon
18 * is up, whether an image is present, whether there is room to pull it, and
19 * performs the pull.
20 *
21 * LQNS, lqsim and qnsolver are deliberately absent, here as in the JAR: their
22 * licence is an evaluation agreement that forbids redistribution, so LINE runs
23 * them only from a binary the user installed.
24 *
25 * THE STORAGE GUARD IS NOT DECORATION. A pull that fills the filesystem backing
26 * the Docker root breaks every container on the machine, not just LINE's, so a
27 * pull is refused unless the free space clears the larger of 2 GiB and three
28 * times the compressed manifest size. When the free space cannot be determined
29 * the pull is ALLOWED: an unknown is not evidence of a full disk, and refusing
30 * would strand users whose daemon does not report a root dir.
31 */
32
33#include <cstddef>
34#include <cstdlib>
35#include <string>
36#include <vector>
37
38#include <sys/stat.h>
39#include <sys/statvfs.h>
40
42
43namespace line {
44namespace io {
45
46/** Conservative free-space floor required before a pull (2 GiB). */
47static const long long DOCKER_DEFAULT_MIN_FREE_BYTES = 2LL * 1024 * 1024 * 1024;
48
49/** True if the Docker daemon is reachable. */
51 const util::ProcResult r = util::capture({"docker", "info"}, 15);
52 return r.exitCode == 0;
53}
54
55/**
56 * @param image the image tag
57 * @return true if it is already present in the local Docker store
58 */
59inline bool docker_has_local_image(const std::string& image) {
60 if (image.empty()) return false;
61 const util::ProcResult r = util::capture({"docker", "images", "-q", image}, 15);
62 return r.exitCode == 0 && !util::trim(r.out).empty();
63}
64
65/**
66 * Pulls an image, streaming Docker's progress to stdout and stderr. No timeout.
67 *
68 * @param image the image tag
69 * @return true on success
70 */
71inline bool docker_pull(const std::string& image) {
72 if (image.empty()) return false;
73 return util::run_inherit({"docker", "pull", image}) == 0;
74}
75
76namespace detail {
77
78/** Usable bytes on the filesystem backing the Docker root dir; -1 if unknown. */
79inline long long docker_free_bytes() {
80 const util::ProcResult r =
81 util::capture({"docker", "info", "--format", "{{.DockerRootDir}}"}, 15);
82 std::string root = r.exitCode == 0 ? util::trim(r.out) : std::string();
83 if (root.empty()) root = "/var/lib/docker";
84
85 // The root dir may not exist for, or be readable by, this user: walk up to
86 // an existing ancestor so the statvfs reports a real filesystem.
87 struct stat st;
88 while (!root.empty() && ::stat(root.c_str(), &st) != 0) {
89 const std::size_t slash = root.find_last_of('/');
90 if (slash == std::string::npos) break;
91 root = slash == 0 ? std::string("/") : root.substr(0, slash);
92 if (root == "/") break;
93 }
94 if (root.empty()) root = "/";
95
96 struct statvfs vfs;
97 if (::statvfs(root.c_str(), &vfs) != 0) return -1;
98 const long long usable =
99 static_cast<long long>(vfs.f_bavail) * static_cast<long long>(vfs.f_frsize);
100 return usable > 0 ? usable : -1;
101}
102
103/** On-disk estimate (compressed layer sizes x3), or 0 if it cannot be determined. */
104inline long long docker_estimate_image_bytes(const std::string& image) {
105 const util::ProcResult r = util::capture({"docker", "manifest", "inspect", image}, 30);
106 if (r.exitCode != 0 || r.out.empty()) return 0;
107 long long sum = 0;
108 const std::string& json = r.out;
109 const std::string key = "\"size\"";
110 std::size_t at = 0;
111 while ((at = json.find(key, at)) != std::string::npos) {
112 std::size_t i = at + key.size();
113 while (i < json.size() && (json[i] == ' ' || json[i] == ':' || json[i] == '\t')) ++i;
114 if (i < json.size() && json[i] >= '0' && json[i] <= '9')
115 sum += std::atoll(json.c_str() + i);
116 at = i;
117 }
118 return sum > 0 ? sum * 3 : 0;
119}
120
121/** LINE_DOCKER_MIN_FREE_BYTES, or -1 when unset or unparsable. */
122inline long long docker_override_min_free_bytes() {
123 const char* v = std::getenv("LINE_DOCKER_MIN_FREE_BYTES");
124 if (v == nullptr || *v == '\0') return -1;
125 const long long parsed = std::atoll(v);
126 return parsed > 0 ? parsed : -1;
127}
128
129} // namespace detail
130
131/**
132 * @param image the image tag
133 * @return true if the Docker storage location has room for it
134 */
135inline bool docker_has_storage_for(const std::string& image) {
136 const long long free = detail::docker_free_bytes();
137 if (free < 0) return true; // could not determine; do not block the pull
138 const long long override = detail::docker_override_min_free_bytes();
139 if (override > 0) return free >= override;
140 const long long est = detail::docker_estimate_image_bytes(image);
141 const long long required = est > DOCKER_DEFAULT_MIN_FREE_BYTES ? est
143 return free >= required;
144}
145
146} // namespace io
147} // namespace line
148
149#endif // LINE_IO_DOCKER_IMAGE_H
static const long long DOCKER_DEFAULT_MIN_FREE_BYTES
Conservative free-space floor required before a pull (2 GiB).
bool docker_has_storage_for(const std::string &image)
bool docker_daemon_available()
True if the Docker daemon is reachable.
bool docker_has_local_image(const std::string &image)
bool docker_pull(const std::string &image)
Pulls an image, streaming Docker's progress to stdout and stderr.
int run_inherit(const std::vector< std::string > &argv)
Runs a command with the parent's stdout and stderr, e.g.
Definition subprocess.h:170
ProcResult capture(const std::vector< std::string > &argv, int timeoutSeconds, bool mergeStderr=false)
Runs a command, capturing stdout and discarding stderr.
Definition subprocess.h:82
std::string trim(const std::string &s)
Trims ASCII whitespace from both ends, as Java's String.trim() does.
Definition subprocess.h:181
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Outcome of a captured command.
Definition subprocess.h:42
int exitCode
Exit status, or -1 when the command could not run.
Definition subprocess.h:43
std::string out
Everything the command wrote to stdout.
Definition subprocess.h:44
Running an external command and capturing its output, with a deadline.