A .gitignore => .gitignore +15 -0
@@ 0,0 1,15 @@
+.dub
+docs.json
+__dummy.html
+docs/
+/serve9
+serve9.so
+serve9.dylib
+serve9.dll
+serve9.a
+serve9.lib
+serve9-test-*
+*.exe
+*.o
+*.obj
+*.lst
A dub.sdl => dub.sdl +6 -0
@@ 0,0 1,6 @@
+name "serve9"
+description "Simple tool for sharing folders via 9P/Styx"
+authors "aquareji"
+copyright "Copyright © 2022, aquareji"
+license "ESL 1.0"
+dependency "styx2000" version="~>1.0.0"
A source/app.d => source/app.d +50 -0
@@ 0,0 1,50 @@
+private {
+ import std.conv : ConvException;
+ import std.getopt;
+ import std.stdio : writeln;
+
+ import serve9.styxserver;
+}
+
+string address;
+ushort port;
+string path;
+string uid = "user";
+string gid = "user";
+bool messages;
+bool bytes;
+
+void main(string[] args)
+{
+ try
+ {
+ getopt(
+ args,
+ std.getopt.config.required,
+ "addr|a", &address, /* server ip */
+ std.getopt.config.required,
+ "port|p", &port, /* server port */
+ std.getopt.config.required,
+ std.getopt.config.caseSensitive,
+ "path|d", &path, /* path to folder */
+ "uid|u", &uid, /* user id */
+ "gid|g", &gid, /* group id */
+ "messages|M", &messages, /* debug mode */
+ "bytes|B", &bytes, /* show raw bytes */
+ std.getopt.config.passThrough
+ );
+
+ auto server = new StyxShareServer(path,uid, gid);
+ with (server)
+ {
+ messagesMode(messages);
+ bytesMode(bytes);
+ setup6(address, port);
+ run;
+ }
+ }
+ catch (Throwable e)
+ {
+ writeln(e.msg);
+ }
+}
A source/io.d => source/io.d +99 -0
@@ 0,0 1,99 @@
+module serve9.io;
+
+private {
+ import std.stdio : File;
+
+ import styx2000.protobj : Count, Data, StyxObject;
+}
+
+auto readAt(string filepath, ulong offset, uint count)
+{
+ File file;
+ file.open(filepath, `rb`);
+ auto size = file.size;
+
+ if (offset >= size)
+ {
+ return cast(StyxObject[])[
+ new Count(0),
+ new Data
+ ];
+ }
+ else
+ {
+ ubyte[] data = new ubyte[count];
+ uint realCount;
+
+ file.seek(offset);
+ file.rawRead(data);
+
+
+ if ((size - offset) < count)
+ {
+ realCount = cast(uint) (size - offset);
+ }
+ else
+ {
+ realCount = count;
+ }
+
+ return cast(StyxObject[])[
+ new Count(cast(uint) realCount),
+ new Data(data[0..realCount])
+ ];
+ }
+}
+
+auto readAt(ubyte[] data, ulong offset, uint count)
+{
+ auto size = data.length;
+ if (offset >= size)
+ {
+ return cast(StyxObject[])[
+ new Count(0),
+ new Data
+ ];
+ }
+ else
+ {
+ ubyte[] bdata;
+
+ if ((offset + count) > data.length)
+ {
+ if (offset >= data.length)
+ {
+ return cast(StyxObject[])[
+ new Count(0),
+ new Data
+ ];
+ }
+ else
+ {
+ bdata = data[offset..$];
+ }
+ }
+ else
+ {
+ bdata = data[offset..offset+count];
+ }
+
+ return cast(StyxObject[])[
+ new Count(cast(uint) bdata.length),
+ new Data(bdata)
+ ];
+ }
+}
+
+auto writeAt(string filepath, ulong offset, uint count, ubyte[] data)
+{
+ File file;
+ file.open(filepath, `wb`);
+
+ auto buffer = data[0..count];
+ file.seek(offset);
+ file.rawWrite(buffer);
+
+ return cast(StyxObject[])[
+ new Count(cast(uint) buffer.length)
+ ];
+}
A source/server.d => source/server.d +131 -0
@@ 0,0 1,131 @@
+module serve9.server;
+
+private {
+ import std.algorithm : remove;
+ import std.socket;
+}
+
+class GenericSimpleServer(uint BUFFER_SIZE, uint MAXIMAL_NUMBER_OF_CONNECTIONS)
+{
+ protected {
+ ubyte[BUFFER_SIZE] _buffer;
+
+ string _address;
+ ushort _port;
+ bool _immediate;
+ Socket _listener;
+ Socket[] _readable;
+ SocketSet _sockets;
+ }
+
+ abstract ubyte[] handle(ubyte[] request);
+
+ final void run()
+ {
+ while (true)
+ {
+ serve;
+
+ scope(failure) {
+ _sockets = null;
+ _listener.close;
+ }
+ }
+ }
+
+ final void setup4(string address, ushort port, int backlog = 10, bool immediate = false)
+ {
+ _address = address;
+ _port = port;
+ _listener = new Socket(AddressFamily.INET, SocketType.STREAM);
+ _immediate = immediate;
+
+ with (_listener)
+ {
+ bind(new InternetAddress(_address, _port));
+ listen(backlog);
+ }
+
+ _sockets = new SocketSet(MAXIMAL_NUMBER_OF_CONNECTIONS + 1);
+ }
+
+ final void setup6(string address, ushort port, int backlog = 10, bool immediate = false)
+ {
+ _address = address;
+ _port = port;
+ _listener = new Socket(AddressFamily.INET6, SocketType.STREAM);
+ _immediate = immediate;
+
+ with (_listener)
+ {
+ bind(new Internet6Address(_address, _port));
+ listen(backlog);
+ }
+
+ _sockets = new SocketSet(MAXIMAL_NUMBER_OF_CONNECTIONS + 1);
+ }
+
+ private
+ {
+ final void serve()
+ {
+ _sockets.add(_listener);
+
+ foreach (socket; _readable)
+ {
+ _sockets.add(socket);
+ }
+
+ Socket.select(_sockets, null, null);
+
+ for (uint i = 0; i < _readable.length; i++)
+ {
+ if (_sockets.isSet(_readable[i]))
+ {
+ auto realBufferSize = _readable[i].receive(_buffer);
+
+ if (realBufferSize != 0)
+ {
+ auto data = _buffer[0..realBufferSize];
+
+ _readable[i].send(
+ handle(data)
+ );
+ }
+
+ if (_immediate)
+ {
+ _readable[i].close;
+ _readable = _readable.remove(i);
+ i--;
+ }
+ }
+ }
+
+ if (_sockets.isSet(_listener))
+ {
+ Socket currentSocket = null;
+
+ scope (failure)
+ {
+ if (currentSocket)
+ {
+ currentSocket.close;
+ }
+ }
+
+ currentSocket = _listener.accept;
+
+ if (_readable.length < MAXIMAL_NUMBER_OF_CONNECTIONS)
+ {
+ _readable ~= currentSocket;
+ }
+ else
+ {
+ currentSocket.close;
+ }
+ }
+ _sockets.reset;
+ }
+ }
+}
A source/styxserver.d => source/styxserver.d +284 -0
@@ 0,0 1,284 @@
+module serve9.styxserver;
+
+private {
+ enum uint BUFFER_SIZE = 8_192;
+ enum uint CONNECTIONS_NUMBER = 60;
+
+ import std.file : dirEntries, DirEntry, SpanMode, exists;
+ import std.path : buildPath, baseName;
+ import std.stdio : writeln;
+
+ import styx2000.extrautil.casts;
+ import styx2000.extrautil.dir;
+ import styx2000.extrautil.dirstat;
+
+ import styx2000.extrautil.mischelpers : createQid, createStat;
+ import styx2000.extrautil.styxmessage;
+
+ import styx2000.protoconst.modes;
+ import styx2000.protoconst.messages;
+ import styx2000.protoconst.qids;
+
+ import styx2000.protomsg : decode, encode;
+
+ import styx2000.protobj;
+
+ import serve9.io;
+ import serve9.server;
+}
+
+class StyxShareServer : GenericSimpleServer!(BUFFER_SIZE, CONNECTIONS_NUMBER)
+{
+ private {
+ string _dir;
+ string _uid;
+ string _gid;
+
+ string[uint] _pool;
+
+ bool _messages;
+ bool _bytes;
+ }
+
+ this(string dir, string uid, string gid)
+ {
+ _dir = dir;
+ _uid = uid;
+ _gid = gid;
+ }
+
+ void messagesMode(bool messages)
+ {
+ _messages = messages;
+ }
+
+ void bytesMode(bool bytes)
+ {
+ _bytes = bytes;
+ }
+
+ override ubyte[] handle(ubyte[] request)
+ {
+ auto rmsg = decode(request);
+ auto tmsg = process(rmsg);
+ auto response = encode(tmsg);
+
+ if (_messages)
+ {
+ writeln(`-> `, rmsg);
+ writeln(`<- `, tmsg);
+ }
+
+ if (_bytes)
+ {
+ writeln(`-> `, request);
+ writeln(`<- `, response);
+ }
+ return response;
+ }
+
+ private {
+ StyxMessage walk(ushort tag, uint fid, uint newfid, string[] nwname) {
+ StyxMessage msg = createHeader(0, STYX_MESSAGE_TYPE.R_WALK, tag);
+
+ if (nwname.length == 0)
+ {
+ msg ~= cast(StyxObject) new Nwqid;
+ _pool[newfid] = _dir;
+ }
+ else
+ {
+ auto path = buildPath(_dir ~ nwname);
+
+ if (path.exists)
+ {
+ Qid[] qids;
+ auto nwqid = new Nwqid;
+
+ foreach (e; nwname)
+ {
+ auto npath = buildPath(_dir ~ nwname);
+ qids ~= createQid(npath);
+ }
+
+ nwqid.setQid(qids);
+ msg ~= cast(StyxObject) nwqid;
+
+ if (path != _dir)
+ {
+ _pool[newfid] = path;
+ }
+ }
+ else
+ {
+ msg = createRmsgError(tag, `Walk error`);
+ }
+ }
+
+ return msg;
+ }
+
+ StyxMessage stat(ushort tag, uint fid) {
+ StyxMessage msg = createHeader(0, STYX_MESSAGE_TYPE.R_STAT, tag);
+
+ if (fid in _pool)
+ {
+ auto path = _pool[fid];
+
+ msg ~= cast(StyxObject) createStat(path, 0, 0, _uid, _gid);
+ }
+ else
+ {
+ msg = createRmsgError(tag, `Stat error`);
+ }
+
+ return msg;
+ }
+
+ StyxMessage open(ushort tag, uint fid, STYX_FILE_MODE mode) {
+ StyxMessage msg;
+
+ if (fid in _pool)
+ {
+ auto path = _pool[fid];
+
+ auto qid = createQid(_dir);
+ msg = createRmsgOpen(tag, qid.getType, qid.getVers, qid.getPath);
+ }
+ else
+ {
+ msg = createRmsgError(tag, `Open error`);
+ }
+
+ return msg;
+ }
+
+ StyxMessage read(ushort tag, uint fid, ulong offset, uint count) {
+ StyxMessage msg = createHeader(0, STYX_MESSAGE_TYPE.R_READ, tag);
+
+ if (fid in _pool)
+ {
+ auto path = _pool[fid];
+
+ if (DirEntry(path).isDir)
+ {
+ Dir[] dirs;
+
+ foreach (e; dirEntries(path, SpanMode.shallow))
+ {
+ auto dir = createStat(e.name, 0, 0, _uid, _gid).stat2dir;
+ dirs ~= dir;
+ }
+
+ auto ds = new DirStat(dirs);
+ msg ~= readAt(ds.pack, offset, count);
+ }
+ else
+ {
+ msg ~= readAt(path, offset, count);
+ }
+ }
+ else
+ {
+ msg = createRmsgError(tag, `Read error`);
+ }
+
+ return msg;
+ }
+
+ StyxMessage write(ushort tag, uint fid, ulong offset, uint count, ubyte[] data) {
+ StyxMessage msg = createHeader(0, STYX_MESSAGE_TYPE.R_WRITE, tag);
+
+ if (fid in _pool)
+ {
+ auto path = _pool[fid];
+
+ if (DirEntry(path).isDir)
+ {
+ msg = createRmsgError(tag, `Write error`);
+ }
+ else
+ {
+ msg ~= writeAt(path, offset, count, data);
+ }
+ }
+ else
+ {
+ msg = createRmsgError(tag, `Write error`);
+ }
+
+ return msg;
+ }
+
+ StyxMessage processFS(STYX_MESSAGE_TYPE type, ushort tag, StyxObject[] args...) {
+ StyxMessage msg;
+
+ auto fid = args[0].toFid.getFid;
+
+ switch (type)
+ {
+ case STYX_MESSAGE_TYPE.T_WALK:
+ auto newfid = args[1].toNewFid.getFid;
+ auto nwname = args[2].toNwname.getName;
+ msg = walk(tag, fid, newfid, nwname);
+ break;
+ case STYX_MESSAGE_TYPE.T_STAT:
+ msg = stat(tag, fid);
+ break;
+ case STYX_MESSAGE_TYPE.T_OPEN:
+ auto mode = args[1].toMode.getMode;
+ msg = open(tag, fid, mode);
+ break;
+ case STYX_MESSAGE_TYPE.T_READ:
+ auto offset = args[1].toOffset.getOffset;
+ auto count = args[2].toCount.getCount;
+ msg = read(tag, fid, offset, count);
+ break;
+ case STYX_MESSAGE_TYPE.T_WRITE:
+ auto offset = args[1].toOffset.getOffset;
+ auto count = args[2].toCount.getCount;
+ auto data = args[3].toData.getData;
+ msg = write(tag, fid, offset, count, data);
+ break;
+ default:
+ msg = createRmsgError(tag, `Wrong message`);
+ break;
+ }
+
+ return msg;
+ }
+
+ StyxMessage process(StyxMessage query) {
+ StyxMessage reply;
+
+ auto type = query[1].toType.getType;
+ auto tag = query[2].toTag.getTag;
+
+ switch (type)
+ {
+ case STYX_MESSAGE_TYPE.T_VERSION:
+ reply = createRmsgVersion;
+ break;
+ case STYX_MESSAGE_TYPE.T_ATTACH:
+ auto fid = query[3].toFid.getFid;
+ _pool[fid] = _dir;
+ reply = createRmsgAttach(tag, STYX_QID_TYPE.QTDIR);
+ break;
+ case STYX_MESSAGE_TYPE.T_CLUNK:
+ auto fid = query[3].toFid.getFid;
+ _pool.remove(fid);
+ reply = createRmsgClunk(tag);
+ break;
+ case STYX_MESSAGE_TYPE.T_FLUSH:
+ reply = createRmsgFlush(tag);
+ break;
+ default:
+ auto args = query[3..$];
+ reply = processFS(type, tag, args);
+ break;
+ }
+
+ return reply;
+ }
+ }
+}