From b4f4cb40a6f3f4ecc05c7b6c8f2e852282234519 Mon Sep 17 00:00:00 2001 From: notaz Date: Sat, 9 Jan 2016 21:09:46 +0200 Subject: [PATCH] get rid of some libc deps mostly important that game called ginge's code doesn't call libc --- loader/Makefile | 1 + loader/emu.c | 58 +++++++-------- loader/header.h | 8 +-- loader/llibc.c | 6 -- loader/llibc.h | 2 - loader/override.c | 176 +++++++++++++++++++++++++++++++-------------- loader/patches.c | 4 +- loader/realfuncs.h | 13 ++++ loader/syscalls.S | 9 ++- loader/syscalls.h | 13 +++- 10 files changed, 186 insertions(+), 104 deletions(-) diff --git a/loader/Makefile b/loader/Makefile index eff5e2f..f326814 100644 --- a/loader/Makefile +++ b/loader/Makefile @@ -4,6 +4,7 @@ CC = $(CROSS_COMPILE)gcc AS = $(CROSS_COMPILE)as CFLAGS += -Wall -ggdb -DLOADER LDFLAGS += -ggdb +#LDFLAGS += -nodefaultlibs # NYET ifndef DEBUG CFLAGS += -O2 -fno-strict-aliasing LDFLAGS += -O2 diff --git a/loader/emu.c b/loader/emu.c index 64c2c99..d142611 100644 --- a/loader/emu.c +++ b/loader/emu.c @@ -370,7 +370,7 @@ static void *fb_sync_thread(void *arg) && wait_ret != -ETIMEDOUT) { err("fb_thread: futex error: %d\n", wait_ret); - g_sleep(1); + sleep(1); goto check_keys; } if (fb_sync_thread_paused) { @@ -977,8 +977,8 @@ void emu_init(void *map_bottom) #ifdef PND if (geteuid() == 0) { - fprintf(stderr, "don't try to run as root, device registers or memory " - "might get trashed crashing the OS or even damaging the device.\n"); + err("don't try to run as root, device registers or memory " + "might get trashed crashing the OS or even damaging the device.\n"); exit(1); } #endif @@ -1051,11 +1051,11 @@ void emu_init(void *map_bottom) sigaction(SIGSEGV, &segv_action, NULL); } -int emu_read_gpiodev(void *buf, int count) +long emu_read_gpiodev(void *buf, int count) { if (count <= 0) { err("gpiodev read %d?\n", count); - return -1; + return -EINVAL; } if (count > 4) count = 4; @@ -1065,25 +1065,24 @@ int emu_read_gpiodev(void *buf, int count) return count; } -static void *emu_mmap_dev(unsigned int length, int prot, int flags, unsigned int offset) +static long emu_mmap_dev(unsigned int length, int prot, int flags, unsigned int offset) { u8 *umem, *umem_end; // SoC regs if ((offset & ~0x1ffff) == 0xc0000000) { - return mmap((void *)0x7f000000, length, PROT_NONE, + return g_mmap2_raw((void *)0x7f000000, length, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED|MAP_NORESERVE, -1, 0); } // MMSP2 blitter if ((offset & ~0xffff) == 0xe0020000) { - return mmap((void *)0x7f100000, length, PROT_NONE, + return g_mmap2_raw((void *)0x7f100000, length, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED|MAP_NORESERVE, -1, 0); } // upper mem if ((offset & 0xfe000000) != 0x02000000) { err("unexpected devmem mmap @ %08x\n", offset); - errno = EINVAL; - return MAP_FAILED; + return -EINVAL; } umem = uppermem_lookup(offset, &umem_end); @@ -1092,10 +1091,11 @@ static void *emu_mmap_dev(unsigned int length, int prot, int flags, unsigned int offset, umem + length - umem_end); dbg("upper mem @ %08x %x = %p\n", offset, length, umem); - return umem; + return (long)umem; } -void *emu_do_mmap(unsigned int length, int prot, int flags, int fd, unsigned int offset) +long emu_do_mmap(unsigned int length, int prot, int flags, int fd, + unsigned int offset) { if (fd == FAKEDEV_MEM) return emu_mmap_dev(length, prot, flags, offset); @@ -1107,11 +1107,10 @@ void *emu_do_mmap(unsigned int length, int prot, int flags, int fd, unsigned int return emu_mmap_dev(length, prot, flags, offset + 0x03381000); err("bad/ni mmap(?, %d, %x, %x, %d, %08x)\n", length, prot, flags, fd, offset); - errno = EINVAL; - return MAP_FAILED; + return -EINVAL; } -int emu_do_munmap(void *addr, unsigned int length) +long emu_do_munmap(void *addr, unsigned int length) { u8 *p = addr; @@ -1131,15 +1130,13 @@ static void emu_sound_open(int fd) // set default buffer size to 16 * 1K frag = (16<<16) | 10; // 16K - ret = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag); - if (ret != 0) { - err("snd ioctl SETFRAGMENT %08x: ", frag); - perror(NULL); - } + ret = g_ioctl_raw(fd, SNDCTL_DSP_SETFRAGMENT, &frag); + if (ret != 0) + err("snd ioctl SETFRAGMENT %08x: %d\n", frag, ret); #endif } -static int emu_sound_ioctl(int fd, int request, void *argp) +static long emu_sound_ioctl(int fd, int request, void *argp) { int *arg = argp; @@ -1155,7 +1152,9 @@ static int emu_sound_ioctl(int fd, int request, void *argp) * Catch this and set to something that works. */ switch(request) { case SNDCTL_DSP_SETFRAGMENT: { - int ret, bsize, frag, frag_cnt; + int bsize, frag, frag_cnt; + long ret; + if (arg == NULL) break; @@ -1180,11 +1179,9 @@ static int emu_sound_ioctl(int fd, int request, void *argp) } frag |= frag_cnt << 16; - ret = ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag); - if (ret != 0) { - err("snd ioctl SETFRAGMENT %08x: ", frag); - perror(NULL); - } + ret = g_ioctl_raw(fd, SNDCTL_DSP_SETFRAGMENT, &frag); + if (ret != 0) + err("snd ioctl SETFRAGMENT %08x: %ld\n", frag, ret); // indicate success even if we fail (because of ALSA mostly), // things like MikMod will bail out otherwise. return 0; @@ -1196,10 +1193,10 @@ static int emu_sound_ioctl(int fd, int request, void *argp) break; } - return ioctl(fd, request, argp); + return g_ioctl_raw(fd, request, argp); } -int emu_do_ioctl(int fd, int request, void *argp) +long emu_do_ioctl(int fd, int request, void *argp) { if (fd == emu_interesting_fds[IFD_SOUND].fd) return emu_sound_ioctl(fd, request, argp); @@ -1265,8 +1262,7 @@ int emu_do_ioctl(int fd, int request, void *argp) fail: err("bad/ni ioctl(%d, %08x, %p)\n", fd, request, argp); - errno = EINVAL; - return -1; + return -EINVAL; } struct dev_fd_t emu_interesting_fds[] = { diff --git a/loader/header.h b/loader/header.h index 4bc6e1b..208e5ab 100644 --- a/loader/header.h +++ b/loader/header.h @@ -43,10 +43,10 @@ struct op_context; void emu_init(void *map_bottom); void emu_call_handle_op(struct op_context *op_ctx); -void *emu_do_mmap(unsigned int length, int prot, int flags, int fd, unsigned int offset); -int emu_do_munmap(void *addr, unsigned int length); -int emu_do_ioctl(int fd, int request, void *argp); -int emu_read_gpiodev(void *buf, int count); +long emu_do_mmap(unsigned int length, int prot, int flags, int fd, unsigned int offset); +long emu_do_munmap(void *addr, unsigned int length); +long emu_do_ioctl(int fd, int request, void *argp); +long emu_read_gpiodev(void *buf, int count); void *emu_do_fopen(const char *path, const char *mode); int emu_do_system(const char *command); int emu_do_execve(const char *filename, char *const argv[], char *const envp[]); diff --git a/loader/llibc.c b/loader/llibc.c index 484b52d..7885372 100644 --- a/loader/llibc.c +++ b/loader/llibc.c @@ -199,10 +199,4 @@ void g_fprintf(int fd, const char *fmt, ...) g_write_raw(fd, buf, d - buf); } -void g_sleep(unsigned int seconds) -{ - struct timespec ts = { seconds, 0 }; - g_nanosleep_raw(&ts, NULL); -} - // vim:shiftwidth=2:expandtab diff --git a/loader/llibc.h b/loader/llibc.h index 5381972..5bcd4ea 100644 --- a/loader/llibc.h +++ b/loader/llibc.h @@ -3,5 +3,3 @@ void g_fprintf(int fd, const char *fmt, ...) #define g_printf(fmt, ...) \ g_fprintf(1, fmt, ##__VA_ARGS__) - -void g_sleep(unsigned int seconds); diff --git a/loader/override.c b/loader/override.c index 86de405..a9d545b 100644 --- a/loader/override.c +++ b/loader/override.c @@ -1,6 +1,6 @@ /* * GINGE - GINGE Is Not Gp2x Emulator - * (C) notaz, 2010-2011 + * (C) notaz, 2010-2011,2016 * * This work is licensed under the MAME license, see COPYING file for details. */ @@ -19,10 +19,12 @@ #include #include "realfuncs.h" +#include "syscalls.h" +#include "llibc.h" #include "header.h" #if (DBG & 1) -#define strace printf +#define strace g_printf #else #define strace(...) #endif @@ -44,9 +46,10 @@ static const struct dev_fd_t takeover_devs[] = { #endif }; -static int w_open(const char *pathname, int flags, mode_t mode) +static long w_open_raw(const char *pathname, int flags, mode_t mode) { - int i, ret; + long ret; + int i; for (i = 0; i < ARRAY_SIZE(takeover_devs); i++) { const char *p, *oname; @@ -66,7 +69,7 @@ static int w_open(const char *pathname, int flags, mode_t mode) } if (i == ARRAY_SIZE(takeover_devs)) - ret = open(pathname, flags, mode); + ret = g_open_raw(pathname, flags, mode); if (ret >= 0) { for (i = 0; emu_interesting_fds[i].name != NULL; i++) { @@ -80,64 +83,95 @@ static int w_open(const char *pathname, int flags, mode_t mode) } } - strace("open(%s) = %d\n", pathname, ret); + strace("open(%s) = %ld\n", pathname, ret); return ret; } -static void *w_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) +static int w_open(const char *pathname, int flags, mode_t mode) +{ + long ret = w_open_raw(pathname, flags, mode); + return g_syscall_error(ret); +} + +static long w_mmap_raw(void *addr, size_t length, int prot, int flags, + int fd, off_t offset) { - void *ret; + long ret; + if (FAKEDEV_MEM <= fd && fd < FAKEDEV_FD_BOUNDARY) ret = emu_do_mmap(length, prot, flags, fd, offset); else - ret = mmap(addr, length, prot, flags, fd, offset); + ret = g_mmap2_raw(addr, length, prot, flags, fd, offset >> 12); - // threads are using heap before they mmap their stack - // printf needs valid stack for pthread/errno - if (((long)&ret & 0xf0000000) == 0xb0000000) - strace("mmap(%p, %x, %x, %x, %d, %lx) = %p\n", addr, length, prot, flags, fd, (long)offset, ret); + strace("mmap(%p, %x, %x, %x, %d, %lx) = %lx\n", + addr, length, prot, flags, fd, (long)offset, ret); return ret; } + +static long w_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) +{ + long ret = w_mmap_raw(addr, length, prot, flags, fd, offset); + return g_syscall_error(ret); +} #define w_mmap2 w_mmap -static int w_munmap(void *addr, size_t length) +static long w_munmap_raw(void *addr, size_t length) { - int ret; + long ret; + ret = emu_do_munmap(addr, length); if (ret == -EAGAIN) - ret = munmap(addr, length); + ret = g_munmap_raw(addr, length); - if (((long)&ret & 0xf0000000) == 0xb0000000) - strace("munmap(%p, %x) = %d\n", addr, length, ret); + strace("munmap(%p, %x) = %ld\n", addr, length, ret); return ret; } -static ssize_t w_read(int fd, void *buf, size_t count) +static int w_munmap(void *addr, size_t length) +{ + long ret = w_munmap_raw(addr, length); + return g_syscall_error(ret); +} + +long w_read_raw(int fd, void *buf, size_t count) { - ssize_t ret; + long ret; + if (fd == FAKEDEV_GPIO) ret = emu_read_gpiodev(buf, count); else - ret = read(fd, buf, count); + ret = g_read_raw(fd, buf, count); - //strace("read(%d, %p, %d) = %d\n", fd, buf, count, ret); + //strace("read(%d, %p, %ld) = %ld\n", fd, buf, count, ret); return ret; } -static int w_ioctl(int fd, int request, void *argp) +static ssize_t w_read(int fd, void *buf, size_t count) { - int ret; + long ret = w_read_raw(fd, buf, count); + return g_syscall_error(ret); +} + +long w_ioctl_raw(int fd, int request, void *argp) +{ + long ret; if ((FAKEDEV_MEM <= fd && fd < FAKEDEV_FD_BOUNDARY) || fd == emu_interesting_fds[IFD_SOUND].fd) ret = emu_do_ioctl(fd, request, argp); else - ret = ioctl(fd, request, argp); + ret = g_ioctl_raw(fd, request, argp); - strace("ioctl(%d, %08x, %p) = %d\n", fd, request, argp, ret); + strace("ioctl(%d, %08x, %p) = %ld\n", fd, request, argp, ret); return ret; } +static int w_ioctl(int fd, int request, void *argp) +{ + long ret = w_ioctl_raw(fd, request, argp); + return g_syscall_error(ret); +} + static int w_sigaction(int signum, const void *act, void *oldact) { strace("sigaction(%d, %p, %p) = %d\n", signum, act, oldact, 0); @@ -228,13 +262,14 @@ static UNUSED int w_execve(const char *filename, char *const argv[], static int w_chdir(const char *path) { - int ret; + long ret; + if (path != NULL && strstr(path, "/usr/gp2x") != NULL) ret = 0; else - ret = chdir(path); + ret = g_chdir_raw(path); strace("chdir(%s) = %d\n", path, ret); - return ret; + return g_syscall_error(ret); } #undef open @@ -243,6 +278,7 @@ static int w_chdir(const char *path) #undef munmap #undef read #undef ioctl +#undef close #undef sigaction #undef tcgetattr #undef tcsetattr @@ -267,12 +303,12 @@ static int w_chdir(const char *path) static typeof(sym) *p_real_##sym // note: update symver too -MAKE_WRAP_SYM(open); +MAKE_WRAP_SYM_N(open); MAKE_WRAP_SYM(fopen); -MAKE_WRAP_SYM(mmap); -MAKE_WRAP_SYM(munmap); -MAKE_WRAP_SYM(read); -MAKE_WRAP_SYM(ioctl); +MAKE_WRAP_SYM_N(mmap); +MAKE_WRAP_SYM_N(munmap); +MAKE_WRAP_SYM_N(read); +MAKE_WRAP_SYM_N(ioctl); MAKE_WRAP_SYM(sigaction); MAKE_WRAP_SYM(tcgetattr); MAKE_WRAP_SYM(tcsetattr); @@ -283,7 +319,7 @@ MAKE_WRAP_SYM_N(execle); MAKE_WRAP_SYM_N(execv); MAKE_WRAP_SYM_N(execvp); MAKE_WRAP_SYM(execve); -MAKE_WRAP_SYM(chdir); +MAKE_WRAP_SYM_N(chdir); typeof(mmap) mmap2 __attribute__((alias("w_mmap"))); #define REAL_FUNC_NP(name) \ @@ -293,33 +329,33 @@ static const struct { const char *name; void **func_ptr; } real_funcs_np[] = { - REAL_FUNC_NP(open), + //REAL_FUNC_NP(open), REAL_FUNC_NP(fopen), - REAL_FUNC_NP(mmap), - REAL_FUNC_NP(munmap), - REAL_FUNC_NP(read), - REAL_FUNC_NP(ioctl), + //REAL_FUNC_NP(mmap), + //REAL_FUNC_NP(munmap), + //REAL_FUNC_NP(read), + //REAL_FUNC_NP(ioctl), REAL_FUNC_NP(sigaction), REAL_FUNC_NP(tcgetattr), REAL_FUNC_NP(tcsetattr), REAL_FUNC_NP(system), // exec* - skipped REAL_FUNC_NP(execve), - REAL_FUNC_NP(chdir), + //REAL_FUNC_NP(chdir), }; -#define open p_real_open +//#define open p_real_open #define fopen p_real_fopen -#define mmap p_real_mmap -#define munmap p_real_munmap -#define read p_real_read -#define ioctl p_real_ioctl +//#define mmap p_real_mmap +//#define munmap p_real_munmap +//#define read p_real_read +//#define ioctl p_real_ioctl #define sigaction p_real_sigaction #define tcgetattr p_real_tcgetattr #define tcsetattr p_real_tcsetattr #define system p_real_system #define execve p_real_execve -#define chdir p_real_chdir +//#define chdir p_real_chdir #undef MAKE_WRAP_SYM #undef REAL_FUNC_NP @@ -331,11 +367,13 @@ int real_open(const char *pathname, int flags, ...) { va_list ap; mode_t mode; + long ret; va_start(ap, flags); mode = va_arg(ap, mode_t); va_end(ap); - return open(pathname, flags, mode); + ret = g_open_raw(pathname, flags, mode); + return g_syscall_error(ret); } FILE *real_fopen(const char *path, const char *mode) @@ -343,24 +381,34 @@ FILE *real_fopen(const char *path, const char *mode) return fopen(path, mode); } -void *real_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) +void *real_mmap(void *addr, size_t length, int prot, int flags, + int fd, off_t offset) { - return mmap(addr, length, prot, flags, fd, offset); + long ret = g_mmap2_raw(addr, length, prot, flags, fd, offset >> 12); + return (void *)g_syscall_error(ret); } int real_munmap(void *addr, size_t length) { - return munmap(addr, length); + return g_syscall_error(g_munmap_raw(addr, length)); } int real_read(int fd, void *buf, size_t count) { - return read(fd, buf, count); + long ret = g_read_raw(fd, buf, count); + return g_syscall_error(ret); } -int real_ioctl(int d, int request, void *argp) +int real_ioctl(int fd, int request, void *argp) { - return ioctl(d, request, argp); + long ret = g_ioctl_raw(fd, request, argp); + return g_syscall_error(ret); +} + +int real_close(int fd) +{ + long ret = g_close_raw(fd); + return g_syscall_error(ret); } int real_sigaction(int signum, const sigaction_t *act, sigaction_t *oldact) @@ -394,7 +442,25 @@ int real_execve(const char *filename, char *const argv[], int real_chdir(const char *path) { - return chdir(path); + long ret = g_chdir_raw(path); + return g_syscall_error(ret); +} + +void real_sleep(unsigned int seconds) +{ + struct timespec ts = { seconds, 0 }; + g_nanosleep_raw(&ts, NULL); +} + +void real_usleep(unsigned int usec) +{ + struct timespec ts = { usec / 1000000, usec % 1000000 }; + g_nanosleep_raw(&ts, NULL); +} + +void real_exit(int status) +{ + g_exit_group_raw(status); } // vim:shiftwidth=2:expandtab diff --git a/loader/patches.c b/loader/patches.c index a6e3192..ada0043 100644 --- a/loader/patches.c +++ b/loader/patches.c @@ -163,8 +163,8 @@ asm( \ " ldmfd sp!, {r1-r3,r12,lr,pc}\n" \ ); -SVC_CMN_R0_MOV_R4_WRAPPER(hw_read, w_read) -SVC_CMN_R0_MOV_R4_WRAPPER(hw_ioctl, w_ioctl) +SVC_CMN_R0_MOV_R4_WRAPPER(hw_read, w_read_raw) +SVC_CMN_R0_MOV_R4_WRAPPER(hw_ioctl, w_ioctl_raw) #define PATCH_(p, f, t) { sig_##p, sig_mask_##p, ARRAY_SIZE(sig_##p), t, f, #p } #define PATCH(f) PATCH_(f, w_##f, 0) diff --git a/loader/realfuncs.h b/loader/realfuncs.h index 8a4fb58..fe83feb 100644 --- a/loader/realfuncs.h +++ b/loader/realfuncs.h @@ -9,6 +9,10 @@ #include #include +#include "llibc.h" + +#define printf(fmt, ...) \ + g_fprintf(1, fmt, ##__VA_ARGS__) int real_open(const char *pathname, int flags, ...); FILE *real_fopen(const char *path, const char *mode); @@ -16,6 +20,7 @@ void *real_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t of int real_munmap(void *addr, size_t length); int real_read(int fd, void *buf, size_t count); int real_ioctl(int fd, int request, void *argp); +int real_close(int fd); int real_sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); typedef struct sigaction sigaction_t; int real_tcgetattr(int fd, struct termios *termios_p); @@ -24,6 +29,10 @@ int real_system(const char *command); // exec* - skipped int real_execve(const char *filename, char *const argv[], char *const envp[]); int real_chdir(const char *path); +void real_sleep(unsigned int seconds); +void real_usleep(unsigned int usec); +void __attribute__((noreturn)) + real_exit(int status); #define open real_open #define fopen real_fopen @@ -31,6 +40,7 @@ int real_chdir(const char *path); #define munmap real_munmap #define read real_read #define ioctl real_ioctl +#define close real_close #define sigaction real_sigaction #define tcgetattr real_tcgetattr #define tcsetattr real_tcsetattr @@ -42,4 +52,7 @@ int real_chdir(const char *path); #define execvp real_execvp #define execve real_execve #define chdir real_chdir +#define sleep real_sleep +#define usleep real_usleep +#define exit real_exit diff --git a/loader/syscalls.S b/loader/syscalls.S index e72e7d2..0d47b81 100644 --- a/loader/syscalls.S +++ b/loader/syscalls.S @@ -75,14 +75,19 @@ g_clone: stmfd sp!, {r4, r5, r6, lr} ldmia r12, {r4, r5, r6} swi \nr - stmfd sp!, {r4, r5, r6, pc} + ldmfd sp!, {r4, r5, r6, pc} .endm raw_syscall_easy g_open_raw, __NR_open raw_syscall_easy g_read_raw, __NR_read raw_syscall_easy g_write_raw, __NR_write +raw_syscall g_mmap2_raw, __NR_mmap2 +raw_syscall_easy g_munmap_raw, __NR_munmap +raw_syscall_easy g_ioctl_raw, __NR_ioctl +raw_syscall_easy g_close_raw, __NR_close +raw_syscall_easy g_chdir_raw, __NR_chdir raw_syscall_easy g_futex_raw, __NR_futex raw_syscall_easy g_nanosleep_raw, __NR_nanosleep raw_syscall_easy g_clock_gettime_raw, __NR_clock_gettime raw_syscall_easy g_rt_sigprocmask_raw, __NR_rt_sigprocmask - +raw_syscall_easy g_exit_group_raw, __NR_exit_group diff --git a/loader/syscalls.h b/loader/syscalls.h index e82e0fa..9a91bd4 100644 --- a/loader/syscalls.h +++ b/loader/syscalls.h @@ -9,15 +9,24 @@ long g_syscall(long number, ...); // arg6 is func ptr, for convenience long g_clone(unsigned long flags, void *child_stack, ...); -int g_syscall_error(long kret); +long g_syscall_error(long kret); // raw - no errno handling long g_open_raw(const char *pathname, int flags, ...); long g_read_raw(int fd, void *buf, size_t count); long g_write_raw(int fd, const void *buf, size_t count); +long g_mmap2_raw(void *addr, size_t length, int prot, int flags, + int fd, off_t offset); +long g_munmap_raw(void *addr, size_t length); +long g_ioctl_raw(int fd, unsigned long request, ...); +long g_close_raw(int fd); +long g_chdir_raw(const char *path); long g_futex_raw(int *uaddr, int op, int val, const struct timespec *timeout); long g_nanosleep_raw(const struct timespec *req, struct timespec *rem); long g_clock_gettime_raw(int clk_id, const struct timespec *tp); long g_rt_sigprocmask_raw(int how, const void *set, void *oldset, - size_t sigsetsize); + size_t sigsetsize); +long __attribute__((noreturn)) + g_exit_group_raw(int status); + -- 2.39.5