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); this port has only the second so far.
16 * Each backend owns its image name; this file only answers whether the daemon
17 * is up, whether an image is present, whether there is room to pull it, and
18 * performs the pull.
19 *
20 * LQNS, lqsim and qnsolver are deliberately absent, here as in the JAR: their
21 * licence is an evaluation agreement that forbids redistribution, so LINE runs
22 * them only from a binary the user installed.
23 *
24 * THE STORAGE GUARD IS NOT DECORATION. A pull that fills the filesystem backing
25 * the Docker root breaks every container on the machine, not just LINE's, so a
26 * pull is refused unless the free space clears the larger of 2 GiB and three
27 * times the compressed manifest size. When the free space cannot be determined
28 * the pull is ALLOWED: an unknown is not evidence of a full disk, and refusing
29 * would strand users whose daemon does not report a root dir.
30 */
31
32#include <cstddef>
33#include <cstdlib>
34#include <string>
35#include <vector>
36
37#include <sys/stat.h>
38#include <sys/statvfs.h>
39
41
42namespace line {
43namespace io {
44
45/** Conservative free-space floor required before a pull (2 GiB). */
46static const long long DOCKER_DEFAULT_MIN_FREE_BYTES = 2LL * 1024 * 1024 * 1024;
47
48/** True if the Docker daemon is reachable. */
50 const util::ProcResult r = util::capture({"docker", "info"}, 15);
51 return r.exitCode == 0;
52}
53
54/**
55 * @param image the image tag
56 * @return true if it is already present in the local Docker store
57 */
58inline bool docker_has_local_image(const std::string& image) {
59 if (image.empty()) return false;
60 const util::ProcResult r = util::capture({"docker", "images", "-q", image}, 15);
61 return r.exitCode == 0 && !util::trim(r.out).empty();
62}
63
64/**
65 * Pulls an image, streaming Docker's progress to stdout and stderr. No timeout.
66 *
67 * @param image the image tag
68 * @return true on success
69 */
70inline bool docker_pull(const std::string& image) {
71 if (image.empty()) return false;
72 return util::run_inherit({"docker", "pull", image}) == 0;
73}
74
75namespace detail {
76
77/** Usable bytes on the filesystem backing the Docker root dir; -1 if unknown. */
78inline long long docker_free_bytes() {
79 const util::ProcResult r =
80 util::capture({"docker", "info", "--format", "{{.DockerRootDir}}"}, 15);
81 std::string root = r.exitCode == 0 ? util::trim(r.out) : std::string();
82 if (root.empty()) root = "/var/lib/docker";
83
84 // The root dir may not exist for, or be readable by, this user: walk up to
85 // an existing ancestor so the statvfs reports a real filesystem.
86 struct stat st;
87 while (!root.empty() && ::stat(root.c_str(), &st) != 0) {
88 const std::size_t slash = root.find_last_of('/');
89 if (slash == std::string::npos) break;
90 root = slash == 0 ? std::string("/") : root.substr(0, slash);
91 if (root == "/") break;
92 }
93 if (root.empty()) root = "/";
94
95 struct statvfs vfs;
96 if (::statvfs(root.c_str(), &vfs) != 0) return -1;
97 const long long usable =
98 static_cast<long long>(vfs.f_bavail) * static_cast<long long>(vfs.f_frsize);
99 return usable > 0 ? usable : -1;
100}
101
102/** On-disk estimate (compressed layer sizes x3), or 0 if it cannot be determined. */
103inline long long docker_estimate_image_bytes(const std::string& image) {
104 const util::ProcResult r = util::capture({"docker", "manifest", "inspect", image}, 30);
105 if (r.exitCode != 0 || r.out.empty()) return 0;
106 long long sum = 0;
107 const std::string& json = r.out;
108 const std::string key = "\"size\"";
109 std::size_t at = 0;
110 while ((at = json.find(key, at)) != std::string::npos) {
111 std::size_t i = at + key.size();
112 while (i < json.size() && (json[i] == ' ' || json[i] == ':' || json[i] == '\t')) ++i;
113 if (i < json.size() && json[i] >= '0' && json[i] <= '9')
114 sum += std::atoll(json.c_str() + i);
115 at = i;
116 }
117 return sum > 0 ? sum * 3 : 0;
118}
119
120/** LINE_DOCKER_MIN_FREE_BYTES, or -1 when unset or unparsable. */
121inline long long docker_override_min_free_bytes() {
122 const char* v = std::getenv("LINE_DOCKER_MIN_FREE_BYTES");
123 if (v == nullptr || *v == '\0') return -1;
124 const long long parsed = std::atoll(v);
125 return parsed > 0 ? parsed : -1;
126}
127
128} // namespace detail
129
130/**
131 * @param image the image tag
132 * @return true if the Docker storage location has room for it
133 */
134inline bool docker_has_storage_for(const std::string& image) {
135 const long long free = detail::docker_free_bytes();
136 if (free < 0) return true; // could not determine; do not block the pull
137 const long long override = detail::docker_override_min_free_bytes();
138 if (override > 0) return free >= override;
139 const long long est = detail::docker_estimate_image_bytes(image);
140 const long long required = est > DOCKER_DEFAULT_MIN_FREE_BYTES ? est
142 return free >= required;
143}
144
145} // namespace io
146} // namespace line
147
148#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
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.