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
+1
View File
@@ -28,6 +28,7 @@ Features:
* Battery level and charging state are now available to apps; detailed battery information requires root or ADB. (Battery)
* Added display mode, physical size, rotation, and HDR information. App-based detection requires Android 13 (API 33); root or ADB can also detect displays on older versions. (Display)
* Added Wi-Fi interface, connection, standard, and security information. Android 11 (API 30) or later is required. Termux:API and location permission are needed to reveal SSID and BSSID; otherwise, they appear as `<redacted>`. (Wifi)
* Added Wallpaper detection on Android, which effectively reports `/data/system/users/0/wallpaper`. (Wallpaper)
* Improved macOS support:
* Improved process-name detection in Top. (Top)
* Improved wallpaper detection on macOS Sonoma and later. (#2559, Wallpaper)
+2
View File
@@ -638,6 +638,7 @@ elseif(ANDROID)
list(APPEND LIBFASTFETCH_SRC
src/common/android/binder.c
src/common/android/dex.c
src/common/android/package.c
src/common/impl/dbus.c
src/common/impl/io_unix.c
src/common/impl/netif_linux.c
@@ -712,6 +713,7 @@ elseif(ANDROID)
src/detection/tpm/tpm_nosupport.c
src/detection/uptime/uptime_linux.c
src/detection/users/users_linux.c
src/detection/wallpaper/wallpaper_android.c
src/detection/wallpaper/wallpaper_linux.c
src/detection/wifi/wifi_android.c
src/detection/wm/wm_linux.c
+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);
+1 -1
View File
@@ -120,7 +120,7 @@ static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t prope
uint8_t replyBuffer[128];
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
const char* error = ffBinderTransact(binder, handle, transaction, &parcel, &reply);
const char* error = ffBinderTransact(binder, handle, transaction, 0, &parcel, &reply);
if (error != nullptr) {
FF_DEBUG("Property %u could not be transacted: %s", property, error);
return error;
+167
View File
@@ -0,0 +1,167 @@
#include "wallpaper.h"
#include "common/android/binder.h"
#include "common/android/package.h"
#include "common/debug.h"
#include "common/io.h"
#include "detection/displayserver/displayserver.h"
#include <unistd.h>
// Android keeps the wallpaper as a file per user, in a directory no app can enter:
//
// /data/system/users/<userId>/wallpaper the image the launcher draws
// /data/system/users/<userId>/wallpaper_orig the picture as it was uploaded, before cropping
//
// /data/system/users/<userId> is mode 0700 and owned by system:system, so `ls`, `stat` and `open` all
// answer EACCES to an app UID: the path is not readable even though it is not secret. What is
// reachable is a descriptor to the file, because android.app.IWallpaperManager opens it inside
// system_server and passes one back, and a descriptor is an ordinary file from then on. readlink(2)
// on it names the file, which is the string this module is asked for -- the image itself is never
// read or decoded.
//
// The gate is the calling package. `getWallpaper(String callingPackage, int which, int userId)`
// answers only when that name is the caller's own: the two-argument overload, which is the null name,
// comes back as a SecurityException raised by StorageManager.checkPermissionReadImages(), and an
// empty string, an unknown package and a real package belonging to a different uid all leave with no
// descriptor. That READ_MEDIA_IMAGES denial is not something a Termux-style app can lift -- the
// permission is not in its manifest and it targets SDK 28 -- so naming the caller is the only route.
//
// Measured on an Android 16 device as an app UID: the descriptor for `which` 1 is
// /data/system/users/0/wallpaper, a 2833949-byte JPEG whose SOF0 reads 2159x3168, while `which` 2
// answers with no descriptor at all because that lock screen is a live wallpaper.
//
// Only one of the two display servers is ever asked. The binder route is Android's, and it answers
// while SurfaceFlinger is the display server; a Termux:X11 or Wayland session puts a real compositor
// on top, and there the wallpaper belongs to that desktop -- so that case goes to wallpaper_linux.c,
// which is compiled alongside this file for exactly this reason.
#define FF_WALLPAPER_ANDROID_SERVICE "wallpaper"
#define FF_WALLPAPER_ANDROID_DESCRIPTOR "android.app.IWallpaperManager"
// WallpaperManager.FLAG_SYSTEM and FLAG_LOCK.
typedef enum FFWallpaperAndroidWhich : int32_t {
FF_WALLPAPER_ANDROID_WHICH_SYSTEM = 1,
FF_WALLPAPER_ANDROID_WHICH_LOCK = 2,
} FFWallpaperAndroidWhich;
// `getWallpaper` is the fourth method of IWallpaperManager$Stub, and the number is read from the
// device's own framework.jar rather than transcribed from AOSP, because a vendor ROM adds and drops
// methods as it likes -- this one dropped `getName`, whose code answers UNKNOWN_TRANSACTION here.
#define FF_WALLPAPER_ANDROID_TRANSACTION_GET_WALLPAPER 4u
// The request is the interface token, the package name, `which` and `userId`. The package name is the
// longest thing in it, and the helper will not write more than 128 bytes of it.
#define FF_WALLPAPER_ANDROID_PARCEL_SIZE 256
// The reply carries the descriptor object plus, for this method, a Bundle of two ints: 108 bytes on
// the device it was measured on, 80 when the wallpaper is a live one and the Bundle comes alone. Only
// the descriptor table is read out of it, so this is sized for headroom rather than for a field --
// being wrong here would fail the detection, not misread it.
#define FF_WALLPAPER_ANDROID_REPLY_SIZE 512
static const char* getWallpaperFile(FFBinder* binder, uint32_t handle, const char* package, FFWallpaperAndroidWhich which, FFstrbuf* result) {
uint8_t parcelBuffer[FF_WALLPAPER_ANDROID_PARCEL_SIZE];
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
ffBinderParcelPutInterfaceToken(&parcel, FF_WALLPAPER_ANDROID_DESCRIPTOR);
ffBinderParcelPutString16(&parcel, package);
ffBinderParcelPutI32(&parcel, (int32_t) which);
ffBinderParcelPutI32(&parcel, 0); // userId, which is the one user a phone has
uint8_t replyBuffer[FF_WALLPAPER_ANDROID_REPLY_SIZE];
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
// TF_ACCEPT_FDS is neither optional nor a hint: a reply carrying a descriptor is only delivered
// when the request asked for one, and a request that did not is answered with BR_FAILED_REPLY --
// which reads like a service that is not running rather than like a missing flag.
const char* error = ffBinderTransact(binder, handle, FF_WALLPAPER_ANDROID_TRANSACTION_GET_WALLPAPER, TF_ACCEPT_FDS, &parcel, &reply);
// Whatever else happened, a descriptor that did arrive is ours: the kernel installed it in our fd
// table, and on the paths below that return early nothing else would close it.
FF_AUTO_CLOSE_FD FFNativeFD fd = reply.fdCount > 0 ? reply.fds[0] : -1;
if (error != nullptr) {
FF_DEBUG("getWallpaper(which %d) could not be transacted: %s", (int) which, error);
return error;
}
if (ffBinderReplyIsStatus(&reply)) {
FF_DEBUG("getWallpaper(which %d) came back as a status reply of %d instead of a parcel",
(int) which, (int) ffBinderReadI32(reply.data, reply.size, 0));
return "Wallpaper service rejected the request";
}
const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
if (exception != 0) {
// -1 is a SecurityException, and for this method it is what the package name check raises.
FF_DEBUG("getWallpaper(which %d) raised exception %d over %zu bytes of reply", (int) which, exception, reply.size);
return "Wallpaper service refused the request";
}
if (!ffIsValidNativeFD(fd)) {
FF_DEBUG("getWallpaper(which %d) carried no descriptor, which is how a live wallpaper answers: "
"%zu bytes of reply",
(int) which, reply.size);
return "The wallpaper is not a file";
}
char path[64];
const int pathLength = snprintf(path, sizeof(path), "/proc/self/fd/%d", (int) fd);
if (pathLength <= 0 || (size_t) pathLength >= sizeof(path)) {
return "Failed to build the /proc path of the wallpaper descriptor";
}
char target[512];
const ssize_t length = readlink(path, target, sizeof(target) - 1);
if (length <= 0) {
FF_DEBUG("readlink(%s) failed", path);
return "Failed to resolve the wallpaper path";
}
target[length] = '\0';
ffStrbufSetNS(result, (uint32_t) length, target);
FF_DEBUG("The wallpaper for which %d is \"%s\"", (int) which, result->chars);
return nullptr;
}
const char* ffDetectWallpaper(FFstrbuf* result) {
// The binder route below reads Android's own wallpaper, which is the one behind the launcher
// only while SurfaceFlinger is the display server. A Termux:X11 or Wayland session runs a real
// compositor on top of the device, and the wallpaper visible there belongs to that desktop, not
// to Android -- so that case is handed to the Linux implementation, which knows GTK and Qt
// settings. Wallpaper is only reported for one of the two, never merged.
const FFDisplayServerResult* wm = ffConnectDisplayServer();
if (!ffStrbufIgnCaseEqualS(&wm->wmProtocolName, FF_WM_PROTOCOL_SURFACEFLINGER)) {
FF_DEBUG("The display server is \"%s\", so the Linux implementation answers", wm->wmProtocolName.chars);
const char* ffDetectWallpaperLinux(FFstrbuf* result);
return ffDetectWallpaperLinux(result);
}
char package[128];
if (!ffAndroidGetOwnPackage(package, sizeof(package))) {
return "Cannot determine the package name of this process";
}
[[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 };
const char* error = ffBinderOpen(&binder);
if (error != nullptr) {
return error;
}
// Released on every path out of this function, including the ones below that return early.
[[gnu::cleanup(ffBinderServiceHandleRelease)]] FFBinderServiceHandle service = { .binder = &binder };
error = ffBinderLookupService(&binder, FF_WALLPAPER_ANDROID_SERVICE, FF_BINDER_SM_GET_SERVICE, &service.handle);
if (error != nullptr) {
FF_DEBUG("Looking up the \"%s\" service failed: %s", FF_WALLPAPER_ANDROID_SERVICE, error);
return error;
}
FF_DEBUG("The \"%s\" service is handle %u", FF_WALLPAPER_ANDROID_SERVICE, service.handle);
// The system wallpaper is what the module means by "the current wallpaper"; the lock screen is a
// second, separate picture, and is only reported when the system one is not a file -- which is
// the live-wallpaper case, where a component draws it and there is no image behind it.
error = getWallpaperFile(&binder, service.handle, package, FF_WALLPAPER_ANDROID_WHICH_SYSTEM, result);
if (error == nullptr) {
return nullptr;
}
FF_DEBUG("The system wallpaper is not a file (%s), so the lock screen is asked for instead", error);
return getWallpaperFile(&binder, service.handle, package, FF_WALLPAPER_ANDROID_WHICH_LOCK, result);
}
+10 -1
View File
@@ -72,7 +72,16 @@ static const char* detectCosmicComp(FFstrbuf* result) {
return nullptr;
}
const char* ffDetectWallpaper(FFstrbuf* result) {
// Android compiles this file next to wallpaper_android.c, which owns ffDetectWallpaper and calls
// this one only while the display server is not SurfaceFlinger -- so the entry point has to step
// aside there. Same arrangement as terminalfont_linux.c and terminalfont_android.c.
const char*
#ifdef __ANDROID__
ffDetectWallpaperLinux
#else
ffDetectWallpaper
#endif
(FFstrbuf* result) {
const FFDisplayServerResult* wm = ffConnectDisplayServer();
if (ffStrbufIgnCaseEqualS(&wm->wmPrettyName, FF_WM_PRETTY_COSMIC_COMP)) {
return detectCosmicComp(result);
+15 -73
View File
@@ -3,7 +3,9 @@
#include "common/android/api.h"
#include "common/android/binder.h"
#include "common/android/dex.h"
#include "common/android/package.h"
#include "common/debug.h"
#include "common/strutil.h"
#include <ifaddrs.h>
#include <net/if.h>
@@ -153,11 +155,6 @@ typedef enum FFWifiAndroidRadioState : int32_t
FF_WIFI_ANDROID_WIFI_STATE_UNKNOWN = 4,
} FFWifiAndroidRadioState;
// The other argument is the caller's package name. The shell UID owns exactly one package and the
// service accepts that name, which is what `cmd` and `dumpsys` pass. The UID itself is
// FF_ANDROID_PRIVILEGED_UID_SHELL, see common/android/api.h. See getOwnPackage().
#define FF_WIFI_ANDROID_SHELL_PACKAGE "com.android.shell"
// An AIDL reply opens with the exception code and, for a Parcelable return, a non-null marker;
// WifiInfo itself then starts at 8. The network id, the RSSI and the link speed have held those
// offsets from Android 9 to 16, and the transmit speed joined them in Android 10 without moving
@@ -290,64 +287,9 @@ typedef enum FFWifiAndroidStandard : int32_t
// Binder
// ---------------------------------------------------------------------------------------------
// `getConnectionInfo` takes the caller's package name, and whether it is checked depends on the
// release: the Android 16 device answered "Package com.termux does not belong to <shell uid>" for a name
// the shell does not own, while the Android 11 device accepted the same name. Passing the real one is
// what works on both. 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 -- but the executable path carries it: an app's
// binaries live under /data/data/<package>/ or /data/user/<user>/<package>/, and /proc/self/exe
// resolves there.
//
// A binary outside those directories has no package of its own, and that is not an edge case: a
// static build pushed to /data/local/tmp is how this runs on a device without Termux, and the Android
// 11 device reported "Cannot determine the package name of this process" for exactly that. The shell UID can
// be answered instead, because it has one name the service accepts. No other UID reaches here: an
// app's own binaries are always under its data directory, so a failure there is a real failure.
static bool getOwnPackage(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 (strncmp(path, dataPrefix, strlen(dataPrefix)) == 0) {
rest = path + strlen(dataPrefix);
} else if (strncmp(path, userPrefix, strlen(userPrefix)) == 0) {
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_WIFI_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;
}
// The caller's own package name, which `getConnectionInfo` takes as its second argument: see
// ffAndroidGetOwnPackage() in common/android/package.h for why it has to be that one and how it is
// derived. `getWallpaper` in the Wallpaper module asks for the same thing.
static bool isMacAddress(const char* value, uint32_t length) {
if (length != FF_WIFI_ANDROID_MAC_STRING_LENGTH) {
@@ -418,7 +360,7 @@ static void detectInterface(FFWifiAndroidConnection* connection) {
}
for (const struct ifaddrs* ifa = addrs; ifa != nullptr; ifa = ifa->ifa_next) {
if (ifa->ifa_name == nullptr || strncmp(ifa->ifa_name, "wlan", 4) != 0) {
if (ifa->ifa_name == nullptr || !ffStrStartsWith(ifa->ifa_name, "wlan")) {
continue;
}
snprintf(connection->interface, sizeof(connection->interface), "%s", ifa->ifa_name);
@@ -493,7 +435,7 @@ static void findStrings(const uint8_t* data, size_t size, size_t from, FFWifiAnd
connection->bssidSeen = true;
// The placeholder means there is no address to report: it is what the service writes
// both for a caller it may not tell and for a connection that has none.
if (strcmp(buffer, FF_WIFI_ANDROID_DEFAULT_MAC_ADDRESS) != 0) {
if (!ffStrEquals(buffer, FF_WIFI_ANDROID_DEFAULT_MAC_ADDRESS)) {
memcpy(connection->bssid, buffer, length + 1);
}
} else if (connection->state[0] == '\0' && isSupplicantState(buffer, length)) {
@@ -804,7 +746,7 @@ static const char* parseConnectionInfo(const uint8_t* data, size_t size, FFWifiA
// connection has no frequency to write, so one that is here and passed the checks above is a
// connection.
if (connection->state[0] != '\0') {
connection->connected = strcmp(connection->state, "COMPLETED") == 0;
connection->connected = ffStrEquals(connection->state, "COMPLETED");
} else {
connection->connected = connection->ssidAbsent
|| ffBinderReadI32(data, size, FF_WIFI_ANDROID_OFF_NET_ID) >= 0;
@@ -876,7 +818,7 @@ static const char* callIntMethod(FFBinder* binder, uint32_t handle, const char*
uint8_t replyBuffer[64];
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
error = ffBinderTransact(binder, handle, (uint32_t) transaction, &parcel, &reply);
error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply);
if (error != nullptr) {
return error;
}
@@ -894,22 +836,22 @@ static const char* callIntMethod(FFBinder* binder, uint32_t handle, const char*
return nullptr;
}
// `getOwnPackage` writes at most this many bytes, terminator included.
// `ffAndroidGetOwnPackage` writes at most this many bytes, terminator included.
#define FF_WIFI_ANDROID_PACKAGE_SIZE 128
// The parcel for `getConnectionInfo` is the interface token, the package name and the -1 that
// stands for a null `callingFeatureId`. A string16 costs 4 + 2 * (length + 1) bytes padded to four,
// and the token puts three int32 in front of the descriptor. Deriving the size from the two names
// rather than rounding it up is what keeps the longest package name `getOwnPackage` can produce
// from marking the parcel truncated -- that fails the whole detection, rather than degrading to a
// call without a name.
// rather than rounding it up is what keeps the longest package name `ffAndroidGetOwnPackage` can
// produce from marking the parcel truncated -- that fails the whole detection, rather than degrading
// to a call without a name.
#define FF_WIFI_ANDROID_PARCEL_SIZE \
(12 + ((4 + 2 * sizeof(FF_WIFI_ANDROID_DESCRIPTOR) + 3) & ~3u) \
+ ((4 + 2 * FF_WIFI_ANDROID_PACKAGE_SIZE + 3) & ~3u) + 4)
static const char* detectWithBinder(FFlist* result) {
char package[FF_WIFI_ANDROID_PACKAGE_SIZE];
if (!getOwnPackage(package, sizeof(package))) {
if (!ffAndroidGetOwnPackage(package, sizeof(package))) {
return "Cannot determine the package name of this process";
}
@@ -945,7 +887,7 @@ static const char* detectWithBinder(FFlist* result) {
uint8_t replyBuffer[FF_WIFI_ANDROID_REPLY_SIZE];
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
error = ffBinderTransact(&binder, service.handle, (uint32_t) transaction, &parcel, &reply);
error = ffBinderTransact(&binder, service.handle, (uint32_t) transaction, 0, &parcel, &reply);
if (error != nullptr) {
return error;
}