xref: /haiku/src/add-ons/kernel/drivers/disk/virtual/nbd/nbd-server.py (revision 4d1cc41eb3a998c0661334fa5c4a3baa4ae5461f)
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)