mirror of
https://github.com/fankes/termux-app.git
synced 2025-09-06 02:35:19 +08:00
Changed/Fixed: Ensure bootstrap installation creates prefix and prefix staging directory before extraction
We manually create the parent directories first so that bootstrap failures are detected early on instead of some sub directory during extraction. Also fixed issue where `TermuxFileUtils.isTermuxFilesDirectoryAccessible()` would not check if a directory file actually existed at TERMUX_FILES_DIR_PATH and may set permissions for a non-directory file at the path. The `TermuxInstaller` was testing if `TERMUX_PREFIX_DIR_PATH` existed later on so check wasn't necessary but function may be called from elsewhere too. Also removed legacy `PREFIX_FILE*` and `STAGING_PREFIX_FILE*` local constants and use the ones provided by `TermuxConstants` directly.
This commit is contained in:
@@ -31,6 +31,11 @@ import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import static com.termux.shared.termux.TermuxConstants.TERMUX_PREFIX_DIR;
|
||||
import static com.termux.shared.termux.TermuxConstants.TERMUX_PREFIX_DIR_PATH;
|
||||
import static com.termux.shared.termux.TermuxConstants.TERMUX_STAGING_PREFIX_DIR;
|
||||
import static com.termux.shared.termux.TermuxConstants.TERMUX_STAGING_PREFIX_DIR_PATH;
|
||||
|
||||
/**
|
||||
* Install the Termux bootstrap packages if necessary by following the below steps:
|
||||
* <p/>
|
||||
@@ -67,7 +72,7 @@ final class TermuxInstaller {
|
||||
// Termux can only be run as the primary user (device owner) since only that
|
||||
// account has the expected file system paths. Verify that:
|
||||
if (!PackageUtils.isCurrentUserThePrimaryUser(activity)) {
|
||||
bootstrapErrorMessage = activity.getString(R.string.bootstrap_error_not_primary_user_message, MarkdownUtils.getMarkdownCodeForString(TermuxConstants.TERMUX_PREFIX_DIR_PATH, false));
|
||||
bootstrapErrorMessage = activity.getString(R.string.bootstrap_error_not_primary_user_message, MarkdownUtils.getMarkdownCodeForString(TERMUX_PREFIX_DIR_PATH, false));
|
||||
Logger.logError(LOG_TAG, "isFilesDirectoryAccessible: " + isFilesDirectoryAccessible);
|
||||
Logger.logError(LOG_TAG, bootstrapErrorMessage);
|
||||
sendBootstrapCrashReportNotification(activity, bootstrapErrorMessage);
|
||||
@@ -87,21 +92,18 @@ final class TermuxInstaller {
|
||||
return;
|
||||
}
|
||||
|
||||
final String PREFIX_FILE_PATH = TermuxConstants.TERMUX_PREFIX_DIR_PATH;
|
||||
final File PREFIX_FILE = TermuxConstants.TERMUX_PREFIX_DIR;
|
||||
|
||||
// If prefix directory exists, even if its a symlink to a valid directory and symlink is not broken/dangling
|
||||
if (FileUtils.directoryFileExists(PREFIX_FILE_PATH, true)) {
|
||||
File[] PREFIX_FILE_LIST = PREFIX_FILE.listFiles();
|
||||
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 prefix directory \"" + PREFIX_FILE_PATH + "\" exists but is empty or only contains the tmp directory.");
|
||||
Logger.logInfo(LOG_TAG, "The termux prefix directory \"" + TERMUX_PREFIX_DIR_PATH + "\" exists but is empty or only contains the tmp directory.");
|
||||
} else {
|
||||
whenDone.run();
|
||||
return;
|
||||
}
|
||||
} else if (FileUtils.fileExists(PREFIX_FILE_PATH, false)) {
|
||||
Logger.logInfo(LOG_TAG, "The prefix directory \"" + PREFIX_FILE_PATH + "\" does not exist but another file exists at its destination.");
|
||||
} else if (FileUtils.fileExists(TERMUX_PREFIX_DIR_PATH, false)) {
|
||||
Logger.logInfo(LOG_TAG, "The termux prefix directory \"" + TERMUX_PREFIX_DIR_PATH + "\" does not exist but another file exists at its destination.");
|
||||
}
|
||||
|
||||
final ProgressDialog progress = ProgressDialog.show(activity, null, activity.getString(R.string.bootstrap_installer_body), true, false);
|
||||
@@ -113,24 +115,35 @@ final class TermuxInstaller {
|
||||
|
||||
Error error;
|
||||
|
||||
final String STAGING_PREFIX_PATH = TermuxConstants.TERMUX_STAGING_PREFIX_DIR_PATH;
|
||||
final File STAGING_PREFIX_FILE = new File(STAGING_PREFIX_PATH);
|
||||
|
||||
// Delete prefix staging directory or any file at its destination
|
||||
error = FileUtils.deleteFile("prefix staging directory", STAGING_PREFIX_PATH, true);
|
||||
error = FileUtils.deleteFile("termux prefix staging directory", TERMUX_STAGING_PREFIX_DIR_PATH, true);
|
||||
if (error != null) {
|
||||
showBootstrapErrorDialog(activity, PREFIX_FILE_PATH, whenDone, Error.getErrorMarkdownString(error));
|
||||
showBootstrapErrorDialog(activity, whenDone, Error.getErrorMarkdownString(error));
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete prefix directory or any file at its destination
|
||||
error = FileUtils.deleteFile("prefix directory", PREFIX_FILE_PATH, true);
|
||||
error = FileUtils.deleteFile("termux prefix directory", TERMUX_PREFIX_DIR_PATH, true);
|
||||
if (error != null) {
|
||||
showBootstrapErrorDialog(activity, PREFIX_FILE_PATH, whenDone, Error.getErrorMarkdownString(error));
|
||||
showBootstrapErrorDialog(activity, whenDone, Error.getErrorMarkdownString(error));
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.logInfo(LOG_TAG, "Extracting bootstrap zip to prefix staging directory \"" + STAGING_PREFIX_PATH + "\".");
|
||||
// Create prefix staging directory if it does not already exist and set required permissions
|
||||
error = TermuxFileUtils.isTermuxPrefixStagingDirectoryAccessible(true, true);
|
||||
if (error != null) {
|
||||
showBootstrapErrorDialog(activity, whenDone, Error.getErrorMarkdownString(error));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create prefix directory if it does not already exist and set required permissions
|
||||
error = TermuxFileUtils.isTermuxPrefixDirectoryAccessible(true, true);
|
||||
if (error != null) {
|
||||
showBootstrapErrorDialog(activity, whenDone, Error.getErrorMarkdownString(error));
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.logInfo(LOG_TAG, "Extracting bootstrap zip to prefix staging directory \"" + TERMUX_STAGING_PREFIX_DIR_PATH + "\".");
|
||||
|
||||
final byte[] buffer = new byte[8096];
|
||||
final List<Pair<String, String>> symlinks = new ArrayList<>(50);
|
||||
@@ -147,23 +160,23 @@ final class TermuxInstaller {
|
||||
if (parts.length != 2)
|
||||
throw new RuntimeException("Malformed symlink line: " + line);
|
||||
String oldPath = parts[0];
|
||||
String newPath = STAGING_PREFIX_PATH + "/" + parts[1];
|
||||
String newPath = TERMUX_STAGING_PREFIX_DIR_PATH + "/" + parts[1];
|
||||
symlinks.add(Pair.create(oldPath, newPath));
|
||||
|
||||
error = ensureDirectoryExists(new File(newPath).getParentFile());
|
||||
if (error != null) {
|
||||
showBootstrapErrorDialog(activity, PREFIX_FILE_PATH, whenDone, Error.getErrorMarkdownString(error));
|
||||
showBootstrapErrorDialog(activity, whenDone, Error.getErrorMarkdownString(error));
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String zipEntryName = zipEntry.getName();
|
||||
File targetFile = new File(STAGING_PREFIX_PATH, zipEntryName);
|
||||
File targetFile = new File(TERMUX_STAGING_PREFIX_DIR_PATH, zipEntryName);
|
||||
boolean isDirectory = zipEntry.isDirectory();
|
||||
|
||||
error = ensureDirectoryExists(isDirectory ? targetFile : targetFile.getParentFile());
|
||||
if (error != null) {
|
||||
showBootstrapErrorDialog(activity, PREFIX_FILE_PATH, whenDone, Error.getErrorMarkdownString(error));
|
||||
showBootstrapErrorDialog(activity, whenDone, Error.getErrorMarkdownString(error));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -189,17 +202,17 @@ final class TermuxInstaller {
|
||||
Os.symlink(symlink.first, symlink.second);
|
||||
}
|
||||
|
||||
Logger.logInfo(LOG_TAG, "Moving prefix staging to prefix directory.");
|
||||
Logger.logInfo(LOG_TAG, "Moving termux prefix staging to prefix directory.");
|
||||
|
||||
if (!STAGING_PREFIX_FILE.renameTo(PREFIX_FILE)) {
|
||||
throw new RuntimeException("Moving prefix staging to prefix directory failed");
|
||||
if (!TERMUX_STAGING_PREFIX_DIR.renameTo(TERMUX_PREFIX_DIR)) {
|
||||
throw new RuntimeException("Moving termux prefix staging to prefix directory failed");
|
||||
}
|
||||
|
||||
Logger.logInfo(LOG_TAG, "Bootstrap packages installed successfully.");
|
||||
activity.runOnUiThread(whenDone);
|
||||
|
||||
} catch (final Exception e) {
|
||||
showBootstrapErrorDialog(activity, PREFIX_FILE_PATH, whenDone, Logger.getStackTracesMarkdownString(null, Logger.getStackTracesStringArray(e)));
|
||||
showBootstrapErrorDialog(activity, whenDone, Logger.getStackTracesMarkdownString(null, Logger.getStackTracesStringArray(e)));
|
||||
|
||||
} finally {
|
||||
activity.runOnUiThread(() -> {
|
||||
@@ -214,7 +227,7 @@ final class TermuxInstaller {
|
||||
}.start();
|
||||
}
|
||||
|
||||
public static void showBootstrapErrorDialog(Activity activity, String PREFIX_FILE_PATH, Runnable whenDone, String message) {
|
||||
public static void showBootstrapErrorDialog(Activity activity, Runnable whenDone, String message) {
|
||||
Logger.logErrorExtended(LOG_TAG, "Bootstrap Error:\n" + message);
|
||||
|
||||
// Send a notification with the exception so that the user knows why bootstrap setup failed
|
||||
@@ -229,7 +242,7 @@ final class TermuxInstaller {
|
||||
})
|
||||
.setPositiveButton(R.string.bootstrap_error_try_again, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
FileUtils.deleteFile("prefix directory", PREFIX_FILE_PATH, true);
|
||||
FileUtils.deleteFile("termux prefix directory", TERMUX_PREFIX_DIR_PATH, true);
|
||||
TermuxInstaller.setupBootstrapIfNeeded(activity, whenDone);
|
||||
}).show();
|
||||
} catch (WindowManager.BadTokenException e1) {
|
||||
|
@@ -9,6 +9,7 @@ import com.termux.shared.logger.Logger;
|
||||
import com.termux.shared.markdown.MarkdownUtils;
|
||||
import com.termux.shared.models.ExecutionCommand;
|
||||
import com.termux.shared.models.errors.Error;
|
||||
import com.termux.shared.models.errors.FileUtilsErrno;
|
||||
import com.termux.shared.shell.TermuxShellEnvironmentClient;
|
||||
import com.termux.shared.shell.TermuxTask;
|
||||
import com.termux.shared.termux.AndroidUtils;
|
||||
@@ -134,7 +135,9 @@ public class TermuxFileUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the existence and permissions of {@link TermuxConstants#TERMUX_FILES_DIR_PATH}.
|
||||
* Validate if {@link TermuxConstants#TERMUX_FILES_DIR_PATH} exists and has
|
||||
* {@link FileUtils#APP_WORKING_DIRECTORY_PERMISSIONS} permissions.
|
||||
*
|
||||
* This is required because binaries compiled for termux are hard coded with
|
||||
* {@link TermuxConstants#TERMUX_PREFIX_DIR_PATH} and the path must be accessible.
|
||||
*
|
||||
@@ -216,14 +219,57 @@ public class TermuxFileUtils {
|
||||
if (createDirectoryIfMissing)
|
||||
context.getFilesDir();
|
||||
|
||||
if (!FileUtils.directoryFileExists(TermuxConstants.TERMUX_FILES_DIR_PATH, true))
|
||||
return FileUtilsErrno.ERRNO_FILE_NOT_FOUND_AT_PATH.getError("termux files directory", TermuxConstants.TERMUX_FILES_DIR_PATH);
|
||||
|
||||
if (setMissingPermissions)
|
||||
FileUtils.setMissingFilePermissions("Termux files directory", TermuxConstants.TERMUX_FILES_DIR_PATH,
|
||||
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,
|
||||
return FileUtils.checkMissingFilePermissions("termux files directory", TermuxConstants.TERMUX_FILES_DIR_PATH,
|
||||
FileUtils.APP_WORKING_DIRECTORY_PERMISSIONS, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if {@link TermuxConstants#TERMUX_PREFIX_DIR_PATH} exists and has
|
||||
* {@link FileUtils#APP_WORKING_DIRECTORY_PERMISSIONS} permissions.
|
||||
* .
|
||||
*
|
||||
* The {@link TermuxConstants#TERMUX_PREFIX_DIR_PATH} directory would not exist if termux has
|
||||
* not been installed or the bootstrap setup has not been run or if it was deleted by the user.
|
||||
*
|
||||
* @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 isTermuxPrefixDirectoryAccessible(boolean createDirectoryIfMissing, boolean setMissingPermissions) {
|
||||
return FileUtils.validateDirectoryFileExistenceAndPermissions("termux prefix directory", TermuxConstants.TERMUX_PREFIX_DIR_PATH,
|
||||
null, createDirectoryIfMissing,
|
||||
FileUtils.APP_WORKING_DIRECTORY_PERMISSIONS, setMissingPermissions, true,
|
||||
false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if {@link TermuxConstants#TERMUX_STAGING_PREFIX_DIR_PATH} exists and has
|
||||
* {@link FileUtils#APP_WORKING_DIRECTORY_PERMISSIONS} permissions.
|
||||
*
|
||||
* @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 isTermuxPrefixStagingDirectoryAccessible(boolean createDirectoryIfMissing, boolean setMissingPermissions) {
|
||||
return FileUtils.validateDirectoryFileExistenceAndPermissions("termux prefix staging directory", TermuxConstants.TERMUX_STAGING_PREFIX_DIR_PATH,
|
||||
null, createDirectoryIfMissing,
|
||||
FileUtils.APP_WORKING_DIRECTORY_PERMISSIONS, setMissingPermissions, true,
|
||||
false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a markdown {@link String} for stat output for various Termux app files paths.
|
||||
*
|
||||
|
Reference in New Issue
Block a user