/*
* Licensed 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 com.addthis.hydra.util;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import com.addthis.basis.util.Parameter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SymlinkHealthCheck extends CountingHealthCheck {
private final Path targetDirectory;
/**
* default is 10 minutes *
*/
private static final long SYMLINK_CHECK_INTERVAL =
Parameter.longValue("symlink.check.interval", 10 * 60 * 1000);
private static final int SYMLINK_MAX_DEPTH = 64;
private static final Logger log = LoggerFactory.getLogger(SymlinkHealthCheck.class);
public SymlinkHealthCheck(Path targetDirectory) {
super(1, "SymlinkDuplicateFailure", true);
this.targetDirectory = targetDirectory;
}
private Path resolveLink(Path path) throws IOException {
int depth;
for (depth = 0; depth < SYMLINK_MAX_DEPTH && Files.isSymbolicLink(path); depth++) {
path = Files.readSymbolicLink(path);
}
if (depth == SYMLINK_MAX_DEPTH) {
return null;
} else {
return path;
}
}
public final HealthCheckThread startHealthCheckThread() {
return this.startHealthCheckThread(SYMLINK_CHECK_INTERVAL, "symlinkHealthCheck");
}
@Override
public boolean check() {
/** a map of sinks back to their sources **/
Map<Path, Path> symlinkMap = new HashMap<>();
try {
if (!Files.exists(targetDirectory)) {
return true;
}
if (!Files.isDirectory(targetDirectory)) {
return true;
}
DirectoryStream<Path> stream = Files.newDirectoryStream(targetDirectory);
for (Path source : stream) {
Path sink = resolveLink(source);
if (sink == null) {
String msg = "The symlink " + source + " has a recursive depth of" +
" greater than " + SYMLINK_MAX_DEPTH;
log.warn(msg);
return false;
}
sink = sink.toAbsolutePath();
if (symlinkMap.containsKey(sink)) {
Path firstSource = symlinkMap.get(sink);
String msg = "The symlinks " + firstSource + " and " + source;
msg += " both resolve to the destination " + sink;
log.warn(msg);
return false;
}
symlinkMap.put(sink, source);
}
} catch (IOException e) {
log.warn(e.getMessage());
return false;
}
return true;
}
}