Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | // Copyright 2014 The Bazel Authors. All rights reserved. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
Ulf Adams | 8afbd3c | 2017-02-28 10:42:48 +0000 | [diff] [blame] | 14 | package com.google.devtools.build.lib.unix; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 15 | |
| 16 | import com.google.common.annotations.VisibleForTesting; |
tomlu | a155b53 | 2017-11-08 20:12:47 +0100 | [diff] [blame] | 17 | import com.google.common.base.Preconditions; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 18 | import com.google.common.collect.Lists; |
| 19 | import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; |
| 20 | import com.google.devtools.build.lib.profiler.Profiler; |
| 21 | import com.google.devtools.build.lib.profiler.ProfilerTask; |
Lukacs Berki | 7ecb2ce | 2016-01-26 15:40:42 +0000 | [diff] [blame] | 22 | import com.google.devtools.build.lib.unix.NativePosixFiles.Dirents; |
| 23 | import com.google.devtools.build.lib.unix.NativePosixFiles.ReadTypes; |
Ulf Adams | 8afbd3c | 2017-02-28 10:42:48 +0000 | [diff] [blame] | 24 | import com.google.devtools.build.lib.vfs.AbstractFileSystemWithCustomStat; |
ccalvarin | bda12a1 | 2018-06-21 18:57:26 -0700 | [diff] [blame] | 25 | import com.google.devtools.build.lib.vfs.DigestHashFunction; |
Ulf Adams | 8afbd3c | 2017-02-28 10:42:48 +0000 | [diff] [blame] | 26 | import com.google.devtools.build.lib.vfs.Dirent; |
| 27 | import com.google.devtools.build.lib.vfs.FileStatus; |
janakr | c873525 | 2021-12-13 12:35:06 -0800 | [diff] [blame] | 28 | import com.google.devtools.build.lib.vfs.Path; |
aehlig | c801c39 | 2017-12-19 07:12:25 -0800 | [diff] [blame] | 29 | import com.google.devtools.build.lib.vfs.PathFragment; |
John Millikin | eae93f6 | 2019-11-11 09:01:04 -0800 | [diff] [blame] | 30 | import java.io.File; |
| 31 | import java.io.FileInputStream; |
Benjamin Peterson | be9fbec | 2019-03-27 05:25:18 -0700 | [diff] [blame] | 32 | import java.io.FileNotFoundException; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 33 | import java.io.IOException; |
John Millikin | eae93f6 | 2019-11-11 09:01:04 -0800 | [diff] [blame] | 34 | import java.io.InputStream; |
Benjamin Peterson | be9fbec | 2019-03-27 05:25:18 -0700 | [diff] [blame] | 35 | import java.io.OutputStream; |
John Millikin | eae93f6 | 2019-11-11 09:01:04 -0800 | [diff] [blame] | 36 | import java.nio.charset.StandardCharsets; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 37 | import java.util.ArrayList; |
| 38 | import java.util.Collection; |
| 39 | import java.util.List; |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 40 | import javax.annotation.Nullable; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 41 | |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 42 | /** This class implements the FileSystem interface using direct calls to the UNIX filesystem. */ |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 43 | @ThreadSafe |
Nathan Harmata | 13a74c0 | 2015-11-18 18:38:14 +0000 | [diff] [blame] | 44 | public class UnixFileSystem extends AbstractFileSystemWithCustomStat { |
Ed Schouten | 05650ff | 2020-09-17 10:01:25 -0700 | [diff] [blame] | 45 | protected final String hashAttributeName; |
| 46 | |
| 47 | public UnixFileSystem(DigestHashFunction hashFunction, String hashAttributeName) { |
buchgr | 559a07d | 2017-11-30 11:09:35 -0800 | [diff] [blame] | 48 | super(hashFunction); |
Ed Schouten | 05650ff | 2020-09-17 10:01:25 -0700 | [diff] [blame] | 49 | this.hashAttributeName = hashAttributeName; |
buchgr | 559a07d | 2017-11-30 11:09:35 -0800 | [diff] [blame] | 50 | } |
| 51 | |
janakr | 1a07443 | 2022-03-07 14:54:37 -0800 | [diff] [blame] | 52 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 66 | /** |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 67 | * 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 70 | */ |
| 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 |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 81 | public boolean isFile() { |
| 82 | return !isDirectory() && !isSymbolicLink(); |
| 83 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 84 | |
| 85 | @Override |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 86 | public boolean isDirectory() { |
| 87 | return status.isDirectory(); |
| 88 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 89 | |
| 90 | @Override |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 91 | public boolean isSymbolicLink() { |
| 92 | return status.isSymbolicLink(); |
| 93 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 94 | |
| 95 | @Override |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 96 | public boolean isSpecialFile() { |
| 97 | return isFile() && !status.isRegularFile(); |
| 98 | } |
Nathan Harmata | d8b6ff2 | 2015-10-20 21:54:34 +0000 | [diff] [blame] | 99 | |
| 100 | @Override |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 101 | public long getSize() { |
| 102 | return status.getSize(); |
| 103 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 104 | |
| 105 | @Override |
| 106 | public long getLastModifiedTime() { |
| 107 | return (status.getLastModifiedTime() * 1000) |
| 108 | + (status.getFractionalLastModifiedTime() / 1000000); |
| 109 | } |
| 110 | |
| 111 | @Override |
| 112 | public long getLastChangeTime() { |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 113 | return (status.getLastChangeTime() * 1000) + (status.getFractionalLastChangeTime() / 1000000); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 114 | } |
| 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 Meumertzheim | 763f1d9 | 2023-04-14 08:28:49 -0700 | [diff] [blame] | 123 | @Override |
| 124 | public int getPermissions() { |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 125 | return status.getPermissions(); |
| 126 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 127 | |
| 128 | @Override |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 129 | public String toString() { |
| 130 | return status.toString(); |
| 131 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 132 | } |
| 133 | |
| 134 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 135 | protected Collection<String> getDirectoryEntries(PathFragment path) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 136 | String name = path.getPathString(); |
| 137 | String[] entries; |
| 138 | long startTime = Profiler.nanoTimeMaybe(); |
| 139 | try { |
Lukacs Berki | 7ecb2ce | 2016-01-26 15:40:42 +0000 | [diff] [blame] | 140 | entries = NativePosixFiles.readdir(name); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 141 | } finally { |
| 142 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_DIR, name); |
| 143 | } |
tomlu | 0a82e70 | 2017-10-23 18:16:44 +0200 | [diff] [blame] | 144 | Collection<String> result = new ArrayList<>(entries.length); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 145 | for (String entry : entries) { |
tomlu | 0a82e70 | 2017-10-23 18:16:44 +0200 | [diff] [blame] | 146 | result.add(entry); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 147 | } |
| 148 | return result; |
| 149 | } |
| 150 | |
| 151 | @Override |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 152 | @Nullable |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 153 | protected PathFragment resolveOneLink(PathFragment path) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 154 | // Beware, this seemingly simple code belies the complex specification of |
| 155 | // FileSystem.resolveOneLink(). |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 156 | return stat(path, false).isSymbolicLink() ? readSymbolicLink(path) : null; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | /** |
Ulf Adams | 8405d40 | 2016-08-10 14:28:53 +0000 | [diff] [blame] | 160 | * Converts from {@link com.google.devtools.build.lib.unix.NativePosixFiles.Dirents.Type} to |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 161 | * {@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 |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 179 | protected Collection<Dirent> readdir(PathFragment path, boolean followSymlinks) |
| 180 | throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 181 | String name = path.getPathString(); |
| 182 | long startTime = Profiler.nanoTimeMaybe(); |
| 183 | try { |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 184 | Dirents unixDirents = |
| 185 | NativePosixFiles.readdir(name, followSymlinks ? ReadTypes.FOLLOW : ReadTypes.NOFOLLOW); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 186 | Preconditions.checkState(unixDirents.hasTypes()); |
| 187 | List<Dirent> dirents = Lists.newArrayListWithCapacity(unixDirents.size()); |
| 188 | for (int i = 0; i < unixDirents.size(); i++) { |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 189 | dirents.add( |
| 190 | new Dirent(unixDirents.getName(i), convertToDirentType(unixDirents.getType(i)))); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 191 | } |
| 192 | return dirents; |
| 193 | } finally { |
| 194 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_DIR, name); |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 199 | protected FileStatus stat(PathFragment path, boolean followSymlinks) throws IOException { |
Eric Fellheimer | e9d50be | 2015-04-28 22:09:57 +0000 | [diff] [blame] | 200 | return statInternal(path, followSymlinks); |
| 201 | } |
| 202 | |
| 203 | @VisibleForTesting |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 204 | protected UnixFileStatus statInternal(PathFragment path, boolean followSymlinks) |
| 205 | throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 206 | String name = path.getPathString(); |
| 207 | long startTime = Profiler.nanoTimeMaybe(); |
| 208 | try { |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 209 | return new UnixFileStatus( |
| 210 | followSymlinks ? NativePosixFiles.stat(name) : NativePosixFiles.lstat(name)); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 211 | } 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 |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 220 | @Nullable |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 221 | protected FileStatus statNullable(PathFragment path, boolean followSymlinks) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 222 | String name = path.getPathString(); |
| 223 | long startTime = Profiler.nanoTimeMaybe(); |
| 224 | try { |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 225 | ErrnoFileStatus stat = |
| 226 | followSymlinks ? NativePosixFiles.errnoStat(name) : NativePosixFiles.errnoLstat(name); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 227 | return stat.hasError() ? null : new UnixFileStatus(stat); |
| 228 | } finally { |
| 229 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_STAT, name); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 234 | protected boolean exists(PathFragment path, boolean followSymlinks) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 235 | return statNullable(path, followSymlinks) != null; |
| 236 | } |
| 237 | |
| 238 | /** |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 239 | * Return true iff the {@code stat} of {@code path} resulted in an {@code ENOENT} or {@code |
| 240 | * ENOTDIR} error. |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 241 | */ |
| 242 | @Override |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 243 | @Nullable |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 244 | protected FileStatus statIfFound(PathFragment path, boolean followSymlinks) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 245 | String name = path.getPathString(); |
| 246 | long startTime = Profiler.nanoTimeMaybe(); |
| 247 | try { |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 248 | ErrnoFileStatus stat = |
| 249 | followSymlinks ? NativePosixFiles.errnoStat(name) : NativePosixFiles.errnoLstat(name); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 250 | 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 |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 269 | protected boolean isReadable(PathFragment path) throws IOException { |
Eric Fellheimer | e9d50be | 2015-04-28 22:09:57 +0000 | [diff] [blame] | 270 | return (statInternal(path, true).getPermissions() & 0400) != 0; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 271 | } |
| 272 | |
| 273 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 274 | protected boolean isWritable(PathFragment path) throws IOException { |
Eric Fellheimer | e9d50be | 2015-04-28 22:09:57 +0000 | [diff] [blame] | 275 | return (statInternal(path, true).getPermissions() & 0200) != 0; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 276 | } |
| 277 | |
| 278 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 279 | protected boolean isExecutable(PathFragment path) throws IOException { |
Eric Fellheimer | e9d50be | 2015-04-28 22:09:57 +0000 | [diff] [blame] | 280 | return (statInternal(path, true).getPermissions() & 0100) != 0; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 281 | } |
| 282 | |
| 283 | /** |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 284 | * 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 287 | * |
| 288 | * @throws IOException if there was an error writing the file's metadata |
| 289 | */ |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 290 | private void modifyPermissionBits(PathFragment path, int permissionBits, boolean add) |
| 291 | throws IOException { |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 292 | int oldMode = statInternal(path, true).getPermissions(); |
| 293 | int newMode = add ? (oldMode | permissionBits) : (oldMode & ~permissionBits); |
| 294 | NativePosixFiles.chmod(path.toString(), newMode); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 295 | } |
| 296 | |
| 297 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 298 | protected void setReadable(PathFragment path, boolean readable) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 299 | modifyPermissionBits(path, 0400, readable); |
| 300 | } |
| 301 | |
| 302 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 303 | public void setWritable(PathFragment path, boolean writable) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 304 | modifyPermissionBits(path, 0200, writable); |
| 305 | } |
| 306 | |
| 307 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 308 | protected void setExecutable(PathFragment path, boolean executable) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 309 | modifyPermissionBits(path, 0111, executable); |
| 310 | } |
| 311 | |
| 312 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 313 | protected void chmod(PathFragment path, int mode) throws IOException { |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 314 | NativePosixFiles.chmod(path.toString(), mode); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 315 | } |
| 316 | |
| 317 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 318 | public boolean supportsModifications(PathFragment path) { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 319 | return true; |
| 320 | } |
| 321 | |
| 322 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 323 | public boolean supportsSymbolicLinksNatively(PathFragment path) { |
Ulf Adams | 8405d40 | 2016-08-10 14:28:53 +0000 | [diff] [blame] | 324 | return true; |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 325 | } |
| 326 | |
| 327 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 328 | public boolean supportsHardLinksNatively(PathFragment path) { |
Googler | e1cd950 | 2016-09-07 14:33:29 +0000 | [diff] [blame] | 329 | return true; |
| 330 | } |
| 331 | |
| 332 | @Override |
Yun Peng | 352f7e7 | 2016-05-09 11:08:25 +0000 | [diff] [blame] | 333 | public boolean isFilePathCaseSensitive() { |
| 334 | return true; |
| 335 | } |
| 336 | |
| 337 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 338 | public boolean createDirectory(PathFragment path) throws IOException { |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 339 | // Note: UNIX mkdir(2), FilesystemUtils.mkdir() and createDirectory all |
| 340 | // have different ways of representing failure! |
Googler | 4da0642 | 2023-02-22 09:20:19 -0800 | [diff] [blame] | 341 | if (NativePosixFiles.mkdir(path.toString(), 0755)) { |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 342 | return true; // successfully created |
| 343 | } |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 344 | |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 345 | // 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 350 | } |
| 351 | } |
| 352 | |
| 353 | @Override |
ajurkowski | cff2ea5 | 2021-06-30 18:44:17 -0700 | [diff] [blame] | 354 | protected boolean createWritableDirectory(PathFragment path) throws IOException { |
| 355 | return NativePosixFiles.mkdirWritable(path.toString()); |
| 356 | } |
| 357 | |
| 358 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 359 | public void createDirectoryAndParents(PathFragment path) throws IOException { |
Googler | 4da0642 | 2023-02-22 09:20:19 -0800 | [diff] [blame] | 360 | NativePosixFiles.mkdirs(path.toString(), 0755); |
tomlu | decca2b | 2017-12-21 08:59:51 -0800 | [diff] [blame] | 361 | } |
| 362 | |
| 363 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 364 | protected void createSymbolicLink(PathFragment linkPath, PathFragment targetFragment) |
aehlig | c801c39 | 2017-12-19 07:12:25 -0800 | [diff] [blame] | 365 | throws IOException { |
tomlu | a729b9b | 2018-02-08 15:32:00 -0800 | [diff] [blame] | 366 | NativePosixFiles.symlink(targetFragment.getSafePathString(), linkPath.toString()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 367 | } |
| 368 | |
| 369 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 370 | protected PathFragment readSymbolicLink(PathFragment path) throws IOException { |
Nathan Harmata | 215974e5 | 2015-09-16 21:31:49 +0000 | [diff] [blame] | 371 | // 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 373 | String name = path.toString(); |
| 374 | long startTime = Profiler.nanoTimeMaybe(); |
| 375 | try { |
aehlig | c801c39 | 2017-12-19 07:12:25 -0800 | [diff] [blame] | 376 | return PathFragment.create(NativePosixFiles.readlink(name)); |
janakr | 5d955f5 | 2021-06-24 15:33:25 -0700 | [diff] [blame] | 377 | } catch (InvalidArgumentIOException e) { |
| 378 | throw new NotASymlinkException(path, e); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 379 | } finally { |
Han-Wen Nienhuys | 14c542c | 2015-03-12 15:09:42 +0000 | [diff] [blame] | 380 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_READLINK, name); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 381 | } |
| 382 | } |
| 383 | |
| 384 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 385 | public void renameTo(PathFragment sourcePath, PathFragment targetPath) throws IOException { |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 386 | NativePosixFiles.rename(sourcePath.toString(), targetPath.toString()); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 387 | } |
| 388 | |
| 389 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 390 | protected long getFileSize(PathFragment path, boolean followSymlinks) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 391 | return stat(path, followSymlinks).getSize(); |
| 392 | } |
| 393 | |
| 394 | @Override |
ajurkowski | ea26e0a | 2021-03-22 16:04:20 -0700 | [diff] [blame] | 395 | protected boolean delete(PathFragment path) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 396 | String name = path.toString(); |
| 397 | long startTime = Profiler.nanoTimeMaybe(); |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 398 | try { |
| 399 | return NativePosixFiles.remove(name); |
| 400 | } finally { |
| 401 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_DELETE, name); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 402 | } |
| 403 | } |
| 404 | |
| 405 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 406 | protected long getLastModifiedTime(PathFragment path, boolean followSymlinks) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 407 | return stat(path, followSymlinks).getLastModifiedTime(); |
| 408 | } |
| 409 | |
| 410 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 411 | public void setLastModifiedTime(PathFragment path, long newTime) throws IOException { |
janakr | c873525 | 2021-12-13 12:35:06 -0800 | [diff] [blame] | 412 | if (newTime == Path.NOW_SENTINEL_TIME) { |
tomlu | 22c2f9a | 2018-01-09 13:52:13 -0800 | [diff] [blame] | 413 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 418 | } |
| 419 | } |
| 420 | |
| 421 | @Override |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 422 | @Nullable |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 423 | public byte[] getxattr(PathFragment path, String name, boolean followSymlinks) |
| 424 | throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 425 | String pathName = path.toString(); |
| 426 | long startTime = Profiler.nanoTimeMaybe(); |
| 427 | try { |
nharmata | ca753c3 | 2018-09-13 10:19:35 -0700 | [diff] [blame] | 428 | return followSymlinks |
| 429 | ? NativePosixFiles.getxattr(pathName, name) |
| 430 | : NativePosixFiles.lgetxattr(pathName, name); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 431 | } 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 |
Googler | 818c5c8 | 2022-07-07 04:08:17 -0700 | [diff] [blame] | 441 | @Nullable |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 442 | protected byte[] getFastDigest(PathFragment path) throws IOException { |
nharmata | 4592a6c | 2020-09-25 11:00:06 -0700 | [diff] [blame] | 443 | // 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 Schouten | 05650ff | 2020-09-17 10:01:25 -0700 | [diff] [blame] | 445 | return hashAttributeName.isEmpty() ? null : getxattr(path, hashAttributeName, true); |
| 446 | } |
| 447 | |
| 448 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 449 | protected byte[] getDigest(PathFragment path) throws IOException { |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 450 | String name = path.toString(); |
| 451 | long startTime = Profiler.nanoTimeMaybe(); |
| 452 | try { |
ccalvarin | dd9f60e | 2018-07-23 18:16:18 -0700 | [diff] [blame] | 453 | return super.getDigest(path); |
Han-Wen Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 454 | } finally { |
| 455 | profiler.logSimpleTask(startTime, ProfilerTask.VFS_MD5, name); |
| 456 | } |
| 457 | } |
Googler | e1cd950 | 2016-09-07 14:33:29 +0000 | [diff] [blame] | 458 | |
| 459 | @Override |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 460 | protected void createFSDependentHardLink(PathFragment linkPath, PathFragment originalPath) |
Googler | e1cd950 | 2016-09-07 14:33:29 +0000 | [diff] [blame] | 461 | throws IOException { |
| 462 | NativePosixFiles.link(originalPath.toString(), linkPath.toString()); |
| 463 | } |
jmmv | fac322b | 2019-03-21 07:53:51 -0700 | [diff] [blame] | 464 | |
| 465 | @Override |
ajurkowski | ea26e0a | 2021-03-22 16:04:20 -0700 | [diff] [blame] | 466 | protected void deleteTreesBelow(PathFragment dir) throws IOException { |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 467 | if (isDirectory(dir, /*followSymlinks=*/ false)) { |
jmmv | fac322b | 2019-03-21 07:53:51 -0700 | [diff] [blame] | 468 | 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 Peterson | be9fbec | 2019-03-27 05:25:18 -0700 | [diff] [blame] | 476 | |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 477 | private static File createJavaIoFile(PathFragment path) { |
John Millikin | eae93f6 | 2019-11-11 09:01:04 -0800 | [diff] [blame] | 478 | 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 |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 496 | protected InputStream createFileInputStream(PathFragment path) throws IOException { |
John Millikin | eae93f6 | 2019-11-11 09:01:04 -0800 | [diff] [blame] | 497 | return new FileInputStream(createJavaIoFile(path)); |
| 498 | } |
| 499 | |
ajurkowski | 8883c61 | 2021-03-08 08:12:37 -0800 | [diff] [blame] | 500 | protected OutputStream createFileOutputStream(PathFragment path, boolean append) |
Benjamin Peterson | be9fbec | 2019-03-27 05:25:18 -0700 | [diff] [blame] | 501 | throws FileNotFoundException { |
twerth | 5a132cc | 2021-07-08 03:45:27 -0700 | [diff] [blame] | 502 | return createFileOutputStream(path, append, /* internal= */ false); |
| 503 | } |
| 504 | |
| 505 | @Override |
| 506 | protected OutputStream createFileOutputStream(PathFragment path, boolean append, boolean internal) |
| 507 | throws FileNotFoundException { |
Benjamin Peterson | be9fbec | 2019-03-27 05:25:18 -0700 | [diff] [blame] | 508 | final String name = path.toString(); |
twerth | 5a132cc | 2021-07-08 03:45:27 -0700 | [diff] [blame] | 509 | if (!internal |
| 510 | && profiler.isActive() |
Benjamin Peterson | be9fbec | 2019-03-27 05:25:18 -0700 | [diff] [blame] | 511 | && (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 Peterson | be9fbec | 2019-03-27 05:25:18 -0700 | [diff] [blame] | 541 | NativePosixFiles.close(fd, this); |
jmmv | eebad98 | 2019-10-23 14:15:25 -0700 | [diff] [blame] | 542 | closed = true; |
Benjamin Peterson | be9fbec | 2019-03-27 05:25:18 -0700 | [diff] [blame] | 543 | } |
| 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 |
arostovtsev | 0212f18 | 2020-04-29 14:07:26 -0700 | [diff] [blame] | 558 | @SuppressWarnings( |
| 559 | "UnsafeFinalization") // Finalizer invokes close; close and write are synchronized. |
jmmv | eebad98 | 2019-10-23 14:15:25 -0700 | [diff] [blame] | 560 | public synchronized void write(byte[] b, int off, int len) throws IOException { |
Benjamin Peterson | be9fbec | 2019-03-27 05:25:18 -0700 | [diff] [blame] | 561 | 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 Nienhuys | d08b27f | 2015-02-25 16:45:20 +0100 | [diff] [blame] | 586 | } |