commit edba571c690ed7bd331842dc03fa7efbc49de226 Author: Wesley Kerfoot Date: Fri Jun 14 22:57:05 2019 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33c11a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*pyc +*~ +venv diff --git a/client.py b/client.py new file mode 100755 index 0000000..a35948a --- /dev/null +++ b/client.py @@ -0,0 +1,60 @@ +#! /usr/bin/env python3 + +import socket +import argparse + +from contextlib import closing +from json import loads + +def read_log(pid): + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s: + s.connect("/tmp/shelltalk.sock") + s.send(("read {0}\n".format(pid)).encode("utf-8")) + + buf = [] + + while True: + chunk = s.recv(1024) + buf.append(chunk) + if b"\n" in chunk: + break + + return loads(b"".join(buf).decode("utf-8")) + +def spawn(pid): + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s: + s.connect("/tmp/shelltalk.sock") + s.send(("spawn {0}\n".format(pid)).encode("utf-8")) + +def write(pid, msg): + with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s: + s.connect("/tmp/shelltalk.sock") + s.send(("write {0} {1}\n".format(pid, msg)).encode("utf-8")) + +parser = argparse.ArgumentParser() + +parser.add_argument("-S", + "--spawn", + nargs=1, + help="Would you like to use me?") + +parser.add_argument("-W", + "--write", + nargs=2, + help="Would you like to log some things?") + +parser.add_argument("-R", + "--read", + nargs=1, + help="Would you like to read some logs?") + +args = parser.parse_args() + +if args.spawn: + print(spawn(*args.spawn)) + +if args.write: + print(write(*args.write)) + +if args.read: + print(read_log(*args.read)) diff --git a/server.rkt b/server.rkt new file mode 100755 index 0000000..1e34d65 --- /dev/null +++ b/server.rkt @@ -0,0 +1,134 @@ +#! /usr/bin/env racket +#lang racket + +(require racket/unix-socket) +(require json) + +;; Resource management functions + +(define (bail . args) + (displayln "/tmp/shelltalk.sock could not be created (may already exist)" + (current-error-port)) + (exit 1)) + +; All messages come through this socket +; It is cleaned up after execution finishes +(define + control-socket + (with-handlers ([exn:fail? bail]) + (unix-socket-listen "/tmp/shelltalk.sock"))) + +(define (close-socket in out) + (close-output-port out) + (close-input-port in)) + +(define (rm-socket . args) + ; Removes the socket + (parameterize ([current-error-port + (open-output-string)]) + + (system "rm /tmp/shelltalk.sock"))) + + +;; Message handling functions + +(define (write-to entries out) + (with-handlers ([exn:fail? (const '())]) + (write-json entries out) + (display "\n" out))) + +(define (log pid entries) + (match (thread-receive) + [(cons 'read out) + (write-to entries out) + (log pid entries)] + + [entry + (log pid (cons entry entries))])) + +(define (logger-send loggers pid message) + (cond + [(hash-has-key? loggers pid) + (thread-send (hash-ref loggers pid) message)] + [else '()])) + +(define (handle-messages loggers) + (match (thread-receive) + [(list 'log pid entry) + (logger-send loggers pid entry) + (handle-messages loggers)] + + [(cons 'spawn pid) + ;; XXX this should check if it exists already + (handle-messages (hash-set loggers + pid + (thread (lambda () (log pid '[])))))] + + [(cons 'kill pid) + (kill-thread (hash-ref loggers pid)) + (handle-messages + (hash-remove loggers pid))] + + [(list 'read pid out) + (displayln "got read message") + ; Reads all the logs for a given pid + (logger-send loggers pid (cons 'read out)) + (handle-messages loggers)])) + +(define message-handler + (thread (lambda () + (handle-messages + (make-immutable-hash '[]))))) + +(define (handle-connection in out) + (define input-string (read-line in 'linefeed)) + (cond + [(eof-object? input-string) + (close-socket in out)] + [else + (match (string-split input-string) + [(list "spawn" pid) + (displayln "got spawn") + (thread-send message-handler (cons 'spawn pid)) + (handle-connection in out)] + + [(list "read" pid) + (displayln "got read") + (thread-send message-handler (list 'read pid out)) + (handle-connection in out)] + + [(list "write" pid message) + (displayln "got write") + (thread-send message-handler (list 'log pid message)) + (handle-connection in out)] + + [(list "close" pid) + (displayln (format "~a closed" pid)) + (thread-send message-handler (cons 'kill pid)) + (close-socket in out)] + + [other + (displayln other) + (handle-connection in out)])])) + +;; Socket handling + +(define (accept-logs) + (let-values + ([(in out) (unix-socket-accept control-socket)]) + (thread + ; hands the read capability over for this shell instance + (lambda () + (file-stream-buffer-mode out 'none) + (handle-connection in out)))) + (accept-logs)) + +; Start execution +; Use dynamic-wind to ensure socket is always cleaned up +(dynamic-wind + (const '()) + (lambda () + (with-handlers ([exn:break? rm-socket] + [exn:fail? rm-socket]) + (accept-logs))) + rm-socket)