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
22
from __future__ import absolute_import, print_function
37
from configparser import NoSectionError, NoOptionError
39
from ConfigParser import NoSectionError, NoOptionError
40
from subprocess import PIPE, Popen
42
from .DistUpgradeView import DistUpgradeView, InstallProgress, AcquireProgress
43
from .DistUpgradeConfigParser import DistUpgradeConfig
45
class NonInteractiveAcquireProgress(AcquireProgress):
46
def update_status(self, uri, descr, shortDescr, status):
47
AcquireProgress.update_status(self, uri, descr, shortDescr, status)
48
#logging.debug("Fetch: updateStatus %s %s" % (uri, status))
49
if status == apt_pkg.STAT_DONE:
50
print("fetched %s (%.2f/100) at %sb/s" % (
51
uri, self.percent, apt_pkg.size_to_str(int(self.current_cps))))
52
if sys.stdout.isatty():
56
class NonInteractiveInstallProgress(InstallProgress):
58
Non-interactive version of the install progress class
60
This ensures that conffile prompts are handled and that
61
hanging scripts are killed after a (long) timeout via ctrl-c
64
def __init__(self, logdir):
65
InstallProgress.__init__(self)
66
logging.debug("setting up environ for non-interactive use")
67
if not os.environ.has_key("DEBIAN_FRONTEND"):
68
os.environ["DEBIAN_FRONTEND"] = "noninteractive"
69
os.environ["APT_LISTCHANGES_FRONTEND"] = "none"
70
os.environ["RELEASE_UPRADER_NO_APPORT"] = "1"
71
self.config = DistUpgradeConfig(".")
73
self.install_run_number = 0
75
if self.config.getWithDefault("NonInteractive","ForceOverwrite", False):
76
apt_pkg.config.set("DPkg::Options::","--force-overwrite")
77
except (NoSectionError, NoOptionError):
80
#apt_pkg.config.set("Debug::pkgOrderList","true")
81
#apt_pkg.config.set("Debug::pkgDPkgPM","true")
82
# default to 2400 sec timeout
85
self.timeout = self.config.getint("NonInteractive","TerminalTimeout")
89
def error(self, pkg, errormsg):
90
logging.error("got a error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
91
# check if re-run of maintainer script is requested
92
if not self.config.getWithDefault(
93
"NonInteractive","DebugBrokenScripts", False):
95
# re-run maintainer script with sh -x/perl debug to get a better
96
# idea what went wrong
98
# FIXME: this is just a approximation for now, we also need
100
# - a version after remove (if upgrade to new version)
102
# not everything is a shell or perl script
104
# if the new preinst fails, its not yet in /var/lib/dpkg/info
105
# so this is inaccurate as well
106
environ = copy.copy(os.environ)
107
environ["PYCENTRAL"] = "debug"
110
# find what maintainer script failed
111
if "post-installation" in errormsg:
112
prefix = "/var/lib/dpkg/info/"
114
argument = "configure"
115
maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
116
elif "pre-installation" in errormsg:
117
prefix = "/var/lib/dpkg/tmp.ci/"
118
#prefix = "/var/lib/dpkg/info/"
121
maintainer_script = "%s/%s" % (prefix, name)
122
elif "pre-removal" in errormsg:
123
prefix = "/var/lib/dpkg/info/"
126
maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
127
elif "post-removal" in errormsg:
128
prefix = "/var/lib/dpkg/info/"
131
maintainer_script = "%s/%s.%s" % (prefix, pkg, name)
133
print("UNKNOWN (trigger?) dpkg/script failure for %s (%s) " % (pkg, errormsg))
136
# find out about the interpreter
137
if not os.path.exists(maintainer_script):
138
logging.error("can not find failed maintainer script '%s' " % maintainer_script)
140
interp = open(maintainer_script).readline()[2:].strip().split()[0]
141
if ("bash" in interp) or ("/bin/sh" in interp):
143
elif ("perl" in interp):
145
environ["PERLDB_OPTS"] = "AutoTrace NonStop"
147
logging.warning("unknown interpreter: '%s'" % interp)
149
# check if debconf is used and fiddle a bit more if it is
150
if ". /usr/share/debconf/confmodule" in open(maintainer_script).read():
151
environ["DEBCONF_DEBUG"] = "developer"
152
environ["DEBIAN_HAS_FRONTEND"] = "1"
153
interp = "/usr/share/debconf/frontend"
154
debug_opts = ["sh","-ex"]
158
cmd.extend(debug_opts)
159
cmd.append(maintainer_script)
162
# check if we need to pass a version
163
if name == "postinst":
164
version = Popen("dpkg-query -s %s|grep ^Config-Version" % pkg,shell=True, stdout=PIPE).communicate()[0]
166
cmd.append(version.split(":",1)[1].strip())
167
elif name == "preinst":
168
pkg = os.path.basename(pkg)
169
pkg = pkg.split("_")[0]
170
version = Popen("dpkg-query -s %s|grep ^Version" % pkg,shell=True, stdout=PIPE).communicate()[0]
172
cmd.append(version.split(":",1)[1].strip())
174
logging.debug("re-running '%s' (%s)" % (cmd, environ))
175
ret = subprocess.call(cmd, env=environ)
176
logging.debug("%s script returned: %s" % (name,ret))
178
def conffile(self, current, new):
179
logging.warning("got a conffile-prompt from dpkg for file: '%s'" % current)
180
# looks like we have a race here *sometimes*
184
os.write(self.master_fd,"n\n")
185
except Exception as e:
186
logging.error("error '%s' when trying to write to the conffile"%e)
188
def start_update(self):
189
InstallProgress.start_update(self)
190
self.last_activity = time.time()
191
progress_log = self.config.getWithDefault("NonInteractive","DpkgProgressLog", False)
193
fullpath = os.path.join(self.logdir, "dpkg-progress.%s.log" % self.install_run_number)
194
logging.debug("writing dpkg progress log to '%s'" % fullpath)
195
self.dpkg_progress_log = open(fullpath, "w")
197
self.dpkg_progress_log = open(os.devnull, "w")
198
self.dpkg_progress_log.write("%s: Start\n" % time.time())
199
def finish_update(self):
200
InstallProgress.finish_update(self)
201
self.dpkg_progress_log.write("%s: Finished\n" % time.time())
202
self.dpkg_progress_log.close()
203
self.install_run_number += 1
204
def status_change(self, pkg, percent, status_str):
205
self.dpkg_progress_log.write("%s:%s:%s:%s\n" % (time.time(),
209
def update_interface(self):
210
InstallProgress.update_interface(self)
211
if self.statusfd == None:
213
if (self.last_activity + self.timeout) < time.time():
214
logging.warning("no activity %s seconds (%s) - sending ctrl-c" % (
215
self.timeout, self.status))
217
os.write(self.master_fd,chr(3))
218
# read master fd and write to stdout so that terminal output
220
res = select.select([self.master_fd],[],[],0.1)
221
while len(res[0]) > 0:
222
self.last_activity = time.time()
224
s = os.read(self.master_fd, 1)
225
sys.stdout.write("%s" % s)
227
# happens after we are finished because the fd is closed
229
res = select.select([self.master_fd],[],[],0.1)
234
logging.debug("doing a pty.fork()")
235
# some maintainer scripts fail without
236
os.environ["TERM"] = "dumb"
237
# unset PAGER so that we can do "diff" in the dpkg prompt
238
os.environ["PAGER"] = "true"
239
(self.pid, self.master_fd) = pty.fork()
241
logging.debug("pid is: %s" % self.pid)
244
class DistUpgradeViewNonInteractive(DistUpgradeView):
245
" non-interactive version of the upgrade view "
246
def __init__(self, datadir=None, logdir=None):
247
DistUpgradeView.__init__(self)
248
self.config = DistUpgradeConfig(".")
249
self._acquireProgress = NonInteractiveAcquireProgress()
250
self._installProgress = NonInteractiveInstallProgress(logdir)
251
self._opProgress = apt.progress.base.OpProgress()
252
sys.__excepthook__ = self.excepthook
253
def excepthook(self, type, value, traceback):
254
" on uncaught exceptions -> print error and reboot "
255
logging.exception("got exception '%s': %s " % (type, value))
256
#sys.excepthook(type, value, traceback)
257
self.confirmRestart()
258
def getOpCacheProgress(self):
259
" return a OpProgress() subclass for the given graphic"
260
return self._opProgress
261
def getAcquireProgress(self):
262
" return an acquire progress object "
263
return self._acquireProgress
264
def getInstallProgress(self, cache=None):
265
" return a install progress object "
266
return self._installProgress
267
def updateStatus(self, msg):
268
""" update the current status of the distUpgrade based
272
def setStep(self, step):
273
""" we have 5 steps current for a upgrade:
274
1. Analyzing the system
275
2. Updating repository information
276
3. Performing the upgrade
277
4. Post upgrade stuff
281
def confirmChanges(self, summary, changes, demotions, downloadSize,
282
actions=None, removal_bold=True):
283
DistUpgradeView.confirmChanges(self, summary, changes, demotions,
284
downloadSize, actions)
285
logging.debug("toinstall: '%s'" % [p.name for p in self.toInstall])
286
logging.debug("toupgrade: '%s'" % [p.name for p in self.toUpgrade])
287
logging.debug("toremove: '%s'" % [p.name for p in self.toRemove])
289
def askYesNoQuestion(self, summary, msg, default='No'):
290
" ask a Yes/No question and return True on 'Yes' "
291
# if this gets enabled upgrades over ssh with the non-interactive
292
# frontend will no longer work
293
#if default.lower() == "no":
296
def confirmRestart(self):
297
" generic ask about the restart, can be overridden "
298
logging.debug("confirmRestart() called")
299
# rebooting here makes sense if we run e.g. in qemu
300
return self.config.getWithDefault("NonInteractive","RealReboot", False)
301
def error(self, summary, msg, extended_msg=None):
303
logging.error("%s %s (%s)" % (summary, msg, extended_msg))
305
logging.error("view.abort called")
308
if __name__ == "__main__":
310
view = DistUpgradeViewNonInteractive()
311
ap = NonInteractiveAcquireProgress()
312
ip = NonInteractiveInstallProgress()
314
#ip.error("linux-image-2.6.17-10-generic","post-installation script failed")
315
ip.error("xserver-xorg","pre-installation script failed")
318
for pkg in sys.argv[1:]:
319
#if cache[pkg].is_installed:
320
# cache[pkg].markDelete()
322
cache[pkg].mark_install()