blob: ee1af6cc9742f4a279c6c5e66de1c10e9778bfa7 [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.vfs;
15
16import static java.nio.charset.StandardCharsets.ISO_8859_1;
17
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.common.base.Predicate;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010019import com.google.common.io.ByteSink;
20import com.google.common.io.ByteSource;
21import com.google.common.io.ByteStreams;
22import com.google.devtools.build.lib.concurrent.ThreadSafety.ConditionallyThreadSafe;
23import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
Mark Schaller6df81792015-12-10 18:47:47 +000024import com.google.devtools.build.lib.util.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010025import java.io.IOException;
26import java.io.InputStream;
27import java.io.OutputStream;
28import java.io.PrintStream;
29import java.nio.charset.Charset;
30import java.util.ArrayList;
31import java.util.Arrays;
32import java.util.Collection;
33import java.util.List;
Philipp Wollermanne219a242016-08-18 14:39:37 +000034import java.util.Set;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035
36/**
37 * Helper functions that implement often-used complex operations on file
38 * systems.
39 */
40@ConditionallyThreadSafe // ThreadSafe except for deleteTree.
41public class FileSystemUtils {
42
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043 private FileSystemUtils() {}
44
45 /****************************************************************************
46 * Path and PathFragment functions.
47 */
48
49 /**
50 * Throws exceptions if {@code baseName} is not a valid base name. A valid
51 * base name:
52 * <ul>
53 * <li>Is not null
54 * <li>Is not an empty string
55 * <li>Is not "." or ".."
56 * <li>Does not contain a slash
57 * </ul>
58 */
59 @ThreadSafe
60 public static void checkBaseName(String baseName) {
61 if (baseName.length() == 0) {
62 throw new IllegalArgumentException("Child must not be empty string ('')");
63 }
64 if (baseName.equals(".") || baseName.equals("..")) {
65 throw new IllegalArgumentException("baseName must not be '" + baseName + "'");
66 }
67 if (baseName.indexOf('/') != -1) {
68 throw new IllegalArgumentException("baseName must not contain a slash: '" + baseName + "'");
69 }
70 }
71
72 /**
73 * Returns the common ancestor between two paths, or null if none (including
74 * if they are on different filesystems).
75 */
76 public static Path commonAncestor(Path a, Path b) {
77 while (a != null && !b.startsWith(a)) {
78 a = a.getParentDirectory(); // returns null at root
79 }
80 return a;
81 }
82
83 /**
84 * Returns the longest common ancestor of the two path fragments, or either "/" or "" (depending
85 * on whether {@code a} is absolute or relative) if there is none.
86 */
87 public static PathFragment commonAncestor(PathFragment a, PathFragment b) {
88 while (a != null && !b.startsWith(a)) {
89 a = a.getParentDirectory();
90 }
91
92 return a;
93 }
94 /**
95 * Returns a path fragment from a given from-dir to a given to-path. May be
96 * either a short relative path "foo/bar", an up'n'over relative path
97 * "../../foo/bar" or an absolute path.
98 */
99 public static PathFragment relativePath(Path fromDir, Path to) {
100 if (to.getFileSystem() != fromDir.getFileSystem()) {
101 throw new IllegalArgumentException("fromDir and to must be on the same FileSystem");
102 }
103
104 return relativePath(fromDir.asFragment(), to.asFragment());
105 }
106
107 /**
108 * Returns a path fragment from a given from-dir to a given to-path.
109 */
110 public static PathFragment relativePath(PathFragment fromDir, PathFragment to) {
111 if (to.equals(fromDir)) {
112 return new PathFragment("."); // same dir, just return '.'
113 }
114 if (to.startsWith(fromDir)) {
115 return to.relativeTo(fromDir); // easy case--it's a descendant
116 }
117 PathFragment ancestor = commonAncestor(fromDir, to);
118 if (ancestor == null) {
119 return to; // no common ancestor, use 'to'
120 }
121 int levels = fromDir.relativeTo(ancestor).segmentCount();
122 StringBuilder dotdots = new StringBuilder();
123 for (int i = 0; i < levels; i++) {
124 dotdots.append("../");
125 }
126 return new PathFragment(dotdots.toString()).getRelative(to.relativeTo(ancestor));
127 }
128
129 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100130 * Removes the shortest suffix beginning with '.' from the basename of the
131 * filename string. If the basename contains no '.', the filename is returned
132 * unchanged.
133 *
Janak Ramakrishnan525019b2015-07-08 17:05:43 +0000134 * <p>e.g. "foo/bar.x" -> "foo/bar"
135 *
136 * <p>Note that if the filename is composed entirely of ".", this method will return the string
137 * with one fewer ".", which may have surprising effects.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100138 */
139 @ThreadSafe
140 public static String removeExtension(String filename) {
141 int lastDotIndex = filename.lastIndexOf('.');
142 if (lastDotIndex == -1) { return filename; }
143 int lastSlashIndex = filename.lastIndexOf('/');
144 if (lastSlashIndex > lastDotIndex) {
145 return filename;
146 }
147 return filename.substring(0, lastDotIndex);
148 }
149
150 /**
151 * Removes the shortest suffix beginning with '.' from the basename of the
152 * PathFragment. If the basename contains no '.', the filename is returned
153 * unchanged.
154 *
155 * <p>e.g. "foo/bar.x" -> "foo/bar"
Janak Ramakrishnan525019b2015-07-08 17:05:43 +0000156 *
157 * <p>Note that if the base filename is composed entirely of ".", this method will return the
158 * filename with one fewer "." in the base filename, which may have surprising effects.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100159 */
160 @ThreadSafe
161 public static PathFragment removeExtension(PathFragment path) {
162 return path.replaceName(removeExtension(path.getBaseName()));
163 }
164
165 /**
166 * Removes the shortest suffix beginning with '.' from the basename of the
167 * Path. If the basename contains no '.', the filename is returned
168 * unchanged.
169 *
170 * <p>e.g. "foo/bar.x" -> "foo/bar"
Janak Ramakrishnan525019b2015-07-08 17:05:43 +0000171 *
172 * <p>Note that if the base filename is composed entirely of ".", this method will return the
173 * filename with one fewer "." in the base filename, which may have surprising effects.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100174 */
175 @ThreadSafe
176 public static Path removeExtension(Path path) {
177 return path.getFileSystem().getPath(removeExtension(path.asFragment()));
178 }
179
180 /**
181 * Returns a new {@code PathFragment} formed by replacing the extension of the
182 * last path segment of {@code path} with {@code newExtension}. Null is
183 * returned iff {@code path} has zero segments.
184 */
185 public static PathFragment replaceExtension(PathFragment path, String newExtension) {
186 return path.replaceName(removeExtension(path.getBaseName()) + newExtension);
187 }
188
189 /**
190 * Returns a new {@code PathFragment} formed by replacing the extension of the
191 * last path segment of {@code path} with {@code newExtension}. Null is
192 * returned iff {@code path} has zero segments or it doesn't end with {@code oldExtension}.
193 */
194 public static PathFragment replaceExtension(PathFragment path, String newExtension,
195 String oldExtension) {
196 String base = path.getBaseName();
197 if (!base.endsWith(oldExtension)) {
198 return null;
199 }
200 String newBase = base.substring(0, base.length() - oldExtension.length()) + newExtension;
201 return path.replaceName(newBase);
202 }
203
204 /**
205 * Returns a new {@code Path} formed by replacing the extension of the
206 * last path segment of {@code path} with {@code newExtension}. Null is
207 * returned iff {@code path} has zero segments.
208 */
209 public static Path replaceExtension(Path path, String newExtension) {
210 PathFragment fragment = replaceExtension(path.asFragment(), newExtension);
211 return fragment == null ? null : path.getFileSystem().getPath(fragment);
212 }
213
214 /**
215 * Returns a new {@code PathFragment} formed by adding the extension to the last path segment of
216 * {@code path}. Null is returned if {@code path} has zero segments.
217 */
218 public static PathFragment appendExtension(PathFragment path, String newExtension) {
219 return path.replaceName(path.getBaseName() + newExtension);
220 }
221
222 /**
223 * Returns a new {@code PathFragment} formed by replacing the first, or all if
224 * {@code replaceAll} is true, {@code oldSegment} of {@code path} with {@code
225 * newSegment}.
226 */
227 public static PathFragment replaceSegments(PathFragment path,
228 String oldSegment, String newSegment, boolean replaceAll) {
229 int count = path.segmentCount();
230 for (int i = 0; i < count; i++) {
231 if (path.getSegment(i).equals(oldSegment)) {
232 path = new PathFragment(path.subFragment(0, i),
233 new PathFragment(newSegment),
234 path.subFragment(i+1, count));
235 if (!replaceAll) {
236 return path;
237 }
238 }
239 }
240 return path;
241 }
242
243 /**
244 * Returns a new {@code PathFragment} formed by appending the given string to the last path
245 * segment of {@code path} without removing the extension. Returns null if {@code path}
246 * has zero segments.
247 */
248 public static PathFragment appendWithoutExtension(PathFragment path, String toAppend) {
249 return path.replaceName(appendWithoutExtension(path.getBaseName(), toAppend));
250 }
251
252 /**
253 * Given a string that represents a file with an extension separated by a '.' and a string
254 * to append, return a string in which {@code toAppend} has been appended to {@code name}
255 * before the last '.' character. If {@code name} does not include a '.', appends {@code
256 * toAppend} at the end.
257 *
258 * <p>For example,
259 * ("libfoo.jar", "-src") ==> "libfoo-src.jar"
260 * ("libfoo", "-src") ==> "libfoo-src"
261 */
262 private static String appendWithoutExtension(String name, String toAppend) {
Ulf Adams07dba942015-03-05 14:47:37 +0000263 int dotIndex = name.lastIndexOf('.');
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100264 if (dotIndex > 0) {
265 String baseName = name.substring(0, dotIndex);
266 String extension = name.substring(dotIndex);
267 return baseName + toAppend + extension;
268 } else {
269 return name + toAppend;
270 }
271 }
272
273 /****************************************************************************
274 * FileSystem property functions.
275 */
276
277 /**
278 * Return the current working directory as expressed by the System property
279 * 'user.dir'.
280 */
281 public static Path getWorkingDirectory(FileSystem fs) {
282 return fs.getPath(getWorkingDirectory());
283 }
284
285 /**
286 * Returns the current working directory as expressed by the System property
287 * 'user.dir'. This version does not require a {@link FileSystem}.
288 */
289 public static PathFragment getWorkingDirectory() {
290 return new PathFragment(System.getProperty("user.dir", "/"));
291 }
292
293 /****************************************************************************
294 * Path FileSystem mutating operations.
295 */
296
297 /**
298 * "Touches" the file or directory specified by the path, following symbolic
299 * links. If it does not exist, it is created as an empty file; otherwise, the
300 * time of last access is updated to the current time.
301 *
302 * @throws IOException if there was an error while touching the file
303 */
304 @ThreadSafe
305 public static void touchFile(Path path) throws IOException {
306 if (path.exists()) {
307 // -1L means "use the current time", and is ultimately implemented by
308 // utime(path, null), thereby using the kernel's clock, not the JVM's.
309 // (A previous implementation based on the JVM clock was found to be
310 // skewy.)
311 path.setLastModifiedTime(-1L);
312 } else {
313 createEmptyFile(path);
314 }
315 }
316
317 /**
318 * Creates an empty regular file with the name of the current path, following
319 * symbolic links.
320 *
321 * @throws IOException if the file could not be created for any reason
322 * (including that there was already a file at that location)
323 */
324 public static void createEmptyFile(Path path) throws IOException {
325 path.getOutputStream().close();
326 }
327
328 /**
329 * Creates or updates a symbolic link from 'link' to 'target'. Replaces
330 * existing symbolic links with target, and skips the link creation if it is
331 * already present. Will also create any missing ancestor directories of the
332 * link. This method is non-atomic
333 *
334 * <p>Note: this method will throw an IOException if there is an unequal
335 * non-symlink at link.
336 *
337 * @throws IOException if the creation of the symbolic link was unsuccessful
338 * for any reason.
339 */
340 @ThreadSafe // but not atomic
341 public static void ensureSymbolicLink(Path link, Path target) throws IOException {
342 ensureSymbolicLink(link, target.asFragment());
343 }
344
345 /**
346 * Creates or updates a symbolic link from 'link' to 'target'. Replaces
347 * existing symbolic links with target, and skips the link creation if it is
348 * already present. Will also create any missing ancestor directories of the
349 * link. This method is non-atomic
350 *
351 * <p>Note: this method will throw an IOException if there is an unequal
352 * non-symlink at link.
353 *
354 * @throws IOException if the creation of the symbolic link was unsuccessful
355 * for any reason.
356 */
357 @ThreadSafe // but not atomic
358 public static void ensureSymbolicLink(Path link, String target) throws IOException {
359 ensureSymbolicLink(link, new PathFragment(target));
360 }
361
362 /**
363 * Creates or updates a symbolic link from 'link' to 'target'. Replaces
364 * existing symbolic links with target, and skips the link creation if it is
365 * already present. Will also create any missing ancestor directories of the
366 * link. This method is non-atomic
367 *
368 * <p>Note: this method will throw an IOException if there is an unequal
369 * non-symlink at link.
370 *
371 * @throws IOException if the creation of the symbolic link was unsuccessful
372 * for any reason.
373 */
374 @ThreadSafe // but not atomic
375 public static void ensureSymbolicLink(Path link, PathFragment target) throws IOException {
376 // TODO(bazel-team): (2009) consider adding the logic for recovering from the case when
377 // we have already created a parent directory symlink earlier.
378 try {
379 if (link.readSymbolicLink().equals(target)) {
380 return; // Do nothing if the link is already there.
381 }
382 } catch (IOException e) { // link missing or broken
383 /* fallthru and do the work below */
384 }
385 if (link.isSymbolicLink()) {
386 link.delete(); // Remove the symlink since it is pointing somewhere else.
387 } else {
388 createDirectoryAndParents(link.getParentDirectory());
389 }
390 try {
391 link.createSymbolicLink(target);
392 } catch (IOException e) {
393 // Only pass on exceptions caused by a true link creation failure.
394 if (!link.isSymbolicLink() ||
395 !link.resolveSymbolicLinks().equals(link.getRelative(target))) {
396 throw e;
397 }
398 }
399 }
400
Eric Fellheimer5843d1f2016-02-11 15:17:55 +0000401 public static ByteSource asByteSource(final Path path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100402 return new ByteSource() {
403 @Override public InputStream openStream() throws IOException {
404 return path.getInputStream();
405 }
406 };
407 }
408
Eric Fellheimer5843d1f2016-02-11 15:17:55 +0000409 public static ByteSink asByteSink(final Path path, final boolean append) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100410 return new ByteSink() {
411 @Override public OutputStream openStream() throws IOException {
412 return path.getOutputStream(append);
413 }
414 };
415 }
416
Eric Fellheimer5843d1f2016-02-11 15:17:55 +0000417 public static ByteSink asByteSink(final Path path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100418 return asByteSink(path, false);
419 }
420
421 /**
422 * Copies the file from location "from" to location "to", while overwriting a
423 * potentially existing "to". File's last modified time, executable and
424 * writable bits are also preserved.
425 *
426 * <p>If no error occurs, the method returns normally. If a parent directory does
427 * not exist, a FileNotFoundException is thrown. An IOException is thrown when
428 * other erroneous situations occur. (e.g. read errors)
429 */
430 @ThreadSafe // but not atomic
431 public static void copyFile(Path from, Path to) throws IOException {
432 try {
433 to.delete();
434 } catch (IOException e) {
435 throw new IOException("error copying file: "
436 + "couldn't delete destination: " + e.getMessage());
437 }
438 asByteSource(from).copyTo(asByteSink(to));
439 to.setLastModifiedTime(from.getLastModifiedTime()); // Preserve mtime.
440 if (!from.isWritable()) {
441 to.setWritable(false); // Make file read-only if original was read-only.
442 }
443 to.setExecutable(from.isExecutable()); // Copy executable bit.
444 }
445
446 /**
447 * Copies a tool binary from one path to another, returning the target path.
448 * The directory of the target path must already exist. The target copy's time
449 * is set to match, as well as its read-only and executable flags. The
450 * operation is skipped if the target file has the same time and size as the
451 * source.
452 */
453 public static Path copyTool(Path source, Path target) throws IOException {
454 FileStatus sourceStat = null;
455 FileStatus targetStat = target.statNullable();
456 if (targetStat != null) {
457 // stat the source file only if we'll need the stat.
458 sourceStat = source.stat(Symlinks.FOLLOW);
459 }
460 if (targetStat == null ||
461 targetStat.getLastModifiedTime() != sourceStat.getLastModifiedTime() ||
462 targetStat.getSize() != sourceStat.getSize()) {
463 copyFile(source, target);
464 target.setWritable(source.isWritable());
465 target.setExecutable(source.isExecutable());
466 target.setLastModifiedTime(source.getLastModifiedTime());
467 }
468 return target;
469 }
470
471 /****************************************************************************
472 * Directory tree operations.
473 */
474
475 /**
476 * Returns a new collection containing all of the paths below a given root
477 * path, for which the given predicate is true. Symbolic links are not
478 * followed, and may appear in the result.
479 *
480 * @throws IOException If the root does not denote a directory
481 */
482 @ThreadSafe
483 public static Collection<Path> traverseTree(Path root, Predicate<? super Path> predicate)
484 throws IOException {
485 List<Path> paths = new ArrayList<>();
486 traverseTree(paths, root, predicate);
487 return paths;
488 }
489
490 /**
491 * Populates an existing Path List, adding all of the paths below a given root
492 * path for which the given predicate is true. Symbolic links are not
493 * followed, and may appear in the result.
494 *
495 * @throws IOException If the root does not denote a directory
496 */
497 @ThreadSafe
498 public static void traverseTree(Collection<Path> paths, Path root,
499 Predicate<? super Path> predicate) throws IOException {
500 for (Path p : root.getDirectoryEntries()) {
501 if (predicate.apply(p)) {
502 paths.add(p);
503 }
504 if (p.isDirectory(Symlinks.NOFOLLOW)) {
505 traverseTree(paths, p, predicate);
506 }
507 }
508 }
509
510 /**
511 * Deletes 'p', and everything recursively beneath it if it's a directory.
512 * Does not follow any symbolic links.
513 *
514 * @throws IOException if any file could not be removed.
515 */
516 @ThreadSafe
517 public static void deleteTree(Path p) throws IOException {
518 deleteTreesBelow(p);
519 p.delete();
520 }
521
522 /**
523 * Deletes all dir trees recursively beneath 'dir' if it's a directory,
524 * nothing otherwise. Does not follow any symbolic links.
525 *
526 * @throws IOException if any file could not be removed.
527 */
528 @ThreadSafe
529 public static void deleteTreesBelow(Path dir) throws IOException {
530 if (dir.isDirectory(Symlinks.NOFOLLOW)) { // real directories (not symlinks)
531 dir.setReadable(true);
532 dir.setWritable(true);
533 dir.setExecutable(true);
534 for (Path child : dir.getDirectoryEntries()) {
535 deleteTree(child);
536 }
537 }
538 }
539
540 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100541 * Copies all dir trees under a given 'from' dir to location 'to', while overwriting
Lukacs Berki96202b12016-02-11 13:45:56 +0000542 * all files in the potentially existing 'to'. Resolves symbolic links.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100543 *
544 * <p>The source and the destination must be non-overlapping, otherwise an
545 * IllegalArgumentException will be thrown. This method cannot be used to copy
546 * a dir tree to a sub tree of itself.
547 *
548 * <p>If no error occurs, the method returns normally. If the given 'from' does
549 * not exist, a FileNotFoundException is thrown. An IOException is thrown when
550 * other erroneous situations occur. (e.g. read errors)
551 */
552 @ThreadSafe
553 public static void copyTreesBelow(Path from , Path to) throws IOException {
554 if (to.startsWith(from)) {
555 throw new IllegalArgumentException(to + " is a subdirectory of " + from);
556 }
557
558 Collection<Path> entries = from.getDirectoryEntries();
559 for (Path entry : entries) {
Lukacs Berki96202b12016-02-11 13:45:56 +0000560 if (entry.isFile()) {
561 Path newEntry = to.getChild(entry.getBaseName());
562 copyFile(entry, newEntry);
563 } else {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100564 Path subDir = to.getChild(entry.getBaseName());
565 subDir.createDirectory();
566 copyTreesBelow(entry, subDir);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100567 }
568 }
569 }
570
571 /**
572 * Attempts to create a directory with the name of the given path, creating
573 * ancestors as necessary.
574 *
575 * <p>Postcondition: completes normally iff {@code dir} denotes an existing
576 * directory (not necessarily canonical); completes abruptly otherwise.
577 *
578 * @return true if the directory was successfully created anew, false if it
579 * already existed (including the case where {@code dir} denotes a symlink
580 * to an existing directory)
581 * @throws IOException if the directory could not be created
582 */
583 @ThreadSafe
584 public static boolean createDirectoryAndParents(Path dir) throws IOException {
Philipp Wollermanne219a242016-08-18 14:39:37 +0000585 return createDirectoryAndParentsWithCache(null, dir);
586 }
587
588 /**
589 * Attempts to create a directory with the name of the given path, creating ancestors as
590 * necessary. Only creates directories or their parents if they are not contained in the set
591 * {@code createdDirs} and instead assumes that they already exist. This saves a round-trip to the
592 * kernel, but is only safe when no one deletes directories that have been created by this method.
593 *
594 * <p>Postcondition: completes normally iff {@code dir} denotes an existing directory (not
595 * necessarily canonical); completes abruptly otherwise.
596 *
597 * @return true if the directory was successfully created anew, false if it already existed
598 * (including the case where {@code dir} denotes a symlink to an existing directory)
599 * @throws IOException if the directory could not be created
600 */
601 @ThreadSafe
602 public static boolean createDirectoryAndParentsWithCache(Set<Path> createdDirs, Path dir)
603 throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100604 // Optimised for minimal number of I/O calls.
605
606 // Don't attempt to create the root directory.
Philipp Wollermanne219a242016-08-18 14:39:37 +0000607 if (dir.getParentDirectory() == null) {
608 return false;
609 }
610
611 // We already created that directory.
612 if (createdDirs != null && createdDirs.contains(dir)) {
613 return false;
614 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100615
616 FileSystem filesystem = dir.getFileSystem();
617 if (filesystem instanceof UnionFileSystem) {
618 // If using UnionFS, make sure that we do not traverse filesystem boundaries when creating
619 // parent directories by rehoming the path on the most specific filesystem.
620 FileSystem delegate = ((UnionFileSystem) filesystem).getDelegate(dir);
621 dir = delegate.getPath(dir.asFragment());
622 }
623
624 try {
Philipp Wollermanne219a242016-08-18 14:39:37 +0000625 boolean result = dir.createDirectory();
626 if (createdDirs != null) {
627 createdDirs.add(dir);
628 }
629 return result;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100630 } catch (IOException e) {
631 if (e.getMessage().endsWith(" (No such file or directory)")) { // ENOENT
Philipp Wollermanne219a242016-08-18 14:39:37 +0000632 createDirectoryAndParentsWithCache(createdDirs, dir.getParentDirectory());
633 boolean result = dir.createDirectory();
634 if (createdDirs != null) {
635 createdDirs.add(dir);
636 }
637 return result;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100638 } else if (e.getMessage().endsWith(" (File exists)") && dir.isDirectory()) { // EEXIST
Philipp Wollermanne219a242016-08-18 14:39:37 +0000639 if (createdDirs != null) {
640 createdDirs.add(dir);
641 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100642 return false;
643 } else {
644 throw e; // some other error (e.g. ENOTDIR, EACCES, etc.)
645 }
646 }
647 }
648
649 /**
650 * Attempts to remove a relative chain of directories under a given base.
651 * Returns {@code true} if the removal was successful, and returns {@code
652 * false} if the removal fails because a directory was not empty. An
653 * {@link IOException} is thrown for any other errors.
654 */
655 @ThreadSafe
656 public static boolean removeDirectoryAndParents(Path base, PathFragment toRemove) {
657 if (toRemove.isAbsolute()) {
658 return false;
659 }
660 try {
661 for (; toRemove.segmentCount() > 0; toRemove = toRemove.getParentDirectory()) {
662 Path p = base.getRelative(toRemove);
663 if (p.exists()) {
664 p.delete();
665 }
666 }
667 } catch (IOException e) {
668 return false;
669 }
670 return true;
671 }
672
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100673 /****************************************************************************
674 * Whole-file I/O utilities for characters and bytes. These convenience
675 * methods are not efficient and should not be used for large amounts of data!
676 */
677
Nathan Harmata4e698242015-10-20 23:18:23 +0000678 /**
679 * Decodes the given byte array assumed to be encoded with ISO-8859-1 encoding (isolatin1).
680 */
681 public static char[] convertFromLatin1(byte[] content) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100682 char[] latin1 = new char[content.length];
683 for (int i = 0; i < latin1.length; i++) { // yeah, latin1 is this easy! :-)
684 latin1[i] = (char) (0xff & content[i]);
685 }
686 return latin1;
687 }
688
689 /**
690 * Writes lines to file using ISO-8859-1 encoding (isolatin1).
691 */
692 @ThreadSafe // but not atomic
693 public static void writeIsoLatin1(Path file, String... lines) throws IOException {
694 writeLinesAs(file, ISO_8859_1, lines);
695 }
696
697 /**
698 * Append lines to file using ISO-8859-1 encoding (isolatin1).
699 */
700 @ThreadSafe // but not atomic
701 public static void appendIsoLatin1(Path file, String... lines) throws IOException {
702 appendLinesAs(file, ISO_8859_1, lines);
703 }
704
705 /**
706 * Writes the specified String as ISO-8859-1 (latin1) encoded bytes to the
707 * file. Follows symbolic links.
708 *
709 * @throws IOException if there was an error
710 */
711 public static void writeContentAsLatin1(Path outputFile, String content) throws IOException {
712 writeContent(outputFile, ISO_8859_1, content);
713 }
714
715 /**
716 * Writes the specified String using the specified encoding to the file.
717 * Follows symbolic links.
718 *
719 * @throws IOException if there was an error
720 */
721 public static void writeContent(Path outputFile, Charset charset, String content)
722 throws IOException {
723 asByteSink(outputFile).asCharSink(charset).write(content);
724 }
725
726 /**
727 * Writes lines to file using the given encoding, ending every line with a
728 * line break '\n' character.
729 */
730 @ThreadSafe // but not atomic
731 public static void writeLinesAs(Path file, Charset charset, String... lines)
732 throws IOException {
733 writeLinesAs(file, charset, Arrays.asList(lines));
734 }
735
736 /**
737 * Appends lines to file using the given encoding, ending every line with a
738 * line break '\n' character.
739 */
740 @ThreadSafe // but not atomic
741 public static void appendLinesAs(Path file, Charset charset, String... lines)
742 throws IOException {
743 appendLinesAs(file, charset, Arrays.asList(lines));
744 }
745
746 /**
747 * Writes lines to file using the given encoding, ending every line with a
748 * line break '\n' character.
749 */
750 @ThreadSafe // but not atomic
751 public static void writeLinesAs(Path file, Charset charset, Iterable<String> lines)
752 throws IOException {
753 createDirectoryAndParents(file.getParentDirectory());
754 asByteSink(file).asCharSink(charset).writeLines(lines);
755 }
756
757 /**
758 * Appends lines to file using the given encoding, ending every line with a
759 * line break '\n' character.
760 */
761 @ThreadSafe // but not atomic
762 public static void appendLinesAs(Path file, Charset charset, Iterable<String> lines)
763 throws IOException {
764 createDirectoryAndParents(file.getParentDirectory());
765 asByteSink(file, true).asCharSink(charset).writeLines(lines);
766 }
767
768 /**
769 * Writes the specified byte array to the output file. Follows symbolic links.
770 *
771 * @throws IOException if there was an error
772 */
773 public static void writeContent(Path outputFile, byte[] content) throws IOException {
774 asByteSink(outputFile).write(content);
775 }
776
777 /**
778 * Returns the entirety of the specified input stream and returns it as a char
779 * array, decoding characters using ISO-8859-1 (Latin1).
780 *
781 * @throws IOException if there was an error
782 */
783 public static char[] readContentAsLatin1(InputStream in) throws IOException {
784 return convertFromLatin1(ByteStreams.toByteArray(in));
785 }
786
787 /**
788 * Returns the entirety of the specified file and returns it as a char array,
789 * decoding characters using ISO-8859-1 (Latin1).
790 *
791 * @throws IOException if there was an error
792 */
793 public static char[] readContentAsLatin1(Path inputFile) throws IOException {
794 return convertFromLatin1(readContent(inputFile));
795 }
796
797 /**
798 * Returns an iterable that allows iterating over ISO-8859-1 (Latin1) text
799 * file contents line by line. If the file ends in a line break, the iterator
800 * will return an empty string as the last element.
801 *
802 * @throws IOException if there was an error
803 */
804 public static Iterable<String> iterateLinesAsLatin1(Path inputFile) throws IOException {
Chris Parsons64fa3512015-12-14 19:28:01 +0000805 return readLines(inputFile, ISO_8859_1);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100806 }
807
808 /**
Chris Parsons64fa3512015-12-14 19:28:01 +0000809 * Returns an iterable that allows iterating over text file contents line by line in the given
810 * {@link Charset}. If the file ends in a line break, the iterator will return an empty string
811 * as the last element.
812 *
813 * @throws IOException if there was an error
814 */
815 public static Iterable<String> readLines(Path inputFile, Charset charset) throws IOException {
816 return asByteSource(inputFile).asCharSource(charset).readLines();
817 }
Luis Fernando Pino Duquebe102182016-05-23 14:03:55 +0000818
Chris Parsons64fa3512015-12-14 19:28:01 +0000819 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100820 * Returns the entirety of the specified file and returns it as a byte array.
821 *
822 * @throws IOException if there was an error
823 */
824 public static byte[] readContent(Path inputFile) throws IOException {
825 return asByteSource(inputFile).read();
826 }
827
828 /**
Florian Weikert75037572015-08-01 20:03:46 +0000829 * Reads the entire file using the given charset and returns the contents as a string
830 */
831 public static String readContent(Path inputFile, Charset charset) throws IOException {
832 return asByteSource(inputFile).asCharSource(charset).read();
833 }
834
835 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100836 * Reads at most {@code limit} bytes from {@code inputFile} and returns it as a byte array.
837 *
838 * @throws IOException if there was an error.
839 */
840 public static byte[] readContentWithLimit(Path inputFile, int limit) throws IOException {
841 Preconditions.checkArgument(limit >= 0, "limit needs to be >=0, but it is %s", limit);
842 ByteSource byteSource = asByteSource(inputFile);
843 byte[] buffer = new byte[limit];
844 try (InputStream inputStream = byteSource.openBufferedStream()) {
845 int read = ByteStreams.read(inputStream, buffer, 0, limit);
Eric Fellheimer7f689942015-11-04 01:20:00 +0000846 return read == limit ? buffer : Arrays.copyOf(buffer, read);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100847 }
848 }
849
850 /**
Nathan Harmata4e698242015-10-20 23:18:23 +0000851 * Reads the given file {@code path}, assumed to have size {@code fileSize}, and does a sanity
852 * check on the number of bytes read.
853 *
854 * <p>Use this method when you already know the size of the file. The sanity check is intended to
855 * catch issues where filesystems incorrectly truncate files.
856 *
857 * @throws IOException if there was an error, or if fewer than {@code fileSize} bytes were read.
858 */
859 public static byte[] readWithKnownFileSize(Path path, long fileSize) throws IOException {
860 if (fileSize > Integer.MAX_VALUE) {
861 throw new IOException("Cannot read file with size larger than 2GB");
862 }
863 int fileSizeInt = (int) fileSize;
864 byte[] bytes = readContentWithLimit(path, fileSizeInt);
865 if (fileSizeInt > bytes.length) {
866 throw new IOException("Unexpected short read from file '" + path
867 + "' (expected " + fileSizeInt + ", got " + bytes.length + " bytes)");
868 }
869 return bytes;
870 }
871
872 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100873 * Dumps diagnostic information about the specified filesystem to {@code out}.
874 * This is the implementation of the filesystem part of the 'blaze dump'
875 * command. It lives here, rather than in DumpCommand, because it requires
876 * privileged access to members of this package.
877 *
878 * <p>Its results are unspecified and MUST NOT be interpreted programmatically.
879 */
880 public static void dump(FileSystem fs, final PrintStream out) {
881 if (!(fs instanceof UnixFileSystem)) {
882 out.println(" Not a UnixFileSystem.");
883 return;
884 }
885
886 // Unfortunately there's no "letrec" for anonymous functions so we have to
887 // (a) name the function, (b) put it in a box and (c) use List not array
888 // because of the generic type. *sigh*.
889 final List<Predicate<Path>> dumpFunction = new ArrayList<>();
890 dumpFunction.add(new Predicate<Path>() {
891 @Override
892 public boolean apply(Path child) {
893 Path path = child;
894 out.println(" " + path + " (" + path.toDebugString() + ")");
895 path.applyToChildren(dumpFunction.get(0));
896 return false;
897 }
898 });
899
900 fs.getRootDirectory().applyToChildren(dumpFunction.get(0));
901 }
902
903 /**
904 * Returns the type of the file system path belongs to.
905 */
906 public static String getFileSystem(Path path) {
907 return path.getFileSystem().getFileSystemType(path);
908 }
909
910 /**
911 * Returns whether the given path starts with any of the paths in the given
912 * list of prefixes.
913 */
914 public static boolean startsWithAny(Path path, Iterable<Path> prefixes) {
915 for (Path prefix : prefixes) {
916 if (path.startsWith(prefix)) {
917 return true;
918 }
919 }
920 return false;
921 }
922
923 /**
924 * Returns whether the given path starts with any of the paths in the given
925 * list of prefixes.
926 */
927 public static boolean startsWithAny(PathFragment path, Iterable<PathFragment> prefixes) {
928 for (PathFragment prefix : prefixes) {
929 if (path.startsWith(prefix)) {
930 return true;
931 }
932 }
933 return false;
934 }
Googlere1cd9502016-09-07 14:33:29 +0000935
936
937 /**
938 * Create a new hard link file at "linkPath" for file at "originalPath". If "originalPath" is a
939 * directory, then for each entry, create link under "linkPath" recursively.
940 *
941 * @param linkPath The path of the new link file to be created
942 * @param originalPath The path of the original file
943 * @throws IOException if there was an error executing {@link Path#createHardLink}
944 */
945 public static void createHardLink(Path linkPath, Path originalPath) throws IOException {
946
947 // Regular file
948 if (originalPath.isFile()) {
949 Path parentDir = linkPath.getParentDirectory();
950 if (!parentDir.exists()) {
951 FileSystemUtils.createDirectoryAndParents(parentDir);
952 }
953 originalPath.createHardLink(linkPath);
954 // Directory
955 } else if (originalPath.isDirectory()) {
956 for (Path originalSubpath : originalPath.getDirectoryEntries()) {
957 Path linkSubpath = linkPath.getRelative(originalSubpath.relativeTo(originalPath));
958 createHardLink(linkSubpath, originalSubpath);
959 }
960 }
961 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100962}