View Javadoc
1   package org.codehaus.plexus.components.io.attributes;
2   
3   /*
4    * Copyright 2007 The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import javax.annotation.Nonnull;
20  import javax.annotation.Nullable;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.nio.file.FileSystem;
25  import java.nio.file.Files;
26  import java.nio.file.LinkOption;
27  import java.nio.file.Path;
28  import java.nio.file.attribute.FileTime;
29  import java.nio.file.attribute.PosixFilePermission;
30  import java.security.Principal;
31  import java.util.Collections;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.WeakHashMap;
35  import java.util.concurrent.ConcurrentHashMap;
36  
37  /*
38   * File attributes
39   * Immutable
40   */
41  public class FileAttributes implements PlexusIoResourceAttributes {
42      public static final LinkOption[] FOLLOW_LINK_OPTIONS = new LinkOption[] {};
43  
44      public static final LinkOption[] NOFOLLOW_LINK_OPTIONS = new LinkOption[] {LinkOption.NOFOLLOW_LINKS};
45  
46      @Nullable
47      private final Integer groupId;
48  
49      @Nullable
50      private final String groupName;
51  
52      @Nullable
53      private final Integer userId;
54  
55      private final String userName;
56  
57      private final boolean symbolicLink;
58  
59      private final boolean regularFile;
60  
61      private final boolean directory;
62  
63      private final boolean other;
64  
65      private final int octalMode;
66  
67      private final Set<PosixFilePermission> permissions;
68  
69      private final long size;
70  
71      private final FileTime lastModifiedTime;
72  
73      private static final Map<FileSystem, Map<Integer, String>> UIDS_CACHE =
74              Collections.synchronizedMap(new WeakHashMap<>());
75  
76      private static final Map<FileSystem, Map<Integer, String>> GIDS_CACHE =
77              Collections.synchronizedMap(new WeakHashMap<>());
78  
79      /**
80       * @deprecated use {@link #FileAttributes(File)} and remove the unused userCache and groupCache parameters
81       */
82      @Deprecated
83      public FileAttributes(
84              @Nonnull File file, @Nonnull Map<Integer, String> userCache, @Nonnull Map<Integer, String> groupCache)
85              throws IOException {
86          this(file);
87      }
88  
89      public FileAttributes(@Nonnull File file) throws IOException {
90          this(file.toPath(), false);
91      }
92  
93      public FileAttributes(@Nonnull File file, boolean followLinks) throws IOException {
94          this(file.toPath(), followLinks);
95      }
96  
97      private static Map<Integer, String> getUserCache(FileSystem fs) {
98          return UIDS_CACHE.computeIfAbsent(fs, f -> new ConcurrentHashMap<>());
99      }
100 
101     private static Map<Integer, String> getGroupCache(FileSystem fs) {
102         return GIDS_CACHE.computeIfAbsent(fs, f -> new ConcurrentHashMap<>());
103     }
104 
105     public FileAttributes(@Nonnull Path path, boolean followLinks) throws IOException {
106         LinkOption[] options = followLinks ? FOLLOW_LINK_OPTIONS : NOFOLLOW_LINK_OPTIONS;
107         Set<String> views = path.getFileSystem().supportedFileAttributeViews();
108         String names;
109         if (views.contains("unix")) {
110             names =
111                     "unix:gid,uid,isSymbolicLink,isRegularFile,isDirectory,isOther,mode,permissions,size,lastModifiedTime";
112         } else if (views.contains("posix")) {
113             names = "posix:*";
114         } else {
115             names = "basic:*";
116         }
117         Map<String, Object> attrs = Files.readAttributes(path, names, options);
118         this.groupId = (Integer) attrs.get("gid");
119         if (attrs.containsKey("group")) {
120             this.groupName = ((Principal) attrs.get("group")).getName();
121         } else if (this.groupId != null) {
122             Map<Integer, String> cache = getGroupCache(path.getFileSystem());
123             String name = cache.get(this.groupId);
124             if (name == null) {
125                 name = getPrincipalName(path, "unix:group");
126                 cache.put(this.groupId, name);
127             }
128             this.groupName = name;
129         } else {
130             this.groupName = null;
131         }
132         this.userId = (Integer) attrs.get("uid");
133         if (attrs.containsKey("owner")) {
134             this.userName = ((Principal) attrs.get("owner")).getName();
135         } else if (this.userId != null) {
136             Map<Integer, String> cache = getUserCache(path.getFileSystem());
137             String name = cache.get(this.userId);
138             if (name == null) {
139                 name = getPrincipalName(path, "unix:owner");
140                 cache.put(this.userId, name);
141             }
142             this.userName = name;
143         } else if (views.contains("owner")) {
144             this.userName = getPrincipalName(path, "owner:owner");
145         } else {
146             this.userName = null;
147         }
148         this.symbolicLink = (Boolean) attrs.get("isSymbolicLink");
149         this.regularFile = (Boolean) attrs.get("isRegularFile");
150         this.directory = (Boolean) attrs.get("isDirectory");
151         this.other = (Boolean) attrs.get("isOther");
152         this.octalMode = attrs.containsKey("mode")
153                 ? (Integer) attrs.get("mode") & 0xfff
154                 : PlexusIoResourceAttributes.UNKNOWN_OCTAL_MODE;
155         //noinspection unchecked
156         this.permissions = attrs.containsKey("permissions")
157                 ? (Set<PosixFilePermission>) attrs.get("permissions")
158                 : Collections.emptySet();
159         this.size = (Long) attrs.get("size");
160         this.lastModifiedTime = (FileTime) attrs.get("lastModifiedTime");
161     }
162 
163     @Nullable
164     private static String getPrincipalName(Path path, String attribute) {
165         try {
166             Object owner = Files.getAttribute(path, attribute, LinkOption.NOFOLLOW_LINKS);
167             return ((Principal) owner).getName();
168         } catch (IOException e) {
169             // Some file systems (e.g., WSL2 mapped network drives) don't provide ownership information
170             // Return null instead of propagating the exception
171             return null;
172         }
173     }
174 
175     public FileAttributes(
176             @Nullable Integer userId,
177             String userName,
178             @Nullable Integer groupId,
179             @Nullable String groupName,
180             int octalMode,
181             boolean symbolicLink,
182             boolean regularFile,
183             boolean directory,
184             boolean other,
185             Set<PosixFilePermission> permissions,
186             long size,
187             FileTime lastModifiedTime) {
188         this.userId = userId;
189         this.userName = userName;
190         this.groupId = groupId;
191         this.groupName = groupName;
192         this.octalMode = octalMode;
193         this.symbolicLink = symbolicLink;
194         this.regularFile = regularFile;
195         this.directory = directory;
196         this.other = other;
197         this.permissions = permissions;
198         this.size = size;
199         this.lastModifiedTime = lastModifiedTime;
200     }
201 
202     public static @Nonnull PlexusIoResourceAttributes uncached(@Nonnull File file) throws IOException {
203         return new FileAttributes(file);
204     }
205 
206     @Nullable
207     public Integer getGroupId() {
208 
209         return groupId;
210     }
211 
212     public boolean hasGroupId() {
213         return false;
214     }
215 
216     public boolean hasUserId() {
217         return false;
218     }
219 
220     @Nullable
221     public String getGroupName() {
222         return groupName;
223     }
224 
225     public Integer getUserId() {
226         return userId;
227     }
228 
229     public String getUserName() {
230         return userName;
231     }
232 
233     public boolean isGroupExecutable() {
234         return containsPermission(PosixFilePermission.GROUP_EXECUTE);
235     }
236 
237     private boolean containsPermission(PosixFilePermission groupExecute) {
238         return permissions.contains(groupExecute);
239     }
240 
241     public boolean isGroupReadable() {
242         return containsPermission(PosixFilePermission.GROUP_READ);
243     }
244 
245     public boolean isGroupWritable() {
246         return containsPermission(PosixFilePermission.GROUP_WRITE);
247     }
248 
249     public boolean isOwnerExecutable() {
250         return containsPermission(PosixFilePermission.OWNER_EXECUTE);
251     }
252 
253     public boolean isOwnerReadable() {
254         return containsPermission(PosixFilePermission.OWNER_READ);
255     }
256 
257     public boolean isOwnerWritable() {
258         return containsPermission(PosixFilePermission.OWNER_WRITE);
259     }
260 
261     public boolean isWorldExecutable() {
262         return containsPermission(PosixFilePermission.OTHERS_EXECUTE);
263     }
264 
265     public boolean isWorldReadable() {
266         return containsPermission(PosixFilePermission.OTHERS_READ);
267     }
268 
269     public boolean isWorldWritable() {
270         return containsPermission(PosixFilePermission.OTHERS_WRITE);
271     }
272 
273     public String toString() {
274         StringBuilder sb = new StringBuilder();
275         sb.append(System.lineSeparator());
276         sb.append("File Attributes:");
277         sb.append(System.lineSeparator());
278         sb.append("------------------------------");
279         sb.append(System.lineSeparator());
280         sb.append("user: ");
281         sb.append(userName == null ? "" : userName);
282         sb.append(System.lineSeparator());
283         sb.append("group: ");
284         sb.append(groupName == null ? "" : groupName);
285         sb.append(System.lineSeparator());
286         sb.append("uid: ");
287         sb.append(hasUserId() ? Integer.toString(userId) : "");
288         sb.append(System.lineSeparator());
289         sb.append("gid: ");
290         sb.append(hasGroupId() ? Integer.toString(groupId) : "");
291 
292         return sb.toString();
293     }
294 
295     public int getOctalMode() {
296         return octalMode;
297     }
298 
299     public int calculatePosixOctalMode() {
300         int result = 0;
301 
302         if (isOwnerReadable()) {
303             result |= AttributeConstants.OCTAL_OWNER_READ;
304         }
305 
306         if (isOwnerWritable()) {
307             result |= AttributeConstants.OCTAL_OWNER_WRITE;
308         }
309 
310         if (isOwnerExecutable()) {
311             result |= AttributeConstants.OCTAL_OWNER_EXECUTE;
312         }
313 
314         if (isGroupReadable()) {
315             result |= AttributeConstants.OCTAL_GROUP_READ;
316         }
317 
318         if (isGroupWritable()) {
319             result |= AttributeConstants.OCTAL_GROUP_WRITE;
320         }
321 
322         if (isGroupExecutable()) {
323             result |= AttributeConstants.OCTAL_GROUP_EXECUTE;
324         }
325 
326         if (isWorldReadable()) {
327             result |= AttributeConstants.OCTAL_WORLD_READ;
328         }
329 
330         if (isWorldWritable()) {
331             result |= AttributeConstants.OCTAL_WORLD_WRITE;
332         }
333 
334         if (isWorldExecutable()) {
335             result |= AttributeConstants.OCTAL_WORLD_EXECUTE;
336         }
337 
338         return result;
339     }
340 
341     public String getOctalModeString() {
342         return Integer.toString(getOctalMode(), 8);
343     }
344 
345     public boolean isSymbolicLink() {
346         return symbolicLink;
347     }
348 
349     public boolean isRegularFile() {
350         return regularFile;
351     }
352 
353     public boolean isDirectory() {
354         return directory;
355     }
356 
357     public boolean isOther() {
358         return other;
359     }
360 
361     public long getSize() {
362         return size;
363     }
364 
365     public FileTime getLastModifiedTime() {
366         return lastModifiedTime;
367     }
368 
369     protected Set<PosixFilePermission> getPermissions() {
370         return permissions;
371     }
372 }