blob: 249fe92706bb7877958244694c2394a904c5e01d [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.
Ulf Adams8afbd3c2017-02-28 10:42:48 +000014package com.google.devtools.build.lib.unix;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010015
16import com.google.common.annotations.VisibleForTesting;
tomlua155b532017-11-08 20:12:47 +010017import com.google.common.base.Preconditions;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010018import com.google.common.collect.Lists;
19import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
20import com.google.devtools.build.lib.profiler.Profiler;
21import com.google.devtools.build.lib.profiler.ProfilerTask;
Lukacs Berki7ecb2ce2016-01-26 15:40:42 +000022import com.google.devtools.build.lib.unix.NativePosixFiles.Dirents;
23import com.google.devtools.build.lib.unix.NativePosixFiles.ReadTypes;
Ulf Adams8afbd3c2017-02-28 10:42:48 +000024import com.google.devtools.build.lib.vfs.AbstractFileSystemWithCustomStat;
ccalvarinbda12a12018-06-21 18:57:26 -070025import com.google.devtools.build.lib.vfs.DigestHashFunction;
Ulf Adams8afbd3c2017-02-28 10:42:48 +000026import com.google.devtools.build.lib.vfs.Dirent;
27import com.google.devtools.build.lib.vfs.FileStatus;
janakrc8735252021-12-13 12:35:06 -080028import com.google.devtools.build.lib.vfs.Path;
aehligc801c392017-12-19 07:12:25 -080029import com.google.devtools.build.lib.vfs.PathFragment;
John Millikineae93f62019-11-11 09:01:04 -080030import java.io.File;
31import java.io.FileInputStream;
Benjamin Petersonbe9fbec2019-03-27 05:25:18 -070032import java.io.FileNotFoundException;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import java.io.IOException;
John Millikineae93f62019-11-11 09:01:04 -080034import java.io.InputStream;
Benjamin Petersonbe9fbec2019-03-27 05:25:18 -070035import java.io.OutputStream;
John Millikineae93f62019-11-11 09:01:04 -080036import java.nio.charset.StandardCharsets;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010037import java.util.ArrayList;
38import java.util.Collection;
39import java.util.List;
Googler818c5c82022-07-07 04:08:17 -070040import javax.annotation.Nullable;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041
Googler818c5c82022-07-07 04:08:17 -070042/** This class implements the FileSystem interface using direct calls to the UNIX filesystem. */
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043@ThreadSafe
Nathan Harmata13a74c02015-11-18 18:38:14 +000044public class UnixFileSystem extends AbstractFileSystemWithCustomStat {
Ed Schouten05650ff2020-09-17 10:01:25 -070045 protected final String hashAttributeName;
46
47 public UnixFileSystem(DigestHashFunction hashFunction, String hashAttributeName) {
buchgr559a07d2017-11-30 11:09:35 -080048 super(hashFunction);
Ed Schouten05650ff2020-09-17 10:01:25 -070049 this.hashAttributeName = hashAttributeName;
buchgr559a07d2017-11-30 11:09:35 -080050 }
51
janakr1a074432022-03-07 14:54:37 -080052 public static Dirent.Type getDirentFromMode(int mode) {
53 if (com.google.devtools.build.lib.unix.FileStatus.isSpecialFile(mode)) {
54 return Dirent.Type.UNKNOWN;
55 } else if (com.google.devtools.build.lib.unix.FileStatus.isFile(mode)) {
56 return Dirent.Type.FILE;
57 } else if (com.google.devtools.build.lib.unix.FileStatus.isDirectory(mode)) {
58 return Dirent.Type.DIRECTORY;
59 } else if (com.google.devtools.build.lib.unix.FileStatus.isSymbolicLink(mode)) {
60 return Dirent.Type.SYMLINK;
61 } else {
62 return Dirent.Type.UNKNOWN;
63 }
64 }
65
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010066 /**
Googler818c5c82022-07-07 04:08:17 -070067 * Eager implementation of FileStatus for file systems that have an atomic stat(2) syscall. A
68 * proxy for {@link com.google.devtools.build.lib.unix.FileStatus}. Note that isFile and
69 * getLastModifiedTime have slightly different meanings between UNIX and VFS.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010070 */
71 @VisibleForTesting
72 protected static class UnixFileStatus implements FileStatus {
73
74 private final com.google.devtools.build.lib.unix.FileStatus status;
75
76 UnixFileStatus(com.google.devtools.build.lib.unix.FileStatus status) {
77 this.status = status;
78 }
79
80 @Override
Googler818c5c82022-07-07 04:08:17 -070081 public boolean isFile() {
82 return !isDirectory() && !isSymbolicLink();
83 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010084
85 @Override
Googler818c5c82022-07-07 04:08:17 -070086 public boolean isDirectory() {
87 return status.isDirectory();
88 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010089
90 @Override
Googler818c5c82022-07-07 04:08:17 -070091 public boolean isSymbolicLink() {
92 return status.isSymbolicLink();
93 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010094
95 @Override
Googler818c5c82022-07-07 04:08:17 -070096 public boolean isSpecialFile() {
97 return isFile() && !status.isRegularFile();
98 }
Nathan Harmatad8b6ff22015-10-20 21:54:34 +000099
100 @Override
Googler818c5c82022-07-07 04:08:17 -0700101 public long getSize() {
102 return status.getSize();
103 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100104
105 @Override
106 public long getLastModifiedTime() {
107 return (status.getLastModifiedTime() * 1000)
108 + (status.getFractionalLastModifiedTime() / 1000000);
109 }
110
111 @Override
112 public long getLastChangeTime() {
Googler818c5c82022-07-07 04:08:17 -0700113 return (status.getLastChangeTime() * 1000) + (status.getFractionalLastChangeTime() / 1000000);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100114 }
115
116 @Override
117 public long getNodeId() {
118 // Note that we may want to include more information in this id number going forward,
119 // especially the device number.
120 return status.getInodeNumber();
121 }
122
Fabian Meumertzheim763f1d92023-04-14 08:28:49 -0700123 @Override
124 public int getPermissions() {
Googler818c5c82022-07-07 04:08:17 -0700125 return status.getPermissions();
126 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100127
128 @Override
Googler818c5c82022-07-07 04:08:17 -0700129 public String toString() {
130 return status.toString();
131 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100132 }
133
134 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800135 protected Collection<String> getDirectoryEntries(PathFragment path) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100136 String name = path.getPathString();
137 String[] entries;
138 long startTime = Profiler.nanoTimeMaybe();
139 try {
Lukacs Berki7ecb2ce2016-01-26 15:40:42 +0000140 entries = NativePosixFiles.readdir(name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100141 } finally {
142 profiler.logSimpleTask(startTime, ProfilerTask.VFS_DIR, name);
143 }
tomlu0a82e702017-10-23 18:16:44 +0200144 Collection<String> result = new ArrayList<>(entries.length);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100145 for (String entry : entries) {
tomlu0a82e702017-10-23 18:16:44 +0200146 result.add(entry);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100147 }
148 return result;
149 }
150
151 @Override
Googler818c5c82022-07-07 04:08:17 -0700152 @Nullable
ajurkowski8883c612021-03-08 08:12:37 -0800153 protected PathFragment resolveOneLink(PathFragment path) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100154 // Beware, this seemingly simple code belies the complex specification of
155 // FileSystem.resolveOneLink().
Googler818c5c82022-07-07 04:08:17 -0700156 return stat(path, false).isSymbolicLink() ? readSymbolicLink(path) : null;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100157 }
158
159 /**
Ulf Adams8405d402016-08-10 14:28:53 +0000160 * Converts from {@link com.google.devtools.build.lib.unix.NativePosixFiles.Dirents.Type} to
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100161 * {@link com.google.devtools.build.lib.vfs.Dirent.Type}.
162 */
163 private static Dirent.Type convertToDirentType(Dirents.Type type) {
164 switch (type) {
165 case FILE:
166 return Dirent.Type.FILE;
167 case DIRECTORY:
168 return Dirent.Type.DIRECTORY;
169 case SYMLINK:
170 return Dirent.Type.SYMLINK;
171 case UNKNOWN:
172 return Dirent.Type.UNKNOWN;
173 default:
174 throw new IllegalArgumentException("Unknown type " + type);
175 }
176 }
177
178 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800179 protected Collection<Dirent> readdir(PathFragment path, boolean followSymlinks)
180 throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100181 String name = path.getPathString();
182 long startTime = Profiler.nanoTimeMaybe();
183 try {
Googler818c5c82022-07-07 04:08:17 -0700184 Dirents unixDirents =
185 NativePosixFiles.readdir(name, followSymlinks ? ReadTypes.FOLLOW : ReadTypes.NOFOLLOW);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100186 Preconditions.checkState(unixDirents.hasTypes());
187 List<Dirent> dirents = Lists.newArrayListWithCapacity(unixDirents.size());
188 for (int i = 0; i < unixDirents.size(); i++) {
Googler818c5c82022-07-07 04:08:17 -0700189 dirents.add(
190 new Dirent(unixDirents.getName(i), convertToDirentType(unixDirents.getType(i))));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100191 }
192 return dirents;
193 } finally {
194 profiler.logSimpleTask(startTime, ProfilerTask.VFS_DIR, name);
195 }
196 }
197
198 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800199 protected FileStatus stat(PathFragment path, boolean followSymlinks) throws IOException {
Eric Fellheimere9d50be2015-04-28 22:09:57 +0000200 return statInternal(path, followSymlinks);
201 }
202
203 @VisibleForTesting
ajurkowski8883c612021-03-08 08:12:37 -0800204 protected UnixFileStatus statInternal(PathFragment path, boolean followSymlinks)
205 throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100206 String name = path.getPathString();
207 long startTime = Profiler.nanoTimeMaybe();
208 try {
Googler818c5c82022-07-07 04:08:17 -0700209 return new UnixFileStatus(
210 followSymlinks ? NativePosixFiles.stat(name) : NativePosixFiles.lstat(name));
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100211 } finally {
212 profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, name);
213 }
214 }
215
216 // Like stat(), but returns null instead of throwing.
217 // This is a performance optimization in the case where clients
218 // catch and don't re-throw.
219 @Override
Googler818c5c82022-07-07 04:08:17 -0700220 @Nullable
ajurkowski8883c612021-03-08 08:12:37 -0800221 protected FileStatus statNullable(PathFragment path, boolean followSymlinks) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100222 String name = path.getPathString();
223 long startTime = Profiler.nanoTimeMaybe();
224 try {
Googler818c5c82022-07-07 04:08:17 -0700225 ErrnoFileStatus stat =
226 followSymlinks ? NativePosixFiles.errnoStat(name) : NativePosixFiles.errnoLstat(name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100227 return stat.hasError() ? null : new UnixFileStatus(stat);
228 } finally {
229 profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, name);
230 }
231 }
232
233 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800234 protected boolean exists(PathFragment path, boolean followSymlinks) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100235 return statNullable(path, followSymlinks) != null;
236 }
237
238 /**
ajurkowski8883c612021-03-08 08:12:37 -0800239 * Return true iff the {@code stat} of {@code path} resulted in an {@code ENOENT} or {@code
240 * ENOTDIR} error.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100241 */
242 @Override
Googler818c5c82022-07-07 04:08:17 -0700243 @Nullable
ajurkowski8883c612021-03-08 08:12:37 -0800244 protected FileStatus statIfFound(PathFragment path, boolean followSymlinks) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100245 String name = path.getPathString();
246 long startTime = Profiler.nanoTimeMaybe();
247 try {
Googler818c5c82022-07-07 04:08:17 -0700248 ErrnoFileStatus stat =
249 followSymlinks ? NativePosixFiles.errnoStat(name) : NativePosixFiles.errnoLstat(name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100250 if (!stat.hasError()) {
251 return new UnixFileStatus(stat);
252 }
253 int errno = stat.getErrno();
254 if (errno == ErrnoFileStatus.ENOENT || errno == ErrnoFileStatus.ENOTDIR) {
255 return null;
256 }
257 // This should not return -- we are calling stat here just to throw the proper exception.
258 // However, since there may be transient IO errors, we cannot guarantee that an exception will
259 // be thrown.
260 // TODO(bazel-team): Extract the exception-construction code and make it visible separately in
261 // FilesystemUtils to avoid having to do a duplicate stat call.
262 return stat(path, followSymlinks);
263 } finally {
264 profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, name);
265 }
266 }
267
268 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800269 protected boolean isReadable(PathFragment path) throws IOException {
Eric Fellheimere9d50be2015-04-28 22:09:57 +0000270 return (statInternal(path, true).getPermissions() & 0400) != 0;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100271 }
272
273 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800274 protected boolean isWritable(PathFragment path) throws IOException {
Eric Fellheimere9d50be2015-04-28 22:09:57 +0000275 return (statInternal(path, true).getPermissions() & 0200) != 0;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100276 }
277
278 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800279 protected boolean isExecutable(PathFragment path) throws IOException {
Eric Fellheimere9d50be2015-04-28 22:09:57 +0000280 return (statInternal(path, true).getPermissions() & 0100) != 0;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100281 }
282
283 /**
ajurkowski8883c612021-03-08 08:12:37 -0800284 * Adds or remove the bits specified in "permissionBits" to the permission mask of the file
285 * specified by {@code path}. If the argument {@code add} is true, the specified permissions are
286 * added, otherwise they are removed.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100287 *
288 * @throws IOException if there was an error writing the file's metadata
289 */
ajurkowski8883c612021-03-08 08:12:37 -0800290 private void modifyPermissionBits(PathFragment path, int permissionBits, boolean add)
291 throws IOException {
tomlu22c2f9a2018-01-09 13:52:13 -0800292 int oldMode = statInternal(path, true).getPermissions();
293 int newMode = add ? (oldMode | permissionBits) : (oldMode & ~permissionBits);
294 NativePosixFiles.chmod(path.toString(), newMode);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100295 }
296
297 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800298 protected void setReadable(PathFragment path, boolean readable) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100299 modifyPermissionBits(path, 0400, readable);
300 }
301
302 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800303 public void setWritable(PathFragment path, boolean writable) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100304 modifyPermissionBits(path, 0200, writable);
305 }
306
307 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800308 protected void setExecutable(PathFragment path, boolean executable) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100309 modifyPermissionBits(path, 0111, executable);
310 }
311
312 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800313 protected void chmod(PathFragment path, int mode) throws IOException {
tomlu22c2f9a2018-01-09 13:52:13 -0800314 NativePosixFiles.chmod(path.toString(), mode);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100315 }
316
317 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800318 public boolean supportsModifications(PathFragment path) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100319 return true;
320 }
321
322 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800323 public boolean supportsSymbolicLinksNatively(PathFragment path) {
Ulf Adams8405d402016-08-10 14:28:53 +0000324 return true;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100325 }
326
327 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800328 public boolean supportsHardLinksNatively(PathFragment path) {
Googlere1cd9502016-09-07 14:33:29 +0000329 return true;
330 }
331
332 @Override
Yun Peng352f7e72016-05-09 11:08:25 +0000333 public boolean isFilePathCaseSensitive() {
334 return true;
335 }
336
337 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800338 public boolean createDirectory(PathFragment path) throws IOException {
tomlu22c2f9a2018-01-09 13:52:13 -0800339 // Note: UNIX mkdir(2), FilesystemUtils.mkdir() and createDirectory all
340 // have different ways of representing failure!
Googler4da06422023-02-22 09:20:19 -0800341 if (NativePosixFiles.mkdir(path.toString(), 0755)) {
tomlu22c2f9a2018-01-09 13:52:13 -0800342 return true; // successfully created
343 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100344
tomlu22c2f9a2018-01-09 13:52:13 -0800345 // false => EEXIST: something is already in the way (file/dir/symlink)
346 if (isDirectory(path, false)) {
347 return false; // directory already existed
348 } else {
349 throw new IOException(path + " (File exists)");
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100350 }
351 }
352
353 @Override
ajurkowskicff2ea52021-06-30 18:44:17 -0700354 protected boolean createWritableDirectory(PathFragment path) throws IOException {
355 return NativePosixFiles.mkdirWritable(path.toString());
356 }
357
358 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800359 public void createDirectoryAndParents(PathFragment path) throws IOException {
Googler4da06422023-02-22 09:20:19 -0800360 NativePosixFiles.mkdirs(path.toString(), 0755);
tomludecca2b2017-12-21 08:59:51 -0800361 }
362
363 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800364 protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment)
aehligc801c392017-12-19 07:12:25 -0800365 throws IOException {
tomlua729b9b2018-02-08 15:32:00 -0800366 NativePosixFiles.symlink(targetFragment.getSafePathString(), linkPath.toString());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100367 }
368
369 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800370 protected PathFragment readSymbolicLink(PathFragment path) throws IOException {
Nathan Harmata215974e52015-09-16 21:31:49 +0000371 // Note that the default implementation of readSymbolicLinkUnchecked calls this method and thus
372 // is optimal since we only make one system call in here.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100373 String name = path.toString();
374 long startTime = Profiler.nanoTimeMaybe();
375 try {
aehligc801c392017-12-19 07:12:25 -0800376 return PathFragment.create(NativePosixFiles.readlink(name));
janakr5d955f52021-06-24 15:33:25 -0700377 } catch (InvalidArgumentIOException e) {
378 throw new NotASymlinkException(path, e);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100379 } finally {
Han-Wen Nienhuys14c542c2015-03-12 15:09:42 +0000380 profiler.logSimpleTask(startTime, ProfilerTask.VFS_READLINK, name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100381 }
382 }
383
384 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800385 public void renameTo(PathFragment sourcePath, PathFragment targetPath) throws IOException {
tomlu22c2f9a2018-01-09 13:52:13 -0800386 NativePosixFiles.rename(sourcePath.toString(), targetPath.toString());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100387 }
388
389 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800390 protected long getFileSize(PathFragment path, boolean followSymlinks) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100391 return stat(path, followSymlinks).getSize();
392 }
393
394 @Override
ajurkowskiea26e0a2021-03-22 16:04:20 -0700395 protected boolean delete(PathFragment path) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100396 String name = path.toString();
397 long startTime = Profiler.nanoTimeMaybe();
tomlu22c2f9a2018-01-09 13:52:13 -0800398 try {
399 return NativePosixFiles.remove(name);
400 } finally {
401 profiler.logSimpleTask(startTime, ProfilerTask.VFS_DELETE, name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100402 }
403 }
404
405 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800406 protected long getLastModifiedTime(PathFragment path, boolean followSymlinks) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100407 return stat(path, followSymlinks).getLastModifiedTime();
408 }
409
410 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800411 public void setLastModifiedTime(PathFragment path, long newTime) throws IOException {
janakrc8735252021-12-13 12:35:06 -0800412 if (newTime == Path.NOW_SENTINEL_TIME) {
tomlu22c2f9a2018-01-09 13:52:13 -0800413 NativePosixFiles.utime(path.toString(), true, 0);
414 } else {
415 // newTime > MAX_INT => -ve unixTime
416 int unixTime = (int) (newTime / 1000);
417 NativePosixFiles.utime(path.toString(), false, unixTime);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100418 }
419 }
420
421 @Override
Googler818c5c82022-07-07 04:08:17 -0700422 @Nullable
ajurkowski8883c612021-03-08 08:12:37 -0800423 public byte[] getxattr(PathFragment path, String name, boolean followSymlinks)
424 throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100425 String pathName = path.toString();
426 long startTime = Profiler.nanoTimeMaybe();
427 try {
nharmataca753c32018-09-13 10:19:35 -0700428 return followSymlinks
429 ? NativePosixFiles.getxattr(pathName, name)
430 : NativePosixFiles.lgetxattr(pathName, name);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100431 } catch (UnsupportedOperationException e) {
432 // getxattr() syscall is not supported by the underlying filesystem (it returned ENOTSUP).
433 // Per method contract, treat this as ENODATA.
434 return null;
435 } finally {
436 profiler.logSimpleTask(startTime, ProfilerTask.VFS_XATTR, pathName);
437 }
438 }
439
440 @Override
Googler818c5c82022-07-07 04:08:17 -0700441 @Nullable
ajurkowski8883c612021-03-08 08:12:37 -0800442 protected byte[] getFastDigest(PathFragment path) throws IOException {
nharmata4592a6c2020-09-25 11:00:06 -0700443 // Attempt to obtain the digest from an extended attribute attached to the file. This is much
444 // faster than reading and digesting the file's contents on the fly, especially for large files.
Ed Schouten05650ff2020-09-17 10:01:25 -0700445 return hashAttributeName.isEmpty() ? null : getxattr(path, hashAttributeName, true);
446 }
447
448 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800449 protected byte[] getDigest(PathFragment path) throws IOException {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100450 String name = path.toString();
451 long startTime = Profiler.nanoTimeMaybe();
452 try {
ccalvarindd9f60e2018-07-23 18:16:18 -0700453 return super.getDigest(path);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100454 } finally {
455 profiler.logSimpleTask(startTime, ProfilerTask.VFS_MD5, name);
456 }
457 }
Googlere1cd9502016-09-07 14:33:29 +0000458
459 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800460 protected void createFSDependentHardLink(PathFragment linkPath, PathFragment originalPath)
Googlere1cd9502016-09-07 14:33:29 +0000461 throws IOException {
462 NativePosixFiles.link(originalPath.toString(), linkPath.toString());
463 }
jmmvfac322b2019-03-21 07:53:51 -0700464
465 @Override
ajurkowskiea26e0a2021-03-22 16:04:20 -0700466 protected void deleteTreesBelow(PathFragment dir) throws IOException {
ajurkowski8883c612021-03-08 08:12:37 -0800467 if (isDirectory(dir, /*followSymlinks=*/ false)) {
jmmvfac322b2019-03-21 07:53:51 -0700468 long startTime = Profiler.nanoTimeMaybe();
469 try {
470 NativePosixFiles.deleteTreesBelow(dir.toString());
471 } finally {
472 profiler.logSimpleTask(startTime, ProfilerTask.VFS_DELETE, dir.toString());
473 }
474 }
475 }
Benjamin Petersonbe9fbec2019-03-27 05:25:18 -0700476
ajurkowski8883c612021-03-08 08:12:37 -0800477 private static File createJavaIoFile(PathFragment path) {
John Millikineae93f62019-11-11 09:01:04 -0800478 final String pathStr = path.getPathString();
479 if (pathStr.chars().allMatch(c -> c < 128)) {
480 return new File(pathStr);
481 }
482
483 // Paths returned from NativePosixFiles are Strings containing raw bytes from the filesystem.
484 // Java's IO subsystem expects paths to be encoded per the `sun.jnu.encoding` setting. This
485 // is difficult to handle generically, but we can special-case the most common case (UTF-8).
486 if ("UTF-8".equals(System.getProperty("sun.jnu.encoding"))) {
487 final byte[] pathBytes = pathStr.getBytes(StandardCharsets.ISO_8859_1);
488 return new File(new String(pathBytes, StandardCharsets.UTF_8));
489 }
490
491 // This will probably fail but not much that can be done without migrating to `java.nio.Files`.
492 return new File(pathStr);
493 }
494
495 @Override
ajurkowski8883c612021-03-08 08:12:37 -0800496 protected InputStream createFileInputStream(PathFragment path) throws IOException {
John Millikineae93f62019-11-11 09:01:04 -0800497 return new FileInputStream(createJavaIoFile(path));
498 }
499
ajurkowski8883c612021-03-08 08:12:37 -0800500 protected OutputStream createFileOutputStream(PathFragment path, boolean append)
Benjamin Petersonbe9fbec2019-03-27 05:25:18 -0700501 throws FileNotFoundException {
twerth5a132cc2021-07-08 03:45:27 -0700502 return createFileOutputStream(path, append, /* internal= */ false);
503 }
504
505 @Override
506 protected OutputStream createFileOutputStream(PathFragment path, boolean append, boolean internal)
507 throws FileNotFoundException {
Benjamin Petersonbe9fbec2019-03-27 05:25:18 -0700508 final String name = path.toString();
twerth5a132cc2021-07-08 03:45:27 -0700509 if (!internal
510 && profiler.isActive()
Benjamin Petersonbe9fbec2019-03-27 05:25:18 -0700511 && (profiler.isProfiling(ProfilerTask.VFS_WRITE)
512 || profiler.isProfiling(ProfilerTask.VFS_OPEN))) {
513 long startTime = Profiler.nanoTimeMaybe();
514 try {
515 return new ProfiledNativeFileOutputStream(NativePosixFiles.openWrite(name, append), name);
516 } finally {
517 profiler.logSimpleTask(startTime, ProfilerTask.VFS_OPEN, name);
518 }
519 } else {
520 return new NativeFileOutputStream(NativePosixFiles.openWrite(name, append));
521 }
522 }
523
524 private static class NativeFileOutputStream extends OutputStream {
525 private final int fd;
526 private boolean closed = false;
527
528 NativeFileOutputStream(int fd) {
529 this.fd = fd;
530 }
531
532 @Override
533 protected void finalize() throws Throwable {
534 close();
535 super.finalize();
536 }
537
538 @Override
539 public synchronized void close() throws IOException {
540 if (!closed) {
Benjamin Petersonbe9fbec2019-03-27 05:25:18 -0700541 NativePosixFiles.close(fd, this);
jmmveebad982019-10-23 14:15:25 -0700542 closed = true;
Benjamin Petersonbe9fbec2019-03-27 05:25:18 -0700543 }
544 super.close();
545 }
546
547 @Override
548 public void write(int b) throws IOException {
549 write(new byte[] {(byte) (b & 0xFF)});
550 }
551
552 @Override
553 public void write(byte[] b) throws IOException {
554 write(b, 0, b.length);
555 }
556
557 @Override
arostovtsev0212f182020-04-29 14:07:26 -0700558 @SuppressWarnings(
559 "UnsafeFinalization") // Finalizer invokes close; close and write are synchronized.
jmmveebad982019-10-23 14:15:25 -0700560 public synchronized void write(byte[] b, int off, int len) throws IOException {
Benjamin Petersonbe9fbec2019-03-27 05:25:18 -0700561 if (closed) {
562 throw new IOException("attempt to write to a closed Outputstream backed by a native file");
563 }
564 NativePosixFiles.write(fd, b, off, len);
565 }
566 }
567
568 private static final class ProfiledNativeFileOutputStream extends NativeFileOutputStream {
569 private final String name;
570
571 public ProfiledNativeFileOutputStream(int fd, String name) throws FileNotFoundException {
572 super(fd);
573 this.name = name;
574 }
575
576 @Override
577 public synchronized void write(byte[] b, int off, int len) throws IOException {
578 long startTime = Profiler.nanoTimeMaybe();
579 try {
580 super.write(b, off, len);
581 } finally {
582 profiler.logSimpleTask(startTime, ProfilerTask.VFS_WRITE, name);
583 }
584 }
585 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100586}