mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-11-12 22:40:54 +01:00
278 lines
14 KiB
C
278 lines
14 KiB
C
#pragma once
|
|
|
|
// Android exposes no public C API for the system services that carry the data fastfetch wants: the
|
|
// NDK has no BatteryManager / WifiManager, libbinder_ndk only speaks to AIDL HALs (it refuses to
|
|
// prepare a transaction for a binder that has no NDK class), and `/system/bin/service call` costs a
|
|
// fork/exec worth 15-20 ms. What is left is /dev/binder itself, which is all that `service` uses
|
|
// underneath. This header speaks that protocol directly: open() + mmap() + BINDER_WRITE_READ, no
|
|
// extra shared library, no child process, ~0.13 ms per call once a handle is cached.
|
|
//
|
|
// Three details are easy to get wrong. All three were confirmed against the device's own
|
|
// libbinder.so and with a ptrace trace of `service`, and getting any of them wrong makes the
|
|
// service answer BAD_TYPE (0x80000001) or BR_FAILED_REPLY (0x7211):
|
|
//
|
|
// * A request parcel must start with three int32 values -- strict mode policy, work source and
|
|
// 'SYST'. Parcel::markForBinder() writes nothing for kernel binder, but the receiving side's
|
|
// Parcel::enforceInterface() still compares the third word against its own header.
|
|
// * Parcel::writeString16() writes the length, the UTF-16 code units, a UTF-16 NUL terminator and
|
|
// then pads to four bytes. Skipping the terminator shifts every following field by four bytes.
|
|
// * A handle arriving in a reply is owned by that reply buffer. It has to be acquired
|
|
// (BC_ACQUIRE + BC_INCREFS) before BC_FREE_BUFFER; otherwise only a weak reference survives and
|
|
// every later transaction on it fails, because the kernel looks up a strong reference.
|
|
|
|
#include "fastfetch.h" // IWYU pragma: keep
|
|
|
|
// The structures and command words below are not defined here: bionic generates this header from
|
|
// bionic/libc/kernel/uapi/linux/android/binder.h and every NDK ships it, so it is the
|
|
// authoritative copy of the binder ABI -- struct binder_write_read, struct binder_transaction_data,
|
|
// struct flat_binder_object, BINDER_WRITE_READ / BINDER_VERSION, the BC_* and BR_* command words,
|
|
// TF_* and BINDER_TYPE_*. Using it beats transcribing it: a second copy can only be wrong, and if
|
|
// the header itself is wrong that is the NDK's problem to fix, not something fastfetch could have
|
|
// caught by keeping its own.
|
|
#include <linux/android/binder.h>
|
|
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
|
|
#define FF_BINDER_DEVICE "/dev/binder"
|
|
|
|
// The kernel allocates reply payloads out of the region handed to mmap(); 1 MiB is what libbinder
|
|
// asks for. Replies themselves are small -- `listServices` on this device is the largest at a few
|
|
// hundred bytes -- but the region also carries the per-process buffer pool.
|
|
#define FF_BINDER_SHARED_SIZE (1024 * 1024)
|
|
|
|
// A command word plus the binder_transaction_data that follows it. The payload never lands here:
|
|
// it lives in the mmap region, and BR_REPLY only hands over a pointer to it.
|
|
#define FF_BINDER_REPLY_BUFFER_SIZE 1024
|
|
|
|
// How many handles a reply may transfer to us. Replies that carry more than this still succeed, but
|
|
// 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.
|
|
#define FF_BINDER_STRICT_MODE_PENALTY_GATHER 0x80000000u // (1 << 31), Parcel.cpp
|
|
#define FF_BINDER_UNSET_WORK_SOURCE 0xffffffffu // IPCThreadState::kUnsetWorkSource
|
|
#define FF_BINDER_HEADER 0x53595354u // B_PACK_CHARS('S', 'Y', 'S', 'T')
|
|
|
|
typedef struct FFBinder {
|
|
int fd;
|
|
uint8_t* shared;
|
|
size_t sharedSize;
|
|
int32_t protocolVersion;
|
|
} FFBinder;
|
|
|
|
// Opens /dev/binder, checks the protocol version and maps the shared region. Returns nullptr on
|
|
// success, a static message otherwise; the device is left closed on failure.
|
|
[[gnu::nonnull(1), nodiscard]] const char* ffBinderOpen(FFBinder* binder);
|
|
|
|
// Usable as a cleanup attribute. Safe on a zeroed or already closed FFBinder.
|
|
[[gnu::nonnull(1)]] void ffBinderClose(FFBinder* binder);
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Parcel writing. The caller owns the buffer; anything that does not fit is dropped and recorded
|
|
// in `truncated`, which ffBinderTransact() then reports as an error.
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
typedef struct FFBinderParcel {
|
|
uint8_t* data;
|
|
size_t capacity;
|
|
size_t size;
|
|
bool truncated;
|
|
} FFBinderParcel;
|
|
|
|
[[nodiscard]] static inline FFBinderParcel ffBinderParcelCreate(uint8_t* data, size_t capacity) {
|
|
return (FFBinderParcel) { .data = data, .capacity = capacity, .size = 0, .truncated = false };
|
|
}
|
|
|
|
[[gnu::nonnull(1), nodiscard]] static inline uint8_t* ffBinderParcelReserve(FFBinderParcel* parcel, size_t bytes) {
|
|
if (bytes > parcel->capacity - parcel->size) {
|
|
parcel->truncated = true;
|
|
return nullptr;
|
|
}
|
|
uint8_t* result = parcel->data + parcel->size;
|
|
parcel->size += bytes;
|
|
return result;
|
|
}
|
|
|
|
[[gnu::nonnull(1)]] static inline void ffBinderParcelPutU32(FFBinderParcel* parcel, uint32_t value) {
|
|
uint8_t* dst = ffBinderParcelReserve(parcel, sizeof(uint32_t));
|
|
if (dst != nullptr) {
|
|
memcpy(dst, &value, sizeof(uint32_t));
|
|
}
|
|
}
|
|
|
|
[[gnu::nonnull(1)]] static inline void ffBinderParcelPutI32(FFBinderParcel* parcel, int32_t value) {
|
|
uint8_t* dst = ffBinderParcelReserve(parcel, sizeof(int32_t));
|
|
if (dst != nullptr) {
|
|
memcpy(dst, &value, sizeof(int32_t));
|
|
}
|
|
}
|
|
|
|
[[gnu::nonnull(1)]] static inline void ffBinderParcelPutU64(FFBinderParcel* parcel, uint64_t value) {
|
|
uint8_t* dst = ffBinderParcelReserve(parcel, sizeof(uint64_t));
|
|
if (dst != nullptr) {
|
|
memcpy(dst, &value, sizeof(uint64_t));
|
|
}
|
|
}
|
|
|
|
// AIDL string16. The descriptors and service names fastfetch asks for are ASCII, so one byte is one
|
|
// UTF-16 code unit; anything else would have to be encoded properly here.
|
|
[[gnu::nonnull(1, 2)]] static inline void ffBinderParcelPutString16(FFBinderParcel* parcel, const char* value) {
|
|
const size_t length = strlen(value);
|
|
const size_t payload = sizeof(uint32_t) + (length + 1) * 2; // length, code units, NUL terminator
|
|
const size_t padded = (payload + 3) & ~(size_t) 3;
|
|
uint8_t* dst = ffBinderParcelReserve(parcel, padded);
|
|
if (dst == nullptr) {
|
|
return;
|
|
}
|
|
|
|
memset(dst, 0, padded);
|
|
const uint32_t length32 = (uint32_t) length;
|
|
memcpy(dst, &length32, sizeof(uint32_t));
|
|
for (size_t i = 0; i < length; i++) {
|
|
dst[sizeof(uint32_t) + i * 2] = (uint8_t) value[i];
|
|
}
|
|
// The two bytes after the last code unit stay zero: that is the UTF-16 NUL terminator, and
|
|
// Parcel::readInplace() advances by (length + 1) * sizeof(char16_t) to account for it.
|
|
}
|
|
|
|
[[gnu::nonnull(1, 2)]] static inline void ffBinderParcelPutInterfaceToken(FFBinderParcel* parcel, const char* descriptor) {
|
|
ffBinderParcelPutU32(parcel, FF_BINDER_STRICT_MODE_PENALTY_GATHER);
|
|
ffBinderParcelPutU32(parcel, FF_BINDER_UNSET_WORK_SOURCE);
|
|
ffBinderParcelPutU32(parcel, FF_BINDER_HEADER);
|
|
ffBinderParcelPutString16(parcel, descriptor);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Transactions
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
typedef struct FFBinderReply {
|
|
uint8_t* data; // caller owned; receives the payload
|
|
size_t capacity; // ditto
|
|
size_t size; // payload length in bytes
|
|
uint32_t code; // transaction code the service echoed back
|
|
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 = {}, .fdCount = 0, .fds = {} };
|
|
}
|
|
|
|
// A reply carrying TF_STATUS_CODE is not an AIDL parcel at all: the first four bytes are the raw
|
|
// status_t that onTransact() returned (-74 being UNKNOWN_TRANSACTION, i.e. no such method). Check
|
|
// this before interpreting anything else.
|
|
[[gnu::nonnull(1), nodiscard]] static inline bool ffBinderReplyIsStatus(const FFBinderReply* reply) {
|
|
return (reply->flags & TF_STATUS_CODE) != 0;
|
|
}
|
|
|
|
[[gnu::nonnull(1), nodiscard]] static inline uint32_t ffBinderReadU32(const uint8_t* data, size_t size, size_t offset) {
|
|
uint32_t value = 0;
|
|
if (offset + sizeof(uint32_t) <= size) {
|
|
memcpy(&value, data + offset, sizeof(uint32_t));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
[[gnu::nonnull(1), nodiscard]] static inline int32_t ffBinderReadI32(const uint8_t* data, size_t size, size_t offset) {
|
|
int32_t value = 0;
|
|
if (offset + sizeof(int32_t) <= size) {
|
|
memcpy(&value, data + offset, sizeof(int32_t));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
[[gnu::nonnull(1), nodiscard]] static inline uint64_t ffBinderReadU64(const uint8_t* data, size_t size, size_t offset) {
|
|
uint64_t value = 0;
|
|
if (offset + sizeof(uint64_t) <= size) {
|
|
memcpy(&value, data + offset, sizeof(uint64_t));
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// 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, 5, 6), nodiscard]] const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, uint32_t flags, const FFBinderParcel* parcel, FFBinderReply* reply);
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Service manager
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
#define FF_BINDER_SERVICE_MANAGER_HANDLE 0u
|
|
#define FF_BINDER_SM_DESCRIPTOR "android.os.IServiceManager"
|
|
|
|
// Entry points of android.os.IServiceManager, as this device answers them. The numbers do not match
|
|
// the AOSP sources: checkService is 4 here, not 3. FF_BINDER_SM_GET_SERVICE returns a nullable
|
|
// IBinder and is the simplest reply to parse; FF_BINDER_SM_CHECK_SERVICE is what `service` itself
|
|
// calls and answers with a parcelable instead.
|
|
#define FF_BINDER_SM_GET_SERVICE 1u
|
|
#define FF_BINDER_SM_GET_SERVICE2 2u
|
|
#define FF_BINDER_SM_CHECK_SERVICE 4u
|
|
#define FF_BINDER_SM_LIST_SERVICES 6u
|
|
|
|
// Resolves a service name to a handle through the service manager. Returns nullptr on success.
|
|
[[gnu::nonnull(1, 2, 4), nodiscard]] const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t transactionCode, uint32_t* handle);
|
|
|
|
// A handle resolved through the service manager, held together with the binder it came from so that a
|
|
// single cleanup attribute can give it back on every path out of the function that took it. Zeroed --
|
|
// or left at handle 0 by a lookup that has not run or did not succeed -- it is a no-op, which is what
|
|
// makes it safe to declare above the lookup itself.
|
|
typedef struct FFBinderServiceHandle {
|
|
FFBinder* binder;
|
|
uint32_t handle;
|
|
} FFBinderServiceHandle;
|
|
|
|
// Returns the strong and weak references ffBinderLookupService() acquired on the handle. Usable as a
|
|
// cleanup attribute; safe on a zeroed or already released FFBinderServiceHandle.
|
|
[[gnu::nonnull(1)]] void ffBinderServiceHandleRelease(FFBinderServiceHandle* service);
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Handles a transaction acquired
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
// Whatever ffBinderTransact() collected for the caller, released together on the way out of a
|
|
// function -- the handles it acquired, plus the descriptors it delivered, which are owned rather
|
|
// than borrowed and would otherwise stay open for the life of the process.
|
|
//
|
|
// Unlike FFBinderServiceHandle this cannot be declared before the transaction runs, because which
|
|
// handles came back is only known afterwards. `replied` is therefore a separate flag the caller
|
|
// raises once ffBinderTransact() has returned: it acquires the handles even when it goes on to fail
|
|
// (a reply that did not fit the caller buffer still had its BC_ACQUIRE tail sent), so releasing
|
|
// without it would drop a reference that was never taken.
|
|
typedef struct FFBinderAcquired {
|
|
FFBinder* binder;
|
|
const uint32_t* handles;
|
|
uint32_t handleCount;
|
|
const int32_t* fds;
|
|
uint32_t fdCount;
|
|
const bool* replied;
|
|
} FFBinderAcquired;
|
|
|
|
// Usable as a cleanup attribute; safe on a zeroed FFBinderAcquired, and on one whose transaction did
|
|
// not reply.
|
|
[[gnu::nonnull(1)]] void ffBinderAcquiredRelease(FFBinderAcquired* acquired);
|