#!/usr/bin/env python2.5

import sys
import os
import shutil
import tempfile
import time
from subprocess import call, check_call, Popen, PIPE

def losetup(source, offset=0):
  # XXX we avoid colliding with other instances of ourself,
  #     but when it comes to other loop-device users we just
  #     pick a range things don't seem to use and hope...
  lockfilename = '/tmp/losetup.lock'
  os.close(os.open(lockfilename, os.O_CREAT+os.O_EXCL)) #lock
  try:
    loopdevice = None
    for i in xrange(32,60): # totally arbitrary, just looks to be unused on black-mesa
      filename = '/dev/loop%d'%i
      if 0 == len(file(filename).read(1)):
        loopdevice = filename # it's empty
        break
    if loopdevice is not None:
      call(['losetup', '-o', str(offset), loopdevice, source])
    else:
      raise RuntimeError('out of loop devices for copying VM image: too many at once?')
  finally:
    os.unlink(lockfilename) #unlock
  return loopdevice
  # XX this means we can duplicate 9 at once.  since it takes around 30 seconds,
  # this seems like an adequate capacity.

def duplicate_lv(source, dest):
  '''OBSOLETE: duplicate boot record, filesystem from LV source to LV dest
  
  source, dest should be device filenames.

  Now we just dd.  Doesn't support resizing, but it's easier,
  especially if we allow the partition table to vary between images.
  '''
  # XXX this is very specific to what the etch sipb-xen-make-iso installer does.
  # XXXX also, it's specific to four gigs.
  # step 1: copy the MBR
  call(['dd', 'if='+source, 'bs=512', 'count=63', 'of='+dest])
  # step 2: fix up partition table
  # XX actually do this; this is our opportunity to resize the filesystem
  # step 3: make the filesystem, and copy its contents in
  sourcefs = losetup(source, 32256)
  destfs = losetup(dest, 32256)
  call(['mkfs.ext3', '-b', '4096', destfs, '987989'])
  tmptree = tempfile.mkdtemp('', 'auto-install.dup.', '/tmp')#yes, args backward
  os.mkdir(tmptree+"/source")
  call(['mount', '-t', 'ext3', '-o', 'ro', sourcefs, tmptree+"/source"])
  os.mkdir(tmptree+"/dest")
  call(['mount', '-t', 'ext3', destfs, tmptree+"/dest"])
  call(['cp', '-aT', tmptree+"/source", tmptree+"/dest"])
  # step 4: twiddle what we want to twiddle
  # XX do this
  # step 5: make the swap area
  swapfs = losetup(dest, 4046870016)
  call(['mkswap', swapfs, str(240943)])
  # call(['losetup', '-d', swapfs])
  print 'losetup -d '+swapfs
  # step 6: clean up.
  # XX actually unmount etc (leaving it is for debugging)
  print 'umount '+tmptree+'/source'
  call(['umount', tmptree+"/dest"]) # needed to flush writes
  print 'losetup -d '+sourcefs
  print 'losetup -d '+destfs

def frob_copy(target, *args):
  '''UNUSED: maybe we'll use this someday; it does isolate the frobber.'''
  # 1. prepare arguments volume
  args_volume = prefix+target+'_args'
  args_device = '/dev/xenvg/' + args_volume
  check_call(['/sbin/lvcreate', 'xenvg', '--name', args_volume, '--size', '4K'])
  file(args_device, 'w').write('\n'.join(args) + '\n')

  # 2. invoke frobber vm
  copier_device = '/dev/xenvg/d_wert_hda'
  check_call(['/usr/sbin/xm', 'create', 'sipb-database',
              'machine_name='+target,
              'disks=' + ' '.join(['phy:'+copier_device+',hda,w',
                                   'phy:'+target_device+',hdc,w',
                                   'phy:'+args_device+',hdd,w'])])

  # XXX should check_call(['/sbin/lvremove', '-f', 'xenvg/'+args_volume])

def frob_copy_simple(target, hostname, rootpw):
  """target should be an LV device filename

  This is highly specific to the etch install produced by sipb-xen-make-iso.
  Generalizing and customizing to other distros is left to the future...
  """
  # 1: mount filesystem
  fs = losetup(target, 32256)
  mntdir = tempfile.mkdtemp('', 'auto-install.frob.', '/tmp')
  call(['mount', '-t', 'ext3', fs, mntdir])
  # 2: do frobbing
  # 2a:  (printf "%s\n" "$ROOTPW"; sleep .3; printf "%s\n" "$ROOTPW") 
  #      | /usr/sbin/chroot "$TARGET" /usr/bin/passwd root
  p = Popen(['/usr/sbin/chroot', mntdir, '/usr/bin/passwd', 'root'], stdin=PIPE)
  p.stdin.write(rootpw+'\n')
  time.sleep(1)
  p.stdin.write(rootpw+'\n')
  p.stdin.close()
  p.wait()
  os.chdir(mntdir)
  # 2b: clear generated file that has eth0's old MAC address
  #      rm $TARGET/etc/udev/rules.d/z25_persistent-net.rules
  os.unlink('etc/udev/rules.d/z25_persistent-net.rules')
  # 2c: hostname.
  # XX Use nullmailer in image, not exim4.  (Fewer copies of hostname.)
  # 2c1: rm /var/lib/dhcp3/dhclient.eth0.leases
  os.unlink('var/lib/dhcp3/dhclient.eth0.leases')
  # 2c2: /etc/hosts, /etc/hostname; /etc/motd? /etc/mailname?
  call(['perl', '-i', '-pe', 's/ice3.servers.csail.mit.edu/'+hostname+'/g',
        'etc/hosts', 'etc/hostname', 'etc/motd', 'etc/mailname'])
  # 3: clean up
  os.chdir('/')
  call(['umount', mntdir])
  os.rmdir(mntdir)
  call(['losetup', '-d', fs])

def duplicate_by_vm(source, target, rootpw, nodd=False):
  # source, target should be machine names
  prefix = 'd_'
  # 1. copy (by dd) source to target
  source_device = '/dev/xenvg/' + prefix + source + '_hda'
  target_device = '/dev/xenvg/' + prefix + target + '_hda'
  if not nodd:
    check_call(['/bin/dd', 'bs=1M', 'conv=nocreat',
                'if='+source_device, 'of='+target_device])
  # 2. frob target
  frob_copy_simple(target_device, target, rootpw)

def main(*argv):
  subcommand = argv[1]
  args = argv[2:]
  os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
  if subcommand == 'lvcopy':
    kwargs = {}
    if args[0] == '--nodd':
      args = args[1:]
      kwargs['nodd'] = True
    duplicate_by_vm(*args, **kwargs)
  else:
    print >>sys.stderr, argv[0]+": unknown subcommand: "+subcommand
    return 2
  return 0

if __name__ == '__main__':
  sys.exit(main(*sys.argv))
