diff --git a/app/src/main/java/com/termux/app/TermuxInstaller.java b/app/src/main/java/com/termux/app/TermuxInstaller.java index b8f6def5..763d1670 100644 --- a/app/src/main/java/com/termux/app/TermuxInstaller.java +++ b/app/src/main/java/com/termux/app/TermuxInstaller.java @@ -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: *

@@ -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> 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) { diff --git a/termux-shared/src/main/java/com/termux/shared/file/TermuxFileUtils.java b/termux-shared/src/main/java/com/termux/shared/file/TermuxFileUtils.java index 67d5e4e8..61e60105 100644 --- a/termux-shared/src/main/java/com/termux/shared/file/TermuxFileUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/file/TermuxFileUtils.java @@ -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. *