summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-04-12 06:37:54 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-04-13 09:05:37 -0600
commitbf32c2cd495099c93195b202158f46870ceed0ef (patch)
tree08b47b0e0772f590aa3ae48254590088a4b4402c
parentd3b4d23a8077d74cd4628948e4687d2d5c24de80 (diff)
lib9p: Test+fix that auth data is tracked in the context
-rw-r--r--lib9p/include/lib9p/srv.h4
-rw-r--r--lib9p/srv.c156
-rw-r--r--lib9p/tests/test_server/CMakeLists.txt1
-rw-r--r--lib9p/tests/test_server/fs_whoami.c156
-rw-r--r--lib9p/tests/test_server/fs_whoami.h20
-rw-r--r--lib9p/tests/test_server/main.c2
-rwxr-xr-xlib9p/tests/testclient-p9p3
-rw-r--r--lib9p/tests/testclient-p9p.explog4
-rw-r--r--lib9p/tests/testclient-sess.c22
-rw-r--r--lib9p/tests/testclient-sess.explog24
10 files changed, 337 insertions, 55 deletions
diff --git a/lib9p/include/lib9p/srv.h b/lib9p/include/lib9p/srv.h
index 1b14c32..7109179 100644
--- a/lib9p/include/lib9p/srv.h
+++ b/lib9p/include/lib9p/srv.h
@@ -24,6 +24,10 @@ CR_CHAN_DECLARE(_lib9p_srv_flushch, bool);
struct lib9p_srv_authinfo {
lib9p_nuid_t uid;
struct lib9p_s uname;
+
+ BEGIN_PRIVATE(LIB9P_SRV_H);
+ unsigned int refcount;
+ END_PRIVATE(LIB9P_SRV_H);
};
struct lib9p_srv_ctx {
diff --git a/lib9p/srv.c b/lib9p/srv.c
index e6a92ad..50e5dae 100644
--- a/lib9p/srv.c
+++ b/lib9p/srv.c
@@ -98,6 +98,7 @@ struct srv_pathinfo {
struct srv_fidinfo {
srv_path_t path;
+ struct lib9p_srv_authinfo *authinfo;
uint8_t flags;
enum srv_filetype type;
union {
@@ -109,6 +110,10 @@ struct srv_fidinfo {
size_t idx;
uint64_t off;
} dir;
+ struct {
+ struct lib9p_s aname;
+ bool completed;
+ } auth;
};
};
@@ -171,6 +176,35 @@ static inline bool srv_check_perm(struct srv_req *ctx, struct lib9p_stat *stat,
return mode & action;
}
+struct lib9p_srv_authinfo *srv_authinfo_new(struct lib9p_s uname, lib9p_nuid_t uid) {
+ struct lib9p_srv_authinfo *ret = malloc(sizeof(struct lib9p_srv_authinfo) + uname.len);
+ if (!ret)
+ return NULL;
+ ret->uid = uid;
+ ret->uname.len = uname.len;
+ ret->uname.utf8 = (void *)&ret[1];
+ memcpy(ret->uname.utf8, uname.utf8, uname.len);
+ ret->refcount = 1;
+ return ret;
+}
+
+struct lib9p_srv_authinfo *srv_authinfo_decref(struct lib9p_srv_authinfo *authinfo) {
+ assert(authinfo);
+ assert(authinfo->refcount);
+ authinfo->refcount--;
+ if (!authinfo->refcount) {
+ free(authinfo);
+ return NULL;
+ }
+ return authinfo;
+}
+
+struct lib9p_srv_authinfo *srv_authinfo_incref(struct lib9p_srv_authinfo *authinfo) {
+ assert(authinfo);
+ authinfo->refcount++;
+ return authinfo;
+}
+
/**
* Ensures that `file` is saved into the pathmap, and increments the
* gc_refcount by 1 (for presumptive insertion into the fidmap).
@@ -228,11 +262,7 @@ static inline void srv_path_decref(struct srv_req *ctx, srv_path_t path) {
static inline void srv_fid_del(struct srv_req *ctx, lib9p_fid_t fid, bool remove) {
struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, fid);
- if (!fidinfo) {
- lib9p_errorf(&ctx->basectx,
- LINUX_EBADF, "bad file number %"PRIu32, fid);
- return;
- }
+ assert(fidinfo);
if (fidinfo->flags & FIDFLAG_RCLOSE)
remove = true;
struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path);
@@ -270,7 +300,8 @@ static inline void srv_fid_del(struct srv_req *ctx, lib9p_fid_t fid, bool remove
}
pathinfo->io_refcount--;
}
- srv_path_decref(ctx, LO_CALL(pathinfo->file, qid).path);
+ fidinfo->authinfo = srv_authinfo_decref(fidinfo->authinfo);
+ srv_path_decref(ctx, fidinfo->path);
map_del(&ctx->parent_sess->fids, fid);
}
@@ -296,8 +327,9 @@ static struct srv_fidinfo *srv_fid_store(struct srv_req *ctx, lib9p_fid_t fid, s
}
}
struct srv_fidinfo *fidinfo = map_store(&ctx->parent_sess->fids, fid, (struct srv_fidinfo){
- .path = qid.path,
- .type = srv_qid_filetype(qid),
+ .path = qid.path,
+ .type = srv_qid_filetype(qid),
+ .authinfo = srv_authinfo_incref(ctx->authinfo),
});
assert(fidinfo);
return fidinfo;
@@ -443,13 +475,11 @@ void lib9p_srv_read(struct lib9p_srv *srv, lo_interface net_stream_conn _conn) {
}
if (srv_read_exactly(conn.fd, buf, 7, &done))
break;
- struct lib9p_srv_authinfo authinfo = {};
struct srv_req req = {
.basectx = {
.version = sess.version,
.max_msg_size = sess.max_msg_size,
},
- .authinfo = &authinfo,
.parent_sess = &sess,
.tag = uint16le_decode(&buf[5]),
@@ -715,19 +745,22 @@ static void handle_Tauth(struct srv_req *ctx,
struct lib9p_msg_Rauth *resp) {
srv_handler_common(ctx, req, resp);
- ctx->authinfo->uid = req->n_uid;
- ctx->authinfo->uname = req->uname;
struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv;
-
if (!srv->auth) {
lib9p_error(&ctx->basectx,
LINUX_EOPNOTSUPP, "authentication not required");
return;
}
+ ctx->authinfo = srv_authinfo_new(req->uname, req->n_uid);
+
srv->auth(ctx, req->aname);
+
lib9p_error(&ctx->basectx,
LINUX_EOPNOTSUPP, "TODO: auth not implemented");
+
+ if (lib9p_ctx_has_error(&ctx->basectx))
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
static void handle_Tattach(struct srv_req *ctx,
@@ -735,58 +768,59 @@ static void handle_Tattach(struct srv_req *ctx,
struct lib9p_msg_Rattach *resp) {
srv_handler_common(ctx, req, resp);
- ctx->authinfo->uid = req->n_uid;
- ctx->authinfo->uname = req->uname;
- struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv;
+ if (req->fid == LIB9P_FID_NOFID) {
+ lib9p_error(&ctx->basectx,
+ LINUX_EBADF, "cannot assign to NOFID");
+ return;
+ }
+ struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv;
if (srv->auth) {
- /*
- struct lib9p_srv_filehandle *fh = map_get(req->afid);
- if (!fh)
+ struct srv_fidinfo *afid = map_load(&ctx->parent_sess->fids, req->afid);
+ if (!afid)
lib9p_error(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file is not a valid FID");
- else if (fh->type != FH_AUTH)
+ else if (afid->type != SRV_FILETYPE_AUTH)
lib9p_error(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file is not an auth-file");
- else if (!lib9p_str_eq(fh->data.auth.uname, req->uname))
+ else if (!lib9p_str_eq(afid->authinfo->uname, req->uname))
lib9p_errorf(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file is for user=\"%.*s\" and cannot be used for user=\"%.*s\"",
- fh->data.auth.uname.len, fh->data.auth.uname.utf8,
+ afid->authinfo->uname.len, afid->authinfo->uname.utf8,
req->uname.len, req->uname.utf8);
- else if (!lib9p_str_eq(fh->data.auth.aname, req->aname))
+#if CONFIG_9P_ENABLE_9P2000_u
+ else if (afid->authinfo->uid != req->n_uid)
+ lib9p_errorf(&ctx->basectx,
+ LINUX_EACCES, "FID provided as auth-file is for user=%"PRIu32" and cannot be used for user=%"PRIu32,
+ afid->authinfo->uid, req->n_uid);
+#endif
+ else if (!lib9p_str_eq(afid->auth.aname, req->aname))
lib9p_errorf(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file is for tree=\"%.*s\" and cannot be used for tree=\"%.*s\"",
- fh->data.auth.aname.len, fh->data.auth.aname.utf8,
+ afid->auth.aname.len, afid->auth.aname.utf8,
req->aname.len, req->aname.utf8);
- else if (!fh->data.auth.authenticated)
+ else if (!afid->auth.completed)
lib9p_error(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file has not completed authentication");
- fh->refcount--;
- if (lib9p_ctx_has_error(&ctx->ctx))
+ if (lib9p_ctx_has_error(&ctx->basectx))
return;
- */
- lib9p_error(&ctx->basectx,
- LINUX_EOPNOTSUPP, "TODO: auth not (yet?) implemented");
- return;
+ ctx->authinfo = srv_authinfo_incref(afid->authinfo);
} else {
if (req->afid != LIB9P_FID_NOFID) {
lib9p_error(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file, but no auth-file is required");
return;
}
- }
-
- if (req->fid == LIB9P_FID_NOFID) {
- lib9p_error(&ctx->basectx,
- LINUX_EBADF, "cannot assign to NOFID");
- return;
+ ctx->authinfo = srv_authinfo_new(req->uname, req->n_uid);
}
/* 1. File object */
lo_interface lib9p_srv_file root_file = srv->rootdir(ctx, req->aname);
assert(LO_IS_NULL(root_file) == lib9p_ctx_has_error(&ctx->basectx));
- if (lib9p_ctx_has_error(&ctx->basectx))
+ if (lib9p_ctx_has_error(&ctx->basectx)) {
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
return;
+ }
struct lib9p_qid root_qid = LO_CALL(root_file, qid);
assert(srv_qid_filetype(root_qid) == SRV_FILETYPE_DIR);
@@ -797,9 +831,11 @@ static void handle_Tattach(struct srv_req *ctx,
/* 3. fidinfo */
if (!srv_fid_store(ctx, req->fid, root_pathinfo, false)) {
srv_path_decref(ctx, root_qid.path);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
return;
}
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
resp->qid = root_qid;
return;
}
@@ -831,6 +867,7 @@ static void handle_Twalk(struct srv_req *ctx,
LINUX_EBADF, "bad file number %"PRIu32, req->fid);
return;
}
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path);
assert(pathinfo);
@@ -884,6 +921,7 @@ static void handle_Twalk(struct srv_req *ctx,
if (resp->nwqid > 0)
lib9p_ctx_clear_error(&ctx->basectx);
}
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
static void handle_Topen(struct srv_req *ctx,
@@ -912,6 +950,7 @@ static void handle_Topen(struct srv_req *ctx,
return;
}
}
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
/* Variables. */
lib9p_o_t reqmode = req->mode;
@@ -925,23 +964,23 @@ static void handle_Topen(struct srv_req *ctx,
assert(parent);
struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, ctx);
if (lib9p_ctx_has_error(&ctx->basectx))
- return;
+ goto topen_return;
lib9p_stat_assert(parent_stat);
if (!srv_check_perm(ctx, &parent_stat, 0b010)) {
lib9p_error(&ctx->basectx,
LINUX_EACCES, "permission denied to remove-on-close");
- return;
+ goto topen_return;
}
fidflags |= FIDFLAG_RCLOSE;
}
struct lib9p_stat stat = LO_CALL(pathinfo->file, stat, ctx);
if (lib9p_ctx_has_error(&ctx->basectx))
- return;
+ goto topen_return;
lib9p_stat_assert(stat);
if ((stat.file_mode & LIB9P_DM_EXCL) && pathinfo->io_refcount) {
lib9p_error(&ctx->basectx,
LINUX_EEXIST, "exclusive file is already opened");
- return;
+ goto topen_return;
}
if (stat.file_mode & LIB9P_DM_APPEND)
reqmode = reqmode & ~LIB9P_O_TRUNC;
@@ -968,7 +1007,7 @@ static void handle_Topen(struct srv_req *ctx,
if (!srv_check_perm(ctx, &stat, perm_bits)) {
lib9p_error(&ctx->basectx,
LINUX_EACCES, "permission denied");
- return;
+ goto topen_return;
}
/* Actually make the call. */
@@ -979,7 +1018,7 @@ static void handle_Topen(struct srv_req *ctx,
fidinfo->dir.io = LO_CALL(pathinfo->file, dopen, ctx);
assert(LO_IS_NULL(fidinfo->dir.io) == lib9p_ctx_has_error(&ctx->basectx));
if (lib9p_ctx_has_error(&ctx->basectx))
- return;
+ goto topen_return;
fidinfo->dir.idx = 0;
fidinfo->dir.off = 0;
qid = LO_CALL(fidinfo->dir.io, qid);
@@ -991,7 +1030,7 @@ static void handle_Topen(struct srv_req *ctx,
reqmode & LIB9P_O_TRUNC);
assert(LO_IS_NULL(fidinfo->file.io) == lib9p_ctx_has_error(&ctx->basectx));
if (lib9p_ctx_has_error(&ctx->basectx))
- return;
+ goto topen_return;
qid = LO_CALL(fidinfo->file.io, qid);
iounit = LO_CALL(fidinfo->file.io, iounit);
break;
@@ -1012,6 +1051,8 @@ static void handle_Topen(struct srv_req *ctx,
fidinfo->flags = fidflags;
resp->qid = qid;
resp->iounit = iounit;
+ topen_return:
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
static void handle_Tcreate(struct srv_req *ctx,
@@ -1044,6 +1085,7 @@ static void handle_Tread(struct srv_req *ctx,
}
/* Do it. */
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
switch (fidinfo->type) {
case SRV_FILETYPE_DIR:
/* Translate byte-offset to object-index. */
@@ -1056,6 +1098,7 @@ static void handle_Tread(struct srv_req *ctx,
lib9p_errorf(&ctx->basectx,
LINUX_EINVAL, "invalid offset (must be 0 or %"PRIu64"): %"PRIu64,
fidinfo->dir.off, req->offset);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
return;
}
/* Do it. */
@@ -1087,6 +1130,7 @@ static void handle_Tread(struct srv_req *ctx,
assert_notreached("TODO: auth not yet implemented");
break;
}
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
static void handle_Twrite(struct srv_req *ctx,
@@ -1110,7 +1154,9 @@ static void handle_Twrite(struct srv_req *ctx,
}
/* Do it. */
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
resp->count = LO_CALL(fidinfo->file.io, pwrite, ctx, req->data, req->count, req->offset);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
static void handle_Tclunk(struct srv_req *ctx,
@@ -1118,7 +1164,16 @@ static void handle_Tclunk(struct srv_req *ctx,
struct lib9p_msg_Rclunk *resp) {
srv_handler_common(ctx, req, resp);
+ struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
+ if (!fidinfo) {
+ lib9p_errorf(&ctx->basectx,
+ LINUX_EBADF, "bad file number %"PRIu32, req->fid);
+ return;
+ }
+
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
srv_fid_del(ctx, req->fid, false);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
static void handle_Tremove(struct srv_req *ctx,
@@ -1126,7 +1181,16 @@ static void handle_Tremove(struct srv_req *ctx,
struct lib9p_msg_Rremove *resp) {
srv_handler_common(ctx, req, resp);
+ struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
+ if (!fidinfo) {
+ lib9p_errorf(&ctx->basectx,
+ LINUX_EBADF, "bad file number %"PRIu32, req->fid);
+ return;
+ }
+
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
srv_fid_del(ctx, req->fid, true);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
static void handle_Tstat(struct srv_req *ctx,
@@ -1143,9 +1207,11 @@ static void handle_Tstat(struct srv_req *ctx,
struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path);
assert(pathinfo);
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
resp->stat = LO_CALL(pathinfo->file, stat, ctx);
if (!lib9p_ctx_has_error(&ctx->basectx))
lib9p_stat_assert(resp->stat);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
static void handle_Twstat(struct srv_req *ctx,
diff --git a/lib9p/tests/test_server/CMakeLists.txt b/lib9p/tests/test_server/CMakeLists.txt
index eb16165..681e583 100644
--- a/lib9p/tests/test_server/CMakeLists.txt
+++ b/lib9p/tests/test_server/CMakeLists.txt
@@ -11,6 +11,7 @@ add_library(test_server_objs OBJECT
main.c
fs_shutdown.c
fs_slowread.c
+ fs_whoami.c
)
target_include_directories(test_server_objs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/config)
target_include_directories(test_server_objs PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
diff --git a/lib9p/tests/test_server/fs_whoami.c b/lib9p/tests/test_server/fs_whoami.c
new file mode 100644
index 0000000..ff6dd25
--- /dev/null
+++ b/lib9p/tests/test_server/fs_whoami.c
@@ -0,0 +1,156 @@
+/* lib9p/tests/test_server/fs_whoami.c - /whoami API endpoint
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdio.h> /* for snprintf() */
+#include <stdlib.h> /* for malloc(), realloc(), free() */
+
+#include "fs_whoami.h"
+
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct whoami_file, whoami_file, static);
+
+struct whoami_fio {
+ struct whoami_file *parent;
+ size_t buf_len;
+ char *buf;
+};
+LO_IMPLEMENTATION_H(lib9p_srv_fio, struct whoami_fio, whoami_fio);
+LO_IMPLEMENTATION_C(lib9p_srv_fio, struct whoami_fio, whoami_fio, static);
+
+size_t whoami_len(struct lib9p_srv_ctx *ctx) {
+ assert(ctx);
+ assert(ctx->authinfo);
+
+ size_t len = 0;
+ uint32_t uid = ctx->authinfo->uid;
+ while (uid) {
+ len++;
+ uid /= 10;
+ }
+ if (!len)
+ len++;
+ len += 2;
+ len += ctx->authinfo->uname.len;
+ return len;
+}
+
+/* srv_file *******************************************************************/
+
+static void whoami_file_free(struct whoami_file *self) {
+ assert(self);
+}
+static struct lib9p_qid whoami_file_qid(struct whoami_file *self) {
+ assert(self);
+ return (struct lib9p_qid){
+ .type = LIB9P_QT_FILE,
+ .vers = 1,
+ .path = self->pathnum,
+ };
+}
+
+static struct lib9p_stat whoami_file_stat(struct whoami_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
+
+ return (struct lib9p_stat){
+ .kern_type = 0,
+ .kern_dev = 0,
+ .file_qid = whoami_file_qid(self),
+ .file_mode = 0444,
+ .file_atime = UTIL9P_ATIME,
+ .file_mtime = UTIL9P_MTIME,
+ .file_size = whoami_len(ctx),
+ .file_name = lib9p_str(self->name),
+ .file_owner_uid = lib9p_str("root"),
+ .file_owner_gid = lib9p_str("root"),
+ .file_last_modified_uid = lib9p_str("root"),
+ .file_extension = lib9p_str(NULL),
+ .file_owner_n_uid = 0,
+ .file_owner_n_gid = 0,
+ .file_last_modified_n_uid = 0,
+ };
+}
+static void whoami_file_wstat(struct whoami_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_stat) {
+ assert(self);
+ assert(ctx);
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot wstat API file");
+}
+static void whoami_file_remove(struct whoami_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot remove API file");
+}
+
+LIB9P_SRV_NOTDIR(struct whoami_file, whoami_file)
+
+static lo_interface lib9p_srv_fio whoami_file_fopen(struct whoami_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
+ assert(self);
+ assert(ctx);
+
+ struct whoami_fio *ret = malloc(sizeof(struct whoami_fio));
+ ret->parent = self;
+ ret->buf_len = 0;
+ ret->buf = NULL;
+
+ return lo_box_whoami_fio_as_lib9p_srv_fio(ret);
+}
+
+/* srv_fio ********************************************************************/
+
+static void whoami_fio_iofree(struct whoami_fio *self) {
+ assert(self);
+ if (self->buf)
+ free(self->buf);
+ free(self);
+}
+
+static struct lib9p_qid whoami_fio_qid(struct whoami_fio *self) {
+ assert(self);
+ assert(self->parent);
+ return whoami_file_qid(self->parent);
+}
+
+static uint32_t whoami_fio_iounit(struct whoami_fio *self) {
+ assert(self);
+ return 0;
+}
+
+static uint32_t whoami_fio_pwrite(struct whoami_fio *LM_UNUSED(self),
+ struct lib9p_srv_ctx *LM_UNUSED(ctx),
+ void *LM_UNUSED(buf), uint32_t LM_UNUSED(byte_count),
+ uint64_t LM_UNUSED(offset)) {
+ assert_notreached("not writable");
+}
+static void whoami_fio_pread(struct whoami_fio *self, struct lib9p_srv_ctx *ctx,
+ uint32_t byte_count, uint64_t byte_offset,
+ struct iovec *ret) {
+ assert(self);
+ assert(ctx);
+ assert(ret);
+
+ size_t data_size = whoami_len(ctx);
+ if (self->buf_len < data_size+1) {
+ self->buf = realloc(self->buf, data_size+1);
+ self->buf_len = data_size+1;
+ }
+ snprintf(self->buf, self->buf_len, "%"PRIu32" %.*s\n",
+ ctx->authinfo->uid, ctx->authinfo->uname.len, ctx->authinfo->uname.utf8);
+
+ if (byte_offset > (uint64_t)data_size) {
+ lib9p_error(&ctx->basectx,
+ LINUX_EINVAL, "offset is past end-of-file length");
+ return;
+ }
+
+ size_t beg_off = (size_t)byte_offset;
+ size_t end_off = beg_off + (size_t)byte_count;
+ if (end_off > data_size)
+ end_off = data_size;
+
+ *ret = (struct iovec){
+ .iov_base = &self->buf[beg_off],
+ .iov_len = end_off-beg_off,
+ };
+}
diff --git a/lib9p/tests/test_server/fs_whoami.h b/lib9p/tests/test_server/fs_whoami.h
new file mode 100644
index 0000000..0d3d311
--- /dev/null
+++ b/lib9p/tests/test_server/fs_whoami.h
@@ -0,0 +1,20 @@
+/* lib9p/tests/test_server/fs_whoami.h - /whoami API endpoint
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIB9P_TESTS_TEST_SERVER_FS_WHOAMI_H_
+#define _LIB9P_TESTS_TEST_SERVER_FS_WHOAMI_H_
+
+#include <util9p/static.h>
+#include <libhw/host_net.h>
+
+struct whoami_file {
+ char *name;
+ uint64_t pathnum;
+};
+LO_IMPLEMENTATION_H(lib9p_srv_file, struct whoami_file, whoami_file);
+#define lo_box_whoami_file_as_lib9p_srv_file(obj) util9p_box(whoami_file, obj)
+
+#endif /* _LIB9P_TESTS_TEST_SERVER_FS_WHOAMI_H_ */
diff --git a/lib9p/tests/test_server/main.c b/lib9p/tests/test_server/main.c
index b543892..e89a75e 100644
--- a/lib9p/tests/test_server/main.c
+++ b/lib9p/tests/test_server/main.c
@@ -21,6 +21,7 @@
#include "static.h"
#include "fs_shutdown.h"
#include "fs_slowread.h"
+#include "fs_whoami.h"
/* configuration **************************************************************/
@@ -76,6 +77,7 @@ struct lib9p_srv_file root =
.flushable = false),
API_FILE(7, "slowread-flushable", slowread,
.flushable = true),
+ API_FILE(8, "whoami", whoami),
);
static lo_interface lib9p_srv_file get_root(struct lib9p_srv_ctx *LM_UNUSED(ctx), struct lib9p_s LM_UNUSED(treename)) {
diff --git a/lib9p/tests/testclient-p9p b/lib9p/tests/testclient-p9p
index 2e5a6f3..9c9fb5e 100755
--- a/lib9p/tests/testclient-p9p
+++ b/lib9p/tests/testclient-p9p
@@ -27,7 +27,8 @@ expect_lines \
'--r--r--r-- M 0 root root 166 Oct 7 2024 README.md' \
'---w--w--w- M 0 root root 0 Oct 7 2024 shutdown' \
'--r--r--r-- M 0 root root 6 Oct 7 2024 slowread' \
- '--r--r--r-- M 0 root root 6 Oct 7 2024 slowread-flushable'
+ '--r--r--r-- M 0 root root 6 Oct 7 2024 slowread-flushable' \
+ '--r--r--r-- M 0 root root 9 Oct 7 2024 whoami'
out=$("${client[@]}" ls -l 'Documentation/')
expect_lines \
diff --git a/lib9p/tests/testclient-p9p.explog b/lib9p/tests/testclient-p9p.explog
index 905a60f..45651a4 100644
--- a/lib9p/tests/testclient-p9p.explog
+++ b/lib9p/tests/testclient-p9p.explog
@@ -19,8 +19,8 @@
> Topen { tag=0 fid=1 mode=(MODE_READ) }
< Ropen { tag=0 qid={ type=(DIR) vers=1 path=1 } iounit=0 }
> Tread { tag=0 fid=1 offset=0 count=4096 }
-< Rread { tag=0 count=361 data=<bytedata> }
-> Tread { tag=0 fid=1 offset=361 count=4096 }
+< Rread { tag=0 count=428 data=<bytedata> }
+> Tread { tag=0 fid=1 offset=428 count=4096 }
< Rread { tag=0 count=0 data="" }
> Tclunk { tag=0 fid=1 }
< Rclunk { tag=0 }
diff --git a/lib9p/tests/testclient-sess.c b/lib9p/tests/testclient-sess.c
index 423dc2c..437c489 100644
--- a/lib9p/tests/testclient-sess.c
+++ b/lib9p/tests/testclient-sess.c
@@ -91,12 +91,28 @@ int main(int argc, char *argv[]) {
recv9p(); /* Rversion */
ctx.version = LIB9P_VER_9P2000_u;
- /* ext version ********************************************************/
- send9p(Tversion, .tag=0, .max_msg_size=57, .version=lib9p_str("9P2000.u"));
+ /* ext version, users *************************************************/
+ send9p(Tversion, .tag=0, .max_msg_size=(8*1024), .version=lib9p_str("9P2000.u"));
recv9p(); /* Rversion */
ctx.version = LIB9P_VER_9P2000_u;
+ send9p(Tattach, .tag=0, .fid=0, .afid=LIB9P_FID_NOFID, .uname=lib9p_str("alice"), .n_uid=1000, .aname=lib9p_str(""));
+ recv9p(); /* Rattach */
+ send9p(Tattach, .tag=0, .fid=1, .afid=LIB9P_FID_NOFID, .uname=lib9p_str("bob"), .n_uid=1001, .aname=lib9p_str(""));
+ recv9p(); /* Rattach */
+ wname[0] = lib9p_str("whoami"); send9p(Twalk, .tag=0, .fid=0, .newfid=2, .nwname=1, .wname=wname);
+ recv9p(); /* Rwalk */
+ wname[0] = lib9p_str("whoami"); send9p(Twalk, .tag=0, .fid=1, .newfid=3, .nwname=1, .wname=wname);
+ recv9p(); /* Rwalk */
+ send9p(Topen, .tag=0, .fid=2, .mode=LIB9P_O_MODE_READ);
+ recv9p(); /* Ropen */
+ send9p(Topen, .tag=0, .fid=3, .mode=LIB9P_O_MODE_READ);
+ recv9p(); /* Ropen */
+ send9p(Tread, .tag=0, .fid=2, .offset=0, .count=100);
+ recv9p(); /* Rread */
+ send9p(Tread, .tag=0, .fid=3, .offset=0, .count=100);
+ recv9p(); /* Rread */
- /* main session *******************************************************/
+ /* flush **************************************************************/
send9p(Tversion, .tag=0, .max_msg_size=(8*1024), .version=lib9p_str("9P2000"));
recv9p(); /* Rversion */
ctx.version = LIB9P_VER_9P2000;
diff --git a/lib9p/tests/testclient-sess.explog b/lib9p/tests/testclient-sess.explog
index b1f3085..6aab242 100644
--- a/lib9p/tests/testclient-sess.explog
+++ b/lib9p/tests/testclient-sess.explog
@@ -11,11 +11,27 @@
> Tversion { tag=0 max_msg_size=57 version="9P2025.u" }
< Rversion { tag=0 max_msg_size=57 version="9P2000.u" }
-# ext version ##################################################################
-> Tversion { tag=0 max_msg_size=57 version="9P2000.u" }
-< Rversion { tag=0 max_msg_size=57 version="9P2000.u" }
+# ext version, users ###########################################################
+> Tversion { tag=0 max_msg_size=8192 version="9P2000.u" }
+< Rversion { tag=0 max_msg_size=4120 version="9P2000.u" }
+> Tattach { tag=0 fid=0 afid=NOFID uname="alice" aname="" n_uid=1000 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+> Tattach { tag=0 fid=1 afid=NOFID uname="bob" aname="" n_uid=1001 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+> Twalk { tag=0 fid=0 newfid=2 nwname=1 wname=["whoami" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(0) vers=1 path=8 } ] }
+> Twalk { tag=0 fid=1 newfid=3 nwname=1 wname=["whoami" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(0) vers=1 path=8 } ] }
+> Topen { tag=0 fid=2 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=8 } iounit=0 }
+> Topen { tag=0 fid=3 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=8 } iounit=0 }
+> Tread { tag=0 fid=2 offset=0 count=100 }
+< Rread { tag=0 count=11 data="1000 alice\n" }
+> Tread { tag=0 fid=3 offset=0 count=100 }
+< Rread { tag=0 count=9 data="1001 bob\n" }
-# main session #################################################################
+# flush ########################################################################
> Tversion { tag=0 max_msg_size=8192 version="9P2000" }
< Rversion { tag=0 max_msg_size=4120 version="9P2000" }
> Tattach { tag=0 fid=0 afid=NOFID uname="nobody" aname="" n_uid=0 }