mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-18 05:58:30 +00:00
Preparation for hiding isolated processes
This commit is contained in:
parent
3f9a64417b
commit
8e61080a4a
@ -7,9 +7,11 @@ import android.content.Context
|
|||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.ComponentInfo
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.PackageManager.*
|
import android.content.pm.PackageManager.*
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.content.pm.ServiceInfo.FLAG_ISOLATED_PROCESS
|
||||||
|
import android.content.pm.ServiceInfo.FLAG_USE_APP_ZYGOTE
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
@ -57,32 +59,10 @@ import java.lang.reflect.Array as JArray
|
|||||||
|
|
||||||
val packageName: String get() = get<Context>().packageName
|
val packageName: String get() = get<Context>().packageName
|
||||||
|
|
||||||
val ApplicationInfo.processes: List<String> @SuppressLint("InlinedApi") get() {
|
val ServiceInfo.isIsolated get() = (flags and FLAG_ISOLATED_PROCESS) != 0
|
||||||
val pm = get<PackageManager>()
|
|
||||||
val appProcessName = processName ?: packageName
|
@get:SuppressLint("InlinedApi")
|
||||||
val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES
|
val ServiceInfo.useAppZygote get() = (flags and FLAG_USE_APP_ZYGOTE) != 0
|
||||||
val packageInfo = try {
|
|
||||||
val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS
|
|
||||||
pm.getPackageInfo(packageName, baseFlag or request)
|
|
||||||
} catch (e: NameNotFoundException) { // EdXposed hooked, issue#3276
|
|
||||||
return listOf(appProcessName)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Exceed binder data transfer limit, fetch each component type separately
|
|
||||||
pm.getPackageInfo(packageName, baseFlag).apply {
|
|
||||||
runCatching { activities = pm.getPackageInfo(packageName, GET_ACTIVITIES).activities }
|
|
||||||
runCatching { services = pm.getPackageInfo(packageName, GET_SERVICES).services }
|
|
||||||
runCatching { receivers = pm.getPackageInfo(packageName, GET_RECEIVERS).receivers }
|
|
||||||
runCatching { providers = pm.getPackageInfo(packageName, GET_PROVIDERS).providers }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fun Array<out ComponentInfo>.processNames() = map { it.processName ?: appProcessName }
|
|
||||||
return with(packageInfo) {
|
|
||||||
activities?.processNames().orEmpty() +
|
|
||||||
services?.processNames().orEmpty() +
|
|
||||||
receivers?.processNames().orEmpty() +
|
|
||||||
providers?.processNames().orEmpty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
fun Context.rawResource(id: Int) = resources.openRawResource(id)
|
||||||
|
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.ui.hide
|
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import com.topjohnwu.magisk.core.utils.currentLocale
|
|
||||||
import com.topjohnwu.magisk.ktx.getLabel
|
|
||||||
|
|
||||||
class HideTarget(line: String) {
|
|
||||||
val packageName: String
|
|
||||||
val process: String
|
|
||||||
|
|
||||||
init {
|
|
||||||
val split = line.split(Regex("\\|"), 2)
|
|
||||||
packageName = split[0]
|
|
||||||
process = split.getOrElse(1) { packageName }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HideAppInfo(info: ApplicationInfo, pm: PackageManager)
|
|
||||||
: ApplicationInfo(info), Comparable<HideAppInfo> {
|
|
||||||
|
|
||||||
val label = info.getLabel(pm)
|
|
||||||
val iconImage: Drawable = info.loadIcon(pm)
|
|
||||||
|
|
||||||
override fun compareTo(other: HideAppInfo) = comparator.compare(this, other)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val comparator = compareBy<HideAppInfo>(
|
|
||||||
{ it.label.toLowerCase(currentLocale) },
|
|
||||||
{ it.packageName }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class HideProcessInfo(
|
|
||||||
val name: String,
|
|
||||||
val packageName: String,
|
|
||||||
val isHidden: Boolean
|
|
||||||
)
|
|
||||||
|
|
||||||
class HideAppTarget(
|
|
||||||
val info: HideAppInfo,
|
|
||||||
val processes: List<HideProcessInfo>
|
|
||||||
) : Comparable<HideAppTarget> {
|
|
||||||
override fun compareTo(other: HideAppTarget) = compareValuesBy(this, other) { it.info }
|
|
||||||
}
|
|
102
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideInfo.kt
Normal file
102
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideInfo.kt
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.hide
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.ComponentInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.PackageManager.*
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import com.topjohnwu.magisk.core.utils.currentLocale
|
||||||
|
import com.topjohnwu.magisk.ktx.getLabel
|
||||||
|
import com.topjohnwu.magisk.ktx.isIsolated
|
||||||
|
import com.topjohnwu.magisk.ktx.useAppZygote
|
||||||
|
|
||||||
|
class CmdlineHiddenItem(line: String) {
|
||||||
|
val packageName: String
|
||||||
|
val process: String
|
||||||
|
|
||||||
|
init {
|
||||||
|
val split = line.split(Regex("\\|"), 2)
|
||||||
|
packageName = split[0]
|
||||||
|
process = split.getOrElse(1) { packageName }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const val ISOLATED_MAGIC = "isolated"
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
class HideAppInfo(info: ApplicationInfo, pm: PackageManager, hideList: List<CmdlineHiddenItem>)
|
||||||
|
: ApplicationInfo(info), Comparable<HideAppInfo> {
|
||||||
|
|
||||||
|
val label = info.getLabel(pm)
|
||||||
|
val iconImage: Drawable = info.loadIcon(pm)
|
||||||
|
val processes = fetchProcesses(pm, hideList)
|
||||||
|
|
||||||
|
override fun compareTo(other: HideAppInfo) = comparator.compare(this, other)
|
||||||
|
|
||||||
|
private fun fetchProcesses(
|
||||||
|
pm: PackageManager,
|
||||||
|
hideList: List<CmdlineHiddenItem>
|
||||||
|
): List<HideProcessInfo> {
|
||||||
|
// Fetch full PackageInfo
|
||||||
|
val baseFlag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES
|
||||||
|
val packageInfo = try {
|
||||||
|
val request = GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS
|
||||||
|
pm.getPackageInfo(packageName, baseFlag or request)
|
||||||
|
} catch (e: NameNotFoundException) {
|
||||||
|
// EdXposed hooked, issue#3276
|
||||||
|
return emptyList()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Exceed binder data transfer limit, fetch each component type separately
|
||||||
|
pm.getPackageInfo(packageName, baseFlag).apply {
|
||||||
|
runCatching { activities = pm.getPackageInfo(packageName, baseFlag or GET_ACTIVITIES).activities }
|
||||||
|
runCatching { services = pm.getPackageInfo(packageName, baseFlag or GET_SERVICES).services }
|
||||||
|
runCatching { receivers = pm.getPackageInfo(packageName, baseFlag or GET_RECEIVERS).receivers }
|
||||||
|
runCatching { providers = pm.getPackageInfo(packageName, baseFlag or GET_PROVIDERS).providers }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val hidden = hideList.filter { it.packageName == packageName || it.packageName == ISOLATED_MAGIC }
|
||||||
|
fun createProcess(name: String, pkg: String = packageName): HideProcessInfo {
|
||||||
|
return HideProcessInfo(name, pkg, hidden.any { it.process == name })
|
||||||
|
}
|
||||||
|
|
||||||
|
var haveAppZygote = false
|
||||||
|
fun Array<out ComponentInfo>.processes() = map { createProcess(it.processName) }
|
||||||
|
fun Array<ServiceInfo>.processes() = map {
|
||||||
|
if (it.isIsolated) {
|
||||||
|
if (it.useAppZygote) {
|
||||||
|
haveAppZygote = true
|
||||||
|
// Using app zygote, don't need to track the process
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
createProcess("${it.processName}:${it.name}", ISOLATED_MAGIC)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
createProcess(it.processName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return with(packageInfo) {
|
||||||
|
activities?.processes().orEmpty() +
|
||||||
|
services?.processes().orEmpty() +
|
||||||
|
receivers?.processes().orEmpty() +
|
||||||
|
providers?.processes().orEmpty() +
|
||||||
|
listOf(if (haveAppZygote) createProcess("${processName}_zygote") else null)
|
||||||
|
}.filterNotNull().distinctBy { it.name }.sortedBy { it.name }
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val comparator = compareBy<HideAppInfo>(
|
||||||
|
{ it.label.toLowerCase(currentLocale) },
|
||||||
|
{ it.packageName }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class HideProcessInfo(
|
||||||
|
val name: String,
|
||||||
|
val packageName: String,
|
||||||
|
var isHidden: Boolean
|
||||||
|
)
|
@ -12,14 +12,13 @@ import com.topjohnwu.magisk.utils.set
|
|||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class HideItem(
|
class HideRvItem(
|
||||||
app: HideAppTarget
|
val info: HideAppInfo
|
||||||
) : ObservableItem<HideItem>(), Comparable<HideItem> {
|
) : ObservableItem<HideRvItem>(), Comparable<HideRvItem> {
|
||||||
|
|
||||||
override val layoutRes = R.layout.item_hide_md2
|
override val layoutRes get() = R.layout.item_hide_md2
|
||||||
|
|
||||||
val info = app.info
|
val processes = info.processes.map { HideProcessRvItem(it) }
|
||||||
val processes = app.processes.map { HideProcessItem(it) }
|
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
var isExpanded = false
|
var isExpanded = false
|
||||||
@ -73,10 +72,10 @@ class HideItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other: HideItem) = comparator.compare(this, other)
|
override fun compareTo(other: HideRvItem) = comparator.compare(this, other)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val comparator = compareBy<HideItem>(
|
private val comparator = compareBy<HideRvItem>(
|
||||||
{ it.itemsChecked == 0 },
|
{ it.itemsChecked == 0 },
|
||||||
{ it.info }
|
{ it.info }
|
||||||
)
|
)
|
||||||
@ -84,16 +83,17 @@ class HideItem(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class HideProcessItem(
|
class HideProcessRvItem(
|
||||||
val process: HideProcessInfo
|
val process: HideProcessInfo
|
||||||
) : ObservableItem<HideProcessItem>() {
|
) : ObservableItem<HideProcessRvItem>() {
|
||||||
|
|
||||||
override val layoutRes = R.layout.item_hide_process_md2
|
override val layoutRes get() = R.layout.item_hide_process_md2
|
||||||
|
|
||||||
@get:Bindable
|
@get:Bindable
|
||||||
var isHidden = process.isHidden
|
var isHidden
|
||||||
set(value) = set(value, field, { field = it }, BR.hidden) {
|
get() = process.isHidden
|
||||||
val arg = if (isHidden) "add" else "rm"
|
set(value) = set(value, process.isHidden, { process.isHidden = it }, BR.hidden) {
|
||||||
|
val arg = if (it) "add" else "rm"
|
||||||
val (name, pkg) = process
|
val (name, pkg) = process
|
||||||
Shell.su("magiskhide --$arg $pkg $name").submit()
|
Shell.su("magiskhide --$arg $pkg $name").submit()
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ class HideProcessItem(
|
|||||||
isHidden = !isHidden
|
isHidden = !isHidden
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contentSameAs(other: HideProcessItem) = process == other.process
|
override fun contentSameAs(other: HideProcessRvItem) = process == other.process
|
||||||
override fun itemSameAs(other: HideProcessItem) = process.name == other.process.name
|
override fun itemSameAs(other: HideProcessRvItem) = process.name == other.process.name
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package com.topjohnwu.magisk.ui.hide
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.pm.ApplicationInfo
|
import android.content.pm.ApplicationInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@ -14,7 +15,6 @@ import com.topjohnwu.magisk.arch.itemBindingOf
|
|||||||
import com.topjohnwu.magisk.core.Config
|
import com.topjohnwu.magisk.core.Config
|
||||||
import com.topjohnwu.magisk.ktx.get
|
import com.topjohnwu.magisk.ktx.get
|
||||||
import com.topjohnwu.magisk.ktx.packageName
|
import com.topjohnwu.magisk.ktx.packageName
|
||||||
import com.topjohnwu.magisk.ktx.processes
|
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.utils.set
|
import com.topjohnwu.magisk.utils.set
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
@ -45,11 +45,11 @@ class HideViewModel : BaseViewModel(), Queryable {
|
|||||||
submitQuery()
|
submitQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
val items = filterableListOf<HideItem>()
|
val items = filterableListOf<HideRvItem>()
|
||||||
val itemBinding = itemBindingOf<HideItem> {
|
val itemBinding = itemBindingOf<HideRvItem> {
|
||||||
it.bindExtra(BR.viewModel, this)
|
it.bindExtra(BR.viewModel, this)
|
||||||
}
|
}
|
||||||
val itemInternalBinding = itemBindingOf<HideProcessItem> {
|
val itemInternalBinding = itemBindingOf<HideProcessRvItem> {
|
||||||
it.bindExtra(BR.viewModel, this)
|
it.bindExtra(BR.viewModel, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,14 +62,13 @@ class HideViewModel : BaseViewModel(), Queryable {
|
|||||||
state = State.LOADING
|
state = State.LOADING
|
||||||
val (apps, diff) = withContext(Dispatchers.Default) {
|
val (apps, diff) = withContext(Dispatchers.Default) {
|
||||||
val pm = get<PackageManager>()
|
val pm = get<PackageManager>()
|
||||||
val hides = Shell.su("magiskhide --ls").exec().out.map { HideTarget(it) }
|
val hideList = Shell.su("magiskhide --ls").exec().out.map { CmdlineHiddenItem(it) }
|
||||||
val apps = pm.getInstalledApplications(PackageManager.MATCH_UNINSTALLED_PACKAGES)
|
val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES)
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { it.enabled && !blacklist.contains(it.packageName) }
|
.filter { it.enabled && !blacklist.contains(it.packageName) }
|
||||||
.map { HideAppInfo(it, pm) }
|
.map { HideAppInfo(it, pm, hideList) }
|
||||||
.map { createTarget(it, hides) }
|
|
||||||
.filter { it.processes.isNotEmpty() }
|
.filter { it.processes.isNotEmpty() }
|
||||||
.map { HideItem(it) }
|
.map { HideRvItem(it) }
|
||||||
.toList()
|
.toList()
|
||||||
.sorted()
|
.sorted()
|
||||||
apps to items.calculateDiff(apps)
|
apps to items.calculateDiff(apps)
|
||||||
@ -80,18 +79,6 @@ class HideViewModel : BaseViewModel(), Queryable {
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
|
|
||||||
private fun createTarget(info: HideAppInfo, hideList: List<HideTarget>): HideAppTarget {
|
|
||||||
val pkg = info.packageName
|
|
||||||
val hidden = hideList.filter { it.packageName == pkg }
|
|
||||||
val processNames = info.processes.distinct()
|
|
||||||
val processes = processNames.map { name ->
|
|
||||||
HideProcessInfo(name, pkg, hidden.any { name == it.process })
|
|
||||||
}
|
|
||||||
return HideAppTarget(info, processes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---
|
|
||||||
|
|
||||||
override fun query() {
|
override fun query() {
|
||||||
items.filter {
|
items.filter {
|
||||||
fun showHidden() = it.itemsChecked != 0
|
fun showHidden() = it.itemsChecked != 0
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="item"
|
name="item"
|
||||||
type="com.topjohnwu.magisk.ui.hide.HideItem" />
|
type="com.topjohnwu.magisk.ui.hide.HideRvItem" />
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="viewModel"
|
name="viewModel"
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="item"
|
name="item"
|
||||||
type="com.topjohnwu.magisk.ui.hide.HideProcessItem" />
|
type="com.topjohnwu.magisk.ui.hide.HideProcessRvItem" />
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="viewModel"
|
name="viewModel"
|
||||||
|
@ -68,7 +68,7 @@ static void load_overlay_rc(const char *overlay) {
|
|||||||
// Do not allow overwrite init.rc
|
// Do not allow overwrite init.rc
|
||||||
unlinkat(dfd, "init.rc", 0);
|
unlinkat(dfd, "init.rc", 0);
|
||||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||||
if (strend(entry->d_name, ".rc") == 0) {
|
if (str_ends(entry->d_name, ".rc")) {
|
||||||
LOGD("Found rc script [%s]\n", entry->d_name);
|
LOGD("Found rc script [%s]\n", entry->d_name);
|
||||||
int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);
|
||||||
rc_list.push_back(fd_full_read(rc));
|
rc_list.push_back(fd_full_read(rc));
|
||||||
|
@ -48,23 +48,27 @@ void set_hide_state(bool state) {
|
|||||||
hide_state = state;
|
hide_state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <bool str_op(string_view, string_view)>
|
||||||
static bool proc_name_match(int pid, const char *name) {
|
static bool proc_name_match(int pid, const char *name) {
|
||||||
char buf[4019];
|
char buf[4019];
|
||||||
sprintf(buf, "/proc/%d/cmdline", pid);
|
sprintf(buf, "/proc/%d/cmdline", pid);
|
||||||
if (FILE *f = fopen(buf, "re")) {
|
if (auto fp = open_file(buf, "re")) {
|
||||||
fgets(buf, sizeof(buf), f);
|
fgets(buf, sizeof(buf), fp.get());
|
||||||
fclose(f);
|
if (str_op(buf, name)) {
|
||||||
if (strcmp(buf, name) == 0)
|
LOGD("hide_utils: kill PID=[%d] (%s)\n", pid, buf);
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void kill_process(const char *name, bool multi = false) {
|
static inline bool str_eql(string_view s, string_view ss) { return s == ss; }
|
||||||
|
|
||||||
|
static void kill_process(const char *name, bool multi = false,
|
||||||
|
bool (*filter)(int, const char *) = proc_name_match<&str_eql>) {
|
||||||
crawl_procfs([=](int pid) -> bool {
|
crawl_procfs([=](int pid) -> bool {
|
||||||
if (proc_name_match(pid, name)) {
|
if (filter(pid, name)) {
|
||||||
if (kill(pid, SIGTERM) == 0)
|
kill(pid, SIGTERM);
|
||||||
LOGD("hide_utils: killed PID=[%d] (%s)\n", pid, name);
|
|
||||||
return multi;
|
return multi;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -72,6 +76,8 @@ static void kill_process(const char *name, bool multi = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool validate(const char *s) {
|
static bool validate(const char *s) {
|
||||||
|
if (strcmp(s, ISOLATED_MAGIC) == 0)
|
||||||
|
return true;
|
||||||
bool dot = false;
|
bool dot = false;
|
||||||
for (char c; (c = *s); ++s) {
|
for (char c; (c = *s); ++s) {
|
||||||
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
|
||||||
@ -87,7 +93,18 @@ static bool validate(const char *s) {
|
|||||||
return dot;
|
return dot;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int add_list(const char *pkg, const char *proc = "") {
|
static void add_hide_set(const char *pkg, const char *proc) {
|
||||||
|
LOGI("hide_list add: [%s/%s]\n", pkg, proc);
|
||||||
|
hide_set.emplace(pkg, proc);
|
||||||
|
if (strcmp(pkg, ISOLATED_MAGIC) == 0) {
|
||||||
|
// Kill all matching isolated processes
|
||||||
|
kill_process(proc, true, proc_name_match<&str_starts>);
|
||||||
|
} else {
|
||||||
|
kill_process(proc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int add_list(const char *pkg, const char *proc) {
|
||||||
if (proc[0] == '\0')
|
if (proc[0] == '\0')
|
||||||
proc = pkg;
|
proc = pkg;
|
||||||
|
|
||||||
@ -105,15 +122,12 @@ static int add_list(const char *pkg, const char *proc = "") {
|
|||||||
char *err = db_exec(sql);
|
char *err = db_exec(sql);
|
||||||
db_err_cmd(err, return DAEMON_ERROR);
|
db_err_cmd(err, return DAEMON_ERROR);
|
||||||
|
|
||||||
LOGI("hide_list add: [%s/%s]\n", pkg, proc);
|
|
||||||
|
|
||||||
// Critical region
|
|
||||||
{
|
{
|
||||||
|
// Critical region
|
||||||
mutex_guard lock(monitor_lock);
|
mutex_guard lock(monitor_lock);
|
||||||
hide_set.emplace(pkg, proc);
|
add_hide_set(pkg, proc);
|
||||||
}
|
}
|
||||||
|
|
||||||
kill_process(proc);
|
|
||||||
return DAEMON_SUCCESS;
|
return DAEMON_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,27 +137,28 @@ int add_list(int client) {
|
|||||||
int ret = add_list(pkg, proc);
|
int ret = add_list(pkg, proc);
|
||||||
free(pkg);
|
free(pkg);
|
||||||
free(proc);
|
free(proc);
|
||||||
update_uid_map();
|
if (ret == DAEMON_SUCCESS)
|
||||||
|
update_uid_map();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rm_list(const char *pkg, const char *proc = "") {
|
static int rm_list(const char *pkg, const char *proc) {
|
||||||
|
bool remove = false;
|
||||||
{
|
{
|
||||||
// Critical region
|
// Critical region
|
||||||
mutex_guard lock(monitor_lock);
|
mutex_guard lock(monitor_lock);
|
||||||
bool remove = false;
|
|
||||||
for (auto it = hide_set.begin(); it != hide_set.end();) {
|
for (auto it = hide_set.begin(); it != hide_set.end();) {
|
||||||
if (it->first == pkg && (proc[0] == '\0' || it->second == proc)) {
|
if (it->first == pkg && (proc[0] == '\0' || it->second == proc)) {
|
||||||
remove = true;
|
remove = true;
|
||||||
LOGI("hide_list rm: [%s]\n", it->second.data());
|
LOGI("hide_list rm: [%s/%s]\n", it->first.data(), it->second.data());
|
||||||
it = hide_set.erase(it);
|
it = hide_set.erase(it);
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!remove)
|
|
||||||
return HIDE_ITEM_NOT_EXIST;
|
|
||||||
}
|
}
|
||||||
|
if (!remove)
|
||||||
|
return HIDE_ITEM_NOT_EXIST;
|
||||||
|
|
||||||
char sql[4096];
|
char sql[4096];
|
||||||
if (proc[0] == '\0')
|
if (proc[0] == '\0')
|
||||||
@ -167,10 +182,11 @@ int rm_list(int client) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void init_list(const char *pkg, const char *proc) {
|
static bool str_ends_safe(string_view s, string_view ss) {
|
||||||
LOGI("hide_list init: [%s/%s]\n", pkg, proc);
|
// Never kill webview zygote
|
||||||
hide_set.emplace(pkg, proc);
|
if (s == "webview_zygote")
|
||||||
kill_process(proc);
|
return false;
|
||||||
|
return str_ends(s, ss);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SNET_PROC "com.google.android.gms.unstable"
|
#define SNET_PROC "com.google.android.gms.unstable"
|
||||||
@ -181,25 +197,26 @@ static bool init_list() {
|
|||||||
LOGD("hide_list: initialize\n");
|
LOGD("hide_list: initialize\n");
|
||||||
|
|
||||||
char *err = db_exec("SELECT * FROM hidelist", [](db_row &row) -> bool {
|
char *err = db_exec("SELECT * FROM hidelist", [](db_row &row) -> bool {
|
||||||
init_list(row["package_name"].data(), row["process"].data());
|
add_hide_set(row["package_name"].data(), row["process"].data());
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
db_err_cmd(err, return false);
|
db_err_cmd(err, return false);
|
||||||
|
|
||||||
// If Android Q+, also kill blastula pool
|
// If Android Q+, also kill blastula pool and all app zygotes
|
||||||
if (SDK_INT >= 29) {
|
if (SDK_INT >= 29) {
|
||||||
kill_process("usap32", true);
|
kill_process("usap32", true);
|
||||||
kill_process("usap64", true);
|
kill_process("usap64", true);
|
||||||
|
kill_process("_zygote", true, proc_name_match<&str_ends_safe>);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add SafetyNet by default
|
// Add SafetyNet by default
|
||||||
init_list(GMS_PKG, SNET_PROC);
|
add_hide_set(GMS_PKG, SNET_PROC);
|
||||||
init_list(MICROG_PKG, SNET_PROC);
|
add_hide_set(MICROG_PKG, SNET_PROC);
|
||||||
|
|
||||||
// We also need to hide the default GMS process if MAGISKTMP != /sbin
|
// We also need to hide the default GMS process if MAGISKTMP != /sbin
|
||||||
// The snet process communicates with the main process and get additional info
|
// The snet process communicates with the main process and get additional info
|
||||||
if (MAGISKTMP != "/sbin")
|
if (MAGISKTMP != "/sbin")
|
||||||
init_list(GMS_PKG, GMS_PKG);
|
add_hide_set(GMS_PKG, GMS_PKG);
|
||||||
|
|
||||||
update_uid_map();
|
update_uid_map();
|
||||||
return true;
|
return true;
|
||||||
@ -251,7 +268,7 @@ int launch_magiskhide() {
|
|||||||
hide_late_sensitive_props();
|
hide_late_sensitive_props();
|
||||||
|
|
||||||
// Start monitoring
|
// Start monitoring
|
||||||
void *(*start)(void*) = [](void*) -> void* { proc_monitor(); return nullptr; };
|
void *(*start)(void*) = [](void*) -> void* { proc_monitor(); };
|
||||||
if (xpthread_create(&proc_monitor_thread, nullptr, start, nullptr))
|
if (xpthread_create(&proc_monitor_thread, nullptr, start, nullptr))
|
||||||
return DAEMON_ERROR;
|
return DAEMON_ERROR;
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include <daemon.hpp>
|
#include <daemon.hpp>
|
||||||
|
|
||||||
#define SIGTERMTHRD SIGUSR1
|
#define SIGTERMTHRD SIGUSR1
|
||||||
|
#define ISOLATED_MAGIC "isolated"
|
||||||
|
|
||||||
// CLI entries
|
// CLI entries
|
||||||
int launch_magiskhide();
|
int launch_magiskhide();
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
#include <stdlib.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
@ -61,13 +58,13 @@ static int parse_ppid(int pid) {
|
|||||||
int ppid;
|
int ppid;
|
||||||
|
|
||||||
sprintf(path, "/proc/%d/stat", pid);
|
sprintf(path, "/proc/%d/stat", pid);
|
||||||
FILE *stat = fopen(path, "re");
|
|
||||||
if (stat == nullptr)
|
auto stat = open_file(path, "re");
|
||||||
|
if (!stat)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
// PID COMM STATE PPID .....
|
// PID COMM STATE PPID .....
|
||||||
fscanf(stat, "%*d %*s %*c %d", &ppid);
|
fscanf(stat.get(), "%*d %*s %*c %d", &ppid);
|
||||||
fclose(stat);
|
|
||||||
|
|
||||||
return ppid;
|
return ppid;
|
||||||
}
|
}
|
||||||
@ -89,20 +86,27 @@ void update_uid_map() {
|
|||||||
string data_path(APP_DATA_DIR);
|
string data_path(APP_DATA_DIR);
|
||||||
size_t len = data_path.length();
|
size_t len = data_path.length();
|
||||||
auto dir = open_dir(APP_DATA_DIR);
|
auto dir = open_dir(APP_DATA_DIR);
|
||||||
|
bool firstIteration = true;
|
||||||
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
for (dirent *entry; (entry = xreaddir(dir.get()));) {
|
||||||
data_path.resize(len);
|
data_path.resize(len);
|
||||||
data_path += '/';
|
data_path += '/';
|
||||||
data_path += entry->d_name;
|
data_path += entry->d_name; // multiuser user id
|
||||||
data_path += '/';
|
data_path += '/';
|
||||||
size_t user_len = data_path.length();
|
size_t user_len = data_path.length();
|
||||||
struct stat st;
|
struct stat st;
|
||||||
for (auto &hide : hide_set) {
|
for (auto &hide : hide_set) {
|
||||||
|
if (hide.first == ISOLATED_MAGIC) {
|
||||||
|
if (!firstIteration) continue;
|
||||||
|
// Setup isolated processes
|
||||||
|
uid_proc_map[-1].emplace_back(hide.second);
|
||||||
|
}
|
||||||
data_path.resize(user_len);
|
data_path.resize(user_len);
|
||||||
data_path += hide.first;
|
data_path += hide.first;
|
||||||
if (stat(data_path.data(), &st))
|
if (stat(data_path.data(), &st))
|
||||||
continue;
|
continue;
|
||||||
uid_proc_map[st.st_uid].emplace_back(hide.second);
|
uid_proc_map[st.st_uid].emplace_back(hide.second);
|
||||||
}
|
}
|
||||||
|
firstIteration = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +228,7 @@ static bool check_pid(int pid) {
|
|||||||
|
|
||||||
sprintf(path, "/proc/%d", pid);
|
sprintf(path, "/proc/%d", pid);
|
||||||
if (stat(path, &st)) {
|
if (stat(path, &st)) {
|
||||||
// Process killed unexpectedly, ignore
|
// Process died unexpectedly, ignore
|
||||||
detach_pid(pid);
|
detach_pid(pid);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -237,7 +241,7 @@ static bool check_pid(int pid) {
|
|||||||
if (auto f = open_file(path, "re")) {
|
if (auto f = open_file(path, "re")) {
|
||||||
fgets(cmdline, sizeof(cmdline), f.get());
|
fgets(cmdline, sizeof(cmdline), f.get());
|
||||||
} else {
|
} else {
|
||||||
// Process killed unexpectedly, ignore
|
// Process died unexpectedly, ignore
|
||||||
detach_pid(pid);
|
detach_pid(pid);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -247,36 +251,59 @@ static bool check_pid(int pid) {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
int uid = st.st_uid;
|
int uid = st.st_uid;
|
||||||
auto it = uid_proc_map.find(uid);
|
auto it = uid_proc_map.end();
|
||||||
if (it != uid_proc_map.end()) {
|
|
||||||
for (auto &s : it->second) {
|
|
||||||
if (s == cmdline) {
|
|
||||||
// Double check whether ns is separated
|
|
||||||
read_ns(pid, &st);
|
|
||||||
bool mnt_ns = true;
|
|
||||||
for (auto &zit : zygote_map) {
|
|
||||||
if (zit.second.st_ino == st.st_ino &&
|
|
||||||
zit.second.st_dev == st.st_dev) {
|
|
||||||
mnt_ns = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// For some reason ns is not separated, abort
|
|
||||||
if (!mnt_ns)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Finally this is our target!
|
if (uid % 100000 > 90000) {
|
||||||
// Detach from ptrace but should still remain stopped.
|
// Isolated process
|
||||||
// The hide daemon will resume the process.
|
it = uid_proc_map.find(-1);
|
||||||
PTRACE_LOG("target found\n");
|
if (it == uid_proc_map.end())
|
||||||
LOGI("proc_monitor: [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid);
|
goto not_target;
|
||||||
detach_pid(pid, SIGSTOP);
|
for (auto &s : it->second) {
|
||||||
hide_daemon(pid);
|
if (str_starts(cmdline, s)) {
|
||||||
return true;
|
LOGI("proc_monitor: (isolated) [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid);
|
||||||
|
goto inject_and_hide;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PTRACE_LOG("[%s] not our target\n", cmdline);
|
|
||||||
|
it = uid_proc_map.find(uid);
|
||||||
|
if (it == uid_proc_map.end())
|
||||||
|
goto not_target;
|
||||||
|
for (auto &s : it->second) {
|
||||||
|
if (s != cmdline)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (str_ends(s, "_zygote")) {
|
||||||
|
LOGI("proc_monitor: (app zygote) [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid);
|
||||||
|
goto inject_and_hide;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double check whether ns is separated
|
||||||
|
read_ns(pid, &st);
|
||||||
|
for (auto &zit : zygote_map) {
|
||||||
|
if (zit.second.st_ino == st.st_ino &&
|
||||||
|
zit.second.st_dev == st.st_dev) {
|
||||||
|
// For some reason ns is not separated, abort
|
||||||
|
goto not_target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally this is our target!
|
||||||
|
// Detach from ptrace but should still remain stopped.
|
||||||
|
// The hide daemon will resume the process.
|
||||||
|
LOGI("proc_monitor: [%s] PID=[%d] UID=[%d]\n", cmdline, pid, uid);
|
||||||
|
detach_pid(pid, SIGSTOP);
|
||||||
|
hide_daemon(pid);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
not_target:
|
||||||
|
PTRACE_LOG("[%s] is not our target\n", cmdline);
|
||||||
|
detach_pid(pid);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
inject_and_hide:
|
||||||
|
// TODO: handle isolated processes and app zygotes
|
||||||
detach_pid(pid);
|
detach_pid(pid);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -324,7 +351,7 @@ static void new_zygote(int pid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define WEVENT(s) (((s) & 0xffff0000) >> 16)
|
#define WEVENT(s) (((s) & 0xffff0000) >> 16)
|
||||||
#define DETACH_AND_CONT { detach = true; continue; }
|
#define DETACH_AND_CONT { detach_pid(pid); continue; }
|
||||||
|
|
||||||
void proc_monitor() {
|
void proc_monitor() {
|
||||||
// Unblock some signals
|
// Unblock some signals
|
||||||
@ -354,9 +381,7 @@ void proc_monitor() {
|
|||||||
setitimer(ITIMER_REAL, &interval, nullptr);
|
setitimer(ITIMER_REAL, &interval, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
int status;
|
for (int status;;) {
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
const int pid = waitpid(-1, &status, __WALL | __WNOTHREAD);
|
const int pid = waitpid(-1, &status, __WALL | __WNOTHREAD);
|
||||||
if (pid < 0) {
|
if (pid < 0) {
|
||||||
if (errno == ECHILD) {
|
if (errno == ECHILD) {
|
||||||
@ -370,38 +395,35 @@ void proc_monitor() {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
bool detach = false;
|
|
||||||
run_finally f([&] {
|
|
||||||
if (detach)
|
|
||||||
// Non of our business now
|
|
||||||
detach_pid(pid);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!WIFSTOPPED(status) /* Ignore if not ptrace-stop */)
|
if (!WIFSTOPPED(status) /* Ignore if not ptrace-stop */)
|
||||||
DETACH_AND_CONT;
|
DETACH_AND_CONT;
|
||||||
|
|
||||||
if (WSTOPSIG(status) == SIGTRAP && WEVENT(status)) {
|
int event = WEVENT(status);
|
||||||
|
int signal = WSTOPSIG(status);
|
||||||
|
|
||||||
|
if (signal == SIGTRAP && event) {
|
||||||
unsigned long msg;
|
unsigned long msg;
|
||||||
xptrace(PTRACE_GETEVENTMSG, pid, nullptr, &msg);
|
xptrace(PTRACE_GETEVENTMSG, pid, nullptr, &msg);
|
||||||
if (zygote_map.count(pid)) {
|
if (zygote_map.count(pid)) {
|
||||||
// Zygote event
|
// Zygote event
|
||||||
switch (WEVENT(status)) {
|
switch (event) {
|
||||||
case PTRACE_EVENT_FORK:
|
case PTRACE_EVENT_FORK:
|
||||||
case PTRACE_EVENT_VFORK:
|
case PTRACE_EVENT_VFORK:
|
||||||
PTRACE_LOG("zygote forked: [%d]\n", msg);
|
PTRACE_LOG("zygote forked: [%lu]\n", msg);
|
||||||
attaches[msg] = true;
|
attaches[msg] = true;
|
||||||
break;
|
break;
|
||||||
case PTRACE_EVENT_EXIT:
|
case PTRACE_EVENT_EXIT:
|
||||||
PTRACE_LOG("zygote exited with status: [%d]\n", msg);
|
PTRACE_LOG("zygote exited with status: [%lu]\n", msg);
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
default:
|
default:
|
||||||
zygote_map.erase(pid);
|
zygote_map.erase(pid);
|
||||||
DETACH_AND_CONT;
|
DETACH_AND_CONT;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (WEVENT(status)) {
|
switch (event) {
|
||||||
case PTRACE_EVENT_CLONE:
|
case PTRACE_EVENT_CLONE:
|
||||||
PTRACE_LOG("create new threads: [%d]\n", msg);
|
PTRACE_LOG("create new threads: [%lu]\n", msg);
|
||||||
if (attaches[pid] && check_pid(pid))
|
if (attaches[pid] && check_pid(pid))
|
||||||
continue;
|
continue;
|
||||||
break;
|
break;
|
||||||
@ -414,7 +436,7 @@ void proc_monitor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
xptrace(PTRACE_CONT, pid);
|
xptrace(PTRACE_CONT, pid);
|
||||||
} else if (WSTOPSIG(status) == SIGSTOP) {
|
} else if (signal == SIGSTOP) {
|
||||||
if (!attaches[pid]) {
|
if (!attaches[pid]) {
|
||||||
// Double check if this is actually a process
|
// Double check if this is actually a process
|
||||||
attaches[pid] = is_process(pid);
|
attaches[pid] = is_process(pid);
|
||||||
@ -432,8 +454,8 @@ void proc_monitor() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Not caused by us, resend signal
|
// Not caused by us, resend signal
|
||||||
xptrace(PTRACE_CONT, pid, nullptr, WSTOPSIG(status));
|
xptrace(PTRACE_CONT, pid, nullptr, signal);
|
||||||
PTRACE_LOG("signal [%d]\n", WSTOPSIG(status));
|
PTRACE_LOG("signal [%d]\n", signal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,12 +58,6 @@ int gen_rand_str(char *buf, int len, bool varlen) {
|
|||||||
return len - 1;
|
return len - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int strend(const char *s1, const char *s2) {
|
|
||||||
size_t l1 = strlen(s1);
|
|
||||||
size_t l2 = strlen(s2);
|
|
||||||
return strcmp(s1 + l1 - l2, s2);
|
|
||||||
}
|
|
||||||
|
|
||||||
int exec_command(exec_t &exec) {
|
int exec_command(exec_t &exec) {
|
||||||
int pipefd[] = {-1, -1};
|
int pipefd[] = {-1, -1};
|
||||||
int outfd = -1;
|
int outfd = -1;
|
||||||
@ -151,12 +145,6 @@ void set_nice_name(const char *name) {
|
|||||||
prctl(PR_SET_NAME, name);
|
prctl(PR_SET_NAME, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ends_with(const std::string_view &s1, const std::string_view &s2) {
|
|
||||||
unsigned l1 = s1.length();
|
|
||||||
unsigned l2 = s2.length();
|
|
||||||
return l1 < l2 ? false : s1.compare(l1 - l2, l2, s2) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Bionic's atoi runs through strtol().
|
* Bionic's atoi runs through strtol().
|
||||||
* Use our own implementation for faster conversion.
|
* Use our own implementation for faster conversion.
|
||||||
|
@ -8,9 +8,6 @@
|
|||||||
#define UID_ROOT 0
|
#define UID_ROOT 0
|
||||||
#define UID_SHELL 2000
|
#define UID_SHELL 2000
|
||||||
|
|
||||||
#define str_contains(s, ss) ((ss) != nullptr && (s).find(ss) != std::string::npos)
|
|
||||||
#define str_starts(s, ss) ((ss) != nullptr && (s).compare(0, strlen(ss), ss) == 0)
|
|
||||||
|
|
||||||
class mutex_guard {
|
class mutex_guard {
|
||||||
public:
|
public:
|
||||||
explicit mutex_guard(pthread_mutex_t &m): mutex(&m) {
|
explicit mutex_guard(pthread_mutex_t &m): mutex(&m) {
|
||||||
@ -65,10 +62,18 @@ using thread_entry = void *(*)(void *);
|
|||||||
int new_daemon_thread(thread_entry entry, void *arg = nullptr, const pthread_attr_t *attr = nullptr);
|
int new_daemon_thread(thread_entry entry, void *arg = nullptr, const pthread_attr_t *attr = nullptr);
|
||||||
int new_daemon_thread(std::function<void()> &&entry);
|
int new_daemon_thread(std::function<void()> &&entry);
|
||||||
|
|
||||||
bool ends_with(const std::string_view &s1, const std::string_view &s2);
|
static inline bool str_contains(std::string_view s, std::string_view ss) {
|
||||||
|
return s.find(ss) != std::string::npos;
|
||||||
|
}
|
||||||
|
static inline bool str_starts(std::string_view s, std::string_view ss) {
|
||||||
|
return s.rfind(ss, 0) == 0;
|
||||||
|
}
|
||||||
|
static inline bool str_ends(std::string_view s, std::string_view ss) {
|
||||||
|
return s.size() >= ss.size() && s.compare(s.size() - ss.size(), std::string::npos, ss) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
int fork_dont_care();
|
int fork_dont_care();
|
||||||
int fork_no_orphan();
|
int fork_no_orphan();
|
||||||
int strend(const char *s1, const char *s2);
|
|
||||||
void init_argv0(int argc, char **argv);
|
void init_argv0(int argc, char **argv);
|
||||||
void set_nice_name(const char *name);
|
void set_nice_name(const char *name);
|
||||||
uint32_t binary_gcd(uint32_t u, uint32_t v);
|
uint32_t binary_gcd(uint32_t u, uint32_t v);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user