#ifdef _GNU_SOURCE #undef _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SIOCETHTOOL 0x8946 #define SIOCGSKNS 0x894C #define TFD_IOC_SET_TICKS _IOW('T', 0, __u64) namespace mlibc { static constexpr bool logIoctls = false; int ioctl_drm(int fd, unsigned long request, void *arg, int *result, HelHandle handle); int sys_ioctl(int fd, unsigned long request, void *arg, int *result) { if (logIoctls) mlibc::infoLogger() << "mlibc: ioctl with" << " type: 0x" << frg::hex_fmt(_IOC_TYPE(request)) << ", number: 0x" << frg::hex_fmt(_IOC_NR(request)) << " (raw request: " << frg::hex_fmt(request) << ")" << " on fd " << fd << frg::endlog; SignalGuard sguard; auto handle = getHandleForFd(fd); if (!handle) return EBADF; if (_IOC_TYPE(request) == 'd') { return ioctl_drm(fd, request, arg, result, handle); } auto handle_siocgif = [&arg, &request, &result]( void (*req_setup)(managarm::fs::IfreqRequest &req, struct ifreq *ifr), int (*resp_parse)(managarm::fs::IfreqReply &resp, struct ifreq *ifr) ) -> int { if (!arg) return EFAULT; auto ifr = reinterpret_cast(arg); managarm::posix::NetserverRequest token_req(getSysdepsAllocator()); managarm::fs::IfreqRequest req(getSysdepsAllocator()); req.set_command(request); req_setup(req, ifr); auto [offer, send_token_req, send_req, send_req_tail, recv_resp] = exchangeMsgsSync( getPosixLane(), helix_ng::offer( helix_ng::want_lane, helix_ng::sendBragiHeadOnly(token_req, getSysdepsAllocator()), helix_ng::sendBragiHeadTail(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_token_req.error()); HEL_CHECK(send_req.error()); HEL_CHECK(send_req_tail.error()); HEL_CHECK(recv_resp.error()); auto preamble = bragi::read_preamble(recv_resp); frg::vector tailBuffer{getSysdepsAllocator()}; tailBuffer.resize(preamble.tail_size()); auto [recv_tail] = exchangeMsgsSync( offer.descriptor().getHandle(), helix_ng::recvBuffer(tailBuffer.data(), tailBuffer.size()) ); HEL_CHECK(recv_tail.error()); auto resp = *bragi::parse_head_tail( recv_resp, tailBuffer, getSysdepsAllocator() ); recv_resp.reset(); int ret = resp_parse(resp, ifr); if (result) *result = 0; return ret; }; managarm::fs::IoctlRequest ioctl_req(getSysdepsAllocator()); switch (request) { case FIONBIO: { auto mode = reinterpret_cast(arg); int flags = fcntl(fd, F_GETFL, 0); if (*mode) { fcntl(fd, F_SETFL, flags | O_NONBLOCK); } else { fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); } return 0; } case FIONREAD: { auto argp = reinterpret_cast(arg); auto handle = getHandleForFd(fd); if (!handle) return EBADF; if (!argp) return EINVAL; managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(FIONREAD); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); if (resp.error() == managarm::fs::Errors::NOT_CONNECTED) { return ENOTCONN; } else { __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *argp = resp.fionread_count(); return 0; } } case FIOCLEX: { managarm::posix::IoctlFioclexRequest req(getSysdepsAllocator()); req.set_fd(fd); auto [offer, sendReq, recvResp] = exchangeMsgsSync( getPosixLane(), helix_ng::offer( helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(sendReq.error()); if (recvResp.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(recvResp.error()); managarm::posix::SvrResponse resp(getSysdepsAllocator()); resp.ParseFromArray(recvResp.data(), recvResp.length()); __ensure(resp.error() == managarm::posix::Errors::SUCCESS); return 0; } case TCGETS: { auto param = reinterpret_cast(arg); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); auto [offer, send_ioctl_req, send_req, recv_resp, recv_attrs] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline(), helix_ng::recvBuffer(param, sizeof(struct termios)) ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); if (send_req.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); HEL_CHECK(recv_attrs.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); __ensure(recv_attrs.actualLength() == sizeof(struct termios)); *result = resp.result(); return 0; } case TCSETS: { auto param = reinterpret_cast(arg); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); auto [offer, send_ioctl_req, send_req, send_attrs, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::sendBuffer(param, sizeof(struct termios)), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); if (send_req.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(send_req.error()); HEL_CHECK(send_attrs.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); if (result) *result = resp.result(); return 0; } case TIOCSCTTY: { managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); auto [offer, send_ioctl_req, send_req, imbue_creds, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::imbueCredentials(), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); if (send_req.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(send_ioctl_req.error()); HEL_CHECK(imbue_creds.error()); HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); if (resp.error() == managarm::fs::Errors::ILLEGAL_ARGUMENT) { return EINVAL; } else if (resp.error() == managarm::fs::Errors::INSUFFICIENT_PERMISSIONS) { return EPERM; } __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *result = resp.result(); return 0; } case TIOCGWINSZ: { auto param = reinterpret_cast(arg); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); if (send_req.error() == kHelErrDismissed) return ENOTTY; HEL_CHECK(send_req.error()); if (recv_resp.error() == kHelErrDismissed) return ENOTTY; HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); if (resp.error() != managarm::fs::Errors::SUCCESS) return resp.error() | toErrno; *result = resp.result(); param->ws_col = resp.pts_width(); param->ws_row = resp.pts_height(); param->ws_xpixel = resp.pts_pixel_width(); param->ws_ypixel = resp.pts_pixel_height(); return 0; } case TIOCSWINSZ: { auto param = reinterpret_cast(arg); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); req.set_pts_width(param->ws_col); req.set_pts_height(param->ws_row); req.set_pts_pixel_width(param->ws_xpixel); req.set_pts_pixel_height(param->ws_ypixel); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); if (send_req.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *result = resp.result(); return 0; } case TIOCGPTN: { auto param = reinterpret_cast(arg); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); if (send_req.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *param = resp.pts_index(); if (result) *result = resp.result(); return 0; } case TIOCGPGRP: { managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); frg::string ser(getSysdepsAllocator()); req.SerializeToString(&ser); auto [offer, send_ioctl_req, send_req, imbue_creds, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBuffer(ser.data(), ser.size()), helix_ng::imbueCredentials(), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); if (send_req.error()) return EINVAL; HEL_CHECK(send_req.error()); if (imbue_creds.error()) { infoLogger( ) << "mlibc: TIOCGPGRP used on unexpected socket, returning EINVAL (FIXME)" << frg::endlog; return EINVAL; } HEL_CHECK(imbue_creds.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); if (resp.error() != managarm::fs::Errors::SUCCESS) return resp.error() | toErrno; *result = resp.result(); *static_cast(arg) = resp.pid(); return 0; } case TIOCSPGRP: { auto param = reinterpret_cast(arg); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); req.set_pgid(*param); frg::string ser(getSysdepsAllocator()); req.SerializeToString(&ser); auto [offer, send_ioctl_req, send_req, imbue_creds, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBuffer(ser.data(), ser.size()), helix_ng::imbueCredentials(), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); if (send_req.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(send_req.error()); HEL_CHECK(imbue_creds.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); if (resp.error() == managarm::fs::Errors::INSUFFICIENT_PERMISSIONS) { return EPERM; } else if (resp.error() == managarm::fs::Errors::ILLEGAL_ARGUMENT) { return EINVAL; } __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *result = resp.result(); return 0; } case TIOCGSID: { managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); frg::string ser(getSysdepsAllocator()); req.SerializeToString(&ser); auto [offer, send_ioctl_req, send_req, imbue_creds, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBuffer(ser.data(), ser.size()), helix_ng::imbueCredentials(), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); if (send_ioctl_req.error()) return EINVAL; HEL_CHECK(send_ioctl_req.error()); if (send_req.error()) return EINVAL; HEL_CHECK(send_req.error()); if (imbue_creds.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(imbue_creds.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); if (resp.error() == managarm::fs::Errors::NOT_A_TERMINAL) { return ENOTTY; } __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *result = resp.result(); *static_cast(arg) = resp.pid(); return 0; } case CDROM_GET_CAPABILITY: { managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); frg::string ser(getSysdepsAllocator()); req.SerializeToString(&ser); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBuffer(ser.data(), ser.size()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); if (send_ioctl_req.error()) return EINVAL; HEL_CHECK(send_ioctl_req.error()); if (send_req.error()) return EINVAL; HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); if (resp.error() == managarm::fs::Errors::NOT_A_TERMINAL) { return ENOTTY; } __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *result = resp.result(); return 0; } case SIOCETHTOOL: mlibc::infoLogger() << "\e[35mmlibc: SIOCETHTOOL is a stub" << frg::endlog; *result = 0; return ENOSYS; case SIOCGSKNS: mlibc::infoLogger() << "\e[35mmlibc: SIOCGSKNS is a stub" << frg::endlog; *result = 0; return ENOSYS; case SG_IO: mlibc::infoLogger() << "\e[35mmlibc: SG_IO is a stub" << frg::endlog; *result = 0; return ENOSYS; } // end of switch() if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCGVERSION)) { *reinterpret_cast(arg) = EV_VERSION; *result = 0; return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCGID)) { memset(arg, 0, sizeof(struct input_id)); auto param = reinterpret_cast(arg); managarm::fs::EvioGetIdRequest req(getSysdepsAllocator()); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::want_lane, helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); auto conversation = offer.descriptor(); HEL_CHECK(send_ioctl_req.error()); HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); auto resp = *bragi::parse_head_only(recv_resp, getSysdepsAllocator()); recv_resp.reset(); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); param->bustype = resp.bustype(); param->vendor = resp.vendor(); param->product = resp.product(); param->version = resp.version(); *result = 0; return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCGNAME(0))) { managarm::fs::EvioGetNameRequest req(getSysdepsAllocator()); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::want_lane, helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); auto conversation = offer.descriptor(); HEL_CHECK(send_ioctl_req.error()); HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); auto preamble = bragi::read_preamble(recv_resp); __ensure(!preamble.error()); frg::vector tailBuffer{getSysdepsAllocator()}; tailBuffer.resize(preamble.tail_size()); auto [recv_tail] = exchangeMsgsSync( conversation.getHandle(), helix_ng::recvBuffer(tailBuffer.data(), tailBuffer.size()) ); HEL_CHECK(recv_tail.error()); auto resp = *bragi::parse_head_tail( recv_resp, tailBuffer, getSysdepsAllocator() ); recv_resp.reset(); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); auto chunk = frg::min(_IOC_SIZE(request), resp.name().size() + 1); memcpy(arg, resp.name().data(), chunk); *result = chunk; return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCGRAB)) { mlibc::infoLogger() << "mlibc: EVIOCGRAB is a no-op" << frg::endlog; *result = 0; return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCGPHYS(0))) { // Returns the sysfs path of the device. const char *s = "input0"; auto chunk = frg::min(_IOC_SIZE(request), strlen(s) + 1); memcpy(arg, s, chunk); *result = chunk; return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCGUNIQ(0))) { // Returns a unique ID for the device. const char *s = "0"; auto chunk = frg::min(_IOC_SIZE(request), strlen(s) + 1); memcpy(arg, s, chunk); *result = chunk; return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCGPROP(0))) { // Returns a bitmask of properties of the device. auto size = _IOC_SIZE(request); memset(arg, 0, size); *result = size; return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCGKEY(0))) { // Returns the current key state. auto size = _IOC_SIZE(request); memset(arg, 0, size); *result = size; return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCGMTSLOTS(0))) { // this ioctl is completely, utterly undocumented // the _IOC_SIZE is a buffer size in bytes, which should be a multiple of int32_t // bytes should be at least sizeof(int32_t) large. // the argument (the pointer to the buffer) is an array of int32_t // the first entry is the number of values supplied, followed by the values // this would have been worthwhile to document ffs // the length argument is the buffer size, in bytes auto bytes = _IOC_SIZE(request); // the length argument should be a multiple of int32_t if (!bytes || bytes % sizeof(int32_t)) return EINVAL; // the number of entries the buffer can hold auto entries = (bytes / sizeof(int32_t)) - 1; managarm::fs::EvioGetMultitouchSlotsRequest req(getSysdepsAllocator()); req.set_code(*reinterpret_cast(arg)); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::want_lane, helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); auto conversation = offer.descriptor(); HEL_CHECK(send_ioctl_req.error()); HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); auto preamble = bragi::read_preamble(recv_resp); __ensure(!preamble.error()); frg::vector tailBuffer{getSysdepsAllocator()}; tailBuffer.resize(preamble.tail_size()); auto [recv_tail] = exchangeMsgsSync( conversation.getHandle(), helix_ng::recvBuffer(tailBuffer.data(), tailBuffer.size()) ); HEL_CHECK(recv_tail.error()); auto resp = *bragi::parse_head_tail( recv_resp, tailBuffer, getSysdepsAllocator() ); recv_resp.reset(); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); auto param = reinterpret_cast(arg); for (size_t i = 0; i < resp.values_size() && i < entries; i++) { param[i + 1] = resp.values(i); } param[0] = resp.values_size(); return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCGLED(0))) { // Returns the current LED state. auto size = _IOC_SIZE(request); memset(arg, 0, size); *result = size; return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCGSW(0))) { auto size = _IOC_SIZE(request); memset(arg, 0, size); *result = size; return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) >= _IOC_NR(EVIOCGBIT(0, 0)) && _IOC_NR(request) <= _IOC_NR(EVIOCGBIT(EV_MAX, 0))) { // Returns a bitmask of capabilities of the device. // If type is zero, return a mask of supported types. // As EV_SYN is zero, this implies that it is impossible // to get the mask of supported synthetic events. auto type = _IOC_NR(request) - _IOC_NR(EVIOCGBIT(0, 0)); if (!type) { // TODO: Check with the Linux ABI if we have to do this. memset(arg, 0, _IOC_SIZE(request)); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(EVIOCGBIT(0, 0)); req.set_size(_IOC_SIZE(request)); auto [offer, send_ioctl_req, send_req, recv_resp, recv_data] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline(), helix_ng::recvBuffer(arg, _IOC_SIZE(request)) ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); if (send_req.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); HEL_CHECK(recv_data.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *result = recv_data.actualLength(); return 0; } else { // TODO: Check with the Linux ABI if we have to do this. memset(arg, 0, _IOC_SIZE(request)); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(EVIOCGBIT(1, 0)); req.set_input_type(type); req.set_size(_IOC_SIZE(request)); auto [offer, send_ioctl_req, send_req, recv_resp, recv_data] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline(), helix_ng::recvBuffer(arg, _IOC_SIZE(request)) ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); if (send_req.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); HEL_CHECK(recv_data.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *result = recv_data.actualLength(); return 0; } } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) == _IOC_NR(EVIOCSCLOCKID)) { auto param = reinterpret_cast(arg); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); req.set_input_clock(*param); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); if (send_req.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *result = resp.result(); return 0; } else if (_IOC_TYPE(request) == 'E' && _IOC_NR(request) >= _IOC_NR(EVIOCGABS(0)) && _IOC_NR(request) <= _IOC_NR(EVIOCGABS(ABS_MAX))) { auto param = reinterpret_cast(arg); auto type = _IOC_NR(request) - _IOC_NR(EVIOCGABS(0)); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(EVIOCGABS(0)); req.set_input_type(type); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); if (send_req.error() == kHelErrDismissed) return EINVAL; HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); param->value = resp.input_value(); param->minimum = resp.input_min(); param->maximum = resp.input_max(); param->fuzz = resp.input_fuzz(); param->flat = resp.input_flat(); param->resolution = resp.input_resolution(); *result = resp.result(); return 0; } else if (request == KDSETMODE) { auto param = reinterpret_cast(arg); mlibc::infoLogger() << "\e[35mmlibc: KD_SETMODE(" << frg::hex_fmt(param) << ") is a no-op" << frg::endlog; *result = 0; return 0; } else if (request == KDGETMODE) { auto param = reinterpret_cast(arg); mlibc::infoLogger() << "\e[35mmlibc: KD_GETMODE is a no-op" << frg::endlog; *param = 0; *result = 0; return 0; } else if (request == KDSKBMODE) { auto param = reinterpret_cast(arg); mlibc::infoLogger() << "\e[35mmlibc: KD_SKBMODE(" << frg::hex_fmt(param) << ") is a no-op" << frg::endlog; *result = 0; return 0; } else if (request == VT_SETMODE) { // auto param = reinterpret_cast(arg); mlibc::infoLogger() << "\e[35mmlibc: VT_SETMODE is a no-op" << frg::endlog; *result = 0; return 0; } else if (request == VT_GETSTATE) { auto param = reinterpret_cast(arg); param->v_active = 0; param->v_signal = 0; param->v_state = 0; mlibc::infoLogger() << "\e[35mmlibc: VT_GETSTATE is a no-op" << frg::endlog; *result = 0; return 0; } else if (request == VT_ACTIVATE || request == VT_WAITACTIVE) { mlibc::infoLogger() << "\e[35mmlibc: VT_ACTIVATE/VT_WAITACTIVE are no-ops" << frg::endlog; *result = 0; return 0; } else if (request == TIOCSPTLCK) { mlibc::infoLogger() << "\e[35mmlibc: TIOCSPTLCK is a no-op" << frg::endlog; if (result) *result = 0; return 0; } else if (request == SIOCGIFNAME) { return handle_siocgif( [](auto req, auto ifr) { req.set_index(ifr->ifr_ifindex); }, [](auto resp, auto ifr) { if (resp.error() != managarm::fs::Errors::SUCCESS) return EINVAL; strncpy(ifr->ifr_name, resp.name().data(), IFNAMSIZ); return 0; } ); } else if (request == SIOCGIFCONF) { if (!arg) return EFAULT; auto ifc = reinterpret_cast(arg); managarm::posix::NetserverRequest token_req(getSysdepsAllocator()); managarm::fs::IfreqRequest req(getSysdepsAllocator()); req.set_command(request); auto [offer, send_token_req, send_req, send_tail, recv_resp] = exchangeMsgsSync( getPosixLane(), helix_ng::offer( helix_ng::want_lane, helix_ng::sendBragiHeadOnly(token_req, getSysdepsAllocator()), helix_ng::sendBragiHeadTail(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); auto conversation = offer.descriptor(); HEL_CHECK(offer.error()); HEL_CHECK(send_token_req.error()); HEL_CHECK(send_req.error()); HEL_CHECK(send_tail.error()); HEL_CHECK(recv_resp.error()); auto preamble = bragi::read_preamble(recv_resp); __ensure(!preamble.error()); frg::vector tailBuffer{getSysdepsAllocator()}; tailBuffer.resize(preamble.tail_size()); auto [recv_tail] = exchangeMsgsSync( conversation.getHandle(), helix_ng::recvBuffer(tailBuffer.data(), tailBuffer.size()) ); HEL_CHECK(recv_tail.error()); auto resp = *bragi::parse_head_tail( recv_resp, tailBuffer, getSysdepsAllocator() ); recv_resp.reset(); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); if (ifc->ifc_buf == nullptr) { ifc->ifc_len = int(resp.ifconf_size() * sizeof(struct ifreq)); return 0; } ifc->ifc_len = frg::min(int(resp.ifconf_size() * sizeof(struct ifreq)), ifc->ifc_len); for (size_t i = 0; i < frg::min(resp.ifconf_size(), ifc->ifc_len / sizeof(struct ifreq)); ++i) { auto &conf = resp.ifconf()[i]; sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(conf.ip4()); ifreq *req = &ifc->ifc_req[i]; strncpy(req->ifr_name, conf.name().data(), IFNAMSIZ); memcpy(&req->ifr_addr, &addr, sizeof(addr)); } if (result) *result = 0; return 0; } else if (request == SIOCGIFNETMASK) { return handle_siocgif( [](auto req, auto ifr) { req.set_name(frg::string{ifr->ifr_name, getSysdepsAllocator()}); }, [](auto resp, auto ifr) { if (resp.error() != managarm::fs::Errors::SUCCESS) return EINVAL; sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr = {htonl(resp.ip4_netmask())}; memcpy(&ifr->ifr_netmask, &addr, sizeof(addr)); return 0; } ); } else if (request == SIOCGIFINDEX) { return handle_siocgif( [](auto req, auto ifr) { req.set_name(frg::string{ifr->ifr_name, getSysdepsAllocator()}); }, [](auto resp, auto ifr) { if (resp.error() != managarm::fs::Errors::SUCCESS) return EINVAL; ifr->ifr_ifindex = resp.index(); return 0; } ); } else if (request == SIOCGIFFLAGS) { return handle_siocgif( [](auto req, auto ifr) { req.set_name(frg::string{ifr->ifr_name, getSysdepsAllocator()}); }, [](auto resp, auto ifr) { if (resp.error() != managarm::fs::Errors::SUCCESS) return EINVAL; ifr->ifr_flags = resp.flags(); return 0; } ); } else if (request == SIOCGIFADDR) { return handle_siocgif( [](auto req, auto ifr) { req.set_name(frg::string{ifr->ifr_name, getSysdepsAllocator()}); }, [](auto resp, auto ifr) { if (resp.error() != managarm::fs::Errors::SUCCESS) return EINVAL; sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr = {htonl(resp.ip4_addr())}; memcpy(&ifr->ifr_addr, &addr, sizeof(addr)); return 0; } ); } else if (request == SIOCGIFMTU) { return handle_siocgif( [](auto req, auto ifr) { req.set_name(frg::string{ifr->ifr_name, getSysdepsAllocator()}); }, [](auto resp, auto ifr) { if (resp.error() != managarm::fs::Errors::SUCCESS) return EINVAL; ifr->ifr_mtu = resp.mtu(); return 0; } ); } else if (request == SIOCGIFBRDADDR) { return handle_siocgif( [](auto req, auto ifr) { req.set_name(frg::string{ifr->ifr_name, getSysdepsAllocator()}); }, [](auto resp, auto ifr) { if (resp.error() != managarm::fs::Errors::SUCCESS) return EINVAL; sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr = {htonl(resp.ip4_broadcast_addr())}; memcpy(&ifr->ifr_broadaddr, &addr, sizeof(addr)); return 0; } ); } else if (request == SIOCGIFHWADDR) { return handle_siocgif( [](auto req, auto ifr) { req.set_name(frg::string{ifr->ifr_name, getSysdepsAllocator()}); }, [](auto resp, auto ifr) { if (resp.error() != managarm::fs::Errors::SUCCESS) return EINVAL; sockaddr addr{}; addr.sa_family = ARPHRD_ETHER; memcpy(addr.sa_data, resp.mac().data(), 6); memcpy(&ifr->ifr_hwaddr, &addr, sizeof(addr)); return 0; } ); } else if (request == IOCTL_WDM_MAX_COMMAND) { auto param = reinterpret_cast(arg); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *result = resp.result(); *param = resp.size(); return 0; } else if (request == NVME_IOCTL_ID) { managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *result = resp.result(); return 0; } else if (request == NVME_IOCTL_ADMIN_CMD) { auto param = reinterpret_cast(arg); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); auto [offer, send_ioctl_req, send_req, send_buffer, send_data, recv_resp, recv_data] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::sendBuffer(param, sizeof(*param)), helix_ng::sendBuffer(reinterpret_cast(param->addr), param->data_len), helix_ng::recvInline(), helix_ng::recvBuffer(reinterpret_cast(param->addr), param->data_len) ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); HEL_CHECK(send_req.error()); HEL_CHECK(send_buffer.error()); HEL_CHECK(send_data.error()); HEL_CHECK(recv_resp.error()); HEL_CHECK(recv_data.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); __ensure(resp.error() == managarm::fs::Errors::SUCCESS); *result = resp.result(); param->result = resp.status(); return 0; } else if (request == TFD_IOC_SET_TICKS) { if (!arg) return EFAULT; auto param = reinterpret_cast(arg); managarm::fs::GenericIoctlRequest req(getSysdepsAllocator()); req.set_command(request); req.set_ticks(*param); auto [offer, send_ioctl_req, send_req, recv_resp] = exchangeMsgsSync( handle, helix_ng::offer( helix_ng::sendBragiHeadOnly(ioctl_req, getSysdepsAllocator()), helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline() ) ); HEL_CHECK(offer.error()); HEL_CHECK(send_ioctl_req.error()); HEL_CHECK(send_req.error()); HEL_CHECK(recv_resp.error()); managarm::fs::GenericIoctlReply resp(getSysdepsAllocator()); resp.ParseFromArray(recv_resp.data(), recv_resp.length()); return 0; } else if (request == FICLONE || request == FICLONERANGE) { mlibc::infoLogger() << "\e[35mmlibc: FICLONE/FICLONERANGE are no-ops" << frg::endlog; *result = -1; return EOPNOTSUPP; } else if (request == FS_IOC_GETFLAGS) { mlibc::infoLogger() << "\e[35mmlibc: FS_IOC_GETFLAGS is a no-op" << frg::endlog; *result = 0; return ENOSYS; } else if (request == FS_IOC_FIEMAP) { mlibc::infoLogger() << "\e[35mmlibc: FS_IOC_FIEMAP is a no-op" << frg::endlog; *result = 0; return ENOSYS; } mlibc::infoLogger() << "mlibc: Unexpected ioctl with" << " type: 0x" << frg::hex_fmt(_IOC_TYPE(request)) << ", number: 0x" << frg::hex_fmt(_IOC_NR(request)) << " (raw request: " << frg::hex_fmt(request) << ")" << frg::endlog; return ENOSYS; } } // namespace mlibc