~ubuntu-core-dev/update-manager/main

« back to all changes in this revision

Viewing changes to DistUpgrade/DistUpgradeMain.py

  • Committer: Michael Terry
  • Date: 2012-06-07 15:06:56 UTC
  • mto: This revision was merged to the branch mainline in revision 2509.
  • Revision ID: michael.terry@canonical.com-20120607150656-7qv9c4fg7o3qk7ab
first pass at splitting out release code

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# DistUpgradeMain.py 
2
 
#  
3
 
#  Copyright (c) 2004-2008 Canonical
4
 
#  
5
 
#  Author: Michael Vogt <michael.vogt@ubuntu.com>
6
 
7
 
#  This program is free software; you can redistribute it and/or 
8
 
#  modify it under the terms of the GNU General Public License as 
9
 
#  published by the Free Software Foundation; either version 2 of the
10
 
#  License, or (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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20
 
#  USA
21
 
 
22
 
from __future__ import absolute_import, print_function
23
 
 
24
 
import warnings
25
 
warnings.filterwarnings("ignore", "Accessed deprecated", DeprecationWarning)
26
 
 
27
 
import apt
28
 
import atexit
29
 
import glob
30
 
import logging
31
 
import os
32
 
import shutil
33
 
import subprocess
34
 
import sys
35
 
 
36
 
from datetime import datetime
37
 
from optparse import OptionParser
38
 
from gettext import gettext as _
39
 
 
40
 
# dirs that the packages will touch, this is needed for AUFS/overlayfs
41
 
# and also for the sanity check before the upgrade
42
 
SYSTEM_DIRS = ["/bin",
43
 
              "/boot",
44
 
              "/etc",
45
 
              "/initrd",
46
 
              "/lib",
47
 
              "/lib32", # ???
48
 
              "/lib64", # ???
49
 
              "/sbin",
50
 
              "/usr",
51
 
              "/var",
52
 
              ]
53
 
 
54
 
 
55
 
 
56
 
from .DistUpgradeController import DistUpgradeController
57
 
from .DistUpgradeConfigParser import DistUpgradeConfig
58
 
 
59
 
 
60
 
def do_commandline():
61
 
    " setup option parser and parse the commandline "
62
 
    parser = OptionParser()
63
 
    parser.add_option("-s", "--sandbox", dest="useAufs", default=False,
64
 
                      action="store_true",
65
 
                      help=_("Sandbox upgrade using aufs"))
66
 
    parser.add_option("-c", "--cdrom", dest="cdromPath", default=None,
67
 
                      help=_("Use the given path to search for a cdrom with upgradable packages"))
68
 
    parser.add_option("--have-prerequists", dest="havePrerequists",
69
 
                      action="store_true", default=False)
70
 
    parser.add_option("--with-network", dest="withNetwork",action="store_true")
71
 
    parser.add_option("--without-network", dest="withNetwork",action="store_false")
72
 
    parser.add_option("--frontend", dest="frontend",default=None,
73
 
                      help=_("Use frontend. Currently available: \n"\
74
 
                             "DistUpgradeViewText, DistUpgradeViewGtk, DistUpgradeViewKDE"))
75
 
    parser.add_option("--mode", dest="mode",default="desktop",
76
 
                      help=_("*DEPRECATED* this option will be ignored"))
77
 
    parser.add_option("--partial", dest="partial", default=False,
78
 
                      action="store_true", 
79
 
                      help=_("Perform a partial upgrade only (no sources.list rewriting)"))
80
 
    parser.add_option("--disable-gnu-screen", action="store_true", 
81
 
                      default=False,
82
 
                      help=_("Disable GNU screen support"))
83
 
    parser.add_option("--datadir", dest="datadir", default=None,
84
 
                      help=_("Set datadir"))
85
 
    return parser.parse_args()
86
 
    
87
 
def setup_logging(options, config):
88
 
    " setup the logging "
89
 
    logdir = config.getWithDefault("Files","LogDir","/var/log/dist-upgrade/")
90
 
    if not os.path.exists(logdir):
91
 
        os.mkdir(logdir)
92
 
    # check if logs exists and move logs into place
93
 
    if glob.glob(logdir+"/*.log"):
94
 
        now = datetime.now()
95
 
        backup_dir = logdir+"/%04i%02i%02i-%02i%02i" % (now.year,now.month,now.day,now.hour,now.minute)
96
 
        if not os.path.exists(backup_dir):
97
 
            os.mkdir(backup_dir)
98
 
        for f in glob.glob(logdir+"/*.log"):
99
 
            shutil.move(f, os.path.join(backup_dir,os.path.basename(f)))
100
 
    fname = os.path.join(logdir,"main.log")
101
 
    # do not overwrite the default main.log
102
 
    if options.partial:
103
 
        fname += ".partial"
104
 
    with open(fname, "a"):
105
 
        pass
106
 
    logging.basicConfig(level=logging.DEBUG,
107
 
                        filename=fname,
108
 
                        format='%(asctime)s %(levelname)s %(message)s',
109
 
                        filemode='w')
110
 
    # log what config files are in use here to detect user
111
 
    # changes
112
 
    logging.info("Using config files '%s'" % config.config_files)
113
 
    logging.info("uname information: '%s'" % " ".join(os.uname()))
114
 
    logging.info("apt version: '%s'" % apt.apt_pkg.VERSION)
115
 
    return logdir
116
 
 
117
 
def save_system_state(logdir):
118
 
    # save package state to be able to re-create failures
119
 
    try:
120
 
        from .apt_clone import AptClone
121
 
    except ImportError:
122
 
        logging.error("failed to import AptClone")
123
 
        return
124
 
    target = os.path.join(logdir, "apt-clone_system_state.tar.gz")
125
 
    logging.debug("creating statefile: '%s'" % target)
126
 
    # this file may contain sensitive data so ensure we create with the
127
 
    # right umask
128
 
    old_umask = os.umask(0o0066)
129
 
    clone = AptClone()
130
 
    clone.save_state(sourcedir="/", target=target, with_dpkg_status=True)
131
 
    # reset umask
132
 
    os.umask(old_umask)
133
 
    # lspci output
134
 
    try:
135
 
        s=subprocess.Popen(["lspci","-nn"], stdout=subprocess.PIPE,
136
 
                           universal_newlines=True).communicate()[0]
137
 
        open(os.path.join(logdir, "lspci.txt"), "w").write(s)
138
 
    except OSError as e:
139
 
        logging.debug("lspci failed: %s" % e)
140
 
    
141
 
def setup_view(options, config, logdir):
142
 
    " setup view based on the config and commandline "
143
 
 
144
 
    # the commandline overwrites the configfile
145
 
    for requested_view in [options.frontend]+config.getlist("View","View"):
146
 
        if not requested_view:
147
 
            continue
148
 
        try:
149
 
            # this should work with py3 and py2.7
150
 
            from importlib import import_module
151
 
            # use relative imports
152
 
            view_modul = import_module("."+requested_view, "DistUpgrade")
153
 
            # won't work with py3
154
 
            #view_modul = __import__(requested_view, globals())
155
 
            view_class = getattr(view_modul, requested_view)
156
 
            instance = view_class(logdir=logdir)
157
 
            break
158
 
        except Exception as e:
159
 
            logging.warning("can't import view '%s' (%s)" % (requested_view,e))
160
 
            print("can't load %s (%s)" % (requested_view, e))
161
 
    else:
162
 
        logging.error("No view can be imported, aborting")
163
 
        print("No view can be imported, aborting")
164
 
        sys.exit(1)
165
 
    return instance
166
 
 
167
 
def run_new_gnu_screen_window_or_reattach():
168
 
    """ check if there is a upgrade already running inside gnu screen,
169
 
        if so, reattach
170
 
        if not, create new screen window
171
 
    """
172
 
    SCREENNAME = "ubuntu-release-upgrade-screen-window"
173
 
    # get the active screen sockets
174
 
    try:
175
 
        out = subprocess.Popen(
176
 
            ["screen","-ls"], stdout=subprocess.PIPE,
177
 
            universal_newlines=True).communicate()[0]
178
 
        logging.debug("screen returned: '%s'" % out)
179
 
    except OSError:
180
 
        logging.info("screen could not be run")
181
 
        return
182
 
    # check if a release upgrade is among them
183
 
    if SCREENNAME in out:
184
 
        logging.info("found active screen session, re-attaching")
185
 
        # if we have it, attach to it
186
 
        os.execv("/usr/bin/screen",  ["screen", "-d", "-r", "-p", SCREENNAME])
187
 
    # otherwise re-exec inside screen with (-L) for logging enabled
188
 
    os.environ["RELEASE_UPGRADER_NO_SCREEN"]="1"
189
 
    # unset escape key to avoid confusing people who are not used to
190
 
    # screen. people who already run screen will not be affected by this
191
 
    # unset escape key with -e, enable log with -L, set name with -S
192
 
    cmd = ["screen", 
193
 
           "-e", "\\0\\0",
194
 
           "-L", 
195
 
           "-c", "screenrc",
196
 
           "-S", SCREENNAME]+sys.argv
197
 
    logging.info("re-exec inside screen: '%s'" % cmd)
198
 
    os.execv("/usr/bin/screen", cmd)
199
 
 
200
 
 
201
 
def main():
202
 
    """ main method """
203
 
    
204
 
    # commandline setup and config
205
 
    (options, args) = do_commandline()
206
 
    config = DistUpgradeConfig(".")
207
 
    logdir = setup_logging(options, config)
208
 
 
209
 
    from .DistUpgradeVersion import VERSION
210
 
    logging.info("release-upgrader version '%s' started" % VERSION)
211
 
 
212
 
    # create view and app objects
213
 
    view = setup_view(options, config, logdir)
214
 
 
215
 
    # gnu screen support
216
 
    if (view.needs_screen and
217
 
        not "RELEASE_UPGRADER_NO_SCREEN" in os.environ and
218
 
        not options.disable_gnu_screen):
219
 
        run_new_gnu_screen_window_or_reattach()
220
 
 
221
 
    app = DistUpgradeController(view, options, datadir=options.datadir)
222
 
    atexit.register(app._enableAptCronJob)
223
 
 
224
 
    # partial upgrade only
225
 
    if options.partial:
226
 
        if not app.doPartialUpgrade():
227
 
            sys.exit(1)
228
 
        sys.exit(0)
229
 
 
230
 
    # save system state (only if not doing just a partial upgrade)
231
 
    save_system_state(logdir)
232
 
 
233
 
    # full upgrade, return error code for success/failure
234
 
    if app.run():
235
 
        return 0
236
 
    return 1
237