#!/usr/bin/python import os import sys import subprocess import time import signal import errno import imp import grp import pwd import debconf sys.path.insert(0, '/usr/lib/ubiquity') import ubiquity.frontend from ubiquity import osextras from ubiquity import gsettings from ubiquity.misc import create_bool from ubiquity.casper import get_casper from ubiquity.debconfcommunicator import DebconfCommunicator def ck_open_session(uid): import dbus bus = dbus.SystemBus () manager_obj = bus.get_object ('org.freedesktop.ConsoleKit', '/org/freedesktop/ConsoleKit/Manager') manager = dbus.Interface (manager_obj, 'org.freedesktop.ConsoleKit.Manager') params = dbus.Array ([], signature = "(sv)") params.append(("unix-user", dbus.Int32(uid))) params.append(("session-type", dbus.String(""))) params.append(("x11-display", dbus.String(os.environ['DISPLAY']))) params.append(("x11-display-device", dbus.String('/dev/tty7'))) params.append(("is-local", dbus.Boolean(True))) cookie = manager.OpenSessionWithParameters(params) os.environ['XDG_SESSION_COOKIE'] = cookie def set_locale(): db = DebconfCommunicator('ubiquity', cloexec=True) locale = '' try: locale = db.get('debian-installer/locale') except debconf.DebconfError: pass db.shutdown() if not locale: return default_locale = open('/etc/default/locale', 'w') print >>default_locale, 'LANG="%s"' % locale default_locale.close() environment = open('/etc/environment') environment_lines = environment.readlines() environment.close() environment = open('/etc/environment', 'w') seen_lang = False for line in environment_lines: if line.startswith('LANG='): print >>environment, 'LANG="%s"' % locale seen_lang = True else: print >>environment, line.rstrip('\n') if not seen_lang: print >>environment, 'LANG="%s"' % locale environment.close() locale_gen = open('/etc/locale.gen', 'w') print >>locale_gen, '%s UTF-8' % locale locale_gen.close() logfile = open('/var/log/installer/dm', 'a') subprocess.call(['/usr/sbin/locale-gen', locale], stdout=logfile, stderr=logfile) logfile.close() class XStartupError(EnvironmentError): pass class MissingProgramError(EnvironmentError): pass class DM: def __init__(self, vt, display, default_username): self.vt = vt self.display = display self.server_started = False self.username = get_casper('USERNAME', default_username) try: self.uid, self.gid = pwd.getpwnam(self.username)[2:4] except KeyError: import syslog syslog.syslog('Could not find %s, falling back to root.' % self.username) self.username = 'root' self.uid, self.gid = 0, 0 os.environ['SUDO_USER'] = self.username self.homedir = pwd.getpwnam(self.username)[5] self.uid = int(self.uid) self.gid = int(self.gid) self.groups = [] for g in grp.getgrall(): if self.username in g[3] or g[0] == self.username: self.groups.append(g[2]) # Look for a frontend module; we won't actually use it (yet), but # this lets us find out which window manager etc. to launch. Be # careful that importing this here will cause the underlying library # to try to talk to the X server, which won't go well. frontend_names = ['gtk_ui', 'kde_ui'] self.frontend = None for f in frontend_names: try: imp.find_module(f, ubiquity.frontend.__path__) self.frontend = f break except ImportError: pass else: raise AttributeError('No frontend available; tried %s' % ', '.join(frontend_names)) db = DebconfCommunicator('ubiquity', cloexec=True) try: self.force_failsafe = create_bool(db.get('ubiquity/force_failsafe_graphics')) except debconf.DebconfError: self.force_failsafe = False db.shutdown() def sigusr1_handler(self, signum, frame): self.server_started = True def active_vt(self): import fcntl import array console = os.open('/dev/tty0', os.O_RDONLY | os.O_NOCTTY) try: VT_GETSTATE = 0x5603 vt_stat = array.array('H', [0, 0, 0]) fcntl.ioctl(console, VT_GETSTATE, vt_stat) return vt_stat[0] finally: os.close(console) def drop_privileges(self): os.setgroups(self.groups) os.setgid(self.gid) os.setuid(self.uid) def server_preexec(self): signal.signal(signal.SIGUSR1, signal.SIG_IGN) def run_hooks(self, hookdir): if os.path.isdir(hookdir): # Exclude hooks containing '.', so that *.dpkg-* et al are avoided. hooks = [entry for entry in os.listdir(hookdir) if '.' not in entry] for hookentry in hooks: hook = os.path.join(hookdir, hookentry) with open('/var/log/installer/dm', 'a') as logfile: subprocess.call( hook, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges) def run(self, *program): # extract the program basename to see if we are in oem-config or ubiquity program_basename = os.path.basename(program[0]) null = open('/dev/null', 'w') try: os.makedirs('/var/log/installer') except OSError, e: # be happy if someone already created the path if e.errno != errno.EEXIST: raise logfile = open('/var/log/installer/dm', 'w') signal.signal(signal.SIGUSR1, self.sigusr1_handler) signal.signal(signal.SIGTTIN, signal.SIG_IGN) signal.signal(signal.SIGTTOU, signal.SIG_IGN) servercommand = ['X', '-br', '-ac', '-noreset', '-nolisten', 'tcp'] try: plymouth_running = subprocess.call(['plymouth', '--ping']) == 0 except OSError: plymouth_running = False if plymouth_running: subprocess.call(['plymouth', 'deactivate']) if subprocess.call(['plymouth', '--has-active-vt']) == 0: self.vt = 'vt%d' % self.active_vt() servercommand.append('-nr') else: subprocess.call(['plymouth', 'quit']) plymouth_running = False servercommand.extend([self.vt, self.display]) for attempt in ('main', 'fbdev', 'vesa'): command = list(servercommand) if attempt == 'main' and self.force_failsafe: continue elif attempt != 'main': # TODO cjwatson 2010-02-11: This is a bodge. The # duplication is nasty, but fortunately bullet-proof X # actually turns out not to be very complicated nowadays. # Most of the complexity is in the fallback session, which I # haven't attempted to integrate here, so you won't get # things like interactive reconfiguration. I believe Evan # is working on doing that, but is blocked on a couple of # Upstart bugs; once all that's resolved, we should back # this out. if attempt == 'fbdev' and not os.path.exists('/dev/fb0'): continue xorg_conf_failsafe = '/etc/X11/xorg.conf.failsafe' command.extend(['-config', xorg_conf_failsafe]) command.extend(['-logfile', '/var/log/Xorg.%s.log' % attempt]) xorg_conf_failsafe_file = open(xorg_conf_failsafe, 'w') print >>xorg_conf_failsafe_file, '''\ Section "Device" Identifier "Configured Video Device" Driver "%s" EndSection Section "Monitor" Identifier "Configured Monitor" EndSection Section "Screen" Identifier "Default Screen" Monitor "Configured Monitor" Device "Configured Video Device" EndSection ''' % attempt xorg_conf_failsafe_file.close() server = subprocess.Popen( command, stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.server_preexec) # Really we should select on a pipe or something, but it's not # worth the effort for now. try: timeout = 60 while not self.server_started: status = server.poll() if type(status) is int and status != 0: if plymouth_running: subprocess.call(['plymouth', 'quit']) raise XStartupError('X server exited with return code ' + str(status)) if timeout == 0: if plymouth_running: subprocess.call(['plymouth', 'quit']) raise XStartupError('X server failed to start after 60' ' seconds') time.sleep(1) timeout -= 1 if plymouth_running: subprocess.call(['plymouth', 'quit', '--retain-splash']) except XStartupError: if attempt == 'vesa': raise if self.server_started: break os.environ['DISPLAY'] = self.display os.environ['HOME'] = self.homedir # Give ubiquity a UID and GID that it can drop privileges to. os.environ['SUDO_UID'] = str(self.uid) os.environ['SUDO_GID'] = str(self.gid) os.environ['GVFS_DISABLE_FUSE' ] = '1' ck_open_session(self.uid) # run simple, custom scripts during install time if program_basename == 'ubiquity': self.run_hooks('/usr/lib/ubiquity/dm-scripts/install') # run simple, custom scripts during oem-config if program_basename == 'oem-config-wrapper': self.run_hooks('/usr/lib/ubiquity/dm-scripts/oem') # Session bus, apparently needed by most interfaces now if ('DBUS_SESSION_BUS_ADDRESS' not in os.environ and osextras.find_on_path('dbus-launch')): dbus_subp = subprocess.Popen( ['dbus-launch', '--exit-with-session'], stdin=null, stdout=subprocess.PIPE, stderr=logfile, preexec_fn=self.drop_privileges) for line in dbus_subp.stdout: try: name, value = line.rstrip('\n').split('=', 1) os.environ[name] = value except ValueError: pass dbus_subp.stdout.close() dbus_subp.wait() gconfd_running = False if (self.frontend == 'gtk_ui' and osextras.find_on_path('gconftool-2')): subprocess.call(['gconftool-2', '--spawn'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges) gconfd_running = True extras = [] if self.frontend == 'gtk_ui': gconf_dir = ('xml:readwrite:%s' % os.path.expanduser('~%s/.gconf' % self.username)) accessibility = False if (osextras.find_on_path('gconftool-2') and gsettings._gsettings_exists()): # Set number of workspaces to 1 in metacity # (still using gconf, GNOME: #621204) subprocess.call( ['gconftool-2', '--config-source', gconf_dir, '--type', 'int', '--set', '/apps/metacity/general/num_workspaces', '1',], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges) # Set gsettings keys gsettings_keys = [ ('org.gnome.desktop.lockdown', 'disable-lock-screen', 'true'), ('org.gnome.desktop.lockdown', 'disable-user-switching', 'true'), ] for gs_schema, gs_key, gs_value in gsettings_keys: gsettings.set(gs_schema, gs_key, gs_value, self.username) accessibility = gsettings.get('org.gnome.desktop.interface', 'toolkit-accessibility', self.username) # Accessibility infrastructure with open('/proc/cmdline', 'r') as fp: if (accessibility == True or 'maybe-ubiquity' in fp.readline()): if os.path.exists('/usr/lib/at-spi2-core/at-spi-bus-launcher'): extras.append(subprocess.Popen( ['/usr/lib/at-spi2-core/at-spi-bus-launcher', '--launch-immediately'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges)) os.environ['GTK_MODULES'] = 'gail:atk-bridge' # Set a desktop wallpaper. with open('/proc/cmdline', 'r') as fp: for background in ('/usr/share/backgrounds/edubuntu_default.png', '/usr/share/xfce4/backdrops/macinnis_wallpaper.png', '/usr/share/xfce4/backdrops/xubuntu-precise-right.png', '/usr/share/backgrounds/warty-final-ubuntu.png'): exists = os.access(background, os.R_OK) if (exists and not 'access=v1' in fp.readline()): extras.append(subprocess.Popen(['/usr/lib/ubiquity/wallpaper', background], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges)) break if self.frontend == 'gtk_ui': if gconfd_running and osextras.find_on_path('metacity'): wm_cmd = ['metacity', '--sm-disable'] elif osextras.find_on_path('xfwm4'): wm_cmd = ['xfwm4'] elif osextras.find_on_path('matchbox-window-manager'): wm_cmd = ['matchbox-window-manager'] elif osextras.find_on_path('openbox-lubuntu'): wm_cmd = ['openbox-lubuntu'] elif osextras.find_on_path('openbox'): wm_cmd = ['openbox'] else: raise MissingProgramError('No window manager found (tried ' 'metacity, xfwm4, matchbox-window-manager, ' 'openbox-lubuntu, openbox)') wm = subprocess.Popen(wm_cmd, stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges) if os.path.exists('/usr/lib/gnome-settings-daemon/gnome-settings-daemon'): proc = subprocess.Popen( ['/usr/lib/gnome-settings-daemon/gnome-settings-daemon'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges) extras.append(proc) if os.path.exists('/usr/bin/xfsettingsd'): extras.append(subprocess.Popen( ['xsetroot', '-solid', 'black'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges)) extras.append(subprocess.Popen( ['/usr/bin/xfsettingsd'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges)) if osextras.find_on_path('lxsession'): proc = subprocess.Popen( ['lxsession','-s','Lubuntu', '-e', 'LXDE', '-a'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges) extras.append(proc) if os.path.exists('/usr/lib/ubiquity/panel') and "xfwm4" not in wm_cmd: if "openbox-lubuntu" not in wm_cmd and "openbox" not in wm_cmd: extras.append(subprocess.Popen( ['/usr/lib/ubiquity/panel'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges)) if osextras.find_on_path('nm-applet'): extras.append(subprocess.Popen( ['nm-applet'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges)) if osextras.find_on_path('ibus-daemon'): extras.append(subprocess.Popen( ['ibus-daemon'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges)) # Simply start bluetooth-applet, ubiquity-bluetooth-agent will # override it from casper to make sure it also covers the regular # live session if osextras.find_on_path('bluetooth-applet'): extras.append(subprocess.Popen( ['bluetooth-applet'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges)) # Accessibility tools if accessibility == True: with open('/proc/cmdline', 'r') as fp: if 'access=m2' in fp.readline(): if osextras.find_on_path('onboard'): extras.append(subprocess.Popen(['onboard'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges)) else: if osextras.find_on_path('orca'): time.sleep(15) extras.append(subprocess.Popen(['orca', '-n'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges)) elif self.frontend == 'kde_ui': # Don't show the background image in v1 accessibility profile. os.setegid(self.gid) os.seteuid(self.uid) path = '/usr/share/wallpapers/kde-default.png' with open('/proc/cmdline', 'r') as fp: if (os.access(path, os.R_OK) and not 'access=v1' in fp.readline()): # Draw a background image for the install progress window. from PyQt4.QtCore import Qt from PyQt4 import QtGui a = QtGui.QApplication(sys.argv) root_win = a.desktop() wallpaper = QtGui.QPixmap(path) wallpaper = wallpaper.scaled(root_win.width(), root_win.height(), Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation) palette = QtGui.QPalette() palette.setBrush(root_win.backgroundRole(), QtGui.QBrush(wallpaper)) root_win.setPalette(palette) root_win.setCursor(Qt.ArrowCursor) a.processEvents() # Force garbage collection so we don't end up with stray X # resources when we kill the X server (LP: #556555). del a os.seteuid(0) os.setegid(0) subprocess.call( ['kwriteconfig', '--file', 'kwinrc', '--group', 'Compositing', '--key', 'Enabled', 'false'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges) wm = subprocess.Popen('kwin', stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges) greeter = subprocess.Popen(program, stdin=null, stdout=logfile, stderr=logfile) ret = greeter.wait() reboot = False if ret != 0: db = DebconfCommunicator('ubiquity', cloexec=True) try: error_cmd = db.get('ubiquity/failure_command') if error_cmd: subprocess.call(['sh', '-c', error_cmd]) except debconf.DebconfError: pass reboot = False try: if '--automatic' in program: reboot = db.get('ubiquity/reboot_on_failure') == 'true' except debconf.DebconfError: pass if reboot: question = 'ubiquity/install_failed_reboot' else: question = 'ubiquity/install_failed' title = '' message = '' try: title = unicode(db.metaget(question, 'description'), 'utf-8', 'replace') message = unicode(db.metaget(question, 'extended_description'), 'utf-8', 'replace') except debconf.DebconfError: pass db.shutdown() if title and message: if self.frontend == 'gtk_ui': cmd = ['zenity', '--error', '--title=%s' % title, '--text=%s' % message] subprocess.call(cmd) elif self.frontend == 'kde_ui': cmd = ['kdialog', '--title=%s' % title, '--msgbox=%s' % message] subprocess.call(cmd) else: # Not ideal, but if we cannot let the user know what's # going on, it's best to drop them into a desktop and let # them figure it out. reboot = False def kill_if_exists(pid, signum): try: os.kill(pid, signum) except OSError, e: if e.errno != errno.ESRCH: raise def sigalrm_handler(signum, frame): kill_if_exists(wm.pid, signal.SIGKILL) for extra in extras: kill_if_exists(extra.pid, signal.SIGKILL) kill_if_exists(wm.pid, signal.SIGTERM) for extra in extras: kill_if_exists(extra.pid, signal.SIGTERM) if gconfd_running: subprocess.call(['gconftool-2', '--shutdown'], stdin=null, stdout=logfile, stderr=logfile, preexec_fn=self.drop_privileges) signal.signal(signal.SIGALRM, sigalrm_handler) signal.alarm(1) # low patience with WMs failing to exit on demand processes = set(extras) processes.add(wm) while processes: done = set() for process in processes: try: process.wait() done.add(process) except OSError, e: if e.errno == errno.EINTR: continue raise processes -= done signal.alarm(0) # Clear the console so we don't see boot-time messages on switch try: vthandle = open('/dev/tty' + self.vt[2:], 'r+') subprocess.call(['clear'], stdin=vthandle, stdout=vthandle) except IOError: pass kill_if_exists(server.pid, signal.SIGTERM) server.wait() if reboot: subprocess.Popen(['reboot']) if ret is not None and ret >= 0: return ret else: return 1 if len(sys.argv) < 4: sys.stderr.write('Usage: %s <:[0-N]> ' '[]\n' % sys.argv[0]) sys.exit(1) vt, display, username = sys.argv[1:4] try: dm = DM(vt, display, username) except XStartupError: sys.exit(1) ret = dm.run(*sys.argv[4:]) if ret == 0: set_locale() sys.exit(ret)