diff --git a/.gitmodules b/.gitmodules index 46f629d..1eff271 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "app/src/c/libressl"] path = dmg2img/src/c/libressl url = https://github.com/libressl-portable/portable.git +[submodule "termux-packages/src/c/termux-packages"] + path = termux-packages/src/c/termux-packages + url = https://github.com/Depau/termux-packages.git diff --git a/app/build.gradle b/app/build.gradle index ad7466d..0debf7b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ android { compileSdkVersion 28 defaultConfig { applicationId "eu.depau.etchdroid" - minSdkVersion 19 + minSdkVersion 21 targetSdkVersion 28 versionCode 1 versionName "1.0" @@ -37,7 +37,7 @@ dependencies { // implementation 'com.github.mjdev:libaums:0.5.5' implementation project(':libaums') implementation project(':dmg2img') - + implementation project(':termux-packages') testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' diff --git a/build.gradle b/build.gradle index 2ac0e46..b0f22a0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.2.61' ext.kotlin_version = '1.2.50' repositories { google() diff --git a/settings.gradle b/settings.gradle index 77a9903..8f5cfc8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -include ':app', ':libaums', ':dmg2img' +include ':app', ':libaums', ':dmg2img', ':termux-packages' project(':libaums').projectDir = new File('libaums/libaums') \ No newline at end of file diff --git a/termux-packages/CMakeLists.txt b/termux-packages/CMakeLists.txt new file mode 100644 index 0000000..d8c36ed --- /dev/null +++ b/termux-packages/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.4) + +set(ASSETS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/main/assets/termux/${ANDROID_ABI}") + +add_subdirectory(src/c/termux-wrapper) + +# Dummy target for Gradle to pick up termux_wrapper +add_executable(termux_packages + ${CMAKE_CURRENT_SOURCE_DIR}/src/c/dummy.c) +add_dependencies(termux_packages termux_build) \ No newline at end of file diff --git a/termux-packages/build.gradle b/termux-packages/build.gradle new file mode 100644 index 0000000..2390fb3 --- /dev/null +++ b/termux-packages/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 28 + defaultConfig { + minSdkVersion 21 + externalNativeBuild { + cmake { + targets "termux_packages" + arguments "-DAPP_PKGNAME=eu.depau.etchdroid", "-DTERMUX_PACKAGES=parted" + } + } + } + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + sourceSets { + main { + assets.srcDirs('src/main/assets') + } + } +} +repositories { + mavenCentral() +} +dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + compile "org.kamranzafar:jtar:2.3" + compile "commons-io:commons-io:2.6" +} \ No newline at end of file diff --git a/termux-packages/src/c/dummy.c b/termux-packages/src/c/dummy.c new file mode 100644 index 0000000..237c8ce --- /dev/null +++ b/termux-packages/src/c/dummy.c @@ -0,0 +1 @@ +int main() {} diff --git a/termux-packages/src/c/termux-packages b/termux-packages/src/c/termux-packages new file mode 160000 index 0000000..e45ad0d --- /dev/null +++ b/termux-packages/src/c/termux-packages @@ -0,0 +1 @@ +Subproject commit e45ad0d7e998fc707dbf4e0f578482b32218e418 diff --git a/termux-packages/src/c/termux-wrapper/CMakeLists.txt b/termux-packages/src/c/termux-wrapper/CMakeLists.txt new file mode 100644 index 0000000..e5bcd5f --- /dev/null +++ b/termux-packages/src/c/termux-wrapper/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.4) + +# NDK is assumed to be placed inside SDK directory +# Should be implemented better +get_filename_component(ANDROID_SDK ${ANDROID_NDK} DIRECTORY) + +if(${ANDROID_ABI} STREQUAL "x86") + set(TERMUX_ARCH "i686") +elseif(${ANDROID_ABI} STREQUAL "arm64-v8a") + set(TERMUX_ARCH "aarch64") +elseif(${ANDROID_ABI} STREQUAL "armeabi-v7a") + set(TERMUX_ARCH "arm") +else() + set(TERMUX_ARCH "${ANDROID_ABI}") +endif() + +set(TERMUX_TOPDIR "${PROJECT_BINARY_DIR}/termux-topdir") +set(TERMUX_DEBDIR "${Project_BINARY_DIR}/termux-debs") +set(TERMUX_PREFIX "/data/data/${APP_PKGNAME}/files/termux/usr") +set(TERMUX_ANDROID_HOME "/data/data/${APP_PKGNAME}/files/termux/home") + +add_custom_target(termux_build + ${CMAKE_COMMAND} -E env + ASSETS_PATH=${ASSETS_PATH} + NDK=${ANDROID_NDK} + ANDROID_HOME=${ANDROID_SDK} + TERMUX_TOPDIR=${TERMUX_TOPDIR} + TERMUX_DEBDIR=${TERMUX_DEBDIR} + TERMUX_PREFIX=${TERMUX_PREFIX} + TERMUX_ANDROID_HOME=${TERMUX_ANDROID_HOME} + TERMUX_ARCH=${TERMUX_ARCH} + ${CMAKE_CURRENT_LIST_DIR}/build-packages.sh ${TERMUX_PACKAGES} + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) diff --git a/termux-packages/src/c/termux-wrapper/build-packages.sh b/termux-packages/src/c/termux-wrapper/build-packages.sh new file mode 100755 index 0000000..6896c34 --- /dev/null +++ b/termux-packages/src/c/termux-wrapper/build-packages.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +cd ../termux-packages + +for package in "$@" +do + ./build-package.sh -a $TERMUX_ARCH $package +done + +cd ../termux-wrapper + +./install-packages.sh $TERMUX_DEBDIR $ASSETS_PATH \ No newline at end of file diff --git a/termux-packages/src/c/termux-wrapper/install-packages.sh b/termux-packages/src/c/termux-wrapper/install-packages.sh new file mode 100755 index 0000000..81f3b03 --- /dev/null +++ b/termux-packages/src/c/termux-wrapper/install-packages.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +DEBDIR="$1" +DESTDIR="$2" + +echo "Creating output tarball" + +cd "$DEBDIR" + +( + rm -f pkg_info.txt + rm -Rf root + rm -Rf tmp +) > /dev/null 2>&1 + +mkdir root + +# Extract packages +ls | sort | grep \\.deb | grep -v -- '-dev_' | while read pkg; do + mkdir tmp + cd tmp + ar -x ../$pkg + tar -xJf control.tar.xz + cat control >> ../pkg_info.txt + echo >> ../pkg_info.txt + cd ../root + tar -xJf ../tmp/data.tar.xz + cd .. + rm -Rf tmp +done + +mkdir -p $DESTDIR + +# Create info files +sha256sum pkg_info.txt > pkg_fprint.txt +cp pkg_info.txt pkg_fprint.txt root/data/data/*/files/termux +cp pkg_info.txt pkg_fprint.txt $DESTDIR + +# Cleanup unneeded files +pushd root/data/data/*/files/termux/usr > /dev/null +rm -Rf include +rm -Rf share/doc share/info share/man + +# Create output tar +cd ../.. +tar -cf $DESTDIR/packages.tar termux + +# Cleanup +popd > /dev/null +rm -Rf root pkg_info.txt pkg_fprint.txt \ No newline at end of file diff --git a/termux-packages/src/main/AndroidManifest.xml b/termux-packages/src/main/AndroidManifest.xml new file mode 100644 index 0000000..7f2802d --- /dev/null +++ b/termux-packages/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/termux-packages/src/main/java/eu/depau/termux_wrapper/TermuxWrapper.kt b/termux-packages/src/main/java/eu/depau/termux_wrapper/TermuxWrapper.kt new file mode 100644 index 0000000..7bfcc9c --- /dev/null +++ b/termux-packages/src/main/java/eu/depau/termux_wrapper/TermuxWrapper.kt @@ -0,0 +1,131 @@ +package eu.depau.termux_wrapper + +import android.content.Context +import android.os.Build +import org.apache.commons.io.FileUtils +import org.kamranzafar.jtar.TarEntry +import org.kamranzafar.jtar.TarInputStream +import java.io.* +import java.nio.file.attribute.PosixFilePermission + + +@Throws(IOException::class) +fun InputStream.readString(): String { + val baos = ByteArrayOutputStream() + val buffer = ByteArray(1024) + var length = this.read(buffer) + + while (length != -1) { + baos.write(buffer, 0, length) + length = this.read(buffer) + } + return baos.toString("UTF-8") +} + + +class TermuxWrapper(val context: Context) { + public var path: MutableList = mutableListOf( + "/usr/local/sbin", + "/usr/local/bin", + "/usr/sbin", + "/usr/bin", + "/sbin", + "/bin" + ) + + val arch: String + get() { + val abi = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + Build.SUPPORTED_ABIS[0] + else + Build.CPU_ABI + + return when { + abi.contains("armeabi-v7a") -> "armeabi-v7a" + abi.contains("x86_64") -> "x86_64" + abi.contains("x86") -> "x86" + abi.contains("arm64-v8a") -> "arm64-v8a" + else -> null!! + } + } + + val needsUpdate: Boolean + get() { + val assetManager = context.assets + val hashAssets = assetManager.open("termux/$arch/pkg_fprint.txt").readString() + val hashFilesFile = File("${context.filesDir}/termux/pkg_fprint.txt") + + if (!hashFilesFile.exists()) + return true + + val hashFiles = FileInputStream(hashFilesFile).readString() + + return hashAssets != hashFiles + } + + private fun applyPerms(tarEntry: TarEntry, file: File) { + // u 2 g 1 o 0 + // [rwx] [rwx] [rwx] + + val mode = tarEntry.header.mode + val permSet: MutableSet = mutableSetOf() + + // Get owner permissions. On Android API < 26 there's no easy way to set the others + val stepInt = (mode shr (6)) and 0xFFF8 + + // Iterate over permission bits + for (bit in listOf(1, 2, 4)) { + val bool = (stepInt and bit) > 0 + when (bit) { + 1 -> file.setExecutable(bool, true) + 2 -> file.setWritable(bool, true) + 4 -> file.setReadable(bool, true) + else -> null!! + } + } + } + + fun doUpdate() { + val oldTarDir = File("${context.filesDir}/termux") + if (oldTarDir.exists()) + FileUtils.deleteDirectory(oldTarDir) + + val destDir = context.filesDir!! + + val assetManager = context.assets + val termuxTar = TarInputStream(assetManager.open("termux/$arch/packages.tar").buffered()) + var tarEntry = termuxTar.nextEntry + + termuxTar.use { tis -> + while (tarEntry != null) { + val data = ByteArray(2048) + val destFile = File("$destDir/${tarEntry.name}") + val fos = FileOutputStream(destFile) + val dest = BufferedOutputStream(fos) + var count = tis.read(data) + + while (count != -1) { + dest.write(data, 0, count) + count = tis.read(data) + } + + dest.close() + applyPerms(tarEntry, destFile) + + tarEntry = tis.nextEntry + } + } + } + + fun getFile(path: String) = File("${context.filesDir}/termux/$path") + + fun which(cmd: String): File? { + for (p in path) { + val file = getFile("$p/$cmd") + + if (file.exists() && file.isFile && file.canExecute()) + return file + } + return null + } +} \ No newline at end of file