582 lines
17 KiB
Python
582 lines
17 KiB
Python
# utils.py
|
|
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
|
|
#
|
|
# Copyright (c) 2004-2013 Canonical
|
|
#
|
|
# Authors: Michael Vogt <mvo@debian.org>
|
|
# Michael Terry <michael.terry@canonical.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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
# USA
|
|
|
|
import locale
|
|
import logging
|
|
import os
|
|
import platform
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from copy import copy
|
|
from gettext import gettext as _
|
|
from gettext import ngettext
|
|
from math import ceil
|
|
from stat import S_IMODE, S_IXUSR, ST_MODE
|
|
from urllib.parse import urlsplit
|
|
from urllib.request import (
|
|
ProxyHandler,
|
|
Request,
|
|
build_opener,
|
|
install_opener,
|
|
urlopen,
|
|
)
|
|
|
|
import apt
|
|
import apt_pkg
|
|
|
|
apt_pkg.init_config()
|
|
|
|
|
|
class ExecutionTime(object):
|
|
"""
|
|
Helper that can be used in with statements to have a simple
|
|
measure of the timing of a particular block of code, e.g.
|
|
with ExecutionTime("db flush"):
|
|
db.flush()
|
|
"""
|
|
|
|
def __init__(self, info=""):
|
|
self.info = info
|
|
|
|
def __enter__(self):
|
|
self.now = time.time()
|
|
|
|
def __exit__(self, type, value, stack):
|
|
print("%s: %s" % (self.info, time.time() - self.now))
|
|
|
|
|
|
class SoftwarePropertiesPage():
|
|
ubuntu_software = 0
|
|
other_software = 1
|
|
updates = 2
|
|
authentication = 3
|
|
additional_drivers = 4
|
|
developer_options = 5
|
|
ubuntu_pro = 6
|
|
|
|
|
|
def get_string_with_no_auth_from_source_entry(entry):
|
|
tmp = copy(entry)
|
|
url_parts = urlsplit(tmp.uri)
|
|
if url_parts.username:
|
|
tmp.uri = tmp.uri.replace(url_parts.username, "hidden-u")
|
|
if url_parts.password:
|
|
tmp.uri = tmp.uri.replace(url_parts.password, "hidden-p")
|
|
return str(tmp)
|
|
|
|
|
|
def is_unity_running():
|
|
"""return True if Unity is currently running"""
|
|
unity_running = False
|
|
try:
|
|
import dbus
|
|
|
|
bus = dbus.SessionBus()
|
|
unity_running = bus.name_has_owner("com.canonical.Unity")
|
|
except Exception:
|
|
logging.exception("could not check for Unity dbus service")
|
|
return unity_running
|
|
|
|
|
|
def is_child_of_process_name(processname, pid=None):
|
|
if not pid:
|
|
pid = os.getpid()
|
|
while pid > 0:
|
|
stat_file = "/proc/%s/stat" % pid
|
|
with open(stat_file) as stat_f:
|
|
stat = stat_f.read()
|
|
# extract command (inside ())
|
|
command = stat.partition("(")[2].rpartition(")")[0]
|
|
if command == processname:
|
|
return True
|
|
# get parent (second to the right of command) and check that next
|
|
pid = int(stat.rpartition(")")[2].split()[1])
|
|
return False
|
|
|
|
|
|
def inside_chroot():
|
|
"""returns True if we are inside a chroot"""
|
|
# if there is no proc or no pid 1 we are very likely inside a chroot
|
|
if not os.path.exists("/proc") or not os.path.exists("/proc/1"):
|
|
return True
|
|
# if the inode is differnt for pid 1 "/" and our "/"
|
|
return os.stat("/") != os.stat("/proc/1/root")
|
|
|
|
|
|
def wrap(t, width=70, subsequent_indent=""):
|
|
"""helpers inspired after textwrap - unfortunately
|
|
we can not use textwrap directly because it break
|
|
packagenames with "-" in them into new lines
|
|
"""
|
|
out = ""
|
|
for s in t.split():
|
|
if (len(out) - out.rfind("\n")) + len(s) > width:
|
|
out += "\n" + subsequent_indent
|
|
out += s + " "
|
|
return out
|
|
|
|
|
|
def twrap(s, **kwargs):
|
|
msg = ""
|
|
paras = s.split("\n")
|
|
for par in paras:
|
|
s = wrap(par, **kwargs)
|
|
msg += s + "\n"
|
|
return msg
|
|
|
|
|
|
def lsmod():
|
|
"return list of loaded modules (or [] if lsmod is not found)"
|
|
modules = []
|
|
# FIXME raise?
|
|
if not os.path.exists("/sbin/lsmod"):
|
|
return []
|
|
p = subprocess.Popen(
|
|
["/sbin/lsmod"], stdout=subprocess.PIPE, universal_newlines=True
|
|
)
|
|
lines = p.communicate()[0].split("\n")
|
|
# remove heading line: "Modules Size Used by"
|
|
del lines[0]
|
|
# add lines to list, skip empty lines
|
|
for line in lines:
|
|
if line:
|
|
modules.append(line.split()[0])
|
|
return modules
|
|
|
|
|
|
def check_and_fix_xbit(path):
|
|
"check if a given binary has the executable bit and if not, add it"
|
|
if not os.path.exists(path):
|
|
return
|
|
mode = S_IMODE(os.stat(path)[ST_MODE])
|
|
if not ((mode & S_IXUSR) == S_IXUSR):
|
|
os.chmod(path, mode | S_IXUSR)
|
|
|
|
|
|
def country_mirror():
|
|
"helper to get the country mirror from the current locale"
|
|
# special cases go here
|
|
lang_mirror = {"c": ""}
|
|
# no lang, no mirror
|
|
if "LANG" not in os.environ:
|
|
return ""
|
|
lang = os.environ["LANG"].lower()
|
|
# check if it is a special case
|
|
if lang[:5] in lang_mirror:
|
|
return lang_mirror[lang[:5]]
|
|
# now check for the most comon form (en_US.UTF-8)
|
|
if "_" in lang:
|
|
country = lang.split(".")[0].split("_")[1]
|
|
if "@" in country:
|
|
country = country.split("@")[0]
|
|
return country + "."
|
|
else:
|
|
return lang[:2] + "."
|
|
return ""
|
|
|
|
|
|
def get_dist():
|
|
"return the codename of the current runing distro"
|
|
# support debug overwrite
|
|
dist = os.environ.get("META_RELEASE_FAKE_CODENAME")
|
|
if dist:
|
|
logging.warning(
|
|
"using fake release name '%s' (because of "
|
|
"META_RELEASE_FAKE_CODENAME environment) " % dist
|
|
)
|
|
return dist
|
|
|
|
os_release = platform.freedesktop_os_release()
|
|
return os_release.get("VERSION_CODENAME", "unknown distribution")
|
|
|
|
|
|
def get_dist_version():
|
|
"return the version of the current running distro"
|
|
# support debug overwrite
|
|
desc = os.environ.get("META_RELEASE_FAKE_VERSION")
|
|
if desc:
|
|
logging.warning(
|
|
"using fake release version '%s' (because of "
|
|
"META_RELEASE_FAKE_VERSION environment) " % desc
|
|
)
|
|
return desc
|
|
|
|
os_release = platform.freedesktop_os_release()
|
|
return os_release.get("VERSION_ID", "unknown distribution")
|
|
|
|
|
|
class HeadRequest(Request):
|
|
def get_method(self):
|
|
return "HEAD"
|
|
|
|
|
|
def url_downloadable(uri, debug_func=None):
|
|
"""
|
|
helper that checks if the given uri exists and is downloadable
|
|
(supports optional debug_func function handler to support
|
|
e.g. logging)
|
|
|
|
Supports http (via HEAD) and ftp (via size request)
|
|
"""
|
|
if not debug_func:
|
|
lambda x: True
|
|
debug_func("url_downloadable: %s" % uri)
|
|
(scheme, netloc, path, querry, fragment) = urlsplit(uri)
|
|
debug_func(
|
|
"s='%s' n='%s' p='%s' q='%s' f='%s'"
|
|
% (scheme, netloc, path, querry, fragment)
|
|
)
|
|
if scheme in ("http", "https"):
|
|
try:
|
|
http_file = urlopen(HeadRequest(uri))
|
|
http_file.close()
|
|
if http_file.code == 200:
|
|
return True
|
|
return False
|
|
except Exception as e:
|
|
debug_func("error from httplib: '%s'" % e)
|
|
return False
|
|
elif scheme == "ftp":
|
|
import ftplib
|
|
|
|
try:
|
|
f = ftplib.FTP(netloc)
|
|
f.login()
|
|
f.cwd(os.path.dirname(path))
|
|
size = f.size(os.path.basename(path))
|
|
f.quit()
|
|
if debug_func:
|
|
debug_func("ftplib.size() returned: %s" % size)
|
|
if size != 0:
|
|
return True
|
|
except Exception as e:
|
|
if debug_func:
|
|
debug_func("error from ftplib: '%s'" % e)
|
|
return False
|
|
return False
|
|
|
|
|
|
def init_proxy(gsettings=None):
|
|
"""init proxy settings
|
|
|
|
* use apt.conf http proxy if present,
|
|
* otherwise look into synaptics config file,
|
|
* otherwise the default behavior will use http_proxy environment
|
|
if present
|
|
"""
|
|
SYNAPTIC_CONF_FILE = "/root/.synaptic/synaptic.conf"
|
|
proxies = {}
|
|
# generic apt config wins
|
|
if apt_pkg.config.find("Acquire::http::Proxy") != "":
|
|
proxies["http"] = apt_pkg.config.find("Acquire::http::Proxy")
|
|
# then synaptic
|
|
elif os.path.exists(SYNAPTIC_CONF_FILE):
|
|
cnf = apt_pkg.Configuration()
|
|
apt_pkg.read_config_file(cnf, SYNAPTIC_CONF_FILE)
|
|
use_proxy = cnf.find_b("Synaptic::useProxy", False)
|
|
if use_proxy:
|
|
proxy_host = cnf.find("Synaptic::httpProxy")
|
|
proxy_port = str(cnf.find_i("Synaptic::httpProxyPort"))
|
|
if proxy_host and proxy_port:
|
|
proxies["http"] = "http://%s:%s/" % (proxy_host, proxy_port)
|
|
if apt_pkg.config.find("Acquire::https::Proxy") != "":
|
|
proxies["https"] = apt_pkg.config.find("Acquire::https::Proxy")
|
|
elif "http" in proxies:
|
|
proxies["https"] = proxies["http"]
|
|
# if we have a proxy, set it
|
|
if proxies:
|
|
# basic verification
|
|
for proxy in proxies.values():
|
|
if not re.match("https?://\\w+", proxy):
|
|
print("proxy '%s' looks invalid" % proxy, file=sys.stderr)
|
|
return
|
|
proxy_support = ProxyHandler(proxies)
|
|
opener = build_opener(proxy_support)
|
|
install_opener(opener)
|
|
if "http" in proxies:
|
|
os.putenv("http_proxy", proxies["http"])
|
|
if "https" in proxies:
|
|
os.putenv("https_proxy", proxies["https"])
|
|
return proxies
|
|
|
|
|
|
def on_battery():
|
|
"""
|
|
Check via dbus if the system is running on battery.
|
|
This function is using UPower per default, if UPower is not
|
|
available it falls-back to DeviceKit.Power.
|
|
"""
|
|
try:
|
|
import dbus
|
|
|
|
bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM)
|
|
try:
|
|
devobj = bus.get_object(
|
|
"org.freedesktop.UPower", "/org/freedesktop/UPower"
|
|
)
|
|
dev = dbus.Interface(devobj, "org.freedesktop.DBus.Properties")
|
|
return dev.Get("org.freedesktop.UPower", "OnBattery")
|
|
except dbus.exceptions.DBusException as e:
|
|
error_unknown = "org.freedesktop.DBus.Error.ServiceUnknown"
|
|
if e._dbus_error_name != error_unknown:
|
|
raise
|
|
devobj = bus.get_object(
|
|
"org.freedesktop.DeviceKit.Power",
|
|
"/org/freedesktop/DeviceKit/Power",
|
|
)
|
|
dev = dbus.Interface(devobj, "org.freedesktop.DBus.Properties")
|
|
return dev.Get("org.freedesktop.DeviceKit.Power", "on_battery")
|
|
except Exception:
|
|
# import sys
|
|
# print("on_battery returned error: ", e, file=sys.stderr)
|
|
return False
|
|
|
|
|
|
def inhibit_sleep():
|
|
"""
|
|
Send a dbus signal to logind to not suspend the system, it will be
|
|
released when the return value drops out of scope
|
|
"""
|
|
try:
|
|
from gi.repository import Gio, GLib
|
|
|
|
connection = Gio.bus_get_sync(Gio.BusType.SYSTEM)
|
|
|
|
var, fdlist = connection.call_with_unix_fd_list_sync(
|
|
"org.freedesktop.login1",
|
|
"/org/freedesktop/login1",
|
|
"org.freedesktop.login1.Manager",
|
|
"Inhibit",
|
|
GLib.Variant(
|
|
"(ssss)",
|
|
(
|
|
"shutdown:sleep",
|
|
"UpdateManager",
|
|
"Updating System",
|
|
"block",
|
|
),
|
|
),
|
|
None,
|
|
0,
|
|
-1,
|
|
None,
|
|
None,
|
|
)
|
|
inhibitor = Gio.UnixInputStream(fd=fdlist.steal_fds()[var[0]])
|
|
|
|
return inhibitor
|
|
except Exception:
|
|
# print("could not send the dbus Inhibit signal: %s" % e)
|
|
return False
|
|
|
|
|
|
def str_to_bool(str):
|
|
if str == "0" or str.upper() == "FALSE":
|
|
return False
|
|
return True
|
|
|
|
|
|
def get_lang():
|
|
import logging
|
|
|
|
try:
|
|
(locale_s, encoding) = locale.getdefaultlocale()
|
|
return locale_s
|
|
except Exception:
|
|
logging.exception("gedefaultlocale() failed")
|
|
return None
|
|
|
|
|
|
def get_ubuntu_flavor(cache=None):
|
|
"""try to guess the flavor based on the running desktop"""
|
|
# this will (of course) not work in a server environment,
|
|
# but the main use case for this is to show the right
|
|
# release notes.
|
|
pkg = get_ubuntu_flavor_package(cache=cache)
|
|
return pkg.split("-", 1)[0]
|
|
|
|
|
|
def _load_meta_pkg_list():
|
|
# This could potentially introduce a circular dependency, but the config
|
|
# parser logic is simple, and doesn't rely on any UpdateManager code.
|
|
from DistUpgrade.DistUpgradeConfigParser import DistUpgradeConfig
|
|
|
|
parser = DistUpgradeConfig("/usr/share/ubuntu-release-upgrader")
|
|
return parser.getlist("Distro", "MetaPkgs")
|
|
|
|
|
|
def get_ubuntu_flavor_package(cache=None):
|
|
"""try to guess the flavor metapackage based on the running desktop"""
|
|
# From spec, first if ubuntu-desktop is installed, use that.
|
|
# Second, grab first installed one from DistUpgrade.cfg.
|
|
# Lastly, fallback to ubuntu-desktop again.
|
|
meta_pkgs = ["ubuntu-desktop"]
|
|
|
|
try:
|
|
meta_pkgs.extend(sorted(_load_meta_pkg_list()))
|
|
except Exception as e:
|
|
print("Could not load list of meta packages:", e)
|
|
|
|
if cache is None:
|
|
cache = apt.Cache()
|
|
for meta_pkg in meta_pkgs:
|
|
cache_pkg = cache[meta_pkg] if meta_pkg in cache else None
|
|
if cache_pkg and cache_pkg.is_installed:
|
|
return meta_pkg
|
|
return "ubuntu-desktop"
|
|
|
|
|
|
def get_ubuntu_flavor_name(cache=None):
|
|
"""try to guess the flavor name based on the running desktop"""
|
|
pkg = get_ubuntu_flavor_package(cache=cache)
|
|
lookup = {"ubuntustudio-desktop": "Ubuntu Studio"}
|
|
if pkg in lookup:
|
|
return lookup[pkg]
|
|
elif pkg.endswith("-desktop"):
|
|
return capitalize_first_word(pkg.rsplit("-desktop", 1)[0])
|
|
elif pkg.endswith("-netbook"):
|
|
return capitalize_first_word(pkg.rsplit("-netbook", 1)[0])
|
|
else:
|
|
return "Ubuntu"
|
|
|
|
|
|
# Unused by update-manager, but still used by ubuntu-release-upgrader
|
|
def error(parent, summary, message):
|
|
import gi
|
|
|
|
gi.require_version("Gtk", "3.0")
|
|
from gi.repository import Gdk, Gtk
|
|
|
|
d = Gtk.MessageDialog(
|
|
parent=parent,
|
|
flags=Gtk.DialogFlags.MODAL,
|
|
type=Gtk.MessageType.ERROR,
|
|
buttons=Gtk.ButtonsType.CLOSE,
|
|
)
|
|
d.set_markup("<big><b>%s</b></big>\n\n%s" % (summary, message))
|
|
d.realize()
|
|
d.get_window().set_functions(Gdk.WMFunction.MOVE)
|
|
d.set_title("")
|
|
d.run()
|
|
d.destroy()
|
|
return False
|
|
|
|
|
|
def humanize_size(bytes):
|
|
"""
|
|
Convert a given size in bytes to a nicer better readable unit
|
|
"""
|
|
|
|
if bytes < 1000 * 1000:
|
|
# to have 0 for 0 bytes, 1 for 0-1000 bytes and for 1 and above
|
|
# round up
|
|
size_in_kb = int(ceil(bytes / float(1000)))
|
|
# TRANSLATORS: download size of small updates, e.g. "250 kB"
|
|
return ngettext("%(size).0f kB", "%(size).0f kB", size_in_kb) % {
|
|
"size": size_in_kb
|
|
}
|
|
else:
|
|
# TRANSLATORS: download size of updates, e.g. "2.3 MB"
|
|
return locale.format_string(_("%.1f MB"), bytes / 1000.0 / 1000.0)
|
|
|
|
|
|
def get_arch():
|
|
return apt_pkg.config.find("APT::Architecture")
|
|
|
|
|
|
def is_port_already_listening(port):
|
|
"""check if the current system is listening on the given tcp port"""
|
|
# index in the line
|
|
INDEX_LOCAL_ADDR = 1
|
|
# INDEX_REMOTE_ADDR = 2
|
|
INDEX_STATE = 3
|
|
# state (st) that we care about
|
|
STATE_LISTENING = "0A"
|
|
# read the data
|
|
with open("/proc/net/tcp") as net_tcp:
|
|
for line in net_tcp.readlines():
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
# split, values are:
|
|
# sl local_address rem_address st tx_queue rx_queue tr
|
|
# tm->when retrnsmt uid timeout inode
|
|
values = line.split()
|
|
state = values[INDEX_STATE]
|
|
if state != STATE_LISTENING:
|
|
continue
|
|
local_port_str = values[INDEX_LOCAL_ADDR].split(":")[1]
|
|
local_port = int(local_port_str, 16)
|
|
if local_port == port:
|
|
return True
|
|
return False
|
|
|
|
|
|
def iptables_active():
|
|
"""Return True if iptables is active"""
|
|
# FIXME: is there a better way?
|
|
iptables_empty = """Chain INPUT (policy ACCEPT)
|
|
target prot opt source destination
|
|
|
|
Chain FORWARD (policy ACCEPT)
|
|
target prot opt source destination
|
|
|
|
Chain OUTPUT (policy ACCEPT)
|
|
target prot opt source destination
|
|
"""
|
|
if os.getuid() != 0:
|
|
raise OSError("Need root to check the iptables state")
|
|
if not os.path.exists("/sbin/iptables"):
|
|
return False
|
|
out = subprocess.Popen(
|
|
["iptables", "-nL"], stdout=subprocess.PIPE, universal_newlines=True
|
|
).communicate()[0]
|
|
if out == iptables_empty:
|
|
return False
|
|
return True
|
|
|
|
|
|
def capitalize_first_word(string):
|
|
"""this uppercases the first word's first letter"""
|
|
if len(string) > 1 and string[0].isalpha() and not string[0].isupper():
|
|
return string[0].capitalize() + string[1:]
|
|
return string
|
|
|
|
|
|
def get_package_label(pkg):
|
|
"""this takes a package synopsis and uppercases the first word's
|
|
first letter
|
|
"""
|
|
name = getattr(pkg.candidate, "summary", "")
|
|
return capitalize_first_word(name)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# print(mirror_from_sources_list())
|
|
# print(on_battery())
|
|
# print(inside_chroot())
|
|
# print(iptables_active())
|
|
error(None, "bar", "baz")
|