mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-12-14 10:52:15 +00:00
Compare commits
55 Commits
canary-281
...
canary-281
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
427a1ca4e5 | ||
|
|
22884e173a | ||
|
|
d1829308e9 | ||
|
|
73840f8721 | ||
|
|
c7d1af9805 | ||
|
|
4ad26d3dfb | ||
|
|
0c70b7670c | ||
|
|
f44d044095 | ||
|
|
5c1cb13472 | ||
|
|
3327fc668e | ||
|
|
610945ac54 | ||
|
|
ddf5474917 | ||
|
|
6ba1685ade | ||
|
|
e02b5f7868 | ||
|
|
ab2e5d1e7e | ||
|
|
f3fef7bfe4 | ||
|
|
c34c7838bb | ||
|
|
c8a16b0e0c | ||
|
|
14f9ed91a1 | ||
|
|
7a207d4ccf | ||
|
|
92a42d901f | ||
|
|
084d89fcce | ||
|
|
55b036c071 | ||
|
|
30e79310ab | ||
|
|
f063fa5054 | ||
|
|
7bd901273c | ||
|
|
c1e061603b | ||
|
|
cb08504fe5 | ||
|
|
c0a1fb77be | ||
|
|
4864c1112a | ||
|
|
9ddeab034b | ||
|
|
c4847ed288 | ||
|
|
b8f1523fb2 | ||
|
|
fb7fa8a6b3 | ||
|
|
9c7d359093 | ||
|
|
eb54bc1fd7 | ||
|
|
d4a0286e13 | ||
|
|
83e66767ff | ||
|
|
7dc010749b | ||
|
|
8e8d013b1b | ||
|
|
bba0373808 | ||
|
|
1fa318dc8c | ||
|
|
6edc5e2037 | ||
|
|
1523ed9f78 | ||
|
|
8e604d2ab8 | ||
|
|
2aba7247a9 | ||
|
|
e66fe8533e | ||
|
|
b03fbb3917 | ||
|
|
c2ece62e4c | ||
|
|
8c972dcf34 | ||
|
|
50af14f2a3 | ||
|
|
e0a356b319 | ||
|
|
c09a792958 | ||
|
|
0bbfe7f44d | ||
|
|
a396abf565 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -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
|
||||
|
||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -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
3
.gitmodules
vendored
@@ -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
|
||||
|
||||
@@ -22,7 +22,7 @@ Click the icon below to download Magisk apk.
|
||||
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/v28.1)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-28103)
|
||||
[](https://github.com/topjohnwu/Magisk/releases/tag/canary-28104)
|
||||
|
||||
## Useful Links
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:label="Magisk"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
|
||||
53
build.py
53
build.py
@@ -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"
|
||||
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
76
native/src/Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() }};
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
90
native/src/base/mount.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use pb_rs::{types::FileDescriptor, ConfigBuilder};
|
||||
use pb_rs::{ConfigBuilder, types::FileDescriptor};
|
||||
|
||||
use crate::codegen::gen_cxx_binding;
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <consts.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <base.hpp>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <consts.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <base.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use pb_rs::{types::FileDescriptor, ConfigBuilder};
|
||||
use pb_rs::{ConfigBuilder, types::FileDescriptor};
|
||||
|
||||
use crate::codegen::gen_cxx_binding;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include <base.hpp>
|
||||
#include <consts.hpp>
|
||||
#include <core.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <flags.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <base.hpp>
|
||||
#include <consts.hpp>
|
||||
#include <core.hpp>
|
||||
#include <selinux.hpp>
|
||||
|
||||
#include "node.hpp"
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <consts.hpp>
|
||||
#include <base.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <core.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
@@ -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
108
native/src/core/selinux.rs
Normal 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()
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <selinux.hpp>
|
||||
#include <consts.hpp>
|
||||
#include <core.hpp>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
mod daemon;
|
||||
mod db;
|
||||
mod pts;
|
||||
|
||||
pub use daemon::SuInfo;
|
||||
pub use pts::{get_pty_num, pump_tty, restore_stdin};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
151
native/src/core/su/pts.rs
Normal 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) };
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <consts.hpp>
|
||||
#include <base.hpp>
|
||||
#include <core.hpp>
|
||||
#include <selinux.hpp>
|
||||
|
||||
#include "zygisk.hpp"
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
279
native/src/init/selinux.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)*]
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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!();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
1
tools/elf-cleaner/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
||||
133
tools/elf-cleaner/Cargo.lock
generated
Normal file
133
tools/elf-cleaner/Cargo.lock
generated
Normal 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",
|
||||
]
|
||||
13
tools/elf-cleaner/Cargo.toml
Normal file
13
tools/elf-cleaner/Cargo.toml
Normal 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
|
||||
85
tools/elf-cleaner/src/main.rs
Normal file
85
tools/elf-cleaner/src/main.rs
Normal 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))
|
||||
}
|
||||
@@ -12,7 +12,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustup_wrapper"
|
||||
name = "rustup-wrapper"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"home",
|
||||
@@ -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
Reference in New Issue
Block a user