mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-17 18:02:19 +02:00
1 parent
fa370b9dbb
commit
fb5f38cb40
39 files changed
+1476
-132
No files matched your search
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
[](https://github.com/komaruworld/mocktail/releases/latest)
|
||||
[](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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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);
|
||||
|
||||
@@ -30,6 +30,7 @@ depends=(
|
||||
makedepends=(
|
||||
'cmake>=3.20'
|
||||
'git'
|
||||
'lld'
|
||||
'ninja'
|
||||
'pkgconf'
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -34,6 +34,7 @@ depends=(
|
||||
makedepends=(
|
||||
'cmake>=3.20'
|
||||
'git'
|
||||
'lld'
|
||||
'ninja'
|
||||
'nlohmann-json'
|
||||
'pkgconf'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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.
|
||||
@@ -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},
|
||||
@@ -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}"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -277,6 +277,8 @@ Main() {
|
||||
ParseArguments "$@"
|
||||
RequireCommand cmake
|
||||
RequireCommand find
|
||||
RequireCommand ld.lld
|
||||
RequireCommand elfedit
|
||||
RequireCommand readelf
|
||||
RequireCommand readlink
|
||||
RequireCommand sort
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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: " +
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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="
|
||||
|
||||
@@ -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
|
||||
@@ -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";
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in new issue
Block a user