From 8ddc171b7b302844d9f4598125fed925b72c686c Mon Sep 17 00:00:00 2001 From: Afonso Bordado Date: Sun, 5 Mar 2023 14:34:37 +0000 Subject: [PATCH 1/9] linux-user: Emulate /proc/cpuinfo output for riscv RISC-V does not expose all extensions via hwcaps, thus some userspace applications may want to query these via /proc/cpuinfo. Currently when querying this file the host's file is shown instead which is slightly confusing. Emulate a basic /proc/cpuinfo file with mmu info and an ISA string. Signed-off-by: Afonso Bordado Reviewed-by: Palmer Dabbelt Acked-by: Palmer Dabbelt Reviewed-by: Laurent Vivier Reviewed-by: Alistair Francis Reviewed-by: LIU Zhiwei Message-Id: <167873059442.9885.15152085316575248452-0@git.sr.ht> [lv: removed the test that fails in CI for unknown reason] Signed-off-by: Laurent Vivier --- linux-user/syscall.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 69f740ff98..085ce53021 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -8231,7 +8231,8 @@ void target_exception_dump(CPUArchState *env, const char *fmt, int code) } #if HOST_BIG_ENDIAN != TARGET_BIG_ENDIAN || \ - defined(TARGET_SPARC) || defined(TARGET_M68K) || defined(TARGET_HPPA) + defined(TARGET_SPARC) || defined(TARGET_M68K) || defined(TARGET_HPPA) || \ + defined(TARGET_RISCV) static int is_proc(const char *filename, const char *entry) { return strcmp(filename, entry) == 0; @@ -8309,6 +8310,35 @@ static int open_cpuinfo(CPUArchState *cpu_env, int fd) } #endif +#if defined(TARGET_RISCV) +static int open_cpuinfo(CPUArchState *cpu_env, int fd) +{ + int i; + int num_cpus = sysconf(_SC_NPROCESSORS_ONLN); + RISCVCPU *cpu = env_archcpu(cpu_env); + const RISCVCPUConfig *cfg = riscv_cpu_cfg((CPURISCVState *) cpu_env); + char *isa_string = riscv_isa_string(cpu); + const char *mmu; + + if (cfg->mmu) { + mmu = (cpu_env->xl == MXL_RV32) ? "sv32" : "sv48"; + } else { + mmu = "none"; + } + + for (i = 0; i < num_cpus; i++) { + dprintf(fd, "processor\t: %d\n", i); + dprintf(fd, "hart\t\t: %d\n", i); + dprintf(fd, "isa\t\t: %s\n", isa_string); + dprintf(fd, "mmu\t\t: %s\n", mmu); + dprintf(fd, "uarch\t\t: qemu\n\n"); + } + + g_free(isa_string); + return 0; +} +#endif + #if defined(TARGET_M68K) static int open_hardware(CPUArchState *cpu_env, int fd) { @@ -8333,7 +8363,7 @@ static int do_openat(CPUArchState *cpu_env, int dirfd, const char *pathname, int #if HOST_BIG_ENDIAN != TARGET_BIG_ENDIAN { "/proc/net/route", open_net_route, is_proc }, #endif -#if defined(TARGET_SPARC) || defined(TARGET_HPPA) +#if defined(TARGET_SPARC) || defined(TARGET_HPPA) || defined(TARGET_RISCV) { "/proc/cpuinfo", open_cpuinfo, is_proc }, #endif #if defined(TARGET_M68K) From 59d11727768a0a29675a78a18c3f87390d5dc90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Wed, 26 Apr 2023 09:06:59 +0200 Subject: [PATCH 2/9] linux-user: report ENOTTY for unknown ioctls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The correct error number for unknown ioctls is ENOTTY. ENOSYS would mean that the ioctl() syscall itself is not implemented, which is very improbable and unexpected for userspace. ENOTTY means "Inappropriate ioctl for device". This is what the kernel returns on unknown ioctls, what qemu is trying to express and what userspace is prepared to handle. Signed-off-by: Thomas Weißschuh Reviewed-by: Philippe Mathieu-Daudé Message-Id: <20230426070659.80649-1-thomas@t-8ch.de> Signed-off-by: Laurent Vivier --- linux-user/syscall.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 085ce53021..954ed14df4 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -5747,7 +5747,7 @@ static abi_long do_ioctl(int fd, int cmd, abi_long arg) if (ie->target_cmd == 0) { qemu_log_mask( LOG_UNIMP, "Unsupported ioctl: cmd=0x%04lx\n", (long)cmd); - return -TARGET_ENOSYS; + return -TARGET_ENOTTY; } if (ie->target_cmd == cmd) break; @@ -5759,7 +5759,7 @@ static abi_long do_ioctl(int fd, int cmd, abi_long arg) } else if (!ie->host_cmd) { /* Some architectures define BSD ioctls in their headers that are not implemented in Linux. */ - return -TARGET_ENOSYS; + return -TARGET_ENOTTY; } switch(arg_type[0]) { @@ -5817,7 +5817,7 @@ static abi_long do_ioctl(int fd, int cmd, abi_long arg) qemu_log_mask(LOG_UNIMP, "Unsupported ioctl type: cmd=0x%04lx type=%d\n", (long)cmd, arg_type[0]); - ret = -TARGET_ENOSYS; + ret = -TARGET_ENOTTY; break; } return ret; From 4b2d2753e88bdb25db5eab84c172135200f15c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 24 Apr 2023 17:34:28 +0200 Subject: [PATCH 3/9] linux-user: Add move_mount() syscall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Weißschuh Reviewed-by: Laurent Vivier [lv: define syscall] Message-Id: <20230424153429.276788-1-thomas@t-8ch.de> Signed-off-by: Laurent Vivier --- linux-user/syscall.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 954ed14df4..9a99e45573 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -8776,6 +8776,12 @@ static int do_getdents64(abi_long dirfd, abi_long arg2, abi_long count) _syscall2(int, pivot_root, const char *, new_root, const char *, put_old) #endif +#if defined(TARGET_NR_move_mount) && defined(__NR_move_mount) +#define __NR_sys_move_mount __NR_move_mount +_syscall5(int, sys_move_mount, int, __from_dfd, const char *, __from_pathname, + int, __to_dfd, const char *, __to_pathname, unsigned int, flag) +#endif + /* This is an internal helper for do_syscall so that it is easier * to have a single return point, so that actions, such as logging * of syscall results, can be performed. @@ -9169,6 +9175,33 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1, unlock_user(p, arg1, 0); return ret; #endif +#if defined(TARGET_NR_move_mount) && defined(__NR_move_mount) + case TARGET_NR_move_mount: + { + void *p2, *p4; + + if (!arg2 || !arg4) { + return -TARGET_EFAULT; + } + + p2 = lock_user_string(arg2); + if (!p2) { + return -TARGET_EFAULT; + } + + p4 = lock_user_string(arg4); + if (!p4) { + unlock_user(p2, arg2, 0); + return -TARGET_EFAULT; + } + ret = get_errno(sys_move_mount(arg1, p2, arg3, p4, arg5)); + + unlock_user(p2, arg2, 0); + unlock_user(p4, arg4, 0); + + return ret; + } +#endif #ifdef TARGET_NR_stime /* not on alpha */ case TARGET_NR_stime: { From 7f696cddd9d7bbde0ecc489eb9a29c7196b29727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Mon, 24 Apr 2023 17:34:29 +0200 Subject: [PATCH 4/9] linux-user: Add open_tree() syscall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Thomas Weißschuh Reviewed-by: Laurent Vivier Message-Id: <20230424153429.276788-2-thomas@t-8ch.de> [lv: move declaration at the beginning of the block, define syscall] Signed-off-by: Laurent Vivier --- linux-user/syscall.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 9a99e45573..00a779797e 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -8776,6 +8776,12 @@ static int do_getdents64(abi_long dirfd, abi_long arg2, abi_long count) _syscall2(int, pivot_root, const char *, new_root, const char *, put_old) #endif +#if defined(TARGET_NR_open_tree) && defined(__NR_open_tree) +#define __NR_sys_open_tree __NR_open_tree +_syscall3(int, sys_open_tree, int, __dfd, const char *, __filename, + unsigned int, __flags) +#endif + #if defined(TARGET_NR_move_mount) && defined(__NR_move_mount) #define __NR_sys_move_mount __NR_move_mount _syscall5(int, sys_move_mount, int, __from_dfd, const char *, __from_pathname, @@ -9202,6 +9208,33 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1, return ret; } #endif +#if defined(TARGET_NR_open_tree) && defined(__NR_open_tree) + case TARGET_NR_open_tree: + { + void *p2; + int host_flags; + + if (!arg2) { + return -TARGET_EFAULT; + } + + p2 = lock_user_string(arg2); + if (!p2) { + return -TARGET_EFAULT; + } + + host_flags = arg3 & ~TARGET_O_CLOEXEC; + if (arg3 & TARGET_O_CLOEXEC) { + host_flags |= O_CLOEXEC; + } + + ret = get_errno(sys_open_tree(arg1, p2, host_flags)); + + unlock_user(p2, arg2, 0); + + return ret; + } +#endif #ifdef TARGET_NR_stime /* not on alpha */ case TARGET_NR_stime: { From b67e5cb43b64cd511785aa1b35876b5e5bf55f69 Mon Sep 17 00:00:00 2001 From: Thomas Huth Date: Mon, 24 Apr 2023 14:21:26 +0200 Subject: [PATCH 5/9] linux-user/main: Use list_cpus() instead of cpu_list() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This way we can get rid of the if'deffery and the XXX comment here (it's repeated in the list_cpus() function anyway). Signed-off-by: Thomas Huth Reviewed-by: Richard Henderson Reviewed-by: Philippe Mathieu-Daudé Message-Id: <20230424122126.236586-1-thuth@redhat.com> Signed-off-by: Laurent Vivier --- linux-user/main.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/linux-user/main.c b/linux-user/main.c index fe03293516..aece4d9e91 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -359,10 +359,7 @@ static void handle_arg_cpu(const char *arg) { cpu_model = strdup(arg); if (cpu_model == NULL || is_help_option(cpu_model)) { - /* XXX: implement xxx_cpu_list for targets that still miss it */ -#if defined(cpu_list) - cpu_list(); -#endif + list_cpus(); exit(EXIT_FAILURE); } } From 64d06015f6f44e3338af0ab2968ef7467dd2f3ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 22 Apr 2023 12:03:13 +0200 Subject: [PATCH 6/9] linux-user: Add new flag VERIFY_NONE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This can be used to validate that an address range is mapped but without being readable or writable. It will be used by an updated implementation of mincore(). Signed-off-by: Thomas Weißschuh Reviewed-by: Laurent Vivier Message-Id: <20230422100314.1650-2-thomas@t-8ch.de> Signed-off-by: Laurent Vivier --- linux-user/qemu.h | 1 + 1 file changed, 1 insertion(+) diff --git a/linux-user/qemu.h b/linux-user/qemu.h index e2e93fbd1d..92f9f5af41 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -168,6 +168,7 @@ abi_long do_brk(abi_ulong new_brk); /* user access */ +#define VERIFY_NONE 0 #define VERIFY_READ PAGE_READ #define VERIFY_WRITE (PAGE_READ | PAGE_WRITE) From f443a26cc6c077f792a5114c5229020ecf44ba3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Sat, 22 Apr 2023 12:03:14 +0200 Subject: [PATCH 7/9] linux-user: Don't require PROT_READ for mincore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kernel does not require PROT_READ for addresses passed to mincore. For example the fincore(1) tool from util-linux uses PROT_NONE and currently does not work under qemu-user. Example (with fincore(1) from util-linux 2.38): $ fincore /proc/self/exe RES PAGES SIZE FILE 24K 6 22.1K /proc/self/exe $ qemu-x86_64 /usr/bin/fincore /proc/self/exe fincore: failed to do mincore: /proc/self/exe: Cannot allocate memory With this patch: $ ./build/qemu-x86_64 /usr/bin/fincore /proc/self/exe RES PAGES SIZE FILE 24K 6 22.1K /proc/self/exe Signed-off-by: Thomas Weißschuh Reviewed-by: Laurent Vivier Message-Id: <20230422100314.1650-3-thomas@t-8ch.de> Signed-off-by: Laurent Vivier --- linux-user/syscall.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 00a779797e..6655982821 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -11993,7 +11993,7 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1, #ifdef TARGET_NR_mincore case TARGET_NR_mincore: { - void *a = lock_user(VERIFY_READ, arg1, arg2, 0); + void *a = lock_user(VERIFY_NONE, arg1, arg2, 0); if (!a) { return -TARGET_ENOMEM; } From a0f8d2701b205d9d7986aa555e0566b13dc18fa0 Mon Sep 17 00:00:00 2001 From: Daniil Kovalev Date: Tue, 4 Apr 2023 08:21:54 +0300 Subject: [PATCH 8/9] linux-user: Fix mips fp64 executables loading If a program requires fr1, we should set the FR bit of CP0 control status register and add F64 hardware flag. The corresponding `else if` branch statement is copied from the linux kernel sources (see `arch_check_elf` function in linux/arch/mips/kernel/elf.c). Signed-off-by: Daniil Kovalev Reviewed-by: Jiaxun Yang Message-Id: <20230404052153.16617-1-dkovalev@compiler-toolchain-for.me> Signed-off-by: Laurent Vivier --- linux-user/mips/cpu_loop.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/linux-user/mips/cpu_loop.c b/linux-user/mips/cpu_loop.c index d5c1c7941d..8735e58bad 100644 --- a/linux-user/mips/cpu_loop.c +++ b/linux-user/mips/cpu_loop.c @@ -290,7 +290,10 @@ void target_cpu_copy_regs(CPUArchState *env, struct target_pt_regs *regs) env->CP0_Status |= (1 << CP0St_FR); env->hflags |= MIPS_HFLAG_F64; } - } else if (!prog_req.fre && !prog_req.frdefault && + } else if (prog_req.fr1) { + env->CP0_Status |= (1 << CP0St_FR); + env->hflags |= MIPS_HFLAG_F64; + } else if (!prog_req.fre && !prog_req.frdefault && !prog_req.fr1 && !prog_req.single && !prog_req.soft) { fprintf(stderr, "qemu: Can't find a matching FPU mode\n"); exit(1); From 1e35d327890bdd117a67f79c52e637fb12bb1bf4 Mon Sep 17 00:00:00 2001 From: Michael Tokarev Date: Sun, 9 Apr 2023 13:53:27 +0300 Subject: [PATCH 9/9] linux-user: fix getgroups/setgroups allocations linux-user getgroups(), setgroups(), getgroups32() and setgroups32() used alloca() to allocate grouplist arrays, with unchecked gidsetsize coming from the "guest". With NGROUPS_MAX being 65536 (linux, and it is common for an application to allocate NGROUPS_MAX for getgroups()), this means a typical allocation is half the megabyte on the stack. Which just overflows stack, which leads to immediate SIGSEGV in actual system getgroups() implementation. An example of such issue is aptitude, eg https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=811087#72 Cap gidsetsize to NGROUPS_MAX (return EINVAL if it is larger than that), and use heap allocation for grouplist instead of alloca(). While at it, fix coding style and make all 4 implementations identical. Try to not impose random limits - for example, allow gidsetsize to be negative for getgroups() - just do not allocate negative-sized grouplist in this case but still do actual getgroups() call. But do not allow negative gidsetsize for setgroups() since its argument is unsigned. Capping by NGROUPS_MAX seems a bit arbitrary, - we can do more, it is not an error if set size will be NGROUPS_MAX+1. But we should not allow integer overflow for the array being allocated. Maybe it is enough to just call g_try_new() and return ENOMEM if it fails. Maybe there's also no need to convert setgroups() since this one is usually smaller and known beforehand (KERN_NGROUPS_MAX is actually 63, - this is apparently a kernel-imposed limit for runtime group set). The patch fixes aptitude segfault mentioned above. Signed-off-by: Michael Tokarev Message-Id: <20230409105327.1273372-1-mjt@msgid.tls.msk.ru> Signed-off-by: Laurent Vivier --- linux-user/syscall.c | 105 +++++++++++++++++++++++++++++-------------- 1 file changed, 71 insertions(+), 34 deletions(-) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 6655982821..89b58b386b 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -11571,39 +11571,58 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1, { int gidsetsize = arg1; target_id *target_grouplist; - gid_t *grouplist; + g_autofree gid_t *grouplist = NULL; int i; - grouplist = alloca(gidsetsize * sizeof(gid_t)); - ret = get_errno(getgroups(gidsetsize, grouplist)); - if (gidsetsize == 0) - return ret; - if (!is_error(ret)) { - target_grouplist = lock_user(VERIFY_WRITE, arg2, gidsetsize * sizeof(target_id), 0); - if (!target_grouplist) - return -TARGET_EFAULT; - for(i = 0;i < ret; i++) - target_grouplist[i] = tswapid(high2lowgid(grouplist[i])); - unlock_user(target_grouplist, arg2, gidsetsize * sizeof(target_id)); + if (gidsetsize > NGROUPS_MAX) { + return -TARGET_EINVAL; } + if (gidsetsize > 0) { + grouplist = g_try_new(gid_t, gidsetsize); + if (!grouplist) { + return -TARGET_ENOMEM; + } + } + ret = get_errno(getgroups(gidsetsize, grouplist)); + if (!is_error(ret) && gidsetsize > 0) { + target_grouplist = lock_user(VERIFY_WRITE, arg2, + gidsetsize * sizeof(target_id), 0); + if (!target_grouplist) { + return -TARGET_EFAULT; + } + for (i = 0; i < ret; i++) { + target_grouplist[i] = tswapid(high2lowgid(grouplist[i])); + } + unlock_user(target_grouplist, arg2, + gidsetsize * sizeof(target_id)); + } + return ret; } - return ret; case TARGET_NR_setgroups: { int gidsetsize = arg1; target_id *target_grouplist; - gid_t *grouplist = NULL; + g_autofree gid_t *grouplist = NULL; int i; - if (gidsetsize) { - grouplist = alloca(gidsetsize * sizeof(gid_t)); - target_grouplist = lock_user(VERIFY_READ, arg2, gidsetsize * sizeof(target_id), 1); + + if (gidsetsize > NGROUPS_MAX || gidsetsize < 0) { + return -TARGET_EINVAL; + } + if (gidsetsize > 0) { + grouplist = g_try_new(gid_t, gidsetsize); + if (!grouplist) { + return -TARGET_ENOMEM; + } + target_grouplist = lock_user(VERIFY_READ, arg2, + gidsetsize * sizeof(target_id), 1); if (!target_grouplist) { return -TARGET_EFAULT; } for (i = 0; i < gidsetsize; i++) { grouplist[i] = low2highgid(tswapid(target_grouplist[i])); } - unlock_user(target_grouplist, arg2, 0); + unlock_user(target_grouplist, arg2, + gidsetsize * sizeof(target_id)); } return get_errno(setgroups(gidsetsize, grouplist)); } @@ -11888,41 +11907,59 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1, { int gidsetsize = arg1; uint32_t *target_grouplist; - gid_t *grouplist; + g_autofree gid_t *grouplist = NULL; int i; - grouplist = alloca(gidsetsize * sizeof(gid_t)); + if (gidsetsize > NGROUPS_MAX) { + return -TARGET_EINVAL; + } + if (gidsetsize > 0) { + grouplist = g_try_new(gid_t, gidsetsize); + if (!grouplist) { + return -TARGET_ENOMEM; + } + } ret = get_errno(getgroups(gidsetsize, grouplist)); - if (gidsetsize == 0) - return ret; - if (!is_error(ret)) { - target_grouplist = lock_user(VERIFY_WRITE, arg2, gidsetsize * 4, 0); + if (!is_error(ret) && gidsetsize > 0) { + target_grouplist = lock_user(VERIFY_WRITE, arg2, + gidsetsize * 4, 0); if (!target_grouplist) { return -TARGET_EFAULT; } - for(i = 0;i < ret; i++) + for (i = 0; i < ret; i++) { target_grouplist[i] = tswap32(grouplist[i]); + } unlock_user(target_grouplist, arg2, gidsetsize * 4); } + return ret; } - return ret; #endif #ifdef TARGET_NR_setgroups32 case TARGET_NR_setgroups32: { int gidsetsize = arg1; uint32_t *target_grouplist; - gid_t *grouplist; + g_autofree gid_t *grouplist = NULL; int i; - grouplist = alloca(gidsetsize * sizeof(gid_t)); - target_grouplist = lock_user(VERIFY_READ, arg2, gidsetsize * 4, 1); - if (!target_grouplist) { - return -TARGET_EFAULT; + if (gidsetsize > NGROUPS_MAX || gidsetsize < 0) { + return -TARGET_EINVAL; + } + if (gidsetsize > 0) { + grouplist = g_try_new(gid_t, gidsetsize); + if (!grouplist) { + return -TARGET_ENOMEM; + } + target_grouplist = lock_user(VERIFY_READ, arg2, + gidsetsize * 4, 1); + if (!target_grouplist) { + return -TARGET_EFAULT; + } + for (i = 0; i < gidsetsize; i++) { + grouplist[i] = tswap32(target_grouplist[i]); + } + unlock_user(target_grouplist, arg2, 0); } - for(i = 0;i < gidsetsize; i++) - grouplist[i] = tswap32(target_grouplist[i]); - unlock_user(target_grouplist, arg2, 0); return get_errno(setgroups(gidsetsize, grouplist)); } #endif