text: #!/usr/bin/python
  import struct, socket, sys
  import os
  # network block device server, substitute for nbd-server.  Probably slower.
  # But it works!  And it's probably a lot easier to improve the
  # performance of this Python version than of the C version.  This
  # Python version is 14% of the size and perhaps 20% of the features of
  # the C version.  Hmm, that's not so great after all...
  # Working:
  # - nbd protocol
  # - read/write serving up files
  # - error handling
  # - file size detection
  # - in theory, large file support... not really
  # - so_reuseaddr
  # - nonforking
  # Missing:
  # - reporting errors to client (in particular writing and reading past end)
  # - multiple clients (this probably requires copy-on-write or read-only)
  # - copy on write
  # - read-only
  # - permission tracking
  # - idle timeouts
  # - running from inetd
  # - filename substitution
  # - partial file exports
  # - exports of large files (bigger than 1/4 of RAM)
  # - manual exportsize specification
  # - so_keepalive
  # - that "split an export file into multiple files" thing that sticks the .0
  #   on the end of your filename
  # - backgrounding
  # - daemonizing
  
  fileSize = 0
  
  class Error(Exception): pass
  
  class buffsock:
      "Buffered socket wrapper; always returns the amount of data you want."
      def __init__(self, sock): self.sock = sock
      def recv(self, nbytes):
          rv = ''
          while len(rv) < nbytes:
              more = self.sock.recv(nbytes - len(rv))
              if more == '': raise Error(nbytes)
              rv += more
          return rv
      def send(self, astring): self.sock.send(astring)
      def close(self): self.sock.close()
              
  
  class debugsock:
      "Debugging socket wrapper."
      def __init__(self, sock): self.sock = sock
      def recv(self, nbytes):
          print "recv(%d) =" % nbytes,
          rv = self.sock.recv(nbytes)
          print `rv`
          return rv
      def send(self, astring):
          print "send(%r) =" % astring,
          rv = self.sock.send(astring)
          print `rv`
          return rv
      def close(self):
          print "close()"
          self.sock.close()
  
  def negotiation(exportsize):
      "Returns initial NBD negotiation sequence for exportsize in bytes."
      return ('NBDMAGIC' + '\x00\x00\x42\x02\x81\x86\x12\x53' +
          struct.pack('>Q', exportsize) + '\0' * 128);
  
  def nbd_reply(error=0, handle=1, data=''):
      "Construct an NBD reply."
      assert type(handle) is type('') and len(handle) == 8
      return ('\x67\x44\x66\x98' + struct.pack('>L', error) + handle + data)
       
  # possible request types
  read_request = 0
  write_request = 1
  disconnect_request = 2
  
  class nbd_request:
      "Decodes an NBD request off the TCP socket."
      def __init__(self, conn):
          conn = buffsock(conn)
          template = '>LL8sQL'
          header = conn.recv(struct.calcsize(template))
          (self.magic, self.type, self.handle, self.offset, 
              self.len) = struct.unpack(template, header)
          if self.magic != 0x25609513: raise Error(self.magic)
          if self.type == write_request: 
              self.data = conn.recv(self.len)
              assert len(self.data) == self.len
      def reply(self, error, data=''):
          return nbd_reply(error=error, handle=self.handle, data=data)
      def range(self):
          return slice(self.offset, self.offset + self.len)
  
  def serveclient(asock, afile):
      "Serves a single client until it exits."
  #    afile.seek(0)
  #    abuf = list(afile.read())
      asock.send(negotiation(fileSize))
      while 1:
          req = nbd_request(asock)
          if req.type == read_request:
              buf = ''
              for i in xrange(0, req.len / 512):
                  po = os.popen("cbits/teststubs %d < %s" % ((req.offset / 512) + i, afile))
                  buf += po.read()
  #            print "%d %d %d" % (req.offset, req.len, len(buf))
              asock.send(req.reply(error=0, data=buf[:req.len]))
  #                data=''.join(abuf[req.range()])))
          elif req.type == write_request:
  #            abuf[req.range()] = req.data
  #            afile.seek(req.offset)
  #            afile.write(req.data)
  #            afile.flush()
              asock.send(req.reply(error=1))
          elif req.type == disconnect_request:
              asock.close()
              return
  
  def mainloop(listensock, afile):
      "Serves clients forever."
      while 1:
          (sock, addr) = listensock.accept()
          print "got conn on", addr
          serveclient(sock, afile)
  
  def main(argv):
      global fileSize
      "Given a port and a filename, serves up the file."
      afile = argv[2] # file(argv[2], 'rb+')
      fileSize = int(argv[3]) 
      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      sock.bind(('', int(argv[1])))
      sock.listen(5)
      mainloop(sock, afile)
  
  if __name__ == '__main__': main(sys.argv)
  
  