Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified app/src/main/assets/linuxfs/usr/local/lib/libwninput.so
Binary file not shown.
2 changes: 1 addition & 1 deletion app/src/main/cpp/proot/src/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ int main(int argc, char *const argv[]) {
tracee = get_tracee(NULL, 0, true);
if (tracee == NULL)
goto error;
tracee->pid = getpid();
set_tracee_pid(tracee, getpid());

/* Set verboseness from env variable, may be overriden by option */
{
Expand Down
127 changes: 120 additions & 7 deletions app/src/main/cpp/proot/src/path/canon.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <assert.h> /* assert(3), */
#include <errno.h> /* E*, */
#include <fcntl.h> /* open(2), O_*, */
#include <limits.h> /* PATH_MAX, */
#include <stdio.h> /* sscanf(3), */
#include <string.h> /* string(3), */
Expand Down Expand Up @@ -153,6 +154,98 @@ static inline int substitute_binding_stat(Tracee *tracee, Finality finality,
return (S_ISLNK(statl.st_mode) ? 1 : 0);
}

/* Below this many directories, lstat(2)-ing each costs less than
* resolve_parent_at_once(). */
#define RESOLVE_AT_ONCE_MIN_DEPTH 3

/**
* Canonicalize at once the directory holding the last component of
* the absolute @user_path, instead of with an lstat(2) per component:
* the kernel resolves it, and it is canonical if the kernel's path is
* the one PRoot would build, ie. no link on the way. Only for a
* lexically clean path under the rootfs binding alone. This function
* returns the offset of the last component in @user_path and puts the
* directory in @guest_path, or returns 0 when the component-wise walk
* has to be used.
*/
static size_t resolve_parent_at_once(Tracee *tracee, const char *user_path,
char guest_path[PATH_MAX]) {
char parent[PATH_MAX];
char expected[PATH_MAX];
char resolved[PATH_MAX];
char link[32];
const Binding *binding;
const char *last;
const char *cursor;
size_t parent_length;
size_t prefix_length;
size_t expected_length;
ssize_t length;
int depth;
int fd;

if (tracee->glue_type != 0)
return 0;

last = strrchr(user_path, '/');
if (last == NULL || last == user_path || last[1] == '\0' ||
strcmp(last + 1, ".") == 0 || strcmp(last + 1, "..") == 0)
return 0;

parent_length = last - user_path;
if (parent_length >= PATH_MAX)
return 0;

/* No empty, "." or ".." component. */
depth = 0;
for (cursor = user_path; cursor < last;) {
const char *end;

end = memchr(cursor + 1, '/', last - cursor);
if (end == NULL)
end = last;
if (end == cursor + 1 || (end == cursor + 2 && cursor[1] == '.') ||
(end == cursor + 3 && cursor[1] == '.' && cursor[2] == '.'))
return 0;
depth++;
cursor = end;
}
if (depth < RESOLVE_AT_ONCE_MIN_DEPTH)
return 0;

memcpy(parent, user_path, parent_length);
parent[parent_length] = '\0';

/* Bindings are ordered deepest first, so the rootfs binding
* matching means no other binding is on the way. */
binding = get_binding(tracee, GUEST, parent);
if (binding == NULL || binding->guest.length != 1)
return 0;

/* A rootfs at "/" adds no prefix. */
prefix_length = (binding->host.length == 1 ? 0 : binding->host.length);
expected_length = prefix_length + parent_length;
if (expected_length >= PATH_MAX)
return 0;
memcpy(expected, binding->host.path, prefix_length);
strcpy(expected + prefix_length, parent);

fd = open(expected, O_PATH | O_DIRECTORY | O_CLOEXEC);
if (fd < 0)
return 0;

snprintf(link, sizeof(link), "/proc/self/fd/%d", fd);
length = readlink(link, resolved, sizeof(resolved));
close(fd);

if (length < 0 || (size_t)length != expected_length ||
memcmp(resolved, expected, expected_length) != 0)
return 0;

strcpy(guest_path, parent);
return last + 1 - user_path;
}

/**
* Copy in @guest_path the canonicalization (see `man 3 realpath`) of
* @user_path regarding to @tracee->root. The path to canonicalize
Expand All @@ -169,6 +262,7 @@ int canonicalize(Tracee *tracee, const char *user_path, bool deref_final,
char host_path[PATH_MAX];
Finality finality;
const char *cursor;
size_t offset;
int status;

/* Avoid infinite loop on circular links. */
Expand All @@ -183,15 +277,20 @@ int canonicalize(Tracee *tracee, const char *user_path, bool deref_final,
} else
strcpy(guest_path, "/");

/* Resolve bindings for the initial '/' component or user_path,
* which is not handled in the loop below.
* In particular HOST_PATH extensions are called from there. */
status = substitute_binding_stat(tracee, NOT_FINAL, guest_path, host_path);
if (status < 0)
return status;
offset = (user_path[0] == '/'
? resolve_parent_at_once(tracee, user_path, guest_path)
: 0);
if (offset == 0) {
/* Resolve bindings for the initial '/' component or user_path,
* which is not handled in the loop below.
* In particular HOST_PATH extensions are called from there. */
status = substitute_binding_stat(tracee, NOT_FINAL, guest_path, host_path);
if (status < 0)
return status;
}

/* Canonicalize recursely 'user_path' into 'guest_path'. */
cursor = user_path;
cursor = user_path + offset;
finality = NOT_FINAL;
while (!IS_FINAL(finality)) {
Comparison comparison;
Expand All @@ -217,6 +316,20 @@ int canonicalize(Tracee *tracee, const char *user_path, bool deref_final,

join_paths(scratch_path, guest_path, component);

/* A final component that is not dereferenced needs no
* lstat(2): whether it is a link only matters to follow
* it. Glue is still built from that lstat(2). */
if (finality == FINAL_NORMAL && !deref_final && tracee->glue_type == 0) {
strcpy(host_path, scratch_path);
status = substitute_binding(tracee, GUEST, host_path);
if (status < 0)
return status;

strcpy(scratch_path, guest_path);
join_paths(guest_path, scratch_path, component);
continue;
}

/* Resolve bindings and check that a non-final
* component exists and either is a directory or is a
* symlink. For this latter case, we check that the
Expand Down
1 change: 1 addition & 0 deletions app/src/main/cpp/proot/src/syscall/sysnums-arm.h
Original file line number Diff line number Diff line change
Expand Up @@ -356,5 +356,6 @@ static const Sysnum sysnums_arm[] = {
[395] = PR_pkey_alloc,
[396] = PR_pkey_free,
[397] = PR_statx,
[435] = PR_clone3,
[439] = PR_faccessat2,
};
1 change: 1 addition & 0 deletions app/src/main/cpp/proot/src/syscall/sysnums-arm64.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,5 +278,6 @@ static const Sysnum sysnums_arm64[] = {
[289] = PR_pkey_alloc,
[290] = PR_pkey_free,
[291] = PR_statx,
[435] = PR_clone3,
[439] = PR_faccessat2,
};
1 change: 1 addition & 0 deletions app/src/main/cpp/proot/src/syscall/sysnums.list
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ SYSNUM(clock_gettime)
SYSNUM(clock_nanosleep)
SYSNUM(clock_settime)
SYSNUM(clone)
SYSNUM(clone3)
SYSNUM(close)
SYSNUM(connect)
SYSNUM(copy_file_range)
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/cpp/proot/src/tracee/event.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ int launch_process(Tracee *tracee, char *const argv[]) {

default: /* parent */
/* We know the pid of the first tracee now. */
tracee->pid = pid;
set_tracee_pid(tracee, pid);
return 0;
}

Expand Down
39 changes: 38 additions & 1 deletion app/src/main/cpp/proot/src/tracee/tracee.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,26 @@
#include "ptrace/wait.h"
#include "syscall/sysnum.h"
#include "tracee/event.h"
#include "tracee/mem.h"
#include "tracee/reg.h"
#include "tracee/tracee.h"

#include "compat.h"

static Tracees tracees;

/* Tracees by pid: every event looks its tracee up, and a session has
* a tracee per thread. */
#define TRACEE_BUCKETS 1024
static Tracees buckets[TRACEE_BUCKETS];

/* Whether a tracee was terminated since the last sweep. */
static bool terminations_pending = false;

static Tracees *bucket_of(pid_t pid) {
return &buckets[(unsigned int)pid % TRACEE_BUCKETS];
}

/**
* Remove @zombie from its parent's list of zombies. Note: this is a
* talloc destructor.
Expand Down Expand Up @@ -85,6 +98,7 @@ static int remove_tracee(Tracee *tracee) {
int event;

LIST_REMOVE(tracee, link);
LIST_REMOVE(tracee, bucket_link);

/* Clean objects that are linked to this tracee's life
* span. */
Expand Down Expand Up @@ -217,6 +231,7 @@ static Tracee *new_tracee(pid_t pid) {
tracee->pid = pid;

LIST_INSERT_HEAD(&tracees, tracee, link);
LIST_INSERT_HEAD(bucket_of(pid), tracee, bucket_link);

tracee->life_context = talloc_new(tracee);

Expand Down Expand Up @@ -314,7 +329,7 @@ Tracee *get_tracee(const Tracee *current_tracee, pid_t pid, bool create) {
if (current_tracee != NULL && current_tracee->pid == pid)
return (Tracee *)current_tracee;

LIST_FOREACH(tracee, &tracees, link) {
LIST_FOREACH(tracee, bucket_of(pid), bucket_link) {
if (tracee->pid == pid) {
/* Flush then allocate a new memory collector. */
TALLOC_FREE(tracee->ctx);
Expand All @@ -327,11 +342,21 @@ Tracee *get_tracee(const Tracee *current_tracee, pid_t pid, bool create) {
return (create ? new_tracee(pid) : NULL);
}

/**
* Change the pid of @tracee, which get_tracee() looks it up by.
*/
void set_tracee_pid(Tracee *tracee, pid_t pid) {
tracee->pid = pid;
LIST_REMOVE(tracee, bucket_link);
LIST_INSERT_HEAD(bucket_of(pid), tracee, bucket_link);
}

/**
* Mark tracee as terminated and optionally take action.
*/
void terminate_tracee(Tracee *tracee) {
tracee->terminated = true;
terminations_pending = true;

/* Case where the terminated tracee is marked
to kill all tracees on exit.
Expand All @@ -348,6 +373,10 @@ void terminate_tracee(Tracee *tracee) {
void free_terminated_tracees() {
Tracee *next;

if (!terminations_pending)
return;
terminations_pending = false;

/* Items can't be deleted when using LIST_FOREACH. */
next = tracees.lh_first;
while (next != NULL) {
Expand Down Expand Up @@ -383,6 +412,14 @@ int new_child(Tracee *parent, word_t clone_flags) {
status = fetch_regs(parent);
if (status >= 0 && get_sysnum(parent, CURRENT) == PR_clone)
clone_flags = peek_reg(parent, CURRENT, SYSARG_1);
else if (status >= 0 && get_sysnum(parent, CURRENT) == PR_clone3) {
/* clone3(2) passes the usual flags as the first word of
* its struct clone_args. glibc 2.34 and later create
* every thread with it. */
word_t flags = peek_word(parent, peek_reg(parent, CURRENT, SYSARG_1));
if (errno == 0)
clone_flags = flags;
}

/* Get the pid of the parent's new child. */
status = ptrace(PTRACE_GETEVENTMSG, parent->pid, NULL, &pid);
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/cpp/proot/src/tracee/tracee.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ typedef struct tracee {
/* Link for the list of all tracees. */
LIST_ENTRY(tracee) link;

/* Link for the tracees whose pid hashes alike. */
LIST_ENTRY(tracee) bucket_link;

/* Process identifier. */
pid_t pid;

Expand Down Expand Up @@ -257,6 +260,7 @@ typedef struct tracee {
#define TRACEE(a) talloc_get_type_abort(talloc_parent(talloc_parent(a)), Tracee)

extern Tracee *get_tracee(const Tracee *tracee, pid_t pid, bool create);
extern void set_tracee_pid(Tracee *tracee, pid_t pid);
extern Tracee *get_stopped_ptracee(const Tracee *ptracer, pid_t pid,
bool only_with_pevent, word_t wait_options);
extern bool has_ptracees(const Tracee *ptracer, pid_t pid, word_t wait_options);
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/cpp/winlator/fakeinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,10 @@ EXPORT int close(int fd) {
return my_close(fd);
}

// Nothing wakes a reader when the app publishes, so a waiting reader looks at the ring again
// at least this often: it bounds how late a press reaches a game that blocks on the device.
static constexpr int kMaxRingWaitMs = 2;

EXPORT ssize_t read(int fd, void *buf, size_t count) {
std::unique_lock<std::recursive_mutex> guard(controller_mutex);
auto controller = controller_map.find(fd);
Expand Down Expand Up @@ -1244,7 +1248,7 @@ EXPORT ssize_t read(int fd, void *buf, size_t count) {
int result = nanosleep(&sleep_time, nullptr);
guard.lock();
if (result < 0) return -1;
if (backoff_ns < 16 * 1000 * 1000) backoff_ns *= 2;
if (backoff_ns < kMaxRingWaitMs * 1000 * 1000) backoff_ns *= 2;
}
}

Expand Down Expand Up @@ -1390,7 +1394,7 @@ static int poll_fake(struct pollfd *fds, nfds_t nfds, int timeout,
if (deadline_ms >= 0 && monotonic_ms() >= deadline_ms)
return 0;

if (backoff_ms < 16)
if (backoff_ms < kMaxRingWaitMs)
backoff_ms *= 2;
}
}
Expand Down Expand Up @@ -1565,7 +1569,7 @@ EXPORT int select(int nfds, fd_set *readfds, fd_set *writefds,
if (deadline_ms >= 0 && monotonic_ms() >= deadline_ms)
return 0;

if (backoff_ms < 16)
if (backoff_ms < kMaxRingWaitMs)
backoff_ms *= 2;
}
}
Expand Down
7 changes: 5 additions & 2 deletions app/src/main/runtime/container/ContainerCreation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import android.os.Handler
import android.os.Looper
import com.winlator.cmod.feature.library.LinuxApps
import com.winlator.cmod.runtime.audio.directaudio.DirectAudioDriver
import com.winlator.cmod.runtime.compat.box64.Box64Preset
import com.winlator.cmod.runtime.compat.fexcore.FEXCorePreset
import com.winlator.cmod.runtime.content.ContentProfile
Expand Down Expand Up @@ -296,12 +297,14 @@ object ContainerCreation {
runtime: ContentProfile?,
): Container? {
val name = uniqueName(containerManager, GAMESCOPE_CONTAINER_NAME)
val wineVersion = runtime?.let { ContentsManager.getEntryName(it) } ?: WineInfo.MAIN_WINE_VERSION.identifier()
// Starts on DirectAudio; the container's settings can change it later.
val data = buildLaunchReadyData(context, contentsManager, name, wineVersion)
.put("audioDriver", DirectAudioDriver.IDENTIFIER)
val created =
if (runtime != null) {
val data = buildLaunchReadyData(context, contentsManager, name, ContentsManager.getEntryName(runtime))
containerManager.createContainer(data, contentsManager)
} else {
val data = buildLaunchReadyData(context, contentsManager, name, WineInfo.MAIN_WINE_VERSION.identifier())
containerManager.createPrefixlessContainer(data)
}
val container = created ?: return null
Expand Down
Loading
Loading