3
# Copyright (c) 2004,2005 Canonical
5
# Author: Michael Vogt <michael.vogt@ubuntu.com>
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.
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.
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
35
from configparser import NoSectionError, NoOptionError
36
from subprocess import PIPE, Popen
38
from .DistUpgradeView import DistUpgradeView, InstallProgress, AcquireProgress
39
from .telemetry import get as get_telemetry
40
from .DistUpgradeConfigParser import DistUpgradeConfig
43
class NonInteractiveAcquireProgress(AcquireProgress):
44
def update_status(self, uri, descr, shortDescr, status):
45
AcquireProgress.update_status(self, uri, descr, shortDescr, status)
46
#logging.debug("Fetch: updateStatus %s %s" % (uri, status))
47
if status == apt_pkg.STAT_DONE:
48
print("fetched %s (%.2f/100) at %sb/s" % (
49
uri, self.percent, apt_pkg.size_to_str(int(self.current_cps))))
50
if sys.stdout.isatty():
54
class NonInteractiveInstallProgress(InstallProgress):
56
Non-interactive version of the install progress class
58
This ensures that conffile prompts are handled and that
59
hanging scripts are killed after a (long) timeout via ctrl-c
62
def __init__(self, logdir):
63
InstallProgress.__init__(self)
64
logging.debug("setting up environ for non-interactive use")
65
if "DEBIAN_FRONTEND" not in os.environ:
66
os.environ["DEBIAN_FRONTEND"] = "noninteractive"
67
os.environ["APT_LISTCHANGES_FRONTEND"] = "none"
68
os.environ["RELEASE_UPRADER_NO_APPORT"] = "1"
69
self.config = DistUpgradeConfig(".")
71
self.install_run_number = 0
73
if self.config.getWithDefault("NonInteractive","ForceOverwrite", False):
74
apt_pkg.config.set("DPkg::Options::","--force-overwrite")
75
except (NoSectionError, NoOptionError):
78
#apt_pkg.config.set("Debug::pkgOrderList","true")
79
#apt_pkg.config.set("Debug::pkgDPkgPM","true")
80
# default to 2400 sec timeout
83
self.timeout = self.config.getint("NonInteractive","TerminalTimeout")
87
def error(self, pkg, errormsg):
88
logging.error("got a error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
89
# check if re-run of maintainer script is requested
90
if not self.config.getWithDefault(
91
"NonInteractive","DebugBrokenScripts", False):
93
# re-run maintainer script with sh -x/perl debug to get a better
94
# idea what went wrong
96
# FIXME: this is just a approximation for now, we also need
98
# - a version after remove (if upgrade to new version)
100
# not everything is a shell or perl script
102
# if the new preinst fails, its not yet in /var/lib/dpkg/info
103
# so this is inaccurate as well
104
environ = copy.copy(os.environ)
105
environ["PYCENTRAL"] = "debug"
108
# find what maintainer script failed
109
if "post-installation" in errormsg:
110
prefix = "/var/lib/dpkg/info/"
112
argument = "configure"
113
maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
114
elif "pre-installation" in errormsg:
115
prefix = "/var/lib/dpkg/tmp.ci/"
116
#prefix = "/var/lib/dpkg/info/"
119
maintainer_script = "%s/%s" % (prefix, name)
120
elif "pre-removal" in errormsg:
121
prefix = "/var/lib/dpkg/info/"
124
maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
125
elif "post-removal" in errormsg:
126
prefix = "/var/lib/dpkg/info/"
129
maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
131
print("UNKNOWN (trigger?) dpkg/script failure for %s (%s) " % (pkg, errormsg))
134
# find out about the interpreter
135
if not os.path.exists(maintainer_script):
136
logging.error("can not find failed maintainer script '%s' " % maintainer_script)
138
with open(maintainer_script) as f:
139
interp = f.readline()[2:].strip().split()[0]
140
if ("bash" in interp) or ("/bin/sh" in interp):
142
elif ("perl" in interp):
144
environ["PERLDB_OPTS"] = "AutoTrace NonStop"
146
logging.warning("unknown interpreter: '%s'" % interp)
148
# check if debconf is used and fiddle a bit more if it is
149
with open(maintainer_script) as f:
150
maintainer_script_text = f.read()
151
if ". /usr/share/debconf/confmodule" in maintainer_script_text:
152
environ["DEBCONF_DEBUG"] = "developer"
153
environ["DEBIAN_HAS_FRONTEND"] = "1"
154
interp = "/usr/share/debconf/frontend"
155
debug_opts = ["sh","-ex"]
159
cmd.extend(debug_opts)
160
cmd.append(maintainer_script)
163
# check if we need to pass a version
164
if name == "postinst":
165
version = Popen("dpkg-query -s %s|grep ^Config-Version" % pkg,
166
shell=True, stdout=PIPE,
167
universal_newlines=True).communicate()[0]
169
cmd.append(version.split(":",1)[1].strip())
170
elif name == "preinst":
171
pkg = os.path.basename(pkg)
172
pkg = pkg.split("_")[0]
173
version = Popen("dpkg-query -s %s|grep ^Version" % pkg,
174
shell=True, stdout=PIPE,
175
universal_newlines=True).communicate()[0]
177
cmd.append(version.split(":",1)[1].strip())
179
logging.debug("re-running '%s' (%s)" % (cmd, environ))
180
ret = subprocess.call(cmd, env=environ)
181
logging.debug("%s script returned: %s" % (name,ret))
183
def conffile(self, current, new):
184
logging.warning("got a conffile-prompt from dpkg for file: '%s'" %
186
# looks like we have a race here *sometimes*
190
os.write(self.master_fd, b"n\n")
191
logging.warning("replied no to the conffile-prompt for file: '%s'" %
193
except Exception as e:
194
logging.error("error '%s' when trying to write to the conffile"%e)
196
def start_update(self):
197
InstallProgress.start_update(self)
198
self.last_activity = time.time()
199
progress_log = self.config.getWithDefault("NonInteractive","DpkgProgressLog", False)
201
fullpath = os.path.join(self.logdir, "dpkg-progress.%s.log" % self.install_run_number)
202
logging.debug("writing dpkg progress log to '%s'" % fullpath)
203
self.dpkg_progress_log = open(fullpath, "w")
205
self.dpkg_progress_log = open(os.devnull, "w")
206
self.dpkg_progress_log.write("%s: Start\n" % time.time())
207
def finish_update(self):
208
InstallProgress.finish_update(self)
209
self.dpkg_progress_log.write("%s: Finished\n" % time.time())
210
self.dpkg_progress_log.close()
211
self.install_run_number += 1
212
def status_change(self, pkg, percent, status_str):
213
self.dpkg_progress_log.write("%s:%s:%s:%s\n" % (time.time(),
217
def update_interface(self):
218
InstallProgress.update_interface(self)
219
if self.statusfd == None:
221
if (self.last_activity + self.timeout) < time.time():
222
logging.warning("no activity %s seconds (%s) - sending ctrl-c" % (
223
self.timeout, self.status))
225
os.write(self.master_fd,chr(3))
226
# read master fd and write to stdout so that terminal output
228
res = select.select([self.master_fd],[],[],0.1)
229
while len(res[0]) > 0:
230
self.last_activity = time.time()
232
s = os.read(self.master_fd, 1)
233
sys.stdout.write("%s" % s.decode(
234
locale.getpreferredencoding(), errors='ignore'))
236
# happens after we are finished because the fd is closed
238
res = select.select([self.master_fd],[],[],0.1)
243
logging.debug("doing a pty.fork()")
244
# some maintainer scripts fail without
245
os.environ["TERM"] = "dumb"
246
# unset PAGER so that we can do "diff" in the dpkg prompt
247
os.environ["PAGER"] = "true"
248
(self.pid, self.master_fd) = pty.fork()
250
logging.debug("pid is: %s" % self.pid)
254
class DistUpgradeViewNonInteractive(DistUpgradeView):
255
" non-interactive version of the upgrade view "
256
def __init__(self, datadir=None, logdir=None):
257
DistUpgradeView.__init__(self)
258
get_telemetry().set_updater_type('NonInteractive')
259
self.config = DistUpgradeConfig(".")
260
self._acquireProgress = NonInteractiveAcquireProgress()
261
self._installProgress = NonInteractiveInstallProgress(logdir)
262
self._opProgress = apt.progress.base.OpProgress()
263
sys.__excepthook__ = self.excepthook
264
def excepthook(self, type, value, tb):
265
" on uncaught exceptions -> print error and reboot "
267
logging.exception("got exception '%s': %s " % (type, value))
268
lines = traceback.format_exception(type, value, tb)
269
logging.error("not handled exception:\n%s" % "".join(lines))
270
#sys.excepthook(type, value, tb)
271
self.confirmRestart()
272
def getOpCacheProgress(self):
273
" return a OpProgress() subclass for the given graphic"
274
return self._opProgress
275
def getAcquireProgress(self):
276
" return an acquire progress object "
277
return self._acquireProgress
278
def getInstallProgress(self, cache=None):
279
" return a install progress object "
280
return self._installProgress
281
def updateStatus(self, msg):
282
""" update the current status of the distUpgrade based
286
def setStep(self, step):
287
""" we have 5 steps current for a upgrade:
288
1. Analyzing the system
289
2. Updating repository information
290
3. Performing the upgrade
291
4. Post upgrade stuff
294
super(DistUpgradeViewNonInteractive, self).setStep(step)
296
def confirmChanges(self, summary, changes, demotions, downloadSize,
297
actions=None, removal_bold=True):
298
DistUpgradeView.confirmChanges(self, summary, changes, demotions,
299
downloadSize, actions)
300
logging.debug("toinstall: '%s'" % [p.name for p in self.toInstall])
301
logging.debug("toupgrade: '%s'" % [p.name for p in self.toUpgrade])
302
logging.debug("toremove: '%s'" % [p.name for p in self.toRemove])
304
def askYesNoQuestion(self, summary, msg, default='No'):
305
" ask a Yes/No question and return True on 'Yes' "
306
# if this gets enabled upgrades over ssh with the non-interactive
307
# frontend will no longer work
308
#if default.lower() == "no":
311
def askCancelContinueQuestion(self, summary, msg, default='Cancel'):
313
def confirmRestart(self):
314
" generic ask about the restart, can be overridden "
315
logging.debug("confirmRestart() called")
316
# rebooting here makes sense if we run e.g. in qemu
317
return self.config.getWithDefault("NonInteractive","RealReboot", False)
318
def error(self, summary, msg, extended_msg=None):
320
logging.error("%s %s (%s)" % (summary, msg, extended_msg))
322
logging.error("view.abort called")
325
if __name__ == "__main__":
327
view = DistUpgradeViewNonInteractive()
328
ap = NonInteractiveAcquireProgress()
329
ip = NonInteractiveInstallProgress()
331
#ip.error("linux-image-2.6.17-10-generic","post-installation script failed")
332
ip.error("xserver-xorg","pre-installation script failed")
335
for pkg in sys.argv[1:]:
336
#if cache[pkg].is_installed:
337
# cache[pkg].mark_delete()
339
cache[pkg].mark_install()