home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2007 September / PCWSEP07.iso / Software / Linux / Linux Mint 3.0 Light / LinuxMint-3.0-Light.iso / casper / filesystem.squashfs / usr / share / ubiquity / install.py < prev    next >
Encoding:
Python Source  |  2007-05-01  |  59.0 KB  |  1,569 lines

  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3.  
  4. # Copyright (C) 2005 Javier Carranza and others for Guadalinex
  5. # Copyright (C) 2005, 2006 Canonical Ltd.
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  20.  
  21. import sys
  22. import os
  23. import platform
  24. import errno
  25. import stat
  26. import re
  27. import textwrap
  28. import shutil
  29. import subprocess
  30. import time
  31. import struct
  32. import socket
  33. import fcntl
  34. import traceback
  35. import syslog
  36. import gzip
  37. import debconf
  38. import apt_pkg
  39. from apt.cache import Cache
  40. from apt.progress import FetchProgress, InstallProgress
  41.  
  42. sys.path.insert(0, '/usr/lib/ubiquity')
  43.  
  44. from ubiquity import misc
  45. from ubiquity.components import language_apply, apt_setup, timezone_apply, \
  46.                                 clock_setup, console_setup_apply, \
  47.                                 usersetup_apply, hw_detect, check_kernels, \
  48.                                 migrationassistant_apply
  49.  
  50. class DebconfFetchProgress(FetchProgress):
  51.     """An object that reports apt's fetching progress using debconf."""
  52.  
  53.     def __init__(self, db, title, info_starting, info):
  54.         FetchProgress.__init__(self)
  55.         self.db = db
  56.         self.title = title
  57.         self.info_starting = info_starting
  58.         self.info = info
  59.         self.old_capb = None
  60.         self.eta = 0.0
  61.  
  62.     def start(self):
  63.         self.db.progress('START', 0, 100, self.title)
  64.         if self.info_starting is not None:
  65.             self.db.progress('INFO', self.info_starting)
  66.         self.old_capb = self.db.capb()
  67.         capb_list = self.old_capb.split()
  68.         capb_list.append('progresscancel')
  69.         self.db.capb(' '.join(capb_list))
  70.  
  71.     # TODO cjwatson 2006-02-27: implement updateStatus
  72.  
  73.     def pulse(self):
  74.         FetchProgress.pulse(self)
  75.         try:
  76.             self.db.progress('SET', int(self.percent))
  77.         except debconf.DebconfError:
  78.             return False
  79.         if self.eta != 0.0:
  80.             time_str = "%d:%02d" % divmod(int(self.eta), 60)
  81.             self.db.subst(self.info, 'TIME', time_str)
  82.             try:
  83.                 self.db.progress('INFO', self.info)
  84.             except debconf.DebconfError:
  85.                 return False
  86.         return True
  87.  
  88.     def stop(self):
  89.         if self.old_capb is not None:
  90.             self.db.capb(self.old_capb)
  91.             self.old_capb = None
  92.             self.db.progress('STOP')
  93.  
  94. class DebconfInstallProgress(InstallProgress):
  95.     """An object that reports apt's installation progress using debconf."""
  96.  
  97.     def __init__(self, db, title, info, error=None):
  98.         InstallProgress.__init__(self)
  99.         self.db = db
  100.         self.title = title
  101.         self.info = info
  102.         self.error_template = error
  103.         self.started = False
  104.         # InstallProgress uses a non-blocking status fd; our run()
  105.         # implementation doesn't need that, and in fact we spin unless the
  106.         # fd is blocking.
  107.         flags = fcntl.fcntl(self.statusfd.fileno(), fcntl.F_GETFL)
  108.         fcntl.fcntl(self.statusfd.fileno(), fcntl.F_SETFL,
  109.                     flags & ~os.O_NONBLOCK)
  110.  
  111.     def startUpdate(self):
  112.         self.db.progress('START', 0, 100, self.title)
  113.         self.started = True
  114.  
  115.     def error(self, pkg, errormsg):
  116.         if self.error_template is not None:
  117.             self.db.subst(self.error_template, 'PACKAGE', pkg)
  118.             self.db.subst(self.error_template, 'MESSAGE', errormsg)
  119.             self.db.input('critical', self.error_template)
  120.             self.db.go()
  121.  
  122.     def statusChange(self, pkg, percent, status):
  123.         self.percent = percent
  124.         self.status = status
  125.         self.db.progress('SET', int(percent))
  126.         self.db.subst(self.info, 'DESCRIPTION', status)
  127.         self.db.progress('INFO', self.info)
  128.  
  129.     def updateInterface(self):
  130.         # TODO cjwatson 2006-02-28: InstallProgress.updateInterface doesn't
  131.         # give us a handy way to spot when percentages/statuses change and
  132.         # aren't pmerror/pmconffile, so we have to reimplement it here.
  133.         if self.statusfd is None:
  134.             return False
  135.         try:
  136.             while not self.read.endswith("\n"):
  137.                 r = os.read(self.statusfd.fileno(),1)
  138.                 if not r:
  139.                     return False
  140.                 self.read += r
  141.         except OSError, (err,errstr):
  142.             print errstr
  143.         if self.read.endswith("\n"):
  144.             s = self.read
  145.             (status, pkg, percent, status_str) = s.split(":", 3)
  146.             if status == "pmerror":
  147.                 self.error(pkg, status_str)
  148.             elif status == "pmconffile":
  149.                 # we get a string like this:
  150.                 # 'current-conffile' 'new-conffile' useredited distedited
  151.                 match = re.compile("\s*\'(.*)\'\s*\'(.*)\'.*").match(status_str)
  152.                 if match:
  153.                     self.conffile(match.group(1), match.group(2))
  154.             else:
  155.                 self.statusChange(pkg, float(percent), status_str.strip())
  156.             self.read = ""
  157.         return True
  158.  
  159.     def run(self, pm):
  160.         # Create a subprocess to deal with turning apt status messages into
  161.         # debconf protocol messages.
  162.         child_pid = self.fork()
  163.         if child_pid == 0:
  164.             # child
  165.             os.close(self.writefd)
  166.             try:
  167.                 while self.updateInterface():
  168.                     pass
  169.             except (KeyboardInterrupt, SystemExit):
  170.                 pass # we're going to exit anyway
  171.             except:
  172.                 for line in traceback.format_exc().split('\n'):
  173.                     syslog.syslog(syslog.LOG_WARNING, line)
  174.             os._exit(0)
  175.  
  176.         self.statusfd.close()
  177.  
  178.         # Redirect stdout to stderr to avoid it interfering with our
  179.         # debconf protocol stream.
  180.         saved_stdout = os.dup(1)
  181.         os.dup2(2, 1)
  182.  
  183.         # Make sure all packages are installed non-interactively. We
  184.         # don't have enough passthrough magic here to deal with any
  185.         # debconf questions they might ask.
  186.         saved_environ_keys = ('DEBIAN_FRONTEND', 'DEBIAN_HAS_FRONTEND',
  187.                               'DEBCONF_USE_CDEBCONF')
  188.         saved_environ = {}
  189.         for key in saved_environ_keys:
  190.             if key in os.environ:
  191.                 saved_environ[key] = os.environ[key]
  192.         os.environ['DEBIAN_FRONTEND'] = 'noninteractive'
  193.         if 'DEBIAN_HAS_FRONTEND' in os.environ:
  194.             del os.environ['DEBIAN_HAS_FRONTEND']
  195.         if 'DEBCONF_USE_CDEBCONF' in os.environ:
  196.             # Probably not a good idea to use this in /target too ...
  197.             del os.environ['DEBCONF_USE_CDEBCONF']
  198.  
  199.         res = pm.ResultFailed
  200.         try:
  201.             res = pm.DoInstall(self.writefd)
  202.         finally:
  203.             # Reap the status-to-debconf subprocess.
  204.             os.close(self.writefd)
  205.             while True:
  206.                 try:
  207.                     (pid, status) = os.waitpid(child_pid, 0)
  208.                     if pid != child_pid:
  209.                         break
  210.                     if os.WIFEXITED(status) or os.WIFSIGNALED(status):
  211.                         break
  212.                 except OSError:
  213.                     break
  214.  
  215.             # Put back stdout.
  216.             os.dup2(saved_stdout, 1)
  217.             os.close(saved_stdout)
  218.  
  219.             # Put back the environment.
  220.             for key in saved_environ_keys:
  221.                 if key in saved_environ:
  222.                     os.environ[key] = saved_environ[key]
  223.                 elif key in os.environ:
  224.                     del os.environ[key]
  225.  
  226.         return res
  227.  
  228.     def finishUpdate(self):
  229.         if self.started:
  230.             self.db.progress('STOP')
  231.             self.started = False
  232.  
  233. class InstallStepError(Exception):
  234.     """Raised when an install step fails.
  235.  
  236.     Attributes:
  237.         message -- message returned with exception
  238.  
  239.     """
  240.  
  241.     def __init__(self, message):
  242.         Exception.__init__(self, message)
  243.         self.message = message
  244.  
  245. class Install:
  246.  
  247.     def __init__(self):
  248.         """Initial attributes."""
  249.  
  250.         if os.path.isdir('/rofs'):
  251.             self.source = '/rofs'
  252.         elif os.path.isdir('/UNIONFS'):
  253.             # Klaus Knopper says this may not actually work very well
  254.             # because it'll copy the WHOLE WORLD (~12GB).
  255.             self.source = '/UNIONFS'
  256.         else:
  257.             self.source = '/var/lib/ubiquity/source'
  258.         self.target = '/target'
  259.         self.kernel_version = platform.release()
  260.         self.db = debconf.Debconf()
  261.  
  262.         apt_pkg.InitConfig()
  263.         apt_pkg.Config.Set("Dir", "/target")
  264.         apt_pkg.Config.Set("Dir::State::status", "/target/var/lib/dpkg/status")
  265.         apt_pkg.Config.Set("APT::GPGV::TrustedKeyring",
  266.                            "/target/etc/apt/trusted.gpg")
  267.         apt_pkg.Config.Set("Acquire::gpgv::Options::",
  268.                            "--ignore-time-conflict")
  269.         apt_pkg.Config.Set("DPkg::Options::", "--root=/target")
  270.         # We don't want apt-listchanges or dpkg-preconfigure, so just clear
  271.         # out the list of pre-installation hooks.
  272.         apt_pkg.Config.Clear("DPkg::Pre-Install-Pkgs")
  273.         apt_pkg.InitSystem()
  274.  
  275.     def excepthook(self, exctype, excvalue, exctb):
  276.         """Crash handler. Dump the traceback to a file so that it can be
  277.         read by the caller."""
  278.  
  279.         if (issubclass(exctype, KeyboardInterrupt) or
  280.             issubclass(exctype, SystemExit)):
  281.             return
  282.  
  283.         tbtext = ''.join(traceback.format_exception(exctype, excvalue, exctb))
  284.         syslog.syslog(syslog.LOG_ERR, "Exception during installation:")
  285.         for line in tbtext.split('\n'):
  286.             syslog.syslog(syslog.LOG_ERR, line)
  287.         tbfile = open('/var/lib/ubiquity/install.trace', 'w')
  288.         print >>tbfile, tbtext
  289.         tbfile.close()
  290.  
  291.         sys.exit(1)
  292.  
  293.     def run(self):
  294.         """Run the install stage: copy everything to the target system, then
  295.         configure it as necessary."""
  296.  
  297.         self.db.progress('START', 0, 100, 'ubiquity/install/title')
  298.         self.db.progress('INFO', 'ubiquity/install/mounting_source')
  299.  
  300.         try:
  301.             if self.source == '/var/lib/ubiquity/source':
  302.                 self.mount_source()
  303.  
  304.             self.db.progress('SET', 1)
  305.             self.db.progress('REGION', 1, 75)
  306.             try:
  307.                 self.copy_all()
  308.             except EnvironmentError, e:
  309.                 if e.errno in (errno.ENOENT, errno.EIO, errno.EFAULT,
  310.                                errno.ENOTDIR, errno.EROFS):
  311.                     if e.filename is None:
  312.                         error_template = 'cd_hd_fault'
  313.                     elif e.filename.startswith('/target'):
  314.                         error_template = 'hd_fault'
  315.                     else:
  316.                         error_template = 'cd_fault'
  317.                     error_template = ('ubiquity/install/copying_error/%s' %
  318.                                       error_template)
  319.                     self.db.subst(error_template, 'ERROR', str(e))
  320.                     self.db.input('critical', error_template)
  321.                     self.db.go()
  322.                     # Exit code 3 signals to the frontend that we have
  323.                     # handled this error.
  324.                     sys.exit(3)
  325.                 else:
  326.                     raise
  327.  
  328.             self.db.progress('SET', 75)
  329.             self.db.progress('REGION', 75, 76)
  330.             self.db.progress('INFO', 'ubiquity/install/locales')
  331.             self.configure_locales()
  332.  
  333.             self.db.progress('SET', 76)
  334.             self.db.progress('REGION', 76, 77)
  335.             self.db.progress('INFO', 'ubiquity/install/user')
  336.             self.configure_user()
  337.  
  338.             self.db.progress('SET', 77)
  339.             self.db.progress('REGION', 77, 78)
  340.             self.run_target_config_hooks()
  341.  
  342.             self.db.progress('SET', 78)
  343.             self.db.progress('REGION', 78, 79)
  344.             self.db.progress('INFO', 'ubiquity/install/network')
  345.             self.configure_network()
  346.  
  347.             self.db.progress('SET', 79)
  348.             self.db.progress('REGION', 79, 80)
  349.             self.db.progress('INFO', 'ubiquity/install/apt')
  350.             self.configure_apt()
  351.  
  352.             #self.db.progress('SET', 80)
  353.             #self.db.progress('REGION', 80, 85)
  354.             # Ignore failures from language pack installation.
  355.             #try:
  356.             #    self.install_language_packs()
  357.             #except InstallStepError:
  358.             #    pass
  359.             #except IOError:
  360.             #    pass
  361.             #except SystemError:
  362.             #    pass
  363.  
  364.             self.db.progress('SET', 85)
  365.             self.db.progress('REGION', 85, 86)
  366.             self.db.progress('INFO', 'ubiquity/install/timezone')
  367.             self.configure_timezone()
  368.  
  369.             self.db.progress('SET', 86)
  370.             self.db.progress('REGION', 86, 87)
  371.             self.db.progress('INFO', 'ubiquity/install/keyboard')
  372.             self.configure_keyboard()
  373.  
  374.             self.db.progress('SET', 87)
  375.             self.db.progress('REGION', 87, 88)
  376.             self.db.progress('INFO', 'ubiquity/install/migrationassistant')
  377.             self.configure_ma()
  378.  
  379.             self.db.progress('SET', 88)
  380.             self.db.progress('REGION', 88, 89)
  381.             self.remove_unusable_kernels()
  382.  
  383.             self.db.progress('SET', 89)
  384.             self.db.progress('REGION', 89, 93)
  385.             self.db.progress('INFO', 'ubiquity/install/hardware')
  386.             self.configure_hardware()
  387.  
  388.             self.db.progress('SET', 93)
  389.             self.db.progress('REGION', 93, 94)
  390.             self.db.progress('INFO', 'ubiquity/install/bootloader')
  391.             self.configure_bootloader()
  392.  
  393.             self.db.progress('SET', 94)
  394.             self.db.progress('REGION', 94, 99)
  395.             self.db.progress('INFO', 'ubiquity/install/removing')
  396.             self.remove_extras()
  397.  
  398.             self.db.progress('SET', 99)
  399.             self.db.progress('INFO', 'ubiquity/install/log_files')
  400.             self.copy_logs()
  401.  
  402.             self.db.progress('SET', 100)
  403.         finally:
  404.             self.cleanup()
  405.             try:
  406.                 self.db.progress('STOP')
  407.             except (KeyboardInterrupt, SystemExit):
  408.                 raise
  409.             except:
  410.                 pass
  411.  
  412.  
  413.     def copy_all(self):
  414.         """Core copy process. This is the most important step of this
  415.         stage. It clones live filesystem into a local partition in the
  416.         selected hard disk."""
  417.  
  418.         files = []
  419.         total_size = 0
  420.  
  421.         self.db.progress('START', 0, 100, 'ubiquity/install/title')
  422.         self.db.progress('INFO', 'ubiquity/install/scanning')
  423.  
  424.         # Obviously doing os.walk() twice is inefficient, but I'd rather not
  425.         # suck the list into ubiquity's memory, and I'm guessing that the
  426.         # kernel's dentry cache will avoid most of the slowness anyway.
  427.         walklen = 0
  428.         for entry in os.walk(self.source):
  429.             walklen += 1
  430.         walkpos = 0
  431.         walkprogress = 0
  432.  
  433.         for dirpath, dirnames, filenames in os.walk(self.source):
  434.             walkpos += 1
  435.             if int(float(walkpos) / walklen * 10) != walkprogress:
  436.                 walkprogress = int(float(walkpos) / walklen * 10)
  437.                 self.db.progress('SET', walkprogress)
  438.  
  439.             sourcepath = dirpath[len(self.source) + 1:]
  440.  
  441.             for name in dirnames + filenames:
  442.                 relpath = os.path.join(sourcepath, name)
  443.                 fqpath = os.path.join(self.source, dirpath, name)
  444.  
  445.                 total_size += os.lstat(fqpath).st_size
  446.                 files.append(relpath)
  447.  
  448.         self.db.progress('SET', 10)
  449.         self.db.progress('INFO', 'ubiquity/install/copying')
  450.  
  451.         # Progress bar handling:
  452.         # We sample progress every half-second (assuming time.time() gives
  453.         # us sufficiently good granularity) and use the average of progress
  454.         # over the last minute or so to decide how much time remains. We
  455.         # don't bother displaying any progress for the first ten seconds in
  456.         # order to allow things to settle down, and we only update the "time
  457.         # remaining" indicator at most every two seconds after that.
  458.  
  459.         copy_progress = 0
  460.         copied_size, counter = 0, 0
  461.         directory_times = []
  462.         time_start = time.time()
  463.         times = [(time_start, copied_size)]
  464.         long_enough = False
  465.         time_last_update = time_start
  466.  
  467.         old_umask = os.umask(0)
  468.         for path in files:
  469.             sourcepath = os.path.join(self.source, path)
  470.             targetpath = os.path.join(self.target, path)
  471.             st = os.lstat(sourcepath)
  472.             mode = stat.S_IMODE(st.st_mode)
  473.             if stat.S_ISLNK(st.st_mode):
  474.                 if not os.path.lexists(targetpath):
  475.                     linkto = os.readlink(sourcepath)
  476.                     os.symlink(linkto, targetpath)
  477.             elif stat.S_ISDIR(st.st_mode):
  478.                 if not os.path.isdir(targetpath):
  479.                     os.mkdir(targetpath, mode)
  480.             elif stat.S_ISCHR(st.st_mode):
  481.                 os.mknod(targetpath, stat.S_IFCHR | mode, st.st_rdev)
  482.             elif stat.S_ISBLK(st.st_mode):
  483.                 os.mknod(targetpath, stat.S_IFBLK | mode, st.st_rdev)
  484.             elif stat.S_ISFIFO(st.st_mode):
  485.                 os.mknod(targetpath, stat.S_IFIFO | mode)
  486.             elif stat.S_ISSOCK(st.st_mode):
  487.                 os.mknod(targetpath, stat.S_IFSOCK | mode)
  488.             elif stat.S_ISREG(st.st_mode):
  489.                 if not os.path.exists(targetpath):
  490.                     shutil.copyfile(sourcepath, targetpath)
  491.  
  492.             copied_size += st.st_size
  493.             os.lchown(targetpath, st.st_uid, st.st_gid)
  494.             if not stat.S_ISLNK(st.st_mode):
  495.                 os.chmod(targetpath, mode)
  496.             if stat.S_ISDIR(st.st_mode):
  497.                 directory_times.append((targetpath, st.st_atime, st.st_mtime))
  498.             # os.utime() sets timestamp of target, not link
  499.             elif not stat.S_ISLNK(st.st_mode):
  500.                 os.utime(targetpath, (st.st_atime, st.st_mtime))
  501.  
  502.             if int((copied_size * 90) / total_size) != copy_progress:
  503.                 copy_progress = int((copied_size * 90) / total_size)
  504.                 self.db.progress('SET', 10 + copy_progress)
  505.  
  506.             time_now = time.time()
  507.             if (time_now - times[-1][0]) >= 0.5:
  508.                 times.append((time_now, copied_size))
  509.                 if not long_enough and time_now - times[0][0] >= 10:
  510.                     long_enough = True
  511.                 if long_enough and time_now - time_last_update >= 2:
  512.                     time_last_update = time_now
  513.                     while (time_now - times[0][0] > 60 and
  514.                            time_now - times[1][0] >= 60):
  515.                         times.pop(0)
  516.                     speed = ((times[-1][1] - times[0][1]) /
  517.                              (times[-1][0] - times[0][0]))
  518.                     time_remaining = int((total_size - copied_size) / speed)
  519.                     if time_remaining < 60:
  520.                         self.db.progress(
  521.                             'INFO', 'ubiquity/install/copying_minute')
  522.  
  523.         # Apply timestamps to all directories now that the items within them
  524.         # have been copied.
  525.         for dirtime in directory_times:
  526.             (directory, atime, mtime) = dirtime
  527.             try:
  528.                 os.utime(directory, (atime, mtime))
  529.             except OSError:
  530.                 # I have no idea why I've been getting lots of bug reports
  531.                 # about this failing, but I really don't care. Ignore it.
  532.                 pass
  533.  
  534.         os.umask(old_umask)
  535.  
  536.         self.db.progress('SET', 100)
  537.         self.db.progress('STOP')
  538.  
  539.  
  540.     def copy_logs(self):
  541.         """copy log files into installed system."""
  542.  
  543.         target_dir = os.path.join(self.target, 'var/log/installer')
  544.         if not os.path.exists(target_dir):
  545.             os.makedirs(target_dir)
  546.  
  547.         for log_file in ('/var/log/syslog', '/var/log/partman',
  548.                          '/var/log/installer/version'):
  549.             target_log_file = os.path.join(target_dir,
  550.                                            os.path.basename(log_file))
  551.             if not misc.ex('cp', '-a', log_file, target_log_file):
  552.                 syslog.syslog(syslog.LOG_ERR,
  553.                               'Failed to copy installation log file')
  554.             os.chmod(target_log_file, stat.S_IRUSR | stat.S_IWUSR)
  555.         try:
  556.             status = open(os.path.join(self.target, 'var/lib/dpkg/status'))
  557.             status_gz = gzip.open(os.path.join(target_dir,
  558.                                                'initial-status.gz'), 'w')
  559.             while True:
  560.                 data = status.read(65536)
  561.                 if not data:
  562.                     break
  563.                 status_gz.write(data)
  564.             status_gz.close()
  565.             status.close()
  566.         except IOError:
  567.             pass
  568.  
  569.  
  570.     def mount_one_image(self, fsfile, mountpoint=None):
  571.         if os.path.splitext(fsfile)[1] == '.cloop':
  572.             blockdev_prefix = 'cloop'
  573.         elif os.path.splitext(fsfile)[1] == '.squashfs':
  574.             blockdev_prefix = 'loop'
  575.  
  576.         if blockdev_prefix == '':
  577.             raise InstallStepError("No source device found for %s" % fsfile)
  578.  
  579.         dev = ''
  580.         sysloops = filter(lambda x: x.startswith(blockdev_prefix),
  581.                           os.listdir('/sys/block'))
  582.         sysloops.sort()
  583.         for sysloop in sysloops:
  584.             try:
  585.                 sysloopf = open(os.path.join('/sys/block', sysloop, 'size'))
  586.                 sysloopsize = sysloopf.readline().strip()
  587.                 sysloopf.close()
  588.                 if sysloopsize == '0':
  589.                     devnull = open('/dev/null')
  590.                     udevinfo = subprocess.Popen(
  591.                         ['udevinfo', '-q', 'name',
  592.                          '-p', os.path.join('/block', sysloop)],
  593.                         stdout=subprocess.PIPE, stderr=devnull)
  594.                     devbase = udevinfo.communicate()[0]
  595.                     devnull.close()
  596.                     if udevinfo.returncode != 0:
  597.                         devbase = sysloop
  598.                     dev = '/dev/%s' % devbase
  599.                     break
  600.             except:
  601.                 continue
  602.  
  603.         if dev == '':
  604.             raise InstallStepError("No loop device available for %s" % fsfile)
  605.  
  606.         misc.ex('losetup', dev, fsfile)
  607.         if mountpoint is None:
  608.             mountpoint = '/var/lib/ubiquity/%s' % sysloop
  609.         if not os.path.isdir(mountpoint):
  610.             os.mkdir(mountpoint)
  611.         misc.ex('mount', dev, mountpoint)
  612.  
  613.         return (dev, mountpoint)
  614.  
  615.     def mount_source(self):
  616.         """mounting loop system from cloop or squashfs system."""
  617.  
  618.         self.devs = []
  619.         self.mountpoints = []
  620.  
  621.         if not os.path.isdir(self.source):
  622.             syslog.syslog('mkdir %s' % self.source)
  623.             os.mkdir(self.source)
  624.  
  625.         fs_preseed = self.db.get('ubiquity/install/filesystem-images')
  626.  
  627.         if fs_preseed == '':
  628.             # Simple autodetection on unionfs systems
  629.             mounts = open('/proc/mounts')
  630.             for line in mounts:
  631.                 (device, fstype) = line.split()[1:3]
  632.                 if fstype == 'squashfs' and os.path.exists(device):
  633.                     misc.ex('mount', '--bind', device, self.source)
  634.                     self.mountpoints.append(self.source)
  635.                     mounts.close()
  636.                     return
  637.             mounts.close()
  638.  
  639.             # Manual detection on non-unionfs systems
  640.             fsfiles = ['/cdrom/casper/filesystem.cloop',
  641.                        '/cdrom/casper/filesystem.squashfs',
  642.                        '/cdrom/META/META.squashfs']
  643.  
  644.             for fsfile in fsfiles:
  645.                 if fsfile != '' and os.path.isfile(fsfile):
  646.                     dev, mountpoint = self.mount_one_image(fsfile, self.source)
  647.                     self.devs.append(dev)
  648.                     self.mountpoints.append(mountpoint)
  649.  
  650.         elif len(fs_preseed.split()) == 1:
  651.             # Just one preseeded image.
  652.             if not os.path.isfile(fs_preseed):
  653.                 raise InstallStepError(
  654.                     "Preseeded filesystem image %s not found" % fs_preseed)
  655.  
  656.                 dev, mountpoint = self.mount_one_image(fsfile, self.source)
  657.                 self.devs.append(dev)
  658.                 self.mountpoints.append(mountpoint)
  659.         else:
  660.             # OK, so we need to mount multiple images and unionfs them
  661.             # together.
  662.             for fsfile in fs_preseed.split():
  663.                 if not os.path.isfile(fsfile):
  664.                     raise InstallStepError(
  665.                         "Preseeded filesystem image %s not found" % fsfile)
  666.  
  667.                 dev, mountpoint = self.mount_one_image(fsfile)
  668.                 self.devs.append(dev)
  669.                 self.mountpoints.append(mountpoint)
  670.  
  671.             assert self.devs
  672.             assert self.mountpoints
  673.  
  674.             misc.ex('mount', '-t', 'unionfs', '-o',
  675.                     'dirs=' + map(lambda x: '%s=ro' % x, self.mountpoints),
  676.                     'unionfs', self.source)
  677.             self.mountpoints.append(self.source)
  678.  
  679.     def umount_source(self):
  680.         """umounting loop system from cloop or squashfs system."""
  681.  
  682.         devs = self.devs
  683.         devs.reverse()
  684.         mountpoints = self.mountpoints
  685.         mountpoints.reverse()
  686.  
  687.         for mountpoint in mountpoints:
  688.             if not misc.ex('umount', mountpoint):
  689.                 raise InstallStepError("Failed to unmount %s" % mountpoint)
  690.         for dev in devs:
  691.             if dev != '' and not misc.ex('losetup', '-d', dev):
  692.                 raise InstallStepError(
  693.                     "Failed to detach loopback device %s" % dev)
  694.  
  695.  
  696.     def chroot_setup(self):
  697.         """Set up /target for safe package management operations."""
  698.         policy_rc_d = os.path.join(self.target, 'usr/sbin/policy-rc.d')
  699.         f = open(policy_rc_d, 'w')
  700.         print >>f, """\
  701. #!/bin/sh
  702. exit 101"""
  703.         f.close()
  704.         os.chmod(policy_rc_d, 0755)
  705.  
  706.         start_stop_daemon = os.path.join(self.target, 'sbin/start-stop-daemon')
  707.         if os.path.exists(start_stop_daemon):
  708.             os.rename(start_stop_daemon, '%s.REAL' % start_stop_daemon)
  709.         f = open(start_stop_daemon, 'w')
  710.         print >>f, """\
  711. #!/bin/sh
  712. echo 1>&2
  713. echo 'Warning: Fake start-stop-daemon called, doing nothing.' 1>&2
  714. exit 0"""
  715.         f.close()
  716.         os.chmod(start_stop_daemon, 0755)
  717.  
  718.         if not os.path.exists(os.path.join(self.target, 'proc/cmdline')):
  719.             self.chrex('mount', '-t', 'proc', 'proc', '/proc')
  720.         if not os.path.exists(os.path.join(self.target, 'sys/devices')):
  721.             self.chrex('mount', '-t', 'sysfs', 'sysfs', '/sys')
  722.  
  723.     def chroot_cleanup(self):
  724.         """Undo the work done by chroot_setup."""
  725.         self.chrex('umount', '/sys')
  726.         self.chrex('umount', '/proc')
  727.  
  728.         start_stop_daemon = os.path.join(self.target, 'sbin/start-stop-daemon')
  729.         os.rename('%s.REAL' % start_stop_daemon, start_stop_daemon)
  730.  
  731.         policy_rc_d = os.path.join(self.target, 'usr/sbin/policy-rc.d')
  732.         os.unlink(policy_rc_d)
  733.  
  734.  
  735.     def run_target_config_hooks(self):
  736.         """Run hook scripts from /usr/lib/ubiquity/target-config. This allows
  737.         casper to hook into us and repeat bits of its configuration in the
  738.         target system."""
  739.  
  740.         hookdir = '/usr/lib/ubiquity/target-config'
  741.  
  742.         if os.path.isdir(hookdir):
  743.             # Exclude hooks containing '.', so that *.dpkg-* et al are avoided.
  744.             hooks = filter(lambda entry: '.' not in entry, os.listdir(hookdir))
  745.             self.db.progress('START', 0, len(hooks), 'ubiquity/install/title')
  746.             self.db.progress('INFO', 'ubiquity/install/target_hooks')
  747.             for hookentry in hooks:
  748.                 hook = os.path.join(hookdir, hookentry)
  749.                 if not os.access(hook, os.X_OK):
  750.                     self.db.progress('STEP', 1)
  751.                     continue
  752.                 # Errors are ignored at present, although this may change.
  753.                 subprocess.call(['log-output', '-t', 'ubiquity',
  754.                                  '--pass-stdout', hook])
  755.                 self.db.progress('STEP', 1)
  756.             self.db.progress('STOP')
  757.  
  758.  
  759.     def configure_locales(self):
  760.         """Apply locale settings to installed system."""
  761.         dbfilter = language_apply.LanguageApply(None)
  762.         ret = dbfilter.run_command(auto_process=True)
  763.         if ret != 0:
  764.             raise InstallStepError("LanguageApply failed with code %d" % ret)
  765.  
  766.         # fontconfig configuration needs to be adjusted based on the
  767.         # selected locale (from language-selector-common.postinst). Ignore
  768.         # errors.
  769.         self.chrex('fontconfig-voodoo', '--auto', '--quiet')
  770.  
  771.  
  772.     def configure_apt(self):
  773.         """Configure /etc/apt/sources.list."""
  774.  
  775.         # Avoid clock skew causing gpg verification issues.
  776.         # This file will be left in place until the end of the install.
  777.         apt_conf_itc = open(os.path.join(
  778.             self.target, 'etc/apt/apt.conf.d/00IgnoreTimeConflict'), 'w')
  779.         print >>apt_conf_itc, ('Acquire::gpgv::Options {'
  780.                                ' "--ignore-time-conflict"; };')
  781.         apt_conf_itc.close()
  782.  
  783.         dbfilter = apt_setup.AptSetup(None)
  784.         ret = dbfilter.run_command(auto_process=True)
  785.         if ret != 0:
  786.             raise InstallStepError("AptSetup failed with code %d" % ret)
  787.  
  788.  
  789.     def get_cache_pkg(self, cache, pkg):
  790.         # work around broken has_key in python-apt 0.6.16
  791.         try:
  792.             return cache[pkg]
  793.         except KeyError:
  794.             return None
  795.  
  796.  
  797.     def record_installed(self, pkgs):
  798.         """Record which packages we've explicitly installed so that we don't
  799.         try to remove them later."""
  800.  
  801.         record_file = "/var/lib/ubiquity/apt-installed"
  802.         if not os.path.exists(os.path.dirname(record_file)):
  803.             os.makedirs(os.path.dirname(record_file))
  804.         record = open(record_file, "a")
  805.  
  806.         for pkg in pkgs:
  807.             print >>record, pkg
  808.  
  809.         record.close()
  810.  
  811.  
  812.     def mark_install(self, cache, pkg):
  813.         cachedpkg = self.get_cache_pkg(cache, pkg)
  814.         if cachedpkg is not None and not cachedpkg.isInstalled:
  815.             apt_error = False
  816.             try:
  817.                 cachedpkg.markInstall()
  818.             except SystemError:
  819.                 apt_error = True
  820.             if cache._depcache.BrokenCount > 0 or apt_error:
  821.                 brokenpkgs = self.broken_packages(cache)
  822.                 while brokenpkgs:
  823.                     for brokenpkg in brokenpkgs:
  824.                         self.get_cache_pkg(cache, brokenpkg).markKeep()
  825.                     new_brokenpkgs = self.broken_packages(cache)
  826.                     if brokenpkgs == new_brokenpkgs:
  827.                         break # we can do nothing more
  828.                     brokenpkgs = new_brokenpkgs
  829.                 assert cache._depcache.BrokenCount == 0
  830.  
  831.  
  832.     def install_language_packs(self):
  833.         langpacks = []
  834.         try:
  835.             langpack_db = self.db.get('pkgsel/language-packs')
  836.             langpacks = langpack_db.replace(',', '').split()
  837.         except debconf.DebconfError:
  838.             pass
  839.         if not langpacks:
  840.             try:
  841.                 langpack_db = self.db.get('localechooser/supported-locales')
  842.                 langpack_set = set()
  843.                 for locale in langpack_db.replace(',', '').split():
  844.                     langpack_set.add(locale.split('_')[0])
  845.                 langpacks = sorted(langpack_set)
  846.             except debconf.DebconfError:
  847.                 pass
  848.         if not langpacks:
  849.             langpack_db = self.db.get('debian-installer/locale')
  850.             langpacks = [langpack_db.split('_')[0]]
  851.         syslog.syslog('keeping language packs for: %s' % ' '.join(langpacks))
  852.  
  853.         try:
  854.             lppatterns = self.db.get('pkgsel/language-pack-patterns').split()
  855.         except debconf.DebconfError:
  856.             return
  857.  
  858.         to_install = []
  859.         for lp in langpacks:
  860.             # Basic language packs, required to get localisation working at
  861.             # all. We install these almost unconditionally; if you want to
  862.             # get rid of even these, you can preseed pkgsel/language-packs
  863.             # to the empty string.
  864.             to_install.append('language-pack-%s' % lp)
  865.             # Other language packs, typically selected by preseeding.
  866.             for pattern in lppatterns:
  867.                 to_install.append(pattern.replace('$LL', lp))
  868.             # More extensive language support packages.
  869.             to_install.append('language-support-%s' % lp)
  870.         self.record_installed(to_install)
  871.  
  872.         self.db.progress('START', 0, 100, 'ubiquity/langpacks/title')
  873.  
  874.         self.db.progress('REGION', 0, 10)
  875.         fetchprogress = DebconfFetchProgress(
  876.             self.db, 'ubiquity/langpacks/title',
  877.             'ubiquity/install/apt_indices_starting',
  878.             'ubiquity/install/apt_indices')
  879.         cache = Cache()
  880.  
  881.         if cache._depcache.BrokenCount > 0:
  882.             syslog.syslog(
  883.                 'not installing language packs, since there are broken '
  884.                 'packages: %s' % ', '.join(self.broken_packages(cache)))
  885.             self.db.progress('STOP')
  886.             return
  887.  
  888.         try:
  889.             # update() returns False on failure and 0 on success. Madness!
  890.             if cache.update(fetchprogress) not in (0, True):
  891.                 fetchprogress.stop()
  892.                 self.db.progress('STOP')
  893.                 return
  894.         except IOError, e:
  895.             for line in str(e).split('\n'):
  896.                 syslog.syslog(syslog.LOG_WARNING, line)
  897.             self.db.progress('STOP')
  898.             raise
  899.         cache.open(None)
  900.         self.db.progress('SET', 10)
  901.  
  902.         self.db.progress('REGION', 10, 100)
  903.         fetchprogress = DebconfFetchProgress(
  904.             self.db, 'ubiquity/langpacks/title', None,
  905.             'ubiquity/langpacks/packages')
  906.         installprogress = DebconfInstallProgress(
  907.             self.db, 'ubiquity/langpacks/title', 'ubiquity/install/apt_info')
  908.  
  909.         for lp in to_install:
  910.             self.mark_install(cache, lp)
  911.         installed_pkgs = []
  912.         for pkg in cache.keys():
  913.             if (cache[pkg].markedInstall or cache[pkg].markedUpgrade or
  914.                 cache[pkg].markedReinstall or cache[pkg].markedDowngrade):
  915.                 installed_pkgs.append(pkg)
  916.         self.record_installed(installed_pkgs)
  917.  
  918.         commit_error = None
  919.         try:
  920.             if not cache.commit(fetchprogress, installprogress):
  921.                 fetchprogress.stop()
  922.                 installprogress.finishUpdate()
  923.                 self.db.progress('STOP')
  924.                 return
  925.         except IOError, e:
  926.             for line in str(e).split('\n'):
  927.                 syslog.syslog(syslog.LOG_WARNING, line)
  928.             commit_error = str(e)
  929.         except SystemError, e:
  930.             for line in str(e).split('\n'):
  931.                 syslog.syslog(syslog.LOG_WARNING, line)
  932.             commit_error = str(e)
  933.         self.db.progress('SET', 100)
  934.  
  935.         cache.open(None)
  936.         if commit_error or cache._depcache.BrokenCount > 0:
  937.             if commit_error is None:
  938.                 commit_error = ''
  939.             brokenpkgs = self.broken_packages(cache)
  940.             syslog.syslog('broken packages after language pack installation: '
  941.                           '%s' % ', '.join(brokenpkgs))
  942.             self.db.subst('ubiquity/install/broken_install', 'ERROR',
  943.                           commit_error)
  944.             self.db.subst('ubiquity/install/broken_install', 'PACKAGES',
  945.                           ', '.join(brokenpkgs))
  946.             self.db.input('critical', 'ubiquity/install/broken_install')
  947.             self.db.go()
  948.  
  949.         self.db.progress('STOP')
  950.  
  951.  
  952.     def configure_timezone(self):
  953.         """Set timezone on installed system."""
  954.  
  955.         dbfilter = timezone_apply.TimezoneApply(None)
  956.         ret = dbfilter.run_command(auto_process=True)
  957.         if ret != 0:
  958.             raise InstallStepError("TimezoneApply failed with code %d" % ret)
  959.  
  960.         dbfilter = clock_setup.ClockSetup(None)
  961.         ret = dbfilter.run_command(auto_process=True)
  962.         if ret != 0:
  963.             raise InstallStepError("ClockSetup failed with code %d" % ret)
  964.  
  965.  
  966.     def configure_keyboard(self):
  967.         """Set keyboard in installed system."""
  968.  
  969.         dbfilter = console_setup_apply.ConsoleSetupApply(None)
  970.         ret = dbfilter.run_command(auto_process=True)
  971.         if ret != 0:
  972.             raise InstallStepError(
  973.                 "ConsoleSetupApply failed with code %d" % ret)
  974.  
  975.  
  976.     def configure_user(self):
  977.         """create the user selected along the installation process
  978.         into the installed system. Default user from live system is
  979.         deleted and skel for this new user is copied to $HOME."""
  980.  
  981.         dbfilter = usersetup_apply.UserSetupApply(None)
  982.         ret = dbfilter.run_command(auto_process=True)
  983.         if ret != 0:
  984.             raise InstallStepError("UserSetupApply failed with code %d" % ret)
  985.     
  986.     def configure_ma(self):
  987.         """import documents, settings, and users from previous operating
  988.         systems."""
  989.  
  990.         if 'UBIQUITY_MIGRATION_ASSISTANT' in os.environ:
  991.             dbfilter = migrationassistant_apply.MigrationAssistantApply(None)
  992.             ret = dbfilter.run_command(auto_process=True)
  993.             if ret != 0:
  994.                 raise InstallStepError("MigrationAssistantApply failed with code %d" % ret)
  995.  
  996.  
  997.     def get_resume_partition(self):
  998.         biggest_size = 0
  999.         biggest_partition = None
  1000.         swaps = open('/proc/swaps')
  1001.         for line in swaps:
  1002.             words = line.split()
  1003.             if words[1] != 'partition':
  1004.                 continue
  1005.             size = int(words[2])
  1006.             if size > biggest_size:
  1007.                 biggest_size = size
  1008.                 biggest_partition = words[0]
  1009.         swaps.close()
  1010.         return biggest_partition
  1011.  
  1012.     def configure_hardware(self):
  1013.         """reconfiguring several packages which depends on the
  1014.         hardware system in which has been installed on and need some
  1015.         automatic configurations to get work."""
  1016.  
  1017.         self.chroot_setup()
  1018.         try:
  1019.             dbfilter = hw_detect.HwDetect(None, self.db)
  1020.             ret = dbfilter.run_command(auto_process=True)
  1021.             if ret != 0:
  1022.                 raise InstallStepError("HwDetect failed with code %d" % ret)
  1023.         finally:
  1024.             self.chroot_cleanup()
  1025.  
  1026.         self.db.progress('INFO', 'ubiquity/install/hardware')
  1027.  
  1028.         misc.ex('/usr/lib/ubiquity/debian-installer-utils'
  1029.                 '/register-module.post-base-installer')
  1030.  
  1031.         resume = self.get_resume_partition()
  1032.         if resume is not None:
  1033.             resume_uuid = None
  1034.             try:
  1035.                 resume_uuid = subprocess.Popen(
  1036.                     ['vol_id', '-u', resume],
  1037.                     stdout=subprocess.PIPE).communicate()[0].rstrip('\n')
  1038.             except OSError:
  1039.                 pass
  1040.             if resume_uuid:
  1041.                 resume = "UUID=%s" % resume_uuid
  1042.             if os.path.exists(os.path.join(self.target,
  1043.                                            'etc/initramfs-tools/conf.d')):
  1044.                 configdir = os.path.join(self.target,
  1045.                                          'etc/initramfs-tools/conf.d')
  1046.             elif os.path.exists(os.path.join(self.target,
  1047.                                              'etc/mkinitramfs/conf.d')):
  1048.                 configdir = os.path.join(self.target,
  1049.                                          'etc/mkinitramfs/conf.d')
  1050.             else:
  1051.                 configdir = None
  1052.             if configdir is not None:
  1053.                 configfile = open(os.path.join(configdir, 'resume'), 'w')
  1054.                 print >>configfile, "RESUME=%s" % resume
  1055.                 configfile.close()
  1056.  
  1057.         try:
  1058.             os.unlink('/target/etc/usplash.conf')
  1059.         except OSError:
  1060.             pass
  1061.  
  1062.         try:
  1063.             os.unlink('/target/etc/popularity-contest.conf')
  1064.         except OSError:
  1065.             pass
  1066.         try:
  1067.             participate = self.db.get('popularity-contest/participate')
  1068.             self.set_debconf('popularity-contest/participate', participate)
  1069.         except debconf.DebconfError:
  1070.             pass
  1071.  
  1072.         self.chroot_setup()
  1073.         self.chrex('dpkg-divert', '--package', 'ubiquity', '--rename',
  1074.                    '--quiet', '--add', '/usr/sbin/update-initramfs')
  1075.         try:
  1076.             os.symlink('/bin/true', '/target/usr/sbin/update-initramfs')
  1077.         except OSError:
  1078.             pass
  1079.  
  1080.         packages = ['linux-image-' + self.kernel_version,
  1081.                     'linux-restricted-modules-' + self.kernel_version,
  1082.                     'usplash',
  1083.                     'popularity-contest']
  1084.  
  1085.         try:
  1086.             for package in packages:
  1087.                 self.reconfigure(package)
  1088.         finally:
  1089.             try:
  1090.                 os.unlink('/target/usr/sbin/update-initramfs')
  1091.             except OSError:
  1092.                 pass
  1093.             self.chrex('dpkg-divert', '--package', 'ubiquity', '--rename',
  1094.                        '--quiet', '--remove', '/usr/sbin/update-initramfs')
  1095.             self.chrex('update-initramfs', '-c', '-k', os.uname()[2])
  1096.             self.chroot_cleanup()
  1097.  
  1098.         # Fix up kernel symlinks now that the initrd exists. Depending on
  1099.         # the architecture, these may be in / or in /boot.
  1100.         bootdir = os.path.join(self.target, 'boot')
  1101.         if self.db.get('base-installer/kernel/linux/link_in_boot') == 'true':
  1102.             linkdir = bootdir
  1103.             linkprefix = ''
  1104.         else:
  1105.             linkdir = self.target
  1106.             linkprefix = 'boot'
  1107.  
  1108.         # Remove old symlinks. We'll set them up from scratch.
  1109.         re_symlink = re.compile('vmlinu[xz]|initrd.img$')
  1110.         for entry in os.listdir(linkdir):
  1111.             if re_symlink.match(entry) is not None:
  1112.                 filename = os.path.join(linkdir, entry)
  1113.                 if os.path.islink(filename):
  1114.                     os.unlink(filename)
  1115.         if linkdir != self.target:
  1116.             # Remove symlinks in /target too, which may have been created on
  1117.             # the live filesystem. This isn't necessary, but it may help
  1118.             # avoid confusion.
  1119.             for entry in os.listdir(self.target):
  1120.                 if re_symlink.match(entry) is not None:
  1121.                     filename = os.path.join(self.target, entry)
  1122.                     if os.path.islink(filename):
  1123.                         os.unlink(filename)
  1124.  
  1125.         # Create symlinks. Prefer our current kernel version if possible,
  1126.         # but if not (perhaps due to a customised live filesystem image),
  1127.         # it's better to create some symlinks than none at all.
  1128.         re_image = re.compile('(vmlinu[xz]|initrd.img)-')
  1129.         for entry in os.listdir(bootdir):
  1130.             match = re_image.match(entry)
  1131.             if match is not None:
  1132.                 imagetype = match.group(1)
  1133.                 linksrc = os.path.join(linkprefix, entry)
  1134.                 linkdst = os.path.join(linkdir, imagetype)
  1135.                 if os.path.exists(linkdst):
  1136.                     if entry.endswith('-' + self.kernel_version):
  1137.                         os.unlink(linkdst)
  1138.                     else:
  1139.                         continue
  1140.                 os.symlink(linksrc, linkdst)
  1141.  
  1142.  
  1143.     def get_all_interfaces(self):
  1144.         """Get all non-local network interfaces."""
  1145.         ifs = []
  1146.         ifs_file = open('/proc/net/dev')
  1147.         # eat header
  1148.         ifs_file.readline()
  1149.         ifs_file.readline()
  1150.  
  1151.         for line in ifs_file:
  1152.             name = re.match('(.*?(?::\d+)?):', line.strip()).group(1)
  1153.             if name == 'lo':
  1154.                 continue
  1155.             ifs.append(name)
  1156.  
  1157.         ifs_file.close()
  1158.         return ifs
  1159.  
  1160.  
  1161.     def configure_network(self):
  1162.         """Automatically configure the network.
  1163.         
  1164.         At present, the only thing the user gets to tweak in the UI is the
  1165.         hostname. Some other things will be copied from the live filesystem,
  1166.         so changes made there will be reflected in the installed system.
  1167.         
  1168.         Unfortunately, at present we have to duplicate a fair bit of netcfg
  1169.         here, because it's hard to drive netcfg in a way that won't try to
  1170.         bring interfaces up and down."""
  1171.  
  1172.         # TODO cjwatson 2006-03-30: just call netcfg instead of doing all
  1173.         # this; requires a netcfg binary that doesn't bring interfaces up
  1174.         # and down
  1175.  
  1176.         for path in ('/etc/network/interfaces', '/etc/resolv.conf'):
  1177.             if os.path.exists(path):
  1178.                 shutil.copy2(path, os.path.join(self.target, path[1:]))
  1179.  
  1180.         try:
  1181.             hostname = self.db.get('netcfg/get_hostname')
  1182.         except debconf.DebconfError:
  1183.             hostname = ''
  1184.         try:
  1185.             domain = self.db.get('netcfg/get_domain')
  1186.         except debconf.DebconfError:
  1187.             domain = ''
  1188.         if hostname == '':
  1189.             hostname = 'ubuntu'
  1190.  
  1191.         fp = open(os.path.join(self.target, 'etc/hostname'), 'w')
  1192.         print >>fp, hostname
  1193.         fp.close()
  1194.  
  1195.         hosts = open(os.path.join(self.target, 'etc/hosts'), 'w')
  1196.         print >>hosts, "127.0.0.1\tlocalhost"
  1197.         if domain:
  1198.             print >>hosts, "127.0.1.1\t%s.%s\t%s" % (hostname, domain,
  1199.                                                      hostname)
  1200.         else:
  1201.             print >>hosts, "127.0.1.1\t%s" % hostname
  1202.         print >>hosts, textwrap.dedent("""\
  1203.  
  1204.             # The following lines are desirable for IPv6 capable hosts
  1205.             ::1     ip6-localhost ip6-loopback
  1206.             fe00::0 ip6-localnet
  1207.             ff00::0 ip6-mcastprefix
  1208.             ff02::1 ip6-allnodes
  1209.             ff02::2 ip6-allrouters
  1210.             ff02::3 ip6-allhosts""")
  1211.         hosts.close()
  1212.  
  1213.         # TODO cjwatson 2006-03-30: from <bits/ioctls.h>; ugh, but no
  1214.         # binding available
  1215.         SIOCGIFHWADDR = 0x8927
  1216.         # <net/if_arp.h>
  1217.         ARPHRD_ETHER = 1
  1218.  
  1219.         if_names = {}
  1220.         sock = socket.socket(socket.SOCK_DGRAM)
  1221.         interfaces = self.get_all_interfaces()
  1222.         for i in range(len(interfaces)):
  1223.             if_names[interfaces[i]] = struct.unpack('H6s',
  1224.                 fcntl.ioctl(sock.fileno(), SIOCGIFHWADDR,
  1225.                             struct.pack('256s', interfaces[i]))[16:24])
  1226.         sock.close()
  1227.  
  1228.         iftab = open(os.path.join(self.target, 'etc/iftab'), 'w')
  1229.  
  1230.         print >>iftab, textwrap.dedent("""\
  1231.             # This file assigns persistent names to network interfaces.
  1232.             # See iftab(5) for syntax.
  1233.             """)
  1234.  
  1235.         for i in range(len(interfaces)):
  1236.             dup = False
  1237.             with_arp = False
  1238.  
  1239.             if_name = if_names[interfaces[i]]
  1240.             if if_name is None or if_name[0] != ARPHRD_ETHER:
  1241.                 continue
  1242.  
  1243.             for j in range(len(interfaces)):
  1244.                 if i == j or if_names[interfaces[j]] is None:
  1245.                     continue
  1246.                 if if_name[1] != if_names[interfaces[j]][1]:
  1247.                     continue
  1248.  
  1249.                 if if_names[interfaces[j]][0] == ARPHRD_ETHER:
  1250.                     dup = True
  1251.  
  1252.             if dup:
  1253.                 continue
  1254.  
  1255.             line = (interfaces[i] + " mac " +
  1256.                     ':'.join(['%02x' % ord(if_name[1][c]) for c in range(6)]))
  1257.             line += " arp %d" % if_name[0]
  1258.             print >>iftab, line
  1259.  
  1260.         iftab.close()
  1261.  
  1262.  
  1263.     def configure_bootloader(self):
  1264.         """configuring and installing boot loader into installed
  1265.         hardware system."""
  1266.  
  1267.         misc.ex('mount', '--bind', '/proc', self.target + '/proc')
  1268.         misc.ex('mount', '--bind', '/dev', self.target + '/dev')
  1269.  
  1270.         archdetect = subprocess.Popen(['archdetect'], stdout=subprocess.PIPE)
  1271.         subarch = archdetect.communicate()[0].strip()
  1272.  
  1273.         try:
  1274.             if subarch.startswith('amd64/') or subarch.startswith('i386/'):
  1275.                 from ubiquity.components import grubinstaller
  1276.                 dbfilter = grubinstaller.GrubInstaller(None)
  1277.                 ret = dbfilter.run_command(auto_process=True)
  1278.                 if ret != 0:
  1279.                     raise InstallStepError(
  1280.                         "GrubInstaller failed with code %d" % ret)
  1281.             elif subarch == 'powerpc/ps3':
  1282.                 from ubiquity.components import kbootinstaller
  1283.                 dbfilter = kbootinstaller.KbootInstaller(None)
  1284.                 ret = dbfilter.run_command(auto_process=True)
  1285.                 if ret != 0:
  1286.                     raise InstallStepError(
  1287.                         "KbootInstaller failed with code %d" % ret)
  1288.             elif subarch.startswith('powerpc/'):
  1289.                 from ubiquity.components import yabootinstaller
  1290.                 dbfilter = yabootinstaller.YabootInstaller(None)
  1291.                 ret = dbfilter.run_command(auto_process=True)
  1292.                 if ret != 0:
  1293.                     raise InstallStepError(
  1294.                         "YabootInstaller failed with code %d" % ret)
  1295.             else:
  1296.                 raise InstallStepError("No bootloader installer found")
  1297.         except ImportError:
  1298.             raise InstallStepError("No bootloader installer found")
  1299.  
  1300.         misc.ex('umount', '-f', self.target + '/proc')
  1301.         misc.ex('umount', '-f', self.target + '/dev')
  1302.  
  1303.  
  1304.     def broken_packages(self, cache):
  1305.         brokenpkgs = set()
  1306.         for pkg in cache.keys():
  1307.             try:
  1308.                 if cache._depcache.IsInstBroken(cache._cache[pkg]):
  1309.                     brokenpkgs.add(pkg)
  1310.             except KeyError:
  1311.                 # Apparently sometimes the cache goes a bit bonkers ...
  1312.                 continue
  1313.         return brokenpkgs
  1314.  
  1315.     def do_remove(self, to_remove, recursive=False):
  1316.         self.db.progress('START', 0, 5, 'ubiquity/install/title')
  1317.         self.db.progress('INFO', 'ubiquity/install/find_removables')
  1318.  
  1319.         fetchprogress = DebconfFetchProgress(
  1320.             self.db, 'ubiquity/install/title',
  1321.             'ubiquity/install/apt_indices_starting',
  1322.             'ubiquity/install/apt_indices')
  1323.         cache = Cache()
  1324.  
  1325.         if cache._depcache.BrokenCount > 0:
  1326.             syslog.syslog(
  1327.                 'not processing removals, since there are broken packages: '
  1328.                 '%s' % ', '.join(self.broken_packages(cache)))
  1329.             self.db.progress('STOP')
  1330.             return
  1331.  
  1332.         while True:
  1333.             removed = set()
  1334.             for pkg in to_remove:
  1335.                 cachedpkg = self.get_cache_pkg(cache, pkg)
  1336.                 if cachedpkg is not None and cachedpkg.isInstalled:
  1337.                     apt_error = False
  1338.                     try:
  1339.                         cachedpkg.markDelete(autoFix=False, purge=True)
  1340.                     except SystemError:
  1341.                         apt_error = True
  1342.                     if apt_error:
  1343.                         cachedpkg.markKeep()
  1344.                     elif cache._depcache.BrokenCount > 0:
  1345.                         # If we're recursively removing packages, or if all
  1346.                         # of the broken packages are in the set of packages
  1347.                         # to remove anyway, then go ahead and try to remove
  1348.                         # them too.
  1349.                         brokenpkgs = self.broken_packages(cache)
  1350.                         broken_removed = set()
  1351.                         while brokenpkgs and (recursive or
  1352.                                               brokenpkgs <= to_remove):
  1353.                             broken_removed_inner = set()
  1354.                             for pkg2 in brokenpkgs:
  1355.                                 cachedpkg2 = self.get_cache_pkg(cache, pkg2)
  1356.                                 if cachedpkg2 is not None:
  1357.                                     broken_removed_inner.add(pkg2)
  1358.                                     try:
  1359.                                         cachedpkg2.markDelete(autoFix=False,
  1360.                                                               purge=True)
  1361.                                     except SystemError:
  1362.                                         apt_error = True
  1363.                                         break
  1364.                             broken_removed |= broken_removed_inner
  1365.                             if apt_error or not broken_removed_inner:
  1366.                                 break
  1367.                             brokenpkgs = self.broken_packages(cache)
  1368.                         if apt_error or cache._depcache.BrokenCount > 0:
  1369.                             # That didn't work. Revert all the removals we
  1370.                             # just tried.
  1371.                             for pkg2 in broken_removed:
  1372.                                 self.get_cache_pkg(cache, pkg2).markKeep()
  1373.                             cachedpkg.markKeep()
  1374.                         else:
  1375.                             removed.add(pkg)
  1376.                             removed |= broken_removed
  1377.                     else:
  1378.                         removed.add(pkg)
  1379.                     assert cache._depcache.BrokenCount == 0
  1380.             if not removed:
  1381.                 break
  1382.             to_remove -= removed
  1383.  
  1384.         self.db.progress('SET', 1)
  1385.         self.db.progress('REGION', 1, 5)
  1386.         fetchprogress = DebconfFetchProgress(
  1387.             self.db, 'ubiquity/install/title', None,
  1388.             'ubiquity/install/fetch_remove')
  1389.         installprogress = DebconfInstallProgress(
  1390.             self.db, 'ubiquity/install/title', 'ubiquity/install/apt_info',
  1391.             'ubiquity/install/apt_error_remove')
  1392.         self.chroot_setup()
  1393.         commit_error = None
  1394.         try:
  1395.             try:
  1396.                 if not cache.commit(fetchprogress, installprogress):
  1397.                     fetchprogress.stop()
  1398.                     installprogress.finishUpdate()
  1399.                     self.db.progress('STOP')
  1400.                     return
  1401.             except SystemError, e:
  1402.                 for line in str(e).split('\n'):
  1403.                     syslog.syslog(syslog.LOG_ERR, line)
  1404.                 commit_error = str(e)
  1405.         finally:
  1406.             self.chroot_cleanup()
  1407.         self.db.progress('SET', 5)
  1408.  
  1409.         cache.open(None)
  1410.         if commit_error or cache._depcache.BrokenCount > 0:
  1411.             if commit_error is None:
  1412.                 commit_error = ''
  1413.             brokenpkgs = self.broken_packages(cache)
  1414.             syslog.syslog('broken packages after removal: '
  1415.                           '%s' % ', '.join(brokenpkgs))
  1416.             self.db.subst('ubiquity/install/broken_remove', 'ERROR',
  1417.                           commit_error)
  1418.             self.db.subst('ubiquity/install/broken_remove', 'PACKAGES',
  1419.                           ', '.join(brokenpkgs))
  1420.             self.db.input('critical', 'ubiquity/install/broken_remove')
  1421.             self.db.go()
  1422.  
  1423.         self.db.progress('STOP')
  1424.  
  1425.  
  1426.     def remove_unusable_kernels(self):
  1427.         """Remove unusable kernels; keeping them may cause us to be unable
  1428.         to boot."""
  1429.  
  1430.         self.db.progress('START', 0, 5, 'ubiquity/install/title')
  1431.  
  1432.         self.db.progress('INFO', 'ubiquity/install/find_removables')
  1433.  
  1434.         # Check for kernel packages to remove.
  1435.         dbfilter = check_kernels.CheckKernels(None)
  1436.         dbfilter.run_command(auto_process=True)
  1437.  
  1438.         remove_kernels = set()
  1439.         if os.path.exists("/var/lib/ubiquity/remove-kernels"):
  1440.             remove_kernels_file = open("/var/lib/ubiquity/remove-kernels")
  1441.             for line in remove_kernels_file:
  1442.                 remove_kernels.add(line.strip())
  1443.             remove_kernels_file.close()
  1444.  
  1445.         if len(remove_kernels) == 0:
  1446.             self.db.progress('STOP')
  1447.             return
  1448.  
  1449.         self.db.progress('SET', 1)
  1450.         self.db.progress('REGION', 1, 5)
  1451.         try:
  1452.             self.do_remove(remove_kernels, recursive=True)
  1453.         except:
  1454.             self.db.progress('STOP')
  1455.             raise
  1456.         self.db.progress('SET', 5)
  1457.         self.db.progress('STOP')
  1458.  
  1459.  
  1460.     def remove_extras(self):
  1461.         """Try to remove packages that are needed on the live CD but not on
  1462.         the installed system."""
  1463.  
  1464.         # Looking through files for packages to remove is pretty quick, so
  1465.         # don't bother with a progress bar for that.
  1466.  
  1467.         # Check for packages specific to the live CD.
  1468.         if (os.path.exists("/cdrom/casper/filesystem.manifest-desktop") and
  1469.             os.path.exists("/cdrom/casper/filesystem.manifest")):
  1470.             desktop_packages = set()
  1471.             manifest = open("/cdrom/casper/filesystem.manifest-desktop")
  1472.             for line in manifest:
  1473.                 if line.strip() != '' and not line.startswith('#'):
  1474.                     desktop_packages.add(line.split()[0])
  1475.             manifest.close()
  1476.             live_packages = set()
  1477.             manifest = open("/cdrom/casper/filesystem.manifest")
  1478.             for line in manifest:
  1479.                 if line.strip() != '' and not line.startswith('#'):
  1480.                     live_packages.add(line.split()[0])
  1481.             manifest.close()
  1482.             difference = live_packages - desktop_packages
  1483.         else:
  1484.             difference = set()
  1485.  
  1486.         # Keep packages we explicitly installed.
  1487.         apt_installed = set()
  1488.         if os.path.exists("/var/lib/ubiquity/apt-installed"):
  1489.             apt_installed_file = open("/var/lib/ubiquity/apt-installed")
  1490.             for line in apt_installed_file:
  1491.                 apt_installed.add(line.strip())
  1492.             apt_installed_file.close()
  1493.         difference -= apt_installed
  1494.  
  1495.         if len(difference) == 0:
  1496.             return
  1497.  
  1498.         # Don't worry about failures removing packages; it will be easier
  1499.         # for the user to sort them out with a graphical package manager (or
  1500.         # whatever) after installation than it will be to try to deal with
  1501.         # them automatically here.
  1502.         self.do_remove(difference)
  1503.  
  1504.  
  1505.     def cleanup(self):
  1506.         """Miscellaneous cleanup tasks."""
  1507.         try:
  1508.             os.unlink(os.path.join(
  1509.                 self.target, 'etc/apt/apt.conf.d/00IgnoreTimeConflict'))
  1510.         except:
  1511.             pass
  1512.         if self.source == '/var/lib/ubiquity/source':
  1513.             self.umount_source()
  1514.  
  1515.  
  1516.     def chrex(self, *args):
  1517.         """executes commands on chroot system (provided by *args)."""
  1518.         return misc.ex('chroot', self.target, *args)
  1519.  
  1520.  
  1521.     def copy_debconf(self, package):
  1522.         """setting debconf database into installed system."""
  1523.  
  1524.         # TODO cjwatson 2006-02-25: unusable here now because we have a
  1525.         # running debconf frontend that's locked the database; fortunately
  1526.         # this isn't critical. We still need to think about how to handle
  1527.         # preseeding in general, though.
  1528.         targetdb = os.path.join(self.target, 'var/cache/debconf/config.dat')
  1529.  
  1530.         misc.ex('debconf-copydb', 'configdb', 'targetdb', '-p',
  1531.                 '^%s/' % package, '--config=Name:targetdb',
  1532.                 '--config=Driver:File','--config=Filename:' + targetdb)
  1533.  
  1534.  
  1535.     def set_debconf(self, question, value):
  1536.         dccomm = subprocess.Popen(['log-output', '-t', 'ubiquity',
  1537.                                    '--pass-stdout',
  1538.                                    'chroot', self.target,
  1539.                                    'debconf-communicate',
  1540.                                    '-fnoninteractive', 'ubiquity'],
  1541.                                   stdin=subprocess.PIPE,
  1542.                                   stdout=subprocess.PIPE, close_fds=True)
  1543.         dc = debconf.Debconf(read=dccomm.stdout, write=dccomm.stdin)
  1544.         dc.set(question, value)
  1545.         dc.fset(question, 'seen', 'true')
  1546.         dccomm.stdin.close()
  1547.         dccomm.wait()
  1548.  
  1549.  
  1550.     def reconfigure(self, package):
  1551.         """executes a dpkg-reconfigure into installed system to each
  1552.         package which provided by args."""
  1553.  
  1554.         self.chrex('dpkg-reconfigure', '-fnoninteractive', package)
  1555.  
  1556.  
  1557. if __name__ == '__main__':
  1558.     if not os.path.exists('/var/lib/ubiquity'):
  1559.         os.makedirs('/var/lib/ubiquity')
  1560.     if os.path.exists('/var/lib/ubiquity/install.trace'):
  1561.         os.unlink('/var/lib/ubiquity/install.trace')
  1562.  
  1563.     install = Install()
  1564.     sys.excepthook = install.excepthook
  1565.     install.run()
  1566.     sys.exit(0)
  1567.  
  1568. # vim:ai:et:sts=4:tw=80:sw=4:
  1569.