blob: 0cab41ca4ffb53c12084bf6b0c8a1b6f7dd515ca [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.
14
15package com.google.devtools.build.lib.unix;
16
Nathan Harmatad8b6ff22015-10-20 21:54:34 +000017import com.google.common.annotations.VisibleForTesting;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.common.hash.HashCode;
19import com.google.devtools.build.lib.UnixJniLoader;
20
21import java.io.File;
22import java.io.FileNotFoundException;
23import java.io.IOException;
24import java.util.logging.Level;
25import java.util.logging.LogManager;
26import java.util.logging.Logger;
27
28/**
29 * Utility methods for access to UNIX filesystem calls not exposed by the Java
30 * SDK. Exception messages are selected to be consistent with those generated
31 * by the java.io package where appropriate--see package javadoc for details.
32 */
33public final class FilesystemUtils {
34
35 private FilesystemUtils() {}
36
37 /**
38 * Returns true iff the file identified by 'path' is a symbolic link. Has
39 * similar semantics to POSIX stat(2) syscall, with all errors being mapped to
40 * a false return.
41 *
42 * @param path the file of interest
43 * @return true iff path exists, is accessible and is a symlink
44 */
45 public static boolean isSymbolicLink(File path) {
46 try {
47 return lstat(path.toString()).isSymbolicLink();
48 } catch (IOException e) {
49 return false;
50 }
51 }
52
53
54 /**
55 * Returns true iff the file identified by 'path' is a directory. Has
56 * similar semantics to POSIX stat(2) syscall, with all errors being mapped to
57 * a false return.
58 *
59 * @param path the file of interest
60 * @return true iff path exists, is accessible and is a symlink
61 */
62 public static boolean isDirectory(String path) {
63 try {
64 return lstat(path).isDirectory();
65 } catch (IOException e) {
66 return false;
67 }
68 }
69
70
71 /**
72 * Marks the file or directory {@code path} as executable. (Non-atomic)
73 *
74 * @see File#setReadOnly
75 *
76 * @param path the file of interest
77 * @throws FileAccessException if path can't be accessed
78 * @throws FileNotFoundException if path doesn't exist
79 * @throws IOException for other filesystem or path errors
80 */
81 public static void setExecutable(File path) throws IOException {
82 String p = path.toString();
83 chmod(p, stat(p).getPermissions() | FileStatus.S_IEXEC);
84 }
85
86 /**
87 * Marks the file or directory {@code path} as owner writable. (Non-atomic)
88 *
89 * @see File#setReadOnly
90 *
91 * @param path the file of interest
92 * @throws FileAccessException if path can't be accessed
93 * @throws FileNotFoundException if path doesn't exist
94 * @throws IOException for other filesystem or path errors
95 */
96 public static void setWritable(File path) throws IOException {
97 String p = path.toString();
98 chmod(p, stat(p).getPermissions() | FileStatus.S_IWUSR);
99 }
100
101 /**
102 * Changes permissions of a file.
103 *
104 * @param path the file whose mode to change.
105 * @param mode the mode bits within 07777, interpreted according to
106 * long-standing UNIX tradition.
107 * @throws IOException if the chmod() syscall failed.
108 */
109 public static void chmod(File path, int mode) throws IOException {
110 int mask = FileStatus.S_ISUID |
111 FileStatus.S_ISGID |
112 FileStatus.S_ISVTX |
113 FileStatus.S_IRWXA;
114 chmod(path.toString(), mode & mask);
115 }
116
117 /*
118 * Native-based implementation
119 */
120
121 static {
122 if (!java.nio.charset.Charset.defaultCharset().name().equals("ISO-8859-1")) {
123 // Defer the Logger call, so we don't deadlock if this is called from Logger
124 // initialization code.
125 new Thread() {
126 @Override
127 public void run() {
128 // wait (if necessary) until the logging system is initialized
129 synchronized (LogManager.getLogManager()) {}
130 Logger.getLogger("com.google.devtools.build.lib.unix.FilesystemUtils").log(Level.FINE,
131 "WARNING: Default character set is not latin1; java.io.File and " +
132 "com.google.devtools.build.lib.unix.FilesystemUtils will represent some filenames " +
133 "differently.");
134 }
135 }.start();
136 }
137 UnixJniLoader.loadJni();
138 }
139
140 /**
141 * Native wrapper around Linux readlink(2) call.
142 *
143 * @param path the file of interest
144 * @return the pathname to which the symbolic link 'path' links
145 * @throws IOException iff the readlink() call failed
146 */
147 public static native String readlink(String path) throws IOException;
148
149 /**
150 * Native wrapper around POSIX chmod(2) syscall: Changes the file access
151 * permissions of 'path' to 'mode'.
152 *
153 * @param path the file of interest
154 * @param mode the POSIX type and permission mode bits to set
155 * @throws IOException iff the chmod() call failed.
156 */
157 public static native void chmod(String path, int mode) throws IOException;
158
159 /**
160 * Native wrapper around POSIX symlink(2) syscall.
161 *
162 * @param oldpath the file to link to
163 * @param newpath the new path for the link
164 * @throws IOException iff the symlink() syscall failed.
165 */
166 public static native void symlink(String oldpath, String newpath)
167 throws IOException;
168
169 /**
170 * Native wrapper around POSIX stat(2) syscall.
171 *
172 * @param path the file to stat.
173 * @return a FileStatus instance containing the metadata.
174 * @throws IOException if the stat() syscall failed.
175 */
176 public static native FileStatus stat(String path) throws IOException;
177
178 /**
179 * Native wrapper around POSIX lstat(2) syscall.
180 *
181 * @param path the file to lstat.
182 * @return a FileStatus instance containing the metadata.
183 * @throws IOException if the lstat() syscall failed.
184 */
185 public static native FileStatus lstat(String path) throws IOException;
186
187 /**
188 * Native wrapper around POSIX stat(2) syscall.
189 *
190 * @param path the file to stat.
191 * @return an ErrnoFileStatus instance containing the metadata.
192 * If there was an error, the return value's hasError() method
193 * will return true, and all stat information is undefined.
194 */
195 public static native ErrnoFileStatus errnoStat(String path);
196
197 /**
198 * Native wrapper around POSIX lstat(2) syscall.
199 *
200 * @param path the file to lstat.
201 * @return an ErrnoFileStatus instance containing the metadata.
202 * If there was an error, the return value's hasError() method
203 * will return true, and all stat information is undefined.
204 */
205 public static native ErrnoFileStatus errnoLstat(String path);
206
207 /**
208 * Native wrapper around POSIX utime(2) syscall.
209 *
210 * Note: negative file times are interpreted as unsigned time_t.
211 *
212 * @param path the file whose times to change.
213 * @param now if true, ignore actime/modtime parameters and use current time.
214 * @param actime the file access time in seconds since the UNIX epoch.
215 * @param modtime the file modification time in seconds since the UNIX epoch.
216 * @throws IOException if the utime() syscall failed.
217 */
218 public static native void utime(String path, boolean now,
219 int actime, int modtime) throws IOException;
220
221 /**
222 * Native wrapper around POSIX mkdir(2) syscall.
223 *
224 * Caveat: errno==EEXIST is mapped to the return value "false", not
225 * IOException. It requires an additional stat() to determine if mkdir
226 * failed because the directory already exists.
227 *
228 * @param path the directory to create.
229 * @param mode the mode with which to create the directory.
230 * @return true if the directory was successfully created; false if the
231 * system call returned EEXIST because some kind of a file (not necessarily
232 * a directory) already exists.
233 * @throws IOException if the mkdir() syscall failed for any other reason.
234 */
235 public static native boolean mkdir(String path, int mode)
236 throws IOException;
237
238 /**
239 * Native wrapper around POSIX opendir(2)/readdir(3)/closedir(3) syscall.
240 *
241 * @param path the directory to read.
242 * @return the list of directory entries in the order they were returned by
243 * the system, excluding "." and "..".
244 * @throws IOException if the call to opendir failed for any reason.
245 */
246 public static String[] readdir(String path) throws IOException {
247 return readdir(path, ReadTypes.NONE).names;
248 }
249
250 /**
251 * An enum for specifying now the types of the individual entries returned by
252 * {@link #readdir(String, ReadTypes)} is to be returned.
253 */
254 public enum ReadTypes {
255 NONE('n'), // Do not read types
256 NOFOLLOW('d'), // Do not follow symlinks
257 FOLLOW('f'); // Follow symlinks; never returns "SYMLINK" and returns "UNKNOWN" when dangling
258
259 private final char code;
260
261 private ReadTypes(char code) {
262 this.code = code;
263 }
264
265 private char getCode() {
266 return code;
267 }
268 }
269
270 /**
271 * A compound return type for readdir(), analogous to struct dirent[] in C. A low memory profile
272 * is critical for this class, as instances are expected to be kept around for caching for
273 * potentially a long time.
274 */
275 public static final class Dirents {
276
277 /**
278 * The type of the directory entry.
279 */
280 public enum Type {
281 FILE,
282 DIRECTORY,
283 SYMLINK,
284 UNKNOWN;
285
286 private static Type forChar(char c) {
287 if (c == 'f') {
288 return Type.FILE;
289 } else if (c == 'd') {
290 return Type.DIRECTORY;
291 } else if (c == 's') {
292 return Type.SYMLINK;
293 } else {
294 return Type.UNKNOWN;
295 }
296 }
297 }
298
299 /** The names of the entries in a directory. */
300 private final String[] names;
301 /**
302 * An optional (nullable) array of entry types, corresponding positionally
303 * to the "names" field. The types are:
304 * 'd': a subdirectory
305 * 'f': a regular file
306 * 's': a symlink (only returned with {@code NOFOLLOW})
307 * '?': anything else
308 * Note that unlike libc, this implementation of readdir() follows
309 * symlinks when determining these types.
310 *
311 * <p>This is intentionally a byte array rather than a array of enums to save memory.
312 */
313 private final byte[] types;
314
315 /** called from JNI */
316 public Dirents(String[] names, byte[] types) {
317 this.names = names;
318 this.types = types;
319 }
320
321 public int size() {
322 return names.length;
323 }
324
325 public boolean hasTypes() {
326 return types != null;
327 }
328
329 public String getName(int i) {
330 return names[i];
331 }
332
333 public Type getType(int i) {
334 return Type.forChar((char) types[i]);
335 }
336 }
337
338 /**
339 * Native wrapper around POSIX opendir(2)/readdir(3)/closedir(3) syscall.
340 *
341 * @param path the directory to read.
342 * @param readTypes How the types of individual entries should be returned. If {@code NONE},
343 * the "types" field in the result will be null.
344 * @return a Dirents object, containing "names", the list of directory entries
345 * (excluding "." and "..") in the order they were returned by the system,
346 * and "types", an array of entry types (file, directory, etc) corresponding
347 * positionally to "names".
348 * @throws IOException if the call to opendir failed for any reason.
349 */
350 public static Dirents readdir(String path, ReadTypes readTypes) throws IOException {
351 // Passing enums to native code is possible, but onerous; we use a char instead.
352 return readdir(path, readTypes.getCode());
353 }
354
355 private static native Dirents readdir(String path, char typeCode)
356 throws IOException;
357
358 /**
359 * Native wrapper around POSIX rename(2) syscall.
360 *
361 * @param oldpath the source location.
362 * @param newpath the destination location.
363 * @throws IOException if the rename failed for any reason.
364 */
365 public static native void rename(String oldpath, String newpath)
366 throws IOException;
367
368 /**
369 * Native wrapper around POSIX remove(3) C library call.
370 *
371 * @param path the file or directory to remove.
372 * @return true iff the file was actually deleted by this call.
373 * @throws IOException if the remove failed, but the file was present prior to the call.
374 */
375 public static native boolean remove(String path) throws IOException;
376
Nathan Harmatad8b6ff22015-10-20 21:54:34 +0000377 /**
378 * Native wrapper around POSIX mkfifo(3) C library call.
379 *
380 * @param path the name of the pipe to create.
381 * @param mode the mode with which to create the pipe.
382 * @throws IOException if the mkfifo failed.
383 */
384 @VisibleForTesting
385 public static native void mkfifo(String path, int mode) throws IOException;
386
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100387 /********************************************************************
388 * *
389 * Linux extended file attributes *
390 * *
391 ********************************************************************/
392
393 /**
394 * Native wrapper around Linux getxattr(2) syscall.
395 *
396 * @param path the file whose extended attribute is to be returned.
397 * @param name the name of the extended attribute key.
398 * @return the value of the extended attribute associated with 'path', if
399 * any, or null if no such attribute is defined (ENODATA).
400 * @throws IOException if the call failed for any other reason.
401 */
402 public static native byte[] getxattr(String path, String name)
403 throws IOException;
404
405 /**
406 * Native wrapper around Linux lgetxattr(2) syscall. (Like getxattr, but
407 * does not follow symbolic links.)
408 *
409 * @param path the file whose extended attribute is to be returned.
410 * @param name the name of the extended attribute key.
411 * @return the value of the extended attribute associated with 'path', if
412 * any, or null if no such attribute is defined (ENODATA).
413 * @throws IOException if the call failed for any other reason.
414 */
415 public static native byte[] lgetxattr(String path, String name)
416 throws IOException;
417
418 /**
419 * Returns the MD5 digest of the specified file, following symbolic links.
420 *
421 * @param path the file whose MD5 digest is required.
422 * @return the MD5 digest, as a 16-byte array.
423 * @throws IOException if the call failed for any reason.
424 */
425 static native byte[] md5sumAsBytes(String path) throws IOException;
426
427 /**
428 * Returns the MD5 digest of the specified file, following symbolic links.
429 *
430 * @param path the file whose MD5 digest is required.
431 * @return the MD5 digest, as a {@link HashCode}
432 * @throws IOException if the call failed for any reason.
433 */
434 public static HashCode md5sum(String path) throws IOException {
435 return HashCode.fromBytes(md5sumAsBytes(path));
436 }
437
438 /**
439 * Removes entire directory tree. Doesn't follow symlinks.
440 *
441 * @param path the file or directory to remove.
442 * @throws IOException if the remove failed.
443 */
444 public static void rmTree(String path) throws IOException {
445 if (isDirectory(path)) {
446 String[] contents = readdir(path);
447 for (String entry : contents) {
448 rmTree(path + "/" + entry);
449 }
450 }
451 remove(path.toString());
452 }
453}