Fixed: Fix bootstrap not installing on app install

Previously, bootstrap was only installed if `$PREFIX` didn't exist, was empty or only had `$PREFIX/tmp`. But now with 03e1d14e, `$PREFIX/etc/termux/termux.env` was also created at app startup before bootstrap check was made, hence it was being assumed that bootstrap was already installed.

Now, bootstrap will be installed even if `$PREFIX/tmp`, `$PREFIX/etc/termux/termux.env.tmp` or `$PREFIX/etc/termux/termux.env` exist but no other files do.

Closes #2844
This commit is contained in:
agnostic-apollo 2022-06-18 05:53:26 +05:00
parent 82b1580312
commit a2df7d791a
7 changed files with 241 additions and 7 deletions

View File

@ -21,6 +21,7 @@ import com.termux.shared.errors.Error;
import com.termux.shared.android.PackageUtils;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.termux.TermuxUtils;
import com.termux.shared.termux.shell.command.environment.TermuxShellEnvironment;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
@ -103,10 +104,8 @@ final class TermuxInstaller {
// If prefix directory exists, even if its a symlink to a valid directory and symlink is not broken/dangling
if (FileUtils.directoryFileExists(TERMUX_PREFIX_DIR_PATH, true)) {
File[] PREFIX_FILE_LIST = TERMUX_PREFIX_DIR.listFiles();
// If prefix directory is empty or only contains the tmp directory
if(PREFIX_FILE_LIST == null || PREFIX_FILE_LIST.length == 0 || (PREFIX_FILE_LIST.length == 1 && TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH.equals(PREFIX_FILE_LIST[0].getAbsolutePath()))) {
Logger.logInfo(LOG_TAG, "The termux prefix directory \"" + TERMUX_PREFIX_DIR_PATH + "\" exists but is empty or only contains the tmp directory.");
if (TermuxFileUtils.isTermuxPrefixDirectoryEmpty()) {
Logger.logInfo(LOG_TAG, "The termux prefix directory \"" + TERMUX_PREFIX_DIR_PATH + "\" exists but is empty or only contains specific unimportant files.");
} else {
whenDone.run();
return;
@ -218,6 +217,10 @@ final class TermuxInstaller {
}
Logger.logInfo(LOG_TAG, "Bootstrap packages installed successfully.");
// Recreate env file since termux prefix was wiped earlier
TermuxShellEnvironment.writeEnvironmentToFile(activity);
activity.runOnUiThread(whenDone);
} catch (final Exception e) {

View File

@ -110,4 +110,9 @@ public class Errno {
}
}
public boolean equalsErrorTypeAndCode(Error error) {
if (error == null) return false;
return type.equals(error.getType()) && code == error.getCode();
}
}

View File

@ -33,6 +33,7 @@ import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.file.LinkOption;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Iterator;
@ -185,6 +186,118 @@ public class FileUtils {
}
/**
* Validate that directory is empty or contains only files in {@code ignoredSubFilePaths}.
*
* If parent path of an ignored file exists, but ignored file itself does not exist, then directory
* is not considered empty.
*
* @param label The optional label for directory to check. This can optionally be {@code null}.
* @param filePath The {@code path} for directory to check.
* @param ignoredSubFilePaths The list of absolute file paths under {@code filePath} dir.
* Validation is done for the paths.
* @param ignoreNonExistentFile The {@code boolean} that decides if it should be considered an
* error if file to be checked doesn't exist.
* @return Returns {@code null} if directory is empty or contains only files in {@code ignoredSubFilePaths}.
* Returns {@code FileUtilsErrno#ERRNO_NON_EMPTY_DIRECTORY_FILE} if a file was found that did not
* exist in the {@code ignoredSubFilePaths}, otherwise returns an appropriate {@code error} if
* checking was not successful.
*/
public static Error validateDirectoryFileEmptyOrOnlyContainsSpecificFiles(String label, String filePath,
final List<String> ignoredSubFilePaths,
final boolean ignoreNonExistentFile) {
label = (label == null || label.isEmpty() ? "" : label + " ");
if (filePath == null || filePath.isEmpty()) return FunctionErrno.ERRNO_NULL_OR_EMPTY_PARAMETER.getError(label + "file path", "isDirectoryFileEmptyOrOnlyContainsSpecificFiles");
try {
File file = new File(filePath);
FileType fileType = getFileType(filePath, false);
// If file exists but not a directory file
if (fileType != FileType.NO_EXIST && fileType != FileType.DIRECTORY) {
return FileUtilsErrno.ERRNO_NON_DIRECTORY_FILE_FOUND.getError(label + "directory", filePath).setLabel(label + "directory");
}
// If file does not exist
if (fileType == FileType.NO_EXIST) {
// If checking is to be ignored if file does not exist
if (ignoreNonExistentFile)
return null;
else {
label += "directory to check if is empty or only contains specific files";
return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError(label, filePath).setLabel(label);
}
}
File[] subFiles = file.listFiles();
if (subFiles == null || subFiles.length == 0)
return null;
// If sub files exists but no file should be ignored
if (ignoredSubFilePaths == null || ignoredSubFilePaths.size() == 0)
return FileUtilsErrno.ERRNO_NON_EMPTY_DIRECTORY_FILE.getError(label, filePath);
// If a sub file does not exist in ignored file path
if (nonIgnoredSubFileExists(subFiles, ignoredSubFilePaths)) {
return FileUtilsErrno.ERRNO_NON_EMPTY_DIRECTORY_FILE.getError(label, filePath);
}
} catch (Exception e) {
return FileUtilsErrno.ERRNO_VALIDATE_DIRECTORY_EMPTY_OR_ONLY_CONTAINS_SPECIFIC_FILES_FAILED_WITH_EXCEPTION.getError(e, label + "directory", filePath, e.getMessage());
}
return null;
}
/**
* Check if {@code subFiles} contains contains a file not in {@code ignoredSubFilePaths}.
*
* If parent path of an ignored file exists, but ignored file itself does not exist, then directory
* is not considered empty.
*
* This function should ideally not be called by itself but through
* {@link #validateDirectoryFileEmptyOrOnlyContainsSpecificFiles(String, String, List, boolean)}.
*
* @param subFiles The list of files of a directory to check.
* @param ignoredSubFilePaths The list of absolute file paths under {@code filePath} dir.
* Validation is done for the paths.
* @return Returns {@code true} if a file was found that did not exist in the {@code ignoredSubFilePaths},
* otherwise {@code false}.
*/
public static boolean nonIgnoredSubFileExists(File[] subFiles, @NonNull List<String> ignoredSubFilePaths) {
if (subFiles == null || subFiles.length == 0) return false;
String subFilePath;
for (File subFile : subFiles) {
subFilePath = subFile.getAbsolutePath();
// If sub file does not exist in ignored sub file paths
if (!ignoredSubFilePaths.contains(subFilePath)) {
boolean isParentPath = false;
for (String ignoredSubFilePath : ignoredSubFilePaths) {
if (ignoredSubFilePath.startsWith(subFilePath + "/") && fileExists(ignoredSubFilePath, false)) {
isParentPath = true;
break;
}
}
// If sub file is not a parent of any existing ignored sub file paths
if (!isParentPath) {
return true;
}
}
if (getFileType(subFilePath, false) == FileType.DIRECTORY) {
// If non ignored sub file found, then early exit, otherwise continue looking
if (nonIgnoredSubFileExists(subFile.listFiles(), ignoredSubFilePaths))
return true;
}
}
return false;
}
/**
* Checks whether a regular file exists at {@code filePath}.
*

View File

@ -34,9 +34,11 @@ public class FileUtilsErrno extends Errno {
public static final Errno ERRNO_NON_SYMLINK_FILE_FOUND_SHORT = new Errno(TYPE, 157, "Non-symlink file found at %1$s path.");
public static final Errno ERRNO_FILE_NOT_AN_ALLOWED_FILE_TYPE = new Errno(TYPE, 158, "The %1$s found at path \"%2$s\" of type \"%3$s\" is not one of allowed file types \"%4$s\".");
public static final Errno ERRNO_NON_EMPTY_DIRECTORY_FILE = new Errno(TYPE, 159, "The %1$s directory at path \"%2$s\" is not empty.");
public static final Errno ERRNO_VALIDATE_FILE_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION = new Errno(TYPE, 159, "Validating file existence and permissions of %1$s at path \"%2$s\" failed.\nException: %3$s");
public static final Errno ERRNO_VALIDATE_DIRECTORY_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION = new Errno(TYPE, 160, "Validating directory existence and permissions of %1$s at path \"%2$s\" failed.\nException: %3$s");
public static final Errno ERRNO_VALIDATE_FILE_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION = new Errno(TYPE, 160, "Validating file existence and permissions of %1$s at path \"%2$s\" failed.\nException: %3$s");
public static final Errno ERRNO_VALIDATE_DIRECTORY_EXISTENCE_AND_PERMISSIONS_FAILED_WITH_EXCEPTION = new Errno(TYPE, 161, "Validating directory existence and permissions of %1$s at path \"%2$s\" failed.\nException: %3$s");
public static final Errno ERRNO_VALIDATE_DIRECTORY_EMPTY_OR_ONLY_CONTAINS_SPECIFIC_FILES_FAILED_WITH_EXCEPTION = new Errno(TYPE, 162, "Validating directory is empty or only contains specific files of %1$s at path \"%2$s\" failed.\nException: %3$s");

View File

@ -4,12 +4,16 @@ import android.content.Context;
import androidx.annotation.NonNull;
import com.termux.shared.errors.Errno;
import com.termux.shared.file.FileUtils;
import com.termux.shared.file.FileUtilsErrno;
import com.termux.shared.logger.Logger;
import com.termux.shared.errors.Error;
import java.io.File;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
public class FileUtilsTests {
@ -68,6 +72,15 @@ public class FileUtilsTests {
String dir1__sub_dir1_label = "dir1/sub_dir1";
String dir1__sub_dir1_path = dir1_path + "/sub_dir1";
String dir1__sub_dir2_label = "dir1/sub_dir2";
String dir1__sub_dir2_path = dir1_path + "/sub_dir2";
String dir1__sub_dir3_label = "dir1/sub_dir3";
String dir1__sub_dir3_path = dir1_path + "/sub_dir3";
String dir1__sub_dir3__sub_reg1_label = "dir1/sub_dir3/sub_reg1";
String dir1__sub_dir3__sub_reg1_path = dir1__sub_dir3_path + "/sub_reg1";
String dir1__sub_reg1_label = "dir1/sub_reg1";
String dir1__sub_reg1_path = dir1_path + "/sub_reg1";
@ -274,6 +287,72 @@ public class FileUtilsTests {
if (FileUtils.fileExists(path, false))
throwException("The " + label + " regular file still exist after deletion");
List<String> ignoredSubFilePaths = Arrays.asList(dir1__sub_dir2_path, dir1__sub_dir3__sub_reg1_path);
// Create dir1 directory file
error = FileUtils.createDirectoryFile(dir1_label, dir1_path);
assertEqual("Failed to create " + dir1_label + " directory file", null, error);
// Test empty dir
error = FileUtils.validateDirectoryFileEmptyOrOnlyContainsSpecificFiles(dir1_label, dir1_path, ignoredSubFilePaths, false);
assertEqual("Failed to validate if " + dir1_label + " directory file is empty", null, error);
// Create dir1/sub_dir3 directory file
label = dir1__sub_dir3_label; path = dir1__sub_dir3_path;
error = FileUtils.createDirectoryFile(label, path);
assertEqual("Failed to create " + label + " directory file", null, error);
if (!FileUtils.directoryFileExists(path, false))
throwException("The " + label + " directory file does not exist as expected after creation");
// Test parent dir existing of non existing ignored regular file
error = FileUtils.validateDirectoryFileEmptyOrOnlyContainsSpecificFiles(dir1_label, dir1_path, ignoredSubFilePaths, false);
assertErrnoEqual("Failed to validate if " + dir1_label + " directory file is empty with parent dir existing of non existing ignored regular file", FileUtilsErrno.ERRNO_NON_EMPTY_DIRECTORY_FILE, error);
// Write "line1" to dir1/sub_dir3/sub_reg1 regular file
label = dir1__sub_dir3__sub_reg1_label; path = dir1__sub_dir3__sub_reg1_path;
error = FileUtils.writeTextToFile(label, path, Charset.defaultCharset(), "line1", false);
assertEqual("Failed to write string to " + label + " file with append mode false", null, error);
if (!FileUtils.regularFileExists(path, false))
throwException("The " + label + " file does not exist as expected after writing to it with append mode false");
// Test ignored regular file existing
error = FileUtils.validateDirectoryFileEmptyOrOnlyContainsSpecificFiles(dir1_label, dir1_path, ignoredSubFilePaths, false);
assertEqual("Failed to validate if " + dir1_label + " directory file is empty with ignored regular file existing", null, error);
// Create dir1/sub_dir2 directory file
label = dir1__sub_dir2_label; path = dir1__sub_dir2_path;
error = FileUtils.createDirectoryFile(label, path);
assertEqual("Failed to create " + label + " directory file", null, error);
if (!FileUtils.directoryFileExists(path, false))
throwException("The " + label + " directory file does not exist as expected after creation");
// Test ignored dir file existing
error = FileUtils.validateDirectoryFileEmptyOrOnlyContainsSpecificFiles(dir1_label, dir1_path, ignoredSubFilePaths, false);
assertEqual("Failed to validate if " + dir1_label + " directory file is empty with ignored dir file existing", null, error);
// Create dir1/sub_dir1 directory file
label = dir1__sub_dir1_label; path = dir1__sub_dir1_path;
error = FileUtils.createDirectoryFile(label, path);
assertEqual("Failed to create " + label + " directory file", null, error);
if (!FileUtils.directoryFileExists(path, false))
throwException("The " + label + " directory file does not exist as expected after creation");
// Test non ignored dir file existing
error = FileUtils.validateDirectoryFileEmptyOrOnlyContainsSpecificFiles(dir1_label, dir1_path, ignoredSubFilePaths, false);
assertErrnoEqual("Failed to validate if " + dir1_label + " directory file is empty with non ignored dir file existing", FileUtilsErrno.ERRNO_NON_EMPTY_DIRECTORY_FILE, error);
// Delete dir1 directory file
label = dir1_label; path = dir1_path;
error = FileUtils.deleteDirectoryFile(label, path, false);
assertEqual("Failed to delete " + label + " directory file", null, error);
FileUtils.getFileType("/dev/ptmx", false);
FileUtils.getFileType("/dev/null", false);
}
@ -299,6 +378,13 @@ public class FileUtilsTests {
return isEquals(expected, actual);
}
public static void assertErrnoEqual(@NonNull final String message, final Errno expected, final Error actual) throws Exception {
if ((expected == null && actual != null) || (expected != null && !expected.equalsErrorTypeAndCode(actual)))
throwException(message + "\nexpected: \"" + expected + "\"\nactual: \"" + actual + "\"\nFull Error:\n" + (actual != null ? actual.toString() : ""));
}
private static boolean isEquals(String expected, String actual) {
return expected.equals(actual);
}

View File

@ -11,7 +11,7 @@ import java.util.Formatter;
import java.util.List;
/*
* Version: v0.51.0
* Version: v0.52.0
* SPDX-License-Identifier: MIT
*
* Changelog
@ -274,6 +274,9 @@ import java.util.List;
*
* - 0.51.0 (2022-06-13)
* - Added `TERMUX_APP.FILE_SHARE_RECEIVER_ACTIVITY_CLASS_NAME` and `TERMUX_APP.FILE_VIEW_RECEIVER_ACTIVITY_CLASS_NAME`.
*
* - 0.52.0 (2022-06-18)
* - Added `TERMUX_PREFIX_DIR_IGNORED_SUB_FILES_PATHS_TO_CONSIDER_AS_EMPTY`.
*/
/**
@ -681,6 +684,11 @@ public final class TermuxConstants {
public static final File TERMUX_APPS_DIR = new File(TERMUX_APPS_DIR_PATH);
/** Termux app $PREFIX directory path ignored sub file paths to consider it empty */
public static final List<String> TERMUX_PREFIX_DIR_IGNORED_SUB_FILES_PATHS_TO_CONSIDER_AS_EMPTY = Arrays.asList(
TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH, TermuxConstants.TERMUX_ENV_TEMP_FILE_PATH, TermuxConstants.TERMUX_ENV_FILE_PATH);
/*
* Termux app and plugin preferences and properties file paths.

View File

@ -1,5 +1,7 @@
package com.termux.shared.termux.file;
import static com.termux.shared.termux.TermuxConstants.TERMUX_PREFIX_DIR_PATH;
import android.content.Context;
import android.os.Environment;
@ -325,6 +327,21 @@ public class TermuxFileUtils {
false, false);
}
/**
* If {@link TermuxConstants#TERMUX_PREFIX_DIR_PATH} doesn't exist, is empty or only contains
* files in {@link TermuxConstants#TERMUX_PREFIX_DIR_IGNORED_SUB_FILES_PATHS_TO_CONSIDER_AS_EMPTY}.
*/
public static boolean isTermuxPrefixDirectoryEmpty() {
Error error = FileUtils.validateDirectoryFileEmptyOrOnlyContainsSpecificFiles("termux prefix",
TERMUX_PREFIX_DIR_PATH, TermuxConstants.TERMUX_PREFIX_DIR_IGNORED_SUB_FILES_PATHS_TO_CONSIDER_AS_EMPTY, true);
if (error == null)
return true;
if (!FileUtilsErrno.ERRNO_NON_EMPTY_DIRECTORY_FILE.equalsErrorTypeAndCode(error))
Logger.logErrorExtended(LOG_TAG, "Failed to check if termux prefix directory is empty:\n" + error.getErrorLogString());
return false;
}
/**
* Get a markdown {@link String} for stat output for various Termux app files paths.
*