org.apache.sshd.common.util.io.ModifiableFileWatcher Maven / Gradle / Ivy
The newest version!
/*
* 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.sshd.common.util.io;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.OsUtils;
import org.apache.sshd.common.util.io.resource.PathResource;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
/**
* Watches over changes for a file and re-loads them if file has changed - including if file is deleted or (re-)created
*
* @author Apache MINA SSHD Project
*/
public class ModifiableFileWatcher extends AbstractLoggingBean {
/**
* The {@link Set} of {@link PosixFilePermission} not allowed if strict permissions are enforced on key files
*/
public static final Set STRICTLY_PROHIBITED_FILE_PERMISSION = Collections.unmodifiableSet(
EnumSet.of(
PosixFilePermission.GROUP_WRITE,
PosixFilePermission.OTHERS_WRITE));
protected final LinkOption[] options;
private final Path file;
private final AtomicReference metadata = new AtomicReference<>();
public ModifiableFileWatcher(Path file) {
this(file, IoUtils.getLinkOptions(true));
}
public ModifiableFileWatcher(Path file, LinkOption... options) {
this.file = Objects.requireNonNull(file, "No path to watch");
// use a clone to avoid being sensitive to changes in the passed array
this.options = (options == null) ? IoUtils.EMPTY_LINK_OPTIONS : options.clone();
}
/**
* @return The watched {@link Path}
*/
public final Path getPath() {
return file;
}
public final boolean exists() throws IOException {
return Files.exists(getPath(), options);
}
public final long size() throws IOException {
if (exists()) {
return Files.size(getPath());
} else {
return -1L;
}
}
public final FileTime lastModified() throws IOException {
if (exists()) {
BasicFileAttributes attrs = Files.readAttributes(getPath(), BasicFileAttributes.class, options);
return attrs.lastModifiedTime();
} else {
return null;
}
}
/**
* @return {@code true} if the watched file has probably been changed
* @throws IOException If failed to query file data
*/
public boolean checkReloadRequired() throws IOException {
FileSnapshot old = metadata.get();
if (old == null) {
metadata.set(FileSnapshot.save(file, options));
return true;
}
FileSnapshot current = old.reload(file, options);
metadata.set(current);
return old != current;
}
/**
* Resets the state attributes used to detect changes to the initial construction values - i.e., file assumed not to
* exist and no known size of modify time
*/
public void resetReloadAttributes() {
metadata.set(FileSnapshot.NO_FILE);
}
/**
* May be called to refresh the state attributes used to detect changes e.g., file existence, size and last-modified
* time once re-loading is successfully completed. If the file does not exist then the attributes are reset to an
* "unknown" state.
*
* @throws IOException If failed to access the file (if exists)
* @see #resetReloadAttributes()
*/
public void updateReloadAttributes() throws IOException {
if (exists()) {
metadata.compareAndSet(null, FileSnapshot.save(file, options));
} else {
resetReloadAttributes();
}
}
public PathResource toPathResource() {
return toPathResource(IoUtils.EMPTY_OPEN_OPTIONS);
}
public PathResource toPathResource(OpenOption... options) {
return new PathResource(getPath(), options);
}
@Override
public String toString() {
return Objects.toString(getPath());
}
/**
*
* Checks if a path has strict permissions
*
*
*
* -
*
* (For {@code Unix}) The path may not have group or others write permissions
*
*
*
* -
*
* The path must be owned by current user.
*
*
*
* -
*
* (For {@code Unix}) The path may be owned by root.
*
*
*
*
*
* @param path The {@link Path} to be checked - ignored if {@code null} or does not exist
* @param options The {@link LinkOption}s to use to query the file's permissions
* @return The violated permission as {@link SimpleImmutableEntry} where key is a loggable message and
* value is the offending object - e.g., {@link PosixFilePermission} or {@link String} for
* owner. Return value is {@code null} if no violations detected
* @throws IOException If failed to retrieve the permissions
* @see #STRICTLY_PROHIBITED_FILE_PERMISSION
*/
public static SimpleImmutableEntry validateStrictConfigFilePermissions(Path path, LinkOption... options)
throws IOException {
if ((path == null) || (!Files.exists(path, options))) {
return null;
}
Collection perms = IoUtils.getPermissions(path, options);
if (GenericUtils.isEmpty(perms)) {
return null;
}
if (OsUtils.isUNIX()) {
PosixFilePermission p = IoUtils.validateExcludedPermissions(perms, STRICTLY_PROHIBITED_FILE_PERMISSION);
if (p != null) {
return new SimpleImmutableEntry<>(String.format("Permissions violation (%s)", p), p);
}
}
String owner = IoUtils.getFileOwner(path, options);
if (GenericUtils.isEmpty(owner)) {
// we cannot get owner
// general issue: jvm does not support permissions
// security issue: specific filesystem does not support permissions
return null;
}
String current = OsUtils.getCurrentUser();
Set expected = new HashSet<>();
expected.add(current);
if (OsUtils.isUNIX()) {
// Windows "Administrator" was considered however in Windows most likely a group is used.
expected.add(OsUtils.ROOT_USER);
}
if (!expected.contains(owner)) {
return new SimpleImmutableEntry<>(String.format("Owner violation (%s)", owner), owner);
}
return null;
}
}