/* * miscellaneous BSD system call shims * * Copyright (c) 2013 Stacey D. Son * * SPDX-License-Identifier: GPL-2.0-or-later */ #ifndef BSD_MISC_H #define BSD_MISC_H #include #include #include #include #include #include "qemu-bsd.h" static int bsd_msgmax; /* quotactl(2) */ static inline abi_long do_bsd_quotactl(abi_ulong path, abi_long cmd, __unused abi_ulong target_addr) { qemu_log("qemu: Unsupported syscall quotactl()\n"); return -TARGET_ENOSYS; } /* reboot(2) */ static inline abi_long do_bsd_reboot(abi_long how) { qemu_log("qemu: Unsupported syscall reboot()\n"); return -TARGET_ENOSYS; } /* uuidgen(2) */ static inline abi_long do_bsd_uuidgen(abi_ulong target_addr, int count) { int i; abi_long ret; g_autofree struct uuid *host_uuid = NULL; /* * 2048 is the kernel limit, but there's no #define for it, nor any sysctl * to query it. */ if (count < 1 || count > 2048) { return -TARGET_EINVAL; } host_uuid = g_malloc(count * sizeof(struct uuid)); ret = get_errno(uuidgen(host_uuid, count)); if (is_error(ret)) { goto out; } for (i = 0; i < count; i++) { ret = host_to_target_uuid(target_addr + (abi_ulong)(sizeof(struct target_uuid) * i), &host_uuid[i]); if (is_error(ret)) { break; } } out: return ret; } /* * System V Semaphores */ /* semget(2) */ static inline abi_long do_bsd_semget(abi_long key, int nsems, int target_flags) { return get_errno(semget(key, nsems, target_to_host_bitmask(target_flags, ipc_flags_tbl))); } /* semop(2) */ static inline abi_long do_bsd_semop(int semid, abi_long ptr, unsigned nsops) { g_autofree struct sembuf *sops = g_malloc(nsops * sizeof(struct sembuf)); struct target_sembuf *target_sembuf; int i; target_sembuf = lock_user(VERIFY_READ, ptr, nsops * sizeof(struct target_sembuf), 1); if (target_sembuf == NULL) { return -TARGET_EFAULT; } for (i = 0; i < nsops; i++) { __get_user(sops[i].sem_num, &target_sembuf[i].sem_num); __get_user(sops[i].sem_op, &target_sembuf[i].sem_op); __get_user(sops[i].sem_flg, &target_sembuf[i].sem_flg); } unlock_user(target_sembuf, ptr, 0); return semop(semid, sops, nsops); } /* __semctl(2) */ static inline abi_long do_bsd___semctl(int semid, int semnum, int target_cmd, abi_ptr un_ptr) { void *target_un; union semun arg; struct semid_ds dsarg; unsigned short *array = NULL; int host_cmd; abi_long ret = 0; abi_ulong target_array, target_buffer; switch (target_cmd) { case TARGET_GETVAL: host_cmd = GETVAL; break; case TARGET_SETVAL: host_cmd = SETVAL; break; case TARGET_GETALL: host_cmd = GETALL; break; case TARGET_SETALL: host_cmd = SETALL; break; case TARGET_IPC_STAT: host_cmd = IPC_STAT; break; case TARGET_IPC_SET: host_cmd = IPC_SET; break; case TARGET_IPC_RMID: host_cmd = IPC_RMID; break; case TARGET_GETPID: host_cmd = GETPID; break; case TARGET_GETNCNT: host_cmd = GETNCNT; break; case TARGET_GETZCNT: host_cmd = GETZCNT; break; default: return -TARGET_EINVAL; } /* * Unlike Linux and the semctl system call, we take a pointer * to the union arg here. */ target_un = lock_user(VERIFY_READ, un_ptr, sizeof(union target_semun), 1); switch (host_cmd) { case GETVAL: case SETVAL: __get_user(arg.val, (abi_int *)target_un); ret = get_errno(semctl(semid, semnum, host_cmd, arg)); break; case GETALL: case SETALL: __get_user(target_array, (abi_ulong *)target_un); ret = target_to_host_semarray(semid, &array, target_array); if (is_error(ret)) { goto out; } arg.array = array; ret = get_errno(semctl(semid, semnum, host_cmd, arg)); if (!is_error(ret)) { ret = host_to_target_semarray(semid, target_array, &array); } break; case IPC_STAT: case IPC_SET: __get_user(target_buffer, (abi_ulong *)target_un); ret = target_to_host_semid_ds(&dsarg, target_buffer); if (is_error(ret)) { goto out; } arg.buf = &dsarg; ret = get_errno(semctl(semid, semnum, host_cmd, arg)); if (!is_error(ret)) { ret = host_to_target_semid_ds(target_buffer, &dsarg); } break; case IPC_RMID: case GETPID: case GETNCNT: case GETZCNT: ret = get_errno(semctl(semid, semnum, host_cmd, NULL)); break; default: ret = -TARGET_EINVAL; break; } out: unlock_user(target_un, un_ptr, 1); return ret; } /* msgctl(2) */ static inline abi_long do_bsd_msgctl(int msgid, int target_cmd, abi_long ptr) { struct msqid_ds dsarg; abi_long ret = -TARGET_EINVAL; int host_cmd; switch (target_cmd) { case TARGET_IPC_STAT: host_cmd = IPC_STAT; break; case TARGET_IPC_SET: host_cmd = IPC_SET; break; case TARGET_IPC_RMID: host_cmd = IPC_RMID; break; default: return -TARGET_EINVAL; } switch (host_cmd) { case IPC_STAT: case IPC_SET: if (target_to_host_msqid_ds(&dsarg, ptr)) { return -TARGET_EFAULT; } ret = get_errno(msgctl(msgid, host_cmd, &dsarg)); if (host_to_target_msqid_ds(ptr, &dsarg)) { return -TARGET_EFAULT; } break; case IPC_RMID: ret = get_errno(msgctl(msgid, host_cmd, NULL)); break; default: ret = -TARGET_EINVAL; break; } return ret; } struct kern_mymsg { long mtype; char mtext[1]; }; static inline abi_long bsd_validate_msgsz(abi_ulong msgsz) { /* Fetch msgmax the first time we need it. */ if (bsd_msgmax == 0) { size_t len = sizeof(bsd_msgmax); if (sysctlbyname("kern.ipc.msgmax", &bsd_msgmax, &len, NULL, 0) == -1) { return -TARGET_EINVAL; } } if (msgsz > bsd_msgmax) { return -TARGET_EINVAL; } return 0; } /* msgsnd(2) */ static inline abi_long do_bsd_msgsnd(int msqid, abi_long msgp, abi_ulong msgsz, int msgflg) { struct target_msgbuf *target_mb; struct kern_mymsg *host_mb; abi_long ret; ret = bsd_validate_msgsz(msgsz); if (is_error(ret)) { return ret; } if (!lock_user_struct(VERIFY_READ, target_mb, msgp, 0)) { return -TARGET_EFAULT; } host_mb = g_malloc(msgsz + sizeof(long)); host_mb->mtype = (abi_long) tswapal(target_mb->mtype); memcpy(host_mb->mtext, target_mb->mtext, msgsz); ret = get_errno(msgsnd(msqid, host_mb, msgsz, msgflg)); g_free(host_mb); unlock_user_struct(target_mb, msgp, 0); return ret; } /* msgget(2) */ static inline abi_long do_bsd_msgget(abi_long key, abi_long msgflag) { abi_long ret; ret = get_errno(msgget(key, msgflag)); return ret; } /* msgrcv(2) */ static inline abi_long do_bsd_msgrcv(int msqid, abi_long msgp, abi_ulong msgsz, abi_long msgtyp, int msgflg) { struct target_msgbuf *target_mb = NULL; char *target_mtext; struct kern_mymsg *host_mb; abi_long ret = 0; ret = bsd_validate_msgsz(msgsz); if (is_error(ret)) { return ret; } if (!lock_user_struct(VERIFY_WRITE, target_mb, msgp, 0)) { return -TARGET_EFAULT; } host_mb = g_malloc(msgsz + sizeof(long)); ret = get_errno(msgrcv(msqid, host_mb, msgsz, tswapal(msgtyp), msgflg)); if (ret > 0) { abi_ulong target_mtext_addr = msgp + sizeof(abi_ulong); target_mtext = lock_user(VERIFY_WRITE, target_mtext_addr, ret, 0); if (target_mtext == NULL) { ret = -TARGET_EFAULT; goto end; } memcpy(target_mb->mtext, host_mb->mtext, ret); unlock_user(target_mtext, target_mtext_addr, ret); } if (!is_error(ret)) { target_mb->mtype = tswapal(host_mb->mtype); } end: if (target_mb != NULL) { unlock_user_struct(target_mb, msgp, 1); } g_free(host_mb); return ret; } /* getdtablesize(2) */ static inline abi_long do_bsd_getdtablesize(void) { return get_errno(getdtablesize()); } #endif /* BSD_MISC_H */