#!/usr/bin/python """Remote filesystem server. The idea is that you run this program on a server via, say, ssh, and then send it commands to get read-only access to files on the remote filesystem. Uses URL-encoding for command arguments and results. This sounds outrageous, since it nearly triples the size of random data, but adds up to a modest 20% overhead if you're piping through ssh -C. """ import os import urllib import subprocess import sys def main(): print "Welcome, RFB." while True: print "Ok" sys.stdout.flush() dispatch(raw_input()) def dispatch(line): args = [urllib.unquote(word) for word in line.split()] try: command = args.pop(0) commands[command](*args) except: report_error() def ls(dirname): for filename in os.listdir(dirname): send(filename) def size(filename): send(str(os.stat(filename).st_size)) def read(filename, offset, size): fo = open(filename) fo.seek(int(offset)) send(fo.read(int(size))) commands = { 'ls': ls, 'size': size, 'read': read, } def report_error(): type, value, traceback = sys.exc_info() print '-', urllib.quote(repr(value)) def send(item): print '+', urllib.quote(item) # client code class RPCError(Exception): pass class CommandFailed(RPCError): pass class ConnectionClosed(RPCError): pass class UnparseableLine(RPCError): pass class RPCRFS: def __init__(self, writer, reader): self.writer = writer self.reader = reader while True: line = self.reader.readline() if line == 'Ok\n': break def rpc(self, args): command = ' '.join(urllib.quote(arg) for arg in args) self.writer.write(command + '\n') self.writer.flush() while True: line = self.reader.readline() if not line: raise ConnectionClosed() assert line.endswith('\n') line = line[:-1] if line.startswith('- '): while True: if self.reader.readline() == 'Ok\n': break raise CommandFailed(urllib.unquote(line[2:])) elif line.startswith('+ '): yield urllib.unquote(line[2:]) elif line == 'Ok': return else: raise UnparseableLine(line) def ls(self, directory): return list(self.rpc(['ls', directory])) def size(self, filename): for size in self.rpc(['size', filename]): result = int(size) return result def read(self, filename, offset, size): for chunk in self.rpc(['read', filename, str(offset), str(size)]): pass return chunk def open(self, filename): return RemoteFile(self, filename) def walk(self, top, func): "Ripped from posixpath.walk." try: names = self.ls(top) except CommandFailed: return func(top, names) for name in names: self.walk(top + '/' + name, func) def close(self): self.writer.close() self.reader.close() def rpcrfs(command): popen = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) return RPCRFS(popen.stdin, popen.stdout) class RemoteFile: def __init__(self, connection, filename): self.connection = connection self.filename = filename self.offset = 0 def read(self, bytes): rv = self.connection.read(self.filename, self.offset, bytes) self.offset += len(rv) return rv def sshfs(user_at_host, installed_location): return rpcrfs(['ssh', '-C', user_at_host, installed_location]) if __name__ == '__main__': main()