add plan9 filesystem support
authorDaniel Mendler <mail@daniel-mendler.de>
Thu, 5 Feb 2015 14:42:28 +0000 (15:42 +0100)
committerDaniel Mendler <mail@daniel-mendler.de>
Tue, 10 Feb 2015 01:13:43 +0000 (02:13 +0100)
src/libs/Network/uip/Network.cpp
src/libs/Network/uip/plan9/plan9.cpp [new file with mode: 0644]
src/libs/Network/uip/plan9/plan9.h [new file with mode: 0644]

index 1fe36e1..ad6e40e 100644 (file)
@@ -23,7 +23,7 @@
 #include "webserver.h"
 #include "dhcpc.h"
 #include "sftpd.h"
-
+#include "plan9.h"
 
 #include <mri.h>
 
@@ -32,6 +32,7 @@
 #define network_enable_checksum CHECKSUM("enable")
 #define network_webserver_checksum CHECKSUM("webserver")
 #define network_telnet_checksum CHECKSUM("telnet")
+#define network_plan9_checksum CHECKSUM("plan9")
 #define network_mac_override_checksum CHECKSUM("mac_override")
 #define network_ip_address_checksum CHECKSUM("ip_address")
 #define network_hostname_checksum CHECKSUM("hostname")
@@ -43,7 +44,7 @@ extern "C" void uip_log(char *m)
     printf("uIP log message: %s\n", m);
 }
 
-static bool webserver_enabled, telnet_enabled, use_dhcp;
+static bool webserver_enabled, telnet_enabled, plan9_enabled, use_dhcp;
 static Network *theNetwork;
 static Sftpd *sftpd;
 static CommandQueue *command_q= CommandQueue::getInstance();
@@ -129,6 +130,7 @@ void Network::on_module_loaded()
 
     webserver_enabled = THEKERNEL->config->value( network_checksum, network_webserver_checksum, network_enable_checksum )->by_default(false)->as_bool();
     telnet_enabled = THEKERNEL->config->value( network_checksum, network_telnet_checksum, network_enable_checksum )->by_default(false)->as_bool();
+    plan9_enabled = THEKERNEL->config->value( network_checksum, network_plan9_checksum, network_enable_checksum )->by_default(false)->as_bool();
 
     string mac = THEKERNEL->config->value( network_checksum, network_mac_override_checksum )->by_default("")->as_string();
     if (mac.size() == 17 ) { // parse mac address
@@ -304,6 +306,12 @@ static void setup_servers()
         printf("Telnetd initialized\n");
     }
 
+    if (plan9_enabled) {
+        // Initialize the plan9 server
+        Plan9::init();
+        printf("Plan9 initialized\n");
+    }
+
     // sftpd service, which is lazily created on reciept of first packet
     uip_listen(HTONS(115));
 }
@@ -395,6 +403,10 @@ extern "C" void app_select_appcall(void)
             if (telnet_enabled) Telnetd::appcall();
             break;
 
+        case HTONS(564):
+            if (plan9_enabled) Plan9::appcall();
+            break;
+
         case HTONS(115):
             if(sftpd == NULL) {
                 sftpd= new Sftpd();
diff --git a/src/libs/Network/uip/plan9/plan9.cpp b/src/libs/Network/uip/plan9/plan9.cpp
new file mode 100644 (file)
index 0000000..9d61ba5
--- /dev/null
@@ -0,0 +1,672 @@
+/*
+ * 9P network filesystem protocol implementation
+ *
+ * by Daniel Mendler <mail@daniel-mendler.de>
+ */
+
+#include "plan9.h"
+#include "DirHandle.h"
+#include "FATFileSystem.h"
+#include "StreamOutputPool.h"
+#include "Kernel.h"
+#include "utils.h"
+#include <string.h>
+#include "uip.h"
+
+//#define DEBUG_PRINTF(...) THEKERNEL->streams->printf("9p " __VA_ARGS__)
+#define DEBUG_PRINTF(...)
+
+#define FAIL(text) \
+    DEBUG_PRINTF("error %s %d\n", text, __LINE__);      \
+    return send_error(text)
+
+#define CHECK(cond, text) if (!(cond)) { FAIL(text); }
+#define IOUNIT            (uip_mss() - sizeof (Message::Twrite))
+#define PACKEDSTRUCT      struct __attribute__ ((packed))
+
+namespace {
+
+// See error mapping http://lxr.free-electrons.com/source/net/9p/error.c
+const char ENOENT_TEXT[]      = "No such file or directory",
+           EIO_TEXT[]         = "Input/output error",
+           FID_UNKNOWN_TEXT[] = "fid unknown or out of range",
+           FID_IN_USE_TEXT[]  = "fid already in use",
+           EBADMSG_TEXT[]     = "Bad message",
+           EEXIST_TEXT[]      = "File exists",
+           EFAULT_TEXT[]      = "Bad address",
+           ENOSYS_TEXT[]      = "Function not implemented",
+           ENOTEMPTY_TEXT[]   = "Directory not empty";
+
+enum {
+    // 9P message types
+    Tversion = 100,
+    Tauth    = 102,
+    Tattach  = 104,
+    Terror   = 106,
+    Rerror,
+    Tflush   = 108,
+    Twalk    = 110,
+    Topen    = 112,
+    Tcreate  = 114,
+    Tread    = 116,
+    Twrite   = 118,
+    Tclunk   = 120,
+    Tremove  = 122,
+    Tstat    = 124,
+    Twstat   = 126,
+
+    // Qid type
+    QTDIR     = 0x80, // directories
+    QTAPPEND  = 0x40, // append only files
+    QTEXCL    = 0x20, // exclusive use files
+    QTMOUNT   = 0x10, // mounted channel
+    QTAUTH    = 0x08, // authentication file
+    QTTMP     = 0x04, // non-backed-up file
+    QTLINK    = 0x02, // symbolic link
+    QTFILE    = 0x00, // plain file
+
+    // mode
+    OREAD     = 0,     // open for read
+    OWRITE    = 1,     // write
+    ORDWR     = 2,     // read and write
+    OEXEC     = 3,      // execute, == read but check execute permission
+    OTRUNC    = 0x10,  // or'ed in (except for exec), truncate file first
+    ORCLOSE   = 0x40,  // or'ed in, remove on close
+
+    // permission bits
+    DMDIR    = 0x80000000, // directories
+    DMAPPEND = 0x40000000, // append only files
+    DMEXCL   = 0x20000000, // exclusive use files
+    DMMOUNT  = 0x10000000, // mounted channel
+    DMAUTH   = 0x08000000, // authentication file
+    DMTMP    = 0x04000000, // non-backed-up file
+
+    MAXWELEM = 16,
+};
+
+PACKEDSTRUCT Header {
+    uint32_t size;
+    uint8_t  type;
+    uint16_t tag;
+};
+
+PACKEDSTRUCT Qid {
+    uint8_t  type;
+    uint32_t vers;
+    uint64_t path;
+
+    Qid() {}
+    Qid(Plan9::Entry* entry)
+        : type(entry->type), vers(entry->vers), path(uint32_t(entry)) {}
+};
+
+PACKEDSTRUCT Stat {
+    uint16_t size;
+    uint16_t type;
+    uint32_t dev;
+    Qid      qid;
+    uint32_t mode;
+    uint32_t atime;
+    uint32_t mtime;
+    uint64_t length;
+};
+
+// Important: 9P assumes little endian byte ordering!
+union __attribute__ ((packed)) Message {
+    PACKEDSTRUCT {
+        uint32_t size;
+        uint8_t  type;
+        uint16_t tag;
+        uint32_t fid;
+    };
+
+    // size[4] Tversion tag[2] msize[4] version[s]
+    // size[4] Rversion tag[2] msize[4] version[s]
+    PACKEDSTRUCT {
+        Header   _header;
+        uint32_t msize;
+    } Tversion, Rversion;
+
+    // // size[4] Tauth tag[2] afid[4] uname[s] aname[s]
+    // PACKEDSTRUCT {
+    //     Header   _header;
+    //     uint32_t afid;
+    // } Tauth;
+
+    // // size[4] Rauth tag[2] aqid[13]
+    // PACKEDSTRUCT {
+    //     Header   _header;
+    //     Qid      aqid;
+    // } Rauth;
+
+    // size[4] Rerror tag[2] ename[s]
+    // size[4] Tclunk tag[2] fid[4]
+    // size[4] Rclunk tag[2]
+    // size[4] Tremove tag[2] fid[4]
+    // size[4] Rremove tag[2]
+    // size[4] Tstat tag[2] fid[4]
+
+    // size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s]
+    PACKEDSTRUCT {
+        Header   _header;
+        uint32_t fid;
+        uint32_t afid;
+    } Tattach;
+
+    // size[4] Rattach tag[2] qid[13]
+    PACKEDSTRUCT {
+        Header   _header;
+        Qid      qid;
+    } Rattach;
+
+    // size[4] Tflush tag[2] oldtag[2]
+    // size[4] Rflush tag[2]
+    PACKEDSTRUCT {
+        Header   _header;
+        uint16_t oldtag;
+    } Tflush;
+
+    // size[4] Twalk tag[2] fid[4] newfid[4] nwname[2] nwname*(wname[s])
+    PACKEDSTRUCT {
+        Header   _header;
+        uint32_t fid;
+        uint32_t newfid;
+        uint16_t nwname;
+        char     wname[0];
+    } Twalk;
+
+    // size[4] Rwalk tag[2] nwqid[2] nwqid*(wqid[13])
+    PACKEDSTRUCT {
+        Header   _header;
+        uint16_t nwqid;
+        Qid      wqid[0];
+    } Rwalk;
+
+    // size[4] Topen tag[2] fid[4] mode[1]
+    PACKEDSTRUCT {
+        Header   _header;
+        uint32_t fid;
+        uint8_t  mode;
+    } Topen;
+
+    // size[4] Ropen tag[2] qid[13] iounit[4]
+    // size[4] Rcreate tag[2] qid[13] iounit[4]
+    PACKEDSTRUCT {
+        Header   _header;
+        Qid      qid;
+        uint32_t iounit;
+    } Ropen, Rcreate;
+
+    // size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1]
+    PACKEDSTRUCT {
+        Header   _header;
+        uint32_t fid;
+        uint16_t name_size;
+        char     name[0];
+    } Tcreate;
+
+    // size[4] Tread tag[2] fid[4] offset[8] count[4]
+    PACKEDSTRUCT {
+        Header   _header;
+        uint32_t fid;
+        uint64_t offset;
+        uint32_t count;
+    } Tread;
+
+    // size[4] Rread tag[2] count[4] data[count]
+    // size[4] Rwrite tag[2] count[4]
+    PACKEDSTRUCT {
+        Header   _header;
+        uint32_t count;
+    } Rread, Rwrite;
+
+    // size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count]
+    PACKEDSTRUCT {
+        Header   _header;
+        uint32_t fid;
+        uint64_t offset;
+        uint32_t count;
+    } Twrite;
+
+    // size[4] Rstat tag[2] stat[n]
+    PACKEDSTRUCT {
+        Header   _header;
+        uint16_t stat_size;
+        Stat     stat;
+    } Rstat;
+
+    // // size[4] Twstat tag[2] fid[4] stat[n]
+    // // size[4] Rwstat tag[2]
+    PACKEDSTRUCT {
+        Header   _header;
+        uint32_t fid;
+        uint16_t stat_size;
+        Stat     stat;
+    } Twstat;
+};
+
+inline char* putstr(char* p, char* end, const char* s)
+{
+    auto n = strlen(s);
+    if (!p || p + 2 + n > end)
+        return nullptr;
+    *p++ = n & 0xFF;
+    *p++ = (n >> 8) & 0xFF;
+    memcpy(p, s, n);
+    return p + n;
+}
+
+inline long flen(const std::string& path)
+{
+    FILE* fp = fopen(path.c_str(), "r");
+    if (!fp)
+        return 0;
+    fseek(fp, 0, SEEK_END);
+    long len = ftell(fp);
+    fclose(fp);
+    return len < 0 ? 0 : len;
+}
+
+size_t putstat(char* buf, char* end, Plan9::Entry* entry)
+{
+    if (buf + sizeof (Stat) > end)
+        return 0;
+
+    char* p = buf;
+    Stat* stat = reinterpret_cast<Stat*>(p);
+    stat->type = 0;
+    stat->dev = 0;
+    stat->qid = entry;
+    stat->mode = entry->type == QTDIR ? (DMDIR | 0755) : 0644;
+    stat->atime = stat->mtime = 1423420000;
+    stat->length = (stat->mode & DMDIR) ? 0 : flen(entry->path);
+    p += sizeof (Stat);
+
+    p = putstr(p, end, entry->path == "/" ? "/" : entry->path.substr(entry->path.rfind('/') + 1).c_str());
+    p = putstr(p, end, "smoothie");
+    p = putstr(p, end, "smoothie");
+    p = putstr(p, end, "smoothie");
+    if (!p) return 0;
+
+    stat->size = p - buf - 2;
+    return p - buf;
+}
+
+inline void start_response(Message* msg, uint16_t size)
+{
+    msg->size = size;
+    ++msg->type;
+}
+
+void send_error(const char* text)
+{
+    char* buf = static_cast<char*>(uip_appdata);
+    Message* msg = static_cast<Message*>(uip_appdata);
+    msg->type = Rerror;
+    msg->size = putstr(buf + sizeof (Header), buf + uip_mss(), text) - buf;
+    uip_send(msg, msg->size);
+}
+
+} // anonymous namespace
+
+Plan9::Plan9()  {}
+Plan9::~Plan9() {}
+
+std::string join_path(const std::string& a, const std::string& b)
+{
+    return a.back() != '/' ? absolute_from_relative(a + "/" + b) :
+            absolute_from_relative(a + b);
+}
+
+Plan9::Entry* Plan9::get_entry(uint8_t type, const std::string& path)
+{
+    std::string abspath = absolute_from_relative(path);
+    auto i = entries.find(abspath);
+    if (i != entries.end())
+        return &(i->second);
+    return &(entries[abspath] = Entry(type, abspath));
+}
+
+Plan9::Entry* Plan9::get_entry(uint32_t fid) const
+{
+    auto i = fids.find(fid);
+    if (i == fids.end())
+        return nullptr;
+    return i->second;
+}
+
+void Plan9::add_fid(uint32_t fid, Entry* entry)
+{
+    fids[fid] = entry;
+    ++entry->refcount;
+}
+
+void Plan9::remove_fid(uint32_t fid)
+{
+    --fids[fid]->refcount;
+    fids.erase(fid);
+}
+
+void Plan9::init()
+{
+    uip_listen(HTONS(564));
+}
+
+void Plan9::appcall()
+{
+    Plan9* instance = static_cast<Plan9*>(uip_conn->appstate);
+
+    if (uip_connected() && !instance) {
+        instance = new Plan9;
+        DEBUG_PRINTF("new instance: %p\n", instance);
+        uip_conn->appstate = instance;
+    }
+
+    if (uip_closed() || uip_aborted() || uip_timedout()) {
+        DEBUG_PRINTF("closed: %p\n", instance);
+        if(instance) {
+            delete instance;
+            uip_conn->appstate = nullptr;
+        }
+        return;
+    }
+
+    if (!instance) {
+        DEBUG_PRINTF("null instance\n");
+        uip_abort();
+        return;
+    }
+
+    if (uip_newdata())
+        instance->handler();
+}
+
+void Plan9::handler()
+{
+    Entry* entry;
+    char* buf = static_cast<char*>(uip_appdata);
+    char* end = buf + uip_datalen();
+
+    Message* msg = reinterpret_cast<Message*>(buf);
+    CHECK(buf + 2 <= end, EBADMSG_TEXT);
+    CHECK(buf + msg->size <= end, EBADMSG_TEXT);
+
+    DEBUG_PRINTF("datalen=%d\n", uip_datalen());
+
+    switch (msg->type) {
+    case Tversion:
+        DEBUG_PRINTF("Tversion\n");
+        start_response(msg, sizeof (msg->Rversion));
+        if (uip_mss() < msg->Rversion.msize)
+            msg->Rversion.msize = uip_mss();
+        msg->size = putstr(buf + msg->size, buf + uip_mss(), "9P2000") - buf;
+        break;
+
+    case Tattach:
+        DEBUG_PRINTF("Tattach\n");
+        CHECK(!get_entry(msg->fid), FID_IN_USE_TEXT);
+        entry = get_entry(QTDIR, "/");
+        add_fid(msg->fid, entry);
+        start_response(msg, sizeof (msg->Rattach));
+        msg->Rattach.qid = entry;
+        break;
+
+    case Tflush:
+        DEBUG_PRINTF("Tflush\n");
+        CHECK(msg->size == sizeof (msg->Tflush), EBADMSG_TEXT);
+        start_response(msg, sizeof (Header));
+        // do nothing
+        break;
+
+    case Twalk:
+        DEBUG_PRINTF("Twalk fid=%lu newfid=%lu\n", msg->Twalk.fid, msg->Twalk.newfid);
+        CHECK(entry = get_entry(msg->Twalk.fid), FID_UNKNOWN_TEXT);
+        CHECK(!get_entry(msg->Twalk.newfid), FID_IN_USE_TEXT);
+
+        if (msg->Twalk.nwname == 0) {
+            start_response(msg, sizeof (msg->Rwalk));
+            msg->Rwalk.nwqid = 0;
+            add_fid(msg->Twalk.newfid, entry);
+        } else {
+            std::string path = entry->path;
+            const char* wname = msg->Twalk.wname;
+            uint16_t num_entries = 0;
+            Entry* entries[MAXWELEM];
+
+            CHECK(msg->Twalk.nwname <= MAXWELEM, EBADMSG_TEXT);
+
+            for (uint16_t i = 0; i < msg->Twalk.nwname; ++i) {
+                CHECK(wname + 2 <= end, EBADMSG_TEXT);
+                uint16_t len = *wname++;
+                len |= *wname++ << 8;
+                CHECK(wname + len <= end, EBADMSG_TEXT);
+                path = join_path(path, std::string(wname, len));
+                wname += len;
+
+                DEBUG_PRINTF("Twalk path=%s\n", path.c_str());
+
+                DIR* dir = opendir(path.c_str());
+                if (dir) {
+                    closedir(dir);
+                    entries[num_entries++] = get_entry(QTDIR, path);
+                } else {
+                    FILE* fp = fopen(path.c_str(), "r");
+                    if (fp) {
+                        fclose(fp);
+                        entries[num_entries++] = get_entry(QTFILE, path);
+                    }
+                    i = msg->Twalk.nwname;
+                }
+            }
+
+            CHECK(num_entries > 0, ENOENT_TEXT);
+            add_fid(msg->Twalk.newfid, entries[num_entries - 1]);
+
+            start_response(msg, sizeof (msg->Rwalk));
+            msg->Rwalk.nwqid = num_entries;
+            Qid* wqid = msg->Rwalk.wqid;
+            for (uint16_t i = 0; i < num_entries; ++i) {
+                *wqid++ = entries[i];
+                msg->size += sizeof (Qid);
+            }
+        }
+        break;
+    case Tstat:
+        CHECK(msg->size == sizeof (Header) + 4, EBADMSG_TEXT);
+        CHECK(entry = get_entry(msg->fid), FID_UNKNOWN_TEXT);
+
+        DEBUG_PRINTF("Tstat fid=%lu %s\n", msg->fid, entry->path.c_str());
+
+        start_response(msg, sizeof (msg->Rstat));
+        CHECK((msg->Rstat.stat_size = putstat(reinterpret_cast<char*>(&(msg->Rstat.stat)), buf + uip_mss(), entry)) > 0, EFAULT_TEXT);
+        msg->size = sizeof (Header) + 2 + msg->Rstat.stat_size;
+        break;
+
+    case Tclunk:
+        DEBUG_PRINTF("Tclunk fid=%lu\n", msg->fid);
+        CHECK(get_entry(msg->fid), FID_UNKNOWN_TEXT);
+        CHECK(msg->size == sizeof (Header) + 4, EBADMSG_TEXT);
+        remove_fid(msg->fid);
+        start_response(msg, sizeof (Header));
+        break;
+
+    case Topen:
+        CHECK(msg->size == sizeof (msg->Topen), EBADMSG_TEXT);
+        CHECK(entry = get_entry(msg->fid), FID_UNKNOWN_TEXT);
+        DEBUG_PRINTF("Topen fid=%lu %s\n", msg->fid, entry->path.c_str());
+
+        if (entry->type != QTDIR && (msg->Topen.mode & OTRUNC)) {
+            FILE* fp = fopen(entry->path.c_str(), "w");
+            CHECK(fp, EIO_TEXT);
+            fclose(fp);
+        }
+
+        start_response(msg, sizeof (msg->Ropen));
+        msg->Ropen.qid = entry;
+        msg->Ropen.iounit = IOUNIT;
+        break;
+
+    case Tread:
+        DEBUG_PRINTF("Tread fid=%lu\n", msg->fid);
+        CHECK(msg->size == sizeof (msg->Tread), EBADMSG_TEXT);
+        CHECK(msg->Tread.count <= IOUNIT, EBADMSG_TEXT);
+        CHECK(entry = get_entry(msg->fid), FID_UNKNOWN_TEXT);
+
+        if (entry->type == QTDIR) {
+            DIR* dir = opendir(entry->path.c_str());
+            CHECK(dir, EIO_TEXT);
+
+            auto offset = msg->Tread.offset;
+            auto count = msg->Tread.count;
+
+            start_response(msg, sizeof (msg->Rread));
+            char* data = buf + sizeof (msg->Rread);
+            struct dirent* ent;
+            while ((ent = readdir(dir)) && count > 0) {
+                auto path = join_path(entry->path, ent->d_name);
+                DEBUG_PRINTF("Tread path %s\n", path.c_str());
+
+                Entry* child = get_entry(ent->d_isdir ? QTDIR : QTFILE, path);
+                char buf[sizeof (Stat) + 128];
+                size_t stat_size = putstat(buf, buf + sizeof (buf), child);
+                CHECK(stat_size > 0, EFAULT_TEXT);
+
+                if (offset >= stat_size) {
+                    offset -= stat_size;
+                } else {
+                    uint16_t size = stat_size - offset;
+                    if (size > count)
+                        break;
+                    memcpy(data + offset, buf, size);
+                    data += size;
+                    msg->Rread.count += size;
+                    msg->size += size;
+                    offset = 0;
+                    count -= size;
+                }
+            }
+            closedir(dir);
+        } else {
+            FILE* fp = fopen(entry->path.c_str(), "r");
+            CHECK(fp, EIO_TEXT);
+            if (fseek(fp, msg->Tread.offset, SEEK_SET)) {
+                fclose(fp);
+                FAIL(EIO_TEXT);
+            }
+            uint32_t count = msg->Tread.count;
+            start_response(msg, sizeof (msg->Rread));
+            msg->Rread.count = fread(buf + msg->size, 1, count, fp);
+            auto ok = msg->Rread.count == count || !ferror(fp);
+            fclose(fp);
+            CHECK(ok, EIO_TEXT);
+            msg->size += msg->Rread.count;
+        }
+        break;
+
+    case Tcreate:
+        {
+            CHECK(msg->size == sizeof (msg->Tcreate) + msg->Tcreate.name_size + 4 + 1, EBADMSG_TEXT);
+            CHECK(msg->Tcreate.name + msg->Tcreate.name_size + 4 <= end, EBADMSG_TEXT);
+            CHECK(entry = get_entry(msg->fid), FID_UNKNOWN_TEXT);
+
+            auto path = join_path(entry->path, std::string(msg->Tcreate.name, msg->Tcreate.name_size));
+            uint32_t perm;
+            memcpy(&perm, msg->Tcreate.name + msg->Tcreate.name_size, 4);
+
+            DEBUG_PRINTF("Tcreate fid=%lu path=%s\n", msg->fid, path.c_str());
+            CHECK(!(perm & ~(DMDIR | 0777)), ENOSYS_TEXT);
+
+            if (perm & DMDIR) {
+                CHECK(!mkdir(path.c_str(), 0755), EEXIST_TEXT);
+            } else {
+                FILE* fp = fopen(path.c_str(), "w");
+                CHECK(fp, EIO_TEXT);
+                fclose(fp);
+            }
+            ++entry->vers;
+            --entry->refcount;
+            entry = get_entry((perm & DMDIR) ? QTDIR : QTFILE, path);
+            fids[msg->fid] = entry;
+            start_response(msg, sizeof (msg->Rcreate));
+            msg->Rcreate.qid = entry;
+            msg->Rcreate.iounit = IOUNIT;
+        }
+        break;
+
+    case Twrite:
+        {
+            DEBUG_PRINTF("Twrite fid=%lu\n", msg->fid);
+            CHECK(msg->size == sizeof (msg->Twrite) + msg->Twrite.count, EBADMSG_TEXT);
+            CHECK(msg->Twrite.count <= IOUNIT, EBADMSG_TEXT);
+            CHECK(entry = get_entry(msg->fid), FID_UNKNOWN_TEXT);
+
+            FILE* fp = fopen(entry->path.c_str(), "r+");
+            CHECK(fp, EIO_TEXT);
+            if (fseek(fp, msg->Twrite.offset, SEEK_SET)) {
+                fclose(fp);
+                FAIL(EIO_TEXT);
+            }
+
+            uint32_t count = fwrite(buf + sizeof (msg->Twrite), 1, msg->Twrite.count, fp);
+            auto ok = count == msg->Twrite.count || !ferror(fp);
+            fclose(fp);
+            CHECK(ok, EIO_TEXT);
+
+            start_response(msg, sizeof (msg->Rwrite));
+            msg->Rwrite.count = count;
+            ++entry->vers;
+        }
+        break;
+
+    case Tremove:
+        {
+            DEBUG_PRINTF("Tremove fid=%lu\n", msg->fid);
+            CHECK(msg->size == sizeof (Header) + 4, EBADMSG_TEXT);
+            CHECK(entry = get_entry(msg->fid), FID_UNKNOWN_TEXT);
+            Entry e = *entry;
+            remove_fid(msg->fid);
+            if (e.refcount == 0)
+                entries.erase(e.path);
+            CHECK(!remove(e.path.c_str()), e.type == QTDIR ? ENOTEMPTY_TEXT : EIO_TEXT);
+            start_response(msg, sizeof (Header));
+        }
+        break;
+
+    case Twstat:
+        {
+            DEBUG_PRINTF("Twstat fid=%lu\n", msg->fid);
+            CHECK(entry = get_entry(msg->fid), FID_UNKNOWN_TEXT);
+            char* name = buf + sizeof (msg->Twstat);
+            uint16_t len = *name++;
+            len |= *name++ << 8;
+            CHECK(name + len <= end, EBADMSG_TEXT);
+            start_response(msg, sizeof (Header));
+            if (len > 0 && entry->path != "/") {
+                std::string newpath = join_path(entry->path.substr(0, entry->path.rfind('/')), std::string(name, len));
+                if (newpath != entry->path) {
+                    CHECK(!rename(entry->path.c_str(), newpath.c_str()), EIO_TEXT);
+                    Entry* newentry = get_entry(entry->type, newpath);
+                    remove_fid(msg->fid);
+                    if (entry->refcount == 0)
+                        entries.erase(entry->path);
+                    add_fid(msg->fid, newentry);
+                }
+            }
+        }
+        break;
+
+    // not implemented
+    // case Tauth:
+    //     start_response(msg);
+    //     msg->Rauth.aqid.type = 0;
+    //     msg->Rauth.aqid.vers = 0;
+    //     msg->Rauth.aqid.path = 1;
+    //     break;
+
+    default:
+        DEBUG_PRINTF("Unknown message %u\n", msg->type);
+        return send_error(ENOSYS_TEXT);
+    }
+
+    uip_send(msg, msg->size);
+}
diff --git a/src/libs/Network/uip/plan9/plan9.h b/src/libs/Network/uip/plan9/plan9.h
new file mode 100644 (file)
index 0000000..c4306c4
--- /dev/null
@@ -0,0 +1,58 @@
+#ifndef __PLAN9_H__
+#define __PLAN9_H__
+
+/*
+ * 9P network filesystem protocol
+ *
+ * by Daniel Mendler <mail@daniel-mendler.de>
+ *
+ * Resources:
+ *
+ *   - Documentation: http://9p.cat-v.org/
+ *   - List of implementations: http://9p.cat-v.org/implementations
+ *   - Specification: http://ericvh.github.io/9p-rfc/
+ *   - Linux documentation: https://www.kernel.org/doc/Documentation/filesystems/9p.txt
+ *
+ * How to use it:
+ *
+ *   1. Add "network.plan9.enable true" to the config
+ *   2. Mount under Linux with "mount -t 9p $ip /mnt/smoothie
+ */
+
+#include <map>
+#include <string>
+#include <stdint.h>
+
+class Plan9
+{
+public:
+    Plan9();
+    ~Plan9();
+
+    static void init();
+    static void appcall();
+
+    struct Entry {
+        uint8_t     type;
+        uint32_t    vers;
+        int         refcount;
+        std::string path;
+
+        Entry() {}
+        Entry(uint8_t t, const std::string& p)
+            : type(t), vers(0), refcount(0), path(p) {}
+    };
+
+private:
+    void handler();
+
+    Entry* get_entry(uint8_t, const std::string&);
+    Entry* get_entry(uint32_t) const;
+    void add_fid(uint32_t, Entry*);
+    void remove_fid(uint32_t);
+
+    std::map<uint32_t,    Entry*> fids;
+    std::map<std::string, Entry>  entries;
+};
+
+#endif