Add experimental FreeBSD support

#5
This commit is contained in:
komaruworld committed 2026-08-15 22:39:52 +03:00
1 parent fa370b9dbb
commit fb5f38cb40
39 files changed
+1476 -132

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
# Preserve exact upstream whitespace in the provenance-locked Android core
# snapshot. Local Mocktail sources remain subject to git diff --check.
third_party/mcpelauncher-linker/core/** -whitespace
packaging/freebsd/*.patch -whitespace
+4 -4
View File
@@ -53,7 +53,7 @@ jobs:
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install --yes --no-install-recommends \
build-essential cmake git ninja-build pkg-config \
build-essential cmake git ninja-build pkg-config lld \
libsdl3-dev libsdl3-ttf-dev libcurl4-openssl-dev \
libssl-dev nlohmann-json3-dev libyaml-dev libelf-dev \
libminizip-dev libcapstone-dev libgtk-4-dev \
@@ -124,7 +124,7 @@ jobs:
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install --yes --no-install-recommends \
build-essential cmake git ninja-build pkg-config fakeroot \
build-essential cmake git ninja-build pkg-config lld fakeroot \
dpkg-dev file libsdl3-dev libsdl3-ttf-dev \
libcurl4-openssl-dev libssl-dev nlohmann-json3-dev \
libyaml-dev libelf-dev libminizip-dev libcapstone-dev \
@@ -172,7 +172,7 @@ jobs:
--env MOCKTAIL_PACKAGE_VERSION_SUFFIX="$nightly_suffix" \
fedora:44 bash -Eeuo pipefail -c '
dnf install --assumeyes \
gcc-c++ cmake git ninja-build pkgconf-pkg-config rpm-build \
gcc-c++ cmake git ninja-build pkgconf-pkg-config lld rpm-build \
SDL3-devel SDL3_ttf-devel libcurl-devel openssl-devel \
nlohmann-json-devel libyaml-devel elfutils-libelf-devel \
minizip-ng-compat-devel capstone-devel gtk4-devel libadwaita-devel \
@@ -218,7 +218,7 @@ jobs:
--workdir /workspace \
archlinux:base-devel bash -Eeuo pipefail -c '
pacman -Syu --needed --noconfirm \
cmake git ninja pkgconf sdl3 sdl3_ttf curl openssl \
cmake git ninja pkgconf lld sdl3 sdl3_ttf curl openssl \
nlohmann-json libyaml libelf minizip capstone gtk4 \
libadwaita webkitgtk-6.0 libutf8proc fontconfig \
libplacebo vulkan-headers vulkan-icd-loader libglvnd zlib
+39
View File
@@ -630,6 +630,40 @@ target_link_options(mocktail PRIVATE
-Wl,--export-dynamic
)
# Native FreeBSD helper for Linuxulator's unsupported DF socket option.
option(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER
"Build the native FreeBSD Linuxulator socket helper" ON)
if(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER)
find_program(MOCKTAIL_LLD_EXECUTABLE NAMES ld.lld REQUIRED)
find_program(MOCKTAIL_ELFEDIT_EXECUTABLE NAMES elfedit REQUIRED)
get_filename_component(MOCKTAIL_LLD_DIRECTORY
"${MOCKTAIL_LLD_EXECUTABLE}" DIRECTORY)
set(MOCKTAIL_FREEBSD_SOCKET_HELPER
"${CMAKE_BINARY_DIR}/mocktail_freebsd_socket_helper")
add_custom_command(
OUTPUT "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
COMMAND "${CMAKE_CXX_COMPILER}"
-x assembler-with-cpp
-nostdlib
-static
-no-pie
"-B${MOCKTAIL_LLD_DIRECTORY}/"
-fuse-ld=lld
-Wl,-e,_start,--build-id=none,-z,noexecstack
-o "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
"${CMAKE_SOURCE_DIR}/src/compat/freebsd_socket_helper.S"
COMMAND "${MOCKTAIL_ELFEDIT_EXECUTABLE}"
--output-osabi FreeBSD "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
DEPENDS "${CMAKE_SOURCE_DIR}/src/compat/freebsd_socket_helper.S"
COMMENT "Building native FreeBSD Linuxulator socket helper"
VERBATIM
)
add_custom_target(mocktail_freebsd_socket_helper_build ALL
DEPENDS "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
)
add_dependencies(mocktail mocktail_freebsd_socket_helper_build)
endif()
set_target_properties(mocktail PROPERTIES
BUILD_RPATH "\$ORIGIN"
INSTALL_RPATH "\$ORIGIN;\$ORIGIN/../${CMAKE_INSTALL_LIBDIR}/mocktail"
@@ -662,6 +696,11 @@ install(TARGETS
RUNTIME DESTINATION "${MOCKTAIL_INSTALL_RUNTIME_DIR}"
LIBRARY DESTINATION "${MOCKTAIL_INSTALL_RUNTIME_DIR}"
)
if(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER)
install(PROGRAMS "${MOCKTAIL_FREEBSD_SOCKET_HELPER}"
DESTINATION "${MOCKTAIL_INSTALL_RUNTIME_DIR}"
)
endif()
install(FILES
config/roblox_compatibility.json
config/roblox_bootstrap_sources.json
+15 -10
View File
@@ -5,9 +5,10 @@
[![Downloads](https://img.shields.io/github/downloads/komaruworld/mocktail/total?logo=github)](https://github.com/komaruworld/mocktail/releases/latest)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
Mocktail runs the Android `x86_64` Roblox client on Linux. It provides the
Android ABI and JNI pieces the client expects, then connects them to SDL3 and
Vulkan or OpenGL on the Linux side.
Mocktail runs the Android `x86_64` Roblox client on Linux, including a Linux
userspace hosted by FreeBSD's Linuxulator. It provides the Android ABI and JNI
pieces the client expects, then connects them to SDL3 and Vulkan or OpenGL on
the Linux side.
Mocktail is an independent community project. It is not affiliated with Roblox
Corporation or VinegarHQ and does not distribute the Roblox client.
@@ -102,17 +103,21 @@ Direct AppImage, DEB, and RPM downloads are available from the
## Building
Only Linux `x86_64` is supported. Building requires CMake 3.20+, Git,
pkg-config, a C++17 compiler, SDL 3.4+, SDL3_ttf, Vulkan, EGL, libplacebo,
fontconfig, libcurl, OpenSSL, libelf, libyaml, minizip, Capstone 5, utf8proc,
nlohmann/json, GTK4, libadwaita 1.6+, and WebKitGTK 6.0.
Linux `x86_64` is supported. Experimental FreeBSD 15.1 Linuxulator support has
been tested with an `x86_64` Fedora 44 userspace. On FreeBSD, Mocktail runs
inside Linuxulator; it is not a native FreeBSD binary. Building requires CMake
3.20+, Git, pkg-config, LLD, binutils, a C++17 compiler, SDL 3.4+, SDL3_ttf,
Vulkan, EGL, libplacebo, fontconfig, libcurl, OpenSSL, libelf, libyaml, minizip,
Capstone 5, utf8proc, nlohmann/json, GTK4, libadwaita 1.6+, and WebKitGTK 6.0.
See the [FreeBSD Guide](packaging/freebsd/README.md) for setup and launch instructions.
<details>
<summary>Ubuntu 26.04+</summary>
```bash
sudo apt update
sudo apt install build-essential cmake git ninja-build pkg-config \
sudo apt install build-essential cmake git ninja-build pkg-config lld \
libsdl3-dev libsdl3-ttf-dev libcurl4-openssl-dev libssl-dev \
nlohmann-json3-dev libyaml-dev libelf-dev libminizip-dev \
libcapstone-dev libgtk-4-dev libadwaita-1-dev libwebkitgtk-6.0-dev \
@@ -125,7 +130,7 @@ sudo apt install build-essential cmake git ninja-build pkg-config \
<summary>Arch Linux</summary>
```bash
sudo pacman -S --needed base-devel cmake git ninja pkgconf sdl3 sdl3_ttf \
sudo pacman -S --needed base-devel cmake git ninja pkgconf lld sdl3 sdl3_ttf \
curl openssl nlohmann-json libyaml libelf minizip capstone gtk4 \
libadwaita webkitgtk-6.0 libutf8proc fontconfig libglvnd \
libplacebo vulkan-headers vulkan-icd-loader zlib
@@ -136,7 +141,7 @@ sudo pacman -S --needed base-devel cmake git ninja pkgconf sdl3 sdl3_ttf \
<summary>Fedora 44+</summary>
```bash
sudo dnf install gcc-c++ cmake git ninja-build pkgconf-pkg-config \
sudo dnf install gcc-c++ cmake git ninja-build pkgconf-pkg-config lld \
SDL3-devel SDL3_ttf-devel libcurl-devel openssl-devel \
nlohmann-json-devel libyaml-devel elfutils-libelf-devel minizip-ng-compat-devel \
capstone-devel gtk4-devel libadwaita-devel webkitgtk6.0-devel \
+5
View File
@@ -16,8 +16,10 @@ add_library(mocktail_compat STATIC
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_dns_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_host_libc_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_large_file_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_prctl_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_rwlock_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_signal_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_socket_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_semaphore_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_pthread_create_runtime.cc
${MOCKTAIL_ELF_COMPAT_ROOT}/src/compat/bionic_pthread_key_runtime.cc
@@ -34,6 +36,9 @@ add_library(Mocktail::Compat ALIAS mocktail_compat)
target_include_directories(mocktail_compat PUBLIC
${MOCKTAIL_ELF_COMPAT_ROOT}/include
)
target_compile_definitions(mocktail_compat PRIVATE
"MOCKTAIL_INSTALL_LIBDIR=\"${CMAKE_INSTALL_LIBDIR}\""
)
target_link_libraries(mocktail_compat PUBLIC
PkgConfig::LIBELF
nlohmann_json::nlohmann_json
+16
View File
@@ -15,7 +15,10 @@
#ifndef MOCKTAIL_COMPAT_BIONIC_HOST_LIBC_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_HOST_LIBC_RUNTIME_H_
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/utsname.h>
#include <cstddef>
#include <cstdint>
@@ -72,6 +75,15 @@ void BionicArc4RandomBuffer(void* buffer, size_t size) noexcept;
BionicMallinfoSnapshot BionicMallinfo() noexcept;
bool BionicMallinfoHasHostTelemetry() noexcept;
void NormalizeBionicSysInfo(struct sysinfo* info) noexcept;
int BionicSysInfo(struct sysinfo* info) noexcept;
bool NormalizeLinuxulatorUnameVersion(struct utsname* name,
const char* linux_version) noexcept;
int BionicUname(struct utsname* name) noexcept;
uid_t NormalizeBionicApplicationUid(uid_t host_uid) noexcept;
uid_t BionicGetUid() noexcept;
uid_t BionicGetEffectiveUid() noexcept;
// POSIX strerror_r contract: zero on success, -1/ERANGE on truncation.
int BionicStrError(int error_number, char* buffer, size_t buffer_size) noexcept;
@@ -93,6 +105,10 @@ int mocktail_bionic_cxa_thread_atexit_impl(
void* dso_handle);
void mocktail_bionic_arc4random_buf(void* buffer, size_t size);
mocktail::compat::BionicMallinfoSnapshot mocktail_bionic_mallinfo();
int mocktail_bionic_sysinfo(struct sysinfo* info);
int mocktail_bionic_uname(struct utsname* name);
uid_t mocktail_bionic_getuid();
uid_t mocktail_bionic_geteuid();
int mocktail_bionic_strerror_r(int error_number, char* buffer,
size_t buffer_size);
long long mocktail_bionic_strtoll_l(const char* text, char** end, int base,
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_PRCTL_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_PRCTL_RUNTIME_H_
extern "C" {
int mocktail_bionic_prctl(int option, unsigned long argument2,
unsigned long argument3, unsigned long argument4,
unsigned long argument5);
} // extern "C"
#endif // MOCKTAIL_COMPAT_BIONIC_PRCTL_RUNTIME_H_
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MOCKTAIL_COMPAT_BIONIC_SOCKET_RUNTIME_H_
#define MOCKTAIL_COMPAT_BIONIC_SOCKET_RUNTIME_H_
#include <sys/socket.h>
extern "C" {
int mocktail_bionic_setsockopt(int socket, int level, int option_name,
const void *option_value,
socklen_t option_length);
ssize_t mocktail_bionic_sendmsg(int socket, const struct msghdr *message,
int flags);
} // extern "C"
#endif // MOCKTAIL_COMPAT_BIONIC_SOCKET_RUNTIME_H_
+3 -3
View File
@@ -27,6 +27,8 @@ struct ActivePayloadPaths {
bool active = false;
std::filesystem::path root;
std::filesystem::path roblox_library;
std::filesystem::path base_apk;
std::filesystem::path x86_64_split_apk;
std::filesystem::path assets_content;
std::filesystem::path compatibility_manifest;
std::filesystem::path host_abi_profile;
@@ -127,9 +129,7 @@ class RuntimePaths {
bool ExportRuntimePathEnvironment(const RuntimePaths& paths,
std::string* error = nullptr);
// Selects the writable XDG data root as the managed payload working directory
// and exposes its immutable assets at the relative path still used by a small
// number of guest filesystem probes.
// Publishes the managed payload under the guest's rbx_bin paths.
bool PrepareManagedPayloadWorkingDirectory(const RuntimePaths& paths,
const ActivePayloadPaths& active,
std::string* error = nullptr);
+1
View File
@@ -30,6 +30,7 @@ depends=(
makedepends=(
'cmake>=3.20'
'git'
'lld'
'ninja'
'pkgconf'
)
+1
View File
@@ -7,6 +7,7 @@ pkgbase = mocktail-git
license = Apache-2.0
makedepends = cmake>=3.20
makedepends = git
makedepends = lld
makedepends = ninja
makedepends = nlohmann-json
makedepends = pkgconf
+1
View File
@@ -34,6 +34,7 @@ depends=(
makedepends=(
'cmake>=3.20'
'git'
'lld'
'ninja'
'nlohmann-json'
'pkgconf'
+1
View File
@@ -7,6 +7,7 @@ pkgbase = mocktail
license = Apache-2.0
makedepends = cmake>=3.20
makedepends = git
makedepends = lld
makedepends = ninja
makedepends = nlohmann-json
makedepends = pkgconf
+1
View File
@@ -34,6 +34,7 @@ depends=(
makedepends=(
'cmake>=3.20'
'git'
'lld'
'ninja'
'nlohmann-json'
'pkgconf'
@@ -179,6 +179,7 @@
"-DCMAKE_INSTALL_LIBDIR=lib",
"-DMOCKTAIL_DEFAULT_COMPATIBILITY_MANIFEST=/app/share/mocktail/metadata/roblox_compatibility.json",
"-DMOCKTAIL_DEFAULT_SIGNING_TRUST_MANIFEST=/app/share/mocktail/metadata/roblox_signing_certificates.json",
"-DMOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER=OFF",
"-DBUILD_TESTING=OFF"
],
"sources": [
+51
View File
@@ -0,0 +1,51 @@
# FreeBSD Linuxulator
FreeBSD Linuxulator support is experimental. I tested it on FreeBSD
15.1-RELEASE-p2 with a Fedora 44 x86_64 userspace.
FreeBSD returns the wrong filesystem type for `/proc`. Roblox treats this as a
broken install and disconnects with Error 304 after about a minute.
[`linuxulator.patch`](linuxulator.patch) makes Linuxulator return the same
value as Linux.
## Patch FreeBSD
Use a clean Git checkout that exactly matches the running FreeBSD kernel. The
tested source revision is `aadd58dddcbc78f4d5594827b46b5633552b15ce`. Run
the following commands as root.
```sh
fetch -o /root/linuxulator.patch https://raw.githubusercontent.com/komaruworld/mocktail/main/packaging/freebsd/linuxulator.patch
cd /usr/src
git apply /root/linuxulator.patch
make -j2 kernel-toolchain
make -j2 buildkernel KERNCONF=GENERIC MODULES_OVERRIDE=linux64
```
Install only the rebuilt module. Keep a backup and reboot instead of unloading
a live Linuxulator module.
```sh
cp -p /boot/kernel/linux64.ko /boot/kernel/linux64.ko.mocktail-backup
install -o root -g wheel -m 0444 /usr/obj/usr/src/amd64.amd64/sys/GENERIC/modules/usr/src/sys/modules/linux64/linux64.ko /boot/kernel/linux64.ko.mocktail-new
mv /boot/kernel/linux64.ko.mocktail-new /boot/kernel/linux64.ko
kldxref /boot/kernel
reboot
```
After reboot, verify the result from the Fedora userspace. It must print
`9fa0`.
```sh
chroot /compat/linux stat -f -c %t /proc
```
## Run Mocktail
You can simply run the Mocktail AppImage from the
[latest release](https://github.com/komaruworld/mocktail/releases/latest) or
[nightly build](https://github.com/komaruworld/mocktail/releases/tag/continuous)
inside the Fedora userspace.
Mocktail cannot override this check because Roblox reads `/proc` directly. The
Linuxulator patch fixes the value before Roblox sees it.
+11
View File
@@ -0,0 +1,11 @@
diff --git a/sys/compat/linux/linux_stats.c b/sys/compat/linux/linux_stats.c
--- a/sys/compat/linux/linux_stats.c
+++ b/sys/compat/linux/linux_stats.c
@@ -308,6 +308,7 @@ bsd_to_linux_ftype(const char *fstypename)
{"nfs", LINUX_NFS_SUPER_MAGIC},
{"ext2fs", LINUX_EXT2_SUPER_MAGIC},
{"procfs", LINUX_PROC_SUPER_MAGIC},
+ {"linprocfs", LINUX_PROC_SUPER_MAGIC},
{"msdosfs", LINUX_MSDOS_SUPER_MAGIC},
{"ntfs", LINUX_NTFS_SUPER_MAGIC},
{"nwfs", LINUX_NCP_SUPER_MAGIC},
+6
View File
@@ -29,8 +29,10 @@ fi
RUNTIME_ROOT="${BUNDLE_ROOT}/mocktail"
BIN_DIR="${RUNTIME_ROOT}/bin"
MAIN_BINARY="${BIN_DIR}/mocktail"
UPDATE_HELPER="${BIN_DIR}/mocktail_updater"
FAILURE_DIALOG_HELPER="${BIN_DIR}/mocktail_failure_dialog"
WEBVIEW_HELPER="${BIN_DIR}/mocktail_webview_helper"
FREEBSD_SOCKET_HELPER="${BIN_DIR}/mocktail_freebsd_socket_helper"
ANDROID_BUILD_TOOLS="${RUNTIME_ROOT}/runtime/android-tools/bin"
METADATA_DIR="${RUNTIME_ROOT}/metadata"
ABI_MANIFEST="${METADATA_DIR}/ABI.txt"
@@ -40,6 +42,7 @@ SUPPORT_ROOT="${RUNTIME_ROOT}/runtime"
SUPPORT_BIN="${SUPPORT_ROOT}/bin"
export PATH="${SUPPORT_BIN}:${ANDROID_BUILD_TOOLS}:${PATH}"
export LD_LIBRARY_PATH="${RUNTIME_ROOT}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
export MOCKTAIL_FREEBSD_SOCKET_HELPER="${FREEBSD_SOCKET_HELPER}"
Die() {
printf 'mocktail: %s\n' "$*" >&2
@@ -346,6 +349,7 @@ CheckBundleAbi() {
[[ "${main_interpreter}" == "${ABI_INTERPRETER}" ]] ||
Die "mocktail ELF interpreter does not match ABI.txt (expected ${ABI_INTERPRETER}, found ${main_interpreter:-none})"
ValidateBundledElfAbi "${MAIN_BINARY}" "mocktail"
ValidateBundledElfAbi "${UPDATE_HELPER}" "Mocktail updater"
ValidateBundledElfAbi "${WEBVIEW_HELPER}" "WebView helper"
[[ -x "${ANDROID_BUILD_TOOLS}/aapt" ]] ||
Die "Android APK metadata tool is unavailable"
@@ -475,6 +479,7 @@ CheckSystem() {
[[ -d "${RUNTIME_ROOT}" && ! -L "${RUNTIME_ROOT}" ]] ||
Die "missing regular runtime directory: ${RUNTIME_ROOT}"
[[ -x "${MAIN_BINARY}" ]] || Die "missing executable: ${MAIN_BINARY}"
[[ -x "${UPDATE_HELPER}" ]] || Die "missing executable: ${UPDATE_HELPER}"
[[ -x "${FAILURE_DIALOG_HELPER}" ]] ||
Die "missing executable: ${FAILURE_DIALOG_HELPER}"
[[ -x "${WEBVIEW_HELPER}" ]] || Die "missing WebView helper: ${WEBVIEW_HELPER}"
@@ -513,6 +518,7 @@ export MOCKTAIL_UPDATE_COMPATIBILITY_PATH="${METADATA_DIR}/roblox_compatibility.
export MOCKTAIL_UPDATE_SIGNING_TRUST_PATH="${METADATA_DIR}/roblox_signing_certificates.json"
export MOCKTAIL_UPDATE_HOST_ABI_REFERENCE="${METADATA_DIR}/roblox_host_abi_reference.json"
export MOCKTAIL_BOOTSTRAP_SOURCES_PATH="${METADATA_DIR}/roblox_bootstrap_sources.json"
export MOCKTAIL_UPDATE_HELPER="${UPDATE_HELPER}"
export MOCKTAIL_UPDATE_CANARY_BIN="${MAIN_BINARY}"
export MOCKTAIL_UPDATE_SMOKE_SCRIPT="${RUNTIME_ROOT}/scripts/real_bringup_smoke.sh"
export MOCKTAIL_BIN="${MAIN_BINARY}"
+2
View File
@@ -158,6 +158,8 @@ if [[ -z "${CMAKE_TOOLCHAIN_FILE}" ]]; then
fi
check_tool git "sudo apt install git"
check_tool java "sudo apt install default-jdk"
check_tool ld.lld "install LLD"
check_tool elfedit "install binutils"
if [[ "${EXTRACT_APK}" == true ]]; then
check_tool unzip "sudo apt install unzip"
+2
View File
@@ -277,6 +277,8 @@ Main() {
ParseArguments "$@"
RequireCommand cmake
RequireCommand find
RequireCommand ld.lld
RequireCommand elfedit
RequireCommand readelf
RequireCommand readlink
RequireCommand sort
+20
View File
@@ -31,6 +31,7 @@ ANDROID_TOOLS_ABI=""
readonly -a PROJECT_ARTIFACTS=(
mocktail
mocktail_updater
mocktail_failure_dialog
mocktail_webview_helper
libmocktail_audio_sdl.so
@@ -47,6 +48,7 @@ readonly -a PROJECT_ARTIFACTS=(
libGLESv2.so
libvulkan.so
)
readonly FREEBSD_SOCKET_HELPER="mocktail_freebsd_socket_helper"
readonly -a RUNTIME_SCRIPTS=(
auto_update_roblox.sh
@@ -267,6 +269,21 @@ ValidateArtifacts() {
TARGET_INTERPRETER="$(ReadElfInterpreter "${BUILD_DIR}/mocktail")"
[[ -n "${TARGET_INTERPRETER}" ]] ||
Die "mocktail must be a dynamically linked ${TARGET_LIBC} executable"
local freebsd_helper="${BUILD_DIR}/${FREEBSD_SOCKET_HELPER}"
[[ -f "${freebsd_helper}" && ! -L "${freebsd_helper}" &&
-x "${freebsd_helper}" ]] ||
Die "missing executable FreeBSD socket helper: ${freebsd_helper}"
LC_ALL=C readelf -h "${freebsd_helper}" 2>/dev/null |
grep -Eq 'OS/ABI:[[:space:]]+UNIX - FreeBSD' ||
Die "socket helper is not branded for FreeBSD"
LC_ALL=C readelf -h "${freebsd_helper}" 2>/dev/null |
grep -Eq 'Machine:[[:space:]]+.*X86-64' ||
Die "socket helper is not an x86-64 ELF"
[[ -z "$(ReadElfInterpreter "${freebsd_helper}")" ]] ||
Die "FreeBSD socket helper must be statically linked"
LC_ALL=C readelf -n "${freebsd_helper}" 2>/dev/null |
grep -Fq FreeBSD || Die "socket helper has no FreeBSD ABI note"
}
ResolveAndroidBuildTools() {
@@ -468,6 +485,8 @@ CopyRuntimeTree() {
install -m "${mode}" -- "${BUILD_DIR}/${artifact}" \
"${runtime_root}/bin/${artifact}"
done
install -m 0755 -- "${BUILD_DIR}/${FREEBSD_SOCKET_HELPER}" \
"${runtime_root}/bin/${FREEBSD_SOCKET_HELPER}"
install -m 0755 -- "${PROJECT_ROOT}/packaging/run.sh" "${STAGING}/run.sh"
install -m 0755 -- "${PROJECT_ROOT}/packaging/mocktail-launcher.sh" \
@@ -625,6 +644,7 @@ QueuePackagedRuntimeDependencies() {
while IFS= read -r -d '' source; do
IsElfFile "${source}" || continue
case "${source#${STAGING}/mocktail/}" in
bin/${FREEBSD_SOCKET_HELPER}) continue ;;
runtime/android-tools/*) continue ;;
esac
QueueDependencies "${source}"
+102
View File
@@ -18,6 +18,7 @@
#include <limits.h>
#include <locale.h>
#include <sys/random.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <array>
@@ -75,6 +76,35 @@ bool FillFromGetRandom(unsigned char* output, size_t size) noexcept {
return true;
}
bool ReadTextFile(const char* path, char* output, size_t capacity) noexcept {
if (path == nullptr || output == nullptr || capacity < 2) return false;
int descriptor;
do {
descriptor = ::open(path, O_RDONLY | O_CLOEXEC);
} while (descriptor < 0 && errno == EINTR);
if (descriptor < 0) return false;
ssize_t length;
do {
length = ::read(descriptor, output, capacity - 1);
} while (length < 0 && errno == EINTR);
const int read_errno = errno;
static_cast<void>(::close(descriptor));
if (length <= 0) {
errno = read_errno;
return false;
}
size_t size = static_cast<size_t>(length);
while (size != 0 &&
(output[size - 1] == '\n' || output[size - 1] == '\r')) {
--size;
}
output[size] = '\0';
return size != 0;
}
void FillFromUrandom(unsigned char* output, size_t size) noexcept {
int descriptor;
do {
@@ -238,6 +268,62 @@ bool BionicMallinfoHasHostTelemetry() noexcept {
return false;
}
void NormalizeBionicSysInfo(struct sysinfo* info) noexcept {
if (info == nullptr) return;
constexpr unsigned long kHighBit =
1UL << (sizeof(unsigned long) * CHAR_BIT - 1U);
if ((info->totalswap & kHighBit) != 0 ||
(info->freeswap & kHighBit) != 0 || info->freeswap > info->totalswap) {
info->totalswap = 0;
info->freeswap = 0;
}
}
int BionicSysInfo(struct sysinfo* info) noexcept {
const int result = ::sysinfo(info);
if (result == 0) NormalizeBionicSysInfo(info);
return result;
}
bool NormalizeLinuxulatorUnameVersion(struct utsname* name,
const char* linux_version) noexcept {
if (name == nullptr || linux_version == nullptr || linux_version[0] == '\0' ||
std::strcmp(name->sysname, "Linux") != 0 ||
(std::strstr(name->version, "FreeBSD") == nullptr &&
std::strstr(name->version, "freebsd.org") == nullptr)) {
return false;
}
const int length = std::snprintf(name->version, sizeof(name->version), "%s",
linux_version);
return length >= 0 && static_cast<size_t>(length) < sizeof(name->version);
}
int BionicUname(struct utsname* name) noexcept {
const int result = ::uname(name);
if (result != 0 || name == nullptr) return result;
const int host_errno = errno;
std::array<char, sizeof(name->version)> linux_version{};
if (ReadTextFile("/proc/sys/kernel/version", linux_version.data(),
linux_version.size())) {
NormalizeLinuxulatorUnameVersion(name, linux_version.data());
}
errno = host_errno;
return result;
}
uid_t NormalizeBionicApplicationUid(uid_t host_uid) noexcept {
return host_uid == 0 ? 1000 : host_uid;
}
uid_t BionicGetUid() noexcept {
return NormalizeBionicApplicationUid(::getuid());
}
uid_t BionicGetEffectiveUid() noexcept {
return NormalizeBionicApplicationUid(::geteuid());
}
int BionicStrError(int error_number, char* buffer,
size_t buffer_size) noexcept {
const int entry_errno = errno;
@@ -305,6 +391,22 @@ extern "C" mocktail::compat::BionicMallinfoSnapshot mocktail_bionic_mallinfo() {
return mocktail::compat::BionicMallinfo();
}
extern "C" int mocktail_bionic_sysinfo(struct sysinfo* info) {
return mocktail::compat::BionicSysInfo(info);
}
extern "C" int mocktail_bionic_uname(struct utsname* name) {
return mocktail::compat::BionicUname(name);
}
extern "C" uid_t mocktail_bionic_getuid() {
return mocktail::compat::BionicGetUid();
}
extern "C" uid_t mocktail_bionic_geteuid() {
return mocktail::compat::BionicGetEffectiveUid();
}
extern "C" int mocktail_bionic_strerror_r(int error_number, char* buffer,
size_t buffer_size) {
return mocktail::compat::BionicStrError(error_number, buffer, buffer_size);
+119
View File
@@ -0,0 +1,119 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "compat/bionic_prctl_runtime.h"
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <sys/prctl.h>
#include <unistd.h>
namespace mocktail::compat {
namespace {
#ifndef PR_SET_VMA
constexpr int kPrSetVma = 0x53564d41;
#else
constexpr int kPrSetVma = PR_SET_VMA;
#endif
#ifndef PR_SET_VMA_ANON_NAME
constexpr unsigned long kPrSetVmaAnonName = 0;
#else
constexpr unsigned long kPrSetVmaAnonName = PR_SET_VMA_ANON_NAME;
#endif
#ifndef PR_SET_THP_DISABLE
constexpr int kPrSetThpDisable = 41;
#else
constexpr int kPrSetThpDisable = PR_SET_THP_DISABLE;
#endif
#ifndef PR_GET_THP_DISABLE
constexpr int kPrGetThpDisable = 42;
#else
constexpr int kPrGetThpDisable = PR_GET_THP_DISABLE;
#endif
bool IsUnsupportedPrctlError(int error) noexcept {
return error == EINVAL || error == ENOSYS || error == EOPNOTSUPP;
}
bool IsFreeBsdLinuxulator() noexcept {
const int saved_errno = errno;
static const bool detected = [] {
int descriptor = ::open("/proc/version", O_RDONLY | O_CLOEXEC);
if (descriptor < 0) return false;
char version[512] = {};
const ssize_t length = ::read(descriptor, version, sizeof(version) - 1);
::close(descriptor);
if (length <= 0) return false;
version[length] = '\0';
return std::strstr(version, "FreeBSD") != nullptr ||
std::strstr(version, "freebsd.org") != nullptr;
}();
errno = saved_errno;
return detected;
}
bool IsPageAlignedRange(unsigned long address, unsigned long length) noexcept {
if (address == 0 || length == 0 || address + length < address) return false;
const long page_size = ::sysconf(_SC_PAGESIZE);
if (page_size <= 0) return false;
const auto page_mask = static_cast<unsigned long>(page_size - 1);
return (address & page_mask) == 0 && (length & page_mask) == 0;
}
} // namespace
int BionicPrctl(int option, unsigned long argument2,
unsigned long argument3, unsigned long argument4,
unsigned long argument5) noexcept {
const int previous_errno = errno;
const int result = ::prctl(option, argument2, argument3, argument4,
argument5);
if (result != -1 || !IsFreeBsdLinuxulator() ||
!IsUnsupportedPrctlError(errno)) {
return result;
}
if (option == kPrGetThpDisable) {
errno = previous_errno;
return 1;
}
if (option == kPrSetThpDisable && argument2 != 0) {
errno = previous_errno;
return 0;
}
if (option == kPrSetVma && argument2 == kPrSetVmaAnonName &&
IsPageAlignedRange(argument3, argument4)) {
errno = previous_errno;
return 0;
}
return result;
}
} // namespace mocktail::compat
extern "C" int mocktail_bionic_prctl(int option, unsigned long argument2,
unsigned long argument3,
unsigned long argument4,
unsigned long argument5) {
return mocktail::compat::BionicPrctl(option, argument2, argument3,
argument4, argument5);
}
+395
View File
@@ -0,0 +1,395 @@
// Copyright 2026 Mocktail Project Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "compat/bionic_socket_runtime.h"
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstdint>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <spawn.h>
#include <string>
#include <vector>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
#ifndef MOCKTAIL_INSTALL_LIBDIR
#define MOCKTAIL_INSTALL_LIBDIR "lib"
#endif
namespace mocktail::compat {
namespace {
constexpr const char kFreeBsdSocketHelper[] =
"mocktail_freebsd_socket_helper";
#ifndef SOL_UDP
constexpr int kSolUdp = 17;
#else
constexpr int kSolUdp = SOL_UDP;
#endif
#ifndef UDP_SEGMENT
constexpr int kUdpSegment = 103;
#else
constexpr int kUdpSegment = UDP_SEGMENT;
#endif
#ifndef UDP_GRO
constexpr int kUdpGro = 104;
#else
constexpr int kUdpGro = UDP_GRO;
#endif
bool IsFreeBsdLinuxulator() noexcept {
const int saved_errno = errno;
static const bool detected = [] {
int descriptor = ::open("/proc/version", O_RDONLY | O_CLOEXEC);
if (descriptor < 0) return false;
char version[512] = {};
const ssize_t length = ::read(descriptor, version, sizeof(version) - 1);
::close(descriptor);
if (length <= 0) return false;
version[length] = '\0';
return std::strstr(version, "FreeBSD") != nullptr ||
std::strstr(version, "freebsd.org") != nullptr;
}();
errno = saved_errno;
return detected;
}
bool IsUnsupportedSocketOptionError(int error) noexcept {
return error == ENOPROTOOPT || error == EINVAL || error == EOPNOTSUPP;
}
struct UdpSegmentControl {
bool present = false;
uint16_t segment_size = 0;
};
struct ParsedControlMessages {
UdpSegmentControl udp_segment;
std::vector<unsigned char> passthrough;
};
bool ParseControlMessages(const msghdr *message,
ParsedControlMessages *result) {
if (message == nullptr || result == nullptr) return false;
if (message->msg_controllen == 0) return true;
if (message->msg_control == nullptr) return false;
const auto *cursor =
static_cast<const unsigned char *>(message->msg_control);
std::size_t remaining = message->msg_controllen;
bool udp_segment_seen = false;
while (remaining >= sizeof(cmsghdr)) {
cmsghdr header{};
std::memcpy(&header, cursor, sizeof(header));
const std::size_t length = header.cmsg_len;
if (length < sizeof(cmsghdr) || length > remaining ||
length > SIZE_MAX - (sizeof(size_t) - 1U)) {
return false;
}
const std::size_t aligned_length = CMSG_ALIGN(length);
if (aligned_length < length) return false;
const bool udp_segment =
header.cmsg_level == kSolUdp && header.cmsg_type == kUdpSegment;
if (udp_segment) {
if (length < CMSG_LEN(sizeof(uint16_t))) return false;
if (!udp_segment_seen) {
std::memcpy(&result->udp_segment.segment_size,
cursor + CMSG_ALIGN(sizeof(cmsghdr)), sizeof(uint16_t));
result->udp_segment.present =
result->udp_segment.segment_size != 0;
udp_segment_seen = true;
}
} else {
const std::size_t old_size = result->passthrough.size();
if (aligned_length > SIZE_MAX - old_size ||
aligned_length > result->passthrough.max_size() - old_size) {
return false;
}
result->passthrough.resize(old_size + aligned_length, 0);
std::memcpy(result->passthrough.data() + old_size, cursor, length);
}
const std::size_t consumed = std::min(aligned_length, remaining);
cursor += consumed;
remaining -= consumed;
}
return true;
}
bool IovecByteCount(const msghdr *message, std::size_t *result) noexcept {
if (message == nullptr || result == nullptr ||
message->msg_iovlen > static_cast<std::size_t>(IOV_MAX) ||
(message->msg_iovlen != 0 && message->msg_iov == nullptr)) {
return false;
}
std::size_t total = 0;
for (std::size_t index = 0; index < message->msg_iovlen; ++index) {
const std::size_t length = message->msg_iov[index].iov_len;
if (length > SIZE_MAX - total) return false;
total += length;
}
*result = total;
return true;
}
std::vector<iovec> SliceIovecs(const msghdr *message, std::size_t offset,
std::size_t length) {
std::vector<iovec> result;
for (std::size_t index = 0;
index < message->msg_iovlen && length != 0; ++index) {
const iovec &source = message->msg_iov[index];
if (offset >= source.iov_len) {
offset -= source.iov_len;
continue;
}
const std::size_t available = source.iov_len - offset;
const std::size_t selected = available < length ? available : length;
auto *base = static_cast<unsigned char *>(source.iov_base) + offset;
result.push_back({base, selected});
length -= selected;
offset = 0;
}
return result;
}
bool DiscoveryModeUsesDontFragment(int mode, bool *enabled) noexcept {
if (enabled == nullptr) return false;
switch (mode) {
case IP_PMTUDISC_DONT:
*enabled = false;
return true;
case IP_PMTUDISC_WANT:
case IP_PMTUDISC_DO:
case IP_PMTUDISC_PROBE:
*enabled = true;
return true;
#ifdef IP_PMTUDISC_INTERFACE
case IP_PMTUDISC_INTERFACE:
*enabled = false;
return true;
#endif
#ifdef IP_PMTUDISC_OMIT
case IP_PMTUDISC_OMIT:
*enabled = false;
return true;
#endif
default:
return false;
}
}
std::string ResolveFreeBsdSocketHelperPath(const std::string &executable_path,
const char *override_path) {
if (override_path != nullptr && override_path[0] == '/') {
return override_path;
}
const std::size_t separator = executable_path.find_last_of('/');
if (separator == std::string::npos) return {};
const std::string executable_directory =
executable_path.substr(0, separator + 1);
const std::string adjacent = executable_directory + kFreeBsdSocketHelper;
if (::access(adjacent.c_str(), X_OK) == 0) return adjacent;
constexpr const char kInstallLibDir[] = MOCKTAIL_INSTALL_LIBDIR;
const std::string installed =
kInstallLibDir[0] == '/'
? std::string(kInstallLibDir) + "/mocktail/" +
kFreeBsdSocketHelper
: executable_directory + "../" + kInstallLibDir + "/mocktail/" +
kFreeBsdSocketHelper;
if (::access(installed.c_str(), X_OK) == 0) return installed;
return {};
}
std::string FreeBsdSocketHelperPath() {
std::array<char, PATH_MAX + 1> executable{};
const ssize_t length =
::readlink("/proc/self/exe", executable.data(), PATH_MAX);
if (length <= 0 || length > PATH_MAX) return {};
executable[static_cast<std::size_t>(length)] = '\0';
return ResolveFreeBsdSocketHelperPath(
std::string(executable.data(), static_cast<std::size_t>(length)),
std::getenv("MOCKTAIL_FREEBSD_SOCKET_HELPER"));
}
int ApplyFreeBsdPathMtuDiscovery(int socket, int discovery_mode) noexcept {
bool dont_fragment = false;
if (!DiscoveryModeUsesDontFragment(discovery_mode, &dont_fragment)) {
return -1;
}
const std::string helper = FreeBsdSocketHelperPath();
if (helper.empty() || ::access(helper.c_str(), X_OK) != 0) return -1;
const int inherited_socket = ::fcntl(socket, F_DUPFD, 3);
if (inherited_socket < 0) return -1;
std::array<char, 32> descriptor{};
const int descriptor_length = std::snprintf(
descriptor.data(), descriptor.size(), "%d", inherited_socket);
if (descriptor_length <= 0 ||
static_cast<std::size_t>(descriptor_length) >= descriptor.size()) {
::close(inherited_socket);
return -1;
}
char enabled[] = "0";
if (dont_fragment) enabled[0] = '1';
char *arguments[] = {
const_cast<char *>(kFreeBsdSocketHelper), descriptor.data(), enabled,
nullptr};
pid_t child = -1;
const int spawn_status =
::posix_spawn(&child, helper.c_str(), nullptr, nullptr, arguments,
environ);
::close(inherited_socket);
if (spawn_status != 0) return -1;
int child_status = 0;
pid_t waited = -1;
do {
waited = ::waitpid(child, &child_status, 0);
} while (waited < 0 && errno == EINTR);
if (waited != child || !WIFEXITED(child_status) ||
WEXITSTATUS(child_status) != 0) {
return -1;
}
return 0;
}
} // namespace
int BionicSetsockopt(int socket, int level, int option_name,
const void *option_value,
socklen_t option_length) noexcept {
const int previous_errno = errno;
const int result =
::setsockopt(socket, level, option_name, option_value, option_length);
if (result == -1 && errno == ENOPROTOOPT && level == IPPROTO_IP &&
option_name == IP_MTU_DISCOVER && option_value != nullptr &&
option_length == sizeof(int)) {
const int discovery_mode = *static_cast<const int *>(option_value);
if (ApplyFreeBsdPathMtuDiscovery(socket, discovery_mode) == 0) {
errno = previous_errno;
return 0;
}
bool dont_fragment = false;
if (IsFreeBsdLinuxulator() &&
DiscoveryModeUsesDontFragment(discovery_mode, &dont_fragment)) {
errno = previous_errno;
return 0;
}
errno = ENOPROTOOPT;
}
if (result == -1 && IsFreeBsdLinuxulator() &&
IsUnsupportedSocketOptionError(errno) && level == kSolUdp &&
option_name == kUdpGro && option_value != nullptr &&
option_length == sizeof(int)) {
const int enabled = *static_cast<const int *>(option_value);
if (enabled == 0 || enabled == 1) {
errno = previous_errno;
return 0;
}
}
return result;
}
ssize_t BionicSendmsg(int socket, const msghdr *message, int flags) noexcept {
if (message == nullptr) {
errno = EINVAL;
return -1;
}
const int previous_errno = errno;
const ssize_t result = ::sendmsg(socket, message, flags);
if (result != -1 || !IsFreeBsdLinuxulator() ||
!IsUnsupportedSocketOptionError(errno)) {
return result;
}
ParsedControlMessages control;
if (!ParseControlMessages(message, &control)) {
errno = EINVAL;
return -1;
}
if (!control.udp_segment.present) return result;
std::size_t total = 0;
if (!IovecByteCount(message, &total)) {
errno = EINVAL;
return -1;
}
if (total == 0) return result;
std::size_t sent_total = 0;
while (sent_total < total) {
const std::size_t segment_length =
std::min<std::size_t>(control.udp_segment.segment_size,
total - sent_total);
std::vector<iovec> iovecs =
SliceIovecs(message, sent_total, segment_length);
if (iovecs.empty()) {
errno = EINVAL;
return -1;
}
msghdr segment = *message;
segment.msg_iov = iovecs.data();
segment.msg_iovlen = iovecs.size();
segment.msg_control =
control.passthrough.empty() ? nullptr : control.passthrough.data();
segment.msg_controllen = control.passthrough.size();
const ssize_t sent = ::sendmsg(socket, &segment, flags);
if (sent < 0) return -1;
if (static_cast<std::size_t>(sent) != segment_length) {
errno = EIO;
return -1;
}
sent_total += segment_length;
}
errno = previous_errno;
return static_cast<ssize_t>(sent_total);
}
} // namespace mocktail::compat
extern "C" int mocktail_bionic_setsockopt(int socket, int level,
int option_name,
const void *option_value,
socklen_t option_length) {
return mocktail::compat::BionicSetsockopt(socket, level, option_name,
option_value, option_length);
}
extern "C" ssize_t mocktail_bionic_sendmsg(int socket,
const struct msghdr *message,
int flags) {
return mocktail::compat::BionicSendmsg(socket, message, flags);
}
+3 -2
View File
@@ -26,7 +26,8 @@ long BionicSysconf(int name) noexcept {
case BionicSysconfName::kChildMax:
return ::sysconf(_SC_CHILD_MAX);
case BionicSysconfName::kClockTicks:
return ::sysconf(_SC_CLK_TCK);
// Android/Linux USER_HZ is 100.
return 100;
case BionicSysconfName::kOpenMax:
return ::sysconf(_SC_OPEN_MAX);
case BionicSysconfName::kIovMax:
@@ -48,7 +49,7 @@ long BionicSysconf(int name) noexcept {
return -1;
}
} // namespace mocktail::compat
} // namespace mocktail::compat
extern "C" long mocktail_bionic_sysconf(int name) {
return mocktail::compat::BionicSysconf(name);
+103
View File
@@ -0,0 +1,103 @@
/*
* Copyright 2026 Mocktail Project Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* argv: inherited socket descriptor, IP_DONTFRAG value. */
.section .note.tag,"a",@note
.p2align 2
.long 8
.long 4
.long 1
.asciz "FreeBSD"
.p2align 2
.long 1500000
.text
.globl _start
.type _start,@function
_start:
cmpq $3, (%rsp)
jne .Lusage
movq 16(%rsp), %rsi
xorl %edi, %edi
.Lparse_fd:
movzbl (%rsi), %eax
testb %al, %al
je .Lfd_ready
subl $'0', %eax
cmpl $9, %eax
ja .Lusage
imull $10, %edi, %edi
addl %eax, %edi
incq %rsi
jmp .Lparse_fd
.Lfd_ready:
movq 24(%rsp), %rsi
cmpb $0, 1(%rsi)
jne .Lusage
movzbl (%rsi), %eax
cmpb $'0', %al
je .Ldisable
cmpb $'1', %al
jne .Lusage
movl $1, %eax
jmp .Lvalue_ready
.Ldisable:
xorl %eax, %eax
.Lvalue_ready:
subq $8, %rsp
movl %eax, (%rsp)
movl $105, %eax
xorl %esi, %esi
movl $67, %edx
movq %rsp, %r10
movl $4, %r8d
syscall
jnc .Lsuccess
/* Retry with IPV6_DONTFRAG for dual-stack sockets. */
movl $105, %eax
movl $41, %esi
movl $62, %edx
movq %rsp, %r10
movl $4, %r8d
syscall
jc .Lerror
.Lsuccess:
xorl %edi, %edi
jmp .Lexit
.Lerror:
movl %eax, %edi
andl $255, %edi
testl %edi, %edi
jne .Lexit
movl $1, %edi
jmp .Lexit
.Lusage:
movl $64, %edi
.Lexit:
movl $1, %eax
syscall
ud2
.size _start, .-_start
.section .note.GNU-stack,"",@progbits
+8
View File
@@ -52,7 +52,9 @@
#include <jni.h>
#include "compat/bionic_abi_exports.h"
#include "compat/bionic_prctl_runtime.h"
#include "compat/bionic_pthread_create_runtime.h"
#include "compat/bionic_socket_runtime.h"
#include "compat/build_profile.h"
#include "compat/elf_build_id.h"
#include "compat/host_abi_experiment.h"
@@ -32804,6 +32806,12 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options,
}
RegisterBionicDnsWrappers();
RegisterBionicPathWrappers();
linker::RegisterSymbol("prctl",
reinterpret_cast<void*>(mocktail_bionic_prctl));
linker::RegisterSymbol(
"setsockopt", reinterpret_cast<void*>(mocktail_bionic_setsockopt));
linker::RegisterSymbol("sendmsg",
reinterpret_cast<void*>(mocktail_bionic_sendmsg));
linker::RegisterSymbol("mprotect", reinterpret_cast<void*>(mocktail_mprotect));
linker::RegisterSymbol("pthread_condattr_init",
reinterpret_cast<void*>(mocktail_pthread_condattr_init));
+16 -2
View File
@@ -134,11 +134,13 @@ bool IsBionicLibcOnlySymbol(std::string_view symbol) {
}
return symbol == "__cmsg_nxthdr" || symbol == "__cxa_thread_atexit_impl" ||
symbol == "__readlink_chk" || symbol == "__register_atfork" ||
symbol == "arc4random_buf" || symbol == "lseek64" ||
symbol == "arc4random_buf" || symbol == "geteuid" ||
symbol == "getuid" || symbol == "lseek64" ||
symbol == "mallinfo" || symbol == "pread64" || symbol == "pwrite64" ||
symbol == "sigaction" || symbol == "strerror_r" ||
symbol == "strtoll_l" || symbol == "strtoull_l" ||
symbol == "sysconf" || HasPrefix(symbol, "pthread_") ||
symbol == "sysconf" || symbol == "sysinfo" || symbol == "uname" ||
HasPrefix(symbol, "pthread_") ||
HasPrefix(symbol, "__pthread_") || HasPrefix(symbol, "sem_");
}
@@ -360,6 +362,18 @@ void RegisterBionicHostLibcRuntimeForLibc() {
mocktail_bionic_arc4random_buf));
RegisterSyntheticSymbol("libc.so", "mallinfo",
reinterpret_cast<void*>(mocktail_bionic_mallinfo));
RegisterSyntheticSymbol("libc.so", "sysinfo",
reinterpret_cast<void*>(mocktail_bionic_sysinfo));
RegisterSyntheticSymbol("libc.so", "uname",
reinterpret_cast<void*>(mocktail_bionic_uname));
RegisterSyntheticSymbol("libc.so", "getuid",
reinterpret_cast<void*>(mocktail_bionic_getuid));
RegisterSyntheticSymbol("libc.so", "geteuid",
reinterpret_cast<void*>(mocktail_bionic_geteuid));
RegisterSymbol("sysinfo", reinterpret_cast<void*>(mocktail_bionic_sysinfo));
RegisterSymbol("uname", reinterpret_cast<void*>(mocktail_bionic_uname));
RegisterSymbol("getuid", reinterpret_cast<void*>(mocktail_bionic_getuid));
RegisterSymbol("geteuid", reinterpret_cast<void*>(mocktail_bionic_geteuid));
RegisterSyntheticSymbol("libc.so", "strerror_r",
reinterpret_cast<void*>(mocktail_bionic_strerror_r));
// Publish this before relocation to prevent binding glibc's incompatible
+8 -2
View File
@@ -63,8 +63,14 @@ std::filesystem::path UpdateHelper(const Environment& environment) {
if (!root.is_absolute()) {
return {};
}
const std::filesystem::path helper = root / "build" / "mocktail_updater";
return IsExecutableRegularFile(helper) ? helper : std::filesystem::path{};
for (const std::filesystem::path& helper :
{root / "build" / "mocktail_updater",
root / "bin" / "mocktail_updater"}) {
if (IsExecutableRegularFile(helper)) {
return helper;
}
}
return {};
}
std::error_code error;
+179 -33
View File
@@ -15,6 +15,8 @@
#include "runtime/runtime_paths.h"
#define JSON_NOEXCEPTION 1
#include <array>
#include <atomic>
#include <cctype>
#include <cstdint>
#include <cstdlib>
@@ -22,6 +24,8 @@
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <unistd.h>
#include <utility>
namespace mocktail {
namespace runtime {
@@ -170,6 +174,129 @@ bool SetEnvironmentDefault(const char* name,
return false;
}
struct ManagedPayloadBinding {
std::filesystem::path target;
std::filesystem::path link;
const char* name;
bool directory;
};
bool ResolveManagedPayloadBinding(ManagedPayloadBinding* binding,
std::string* error) {
std::error_code filesystem_error;
const std::filesystem::file_status target_status =
std::filesystem::symlink_status(binding->target, filesystem_error);
const bool expected_type =
binding->directory ? std::filesystem::is_directory(target_status)
: std::filesystem::is_regular_file(target_status);
if (filesystem_error || !expected_type ||
std::filesystem::is_symlink(target_status)) {
if (error != nullptr) {
*error = "managed payload " + std::string(binding->name) +
" target is missing, invalid, or a symlink: " +
binding->target.string();
}
return false;
}
binding->target =
std::filesystem::canonical(binding->target, filesystem_error);
if (filesystem_error) {
if (error != nullptr) {
*error = "cannot canonicalize managed payload " +
std::string(binding->name) + " target: " +
binding->target.string();
}
return false;
}
const std::filesystem::file_status link_status =
std::filesystem::symlink_status(binding->link, filesystem_error);
if (filesystem_error == std::errc::no_such_file_or_directory) {
filesystem_error.clear();
return true;
}
if (!filesystem_error &&
link_status.type() == std::filesystem::file_type::not_found) {
return true;
}
if (filesystem_error) {
if (error != nullptr) {
*error = "cannot inspect managed payload " +
std::string(binding->name) + " path: " +
binding->link.string();
}
return false;
}
if (!std::filesystem::is_symlink(link_status)) {
if (error != nullptr) {
*error = "refusing to replace non-symlink managed payload " +
std::string(binding->name) + " path: " +
binding->link.string();
}
return false;
}
return true;
}
bool PublishManagedPayloadBinding(const ManagedPayloadBinding& binding,
std::string* error) {
std::error_code filesystem_error;
const std::filesystem::path current_target =
std::filesystem::read_symlink(binding.link, filesystem_error);
if (!filesystem_error && current_target == binding.target) {
return true;
}
if (filesystem_error == std::errc::no_such_file_or_directory) {
filesystem_error.clear();
} else if (filesystem_error) {
if (error != nullptr) {
*error = "cannot read managed payload " + std::string(binding.name) +
" link: " + binding.link.string();
}
return false;
}
static std::atomic_uint64_t sequence{0};
const std::filesystem::path temporary_link =
binding.link.string() + ".mocktail-next-" +
std::to_string(static_cast<unsigned long long>(getpid())) + "-" +
std::to_string(sequence.fetch_add(1, std::memory_order_relaxed));
const std::filesystem::file_status temporary_status =
std::filesystem::symlink_status(temporary_link, filesystem_error);
if (filesystem_error == std::errc::no_such_file_or_directory ||
(!filesystem_error && temporary_status.type() ==
std::filesystem::file_type::not_found)) {
filesystem_error.clear();
} else {
if (error != nullptr) {
*error = "managed payload temporary link already exists: " +
temporary_link.string();
}
return false;
}
if (binding.directory) {
std::filesystem::create_directory_symlink(binding.target, temporary_link,
filesystem_error);
} else {
std::filesystem::create_symlink(binding.target, temporary_link,
filesystem_error);
}
if (!filesystem_error) {
std::filesystem::rename(temporary_link, binding.link, filesystem_error);
}
if (filesystem_error) {
std::error_code cleanup_error;
std::filesystem::remove(temporary_link, cleanup_error);
if (error != nullptr) {
*error = "cannot publish managed payload " + std::string(binding.name) +
" link: " + binding.link.string();
}
return false;
}
return true;
}
} // namespace
RuntimePaths RuntimePaths::FromEnvironment(
@@ -329,6 +456,10 @@ ActivePayloadPaths RuntimePaths::ResolveActivePayload() const {
}
const std::filesystem::path library = canonical_root / "libroblox.so";
const std::filesystem::path package_root = canonical_root / "sober_apk";
const std::filesystem::path base_apk = package_root / "base.apk";
const std::filesystem::path x86_64_split_apk =
package_root / "split_config.x86_64.apk";
const std::filesystem::path assets_root = canonical_root / "assets";
const std::filesystem::path assets = canonical_root / "assets/content";
const std::filesystem::file_status library_status =
@@ -338,6 +469,27 @@ ActivePayloadPaths RuntimePaths::ResolveActivePayload() const {
result.error = "active payload libroblox.so is missing or invalid";
return result;
}
const std::filesystem::file_status package_root_status =
std::filesystem::symlink_status(package_root, filesystem_error);
if (filesystem_error ||
!std::filesystem::is_directory(package_root_status) ||
std::filesystem::is_symlink(package_root_status)) {
result.error = "active payload sober_apk root is missing or invalid";
return result;
}
for (const auto& [path, name] :
std::array<std::pair<std::filesystem::path, const char*>, 2>{
std::pair{base_apk, "base.apk"},
std::pair{x86_64_split_apk, "split_config.x86_64.apk"}}) {
const std::filesystem::file_status status =
std::filesystem::symlink_status(path, filesystem_error);
if (filesystem_error || !std::filesystem::is_regular_file(status) ||
std::filesystem::is_symlink(status)) {
result.error = "active payload " + std::string(name) +
" is missing or invalid";
return result;
}
}
const std::filesystem::file_status assets_root_status =
std::filesystem::symlink_status(assets_root, filesystem_error);
if (filesystem_error || !std::filesystem::is_directory(assets_root_status) ||
@@ -356,6 +508,8 @@ ActivePayloadPaths RuntimePaths::ResolveActivePayload() const {
result.active = true;
result.root = canonical_root;
result.roblox_library = library;
result.base_apk = base_apk;
result.x86_64_split_apk = x86_64_split_apk;
result.assets_content = assets;
const auto compatibility_manifest =
@@ -488,45 +642,37 @@ bool PrepareManagedPayloadWorkingDirectory(const RuntimePaths& paths,
}
return false;
}
const std::filesystem::path assets_link = rbx_root / "assets";
const std::filesystem::file_status link_status =
std::filesystem::symlink_status(assets_link, filesystem_error);
if (filesystem_error == std::errc::no_such_file_or_directory) {
filesystem_error.clear();
} else if (filesystem_error) {
const std::filesystem::file_status rbx_root_status =
std::filesystem::symlink_status(rbx_root, filesystem_error);
if (filesystem_error || !std::filesystem::is_directory(rbx_root_status) ||
std::filesystem::is_symlink(rbx_root_status)) {
if (error != nullptr) {
*error =
"cannot inspect managed payload asset link: " + assets_link.string();
}
return false;
} else if (std::filesystem::is_symlink(link_status)) {
std::filesystem::remove(assets_link, filesystem_error);
} else if (!std::filesystem::is_directory(link_status)) {
if (error != nullptr) {
*error = "managed payload asset path is not a directory: " +
assets_link.string();
}
return false;
} else {
std::filesystem::current_path(paths.data_root(), filesystem_error);
if (!filesystem_error) {
return true;
}
}
if (filesystem_error) {
if (error != nullptr) {
*error =
"cannot replace managed payload asset link: " + assets_link.string();
*error = "managed payload rbx_bin root is invalid or a symlink: " +
rbx_root.string();
}
return false;
}
std::filesystem::create_directory_symlink(active.root / "assets", assets_link,
filesystem_error);
if (!filesystem_error) {
std::filesystem::current_path(paths.data_root(), filesystem_error);
std::array<ManagedPayloadBinding, 3> bindings{{
{active.root / "assets", rbx_root / "assets", "assets", true},
{active.root / "sober_apk", rbx_root / "sober_apk", "APK directory",
true},
{active.root / "libroblox.so", rbx_root / "libroblox.so",
"native library", false},
}};
// Validate all destinations before publishing any of them.
for (ManagedPayloadBinding& binding : bindings) {
if (!ResolveManagedPayloadBinding(&binding, error)) {
return false;
}
}
for (const ManagedPayloadBinding& binding : bindings) {
if (!PublishManagedPayloadBinding(binding, error)) {
return false;
}
}
std::filesystem::current_path(paths.data_root(), filesystem_error);
if (filesystem_error) {
if (error != nullptr) {
*error = "cannot activate managed payload working directory: " +
+3 -1
View File
@@ -964,7 +964,7 @@ add_test(
)
find_program(MOCKTAIL_PATCHELF_EXECUTABLE patchelf)
if(MOCKTAIL_PATCHELF_EXECUTABLE)
if(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER AND MOCKTAIL_PATCHELF_EXECUTABLE)
add_test(
NAME PortablePackagingTest.RelocatableReadOnlyBundle
COMMAND ${CMAKE_COMMAND} -E env
@@ -1003,8 +1003,10 @@ add_test(
${CMAKE_SOURCE_DIR}
)
if(MOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER)
add_test(
NAME SystemInstallTest.DynamicRuntimeLayout
COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/system_install_test.sh
${CMAKE_BINARY_DIR}
)
endif()
+86 -13
View File
@@ -53,8 +53,8 @@ struct DestructorProbe {
std::atomic<int> calls{0};
};
void RecordThreadDestructor(void* argument) {
auto* probe = static_cast<DestructorProbe*>(argument);
void RecordThreadDestructor(void *argument) {
auto *probe = static_cast<DestructorProbe *>(argument);
probe->calls.fetch_add(1, std::memory_order_relaxed);
}
@@ -68,10 +68,10 @@ TEST(BionicHostLibcRuntimeTest, WalksValidControlMessages) {
message.msg_control = control.data();
message.msg_controllen = control.size();
auto* first = reinterpret_cast<Header*>(control.data());
auto *first = reinterpret_cast<Header *>(control.data());
first->cmsg_len = ControlMessageLength(kPayloadSize);
auto* second = reinterpret_cast<Header*>(control.data() +
ControlMessageSpace(kPayloadSize));
auto *second = reinterpret_cast<Header *>(control.data() +
ControlMessageSpace(kPayloadSize));
second->cmsg_len = ControlMessageLength(kPayloadSize);
EXPECT_EQ(mocktail_bionic_cmsg_nxthdr(&message, first), second);
@@ -84,7 +84,7 @@ TEST(BionicHostLibcRuntimeTest, RejectsMalformedControlMessages) {
mocktail::compat::BionicMessageHeader message{};
message.msg_control = control.data();
message.msg_controllen = control.size();
auto* header = reinterpret_cast<Header*>(control.data());
auto *header = reinterpret_cast<Header *>(control.data());
header->cmsg_len = sizeof(Header) - 1;
EXPECT_EQ(mocktail_bionic_cmsg_nxthdr(&message, header), nullptr);
@@ -145,6 +145,80 @@ TEST(BionicHostLibcRuntimeTest, ReturnsAbiSizedAllocatorSnapshot) {
}
}
TEST(BionicHostLibcRuntimeTest, PreservesValidSysInfoCounters) {
struct sysinfo info{};
info.totalram = 8UL * 1024UL * 1024UL * 1024UL;
info.freeram = 2UL * 1024UL * 1024UL * 1024UL;
info.totalswap = 4UL * 1024UL * 1024UL * 1024UL;
info.freeswap = 1UL * 1024UL * 1024UL * 1024UL;
mocktail::compat::NormalizeBionicSysInfo(&info);
EXPECT_EQ(info.totalram, 8UL * 1024UL * 1024UL * 1024UL);
EXPECT_EQ(info.freeram, 2UL * 1024UL * 1024UL * 1024UL);
EXPECT_EQ(info.totalswap, 4UL * 1024UL * 1024UL * 1024UL);
EXPECT_EQ(info.freeswap, 1UL * 1024UL * 1024UL * 1024UL);
}
TEST(BionicHostLibcRuntimeTest, ClearsLinuxulatorUnderflowedSwapCounters) {
struct sysinfo info{};
info.totalram = 8UL * 1024UL * 1024UL * 1024UL;
info.freeram = 2UL * 1024UL * 1024UL * 1024UL;
info.totalswap = std::numeric_limits<unsigned long>::max() - 40959UL;
info.freeswap = std::numeric_limits<unsigned long>::max() - 11550719UL;
mocktail::compat::NormalizeBionicSysInfo(&info);
EXPECT_EQ(info.totalram, 8UL * 1024UL * 1024UL * 1024UL);
EXPECT_EQ(info.freeram, 2UL * 1024UL * 1024UL * 1024UL);
EXPECT_EQ(info.totalswap, 0UL);
EXPECT_EQ(info.freeswap, 0UL);
}
TEST(BionicHostLibcRuntimeTest, CallsHostSysInfoThroughBionicAdapter) {
struct sysinfo info{};
ASSERT_EQ(mocktail_bionic_sysinfo(&info), 0);
EXPECT_GT(info.totalram, 0UL);
EXPECT_LE(info.freeswap, info.totalswap);
}
TEST(BionicHostLibcRuntimeTest, NormalizesContradictoryLinuxulatorUname) {
struct utsname name{};
std::snprintf(name.sysname, sizeof(name.sysname), "Linux");
std::snprintf(name.release, sizeof(name.release), "5.15.0");
std::snprintf(name.version, sizeof(name.version),
"FreeBSD 15.1-RELEASE-p2 GENERIC");
EXPECT_TRUE(mocktail::compat::NormalizeLinuxulatorUnameVersion(
&name, "#4 SMP PREEMPT_DYNAMIC"));
EXPECT_STREQ(name.sysname, "Linux");
EXPECT_STREQ(name.release, "5.15.0");
EXPECT_STREQ(name.version, "#4 SMP PREEMPT_DYNAMIC");
}
TEST(BionicHostLibcRuntimeTest, PreservesNativeLinuxUname) {
struct utsname name{};
std::snprintf(name.sysname, sizeof(name.sysname), "Linux");
std::snprintf(name.version, sizeof(name.version), "#1 SMP PREEMPT_DYNAMIC");
EXPECT_FALSE(mocktail::compat::NormalizeLinuxulatorUnameVersion(
&name, "#4 SMP PREEMPT_DYNAMIC"));
EXPECT_STREQ(name.version, "#1 SMP PREEMPT_DYNAMIC");
ASSERT_EQ(mocktail_bionic_uname(&name), 0);
EXPECT_STREQ(name.sysname, "Linux");
}
TEST(BionicHostLibcRuntimeTest, HidesPrivilegedHostIdentityFromAndroidGuest) {
EXPECT_EQ(mocktail::compat::NormalizeBionicApplicationUid(0), 1000U);
EXPECT_EQ(mocktail::compat::NormalizeBionicApplicationUid(1000), 1000U);
EXPECT_EQ(mocktail::compat::NormalizeBionicApplicationUid(12345), 12345U);
EXPECT_EQ(mocktail_bionic_getuid(),
mocktail::compat::NormalizeBionicApplicationUid(::getuid()));
EXPECT_EQ(mocktail_bionic_geteuid(),
mocktail::compat::NormalizeBionicApplicationUid(::geteuid()));
}
TEST(BionicHostLibcRuntimeTest, UsesBionicPosixStrErrorContract) {
std::array<char, 128> buffer{};
@@ -175,7 +249,7 @@ TEST(BionicHostLibcRuntimeTest, ParsesIntegersInScopedCLocale) {
auto guest_locale =
reinterpret_cast<mocktail::compat::BionicLocale>(uintptr_t{1});
char* signed_end = nullptr;
char *signed_end = nullptr;
errno = EDOM;
EXPECT_EQ(
mocktail_bionic_strtoll_l("-0x7f-rest", &signed_end, 0, guest_locale),
@@ -183,7 +257,7 @@ TEST(BionicHostLibcRuntimeTest, ParsesIntegersInScopedCLocale) {
EXPECT_STREQ(signed_end, "-rest");
EXPECT_EQ(errno, EDOM);
char* unsigned_end = nullptr;
char *unsigned_end = nullptr;
errno = 0;
EXPECT_EQ(mocktail_bionic_strtoull_l("18446744073709551616!", &unsigned_end,
10, guest_locale),
@@ -215,12 +289,11 @@ TEST(BionicHostLibcRuntimeTest, FortifiedReadlinkAbortsAboveSsizeLimit) {
EXPECT_DEATH(
{
char buffer{};
mocktail___readlink_chk(
"/proc/self/exe", &buffer,
static_cast<size_t>(SSIZE_MAX) + size_t{1},
std::numeric_limits<size_t>::max());
mocktail___readlink_chk("/proc/self/exe", &buffer,
static_cast<size_t>(SSIZE_MAX) + size_t{1},
std::numeric_limits<size_t>::max());
},
"FORTIFY: readlink");
}
} // namespace
} // namespace
+8 -9
View File
@@ -16,21 +16,20 @@ TEST(BionicSysconfTest, TranslatesPageSizeAliases) {
EXPECT_EQ(BionicSysconf(static_cast<int>(BionicSysconfName::kPageSize)),
host_page_size);
EXPECT_EQ(
BionicSysconf(static_cast<int>(BionicSysconfName::kPageSizeAlias)),
host_page_size);
EXPECT_EQ(BionicSysconf(static_cast<int>(BionicSysconfName::kPageSizeAlias)),
host_page_size);
EXPECT_GT(host_page_size, 0);
EXPECT_EQ(host_page_size & (host_page_size - 1), 0);
}
TEST(BionicSysconfTest, TranslatesObservedCapacityNames) {
EXPECT_EQ(BionicSysconf(static_cast<int>(BionicSysconfName::kClockTicks)),
::sysconf(_SC_CLK_TCK));
100);
EXPECT_EQ(BionicSysconf(static_cast<int>(BionicSysconfName::kIovMax)),
::sysconf(_SC_IOV_MAX));
EXPECT_EQ(BionicSysconf(
static_cast<int>(BionicSysconfName::kProcessorsConfigured)),
::sysconf(_SC_NPROCESSORS_CONF));
EXPECT_EQ(
BionicSysconf(static_cast<int>(BionicSysconfName::kProcessorsConfigured)),
::sysconf(_SC_NPROCESSORS_CONF));
EXPECT_EQ(
BionicSysconf(static_cast<int>(BionicSysconfName::kProcessorsOnline)),
::sysconf(_SC_NPROCESSORS_ONLN));
@@ -45,5 +44,5 @@ TEST(BionicSysconfTest, RejectsUnknownBionicName) {
EXPECT_EQ(errno, EINVAL);
}
} // namespace
} // namespace mocktail::compat
} // namespace
} // namespace mocktail::compat
+1
View File
@@ -98,6 +98,7 @@ assert project_sources == [
]
mocktail_options = set(modules["mocktail"]["config-opts"])
assert "-DBUILD_TESTING=OFF" in mocktail_options
assert "-DMOCKTAIL_BUILD_FREEBSD_SOCKET_HELPER=OFF" in mocktail_options
assert "-DCMAKE_INSTALL_LIBDIR=lib" in mocktail_options
assert (
"-DMOCKTAIL_DEFAULT_COMPATIBILITY_MANIFEST="
+57 -45
View File
@@ -33,8 +33,8 @@ TEST(LinkerRegistryTest, KeepsSyntheticExportsOwnedBySoname) {
linker::RegisterSyntheticSymbol("libmocktail_test_log.so", "sharedName",
&log_export);
linker::SymbolMap android_symbols = linker::GetSyntheticLibrarySymbols(
"libmocktail_test_android.so");
linker::SymbolMap android_symbols =
linker::GetSyntheticLibrarySymbols("libmocktail_test_android.so");
linker::SymbolMap log_symbols =
linker::GetSyntheticLibrarySymbols("libmocktail_test_log.so");
@@ -64,31 +64,31 @@ TEST(LinkerRegistryTest, KeepsBionicPthreadKeyAbiOwnedByLibc) {
const linker::SymbolMap symbols =
linker::GetSyntheticLibrarySymbols("libc.so");
EXPECT_EQ(symbols.at("pthread_key_create"),
reinterpret_cast<void*>(mocktail_bionic_pthread_key_create));
reinterpret_cast<void *>(mocktail_bionic_pthread_key_create));
EXPECT_EQ(symbols.at("pthread_key_delete"),
reinterpret_cast<void*>(mocktail_bionic_pthread_key_delete));
reinterpret_cast<void *>(mocktail_bionic_pthread_key_delete));
EXPECT_EQ(symbols.at("pthread_getspecific"),
reinterpret_cast<void*>(mocktail_bionic_pthread_getspecific));
reinterpret_cast<void *>(mocktail_bionic_pthread_getspecific));
EXPECT_EQ(symbols.at("pthread_setspecific"),
reinterpret_cast<void*>(mocktail_bionic_pthread_setspecific));
reinterpret_cast<void *>(mocktail_bionic_pthread_setspecific));
EXPECT_EQ(symbols.at("sem_init"),
reinterpret_cast<void*>(mocktail_bionic_sem_init));
reinterpret_cast<void *>(mocktail_bionic_sem_init));
EXPECT_EQ(symbols.at("sem_destroy"),
reinterpret_cast<void*>(mocktail_bionic_sem_destroy));
reinterpret_cast<void *>(mocktail_bionic_sem_destroy));
EXPECT_EQ(symbols.at("sem_wait"),
reinterpret_cast<void*>(mocktail_bionic_sem_wait));
reinterpret_cast<void *>(mocktail_bionic_sem_wait));
EXPECT_EQ(symbols.at("sem_post"),
reinterpret_cast<void*>(mocktail_bionic_sem_post));
reinterpret_cast<void *>(mocktail_bionic_sem_post));
EXPECT_EQ(symbols.at("pthread_rwlock_init"),
reinterpret_cast<void*>(mocktail_bionic_pthread_rwlock_init));
reinterpret_cast<void *>(mocktail_bionic_pthread_rwlock_init));
EXPECT_EQ(symbols.at("pthread_rwlock_destroy"),
reinterpret_cast<void*>(mocktail_bionic_pthread_rwlock_destroy));
reinterpret_cast<void *>(mocktail_bionic_pthread_rwlock_destroy));
EXPECT_EQ(symbols.at("pthread_rwlock_rdlock"),
reinterpret_cast<void*>(mocktail_bionic_pthread_rwlock_rdlock));
reinterpret_cast<void *>(mocktail_bionic_pthread_rwlock_rdlock));
EXPECT_EQ(symbols.at("pthread_rwlock_wrlock"),
reinterpret_cast<void*>(mocktail_bionic_pthread_rwlock_wrlock));
reinterpret_cast<void *>(mocktail_bionic_pthread_rwlock_wrlock));
EXPECT_EQ(symbols.at("pthread_rwlock_unlock"),
reinterpret_cast<void*>(mocktail_bionic_pthread_rwlock_unlock));
reinterpret_cast<void *>(mocktail_bionic_pthread_rwlock_unlock));
}
TEST(LinkerRegistryTest, KeepsBionicAtForkAbiOwnedByLibc) {
@@ -97,7 +97,7 @@ TEST(LinkerRegistryTest, KeepsBionicAtForkAbiOwnedByLibc) {
const linker::SymbolMap symbols =
linker::GetSyntheticLibrarySymbols("libc.so");
EXPECT_EQ(symbols.at("__register_atfork"),
reinterpret_cast<void*>(mocktail_bionic_register_atfork));
reinterpret_cast<void *>(mocktail_bionic_register_atfork));
}
TEST(LinkerRegistryTest, KeepsHostIndependentBionicAbiOwnedByLibc) {
@@ -106,36 +106,51 @@ TEST(LinkerRegistryTest, KeepsHostIndependentBionicAbiOwnedByLibc) {
const linker::SymbolMap symbols =
linker::GetSyntheticLibrarySymbols("libc.so");
EXPECT_EQ(symbols.at("getaddrinfo"),
reinterpret_cast<void*>(mocktail_bionic_getaddrinfo));
reinterpret_cast<void *>(mocktail_bionic_getaddrinfo));
EXPECT_EQ(symbols.at("freeaddrinfo"),
reinterpret_cast<void*>(mocktail_bionic_freeaddrinfo));
reinterpret_cast<void *>(mocktail_bionic_freeaddrinfo));
EXPECT_EQ(symbols.at("gethostbyname"),
reinterpret_cast<void*>(mocktail_bionic_gethostbyname));
reinterpret_cast<void *>(mocktail_bionic_gethostbyname));
EXPECT_EQ(symbols.at("__cmsg_nxthdr"),
reinterpret_cast<void*>(mocktail_bionic_cmsg_nxthdr));
EXPECT_EQ(
symbols.at("__cxa_thread_atexit_impl"),
reinterpret_cast<void*>(mocktail_bionic_cxa_thread_atexit_impl));
reinterpret_cast<void *>(mocktail_bionic_cmsg_nxthdr));
EXPECT_EQ(symbols.at("__cxa_thread_atexit_impl"),
reinterpret_cast<void *>(mocktail_bionic_cxa_thread_atexit_impl));
EXPECT_EQ(symbols.at("__readlink_chk"),
reinterpret_cast<void*>(mocktail___readlink_chk));
reinterpret_cast<void *>(mocktail___readlink_chk));
EXPECT_EQ(symbols.at("arc4random_buf"),
reinterpret_cast<void*>(mocktail_bionic_arc4random_buf));
reinterpret_cast<void *>(mocktail_bionic_arc4random_buf));
EXPECT_EQ(symbols.at("mallinfo"),
reinterpret_cast<void*>(mocktail_bionic_mallinfo));
reinterpret_cast<void *>(mocktail_bionic_mallinfo));
EXPECT_EQ(symbols.at("sysinfo"),
reinterpret_cast<void *>(mocktail_bionic_sysinfo));
EXPECT_EQ(symbols.at("uname"),
reinterpret_cast<void *>(mocktail_bionic_uname));
EXPECT_EQ(symbols.at("getuid"),
reinterpret_cast<void *>(mocktail_bionic_getuid));
EXPECT_EQ(symbols.at("geteuid"),
reinterpret_cast<void *>(mocktail_bionic_geteuid));
EXPECT_EQ(symbols.at("strerror_r"),
reinterpret_cast<void*>(mocktail_bionic_strerror_r));
reinterpret_cast<void *>(mocktail_bionic_strerror_r));
EXPECT_EQ(symbols.at("strtoll_l"),
reinterpret_cast<void*>(mocktail_bionic_strtoll_l));
reinterpret_cast<void *>(mocktail_bionic_strtoll_l));
EXPECT_EQ(symbols.at("strtoull_l"),
reinterpret_cast<void*>(mocktail_bionic_strtoull_l));
reinterpret_cast<void *>(mocktail_bionic_strtoull_l));
EXPECT_EQ(linker::GetBionicSymbols().at("strerror_r"),
reinterpret_cast<void*>(mocktail_bionic_strerror_r));
reinterpret_cast<void *>(mocktail_bionic_strerror_r));
EXPECT_EQ(linker::GetBionicSymbols().at("getaddrinfo"),
reinterpret_cast<void*>(mocktail_bionic_getaddrinfo));
reinterpret_cast<void *>(mocktail_bionic_getaddrinfo));
EXPECT_EQ(linker::GetBionicSymbols().at("freeaddrinfo"),
reinterpret_cast<void*>(mocktail_bionic_freeaddrinfo));
reinterpret_cast<void *>(mocktail_bionic_freeaddrinfo));
EXPECT_EQ(linker::GetBionicSymbols().at("gethostbyname"),
reinterpret_cast<void*>(mocktail_bionic_gethostbyname));
reinterpret_cast<void *>(mocktail_bionic_gethostbyname));
EXPECT_EQ(linker::GetBionicSymbols().at("getuid"),
reinterpret_cast<void *>(mocktail_bionic_getuid));
EXPECT_EQ(linker::GetBionicSymbols().at("geteuid"),
reinterpret_cast<void *>(mocktail_bionic_geteuid));
EXPECT_EQ(linker::GetBionicSymbols().at("sysinfo"),
reinterpret_cast<void *>(mocktail_bionic_sysinfo));
EXPECT_EQ(linker::GetBionicSymbols().at("uname"),
reinterpret_cast<void *>(mocktail_bionic_uname));
}
TEST(LinkerRegistryTest, KeepsBionicLargeFileAbiOwnedByLibc) {
@@ -144,11 +159,11 @@ TEST(LinkerRegistryTest, KeepsBionicLargeFileAbiOwnedByLibc) {
const linker::SymbolMap symbols =
linker::GetSyntheticLibrarySymbols("libc.so");
EXPECT_EQ(symbols.at("pread64"),
reinterpret_cast<void*>(mocktail_bionic_pread64));
reinterpret_cast<void *>(mocktail_bionic_pread64));
EXPECT_EQ(symbols.at("pwrite64"),
reinterpret_cast<void*>(mocktail_bionic_pwrite64));
reinterpret_cast<void *>(mocktail_bionic_pwrite64));
EXPECT_EQ(symbols.at("lseek64"),
reinterpret_cast<void*>(mocktail_bionic_lseek64));
reinterpret_cast<void *>(mocktail_bionic_lseek64));
}
TEST(LinkerRegistryTest, KeepsBionicSysconfAbiOwnedByLibc) {
@@ -157,7 +172,7 @@ TEST(LinkerRegistryTest, KeepsBionicSysconfAbiOwnedByLibc) {
const linker::SymbolMap symbols =
linker::GetSyntheticLibrarySymbols("libc.so");
EXPECT_EQ(symbols.at("sysconf"),
reinterpret_cast<void*>(mocktail_bionic_sysconf));
reinterpret_cast<void *>(mocktail_bionic_sysconf));
}
TEST(LinkerRegistryTest, KeepsBionicStdioAbiOwnedByLibc) {
@@ -167,18 +182,15 @@ TEST(LinkerRegistryTest, KeepsBionicStdioAbiOwnedByLibc) {
linker::GetSyntheticLibrarySymbols("libc.so");
EXPECT_EQ(symbols.at("__sF"),
mocktail::compat::BionicFileArraySymbolAddress());
EXPECT_EQ(symbols.at("stdin"),
mocktail::compat::BionicStdinSymbolAddress());
EXPECT_EQ(symbols.at("stdin"), mocktail::compat::BionicStdinSymbolAddress());
EXPECT_EQ(symbols.at("stdout"),
mocktail::compat::BionicStdoutSymbolAddress());
EXPECT_EQ(symbols.at("stderr"),
mocktail::compat::BionicStderrSymbolAddress());
EXPECT_EQ(symbols.at("fflush"),
reinterpret_cast<void*>(mocktail_fflush));
EXPECT_EQ(symbols.at("fscanf"),
reinterpret_cast<void*>(mocktail_fscanf));
EXPECT_EQ(symbols.at("fflush"), reinterpret_cast<void *>(mocktail_fflush));
EXPECT_EQ(symbols.at("fscanf"), reinterpret_cast<void *>(mocktail_fscanf));
EXPECT_EQ(symbols.at("__fwrite_chk"),
reinterpret_cast<void*>(mocktail___fwrite_chk));
reinterpret_cast<void *>(mocktail___fwrite_chk));
}
TEST(LinkerRegistryTest, KeepsBionicDynamicLoaderOwnedByLibdl) {
@@ -211,4 +223,4 @@ TEST(LinkerRegistryTest, WithholdsEveryCrashpadJniEntrypoint) {
"nativeAppBridgeV2InitWithParams"));
}
} // namespace
} // namespace
+21
View File
@@ -104,6 +104,27 @@ TEST(PayloadUpdatePreflightTest, RunsNativeUpdater) {
EXPECT_TRUE(result.attempted);
}
TEST(PayloadUpdatePreflightTest, RunsPackagedUpdaterFromProjectBin) {
TemporaryDirectory temporary;
const std::filesystem::path project_root = temporary.root() / "portable";
const std::filesystem::path helper = project_root / "bin/mocktail_updater";
ASSERT_TRUE(std::filesystem::create_directories(helper.parent_path()));
ASSERT_TRUE(std::filesystem::copy_file(MOCKTAIL_TEST_UPDATE_HELPER, helper));
std::filesystem::permissions(
helper,
std::filesystem::perms::owner_exec | std::filesystem::perms::group_exec |
std::filesystem::perms::others_exec,
std::filesystem::perm_options::add);
const MapEnvironment environment(
{{"MOCKTAIL_PROJECT_ROOT", project_root.string()}});
const RuntimePaths paths = RuntimePaths::FromEnvironment(environment);
const auto result = RunPayloadUpdatePreflight(environment, paths);
ASSERT_TRUE(result) << result.error;
EXPECT_TRUE(result.attempted);
}
TEST(PayloadUpdatePreflightTest, StreamsUpdaterStagesToNativeDialog) {
TemporaryDirectory temporary;
const std::filesystem::path capture = temporary.root() / "progress";
+10 -1
View File
@@ -23,7 +23,7 @@ grep -q '^artifact_name=mocktail-linux-x86_64-glibc-standalone$' \
<<<"${dry_run}"
grep -q '^interpreter=.*/ld-linux-x86-64\.so\.2$' <<<"${dry_run}"
grep -q '^android_tools_libc=glibc$' <<<"${dry_run}"
grep -q '^project_artifacts=16$' <<<"${dry_run}"
grep -q '^project_artifacts=17$' <<<"${dry_run}"
grep -q 'host=libc\.so\.6 reason=glibc' <<<"${dry_run}"
grep -q 'host=libEGL\.so\.1 reason=gpu-driver-stack' <<<"${dry_run}"
! grep -q 'host=libwebkitgtk-6\.0\.so\.4' <<<"${dry_run}"
@@ -114,8 +114,10 @@ RefreshBundleChecksums() {
for path in \
run.sh \
mocktail/bin/mocktail \
mocktail/bin/mocktail_updater \
mocktail/bin/mocktail_failure_dialog \
mocktail/bin/mocktail_webview_helper \
mocktail/bin/mocktail_freebsd_socket_helper \
mocktail/bin/libEGL.so \
mocktail/bin/libvulkan.so \
mocktail/runtime \
@@ -164,6 +166,11 @@ runtime_entries="$(find "${runtime}" -mindepth 1 -maxdepth 1 \
mocktail/metadata/SHA256SUMS.txt)
"${runtime}/runtime/android-tools/bin/aapt" version >/dev/null
"${runtime}/runtime/android-tools/bin/apksigner" version >/dev/null
LC_ALL=C readelf -h "${runtime}/bin/mocktail_freebsd_socket_helper" |
grep -Fq 'UNIX - FreeBSD'
[[ -z "$(LC_ALL=C readelf -l \
"${runtime}/bin/mocktail_freebsd_socket_helper" |
sed -n 's/.*Requesting program interpreter: \([^]]*\).*/\1/p')" ]]
[[ "$(stat -c '%a' "${bundle}")" == 755 ]]
packaged_root_entries="$(find "${bundle}" -mindepth 1 -maxdepth 1 \
-printf '%f\n' | LC_ALL=C sort)"
@@ -262,6 +269,8 @@ int main(void) {
EOF
cp -- "${musl_runtime}/bin/mocktail" \
"${musl_runtime}/bin/mocktail_failure_dialog"
cp -- "${musl_runtime}/bin/mocktail" \
"${musl_runtime}/bin/mocktail_updater"
cp -- "${musl_runtime}/bin/mocktail" \
"${musl_runtime}/bin/mocktail_webview_helper"
for tool in aapt apkanalyzer apksigner; do
+116 -7
View File
@@ -102,6 +102,15 @@ class ScopedCurrentPath {
std::filesystem::path original_;
};
void WriteManagedPayloadFiles(const std::filesystem::path& payload) {
ASSERT_TRUE(RuntimePaths::EnsureDirectory(payload / "assets/content"));
ASSERT_TRUE(RuntimePaths::EnsureDirectory(payload / "sober_apk"));
std::ofstream(payload / "libroblox.so") << "ELF fixture";
std::ofstream(payload / "sober_apk/base.apk") << "base APK fixture";
std::ofstream(payload / "sober_apk/split_config.x86_64.apk")
<< "x86_64 split APK fixture";
}
TEST(RuntimePathsTest, DerivesXdgDefaults) {
const MapEnvironment environment({{"HOME", "/home/mocktail"}});
const RuntimePaths paths =
@@ -162,8 +171,7 @@ TEST(RuntimePathsTest, ResolvesValidatedActivePayload) {
const std::string payload_id =
"2546-d0cb1fa0deb3d9161b4cd77530cbcd2e50de3a21";
const std::filesystem::path payload = data / "payloads" / payload_id;
ASSERT_TRUE(RuntimePaths::EnsureDirectory(payload / "assets/content"));
std::ofstream(payload / "libroblox.so") << "ELF fixture";
WriteManagedPayloadFiles(payload);
std::ofstream(data / "current.json")
<< "{\"schema_version\":1,\"payload_id\":\"" << payload_id
<< "\",\"payload_path\":\"payloads/" << payload_id
@@ -182,10 +190,41 @@ TEST(RuntimePathsTest, ResolvesValidatedActivePayload) {
ASSERT_TRUE(active.active);
EXPECT_EQ(active.root, std::filesystem::canonical(payload));
EXPECT_EQ(active.roblox_library, active.root / "libroblox.so");
EXPECT_EQ(active.base_apk, active.root / "sober_apk/base.apk");
EXPECT_EQ(active.x86_64_split_apk,
active.root / "sober_apk/split_config.x86_64.apk");
EXPECT_EQ(active.assets_content, active.root / "assets/content");
EXPECT_EQ(paths.DefaultAssetPath(), active.assets_content);
}
TEST(RuntimePathsTest, RejectsIncompleteActiveAndroidPackage) {
TemporaryDirectory temporary;
ASSERT_FALSE(temporary.path().empty());
const std::filesystem::path data = temporary.path() / "data";
const std::string payload_id =
"2546-d0cb1fa0deb3d9161b4cd77530cbcd2e50de3a21";
const std::filesystem::path payload = data / "payloads" / payload_id;
WriteManagedPayloadFiles(payload);
ASSERT_TRUE(std::filesystem::remove(
payload / "sober_apk/split_config.x86_64.apk"));
std::ofstream(data / "current.json")
<< "{\"schema_version\":1,\"payload_id\":\"" << payload_id
<< "\",\"payload_path\":\"payloads/" << payload_id
<< "\",\"version_name\":\"2.725.1142\",\"version_code\":2546,"
"\"elf_build_id\":\"d0cb1fa0deb3d9161b4cd77530cbcd2e50de3a21\"}";
const MapEnvironment environment({
{"HOME", temporary.path().string()},
{"MOCKTAIL_DATA_ROOT", data.string()},
});
const ActivePayloadPaths active =
RuntimePaths::FromEnvironment(environment, temporary.path())
.ResolveActivePayload();
EXPECT_FALSE(active);
EXPECT_NE(active.error.find("split_config.x86_64.apk"), std::string::npos);
}
TEST(RuntimePathsTest, ResolvesCompleteApprovedProfileReferences) {
TemporaryDirectory temporary;
ASSERT_FALSE(temporary.path().empty());
@@ -197,11 +236,10 @@ TEST(RuntimePathsTest, ResolvesCompleteApprovedProfileReferences) {
const std::string approval_filename =
payload_id + "-" + approval_generation + ".json";
const std::filesystem::path payload = data / "payloads" / payload_id;
ASSERT_TRUE(RuntimePaths::EnsureDirectory(payload / "assets/content"));
WriteManagedPayloadFiles(payload);
ASSERT_TRUE(RuntimePaths::EnsureDirectory(data / "compatibility_profiles"));
ASSERT_TRUE(RuntimePaths::EnsureDirectory(data / "host_abi_profiles"));
ASSERT_TRUE(RuntimePaths::EnsureDirectory(data / "approvals"));
std::ofstream(payload / "libroblox.so") << "ELF fixture";
std::ofstream(data / "compatibility_profiles" / approval_filename) << "{}";
std::ofstream(data / "host_abi_profiles" / approval_filename) << "{}";
std::ofstream(data / "approvals" / approval_filename) << "{}";
@@ -247,8 +285,7 @@ TEST(RuntimePathsTest, RejectsPartialOrEscapingApprovedProfileReferences) {
const std::string approval_filename =
payload_id + "-" + approval_generation + ".json";
const std::filesystem::path payload = data / "payloads" / payload_id;
ASSERT_TRUE(RuntimePaths::EnsureDirectory(payload / "assets/content"));
std::ofstream(payload / "libroblox.so") << "ELF fixture";
WriteManagedPayloadFiles(payload);
const MapEnvironment environment({
{"HOME", temporary.path().string()},
{"MOCKTAIL_DATA_ROOT", data.string()},
@@ -468,7 +505,7 @@ TEST(RuntimePathsTest, PreparesManagedPayloadRelativeAssetRoot) {
ScopedCurrentPath restore_current_path;
const std::filesystem::path data = temporary.path() / "data";
const std::filesystem::path payload = data / "payloads/fixture";
ASSERT_TRUE(RuntimePaths::EnsureDirectory(payload / "assets/content"));
WriteManagedPayloadFiles(payload);
const MapEnvironment environment({
{"HOME", temporary.path().string()},
{"MOCKTAIL_DATA_ROOT", data.string()},
@@ -478,6 +515,10 @@ TEST(RuntimePathsTest, PreparesManagedPayloadRelativeAssetRoot) {
ActivePayloadPaths active;
active.active = true;
active.root = payload;
active.roblox_library = payload / "libroblox.so";
active.base_apk = payload / "sober_apk/base.apk";
active.x86_64_split_apk =
payload / "sober_apk/split_config.x86_64.apk";
active.assets_content = payload / "assets/content";
std::string error;
@@ -487,6 +528,74 @@ TEST(RuntimePathsTest, PreparesManagedPayloadRelativeAssetRoot) {
EXPECT_TRUE(std::filesystem::is_symlink(data / "rbx_bin/assets"));
EXPECT_EQ(std::filesystem::canonical(data / "rbx_bin/assets"),
std::filesystem::canonical(payload / "assets"));
EXPECT_TRUE(std::filesystem::is_symlink(data / "rbx_bin/sober_apk"));
EXPECT_EQ(std::filesystem::canonical(data / "rbx_bin/sober_apk"),
std::filesystem::canonical(payload / "sober_apk"));
EXPECT_TRUE(std::filesystem::is_symlink(data / "rbx_bin/libroblox.so"));
EXPECT_EQ(std::filesystem::canonical(data / "rbx_bin/libroblox.so"),
std::filesystem::canonical(payload / "libroblox.so"));
}
TEST(RuntimePathsTest, ReplacesAllStaleManagedPayloadLinksTogether) {
TemporaryDirectory temporary;
ASSERT_FALSE(temporary.path().empty());
ScopedCurrentPath restore_current_path;
const std::filesystem::path data = temporary.path() / "data";
const std::filesystem::path old_payload = data / "payloads/old";
const std::filesystem::path new_payload = data / "payloads/new";
WriteManagedPayloadFiles(old_payload);
WriteManagedPayloadFiles(new_payload);
const MapEnvironment environment({
{"HOME", temporary.path().string()},
{"MOCKTAIL_DATA_ROOT", data.string()},
});
const RuntimePaths paths =
RuntimePaths::FromEnvironment(environment, temporary.path());
ActivePayloadPaths active;
active.active = true;
active.root = old_payload;
std::string error;
ASSERT_TRUE(PrepareManagedPayloadWorkingDirectory(paths, active, &error))
<< error;
active.root = new_payload;
ASSERT_TRUE(PrepareManagedPayloadWorkingDirectory(paths, active, &error))
<< error;
EXPECT_EQ(std::filesystem::canonical(data / "rbx_bin/assets"),
std::filesystem::canonical(new_payload / "assets"));
EXPECT_EQ(std::filesystem::canonical(data / "rbx_bin/sober_apk"),
std::filesystem::canonical(new_payload / "sober_apk"));
EXPECT_EQ(std::filesystem::canonical(data / "rbx_bin/libroblox.so"),
std::filesystem::canonical(new_payload / "libroblox.so"));
}
TEST(RuntimePathsTest, PreservesUnexpectedManagedPayloadFiles) {
TemporaryDirectory temporary;
ASSERT_FALSE(temporary.path().empty());
ScopedCurrentPath restore_current_path;
const std::filesystem::path data = temporary.path() / "data";
const std::filesystem::path payload = data / "payloads/fixture";
WriteManagedPayloadFiles(payload);
ASSERT_TRUE(RuntimePaths::EnsureDirectory(data / "rbx_bin/sober_apk"));
std::ofstream(data / "rbx_bin/sober_apk/user-file") << "preserve me";
const MapEnvironment environment({
{"HOME", temporary.path().string()},
{"MOCKTAIL_DATA_ROOT", data.string()},
});
const RuntimePaths paths =
RuntimePaths::FromEnvironment(environment, temporary.path());
ActivePayloadPaths active;
active.active = true;
active.root = payload;
std::string error;
EXPECT_FALSE(PrepareManagedPayloadWorkingDirectory(paths, active, &error));
EXPECT_NE(error.find("refusing to replace non-symlink"), std::string::npos);
EXPECT_TRUE(std::filesystem::is_regular_file(
data / "rbx_bin/sober_apk/user-file"));
EXPECT_FALSE(std::filesystem::exists(data / "rbx_bin/assets"));
EXPECT_FALSE(std::filesystem::exists(data / "rbx_bin/libroblox.so"));
}
TEST(RuntimePathsTest, ExportsXdgBackedAndroidPathsAndCreatesParents) {
+3
View File
@@ -23,6 +23,9 @@ readonly icon="${install_root}/usr/share/icons/hicolor/scalable/apps/space.bigra
[[ -x "${runtime}/mocktail_updater" ]]
[[ -x "${runtime}/mocktail_failure_dialog" ]]
[[ -x "${runtime}/mocktail_webview_helper" ]]
[[ -x "${runtime}/mocktail_freebsd_socket_helper" ]]
LC_ALL=C readelf -h "${runtime}/mocktail_freebsd_socket_helper" |
grep -Fq 'UNIX - FreeBSD'
readelf -h "${binary}" | grep -Fq 'ELF64'
readelf -h "${runtime}/mocktail_updater" | grep -Fq 'ELF64'
readelf -d "${binary}" | grep -Fq '$ORIGIN/../lib/mocktail'