blob: 2998356786b342a910bca304e2425c116b992cc3 [file] [log] [blame]
buchgr357cb1e2019-04-03 06:15:02 -07001// Copyright 2019 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.
Googler4785a952019-10-22 04:54:34 -070014//
buchgr357cb1e2019-04-03 06:15:02 -070015
16package com.google.devtools.build.lib.remote;
17
Chi Wang0eeac8b2022-10-18 03:25:31 -070018import static com.google.common.base.Preconditions.checkArgument;
ajurkowski8883c612021-03-08 08:12:37 -080019import static com.google.common.base.Preconditions.checkNotNull;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070020import static com.google.common.base.Preconditions.checkState;
Chi Wangf4deafb2022-10-14 02:50:31 -070021import static com.google.common.collect.ImmutableMap.toImmutableMap;
22import static com.google.common.collect.Streams.stream;
ajurkowski8883c612021-03-08 08:12:37 -080023
Chi Wang0eeac8b2022-10-18 03:25:31 -070024import com.google.common.annotations.VisibleForTesting;
Googler97dea592022-11-08 05:03:04 -080025import com.google.common.collect.ImmutableList;
Chi Wangf4deafb2022-10-14 02:50:31 -070026import com.google.common.collect.ImmutableMap;
Chi Wang963640a2023-02-20 03:20:39 -080027import com.google.devtools.build.lib.actions.ActionInput;
buchgr357cb1e2019-04-03 06:15:02 -070028import com.google.devtools.build.lib.actions.ActionInputMap;
Chi Wang79b5b5d2022-08-22 02:34:05 -070029import com.google.devtools.build.lib.actions.Artifact;
30import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
31import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
buchgr357cb1e2019-04-03 06:15:02 -070032import com.google.devtools.build.lib.actions.FileArtifactValue;
33import com.google.devtools.build.lib.actions.FileArtifactValue.RemoteFileArtifactValue;
Googler2a8ab5b2023-03-10 08:20:47 -080034import com.google.devtools.build.lib.actions.MetadataProvider;
Chi Wang29ee1912022-11-29 06:42:31 -080035import com.google.devtools.build.lib.actions.RemoteFileStatus;
Chi Wang79b5b5d2022-08-22 02:34:05 -070036import com.google.devtools.build.lib.actions.cache.MetadataInjector;
Chi Wang28765692022-10-17 04:16:35 -070037import com.google.devtools.build.lib.clock.Clock;
Chi Wang79b5b5d2022-08-22 02:34:05 -070038import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
buchgr357cb1e2019-04-03 06:15:02 -070039import com.google.devtools.build.lib.vfs.DelegateFileSystem;
Chi Wang28765692022-10-17 04:16:35 -070040import com.google.devtools.build.lib.vfs.DigestHashFunction;
buchgr357cb1e2019-04-03 06:15:02 -070041import com.google.devtools.build.lib.vfs.Dirent;
42import com.google.devtools.build.lib.vfs.FileStatus;
43import com.google.devtools.build.lib.vfs.FileSystem;
44import com.google.devtools.build.lib.vfs.Path;
45import com.google.devtools.build.lib.vfs.PathFragment;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070046import com.google.devtools.build.lib.vfs.Symlinks;
Chi Wang28765692022-10-17 04:16:35 -070047import com.google.devtools.build.lib.vfs.inmemoryfs.FileInfo;
48import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryContentInfo;
49import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
Chi Wang0eeac8b2022-10-18 03:25:31 -070050import com.google.errorprone.annotations.CanIgnoreReturnValue;
buchgr357cb1e2019-04-03 06:15:02 -070051import java.io.FileNotFoundException;
52import java.io.IOException;
53import java.io.InputStream;
Chi Wang28765692022-10-17 04:16:35 -070054import java.io.OutputStream;
Googler4785a952019-10-22 04:54:34 -070055import java.nio.channels.ReadableByteChannel;
buchgr357cb1e2019-04-03 06:15:02 -070056import java.util.Collection;
Googler97dea592022-11-08 05:03:04 -080057import java.util.HashMap;
58import java.util.HashSet;
Chi Wang79b5b5d2022-08-22 02:34:05 -070059import java.util.Map;
buchgr357cb1e2019-04-03 06:15:02 -070060import javax.annotation.Nullable;
61
62/**
63 * This is a basic implementation and incomplete implementation of an action file system that's been
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -070064 * tuned to what internal (non-spawn) actions in Bazel currently use.
buchgr357cb1e2019-04-03 06:15:02 -070065 *
66 * <p>The implementation mostly delegates to the local file system except for the case where an
ajurkowski8883c612021-03-08 08:12:37 -080067 * action input is a remotely stored action output. Most notably {@link
68 * #getInputStream(PathFragment)} and {@link #createSymbolicLink(PathFragment, PathFragment)}.
buchgr357cb1e2019-04-03 06:15:02 -070069 *
70 * <p>This implementation only supports creating local action outputs.
71 */
Googler2a8ab5b2023-03-10 08:20:47 -080072public class RemoteActionFileSystem extends DelegateFileSystem implements MetadataProvider {
buchgr357cb1e2019-04-03 06:15:02 -070073
ajurkowski8883c612021-03-08 08:12:37 -080074 private final PathFragment execRoot;
75 private final PathFragment outputBase;
Googler2a8ab5b2023-03-10 08:20:47 -080076 private final MetadataProvider fileCache;
buchgr357cb1e2019-04-03 06:15:02 -070077 private final ActionInputMap inputArtifactData;
Chi Wangf4deafb2022-10-14 02:50:31 -070078 private final ImmutableMap<PathFragment, Artifact> outputMapping;
buchgr357cb1e2019-04-03 06:15:02 -070079 private final RemoteActionInputFetcher inputFetcher;
Chi Wang28765692022-10-17 04:16:35 -070080 private final RemoteInMemoryFileSystem remoteOutputTree;
Chi Wang79b5b5d2022-08-22 02:34:05 -070081
82 @Nullable private MetadataInjector metadataInjector = null;
buchgr357cb1e2019-04-03 06:15:02 -070083
Jakob Buchgraber484ffae2019-06-19 06:04:12 -070084 RemoteActionFileSystem(
buchgr357cb1e2019-04-03 06:15:02 -070085 FileSystem localDelegate,
86 PathFragment execRootFragment,
87 String relativeOutputPath,
88 ActionInputMap inputArtifactData,
Chi Wangf4deafb2022-10-14 02:50:31 -070089 Iterable<Artifact> outputArtifacts,
Googler2a8ab5b2023-03-10 08:20:47 -080090 MetadataProvider fileCache,
buchgr357cb1e2019-04-03 06:15:02 -070091 RemoteActionInputFetcher inputFetcher) {
92 super(localDelegate);
ajurkowski8883c612021-03-08 08:12:37 -080093 this.execRoot = checkNotNull(execRootFragment, "execRootFragment");
94 this.outputBase = execRoot.getRelative(checkNotNull(relativeOutputPath, "relativeOutputPath"));
95 this.inputArtifactData = checkNotNull(inputArtifactData, "inputArtifactData");
Chi Wangf4deafb2022-10-14 02:50:31 -070096 this.outputMapping =
97 stream(outputArtifacts).collect(toImmutableMap(Artifact::getExecPath, a -> a));
Googler2a8ab5b2023-03-10 08:20:47 -080098 this.fileCache = checkNotNull(fileCache, "fileCache");
ajurkowski8883c612021-03-08 08:12:37 -080099 this.inputFetcher = checkNotNull(inputFetcher, "inputFetcher");
Chi Wang28765692022-10-17 04:16:35 -0700100 this.remoteOutputTree = new RemoteInMemoryFileSystem(getDigestFunction());
buchgr357cb1e2019-04-03 06:15:02 -0700101 }
102
Chi Wang0eeac8b2022-10-18 03:25:31 -0700103 @VisibleForTesting
104 protected RemoteInMemoryFileSystem getRemoteOutputTree() {
105 return remoteOutputTree;
106 }
107
108 @VisibleForTesting
109 protected FileSystem getLocalFileSystem() {
110 return delegateFs;
111 }
112
Jakob Buchgraber484ffae2019-06-19 06:04:12 -0700113 /** Returns true if {@code path} is a file that's stored remotely. */
114 boolean isRemote(Path path) {
Googler2a8ab5b2023-03-10 08:20:47 -0800115 return isRemote(path.asFragment());
116 }
117
118 private boolean isRemote(PathFragment path) {
119 return getRemoteMetadata(path) != null;
Jakob Buchgraber484ffae2019-06-19 06:04:12 -0700120 }
121
Chi Wang79b5b5d2022-08-22 02:34:05 -0700122 public void updateContext(MetadataInjector metadataInjector) {
123 this.metadataInjector = metadataInjector;
124 }
125
Chi Wang1ebb04b2023-03-03 07:13:57 -0800126 void injectRemoteFile(PathFragment path, byte[] digest, long size, long expireAtEpochMilli)
127 throws IOException {
Chi Wang0eeac8b2022-10-18 03:25:31 -0700128 if (!isOutput(path)) {
Chi Wang28765692022-10-17 04:16:35 -0700129 return;
Chi Wang79b5b5d2022-08-22 02:34:05 -0700130 }
Chi Wang1ebb04b2023-03-03 07:13:57 -0800131 remoteOutputTree.injectRemoteFile(path, digest, size, expireAtEpochMilli);
Chi Wang79b5b5d2022-08-22 02:34:05 -0700132 }
133
Chi Wang28765692022-10-17 04:16:35 -0700134 void flush() throws IOException {
Chi Wangf4deafb2022-10-14 02:50:31 -0700135 checkNotNull(metadataInjector, "metadataInjector is null");
136
137 for (Map.Entry<PathFragment, Artifact> entry : outputMapping.entrySet()) {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700138 PathFragment path = execRoot.getRelative(entry.getKey());
Chi Wangf4deafb2022-10-14 02:50:31 -0700139 Artifact output = entry.getValue();
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700140
Chi Wang29ee1912022-11-29 06:42:31 -0800141 maybeInjectMetadataForSymlink(path, output);
Chi Wang79b5b5d2022-08-22 02:34:05 -0700142 }
143 }
144
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700145 /**
146 * Inject metadata for non-symlink outputs that were materialized as a symlink to a remote
147 * artifact.
148 *
149 * <p>If a non-symlink output is materialized as a symlink, the symlink has "copy" semantics,
150 * i.e., the output metadata is identical to that of the symlink target. For these artifacts, we
151 * inject their metadata instead of collecting it from the filesystem. This is done for two
152 * reasons:
153 *
154 * <ul>
155 * <li>It avoids implementing filesystem operations for resolving symlinks and (in the case of a
156 * tree artifact) listing directories, which are especially tricky since the symlink and its
157 * target may reside on different filesystems;
158 * <li>It lets us add a special field to the output metadata to tell the input prefetcher that
159 * the output should be materialized as a symlink to the original location, which avoids
160 * fetching multiple copies when multiple symlinks to the same artifact are created in the
161 * same build.
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700162 */
Chi Wang29ee1912022-11-29 06:42:31 -0800163 private void maybeInjectMetadataForSymlink(PathFragment linkPath, Artifact output)
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700164 throws IOException {
165 if (output.isSymlink()) {
Chi Wang29ee1912022-11-29 06:42:31 -0800166 return;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700167 }
168
169 Path outputTreePath = remoteOutputTree.getPath(linkPath);
170
171 if (!outputTreePath.exists(Symlinks.NOFOLLOW)) {
Chi Wang29ee1912022-11-29 06:42:31 -0800172 return;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700173 }
174
175 PathFragment targetPath;
176 try {
177 targetPath = outputTreePath.readSymbolicLink();
178 } catch (NotASymlinkException e) {
Chi Wang29ee1912022-11-29 06:42:31 -0800179 return;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700180 }
181
182 checkState(
183 targetPath.isAbsolute(),
184 "non-symlink artifact materialized as symlink must point to absolute path");
185
186 if (output.isTreeArtifact()) {
187 TreeArtifactValue metadata = getRemoteTreeMetadata(targetPath);
188 if (metadata == null) {
Chi Wang29ee1912022-11-29 06:42:31 -0800189 return;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700190 }
191
192 SpecialArtifact parent = (SpecialArtifact) output;
193 TreeArtifactValue.Builder injectedTree = TreeArtifactValue.newBuilder(parent);
194 // Avoid a double indirection when the target is already materialized as a symlink.
195 injectedTree.setMaterializationExecPath(
196 metadata.getMaterializationExecPath().orElse(targetPath.relativeTo(execRoot)));
197 // TODO: Check directory content on the local fs to support mixed tree.
198 for (Map.Entry<TreeFileArtifact, FileArtifactValue> entry :
199 metadata.getChildValues().entrySet()) {
200 TreeFileArtifact child =
201 TreeFileArtifact.createTreeOutput(parent, entry.getKey().getParentRelativePath());
202 RemoteFileArtifactValue childMetadata = (RemoteFileArtifactValue) entry.getValue();
203 injectedTree.putChild(child, childMetadata);
204 }
205
206 metadataInjector.injectTree(parent, injectedTree.build());
207 } else {
208 RemoteFileArtifactValue metadata = getRemoteMetadata(targetPath);
209 if (metadata == null) {
Chi Wang29ee1912022-11-29 06:42:31 -0800210 return;
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700211 }
212
213 RemoteFileArtifactValue injectedMetadata =
214 RemoteFileArtifactValue.create(
215 metadata.getDigest(),
216 metadata.getSize(),
217 metadata.getLocationIndex(),
Chi Wang1ebb04b2023-03-03 07:13:57 -0800218 metadata.getExpireAtEpochMilli(),
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700219 // Avoid a double indirection when the target is already materialized as a symlink.
220 metadata.getMaterializationExecPath().orElse(targetPath.relativeTo(execRoot)));
221
222 metadataInjector.injectFile(output, injectedMetadata);
223 }
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700224 }
225
Chi Wang28765692022-10-17 04:16:35 -0700226 private RemoteFileArtifactValue createRemoteMetadata(RemoteFileInfo remoteFile) {
227 return RemoteFileArtifactValue.create(
Chi Wang1ebb04b2023-03-03 07:13:57 -0800228 remoteFile.getFastDigest(),
229 remoteFile.getSize(),
230 /* locationIndex= */ 1,
231 remoteFile.getExpireAtEpochMilli());
Chi Wang28765692022-10-17 04:16:35 -0700232 }
233
buchgr357cb1e2019-04-03 06:15:02 -0700234 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800235 public String getFileSystemType(PathFragment path) {
buchgr357cb1e2019-04-03 06:15:02 -0700236 return "remoteActionFS";
237 }
238
239 @Override
ajurkowskiea26e0a2021-03-22 16:04:20 -0700240 protected boolean delete(PathFragment path) throws IOException {
Googler9b8304c2022-10-19 08:42:24 -0700241 boolean deleted = super.delete(path);
Googler486a9642023-03-08 01:31:02 -0800242 if (isOutput(path)) {
Googler9b8304c2022-10-19 08:42:24 -0700243 deleted = remoteOutputTree.getPath(path).delete() || deleted;
buchgr357cb1e2019-04-03 06:15:02 -0700244 }
Googler9b8304c2022-10-19 08:42:24 -0700245 return deleted;
buchgr357cb1e2019-04-03 06:15:02 -0700246 }
247
248 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800249 protected InputStream getInputStream(PathFragment path) throws IOException {
buchgr357cb1e2019-04-03 06:15:02 -0700250 downloadFileIfRemote(path);
Googler2a8ab5b2023-03-10 08:20:47 -0800251 // TODO(tjgq): Consider only falling back to the local filesystem for source (non-output) files.
252 // See getMetadata() for why this isn't currently possible.
buchgr357cb1e2019-04-03 06:15:02 -0700253 return super.getInputStream(path);
254 }
255
256 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800257 protected ReadableByteChannel createReadableByteChannel(PathFragment path) throws IOException {
Googler4785a952019-10-22 04:54:34 -0700258 downloadFileIfRemote(path);
ichern03be73e2019-10-22 10:16:44 -0700259 return super.createReadableByteChannel(path);
Googler4785a952019-10-22 04:54:34 -0700260 }
261
262 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800263 public void setLastModifiedTime(PathFragment path, long newTime) throws IOException {
Chi Wang33b514b2022-12-12 06:36:08 -0800264 FileNotFoundException remoteException = null;
265 try {
266 // We can't set mtime for a remote file, set mtime of in-memory file node instead.
267 remoteOutputTree.setLastModifiedTime(path, newTime);
268 } catch (FileNotFoundException e) {
269 remoteException = e;
buchgr357cb1e2019-04-03 06:15:02 -0700270 }
Chi Wang33b514b2022-12-12 06:36:08 -0800271
272 FileNotFoundException localException = null;
273 try {
274 super.setLastModifiedTime(path, newTime);
275 } catch (FileNotFoundException e) {
276 localException = e;
277 }
278
279 if (remoteException == null || localException == null) {
280 return;
281 }
282
283 localException.addSuppressed(remoteException);
284 throw localException;
buchgr357cb1e2019-04-03 06:15:02 -0700285 }
286
287 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800288 protected byte[] getFastDigest(PathFragment path) throws IOException {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700289 RemoteFileArtifactValue m = getRemoteMetadata(path);
buchgr357cb1e2019-04-03 06:15:02 -0700290 if (m != null) {
291 return m.getDigest();
292 }
293 return super.getFastDigest(path);
294 }
295
296 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800297 protected byte[] getDigest(PathFragment path) throws IOException {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700298 RemoteFileArtifactValue m = getRemoteMetadata(path);
buchgr357cb1e2019-04-03 06:15:02 -0700299 if (m != null) {
300 return m.getDigest();
301 }
302 return super.getDigest(path);
303 }
304
305 // -------------------- File Permissions --------------------
Googlercc712ee2022-11-25 03:25:58 -0800306 // Remote files are always readable, writable and executable since we can't control their
307 // permissions.
buchgr357cb1e2019-04-03 06:15:02 -0700308
Googler4312c5f2022-11-28 01:54:32 -0800309 private boolean existsInMemory(PathFragment path) {
310 return remoteOutputTree.getPath(path).isDirectory() || getRemoteMetadata(path) != null;
311 }
312
buchgr357cb1e2019-04-03 06:15:02 -0700313 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800314 protected boolean isReadable(PathFragment path) throws IOException {
Googler4312c5f2022-11-28 01:54:32 -0800315 return existsInMemory(path) || super.isReadable(path);
buchgr357cb1e2019-04-03 06:15:02 -0700316 }
317
318 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800319 protected boolean isWritable(PathFragment path) throws IOException {
Googler4312c5f2022-11-28 01:54:32 -0800320 if (existsInMemory(path)) {
Googlercc712ee2022-11-25 03:25:58 -0800321 // If path exists locally, also check whether it's writable. We need this check for the case
322 // where the action need to delete their local outputs but the parent directory is not
323 // writable.
324 try {
325 return super.isWritable(path);
326 } catch (FileNotFoundException e) {
327 // Intentionally ignored
328 return true;
329 }
330 }
331
332 return super.isWritable(path);
buchgr357cb1e2019-04-03 06:15:02 -0700333 }
334
335 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800336 protected boolean isExecutable(PathFragment path) throws IOException {
Googler4312c5f2022-11-28 01:54:32 -0800337 return existsInMemory(path) || super.isExecutable(path);
buchgr357cb1e2019-04-03 06:15:02 -0700338 }
339
340 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800341 protected void setReadable(PathFragment path, boolean readable) throws IOException {
Googlercc712ee2022-11-25 03:25:58 -0800342 try {
buchgr357cb1e2019-04-03 06:15:02 -0700343 super.setReadable(path, readable);
Googlercc712ee2022-11-25 03:25:58 -0800344 } catch (FileNotFoundException e) {
Googler4312c5f2022-11-28 01:54:32 -0800345 // in case of missing in-memory path, re-throw the error.
346 if (!existsInMemory(path)) {
Googlercc712ee2022-11-25 03:25:58 -0800347 throw e;
348 }
buchgr357cb1e2019-04-03 06:15:02 -0700349 }
350 }
351
352 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800353 public void setWritable(PathFragment path, boolean writable) throws IOException {
Googlercc712ee2022-11-25 03:25:58 -0800354 try {
buchgr357cb1e2019-04-03 06:15:02 -0700355 super.setWritable(path, writable);
Googlercc712ee2022-11-25 03:25:58 -0800356 } catch (FileNotFoundException e) {
Googler4312c5f2022-11-28 01:54:32 -0800357 // in case of missing in-memory path, re-throw the error.
358 if (!existsInMemory(path)) {
Googlercc712ee2022-11-25 03:25:58 -0800359 throw e;
360 }
buchgr357cb1e2019-04-03 06:15:02 -0700361 }
362 }
363
364 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800365 protected void setExecutable(PathFragment path, boolean executable) throws IOException {
Googlercc712ee2022-11-25 03:25:58 -0800366 try {
buchgr357cb1e2019-04-03 06:15:02 -0700367 super.setExecutable(path, executable);
Googlercc712ee2022-11-25 03:25:58 -0800368 } catch (FileNotFoundException e) {
Googler4312c5f2022-11-28 01:54:32 -0800369 // in case of missing in-memory path, re-throw the error.
370 if (!existsInMemory(path)) {
Googlercc712ee2022-11-25 03:25:58 -0800371 throw e;
372 }
buchgr357cb1e2019-04-03 06:15:02 -0700373 }
374 }
375
376 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800377 protected void chmod(PathFragment path, int mode) throws IOException {
Googlercc712ee2022-11-25 03:25:58 -0800378 try {
buchgr357cb1e2019-04-03 06:15:02 -0700379 super.chmod(path, mode);
Googlercc712ee2022-11-25 03:25:58 -0800380 } catch (FileNotFoundException e) {
Googler4312c5f2022-11-28 01:54:32 -0800381 // in case of missing in-memory path, re-throw the error.
382 if (!existsInMemory(path)) {
Googlercc712ee2022-11-25 03:25:58 -0800383 throw e;
384 }
buchgr357cb1e2019-04-03 06:15:02 -0700385 }
386 }
387
388 // -------------------- Symlinks --------------------
389
390 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800391 protected PathFragment readSymbolicLink(PathFragment path) throws IOException {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700392 RemoteFileArtifactValue m = getRemoteMetadata(path);
buchgr357cb1e2019-04-03 06:15:02 -0700393 if (m != null) {
394 // We don't support symlinks as remote action outputs.
395 throw new IOException(path + " is not a symbolic link");
396 }
397 return super.readSymbolicLink(path);
398 }
399
400 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800401 protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
402 throws IOException {
buchgr357cb1e2019-04-03 06:15:02 -0700403 super.createSymbolicLink(linkPath, targetFragment);
Googler486a9642023-03-08 01:31:02 -0800404 if (isOutput(linkPath)) {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700405 remoteOutputTree.getPath(linkPath).createSymbolicLink(targetFragment);
406 }
buchgr357cb1e2019-04-03 06:15:02 -0700407 }
408
409 // -------------------- Implementations based on stat() --------------------
410
411 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800412 protected long getLastModifiedTime(PathFragment path, boolean followSymlinks) throws IOException {
Chi Wang33b514b2022-12-12 06:36:08 -0800413 try {
414 // We can't get mtime for a remote file, use mtime of in-memory file node instead.
415 return remoteOutputTree
416 .getPath(path)
417 .getLastModifiedTime(followSymlinks ? Symlinks.FOLLOW : Symlinks.NOFOLLOW);
418 } catch (FileNotFoundException e) {
419 // Intentionally ignored
420 }
421
422 return super.getLastModifiedTime(path, followSymlinks);
buchgr357cb1e2019-04-03 06:15:02 -0700423 }
424
425 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800426 protected long getFileSize(PathFragment path, boolean followSymlinks) throws IOException {
buchgr357cb1e2019-04-03 06:15:02 -0700427 FileStatus stat = stat(path, followSymlinks);
428 return stat.getSize();
429 }
430
431 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800432 protected boolean isFile(PathFragment path, boolean followSymlinks) {
buchgr357cb1e2019-04-03 06:15:02 -0700433 FileStatus stat = statNullable(path, followSymlinks);
434 return stat != null && stat.isFile();
435 }
436
437 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800438 protected boolean isSymbolicLink(PathFragment path) {
buchgr357cb1e2019-04-03 06:15:02 -0700439 FileStatus stat = statNullable(path, /* followSymlinks= */ false);
440 return stat != null && stat.isSymbolicLink();
441 }
442
443 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800444 protected boolean isDirectory(PathFragment path, boolean followSymlinks) {
buchgr357cb1e2019-04-03 06:15:02 -0700445 FileStatus stat = statNullable(path, followSymlinks);
446 return stat != null && stat.isDirectory();
447 }
448
449 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800450 protected boolean isSpecialFile(PathFragment path, boolean followSymlinks) {
buchgr357cb1e2019-04-03 06:15:02 -0700451 FileStatus stat = statNullable(path, followSymlinks);
452 return stat != null && stat.isDirectory();
453 }
454
455 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800456 protected boolean exists(PathFragment path, boolean followSymlinks) {
buchgr357cb1e2019-04-03 06:15:02 -0700457 try {
458 return statIfFound(path, followSymlinks) != null;
459 } catch (IOException e) {
460 return false;
461 }
462 }
463
464 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800465 public boolean exists(PathFragment path) {
buchgr357cb1e2019-04-03 06:15:02 -0700466 return exists(path, /* followSymlinks= */ true);
467 }
468
469 @Nullable
470 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800471 protected FileStatus statIfFound(PathFragment path, boolean followSymlinks) throws IOException {
buchgr357cb1e2019-04-03 06:15:02 -0700472 try {
473 return stat(path, followSymlinks);
474 } catch (FileNotFoundException e) {
475 return null;
476 }
477 }
478
479 @Nullable
480 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800481 protected FileStatus statNullable(PathFragment path, boolean followSymlinks) {
buchgr357cb1e2019-04-03 06:15:02 -0700482 try {
483 return stat(path, followSymlinks);
484 } catch (IOException e) {
485 return null;
486 }
487 }
488
489 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800490 protected FileStatus stat(PathFragment path, boolean followSymlinks) throws IOException {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700491 RemoteFileArtifactValue m = getRemoteMetadata(path);
buchgr357cb1e2019-04-03 06:15:02 -0700492 if (m != null) {
493 return statFromRemoteMetadata(m);
494 }
495 return super.stat(path, followSymlinks);
496 }
497
janakr0fdf1a12020-10-08 13:04:12 -0700498 private static FileStatus statFromRemoteMetadata(RemoteFileArtifactValue m) {
Chi Wang29ee1912022-11-29 06:42:31 -0800499 return new RemoteFileStatus() {
500 @Override
501 public byte[] getDigest() {
502 return m.getDigest();
503 }
504
buchgr357cb1e2019-04-03 06:15:02 -0700505 @Override
506 public boolean isFile() {
507 return m.getType().isFile();
508 }
509
510 @Override
511 public boolean isDirectory() {
512 return m.getType().isDirectory();
513 }
514
515 @Override
516 public boolean isSymbolicLink() {
517 return m.getType().isSymlink();
518 }
519
520 @Override
521 public boolean isSpecialFile() {
522 return m.getType().isSpecialFile();
523 }
524
525 @Override
526 public long getSize() {
527 return m.getSize();
528 }
529
530 @Override
531 public long getLastModifiedTime() {
532 return m.getModifiedTime();
533 }
534
535 @Override
536 public long getLastChangeTime() {
537 return m.getModifiedTime();
538 }
539
540 @Override
541 public long getNodeId() {
janakr0fdf1a12020-10-08 13:04:12 -0700542 throw new UnsupportedOperationException("Cannot get node id for " + m);
buchgr357cb1e2019-04-03 06:15:02 -0700543 }
Chi Wang29ee1912022-11-29 06:42:31 -0800544
545 @Override
546 public RemoteFileArtifactValue getRemoteMetadata() {
547 return m;
548 }
buchgr357cb1e2019-04-03 06:15:02 -0700549 };
550 }
551
Googler2a8ab5b2023-03-10 08:20:47 -0800552 @Override
buchgr357cb1e2019-04-03 06:15:02 -0700553 @Nullable
Googler2a8ab5b2023-03-10 08:20:47 -0800554 public ActionInput getInput(String execPath) {
555 ActionInput input = inputArtifactData.getInput(execPath);
556 if (input != null) {
557 return input;
558 }
559 input = outputMapping.get(PathFragment.create(execPath));
560 if (input != null) {
561 return input;
562 }
563 if (!isOutput(execRoot.getRelative(execPath))) {
564 return fileCache.getInput(execPath);
565 }
566 return null;
567 }
568
569 ActionInput getActionInput(PathFragment path) {
570 return getInput(path.relativeTo(execRoot).getPathString());
Chi Wang963640a2023-02-20 03:20:39 -0800571 }
572
573 @Nullable
Googler2a8ab5b2023-03-10 08:20:47 -0800574 @Override
575 public FileArtifactValue getMetadata(ActionInput input) throws IOException {
576 PathFragment execPath = input.getExecPath();
577 FileArtifactValue m = getMetadataByExecPath(execPath);
578 if (m != null) {
579 return m;
buchgr357cb1e2019-04-03 06:15:02 -0700580 }
Googler2a8ab5b2023-03-10 08:20:47 -0800581 // TODO(tjgq): Consider only falling back to the local filesystem for source (non-output) files.
582 // The output fallback is needed when an undeclared output of a spawn is consumed by another
583 // spawn within the same action; specifically, when the first spawn is local but the second is
584 // remote, or, in the context of a failed test attempt, when both spawns are remote but the
585 // first one fails. In both cases, we don't currently inject the output metadata for the first
586 // spawn; if we did so, then we could stop falling back here.
587 return fileCache.getMetadata(input);
588 }
589
590 @Nullable
591 private FileArtifactValue getMetadataByExecPath(PathFragment execPath) {
ajurkowskiaad74a32021-07-12 12:56:47 -0700592 FileArtifactValue m = inputArtifactData.getMetadata(execPath);
Googler2a8ab5b2023-03-10 08:20:47 -0800593 if (m != null) {
594 return m;
buchgr357cb1e2019-04-03 06:15:02 -0700595 }
Chi Wang79b5b5d2022-08-22 02:34:05 -0700596
Chi Wang28765692022-10-17 04:16:35 -0700597 RemoteFileInfo remoteFile =
Googler2a8ab5b2023-03-10 08:20:47 -0800598 remoteOutputTree.getRemoteFileInfo(
599 execRoot.getRelative(execPath), /* followSymlinks= */ true);
Chi Wang28765692022-10-17 04:16:35 -0700600 if (remoteFile != null) {
601 return createRemoteMetadata(remoteFile);
602 }
603
604 return null;
buchgr357cb1e2019-04-03 06:15:02 -0700605 }
606
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700607 @Nullable
Googler2a8ab5b2023-03-10 08:20:47 -0800608 RemoteFileArtifactValue getRemoteMetadata(PathFragment path) {
609 if (!isOutput(path)) {
610 return null;
611 }
612 FileArtifactValue m = getMetadataByExecPath(path.relativeTo(execRoot));
613 if (m != null && m.isRemote()) {
614 return (RemoteFileArtifactValue) m;
615 }
616 return null;
617 }
618
619 @Nullable
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700620 private TreeArtifactValue getRemoteTreeMetadata(PathFragment path) {
Googler486a9642023-03-08 01:31:02 -0800621 if (!isOutput(path)) {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700622 return null;
623 }
Googler2a8ab5b2023-03-10 08:20:47 -0800624 TreeArtifactValue m = inputArtifactData.getTreeMetadata(path.relativeTo(execRoot));
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700625 // TODO: Handle partially remote tree artifacts.
626 if (m != null && m.isEntirelyRemote()) {
627 return m;
628 }
629 // TODO(tjgq): Synthesize TreeArtifactValue from remoteOutputTree.
630 return null;
631 }
632
ajurkowski8883c612021-03-08 08:12:37 -0800633 private void downloadFileIfRemote(PathFragment path) throws IOException {
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700634 FileArtifactValue m = getRemoteMetadata(path);
buchgr357cb1e2019-04-03 06:15:02 -0700635 if (m != null) {
636 try {
Chi Wang963640a2023-02-20 03:20:39 -0800637 inputFetcher.downloadFile(delegateFs.getPath(path), getActionInput(path), m);
buchgr357cb1e2019-04-03 06:15:02 -0700638 } catch (InterruptedException e) {
639 Thread.currentThread().interrupt();
640 throw new IOException(
641 String.format("Received interrupt while fetching file '%s'", path), e);
642 }
643 }
644 }
645
Chi Wang0eeac8b2022-10-18 03:25:31 -0700646 private boolean isOutput(PathFragment path) {
647 return path.startsWith(outputBase);
648 }
649
650 @Override
651 public void renameTo(PathFragment sourcePath, PathFragment targetPath) throws IOException {
652 checkArgument(isOutput(sourcePath), "sourcePath must be an output path");
653 checkArgument(isOutput(targetPath), "targetPath must be an output path");
654
655 FileNotFoundException remoteException = null;
656 try {
657 remoteOutputTree.renameTo(sourcePath, targetPath);
658 } catch (FileNotFoundException e) {
659 remoteException = e;
660 }
661
662 FileNotFoundException localException = null;
663 try {
664 delegateFs.renameTo(sourcePath, targetPath);
665 } catch (FileNotFoundException e) {
666 localException = e;
667 }
668
669 if (remoteException == null || localException == null) {
670 return;
671 }
672
673 localException.addSuppressed(remoteException);
674 throw localException;
675 }
676
677 @Override
678 public void createDirectoryAndParents(PathFragment path) throws IOException {
679 super.createDirectoryAndParents(path);
Googler486a9642023-03-08 01:31:02 -0800680 if (isOutput(path)) {
Chi Wang0eeac8b2022-10-18 03:25:31 -0700681 remoteOutputTree.createDirectoryAndParents(path);
682 }
683 }
684
685 @CanIgnoreReturnValue
686 @Override
687 public boolean createDirectory(PathFragment path) throws IOException {
688 boolean created = delegateFs.createDirectory(path);
Googler486a9642023-03-08 01:31:02 -0800689 if (isOutput(path)) {
Chi Wang0eeac8b2022-10-18 03:25:31 -0700690 created = remoteOutputTree.createDirectory(path) || created;
691 }
692 return created;
693 }
694
Googler97dea592022-11-08 05:03:04 -0800695 @Override
696 protected ImmutableList<String> getDirectoryEntries(PathFragment path) throws IOException {
697 HashSet<String> entries = new HashSet<>();
698
699 boolean ignoredNotFoundInRemote = false;
700 if (isOutput(path)) {
701 try {
702 delegateFs.getPath(path).getDirectoryEntries().stream()
703 .map(Path::getBaseName)
704 .forEach(entries::add);
705 ignoredNotFoundInRemote = true;
706 } catch (FileNotFoundException ignored) {
707 // Intentionally ignored
708 }
709 }
710
711 try {
712 remoteOutputTree.getPath(path).getDirectoryEntries().stream()
713 .map(Path::getBaseName)
714 .forEach(entries::add);
715 } catch (FileNotFoundException e) {
716 if (!ignoredNotFoundInRemote) {
717 throw e;
718 }
719 }
720
721 // sort entries to get a deterministic order.
722 return ImmutableList.sortedCopyOf(entries);
723 }
724
725 @Override
726 protected Collection<Dirent> readdir(PathFragment path, boolean followSymlinks)
727 throws IOException {
728 HashMap<String, Dirent> entries = new HashMap<>();
729
730 boolean ignoredNotFoundInRemote = false;
731 if (isOutput(path)) {
732 try {
733 for (var entry :
734 delegateFs
735 .getPath(path)
736 .readdir(followSymlinks ? Symlinks.FOLLOW : Symlinks.NOFOLLOW)) {
737 entries.put(entry.getName(), entry);
738 }
739 ignoredNotFoundInRemote = true;
740 } catch (FileNotFoundException ignored) {
741 // Intentionally ignored
742 }
743 }
744
745 try {
746 for (var entry :
747 remoteOutputTree
748 .getPath(path)
749 .readdir(followSymlinks ? Symlinks.FOLLOW : Symlinks.NOFOLLOW)) {
750 entries.put(entry.getName(), entry);
751 }
752 } catch (FileNotFoundException e) {
753 if (!ignoredNotFoundInRemote) {
754 throw e;
755 }
756 }
757
758 // sort entries to get a deterministic order.
759 return ImmutableList.sortedCopyOf(entries.values());
760 }
761
buchgr357cb1e2019-04-03 06:15:02 -0700762 /*
763 * -------------------- TODO(buchgr): Not yet implemented --------------------
764 *
765 * The below methods have not (yet) been properly implemented due to time constraints mostly and
Tiago Quelhas32b0f5a2022-10-20 07:05:34 -0700766 * with little risk as they currently don't seem to be used by internal actions in Bazel. However,
buchgr357cb1e2019-04-03 06:15:02 -0700767 * before making the --experimental_remote_download_outputs flag non-experimental we should make
768 * sure to fully implement this file system.
769 */
770
771 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800772 protected void createFSDependentHardLink(PathFragment linkPath, PathFragment originalPath)
773 throws IOException {
buchgr357cb1e2019-04-03 06:15:02 -0700774 super.createFSDependentHardLink(linkPath, originalPath);
775 }
776
777 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800778 protected void createHardLink(PathFragment linkPath, PathFragment originalPath)
779 throws IOException {
buchgr357cb1e2019-04-03 06:15:02 -0700780 super.createHardLink(linkPath, originalPath);
781 }
Chi Wang28765692022-10-17 04:16:35 -0700782
783 static class RemoteInMemoryFileSystem extends InMemoryFileSystem {
784
785 public RemoteInMemoryFileSystem(DigestHashFunction hashFunction) {
786 super(hashFunction);
787 }
788
789 @Override
790 protected synchronized OutputStream getOutputStream(PathFragment path, boolean append)
791 throws IOException {
792 // To get an output stream from remote file, we need to first stage it.
793 throw new IllegalStateException("Shouldn't be called directly");
794 }
795
796 @Override
797 protected FileInfo newFile(Clock clock, PathFragment path) {
798 return new RemoteFileInfo(clock);
799 }
800
Chi Wang1ebb04b2023-03-03 07:13:57 -0800801 void injectRemoteFile(PathFragment path, byte[] digest, long size, long expireAtEpochMilli)
802 throws IOException {
Chi Wang28765692022-10-17 04:16:35 -0700803 createDirectoryAndParents(path.getParentDirectory());
804 InMemoryContentInfo node = getOrCreateWritableInode(path);
805 // If a node was already existed and is not a remote file node (i.e. directory or symlink node
806 // ), throw an error.
807 if (!(node instanceof RemoteFileInfo)) {
808 throw new IOException("Could not inject into " + node);
809 }
810
811 RemoteFileInfo remoteFileInfo = (RemoteFileInfo) node;
Chi Wang1ebb04b2023-03-03 07:13:57 -0800812 remoteFileInfo.set(digest, size, expireAtEpochMilli);
Chi Wang28765692022-10-17 04:16:35 -0700813 }
814
815 @Nullable
816 RemoteFileInfo getRemoteFileInfo(PathFragment path, boolean followSymlinks) {
817 InMemoryContentInfo node = inodeStatErrno(path, followSymlinks).inode();
818 if (!(node instanceof RemoteFileInfo)) {
819 return null;
820 }
821 return (RemoteFileInfo) node;
822 }
823 }
824
825 static class RemoteFileInfo extends FileInfo {
826
827 private byte[] digest;
828 private long size;
Chi Wang28765692022-10-17 04:16:35 -0700829
Chi Wang1ebb04b2023-03-03 07:13:57 -0800830 private long expireAtEpochMilli;
831
Chi Wang28765692022-10-17 04:16:35 -0700832 RemoteFileInfo(Clock clock) {
833 super(clock);
834 }
835
Chi Wang1ebb04b2023-03-03 07:13:57 -0800836 private void set(byte[] digest, long size, long expireAtEpochMilli) {
Chi Wang28765692022-10-17 04:16:35 -0700837 this.digest = digest;
838 this.size = size;
Chi Wang1ebb04b2023-03-03 07:13:57 -0800839 this.expireAtEpochMilli = expireAtEpochMilli;
Chi Wang28765692022-10-17 04:16:35 -0700840 }
841
842 @Override
843 public OutputStream getOutputStream(boolean append) throws IOException {
844 throw new IllegalStateException("Shouldn't be called directly");
845 }
846
847 @Override
848 public InputStream getInputStream() throws IOException {
849 throw new IllegalStateException("Shouldn't be called directly");
850 }
851
852 @Override
853 public byte[] getxattr(String name) throws IOException {
854 throw new IllegalStateException("Shouldn't be called directly");
855 }
856
857 @Override
858 public byte[] getFastDigest() {
859 return digest;
860 }
861
862 @Override
863 public long getSize() {
864 return size;
865 }
Chi Wang1ebb04b2023-03-03 07:13:57 -0800866
867 public long getExpireAtEpochMilli() {
868 return expireAtEpochMilli;
869 }
Chi Wang28765692022-10-17 04:16:35 -0700870 }
buchgr357cb1e2019-04-03 06:15:02 -0700871}