Wallpaper (Android): adds support

This commit is contained in:
李通洲
2026-09-24 23:42:12 +08:00
parent bba45429ae
commit 0e04ebdf63
10 changed files with 323 additions and 89 deletions
+19 -10
View File
@@ -47,11 +47,12 @@ void ffBinderClose(FFBinder* binder) {
binder->sharedSize = 0;
}
const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, const FFBinderParcel* parcel, FFBinderReply* reply) {
const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, const FFBinderParcel* parcel, FFBinderReply* reply) {
reply->size = 0;
reply->code = 0;
reply->flags = 0;
reply->handleCount = 0;
reply->fdCount = 0;
if (parcel->truncated) {
return "Binder parcel does not fit the caller buffer";
@@ -62,7 +63,7 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, c
.target = { .handle = handle },
.cookie = 0,
.code = code,
.flags = 0, // a synchronous call; TF_ONE_WAY is the only flag the kernel acts on here
.flags = flags,
.sender_pid = 0,
.sender_euid = 0,
.data_size = parcel->size,
@@ -146,8 +147,10 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, c
}
}
// Walk the offset table for the flat objects the service sent us and keep the
// strong handles: those are the ones a later transaction can address.
// Walk the offset table for the flat objects the service sent us. A reply may carry
// both kinds at once and they are not interchangeable: a handle is a reference that
// has to be acquired (see below) to outlive the reply buffer, while a file
// descriptor was installed in our own fd table by the kernel and is simply ours.
for (size_t offset = 0; offset + sizeof(binder_size_t) <= offsetsSize; offset += sizeof(binder_size_t)) {
binder_size_t objectOffset = 0;
memcpy(&objectOffset, offsets + offset, sizeof(objectOffset));
@@ -156,11 +159,17 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, c
}
struct flat_binder_object object;
memcpy(&object, payload + objectOffset, sizeof(object));
if (object.hdr.type != BINDER_TYPE_HANDLE) {
continue;
}
if (reply->handleCount < FF_BINDER_MAX_HANDLES) {
reply->handles[reply->handleCount++] = object.handle;
if (object.hdr.type == BINDER_TYPE_HANDLE) {
if (reply->handleCount < FF_BINDER_MAX_HANDLES) {
reply->handles[reply->handleCount++] = object.handle;
}
} else if (object.hdr.type == BINDER_TYPE_FD) {
if (reply->fdCount < FF_BINDER_MAX_FDS) {
reply->fds[reply->fdCount++] = (int32_t) object.handle;
} else {
// Nothing is going to hold this one and nothing is going to close it.
close((int) object.handle);
}
}
}
@@ -224,7 +233,7 @@ const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t t
uint8_t replyBuffer[256];
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
const char* error = ffBinderTransact(binder, FF_BINDER_SERVICE_MANAGER_HANDLE, transactionCode, &parcel, &reply);
const char* error = ffBinderTransact(binder, FF_BINDER_SERVICE_MANAGER_HANDLE, transactionCode, 0, &parcel, &reply);
if (error != nullptr) {
return error;
}
+20 -4
View File
@@ -51,6 +51,12 @@
// the surplus handles are left unacquired and will fail on first use.
#define FF_BINDER_MAX_HANDLES 4
// How many file descriptors a reply may hand over. A descriptor is not a handle: the kernel installs
// it in the caller's own fd table as it delivers the reply, so it is owned rather than borrowed and
// the caller closes it. A reply carrying more than this still succeeds, and the surplus is closed
// again by ffBinderTransact() because nothing would be left holding it.
#define FF_BINDER_MAX_FDS 4
// The three int32 values Parcel::writeInterfaceToken() puts in front of the descriptor, and that
// Parcel::enforceInterface() checks the last of. These come from libbinder rather than from the
// kernel, which is why they are spelled out here.
@@ -159,10 +165,12 @@ typedef struct FFBinderReply {
uint32_t flags; // TF_* of the reply
uint32_t handleCount;
uint32_t handles[FF_BINDER_MAX_HANDLES];
uint32_t fdCount;
int32_t fds[FF_BINDER_MAX_FDS]; // owned by the caller, which closes them
} FFBinderReply;
[[nodiscard]] static inline FFBinderReply ffBinderReplyCreate(uint8_t* data, size_t capacity) {
return (FFBinderReply) { .data = data, .capacity = capacity, .size = 0, .code = 0, .flags = 0, .handleCount = 0, .handles = {} };
return (FFBinderReply) { .data = data, .capacity = capacity, .size = 0, .code = 0, .flags = 0, .handleCount = 0, .handles = {}, .fdCount = 0, .fds = {} };
}
// A reply carrying TF_STATUS_CODE is not an AIDL parcel at all: the first four bytes are the raw
@@ -196,13 +204,21 @@ typedef struct FFBinderReply {
return value;
}
// Synchronous transaction. Returns nullptr on success, a static message otherwise. Handles the
// reply carries are acquired and added to `reply->handles` before the reply buffer is freed.
// Synchronous transaction. Returns nullptr on success, a static message otherwise.
//
// `flags` carries the TF_* values for the request, and one of them is not cosmetic: a reply that
// carries a file descriptor is only delivered at all when the request asked with TF_ACCEPT_FDS. A
// request that did not gets BR_FAILED_REPLY instead, which reads like a service that is not running
// rather than like a missing flag. TF_ONE_WAY is the other one the kernel acts on, and it is what
// makes a call fire-and-forget -- there is then no reply to fill `reply` from.
//
// Handles the reply carries are acquired and added to `reply->handles` before the reply buffer is
// freed; descriptors are added to `reply->fds`, which the caller owns and closes.
//
// `handle` 0 is the service manager; every other handle comes from ffBinderLookupService(). A
// service that never replies blocks this call forever -- none of the services fastfetch reads does,
// but there is no timeout here to fall back on.
[[gnu::nonnull(1, 4, 5), nodiscard]] const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, const FFBinderParcel* parcel, FFBinderReply* reply);
[[gnu::nonnull(1, 5, 6), nodiscard]] const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, const FFBinderParcel* parcel, FFBinderReply* reply);
// ---------------------------------------------------------------------------------------------
// Service manager
+65
View File
@@ -0,0 +1,65 @@
#include "common/android/package.h"
#include "common/android/api.h"
#include "common/debug.h"
#include "common/strutil.h"
#include <string.h>
#include <unistd.h>
// There is no way for a process to ask for its own package name: it is not in /proc/self/status, and
// an app cannot list /data/data. The executable path carries it instead -- an app's binaries live
// under /data/data/<package>/ or /data/user/<user>/<package>/, and /proc/self/exe resolves there.
// Under Termux that is /data/data/com.termux/files/usr/bin/<binary>, whose third component is the
// package.
//
// The memory cgroup carries the same information (4:memory:/apps/com.termux on the device this was
// verified on), but the unified hierarchy spells it out as /apps/uid_10478/pid_26943 with no name
// left in it, so the executable is the portable half of the two.
bool ffAndroidGetOwnPackage(char* buffer, size_t capacity) {
char path[4096];
const ssize_t length = readlink("/proc/self/exe", path, sizeof(path) - 1);
if (length <= 0) {
FF_DEBUG("Cannot read /proc/self/exe, so the calling package cannot be derived");
return false;
}
path[length] = '\0';
const char* rest = nullptr;
const char* const dataPrefix = "/data/data/";
const char* const userPrefix = "/data/user/";
if (ffStrStartsWith(path, dataPrefix)) {
rest = path + strlen(dataPrefix);
} else if (ffStrStartsWith(path, userPrefix)) {
// /data/user/<user>/<package>/... -- the user number has to be stepped over before the
// package name starts, and only strchr() answers where that is.
rest = strchr(path + strlen(userPrefix), '/');
if (rest != nullptr) {
rest += 1;
}
}
if (rest == nullptr) {
const uint32_t uid = instance.state.platform.uid;
FF_DEBUG("The executable is not in an app data directory (\"%s\"), uid %u", path, uid);
if (uid != FF_ANDROID_PRIVILEGED_UID_SHELL) {
return false;
}
static const char shellPackage[] = FF_ANDROID_SHELL_PACKAGE;
if (sizeof(shellPackage) > capacity) {
return false;
}
memcpy(buffer, shellPackage, sizeof(shellPackage));
FF_DEBUG("The calling package is \"%s\" (the shell owns it)", buffer);
return true;
}
const char* end = strchr(rest, '/');
const size_t nameLength = end != nullptr ? (size_t) (end - rest) : strlen(rest);
if (nameLength == 0 || nameLength >= capacity) {
return false;
}
memcpy(buffer, rest, nameLength);
buffer[nameLength] = '\0';
FF_DEBUG("The calling package is \"%s\"", buffer);
return true;
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <stdbool.h>
#include <stddef.h>
// The one package name the shell UID owns. android.os.Process.SHELL_UID is a single app UID that
// owns exactly one package, so a process that cannot derive a package of its own -- a static build
// pushed to /data/local/tmp is the case that showed up -- can answer with this name and have the
// system services accept it. See FF_ANDROID_PRIVILEGED_UID_SHELL in common/android/api.h.
#define FF_ANDROID_SHELL_PACKAGE "com.android.shell"
// Some of the system services take the caller's package name as an argument and check it against the
// calling UID rather than against the name being real: a name nobody owns, and a real name belonging
// to another app, are refused exactly like a null one. So it has to be the caller's own name.
//
// Whether the name is checked at all depends on the service and the release -- `getConnectionInfo`
// answered "Package com.termux does not belong to <shell uid>" on one Android 16 device while the
// same name was accepted on an Android 11 one, and `getWallpaper` refuses a null, empty or foreign
// name on that Android 16 device -- so passing the real one is what works everywhere.
//
// Writes the name into `buffer`, terminator included, and returns whether one was found. `capacity`
// is the caller's, because the parcel the name ends up in is sized from it.
[[gnu::nonnull(1), nodiscard]] bool ffAndroidGetOwnPackage(char* buffer, size_t capacity);