#! /usr/bin/env python
# -*- coding: UTF-8 -*-

# Copyright (C) 2005, 2006 Canonical Ltd.
# Written by Colin Watson <cjwatson@ubuntu.com>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

# This program handles OEM installations: it is run on the first boot after
# the end user receives the system, to configure settings specific to that
# end user.

import sys
import os
import optparse
import subprocess
import tempfile
import errno

sys.path.insert(0, '/usr/lib/oem-config')

from oem_config import im_switch

VERSION = '@VERSION@'

class Wizard:
    def __init__(self, frontend_name=None, query=False):
        if frontend_name is None:
            frontend_names = ['gtk_ui', 'kde_ui', 'debconf_ui']
        else:
            frontend_names = [frontend_name]
        chosen = None
        if query:
            import warnings
            warnings.filterwarnings('ignore')
        for f in frontend_names:
            try:
                mod = __import__('oem_config.frontend', globals(), locals(),
                                 [f])
            except ImportError:
                continue
            if hasattr(mod, f):
                chosen = f
                self.ui = getattr(mod, f)
                break
        else:
            raise AttributeError, ('No frontend available; tried %s' %
                                   ', '.join(frontend_names))
        os.environ['OEM_CONFIG_FRONTEND'] = chosen

    def run(self):
        self.frontend = self.ui.Frontend()
        return self.frontend.run()

    def stop(self):
        if hasattr(self.frontend, 'stop'):
            self.frontend.stop()

def disable_autologin():
    import traceback

    # Reverse actions of finish-install.d/07oem-config-user.
    for name in ('/etc/gdm/gdm-cdd.conf', '/etc/gdm/gdm.conf',
                 '/etc/kde4/kdm/kdmrc'):
        if os.path.exists('%s.oem' % name):
            try:
                os.rename('%s.oem' % name, name)
            except OSError:
                traceback.print_exc()

def run_hooks():
    """Run hook scripts from /usr/lib/oem-config/post-install."""

    hookdir = '/usr/lib/oem-config/post-install'

    if os.path.isdir(hookdir):
        # Exclude hooks containing '.', so that *.dpkg-* et al are avoided.
        hooks = filter(lambda entry: '.' not in entry, os.listdir(hookdir))
        child_env = dict(os.environ)
        child_env['DEBIAN_FRONTEND'] = 'noninteractive'
        if 'DEBIAN_HAS_FRONTEND' in child_env:
            del child_env['DEBIAN_HAS_FRONTEND']
        for hookentry in hooks:
            hook = os.path.join(hookdir, hookentry)
            if os.access(hook, os.X_OK):
                # Errors are ignored at present, although this may change.
                subprocess.call([hook], env=child_env)

def open_terminal():
    """Set up the terminal to run oem-config's debconf frontend."""

    # Set up a framebuffer and start bterm if debian-installer/framebuffer
    # says to do so. See
    # rootskel/src/lib/debian-installer-startup.d/S40framebuffer-module-linux-x86.
    # TODO: more careful architecture handling

    import debconf

    if 'OEM_CONFIG_BTERM' not in os.environ:
        os.environ['OEM_CONFIG_BTERM'] = '1'

        framebuffer = False
        dccomm = subprocess.Popen(['debconf-communicate',
                                   '-fnoninteractive', 'oem-config'],
                                  stdin=subprocess.PIPE,
                                  stdout=subprocess.PIPE, close_fds=True)
        try:
            dc = debconf.Debconf(read=dccomm.stdout, write=dccomm.stdin)
            try:
                if dc.get('debian-installer/framebuffer') == 'true':
                    framebuffer = True
            except debconf.DebconfError:
                pass
        finally:
            dccomm.stdin.close()
            dccomm.wait()

        if framebuffer:
            def fb_has(substring):
                try:
                    fb = open('/proc/fb')
                except IOError:
                    return False
                try:
                    for line in fb:
                        if substring in line:
                            return True
                finally:
                    fb.close()
                return False

            got_fb = False
            if fb_has('VESA'):
                got_fb = True

            devnull = open('/dev/null', 'w')

            if not got_fb:
                subprocess.call(['modprobe', '-q', 'vesafb'],
                                stdout=devnull, stderr=devnull)
                if fb_has(''):
                    got_fb = True

            if not got_fb:
                subprocess.call(['modprobe', '-q', 'vga16fb'],
                                stdout=devnull, stderr=devnull)
                if fb_has(''):
                    got_fb = True

            if got_fb:
                if not os.path.isdir('/sys/class/graphics/fbcon'):
                    subprocess.call(['modprobe', '-q', 'fbcon'],
                                    stdout=devnull, stderr=devnull)

                # TODO: import debian-installer-utils and use update-dev?
                subprocess.call(['udevadm', 'settle'])

            devnull.close()

            if os.path.exists('/dev/fb0'):
                bterm_args = ['bterm',
                              '-f', '/usr/share/oem-config/unifont.bgf', '--']
                bterm_args.extend(sys.argv)
                os.execvp('bterm', bterm_args)

    # Start a new session and start a controlling terminal. Approach loosely
    # borrowed from util-linux.

    if 'OEM_CONFIG_CTTY' not in os.environ:
        os.environ['OEM_CONFIG_CTTY'] = '1'

        import fcntl
        import termios

        try:
            os.setsid()
        except OSError:
            pass

        ttyn = os.ttyname(0)
        tty = os.open(ttyn, os.O_RDWR | os.O_NONBLOCK)
        flags = fcntl.fcntl(tty, fcntl.F_GETFL)
        fcntl.fcntl(tty, fcntl.F_SETFL, flags)
        # Leave stderr alone in the following; it's already redirected to
        # our log file.
        for i in range(tty):
            if i != 2:
                os.close(i)
        for i in range(2):
            if tty != i:
                os.dup2(tty, i)
        if tty >= 3:
            os.close(tty)

        fcntl.ioctl(0, termios.TIOCSCTTY, 1)

def start_debconf():
    """debconf_ui needs to run within a debconf frontend."""

    if 'DEBIAN_HAS_FRONTEND' in os.environ:
        # debconf already started, so just clean up the configuration file
        # if any (debconf has already read it by now).
        if 'DEBCONF_SYSTEMRC' in os.environ:
            try:
                os.unlink(os.environ['DEBCONF_SYSTEMRC'])
            except OSError:
                pass
        return

    print >>sys.stderr, "debconf_ui selected; starting debconf frontend"

    if 'DEBCONF_USE_CDEBCONF' not in os.environ:
        # This is rather unsatisfactory. Perhaps it would be better to
        # have a custom debconf program, a bit like dpkg-reconfigure.
        debconfrc_fd, debconfrc = tempfile.mkstemp()
        os.chmod(debconfrc, 0644)
        debconfrc_file = os.fdopen(debconfrc_fd, 'w')
        orig_debconfrc = open('/etc/debconf.conf')
        state = 0
        for line in orig_debconfrc:
            if (state == 0 and
                line.rstrip('\n') and not line.startswith('#')):
                state = 1
            elif state == 1 and not line.rstrip('\n'):
                print >>debconfrc_file, 'Reshow: true'
                state = 2
            print >>debconfrc_file, line,
        orig_debconfrc.close()
        debconfrc_file.close()
        os.environ['DEBCONF_SYSTEMRC'] = debconfrc

        os.environ['DEBCONF_PACKAGE'] = 'oem-config'
    else:
        os.environ['DEBCONF_SHOWOLD'] = 'true'
        # TODO: need to set owner somehow

    import debconf
    debconf.runFrontEnd() # re-execs this program

if __name__ == '__main__':
    usage = ("usage: %prog [options]\n\n"
             "Use oem-config-prepare to prepare the system for an end user.")
    parser = optparse.OptionParser(usage=usage, version=VERSION)
    parser.add_option('-f', '--frontend', metavar='FRONTEND',
                      help="Use the given frontend (gtk_ui).")
    parser.add_option('-d', '--debug', dest='debug', action='store_true',
                      help='debug mode (warning: passwords will be logged!)')
    parser.add_option('--pdb', dest='debug_pdb', action='store_true',
                      help='drop into Python debugger on a crash')
    parser.add_option('--cdebconf', dest='cdebconf', action='store_true',
                      help='use cdebconf instead of debconf (experimental)')
    parser.add_option('--old-tzmap', dest='old_tzmap', action='store_true',
                      help='use the old timezone map.')
    parser.add_option('--only', dest='only', action='store_true',
                      help='tell the application that it is the only desktop ' \
                      'program running so that it can customize its UI to ' \
                      'better suit a minimal environment.')
    parser.add_option('-q', '--query', dest='query', action='store_true',
                      help='find out which frontend will be used by default')
    parser.set_defaults(frontend=None, debug_pdb=False, cdebconf=False,
                        old_tzmap=False, only=False, query=False)
    (options, args) = parser.parse_args()

    if args:
        parser.print_help(sys.stderr)
        sys.exit(2)

    if options.debug:
        os.environ['OEM_CONFIG_DEBUG'] = '1'

    if options.debug_pdb:
        os.environ['OEM_CONFIG_DEBUG_PDB'] = '1'

    if options.cdebconf:
        # Note that this needs to be set before DebconfCommunicate is
        # imported by anything.
        os.environ['DEBCONF_USE_CDEBCONF'] = '1'
        prepend_path('/usr/lib/cdebconf')

    if options.old_tzmap:
        os.environ['UBIQUITY_OLD_TZMAP'] = '1'

    if options.only:
        os.environ['OEM_CONFIG_ONLY'] = '1'

    if not options.query:
        # The frontend should take care of displaying a helpful message if
        # we are being run without root privileges.
        try:
            log = os.open('/var/log/oem-config.log',
                os.O_WRONLY | os.O_CREAT | os.O_APPEND)
            os.dup2(log, 2)
            os.close(log)
            sys.stderr = os.fdopen(2, 'a', 1)
            print >>sys.stderr, "oem-config %s" % VERSION
        except (IOError, OSError), err:
            if err.errno != errno.EACCES:
                raise

    if 'OEM_CONFIG_DEBUG' in os.environ:
        if 'DEBCONF_DEBUG' not in os.environ:
            os.environ['DEBCONF_DEBUG'] = 'developer|filter'

    if not options.query:
        disable_autologin()
    wizard = Wizard(frontend_name=options.frontend, query=options.query)
    if options.query:
        print os.environ['OEM_CONFIG_FRONTEND']
        sys.exit(0)
    else:
        if os.environ['OEM_CONFIG_FRONTEND'] == 'debconf_ui':
            open_terminal()
            start_debconf()
        ret = wizard.run()
        run_hooks()
        im_switch.kill_im()
        wizard.stop()
        sys.exit(ret)
