1*4d1cc41eSFrançois Revol#!/usr/bin/python 2*4d1cc41eSFrançois Revol# from http://lists.canonical.org/pipermail/kragen-hacks/2004-May/000397.html 3*4d1cc41eSFrançois Revolimport struct, socket, sys 4*4d1cc41eSFrançois Revol# network block device server, substitute for nbd-server. Probably slower. 5*4d1cc41eSFrançois Revol# But it works! And it's probably a lot easier to improve the 6*4d1cc41eSFrançois Revol# performance of this Python version than of the C version. This 7*4d1cc41eSFrançois Revol# Python version is 14% of the size and perhaps 20% of the features of 8*4d1cc41eSFrançois Revol# the C version. Hmm, that's not so great after all... 9*4d1cc41eSFrançois Revol# Working: 10*4d1cc41eSFrançois Revol# - nbd protocol 11*4d1cc41eSFrançois Revol# - read/write serving up files 12*4d1cc41eSFrançois Revol# - error handling 13*4d1cc41eSFrançois Revol# - file size detection 14*4d1cc41eSFrançois Revol# - in theory, large file support... not really 15*4d1cc41eSFrançois Revol# - so_reuseaddr 16*4d1cc41eSFrançois Revol# - nonforking 17*4d1cc41eSFrançois Revol# Missing: 18*4d1cc41eSFrançois Revol# - reporting errors to client (in particular writing and reading past end) 19*4d1cc41eSFrançois Revol# - multiple clients (this probably requires copy-on-write or read-only) 20*4d1cc41eSFrançois Revol# - copy on write 21*4d1cc41eSFrançois Revol# - read-only 22*4d1cc41eSFrançois Revol# - permission tracking 23*4d1cc41eSFrançois Revol# - idle timeouts 24*4d1cc41eSFrançois Revol# - running from inetd 25*4d1cc41eSFrançois Revol# - filename substitution 26*4d1cc41eSFrançois Revol# - partial file exports 27*4d1cc41eSFrançois Revol# - exports of large files (bigger than 1/4 of RAM) 28*4d1cc41eSFrançois Revol# - manual exportsize specification 29*4d1cc41eSFrançois Revol# - so_keepalive 30*4d1cc41eSFrançois Revol# - that "split an export file into multiple files" thing that sticks the .0 31*4d1cc41eSFrançois Revol# on the end of your filename 32*4d1cc41eSFrançois Revol# - backgrounding 33*4d1cc41eSFrançois Revol# - daemonizing 34*4d1cc41eSFrançois Revol 35*4d1cc41eSFrançois Revolclass Error(Exception): pass 36*4d1cc41eSFrançois Revol 37*4d1cc41eSFrançois Revolclass buffsock: 38*4d1cc41eSFrançois Revol "Buffered socket wrapper; always returns the amount of data you want." 39*4d1cc41eSFrançois Revol def __init__(self, sock): self.sock = sock 40*4d1cc41eSFrançois Revol def recv(self, nbytes): 41*4d1cc41eSFrançois Revol rv = '' 42*4d1cc41eSFrançois Revol while len(rv) < nbytes: 43*4d1cc41eSFrançois Revol more = self.sock.recv(nbytes - len(rv)) 44*4d1cc41eSFrançois Revol if more == '': raise Error(nbytes) 45*4d1cc41eSFrançois Revol rv += more 46*4d1cc41eSFrançois Revol return rv 47*4d1cc41eSFrançois Revol def send(self, astring): self.sock.send(astring) 48*4d1cc41eSFrançois Revol def close(self): self.sock.close() 49*4d1cc41eSFrançois Revol 50*4d1cc41eSFrançois Revol 51*4d1cc41eSFrançois Revolclass debugsock: 52*4d1cc41eSFrançois Revol "Debugging socket wrapper." 53*4d1cc41eSFrançois Revol def __init__(self, sock): self.sock = sock 54*4d1cc41eSFrançois Revol def recv(self, nbytes): 55*4d1cc41eSFrançois Revol print "recv(%d) =" % nbytes, 56*4d1cc41eSFrançois Revol rv = self.sock.recv(nbytes) 57*4d1cc41eSFrançois Revol print `rv` 58*4d1cc41eSFrançois Revol return rv 59*4d1cc41eSFrançois Revol def send(self, astring): 60*4d1cc41eSFrançois Revol print "send(%r) =" % astring, 61*4d1cc41eSFrançois Revol rv = self.sock.send(astring) 62*4d1cc41eSFrançois Revol print `rv` 63*4d1cc41eSFrançois Revol return rv 64*4d1cc41eSFrançois Revol def close(self): 65*4d1cc41eSFrançois Revol print "close()" 66*4d1cc41eSFrançois Revol self.sock.close() 67*4d1cc41eSFrançois Revol 68*4d1cc41eSFrançois Revoldef negotiation(exportsize): 69*4d1cc41eSFrançois Revol "Returns initial NBD negotiation sequence for exportsize in bytes." 70*4d1cc41eSFrançois Revol return ('NBDMAGIC' + '\x00\x00\x42\x02\x81\x86\x12\x53' + 71*4d1cc41eSFrançois Revol struct.pack('>Q', exportsize) + '\0' * 128); 72*4d1cc41eSFrançois Revol 73*4d1cc41eSFrançois Revoldef nbd_reply(error=0, handle=1, data=''): 74*4d1cc41eSFrançois Revol "Construct an NBD reply." 75*4d1cc41eSFrançois Revol assert type(handle) is type('') and len(handle) == 8 76*4d1cc41eSFrançois Revol return ('\x67\x44\x66\x98' + struct.pack('>L', error) + handle + data) 77*4d1cc41eSFrançois Revol 78*4d1cc41eSFrançois Revol# possible request types 79*4d1cc41eSFrançois Revolread_request = 0 80*4d1cc41eSFrançois Revolwrite_request = 1 81*4d1cc41eSFrançois Revoldisconnect_request = 2 82*4d1cc41eSFrançois Revol 83*4d1cc41eSFrançois Revolclass nbd_request: 84*4d1cc41eSFrançois Revol "Decodes an NBD request off the TCP socket." 85*4d1cc41eSFrançois Revol def __init__(self, conn): 86*4d1cc41eSFrançois Revol conn = buffsock(conn) 87*4d1cc41eSFrançois Revol template = '>LL8sQL' 88*4d1cc41eSFrançois Revol header = conn.recv(struct.calcsize(template)) 89*4d1cc41eSFrançois Revol (self.magic, self.type, self.handle, self.offset, 90*4d1cc41eSFrançois Revol self.len) = struct.unpack(template, header) 91*4d1cc41eSFrançois Revol if self.magic != 0x25609513: raise Error(self.magic) 92*4d1cc41eSFrançois Revol if self.type == write_request: 93*4d1cc41eSFrançois Revol self.data = conn.recv(self.len) 94*4d1cc41eSFrançois Revol assert len(self.data) == self.len 95*4d1cc41eSFrançois Revol def reply(self, error, data=''): 96*4d1cc41eSFrançois Revol return nbd_reply(error=error, handle=self.handle, data=data) 97*4d1cc41eSFrançois Revol def range(self): 98*4d1cc41eSFrançois Revol return slice(self.offset, self.offset + self.len) 99*4d1cc41eSFrançois Revol 100*4d1cc41eSFrançois Revoldef serveclient(asock, afile): 101*4d1cc41eSFrançois Revol "Serves a single client until it exits." 102*4d1cc41eSFrançois Revol afile.seek(0) 103*4d1cc41eSFrançois Revol abuf = list(afile.read()) 104*4d1cc41eSFrançois Revol asock.send(negotiation(len(abuf))) 105*4d1cc41eSFrançois Revol while 1: 106*4d1cc41eSFrançois Revol req = nbd_request(asock) 107*4d1cc41eSFrançois Revol if req.type == read_request: 108*4d1cc41eSFrançois Revol asock.send(req.reply(error=0, 109*4d1cc41eSFrançois Revol data=''.join(abuf[req.range()]))) 110*4d1cc41eSFrançois Revol elif req.type == write_request: 111*4d1cc41eSFrançois Revol abuf[req.range()] = req.data 112*4d1cc41eSFrançois Revol afile.seek(req.offset) 113*4d1cc41eSFrançois Revol afile.write(req.data) 114*4d1cc41eSFrançois Revol afile.flush() 115*4d1cc41eSFrançois Revol asock.send(req.reply(error=0)) 116*4d1cc41eSFrançois Revol elif req.type == disconnect_request: 117*4d1cc41eSFrançois Revol asock.close() 118*4d1cc41eSFrançois Revol return 119*4d1cc41eSFrançois Revol 120*4d1cc41eSFrançois Revoldef mainloop(listensock, afile): 121*4d1cc41eSFrançois Revol "Serves clients forever." 122*4d1cc41eSFrançois Revol while 1: 123*4d1cc41eSFrançois Revol (sock, addr) = listensock.accept() 124*4d1cc41eSFrançois Revol print "got conn on", addr 125*4d1cc41eSFrançois Revol serveclient(sock, afile) 126*4d1cc41eSFrançois Revol 127*4d1cc41eSFrançois Revoldef main(argv): 128*4d1cc41eSFrançois Revol "Given a port and a filename, serves up the file." 129*4d1cc41eSFrançois Revol afile = file(argv[2], 'rb+') 130*4d1cc41eSFrançois Revol sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 131*4d1cc41eSFrançois Revol sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 132*4d1cc41eSFrançois Revol sock.bind(('', int(argv[1]))) 133*4d1cc41eSFrançois Revol sock.listen(5) 134*4d1cc41eSFrançois Revol mainloop(sock, afile) 135*4d1cc41eSFrançois Revol 136*4d1cc41eSFrançois Revolif __name__ == '__main__': main(sys.argv)