]> source.dussan.org Git - archiva.git/blob
551729a0fdc6842e17fdd9bd65e8489dd98ba70c
[archiva.git] /
1 package org.apache.archiva.repository.storage.fs;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  */
20
21 import org.apache.archiva.repository.storage.RepositoryStorage;
22 import org.apache.archiva.repository.storage.StorageAsset;
23 import org.apache.commons.lang3.StringUtils;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.io.OutputStream;
30 import java.nio.channels.FileChannel;
31 import java.nio.channels.ReadableByteChannel;
32 import java.nio.channels.WritableByteChannel;
33 import java.nio.file.*;
34 import java.nio.file.attribute.*;
35 import java.time.Instant;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.List;
39 import java.util.Set;
40 import java.util.stream.Collectors;
41
42 /**
43  * Implementation of an asset that is stored on the filesystem.
44  * <p>
45  * The implementation does not check the given paths. Caller should normalize the asset path
46  * and check, if the base path is a parent of the resulting path.
47  * <p>
48  * The file must not exist for all operations.
49  *
50  * @author Martin Stockhammer <martin_s@apache.org>
51  */
52 public class FilesystemAsset implements StorageAsset, Comparable {
53
54     private final static Logger log = LoggerFactory.getLogger(FilesystemAsset.class);
55
56     private final Path basePath;
57     private final Path assetPath;
58     private final String relativePath;
59
60     public static final String DEFAULT_POSIX_FILE_PERMS = "rw-rw----";
61     public static final String DEFAULT_POSIX_DIR_PERMS = "rwxrwx---";
62
63     public static final Set<PosixFilePermission> DEFAULT_POSIX_FILE_PERMISSIONS;
64     public static final Set<PosixFilePermission> DEFAULT_POSIX_DIR_PERMISSIONS;
65
66     public static final AclEntryPermission[] DEFAULT_ACL_FILE_PERMISSIONS = new AclEntryPermission[]{
67             AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
68             AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA
69     };
70
71     public static final AclEntryPermission[] DEFAULT_ACL_DIR_PERMISSIONS = new AclEntryPermission[]{
72             AclEntryPermission.ADD_FILE, AclEntryPermission.ADD_SUBDIRECTORY, AclEntryPermission.DELETE_CHILD,
73             AclEntryPermission.DELETE, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_ACL,
74             AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_DATA, AclEntryPermission.APPEND_DATA
75     };
76
77     static {
78
79         DEFAULT_POSIX_FILE_PERMISSIONS = PosixFilePermissions.fromString(DEFAULT_POSIX_FILE_PERMS);
80         DEFAULT_POSIX_DIR_PERMISSIONS = PosixFilePermissions.fromString(DEFAULT_POSIX_DIR_PERMS);
81     }
82
83     Set<PosixFilePermission> defaultPosixFilePermissions = DEFAULT_POSIX_FILE_PERMISSIONS;
84     Set<PosixFilePermission> defaultPosixDirectoryPermissions = DEFAULT_POSIX_DIR_PERMISSIONS;
85
86     List<AclEntry> defaultFileAcls;
87     List<AclEntry> defaultDirectoryAcls;
88
89     boolean supportsAcl = false;
90     boolean supportsPosix = false;
91     final boolean setPermissionsForNew;
92     final RepositoryStorage storage;
93
94     boolean directoryHint = false;
95
96     private static final OpenOption[] REPLACE_OPTIONS = new OpenOption[]{StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE};
97     private static final OpenOption[] APPEND_OPTIONS = new OpenOption[]{StandardOpenOption.APPEND};
98
99
100     FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath) {
101         this.assetPath = assetPath;
102         this.relativePath = normalizePath(path);
103         this.setPermissionsForNew=false;
104         this.basePath = basePath;
105         this.storage = storage;
106         init();
107     }
108
109     /**
110      * Creates an asset for the given path. The given paths are not checked.
111      * The base path should be an absolute path.
112      *
113      * @param path The logical path for the asset relative to the repository.
114      * @param assetPath The asset path.
115      */
116     public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath) {
117         this.assetPath = assetPath;
118         this.relativePath = normalizePath(path);
119         this.setPermissionsForNew = false;
120         this.basePath = null;
121         this.storage = storage;
122         // The base directory is always a directory
123         if ("".equals(path) || "/".equals(path)) {
124             this.directoryHint = true;
125         }
126         init();
127     }
128
129     /**
130      * Creates an asset for the given path. The given paths are not checked.
131      * The base path should be an absolute path.
132      *
133      * @param path The logical path for the asset relative to the repository
134      * @param assetPath The asset path.
135      * @param directory This is only relevant, if the represented file or directory does not exist yet and
136      *                  is a hint.
137      */
138     public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath, boolean directory) {
139         this.assetPath = assetPath;
140         this.relativePath = normalizePath(path);
141         this.directoryHint = directory;
142         this.setPermissionsForNew = false;
143         this.basePath = basePath;
144         this.storage = storage;
145         init();
146     }
147
148     /**
149      * Creates an asset for the given path. The given paths are not checked.
150      * The base path should be an absolute path.
151      *
152      * @param path The logical path for the asset relative to the repository
153      * @param assetPath The asset path.
154      * @param directory This is only relevant, if the represented file or directory does not exist yet and
155      *                  is a hint.
156      */
157     public FilesystemAsset(RepositoryStorage storage, String path, Path assetPath, Path basePath, boolean directory, boolean setPermissionsForNew) {
158         this.assetPath = assetPath;
159         this.relativePath = normalizePath(path);
160         this.directoryHint = directory;
161         this.setPermissionsForNew = setPermissionsForNew;
162         this.basePath = basePath;
163         this.storage = storage;
164         init();
165     }
166
167     private String normalizePath(final String path) {
168         if (!path.startsWith("/")) {
169             return "/"+path;
170         } else {
171             String tmpPath = path;
172             while (tmpPath.startsWith("//")) {
173                 tmpPath = tmpPath.substring(1);
174             }
175             return tmpPath;
176         }
177     }
178
179     private void init() {
180
181         if (setPermissionsForNew) {
182             try {
183                 supportsAcl = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(AclFileAttributeView.class);
184             } catch (IOException e) {
185                 log.error("Could not check filesystem capabilities {}", e.getMessage());
186             }
187             try {
188                 supportsPosix = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(PosixFileAttributeView.class);
189             } catch (IOException e) {
190                 log.error("Could not check filesystem capabilities {}", e.getMessage());
191             }
192
193             if (supportsAcl) {
194                 AclFileAttributeView aclView = Files.getFileAttributeView(assetPath.getParent(), AclFileAttributeView.class);
195                 UserPrincipal owner = null;
196                 try {
197                     owner = aclView.getOwner();
198                     setDefaultFileAcls(processPermissions(owner, DEFAULT_ACL_FILE_PERMISSIONS));
199                     setDefaultDirectoryAcls(processPermissions(owner, DEFAULT_ACL_DIR_PERMISSIONS));
200
201                 } catch (IOException e) {
202                     supportsAcl = false;
203                 }
204
205
206             }
207         }
208     }
209
210     private List<AclEntry> processPermissions(UserPrincipal owner, AclEntryPermission[] defaultAclFilePermissions) {
211         AclEntry.Builder aclBuilder = AclEntry.newBuilder();
212         aclBuilder.setPermissions(defaultAclFilePermissions);
213         aclBuilder.setType(AclEntryType.ALLOW);
214         aclBuilder.setPrincipal(owner);
215         ArrayList<AclEntry> aclList = new ArrayList<>();
216         aclList.add(aclBuilder.build());
217         return aclList;
218     }
219
220
221     @Override
222     public RepositoryStorage getStorage( )
223     {
224         return storage;
225     }
226
227     @Override
228     public String getPath() {
229         return relativePath;
230     }
231
232     @Override
233     public String getName() {
234         return assetPath.getFileName().toString();
235     }
236
237     @Override
238     public Instant getModificationTime() {
239         try {
240             return Files.getLastModifiedTime(assetPath).toInstant();
241         } catch (IOException e) {
242             log.error("Could not read modification time of {}", assetPath);
243             return Instant.now();
244         }
245     }
246
247     /**
248      * Returns true, if the path of this asset points to a directory
249      *
250      * @return
251      */
252     @Override
253     public boolean isContainer() {
254         if (Files.exists(assetPath)) {
255             return Files.isDirectory(assetPath);
256         } else {
257             return directoryHint;
258         }
259     }
260
261     @Override
262     public boolean isLeaf( )
263     {
264         if (Files.exists( assetPath )) {
265             return Files.isRegularFile( assetPath );
266         } else {
267             return !directoryHint;
268         }
269     }
270
271     /**
272      * Returns the list of directory entries, if this asset represents a directory.
273      * Otherwise a empty list will be returned.
274      *
275      * @return The list of entries in the directory, if it exists.
276      */
277     @Override
278     public List<StorageAsset> list() {
279         try {
280             return Files.list(assetPath).map(p -> new FilesystemAsset(storage, relativePath + "/" + p.getFileName().toString(), assetPath.resolve(p)))
281                     .collect(Collectors.toList());
282         } catch (IOException e) {
283             return Collections.EMPTY_LIST;
284         }
285     }
286
287     /**
288      * Returns the size of the represented file. If it cannot be determined, -1 is returned.
289      *
290      * @return
291      */
292     @Override
293     public long getSize() {
294         try {
295             return Files.size(assetPath);
296         } catch (IOException e) {
297             return -1;
298         }
299     }
300
301     /**
302      * Returns a input stream to the underlying file, if it exists. The caller has to make sure, that
303      * the stream is closed after it was used.
304      *
305      * @return
306      * @throws IOException
307      */
308     @Override
309     public InputStream getReadStream() throws IOException {
310         if (isContainer()) {
311             throw new IOException("Can not create input stream for container");
312         }
313         return Files.newInputStream(assetPath);
314     }
315
316     @Override
317     public ReadableByteChannel getReadChannel( ) throws IOException
318     {
319         return FileChannel.open( assetPath, StandardOpenOption.READ );
320     }
321
322     private OpenOption[] getOpenOptions(boolean replace) {
323         return replace ? REPLACE_OPTIONS : APPEND_OPTIONS;
324     }
325
326     @Override
327     public OutputStream getWriteStream( boolean replace) throws IOException {
328         OpenOption[] options = getOpenOptions( replace );
329         if (!Files.exists( assetPath )) {
330             create();
331         }
332         return Files.newOutputStream(assetPath, options);
333     }
334
335     @Override
336     public WritableByteChannel getWriteChannel( boolean replace ) throws IOException
337     {
338         OpenOption[] options = getOpenOptions( replace );
339         return FileChannel.open( assetPath, options );
340     }
341
342     @Override
343     public boolean replaceDataFromFile( Path newData) throws IOException {
344         final boolean createNew = !Files.exists(assetPath);
345         Path backup = null;
346         if (!createNew) {
347             backup = findBackupFile(assetPath);
348         }
349         try {
350             if (!createNew) {
351                 Files.move(assetPath, backup);
352             }
353             Files.move(newData, assetPath, StandardCopyOption.REPLACE_EXISTING);
354             applyDefaultPermissions(assetPath);
355             return true;
356         } catch (IOException e) {
357             log.error("Could not overwrite file {}", assetPath);
358             // Revert if possible
359             if (backup != null && Files.exists(backup)) {
360                 Files.move(backup, assetPath, StandardCopyOption.REPLACE_EXISTING);
361             }
362             throw e;
363         } finally {
364             if (backup != null) {
365                 try {
366                     Files.deleteIfExists(backup);
367                 } catch (IOException e) {
368                     log.error("Could not delete backup file {}", backup);
369                 }
370             }
371         }
372
373     }
374
375     private void applyDefaultPermissions(Path filePath) {
376         try {
377             if (supportsPosix) {
378                 Set<PosixFilePermission> perms;
379                 if (Files.isDirectory(filePath)) {
380                     perms = defaultPosixFilePermissions;
381                 } else {
382                     perms = defaultPosixDirectoryPermissions;
383                 }
384                 Files.setPosixFilePermissions(filePath, perms);
385             } else if (supportsAcl) {
386                 List<AclEntry> perms;
387                 if (Files.isDirectory(filePath)) {
388                     perms = getDefaultDirectoryAcls();
389                 } else {
390                     perms = getDefaultFileAcls();
391                 }
392                 AclFileAttributeView aclAttr = Files.getFileAttributeView(filePath, AclFileAttributeView.class);
393                 aclAttr.setAcl(perms);
394             }
395         } catch (IOException e) {
396             log.error("Could not set permissions for {}: {}", filePath, e.getMessage());
397         }
398     }
399
400     private Path findBackupFile(Path file) {
401         String ext = ".bak";
402         Path backupPath = file.getParent().resolve(file.getFileName().toString() + ext);
403         int idx = 0;
404         while (Files.exists(backupPath)) {
405             backupPath = file.getParent().resolve(file.getFileName().toString() + ext + idx++);
406         }
407         return backupPath;
408     }
409
410     @Override
411     public boolean exists() {
412         return Files.exists(assetPath);
413     }
414
415     @Override
416     public Path getFilePath() throws UnsupportedOperationException {
417         return assetPath;
418     }
419
420     @Override
421     public boolean isFileBased( )
422     {
423         return true;
424     }
425
426     @Override
427     public boolean hasParent( )
428     {
429         if (basePath!=null && assetPath.equals(basePath)) {
430                 return false;
431         }
432         return assetPath.getParent()!=null;
433     }
434
435     @Override
436     public StorageAsset getParent( )
437     {
438         Path parentPath;
439         if (basePath!=null && assetPath.equals( basePath )) {
440             parentPath=null;
441         } else
442         {
443             parentPath = assetPath.getParent( );
444         }
445         String relativeParent = StringUtils.substringBeforeLast( relativePath,"/");
446         if (parentPath!=null) {
447             return new FilesystemAsset(storage, relativeParent, parentPath, basePath, true, setPermissionsForNew );
448         } else {
449             return null;
450         }
451     }
452
453     @Override
454     public StorageAsset resolve(String toPath) {
455         return storage.getAsset(this.getPath()+"/"+toPath);
456     }
457
458
459     public void setDefaultFileAcls(List<AclEntry> acl) {
460         defaultFileAcls = acl;
461     }
462
463     public List<AclEntry> getDefaultFileAcls() {
464         return defaultFileAcls;
465     }
466
467     public void setDefaultPosixFilePermissions(Set<PosixFilePermission> perms) {
468         defaultPosixFilePermissions = perms;
469     }
470
471     public Set<PosixFilePermission> getDefaultPosixFilePermissions() {
472         return defaultPosixFilePermissions;
473     }
474
475     public void setDefaultDirectoryAcls(List<AclEntry> acl) {
476         defaultDirectoryAcls = acl;
477     }
478
479     public List<AclEntry> getDefaultDirectoryAcls() {
480         return defaultDirectoryAcls;
481     }
482
483     public void setDefaultPosixDirectoryPermissions(Set<PosixFilePermission> perms) {
484         defaultPosixDirectoryPermissions = perms;
485     }
486
487     public Set<PosixFilePermission> getDefaultPosixDirectoryPermissions() {
488         return defaultPosixDirectoryPermissions;
489     }
490
491     @Override
492     public void create() throws IOException {
493         if (!Files.exists(assetPath)) {
494             if (directoryHint) {
495                 Files.createDirectories(assetPath);
496             } else {
497                 if (!Files.exists( assetPath.getParent() )) {
498                     Files.createDirectories( assetPath.getParent( ) );
499                 }
500                 Files.createFile(assetPath);
501             }
502             if (setPermissionsForNew) {
503                 applyDefaultPermissions(assetPath);
504             }
505         }
506     }
507
508     @Override
509     public String toString() {
510         return relativePath+":"+assetPath;
511     }
512
513     @Override
514     public int compareTo(Object o) {
515         if (o instanceof FilesystemAsset) {
516             if (this.getPath()!=null) {
517                 return this.getPath().compareTo(((FilesystemAsset) o).getPath());
518             } else {
519                 return 0;
520             }
521         }
522         return 0;
523     }
524 }