summaryrefslogtreecommitdiff
path: root/lib9p/include/lib9p/9p.h
blob: 7724fc86d3a196b5091627ab84e1cb70b6ae90f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/* lib9p/9p.h - Base 9P protocol definitions for both clients and servers
 *
 * Copyright (C) 2024-2025  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#ifndef _LIB9P_9P_H_
#define _LIB9P_9P_H_

#include <stdbool.h>
#include <sys/types.h> /* for ssize_t */

#include <libmisc/assert.h>

#include <lib9p/linux-errno.h>
#include <lib9p/9p.generated.h>

#ifndef CONFIG_9P_MAX_ERR_SIZE
	#error config.h must define CONFIG_9P_MAX_ERR_SIZE
#endif

/* constants ******************************************************************/

enum {
	LIB9P_DEFAULT_PORT_9FS   =  564,
	LIB9P_DEFAULT_PORT_STYX  = 6666,
};

/* strings ********************************************************************/

const char *lib9p_version_str(enum lib9p_version);
const char *lib9p_msgtype_str(enum lib9p_version, enum lib9p_msg_type);

struct lib9p_s lib9p_str(char *s);
struct lib9p_s lib9p_strn(char *s, size_t maxlen);
struct lib9p_s lib9p_str_slice(struct lib9p_s s, uint16_t beg, uint16_t end);
#define lib9p_str_sliceleft(s, beg) lib9p_str_slice(s, beg, (s).len)
bool lib9p_str_eq(struct lib9p_s a, struct lib9p_s b);

/* ctx ************************************************************************/

struct lib9p_ctx {
	/* negotiated */
	enum lib9p_version      version;
	uint32_t                max_msg_size;

	/* state */
#ifdef CONFIG_9P_ENABLE_9P2000_u
	uint32_t                err_num;
#endif
	[[gnu::nonstring]] char err_msg[CONFIG_9P_MAX_ERR_SIZE];
};

void lib9p_ctx_clear_error(struct lib9p_ctx *ctx);

bool lib9p_ctx_has_error(struct lib9p_ctx *ctx);

/** Write an static error into ctx, return -1.  */
int lib9p_error(struct lib9p_ctx *ctx, uint32_t linux_errno, char const *msg);
/** Write a printf-style error into ctx, return -1.  */
int lib9p_errorf(struct lib9p_ctx *ctx, uint32_t linux_errno, char const *fmt, ...);

/* main T-message functions ***************************************************/

/**
 * Validate a message's structure; its size, string encodings, enums,
 * and bitfields.
 *
 * Return how much space the message will take when unmarshaled.  This
 * number may be larger than net_bytes due to (1) struct padding, (2)
 * array pointers.
 *
 * Emits an error (return -1, set ctx->err_num and ctx->err_msg) if
 * either the message type is unknown, or if net_bytes is too short
 * for that message type, or if an invalid string (invalid UTF-8,
 * contains a nul-byte) is encountered.
 *
 * @param net_bytes : the complete request, starting with the "size[4]"
 *
 * @return required size, or -1 on error
 *
 * @errno LINUX_EOPNOTSUPP: message is an R-message
 * @errno LINUX_EOPNOTSUPP: message has unknown type
 * @errno LINUX_EBADMSG: message is wrong size for content
 * @errno LINUX_EBADMSG: message contains invalid UTF-8
 * @errno LINUX_EBADMSG: message contains a bitfield with unknown bits
 * @errno LINUX_EMSGSIZE: would-be return value overflows SSIZE_MAX
 */
ssize_t lib9p_Tmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes);

/**
 * Unmarshal the 9P message `net_bytes` into the C struct `ret_body`.
 *
 * lib9p_Tmsg_unmarshal does no validation; you must run
 * lib9p_Tmsg_validate() first.
 *
 * @param ctx : negotiated protocol parameters
 * @param net_bytes : the complete message, starting with the "size[4]"
 *
 * @return ret_typ : the mesage type
 * @return ret_body : the message body, must be at least lib9p_Tmsg_validate() bytes
 */
void lib9p_Tmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes,
                          enum lib9p_msg_type *ret_typ, void *ret_body);

/**
 * Marshal a `struct lib9p_msg_{typ}` structure into a byte-array.
 *
 * lib9p_Tmsg_marshal does no validation; it trusts that the
 * programmer won't give it garbage input.  However, just as it
 * doesn't marshal struct fields that aren't in ctx->version, it won't
 * marshal bitfield bits that aren't in ctx->version; it applies a
 * version-specific mask to bitfields.
 *
 * @param ctx : negotiated protocol parameters, where to record errors
 * @param typ : the message type
 * @param msg : the message to encode
 *
 * @return ret_bytes : the buffer to encode to, must be at be at least ctx->max_msg_size bytes
 * @return whether there was an error (false=success, true=error)
 *
 * @errno LINUX_ERANGE: reply does not fit in ctx->max_msg_size
 */
bool lib9p_Tmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body,
                        struct lib9p_Tmsg_send_buf *ret);

/* main R-message functions ***************************************************/

/** Same as above, but for R-messages instead of T-messages.  */

ssize_t lib9p_Rmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes);
void lib9p_Rmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes,
                          enum lib9p_msg_type *ret_typ, void *ret_body);
bool lib9p_Rmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body,
                        struct lib9p_Rmsg_send_buf *ret);


/* `struct lib9p_stat` helpers ************************************************/

/** Assert that a `struct lib9p_stat` object looks valid.  */
static inline void lib9p_stat_assert(struct lib9p_stat stat) {
	assert( ((bool)(stat.file_mode & LIB9P_DM_DIR         )) == ((bool)(stat.file_qid.type & LIB9P_QT_DIR         )) );
	assert( ((bool)(stat.file_mode & LIB9P_DM_APPEND      )) == ((bool)(stat.file_qid.type & LIB9P_QT_APPEND      )) );
	assert( ((bool)(stat.file_mode & LIB9P_DM_EXCL        )) == ((bool)(stat.file_qid.type & LIB9P_QT_EXCL        )) );
	assert( ((bool)(stat.file_mode & _LIB9P_DM_PLAN9_MOUNT)) == ((bool)(stat.file_qid.type & _LIB9P_QT_PLAN9_MOUNT)) );
	assert( ((bool)(stat.file_mode & LIB9P_DM_AUTH        )) == ((bool)(stat.file_qid.type & LIB9P_QT_AUTH        )) );
	assert( ((bool)(stat.file_mode & LIB9P_DM_TMP         )) == ((bool)(stat.file_qid.type & LIB9P_QT_TMP         )) );
	assert( (stat.file_size == 0) || !(stat.file_mode & LIB9P_DM_DIR) );
}

/**
 * Validate a message's `stat` structure.
 *
 * @param ctx : negotiated protocol parameters, where to record errors
 * @param net_bytes : network-encoded stat structure
 * @param net_size : the number of net_bytes that may be read
 *
 * @return ret_net_size : number of bytes consumed; <=net_size
 * @return ret_host_size : number of bytes that lib9p_stat_unmarshal would take
 * @return whether there was an error
 */
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);

/**
 * Unmarshal the 9P `net_bytes` into the C struct `ret_obj`.
 *
 * lib9p_stat_unmarshal does no validation; you must run
 * lib9p_stat_validate() first.
 *
 * @param ctx : negotiated protocol parameters
 * @param net_bytes : network-encoded stat structure
 *
 * @return ret_obj : where to put the stat object itself
 * @return ret_extra : where to put strings for the stat object
 * @return consumed net_bytes
 */
uint32_t lib9p_stat_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes,
                              struct lib9p_stat *ret_obj, void *ret_extra);

/**
 * Marhsal a `struct lib9p_stat` structure into a byte-array.
 *
 * lib9p_Tmsg_marshal does no validation; it trusts that the
 * programmer won't give it garbage input.  However, just as it
 * doesn't marshal struct fields that aren't in ctx->version, it won't
 * marshal bitfield bits that aren't in ctx->version; it applies a
 * version-specific mask to bitfields.
 *
 * @param ctx : negotiated protocol parameters, where to record errors
 * @param max_net_size : the maximum network-encoded size to allow
 * @param obj : the message to encode
 *
 * @return ret_bytes: the buffer to encode into
 * @return the number of bytes written, or 0 if the stat object does not fit in max_net_size
 *
 * @errno LINUX_ERANGE: reply does not fit in max_net_size
 */
uint32_t lib9p_stat_marshal(struct lib9p_ctx *ctx, uint32_t max_net_size, struct lib9p_stat *obj,
                            uint8_t *ret_bytes);

#endif /* _LIB9P_9P_H_ */