Jari Vetoniemi před 10 roky
rodič
revize
fe909f1a9e
11 změnil soubory, kde provedl 2169 přidání a 0 odebrání
  1. +8
    -0
      .gitmodules
  2. +33
    -0
      CMakeLists.txt
  3. +2
    -0
      bin/CMakeLists.txt
  4. +993
    -0
      bin/server.c
  5. +3
    -0
      lib/CMakeLists.txt
  6. +1
    -0
      lib/chck
  7. +2
    -0
      src/CMakeLists.txt
  8. +905
    -0
      src/pi9.c
  9. +120
    -0
      src/pi9.h
  10. +58
    -0
      src/pi9_string.c
  11. +44
    -0
      src/pi9_string.h

+ 8
- 0
.gitmodules Zobrazit soubor

@ -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

+ 33
- 0
CMakeLists.txt Zobrazit soubor

@ -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)

+ 2
- 0
bin/CMakeLists.txt Zobrazit soubor

@ -0,0 +1,2 @@
add_executable(server server.c)
target_link_libraries(server pi9 chck_pool chck_lut)

+ 993
- 0
bin/server.c Zobrazit soubor

@ -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;
}

+ 3
- 0
lib/CMakeLists.txt Zobrazit soubor

@ -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)

+ 1
- 0
lib/chck

@ -0,0 +1 @@
Subproject commit 8ebf54a04ff2119fe36bf81b8b3ac52df45be38b

+ 2
- 0
src/CMakeLists.txt Zobrazit soubor

@ -0,0 +1,2 @@
add_library(pi9 pi9.c pi9_string.c)
target_link_libraries(pi9 chck_buffer)

+ 905
- 0
src/pi9.c Zobrazit soubor

@ -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);
}

+ 120
- 0
src/pi9.h Zobrazit soubor

@ -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__ */

+ 58
- 0
src/pi9_string.c Zobrazit soubor

@ -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);
}

+ 44
- 0
src/pi9_string.h Zobrazit soubor

@ -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__ */

Načítá se…
Zrušit
Uložit