You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FilesystemAsset.java 16KB

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