2015-10-25 14:27:32 +00:00
package com.termux.app ;
import android.app.Activity ;
import android.app.AlertDialog ;
import android.app.ProgressDialog ;
import android.content.Context ;
2015-12-23 00:43:41 +00:00
import android.os.Environment ;
2016-07-31 20:28:17 +00:00
import android.os.UserManager ;
2015-10-25 14:27:32 +00:00
import android.system.Os ;
import android.util.Pair ;
2015-11-12 23:10:21 +00:00
import android.view.WindowManager ;
2015-10-25 14:27:32 +00:00
import com.termux.R ;
2021-04-07 06:31:30 +00:00
import com.termux.shared.file.FileUtils ;
import com.termux.shared.logger.Logger ;
import com.termux.shared.termux.TermuxConstants ;
2015-10-25 14:27:32 +00:00
2015-12-12 02:02:06 +00:00
import java.io.BufferedReader ;
2019-10-13 21:13:06 +00:00
import java.io.ByteArrayInputStream ;
2015-12-12 02:02:06 +00:00
import java.io.File ;
import java.io.FileOutputStream ;
import java.io.InputStreamReader ;
import java.util.ArrayList ;
import java.util.List ;
import java.util.zip.ZipEntry ;
import java.util.zip.ZipInputStream ;
2015-10-25 14:27:32 +00:00
/ * *
* Install the Termux bootstrap packages if necessary by following the below steps :
2016-06-27 23:03:03 +00:00
* < p / >
2015-10-25 14:27:32 +00:00
* ( 1 ) If $PREFIX already exist , assume that it is correct and be done . Note that this relies on that we do not create a
2021-03-23 23:55:10 +00:00
* broken $PREFIX directory below .
2016-06-27 23:03:03 +00:00
* < p / >
2015-10-25 14:27:32 +00:00
* ( 2 ) A progress dialog is shown with " Installing... " message and a spinner .
2016-06-27 23:03:03 +00:00
* < p / >
2021-04-06 01:00:21 +00:00
* ( 3 ) A staging directory , $STAGING_PREFIX , is cleared if left over from broken installation below .
2016-06-27 23:03:03 +00:00
* < p / >
2019-10-13 21:13:06 +00:00
* ( 4 ) The zip file is loaded from a shared library .
2016-06-27 23:03:03 +00:00
* < p / >
2015-10-25 14:27:32 +00:00
* ( 5 ) The zip , containing entries relative to the $PREFIX , is is downloaded and extracted by a zip input stream
2017-09-13 15:16:08 +00:00
* continuously encountering zip file entries :
2016-06-27 23:03:03 +00:00
* < p / >
2015-10-25 14:27:32 +00:00
* ( 5 . 1 ) If the zip entry encountered is SYMLINKS . txt , go through it and remember all symlinks to setup .
2016-06-27 23:03:03 +00:00
* < p / >
2015-10-25 14:27:32 +00:00
* ( 5 . 2 ) For every other zip entry , extract it into $STAGING_PREFIX and set execute permissions if necessary .
* /
final class TermuxInstaller {
2021-03-13 11:49:29 +00:00
private static final String LOG_TAG = " TermuxInstaller " ;
2021-04-06 01:00:21 +00:00
/** Performs bootstrap setup if necessary. */
static void setupBootstrapIfNeeded ( final Activity activity , final Runnable whenDone ) {
2016-06-27 23:03:03 +00:00
// Termux can only be run as the primary user (device owner) since only that
// account has the expected file system paths. Verify that:
2016-07-31 20:28:17 +00:00
UserManager um = ( UserManager ) activity . getSystemService ( Context . USER_SERVICE ) ;
2016-06-27 23:03:03 +00:00
boolean isPrimaryUser = um . getSerialNumberForUser ( android . os . Process . myUserHandle ( ) ) = = 0 ;
if ( ! isPrimaryUser ) {
2021-03-23 23:55:10 +00:00
String bootstrapErrorMessage = activity . getString ( R . string . bootstrap_error_not_primary_user_message , TermuxConstants . TERMUX_PREFIX_DIR_PATH ) ;
Logger . logError ( LOG_TAG , bootstrapErrorMessage ) ;
new AlertDialog . Builder ( activity ) . setTitle ( R . string . bootstrap_error_title ) . setMessage ( bootstrapErrorMessage )
2018-09-28 22:49:05 +00:00
. setOnDismissListener ( dialog - > System . exit ( 0 ) ) . setPositiveButton ( android . R . string . ok , null ) . show ( ) ;
2016-06-27 23:03:03 +00:00
return ;
}
2015-10-25 14:27:32 +00:00
2021-03-11 15:11:59 +00:00
final File PREFIX_FILE = TermuxConstants . TERMUX_PREFIX_DIR ;
2016-06-27 23:03:03 +00:00
if ( PREFIX_FILE . isDirectory ( ) ) {
whenDone . run ( ) ;
return ;
}
2015-10-25 14:27:32 +00:00
2016-06-27 23:03:03 +00:00
final ProgressDialog progress = ProgressDialog . show ( activity , null , activity . getString ( R . string . bootstrap_installer_body ) , true , false ) ;
new Thread ( ) {
@Override
public void run ( ) {
try {
2021-04-06 01:00:21 +00:00
Logger . logInfo ( LOG_TAG , " Installing " + TermuxConstants . TERMUX_APP_NAME + " bootstrap packages. " ) ;
String errmsg ;
2021-03-11 15:11:59 +00:00
final String STAGING_PREFIX_PATH = TermuxConstants . TERMUX_STAGING_PREFIX_DIR_PATH ;
2016-06-27 23:03:03 +00:00
final File STAGING_PREFIX_FILE = new File ( STAGING_PREFIX_PATH ) ;
2015-10-25 14:27:32 +00:00
2021-04-06 01:00:21 +00:00
errmsg = FileUtils . clearDirectory ( activity , " prefix staging directory " , STAGING_PREFIX_PATH ) ;
if ( errmsg ! = null ) {
throw new RuntimeException ( errmsg ) ;
2016-06-27 23:03:03 +00:00
}
2015-10-25 14:27:32 +00:00
2021-03-23 23:55:10 +00:00
Logger . logInfo ( LOG_TAG , " Extracting bootstrap zip to prefix staging directory \" " + TermuxConstants . TERMUX_STAGING_PREFIX_DIR_PATH + " \" . " ) ;
2016-06-27 23:03:03 +00:00
final byte [ ] buffer = new byte [ 8096 ] ;
final List < Pair < String , String > > symlinks = new ArrayList < > ( 50 ) ;
2015-10-25 14:27:32 +00:00
2019-10-13 21:13:06 +00:00
final byte [ ] zipBytes = loadZipBytes ( ) ;
try ( ZipInputStream zipInput = new ZipInputStream ( new ByteArrayInputStream ( zipBytes ) ) ) {
2016-06-27 23:03:03 +00:00
ZipEntry zipEntry ;
while ( ( zipEntry = zipInput . getNextEntry ( ) ) ! = null ) {
if ( zipEntry . getName ( ) . equals ( " SYMLINKS.txt " ) ) {
BufferedReader symlinksReader = new BufferedReader ( new InputStreamReader ( zipInput ) ) ;
String line ;
while ( ( line = symlinksReader . readLine ( ) ) ! = null ) {
String [ ] parts = line . split ( " ← " ) ;
if ( parts . length ! = 2 )
throw new RuntimeException ( " Malformed symlink line: " + line ) ;
String oldPath = parts [ 0 ] ;
String newPath = STAGING_PREFIX_PATH + " / " + parts [ 1 ] ;
symlinks . add ( Pair . create ( oldPath , newPath ) ) ;
2018-11-17 21:50:27 +00:00
2021-04-06 01:00:21 +00:00
ensureDirectoryExists ( activity , new File ( newPath ) . getParentFile ( ) ) ;
2016-06-27 23:03:03 +00:00
}
} else {
String zipEntryName = zipEntry . getName ( ) ;
File targetFile = new File ( STAGING_PREFIX_PATH , zipEntryName ) ;
2018-11-17 21:50:27 +00:00
boolean isDirectory = zipEntry . isDirectory ( ) ;
2021-04-06 01:00:21 +00:00
ensureDirectoryExists ( activity , isDirectory ? targetFile : targetFile . getParentFile ( ) ) ;
2018-11-17 21:50:27 +00:00
if ( ! isDirectory ) {
2016-06-27 23:03:03 +00:00
try ( FileOutputStream outStream = new FileOutputStream ( targetFile ) ) {
int readBytes ;
while ( ( readBytes = zipInput . read ( buffer ) ) ! = - 1 )
outStream . write ( buffer , 0 , readBytes ) ;
}
if ( zipEntryName . startsWith ( " bin/ " ) | | zipEntryName . startsWith ( " libexec " ) | | zipEntryName . startsWith ( " lib/apt/methods " ) ) {
//noinspection OctalInteger
Os . chmod ( targetFile . getAbsolutePath ( ) , 0700 ) ;
}
}
}
}
}
2015-10-25 14:27:32 +00:00
2016-06-27 23:03:03 +00:00
if ( symlinks . isEmpty ( ) )
throw new RuntimeException ( " No SYMLINKS.txt encountered " ) ;
for ( Pair < String , String > symlink : symlinks ) {
Os . symlink ( symlink . first , symlink . second ) ;
}
2015-10-25 14:27:32 +00:00
2021-03-23 23:55:10 +00:00
Logger . logInfo ( LOG_TAG , " Moving prefix staging to prefix directory. " ) ;
2016-06-27 23:03:03 +00:00
if ( ! STAGING_PREFIX_FILE . renameTo ( PREFIX_FILE ) ) {
2021-03-23 23:55:10 +00:00
throw new RuntimeException ( " Moving prefix staging to prefix directory failed " ) ;
2016-06-27 23:03:03 +00:00
}
2015-10-25 14:27:32 +00:00
2021-03-23 23:55:10 +00:00
Logger . logInfo ( LOG_TAG , " Bootstrap packages installed successfully. " ) ;
2018-09-28 22:49:05 +00:00
activity . runOnUiThread ( whenDone ) ;
2016-06-27 23:03:03 +00:00
} catch ( final Exception e ) {
2021-03-13 11:49:29 +00:00
Logger . logStackTraceWithMessage ( LOG_TAG , " Bootstrap error " , e ) ;
2018-09-28 22:49:05 +00:00
activity . runOnUiThread ( ( ) - > {
try {
new AlertDialog . Builder ( activity ) . setTitle ( R . string . bootstrap_error_title ) . setMessage ( R . string . bootstrap_error_body )
. setNegativeButton ( R . string . bootstrap_error_abort , ( dialog , which ) - > {
dialog . dismiss ( ) ;
activity . finish ( ) ;
} ) . setPositiveButton ( R . string . bootstrap_error_try_again , ( dialog , which ) - > {
2021-03-23 23:55:10 +00:00
dialog . dismiss ( ) ;
2021-04-06 01:00:21 +00:00
TermuxInstaller . setupBootstrapIfNeeded ( activity , whenDone ) ;
2021-03-23 23:55:10 +00:00
} ) . show ( ) ;
2018-09-28 22:49:05 +00:00
} catch ( WindowManager . BadTokenException e1 ) {
// Activity already dismissed - ignore.
2016-06-27 23:03:03 +00:00
}
} ) ;
} finally {
2018-09-28 22:49:05 +00:00
activity . runOnUiThread ( ( ) - > {
try {
progress . dismiss ( ) ;
} catch ( RuntimeException e ) {
// Activity already dismissed - ignore.
2016-06-27 23:03:03 +00:00
}
} ) ;
}
}
} . start ( ) ;
}
2015-10-25 14:27:32 +00:00
2018-11-17 21:50:27 +00:00
static void setupStorageSymlinks ( final Context context ) {
2016-06-27 23:03:03 +00:00
final String LOG_TAG = " termux-storage " ;
2021-03-23 23:55:10 +00:00
Logger . logInfo ( LOG_TAG , " Setting up storage symlinks. " ) ;
2016-06-27 23:03:03 +00:00
new Thread ( ) {
public void run ( ) {
try {
2021-04-06 01:00:21 +00:00
String errmsg ;
2021-03-11 15:11:59 +00:00
File storageDir = TermuxConstants . TERMUX_STORAGE_HOME_DIR ;
2015-12-12 02:02:06 +00:00
2021-04-06 01:00:21 +00:00
errmsg = FileUtils . clearDirectory ( context , " ~/storage " , storageDir . getAbsolutePath ( ) ) ;
if ( errmsg ! = null ) {
Logger . logErrorAndShowToast ( context , LOG_TAG , errmsg ) ;
2016-06-27 23:03:03 +00:00
return ;
}
2015-12-23 00:43:41 +00:00
2021-03-23 23:55:10 +00:00
Logger . logInfo ( LOG_TAG , " Setting up storage symlinks at ~/storage/shared, ~/storage/downloads, ~/storage/dcim, ~/storage/pictures, ~/storage/music and ~/storage/movies for directories in \" " + Environment . getExternalStorageDirectory ( ) . getAbsolutePath ( ) + " \" . " ) ;
2016-06-27 23:03:03 +00:00
File sharedDir = Environment . getExternalStorageDirectory ( ) ;
Os . symlink ( sharedDir . getAbsolutePath ( ) , new File ( storageDir , " shared " ) . getAbsolutePath ( ) ) ;
2015-12-23 00:43:41 +00:00
2016-06-27 23:03:03 +00:00
File downloadsDir = Environment . getExternalStoragePublicDirectory ( Environment . DIRECTORY_DOWNLOADS ) ;
Os . symlink ( downloadsDir . getAbsolutePath ( ) , new File ( storageDir , " downloads " ) . getAbsolutePath ( ) ) ;
2015-12-23 00:43:41 +00:00
2016-06-27 23:03:03 +00:00
File dcimDir = Environment . getExternalStoragePublicDirectory ( Environment . DIRECTORY_DCIM ) ;
Os . symlink ( dcimDir . getAbsolutePath ( ) , new File ( storageDir , " dcim " ) . getAbsolutePath ( ) ) ;
2015-12-23 00:43:41 +00:00
2016-06-27 23:03:03 +00:00
File picturesDir = Environment . getExternalStoragePublicDirectory ( Environment . DIRECTORY_PICTURES ) ;
Os . symlink ( picturesDir . getAbsolutePath ( ) , new File ( storageDir , " pictures " ) . getAbsolutePath ( ) ) ;
2015-12-23 00:43:41 +00:00
2016-06-27 23:03:03 +00:00
File musicDir = Environment . getExternalStoragePublicDirectory ( Environment . DIRECTORY_MUSIC ) ;
Os . symlink ( musicDir . getAbsolutePath ( ) , new File ( storageDir , " music " ) . getAbsolutePath ( ) ) ;
2015-12-23 00:43:41 +00:00
2016-06-27 23:03:03 +00:00
File moviesDir = Environment . getExternalStoragePublicDirectory ( Environment . DIRECTORY_MOVIES ) ;
Os . symlink ( moviesDir . getAbsolutePath ( ) , new File ( storageDir , " movies " ) . getAbsolutePath ( ) ) ;
2015-12-23 00:43:41 +00:00
2016-06-27 23:03:03 +00:00
final File [ ] dirs = context . getExternalFilesDirs ( null ) ;
2016-12-04 00:04:19 +00:00
if ( dirs ! = null & & dirs . length > 1 ) {
for ( int i = 1 ; i < dirs . length ; i + + ) {
File dir = dirs [ i ] ;
if ( dir = = null ) continue ;
String symlinkName = " external- " + i ;
2021-03-23 23:55:10 +00:00
Logger . logInfo ( LOG_TAG , " Setting up storage symlinks at ~/storage/ " + symlinkName + " for \" " + dir . getAbsolutePath ( ) + " \" . " ) ;
2016-12-04 00:04:19 +00:00
Os . symlink ( dir . getAbsolutePath ( ) , new File ( storageDir , symlinkName ) . getAbsolutePath ( ) ) ;
}
2016-06-27 23:03:03 +00:00
}
2021-03-23 23:55:10 +00:00
Logger . logInfo ( LOG_TAG , " Storage symlinks created successfully. " ) ;
2016-06-27 23:03:03 +00:00
} catch ( Exception e ) {
2021-03-13 11:49:29 +00:00
Logger . logStackTraceWithMessage ( LOG_TAG , " Error setting up link " , e ) ;
2016-06-27 23:03:03 +00:00
}
}
} . start ( ) ;
}
2015-12-12 02:02:06 +00:00
2021-04-06 01:00:21 +00:00
private static void ensureDirectoryExists ( Context context , File directory ) {
String errmsg ;
errmsg = FileUtils . createDirectoryFile ( context , directory . getAbsolutePath ( ) ) ;
if ( errmsg ! = null ) {
throw new RuntimeException ( errmsg ) ;
}
}
public static byte [ ] loadZipBytes ( ) {
// Only load the shared library when necessary to save memory usage.
System . loadLibrary ( " termux-bootstrap " ) ;
return getZip ( ) ;
}
public static native byte [ ] getZip ( ) ;
2015-10-25 14:27:32 +00:00
}