package com.termux.shared.file; import android.content.Context; import android.os.Environment; import androidx.annotation.NonNull; import com.termux.shared.models.errors.Error; import com.termux.shared.termux.TermuxConstants; import java.io.File; import java.util.regex.Pattern; public class TermuxFileUtils { /** * Replace "$PREFIX/" or "~/" prefix with termux absolute paths. * * @param path The {@code path} to expand. * @return Returns the {@code expand path}. */ public static String getExpandedTermuxPath(String path) { if (path != null && !path.isEmpty()) { path = path.replaceAll("^\\$PREFIX$", TermuxConstants.TERMUX_PREFIX_DIR_PATH); path = path.replaceAll("^\\$PREFIX/", TermuxConstants.TERMUX_PREFIX_DIR_PATH + "/"); path = path.replaceAll("^~/$", TermuxConstants.TERMUX_HOME_DIR_PATH); path = path.replaceAll("^~/", TermuxConstants.TERMUX_HOME_DIR_PATH + "/"); } return path; } /** * Replace termux absolute paths with "$PREFIX/" or "~/" prefix. * * @param path The {@code path} to unexpand. * @return Returns the {@code unexpand path}. */ public static String getUnExpandedTermuxPath(String path) { if (path != null && !path.isEmpty()) { path = path.replaceAll("^" + Pattern.quote(TermuxConstants.TERMUX_PREFIX_DIR_PATH) + "/", "\\$PREFIX/"); path = path.replaceAll("^" + Pattern.quote(TermuxConstants.TERMUX_HOME_DIR_PATH) + "/", "~/"); } return path; } /** * Get canonical path. * * @param path The {@code path} to convert. * @param prefixForNonAbsolutePath Optional prefix path to prefix before non-absolute paths. This * can be set to {@code null} if non-absolute paths should * be prefixed with "/". The call to {@link File#getCanonicalPath()} * will automatically do this anyways. * @param expandPath The {@code boolean} that decides if input path is first attempted to be expanded by calling * {@link TermuxFileUtils#getExpandedTermuxPath(String)} before its passed to * {@link FileUtils#getCanonicalPath(String, String)}. * @return Returns the {@code canonical path}. */ public static String getCanonicalPath(String path, final String prefixForNonAbsolutePath, final boolean expandPath) { if (path == null) path = ""; if (expandPath) path = getExpandedTermuxPath(path); return FileUtils.getCanonicalPath(path, prefixForNonAbsolutePath); } /** * Check if {@code path} is under the allowed termux working directory paths. If it is, then * allowed parent path is returned. * * @param path The {@code path} to check. * @return Returns the allowed path if it {@code path} is under it, otherwise {@link TermuxConstants#TERMUX_FILES_DIR_PATH}. */ public static String getMatchedAllowedTermuxWorkingDirectoryParentPathForPath(String path) { if (path == null || path.isEmpty()) return TermuxConstants.TERMUX_FILES_DIR_PATH; if (path.startsWith(TermuxConstants.TERMUX_STORAGE_HOME_DIR_PATH + "/")) { return TermuxConstants.TERMUX_STORAGE_HOME_DIR_PATH; } if (path.startsWith(Environment.getExternalStorageDirectory().getAbsolutePath() + "/")) { return Environment.getExternalStorageDirectory().getAbsolutePath(); } else if (path.startsWith("/sdcard/")) { return "/sdcard"; } else { return TermuxConstants.TERMUX_FILES_DIR_PATH; } } /** * Validate the existence and permissions of directory file at path as a working directory for * termux app. * * The creation of missing directory and setting of missing permissions will only be done if * {@code path} is under paths returned by {@link #getMatchedAllowedTermuxWorkingDirectoryParentPathForPath(String)}. * * The permissions set to directory will be {@link FileUtils#APP_WORKING_DIRECTORY_PERMISSIONS}. * * @param label The optional label for the directory file. This can optionally be {@code null}. * @param filePath The {@code path} for file to validate or create. Symlinks will not be followed. * @param createDirectoryIfMissing The {@code boolean} that decides if directory file * should be created if its missing. * @param setPermissions The {@code boolean} that decides if permissions are to be * automatically set defined by {@code permissionsToCheck}. * @param setMissingPermissionsOnly The {@code boolean} that decides if only missing permissions * are to be set or if they should be overridden. * @param ignoreErrorsIfPathIsInParentDirPath The {@code boolean} that decides if existence * and permission errors are to be ignored if path is * in {@code parentDirPath}. * @param ignoreIfNotExecutable The {@code boolean} that decides if missing executable permission * error is to be ignored. This allows making an attempt to set * executable permissions, but ignoring if it fails. * @return Returns the {@code error} if path is not a directory file, failed to create it, * or validating permissions failed, otherwise {@code null}. */ public static Error validateDirectoryFileExistenceAndPermissions(String label, final String filePath, final boolean createDirectoryIfMissing, final boolean setPermissions, final boolean setMissingPermissionsOnly, final boolean ignoreErrorsIfPathIsInParentDirPath, final boolean ignoreIfNotExecutable) { return FileUtils.validateDirectoryFileExistenceAndPermissions(label, filePath, TermuxFileUtils.getMatchedAllowedTermuxWorkingDirectoryParentPathForPath(filePath), createDirectoryIfMissing, FileUtils.APP_WORKING_DIRECTORY_PERMISSIONS, setPermissions, setMissingPermissionsOnly, ignoreErrorsIfPathIsInParentDirPath, ignoreIfNotExecutable); } /** * Validate the existence and permissions of {@link TermuxConstants#TERMUX_FILES_DIR_PATH}. * * The directory will not be created manually but by calling {@link Context#getFilesDir()} * so that android itself creates it. The `/data/data/[package_name]` directory cannot be * created by an app itself. Note that the path returned by {@link Context#getFilesDir()} will * be under `/data/user/[id]/[package_name]` instead of `/data/data/[package_name]` * defined by default by {@link TermuxConstants#TERMUX_FILES_DIR_PATH}, where id will be 0 for * primary user and a higher number for other users/profiles. If app is running under work profile * or secondary user, then {@link TermuxConstants#TERMUX_FILES_DIR_PATH} will not be accessible * and will not be automatically created, unless there is a bind mount from `/data/user/[id]` * to `/data/data`, ideally in the right namespace. * * The permissions set to directory will be {@link FileUtils#APP_WORKING_DIRECTORY_PERMISSIONS}. * * https://source.android.com/devices/tech/admin/multi-user * * @param context The {@link Context} for operations. * @param createDirectoryIfMissing The {@code boolean} that decides if directory file * should be created if its missing. * @param setMissingPermissions The {@code boolean} that decides if permissions are to be * automatically set. * @return Returns the {@code error} if path is not a directory file, failed to create it, * or validating permissions failed, otherwise {@code null}. */ public static Error isTermuxFilesDirectoryAccessible(@NonNull final Context context, boolean createDirectoryIfMissing, boolean setMissingPermissions) { if (createDirectoryIfMissing) context.getFilesDir(); if (setMissingPermissions) FileUtils.setMissingFilePermissions("Termux files directory", TermuxConstants.TERMUX_FILES_DIR_PATH, FileUtils.APP_WORKING_DIRECTORY_PERMISSIONS); return FileUtils.checkMissingFilePermissions("Termux files directory", TermuxConstants.TERMUX_FILES_DIR_PATH, FileUtils.APP_WORKING_DIRECTORY_PERMISSIONS, false); } }