| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.lucene.store; |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.nio.file.FileAlreadyExistsException; |
| import java.nio.file.Files; |
| import java.nio.file.NoSuchFileException; |
| import java.nio.file.Path; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| |
| /** |
| * This directory wrapper overrides {@link Directory#copyFrom(Directory, String, String, IOContext)} in order |
| * to optionally use a hard-link instead of a full byte by byte file copy if applicable. Hard-links are only used if the |
| * underlying filesystem supports it and if the {@link java.nio.file.LinkPermission} "hard" is granted. |
| * |
| * <p><b>NOTE:</b> Using hard-links changes the copy semantics of |
| * {@link Directory#copyFrom(Directory, String, String, IOContext)}. When hard-links are used changes to the source file |
| * will be reflected in the target file and vice-versa. Within Lucene, files are write once and should not be modified |
| * after they have been written. This directory should not be used in situations where files change after they have |
| * been written. |
| * </p> |
| */ |
| public final class HardlinkCopyDirectoryWrapper extends FilterDirectory { |
| /** |
| * Creates a new HardlinkCopyDirectoryWrapper delegating to the given directory |
| */ |
| public HardlinkCopyDirectoryWrapper(Directory in) { |
| super(in); |
| } |
| |
| @Override |
| public void copyFrom(Directory from, String srcFile, String destFile, IOContext context) throws IOException { |
| final Directory fromUnwrapped = FilterDirectory.unwrap(from); |
| final Directory toUnwrapped = FilterDirectory.unwrap(this); |
| // try to unwrap to FSDirectory - we might be able to just create hard-links of these files and save copying |
| // the entire file. |
| Exception suppressedException = null; |
| boolean tryCopy = true; |
| if (fromUnwrapped instanceof FSDirectory |
| && toUnwrapped instanceof FSDirectory) { |
| final Path fromPath = ((FSDirectory) fromUnwrapped).getDirectory(); |
| final Path toPath = ((FSDirectory) toUnwrapped).getDirectory(); |
| |
| if (Files.isReadable(fromPath.resolve(srcFile)) && Files.isWritable(toPath)) { |
| // only try hardlinks if we have permission to access the files |
| // if not super.copyFrom() will give us the right exceptions |
| suppressedException = AccessController.doPrivileged((PrivilegedAction<Exception>) () -> { |
| try { |
| Files.createLink(toPath.resolve(destFile), fromPath.resolve(srcFile)); |
| } catch (FileNotFoundException | NoSuchFileException | FileAlreadyExistsException ex) { |
| return ex; // in these cases we bubble up since it's a true error condition. |
| } catch (IOException |
| | UnsupportedOperationException // if the FS doesn't support hard-links |
| | SecurityException ex // we don't have permission to use hard-links just fall back to byte copy |
| ) { |
| // hard-links are not supported or the files are on different filesystems |
| // we could go deeper and check if their filesstores are the same and opt |
| // out earlier but for now we just fall back to normal file-copy |
| return ex; |
| } |
| return null; |
| }); |
| tryCopy = suppressedException != null; |
| } |
| } |
| if (tryCopy) { |
| try { |
| super.copyFrom(from, srcFile, destFile, context); |
| } catch (Exception ex) { |
| if (suppressedException != null) { |
| ex.addSuppressed(suppressedException); |
| } |
| throw ex; |
| } |
| } |
| } |
| } |