@ -0,0 +1,8 @@ | |||
[submodule "chck"] | |||
path = chck | |||
url = git://github.com/Cloudef/chck.git | |||
branch = refactor | |||
[submodule "lib/chck"] | |||
path = lib/chck | |||
url = git://github.com/Cloudef/chck.git | |||
branch = refactor |
@ -0,0 +1,33 @@ | |||
CMAKE_MINIMUM_REQUIRED(VERSION 2.8) | |||
PROJECT(pi9) | |||
set(PI9_NAME "pi9") | |||
set(PI9_DESCRIPTION "9p server abstraction library") | |||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${pi9_SOURCE_DIR}/CMake) | |||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) | |||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) | |||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) | |||
if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) | |||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-variadic-macros -Wno-long-long") | |||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-variadic-macros -Wno-long-long") | |||
endif () | |||
if (UNIX AND CMAKE_COMPILER_IS_GNUCC) | |||
set(CMAKE_POSITION_INDEPENDENT_CODE ON) | |||
if (${CMAKE_VERSION} VERSION_LESS 2.8.9) | |||
add_definitions(-fPIC) | |||
endif () | |||
endif () | |||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -D_DEFAULT_SOURCE") | |||
include_directories( | |||
${CMAKE_CURRENT_SOURCE_DIR}/src | |||
${CMAKE_CURRENT_SOURCE_DIR}/lib/chck) | |||
add_subdirectory(lib) | |||
add_subdirectory(src) | |||
add_subdirectory(bin) | |||
file(COPY src/pi9.h src/pi9_string.h DESTINATION include/pi9) |
@ -0,0 +1,2 @@ | |||
add_executable(server server.c) | |||
target_link_libraries(server pi9 chck_pool chck_lut) |
@ -0,0 +1,993 @@ | |||
#include <stdlib.h> | |||
#include <sys/socket.h> | |||
#include <sys/un.h> | |||
#include <sys/stat.h> | |||
#include <unistd.h> | |||
#include <time.h> | |||
#include <poll.h> | |||
#include <signal.h> | |||
#include <assert.h> | |||
#include "pi9.h" | |||
#include "chck/pool/pool.h" | |||
#include "chck/lut/lut.h" | |||
#define M(m) (m & 3) | |||
#ifndef offsetof | |||
# if __GNUC__ | |||
# define offsetof(st, m) __builtin_offsetof(st, m) | |||
# else | |||
# define offsetof(st, m) ((size_t)(&((st *)0)->m)) | |||
# endif | |||
#endif | |||
static const size_t NONODE = (size_t)~0; | |||
struct fs { | |||
struct chck_hash_table fids; | |||
struct chck_pool nodes; | |||
size_t root; | |||
}; | |||
struct node { | |||
struct pi9_stat stat; | |||
struct chck_iter_pool childs; | |||
size_t parent; | |||
uint8_t omode; | |||
bool open; | |||
struct node_procs { | |||
bool (*read)(struct pi9 *pi9, struct node *node, uint64_t offset, uint32_t count); | |||
bool (*write)(struct pi9 *pi9, struct node *node, uint64_t offset, uint32_t count, const void *data); | |||
uint64_t (*size)(struct pi9 *pi9, struct node *node); | |||
} procs; | |||
}; | |||
enum { | |||
NONE = 0, | |||
ROOT, | |||
}; | |||
static bool | |||
node_init(struct node *node, struct node_procs *procs) | |||
{ | |||
memset(node, 0, sizeof(struct node)); | |||
memcpy(&node->procs, procs, sizeof(struct node_procs)); | |||
node->stat.qid.path = node->parent = NONODE; | |||
return chck_iter_pool(&node->childs, 4, 0, sizeof(size_t)); | |||
} | |||
static void | |||
node_release(struct node *node) | |||
{ | |||
if (!node) | |||
return; | |||
pi9_stat_release(&node->stat); | |||
chck_iter_pool_release(&node->childs); | |||
} | |||
static struct node* | |||
get_node(struct fs *fs, size_t node) | |||
{ | |||
return (node == NONODE ? NULL : chck_pool_get(&fs->nodes, node)); | |||
} | |||
static bool | |||
unlink_nodes(struct fs *fs, struct node *parent, struct node *child) | |||
{ | |||
assert(fs); | |||
if (!child) | |||
return false; | |||
if (parent) { | |||
size_t *n; | |||
chck_iter_pool_for_each(&parent->childs, n) { | |||
if (*n != child->stat.qid.path) | |||
continue; | |||
chck_iter_pool_remove(&parent->childs, _I - 1); | |||
break; | |||
} | |||
} | |||
child->parent = NONODE; | |||
return true; | |||
} | |||
static void | |||
unlink_childs(struct fs *fs, struct node *parent) | |||
{ | |||
if (!parent) | |||
return; | |||
size_t *c; | |||
chck_iter_pool_for_each(&parent->childs, c) | |||
unlink_nodes(fs, parent, get_node(fs, *c)); | |||
chck_iter_pool_release(&parent->childs); | |||
} | |||
static bool | |||
link_nodes(struct fs *fs, struct node *parent, struct node *child) | |||
{ | |||
assert(fs); | |||
if (!parent || !child) | |||
return false; | |||
if (child->parent != NONODE && !unlink_nodes(fs, get_node(fs, child->parent), child)) | |||
return false; | |||
if (child->stat.qid.path != NONODE && !chck_iter_pool_push_back(&parent->childs, &child->stat.qid.path)) | |||
return false; | |||
child->parent = parent->stat.qid.path; | |||
return true; | |||
} | |||
static size_t | |||
add_node(struct fs *fs, struct node *node) | |||
{ | |||
assert(fs && node); | |||
size_t n; | |||
struct node *p; | |||
if (!(p = chck_pool_add(&fs->nodes, node, &n))) | |||
return NONODE; | |||
return (p->stat.qid.path = n); | |||
} | |||
static void | |||
remove_node(struct fs *fs, struct node *node) | |||
{ | |||
if (!node) | |||
return; | |||
unlink_childs(fs, node); | |||
unlink_nodes(fs, get_node(fs, node->parent), node); | |||
node_release(node); | |||
if (node->stat.qid.path != NONODE) { | |||
chck_pool_remove(&fs->nodes, node->stat.qid.path); | |||
node = NULL; // ↑ node pointer is garbage after this | |||
} | |||
} | |||
static size_t | |||
add_node_linked(struct fs *fs, struct node *node, size_t parent) | |||
{ | |||
assert(fs && node); | |||
size_t n; | |||
if ((n = add_node(fs, node)) == NONODE) | |||
return NONODE; | |||
if (!link_nodes(fs, chck_pool_get(&fs->nodes, parent), get_node(fs, n))) { | |||
remove_node(fs, get_node(fs, n)); | |||
return NONODE; | |||
} | |||
return n; | |||
} | |||
static inline struct node* | |||
fid_to_node(struct fs *fs, uint32_t fid, size_t *n) | |||
{ | |||
if (n) *n = 0; | |||
if (fid == PI9_NOFID) | |||
return NULL; | |||
size_t *i; | |||
if (!(i = chck_hash_table_get(&fs->fids, fid))) | |||
return NULL; | |||
if (n) *n = *i; | |||
return get_node(fs, *i); | |||
} | |||
static bool | |||
set_fid(struct fs *fs, uint32_t fid, const size_t *node) | |||
{ | |||
return chck_hash_table_set(&fs->fids, fid, node); | |||
} | |||
static bool | |||
clunk_fid(struct fs *fs, uint32_t fid) | |||
{ | |||
struct node *f; | |||
if ((f = fid_to_node(fs, fid, NULL))) { | |||
if (f->omode & PI9_ORCLOSE) { | |||
remove_node(fs, f); | |||
f = NULL; // ↑ f is garbage after this | |||
} else { | |||
f->omode = 0; | |||
f->open = false; | |||
} | |||
} | |||
return set_fid(fs, fid, NULL); | |||
} | |||
static bool | |||
cb_read_qtdir(struct pi9 *pi9, struct node *node, uint64_t offset, uint32_t count) | |||
{ | |||
(void)offset, (void)count; | |||
// For directories, read returns an integral number of directory entries exactly as in stat (see stat(5)), | |||
// one for each member of the directory. The read request message must have offset equal to zero or the | |||
// value of offset in the previous read on the directory, plus the number of bytes returned in the previous read. | |||
// In other words, seeking other than to the beginning is illegal in a directory (see seek(2)). | |||
struct pi9_stat stats[2]; | |||
memcpy(&stats[0], &node->stat, sizeof(struct pi9_stat)); | |||
memcpy(&stats[1], &node->stat, sizeof(struct pi9_stat)); | |||
pi9_string_set_cstr_with_length(&stats[0].name, ".", 1, false); | |||
pi9_string_set_cstr_with_length(&stats[1].name, "..", 2, false); | |||
for (uint32_t i = 0; i < 2; ++i) { | |||
if (!pi9_write_stat(&stats[i], pi9->out)) | |||
return false; | |||
} | |||
for (size_t i = 0; i < node->childs.items.count; ++i) { | |||
size_t *n = chck_iter_pool_get(&node->childs, i); | |||
struct node *c = get_node(pi9->userdata, *n); | |||
assert(c); | |||
// update size from callback | |||
if (c->procs.size) | |||
c->stat.length = c->procs.size(pi9, c); | |||
if (!pi9_write_stat(&c->stat, pi9->out)) | |||
return false; | |||
} | |||
return true; | |||
} | |||
static bool | |||
cb_read_hello(struct pi9 *pi9, struct node *node, uint64_t offset, uint32_t count) | |||
{ | |||
(void)node, (void)offset, (void)count; | |||
// The read request asks for count bytes of data from the file identified by fid, | |||
// which must be opened for reading, starting offset bytes after the beginning of the file. | |||
// The bytes are returned with the read reply message. | |||
// XXX: Just example here, we ignore offset and count | |||
if (pi9_write("Hello World!", 1, sizeof("Hello World!"), pi9->out) != sizeof("Hello World!")) | |||
return false; | |||
return true; | |||
} | |||
static bool | |||
cb_write_hello(struct pi9 *pi9, struct node *node, uint64_t offset, uint32_t count, const void *data) | |||
{ | |||
(void)pi9, (void)offset; | |||
fprintf(stderr, "\n--- wrote to %s\n", node->stat.name.data); | |||
fprintf(stderr, "(%u) ", count); | |||
for (size_t i = 0; i < count; ++i) putc(*(char*)(data + i), stderr); | |||
fprintf(stderr, "---\n\n"); | |||
return true; | |||
} | |||
static uint64_t | |||
cb_size_hello(struct pi9 *pi9, struct node *node) | |||
{ | |||
(void)pi9, (void)node; | |||
return sizeof("Hello World!"); | |||
} | |||
static void | |||
fs_release(struct fs *fs) | |||
{ | |||
if (!fs) | |||
return; | |||
chck_hash_table_release(&fs->fids); | |||
chck_pool_release(&fs->nodes); | |||
} | |||
static bool | |||
fs_init(struct fs *fs, uint32_t initial_nodes) | |||
{ | |||
assert(fs); | |||
memset(fs, 0, sizeof(struct fs)); | |||
if (!chck_hash_table(&fs->fids, PI9_NOFID, 128, sizeof(size_t))) | |||
goto fail; | |||
chck_hash_table_uint_algorithm(&fs->fids, chck_incremental_uint_hash); | |||
if (!chck_pool(&fs->nodes, 32, initial_nodes, sizeof(struct node))) | |||
goto fail; | |||
struct node_procs dir_procs = { | |||
.read = cb_read_qtdir, | |||
.write = NULL, | |||
.size = NULL, | |||
}; | |||
struct node root; | |||
if (!node_init(&root, &dir_procs)) | |||
goto fail; | |||
root.stat = (struct pi9_stat) { | |||
.type = ROOT, | |||
.dev = 0, | |||
.qid = { PI9_QTDIR, 0, 0 }, | |||
.mode = PI9_DMDIR | 0700, | |||
.atime = time(NULL), | |||
.mtime = time(NULL), | |||
.length = 0, // dirs are always 0 | |||
.name = { NULL, 0, false }, | |||
.uid = { NULL, 0, false }, | |||
.gid = { NULL, 0, false }, | |||
.muid = { NULL, 0, false }, | |||
}; | |||
struct node_procs test_procs = { | |||
.read = cb_read_hello, | |||
.write = cb_write_hello, | |||
.size = cb_size_hello, | |||
}; | |||
struct node test; | |||
node_init(&test, &test_procs); | |||
test.stat = (struct pi9_stat) { | |||
.type = 0, | |||
.dev = 0, | |||
.qid = { PI9_QTFILE, 0, 1 }, | |||
.mode = 0600, | |||
.atime = time(NULL), | |||
.mtime = time(NULL), | |||
.length = 0, // we use callback | |||
.name = { "hello", sizeof("hello"), false }, | |||
.uid = { NULL, 0, false }, | |||
.gid = { NULL, 0, false }, | |||
.muid = { NULL, 0, false }, | |||
}; | |||
if ((fs->root = add_node(fs, &root)) == NONODE) { | |||
node_release(&root); | |||
node_release(&test); | |||
goto fail; | |||
} | |||
if (add_node_linked(fs, &test, fs->root) == NONODE) { | |||
node_release(&root); | |||
node_release(&test); | |||
goto fail; | |||
} | |||
return true; | |||
fail: | |||
fs_release(fs); | |||
return false; | |||
} | |||
static bool | |||
cb_attach(struct pi9 *pi9, uint16_t tag, uint32_t fid, uint32_t afid, const struct pi9_string *uname, const struct pi9_string *aname, struct pi9_qid **qid) | |||
{ | |||
(void)tag, (void)uname, (void)aname; | |||
// if afid == NOFID then no authentication is requested | |||
struct node *af = NULL; | |||
if (afid != PI9_NOFID && !(af = fid_to_node(pi9->userdata, afid, NULL))) | |||
goto err_not_allowed; // the node for authentication does not exist | |||
// asked for authentication but we don't support it | |||
if (af) | |||
goto err_no_auth; | |||
// fid that is in use cannot be used to access root node | |||
if (fid_to_node(pi9->userdata, fid, NULL)) | |||
goto err_not_allowed; | |||
struct fs *fs = pi9->userdata; | |||
if (!set_fid(fs, fid, &fs->root)) | |||
goto err_oom; | |||
struct node *f; | |||
if (!(f = get_node(fs, fs->root))) | |||
goto err_nofid; | |||
*qid = &f->stat.qid; | |||
return true; | |||
err_nofid: | |||
pi9_write_error(tag, ERR_NO_FID, pi9->out); | |||
return false; | |||
err_oom: | |||
pi9_write_error(tag, ERR_OUT_OF_MEMORY, pi9->out); | |||
return false; | |||
err_not_allowed: | |||
pi9_write_error(tag, ERR_NOT_ALLOWED, pi9->out); | |||
return false; | |||
err_no_auth: | |||
pi9_write_error(tag, ERR_NO_AUTH, pi9->out); | |||
return false; | |||
} | |||
static bool | |||
cb_flush(struct pi9 *pi9, uint16_t tag, uint16_t oldtag) | |||
{ | |||
(void)pi9, (void)tag, (void)oldtag; | |||
// When the response to a request is no longer needed, such as when a user interrupts a process doing a read(2), a | |||
// Tflush request is sent to the server to purge the pending response. The message being flushed is identified by oldtag. | |||
// The semantics of flush depends on messages arriving in order. | |||
// The server may respond to the pending request before responding to the Tflush. | |||
// It is possible for a client to send multiple Tflush messages for a particular pending request. | |||
// Each subsequent Tflush must contain as oldtag the tag of the pending request (not a previous Tflush). | |||
// Should multiple Tflushes be received for a pending request, they must be answered in order. | |||
// A Rflush for any of the multiple Tflushes implies an answer for all previous ones. Therefore, | |||
// should a server receive a request and then multiple flushes for that request, it need respond only to the last flush. | |||
return true; | |||
} | |||
static bool | |||
cb_walk(struct pi9 *pi9, uint16_t tag, uint32_t fid, uint32_t newfid, uint16_t nwname, const struct pi9_string *walks, struct pi9_qid **qids, uint16_t *out_nwqid) | |||
{ | |||
(void)tag; | |||
size_t index; | |||
struct node *f; | |||
if (!(f = fid_to_node(pi9->userdata, fid, &index))) | |||
goto err_nofid; | |||
// The walk request carries as arguments an existing fid and a proposed newfid | |||
// (which must not be in use unless it is the same as fid) | |||
if (fid != newfid && fid_to_node(pi9->userdata, newfid, NULL)) | |||
goto err_fid_in_use; | |||
struct node *wnode = f; | |||
for (uint32_t i = 0; i < nwname; ++i) { | |||
// The fid must represent a directory unless zero path name elements are specified. | |||
if (wnode->stat.qid.type != PI9_QTDIR) { | |||
// Otherwise, the walk will return an Rwalk message containing nwqid qids corresponding, in order, | |||
// to the files that are visited by the nwqid successful elementwise walks; | |||
break; | |||
} | |||
// The name ``..'' (dot-dot) represents the parent directory. | |||
// Single (dot) is not used in 9p protocol | |||
if (pi9_string_eq_cstr(&walks[i], "..")) { | |||
struct node *n; | |||
if (wnode->stat.type == ROOT) { | |||
// A walk of the name ``..'' in the root directory of a server is equivalent to a walk with no name elements. | |||
n = wnode; | |||
} else if (!(n = get_node(pi9->userdata, wnode->parent))) { | |||
break; | |||
} | |||
qids[*out_nwqid] = &n->stat.qid; | |||
index = (wnode->parent != NONODE ? wnode->parent : index); | |||
wnode = n; | |||
(*out_nwqid)++; | |||
} else { | |||
size_t *c; | |||
chck_iter_pool_for_each(&wnode->childs, c) { | |||
struct node *n; | |||
if (!(n = get_node(pi9->userdata, *c)) || pi9_string_eq(&walks[i], &n->stat.name)) | |||
continue; | |||
qids[*out_nwqid] = &n->stat.qid; | |||
index = *c; | |||
wnode = n; | |||
(*out_nwqid)++; | |||
break; | |||
} | |||
} | |||
} | |||
// If the first element cannot be walked for any reason, Rerror is returned. | |||
if (nwname > 0 && *out_nwqid == 0) | |||
goto err_not_directory; | |||
// Index is only affected if equal | |||
if (*out_nwqid != nwname) | |||
index = NONODE; | |||
// Always same when affected, otherwise unaffected (index == NONODE) | |||
assert(*out_nwqid == nwname || index == NONODE); | |||
// It is legal for nwname to be zero, in which case newfid will represent the same file as fid | |||
if (index != NONODE && !set_fid(pi9->userdata, newfid, &index)) | |||
goto err_oom; | |||
return true; | |||
err_nofid: | |||
pi9_write_error(tag, ERR_NO_FID, pi9->out); | |||
return false; | |||
err_fid_in_use: | |||
pi9_write_error(tag, ERR_FID_IN_USE, pi9->out); | |||
return false; | |||
err_not_directory: | |||
pi9_write_error(tag, ERR_NOT_DIRECTORY, pi9->out); | |||
return false; | |||
err_oom: | |||
pi9_write_error(tag, ERR_OUT_OF_MEMORY, pi9->out); | |||
return false; | |||
} | |||
static bool | |||
cb_open(struct pi9 *pi9, uint16_t tag, uint32_t fid, uint8_t mode, struct pi9_qid **out_qid, uint32_t *out_iounit) | |||
{ | |||
(void)tag, (void)out_iounit; | |||
// if mode has the OTRUNC (0x10) bit set, the file is to be truncated, which requires write permission | |||
// (if the file is append-only, and permission is granted, the open succeeds but the file will not be truncated); | |||
// if the mode has the ORCLOSE (0x40) bit set, the file is to be removed when the fid is clunked, | |||
// which requires permission to remove the file from its directory. | |||
// It is illegal to write a directory, truncate it, or attempt to remove it on close. | |||
// If the file is marked for exclusive use (see stat(5)), only one client can have the file open at any time. | |||
// That is, after such a file has been opened, further opens will fail until fid has been clunked. | |||
struct node *f; | |||
if (!(f = fid_to_node(pi9->userdata, fid, NULL))) | |||
goto err_nofid; | |||
// It is an error for either of these messages if the fid is already the product of a successful open or create message. | |||
if (f->open) | |||
goto err_not_allowed; | |||
// The iounit field returned by open and create may be zero. If it is not, | |||
// it is the maximum number of bytes that are guaranteed to be read from or written to the file | |||
// without breaking the I/O transfer into multiple 9P messages. | |||
// (out_iounit is by default 0) | |||
f->omode = mode; | |||
f->open = true; | |||
*out_qid = &f->stat.qid; | |||
return true; | |||
err_nofid: | |||
pi9_write_error(tag, ERR_NO_FID, pi9->out); | |||
return false; | |||
err_not_allowed: | |||
pi9_write_error(tag, ERR_NOT_ALLOWED, pi9->out); | |||
return false; | |||
} | |||
static bool | |||
cb_create(struct pi9 *pi9, uint16_t tag, uint32_t fid, const struct pi9_string *name, uint32_t perm, uint8_t mode, struct pi9_qid **out_qid, uint32_t *out_iounit) | |||
{ | |||
(void)tag, (void)perm, (void)mode, (void)out_qid, (void)out_iounit; | |||
// The create request asks the file server to create a new file with the name supplied, | |||
// in the directory (dir) represented by fid, and requires write permission in the directory. | |||
// The owner of the file is the implied user id of the request, the group of the file is the same as dir, | |||
// and the permissions are the value of ```perm & (~0666 | (dir.perm & 0666))``` if a regular file is being created | |||
// and ```perm & (~0777 | (dir.perm & 0777))``` if a directory is being created. This means, for example, | |||
// that if the create allows read permission to others, but the containing directory does not, | |||
// then the created file will not allow others to read the file. | |||
// Finally, the newly created file is opened according to mode, and fid will represent the newly opened file. | |||
// Mode is not checked against the permissions in perm. The qid for the new file is returned with the create reply message. | |||
// Directories are created by setting the DMDIR bit (0x80000000) in the perm. | |||
struct node *f; | |||
if (!(f = fid_to_node(pi9->userdata, fid, NULL))) | |||
goto err_nofid; | |||
// It is an error for either of these messages if the fid is already the product of a successful open or create message. | |||
if (f->open || f->stat.qid.type != PI9_QTDIR) | |||
goto err_not_allowed; | |||
// An attempt to create a file in a directory where the given name already exists will be rejected | |||
size_t *c; | |||
chck_iter_pool_for_each(&f->childs, c) { | |||
struct node *n = get_node(pi9->userdata, *c); | |||
assert(n); | |||
if (pi9_string_eq(&n->stat.name, name)) | |||
goto err_not_allowed; | |||
} | |||
// The iounit field returned by open and create may be zero. If it is not, | |||
// it is the maximum number of bytes that are guaranteed to be read from or written to the file | |||
// without breaking the I/O transfer into multiple 9P messages. | |||
// (out_iounit is by default 0) | |||
// FIXME: creation code here | |||
pi9_write_error(tag, ERR_NO_FID, pi9->out); | |||
return false; | |||
err_nofid: | |||
pi9_write_error(tag, ERR_NO_FID, pi9->out); | |||
return false; | |||
err_not_allowed: | |||
pi9_write_error(tag, ERR_NOT_ALLOWED, pi9->out); | |||
return false; | |||
} | |||
static bool | |||
cb_read(struct pi9 *pi9, uint16_t tag, uint32_t fid, uint64_t offset, uint32_t count) | |||
{ | |||
(void)tag; | |||
struct node *f; | |||
if (!(f = fid_to_node(pi9->userdata, fid, NULL))) | |||
goto err_nofid; | |||
if (M(f->omode) != PI9_OREAD) | |||
goto err_not_allowed; | |||
if (f->procs.read && !f->procs.read(pi9, f, offset, count)) | |||
goto err_write; | |||
return true; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, pi9->out); | |||
return false; | |||
err_nofid: | |||
pi9_write_error(tag, ERR_NO_FID, pi9->out); | |||
return false; | |||
err_not_allowed: | |||
pi9_write_error(tag, ERR_NOT_ALLOWED, pi9->out); | |||
return false; | |||
} | |||
static bool | |||
cb_write(struct pi9 *pi9, uint16_t tag, uint32_t fid, uint64_t offset, uint32_t count, const void *data) | |||
{ | |||
(void)tag; | |||
struct node *f; | |||
if (!(f = fid_to_node(pi9->userdata, fid, NULL))) | |||
goto err_nofid; | |||
// Directories may not be written. | |||
if (f->stat.qid.type == PI9_QTDIR || M(f->omode) != PI9_OWRITE) | |||
goto err_not_allowed; | |||
// The write request asks that count bytes of data be recorded in the file identified by fid, | |||
// which must be opened for writing, starting offset bytes after the beginning of the file. | |||
// If the file is append-only, the data will be placed at the end of the file regardless of offset. | |||
if (f->procs.write && !f->procs.write(pi9, f, offset, count, data)) | |||
goto err_write; | |||
return true; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, pi9->out); | |||
return false; | |||
err_nofid: | |||
pi9_write_error(tag, ERR_NO_FID, pi9->out); | |||
return false; | |||
err_not_allowed: | |||
pi9_write_error(tag, ERR_NOT_ALLOWED, pi9->out); | |||
return false; | |||
} | |||
static bool | |||
cb_clunk(struct pi9 *pi9, uint16_t tag, uint32_t fid) | |||
{ | |||
(void)tag; | |||
if (!clunk_fid(pi9->userdata, fid)) | |||
goto err_oom; | |||
// The actual file is not removed on the server unless the fid had been opened with ORCLOSE. | |||
// Once a fid has been clunked, the same fid can be reused in a new walk or attach request. | |||
// Even if the clunk returns an error, the fid is no longer valid. | |||
// A clunk message is generated by close and indirectly by other actions such as failed open calls. | |||
return true; | |||
err_oom: | |||
pi9_write_error(tag, ERR_OUT_OF_MEMORY, pi9->out); | |||
return false; | |||
} | |||
static bool | |||
cb_remove(struct pi9 *pi9, uint16_t tag, uint32_t fid) | |||
{ | |||
(void)tag; | |||
struct node *f; | |||
if (!(f = fid_to_node(pi9->userdata, fid, NULL))) | |||
goto err_nofid; | |||
// The remove request asks the file server both to remove the file represented by fid and to clunk the fid, even if the remove fails. | |||
// This request will fail if the client does not have write permission in the parent directory. | |||
// It is correct to consider remove to be a clunk with the side effect of removing the file if permissions allow. | |||
// If a file has been opened as multiple fids, possibly on different connections, and one fid is used to remove the file, | |||
// whether the other fids continue to provide access to the file is implementation-defined. | |||
if (!clunk_fid(pi9->userdata, fid)) | |||
goto err_oom; | |||
if (f->stat.type == ROOT) | |||
goto err_not_allowed; | |||
remove_node(pi9->userdata, f); | |||
return true; | |||
err_nofid: | |||
pi9_write_error(tag, ERR_NO_FID, pi9->out); | |||
return false; | |||
err_oom: | |||
pi9_write_error(tag, ERR_OUT_OF_MEMORY, pi9->out); | |||
return false; | |||
err_not_allowed: | |||
pi9_write_error(tag, ERR_NOT_ALLOWED, pi9->out); | |||
return false; | |||
} | |||
static bool | |||
cb_stat(struct pi9 *pi9, uint16_t tag, uint32_t fid, struct pi9_stat **out_stat) | |||
{ | |||
(void)tag; | |||
// The stat request requires no special permissions. | |||
struct node *f; | |||
if (!(f = fid_to_node(pi9->userdata, fid, NULL))) | |||
goto err_nofid; | |||
// update size from callback | |||
if (f->procs.size) | |||
f->stat.length = f->procs.size(pi9, f); | |||
*out_stat = &f->stat; | |||
return true; | |||
err_nofid: | |||
pi9_write_error(tag, ERR_NO_FID, pi9->out); | |||
return false; | |||
} | |||
static bool | |||
cb_twstat(struct pi9 *pi9, uint16_t tag, uint32_t fid, const struct pi9_stat *stat) | |||
{ | |||
(void)tag; | |||
struct node *f; | |||
if (!(f = fid_to_node(pi9->userdata, fid, NULL))) | |||
goto err_nofid; | |||
// It is an error to attempt to set the length of a directory to a non-zero value, and servers may decide to reject | |||
// length changes for other reasons. | |||
if (f->stat.qid.type == PI9_QTDIR && stat->length != f->stat.length) | |||
goto err_not_allowed; | |||
// wstat request can avoid modifying some properties of the file by providing explicit ``don't touch'' values in the stat data | |||
// that is sent: zero-length strings for text values and the maximum unsigned value of appropriate size for integral values. | |||
// As a special case, if all the elements of the directory entry in a Twstat message are ``don't touch'' values, the server | |||
// may interpret it as a request to guarantee that the contents of the associated file are committed to stable storage | |||
// before the Rwstat message is returned. | |||
// (Consider the message to mean, ``make the state of the file exactly what it claims to be.'') | |||
struct pi9_stat wstat; | |||
memcpy(&wstat, stat, sizeof(wstat)); | |||
if (wstat.type == (uint16_t)~0) wstat.type = f->stat.type; | |||
if (wstat.dev == (uint32_t)~0) wstat.dev = f->stat.dev; | |||
if (wstat.qid.type == (uint8_t)~0) wstat.qid.type = f->stat.qid.type; | |||
if (wstat.qid.vers == (uint32_t)~0) wstat.qid.vers = f->stat.qid.vers; | |||
if (wstat.qid.path == (uint64_t)~0) wstat.qid.path = f->stat.qid.path; | |||
if (wstat.mode == (uint32_t)~0) wstat.mode = f->stat.mode; | |||
if (wstat.atime == (uint32_t)~0) wstat.atime = f->stat.atime; | |||
if (wstat.mtime == (uint32_t)~0) wstat.mtime = f->stat.mtime; | |||
if (wstat.length == (uint64_t)~0) wstat.length = f->stat.length; | |||
struct pi9_string *fields[4] = { &wstat.name, &wstat.uid, &wstat.gid, &wstat.muid }; | |||
struct pi9_string *fields2[4] = { &f->stat.name, &f->stat.uid, &f->stat.gid, &wstat.muid }; | |||
for (uint32_t i = 0; i < 4; ++i) { | |||
if (fields[i]->size > 0) | |||
continue; | |||
pi9_string_set(fields[i], fields2[i], false); | |||
} | |||
// The length can be changed (affecting the actual length of the file) by anyone with write permission on the file. | |||
// of the file's current group. The directory bit cannot be changed by a wstat; | |||
// The mode and mtime can be changed by the owner of the file or the group leader the other defined permission and mode bits can. | |||
// The gid can be changed: by the owner if also a member of the new group; | |||
// or by the group leader of the file's current group if also leader of the new group | |||
// (see intro(5) for more information about permissions and users(6) for users and groups). | |||
// None of the other data can be altered by a wstat and attempts to change them will trigger an error. | |||
// In particular, it is illegal to attempt to change the owner of a file. | |||
// (These conditions may be relaxed when establishing the initial state of a file server; see fsconfig(8).) | |||
if (wstat.type != f->stat.type || | |||
wstat.dev != f->stat.dev || | |||
wstat.atime != f->stat.atime || | |||
memcmp(&wstat.qid, &f->stat.qid, sizeof(wstat.qid)) || | |||
!pi9_string_eq(&wstat.uid, &f->stat.uid) || | |||
!pi9_string_eq(&wstat.muid, &f->stat.muid)) | |||
goto err_not_allowed; | |||
if (wstat.name.size > 0 && !pi9_string_eq(&wstat.name, &f->stat.name)) { | |||
// The name can be changed by anyone with write permission in the parent directory; | |||
// it is an error to change the name to that of an existing file. | |||
struct node *p; | |||
if ((p = get_node(pi9->userdata, f->parent))) { | |||
size_t *c; | |||
chck_iter_pool_for_each(&p->childs, c) { | |||
struct node *n = get_node(pi9->userdata, *c); | |||
assert(n); | |||
if (pi9_string_eq(&n->stat.name, &wstat.name)) | |||
goto err_not_allowed; | |||
} | |||
} | |||
} | |||
// ↑ | |||
// Either all the changes in wstat request happen, or none of them does: | |||
// if the request succeeds, all changes were made; if it fails, none were. | |||
memcpy(&f->stat, &wstat, offsetof(struct pi9_stat, name)); | |||
if (f->parent != NONODE && wstat.name.size > 0) | |||
pi9_string_set(&f->stat.name, &wstat.name, true); // this can fail in OOM, but oh well | |||
return true; | |||
err_nofid: | |||
pi9_write_error(tag, ERR_NO_FID, pi9->out); | |||
return false; | |||
err_not_allowed: | |||
pi9_write_error(tag, ERR_NOT_ALLOWED, pi9->out); | |||
return false; | |||
} | |||
static bool running = true; | |||
static void | |||
sigint(int32_t signal) | |||
{ | |||
(void)signal; | |||
running = false; | |||
} | |||
static int32_t | |||
sock_unix(char *address, struct sockaddr_un *sa, socklen_t *salen) | |||
{ | |||
assert(address && sa && salen); | |||
memset(sa, 0, sizeof(*sa)); | |||
sa->sun_family = AF_UNIX; | |||
strncpy(sa->sun_path, address, sizeof(sa->sun_path)); | |||
*salen = SUN_LEN(sa); | |||
int32_t fd; | |||
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) | |||
return -1; | |||
return fd; | |||
} | |||
static int32_t | |||
announce_unix(char *file) | |||
{ | |||
assert(file); | |||
int32_t fd; | |||
socklen_t salen; | |||
struct sockaddr_un sa; | |||
if ((fd = sock_unix(file, &sa, &salen)) < 0) | |||
return -1; | |||
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int)) < 0) | |||
goto fail; | |||
unlink(file); | |||
if (bind(fd, (struct sockaddr*)&sa, salen) < 0) | |||
goto fail; | |||
chmod(file, S_IRWXU); | |||
if (listen(fd, 0) < 0) | |||
goto fail; | |||
return fd; | |||
fail: | |||
close(fd); | |||
return -1; | |||
} | |||
int | |||
main(int argc, char *argv[]) | |||
{ | |||
(void)argc, (void)argv; | |||
struct fs fs; | |||
if (!fs_init(&fs, 1)) | |||
return EXIT_FAILURE; | |||
struct pi9_procs procs = { | |||
.auth = NULL, | |||
.attach = cb_attach, | |||
.flush = cb_flush, | |||
.walk = cb_walk, | |||
.open = cb_open, | |||
.create = cb_create, | |||
.read = cb_read, | |||
.write = cb_write, | |||
.clunk = cb_clunk, | |||
.remove = cb_remove, | |||
.stat = cb_stat, | |||
.twstat = cb_twstat | |||
}; | |||
struct pi9 pi9; | |||
if (!pi9_init(&pi9, 0, &procs, &fs)) | |||
return EXIT_FAILURE; | |||
struct pollfd fds[2] = {{0}}; | |||
fds[0].events = POLLIN; | |||
fds[1].events = POLLIN; | |||
fds[1].fd = -1; | |||
if ((fds[0].fd = announce_unix("9p")) < 0) | |||
return EXIT_FAILURE; | |||
signal(SIGINT, sigint); | |||
int32_t clients = 0; | |||
while (running) { | |||
int32_t ret; | |||
if ((ret = poll(fds, 1 + clients, 500)) <= 0) | |||
continue; | |||
for (int32_t i = 0; i < 1 + clients; ++i) { | |||
if (fds[i].revents & POLLIN) { | |||
if (i == 0) { | |||
int32_t fd; | |||
if ((fd = accept(fds[i].fd, NULL, NULL)) >= 0) { | |||
if (clients > 0) { | |||
fprintf(stderr, "Rejected client\n"); | |||
close(fd); | |||
} else { | |||
fds[1 + clients].fd = fd; | |||
++clients; | |||
fprintf(stderr, "Accepted a new client\n"); | |||
} | |||
} | |||
} else { | |||
char buffer[32]; | |||
if (recv(fds[i].fd, buffer, sizeof(buffer), MSG_PEEK | MSG_DONTWAIT) == 0) { | |||
close(fds[i].fd); | |||
fds[i].fd = -1; | |||
if (clients > 0) | |||
--clients; | |||
fprintf(stderr, "Client disconnected\n"); | |||
} else { | |||
pi9_process(&pi9, fds[i].fd); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
for (int32_t i = 0; i < 1 + clients; ++i) | |||
close(fds[i].fd); | |||
pi9_release(&pi9); | |||
fs_release(&fs); | |||
unlink("9p"); | |||
return EXIT_SUCCESS; | |||
} |
@ -0,0 +1,3 @@ | |||
add_library(chck_buffer STATIC chck/chck/buffer/buffer.c) | |||
add_library(chck_pool STATIC chck/chck/pool/pool.c) | |||
add_library(chck_lut STATIC chck/chck/lut/lut.c) |
@ -0,0 +1,2 @@ | |||
add_library(pi9 pi9.c pi9_string.c) | |||
target_link_libraries(pi9 chck_buffer) |
@ -0,0 +1,905 @@ | |||
#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 true | |||
#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 | |||
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 chck_buffer *in, struct chck_buffer *out) | |||
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 chck_buffer *in, struct chck_buffer *out); | |||
} 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[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 chck_buffer *out) | |||
{ | |||
return (chck_buffer_write_int(&qid->type, sizeof(qid->type), out) && | |||
chck_buffer_write_int(&qid->vers, sizeof(qid->vers), out) && | |||
chck_buffer_write_int(&qid->path, sizeof(qid->path), out)); | |||
} | |||
static inline bool | |||
read_qid(struct pi9_qid *qid, struct chck_buffer *in) | |||
{ | |||
return (chck_buffer_read_int(&qid->type, sizeof(qid->type), in) && | |||
chck_buffer_read_int(&qid->vers, sizeof(qid->vers), in) && | |||
chck_buffer_read_int(&qid->path, sizeof(qid->path), in)); | |||
} | |||
static inline bool | |||
read_stat(struct pi9_stat *stat, struct chck_buffer *in) | |||
{ | |||
uint16_t size; | |||
if (!chck_buffer_read_int(&size, sizeof(size), in) || | |||
!chck_buffer_read_int(&stat->type, sizeof(stat->type), in) || | |||
!chck_buffer_read_int(&stat->dev, sizeof(stat->dev), in) || | |||
!read_qid(&stat->qid, in) || | |||
!chck_buffer_read_int(&stat->mode, sizeof(stat->mode), in) || | |||
!chck_buffer_read_int(&stat->atime, sizeof(stat->atime), in) || | |||
!chck_buffer_read_int(&stat->mtime, sizeof(stat->mtime), in) || | |||
!chck_buffer_read_int(&stat->length, sizeof(stat->length), 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), in)) | |||
return false; | |||
pi9_string_set_cstr_with_length(fields[i], in->curpos, len, false); | |||
} | |||
return true; | |||
} | |||
static bool | |||
op_Tversion(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
// 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), in) || | |||
!chck_buffer_read_int(&vsize, sizeof(vsize), 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, 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 = (vsize > 0 && pi9_cstrneq(version.data, "9P", (vsize >= 2 ? 2 : vsize)) ? preferred : "unknown"); | |||
vsize = strlen(reply); | |||
const uint32_t size = HDRSZ + sizeof(msize) + sizeof(vsize) + vsize; | |||
if (!chck_buffer_write_int(&size, sizeof(size), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rversion}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out) || | |||
!chck_buffer_write_int(&msize, sizeof(msize), out) || | |||
!chck_buffer_write_string_of_type(reply, vsize, sizeof(uint16_t), out)) | |||
goto err_write; | |||
} else { | |||
const size_t size = HDRSZ + sizeof(msize) + sizeof(vsize) + vsize; | |||
if (chck_buffer_write(in->buffer, 1, size, out) != size) | |||
goto err_write; | |||
*(uint8_t*)(out->buffer + sizeof(uint32_t)) = Rversion; | |||
} | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
err_not_allowed: | |||
pi9_write_error(tag, ERR_NOT_ALLOWED, out); | |||
return false; | |||
} | |||
static bool | |||
op_Tauth(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint32_t afid; | |||
if (!chck_buffer_read_int(&afid, sizeof(afid), 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), in)) | |||
goto err_read; | |||
struct pi9_string uname = {0}; | |||
pi9_string_set_cstr_with_length(&uname, in->curpos, usize, false); | |||
chck_buffer_seek(in, usize, SEEK_CUR); | |||
uint16_t asize; | |||
if (!chck_buffer_read_int(&asize, sizeof(asize), in)) | |||
goto err_read; | |||
struct pi9_string aname = {0}; | |||
pi9_string_set_cstr_with_length(&aname, 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), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rauth}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out) || | |||
!write_qid(qid, out)) | |||
goto err_write; | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
err_no_auth: | |||
pi9_write_error(tag, ERR_NO_AUTH, out); | |||
return false; | |||
} | |||
static bool | |||
op_Tattach(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint32_t fid, afid; | |||
if (!chck_buffer_read_int(&fid, sizeof(fid), in) || | |||
!chck_buffer_read_int(&afid, sizeof(afid), 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), in)) | |||
goto err_read; | |||
struct pi9_string uname = {0}; | |||
pi9_string_set_cstr_with_length(&uname, in->curpos, usize, false); | |||
chck_buffer_seek(in, usize, SEEK_CUR); | |||
uint16_t asize; | |||
if (!chck_buffer_read_int(&asize, sizeof(asize), in)) | |||
goto err_read; | |||
struct pi9_string aname = {0}; | |||
pi9_string_set_cstr_with_length(&aname, 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), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rattach}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out) || | |||
!write_qid(qid, out)) | |||
goto err_write; | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
} | |||
static bool | |||
op_Tflush(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint16_t oldtag; | |||
if (!chck_buffer_read_int(&oldtag, sizeof(oldtag), 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), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rflush}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out)) | |||
goto err_write; | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
} | |||
static bool | |||
op_Twalk(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint16_t nwname; | |||
uint32_t fid, newfid; | |||
if (!chck_buffer_read_int(&fid, sizeof(fid), in) || | |||
!chck_buffer_read_int(&newfid, sizeof(newfid), in) || | |||
!chck_buffer_read_int(&nwname, sizeof(nwname), 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 < MAXWELEM; ++i) { | |||
uint16_t len; | |||
if (!chck_buffer_read_int(&len, sizeof(len), in)) | |||
goto err_read; | |||
pi9_string_set_cstr_with_length(&walks[i], 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), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rwalk}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out) || | |||
!chck_buffer_write_int(&nwqid, sizeof(nwqid), out)) | |||
goto err_write; | |||
for (uint32_t i = 0; i < nwqid; ++i) { | |||
if (!write_qid(qids[i], out)) | |||
goto err_write; | |||
} | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
err_not_allowed: | |||
pi9_write_error(tag, ERR_NOT_ALLOWED, out); | |||
return false; | |||
} | |||
static bool | |||
op_Topen(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint8_t mode; | |||
uint32_t fid; | |||
if (!chck_buffer_read_int(&fid, sizeof(fid), in) || | |||
!chck_buffer_read_int(&mode, sizeof(mode), 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), out) || | |||
!chck_buffer_write_int((uint8_t[]){Ropen}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out) || | |||
!write_qid(qid, out) || | |||
!chck_buffer_write_int(&iounit, sizeof(iounit), out)) | |||
goto err_write; | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
err_not_allowed: | |||
pi9_write_error(tag, ERR_NOT_ALLOWED, out); | |||
return false; | |||
} | |||
static bool | |||
op_Tcreate(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint32_t fid; | |||
uint16_t nsize; | |||
if (!chck_buffer_read_int(&fid, sizeof(fid), in) || | |||
!chck_buffer_read_int(&nsize, sizeof(nsize), in)) | |||
goto err_read; | |||
struct pi9_string name = {0}; | |||
pi9_string_set_cstr_with_length(&name, in->curpos, nsize, false); | |||
uint8_t mode; | |||
uint32_t perm; | |||
if (!chck_buffer_read_int(&perm, sizeof(perm), in) || | |||
!chck_buffer_read_int(&mode, sizeof(mode), 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), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rcreate}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out) || | |||
!write_qid(qid, out) || | |||
!chck_buffer_write_int(&iounit, sizeof(iounit), out)) | |||
goto err_write; | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
err_not_allowed: | |||
pi9_write_error(tag, ERR_NOT_ALLOWED, out); | |||
return false; | |||
} | |||
static bool | |||
op_Tread(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint64_t offset; | |||
uint32_t fid, count; | |||
if (!chck_buffer_read_int(&fid, sizeof(fid), in) || | |||
!chck_buffer_read_int(&offset, sizeof(offset), in) || | |||
!chck_buffer_read_int(&count, sizeof(count), in)) | |||
goto err_read; | |||
#if VERBOSE | |||
fprintf(stderr, "Tread %u %u %"PRIu64" %u\n", tag, fid, offset, count); | |||
#endif | |||
chck_buffer_seek(out, HDRSZ + sizeof(uint32_t), SEEK_SET); | |||
void *start = out->curpos; | |||
if (pi9->procs.read && !pi9->procs.read(pi9, tag, fid, offset, count)) | |||
return false; | |||
const uint32_t sbufsz = (offset != 0 ? 0 : (out->curpos - start)); | |||
const uint32_t size = HDRSZ + sizeof(sbufsz) + sbufsz; | |||
chck_buffer_seek(out, 0, SEEK_SET); | |||
if (!chck_buffer_write_int(&size, sizeof(size), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rread}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out) || | |||
!chck_buffer_write_int(&sbufsz, sizeof(sbufsz), out)) | |||
goto err_write; | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
} | |||
static bool | |||
op_Twrite(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint64_t offset; | |||
uint32_t fid, count; | |||
if (!chck_buffer_read_int(&fid, sizeof(fid), in) || | |||
!chck_buffer_read_int(&offset, sizeof(offset), in) || | |||
!chck_buffer_read_int(&count, sizeof(count), 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 ? in->curpos : NULL))) | |||
return false; | |||
const uint32_t size = HDRSZ + sizeof(count); | |||
if (!chck_buffer_write_int(&size, sizeof(size), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rwrite}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out) || | |||
!chck_buffer_write_int(&count, sizeof(count), out)) | |||
goto err_write; | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
} | |||
static bool | |||
op_Tclunk(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint32_t fid; | |||
if (!chck_buffer_read_int(&fid, sizeof(fid), 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), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rclunk}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out)) | |||
goto err_write; | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
} | |||
static bool | |||
op_Tremove(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint32_t fid; | |||
if (!chck_buffer_read_int(&fid, sizeof(fid), 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), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rremove}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out)) | |||
goto err_write; | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
} | |||
static bool | |||
op_Tstat(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint32_t fid; | |||
if (!chck_buffer_read_int(&fid, sizeof(fid), in)) | |||
goto err_read; | |||
#if VERBOSE | |||
fprintf(stderr, "Tstat %u %u\n", tag, fid); | |||
#endif | |||
chck_buffer_seek(out, HDRSZ + sizeof(uint16_t), SEEK_SET); | |||
void *start = 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, out)) | |||
goto err_write; | |||
// too big | |||
if (out->curpos - start > 0xFFFF) | |||
goto err_write; | |||
const uint16_t sbufsz = (out->curpos - start); | |||
const uint32_t size = HDRSZ + sizeof(sbufsz) + sbufsz; | |||
chck_buffer_seek(out, 0, SEEK_SET); | |||
if (!chck_buffer_write_int(&size, sizeof(size), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rstat}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out) || | |||
!chck_buffer_write_int(&sbufsz, sizeof(sbufsz), out)) | |||
goto err_write; | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
} | |||
static bool | |||
op_Twstat(struct pi9 *pi9, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
uint32_t fid; | |||
uint16_t sbufsz; | |||
if (!chck_buffer_read_int(&fid, sizeof(fid), in) || | |||
!chck_buffer_read_int(&sbufsz, sizeof(sbufsz), 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, in)) | |||
goto err_read; | |||
if (pi9->procs.twstat && !pi9->procs.twstat(pi9, tag, fid, &stat)) | |||
return false; | |||
if (!chck_buffer_write_int(&HDRSZ, sizeof(HDRSZ), out) || | |||
!chck_buffer_write_int((uint8_t[]){Rwstat}, sizeof(uint8_t), out) || | |||
!chck_buffer_write_int(&tag, sizeof(tag), out)) | |||
goto err_write; | |||
return true; | |||
err_read: | |||
pi9_write_error(tag, ERR_READ, out); | |||
return false; | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
} | |||
static inline bool | |||
call_op(struct pi9 *pi9, enum op op, uint16_t tag, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
// check opcode range, and only allow T opcodes (% 2), also check that message meets minimum size | |||
if (op <= OPFIRST || op >= OPLAST || op % 2 != 0 || 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 (out->size < ops[op].msz && !chck_buffer_resize(out, ops[op].msz)) | |||
goto err_write; | |||
return ops[op].cb(pi9, tag, in, out); | |||
err_write: | |||
pi9_write_error(tag, ERR_WRITE, out); | |||
return false; | |||
err_unknown_op: | |||
pi9_write_error(tag, ERR_UNKNOWN_OP, out); | |||
return false; | |||
} | |||
static bool | |||
read_msg(struct pi9 *pi9, int32_t fd, struct chck_buffer *in, struct chck_buffer *out) | |||
{ | |||
assert(pi9 && fd >= 0); | |||
if (chck_buffer_fill_from_fd(fd, 1, pi9->msize, in) < HDRSZ) | |||
return false; | |||
uint32_t size; | |||
if (!chck_buffer_read_int(&size, sizeof(size), in) || size < HDRSZ) | |||
return false; | |||
if (in->size < size) { | |||
const size_t to_read = size - in->size; | |||
chck_buffer_seek(in, 0, SEEK_END); | |||
if (chck_buffer_fill_from_fd(fd, 1, to_read, in) < to_read) | |||
return false; | |||
chck_buffer_seek(in, sizeof(size), SEEK_SET); | |||
} | |||
uint8_t op; | |||
uint16_t tag; | |||
if (!chck_buffer_read_int(&op, sizeof(uint8_t), in) || | |||
!chck_buffer_read_int(&tag, sizeof(uint16_t), in)) | |||
return false; | |||
#if VERBOSE | |||
fprintf(stderr, "Read message of size: %u (%u : %u)\n", size, op, tag); | |||
#endif | |||
for (uint32_t i = 0; i < size; ++i) | |||
putc(*(char*)(in->buffer + i), stderr); | |||
putc('\n', stderr); | |||
return call_op(pi9, op, tag, in, out); | |||
} | |||
static inline bool | |||
write_msg(int32_t fd, struct chck_buffer *out) | |||
{ | |||
assert(fd >= 0 && out->buffer); | |||
const uint32_t size = *(uint32_t*)out->buffer; | |||
#if VERBOSE | |||
fprintf(stderr, "Write message of size: %u\n", size); | |||
#endif | |||
return write(fd, out->buffer, size) == size; | |||
} | |||
bool | |||
pi9_write_stat(struct pi9_stat *stat, struct chck_buffer *out) | |||
{ | |||
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), out) && | |||
chck_buffer_write_int(&stat->type, sizeof(stat->type), out) && | |||
chck_buffer_write_int(&stat->dev, sizeof(stat->dev), out) && | |||
write_qid(&stat->qid, out) && | |||
chck_buffer_write_int(&stat->mode, sizeof(stat->mode), out) && | |||
chck_buffer_write_int(&stat->atime, sizeof(stat->atime), out) && | |||
chck_buffer_write_int(&stat->mtime, sizeof(stat->mtime), out) && | |||
chck_buffer_write_int(&stat->length, sizeof(stat->length), out) && | |||
chck_buffer_write_string_of_type(stat->name.data, stat->name.size, sizeof(uint16_t), out) && | |||
chck_buffer_write_string_of_type(stat->uid.data, stat->uid.size, sizeof(uint16_t), out) && | |||
chck_buffer_write_string_of_type(stat->gid.data, stat->gid.size, sizeof(uint16_t), out) && | |||
chck_buffer_write_string_of_type(stat->muid.data, stat->muid.size, sizeof(uint16_t), out)); | |||
} | |||
size_t | |||
pi9_write(const void *src, size_t size, size_t nmemb, struct chck_buffer *out) | |||
{ | |||
return chck_buffer_write(src, size, nmemb, 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, int32_t fd) | |||
{ | |||
assert(pi9 && fd >= 0 && pi9->in && pi9->out); | |||
chck_buffer_seek(pi9->in, 0, SEEK_SET); | |||
chck_buffer_seek(pi9->out, 0, SEEK_SET); | |||
bool ret = true; | |||
if (!read_msg(pi9, fd, pi9->in, pi9->out)) { | |||
if (pi9->out->curpos == pi9->out->buffer) | |||
pi9_write_error(NOTAG, ERR_READ, pi9->out); | |||
ret = false; | |||
} | |||
if (!write_msg(fd, pi9->out)) { | |||
pi9_write_error(NOTAG, ERR_WRITE, pi9->out); | |||
write_msg(fd, pi9->out); | |||
ret = false; | |||
} | |||
return ret; | |||
} | |||
void | |||
pi9_release(struct pi9 *pi9) | |||
{ | |||
if (!pi9) | |||
return; | |||
chck_buffer_release(pi9->out); | |||
chck_buffer_release(pi9->in); | |||
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; | |||
if (!(pi9->in = malloc(sizeof(struct chck_buffer)))) | |||
goto fail; | |||
if (!(pi9->out = malloc(sizeof(struct chck_buffer)))) | |||
goto fail; | |||
if (!chck_buffer(pi9->in, pi9->msize, CHCK_ENDIANESS_LITTLE)) | |||
goto fail; | |||
if (!chck_buffer(pi9->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 chck_buffer *out) | |||
{ | |||
assert(out); | |||
const int32_t size = HDRSZ + sizeof(uint16_t) + errors[error].size; | |||
chck_buffer_seek(out, 0, SEEK_SET); | |||
chck_buffer_write_int(&size, sizeof(size), out); | |||
chck_buffer_write_int((uint8_t[]){ Rerror }, sizeof(uint8_t), out); | |||
chck_buffer_write_int(&tag, sizeof(tag), out); | |||
chck_buffer_write_string_of_type(errors[error].msg, errors[error].size, sizeof(uint16_t), out); | |||
fprintf(stderr, "%s\n", errors[error].msg); | |||
} |
@ -0,0 +1,120 @@ | |||
#ifndef __pi9_h__ | |||
#define __pi9_h__ | |||
#include <stdio.h> | |||
#include "pi9_string.h" | |||
static const uint32_t PI9_NOFID = (uint32_t)~0; | |||
struct chck_buffer; | |||
struct pi9_qid { | |||
uint8_t type; | |||
uint32_t vers; | |||
uint64_t path; | |||
}; | |||
struct pi9_stat { | |||
uint16_t type; | |||
uint32_t dev; | |||
struct pi9_qid qid; | |||
uint32_t mode; | |||
uint32_t atime; | |||
uint32_t mtime; | |||
uint64_t length; | |||
struct pi9_string name; | |||
struct pi9_string uid; | |||
struct pi9_string gid; | |||
struct pi9_string muid; | |||
}; | |||
struct pi9 { | |||
void *userdata; | |||
struct chck_buffer *in, *out; | |||
uint32_t msize; | |||
struct pi9_procs { | |||
bool (*auth)(struct pi9 *pi9, uint16_t tag, uint32_t afid, const struct pi9_string *uname, const struct pi9_string *aname, struct pi9_qid **qid); | |||
bool (*attach)(struct pi9 *pi9, uint16_t tag, uint32_t fid, uint32_t afid, const struct pi9_string *uname, const struct pi9_string *aname, struct pi9_qid **qid); | |||
bool (*flush)(struct pi9 *pi9, uint16_t tag, uint16_t oldtag); | |||
bool (*walk)(struct pi9 *pi9, uint16_t tag, uint32_t fid, uint32_t newfid, uint16_t nwname, const struct pi9_string *walks, struct pi9_qid **qids, uint16_t *out_nwqid); | |||
bool (*open)(struct pi9 *pi9, uint16_t tag, uint32_t fid, uint8_t mode, struct pi9_qid **out_qid, uint32_t *out_iounit); | |||
bool (*create)(struct pi9 *pi9, uint16_t tag, uint32_t fid, const struct pi9_string *name, uint32_t perm, uint8_t mode, struct pi9_qid **out_qid, uint32_t *out_iounit); | |||
bool (*read)(struct pi9 *pi9, uint16_t tag, uint32_t fid, uint64_t offset, uint32_t count); | |||
bool (*write)(struct pi9 *pi9, uint16_t tag, uint32_t fid, uint64_t offset, uint32_t count, const void *data); | |||
bool (*clunk)(struct pi9 *pi9, uint16_t tag, uint32_t fid); | |||
bool (*remove)(struct pi9 *pi9, uint16_t tag, uint32_t fid); | |||
bool (*stat)(struct pi9 *pi9, uint16_t tag, uint32_t fid, struct pi9_stat **out_stat); | |||
bool (*twstat)(struct pi9 *pi9, uint16_t tag, uint32_t fid, const struct pi9_stat *stat); | |||
} procs; | |||
}; | |||
// from libc.h | |||
enum { | |||
PI9_OREAD = 0x0000, // open for read | |||
PI9_OWRITE = 0x0001, // write | |||
PI9_ORDWR = 0x0002, // read and write | |||
PI9_OEXEC = 0x0003, // execute, == read but check execute permission | |||
PI9_OTRUNC = 0x0010, // or'ed in (except for exec), truncate file first | |||
PI9_OCEXEC = 0x0020, // or'ed in, close on exec | |||
PI9_ORCLOSE = 0x0040, // or'ed in, remove on close | |||
PI9_ODIRECT = 0x0080, // or'ed in, direct access | |||
PI9_ONONBLOCK = 0x0100, // or'ed in, non-blocking call | |||
PI9_OEXCL = 0x1000, // or'ed in, exclusive use (create only) | |||
PI9_OLOCK = 0x2000, // or'ed in, lock after opening | |||
PI9_OAPPEND = 0x4000 // or'ed in, append only | |||
}; | |||
// bits in pi9_qid.type | |||
enum { | |||
PI9_QTDIR = 0x80, // type bit for directories | |||
PI9_QTAPPEND = 0x40, // type bit for append only files | |||
PI9_QTEXCL = 0x20, // type bit for exclusive use files | |||
PI9_QTMOUNT = 0x10, // type bit for mounted channel | |||
PI9_QTAUTH = 0x08, // type bit for authentication file | |||
PI9_QTTMP = 0x04, // type bit for non-backed-up file | |||
PI9_QTSYMLINK = 0x02, // type bit for symbolic link | |||
PI9_QTFILE = 0x00 // type bits for plain file | |||
}; | |||
// bits in pi9_stat.mode | |||
enum { | |||
PI9_DMEXEC = 0x1, // mode bit for execute permission | |||
PI9_DMWRITE = 0x2, // mode bit for write permission | |||
PI9_DMREAD = 0x4, // mode bit for read permission | |||
PI9_DMDIR = 0x80000000, // mode bit for directories | |||
PI9_DMAPPEND = 0x40000000, // mode bit for append only files | |||
PI9_DMEXCL = 0x20000000, // mode bit for exclusive use files | |||
PI9_DMMOUNT = 0x10000000, // mode bit for mounted channel | |||
PI9_DMAUTH = 0x08000000, // mode bit for authentication file | |||
PI9_DMTMP = 0x04000000, // mode bit for non-backed-up file | |||
}; | |||
enum pi9_error { | |||
ERR_READ, | |||
ERR_WRITE, | |||
ERR_NO_AUTH, | |||
ERR_NOT_DIRECTORY, | |||
ERR_NO_FID, | |||
ERR_FID_IN_USE, | |||
ERR_NOT_ALLOWED, | |||
ERR_UNKNOWN_OP, | |||
ERR_OUT_OF_MEMORY, | |||
ERR_LAST, | |||
}; | |||
bool pi9_write_stat(struct pi9_stat *stat, struct chck_buffer *out); | |||
void pi9_write_error(uint16_t tag, enum pi9_error error, struct chck_buffer *out); | |||
size_t pi9_write(const void *src, size_t size, size_t nmemb, struct chck_buffer *out); | |||
void pi9_stat_release(struct pi9_stat *stat); | |||
bool pi9_process(struct pi9 *pi9, int32_t fd); | |||
bool pi9_init(struct pi9 *pi9, uint32_t msize, struct pi9_procs *procs, void *userdata); | |||
void pi9_release(struct pi9 *pi9); | |||
#define pi9_write_error(t, e, o) \ | |||
{ fprintf(stderr, "%s (%s) @ %u: ", ((strrchr(__FILE__, '/') ?: __FILE__ - 1) + 1), __FUNCTION__, __LINE__); \ | |||
pi9_write_error(t, e, o); } | |||
#endif /* __pi9_h__ */ |
@ -0,0 +1,58 @@ | |||
#include <stdlib.h> | |||
#include <stddef.h> | |||
#include <assert.h> | |||
#include "pi9_string.h" | |||
static inline char* | |||
ccopy(const char *str, size_t len) | |||
{ | |||
char *cpy = calloc(1, len); | |||
return (cpy ? memcpy(cpy, str, len) : NULL); | |||
} | |||
void | |||
pi9_string_release(struct pi9_string *string) | |||
{ | |||
assert(string); | |||
if (string->is_heap && string->data) | |||
free(string->data); | |||
string->data = NULL; | |||
string->size = 0; | |||
} | |||
bool | |||
pi9_string_set_cstr_with_length(struct pi9_string *string, const char *data, uint16_t length, bool is_heap) | |||
{ | |||
assert(string); | |||
char *copy = (char*)data; | |||
if (is_heap && data && length > 0 && !(copy = ccopy(data, length))) | |||
return false; | |||
pi9_string_release(string); | |||
string->is_heap = is_heap; | |||
string->data = (length > 0 ? copy : NULL); | |||
string->size = length; | |||
return true; | |||
} | |||
bool | |||
pi9_string_set_cstr(struct pi9_string *string, const char *data, bool is_heap) | |||
{ | |||
assert(string); | |||
return pi9_string_set_cstr_with_length(string, data, (data ? strlen(data) : 0), is_heap); | |||
} | |||
bool | |||
pi9_string_set(struct pi9_string *string, const struct pi9_string *other, bool is_heap) | |||
{ | |||
if (string->data == other->data) { | |||
string->size = other->size; | |||
return true; | |||
} | |||
return pi9_string_set_cstr_with_length(string, other->data, other->size, is_heap); | |||
} |
@ -0,0 +1,44 @@ | |||
#ifndef __pi9_string_h__ | |||
#define __pi9_string_h__ | |||
#include <stdbool.h> | |||
#include <stdint.h> | |||
#include <string.h> | |||
struct pi9_string { | |||
char *data; | |||
uint16_t size; | |||
bool is_heap; | |||
}; | |||
static inline bool | |||
pi9_string_eq(const struct pi9_string *a, const struct pi9_string *b) | |||
{ | |||
return (a->data == b->data) || (a->size == b->size && !memcmp(a->data, b->data, a->size)); | |||
} | |||
static inline bool | |||
pi9_string_eq_cstr(const struct pi9_string *a, const char *cstr) | |||
{ | |||
return (cstr == a->data) || (a->data && cstr && !strcmp(a->data, cstr)); | |||
} | |||
static inline bool | |||
pi9_cstreq(const char *a, const char *b) | |||
{ | |||
return (a == b) || (a && b && !strcmp(a, b)); | |||
} | |||
static inline bool | |||
pi9_cstrneq(const char *a, const char *b, size_t len) | |||
{ | |||
return (a == b) || (a && b && !strncmp(a, b, len)); | |||
} | |||
void pi9_string_release(struct pi9_string *string); | |||
bool pi9_string_set_cstr(struct pi9_string *string, const char *data, bool is_heap); | |||
bool pi9_string_set_cstr_with_length(struct pi9_string *string, const char *data, uint16_t length, bool is_heap); | |||
bool pi9_string_set(struct pi9_string *string, const struct pi9_string *other, bool is_heap); | |||
#endif /* __pi9_string_h__ */ |