blob: 967c08b5d6ce592d10378e5025e095c956517eb6 [file] [log] [blame]
Googler9475ce82022-10-19 07:07:33 -07001// Copyright 2017 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.sandbox;
16
Googlerf90dcf92023-03-08 07:36:20 -080017import static com.google.devtools.build.lib.sandbox.LinuxSandboxCommandLineBuilder.NetworkNamespace.NETNS;
18import static com.google.devtools.build.lib.sandbox.LinuxSandboxCommandLineBuilder.NetworkNamespace.NETNS_WITH_LOOPBACK;
19
Googler8e32f442022-12-12 00:40:58 -080020import com.google.auto.value.AutoValue;
Googler9475ce82022-10-19 07:07:33 -070021import com.google.common.base.Preconditions;
22import com.google.common.collect.ImmutableList;
Googler9475ce82022-10-19 07:07:33 -070023import com.google.common.collect.ImmutableSet;
24import com.google.devtools.build.lib.actions.ExecutionRequirements;
25import com.google.devtools.build.lib.vfs.Path;
26import com.google.devtools.build.lib.vfs.PathFragment;
27import com.google.errorprone.annotations.CanIgnoreReturnValue;
28import java.time.Duration;
Googlere9f0ab32023-01-09 09:41:14 -080029import java.util.HashSet;
Googler9475ce82022-10-19 07:07:33 -070030import java.util.List;
31import java.util.Map;
32import java.util.Set;
33
34/**
35 * A builder class for constructing the full command line to run a command using the {@code
36 * linux-sandbox} tool.
37 */
38public class LinuxSandboxCommandLineBuilder {
Googler8e32f442022-12-12 00:40:58 -080039 /** A bind mount that needs to be present when the sandboxed command runs. */
40 @AutoValue
41 public abstract static class BindMount {
42 public static BindMount of(Path mountPoint, Path source) {
43 return new AutoValue_LinuxSandboxCommandLineBuilder_BindMount(mountPoint, source);
44 }
45
46 /** "target" in mount(2) */
47 public abstract Path getMountPoint();
48
49 /** "source" in mount(2) */
50 public abstract Path getContent();
51 }
52
Googler9475ce82022-10-19 07:07:33 -070053 private final Path linuxSandboxPath;
Googler9475ce82022-10-19 07:07:33 -070054 private Path hermeticSandboxPath;
55 private Path workingDirectory;
56 private Duration timeout;
57 private Duration killDelay;
Googler77652262022-12-01 02:15:33 -080058 private boolean persistentProcess;
Googler9475ce82022-10-19 07:07:33 -070059 private Path stdoutPath;
60 private Path stderrPath;
61 private Set<Path> writableFilesAndDirectories = ImmutableSet.of();
62 private ImmutableSet<PathFragment> tmpfsDirectories = ImmutableSet.of();
Googler8e32f442022-12-12 00:40:58 -080063 private List<BindMount> bindMounts = ImmutableList.of();
Googler9475ce82022-10-19 07:07:33 -070064 private Path statisticsPath;
65 private boolean useFakeHostname = false;
Googlerf90dcf92023-03-08 07:36:20 -080066 private NetworkNamespace createNetworkNamespace = NetworkNamespace.NO_NETNS;
Googler9475ce82022-10-19 07:07:33 -070067 private boolean useFakeRoot = false;
68 private boolean useFakeUsername = false;
69 private boolean enablePseudoterminal = false;
Googler7b620af2023-06-05 06:52:53 -070070 private String sandboxDebugPath = null;
Googler9475ce82022-10-19 07:07:33 -070071 private boolean sigintSendsSigterm = false;
Googler03996c12022-12-08 08:39:47 -080072 private String cgroupsDir;
Googler9475ce82022-10-19 07:07:33 -070073
Fabian Meumertzheim48ce49b2024-01-03 11:30:32 -080074 private LinuxSandboxCommandLineBuilder(Path linuxSandboxPath) {
Googler9475ce82022-10-19 07:07:33 -070075 this.linuxSandboxPath = linuxSandboxPath;
Googler9475ce82022-10-19 07:07:33 -070076 }
77
78 /** Returns a new command line builder for the {@code linux-sandbox} tool. */
Fabian Meumertzheim48ce49b2024-01-03 11:30:32 -080079 public static LinuxSandboxCommandLineBuilder commandLineBuilder(Path linuxSandboxPath) {
80 return new LinuxSandboxCommandLineBuilder(linuxSandboxPath);
Googler9475ce82022-10-19 07:07:33 -070081 }
82
83 /**
84 * Sets the sandbox path to chroot to, required for the hermetic linux sandbox to figure out where
85 * the working directory is.
86 */
87 @CanIgnoreReturnValue
88 public LinuxSandboxCommandLineBuilder setHermeticSandboxPath(Path sandboxPath) {
89 this.hermeticSandboxPath = sandboxPath;
90 return this;
91 }
92
93 /** Sets the working directory to use, if any. */
94 @CanIgnoreReturnValue
95 public LinuxSandboxCommandLineBuilder setWorkingDirectory(Path workingDirectory) {
96 this.workingDirectory = workingDirectory;
97 return this;
98 }
99
100 /** Sets the timeout for the command run using the {@code linux-sandbox} tool. */
101 @CanIgnoreReturnValue
102 public LinuxSandboxCommandLineBuilder setTimeout(Duration timeout) {
103 this.timeout = timeout;
104 return this;
105 }
106
107 /**
108 * Sets the kill delay for commands run using the {@code linux-sandbox} tool that exceed their
109 * timeout.
110 */
111 @CanIgnoreReturnValue
112 public LinuxSandboxCommandLineBuilder setKillDelay(Duration killDelay) {
113 this.killDelay = killDelay;
114 return this;
115 }
116
Googler77652262022-12-01 02:15:33 -0800117 @CanIgnoreReturnValue
118 public LinuxSandboxCommandLineBuilder setPersistentProcess(boolean persistentProcess) {
119 this.persistentProcess = persistentProcess;
120 return this;
121 }
122
Googler9475ce82022-10-19 07:07:33 -0700123 /** Sets the path to use for redirecting stdout, if any. */
124 @CanIgnoreReturnValue
125 public LinuxSandboxCommandLineBuilder setStdoutPath(Path stdoutPath) {
126 this.stdoutPath = stdoutPath;
127 return this;
128 }
129
130 /** Sets the path to use for redirecting stderr, if any. */
131 @CanIgnoreReturnValue
132 public LinuxSandboxCommandLineBuilder setStderrPath(Path stderrPath) {
133 this.stderrPath = stderrPath;
134 return this;
135 }
136
137 /** Sets the files or directories to make writable for the sandboxed process, if any. */
138 @CanIgnoreReturnValue
139 public LinuxSandboxCommandLineBuilder setWritableFilesAndDirectories(
140 Set<Path> writableFilesAndDirectories) {
141 this.writableFilesAndDirectories = writableFilesAndDirectories;
142 return this;
143 }
144
Googlere9f0ab32023-01-09 09:41:14 -0800145 @CanIgnoreReturnValue
146 public LinuxSandboxCommandLineBuilder addWritablePath(Path writablePath) {
147 if (this.writableFilesAndDirectories == null) {
148 this.writableFilesAndDirectories = new HashSet<>();
149 }
150 this.writableFilesAndDirectories.add(writablePath);
151 return this;
152 }
153
Googler9475ce82022-10-19 07:07:33 -0700154 /** Sets the directories where to mount an empty tmpfs, if any. */
155 @CanIgnoreReturnValue
156 public LinuxSandboxCommandLineBuilder setTmpfsDirectories(
157 ImmutableSet<PathFragment> tmpfsDirectories) {
158 this.tmpfsDirectories = tmpfsDirectories;
159 return this;
160 }
161
162 /**
163 * Sets the sources and targets of files or directories to explicitly bind-mount in the sandbox,
164 * if any.
165 */
166 @CanIgnoreReturnValue
Googler8e32f442022-12-12 00:40:58 -0800167 public LinuxSandboxCommandLineBuilder setBindMounts(List<BindMount> bindMounts) {
Googler9475ce82022-10-19 07:07:33 -0700168 this.bindMounts = bindMounts;
169 return this;
170 }
171
172 /** Sets the path for writing execution statistics (e.g. resource usage). */
173 @CanIgnoreReturnValue
174 public LinuxSandboxCommandLineBuilder setStatisticsPath(Path statisticsPath) {
175 this.statisticsPath = statisticsPath;
176 return this;
177 }
178
179 /** Sets whether to use a fake 'localhost' hostname inside the sandbox. */
180 @CanIgnoreReturnValue
181 public LinuxSandboxCommandLineBuilder setUseFakeHostname(boolean useFakeHostname) {
182 this.useFakeHostname = useFakeHostname;
183 return this;
184 }
185
Googlerf90dcf92023-03-08 07:36:20 -0800186 /** Sets whether and how to create a new network namespace. */
Googler9475ce82022-10-19 07:07:33 -0700187 @CanIgnoreReturnValue
Googlerf90dcf92023-03-08 07:36:20 -0800188 public LinuxSandboxCommandLineBuilder setCreateNetworkNamespace(
189 NetworkNamespace createNetworkNamespace) {
Googler9475ce82022-10-19 07:07:33 -0700190 this.createNetworkNamespace = createNetworkNamespace;
191 return this;
192 }
193
194 /** Sets whether to pretend to be 'root' inside the namespace. */
195 @CanIgnoreReturnValue
196 public LinuxSandboxCommandLineBuilder setUseFakeRoot(boolean useFakeRoot) {
197 this.useFakeRoot = useFakeRoot;
198 return this;
199 }
200
201 /** Sets whether to use a fake 'nobody' username inside the sandbox. */
202 @CanIgnoreReturnValue
203 public LinuxSandboxCommandLineBuilder setUseFakeUsername(boolean useFakeUsername) {
204 this.useFakeUsername = useFakeUsername;
205 return this;
206 }
207
208 /**
209 * Sets whether to set group to 'tty' and make /dev/pts writable inside the sandbox in order to
210 * enable the use of pseudoterminals.
211 */
212 @CanIgnoreReturnValue
213 public LinuxSandboxCommandLineBuilder setEnablePseudoterminal(boolean enablePseudoterminal) {
214 this.enablePseudoterminal = enablePseudoterminal;
215 return this;
216 }
217
Googler7b620af2023-06-05 06:52:53 -0700218 /** Sets the output path for sandbox debugging messages. */
Googler9475ce82022-10-19 07:07:33 -0700219 @CanIgnoreReturnValue
Googler7b620af2023-06-05 06:52:53 -0700220 public LinuxSandboxCommandLineBuilder setSandboxDebugPath(String sandboxDebugPath) {
221 this.sandboxDebugPath = sandboxDebugPath;
Googler9475ce82022-10-19 07:07:33 -0700222 return this;
223 }
224
Googler03996c12022-12-08 08:39:47 -0800225 /**
226 * Sets the directory to be used for cgroups. Cgroups can be used to set limits on resource usage
227 * of a subprocess tree, and to gather statistics. Requires cgroups v2 and systemd. This directory
228 * must be under {@code /sys/fs/cgroup} and the user running Bazel must have write permissions to
229 * this directory, its parent directory, and the cgroup directory for the Bazel process.
230 */
231 @CanIgnoreReturnValue
232 public LinuxSandboxCommandLineBuilder setCgroupsDir(String cgroupsDir) {
233 this.cgroupsDir = cgroupsDir;
234 return this;
235 }
236
Googler9475ce82022-10-19 07:07:33 -0700237 /** Incorporates settings from a spawn's execution info. */
238 @CanIgnoreReturnValue
239 public LinuxSandboxCommandLineBuilder addExecutionInfo(Map<String, String> executionInfo) {
240 if (executionInfo.containsKey(ExecutionRequirements.GRACEFUL_TERMINATION)) {
241 sigintSendsSigterm = true;
242 }
243 return this;
244 }
245
246 /** Builds the command line to invoke a specific command using the {@code linux-sandbox} tool. */
Fabian Meumertzheim48ce49b2024-01-03 11:30:32 -0800247 public ImmutableList<String> buildForCommand(List<String> commandArguments) {
Googler9475ce82022-10-19 07:07:33 -0700248 Preconditions.checkState(
249 !(this.useFakeUsername && this.useFakeRoot),
250 "useFakeUsername and useFakeRoot are exclusive");
251
252 ImmutableList.Builder<String> commandLineBuilder = ImmutableList.builder();
253
254 commandLineBuilder.add(linuxSandboxPath.getPathString());
255 if (workingDirectory != null) {
256 commandLineBuilder.add("-W", workingDirectory.getPathString());
257 }
258 if (timeout != null) {
259 commandLineBuilder.add("-T", Long.toString(timeout.getSeconds()));
260 }
261 if (killDelay != null) {
262 commandLineBuilder.add("-t", Long.toString(killDelay.getSeconds()));
263 }
264 if (stdoutPath != null) {
265 commandLineBuilder.add("-l", stdoutPath.getPathString());
266 }
267 if (stderrPath != null) {
268 commandLineBuilder.add("-L", stderrPath.getPathString());
269 }
270 for (Path writablePath : writableFilesAndDirectories) {
271 commandLineBuilder.add("-w", writablePath.getPathString());
272 }
273 for (PathFragment tmpfsPath : tmpfsDirectories) {
274 commandLineBuilder.add("-e", tmpfsPath.getPathString());
275 }
Googler8e32f442022-12-12 00:40:58 -0800276 for (BindMount bindMount : bindMounts) {
277 commandLineBuilder.add("-M", bindMount.getContent().getPathString());
Googler9475ce82022-10-19 07:07:33 -0700278 // The file is mounted in a custom location inside the sandbox.
Googler8e32f442022-12-12 00:40:58 -0800279 if (!bindMount.getContent().equals(bindMount.getMountPoint())) {
280 commandLineBuilder.add("-m", bindMount.getMountPoint().getPathString());
Googler9475ce82022-10-19 07:07:33 -0700281 }
282 }
283 if (statisticsPath != null) {
284 commandLineBuilder.add("-S", statisticsPath.getPathString());
285 }
286 if (hermeticSandboxPath != null) {
287 commandLineBuilder.add("-h", hermeticSandboxPath.getPathString());
288 }
289 if (useFakeHostname) {
290 commandLineBuilder.add("-H");
291 }
Googlerf90dcf92023-03-08 07:36:20 -0800292 if (createNetworkNamespace == NETNS_WITH_LOOPBACK) {
Googler9475ce82022-10-19 07:07:33 -0700293 commandLineBuilder.add("-N");
Googlerf90dcf92023-03-08 07:36:20 -0800294 } else if (createNetworkNamespace == NETNS) {
295 commandLineBuilder.add("-n");
Googler9475ce82022-10-19 07:07:33 -0700296 }
297 if (useFakeRoot) {
298 commandLineBuilder.add("-R");
299 }
300 if (useFakeUsername) {
301 commandLineBuilder.add("-U");
302 }
303 if (enablePseudoterminal) {
304 commandLineBuilder.add("-P");
305 }
Googler7b620af2023-06-05 06:52:53 -0700306 if (sandboxDebugPath != null) {
307 commandLineBuilder.add("-D", sandboxDebugPath);
Googler9475ce82022-10-19 07:07:33 -0700308 }
309 if (sigintSendsSigterm) {
310 commandLineBuilder.add("-i");
311 }
Googler77652262022-12-01 02:15:33 -0800312 if (persistentProcess) {
313 commandLineBuilder.add("-p");
314 }
Googler03996c12022-12-08 08:39:47 -0800315 if (cgroupsDir != null) {
316 commandLineBuilder.add("-C", cgroupsDir);
317 }
Googler9475ce82022-10-19 07:07:33 -0700318 commandLineBuilder.add("--");
319 commandLineBuilder.addAll(commandArguments);
320
321 return commandLineBuilder.build();
322 }
Googlerf90dcf92023-03-08 07:36:20 -0800323
324 /** Enum for the possibilities for creating a network namespace in the sandbox. */
325 public enum NetworkNamespace {
326 /** No network namespace will be created, sandboxed processes can access the network freely. */
327 NO_NETNS,
328 /** A fresh network namespace will be created. */
329 NETNS,
330 /** A fresh network namespace will be created, and a loopback device will be set up in it. */
331 NETNS_WITH_LOOPBACK,
332 }
Googler9475ce82022-10-19 07:07:33 -0700333}