blob: c7b164a99545c85373dc9766f7164f71d64eac65 [file] [log] [blame]
/*
* 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;
}
}
}
}