~aquaratixc/serve9

1ec903cd98e1ab8c4333e5e9de201498f78ff335 — Oleg Bakharev 2 years ago e9f3cd8
Created project structure and added source files
6 files changed, 585 insertions(+), 0 deletions(-)

A .gitignore
A dub.sdl
A source/app.d
A source/io.d
A source/server.d
A source/styxserver.d
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;
		}
	}
}