2 # from http://lists.canonical.org/pipermail/kragen-hacks/2004-May/000397.html
3 import struct
, socket
, sys
4 # network block device server, substitute for nbd-server. Probably slower.
5 # But it works! And it's probably a lot easier to improve the
6 # performance of this Python version than of the C version. This
7 # Python version is 14% of the size and perhaps 20% of the features of
8 # the C version. Hmm, that's not so great after all...
11 # - read/write serving up files
13 # - file size detection
14 # - in theory, large file support... not really
18 # - reporting errors to client (in particular writing and reading past end)
19 # - multiple clients (this probably requires copy-on-write or read-only)
22 # - permission tracking
24 # - running from inetd
25 # - filename substitution
26 # - partial file exports
27 # - exports of large files (bigger than 1/4 of RAM)
28 # - manual exportsize specification
30 # - that "split an export file into multiple files" thing that sticks the .0
31 # on the end of your filename
35 class Error(Exception): pass
38 "Buffered socket wrapper; always returns the amount of data you want."
39 def __init__(self
, sock
): self
.sock
= sock
40 def recv(self
, nbytes
):
42 while len(rv
) < nbytes
:
43 more
= self
.sock
.recv(nbytes
- len(rv
))
44 if more
== '': raise Error(nbytes
)
47 def send(self
, astring
): self
.sock
.send(astring
)
48 def close(self
): self
.sock
.close()
52 "Debugging socket wrapper."
53 def __init__(self
, sock
): self
.sock
= sock
54 def recv(self
, nbytes
):
55 print "recv(%d) =" % nbytes
,
56 rv
= self
.sock
.recv(nbytes
)
59 def send(self
, astring
):
60 print "send(%r) =" % astring
,
61 rv
= self
.sock
.send(astring
)
68 def negotiation(exportsize
):
69 "Returns initial NBD negotiation sequence for exportsize in bytes."
70 return ('NBDMAGIC' + '\x00\x00\x42\x02\x81\x86\x12\x53' +
71 struct
.pack('>Q', exportsize
) + '\0' * 128);
73 def nbd_reply(error
=0, handle
=1, data
=''):
74 "Construct an NBD reply."
75 assert type(handle
) is type('') and len(handle
) == 8
76 return ('\x67\x44\x66\x98' + struct
.pack('>L', error
) + handle
+ data
)
78 # possible request types
81 disconnect_request
= 2
84 "Decodes an NBD request off the TCP socket."
85 def __init__(self
, conn
):
88 header
= conn
.recv(struct
.calcsize(template
))
89 (self
.magic
, self
.type, self
.handle
, self
.offset
,
90 self
.len) = struct
.unpack(template
, header
)
91 if self
.magic
!= 0x25609513: raise Error(self
.magic
)
92 if self
.type == write_request
:
93 self
.data
= conn
.recv(self
.len)
94 assert len(self
.data
) == self
.len
95 def reply(self
, error
, data
=''):
96 return nbd_reply(error
=error
, handle
=self
.handle
, data
=data
)
98 return slice(self
.offset
, self
.offset
+ self
.len)
100 def serveclient(asock
, afile
):
101 "Serves a single client until it exits."
103 abuf
= list(afile
.read())
104 asock
.send(negotiation(len(abuf
)))
106 req
= nbd_request(asock
)
107 if req
.type == read_request
:
108 asock
.send(req
.reply(error
=0,
109 data
=''.join(abuf
[req
.range()])))
110 elif req
.type == write_request
:
111 abuf
[req
.range()] = req
.data
112 afile
.seek(req
.offset
)
113 afile
.write(req
.data
)
115 asock
.send(req
.reply(error
=0))
116 elif req
.type == disconnect_request
:
120 def mainloop(listensock
, afile
):
121 "Serves clients forever."
123 (sock
, addr
) = listensock
.accept()
124 print "got conn on", addr
125 serveclient(sock
, afile
)
128 "Given a port and a filename, serves up the file."
129 afile
= file(argv
[2], 'rb+')
130 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
131 sock
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_REUSEADDR
, 1)
132 sock
.bind(('', int(argv
[1])))
134 mainloop(sock
, afile
)
136 if __name__
== '__main__': main(sys
.argv
)