Compare commits

...

55 Commits

Author SHA1 Message Date
topjohnwu
427a1ca4e5 Release new canary build
[skip ci]
2025-04-29 11:54:17 -07:00
topjohnwu
22884e173a Implement reboot in Rust 2025-04-28 17:22:14 -07:00
topjohnwu
d1829308e9 Move more daemon_start code into Rust 2025-04-28 17:22:14 -07:00
topjohnwu
73840f8721 Migrate selinux.cpp to selinux.rs 2025-04-28 17:22:14 -07:00
topjohnwu
c7d1af9805 Stop using PathBuf in package.rs 2025-04-28 17:22:14 -07:00
topjohnwu
4ad26d3dfb Better path methods 2025-04-28 17:22:14 -07:00
topjohnwu
0c70b7670c Cleanup dir implementations 2025-04-28 17:22:14 -07:00
topjohnwu
f44d044095 Remove Utf8CStrBuffer 2025-04-28 17:22:14 -07:00
topjohnwu
5c1cb13472 Remove AsUtf8CStr trait 2025-04-28 17:22:14 -07:00
topjohnwu
3327fc668e Remove FsPath and FsPathMnt trait
Directly use Utf8CStr
2025-04-28 17:22:14 -07:00
topjohnwu
610945ac54 Remove open_fd macro 2025-04-28 17:22:14 -07:00
Howard Wu
ddf5474917 apt-get update before install to fix ci 2025-04-28 11:16:14 -07:00
Howard Wu
6ba1685ade Fix some seopt log 2025-04-22 03:25:21 -07:00
topjohnwu
e02b5f7868 Rename cstr_buf to cstr::buf 2025-04-22 03:21:00 -07:00
topjohnwu
ab2e5d1e7e Make FsPathBuf a trait and rename to FsPathBuilder 2025-04-22 03:21:00 -07:00
topjohnwu
f3fef7bfe4 Make FsPath a trait 2025-04-22 03:21:00 -07:00
topjohnwu
c34c7838bb Cleanup cstr implementation 2025-04-22 03:21:00 -07:00
topjohnwu
c8a16b0e0c Remove unused code 2025-04-16 17:13:03 -07:00
topjohnwu
14f9ed91a1 Remove unused methods 2025-04-15 11:35:31 -07:00
topjohnwu
7a207d4ccf Only accept UTF-8 directory entries 2025-04-15 10:26:22 -07:00
topjohnwu
92a42d901f Move most implementation into Directory 2025-04-15 10:26:22 -07:00
topjohnwu
084d89fcce Create Utf8CStrBuffer type 2025-04-15 10:26:22 -07:00
topjohnwu
55b036c071 Introduce BorrowedDirectory 2025-04-15 10:26:22 -07:00
topjohnwu
30e79310ab Make pointers NonNull after error check 2025-04-15 00:18:48 -07:00
topjohnwu
f063fa5054 Cleanup xwrap implementation 2025-04-15 00:18:48 -07:00
topjohnwu
7bd901273c Provide richer error messages
Make sure most syscall/libc calls results are mapped to OsResult
that can produce more detailed error messages.
2025-04-15 00:18:48 -07:00
topjohnwu
c1e061603b Specify ADB_SERIAL for emulator 2025-04-13 21:43:11 -07:00
topjohnwu
cb08504fe5 Update cargo dependencies 2025-04-11 14:48:16 -07:00
topjohnwu
c0a1fb77be Code cleanup 2025-04-11 14:48:01 -07:00
LoveSy
4864c1112a no pty for -c by default, and add -i to force pty 2025-04-11 13:21:10 -07:00
LoveSy
9ddeab034b Fix wrong tty pump
See #1463
2025-04-11 13:21:10 -07:00
LoveSy
c4847ed288 Move pts to rust, and avoid using thread 2025-04-11 13:21:10 -07:00
topjohnwu
b8f1523fb2 Minor code reorg
[skip ci]
2025-04-08 17:20:22 -07:00
topjohnwu
fb7fa8a6b3 Update to ONDK r29.1 2025-04-08 12:11:59 -07:00
topjohnwu
9c7d359093 Optimize and format imports
[skip ci]
2025-04-08 09:57:09 -07:00
topjohnwu
eb54bc1fd7 Cleanup unused code 2025-04-08 02:33:52 -07:00
topjohnwu
d4a0286e13 Migrate magiskinit selinux.cpp to Rust 2025-04-08 02:33:52 -07:00
Steven Xu
83e66767ff refactor: use empty navOptions 2025-04-02 09:13:32 -07:00
Steven Xu
7dc010749b feat: remove animation settings button transition 2025-04-02 09:13:32 -07:00
Steven Xu
8e8d013b1b feat: remove log overscroll 2025-04-02 09:13:32 -07:00
Steven Xu
bba0373808 feat: remove navigation transition when clicking buttons on the bottom bar 2025-04-02 09:13:32 -07:00
topjohnwu
1fa318dc8c Use Rust elf-cleaner implementation 2025-04-01 18:32:54 -07:00
cheesetosti
6edc5e2037 Update install.md
fixed grammar n stuff
2025-04-01 12:13:29 -07:00
topjohnwu
1523ed9f78 Always go through rustup proxies 2025-04-01 10:01:35 -07:00
topjohnwu
8e604d2ab8 Update cuttlefish CI 2025-03-28 00:12:31 -07:00
topjohnwu
2aba7247a9 Skip stub APK install on emulator
Reduce test flakiness
2025-03-26 13:15:12 -07:00
topjohnwu
e66fe8533e API 36 does not support wait_for_bootanim 2025-03-26 13:15:12 -07:00
vvb2060
b03fbb3917 avd_test: upgrade to android16 beta3 2025-03-26 13:15:12 -07:00
vvb2060
c2ece62e4c native: delete global 16k option
NDK 28 enable 16 KiB page size compatibility option by default, delete the global option to restore 4k alignment for 32-bit arch.
2025-03-26 13:15:12 -07:00
vvb2060
8c972dcf34 app: target sdk 36 2025-03-26 13:15:12 -07:00
topjohnwu
50af14f2a3 Move all MagiskInit entrypoints into init.rs 2025-03-24 17:26:03 -07:00
topjohnwu
e0a356b319 Introduce mount helper methods 2025-03-24 17:26:03 -07:00
topjohnwu
c09a792958 Reorganize magiskinit code 2025-03-24 17:26:03 -07:00
topjohnwu
0bbfe7f44d Fix 2SI on legacy SAR devices 2025-03-24 17:26:03 -07:00
topjohnwu
a396abf565 Minor changes
[skip ci]
2025-03-22 01:16:51 -07:00
102 changed files with 2899 additions and 2945 deletions

3
.gitattributes vendored
View File

@@ -12,7 +12,8 @@
# Denote all files that are truly binary and should not be modified.
tools/** binary
tools/rustup_wrapper/** -binary
tools/rustup-wrapper/** -binary
tools/elf-cleaner/** -binary
*.jar binary
*.exe binary
*.apk binary

View File

@@ -88,9 +88,9 @@ jobs:
version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]
type: [""]
include:
- version: "Baklava"
- version: 36
type: "google_apis"
- version: "Baklava"
- version: 36
type: "google_apis_ps16k"
steps:
@@ -177,8 +177,8 @@ jobs:
fail-fast: false
matrix:
include:
- branch: "aosp-main"
device: "aosp_cf_x86_64_phone"
- branch: "aosp-android-latest-release"
device: "aosp_cf_x86_64_only_phone"
steps:
- name: Check out

3
.gitmodules vendored
View File

@@ -25,6 +25,3 @@
[submodule "crt0"]
path = native/src/external/crt0
url = https://github.com/topjohnwu/crt0.git
[submodule "termux-elf-cleaner"]
path = tools/termux-elf-cleaner
url = https://github.com/termux/termux-elf-cleaner.git

View File

@@ -22,7 +22,7 @@ Click the icon below to download Magisk apk.
[![](https://img.shields.io/badge/Magisk-v28.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
[![](https://img.shields.io/badge/Magisk%20Beta-v28.1-blue)](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://github.com/topjohnwu/Magisk/releases/tag/canary-28103)
[![](https://img.shields.io/badge/Magisk-Canary-red)](https://github.com/topjohnwu/Magisk/releases/tag/canary-28104)
## Useful Links

View File

@@ -1,10 +1,13 @@
package com.topjohnwu.magisk.arch
import android.content.ContentResolver
import android.view.KeyEvent
import androidx.databinding.ViewDataBinding
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.navOptions
import com.topjohnwu.magisk.utils.AccessibilityUtils
abstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Binding>() {
@@ -31,7 +34,17 @@ abstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Bindin
}
}
companion object {
fun navigate(directions: NavDirections, navigation: NavController, cr: ContentResolver) {
if (AccessibilityUtils.isAnimationEnabled(cr)) {
navigation.navigate(directions)
} else {
navigation.navigate(directions, navOptions {})
}
}
}
fun NavDirections.navigate() {
navigation.navigate(this)
navigate(this, navigation, contentResolver)
}
}

View File

@@ -17,6 +17,8 @@ import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.download.DownloadEngine
import com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding
import com.topjohnwu.magisk.core.R as CoreR
import androidx.navigation.findNavController
import com.topjohnwu.magisk.arch.NavigationActivity
class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
@@ -68,7 +70,13 @@ class HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {
override fun onMenuItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_settings ->
HomeFragmentDirections.actionHomeFragmentToSettingsFragment().navigate()
activity?.let {
NavigationActivity.navigate(
HomeFragmentDirections.actionHomeFragmentToSettingsFragment(),
it.findNavController(R.id.main_nav_host),
it.contentResolver,
)
}
R.id.action_reboot -> activity?.let { RebootMenu.inflate(it).show() }
else -> return super.onOptionsItemSelected(item)
}

View File

@@ -5,6 +5,7 @@ import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.widget.HorizontalScrollView
import androidx.core.view.MenuProvider
import androidx.core.view.isVisible
import com.topjohnwu.magisk.R
@@ -12,6 +13,7 @@ import com.topjohnwu.magisk.arch.BaseFragment
import com.topjohnwu.magisk.arch.viewModel
import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding
import com.topjohnwu.magisk.ui.MainActivity
import com.topjohnwu.magisk.utils.AccessibilityUtils
import com.topjohnwu.magisk.utils.MotionRevealHelper
import rikka.recyclerview.addEdgeSpacing
import rikka.recyclerview.addItemSpacing
@@ -56,6 +58,11 @@ class LogFragment : BaseFragment<FragmentLogMd2Binding>(), MenuProvider {
addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1)
fixEdgeEffect()
}
if (!AccessibilityUtils.isAnimationEnabled(requireContext().contentResolver)) {
val scrollView = view.findViewById<HorizontalScrollView>(R.id.log_scroll_magisk)
scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER)
}
}

View File

@@ -0,0 +1,14 @@
package com.topjohnwu.magisk.utils
import android.content.ContentResolver
import android.provider.Settings
class AccessibilityUtils {
companion object {
fun isAnimationEnabled(cr: ContentResolver): Boolean {
return !(Settings.Global.getFloat(cr, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f) == 0.0f
&& Settings.Global.getFloat(cr, Settings.Global.TRANSITION_ANIMATION_SCALE, 1.0f) == 0.0f
&& Settings.Global.getFloat(cr, Settings.Global.WINDOW_ANIMATION_SCALE, 1.0f) == 0.0f)
}
}
}

View File

@@ -16,6 +16,7 @@
android:layout_height="match_parent">
<HorizontalScrollView
android:id="@+id/log_scroll_magisk"
gone="@{viewModel.loading}"
android:layout_width="match_parent"
android:layout_height="match_parent"

View File

@@ -25,6 +25,7 @@
<application
android:allowBackup="false"
android:enableOnBackInvokedCallback="false"
android:label="Magisk"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"

View File

@@ -148,27 +148,16 @@ def cmd_out(cmds: list):
def clean_elf():
if is_windows:
elf_cleaner = Path("tools", "elf-cleaner.exe")
else:
elf_cleaner = Path("native", "out", "elf-cleaner")
if not elf_cleaner.exists():
execv(
[
"gcc",
'-DPACKAGE_NAME="termux-elf-cleaner"',
'-DPACKAGE_VERSION="2.1.1"',
'-DCOPYRIGHT="Copyright (C) 2022 Termux."',
"tools/termux-elf-cleaner/elf-cleaner.cpp",
"tools/termux-elf-cleaner/arghandling.c",
"-o",
elf_cleaner,
]
)
cmds = [elf_cleaner, "--api-level", "23"]
cargo_toml = Path("tools", "elf-cleaner", "Cargo.toml")
cmds = ["run", "--release", "--manifest-path", cargo_toml]
if args.verbose == 0:
cmds.append("-q")
elif args.verbose > 1:
cmds.append("--verbose")
cmds.append("--")
cmds.extend(glob.glob("native/out/*/magisk"))
cmds.extend(glob.glob("native/out/*/magiskpolicy"))
execv(cmds)
run_cargo(cmds)
def run_ndk_build(cmds: list):
@@ -234,10 +223,9 @@ def build_cpp_src(targets: set):
def run_cargo(cmds):
ensure_paths()
env = os.environ.copy()
env["PATH"] = f'{rust_bin}{os.pathsep}{env["PATH"]}'
env["CARGO_BUILD_RUSTC"] = str(rust_bin / f"rustc{EXE_EXT}")
env["RUSTUP_TOOLCHAIN"] = str(rust_sysroot)
env["CARGO_BUILD_RUSTFLAGS"] = f"-Z threads={min(8, cpu_count)}"
return execv([cargo, *cmds], env)
return execv(["cargo", *cmds], env)
def build_rust_src(targets: set):
@@ -485,6 +473,7 @@ def cleanup():
if "native" in targets:
rm_rf(Path("native", "out"))
rm_rf(Path("tools", "elf-cleaner", "target"))
if "app" in targets:
header("* Cleaning app")
@@ -505,11 +494,10 @@ def build_all():
def clippy_cli():
args.force_out = True
os.chdir(Path("native", "src"))
cmds = ["clippy", "--no-deps", "--target", ""]
cmds = ["clippy", "--no-deps", "--target"]
for triple in build_abis.values():
cmds[3] = triple
run_cargo(cmds)
run_cargo(cmds + ["--release"])
run_cargo(cmds + [triple])
run_cargo(cmds + [triple, "--release"])
os.chdir(Path("..", ".."))
@@ -555,8 +543,8 @@ def setup_rustup():
tgt = wrapper_dir / src.name
tgt.symlink_to(f"rustup{EXE_EXT}")
# Build rustup_wrapper
wrapper_src = Path("tools", "rustup_wrapper")
# Build rustup-wrapper
wrapper_src = Path("tools", "rustup-wrapper")
cargo_toml = wrapper_src / "Cargo.toml"
cmds = ["build", "--release", f"--manifest-path={cargo_toml}"]
if args.verbose > 1:
@@ -566,7 +554,7 @@ def setup_rustup():
# Replace rustup with wrapper
wrapper = wrapper_dir / (f"rustup{EXE_EXT}")
wrapper.unlink(missing_ok=True)
cp(wrapper_src / "target" / "release" / (f"rustup_wrapper{EXE_EXT}"), wrapper)
cp(wrapper_src / "target" / "release" / (f"rustup-wrapper{EXE_EXT}"), wrapper)
wrapper.chmod(0o755)
@@ -652,8 +640,8 @@ def patch_avd_file():
def ensure_paths():
global sdk_path, ndk_root, ndk_path, ndk_build, rust_bin
global llvm_bin, cargo, gradlew, adb_path, native_gen_path
global sdk_path, ndk_root, ndk_path, ndk_build, rust_sysroot
global llvm_bin, gradlew, adb_path, native_gen_path
# Skip if already initialized
if "sdk_path" in globals():
@@ -670,11 +658,10 @@ def ensure_paths():
ndk_root = sdk_path / "ndk"
ndk_path = ndk_root / "magisk"
ndk_build = ndk_path / "ndk-build"
rust_bin = ndk_path / "toolchains" / "rust" / "bin"
rust_sysroot = ndk_path / "toolchains" / "rust"
llvm_bin = (
ndk_path / "toolchains" / "llvm" / "prebuilt" / f"{os_name}-x86_64" / "bin"
)
cargo = rust_bin / "cargo"
adb_path = sdk_path / "platform-tools" / "adb"
gradlew = Path.cwd() / "gradlew"

View File

@@ -71,8 +71,8 @@ private val Project.androidComponents
fun Project.setupCommon() {
androidBase {
compileSdkVersion(35)
buildToolsVersion = "35.0.1"
compileSdkVersion(36)
buildToolsVersion = "36.0.0"
ndkPath = "$sdkDirectory/ndk/magisk"
ndkVersion = "29.0.13113456"
@@ -302,7 +302,7 @@ fun Project.setupAppCommon() {
}
defaultConfig {
targetSdk = 35
targetSdk = 36
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt")
)

View File

@@ -40,12 +40,9 @@
### Developing Rust
The Magisk NDK package [ONDK](https://github.com/topjohnwu/ondk) (the one installed with `./build.py ndk`) bundles a complete Rust toolchain, so _building_ the Magisk project itself does not require any further configuration. However, if you'd like to work on the Rust codebase with proper support, you'd need some setup as most development tools are built around `rustup`.
First, install [rustup](https://www.rust-lang.org/tools/install), the official Rust toolchain manager. The Magisk NDK package [ONDK](https://github.com/topjohnwu/ondk) (the one installed with `./build.py ndk`) bundles a complete Rust toolchain, so _building_ the Magisk project itself does not require any further configuration.
Let's first setup `rustup` to use our custom ONDK Rust toolchain by default:
- Install [rustup](https://rustup.rs/), the official Rust toolchain manager
- Link the ONDK Rust toolchain and set it as default:
However, if you'd like to work on the Rust codebase, it'll be easier if you link ONDK's Rust toolchain in `rustup` and set it as default so several development tools and IDEs will work properly:
```bash
# Link the ONDK toolchain with the name "magisk"
@@ -54,7 +51,7 @@ rustup toolchain link magisk "$ANDROID_HOME/ndk/magisk/toolchains/rust"
rustup default magisk
```
If you plan to use VSCode, you can then install the [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) plugin and everything should be good to go. If you plan to use Jetbrain IDEs (e.g. [Rustrover](https://www.jetbrains.com/rust/), or its Rust Plugin), due to its poor support with custom toolchains, we need some additional setup:
If you plan to use VSCode, you can then install the [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) plugin and everything should be good to go. If you plan to use Jetbrain IDEs (e.g. [Rustrover](https://www.jetbrains.com/rust/), or its Rust Plugin), we need some additional setup:
- Install the official nightly toolchain and add some components. We won't actually use the nightly toolchain for anything other than tricking the IDE to cooperate; the magic happens in the wrapper we setup in the next step.

View File

@@ -76,7 +76,7 @@ As a summary, after installing Magisk in recovery **(starting from power off)**:
Before proceeding, please acknowledge that:
- Installing Magisk **WILL** trip your Knox Warranty Bit, this action is not reversible in any way.
- Installing Magisk for the first time **REQUIRES** a full data wipe (this is **NOT** counting the data wipe when unlocking bootloader). Please make a backup your data.
- Installing Magisk for the first time **REQUIRES** a full data wipe (this is **NOT** counting the data wipe when unlocking bootloader). Please make a backup of your data.
### Flashing Tools
@@ -86,7 +86,7 @@ Before proceeding, please acknowledge that:
### Requirements
To verify whether or not Magisk can be installed in your Samsung device, you first must check the OEM Lock and KnoxGuard (RMM) status, to do so boot your device in Download mode with its key combo.
To verify whether or not Magisk can be installed in your Samsung device, you first must check the OEM Lock and KnoxGuard (RMM) status. To do so, boot your device in Download mode with its key combo.
Possible OEM Lock values are the following:
- **ON (L)**: fully locked.

View File

@@ -30,5 +30,5 @@ android.nonFinalResIds=false
# Magisk
magisk.stubVersion=40
magisk.versionCode=28103
magisk.ondkVersion=r29.0
magisk.versionCode=28104
magisk.ondkVersion=r29.1

View File

@@ -1,7 +1,7 @@
[versions]
kotlin = "2.1.10"
android = "8.9.0"
ksp = "2.1.10-1.0.31"
kotlin = "2.1.20"
android = "8.9.1"
ksp = "2.1.20-1.0.31"
rikka = "1.3.0"
navigation = "2.8.4"
libsu = "6.0.0"

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

View File

@@ -19,7 +19,6 @@ LOCAL_SRC_FILES := \
core/magisk.cpp \
core/daemon.cpp \
core/scripting.cpp \
core/selinux.cpp \
core/sqlite.cpp \
core/module.cpp \
core/thread.cpp \
@@ -27,7 +26,6 @@ LOCAL_SRC_FILES := \
core/resetprop/resetprop.cpp \
core/su/su.cpp \
core/su/connect.cpp \
core/su/pts.cpp \
core/zygisk/entry.cpp \
core/zygisk/module.cpp \
core/zygisk/hook.cpp \
@@ -66,7 +64,6 @@ LOCAL_SRC_FILES := \
init/mount.cpp \
init/rootdir.cpp \
init/getinfo.cpp \
init/selinux.cpp \
init/init-rs.cpp
LOCAL_LDFLAGS := -static

View File

@@ -5,7 +5,6 @@ APP_STL := none
APP_PLATFORM := android-23
APP_THIN_ARCHIVE := true
APP_STRIP_MODE := none
APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true
ifdef MAGISK_DEBUG

76
native/src/Cargo.lock generated
View File

@@ -67,9 +67,9 @@ checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base64ct"
version = "1.6.0"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
[[package]]
name = "bit-set"
@@ -106,9 +106,9 @@ dependencies = [
[[package]]
name = "bytemuck_derive"
version = "1.8.1"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a"
checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1"
dependencies = [
"proc-macro2",
"quote",
@@ -123,9 +123,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.2.16"
version = "1.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
dependencies = [
"shlex",
]
@@ -138,18 +138,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.31"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767"
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.31"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863"
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
dependencies = [
"anstyle",
"clap_lex",
@@ -174,9 +174,9 @@ dependencies = [
[[package]]
name = "const-oid"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cb3c4a0d3776f7535c32793be81d6d5fec0d48ac70955d9834e643aa249a52f"
checksum = "0dabb6555f92fb9ee4140454eb5dcd14c7960e1225c6d1a6cc361f032947713e"
[[package]]
name = "const_format"
@@ -234,9 +234,9 @@ dependencies = [
[[package]]
name = "crypto-primes"
version = "0.6.1"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76db6c35962f23f58ab08c156831c55caf69fe85a63ae3780d4fdb24c38b32b6"
checksum = "2acbaf157961745008b5a80ee1cc974150691304fe9177edf69747142bfd9878"
dependencies = [
"crypto-bigint",
"rand_core",
@@ -376,9 +376,9 @@ checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67"
[[package]]
name = "ff"
version = "0.13.0"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
dependencies = [
"rand_core",
"subtle",
@@ -386,15 +386,15 @@ dependencies = [
[[package]]
name = "flagset"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec"
checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
[[package]]
name = "foldhash"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "getrandom"
@@ -457,24 +457,24 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.170"
version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "libz-rs-sys"
version = "0.4.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "902bc563b5d65ad9bba616b490842ef0651066a1a1dc3ce1087113ffcb873c8d"
checksum = "6489ca9bd760fe9642d7644e827b0c9add07df89857b0416ee15c1cc1a3b8c5a"
dependencies = [
"zlib-rs",
]
[[package]]
name = "log"
version = "0.4.26"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "magisk"
@@ -627,7 +627,7 @@ dependencies = [
[[package]]
name = "pb-rs"
version = "0.10.0"
source = "git+https://github.com/tafia/quick-protobuf.git?rev=2f37d5a65504de7d716b5b28fd82219501a901a9#2f37d5a65504de7d716b5b28fd82219501a901a9"
source = "git+https://github.com/tafia/quick-protobuf.git#54e7d6c5d981c6f7cec2e9a2167c10ed0f9392b4"
dependencies = [
"log",
"nom",
@@ -690,16 +690,16 @@ dependencies = [
[[package]]
name = "quick-protobuf"
version = "0.8.1"
source = "git+https://github.com/tafia/quick-protobuf.git?rev=2f37d5a65504de7d716b5b28fd82219501a901a9#2f37d5a65504de7d716b5b28fd82219501a901a9"
source = "git+https://github.com/tafia/quick-protobuf.git#54e7d6c5d981c6f7cec2e9a2167c10ed0f9392b4"
dependencies = [
"byteorder",
]
[[package]]
name = "quote"
version = "1.0.39"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
@@ -771,18 +771,18 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.218"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.218"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
@@ -867,9 +867,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.99"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
@@ -1072,6 +1072,6 @@ dependencies = [
[[package]]
name = "zlib-rs"
version = "0.4.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b20717f0917c908dc63de2e44e97f1e6b126ca58d0e391cee86d504eb8fbd05"
checksum = "868b928d7949e09af2f6086dfc1e01936064cc7a819253bce650d4e2a2d63ba8"

View File

@@ -10,45 +10,44 @@ edition = "2024"
[workspace.dependencies]
cxx = { path = "external/cxx-rs" }
cxx-gen = { path = "external/cxx-rs/gen/lib" }
libc = "0.2"
cfg-if = "1.0"
num-traits = "0.2"
num-derive = "0.4"
thiserror = "2.0"
byteorder = "1"
size = "0.5"
sha1 = "0.11.0-pre.4"
sha2 = "0.11.0-pre.4"
digest = "0.11.0-pre.9"
libc = "0.2.171"
cfg-if = "1.0.0"
num-traits = "0.2.19"
num-derive = "0.4.2"
thiserror = "2.0.12"
byteorder = "1.5.0"
size = "0.5.0"
bytemuck = "1.22.0"
fdt = "0.1.5"
const_format = "0.2.34"
bit-set = "0.8.0"
syn = "2.0.100"
quote = "1.0.40"
proc-macro2 = "1.0.94"
argh = { version = "0.1.13", default-features = false }
libz-rs-sys = { version = "0.5.0", features = ["export-symbols"] }
libbz2-rs-sys = { version = "0.1.3" }
pb-rs = { version = "0.10.0", default-features = false }
quick-protobuf = "0.8.1"
# Rust crypto crates are tied together
sha1 = "=0.11.0-pre.4"
sha2 = "=0.11.0-pre.4"
digest = "=0.11.0-pre.9"
p256 = "0.14.0-pre.2"
p384 = "0.14.0-pre.2"
p521 = "0.14.0-pre.2"
rsa = "0.10.0-pre.4"
x509-cert = "0.3.0-pre.0"
der = "0.8.0-rc.1"
bytemuck = "1.16"
fdt = "0.1"
const_format = "0.2"
bit-set = "0.8"
syn = "2"
quote = "1"
proc-macro2 = "1"
argh = { version = "0.1.13", default-features = false }
libz-rs-sys = { version = "0.4.2", default-features = false, features=["c-allocator"] }
libbz2-rs-sys = { version = "0.1.3", default-features = false, features = ["c-allocator"] }
# Pin version to prevent cargo update break builds
# Pin version to prevent cargo update breaking builds
block-buffer = "=0.11.0-rc.3"
sec1 = "=0.8.0-rc.3"
[workspace.dependencies.pb-rs]
git = "https://github.com/tafia/quick-protobuf.git"
rev = "2f37d5a65504de7d716b5b28fd82219501a901a9"
default-features = false
[workspace.dependencies.quick-protobuf]
git = "https://github.com/tafia/quick-protobuf.git"
rev = "2f37d5a65504de7d716b5b28fd82219501a901a9"
[patch.crates-io]
pb-rs = { git = "https://github.com/tafia/quick-protobuf.git" }
quick-protobuf = { git = "https://github.com/tafia/quick-protobuf.git" }
[profile.dev]
opt-level = "z"
@@ -60,3 +59,4 @@ opt-level = "z"
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true

View File

@@ -1,10 +1,10 @@
use cxx::{type_id, ExternType};
use cxx::{ExternType, type_id};
use libc::c_char;
use std::borrow::Borrow;
use std::cmp::min;
use std::ffi::{CStr, FromBytesWithNulError, OsStr};
use std::fmt::{Debug, Display, Formatter, Write};
use std::ops::{Deref, DerefMut};
use std::ops::Deref;
use std::os::unix::ffi::OsStrExt;
use std::path::{Path, PathBuf};
use std::str::Utf8Error;
@@ -22,7 +22,7 @@ use crate::slice_from_ptr_mut;
// Utf8CStrBufRef: reference to a fixed sized buffer
// Utf8CStrBufArr<N>: fixed sized buffer allocated on the stack
//
// For easier usage, please use the helper functions in cstr_buf.
// For easier usage, please use the helper functions in cstr::buf.
//
// In most cases, these are the types being used
//
@@ -37,11 +37,11 @@ use crate::slice_from_ptr_mut;
// Public helper functions
pub mod cstr_buf {
pub mod buf {
use super::{Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString};
#[inline(always)]
pub fn with_capacity(capacity: usize) -> Utf8CString {
pub fn dynamic(capacity: usize) -> Utf8CString {
Utf8CString::with_capacity(capacity)
}
@@ -68,9 +68,7 @@ pub mod cstr_buf {
// Trait definitions
pub trait Utf8CStrBuf:
Write + AsRef<Utf8CStr> + AsMut<Utf8CStr> + Deref<Target = Utf8CStr> + DerefMut
{
pub trait Utf8CStrBuf: Write + AsRef<Utf8CStr> + Deref<Target = Utf8CStr> {
// The length of the string without the terminating null character.
// assert_true(len <= capacity - 1)
fn len(&self) -> usize;
@@ -82,11 +80,12 @@ pub trait Utf8CStrBuf:
// 3. All bytes from 0 to len is valid UTF-8 and does not contain null
unsafe fn set_len(&mut self, len: usize);
fn push_str(&mut self, s: &str) -> usize;
fn push_lossy(&mut self, s: &[u8]) -> usize;
// The capacity of the internal buffer. The maximum string length this buffer can contain
// is capacity - 1, because the last byte is reserved for the terminating null character.
fn capacity(&self) -> usize;
fn clear(&mut self);
fn as_mut_ptr(&mut self) -> *mut c_char;
fn truncate(&mut self, new_len: usize);
#[inline(always)]
fn is_empty(&self) -> bool {
@@ -94,65 +93,6 @@ pub trait Utf8CStrBuf:
}
}
trait Utf8CStrBufWithSlice: Utf8CStrBuf {
fn buf(&self) -> &[u8];
unsafe fn mut_buf(&mut self) -> &mut [u8];
}
trait AsUtf8CStr {
fn as_utf8_cstr(&self) -> &Utf8CStr;
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr;
}
impl<T: Utf8CStrBufWithSlice> AsUtf8CStr for T {
#[inline(always)]
fn as_utf8_cstr(&self) -> &Utf8CStr {
// SAFETY: the internal buffer is always UTF-8 checked
// SAFETY: self.used is guaranteed to always <= SIZE - 1
unsafe { Utf8CStr::from_bytes_unchecked(self.buf().get_unchecked(..(self.len() + 1))) }
}
#[inline(always)]
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr {
// SAFETY: the internal buffer is always UTF-8 checked
// SAFETY: self.used is guaranteed to always <= SIZE - 1
unsafe {
let len = self.len() + 1;
Utf8CStr::from_bytes_unchecked_mut(self.mut_buf().get_unchecked_mut(..len))
}
}
}
// Implementation for Utf8CString
fn utf8_cstr_buf_append(buf: &mut dyn Utf8CStrBufWithSlice, s: &[u8]) -> usize {
let mut used = buf.len();
if used >= buf.capacity() - 1 {
// Truncate
return 0;
}
let dest = unsafe { &mut buf.mut_buf()[used..] };
let len = min(s.len(), dest.len() - 1);
if len > 0 {
dest[..len].copy_from_slice(&s[..len]);
}
dest[len] = b'\0';
used += len;
unsafe { buf.set_len(used) };
len
}
fn utf8_cstr_append_lossy(buf: &mut dyn Utf8CStrBuf, s: &[u8]) -> usize {
let mut len = 0_usize;
for chunk in s.utf8_chunks() {
len += buf.push_str(chunk.valid());
if !chunk.invalid().is_empty() {
len += buf.push_str(char::REPLACEMENT_CHARACTER.encode_utf8(&mut [0; 4]));
}
}
len
}
pub trait StringExt {
fn nul_terminate(&mut self) -> &mut [u8];
}
@@ -206,23 +146,12 @@ impl Utf8CString {
}
}
impl AsUtf8CStr for Utf8CString {
impl AsRef<Utf8CStr> for Utf8CString {
#[inline(always)]
fn as_utf8_cstr(&self) -> &Utf8CStr {
fn as_ref(&self) -> &Utf8CStr {
// SAFETY: the internal string is always null terminated
unsafe { mem::transmute(slice::from_raw_parts(self.0.as_ptr(), self.0.len() + 1)) }
}
#[inline(always)]
fn as_utf8_cstr_mut(&mut self) -> &mut Utf8CStr {
// SAFETY: the internal string is always null terminated
unsafe {
mem::transmute(slice::from_raw_parts_mut(
self.0.as_mut_ptr(),
self.0.len() + 1,
))
}
}
}
impl Utf8CStrBuf for Utf8CString {
@@ -243,11 +172,6 @@ impl Utf8CStrBuf for Utf8CString {
s.len()
}
#[inline(always)]
fn push_lossy(&mut self, s: &[u8]) -> usize {
utf8_cstr_append_lossy(self, s)
}
fn capacity(&self) -> usize {
self.0.capacity()
}
@@ -256,6 +180,15 @@ impl Utf8CStrBuf for Utf8CString {
self.0.clear();
self.0.nul_terminate();
}
fn as_mut_ptr(&mut self) -> *mut c_char {
self.0.as_mut_ptr().cast()
}
fn truncate(&mut self, new_len: usize) {
self.0.truncate(new_len);
self.0.nul_terminate();
}
}
impl From<String> for Utf8CString {
@@ -265,6 +198,12 @@ impl From<String> for Utf8CString {
}
}
impl From<&str> for Utf8CString {
fn from(value: &str) -> Self {
value.to_string().into()
}
}
impl Borrow<Utf8CStr> for Utf8CString {
fn borrow(&self) -> &Utf8CStr {
self.deref()
@@ -290,18 +229,6 @@ impl<'a> From<&'a mut [u8]> for Utf8CStrBufRef<'a> {
}
}
impl Utf8CStrBufWithSlice for Utf8CStrBufRef<'_> {
#[inline(always)]
fn buf(&self) -> &[u8] {
self.buf
}
#[inline(always)]
unsafe fn mut_buf(&mut self) -> &mut [u8] {
self.buf
}
}
// UTF-8 validated + null terminated buffer on the stack
pub struct Utf8CStrBufArr<const N: usize> {
used: usize,
@@ -317,18 +244,6 @@ impl<const N: usize> Utf8CStrBufArr<N> {
}
}
impl<const N: usize> Utf8CStrBufWithSlice for Utf8CStrBufArr<N> {
#[inline(always)]
fn buf(&self) -> &[u8] {
&self.buf
}
#[inline(always)]
unsafe fn mut_buf(&mut self) -> &mut [u8] {
&mut self.buf
}
}
impl Default for Utf8CStrBufArr<4096> {
fn default() -> Self {
Utf8CStrBufArr::<4096>::new()
@@ -360,7 +275,7 @@ impl Utf8CStr {
Self::from_cstr(CStr::from_bytes_with_nul(buf)?)
}
pub fn from_string(s: &mut String) -> &mut Utf8CStr {
pub fn from_string(s: &mut String) -> &Utf8CStr {
let buf = s.nul_terminate();
// SAFETY: the null byte is explicitly added to the buffer
unsafe { mem::transmute(buf) }
@@ -371,11 +286,6 @@ impl Utf8CStr {
unsafe { mem::transmute(buf) }
}
#[inline(always)]
unsafe fn from_bytes_unchecked_mut(buf: &mut [u8]) -> &mut Utf8CStr {
unsafe { mem::transmute(buf) }
}
pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a Utf8CStr, StrErr> {
if ptr.is_null() {
return Err(StrErr::NullPointerError);
@@ -400,11 +310,6 @@ impl Utf8CStr {
self.0.as_ptr().cast()
}
#[inline(always)]
pub fn as_mut_ptr(&mut self) -> *mut c_char {
self.0.as_mut_ptr().cast()
}
#[inline(always)]
pub fn as_cstr(&self) -> &CStr {
// SAFETY: Already validated as null terminated during construction
@@ -417,13 +322,6 @@ impl Utf8CStr {
// SAFETY: The length of the slice is at least 1 due to null termination check
unsafe { str::from_utf8_unchecked(self.0.get_unchecked(..self.0.len() - 1)) }
}
#[inline(always)]
pub fn as_str_mut(&mut self) -> &mut str {
// SAFETY: Already UTF-8 validated during construction
// SAFETY: The length of the slice is at least 1 due to null termination check
unsafe { str::from_utf8_unchecked_mut(self.0.get_unchecked_mut(..self.0.len() - 1)) }
}
}
impl Deref for Utf8CStr {
@@ -435,13 +333,6 @@ impl Deref for Utf8CStr {
}
}
impl DerefMut for Utf8CStr {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_str_mut()
}
}
impl ToOwned for Utf8CStr {
type Owned = Utf8CString;
@@ -452,6 +343,12 @@ impl ToOwned for Utf8CStr {
}
}
impl AsRef<Utf8CStr> for Utf8CStr {
fn as_ref(&self) -> &Utf8CStr {
self
}
}
// Notice that we only implement ExternType on Utf8CStr *reference*
unsafe impl ExternType for &Utf8CStr {
type Id = type_id!("rust::Utf8CStr");
@@ -470,150 +367,40 @@ const_assert_eq!(align_of::<&Utf8CStr>(), align_of::<[usize; 2]>());
// File system path extensions types
#[repr(transparent)]
pub struct FsPath(Utf8CStr);
impl FsPath {
#[inline(always)]
pub fn from<T: AsRef<Utf8CStr> + ?Sized>(value: &T) -> &FsPath {
unsafe { mem::transmute(value.as_ref()) }
}
#[inline(always)]
pub fn from_mut<T: AsMut<Utf8CStr> + ?Sized>(value: &mut T) -> &mut FsPath {
unsafe { mem::transmute(value.as_mut()) }
}
}
impl Deref for FsPath {
type Target = Utf8CStr;
#[inline(always)]
fn deref(&self) -> &Utf8CStr {
&self.0
}
}
impl DerefMut for FsPath {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Utf8CStr {
&mut self.0
}
}
#[repr(transparent)]
pub struct FsPathFollow(Utf8CStr);
impl Deref for FsPathFollow {
type Target = Utf8CStr;
impl AsRef<Utf8CStr> for FsPathFollow {
#[inline(always)]
fn deref(&self) -> &Utf8CStr {
fn as_ref(&self) -> &Utf8CStr {
&self.0
}
}
impl DerefMut for FsPathFollow {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Utf8CStr {
&mut self.0
}
}
enum Utf8CStrBufOwned<const N: usize> {
Dynamic(Utf8CString),
Fixed(Utf8CStrBufArr<N>),
}
impl<const N: usize> Deref for Utf8CStrBufOwned<N> {
type Target = dyn Utf8CStrBuf;
fn deref(&self) -> &Self::Target {
match self {
Utf8CStrBufOwned::Dynamic(s) => s,
Utf8CStrBufOwned::Fixed(arr) => arr,
}
}
}
impl<const N: usize> DerefMut for Utf8CStrBufOwned<N> {
fn deref_mut(&mut self) -> &mut Self::Target {
match self {
Utf8CStrBufOwned::Dynamic(s) => s,
Utf8CStrBufOwned::Fixed(arr) => arr,
}
}
}
pub struct FsPathBuf<const N: usize>(Utf8CStrBufOwned<N>);
impl FsPathBuf<0> {
pub fn new_dynamic(capacity: usize) -> Self {
FsPathBuf(Utf8CStrBufOwned::Dynamic(Utf8CString::with_capacity(
capacity,
)))
}
}
impl Default for FsPathBuf<4096> {
fn default() -> Self {
FsPathBuf(Utf8CStrBufOwned::Fixed(cstr_buf::default()))
}
}
impl<const N: usize> FsPathBuf<N> {
pub fn new() -> Self {
FsPathBuf(Utf8CStrBufOwned::Fixed(cstr_buf::new::<N>()))
}
pub fn clear(&mut self) {
self.0.clear();
}
pub fn join<T: AsRef<str>>(mut self, path: T) -> Self {
fn inner(buf: &mut dyn Utf8CStrBuf, path: &str) {
if path.starts_with('/') {
buf.clear();
}
if !buf.is_empty() && !buf.ends_with('/') {
buf.push_str("/");
}
buf.push_str(path);
}
inner(self.0.deref_mut(), path.as_ref());
self
}
pub fn join_fmt<T: Display>(mut self, name: T) -> Self {
self.0.write_fmt(format_args!("/{}", name)).ok();
self
}
}
impl<const N: usize> Deref for FsPathBuf<N> {
type Target = FsPath;
fn deref(&self) -> &FsPath {
FsPath::from(self.0.deref())
}
}
impl<const N: usize> DerefMut for FsPathBuf<N> {
fn deref_mut(&mut self) -> &mut FsPath {
FsPath::from_mut(self.0.deref_mut())
}
}
// Boilerplate trait implementations
macro_rules! impl_str {
// impl<T: AsRef<Utf8CStr>> Deref<Target = Utf8CStr> for T { ... }
macro_rules! impl_cstr_deref {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> AsRef<Utf8CStr> for $t {
impl<$($g)*> Deref for $t {
type Target = Utf8CStr;
#[inline(always)]
fn as_ref(&self) -> &Utf8CStr {
self
fn deref(&self) -> &Utf8CStr {
self.as_ref()
}
}
)*}
}
impl_cstr_deref!(
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
(Utf8CString,)
(FsPathFollow,)
);
// impl<T: Deref<Target = Utf8CStr>> BoilerPlate for T { ... }
macro_rules! impl_cstr_misc {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> AsRef<str> for $t {
#[inline(always)]
fn as_ref(&self) -> &str {
@@ -674,7 +461,7 @@ macro_rules! impl_str {
self == other.as_cstr()
}
}
impl<T: AsRef<Utf8CStr>, $($g)*> PartialEq<T> for $t {
impl<T: AsRef<Utf8CStr> + ?Sized, $($g)*> PartialEq<T> for $t {
#[inline(always)]
fn eq(&self, other: &T) -> bool {
self.as_bytes_with_nul() == other.as_ref().as_bytes_with_nul()
@@ -683,56 +470,39 @@ macro_rules! impl_str {
)*}
}
impl_str!(
impl_cstr_misc!(
(Utf8CStr,)
(FsPath,)
(FsPathFollow,)
(FsPathBuf<N>, const N: usize)
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
(Utf8CString,)
(FsPathFollow,)
);
macro_rules! impl_str_buf {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> Write for $t {
#[inline(always)]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.push_str(s);
Ok(())
}
}
impl<$($g)*> Deref for $t {
type Target = Utf8CStr;
#[inline(always)]
fn deref(&self) -> &Utf8CStr {
self.as_utf8_cstr()
}
}
impl<$($g)*> DerefMut for $t {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Utf8CStr {
self.as_utf8_cstr_mut()
}
}
impl<$($g)*> AsMut<Utf8CStr> for $t {
#[inline(always)]
fn as_mut(&mut self) -> &mut Utf8CStr {
self.as_utf8_cstr_mut()
}
}
)*}
fn copy_cstr_truncate(dest: &mut [u8], src: &[u8]) -> usize {
if dest.len() <= 1 {
// Truncate
return 0;
}
let len = min(src.len(), dest.len() - 1);
if len > 0 {
dest[..len].copy_from_slice(&src[..len]);
}
dest[len] = b'\0';
len
}
impl_str_buf!(
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
(Utf8CString,)
);
macro_rules! impl_str_buf_with_slice {
// impl<T> AsRef<Utf8CStr> for T { ... }
// impl<T> Utf8CStrBuf for T { ... }
macro_rules! impl_cstr_buf {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> AsRef<Utf8CStr> for $t {
#[inline(always)]
fn as_ref(&self) -> &Utf8CStr {
// SAFETY: the internal buffer is always UTF-8 checked
// SAFETY: self.used is guaranteed to always <= SIZE - 1
unsafe { Utf8CStr::from_bytes_unchecked(self.buf.get_unchecked(..(self.used + 1))) }
}
}
impl<$($g)*> Utf8CStrBuf for $t {
#[inline(always)]
fn len(&self) -> usize {
@@ -744,11 +514,11 @@ macro_rules! impl_str_buf_with_slice {
}
#[inline(always)]
fn push_str(&mut self, s: &str) -> usize {
utf8_cstr_buf_append(self, s.as_bytes())
}
#[inline(always)]
fn push_lossy(&mut self, s: &[u8]) -> usize {
utf8_cstr_append_lossy(self, s)
// SAFETY: self.used is guaranteed to always <= SIZE - 1
let dest = unsafe { self.buf.get_unchecked_mut(self.used..) };
let len = copy_cstr_truncate(dest, s.as_bytes());
self.used += len;
len
}
#[inline(always)]
fn capacity(&self) -> usize {
@@ -759,15 +529,45 @@ macro_rules! impl_str_buf_with_slice {
self.buf[0] = b'\0';
self.used = 0;
}
#[inline(always)]
fn as_mut_ptr(&mut self) -> *mut c_char {
self.buf.as_mut_ptr().cast()
}
fn truncate(&mut self, new_len: usize) {
if self.used <= new_len {
return;
}
self.buf[new_len] = b'\0';
self.used = new_len;
}
}
)*}
}
impl_str_buf_with_slice!(
impl_cstr_buf!(
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
);
// impl<T: Utf8CStrBuf> Write for T { ... }
macro_rules! impl_cstr_buf_write {
($( ($t:ty, $($g:tt)*) )*) => {$(
impl<$($g)*> Write for $t {
#[inline(always)]
fn write_str(&mut self, s: &str) -> fmt::Result {
self.push_str(s);
Ok(())
}
}
)*}
}
impl_cstr_buf_write!(
(Utf8CStrBufRef<'_>,)
(Utf8CStrBufArr<N>, const N: usize)
(Utf8CString,)
);
#[macro_export]
macro_rules! cstr {
($str:expr) => {{
@@ -781,14 +581,5 @@ macro_rules! cstr {
#[macro_export]
macro_rules! raw_cstr {
($str:expr) => {{
$crate::cstr!($str).as_ptr()
}};
}
#[macro_export]
macro_rules! path {
($str:expr) => {{
$crate::FsPath::from($crate::cstr!($str))
}};
($str:expr) => {{ $crate::cstr!($str).as_ptr() }};
}

View File

@@ -1,6 +1,5 @@
// Functions in this file are only for exporting to C++, DO NOT USE IN RUST
use std::io;
use std::os::fd::{BorrowedFd, OwnedFd, RawFd};
use cfg_if::cfg_if;
@@ -9,14 +8,14 @@ use libc::{c_char, mode_t};
use crate::files::map_file_at;
pub(crate) use crate::xwrap::*;
use crate::{
clone_attr, cstr, cstr_buf, fclone_attr, fd_path, map_fd, map_file, slice_from_ptr,
CxxResultExt, Directory, FsPath, Utf8CStr,
CxxResultExt, Directory, OsResultStatic, Utf8CStr, clone_attr, cstr, fclone_attr, fd_path,
map_fd, map_file, slice_from_ptr,
};
pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize {
let mut buf = cstr_buf::wrap(buf);
let mut buf = cstr::buf::wrap(buf);
fd_path(fd, &mut buf)
.log_cxx_with_msg(|w| w.write_str("fd_path failed"))
.log_cxx()
.map_or(-1_isize, |_| buf.len() as isize)
}
@@ -24,11 +23,11 @@ pub(crate) fn fd_path_for_cxx(fd: RawFd, buf: &mut [u8]) -> isize {
unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = cstr_buf::wrap_ptr(buf, bufsz);
FsPath::from(p)
.realpath(&mut buf)
.map_or(-1, |_| buf.len() as isize)
Ok(path) => {
let mut buf = cstr::buf::wrap_ptr(buf, bufsz);
path.realpath(&mut buf)
.log_cxx()
.map_or(-1_isize, |_| buf.len() as isize)
}
Err(_) => -1,
}
@@ -39,7 +38,7 @@ unsafe extern "C" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: us
unsafe extern "C" fn mkdirs_for_cxx(path: *const c_char, mode: mode_t) -> i32 {
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p).mkdirs(mode).map_or(-1, |_| 0),
Ok(path) => path.mkdirs(mode).map_or(-1, |_| 0),
Err(_) => -1,
}
}
@@ -49,7 +48,7 @@ unsafe extern "C" fn mkdirs_for_cxx(path: *const c_char, mode: mode_t) -> i32 {
unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool {
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p).remove_all().is_ok(),
Ok(path) => path.remove_all().is_ok(),
Err(_) => false,
}
}
@@ -57,7 +56,7 @@ unsafe extern "C" fn rm_rf_for_cxx(path: *const c_char) -> bool {
#[unsafe(no_mangle)]
unsafe extern "C" fn frm_rf(fd: OwnedFd) -> bool {
fn inner(fd: OwnedFd) -> io::Result<()> {
fn inner(fd: OwnedFd) -> OsResultStatic<()> {
Directory::try_from(fd)?.remove_all()
}
inner(fd).is_ok()
@@ -83,7 +82,7 @@ pub(crate) fn map_fd_for_cxx(fd: RawFd, sz: usize, rw: bool) -> &'static mut [u8
}
}
pub(crate) unsafe fn readlinkat_for_cxx(
pub(crate) unsafe fn readlinkat(
dirfd: RawFd,
path: *const c_char,
buf: *mut u8,
@@ -115,14 +114,7 @@ unsafe extern "C" fn cp_afc_for_cxx(src: *const c_char, dest: *const c_char) ->
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.copy_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("cp_afc {} -> {} failed", src, dest))
})
.is_ok();
return src.copy_to(dest).log_cxx().is_ok();
}
}
false
@@ -134,14 +126,7 @@ unsafe extern "C" fn mv_path_for_cxx(src: *const c_char, dest: *const c_char) ->
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.move_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("mv_path {} -> {} failed", src, dest))
})
.is_ok();
return src.move_to(dest).log_cxx().is_ok();
}
}
false
@@ -153,14 +138,7 @@ unsafe extern "C" fn link_path_for_cxx(src: *const c_char, dest: *const c_char)
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return src
.link_to(dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("link_path {} -> {} failed", src, dest))
})
.is_ok();
return src.link_to(dest).log_cxx().is_ok();
}
}
false
@@ -172,13 +150,7 @@ unsafe extern "C" fn clone_attr_for_cxx(src: *const c_char, dest: *const c_char)
unsafe {
if let Ok(src) = Utf8CStr::from_ptr(src) {
if let Ok(dest) = Utf8CStr::from_ptr(dest) {
let src = FsPath::from(src);
let dest = FsPath::from(dest);
return clone_attr(src, dest)
.log_cxx_with_msg(|w| {
w.write_fmt(format_args!("clone_attr {} -> {} failed", src, dest))
})
.is_ok();
return clone_attr(src, dest).log_cxx().is_ok();
}
}
false
@@ -187,9 +159,7 @@ unsafe extern "C" fn clone_attr_for_cxx(src: *const c_char, dest: *const c_char)
#[unsafe(export_name = "fclone_attr")]
unsafe extern "C" fn fclone_attr_for_cxx(a: RawFd, b: RawFd) -> bool {
fclone_attr(a, b)
.log_cxx_with_msg(|w| w.write_str("fclone_attr failed"))
.is_ok()
fclone_attr(a, b).log_cxx().is_ok()
}
#[unsafe(export_name = "cxx$utf8str$new")]

View File

@@ -1,36 +1,38 @@
use crate::cxx_extern::readlinkat_for_cxx;
use crate::cxx_extern::readlinkat;
use crate::{
cstr, cstr_buf, errno, fd_path, fd_set_attr, FileAttr, FsPath, LibcReturn, Utf8CStr,
Utf8CStrBuf,
FsPathBuilder, LibcReturn, OsError, OsResult, OsResultStatic, Utf8CStr, Utf8CStrBuf, cstr,
errno, fd_path, fd_set_attr,
};
use libc::{dirent, O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY};
use std::ffi::CStr;
use libc::{EEXIST, O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY, dirent, mode_t};
use std::fs::File;
use std::ops::Deref;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
use std::{io, mem, slice};
use std::ptr::NonNull;
use std::{mem, slice};
pub struct DirEntry<'a> {
dir: &'a Directory,
entry: &'a dirent,
dir: BorrowedDirectory<'a>,
entry: NonNull<dirent>,
d_name_len: usize,
}
impl DirEntry<'_> {
pub fn name(&self) -> &CStr {
pub fn as_ptr(&self) -> *mut dirent {
self.entry.as_ptr()
}
pub fn name(&self) -> &Utf8CStr {
unsafe {
CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(
Utf8CStr::from_bytes_unchecked(slice::from_raw_parts(
self.d_name.as_ptr().cast(),
self.d_name_len,
))
}
}
pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
self.dir.path(buf)?;
buf.push_str("/");
buf.push_lossy(self.name().to_bytes());
Ok(())
pub fn resolve_path(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
self.dir.path_at(self.name(), buf)
}
pub fn is_dir(&self) -> bool {
@@ -61,69 +63,37 @@ impl DirEntry<'_> {
self.d_type == libc::DT_SOCK
}
pub fn unlink(&self) -> io::Result<()> {
pub fn unlink(&self) -> OsResult<()> {
let flag = if self.is_dir() { libc::AT_REMOVEDIR } else { 0 };
unsafe {
libc::unlinkat(self.dir.as_raw_fd(), self.d_name.as_ptr(), flag).check_os_err()?;
}
Ok(())
self.dir.unlink_at(self.name(), flag)
}
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
buf.clear();
unsafe {
let r = readlinkat_for_cxx(
self.dir.as_raw_fd(),
self.d_name.as_ptr(),
buf.as_mut_ptr().cast(),
buf.capacity(),
)
.check_os_err()? as usize;
buf.set_len(r);
}
Ok(())
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<()> {
self.dir.read_link_at(self.name(), buf)
}
unsafe fn open_fd(&self, flags: i32) -> io::Result<RawFd> {
unsafe { self.dir.open_raw_fd(self.name(), flags, 0) }
}
pub fn open_as_dir(&self) -> io::Result<Directory> {
pub fn open_as_dir(&self) -> OsResult<Directory> {
if !self.is_dir() {
return Err(io::Error::from(io::ErrorKind::NotADirectory));
return Err(OsError::with_os_error(
libc::ENOTDIR,
"fdopendir",
Some(self.name()),
None,
));
}
unsafe { Directory::try_from(OwnedFd::from_raw_fd(self.open_fd(O_RDONLY)?)) }
self.dir.open_as_dir_at(self.name())
}
pub fn open_as_file(&self, flags: i32) -> io::Result<File> {
pub fn open_as_file(&self, flags: i32) -> OsResult<File> {
if self.is_dir() {
return Err(io::Error::from(io::ErrorKind::IsADirectory));
return Err(OsError::with_os_error(
libc::EISDIR,
"open_as_file",
Some(self.name()),
None,
));
}
unsafe { Ok(File::from_raw_fd(self.open_fd(flags)?)) }
}
pub fn get_attr(&self) -> io::Result<FileAttr> {
let mut path = cstr_buf::default();
self.path(&mut path)?;
FsPath::from(&path).get_attr()
}
pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> {
let mut path = cstr_buf::default();
self.path(&mut path)?;
FsPath::from(&path).set_attr(attr)
}
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> {
let mut path = cstr_buf::default();
self.path(&mut path)?;
FsPath::from(&path).get_secontext(con)
}
pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> {
let mut path = cstr_buf::default();
self.path(&mut path)?;
FsPath::from(&path).set_secontext(con)
self.dir.open_as_file_at(self.name(), flags, 0)
}
}
@@ -131,12 +101,37 @@ impl Deref for DirEntry<'_> {
type Target = dirent;
fn deref(&self) -> &dirent {
self.entry
unsafe { self.entry.as_ref() }
}
}
#[repr(transparent)]
pub struct Directory {
dirp: *mut libc::DIR,
inner: NonNull<libc::DIR>,
}
#[repr(transparent)]
pub struct BorrowedDirectory<'a> {
inner: NonNull<libc::DIR>,
phantom: PhantomData<&'a Directory>,
}
impl Deref for BorrowedDirectory<'_> {
type Target = Directory;
fn deref(&self) -> &Directory {
// SAFETY: layout of NonNull<libc::DIR> is the same as Directory
// SAFETY: the lifetime of the raw pointer is tracked in the PhantomData
unsafe { mem::transmute(&self.inner) }
}
}
impl DerefMut for BorrowedDirectory<'_> {
fn deref_mut(&mut self) -> &mut Directory {
// SAFETY: layout of NonNull<libc::DIR> is the same as Directory
// SAFETY: the lifetime of the raw pointer is tracked in the PhantomData
unsafe { mem::transmute(&mut self.inner) }
}
}
pub enum WalkResult {
@@ -146,32 +141,60 @@ pub enum WalkResult {
}
impl Directory {
pub fn open(path: &Utf8CStr) -> io::Result<Directory> {
let dirp = unsafe { libc::opendir(path.as_ptr()) }.check_os_err()?;
Ok(Directory { dirp })
fn borrow(&self) -> BorrowedDirectory {
BorrowedDirectory {
inner: self.inner,
phantom: PhantomData,
}
}
pub fn read(&mut self) -> io::Result<Option<DirEntry<'_>>> {
fn openat<'a>(&self, name: &'a Utf8CStr, flags: i32, mode: u32) -> OsResult<'a, OwnedFd> {
unsafe {
libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode)
.as_os_result("openat", Some(name), None)
.map(|fd| OwnedFd::from_raw_fd(fd))
}
}
fn path_at(&self, name: &Utf8CStr, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
self.resolve_path(buf)?;
buf.append_path(name);
Ok(())
}
}
impl Directory {
pub fn open(path: &Utf8CStr) -> OsResult<Directory> {
let dirp = unsafe { libc::opendir(path.as_ptr()) };
let dirp = dirp.as_os_result("opendir", Some(path), None)?;
Ok(Directory { inner: dirp })
}
pub fn read(&mut self) -> OsResult<'static, Option<DirEntry>> {
*errno() = 0;
let e = unsafe { libc::readdir(self.dirp) };
let e = unsafe { libc::readdir(self.inner.as_ptr()) };
if e.is_null() {
return if *errno() != 0 {
Err(io::Error::last_os_error())
Err(OsError::last_os_error("readdir", None, None))
} else {
Ok(None)
};
}
// Skip both "." and ".."
// Skip non UTF-8 entries, ".", and ".."
unsafe {
let entry = &*e;
let d_name = CStr::from_ptr(entry.d_name.as_ptr());
if d_name == cstr!(".") || d_name == cstr!("..") {
let Ok(name) = Utf8CStr::from_ptr(entry.d_name.as_ptr()) else {
return self.read();
};
if name == "." || name == ".." {
self.read()
} else {
let e = DirEntry {
dir: self,
entry,
d_name_len: d_name.to_bytes_with_nul().len(),
dir: self.borrow(),
entry: NonNull::from(entry),
d_name_len: name.as_bytes_with_nul().len(),
};
Ok(Some(e))
}
@@ -179,23 +202,81 @@ impl Directory {
}
pub fn rewind(&mut self) {
unsafe { libc::rewinddir(self.dirp) }
unsafe { libc::rewinddir(self.inner.as_ptr()) };
}
unsafe fn open_raw_fd(&self, name: &CStr, flags: i32, mode: i32) -> io::Result<RawFd> {
pub fn open_as_dir_at<'a>(&self, name: &'a Utf8CStr) -> OsResult<'a, Directory> {
let fd = self.openat(name, O_RDONLY, 0)?;
Directory::try_from(fd).map_err(|e| e.set_args(Some(name), None))
}
pub fn open_as_file_at<'a>(
&self,
name: &'a Utf8CStr,
flags: i32,
mode: u32,
) -> OsResult<'a, File> {
let fd = self.openat(name, flags, mode)?;
Ok(File::from(fd))
}
pub fn read_link_at<'a>(
&self,
name: &'a Utf8CStr,
buf: &mut dyn Utf8CStrBuf,
) -> OsResult<'a, ()> {
buf.clear();
unsafe {
libc::openat(self.as_raw_fd(), name.as_ptr(), flags | O_CLOEXEC, mode).check_os_err()
let r = readlinkat(
self.as_raw_fd(),
name.as_ptr(),
buf.as_mut_ptr().cast(),
buf.capacity(),
)
.as_os_result("readlinkat", Some(name), None)? as usize;
buf.set_len(r);
}
Ok(())
}
pub fn mkdir_at<'a>(&self, name: &'a Utf8CStr, mode: mode_t) -> OsResult<'a, ()> {
unsafe {
if libc::mkdirat(self.as_raw_fd(), name.as_ptr(), mode as mode_t) < 0
&& *errno() != EEXIST
{
return Err(OsError::last_os_error("mkdirat", Some(name), None));
}
}
Ok(())
}
// ln -s target self/name
pub fn create_symlink_at<'a>(
&self,
name: &'a Utf8CStr,
target: &'a Utf8CStr,
) -> OsResult<'a, ()> {
unsafe {
libc::symlinkat(target.as_ptr(), self.as_raw_fd(), name.as_ptr()).check_os_err(
"symlinkat",
Some(target),
Some(name),
)
}
}
pub fn open_fd(&self, name: &Utf8CStr, flags: i32, mode: i32) -> io::Result<OwnedFd> {
pub fn unlink_at<'a>(&self, name: &'a Utf8CStr, flag: i32) -> OsResult<'a, ()> {
unsafe {
self.open_raw_fd(name.as_cstr(), flags, mode)
.map(|fd| OwnedFd::from_raw_fd(fd))
libc::unlinkat(self.as_raw_fd(), name.as_ptr(), flag).check_os_err(
"unlinkat",
Some(name),
None,
)?;
}
Ok(())
}
pub fn contains_path(&self, path: &CStr) -> bool {
pub fn contains_path(&self, path: &Utf8CStr) -> bool {
// WARNING: Using faccessat is incorrect, because the raw linux kernel syscall
// does not support the flag AT_SYMLINK_NOFOLLOW until 5.8 with faccessat2.
// Use fstatat to check the existence of a file instead.
@@ -210,25 +291,25 @@ impl Directory {
}
}
pub fn path(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
pub fn resolve_path(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
fd_path(self.as_raw_fd(), buf)
}
pub fn post_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
pub fn post_order_walk<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self,
mut f: F,
) -> io::Result<WalkResult> {
) -> OsResultStatic<WalkResult> {
self.post_order_walk_impl(&mut f)
}
pub fn pre_order_walk<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
pub fn pre_order_walk<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self,
mut f: F,
) -> io::Result<WalkResult> {
) -> OsResultStatic<WalkResult> {
self.pre_order_walk_impl(&mut f)
}
pub fn remove_all(&mut self) -> io::Result<()> {
pub fn remove_all(&mut self) -> OsResultStatic<()> {
self.post_order_walk(|e| {
e.unlink()?;
Ok(WalkResult::Continue)
@@ -236,60 +317,20 @@ impl Directory {
Ok(())
}
pub fn copy_into(&mut self, dir: &Directory) -> io::Result<()> {
while let Some(ref e) = self.read()? {
let attr = e.get_attr()?;
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
if e.is_dir() {
unsafe {
libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?;
}
let mut src = e.open_as_dir()?;
let dest = new_entry.open_as_dir()?;
src.copy_into(&dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_file() {
let mut src = e.open_as_file(O_RDONLY)?;
let mut dest = unsafe {
File::from_raw_fd(dir.open_raw_fd(
e.name(),
O_WRONLY | O_CREAT | O_TRUNC,
0o777,
)?)
};
std::io::copy(&mut src, &mut dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_symlink() {
let mut path = cstr_buf::default();
e.read_link(&mut path)?;
unsafe {
libc::symlinkat(path.as_ptr(), dir.as_raw_fd(), e.d_name.as_ptr())
.as_os_err()?;
}
new_entry.set_attr(&attr)?;
}
}
Ok(())
pub fn copy_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
let mut buf = cstr::buf::default();
self.copy_into_impl(dir, &mut buf)
}
pub fn move_into(&mut self, dir: &Directory) -> io::Result<()> {
pub fn move_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
let dir_fd = self.as_raw_fd();
while let Some(ref e) = self.read()? {
if e.is_dir() && dir.contains_path(e.name()) {
// Destination folder exists, needs recursive move
let mut src = e.open_as_dir()?;
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
let dest = new_entry.open_as_dir()?;
let dest = dir.open_as_dir_at(e.name())?;
src.move_into(&dest)?;
return e.unlink();
return Ok(e.unlink()?);
}
unsafe {
@@ -299,51 +340,23 @@ impl Directory {
dir.as_raw_fd(),
e.d_name.as_ptr(),
)
.as_os_err()?;
.check_os_err("renameat", Some(e.name()), None)?;
}
}
Ok(())
}
pub fn link_into(&mut self, dir: &Directory) -> io::Result<()> {
let dir_fd = self.as_raw_fd();
while let Some(ref e) = self.read()? {
if e.is_dir() {
unsafe {
libc::mkdirat(dir.as_raw_fd(), e.d_name.as_ptr(), 0o777).as_os_err()?;
}
let attr = e.get_attr()?;
let new_entry = DirEntry {
dir,
entry: e.entry,
d_name_len: e.d_name_len,
};
let mut src = e.open_as_dir()?;
let dest = new_entry.open_as_dir()?;
src.link_into(&dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else {
unsafe {
libc::linkat(
dir_fd,
e.d_name.as_ptr(),
dir.as_raw_fd(),
e.d_name.as_ptr(),
0,
)
.as_os_err()?;
}
}
}
Ok(())
pub fn link_into(&mut self, dir: &Directory) -> OsResultStatic<()> {
let mut buf = cstr::buf::default();
self.link_into_impl(dir, &mut buf)
}
}
impl Directory {
fn post_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
fn post_order_walk_impl<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self,
f: &mut F,
) -> io::Result<WalkResult> {
) -> OsResultStatic<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
@@ -365,10 +378,10 @@ impl Directory {
}
}
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> io::Result<WalkResult>>(
fn pre_order_walk_impl<F: FnMut(&DirEntry) -> OsResultStatic<WalkResult>>(
&mut self,
f: &mut F,
) -> io::Result<WalkResult> {
) -> OsResultStatic<WalkResult> {
use WalkResult::*;
loop {
match self.read()? {
@@ -388,20 +401,82 @@ impl Directory {
}
}
}
fn copy_into_impl(
&mut self,
dest_dir: &Directory,
buf: &mut dyn Utf8CStrBuf,
) -> OsResultStatic<()> {
while let Some(ref e) = self.read()? {
e.resolve_path(buf)?;
let attr = buf.get_attr()?;
if e.is_dir() {
dest_dir.mkdir_at(e.name(), 0o777)?;
let mut src = e.open_as_dir()?;
let dest = dest_dir.open_as_dir_at(e.name())?;
src.copy_into_impl(&dest, buf)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_file() {
let mut src = e.open_as_file(O_RDONLY)?;
let mut dest =
dest_dir.open_as_file_at(e.name(), O_WRONLY | O_CREAT | O_TRUNC, 0o777)?;
std::io::copy(&mut src, &mut dest)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else if e.is_symlink() {
e.read_link(buf)?;
dest_dir.create_symlink_at(e.name(), buf)?;
dest_dir.path_at(e.name(), buf)?;
buf.set_attr(&attr)?;
}
}
Ok(())
}
fn link_into_impl(
&mut self,
dest_dir: &Directory,
buf: &mut dyn Utf8CStrBuf,
) -> OsResultStatic<()> {
let dir_fd = self.as_raw_fd();
while let Some(ref e) = self.read()? {
if e.is_dir() {
dest_dir.mkdir_at(e.name(), 0o777)?;
e.resolve_path(buf)?;
let attr = buf.get_attr()?;
let mut src = e.open_as_dir()?;
let dest = dest_dir.open_as_dir_at(e.name())?;
src.link_into_impl(&dest, buf)?;
fd_set_attr(dest.as_raw_fd(), &attr)?;
} else {
unsafe {
libc::linkat(
dir_fd,
e.d_name.as_ptr(),
dest_dir.as_raw_fd(),
e.d_name.as_ptr(),
0,
)
.check_os_err("linkat", Some(e.name()), None)?;
}
}
}
Ok(())
}
}
impl TryFrom<OwnedFd> for Directory {
type Error = io::Error;
type Error = OsError<'static>;
fn try_from(fd: OwnedFd) -> io::Result<Self> {
let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) }.check_os_err()?;
Ok(Directory { dirp })
fn try_from(fd: OwnedFd) -> OsResult<'static, Self> {
let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) };
let dirp = dirp.as_os_result("fdopendir", None, None)?;
Ok(Directory { inner: dirp })
}
}
impl AsRawFd for Directory {
fn as_raw_fd(&self) -> RawFd {
unsafe { libc::dirfd(self.dirp) }
unsafe { libc::dirfd(self.inner.as_ptr()) }
}
}
@@ -414,7 +489,7 @@ impl AsFd for Directory {
impl Drop for Directory {
fn drop(&mut self) {
unsafe {
libc::closedir(self.dirp);
libc::closedir(self.inner.as_ptr());
}
}
}

View File

@@ -1,16 +1,17 @@
use crate::{
cstr_buf, errno, error, Directory, FsPath, FsPathBuf, FsPathFollow, LibcReturn, Utf8CStr,
Utf8CStrBuf,
Directory, FsPathFollow, LibcReturn, OsError, OsResult, OsResultStatic, Utf8CStr, Utf8CStrBuf,
cstr, errno, error,
};
use bytemuck::{bytes_of, bytes_of_mut, Pod};
use bytemuck::{Pod, bytes_of, bytes_of_mut};
use libc::{
c_uint, makedev, mode_t, stat, EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY,
O_RDWR, O_TRUNC, O_WRONLY,
EEXIST, ENOENT, F_OK, O_CLOEXEC, O_CREAT, O_PATH, O_RDONLY, O_RDWR, O_TRUNC, O_WRONLY, c_uint,
makedev, mode_t, stat,
};
use mem::MaybeUninit;
use num_traits::AsPrimitive;
use std::cmp::min;
use std::ffi::CStr;
use std::fmt::Display;
use std::fs::File;
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};
use std::os::fd::{AsFd, BorrowedFd};
@@ -19,28 +20,6 @@ use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd, RawFd};
use std::path::Path;
use std::{io, mem, ptr, slice};
pub fn __open_fd_impl(path: &Utf8CStr, flags: i32, mode: mode_t) -> io::Result<OwnedFd> {
unsafe {
let fd = libc::open(path.as_ptr(), flags, mode as c_uint).check_os_err()?;
Ok(OwnedFd::from_raw_fd(fd))
}
}
#[macro_export]
macro_rules! open_fd {
($path:expr, $flags:expr) => {
$crate::__open_fd_impl($path, $flags, 0)
};
($path:expr, $flags:expr, $mode:expr) => {
$crate::__open_fd_impl($path, $flags, $mode)
};
}
pub fn fd_path(fd: RawFd, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
let path = FsPathBuf::default().join("/proc/self/fd").join_fmt(fd);
path.read_link(buf)
}
pub trait ReadExt {
fn skip(&mut self, len: usize) -> io::Result<()>;
fn read_pod<F: Pod>(&mut self, data: &mut F) -> io::Result<()>;
@@ -137,6 +116,24 @@ impl<T: Write> WriteExt for T {
}
}
fn open_fd(path: &Utf8CStr, flags: i32, mode: mode_t) -> OsResult<OwnedFd> {
unsafe {
let fd = libc::open(path.as_ptr(), flags, mode as c_uint).as_os_result(
"open",
Some(path),
None,
)?;
Ok(OwnedFd::from_raw_fd(fd))
}
}
pub fn fd_path(fd: RawFd, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
let path = cstr::buf::new::<64>()
.join_path("/proc/self/fd")
.join_path_fmt(fd);
path.read_link(buf).map_err(|e| e.set_args(None, None))
}
pub struct FileAttr {
pub st: libc::stat,
#[cfg(feature = "selinux")]
@@ -189,17 +186,17 @@ impl FileAttr {
const XATTR_NAME_SELINUX: &CStr = c"security.selinux";
impl FsPath {
impl Utf8CStr {
pub fn follow_link(&self) -> &FsPathFollow {
unsafe { mem::transmute(self) }
}
pub fn open(&self, flags: i32) -> io::Result<File> {
Ok(File::from(open_fd!(self, flags)?))
pub fn open(&self, flags: i32) -> OsResult<File> {
Ok(File::from(open_fd(self, flags, 0)?))
}
pub fn create(&self, flags: i32, mode: mode_t) -> io::Result<File> {
Ok(File::from(open_fd!(self, flags, mode)?))
pub fn create(&self, flags: i32, mode: mode_t) -> OsResult<File> {
Ok(File::from(open_fd(self, O_CREAT | flags, mode)?))
}
pub fn exists(&self) -> bool {
@@ -209,76 +206,81 @@ impl FsPath {
}
}
pub fn rename_to<T: AsRef<Utf8CStr>>(&self, name: T) -> io::Result<()> {
unsafe { libc::rename(self.as_ptr(), name.as_ref().as_ptr()).as_os_err() }
pub fn rename_to<'a>(&'a self, name: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::rename(self.as_ptr(), name.as_ptr()).check_os_err(
"rename",
Some(self),
Some(name),
)
}
}
pub fn remove(&self) -> io::Result<()> {
unsafe { libc::remove(self.as_ptr()).as_os_err() }
pub fn remove(&self) -> OsResult<()> {
unsafe { libc::remove(self.as_ptr()).check_os_err("remove", Some(self), None) }
}
pub fn remove_all(&self) -> io::Result<()> {
pub fn remove_all(&self) -> OsResultStatic<()> {
let attr = self.get_attr()?;
if attr.is_dir() {
let mut dir = Directory::try_from(open_fd!(self, O_RDONLY | O_CLOEXEC)?)?;
let mut dir = Directory::try_from(open_fd(self, O_RDONLY | O_CLOEXEC, 0)?)?;
dir.remove_all()?;
}
self.remove()
Ok(self.remove()?)
}
#[allow(clippy::unnecessary_cast)]
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<()> {
buf.clear();
unsafe {
let r = libc::readlink(self.as_ptr(), buf.as_mut_ptr(), buf.capacity() - 1)
.check_os_err()? as isize;
.as_os_result("readlink", Some(self), None)? as isize;
*(buf.as_mut_ptr().offset(r) as *mut u8) = b'\0';
buf.set_len(r as usize);
}
Ok(())
}
pub fn mkdir(&self, mode: mode_t) -> io::Result<()> {
pub fn mkdir(&self, mode: mode_t) -> OsResult<()> {
unsafe {
if libc::mkdir(self.as_ptr(), mode) < 0 {
if *errno() == EEXIST {
libc::chmod(self.as_ptr(), mode).as_os_err()?;
libc::chmod(self.as_ptr(), mode).check_os_err("chmod", Some(self), None)?;
} else {
return Err(io::Error::last_os_error());
return Err(OsError::last_os_error("mkdir", Some(self), None));
}
}
}
Ok(())
}
pub fn mkdirs(&self, mode: mode_t) -> io::Result<()> {
pub fn mkdirs(&self, mode: mode_t) -> OsResultStatic<()> {
if self.is_empty() {
return Ok(());
}
let mut arr = cstr_buf::default();
arr.push_str(self);
let mut off = 1;
unsafe {
let buf = arr.as_bytes_mut();
while let Some(p) = buf[off..].iter().position(|c| *c == b'/') {
buf[off + p] = b'\0';
if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST {
return Err(io::Error::last_os_error());
let mut path = cstr::buf::default();
let mut components = self.split('/').filter(|s| !s.is_empty());
loop {
let Some(s) = components.next() else {
break;
};
path.append_path(s);
unsafe {
if libc::mkdir(path.as_ptr(), mode) < 0 && *errno() != EEXIST {
return Err(OsError::last_os_error("mkdir", Some(&path), None))?;
}
buf[off + p] = b'/';
off += p + 1;
}
if libc::mkdir(buf.as_ptr().cast(), mode) < 0 && *errno() != EEXIST {
return Err(io::Error::last_os_error());
}
}
*errno() = 0;
Ok(())
}
// Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp
pub fn realpath(&self, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
let fd = open_fd!(self, O_PATH | O_CLOEXEC)?;
pub fn realpath(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<()> {
let fd = self.open(O_PATH | O_CLOEXEC)?;
let mut st1: libc::stat;
let mut st2: libc::stat;
let mut skip_check = false;
@@ -292,19 +294,19 @@ impl FsPath {
fd_path(fd.as_raw_fd(), buf)?;
unsafe {
st2 = mem::zeroed();
libc::stat(buf.as_ptr(), &mut st2).as_os_err()?;
libc::stat(buf.as_ptr(), &mut st2).check_os_err("stat", Some(self), None)?;
if !skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino) {
*errno() = ENOENT;
return Err(io::Error::last_os_error());
return Err(OsError::last_os_error("realpath", Some(self), None));
}
}
Ok(())
}
pub fn get_attr(&self) -> io::Result<FileAttr> {
pub fn get_attr(&self) -> OsResult<FileAttr> {
let mut attr = FileAttr::new();
unsafe {
libc::lstat(self.as_ptr(), &mut attr.st).as_os_err()?;
libc::lstat(self.as_ptr(), &mut attr.st).check_os_err("lstat", Some(self), None)?;
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
@@ -312,12 +314,20 @@ impl FsPath {
Ok(attr)
}
pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> {
pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {
unsafe {
if !attr.is_symlink() {
libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).as_os_err()?;
libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).check_os_err(
"chmod",
Some(self),
None,
)?;
}
libc::lchown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).as_os_err()?;
libc::lchown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).check_os_err(
"lchown",
Some(self),
None,
)?;
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
@@ -327,7 +337,7 @@ impl FsPath {
Ok(())
}
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> {
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<()> {
unsafe {
let sz = libc::lgetxattr(
self.as_ptr(),
@@ -338,7 +348,7 @@ impl FsPath {
if sz < 1 {
con.clear();
if *errno() != libc::ENODATA {
return Err(io::Error::last_os_error());
return Err(OsError::last_os_error("lgetxattr", Some(self), None));
}
} else {
con.set_len((sz - 1) as usize);
@@ -347,7 +357,7 @@ impl FsPath {
Ok(())
}
pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> {
pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::lsetxattr(
self.as_ptr(),
@@ -356,11 +366,11 @@ impl FsPath {
con.len() + 1,
0,
)
.as_os_err()
.check_os_err("lsetxattr", Some(self), Some(con))
}
}
pub fn copy_to(&self, path: &FsPath) -> io::Result<()> {
pub fn copy_to(&self, path: &Utf8CStr) -> OsResultStatic<()> {
let attr = self.get_attr()?;
if attr.is_dir() {
path.mkdir(0o777)?;
@@ -375,10 +385,14 @@ impl FsPath {
let mut dest = path.create(O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0o777)?;
std::io::copy(&mut src, &mut dest)?;
} else if attr.is_symlink() {
let mut buf = cstr_buf::default();
let mut buf = cstr::buf::default();
self.read_link(&mut buf)?;
unsafe {
libc::symlink(buf.as_ptr(), path.as_ptr()).as_os_err()?;
libc::symlink(buf.as_ptr(), path.as_ptr()).check_os_err(
"symlink",
Some(&buf),
Some(path),
)?;
}
}
}
@@ -386,7 +400,7 @@ impl FsPath {
Ok(())
}
pub fn move_to(&self, path: &FsPath) -> io::Result<()> {
pub fn move_to(&self, path: &Utf8CStr) -> OsResultStatic<()> {
if path.exists() {
let attr = path.get_attr()?;
if attr.is_dir() {
@@ -397,37 +411,59 @@ impl FsPath {
path.remove()?;
}
}
self.rename_to(path)
self.rename_to(path)?;
Ok(())
}
pub fn link_to(&self, path: &FsPath) -> io::Result<()> {
pub fn parent_dir(&self) -> Option<&str> {
Path::new(self.as_str())
.parent()
.map(Path::as_os_str)
// SAFETY: all substring of self is valid UTF-8
.map(|s| unsafe { std::str::from_utf8_unchecked(s.as_bytes()) })
}
pub fn file_name(&self) -> Option<&str> {
Path::new(self.as_str())
.file_name()
// SAFETY: all substring of self is valid UTF-8
.map(|s| unsafe { std::str::from_utf8_unchecked(s.as_bytes()) })
}
// ln self path
pub fn link_to(&self, path: &Utf8CStr) -> OsResultStatic<()> {
let attr = self.get_attr()?;
if attr.is_dir() {
path.mkdir(0o777)?;
path.set_attr(&attr)?;
let mut src = Directory::open(self)?;
let dest = Directory::open(path)?;
src.link_into(&dest)
Ok(src.link_into(&dest)?)
} else {
unsafe { libc::link(self.as_ptr(), path.as_ptr()).as_os_err() }
unsafe {
libc::link(self.as_ptr(), path.as_ptr()).check_os_err(
"link",
Some(self),
Some(path),
)?;
}
Ok(())
}
}
pub fn symlink_to(&self, path: &FsPath) -> io::Result<()> {
unsafe { libc::symlink(self.as_ptr(), path.as_ptr()).as_os_err() }
// ln -s target self
pub fn create_symlink_to<'a>(&'a self, target: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::symlink(target.as_ptr(), self.as_ptr()).check_os_err(
"symlink",
Some(target),
Some(self),
)
}
}
pub fn parent(&self, buf: &mut dyn Utf8CStrBuf) -> bool {
buf.clear();
if let Some(parent) = Path::new(self.as_str()).parent() {
let bytes = parent.as_os_str().as_bytes();
// SAFETY: all substring of self is valid UTF-8
let parent = unsafe { std::str::from_utf8_unchecked(bytes) };
buf.push_str(parent);
true
} else {
false
}
pub fn mkfifo(&self, mode: mode_t) -> OsResult<()> {
unsafe { libc::mkfifo(self.as_ptr(), mode).check_os_err("mkfifo", Some(self), None) }
}
}
@@ -436,10 +472,10 @@ impl FsPathFollow {
unsafe { libc::access(self.as_ptr(), F_OK) == 0 }
}
pub fn get_attr(&self) -> io::Result<FileAttr> {
pub fn get_attr(&self) -> OsResult<FileAttr> {
let mut attr = FileAttr::new();
unsafe {
libc::stat(self.as_ptr(), &mut attr.st).as_os_err()?;
libc::stat(self.as_ptr(), &mut attr.st).check_os_err("stat", Some(self), None)?;
#[cfg(feature = "selinux")]
self.get_secontext(&mut attr.con)?;
@@ -447,10 +483,18 @@ impl FsPathFollow {
Ok(attr)
}
pub fn set_attr(&self, attr: &FileAttr) -> io::Result<()> {
pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {
unsafe {
libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).as_os_err()?;
libc::chown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).as_os_err()?;
libc::chmod(self.as_ptr(), (attr.st.st_mode & 0o777).as_()).check_os_err(
"chmod",
Some(self),
None,
)?;
libc::chown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).check_os_err(
"chown",
Some(self),
None,
)?;
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
@@ -460,7 +504,7 @@ impl FsPathFollow {
Ok(())
}
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> io::Result<()> {
pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<()> {
unsafe {
let sz = libc::getxattr(
self.as_ptr(),
@@ -471,7 +515,7 @@ impl FsPathFollow {
if sz < 1 {
con.clear();
if *errno() != libc::ENODATA {
return Err(io::Error::last_os_error());
return Err(OsError::last_os_error("getxattr", Some(self), None));
}
} else {
con.set_len((sz - 1) as usize);
@@ -480,7 +524,7 @@ impl FsPathFollow {
Ok(())
}
pub fn set_secontext(&self, con: &Utf8CStr) -> io::Result<()> {
pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::setxattr(
self.as_ptr(),
@@ -489,86 +533,150 @@ impl FsPathFollow {
con.len() + 1,
0,
)
.as_os_err()
.check_os_err("setxattr", Some(self), Some(con))
}
}
}
pub fn fd_get_attr(fd: RawFd) -> io::Result<FileAttr> {
pub trait FsPathBuilder {
fn join_path<T: AsRef<str>>(mut self, path: T) -> Self
where
Self: Sized,
{
self.append_path(path);
self
}
fn join_path_fmt<T: Display>(mut self, name: T) -> Self
where
Self: Sized,
{
self.append_path_fmt(name);
self
}
fn append_path<T: AsRef<str>>(&mut self, path: T) -> &mut Self;
fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self;
}
fn append_path_impl(buf: &mut dyn Utf8CStrBuf, path: &str) {
if path.starts_with('/') {
buf.clear();
}
if !buf.is_empty() && !buf.ends_with('/') {
buf.push_str("/");
}
buf.push_str(path);
}
impl<S: Utf8CStrBuf + Sized> FsPathBuilder for S {
fn append_path<T: AsRef<str>>(&mut self, path: T) -> &mut Self {
append_path_impl(self, path.as_ref());
self
}
fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self {
self.write_fmt(format_args!("/{}", name)).ok();
self
}
}
impl FsPathBuilder for dyn Utf8CStrBuf + '_ {
fn append_path<T: AsRef<str>>(&mut self, path: T) -> &mut Self {
append_path_impl(self, path.as_ref());
self
}
fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self {
self.write_fmt(format_args!("/{}", name)).ok();
self
}
}
pub fn fd_get_attr(fd: RawFd) -> OsResult<'static, FileAttr> {
let mut attr = FileAttr::new();
unsafe {
libc::fstat(fd, &mut attr.st).as_os_err()?;
libc::fstat(fd, &mut attr.st).check_os_err("fstat", None, None)?;
#[cfg(feature = "selinux")]
{
let sz = libc::fgetxattr(
fd,
XATTR_NAME_SELINUX.as_ptr(),
attr.con.as_mut_ptr().cast(),
attr.con.capacity(),
);
if sz < 1 {
if *errno() != libc::ENODATA {
return Err(io::Error::last_os_error());
}
} else {
attr.con.set_len((sz - 1) as usize);
}
}
fd_get_secontext(fd, &mut attr.con)?;
}
Ok(attr)
}
pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> io::Result<()> {
pub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> OsResult<()> {
unsafe {
libc::fchmod(fd, (attr.st.st_mode & 0o777).as_()).as_os_err()?;
libc::fchown(fd, attr.st.st_uid, attr.st.st_gid).as_os_err()?;
libc::fchmod(fd, (attr.st.st_mode & 0o777).as_()).check_os_err("fchmod", None, None)?;
libc::fchown(fd, attr.st.st_uid, attr.st.st_gid).check_os_err("fchown", None, None)?;
#[cfg(feature = "selinux")]
if !attr.con.is_empty() {
libc::fsetxattr(
fd,
XATTR_NAME_SELINUX.as_ptr(),
attr.con.as_ptr().cast(),
attr.con.len() + 1,
0,
)
.as_os_err()?;
fd_set_secontext(fd, &attr.con)?;
}
}
Ok(())
}
pub fn clone_attr(a: &FsPath, b: &FsPath) -> io::Result<()> {
let attr = a.get_attr()?;
b.set_attr(&attr)
pub fn fd_get_secontext(fd: RawFd, con: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {
unsafe {
let sz = libc::fgetxattr(
fd,
XATTR_NAME_SELINUX.as_ptr(),
con.as_mut_ptr().cast(),
con.capacity(),
);
if sz < 1 {
if *errno() != libc::ENODATA {
return Err(OsError::last_os_error("fgetxattr", None, None));
}
} else {
con.set_len((sz - 1) as usize);
}
}
Ok(())
}
pub fn fclone_attr(a: RawFd, b: RawFd) -> io::Result<()> {
pub fn fd_set_secontext(fd: RawFd, con: &Utf8CStr) -> OsResult<()> {
unsafe {
libc::fsetxattr(
fd,
XATTR_NAME_SELINUX.as_ptr(),
con.as_ptr().cast(),
con.len() + 1,
0,
)
.check_os_err("fsetxattr", Some(con), None)
}
}
pub fn clone_attr<'a>(a: &'a Utf8CStr, b: &'a Utf8CStr) -> OsResult<'a, ()> {
let attr = a.get_attr().map_err(|e| e.set_args(Some(a), None))?;
b.set_attr(&attr).map_err(|e| e.set_args(Some(b), None))
}
pub fn fclone_attr(a: RawFd, b: RawFd) -> OsResult<'static, ()> {
let attr = fd_get_attr(a)?;
fd_set_attr(b, &attr)
fd_set_attr(b, &attr).map_err(|e| e.set_args(None, None))
}
pub struct MappedFile(&'static mut [u8]);
impl MappedFile {
pub fn open(path: &Utf8CStr) -> io::Result<MappedFile> {
pub fn open(path: &Utf8CStr) -> OsResult<MappedFile> {
Ok(MappedFile(map_file(path, false)?))
}
pub fn open_rw(path: &Utf8CStr) -> io::Result<MappedFile> {
pub fn open_rw(path: &Utf8CStr) -> OsResult<MappedFile> {
Ok(MappedFile(map_file(path, true)?))
}
pub fn openat<T: AsFd>(dir: &T, path: &Utf8CStr) -> io::Result<MappedFile> {
pub fn openat<'a, T: AsFd>(dir: &T, path: &'a Utf8CStr) -> OsResult<'a, MappedFile> {
Ok(MappedFile(map_file_at(dir.as_fd(), path, false)?))
}
pub fn openat_rw<T: AsFd>(dir: &T, path: &Utf8CStr) -> io::Result<MappedFile> {
pub fn openat_rw<'a, T: AsFd>(dir: &T, path: &'a Utf8CStr) -> OsResult<'a, MappedFile> {
Ok(MappedFile(map_file_at(dir.as_fd(), path, true)?))
}
pub fn create(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result<MappedFile> {
pub fn create(fd: BorrowedFd, sz: usize, rw: bool) -> OsResult<MappedFile> {
Ok(MappedFile(map_fd(fd, sz, rw)?))
}
}
@@ -599,15 +707,15 @@ unsafe extern "C" {
}
// We mark the returned slice static because it is valid until explicitly unmapped
pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> io::Result<&'static mut [u8]> {
pub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> OsResult<&'static mut [u8]> {
unsafe { map_file_at(BorrowedFd::borrow_raw(libc::AT_FDCWD), path, rw) }
}
pub(crate) fn map_file_at(
pub(crate) fn map_file_at<'a>(
dirfd: BorrowedFd,
path: &Utf8CStr,
path: &'a Utf8CStr,
rw: bool,
) -> io::Result<&'static mut [u8]> {
) -> OsResult<'a, &'static mut [u8]> {
#[cfg(target_pointer_width = "64")]
const BLKGETSIZE64: u32 = 0x80081272;
@@ -617,23 +725,29 @@ pub(crate) fn map_file_at(
let flag = if rw { O_RDWR } else { O_RDONLY };
let fd = unsafe {
OwnedFd::from_raw_fd(
libc::openat(dirfd.as_raw_fd(), path.as_ptr(), flag | O_CLOEXEC).check_os_err()?,
libc::openat(dirfd.as_raw_fd(), path.as_ptr(), flag | O_CLOEXEC).as_os_result(
"openat",
Some(path),
None,
)?,
)
};
let attr = fd_get_attr(fd.as_raw_fd())?;
let sz = if attr.is_block_device() {
let mut sz = 0_u64;
unsafe { ioctl(fd.as_raw_fd(), BLKGETSIZE64, &mut sz) }.as_os_err()?;
unsafe {
ioctl(fd.as_raw_fd(), BLKGETSIZE64, &mut sz).check_os_err("ioctl", Some(path), None)?;
}
sz
} else {
attr.st.st_size as u64
};
map_fd(fd.as_fd(), sz as usize, rw)
map_fd(fd.as_fd(), sz as usize, rw).map_err(|e| e.set_args(Some(path), None))
}
pub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result<&'static mut [u8]> {
pub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> OsResult<'static, &'static mut [u8]> {
let flag = if rw {
libc::MAP_SHARED
} else {
@@ -649,7 +763,7 @@ pub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> io::Result<&'static
0,
);
if ptr == libc::MAP_FAILED {
return Err(io::Error::last_os_error());
return Err(OsError::last_os_error("mmap", None, None));
}
Ok(slice::from_raw_parts_mut(ptr.cast(), sz))
}
@@ -724,8 +838,7 @@ fn parse_mount_info_line(line: &str) -> Option<MountInfo> {
pub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {
let mut res = vec![];
let mut path = format!("/proc/{}/mountinfo", pid);
if let Ok(fd) = open_fd!(Utf8CStr::from_string(&mut path), O_RDONLY | O_CLOEXEC) {
let file = File::from(fd);
if let Ok(file) = Utf8CStr::from_string(&mut path).open(O_RDONLY | O_CLOEXEC) {
BufReader::new(file).foreach_lines(|line| {
parse_mount_info_line(line)
.map(|info| res.push(info))

View File

@@ -6,7 +6,9 @@ pub use const_format;
pub use libc;
use num_traits::FromPrimitive;
pub use cstr::*;
pub use cstr::{
FsPathFollow, StrErr, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString,
};
use cxx_extern::*;
pub use dir::*;
pub use ffi::fork_dont_care;
@@ -15,12 +17,13 @@ pub use logging::*;
pub use misc::*;
pub use result::*;
mod cstr;
pub mod cstr;
mod cxx_extern;
mod dir;
mod files;
mod logging;
mod misc;
mod mount;
mod result;
mod xwrap;

View File

@@ -1,13 +1,13 @@
use std::fmt;
use std::fmt::Arguments;
use std::io::{stderr, stdout, Write};
use std::io::{Write, stderr, stdout};
use std::process::exit;
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use crate::ffi::LogLevelCxx;
use crate::{cstr_buf, Utf8CStr};
use crate::{Utf8CStr, cstr};
// Ugly hack to avoid using enum
#[allow(non_snake_case, non_upper_case_globals)]
@@ -96,7 +96,7 @@ pub fn log_from_cxx(level: LogLevelCxx, msg: &Utf8CStr) {
pub fn log_with_formatter<F: FnOnce(Formatter) -> fmt::Result>(level: LogLevel, f: F) {
log_with_writer(level, |write| {
let mut buf = cstr_buf::default();
let mut buf = cstr::buf::default();
f(&mut buf).ok();
write(level, &buf);
});

View File

@@ -186,27 +186,6 @@ int parse_int(string_view s) {
return parse_num<int, 10>(s);
}
uint64_t parse_uint64_hex(string_view s) {
return parse_num<uint64_t, 16>(s);
}
uint32_t binary_gcd(uint32_t u, uint32_t v) {
if (u == 0) return v;
if (v == 0) return u;
auto shift = __builtin_ctz(u | v);
u >>= __builtin_ctz(u);
do {
v >>= __builtin_ctz(v);
if (u > v) {
auto t = v;
v = u;
u = t;
}
v -= u;
} while (v != 0);
return u << shift;
}
int switch_mnt_ns(int pid) {
int ret = -1;
int fd = syscall(__NR_pidfd_open, pid, 0);
@@ -255,10 +234,6 @@ vector<string> split(string_view s, string_view delims) {
return split_impl<string>(s, delims);
}
vector<string_view> split_view(string_view s, string_view delims) {
return split_impl<string_view>(s, delims);
}
#undef vsnprintf
int vssprintf(char *dest, size_t size, const char *fmt, va_list ap) {
if (size > 0) {

View File

@@ -71,57 +71,6 @@ static inline void default_new(T *&p) { p = new T(); }
template<class T>
static inline void default_new(std::unique_ptr<T> &p) { p.reset(new T()); }
template<typename T, typename Impl>
class stateless_allocator {
public:
using value_type = T;
T *allocate(size_t num) { return static_cast<T*>(Impl::allocate(sizeof(T) * num)); }
void deallocate(T *ptr, size_t num) { Impl::deallocate(ptr, sizeof(T) * num); }
stateless_allocator() = default;
stateless_allocator(const stateless_allocator&) = default;
stateless_allocator(stateless_allocator&&) = default;
template <typename U>
stateless_allocator(const stateless_allocator<U, Impl>&) {}
bool operator==(const stateless_allocator&) { return true; }
bool operator!=(const stateless_allocator&) { return false; }
};
class dynamic_bitset_impl {
public:
using slot_type = unsigned long;
constexpr static int slot_size = sizeof(slot_type) * 8;
using slot_bits = std::bitset<slot_size>;
size_t slots() const { return slot_list.size(); }
slot_type get_slot(size_t slot) const {
return slot_list.size() > slot ? slot_list[slot].to_ulong() : 0ul;
}
void emplace_back(slot_type l) {
slot_list.emplace_back(l);
}
protected:
slot_bits::reference get(size_t pos) {
size_t slot = pos / slot_size;
size_t index = pos % slot_size;
if (slot_list.size() <= slot) {
slot_list.resize(slot + 1);
}
return slot_list[slot][index];
}
bool get(size_t pos) const {
size_t slot = pos / slot_size;
size_t index = pos % slot_size;
return slot_list.size() > slot && slot_list[slot][index];
}
private:
std::vector<slot_bits> slot_list;
};
struct dynamic_bitset : public dynamic_bitset_impl {
slot_bits::reference operator[] (size_t pos) { return get(pos); }
bool operator[] (size_t pos) const { return get(pos); }
};
struct StringCmp {
using is_transparent = void;
bool operator()(std::string_view a, std::string_view b) const { return a < b; }
@@ -198,13 +147,6 @@ struct byte_data : public byte_view {
rust::Vec<size_t> patch(byte_view from, byte_view to);
};
template<size_t N>
struct byte_array : public byte_data {
byte_array() : byte_data(arr, N), arr{0} {}
private:
uint8_t arr[N];
};
class byte_stream;
struct heap_data : public byte_data {
@@ -238,7 +180,6 @@ rust::Vec<size_t> mut_u8_patch(
rust::Slice<const uint8_t> from,
rust::Slice<const uint8_t> to);
uint64_t parse_uint64_hex(std::string_view s);
int parse_int(std::string_view s);
using thread_entry = void *(*)(void *);
@@ -270,11 +211,9 @@ int fork_dont_care();
int fork_no_orphan();
void init_argv0(int argc, char **argv);
void set_nice_name(const char *name);
uint32_t binary_gcd(uint32_t u, uint32_t v);
int switch_mnt_ns(int pid);
std::string &replace_all(std::string &str, std::string_view from, std::string_view to);
std::vector<std::string> split(std::string_view s, std::string_view delims);
std::vector<std::string_view> split_view(std::string_view, std::string_view delims);
// Similar to vsnprintf, but the return value is the written number of bytes
__printflike(3, 0) int vssprintf(char *dest, size_t size, const char *fmt, va_list ap);

View File

@@ -1,13 +1,13 @@
use crate::{ffi, StrErr, Utf8CStr};
use crate::{StrErr, Utf8CStr, ffi};
use argh::EarlyExit;
use libc::c_char;
use std::fmt::Arguments;
use std::io::Write;
use std::mem::ManuallyDrop;
use std::process::exit;
use std::sync::atomic::{AtomicPtr, Ordering};
use std::sync::Arc;
use std::{fmt, io, slice, str};
use std::sync::atomic::{AtomicPtr, Ordering};
use std::{fmt, slice, str};
pub fn errno() -> &'static mut i32 {
unsafe { &mut *libc::__errno() }
@@ -37,52 +37,6 @@ pub unsafe fn slice_from_ptr_mut<'a, T>(buf: *mut T, len: usize) -> &'a mut [T]
}
}
// Check libc return value and map to Result
pub trait LibcReturn
where
Self: Copy,
{
fn is_error(&self) -> bool;
fn check_os_err(self) -> io::Result<Self> {
if self.is_error() {
Err(io::Error::last_os_error())
} else {
Ok(self)
}
}
fn as_os_err(self) -> io::Result<()> {
self.check_os_err()?;
Ok(())
}
}
macro_rules! impl_libc_return {
($($t:ty)*) => ($(
impl LibcReturn for $t {
#[inline]
fn is_error(&self) -> bool {
*self < 0
}
}
)*)
}
impl_libc_return! { i8 i16 i32 i64 isize }
impl<T> LibcReturn for *const T {
#[inline]
fn is_error(&self) -> bool {
self.is_null()
}
}
impl<T> LibcReturn for *mut T {
#[inline]
fn is_error(&self) -> bool {
self.is_null()
}
}
pub trait BytesExt {
fn find(&self, needle: &[u8]) -> Option<usize>;
fn contains(&self, needle: &[u8]) -> bool {

90
native/src/base/mount.rs Normal file
View File

@@ -0,0 +1,90 @@
use crate::{LibcReturn, OsResult, Utf8CStr};
use libc::c_ulong;
use std::ptr;
impl Utf8CStr {
pub fn bind_mount_to<'a>(&'a self, path: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::mount(
self.as_ptr(),
path.as_ptr(),
ptr::null(),
libc::MS_BIND,
ptr::null(),
)
.check_os_err("bind_mount", Some(self), Some(path))
}
}
pub fn remount_mount_point_flags(&self, flags: c_ulong) -> OsResult<()> {
unsafe {
libc::mount(
ptr::null(),
self.as_ptr(),
ptr::null(),
libc::MS_BIND | libc::MS_REMOUNT | flags,
ptr::null(),
)
.check_os_err("remount", Some(self), None)
}
}
pub fn remount_mount_flags(&self, flags: c_ulong) -> OsResult<()> {
unsafe {
libc::mount(
ptr::null(),
self.as_ptr(),
ptr::null(),
libc::MS_REMOUNT | flags,
ptr::null(),
)
.check_os_err("remount", Some(self), None)
}
}
pub fn remount_with_data(&self, data: &Utf8CStr) -> OsResult<()> {
unsafe {
libc::mount(
ptr::null(),
self.as_ptr(),
ptr::null(),
libc::MS_REMOUNT,
data.as_ptr().cast(),
)
.check_os_err("remount", Some(self), None)
}
}
pub fn move_mount_to<'a>(&'a self, path: &'a Utf8CStr) -> OsResult<'a, ()> {
unsafe {
libc::mount(
self.as_ptr(),
path.as_ptr(),
ptr::null(),
libc::MS_MOVE,
ptr::null(),
)
.check_os_err("move_mount", Some(self), Some(path))
}
}
pub fn unmount(&self) -> OsResult<()> {
unsafe {
libc::umount2(self.as_ptr(), libc::MNT_DETACH).check_os_err("unmount", Some(self), None)
}
}
pub fn set_mount_private(&self, recursive: bool) -> OsResult<()> {
let flag = if recursive { libc::MS_REC } else { 0 };
unsafe {
libc::mount(
ptr::null(),
self.as_ptr(),
ptr::null(),
libc::MS_PRIVATE | flag,
ptr::null(),
)
.check_os_err("set_mount_private", Some(self), None)
}
}
}

View File

@@ -1,9 +1,10 @@
use std::fmt;
use crate::logging::Formatter;
use crate::{LogLevel, errno, log_with_args, log_with_formatter};
use std::fmt::Display;
use std::panic::Location;
use crate::logging::Formatter;
use crate::{log_with_args, log_with_formatter, LogLevel};
use std::ptr::NonNull;
use std::{fmt, io};
use thiserror::Error;
// Error handling throughout the Rust codebase in Magisk:
//
@@ -61,7 +62,6 @@ pub trait ResultExt<T> {
// Internal C++ bridging logging routines
pub(crate) trait CxxResultExt<T> {
fn log_cxx(self) -> LoggedResult<T>;
fn log_cxx_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T>;
}
trait Loggable<T> {
@@ -78,10 +78,6 @@ impl<T, R: Loggable<T>> CxxResultExt<T> for R {
fn log_cxx(self) -> LoggedResult<T> {
self.do_log(LogLevel::ErrorCxx, None)
}
fn log_cxx_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {
self.do_log_msg(LogLevel::ErrorCxx, None, f)
}
}
impl<T, R: Loggable<T>> ResultExt<T> for R {
@@ -205,3 +201,205 @@ impl<T: Display> From<T> for LoggedError {
LoggedError::default()
}
}
// Check libc return value and map to Result
pub trait LibcReturn
where
Self: Copy,
{
type Value;
fn is_error(&self) -> bool;
fn map_val(self) -> Self::Value;
fn as_os_result<'a>(
self,
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsResult<'a, Self::Value> {
if self.is_error() {
Err(OsError::last_os_error(name, arg1, arg2))
} else {
Ok(self.map_val())
}
}
fn check_os_err<'a>(
self,
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsResult<'a, ()> {
if self.is_error() {
Err(OsError::last_os_error(name, arg1, arg2))
} else {
Ok(())
}
}
fn check_io_err(self) -> io::Result<()> {
if self.is_error() {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
macro_rules! impl_libc_return {
($($t:ty)*) => ($(
impl LibcReturn for $t {
type Value = Self;
#[inline(always)]
fn is_error(&self) -> bool {
*self < 0
}
#[inline(always)]
fn map_val(self) -> Self::Value {
self
}
}
)*)
}
impl_libc_return! { i8 i16 i32 i64 isize }
impl<T> LibcReturn for *mut T {
type Value = NonNull<T>;
#[inline(always)]
fn is_error(&self) -> bool {
self.is_null()
}
#[inline(always)]
fn map_val(self) -> NonNull<T> {
// SAFETY: pointer is null checked by is_error
unsafe { NonNull::new_unchecked(self.cast()) }
}
}
#[derive(Debug)]
enum OwnableStr<'a> {
None,
Borrowed(&'a str),
Owned(Box<str>),
}
impl OwnableStr<'_> {
fn into_owned(self) -> OwnableStr<'static> {
match self {
OwnableStr::None => OwnableStr::None,
OwnableStr::Borrowed(s) => OwnableStr::Owned(Box::from(s)),
OwnableStr::Owned(s) => OwnableStr::Owned(s),
}
}
fn ok(&self) -> Option<&str> {
match self {
OwnableStr::None => None,
OwnableStr::Borrowed(s) => Some(*s),
OwnableStr::Owned(s) => Some(s),
}
}
}
impl<'a> From<Option<&'a str>> for OwnableStr<'a> {
fn from(value: Option<&'a str>) -> Self {
value.map(OwnableStr::Borrowed).unwrap_or(OwnableStr::None)
}
}
#[derive(Debug)]
pub struct OsError<'a> {
code: i32,
name: &'static str,
arg1: OwnableStr<'a>,
arg2: OwnableStr<'a>,
}
impl OsError<'_> {
pub fn with_os_error<'a>(
code: i32,
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsError<'a> {
OsError {
code,
name,
arg1: OwnableStr::from(arg1),
arg2: OwnableStr::from(arg2),
}
}
pub fn last_os_error<'a>(
name: &'static str,
arg1: Option<&'a str>,
arg2: Option<&'a str>,
) -> OsError<'a> {
Self::with_os_error(*errno(), name, arg1, arg2)
}
pub fn set_args<'a>(self, arg1: Option<&'a str>, arg2: Option<&'a str>) -> OsError<'a> {
Self::with_os_error(self.code, self.name, arg1, arg2)
}
pub fn into_owned(self) -> OsError<'static> {
OsError {
code: *errno(),
name: self.name,
arg1: self.arg1.into_owned(),
arg2: self.arg2.into_owned(),
}
}
fn as_io_error(&self) -> io::Error {
io::Error::from_raw_os_error(self.code)
}
}
impl Display for OsError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let error = self.as_io_error();
if self.name.is_empty() {
write!(f, "{:#}", error)
} else {
match (self.arg1.ok(), self.arg2.ok()) {
(Some(arg1), Some(arg2)) => {
write!(f, "{} '{}' '{}': {:#}", self.name, arg1, arg2, error)
}
(Some(arg1), None) => {
write!(f, "{} '{}': {:#}", self.name, arg1, error)
}
_ => {
write!(f, "{}: {:#}", self.name, error)
}
}
}
}
}
impl std::error::Error for OsError<'_> {}
pub type OsResult<'a, T> = Result<T, OsError<'a>>;
#[derive(Debug, Error)]
pub enum OsErrorStatic {
#[error(transparent)]
Os(OsError<'static>),
#[error(transparent)]
Io(#[from] io::Error),
}
// Convert non-static OsError to static
impl<'a> From<OsError<'a>> for OsErrorStatic {
fn from(value: OsError<'a>) -> Self {
OsErrorStatic::Os(value.into_owned())
}
}
pub type OsResultStatic<T> = Result<T, OsErrorStatic>;

View File

@@ -25,7 +25,6 @@ int xsocket(int domain, int type, int protocol);
int xbind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int xlisten(int sockfd, int backlog);
int xaccept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
int xaccess(const char *path, int mode);
int xstat(const char *pathname, struct stat *buf);
int xfstat(int fd, struct stat *buf);
int xdup(int fd);

View File

@@ -1,62 +1,26 @@
// Functions in this file are only for exporting to C++, DO NOT USE IN RUST
use std::ffi::CStr;
use std::os::unix::io::RawFd;
use crate::cxx_extern::readlinkat;
use crate::{
BorrowedDirectory, CxxResultExt, LibcReturn, Utf8CStr, cstr, slice_from_ptr, slice_from_ptr_mut,
};
use libc::{
c_char, c_uint, c_ulong, c_void, dev_t, mode_t, nfds_t, off_t, pollfd, sockaddr, socklen_t,
ssize_t,
};
use std::ffi::CStr;
use std::fs::File;
use std::io::{Read, Write};
use std::mem::ManuallyDrop;
use std::os::fd::FromRawFd;
use std::os::unix::io::RawFd;
use std::ptr;
use std::ptr::NonNull;
use crate::cxx_extern::readlinkat_for_cxx;
use crate::{CxxResultExt, FsPath, Utf8CStr, Utf8CStrBufRef, errno, raw_cstr};
fn ptr_to_str<'a, T>(ptr: *const T) -> &'a str {
fn ptr_to_str<'a>(ptr: *const c_char) -> Option<&'a str> {
if ptr.is_null() {
"(null)"
None
} else {
unsafe { CStr::from_ptr(ptr.cast()) }.to_str().unwrap_or("")
}
}
fn error_str() -> &'static str {
unsafe { ptr_to_str(libc::strerror(*errno())) }
}
macro_rules! error_cxx {
($($args:tt)+) => {
($crate::log_with_args($crate::LogLevel::ErrorCxx, format_args_nl!($($args)+)))
}
}
macro_rules! perror {
($fmt:expr) => {
$crate::log_with_formatter($crate::LogLevel::ErrorCxx, |w| {
w.write_str($fmt)?;
w.write_fmt(format_args_nl!(" failed with {}: {}", $crate::errno(), error_str()))
})
};
($fmt:expr, $($args:tt)*) => {
$crate::log_with_formatter($crate::LogLevel::ErrorCxx, |w| {
w.write_fmt(format_args!($fmt, $($args)*))?;
w.write_fmt(format_args_nl!(" failed with {}: {}", $crate::errno(), error_str()))
})
};
}
mod c_export {
use std::os::unix::io::RawFd;
use crate::{slice_from_ptr, slice_from_ptr_mut};
#[unsafe(no_mangle)]
unsafe extern "C" fn xwrite(fd: RawFd, buf: *const u8, bufsz: usize) -> isize {
unsafe { super::xwrite(fd, slice_from_ptr(buf, bufsz)) }
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xxread(fd: RawFd, buf: *mut u8, bufsz: usize) -> isize {
unsafe { super::xxread(fd, slice_from_ptr_mut(buf, bufsz)) }
unsafe { CStr::from_ptr(ptr) }.to_str().ok()
}
}
@@ -64,11 +28,10 @@ mod c_export {
unsafe extern "C" fn xrealpath(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = Utf8CStrBufRef::from_ptr(buf, bufsz);
FsPath::from(p)
.realpath(&mut buf)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("realpath {} failed", p)))
Ok(path) => {
let mut buf = cstr::buf::wrap_ptr(buf, bufsz);
path.realpath(&mut buf)
.log_cxx()
.map_or(-1, |_| buf.len() as isize)
}
Err(_) => -1,
@@ -80,11 +43,10 @@ unsafe extern "C" fn xrealpath(path: *const c_char, buf: *mut u8, bufsz: usize)
unsafe extern "C" fn xreadlink(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => {
let mut buf = Utf8CStrBufRef::from_ptr(buf, bufsz);
FsPath::from(p)
.read_link(&mut buf)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("readlink {} failed", p)))
Ok(path) => {
let mut buf = cstr::buf::wrap_ptr(buf, bufsz);
path.read_link(&mut buf)
.log_cxx()
.map_or(-1, |_| buf.len() as isize)
}
Err(_) => -1,
@@ -100,244 +62,176 @@ unsafe extern "C" fn xreadlinkat(
bufsz: usize,
) -> isize {
unsafe {
let r = readlinkat_for_cxx(dirfd, path, buf, bufsz);
if r < 0 {
perror!("readlinkat {}", ptr_to_str(path))
}
r
readlinkat(dirfd, path, buf, bufsz)
.as_os_result("readlinkat", ptr_to_str(path), None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xfopen(path: *const c_char, mode: *const c_char) -> *mut libc::FILE {
unsafe {
let fp = libc::fopen(path, mode);
if fp.is_null() {
perror!("fopen {}", ptr_to_str(path));
}
fp
libc::fopen(path, mode)
.as_os_result("fopen", ptr_to_str(path), None)
.log_cxx()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xfdopen(fd: RawFd, mode: *const c_char) -> *mut libc::FILE {
unsafe {
let fp = libc::fdopen(fd, mode);
if fp.is_null() {
perror!("fdopen");
}
fp
libc::fdopen(fd, mode)
.as_os_result("fdopen", None, None)
.log_cxx()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xopen(path: *const c_char, flags: i32, mode: mode_t) -> RawFd {
unsafe {
let r = libc::open(path, flags, mode as c_uint);
if r < 0 {
perror!("open {}", ptr_to_str(path));
}
r
libc::open(path, flags, mode as c_uint)
.as_os_result("open", ptr_to_str(path), None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xopenat(dirfd: RawFd, path: *const c_char, flags: i32, mode: mode_t) -> RawFd {
unsafe {
let r = libc::openat(dirfd, path, flags, mode as c_uint);
if r < 0 {
perror!("openat {}", ptr_to_str(path));
}
r
libc::openat(dirfd, path, flags, mode as c_uint)
.as_os_result("openat", ptr_to_str(path), None)
.log_cxx()
.unwrap_or(-1)
}
}
// Fully write data slice
fn xwrite(fd: RawFd, data: &[u8]) -> isize {
unsafe {
let mut write_sz: usize = 0;
let mut r: ssize_t;
let mut remain: &[u8] = data;
loop {
r = libc::write(fd, remain.as_ptr().cast(), remain.len());
if r < 0 {
if *errno() == libc::EINTR {
continue;
}
perror!("write");
return r;
}
let r = r as usize;
write_sz += r;
remain = &remain[r..];
if r == 0 || remain.is_empty() {
break;
}
}
if !remain.is_empty() {
error_cxx!("write ({} != {})", write_sz, data.len())
}
write_sz as isize
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xwrite(fd: RawFd, buf: *const u8, bufsz: usize) -> isize {
let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
let data = unsafe { slice_from_ptr(buf, bufsz) };
file.write_all(data)
.log_cxx()
.map_or(-1, |_| data.len() as isize)
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xread(fd: RawFd, buf: *mut c_void, bufsz: usize) -> isize {
unsafe {
let r = libc::read(fd, buf, bufsz);
if r < 0 {
perror!("read");
}
r
libc::read(fd, buf, bufsz)
.as_os_result("read", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
// Fully read size of data slice
fn xxread(fd: RawFd, data: &mut [u8]) -> isize {
unsafe {
let mut read_sz: usize = 0;
let mut r: ssize_t;
let mut remain: &mut [u8] = data;
loop {
r = libc::read(fd, remain.as_mut_ptr().cast(), remain.len());
if r < 0 {
if *errno() == libc::EINTR {
continue;
}
perror!("read");
return r;
}
let r = r as usize;
read_sz += r;
remain = &mut remain[r..];
if r == 0 || remain.is_empty() {
break;
}
}
if !remain.is_empty() {
error_cxx!("read ({} != {})", read_sz, data.len())
}
read_sz as isize
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xxread(fd: RawFd, buf: *mut u8, bufsz: usize) -> isize {
let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
let data = unsafe { slice_from_ptr_mut(buf, bufsz) };
file.read_exact(data)
.log_cxx()
.map_or(-1, |_| data.len() as isize)
}
pub(crate) fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32 {
unsafe {
let r = libc::pipe2(fds.as_mut_ptr(), flags);
if r < 0 {
perror!("pipe2");
}
r
libc::pipe2(fds.as_mut_ptr(), flags)
.as_os_result("pipe2", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
extern "C" fn xsetns(fd: RawFd, nstype: i32) -> i32 {
unsafe {
let r = libc::setns(fd, nstype);
if r < 0 {
perror!("setns");
}
r
libc::setns(fd, nstype)
.as_os_result("setns", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
extern "C" fn xunshare(flags: i32) -> i32 {
unsafe {
let r = libc::unshare(flags);
if r < 0 {
perror!("unshare");
}
r
libc::unshare(flags)
.as_os_result("unshare", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xopendir(path: *const c_char) -> *mut libc::DIR {
unsafe {
let dp = libc::opendir(path);
if dp.is_null() {
perror!("opendir {}", ptr_to_str(path));
}
dp
libc::opendir(path)
.as_os_result("opendir", ptr_to_str(path), None)
.log_cxx()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
}
#[unsafe(no_mangle)]
extern "C" fn xfdopendir(fd: RawFd) -> *mut libc::DIR {
unsafe {
let dp = libc::fdopendir(fd);
if dp.is_null() {
perror!("fdopendir");
}
dp
libc::fdopendir(fd)
.as_os_result("fdopendir", None, None)
.log_cxx()
.map_or(ptr::null_mut(), NonNull::as_ptr)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xreaddir(dirp: *mut libc::DIR) -> *mut libc::dirent {
unsafe {
*errno() = 0;
loop {
let e = libc::readdir(dirp);
if e.is_null() {
if *errno() != 0 {
perror!("readdir")
}
} else {
// Filter out . and ..
let s = (*e).d_name.as_ptr();
if libc::strcmp(s, raw_cstr!(".")) == 0 || libc::strcmp(s, raw_cstr!("..")) == 0 {
continue;
}
};
return e;
}
}
unsafe extern "C" fn xreaddir(mut dir: BorrowedDirectory) -> *mut libc::dirent {
dir.read()
.log_cxx()
.ok()
.flatten()
.map_or(ptr::null_mut(), |entry| entry.as_ptr())
}
#[unsafe(no_mangle)]
extern "C" fn xsetsid() -> i32 {
unsafe {
let r = libc::setsid();
if r < 0 {
perror!("setsid");
}
r
libc::setsid()
.as_os_result("setsid", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
extern "C" fn xsocket(domain: i32, ty: i32, protocol: i32) -> RawFd {
unsafe {
let fd = libc::socket(domain, ty, protocol);
if fd < 0 {
perror!("socket");
}
fd
libc::socket(domain, ty, protocol)
.as_os_result("socket", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xbind(socket: i32, address: *const sockaddr, len: socklen_t) -> i32 {
unsafe {
let r = libc::bind(socket, address, len);
if r < 0 {
perror!("bind");
}
r
libc::bind(socket, address, len)
.as_os_result("bind", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
extern "C" fn xlisten(socket: i32, backlog: i32) -> i32 {
unsafe {
let r = libc::listen(socket, backlog);
if r < 0 {
perror!("listen");
}
r
libc::listen(socket, backlog)
.as_os_result("listen", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
@@ -349,77 +243,60 @@ unsafe extern "C" fn xaccept4(
flg: i32,
) -> RawFd {
unsafe {
let fd = libc::accept4(sockfd, addr, len, flg);
if fd < 0 {
perror!("accept4");
}
fd
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xaccess(path: *const c_char, mode: i32) -> i32 {
unsafe {
let r = libc::access(path, mode);
if r < 0 {
perror!("access {}", ptr_to_str(path));
}
r
libc::accept4(sockfd, addr, len, flg)
.as_os_result("accept4", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xstat(path: *const c_char, buf: *mut libc::stat) -> i32 {
unsafe {
let r = libc::stat(path, buf);
if r < 0 {
perror!("stat {}", ptr_to_str(path));
}
r
libc::stat(path, buf)
.as_os_result("stat", ptr_to_str(path), None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xfstat(fd: RawFd, buf: *mut libc::stat) -> i32 {
unsafe {
let r = libc::fstat(fd, buf);
if r < 0 {
perror!("fstat");
}
r
libc::fstat(fd, buf)
.as_os_result("fstat", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
extern "C" fn xdup(oldfd: RawFd) -> RawFd {
unsafe {
let fd = libc::dup(oldfd);
if fd < 0 {
perror!("dup");
}
fd
libc::dup(oldfd)
.as_os_result("dup", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
extern "C" fn xdup2(oldfd: RawFd, newfd: RawFd) -> RawFd {
unsafe {
let fd = libc::dup2(oldfd, newfd);
if fd < 0 {
perror!("dup2");
}
fd
libc::dup2(oldfd, newfd)
.as_os_result("dup2", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xsymlink(target: *const c_char, linkpath: *const c_char) -> i32 {
unsafe {
let r = libc::symlink(target, linkpath);
if r < 0 {
perror!("symlink {} -> {}", ptr_to_str(target), ptr_to_str(linkpath));
}
r
libc::symlink(target, linkpath)
.as_os_result("symlink", ptr_to_str(target), ptr_to_str(linkpath))
.log_cxx()
.unwrap_or(-1)
}
}
@@ -432,44 +309,40 @@ unsafe extern "C" fn xmount(
data: *const c_void,
) -> i32 {
unsafe {
let r = libc::mount(src, target, fstype, flags, data);
if r < 0 {
perror!("mount {} -> {}", ptr_to_str(src), ptr_to_str(target));
}
r
libc::mount(src, target, fstype, flags, data)
.as_os_result("mount", ptr_to_str(src), ptr_to_str(target))
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xumount2(target: *const c_char, flags: i32) -> i32 {
unsafe {
let r = libc::umount2(target, flags);
if r < 0 {
perror!("umount2 {}", ptr_to_str(target));
}
r
libc::umount2(target, flags)
.as_os_result("umount2", ptr_to_str(target), None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xrename(oldname: *const c_char, newname: *const c_char) -> i32 {
unsafe {
let r = libc::rename(oldname, newname);
if r < 0 {
perror!("rename {} -> {}", ptr_to_str(oldname), ptr_to_str(newname));
}
r
libc::rename(oldname, newname)
.as_os_result("rename", ptr_to_str(oldname), ptr_to_str(newname))
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xmkdir(path: *const c_char, mode: mode_t) -> i32 {
unsafe {
let r = libc::mkdir(path, mode);
if r < 0 && *errno() != libc::EEXIST {
perror!("mkdir {}", ptr_to_str(path));
match Utf8CStr::from_ptr(path) {
Ok(path) => path.mkdir(mode).log_cxx().map_or(-1, |_| 0),
Err(_) => -1,
}
r
}
}
@@ -477,10 +350,7 @@ unsafe extern "C" fn xmkdir(path: *const c_char, mode: mode_t) -> i32 {
unsafe extern "C" fn xmkdirs(path: *const c_char, mode: mode_t) -> i32 {
unsafe {
match Utf8CStr::from_ptr(path) {
Ok(p) => FsPath::from(p)
.mkdirs(mode)
.log_cxx_with_msg(|w| w.write_fmt(format_args!("mkdirs {} failed", p)))
.map_or(-1, |_| 0),
Ok(path) => path.mkdirs(mode).log_cxx().map_or(-1, |_| 0),
Err(_) => -1,
}
}
@@ -494,43 +364,39 @@ unsafe extern "C" fn xsendfile(
count: usize,
) -> isize {
unsafe {
let r = libc::sendfile(out_fd, in_fd, offset, count);
if r < 0 {
perror!("sendfile");
}
r
libc::sendfile(out_fd, in_fd, offset, count)
.as_os_result("sendfile", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
extern "C" fn xfork() -> i32 {
unsafe {
let r = libc::fork();
if r < 0 {
perror!("fork");
}
r
libc::fork()
.as_os_result("fork", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xpoll(fds: *mut pollfd, nfds: nfds_t, timeout: i32) -> i32 {
unsafe {
let r = libc::poll(fds, nfds, timeout);
if r < 0 {
perror!("poll");
}
r
libc::poll(fds, nfds, timeout)
.as_os_result("poll", None, None)
.log_cxx()
.unwrap_or(-1)
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn xmknod(pathname: *const c_char, mode: mode_t, dev: dev_t) -> i32 {
unsafe {
let r = libc::mknod(pathname, mode, dev);
if r < 0 {
perror!("mknod {}", ptr_to_str(pathname));
}
r
libc::mknod(pathname, mode, dev)
.as_os_result("mknod", ptr_to_str(pathname), None)
.log_cxx()
.unwrap_or(-1)
}
}

View File

@@ -13,7 +13,7 @@ pb-rs = { workspace = true }
[dependencies]
base = { path = "../base" }
cxx = { path = "../external/cxx-rs" }
cxx = { workspace = true }
byteorder = { workspace = true }
size = { workspace = true }
quick-protobuf = { workspace = true }
@@ -25,8 +25,6 @@ p256 = { workspace = true }
p384 = { workspace = true }
p521 = { workspace = true }
rsa = { workspace = true, features = ["sha2"] }
block-buffer = { workspace = true }
sec1 = { workspace = true }
x509-cert = { workspace = true }
der = { workspace = true, features = ["derive", "pem"] }
fdt = { workspace = true }
@@ -34,3 +32,7 @@ bytemuck = { workspace = true, features = ["derive", "min_const_generics"] }
num-traits = { workspace = true }
libz-rs-sys = { workspace = true }
libbz2-rs-sys = { workspace = true }
# Pin version to prevent cargo update break builds
block-buffer = { workspace = true }
sec1 = { workspace = true }

View File

@@ -1,4 +1,4 @@
use pb_rs::{types::FileDescriptor, ConfigBuilder};
use pb_rs::{ConfigBuilder, types::FileDescriptor};
use crate::codegen::gen_cxx_binding;

View File

@@ -10,18 +10,18 @@ use std::process::exit;
use std::str;
use argh::FromArgs;
use bytemuck::{from_bytes, Pod, Zeroable};
use bytemuck::{Pod, Zeroable, from_bytes};
use num_traits::cast::AsPrimitive;
use size::{Base, Size, Style};
use base::libc::{
c_char, dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t, O_CLOEXEC, O_CREAT,
O_RDONLY, O_TRUNC, O_WRONLY, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_IRGRP,
S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR,
O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY, S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT,
S_IFREG, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR,
c_char, dev_t, gid_t, major, makedev, minor, mknod, mode_t, uid_t,
};
use base::{
cstr_buf, log_err, map_args, BytesExt, EarlyExitExt, FsPath, LoggedResult, MappedFile,
ResultExt, Utf8CStr, Utf8CStrBuf, WriteExt,
BytesExt, EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, Utf8CStrBuf, WriteExt,
cstr, log_err, map_args,
};
use crate::check_env;
@@ -342,13 +342,13 @@ impl Cpio {
eprintln!("Extracting entry [{}] to [{}]", path, out);
let out = Utf8CStr::from_string(out);
let out = FsPath::from(out);
let mut buf = cstr_buf::default();
let mut buf = cstr::buf::default();
// Make sure its parent directories exist
if out.parent(&mut buf) {
FsPath::from(&buf).mkdirs(0o755)?;
if let Some(dir) = out.parent_dir() {
buf.push_str(dir);
buf.mkdirs(0o755)?;
}
let mode: mode_t = (entry.mode & 0o777).into();
@@ -362,7 +362,7 @@ impl Cpio {
S_IFLNK => {
buf.clear();
buf.push_str(str::from_utf8(entry.data.as_slice())?);
FsPath::from(&buf).symlink_to(out)?;
out.create_symlink_to(&buf)?;
}
S_IFBLK | S_IFCHR => {
let dev = makedev(entry.rdevmajor.try_into()?, entry.rdevminor.try_into()?);
@@ -399,7 +399,6 @@ impl Cpio {
return Err(log_err!("path cannot end with / for add"));
}
let file = Utf8CStr::from_string(file);
let file = FsPath::from(&file);
let attr = file.get_attr()?;
let mut content = Vec::<u8>::new();
@@ -413,8 +412,8 @@ impl Cpio {
file.open(O_RDONLY | O_CLOEXEC)?.read_to_end(&mut content)?;
mode | S_IFREG
} else {
rdevmajor = unsafe { major(attr.st.st_rdev.as_()) }.as_();
rdevminor = unsafe { minor(attr.st.st_rdev.as_()) }.as_();
rdevmajor = major(attr.st.st_rdev.as_()).as_();
rdevminor = minor(attr.st.st_rdev.as_()).as_();
if attr.is_block_device() {
mode | S_IFBLK
} else if attr.is_char_device() {
@@ -765,7 +764,7 @@ pub fn cpio_commands(argc: i32, argv: *const *const c_char) -> bool {
CpioCli::from_args(&["magiskboot", "cpio"], &cmds).on_early_exit(print_cpio_usage);
let file = Utf8CStr::from_string(&mut cli.file);
let mut cpio = if FsPath::from(file).exists() {
let mut cpio = if file.exists() {
Cpio::load_from_file(file)?
} else {
Cpio::new()

View File

@@ -2,12 +2,12 @@ use std::{cell::UnsafeCell, process::exit};
use argh::FromArgs;
use fdt::{
node::{FdtNode, NodeProperty},
Fdt, FdtError,
node::{FdtNode, NodeProperty},
};
use base::{
libc::c_char, log_err, map_args, EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr,
EarlyExitExt, LoggedResult, MappedFile, ResultExt, Utf8CStr, libc::c_char, log_err, map_args,
};
use crate::{check_env, patch::patch_verity};

View File

@@ -3,14 +3,14 @@
#![feature(iter_intersperse)]
#![feature(try_blocks)]
pub use libz_rs_sys::*;
pub use libbz2_rs_sys::*;
pub use base;
use cpio::cpio_commands;
use dtb::dtb_commands;
pub use libbz2_rs_sys::*;
pub use libz_rs_sys::*;
use patch::hexpatch;
use payload::extract_boot_from_payload;
use sign::{get_sha, sha1_hash, sha256_hash, sign_boot_image, verify_boot_image, SHA};
use sign::{SHA, get_sha, sha1_hash, sha256_hash, sign_boot_image, verify_boot_image};
use std::env;
mod cpio;

View File

@@ -9,10 +9,10 @@ use quick_protobuf::{BytesReader, MessageRead};
use crate::{
ffi,
proto::update_metadata::{mod_InstallOperation::Type, DeltaArchiveManifest},
proto::update_metadata::{DeltaArchiveManifest, mod_InstallOperation::Type},
};
use base::{
error, ffi::Utf8CStrRef, LoggedError, LoggedResult, ReadSeekExt, ResultExt, Utf8CStr, WriteExt,
LoggedError, LoggedResult, ReadSeekExt, ResultExt, Utf8CStr, WriteExt, error, ffi::Utf8CStrRef,
};
macro_rules! bad_payload {

View File

@@ -15,18 +15,18 @@ use rsa::pkcs1v15::{
Signature as RsaSignature, SigningKey as RsaSigningKey, VerifyingKey as RsaVerifyingKey,
};
use rsa::pkcs8::SubjectPublicKeyInfoRef;
use rsa::signature::hazmat::{PrehashSigner, PrehashVerifier};
use rsa::signature::SignatureEncoding;
use rsa::signature::hazmat::{PrehashSigner, PrehashVerifier};
use rsa::{RsaPrivateKey, RsaPublicKey};
use sha1::Sha1;
use sha2::{Sha256, Sha384, Sha512};
use x509_cert::der::asn1::{OctetString, PrintableString};
use x509_cert::der::Any;
use x509_cert::spki::AlgorithmIdentifier;
use x509_cert::Certificate;
use x509_cert::der::Any;
use x509_cert::der::asn1::{OctetString, PrintableString};
use x509_cert::spki::AlgorithmIdentifier;
use base::libc::c_char;
use base::{log_err, LoggedResult, MappedFile, ResultExt, StrErr, Utf8CStr};
use base::{LoggedResult, MappedFile, ResultExt, StrErr, Utf8CStr, log_err};
use crate::ffi::BootImage;

View File

@@ -1,7 +1,6 @@
#include <sys/stat.h>
#include <consts.hpp>
#include <selinux.hpp>
#include <base.hpp>
int main(int argc, char *argv[]) {

View File

@@ -3,7 +3,6 @@
#include <sys/stat.h>
#include <consts.hpp>
#include <selinux.hpp>
#include <base.hpp>
using namespace std;

View File

@@ -1,4 +1,4 @@
use pb_rs::{types::FileDescriptor, ConfigBuilder};
use pb_rs::{ConfigBuilder, types::FileDescriptor};
use crate::codegen::gen_cxx_binding;

View File

@@ -8,7 +8,6 @@
#include <consts.hpp>
#include <base.hpp>
#include <core.hpp>
#include <selinux.hpp>
#include <flags.h>
using namespace std;
@@ -129,13 +128,6 @@ static void poll_ctrl_handler(pollfd *pfd) {
}
}
void MagiskD::reboot() const noexcept {
if (is_recovery())
exec_command_sync("/system/bin/reboot", "recovery");
else
exec_command_sync("/system/bin/reboot");
}
bool get_client_cred(int fd, sock_cred *cred) {
socklen_t len = sizeof(ucred);
if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, cred, &len) != 0)
@@ -326,19 +318,6 @@ static void handle_request(pollfd *pfd) {
}
}
static void switch_cgroup(const char *cgroup, int pid) {
char buf[32];
ssprintf(buf, sizeof(buf), "%s/cgroup.procs", cgroup);
if (access(buf, F_OK) != 0)
return;
int fd = xopen(buf, O_WRONLY | O_APPEND | O_CLOEXEC);
if (fd == -1)
return;
ssprintf(buf, sizeof(buf), "%d\n", pid);
xwrite(fd, buf, strlen(buf));
close(fd);
}
static void daemon_entry() {
android_logging();
@@ -360,51 +339,15 @@ static void daemon_entry() {
if (fd > STDERR_FILENO)
close(fd);
setsid();
setcon(MAGISK_PROC_CON);
rust::daemon_entry();
SDK_INT = MagiskD::Get().sdk_int();
// Escape from cgroup
int pid = getpid();
switch_cgroup("/acct", pid);
switch_cgroup("/dev/cg2_bpf", pid);
switch_cgroup("/sys/fs/cgroup", pid);
if (get_prop("ro.config.per_app_memcg") != "false") {
switch_cgroup("/dev/memcg/apps", pid);
}
// Get self stat
xstat("/proc/self/exe", &self_st);
// Samsung workaround #7887
if (access("/system_ext/app/mediatek-res/mediatek-res.apk", F_OK) == 0) {
set_prop("ro.vendor.mtk_model", "0");
}
restore_tmpcon();
// Cleanups
const char *tmp = get_magisk_tmp();
char path[64];
ssprintf(path, sizeof(path), "%s/" ROOTMNT, tmp);
if (access(path, F_OK) == 0) {
file_readline(true, path, [](string_view line) -> bool {
umount2(line.data(), MNT_DETACH);
return true;
});
}
if (getenv("REMOUNT_ROOT")) {
xmount(nullptr, "/", nullptr, MS_REMOUNT | MS_RDONLY, nullptr);
unsetenv("REMOUNT_ROOT");
}
ssprintf(path, sizeof(path), "%s/" ROOTOVL, tmp);
rm_rf(path);
fd = xsocket(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0);
sockaddr_un addr = {.sun_family = AF_LOCAL};
ssprintf(addr.sun_path, sizeof(addr.sun_path), "%s/" MAIN_SOCKET, tmp);
ssprintf(addr.sun_path, sizeof(addr.sun_path), "%s/" MAIN_SOCKET, get_magisk_tmp());
unlink(addr.sun_path);
if (xbind(fd, (sockaddr *) &addr, sizeof(addr)))
exit(1);

View File

@@ -1,21 +1,23 @@
use crate::consts::{MAGISK_FULL_VER, MAIN_CONFIG, SECURE_DIR};
use crate::consts::{MAGISK_FULL_VER, MAGISK_PROC_CON, MAIN_CONFIG, ROOTMNT, ROOTOVL, SECURE_DIR};
use crate::db::Sqlite3;
use crate::ffi::{
check_key_combo, disable_modules, exec_common_scripts, exec_module_scripts, get_magisk_tmp,
initialize_denylist, setup_magisk_env, DbEntryKey, ModuleInfo, RequestCode,
DbEntryKey, ModuleInfo, RequestCode, check_key_combo, disable_modules, exec_common_scripts,
exec_module_scripts, get_magisk_tmp, initialize_denylist, setup_magisk_env,
};
use crate::get_prop;
use crate::logging::{magisk_logging, setup_logfile, start_log_daemon};
use crate::mount::{clean_mounts, setup_mounts};
use crate::package::ManagerInfo;
use crate::selinux::restore_tmpcon;
use crate::su::SuInfo;
use base::libc::{O_CLOEXEC, O_RDONLY};
use crate::{get_prop, set_prop};
use base::libc::{O_APPEND, O_CLOEXEC, O_RDONLY, O_WRONLY};
use base::{
cstr, error, info, libc, open_fd, path, AtomicArc, BufReadExt, FsPathBuf, ResultExt, Utf8CStr,
AtomicArc, BufReadExt, FsPathBuilder, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, error, info, libc,
};
use std::fs::File;
use std::io::BufReader;
use std::fmt::Write as FmtWrite;
use std::io::{BufReader, Write};
use std::os::unix::net::UnixStream;
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Mutex, OnceLock};
@@ -78,10 +80,6 @@ impl MagiskD {
unsafe { MAGISKD.get().unwrap_unchecked() }
}
pub fn is_recovery(&self) -> bool {
self.is_recovery
}
pub fn zygisk_enabled(&self) -> bool {
self.zygisk_enabled.load(Ordering::Acquire)
}
@@ -105,7 +103,7 @@ impl MagiskD {
self.preserve_stub_apk();
// Check secure dir
let secure_dir = path!(SECURE_DIR);
let secure_dir = cstr!(SECURE_DIR);
if !secure_dir.exists() {
if self.sdk_int < 24 {
secure_dir.mkdir(0o700).log_ok();
@@ -172,7 +170,7 @@ impl MagiskD {
self.set_db_setting(DbEntryKey::BootloopCount, 0).log_ok();
// At this point it's safe to create the folder
let secure_dir = path!(SECURE_DIR);
let secure_dir = cstr!(SECURE_DIR);
if !secure_dir.exists() {
secure_dir.mkdir(0o700).log_ok();
}
@@ -216,9 +214,26 @@ impl MagiskD {
}
}
}
pub fn reboot(&self) {
if self.is_recovery {
Command::new("/system/bin/reboot").arg("recovery").status()
} else {
Command::new("/system/bin/reboot").status()
}
.ok();
}
}
pub fn daemon_entry() {
unsafe { libc::setsid() };
// Make sure the current context is magisk
if let Ok(mut current) = cstr!("/proc/self/attr/current").open(O_WRONLY | O_CLOEXEC) {
let con = cstr!(MAGISK_PROC_CON);
current.write_all(con.as_bytes_with_nul()).log_ok();
}
start_log_daemon();
magisk_logging();
info!("Magisk {} daemon started", MAGISK_FULL_VER);
@@ -228,13 +243,13 @@ pub fn daemon_entry() {
|| get_prop(cstr!("ro.product.device"), false).contains("vsoc");
// Load config status
let path = FsPathBuf::<64>::new()
.join(get_magisk_tmp())
.join(MAIN_CONFIG);
let magisk_tmp = get_magisk_tmp();
let mut tmp_path = cstr::buf::new::<64>()
.join_path(magisk_tmp)
.join_path(MAIN_CONFIG);
let mut is_recovery = false;
if let Ok(file) = path.open(O_RDONLY | O_CLOEXEC) {
let mut file = BufReader::new(file);
file.foreach_props(|key, val| {
if let Ok(main_config) = tmp_path.open(O_RDONLY | O_CLOEXEC) {
BufReader::new(main_config).foreach_props(|key, val| {
if key == "RECOVERYMODE" {
is_recovery = val == "true";
return false;
@@ -242,11 +257,11 @@ pub fn daemon_entry() {
true
});
}
tmp_path.truncate(magisk_tmp.len());
let mut sdk_int = -1;
if let Ok(file) = path!("/system/build.prop").open(O_RDONLY | O_CLOEXEC) {
let mut file = BufReader::new(file);
file.foreach_props(|key, val| {
if let Ok(build_prop) = cstr!("/system/build.prop").open(O_RDONLY | O_CLOEXEC) {
BufReader::new(build_prop).foreach_props(|key, val| {
if key == "ro.build.version.sdk" {
sdk_int = val.parse::<i32>().unwrap_or(-1);
return false;
@@ -262,6 +277,44 @@ pub fn daemon_entry() {
}
info!("* Device API level: {}", sdk_int);
restore_tmpcon().log_ok();
// Escape from cgroup
let pid = unsafe { libc::getpid() };
switch_cgroup("/acct", pid);
switch_cgroup("/dev/cg2_bpf", pid);
switch_cgroup("/sys/fs/cgroup", pid);
if get_prop(cstr!("ro.config.per_app_memcg"), false) != "false" {
switch_cgroup("/dev/memcg/apps", pid);
}
// Samsung workaround #7887
if cstr!("/system_ext/app/mediatek-res/mediatek-res.apk").exists() {
set_prop(cstr!("ro.vendor.mtk_model"), cstr!("0"), false);
}
// Cleanup pre-init mounts
tmp_path.append_path(ROOTMNT);
if let Ok(mount_list) = tmp_path.open(O_RDONLY | O_CLOEXEC) {
BufReader::new(mount_list).foreach_lines(|line| {
let item = Utf8CStr::from_string(line);
item.unmount().log_ok();
true
})
}
tmp_path.truncate(magisk_tmp.len());
// Remount rootfs as read-only if requested
if std::env::var_os("REMOUNT_ROOT").is_some() {
cstr!("/").remount_mount_flags(libc::MS_RDONLY).log_ok();
unsafe { std::env::remove_var("REMOUNT_ROOT") };
}
// Remove all pre-init overlay files to free-up memory
tmp_path.append_path(ROOTOVL);
tmp_path.remove_all().log_ok();
tmp_path.truncate(magisk_tmp.len());
let magiskd = MagiskD {
sdk_int,
is_emulator,
@@ -272,9 +325,22 @@ pub fn daemon_entry() {
MAGISKD.set(magiskd).ok();
}
fn switch_cgroup(cgroup: &str, pid: i32) {
let mut buf = cstr::buf::new::<64>()
.join_path(cgroup)
.join_path("cgroup.procs");
if !buf.exists() {
return;
}
if let Ok(mut file) = buf.open(O_WRONLY | O_APPEND | O_CLOEXEC) {
buf.clear();
buf.write_fmt(format_args!("{}", pid)).ok();
file.write_all(buf.as_bytes()).log_ok();
}
}
fn check_data() -> bool {
if let Ok(fd) = open_fd!(cstr!("/proc/mounts"), O_RDONLY | O_CLOEXEC) {
let file = File::from(fd);
if let Ok(file) = cstr!("/proc/mounts").open(O_RDONLY | O_CLOEXEC) {
let mut mnt = false;
BufReader::new(file).foreach_lines(|line| {
if line.contains(" /data ") && !line.contains("tmpfs") {

View File

@@ -1,9 +1,10 @@
#![allow(improper_ctypes, improper_ctypes_definitions)]
use crate::daemon::{MagiskD, MAGISKD};
use crate::daemon::{MAGISKD, MagiskD};
use crate::ffi::{
open_and_init_db, sqlite3, sqlite3_errstr, DbEntryKey, DbStatement, DbValues, MntNsMode,
DbEntryKey, DbStatement, DbValues, MntNsMode, open_and_init_db, sqlite3, sqlite3_errstr,
};
use crate::socket::{IpcRead, IpcWrite};
use DbArg::{Integer, Text};
use base::{LoggedResult, ResultExt, Utf8CStr};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
@@ -15,7 +16,6 @@ use std::pin::Pin;
use std::ptr;
use std::ptr::NonNull;
use thiserror::Error;
use DbArg::{Integer, Text};
fn sqlite_err_str(code: i32) -> &'static Utf8CStr {
// SAFETY: sqlite3 always returns UTF-8 strings

View File

@@ -10,7 +10,6 @@
#include <base.hpp>
#include <sqlite.hpp>
#include <core.hpp>
#include <selinux.hpp>
#include "deny.hpp"
@@ -109,10 +108,10 @@ static bool proc_name_match(int pid, string_view name) {
bool proc_context_match(int pid, string_view context) {
char buf[PATH_MAX];
char con[1024];
char con[1024] = {0};
sprintf(buf, "/proc/%d", pid);
if (lgetfilecon(buf, { con, sizeof(con) }) >= 0) {
if (lgetfilecon(buf, byte_data{ con, sizeof(con) })) {
return str_starts(con, context);
}
return false;

View File

@@ -1,7 +1,7 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam};
use syn::{Data, DeriveInput, Fields, GenericParam, parse_macro_input, parse_quote};
pub(crate) fn derive_decodable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);

View File

@@ -1,16 +0,0 @@
#pragma once
#include <base.hpp>
int setcon(const char *con);
int getfilecon(const char *path, byte_data con);
int lgetfilecon(const char *path, byte_data con);
int fgetfilecon(int fd, byte_data con);
int setfilecon(const char *path, const char *con);
int lsetfilecon(const char *path, const char *con);
int fsetfilecon(int fd, const char *con);
int getfilecon_at(int dirfd, const char *name, byte_data con);
void setfilecon_at(int dirfd, const char *name, const char *con);
void restorecon();
void restore_tmpcon();

View File

@@ -8,18 +8,20 @@
use crate::ffi::SuRequest;
use crate::socket::Encodable;
use base::{libc, Utf8CStr};
use cxx::{type_id, ExternType};
use daemon::{daemon_entry, MagiskD};
use base::{Utf8CStr, libc};
use cxx::{ExternType, type_id};
use daemon::{MagiskD, daemon_entry};
use derive::Decodable;
use logging::{android_logging, setup_logfile, zygisk_close_logd, zygisk_get_logd, zygisk_logging};
use mount::{find_preinit_device, revert_unmount};
use resetprop::{persist_delete_prop, persist_get_prop, persist_get_props, persist_set_prop};
use selinux::{lgetfilecon, lsetfilecon, restorecon, setfilecon};
use socket::{recv_fd, recv_fds, send_fd, send_fds};
use std::fs::File;
use std::mem::ManuallyDrop;
use std::ops::DerefMut;
use std::os::fd::FromRawFd;
use su::{get_pty_num, pump_tty, restore_stdin};
use zygisk::zygisk_should_load_module;
#[path = "../include/consts.rs"]
@@ -30,6 +32,7 @@ mod logging;
mod mount;
mod package;
mod resetprop;
mod selinux;
mod socket;
mod su;
mod zygisk;
@@ -134,6 +137,8 @@ pub mod ffi {
#[cxx_name = "prop_cb"]
type PropCb;
unsafe fn get_prop_rs(name: *const c_char, persist: bool) -> String;
#[cxx_name = "set_prop"]
unsafe fn set_prop_rs(name: *const c_char, value: *const c_char, skip_svc: bool) -> i32;
unsafe fn prop_cb_exec(
cb: Pin<&mut PropCb>,
name: *const c_char,
@@ -204,6 +209,13 @@ pub mod ffi {
fn recv_fd(socket: i32) -> i32;
fn recv_fds(socket: i32) -> Vec<i32>;
unsafe fn write_to_fd(self: &SuRequest, fd: i32);
fn pump_tty(infd: i32, outfd: i32);
fn get_pty_num(fd: i32) -> i32;
fn restore_stdin() -> bool;
fn restorecon();
fn lgetfilecon(path: Utf8CStrRef, con: &mut [u8]) -> bool;
fn setfilecon(path: Utf8CStrRef, con: Utf8CStrRef) -> bool;
fn lsetfilecon(path: Utf8CStrRef, con: Utf8CStrRef) -> bool;
#[namespace = "rust"]
fn daemon_entry();
@@ -219,7 +231,7 @@ pub mod ffi {
// FFI for MagiskD
extern "Rust" {
type MagiskD;
fn is_recovery(&self) -> bool;
fn reboot(&self);
fn sdk_int(&self) -> i32;
fn zygisk_enabled(&self) -> bool;
fn boot_stage_handler(&self, client: i32, code: i32);
@@ -241,8 +253,6 @@ pub mod ffi {
fn get() -> &'static MagiskD;
}
unsafe extern "C++" {
#[allow(dead_code)]
fn reboot(self: &MagiskD);
fn handle_modules(self: &MagiskD) -> Vec<ModuleInfo>;
}
}
@@ -267,3 +277,7 @@ impl SuRequest {
pub fn get_prop(name: &Utf8CStr, persist: bool) -> String {
unsafe { ffi::get_prop_rs(name.as_ptr(), persist) }
}
pub fn set_prop(name: &Utf8CStr, value: &Utf8CStr, skip_svc: bool) -> bool {
unsafe { ffi::set_prop_rs(name.as_ptr(), value.as_ptr(), skip_svc) == 0 }
}

View File

@@ -1,15 +1,15 @@
use crate::consts::{LOGFILE, LOG_PIPE};
use crate::consts::{LOG_PIPE, LOGFILE};
use crate::ffi::get_magisk_tmp;
use crate::logging::LogFile::{Actual, Buffer};
use base::libc::{
getpid, gettid, localtime_r, pthread_sigmask, sigaddset, sigset_t, sigtimedwait, time_t,
timespec, tm, O_CLOEXEC, O_RDWR, O_WRONLY, PIPE_BUF, SIGPIPE, SIG_BLOCK, SIG_SETMASK,
O_CLOEXEC, O_RDWR, O_WRONLY, PIPE_BUF, SIG_BLOCK, SIG_SETMASK, SIGPIPE, getpid, gettid,
localtime_r, pthread_sigmask, sigaddset, sigset_t, sigtimedwait, time_t, timespec, tm,
};
use base::{
const_format::concatcp, cstr_buf, libc, raw_cstr, FsPathBuf, LogLevel, Logger, ReadExt,
Utf8CStr, Utf8CStrBuf, WriteExt, LOGGER,
FsPathBuilder, LOGGER, LogLevel, Logger, ReadExt, Utf8CStr, Utf8CStrBuf, WriteExt,
const_format::concatcp, cstr, libc, raw_cstr,
};
use bytemuck::{bytes_of, write_zeroes, Pod, Zeroable};
use bytemuck::{Pod, Zeroable, bytes_of, write_zeroes};
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::FromPrimitive;
use std::cmp::min;
@@ -130,7 +130,7 @@ fn write_log_to_pipe(mut logd: &File, prio: i32, msg: &Utf8CStr) -> io::Result<u
let io2 = IoSlice::new(msg);
let result = logd.write_vectored(&[io1, io2]);
if let Err(ref e) = result {
let mut buf = cstr_buf::default();
let mut buf = cstr::buf::default();
buf.write_fmt(format_args!("Cannot write_log_to_pipe: {}", e))
.ok();
android_log_write(LogLevel::Error, &buf);
@@ -180,7 +180,9 @@ pub fn zygisk_get_logd() -> i32 {
let mut fd = ZYGISK_LOGD.load(Ordering::Relaxed);
if fd < 0 {
android_logging();
let path = FsPathBuf::default().join(get_magisk_tmp()).join(LOG_PIPE);
let path = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(LOG_PIPE);
// Open as RW as sometimes it may block
fd = unsafe { libc::open(path.as_ptr(), O_RDWR | O_CLOEXEC) };
if fd >= 0 {
@@ -266,7 +268,7 @@ extern "C" fn logfile_writer(arg: *mut c_void) -> *mut c_void {
let mut meta = LogMeta::zeroed();
let mut msg_buf = [0u8; MAX_MSG_LEN];
let mut aux = cstr_buf::new::<64>();
let mut aux = cstr::buf::new::<64>();
loop {
// Read request
@@ -355,7 +357,9 @@ pub fn setup_logfile() {
}
pub fn start_log_daemon() {
let path = FsPathBuf::default().join(get_magisk_tmp()).join(LOG_PIPE);
let path = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path(LOG_PIPE);
unsafe {
libc::mkfifo(path.as_ptr(), 0o666);

View File

@@ -4,7 +4,6 @@
#include <base.hpp>
#include <consts.hpp>
#include <core.hpp>
#include <selinux.hpp>
#include <flags.h>
using namespace std;

View File

@@ -7,7 +7,6 @@
#include <base.hpp>
#include <consts.hpp>
#include <core.hpp>
#include <selinux.hpp>
#include "node.hpp"

View File

@@ -1,15 +1,14 @@
use std::{
cmp::Ordering::{Greater, Less},
path::{Path, PathBuf},
ptr,
};
use num_traits::AsPrimitive;
use base::libc::{c_uint, dev_t};
use base::{
cstr, cstr_buf, debug, info, libc, parse_mount_info, raw_cstr, warn, FsPath, FsPathBuf,
LibcReturn, LoggedResult, MountInfo, ResultExt, Utf8CStr,
FsPathBuilder, LibcReturn, LoggedResult, MountInfo, ResultExt, Utf8CStr, Utf8CStrBuf, cstr,
debug, info, libc, parse_mount_info, warn,
};
use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR};
@@ -22,7 +21,9 @@ pub fn setup_mounts() {
let magisk_tmp = get_magisk_tmp();
// Mount preinit directory
let dev_path = FsPathBuf::<64>::new().join(magisk_tmp).join(PREINITDEV);
let dev_path = cstr::buf::new::<64>()
.join_path(magisk_tmp)
.join_path(PREINITDEV);
let mut linked = false;
if let Ok(attr) = dev_path.get_attr() {
if attr.st.st_mode & libc::S_IFMT as c_uint == libc::S_IFBLK.as_() {
@@ -32,7 +33,9 @@ pub fn setup_mounts() {
// What we do instead is to scan through the current mountinfo and find a pre-existing
// mount point mounting our desired partition, and then bind mount the target folder.
let preinit_dev = attr.st.st_rdev;
let mnt_path = FsPathBuf::default().join(magisk_tmp).join(PREINITMIRR);
let mnt_path = cstr::buf::default()
.join_path(magisk_tmp)
.join_path(PREINITMIRR);
for info in parse_mount_info("self") {
if info.root == "/" && info.device == preinit_dev {
if !info.fs_option.split(',').any(|s| s == "rw") {
@@ -44,15 +47,10 @@ pub fn setup_mounts() {
let mut preinit_dir = resolve_preinit_dir(target);
let preinit_dir = Utf8CStr::from_string(&mut preinit_dir);
let r: LoggedResult<()> = try {
FsPath::from(preinit_dir).mkdir(0o700)?;
let mut buf = cstr_buf::default();
if mnt_path.parent(&mut buf) {
FsPath::from(&buf).mkdirs(0o755)?;
}
preinit_dir.mkdir(0o700)?;
mnt_path.mkdirs(0o755)?;
mnt_path.remove().ok();
unsafe {
libc::symlink(preinit_dir.as_ptr(), mnt_path.as_ptr()).as_os_err()?
}
mnt_path.create_symlink_to(preinit_dir)?;
};
if r.is_ok() {
linked = true;
@@ -70,54 +68,29 @@ pub fn setup_mounts() {
}
// Bind remount module root to clear nosuid
let module_mnt = FsPathBuf::default().join(magisk_tmp).join(MODULEMNT);
let module_mnt = cstr::buf::default()
.join_path(magisk_tmp)
.join_path(MODULEMNT);
let _: LoggedResult<()> = try {
module_mnt.mkdir(0o755)?;
unsafe {
libc::mount(
raw_cstr!(MODULEROOT),
module_mnt.as_ptr(),
ptr::null(),
libc::MS_BIND,
ptr::null(),
)
.as_os_err()?;
libc::mount(
ptr::null(),
module_mnt.as_ptr(),
ptr::null(),
libc::MS_REMOUNT | libc::MS_BIND | libc::MS_RDONLY,
ptr::null(),
)
.as_os_err()?;
}
cstr!(MODULEROOT).bind_mount_to(&module_mnt)?;
module_mnt.remount_mount_point_flags(libc::MS_RDONLY)?;
};
}
pub fn clean_mounts() {
let magisk_tmp = get_magisk_tmp();
let mut module_mnt = FsPathBuf::default().join(magisk_tmp).join(MODULEMNT);
let _: LoggedResult<()> = try {
unsafe {
libc::umount2(module_mnt.as_ptr(), libc::MNT_DETACH).as_os_err()?;
}
};
let mut buf = cstr::buf::default();
module_mnt.clear();
let worker_dir = module_mnt.join(magisk_tmp).join(WORKERDIR);
let module_mnt = buf.append_path(magisk_tmp).append_path(MODULEMNT);
module_mnt.unmount().log_ok();
buf.clear();
let worker_dir = buf.append_path(magisk_tmp).append_path(WORKERDIR);
let _: LoggedResult<()> = try {
unsafe {
libc::mount(
ptr::null(),
worker_dir.as_ptr(),
ptr::null(),
libc::MS_PRIVATE | libc::MS_REC,
ptr::null(),
)
.as_os_err()?;
libc::umount2(worker_dir.as_ptr(), libc::MNT_DETACH).as_os_err()?;
}
worker_dir.set_mount_private(true)?;
worker_dir.unmount()?;
};
}
@@ -213,32 +186,26 @@ pub fn find_preinit_device() -> String {
&& let Ok(tmp) = std::env::var("MAGISKTMP")
&& !tmp.is_empty()
{
let mut mirror_dir = FsPathBuf::default().join(&tmp).join(PREINITMIRR);
let preinit_dir = FsPath::from(Utf8CStr::from_string(&mut preinit_dir));
let mut buf = cstr::buf::default();
let mirror_dir = buf.append_path(&tmp).append_path(PREINITMIRR);
let preinit_dir = Utf8CStr::from_string(&mut preinit_dir);
let _: LoggedResult<()> = try {
preinit_dir.mkdirs(0o700)?;
let mut buf = cstr_buf::default();
if mirror_dir.parent(&mut buf) {
FsPath::from(&buf).mkdirs(0o755)?;
}
unsafe {
libc::umount2(mirror_dir.as_ptr(), libc::MNT_DETACH)
.as_os_err()
.ok(); // ignore error
mirror_dir.remove().ok();
libc::symlink(preinit_dir.as_ptr(), mirror_dir.as_ptr()).as_os_err()?;
}
mirror_dir.mkdirs(0o755)?;
mirror_dir.unmount().ok();
mirror_dir.remove().ok();
mirror_dir.create_symlink_to(preinit_dir)?;
};
if std::env::var_os("MAKEDEV").is_some() {
mirror_dir.clear();
let dev_path = mirror_dir.join(&tmp).join(PREINITDEV);
buf.clear();
let dev_path = buf.append_path(&tmp).append_path(PREINITDEV);
unsafe {
libc::mknod(
dev_path.as_ptr(),
libc::S_IFBLK | 0o600,
info.device as dev_t,
)
.as_os_err()
.check_io_err()
.log()
.ok();
}

View File

@@ -1,11 +1,11 @@
use crate::consts::{APP_PACKAGE_NAME, MAGISK_VER_CODE};
use crate::daemon::{to_app_id, MagiskD, AID_APP_END, AID_APP_START, AID_USER_OFFSET};
use crate::ffi::{get_magisk_tmp, install_apk, uninstall_pkg, DbEntryKey};
use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY};
use crate::daemon::{AID_APP_END, AID_APP_START, AID_USER_OFFSET, MagiskD, to_app_id};
use crate::ffi::{DbEntryKey, get_magisk_tmp, install_apk, uninstall_pkg};
use base::WalkResult::{Abort, Continue, Skip};
use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, O_TRUNC, O_WRONLY};
use base::{
cstr, cstr_buf, error, fd_get_attr, open_fd, warn, BufReadExt, Directory, FsPath, FsPathBuf,
LoggedResult, ReadExt, ResultExt, Utf8CStrBuf,
BufReadExt, Directory, FsPathBuilder, LoggedResult, ReadExt, ResultExt, Utf8CStrBuf,
Utf8CString, cstr, error, fd_get_attr, warn,
};
use bit_set::BitSet;
use cxx::CxxString;
@@ -14,14 +14,13 @@ use std::fs::File;
use std::io;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::os::fd::AsRawFd;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::time::Duration;
const EOCD_MAGIC: u32 = 0x06054B50;
const APK_SIGNING_BLOCK_MAGIC: [u8; 16] = *b"APK Sig Block 42";
const SIGNATURE_SCHEME_V2_MAGIC: u32 = 0x7109871A;
const PACKAGES_XML: &str = "/data/system/packages.xml";
macro_rules! bad_apk {
($msg:literal) => {
@@ -152,15 +151,16 @@ fn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {
res.log().unwrap_or(vec![])
}
fn find_apk_path(pkg: &str, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
fn find_apk_path(pkg: &str) -> LoggedResult<Utf8CString> {
let mut buf = cstr::buf::default();
Directory::open(cstr!("/data/app"))?.pre_order_walk(|e| {
if !e.is_dir() {
return Ok(Skip);
}
let name_bytes = e.name().to_bytes();
let name_bytes = e.name().as_bytes();
if name_bytes.starts_with(pkg.as_bytes()) && name_bytes[pkg.len()] == b'-' {
// Found the APK path, we can abort now
e.path(buf)?;
e.resolve_path(&mut buf)?;
return Ok(Abort);
}
if name_bytes.starts_with(b"~~") {
@@ -171,7 +171,7 @@ fn find_apk_path(pkg: &str, buf: &mut dyn Utf8CStrBuf) -> io::Result<()> {
if !buf.is_empty() {
buf.push_str("/base.apk");
}
Ok(())
Ok(buf.to_owned())
}
enum Status {
@@ -204,47 +204,41 @@ impl Default for ManagerInfo {
#[derive(Default)]
struct TrackedFile {
path: PathBuf,
path: Utf8CString,
timestamp: Duration,
}
impl TrackedFile {
fn new<T: AsRef<Path>>(path: T) -> TrackedFile {
fn inner(path: &Path) -> TrackedFile {
let meta = match path.metadata() {
Ok(meta) => meta,
Err(_) => return TrackedFile::default(),
};
let timestamp = Duration::new(meta.ctime() as u64, meta.ctime_nsec() as u32);
TrackedFile {
path: PathBuf::from(path),
timestamp,
}
}
inner(path.as_ref())
fn new(path: Utf8CString) -> TrackedFile {
let attr = match path.get_attr() {
Ok(attr) => attr,
Err(_) => return TrackedFile::default(),
};
let timestamp = Duration::new(attr.st.st_ctime as u64, attr.st.st_ctime_nsec as u32);
TrackedFile { path, timestamp }
}
fn is_same(&self) -> bool {
if self.path.as_os_str().is_empty() {
if self.path.is_empty() {
return false;
}
let meta = match self.path.metadata() {
Ok(meta) => meta,
let attr = match self.path.get_attr() {
Ok(attr) => attr,
Err(_) => return false,
};
let timestamp = Duration::new(meta.ctime() as u64, meta.ctime_nsec() as u32);
let timestamp = Duration::new(attr.st.st_ctime as u64, attr.st.st_ctime_nsec as u32);
timestamp == self.timestamp
}
}
impl ManagerInfo {
fn check_dyn(&mut self, daemon: &MagiskD, user: i32, pkg: &str) -> Status {
let apk = FsPathBuf::default()
.join(daemon.app_data_dir())
.join_fmt(user)
.join(pkg)
.join("dyn")
.join("current.apk");
let apk = cstr::buf::default()
.join_path(daemon.app_data_dir())
.join_path_fmt(user)
.join_path(pkg)
.join_path("dyn")
.join_path("current.apk");
let uid: i32;
let cert = match apk.open(O_RDONLY | O_CLOEXEC) {
Ok(mut fd) => {
@@ -268,16 +262,15 @@ impl ManagerInfo {
}
self.repackaged_app_id = to_app_id(uid);
self.tracked_files.insert(user, TrackedFile::new(apk));
self.tracked_files
.insert(user, TrackedFile::new(apk.to_owned()));
Status::Installed
}
fn check_stub(&mut self, user: i32, pkg: &str) -> Status {
let mut arr = cstr_buf::default();
if find_apk_path(pkg, &mut arr).is_err() {
let Ok(apk) = find_apk_path(pkg) else {
return Status::NotInstalled;
}
let apk = FsPath::from(&arr);
};
let cert = match apk.open(O_RDONLY | O_CLOEXEC) {
Ok(mut fd) => read_certificate(&mut fd, -1),
@@ -286,7 +279,7 @@ impl ManagerInfo {
if cert.is_empty() || (pkg == self.repackaged_pkg && cert != self.repackaged_cert) {
error!("pkg: repackaged APK signature invalid: {}", apk);
uninstall_pkg(apk);
uninstall_pkg(&apk);
return Status::CertMismatch;
}
@@ -298,11 +291,9 @@ impl ManagerInfo {
}
fn check_orig(&mut self, user: i32) -> Status {
let mut arr = cstr_buf::default();
if find_apk_path(APP_PACKAGE_NAME, &mut arr).is_err() {
let Ok(apk) = find_apk_path(APP_PACKAGE_NAME) else {
return Status::NotInstalled;
}
let apk = FsPath::from(&arr);
};
let cert = match apk.open(O_RDONLY | O_CLOEXEC) {
Ok(mut fd) => read_certificate(&mut fd, MAGISK_VER_CODE),
@@ -328,12 +319,9 @@ impl ManagerInfo {
let tmp_apk = cstr!("/data/stub.apk");
let result: LoggedResult<()> = try {
{
let mut tmp_fd = File::from(open_fd!(
tmp_apk,
O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
0o600
)?);
io::copy(stub_fd, &mut tmp_fd)?;
let mut tmp_apk_file =
tmp_apk.create(O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0o600)?;
io::copy(stub_fd, &mut tmp_apk_file)?;
}
// Seek the fd back to start
stub_fd.seek(SeekFrom::Start(0))?;
@@ -356,14 +344,14 @@ impl ManagerInfo {
&& file.is_same()
{
// no APK
if file.path == Path::new("/data/system/packages.xml") {
if install {
if &file.path == PACKAGES_XML {
if install && !daemon.is_emulator {
self.install_stub();
}
return (-1, "");
}
// dyn APK is still the same
if file.path.starts_with(daemon.app_data_dir()) {
if file.path.starts_with(daemon.app_data_dir().as_str()) {
return (
user * AID_USER_OFFSET + self.repackaged_app_id,
&self.repackaged_pkg,
@@ -402,7 +390,7 @@ impl ManagerInfo {
)
} else {
(-1, "")
}
};
}
Status::NotInstalled => {
daemon.rm_db_string(DbEntryKey::SuManager).ok();
@@ -432,9 +420,9 @@ impl ManagerInfo {
// If we cannot find any manager, track packages.xml for new package installs
self.tracked_files
.insert(user, TrackedFile::new("/data/system/packages.xml"));
.insert(user, TrackedFile::new(PACKAGES_XML.into()));
if install {
if install && !daemon.is_emulator {
self.install_stub();
}
(-1, "")
@@ -443,10 +431,10 @@ impl ManagerInfo {
impl MagiskD {
fn get_package_uid(&self, user: i32, pkg: &str) -> i32 {
let path = FsPathBuf::default()
.join(self.app_data_dir())
.join_fmt(user)
.join(pkg);
let path = cstr::buf::default()
.join_path(self.app_data_dir())
.join_path_fmt(user)
.join_path(pkg);
path.get_attr()
.map(|attr| attr.st.st_uid as i32)
.unwrap_or(-1)
@@ -455,7 +443,9 @@ impl MagiskD {
pub fn preserve_stub_apk(&self) {
let mut info = self.manager_info.lock().unwrap();
let apk = FsPathBuf::default().join(get_magisk_tmp()).join("stub.apk");
let apk = cstr::buf::default()
.join_path(get_magisk_tmp())
.join_path("stub.apk");
if let Ok(mut fd) = apk.open(O_RDONLY | O_CLOEXEC) {
info.trusted_cert = read_certificate(&mut fd, MAGISK_VER_CODE);
@@ -518,7 +508,9 @@ impl MagiskD {
match user_dir.read()? {
None => break,
Some(e) => {
let attr = e.get_attr()?;
let mut entry_path = cstr::buf::default();
e.resolve_path(&mut entry_path)?;
let attr = entry_path.get_attr()?;
let app_id = to_app_id(attr.st.st_uid as i32);
if (AID_APP_START..=AID_APP_END).contains(&app_id) {
let app_no = app_id - AID_APP_START;

View File

@@ -9,15 +9,15 @@ use std::{
use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};
use crate::ffi::{prop_cb_exec, PropCb};
use crate::ffi::{PropCb, prop_cb_exec};
use crate::resetprop::proto::persistent_properties::{
mod_PersistentProperties::PersistentPropertyRecord, PersistentProperties,
PersistentProperties, mod_PersistentProperties::PersistentPropertyRecord,
};
use base::const_format::concatcp;
use base::libc::{O_CLOEXEC, O_RDONLY};
use base::{
clone_attr, cstr, debug, libc::mkstemp, path, Directory, FsPathBuf, LibcReturn, LoggedResult,
MappedFile, SilentResultExt, Utf8CStr, WalkResult,
Directory, FsPathBuilder, LibcReturn, LoggedResult, MappedFile, SilentResultExt, Utf8CStr,
Utf8CStrBuf, WalkResult, clone_attr, cstr, debug, libc::mkstemp,
};
const PERSIST_PROP_DIR: &str = "/data/property";
@@ -64,11 +64,13 @@ impl PropExt for PersistentProperties {
}
fn check_proto() -> bool {
path!(PERSIST_PROP).exists()
cstr!(PERSIST_PROP).exists()
}
fn file_get_prop(name: &Utf8CStr) -> LoggedResult<String> {
let path = FsPathBuf::default().join(PERSIST_PROP_DIR).join(name);
let path = cstr::buf::default()
.join_path(PERSIST_PROP_DIR)
.join_path(name);
let mut file = path.open(O_RDONLY | O_CLOEXEC).silent()?;
debug!("resetprop: read prop from [{}]", path);
let mut s = String::new();
@@ -77,20 +79,23 @@ fn file_get_prop(name: &Utf8CStr) -> LoggedResult<String> {
}
fn file_set_prop(name: &Utf8CStr, value: Option<&Utf8CStr>) -> LoggedResult<()> {
let path = FsPathBuf::default().join(PERSIST_PROP_DIR).join(name);
let path = cstr::buf::default()
.join_path(PERSIST_PROP_DIR)
.join_path(name);
if let Some(value) = value {
let mut tmp = FsPathBuf::default()
.join(PERSIST_PROP_DIR)
.join("prop.XXXXXX");
let mut tmp = cstr::buf::default()
.join_path(PERSIST_PROP_DIR)
.join_path("prop.XXXXXX");
{
let mut f = unsafe {
let fd = mkstemp(tmp.as_mut_ptr()).check_os_err()?;
File::from_raw_fd(fd)
mkstemp(tmp.as_mut_ptr())
.as_os_result("mkstemp", None, None)
.map(|fd| File::from_raw_fd(fd))?
};
f.write_all(value.as_bytes())?;
}
debug!("resetprop: write prop to [{}]", tmp);
tmp.rename_to(path)?
tmp.rename_to(&path)?
} else {
path.remove().silent()?;
debug!("resetprop: unlink [{}]", path);
@@ -110,16 +115,17 @@ fn proto_read_props() -> LoggedResult<PersistentProperties> {
}
fn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> {
let mut tmp = FsPathBuf::default().join(concatcp!(PERSIST_PROP, ".XXXXXX"));
let mut tmp = cstr::buf::default().join_path(concatcp!(PERSIST_PROP, ".XXXXXX"));
{
let f = unsafe {
let fd = mkstemp(tmp.as_mut_ptr()).check_os_err()?;
File::from_raw_fd(fd)
mkstemp(tmp.as_mut_ptr())
.as_os_result("mkstemp", None, None)
.map(|fd| File::from_raw_fd(fd))?
};
debug!("resetprop: encode with protobuf [{}]", tmp);
props.write_message(&mut Writer::new(BufWriter::new(f)))?;
}
clone_attr(path!(PERSIST_PROP), &tmp)?;
clone_attr(cstr!(PERSIST_PROP), &tmp)?;
tmp.rename_to(cstr!(PERSIST_PROP))?;
Ok(())
}
@@ -162,10 +168,8 @@ pub fn persist_get_props(mut prop_cb: Pin<&mut PropCb>) {
let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR))?;
dir.pre_order_walk(|e| {
if e.is_file() {
if let Ok(name) = Utf8CStr::from_cstr(e.name()) {
if let Ok(mut value) = file_get_prop(name) {
prop_cb.exec(name, Utf8CStr::from_string(&mut value));
}
if let Ok(mut value) = file_get_prop(e.name()) {
prop_cb.exec(e.name(), Utf8CStr::from_string(&mut value));
}
}
// Do not traverse recursively

View File

@@ -4,7 +4,6 @@
#include <consts.hpp>
#include <base.hpp>
#include <selinux.hpp>
#include <core.hpp>
using namespace std;

View File

@@ -1,139 +0,0 @@
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/xattr.h>
#include <consts.hpp>
#include <base.hpp>
#include <selinux.hpp>
#include <core.hpp>
#include <flags.h>
using namespace std;
int setcon(const char *con) {
int fd = open("/proc/self/attr/current", O_WRONLY | O_CLOEXEC);
if (fd < 0)
return fd;
size_t len = strlen(con) + 1;
int rc = write(fd, con, len);
close(fd);
return rc != len;
}
int getfilecon(const char *path, byte_data con) {
return syscall(__NR_getxattr, path, XATTR_NAME_SELINUX, con.buf(), con.sz());
}
int lgetfilecon(const char *path, byte_data con) {
return syscall(__NR_lgetxattr, path, XATTR_NAME_SELINUX, con.buf(), con.sz());
}
int fgetfilecon(int fd, byte_data con) {
return syscall(__NR_fgetxattr, fd, XATTR_NAME_SELINUX, con.buf(), con.sz());
}
int setfilecon(const char *path, const char *con) {
return syscall(__NR_setxattr, path, XATTR_NAME_SELINUX, con, strlen(con) + 1, 0);
}
int lsetfilecon(const char *path, const char *con) {
return syscall(__NR_lsetxattr, path, XATTR_NAME_SELINUX, con, strlen(con) + 1, 0);
}
int fsetfilecon(int fd, const char *con) {
return syscall(__NR_fsetxattr, fd, XATTR_NAME_SELINUX, con, strlen(con) + 1, 0);
}
int getfilecon_at(int dirfd, const char *name, byte_data con) {
char path[4096];
fd_pathat(dirfd, name, path, sizeof(path));
return lgetfilecon(path, con);
}
void setfilecon_at(int dirfd, const char *name, const char *con) {
char path[4096];
fd_pathat(dirfd, name, path, sizeof(path));
lsetfilecon(path, con);
}
#define UNLABEL_CON "u:object_r:unlabeled:s0"
#define SYSTEM_CON "u:object_r:system_file:s0"
#define ADB_CON "u:object_r:adb_data_file:s0"
#define ROOT_CON "u:object_r:rootfs:s0"
static void restore_syscon_from_null(int dirfd) {
struct dirent *entry;
char con[1024];
if (fgetfilecon(dirfd, { con, sizeof(con) }) >= 0) {
if (con[0] == '\0' || strcmp(con, UNLABEL_CON) == 0)
fsetfilecon(dirfd, SYSTEM_CON);
}
auto dir = xopen_dir(dirfd);
while ((entry = xreaddir(dir.get()))) {
int fd = openat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
if (entry->d_type == DT_DIR) {
restore_syscon_from_null(fd);
continue;
} else if (entry->d_type == DT_REG) {
if (fgetfilecon(fd, { con, sizeof(con) }) >= 0) {
if (con[0] == '\0' || strcmp(con, UNLABEL_CON) == 0)
fsetfilecon(fd, SYSTEM_CON);
}
} else if (entry->d_type == DT_LNK) {
if (getfilecon_at(dirfd, entry->d_name, { con, sizeof(con) }) >= 0) {
if (con[0] == '\0' || strcmp(con, UNLABEL_CON) == 0)
setfilecon_at(dirfd, entry->d_name, SYSTEM_CON);
}
}
close(fd);
}
}
static void restore_syscon(int dirfd) {
struct dirent *entry;
fsetfilecon(dirfd, SYSTEM_CON);
fchown(dirfd, 0, 0);
auto dir = xopen_dir(dirfd);
while ((entry = xreaddir(dir.get()))) {
int fd = xopenat(dirfd, entry->d_name, O_RDONLY | O_CLOEXEC);
if (entry->d_type == DT_DIR) {
restore_syscon(fd);
continue;
} else if (entry->d_type) {
fsetfilecon(fd, SYSTEM_CON);
fchown(fd, 0, 0);
}
close(fd);
}
}
void restorecon() {
int fd = xopen("/sys/fs/selinux/context", O_WRONLY | O_CLOEXEC);
if (write(fd, ADB_CON, sizeof(ADB_CON)) >= 0)
lsetfilecon(SECURE_DIR, ADB_CON);
close(fd);
lsetfilecon(MODULEROOT, SYSTEM_CON);
restore_syscon_from_null(xopen(MODULEROOT, O_RDONLY | O_CLOEXEC));
restore_syscon(xopen(DATABIN, O_RDONLY | O_CLOEXEC));
}
void restore_tmpcon() {
const char *tmp = get_magisk_tmp();
if (tmp == "/sbin"sv)
setfilecon(tmp, ROOT_CON);
else
chmod(tmp, 0711);
auto dir = xopen_dir(tmp);
int dfd = dirfd(dir.get());
for (dirent *entry; (entry = xreaddir(dir.get()));)
setfilecon_at(dfd, entry->d_name, SYSTEM_CON);
string logd = tmp + "/"s LOG_PIPE;
setfilecon(logd.data(), MAGISK_LOG_CON);
}

108
native/src/core/selinux.rs Normal file
View File

@@ -0,0 +1,108 @@
use crate::consts::{DATABIN, LOG_PIPE, MAGISK_LOG_CON, MODULEROOT, SECURE_DIR};
use crate::ffi::get_magisk_tmp;
use base::libc::{O_CLOEXEC, O_WRONLY};
use base::{Directory, FsPathBuilder, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, libc};
use std::io::Write;
const UNLABEL_CON: &Utf8CStr = cstr!("u:object_r:unlabeled:s0");
const SYSTEM_CON: &Utf8CStr = cstr!("u:object_r:system_file:s0");
const ADB_CON: &Utf8CStr = cstr!("u:object_r:adb_data_file:s0");
const ROOT_CON: &Utf8CStr = cstr!("u:object_r:rootfs:s0");
fn restore_syscon_from_unlabeled(
path: &mut dyn Utf8CStrBuf,
con: &mut dyn Utf8CStrBuf,
) -> LoggedResult<()> {
let dir_path_len = path.len();
if path.get_secontext(con).log().is_ok() && con.as_str() == UNLABEL_CON {
path.set_secontext(SYSTEM_CON)?;
}
let mut dir = Directory::open(path)?;
while let Some(ref e) = dir.read()? {
path.truncate(dir_path_len);
path.append_path(e.name());
if e.is_dir() {
restore_syscon_from_unlabeled(path, con)?;
} else if (e.is_file() || e.is_symlink())
&& path.get_secontext(con).log().is_ok()
&& con.as_str() == UNLABEL_CON
{
path.set_secontext(SYSTEM_CON)?;
}
}
Ok(())
}
fn restore_syscon(path: &mut dyn Utf8CStrBuf) -> LoggedResult<()> {
let dir_path_len = path.len();
path.set_secontext(SYSTEM_CON)?;
unsafe { libc::lchown(path.as_ptr(), 0, 0) };
let mut dir = Directory::open(path)?;
while let Some(ref e) = dir.read()? {
path.truncate(dir_path_len);
path.append_path(e.name());
if e.is_dir() {
restore_syscon(path)?;
} else if e.is_file() || e.is_symlink() {
path.set_secontext(SYSTEM_CON)?;
unsafe { libc::lchown(path.as_ptr(), 0, 0) };
}
}
Ok(())
}
pub(crate) fn restorecon() {
if let Ok(mut file) = cstr!("/sys/fs/selinux/context")
.open(O_WRONLY | O_CLOEXEC)
.log()
{
if file.write_all(ADB_CON.as_bytes_with_nul()).is_ok() {
cstr!(SECURE_DIR).set_secontext(ADB_CON).log_ok();
}
}
let mut path = cstr::buf::default();
let mut con = cstr::buf::new::<1024>();
path.push_str(MODULEROOT);
path.set_secontext(SYSTEM_CON).log_ok();
restore_syscon_from_unlabeled(&mut path, &mut con).log_ok();
path.clear();
path.push_str(DATABIN);
restore_syscon(&mut path).log_ok();
}
pub(crate) fn restore_tmpcon() -> LoggedResult<()> {
let tmp = get_magisk_tmp();
if tmp == "/sbin" {
tmp.set_secontext(ROOT_CON)?;
} else {
unsafe { libc::chmod(tmp.as_ptr(), 0o711) };
}
let mut path = cstr::buf::default();
let mut dir = Directory::open(tmp)?;
while let Some(ref e) = dir.read()? {
e.resolve_path(&mut path)?;
path.set_secontext(SYSTEM_CON)?;
}
path.clear();
path.append_path(tmp).append_path(LOG_PIPE);
path.set_secontext(cstr!(MAGISK_LOG_CON))?;
Ok(())
}
pub(crate) fn lgetfilecon(path: &Utf8CStr, con: &mut [u8]) -> bool {
let mut con = cstr::buf::wrap(con);
path.get_secontext(&mut con).is_ok()
}
pub(crate) fn setfilecon(path: &Utf8CStr, con: &Utf8CStr) -> bool {
path.follow_link().set_secontext(con).is_ok()
}
pub(crate) fn lsetfilecon(path: &Utf8CStr, con: &Utf8CStr) -> bool {
path.set_secontext(con).is_ok()
}

View File

@@ -1,5 +1,5 @@
use base::{libc, warn, ReadExt, ResultExt, WriteExt};
use bytemuck::{bytes_of, bytes_of_mut, Zeroable};
use base::{ReadExt, ResultExt, WriteExt, libc, warn};
use bytemuck::{Zeroable, bytes_of, bytes_of_mut};
use std::io;
use std::io::{ErrorKind, IoSlice, IoSliceMut, Read, Write};
use std::mem::ManuallyDrop;

View File

@@ -2,7 +2,6 @@
#include <sys/wait.h>
#include <base.hpp>
#include <selinux.hpp>
#include <consts.hpp>
#include <core.hpp>

View File

@@ -1,12 +1,12 @@
use crate::daemon::{to_app_id, to_user_id, MagiskD, AID_ROOT, AID_SHELL};
use crate::UCred;
use crate::daemon::{AID_ROOT, AID_SHELL, MagiskD, to_app_id, to_user_id};
use crate::db::{DbSettings, MultiuserMode, RootAccess};
use crate::ffi::{
app_log, app_notify, app_request, exec_root_shell, SuAppRequest, SuPolicy, SuRequest,
SuAppRequest, SuPolicy, SuRequest, app_log, app_notify, app_request, exec_root_shell,
};
use crate::socket::IpcRead;
use crate::su::db::RootSettings;
use crate::UCred;
use base::{debug, error, exit_on_error, libc, warn, LoggedResult, ResultExt, WriteExt};
use base::{LoggedResult, ResultExt, WriteExt, debug, error, exit_on_error, libc, warn};
use std::fs::File;
use std::os::fd::{FromRawFd, IntoRawFd};
use std::os::unix::net::UnixStream;

View File

@@ -1,5 +1,5 @@
use crate::daemon::{
to_app_id, to_user_id, MagiskD, AID_APP_END, AID_APP_START, AID_ROOT, AID_SHELL,
AID_APP_END, AID_APP_START, AID_ROOT, AID_SHELL, MagiskD, to_app_id, to_user_id,
};
use crate::db::DbArg::Integer;
use crate::db::{MultiuserMode, RootAccess, SqlTable, SqliteResult, SqliteReturn};

View File

@@ -1,4 +1,6 @@
mod daemon;
mod db;
mod pts;
pub use daemon::SuInfo;
pub use pts::{get_pty_num, pump_tty, restore_stdin};

View File

@@ -1,294 +0,0 @@
/*
* Copyright 2013, Tan Chee Eng (@tan-ce)
*/
/*
* pts.c
*
* Manages the pseudo-terminal driver on Linux/Android and provides some
* helper functions to handle raw input mode and terminal window resizing
*/
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <base.hpp>
#include "pts.hpp"
/**
* Helper functions
*/
// Ensures all the data is written out
static int write_blocking(int fd, char *buf, ssize_t bufsz) {
ssize_t ret, written;
written = 0;
do {
ret = write(fd, buf + written, bufsz - written);
if (ret == -1) return -1;
written += ret;
} while (written < bufsz);
return 0;
}
/**
* Pump data from input FD to output FD. If close_output is
* true, then close the output FD when we're done.
*/
static void pump(int input, int output, bool close_output = true) {
char buf[4096];
int len;
while ((len = read(input, buf, 4096)) > 0) {
if (write_blocking(output, buf, len) == -1) break;
}
close(input);
if (close_output) close(output);
}
static void* pump_thread(void* data) {
int *fds = (int*) data;
pump(fds[0], fds[1]);
delete[] fds;
return nullptr;
}
static void pump_async(int input, int output) {
pthread_t writer;
int *fds = new int[2];
fds[0] = input;
fds[1] = output;
pthread_create(&writer, nullptr, pump_thread, fds);
}
/**
* pts_open
*
* Opens a pts device and returns the name of the slave tty device.
*
* Arguments
* slave_name the name of the slave device
* slave_name_size the size of the buffer passed via slave_name
*
* Return Values
* on failure either -2 or -1 (errno set) is returned.
* on success, the file descriptor of the master device is returned.
*/
int pts_open(char *slave_name, size_t slave_name_size) {
int fdm;
// Open master ptmx device
fdm = open("/dev/ptmx", O_RDWR);
if (fdm == -1)
goto error;
// Get the slave name
if (ptsname_r(fdm, slave_name, slave_name_size - 1))
goto error;
slave_name[slave_name_size - 1] = '\0';
// Grant, then unlock
if (grantpt(fdm) == -1)
goto error;
if (unlockpt(fdm) == -1)
goto error;
return fdm;
error:
close(fdm);
PLOGE("pts_open");
return -1;
}
int get_pty_num(int fd) {
int pty_num = -1;
if (ioctl(fd, TIOCGPTN, &pty_num) != 0) {
LOGW("get_pty_num failed with %d: %s\n", errno, std::strerror(errno));
return -1;
}
return pty_num;
}
// Stores the previous termios of stdin
static struct termios old_stdin;
static int stdin_is_raw = 0;
/**
* set_stdin_raw
*
* Changes stdin to raw unbuffered mode, disables echo,
* auto carriage return, etc.
*
* Return Value
* on failure -1, and errno is set
* on success 0
*/
int set_stdin_raw() {
struct termios termios{};
if (tcgetattr(STDIN_FILENO, &termios) < 0) {
return -1;
}
old_stdin = termios;
cfmakeraw(&termios);
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios) < 0) {
// https://blog.zhanghai.me/fixing-line-editing-on-android-8-0/
if (tcsetattr(STDIN_FILENO, TCSADRAIN, &termios) < 0) {
return -1;
}
}
stdin_is_raw = 1;
return 0;
}
/**
* restore_stdin
*
* Restore termios on stdin to the state it was before
* set_stdin_raw() was called. If set_stdin_raw() was
* never called, does nothing and doesn't return an error.
*
* This function is async-safe.
*
* Return Value
* on failure, -1 and errno is set
* on success, 0
*/
int restore_stdin() {
if (!stdin_is_raw) return 0;
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
if (tcsetattr(STDIN_FILENO, TCSADRAIN, &old_stdin) < 0) {
return -1;
}
}
stdin_is_raw = 0;
return 0;
}
// Flag indicating whether the sigwinch watcher should terminate.
static volatile bool close_sigwinch_watcher = false;
/**
* Thread process. Wait for a SIGWINCH to be received, then update
* the terminal size.
*/
static void *watch_sigwinch(void *data) {
sigset_t winch;
int *fds = (int *)data;
int sig;
sigemptyset(&winch);
sigaddset(&winch, SIGWINCH);
pthread_sigmask(SIG_UNBLOCK, &winch, nullptr);
do {
if (close_sigwinch_watcher)
break;
// Get the new terminal size
struct winsize w;
if (ioctl(fds[0], TIOCGWINSZ, &w) == -1)
continue;
// Set the new terminal size
ioctl(fds[1], TIOCSWINSZ, &w);
} while (sigwait(&winch, &sig) == 0);
delete[] fds;
return nullptr;
}
/**
* watch_sigwinch_async
*
* After calling this function, if the application receives
* SIGWINCH, the terminal window size will be read from
* "input" and set on "output".
*
* NOTE: This function blocks SIGWINCH and spawns a thread.
* NOTE 2: This function must be called before any of the
* pump functions.
*
* Arguments
* master A file descriptor of the TTY window size to follow
* slave A file descriptor of the TTY window size which is
* to be set on SIGWINCH
*
* Return Value
* on failure, -1 and errno will be set. In this case, no
* thread has been spawned and SIGWINCH will not be
* blocked.
* on success, 0
*/
int watch_sigwinch_async(int master, int slave) {
pthread_t watcher;
int *fds = new int[2];
// Block SIGWINCH so sigwait can later receive it
sigset_t winch;
sigemptyset(&winch);
sigaddset(&winch, SIGWINCH);
if (pthread_sigmask(SIG_BLOCK, &winch, nullptr) == -1) {
delete[] fds;
return -1;
}
// Initialize some variables, then start the thread
close_sigwinch_watcher = 0;
fds[0] = master;
fds[1] = slave;
int ret = pthread_create(&watcher, nullptr, &watch_sigwinch, fds);
if (ret != 0) {
delete[] fds;
errno = ret;
return -1;
}
return 0;
}
/**
* pump_stdin_async
*
* Forward data from STDIN to the given FD
* in a separate thread
*/
void pump_stdin_async(int outfd) {
// Put stdin into raw mode
set_stdin_raw();
// Pump data from stdin to the PTY
pump_async(STDIN_FILENO, outfd);
}
/**
* pump_stdout_blocking
*
* Forward data from the FD to STDOUT.
* Returns when the remote end of the FD closes.
*
* Before returning, restores stdin settings.
*/
void pump_stdout_blocking(int infd) {
// Pump data from stdout to PTY
pump(infd, STDOUT_FILENO, false /* Don't close output when done */);
// Cleanup
restore_stdin();
close_sigwinch_watcher = true;
raise(SIGWINCH);
}

View File

@@ -1,102 +0,0 @@
/*
* Copyright 2013, Tan Chee Eng (@tan-ce)
*/
/*
* pts.hpp
*
* Manages the pseudo-terminal driver on Linux/Android and provides some
* helper functions to handle raw input mode and terminal window resizing
*/
#ifndef _PTS_H_
#define _PTS_H_
#include <sys/types.h>
/**
* pts_open
*
* Opens a pts device and returns the name of the slave tty device.
*
* Arguments
* slave_name the name of the slave device
* slave_name_size the size of the buffer passed via slave_name
*
* Return Values
* on failure either -2 or -1 (errno set) is returned.
* on success, the file descriptor of the master device is returned.
*/
int pts_open(char *slave_name, size_t slave_name_size);
int get_pty_num(int fd);
/**
* set_stdin_raw
*
* Changes stdin to raw unbuffered mode, disables echo,
* auto carriage return, etc.
*
* Return Value
* on failure -1, and errno is set
* on success 0
*/
int set_stdin_raw(void);
/**
* restore_stdin
*
* Restore termios on stdin to the state it was before
* set_stdin_raw() was called. If set_stdin_raw() was
* never called, does nothing and doesn't return an error.
*
* This function is async-safe.
*
* Return Value
* on failure, -1 and errno is set
* on success, 0
*/
int restore_stdin(void);
/**
* watch_sigwinch_async
*
* After calling this function, if the application receives
* SIGWINCH, the terminal window size will be read from
* "input" and set on "output".
*
* NOTE: This function blocks SIGWINCH and spawns a thread.
*
* Arguments
* master A file descriptor of the TTY window size to follow
* slave A file descriptor of the TTY window size which is
* to be set on SIGWINCH
*
* Return Value
* on failure, -1 and errno will be set. In this case, no
* thread has been spawned and SIGWINCH will not be
* blocked.
* on success, 0
*/
int watch_sigwinch_async(int master, int slave);
/**
* pump_stdin_async
*
* Forward data from STDIN to the given FD
* in a separate thread
*/
void pump_stdin_async(int outfd);
/**
* pump_stdout_blocking
*
* Forward data from the FD to STDOUT.
* Returns when the remote end of the FD closes.
*
* Before returning, restores stdin settings.
*/
void pump_stdout_blocking(int infd);
#endif

151
native/src/core/su/pts.rs Normal file
View File

@@ -0,0 +1,151 @@
use base::{
ResultExt, error,
libc::{
POLLIN, SFD_CLOEXEC, SIG_BLOCK, SIGWINCH, TCSADRAIN, TCSAFLUSH, TIOCGWINSZ, TIOCSWINSZ,
cfmakeraw, close, poll, pollfd, raise, sigaddset, sigemptyset, signalfd, sigprocmask,
sigset_t, tcsetattr, winsize,
},
libc::{STDIN_FILENO, STDOUT_FILENO, tcgetattr, termios},
warn,
};
use std::fs::File;
use std::io::{Read, Write};
use std::mem::ManuallyDrop;
use std::os::fd::{FromRawFd, RawFd};
use std::ptr::null_mut;
static mut OLD_STDIN: Option<termios> = None;
const TIOCGPTN: u32 = 0x80045430;
unsafe extern "C" {
// Don't use the declaration from the libc crate as request should be u32 not i32
fn ioctl(fd: RawFd, request: u32, ...) -> i32;
}
pub fn get_pty_num(fd: i32) -> i32 {
let mut pty_num = -1i32;
if unsafe { ioctl(fd, TIOCGPTN, &mut pty_num) } != 0 {
warn!("Failed to get pty number");
}
pty_num
}
fn set_stdin_raw() -> bool {
unsafe {
let mut termios: termios = std::mem::zeroed();
if tcgetattr(STDIN_FILENO, &mut termios) < 0 {
return false;
}
let old_c_oflag = termios.c_oflag;
OLD_STDIN = Some(termios);
cfmakeraw(&mut termios);
// don't modify output flags, since we are not setting stdout raw
termios.c_oflag = old_c_oflag;
if tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios) < 0
&& tcsetattr(STDIN_FILENO, TCSADRAIN, &termios) < 0
{
warn!("Failed to set terminal attributes");
return false;
}
}
true
}
pub fn restore_stdin() -> bool {
unsafe {
if let Some(ref termios) = OLD_STDIN {
if tcsetattr(STDIN_FILENO, TCSAFLUSH, termios) < 0
&& tcsetattr(STDIN_FILENO, TCSADRAIN, termios) < 0
{
warn!("Failed to restore terminal attributes");
return false;
}
}
OLD_STDIN = None;
true
}
}
fn resize_pty(outfd: i32) {
let mut ws: winsize = unsafe { std::mem::zeroed() };
if unsafe { ioctl(STDIN_FILENO, TIOCGWINSZ as u32, &mut ws) } >= 0 {
unsafe { ioctl(outfd, TIOCSWINSZ as u32, &ws) };
}
}
pub fn pump_tty(infd: i32, outfd: i32) {
set_stdin_raw();
let sfd = unsafe {
let mut mask: sigset_t = std::mem::zeroed();
sigemptyset(&mut mask);
sigaddset(&mut mask, SIGWINCH);
if sigprocmask(SIG_BLOCK, &mask, null_mut()) < 0 {
error!("sigprocmask");
}
signalfd(-1, &mask, SFD_CLOEXEC)
};
resize_pty(outfd);
let mut pfds = [
pollfd {
fd: if outfd > 0 { STDIN_FILENO } else { -1 },
events: POLLIN,
revents: 0,
},
pollfd {
fd: infd,
events: POLLIN,
revents: 0,
},
pollfd {
fd: sfd,
events: POLLIN,
revents: 0,
},
];
let mut buf = [0u8; 4096];
'poll: loop {
let ready = unsafe { poll(pfds.as_mut_ptr(), pfds.len() as _, -1) };
if ready < 0 {
error!("poll error");
break;
}
for pfd in &pfds {
if pfd.revents & POLLIN != 0 {
let mut in_file = ManuallyDrop::new(unsafe { File::from_raw_fd(pfd.fd) });
let Ok(n) = in_file.read(&mut buf) else {
error!("read error");
break 'poll;
};
if pfd.fd == STDIN_FILENO {
let mut out = ManuallyDrop::new(unsafe { File::from_raw_fd(outfd) });
out.write_all(&buf[..n]).log_ok();
} else if pfd.fd == infd {
let mut out = ManuallyDrop::new(unsafe { File::from_raw_fd(STDOUT_FILENO) });
out.write_all(&buf[..n]).log_ok();
} else if pfd.fd == sfd {
resize_pty(outfd);
}
} else if pfd.revents != 0 && pfd.fd == infd {
unsafe { close(pfd.fd) };
break 'poll;
}
}
}
restore_stdin();
unsafe { raise(SIGWINCH) };
}

View File

@@ -20,8 +20,6 @@
#include <flags.h>
#include <core.hpp>
#include "pts.hpp"
using namespace std;
#define DEFAULT_SHELL "/system/bin/sh"
@@ -41,10 +39,11 @@ int quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGI
"Usage: su [options] [-] [user [argument...]]\n\n"
"Options:\n"
" -c, --command COMMAND Pass COMMAND to the invoked shell\n"
" -i, --interactive Force pseudo-terminal allocation when using -c\n"
" -g, --group GROUP Specify the primary group\n"
" -G, --supp-group GROUP Specify a supplementary group.\n"
" -G, --supp-group GROUP Specify a supplementary group\n"
" The first specified supplementary group is also used\n"
" as a primary group if the option -g is not specified.\n"
" as a primary group if the option -g is not specified\n"
" -Z, --context CONTEXT Change SELinux context\n"
" -t, --target PID PID to take mount namespace from\n"
" -h, --help Display this help message and exit\n"
@@ -105,6 +104,7 @@ int su_client_main(int argc, char *argv[]) {
{ "target", required_argument, nullptr, 't' },
{ "group", required_argument, nullptr, 'g' },
{ "supp-group", required_argument, nullptr, 'G' },
{ "interactive", no_argument, nullptr, 'i' },
{ nullptr, 0, nullptr, 0 },
};
@@ -119,7 +119,9 @@ int su_client_main(int argc, char *argv[]) {
strcpy(argv[i], "-M");
}
while ((c = getopt_long(argc, argv, "c:hlmps:VvuZ:Mt:g:G:", long_opts, nullptr)) != -1) {
bool interactive = false;
while ((c = getopt_long(argc, argv, "c:hlimps:VvuZ:Mt:g:G:", long_opts, nullptr)) != -1) {
switch (c) {
case 'c': {
string command;
@@ -134,6 +136,9 @@ int su_client_main(int argc, char *argv[]) {
}
case 'h':
usage(EXIT_SUCCESS);
case 'i':
interactive = true;
break;
case 'l':
req.login = true;
break;
@@ -219,10 +224,11 @@ int su_client_main(int argc, char *argv[]) {
}
// Determine which one of our streams are attached to a TTY
interactive |= req.command.empty();
int atty = 0;
if (isatty(STDIN_FILENO)) atty |= ATTY_IN;
if (isatty(STDOUT_FILENO)) atty |= ATTY_OUT;
if (isatty(STDERR_FILENO)) atty |= ATTY_ERR;
if (isatty(STDIN_FILENO) && interactive) atty |= ATTY_IN;
if (isatty(STDOUT_FILENO) && interactive) atty |= ATTY_OUT;
if (isatty(STDERR_FILENO) && interactive) atty |= ATTY_ERR;
// Send stdin
send_fd(fd, (atty & ATTY_IN) ? -1 : STDIN_FILENO);
@@ -241,9 +247,9 @@ int su_client_main(int argc, char *argv[]) {
if (atty) {
setup_sighandlers(sighandler);
watch_sigwinch_async(STDOUT_FILENO, ptmx);
pump_stdin_async(ptmx);
pump_stdout_blocking(ptmx);
// if stdin is not a tty, if we pump to ptmx, our process may intercept the input to ptmx and
// output to stdout, which cause the target process lost input.
pump_tty(ptmx, (atty & ATTY_IN) ? ptmx : -1);
}
// Get the exit code

View File

@@ -1,13 +1,13 @@
use crate::consts::MODULEROOT;
use crate::daemon::{to_user_id, MagiskD};
use crate::daemon::{MagiskD, to_user_id};
use crate::ffi::{
get_magisk_tmp, restore_zygisk_prop, update_deny_flags, ZygiskRequest, ZygiskStateFlags,
ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, restore_zygisk_prop, update_deny_flags,
};
use crate::socket::{IpcRead, UnixSocketExt};
use base::libc::{O_CLOEXEC, O_CREAT, O_RDONLY, STDOUT_FILENO};
use base::{
cstr, cstr_buf, error, fork_dont_care, libc, open_fd, raw_cstr, warn, Directory, FsPathBuf,
LoggedError, LoggedResult, ResultExt, WriteExt,
Directory, FsPathBuilder, LoggedError, LoggedResult, ResultExt, WriteExt, cstr, error,
fork_dont_care, libc, raw_cstr, warn,
};
use std::fmt::Write;
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
@@ -37,9 +37,11 @@ fn exec_zygiskd(is_64_bit: bool, remote: UnixStream) {
#[cfg(target_pointer_width = "32")]
let magisk = "magisk";
let exe = FsPathBuf::<64>::new().join(get_magisk_tmp()).join(magisk);
let exe = cstr::buf::new::<64>()
.join_path(get_magisk_tmp())
.join_path(magisk);
let mut fd_str = cstr_buf::new::<16>();
let mut fd_str = cstr::buf::new::<16>();
write!(fd_str, "{}", remote.as_raw_fd()).ok();
unsafe {
libc::execl(
@@ -183,13 +185,13 @@ impl MagiskD {
let failed_ids: Vec<i32> = client.read_decodable()?;
if let Some(module_list) = self.module_list.get() {
for id in failed_ids {
let path = FsPathBuf::default()
.join(MODULEROOT)
.join(&module_list[id as usize].name)
.join("zygisk");
let path = cstr::buf::default()
.join_path(MODULEROOT)
.join_path(&module_list[id as usize].name)
.join_path("zygisk");
// Create the unloaded marker file
if let Ok(dir) = Directory::open(&path) {
dir.open_fd(cstr!("unloaded"), O_CREAT | O_RDONLY, 0o644)
dir.open_as_file_at(cstr!("unloaded"), O_CREAT | O_RDONLY, 0o644)
.log()
.ok();
}
@@ -202,8 +204,10 @@ impl MagiskD {
fn get_mod_dir(&self, mut client: UnixStream) -> LoggedResult<()> {
let id: i32 = client.read_decodable()?;
let module = &self.module_list.get().unwrap()[id as usize];
let dir = FsPathBuf::default().join(MODULEROOT).join(&module.name);
let fd = open_fd!(&dir, O_RDONLY | O_CLOEXEC)?;
let dir = cstr::buf::default()
.join_path(MODULEROOT)
.join_path(&module.name);
let fd = dir.open(O_RDONLY | O_CLOEXEC)?;
client.send_fds(&[fd.as_raw_fd()])?;
Ok(())
}

View File

@@ -5,7 +5,6 @@
#include <consts.hpp>
#include <base.hpp>
#include <core.hpp>
#include <selinux.hpp>
#include "zygisk.hpp"

View File

@@ -20,11 +20,8 @@
#define BBPATH INTLROOT "/busybox"
#define ROOTOVL INTLROOT "/rootdir"
#define SHELLPTS INTLROOT "/pts"
#define ROOTMNT ROOTOVL "/.mount_list"
#define SELINUXMOCK INTLROOT "/selinux"
#define MAIN_CONFIG INTLROOT "/config"
#define MAIN_SOCKET DEVICEDIR "/socket"
#define LOG_PIPE DEVICEDIR "/log"
constexpr const char *applet_names[] = { "su", "resetprop", nullptr };
@@ -39,7 +36,6 @@ constexpr const char *applet_names[] = { "su", "resetprop", nullptr };
#define MAGISK_FILE_CON "u:object_r:" SEPOL_FILE_TYPE ":s0"
// Log pipe that only root and zygote can open
#define SEPOL_LOG_TYPE "magisk_log_file"
#define MAGISK_LOG_CON "u:object_r:" SEPOL_LOG_TYPE ":s0"
extern int SDK_INT;
#define APP_DATA_DIR (SDK_INT >= 24 ? "/data/user_de" : "/data/user")

View File

@@ -15,15 +15,26 @@ pub const LOGFILE: &str = "/cache/magisk.log";
// data paths
pub const SECURE_DIR: &str = "/data/adb";
pub const MODULEROOT: &str = concatcp!(SECURE_DIR, "/modules");
pub const DATABIN: &str = concatcp!(SECURE_DIR, "/magisk");
// tmpfs paths
const INTERNAL_DIR: &str = ".magisk";
pub const LOG_PIPE: &str = concatcp!(INTERNAL_DIR, "/device/log");
pub const MAIN_CONFIG: &str = concatcp!(INTERNAL_DIR, "/config");
pub const PREINITMIRR: &str = concatcp!(INTERNAL_DIR, "/preinit");
pub const MODULEMNT: &str = concatcp!(INTERNAL_DIR, "/modules");
pub const WORKERDIR: &str = concatcp!(INTERNAL_DIR, "/worker");
pub const DEVICEDIR: &str = concatcp!(INTERNAL_DIR, "/device");
pub const PREINITDEV: &str = concatcp!(DEVICEDIR, "/preinit");
pub const LOG_PIPE: &str = concatcp!(DEVICEDIR, "/log");
pub const ROOTOVL: &str = concatcp!(INTERNAL_DIR, "/rootdir");
pub const ROOTMNT: &str = concatcp!(ROOTOVL, "/.mount_list");
pub const SELINUXMOCK: &str = concatcp!(INTERNAL_DIR, "/selinux");
// Unconstrained domain the daemon and root processes run in
pub const SEPOL_PROC_DOMAIN: &str = "magisk";
pub const MAGISK_PROC_CON: &str = concatcp!("u:r:", SEPOL_PROC_DOMAIN, ":s0");
// Unconstrained file type that anyone can access
pub const SEPOL_FILE_TYPE: &str = "magisk_file";
// Log pipe that only root and zygote can open
pub const SEPOL_LOG_TYPE: &str = "magisk_log_file";
pub const MAGISK_LOG_CON: &str = concatcp!("u:object_r:", SEPOL_LOG_TYPE, ":s0");

View File

@@ -1,10 +1,10 @@
use crate::ffi::{backup_init, BootConfig, MagiskInit};
use base::{path, BytesExt, MappedFile};
use crate::ffi::{BootConfig, MagiskInit, backup_init};
use base::{BytesExt, MappedFile, cstr};
impl BootConfig {
#[allow(unused_imports, unused_unsafe)]
pub(crate) fn print(&self) {
use base::{debug, Utf8CStr};
use base::{Utf8CStr, debug};
debug!("skip_initramfs=[{}]", self.skip_initramfs);
debug!("force_normal_boot=[{}]", self.force_normal_boot);
debug!("rootwait=[{}]", self.rootwait);
@@ -37,11 +37,11 @@ impl BootConfig {
impl MagiskInit {
pub(crate) fn check_two_stage(&self) -> bool {
path!("/first_stage_ramdisk").exists() ||
path!("/second_stage_resources").exists() ||
path!("/system/bin/init").exists() ||
cstr!("/first_stage_ramdisk").exists() ||
cstr!("/second_stage_resources").exists() ||
cstr!("/system/bin/init").exists() ||
// Use the apex folder to determine whether 2SI (Android 10+)
path!("/apex").exists() ||
cstr!("/apex").exists() ||
// If we still have no indication, parse the original init and see what's up
MappedFile::open(backup_init())
.map(|data| data.contains(b"selinux_setup"))

View File

@@ -11,10 +11,30 @@
#include <base.hpp>
#include <stream.hpp>
#include <sepolicy.hpp>
#include "init-rs.hpp"
int magisk_proxy_main(int, char *argv[]);
rust::Utf8CStr backup_init();
// Expose some constants to Rust
static inline rust::Utf8CStr split_plat_cil() {
return SPLIT_PLAT_CIL;
};
static inline rust::Utf8CStr preload_lib() {
return PRELOAD_LIB;
}
static inline rust::Utf8CStr preload_policy() {
return PRELOAD_POLICY;
}
static inline rust::Utf8CStr preload_ack() {
return PRELOAD_ACK;
}
#endif

View File

@@ -1,15 +1,17 @@
use crate::ffi::backup_init;
use crate::mount::is_rootfs;
use crate::twostage::hexpatch_init_for_second_stage;
use crate::{
ffi::{magisk_proxy_main, BootConfig, MagiskInit},
ffi::{BootConfig, MagiskInit, magisk_proxy_main},
logging::setup_klog,
};
use base::{
debug, info,
LibcReturn, LoggedResult, ResultExt, cstr, info,
libc::{basename, getpid, mount, umask},
path, raw_cstr, FsPath, LibcReturn, LoggedResult, ResultExt,
raw_cstr,
};
use std::{
ffi::{c_char, CStr},
ffi::{CStr, c_char},
ptr::null,
};
@@ -35,50 +37,90 @@ impl MagiskInit {
}
}
pub(crate) fn legacy_system_as_root(&mut self) {
info!("Legacy SAR Init");
fn first_stage(&self) {
info!("First Stage Init");
self.prepare_data();
if self.mount_system_root() {
self.redirect_second_stage();
if !cstr!("/sdcard").exists() && !cstr!("/first_stage_ramdisk/sdcard").exists() {
self.hijack_init_with_switch_root();
self.restore_ramdisk_init();
} else {
self.restore_ramdisk_init();
// Fallback to hexpatch if /sdcard exists
hexpatch_init_for_second_stage(true);
}
}
fn second_stage(&mut self) {
info!("Second Stage Init");
cstr!("/init").unmount().ok();
cstr!("/system/bin/init").unmount().ok(); // just in case
cstr!("/data/init").remove().ok();
unsafe {
// Make sure init dmesg logs won't get messed up
*self.argv = raw_cstr!("/system/bin/init") as *mut _;
}
// Some weird devices like meizu, uses 2SI but still have legacy rootfs
if is_rootfs() {
// We are still on rootfs, so make sure we will execute the init of the 2nd stage
let init_path = cstr!("/init");
init_path.remove().ok();
init_path
.create_symlink_to(cstr!("/system/bin/init"))
.log_ok();
self.patch_rw_root();
} else {
self.patch_ro_root();
}
}
pub(crate) fn rootfs(&mut self) {
fn legacy_system_as_root(&mut self) {
info!("Legacy SAR Init");
self.prepare_data();
let is_two_stage = self.mount_system_root();
if is_two_stage {
hexpatch_init_for_second_stage(false);
} else {
self.patch_ro_root();
}
}
fn rootfs(&mut self) {
info!("RootFS Init");
self.prepare_data();
debug!("Restoring /init\n");
path!("/.backup/init").rename_to(path!("/init")).log_ok();
self.restore_ramdisk_init();
self.patch_rw_root();
}
pub(crate) fn recovery(&self) {
fn recovery(&self) {
info!("Ramdisk is recovery, abort");
self.restore_ramdisk_init();
path!("/.backup").remove_all().ok();
cstr!("/.backup").remove_all().ok();
}
pub(crate) fn restore_ramdisk_init(&self) {
path!("/init").remove().ok();
fn restore_ramdisk_init(&self) {
cstr!("/init").remove().ok();
let orig_init = FsPath::from(backup_init());
let orig_init = backup_init();
if orig_init.exists() {
orig_init.rename_to(path!("/init")).log_ok();
orig_init.rename_to(cstr!("/init")).log_ok();
} else {
// If the backup init is missing, this means that the boot ramdisk
// was created from scratch, and the real init is in a separate CPIO,
// which is guaranteed to be placed at /system/bin/init.
path!("/system/bin/init")
.symlink_to(path!("/init"))
cstr!("/init")
.create_symlink_to(cstr!("/system/bin/init"))
.log_ok();
}
}
fn start(&mut self) -> LoggedResult<()> {
if !path!("/proc/cmdline").exists() {
path!("/proc").mkdir(0o755)?;
if !cstr!("/proc/cmdline").exists() {
cstr!("/proc").mkdir(0o755)?;
unsafe {
mount(
raw_cstr!("proc"),
@@ -88,11 +130,11 @@ impl MagiskInit {
null(),
)
}
.as_os_err()?;
.check_io_err()?;
self.mount_list.push("/proc".to_string());
}
if !path!("/sys/block").exists() {
path!("/sys").mkdir(0o755)?;
if !cstr!("/sys/block").exists() {
cstr!("/sys").mkdir(0o755)?;
unsafe {
mount(
raw_cstr!("sysfs"),
@@ -102,7 +144,7 @@ impl MagiskInit {
null(),
)
}
.as_os_err()?;
.check_io_err()?;
self.mount_list.push("/sys".to_string());
}
@@ -117,7 +159,7 @@ impl MagiskInit {
self.legacy_system_as_root();
} else if self.config.force_normal_boot {
self.first_stage();
} else if path!("/sbin/recovery").exists() || path!("/system/bin/recovery").exists() {
} else if cstr!("/sbin/recovery").exists() || cstr!("/system/bin/recovery").exists() {
self.recovery();
} else if self.check_two_stage() {
self.first_stage();

View File

@@ -7,7 +7,7 @@ use logging::setup_klog;
// Has to be pub so all symbols in that crate is included
pub use magiskpolicy;
use mount::{is_device_mounted, switch_root};
use rootdir::{inject_magisk_rc, OverlayAttr};
use rootdir::{OverlayAttr, inject_magisk_rc};
#[path = "../include/consts.rs"]
mod consts;
@@ -16,6 +16,7 @@ mod init;
mod logging;
mod mount;
mod rootdir;
mod selinux;
mod twostage;
#[cxx::bridge]
@@ -56,6 +57,12 @@ pub mod ffi {
unsafe fn magisk_proxy_main(argc: i32, argv: *mut *mut c_char) -> i32;
fn backup_init() -> Utf8CStrRef<'static>;
// Constants
fn split_plat_cil() -> Utf8CStrRef<'static>;
fn preload_lib() -> Utf8CStrRef<'static>;
fn preload_policy() -> Utf8CStrRef<'static>;
fn preload_ack() -> Utf8CStrRef<'static>;
}
#[namespace = "rust"]
@@ -81,6 +88,7 @@ pub mod ffi {
type OverlayAttr;
fn parse_config_file(self: &mut MagiskInit);
fn mount_overlay(self: &mut MagiskInit, dest: Utf8CStrRef);
fn handle_sepolicy(self: &mut MagiskInit);
fn restore_overlay_contexts(self: &MagiskInit);
}
unsafe extern "C++" {
@@ -94,7 +102,6 @@ pub mod ffi {
fn collect_devices(self: &MagiskInit);
fn mount_preinit_dir(self: &MagiskInit);
unsafe fn find_block(self: &MagiskInit, partname: *const c_char) -> u64;
fn handle_sepolicy(self: &mut MagiskInit);
unsafe fn patch_fissiond(self: &mut MagiskInit, tmp_path: *const c_char);
}
}

View File

@@ -1,15 +1,15 @@
use base::{
cstr,
LOGGER, LogLevel, Logger, SilentResultExt, Utf8CStr, cstr,
libc::{
makedev, mknod, syscall, SYS_dup3, O_CLOEXEC, O_RDWR, O_WRONLY, STDERR_FILENO,
STDIN_FILENO, STDOUT_FILENO, S_IFCHR,
O_CLOEXEC, O_RDWR, O_WRONLY, S_IFCHR, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO, SYS_dup3,
makedev, mknod, syscall,
},
open_fd, path, raw_cstr, LogLevel, Logger, Utf8CStr, LOGGER,
raw_cstr,
};
use std::mem::ManuallyDrop;
use std::{
fs::File,
io::{IoSlice, Write},
mem,
os::fd::{FromRawFd, IntoRawFd, RawFd},
};
@@ -19,11 +19,11 @@ static mut KMSG: RawFd = -1;
pub fn setup_klog() {
unsafe {
// Shut down first 3 fds
let mut fd = open_fd!(cstr!("/dev/null"), O_RDWR | O_CLOEXEC);
let mut fd = cstr!("/dev/null").open(O_RDWR | O_CLOEXEC).silent();
if fd.is_err() {
mknod(raw_cstr!("/null"), S_IFCHR | 0o666, makedev(1, 3));
fd = open_fd!(cstr!("/null"), O_RDWR | O_CLOEXEC);
path!("/null").remove().ok();
fd = cstr!("/null").open(O_RDWR | O_CLOEXEC).silent();
cstr!("/null").remove().ok();
}
if let Ok(ref fd) = fd {
syscall(SYS_dup3, fd, STDIN_FILENO, O_CLOEXEC);
@@ -32,21 +32,17 @@ pub fn setup_klog() {
}
// Then open kmsg fd
let mut fd = open_fd!(cstr!("/dev/kmsg"), O_WRONLY | O_CLOEXEC);
let mut fd = cstr!("/dev/kmsg").open(O_WRONLY | O_CLOEXEC).silent();
if fd.is_err() {
mknod(raw_cstr!("/kmsg"), S_IFCHR | 0o666, makedev(1, 11));
fd = open_fd!(cstr!("/kmsg"), O_WRONLY | O_CLOEXEC);
path!("/kmsg").remove().ok();
fd = cstr!("/kmsg").open(O_WRONLY | O_CLOEXEC).silent();
cstr!("/kmsg").remove().ok();
}
KMSG = fd.map(|fd| fd.into_raw_fd()).unwrap_or(-1);
}
// Disable kmsg rate limiting
if let Ok(rate) = open_fd!(
cstr!("/proc/sys/kernel/printk_devkmsg"),
O_WRONLY | O_CLOEXEC
) {
let mut rate = File::from(rate);
if let Ok(mut rate) = cstr!("/proc/sys/kernel/printk_devkmsg").open(O_WRONLY | O_CLOEXEC) {
writeln!(rate, "on").ok();
}
@@ -55,9 +51,8 @@ pub fn setup_klog() {
if fd >= 0 {
let io1 = IoSlice::new("magiskinit: ".as_bytes());
let io2 = IoSlice::new(msg.as_bytes());
let mut kmsg = unsafe { File::from_raw_fd(fd) };
let mut kmsg = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });
let _ = kmsg.write_vectored(&[io1, io2]).ok();
mem::forget(kmsg);
}
}

View File

@@ -1,23 +1,23 @@
use crate::ffi::MagiskInit;
use base::libc::{TMPFS_MAGIC, statfs};
use base::{
cstr, debug, libc,
libc::{chdir, chroot, execve, exit, mount, umount2, MNT_DETACH, MS_MOVE},
parse_mount_info, path, raw_cstr, Directory, LibcReturn, LoggedResult, ResultExt, StringExt,
Utf8CStr,
Directory, FsPathBuilder, LibcReturn, LoggedResult, ResultExt, Utf8CStr, cstr, debug, libc,
libc::{chdir, chroot, execve, exit, mount},
parse_mount_info, raw_cstr,
};
use cxx::CxxString;
use std::ffi::c_long;
use std::{
collections::BTreeSet,
ops::Bound::{Excluded, Unbounded},
pin::Pin,
ptr::null as nullptr,
};
unsafe extern "C" {
static environ: *const *mut libc::c_char;
}
pub fn switch_root(path: &Utf8CStr) {
pub(crate) fn switch_root(path: &Utf8CStr) {
let res: LoggedResult<()> = try {
debug!("Switch root to {}", path);
let mut mounts = BTreeSet::new();
@@ -34,26 +34,19 @@ pub fn switch_root(path: &Utf8CStr) {
continue;
}
}
let mut new_path = format!("{}/{}", path.as_str(), &info.target);
std::fs::create_dir(&new_path).ok();
unsafe {
let mut target = info.target.clone();
mount(
target.nul_terminate().as_ptr().cast(),
new_path.nul_terminate().as_ptr().cast(),
nullptr(),
MS_MOVE,
nullptr(),
)
.as_os_err()?;
}
let mut target = info.target.clone();
let target = Utf8CStr::from_string(&mut target);
let new_path = cstr::buf::default()
.join_path(path)
.join_path(info.target.trim_start_matches('/'));
new_path.mkdirs(0o755).ok();
target.move_mount_to(&new_path)?;
mounts.insert(info.target);
}
unsafe {
chdir(path.as_ptr()).as_os_err()?;
mount(path.as_ptr(), raw_cstr!("/"), nullptr(), MS_MOVE, nullptr()).as_os_err()?;
chdir(path.as_ptr()).check_io_err()?;
path.move_mount_to(cstr!("/"))?;
chroot(raw_cstr!("."));
}
@@ -63,7 +56,7 @@ pub fn switch_root(path: &Utf8CStr) {
res.ok();
}
pub fn is_device_mounted(dev: u64, target: Pin<&mut CxxString>) -> bool {
pub(crate) fn is_device_mounted(dev: u64, target: Pin<&mut CxxString>) -> bool {
for mount in parse_mount_info("self") {
if mount.root == "/" && mount.device == dev {
target.push_str(&mount.target);
@@ -73,10 +66,20 @@ pub fn is_device_mounted(dev: u64, target: Pin<&mut CxxString>) -> bool {
false
}
const RAMFS_MAGIC: u64 = 0x858458f6;
pub(crate) fn is_rootfs() -> bool {
unsafe {
let mut sfs: statfs = std::mem::zeroed();
statfs(raw_cstr!("/"), &mut sfs);
sfs.f_type as u64 == RAMFS_MAGIC || sfs.f_type as c_long == TMPFS_MAGIC
}
}
impl MagiskInit {
pub(crate) fn prepare_data(&self) {
debug!("Setup data tmp");
path!("/data").mkdir(0o755).log_ok();
cstr!("/data").mkdir(0o755).log_ok();
unsafe {
mount(
raw_cstr!("magisk"),
@@ -86,31 +89,27 @@ impl MagiskInit {
raw_cstr!("mode=755").cast(),
)
}
.as_os_err()
.check_io_err()
.log_ok();
path!("/init").copy_to(path!("/data/magiskinit")).log_ok();
path!("/.backup").copy_to(path!("/data/.backup")).log_ok();
path!("/overlay.d")
.copy_to(path!("/data/overlay.d"))
cstr!("/init").copy_to(cstr!("/data/magiskinit")).log_ok();
cstr!("/.backup").copy_to(cstr!("/data/.backup")).log_ok();
cstr!("/overlay.d")
.copy_to(cstr!("/data/overlay.d"))
.log_ok();
}
pub(crate) fn exec_init(&self) {
unsafe {
for p in self.mount_list.iter().rev() {
if umount2(p.as_ptr().cast(), MNT_DETACH)
.as_os_err()
.log()
.is_ok()
{
debug!("Unmount [{}]", p);
}
pub(crate) fn exec_init(&mut self) {
for path in self.mount_list.iter_mut().rev() {
let path = Utf8CStr::from_string(path);
if path.unmount().log().is_ok() {
debug!("Unmount [{}]", path);
}
}
unsafe {
execve(raw_cstr!("/init"), self.argv.cast(), environ.cast())
.as_os_err()
.log()
.ok();
.check_io_err()
.log_ok();
exit(1);
}
}

View File

@@ -2,8 +2,8 @@ use crate::consts::{ROOTMNT, ROOTOVL};
use crate::ffi::MagiskInit;
use base::libc::{O_CREAT, O_RDONLY, O_WRONLY};
use base::{
clone_attr, cstr, cstr_buf, debug, libc, path, BufReadExt, Directory, FsPath, FsPathBuf,
LibcReturn, LoggedResult, ResultExt, Utf8CStr, Utf8CString,
BufReadExt, Directory, FsPathBuilder, LoggedResult, ResultExt, Utf8CStr, Utf8CString,
clone_attr, cstr, debug,
};
use std::io::BufReader;
use std::{
@@ -11,7 +11,6 @@ use std::{
io::Write,
mem,
os::fd::{FromRawFd, RawFd},
ptr,
};
pub fn inject_magisk_rc(fd: RawFd, tmp_dir: &Utf8CStr) {
@@ -48,7 +47,7 @@ pub struct OverlayAttr(Utf8CString, Utf8CString);
impl MagiskInit {
pub(crate) fn parse_config_file(&mut self) {
if let Ok(fd) = path!("/data/.backup/.magisk").open(O_RDONLY) {
if let Ok(fd) = cstr!("/data/.backup/.magisk").open(O_RDONLY) {
let mut reader = BufReader::new(fd);
reader.foreach_props(|key, val| {
if key == "PREINITDEVICE" {
@@ -67,14 +66,14 @@ impl MagiskInit {
mount_list: &mut String,
) -> LoggedResult<()> {
let mut dir = Directory::open(src_dir)?;
let mut con = cstr_buf::default();
let mut con = cstr::buf::default();
loop {
match &dir.read()? {
None => return Ok(()),
Some(e) => {
let name = e.name().to_str()?;
let src = FsPathBuf::new_dynamic(256).join(src_dir).join(name);
let dest = FsPathBuf::new_dynamic(256).join(dest_dir).join(name);
let name = e.name();
let src = cstr::buf::dynamic(256).join_path(src_dir).join_path(name);
let dest = cstr::buf::dynamic(256).join_path(dest_dir).join_path(name);
if dest.exists() {
if e.is_dir() {
// Recursive
@@ -83,16 +82,7 @@ impl MagiskInit {
debug!("Mount [{}] -> [{}]", src, dest);
clone_attr(&dest, &src)?;
dest.get_secontext(&mut con)?;
unsafe {
libc::mount(
src.as_ptr(),
dest.as_ptr(),
ptr::null(),
libc::MS_BIND,
ptr::null(),
)
.as_os_err()?;
};
src.bind_mount_to(&dest)?;
self.overlay_con
.push(OverlayAttr(dest.to_owned(), con.to_owned()));
mount_list.push_str(dest.as_str());
@@ -108,7 +98,7 @@ impl MagiskInit {
let mut mount_list = String::new();
self.mount_impl(cstr!(ROOTOVL), dest, &mut mount_list)
.log_ok();
if let Ok(mut fd) = path!(ROOTMNT).create(O_CREAT | O_WRONLY, 0) {
if let Ok(mut fd) = cstr!(ROOTMNT).create(O_CREAT | O_WRONLY, 0) {
fd.write(mount_list.as_bytes()).log_ok();
}
}
@@ -116,7 +106,7 @@ impl MagiskInit {
pub(crate) fn restore_overlay_contexts(&self) {
self.overlay_con.iter().for_each(|attr| {
let OverlayAttr(path, con) = attr;
FsPath::from(path).set_secontext(con).log_ok();
path.set_secontext(con).log_ok();
})
}
}

View File

@@ -1,246 +0,0 @@
#include <sys/mount.h>
#include <sys/xattr.h>
#include <consts.hpp>
#include <sepolicy.hpp>
#include "init.hpp"
using namespace std;
#define POLICY_VERSION "/selinux_version"
#define MOCK_VERSION SELINUXMOCK "/version"
#define MOCK_LOAD SELINUXMOCK "/load"
#define MOCK_ENFORCE SELINUXMOCK "/enforce"
#define MOCK_REQPROT SELINUXMOCK "/checkreqprot"
static void mock_fifo(const char *target, const char *mock) {
LOGD("Hijack [%s]\n", target);
mkfifo(mock, 0666);
xmount(mock, target, nullptr, MS_BIND, nullptr);
}
static void mock_file(const char *target, const char *mock) {
LOGD("Hijack [%s]\n", target);
close(xopen(mock, O_CREAT | O_RDONLY, 0666));
xmount(mock, target, nullptr, MS_BIND, nullptr);
}
enum SePatchStrategy {
// 2SI, Android 10+
// On 2SI devices, the 2nd stage init is always a dynamic executable.
// This meant that instead of going through convoluted hacks, we can just
// LD_PRELOAD and replace security_load_policy with our own implementation.
LD_PRELOAD,
// Treble enabled, Android 8.0+
// selinuxfs is mounted in init.cpp. Errors when mounting selinuxfs is ignored,
// which means that we can directly mount selinuxfs ourselves and hijack nodes in it.
SELINUXFS,
// Dynamic patching, Android 6.0 - 7.1
// selinuxfs is mounted in libselinux's selinux_android_load_policy(). Errors when
// mounting selinuxfs is fatal, which means we need to block init's control flow after
// it mounted selinuxfs for us, then we can hijack nodes in it.
LEGACY,
};
void MagiskInit::handle_sepolicy() noexcept {
xmkdir(SELINUXMOCK, 0711);
// Read all custom rules into memory
string rules;
auto rule = "/data/" PREINITMIRR "/sepolicy.rule";
if (xaccess(rule, R_OK) == 0) {
LOGD("Loading custom sepolicy patch: [%s]\n", rule);
full_read(rule, rules);
}
// Step 0: determine strategy
SePatchStrategy strat;
if (access("/system/bin/init", F_OK) == 0) {
strat = LD_PRELOAD;
} else {
auto init = mmap_data("/init");
if (init.contains(SPLIT_PLAT_CIL)) {
// Supports split policy
strat = SELINUXFS;
} else if (init.contains(POLICY_VERSION)) {
// Does not support split policy, hijack /selinux_version
strat = LEGACY;
} else {
LOGE("Unknown sepolicy setup, abort...\n");
return;
}
}
// Step 1: setup for intercepting init boot control flow
switch (strat) {
case LD_PRELOAD: {
LOGI("SePatchStrategy: LD_PRELOAD\n");
cp_afc("init-ld", PRELOAD_LIB);
setenv("LD_PRELOAD", PRELOAD_LIB, 1);
mkfifo(PRELOAD_ACK, 0666);
break;
}
case SELINUXFS: {
LOGI("SePatchStrategy: SELINUXFS\n");
if (access(SELINUX_ENFORCE, F_OK) != 0) {
// selinuxfs was not already mounted, mount it ourselves
// Remount procfs with proper options
xmount(nullptr, "/proc", nullptr, MS_REMOUNT, "hidepid=2,gid=3009");
// Preserve sysfs and procfs
decltype(mount_list) new_mount_list;
std::remove_copy_if(
mount_list.begin(), mount_list.end(),
std::back_inserter(new_mount_list),
[](const auto &s) { return s == "/proc" || s == "/sys"; });
new_mount_list.swap(mount_list);
// Mount selinuxfs
xmount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, nullptr);
}
mock_file(SELINUX_LOAD, MOCK_LOAD);
mock_fifo(SELINUX_ENFORCE, MOCK_ENFORCE);
break;
}
case LEGACY: {
LOGI("SePatchStrategy: LEGACY\n");
if (access(POLICY_VERSION, F_OK) != 0) {
// The file does not exist, create one
close(xopen(POLICY_VERSION, O_RDONLY | O_CREAT, 0644));
}
// The only purpose of this is to block init's control flow after it mounts selinuxfs
// and before it calls security_load_policy().
// Target: selinux_android_load_policy() -> set_policy_index() -> open(POLICY_VERSION)
mock_fifo(POLICY_VERSION, MOCK_VERSION);
break;
}
}
// Create a new process waiting for init operations
if (xfork()) {
return;
}
// Step 2: wait for selinuxfs to be mounted (only for LEGACY)
if (strat == LEGACY) {
// Busy wait until selinuxfs is mounted
while (access(SELINUX_ENFORCE, F_OK)) {
// Retry every 100ms
usleep(100000);
}
// On Android 6.0, init does not call security_getenforce() first; instead it directly
// call security_setenforce() after security_load_policy(). What's even worse, it opens the
// enforce node with O_RDWR, which will not block when opening FIFO files. As a workaround,
// we do not mock the enforce node, and block init with mock checkreqprot instead.
// Android 7.0 - 7.1 doesn't have this issue, but for simplicity, let's just use the
// same blocking strategy for both since it also works just fine.
mock_file(SELINUX_LOAD, MOCK_LOAD);
mock_fifo(SELINUX_REQPROT, MOCK_REQPROT);
// This will unblock init at selinux_android_load_policy() -> set_policy_index().
close(xopen(MOCK_VERSION, O_WRONLY));
xumount2(POLICY_VERSION, MNT_DETACH);
// libselinux does not read /selinux_version after open; instead it mmap the file,
// which can never succeed on FIFO files. This is fine as set_policy_index() will just
// fallback to the default index 0.
}
// Step 3: obtain sepolicy, patch, and load the patched sepolicy
if (strat == LD_PRELOAD) {
// This open will block until preload.so finish writing the sepolicy
owned_fd ack_fd = xopen(PRELOAD_ACK, O_WRONLY);
auto sepol = SePolicy::from_file(PRELOAD_POLICY);
// Remove the files before loading the policy
unlink(PRELOAD_POLICY);
unlink(PRELOAD_ACK);
sepol.magisk_rules();
sepol.load_rules(rules);
sepol.to_file(SELINUX_LOAD);
// restore mounted files' context after sepolicy loaded
restore_overlay_contexts();
// Write ack to restore preload.so's control flow
xwrite(ack_fd, &ack_fd, 1);
} else {
int mock_enforce = -1;
if (strat == LEGACY) {
// Busy wait until sepolicy is fully written.
struct stat st{};
decltype(st.st_size) sz;
do {
sz = st.st_size;
// Check every 100ms
usleep(100000);
xstat(MOCK_LOAD, &st);
} while (sz == 0 || sz != st.st_size);
} else {
// This open will block until init calls security_getenforce().
mock_enforce = xopen(MOCK_ENFORCE, O_WRONLY);
}
// Cleanup the hijacks
umount2("/init", MNT_DETACH);
xumount2(SELINUX_LOAD, MNT_DETACH);
umount2(SELINUX_ENFORCE, MNT_DETACH);
umount2(SELINUX_REQPROT, MNT_DETACH);
auto sepol = SePolicy::from_file(MOCK_LOAD);
sepol.magisk_rules();
sepol.load_rules(rules);
sepol.to_file(SELINUX_LOAD);
// For some reason, restorecon on /init won't work in some cases
setxattr("/init", XATTR_NAME_SELINUX, "u:object_r:init_exec:s0", 24, 0);
// restore mounted files' context after sepolicy loaded
restore_overlay_contexts();
// We need to make sure the actual init process is blocked until sepolicy is loaded,
// or else restorecon will fail and re-exec won't change context, causing boot failure.
// We (ab)use the fact that init either reads the enforce node, or writes the checkreqprot
// node, and because both has been replaced with FIFO files, init will block until we
// handle it, effectively hijacking its control flow until the patched sepolicy is loaded.
if (strat == LEGACY) {
// init is blocked on checkreqprot, write to the real node first, then
// unblock init by opening the mock FIFO.
owned_fd real_req = xopen(SELINUX_REQPROT, O_WRONLY);
xwrite(real_req, "0", 1);
owned_fd mock_req = xopen(MOCK_REQPROT, O_RDONLY);
full_read(mock_req);
} else {
// security_getenforce was called
string data = full_read(SELINUX_ENFORCE);
xwrite(mock_enforce, data.data(), data.length());
close(mock_enforce);
}
}
// At this point, the init process will be unblocked
// and continue on with restorecon + re-exec.
// Terminate process
exit(0);
}

279
native/src/init/selinux.rs Normal file
View File

@@ -0,0 +1,279 @@
use crate::consts::{PREINITMIRR, SELINUXMOCK};
use crate::ffi::{MagiskInit, preload_ack, preload_lib, preload_policy, split_plat_cil};
use base::const_format::concatcp;
use base::{
BytesExt, LibcReturn, LoggedResult, MappedFile, ResultExt, Utf8CStr, cstr, debug, error, info,
libc, raw_cstr,
};
use magiskpolicy::ffi::SePolicy;
use std::io::{Read, Write};
use std::ptr;
use std::thread::sleep;
use std::time::Duration;
const POLICY_VERSION: &Utf8CStr = cstr!("/selinux_version");
const MOCK_VERSION: &Utf8CStr = cstr!(concatcp!(SELINUXMOCK, "/version"));
const MOCK_LOAD: &Utf8CStr = cstr!(concatcp!(SELINUXMOCK, "/load"));
const MOCK_ENFORCE: &Utf8CStr = cstr!(concatcp!(SELINUXMOCK, "/enforce"));
const MOCK_REQPROT: &Utf8CStr = cstr!(concatcp!(SELINUXMOCK, "/checkreqprot"));
const SELINUX_MNT: &str = "/sys/fs/selinux";
const SELINUX_ENFORCE: &Utf8CStr = cstr!(concatcp!(SELINUX_MNT, "/enforce"));
const SELINUX_LOAD: &Utf8CStr = cstr!(concatcp!(SELINUX_MNT, "/load"));
const SELINUX_REQPROT: &Utf8CStr = cstr!(concatcp!(SELINUX_MNT, "/checkreqprot"));
enum SePatchStrategy {
// 2SI, Android 10+
// On 2SI devices, the 2nd stage init is always a dynamic executable.
// This meant that instead of going through convoluted hacks, we can just
// LD_PRELOAD and replace security_load_policy with our own implementation.
LdPreload,
// Treble enabled, Android 8.0+
// selinuxfs is mounted in init.cpp. Errors when mounting selinuxfs is ignored,
// which means that we can directly mount selinuxfs ourselves and hijack nodes in it.
SelinuxFs,
// Dynamic patching, Android 6.0 - 7.1
// selinuxfs is mounted in libselinux's selinux_android_load_policy(). Errors when
// mounting selinuxfs is fatal, which means we need to block init's control flow after
// it mounted selinuxfs for us, then we can hijack nodes in it.
Legacy,
}
// Note for non-LD_PRELOAD strategy:
//
// We need to make sure the actual init process is blocked until sepolicy is loaded,
// or else restorecon will fail and re-exec won't change context, causing boot failure.
// We (ab)use the fact that init either reads the enforce node, or writes the checkreqprot
// node, and because both has been replaced with FIFO files, init will block until we
// handle it, effectively hijacking its control flow until the patched sepolicy is loaded.
fn mock_fifo(target: &Utf8CStr, mock: &Utf8CStr) -> LoggedResult<()> {
debug!("Hijack [{}]", target);
mock.mkfifo(0o666)?;
mock.bind_mount_to(target).log()
}
fn mock_file(target: &Utf8CStr, mock: &Utf8CStr) -> LoggedResult<()> {
debug!("Hijack [{}]", target);
drop(mock.create(libc::O_RDONLY, 0o666)?);
mock.bind_mount_to(target).log()
}
impl MagiskInit {
pub(crate) fn handle_sepolicy(&mut self) {
self.handle_sepolicy_impl().ok();
}
fn cleanup_and_load(&self, rules: &str) {
// Cleanup the hijacks
cstr!("/init").unmount().ok();
SELINUX_LOAD.unmount().log_ok();
SELINUX_ENFORCE.unmount().ok();
SELINUX_REQPROT.unmount().ok();
let mut sepol = SePolicy::from_file(MOCK_LOAD);
sepol.magisk_rules();
sepol.load_rules(rules);
sepol.to_file(SELINUX_LOAD);
// For some reason, restorecon on /init won't work in some cases
cstr!("/init")
.follow_link()
.set_secontext(cstr!("u:object_r:init_exec:s0"))
.ok();
// restore mounted files' context after sepolicy loaded
self.restore_overlay_contexts();
}
fn handle_sepolicy_impl(&mut self) -> LoggedResult<()> {
cstr!(SELINUXMOCK).mkdir(0o711)?;
let mut rules = String::new();
let rule_file = cstr!(concatcp!("/data/", PREINITMIRR, "/sepolicy.rule"));
if rule_file.exists() {
debug!("Loading custom sepolicy patch: [{}]", rule_file);
rule_file.open(libc::O_RDONLY)?.read_to_string(&mut rules)?;
}
// Step 0: determine strategy
let strat: SePatchStrategy;
if cstr!("/system/bin/init").exists() {
strat = SePatchStrategy::LdPreload;
} else {
let init = MappedFile::open(cstr!("/init"))?;
if init.contains(split_plat_cil().as_str().as_bytes()) {
// Supports split policy
strat = SePatchStrategy::SelinuxFs;
} else if init.contains(POLICY_VERSION.as_bytes()) {
// Does not support split policy, hijack /selinux_version
strat = SePatchStrategy::Legacy;
} else {
error!("Unknown sepolicy setup, abort...");
return Ok(());
}
}
// Step 1: setup for intercepting init boot control flow
match strat {
SePatchStrategy::LdPreload => {
info!("SePatchStrategy: LD_PRELOAD");
cstr!("init-ld").copy_to(preload_lib())?;
unsafe {
libc::setenv(raw_cstr!("LD_PRELOAD"), preload_lib().as_ptr(), 1);
}
preload_ack().mkfifo(0o666)?;
}
SePatchStrategy::SelinuxFs => {
info!("SePatchStrategy: SELINUXFS");
if !SELINUX_ENFORCE.exists() {
// selinuxfs was not already mounted, mount it ourselves
// Remount procfs with proper options
cstr!("/proc").remount_with_data(cstr!("hidepid=2,gid=3009"))?;
// Preserve sysfs and procfs
self.mount_list.retain(|s| s != "/proc" && s != "/sys");
// Mount selinuxfs
unsafe {
libc::mount(
raw_cstr!("selinuxfs"),
raw_cstr!(SELINUX_MNT),
raw_cstr!("selinuxfs"),
0,
ptr::null(),
)
.check_io_err()?;
}
}
mock_file(SELINUX_LOAD, MOCK_LOAD)?;
mock_fifo(SELINUX_ENFORCE, MOCK_ENFORCE)?;
}
SePatchStrategy::Legacy => {
info!("SePatchStrategy: LEGACY");
if !POLICY_VERSION.exists() {
// The file does not exist, create one
drop(POLICY_VERSION.create(libc::O_RDONLY, 0o666)?);
}
// The only purpose of this is to block init's control flow after it mounts
// selinuxfs and before it calls security_load_policy().
// selinux_android_load_policy() -> set_policy_index() -> open(POLICY_VERSION)
mock_fifo(POLICY_VERSION, MOCK_VERSION)?;
}
}
// Create a new process waiting for init operations
let pid = unsafe { libc::fork() };
if pid != 0 {
return Ok(());
}
// Step 2: wait for selinuxfs to be mounted (only for LEGACY)
let wait = Duration::from_millis(100);
if matches!(strat, SePatchStrategy::Legacy) {
// Busy wait until selinuxfs is mounted
while !SELINUX_ENFORCE.exists() {
// Retry every 100ms
sleep(wait);
}
// On Android 6.0, init does not call security_getenforce() first; instead it directly
// call security_setenforce() after security_load_policy(). What's even worse, it opens
// the enforce node with O_RDWR, which will not block when opening FIFO files.
// As a workaround, we do not mock the enforce node, and block init with mocking
// checkreqprot instead.
// Android 7.0 - 7.1 doesn't have this issue, but for simplicity, let's just use the
// same blocking strategy for both since it also works just fine.
mock_file(SELINUX_LOAD, MOCK_LOAD)?;
mock_fifo(SELINUX_REQPROT, MOCK_REQPROT)?;
// This will unblock init at selinux_android_load_policy() -> set_policy_index().
drop(MOCK_VERSION.open(libc::O_WRONLY)?);
POLICY_VERSION.unmount()?;
// libselinux does not read /selinux_version after open; instead it mmap the file,
// which can never succeed on FIFO files. This is fine as set_policy_index() will just
// fallback to the default index 0.
}
// Step 3: obtain sepolicy, patch, and load the patched sepolicy
match strat {
SePatchStrategy::LdPreload => {
// This open will block until preload.so finish writing the sepolicy
let mut ack_fd = preload_ack().open(libc::O_WRONLY)?;
let mut sepol = SePolicy::from_file(preload_policy());
// Remove the files before loading the policy
preload_policy().remove()?;
preload_ack().remove()?;
sepol.magisk_rules();
sepol.load_rules(&rules);
sepol.to_file(SELINUX_LOAD);
self.restore_overlay_contexts();
// Write ack to restore preload.so's control flow
ack_fd.write_all("0".as_bytes())?;
}
SePatchStrategy::SelinuxFs => {
// This open will block until init calls security_getenforce().
let mut mock_enforce = MOCK_ENFORCE.open(libc::O_WRONLY)?;
self.cleanup_and_load(&rules);
// security_getenforce was called, read from real and redirect to mock
let mut data = vec![];
SELINUX_ENFORCE
.open(libc::O_RDONLY)?
.read_to_end(&mut data)?;
mock_enforce.write_all(&data)?;
}
SePatchStrategy::Legacy => {
let mut sz = 0_usize;
// Busy wait until sepolicy is fully written.
loop {
let attr = MOCK_LOAD.get_attr()?;
if sz != 0 && sz == attr.st.st_size as usize {
break;
}
sz = attr.st.st_size as usize;
// Check every 100ms
sleep(wait);
}
self.cleanup_and_load(&rules);
// init is blocked on checkreqprot, write to the real node first, then
// unblock init by opening the mock FIFO.
SELINUX_REQPROT
.open(libc::O_WRONLY)?
.write_all("0".as_bytes())?;
let mut v = vec![];
MOCK_REQPROT.open(libc::O_RDONLY)?.read_to_end(&mut v)?;
}
}
// At this point, the init process will be unblocked
// and continue on with restorecon + re-exec.
// Terminate process
std::process::exit(0);
}
}

View File

@@ -1,148 +1,102 @@
use crate::ffi::MagiskInit;
use base::{
clone_attr, cstr, debug, error, info,
libc::{
mount, statfs, umount2, MNT_DETACH, MS_BIND, O_CLOEXEC,
O_CREAT, O_RDONLY, O_WRONLY, TMPFS_MAGIC,
},
path, raw_cstr, LibcReturn, MappedFile, MutBytesExt, ResultExt,
LoggedResult, MappedFile, MutBytesExt, ResultExt, cstr, debug, error,
libc::{O_CLOEXEC, O_CREAT, O_RDONLY, O_WRONLY},
};
use std::{ffi::c_long, io::Write, ptr::null};
use std::io::Write;
pub(crate) fn hexpatch_init_for_second_stage(writable: bool) {
let init = if writable {
MappedFile::open_rw(cstr!("/init"))
} else {
MappedFile::open(cstr!("/init"))
};
let Ok(mut init) = init else {
error!("Failed to open /init for hexpatch");
return;
};
// Redirect original init to magiskinit
let from = "/system/bin/init";
let to = "/data/magiskinit";
let v = init.patch(from.as_bytes(), to.as_bytes());
#[allow(unused_variables)]
for off in &v {
debug!("Patch @ {:#010X} [{}] -> [{}]", off, from, to);
}
if !writable {
// If we cannot directly modify /init, we need to bind mount a replacement on top of it
let src = cstr!("/init");
let dest = cstr!("/data/init");
let _: LoggedResult<()> = try {
{
let mut fd = dest.create(O_CREAT | O_WRONLY, 0)?;
fd.write_all(init.as_ref())?;
}
let attr = src.follow_link().get_attr()?;
dest.set_attr(&attr)?;
dest.bind_mount_to(src)?;
};
}
}
impl MagiskInit {
pub(crate) fn first_stage(&self) {
info!("First Stage Init");
self.prepare_data();
pub(crate) fn hijack_init_with_switch_root(&self) {
// We make use of original init's `SwitchRoot` to help us bind mount
// magiskinit to /system/bin/init to hijack second stage init.
//
// Two important assumption about 2SI:
// - The second stage init is always /system/bin/init
// - After `SwitchRoot`, /sdcard is always a symlink to `/storage/self/primary`.
//
// `SwitchRoot` will perform the following:
// - Recursive move all mounts under `/` to `/system`
// - chroot to `/system`
//
// The trick here is that in Magisk's first stage init, we can mount magiskinit to /sdcard,
// and create a symlink at /storage/self/primary pointing to /system/system/bin/init.
//
// During init's `SwitchRoot`, it will mount move /sdcard (which is magiskinit)
// to /system/sdcard, which is a symlink to /storage/self/primary, which is a
// symlink to /system/system/bin/init, which will eventually become /system/bin/init after
// chroot to /system. The effective result is that we coerce the original init into bind
// mounting magiskinit to /system/bin/init, successfully hijacking the second stage init.
//
// An edge case is that some devices (like meizu) use 2SI but does not switch root.
// In that case, they must already have a /sdcard in ramfs, thus we can check if
// /sdcard exists and fallback to using hexpatch.
if !path!("/sdcard").exists() && !path!("/first_stage_ramdisk/sdcard").exists() {
if self.config.force_normal_boot {
path!("/first_stage_ramdisk/storage/self")
.mkdirs(0o755)
.log_ok();
path!("/system/system/bin/init")
.symlink_to(path!("/first_stage_ramdisk/storage/self/primary"))
.log_ok();
debug!(
"Symlink /first_stage_ramdisk/storage/self/primary -> /system/system/bin/init"
);
path!("/first_stage_ramdisk/sdcard")
.create(O_RDONLY | O_CREAT | O_CLOEXEC, 0)
.log_ok();
} else {
path!("/storage/self").mkdirs(0o755).log_ok();
path!("/system/system/bin/init")
.symlink_to(path!("/storage/self/primary"))
.log_ok();
debug!("Symlink /storage/self/primary -> /system/system/bin/init");
}
path!("/init").rename_to(path!("/sdcard")).log_ok();
// Try to keep magiskinit in rootfs for samsung RKP
if unsafe {
mount(
raw_cstr!("/sdcard"),
raw_cstr!("/sdcard"),
null(),
MS_BIND,
null(),
)
} == 0
{
debug!("Bind mount /sdcard -> /sdcard");
} else {
// rootfs before 3.12
unsafe {
mount(
raw_cstr!("/data/magiskinit"),
raw_cstr!("/sdcard"),
null(),
MS_BIND,
null(),
)
};
debug!("Bind mount /data/magiskinit -> /sdcard");
}
self.restore_ramdisk_init();
if self.config.force_normal_boot {
cstr!("/first_stage_ramdisk/storage/self")
.mkdirs(0o755)
.log_ok();
cstr!("/first_stage_ramdisk/storage/self/primary")
.create_symlink_to(cstr!("/system/system/bin/init"))
.log_ok();
debug!("Symlink /first_stage_ramdisk/storage/self/primary -> /system/system/bin/init");
cstr!("/first_stage_ramdisk/sdcard")
.create(O_RDONLY | O_CREAT | O_CLOEXEC, 0)
.log_ok();
} else {
self.restore_ramdisk_init();
// fallback to hexpatch if /sdcard exists
match MappedFile::open_rw(cstr!("/init")) {
Ok(mut map) => {
let from = "/system/bin/init";
let to = "/data/magiskinit";
// Redirect original init to magiskinit
let v = map.patch(from.as_bytes(), to.as_bytes());
#[allow(unused_variables)]
for off in &v {
debug!("Patch @ {:#010X} [{}] -> [{}]", off, from, to);
}
}
_ => {
error!("Failed to open /init for hexpatch");
}
}
cstr!("/storage/self").mkdirs(0o755).log_ok();
cstr!("/storage/self/primary")
.create_symlink_to(cstr!("/system/system/bin/init"))
.log_ok();
debug!("Symlink /storage/self/primary -> /system/system/bin/init");
}
}
cstr!("/init").rename_to(cstr!("/sdcard")).log_ok();
pub(crate) fn redirect_second_stage(&self) {
let src = path!("/init");
let dest = path!("/data/init");
// Patch init binary
match MappedFile::open(src) {
Ok(mut map) => {
let from = "/system/bin/init";
let to = "/data/magiskinit";
// Redirect original init to magiskinit
let v = map.patch(from.as_bytes(), to.as_bytes());
#[allow(unused_variables)]
for off in &v {
debug!("Patch @ {:#010X} [{}] -> [{}]", off, from, to);
}
match dest.create(O_CREAT | O_WRONLY, 0) {
Ok(mut dest) => {
dest.write_all(map.as_ref()).log_ok();
}
_ => {
error!("Failed to create {}", dest);
}
}
}
_ => {
error!("Failed to open {} for hexpatch", src);
}
}
clone_attr(src, dest).log_ok();
unsafe {
mount(dest.as_ptr(), src.as_ptr(), null(), MS_BIND, null())
.as_os_err()
.ok();
}
}
pub(crate) fn second_stage(&mut self) {
info!("Second Stage Init");
unsafe {
umount2(raw_cstr!("/init"), MNT_DETACH);
umount2(raw_cstr!("/system/bin/init"), MNT_DETACH); // just in case
path!("/data/init").remove().ok();
// Make sure init dmesg logs won't get messed up
*self.argv = raw_cstr!("/system/bin/init") as *mut _;
// Some weird devices like meizu, uses 2SI but still have legacy rootfs
let mut sfs: statfs = std::mem::zeroed();
statfs(raw_cstr!("/"), &mut sfs);
if sfs.f_type == 0x858458f6 || sfs.f_type as c_long == TMPFS_MAGIC {
// We are still on rootfs, so make sure we will execute the init of the 2nd stage
let init_path = path!("/init");
init_path.remove().ok();
path!("/system/bin/init").symlink_to(init_path).log_ok();
self.patch_rw_root();
} else {
self.patch_ro_root();
}
// First try to mount magiskinit from rootfs to workaround Samsung RKP
if cstr!("/sdcard").bind_mount_to(cstr!("/sdcard")).is_ok() {
debug!("Bind mount /sdcard -> /sdcard");
} else {
// Binding mounting from rootfs is not supported before Linux 3.12
cstr!("/data/magiskinit")
.bind_mount_to(cstr!("/sdcard"))
.log_ok();
debug!("Bind mount /data/magiskinit -> /sdcard");
}
}
}

View File

@@ -1,9 +1,12 @@
use crate::ffi::SePolicy;
use crate::statement::format_statement_help;
use argh::FromArgs;
use base::{
EarlyExitExt, LoggedResult, Utf8CStr, cmdline_logging, cstr, libc::umask, log_err, map_args,
EarlyExitExt, FmtAdaptor, LoggedResult, Utf8CStr, cmdline_logging, cstr, libc::umask, log_err,
map_args,
};
use std::{ffi::c_char, io::Cursor};
use std::ffi::c_char;
use std::io::stderr;
#[derive(FromArgs)]
struct Cli {
@@ -61,7 +64,8 @@ it will load from current live policies (/sys/fs/selinux/policy)
cmd
);
SePolicy::print_statement_help();
format_statement_help(&mut FmtAdaptor(&mut stderr())).ok();
eprintln!();
}
#[unsafe(no_mangle)]
@@ -116,7 +120,7 @@ pub unsafe extern "C" fn main(
}
for statement in &cli.polices {
sepol.load_rules_from_reader(&mut Cursor::new(statement));
sepol.load_rules(statement);
}
if cli.live && !sepol.to_file(cstr!("/sys/fs/selinux/load")) {

View File

@@ -17,8 +17,4 @@
// selinuxfs paths
#define SELINUX_MNT "/sys/fs/selinux"
#define SELINUX_ENFORCE SELINUX_MNT "/enforce"
#define SELINUX_POLICY SELINUX_MNT "/policy"
#define SELINUX_LOAD SELINUX_MNT "/load"
#define SELINUX_VERSION SELINUX_MNT "/policyvers"
#define SELINUX_REQPROT SELINUX_MNT "/checkreqprot"

View File

@@ -2,14 +2,13 @@
#![feature(try_blocks)]
pub use base;
use base::libc::{O_CLOEXEC, O_RDONLY};
use base::{BufReadExt, FsPath, LoggedResult, Utf8CStr};
use cxx::CxxString;
use std::fmt::Write;
use std::io::{BufRead, BufReader, Cursor};
use crate::ffi::SePolicy;
#[path = "../include/consts.rs"]
mod consts;
#[cfg(feature = "main")]
mod cli;
mod rules;
@@ -84,37 +83,12 @@ pub mod ffi {
}
extern "Rust" {
fn parse_statement(self: &mut SePolicy, statement: Utf8CStrRef);
fn magisk_rules(self: &mut SePolicy);
fn load_rule_file(self: &mut SePolicy, filename: Utf8CStrRef);
fn load_rules(self: &mut SePolicy, rules: &CxxString);
#[Self = SePolicy]
fn xperm_to_string(perm: &Xperm) -> String;
}
}
impl SePolicy {
fn load_rules(self: &mut SePolicy, rules: &CxxString) {
let mut cursor = Cursor::new(rules.as_bytes());
self.load_rules_from_reader(&mut cursor);
}
pub fn load_rule_file(self: &mut SePolicy, filename: &Utf8CStr) {
let result: LoggedResult<()> = try {
let file = FsPath::from(filename).open(O_RDONLY | O_CLOEXEC)?;
let mut reader = BufReader::new(file);
self.load_rules_from_reader(&mut reader);
};
result.ok();
}
fn load_rules_from_reader<T: BufRead>(self: &mut SePolicy, reader: &mut T) {
reader.foreach_lines(|line| {
self.parse_statement(line);
true
});
}
fn xperm_to_string(perm: &ffi::Xperm) -> String {
let mut s = String::new();
if perm.reset {

View File

@@ -1,5 +1,6 @@
use crate::{ffi::Xperm, SePolicy};
use base::{set_log_level_state, LogLevel};
use crate::consts::{SEPOL_FILE_TYPE, SEPOL_LOG_TYPE, SEPOL_PROC_DOMAIN};
use crate::{SePolicy, ffi::Xperm};
use base::{LogLevel, set_log_level_state};
macro_rules! rules {
(@args all) => {
@@ -12,22 +13,22 @@ macro_rules! rules {
vec!["servicemanager", "vndservicemanager", "hwservicemanager"]
};
(@args [proc]) => {
vec!["magisk"]
vec![SEPOL_PROC_DOMAIN]
};
(@args [file]) => {
vec!["magisk_file"]
vec![SEPOL_FILE_TYPE]
};
(@args [log]) => {
vec!["magisk_log_file"]
vec![SEPOL_LOG_TYPE]
};
(@args proc) => {
"magisk"
SEPOL_PROC_DOMAIN
};
(@args file) => {
"magisk_file"
SEPOL_FILE_TYPE
};
(@args log) => {
"magisk_log_file"
SEPOL_LOG_TYPE
};
(@args [$($arg:tt)*]) => {
vec![$($arg)*]

View File

@@ -664,13 +664,13 @@ bool sepol_impl::add_typeattribute(Str type, Str attr) {
LOGW("type %.*s does not exist\n", (int) type.size(), type.data());
return false;
} else if (type_d->flavor == TYPE_ATTRIB) {
LOGW("type %.*s is an attribute\n", (int) attr.size(), attr.data());
LOGW("type %.*s is an attribute\n", (int) type.size(), type.data());
return false;
}
type_datum *attr_d = hashtab_find(db->p_types.table, attr);
if (attr_d == nullptr) {
LOGW("attribute %.*s does not exist\n", (int) type.size(), type.data());
LOGW("attribute %.*s does not exist\n", (int) attr.size(), attr.data());
return false;
} else if (attr_d->flavor != TYPE_ATTRIB) {
LOGW("type %.*s is not an attribute \n", (int) attr.size(), attr.data());

View File

@@ -1,10 +1,11 @@
use std::fmt::{Display, Formatter, Write};
use std::io::stderr;
use std::io::{BufRead, BufReader, Cursor};
use std::{iter::Peekable, vec::IntoIter};
use crate::ffi::Xperm;
use crate::SePolicy;
use base::{error, warn, FmtAdaptor};
use crate::ffi::Xperm;
use base::libc::{O_CLOEXEC, O_RDONLY};
use base::{BufReadExt, LoggedResult, Utf8CStr, error, warn};
pub enum Token<'a> {
AL,
@@ -64,10 +65,10 @@ fn parse_id<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, &'a str> {
}
}
// names ::= ID(n) { vec![n] };
// names ::= names(mut v) ID(n) { v.push(n); v };
// term ::= ID(n) { vec![n] }
// term ::= LB names(n) RB { n };
// names ::= ID(n) { vec![n] };
// names ::= names(mut v) ID(n) { v.push(n); v };
// term ::= ID(n) { vec![n] }
// term ::= LB names(n) RB { n };
fn parse_term<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec<&'a str>> {
match tokens.next() {
Some(Token::ID(name)) => Ok(vec![name]),
@@ -86,13 +87,13 @@ fn parse_term<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec<&'a str>> {
}
}
// names ::= ST { vec![] }
// names ::= ID(n) { vec![n] };
// names ::= names(mut v) ID(n) { v.push(n); v };
// names ::= names(n) ST { vec![] };
// sterm ::= ST { vec![] }
// sterm ::= ID(n) { vec![n] }
// sterm ::= LB names(n) RB { n };
// names ::= ST { vec![] }
// names ::= ID(n) { vec![n] };
// names ::= names(mut v) ID(n) { v.push(n); v };
// names ::= names(n) ST { vec![] };
// sterm ::= ST { vec![] }
// sterm ::= ID(n) { vec![n] }
// sterm ::= LB names(n) RB { n };
fn parse_sterm<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec<&'a str>> {
match tokens.next() {
Some(Token::ID(name)) => Ok(vec![name]),
@@ -117,8 +118,8 @@ fn parse_sterm<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec<&'a str>> {
}
}
// xperm ::= HX(low) { Xperm{low, high: low, reset: false} };
// xperm ::= HX(low) HP HX(high) { Xperm{low, high, reset: false} };
// xperm ::= HX(low) { Xperm{low, high: low, reset: false} };
// xperm ::= HX(low) HP HX(high) { Xperm{low, high, reset: false} };
fn parse_xperm<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Xperm> {
let low = match tokens.next() {
Some(Token::HX(low)) => low,
@@ -141,13 +142,13 @@ fn parse_xperm<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Xperm> {
})
}
// xperms ::= HX(low) { if low > 0 { vec![Xperm{low, high: low, reset: false}] } else { vec![Xperm{low: 0x0000, high: 0xFFFF, reset: true}] }};
// xperms ::= LB xperm_list(l) RB { l };
// xperms ::= TL LB xperm_list(mut l) RB { l.iter_mut().for_each(|x| { x.reset = true; }); l };
// xperms ::= ST { vec![Xperm{low: 0x0000, high: 0xFFFF, reset: false}] };
// xperms ::= HX(low) { if low > 0 { vec![Xperm{low, high: low, reset: false}] } else { vec![Xperm{low: 0x0000, high: 0xFFFF, reset: true}] }};
// xperms ::= LB xperm_list(l) RB { l };
// xperms ::= TL LB xperm_list(mut l) RB { l.iter_mut().for_each(|x| { x.reset = true; }); l };
// xperms ::= ST { vec![Xperm{low: 0x0000, high: 0xFFFF, reset: false}] };
//
// xperm_list ::= xperm(p) { vec![p] }
// xperm_list ::= xperm_list(mut l) xperm(p) { l.push(p); l }
// xperm_list ::= xperm(p) { vec![p] }
// xperm_list ::= xperm_list(mut l) xperm(p) { l.push(p); l }
fn parse_xperms<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec<Xperm>> {
let mut xperms = Vec::new();
let reset = match tokens.peek() {
@@ -209,177 +210,6 @@ fn match_string<'a>(tokens: &mut Tokens<'a>, pattern: &str) -> ParseResult<'a, (
Err(ParseError::General)
}
// statement ::= AL sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.allow(s, t, c, p); };
// statement ::= DN sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.deny(s, t, c, p); };
// statement ::= AA sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.auditallow(s, t, c, p); };
// statement ::= DA sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.dontaudit(s, t, c, p); };
// statement ::= AX sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.allowxperm(s, t, c, p); };
// statement ::= AY sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.auditallowxperm(s, t, c, p); };
// statement ::= DX sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.dontauditxperm(s, t, c, p); };
// statement ::= PM sterm(t) { sepolicy.permissive(t); };
// statement ::= EF sterm(t) { sepolicy.enforce(t); };
// statement ::= TA term(t) term(a) { sepolicy.typeattribute(t, a); };
// statement ::= TY ID(t) { sepolicy.type_(t, vec![]);};
// statement ::= TY ID(t) term(a) { sepolicy.type_(t, a);};
// statement ::= AT ID(t) { sepolicy.attribute(t); };
// statement ::= TT ID(s) ID(t) ID(c) ID(d) { sepolicy.type_transition(s, t, c, d, vec![]); };
// statement ::= TT ID(s) ID(t) ID(c) ID(d) ID(o) { sepolicy.type_transition(s, t, c, d, vec![o]); };
// statement ::= TC ID(s) ID(t) ID(c) ID(d) { sepolicy.type_change(s, t, c, d); };
// statement ::= TM ID(s) ID(t) ID(c) ID(d) { sepolicy.type_member(s, t, c, d);};
// statement ::= GF ID(s) ID(t) ID(c) { sepolicy.genfscon(s, t, c); };
fn exec_statement<'a>(sepolicy: &mut SePolicy, tokens: &mut Tokens<'a>) -> ParseResult<'a, ()> {
let action = match tokens.next() {
Some(token) => token,
_ => Err(ParseError::ShowHelp)?,
};
let check_additional_args = |tokens: &mut Tokens<'a>| {
if tokens.peek().is_none() {
Ok(())
} else {
Err(ParseError::General)
}
};
match action {
Token::AL | Token::DN | Token::AA | Token::DA => {
let result: ParseResult<()> = try {
let s = parse_sterm(tokens)?;
let t = parse_sterm(tokens)?;
let c = parse_sterm(tokens)?;
let p = parse_sterm(tokens)?;
check_additional_args(tokens)?;
match action {
Token::AL => sepolicy.allow(s, t, c, p),
Token::DN => sepolicy.deny(s, t, c, p),
Token::AA => sepolicy.auditallow(s, t, c, p),
Token::DA => sepolicy.dontaudit(s, t, c, p),
_ => unreachable!(),
}
};
if result.is_err() {
Err(ParseError::AvtabAv(action))?
}
}
Token::AX | Token::AY | Token::DX => {
let result: ParseResult<()> = try {
let s = parse_sterm(tokens)?;
let t = parse_sterm(tokens)?;
let c = parse_sterm(tokens)?;
match_string(tokens, "ioctl")?;
let p = parse_xperms(tokens)?;
check_additional_args(tokens)?;
match action {
Token::AX => sepolicy.allowxperm(s, t, c, p),
Token::AY => sepolicy.auditallowxperm(s, t, c, p),
Token::DX => sepolicy.dontauditxperm(s, t, c, p),
_ => unreachable!(),
}
};
if result.is_err() {
Err(ParseError::AvtabXperms(action))?
}
}
Token::PM | Token::EF => {
let result: ParseResult<()> = try {
let t = parse_sterm(tokens)?;
check_additional_args(tokens)?;
match action {
Token::PM => sepolicy.permissive(t),
Token::EF => sepolicy.enforce(t),
_ => unreachable!(),
}
};
if result.is_err() {
Err(ParseError::TypeState(action))?
}
}
Token::TA => {
let result: ParseResult<()> = try {
let t = parse_term(tokens)?;
let a = parse_term(tokens)?;
check_additional_args(tokens)?;
sepolicy.typeattribute(t, a)
};
if result.is_err() {
Err(ParseError::TypeAttr)?
}
}
Token::TY => {
let result: ParseResult<()> = try {
let t = parse_id(tokens)?;
let a = if tokens.peek().is_none() {
vec![]
} else {
parse_term(tokens)?
};
check_additional_args(tokens)?;
sepolicy.type_(t, a)
};
if result.is_err() {
Err(ParseError::NewType)?
}
}
Token::AT => {
let result: ParseResult<()> = try {
let t = parse_id(tokens)?;
check_additional_args(tokens)?;
sepolicy.attribute(t)
};
if result.is_err() {
Err(ParseError::NewAttr)?
}
}
Token::TC | Token::TM => {
let result: ParseResult<()> = try {
let s = parse_id(tokens)?;
let t = parse_id(tokens)?;
let c = parse_id(tokens)?;
let d = parse_id(tokens)?;
check_additional_args(tokens)?;
match action {
Token::TC => sepolicy.type_change(s, t, c, d),
Token::TM => sepolicy.type_member(s, t, c, d),
_ => unreachable!(),
}
};
if result.is_err() {
Err(ParseError::AvtabType(action))?
}
}
Token::TT => {
let result: ParseResult<()> = try {
let s = parse_id(tokens)?;
let t = parse_id(tokens)?;
let c = parse_id(tokens)?;
let d = parse_id(tokens)?;
let o = if tokens.peek().is_none() {
""
} else {
parse_id(tokens)?
};
check_additional_args(tokens)?;
sepolicy.type_transition(s, t, c, d, o);
};
if result.is_err() {
Err(ParseError::TypeTrans)?
}
}
Token::GF => {
let result: ParseResult<()> = try {
let s = parse_id(tokens)?;
let t = parse_id(tokens)?;
let c = parse_id(tokens)?;
check_additional_args(tokens)?;
sepolicy.genfscon(s, t, c)
};
if result.is_err() {
Err(ParseError::GenfsCon)?
}
}
_ => Err(ParseError::UnknownAction(action))?,
}
Ok(())
}
fn extract_token<'a>(s: &'a str, tokens: &mut Vec<Token<'a>>) {
match s {
"allow" => tokens.push(Token::AL),
@@ -442,18 +272,210 @@ fn tokenize_statement(statement: &str) -> Vec<Token> {
}
impl SePolicy {
pub fn parse_statement(self: &mut SePolicy, statement: &str) {
pub fn load_rules(&mut self, rules: &str) {
let mut cursor = Cursor::new(rules.as_bytes());
self.load_rules_from_reader(&mut cursor);
}
pub fn load_rule_file(&mut self, filename: &Utf8CStr) {
let result: LoggedResult<()> = try {
let file = filename.open(O_RDONLY | O_CLOEXEC)?;
let mut reader = BufReader::new(file);
self.load_rules_from_reader(&mut reader);
};
result.ok();
}
fn load_rules_from_reader<T: BufRead>(&mut self, reader: &mut T) {
reader.foreach_lines(|line| {
self.parse_statement(line);
true
});
}
fn parse_statement(&mut self, statement: &str) {
let statement = statement.trim();
if statement.is_empty() || statement.starts_with('#') {
return;
}
let mut tokens = tokenize_statement(statement).into_iter().peekable();
let result = exec_statement(self, &mut tokens);
let result = self.exec_statement(&mut tokens);
if let Err(e) = result {
warn!("Syntax error in: \"{}\"", statement);
error!("Hint: {}", e);
}
}
// statement ::= AL sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.allow(s, t, c, p); };
// statement ::= DN sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.deny(s, t, c, p); };
// statement ::= AA sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.auditallow(s, t, c, p); };
// statement ::= DA sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.dontaudit(s, t, c, p); };
// statement ::= AX sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.allowxperm(s, t, c, p); };
// statement ::= AY sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.auditallowxperm(s, t, c, p); };
// statement ::= DX sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.dontauditxperm(s, t, c, p); };
// statement ::= PM sterm(t) { sepolicy.permissive(t); };
// statement ::= EF sterm(t) { sepolicy.enforce(t); };
// statement ::= TA term(t) term(a) { sepolicy.typeattribute(t, a); };
// statement ::= TY ID(t) { sepolicy.type_(t, vec![]);};
// statement ::= TY ID(t) term(a) { sepolicy.type_(t, a);};
// statement ::= AT ID(t) { sepolicy.attribute(t); };
// statement ::= TT ID(s) ID(t) ID(c) ID(d) { sepolicy.type_transition(s, t, c, d, vec![]); };
// statement ::= TT ID(s) ID(t) ID(c) ID(d) ID(o) { sepolicy.type_transition(s, t, c, d, vec![o]); };
// statement ::= TC ID(s) ID(t) ID(c) ID(d) { sepolicy.type_change(s, t, c, d); };
// statement ::= TM ID(s) ID(t) ID(c) ID(d) { sepolicy.type_member(s, t, c, d);};
// statement ::= GF ID(s) ID(t) ID(c) { sepolicy.genfscon(s, t, c); };
fn exec_statement<'a>(&mut self, tokens: &mut Tokens<'a>) -> ParseResult<'a, ()> {
let action = match tokens.next() {
Some(token) => token,
_ => Err(ParseError::ShowHelp)?,
};
let check_additional_args = |tokens: &mut Tokens<'a>| {
if tokens.peek().is_none() {
Ok(())
} else {
Err(ParseError::General)
}
};
match action {
Token::AL | Token::DN | Token::AA | Token::DA => {
let result: ParseResult<()> = try {
let s = parse_sterm(tokens)?;
let t = parse_sterm(tokens)?;
let c = parse_sterm(tokens)?;
let p = parse_sterm(tokens)?;
check_additional_args(tokens)?;
match action {
Token::AL => self.allow(s, t, c, p),
Token::DN => self.deny(s, t, c, p),
Token::AA => self.auditallow(s, t, c, p),
Token::DA => self.dontaudit(s, t, c, p),
_ => unreachable!(),
}
};
if result.is_err() {
Err(ParseError::AvtabAv(action))?
}
}
Token::AX | Token::AY | Token::DX => {
let result: ParseResult<()> = try {
let s = parse_sterm(tokens)?;
let t = parse_sterm(tokens)?;
let c = parse_sterm(tokens)?;
match_string(tokens, "ioctl")?;
let p = parse_xperms(tokens)?;
check_additional_args(tokens)?;
match action {
Token::AX => self.allowxperm(s, t, c, p),
Token::AY => self.auditallowxperm(s, t, c, p),
Token::DX => self.dontauditxperm(s, t, c, p),
_ => unreachable!(),
}
};
if result.is_err() {
Err(ParseError::AvtabXperms(action))?
}
}
Token::PM | Token::EF => {
let result: ParseResult<()> = try {
let t = parse_sterm(tokens)?;
check_additional_args(tokens)?;
match action {
Token::PM => self.permissive(t),
Token::EF => self.enforce(t),
_ => unreachable!(),
}
};
if result.is_err() {
Err(ParseError::TypeState(action))?
}
}
Token::TA => {
let result: ParseResult<()> = try {
let t = parse_term(tokens)?;
let a = parse_term(tokens)?;
check_additional_args(tokens)?;
self.typeattribute(t, a)
};
if result.is_err() {
Err(ParseError::TypeAttr)?
}
}
Token::TY => {
let result: ParseResult<()> = try {
let t = parse_id(tokens)?;
let a = if tokens.peek().is_none() {
vec![]
} else {
parse_term(tokens)?
};
check_additional_args(tokens)?;
self.type_(t, a)
};
if result.is_err() {
Err(ParseError::NewType)?
}
}
Token::AT => {
let result: ParseResult<()> = try {
let t = parse_id(tokens)?;
check_additional_args(tokens)?;
self.attribute(t)
};
if result.is_err() {
Err(ParseError::NewAttr)?
}
}
Token::TC | Token::TM => {
let result: ParseResult<()> = try {
let s = parse_id(tokens)?;
let t = parse_id(tokens)?;
let c = parse_id(tokens)?;
let d = parse_id(tokens)?;
check_additional_args(tokens)?;
match action {
Token::TC => self.type_change(s, t, c, d),
Token::TM => self.type_member(s, t, c, d),
_ => unreachable!(),
}
};
if result.is_err() {
Err(ParseError::AvtabType(action))?
}
}
Token::TT => {
let result: ParseResult<()> = try {
let s = parse_id(tokens)?;
let t = parse_id(tokens)?;
let c = parse_id(tokens)?;
let d = parse_id(tokens)?;
let o = if tokens.peek().is_none() {
""
} else {
parse_id(tokens)?
};
check_additional_args(tokens)?;
self.type_transition(s, t, c, d, o);
};
if result.is_err() {
Err(ParseError::TypeTrans)?
}
}
Token::GF => {
let result: ParseResult<()> = try {
let s = parse_id(tokens)?;
let t = parse_id(tokens)?;
let c = parse_id(tokens)?;
check_additional_args(tokens)?;
self.genfscon(s, t, c)
};
if result.is_err() {
Err(ParseError::GenfsCon)?
}
}
_ => Err(ParseError::UnknownAction(action))?,
}
Ok(())
}
}
// Token to string
@@ -520,11 +542,11 @@ impl Display for ParseError<'_> {
}
}
fn format_statement_help(f: &mut dyn Write) -> std::fmt::Result {
pub(crate) fn format_statement_help(f: &mut dyn Write) -> std::fmt::Result {
write!(
f,
r#"** Policy statements:
One policy statement should be treated as a single parameter;
this means each policy statement should be enclosed in quotes.
Multiple policy statements can be provided in a single command.
@@ -596,10 +618,3 @@ allowxperm source target class ioctl *
ParseError::GenfsCon
)
}
impl SePolicy {
pub fn print_statement_help() {
format_statement_help(&mut FmtAdaptor(&mut stderr())).ok();
eprintln!();
}
}

View File

@@ -3,7 +3,10 @@
set -xe
. scripts/test_common.sh
emu_args_base="-no-window -no-audio -no-boot-anim -gpu swiftshader_indirect -read-only -no-snapshot -cores $core_count"
emu_port=5682
export ANDROID_SERIAL="emulator-$emu_port"
emu_args_base="-no-window -no-audio -no-boot-anim -gpu swiftshader_indirect -read-only -no-snapshot -port $emu_port -cores $core_count"
emu_pid=
atd_min_api=30
@@ -21,20 +24,6 @@ cleanup() {
exit 1
}
wait_for_bootanim() {
set -e
adb wait-for-device
while true; do
local result="$(adb exec-out getprop init.svc.bootanim)"
if [ $? -ne 0 ]; then
exit 1
elif [ "$result" = "stopped" ]; then
break
fi
sleep 2
done
}
wait_for_boot() {
set -e
adb wait-for-device
@@ -50,10 +39,9 @@ wait_for_boot() {
}
wait_emu() {
local wait_fn=$1
local which_pid
timeout $boot_timeout bash -c $wait_fn &
timeout $boot_timeout bash -c wait_for_boot &
local wait_pid=$!
# Handle the case when emulator dies earlier than timeout
@@ -75,12 +63,12 @@ test_emu() {
fi
emu_pid=$!
wait_emu wait_for_boot
wait_emu
run_setup $variant
adb reboot
wait_emu wait_for_boot
wait_emu
run_tests
}
@@ -136,7 +124,7 @@ test_main() {
print_title "* Launching $avd_pkg"
"$emu" @test $emu_args >/dev/null 2>&1 &
emu_pid=$!
wait_emu wait_for_bootanim
wait_emu
# Update arguments for Magisk runs
emu_args="$emu_args -ramdisk magisk_patched.img -feature -SystemAsRoot"
@@ -168,7 +156,6 @@ test_main() {
trap cleanup EXIT
export -f wait_for_boot
export -f wait_for_bootanim
case $(uname -m) in
'arm64'|'aarch64')
@@ -205,9 +192,9 @@ else
test_main $api
done
# Android 16 Beta
test_main Baklava google_apis
test_main 36 google_apis
# Run 16k page tests
test_main Baklava google_apis_ps16k
test_main 36 google_apis_ps16k
fi
"$avd" delete avd -n test

View File

@@ -19,7 +19,8 @@ run_cvd_bin() {
}
setup_env() {
curl -LO https://github.com/topjohnwu/magisk-files/releases/download/files/cuttlefish-base_0.9.30_amd64.deb
curl -LO https://github.com/topjohnwu/magisk-files/releases/download/files/cuttlefish-base_1.2.0_amd64.deb
sudo apt-get update
sudo dpkg -i ./cuttlefish-base_*_*64.deb || sudo apt-get install -f
rm cuttlefish-base_*_*64.deb
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
@@ -37,12 +38,12 @@ download_cf() {
local device=$2
if [ -z $branch ]; then
branch='aosp-main'
branch='aosp-android-latest-release'
fi
if [ -z $device ]; then
device='aosp_cf_x86_64_phone'
device='aosp_cf_x86_64_only_phone'
fi
local target="${device}-trunk_staging-userdebug"
local target="${device}-userdebug"
local build_id=$(curl -sL https://ci.android.com/builds/branches/${branch}/status.json | \
jq -r ".targets[] | select(.name == \"$target\") | .last_known_good_build")

View File

@@ -12,7 +12,7 @@ emu="$ANDROID_HOME/emulator/emulator"
sdk="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
avd="$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager"
boot_timeout=600
boot_timeout=100
core_count=$(nproc)
if [ $core_count -gt 8 ]; then

Binary file not shown.

1
tools/elf-cleaner/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

133
tools/elf-cleaner/Cargo.lock generated Normal file
View File

@@ -0,0 +1,133 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "anyhow"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "elf-cleaner"
version = "0.0.0"
dependencies = [
"anyhow",
"object",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "flate2"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
dependencies = [
"foldhash",
]
[[package]]
name = "indexmap"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "miniz_oxide"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
"adler2",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"crc32fast",
"flate2",
"hashbrown",
"indexmap",
"memchr",
"ruzstd",
]
[[package]]
name = "ruzstd"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f"
dependencies = [
"twox-hash",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "twox-hash"
version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if",
"static_assertions",
]

View File

@@ -0,0 +1,13 @@
[package]
name = "elf-cleaner"
version = "0.0.0"
edition = "2024"
[dependencies]
object = { version = "0.36", features = ["build"] }
anyhow = "1.0"
[profile.release]
strip = true
lto = true
codegen-units = 1

View File

@@ -0,0 +1,85 @@
use object::build::elf::{Builder, Dynamic, SectionData};
use object::elf;
use std::{env, fs};
// Implementation adapted from https://github.com/termux/termux-elf-cleaner
// Missing ELF constants
const DT_AARCH64_BTI_PLT: u32 = elf::DT_LOPROC + 1;
const DT_AARCH64_PAC_PLT: u32 = elf::DT_LOPROC + 3;
const DT_AARCH64_VARIANT_PCS: u32 = elf::DT_LOPROC + 5;
const SUPPORTED_DT_FLAGS: u32 = elf::DF_1_NOW | elf::DF_1_GLOBAL;
fn print_remove_dynamic(name: &str, path: &str) {
println!("Removing dynamic section entry {} in '{}'", name, path);
}
fn process_elf(path: &str) -> anyhow::Result<()> {
let bytes = fs::read(path)?;
let mut elf = Builder::read(bytes.as_slice())?;
let is_aarch64 = elf.header.e_machine == elf::EM_AARCH64;
elf.sections.iter_mut().for_each(|section| {
if let SectionData::Dynamic(entries) = &mut section.data {
// Remove unsupported entries
entries.retain(|e| {
let tag = e.tag();
match tag {
elf::DT_RPATH => {
print_remove_dynamic("DT_RPATH", path);
return false;
}
elf::DT_RUNPATH => {
print_remove_dynamic("DT_RUNPATH", path);
return false;
}
_ => {}
}
if is_aarch64 {
match tag {
DT_AARCH64_BTI_PLT => {
print_remove_dynamic("DT_AARCH64_BTI_PLT", path);
return false;
}
DT_AARCH64_PAC_PLT => {
print_remove_dynamic("DT_AARCH64_PAC_PLT", path);
return false;
}
DT_AARCH64_VARIANT_PCS => {
print_remove_dynamic("DT_AARCH64_VARIANT_PCS", path);
return false;
}
_ => {}
}
}
true
});
// Remove unsupported flags
for entry in entries.iter_mut() {
if let Dynamic::Integer { tag, val } = entry {
if *tag == elf::DT_FLAGS_1 {
let new_flags = *val & SUPPORTED_DT_FLAGS as u64;
if new_flags != *val {
println!(
"Replacing unsupported DT_FLAGS_1 {:#x} with {:#x} in '{}'",
*val, new_flags, path
);
*val = new_flags;
}
break;
}
}
}
}
});
let mut out_bytes = Vec::new();
elf.write(&mut out_bytes)?;
fs::write(path, &out_bytes)?;
Ok(())
}
fn main() -> anyhow::Result<()> {
env::args().skip(1).try_for_each(|s| process_elf(&s))
}

View File

@@ -12,7 +12,7 @@ dependencies = [
]
[[package]]
name = "rustup_wrapper"
name = "rustup-wrapper"
version = "0.0.0"
dependencies = [
"home",

View File

@@ -1,7 +1,7 @@
[package]
name = "rustup_wrapper"
name = "rustup-wrapper"
version = "0.0.0"
edition = "2021"
edition = "2024"
[dependencies]
home = "0.5"

Some files were not shown because too many files have changed in this diff Show More