~ubuntu-core-dev/ubuntu-release-upgrader/trunk

« back to all changes in this revision

Viewing changes to DistUpgrade/DistUpgradeMain.py

  • Committer: Balint Reczey
  • Date: 2019-12-17 20:29:55 UTC
  • Revision ID: balint.reczey@canonical.com-20191217202955-nqe4xz2c54s60y59
Moved to git at https://git.launchpad.net/ubuntu-release-upgrader

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