25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

933 lines
27 KiB

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include "pi9.h"
#include <chck/buffer/buffer.h>
#define VERBOSE false
#define MAXWELEM 16
static const uint16_t NOTAG = (uint16_t)~0;
static const uint32_t HDRSZ = 7; // size of header
static const uint32_t QIDSZ = 13; // size of serialized pi9_qid
static const uint32_t STATHDRSZ = 47; // size of serialized pi9_stat, until the variable length data
struct pi9_stream {
struct chck_buffer out, in;
};
enum op {
OPFIRST,
Tversion = 0x64,
Rversion,
Tauth = 0x66,
Rauth,
Tattach = 0x68,
Rattach,
Terror = 0x6A, // illegal
Rerror,
Tflush = 0x6C,
Rflush,
Twalk = 0x6E,
Rwalk,
Topen = 0x70,
Ropen,
Tcreate = 0x72,
Rcreate,
Tread = 0x74,
Rread,
Twrite = 0x76,
Rwrite,
Tclunk = 0x78,
Rclunk,
Tremove = 0x7A,
Rremove,
Tstat = 0x7C,
Rstat,
Twstat = 0x7E,
Rwstat,
OPLAST,
};
#define DECOP(x) static bool op_##x(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
DECOP(Tversion);
DECOP(Tauth);
DECOP(Tattach);
DECOP(Tflush);
DECOP(Twalk);
DECOP(Topen);
DECOP(Tcreate);
DECOP(Tread);
DECOP(Twrite);
DECOP(Tclunk);
DECOP(Tremove);
DECOP(Tstat);
DECOP(Twstat);
#undef DECOP
static struct {
size_t msz;
bool (*cb)(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream);
} ops[] = {
[Tversion] = { 6, op_Tversion },
[Tauth] = { 8, op_Tauth },
[Terror] = { 0, NULL }, // invalid
[Tflush] = { 2, op_Tflush },
[Tattach] = { 12, op_Tattach },
[Twalk] = { 10, op_Twalk },
[Topen] = { 5, op_Topen },
[Tcreate] = { 11, op_Tcreate },
[Tread] = { 16, op_Tread },
[Twrite] = { 16, op_Twrite },
[Tclunk] = { 4, op_Tclunk },
[Tremove] = { 4, op_Tremove },
[Tstat] = { 4, op_Tstat },
[Twstat] = { 6, op_Twstat },
};
static const struct {
const char *msg;
size_t size;
} errors[PI9_ERR_LAST] = {
#define MSG(x) { x, sizeof(x) }
MSG("Could not read message."),
MSG("Could not write message."),
MSG("Authentication unnecessary."),
MSG("File is not a directory."),
MSG("No such file or directory."),
MSG("Fid already in user."),
MSG("Operation not allowed."),
MSG("Unknown op code."),
MSG("Out of memory."),
#undef MSG
};
static inline bool
write_qid(struct pi9_qid *qid, struct pi9_stream *stream)
{
assert(qid && stream);
return (chck_buffer_write_int(&qid->type, sizeof(qid->type), &stream->out) &&
chck_buffer_write_int(&qid->vers, sizeof(qid->vers), &stream->out) &&
chck_buffer_write_int(&qid->path, sizeof(qid->path), &stream->out));
}
static inline bool
read_qid(struct pi9_qid *qid, struct pi9_stream *stream)
{
assert(qid && stream);
return (chck_buffer_read_int(&qid->type, sizeof(qid->type), &stream->in) &&
chck_buffer_read_int(&qid->vers, sizeof(qid->vers), &stream->in) &&
chck_buffer_read_int(&qid->path, sizeof(qid->path), &stream->in));
}
static inline bool
read_stat(struct pi9_stat *stat, struct pi9_stream *stream)
{
assert(stat && stream);
uint16_t size;
if (!chck_buffer_read_int(&size, sizeof(size), &stream->in) ||
!chck_buffer_read_int(&stat->type, sizeof(stat->type), &stream->in) ||
!chck_buffer_read_int(&stat->dev, sizeof(stat->dev), &stream->in) ||
!read_qid(&stat->qid, stream) ||
!chck_buffer_read_int(&stat->mode, sizeof(stat->mode), &stream->in) ||
!chck_buffer_read_int(&stat->atime, sizeof(stat->atime), &stream->in) ||
!chck_buffer_read_int(&stat->mtime, sizeof(stat->mtime), &stream->in) ||
!chck_buffer_read_int(&stat->length, sizeof(stat->length), &stream->in))
return false;
struct pi9_string *fields[4] = { &stat->name, &stat->uid, &stat->gid, &stat->muid };
for (uint32_t i = 0; i < 4; ++i) {
uint16_t len;
if (!chck_buffer_read_int(&len, sizeof(len), &stream->in))
return false;
pi9_string_set_cstr_with_length(fields[i], (char*)stream->in.curpos, len, false);
}
return true;
}
static bool
op_Tversion(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
// tag should always be NOTAG in version messages
if (tag != NOTAG)
goto err_not_allowed;
uint32_t msize;
uint16_t vsize;
if (!chck_buffer_read_int(&msize, sizeof(msize), &stream->in) ||
!chck_buffer_read_int(&vsize, sizeof(vsize), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Tversion %u %u\n", tag, msize);
#endif
struct pi9_string version = {0};
pi9_string_set_cstr_with_length(&version, (char*)stream->in.curpos, vsize, false);
// A successful version request initializes the connection.
// All outstanding I/O on the connection is aborted; all active fids are freed (`clunked') automatically.
// The set of messages between version requests is called a session.
// FIXME: ↑ we need to tell higher level to abort all outstanding I/O
// honor the buffer size request
pi9->msize = (msize > 0 ? msize : pi9->msize);
// only support 9P2000, maybe 9P2000.L later, .u probably never
if (!pi9_string_eq_cstr(&version, "9P2000")) {
static const char *preferred = "9P2000";
const char *reply = (pi9_string_starts_with_cstr(&version, "9P") ? preferred : "unknown");
vsize = strlen(reply);
const uint32_t size = HDRSZ + sizeof(msize) + sizeof(vsize) + vsize;
if (!chck_buffer_write_int(&size, sizeof(size), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rversion}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out) ||
!chck_buffer_write_int(&msize, sizeof(msize), &stream->out) ||
!chck_buffer_write_string_of_type(reply, vsize, sizeof(uint16_t), &stream->out))
goto err_write;
} else {
const size_t size = HDRSZ + sizeof(msize) + sizeof(vsize) + vsize;
if (chck_buffer_write(stream->in.buffer, 1, size, &stream->out) != size)
goto err_write;
*(uint8_t*)(stream->in.buffer + sizeof(uint32_t)) = Rversion;
}
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
err_not_allowed:
pi9_write_error(tag, PI9_ERR_NOT_ALLOWED, stream);
return false;
}
static bool
op_Tauth(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint32_t afid;
if (!chck_buffer_read_int(&afid, sizeof(afid), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Tauth %u %u\n", tag, afid);
#endif
uint16_t usize;
if (!chck_buffer_read_int(&usize, sizeof(usize), &stream->in))
goto err_read;
struct pi9_string uname = {0};
pi9_string_set_cstr_with_length(&uname, (char*)stream->in.curpos, usize, false);
chck_buffer_seek(&stream->in, usize, SEEK_CUR);
uint16_t asize;
if (!chck_buffer_read_int(&asize, sizeof(asize), &stream->in))
goto err_read;
struct pi9_string aname = {0};
pi9_string_set_cstr_with_length(&aname, (char*)stream->in.curpos, asize, false);
struct pi9_qid *qid = NULL;
if (pi9->procs.auth && !pi9->procs.auth(pi9, tag, afid, &uname, &aname, &qid))
return false;
if (!qid)
goto err_no_auth;
const uint32_t size = HDRSZ + QIDSZ;
if (!chck_buffer_write_int(&size, sizeof(size), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rauth}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out) ||
(qid && !write_qid(qid, stream)))
goto err_write;
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
err_no_auth:
pi9_write_error(tag, PI9_ERR_NO_AUTH, stream);
return false;
}
static bool
op_Tattach(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint32_t fid, afid;
if (!chck_buffer_read_int(&fid, sizeof(fid), &stream->in) ||
!chck_buffer_read_int(&afid, sizeof(afid), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Tattach %u %u %u\n", tag, fid, afid);
#endif
uint16_t usize;
if (!chck_buffer_read_int(&usize, sizeof(usize), &stream->in))
goto err_read;
struct pi9_string uname = {0};
pi9_string_set_cstr_with_length(&uname, (char*)stream->in.curpos, usize, false);
chck_buffer_seek(&stream->in, usize, SEEK_CUR);
uint16_t asize;
if (!chck_buffer_read_int(&asize, sizeof(asize), &stream->in))
goto err_read;
struct pi9_string aname = {0};
pi9_string_set_cstr_with_length(&aname, (char*)stream->in.curpos, asize, false);
struct pi9_qid *qid = NULL;
if (pi9->procs.attach && !pi9->procs.attach(pi9, tag, fid, afid, &uname, &aname, &qid))
return false;
const uint32_t size = HDRSZ + QIDSZ;
if (!chck_buffer_write_int(&size, sizeof(size), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rattach}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out) ||
!write_qid(qid, stream))
goto err_write;
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
}
static bool
op_Tflush(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint16_t oldtag;
if (!chck_buffer_read_int(&oldtag, sizeof(oldtag), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Tflush %u %u\n", tag, oldtag);
#endif
if (pi9->procs.flush && !pi9->procs.flush(pi9, tag, oldtag))
return false;
// The server should answer the flush message immediately.
// If it recognizes oldtag as the tag of a pending transaction, it should abort any pending response and discard that tag.
// In either case, it should respond with an Rflush echoing the tag (not oldtag) of the Tflush message.
// A Tflush can never be responded to by an Rerror message.
if (!chck_buffer_write_int(&HDRSZ, sizeof(HDRSZ), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rflush}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out))
goto err_write;
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
}
static bool
op_Twalk(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint16_t nwname;
uint32_t fid, newfid;
if (!chck_buffer_read_int(&fid, sizeof(fid), &stream->in) ||
!chck_buffer_read_int(&newfid, sizeof(newfid), &stream->in) ||
!chck_buffer_read_int(&nwname, sizeof(nwname), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Twalk %u %u %u %u\n", tag, fid, newfid, nwname);
#endif
if (nwname > MAXWELEM)
goto err_not_allowed;
struct pi9_string walks[MAXWELEM] = {{0}};
for (uint32_t i = 0; i < nwname; ++i) {
uint16_t len;
if (!chck_buffer_read_int(&len, sizeof(len), &stream->in))
goto err_read;
pi9_string_set_cstr_with_length(&walks[i], (char*)stream->in.curpos, len, false);
}
uint16_t nwqid = 0;
struct pi9_qid *qids[MAXWELEM];
if (pi9->procs.walk && !pi9->procs.walk(pi9, tag, fid, newfid, nwname, walks, qids, &nwqid))
return false;
// must be always less or equal
assert(nwqid <= nwname);
const uint32_t size = HDRSZ + sizeof(nwqid) + nwqid * QIDSZ;
if (!chck_buffer_write_int(&size, sizeof(size), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rwalk}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out) ||
!chck_buffer_write_int(&nwqid, sizeof(nwqid), &stream->out))
goto err_write;
for (uint32_t i = 0; i < nwqid; ++i) {
if (!write_qid(qids[i], stream))
goto err_write;
}
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
err_not_allowed:
pi9_write_error(tag, PI9_ERR_NOT_ALLOWED, stream);
return false;
}
static bool
op_Topen(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint8_t mode;
uint32_t fid;
if (!chck_buffer_read_int(&fid, sizeof(fid), &stream->in) ||
!chck_buffer_read_int(&mode, sizeof(mode), &stream->in))
goto err_read;
// All other bits in mode should be zero
// ↑ Means that I should validate the mode for unknown bits to p9 protocol
#if VERBOSE
fprintf(stderr, "Topen %u %u %u\n", tag, fid, mode);
#endif
uint32_t iounit = 0;
struct pi9_qid *qid = NULL;
if (pi9->procs.open && !pi9->procs.open(pi9, tag, fid, mode, &qid, &iounit))
return false;
if (!qid)
goto err_not_allowed;
const uint32_t size = HDRSZ + QIDSZ + sizeof(iounit);
if (!chck_buffer_write_int(&size, sizeof(size), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Ropen}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out) ||
!write_qid(qid, stream) ||
!chck_buffer_write_int(&iounit, sizeof(iounit), &stream->out))
goto err_write;
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
err_not_allowed:
pi9_write_error(tag, PI9_ERR_NOT_ALLOWED, stream);
return false;
}
static bool
op_Tcreate(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint32_t fid;
uint16_t nsize;
if (!chck_buffer_read_int(&fid, sizeof(fid), &stream->in) ||
!chck_buffer_read_int(&nsize, sizeof(nsize), &stream->in))
goto err_read;
struct pi9_string name = {0};
pi9_string_set_cstr_with_length(&name, (char*)stream->in.curpos, nsize, false);
uint8_t mode;
uint32_t perm;
if (!chck_buffer_read_int(&perm, sizeof(perm), &stream->in) ||
!chck_buffer_read_int(&mode, sizeof(mode), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Tcreate %u %u\n", tag, fid);
#endif
// The names . and .. are special; it is illegal to create files with these names.
if (pi9_string_eq_cstr(&name, ".") || pi9_string_eq_cstr(&name, ".."))
goto err_not_allowed;
uint32_t iounit = 0;
struct pi9_qid *qid = NULL;
if (pi9->procs.create && !pi9->procs.create(pi9, tag, fid, &name, perm, mode, &qid, &iounit))
return false;
if (!qid)
goto err_not_allowed;
const uint32_t size = HDRSZ + QIDSZ + sizeof(iounit);
if (!chck_buffer_write_int(&size, sizeof(size), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rcreate}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out) ||
!write_qid(qid, stream) ||
!chck_buffer_write_int(&iounit, sizeof(iounit), &stream->out))
goto err_write;
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
err_not_allowed:
pi9_write_error(tag, PI9_ERR_NOT_ALLOWED, stream);
return false;
}
static bool
op_Tread(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint64_t offset;
uint32_t fid, count;
if (!chck_buffer_read_int(&fid, sizeof(fid), &stream->in) ||
!chck_buffer_read_int(&offset, sizeof(offset), &stream->in) ||
!chck_buffer_read_int(&count, sizeof(count), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Tread %u %u %"PRIu64" %u\n", tag, fid, offset, count);
#endif
if (pi9->procs.read && !pi9->procs.read(pi9, tag, fid, offset, count))
goto err_write;
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
}
static bool
op_Twrite(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint64_t offset;
uint32_t fid, count;
if (!chck_buffer_read_int(&fid, sizeof(fid), &stream->in) ||
!chck_buffer_read_int(&offset, sizeof(offset), &stream->in) ||
!chck_buffer_read_int(&count, sizeof(count), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Twrite %u %"PRIu64" %u\n", fid, offset, count);
#endif
if (pi9->procs.write && !pi9->procs.write(pi9, tag, fid, offset, count, (count > 0 ? stream->in.curpos : NULL)))
return false;
const uint32_t size = HDRSZ + sizeof(count);
if (!chck_buffer_write_int(&size, sizeof(size), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rwrite}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out) ||
!chck_buffer_write_int(&count, sizeof(count), &stream->out))
goto err_write;
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
}
static bool
op_Tclunk(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint32_t fid;
if (!chck_buffer_read_int(&fid, sizeof(fid), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Tclunk %u %u\n", tag, fid);
#endif
if (pi9->procs.clunk && !pi9->procs.clunk(pi9, tag, fid))
return false;
if (!chck_buffer_write_int(&HDRSZ, sizeof(HDRSZ), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rclunk}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out))
goto err_write;
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
}
static bool
op_Tremove(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint32_t fid;
if (!chck_buffer_read_int(&fid, sizeof(fid), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Tremove %u %u\n", tag, fid);
#endif
if (pi9->procs.remove && !pi9->procs.remove(pi9, tag, fid))
return false;
if (!chck_buffer_write_int(&HDRSZ, sizeof(HDRSZ), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rremove}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out))
goto err_write;
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
}
static bool
op_Tstat(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint32_t fid;
if (!chck_buffer_read_int(&fid, sizeof(fid), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Tstat %u %u\n", tag, fid);
#endif
chck_buffer_seek(&stream->out, HDRSZ + sizeof(uint16_t), SEEK_SET);
uint8_t *start = stream->out.curpos;
struct pi9_stat *stat = NULL;
if (pi9->procs.stat && !pi9->procs.stat(pi9, tag, fid, &stat))
return false;
if (stat && !pi9_write_stat(stat, stream))
goto err_write;
// too big
if (stream->out.curpos - start > 0xFFFF)
goto err_write;
const uint16_t sbufsz = (stream->out.curpos - start);
const uint32_t size = HDRSZ + sizeof(sbufsz) + sbufsz;
chck_buffer_seek(&stream->out, 0, SEEK_SET);
if (!chck_buffer_write_int(&size, sizeof(size), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rstat}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out) ||
!chck_buffer_write_int(&sbufsz, sizeof(sbufsz), &stream->out))
goto err_write;
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
}
static bool
op_Twstat(struct pi9 *pi9, uint16_t tag, struct pi9_stream *stream)
{
uint32_t fid;
uint16_t sbufsz;
if (!chck_buffer_read_int(&fid, sizeof(fid), &stream->in) ||
!chck_buffer_read_int(&sbufsz, sizeof(sbufsz), &stream->in))
goto err_read;
#if VERBOSE
fprintf(stderr, "Twstat %u %u %u\n", tag, fid, sbufsz);
#endif
struct pi9_stat stat = {0};
if (!read_stat(&stat, stream))
goto err_read;
if (pi9->procs.twstat && !pi9->procs.twstat(pi9, tag, fid, &stat))
return false;
if (!chck_buffer_write_int(&HDRSZ, sizeof(HDRSZ), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rwstat}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&tag, sizeof(tag), &stream->out))
goto err_write;
return true;
err_read:
pi9_write_error(tag, PI9_ERR_READ, stream);
return false;
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
}
static inline bool
call_op(struct pi9 *pi9, enum op op, uint16_t tag, struct pi9_stream *stream)
{
// check opcode range, and only allow T opcodes (% 2), also check that message meets minimum size
if (op <= OPFIRST || op >= OPLAST || op % 2 != 0 || stream->in.size < ops[op].msz)
goto err_unknown_op;
if (op == Terror) {
#if VERBOSE
fprintf(stderr, "Got error from the client.\n");
#endif
return true;
}
if (stream->out.size < ops[op].msz && !chck_buffer_resize(&stream->out, ops[op].msz))
goto err_write;
return ops[op].cb(pi9, tag, stream);
err_write:
pi9_write_error(tag, PI9_ERR_WRITE, stream);
return false;
err_unknown_op:
pi9_write_error(tag, PI9_ERR_UNKNOWN_OP, stream);
return false;
}
static bool
read_msg(struct pi9 *pi9, int fd, struct pi9_stream *stream)
{
assert(pi9 && fd >= 0);
if (chck_buffer_fill_from_fd(fd, 1, pi9->msize, &stream->in) < HDRSZ)
return false;
uint32_t size;
if (!chck_buffer_read_int(&size, sizeof(size), &stream->in) || size < HDRSZ)
return false;
if (stream->in.size < size) {
const size_t to_read = size - stream->in.size;
chck_buffer_seek(&stream->in, 0, SEEK_END);
if (chck_buffer_fill_from_fd(fd, 1, to_read, &stream->in) < to_read)
return false;
chck_buffer_seek(&stream->in, sizeof(size), SEEK_SET);
}
uint8_t op;
uint16_t tag;
if (!chck_buffer_read_int(&op, sizeof(uint8_t), &stream->in) ||
!chck_buffer_read_int(&tag, sizeof(uint16_t), &stream->in))
return false;
#if VERBOSE
fprintf(stderr, "Read message of size: %u (%u : %u)\n", size, op, tag);
for (uint32_t i = 0; i < size; ++i)
putc(*(char*)(stream->in.buffer + i), stderr);
putc('\n', stderr);
#endif
return call_op(pi9, op, tag, stream);
}
static inline bool
write_msg(int fd, struct pi9_stream *stream)
{
assert(fd >= 0 && stream->out.buffer);
if ((size_t)(stream->out.curpos - stream->out.buffer) < sizeof(uint32_t))
return true;
const uint32_t size = *(uint32_t*)stream->out.buffer;
#if VERBOSE
fprintf(stderr, "Write message of size: %u\n", size);
#endif
return write(fd, stream->out.buffer, size) == size;
}
void
pi9_reply_start(struct pi9_reply *reply, uint16_t tag, struct pi9_stream *stream)
{
assert(reply && stream);
memset(reply, 0, sizeof(struct pi9_reply));
chck_buffer_seek(&stream->out, HDRSZ + sizeof(uint32_t), SEEK_SET);
reply->start = stream->out.curpos;
reply->tag = tag;
}
bool
pi9_reply_send(struct pi9_reply *reply, int fd, struct pi9_stream *stream)
{
assert(reply && stream);
const uint32_t sbufsz = (stream->out.curpos - reply->start);
const uint32_t size = HDRSZ + sizeof(sbufsz) + sbufsz;
chck_buffer_seek(&stream->out, 0, SEEK_SET);
if (!chck_buffer_write_int(&size, sizeof(size), &stream->out) ||
!chck_buffer_write_int((uint8_t[]){Rread}, sizeof(uint8_t), &stream->out) ||
!chck_buffer_write_int(&reply->tag, sizeof(reply->tag), &stream->out) ||
!chck_buffer_write_int(&sbufsz, sizeof(sbufsz), &stream->out))
return false;
return (stream->in.buffer == stream->in.curpos ? write_msg(fd, stream) : true);
}
bool
pi9_write_stat(struct pi9_stat *stat, struct pi9_stream *stream)
{
const size_t size = STATHDRSZ + stat->name.size + stat->uid.size + stat->gid.size + stat->muid.size;
// too big
if (size > 0xFFFF)
return false;
const uint16_t size16 = size;
return (chck_buffer_write_int(&size16, sizeof(size16), &stream->out) &&
chck_buffer_write_int(&stat->type, sizeof(stat->type), &stream->out) &&
chck_buffer_write_int(&stat->dev, sizeof(stat->dev), &stream->out) &&
write_qid(&stat->qid, stream) &&
chck_buffer_write_int(&stat->mode, sizeof(stat->mode), &stream->out) &&
chck_buffer_write_int(&stat->atime, sizeof(stat->atime), &stream->out) &&
chck_buffer_write_int(&stat->mtime, sizeof(stat->mtime), &stream->out) &&
chck_buffer_write_int(&stat->length, sizeof(stat->length), &stream->out) &&
chck_buffer_write_string_of_type(stat->name.data, stat->name.size, sizeof(uint16_t), &stream->out) &&
chck_buffer_write_string_of_type(stat->uid.data, stat->uid.size, sizeof(uint16_t), &stream->out) &&
chck_buffer_write_string_of_type(stat->gid.data, stat->gid.size, sizeof(uint16_t), &stream->out) &&
chck_buffer_write_string_of_type(stat->muid.data, stat->muid.size, sizeof(uint16_t), &stream->out));
}
size_t
pi9_write(const void *src, size_t size, size_t nmemb, struct pi9_stream *stream)
{
return chck_buffer_write(src, size, nmemb, &stream->out);
}
void
pi9_stat_release(struct pi9_stat *stat)
{
if (!stat)
return;
struct pi9_string *fields[4] = { &stat->name, &stat->uid, &stat->gid, &stat->muid };
for (uint32_t i = 0; i < 4; ++i)
pi9_string_release(fields[i]);
}
bool
pi9_process(struct pi9 *pi9, int fd)
{
assert(pi9 && fd >= 0 && pi9->stream);
pi9->fd = fd;
bool ret = true;
if (!read_msg(pi9, fd, pi9->stream)) {
if (pi9->stream->out.curpos == pi9->stream->out.buffer)
pi9_write_error(NOTAG, PI9_ERR_READ, pi9->stream);
ret = false;
}
if (!write_msg(fd, pi9->stream)) {
pi9_write_error(NOTAG, PI9_ERR_WRITE, pi9->stream);
write_msg(fd, pi9->stream);
ret = false;
}
chck_buffer_seek(&pi9->stream->in, 0, SEEK_SET);
chck_buffer_seek(&pi9->stream->out, 0, SEEK_SET);
pi9->fd = -1;
return ret;
}
void
pi9_release(struct pi9 *pi9)
{
if (!pi9)
return;
chck_buffer_release(&pi9->stream->out);
chck_buffer_release(&pi9->stream->in);
free(pi9->stream);
memset(pi9, 0, sizeof(struct pi9));
}
bool
pi9_init(struct pi9 *pi9, uint32_t msize, struct pi9_procs *procs, void *userdata)
{
assert(pi9);
memset(pi9, 0, sizeof(struct pi9));
memcpy(&pi9->procs, procs, sizeof(struct pi9_procs));
pi9->msize = (msize > 0 ? msize : 8192);
pi9->userdata = userdata;
pi9->fd = -1;
if (!(pi9->stream = calloc(1, sizeof(struct pi9_stream))))
goto fail;
if (!chck_buffer(&pi9->stream->in, pi9->msize, CHCK_ENDIANESS_LITTLE))
goto fail;
if (!chck_buffer(&pi9->stream->out, pi9->msize, CHCK_ENDIANESS_LITTLE))
goto fail;
return true;
fail:
pi9_release(pi9);
return false;
}
#undef pi9_write_error
void
pi9_write_error(uint16_t tag, enum pi9_error error, struct pi9_stream *stream)
{
assert(stream);
const int32_t size = HDRSZ + sizeof(uint16_t) + errors[error].size;
chck_buffer_seek(&stream->out, 0, SEEK_SET);
chck_buffer_write_int(&size, sizeof(size), &stream->out);
chck_buffer_write_int((uint8_t[]){ Rerror }, sizeof(uint8_t), &stream->out);
chck_buffer_write_int(&tag, sizeof(tag), &stream->out);
chck_buffer_write_string_of_type(errors[error].msg, errors[error].size, sizeof(uint16_t), &stream->out);
fprintf(stderr, "%s\n", errors[error].msg);
}