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 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  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, Comparable {
  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(final String path) {
  140. if (!path.startsWith("/")) {
  141. return "/"+path;
  142. } else {
  143. String tmpPath = path;
  144. while (tmpPath.startsWith("//")) {
  145. tmpPath = tmpPath.substring(1);
  146. }
  147. return tmpPath;
  148. }
  149. }
  150. private void init() {
  151. if (setPermissionsForNew) {
  152. try {
  153. supportsAcl = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(AclFileAttributeView.class);
  154. } catch (IOException e) {
  155. log.error("Could not check filesystem capabilities {}", e.getMessage());
  156. }
  157. try {
  158. supportsPosix = Files.getFileStore(assetPath.getRoot()).supportsFileAttributeView(PosixFileAttributeView.class);
  159. } catch (IOException e) {
  160. log.error("Could not check filesystem capabilities {}", e.getMessage());
  161. }
  162. if (supportsAcl) {
  163. AclFileAttributeView aclView = Files.getFileAttributeView(assetPath.getParent(), AclFileAttributeView.class);
  164. UserPrincipal owner = null;
  165. try {
  166. owner = aclView.getOwner();
  167. setDefaultFileAcls(processPermissions(owner, DEFAULT_ACL_FILE_PERMISSIONS));
  168. setDefaultDirectoryAcls(processPermissions(owner, DEFAULT_ACL_DIR_PERMISSIONS));
  169. } catch (IOException e) {
  170. supportsAcl = false;
  171. }
  172. }
  173. }
  174. }
  175. private List<AclEntry> processPermissions(UserPrincipal owner, AclEntryPermission[] defaultAclFilePermissions) {
  176. AclEntry.Builder aclBuilder = AclEntry.newBuilder();
  177. aclBuilder.setPermissions(defaultAclFilePermissions);
  178. aclBuilder.setType(AclEntryType.ALLOW);
  179. aclBuilder.setPrincipal(owner);
  180. ArrayList<AclEntry> aclList = new ArrayList<>();
  181. aclList.add(aclBuilder.build());
  182. return aclList;
  183. }
  184. @Override
  185. public RepositoryStorage getStorage( )
  186. {
  187. return storage;
  188. }
  189. @Override
  190. public String getPath() {
  191. return relativePath;
  192. }
  193. @Override
  194. public String getName() {
  195. return assetPath.getFileName().toString();
  196. }
  197. @Override
  198. public Instant getModificationTime() {
  199. try {
  200. return Files.getLastModifiedTime(assetPath).toInstant();
  201. } catch (IOException e) {
  202. log.error("Could not read modification time of {}", assetPath);
  203. return Instant.now();
  204. }
  205. }
  206. /**
  207. * Returns true, if the path of this asset points to a directory
  208. *
  209. * @return
  210. */
  211. @Override
  212. public boolean isContainer() {
  213. if (Files.exists(assetPath)) {
  214. return Files.isDirectory(assetPath);
  215. } else {
  216. return directoryHint;
  217. }
  218. }
  219. /**
  220. * Returns the list of directory entries, if this asset represents a directory.
  221. * Otherwise a empty list will be returned.
  222. *
  223. * @return The list of entries in the directory, if it exists.
  224. */
  225. @Override
  226. public List<StorageAsset> list() {
  227. try {
  228. return Files.list(assetPath).map(p -> new FilesystemAsset(storage, relativePath + "/" + p.getFileName().toString(), assetPath.resolve(p)))
  229. .collect(Collectors.toList());
  230. } catch (IOException e) {
  231. return Collections.EMPTY_LIST;
  232. }
  233. }
  234. /**
  235. * Returns the size of the represented file. If it cannot be determined, -1 is returned.
  236. *
  237. * @return
  238. */
  239. @Override
  240. public long getSize() {
  241. try {
  242. return Files.size(assetPath);
  243. } catch (IOException e) {
  244. return -1;
  245. }
  246. }
  247. /**
  248. * Returns a input stream to the underlying file, if it exists. The caller has to make sure, that
  249. * the stream is closed after it was used.
  250. *
  251. * @return
  252. * @throws IOException
  253. */
  254. @Override
  255. public InputStream getReadStream() throws IOException {
  256. if (isContainer()) {
  257. throw new IOException("Can not create input stream for container");
  258. }
  259. return Files.newInputStream(assetPath);
  260. }
  261. @Override
  262. public ReadableByteChannel getReadChannel( ) throws IOException
  263. {
  264. return FileChannel.open( assetPath, StandardOpenOption.READ );
  265. }
  266. private OpenOption[] getOpenOptions(boolean replace) {
  267. return replace ? REPLACE_OPTIONS : APPEND_OPTIONS;
  268. }
  269. @Override
  270. public OutputStream getWriteStream( boolean replace) throws IOException {
  271. OpenOption[] options = getOpenOptions( replace );
  272. if (!Files.exists( assetPath )) {
  273. create();
  274. }
  275. return Files.newOutputStream(assetPath, options);
  276. }
  277. @Override
  278. public WritableByteChannel getWriteChannel( boolean replace ) throws IOException
  279. {
  280. OpenOption[] options = getOpenOptions( replace );
  281. return FileChannel.open( assetPath, options );
  282. }
  283. @Override
  284. public boolean replaceDataFromFile( Path newData) throws IOException {
  285. final boolean createNew = !Files.exists(assetPath);
  286. Path backup = null;
  287. if (!createNew) {
  288. backup = findBackupFile(assetPath);
  289. }
  290. try {
  291. if (!createNew) {
  292. Files.move(assetPath, backup);
  293. }
  294. Files.move(newData, assetPath, StandardCopyOption.REPLACE_EXISTING);
  295. applyDefaultPermissions(assetPath);
  296. return true;
  297. } catch (IOException e) {
  298. log.error("Could not overwrite file {}", assetPath);
  299. // Revert if possible
  300. if (backup != null && Files.exists(backup)) {
  301. Files.move(backup, assetPath, StandardCopyOption.REPLACE_EXISTING);
  302. }
  303. throw e;
  304. } finally {
  305. if (backup != null) {
  306. try {
  307. Files.deleteIfExists(backup);
  308. } catch (IOException e) {
  309. log.error("Could not delete backup file {}", backup);
  310. }
  311. }
  312. }
  313. }
  314. private void applyDefaultPermissions(Path filePath) {
  315. try {
  316. if (supportsPosix) {
  317. Set<PosixFilePermission> perms;
  318. if (Files.isDirectory(filePath)) {
  319. perms = defaultPosixFilePermissions;
  320. } else {
  321. perms = defaultPosixDirectoryPermissions;
  322. }
  323. Files.setPosixFilePermissions(filePath, perms);
  324. } else if (supportsAcl) {
  325. List<AclEntry> perms;
  326. if (Files.isDirectory(filePath)) {
  327. perms = getDefaultDirectoryAcls();
  328. } else {
  329. perms = getDefaultFileAcls();
  330. }
  331. AclFileAttributeView aclAttr = Files.getFileAttributeView(filePath, AclFileAttributeView.class);
  332. aclAttr.setAcl(perms);
  333. }
  334. } catch (IOException e) {
  335. log.error("Could not set permissions for {}: {}", filePath, e.getMessage());
  336. }
  337. }
  338. private Path findBackupFile(Path file) {
  339. String ext = ".bak";
  340. Path backupPath = file.getParent().resolve(file.getFileName().toString() + ext);
  341. int idx = 0;
  342. while (Files.exists(backupPath)) {
  343. backupPath = file.getParent().resolve(file.getFileName().toString() + ext + idx++);
  344. }
  345. return backupPath;
  346. }
  347. @Override
  348. public boolean exists() {
  349. return Files.exists(assetPath);
  350. }
  351. @Override
  352. public Path getFilePath() throws UnsupportedOperationException {
  353. return assetPath;
  354. }
  355. @Override
  356. public boolean isFileBased( )
  357. {
  358. return true;
  359. }
  360. @Override
  361. public boolean hasParent( )
  362. {
  363. if (basePath!=null && assetPath.equals(basePath)) {
  364. return false;
  365. }
  366. return assetPath.getParent()!=null;
  367. }
  368. @Override
  369. public StorageAsset getParent( )
  370. {
  371. Path parentPath;
  372. if (basePath!=null && assetPath.equals( basePath )) {
  373. parentPath=null;
  374. } else
  375. {
  376. parentPath = assetPath.getParent( );
  377. }
  378. String relativeParent = StringUtils.substringBeforeLast( relativePath,"/");
  379. if (parentPath!=null) {
  380. return new FilesystemAsset(storage, relativeParent, parentPath, basePath, true, setPermissionsForNew );
  381. } else {
  382. return null;
  383. }
  384. }
  385. @Override
  386. public StorageAsset resolve(String toPath) {
  387. return storage.getAsset(this.getPath()+"/"+toPath);
  388. }
  389. public void setDefaultFileAcls(List<AclEntry> acl) {
  390. defaultFileAcls = acl;
  391. }
  392. public List<AclEntry> getDefaultFileAcls() {
  393. return defaultFileAcls;
  394. }
  395. public void setDefaultPosixFilePermissions(Set<PosixFilePermission> perms) {
  396. defaultPosixFilePermissions = perms;
  397. }
  398. public Set<PosixFilePermission> getDefaultPosixFilePermissions() {
  399. return defaultPosixFilePermissions;
  400. }
  401. public void setDefaultDirectoryAcls(List<AclEntry> acl) {
  402. defaultDirectoryAcls = acl;
  403. }
  404. public List<AclEntry> getDefaultDirectoryAcls() {
  405. return defaultDirectoryAcls;
  406. }
  407. public void setDefaultPosixDirectoryPermissions(Set<PosixFilePermission> perms) {
  408. defaultPosixDirectoryPermissions = perms;
  409. }
  410. public Set<PosixFilePermission> getDefaultPosixDirectoryPermissions() {
  411. return defaultPosixDirectoryPermissions;
  412. }
  413. @Override
  414. public void create() throws IOException {
  415. if (!Files.exists(assetPath)) {
  416. if (directoryHint) {
  417. Files.createDirectories(assetPath);
  418. } else {
  419. if (!Files.exists( assetPath.getParent() )) {
  420. Files.createDirectories( assetPath.getParent( ) );
  421. }
  422. Files.createFile(assetPath);
  423. }
  424. if (setPermissionsForNew) {
  425. applyDefaultPermissions(assetPath);
  426. }
  427. }
  428. }
  429. @Override
  430. public String toString() {
  431. return relativePath+":"+assetPath;
  432. }
  433. @Override
  434. public int compareTo(Object o) {
  435. if (o instanceof FilesystemAsset) {
  436. if (this.getPath()!=null) {
  437. return this.getPath().compareTo(((FilesystemAsset) o).getPath());
  438. } else {
  439. return 0;
  440. }
  441. }
  442. return 0;
  443. }
  444. }