/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/
package alluxio.util.io;
import alluxio.AlluxioURI;
import alluxio.exception.InvalidPathException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.UserPrincipal;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.util.Set;
import javax.annotation.concurrent.ThreadSafe;
/**
* Provides utility methods for working with files and directories.
*
* By convention, methods take file path strings as parameters.
*
* TODO(peis): Move everything to nio.
*/
@ThreadSafe
public final class FileUtils {
private static final Logger LOG = LoggerFactory.getLogger(FileUtils.class);
/**
* Changes the local file's group.
*
* @param path that will change owner
* @param group the new group
*/
public static void changeLocalFileGroup(String path, String group) throws IOException {
UserPrincipalLookupService lookupService =
FileSystems.getDefault().getUserPrincipalLookupService();
PosixFileAttributeView view =
Files.getFileAttributeView(Paths.get(path), PosixFileAttributeView.class,
LinkOption.NOFOLLOW_LINKS);
GroupPrincipal groupPrincipal = lookupService.lookupPrincipalByGroupName(group);
view.setGroup(groupPrincipal);
}
/**
* Changes local file's permission.
*
* @param filePath that will change permission
* @param perms the permission, e.g. "rwxr--r--"
*/
public static void changeLocalFilePermission(String filePath, String perms) throws IOException {
Files.setPosixFilePermissions(Paths.get(filePath), PosixFilePermissions.fromString(perms));
}
/**
* Changes local file's permission to be "rwxrwxrwx".
*
* @param filePath that will change permission
*/
public static void changeLocalFileToFullPermission(String filePath) throws IOException {
changeLocalFilePermission(filePath, "rwxrwxrwx");
}
/**
* Gets local file's owner.
*
* @param filePath the file path
* @return the owner of the local file
*/
public static String getLocalFileOwner(String filePath) throws IOException {
PosixFileAttributes attr =
Files.readAttributes(Paths.get(filePath), PosixFileAttributes.class);
return attr.owner().getName();
}
/**
* Gets local file's group.
*
* @param filePath the file path
* @return the group of the local file
*/
public static String getLocalFileGroup(String filePath) throws IOException {
PosixFileAttributes attr =
Files.readAttributes(Paths.get(filePath), PosixFileAttributes.class);
return attr.group().getName();
}
/**
* Gets local file's permission mode.
*
* @param filePath the file path
* @return the file mode in short, e.g. 0777
*/
public static short getLocalFileMode(String filePath) throws IOException {
Set<PosixFilePermission> permission =
Files.readAttributes(Paths.get(filePath), PosixFileAttributes.class).permissions();
return translatePosixPermissionToMode(permission);
}
/**
* Translate posix file permissions to short mode.
*
* @param permission posix file permission
* @return mode for file
*/
public static short translatePosixPermissionToMode(Set<PosixFilePermission> permission) {
int mode = 0;
for (PosixFilePermission action : PosixFilePermission.values()) {
mode = mode << 1;
mode += permission.contains(action) ? 1 : 0;
}
return (short) mode;
}
/**
* Changes the local file's user.
*
* @param path that will change owner
* @param user the new user
*/
public static void changeLocalFileUser(String path, String user) throws IOException {
UserPrincipalLookupService lookupService =
FileSystems.getDefault().getUserPrincipalLookupService();
PosixFileAttributeView view =
Files.getFileAttributeView(Paths.get(path), PosixFileAttributeView.class,
LinkOption.NOFOLLOW_LINKS);
UserPrincipal userPrincipal = lookupService.lookupPrincipalByName(user);
view.setOwner(userPrincipal);
}
/**
* Sticky bit can be set primarily on directories in UNIX / Linux.
*
* If the sticky bit of is enabled on a directory, only the owner and the root user can delete /
* rename the files or directories within that directory. No one else can delete other users data
* in this directory(Where sticky bit is set).
*
* This is a security measure to avoid deletion of folders and their content (sub-folders and
* files), though other users have full permissions.
*
* Setting the sticky bit of a file is a no-op.
*
* @param dir absolute dir path to set the sticky bit
*/
public static void setLocalDirStickyBit(String dir) {
try {
// Support for sticky bit is platform specific. Check if the path starts with "/" and if so,
// assume that the host supports the chmod command.
if (dir.startsWith(AlluxioURI.SEPARATOR)) {
// TODO(peis): This is very slow. Consider removing this.
Runtime.getRuntime().exec("chmod +t " + dir);
}
} catch (IOException e) {
LOG.info("Can not set the sticky bit of the directory: {}", dir, e);
}
}
/**
* Creates the local block path and all the parent directories. Also, sets the appropriate
* permissions.
*
* @param path the path of the block
*/
public static void createBlockPath(String path) throws IOException {
try {
createStorageDirPath(PathUtils.getParent(path));
} catch (InvalidPathException e) {
throw new IOException("Failed to create block path, get parent path of " + path + "failed",
e);
} catch (IOException e) {
throw new IOException("Failed to create block path " + path, e);
}
}
/**
* Moves file from one place to another, can across storage devices (e.g., from memory to SSD)
* when {@link File#renameTo} may not work.
*
* Current implementation uses {@link com.google.common.io.Files#move(File, File)}, may change if
* there is a better solution.
*
* @param srcPath pathname string of source file
* @param dstPath pathname string of destination file
*/
public static void move(String srcPath, String dstPath) throws IOException {
com.google.common.io.Files.move(new File(srcPath), new File(dstPath));
}
/**
* Deletes the file or directory.
*
* Current implementation uses {@link java.io.File#delete()}, may change if there is a better
* solution.
*
* @param path pathname string of file or directory
*/
public static void delete(String path) throws IOException {
File file = new File(path);
if (!file.delete()) {
throw new IOException("Failed to delete " + path);
}
}
/**
* Deletes a file or a directory, recursively if it is a directory.
*
* @param path pathname to be deleted
*/
public static void deletePathRecursively(String path) throws IOException {
Path root = Paths.get(path);
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
if (e == null) {
Files.delete(dir);
return FileVisitResult.CONTINUE;
} else {
throw e;
}
}
});
}
/**
* Creates the storage directory path, including any necessary but nonexistent parent directories.
* If the directory already exists, do nothing.
*
* Also, appropriate directory permissions (777 + StickyBit, namely "drwxrwxrwt") are set.
*
* @param path storage directory path to create
*/
public static void createStorageDirPath(String path) throws IOException {
File dir = new File(path);
if (dir.exists()) {
return;
}
if (!dir.mkdirs()) {
if (dir.exists()) {
// This dir has been created concurrently.
return;
}
throw new IOException("Failed to create folder " + path);
}
String absolutePath = dir.getAbsolutePath();
changeLocalFileToFullPermission(absolutePath);
setLocalDirStickyBit(absolutePath);
LOG.info("Folder {} was created!", path);
}
/**
* Creates an empty file and its intermediate directories if necessary.
*
* @param filePath pathname string of the file to create
*/
public static void createFile(String filePath) throws IOException {
File file = new File(filePath);
com.google.common.io.Files.createParentDirs(file);
if (!file.createNewFile()) {
throw new IOException("File already exists " + filePath);
}
}
/**
* Creates an empty directory and its intermediate directories if necessary.
*
* @param path path of the directory to create
*/
public static void createDir(String path) throws IOException {
new File(path).mkdirs();
}
/**
* Checks if a path exists.
*
* @param path the given path
* @return true if path exists, false otherwise
*/
public static boolean exists(String path) {
return new File(path).exists();
}
private FileUtils() {} // prevent instantiation
}