blob: e8c637927206a56b9be21770c3aa8b52d9e41d05 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
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.
14package com.google.devtools.build.lib.skyframe;
15
Janak Ramakrishnan29c5ab42015-05-14 19:38:12 +000016import com.google.common.annotations.VisibleForTesting;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010017import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
Mark Schaller6df81792015-12-10 18:47:47 +000018import com.google.devtools.build.lib.util.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010019import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
20import com.google.devtools.build.lib.vfs.FileStatus;
21import com.google.devtools.build.lib.vfs.FileStatusWithDigest;
22import com.google.devtools.build.lib.vfs.FileStatusWithDigestAdapter;
23import com.google.devtools.build.lib.vfs.Path;
24import com.google.devtools.build.lib.vfs.PathFragment;
25import com.google.devtools.build.lib.vfs.RootedPath;
26import com.google.devtools.build.lib.vfs.Symlinks;
janakrbfdad902017-05-03 21:38:28 +020027import com.google.devtools.build.skyframe.LegacySkyKey;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import com.google.devtools.build.skyframe.SkyKey;
29import com.google.devtools.build.skyframe.SkyValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import java.io.IOException;
31import java.util.Arrays;
32import java.util.Objects;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import javax.annotation.Nullable;
34
35/**
36 * Encapsulates the filesystem operations needed to get state for a path. This is at least a
37 * 'lstat' to determine what type of file the path is.
38 * <ul>
39 * <li> For a non-existent file, the non existence is noted.
40 * <li> For a symlink, the symlink target is noted.
41 * <li> For a directory, the existence is noted.
42 * <li> For a file, the existence is noted, along with metadata about the file (e.g.
Nathan Harmatad8b6ff22015-10-20 21:54:34 +000043 * file digest). See {@link RegularFileStateValue}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044 * <ul>
45 *
46 * <p>This class is an implementation detail of {@link FileValue} and should not be used outside of
47 * {@link FileFunction}. Instead, {@link FileValue} should be used by consumers that care about
48 * files.
49 *
50 * <p>All subclasses must implement {@link #equals} and {@link #hashCode} properly.
51 */
Janak Ramakrishnan29c5ab42015-05-14 19:38:12 +000052@VisibleForTesting
53public abstract class FileStateValue implements SkyValue {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054
Michajlo Matijkiw11184592015-10-16 19:00:45 +000055 public static final DirectoryFileStateValue DIRECTORY_FILE_STATE_NODE =
56 new DirectoryFileStateValue();
57 public static final NonexistentFileStateValue NONEXISTENT_FILE_STATE_NODE =
58 new NonexistentFileStateValue();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010059
Nathan Harmata8cd29782015-11-10 03:24:01 +000060 /** Type of a path. */
61 public enum Type {
Nathan Harmatad8b6ff22015-10-20 21:54:34 +000062 REGULAR_FILE,
63 SPECIAL_FILE,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010064 DIRECTORY,
65 SYMLINK,
66 NONEXISTENT,
67 }
68
69 protected FileStateValue() {
70 }
71
Nathan Harmata8cd29782015-11-10 03:24:01 +000072 public static FileStateValue create(RootedPath rootedPath,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010073 @Nullable TimestampGranularityMonitor tsgm) throws InconsistentFilesystemException,
74 IOException {
75 Path path = rootedPath.asPath();
76 // Stat, but don't throw an exception for the common case of a nonexistent file. This still
77 // throws an IOException in case any other IO error is encountered.
78 FileStatus stat = path.statIfFound(Symlinks.NOFOLLOW);
79 if (stat == null) {
80 return NONEXISTENT_FILE_STATE_NODE;
81 }
82 return createWithStatNoFollow(rootedPath, FileStatusWithDigestAdapter.adapt(stat), tsgm);
83 }
84
Janak Ramakrishnanb4d2d1a2015-05-06 18:17:49 +000085 static FileStateValue createWithStatNoFollow(RootedPath rootedPath,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010086 FileStatusWithDigest statNoFollow, @Nullable TimestampGranularityMonitor tsgm)
87 throws InconsistentFilesystemException, IOException {
88 Path path = rootedPath.asPath();
89 if (statNoFollow.isFile()) {
Nathan Harmatad8b6ff22015-10-20 21:54:34 +000090 return statNoFollow.isSpecialFile()
Julio Merino472b4112016-11-01 21:02:52 +000091 ? SpecialFileStateValue.fromStat(path.asFragment(), statNoFollow, tsgm)
Nathan Harmatad8b6ff22015-10-20 21:54:34 +000092 : RegularFileStateValue.fromPath(path, statNoFollow, tsgm);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010093 } else if (statNoFollow.isDirectory()) {
94 return DIRECTORY_FILE_STATE_NODE;
95 } else if (statNoFollow.isSymbolicLink()) {
Nathan Harmata215974e52015-09-16 21:31:49 +000096 return new SymlinkFileStateValue(path.readSymbolicLinkUnchecked());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010097 }
98 throw new InconsistentFilesystemException("according to stat, existing path " + path + " is "
99 + "neither a file nor directory nor symlink.");
100 }
101
Janak Ramakrishnan29c5ab42015-05-14 19:38:12 +0000102 @VisibleForTesting
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100103 @ThreadSafe
Janak Ramakrishnan29c5ab42015-05-14 19:38:12 +0000104 public static SkyKey key(RootedPath rootedPath) {
janakrbfdad902017-05-03 21:38:28 +0200105 return LegacySkyKey.create(SkyFunctions.FILE_STATE, rootedPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100106 }
107
Nathan Harmata8cd29782015-11-10 03:24:01 +0000108 public abstract Type getType();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100109
110 PathFragment getSymlinkTarget() {
111 throw new IllegalStateException();
112 }
113
114 long getSize() {
115 throw new IllegalStateException();
116 }
117
118 @Nullable
119 byte[] getDigest() {
120 throw new IllegalStateException();
121 }
122
Nathan Harmata5fb10732015-09-29 22:59:02 +0000123 @Override
124 public String toString() {
125 return prettyPrint();
126 }
127
128 abstract String prettyPrint();
129
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100130 /**
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000131 * Implementation of {@link FileStateValue} for regular files that exist.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100132 *
133 * <p>A union of (digest, mtime). We use digests only if a fast digest lookup is available from
134 * the filesystem. If not, we fall back to mtime-based digests. This avoids the case where Blaze
135 * must read all files involved in the build in order to check for modifications in the case
136 * where fast digest lookups are not available.
137 */
138 @ThreadSafe
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000139 public static final class RegularFileStateValue extends FileStateValue {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100140 private final long size;
141 // Only needed for empty-file equality-checking. Otherwise is always -1.
142 // TODO(bazel-team): Consider getting rid of this special case for empty files.
143 private final long mtime;
144 @Nullable private final byte[] digest;
145 @Nullable private final FileContentsProxy contentsProxy;
146
Michajlo Matijkiw9880b692015-10-27 23:05:49 +0000147 public RegularFileStateValue(long size, long mtime, byte[] digest,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100148 FileContentsProxy contentsProxy) {
149 Preconditions.checkState((digest == null) != (contentsProxy == null));
150 this.size = size;
151 // mtime is forced to be -1 so that we do not accidentally depend on it for non-empty files,
152 // which should only be compared using digests.
153 this.mtime = size == 0 ? mtime : -1;
154 this.digest = digest;
155 this.contentsProxy = contentsProxy;
156 }
157
158 /**
159 * Create a FileFileStateValue instance corresponding to the given existing file.
160 * @param stat must be of type "File". (Not a symlink).
161 */
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000162 private static RegularFileStateValue fromPath(Path path, FileStatusWithDigest stat,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100163 @Nullable TimestampGranularityMonitor tsgm)
164 throws InconsistentFilesystemException {
165 Preconditions.checkState(stat.isFile(), path);
Michajlo Matijkiw528957e2016-01-19 21:17:45 +0000166
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100167 try {
Michajlo Matijkiw528957e2016-01-19 21:17:45 +0000168 byte[] digest = tryGetDigest(path, stat);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100169 if (digest == null) {
170 long mtime = stat.getLastModifiedTime();
171 // Note that TimestampGranularityMonitor#notifyDependenceOnFileTime is a thread-safe
172 // method.
173 if (tsgm != null) {
Julio Merino472b4112016-11-01 21:02:52 +0000174 tsgm.notifyDependenceOnFileTime(path.asFragment(), mtime);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100175 }
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000176 return new RegularFileStateValue(stat.getSize(), stat.getLastModifiedTime(), null,
ulfjack3cb4cea2017-04-05 11:38:17 +0000177 FileContentsProxy.create(stat));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100178 } else {
179 // We are careful here to avoid putting the value ID into FileMetadata if we already have
180 // a digest. Arbitrary filesystems may do weird things with the value ID; a digest is more
181 // robust.
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000182 return new RegularFileStateValue(stat.getSize(), stat.getLastModifiedTime(), digest, null);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100183 }
184 } catch (IOException e) {
185 String errorMessage = e.getMessage() != null
186 ? "error '" + e.getMessage() + "'" : "an error";
187 throw new InconsistentFilesystemException("'stat' said " + path + " is a file but then we "
Nathan Harmata96be0c12015-09-11 22:15:50 +0000188 + "later encountered " + errorMessage + " which indicates that " + path + " is no "
189 + "longer a file. Did you delete it during the build?");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100190 }
191 }
192
Michajlo Matijkiw528957e2016-01-19 21:17:45 +0000193 @Nullable
194 private static byte[] tryGetDigest(Path path, FileStatusWithDigest stat) throws IOException {
195 try {
196 byte[] digest = stat.getDigest();
197 return digest != null ? digest : path.getFastDigest();
198 } catch (IOException ioe) {
199 if (!path.isReadable()) {
200 return null;
201 }
202 throw ioe;
203 }
204 }
205
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100206 @Override
Nathan Harmata8cd29782015-11-10 03:24:01 +0000207 public Type getType() {
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000208 return Type.REGULAR_FILE;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100209 }
210
211 @Override
Michajlo Matijkiw9880b692015-10-27 23:05:49 +0000212 public long getSize() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100213 return size;
214 }
215
Michajlo Matijkiw9880b692015-10-27 23:05:49 +0000216 public long getMtime() {
217 return mtime;
218 }
219
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100220 @Override
221 @Nullable
Michajlo Matijkiw9880b692015-10-27 23:05:49 +0000222 public byte[] getDigest() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100223 return digest;
224 }
225
Michajlo Matijkiw9880b692015-10-27 23:05:49 +0000226 public FileContentsProxy getContentsProxy() {
227 return contentsProxy;
228 }
229
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100230 @Override
231 public boolean equals(Object obj) {
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000232 if (obj instanceof RegularFileStateValue) {
233 RegularFileStateValue other = (RegularFileStateValue) obj;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100234 return size == other.size && mtime == other.mtime && Arrays.equals(digest, other.digest)
235 && Objects.equals(contentsProxy, other.contentsProxy);
236 }
237 return false;
238 }
239
240 @Override
241 public int hashCode() {
242 return Objects.hash(size, mtime, Arrays.hashCode(digest), contentsProxy);
243 }
244
245 @Override
Nathan Harmata5fb10732015-09-29 22:59:02 +0000246 public String prettyPrint() {
247 String contents = digest != null
Googler231408b2016-04-12 03:29:20 +0000248 ? String.format("digest of %s", Arrays.toString(digest))
Nathan Harmata5fb10732015-09-29 22:59:02 +0000249 : contentsProxy.prettyPrint();
250 String extra = mtime != -1 ? String.format(" and mtime of %d", mtime) : "";
251 return String.format("regular file with size of %d and %s%s", size, contents, extra);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100252 }
253 }
254
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000255 /** Implementation of {@link FileStateValue} for special files that exist. */
256 public static final class SpecialFileStateValue extends FileStateValue {
257 private final FileContentsProxy contentsProxy;
258
Michajlo Matijkiw9880b692015-10-27 23:05:49 +0000259 public SpecialFileStateValue(FileContentsProxy contentsProxy) {
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000260 this.contentsProxy = contentsProxy;
261 }
262
Julio Merino472b4112016-11-01 21:02:52 +0000263 static SpecialFileStateValue fromStat(PathFragment path, FileStatusWithDigest stat,
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000264 @Nullable TimestampGranularityMonitor tsgm) throws IOException {
265 long mtime = stat.getLastModifiedTime();
266 // Note that TimestampGranularityMonitor#notifyDependenceOnFileTime is a thread-safe
267 // method.
268 if (tsgm != null) {
Julio Merino472b4112016-11-01 21:02:52 +0000269 tsgm.notifyDependenceOnFileTime(path, mtime);
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000270 }
ulfjack3cb4cea2017-04-05 11:38:17 +0000271 return new SpecialFileStateValue(FileContentsProxy.create(stat));
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000272 }
273
274 @Override
Nathan Harmata8cd29782015-11-10 03:24:01 +0000275 public Type getType() {
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000276 return Type.SPECIAL_FILE;
277 }
278
279 @Override
280 long getSize() {
281 return 0;
282 }
283
284 @Override
285 @Nullable
286 byte[] getDigest() {
287 return null;
288 }
289
Michajlo Matijkiw9880b692015-10-27 23:05:49 +0000290 public FileContentsProxy getContentsProxy() {
291 return contentsProxy;
292 }
293
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000294 @Override
295 public boolean equals(Object obj) {
296 if (obj instanceof SpecialFileStateValue) {
297 SpecialFileStateValue other = (SpecialFileStateValue) obj;
298 return Objects.equals(contentsProxy, other.contentsProxy);
299 }
300 return false;
301 }
302
303 @Override
304 public int hashCode() {
305 return contentsProxy.hashCode();
306 }
307
308 @Override
309 public String prettyPrint() {
310 return String.format("special file with %s", contentsProxy.prettyPrint());
311 }
312 }
313
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100314 /** Implementation of {@link FileStateValue} for directories that exist. */
Michajlo Matijkiw11184592015-10-16 19:00:45 +0000315 public static final class DirectoryFileStateValue extends FileStateValue {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100316
317 private DirectoryFileStateValue() {
318 }
319
320 @Override
Nathan Harmata8cd29782015-11-10 03:24:01 +0000321 public Type getType() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100322 return Type.DIRECTORY;
323 }
324
325 @Override
Nathan Harmata5fb10732015-09-29 22:59:02 +0000326 public String prettyPrint() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100327 return "directory";
328 }
329
330 // This object is normally a singleton, but deserialization produces copies.
331 @Override
332 public boolean equals(Object obj) {
333 return obj instanceof DirectoryFileStateValue;
334 }
335
336 @Override
337 public int hashCode() {
338 return 7654321;
339 }
340 }
341
342 /** Implementation of {@link FileStateValue} for symlinks. */
Michajlo Matijkiw11184592015-10-16 19:00:45 +0000343 public static final class SymlinkFileStateValue extends FileStateValue {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100344
345 private final PathFragment symlinkTarget;
346
Michajlo Matijkiw9880b692015-10-27 23:05:49 +0000347 public SymlinkFileStateValue(PathFragment symlinkTarget) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100348 this.symlinkTarget = symlinkTarget;
349 }
350
351 @Override
Nathan Harmata8cd29782015-11-10 03:24:01 +0000352 public Type getType() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100353 return Type.SYMLINK;
354 }
355
356 @Override
Michajlo Matijkiw9880b692015-10-27 23:05:49 +0000357 public PathFragment getSymlinkTarget() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100358 return symlinkTarget;
359 }
360
361 @Override
362 public boolean equals(Object obj) {
363 if (!(obj instanceof SymlinkFileStateValue)) {
364 return false;
365 }
366 SymlinkFileStateValue other = (SymlinkFileStateValue) obj;
367 return symlinkTarget.equals(other.symlinkTarget);
368 }
369
370 @Override
371 public int hashCode() {
372 return symlinkTarget.hashCode();
373 }
374
375 @Override
Nathan Harmata5fb10732015-09-29 22:59:02 +0000376 public String prettyPrint() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100377 return "symlink to " + symlinkTarget;
378 }
379 }
380
381 /** Implementation of {@link FileStateValue} for nonexistent files. */
Michajlo Matijkiw11184592015-10-16 19:00:45 +0000382 public static final class NonexistentFileStateValue extends FileStateValue {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100383
384 private NonexistentFileStateValue() {
385 }
386
387 @Override
Nathan Harmata8cd29782015-11-10 03:24:01 +0000388 public Type getType() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100389 return Type.NONEXISTENT;
390 }
391
392 @Override
Nathan Harmata5fb10732015-09-29 22:59:02 +0000393 public String prettyPrint() {
394 return "nonexistent path";
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100395 }
396
397 // This object is normally a singleton, but deserialization produces copies.
398 @Override
399 public boolean equals(Object obj) {
400 return obj instanceof NonexistentFileStateValue;
401 }
402
403 @Override
404 public int hashCode() {
405 return 8765432;
406 }
407 }
408}