-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
497 additions
and
1,129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,29 @@ | ||
# Plan 9 Filesystem Protocol, as implemented in Clojure. | ||
|
||
```clj | ||
[phlegyas "0.0.1-SNAPSHOT"] | ||
[phlegyas "0.1.1"] | ||
``` | ||
|
||
*WARNING: DRAGONS LIE AHEAD! THIS IS WOEFULLY INCOMPLETE. USE AT YOUR OWN PERIL!* | ||
|
||
The vast majority of the protocol-level documentation was sourced from the wonderful [Plan 9 from User Space](https://9fans.github.io/plan9port/man/man9/) project. | ||
|
||
I have copied the test resources from [droyo's styx package](https://github.com/droyo/styx/), credit due for making it available. | ||
|
||
Run `lein test` to verify things work as they should. Currently, 100% of the provided framedumps are successfully handled, hopefully indicating that this is fully up to spec. | ||
|
||
"LISP programmers know the value of everything and the cost of nothing." Thus, I have not measured performance of the encode/decode in any serious manner, and the example state machine is a dumb single loop, likely unsuitable for any serious use. However, the principles of how to piece things together should be evident, and the design entirely customisable. | ||
|
||
Note the field names in `types.clj`. The `assemble-packet` function will take a map of these and create a byte-array for you. `disassemble-packet` will do the reverse. | ||
|
||
Development Notes: | ||
|
||
There are still many functions that require implementation, not least the VFS layer. Consider it unstable and subject to major changes. | ||
|
||
I have included a built-in TCP server in order to aid this development, accessible from the phlegyas.core namespace. | ||
This release solely covers byte-array encoding/decoding. | ||
|
||
Jack in with Spacemacs/CIDER with `,'` and then, at the REPL, `(r)` | ||
I have included the test resources from [droyo's styx package](https://github.com/droyo/styx/). Run `lein test` to verify things work as they should. Currently, 100% of the provided framedumps are successfully handled, hopefully indicating that this is up to spec. | ||
|
||
This will start a server at localhost on port 10001. | ||
## Usage | ||
|
||
For testing: | ||
Keys for the frame encoding can be found in the `phlegyas.types` namespace. Check the `frame-layouts` map. There are only a few special cases, namely: | ||
* `:Twrite` and `:Rread` frames, where the `count[4]` is automatically calculated. | ||
* `:Twalk`, where `nwname[2]` is automatically calculated and `:wnames` should be a vector of strings. | ||
* `:Rwalk`, where `nwqid[2]` is automatically calculated and `:nqwids` should be a vector of `{:qid-type qid.type[1] :qid-vers qid.vers[4] :qid-path qid.path[8]}` maps. | ||
* The `qid.type[1]`, `qid.vers[4]`, `qid.path[8]` fields are named with dashes rather than dots, to make the buffer operator functions easier to resolve. | ||
|
||
`git clone https://github.com/9fans/plan9port.git && cd plan9port && ./INSTALL` | ||
Encoding and decoding, as done via the REPL: | ||
|
||
Then run the built 9P FUSE client: | ||
|
||
`9pfuse -D 'tcp!localhost!10001' mount-point-goes-here` | ||
|
||
This should aid in the development cycle. | ||
|
||
The example VFS layer will create a single filesystem for attaching, and some example files within, with both dynamic and static content. | ||
|
||
There's also a few examples of callback / stream usage in the core and state namespace. | ||
``` | ||
phlegyas.core=> (vec (assemble-packet {:frame :Tversion :tag 0 :msize 8192 :version "9P2000"})) | ||
[19 0 0 0 100 0 0 0 32 0 0 6 0 57 80 50 48 48 48] | ||
When hitting inevitable issues, a simple call to `(r)` again will reset the service back to a clean state, ready to continue on your adventures. | ||
phlegyas.core=> (disassemble-packet (byte-array [19 0 0 0 100 0 0 0 32 0 0 6 0 57 80 50 48 48 48])) | ||
{:frame :Tversion, :tag 0, :msize 8192, :version "9P2000"} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
(ns phlegyas.buffers | ||
(:require [primitive-math :as math | ||
:refer [int->uint | ||
short->ushort | ||
long->ulong]])) | ||
|
||
(defn get-tag | ||
"Read tag[2] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getShort short->ushort)) | ||
|
||
(defn get-oldtag | ||
"Read oldtag[2] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getShort short->ushort)) | ||
|
||
(defn get-msize | ||
"Read msize[4] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-string | ||
"Read string[s] from the byte buffer." | ||
[buffer] | ||
(let [string-size (-> buffer .getShort short->ushort)] | ||
(String. (byte-array (map byte (for [i (range string-size)] (.get buffer)))) "UTF-8"))) | ||
|
||
(defn get-version | ||
"Read version[s] from the byte buffer." | ||
[buffer] | ||
(get-string buffer)) | ||
|
||
(defn get-uname | ||
"Read uname[s] from the byte buffer." | ||
[buffer] | ||
(get-string buffer)) | ||
|
||
(defn get-aname | ||
"Read aname[s] from the byte buffer." | ||
[buffer] | ||
(get-string buffer)) | ||
|
||
(defn get-ename | ||
"Read ename[s] from the byte buffer." | ||
[buffer] | ||
(get-string buffer)) | ||
|
||
(defn get-fid | ||
"Read fid[4] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-newfid | ||
"Read newfid[4] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-afid | ||
"Read afid[4] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-perm | ||
"Read perm[4] from the byte buffer" | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-offset | ||
"Read offset[8] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getLong long->ulong)) | ||
|
||
(defn get-count | ||
"Read count[4] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-size | ||
"Read size[2] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getShort short->ushort)) | ||
|
||
(defn get-ssize | ||
"Read size[2] from the byte buffer. Rstat and Twstat have repeated | ||
size field, with our ssize being +2 more than size. | ||
See BUGS section of stat(9) manual for more information." | ||
[buffer] | ||
(-> buffer .getShort short->ushort)) | ||
|
||
(defn get-type | ||
"Read type[2] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getShort short->ushort)) | ||
|
||
(defn get-dev | ||
"Read dev[4] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-qid-type | ||
"Read qid.type[1] from the byte buffer." | ||
[buffer] | ||
(-> buffer .get)) | ||
|
||
(defn get-qid-vers | ||
"Read qid.vers[4] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-qid-path | ||
"Read qid.path[8] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getLong long->ulong)) | ||
|
||
(defn get-mode | ||
"Read mode[4] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-atime | ||
"Read atime[4] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-mtime | ||
"Read mtime[4] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-length | ||
"Read length[8] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getLong long->ulong)) | ||
|
||
(defn get-name | ||
"Read name[s] from the byte buffer." | ||
[buffer] | ||
(get-string buffer)) | ||
|
||
(defn get-uid | ||
"Read uid[s] from the byte buffer." | ||
[buffer] | ||
(get-string buffer)) | ||
|
||
(defn get-gid | ||
"Read gid[s] from the byte buffer." | ||
[buffer] | ||
(get-string buffer)) | ||
|
||
(defn get-muid | ||
"Read muid[s] from the byte buffer." | ||
[buffer] | ||
(get-string buffer)) | ||
|
||
(defn get-iomode | ||
"Read mode[1] from the byte buffer." | ||
[buffer] | ||
(-> buffer .get)) | ||
|
||
(defn get-iounit | ||
"Read iounit[4] from the byte buffer." | ||
[buffer] | ||
(-> buffer .getInt int->uint)) | ||
|
||
(defn get-data | ||
"Read count[4] bytes of data from the byte buffer." | ||
[buffer] | ||
(let [data-size (-> buffer .getInt int->uint)] | ||
(byte-array (map byte (for [i (range data-size)] (.get buffer)))))) | ||
|
||
(defn get-wnames | ||
"Read nwname[2] of wname[s] from the byte buffer." | ||
[buffer] | ||
(let [nwname (-> buffer .getShort short->ushort)] | ||
(if (= nwname 0) | ||
[] | ||
(loop [wnames [] | ||
count nwname] | ||
(if (= count 0) | ||
wnames | ||
(recur (conj wnames (get-string buffer)) (- count 1))))))) | ||
|
||
(defn get-nwqids | ||
"Read nqwid[2] of qid[13] from the byte buffer." | ||
[buffer] | ||
(let [nwqid (-> buffer .getShort short->ushort)] | ||
(if (= nwqid 0) | ||
[] | ||
(loop [qids [] | ||
count nwqid] | ||
(if (= count 0) | ||
qids | ||
(recur (conj qids {:qid-type (get-qid-type buffer) :qid-vers (get-qid-vers buffer) :qid-path (get-qid-path buffer)}) | ||
(- count 1))))))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,87 +1,3 @@ | ||
(ns phlegyas.core | ||
(:require [phlegyas.frames :refer :all] | ||
[phlegyas.state :refer :all] | ||
[phlegyas.vfs :refer :all] | ||
[phlegyas.util :refer :all] | ||
[phlegyas.types :refer :all] | ||
[clojure.core.async :as async] | ||
[manifold.stream :as s] | ||
[aleph.tcp :as tcp] | ||
[taoensso.timbre :as log] | ||
[primitive-math :as math | ||
:refer [int->uint short->ushort | ||
uint->int ushort->short | ||
ubyte->byte byte->ubyte]])) | ||
|
||
(def example-mutation-stream (s/stream)) | ||
(def state-defaults {:root-filesystem #'example-filesystem! | ||
:mutation-stream example-mutation-stream}) | ||
|
||
(defn example-callback | ||
[{:keys [state data]}] | ||
(log/info "callback activated!") | ||
(log/info "adding a file to the filesystem...") | ||
(let [fs ((:root-filesystem-name state) (:fs-map state)) | ||
file-path (swap! (:path-pool fs) inc) | ||
synthetic-file (synthetic-file file-path | ||
(:filename data) | ||
"root" | ||
"root" | ||
0444 | ||
"callback!" | ||
(fn [x] (.getBytes (:custom-data-field (:stat x)) "UTF-8")) | ||
(sizeof-string "callback!"))] | ||
(assoc state :fs-map (assoc (:fs-map state) | ||
(:id fs) | ||
(-> fs | ||
(insert-file! file-path synthetic-file) | ||
(update-children! (:root-path fs) file-path)))))) | ||
|
||
(defn add-file | ||
[filename] | ||
(s/put! example-mutation-stream {:fn example-callback | ||
:data {:filename filename}})) | ||
|
||
|
||
(defn server! | ||
[in out & {:keys [state-machine initial-state] :or {state-machine #'mutate-state initial-state state-defaults}}] | ||
(async/thread | ||
(let [frame-stream (s/stream) | ||
connection-id (java.util.UUID/randomUUID)] | ||
(log/info connection-id "connection established.") | ||
(frame-assembler in frame-stream) | ||
(loop [state (into initial-state {:connection-id connection-id})] | ||
(let [frame @(s/take! frame-stream)] | ||
(log/debug "State:" state) | ||
(if (nil? frame) | ||
(do | ||
(log/info connection-id "connection closed.")) | ||
(recur (state-machine frame out state)))))))) | ||
|
||
(log/set-level! :info) | ||
|
||
(def srv nil) | ||
|
||
(defn tcp-route | ||
[s info] | ||
(let [in (s/stream) | ||
out (s/stream) | ||
ninep-server (server! in out)] | ||
(s/connect s in) | ||
(s/connect out s))) | ||
|
||
(defn go | ||
[] | ||
(def srv (tcp/start-server tcp-route {:port 10001 :join? false}))) | ||
|
||
(defn r | ||
[] | ||
(if (nil? srv) | ||
(go) | ||
(do | ||
(.close srv) | ||
(go)))) | ||
|
||
(defn dial | ||
[host port] | ||
(tcp/client {:host host :port port})) | ||
(:require [phlegyas.frames :refer :all]) | ||
(:gen-class)) |
Oops, something went wrong.