/* lib9p/9p.c - Base 9P protocol utilities for both clients and servers * * Copyright (C) 2024-2025 Luke T. Shumaker * SPDX-License-Identifier: AGPL-3.0-or-later */ #include /* for PRIu{n} */ #include /* for va_* */ #include /* for vsnprintf() */ #include /* for strncpy() */ #define LOG_NAME 9P #include /* for const_byte_str() */ #include #include "internal.h" /* strings ********************************************************************/ const char *lib9p_version_str(enum lib9p_version ver) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" assert(0 <= ver && ver < LIB9P_VER_NUM); #pragma GCC diagnostic pop return _lib9p_table_ver_name[ver]; } const char *lib9p_msgtype_str(enum lib9p_version ver, enum lib9p_msg_type typ) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wtype-limits" assert(0 <= ver && ver < LIB9P_VER_NUM); assert(0 <= typ && typ <= 0xFF); #pragma GCC diagnostic pop return _lib9p_table_msg_name[ver][typ] ?: const_byte_str(typ); } struct lib9p_s lib9p_str(char *s) { if (!s) return (struct lib9p_s){0}; return (struct lib9p_s){ .len = strlen(s), .utf8 = s, }; } struct lib9p_s lib9p_strn(char *s, size_t maxlen) { if (maxlen == 0 || !s) return (struct lib9p_s){0}; return (struct lib9p_s){ .len = strnlen(s, maxlen), .utf8 = s, }; } struct lib9p_s lib9p_str_slice(struct lib9p_s s, uint16_t beg, uint16_t end) { assert(s.len == 0 || s.utf8); assert(beg <= end && end <= s.len); return (struct lib9p_s){ .len = end - beg, .utf8 = &s.utf8[beg], }; } bool lib9p_str_eq(struct lib9p_s a, struct lib9p_s b) { return a.len == b.len && (a.len == 0 || memcmp(a.utf8, b.utf8, a.len) == 0); } /* ctx ************************************************************************/ void lib9p_ctx_clear_error(struct lib9p_ctx *ctx) { assert(ctx); #if CONFIG_9P_ENABLE_9P2000_u ctx->err_num = 0; #endif ctx->err_msg[0] = '\0'; } bool lib9p_ctx_has_error(struct lib9p_ctx *ctx) { assert(ctx); return ctx->err_msg[0]; } int lib9p_error(struct lib9p_ctx *ctx, uint32_t linux_errno, char const *msg) { if (lib9p_ctx_has_error(ctx)) return -1; strncpy(ctx->err_msg, msg, sizeof(ctx->err_msg)); ctx->err_msg[sizeof(ctx->err_msg)-1] = '\0'; #if CONFIG_9P_ENABLE_9P2000_u ctx->err_num = linux_errno; #else (void)(linux_errno); #endif return -1; } int lib9p_errorf(struct lib9p_ctx *ctx, uint32_t linux_errno, char const *fmt, ...) { int n; va_list args; if (lib9p_ctx_has_error(ctx)) return -1; va_start(args, fmt); n = vsnprintf(ctx->err_msg, sizeof(ctx->err_msg), fmt, args); va_end(args); if ((size_t)(n+1) < sizeof(ctx->err_msg)) memset(&ctx->err_msg[n+1], 0, sizeof(ctx->err_msg)-(n+1)); #if CONFIG_9P_ENABLE_9P2000_u ctx->err_num = linux_errno; #else (void)(linux_errno); #endif return -1; } /* main message functions *****************************************************/ static ssize_t _lib9p_validate(uint8_t xxx_low_typ_bit, const char *xxx_errmsg, const struct _lib9p_recv_tentry xxx_table[LIB9P_VER_NUM][0x80], struct lib9p_ctx *ctx, uint8_t *net_bytes) { /* Inspect the first 5 bytes ourselves. */ struct _validate_ctx subctx = { /* input */ .ctx = ctx, .net_size = uint32le_decode(net_bytes), .net_bytes = net_bytes, /* output */ .net_offset = 0, .host_extra = 0, }; if (subctx.net_size < 5) return lib9p_error(ctx, LINUX_EBADMSG, "message is impossibly short"); uint8_t typ = net_bytes[4]; if (typ % 2 != xxx_low_typ_bit) return lib9p_errorf(ctx, LINUX_EOPNOTSUPP, "%s: message_type=%s", xxx_errmsg, lib9p_msgtype_str(ctx->version, typ)); struct _lib9p_recv_tentry tentry = xxx_table[ctx->version][typ/2]; if (!tentry.validate) return lib9p_errorf(ctx, LINUX_EOPNOTSUPP, "unknown message type: %s (protocol_version=%s)", lib9p_msgtype_str(ctx->version, typ), lib9p_version_str(ctx->version)); /* Now use the message-type-specific tentry to process the whole thing. */ if (tentry.validate(&subctx)) return -1; assert(subctx.net_offset <= subctx.net_size); if (subctx.net_offset < subctx.net_size) return lib9p_errorf(ctx, LINUX_EBADMSG, "message has %"PRIu32" extra bytes", subctx.net_size - subctx.net_offset); /* Return. */ ssize_t ret; if (__builtin_add_overflow(tentry.basesize, subctx.host_extra, &ret)) return lib9p_error(ctx, LINUX_EMSGSIZE, "unmarshalled payload overflows SSIZE_MAX"); return ret; } ssize_t lib9p_Tmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes) { return _lib9p_validate(0, "expected a T-message but got an R-message", _lib9p_table_Tmsg_recv, ctx, net_bytes); } ssize_t lib9p_Rmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes) { return _lib9p_validate(1, "expected an R-message but got a T-message", _lib9p_table_Rmsg_recv, ctx, net_bytes); } static void _lib9p_unmarshal(const struct _lib9p_recv_tentry xxx_table[LIB9P_VER_NUM][0x80], struct lib9p_ctx *ctx, uint8_t *net_bytes, enum lib9p_msg_type *ret_typ, void *ret_body) { enum lib9p_msg_type typ = net_bytes[4]; *ret_typ = typ; struct _lib9p_recv_tentry tentry = xxx_table[ctx->version][typ/2]; struct _unmarshal_ctx subctx = { /* input */ .ctx = ctx, .net_bytes = net_bytes, /* output */ .net_offset = 0, .extra = ret_body + tentry.basesize, }; tentry.unmarshal(&subctx, ret_body); } void lib9p_Tmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, enum lib9p_msg_type *ret_typ, void *ret_body) { _lib9p_unmarshal(_lib9p_table_Tmsg_recv, ctx, net_bytes, ret_typ, ret_body); } void lib9p_Rmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, enum lib9p_msg_type *ret_typ, void *ret_body) { _lib9p_unmarshal(_lib9p_table_Rmsg_recv, ctx, net_bytes, ret_typ, ret_body); } static bool _lib9p_marshal(const struct _lib9p_send_tentry xxx_table[LIB9P_VER_NUM][0x80], struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body, size_t *ret_iov_cnt, struct iovec *ret_iov, uint8_t *ret_copied) { struct _marshal_ctx subctx = { /* input */ .ctx = ctx, /* ouptut */ .net_iov_cnt = 1, .net_iov = ret_iov, .net_copied_size = 0, .net_copied = ret_copied, }; struct _lib9p_send_tentry tentry = xxx_table[ctx->version][typ/2]; bool ret_erred = tentry.marshal(&subctx, body); if (ret_iov[subctx.net_iov_cnt-1].iov_len == 0) subctx.net_iov_cnt--; *ret_iov_cnt = subctx.net_iov_cnt; return ret_erred; } bool lib9p_Tmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body, struct lib9p_Tmsg_send_buf *ret) { assert(typ % 2 == 0); memset(ret, 0, sizeof(*ret)); return _lib9p_marshal(_lib9p_table_Tmsg_send, ctx, typ, body, &ret->iov_cnt, ret->iov, ret->copied); } bool lib9p_Rmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body, struct lib9p_Rmsg_send_buf *ret) { assert(typ % 2 == 1); memset(ret, 0, sizeof(*ret)); return _lib9p_marshal(_lib9p_table_Rmsg_send, ctx, typ, body, &ret->iov_cnt, ret->iov, ret->copied); } /* `struct lib9p_stat` helpers ************************************************/ bool lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size, ssize_t *ret_host_size) { struct _validate_ctx subctx = { /* input */ .ctx = ctx, .net_size = net_size, .net_bytes = net_bytes, /* output */ .net_offset = 0, .host_extra = 0, }; if (_lib9p_stat_validate(&subctx)) return true; if (ret_net_size) *ret_net_size = subctx.net_offset; if (ret_host_size) if (__builtin_add_overflow(sizeof(struct lib9p_stat), subctx.host_extra, ret_host_size)) return lib9p_error(ctx, LINUX_EMSGSIZE, "unmarshalled stat object overflows SSIZE_MAX"); return false; } uint32_t lib9p_stat_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, struct lib9p_stat *ret_obj, void *ret_extra) { struct _unmarshal_ctx subctx = { /* input */ .ctx = ctx, .net_bytes = net_bytes, /* output */ .net_offset = 0, .extra = ret_extra, }; _lib9p_stat_unmarshal(&subctx, ret_obj); return subctx.net_offset; } uint32_t lib9p_stat_marshal(struct lib9p_ctx *ctx, uint32_t max_net_size, struct lib9p_stat *obj, uint8_t *ret_bytes) { struct lib9p_ctx _ctx = *ctx; _ctx.max_msg_size = max_net_size; struct iovec iov = {0}; struct _marshal_ctx subctx = { /* input */ .ctx = &_ctx, /* output */ .net_iov_cnt = 1, .net_iov = &iov, .net_copied_size = 0, .net_copied = ret_bytes, }; if (_lib9p_stat_marshal(&subctx, obj)) return 0; return subctx.net_iov[0].iov_len; }