ohmu
·
2026-10-15
1#pragma once
2
3// Android exposes no public C API for the system services that carry the data fastfetch wants: the
4// NDK has no BatteryManager / WifiManager, libbinder_ndk only speaks to AIDL HALs (it refuses to
5// prepare a transaction for a binder that has no NDK class), and `/system/bin/service call` costs a
6// fork/exec worth 15-20 ms. What is left is /dev/binder itself, which is all that `service` uses
7// underneath. This header speaks that protocol directly: open() + mmap() + BINDER_WRITE_READ, no
8// extra shared library, no child process, ~0.13 ms per call once a handle is cached.
9//
10// Three details are easy to get wrong. All three were confirmed against the device's own
11// libbinder.so and with a ptrace trace of `service`, and getting any of them wrong makes the
12// service answer BAD_TYPE (0x80000001) or BR_FAILED_REPLY (0x7211):
13//
14// * A request parcel must start with three int32 values -- strict mode policy, work source and
15// 'SYST'. Parcel::markForBinder() writes nothing for kernel binder, but the receiving side's
16// Parcel::enforceInterface() still compares the third word against its own header.
17// * Parcel::writeString16() writes the length, the UTF-16 code units, a UTF-16 NUL terminator and
18// then pads to four bytes. Skipping the terminator shifts every following field by four bytes.
19// * A handle arriving in a reply is owned by that reply buffer. It has to be acquired
20// (BC_ACQUIRE + BC_INCREFS) before BC_FREE_BUFFER; otherwise only a weak reference survives and
21// every later transaction on it fails, because the kernel looks up a strong reference.
22
23#include "fastfetch.h" // IWYU pragma: keep
24
25// The structures and command words below are not defined here: bionic generates this header from
26// bionic/libc/kernel/uapi/linux/android/binder.h and every NDK ships it, so it is the
27// authoritative copy of the binder ABI -- struct binder_write_read, struct binder_transaction_data,
28// struct flat_binder_object, BINDER_WRITE_READ / BINDER_VERSION, the BC_* and BR_* command words,
29// TF_* and BINDER_TYPE_*. Using it beats transcribing it: a second copy can only be wrong, and if
30// the header itself is wrong that is the NDK's problem to fix, not something fastfetch could have
31// caught by keeping its own.
32#include <linux/android/binder.h>
33
34#include <string.h>
35#include <sys/ioctl.h>
36
37#define FF_BINDER_DEVICE "/dev/binder"
38
39// The kernel allocates reply payloads out of the region handed to mmap(); 1 MiB is what libbinder
40// asks for. Replies themselves are small -- `listServices` on this device is the largest at a few
41// hundred bytes -- but the region also carries the per-process buffer pool.
42#define FF_BINDER_SHARED_SIZE (1024 * 1024)
43
44// A command word plus the binder_transaction_data that follows it. The payload never lands here:
45// it lives in the mmap region, and BR_REPLY only hands over a pointer to it.
46#define FF_BINDER_REPLY_BUFFER_SIZE 1024
47
48// How many handles a reply may transfer to us. Replies that carry more than this still succeed, but
49// the surplus handles are left unacquired and will fail on first use.
50#define FF_BINDER_MAX_HANDLES 4
51
52// How many file descriptors a reply may hand over. A descriptor is not a handle: the kernel installs
53// it in the caller's own fd table as it delivers the reply, so it is owned rather than borrowed and
54// the caller closes it. A reply carrying more than this still succeeds, and the surplus is closed
55// again by ffBinderTransact() because nothing would be left holding it.
56#define FF_BINDER_MAX_FDS 4
57
58// The three int32 values Parcel::writeInterfaceToken() puts in front of the descriptor, and that
59// Parcel::enforceInterface() checks the last of. These come from libbinder rather than from the
60// kernel, which is why they are spelled out here.
61#define FF_BINDER_STRICT_MODE_PENALTY_GATHER 0x80000000u // (1 << 31), Parcel.cpp
62#define FF_BINDER_UNSET_WORK_SOURCE 0xffffffffu // IPCThreadState::kUnsetWorkSource
63#define FF_BINDER_HEADER 0x53595354u // B_PACK_CHARS('S', 'Y', 'S', 'T')
64
65typedef struct FFBinder {
66 int fd;
67 uint8_t* shared;
68 size_t sharedSize;
69 int32_t protocolVersion;
70} FFBinder;
71
72// Opens /dev/binder, checks the protocol version and maps the shared region. Returns nullptr on
73// success, a static message otherwise; the device is left closed on failure.
74[[gnu::nonnull(1), nodiscard]] const char* ffBinderOpen(FFBinder* binder);
75
76// Usable as a cleanup attribute. Safe on a zeroed or already closed FFBinder.
77[[gnu::nonnull(1)]] void ffBinderClose(FFBinder* binder);
78
79// ---------------------------------------------------------------------------------------------
80// Parcel writing. The caller owns the buffer; anything that does not fit is dropped and recorded
81// in `truncated`, which ffBinderTransact() then reports as an error.
82// ---------------------------------------------------------------------------------------------
83
84typedef struct FFBinderParcel {
85 uint8_t* data;
86 size_t capacity;
87 size_t size;
88 bool truncated;
89} FFBinderParcel;
90
91[[nodiscard]] static inline FFBinderParcel ffBinderParcelCreate(uint8_t* data, size_t capacity) {
92 return (FFBinderParcel) { .data = data, .capacity = capacity, .size = 0, .truncated = false };
93}
94
95[[gnu::nonnull(1), nodiscard]] static inline uint8_t* ffBinderParcelReserve(FFBinderParcel* parcel, size_t bytes) {
96 if (bytes > parcel->capacity - parcel->size) {
97 parcel->truncated = true;
98 return nullptr;
99 }
100 uint8_t* result = parcel->data + parcel->size;
101 parcel->size += bytes;
102 return result;
103}
104
105[[gnu::nonnull(1)]] static inline void ffBinderParcelPutU32(FFBinderParcel* parcel, uint32_t value) {
106 uint8_t* dst = ffBinderParcelReserve(parcel, sizeof(uint32_t));
107 if (dst != nullptr) {
108 memcpy(dst, &value, sizeof(uint32_t));
109 }
110}
111
112[[gnu::nonnull(1)]] static inline void ffBinderParcelPutI32(FFBinderParcel* parcel, int32_t value) {
113 uint8_t* dst = ffBinderParcelReserve(parcel, sizeof(int32_t));
114 if (dst != nullptr) {
115 memcpy(dst, &value, sizeof(int32_t));
116 }
117}
118
119[[gnu::nonnull(1)]] static inline void ffBinderParcelPutU64(FFBinderParcel* parcel, uint64_t value) {
120 uint8_t* dst = ffBinderParcelReserve(parcel, sizeof(uint64_t));
121 if (dst != nullptr) {
122 memcpy(dst, &value, sizeof(uint64_t));
123 }
124}
125
126// AIDL string16. The descriptors and service names fastfetch asks for are ASCII, so one byte is one
127// UTF-16 code unit; anything else would have to be encoded properly here.
128[[gnu::nonnull(1, 2)]] static inline void ffBinderParcelPutString16(FFBinderParcel* parcel, const char* value) {
129 const size_t length = strlen(value);
130 const size_t payload = sizeof(uint32_t) + (length + 1) * 2; // length, code units, NUL terminator
131 const size_t padded = (payload + 3) & ~(size_t) 3;
132 uint8_t* dst = ffBinderParcelReserve(parcel, padded);
133 if (dst == nullptr) {
134 return;
135 }
136
137 memset(dst, 0, padded);
138 const uint32_t length32 = (uint32_t) length;
139 memcpy(dst, &length32, sizeof(uint32_t));
140 for (size_t i = 0; i < length; i++) {
141 dst[sizeof(uint32_t) + i * 2] = (uint8_t) value[i];
142 }
143 // The two bytes after the last code unit stay zero: that is the UTF-16 NUL terminator, and
144 // Parcel::readInplace() advances by (length + 1) * sizeof(char16_t) to account for it.
145}
146
147[[gnu::nonnull(1, 2)]] static inline void ffBinderParcelPutInterfaceToken(FFBinderParcel* parcel, const char* descriptor) {
148 ffBinderParcelPutU32(parcel, FF_BINDER_STRICT_MODE_PENALTY_GATHER);
149 ffBinderParcelPutU32(parcel, FF_BINDER_UNSET_WORK_SOURCE);
150 ffBinderParcelPutU32(parcel, FF_BINDER_HEADER);
151 ffBinderParcelPutString16(parcel, descriptor);
152}
153
154// ---------------------------------------------------------------------------------------------
155// Transactions
156// ---------------------------------------------------------------------------------------------
157
158typedef struct FFBinderReply {
159 uint8_t* data; // caller owned; receives the payload
160 size_t capacity; // ditto
161 size_t size; // payload length in bytes
162 uint32_t code; // transaction code the service echoed back
163 uint32_t flags; // TF_* of the reply
164 uint32_t handleCount;
165 uint32_t handles[FF_BINDER_MAX_HANDLES];
166 uint32_t fdCount;
167 int32_t fds[FF_BINDER_MAX_FDS]; // owned by the caller, which closes them
168} FFBinderReply;
169
170[[nodiscard]] static inline FFBinderReply ffBinderReplyCreate(uint8_t* data, size_t capacity) {
171 return (FFBinderReply) { .data = data, .capacity = capacity, .size = 0, .code = 0, .flags = 0, .handleCount = 0, .handles = {}, .fdCount = 0, .fds = {} };
172}
173
174// A reply carrying TF_STATUS_CODE is not an AIDL parcel at all: the first four bytes are the raw
175// status_t that onTransact() returned (-74 being UNKNOWN_TRANSACTION, i.e. no such method). Check
176// this before interpreting anything else.
177[[gnu::nonnull(1), nodiscard]] static inline bool ffBinderReplyIsStatus(const FFBinderReply* reply) {
178 return (reply->flags & TF_STATUS_CODE) != 0;
179}
180
181[[gnu::nonnull(1), nodiscard]] static inline uint32_t ffBinderReadU32(const uint8_t* data, size_t size, size_t offset) {
182 uint32_t value = 0;
183 if (offset + sizeof(uint32_t) <= size) {
184 memcpy(&value, data + offset, sizeof(uint32_t));
185 }
186 return value;
187}
188
189[[gnu::nonnull(1), nodiscard]] static inline int32_t ffBinderReadI32(const uint8_t* data, size_t size, size_t offset) {
190 int32_t value = 0;
191 if (offset + sizeof(int32_t) <= size) {
192 memcpy(&value, data + offset, sizeof(int32_t));
193 }
194 return value;
195}
196
197[[gnu::nonnull(1), nodiscard]] static inline uint64_t ffBinderReadU64(const uint8_t* data, size_t size, size_t offset) {
198 uint64_t value = 0;
199 if (offset + sizeof(uint64_t) <= size) {
200 memcpy(&value, data + offset, sizeof(uint64_t));
201 }
202 return value;
203}
204
205// Synchronous transaction. Returns nullptr on success, a static message otherwise.
206//
207// `flags` carries the TF_* values for the request, and one of them is not cosmetic: a reply that
208// carries a file descriptor is only delivered at all when the request asked with TF_ACCEPT_FDS. A
209// request that did not gets BR_FAILED_REPLY instead, which reads like a service that is not running
210// rather than like a missing flag. TF_ONE_WAY is the other one the kernel acts on, and it is what
211// makes a call fire-and-forget -- there is then no reply to fill `reply` from.
212//
213// Handles the reply carries are acquired and added to `reply->handles` before the reply buffer is
214// freed; descriptors are added to `reply->fds`, which the caller owns and closes.
215//
216// `handle` 0 is the service manager; every other handle comes from ffBinderLookupService(). A
217// service that never replies blocks this call forever -- none of the services fastfetch reads does,
218// but there is no timeout here to fall back on.
219[[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);
220
221// ---------------------------------------------------------------------------------------------
222// Service manager
223// ---------------------------------------------------------------------------------------------
224
225#define FF_BINDER_SERVICE_MANAGER_HANDLE 0u
226#define FF_BINDER_SM_DESCRIPTOR "android.os.IServiceManager"
227
228// Entry points of android.os.IServiceManager, as this device answers them. The numbers do not match
229// the AOSP sources: checkService is 4 here, not 3. FF_BINDER_SM_GET_SERVICE returns a nullable
230// IBinder and is the simplest reply to parse; FF_BINDER_SM_CHECK_SERVICE is what `service` itself
231// calls and answers with a parcelable instead.
232#define FF_BINDER_SM_GET_SERVICE 1u
233#define FF_BINDER_SM_GET_SERVICE2 2u
234#define FF_BINDER_SM_CHECK_SERVICE 4u
235#define FF_BINDER_SM_LIST_SERVICES 6u
236
237// Resolves a service name to a handle through the service manager. Returns nullptr on success.
238[[gnu::nonnull(1, 2, 4), nodiscard]] const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t transactionCode, uint32_t* handle);
239
240// A handle resolved through the service manager, held together with the binder it came from so that a
241// single cleanup attribute can give it back on every path out of the function that took it. Zeroed --
242// or left at handle 0 by a lookup that has not run or did not succeed -- it is a no-op, which is what
243// makes it safe to declare above the lookup itself.
244typedef struct FFBinderServiceHandle {
245 FFBinder* binder;
246 uint32_t handle;
247} FFBinderServiceHandle;
248
249// Returns the strong and weak references ffBinderLookupService() acquired on the handle. Usable as a
250// cleanup attribute; safe on a zeroed or already released FFBinderServiceHandle.
251[[gnu::nonnull(1)]] void ffBinderServiceHandleRelease(FFBinderServiceHandle* service);
252
253// ---------------------------------------------------------------------------------------------
254// Handles a transaction acquired
255// ---------------------------------------------------------------------------------------------
256
257// Whatever ffBinderTransact() collected for the caller, released together on the way out of a
258// function -- the handles it acquired, plus the descriptors it delivered, which are owned rather
259// than borrowed and would otherwise stay open for the life of the process.
260//
261// Unlike FFBinderServiceHandle this cannot be declared before the transaction runs, because which
262// handles came back is only known afterwards. `replied` is therefore a separate flag the caller
263// raises once ffBinderTransact() has returned: it acquires the handles even when it goes on to fail
264// (a reply that did not fit the caller buffer still had its BC_ACQUIRE tail sent), so releasing
265// without it would drop a reference that was never taken.
266typedef struct FFBinderAcquired {
267 FFBinder* binder;
268 const uint32_t* handles;
269 uint32_t handleCount;
270 const int32_t* fds;
271 uint32_t fdCount;
272 const bool* replied;
273} FFBinderAcquired;
274
275// Usable as a cleanup attribute; safe on a zeroed FFBinderAcquired, and on one whose transaction did
276// not reply.
277[[gnu::nonnull(1)]] void ffBinderAcquiredRelease(FFBinderAcquired* acquired);