mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
1344 lines
38 KiB
C
1344 lines
38 KiB
C
/* droiddeck-ntsync: a userspace implementation of the /dev/ntsync interface for kernels without the driver.
|
|
* Credit: ntsync, the kernel driver and its Wine client, by Elizabeth Figura. */
|
|
#define _GNU_SOURCE
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/futex.h>
|
|
#include <linux/ioctl.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/random.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/timerfd.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
struct ns_sem_args {
|
|
uint32_t count;
|
|
uint32_t max;
|
|
};
|
|
|
|
struct ns_mutex_args {
|
|
uint32_t owner;
|
|
uint32_t count;
|
|
};
|
|
|
|
struct ns_event_args {
|
|
uint32_t manual;
|
|
uint32_t signaled;
|
|
};
|
|
|
|
struct ns_wait_args {
|
|
uint64_t timeout;
|
|
uint64_t objs;
|
|
uint32_t count;
|
|
uint32_t index;
|
|
uint32_t flags;
|
|
uint32_t owner;
|
|
uint32_t alert;
|
|
uint32_t pad;
|
|
};
|
|
|
|
#define NS_CREATE_SEM _IOW('N', 0x80, struct ns_sem_args)
|
|
#define NS_SEM_RELEASE _IOWR('N', 0x81, uint32_t)
|
|
#define NS_WAIT_ANY _IOWR('N', 0x82, struct ns_wait_args)
|
|
#define NS_WAIT_ALL _IOWR('N', 0x83, struct ns_wait_args)
|
|
#define NS_CREATE_MUTEX _IOW('N', 0x84, struct ns_mutex_args)
|
|
#define NS_MUTEX_UNLOCK _IOWR('N', 0x85, struct ns_mutex_args)
|
|
#define NS_MUTEX_KILL _IOW('N', 0x86, uint32_t)
|
|
#define NS_CREATE_EVENT _IOW('N', 0x87, struct ns_event_args)
|
|
#define NS_EVENT_SET _IOR('N', 0x88, uint32_t)
|
|
#define NS_EVENT_RESET _IOR('N', 0x89, uint32_t)
|
|
#define NS_EVENT_PULSE _IOR('N', 0x8a, uint32_t)
|
|
#define NS_SEM_READ _IOR('N', 0x8b, struct ns_sem_args)
|
|
#define NS_MUTEX_READ _IOR('N', 0x8c, struct ns_mutex_args)
|
|
#define NS_EVENT_READ _IOR('N', 0x8d, struct ns_event_args)
|
|
|
|
#define NS_WAIT_REALTIME 1u
|
|
#define NS_MAX_WAIT 64u
|
|
#define NS_ENTS (NS_MAX_WAIT + 1u)
|
|
#define NS_SLOTS 65536u
|
|
#define NS_QS 16384u
|
|
#define NS_PROCS 256u
|
|
#define NS_FDS 65536
|
|
#define NS_DEVS 8
|
|
#define NS_VERSION 1u
|
|
#define NS_HEAD_MAGIC 0x31434e5953544e44ull
|
|
#define NS_DEV_MAGIC 0x4e54445600000000ull
|
|
#define NS_OBJ_MAGIC 0x27ull
|
|
#define NS_WAITERS 0x80000000u
|
|
#define NS_SWEEP_NS 2000000000ull
|
|
#define NS_DEAD 0xffffffffu
|
|
|
|
#ifndef CLOSE_RANGE_CLOEXEC
|
|
#define CLOSE_RANGE_CLOEXEC (1u << 2)
|
|
#endif
|
|
|
|
#define NS_E_VALID (1ull << 63)
|
|
#define NS_E_DEVICE (1ull << 62)
|
|
|
|
enum { NS_FREE, NS_SEM, NS_MUTEX, NS_EVENT };
|
|
|
|
struct ns_proc {
|
|
uint32_t pid;
|
|
uint32_t pad;
|
|
};
|
|
|
|
struct ns_head {
|
|
uint64_t magic;
|
|
uint32_t version;
|
|
uint32_t tag;
|
|
uint32_t all_lock;
|
|
uint32_t proc_lock;
|
|
uint64_t slot_free;
|
|
uint64_t q_free;
|
|
uint32_t slot_hwm;
|
|
uint32_t q_hwm;
|
|
uint64_t sweep_at;
|
|
struct ns_proc procs[NS_PROCS];
|
|
};
|
|
|
|
struct ns_obj {
|
|
uint32_t lock;
|
|
uint32_t type;
|
|
uint32_t gen;
|
|
uint32_t all_hint;
|
|
uint32_t a;
|
|
uint32_t b;
|
|
uint32_t ownerdead;
|
|
uint32_t busy;
|
|
uint32_t any_head;
|
|
uint32_t any_tail;
|
|
uint32_t all_head;
|
|
uint32_t all_tail;
|
|
uint32_t next_free;
|
|
uint32_t pad[3];
|
|
uint64_t holders[NS_PROCS / 64];
|
|
uint64_t pad2[4];
|
|
};
|
|
|
|
struct ns_ent {
|
|
uint32_t next;
|
|
uint32_t prev;
|
|
uint32_t obj;
|
|
uint32_t index;
|
|
uint32_t linked;
|
|
};
|
|
|
|
struct ns_q {
|
|
int32_t signaled;
|
|
uint32_t owner;
|
|
uint32_t tid;
|
|
uint32_t pid;
|
|
uint32_t proc;
|
|
uint32_t count;
|
|
uint32_t total;
|
|
uint32_t all;
|
|
uint32_t ownerdead;
|
|
uint32_t waiting;
|
|
uint32_t inuse;
|
|
uint32_t next_free;
|
|
struct ns_ent ent[NS_ENTS];
|
|
};
|
|
|
|
#define NS_HEAD_SIZE 65536ul
|
|
#define NS_OBJ_OFF NS_HEAD_SIZE
|
|
#define NS_Q_OFF (NS_OBJ_OFF + (size_t)NS_SLOTS * sizeof(struct ns_obj))
|
|
#define NS_REGION (NS_Q_OFF + (size_t)NS_QS * sizeof(struct ns_q))
|
|
|
|
_Static_assert(sizeof(struct ns_head) <= NS_HEAD_SIZE, "ntsync head");
|
|
_Static_assert(sizeof(struct ns_obj) == 128, "ntsync object");
|
|
|
|
struct ns_dev {
|
|
struct ns_head *h;
|
|
uint64_t ino;
|
|
uint32_t tag;
|
|
int proc;
|
|
uint32_t *counts;
|
|
uint32_t refs;
|
|
};
|
|
|
|
struct ns_tls {
|
|
struct ns_dev *d;
|
|
uint32_t q;
|
|
int waiting;
|
|
};
|
|
|
|
static struct ns_dev ns_devs[NS_DEVS];
|
|
static int ns_ndev;
|
|
static uint64_t *ns_fdt;
|
|
static pthread_mutex_t ns_mx = PTHREAD_MUTEX_INITIALIZER;
|
|
static pid_t ns_pid;
|
|
static int ns_state = -1;
|
|
static pthread_key_t ns_key;
|
|
static pthread_once_t ns_once = PTHREAD_ONCE_INIT;
|
|
static __thread uint32_t ns_tid;
|
|
static __thread struct ns_tls ns_tq;
|
|
|
|
static int (*ns_real_close)(int);
|
|
|
|
static int ns_on(void) {
|
|
if (ns_state < 0) {
|
|
const char *e = getenv("BL_SYNC");
|
|
ns_state = e && e[0] == '1';
|
|
}
|
|
return ns_state;
|
|
}
|
|
|
|
static int ns_close_fd(int fd) {
|
|
if (!ns_real_close) ns_real_close = (int (*)(int))dlsym(RTLD_NEXT, "close");
|
|
return ns_real_close(fd);
|
|
}
|
|
|
|
static inline void ns_relax(void) {
|
|
#if defined(__aarch64__)
|
|
__asm__ volatile("yield" ::: "memory");
|
|
#elif defined(__x86_64__) || defined(__i386__)
|
|
__asm__ volatile("pause" ::: "memory");
|
|
#endif
|
|
}
|
|
|
|
static uint32_t ns_self(void) {
|
|
if (!ns_tid) ns_tid = (uint32_t)syscall(SYS_gettid);
|
|
return ns_tid;
|
|
}
|
|
|
|
static uint32_t ns_getpid(void) {
|
|
if (!ns_pid) ns_pid = getpid();
|
|
return (uint32_t)ns_pid;
|
|
}
|
|
|
|
static long ns_futex(void *w, int op, uint32_t val, const struct timespec *ts, uint32_t bits) {
|
|
return syscall(SYS_futex, w, op, val, ts, NULL, bits);
|
|
}
|
|
|
|
static int ns_dead(uint32_t id) {
|
|
return id && kill((pid_t)id, 0) == -1 && errno == ESRCH;
|
|
}
|
|
|
|
static void ns_lock(uint32_t *w) {
|
|
uint32_t me = ns_self(), want = me;
|
|
for (unsigned spin = 0;; spin++) {
|
|
uint32_t v = 0;
|
|
if (__atomic_compare_exchange_n(w, &v, want, 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) return;
|
|
if (spin < 100) {
|
|
ns_relax();
|
|
continue;
|
|
}
|
|
if (!(v & NS_WAITERS)) {
|
|
if (!__atomic_compare_exchange_n(w, &v, v | NS_WAITERS, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) continue;
|
|
v |= NS_WAITERS;
|
|
}
|
|
want = me | NS_WAITERS;
|
|
struct timespec ts = {0, 20000000};
|
|
int saved = errno;
|
|
if (ns_futex(w, FUTEX_WAIT, v, &ts, 0) == -1 && errno == ETIMEDOUT && ns_dead(v & ~NS_WAITERS)
|
|
&& __atomic_compare_exchange_n(w, &v, want, 0, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
|
|
errno = saved;
|
|
return;
|
|
}
|
|
errno = saved;
|
|
}
|
|
}
|
|
|
|
static void ns_unlock(uint32_t *w) {
|
|
if (__atomic_exchange_n(w, 0, __ATOMIC_RELEASE) & NS_WAITERS) {
|
|
int saved = errno;
|
|
ns_futex(w, FUTEX_WAKE, 1, NULL, 0);
|
|
errno = saved;
|
|
}
|
|
}
|
|
|
|
static void ns_block(sigset_t *old) {
|
|
sigset_t s;
|
|
sigfillset(&s);
|
|
sigdelset(&s, SIGSEGV);
|
|
sigdelset(&s, SIGBUS);
|
|
sigdelset(&s, SIGILL);
|
|
sigdelset(&s, SIGFPE);
|
|
sigdelset(&s, SIGTRAP);
|
|
pthread_sigmask(SIG_BLOCK, &s, old);
|
|
}
|
|
|
|
static void ns_restore(const sigset_t *old) {
|
|
pthread_sigmask(SIG_SETMASK, old, NULL);
|
|
}
|
|
|
|
static inline struct ns_obj *ns_obj(struct ns_head *h, uint32_t s) {
|
|
return (struct ns_obj *)((char *)h + NS_OBJ_OFF) + s;
|
|
}
|
|
|
|
static inline struct ns_q *ns_q(struct ns_head *h, uint32_t i) {
|
|
return (struct ns_q *)((char *)h + NS_Q_OFF) + i;
|
|
}
|
|
|
|
static inline uint32_t ns_node_id(uint32_t q, uint32_t i) {
|
|
return q * NS_ENTS + i + 1;
|
|
}
|
|
|
|
static inline struct ns_ent *ns_node(struct ns_head *h, uint32_t id) {
|
|
return &ns_q(h, (id - 1) / NS_ENTS)->ent[(id - 1) % NS_ENTS];
|
|
}
|
|
|
|
static inline struct ns_q *ns_node_q(struct ns_head *h, uint32_t id) {
|
|
return ns_q(h, (id - 1) / NS_ENTS);
|
|
}
|
|
|
|
static void ns_link(struct ns_head *h, uint32_t *head, uint32_t *tail, uint32_t id) {
|
|
struct ns_ent *e = ns_node(h, id);
|
|
e->next = 0;
|
|
e->prev = *tail;
|
|
if (*tail) ns_node(h, *tail)->next = id;
|
|
else *head = id;
|
|
*tail = id;
|
|
e->linked = 1;
|
|
}
|
|
|
|
static void ns_unlink(struct ns_head *h, uint32_t *head, uint32_t *tail, uint32_t id) {
|
|
struct ns_ent *e = ns_node(h, id);
|
|
if (!e->linked) return;
|
|
if (e->prev) ns_node(h, e->prev)->next = e->next;
|
|
else *head = e->next;
|
|
if (e->next) ns_node(h, e->next)->prev = e->prev;
|
|
else *tail = e->prev;
|
|
e->next = e->prev = 0;
|
|
e->linked = 0;
|
|
}
|
|
|
|
static int ns_no_holders(const struct ns_obj *o) {
|
|
for (unsigned i = 0; i < NS_PROCS / 64; i++)
|
|
if (o->holders[i]) return 0;
|
|
return 1;
|
|
}
|
|
|
|
static void ns_push(uint64_t *head, uint32_t *next, uint32_t index) {
|
|
uint64_t old = __atomic_load_n(head, __ATOMIC_ACQUIRE), nh;
|
|
do {
|
|
__atomic_store_n(next, (uint32_t)old, __ATOMIC_RELAXED);
|
|
nh = (((old >> 32) + 1) << 32) | (index + 1);
|
|
} while (!__atomic_compare_exchange_n(head, &old, nh, 1, __ATOMIC_RELEASE, __ATOMIC_ACQUIRE));
|
|
}
|
|
|
|
static int ns_pop(struct ns_head *h, uint64_t *head, int objs, uint32_t *out) {
|
|
uint64_t old = __atomic_load_n(head, __ATOMIC_ACQUIRE);
|
|
while ((uint32_t)old) {
|
|
uint32_t i = (uint32_t)old - 1;
|
|
uint32_t next = __atomic_load_n(objs ? &ns_obj(h, i)->next_free : &ns_q(h, i)->next_free, __ATOMIC_RELAXED);
|
|
uint64_t nh = (((old >> 32) + 1) << 32) | next;
|
|
if (__atomic_compare_exchange_n(head, &old, nh, 1, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE)) {
|
|
*out = i;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ns_grow(uint32_t *hwm, uint32_t limit, uint32_t *out) {
|
|
uint32_t v = __atomic_load_n(hwm, __ATOMIC_RELAXED);
|
|
while (v < limit) {
|
|
if (__atomic_compare_exchange_n(hwm, &v, v + 1, 1, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) {
|
|
*out = v;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ns_maybe_free(struct ns_head *h, struct ns_obj *o) {
|
|
if (o->type == NS_FREE || o->busy || !ns_no_holders(o)) return;
|
|
o->type = NS_FREE;
|
|
o->gen = (o->gen + 1) & 0xfffff;
|
|
ns_push(&h->slot_free, &o->next_free, (uint32_t)(o - ns_obj(h, 0)));
|
|
}
|
|
|
|
static int ns_lock_obj(struct ns_head *h, struct ns_obj *o) {
|
|
ns_lock(&o->lock);
|
|
if (!o->all_hint) return 0;
|
|
ns_unlock(&o->lock);
|
|
ns_lock(&h->all_lock);
|
|
ns_lock(&o->lock);
|
|
return 1;
|
|
}
|
|
|
|
static void ns_unlock_obj(struct ns_head *h, struct ns_obj *o, int all) {
|
|
ns_unlock(&o->lock);
|
|
if (all) ns_unlock(&h->all_lock);
|
|
}
|
|
|
|
static int ns_claim(struct ns_q *q, int32_t index) {
|
|
if (q->pid != ns_getpid()) {
|
|
int saved = errno, dead = ns_dead(q->pid);
|
|
errno = saved;
|
|
if (dead) return 0;
|
|
}
|
|
int32_t want = -1;
|
|
return __atomic_compare_exchange_n(&q->signaled, &want, index, 0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED);
|
|
}
|
|
|
|
static void ns_wake(struct ns_q *q) {
|
|
int saved = errno;
|
|
ns_futex(&q->signaled, FUTEX_WAKE, 1, NULL, 0);
|
|
errno = saved;
|
|
}
|
|
|
|
static int ns_signaled(const struct ns_obj *o, uint32_t owner) {
|
|
switch (o->type) {
|
|
case NS_SEM:
|
|
return o->a != 0;
|
|
case NS_MUTEX:
|
|
if (o->b && o->b != owner) return 0;
|
|
return o->a < UINT32_MAX;
|
|
case NS_EVENT:
|
|
return o->b != 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ns_take(struct ns_obj *o, struct ns_q *q) {
|
|
switch (o->type) {
|
|
case NS_SEM:
|
|
o->a--;
|
|
break;
|
|
case NS_MUTEX:
|
|
if (o->ownerdead) q->ownerdead = 1;
|
|
o->ownerdead = 0;
|
|
o->a++;
|
|
o->b = q->owner;
|
|
break;
|
|
case NS_EVENT:
|
|
if (!o->a) o->b = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void ns_wake_any(struct ns_head *h, struct ns_obj *o) {
|
|
uint32_t id = o->any_head;
|
|
while (id) {
|
|
struct ns_ent *e = ns_node(h, id);
|
|
struct ns_q *q = ns_node_q(h, id);
|
|
uint32_t next = e->next;
|
|
if (!ns_signaled(o, q->owner)) {
|
|
if (o->type != NS_MUTEX || o->a == UINT32_MAX) return;
|
|
} else if (ns_claim(q, (int32_t)e->index)) {
|
|
ns_take(o, q);
|
|
ns_wake(q);
|
|
}
|
|
id = next;
|
|
}
|
|
}
|
|
|
|
static void ns_wake_all_q(struct ns_head *h, struct ns_q *q, struct ns_obj *locked) {
|
|
uint32_t n = q->count;
|
|
int ok = 1;
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
struct ns_obj *o = ns_obj(h, q->ent[i].obj);
|
|
if (o != locked) ns_lock(&o->lock);
|
|
}
|
|
for (uint32_t i = 0; i < n && ok; i++)
|
|
ok = ns_signaled(ns_obj(h, q->ent[i].obj), q->owner);
|
|
if (ok && ns_claim(q, 0)) {
|
|
for (uint32_t i = 0; i < n; i++) ns_take(ns_obj(h, q->ent[i].obj), q);
|
|
ns_wake(q);
|
|
}
|
|
for (uint32_t i = 0; i < n; i++) {
|
|
struct ns_obj *o = ns_obj(h, q->ent[i].obj);
|
|
if (o != locked) ns_unlock(&o->lock);
|
|
}
|
|
}
|
|
|
|
static void ns_wake_all_obj(struct ns_head *h, struct ns_obj *o) {
|
|
uint32_t id = o->all_head;
|
|
while (id) {
|
|
uint32_t next = ns_node(h, id)->next;
|
|
ns_wake_all_q(h, ns_node_q(h, id), o);
|
|
id = next;
|
|
}
|
|
}
|
|
|
|
static void ns_drop_q(struct ns_head *h, uint32_t qi) {
|
|
struct ns_q *q = ns_q(h, qi);
|
|
if (q->waiting) {
|
|
if (q->all) ns_lock(&h->all_lock);
|
|
for (uint32_t i = 0; i < q->total; i++) {
|
|
struct ns_ent *e = &q->ent[i];
|
|
if (e->obj == UINT32_MAX) continue;
|
|
struct ns_obj *o = ns_obj(h, e->obj);
|
|
int all = 0;
|
|
if (q->all && i < q->count) {
|
|
ns_lock(&o->lock);
|
|
} else {
|
|
if (q->all) ns_lock(&o->lock);
|
|
else all = ns_lock_obj(h, o);
|
|
}
|
|
if (e->linked) {
|
|
if (q->all && i < q->count) {
|
|
ns_unlink(h, &o->all_head, &o->all_tail, ns_node_id(qi, i));
|
|
o->all_hint--;
|
|
} else {
|
|
ns_unlink(h, &o->any_head, &o->any_tail, ns_node_id(qi, i));
|
|
}
|
|
}
|
|
o->busy--;
|
|
e->obj = UINT32_MAX;
|
|
ns_maybe_free(h, o);
|
|
if (q->all) ns_unlock(&o->lock);
|
|
else ns_unlock_obj(h, o, all);
|
|
}
|
|
if (q->all) ns_unlock(&h->all_lock);
|
|
q->waiting = 0;
|
|
}
|
|
}
|
|
|
|
static void ns_free_q(struct ns_head *h, uint32_t qi) {
|
|
struct ns_q *q = ns_q(h, qi);
|
|
ns_drop_q(h, qi);
|
|
q->inuse = 0;
|
|
ns_push(&h->q_free, &q->next_free, qi);
|
|
}
|
|
|
|
static void ns_sweep_proc(struct ns_head *h, uint32_t p) {
|
|
uint32_t qn = __atomic_load_n(&h->q_hwm, __ATOMIC_ACQUIRE);
|
|
for (uint32_t i = 0; i < qn; i++) {
|
|
struct ns_q *q = ns_q(h, i);
|
|
if (q->inuse && q->proc == p) ns_free_q(h, i);
|
|
}
|
|
uint32_t sn = __atomic_load_n(&h->slot_hwm, __ATOMIC_ACQUIRE);
|
|
uint64_t bit = 1ull << (p % 64);
|
|
for (uint32_t s = 0; s < sn; s++) {
|
|
struct ns_obj *o = ns_obj(h, s);
|
|
if (!(__atomic_load_n(&o->holders[p / 64], __ATOMIC_RELAXED) & bit)) continue;
|
|
ns_lock(&o->lock);
|
|
o->holders[p / 64] &= ~bit;
|
|
ns_maybe_free(h, o);
|
|
ns_unlock(&o->lock);
|
|
}
|
|
__atomic_store_n(&h->procs[p].pid, 0, __ATOMIC_RELEASE);
|
|
}
|
|
|
|
static void ns_sweep_dead(struct ns_head *h) {
|
|
int saved = errno;
|
|
ns_lock(&h->proc_lock);
|
|
for (uint32_t i = 0; i < NS_PROCS; i++) {
|
|
uint32_t pid = h->procs[i].pid;
|
|
if (pid && pid != ns_getpid() && ns_dead(pid)) ns_sweep_proc(h, i);
|
|
}
|
|
ns_unlock(&h->proc_lock);
|
|
errno = saved;
|
|
}
|
|
|
|
static void ns_maybe_sweep(struct ns_head *h) {
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
uint64_t now = (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
|
|
uint64_t last = __atomic_load_n(&h->sweep_at, __ATOMIC_RELAXED);
|
|
if (now - last < NS_SWEEP_NS) return;
|
|
if (!__atomic_compare_exchange_n(&h->sweep_at, &last, now, 0, __ATOMIC_RELAXED, __ATOMIC_RELAXED)) return;
|
|
ns_sweep_dead(h);
|
|
}
|
|
|
|
static int ns_attach(struct ns_dev *d) {
|
|
if (d->proc >= 0) return d->proc;
|
|
struct ns_head *h = d->h;
|
|
uint32_t pid = ns_getpid();
|
|
int slot = -1;
|
|
if (!d->counts) {
|
|
void *m = mmap(NULL, (size_t)NS_SLOTS * sizeof(uint32_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
if (m == MAP_FAILED) return -1;
|
|
d->counts = m;
|
|
}
|
|
int saved = errno;
|
|
ns_lock(&h->proc_lock);
|
|
for (uint32_t i = 0; i < NS_PROCS && slot < 0; i++)
|
|
if (h->procs[i].pid == pid) {
|
|
ns_sweep_proc(h, i);
|
|
slot = (int)i;
|
|
}
|
|
for (uint32_t i = 0; i < NS_PROCS && slot < 0; i++)
|
|
if (!h->procs[i].pid) slot = (int)i;
|
|
for (uint32_t i = 0; i < NS_PROCS && slot < 0; i++)
|
|
if (ns_dead(h->procs[i].pid)) {
|
|
ns_sweep_proc(h, i);
|
|
slot = (int)i;
|
|
}
|
|
if (slot >= 0) __atomic_store_n(&h->procs[slot].pid, pid, __ATOMIC_RELEASE);
|
|
ns_unlock(&h->proc_lock);
|
|
errno = saved;
|
|
__atomic_store_n(&d->proc, slot, __ATOMIC_RELEASE);
|
|
return slot;
|
|
}
|
|
|
|
static int ns_table(void) {
|
|
if (ns_fdt) return 1;
|
|
void *m = mmap(NULL, (size_t)NS_FDS * sizeof(uint64_t), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
|
if (m == MAP_FAILED) return 0;
|
|
__atomic_store_n(&ns_fdt, (uint64_t *)m, __ATOMIC_RELEASE);
|
|
return 1;
|
|
}
|
|
|
|
static struct ns_dev *ns_find(uint32_t tag) {
|
|
for (int i = 0; i < ns_ndev; i++)
|
|
if (ns_devs[i].h && ns_devs[i].tag == tag) return &ns_devs[i];
|
|
return NULL;
|
|
}
|
|
|
|
static int ns_dev_get(struct ns_dev *d) {
|
|
uint32_t r = __atomic_load_n(&d->refs, __ATOMIC_ACQUIRE);
|
|
while (r && r != NS_DEAD)
|
|
if (__atomic_compare_exchange_n(&d->refs, &r, r + 1, 1, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void ns_release(struct ns_dev *d) {
|
|
struct ns_head *h = d->h;
|
|
__atomic_store_n(&d->tag, 0, __ATOMIC_RELEASE);
|
|
if (d->proc >= 0) {
|
|
ns_lock(&h->proc_lock);
|
|
ns_sweep_proc(h, (uint32_t)d->proc);
|
|
ns_unlock(&h->proc_lock);
|
|
}
|
|
if (d->counts) munmap(d->counts, (size_t)NS_SLOTS * sizeof(uint32_t));
|
|
munmap(h, NS_REGION);
|
|
d->counts = NULL;
|
|
d->proc = -1;
|
|
d->ino = 0;
|
|
__atomic_store_n(&d->h, NULL, __ATOMIC_RELEASE);
|
|
}
|
|
|
|
static void ns_dev_put_locked(struct ns_dev *d) {
|
|
if (__atomic_sub_fetch(&d->refs, 1, __ATOMIC_ACQ_REL)) return;
|
|
uint32_t zero = 0;
|
|
if (d->h && __atomic_compare_exchange_n(&d->refs, &zero, NS_DEAD, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) ns_release(d);
|
|
}
|
|
|
|
static void ns_dev_put(struct ns_dev *d) {
|
|
if (__atomic_sub_fetch(&d->refs, 1, __ATOMIC_ACQ_REL)) return;
|
|
sigset_t old;
|
|
int saved = errno;
|
|
ns_block(&old);
|
|
pthread_mutex_lock(&ns_mx);
|
|
uint32_t zero = 0;
|
|
if (d->h && __atomic_compare_exchange_n(&d->refs, &zero, NS_DEAD, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) ns_release(d);
|
|
pthread_mutex_unlock(&ns_mx);
|
|
ns_restore(&old);
|
|
errno = saved;
|
|
}
|
|
|
|
static struct ns_dev *ns_map(int fd, uint32_t tag, uint64_t ino) {
|
|
struct ns_dev *d = ns_find(tag);
|
|
if (d) return d->ino == ino ? d : NULL;
|
|
for (int i = 0; i < ns_ndev && !d; i++)
|
|
if (!ns_devs[i].h) d = &ns_devs[i];
|
|
if (!d && ns_ndev == NS_DEVS) return NULL;
|
|
struct ns_head *h = mmap(NULL, NS_REGION, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (h == MAP_FAILED) return NULL;
|
|
if (h->magic != NS_HEAD_MAGIC || h->version != NS_VERSION || h->tag != tag) {
|
|
munmap(h, NS_REGION);
|
|
return NULL;
|
|
}
|
|
if (!d) d = &ns_devs[ns_ndev];
|
|
d->ino = ino;
|
|
d->proc = -1;
|
|
d->counts = NULL;
|
|
d->refs = 0;
|
|
__atomic_store_n(&d->h, h, __ATOMIC_RELEASE);
|
|
__atomic_store_n(&d->tag, tag, __ATOMIC_RELEASE);
|
|
if (d == &ns_devs[ns_ndev]) __atomic_store_n(&ns_ndev, ns_ndev + 1, __ATOMIC_RELEASE);
|
|
return d;
|
|
}
|
|
|
|
static uint64_t ns_entry_dev(struct ns_dev *d) {
|
|
return NS_E_VALID | NS_E_DEVICE | ((uint64_t)(d - ns_devs) << 56);
|
|
}
|
|
|
|
static uint64_t ns_entry_obj(struct ns_dev *d, uint32_t gen, uint32_t s) {
|
|
return NS_E_VALID | ((uint64_t)(d - ns_devs) << 56) | ((uint64_t)gen << 20) | s;
|
|
}
|
|
|
|
static uint64_t ns_token(uint32_t tag, uint32_t gen, uint32_t s) {
|
|
return (NS_OBJ_MAGIC << 56) | ((uint64_t)(tag & 0xffff) << 40) | ((uint64_t)(gen & 0xfffff) << 20) | s;
|
|
}
|
|
|
|
static void ns_forget_locked(int fd) {
|
|
uint64_t e = __atomic_exchange_n(&ns_fdt[fd], 0, __ATOMIC_ACQ_REL);
|
|
if (!e) return;
|
|
struct ns_dev *d = &ns_devs[(e >> 56) & 7];
|
|
if (!(e & NS_E_DEVICE)) {
|
|
uint32_t s = (uint32_t)(e & 0xfffff);
|
|
if (d->proc >= 0 && d->counts && d->counts[s] && !--d->counts[s]) {
|
|
struct ns_obj *o = ns_obj(d->h, s);
|
|
ns_lock(&o->lock);
|
|
o->holders[d->proc / 64] &= ~(1ull << (d->proc % 64));
|
|
ns_maybe_free(d->h, o);
|
|
ns_unlock(&o->lock);
|
|
}
|
|
}
|
|
ns_dev_put_locked(d);
|
|
}
|
|
|
|
static void ns_forget(int fd) {
|
|
sigset_t old;
|
|
int saved = errno;
|
|
ns_block(&old);
|
|
pthread_mutex_lock(&ns_mx);
|
|
ns_forget_locked(fd);
|
|
pthread_mutex_unlock(&ns_mx);
|
|
ns_restore(&old);
|
|
errno = saved;
|
|
}
|
|
|
|
static uint64_t ns_adopt(struct ns_dev *d, uint32_t tag, int fd, uint32_t gen, uint32_t s) {
|
|
uint64_t e = 0;
|
|
sigset_t old;
|
|
ns_block(&old);
|
|
pthread_mutex_lock(&ns_mx);
|
|
if (!d->h || d->tag != tag || !ns_table() || ns_attach(d) < 0 || s >= NS_SLOTS) goto out;
|
|
if ((e = __atomic_load_n(&ns_fdt[fd], __ATOMIC_ACQUIRE))) goto out;
|
|
struct ns_obj *o = ns_obj(d->h, s);
|
|
ns_lock(&o->lock);
|
|
if (o->type != NS_FREE && o->gen == gen) {
|
|
if (!d->counts[s]++) o->holders[d->proc / 64] |= 1ull << (d->proc % 64);
|
|
e = ns_entry_obj(d, gen, s);
|
|
d->refs++;
|
|
__atomic_store_n(&ns_fdt[fd], e, __ATOMIC_RELEASE);
|
|
}
|
|
ns_unlock(&o->lock);
|
|
out:
|
|
pthread_mutex_unlock(&ns_mx);
|
|
ns_restore(&old);
|
|
return e;
|
|
}
|
|
|
|
static uint64_t ns_note_device(int fd) {
|
|
struct stat st;
|
|
uint64_t e = 0;
|
|
if (syscall(SYS_fstat, fd, &st) != 0 || !S_ISREG(st.st_mode)) return 0;
|
|
if (((uint64_t)st.st_size & ~0xffffull) != NS_DEV_MAGIC) return 0;
|
|
sigset_t old;
|
|
ns_block(&old);
|
|
pthread_mutex_lock(&ns_mx);
|
|
struct ns_dev *d = ns_table() ? ns_map(fd, (uint32_t)st.st_size & 0xffff, (uint64_t)st.st_ino) : NULL;
|
|
if (d) {
|
|
e = __atomic_load_n(&ns_fdt[fd], __ATOMIC_ACQUIRE);
|
|
if (!e) {
|
|
e = ns_entry_dev(d);
|
|
d->refs++;
|
|
__atomic_store_n(&ns_fdt[fd], e, __ATOMIC_RELEASE);
|
|
}
|
|
}
|
|
pthread_mutex_unlock(&ns_mx);
|
|
ns_restore(&old);
|
|
return e;
|
|
}
|
|
|
|
static uint64_t ns_lookup(int fd) {
|
|
if (fd < 0 || fd >= NS_FDS) return 0;
|
|
uint64_t *t = __atomic_load_n(&ns_fdt, __ATOMIC_ACQUIRE);
|
|
if (t) {
|
|
uint64_t e = __atomic_load_n(&t[fd], __ATOMIC_ACQUIRE);
|
|
if (e) return e;
|
|
}
|
|
int saved = errno;
|
|
struct itimerspec its;
|
|
uint64_t e = 0;
|
|
if (timerfd_gettime(fd, &its) == 0) {
|
|
uint64_t tok = (uint64_t)its.it_interval.tv_sec * 1000000000ull + (uint64_t)its.it_interval.tv_nsec;
|
|
if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0 && (tok >> 56) == NS_OBJ_MAGIC) {
|
|
struct ns_dev *d = NULL;
|
|
uint32_t tag = (uint32_t)(tok >> 40) & 0xffff;
|
|
int n = __atomic_load_n(&ns_ndev, __ATOMIC_ACQUIRE);
|
|
for (int i = 0; i < n && !d; i++)
|
|
if (__atomic_load_n(&ns_devs[i].tag, __ATOMIC_ACQUIRE) == tag) d = &ns_devs[i];
|
|
if (d) e = ns_adopt(d, tag, fd, (uint32_t)(tok >> 20) & 0xfffff, (uint32_t)tok & 0xfffff);
|
|
}
|
|
} else {
|
|
e = ns_note_device(fd);
|
|
}
|
|
errno = saved;
|
|
return e;
|
|
}
|
|
|
|
static int ns_ready(struct ns_dev *d) {
|
|
if (__atomic_load_n(&ns_fdt, __ATOMIC_ACQUIRE) && __atomic_load_n(&d->proc, __ATOMIC_ACQUIRE) >= 0) return 1;
|
|
pthread_mutex_lock(&ns_mx);
|
|
int ok = ns_table() && ns_attach(d) >= 0;
|
|
pthread_mutex_unlock(&ns_mx);
|
|
return ok;
|
|
}
|
|
|
|
static void ns_thread_gone(void *p) {
|
|
struct ns_tls *t = p;
|
|
if (!t || !t->d || !t->q) return;
|
|
sigset_t old;
|
|
struct ns_dev *d = t->d;
|
|
ns_block(&old);
|
|
ns_free_q(d->h, t->q - 1);
|
|
t->q = 0;
|
|
t->waiting = 0;
|
|
t->d = NULL;
|
|
ns_restore(&old);
|
|
ns_dev_put(d);
|
|
}
|
|
|
|
static void ns_key_init(void) {
|
|
pthread_key_create(&ns_key, ns_thread_gone);
|
|
}
|
|
|
|
static struct ns_q *ns_thread_q(struct ns_dev *d, uint32_t *qi) {
|
|
struct ns_head *h = d->h;
|
|
if (ns_tq.d && ns_tq.q && ns_tq.waiting) {
|
|
ns_drop_q(ns_tq.d->h, ns_tq.q - 1);
|
|
ns_tq.waiting = 0;
|
|
}
|
|
if (ns_tq.d == d && ns_tq.q) {
|
|
*qi = ns_tq.q - 1;
|
|
return ns_q(h, *qi);
|
|
}
|
|
if (ns_tq.d && ns_tq.q) {
|
|
struct ns_dev *old = ns_tq.d;
|
|
ns_free_q(old->h, ns_tq.q - 1);
|
|
ns_tq.q = 0;
|
|
ns_tq.d = NULL;
|
|
ns_dev_put(old);
|
|
}
|
|
pthread_once(&ns_once, ns_key_init);
|
|
uint32_t i;
|
|
if (!ns_pop(h, &h->q_free, 0, &i) && !ns_grow(&h->q_hwm, NS_QS, &i)) {
|
|
ns_sweep_dead(h);
|
|
if (!ns_pop(h, &h->q_free, 0, &i)) return NULL;
|
|
}
|
|
struct ns_q *q = ns_q(h, i);
|
|
q->proc = (uint32_t)d->proc;
|
|
q->waiting = 0;
|
|
q->inuse = 1;
|
|
__atomic_add_fetch(&d->refs, 1, __ATOMIC_ACQ_REL);
|
|
ns_tq.d = d;
|
|
ns_tq.q = i + 1;
|
|
ns_tq.waiting = 0;
|
|
pthread_setspecific(ns_key, &ns_tq);
|
|
*qi = i;
|
|
return q;
|
|
}
|
|
|
|
static int ns_create(struct ns_dev *d, int type, uint32_t a, uint32_t b) {
|
|
struct ns_head *h = d->h;
|
|
sigset_t old;
|
|
int fd = -1, err = 0;
|
|
uint32_t s = 0;
|
|
ns_block(&old);
|
|
pthread_mutex_lock(&ns_mx);
|
|
if (!ns_table() || ns_attach(d) < 0) {
|
|
err = ENOMEM;
|
|
goto out;
|
|
}
|
|
if (!ns_pop(h, &h->slot_free, 1, &s) && !ns_grow(&h->slot_hwm, NS_SLOTS, &s)) {
|
|
ns_sweep_dead(h);
|
|
if (!ns_pop(h, &h->slot_free, 1, &s)) {
|
|
err = ENOMEM;
|
|
goto out;
|
|
}
|
|
}
|
|
struct ns_obj *o = ns_obj(h, s);
|
|
ns_lock(&o->lock);
|
|
o->a = a;
|
|
o->b = b;
|
|
o->ownerdead = 0;
|
|
o->all_hint = 0;
|
|
o->any_head = o->any_tail = o->all_head = o->all_tail = 0;
|
|
memset(o->holders, 0, sizeof(o->holders));
|
|
o->holders[d->proc / 64] = 1ull << (d->proc % 64);
|
|
o->type = (uint32_t)type;
|
|
uint32_t gen = o->gen;
|
|
ns_unlock(&o->lock);
|
|
fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
|
|
if (fd >= 0) {
|
|
uint64_t tok = ns_token(d->tag, gen, s);
|
|
struct itimerspec its = {{(time_t)(tok / 1000000000ull), (long)(tok % 1000000000ull)}, {0, 0}};
|
|
if (fd >= NS_FDS || timerfd_settime(fd, 0, &its, NULL) != 0) {
|
|
err = fd >= NS_FDS ? EMFILE : errno;
|
|
ns_close_fd(fd);
|
|
fd = -1;
|
|
}
|
|
} else {
|
|
err = errno;
|
|
}
|
|
ns_lock(&o->lock);
|
|
if (fd < 0) {
|
|
o->holders[d->proc / 64] &= ~(1ull << (d->proc % 64));
|
|
ns_maybe_free(h, o);
|
|
} else {
|
|
d->counts[s] = 1;
|
|
d->refs++;
|
|
__atomic_store_n(&ns_fdt[fd], ns_entry_obj(d, gen, s), __ATOMIC_RELEASE);
|
|
}
|
|
ns_unlock(&o->lock);
|
|
out:
|
|
pthread_mutex_unlock(&ns_mx);
|
|
ns_restore(&old);
|
|
if (fd < 0) errno = err;
|
|
return fd;
|
|
}
|
|
|
|
static int ns_expired(const struct ns_wait_args *args) {
|
|
struct timespec now;
|
|
if (clock_gettime((args->flags & NS_WAIT_REALTIME) ? CLOCK_REALTIME : CLOCK_MONOTONIC, &now)) return 0;
|
|
return (uint64_t)now.tv_sec * 1000000000ull + (uint64_t)now.tv_nsec >= args->timeout;
|
|
}
|
|
|
|
static int ns_sleep(struct ns_q *q, const struct ns_wait_args *args) {
|
|
struct timespec ts, *tsp = NULL;
|
|
int op = FUTEX_WAIT_BITSET | ((args->flags & NS_WAIT_REALTIME) ? FUTEX_CLOCK_REALTIME : 0);
|
|
if (args->timeout != UINT64_MAX) {
|
|
ts.tv_sec = (time_t)(args->timeout / 1000000000ull);
|
|
ts.tv_nsec = (long)(args->timeout % 1000000000ull);
|
|
tsp = &ts;
|
|
}
|
|
while (__atomic_load_n(&q->signaled, __ATOMIC_ACQUIRE) == -1) {
|
|
if (tsp && ns_expired(args)) return ETIMEDOUT;
|
|
if (ns_futex(&q->signaled, op, (uint32_t)-1, tsp, FUTEX_BITSET_MATCH_ANY) == -1) {
|
|
if (errno == ETIMEDOUT) return ETIMEDOUT;
|
|
if (errno == EINTR) return EINTR;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int ns_wait(struct ns_dev *d, struct ns_wait_args *ua, int all) {
|
|
struct ns_head *h = d->h;
|
|
struct ns_wait_args args;
|
|
int fds[NS_ENTS];
|
|
uint32_t slots[NS_ENTS], gens[NS_ENTS];
|
|
memcpy(&args, ua, sizeof(args));
|
|
if (args.pad || (args.flags & ~NS_WAIT_REALTIME) || !args.owner || args.count > NS_MAX_WAIT) return EINVAL;
|
|
uint32_t total = args.count + (args.alert ? 1 : 0);
|
|
if (args.count) memcpy(fds, (const void *)(uintptr_t)args.objs, args.count * sizeof(int));
|
|
if (args.alert) fds[args.count] = (int)args.alert;
|
|
for (uint32_t i = 0; i < total; i++) {
|
|
uint64_t e = ns_lookup(fds[i]);
|
|
if (!e || (e & NS_E_DEVICE) || &ns_devs[(e >> 56) & 7] != d) return EINVAL;
|
|
slots[i] = (uint32_t)(e & 0xfffff);
|
|
gens[i] = (uint32_t)(e >> 20) & 0xfffff;
|
|
if (all && i < args.count)
|
|
for (uint32_t j = 0; j < i; j++)
|
|
if (slots[j] == slots[i]) return EINVAL;
|
|
}
|
|
sigset_t old;
|
|
ns_block(&old);
|
|
uint32_t qi;
|
|
struct ns_q *q = ns_ready(d) ? ns_thread_q(d, &qi) : NULL;
|
|
if (!q) {
|
|
ns_restore(&old);
|
|
return ENOMEM;
|
|
}
|
|
ns_maybe_sweep(h);
|
|
__atomic_store_n(&q->signaled, -1, __ATOMIC_RELAXED);
|
|
q->owner = args.owner;
|
|
q->tid = ns_self();
|
|
q->pid = ns_getpid();
|
|
q->count = args.count;
|
|
q->total = 0;
|
|
q->all = (uint32_t)all;
|
|
q->ownerdead = 0;
|
|
for (uint32_t i = 0; i < total; i++) {
|
|
q->ent[i].obj = UINT32_MAX;
|
|
q->ent[i].index = i;
|
|
q->ent[i].linked = 0;
|
|
q->ent[i].next = q->ent[i].prev = 0;
|
|
}
|
|
q->total = total;
|
|
q->waiting = 1;
|
|
ns_tq.waiting = 1;
|
|
int bad = 0;
|
|
if (!all) {
|
|
for (uint32_t i = 0; i < total && !bad; i++) {
|
|
struct ns_obj *o = ns_obj(h, slots[i]);
|
|
int a = ns_lock_obj(h, o);
|
|
if (o->type == NS_FREE || o->gen != gens[i]) {
|
|
bad = 1;
|
|
} else {
|
|
o->busy++;
|
|
q->ent[i].obj = slots[i];
|
|
ns_link(h, &o->any_head, &o->any_tail, ns_node_id(qi, i));
|
|
}
|
|
ns_unlock_obj(h, o, a);
|
|
}
|
|
for (uint32_t i = 0; i < total && !bad; i++) {
|
|
if (__atomic_load_n(&q->signaled, __ATOMIC_ACQUIRE) != -1) break;
|
|
struct ns_obj *o = ns_obj(h, slots[i]);
|
|
int a = ns_lock_obj(h, o);
|
|
ns_wake_any(h, o);
|
|
ns_unlock_obj(h, o, a);
|
|
}
|
|
} else {
|
|
ns_lock(&h->all_lock);
|
|
for (uint32_t i = 0; i < total && !bad; i++) {
|
|
struct ns_obj *o = ns_obj(h, slots[i]);
|
|
ns_lock(&o->lock);
|
|
if (o->type == NS_FREE || o->gen != gens[i]) {
|
|
bad = 1;
|
|
} else {
|
|
o->busy++;
|
|
q->ent[i].obj = slots[i];
|
|
if (i < args.count) {
|
|
o->all_hint++;
|
|
ns_link(h, &o->all_head, &o->all_tail, ns_node_id(qi, i));
|
|
} else {
|
|
ns_link(h, &o->any_head, &o->any_tail, ns_node_id(qi, i));
|
|
}
|
|
}
|
|
ns_unlock(&o->lock);
|
|
}
|
|
if (!bad) ns_wake_all_q(h, q, NULL);
|
|
ns_unlock(&h->all_lock);
|
|
if (!bad && args.alert && __atomic_load_n(&q->signaled, __ATOMIC_ACQUIRE) == -1) {
|
|
struct ns_obj *o = ns_obj(h, slots[args.count]);
|
|
int a = ns_lock_obj(h, o);
|
|
ns_wake_any(h, o);
|
|
ns_unlock_obj(h, o, a);
|
|
}
|
|
}
|
|
int ret = 0;
|
|
if (!bad && __atomic_load_n(&q->signaled, __ATOMIC_ACQUIRE) == -1) {
|
|
sigset_t held;
|
|
ns_restore(&old);
|
|
ret = ns_sleep(q, &args);
|
|
ns_block(&held);
|
|
}
|
|
ns_drop_q(h, qi);
|
|
ns_tq.waiting = 0;
|
|
int32_t got = __atomic_load_n(&q->signaled, __ATOMIC_ACQUIRE);
|
|
int dead = (int)q->ownerdead;
|
|
ns_restore(&old);
|
|
if (bad) return EINVAL;
|
|
if (got != -1) {
|
|
ua->index = (uint32_t)got;
|
|
return dead ? EOWNERDEAD : 0;
|
|
}
|
|
return ret ? ret : ETIMEDOUT;
|
|
}
|
|
|
|
static int ns_obj_ioctl(struct ns_dev *d, uint64_t e, unsigned long req, void *arg) {
|
|
struct ns_head *h = d->h;
|
|
uint32_t s = (uint32_t)(e & 0xfffff), gen = (uint32_t)(e >> 20) & 0xfffff;
|
|
struct ns_obj *o = ns_obj(h, s);
|
|
uint32_t type = __atomic_load_n(&o->type, __ATOMIC_ACQUIRE), u32 = 0, prev = 0;
|
|
struct ns_mutex_args margs = {0, 0};
|
|
int want, ret = 0;
|
|
switch (req) {
|
|
case NS_SEM_RELEASE:
|
|
memcpy(&u32, arg, sizeof(u32));
|
|
want = NS_SEM;
|
|
break;
|
|
case NS_SEM_READ:
|
|
want = NS_SEM;
|
|
break;
|
|
case NS_MUTEX_UNLOCK:
|
|
memcpy(&margs, arg, sizeof(margs));
|
|
if (!margs.owner) return EINVAL;
|
|
want = NS_MUTEX;
|
|
break;
|
|
case NS_MUTEX_KILL:
|
|
memcpy(&u32, arg, sizeof(u32));
|
|
if (!u32) return EINVAL;
|
|
if (type == NS_MUTEX && __atomic_load_n(&o->b, __ATOMIC_ACQUIRE) != u32) return EPERM;
|
|
want = NS_MUTEX;
|
|
break;
|
|
case NS_MUTEX_READ:
|
|
want = NS_MUTEX;
|
|
break;
|
|
case NS_EVENT_SET:
|
|
case NS_EVENT_RESET:
|
|
case NS_EVENT_PULSE:
|
|
case NS_EVENT_READ:
|
|
want = NS_EVENT;
|
|
break;
|
|
default:
|
|
return ENOTTY;
|
|
}
|
|
if (type != (uint32_t)want) return EINVAL;
|
|
sigset_t old;
|
|
ns_block(&old);
|
|
int all = ns_lock_obj(h, o);
|
|
if (o->type != (uint32_t)want || o->gen != gen) {
|
|
ns_unlock_obj(h, o, all);
|
|
ns_restore(&old);
|
|
return EINVAL;
|
|
}
|
|
switch (req) {
|
|
case NS_SEM_RELEASE: {
|
|
prev = o->a;
|
|
uint32_t sum = o->a + u32;
|
|
if (sum < o->a || sum > o->b) {
|
|
ret = EOVERFLOW;
|
|
break;
|
|
}
|
|
o->a = sum;
|
|
if (all) ns_wake_all_obj(h, o);
|
|
ns_wake_any(h, o);
|
|
break;
|
|
}
|
|
case NS_SEM_READ: {
|
|
struct ns_sem_args r = {o->a, o->b};
|
|
memcpy(arg, &r, sizeof(r));
|
|
break;
|
|
}
|
|
case NS_MUTEX_UNLOCK:
|
|
prev = o->a;
|
|
if (o->b != margs.owner) {
|
|
ret = EPERM;
|
|
break;
|
|
}
|
|
if (!--o->a) o->b = 0;
|
|
if (all) ns_wake_all_obj(h, o);
|
|
ns_wake_any(h, o);
|
|
break;
|
|
case NS_MUTEX_KILL:
|
|
if (o->b != u32) {
|
|
ret = EPERM;
|
|
break;
|
|
}
|
|
o->ownerdead = 1;
|
|
o->b = 0;
|
|
o->a = 0;
|
|
if (all) ns_wake_all_obj(h, o);
|
|
ns_wake_any(h, o);
|
|
break;
|
|
case NS_MUTEX_READ: {
|
|
struct ns_mutex_args r = {o->b, o->a};
|
|
memcpy(arg, &r, sizeof(r));
|
|
if (o->ownerdead) ret = EOWNERDEAD;
|
|
break;
|
|
}
|
|
case NS_EVENT_SET:
|
|
case NS_EVENT_PULSE:
|
|
prev = o->b;
|
|
o->b = 1;
|
|
if (all) ns_wake_all_obj(h, o);
|
|
ns_wake_any(h, o);
|
|
if (req == NS_EVENT_PULSE) o->b = 0;
|
|
break;
|
|
case NS_EVENT_RESET:
|
|
prev = o->b;
|
|
o->b = 0;
|
|
break;
|
|
case NS_EVENT_READ: {
|
|
struct ns_event_args r = {o->a, o->b};
|
|
memcpy(arg, &r, sizeof(r));
|
|
break;
|
|
}
|
|
}
|
|
ns_unlock_obj(h, o, all);
|
|
ns_restore(&old);
|
|
if (ret) return ret;
|
|
if (req == NS_SEM_RELEASE || req == NS_EVENT_SET || req == NS_EVENT_RESET || req == NS_EVENT_PULSE)
|
|
memcpy(arg, &prev, sizeof(prev));
|
|
else if (req == NS_MUTEX_UNLOCK)
|
|
memcpy((char *)arg + offsetof(struct ns_mutex_args, count), &prev, sizeof(prev));
|
|
return 0;
|
|
}
|
|
|
|
static int ns_dev_ioctl(struct ns_dev *d, unsigned long req, void *arg, int *fd) {
|
|
switch (req) {
|
|
case NS_CREATE_SEM: {
|
|
struct ns_sem_args a;
|
|
memcpy(&a, arg, sizeof(a));
|
|
if (a.count > a.max) return EINVAL;
|
|
return (*fd = ns_create(d, NS_SEM, a.count, a.max)) < 0 ? errno : 0;
|
|
}
|
|
case NS_CREATE_MUTEX: {
|
|
struct ns_mutex_args a;
|
|
memcpy(&a, arg, sizeof(a));
|
|
if (!a.owner != !a.count) return EINVAL;
|
|
return (*fd = ns_create(d, NS_MUTEX, a.count, a.owner)) < 0 ? errno : 0;
|
|
}
|
|
case NS_CREATE_EVENT: {
|
|
struct ns_event_args a;
|
|
memcpy(&a, arg, sizeof(a));
|
|
return (*fd = ns_create(d, NS_EVENT, a.manual, a.signaled)) < 0 ? errno : 0;
|
|
}
|
|
case NS_WAIT_ANY:
|
|
return ns_wait(d, arg, 0);
|
|
case NS_WAIT_ALL:
|
|
return ns_wait(d, arg, 1);
|
|
}
|
|
return ENOTTY;
|
|
}
|
|
|
|
int bl_ntsync_ioctl(int fd, unsigned long req, void *arg, int *rc) __attribute__((visibility("hidden")));
|
|
int bl_ntsync_ioctl(int fd, unsigned long req, void *arg, int *rc) {
|
|
if (_IOC_TYPE(req) != 'N' || _IOC_NR(req) < 0x80 || _IOC_NR(req) > 0x8d) return 0;
|
|
if (!__atomic_load_n(&ns_ndev, __ATOMIC_ACQUIRE) && !ns_on()) return 0;
|
|
uint64_t e = ns_lookup(fd);
|
|
if (!e) return 0;
|
|
struct ns_dev *d = &ns_devs[(e >> 56) & 7];
|
|
if (!ns_dev_get(d)) return 0;
|
|
int out = 0, err = (e & NS_E_DEVICE) ? ns_dev_ioctl(d, req, arg, &out) : ns_obj_ioctl(d, e, req, arg);
|
|
ns_dev_put(d);
|
|
if (err) {
|
|
errno = err;
|
|
*rc = -1;
|
|
} else {
|
|
*rc = out;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int bl_ntsync_open(const char *path, int flags) __attribute__((visibility("hidden")));
|
|
int bl_ntsync_open(const char *path, int flags) {
|
|
if (!path || strcmp(path, "/dev/ntsync") != 0 || !ns_on()) return -2;
|
|
int fd = (int)syscall(SYS_openat, AT_FDCWD, path, flags, 0);
|
|
if (fd >= 0) return fd;
|
|
fd = memfd_create("droiddeck-ntsync", (flags & O_CLOEXEC) ? MFD_CLOEXEC : 0);
|
|
if (fd < 0) return -1;
|
|
uint32_t tag = 0;
|
|
for (int tries = 0; tries < 64 && (!tag || ns_find(tag)); tries++) {
|
|
uint16_t r = 0;
|
|
if (getrandom(&r, sizeof(r), GRND_NONBLOCK) != sizeof(r)) r = (uint16_t)(ns_getpid() * 40503u + (uint32_t)tries * 9973u + (uint32_t)time(NULL));
|
|
tag = r;
|
|
}
|
|
struct ns_head *h = MAP_FAILED;
|
|
if (fd >= NS_FDS || ftruncate(fd, (off_t)(NS_DEV_MAGIC | tag)) != 0
|
|
|| (h = mmap(NULL, NS_REGION, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
|
|
int err = fd >= NS_FDS ? EMFILE : errno;
|
|
ns_close_fd(fd);
|
|
errno = err;
|
|
return -1;
|
|
}
|
|
h->version = NS_VERSION;
|
|
h->tag = tag;
|
|
__atomic_store_n(&h->magic, NS_HEAD_MAGIC, __ATOMIC_RELEASE);
|
|
munmap(h, NS_REGION);
|
|
if (!ns_note_device(fd)) {
|
|
ns_close_fd(fd);
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
static void ns_received(struct msghdr *msg) {
|
|
for (struct cmsghdr *c = CMSG_FIRSTHDR(msg); c; c = CMSG_NXTHDR(msg, c)) {
|
|
if (c->cmsg_level != SOL_SOCKET || c->cmsg_type != SCM_RIGHTS) continue;
|
|
size_t n = (c->cmsg_len - CMSG_LEN(0)) / sizeof(int);
|
|
int *fds = (int *)CMSG_DATA(c);
|
|
for (size_t i = 0; i < n; i++) {
|
|
int fd = fds[i];
|
|
if (fd < 0 || fd >= NS_FDS) continue;
|
|
if (ns_fdt && __atomic_load_n(&ns_fdt[fd], __ATOMIC_ACQUIRE)) ns_forget(fd);
|
|
ns_note_device(fd);
|
|
}
|
|
}
|
|
}
|
|
|
|
ssize_t recvmsg(int fd, struct msghdr *msg, int flags) {
|
|
static ssize_t (*real)(int, struct msghdr *, int);
|
|
if (!real) real = (ssize_t (*)(int, struct msghdr *, int))dlsym(RTLD_NEXT, "recvmsg");
|
|
ssize_t r = real(fd, msg, flags);
|
|
if (r >= 0 && msg && msg->msg_control && msg->msg_controllen && ns_on()) {
|
|
int saved = errno;
|
|
ns_received(msg);
|
|
errno = saved;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void bl_fsync_fds_closed(unsigned int first, unsigned int last) __attribute__((visibility("hidden")));
|
|
int bl_meta_on(void) __attribute__((visibility("hidden")));
|
|
uint64_t bl_meta_now(void) __attribute__((visibility("hidden")));
|
|
void bl_meta_log(const char *op, int dirfd, const char *path, int flags, uint64_t start, long result, int err)
|
|
__attribute__((visibility("hidden")));
|
|
|
|
int close(int fd) {
|
|
if (fd >= 0) bl_fsync_fds_closed((unsigned int)fd, (unsigned int)fd);
|
|
uint64_t *t = __atomic_load_n(&ns_fdt, __ATOMIC_ACQUIRE);
|
|
if (t && fd >= 0 && fd < NS_FDS && __atomic_load_n(&t[fd], __ATOMIC_ACQUIRE)) ns_forget(fd);
|
|
if (!bl_meta_on()) return ns_close_fd(fd);
|
|
uint64_t t0 = bl_meta_now();
|
|
int r = ns_close_fd(fd);
|
|
int e = errno;
|
|
if (bl_meta_now() - t0 > 1000000ULL) bl_meta_log("close", fd, "/steamapps/(slow close)", 0, t0, r, e);
|
|
errno = e;
|
|
return r;
|
|
}
|
|
|
|
int dup2(int oldfd, int newfd) {
|
|
static int (*real)(int, int);
|
|
if (!real) real = (int (*)(int, int))dlsym(RTLD_NEXT, "dup2");
|
|
if (oldfd != newfd && newfd >= 0) bl_fsync_fds_closed((unsigned int)newfd, (unsigned int)newfd);
|
|
uint64_t *t = __atomic_load_n(&ns_fdt, __ATOMIC_ACQUIRE);
|
|
if (t && oldfd != newfd && newfd >= 0 && newfd < NS_FDS && __atomic_load_n(&t[newfd], __ATOMIC_ACQUIRE)) ns_forget(newfd);
|
|
return real(oldfd, newfd);
|
|
}
|
|
|
|
int dup3(int oldfd, int newfd, int flags) {
|
|
static int (*real)(int, int, int);
|
|
if (!real) real = (int (*)(int, int, int))dlsym(RTLD_NEXT, "dup3");
|
|
if (oldfd != newfd && newfd >= 0) bl_fsync_fds_closed((unsigned int)newfd, (unsigned int)newfd);
|
|
uint64_t *t = __atomic_load_n(&ns_fdt, __ATOMIC_ACQUIRE);
|
|
if (t && oldfd != newfd && newfd >= 0 && newfd < NS_FDS && __atomic_load_n(&t[newfd], __ATOMIC_ACQUIRE)) ns_forget(newfd);
|
|
return real(oldfd, newfd, flags);
|
|
}
|
|
|
|
static void ns_forget_range(unsigned int first, unsigned int last) {
|
|
uint64_t *t = __atomic_load_n(&ns_fdt, __ATOMIC_ACQUIRE);
|
|
if (!t) return;
|
|
for (unsigned int fd = first; fd <= last && fd < (unsigned int)NS_FDS; fd++)
|
|
if (__atomic_load_n(&t[fd], __ATOMIC_ACQUIRE)) ns_forget((int)fd);
|
|
}
|
|
|
|
int close_range(unsigned int first, unsigned int last, int flags) {
|
|
static int (*real)(unsigned int, unsigned int, int);
|
|
if (!real) real = (int (*)(unsigned int, unsigned int, int))dlsym(RTLD_NEXT, "close_range");
|
|
if (!(flags & CLOSE_RANGE_CLOEXEC)) {
|
|
bl_fsync_fds_closed(first, last);
|
|
ns_forget_range(first, last);
|
|
}
|
|
if (!real) {
|
|
errno = ENOSYS;
|
|
return -1;
|
|
}
|
|
return real(first, last, flags);
|
|
}
|
|
|
|
void closefrom(int lowfd) {
|
|
static void (*real)(int);
|
|
if (!real) real = (void (*)(int))dlsym(RTLD_NEXT, "closefrom");
|
|
if (lowfd >= 0) {
|
|
bl_fsync_fds_closed((unsigned int)lowfd, ~0u);
|
|
ns_forget_range((unsigned int)lowfd, ~0u);
|
|
}
|
|
if (real) real(lowfd);
|
|
}
|
|
|
|
static void ns_fork_prepare(void) {
|
|
pthread_mutex_lock(&ns_mx);
|
|
}
|
|
|
|
static void ns_fork_parent(void) {
|
|
pthread_mutex_unlock(&ns_mx);
|
|
}
|
|
|
|
static void ns_fork_child(void) {
|
|
pthread_mutex_init(&ns_mx, NULL);
|
|
ns_pid = 0;
|
|
ns_tid = 0;
|
|
ns_fdt = NULL;
|
|
memset(&ns_tq, 0, sizeof(ns_tq));
|
|
for (int i = 0; i < ns_ndev; i++) {
|
|
ns_devs[i].proc = -1;
|
|
ns_devs[i].counts = NULL;
|
|
ns_devs[i].refs = 0;
|
|
}
|
|
}
|
|
|
|
__attribute__((constructor)) static void ns_init(void) {
|
|
pthread_atfork(ns_fork_prepare, ns_fork_parent, ns_fork_child);
|
|
}
|