diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4b1bb18 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +all:updater + +updater: + cd backend && ./setup.py build diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..70da784 --- /dev/null +++ b/README.MD @@ -0,0 +1,86 @@ +### Software Updater for apt + +- 目录架构: + + ```shell + backend debian Makefile plugin README.MD + ``` + +- 其中分为控制面板插件目录`plugin` and 更新后端目录`backend`,两个模块相互隔离,公用一个包来安装 + +- GitLab分支介绍 + + - backend_dev:后端更新代码维护的分支 + - plugin_dev:控制面板插件维护的分支 + - dev:分支只要负责将后端更新代码和插件的代码进行合并编包测试的分支,最新的代码在此分支上 + - master:负责最终出版本的分支,dev上验证成功后,将代码合并到master上进行编包 + + + +- 安装依赖 + + ``` + sudo apt install dh-python python3-all python3-distutils-extra gir1.2-snapd-1 apt-clone intltool at-spi2-core -y + ``` + +- + + + +### 后端服务: + +- 后端服务主要负责更新、安装、升级等等各种安装和下载的过程处理 + +- 查看后端日志:`tail -f /var/log/kylin-system-updater/kylin-system-updater.log.1` + +- 调试后端代码: + + - 进入backend目录下直接运行`kylin-system-updater` + + - 调试参数 + + ```shell + -d 日志直接输出到终端,不输出到log文件中 + -n 不更新摸板不刷新source.list and important 列表 + -r 替换已经运行的后端程序 + -c 关闭源过滤等各种过滤代码 + + ``` + + +### 配置文件 + +- 名称:`system-updater.conf` + +- 路径:`/var/lib/kylin-system-updater` + +- 配置项: + + ```shell + #自动更新使用 + [AutoUpgrade] + #升级列表,自动更新使用 + upgradelist = + + #系统状态 + [SystemStatus] + #标志是否异常强制关闭 + isabnormalreboot = False + ``` + + + +### 文档 + +#### Aptdaemon + +- https://pythonhosted.org/aptdaemon/aptdaemon.client.html?highlight=commit_packages#aptdaemon.client.AptClient.commit_packages + +#### python-apt + +- https://apt-team.pages.debiahn.net/python-apt/library/apt_pkg.html + +#### 方法与信号接口文档 + +- 参考interface.md 文档 + diff --git a/backend/SystemUpdater/Core/AlertWatcher.py b/backend/SystemUpdater/Core/AlertWatcher.py new file mode 100644 index 0000000..c6f2b52 --- /dev/null +++ b/backend/SystemUpdater/Core/AlertWatcher.py @@ -0,0 +1,103 @@ +# AlertWatcher.py +# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- +# +# Copyright (c) 2010 Mohamed Amine IL Idrissi +# +# Author: Mohamed Amine IL Idrissi +# +# 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 + +from __future__ import absolute_import +import logging + +from gi.repository import GObject +import dbus +from dbus.mainloop.glib import DBusGMainLoop + + +class AlertWatcher(GObject.GObject): + """ a class that checks for alerts and reports them, like a battery + or network warning """ + + __gsignals__ = {"network-alert": (GObject.SignalFlags.RUN_FIRST, + None, + (GObject.TYPE_INT,)), + "battery-alert": (GObject.SignalFlags.RUN_FIRST, + None, + (GObject.TYPE_BOOLEAN,)), + "network-3g-alert": (GObject.SignalFlags.RUN_FIRST, + None, + (GObject.TYPE_BOOLEAN, + GObject.TYPE_BOOLEAN,)), + } + + def __init__(self): + GObject.GObject.__init__(self) + DBusGMainLoop(set_as_default=True) + self.bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM) + # make it always connected if NM isn't available + self.network_state = 3 + + def check_alert_state(self): + try: + #network + obj = self.bus.get_object("org.freedesktop.NetworkManager", + "/org/freedesktop/NetworkManager") + obj.connect_to_signal( + "StateChanged", + self._on_network_state_changed, + dbus_interface="org.freedesktop.NetworkManager") + interface = dbus.Interface(obj, "org.freedesktop.DBus.Properties") + self.network_state = interface.Get( + "org.freedesktop.NetworkManager", "State") + self._network_alert(self.network_state) + + # power + # obj = self.bus.get_object('org.freedesktop.UPower', + # '/org/freedesktop/UPower') + # obj.connect_to_signal("Changed", self._power_changed, + # dbus_interface="org.freedesktop.UPower") + # self._power_changed() + # 3g + # self._update_3g_state() + except dbus.exceptions.DBusException as e: + logging.error(str(e)) + pass + + def _on_network_state_changed(self, state): + self._network_alert(state) + # self._update_3g_state() + + # def _update_3g_state(self): + # from .roam import NetworkManagerHelper + # nm = NetworkManagerHelper() + # on_3g = nm.is_active_connection_gsm_or_cdma() + # is_roaming = nm.is_active_connection_gsm_or_cdma_roaming() + # self._network_3g_alert(on_3g, is_roaming) + + # def _network_3g_alert(self, on_3g, is_roaming): + # self.emit("network-3g-alert", on_3g, is_roaming) + + def _network_alert(self, state): + self.network_state = state + self.emit("network-alert", state) + + # def _power_changed(self): + # obj = self.bus.get_object("org.freedesktop.UPower", + # "/org/freedesktop/UPower") + # interface = dbus.Interface(obj, "org.freedesktop.DBus.Properties") + # on_battery = interface.Get("org.freedesktop.UPower", "OnBattery") + # self.emit("battery-alert", on_battery) diff --git a/backend/SystemUpdater/Core/DataAcquisition.py b/backend/SystemUpdater/Core/DataAcquisition.py new file mode 100644 index 0000000..33b0d20 --- /dev/null +++ b/backend/SystemUpdater/Core/DataAcquisition.py @@ -0,0 +1,602 @@ +# DataAcquisition.py +# supervisory control and data acquisition + +#!/usr/bin/python3 +import os +import json +import dbus +import uuid +import time +import socket +import base64 +import shutil +import hashlib +import logging +import tarfile +import requests +import datetime +import threading + +from email import message +from datetime import datetime +from binascii import a2b_hex +from Crypto.PublicKey import RSA +from urllib import parse, request +from PyQt5.QtCore import QSettings +from SystemUpdater.Core import enums +from Crypto.Cipher import PKCS1_OAEP +from json.decoder import JSONDecodeError +from dbus.exceptions import DBusException +from SystemUpdater.Core.UpdaterConfigParser import UpgradeConfig + +LOCALTIDDIR = "/var/lib/kylin-system-updater/" +LOCALTIDFILE = "tidfile.conf" +MSGSNDDIR = "/var/lib/kylin-system-updater/sendinfos/" + +SOURCE_PKGNAME = { + 'Kylin System Updater': 'kylin-system-updater', + 'Kylin Unattended Upgrade': 'unattended-upgrades' +} + +class UpdateMsgCollector(): + ACTION_DEFUALT_STATUS = -1 + ACTION_UPDATE = 0 + ACTION_INSTALL = 1 + ACTION_INSTALL_DEB = 2 + ACTION_CHECK_RESOLVER = 3 + ACTION_DOWNLOADONLY = 4 + ACTION_FIX_BROKEN = 5 + ACTION_REMOVE_PACKAGES = 6 + ACTION_FIX_INCOMPLETE = 7 + ACTION_CLEAN = 8 + ACTION_INSTALL_SHUTDOWN = 9 + + MODE_DEFAULT_STATUS = -1 + + #1、ACTION_INSTALL 安装的子类 + #部分升级 + MODE_INSTALL_PARTIAL = 0 + #全部升级 + MODE_INSTALL_ALL = 1 + #系统全盘升级 + MODE_INSTALL_SYSTEM = 2 + #后端内部安装包使用 目前 更新配置包和升级本身使用 + MODE_INSTALL_SINGLE = 3 + + #2、更新的子类 + MODE_UPDATE_CACHE = 0 + MODE_UPDATE_ALL = 1 + + mode_map = { + MODE_INSTALL_PARTIAL:"upgrade_system", + MODE_INSTALL_ALL:"upgrade_all", + MODE_INSTALL_PARTIAL:"upgrade_partial" + } + action_map = { + ACTION_CHECK_RESOLVER:enums.MONIT_DEPRESOLUT, + ACTION_INSTALL:enums.MONIT_INSTALL, + ACTION_INSTALL_DEB:enums.MONIT_INSTALLDEB, + 10:enums.MONIT_FINISH, + "finish-update":enums.MONIT_FINISH, + "finish-install":enums.MONIT_FINISH + } + messageType_map = { + # InstallBackend.ACTION_CHECK_RESOLVER:"UpdateDetect", + ACTION_CHECK_RESOLVER:"DepResolution", + # InstallBackend.ACTION_CHECK_RESOLVER:"Downloading", + ACTION_INSTALL:"Installing", + # InstallBackend.ACTION_CHECK_RESOLVER:"UpgradeFinish", + ACTION_INSTALL_DEB:"InstallerInfo", + 10:"Background-Upgrade", + "finish-update":"UpdateInfos", + "finish-install":"InstallInfos" + } + + def __init__(self, manager=None): + self.uuid = '' + self.status = '' + self.upgrade_mode = '' + self.upgrade_action = '' + self.UploadMessage = {} + self.PackageInfo = {} + self.UpdateInfos = {} + self.background_version = {} + self.background_upgradable = [] + self.background_list = [] + self.upgrade_list = [] + self.waitSendList = [] + self.cache = None + self.updateManager = manager + # 转换 & 加密 + self.convertor = FormatConvert(self) + # 发送器 + self.sender = MessageSend(self) + logging.info("Initialize Update MessageSend Collector to success...") + + def GenUploadMessage(self, dict_message, local_uuid = ''): + UploadMessage = {} + # 获取将要上传的数据,获取东八区时间 + UploadMessage['createTimeStamp'] = get_east_8_time() + try: + if "packageName" in dict_message.keys(): + dict_message.pop("packageName") + for key in dict_message.keys(): + UploadMessage[key] = dict_message[key] + if local_uuid != '': + UploadMessage['UUID'] = str(local_uuid) + else: + UploadMessage['UUID'] = str(uuid.uuid1()) + except Exception as e: + logging.error(str(e)) + + json_UploadMessage = self.convertor.dictConvertJson(UploadMessage) + + logging.debug('Generate UploadMessage: %s.',json_UploadMessage) + self.UploadMessage = UploadMessage.copy() + UploadMessage.clear() + + def GenPackageInfo(self, messageType, packageName): + PackageInfo = {} + PackageInfo['messageType'] = str(messageType) + PackageInfo['packageName'] = str(packageName) + key = str(packageName)+'_'+str(messageType) + # 获取本地tid + self.sender.GetLocalTid(key) + PackageInfo["tid"] = str(self.sender.localtid) + + json_PackageInfo = self.convertor.dictConvertJson(PackageInfo) + + logging.debug('Generate PackageInfo: %s.',json_PackageInfo) + self.PackageInfo = PackageInfo.copy() + PackageInfo.clear() + + def setUploadMessage(self, KeyValue): + # FIXME:数据获取顺序问题 + pass + + def UpdateMsg(self, messageType, json_message, uuid = ''): + # para: messageType(消息类型): "UpdateInfos"、 "InstallInfos"、 "RemoveInfo" + # para: dict_message(数据内容): 必须包含 "packageName"、"source"", 采集器会进行检测 + dict_message = self.convertor.JsonConvertDict(json_message) + if messageType == "": + messageType = "SystemUpdate" + if type(dict_message) != type(dict) and "appname" not in dict_message.keys(): + raise AttributeError("'%s' object has no attribute '%s'" % ("dict message", "appname")) + + # 生成UploadMessage与PackageInfo + try: + self.GenPackageInfo(messageType, "kylin-system-updater") + self.GenUploadMessage(dict_message, local_uuid = uuid) + except Exception as e: + logging.error(str(e)) + + # sha256 + json_UploadMessage = self.convertor.dictConvertJson(self.UploadMessage) + json_PackageInfo = self.convertor.dictConvertJson(self.PackageInfo) + shaValue = self.convertor.Sha256Value(json_UploadMessage) + encodeMsg = self.convertor.EncodeRSAtoBase64(shaValue) + + # dbus发送 + try: + self.sender.MsgSendToServer(json_UploadMessage, json_PackageInfo, encodeMsg) + except Exception as e: + logging.error(e) + + def Generate_Msg(self, upgrade_list, mode): + try: + self.upgrade_list = upgrade_list + self.upgrade_mode = mode + self.uuid = str(uuid.uuid1()) + self.UpdateInfos.update({"upgradeMode":self.mode_map.get(self.upgrade_mode, "default-mode")}) + except DBusException as e: + logging.error(e) + + def Upgrade_Process_Msg(self, action, dict_msg = {}): + if self.updateManager.configs_uncover.getWithDefault("SystemStatus", "upload_upgrade_log", False) == True: + tmp_dict = {} + tmp_dict.update(dict_msg) + try: + self.UpdateInfos.update({"step":self.action_map.get(action, "")}) + if self.upgrade_mode == self.MODE_INSTALL_SYSTEM: + self.UpdateInfos.update({"appname":"Upgrade System"}) + tmp_dict.update(self.UpdateInfos) + json_file = json.dumps(tmp_dict.copy()) + self.UpdateMsg(self.messageType_map.get(action, ""), json_file, self.uuid) + else: + if action == self.ACTION_INSTALL_DEB: + tmp_dict.update({"step":self.action_map.get(action, "")}) + json_file = json.dumps(tmp_dict.copy()) + self.UpdateMsg(self.messageType_map.get(action, ""), json_file, self.uuid) + else: + tmp_dict.update(self.UpdateInfos) + json_file = json.dumps(tmp_dict.copy()) + self.UpdateMsg(self.messageType_map.get(action, ""), json_file, self.uuid) + except Exception as e: + logging.error(e) + tmp_dict.clear() + + def make_background_version(self,pkg): + if pkg.is_installed == True: + self.background_version.update({pkg.name:pkg.installed.source_version}) + else: + self.background_version.update({pkg.name:"Unknown"}) + self.background_list.append(pkg.name) + + def Msg_Clean(self): + self.UploadMessage = {} + self.PackageInfo = {} + self.UpdateInfos = {} +class FormatConvert(): + def __init__(self, DataCollector): + #秘钥 + self.publickey = UniqueKey() + self.collector = DataCollector + + def dictConvertJson(self, dict_msg): + #字典转换为json格式字符串 + json_file = '' + try: + json_file = json.dumps(dict_msg) + except JSONDecodeError as e: + logging.error(str(e)) + return json_file + + def JsonConvertDict(self, json_file): + # json格式字符串转换为字典 + dict_file = {} + try: + dict_file = json.loads(json_file) + except JSONDecodeError as e: + logging.error(str(e)) + return dict_file + + def Sha256Value(self, json_file): + # 计算sha256值 + hsobj = hashlib.sha256() + try: + hsobj.update(json_file.encode("utf-8")) + except ValueError as e: + logging.error("SHA256 value error: %s.",str(e)) + except Exception as e: + logging.error(str(e)) + + return hsobj.hexdigest() + + def EncodeRSAtoBase64(self, value): + # 将value进行RSA加密并base64转码 + try: + # 计算hex值 + value_hex = a2b_hex(value) + # 加载公钥,填充格式OAEP + uniqueKey = self.publickey.keyvalue.encode('utf-8') + uniqueKeyorig = base64.b64decode(uniqueKey) # 公钥文件 + rsa_pubkey = RSA.importKey(uniqueKeyorig) # RSA公钥 + oaep_pub = PKCS1_OAEP.new(rsa_pubkey) # OAEP填充 + # 加密数据 + encodemsg = oaep_pub.encrypt(value_hex) + # 加密数据Base64转码 + enMsg = base64.b64encode(encodemsg) + except ValueError: + logging.error("Value error: %s.", value) + except TypeError: + logging.error("RSA key has no private half.") + return enMsg + + +class MessageSend(): + ERR_PARA_FROMAT = 1 + ERR_NO_LOACLTID = 2 + ERR_ABNORMAL_SHA = 3 + ERR_UPLOADMSG_SHA = 4 + ERR_UPLOADMSG_CTS = 5 + + def __init__(self, DataCollector=None) -> None: + # self.convertor = FormatConvert() + if DataCollector == None: + self.collector = UpdateMsgCollector() + else: + self.collector = DataCollector + + def MsgSendToServer(self, UploadMessage, PackageInfo, encodeMsg): + daqbus = dbus.SystemBus() + try: + daqobj = daqbus.get_object('com.kylin.daq', '/com/kylin/daq') + daqinterface = dbus.Interface(daqobj, dbus_interface='com.kylin.daq.interface') + except DBusException as e: + logging.error("kylin-daq service error: "+str(e)) + return + try: + retval,retid = daqinterface.UploadMessage(PackageInfo, UploadMessage, encodeMsg) + except AttributeError: + logging.error("Call UploadMessage: Attribute Error.") + self.Send_finally(retval, retid, PackageInfo, UploadMessage, encodeMsg) + + def Send_finally(self, retval, retid, json_PackageInfo, json_UploadMessage, encodeMsg): + # 根据发送结果进行处理 + result = '' + PackageInfo = self.collector.convertor.JsonConvertDict(json_PackageInfo) + if retval != 0: + if retval == self.ERR_PARA_FROMAT: + result = "Parameter format error" + logging.debug("Sent Status: false - packageName: %s : result: %s.", PackageInfo['packageName'], result) + elif retval == self.ERR_NO_LOACLTID: + result = "The tid value in packageInfo is abnormal, but the message is saved successfully" + logging.debug("Sent Status: false - packageName: %s : result: %s.", PackageInfo['packageName'], result) + # 将返回的tid保存到本地 + key = PackageInfo['packageName']+'_'+PackageInfo['messageType'] + self.SaveTid(key, retid) + elif retval == self.ERR_ABNORMAL_SHA: + result = "Abnormal UploadedMessage Sha256" + logging.debug("Sent Status: false - packageName: %s : result: %s.", PackageInfo['packageName'], result) + elif retval == self.ERR_UPLOADMSG_SHA: + result = "Description The UploadedMessageSha256 was decrypted incorrectly" + logging.debug("Sent Status: false - packageName: %s : result: %s.", PackageInfo['packageName'], result) + elif retval == self.ERR_UPLOADMSG_CTS: + result = "The createTimeStamp field of UploadedMessage is abnormal" + logging.debug("Sent Status: false - packageName: %s : result: %s.", PackageInfo['packageName'], result) + elif retval == self.ERR_UPLOADMSG_CTS: + result = "Invalid key included in \"uploadedMessage\" or \"packageInfo\": <@timestamp>,<_id>,<_index>,<_type>,,,,, check upload field" + logging.debug("Sent Status: false - packageName: %s : result: %s.", PackageInfo['packageName'], result) + else: + logging.debug("Sent Status: false - packageName: %s : retval: %s.", PackageInfo['packageName'], retval) + # 上传失败写入本地json + if retval != self.ERR_NO_LOACLTID or retval == self.ERR_NO_LOACLTID: + self.WriteToJson(PackageInfo['messageType'], json_PackageInfo, json_UploadMessage, encodeMsg) + elif retval == 0: + result = "Send to server success" + logging.debug("Sent Status: True - packageName: %s : result: %s.", PackageInfo['packageName'], result) + + def GetLocalTid(self, key): + # 试图获取本地tid + try: + # 存放至数据库 + tid = self.collector.updateManager.sqlite3_server.select_from_tid("tid",key) + if tid == "None" or tid == None: + self.localtid = "" + else: + self.localtid = tid + except Exception as e: + logging.error(str(e)) + + def SaveTid(self, key, localtid): + if len(localtid) == 0: + return + _localtid = str(localtid) + try: + # 写入数据库 + self.collector.updateManager.sqlite3_server.insert_into_tid(key, _localtid) + except Exception as e: + logging.error(str(e)) + + def WriteToJson(self, messageType, json_PackageInfo, json_UploadMessage, encodeMsg): + #发送失败时,写入本地json中定时发送 + Msg = {} + Msg["PackageInfo"] = json_PackageInfo + Msg["UploadMessage"] = json_UploadMessage + Msg["encodeMsg"] = str(encodeMsg) + json_file = self.collector.convertor.dictConvertJson(Msg) + # 保存信息 + try: + if not os.path.exists(MSGSNDDIR): + os.mkdir(MSGSNDDIR) + # 根据messageType保存信息 + with open(MSGSNDDIR+messageType+".json","a") as f: + f.write(json_file) + f.write("\n") + except Exception as e: + logging.error(str(e)) + + def _TimedTransmission(self, file_path = MSGSNDDIR): + classify_list = [name for name in os.listdir(file_path) if name.endswith(".json")] + for f in classify_list: + # 循环发送每一个文件 + self._ReadFromFile(os.path.join(file_path, f)) + + def _ReadFromFile(self, json_path): + new_lines = [] + # 从本地文件读取 + if not os.path.exists(json_path): + return + with open(json_path, "r+") as f: + lines = f.readlines() + + # file is empty and path is exit -> remove file + if len(lines) == 0 and os.path.exists(json_path): + os.remove(json_path) + return + + #send installinfo or updateinfo + for line in lines: + (retval,retid) = self._file_send_server(line) + if retval != 0: # success + new_lines.append(line) + if os.path.exists(json_path): + os.remove(json_path) + if len(new_lines) != 0: + with open(json_path, "w+") as f: + for line in lines: + f.write(line) + + def _file_send_server(self, json): + UploadMessage = {} + PackageInfo = {} + encodeMsg = '' + dict_msg = self.collector.convertor.JsonConvertDict(json) + if 'UploadMessage' in dict_msg.keys(): + UploadMessage = dict_msg['UploadMessage'] + UploadMessage = self.collector.convertor.dictConvertJson(UploadMessage) + if 'PackageInfo' in dict_msg.keys(): + PackageInfo = dict_msg['PackageInfo'] + PackageInfo = self.collector.convertor.dictConvertJson(PackageInfo) + if 'encodeMsg' in dict_msg.keys(): + encodeMsg = str(dict_msg['encodeMsg']) + if len(UploadMessage) == 0 or len(PackageInfo) == 0 or encodeMsg == '': + logging.error("Msg error") + return 6, '' + daqbus = dbus.SystemBus() + try: + daqobj = daqbus.get_object('com.kylin.daq', '/com/kylin/daq') + daqinterface = dbus.Interface(daqobj, dbus_interface='com.kylin.daq.interface') + except DBusException as e: + logging.error(str(e)) + try: + retval,retid = daqinterface.UploadMessage(PackageInfo, UploadMessage, encodeMsg) + except AttributeError: + logging.error("Call UploadMessage: Attribute Error.") + return (retval,retid) + +class UniqueKey(): + keyvalue = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR\ +OEFNSUlCQ2dLQ0FRRUFzdW1NTFJEdlFNb0tEQkRJODRqSgpqc1A0Mk55V0pWVEZob2Jra3ZiT05j\ +dExYTXVzRmo2TzJUblZYU3Z6VlVLSjRqZkpwT2l2WEphOVB5Z2wzYTRnClBzSU40enNCMEdOY0tr\ +R3VsS2RrV2x6S3lWQ2xlTzhiQnN6SjkwbTc3cWF6YWg3a1A0TUl0WTVFczBpSkpiR0oKN1MxcERj\ +MlJkNnVFQWJLaXJyRTFlNzlFTEd4am5VN2V5NWkyRDE2WWJoZEQwZ2lNa2RHR3piQXBKTWZWRVJR\ +TQo1NXorMFVqdS8zSFJhNFY3b3p2TGRPRE5HUURaeWNJU0l3VHBLbFR3RjBxazdCNjVhTUlJenQ1\ +dnhOK1lxYU1GClppZFRLNzcxNjdqNEExZ3F3MG45bjlybWVXUGRWZ3dudnRtVXp4Q1krNk05SXpK\ +TDI3eWpRUTV1WGQ3RVdMT3IKbndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==" + +class PHPServer(threading.Thread): + LOG_PATH = "/var/log/kylin-system-updater" + KYLIN_SOFTWARE_PROPERTIES_LOG = "/var/log/kylin-software-properties.log" + PINGBACK_INTERNET_URL = "http://archive1.kylinos.cn:32294/kylin-update-manager-server/main.php?" + PINGBACK_INTERNET_FILE_URL = "http://archive1.kylinos.cn:32294/kylin-update-manager-server/get_file.php?" + PINGBACK_INTRANET_URL = "http://archive.kylinos-intranet.cn/kylin-update-manager-server/main.php?" + PINGBACK_INTRANET_FILE_URL = "http://archive.kylinos-intranet.cn/kylin-update-manager-server/get_file.php?" + SYSTEM_VERSION_PATH = "/etc/kylin-version/kylin-system-version.conf" + + def get_values(self, _appname="", _appversion="", _state='', _errorcode='', _errorstring=""): + self.appname = _appname + self.appversion = _appversion + self.status = _state + self.errorcode = _errorcode + self.errorstring = _errorstring + + def run(self): + # 获取本机ip + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(('8.8.8.8', 80)) + host_ip = s.getsockname()[0] + except: + host_ip = 'Ip failed to get' + # 获取系统版本 + with open('/etc/lsb-release', 'r') as f: + for line in f.readlines(): + if line.strip().startswith('DISTRIB_DESCRIPTION='): + versions = line.strip().split('=') + if "V10" in line and "SP1" in versions[1]: + version = "V10SP1" + else: + version = "V10Pro" + break + # 获取软件版本 + output = os.popen('dpkg -l|grep kylin-system-updater').readlines() + if output: + soft_version = output[0].strip().split()[2] + # 获取时间 + nowtime = time.strftime('%Y-%m-%d-%H:%M:%S', time.localtime(time.time())) + # 获取Mac + mac = uuid.UUID(int=uuid.getnode()).hex[-12:] + host_mac = ":".join([mac[e:e + 2] for e in range(0, 11, 2)]) + # 获取序列号 + if os.path.exists("/etc/.kyinfo"): + settings = QSettings("/etc/.kyinfo", QSettings.IniFormat) + settings.beginGroup("servicekey") + key = str(settings.value("key")).strip() + settings.endGroup() + else: + key = "0" + try: + # 用于收集源管理器的更新日志 + if self.status != "success": + # nowtime = datetime.utcnow( ).strftime ( '%Y-%m-%d %H:%M:%S.%f' )[:-3] + nowtime = get_east_8_time() + log_dir = os.path.join(self.LOG_PATH, host_mac + "_" + nowtime) + log_file_gzip = log_dir + ".tar.gz" + os.makedirs(log_dir, exist_ok=True) + + #get updater log + updater_path = os.path.join(log_dir,"kylin-system-updater") + os.makedirs(updater_path, exist_ok=True) + #get updater log + if os.path.exists("/var/log/kylin-system-updater/kylin-system-updater.log.1"): + shutil.copy("/var/log/kylin-system-updater/kylin-system-updater.log.1", log_dir) + if os.path.exists("/var/log/kylin-system-updater/kylin-system-updater.log.1.1.gz"): + shutil.copy("/var/log/kylin-system-updater/kylin-system-updater.log.1.1.gz", log_dir) + + #get apt log + if os.path.exists("/var/log/apt/history.log"): + shutil.copy("/var/log/apt/history.log", log_dir) + if os.path.exists("/var/log/apt/term.log"): + shutil.copy("/var/log/apt/term.log", log_dir) + + #get version file + if os.path.exists(self.SYSTEM_VERSION_PATH): + shutil.copy(self.SYSTEM_VERSION_PATH, log_dir) + gZipFile(log_dir, log_file_gzip) + header = {'Content-Type': "multipart/form-data", "Accept-Encoding": "gzip"} + try: + with open(log_file_gzip, "rb") as f: + requests.post(self.PINGBACK_INTRANET_FILE_URL + "filename=" + os.path.basename(log_file_gzip), + data=f.read(), headers=header) + except: + with open(log_file_gzip, "rb") as f: + requests.post(self.PINGBACK_INTERNET_FILE_URL + "filename=" + os.path.basename(log_file_gzip), + data=f.read(), headers=header) + shutil.rmtree(log_dir) + os.remove(log_file_gzip) + else: + log_file_gzip = "" + kmg_tmp = {'ip': host_ip, 'version': version, 'soft_version': soft_version, 'datetime': nowtime, + 'host_mac': host_mac, 'appname': self.appname, 'appversion': self.appversion, 'serial_number': key, + 'state': self.status, 'filename': log_file_gzip, 'errorcode': self.errorcode, 'errorstring': self.errorstring} + kmg = parse.urlencode(kmg_tmp) + logging.debug("PHPServer UpdateInfos: %s .", kmg_tmp) + # 优先使用内网服务器,再使用外网 + try: + url = self.PINGBACK_INTRANET_URL + kmg + req = request.urlopen(url=url, timeout=3) + logging.info("The Intranet log server is successfully accessed, pkgname:%s .",self.appname) + except: + url = self.PINGBACK_INTERNET_URL + kmg + req = request.urlopen(url=url, timeout=3) + logging.info("The external log server is successfully accessed, pkgname:%s .",self.appname) + except Exception as e: + logging.error("Failed to access the external log server: %s, pkgname:%s .", e, self.appname) + if os.path.isfile(log_file_gzip): + os.remove(log_file_gzip) + +def PHPSeverSend(_appname="", _appversion="", _statue="", _errorcode="", _errorstring=""): + send_thread = PHPServer() + send_thread.get_values(_appname=_appname, _appversion=_appversion, _state=_statue, _errorcode=_errorcode, _errorstring=_errorstring) + send_thread.start() + +def gZipFile(src, dst): + with tarfile.open(dst, "w:gz") as tar: + tar.add(src, arcname=os.path.basename(src)) + +def get_east_8_time(): + import time + # UTC时间 + utc_time = datetime.utcnow() + # 转时间字符串 + utc_time = utc_time.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] + time_suffix = utc_time.split(".")[1] + # 字符串转时间元祖 + utc_time = time.strptime(utc_time, "%Y-%m-%d %H:%M:%S.%f") + # 时间元祖转时间戳 + utc_time = time.mktime(utc_time) + # 生成东八区时间时间戳 + now_time = utc_time + 8*60*60 + # 时间戳转时间元祖 + now_time = time.localtime(now_time) + # 时间元祖转字符串 + now_time = time.strftime("%Y-%m-%d %H:%M:%S",now_time) + now_time = now_time + "." +time_suffix + return now_time + # return 0 + +if __name__ == "__main__": + # 执行定时发送 + ms = MessageSend() + ms._ReadFromFile("/var/lib/kylin-system-updater/sendinfos/testMsg.json") \ No newline at end of file diff --git a/backend/SystemUpdater/Core/DataMigration.py b/backend/SystemUpdater/Core/DataMigration.py new file mode 100755 index 0000000..e6bb6fa --- /dev/null +++ b/backend/SystemUpdater/Core/DataMigration.py @@ -0,0 +1,400 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import os +import logging +import sqlite3 +from operator import itemgetter +from gettext import gettext as _ +from optparse import OptionParser + +DB_UPDATER = "/var/cache/kylin-update-manager/kylin-update-manager.db" +DB_UPGRADE = "/var/cache/kylin-system-updater/kylin-system-updater.db" +VER_DB = "/usr/share/kylin-system-updater/kylin-system-updater.db" + +def dateMigration(options=None, old_db=None, old_db_cursor=None, new_db=None, new_db_cursor=None): + print(_("Loading Sqlite3Server...")) + if options==None: + old_path = DB_UPDATER + new_path = DB_UPGRADE + try: + if old_db==None and old_db_cursor==None: + old_db = sqlite3.connect(old_path, check_same_thread=False) + old_db_cursor = old_db.cursor() + if new_db==None and new_db_cursor==None: + new_db = sqlite3.connect(new_path, check_same_thread=False) + new_db_cursor = new_db.cursor() + except Exception as e: + print(e) + + sql_commnd = "" + old_cfg_dict = {} + new_cfg_dict = {} + # step 1: 更新旧配置数据 + try: + print("更新旧配置数据") + sql_commnd = "SELECT * FROM display where id=1" + old_db_cursor.execute(sql_commnd) + old_cfg = old_db_cursor.fetchone() + for od in old_db_cursor.description: + old_cfg_dict.update({str(od[0]):old_cfg[old_db_cursor.description.index(od)]}) + new_db_cursor.execute(sql_commnd) + new_cfg = new_db_cursor.fetchone() + for od in new_db_cursor.description: + new_cfg_dict.update({str(od[0]):new_cfg[new_db_cursor.description.index(od)]}) + + if "download_limit" in new_cfg_dict.keys() and "download_limit_value" in new_cfg_dict.keys(): + if new_cfg_dict['download_limit'] != None or new_cfg_dict['download_limit_value'] != None: + print("目标数据库有更新的配置项") + else: + sql_commnd = "UPDATE display set check_time='"+old_cfg_dict['check_time']+"' Where id=1" + new_db_cursor.execute(sql_commnd) + new_db.commit() + sql_commnd = "UPDATE display set update_time='"+old_cfg_dict['update_time']+"' Where id=1" + new_db_cursor.execute(sql_commnd) + new_db.commit() + sql_commnd = "UPDATE display set auto_check='"+old_cfg_dict['auto_check']+"' Where id=1" + new_db_cursor.execute(sql_commnd) + new_db.commit() + sql_commnd = "UPDATE display set system_version='"+old_cfg_dict['system_version']+"' Where id=1" + new_db_cursor.execute(sql_commnd) + new_db.commit() + if old_cfg_dict['auto_backup'] != None: + sql_commnd = "UPDATE display set auto_backup='"+old_cfg_dict['auto_backup']+"' Where id=1" + new_db_cursor.execute(sql_commnd) + new_db.commit() + if 'download_limit' in old_cfg_dict.keys() and old_cfg_dict['download_limit'] != None: + sql_commnd = "UPDATE display set download_limit='"+old_cfg_dict['download_limit']+"' Where id=1" + new_db_cursor.execute(sql_commnd) + new_db.commit() + if 'download_limit_value' in old_cfg_dict.keys() and old_cfg_dict['download_limit_value'] != None: + sql_commnd = "UPDATE display set download_limit_value='"+old_cfg_dict['download_limit_value']+"' Where id=1" + new_db_cursor.execute(sql_commnd) + new_db.commit() + + except Exception as e: + print(e) + print("更新配置文件错误") + return + + # step 2: 更新installed + try: + print("更新installed") + update_record_dict = {} + tmp_update_record_dict = [] + sql_commnd = "SELECT * FROM installed" + old_db_cursor.execute(sql_commnd) + update_record = old_db_cursor.fetchall() + sql_commnd = "SELECT * FROM updateinfos" + new_db_cursor.execute(sql_commnd) + new_update_record = new_db_cursor.fetchall() + + for ur in update_record: + id,appname,version,time,description,icon,statue,keyword,errorcode = ur + if errorcode in range(200): + errorcode = 'null' + update_record_dict.clear() + update_record_dict.update({"appname":appname}) + update_record_dict.update({"version":version}) + update_record_dict.update({"time":time}) + update_record_dict.update({"description":description}) + update_record_dict.update({"icon":icon}) + update_record_dict.update({"statue":statue}) + update_record_dict.update({"keyword":'1'}) + update_record_dict.update({"errorcode":errorcode}) + tmp_update_record_dict.append(update_record_dict.copy()) + for ur in new_update_record: + id,appname,version,description,date,status,keyword,errorcode = ur + if errorcode in range(200): + errorcode = 'null' + update_record_dict.clear() + update_record_dict.update({"appname":appname}) + update_record_dict.update({"version":version}) + update_record_dict.update({"time":date}) + update_record_dict.update({"description":description}) + update_record_dict.update({"icon":None}) + update_record_dict.update({"statue":status}) + update_record_dict.update({"keyword":'1'}) + update_record_dict.update({"errorcode":errorcode}) + tmp_update_record_dict.append(update_record_dict.copy()) + + # 按时间排序 + tmp_update_record_dict = sorted(tmp_update_record_dict, key=itemgetter('time')) + print("更新installed success") + + except Exception as e: + print(e) + print("更新安装记录错误") + return + + try: + # 删除 tmp + # DeleteTable(options.new_path+':'+'tmp') + # 创建表 + sql_commnd = "create table IF NOT EXISTS tmp('id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,\ + 'appname' TEXT,\ + 'version' TEXT,\ + 'description' TEXT,\ + 'date' TEXT,\ + 'status' TEXT,\ + 'keyword' TEXT,\ + 'errorcode' TEXT) " + new_db_cursor.execute(sql_commnd) + # 更新数据 + for urd in tmp_update_record_dict: + new_db_cursor.execute( + "insert into tmp (appname, version, description, date, status, keyword, errorcode) values(?," + "?,?,?,?,?,?)", + (urd['appname'], urd['version'], urd['description'], urd['time'], urd['statue'], urd['keyword'], urd['errorcode'])) + new_db.commit() + + # 删除updateinfos + sql_commnd = "drop table updateinfos" + new_db_cursor.execute(sql_commnd) + new_db.commit() + # 修改表名 + sql_commnd = "alter table tmp rename to updateinfos" + new_db_cursor.execute(sql_commnd) + new_db.commit() + except Exception as e: + print(e) + print("安装记录迁移错误") + return + print("数据迁移成功.") + +def CleanTable(db_table): + db_path, table_name = str(db_table).split(":") + if not os.path.isfile(db_path): + print("db path error.") + exit(-1) + print(_("Loading Sqlite3Server...")) + try: + db = sqlite3.connect(db_path, check_same_thread=False) + db_cursor = db.cursor() + sql_commnd = 'delete from '+table_name + db_cursor.execute(sql_commnd) + db.commit() + print("clean %s success."%table_name) + except Exception as e: + print(e) + print("clean %s error."%table_name) + +def DeleteTable(db_table): + db_path, table_name = str(db_table).split(":") + if not os.path.isfile(db_path): + print("db path error.") + exit(-1) + print(_("Loading Sqlite3Server...")) + try: + db = sqlite3.connect(db_path, check_same_thread=False) + db_cursor = db.cursor() + sql_commnd = 'drop table '+table_name + db_cursor.execute(sql_commnd) + db.commit() + print("delete %s success."%table_name) + except Exception as e: + print("delete %s error: %s"%(table_name,e)) + +def _has_first_migration(new_db, new_db_cursor): + try: + sql_commnd = "select * from sqlite_master where type='table' and name='display';" + new_db_cursor.execute(sql_commnd) + retval = new_db_cursor.fetchone() + for rv in retval: + if "firstmigration" in str(rv): + return True + except Exception as e: + print(e) + return False + +def _is_first_migration(new_db, new_db_cursor): + try: + sql_commnd = "select firstmigration from display;" + new_db_cursor.execute(sql_commnd) + retval = new_db_cursor.fetchone() + if "yes" in retval: + return True + else : + return False + except Exception as e: + print(e) + return False + +def _is_display_exist_fields(field, db, db_cursor): + try: + sql_commnd = "select * from sqlite_master where type='table' and name='display';" + db_cursor.execute(sql_commnd) + retval = db_cursor.fetchone() + for rv in retval: + if field in str(rv): + return True + except Exception as e: + print(e) + return False + return False + +def _is_updateinfos_exist_fields(field, db, db_cursor): + try: + sql_commnd = "select * from sqlite_master where type='table' and name='updateinfos';" + db_cursor.execute(sql_commnd) + retval = db_cursor.fetchone() + for rv in retval: + if field in str(rv): + return True + except Exception as e: + print(e) + return False + return False + +def _add_display_fields(fields_default, default_table = True): + try: + if "=" not in fields_default: + print("format: field=value") + return False + field, value = fields_default.split('=') + # print(_("Loading Sqlite3Server...")) + db = sqlite3.connect(DB_UPGRADE, check_same_thread=False) + db_cursor = db.cursor() + if default_table: + if _is_display_exist_fields(field, db, db_cursor): + print("field %s is exist."%field) + return False + # 字段不存在,新增字段 + sql_commnd = "alter table display add column "+field+" TEXT;" + db_cursor.execute(sql_commnd) + sql_commnd = "UPDATE display SET "+field+"='"+value+"'" + db_cursor.execute(sql_commnd) + db.commit() + else: + if _is_updateinfos_exist_fields(field, db, db_cursor): + print("field %s is exist."%field) + return False + # 字段不存在,新增字段 + sql_commnd = "alter table updateinfos add column "+field+" TEXT;" + db_cursor.execute(sql_commnd) + db.commit() + except Exception as e: + print(e) + return False + print("Succeeded in adding field: '%s' "%field) + return True + +def _add_new_table(table): + table = str(table).strip() + if "=" not in table: + return False + opt, fields = table.split('=') + try: + if fields == 'tid_search': + db = sqlite3.connect(DB_UPGRADE, check_same_thread=False) + db_cursor = db.cursor() + sql_commnd = "create table IF NOT EXISTS tid_search('id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,\ + 'key' TEXT,\ + 'tid' TEXT) " + db_cursor.execute(sql_commnd) + db.commit() + db.close() + except Exception as e: + print(e) + return False + +def CopyData(): + try: + # 判断新字段是否存在 + if (os.path.exists(VER_DB) and os.path.exists(DB_UPGRADE)): + print(_("Loading Sqlite3Server...")) + try: + new_db = sqlite3.connect(DB_UPGRADE, check_same_thread=False) + new_db_cursor = new_db.cursor() + ver_db = sqlite3.connect(VER_DB, check_same_thread=False) + ver_db_cursor = ver_db.cursor() + except Exception as e: + print(e) + + if (_has_first_migration(new_db, new_db_cursor)): # 存在 firstmigration + if (_is_first_migration(new_db, new_db_cursor)): + # 数据迁移 + dateMigration(new_db=new_db, new_db_cursor=new_db_cursor) + sql_commnd = "UPDATE display SET firstmigration='no';" + new_db_cursor.execute(sql_commnd) + new_db.commit() + else: + print("No data migration is required ...") + else:# 不存在firstmigration + # 新增 firstmigration 字段 + sql_commnd = "alter table display add column firstmigration text;" + new_db_cursor.execute(sql_commnd) + sql_commnd = "UPDATE display SET firstmigration='yes';" + new_db_cursor.execute(sql_commnd) + #数据迁移 + dateMigration(new_db=new_db, new_db_cursor=new_db_cursor) + sql_commnd = "UPDATE display SET firstmigration='no';" + new_db_cursor.execute(sql_commnd) + new_db.commit() + + else : + print("Not found kylin-system-updater.db, ensure that \'kylin-system-updater\' is successfully installed ... ") + exit(-1) + except Exception as e: + print(e) + +if __name__ == "__main__": + # Begin parsing of options + parser = OptionParser() + parser.add_option ("-d", "--debug", action="store_true", default=False, + help=_("Show debug messages")) + parser.add_option ("-o", "--old-path", dest="old_path", + help=_("Enter the old database address")) + parser.add_option ("-n", "--new-path", dest="new_path", + help=_("Enter the new database address")) + parser.add_option ("-c", "--clean-table", dest="clean_table", + help=_("Clear the table")) + parser.add_option ("-r", "--delete-table", dest="delete_table", + help=_("Delete the table")) + parser.add_option ("-m", "--data-migration", default=False, action="store_true", + dest="data_migration", help=_("data migration")) + parser.add_option ("-f", "--add-display-fields", + dest="add_display_fields", help=_("add display fields")) + parser.add_option ("-u", "--add-updateinfos-fields", + dest="add_updateinfos_fields", help=_("add updateinfos fields")) + parser.add_option ("-t", "--add-new-table", + dest="add_new_table", help=_("add new table")) + (options, args) = parser.parse_args() + + if options.clean_table: + if ":" not in options.clean_table: + print("format error: ") + else: + CleanTable(str(options.clean_table)) + + if options.delete_table: + if ":" not in options.delete_table: + print("format error: ") + else: + DeleteTable(str(options.delete_table)) + + if options.add_display_fields: + _add_display_fields(str(options.add_display_fields)) + + if options.add_updateinfos_fields: + _add_display_fields(str(options.add_updateinfos_fields), default_table = False) + + if options.add_new_table: + _add_new_table(str(options.add_new_table)) + + if options.data_migration: + CopyData() + exit(0) + + if options.old_path or options.new_path: + # 检查文件 + if not options.old_path or not options.new_path: + print("parameter error") + exit(-1) + if not os.path.isfile(options.old_path): + print("The source database file does not exist") + exit(-1) + if not os.path.isfile(options.new_path): + print("The destination database file does not exist") + exit(-1) + dateMigration(options) diff --git a/backend/SystemUpdater/Core/Database.py b/backend/SystemUpdater/Core/Database.py new file mode 100644 index 0000000..6b8715e --- /dev/null +++ b/backend/SystemUpdater/Core/Database.py @@ -0,0 +1,683 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +from math import log10 +import os +import re +import json +import yaml +import shutil +import sqlite3 +import logging +import datetime +from gettext import gettext as _ +from sys import exec_prefix +from SystemUpdater.Core.DataAcquisition import PHPSeverSend +from SystemUpdater.Core.UpdaterConfigParser import UpgradeConfig +from SystemUpdater.Core.utils import get_config_patch + +import apt_pkg +from ..backend import InstallBackend + +DB_FILE = os.path.join("/var/cache/kylin-system-updater/kylin-system-updater.db") +# UMDB_FILE = os.path.join("/var/cache/kylin-system-updater/kylin-system-updater.db") +INSTALLED_LIST = [{"item": "errorcode", "type": "int", "default": "0"}] +DISPALY_LIST = [] + +class Sqlite3Server(object): + def __init__(self, updateManager): + self.connect = None + self.window_main = updateManager + self.config_path = get_config_patch() + self.init_sqlit() + + # uncoverable配置文件 + self.ucconfigs = UpgradeConfig(datadir = "/etc/kylin-version", name = "kylin-system-version.conf") + self._system_version_config() + + # 初始化连接数据库,修改为使用时连接 + def init_sqlit(self): + try: + logging.info(_("Initialize database files ...")) + if not os.path.isfile(DB_FILE): + if not os.path.isdir(os.path.dirname(DB_FILE)): + os.makedirs(os.path.dirname(DB_FILE)) + shutil.copy("/usr/share/kylin-system-updater/kylin-system-updater.db", os.path.dirname(DB_FILE)) + except Exception as e: + logging.error("Failed to initialize database files: %s", str(e)) + + #connect连接数据库 + def connect_database(self): + try: + logging.debug("Connect database ...") + self.connect = sqlite3.connect(DB_FILE, check_same_thread=False) + self.cursor = self.connect.cursor() + except Exception as e: + logging.error("Failed to connect database: %s", str(e)) + + #disconnect连接数据库 + def disconnect_database(self): + try: + logging.debug("Disconnect database ...") + if self.connect != None: + self.connect.close() + if self.connect != None: + del self.cursor + except Exception as e: + logging.error("Failed to disconnect database: %s", str(e)) + + # 数据库表格中动态增加新的字段用于扩展 + def insert_new_field(self): + if len(INSTALLED_LIST) == 0 and len(DISPALY_LIST) == 0: + return + self.cursor.execute("select sql from sqlite_master where name='installed'") + installed_sql = self.cursor.fetchone()[0] + pattern = re.compile(r'\"\w+\"') + installed_sql_list = pattern.findall(installed_sql) + for value in INSTALLED_LIST: + for field in installed_sql_list: + if value["item"] == str(field).strip("\""): + break + elif field == installed_sql_list[len(installed_sql_list) - 1]: + try: + if value["default"] != "": + sql = 'alter table installed add column "' + value["item"] + '" ' + value["type"] \ + + ' default ' + str(value["default"]) + else: + sql = 'alter table installed add column "' + value["item"] + '" ' + value["type"] + self.cursor.execute(sql) + logging.info(_("installed table insert new field: %s"), value["item"]) + except: + logging.error(_("installed table failed to insert a new field:"), value["item"], exc_info=True) + + self.cursor.execute("select sql from sqlite_master where name='display'") + display_sql = self.cursor.fetchone()[0] + pattern = re.compile(r'\"\w+\"') + display_sql_list = pattern.findall(display_sql) + for value in DISPALY_LIST: + for field in display_sql_list: + if value["item"] == str(field).strip("\""): + break + elif field == display_sql_list[len(display_sql_list) - 1]: + try: + if value["default"] != "": + sql = 'alter table display add column "' + value["item"] + '" ' + value["type"] \ + + ' default ' + str(value["default"]) + else: + sql = 'alter table installed add column "' + value["item"] + '" ' + value["type"] + self.cursor.execute(sql) + logging.info(_("display table insert new field: %s"), value["item"]) + except: + logging.error(_("display table failed to insert a new field: %s"), value["item"], exc_info=True) + + # 写入数据到installed表中 + def insert_into_installed(self, *args, **kwargs): + self.connect_database() + self.cursor.execute( + "insert into installed (appname, version, time, description, icon, statue, keyword, errorcode) values(?," + "?,?,?,?,?,?,?)", (args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7])) + self.connect.commit() + logging.info("Database: Insert (%s=%s) To installed Complete ...", args[0], args[1]) + self.disconnect_database() + + # 写入数据到display表中 + def insert_into_display(self, *args, **kwargs): + self.connect_database() + try: + sql = "update display set " + args[0] + "='" + args[1] + "' where id = 1" + self.cursor.execute(sql) + self.connect.commit() + except Exception as e: + logging.error("Insert error: %s.", str(e)) + self.disconnect_database() + return False + logging.info("Database: Insert (%s=%s) To display Complete ...", args[0], args[1]) + self.disconnect_database() + return True + + # 写入数据到tid_search表中 + def insert_into_tid(self, *args, **kwargs): + self.connect_database() + self.cursor.execute( + "insert into tid_search (key, tid) values(?,?)", + (args[0], args[1])) + self.connect.commit() + logging.info("Database: Insert (%s=%s) To tid_search Complete ...", args[0], args[1]) + self.disconnect_database() + + # 搜索tid_search表,获取tid值 + def select_from_tid(self, *args, **kwargs): + retval = '' + self.connect_database() + try: + sql = "select "+args[0]+" from tid_search where key='"+args[1]+"'" + self.cursor.execute(sql) + rets = self.cursor.fetchall() + if len(rets)!= 0: + if len(rets[0])!=0: + ret_first = rets[0] + retval = str(ret_first[0]) + except Exception as e: + logging.error("Insert error: %s.", str(e)) + self.disconnect_database() + logging.info("Database: Select tid_search data Complete...") + self.disconnect_database() + return retval + + # 读出display表中数据 + def select_from_display(self, *args, **kwargs): + self.connect_database() + try: + sql = "select "+args[0]+" from display" + self.cursor.execute(sql) + self.connect.commit() + retval = str(self.cursor.fetchone()[0]) + except Exception as e: + logging.error("select error: %s.", str(e)) + self.disconnect_database() + return "Error" + logging.info("Database: Search display Complete (%s) ...", args[0]) + self.disconnect_database() + return retval + + # 写入updateinfos表中 + def insert_into_updateinfo(self, *args, **kwargs): + self.connect_database() + try: + self.cursor.execute( + "insert into updateinfos (appname, version, description, date, status, keyword, errorcode, appname_cn, status_cn, changelog) values(?," + "?,?,?,?,?,?,?,?,?)", + (args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9])) + self.connect.commit() + except Exception as e: + logging.error("Insert error: %s.", str(e)) + self.disconnect_database() + logging.info(_("Database: Insert To updateinfos Complete...")) + self.disconnect_database() + + # 接收更新列表与信息,生成数据并插入数据库中 + def insert_info(self, mode, pkg_list=[], pkg_group=[], adjust_pkg=[], success = False, error_string = '', error_desc = ''): + errstr = error_string + " " + error_desc + status = " " + status_cn = " " + appname_cn = "" + UpdateInfos = {} + InstallInfos = {} + time = datetime.datetime.now() + timestr = datetime.datetime.strftime(time, "%Y-%m-%d %H:%M:%S") + pkg_list = pkg_list.copy() + pkg_group = pkg_group.copy() + adjust_pkg = adjust_pkg.copy() + # 更新列表空,无更新 + if not pkg_list and not pkg_group and mode != InstallBackend.MODE_INSTALL_SYSTEM: + logging.info("There is no update.") + return True + + if success: + status = 'success' + status_cn = '成功' + else: + status = 'failed' + status_cn = '失败' + changeLog = "" + + try: + # 判断更新方式 + if mode == InstallBackend.MODE_INSTALL_PARTIAL: # 部分更新 + pkg_adj = "" + # 判断更新包为单包或更新组 + if pkg_group: + # 更新组 + pkgname = pkg_group.pop(0) + pkgversion,pkgdescription,appname_cn = self.GetGroupmsg(pkgname) + #更新信息update-infos + UpdateInfos.update({"appname":str(pkgname)}) + UpdateInfos.update({"source":"Kylin System Updater"}) + UpdateInfos.update({"status":status}) + UpdateInfos.update({"errorCode":str(error_string+" "+error_desc)}) + self.window_main.collector.Upgrade_Process_Msg("finish-update", UpdateInfos.copy()) + #安装信息install-infos + InstallInfos.update({"appname":str(pkgname)}) + if pkgname in self.window_main.update_list.upgrade_meta.versoin_pkgs['groups_upgrade'].keys(): + InstallInfos.update({"old_version":str(self.window_main.update_list.upgrade_meta.versoin_pkgs['groups_upgrade'][pkgname])}) + else: + InstallInfos.update({"old_version":'UnKnown'}) + InstallInfos.update({"new_version":str(pkgversion)}) + InstallInfos.update({"status":status}) + InstallInfos.update({"errorCode":str(error_string+" "+error_desc)}) + self.window_main.collector.Upgrade_Process_Msg("finish-install", InstallInfos.copy()) + # 系统升级完成 ..判断版本号 + if status == "success" and "kylin-update-desktop-system" in pkgname: + # 更新版本号 + if "=" in str(pkgversion): + pkgversion = str(pkgversion).split('=')[-1] + logging.info("Complete system upgrade, refresh system version ...") + self._refresh_system_version(pkgversion) + #移除step-two标记 + self._removal_of_marker() + + #FIXME: 临时方案 PHP + PHPSeverSend(_appname=pkgname, _appversion=pkgversion, _statue=status, _errorcode="10000100", _errorstring=errstr) + file_path = os.path.join(get_config_patch(), str(pkgname) + ".yaml") + with open(file_path, "r") as stream: + try: + data_yaml = yaml.safe_load(stream) + changeLog = data_yaml['changelog'] + except yaml.YAMLError as exc: + logging.error(exc) + elif pkg_list: + changeLog = " " + # 单包更新 # 获取单包数据插入数据库 + pkgname = pkg_list.pop(0) + for adj in adjust_pkg: + if pkgname in adj: + # 该部分升级的单包为调整版本,与候选版本不一致 + pkg_adj = adj + break + if pkg_adj: # 有调整的情况 + try: + pkg = self.window_main.cache[pkg_adj.split("=")[0]] + for ver in pkg.versions: + if ver.version == pkg_adj.split("=")[1]: + pkg_inst_ver = ver + break + pkgname = pkg_adj.split("=")[0] + pkgversion = str(pkg_inst_ver.source_version) + pkgdescription = str(pkg_inst_ver.description) + except Exception as e: + logging.error(_("%s could not be detected in the source because the source was changed or for other reasons."), \ + str(pkgname)) + logging.error(str(e)) + else: # 没有调整的情况 + try: + pkg = self.window_main.cache[pkgname] + pkgversion = str(pkg.candidate.version) + pkgdescription = str(pkg.candidate.raw_description) + except Exception as e: + logging.error(str(e)) + + #更新信息update-infos + UpdateInfos.update({"appname":str(pkgname)}) + UpdateInfos.update({"source":"Kylin System Updater"}) + UpdateInfos.update({"status":status}) + UpdateInfos.update({"errorCode":str(error_string+" "+error_desc)}) + self.window_main.collector.Upgrade_Process_Msg("finish-update", UpdateInfos.copy()) + #安装信息install-infos + InstallInfos.update({"appname":str(pkgname)}) + if pkgname in self.window_main.update_list.upgrade_meta.versoin_pkgs['single_upgrade'].keys(): + InstallInfos.update({"old_version":str(self.window_main.update_list.upgrade_meta.versoin_pkgs['single_upgrade'][pkgname])}) + else: + InstallInfos.update({"old_version":'UnKnown'}) + InstallInfos.update({"new_version":str(pkgversion)}) + InstallInfos.update({"status":status}) + InstallInfos.update({"errorCode":str(error_string+" "+error_desc)}) + self.window_main.collector.Upgrade_Process_Msg("finish-install", InstallInfos.copy()) + # 软件商店获取中文名 + appname_cn = self.get_cn_appname(str(pkgname)) + #FIXME: 临时方案 PHP + PHPSeverSend(_appname=pkgname, _appversion=pkgversion, _statue=status, _errorcode="10000100", _errorstring=errstr) + try: + self.insert_into_updateinfo(pkgname, pkgversion, pkgdescription, timestr, status, "1", errstr, appname_cn, status_cn, changeLog) + # FIXME: 发送插入数据库成功的信号local_upgrade_list + self.window_main.dbusController.UpdateSqlitSingle(pkgname, timestr) + # 数据库文件被删除或者新增字段导致需要重新初始化数据库再写入 + except Exception as e: + self.init_sqlit() + self.insert_into_updateinfo(pkgname, pkgversion, pkgdescription, timestr, status, "1", errstr, appname_cn, status_cn, changeLog) + # FIXME: 这里也需要, 发送插入数据库成功的信号 + self.window_main.dbusController.UpdateSqlitSingle(pkgname, timestr) + elif mode == InstallBackend.MODE_INSTALL_ALL: # 系统全部升级 + # # insert signal deb first + for i in pkg_list: + changeLog = "" + try: + pkg = self.window_main.cache[i] + except Exception as e: + logging.error(_("%s could not be detected in the source because the source was changed or for other reasons."), \ + str(i)) + continue + if not pkg: + continue + pkgversion = str(pkg.candidate.version) + pkgdescription = str(pkg.candidate.raw_description) + + #更新信息update-infos + UpdateInfos.update({"appname":str(pkg.name)}) + UpdateInfos.update({"source":"Kylin System Updater"}) + UpdateInfos.update({"status":status}) + UpdateInfos.update({"errorCode":str(error_string+" "+error_desc)}) + self.window_main.collector.Upgrade_Process_Msg("finish-update", UpdateInfos.copy()) + #安装信息install-infos + InstallInfos.update({"appname":str(pkg.name)}) + if pkg.name in self.window_main.update_list.upgrade_meta.versoin_pkgs['groups_upgrade'].keys(): + InstallInfos.update({"old_version":str(self.window_main.update_list.upgrade_meta.versoin_pkgs['groups_upgrade'][pkg.name])}) + else: + InstallInfos.update({"old_version":'UnKnown'}) + InstallInfos.update({"new_version":str(pkgversion)}) + InstallInfos.update({"status":status}) + InstallInfos.update({"errorCode":str(error_string+" "+error_desc)}) + self.window_main.collector.Upgrade_Process_Msg("finish-install", InstallInfos.copy()) + + try: + # 软件商店获取中文名 + appname_cn = self.get_cn_appname(str(i)) + self.insert_into_updateinfo(str(i), pkgversion, pkgdescription, timestr, status, "1", errstr, appname_cn, status_cn, changeLog) + self.window_main.dbusController.UpdateSqlitSingle(str(i), timestr) + # 数据库文件被删除或者新增字段导致需要重新初始化数据库再写入 + except Exception as e: + self.init_sqlit() + self.insert_into_updateinfo(str(i), pkgversion, pkgdescription, timestr, status, "1", errstr, appname_cn, status_cn, changeLog) + self.window_main.dbusController.UpdateSqlitSingle(str(i), timestr) + #FIXME: 临时方案 PHP + PHPSeverSend(_appname=pkg.name, _appversion=pkgversion, _statue=status, _errorcode="10000100", _errorstring=errstr) + # insert group deb next + for i in pkg_group: + # FIXME: 获取组信息 + pkgversion,pkgdescription,appname_cn = self.GetGroupmsg(i) + #更新信息update-infos + UpdateInfos.update({"appname":str(i)}) + UpdateInfos.update({"source":"Kylin System Updater"}) + UpdateInfos.update({"status":status}) + UpdateInfos.update({"errorCode":str(error_string+" "+error_desc)}) + self.window_main.collector.Upgrade_Process_Msg("finish-update", UpdateInfos.copy()) + #安装信息install-infos + InstallInfos.update({"appname":str(i)}) + if i in self.window_main.update_list.upgrade_meta.versoin_pkgs['groups_upgrade'].keys(): + InstallInfos.update({"old_version":str(self.window_main.update_list.upgrade_meta.versoin_pkgs['groups_upgrade'][i])}) + else: + InstallInfos.update({"old_version":'UnKnown'}) + InstallInfos.update({"new_version":str(pkgversion)}) + InstallInfos.update({"status":status}) + InstallInfos.update({"errorCode":str(error_string+" "+error_desc)}) + json_file = json.dumps(InstallInfos.copy()) + try: + self.window_main.collector.UpdateMsg("InstallInfos", json_file) + except: + pass + + #FIXME: 临时方案 PHP + PHPSeverSend(_appname=i, _appversion=pkgversion, _statue=status, _errorcode="10000100", _errorstring=errstr) + file_path = os.path.join(get_config_patch(), str(i) + ".yaml") + with open(file_path, "r") as stream: + try: + data_yaml = yaml.safe_load(stream) + changeLog = data_yaml['changelog'] + except yaml.YAMLError as exc: + logging.error(exc) + try: + self.insert_into_updateinfo(str(i), pkgversion, pkgdescription, timestr, status, "1", errstr, appname_cn, status_cn, changeLog) + self.window_main.dbusController.UpdateSqlitSingle(str(i), timestr) + # 数据库文件被删除或者新增字段导致需要重新初始化数据库再写入 + except Exception as e: + self.init_sqlit() + self.insert_into_updateinfo(str(i), pkgversion, pkgdescription, timestr, status, "1", errstr, appname_cn, status_cn, changeLog) + self.window_main.dbusController.UpdateSqlitSingle(str(i), timestr) + + # 系统升级完成 ..判断版本号 + if status == "success" and "kylin-update-desktop-system" in pkg_group: + # 更新版本号 + if "=" in str(pkgversion): + pkgversion = str(pkgversion).split('=')[-1] + logging.info("Complete system upgrade, refresh system version ...") + self._refresh_system_version(str(pkgversion)) + #移除step-two标记 + self._removal_of_marker() + + elif mode == InstallBackend.MODE_INSTALL_SYSTEM: # 全盘升级 + self.insert_into_updateinfo(_("Upgrade System"), "", "This is a complete system upgrade, equivalent to the implementation of apt dist-upgrade", timestr, status, "1", errstr, str("全盘升级"), status_cn, " ") + self.window_main.dbusController.UpdateSqlitSingle("Upgrade System", timestr) + # 全盘更新完成 ..判断版本号 + if status == "success": + # 更新版本号 + self._refresh_system_version(pseudo_version=True) + #移除step-two标记 + self._removal_of_marker() + else: + logging.warning("Cache is None.") + except Exception as e: + logging.error("record update error: %s.",str(e)) + + # 获取group信息 + def GetGroupmsg(self, appname): + jsonfile = appname+".json" + files = os.listdir(self.config_path) #获取文件夹中所有文件 + if jsonfile in files: # 存在 + # 读取组JSON文件 + with open(self.config_path+jsonfile, "r") as f: + try : + data = json.load(f) + except json.JSONDecodeError as e: + logging.error(str(e)) + try: + version = data['version'] + if "=" in version: + version = version.split("=")[1].strip() + tmpdescription = data['description'] + appname_cn = data['name']['zh_CN'] + except Exception as e: + logging.error(str(e)) + if "zh_CN" in tmpdescription and "en_US" in tmpdescription: + description = tmpdescription["zh_CN"] + ": " + tmpdescription["en_US"] + return (version,description,appname_cn) + else: # 不存在 + return (None, None, None) + + def refreshpkglist(self): + pkgs_install = [] + pkgs_upgrade = [] + pkgs_remove = [] + + for pkg in self.window_main.cache: + try: + if pkg.marked_install: + pkgs_install.append(pkg.name) + if pkg.marked_upgrade: + pkgs_upgrade.append(pkg.name) + elif pkg.marked_delete: + pkgs_remove.append(pkg.name) + except KeyError: + # pkg missing from fresh_cache can't be modified + pass + return pkgs_install,pkgs_upgrade,pkgs_remove + + def _removal_of_marker(self): + try: + marker_path = "/var/cache/kylin-update-manager/ignoreOrDelay" + if os.path.exists(marker_path): + with open(marker_path, 'r+') as f: + line= f.readline() + if "2107" in line or "2203" in line: + f.seek(0) + f.truncate() + except Exception as e: + logging.error("Removing the upgrade success mark error: %s.",str(e)) + + # #查找数据库 + # def find_msg_from_datebase(self, table, field, action = 'check', cid = 0): + # # 查询数据 + # try: + # sql = "select "+field+" from "+table + # self.cursor.execute(sql) + # update_count = self.cursor.fetchone()[0] + # logging.info("%d history updates detected.", update_count) + # except Exception as e: + # logging.error("Check update error: %s", str(e)) + + def _system_version_config(self): + self.connect_database() + try: + sql = "select init_version from display where id=1" + self.cursor.execute(sql) + _is_init_verison = self.cursor.fetchone()[0] + if _is_init_verison == "yes": + update_version, os_version = self.get_default_version() + logging.info("Need to refresh version ...") + self._refresh_system_version(update_version, os_version) + sql = "update display set init_version = 'no'" + self.cursor.execute(sql) + self.connect.commit() + except Exception as e: + logging.error(str(e)) + self.disconnect_database() + self.disconnect_database() + + def _refresh_system_version(self, update_version='', os_version = '', pseudo_version = False): + try: + if "=" in update_version: + update_version = str(update_version).split('=')[-1] + if "=" in os_version: + os_version = str(os_version).split('=')[-1] + os_version = os_version.strip() + update_version = update_version.strip() + + #刷新系统版本号:os_version + if update_version == '': + update_version, os_version = self.get_default_version() + if os_version == '' and os.path.isfile("/etc/os-release"): + with open("/etc/os-release", "r+") as f: + lines = f.readlines() + for line in lines: + if "KYLIN_RELEASE_ID" in line: + os_version = line.split('=')[1] + os_version = os_version.strip() + if not pseudo_version: + if len(os_version) != 0: + self.ucconfigs.setValue("SYSTEM","os_version",str(os_version),True) + if len(update_version) != 0: + self.ucconfigs.setValue("SYSTEM","update_version",str(update_version),True) + else: + current_update_version, current_os_version = self.get_current_version() + if current_os_version != os_version: + os_version+='*' + self.ucconfigs.setValue("SYSTEM","os_version",str(os_version),True) + if current_update_version != update_version: + update_version+='*' + self.ucconfigs.setValue("SYSTEM","update_version",str(update_version),True) + except Exception as e: + logging.error("Refresh system version error: %s.",str(e)) + + def get_default_version(self): + update_version = "" + os_version = "" + INPUT_CONFIG_PATH = self.config_path + 'kylin-update-desktop-system.json' + if os.path.isfile(INPUT_CONFIG_PATH): # 存在 + # 读取JSON文件 + with open(INPUT_CONFIG_PATH, "r") as f: + try : + data = json.load(f) + except json.JSONDecodeError as e: + logging.error(str(e)) + try: + update_version = data['version'] + if "=" in update_version: + update_version = update_version.split('=')[-1].strip() + except Exception as e: + logging.error("get_default_version error: %s .",str(e)) + version_path = "/etc/os-release" + if os.path.isfile(version_path): + with open(version_path, "r+") as f: + lines = f.readlines() + for line in lines: + if "KYLIN_RELEASE_ID" in line: + os_version = line.split('=')[1] + os_version = eval(os_version.strip()) + if update_version == "" and os_version != "": + update_version = os_version + elif update_version != "" and os_version == "": + os_version = update_version + return str(update_version),str(os_version) + + def get_current_version(self): + os_version = '' + update_version = '' + try: + if not os.path.exists("/etc/kylin-version/kylin-system-version.conf"): + logging.warning("System version file doesn't exist.") + return os_version,update_version + os_version = eval(str(self.window_main.sqlite3_server.ucconfigs.get("SYSTEM","os_version"))) + update_version = str(self.window_main.sqlite3_server.ucconfigs.get("SYSTEM","update_version")) + except Exception as e: + logging.error(str(e)) + return update_version,os_version + logging.info('Current os_version: %s, release_id: %s .', os_version, update_version) + return str(update_version),str(os_version) + + def get_cn_appname(self, name): + try: + SC_DB_FILE = "/usr/share/kylin-software-center/data/uksc.db" + if os.path.isfile(SC_DB_FILE): + connect = sqlite3.connect(SC_DB_FILE, check_same_thread=False) + cursor = connect.cursor() + else: + logging.warning("software center database isn't exist .") + return "" + sql = "select display_name_cn from application where display_name='"+name+"'" + cursor.execute(sql) + connect.commit() + retval = cursor.fetchone() + connect.close() + if retval != None and len(retval) != 0: + return str(retval[0]) + else: + return '' + except Exception as e: + logging.error(_("Failed to initialize the database: %s"), str(e)) + return '' + + def insert_upgrade_history(self, args, caller): + caller_list = ['kylin-unattended-upgrade', "d-feet", "root"] + _in_list = False + for cl in caller_list: + if caller in cl: + _in_list = True + if _in_list == False: + logging.warning("Caller \" %s \": Operation without permission...", caller) + return False + # {"appname":GLib.Variant("s", "kylin-system-updater"), "version":GLib.Variant("s", "0.0")} + # "description":GLib.Variant("s", "Update Manager for Kylin"), "date":GLib.Variant("s", "2022-07-27 15:23:51") + # "status":GLib.Variant("s", "failed"), "keyword":GLib.Variant("s", "1") + # "errorcode":GLib.Variant("s", "System upgrade is complete. "), "appname_cn":GLib.Variant("s", "音乐") + upgrade_info = {} + try: + for it in args: + upgrade_info[str(it)] = str(args[str(it)]) + logging.info("upgrade_info: %s", upgrade_info) + + if "appname" in upgrade_info.keys() and "version" in upgrade_info.keys() \ + and "description" in upgrade_info.keys() \ + and "date" in upgrade_info.keys() \ + and "status" in upgrade_info.keys() \ + and "keyword" in upgrade_info.keys() \ + and "errorcode" in upgrade_info.keys() \ + and "appname_cn" in upgrade_info.keys() \ + and "status_cn" in upgrade_info.keys() \ + and "changelog" in upgrade_info.keys(): + appname = upgrade_info["appname"] + if "kylin-unattended-upgrade" == appname: + upgrade_info["appname"] = self.get_cn_appname(appname) + if upgrade_info["appname"] == "": + upgrade_info["appname"] = _("kylin-unattended-upgrade") + if appname in self.window_main.cache and upgrade_info["description"] == "": + pkg = self.window_main.cache[appname] + if pkg.is_installed: + upgrade_info["description"] = pkg.installed.description + self.insert_into_updateinfo( upgrade_info["appname"], upgrade_info["version"], \ + upgrade_info["description"], \ + upgrade_info["date"], \ + upgrade_info["status"], \ + upgrade_info["keyword"], \ + upgrade_info["errorcode"], \ + upgrade_info["appname_cn"], \ + upgrade_info["status_cn"], \ + upgrade_info["changelog"] ) + else: + logging.warning("Incomplete field.") + return False + except Exception as e: + logging.error(e) + return False + return True + +def listtojsonstr(lists): + import json + jsonfile = json.dumps(lists) + return jsonfile diff --git a/backend/SystemUpdater/Core/DistUpgradeCache.py b/backend/SystemUpdater/Core/DistUpgradeCache.py new file mode 100644 index 0000000..2e6f178 --- /dev/null +++ b/backend/SystemUpdater/Core/DistUpgradeCache.py @@ -0,0 +1,1251 @@ +# DistUpgradeCache.py +# +# Copyright (c) 2004-2008 Canonical +# +# Author: Michael Vogt +# +# 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 apt +import apt_pkg +import glob +import locale +import os +import re +import logging +import time +import datetime +import threading +import configparser +from subprocess import Popen, PIPE + +from gettext import gettext as _ + +from .utils import inside_chroot + +class CacheException(Exception): + pass + + +class CacheExceptionLockingFailed(CacheException): + pass + + +class CacheExceptionDpkgInterrupted(CacheException): + pass + + +def estimate_kernel_initrd_size_in_boot(): + """estimate the amount of space used by the kernel and initramfs in /boot, + including a safety margin + """ + kernel = 0 + initrd = 0 + kver = os.uname()[2] + for f in glob.glob("/boot/*%s*" % kver): + if f == '/boot/initrd.img-%s' % kver: + initrd += os.path.getsize(f) + # don't include in the estimate any files that are left behind by + # an interrupted package manager run + elif (f.find('initrd.img') >= 0 or f.find('.bak') >= 0 + or f.find('.dpkg-') >= 0): + continue + else: + kernel += os.path.getsize(f) + if kernel == 0: + # logging.warning( + # "estimate_kernel_initrd_size_in_boot() returned '0' for kernel?") + kernel = 16*1024*1024 + if initrd == 0: + # logging.warning( + # "estimate_kernel_initrd_size_in_boot() returned '0' for initrd?") + initrd = 100*1024*1024 + # add small safety buffer + kernel += 1*1024*1024 + # safety buffer as a percentage of the existing initrd's size + initrd_buffer = 1*1024*1024 + if initrd * 0.05 > initrd_buffer: + initrd_buffer = initrd * 0.05 + initrd += initrd_buffer + return kernel,initrd +KERNEL_SIZE, INITRD_SIZE = estimate_kernel_initrd_size_in_boot() + + +class FreeSpaceRequired(object): + """ FreeSpaceRequired object: + + This exposes: + - the total size required (size_total) + - the dir that requires the space (dir) + - the additional space that is needed (size_needed) + """ + def __init__(self, size_total, dir, size_needed): + self.size_total = size_total + self.dir = dir + self.size_needed = size_needed + def __str__(self): + return "FreeSpaceRequired Object: Dir: %s size_total: %s size_needed: %s" % (self.dir, self.size_total, self.size_needed) + + +class NotEnoughFreeSpaceError(CacheException): + """ + Exception if there is not enough free space for this operation + + """ + def __init__(self, free_space_required_list): + self.free_space_required_list = free_space_required_list + + +class MyCache(apt.Cache): + ReInstReq = 1 + HoldReInstReq = 3 + + # init + def __init__(self, config, view, quirks, progress=None, lock=True): + self.to_install = [] + self.to_remove = [] + self.view = view + self.quirks = quirks + self.lock = False + self.partialUpgrade = False + self.config = config + self.metapkgs = self.config.getlist("Distro", "MetaPkgs") + # acquire lock + self._listsLock = -1 + if lock: + try: + apt_pkg.pkgsystem_lock() + self.lock_lists_dir() + self.lock = True + except SystemError as e: + # checking for this is ok, its not translatable + if "dpkg --configure -a" in str(e): + raise CacheExceptionDpkgInterrupted(e) + raise CacheExceptionLockingFailed(e) + # Do not create the cache until we know it is not locked + apt.Cache.__init__(self, progress) + # a list of regexp that are not allowed to be removed + self.removal_blacklist = config.getListFromFile("Distro", "RemovalBlacklistFile") + # the linux metapackage should not be removed + self.linux_metapackage = self.quirks._get_linux_metapackage(self, False) + self.uname = Popen(["uname", "-r"], stdout=PIPE, + universal_newlines=True).communicate()[0].strip() + self._initAptLog() + # from hardy on we use recommends by default, so for the + # transition to the new dist we need to enable them now + if (config.get("Sources", "From") == "hardy" and + not "RELEASE_UPGRADE_NO_RECOMMENDS" in os.environ): + apt_pkg.config.set("APT::Install-Recommends", "true") + + + apt_pkg.config.set("APT::AutoRemove::SuggestsImportant", "false") + + def _apply_dselect_upgrade(self): + """ honor the dselect install state """ + for pkg in self: + if pkg.is_installed: + continue + if pkg._pkg.selected_state == apt_pkg.SELSTATE_INSTALL: + # upgrade() will take care of this + pkg.mark_install(auto_inst=False, auto_fix=False) + + @property + def req_reinstall_pkgs(self): + " return the packages not downloadable packages in reqreinst state " + reqreinst = set() + for pkg in self: + if ((not pkg.candidate or not pkg.candidate.downloadable) + and + (pkg._pkg.inst_state == self.ReInstReq or + pkg._pkg.inst_state == self.HoldReInstReq)): + reqreinst.add(pkg.name) + return reqreinst + + def fix_req_reinst(self, view): + " check for reqreinst state and offer to fix it " + reqreinst = self.req_reinstall_pkgs + if len(reqreinst) > 0: + header = _("Remove package in bad state", + "Remove packages in bad state", + len(reqreinst)) + summary = _("The package '%s' is in an inconsistent " + "state and needs to be reinstalled, but " + "no archive can be found for it. " + "Do you want to remove this package " + "now to continue?", + "The packages '%s' are in an inconsistent " + "state and need to be reinstalled, but " + "no archives can be found for them. Do you " + "want to remove these packages now to " + "continue?", + len(reqreinst)) % ", ".join(reqreinst) + if view.askYesNoQuestion(header, summary): + self.release_lock() + cmd = ["/usr/bin/dpkg", "--remove", "--force-remove-reinstreq"] + list(reqreinst) + view.getTerminal().call(cmd) + self.get_lock() + return True + return False + + # logging stuff + def _initAptLog(self): + " init logging, create log file" + logdir = self.config.getWithDefault("Files", "LogDir", + "/var/log/dist-upgrade") + if not os.path.exists(logdir): + os.makedirs(logdir) + apt_pkg.config.set("Dir::Log", logdir) + apt_pkg.config.set("Dir::Log::Terminal", "apt-term.log") + self.logfd = os.open(os.path.join(logdir, "apt.log"), + os.O_RDWR | os.O_CREAT | os.O_APPEND, 0o644) + now = datetime.datetime.now() + header = "Log time: %s\n" % now + os.write(self.logfd, header.encode("utf-8")) + + # turn on debugging in the cache + apt_pkg.config.set("Debug::pkgProblemResolver", "true") + apt_pkg.config.set("Debug::pkgDepCache::Marker", "true") + apt_pkg.config.set("Debug::pkgDepCache::AutoInstall", "true") + def _startAptResolverLog(self): + if hasattr(self, "old_stdout"): + os.close(self.old_stdout) + os.close(self.old_stderr) + self.old_stdout = os.dup(1) + self.old_stderr = os.dup(2) + os.dup2(self.logfd, 1) + os.dup2(self.logfd, 2) + def _stopAptResolverLog(self): + os.fsync(1) + os.fsync(2) + os.dup2(self.old_stdout, 1) + os.dup2(self.old_stderr, 2) + # use this decorator instead of the _start/_stop stuff directly + # FIXME: this should probably be a decorator class where all + # logging is moved into? + def withResolverLog(f): + " decorator to ensure that the apt output is logged " + def wrapper(*args, **kwargs): + args[0]._startAptResolverLog() + res = f(*args, **kwargs) + args[0]._stopAptResolverLog() + return res + return wrapper + + # properties + @property + def required_download(self): + """ get the size of the packages that are required to download """ + pm = apt_pkg.PackageManager(self._depcache) + fetcher = apt_pkg.Acquire() + pm.get_archives(fetcher, self._list, self._records) + return fetcher.fetch_needed + @property + def additional_required_space(self): + """ get the size of the additional required space on the fs """ + return self._depcache.usr_size + @property + def additional_required_space_for_snaps(self): + """ get the extra size needed to install the snap replacements """ + try: + # update-manager uses DistUpgradeCache.MyCache as the base class + # of its own MyCache version - but without actually calling our + # constructor at all. This causes that the MyCache version from + # update-manager has no self.quirks attribute while still calling + # our default version of checkFreeSpace(). Since extra_snap_space + # is only used on dist-upgrades, let's just not care and return 0 + # in this weird, undocumented case. + return self.quirks.extra_snap_space + except AttributeError: + return 0 + @property + def is_broken(self): + """ is the cache broken """ + return self._depcache.broken_count > 0 + + # methods + def lock_lists_dir(self): + name = apt_pkg.config.find_dir("Dir::State::Lists") + "lock" + self._listsLock = apt_pkg.get_lock(name) + if self._listsLock < 0: + e = "Can not lock '%s' " % name + raise CacheExceptionLockingFailed(e) + def unlock_lists_dir(self): + if self._listsLock > 0: + os.close(self._listsLock) + self._listsLock = -1 + def update(self, fprogress=None): + """ + our own update implementation is required because we keep the lists + dir lock + """ + self.unlock_lists_dir() + res = apt.Cache.update(self, fprogress) + self.lock_lists_dir() + if fprogress and fprogress.release_file_download_error: + # FIXME: not ideal error message, but we just reuse a + # existing one here to avoid a new string + raise IOError(_("The server may be overloaded")) + if res == False: + raise IOError("apt.cache.update() returned False, but did not raise exception?!?") + + def commit(self, fprogress, iprogress): + logging.info("cache.commit()") + if self.lock: + self.release_lock() + apt.Cache.commit(self, fprogress, iprogress) + + def release_lock(self, pkgSystemOnly=True): + if self.lock: + try: + apt_pkg.pkgsystem_unlock() + self.lock = False + except SystemError as e: + logging.debug("failed to SystemUnLock() (%s) " % e) + + def get_lock(self, pkgSystemOnly=True): + if not self.lock: + try: + apt_pkg.pkgsystem_lock() + self.lock = True + except SystemError as e: + logging.debug("failed to SystemLock() (%s) " % e) + + def downloadable(self, pkg, useCandidate=True): + " check if the given pkg can be downloaded " + if useCandidate: + ver = self._depcache.get_candidate_ver(pkg._pkg) + else: + ver = pkg._pkg.current_ver + if ver == None: + logging.warning("no version information for '%s' (useCandidate=%s)" % (pkg.name, useCandidate)) + return False + return ver.downloadable + + def pkg_auto_removable(self, pkg): + """ check if the pkg is auto-removable """ + return (pkg.is_installed and + self._depcache.is_garbage(pkg._pkg)) + + def fix_broken(self): + """ try to fix broken dependencies on the system, may throw + SystemError when it can't""" + return self._depcache.fix_broken() + + def create_snapshot(self): + """ create a snapshot of the current changes """ + self.to_install = [] + self.to_remove = [] + for pkg in self.get_changes(): + if pkg.marked_install or pkg.marked_upgrade: + self.to_install.append(pkg.name) + if pkg.marked_delete: + self.to_remove.append(pkg.name) + + def clear(self): + self._depcache.init() + + def restore_snapshot(self): + """ restore a snapshot """ + actiongroup = apt_pkg.ActionGroup(self._depcache) + # just make pyflakes shut up, later we need to use + # with self.actiongroup(): + actiongroup + self.clear() + for name in self.to_remove: + pkg = self[name] + pkg.mark_delete() + for name in self.to_install: + pkg = self[name] + pkg.mark_install(auto_fix=False, auto_inst=False) + + def need_server_mode(self): + """ + This checks if we run on a desktop or a server install. + + A server install has more freedoms, for a desktop install + we force a desktop meta package to be install on the upgrade. + + We look for a installed desktop meta pkg and for key + dependencies, if none of those are installed we assume + server mode + """ + #logging.debug("need_server_mode() run") + # check for the MetaPkgs (e.g. ubuntu-desktop) + metapkgs = self.config.getlist("Distro", "MetaPkgs") + for key in metapkgs: + # if it is installed we are done + if key in self and self[key].is_installed: + logging.debug("need_server_mode(): run in 'desktop' mode, (because of pkg '%s')" % key) + return False + # if it is not installed, but its key depends are installed + # we are done too (we auto-select the package later) + deps_found = True + for pkg in self.config.getlist(key, "KeyDependencies"): + deps_found &= pkg in self and self[pkg].is_installed + if deps_found: + logging.debug("need_server_mode(): run in 'desktop' mode, (because of key deps for '%s')" % key) + return False + logging.debug("need_server_mode(): can not find a desktop meta package or key deps, running in server mode") + return True + + def sanity_check(self, view): + """ check if the cache is ok and if the required metapkgs + are installed + """ + if self.is_broken: + try: + logging.debug("Have broken pkgs, trying to fix them") + self.fix_broken() + except SystemError: + view.error(_("Broken packages"), + _("Your system contains broken packages " + "that couldn't be fixed with this " + "software. " + "Please fix them first using synaptic or " + "apt-get before proceeding.")) + return False + return True + + def mark_install(self, pkg, reason="", **flags): + logging.debug("Installing '%s' (%s)" % (pkg, reason)) + if pkg in self: + self[pkg].mark_install(**flags) + if not (self[pkg].marked_install or self[pkg].marked_upgrade): + logging.error("Installing/upgrading '%s' failed" % pkg) + #raise SystemError("Installing '%s' failed" % pkg) + return False + return True + + def mark_upgrade(self, pkg, reason=""): + logging.debug("Upgrading '%s' (%s)" % (pkg, reason)) + if pkg in self and self[pkg].is_installed: + self[pkg].mark_upgrade() + if not self[pkg].marked_upgrade: + logging.error("Upgrading '%s' failed" % pkg) + return False + return True + + def mark_remove(self, pkg, reason="", **flags): + logging.debug("Removing '%s' (%s)" % (pkg, reason)) + if pkg in self: + self[pkg].mark_delete(**flags) + + def mark_purge(self, pkg, reason=""): + logging.debug("Purging '%s' (%s)" % (pkg, reason)) + if pkg in self: + self._depcache.mark_delete(self[pkg]._pkg, True) + + def _keep_installed(self, pkgname, reason): + if (pkgname in self + and self[pkgname].is_installed + and self[pkgname].marked_delete): + self.mark_install(pkgname, reason) + + def keep_installed_rule(self): + """ run after the dist-upgrade to ensure that certain + packages are kept installed """ + # first the global list + for pkgname in self.config.getlist("Distro", "KeepInstalledPkgs"): + self._keep_installed(pkgname, "Distro KeepInstalledPkgs rule") + # the the per-metapkg rules + for key in self.metapkgs: + if key in self and (self[key].is_installed or + self[key].marked_install): + for pkgname in self.config.getlist(key, "KeepInstalledPkgs"): + self._keep_installed(pkgname, "%s KeepInstalledPkgs rule" % key) + + # only enforce section if we have a network. Otherwise we run + # into CD upgrade issues for installed language packs etc + if self.config.get("Options", "withNetwork") == "True": + logging.debug("Running KeepInstalledSection rules") + # now the KeepInstalledSection code + for section in self.config.getlist("Distro", "KeepInstalledSection"): + for pkg in self: + if (pkg.candidate and pkg.candidate.downloadable + and pkg.marked_delete + and pkg.candidate.section == section): + self._keep_installed(pkg.name, "Distro KeepInstalledSection rule: %s" % section) + for key in self.metapkgs: + if key in self and (self[key].is_installed or + self[key].marked_install): + for section in self.config.getlist(key, "KeepInstalledSection"): + for pkg in self: + if (pkg.candidate and pkg.candidate.downloadable + and pkg.marked_delete and + pkg.candidate.section == section): + self._keep_installed(pkg.name, "%s KeepInstalledSection rule: %s" % (key, section)) + + + def pre_upgrade_rule(self): + " run before the upgrade was done in the cache " + # run the quirks handlers + if not self.partialUpgrade: + self.quirks.run("PreDistUpgradeCache") + + def post_upgrade_rule(self): + " run after the upgrade was done in the cache " + for (rule, action) in [("Install", self.mark_install), + ("Upgrade", self.mark_upgrade), + ("Remove", self.mark_remove), + ("Purge", self.mark_purge)]: + # first the global list + for pkg in self.config.getlist("Distro", "PostUpgrade%s" % rule): + action(pkg, "Distro PostUpgrade%s rule" % rule) + for key in self.metapkgs: + if key in self and (self[key].is_installed or + self[key].marked_install): + for pkg in self.config.getlist(key, "PostUpgrade%s" % rule): + action(pkg, "%s PostUpgrade%s rule" % (key, rule)) + # run the quirks handlers + if not self.partialUpgrade: + self.quirks.run("PostDistUpgradeCache") + + def checkForNvidia(self): + """ + this checks for nvidia hardware and checks what driver is needed + """ + logging.debug("nvidiaUpdate()") + # if the free drivers would give us a equally hard time, we would + # never be able to release + try: + from NvidiaDetector.nvidiadetector import NvidiaDetection + except (ImportError, SyntaxError) as e: + # SyntaxError is temporary until the port of NvidiaDetector to + # Python 3 is in the archive. + logging.error("NvidiaDetector can not be imported %s" % e) + return False + try: + # get new detection module and use the modalises files + # from within the release-upgrader + nv = NvidiaDetection(obsolete="./ubuntu-drivers-obsolete.pkgs") + #nv = NvidiaDetection() + # check if a binary driver is installed now + for oldDriver in nv.oldPackages: + if oldDriver in self and self[oldDriver].is_installed: + self.mark_remove(oldDriver, "old nvidia driver") + break + else: + logging.info("no old nvidia driver installed, installing no new") + return False + # check which one to use + driver = nv.selectDriver() + logging.debug("nv.selectDriver() returned '%s'" % driver) + if not driver in self: + logging.warning("no '%s' found" % driver) + return False + if not (self[driver].marked_install or self[driver].marked_upgrade): + self[driver].mark_install() + logging.info("installing %s as suggested by NvidiaDetector" % driver) + return True + except Exception as e: + logging.error("NvidiaDetection returned a error: %s" % e) + return False + + + def _has_kernel_headers_installed(self): + for pkg in self: + if (pkg.name.startswith("linux-headers-") and + pkg.is_installed): + return True + return False + + def checkForKernel(self): + """ check for the running kernel and try to ensure that we have + an updated version + """ + logging.debug("Kernel uname: '%s' " % self.uname) + try: + (version, build, flavour) = self.uname.split("-") + except Exception as e: + logging.warning("Can't parse kernel uname: '%s' (self compiled?)" % e) + return False + # now check if we have a SMP system + dmesg = Popen(["dmesg"], stdout=PIPE).communicate()[0] + if b"WARNING: NR_CPUS limit" in dmesg: + logging.debug("UP kernel on SMP system!?!") + return True + + def checkPriority(self): + # tuple of priorities we require to be installed + need = ('required', ) + # stuff that its ok not to have + removeEssentialOk = self.config.getlist("Distro", "RemoveEssentialOk") + # check now + for pkg in self: + # WORKAROUND bug on the CD/python-apt #253255 + ver = pkg._pcache._depcache.get_candidate_ver(pkg._pkg) + if ver and ver.priority == 0: + logging.error("Package %s has no priority set" % pkg.name) + continue + if (pkg.candidate and pkg.candidate.downloadable and + not (pkg.is_installed or pkg.marked_install) and + not pkg.name in removeEssentialOk and + # ignore multiarch priority required packages + not ":" in pkg.name and + pkg.candidate.priority in need): + self.mark_install(pkg.name, "priority in required set '%s' but not scheduled for install" % need) + + # FIXME: make this a decorator (just like the withResolverLog()) + def updateGUI(self, view, lock): + i = 0 + while lock.locked(): + if i % 15 == 0: + view.pulseProgress() + view.processEvents() + time.sleep(0.02) + i += 1 + view.pulseProgress(finished=True) + view.processEvents() + + @withResolverLog + def distUpgrade(self, view, serverMode, partialUpgrade): + # keep the GUI alive + lock = threading.Lock() + lock.acquire() + t = threading.Thread(target=self.updateGUI, args=(self.view, lock,)) + t.start() + try: + # mvo: disabled as it casues to many errornous installs + #self._apply_dselect_upgrade() + + # run PreDistUpgradeCache quirks + self.pre_upgrade_rule() + + # upgrade (and make sure this way that the cache is ok) + self.upgrade(True) + + # check that everything in priority required is installed + self.checkPriority() + + # see if our KeepInstalled rules are honored + self.keep_installed_rule() + + # check if we got a new kernel (if we are not inside a + # chroot) + if inside_chroot(): + logging.warning("skipping kernel checks because we run inside a chroot") + else: + self.checkForKernel() + + # check for nvidia stuff + self.checkForNvidia() + + # and if we have some special rules + self.post_upgrade_rule() + + # install missing meta-packages (if not in server upgrade mode) + self._keepBaseMetaPkgsInstalled(view) + if not serverMode: + # if this fails, a system error is raised + self._installMetaPkgs(view) + + # see if it all makes sense, if not this function raises + self._verifyChanges() + + if self.is_broken: + raise SystemError(_("Broken packages after upgrade: %s") % ", ".join(p.name for p in self if p.is_inst_broken or p.is_now_broken)) + + except SystemError as e: + # the most likely problem is the 3rd party pkgs so don't address + # foreignPkgs and devRelease being True + details = _("An unresolvable problem occurred while " + "calculating the upgrade.\n\n ") + if self.config.get("Options", "foreignPkgs") == "True": + details += _("This was likely caused by:\n" + " * Unofficial software packages not provided by Ubuntu\n" + "Please use the tool 'ppa-purge' from the ppa-purge \n" + "package to remove software from a Launchpad PPA and \n" + "try the upgrade again.\n" + "\n") + elif self.config.get("Options", "foreignPkgs") == "False" and \ + self.config.get("Options", "devRelease") == "True": + details += _("This was caused by:\n" + " * Upgrading to a pre-release version of Ubuntu\n" + "This is most likely a transient problem, \n" + "please try again later.\n") + # we never have partialUpgrades (including removes) on a stable system + # with only ubuntu sources so we do not recommend reporting a bug + if partialUpgrade: + details += _("This is most likely a transient problem, " + "please try again later.") + else: + details += _("If none of this applies, then please report this bug using " + "the command 'ubuntu-bug ubuntu-release-upgrader-core' in a terminal. ") + details += _("If you want to investigate this yourself the log files in " + "'/var/log/dist-upgrade' will contain details about the upgrade. " + "Specifically, look at 'main.log' and 'apt.log'.") + # make the error text available again on stdout for the + # text frontend + self._stopAptResolverLog() + view.error(_("Could not calculate the upgrade"), details) + # may contain utf-8 (LP: #1310053) + error_msg = str(e) + logging.error("Dist-upgrade failed: '%s'", error_msg) + # start the resolver log again because this is run with + # the withResolverLog decorator + self._startAptResolverLog() + return False + finally: + # wait for the gui-update thread to exit + lock.release() + t.join() + + # check the trust of the packages that are going to change + untrusted = [] + downgrade = [] + for pkg in self.get_changes(): + if pkg.marked_delete: + continue + # special case because of a bug in pkg.candidate.origins + if pkg.marked_downgrade: + downgrade.append(pkg.name) + for ver in pkg._pkg.version_list: + # version is lower than installed one + if apt_pkg.version_compare( + ver.ver_str, pkg.installed.version) < 0: + for (verFileIter, index) in ver.file_list: + indexfile = pkg._pcache._list.find_index(verFileIter) + if indexfile and not indexfile.is_trusted: + untrusted.append(pkg.name) + break + continue + origins = pkg.candidate.origins + trusted = False + for origin in origins: + #print(origin) + trusted |= origin.trusted + if not trusted: + untrusted.append(pkg.name) + # check if the user overwrote the unauthenticated warning + try: + b = self.config.getboolean("Distro", "AllowUnauthenticated") + if b: + logging.warning("AllowUnauthenticated set!") + return True + except configparser.NoOptionError: + pass + if len(downgrade) > 0: + downgrade.sort() + logging.error("Packages to downgrade found: '%s'" % + " ".join(downgrade)) + if len(untrusted) > 0: + untrusted.sort() + logging.error("Unauthenticated packages found: '%s'" % + " ".join(untrusted)) + # FIXME: maybe ask a question here? instead of failing? + self._stopAptResolverLog() + view.error(_("Error authenticating some packages"), + _("It was not possible to authenticate some " + "packages. This may be a transient network problem. " + "You may want to try again later. See below for a " + "list of unauthenticated packages."), + "\n".join(untrusted)) + # start the resolver log again because this is run with + # the withResolverLog decorator + self._startAptResolverLog() + return False + return True + + def _verifyChanges(self): + """ this function tests if the current changes don't violate + our constrains (blacklisted removals etc) + """ + main_arch = apt_pkg.config.find("APT::Architecture") + removeEssentialOk = self.config.getlist("Distro", "RemoveEssentialOk") + # check changes + for pkg in self.get_changes(): + if pkg.marked_delete and self._inRemovalBlacklist(pkg.name): + logging.debug("The package '%s' is marked for removal but it's in the removal blacklist", pkg.name) + raise SystemError(_("The package '%s' is marked for removal but it is in the removal blacklist.") % pkg.name) + if pkg.marked_delete and ( + pkg._pkg.essential == True and + pkg.installed.architecture in (main_arch, "all") and + not pkg.name in removeEssentialOk): + logging.debug("The package '%s' is marked for removal but it's an ESSENTIAL package", pkg.name) + raise SystemError(_("The essential package '%s' is marked for removal.") % pkg.name) + # check bad-versions blacklist + badVersions = self.config.getlist("Distro", "BadVersions") + for bv in badVersions: + (pkgname, ver) = bv.split("_") + if (pkgname in self and self[pkgname].candidate and + self[pkgname].candidate.version == ver and + (self[pkgname].marked_install or + self[pkgname].marked_upgrade)): + raise SystemError(_("Trying to install blacklisted version '%s'") % bv) + return True + + def _lookupPkgRecord(self, pkg): + """ + helper to make sure that the pkg._records is pointing to the right + location - needed because python-apt 0.7.9 dropped the python-apt + version but we can not yet use the new version because on upgrade + the old version is still installed + """ + ver = pkg._pcache._depcache.get_candidate_ver(pkg._pkg) + if ver is None: + print("No candidate ver: ", pkg.name) + return False + if ver.file_list is None: + print("No file_list for: %s " % self._pkg.name()) + return False + f, index = ver.file_list.pop(0) + pkg._pcache._records.lookup((f, index)) + return True + + @property + def installedTasks(self): + tasks = {} + installed_tasks = set() + for pkg in self: + if not self._lookupPkgRecord(pkg): + logging.debug("no PkgRecord found for '%s', skipping " % pkg.name) + continue + for line in pkg._pcache._records.record.split("\n"): + if line.startswith("Task:"): + for task in (line[len("Task:"):]).split(","): + task = task.strip() + if task not in tasks: + tasks[task] = set() + tasks[task].add(pkg.name) + for task in tasks: + installed = True + ignored_tasks = self.config.getlist("Distro", "IgnoredTasks") + if task in ignored_tasks: + installed = False + for pkgname in tasks[task]: + if not self[pkgname].is_installed: + installed = False + break + if installed: + installed_tasks.add(task) + return installed_tasks + + def installTasks(self, tasks): + logging.debug("running installTasks") + for pkg in self: + if pkg.marked_install or pkg.is_installed: + continue + self._lookupPkgRecord(pkg) + if not (hasattr(pkg._pcache._records, "record") and pkg._pcache._records.record): + logging.warning("can not find Record for '%s'" % pkg.name) + continue + for line in pkg._pcache._records.record.split("\n"): + if line.startswith("Task:"): + for task in (line[len("Task:"):]).split(","): + task = task.strip() + if task in tasks: + pkg.mark_install() + return True + + def _keepBaseMetaPkgsInstalled(self, view): + for pkg in self.config.getlist("Distro", "BaseMetaPkgs"): + self._keep_installed(pkg, "base meta package keep installed rule") + + def _installMetaPkgs(self, view): + + def metaPkgInstalled(): + """ + internal helper that checks if at least one meta-pkg is + installed or marked install + """ + for key in metapkgs: + if key in self: + pkg = self[key] + if pkg.is_installed and pkg.marked_delete: + logging.debug("metapkg '%s' installed but marked_delete" % pkg.name) + if ((pkg.is_installed and not pkg.marked_delete) + or self[key].marked_install): + return True + return False + + # now check for ubuntu-desktop, kubuntu-desktop, edubuntu-desktop + metapkgs = self.config.getlist("Distro", "MetaPkgs") + + # we never go without ubuntu-base + for pkg in self.config.getlist("Distro", "BaseMetaPkgs"): + self[pkg].mark_install() + + # every meta-pkg that is installed currently, will be marked + # install (that result in a upgrade and removes a mark_delete) + for key in metapkgs: + try: + if (key in self and + self[key].is_installed and + self[key].is_upgradable): + logging.debug("Marking '%s' for upgrade" % key) + self[key].mark_upgrade() + except SystemError as e: + # warn here, but don't fail, its possible that meta-packages + # conflict (like ubuntu-desktop vs xubuntu-desktop) LP: #775411 + logging.warning("Can't mark '%s' for upgrade (%s)" % (key, e)) + + # check if we have a meta-pkg, if not, try to guess which one to pick + if not metaPkgInstalled(): + logging.debug("none of the '%s' meta-pkgs installed" % metapkgs) + for key in metapkgs: + deps_found = True + for pkg in self.config.getlist(key, "KeyDependencies"): + deps_found &= pkg in self and self[pkg].is_installed + if deps_found: + logging.debug("guessing '%s' as missing meta-pkg" % key) + try: + self[key].mark_install() + except (SystemError, KeyError) as e: + logging.error("failed to mark '%s' for install (%s)" % + (key, e)) + view.error(_("Can't install '%s'") % key, + _("It was impossible to install a " + "required package. Please report " + "this as a bug using " + "'ubuntu-bug ubuntu-release-upgrader-core' in " + "a terminal.")) + return False + logging.debug("marked_install: '%s' -> '%s'" % (key, self[key].marked_install)) + break + # check if we actually found one + if not metaPkgInstalled(): + meta_pkgs = ', '.join(metapkgs[0:-1]) + view.error(_("Can't guess meta-package"), + _("Your system does not contain a " + "%s or %s package and it was not " + "possible to detect which version of " + "Ubuntu you are running.\n " + "Please install one of the packages " + "above first using synaptic or " + "apt-get before proceeding.") % + (meta_pkgs, metapkgs[-1])) + return False + return True + + def _inRemovalBlacklist(self, pkgname): + for expr in self.removal_blacklist: + if re.compile(expr).match(pkgname): + logging.debug("blacklist expr '%s' matches '%s'" % (expr, pkgname)) + return True + return False + + @withResolverLog + def tryMarkObsoleteForRemoval(self, pkgname, remove_candidates, forced_obsoletes, foreign_pkgs): + #logging.debug("tryMarkObsoleteForRemoval(): %s" % pkgname) + # sanity check, first see if it looks like a running kernel pkg + if pkgname.endswith(self.uname): + logging.debug("skipping running kernel pkg '%s'" % pkgname) + return False + if pkgname == self.linux_metapackage: + logging.debug("skipping kernel metapackage '%s'" % pkgname) + return False + if self._inRemovalBlacklist(pkgname): + logging.debug("skipping '%s' (in removalBlacklist)" % pkgname) + return False + # ensure we honor KeepInstalledSection here as well + for section in self.config.getlist("Distro", "KeepInstalledSection"): + if (pkgname in self and self[pkgname].installed and + self[pkgname].installed.section == section): + logging.debug("skipping '%s' (in KeepInstalledSection)" % pkgname) + return False + # if we don't have the package anyway, we are fine (this can + # happen when forced_obsoletes are specified in the config file) + if pkgname not in self: + #logging.debug("package '%s' not in cache" % pkgname) + return True + # check if we want to purge + try: + purge = self.config.getboolean("Distro", "PurgeObsoletes") + except configparser.NoOptionError: + purge = False + + # if this package has not been forced obsolete, only + # delete it if it doesn't remove other dependents + # that are not obsolete as well + actiongroup = apt_pkg.ActionGroup(self._depcache) + # just make pyflakes shut up, later we should use + # with self.actiongroup(): + actiongroup + self.create_snapshot() + try: + self[pkgname].mark_delete(purge=purge) + self.view.processEvents() + if pkgname in forced_obsoletes: + return True + #logging.debug("marking '%s' for removal" % pkgname) + for pkg in self.get_changes(): + if (pkg.name not in remove_candidates or + pkg.name in foreign_pkgs or + self._inRemovalBlacklist(pkg.name) or + pkg.name == self.linux_metapackage): + logging.debug("package '%s' produces an unwanted removal '%s', skipping" % (pkgname, pkg.name)) + self.restore_snapshot() + return False + except (SystemError, KeyError) as e: + logging.warning("_tryMarkObsoleteForRemoval failed for '%s' (%s: %s)" % (pkgname, repr(e), e)) + self.restore_snapshot() + return False + return True + + def _getObsoletesPkgs(self): + " get all package names that are not downloadable " + obsolete_pkgs = set() + for pkg in self: + if pkg.is_installed: + # check if any version is downloadable. we need to check + # for older ones too, because there might be + # cases where e.g. firefox in gutsy-updates is newer + # than hardy + if not self.anyVersionDownloadable(pkg): + obsolete_pkgs.add(pkg.name) + return obsolete_pkgs + + def anyVersionDownloadable(self, pkg): + " helper that checks if any of the version of pkg is downloadable " + for ver in pkg._pkg.version_list: + if ver.downloadable: + return True + return False + + def _getUnusedDependencies(self): + " get all package names that are not downloadable " + unused_dependencies = set() + for pkg in self: + if pkg.is_installed and self._depcache.is_garbage(pkg._pkg): + unused_dependencies.add(pkg.name) + return unused_dependencies + + def get_installed_demoted_packages(self): + """ return list of installed and demoted packages + + If a demoted package is a automatic install it will be skipped + """ + demotions = set() + demotions_file = self.config.get("Distro", "Demotions") + if os.path.exists(demotions_file): + with open(demotions_file) as demotions_f: + for line in demotions_f: + if not line.startswith("#"): + demotions.add(line.strip()) + installed_demotions = set() + for demoted_pkgname in demotions: + if demoted_pkgname not in self: + continue + pkg = self[demoted_pkgname] + if (not pkg.is_installed or + self._depcache.is_auto_installed(pkg._pkg) or + pkg.marked_delete): + continue + installed_demotions.add(pkg) + return list(installed_demotions) + + def _getForeignPkgs(self, allowed_origin, fromDist, toDist): + """ get all packages that are installed from a foreign repo + (and are actually downloadable) + """ + foreign_pkgs = set() + for pkg in self: + if pkg.is_installed and self.downloadable(pkg): + if not pkg.candidate: + continue + # assume it is foreign and see if it is from the + # official archive + foreign = True + for origin in pkg.candidate.origins: + # FIXME: use some better metric here + if fromDist in origin.archive and \ + origin.origin == allowed_origin: + foreign = False + if toDist in origin.archive and \ + origin.origin == allowed_origin: + foreign = False + if foreign: + foreign_pkgs.add(pkg.name) + return foreign_pkgs + + def checkFreeSpace(self, snapshots_in_use=False): + """ + this checks if we have enough free space on /var, /boot and /usr + with the given cache + + Note: this can not be fully accurate if there are multiple + mountpoints for /usr, /var, /boot + """ + + class FreeSpace(object): + " helper class that represents the free space on each mounted fs " + def __init__(self, initialFree): + self.free = initialFree + self.need = 0 + + def make_fs_id(d): + """ return 'id' of a directory so that directories on the + same filesystem get the same id (simply the mount_point) + """ + for mount_point in mounted: + if d.startswith(mount_point): + return mount_point + return "/" + + # this is all a bit complicated + # 1) check what is mounted (in mounted) + # 2) create FreeSpace objects for the dirs we are interested in + # (mnt_map) + # 3) use the mnt_map to check if we have enough free space and + # if not tell the user how much is missing + mounted = [] + mnt_map = {} + fs_free = {} + with open("/proc/mounts") as mounts: + for line in mounts: + try: + (what, where, fs, options, a, b) = line.split() + except ValueError as e: + logging.debug("line '%s' in /proc/mounts not understood (%s)" % (line, e)) + continue + if not where in mounted: + mounted.append(where) + # make sure mounted is sorted by longest path + mounted.sort(key=len, reverse=True) + archivedir = apt_pkg.config.find_dir("Dir::Cache::archives") + aufs_rw_dir = "/tmp/" + if (hasattr(self, "config") and + self.config.getWithDefault("Aufs", "Enabled", False)): + aufs_rw_dir = self.config.get("Aufs", "RWDir") + if not os.path.exists(aufs_rw_dir): + os.makedirs(aufs_rw_dir) + logging.debug("cache aufs_rw_dir: %s" % aufs_rw_dir) + for d in ["/", "/usr", "/var", "/boot", archivedir, aufs_rw_dir, "/home", "/tmp/"]: + d = os.path.realpath(d) + fs_id = make_fs_id(d) + if os.path.exists(d): + st = os.statvfs(d) + free = st.f_bavail * st.f_frsize + else: + logging.warning("directory '%s' does not exists" % d) + free = 0 + if fs_id in mnt_map: + logging.debug("Dir %s mounted on %s" % + (d, mnt_map[fs_id])) + fs_free[d] = fs_free[mnt_map[fs_id]] + else: + logging.debug("Free space on %s: %s" % + (d, free)) + mnt_map[fs_id] = d + fs_free[d] = FreeSpace(free) + del mnt_map + logging.debug("fs_free contains: '%s'" % fs_free) + + # now calculate the space that is required on /boot + # we do this by checking how many linux-image-$ver packages + # are installed or going to be installed + kernel_count = 0 + for pkg in self: + # we match against everything that looks like a kernel + # and add space check to filter out metapackages + if re.match("^linux-(image|image-debug)-[0-9.]*-.*", pkg.name): + # upgrade because early in the release cycle the major version + # may be the same or they might be -lts- kernels + if pkg.marked_install or pkg.marked_upgrade: + logging.debug("%s (new-install) added with %s to boot space" % (pkg.name, KERNEL_SIZE)) + kernel_count += 1 + # space calculated per LP: #1646222 + space_in_boot = (kernel_count * KERNEL_SIZE + + (kernel_count + 1) * INITRD_SIZE) + + # we check for various sizes: + # archivedir is where we download the debs + # /usr is assumed to get *all* of the install space (incorrect, + # but as good as we can do currently + safety buffer + # / has a small safety buffer as well + required_for_aufs = 0.0 + if (hasattr(self, "config") and + self.config.getWithDefault("Aufs", "Enabled", False)): + logging.debug("taking aufs overlay into space calculation") + aufs_rw_dir = self.config.get("Aufs", "RWDir") + # if we use the aufs rw overlay all the space is consumed + # the overlay dir + for pkg in self: + if pkg.marked_upgrade or pkg.marked_install: + required_for_aufs += pkg.candidate.installed_size + + # add old size of the package if we use snapshots + required_for_snapshots = 0.0 + if snapshots_in_use: + for pkg in self: + if (pkg.is_installed and + (pkg.marked_upgrade or pkg.marked_delete)): + required_for_snapshots += pkg.installed.installed_size + logging.debug("additional space for the snapshots: %s" % required_for_snapshots) + + # sum up space requirements + for (dir, size) in [(archivedir, self.required_download), + ("/usr", self.additional_required_space), + # this is only >0 for the deb-to-snap quirks + ("/var", self.additional_required_space_for_snaps), + # plus 50M safety buffer in /usr + ("/usr", 50*1024*1024), + ("/boot", space_in_boot), + ("/tmp", 5*1024*1024), # /tmp for dkms LP: #427035 + ("/", 10*1024*1024), # small safety buffer / + (aufs_rw_dir, required_for_aufs), + # if snapshots are in use + ("/usr", required_for_snapshots), + ]: + # we are ensuring we have more than enough free space not less + if size < 0: + continue + dir = os.path.realpath(dir) + logging.debug("dir '%s' needs '%s' of '%s' (%f)" % (dir, size, fs_free[dir], fs_free[dir].free)) + fs_free[dir].free -= size + fs_free[dir].need += size + + # check for space required violations + required_list = {} + for dir in fs_free: + if fs_free[dir].free < 0: + # ensure unicode here (LP: #1172740) + free_at_least = apt_pkg.size_to_str(float(abs(fs_free[dir].free)+1)) + if isinstance(free_at_least, bytes): + free_at_least = free_at_least.decode( + locale.getpreferredencoding()) + free_needed = apt_pkg.size_to_str(fs_free[dir].need) + if isinstance(free_needed, bytes): + free_needed = free_needed.decode( + locale.getpreferredencoding()) + # make_fs_id ensures we only get stuff on the same + # mountpoint, so we report the requirements only once + # per mountpoint + required_list[make_fs_id(dir)] = FreeSpaceRequired(free_needed, make_fs_id(dir), free_at_least) + # raise exception if free space check fails + if len(required_list) > 0: + logging.error("Not enough free space: %s" % [str(i) for i in required_list]) + raise NotEnoughFreeSpaceError(list(required_list.values())) + return True + + +if __name__ == "__main__": + import sys + # from .DistUpgradeConfigParser import DistUpgradeConfig + # from .DistUpgradeView import DistUpgradeView + # print("foo") + # c = MyCache(DistUpgradeConfig("."), DistUpgradeView(), None) + # #c.checkForNvidia() + # #print(c._identifyObsoleteKernels()) + # print(c.checkFreeSpace()) + sys.exit() + + # c.clear() + # c.create_snapshot() + # c.installedTasks + # c.installTasks(["ubuntu-desktop"]) + # print(c.get_changes()) + # c.restore_snapshot() diff --git a/backend/SystemUpdater/Core/DpkgInstallProgress.py b/backend/SystemUpdater/Core/DpkgInstallProgress.py new file mode 100755 index 0000000..67c4b83 --- /dev/null +++ b/backend/SystemUpdater/Core/DpkgInstallProgress.py @@ -0,0 +1,124 @@ +import os +from gettext import gettext as _ +import apt +import logging +import fcntl +import apt_pkg + + +class LogInstallProgress(apt.progress.base.InstallProgress): + """ Install progress that writes to self.progress_log + (/var/run/unattended-upgrades.progress by default) + """ + def __init__(self,file): + # type: (str) -> None + apt.progress.base.InstallProgress.__init__(self) + self.output_logfd = None # type: int + self.filename=file + self.error_pkg="" + self.errormsg="" + # raise Exception("for test!!!") + + def error(self,pkg, errormsg): + logging.error(("Install mode - dpkg, Install error: %s"), errormsg) + self.error_pkg=self.filename + self.errormsg = errormsg + + def status_change(self, pkg, percent, status): + # type: (str, float, str) -> None + logging.info(("pkg:%s,percent:%s,status:%s"),pkg,percent,status) + with open(self.progress_log, "w") as f: + f.write(("%s")%percent) + f.write(_("当前进度: %s ,正在安装:%s,当前状态:%s") % (percent, pkg,status)) + f.write(_("Progress: %s %% (%s)") % (percent, pkg)) + + def _fixup_fds(self): + # () -> None + required_fds = [0, 1, 2, # stdin, stdout, stderr + self.writefd, + self.write_stream.fileno(), + self.statusfd, + self.status_stream.fileno() + ] + # ensure that our required fds close on exec + for fd in required_fds[3:]: + old_flags = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC) + # close all fds + try: + # PEP-446 implemented in Python 3.4 made all descriptors + # CLOEXEC, but we need to be able to pass writefd to dpkg + # when we spawn it + os.set_inheritable(self.writefd, True) + except AttributeError: # if we don't have os.set_inheritable() + pass + proc_fd = "/proc/self/fd" + if os.path.exists(proc_fd): + error_count = 0 + for fdname in os.listdir(proc_fd): + try: + fd = int(fdname) + except Exception: + print("ERROR: can not get fd for %s" % fdname) + if fd in required_fds: + continue + try: + os.close(fd) + # print("closed: ", fd) + except OSError as e: + # there will be one fd that can not be closed + # as its the fd from pythons internal diropen() + # so its ok to ignore one close error + error_count += 1 + if error_count > 1: + print("ERROR: os.close(%s): %s" % (fd, e)) + + def _redirect_stdin(self): + # type: () -> None + REDIRECT_INPUT = os.devnull + fd = os.open(REDIRECT_INPUT, os.O_RDWR) + os.dup2(fd, 0) + + def _redirect_output(self): + # type: () -> None + # do not create log in dry-run mode, just output to stdout/stderr + if not apt_pkg.config.find_b("Debug::pkgDPkgPM", False): + logfd = self._get_logfile_dpkg_fd() + os.dup2(logfd, 1) + os.dup2(logfd, 2) + + def _get_logfile_dpkg_fd(self): + # type: () -> int + logfd = os.open( + "/var/log/kylin-system-updater/kylin-system-updater.log.1", os.O_RDWR | os.O_APPEND | os.O_CREAT, 0o640) + try: + import grp + adm_gid = grp.getgrnam("adm").gr_gid + os.fchown(logfd, 0, adm_gid) + except (KeyError, OSError): + pass + return logfd + + def update_interface(self): + # type: () -> None + # call super class first + apt.progress.base.InstallProgress.update_interface(self) + + def _log_in_dpkg_log(self, msg): + # type: (str) -> None + logfd = self._get_logfile_dpkg_fd() + os.write(logfd, msg.encode("utf-8")) + os.close(logfd) + + def finish_update(self): + # if error_status == 1: + # os._exit(1) + pass + + def fork(self): + pid = os.fork() + if pid == 0: + self._fixup_fds() + self._redirect_stdin() + self._redirect_output() + return pid \ No newline at end of file diff --git a/backend/SystemUpdater/Core/LogManager.py b/backend/SystemUpdater/Core/LogManager.py new file mode 100644 index 0000000..8fd668d --- /dev/null +++ b/backend/SystemUpdater/Core/LogManager.py @@ -0,0 +1,69 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +#参考文档: https://cuiqingcai.com/6080.html + +import os + +path = '/var/log/kylin-system-updater/' + +numlist = [] + +def get_FileSize(filePath): + fsize = os.path.getsize(filePath) + fsize = fsize / float(1024 * 1024) + return round(fsize, 2) + +#日志回滚 +def get_logfile(): + if not os.path.exists(path): + os.makedirs(path) + #优先获取当前未写满100M的日志编号文件 + for i in os.listdir(path): + if "kylin-system-updater.log." in os.path.basename(path + i): + numlist.append((path + i).split(".")[2]) + if get_FileSize(path + i) < 100: + return path + i + #获取1-5未使用的最小数字的标号作为日志文件 + for i in range(1, 6): + if str(i) not in numlist: + return(os.path.join(path, ("kylin-system-updater.log.%s") % i)) + try: + #编号1-5日志文件均写满时,删除第一个,往前移动日志编号,获取最后一个编号作为日志文件 + if len(numlist) != 0: + os.remove(os.path.join(path, "kylin-system-updater.log.1")) + for i in range(2, 6): + os.rename(os.path.join(path, ("kylin-system-updater.log.%s") % i), + os.path.join(path, ("kylin-system-updater.log.%s") % (i - 1))) + return os.path.join(path, "kylin-system-updater.log.5") + #默认情况下未生成任何日志时使用编号1的日志文件 + else: + return os.path.join(path, "kylin-system-updater.log.1") + except: + return os.path.join(path, "kylin-system-updater.log.1") + +def upgrade_strategies_logfile(): + if not os.path.exists(path): + os.makedirs(path) + #优先获取当前未写满100M的日志编号文件 + for i in os.listdir(path): + if "upgrade-strategies-daemon.log." in os.path.basename(path + i): + numlist.append((path + i).split(".")[2]) + if get_FileSize(path + i) < 10: + return path + i + #获取1-5未使用的最小数字的标号作为日志文件 + for i in range(1, 6): + if str(i) not in numlist: + return(os.path.join(path, ("upgrade-strategies-daemon.log.%s") % i)) + try: + #编号1-5日志文件均写满时,删除第一个,往前移动日志编号,获取最后一个编号作为日志文件 + if len(numlist) != 0: + os.remove(os.path.join(path, "upgrade-strategies-daemon.log.1")) + for i in range(2, 6): + os.rename(os.path.join(path, ("upgrade-strategies-daemon.log.%s") % i), + os.path.join(path, ("upgrade-strategies-daemon.log.%s") % (i - 1))) + return os.path.join(path, "upgrade-strategies-daemon.log.5") + #默认情况下未生成任何日志时使用编号1的日志文件 + else: + return os.path.join(path, "upgrade-strategies-daemon.log.1") + except: + return os.path.join(path, "upgrade-strategies-daemon.log.1") diff --git a/backend/SystemUpdater/Core/MyCache.py b/backend/SystemUpdater/Core/MyCache.py new file mode 100644 index 0000000..d6bbd4b --- /dev/null +++ b/backend/SystemUpdater/Core/MyCache.py @@ -0,0 +1,434 @@ +# MyCache.py +# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- +# +# Copyright (c) 2004-2008 Canonical +# +# Author: Michael Vogt +# +# 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 + +from __future__ import absolute_import, print_function + +import warnings +warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning) +import apt +import apt_pkg +import logging +import os +from urllib.error import HTTPError +from urllib.request import urlopen +from urllib.parse import urlsplit +from http.client import BadStatusLine +import socket +import re +import SystemUpdater.Core.DistUpgradeCache +from gettext import gettext as _ +try: + from launchpadlib.launchpad import Launchpad +except ImportError: + Launchpad = None + +CHANGELOGS_POOL = "https://changelogs.ubuntu.com/changelogs/pool/" +CHANGELOGS_URI = CHANGELOGS_POOL + "%s/%s/%s/%s_%s/%s" + + +class HttpsChangelogsUnsupportedError(Exception): + """ https changelogs with credentials are unsupported because of the + lack of certitifcation validation in urllib2 which allows MITM + attacks to steal the credentials + """ + pass + + +class MyCache(SystemUpdater.Core.DistUpgradeCache.MyCache): + + CHANGELOG_ORIGIN = "Ubuntu" + + def __init__(self, progress, rootdir=None): + apt.Cache.__init__(self, progress, rootdir) + # save for later + self.rootdir = rootdir + # raise if we have packages in reqreinst state + # and let the caller deal with that (runs partial upgrade) + assert len(self.req_reinstall_pkgs) == 0 + # check if the dpkg journal is ok (we need to do that here + # too because libapt will only do it when it tries to lock + # the packaging system) + if self._dpkgJournalDirty() == True: + logging.info("dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem.") + # init the regular cache + self._initDepCache() + self.all_changes = {} + self.all_news = {} + # on broken packages, try to fix via saveDistUpgrade() + # if self._depcache.broken_count > 0: + # self.saveDistUpgrade() + # assert (self._depcache.broken_count == 0 + # and self._depcache.del_count == 0) + self.launchpad = None + + def _dpkgJournalDirty(self): + """ + test if the dpkg journal is dirty + (similar to debSystem::CheckUpdates) + """ + d = os.path.dirname( + apt_pkg.config.find_file("Dir::State::status")) + "/updates" + for f in os.listdir(d): + if re.match("[0-9]+", f): + return True + return False + + def _initDepCache(self): + self._depcache.read_pinfile() + self._depcache.init() + + def clear(self): + self._initDepCache() + + @property + def required_download(self): + """ get the size of the packages that are required to download """ + pm = apt_pkg.PackageManager(self._depcache) + fetcher = apt_pkg.Acquire() + pm.get_archives(fetcher, self._list, self._records) + return fetcher.fetch_needed + + @property + def install_count(self): + return self._depcache.inst_count + + def keep_count(self): + return self._depcache.keep_count + + @property + def del_count(self): + return self._depcache.del_count + + def _check_dependencies(self, target, deps): + """Return True if any of the dependencies in deps match target.""" + # TODO: handle virtual packages + for dep_or in deps: + if not dep_or: + continue + match = True + for base_dep in dep_or: + if (base_dep.name != target.package.shortname + or not apt_pkg.check_dep( + target.version, base_dep.relation, base_dep.version)): + match = False + if match: + return True + return False + + def find_removal_justification(self, pkg): + target = pkg.installed + if not target: + return False + for cpkg in self: + candidate = cpkg.candidate + if candidate is not None: + if (self._check_dependencies( + target, candidate.get_dependencies("Conflicts")) + and self._check_dependencies( + target, candidate.get_dependencies("Replaces"))): + logging.info( + "%s Conflicts/Replaces %s; allowing removal" % ( + candidate.package.shortname, pkg.shortname)) + return True + return False + + def saveDistUpgrade(self): + """ this functions mimics a upgrade but will never remove anything """ + #upgrade(True) 为True时使用dist-upgrade进行升级 + self._depcache.upgrade(True) + wouldDelete = self._depcache.del_count + wouldDelete = 0 + if wouldDelete > 0: + deleted_pkgs = [pkg for pkg in self if pkg.marked_delete] + assert wouldDelete == len(deleted_pkgs) + for pkg in deleted_pkgs: + if self.find_removal_justification(pkg): + wouldDelete -= 1 + if wouldDelete > 0: + self.clear() + assert (self._depcache.broken_count == 0 + and self._depcache.del_count == 0) + # else: + # assert self._depcache.broken_count == 0 + self._depcache.upgrade() + return wouldDelete + + def _strip_epoch(self, verstr): + " strip of the epoch " + vers_no_epoch = verstr.split(":") + if len(vers_no_epoch) > 1: + verstr = "".join(vers_no_epoch[1:]) + return verstr + + def _get_changelog_or_news(self, name, fname, strict_versioning=False, + changelogs_uri=None): + " helper that fetches the file in question " + # don't touch the gui in this function, it needs to be thread-safe + pkg = self[name] + + # get the src package name + srcpkg = pkg.candidate.source_name + + # assume "main" section + src_section = "main" + # use the section of the candidate as a starting point + section = pkg._pcache._depcache.get_candidate_ver(pkg._pkg).section + + # get the source version + srcver_epoch = pkg.candidate.source_version + srcver = self._strip_epoch(srcver_epoch) + + split_section = section.split("/") + if len(split_section) > 1: + src_section = split_section[0] + + # lib is handled special + prefix = srcpkg[0] + if srcpkg.startswith("lib"): + prefix = "lib" + srcpkg[3] + + # the changelogs_uri argument overrides the default changelogs_uri, + # this is useful for e.g. PPAs where we construct the changelogs + # path differently + if changelogs_uri: + uri = changelogs_uri + else: + uri = CHANGELOGS_URI % (src_section, prefix, srcpkg, srcpkg, + srcver, fname) + + # https uris are not supported when they contain a username/password + # because the urllib2 https implementation will not check certificates + # and so its possible to do a man-in-the-middle attack to steal the + # credentials + res = urlsplit(uri) + if res.scheme == "https" and res.username: + raise HttpsChangelogsUnsupportedError( + "https locations with username/password are not" + "supported to fetch changelogs") + + # print("Trying: %s " % uri) + changelog = urlopen(uri) + #print(changelog.read()) + # do only get the lines that are new + alllines = "" + regexp = "^%s \\((.*)\\)(.*)$" % (re.escape(srcpkg)) + + while True: + line = changelog.readline().decode("UTF-8", "replace") + if line == "": + break + match = re.match(regexp, line) + if match: + # strip epoch from installed version + # and from changelog too + installed = getattr(pkg.installed, "version", None) + if installed and ":" in installed: + installed = installed.split(":", 1)[1] + changelogver = match.group(1) + if changelogver and ":" in changelogver: + changelogver = changelogver.split(":", 1)[1] + # we test for "==" here for changelogs + # to ensure that the version + # is actually really in the changelog - if not + # just display it all, this catches cases like: + # gcc-defaults with "binver=4.3.1" and srcver=1.76 + # + # for NEWS.Debian we do require the changelogver > installed + if strict_versioning: + if (installed + and apt_pkg.version_compare(changelogver, + installed) < 0): + break + else: + if (installed + and apt_pkg.version_compare(changelogver, + installed) == 0): + break + alllines = alllines + line + return alllines + + def _extract_ppa_changelog_uri(self, name): + """Return the changelog URI from the Launchpad API + + Return None in case of an error. + """ + if not Launchpad: + logging.warning("Launchpadlib not available, cannot retrieve PPA " + "changelog") + return None + + cdt = self[name].candidate + for uri in cdt.uris: + if urlsplit(uri).hostname != 'ppa.launchpad.net': + continue + match = re.search('http.*/(.*)/(.*)/ubuntu/.*', uri) + if match is not None: + user, ppa = match.group(1), match.group(2) + break + else: + logging.error("Unable to find a valid PPA candidate URL.") + return + + # Login on launchpad if we are not already + if self.launchpad is None: + self.launchpad = Launchpad.login_anonymously('update-manager', + 'production', + version='devel') + + archive = self.launchpad.archives.getByReference( + reference='~%s/ubuntu/%s' % (user, ppa) + ) + if archive is None: + logging.error("Unable to retrieve the archive from the Launchpad " + "API.") + return + + spphs = archive.getPublishedSources(source_name=cdt.source_name, + exact_match=True, + version=cdt.source_version) + if not spphs: + logging.error("No published sources were retrieved from the " + "Launchpad API.") + return + + return spphs[0].changelogUrl() + + def _guess_third_party_changelogs_uri_by_source(self, name): + pkg = self[name] + deb_uri = pkg.candidate.uri + if deb_uri is None: + return None + srcrec = pkg.candidate.record.get("Source") + if not srcrec: + return None + # srcpkg can be "apt" or "gcc-default (1.0)" + srcpkg = srcrec.split("(")[0].strip() + if "(" in srcrec: + srcver = srcrec.split("(")[1].rstrip(")") + else: + srcver = pkg.candidate.source_version + base_uri = deb_uri.rpartition("/")[0] + return base_uri + "/%s_%s.changelog" % (srcpkg, srcver) + + def _guess_third_party_changelogs_uri_by_binary(self, name): + """ guess changelogs uri based on ArchiveURI by replacing .deb + with .changelog + """ + # there is always a pkg and a pkg.candidate, no need to add + # check here + pkg = self[name] + deb_uri = pkg.candidate.uri + if deb_uri: + return "%s.changelog" % deb_uri.rsplit(".", 1)[0] + return None + + def get_news_and_changelog(self, name, lock): + self.get_news(name) + self.get_changelog(name) + try: + lock.release() + except Exception: + pass + + def get_news(self, name): + " get the NEWS.Debian file from the changelogs location " + try: + news = self._get_changelog_or_news(name, "NEWS.Debian", True) + except Exception: + return + if news: + self.all_news[name] = news + + def _fetch_changelog_for_third_party_package(self, name, origins): + # Special case for PPAs + changelogs_uri_ppa = None + for origin in origins: + if origin.origin.startswith('LP-PPA-'): + try: + changelogs_uri_ppa = self._extract_ppa_changelog_uri(name) + break + except Exception: + logging.exception("Unable to connect to the Launchpad " + "API.") + # Try non official changelog location + changelogs_uri_binary = \ + self._guess_third_party_changelogs_uri_by_binary(name) + changelogs_uri_source = \ + self._guess_third_party_changelogs_uri_by_source(name) + error_message = "" + for changelogs_uri in [changelogs_uri_ppa, + changelogs_uri_binary, + changelogs_uri_source]: + if changelogs_uri: + try: + changelog = self._get_changelog_or_news( + name, "changelog", False, changelogs_uri) + self.all_changes[name] += changelog + except (HTTPError, HttpsChangelogsUnsupportedError): + # no changelogs_uri or 404 + error_message = _( + "This update does not come from a " + "source that supports changelogs.") + except (IOError, BadStatusLine, socket.error): + # network errors and others + logging.exception("error on changelog fetching") + error_message = _( + "Failed to download the list of changes. \n" + "Please check your Internet connection.") + self.all_changes[name] += error_message + + def get_changelog(self, name): + " get the changelog file from the changelog location " + origins = self[name].candidate.origins + self.all_changes[name] = _("Changes for %s versions:\n" + "Installed version: %s\n" + "Available version: %s\n\n") % \ + (name, getattr(self[name].installed, "version", None), + self[name].candidate.version) + if self.CHANGELOG_ORIGIN not in [o.origin for o in origins]: + self._fetch_changelog_for_third_party_package(name, origins) + return + # fixup epoch handling version + srcpkg = self[name].candidate.source_name + srcver_epoch = self[name].candidate.source_version.replace(':', '%3A') + try: + changelog = self._get_changelog_or_news(name, "changelog") + if len(changelog) == 0: + changelog = _("The changelog does not contain any relevant " + "changes.\n\n" + "Please use http://launchpad.net/ubuntu/+source/" + "%s/%s/+changelog\n" + "until the changes become available or try " + "again later.") % (srcpkg, srcver_epoch) + except HTTPError: + changelog = _("The list of changes is not available yet.\n\n" + "Please use http://launchpad.net/ubuntu/+source/" + "%s/%s/+changelog\n" + "until the changes become available or try again " + "later.") % (srcpkg, srcver_epoch) + except (IOError, BadStatusLine, socket.error) as e: + print("caught exception: ", e) + changelog = _("Failed to download the list " + "of changes. \nPlease " + "check your Internet " + "connection.") + self.all_changes[name] += changelog diff --git a/backend/SystemUpdater/Core/OriginFilter.py b/backend/SystemUpdater/Core/OriginFilter.py new file mode 100644 index 0000000..0d5d8ea --- /dev/null +++ b/backend/SystemUpdater/Core/OriginFilter.py @@ -0,0 +1,580 @@ +#!/usr/bin/python3 +import apt +import apt_pkg +import fnmatch +import logging +import logging.handlers +import re +import os +import sys +import string +import subprocess +import json +try: + from typing import List + from typing import Union +except ImportError: + pass + +from gettext import gettext as _ + +SYSTEM_UPDATER_CORE_LIB_PATH="/usr/share/kylin-system-updater/" +sys.path.append(SYSTEM_UPDATER_CORE_LIB_PATH) +from SystemUpdater.Core.utils import get_config_patch + +ImportantListPath="/var/lib/kylin-software-properties/template/important.list" +SOURCESLIST = "/etc/apt/sources.list" + +# no py3 lsb_release in debian :/ +DISTRO_CODENAME = subprocess.check_output( + ["lsb_release", "-c", "-s"], universal_newlines=True).strip() # type: str +DISTRO_DESC = subprocess.check_output( + ["lsb_release", "-d", "-s"], universal_newlines=True).strip() # type: str +DISTRO_ID = subprocess.check_output( + ["lsb_release", "-i", "-s"], universal_newlines=True).strip() # type: str + +ARCHITECTUREMAP = ['arm64','amd64','armhf','i386','loongarch64','mips64el','sw64'] + +RELEASEOFFSET = 1 +ORIGINOFFSET = 2 +HTTPTYPE = "HTTP" +FTPTYPE = "FTP" + +class UpdateListFilterCache(apt.Cache): + + def __init__(self, window_main): + self.window_main = window_main + # whitelist + self.upgradeList = [] + # 必须升级的包 + self.installList = [] + + self.config_path = get_config_patch() + + # 获取源属性 + self.origin_property = OriginProperty() + self.origin_property.get_allowed_sources() + self.origin_property.get_allowed_origin() + + self.allowed_origins = get_allowed_origins(self.origin_property.allow_origin) + + self.allowed_origins = deleteDuplicatedElementFromList(self.allowed_origins) + logging.info(_("Allowed origins: %s"), + self.allowed_origins) + + # self.blacklist = apt_pkg.config.value_list( + # "Kylin-system-updater::Package-Blacklist") + # self.blacklist = deleteDuplicatedElementFromList(self.blacklist) + + # self.whitelist = apt_pkg.config.value_list( + # "Kylin-system-updater::Package-Whitelist") + # self.whitelist = deleteDuplicatedElementFromList(self.whitelist) + + # self.strict_whitelist = apt_pkg.config.find_b( + # "Kylin-system-updater::Package-Whitelist-Strict", False) + + def checkInCache(self): + logging.info("start Check in cache") + tmplist = [] + cache = apt.Cache() + for i in self.upgradeList: + try: + cache[i] + tmplist.append(i) + except Exception as e: + pass + self.upgradeList = tmplist + + def initLocalPackagesList(self): + jsonfiles = [] + tmplist = [] + + # 获取importantlist 本次更新推送 + with open(ImportantListPath, 'r') as f: + text = f.read() + importantList = text.split() + logging.info("importantList: %s",importantList) + f.close() + + if not importantList: + logging.error("importantList is empty") + exit(-1) + + # 获取/usr/share/kylin-update-desktop-config/data/下所有json文件 + for root,dirs,files in os.walk(self.config_path): + pass + for i in files: + if ".json" in i: + jsonfiles.append(i.split('.')[0]) + + # 找到importantlist中对应的json文件 + for i in importantList: + if i not in jsonfiles: + # 说明这个是单独的包,不在分组中 + # 加入更新列表 + if i not in self.upgradeList: + self.upgradeList.append(i) + else: + # 在分组中 + # 获取每个对应json文件中的upgrade_list + if i in jsonfiles: + filepath = os.path.join(self.config_path, i) + filepath = filepath+".json" + with open(filepath, 'r') as f: + pkgdict = f.read() + jsonfile = json.loads(pkgdict) + tmplist = jsonfile['install_list'] + for j in tmplist: + if j not in self.upgradeList: + self.upgradeList.append(j) + f.close() + + # 更改:传入包列表,经过源过滤,返回的pkg中进行版本调整 + def check_in_allowed_origin(self, pkg_lists, _is_adjust): + new_upgrade_pkgs = [] + adjust_candidate_pkgs = [] + for pkg in pkg_lists: + try: + new_ver = ver_in_allowed_origin(pkg, self.allowed_origins) + if _is_adjust and len(new_ver) == 0: + logging.warning("< %s > did not find a suitable version..." % pkg.name) + continue + if len(new_ver) == 0: + continue + if not pkg.installed: # 判断安装列表 + if pkg.candidate == new_ver[0] and pkg not in new_upgrade_pkgs: + new_upgrade_pkgs.append(pkg) + elif new_ver[0] != pkg.candidate and pkg not in new_upgrade_pkgs: + logging.info("adjusting candidate version: %s" % new_ver[0]) + if _is_adjust == True: + pkg.candidate = new_ver[0] + adjust_candidate_pkgs.append(pkg.name+"="+pkg.candidate.version) + new_upgrade_pkgs.append(pkg) + else: # 判断升级列表 + for nv in new_ver: + if nv > pkg.installed and nv != pkg.candidate: + logging.info("adjusting candidate version: %s" % nv) + if _is_adjust == True: + pkg.candidate = nv + adjust_candidate_pkgs.append(pkg.name+"="+pkg.candidate.version) + break + elif nv > pkg.installed and nv == pkg.candidate: + new_upgrade_pkgs.append(pkg) + break + elif _is_adjust == True: + logging.warning("< %s > did not find a suitable version..." % pkg.name) + except NoAllowedOriginError: + logging.error("Cannot found allowed version: %s", pkg.name) + continue + + return (new_upgrade_pkgs, adjust_candidate_pkgs) + + def is_pkgname_in_blacklist(self, pkgs): + blacklist_filter_pkgs = [] + for pkg in pkgs: + if pkg.name in self.blacklist: + pass + else : + blacklist_filter_pkgs.append(pkg) + + return blacklist_filter_pkgs + + def is_pkgname_in_whitelist(self, pkgs): + whitelist_filter_upgrade_pkgs = [] + for pkg in pkgs: + if pkg.name in self.upgradeList: + whitelist_filter_upgrade_pkgs.append(pkg) + else : + pkg.mark_keep() + return whitelist_filter_upgrade_pkgs + +class OriginProperty(): + + def __init__(self): + # 包含了本地所有源 http & ftp + self.local_sourcelist = {"http":[],"ftp":[]} + # 经过解析后的本地源,获取所有的分发属性 + self.local_origin = {"http":[],"ftp":[]} + # 允许的源列表 + self.allow_sources = [] + # 允许的源+属性 + self.allow_origin = {"http":[],"ftp":[]} + # 加载本地所有源 + self.init_local_origin() + # 进行属性解析 + self.analytic_properties(self.local_sourcelist) + + def init_local_origin(self): + http_origin = {} + ftp_orgin = {} + #apt policy + sh_retval = os.popen("apt-cache policy").read().split("\n") + # policy = [ rv for rv in sh_retval if "http" in rv or "ftp" in rv or "release" in rv or "origin" in rv] + for rv in sh_retval: + if "http" in rv: + http_origin['sources'] = rv + http_origin['release'] = sh_retval[sh_retval.index(rv) + RELEASEOFFSET] + http_origin['origin'] = sh_retval[sh_retval.index(rv) + ORIGINOFFSET] + self.local_sourcelist['http'].append(http_origin.copy()) + elif "ftp" in rv: + ftp_orgin['sources'] = rv + ftp_orgin['release'] = sh_retval[sh_retval.index(rv) + RELEASEOFFSET] + ftp_orgin['origin'] = sh_retval[sh_retval.index(rv) + ORIGINOFFSET] + self.local_sourcelist['ftp'].append(ftp_orgin.copy()) + + def merge_origin(self, source_type, source_origin): + is_append = True + if source_type == HTTPTYPE: + if self.local_origin['http']: + for lo in self.local_origin['http']: + if lo['origin_source'] == source_origin['origin_source'] and lo['dist'] == source_origin['dist']: + lo['component'] = list(set(lo['component']).union(set(source_origin['component']))) + is_append = False + if is_append: + self.local_origin['http'].append(source_origin.copy()) + else: + self.local_origin['http'].append(source_origin.copy()) + elif source_type == FTPTYPE: + if self.local_origin['ftp']: + for lo in self.local_origin['ftp']: + if lo['origin_source'] == source_origin['origin_source'] and lo['dist'] == source_origin['dist']: + lo['component'] = list(set(lo['component']).union(set(source_origin['component']))) + is_append = False + if is_append: + self.local_origin['ftp'].append(source_origin.copy()) + else: + self.local_origin['ftp'].append(source_origin.copy()) + + def analytic_properties(self, local_sourcelist): + http_origin = {"component":[],"release":{}} + ftp_orgin = {"component":[],"release":{}} + dist_list = [] + # 经过解析后的本地源,获取所有的分发属性 + for ls in local_sourcelist['http']: + for item in filter(not_empty, ls['sources'].split(' ')): + if item.isdigit(): + http_origin['policy_priority'] = item + elif "http" in item: + http_origin['origin_source'] = item + elif "/" in item: + dist_list = item.split("/") + dist_list.pop() + http_origin['dist'] = "/".join(dist_list) + http_origin['component'].append(item.split("/")[1]) + elif item not in ARCHITECTUREMAP and item != "Packages": + http_origin['component'].append(item) + release_list = ls['release'].split(',') + release_list = [ rl.strip() for rl in release_list ] + if "release" in release_list[0]: + release_list[0] = release_list[0].lstrip("release").strip() + for rl in release_list: + if "=" in rl: + self.generate_dict(http_origin['release'], rl) + for item in filter(not_empty, ls['origin'].split(' ')): + if "origin" not in ls['origin']: + break + elif "origin" != item: + http_origin['origin'] = item + self.merge_origin(HTTPTYPE, http_origin) + http_origin = {"component":[],"release":{}} + + for ls in local_sourcelist['ftp']: + for item in filter(not_empty, ls['sources'].split(' ')): + if item.isdigit(): + ftp_orgin['policy_priority'] = item + elif "ftp" in item: + ftp_orgin['origin_source'] = item + elif "/" in item: + ftp_orgin['dist'] = item.split("/")[0] + ftp_orgin['component'].append(item.split("/")[1]) + elif item not in ARCHITECTUREMAP and item != "Packages": + ftp_orgin['component'].append(item) + release_list = ls['release'].split(',') + if "release " in release_list[0]: + release_list[0] = release_list[0].lstrip("release ") + for rl in release_list: + if "=" in rl: + self.generate_dict(ftp_orgin['release'], rl) + for item in filter(not_empty, ls['origin'].split(' ')): + if "origin" not in ls['origin']: + break + elif "origin" != item: + ftp_orgin['origin'] = item + self.merge_origin(FTPTYPE, ftp_orgin) + ftp_orgin = {"component":[],"release":{}} + + def generate_dict(self, dict, item): + item = item.strip() + if item == "": + logging.warning("empty match string matches nothing") + return False + (what, value) = [ s for s in item.split("=")] + if what in ('o', 'origin'): + dict['origin'] = value + elif what in ("l", "label"): + dict['label'] = value + elif what in ("a", "suite", "archive"): + dict['archive'] = value + elif what in ("c", "component"): + dict['component'] = value + elif what in ("site",): + dict['site'] = value + elif what in ("n", "codename",): + dict['codename'] = value + else: + dict[what] = value + # raise UnknownMatcherError( + # "Unknown whitelist entry for matcher %s (value %s)" % ( + # what, value)) + + def get_allowed_sources(self): + # 源地址,在本地源列表中查找. 源服务器下发source.list为允许的源, 本模块屏蔽了sources.list.d下的源 + # 获取允许的源 + try: + old_sources_list = apt_pkg.config.find("Dir::Etc::sourcelist") + old_sources_list_d = apt_pkg.config.find("Dir::Etc::sourceparts") + old_cleanup = apt_pkg.config.find("APT::List-Cleanup") + apt_pkg.config.set("Dir::Etc::sourcelist", + os.path.abspath(SOURCESLIST)) + apt_pkg.config.set("Dir::Etc::sourceparts", "xxx") + apt_pkg.config.set("APT::List-Cleanup", "0") + slist = apt_pkg.SourceList() + slist.read_main_list() + self.allow_sources = slist.list + except Exception as e: + logging.error(str(e)) + finally: + apt_pkg.config.set("Dir::Etc::sourcelist", + old_sources_list) + apt_pkg.config.set("Dir::Etc::sourceparts", + old_sources_list_d) + apt_pkg.config.set("APT::List-Cleanup", + old_cleanup) + + def get_allowed_origin(self): + # 获取允许的源 + # 生成源与属性 + self.local_origin + self.allow_sources + self.allow_origin + try: + for item in self.allow_sources: + for lo in self.local_origin['http']: + if item.uri.strip('/') == lo['origin_source'].strip('/') and item.dist == lo['dist']: + self.allow_origin['http'].append(lo) + for lo in self.local_origin['ftp']: + if item.uri.strip('/') == lo['origin_source'].strip('/') and item.dist == lo['dist']: + self.allow_origin['ftp'].append(lo) + except Exception as e: + logging.error(str(e)) + +class UnattendUpgradeFilter(): + def __init__(self) -> None: + pass + + def GetAllowOrigins(self): + # 获取源属性 + self.origin_property = OriginProperty() + self.origin_property.get_allowed_sources() + self.origin_property.get_allowed_origin() + + self.allowed_origins = get_allowed_origins(self.origin_property.allow_origin) + + self.allowed_origins = deleteDuplicatedElementFromList(self.allowed_origins) + logging.info(_("Allowed origins: %s"), + self.allowed_origins) + return self.allowed_origins + + +def ver_in_allowed_origin(pkg, allow_origin): + # type: (apt.Package, List[str]) -> apt.package.Version + allown_versions = [] + versions = _get_priority_order(pkg) + # 获取每个优先级别中 允许源的最高版本 + allown_versions = _get_allowed_list(versions, allow_origin) + + return allown_versions + +def _get_priority_order(pkg): + versions = [] + for ver in pkg.versions: + if versions: + for v in versions: + if v.policy_priority >= ver.policy_priority and v == versions[-1]: + break + elif v.policy_priority >= ver.policy_priority and v != versions[-1]: + continue + else: + index = versions.index(v) + versions.insert(index,ver) + break + if v == versions[-1] and versions[-1].policy_priority >= ver.policy_priority: + versions.append(ver) + else: + versions.append(ver) + return versions + +def _get_allowed_list(versions, allow_origin): + current_priority = -100 + allown_versions = [] + for ver in versions: + if current_priority != ver.policy_priority: + if is_in_allowed_origin(ver, allow_origin): + allown_versions.append(ver) + current_priority = ver.policy_priority + else: + continue + return allown_versions + +def get_allowed_origins(allow_origin): + """ return a list of allowed origins + """ + allowed_origins = [] + origin = '' + archive = '' + uri = '' + label = '' + for ao in (allow_origin['http']+allow_origin['ftp']): + if 'origin' in ao['release']: + origin = 'o='+ao['release']['origin'] + else: + origin = 'o=' + if 'archive' in ao['release']: + archive = 'a='+ao['release']['archive'] + else: + archive = 'a=' + if 'label' in ao['release']: + label = 'l='+ao['release']['label'] + else: + label = 'l=' + if 'origin_source' in ao: + uri = 'uri='+ao['origin_source'] + else: + uri = 'uri=' + allowed_origins.append(origin+","+archive+","+label+","+uri) + return allowed_origins + +def get_allowed_origins_legacy(): + # type: () -> List[str] + """ legacy support for old Allowed-Origins var """ + allowed_origins = [] # type: List[str] + key = "Kylin-system-updater::Allowed-Origins" + try: + for s in apt_pkg.config.value_list(key): + # if there is a ":" use that as seperator, else use spaces + if re.findall(r'(? str + """ substitude known mappings and return a new string + + Currently supported ${distro-release} + """ + mapping = {"distro_codename": get_distro_codename(), + "distro_id": get_distro_id()} + return string.Template(line).substitute(mapping) + + +def get_distro_codename(): + # type: () -> str + return DISTRO_CODENAME + + +def get_distro_id(): + # type: () -> str + return DISTRO_ID + +def is_in_allowed_origin(ver, allowed_origins): + # type: (apt.package.Version, List[str]) -> bool + if not ver: + return False + for origin in ver.origins: + if is_allowed_origin(origin, allowed_origins): + return True + return False + +def is_allowed_origin(origin, allowed_origins): + # type: (Union[apt.package.Origin, apt_pkg.PackageFile], List[str]) -> bool + for allowed in allowed_origins: + if match_whitelist_string(allowed, origin): + return True + return False + +def match_whitelist_string(whitelist, origin): + # type: (str, Union[apt.package.Origin, apt_pkg.PackageFile]) -> bool + """ + take a whitelist string in the form "origin=Debian,label=Debian-Security" + and match against the given python-apt origin. A empty whitelist string + never matches anything. + """ + whitelist = whitelist.strip() + if whitelist == "": + logging.warning("empty match string matches nothing") + return False + res = True + # make "\," the html quote equivalent + whitelist = whitelist.replace("\\,", "%2C") + for token in whitelist.split(","): + # strip and unquote the "," back + (what, value) = [s.strip().replace("%2C", ",") + for s in token.split("=")] + # logging.debug("matching %s=%s against %s" % ( + # what, value, origin)) + # support substitution here as well + value = substitute(value) + # first char is apt-cache policy output, send is the name + # in the Release file + if what in ("o", "origin"): + match = fnmatch.fnmatch(origin.origin, value) + elif what in ("l", "label"): + match = fnmatch.fnmatch(origin.label, value) + elif what in ("a", "suite", "archive"): + match = fnmatch.fnmatch(origin.archive, value) + elif what in ("c", "component"): + match = fnmatch.fnmatch(origin.component, value) + elif what in ("site",): + match = fnmatch.fnmatch(origin.site, value) + elif what in ("n", "codename",): + match = fnmatch.fnmatch(origin.codename, value) + elif what in ("uri",): + match = True + else: + raise UnknownMatcherError( + "Unknown whitelist entry for matcher %s (token %s)" % ( + what, token)) + # update res + res = res and match + # logging.debug("matching %s=%s against %s" % ( + # what, value, origin)) + return res + +def deleteDuplicatedElementFromList(list): + resultList = [] + for item in list: + if not item in resultList: + resultList.append(item) + return resultList + +def not_empty(s): + return s and s.strip() + +class UnknownMatcherError(ValueError): + pass + +class NoAllowedOriginError(ValueError): + pass diff --git a/backend/SystemUpdater/Core/UpdateList.py b/backend/SystemUpdater/Core/UpdateList.py new file mode 100644 index 0000000..cf5c987 --- /dev/null +++ b/backend/SystemUpdater/Core/UpdateList.py @@ -0,0 +1,633 @@ +# UpdateList.py + +from gettext import gettext as _ +import logging +import os +import json +import yaml +import shutil +from gi.repository import Gio +from .OriginFilter import UpdateListFilterCache +from .errors import * +from .enums import * +from SystemUpdater.Core.utils import get_config_patch + +class LocalUpgradeDataList: + """ + Represent the (potentially partial) results of an unattended-upgrades + run + """ + def __init__(self, + groups_pkgs={}, + upgrade_groups=[], + single_pkgs=[], + adjust_pkgs=[], + ): + #可升级的组列表 + self.upgrade_groups = upgrade_groups + #组列表中包含的包 + self.groups_pkgs = groups_pkgs + #推送的可升级的单包 + self.single_pkgs = single_pkgs + #调整版本列表 源过滤 + self.adjust_pkgs = adjust_pkgs + #加版本号的升级包 + self.versoin_pkgs = {'single_upgrade':{}, 'groups_upgrade':{}} + +class UpdateList(): + OUTPUT_CONFIG_PATH = '/var/lib/kylin-system-updater/json/' + IMPORTANT_LIST_PATH = '/var/lib/kylin-software-properties/template/important.list' + + def __init__(self,parent): + self.parent = parent + + #所有的组升级安装列表 + self.upgrade_meta = LocalUpgradeDataList({},[],[],[]) + + if 'XDG_CURRENT_DESKTOP' in os.environ: + self.current_desktop = os.environ.get('XDG_CURRENT_DESKTOP') + else: + self.current_desktop = '' + + if 'XDG_DATA_DIRS' in os.environ and os.environ['XDG_DATA_DIRS']: + data_dirs = os.environ['XDG_DATA_DIRS'] + else: + data_dirs = '/usr/local/share/:/usr/share/' + + self.application_dirs = [os.path.join(base, 'applications') + for base in data_dirs.split(':')] + + self.config_path = get_config_patch() + + if self.parent.install_mode.check_filter() == True: + #开启原过滤 + self.fu = UpdateListFilterCache(self.parent) + else: + self.fu = None + logging.info("Close to Allowed origin fiter...") + + + #清空上次输出的分组JSON文件 + def _empty_output_dir(self): + #清空 升级列表 + if not os.path.exists(self.OUTPUT_CONFIG_PATH): + os.makedirs(self.OUTPUT_CONFIG_PATH) + logging.info('making the ConfigPath(%s) is complete...',self.OUTPUT_CONFIG_PATH) + else: + shutil.rmtree(self.OUTPUT_CONFIG_PATH) + os.makedirs(self.OUTPUT_CONFIG_PATH) + logging.info('Emptying the ConfigPath(%s) is complete...',self.OUTPUT_CONFIG_PATH) + + #读取推送列表,判断分组和单包推送,再进行源过滤 + def _make_important_list(self,cache,pkgs_upgrade,important_list = []): + upgradeable_pkgs = [] + tmp = [] + upgradeable_groups = [] + + logging.info("The Server Push List: %a",important_list) + + for pkg_name in important_list: + #检查是否在cache 没有在cache中属于组 + if pkg_name in cache: + pkg_obj = cache[pkg_name] + #在可升级的列表当中 此步骤为了排除已安装不需要升级的 + if pkg_obj.is_installed: + if pkg_name in pkgs_upgrade: + pkgs_upgrade.remove(pkg_name) + tmp.append(pkg_obj) + else: + tmp.append(pkg_obj) + else: + upgradeable_groups.append(pkg_name) + + if tmp != []: + install_list,upgrade_list,adjust_pkgs = self._make_fiter_origin(tmp,True) + self.upgrade_meta.adjust_pkgs.extend(adjust_pkgs) + upgradeable_pkgs = install_list + upgrade_list + + logging.info("Push Single Packages: %a, Push Groups:%a",upgradeable_pkgs,upgradeable_groups) + return upgradeable_groups,upgradeable_pkgs + + def _make_pkg_info_json(self,cache,pkgs_list): + total_download_size = 0 + total_installed_size = 0 + pkgs_info_json = {} + + for pkg_name in pkgs_list: + pkg = cache[pkg_name] + #当前版本 + cur_version = getattr(pkg.installed, "version", '') + new_version = getattr(pkg.candidate, "version", '') + + #获取下载大小 + download_size = getattr(pkg.candidate, "size", 0) + installed_size = getattr(pkg.candidate, "installed_size", 0) + + total_download_size = total_download_size + download_size + total_installed_size = total_installed_size + installed_size + + pkgs_info_json.update({pkg_name:{"cur_version":cur_version,"new_version":new_version,\ + "download_size":str(download_size),"install_size":str(installed_size)}}) + + pkgs_info_json.update({"total_download_size":str(total_download_size)}) + pkgs_info_json.update({"total_install_size":str(total_installed_size)}) + return pkgs_info_json + + #检查包是否在cache中 返回新得列表没 有安装的话才添加到列表 + def _check_pkg_in_cache(self,cache,pkgs_list): + new_pkgs_list = [] + for pkg_name in pkgs_list: + #检查是否在cache 以及 是否安装检查 + if pkg_name in cache and not cache[pkg_name].is_installed: + new_pkgs_list.append(pkg_name) + else: + pass + return new_pkgs_list + + def _make_group_output_json(self,data,data_yaml,upgrade_pkgs_json,install_pkgs_json): + groups_base_info = {} + output_json = {} + + #FIXME: 确定输出文件的文件名 以及放置位置 + output_config_name = self.OUTPUT_CONFIG_PATH + data['package'] + '.json' + + #4、添加一些基础信息 + groups_base_info.update({"package":data['package']}) + groups_base_info.update({"new_version":data['version']}) + groups_base_info.update({"name":data['name']}) + groups_base_info.update({"description":data['description']}) + groups_base_info.update({"icon":data['icon']}) + + #添加读yaml文件 + groups_base_info.update({"changelog":data_yaml['changelog']}) + + #5、添加升级的内容 + output_json.update(groups_base_info) + output_json.update({"upgrade_list":upgrade_pkgs_json}) + output_json.update({"install_list":install_pkgs_json}) + # output_json.update({"hold_list":hold_pkgs_list}) + # output_json.update({"remove_list":remove_pkgs_list}) + + #6 产生JSON文件 + with open(output_config_name, 'w', encoding='utf-8') as f: + json.dump(output_json, f, ensure_ascii=False, indent=4) + logging.info("Generate Jsonfile(%s) to complete... ",output_config_name) + + #进行源过滤,is_adjust 是否调整cache中的候选版本,单包推送会调整保持控制面板显示正确的版本 + def _make_fiter_origin(self,pkgs_list,adjust_versions): + install_pkgs = [] + upgrade_pkgs = [] + adjust_pkgs = [] + + #是否进行源过滤的选项 + if self.fu != None: + try: + after_pkgs_list,adjust_pkgs = self.fu.check_in_allowed_origin(pkgs_list,adjust_versions) + except Exception as e: + after_pkgs_list = pkgs_list + logging.error("Check Allowed origin is occur error:" + str(e)) + else: + after_pkgs_list = pkgs_list + adjust_pkgs = [] + + for pkg_obj in after_pkgs_list: + if pkg_obj.is_installed: + upgrade_pkgs.append(pkg_obj.name) + else: + install_pkgs.append(pkg_obj.name) + + return install_pkgs,upgrade_pkgs,adjust_pkgs + + #从本地中获取本次升级需要升级的包 部分升级和全部升级使用 全盘升级不适用 + def _make_pkgs_list(self,cache,groups_pkgs,groups_list,pkg_list): + pkgs_install = [] + pkgs_upgrade = [] + + #单包的升级方式 + for pkg in pkg_list: + if cache[pkg].is_installed: + pkgs_upgrade.append(pkg) + else: + pkgs_install.append(pkg) + + #遍历升级组列表 + for group_name in groups_list: + pkgs_install += groups_pkgs.get(group_name,[]).get('pkgs_install',[]) + pkgs_upgrade += groups_pkgs.get(group_name,[]).get('pkgs_upgrade',[]) + + return pkgs_install,pkgs_upgrade + + #输出白名单的配置 + def _make_autoupgrade_config(self,cache,upgrade_data,_adjust_pkgs): + pkgs_install,pkgs_upgrade = self._make_pkgs_list(cache,upgrade_data.groups_pkgs,upgrade_data.upgrade_groups,upgrade_data.single_pkgs) + split_adjust_pkgs = [i.split("=")[0] for i in _adjust_pkgs] + + output_config_name = self.OUTPUT_CONFIG_PATH + 'auto-upgrade-list.json' + output_json = {} + install_info = {} + for pkg in pkgs_install: + pkg_cache = cache[pkg] + pkgs_json = {} + pkgs_json.update({"cur_version":getattr(pkg_cache.installed, "version", '')}) + + if pkg in split_adjust_pkgs: + version_adjust = _adjust_pkgs[split_adjust_pkgs.index(pkg)].split("=")[1] + pkgs_json.update({"new_version":version_adjust}) + else: + pkgs_json.update({"new_version":getattr(pkg_cache.candidate, "version", '')}) + install_info.update({pkg:pkgs_json}) + + upgrade_json = {} + for pkg in pkgs_upgrade: + pkg_cache = cache[pkg] + pkgs_json = {} + pkgs_json.update({"cur_version":getattr(pkg_cache.installed, "version", '')}) + + if pkg in split_adjust_pkgs: + version_adjust = _adjust_pkgs[split_adjust_pkgs.index(pkg)].split("=")[1] + pkgs_json.update({"new_version":version_adjust}) + else: + pkgs_json.update({"new_version":getattr(pkg_cache.candidate, "version", '')}) + + upgrade_json.update({pkg:pkgs_json}) + + group_json = {} + for ug in self.upgrade_meta.groups_pkgs: + pkgs_json = {} + with open(self.config_path + str(ug) + ".yaml", "r") as stream: + try: + data_yaml = yaml.safe_load(stream) + pkgs_json.update({"cur_version":""}) + pkgs_json.update({"new_version":data_yaml["version"]}) + pkgs_json.update({"changelog":data_yaml["changelog"]}) + except yaml.YAMLError as exc: + logging.error(exc) + group_json.update({ug:pkgs_json}) + + single_json = {} + for us in self.upgrade_meta.single_pkgs: + pkg_cache = cache[us] + pkgs_json = {} + pkgs_json.update({"cur_version":getattr(pkg_cache.installed, "version", '')}) + + if pkg in split_adjust_pkgs: + version_adjust = _adjust_pkgs[split_adjust_pkgs.index(pkg)].split("=")[1] + pkgs_json.update({"new_version":version_adjust}) + else: + pkgs_json.update({"new_version":getattr(pkg_cache.candidate, "version", '')}) + pkgs_json.update({"changelog":""}) + single_json.update({us:pkgs_json}) + + output_json.update({"upgrade_list":upgrade_json}) + output_json.update({"install_list":install_info}) + output_json.update({"group_json":group_json}) + output_json.update({"single_json":single_json}) + + #产生JSON文件 + with open(output_config_name, 'w', encoding='utf-8') as f: + json.dump(output_json, f, ensure_ascii=False, indent=4) + logging.info("Generate AutoUpgrade Configfile to Complete and Jsonfile(%s) to complete... ",output_config_name) + + def _split_package_id(self,package): + """Return the name, the version number and the release of the + specified package.""" + if "=" in package: + name, version = package.split("=", 1) + release = None + elif "/" in package: + name, release = package.split("/", 1) + version = None + else: + name = package + version = release = None + return name, version, release + + def _make_downgrade(self,cache,downgrade_pkgs): + output_downgrade = [] + adjust_pkgs = [] + for pkg_name, pkg_ver, pkg_rel in [self._split_package_id(pkg) + for pkg in downgrade_pkgs]: + try: + pkg = cache[pkg_name] + except KeyError: + logging.warning("Package %s isn't available",pkg_name) + continue + if not pkg.is_installed: + logging.warning("Package %s isn't installed",pkg_name) + + if pkg_ver: + if pkg.installed and pkg.installed.version < pkg_ver: + logging.warning("The former version %s of %s is already installed",pkg.installed.version, pkg.name) + continue + elif pkg.installed and pkg.installed.version == pkg_ver: + logging.warning("The version %s of %s is already installed",pkg.installed.version, pkg.name) + continue + + try: + pkg.candidate = pkg.versions[pkg_ver] + except KeyError: + logging.warning("The version %s of %s isn't available",pkg_ver, pkg_name) + continue + + output_downgrade.append(pkg_name) + adjust_pkgs.append(pkg_name+'='+pkg_ver) + + return output_downgrade,adjust_pkgs + + def _get_downgrade_list(self,cache,data): + downgrade_pkgs = [] + + try: + downgrade_raw = data['force_install_list'] + except Exception as e: + downgrade_raw = [] + + for pkg_name, pkg_ver, pkg_rel in [self._split_package_id(pkg) + for pkg in downgrade_raw]: + + if pkg_name in cache: + downgrade_pkgs.append(pkg_name) + else: + logging.warning("Package %s isn't available",pkg_name) + continue + + return downgrade_raw,downgrade_pkgs + + def _make_groups_pkgs(self,cache,data,pkgs_upgrade = []): + + upgrade_pkgs_list = data['upgrade_list'] + #检查包是否在cache中 以及是否已经安装 没有安装的话才添加到列表 + new_install_list = self._check_pkg_in_cache(cache,data['install_list']) + + downgrade_raw,downgrade_pkgs = self._get_downgrade_list(cache,data) + #被降级的软件包优先级最高 + for pkg in downgrade_pkgs: + if pkg in upgrade_pkgs_list: + upgrade_pkgs_list.remove(pkg) + if pkg in new_install_list: + new_install_list.remove(pkg) + if pkg in self.upgrade_meta.single_pkgs: + self.upgrade_meta.single_pkgs.remove(pkg) + + #进行交集 升级列表 + new_upgrade_list = list(set(pkgs_upgrade) & set(upgrade_pkgs_list)) + + #进行源过滤 + new_install_list,new_upgrade_list,adjust_pkgs = self._make_fiter_origin([cache[pkg] for pkg in new_install_list + new_upgrade_list],False) + self.upgrade_meta.adjust_pkgs.extend(adjust_pkgs) + + #在总升级列表中移除这些包 + for pkg in new_upgrade_list: + pkgs_upgrade.remove(pkg) + + downgrade_pkg,adjust_pkgs = self._make_downgrade(cache,downgrade_raw) + self.upgrade_meta.adjust_pkgs.extend(adjust_pkgs) + new_upgrade_list.extend(downgrade_pkg) + + #单包的优先级最高 从组中剔除此包 + for pkg in self.upgrade_meta.single_pkgs: + if pkg in new_install_list: + new_install_list.remove(pkg) + + return new_install_list,new_upgrade_list + + + def _make_groups_upgrade(self,cache,group_list,is_openkylin,pkgs_install,pkgs_upgrade): + upgrade_list = [] + install_list = [] + + if os.path.isdir(self.config_path) == False: + logging.warning("configPath(%s) is not exists...",self.config_path) + return + + files = os.listdir(self.config_path) #获得文件夹中所有文件的名称列表 + + for ifile in files: + #判是否是目录以及是否以JSON结尾 + if ifile.endswith('.json'): + #读取组JSON文件 + with open(self.config_path+ifile,'r') as f: + try: + data = json.load(f) + except Exception as exc: + logging.error(exc) + raise UpdateBaseError(ERROR_LOAD_CONFIG_FAILED) + + group_name = data['package'] + #读取组的yaml 文件的changelog的信息 + with open(self.config_path + group_name + ".yaml", "r") as stream: + try: + data_yaml = yaml.safe_load(stream) + except Exception as exc: + logging.error(exc) + raise UpdateBaseError(ERROR_LOAD_CONFIG_FAILED) + + #过滤没有推送的配置文件 + if not group_name in group_list: + continue + + if is_openkylin == True: + install_list,upgrade_list = pkgs_install,pkgs_upgrade + else: + install_list,upgrade_list = self._make_groups_pkgs(cache,data,pkgs_upgrade) + + #判断当前是否可升级或者新装的包 + if len(install_list) == 0 and len(upgrade_list) == 0: + continue + + #3、生成升级的包列表JSON + upgrade_pkgs_json = self._make_pkg_info_json(cache,upgrade_list) + #2、生成安装的软件列表 + install_pkgs_json = self._make_pkg_info_json(cache,install_list) + #输出JSON配置文件 + self._make_group_output_json(data,data_yaml,upgrade_pkgs_json,install_pkgs_json) + + #保存分组版本号,好像没有 + self.upgrade_meta.versoin_pkgs['groups_upgrade'].update({group_name:''}) + + #添加到字典维护的升级列表 + self.upgrade_meta.upgrade_groups.append(group_name) + self.upgrade_meta.groups_pkgs.update({group_name:{"pkgs_upgrade":upgrade_list,"pkgs_install":install_list}}) + logging.info("Group(%s) upgrade:%d install:%d",group_name,len(upgrade_list),len(install_list)) + else: + pass + + def _make_openkylin_output_json(self,upgrade_pkgs_json,install_pkgs_json): + groups_base_info = {} + output_json = {} + + #FIXME: 确定输出文件的文件名 以及放置位置 + output_config_name = self.OUTPUT_CONFIG_PATH + "kylin-update-desktop-system.json" + + #4、添加一些基础信息 + groups_base_info.update({"package":"kylin-update-desktop-system"}) + groups_base_info.update({"new_version":"33797.0001"}) + groups_base_info.update({"name":{"zh_CN": "系统更新","en_US": "Kylin OS"}}) + groups_base_info.update({"description":{"zh_CN": "Openkylin-系统更新包","en_US": "Openkylin-System Update Package"}}) + groups_base_info.update({"icon":" "}) + + #添加读yaml文件 + groups_base_info.update({"changelog":"Openkylin-系统更新包\n"}) + + #5、添加升级的内容 + output_json.update(groups_base_info) + output_json.update({"upgrade_list":upgrade_pkgs_json}) + output_json.update({"install_list":install_pkgs_json}) + + #6 产生JSON文件 + with open(output_config_name, 'w', encoding='utf-8') as f: + json.dump(output_json, f, ensure_ascii=False, indent=4) + logging.info("Generate Jsonfile(%s) to complete... ",output_config_name) + + def _rate_application_for_package(self, application, pkg): + score = 0 + desktop_file = os.path.basename(application.get_filename()) + application_id = os.path.splitext(desktop_file)[0] + + if application.should_show(): + score += 1 + + if application_id == pkg.name: + score += 5 + + return score + + def _file_is_application(self, file_path): + # WARNING: This is called often if there's a lot of updates. A poor + # performing call here has a huge impact on the overall performance! + if not file_path.endswith(".desktop"): + # First the obvious case: If the path doesn't end in a .desktop + # extension, this isn't a desktop file. + return False + + file_path = os.path.abspath(file_path) + for app_dir in self.application_dirs: + if file_path.startswith(app_dir): + return True + return False + + def get_application_for_package(self, pkg): + desktop_files = [] + rated_applications = [] + + for installed_file in pkg.installed_files: + if self._file_is_application(installed_file): + desktop_files.append(installed_file) + + for desktop_file in desktop_files: + try: + application = Gio.DesktopAppInfo.new_from_filename( + desktop_file) + application.set_desktop_env(self.current_desktop) + except Exception as e: + logging.warning("Error loading .desktop file %s: %s" % + (desktop_file, e)) + continue + score = self._rate_application_for_package(application, pkg) + if score > 0: + rated_applications.append((score, application)) + + rated_applications.sort(key=lambda app: app[0], reverse=True) + if len(rated_applications) > 0: + return rated_applications[0][1] + else: + return None + + def _make_single_upgrade(self,cache,pkg_list): + for pkg in pkg_list: + zh_name = '' + base_info = {} + output_json = {} + output_config_name = self.OUTPUT_CONFIG_PATH + pkg + '.json' + + pkg_cache = cache[pkg] + + #获取包的软件名称,从主题中读取,只有可升级,是软件的包才可以读取 + if pkg_cache.is_installed: + app = self.get_application_for_package(pkg_cache) + if app is not None: + zh_name = app.get_display_name() + + pkgs_json = self._make_pkg_info_json(cache,[pkg]) + en_name = getattr(pkg_cache.candidate, "summary", '') + description_str = getattr(pkg_cache.candidate, "description", '') + + #4、添加一些基础信息 + base_info.update({"package":pkg}) + + base_info.update({"cur_version":getattr(pkg_cache.installed, "version", '')}) + base_info.update({"new_version":getattr(pkg_cache.candidate, "version", '')}) + base_info.update({"name":{"zh_CN":zh_name,"en_US":en_name}}) + base_info.update({"description":{"zh_CN":description_str,"en_US":description_str}}) + base_info.update({"icon":''}) + + #5、添加升级的内容 + output_json.update(base_info) + if pkg_cache.is_installed: + output_json.update({"upgrade_list":pkgs_json}) + output_json.update({"install_list":{}}) + else: + output_json.update({"upgrade_list":{}}) + output_json.update({"install_list":pkgs_json}) + + #产生JSON文件 + with open(output_config_name, 'w', encoding='utf-8') as f: + json.dump(output_json, f, ensure_ascii=False, indent=4) + logging.info("Generate Jsonfile(%s) to complete... ",output_config_name) + + #6、保存单包版本号 + self.upgrade_meta.versoin_pkgs['single_upgrade'].update({pkg_cache.name:getattr(pkg_cache.installed, "version", '')}) + + def _make_distupgrade(self,cache): + pkgs_upgrade = [] + pkgs_install = [] + if cache.get_changes(): + cache.clear() + cache._depcache.upgrade(True) + + #查找所有可升级的包 + for pkg in cache: + try: + if pkg.marked_install: + pkgs_install.append(pkg.name) + elif pkg.marked_upgrade: + pkgs_upgrade.append(pkg.name) + except KeyError: + pass + return pkgs_install,pkgs_upgrade + + def update_kylin(self,cache,important_data,is_openkylin = False): + pkgs_install = [] + pkgs_upgrade = [] + + #查找所有可升级的包 + if is_openkylin == True: + pkgs_install,pkgs_upgrade = self._make_distupgrade(cache) + else: + for pkg in cache: + if pkg.is_upgradable and pkg.is_installed: + pkgs_upgrade.append(pkg.name) + + logging.info("System all upgradeable packages:upgrade:%d install:%d ",len(pkgs_upgrade),len(pkgs_install)) + + group_important_list,self.upgrade_meta.single_pkgs = self._make_important_list(cache,pkgs_upgrade,important_data) + + #清空输出的目录 + self._empty_output_dir() + + #important_list 为空时此次不需要升级 + if not group_important_list and not self.upgrade_meta.single_pkgs: + self.parent.dbusController.UpdateDetectFinished(True,[],'','') + return + + #产生单包的JSON + self._make_single_upgrade(cache,self.upgrade_meta.single_pkgs) + + #分组的包的JSON + self._make_groups_upgrade(cache,group_important_list,is_openkylin,pkgs_install,pkgs_upgrade) + + self._make_autoupgrade_config(cache,self.upgrade_meta,self.upgrade_meta.adjust_pkgs) + + self.parent.dbusController.UpdateDetectFinished(True,self.upgrade_meta.upgrade_groups + self.upgrade_meta.single_pkgs,'','') + + return diff --git a/backend/SystemUpdater/Core/UpdaterConfigParser.py b/backend/SystemUpdater/Core/UpdaterConfigParser.py new file mode 100755 index 0000000..bfef7bd --- /dev/null +++ b/backend/SystemUpdater/Core/UpdaterConfigParser.py @@ -0,0 +1,105 @@ +#!/usr/bin/python3 +# DistUpgradeConfigParser.py +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + + +from configparser import NoOptionError +from configparser import ConfigParser as SafeConfigParser +import os.path +import logging + +class UpgradeConfig(SafeConfigParser): + def __init__(self, datadir="/var/lib/kylin-system-updater/", name="system-updater.conf",defaults_dir=None): + SafeConfigParser.__init__(self) + self.datadir = datadir + maincfg = os.path.join(datadir, name) + # defaults are read first + self.config_files = [] + if defaults_dir: + self.config_files.append(os.path.join(datadir, defaults_dir)) + # our config file + self.config_files += [maincfg] + self.read(self.config_files) + logging.info("Initialize Upgrade ConfigFile(%s) to success",str(self.config_files)) + + def optionxform(self, optionstr): + return optionstr + + def reReadConfigFiles(self): + self.read(self.config_files) + + def setValue(self, section, option, value=None,is_write = True): + if option != 'upgradelist': + logging.info("SetValue Section:%s Option:%s Value:%s",section, option, value) + try: + self.reReadConfigFiles() + + self.set(section, option,value) + except Exception as e: + logging.error("Error: setValue section:%s option:%s value:%s",section, option, value) + logging.error(str(e)) + return False + if is_write == True: + with open(self.config_files[-1], 'w+') as configfile: + self.write(configfile) + return True + + def getWithDefault(self, section, option, default,re_read=False): + try: + if re_read == True: + self.reReadConfigFiles() + + if type(default) == bool: + return self.getboolean(section, option) + elif type(default) == float: + return self.getfloat(section, option) + elif type(default) == int: + return self.getint(section, option) + return self.get(section, option) + except Exception as e: + logging.error(str(e)) + return default + + def getlist(self, section, option): + try: + tmp = self.get(section, option) + except Exception as e: + logging.error(str(e)) + return [] + items = [x.strip() for x in tmp.split(" ")] + if '' in items and len(items): + return [] + return items + + def setListValue(self, section, option, value=None,is_write = True): + tmp = str(value).replace('[', '').replace(']', '') + tmp = tmp.replace("'", '').replace(',', '') + try: + self.set(section, option,tmp) + except Exception as e: + logging.error("setListValue section:%s option:%s",section, option) + logging.error(str(e)) + return + if is_write == True: + with open(self.config_files[-1], 'w+') as configfile: + self.write(configfile) + + def getListFromFile(self, section, option): + try: + filename = self.get(section, option) + except NoOptionError: + return [] + p = os.path.join(self.datadir, filename) + if not os.path.exists(p): + logging.error("getListFromFile: no '%s' found" % p) + with open(p) as f: + items = [x.strip() for x in f] + return [s for s in items if not s.startswith("#") and not s == ""] + + +if __name__ == "__main__": + # c = UpgradeConfig("/home/x/share/kylin-system-updater/backend/data/") + # print(c.setValue("SystemStatus", "abnormal_reboot", str(False)),True) + # print(c.getWithDefault("SystemStatus", "abnormal_reboot", False)) + pass diff --git a/backend/SystemUpdater/Core/__init__.py b/backend/SystemUpdater/Core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/SystemUpdater/Core/enums.py b/backend/SystemUpdater/Core/enums.py new file mode 100644 index 0000000..5e5aa5f --- /dev/null +++ b/backend/SystemUpdater/Core/enums.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""enums - Enumerates for apt daemon dbus messages""" + +__all__ = ( + "ERROR_UPDATE_DEFAULT_FAILED", + "ERROR_UPDATE_SOURCE_FAILED","ERROR_NETWORK_FAILED","ERROR_NOT_GROUPS_CONFIG","ERROR_SOFTWARE_INDEX_RROKEN", + "ERROR_NOT_INIT_PACKAGESINFIO","ERROR_READ_IMPORTANTLIST_FAILED","ERROR_RESOLVER_FAILED","ERROR_NOT_UPGRADE_PACKAGES", + "ERROR_REMOVE_ESSENTIAL_PACKAGES","ERROR_NOT_DISK_SPACE","ERROR_NOT_CONFIGPKG_DEPENDENCIES","ERROR_NOT_SELFPKG_DEPENDENCIES", + "ERROR_NOT_FIX_SYSTEM","ERROR_READ_LOCAL_DEB","ERROR_LOCAL_DEB_FORMAT","ERROR_INSTALL_DEB_BASE","ERROR_LOAD_CONFIG_FAILED", + "ERROR_UPDATE_KEY_SIGNATURES","ERROR_UPDATE_NET_AUTHENTICATION","ERROR_UPDATE_NOTREAD_SOURCES","PRIORITY_UPGRADE_SUCCCESSED", + "ERROR_UPDATE_INVALID_TIME", + + "get_error_description_from_enum", "get_error_string_from_enum", "get_source_name_from_enum", "get_caller_from_enum") + +import gettext +gettext.bindtextdomain('kylin-system-updater', '/usr/share/locale') +gettext.textdomain('kylin-system-updater') +_ = gettext.gettext + +PRIORITY_UPGRADE_SUCCCESSED = "priority-upgrade-successed" + +#apt update阶段出现的错误解析 +ERROR_UPDATE_DEFAULT_FAILED = "error-update-default-failed" +ERROR_UPDATE_KEY_SIGNATURES = "The following signatures" +ERROR_UPDATE_NET_AUTHENTICATION ="does the network require authentication?" +ERROR_UPDATE_NOTREAD_SOURCES = "The list of sources could not be read" +ERROR_UPDATE_INVALID_TIME = "(invalid for another" + +ERROR_UPDATE_SOURCE_FAILED = "error-update-source-failed" +ERROR_NETWORK_FAILED = "error-network-failed" +ERROR_NOT_GROUPS_CONFIG = "error-not-groups-config" +ERROR_NOT_CONFIGPKG_DEPENDENCIES = "error-not-configpkg-dependencies" +ERROR_NOT_SELFPKG_DEPENDENCIES = "error-not-selfpkg-dependencies" + +ERROR_NOT_FIX_SYSTEM = "error-not-fix-system" + +ERROR_LOAD_CONFIG_FAILED = "error-load-config-failed" + +#自己的 +ERROR_SOFTWARE_INDEX_RROKEN = "error-software-index-broken" +ERROR_NOT_INIT_PACKAGESINFIO = "error-not-init-packagesinfo" +ERROR_READ_IMPORTANTLIST_FAILED = "error-read-importantlist-failed" +ERROR_RESOLVER_FAILED = "error-resolver-failed" +ERROR_NOT_UPGRADE_PACKAGES = "error-not-upgrade-packages" +ERROR_REMOVE_ESSENTIAL_PACKAGES = "error-remove-essential-packages" +ERROR_NOT_DISK_SPACE = "error-not-disk-space" +ERROR_READ_LOCAL_DEB = "error-read-local-deb" +ERROR_LOCAL_DEB_FORMAT = "error-local-deb-format" +ERROR_INSTALL_DEB_BASE = "error-install-deb-base" + +_STRINGS_ERROR = { + PRIORITY_UPGRADE_SUCCCESSED: _("Update Manager upgrade is complete, please restart the setting panel before performing the system update."), + + #update + ERROR_UPDATE_DEFAULT_FAILED: _("Check for update exceptions!"), + ERROR_UPDATE_SOURCE_FAILED: _("Check for update exceptions!"), + ERROR_NETWORK_FAILED: _("Network anomaly, can't check for updates!"), + ERROR_UPDATE_KEY_SIGNATURES: _("Check for update exceptions!"), + ERROR_READ_IMPORTANTLIST_FAILED: _("Check for update exceptions!"), + ERROR_SOFTWARE_INDEX_RROKEN: _("Check for update exceptions!"), + ERROR_NOT_INIT_PACKAGESINFIO: _("Check for update exceptions!"), + ERROR_NOT_FIX_SYSTEM: _("Check for update exceptions!"), + ERROR_LOAD_CONFIG_FAILED: _("Check for update exceptions!"), + + #优先升级 + ERROR_NOT_GROUPS_CONFIG: _("Upgrade configuration acquisition exception."), + ERROR_NOT_CONFIGPKG_DEPENDENCIES: _("Upgrade configuration acquisition exception."), + ERROR_NOT_SELFPKG_DEPENDENCIES: _("Priority upgrade status exception."), + + #install + ERROR_RESOLVER_FAILED: _("Could not calculate the upgrade"), + ERROR_NOT_UPGRADE_PACKAGES: _("There is an exception in the update package."), + ERROR_REMOVE_ESSENTIAL_PACKAGES: _("There is an exception in the update package."), + ERROR_NOT_DISK_SPACE: _("Disk space is insufficient, please clean the disk and then upgrade"), + ERROR_READ_LOCAL_DEB:_(" "), + ERROR_LOCAL_DEB_FORMAT:_(" "), + ERROR_INSTALL_DEB_BASE:_(" ")} + +_DESCS_ERROR = { + #update + ERROR_UPDATE_SOURCE_FAILED: _("Unable to access the source management server"), + ERROR_NETWORK_FAILED: _("Please check your network connection and retry."), + ERROR_UPDATE_KEY_SIGNATURES: _("Check your source public key signature"), + ERROR_UPDATE_NOTREAD_SOURCES: _("Please check your source list and retry."), + ERROR_UPDATE_INVALID_TIME: _("Please check the system time and synchronize the system time before updating."), + ERROR_UPDATE_NET_AUTHENTICATION: _("Check if your network requires authentication?"), + ERROR_NOT_GROUPS_CONFIG: _("Unable to get group configuration package, Please check if the configuration package exists in the software source repository."), + ERROR_NOT_INIT_PACKAGESINFIO: _("An unresolvable problem occurred while initializing the package."), + ERROR_SOFTWARE_INDEX_RROKEN: _("Software index is broken") + _("It is impossible to install or remove any software. " + "Please use the package manager \"Synaptic\" or run " + "\"sudo apt-get install -f\" in a terminal to fix " + "this issue at first."), + ERROR_READ_IMPORTANTLIST_FAILED: _("read important list failed"), + ERROR_NOT_CONFIGPKG_DEPENDENCIES: _("Unable to install group configuration package, Please check the configuration package related dependencies."), + ERROR_NOT_SELFPKG_DEPENDENCIES: _("Unable to perform priority upgrade, please check the dependency related to the priority upgrade package."), + + ERROR_LOAD_CONFIG_FAILED: _("The system update configuration file is read abnormally, please check if the system update configuration file format is correct."), + ERROR_NOT_FIX_SYSTEM: _("The system APT environment is abnormal, please check the system APT environment."), + + #install + ERROR_RESOLVER_FAILED: _("nothing"), + ERROR_NOT_UPGRADE_PACKAGES: _("This update cannot detect the upgradeable package."), + ERROR_REMOVE_ESSENTIAL_PACKAGES: _("You request the removal of a system-essential package."), + ERROR_NOT_DISK_SPACE: _("test"), + ERROR_READ_LOCAL_DEB:_("Deb format exception, read local deb file error."), + ERROR_LOCAL_DEB_FORMAT:_("Deb format exception, failed to parse package file."), + ERROR_INSTALL_DEB_BASE:_("Install deb error.") + } + +#UPGRADE MONITOR STATUS +MONIT_DETECT = "step-updatedetect" +MONIT_DEPRESOLUT = "step-depresolution" +MONIT_DOWNLOAD = "step-downloading" +MONIT_INSTALL = "step-installing" +MONIT_FINISH = "step-finish" +MONIT_INSTALLDEB = "step-installdeb" + +SOURCE_NAME = { + 'kylin-installer':_("Kylin Installer"), + 'kylin-uninstaller':_("Kylin Uninstaller"), + 'kylin-background-upgrade':_("Kylin Background Upgrade"), + 'kylin-software-center':_("Kylin Software Center"), +} + +CALLER = { + 'kylin-installer':"Kylin Installer", + 'kylin-uninstaller':"Kylin Uninstaller", + 'kylin-background-upgrade':"Kylin Background Upgrade", + 'kylin-software-center':"Kylin Software Center", +} + +def get_error_description_from_enum(enum): + """Get a long description of an error. + + :param enum: The transaction error enum, e.g. :data:`ERROR_NO_LOCK`. + :returns: The description string. + """ + try: + return _DESCS_ERROR[enum] + except KeyError: + return None + + +def get_error_string_from_enum(enum): + """Get a short description of an error. + + :param enum: The transaction error enum, e.g. :data:`ERROR_NO_LOCK`. + :returns: The description string. + """ + try: + return _STRINGS_ERROR[enum] + except KeyError: + return None + + +def get_source_name_from_enum(enum): + try: + return SOURCE_NAME[enum] + except KeyError: + return _("Kylin System Updater") + +def get_caller_from_enum(enum): + try: + return CALLER[enum] + except KeyError: + return _("Kylin System Updater") + +# vim:ts=4:sw=4:et diff --git a/backend/SystemUpdater/Core/errors.py b/backend/SystemUpdater/Core/errors.py new file mode 100644 index 0000000..9c8016b --- /dev/null +++ b/backend/SystemUpdater/Core/errors.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Exception classes""" + +# __all__ = ("UpdateBaseError") + +import logging +from selectors import EpollSelector +import sys +from .enums import * + +PY3K = sys.version_info.major > 2 + +class UpdateBaseError(Exception): + + """Internal error if a transaction could not be processed successfully.""" + + _dbus_error_name = "org.debian.apt.TransactionFailed" + + def __init__(self, code,header=None,desc=None,details="",*args): + if not args: + # Avoid string replacements if not used + details = details.replace("%", "%%") + args = tuple([_convert_unicode(arg) for arg in args]) + details = _convert_unicode(details) + self.code = code + self.details = details + self.details_args = args + if header == None: + self.header = get_error_string_from_enum(self.code) + else: + self.header = header + + if desc == None: + self.desc = get_error_description_from_enum(self.code) + else: + self.desc = desc + + Exception.__init__(self, *args) + # AptDaemonError.__init__(self, "%s: %s" % (code, details % args)) + + def __unicode__(self): + return "%s" % \ + (get_error_string_from_enum(self.code)) + + def __str__(self): + if PY3K: + return self.__unicode__() + else: + return self.__unicode__().encode("utf-8") + +class UpdateProgressExit(Exception): + def __init__(self,*args): + Exception.__init__(self, *args) + logging.info("Update Progress wiil be Exit...") + +def _convert_unicode(text, encoding="UTF-8"): + """Always return an unicode.""" + if PY3K and not isinstance(text, str): + text = str(text, encoding, errors="ignore") + elif not PY3K and not isinstance(text, unicode): + text = unicode(text, encoding, errors="ignore") + return text + +# vim:ts=4:sw=4:et diff --git a/backend/SystemUpdater/Core/loop.py b/backend/SystemUpdater/Core/loop.py new file mode 100644 index 0000000..3c1c60f --- /dev/null +++ b/backend/SystemUpdater/Core/loop.py @@ -0,0 +1,13 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +"""Main loop for aptdaemon.""" + +__all__ = ("mainloop", "get_main_loop") + +from gi.repository import GLib + +mainloop = GLib.MainLoop() + +def get_main_loop(): + """Return the glib main loop as a singleton.""" + return mainloop \ No newline at end of file diff --git a/backend/SystemUpdater/Core/utils.py b/backend/SystemUpdater/Core/utils.py new file mode 100644 index 0000000..2717de1 --- /dev/null +++ b/backend/SystemUpdater/Core/utils.py @@ -0,0 +1,809 @@ +# utils.py +# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- +# +# Copyright (c) 2004-2013 Canonical +# +# Authors: Michael Vogt +# Michael Terry +# +# 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 + +from __future__ import print_function + +from gettext import gettext as _ +from gettext import ngettext +from stat import (S_IMODE, ST_MODE, S_IXUSR) +from math import ceil + +import apt +import dbus +import apt_pkg +apt_pkg.init_config() + +import shutil +import locale +import logging +import re +import os +import subprocess +import sys +import time +import fcntl +from urllib.request import ( + ProxyHandler, + Request, + build_opener, + install_opener, + urlopen, +) +import dbus +from urllib.parse import urlsplit + +from copy import copy +import psutil +import ctypes +from ctypes import * +import struct + +# 禁止关机锁文件路径 +VERIFY_SO = "libkylin_signtool.so" + +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)) + + +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 " + # then check the real one + from subprocess import Popen, PIPE + p = Popen(["lsb_release", "-i", "-s"], stdout=PIPE, + universal_newlines=True) + res = p.wait() + if res != 0: + sys.stderr.write("lsb_release returned exitcode: %i\n" % res) + return "unknown distribution" + dist = p.stdout.readline().strip() + p.stdout.close() + return dist + + +def get_dist_version(): + " return the version of the current running distro " + # then check the real one + from subprocess import Popen, PIPE + p = Popen(["lsb_release", "-r", "-s"], stdout=PIPE, + universal_newlines=True) + res = p.wait() + if res != 0: + sys.stderr.write("lsb_release returned exitcode: %i\n" % res) + return "unknown distribution" + desc = p.stdout.readline().strip() + p.stdout.close() + return desc + + +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 is_chinese(string): + """ + 检查整个字符串是否包含中文 + :param string: 需要检查的字符串 + :return: bool + """ + for ch in string: + if u'\u4e00' <= ch <= u'\u9fff': + return True + + 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 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 Gtk, Gdk + d = Gtk.MessageDialog(parent=parent, + flags=Gtk.DialogFlags.MODAL, + type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.CLOSE) + d.set_markup("%s\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 _split_package_id(package): + """Return the name, the version number and the release of the + specified package.""" + if ":" in package: + name, arch = package.split(":", 1) + # release = None + # elif "/" in package: + # name, release = package.split("/", 1) + # version = None + else: + name = package + arch = None + return name, arch + + +def get_config_patch(): + #检查组配置文件当前的目录 + NOW_UPDATE_CONFIG = '/usr/share/kylin-update-desktop-config/config/' + OLD_UPDATE_CONFIG = '/usr/share/kylin-update-desktop-config/data/' + if os.path.exists(NOW_UPDATE_CONFIG): + return NOW_UPDATE_CONFIG + elif os.path.exists(OLD_UPDATE_CONFIG): + return NOW_UPDATE_CONFIG + else: + return NOW_UPDATE_CONFIG + +def get_broken_details(cache,now=True): + """Return a message which provides debugging information about + broken packages. + + This method is basically a Python implementation of apt-get.cc's + ShowBroken. + + Keyword arguments: + trans -- the corresponding transaction + #表示当前系统apt已经破损的话是True 如果是安装软件包讲导致破损的话是False + now -- if we check currently broken dependecies or the installation + candidate + """ + msg = _("The following packages have unmet dependencies:") + msg += "\n\n" + for pkg in cache: + if not ((now and pkg.is_now_broken) or + (not now and pkg.is_inst_broken)): + continue + msg += "%s: " % pkg.name + #获取出现问题的包的版本 + if now: + version = pkg.installed + else: + version = pkg.candidate + indent = " " * (len(pkg.name) + 2) + dep_msg = "" + #拿取依赖关系 + for dep in version.dependencies: + or_msg = "" + for base_dep in dep.or_dependencies: + if or_msg: + or_msg += "or\n" + or_msg += indent + # Check if it's an important dependency + # See apt-pkg/depcache.cc IsImportantDep + # See apt-pkg/pkgcache.cc IsCritical() + if not (base_dep.rawtype in ["Depends","PreDepends", + "Obsoletes", "DpkgBreaks", + "Conflicts"] or + (apt_pkg.config.find_b("APT::Install-Recommends", + False) and + base_dep.rawtype == "Recommends") or + (apt_pkg.config.find_b("APT::Install-Suggests", + False) and + base_dep.rawtype == "Suggests")): + continue + # Get the version of the target package + try: + pkg_name,pkg_arch = _split_package_id(base_dep.name) + pkg_dep = cache[pkg_name] + except KeyError: + dep_version = None + else: + if now: + dep_version = pkg_dep.installed + else: + dep_version = pkg_dep.candidate + # We only want to display dependencies which cannot + # be satisfied + if dep_version and not apt_pkg.check_dep(base_dep.version, + base_dep.relation, + dep_version.version): + break + or_msg = "%s: %s " % (base_dep.rawtype, base_dep.name) + if base_dep.version: + or_msg += "(%s %s) " % (base_dep.relation, + base_dep.version) + if cache.is_virtual_package(base_dep.name): + or_msg += _("but it is a virtual package") + #表示这个依赖包没有安装 源里面没有 + elif not dep_version: + if now: + or_msg += _("but it is not installed") + else: + #要依赖包 不存在时走此 + or_msg += _("but it is not going to " + "be installed") + #表示安装的版本与需要的版本不一致 在这个地方来再进行递归安装判断 具体那些包造成的不能安装 + elif now: + # TRANSLATORS: %s is a version number + or_msg += (_("but %s is installed") % + dep_version.version) + else: + #安装之后出现破损的话走这里 + # TRANSLATORS: %s is a version number + or_msg += (_("but %s is to be installed") % + dep_version.version) + else: + # Only append an or-group if at least one of the + # dependencies cannot be satisfied + if dep_msg: + dep_msg += indent + dep_msg += or_msg + dep_msg += "\n" + msg += dep_msg + return msg + + +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) + +# 查看uu进程是否需要kill +def kill_process(path): + try: + # 判断文件是否存在 + if (os.path.exists(path)): + with open(path, "r") as f: + pid = f.read() + if len(pid) == 0: + return False + logging.info("Unattended Upgrades run path: %d. ", int(pid)) + os.kill(int(pid), 9) + logging.info('Unattended Upgrades is running, kill pid: %d. ', int(pid)) + else: + logging.warning('%s is not exist.', path) + except Exception as e: + logging.error(e) + return False + return True + +def whether_to_quit_uu(): + osreleasedict={} + try: + with open('/etc/os-release') as f: + lines = f.readlines() + for line in lines: + ls = line.strip().split('=',1) + osreleasedict.update({ls[0]:ls[1].strip('"')}) + except Exception as e: + pass + if 'SUB_PROJECT_CODENAME' not in osreleasedict.keys(): + osreleasedict.update({'SUB_PROJECT_CODENAME':''}) + if 'PROJECT_CODENAME' in osreleasedict: + if osreleasedict['PROJECT_CODENAME']=='V10SP1-edu': + if 'SUB_PROJECT_CODENAME' in osreleasedict: + if osreleasedict['SUB_PROJECT_CODENAME']=='mavis': + return False + else: + logging.info("SUB_PROJECT_CODENAME != mavis") + else: + logging.info("no SUB_PROJECT_CODENAME") + else: + logging.info("PROJECT_CODENAME != V10SP1-edu") + else: + logging.info("no PROJECT_CODENAME") + + return True + +def get_proc_from_dbus_name(dbus_name, bus=None): + """Return a deferred that gets the id of process owning the given + system D-Bus name. + """ + + if not bus: + bus = dbus.SystemBus() + bus_obj = bus.get_object("org.freedesktop.DBus", + "/org/freedesktop/DBus/Bus") + pid = bus_obj.GetConnectionUnixProcessID(dbus_name, + dbus_interface="org.freedesktop.DBus") + proc = psutil.Process(int(pid)) + + # with open("/proc/%s/status" % pid) as process: + # values = [v for v in process.readlines() if v.startswith("Uid:")] + # uid = int(values[0].split()[1]) + + # #检查是否root用户执行 + # if uid == 0: + # return "root" + + return proc.name() + +def deb_verify(deb_path, _isinstall = False): + logging.info("Verify pkg:%s.",deb_path) + _deb_path = str(deb_path) + try: + # # 加载验证签名库 , 验签接口暂时无法调用 + # args = ["dpkg-architecture", "-qDEB_TARGET_MULTIARCH"] + # ret = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + # verifyso_path = os.path.join("/usr/lib/",str(ret.stdout).strip(),VERIFY_SO) + # logging.info("Load verify interface:%s.",verifyso_path) + # verifyso = ctypes.CDLL(verifyso_path) + # #环境初始化 + # ret = verifyso.SOF_Initialize(ctx_obj) + # if (ret) : + # logging.info("SOF_InitializeEx error!") + # return 2 + # if os.path.isfile(_deb_path): + # ret = verifyso.BJCA_dodebverify(None, bytes(_deb_path, encoding='utf8'), _isinstall) + if not os.path.isfile("/usr/bin/kylinsigntool"): + logging.error("SOF_InitializeEx error!") + return 1 + args = ["/usr/bin/kylinsigntool", "-v", _deb_path] + ret = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + if "Signature Verified failed" in str(ret.stdout).strip() or "签名验证失败" in str(ret.stdout).strip(): + logging.info("Signature Verified failed!") + return 2 + elif "Signature Verified Ok" in str(ret.stdout).strip() or "签名验证成功" in str(ret.stdout).strip(): + logging.info("Signature Verified Ok!") + return 0 + except Exception as e: + logging.error(e) + return 3 + +def PolicyKit_Authority(details = '', sender = None): + _allow_kylinsign = False + _verify_kylinsign = False + try: + #获取未知来源应用安装策略Unknown sources apply installation policies + inst_policies_path = "/etc/dpkg/dpkg.cfg" + if os.path.isfile(inst_policies_path): + with open(inst_policies_path, "r") as f: + lines = f.readlines() + for line in lines: + if "allow-kylinsign" in line: + _allow_kylinsign = True + if "verify-kylinsign" in line: + _verify_kylinsign = True + if _allow_kylinsign == True and _verify_kylinsign == False: #策略: 阻止 + logging.debug("unknown sources apply installation policies: deter") + return False,_("The package is unsigned, refuses to install.") + elif _allow_kylinsign == True and _verify_kylinsign == True: #策略: 警告 + logging.debug("unknown sources apply installation policies: warning") + elif _allow_kylinsign == False and _verify_kylinsign == False: #策略: 关闭 + logging.debug("unknown sources apply installation policies: close") + else: + logging.warning("Unknown sources apply installation policies get failed.") + + #用户鉴权 + details = {'polkit.message':details} + cancel_id = '' + action = "cn.kylinos.KylinSystemUpdater.action" + kit = dbus.SystemBus().get_object('org.freedesktop.PolicyKit1', '/org/freedesktop/PolicyKit1/Authority') + kit = dbus.Interface(kit, 'org.freedesktop.PolicyKit1.Authority') + (granted, notused , details) = kit.CheckAuthorization( + ('system-bus-name', {'name': sender}), + action, details, dbus.UInt32(1),cancel_id, timeout=60*60*24*7) + if granted: + logging.info("Authentication success ...") + return True,_("Authentication success.") + else: + logging.info("Authentication failure ...") + return False,_("Authentication failure.") + except Exception as e: + logging.error(e) + return False,str(e) + +if __name__ == "__main__": + #print(mirror_from_sources_list()) + #print(on_battery()) + #print(inside_chroot()) + #print(iptables_active()) + error(None, "bar", "baz") diff --git a/backend/SystemUpdater/UpdateManager.py b/backend/SystemUpdater/UpdateManager.py new file mode 100644 index 0000000..198e0bc --- /dev/null +++ b/backend/SystemUpdater/UpdateManager.py @@ -0,0 +1,1149 @@ +# UpdateManager.py +# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- +import os +import apt_pkg +import sys +import time +import shutil +import dbus +import logging +import dbus.service +import threading +import subprocess +import traceback +from apt import Cache +from gettext import gettext as _ +from apt.debfile import DebPackage +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) + +from .Core.errors import * +from .Core.enums import * +from .Core.MyCache import MyCache +from .UpdateManagerDbus import UpdateManagerDbusController,UpdateManagerDbusControllerUtils,UPDATER_DBUS_INTERFACE,UPDATER_DBUS_PATH,UPDATER_DBUS_SERVICE +from .Core.UpdateList import UpdateList +from .backend import InstallBackend,get_backend +from .Core.Database import Sqlite3Server +from .Core.loop import mainloop +from .Core.DataAcquisition import UpdateMsgCollector + +from SystemUpdater.Core.UpdaterConfigParser import UpgradeConfig +from SystemUpdater.Core.utils import kill_process +from SystemUpdater.Core.DpkgInstallProgress import LogInstallProgress +from SystemUpdater.Core.utils import deb_verify,PolicyKit_Authority,get_proc_from_dbus_name,whether_to_quit_uu,get_dist + +class UpdateManager(): + BACKEND_PKG_NAME = 'kylin-system-updater' + FRONTEND_PKG_NAME = "kylin-update-frontend" + GROUPS_PKG_NAME = 'kylin-update-desktop-config' + SOURCES_UPDATE_NAME = "kylin-software-properties" + APTD_PKG_NAME = "aptdaemon" + RUN_UNATTENDED_UPGRADE = '/var/run/unattended-upgrades.pid' + RETRY_LIMIT_NUM = 2 + + def __init__(self,options): + try: + self.options = options + self.cache = None + self.update_list = None + self.init_config_aptdeamon = False + self.aptd_lang_switch = False + self.retry_limit = self.RETRY_LIMIT_NUM + self.now_working = InstallBackend.ACTION_DEFUALT_STATUS + #dbus + self.dbusController = self._setup_dbus() + self.dbusControllerUtils = self._setup_dbus_utils() + #config + self.configs_uncover = UpgradeConfig(defaults_dir="system-updater-defaults.conf") + self.configs_cover = UpgradeConfig(name = "system-updater-coverable.conf") + self.uuconfigs = UpgradeConfig(datadir = "/var/lib/unattended-upgrades/", name = "unattended-upgrades-policy.conf") + #数据采集器 + self.collector = UpdateMsgCollector(self) + #连接数据库 + self.sqlite3_server = Sqlite3Server(self) + self.simulate_mode = SimulateTerminal() + self.install_mode = UpdateInstallMode(self) + self.apt_p2p_config = AptP2pConfigManager() + self._reload_options_config() + self._refresh_cache_only() + + except Exception as e: + logging.error(e) + traceback.print_exc() + + def _reload_options_config(self): + #添加默认保留旧配置 + apt_pkg.config["DPkg::Options::"] = "--force-confold" + options_new = list(set(apt_pkg.config.value_list("DPkg::Options"))) + for option in ("--force-confnew","--force-confdef"): + if option in options_new: + options_new.remove(option) + #清除所有配置重新加载 + apt_pkg.config.clear("DPkg::Options") + for option in options_new: + apt_pkg.config["DPkg::Options::"] = option + #去除安装推荐和建议的软件包 + if apt_pkg.config.find_b("APT::Install-Recommends",False) == True: + apt_pkg.config.clear("APT::Install-Recommends") + if apt_pkg.config.find_b("APT::Install-Suggests",False) == True: + apt_pkg.config.clear("APT::Install-Suggests") + + def check_frontend_pkg(self): + #控制面板前端包的检查升级 + if self.FRONTEND_PKG_NAME in self.cache: + self_pkg = self.cache[self.FRONTEND_PKG_NAME] + + if not self_pkg.is_installed: + logging.info("Check: Frontend(%s) start new installing...",self.FRONTEND_PKG_NAME) + self_pkg.mark_install() + self.start_install(InstallBackend.MODE_INSTALL_SINGLE,True) + else: + #没有在cache中就认为不需要升级 + logging.error("Check: Frontend(%s) The upgrade package is not in Cache...",self.FRONTEND_PKG_NAME) + + #检查是否需要重新启动aptdeamon 目前需要重启的有限速功能 + def check_conifg_aptdeamon(self): + if self.init_config_aptdeamon == True: + self.init_config_aptdeamon = False + self.dbusController.set_aptdeamon_environ("init","config") + + def run(self): + """Start the daemon and listen for calls.""" + logging.info("Waiting for calls...") + try: + mainloop.run() + except KeyboardInterrupt: + self.dbusController.Quit(None) + + #进行清空所有下载的文件 + def start_clean(self): + clean_backend = get_backend(self, InstallBackend.ACTION_CLEAN) + clean_backend.start() + + #进行修复破损的包的操作 apt install -f + def start_fix_broken(self): + fix_backend = get_backend(self, InstallBackend.ACTION_FIX_BROKEN) + fix_backend.start() + + #进行 dpkg --configure + def start_fix_incomplete(self): + fix_backend = get_backend(self, InstallBackend.ACTION_FIX_INCOMPLETE) + fix_backend.start() + + #进行升级的操作 + def start_install(self,upgrade_mode,not_resolver = False,upgrade_content = []): + try: + if self.install_mode.shutdown_mode() == True and upgrade_mode != InstallBackend.MODE_INSTALL_SINGLE: + #部分升级的方式 计算的时候 补上上次更新的内容一起计算 + if upgrade_mode == InstallBackend.MODE_INSTALL_PARTIAL: + upgrade_content += self.install_mode.tmp_content + + if not_resolver == True: + kill_process(self.RUN_UNATTENDED_UPGRADE) + #未下载的阶段 + install_backend = get_backend(self, InstallBackend.ACTION_DOWNLOADONLY,upgrade_mode) + install_backend.start(upgrade_content) + else: + resolver_backend = get_backend(self, InstallBackend.ACTION_CHECK_RESOLVER,upgrade_mode) + resolver_backend.start(upgrade_content) + else: + if not_resolver == True: + if whether_to_quit_uu(): + kill_process(self.RUN_UNATTENDED_UPGRADE) + install_backend = get_backend(self, InstallBackend.ACTION_INSTALL,upgrade_mode) + install_backend.start(upgrade_content) + else: + resolver_backend = get_backend(self, InstallBackend.ACTION_CHECK_RESOLVER,upgrade_mode) + resolver_backend.start(upgrade_content) + except Exception as e: + logging.error(e) + + #进行更新的操作 + def start_update(self,update_mode = InstallBackend.MODE_UPDATE_ALL): + try: + #更新前的准备 + self.configs_cover.reReadConfigFiles() + self.retry_limit = self.RETRY_LIMIT_NUM + + self.install_mode.reset_shutdown_mode() + + #检查 光盘源 + self.install_mode.check_source() + + if self.install_mode.check_network() == True: + self.dbusController.check_connectivity() + + if self.install_mode.update_important() == True: + self.dbusController.on_update_important_list() + return + + #不进行update + if self.options.no_update: + self.start_available() + return + + self.start_update_backend(update_mode = update_mode) + + except UpdateBaseError as excep: + self.dbusController.UpdateDetectFinished(False,[''],excep.header,excep.desc) + except UpdateProgressExit: + pass + except Exception as e: + logging.error(e) + traceback.print_exc() + + def start_update_backend(self,update_mode = InstallBackend.MODE_UPDATE_ALL): + #调用aptdeamon进行update + update_backend = get_backend(self, InstallBackend.ACTION_UPDATE,update_mode) + update_backend.start() + + def start_available(self): + try: + self.refresh_cache() + + self.update_list = UpdateList(self) + + #1、 检查出现安装过程异常重启 出现的话 进行异常修复 + if self.configs_uncover.getWithDefault("SystemStatus", "abnormal_reboot", False) == True: + self.configs_uncover.setValue("SystemStatus","abnormal_reboot",str(False),True) + logging.warning("start fix Abnormal Reboot broken pkgs...") + self.start_fix_broken() + return + + #检查当前系统的状态 是否存在系统为破损状态 导致出现异常 + self._check_system_broken(self.cache) + + #检查优先自我升级 + self._check_self_upgrade(self.cache) + + self.update_list.update_kylin(self.cache,self.install_mode.get_important_data(),self.install_mode.is_openkylin_desktop()) + + if self.cache != None and self.cache.get_changes(): + self.cache.clear() + + except UpdateBaseError as excep: + self.dbusController.UpdateDetectFinished(False,[''],excep.header,excep.desc) + except UpdateProgressExit as excep: + pass + except Exception as e: + logging.error(e) + traceback.print_exc() + + def refresh_cache(self): + try: + #第一次进入 之后update不进入 + if self.cache is None: + self.cache = MyCache(None) + else: + self.cache.open(None) + self.cache._initDepCache() + except AssertionError: + raise UpdateBaseError(ERROR_SOFTWARE_INDEX_RROKEN) + + except SystemError as e: + logging.error(str(e)) + raise UpdateBaseError(ERROR_NOT_INIT_PACKAGESINFIO) + + def _refresh_cache_only(self): + #第一次进入 之后update不进入 + if self.cache is None: + self.cache = MyCache(None) + else: + self.cache.open(None) + self.cache._initDepCache() + + def _check_self_upgrade(self,cache): + need_upgrade = False + self_upgrade = [] + important_list = self.install_mode.get_important_data() + + for pkg_name in [self.BACKEND_PKG_NAME,self.APTD_PKG_NAME,self.FRONTEND_PKG_NAME]: + if pkg_name in cache: + self_pkg = cache[pkg_name] + if self_pkg.is_installed: + if self_pkg.is_upgradable: + logging.info("Check: (%s) will upgrading From %s to %s...",pkg_name,\ + self_pkg.installed.source_version,self_pkg.candidate.source_version) + if pkg_name in important_list: + try: + logging.info("Check: (%s) start upgrading From %s to %s...",pkg_name,\ + self_pkg.installed.source_version,self_pkg.candidate.source_version) + self_pkg.mark_install(True,False,True) + self_upgrade.append(pkg_name) + need_upgrade = True + except SystemError: + self.simulate_mode.thread_install([pkg_name]) + logging.error("Check: mark %s to upgrade Failed...",pkg_name) + raise UpdateBaseError(ERROR_NOT_SELFPKG_DEPENDENCIES) + else: + logging.info("Check: (%s:%s) No need to upgrade and duo to not pust...",pkg_name,self_pkg.installed.source_version) + else: + logging.info("Check: (%s:%s) No need to upgrade...",pkg_name,self_pkg.installed.source_version) + else: + logging.info("Check: (%s) Not to be installed...",pkg_name) + else: + logging.error("Check: (%s) The upgrade package is not in Cache...",pkg_name) + + #config包 + for pkg_name in [self.GROUPS_PKG_NAME]: + if pkg_name in cache: + self_pkg = cache[pkg_name] + if self_pkg.is_installed: + if self_pkg.is_upgradable: + logging.info("Check: groups JSON ConfigPkgs(%s) start upgrading From %s to %s...",pkg_name,\ + self_pkg.installed.source_version,self_pkg.candidate.source_version) + try: + self_pkg.mark_install(True, False, True) + self_upgrade.append(pkg_name) + need_upgrade = True + except SystemError: + logging.error("Check: mark %s to upgrade Failed...",pkg_name) + self.simulate_mode.thread_install([pkg_name]) + raise UpdateBaseError(ERROR_NOT_CONFIGPKG_DEPENDENCIES) + else: + logging.info("Check: ConfigPkgs(%s:%s) No need to upgrade...",pkg_name,self_pkg.installed.source_version) + else: + logging.info("Check: groups JSON ConfigPkgs(%s) start new installing...",pkg_name) + try: + self_pkg.mark_install(True, False, True) + self_upgrade.append(pkg_name) + need_upgrade = True + except SystemError: + logging.error("Check: mark %s to install Failed...",pkg_name) + self.simulate_mode.thread_install([pkg_name]) + raise UpdateBaseError(ERROR_NOT_CONFIGPKG_DEPENDENCIES) + else: + logging.error("Check: groups JSON ConfigPkgs(%s) is not in Cache...",pkg_name) + raise UpdateBaseError(ERROR_NOT_GROUPS_CONFIG) + + if need_upgrade == True: + self.dbusController.UpdateDetectStatusChanged(95,_("Priority Upgrade Package being updated")) + self.start_install(InstallBackend.MODE_INSTALL_SINGLE,True,upgrade_content=self_upgrade) + raise UpdateProgressExit() + + def _check_system_broken(self,cache): + if cache.get_changes(): + cache.clear() + #获取出现破损状态包的数量 + if cache._depcache.broken_count or cache._depcache.del_count > 0 or \ + cache._depcache.inst_count > 0: + #线程获取详细的卸载软件包情况 + self.simulate_mode.start_caculate(["apt-get", "install","-f","--simulate"],thread=True) + else: + logging.info("Check: System Apt Cache for Broken Successfully...") + return + fix_backend = get_backend(self, InstallBackend.ACTION_CHECK_BROKEN) + fix_backend.start() + raise UpdateProgressExit() + + def _setup_dbus(self): + # check if there is another g-a-i already and if not setup one + # listening on dbus + bus = dbus.SystemBus() + try: + bus_name = dbus.service.BusName(UPDATER_DBUS_SERVICE, + bus, + do_not_queue=True) + logging.info("Initiate dbus success ...") + UpdateManagerDbusControllerUtils(self, bus_name) + return UpdateManagerDbusController(self, bus_name) + except dbus.exceptions.NameExistsException: + if self.options.replace is False: + logging.critical("Another daemon is already running") + sys.exit(1) + logging.warning("Replacing already running daemon") + + retry_reboot_times = 0 + the_other_guy = bus.get_object(UPDATER_DBUS_SERVICE, + UPDATER_DBUS_PATH) + the_other_guy.Quit(dbus_interface=UPDATER_DBUS_INTERFACE, + timeout=300) + time.sleep(1) + while True: + retry_reboot_times = retry_reboot_times + 1 + #当重试次数超过5次时退出程序 + if retry_reboot_times > 5: + logging.critical("Reboot backend is Failed...") + sys.exit(1) + try: + bus_name = dbus.service.BusName(UPDATER_DBUS_SERVICE, + bus, + do_not_queue=True) + logging.warning("Replacing already running daemon to Success...") + return UpdateManagerDbusController(self, bus_name) + except dbus.exceptions.NameExistsException: + the_other_guy = bus.get_object(UPDATER_DBUS_SERVICE, + UPDATER_DBUS_PATH) + the_other_guy.Quit(dbus_interface=UPDATER_DBUS_INTERFACE, + timeout=300) + logging.error("Dbus has not withdrawn and retry reboot times:%d...",retry_reboot_times) + time.sleep(1) + + def _setup_dbus_utils(self): + # check if there is another g-a-i already and if not setup one + # listening on dbus + bus = dbus.SystemBus() + try: + bus_name = dbus.service.BusName(UPDATER_DBUS_SERVICE, + bus, + do_not_queue=True) + return UpdateManagerDbusControllerUtils(self, bus_name) + except dbus.exceptions.NameExistsException: + if self.options.replace is False: + logging.critical("Another daemon is already running") + sys.exit(1) + logging.warning("Replacing already running daemon") + + retry_reboot_times = 0 + the_other_guy = bus.get_object(UPDATER_DBUS_SERVICE, + UPDATER_DBUS_PATH) + the_other_guy.Quit(dbus_interface=UPDATER_DBUS_INTERFACE, + timeout=300) + time.sleep(1) + while True: + retry_reboot_times = retry_reboot_times + 1 + #当重试次数超过5次时退出程序 + if retry_reboot_times > 5: + logging.critical("Reboot backend is Failed...") + sys.exit(1) + try: + bus_name = dbus.service.BusName(UPDATER_DBUS_SERVICE, + bus, + do_not_queue=True) + logging.warning("Replacing already running daemon to Success...") + return UpdateManagerDbusController(self, bus_name) + except dbus.exceptions.NameExistsException: + the_other_guy = bus.get_object(UPDATER_DBUS_SERVICE, + UPDATER_DBUS_PATH) + the_other_guy.Quit(dbus_interface=UPDATER_DBUS_INTERFACE, + timeout=300) + logging.error("Dbus has not withdrawn and retry reboot times:%d...",retry_reboot_times) + time.sleep(1) + + def start_back_upgrade(self, pkglist): + try: + install_backend = get_backend(self, InstallBackend.ACTION_BACKGROUND_UPGRADE) + install_backend.start_alone(partial_upgrade_list = pkglist) + except Exception as e: + logging.error(str(e)) + + # 进行本地deb包安装的操作 + # _check_local_dep : 是否查询本地依赖 + # _auto_satisfy : 是否通过网络下载依赖 + def start_deb_install(self, deb_path = "", _check_local_dep = False, _auto_satisfy = False, source = '', sender=None): + header = '' + desc = '' + absolute_path, debname = os.path.split(deb_path) + self.deb_obj = {} + UpdateMsg = {} + try: + if not os.path.isfile(deb_path): + logging.info("No such file or directory: %s.",deb_path) + self.dbusController.InstalldebFinished(False,'No such file or directory .','') + return + # 验签提权 + sender_name = get_proc_from_dbus_name(sender) + caller = get_caller_from_enum(sender_name) + caller_trans = get_source_name_from_enum(sender_name) + if deb_verify(deb_path) != 0: #验签失败,提权 + (status,error_string) = PolicyKit_Authority(caller_trans+_(" requires authentication to install software packages."),sender) + if not status: + self.dbusController.InstalldebFinished(False,error_string,'') + return + self.deb_obj.update({"debname":str(debname)}) + self.deb_obj.update({"old_version":""}) + self.deb_obj.update({"source":str(caller)}) + UpdateMsg.update({"appname":str(self.deb_obj.get("debname","None").split("_")[0])}) + UpdateMsg.update({"source":str(self.deb_obj.get("source","kylin-system-updater"))}) + UpdateMsg.update({"new_version":str(self.deb_obj.get("debname","None").split("_")[1])}) + deb_cache, ins, _isinstall = self._suit_install_mode(deb_path) + if self._is_broken > 0 or not self.cacheSatisfy or self._need_downgrade: + # 走 dpkg 安装流程,说明本地apt环境已经损坏,or dep not satisfied or need downgrade + dep_satisfy, header, desc = self._deb_install(deb_cache, deb_path, _check_local_dep, ins) + if dep_satisfy: + self.dbusController.InstalldebFinished(True, header, desc) + UpdateMsg.update({"status":"success"}) + UpdateMsg.update({"errorCode":" "}) + else: + self.dbusController.InstalldebFinished(False, header, desc) + UpdateMsg.update({"status":"failed"}) + UpdateMsg.update({"errorCode":str(header+", "+desc)}) + #dpkg发送数据 + if self.configs_uncover.getWithDefault("SystemStatus", "upload_installer_log", False) == True: + UpdateMsg.update({"old_version":str(self.deb_obj.get("old_version","None"))}) + self.collector.Upgrade_Process_Msg(InstallBackend.ACTION_INSTALL_DEB, UpdateMsg.copy()) + self.deb_obj = {} + else: + # apt 安装流程 + dep_satisfy, header, desc = self._attempt_depends(deb_cache, deb_path, _check_local_dep,_auto_satisfy, ins) + if dep_satisfy: + install_backend = get_backend(self, InstallBackend.ACTION_INSTALL_DEB) + install_backend.start_alone(partial_upgrade_list = deb_path, _is_install = _auto_satisfy, caller=caller_trans) + else: + self.dbusController.InstalldebFinished(False, header, desc) + except UpdateBaseError as excep: + self.dbusController.InstalldebFinished(False,excep.header,excep.desc) + UpdateMsg.update({"old_version":str(self.deb_obj.get("old_version","None"))}) + UpdateMsg.update({"errorCode":str(excep.header+", "+excep.desc)}) + UpdateMsg.update({"status":"failed"}) + self.collector.Upgrade_Process_Msg(InstallBackend.ACTION_INSTALL_DEB, UpdateMsg.copy()) + except Exception as e: + logging.info(str(e)) + traceback.print_exc() + self.dbusController.InstalldebFinished(False, str(e), desc) + UpdateMsg.update({"old_version":str(self.deb_obj.get("old_version","None"))}) + UpdateMsg.update({"errorCode":str(e)}) + UpdateMsg.update({"status":"failed"}) + self.collector.Upgrade_Process_Msg(InstallBackend.ACTION_INSTALL_DEB, UpdateMsg.copy()) + + #进行删除的操作 + def start_purge_pkgs(self,pkgs_list): + try: + # 是否有破损的包 + deb_cache = Cache() + broken_count = deb_cache._depcache.broken_count + if broken_count > 0: + _success,header,desc = self._dpkg_purge_pkgs(pkgs_list) + if _success == True: + logging.info(header) + self.dbusController.PurgePackagesFinished(_success,'',desc) + else: + self.dbusController.PurgePackagesFinished(_success,header,desc) + else: + purge_backend = get_backend(self, InstallBackend.ACTION_REMOVE_PACKAGES) + purge_backend.start(partial_upgrade_list = pkgs_list) + + deb_cache.close() + except Exception as e: + logging.error(e) + traceback.print_exc() + + def _dpkg_purge_pkgs(self,pkgs_list): + success = False + pkg_list_str = " ".join(pkgs_list) + args = ["dpkg", "-r"] + args.extend([pkg_list_str]) + + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + success = p.returncode == 0 + return success,p.stdout,'' + + # 是否查找本地依赖 + def _attempt_depends(self,deb_cache, deb_path,_check_local_dep,_auto_satisfy, _install): + depends_list = [] + depends_pkg = [] + satisfy_list = [] + depends_count = 0 + _local_satisfy = False + error_string = '' + error_desc = '' + absolute_path, debname = os.path.split(deb_path) + # 依赖不满足的情况 + if len(_install) > 0: + if _check_local_dep: #查找本地 + # 需要查找本地依赖 + if len(_install) > 0: + for pkg in deb_cache: + if pkg.marked_install and pkg.name != str(debname.split("_")[0]): + depends_pkg.append(pkg) + elif pkg.marked_upgrade and pkg.name != str(debname.split("_")[0]): + depends_pkg.append(pkg) + if len(depends_pkg)>0: #查找本地deb包 + depends_list = [debfile for debfile in os.listdir(absolute_path) if debfile.endswith(".deb")] + for depends in depends_pkg: + for debfile in depends_list: + if depends.name in debfile and depends.candidate.version in debfile: + #FIXME:检查depends包的合法性 + depends_count += 1 + satisfy_list.append(debfile) + if depends_count < len(depends_pkg): + #本地依赖不满足 判断源下载 + if _auto_satisfy: + _local_satisfy = True + elif not _auto_satisfy: + _local_satisfy = False + error_string = str(debname.split("_")[0])+_("dependency is not satisfied") + error_desc = ",".join(_install) + logging.error(error_string+ error_desc) + else: + #将应用包与依赖包拷贝至archive目录安装 + try: + if debname not in satisfy_list: + satisfy_list.append(debname) + for satisfy in satisfy_list: + shutil.copy(os.path.join(absolute_path,satisfy),"/var/cache/apt/archives/") + logging.info("move debpkg: %s",satisfy) + _local_satisfy = True + except Exception as e: + logging.info(str(e)) + return _local_satisfy,error_string,error_desc + elif not _check_local_dep and _auto_satisfy: + _local_satisfy = True + if _install: + error_string = str(debname.split("_")[0])+_("dependency is not satisfied will download") + error_desc = ",".join(_install) + logging.error(error_string+error_desc) + return _local_satisfy,error_string,error_desc + elif not _check_local_dep and not _auto_satisfy: + _local_satisfy = False + if _install: + error_string = str(debname.split("_")[0])+_("dependency is not satisfied") + error_desc = ",".join(_install) + logging.error(error_string+error_desc) + return _local_satisfy,error_string,error_desc + # 依赖满足 + else: + _local_satisfy = True + error_string = '' + error_desc='' + return _local_satisfy,error_string,error_desc + + def _deb_install(self, deb_cache, deb_path, _check_local_dep, _install): + depends_list = [] + depends_pkg = [] + satisfy_list = [] + noSatisfyList = [] + depends_count = 0 + error_string = '' + header = '' + desc = '' + absolute_path, debname = os.path.split(deb_path) + # 依赖不满足的情况 + if len(_install) > 0 or len(self.noSatisfyList) > 0: + if _check_local_dep: #查找本地 + # 需要查找本地依赖 + if len(self.noSatisfyList) > 0: + for nS in self.noSatisfyList: + if len(nS[0]) == 1: + noSatisfyList.append(str(nS[0][0])) + else : + noSatisfyList.append(str(nS[0][0])) + if len(_install) > 0: + for pkg in deb_cache: + if pkg.marked_install and pkg.name != str(debname.split("_")[0]): + depends_pkg.append(pkg) + elif pkg.marked_upgrade and pkg.name != str(debname.split("_")[0]): + depends_pkg.append(pkg) + if len(depends_pkg)>0 or len(noSatisfyList)>0: #查找本地deb包 + depends_list = [debfile for debfile in os.listdir(absolute_path) if debfile.endswith(".deb")] + for depends in depends_pkg: + for debfile in depends_list: + if "%3a" in debfile: + debfile=debfile.replace("%3a",":") + if depends.name in debfile and depends.candidate.version in debfile: + depends_count += 1 + satisfy_list.append(debfile) + for depends in noSatisfyList: + for debfile in depends_list: + if "%3a" in debfile: + debfile=debfile.replace("%3a",":") + if depends.split('_')[0] == debfile.split('_')[0] and depends.split('_')[1] == debfile.split('_')[1] and debfile not in satisfy_list: + depends_count += 1 + satisfy_list.append(debfile) + if depends_count < len(noSatisfyList) or depends_count < len(depends_pkg): + #本地依赖不满足 + error_string = str(debname.split("_")[0])+_("dependency is not satisfied")+", ".join(noSatisfyList) + logging.error(error_string) + header = error_string + return False,header, desc + else: + #安装依赖包 + if debname not in satisfy_list: + satisfy_list.append(debname) + for satisfy in satisfy_list: + try: + deb = DebPackage(os.path.join(absolute_path,satisfy)) + iprogress = LogInstallProgress(deb_path) + ret = deb.install(install_progress=iprogress) + if ret != 0: + return False, iprogress.errormsg, desc + except Exception as e: + logging.error(e) + return False, str(e), desc + return True, header, desc + elif not _check_local_dep: + if _install: + error_string = str(debname.split("_")[0])+_("dependency is not satisfied")+":".join(_install) + logging.error(error_string) + header = error_string + return False,header, desc + # 依赖满足 + else: + try: + deb = DebPackage(deb_path) + iprogress = LogInstallProgress(deb_path) + ret = deb.install(install_progress=iprogress) + if ret != 0: + return False, iprogress.errormsg, desc + except Exception as e: + logging.error(e) + return False, str(e), desc + return True, header, desc + + def _suit_install_mode(self, deb_path): + self._is_broken = False + self.cacheSatisfy = False + _is_install = False + absolute_path, debname = os.path.split(deb_path) + # 检查本地破损 + try: + deb_cache = Cache() + logging.info("Install deb_package, check broken") + broken_count = deb_cache._depcache.broken_count + debPackage = DebPackage(deb_path, deb_cache) + debPackage.check(allow_downgrade=True) + logging.info("Install deb_package, required changes") + (install, remove, unauth) = debPackage.required_changes # need in cach + if broken_count > 0: + self._is_broken = True + else : + self._is_broken = False + if debname.split("_")[0] in deb_cache: + pkg = deb_cache[debname.split("_")[0]] + if pkg.is_installed: + self.deb_obj.update({"old_version":str(pkg.installed.version)}) + except apt_pkg.Error as e: + logging.error(str(e)) + if "E:read" in str(e): + raise UpdateBaseError(ERROR_READ_LOCAL_DEB) + else: + raise UpdateBaseError(ERROR_LOCAL_DEB_FORMAT) + except Exception as e: + logging.error(str(e)) + raise UpdateBaseError(ERROR_INSTALL_DEB_BASE) + self._need_downgrade = False + # 不满足的依赖列表 + depends = debPackage.depends + self.noSatisfyList = self._gen_noSatisfyList(depends, deb_cache) + _list = [] + # cache是否满足 + if len(install) == 0 and len(self.noSatisfyList) == 0: + self.cacheSatisfy = True + else: + for ns in self.noSatisfyList: + for or_group in ns: + for pkg in install: + if pkg == or_group[0]: + _list.append(ns) + if len(_list) == len(self.noSatisfyList): + self.cacheSatisfy = True + else: + self.cacheSatisfy = False + logging.info("Cache satisfy is %r.",self.cacheSatisfy) + return deb_cache, install, _is_install + + def _gen_noSatisfyList(self, depends, deb_cache): + _noSatisfyList = [] + _group_satify = False + providers = [] + for or_group in depends: + for deb_info in or_group: + debname,ver,oper = deb_info + if ":" in debname: + debname=debname.split(":")[0] + try: + if debname not in deb_cache: + if deb_cache.is_virtual_package(debname): + logging.warning("The cache has no package named '%s', this is a virtual package...", debname) + providers = deb_cache.get_providing_packages(debname) + # hear! + if len(providers) < 1: + logging.error("Can not found depend %s.", debname) + continue + depname = providers[0].name + pkg = deb_cache[depname] + cand = deb_cache._depcache.get_candidate_ver(pkg._pkg) + if not cand: + _group_satify = False + if not apt_pkg.check_dep(cand.ver_str, oper, ver): + _group_satify = False + else: + _group_satify = True + else: + pkg = deb_cache[debname] + if (apt_pkg.check_dep(pkg.candidate.source_version, oper, ver)) or (pkg.installed and apt_pkg.check_dep(pkg.installed.version, oper, ver)) or (not pkg.installed and apt_pkg.check_dep(pkg.candidate.version, oper, ver)) and _group_satify == False: + # if not apt_pkg.check_dep(cand, oper, ver): #no candidate + _group_satify = True + except Exception as e: + logging.error(str(e)) + if _group_satify == False and or_group not in _noSatisfyList: + _noSatisfyList.append(or_group) + _group_satify = False + return _noSatisfyList + +class MakeSourceInit(): + DIR_MRDIA = "/media/" + MOUNT_SQUASHFS_PATH = "/media/kylin/kylin-test-upgrade/upgrade-pool/" + def __init__(self): + self.is_disc = False + self.is_mounted = False + + def mount_squashfs(self,mount_source): + args = ["mount", "-o","loop",mount_source,self.MOUNT_SQUASHFS_PATH] + + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + logging.info(str(p.stdout)) + if p.returncode == 0: + self.is_mounted = True + return True,' ' + elif p.returncode == 1: + self.is_mounted = True + return True,' ' + else: + self.is_mounted = False + return False,str(p.stdout) + + def check_mount(self): + if self.is_mounted == True: + args = ["umount",self.MOUNT_SQUASHFS_PATH] + logging.info("Will be to umount the offlinesource...") + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + logging.info(str(p.stdout)) + if p.returncode == 0: + self.is_mounted = False + return True + else: + return False + + #判断是否为光盘源 + #光盘源格式 deb file:///home/someone/packs/ + def check_source(self): + logging.info("Check: Whether to use CD-ROM source updates Successfully...") + if os.path.exists(self.DIR_MRDIA): + for first_dir in os.listdir(self.DIR_MRDIA): + #到/media/x + check_dir_one = self.DIR_MRDIA + first_dir + "/" + + if not os.path.isdir(check_dir_one): + continue + + for second_dir in os.listdir(check_dir_one): + #到/media/x/test + check_dir_two = check_dir_one + second_dir + "/" + + if not os.path.isdir(check_dir_two): + continue + check_file = check_dir_two + "ss.map" + + logging.info("Check: CD-ROM source File(%s)",check_file) + if os.path.exists(check_file): + self.is_disc = True + logging.info("Use to CD-Source and Turn off NetworkCheck and CloseFiter...") + return + #没有返回存在光盘源就说明不存在 + self.is_disc = False + return + else: + self.is_disc = False + return + +class AptP2pConfigManager(): + APT_P2P_FILE = "/etc/apt-p2p/apt-p2p.conf" + HEADER_DSC = "apt-p2p config(/etc/apt-p2p/apt-p2p.conf) is not exists..." + def __init__(self): + if os.path.exists(self.APT_P2P_FILE): + self.p2pConfigs = UpgradeConfig(datadir = "/etc/apt-p2p/", name = "apt-p2p.conf") + else: + self.p2pConfigs = None + + def get_bootstrap(self): + if self.p2pConfigs == None: + return self.HEADER_DSC + + return self.p2pConfigs.getWithDefault("apt_p2p_Khashmir", "BOOTSTRAP", "Failed") + + def set_bootstrap(self,value): + if self.p2pConfigs == None: + return self.HEADER_DSC + self.p2pConfigs.setValue("apt_p2p_Khashmir","BOOTSTRAP",str(value)) + +class SimulateTerminal(): + ZH_UNMET_DEPENDENCIES = '下列软件包有未满足的依赖关系:' + EN_UNMET_DEPENDENCIES = 'The following packages have unmet dependencies:' + def __init__(self): + self.update_args = ["apt-get", "update"] + self.install_args = ["apt-get", "install","--simulate"] + + def _sub_emulate(self,args): + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + logging.info(str(p.stdout)) + return p.stdout + + def start_caculate(self,args = [],thread = False): + tmp_str = '' + if thread is True: + threading_emulate = threading.Thread(target=self._sub_emulate,args=(args,)) + threading_emulate.start() + else: + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + tmp_str = p.stdout + return tmp_str + + def _emulate_install(self,pkgs): + args = ["apt-get", "install","--simulate"] + args = args + pkgs + + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + logging.info(str(p.stdout)) + return p.stdout + + def emulate_update(self): + args = ["apt-get", "update"] + + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + logging.info(str(p.stdout)) + return p.stdout + + def thread_install(self,pkgs): + threading_emulate = threading.Thread(target=self.dependencies_broken,args=(pkgs,True)) + threading_emulate.start() + + def thread_update(self): + threading_emulate = threading.Thread(target=self.emulate_update) + threading_emulate.start() + + def dependencies_broken(self,pkgs,thread=False): + terminal_msg = self._emulate_install(pkgs) + + if len(terminal_msg) > 500: + terminal_msg = '' + if self.ZH_UNMET_DEPENDENCIES in terminal_msg: + terminal_msg = '\n' + self.ZH_UNMET_DEPENDENCIES + terminal_msg.split(self.ZH_UNMET_DEPENDENCIES)[1] + elif self.EN_UNMET_DEPENDENCIES in terminal_msg: + terminal_msg = '\n' + self.EN_UNMET_DEPENDENCIES + terminal_msg.split(self.EN_UNMET_DEPENDENCIES)[1] + else: + terminal_msg = '' + + if thread == True: + logging.info(terminal_msg) + + return terminal_msg + +class UpdateInstallMode(): + OPENKYLIN_DISTTRIBUTOR = "Openkylin" + KYLIN_DISTTRIBUTOR = "Kylin" + SYSTEM_UPDATE_GROUPS = "kylin-update-desktop-system" + DIR_MRDIA = "/media/" + MOUNT_SQUASHFS_PATH = "/media/kylin/kylin-test-upgrade/upgrade-pool/" + IMPORTANT_LIST_PATH = '/var/lib/kylin-software-properties/template/important.list' + + def __init__(self,parent): + self.parent = parent + self.is_disc = False + self.is_mounted = False + self.dist = get_dist() + + if self.shutdown_mode() == True: + logging.info("Initialize Shutdown Install Model...") + self.bus = dbus.SystemBus() + self.logind_proxy = self.bus.get_object('org.freedesktop.login1', '/org/freedesktop/login1') + self._prepare_shutdown_model() + self.tmp_content = [] + self.inhibit_lock = None + + def is_openkylin_desktop(self): + return self.dist == self.OPENKYLIN_DISTTRIBUTOR + # return True + + def check_network(self): + if self.parent.options.no_check_network is False and self.is_disc == False: + return True + else: + return False + + def update_important(self): + if self.parent.options.no_update_source is False and self.is_openkylin_desktop() == False: + return True + else: + return False + + def get_important_data(self): + important_list = [] + if self.is_openkylin_desktop() == False: + with open(self.IMPORTANT_LIST_PATH, 'r') as f: + data = f.read() + important_list = data.split() + else: + important_list = [self.SYSTEM_UPDATE_GROUPS,self.parent.BACKEND_PKG_NAME,self.parent.APTD_PKG_NAME,self.parent.FRONTEND_PKG_NAME] + return important_list + + def check_filter(self): + if self.parent.options.close_filter == False and self.is_disc == False and self.is_openkylin_desktop() == False: + return True + else: + return False + + def _plymouth_splash(self): + if os.path.exists("/bin/plymouth"): + logging.debug("Running plymouth --splash") + subprocess.run(["/sbin/plymouthd", "--mode=shutdown","--attach-to-session"]) + subprocess.Popen(["/bin/plymouth", "show-splash","--wait"]) + subprocess.call(["/bin/plymouth","system-update","--progress=0"]) + + def _inhibit_sleep(self): + """ + 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) + + #首先设置systemd默认延长时间为1800 + getter_interface = dbus.Interface( + self.logind_proxy, + dbus_interface='org.freedesktop.login1.Manager') + ret = getter_interface.SetInhibitDelayMaxSec(1800) + + var, fdlist = connection.call_with_unix_fd_list_sync( + 'org.freedesktop.login1', '/org/freedesktop/login1', + 'org.freedesktop.login1.Manager', 'Inhibit', + GLib.Variant('(ssss)', + ('shutdown', + 'Kylin System Updater', 'Updating System', + 'delay')), + None, 0, -1, None, None) + inhibitor = Gio.UnixInputStream(fd=fdlist.steal_fds()[var[0]]) + + return inhibitor + except Exception as e: + logging.error(e) + return False + + def _prompt_in_boot(self): + #关机安装完成之后开机时进行提醒 + popconfigs = UpgradeConfig(datadir = "/opt/apt_result/", name = "ota_result") + popconfigs.setValue("OTA","status","success") + popconfigs.setValue("OTA","upgrade","1") + + def _start_install_shutdown(self): + if self.manual_install() == True: + self._plymouth_splash() + install_backend = get_backend(self.parent, InstallBackend.ACTION_INSTALL_SHUTDOWN) + install_backend.start() + elif self.inhibit_lock != None: + self.inhibit_lock.close() + logging.info("No packages to be installed and Releasing the shutdown lock...") + + def _prepare_shutdown_model(self): + def prepare_for_shutdown_handler(active): + """ Handle PrepareForShutdown() """ + if not active: + logging.warning("PrepareForShutdown(false) received, " + "this should not happen") + + logging.info("Prepare For Shutdown arrived, starting final iterations....") + + self._start_install_shutdown() + + self.logind_proxy.connect_to_signal( + "PrepareForShutdown", prepare_for_shutdown_handler) + + def auto_install(self): + return self.parent.configs_uncover.getWithDefault("InstallMode", "auto_install", False) + + def manual_install(self): + return self.parent.configs_uncover.getWithDefault("InstallMode", "manual_install", False) + + def shutdown_mode(self): + return self.parent.configs_uncover.getWithDefault("InstallMode", "shutdown_install", False) + + def set_shutdown_install(self,status=False): + self.parent.configs_uncover.setValue("InstallMode","manual_install",str(status)) + + def reset_shutdown_mode(self): + if self.shutdown_mode() == True: + self.tmp_content = [] + self.parent.configs_uncover.setValue("InstallMode","manual_install",str(False)) + + #释放锁 更新完成时重新那锁 + if self.inhibit_lock != None: + self.inhibit_lock.close() + self.inhibit_lock = None + + def install_finished(self): + self.parent.configs_uncover.setValue("InstallMode","manual_install",str(False)) + self.parent.configs_uncover.setValue("InstallMode","auto_install",str(False)) + + self._prompt_in_boot() + + def check_install_required(self): + if self.shutdown_model() == True: + if self.manual_install() == True: + logging.info("Now need to shutdown install in manual install model...") + return 1 + elif self.auto_install() == True: + logging.info("Now need to shutdown install in auto install model...") + return 2 + else: + return 0 + else: + logging.info("No need to shutdown install in the normal model...") + return 0 + + def get_inhibit_lock(self): + #只要是关机更新模式就监听抑制关机 + if self.inhibit_lock == None: + self.inhibit_lock = self._inhibit_sleep() + if self.inhibit_lock == None: + logging.error("Prepare inhibit lock failed...") + + #判断是否为光盘源 + #光盘源格式 deb file:///home/someone/packs/ + def check_source(self): + logging.info("Check: Whether to use CD-ROM source updates Successfully...") + if os.path.exists(self.DIR_MRDIA): + for first_dir in os.listdir(self.DIR_MRDIA): + #到/media/x + check_dir_one = self.DIR_MRDIA + first_dir + "/" + + if not os.path.isdir(check_dir_one): + continue + + for second_dir in os.listdir(check_dir_one): + #到/media/x/test + check_dir_two = check_dir_one + second_dir + "/" + + if not os.path.isdir(check_dir_two): + continue + check_file = check_dir_two + "ss.map" + + logging.info("Check: CD-ROM source File(%s)",check_file) + if os.path.exists(check_file): + self.is_disc = True + logging.info("Use to CD-Source and Turn off NetworkCheck and CloseFiter...") + return + #没有返回存在光盘源就说明不存在 + self.is_disc = False + return + else: + self.is_disc = False + return + + # #当检查失败时 再切换到ping再进行一次检查 + # def mount_squashfs(self,mount_source): + # args = ["mount", "-o","loop",mount_source,self.MOUNT_SQUASHFS_PATH] + + # p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + # logging.info(str(p.stdout)) + # if p.returncode == 0: + # self.is_mounted = True + # return True,' ' + # elif p.returncode == 1: + # self.is_mounted = True + # return True,' ' + # else: + # self.is_mounted = False + # return False,str(p.stdout) + + # def check_mount(self): + # if self.is_mounted == True: + # args = ["umount",self.MOUNT_SQUASHFS_PATH] + # logging.info("Will be to umount the offlinesource...") + # p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + # logging.info(str(p.stdout)) + # if p.returncode == 0: + # self.is_mounted = False + # return True + # else: + # return False \ No newline at end of file diff --git a/backend/SystemUpdater/UpdateManagerDbus.py b/backend/SystemUpdater/UpdateManagerDbus.py new file mode 100755 index 0000000..beba797 --- /dev/null +++ b/backend/SystemUpdater/UpdateManagerDbus.py @@ -0,0 +1,1278 @@ +#!/usr/bin/python3 +import os +import dbus +import time +import dbus.service +import logging + +import apt_pkg +from gettext import gettext as _ +from .backend import InstallBackend +from .Core.loop import mainloop +import SystemUpdater.Core.enums as enums +from .Core.errors import * +from .Core.utils import humanize_size +from SystemUpdater.Core.utils import get_proc_from_dbus_name,PolicyKit_Authority +from SystemUpdater.Core.UpdaterConfigParser import UpgradeConfig +import locale +from xml.etree import ElementTree +from .Core.MyCache import MyCache +from importlib import reload + +UPDATER_DBUS_INTERFACE = 'com.kylin.systemupgrade.interface' +UPDATER_DBUS_PATH = '/com/kylin/systemupgrade' +UPDATER_DBUS_SERVICE = 'com.kylin.systemupgrade' +RUN_UNATTENDED_UPGRADE = '/var/run/unattended-upgrades.pid' +SYSTEM_VERSION = '/etc/kylin-version/kylin-system-version.conf' +UPDATER_DBUS_PATH_UTILS = '/com/kylin/systemupgrade/utils' + +#颜色设置 +COLORLOG_SUFFIX = "\033[0m" + +# Define some foreground colors +BLACK = 30 +RED = 31 +GREEN = 32 +YELLOW = 33 +BLUE = 34 +MAGENTA = 35 +CYAN = 36 +WHITE = 37 + +#字体颜色 +FRONT_COLOR_SEQ = "\033[1;%dm" +#背景颜色 +BACK_COLOR_SEQ = "\033[%d;1m" + +COLORLOG_PREFIX = FRONT_COLOR_SEQ % GREEN +COLORMETHOR_PREFIX = FRONT_COLOR_SEQ % CYAN + +UU_UPGRADE_MODE_TIMING = 0 +UU_UPGRADE_MODE_BEFORE_SHUTDOWN = 1 + +#dbus 建立 +class UpdateManagerDbusController(dbus.service.Object): + """ this is a helper to provide the UpdateManagerIFace """ + + RETURN_SUCCESS_CODE = 0 + RETURN_SUCCESS_DESC = "" + + RETURN_UNKNOWN_CODE = -1 + RETURN_UNKNOWN_DESC = "" + + RETURN_BUSY_STATE = 1 + RETURN_BUSY_DESC = "In the process of updating or Upgrading..." + + + def __init__(self, parent, bus_name, + object_path=UPDATER_DBUS_PATH): + dbus.service.Object.__init__(self, bus_name, object_path) + self.parent = parent + self.bus = dbus.SystemBus() + + self.transaction = None + + + def _update_important_reply(self,retval): + if bool(retval) == False: + self.UpdateDetectFinished(False,[''],enums.get_error_string_from_enum(enums.ERROR_UPDATE_SOURCE_FAILED),\ + enums.get_error_description_from_enum(enums.ERROR_UPDATE_SOURCE_FAILED)) + else: + self.parent.start_update_backend() + + def _update_important_error(self,retval): + logging.error(str(retval)) + self.UpdateDetectFinished(False,[''],enums.get_error_string_from_enum(enums.ERROR_UPDATE_SOURCE_FAILED),\ + enums.get_error_description_from_enum(enums.ERROR_UPDATE_SOURCE_FAILED)) + + #更新important.list的本次升级的列表 + def on_update_important_list(self): + self.UpdateDetectStatusChanged(10,_("Updating Source Template")) + obj = self.bus.get_object('com.kylin.software.properties', '/com/kylin/software/properties') + interface = dbus.Interface(obj, dbus_interface='com.kylin.software.properties.interface') + interface.updateSourceTemplate(timeout=20,reply_handler=self._update_important_reply,error_handler=self._update_important_error) + + def is_reboot_required(self): + """If a reboot is required to get all changes into effect.""" + return os.path.exists(os.path.join(apt_pkg.config.find_dir("Dir"), + "var/run/reboot-required")) + + def is_logout_required(self): + """If a logout is required to get all changes into effect.""" + return os.path.exists(os.path.join(apt_pkg.config.find_dir("Dir"), + "var/run/logout-required")) + + #重启aptdeamon后台服务 + def make_aptdeamon_restart(self): + try: + obj = self.bus.get_object('org.debian.apt', '/org/debian/apt') + interface = dbus.Interface(obj, dbus_interface='org.debian.apt') + logging.info("Now start to restart Aptdeamon...") + interface.Quit() + except Exception as e: + logging.error(str(e)) + + #设置aptdeamon的环境变量 + def set_aptdeamon_environ(self,key,value): + try: + logging.info("Set aptdeaom environment variables %s = %s...",key,value) + obj = self.bus.get_object('org.debian.apt', '/org/debian/apt') + interface = dbus.Interface(obj, dbus_interface='org.debian.apt') + retval = interface.SetEnviron(key,value) + return retval + except Exception as e: + logging.error(str(e)) + if key == "init" and value == "config": + self.make_aptdeamon_restart() + time.sleep(0.5) + return False + + def check_connectivity(self): + try: + self.UpdateDetectStatusChanged(5,_("Checking network connection")) + obj = self.bus.get_object("org.freedesktop.NetworkManager","/org/freedesktop/NetworkManager") + interface = dbus.Interface(obj, "org.freedesktop.NetworkManager") + retval = interface.CheckConnectivity(timeout=0.5) + except Exception: + retval = 4 + #1 表示没有网卡可以使用 + if retval == 1: + raise UpdateBaseError(enums.ERROR_NETWORK_FAILED) + + def _check_prohibit_user(self, sender_name): + prohibit_list = ["dbus-send","gdbus"] + if sender_name in prohibit_list: + raise dbus.exceptions.DBusException("ERROR: You are not allowed to perform this action.") + + @dbus.service.method(UPDATER_DBUS_INTERFACE, + in_signature="", out_signature="", + sender_keyword="caller_name") + def Quit(self, caller_name): + """Request a shutdown of the daemon.""" + #如果在下载就请求 取消 + self.CancelDownload() + logging.info("Quitting was requested") + logging.debug("Quitting main loop...") + mainloop.quit() + logging.debug("Exit") + + #检查是否需要安装或者重启安装的请求 + @dbus.service.method(UPDATER_DBUS_INTERFACE,out_signature='i') + def CheckInstallRequired(self): + try: + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' CheckInstallRequired ...') + return self.parent.install_mode.check_install_required() + except Exception as e: + logging.error(str(e)) + return 0 + + #set config value + @dbus.service.method(UPDATER_DBUS_INTERFACE,in_signature='sss',out_signature='b') + def SetConfigValue(self,section, option, value): + try: + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' SetConfigValue ...') + if self.parent.configs_uncover.has_section(str(section)) and self.parent.configs_uncover.has_option(str(section),str(option)): + if self.parent.configs_uncover.setValue(str(section), str(option),str(value)) == True: + return True + else: + return False + elif self.parent.sqlite3_server.ucconfigs.has_section(str(section)) and self.parent.sqlite3_server.ucconfigs.has_option(str(section),str(option)): + if self.parent.sqlite3_server.ucconfigs.setValue(str(section), str(option),str(value)) == True: + return True + else: + return False + except Exception as e: + logging.error(e) + return False + + #get config value + @dbus.service.method(UPDATER_DBUS_INTERFACE,in_signature='ss',out_signature='bs') + def GetConfigValue(self,section, option): + try: + if self.parent.configs_cover.has_section(str(section)) and self.parent.configs_cover.has_option(str(section),str(option)): + value = str(self.parent.configs_cover.get(str(section), str(option))) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+" GetConfigValue section:%s option:%s value:%s ...",section,option,value) + return True,value + if self.parent.configs_uncover.has_section(str(section)) and self.parent.configs_uncover.has_option(str(section),str(option)): + value = str(self.parent.configs_uncover.get(str(section), str(option))) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+" GetConfigValue section:%s option:%s value:%s ...",section,option,value) + return True,value + elif self.parent.sqlite3_server.ucconfigs.has_section(str(section)) and self.parent.sqlite3_server.ucconfigs.has_option(str(section),str(option)): + value = str(self.parent.sqlite3_server.ucconfigs.get(str(section), str(option))) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+" GetConfigValue section:%s option:%s value:%s ...",section,option,value) + return True,value + else: + logging.warning("Warning: Can't found section:%s option:%s ... ",section, option) + except Exception as e: + logging.error("Error: GetConfigValue section:%s option:%s, %s.",section, option, e) + return False,'' + + #Remove all downloaded files. + # @dbus.service.method(UPDATER_DBUS_INTERFACE,out_signature='b') + # def Clean(self): + # try: + # #处于更新和升级中的话 不进行更新 + # if self.parent.now_working != InstallBackend.ACTION_DEFUALT_STATUS: + # logging.warning('Clean In the process of updating or Upgrading...') + # return False,'In the process of updating or Upgrading...' + # else: + # logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' Clean ...') + # self.parent.start_clean() + # return True + # except Exception: + # return False + + #获取后端现在的状态 + @dbus.service.method(UPDATER_DBUS_INTERFACE,in_signature='s',out_signature='i') + def GetBackendStatus(self,user_lang): + try: + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' GetBackendStatus and user_lang = %s ...',str(user_lang)) + #当传入为空时 直接返回 + if str(user_lang) == '': + logging.info("The incoming language is null...") + else: + (input_lang, encoding) = locale._parse_localename(str(user_lang)) + (local_lang, encoding) = locale._parse_localename(str(os.environ["LANG"])) + (local_language, encoding) = locale._parse_localename(str(os.environ["LANGUAGE"])) + + #合法性的校验 + if input_lang == None or input_lang == '': + logging.info("The incoming language is Illegal...") + return self.parent.now_working + + #处于更新和升级中的话 不进行更新 + if input_lang != local_language or input_lang != local_lang: + logging.warning("LANG: switched %s to %s",os.environ["LANG"],user_lang) + logging.warning("LANGUAGE: switched %s to %s",os.environ["LANGUAGE"],user_lang) + os.environ["LANG"] = user_lang + os.environ["LANGUAGE"] = user_lang + self.parent.aptd_lang_switch = True + reload(enums) + + return self.parent.now_working + except Exception as e: + logging.error(str(e)) + return self.parent.now_working + + #apt install -f + @dbus.service.method(UPDATER_DBUS_INTERFACE,out_signature='is') + def FixBrokenDepends(self): + try: + #处于更新和升级中的话 不进行更新 + if self.parent.now_working != InstallBackend.ACTION_DEFUALT_STATUS: + logging.warning('FixBrokenDepends In the process of updating or Upgrading...') + return self.RETURN_BUSY_STATE,self.RETURN_BUSY_DESC + else: + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' FixBrokenDepends ...') + self.parent.start_fix_broken() + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + except Exception as e: + logging.error(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + + #dpkg configure -a + @dbus.service.method(UPDATER_DBUS_INTERFACE,out_signature='is') + def FixIncompleteInstall(self): + try: + #处于更新和升级中的话 不进行更新 + if self.parent.now_working != InstallBackend.ACTION_DEFUALT_STATUS: + logging.warning('FixIncompleteInstall In the process of updating or Upgrading...') + return self.RETURN_BUSY_STATE,self.RETURN_BUSY_DESC + else: + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' FixIncompleteInstall ...') + self.parent.start_fix_incomplete() + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + except Exception as e: + logging.error(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + + #更新的dbus + @dbus.service.method(UPDATER_DBUS_INTERFACE,out_signature='is',sender_keyword='sender') + def UpdateDetect(self,sender=None): + try: + #处于更新和升级中的话 不进行更新 + if self.parent.now_working != InstallBackend.ACTION_DEFUALT_STATUS: + logging.warning('UpdateDetect In the process of Updating or Upgrading...') + return self.RETURN_BUSY_STATE,self.RETURN_BUSY_DESC + else: + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' UpdateDetect sender:%s...',sender_name) + self._check_prohibit_user(sender_name) + self.parent.start_update() + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + except Exception as e: + logging.error(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + + #部分升级 + @dbus.service.method(UPDATER_DBUS_INTERFACE,in_signature='bas',out_signature='is',sender_keyword='sender') + def DistUpgradePartial(self,_is_install,_partial_upgrade_list,sender=None): + try: + not_resolver = bool(_is_install) + if not self.parent.update_list: + logging.warning('Perform \"UpdateDetect\" first') + return self.RETURN_BUSY_STATE,self.RETURN_BUSY_DESC + #处于更新和升级中的话 不进行升级 + if self.parent.now_working != InstallBackend.ACTION_DEFUALT_STATUS: + logging.warning('DistUpgradePartial In the process of updating or Upgrading...') + return self.RETURN_BUSY_STATE,self.RETURN_BUSY_DESC + else: + sender_name = get_proc_from_dbus_name(sender) + partial_upgrade_list = [str(i) for i in _partial_upgrade_list] + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' DistUpgradePartial sender:%s and not_resolver:%r, partial_upgrade_list:%s. ',sender_name,not_resolver,",".join(partial_upgrade_list)) + self._check_prohibit_user(sender_name) + local_upgrade_groups = self.parent.update_list.upgrade_meta.upgrade_groups + local_single_pkgs = self.parent.update_list.upgrade_meta.single_pkgs + + new_upgrade_list = list(set(partial_upgrade_list) & set(local_upgrade_groups + local_single_pkgs)) + + if new_upgrade_list: + self.parent.start_install(InstallBackend.MODE_INSTALL_PARTIAL,not_resolver,partial_upgrade_list) + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + else: + logging.warning('input upgrade list(%s) not in local upgrade_list(%s)',partial_upgrade_list,local_upgrade_groups+local_single_pkgs) + return self.RETURN_BUSY_STATE,self.RETURN_BUSY_DESC + except Exception as e: + logging.info(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + + #全部升级 + @dbus.service.method(UPDATER_DBUS_INTERFACE,in_signature='b',out_signature='is',sender_keyword='sender') + def DistUpgradeAll(self,_is_install,sender=None): + try: + not_resolver = bool(_is_install) + if not self.parent.update_list: + logging.warning('Perform \"UpdateDetect\" first') + return self.RETURN_BUSY_STATE,self.RETURN_BUSY_DESC + #处于更新和升级中的话 不进行升级 + if self.parent.now_working != InstallBackend.ACTION_DEFUALT_STATUS: + logging.warning('DistUpgradeAll In the process of updating or Upgrading...') + return self.RETURN_BUSY_STATE,self.RETURN_BUSY_DESC + else: + sender_name = get_proc_from_dbus_name(sender) + self._check_prohibit_user(sender_name) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' DistUpgradeAll and not_resolver:%r...',not_resolver) + self.parent.start_install(InstallBackend.MODE_INSTALL_ALL,not_resolver) + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + except Exception as e: + logging.info(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + + # 全盘升级 也就是 apt dist-upgrade 升级的方式 + @dbus.service.method(UPDATER_DBUS_INTERFACE,in_signature='b',out_signature='is',sender_keyword='sender') + def DistUpgradeSystem(self,_is_install,sender=None): + try: + not_resolver = bool(_is_install) + #处于更新和升级中的话 不进行升级 + if self.parent.now_working != InstallBackend.ACTION_DEFUALT_STATUS: + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' DistUpgradeSystem and not_resolver:%r...',not_resolver) + logging.warning('DistUpgradeSystem In the process of updating or Upgrading...') + return self.RETURN_BUSY_STATE,self.RETURN_BUSY_DESC + else: + sender_name = get_proc_from_dbus_name(sender) + self._check_prohibit_user(sender_name) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' DistUpgradeSystem and not_resolver:%r...',not_resolver) + self.parent.start_install(InstallBackend.MODE_INSTALL_SYSTEM,not_resolver) + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + except Exception as e: + logging.error(False, str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + + #卸载包 + @dbus.service.method(UPDATER_DBUS_INTERFACE,in_signature='asss',out_signature='is',sender_keyword='sender') + def PurgePackages(self,_purge_list,cur_user,user_lang = '', sender=None): + try: + #当传入为空时 直接返回 + if str(user_lang) == '': + logging.info("The incoming language is null...") + else: + (input_lang, encoding) = locale._parse_localename(str(user_lang)) + (local_lang, encoding) = locale._parse_localename(str(os.environ["LANG"])) + (local_language, encoding) = locale._parse_localename(str(os.environ["LANGUAGE"])) + + #合法性的校验 + if input_lang == None or input_lang == '': + logging.info("The incoming language is Illegal...") + return self.RETURN_UNKNOWN_CODE,"The incoming language is Illegal..." + + #处于更新和升级中的话 不进行更新 + if input_lang != local_language or input_lang != local_lang: + logging.warning("LANG: switched %s to %s",os.environ["LANG"],user_lang) + logging.warning("LANGUAGE: switched %s to %s",os.environ["LANGUAGE"],user_lang) + os.environ["LANG"] = user_lang + os.environ["LANGUAGE"] = user_lang + self.parent.aptd_lang_switch = True + reload(enums) + + purge_list = [str(pkg) for pkg in _purge_list] + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' DistPurgePackages Sender:%s and purge list is:%s...',sender_name, purge_list) + (status, details) = PolicyKit_Authority(get_source_name_from_enum(sender_name)+_(" requires authentication to uninstall software packages."), sender) + if not status: + self.PurgePackagesFinished(False,details,'') + return self.RETURN_UNKNOWN_CODE,details + + #目前只有360使用这个环境变量 当其他包也使用时 可以将这个权限放开 + if "360epp" in purge_list: + #需要对aptdeamon加这两个环境变量 才可以提示弹窗 + self.set_aptdeamon_environ("XAUTHORITY","/home/"+str(cur_user)+"/.Xauthority") + self.set_aptdeamon_environ("DISPLAY",":0") + + # 处于更新和升级中的话 不进行升级 + if self.parent.now_working != InstallBackend.ACTION_DEFUALT_STATUS: + self.PurgePackagesFinished(False,_("Other tasks are being updated and upgraded, please uninstall them later."),'') + logging.warning('PurgePackages In the process of updating or Upgrading...') + return self.RETURN_BUSY_STATE,self.RETURN_BUSY_DESC + else: + self._check_prohibit_user(sender_name) + + self.parent.start_purge_pkgs(purge_list) + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + except Exception as e: + logging.error(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + + # 取消transaction + @dbus.service.method(UPDATER_DBUS_INTERFACE, out_signature='b') + def CancelDownload(self): + status = False + try: + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' CancelDownload...') + + #空时直接返回 + if self.transaction == None: + logging.info("transaction is none") + return False + + if self.transaction.cancellable == True: + self.transaction.cancel() + status = True + logging.info("dbus-mothod cancel task Success") + else: + status = False + logging.info("cancel task Failed") + + except Exception as e: + logging.error("cancel task Failed" + str(e)) + return status + return status + + + # 安装本地deb包 + @dbus.service.method(UPDATER_DBUS_INTERFACE,in_signature='ssbbs',out_signature='is',sender_keyword='sender') + def InstallDebFile(self,source = "unKnown", path = "", _check_local_dep = False, _auto_satisfy = False, user_lang = '', sender=None): + try: + #当传入为空时 直接返回 + if str(user_lang) == '': + logging.info("The incoming language is null...") + else: + (input_lang, encoding) = locale._parse_localename(str(user_lang)) + (local_lang, encoding) = locale._parse_localename(str(os.environ["LANG"])) + (local_language, encoding) = locale._parse_localename(str(os.environ["LANGUAGE"])) + + #合法性的校验 + if input_lang == None or input_lang == '': + logging.info("The incoming language is Illegal...") + return self.RETURN_UNKNOWN_CODE,"The incoming language is Illegal..." + + #处于更新和升级中的话 不进行更新 + if input_lang != local_language or input_lang != local_lang: + logging.warning("LANG: switched %s to %s",os.environ["LANG"],user_lang) + logging.warning("LANGUAGE: switched %s to %s",os.environ["LANGUAGE"],user_lang) + os.environ["LANG"] = user_lang + os.environ["LANGUAGE"] = user_lang + self.parent.aptd_lang_switch = True + reload(enums) + sender_name = get_proc_from_dbus_name(sender) + self._check_prohibit_user(sender_name) + + check_local_dep = bool(_check_local_dep) + auto_satisfy = bool(_auto_satisfy) + deb_path = str(path) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' InstallDebFile and check_local_dep:%r, auto_satisfy:%r, current_lang:%s , InstallDebFile sender: %s .',\ + check_local_dep,auto_satisfy,user_lang,sender_name) + logging.info("Will install: %s.",path) + self.parent.start_deb_install(deb_path, _check_local_dep, _auto_satisfy, source, sender) + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + except Exception as e: + logging.error(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + + # commitpackages + @dbus.service.method(UPDATER_DBUS_INTERFACE,in_signature='as',out_signature='is',sender_keyword='sender') + def InstallPackages(self, pkg_list = [], sender=None): + try: + sender_name = get_proc_from_dbus_name(sender) + self._check_prohibit_user(sender_name) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' InstallPackages : [%s], InstallPackages sender: %s .', ", ".join(list(pkg_list)), sender_name) + self.parent.start_back_upgrade(pkg_list) + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + except Exception as e: + logging.error(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + + #更新的dbus + @dbus.service.method(UPDATER_DBUS_INTERFACE,out_signature='is',sender_keyword='sender') + def UpdateCache(self,sender=None): + try: + #处于更新和升级中的话 不进行更新 + if self.parent.now_working != InstallBackend.ACTION_DEFUALT_STATUS: + logging.warning('UpdateDetect In the process of Updating or Upgrading...') + return self.RETURN_BUSY_STATE,self.RETURN_BUSY_DESC + else: + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' UpdateCache sender:%s...',sender_name) + self._check_prohibit_user(sender_name) + self.parent.start_update_backend(InstallBackend.MODE_UPDATE_CACHE) + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + except Exception as e: + logging.error(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + + # dbus接口:向数据库display表中插入数据 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='ss', out_signature='is', sender_keyword='sender') + def InsertInstallState(self, item, value, sender=None): + try: + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' InsertInstallState, options:%s,value:%s, InsertInstallState sender: %s .' % (item, value, sender_name)) + self.parent.sqlite3_server.insert_into_display(item, value) + logging.info("Database inserted successfully,options:%s,value:%s" % (item, value)) + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + except Exception as e: + logging.error("Database insert failed,options:%s,value:%s" % (item, value)) + return self.RETURN_UNKNOWN_CODE,str(e) + + # TODO 已迁移 + # 是否允许关机前更新 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='ss', out_signature='is', sender_keyword='sender') + def UnattendedUpgradeValue(self, operation, value="false", sender=None): + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' UnattendedUpgradeValue sender:%s ', sender_name) + if operation.lower() != "get" and operation.lower() != "set": + return self.RETURN_UNKNOWN_CODE, 'Please input [\"set\", \"value\"] to set. \nor [\"get\"] to get whether updates are allowed before shutdown.' + if operation == "set": + try: + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' UnattendedUpgradeValue is going to %s [allow_unattended_upgrades_shutdown] value to %s.'%(operation,value)) + self.parent.sqlite3_server.insert_into_display("allow_unattended_upgrades_shutdown", value.lower()) + except Exception as e: + logging.error(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + else: + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' UnattendedUpgradeValue is going to %s [allow_unattended_upgrades_shutdown] value.'%(operation)) + try: + value = self.parent.sqlite3_server.select_from_display("allow_unattended_upgrades_shutdown") + logging.info("[allow_unattended_upgrades_shutdown] value is %s."%(value)) + except Exception as e: + logging.error(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + + # TODO 已迁移 + # 设置自动更新时间 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='s', out_signature='is',sender_keyword='sender') + def SetAutoUpgradePeriod(self, period, sender = None): + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' SetAutoUpgradePeriod will be set value %s, SetAutoUpgradePeriod sender: %s.'%(period, sender_name)) + try: + self.parent.sqlite3_server.insert_into_display("update_period", period.lower()) + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + except Exception as e: + logging.error(str(e)) + return self.RETURN_UNKNOWN_CODE,str(e) + + # TODO 已迁移 + # # dbus接口:改变apt下载速度 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='sb', out_signature='is',sender_keyword='sender') + def SetDownloadspeedMax(self, speed, set,sender = None): + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' SetDownloadspeedMax, speed:%s, set:%r, sender name: %s .'%(speed, set, sender_name)) + #来重启Aptdeamon + self.parent.init_config_aptdeamon = True + if set: + with open("/etc/apt/apt.conf.d/80apt-download", "w+") as f: + try: + f.write("Acquire::http::Dl-Limit" + " \"" + "%s" % str(speed) + "\";\n") + f.write("Acquire::https::Dl-Limit" + " \"" + "%s" % str(speed) + "\";\n") + #更改数据库值 + self.parent.sqlite3_server.insert_into_display("download_limit","true") + self.parent.sqlite3_server.insert_into_display("download_limit_value",str(speed)) + #发送信号 + self.ButtonStatusChange("speed" , str(speed)) + except Exception as e: + logging.error(e) + return self.RETURN_UNKNOWN_CODE,str(e) + else: + if os.path.exists("/etc/apt/apt.conf.d/80apt-download"): + os.remove("/etc/apt/apt.conf.d/80apt-download") + self.parent.sqlite3_server.insert_into_display("download_limit","false") + self.ButtonStatusChange("speed", "0") + else: + self.parent.sqlite3_server.insert_into_display("download_limit","false") + self.ButtonStatusChange("speed", "0") + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + + # TODO 已迁移 + # # dbus接口:获取apt下载速度 + @dbus.service.method(UPDATER_DBUS_INTERFACE, out_signature='bs',sender_keyword='sender') + def GetDownloadspeedLimitValue(self,sender = None): + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' GetDownloadspeedLimitValue sender: %s .', sender_name) + try: + download_limit = self.parent.sqlite3_server.select_from_display("download_limit") + if download_limit == "true": + download_limit_value = self.parent.sqlite3_server.select_from_display("download_limit_value") + return True,str(download_limit_value) + else: + return False,str("0") + except: + return False, "0" + + + ## dbus接口: 开启或关闭预下载功能 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='b', out_signature='b',sender_keyword='sender') + def SetPreDownloadState(self, _state,sender = None): + state = bool(_state) + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' SetPreDownloadState, state is %r, sender name: %s .',state,sender_name) + try: + if state: + self.parent.uuconfigs.setValue("autoUpgradePolicy", "preDownload", "on", True) + else : + self.parent.uuconfigs.setValue("autoUpgradePolicy", "preDownload", "off", True) + self.ChangeUpgradePolicy() + except Exception as e: + logging.error(str(e)) + return False + return True + + ## dbus接口: 发送立即更新的信号 + @dbus.service.method(UPDATER_DBUS_INTERFACE, out_signature='b') + def AutoUpgradeAllNow(self): + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' AutoUpgradeAllNow ...') + try: + self.UpgradeAllNow() + except Exception as e: + logging.error(str(e)) + return False + return True + + ## dbus接口: 开启关闭自动更新功能 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='b', out_signature='b') + def SetAutoUpgradeState(self, _state): + state = bool(_state) + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' SetAutoUpgradeState, state is %r ...',state) + try: + if state: + self.parent.uuconfigs.setValue("autoUpgradePolicy", "autoUpgradeState", "on", True) + self.parent.sqlite3_server.insert_into_display("autoupdate_allow", "true") + self.ButtonStatusChange("autoUpgradeStatus", "true") + else : + self.parent.uuconfigs.setValue("autoUpgradePolicy", "autoUpgradeState", "off", True) + # self.parent.uuconfigs.setValue("autoUpgradePolicy", "downloadMode", "manual", True) + # self.parent.uuconfigs.setValue("autoUpgradePolicy", "installMode", "manual", True) + self.parent.sqlite3_server.insert_into_display("autoupdate_allow", "false") + self.ButtonStatusChange("autoUpgradeStatus", "false") + self.ChangeUpgradePolicy() + + except Exception as e: + logging.error(str(e)) + return False + return True + + ## dbus接口: 设置自动更新策略 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='is', out_signature='b') + def SetAutoUpgradeMode(self, mode, time): + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' SetAutoUpgradeMode, mode is %s, time is %s ...',mode, time) + try: + if mode == UU_UPGRADE_MODE_TIMING: + self.parent.uuconfigs.setValue("autoUpgradePolicy", "downloadMode", "timing", True) + self.parent.uuconfigs.setValue("autoUpgradePolicy", "downloadTime", str(time), True) + self.parent.uuconfigs.setValue("autoUpgradePolicy", "installMode", "timing", True) + self.ButtonStatusChange("autoUpgradeTime", str(time)) + elif mode == UU_UPGRADE_MODE_BEFORE_SHUTDOWN: + self.parent.uuconfigs.setValue("autoUpgradePolicy", "downloadMode", "timing", True) + self.parent.uuconfigs.setValue("autoUpgradePolicy", "installMode", "bshutdown", True) + self.ChangeUpgradePolicy() + except Exception as e: + logging.error(str(e)) + return False + return True + + ## dbus接口: 获取自动更新属性信息 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='s', out_signature='s') + def Get(self, propertyName): + propertyValue = '' + retvalue = '' + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' Get, propertyName is %s ...',propertyName) + try: + if propertyName == "PreDownloadState": + retvalue = self.parent.uuconfigs.getWithDefault("autoUpgradePolicy", "preDownload", "False") + if retvalue == "on": + propertyValue = "on" + elif retvalue == "off": + propertyValue = "off" + elif propertyName == "AutoUpgradeState": + retvalue = self.parent.uuconfigs.getWithDefault("autoUpgradePolicy", "autoUpgradeState", "False") + if retvalue == "on": + propertyValue = "on" + elif retvalue == "off": + propertyValue = "off" + elif propertyName == "AutoUpgradeMode": + retvalue = self.parent.uuconfigs.getWithDefault("autoUpgradePolicy", "downloadtime", "False") + if retvalue == "before_shutdown": #关机前更新 + propertyValue = "before_shutdown" + elif ":" in retvalue: #定时更新 + propertyValue = "timing+"+retvalue + logging.info("Get '%s': %s",propertyName, propertyValue) + except Exception as e: + logging.error(str(e)) + return propertyValue + + # 监控是否需要重启的操作 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='s', out_signature='b') + def CheckRebootRequired(self, model): + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' CheckRebootRequired is be %s',str(model)) + try: + #更新完成之后检查是否需要重启 + if self.is_reboot_required() == True: + self.RebootLogoutRequired("reboot") + logging.info("System need to Reboot...") + elif self.is_logout_required() == True: + self.RebootLogoutRequired("logout") + logging.info("System need to Logout...") + else: + logging.info("System not need to Reboot or Logout...") + except Exception as e: + logging.error(str(e)) + return False + return True + + # TODO 已迁移 + # kill 进程 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='i', out_signature='b') + def KillProcessSignal(self, pid): + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' KillProcessSignal is %d', pid) + try: + # 判断文件是否存在 + if (os.path.exists(RUN_UNATTENDED_UPGRADE)): + os.kill(int(pid), 9) + logging.info('%s has been killed', pid) + else: + logging.warning('%s is not exist.', RUN_UNATTENDED_UPGRADE) + except Exception as e: + logging.error(str(e)) + return False + return True + + # TODO 已迁移 + # 获取数据库值 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='bss', out_signature='s') + def GetSetDatabaseInfo(self, gs, table, field): + Text = 'NULL' + try: + if gs: #get + if table == 'display': + Text = self.parent.sqlite3_server.select_from_display(str(field)) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' GetDatabaseInfo Table:%s Field:%s Text:%s',table,field,Text) + else: #set + if table == 'display' and "=" in field: + field, value = str(field).split("=") + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' SetDatabaseInfo Table:%s Field:%s', table, field) + self.parent.sqlite3_server.insert_into_display(field, value) + return "success" + except Exception as e: + logging.error(str(e)) + return Text + return Text + + # 获取系统版本号 + @dbus.service.method(UPDATER_DBUS_INTERFACE, out_signature='ss',sender_keyword='sender') + def GetSystemUpdateVersion(self,sender=None): + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' GetSystemUpdateVersion sender: %s .', sender_name) + os_version = '' + update_version = '' + try: + self.parent.sqlite3_server.ucconfigs = UpgradeConfig(datadir = "/etc/kylin-version", name = "kylin-system-version.conf") + if not os.path.exists("/etc/kylin-version/kylin-system-version.conf"): + logging.warning("System version file doesn't exist.") + update_version,os_version = self.parent.sqlite3_server.get_default_version() + return str(os_version),str(update_version) + os_version = str(self.parent.sqlite3_server.ucconfigs.get("SYSTEM","os_version")) + update_version = str(self.parent.sqlite3_server.ucconfigs.get("SYSTEM","update_version")) + except Exception as e: + logging.error(str(e)) + return str(e),'' + logging.info('Current os_version: %s, release_id: %s .', os_version, update_version) + return os_version,update_version + + # @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='s', out_signature='bs') + # def MountSquashfsSource(self, mount_source): + # logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' MountSquashfsSource %s',str(mount_source)) + # try: + # ret,dsc = self.parent.source_info.mount_squashfs(str(mount_source)) + # logging.info(COLORLOG_PREFIX+"Emitting"+COLORLOG_SUFFIX+" FixBrokenStatusChanged finished = %r dec = %s",ret,dsc) + # return ret,dsc + # except Exception as e: + # logging.error(str(e)) + # return False,str(e) + + #更新进度信息 0~100 进度信息 101为非预期的信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='is') + def UpdateDetectStatusChanged(self,progress,status): + logging.info(COLORLOG_PREFIX+"Emitting"+COLORLOG_SUFFIX+" UpdateDetectStatusChanged progress = %d , status = %s",progress,status) + + #更新完成的信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='basss') + def UpdateDetectFinished(self, success, upgrade_group,error_string='',error_desc=''): + logging.info(COLORLOG_PREFIX + "Emitting"+ COLORLOG_SUFFIX + " UpdateDetectFinished success = %r , upgrade_group = %a, error_string = %s , error_desc = %s ",\ + success,upgrade_group,error_string,error_desc) + + #升级的进度信息 0~100 进度信息 101为非预期的信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='asiss') + def UpdateDloadAndInstStaChanged(self,groups_list,progress,status,current_details): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" UpdateDloadAndInstStaChanged upgrade groups_list = %s progress = %d , status = %s ,current_details = %s\033[0m",\ + groups_list,progress,status,current_details) + + #升级完成的信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='basss') + def UpdateInstallFinished(self, success, upgrade_group,error_string='',error_desc=''): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" UpdateInstallFinished success = %r , upgrade_group = %a, error_string = %s , error_desc = %s ",\ + success,upgrade_group, error_string,error_desc) + + #升级完成的信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='basss') + def UpdateDownloadFinished(self, success, upgrade_group,error_string='',error_desc=''): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" UpdateDownloadFinished success = %r , upgrade_group = %a, error_string = %s , error_desc = %s ",\ + success,upgrade_group, error_string,error_desc) + + #发送下载包信息 fix bug 字节大小改成u 无符号32位 + @dbus.service.signal(UPDATER_DBUS_INTERFACE, signature='asiiuui') + def UpdateDownloadInfo(self,upgrade_group,current_items, total_items, currenty_bytes, total_bytes, current_cps): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" UpdateDownloadInfo upgrade_group = %a,current_items = %d, total_items = %d, currenty_bytes = %s, total_bytes = %s, current_cps = %s/s",\ + upgrade_group,\ + current_items, total_items, \ + humanize_size(currenty_bytes), humanize_size(total_bytes),\ + humanize_size(current_cps)) + + #查询解决依赖 信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='bbasasasss') + def UpdateDependResloveStatus(self, resolver_status, remove_status,remove_pkgs,pkg_raw_description,delete_desc,error_string='',error_desc=''): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" UpdateDependResloveStatus:resolver_status = %r , remove_status = %r , remove_pkgs = %a,pkg_raw_description = %s ,delete_desc = %s,error_string = %s , error_desc = %s ",\ + resolver_status,remove_status,remove_pkgs,pkg_raw_description,delete_desc,error_string,error_desc) + + #查询解决依赖 信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='bbasasasss') + def UpdateFixBrokenStatus(self, resolver_status, remove_status,remove_pkgs,pkg_raw_description,delete_desc,error_string='',error_desc=''): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" UpdateFixBrokenStatus:resolver_status = %r , remove_status = %r , remove_pkgs = %a,pkg_raw_description = %s ,delete_desc = %s,error_string = %s , error_desc = %s ",\ + resolver_status,remove_status,remove_pkgs,pkg_raw_description,delete_desc,error_string,error_desc) + + #查询dist-upgrade解决依赖 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='bbasasasss') + def DistupgradeDependResloveStatus(self, resolver_status, remove_status,remove_pkgs,pkg_raw_description,delete_desc,error_string='',error_desc=''): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" DistupgradeDependResloveStatus:resolver_status = %r , remove_status = %r , remove_pkgs = %a,pkg_raw_description = %s ,delete_desc = %s ,error_string = %s , error_desc = %s ",\ + resolver_status,remove_status,remove_pkgs,pkg_raw_description,delete_desc,error_string,error_desc) + + # 信号是否可取消 + @dbus.service.signal(UPDATER_DBUS_INTERFACE, signature='b') + def Cancelable(self, Cancelable): + pass + # logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" Cancelable: %r",Cancelable) + + # 插入数据库完成后发送 + @dbus.service.signal(UPDATER_DBUS_INTERFACE, signature='ss') + def UpdateSqlitSingle(self, appname, date): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" UpdateSqlitSingle: [ %s ]: date: %s .",\ + appname, date) + + #更新进度信息 0~100 进度信息 101为非预期的信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='bbisss') + def FixBrokenStatusChanged(self,finished,success,progress,status,error_string='',error_desc=''): + logging.info(COLORLOG_PREFIX+"Emitting"+COLORLOG_SUFFIX+" FixBrokenStatusChanged finished = %r , success = %r,progress = %d , status = %s,error_string = %s , error_desc = %s",\ + finished,success,progress,status,error_string,error_desc) + + #卸载完成的信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='bss') + def PurgePackagesFinished(self, success,error_string='',error_desc=''): + logging.info(COLORLOG_PREFIX + "Emitting"+ COLORLOG_SUFFIX + " PurgePackagesFinished success = %r , error_string = %s , error_desc = %s ",\ + success,error_string,error_desc) + + #卸载进度信息 0~100 进度信息 101为非预期的信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='iss') + def PurgePkgStatusChanged(self,progress,status,current_details): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" PurgePkgStatusChanged progress = %d , status = %s ,current_details = %s",\ + progress,status,current_details) + + #安装deb包完成的信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='bss') + def InstalldebFinished(self, success,error_string='',error_desc=''): + logging.info(COLORLOG_PREFIX + "Emitting"+ COLORLOG_SUFFIX + " InstalldebFinished success = %r , error_string = %s , error_desc = %s ",\ + success,error_string,error_desc) + + #安装进度信息 0~100 进度信息 101为非预期的信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='iss') + def InstalldebStatusChanged(self,progress,status,current_details): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" InstalldebStatusChanged progress = %d , status = %s ,current_details = %s",\ + progress,status,current_details) + + # dbus 信号:用于发送本地安装snap包的结果 + @dbus.service.signal(UPDATER_DBUS_INTERFACE, signature='sa{sv}') + def KumSnapSignal(self, type_msg, msg): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" KumSnapSignal type_msg = %s,msg = %s",type_msg,str(msg)) + + #重启和注销请求信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='s') + def RebootLogoutRequired(self,required_status=''): + logging.info(COLORLOG_PREFIX + "Emitting"+ COLORLOG_SUFFIX + " RebootLogoutRequired required_status = %s",required_status)\ + + #限速修改信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='ss') + def ButtonStatusChange(self, signal_types = '', value=''): + logging.info(COLORLOG_PREFIX + "Emitting"+ COLORLOG_SUFFIX + " ButtonStatusChange signal_types = %s, value = %s.",signal_types, value) + + # dbus 信号:用于发送立即更新信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE) + def UpgradeAllNow(self): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" UpgradeAllNow") + + # dbus 信号:用于发送自动更新配置更改信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE) + def ChangeUpgradePolicy(self): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" ChangeUpgradePolicy") + + #下载安装前的状态检查信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='bs') + def InstallDetectStatus(self, success,error_code=''): + logging.info(COLORLOG_PREFIX + "Emitting"+ COLORLOG_SUFFIX + " InstallDetectStatus success = %r , error_code = %s",\ + success,error_code) + + WRITABLE_PROPERTIES = () + + # pylint: disable-msg=C0103,C0322 + @dbus.service.signal(dbus_interface=dbus.PROPERTIES_IFACE, + signature="sa{sv}as") + def PropertiesChanged(self, interface, changed_properties, + invalidated_properties): + """The signal gets emitted if a property of the object's + interfaces changed. + + :param property: The name of the interface. + :param changed_properties: A dictrionary of changed + property/value pairs + :param invalidated_properties: An array of property names which + changed but the value isn't conveyed. + + :type interface: s + :type changed_properties: a{sv} + :type invalidated_properties: as + """ + logging.info("Emitting PropertiesChanged: %s, %s, %s" % + (interface, changed_properties, invalidated_properties)) + + # pylint: disable-msg=C0103,C0322 + @dbus.service.method(dbus.INTROSPECTABLE_IFACE, + in_signature='', out_signature='s', + path_keyword='object_path', + connection_keyword='connection') + def Introspect(self, object_path, connection): + # Inject the properties into the introspection xml data + data = dbus.service.Object.Introspect(self, object_path, connection) + xml = ElementTree.fromstring(data) + for iface in xml.findall("interface"): + props = self._get_properties(iface.attrib["name"]) + for key, value in props.items(): + attrib = {"name": key} + if key in self.WRITABLE_PROPERTIES: + attrib["access"] = "readwrite" + else: + attrib["access"] = "read" + if isinstance(value, dbus.String): + attrib["type"] = "s" + elif isinstance(value, dbus.UInt32): + attrib["type"] = "u" + elif isinstance(value, dbus.Int32): + attrib["type"] = "i" + elif isinstance(value, dbus.UInt64): + attrib["type"] = "t" + elif isinstance(value, dbus.Int64): + attrib["type"] = "x" + elif isinstance(value, dbus.Boolean): + attrib["type"] = "b" + elif isinstance(value, dbus.Struct): + attrib["type"] = "(%s)" % value.signature + elif isinstance(value, dbus.Dictionary): + attrib["type"] = "a{%s}" % value.signature + elif isinstance(value, dbus.Array): + attrib["type"] = "a%s" % value.signature + else: + raise Exception("Type %s of property %s isn't " + "convertable" % (type(value), key)) + iface.append(ElementTree.Element("property", attrib)) + new_data = ElementTree.tostring(xml, encoding="UTF-8") + return new_data + + # pylint: disable-msg=C0103,C0322 + @dbus.service.method(dbus.PROPERTIES_IFACE, + in_signature="ssv", out_signature="", + sender_keyword="sender") + def Set(self, iface, name, value, sender): + """Set a property. + + Only the user who intiaited the transaction is + allowed to modify it. + + :param iface: The interface which provides the property. + :param name: The name of the property which should be modified. + :param value: The new value of the property. + + :type iface: s + :type name: s + :type value: v + """ + logging.info("Set() was called: %s, %s" % (name, value)) + return self._set_property(iface, name, value, sender) + + # pylint: disable-msg=C0103,C0322 + @dbus.service.method(dbus.PROPERTIES_IFACE, + in_signature="s", out_signature="a{sv}") + def GetAll(self, iface): + """Get all available properties of the given interface.""" + logging.info("GetAll() was called: %s" % iface) + return self._get_properties(iface) + + # pylint: disable-msg=C0103,C0322 + @dbus.service.method(dbus.PROPERTIES_IFACE, + in_signature="ss", out_signature="v") + def Get(self, iface, property): + """Return the value of the given property provided by the given + interface. + """ + logging.info("Get() was called: %s, %s" % (iface, property)) + return self._get_properties(iface)[property] + + def _set_property(self, iface, name, value, sender): + """Helper to set a property on the properties D-Bus interface.""" + if iface == UPDATER_DBUS_INTERFACE: + if name == "ShutdownInstall": + self.parent.configs_uncover.setValue("InstallMode","shutdown_install",str(bool(value))) + elif name == "P2pBootstrap": + self.parent.apt_p2p_config.set_bootstrap(str(value)) + else: + raise dbus.exceptions.DBusException("Unknown or read only " + "property: %s" % name) + else: + raise dbus.exceptions.DBusException("Unknown interface: %s" % + iface) + + def _get_properties(self, iface): + """Helper get the properties of a D-Bus interface.""" + if iface == UPDATER_DBUS_INTERFACE: + return { + "ShutdownInstall": dbus.Boolean( + self.parent.configs_uncover.getWithDefault("InstallMode", "shutdown_install", False)), + + "P2pBootstrap": dbus.String(self.parent.apt_p2p_config.get_bootstrap()) + } + else: + return {} + +class UpdateManagerDbusControllerUtils(dbus.service.Object): + """ this is a helper to provide the UpdateManagerIFace """ + + def __init__(self, parent, bus_name, + object_path=UPDATER_DBUS_PATH_UTILS): + try: + dbus.service.Object.__init__(self, bus_name, object_path) + except Exception as e: + # pass + logging.warning(e) + self.parent = parent + self.bus = dbus.SystemBus() + + self.transaction = None + + def _check_prohibit_user(self, sender_name): + prohibit_list = ["dbus-send","gdbus"] + if sender_name in prohibit_list: + raise dbus.exceptions.DBusException("ERROR: You are not allowed to perform this action.") + + # # dbus接口: 后端大数据采集 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='ss', out_signature='b',sender_keyword='sender') + def DataBackendCollect(self, messageType, uploadMessage, sender=None): + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' DataBackendCollect, messageType is %s, called by: %s .',messageType,sender_name) + logging.debug("uploadMessage: %s.", uploadMessage) + try: + self.parent.collector.UpdateMsg(messageType, uploadMessage) + except Exception as e: + logging.error(str(e)) + + # 检查更新 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='as', out_signature='as', sender_keyword='sender') + def CheckInstalledOrUpgrade(self, pkgs, sender=None): + sender_name = get_proc_from_dbus_name(sender) + try: + pkglist = [] + cache = MyCache(None) + self.parent.collector.cache = cache + for i in pkgs: + name = str(i).strip().replace("\n", "") + if name in cache: + pkg = cache[name] + if pkg.is_installed == False or pkg.is_upgradable == True: + pkglist.append(pkg.name) + pkglist.append(str(pkg.candidate.size)) + self.parent.collector.make_background_version(pkg) + self.parent.collector.background_upgradable.append(pkg.name) + except: + logging.error("CheckInstalledOrUpgrade: Failed to obtain package information: %s" % str(i), exc_info=True) + # if len(pkglist) != 0: + # logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' CheckInstalledOrUpgrade, called by: %s, Upgrade list: %s .', sender_name, ", ".join(pkglist)) + return pkglist + + # 提供插入更新历史的接口 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='a{sv}', out_signature='b', sender_keyword='sender') + def InsertUpgradeHistory(self, arg = {}, sender=None): + try: + # {"appname":GLib.Variant("s", "kylin-system-updater"), "version":GLib.Variant("s", "string value")} + # "description":GLib.Variant("s", "Update Manager for Kylin"), "date":GLib.Variant("s", "2022-07-27 15:23:51") + # "status":GLib.Variant("s", "failed"), "keyword":GLib.Variant("s", "1") + # "errorcode":GLib.Variant("s", "System upgrade is complete. "), "appname_cn":GLib.Variant("s", "音乐") + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' Call InsertUpgradeHistory from %s.', sender_name) + if not self.parent.sqlite3_server.insert_upgrade_history(arg, sender_name) : + return False + except Exception as e: + logging.error("InsertUpgradeHistory Failed: %s" % str(e), exc_info=True) + return False + return True + + WRITABLE_PROPERTIES = () + + # pylint: disable-msg=C0103,C0322 + @dbus.service.signal(dbus_interface=dbus.PROPERTIES_IFACE, + signature="sa{sv}as") + def PropertiesChanged(self, interface, changed_properties, + invalidated_properties): + """The signal gets emitted if a property of the object's + interfaces changed. + + :param property: The name of the interface. + :param changed_properties: A dictrionary of changed + property/value pairs + :param invalidated_properties: An array of property names which + changed but the value isn't conveyed. + + :type interface: s + :type changed_properties: a{sv} + :type invalidated_properties: as + """ + logging.info("Emitting PropertiesChanged: %s, %s, %s" % + (interface, changed_properties, invalidated_properties)) + + # pylint: disable-msg=C0103,C0322 + @dbus.service.method(dbus.INTROSPECTABLE_IFACE, + in_signature='', out_signature='s', + path_keyword='object_path', + connection_keyword='connection') + def Introspect(self, object_path, connection): + # Inject the properties into the introspection xml data + data = dbus.service.Object.Introspect(self, object_path, connection) + xml = ElementTree.fromstring(data) + for iface in xml.findall("interface"): + props = self._get_properties(iface.attrib["name"]) + for key, value in props.items(): + attrib = {"name": key} + if key in self.WRITABLE_PROPERTIES: + attrib["access"] = "readwrite" + else: + attrib["access"] = "read" + if isinstance(value, dbus.String): + attrib["type"] = "s" + elif isinstance(value, dbus.UInt32): + attrib["type"] = "u" + elif isinstance(value, dbus.Int32): + attrib["type"] = "i" + elif isinstance(value, dbus.UInt64): + attrib["type"] = "t" + elif isinstance(value, dbus.Int64): + attrib["type"] = "x" + elif isinstance(value, dbus.Boolean): + attrib["type"] = "b" + elif isinstance(value, dbus.Struct): + attrib["type"] = "(%s)" % value.signature + elif isinstance(value, dbus.Dictionary): + attrib["type"] = "a{%s}" % value.signature + elif isinstance(value, dbus.Array): + attrib["type"] = "a%s" % value.signature + else: + raise Exception("Type %s of property %s isn't " + "convertable" % (type(value), key)) + iface.append(ElementTree.Element("property", attrib)) + new_data = ElementTree.tostring(xml, encoding="UTF-8") + return new_data + + # pylint: disable-msg=C0103,C0322 + @dbus.service.method(dbus.PROPERTIES_IFACE, + in_signature="ssv", out_signature="", + sender_keyword="sender") + def Set(self, iface, name, value, sender): + """Set a property. + + Only the user who intiaited the transaction is + allowed to modify it. + + :param iface: The interface which provides the property. + :param name: The name of the property which should be modified. + :param value: The new value of the property. + + :type iface: s + :type name: s + :type value: v + """ + logging.info("Set() was called: %s, %s" % (name, value)) + return self._set_property(iface, name, value, sender) + + # pylint: disable-msg=C0103,C0322 + @dbus.service.method(dbus.PROPERTIES_IFACE, + in_signature="s", out_signature="a{sv}") + def GetAll(self, iface): + """Get all available properties of the given interface.""" + logging.info("GetAll() was called: %s" % iface) + return self._get_properties(iface) + + # pylint: disable-msg=C0103,C0322 + @dbus.service.method(dbus.PROPERTIES_IFACE, + in_signature="ss", out_signature="v") + def Get(self, iface, property): + """Return the value of the given property provided by the given + interface. + """ + logging.info("Get() was called: %s, %s" % (iface, property)) + return self._get_properties(iface)[property] + + def _set_property(self, iface, name, value, sender): + """Helper to set a property on the properties D-Bus interface.""" + if iface == UPDATER_DBUS_INTERFACE: + if name == "UploadUpgradeLog": + self.parent.configs_uncover.setValue("SystemStatus","upload_upgrade_log",str(bool(value))) + if name == "UploadInstallerLog": + self.parent.configs_uncover.setValue("SystemStatus","upload_installer_log",str(bool(value))) + else: + raise dbus.exceptions.DBusException("Unknown or read only " + "property: %s" % name) + else: + raise dbus.exceptions.DBusException("Unknown interface: %s" % + iface) + + def _get_properties(self, iface): + """Helper get the properties of a D-Bus interface.""" + if iface == UPDATER_DBUS_INTERFACE: + return { + "UploadUpgradeLog": dbus.Boolean( + self.parent.configs_uncover.getWithDefault("SystemStatus", "upload_upgrade_log", True)), + "UploadInstallerLog": dbus.Boolean( + self.parent.configs_uncover.getWithDefault("SystemStatus", "upload_installer_log", False)) + } + else: + return {} diff --git a/backend/SystemUpdater/UpgradeStrategies.py b/backend/SystemUpdater/UpgradeStrategies.py new file mode 100644 index 0000000..78902f9 --- /dev/null +++ b/backend/SystemUpdater/UpgradeStrategies.py @@ -0,0 +1,101 @@ +# UpdateManager.py +# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- +import os +import sys +import time +import dbus +import logging +import dbus.service +import traceback +from gettext import gettext as _ +from dbus.mainloop.glib import DBusGMainLoop +from gi.repository import GLib +DBusGMainLoop(set_as_default=True) + +from .UpgradeStrategiesDbus import UpgradeStrategiesDbusController,UPDATER_DBUS_INTERFACE,UPDATER_DBUS_PATH,UPDATER_DBUS_SERVICE +from .Core.Database import Sqlite3Server +from .Core.loop import mainloop + +from SystemUpdater.Core.UpdaterConfigParser import UpgradeConfig + +STRATEGY_IDLE_INTERVAL = 2*60 +STRATEGY_IDLE_TIMEOUT = 6*60 +class UpgradeStrategies(): + def __init__(self,options): + try: + self.options = options + #dbus + self.dbusController = self._setup_dbus() + #config + self.uuconfigs = UpgradeConfig(datadir = "/var/lib/unattended-upgrades/", name = "unattended-upgrades-policy.conf") + self.sqlite3_server = Sqlite3Server(self) + #策略配置接口的超时退出机制 + self.strategy_timestamp = 0 + GLib.timeout_add_seconds(STRATEGY_IDLE_INTERVAL, + self._check_strategy_inactivity) + except Exception as e: + logging.error(e) + traceback.print_exc() + + def run(self): + """Start the daemon and listen for calls.""" + logging.info("Waiting for calls...") + try: + mainloop.run() + except KeyboardInterrupt: + self.dbusController.Quit(None) + + def _setup_dbus(self): + # check if there is another g-a-i already and if not setup one + # listening on dbus + bus = dbus.SystemBus() + try: + bus_name = dbus.service.BusName(UPDATER_DBUS_SERVICE, + bus, + do_not_queue=True) + logging.info("Initiate dbus success ...") + return UpgradeStrategiesDbusController(self, bus_name) + except dbus.exceptions.NameExistsException: + if self.options.replace is False: + logging.critical("Another daemon is already running") + sys.exit(1) + logging.warning("Replacing already running daemon") + + retry_reboot_times = 0 + the_other_guy = bus.get_object(UPDATER_DBUS_SERVICE, + UPDATER_DBUS_PATH) + the_other_guy.Quit(dbus_interface=UPDATER_DBUS_INTERFACE, + timeout=300) + time.sleep(1) + while True: + retry_reboot_times = retry_reboot_times + 1 + #当重试次数超过5次时退出程序 + if retry_reboot_times > 5: + logging.critical("Reboot backend is Failed...") + sys.exit(1) + try: + bus_name = dbus.service.BusName(UPDATER_DBUS_SERVICE, + bus, + do_not_queue=True) + logging.warning("Replacing already running daemon to Success...") + return UpgradeStrategiesDbusController(self, bus_name) + except dbus.exceptions.NameExistsException: + the_other_guy = bus.get_object(UPDATER_DBUS_SERVICE, + UPDATER_DBUS_PATH) + the_other_guy.Quit(dbus_interface=UPDATER_DBUS_INTERFACE, + timeout=300) + logging.error("Dbus has not withdrawn and retry reboot times:%d...",retry_reboot_times) + time.sleep(1) + + def _check_strategy_inactivity(self): + logging.info("Checking for inactivity in Strategies daemon ...") + timestamp = self.strategy_timestamp + if timestamp == 0: + self.strategy_timestamp = time.time() + return True + #超时退出 + if self.strategy_timestamp != 0 and time.time() - self.strategy_timestamp > STRATEGY_IDLE_TIMEOUT: + logging.warning("Quitting due to inactivity") + self.dbusController.Quit(None) + return False + return True \ No newline at end of file diff --git a/backend/SystemUpdater/UpgradeStrategiesDbus.py b/backend/SystemUpdater/UpgradeStrategiesDbus.py new file mode 100644 index 0000000..dec8a97 --- /dev/null +++ b/backend/SystemUpdater/UpgradeStrategiesDbus.py @@ -0,0 +1,604 @@ +#!/usr/bin/python3 +import os +import dbus +import dbus.service +import logging +import subprocess +from gettext import gettext as _ +from .Core.loop import mainloop +from SystemUpdater.Core.utils import get_proc_from_dbus_name + +UPDATER_DBUS_INTERFACE = 'com.kylin.UpgradeStrategies.interface' +UPDATER_DBUS_PATH = '/com/kylin/UpgradeStrategies' +UPDATER_DBUS_SERVICE = 'com.kylin.UpgradeStrategies' +RUN_UNATTENDED_UPGRADE = '/var/run/unattended-upgrades.pid' +SYSTEM_VERSION = '/etc/kylin-version/kylin-system-version.conf' + +#颜色设置 +COLORLOG_SUFFIX = "\033[0m" + +# Define some foreground colors +BLACK = 30 +RED = 31 +GREEN = 32 +YELLOW = 33 +BLUE = 34 +MAGENTA = 35 +CYAN = 36 +WHITE = 37 + +#字体颜色 +FRONT_COLOR_SEQ = "\033[1;%dm" +#背景颜色 +BACK_COLOR_SEQ = "\033[%d;1m" + +COLORLOG_PREFIX = FRONT_COLOR_SEQ % GREEN +COLORMETHOR_PREFIX = FRONT_COLOR_SEQ % CYAN + +UU_UPGRADE_MODE_AUTOMATIC_DOWNLOAD = 0 +UU_UPGRADE_MODE_MANUAL = 1 +UU_UPGRADE_MODE_AUTOMATIC_INSTALL = 2 +UU_UPGRADE_MODE_BEFORE_SHUTDOWN = 3 + + +#dbus 建立 +class UpgradeStrategiesDbusController(dbus.service.Object): + """ this is a helper to provide the UpdateManagerIFace """ + + P2P_DEDAULT_PATH = "/etc/default/apt-p2p" + RETURN_SUCCESS_CODE = 0 + RETURN_SUCCESS_DESC = "" + + RETURN_UNKNOWN_CODE = -1 + RETURN_UNKNOWN_DESC = "" + + def __init__(self, parent, bus_name, + object_path=UPDATER_DBUS_PATH): + dbus.service.Object.__init__(self, bus_name, object_path) + self.parent = parent + self.bus = dbus.SystemBus() + + self.transaction = None + + def __check_change__(self, _config = None, _section = "", _option = "", _value = ""): + if _config == None: + return False + if _value == _config.getWithDefault(_section,_option,_value," "): + return True + return False + + @dbus.service.method(UPDATER_DBUS_INTERFACE, + in_signature="", out_signature="", + sender_keyword="caller_name") + def Quit(self, caller_name): + """Request a shutdown of the daemon.""" + #如果在下载就请求 取消 + logging.info("Quitting was requested") + logging.debug("Quitting main loop...") + mainloop.quit() + logging.debug("Exit") + + ## dbus接口: 开启或关闭预下载功能 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='s', out_signature='is',sender_keyword='sender') + def ChangingP2PStatus(self,_status,sender = None): + status = str(_status) + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' ChangingP2PStatus, _status = %s , sender name: %s',status,sender_name) + + if os.path.exists(self.P2P_DEDAULT_PATH): + if status == "enable": + with open(self.P2P_DEDAULT_PATH, 'w+') as configfile: + configfile.write("#enable=true\n") + #第一次进行检查是否已经开启 + args = ["systemctl","is-enabled","apt-p2p.service"] + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + if p.returncode != 0: + logging.info("Apt-p2p service is not runing and will be enable...") + #第二次进行重启 + args = ["systemctl","enable","apt-p2p.service"] + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + if p.returncode == 0: + logging.info("Service Enable Execute successfully") + #第三次进行重启 + args = ["systemctl","restart","apt-p2p.service"] + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + if p.returncode == 0: + logging.info("Restart Execute successfully") + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + else: + logging.error(str(p.stdout)) + logging.error("Failed to execute reboot") + return self.RETURN_UNKNOWN_CODE,str(p.stdout) + else: + logging.error(str(p.stdout)) + logging.error("Failed to execute enable") + return self.RETURN_UNKNOWN_CODE,str(p.stdout) + else: + logging.info("Apt-p2p service has been enabled and not need to reabled...") + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + + elif status == "disable": + with open(self.P2P_DEDAULT_PATH, 'w+') as configfile: + configfile.write("enable=false\n") + + args = ["systemctl","is-enabled","apt-p2p.service"] + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + if p.returncode == 0: + logging.info("Apt-p2p service is runing and will be disable...") + args = ["systemctl","disable","apt-p2p.service"] + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + if p.returncode == 0: + logging.info("Service disable Execute successfully") + + args = ["systemctl","stop","apt-p2p.service"] + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + + if p.returncode == 0: + logging.info("Stop Execute successfully") + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + else: + logging.error(str(p.stdout)) + logging.error("Failed to execute Stop") + return self.RETURN_UNKNOWN_CODE,str(p.stdout) + else: + logging.error(str(p.stdout)) + logging.error("Failed to execute disable") + return self.RETURN_UNKNOWN_CODE,str(p.stdout) + else: + logging.info("Apt-p2p service has been disabled and not need to redisabled...") + return self.RETURN_SUCCESS_CODE,self.RETURN_SUCCESS_DESC + else: + logging.waring("error: input value _status=%s",status) + else: + logging.waring("apt-p2p function is not install...") + + ## dbus接口: 开启或关闭预下载功能 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='bs', out_signature='b',sender_keyword='sender') + def SetPreDownloadState(self, _state, _time, sender = None): + state = bool(_state) + time = str(_time) + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' SetPreDownloadState, state is %r, time: %s, sender name: %s .',state,time,sender_name) + try: + if state: + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "preDownload", "on"): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "preDownload", "on", True) + self.PropertyChanged("preDownload","on") + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "preDownloadTime", time): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "preDownloadTime", time, True) + self.PropertyChanged("preDownloadTime",time) + else: + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "preDownload", "off"): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "preDownload", "off", True) + self.PropertyChanged("preDownload","off") + except Exception as e: + logging.error(str(e)) + return False + return True + + ## 设置更新周期 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='i', out_signature='b',sender_keyword='sender') + def SetUpdateDays(self, days, sender = None): + _days = int(days) + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' SetUpdateDays, days: %d , sender:%s .'\ + ,_days,sender_name) + try: + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "updateDays", _days): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "updateDays", str(_days), True) + self.PropertyChanged("updateDays",str(_days)) + except Exception as e: + logging.error(str(e)) + return False + return True + + # 设置自动更新时间 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='s', out_signature='bs',sender_keyword='sender') + def SetAutoUpgradeRandomRange(self,randomRange,sender=None): + _randomRange = str(randomRange) + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' SetAutoUpgradeRandomRange will be set value %s, sender: %s .',\ + _randomRange,sender_name) + try: + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "randomRange", _randomRange): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "randomRange", _randomRange, True) + self.PropertyChanged("randomRange",_randomRange) + except Exception as e: + logging.error(str(e)) + return True,"success" + + ## 设置是否开启自动重启,以及设置自动重启的时间 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='bs', out_signature='b',sender_keyword='sender') + def SetAutomaticReboot(self, status, reboot_time, sender = None): + _state = bool(status) + _reboot_time = str(reboot_time) + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' SetAutomaticReboot, status is %r, reboot_time: %s, sender name: %s .'\ + ,_state,_reboot_time,sender_name) + try: + if _state: + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "automaticReboot", "on"): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "automaticReboot", "on", True) + self.PropertyChanged("automaticReboot","on") + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "automaticRebootTime", _reboot_time): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "automaticRebootTime", _reboot_time, True) + self.PropertyChanged("automaticRebootTime",_reboot_time) + else: + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "automaticReboot", "off"): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "automaticReboot", "off", True) + self.PropertyChanged("automaticReboot","off") + except Exception as e: + logging.error(str(e)) + return False + return True + + ## dbus接口: 开启关闭自动更新功能 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='b', out_signature='b') + def SetAutoUpgradeState(self, _state): + state = bool(_state) + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' SetAutoUpgradeState, state is %r ...',state) + try: + if state: + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "autoUpgradeState", "on"): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "autoUpgradeState", "on", True) + self.parent.sqlite3_server.insert_into_display("autoupdate_allow", "true") + self.PropertyChanged("autoUpgradeState","on") + self.ButtonStatusChange("autoUpgradeStatus", "true") + else : + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "autoUpgradeState", "off"): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "autoUpgradeState", "off", True) + self.parent.sqlite3_server.insert_into_display("autoupdate_allow", "false") + self.PropertyChanged("autoUpgradeState","off") + self.ButtonStatusChange("autoUpgradeStatus", "false") + except Exception as e: + logging.error(str(e)) + return False + return True + + ## dbus接口: 设置自动更新策略 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='is', out_signature='b') + def SetAutoUpgradeMode(self, mode, time): + _time = str(time) + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' SetAutoUpgradeMode, mode is %s, time is %s ...',mode,_time) + try: + if mode == UU_UPGRADE_MODE_AUTOMATIC_DOWNLOAD: + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "downloadMode", "timing"): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "downloadMode", "timing", True) + self.PropertyChanged("downloadMode","timing") + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "downloadTime", str(_time)): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "downloadTime", str(_time), True) + self.PropertyChanged("downloadTime",str(_time)) + self.ButtonStatusChange("autoUpgradeTime", str(_time)) + elif mode == UU_UPGRADE_MODE_AUTOMATIC_INSTALL: + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "installMode", "timing"): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "installMode", "timing", True) + self.PropertyChanged("installMode","timing") + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "installTime", str(_time)): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "installTime", str(_time), True) + self.PropertyChanged("installTime",str(_time)) + elif mode == UU_UPGRADE_MODE_BEFORE_SHUTDOWN: + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "installTime", "bshutdown"): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "installMode", "bshutdown", True) + self.PropertyChanged("installMode","bshutdown") + elif mode == UU_UPGRADE_MODE_MANUAL: + if not self.__check_change__(self.parent.uuconfigs, "autoUpgradePolicy", "downloadMode", "manual"): + self.parent.uuconfigs.setValue("autoUpgradePolicy", "downloadMode", "manual", True) + self.PropertyChanged("downloadMode","manual") + except Exception as e: + logging.error(str(e)) + return False + return True + + # # dbus接口:改变apt下载速度 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='sb', out_signature='b',sender_keyword='sender') + def SetDownloadspeedMax(self, speed, set,sender = None): + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' SetDownloadspeedMax, speed:%s, set:%r, sender name: %s .'%(speed, set, sender_name)) + #来重启Aptdeamon + self.parent.init_config_aptdeamon = True + if set: + with open("/etc/apt/apt.conf.d/80apt-download", "w+") as f: + try: + f.write("Acquire::http::Dl-Limit" + " \"" + "%s" % str(speed) + "\";\n") + f.write("Acquire::https::Dl-Limit" + " \"" + "%s" % str(speed) + "\";\n") + #更改数据库值 + self.parent.sqlite3_server.insert_into_display("download_limit","true") + self.parent.sqlite3_server.insert_into_display("download_limit_value",str(speed)) + #发送信号 + self.ButtonStatusChange("speed" , str(speed)) + return True + except Exception as e: + logging.error(e) + return False + else: + if os.path.exists("/etc/apt/apt.conf.d/80apt-download"): + os.remove("/etc/apt/apt.conf.d/80apt-download") + self.parent.sqlite3_server.insert_into_display("download_limit","false") + self.ButtonStatusChange("speed", "0") + return True + else: + self.parent.sqlite3_server.insert_into_display("download_limit","false") + self.ButtonStatusChange("speed", "0") + return True + + # # dbus接口:获取apt下载速度 + @dbus.service.method(UPDATER_DBUS_INTERFACE, out_signature='bs',sender_keyword='sender') + def GetDownloadspeedLimitValue(self,sender = None): + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' GetDownloadspeedLimitValue sender: %s .', sender_name) + try: + download_limit = self.parent.sqlite3_server.select_from_display("download_limit") + if download_limit == "true": + download_limit_value = self.parent.sqlite3_server.select_from_display("download_limit_value") + return True,str(download_limit_value) + else: + return False,str("0") + except: + return False, "0" + + # 是否允许关机前更新 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='ss', out_signature='bs', sender_keyword='sender') + def UnattendedUpgradeValue(self, operation, value="false", sender=None): + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' UnattendedUpgradeValue sender:%s ', sender_name) + if operation.lower() != "get" and operation.lower() != "set": + return False, 'Please input [\"set\", \"value\"] to set. \nor [\"get\"] to get whether updates are allowed before shutdown.' + if operation == "set": + try: + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' UnattendedUpgradeValue is going to %s [allow_unattended_upgrades_shutdown] value to %s.'%(operation,value)) + self.parent.sqlite3_server.insert_into_display("allow_unattended_upgrades_shutdown", value.lower()) + except Exception as e: + logging.error(str(e)) + return False,str(e) + else: + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' UnattendedUpgradeValue is going to %s [allow_unattended_upgrades_shutdown] value.'%(operation)) + try: + value = self.parent.sqlite3_server.select_from_display("allow_unattended_upgrades_shutdown") + logging.info("[allow_unattended_upgrades_shutdown] value is %s."%(value)) + except Exception as e: + logging.error(str(e)) + return False,str(e) + return True,"success" + + # 设置自动更新时间 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='s', out_signature='bs',sender_keyword='sender') + def SetAutoUpgradePeriod(self, period, sender = None): + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' SetAutoUpgradePeriod will be set value %s, SetAutoUpgradePeriod sender: %s.'%(period, sender_name)) + try: + self.parent.sqlite3_server.insert_into_display("update_period", period.lower()) + except Exception as e: + logging.error(str(e)) + return True,"success" + + # 获取数据库值 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='bss', out_signature='s') + def GetSetDatabaseInfo(self, gs, table, field): + Text = 'NULL' + try: + if gs: #get + if table == 'display': + Text = self.parent.sqlite3_server.select_from_display(str(field)) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' GetDatabaseInfo Table:%s Field:%s Text:%s',table,field,Text) + else: #set + if table == 'display' and "=" in field: + field, value = str(field).split("=") + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' SetDatabaseInfo Table:%s Field:%s', table, field) + self.parent.sqlite3_server.insert_into_display(field, value) + return "success" + except Exception as e: + logging.error(str(e)) + return Text + return Text + + ## dbus接口: 发送立即更新的信号 + @dbus.service.method(UPDATER_DBUS_INTERFACE, out_signature='b') + def AutoUpgradeAllNow(self): + logging.info(COLORMETHOR_PREFIX+'method'+COLORLOG_SUFFIX+' AutoUpgradeAllNow ...') + try: + self.UpgradeAllNow() + except Exception as e: + logging.error(str(e)) + return False + return True + + # kill 进程 + @dbus.service.method(UPDATER_DBUS_INTERFACE, in_signature='i', out_signature='b') + def KillProcessSignal(self, pid): + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' KillProcessSignal is %d', pid) + try: + # 判断文件是否存在 + if (os.path.exists(RUN_UNATTENDED_UPGRADE)): + os.kill(int(pid), 9) + logging.info('%s has been killed', pid) + else: + logging.warning('%s is not exist.', RUN_UNATTENDED_UPGRADE) + except Exception as e: + logging.error(str(e)) + return False + return True + + #设置数据库配置信息 + @dbus.service.method(UPDATER_DBUS_INTERFACE,in_signature='ss',out_signature='b',sender_keyword='sender') + def DatabaseInfoSet(self,field_name,field_value,sender=None): + Status = False + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' SetDatabaseInfo,field_name:%s,field_value:%s,caller:%s .',\ + field_name,field_value,sender_name) + Status = self.parent.sqlite3_server.insert_into_display(field_name,field_value) + return bool(Status) + + #数据库获取配置信息 + @dbus.service.method(UPDATER_DBUS_INTERFACE,in_signature='s',out_signature='s',sender_keyword='sender') + def DatabaseInfoGet(self,field_name,sender=None): + field_value = '' + sender_name = get_proc_from_dbus_name(sender) + logging.info(COLORMETHOR_PREFIX+'Method'+COLORLOG_SUFFIX+' GetDatabaseInfo field_name:%s caller:%s',field_name,sender_name) + field_value = self.parent.sqlite3_server.select_from_display(str(field_name)) + logging.info("Get field_value:%s",field_value) + return field_value + + #限速修改信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='ss') + def ButtonStatusChange(self, signal_types = '', value=''): + logging.info(COLORLOG_PREFIX + "Emitting"+ COLORLOG_SUFFIX + " ButtonStatusChange signal_types = %s, value = %s.",signal_types, value) + + # dbus 信号:用于发送立即更新信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE) + def UpgradeAllNow(self): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" UpgradeAllNow") + + # dbus 信号:用于发送自动更新配置更改信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE) + def ChangeUpgradePolicy(self): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" ChangeUpgradePolicy") + + + #限速修改信号 + @dbus.service.signal(UPDATER_DBUS_INTERFACE,signature='ss') + def ButtonStatusChange(self, signal_types = '', value=''): + logging.info(COLORLOG_PREFIX + "Emitting"+ COLORLOG_SUFFIX + " ButtonStatusChange signal_types = %s, value = %s.",signal_types, value) + + + # signal:属性发生改变 + @dbus.service.signal(dbus_interface=UPDATER_DBUS_INTERFACE, + signature="ss") + def PropertyChanged(self, property, value): + logging.info(COLORLOG_PREFIX + "Emitting" + COLORLOG_SUFFIX +" PropertyChanged: ( %s, %s )" % (property, value)) + + + # WRITABLE_PROPERTIES = () + + # # pylint: disable-msg=C0103,C0322 + # @dbus.service.signal(dbus_interface=dbus.PROPERTIES_IFACE, + # signature="sa{sv}as") + # def PropertiesChanged(self, interface, changed_properties, + # invalidated_properties): + # """The signal gets emitted if a property of the object's + # interfaces changed. + + # :param property: The name of the interface. + # :param changed_properties: A dictrionary of changed + # property/value pairs + # :param invalidated_properties: An array of property names which + # changed but the value isn't conveyed. + + # :type interface: s + # :type changed_properties: a{sv} + # :type invalidated_properties: as + # """ + # logging.info("Emitting PropertiesChanged: %s, %s, %s" % + # (interface, changed_properties, invalidated_properties)) + + # # pylint: disable-msg=C0103,C0322 + # @dbus.service.method(dbus.INTROSPECTABLE_IFACE, + # in_signature='', out_signature='s', + # path_keyword='object_path', + # connection_keyword='connection') + # def Introspect(self, object_path, connection): + # # Inject the properties into the introspection xml data + # data = dbus.service.Object.Introspect(self, object_path, connection) + # xml = ElementTree.fromstring(data) + # for iface in xml.findall("interface"): + # props = self._get_properties(iface.attrib["name"]) + # for key, value in props.items(): + # attrib = {"name": key} + # if key in self.WRITABLE_PROPERTIES: + # attrib["access"] = "readwrite" + # else: + # attrib["access"] = "read" + # if isinstance(value, dbus.String): + # attrib["type"] = "s" + # elif isinstance(value, dbus.UInt32): + # attrib["type"] = "u" + # elif isinstance(value, dbus.Int32): + # attrib["type"] = "i" + # elif isinstance(value, dbus.UInt64): + # attrib["type"] = "t" + # elif isinstance(value, dbus.Int64): + # attrib["type"] = "x" + # elif isinstance(value, dbus.Boolean): + # attrib["type"] = "b" + # elif isinstance(value, dbus.Struct): + # attrib["type"] = "(%s)" % value.signature + # elif isinstance(value, dbus.Dictionary): + # attrib["type"] = "a{%s}" % value.signature + # elif isinstance(value, dbus.Array): + # attrib["type"] = "a%s" % value.signature + # else: + # raise Exception("Type %s of property %s isn't " + # "convertable" % (type(value), key)) + # iface.append(ElementTree.Element("property", attrib)) + # new_data = ElementTree.tostring(xml, encoding="UTF-8") + # return new_data + + # # pylint: disable-msg=C0103,C0322 + # @dbus.service.method(dbus.PROPERTIES_IFACE, + # in_signature="ssv", out_signature="", + # sender_keyword="sender") + # def Set(self, iface, name, value, sender): + # """Set a property. + + # Only the user who intiaited the transaction is + # allowed to modify it. + + # :param iface: The interface which provides the property. + # :param name: The name of the property which should be modified. + # :param value: The new value of the property. + + # :type iface: s + # :type name: s + # :type value: v + # """ + # logging.info("Set() was called: %s, %s" % (name, value)) + # return self._set_property(iface, name, value, sender) + + # # pylint: disable-msg=C0103,C0322 + # @dbus.service.method(dbus.PROPERTIES_IFACE, + # in_signature="s", out_signature="a{sv}") + # def GetAll(self, iface): + # """Get all available properties of the given interface.""" + # logging.info("GetAll() was called: %s" % iface) + # return self._get_properties(iface) + + # # pylint: disable-msg=C0103,C0322 + # @dbus.service.method(dbus.PROPERTIES_IFACE, + # in_signature="ss", out_signature="v") + # def Get(self, iface, property): + # """Return the value of the given property provided by the given + # interface. + # """ + # logging.info("Get() was called: %s, %s" % (iface, property)) + # return self._get_properties(iface)[property] + + # def _set_property(self, iface, name, value, sender): + # """Helper to set a property on the properties D-Bus interface.""" + # if iface == UPDATER_DBUS_INTERFACE: + # if name == "ShutdownInstall": + # self.parent.configs_uncover.setValue("InstallMode","shutdown_install",str(bool(value))) + # elif name == "UploadUpgradeLog": + # self.parent.configs_uncover.setValue("SystemStatus","upload_upgrade_log",str(bool(value))) + # elif name == "UploadInstallerLog": + # self.parent.configs_uncover.setValue("SystemStatus","upload_installer_log",str(bool(value))) + # else: + # raise dbus.exceptions.DBusException("Unknown or read only " + # "property: %s" % name) + # else: + # raise dbus.exceptions.DBusException("Unknown interface: %s" % + # iface) + + # def _get_properties(self, iface): + # """Helper get the properties of a D-Bus interface.""" + # if iface == UPDATER_DBUS_INTERFACE: + # return { + # "ShutdownInstall": dbus.Boolean( + # self.parent.configs_uncover.getWithDefault("InstallMode", "shutdown_install", False)), + + # "UploadUpgradeLog": dbus.Boolean( + # self.parent.configs_uncover.getWithDefault("SystemStatus", "upload_upgrade_log", True)), + + # "UploadInstallerLog": dbus.Boolean( + # self.parent.configs_uncover.getWithDefault("SystemStatus", "upload_installer_log", False)) + # } + # else: + # return {} diff --git a/backend/SystemUpdater/__init__.py b/backend/SystemUpdater/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/SystemUpdater/backend/InstallBackendAptdaemon.py b/backend/SystemUpdater/backend/InstallBackendAptdaemon.py new file mode 100644 index 0000000..9fbe894 --- /dev/null +++ b/backend/SystemUpdater/backend/InstallBackendAptdaemon.py @@ -0,0 +1,529 @@ +#!/usr/bin/env python + +import aptdaemon.client as client +import aptdaemon.errors as errors +import aptdaemon.enums as enums +from aptdaemon.enums import ( + EXIT_SUCCESS, + EXIT_FAILED, + EXIT_CANCELLED, + STATUS_FINISHED, + get_error_description_from_enum, + get_error_string_from_enum, + get_status_string_from_enum + ) +from defer import inline_callbacks +from SystemUpdater.backend import InstallBackend +import logging +from gettext import gettext as _ +from SystemUpdater.Core.utils import humanize_size +import dbus,time +from gi.repository import GLib +import traceback +from importlib import reload + +# 超时检测 秒单位 +UPDATER_IDLE_CHECK_INTERVAL = 5 +#安装的超时检查20分钟 按照5秒检查一次 +INSTALL_IDLE_TIMEOUT = 4 * 60 +#更新超时检查 5分钟 +UPDATE_IDLE_TIMEOUT = 5 * 60 + +class InstallBackendAptdaemon(InstallBackend): + """Makes use of aptdaemon to refresh the cache and to install updates.""" + + def __init__(self, window_main, action,mode = InstallBackend.MODE_DEFAULT_STATUS): + InstallBackend.__init__(self, window_main, action,mode) + self.window_main = window_main + #切换aptdaemon的语言 重新导入模块就可以进行切换 + if self.window_main.aptd_lang_switch == True: + self.window_main.aptd_lang_switch = False + reload(client) + reload(errors) + reload(enums) + + #客户端连接aptdeamon的dbus接口 + self.client = client.AptClient() + self.trans_failed_msg = None + + #是否在安装状态 判断依据进度>50 + self.on_install_stage = False + if self.action == self.ACTION_INSTALL_SHUTDOWN: + self.on_install_stage = True + + if self.action == self.ACTION_UPDATE: + #更新的超时检查机制 超时时取消下载 + self.update_timestamp = 0 + GLib.timeout_add_seconds(UPDATER_IDLE_CHECK_INTERVAL, + self._check_update_inactivity) + + #定时模拟发送进度 为了让进度更加线性 + self.simulation_progress = 0 + GLib.timeout_add_seconds(1,self._check_simulation_progress) + + elif self.action == self.ACTION_INSTALL or self.action == self.ACTION_INSTALL_SHUTDOWN: + #安装的超时间检查 超时解除禁止关机 + self.install_timestamp = INSTALL_IDLE_TIMEOUT + self.check_progress = 0 + GLib.timeout_add_seconds(UPDATER_IDLE_CHECK_INTERVAL, + self._check_install_inactivity) + + def _check_simulation_progress(self): + self.simulation_progress += 20 + + if self.aptd_base.progress >= 90 or self.simulation_progress > 80: + return False + else: + self._dist_status_changed(self.action,[],self.simulation_progress,self.aptd_base.status,self.aptd_base.details) + return True + + def _check_install_inactivity(self): + """Shutdown the daemon if it has been inactive for time specified + in INSTALL_IDLE_TIMEOUT. + """ + logging.info("Checking for inactivity in Installing Time:%d...",self.install_timestamp) + + if self.window_main.now_working != self.ACTION_INSTALL and self.window_main.now_working != self.ACTION_INSTALL_SHUTDOWN: + logging.info("Installing to exit and timeout check quit...") + return False + + # 进度不同时 更新时间戳 + if self.aptd_base.progress != self.check_progress: + self.check_progress = self.aptd_base.progress + self.install_timestamp = INSTALL_IDLE_TIMEOUT + + #只有安装的时候启用 下载时候不使用 + if (self.install_timestamp <= 0 and self.on_install_stage == True): + logging.error("Quitting due to inactivity(%s)",self.aptd_base.details) + if self.action == self.ACTION_INSTALL_SHUTDOWN: + #关机安装模式 解除禁止关机锁 + self.window_main.inhibit_lock.close() + logging.info("Installtion timeout to exit Due to inactivity and Releasing the shutdown lock...") + else: + #超时只单独进行解锁关机 + self.inhibit_shutdown.unlock() + self._action_done(self.ACTION_INSTALL, + is_cancelled=False, success=False, + #FIXME: 安装超时退出 + error_string=_("Could not install the upgrades"), + error_desc=_("Installtion timeout to exit Due to inactivity") + self.aptd_base.details) + + # self.window_main.dbusController.Quit(None) + return False + else: + self.install_timestamp = self.install_timestamp - 1 + + return True + + def _check_update_inactivity(self): + logging.info("Checking for inactivity in Updating...") + #退出定时器 当更新完毕的时候 + if (self.aptd_base.cancelable == False and self.aptd_base.progress >= 90) or self.window_main.now_working != self.ACTION_UPDATE: + logging.info("Updating to exit and timeout check quit...") + return False + + #当更新进入下载状态时 记录进去的时间 + timestamp = self.update_timestamp + if self.aptd_base.cancelable == True and timestamp == 0: + self.update_timestamp = time.time() + return True + + #超时设置 + if self.update_timestamp != 0 and time.time() - self.update_timestamp > UPDATE_IDLE_TIMEOUT \ + and self.aptd_base.cancelable == True: + + logging.error("Quitting due to inactivity") + self.window_main.dbusController.transaction.cancel() + return False + + return True + + @inline_callbacks + def update(self): + """刷新包cache""" + try: + trans = yield self.client.update_cache(defer=True) + self.window_main.dbusController.transaction = trans + # 注册回调函数 接收更新的状态 + yield self._show_transaction(trans, self.ACTION_UPDATE, + _("Checking for updates…"), False) + except errors.NotAuthorizedError: + self._action_done(self.ACTION_UPDATE, + authorized=False, success=False, + error_string='', error_desc='') + except Exception: + self._action_done(self.ACTION_UPDATE, + is_cancelled=False, success=False, + error_string='', error_desc='') + raise + + @inline_callbacks + def commit(self,model,pkgs_install, pkgs_upgrade, pkgs_remove,pkgs_downgrade = []): + """Commit a list of package adds and removes""" + try: + reinstall = purge = [] + trans = yield self.client.commit_packages( + pkgs_install, reinstall, pkgs_remove, purge = purge, upgrade = pkgs_upgrade, + downgrade = pkgs_downgrade,defer=True) + self.window_main.dbusController.transaction = trans + + yield self._show_transaction(trans, self.action, + _("Installing updates…"), True) + except errors.NotAuthorizedError: + self._action_done(self.action, + authorized=False, success=False, + error_string='', error_desc='') + except errors.TransactionFailed as e: + self.trans_failed_msg = str(e) + except dbus.DBusException as e: + if e.get_dbus_name() != "org.freedesktop.DBus.Error.NoReply": + raise + self._action_done(self.action, + authorized=False, success=False, + error_string='', error_desc='') + except Exception: + self._action_done(self.action, + is_cancelled=False, success=False, + error_string='', error_desc='') + raise + + @inline_callbacks + def commit_only(self,model,pkgs_install, pkgs_upgrade, pkgs_remove,pkgs_downgrade = []): + """Commit a list of package adds and removes""" + try: + reinstall = purge = [] + trans = yield self.client.commit_only( + pkgs_install, reinstall, pkgs_remove, purge = purge, upgrade = pkgs_upgrade, + downgrade = pkgs_downgrade,download = model, defer=True) + self.window_main.dbusController.transaction = trans + + yield self._show_transaction(trans, self.action, + _("Installing updates…"), True) + except errors.NotAuthorizedError: + self._action_done(self.action, + authorized=False, success=False, + error_string='', error_desc='') + except errors.TransactionFailed as e: + self.trans_failed_msg = str(e) + except dbus.DBusException as e: + if e.get_dbus_name() != "org.freedesktop.DBus.Error.NoReply": + raise + self._action_done(self.action, + authorized=False, success=False, + error_string='', error_desc='') + except Exception: + self._action_done(self.action, + is_cancelled=False, success=False, + error_string='', error_desc='') + raise + + @inline_callbacks + def install_deb(self,install_path,install_force): + """安装deb包 """ + try: + trans = yield self.client.install_file(path = install_path,force = install_force,defer=True) + # 注册回调函数 接收更新的状态 + yield self._show_transaction(trans, self.ACTION_INSTALL_DEB, + _("Installing deb packages…"), False) + except errors.NotAuthorizedError: + self._action_done(self.ACTION_INSTALL_DEB, + authorized=False, success=False, + error_string='', error_desc='') + except Exception as e: + self._action_done(self.ACTION_INSTALL_DEB, + is_cancelled=False, success=False, + error_string=str(e), error_desc='') + # raise + + @inline_callbacks + def fix_broken(self): + """安装deb包 """ + try: + trans = yield self.client.fix_broken_depends(defer=True) + self.window_main.dbusController.transaction = trans + # 注册回调函数 接收更新的状态 + yield self._show_transaction(trans, self.ACTION_FIX_BROKEN, + _("Installing deb packages…"), False) + except errors.NotAuthorizedError: + self._action_done(self.ACTION_FIX_BROKEN, + authorized=False, success=False, + error_string='', error_desc='') + except Exception: + self._action_done(self.ACTION_FIX_BROKEN, + is_cancelled=False, success=False, + error_string='', error_desc='') + raise + + @inline_callbacks + def fix_incomplete(self): + """修复未完成的安装 """ + try: + trans = yield self.client.fix_incomplete_install(defer=True) + self.window_main.dbusController.transaction = trans + # 注册回调函数 接收更新的状态 + yield self._show_transaction(trans, self.ACTION_FIX_INCOMPLETE, + _("fix incomplete install"), False) + except errors.NotAuthorizedError: + self._action_done(self.ACTION_FIX_INCOMPLETE, + authorized=False, success=False, + error_string='', error_desc='') + except Exception: + self._action_done(self.ACTION_FIX_INCOMPLETE, + is_cancelled=False, success=False, + error_string='', error_desc='') + raise + + @inline_callbacks + def clean(self): + """清空所有下载的文件 """ + try: + trans = yield self.client.clean(defer=True) + self.window_main.dbusController.transaction = trans + # 注册回调函数 接收更新的状态 + yield self._show_transaction(trans, self.ACTION_CLEAN, + _("Remove all downloaded files."), False) + except errors.NotAuthorizedError: + self._action_done(self.ACTION_CLEAN, + authorized=False, success=False, + error_string='', error_desc='') + except Exception: + self._action_done(self.ACTION_CLEAN, + is_cancelled=False, success=False, + error_string='', error_desc='') + raise + + @inline_callbacks + def purge_packages(self,pkgs_purge): + """卸载deb包 """ + try: + # trans = yield self.client.remove_packages(package_names = pkgs_purge,defer=True) + trans = yield self.client.commit_packages([],[],[],pkgs_purge,[],[],defer=True) + self.window_main.dbusController.transaction = trans + # 注册回调函数 接收更新的状态 + yield self._show_transaction(trans, self.ACTION_REMOVE_PACKAGES, + _("Installing deb packages…"), False) + except errors.NotAuthorizedError: + self._action_done(self.ACTION_REMOVE_PACKAGES, + authorized=False, success=False, + error_string='', error_desc='') + except Exception as e: + logging.error(str(e)) + # self._action_done(self.ACTION_REMOVE_PACKAGES, + # is_cancelled=False, success=False, + # error_string=str(e), error_desc='') + + #进度回调 + def _on_progress_changed(self, trans,progress,action): + #不要101这种未知状态 + if progress == 101: + return + + #过滤掉不是线性的进度 + if progress > self.aptd_base.progress: + self.aptd_base.progress = progress + else: + return + + self.aptd_base.progress = progress + self._dist_status_changed(action,self.now_upgrade.upgrade_content,self.aptd_base.progress,self.aptd_base.status,self.aptd_base.details) + + + #同步状态回调 + def _on_status_changed(self, trans, status,action): + if action == self.ACTION_UPDATE and status == STATUS_FINISHED: + return + + #转化词条 + self.aptd_base.status = get_status_string_from_enum(status) + if self.aptd_base.status == None: + return + + self._dist_status_changed(action,self.now_upgrade.upgrade_content,\ + self.aptd_base.progress,self.aptd_base.status,self.aptd_base.details) + + #分发进度状态和细节信息 + def _dist_status_changed(self,action,upgrade_content = [],progress = 0,status = '',details = ''): + if action == self.ACTION_UPDATE: # 更新进度100后推迟发出100%的信号 -- 等待源过滤完成 + if progress == 11: + progress = 15 + + if progress != 100: + self.window_main.dbusController.UpdateDetectStatusChanged(progress,status) + elif action == self.ACTION_INSTALL: + #50%时候 属于下载状态切换到安装状态的过程 下面的代码只执行一次 + if progress >= 50 and progress < 90 and self.on_install_stage == False: + logging.info("The process is now in the installtion phase") + self.on_install_stage = True + self.safe_manager.shutdown_safe() + self._start_install_lock(_("Kylin System Updater")) + + #只处理从下载切换到安装时出现的网络问题 + #当网络波动时下载某些软件包失败时属于异常状态进行重试时 不发送后续进度 等待重试正常是 进行下载安装 + if self.now_upgrade.version_upgrade == True and progress >= 48 and self.on_install_stage != True and 'Failed to fetch' in self.aptd_base.error_details: + logging.warning("Arise Failed to fetch and Need retry Upgrade...") + self.now_upgrade.need_retry = True + return + + #在下载阶段发送取消信号 + if self.on_install_stage == False: + self.window_main.dbusController.Cancelable(self.aptd_base.cancelable) + + self.window_main.dbusController.UpdateDloadAndInstStaChanged(upgrade_content,progress,status,details) + elif action == self.ACTION_INSTALL_SHUTDOWN: + # 写入进度 到plymouth + self._progress_to_plymouth(progress) + self.window_main.dbusController.UpdateDloadAndInstStaChanged(upgrade_content,progress,status,details) + + elif action == self.ACTION_DOWNLOADONLY: + #只处理从下载切换到安装时出现的网络问题 + #当网络波动时下载某些软件包失败时属于异常状态进行重试时 不发送后续进度 等待重试正常是 进行下载安装 + if self.now_upgrade.version_upgrade == True and progress >= 48 and 'Failed to fetch' in self.aptd_base.error_details: + logging.warning("Arise Failed to fetch and Need retry Upgrade...") + self.now_upgrade.need_retry = True + return + + self.window_main.dbusController.Cancelable(self.aptd_base.cancelable) + self.window_main.dbusController.UpdateDloadAndInstStaChanged(upgrade_content,progress,status,details) + + elif action == self.ACTION_FIX_BROKEN: + self.window_main.dbusController.FixBrokenStatusChanged(False,True,progress,status,'','') + elif action == self.ACTION_REMOVE_PACKAGES: + self.window_main.dbusController.PurgePkgStatusChanged(progress,status,details) + elif action == self.ACTION_INSTALL_DEB or action == self.ACTION_BACKGROUND_UPGRADE: + self.window_main.dbusController.InstalldebStatusChanged(progress,status,details) + else: + logging.info("Other Action:progress = %d , status = %s ,details = %s",progress,status,details) + + def _on_details_changed(self, trans, details,action): + self.aptd_base.details = details + self._dist_status_changed(action,self.now_upgrade.upgrade_groups+self.now_upgrade.single_pkgs,\ + self.aptd_base.progress,self.aptd_base.status,self.aptd_base.details) + + def _on_download_changed(self, trans, details): + logging.info(details) + + # eta 剩余时间不正确,取消掉 + def _on_progress_download_changed(self,trans,current_items, total_items, currenty_bytes, total_bytes, current_cps, eta): + if self.action == self.ACTION_INSTALL or self.action == self.ACTION_DOWNLOADONLY or self.action == self.ACTION_BACKGROUND_UPGRADE: + self.window_main.dbusController.UpdateDownloadInfo(\ + self.now_upgrade.upgrade_groups+self.now_upgrade.single_pkgs,\ + current_items, total_items, \ + currenty_bytes, total_bytes, \ + current_cps) + else: + if self.action == self.ACTION_UPDATE or self.action == self.ACTION_REMOVE_PACKAGES: + return + logging.info("Other Action:current_items = %d, total_items = %d, currenty_bytes = %s, total_bytes = %s, current_cps = %s/s",\ + current_items, total_items, \ + humanize_size(currenty_bytes), humanize_size(total_bytes),\ + humanize_size(current_cps)) + + def _on_cancellable_changed(self, trans, Cancelable): + #下面的这些 不发送取消信号 + if self.action == self.ACTION_REMOVE_PACKAGES: + return + + if self.action != self.ACTION_UPDATE: + logging.info("\033[1;32m" + "Emitting" + "\033[0m" +" Cancelable: %r",Cancelable) + self.window_main.dbusController.Cancelable(Cancelable) + #增加取消信号的频发机制 + self.aptd_base.cancelable = Cancelable + + def _on_config_file_conflict(self, transaction, old, new): + logging.info("Config file conflict oldconf = %s , newconf = %s...",str(old),str(new)) + logging.info("Default To Replace Old Configfile...") + #默认替换旧的配置文件 + transaction.resolve_config_file_conflict(old, "keep") + # transaction.resolve_config_file_conflict(old, "keep") + + #增加记录当产生错误的时候 详细信息 + def _on_error_changed(self, trans,error_code, error_details): + # error_string = get_error_string_from_enum(error_code) + self.aptd_base.error_details = str(error_details) + logging.error(str(error_details)) + + @inline_callbacks + def _show_transaction(self, trans, action, header, show_details): + #更新和升级最后完成和失败都会走此在此进行完成之后的处理 + trans.connect("finished", self._on_finished, action) + #升级和更新的状态信息和进度 + trans.connect("status-changed", self._on_status_changed,action) + trans.connect("progress-changed", self._on_progress_changed,action) + #取消升级 + trans.connect("cancellable-changed", self._on_cancellable_changed) + #下载的进度信息 + trans.connect("progress-details-changed", self._on_progress_download_changed) + trans.connect("status-details-changed", self._on_details_changed,action) + trans.connect("error", self._on_error_changed) + + trans.connect("config-file-conflict", self._on_config_file_conflict) + + # yield trans.set_debconf_frontend("ukui") + # yield trans.set_locale(os.environ["LANGUAGE"] + ".UTF-8") + yield trans.run() + + def _on_finished(self, trans, status, action): + try: + error_string = '' + error_desc = '' + #退出 + self.on_install_stage = False + if status == EXIT_FAILED: + # self.log_audit(str(trans.error.code)) + error_string = get_error_string_from_enum(trans.error.code) + error_desc = get_error_description_from_enum(trans.error.code) + if self.trans_failed_msg: + error_desc = error_desc + "\n" + self.trans_failed_msg + #取消下载 + elif status == EXIT_CANCELLED: + error_string = _("Failed to fetch") + error_desc = _("_Cancel Upgrade") + elif status == EXIT_SUCCESS and action == self.ACTION_INSTALL: + error_string = _("System upgrade is complete.") + elif status == EXIT_SUCCESS and action == self.ACTION_REMOVE_PACKAGES: + error_string = _("Uninstallation completed") + + is_success = (status == EXIT_SUCCESS) + self._action_done(action, + is_cancelled=(status == EXIT_CANCELLED), success=is_success, + error_string=error_string, error_desc=error_desc) + except Exception as e: + logging.error(e) + traceback.print_exc() + + # def log_audit(self,error_code): + # if error_code == "error-package-manager-failed": + # error_msg = self.check_install_error() + + # with open("/var/log/kylin-system-updater/error_details.log", 'w+') as configfile: + # configfile.write(str(error_msg)) + + # def check_install_error(self): + # locate_error = '' + # with open("/var/log/apt/term.log", "r+") as f: + # log_start = None + # log_end = None + # aptterm_log = f.readlines() + + # reverse_log = aptterm_log[::-1] + + # for logstr in reverse_log: + # if log_end == None and "Log ended:" in logstr: + # log_end = aptterm_log.index(logstr) + # if log_start == None and "Log started:" in logstr: + # log_start = aptterm_log.index(logstr) + + # if log_end != None and log_start != None: + # break + + # latest_log = aptterm_log[log_start:log_end+1] + # error_deb = latest_log[-2].strip() + + # error_msg_line = None + # for log_msg in latest_log: + # if error_deb in log_msg: + # error_msg_line = latest_log.index(log_msg) + # locate_error = latest_log[error_msg_line-5:error_msg_line+5] + # break + + # return locate_error \ No newline at end of file diff --git a/backend/SystemUpdater/backend/__init__.py b/backend/SystemUpdater/backend/__init__.py new file mode 100644 index 0000000..763f4f6 --- /dev/null +++ b/backend/SystemUpdater/backend/__init__.py @@ -0,0 +1,1243 @@ +#!/usr/bin/env python +# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- + +"""Integration of package managers into SystemUpdater""" + +import os +import apt +import apt_pkg +import json +import logging +import subprocess +import traceback +import shutil +import fcntl +import dbus +import threading +from apt import Cache +from gettext import gettext as _ +from SystemUpdater.Core.errors import * +from SystemUpdater.Core.enums import * +from SystemUpdater.Core.DataAcquisition import get_east_8_time +from SystemUpdater.Core.UpdateList import LocalUpgradeDataList +from SystemUpdater.Core.DistUpgradeCache import NotEnoughFreeSpaceError +from SystemUpdater.Core.utils import get_config_patch + +class NowUpgradeMeta: + """ + Represent the (potentially partial) results of an unattended-upgrades + run + """ + def __init__(self, + parent, + upgrade_groups = [], + single_pkgs = [], + version_upgrade = False, + need_retry = False, + ): + self.parent = parent + #组列表中包含的包 + self.upgrade_groups = upgrade_groups + #推送的可升级的单包 + self.single_pkgs = single_pkgs + self.version_upgrade = version_upgrade + self.need_retry = need_retry + self.upgrade_content = self.upgrade_groups + self.single_pkgs + + #获取当前升级的升级列表 + def _make_groups_list(self,upgrade_data,_upgrade_mode,partial_upgrade_list): + groups_list = [] + pkg_list = [] + + #部分升级 + if _upgrade_mode == self.parent.MODE_INSTALL_PARTIAL: + for elem in partial_upgrade_list: + #组升级方式 + if elem in upgrade_data.upgrade_groups: + groups_list.append(elem) + #单包升级方式 + elif elem in upgrade_data.single_pkgs: + pkg_list.append(elem) + else: + logging.warning("this package(%s) not in selected list",elem) + + #全部升级列表 + elif _upgrade_mode == self.parent.MODE_INSTALL_ALL: + groups_list = upgrade_data.upgrade_groups + pkg_list = upgrade_data.single_pkgs + + return groups_list,pkg_list + + def make_upgrade_content(self,content): + if self.parent.action_mode != self.parent.MODE_INSTALL_SINGLE: + self.upgrade_groups,self.single_pkgs = self._make_groups_list(self.parent.upgrade_data,self.parent.action_mode,content) + self.upgrade_content = self.upgrade_groups + self.single_pkgs + else: + self.single_pkgs = content + self.upgrade_content = self.upgrade_groups + self.single_pkgs + +class UpdateEssentialItem(): + def __init__(self,parent): + self.essential_pkgs = [] + self.remove_white = [] + + self.read_path = get_config_patch() + 'kylin-update-desktop-system.json' + self.update() + + def update(self): + if os.path.exists(self.read_path): + with open(self.read_path,'r') as f: + try: + data = json.load(f) + self.essential_pkgs = data['install_list'] + except Exception as exc: + logging.warning(exc) + try: + self.remove_white = data['remove_white_list'] + except Exception as exc: + pass + # logging.warning(exc) + + def check_essential(self,remove_pkgs): + logging.info("Check: For remove of Essential Packages...") + for pkg in remove_pkgs: + if pkg in self.essential_pkgs: + logging.error("Essential Packages(%s) will be Removed...",pkg) + raise UpdateBaseError(ERROR_REMOVE_ESSENTIAL_PACKAGES) + + def check_white(self,remove_pkgs): + logging.info("Check: remove pkg in White Packages...") + new_upgrade_list = list(set(remove_pkgs) & set(self.remove_white)) + + for pkg in new_upgrade_list: + if pkg in remove_pkgs: + logging.info("%s will be remove in remove pkg...",pkg) + remove_pkgs.remove(pkg) + + +class AptdBaseInformation: + """ + 进度 状态 状态细节信息 + """ + def __init__(self, + progress = 0, + status='', + details='', + cancelable = False, + error_details = '', + ): + + self.progress = progress + self.status = status + self.details = details + self.cancelable = cancelable + self.error_details = error_details + +class InstallBackend(): + INSTALL_OUTPUT_JSON = "/var/lib/kylin-system-updater/json/showdown_install.json" + + ACTION_DEFUALT_STATUS = -1 + ACTION_UPDATE = 0 + ACTION_INSTALL = 1 + ACTION_INSTALL_DEB = 2 + ACTION_CHECK_RESOLVER = 3 + ACTION_DOWNLOADONLY = 4 + ACTION_FIX_BROKEN = 5 + ACTION_REMOVE_PACKAGES = 6 + ACTION_FIX_INCOMPLETE = 7 + ACTION_CLEAN = 8 + ACTION_INSTALL_SHUTDOWN = 9 + ACTION_BACKGROUND_UPGRADE = 10 + ACTION_CHECK_BROKEN = 11 + + MODE_DEFAULT_STATUS = -1 + + #1、ACTION_INSTALL 安装的子类 + #部分升级 + MODE_INSTALL_PARTIAL = 0 + #全部升级 + MODE_INSTALL_ALL = 1 + #系统全盘升级 + MODE_INSTALL_SYSTEM = 2 + #后端内部安装包使用 目前 更新配置包和升级本身使用 + MODE_INSTALL_SINGLE = 3 + + #2、更新的子类 + MODE_UPDATE_CACHE = 0 + MODE_UPDATE_ALL = 1 + + def __init__(self, window_main, action,mode = MODE_DEFAULT_STATUS): + self.window_main = window_main + self.cache = window_main.cache + self.action = action + self.action_mode = mode + + self.window_main.now_working = action + + self.aptd_base = AptdBaseInformation() + + self.inhibit_shutdown = InhibitShutdownLock() + + self.update_essential = UpdateEssentialItem(self) + + if self.action == self.ACTION_INSTALL: + self.safe_manager = UpdateSafeManager() + + #更新的时候此对象还未生成 + if self.window_main.update_list != None: + self.upgrade_data = window_main.update_list.upgrade_meta + else: + self.upgrade_data = LocalUpgradeDataList() + + #要拿到升级列表必须调用_make_upgrade_list + self.now_upgrade = NowUpgradeMeta(parent=self) + + def start(self,partial_upgrade_list = []): + try: + #安装升级包 首先必须调用ACTION_CHECK_RESOLVER 计算依赖解决方便 标记cache 进行升级 + if self.action == self.ACTION_INSTALL or self.action == self.ACTION_DOWNLOADONLY: + #拿到升级列表 + self.now_upgrade.make_upgrade_content(partial_upgrade_list) + + pkgs_install,pkgs_upgrade,pkgs_remove,pkgs_downgrade = self._get_mark_from_cache(self.cache,self.upgrade_data.adjust_pkgs,self.action_mode) + logging.info("INSTALL install:%d , upgrade:%d remove:%d pkgs_downgrade:%d",len(pkgs_install),\ + len(pkgs_upgrade),len(pkgs_remove),len(pkgs_downgrade)) + + #当下载数量大于200个包时 就认为属于大版本升级 开启重试机制 + if len(pkgs_install) + len(pkgs_upgrade) > 100: + logging.info("Open a major version upgrade and Retry mechanism on...") + self.now_upgrade.version_upgrade = True + + #检查是否存在可升级的包 + if len(pkgs_install) == 0 and len(pkgs_upgrade) == 0 and len(pkgs_remove) == 0 and len(pkgs_downgrade) == 0: + pkgs_install,pkgs_upgrade = self._make_pkgs_list(self.cache,self.upgrade_data.groups_pkgs,self.now_upgrade.upgrade_groups,self.now_upgrade.single_pkgs) + logging.warning("There is an exception in the update package install = %r upgrade = %r",pkgs_install,pkgs_upgrade) + + # if self.action_mode == self.MODE_INSTALL_SINGLE: + # logging.warning("MODE_INSTALL_SINGLE install:%s , upgrade:%s remove:%s",str(pkgs_install),str(pkgs_upgrade),str(pkgs_remove)) + + self.update_essential.check_essential(pkgs_remove) + + #检查磁盘的状态 + self.check_free_space(self.cache) + + for ul in self.window_main.collector.upgrade_list: + self.window_main.collector.Upgrade_Process_Msg(self.action, {"appname":ul}) + + if self.action == self.ACTION_INSTALL: + self.commit(self.action,pkgs_install, pkgs_upgrade, pkgs_remove,pkgs_downgrade) + elif self.action == self.ACTION_DOWNLOADONLY: + self._update_to_config(self.now_upgrade,pkgs_install,pkgs_upgrade,pkgs_remove) + self.commit_only(self.action,pkgs_install, pkgs_upgrade, pkgs_remove,pkgs_downgrade) + + elif self.action == self.ACTION_INSTALL_SHUTDOWN: + self.now_upgrade,pkgs_install,pkgs_upgrade,pkgs_remove = self._config_to_upgrade() + logging.info("ACTION_INSTALL_SHUTDOWN install:%d , upgrade:%d remove:%d",len(pkgs_install),len(pkgs_upgrade),len(pkgs_remove)) + + # self._start_install_lock() + self.commit(self.action,pkgs_install, pkgs_upgrade, pkgs_remove) + + #计算依赖解决方案 + elif self.action == self.ACTION_CHECK_RESOLVER: + #被删除包的描述 + raw_description = [] + + #判断是否配置aptdaemon的限速 + self.window_main.check_conifg_aptdeamon() + + if self.action_mode != self.MODE_INSTALL_SYSTEM: + #获取要升级的组列表 + self.now_upgrade.make_upgrade_content(partial_upgrade_list) + #获取要升级和安装的包列表 + pkgs_install,pkgs_upgrade = self._make_pkgs_list(self.cache,self.upgrade_data.groups_pkgs,self.now_upgrade.upgrade_groups,self.now_upgrade.single_pkgs) + #计算解决依赖关系 + self._make_problem_resolver(self.cache,pkgs_install,pkgs_upgrade,self.upgrade_data.adjust_pkgs) + pkgs_install,pkgs_upgrade,pkgs_remove,pkgs_downgrade = self._get_mark_from_cache(self.cache,self.upgrade_data.adjust_pkgs,self.action_mode) + else: + # 使用全盘升级 全盘使用dist-upgrade + if self.cache.get_changes(): + self.cache.clear() + self.cache._depcache.upgrade(True) + pkgs_install,pkgs_upgrade,pkgs_remove,pkgs_downgrade = self._get_mark_from_cache(self.cache,self.upgrade_data.adjust_pkgs,self.action_mode) + + logging.warning("ProblemResolver of the deletion package list:%s",str(pkgs_remove)) + self.update_essential.check_white(pkgs_remove) + logging.info("RESOLVER install:%d , upgrade:%d remove:%d pkgs_downgrade:%d",len(pkgs_install),len(pkgs_upgrade),\ + len(pkgs_remove),len(pkgs_downgrade)) + is_remove_pkgs = len(pkgs_remove) != 0 + + # 数据上报 + self.window_main.collector.Generate_Msg(self.now_upgrade.upgrade_groups+self.now_upgrade.single_pkgs, self.action_mode) + errorCode = "" + if is_remove_pkgs: + errorCode = _("Need remove pkgs: ")+", ".join(pkgs_remove) + for ul in self.window_main.collector.upgrade_list: + self.window_main.collector.Upgrade_Process_Msg(self.action, {"appname":ul, "status":is_remove_pkgs, "errorCode":errorCode}) + + #补充删除包的描述信息,删除描述 + delete_desc = [] + for pkg in pkgs_remove: + pkg_obj = self.cache[pkg] + raw_description.append(getattr(pkg_obj.candidate, "summary", '')) + delete_desc.append('') + + if self.action_mode != self.MODE_INSTALL_SYSTEM: + self.window_main.dbusController.UpdateDependResloveStatus(True,is_remove_pkgs,pkgs_remove,raw_description,delete_desc,'','') + else: + self.window_main.dbusController.DistupgradeDependResloveStatus(True,is_remove_pkgs,pkgs_remove,raw_description,delete_desc,'','') + + self._action_done(self.action,False,True,'','') + + if is_remove_pkgs: + threading_emulate = threading.Thread(target=self._emulate_calcul_delete,args=((pkgs_upgrade + pkgs_install),)) + threading_emulate.start() + + #修复未满足的依赖关系 与apt-get -f install匹配 + elif self.action == self.ACTION_CHECK_BROKEN: + try: + #计算依赖的解决方案 + self.cache.fix_broken() + except Exception as e: + logging.error(e) + raise UpdateBaseError(ERROR_RESOLVER_FAILED) + + pkgs_install,pkgs_upgrade,pkgs_remove = self._get_pkgs_from_cache(self.cache) + logging.warning("Fix broken Packages is need to install:%a and upgrade:%a and remove:%a",pkgs_install,pkgs_upgrade,pkgs_remove) + + #当存在需要卸载包的情况时 不进行修复 + if len(pkgs_remove) == 0: + logging.warning("start fix system broken pkgs...") + self.fix_broken() + else: + raw_description = [] + delete_desc = [] + #添加关于删除包的描述信息 + for pkg in pkgs_remove: + delete_desc.append('') + pkg_obj = self.cache[pkg] + raw_description.append(getattr(pkg_obj.candidate, "summary", '')) + + self.window_main.dbusController.UpdateFixBrokenStatus(True,True,pkgs_remove,raw_description,delete_desc,'','') + self._action_done(self.action,False,True,'','') + + elif self.action == self.ACTION_FIX_BROKEN: + pkgs_install,pkgs_upgrade,pkgs_remove = self._get_pkgs_from_cache(self.cache) + + self.update_essential.check_essential(pkgs_remove) + self.fix_broken() + # 修复不完整的安装dpkg configure -a + elif self.action == self.ACTION_FIX_INCOMPLETE: + self.fix_incomplete() + #卸载包 + elif self.action == self.ACTION_REMOVE_PACKAGES: + self._start_install_lock(_("Kylin System Updater")) + self.purge_packages(partial_upgrade_list) + elif self.action == self.ACTION_CLEAN: + self.clean() + #更新cache + elif self.action == self.ACTION_UPDATE: + #检查依赖之前 判断是否需要重启aptdeamon 目前为了生效限速功能 + self.window_main.check_conifg_aptdeamon() + self.update() + except UpdateBaseError as excep: + self._action_done(self.action,True,False,excep.header,excep.desc,excep.code) + except UpdateProgressExit as excep: + pass + except Exception as e: + logging.error(e) + traceback.print_exc() + + def start_alone(self,partial_upgrade_list = [],_is_install = False, caller=''): + # 安装本地deb包的接口 + if self.action == self.ACTION_INSTALL_DEB: + try: + self._start_install_lock(caller=caller) + self.install_deb(install_path = partial_upgrade_list, install_force = _is_install) + except Exception as e: + logging.error(str(e)) + # 安装在线包的接口 + elif self.action == self.ACTION_BACKGROUND_UPGRADE: + try: + pkgs_install = [ str(pkg) for pkg in partial_upgrade_list] + logging.info("Install deb package, open cache") + cache = Cache() + for pkg_name in pkgs_install: + if pkg_name not in cache: + if "=" not in pkg_name or ("=" in pkg_name and pkg_name.split("=")[0] not in cache): + # 没找到包或格式不正确 + self.window_main.dbusController.UpdateInstallFinished(False, pkgs_install, "'"+pkg_name+"' is not in cache", "") + return + self._start_install_lock(caller=caller) + self.commit(self.ACTION_INSTALL,pkgs_install,[],[]) + except Exception as e: + logging.error(str(e)) + self.window_main.dbusController.UpdateInstallFinished(False, pkgs_install, str(e), "") + + def update(self): + """Run a update to refresh the package list""" + raise NotImplementedError + + def clean(self): + """Remove all downloaded files.t""" + raise NotImplementedError + + def commit(self,model,pkgs_install, pkgs_upgrade, pkgs_remove,pkgs_downgrade = []): + """Commit the cache changes """ + raise NotImplementedError + + def install_deb(self,install_path,install_force): + """install_deb the cache changes """ + raise NotImplementedError + + def fix_broken(self): + """fix_broken_depends""" + raise NotImplementedError + + def fix_incomplete(self): + """fix_incomplete_install""" + raise NotImplementedError + + def purge_packages(self,pkgs_purge): + """purge_packages""" + raise NotImplementedError + + def _get_pkgs_from_cache(self,cache): + pkgs_install = [] + pkgs_upgrade = [] + pkgs_remove = [] + + #计算修复破损包 方案 需要删除 新装那些包 + for pkg in cache: + try: + if pkg.marked_install : + pkgs_install.append(pkg.name) + elif pkg.marked_upgrade: + pkgs_upgrade.append(pkg.name) + elif pkg.marked_delete: + pkgs_remove.append(pkg.name) + except KeyError: + # pkg missing from fresh_cache can't be modified + pass + return pkgs_install,pkgs_upgrade,pkgs_remove + + #从cache中拿到标记的列表 + def _get_mark_from_cache(self,cache,_adjust_pkgs,upgrade_mode): + pkgs_install = [] + pkgs_upgrade = [] + pkgs_remove = [] + pkgs_downgrade = [] + + #全盘升级不做任何的调整 + if upgrade_mode == self.MODE_INSTALL_SYSTEM or upgrade_mode == self.MODE_INSTALL_SINGLE: + adjust_pkgs = [] + else: + #获取调整包列表 去掉版本号 + adjust_pkgs = [i.split("=")[0] for i in _adjust_pkgs] + + for pkg in cache: + try: + if pkg.marked_install: + pkgname = pkg.name + if pkg.is_auto_installed: + pkgname += "#auto" + if pkg.name in adjust_pkgs: + pkgs_install.append(_adjust_pkgs[adjust_pkgs.index(pkg.name)]) + else: + pkgs_install.append(pkgname) + elif pkg.marked_upgrade: + if pkg.name in adjust_pkgs: + pkgs_upgrade.append(_adjust_pkgs[adjust_pkgs.index(pkg.name)]) + else: + pkgs_upgrade.append(pkg.name) + elif pkg.marked_delete: + pkgs_remove.append(pkg.name) + elif pkg.marked_downgrade: + pkgs_downgrade.append(pkg.name+'='+pkg.candidate.source_version) + except KeyError: + pass + return pkgs_install,pkgs_upgrade,pkgs_remove,pkgs_downgrade + + #获取当前升级的升级列表 + def _make_groups_list(self,upgrade_data,_upgrade_mode,partial_upgrade_list): + groups_list = [] + pkg_list = [] + + #部分升级 + if _upgrade_mode == self.MODE_INSTALL_PARTIAL: + for elem in partial_upgrade_list: + #组升级方式 + if elem in upgrade_data.upgrade_groups: + groups_list.append(elem) + #单包升级方式 + elif elem in upgrade_data.single_pkgs: + pkg_list.append(elem) + else: + logging.warning("this package(%s) not in selected list",elem) + + #全部升级列表 + elif _upgrade_mode == self.MODE_INSTALL_ALL: + groups_list = upgrade_data.upgrade_groups + pkg_list = upgrade_data.single_pkgs + + return groups_list,pkg_list + + #从本地中获取本次升级需要升级的包 部分升级和全部升级使用 全盘升级不适用 + def _make_pkgs_list(self,cache,groups_pkgs,groups_list,pkg_list): + pkgs_install = [] + pkgs_upgrade = [] + + try: + #单包的升级方式 + for pkg in pkg_list: + if cache[pkg].is_installed: + pkgs_upgrade.append(pkg) + else: + pkgs_install.append(pkg) + + #遍历升级组列表 + for group_name in groups_list: + pkgs_install += groups_pkgs.get(group_name,[]).get('pkgs_install',[]) + pkgs_upgrade += groups_pkgs.get(group_name,[]).get('pkgs_upgrade',[]) + + return pkgs_install,pkgs_upgrade + except Exception as e: + logging.error(str(e)) + return [],[] + + def _installed_dependencies(self, pkg_name, all_deps=None): + """Recursively return all installed dependencies of a given package.""" + # FIXME: Should be part of python-apt, since it makes use of non-public + # API. Perhaps by adding a recursive argument to + # apt.package.Version.get_dependencies() + if not all_deps: + all_deps = set() + if pkg_name not in self.cache: + return all_deps + cur = self.cache[pkg_name]._pkg.current_ver + if not cur: + return all_deps + for sec in ("PreDepends", "Depends", "Recommends"): + try: + for dep in cur.depends_list[sec]: + dep_name = dep[0].target_pkg.name + if dep_name not in all_deps: + all_deps.add(dep_name) + all_deps |= self._installed_dependencies(dep_name, + all_deps) + except KeyError: + pass + return all_deps + + def _split_package_id(self,package): + """Return the name, the version number and the release of the + specified package.""" + if "=" in package: + name, version = package.split("=", 1) + release = None + elif "/" in package: + name, release = package.split("/", 1) + version = None + else: + name = package + version = release = None + return name, version, release + + #将获取本次升级的包 进行计算依赖关系 解决依赖问题 + def _make_problem_resolver(self,cache,pkgs_install = [],pkgs_upgrade = [],adjust_pkgs = []): + try: + logging.info("ProblemResolver install:%d , upgrade:%d",len(pkgs_install),len(pkgs_upgrade)) + logging.info("Start calculating dependencies...") + + #actiongroup 可以加速计算依赖关系 计算花费的时间将大幅度缩减 + with cache.actiongroup(): + if cache.get_changes(): + cache.clear() + resolver = apt.cache.ProblemResolver(cache) + + for pkg_name, pkg_ver, pkg_rel in [self._split_package_id(pkg) + for pkg in adjust_pkgs]: + try: + pkg = cache[pkg_name] + pkg.candidate = pkg.versions[pkg_ver] + except KeyError: + logging.warning("The version %s of %s isn't available",pkg_ver, pkg_name) + continue + + for pkg in pkgs_upgrade + pkgs_install: + pkg_cache = cache[pkg] + if pkg_cache.is_upgradable == False and pkg_cache.is_installed == True: + logging.warning("pkg(%s) not upgrade or install and will be continue...",pkg) + continue + + pkg_cache.mark_install(False, True, True) + + if pkg_cache.is_upgradable == True: + auto = pkg_cache.is_auto_installed + pkg_cache.mark_auto(auto) + + if pkg_cache.marked_keep == True: + pkg_cache.mark_install(False, False, True) + if pkg_cache.marked_keep == True: + raise + + resolver.clear(pkg_cache) + resolver.protect(pkg_cache) + + resolver.resolve() + return + except Exception as e: + logging.error(str(e)) + pkg_string = '' + for pkg in pkgs_install + pkgs_upgrade: + pkg_string = pkg_string + ' ' + str(pkg) + logging.error('Resolver calculation Packages List: '+pkg_string+'\n') + terminal_msg = self.window_main.simulate_mode.dependencies_broken(pkgs_install + pkgs_upgrade) + logging.error(terminal_msg) + raise UpdateBaseError(ERROR_RESOLVER_FAILED, + desc= terminal_msg) + + def _emulate_calcul_delete(self,total_pkg): + logging.info("[DELETE_DETAILS] Start calculating delete dependencies...") + delete_pkgs = [] + last_delete_count = 0 + cache = Cache() + + with cache.actiongroup(): + if cache.get_changes(): + cache.clear() + for pkg in total_pkg: + if '#' in pkg: + pkg = pkg.split("#")[0] + elif '=' in pkg: + pkg = pkg.split("=")[0] + + try: + cache[pkg].mark_install() + except Exception as e: + logging.error(str(e)) + break + + if cache.delete_count != last_delete_count: + last_delete_count = cache.delete_count + for pkg_obj in cache.get_changes(): + if pkg_obj.marked_delete and pkg_obj.name not in delete_pkgs: + delete_pkgs.append(pkg_obj.name) + delete_pkgs.append(pkg) + logging.info("[DELETE_DETAILS] %s Will be deleted Due to Install or Upgrade %s",pkg_obj.name,pkg) + + logging.info("[DELETE_DETAILS] End calculating delete dependencies...") + cache.close() + + def _send_error_code(self,error_code): + if error_code == ERROR_NOT_DISK_SPACE: + self.window_main.dbusController.InstallDetectStatus(False,error_code) + + def _self_upgrade_finished(self,success,error_string='',error_desc=''): + if success: + if self.window_main.configs_cover.getWithDefault("SystemStatusCover", "priority_upgrade_restart", False,True) == True: + error_string = get_error_string_from_enum(PRIORITY_UPGRADE_SUCCCESSED) + if self.window_main.APTD_PKG_NAME in self.now_upgrade.upgrade_content: + self.window_main.dbusController.UpdateDetectFinished(False,[self.window_main.BACKEND_PKG_NAME],error_string,'') + self.window_main.dbusController.make_aptdeamon_restart() + + #当单包升级的时候 升级本身时,让程序退出,再重新启动 + if self.window_main.FRONTEND_PKG_NAME in self.now_upgrade.upgrade_content: + self.window_main.dbusController.UpdateDetectFinished(False,[self.window_main.BACKEND_PKG_NAME],error_string,'') + + #当单包升级的时候 升级本身时,让程序退出,再重新启动 + if self.window_main.BACKEND_PKG_NAME in self.now_upgrade.upgrade_content: + self.window_main.dbusController.UpdateDetectFinished(False,[self.window_main.BACKEND_PKG_NAME],error_string,'') + #升级本身完成后 退出 有systemd 来进行重启服务 + self.window_main.dbusController.Quit(None) + return + + #当单包升级的时候 升级本身时,让程序退出,再重新启动 + if self.window_main.GROUPS_PKG_NAME in self.now_upgrade.upgrade_content: + self.window_main.start_available() + else: + self.window_main.start_available() + else: + self.window_main.dbusController.UpdateDetectFinished(False,[''],get_error_string_from_enum(ERROR_UPDATE_DEFAULT_FAILED),\ + error_string +' '+error_desc) + + #调用aptdeamon结束之后处理的地方 不管是出错还是正常都在此处理 + def _action_done(self, action, is_cancelled,success, error_string='',error_desc='',error_code=''): + #后端的状态 到空闲状态 + self.window_main.now_working = self.ACTION_DEFUALT_STATUS + + #升级完成后走的分支 + if action == self.ACTION_INSTALL: + false_num = 0 + self.safe_manager.reset_safe() + self._release_install_lock() + self._send_error_code(error_code) + + if self.action_mode == self.MODE_INSTALL_SINGLE: + self._self_upgrade_finished(success,error_string,error_desc) + else: + if self.now_upgrade.version_upgrade == True and self.now_upgrade.need_retry == True and success == False: + #增加重试次数的限制 + if self.window_main.retry_limit != 0: + logging.warning("Retry the upgrade and installaton(%s) and retry number: %d",self.now_upgrade.upgrade_content,self.window_main.retry_limit) + self.window_main.start_install(self.action_mode,True,self.now_upgrade.upgrade_content) + self.window_main.retry_limit = self.window_main.retry_limit - 1 + return + try: + false_num = self._make_insert_info(success,is_cancelled,self.now_upgrade,error_string,error_desc) + except Exception as e: + logging.error(e) + + if success == False: + logging.info("The number of failed installations or upgrades is %d",false_num) + + #修复bug 所有的包都安装成功了,但是却返回的失败,计算这种状况 从失败切换到安装成功的状态 + #这个 为True的很低 大多数为False 只有当系统中apt 出现问题时 才会复现这种问题 + if success == False and false_num == 0 and self.now_upgrade.upgrade_content != []: + logging.warning("Special Case switch success status from False to True...") + # success = True + + if success: + #当组列表为空时 表示现在的单独进行安装某些包或卸载,不发信号到控制面板 + #升级完成后从升级列表删除 + for groups in self.now_upgrade.upgrade_groups: + self.upgrade_data.upgrade_groups.remove(groups) + + for pkg in self.now_upgrade.single_pkgs: + self.upgrade_data.single_pkgs.remove(pkg) + + error_string = '' + error_desc = '' + + self.window_main.dbusController.UpdateInstallFinished(success,self.now_upgrade.upgrade_content,error_string,error_desc) + + if success: + self.window_main.dbusController.CheckRebootRequired("self") + #安装完成之后 更新一次cache + logging.info("Install or Upgrade successful, so Now update Cache...") + self.window_main.refresh_cache() + + elif action == self.ACTION_INSTALL_SHUTDOWN: + # self._release_install_lock() + self._send_error_code(error_code) + #插入数据库 + self.window_main.sqlite3_server.insert_info(self.ACTION_INSTALL,self.now_upgrade.single_pkgs,\ + self.now_upgrade.upgrade_groups,[],success,error_string,error_desc) + if success == True: + #当升级完成时 将手动和自动安装的标志位全部清空 + self.window_main.install_mode.install_finished() + + self.window_main.dbusController.UpdateInstallFinished(success,self.now_upgrade.upgrade_content,error_string,error_desc) + + #释放锁 允许关机 + if self.window_main.install_mode.inhibit_lock != None: + self.window_main.install_mode.inhibit_lock.close() + logging.info("Install Packages Finished and Releasing the shutdown lock...") + else: + logging.error("Install Packages Finished and Releasing the shutdown lock Failed...") + #升级本身完成后 来释放关机锁 + self.window_main.dbusController.Quit(None) + + elif action == self.ACTION_CHECK_RESOLVER: + if success == False: + self.window_main.sqlite3_server.insert_info(self.action_mode,self.now_upgrade.single_pkgs,\ + self.now_upgrade.upgrade_groups,[],success,error_string,error_desc) + + if self.action_mode != self.MODE_INSTALL_SYSTEM: + self.window_main.dbusController.UpdateDependResloveStatus(success,False,[''],[''],[''],error_string,error_desc) + else: + self.window_main.dbusController.DistupgradeDependResloveStatus(success,False,[''],[''],[''],error_string,error_desc) + + elif action == self.ACTION_DOWNLOADONLY: + if self.now_upgrade.version_upgrade == True and self.now_upgrade.need_retry == True and success == False: + #增加重试次数的限制 + if self.window_main.retry_limit != 0: + logging.warning("Retry the upgrade and installaton(%s) and retry number: %d",self.now_upgrade.upgrade_content,self.window_main.retry_limit) + self.window_main.start_install(self.action_mode,True,self.now_upgrade.upgrade_content) + self.window_main.retry_limit = self.window_main.retry_limit - 1 + return + + if success == True and self.action_mode == self.MODE_INSTALL_PARTIAL: + self.window_main.install_mode.tmp_content += self.now_upgrade.upgrade_content + + if success == False: + self.window_main.sqlite3_server.insert_info(self.action_mode,self.now_upgrade.single_pkgs,\ + self.now_upgrade.upgrade_groups,[],success,error_string,error_desc) + + #如果下载成功 就标志需要 安装重启 + if success == True: + #安装成功的话再获取关机锁 + self.window_main.install_mode.get_inhibit_lock() + + self.window_main.install_mode.set_shutdown_install(success) + + self.window_main.dbusController.UpdateDownloadFinished(success,self.now_upgrade.upgrade_content,error_string,error_desc) + + elif action == self.ACTION_UPDATE and self.action_mode == self.MODE_UPDATE_ALL: + if success == False: + # threading_emulate = threading.Thread(target=self.emulate_update) + # threading_emulate.start() + + if ERROR_UPDATE_KEY_SIGNATURES in self.aptd_base.error_details: + error_desc = get_error_description_from_enum(ERROR_UPDATE_KEY_SIGNATURES) + elif ERROR_UPDATE_NET_AUTHENTICATION in self.aptd_base.error_details: + error_desc = get_error_description_from_enum(ERROR_UPDATE_NET_AUTHENTICATION) + elif ERROR_UPDATE_NOTREAD_SOURCES in self.aptd_base.error_details: + error_desc = get_error_description_from_enum(ERROR_UPDATE_NOTREAD_SOURCES) + elif ERROR_UPDATE_INVALID_TIME in self.aptd_base.error_details: + error_desc = get_error_description_from_enum(ERROR_UPDATE_INVALID_TIME) + + if success: + #开始生成列表 + self.window_main.start_available() + else: + self.window_main.dbusController.UpdateDetectFinished(success,[''],get_error_string_from_enum(ERROR_UPDATE_DEFAULT_FAILED),\ + error_string +' '+error_desc) + self.window_main.sqlite3_server.insert_into_display("check_time",get_east_8_time()[0:-4]) + + elif action == self.ACTION_UPDATE and self.action_mode == self.MODE_UPDATE_CACHE: + self.window_main.dbusController.UpdateDetectFinished(success,[''],error_string,error_desc) + elif action == self.ACTION_FIX_BROKEN: + self.window_main.dbusController.FixBrokenStatusChanged(True,success,100,'',error_string,error_desc) + logging.warning("fix broken packages is complete...") + + if success: + logging.info("Fix broken packages is complete to success...") + self.window_main.start_available() + else: + self.window_main.dbusController.UpdateDetectFinished(success,[''],\ + get_error_string_from_enum(ERROR_NOT_FIX_SYSTEM),error_string+' '+error_desc) + logging.error("fix broken packages is complete to failed...") + + elif action == self.ACTION_FIX_INCOMPLETE: + if success: + logging.info("fix incomplete install success.") + else: + logging.warning("fix incomplete install failed.") + + elif action == self.ACTION_REMOVE_PACKAGES: + self._release_install_lock() + self.window_main.dbusController.PurgePackagesFinished(success,error_string,error_desc) + + elif action == self.ACTION_INSTALL_DEB: + self._release_install_lock() + #FIXME: '\r\n: \r\n\r\n'就认为是验证失败 + if success == False and '\r\n: \r\n\r\n' in self.aptd_base.error_details: + error_string = _("Package validation failed and installation was rejected.") + error_desc = '' + self.window_main.dbusController.InstalldebFinished(success,error_string,error_desc) + UpdateMsg = {} + if success: + status = 'success' + UpdateMsg.update({"errorCode":" "}) + else: + status = 'failed' + UpdateMsg.update({"errorCode":str(error_string+" "+error_desc)}) + #apt发送数据 + if self.window_main.configs_uncover.getWithDefault("SystemStatus", "upload_installer_log", False) == True: + + UpdateMsg.update({"appname":str(self.window_main.deb_obj.get("debname","None").split("_")[0])}) + UpdateMsg.update({"source":str(self.window_main.deb_obj.get("source","kylin-system-updater"))}) + UpdateMsg.update({"status":str(status)}) + UpdateMsg.update({"new_version":str(self.window_main.deb_obj.get("debname","None").split("_")[1])}) + UpdateMsg.update({"old_version":str(self.window_main.deb_obj.get("old_version","None"))}) + self.window_main.collector.Upgrade_Process_Msg(self.action, UpdateMsg.copy()) + self.window_main.deb_obj = {} + elif action == self.ACTION_BACKGROUND_UPGRADE: + self._release_install_lock() + UpdateMsg = {} + if success: + status = 'success' + else: + status = 'failed' + self.window_main.collector.Generate_Msg(self.window_main.collector.background_list, self.action_mode) + for bl in self.window_main.collector.background_list: + pkg = self.window_main.collector.cache[bl] + UpdateMsg.update({"appname":str(bl)}) + UpdateMsg.update({"source":"Kylin Background upgrade"}) + UpdateMsg.update({"status":str(status)}) + UpdateMsg.update({"errorCode":str(error_string+" "+error_desc)}) + if self.window_main.collector.background_version[bl]: + UpdateMsg.update({"old_version":self.window_main.collector.background_version[bl]}) + UpdateMsg.update({"new_version":self.window_main.collector.cache[bl].candidate.source_version}) + # self.window_main.collector.Upgrade_Process_Msg(self.action, UpdateMsg.copy()) + + self.window_main.dbusController.UpdateInstallFinished(success,self.now_upgrade.upgrade_content,error_string,error_desc) + + + def _start_install_lock(self, caller='Kylin System Updater'): + self.window_main.configs_uncover.setValue("SystemStatus","abnormal_reboot",str(True)) + self.inhibit_shutdown.lock(caller=caller) + + def _release_install_lock(self): + self.window_main.configs_uncover.setValue("SystemStatus","abnormal_reboot",str(False)) + self.inhibit_shutdown.unlock() + + def _message_to_plymouth(self,message): + subprocess.call(["/bin/plymouth", "message", "--text", message]) + + def _progress_to_plymouth(self,progress): + tmp = ("--progress=%d"%progress) + run_cmd = ["/bin/plymouth","system-update",tmp] + subprocess.call(run_cmd) + + def _update_to_config(self,now_upgrade,pkgs_install,pkgs_upgrade,pkgs_remove): + output_upgrade = {} + + output_upgrade.update({"upgrade_groups":now_upgrade.upgrade_groups}) + output_upgrade.update({"single_pkgs":now_upgrade.single_pkgs}) + output_upgrade.update({"pkgs_install":pkgs_install}) + output_upgrade.update({"pkgs_upgrade":pkgs_upgrade}) + output_upgrade.update({"pkgs_remove":pkgs_remove}) + + + #6 产生JSON文件 + with open(self.INSTALL_OUTPUT_JSON, 'w', encoding='utf-8') as f: + json.dump(output_upgrade, f, ensure_ascii=False, indent=4) + + logging.info("Generate Jsonfile(%s) to complete... ",self.INSTALL_OUTPUT_JSON) + logging.info("Update Packages list to config file in shutdown model...") + + def _config_to_upgrade(self): + #读取组JSON文件 + with open(self.INSTALL_OUTPUT_JSON,'r') as f: + upgrade_groups = [] + single_pkgs = [] + pkgs_install = [] + pkgs_upgrade = [] + pkgs_remove = [] + + try: + data = json.load(f) + upgrade_groups = data["upgrade_groups"] + single_pkgs = data["single_pkgs"] + pkgs_install = data["pkgs_install"] + pkgs_upgrade = data["pkgs_upgrade"] + pkgs_remove = data["pkgs_remove"] + except Exception as exc: + logging.error(exc) + + return NowUpgradeMeta(self,upgrade_groups,single_pkgs),pkgs_install,pkgs_upgrade,pkgs_remove + + #将安装完成的插入数据库 安装失败的计算那些包安装失败了 分类插入数据库中 + #将安装失败的数据进行返回 + def _make_insert_info(self,success,is_cancelled,_now_upgrade,error_string,error_desc): + false_num = 0 + #目前去掉打印 错误日志 + # error_desc = error_desc +'\n\n'+self.aptd_base.error_details + #在安装失败 and 不是取消升级 则走下面的 否则不计算全部直接插入数据库 + if success == False and is_cancelled == False and self.action_mode != self.MODE_INSTALL_SYSTEM: + #获取当前系统中最新的cache + fresh_cache = Cache(rootdir=self.window_main.cache.rootdir) + #获取调整包列表 去掉版本号 + adjust_pkgs = [i.split("=")[0] for i in self.upgrade_data.adjust_pkgs] + + #升级存在单包更新 失败后计算每一个包是否安装完成 相应判断进行插入 + for pkg in _now_upgrade.single_pkgs: + pkg_obj = fresh_cache[pkg] + + #判断是否是源过滤调整的包 调整的话 判断安装版本 来解决是否安装成功 + if pkg in adjust_pkgs: + if pkg_obj._pkg.inst_state == apt_pkg.INSTSTATE_OK and pkg_obj._pkg.current_state == apt_pkg.CURSTATE_INSTALLED and pkg_obj.name + '=' + pkg_obj.installed.source_version in self.upgrade_data.adjust_pkgs: + self.window_main.sqlite3_server.insert_info(self.action_mode,[pkg],[],self.upgrade_data.adjust_pkgs,True,'', '') + else: + false_num += 1 + logging.warning("This single-package(%s) is not install or upgrade",pkg) + self.window_main.sqlite3_server.insert_info(self.action_mode,[pkg],[],self.upgrade_data.adjust_pkgs,False,error_string, error_desc) + else: + #非调整版本的计算方式 + if pkg_obj._pkg.inst_state == apt_pkg.INSTSTATE_OK and pkg_obj._pkg.current_state == apt_pkg.CURSTATE_INSTALLED and pkg_obj.is_now_broken == False: + self.window_main.sqlite3_server.insert_info(self.action_mode,[pkg],[],self.upgrade_data.adjust_pkgs,True,'', '') + else: + false_num += 1 + logging.warning("This single-package(%s) is not install or upgrade",pkg) + self.window_main.sqlite3_server.insert_info(self.action_mode,[pkg],[],self.upgrade_data.adjust_pkgs,False,error_string, error_desc) + + #组的计算是否升级成功的方式 将组按照单包来计算的 + if _now_upgrade.upgrade_groups != []: + pkgs_install,pkgs_upgrade = self._make_pkgs_list(self.cache,self.upgrade_data.groups_pkgs,_now_upgrade.upgrade_groups,[]) + install_error_pkgs = [] + total_pkg = pkgs_install + pkgs_upgrade + + for pkg in total_pkg[::-1]: + pkg_obj = fresh_cache[pkg] + if pkg in adjust_pkgs: + if pkg_obj.is_installed == True and pkg_obj.is_now_broken == False and pkg_obj.installed.source_version + pkg_obj.name in self.upgrade_data.adjust_pkgs: + total_pkg.remove(pkg) + else: + install_error_pkgs.append(pkg) + else: + if pkg_obj._pkg.inst_state == apt_pkg.INSTSTATE_OK and pkg_obj._pkg.current_state == apt_pkg.CURSTATE_INSTALLED and pkg_obj.is_now_broken == False: + total_pkg.remove(pkg) + else: + install_error_pkgs.append(pkg) + + # group_error_log = '\n'+ "This groups-package"+str(install_error_pkgs)+" is not install or upgrade" + '\n' + # logging.warning(group_error_log) + #如果没有 + if total_pkg == []: + self.window_main.sqlite3_server.insert_info(self.action_mode,[],_now_upgrade.upgrade_groups,self.upgrade_data.adjust_pkgs,True,'', '') + else: + false_num += 1 + self.window_main.sqlite3_server.insert_info(self.action_mode,[],_now_upgrade.upgrade_groups,self.upgrade_data.adjust_pkgs,False,error_string,error_desc) + #关闭临时cache + fresh_cache.close() + #其他情况直接写入数据库 不进行计算 + else: + #取消的话全部是失败 + if is_cancelled == True: + false_num = len(_now_upgrade.single_pkgs) + len(_now_upgrade.upgrade_groups) + self.window_main.sqlite3_server.insert_info(self.action_mode,_now_upgrade.single_pkgs,_now_upgrade.upgrade_groups,self.upgrade_data.adjust_pkgs,success,error_string, error_desc) + + return false_num + + def emulate_calcul_broken(self,pkgs): + args = ["apt-get", "install","--simulate"] + args = args + pkgs + + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + # logging.error(str(p.stdout)) + return p.stdout + + def emulate_update(self): + args = ["apt-get", "update"] + + p = subprocess.run(args, stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) + logging.warning(str(p.stdout)) + # return p.stdout + + def check_free_space(self,cache): + err_sum = _("Not enough free disk space") + err_msg = _("The upgrade needs a total of %s free space on " + "disk '%s'. " + "Please free at least an additional %s of disk " + "space on '%s'. %s") + # specific ways to resolve lack of free space + remedy_archivedir = _("Remove temporary packages of former " + "installations using 'sudo apt clean'.") + remedy_boot = _("You can remove old kernels using " + "'sudo apt autoremove', and you could also " + "set COMPRESS=xz in " + "/etc/initramfs-tools/initramfs.conf to " + "reduce the size of your initramfs.") + remedy_root = _("Empty your trash and remove temporary " + "packages of former installations using " + "'sudo apt clean'.") + remedy_tmp = _("Reboot to clean up files in /tmp.") + remedy_usr = _("") + # check free space and error if its not enough + try: + cache.checkFreeSpace() + except NotEnoughFreeSpaceError as e: + # CheckFreeSpace examines where packages are cached + archivedir = apt_pkg.config.find_dir("Dir::Cache::archives") + err_long = "" + for req in e.free_space_required_list: + if err_long != "": + err_long += " " + if req.dir == archivedir: + err_long += err_msg % (req.size_total, req.dir, + req.size_needed, req.dir, + remedy_archivedir) + elif req.dir == "/boot": + err_long += err_msg % (req.size_total, req.dir, + req.size_needed, req.dir, + remedy_boot) + elif req.dir == "/": + err_long += err_msg % (req.size_total, req.dir, + req.size_needed, req.dir, + remedy_root) + elif req.dir == "/tmp": + err_long += err_msg % (req.size_total, req.dir, + req.size_needed, req.dir, + remedy_tmp) + elif req.dir == "/usr": + err_long += err_msg % (req.size_total, req.dir, + req.size_needed, req.dir, + remedy_usr) + # #当单包安装检查内存不够的时候,报错到控制面板 单包安装属于更新才使用的所以直接赋值为更新 再进行之后的报错处理 + if self.action_mode == self.MODE_INSTALL_SINGLE: + self.action = self.ACTION_UPDATE + raise UpdateBaseError(ERROR_NOT_DISK_SPACE,desc=err_sum) + except SystemError: + logging.exception("free space check failed") + logging.info("Disk Check finished...") + +# try aptdaemon +if os.path.exists("/usr/sbin/aptd") \ + and "UPDATE_MANAGER_FORCE_BACKEND_SYNAPTIC" not in os.environ: + # check if the gtkwidgets are installed as well + try: + from .InstallBackendAptdaemon import InstallBackendAptdaemon + except ImportError: + logging.exception("importing aptdaemon") + +def get_backend(*args, **kwargs): + """Select and return a package manager backend.""" + # try aptdaemon + if (os.path.exists("/usr/sbin/aptd") + and "UPDATE_MANAGER_FORCE_BACKEND_SYNAPTIC" not in os.environ): + # check if the gtkwidgets are installed as well + try: + return InstallBackendAptdaemon(*args, **kwargs) + except NameError: + logging.exception("using aptdaemon failed") + + # nothing found, raise + raise Exception("No working backend found, please try installing " + "aptdaemon or synaptic") + +class InhibitShutdownLock(): + # 禁止关机锁文件路径 + FILELOCK_PATH = "/tmp/lock/" + SHUTDOWN_BLOCK_FILELOCK = "kylin-update.lock" + + def __init__(self): + self.inhibit_lock = None + self.pidfile = None + + #安装时禁止关机 进行加锁 + def lock(self, caller='Kylin System Updater'): + """ + 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', + caller, 'Installing Packages', + 'block')), + None, 0, -1, None, None) + self.inhibit_lock = Gio.UnixInputStream(fd=fdlist.steal_fds()[var[0]]) + + self.LockedPreventShutdown() + logging.info("Shutdown Has been locked...") + except Exception as e: + logging.error(e) + + #解锁禁止关机 + def unlock(self): + try: + self.unLockedEnableShutdown() + if self.inhibit_lock != None: + self.inhibit_lock.close() + self.inhibit_lock == None + logging.info("Shutdown Has been unlocked...") + else: + logging.info("Not locked and Quitting ...") + except Exception as e: + logging.error("unlock failed." + str(e)) + + #安装时禁止关机 进行加锁 + def LockedPreventShutdown(self): + + #不为空是表示以及被锁 + if self.pidfile != None: + logging.error("self.pidfile file disc not is None,Has been locked...") + return False + + if not os.path.exists(self.FILELOCK_PATH): + #不存在创建 + logging.info("File(%s) is not exists and will be create",self.FILELOCK_PATH) + os.makedirs(self.FILELOCK_PATH) + else: + #当目录存在时进行删除 不删除进行创建文件的话会报错 + # file cannot be locked.[Errno 11] Resource temporarily unavailable + # 资源被占用报错 + shutil.rmtree(self.FILELOCK_PATH) + logging.info("File(%s) is exists and will be delete and create",self.FILELOCK_PATH) + os.makedirs(self.FILELOCK_PATH) + + try: + self.pidfile = open(os.path.join(self.FILELOCK_PATH, self.SHUTDOWN_BLOCK_FILELOCK), "w+") + fcntl.flock(self.pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB) + return True + except Exception as e: + logging.error("file cannot be locked." + str(e)) + self.pidfile.close() + self.pidfile = None + return False + + #解锁禁止关机 + def unLockedEnableShutdown(self): + #未加锁退出 + if not self.pidfile: + logging.info("Not locked and Quitting ...") + return False + try: + fcntl.flock(self.pidfile, fcntl.LOCK_UN) + self.pidfile.close() + self.pidfile = None + + # Fix 修复权限问题 当普通用户无法使用 所以直接删除目录 + if os.path.exists(self.FILELOCK_PATH): + shutil.rmtree(self.FILELOCK_PATH) + logging.info('Emptying the lockPath(%s) is complete...',self.FILELOCK_PATH) + else: + logging.info("Emptying the lockPath(%s) is Failed...",self.FILELOCK_PATH) + + return True + except Exception as e: + logging.error("unlock failed." + str(e)) + self.pidfile.close() + self.pidfile = None + return False + +class UpdateSafeManager(): + KYSEC_STATUS = "/sys/kernel/security/kysec/status" + KYSEC_EXECTL = "/sys/kernel/security/kysec/exectl" + + KYSEC_EXECUT_CONTROL = 0 + KYSEC_NETWORK_CONTROL = 1 + KYSEC_SHUTDOWN_CODE = 0 + + def __init__(self): + self.bus = dbus.SystemBus() + self.safe_status = False + self.safe_exectl_code = self.KYSEC_SHUTDOWN_CODE + self.check_status() + + def check_status(self): + self._check_safe_status() + self._check_safe_exectl() + + def _check_safe_status(self): + if os.path.exists(self.KYSEC_STATUS): + with open(self.KYSEC_STATUS, 'r') as f: + data = f.read() + if data != "0": + self.safe_status = True + + def _check_safe_exectl(self): + if self.safe_status == True: + if os.path.exists(self.KYSEC_EXECTL): + with open(self.KYSEC_EXECTL, 'r') as f: + data = f.read() + self.safe_exectl_code = int(data) + logging.info("Now kylin Sec has opened and exectl status:%s...",data) + + def reset_safe(self): + if self.safe_status == True: + self._set_kysec_status(self.KYSEC_EXECUT_CONTROL,self.safe_exectl_code) + self._set_kysec_status(self.KYSEC_NETWORK_CONTROL,2) + + def shutdown_safe(self): + if self.safe_status == True: + self._set_kysec_status(self.KYSEC_EXECUT_CONTROL,self.KYSEC_SHUTDOWN_CODE) + self._set_kysec_status(self.KYSEC_NETWORK_CONTROL,self.KYSEC_SHUTDOWN_CODE) + + def _set_kysec_status(self,fun,value): + try: + obj = self.bus.get_object('com.kylin.kysec', '/xattr') + interface = dbus.Interface(obj,dbus_interface='com.kylin.kysec.xattr') + retval = interface.kysec_xattr_set_func_status(fun,value,timeout=0.5) + logging.info("Set kysec_xattr_set_func_status %s...",str(value)) + return True + except Exception as e: + logging.error("Set kylin Sec Failed and fun:%d value:%d Error msg:" + str(e),fun,value) + return False diff --git a/backend/data/30kylin-system-updater b/backend/data/30kylin-system-updater new file mode 100644 index 0000000..fed1020 --- /dev/null +++ b/backend/data/30kylin-system-updater @@ -0,0 +1 @@ +Dir::Bin::Methods::ftp "ftp"; diff --git a/backend/data/cn.kylinos.KylinSystemUpdater.policy b/backend/data/cn.kylinos.KylinSystemUpdater.policy new file mode 100644 index 0000000..64c3262 --- /dev/null +++ b/backend/data/cn.kylinos.KylinSystemUpdater.policy @@ -0,0 +1,25 @@ + + + + + Kylin System Updater + www.kylinos.cn + kylin-system-updater + + + <_description> + system level settings + + <_message> + To Change the settings, you need to authenticate. + + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + diff --git a/backend/data/cn.kylinos.UpgradeStrategies.policy b/backend/data/cn.kylinos.UpgradeStrategies.policy new file mode 100644 index 0000000..debbe26 --- /dev/null +++ b/backend/data/cn.kylinos.UpgradeStrategies.policy @@ -0,0 +1,25 @@ + + + + + Kylin System Updater Config Manager + www.kylinos.cn + kylin-upgrade-strategies + + + <_description> + system level settings + + <_message> + To Change the settings, you need to authenticate. + + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + diff --git a/backend/data/com.kylin.UpgradeStrategies.conf b/backend/data/com.kylin.UpgradeStrategies.conf new file mode 100644 index 0000000..9142c8c --- /dev/null +++ b/backend/data/com.kylin.UpgradeStrategies.conf @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/backend/data/com.kylin.UpgradeStrategies.service b/backend/data/com.kylin.UpgradeStrategies.service new file mode 100644 index 0000000..4b70a1d --- /dev/null +++ b/backend/data/com.kylin.UpgradeStrategies.service @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=com.kylin.UpgradeStrategies +Exec=/bin/python3 /usr/share/kylin-system-updater/kylin-upgrade-strategies -r -d +User=root \ No newline at end of file diff --git a/backend/data/com.kylin.systemupgrade.conf b/backend/data/com.kylin.systemupgrade.conf new file mode 100644 index 0000000..0408835 --- /dev/null +++ b/backend/data/com.kylin.systemupgrade.conf @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/backend/data/com.kylin.systemupgrade.limit b/backend/data/com.kylin.systemupgrade.limit new file mode 100644 index 0000000..2d80c3a --- /dev/null +++ b/backend/data/com.kylin.systemupgrade.limit @@ -0,0 +1,14 @@ +[whitelist] +key1 = /usr/bin/kylin-background-upgrade +key2 = /usr/bin/ukui-control-center +key3 = /usr/bin/kylin-installer +key4 = /usr/bin/kylin-uninstaller +key5 = /usr/bin/kylin-software-properties-service +key6 = /usr/bin/kylin-source-update +key7 = /usr/bin/kylin-source-manager +key8 = /usr/bin/kylin-unattended-upgrade +key9 = /usr/bin/kylin-software-center +key10 = /usr/bin/kylin-printer +key11 = /usr/bin/kylin-printer-applet +key12 = /usr/bin/hedron-client +key13 = /usr/bin/kylin-software-center-plugin-synchrodata \ No newline at end of file diff --git a/backend/data/kylin-logout-required b/backend/data/kylin-logout-required new file mode 100755 index 0000000..c066434 --- /dev/null +++ b/backend/data/kylin-logout-required @@ -0,0 +1,26 @@ +#!/bin/sh + +# a) it breaks if its not available +# b) the string we have here does not need it (because it has no vars) +eval_gettext() { + if [ -x /usr/bin/gettext ]; then + echo $(gettext "$1") + else + echo "$1" + fi +} +export TEXTDOMAIN=update-notifier +export TEXTDOMAINDIR=/usr/share/locale + +case "$DPKG_MAINTSCRIPT_PACKAGE::$DPKG_MAINTSCRIPT_NAME" in + linux-image-extra*::postrm) + exit 0;; +esac + +if [ "$0" = "/etc/kernel/postinst.d/update-notifier" ]; then + DPKG_MAINTSCRIPT_PACKAGE=linux-base +fi + +# Wake the applet up +echo "*** $(eval_gettext "System logout required") ***" > /var/run/logout-required +echo "$DPKG_MAINTSCRIPT_PACKAGE" >> /var/run/logout-required.pkgs diff --git a/backend/data/kylin-reboot-required b/backend/data/kylin-reboot-required new file mode 100755 index 0000000..b88ab36 --- /dev/null +++ b/backend/data/kylin-reboot-required @@ -0,0 +1,26 @@ +#!/bin/sh + +# a) it breaks if its not available +# b) the string we have here does not need it (because it has no vars) +eval_gettext() { + if [ -x /usr/bin/gettext ]; then + echo $(gettext "$1") + else + echo "$1" + fi +} +export TEXTDOMAIN=update-notifier +export TEXTDOMAINDIR=/usr/share/locale + +case "$DPKG_MAINTSCRIPT_PACKAGE::$DPKG_MAINTSCRIPT_NAME" in + linux-image-extra*::postrm) + exit 0;; +esac + +if [ "$0" = "/etc/kernel/postinst.d/update-notifier" ]; then + DPKG_MAINTSCRIPT_PACKAGE=linux-base +fi + +# Wake the applet up +echo "*** $(eval_gettext "System restart required") ***" > /var/run/reboot-required +echo "$DPKG_MAINTSCRIPT_PACKAGE" >> /var/run/reboot-required.pkgs diff --git a/backend/data/kylin-system-updater b/backend/data/kylin-system-updater new file mode 100644 index 0000000..b0ad5fc --- /dev/null +++ b/backend/data/kylin-system-updater @@ -0,0 +1,10 @@ +/var/log/kylin-system-updater/kylin-system-updater.log.1 +{ + weekly + missingok + rotate 3 + compress + notifempty + minsize 10M + copytruncate +} diff --git a/backend/data/kylin-system-updater.db b/backend/data/kylin-system-updater.db new file mode 100644 index 0000000..26e9d40 Binary files /dev/null and b/backend/data/kylin-system-updater.db differ diff --git a/backend/data/kylin-system-updater.service b/backend/data/kylin-system-updater.service new file mode 100644 index 0000000..df4c1f2 --- /dev/null +++ b/backend/data/kylin-system-updater.service @@ -0,0 +1,13 @@ +[Unit] +Description=kylin-system-updater dbus daemon +StartLimitIntervalSec=0 + +[Service] +Type=dbus +Restart=always +RestartSec=3 +BusName=com.kylin.systemupgrade +ExecStart=/usr/share/kylin-system-updater/kylin-system-updater + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/backend/data/kylin-system-version.conf b/backend/data/kylin-system-version.conf new file mode 100644 index 0000000..1d12ee6 --- /dev/null +++ b/backend/data/kylin-system-version.conf @@ -0,0 +1,3 @@ +[SYSTEM] +os_version = +update_version = \ No newline at end of file diff --git a/backend/data/system-updater-coverable.conf b/backend/data/system-updater-coverable.conf new file mode 100644 index 0000000..f03d938 --- /dev/null +++ b/backend/data/system-updater-coverable.conf @@ -0,0 +1,12 @@ +#此配置文件内的所有配置项,在新装时都会被替换掉 +[SystemStatusCover] +close_source_filter = False +priority_upgrade_restart = True + +[ConfigPkgStatus] +check_resover_remove = False +check_frontend_pkg = True + +[AutoUpgradeConfig] +upgradeInterval = 7 +downloadRandom = 180 diff --git a/backend/data/system-updater-defaults.conf b/backend/data/system-updater-defaults.conf new file mode 100644 index 0000000..a540b41 --- /dev/null +++ b/backend/data/system-updater-defaults.conf @@ -0,0 +1,9 @@ +[SystemStatus] +abnormal_reboot = False +upload_upgrade_log = True +upload_installer_log = False + +[InstallMode] +shutdown_install = False +manual_install = False +auto_install = False \ No newline at end of file diff --git a/backend/data/unattended-upgrades-policy.conf b/backend/data/unattended-upgrades-policy.conf new file mode 100644 index 0000000..6e92a5a --- /dev/null +++ b/backend/data/unattended-upgrades-policy.conf @@ -0,0 +1,30 @@ +[autoUpgradePolicy] +#自动更新的开关 +autoUpgradeState = off + +#预下载开关 +preDownload = off + +# 预下载的时间为时间段 例如:10:00-11:00 +preDownloadTime = 10:00 + +#添加检查更新的周期 以天为单位 +updateDays = 1 + +# timing 为定时下载 manaual手动下载 +downloadMode = timing + +# 下载的时间为时间段 例如:10:00-11:00 +downloadTime = 10:00 + +#安装存在定时timing 手动:manual 关机安装bshutdown +installMode = timing + +#安装也为时间段 例如:00:00 +installTime = 10:00 + +#是否开启自动重启 以及自动重启时间可以调节 +automaticReboot = off + +#自动重启时间的调节 now为立即重启 重启时间调节 例如00:00 +automaticRebootTime = now diff --git a/backend/data/unattended-upgrades-timestamp b/backend/data/unattended-upgrades-timestamp new file mode 100644 index 0000000..573541a --- /dev/null +++ b/backend/data/unattended-upgrades-timestamp @@ -0,0 +1 @@ +0 diff --git a/backend/interface.md b/backend/interface.md new file mode 100644 index 0000000..addf4bc --- /dev/null +++ b/backend/interface.md @@ -0,0 +1,657 @@ +## DBUS接口 + +[TOC] + + + +### 对应版本信息 + +| 软件包 | 目前版本 | 备注 | +| :------------------: | :-----------------------------: | :--: | +| kylin-system-updater | kylin-system-updater 1.4.16kord | | +| aptdaemon | 1.1.1+bzr982-0kylin32.3 | | +| | | | + + + +### 描述 + +实现系统升级以python apt库和aptdeamon的形式 + + + +### Dbus接口信息 + +| 名称 | 含义 | +| -------------- | --------------------------------- | +| BUS类型 | SYSTEM BUS | +| DBUS名称 | com.kylin.systemupgrade | +| OBJECT路径 | /com/kylin/systemupgrade | +| INTERFACES名称 | com.kylin.systemupgrade.interface | +| 属性名称 | org.freedesktop.DBus.Properties | + + + +### Apt-p2p配置项设置 + +### Dbus接口信息 + +| 名称 | 含义 | +| -------------- | --------------------------------- | +| BUS类型 | SYSTEM BUS | +| DBUS名称 | com.kylin.systemupgrade | +| OBJECT路径 | /com/kylin/systemupgrade | +| INTERFACES名称 | com.kylin.systemupgrade.interface | +| 属性名称 | org.freedesktop.DBus.Properties | + + + +#### Get + +- `简介:`获取属性的值 + +- `入参:` `s`iface:要设置的属性的接口, `s`property:要设置的属性名称 + +- `出参:` `Variant`变量 + +- `示例:` + + ``` + #获取p2p的配置 + + Get(com.kylin.systemupgrade.interface,P2pBootstrap) + ``` + + + + + +#### Set + +- `简介:`设置属性的值 + +- `入参:` `s`iiface:要设置的属性的接口, `s`iproperty:要设置的属性名称 `Variant`value:要设置的值 + +- `出参:` + +- `示例:` + + ``` + #设置p2p的配置 + + set("com.kylin.systemupgrade.interface","P2pBootstrap",GLib.Variant('s', "test")) + ``` + + + + + +### 方法列表 + +| Method Name | Input Args | Output Args | means | 异步任务 | +| ------------------ | ---------- | ----------- | --------------------------------- | ------------------ | +| UpdateDetect | 无 | b | 更新cache,产生组升级列表JSON文件 | | +| DistUpgradeAll | b | b | 全部升级 | | +| DistUpgradePartial | b,as | b | 部分升级 | | +| DistUpgradeSystem | b | b | 全盘升级 | | +| CancelDownload | 无 | b | 取消升级 | | +| InsertInstallState | ss | b | 向display表插入数据 | | +| GtDownloadspeedLimitValue | 无 | b | 获取当前限速 | | +| SetDownloadspeedMax | sb | b | 设置限速 | | +| GetBackendStatus | 无 | i | 控制获取后端状态 | | +| UnattendedUpgradeValue | ss | bs | 获取是否允许关机前更新 | | +| PurgePackages | as | b | 卸载软件包 | | +| InstalldebFile | ssbb | b | 安装本地deb包 | | +| DataBackendCollect | ss | b | | | +| CheckRebootRequired | s | b | 检查是否需要重启的方法,以及弹窗提示 | | + + +### Method分析 + +#### UpdateDetect + +- `简介:`更新cache对象,完成从之后拿到系统中所有可升级的包再经过源过滤、白名单等等的过滤,最后输出当前`可升级的包以及分组(JSON配置 输出目录: /var/lib/kylin-system-updater) + +- `入参:`无 +- `出参:`True or False 注意:不通过返回值来判断有没有执行成功 通过下面的信号 +- `对应信号:` + - `UpdateDetectStatusChanged:` 更新的进度信息和状态信息 + - `UpdateDetectFinished:`更新的完成的信号 + + + +#### DistUpgradePartial + +- `简介:` 升级部分软件包或者分组 + +- `入参:` `b:` False模式:只进行获取升级列表以及计算修复依赖关系,以及计算是否存在删除的包,`True模式:`直接进行安装的操作 注意:必须选使用False模式获取升级列表以及计算依赖关系再进行True模式 + + `as:` 输入需要升级或者安装的分组 例如 升级系统组:["kylin-update-desktop-system"] + +- `出参:`True or False 注意:不通过返回值来判断有没有执行成功 通过下面的信号 + +- `对应信号:` + + - `UpdateDependResloveStatus:` 升级计算依赖修复反馈信号 + - `UpdateDloadAndInstStaChanged:`升级安装过程的进度信号以及状态 + - `UpdateInstallFinished:` 升级安装完成的信号 + + + +#### DistUpgradeAll + +- `简介:`升级全部可升级的分组 + +- `入参:` `b:` False模式:只进行获取升级列表以及计算修复依赖关系,以及计算是否存在删除的包,`True模式:`直接进行安装的操作 注意:必须选使用False模式获取升级列表以及计算依赖关系再进行True模式 +- `出参:`True or False 注意:不通过返回值来判断有没有执行成功 通过下面的信号 +- `对应信号:` + - `UpdateDependResloveStatus:` 升级计算依赖修复反馈信号 + - `UpdateDloadAndInstStaChanged:`升级安装过程的进度信号以及状态 + - `UpdateInstallFinished:` 升级安装完成的信号 + + + + +#### UpdateDownloadInfo + +- `介绍:` 发送下载包信息信号 + +- `出参`: `i:`当前正在下载的项,`i:`所有下载的项,`i:`当前下载的字节,`i:`总的需要下载的字节,`i:`下载速度 + +- `示例:` + + ```sh + current_items = 1, total_items = 1, currenty_bytes = 45 kB, total_bytes = 45 kB, current_cps = 0 kB/s + + ``` + + + +#### GetBackendStatus + +- `介绍:` 获取后端的状态,现在正在处理那些任务 + +- `入参:` `s:`当前用户的语言变量 例如传入语言环境变量`LANG` 的值`zh_CN.UTF-8` 就会将升级的语言切换为中文 同理其他也能相应设置 + +- `出参`: `i:`当前任务ID,整型数字 + +- `状态示例列表:` + + ```python + ACTION_DEFUALT_STATUS = -1 #默认状态空闲状态 + ACTION_UPDATE = 0 #处于更新cache状态 + ACTION_INSTALL = 1 #包括部分升级、全部升级、q + ACTION_INSTALL_DEB = 2 #处于安装deb的状态 + ACTION_CHECK_RESOLVER = 3 #处于计算依赖过程 + ACTION_DOWNLOADONLY = 4 #单独下载软件包过程 + ACTION_FIX_BROKEN = 5 #修复依赖的过程 + ACTION_REMOVE_PACKAGES = 6 #卸载包的状态中 + ``` + + + +#### UnattendedUpgradeValue + +- `介绍:` 设置或获取是否允许关机前更新 + +- `入参`: `s:`operation("get"/"set"),`s:`value将要设置的值 + +- `示例:` + + ```sh + operation = "set", value = "false" + + ``` + + + +#### InstalldebFile + +- `简介:`安装本地deb包 + +- `入参:` `source:(string)` 安装来源,`path:(string)`本地deb包绝对路径,`_check_local_dep:(bool)`出现依赖问题时是否查询本路径下是否存在满足的包,`_auto_satisfy:(bool)`出现依赖问题时是否通过网络下载并安装依赖包 +- `出参:`True or False +- `对应信号:` + - `InstalldebStatusChanged`:安装过程的进度信号以及状态 + - `InstalldebFinished`:安装完成的信号 +- `示例:` + + ```sh + source = 'kylin-installer', path = '/home/kylin/kylin-video_3.1.0-94.5_amd64.deb', _check_local_dep = 0, _auto_satisfy = 1 + + ``` + + + +#### PurgePackages + +- `简介:`卸载系统中的软件包 + +- `入参:` `as:` 需要卸载的包列表 `s:`当前用户的用户名 例如:kylin用户就传入`kylin`字符串 + +- `出参:`True or False 出参值不做任何参考意义 `注意:`其中False的时候表示后端正在处理其他任务会报错,其中完成信号也会反馈结果,故不采用方法的返回值来判断错误类型 + +- `对应信号:` + + - `PurgePkgStatusChanged:`卸载过程的进度信号以及状态 + - `PurgePackagesFinished:` 卸载完成的信号 + +- `示例:` + + ```sh + _purge_list = ['kylin-video','tree'] cur_user = 'kylin' + ``` + + + +#### DataBackendCollect + +- `介绍:` 后端数据采集 + +- `入参`: `messageType: ` 消息埋点(string), `uploadMessage: ` 上传数据(json格式字符串),必须包含的字段: "packageName" + +- `示例:` + + ```sh + messageType = "UpdateInfos", uploadMessage = "{\"packageName\":\"kylin-system-updater\",\"source\":\"kylin-system-updater\",\"status\":\"True\",\"errorCode\":\"\",\"versionOld\":\"1.2.13.2kord\",\"versionNew\":\"1.2.17.1kord\"}" + + messageType: 消息埋点(string) "UpdateInfos"、"InstallInfos"、"RemoveInfos"... + source: 安装来源 "kylin-installer"、"unattented-upgrade"、"kylin-software-center"、"kylin-system-updater"... + status: 安装或卸载状态 "True"/"False" + errorCode: 错误信息 ""/"..." + versionOld: 旧版本号 "1.2.13.2kord" + versionNew: 新版本号 "1.2.17.1kord" + + ``` + + + +#### CheckRebootRequired + +- `介绍:` 检查是否需要重启的方法,以及弹窗提示 +- `入参`: `s:`标识那个应用调的此接口(如自动更新可填入字符串“autoyupgrade”) +- `出参:`True or False 执行的结果 + + + +#### SetConfigValue + +- `简介:`设置配置文件的值 配置文件的目录`/var/lib/kylin-system-updater/system-updater.conf` + +- `入参:` `section`, `option`,` value` 不管布尔、列表的数据类型都转化成字符串类型来写入 + +- 例如传入"InstallModel","shutdown_install","False" + + ``` + [InstallMode] + shutdown_install = True + manual_install = False + auto_install = False + pkgs_install = + pkgs_upgrade = + pkgs_remove = + ``` + +- `出参:`True :设置值成功 False: 设置失败 + +#### GetConfigValue + +- `简介:`获取配置文件的值 配置文件的目录`/var/lib/kylin-system-updater/system-updater.conf` + +- `入参:` `section`,` option` 例如传入"InstallModel","shutdown_install" + +- `出参:` `bool:`True :获取值成功 False: 获取失败 `Value:`值都以字符串类型来返回 + + ``` + [InstallMode] + shutdown_install = True + manual_install = False + auto_install = False + pkgs_install = + pkgs_upgrade = + pkgs_remove = + ``` + + + +#### CheckInstallRequired + +- `简介:`检查当前系统是否需要关机安装或者重启安装 +- `入参:` 无 +- `出参:` `int:` 类型返回值 表示当前系统是否需要安装 返回值数值含义如下。简要为0时不需要安装,不为零时需要进行提示安装 + - `1` 手动更新请求当前系统在关机时进行安装软件包 + - `2` 自动更新请求当前系统在关机时进行安装软件包 + - `0` 当前系统不需要进行安装 + + + +#### FixBrokenDepends + +- `简介:` 修复当前的系统Apt环境,收到`UpdateFixBrokenStatus`后进行调用,类似于调用apt install -f 来进行修复 +- `入参:` 无 +- `出参:` True or False 执行的结果 无实际意义 +- `对应信号:` + - `FixBrokenStatusChanged:` 修复依赖的状态信号 可不使用 + + + +#### MountSquashfsSource + +- `简介:` 挂载离线源squashfs +- `入参:` `s:` 挂载文件的位置 +- `出参:` `b:` True or False 执行的结果,`s:` 字符类型错误信息描述 + + + +### Signal列表 + +| Signal Name | Output Args | means | +| ---------------------------- | ----------- | ------------------------ | +| UpdateDetectStatusChanged | i,s | 更新进度信息以及状态信息 | +| UpdateDetectFinished | b,as,s,s | 更新完成信号 | +| UpdateDloadAndInstStaChanged | as,i,s,s | 升级的进度信号以及状态 | +| UpdateInstallFinished | b,as,s,s | 升级完成的信号 | +| UpdateDownloadInfo | i,i,u,u,i | 发送下载包信息信号 | +| UpdateDependResloveStatus | b,b,s | 更新依赖修复信息 | +| DistupgradeDependResloveStatus | b,s | 更新全盘修复信息 | +| Cancelable | b | 是否可取消 | +| UpdateSqlitSingle | | | +| FixBrokenStatusChanged | iiisss | 修复依赖的状态信号 | +| PurgePackagesFinished | iss | 卸载完成信号 | +| PurgePkgStatusChanged | bss | 卸载进度信息以及状态信息 | +| RebootLogoutRequired | s | 请求重启或者注销的信号 | +| UpdateInstallFinished | b,as,s,s | 下载完成的信号 | + + + +### Signal分析 + +#### UpdateDetectStatusChanged + +- `介绍:`更新的进度信息和状态信息 + +- `出参`:`i:`更新的进度信息从0-100%,`s:`更新的状态信息, + +- `示例:` + + ```sh + progress = 9 , status = 正在解决依赖关系 + progress = 92 , status = 正在载入软件列表 + progress = 92 , status = 完成 + ``` + + + +#### UpdateDetectFinished + +- `介绍:`更新的完成的信号 + +- `出参`: `b:`更新是否成功,`as:`可升级的组列表,`s:`产生错误的结果,`s:`产生错误的原因 + +- `示例:` + + ```sh + success = True , upgrade_group = ['kylin-update-desktop-system', 'tree', 'texinfo', 'kylin-update-manager', 'dnsmasq-base', 'vino', 'dpkg-dev', 'ghostscript', 'atril', 'wpasupplicant', 'eom', 'eom-common', 'fcitx-bin', 'fcitx-data', 'fcitx-frontend-gtk2', 'wps-office'], error_string = , error_desc = + + error_string = 获取更新软件推送失败,请稍后再进行尝试更新 , error_desc = 推送服务器连接异常 + + ``` + +#### UpdateDependResloveStatus + +- `介绍:`升级计算依赖修复反馈信号 + +- `出参`: `b:`修复依赖关系是否成功,`b:`是否存在升级需要卸载的包,`as:`卸载的包列表,`as:`卸载的包的描述信息,`as:`卸载此包的原因 升级安装那些包导致的,`s:`产生错误的结果,`s:`产生错误的原因 + +- `示例:` + + ```sh + UpdateDependResloveStatus:resolver_status = True , remove_status = True , remove_pkgs = ['kylin-burner-i18n'],pkg_raw_description = ['Sophisticated CD/DVD burning application - localizations files'] ,delete_desc = ['kylin-burner-i18n 将要被删除,由于升级 kylin-burner'],error_string = , error_desc = + + ``` + + + + +#### UpdateFixBrokenStatus + +- `介绍:`更新检查过程中是否需要修复系统Apt的环境,提示是否存在卸载软件包的情况 + +- `出参`: `b:`是否能修复系统环境,`b:`是否存在修复需要卸载的包,`as:`卸载的包列表,`as:`卸载的包的描述信息,`as:`卸载此包的原因,`s:`产生错误的结果,`s:`产生错误的原因 + +- `示例:` + + ```sh + UpdateDependResloveStatus:resolver_status = True , remove_status = True , remove_pkgs = ['kylin-burner-i18n'],pkg_raw_description = ['Sophisticated CD/DVD burning application - localizations files'] ,delete_desc = ['kylin-burner-i18n 将要被删除,由于升级 kylin-burner'],error_string = , error_desc = + + ``` + + + + +#### UpdateDloadAndInstStaChanged + +- `介绍:` 升级安装过程的进度信号以及状态 + +- `出参`: `as:`当前那些组在升级安装 `i:`更新的进度信息从0-100%,`s:`更新的状态信息 `s:`下载的细节信息 + +- ` 示例:` + + ```sh + groups_list = ['kylin-update-desktop-system'] progress = 15 , status = 下载中 current_details = 下载中 tree + ``` + + + +#### UpdateInstallFinished + +- `介绍:` 升级安装完成的信号 + +- `出参`: `b:`升级是否成功,`as:`可升级的组列表,`s:`产生错误的结果,`s:`产生错误的原因 + +- `示例:` + + ```sh + pdateInstallFinished success = True , upgrade_group = ['tree'], error_string = 系统升级完成。 , error_desc = + + ``` + + + +#### UpdateDownloadFinished + +- `介绍:` 下载完成的信号 + +- `出参`: `b:`下载是否成功,`as:`可升级的组列表,`s:`产生错误的结果,`s:`产生错误的原因 + +- `示例:` + + ```sh + pdateInstallFinished success = True , upgrade_group = ['tree'], error_string = 系统升级完成。 , error_desc = + + ``` + + + +#### FixBrokenStatusChanged + +- `介绍:` 修复依赖过程中的状态反馈信号 + +- `出参`: `i:`修复依赖是否完成 `i:`修复依赖过程是否成功`注意:只有修复完成时,修复依赖是否成功才有意义`,`i:`修复的进度信息 `s:`修复的状态信息 `s:`产生错误的结果,`s:`产生错误的原因 + +- ` 示例:` + + ```sh + emit FixBrokenStatusChanged finished = False , success = True,progress = 66 , status = 正在应用更改,error_string = , error_desc = + ``` + +- + + + +#### PurgePkgStatusChanged + +- `介绍:`卸载的进度信息和状态信息以及状态的细节信息 + +- `出参`:`i:`卸载的进度信息从0-100%,`s:`卸载的状态信息,`s:`卸载的细节信息 + +- `示例:` + + ```sh + INFO:emit PurgePkgStatusChanged progress = 63 , status = 正在应用更改 ,current_details = 正在准备删除 kylin-video + INFO:emit PurgePkgStatusChanged progress = 76 , status = 正在应用更改 ,current_details = 正在卸载 kylin-video + + ``` + + + + +#### PurgePackagesFinished + +- `介绍:`卸载的完成的信号 + +- `出参`: `b:`卸载是否成功,`s:`产生错误的结果,`s:`产生错误的原因 + +- `示例:` + + ```sh + #卸载完成 + PurgePackagesFinished success = True , error_string = 卸载完成。 , error_desc = + + #卸载失败 + PurgePackagesFinished success = False , error_string = 软件包不存在 , error_desc = 检查包名的拼写是否正确,以及是否启用了相应的仓库。 + PurgePackagesFinished success = False , error_string = 软件包没有安装 , error_desc = 不需要进行卸载。 + + #卸载失败 由于正在处理其他任务也同样会报错 + PurgePackagesFinished success = False , error_string = 其他任务正在更新升级中,请稍后再卸载。 , error_desc = + ``` + + + +#### InstalldebStatusChanged + +- `介绍:`安装的进度信息和状态信息以及状态的细节信息 + +- `出参`:`i:`安装的进度信息从0-100%,`s:`安装的状态信息,`s:`安装的细节信息 + +- `示例:` + + ```sh + InstalldebStatusChanged progress = 57 , status = 正在应用更改 ,current_details = 正在配置 python3-bandit + InstalldebStatusChanged progress = 57 , status = 正在应用更改 ,current_details = python3-bandit 已安装 + + ``` + + + + +#### InstalldebFinished + +- `介绍:`安装的完成的信号 + +- `出参`: `b:`安装是否成功,`s:`产生错误的结果,`s:`产生错误的原因 + +- `示例:` + + ```sh + #安装完成 + InstalldebFinished success = True , error_string = , error_desc = + + #安装失败 缺少依赖的 + InstalldebFinished success = False , error_string = bandit dependency is not satisfied , error_desc = python3-bandit + + #安装失败 选择从网络拉依赖 网络断开 报网络错误 + InstalldebFinished success = False , error_string = 下载软件包文件失败 , error_desc = 检查您的网络连接。 + ``` + + + + +#### RebootLogoutRequired + +- `介绍:`请求重启和注销的信号 + +- `出参`: `s:` "reboot" 表示重启 "logout"表示注销 + +- `示例:` + + ```sh + Emitting RebootLogoutRequired required_status = reboot + ``` + + + +#### InstallDetectStatus + +- `介绍:`下载安装前的状态检查 + +- `出参`: `b:`检查出错时为`False`,没有错误`success`,`s:`产生错误的码 + +- 错误码示例: + + ```python + ERROR_NOT_DISK_SPACE = "error-not-disk-space" + ``` + +- `示例:` + + ```sh + #表示出现磁盘已满的错误z + InstallDetectStatus success = False , error_code = "error-not-disk-space" + ``` + + + + + + + + +后端日志:`/var/log/kylin-system-updater/kylin-system-updater.log.1` + +### 更新过程报错信息总结 + +| 错误信息 | 错误原因 | 解决办法 | +| -------------------- | ------------------------------------------------------------ | ---------------------------------------- | +| 下载软件仓库信息失败 | 源存在问题,使用apt update检查,若存在报错则更新管理器无问题 | 检查源是否可以使用 | +| 无法访问源管理服务器 | 源管理服务器存在问题 | 源管理服务器是否可用或者检查源服务器配置 | +| 软件索引已经损坏 | 当前系统中cache被破坏,apt无法使用 | 终端检查错误原因进行解决 | +| 无法初始化软件包信息 | apt存在某些问题 | 具体错误原因查看日志相应解决 | +| 无法获取组配置软件包 | 源中不存在kylin-update-desktop-config | 将此包放入源仓库或者写配置文件不强制安装 | +| 无法读取推送升级列表 | 读取推送列表出现问题 | 检查important.list是否存在 | +| 获取软件推送失败 | 老版本文案同 无法访问源管理服务器解决 | | + + + +### 安装过程报错信息总结 + +| 错误信息 | 错误原因 | 解决办法 | +| ------------------ | ------------------------------ | ---------------------------------- | +| 软件包操作失败 | 被升级的软件包有问题 | 检查后端log日志查看那个包存在问题 | +| 下载软件包文件失败 | 网络原因的或者这个软件包的仓库 | 检查网络以及源仓库 | +| 磁盘空间不足 | 磁盘的空间不足 | 查看日志那些目录空间不足 | +| 不能计算升级 | 无法计算依赖关系 | 检查日志那个包出现的问题,相应解决 | +| | | | + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/kylin-system-updater b/backend/kylin-system-updater new file mode 100755 index 0000000..2fe08b5 --- /dev/null +++ b/backend/kylin-system-updater @@ -0,0 +1,103 @@ +#!/usr/bin/python3 + +from SystemUpdater.UpdateManager import UpdateManager +from gettext import gettext as _ +import logging +from optparse import OptionParser +import dbus +dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) +import signal +import os +import sys +import gettext + +from SystemUpdater.Core.LogManager import get_logfile as logfile + +gettext.bindtextdomain('kylin-system-updater', '/usr/share/locale') +gettext.textdomain('kylin-system-updater') +_ = gettext.gettext + +#定义日志的格式 +FORMAT = "%(asctime)s [%(levelname)s]: %(message)s" + +FORMAT_DEBUG = '%(asctime)-15s %(levelname)s(%(filename)s:%(lineno)d):%(message)s' + +def signal_handler_term(signal, frame): + # type: (int, object) -> None + logging.warning("SIGTERM received, will stop") + app.dbusController.Quit(None) + +if __name__ == "__main__": + # Begin parsing of options + parser = OptionParser() + parser.add_option ("-d", "--debug", action="store_true", default=False, + help=_("Show debug messages")) + parser.add_option("-r", "--replace", + default=False, + action="store_true", dest="replace", + help=_("Quit and replace an already running " + "daemon")) + parser.add_option ("-n","--no-update-source", action="store_true", + dest="no_update_source", default=False, + help=_("Do not check for updates source when updating")) + parser.add_option ("--no-update", action="store_true", + dest="no_update", default=False, + help="Do not check for updates when starting") + parser.add_option("-c", "--close-filter", + default=False, + action="store_true", dest="close_filter", + help=_("Quit and close allowed origin")) + parser.add_option("--no-check-network", + default=False, + action="store_true", dest="no_check_network", + help=_("Quit and close check network")) + + (options, args) = parser.parse_args() + + if os.getuid() != 0: + print(_("You need to be root to run this application")) + sys.exit(1) + + # set debconf to NON_INTERACTIVE + os.environ["DEBIAN_FRONTEND"] = "noninteractive" + os.environ["TERM"] = "xterm" + os.environ["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + + #当不存在语言变量时 默认显示中文 + if not "LANGUAGE" in os.environ: + os.environ["LANGUAGE"] = "zh_CN.UTF-8" + + #当不存在语言变量时 默认显示中文 + if not "LANG" in os.environ: + os.environ["LANG"] = "zh_CN.UTF-8" + + #做一些规范处理 + if os.environ["LANGUAGE"] == "en": + os.environ["LANGUAGE"] = "en_US.UTF-8" + if os.environ["LANGUAGE"] == "zh_CN:en" or os.environ["LANGUAGE"] == "zh_CN:zh": + os.environ["LANGUAGE"] = "zh_CN.UTF-8" + + # ensure that we are not killed when the terminal goes away e.g. on + # shutdown + signal.signal(signal.SIGHUP, signal.SIG_IGN) + signal.signal(signal.SIGINT,signal_handler_term) + + if options.debug: + logging.basicConfig(format=FORMAT,level=logging.INFO,datefmt='%m-%d,%H:%M:%S') + else: + logging.basicConfig(format=FORMAT,level=logging.DEBUG,datefmt='%m-%d,%H:%M:%S',filename = logfile(),filemode = 'a') + + logging.info('kylin-system-updater(LANGUAGE:%s LANG:%s) starting ...',os.environ["LANGUAGE"],os.environ["LANG"]) + + app = UpdateManager(options) + + #当出现安装过程中异常的重启时 开机直接进行修复操作 + # if app.configs_cover.getWithDefault("ConfigPkgStatus", "check_frontend_pkg", False) == True: + # app.configs_cover.setValue("ConfigPkgStatus","check_frontend_pkg",str(False),True) + # app.check_frontend_pkg() + + #当出现安装过程中异常的重启时 开机直接进行修复操作 + if app.configs_uncover.getWithDefault("SystemStatus", "abnormal_reboot", False) == True: + app.start_update() + + app.run() \ No newline at end of file diff --git a/backend/kylin-upgrade-strategies b/backend/kylin-upgrade-strategies new file mode 100755 index 0000000..a343f46 --- /dev/null +++ b/backend/kylin-upgrade-strategies @@ -0,0 +1,81 @@ +#!/usr/bin/python3 + +from SystemUpdater.UpgradeStrategies import UpgradeStrategies +from gettext import gettext as _ +import logging +from optparse import OptionParser +import dbus +dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) +import signal +import os +import sys +import gettext + +from SystemUpdater.Core.LogManager import upgrade_strategies_logfile as logfile + +gettext.bindtextdomain('kylin-system-updater', '/usr/share/locale') +gettext.textdomain('kylin-system-updater') +_ = gettext.gettext + +#定义日志的格式 +FORMAT = "%(asctime)s [%(levelname)s]: %(message)s" + +FORMAT_DEBUG = '%(asctime)-15s %(levelname)s(%(filename)s:%(lineno)d):%(message)s' + +def signal_handler_term(signal, frame): + # type: (int, object) -> None + logging.warning("SIGTERM received, will stop") + app.dbusController.Quit(None) + +if __name__ == "__main__": + # Begin parsing of options + parser = OptionParser() + parser.add_option ("-d", "--debug", action="store_true", default=False, + help=_("Show debug messages")) + parser.add_option("-r", "--replace", + default=False, + action="store_true", dest="replace", + help=_("Quit and replace an already running " + "daemon")) + + + (options, args) = parser.parse_args() + + if os.getuid() != 0: + print(_("You need to be root to run this application")) + sys.exit(1) + + # set debconf to NON_INTERACTIVE + os.environ["DEBIAN_FRONTEND"] = "noninteractive" + os.environ["TERM"] = "xterm" + os.environ["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + + #当不存在语言变量时 默认显示中文 + if not "LANGUAGE" in os.environ: + os.environ["LANGUAGE"] = "zh_CN.UTF-8" + + #当不存在语言变量时 默认显示中文 + if not "LANG" in os.environ: + os.environ["LANG"] = "zh_CN.UTF-8" + + #做一些规范处理 + if os.environ["LANGUAGE"] == "en": + os.environ["LANGUAGE"] = "en_US.UTF-8" + if os.environ["LANGUAGE"] == "zh_CN:en" or os.environ["LANGUAGE"] == "zh_CN:zh": + os.environ["LANGUAGE"] = "zh_CN.UTF-8" + + # ensure that we are not killed when the terminal goes away e.g. on + # shutdown + signal.signal(signal.SIGHUP, signal.SIG_IGN) + signal.signal(signal.SIGINT,signal_handler_term) + + if options.debug: + logging.basicConfig(format=FORMAT,level=logging.INFO,datefmt='%m-%d,%H:%M:%S') + else: + logging.basicConfig(format=FORMAT,level=logging.DEBUG,datefmt='%m-%d,%H:%M:%S',filename = logfile(),filemode = 'a') + + + logging.info('Updater Config Manager Daemon(LANGUAGE:%s LANG:%s) starting ...',os.environ["LANGUAGE"],os.environ["LANG"]) + + app = UpgradeStrategies(options) + app.run() \ No newline at end of file diff --git a/backend/po/ChangeLog b/backend/po/ChangeLog new file mode 100644 index 0000000..123b7dd --- /dev/null +++ b/backend/po/ChangeLog @@ -0,0 +1,6 @@ +2021-09-16 XueYi Luo + + * zh_CN.po: Updated Simplified Chinese translation. + * zh_HK.po: Updated translation for HongKong,china. + * zh_TW.po: Updated translation for Taiwan,China. + diff --git a/backend/po/Makefile b/backend/po/Makefile new file mode 100644 index 0000000..dacf43d --- /dev/null +++ b/backend/po/Makefile @@ -0,0 +1,28 @@ + +top_srcdir=`pwd`/.. + +DOMAIN=kylin-system-updater +PO_FILES := $(wildcard *.po) +CONTACT=sebastian.heinlein@web.de +XGETTEXT_ARGS = --msgid-bugs-address=$(CONTACT) +XGETTEXT_ARGS += --keyword=unicode_gettext:2 --keyword=unicode_ngettext:2,3 +XGETTEXT_ARGS += --language=python + +all: update-po + +# update the pot +$(DOMAIN).pot: + XGETTEXT_ARGS="$(XGETTEXT_ARGS)" intltool-update -p -g $(DOMAIN) + +# merge the new stuff into the po files +merge-po: $(PO_FILES) + XGETTEXT_ARGS="$(XGETTEXT_ARGS)" intltool-update -r -g $(DOMAIN); + +# create mo from the pos +%.mo : %.po + mkdir -p mo/$(subst .po,,$<)/LC_MESSAGES/ + msgfmt $< -o mo/$(subst .po,,$<)/LC_MESSAGES/$(DOMAIN).mo + +# dummy target +update-po: $(DOMAIN).pot merge-po $(patsubst %.po,%.mo,$(wildcard *.po)) + diff --git a/backend/po/POTFILES.in b/backend/po/POTFILES.in new file mode 100644 index 0000000..ada57ef --- /dev/null +++ b/backend/po/POTFILES.in @@ -0,0 +1,11 @@ +[encoding: UTF-8] +SystemUpdater/backend/InstallBackendAptdaemon.py +SystemUpdater/backend/__init__.py +SystemUpdater/UpdateManager.py +SystemUpdater/Core/MyCache.py +SystemUpdater/Core/UpdateList.py +SystemUpdater/Core/OriginFilter.py +SystemUpdater/Core/Database.py +SystemUpdater/UpdateManagerDbus.py +SystemUpdater/Core/utils.py +SystemUpdater/Core/enums.py diff --git a/backend/po/POTFILES.skip b/backend/po/POTFILES.skip new file mode 100644 index 0000000..e69de29 diff --git a/backend/po/zh_CN.po b/backend/po/zh_CN.po new file mode 100644 index 0000000..6a2e677 --- /dev/null +++ b/backend/po/zh_CN.po @@ -0,0 +1,2763 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: sebastian.heinlein@web.de\n" +"POT-Creation-Date: 2012-06-14 00:53+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. TRANSLATORS: download size of small updates, e.g. "250 kB" +#: ../DistUpgrade/utils.py:433 ../UpdateManager/Core/utils.py:433 +#, python-format +msgid "%(size).0f kB" +msgid_plural "%(size).0f kB" +msgstr[0] "%(size).0f kB" + +#. TRANSLATORS: download size of updates, e.g. "2.3 MB" +#: ../DistUpgrade/utils.py:436 ../UpdateManager/Core/utils.py:436 +#, python-format +msgid "%.1f MB" +msgstr "%.1f MB" + +#. TRANSLATORS: %s is a country +#: ../DistUpgrade/distro.py:206 ../DistUpgrade/distro.py:436 +#, python-format +msgid "Server for %s" +msgstr "%s 的服务器" + +#. More than one server is used. Since we don't handle this case +#. in the user interface we set "custom servers" to true and +#. append a list of all used servers +#: ../DistUpgrade/distro.py:224 ../DistUpgrade/distro.py:230 +#: ../DistUpgrade/distro.py:246 +msgid "Main server" +msgstr "主服务器" + +#: ../DistUpgrade/distro.py:250 +msgid "Custom servers" +msgstr "自定义服务器" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:142 +msgid "Could not calculate sources.list entry" +msgstr "无法计算 sources.list 条目" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:251 +msgid "" +"Unable to locate any package files, perhaps this is not a Ubuntu Disc or the " +"wrong architecture?" +msgstr "无法定位任何软件包文件,也许这张不是 Ubuntu 光盘,或者其架构错误?" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:294 +msgid "Failed to add the CD" +msgstr "添加 CD 失败" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:295 +#, python-format +msgid "" +"There was a error adding the CD, the upgrade will abort. Please report this " +"as a bug if this is a valid Ubuntu CD.\n" +"\n" +"The error message was:\n" +"'%s'" +msgstr "" +"添加 CD 时出错,升级中止。如果这是一张有效的 Ubuntu CD,请您报告这个错误。\n" +"\n" +"错误信息是:\n" +"'%s'" + +#: ../DistUpgrade/DistUpgradeCache.py:151 +msgid "Remove package in bad state" +msgid_plural "Remove packages in bad state" +msgstr[0] "卸载状态异常的软件包" + +#: ../DistUpgrade/DistUpgradeCache.py:154 +#, python-format +msgid "" +"The package '%s' is in an inconsistent state and needs to be reinstalled, " +"but no archive can be found for it. Do you want to remove this package now " +"to continue?" +msgid_plural "" +"The packages '%s' are in an inconsistent state and need to be reinstalled, " +"but no archives can be found for them. Do you want to remove these packages " +"now to continue?" +msgstr[0] "" +"软件包“%s”处于不一致的状态,需要重新安装,但是没有找到对应的存档。您希望现在" +"删除这个软件包以进行下一步吗?" + +#. FIXME: not ideal error message, but we just reuse a +#. existing one here to avoid a new string +#: ../DistUpgrade/DistUpgradeCache.py:255 +msgid "The server may be overloaded" +msgstr "服务器可能已过载" + +#: ../DistUpgrade/DistUpgradeCache.py:368 +msgid "Broken packages" +msgstr "破损的软件包" + +#: ../DistUpgrade/DistUpgradeCache.py:369 +msgid "" +"Your system contains broken packages that couldn't be fixed with this " +"software. Please fix them first using synaptic or apt-get before proceeding." +msgstr "" +"您的系统包含有本软件不能修复的破损软件包,在您继续前请先用新立得或者 apt-get " +"修复它们。" + +#. FIXME: change the text to something more useful +#: ../DistUpgrade/DistUpgradeCache.py:693 +#, python-format +msgid "" +"An unresolvable problem occurred while calculating the upgrade:\n" +"%s\n" +"\n" +" This can be caused by:\n" +" * Upgrading to a pre-release version of Ubuntu\n" +" * Running the current pre-release version of Ubuntu\n" +" * Unofficial software packages not provided by Ubuntu\n" +"\n" +msgstr "" +"在准备升级时发生了一个无法解决的问题:\n" +"%s\n" +"\n" +" 这可能是由以下原因引起的:\n" +" * 升级到了预发行 Ubuntu 版本\n" +" * 正在运行当前的预发行 Ubuntu 版本\n" +" * 非 Ubuntu 提供的非官方软件包\n" +"\n" + +#: ../DistUpgrade/DistUpgradeCache.py:703 +msgid "This is most likely a transient problem, please try again later." +msgstr "很可能发生了一个传输问题,请稍后重试。" + +#: ../DistUpgrade/DistUpgradeCache.py:706 +msgid "" +"If none of this applies, then please report this bug using the command " +"'ubuntu-bug update-manager' in a terminal." +msgstr "" +"如果没有应用任何变更,您可以在终端里输入命令‘ubuntu-bug update-manager’来报告" +"这个 bug。" + +#: ../DistUpgrade/DistUpgradeCache.py:711 +#: ../UpdateManager/UpdateManager.py:1031 +msgid "Could not calculate the upgrade" +msgstr "不能计算升级" + +#: ../DistUpgrade/DistUpgradeCache.py:762 +msgid "Error authenticating some packages" +msgstr "一些软件包认证出错" + +#: ../DistUpgrade/DistUpgradeCache.py:763 +msgid "" +"It was not possible to authenticate some packages. This may be a transient " +"network problem. You may want to try again later. See below for a list of " +"unauthenticated packages." +msgstr "" +"一些软件包无法通过签名验证。这可能是暂时的网络问题,您可以在稍后再试。以下是" +"未认证软件包的列表。" + +#: ../DistUpgrade/DistUpgradeCache.py:783 +#, python-format +msgid "" +"The package '%s' is marked for removal but it is in the removal blacklist." +msgstr "软件包“%s”标记为可移除,但它已在移除黑名单中。" + +#: ../DistUpgrade/DistUpgradeCache.py:787 +#, python-format +msgid "The essential package '%s' is marked for removal." +msgstr "必要的软件包“%s”被标记为移除。" + +#: ../DistUpgrade/DistUpgradeCache.py:796 +#, python-format +msgid "Trying to install blacklisted version '%s'" +msgstr "尝试安装黑名单版本“%s”" + +#: ../DistUpgrade/DistUpgradeCache.py:914 +#, python-format +msgid "Can't install '%s'" +msgstr "无法安装 '%s'" + +#: ../DistUpgrade/DistUpgradeCache.py:915 +msgid "" +"It was impossible to install a required package. Please report this as a bug " +"using 'ubuntu-bug update-manager' in a terminal." +msgstr "" +"无法安装一个必需的软件包。请在终端里输入命令‘ubuntu-bug update-manager’来报告" +"这个 bug。" + +#. FIXME: provide a list +#: ../DistUpgrade/DistUpgradeCache.py:926 +msgid "Can't guess meta-package" +msgstr "无法猜出元软件包" + +#: ../DistUpgrade/DistUpgradeCache.py:927 +msgid "" +"Your system does not contain a ubuntu-desktop, kubuntu-desktop, xubuntu-" +"desktop or edubuntu-desktop package and it was not possible to detect which " +"version of Ubuntu you are running.\n" +" Please install one of the packages above first using synaptic or apt-get " +"before proceeding." +msgstr "" +"您的系统没有安装 ubuntu-desktop,kubuntu-desktop 或 eubuntu-desktop 软件包所" +"以无法确定运行的 ubuntu 的版本。\n" +" 请先用新立得或 APT 安装以上所举软件包中的一个。" + +#: ../DistUpgrade/DistUpgradeController.py:114 +msgid "Reading cache" +msgstr "正在读取缓存" + +#: ../DistUpgrade/DistUpgradeController.py:223 +msgid "Unable to get exclusive lock" +msgstr "无法获得排它锁" + +#: ../DistUpgrade/DistUpgradeController.py:224 +msgid "" +"This usually means that another package management application (like apt-get " +"or aptitude) already running. Please close that application first." +msgstr "" +"这通常意味着另一个软件包管理程序(如 apt-get 或 aptitude)正在运行。请先关闭那" +"个程序。" + +#: ../DistUpgrade/DistUpgradeController.py:257 +msgid "Upgrading over remote connection not supported" +msgstr "不支持通过远程连接升级" + +#: ../DistUpgrade/DistUpgradeController.py:258 +msgid "" +"You are running the upgrade over a remote ssh connection with a frontend " +"that does not support this. Please try a text mode upgrade with 'do-release-" +"upgrade'.\n" +"\n" +"The upgrade will abort now. Please try without ssh." +msgstr "" +"您正在通过远程 SSH 升级,而前端程序不支持这种方式。请尝试在文本模式下通过 " +"'do-release-upgrade' 命令进行升级。\n" +"\n" +"现在将退出升级。请试试不使用 ssh 的方式。" + +#: ../DistUpgrade/DistUpgradeController.py:272 +msgid "Continue running under SSH?" +msgstr "继续在 SSH 下执行?" + +#: ../DistUpgrade/DistUpgradeController.py:273 +#, python-format +msgid "" +"This session appears to be running under ssh. It is not recommended to " +"perform a upgrade over ssh currently because in case of failure it is harder " +"to recover.\n" +"\n" +"If you continue, an additional ssh daemon will be started at port '%s'.\n" +"Do you want to continue?" +msgstr "" +"此会话似乎是在 SSH 下运行。目前不推荐通过 SSH 执行升级,因为升级失败时较难恢" +"复。\n" +"\n" +"如果您选择继续,将在 '%s' 端口上建立额外的 SSH 守护进程。\n" +"您想要继续吗?" + +#: ../DistUpgrade/DistUpgradeController.py:287 +msgid "Starting additional sshd" +msgstr "正在启用额外的 ssh 守护进程" + +#: ../DistUpgrade/DistUpgradeController.py:288 +#, python-format +msgid "" +"To make recovery in case of failure easier, an additional sshd will be " +"started on port '%s'. If anything goes wrong with the running ssh you can " +"still connect to the additional one.\n" +msgstr "" +"为了在失败时更容易恢复,将在端口“%s”开启一个额外的 ssh 守护进程。如果当前运行" +"的 ssh 发生错误,您仍能够通过该额外的 ssh 进行连接。\n" + +#: ../DistUpgrade/DistUpgradeController.py:296 +#, python-format +msgid "" +"If you run a firewall, you may need to temporarily open this port. As this " +"is potentially dangerous it's not done automatically. You can open the port " +"with e.g.:\n" +"'%s'" +msgstr "" +"如果您运行了一个防火墙,可能需要临时打开这个端口。这可能有些危险,因此没有自" +"动进行这个操作。您可以通过类似这样的命令打开端口:\n" +"%s" + +#: ../DistUpgrade/DistUpgradeController.py:368 +#: ../DistUpgrade/DistUpgradeController.py:413 +msgid "Can not upgrade" +msgstr "不能升级" + +#: ../DistUpgrade/DistUpgradeController.py:369 +#, python-format +msgid "An upgrade from '%s' to '%s' is not supported with this tool." +msgstr "此工具不支持从 '%s' 到 ‘%s' 的升级。" + +#: ../DistUpgrade/DistUpgradeController.py:378 +msgid "Sandbox setup failed" +msgstr "安装沙盒失败" + +#: ../DistUpgrade/DistUpgradeController.py:379 +msgid "It was not possible to create the sandbox environment." +msgstr "不能创建沙盒环境" + +#: ../DistUpgrade/DistUpgradeController.py:385 +msgid "Sandbox mode" +msgstr "沙盒模式" + +#: ../DistUpgrade/DistUpgradeController.py:386 +#, python-format +msgid "" +"This upgrade is running in sandbox (test) mode. All changes are written to " +"'%s' and will be lost on the next reboot.\n" +"\n" +"*No* changes written to a system directory from now until the next reboot " +"are permanent." +msgstr "" +"本次更新运行在沙盘(测试)模式,所有变更都将写入 '%s' 并会在重启后丢失。\n" +"\n" +"从现在起对系统目录的变更重启后都将 *不复存在* 。" + +#: ../DistUpgrade/DistUpgradeController.py:414 +msgid "" +"Your python install is corrupted. Please fix the '/usr/bin/python' symlink." +msgstr "您的 python 安装错误,请修复“/usr/bin/python”符号链接。" + +#: ../DistUpgrade/DistUpgradeController.py:440 +msgid "Package 'debsig-verify' is installed" +msgstr "已安装软件包“debsig-verify”" + +#: ../DistUpgrade/DistUpgradeController.py:441 +msgid "" +"The upgrade can not continue with that package installed.\n" +"Please remove it with synaptic or 'apt-get remove debsig-verify' first and " +"run the upgrade again." +msgstr "" +"由于安装了上述软件包而无法继续升级。\n" +"请使用新立得软件包管理器来移除它,或者先使用 “sudo apt-get remove debsig-" +"verify”卸载后再重新尝试升级。" + +#: ../DistUpgrade/DistUpgradeController.py:453 +#, python-format +msgid "Can not write to '%s'" +msgstr "无法写入 '%s'" + +#: ../DistUpgrade/DistUpgradeController.py:454 +#, python-format +msgid "" +"Its not possible to write to the system directory '%s' on your system. The " +"upgrade can not continue.\n" +"Please make sure that the system directory is writable." +msgstr "" +"无法写入您的系统目录 %s ,升级无法继续。\n" +"请确保系统目录可写。" + +#: ../DistUpgrade/DistUpgradeController.py:465 +msgid "Include latest updates from the Internet?" +msgstr "包括网络上的最新更新?" + +#: ../DistUpgrade/DistUpgradeController.py:466 +msgid "" +"The upgrade system can use the internet to automatically download the latest " +"updates and install them during the upgrade. If you have a network " +"connection this is highly recommended.\n" +"\n" +"The upgrade will take longer, but when it is complete, your system will be " +"fully up to date. You can choose not to do this, but you should install the " +"latest updates soon after upgrading.\n" +"If you answer 'no' here, the network is not used at all." +msgstr "" +"升级系统能够从网络上自动下载最新的更新文件并自动安装。如果网络连接可用,强烈" +"建议启用此选项。\n" +"\n" +"升级时间可能加长,但升级结束后,您的系统将是最新的。您也可以选择在升级结束后" +"再进行系统更新。\n" +"如果选择\"否\",就不会使用网络。" + +#: ../DistUpgrade/DistUpgradeController.py:686 +#, python-format +msgid "disabled on upgrade to %s" +msgstr "已禁止升级到 %s" + +#: ../DistUpgrade/DistUpgradeController.py:713 +msgid "No valid mirror found" +msgstr "未找到可用的镜像" + +#: ../DistUpgrade/DistUpgradeController.py:714 +#, python-format +msgid "" +"While scanning your repository information no mirror entry for the upgrade " +"was found. This can happen if you run a internal mirror or if the mirror " +"information is out of date.\n" +"\n" +"Do you want to rewrite your 'sources.list' file anyway? If you choose 'Yes' " +"here it will update all '%s' to '%s' entries.\n" +"If you select 'No' the upgrade will cancel." +msgstr "" +"扫描仓库时未发现升级信息。可能您使用的是内部镜像或者镜像信息已经过期。\n" +"\n" +"是否依然重写 \"sources.list\" 文件?如果选择“是”,将会把所有 “%s” 升级到 " +"“%s”。" + +#. hm, still nothing useful ... +#: ../DistUpgrade/DistUpgradeController.py:734 +msgid "Generate default sources?" +msgstr "生成默认的源?" + +#: ../DistUpgrade/DistUpgradeController.py:735 +#, python-format +msgid "" +"After scanning your 'sources.list' no valid entry for '%s' was found.\n" +"\n" +"Should default entries for '%s' be added? If you select 'No', the upgrade " +"will cancel." +msgstr "" +"扫描“sources.list”后未发现用于“%s”的可用项。\n" +"\n" +"是否为“%s”添加默认项?如果选择“否”,将会取消升级。" + +#: ../DistUpgrade/DistUpgradeController.py:770 +msgid "Repository information invalid" +msgstr "仓库信息无效" + +#: ../DistUpgrade/DistUpgradeController.py:771 +msgid "" +"Upgrading the repository information resulted in a invalid file so a bug " +"reporting process is being started." +msgstr "更新软件源时返回了无效文件,已启动错误报告进程。" + +#: ../DistUpgrade/DistUpgradeController.py:778 +msgid "Third party sources disabled" +msgstr "第三方源被禁用" + +#: ../DistUpgrade/DistUpgradeController.py:779 +msgid "" +"Some third party entries in your sources.list were disabled. You can re-" +"enable them after the upgrade with the 'software-properties' tool or your " +"package manager." +msgstr "" +"您的 sources.list 中的一些第三方源被禁用。您可以在升级后用\"软件源\"工具或包" +"管理器来重新启用它们。" + +#: ../DistUpgrade/DistUpgradeController.py:819 +msgid "Package in inconsistent state" +msgid_plural "Packages in inconsistent state" +msgstr[0] "软件包存在冲突" + +#: ../DistUpgrade/DistUpgradeController.py:822 +#, python-format +msgid "" +"The package '%s' is in an inconsistent state and needs to be reinstalled, " +"but no archive can be found for it. Please reinstall the package manually or " +"remove it from the system." +msgid_plural "" +"The packages '%s' are in an inconsistent state and need to be reinstalled, " +"but no archive can be found for them. Please reinstall the packages manually " +"or remove them from the system." +msgstr[0] "" +"软件包“%s”处于冲突状态,需要重新安装, 但是没能找到它的存档。请重新手动安装这" +"个软件包或者将其从系统中删除。" + +#: ../DistUpgrade/DistUpgradeController.py:870 +msgid "Error during update" +msgstr "升级时出错" + +#: ../DistUpgrade/DistUpgradeController.py:871 +msgid "" +"A problem occurred during the update. This is usually some sort of network " +"problem, please check your network connection and retry." +msgstr "升级过程中出错。这通常是一些网络问题,请检查您的网络连接后再试" + +#. print("on_button_install_clicked") +#: ../DistUpgrade/DistUpgradeController.py:880 +#: ../UpdateManager/UpdateManager.py:757 +msgid "Not enough free disk space" +msgstr "磁盘空间不足" + +#: ../DistUpgrade/DistUpgradeController.py:881 +#, python-format +msgid "" +"The upgrade has aborted. The upgrade needs a total of %s free space on disk " +"'%s'. Please free at least an additional %s of disk space on '%s'. Empty " +"your trash and remove temporary packages of former installations using 'sudo " +"apt-get clean'." +msgstr "" +"升级已被中断。此次升级需要有 %s 的可用空间在磁盘 %s 上。请释放至少 %s 的空间" +"在磁盘 %s 上。您可以清空回收站并使用“sudo apt-get clean”命令以清除之前安装操" +"作留下的临时文件。" + +#. calc the dist-upgrade and see if the removals are ok/expected +#. do the dist-upgrade +#: ../DistUpgrade/DistUpgradeController.py:910 +#: ../DistUpgrade/DistUpgradeController.py:1692 +msgid "Calculating the changes" +msgstr "正在计算变更" + +#. ask the user +#: ../DistUpgrade/DistUpgradeController.py:942 +msgid "Do you want to start the upgrade?" +msgstr "您要开始升级么?" + +#: ../DistUpgrade/DistUpgradeController.py:1008 +msgid "Upgrade canceled" +msgstr "升级已取消" + +#: ../DistUpgrade/DistUpgradeController.py:1009 +msgid "" +"The upgrade will cancel now and the original system state will be restored. " +"You can resume the upgrade at a later time." +msgstr "升级将会取消,系统会恢复到原始状态。您可以在之后继续升级。" + +#: ../DistUpgrade/DistUpgradeController.py:1015 +#: ../DistUpgrade/DistUpgradeController.py:1149 +msgid "Could not download the upgrades" +msgstr "无法下载升级包" + +#: ../DistUpgrade/DistUpgradeController.py:1016 +msgid "" +"The upgrade has aborted. Please check your Internet connection or " +"installation media and try again. All files downloaded so far have been kept." +msgstr "" +"已中止升级。请检查您的互联网连接或安装媒体并重试。所有已下载的文件都已保存。" + +#. FIXME: strings are not good, but we are in string freeze +#. currently +#: ../DistUpgrade/DistUpgradeController.py:1100 +#: ../DistUpgrade/DistUpgradeController.py:1137 +#: ../DistUpgrade/DistUpgradeController.py:1242 +msgid "Error during commit" +msgstr "确认时出错" + +#. generate a new cache +#: ../DistUpgrade/DistUpgradeController.py:1102 +#: ../DistUpgrade/DistUpgradeController.py:1139 +#: ../DistUpgrade/DistUpgradeController.py:1281 +msgid "Restoring original system state" +msgstr "正在恢复原始系统状态" + +#: ../DistUpgrade/DistUpgradeController.py:1103 +#: ../DistUpgrade/DistUpgradeController.py:1118 +#: ../DistUpgrade/DistUpgradeController.py:1140 +msgid "Could not install the upgrades" +msgstr "无法安装升级" + +#. invoke the frontend now and show a error message +#: ../DistUpgrade/DistUpgradeController.py:1108 +msgid "" +"The upgrade has aborted. Your system could be in an unusable state. A " +"recovery will run now (dpkg --configure -a)." +msgstr "" +"更新已取消。您的系统可能处在不稳定状态。正在恢复 (dpkg --configure -a)。" + +#: ../DistUpgrade/DistUpgradeController.py:1113 +#, python-format +msgid "" +"\n" +"\n" +"Please report this bug in a browser at http://bugs.launchpad.net/ubuntu/" +"+source/update-manager/+filebug and attach the files in /var/log/dist-" +"upgrade/ to the bug report.\n" +"%s" +msgstr "" +"\n" +"\n" +"请使用浏览器在 http://bugs.launchpad.net/ubuntu/+source/update-manager/" +"+filebug 中报告此错误,然后随错误报告附上 /var/log/dist-upgrade/ 中的文件。\n" +"%s" + +#: ../DistUpgrade/DistUpgradeController.py:1150 +msgid "" +"The upgrade has aborted. Please check your Internet connection or " +"installation media and try again. " +msgstr "更新已取消。请检查您的因特网连接或安装媒体,然后再试一遍。 " + +#: ../DistUpgrade/DistUpgradeController.py:1230 +msgid "Remove obsolete packages?" +msgstr "删除陈旧的软件包?" + +#: ../DistUpgrade/DistUpgradeController.py:1231 +#: ../DistUpgrade/DistUpgrade.ui.h:8 +msgid "_Keep" +msgstr "保持(_K)" + +#: ../DistUpgrade/DistUpgradeController.py:1231 +msgid "_Remove" +msgstr "删除(_R)" + +#: ../DistUpgrade/DistUpgradeController.py:1243 +msgid "" +"A problem occurred during the clean-up. Please see the below message for " +"more information. " +msgstr "清理时出现问题。更多信息请查看以下消息。 " + +#. FIXME: instead of error out, fetch and install it +#. here +#: ../DistUpgrade/DistUpgradeController.py:1319 +msgid "Required depends is not installed" +msgstr "需要的依赖关系未安装" + +#: ../DistUpgrade/DistUpgradeController.py:1320 +#, python-format +msgid "The required dependency '%s' is not installed. " +msgstr "需要的依赖关系“%s”未安装 。 " + +#. sanity check (check for ubuntu-desktop, brokenCache etc) +#. then open the cache (again) +#: ../DistUpgrade/DistUpgradeController.py:1588 +#: ../DistUpgrade/DistUpgradeController.py:1653 +msgid "Checking package manager" +msgstr "正在检查软件包管理器" + +#: ../DistUpgrade/DistUpgradeController.py:1593 +msgid "Unable to get group configuration package, Please check if the configuration package exists in the software source repository." +msgstr "无法获取组配置软件包,请检查组配置包是否存在软件源仓库中。" + +#: ../DistUpgrade/DistUpgradeController.py:1593 +msgid "Unable to install group configuration package, Please check the configuration package related dependencies." +msgstr "无法安装组配置包,请检查组配置包相关依赖。" + +msgid "Unable to perform priority upgrade, please check the dependency related to the priority upgrade package." +msgstr "无法进行优先升级,请检查优先升级包相关依赖。" + +#: ../DistUpgrade/DistUpgradeController.py:1594 +msgid "" +"Preparing the system for the upgrade failed so a bug reporting process is " +"being started." +msgstr "更新时的系统准备失败,已启动错误报告进程。" + +#: ../DistUpgrade/DistUpgradeController.py:1608 +msgid "Getting upgrade prerequisites failed" +msgstr "准备升级失败" + +#: ../DistUpgrade/DistUpgradeController.py:1609 +msgid "" +"The system was unable to get the prerequisites for the upgrade. The upgrade " +"will abort now and restore the original system state.\n" +"\n" +"Additionally, a bug reporting process is being started." +msgstr "" +"系统不能满足升级先决条件。现在会中止升级并恢复原来的系统状态。\n" +"\n" +"此外,错误报告进程也将启动。" + +#: ../DistUpgrade/DistUpgradeController.py:1637 +msgid "Updating repository information" +msgstr "正在更新软件仓库信息" + +#: ../DistUpgrade/DistUpgradeController.py:1644 +msgid "Failed to add the cdrom" +msgstr "添加光驱失败" + +#: ../DistUpgrade/DistUpgradeController.py:1645 +msgid "Sorry, adding the cdrom was not successful." +msgstr "对不起,没有成功添加光驱。" + +#: ../DistUpgrade/DistUpgradeController.py:1673 +msgid "Invalid package information" +msgstr "无效的软件包信息" + +#: ../DistUpgrade/DistUpgradeController.py:1674 +msgid "After updating your package " +msgstr "" + +#: ../DistUpgrade/DistUpgradeController.py:1698 +#: ../DistUpgrade/DistUpgradeController.py:1750 +msgid "Fetching" +msgstr "正在获取" + +#: ../DistUpgrade/DistUpgradeController.py:1704 +#: ../DistUpgrade/DistUpgradeController.py:1754 +msgid "Upgrading" +msgstr "正在升级" + +#. don't abort here, because it would restore the sources.list +#: ../DistUpgrade/DistUpgradeController.py:1709 +#: ../DistUpgrade/DistUpgradeController.py:1756 +#: ../DistUpgrade/DistUpgradeController.py:1763 +#: ../DistUpgrade/DistUpgradeController.py:1774 +msgid "Upgrade complete" +msgstr "升级完成" + +#: ../DistUpgrade/DistUpgradeController.py:1710 +#: ../DistUpgrade/DistUpgradeController.py:1757 +#: ../DistUpgrade/DistUpgradeController.py:1764 +msgid "" +"The upgrade has completed but there were errors during the upgrade process." +msgstr "升级已完成,但其间出现错误。" + +#: ../DistUpgrade/DistUpgradeController.py:1717 +msgid "Searching for obsolete software" +msgstr "正在搜索废弃的软件" + +#: ../DistUpgrade/DistUpgradeController.py:1726 +msgid "System upgrade is complete." +msgstr "系统升级完成。" + +#: ../DistUpgrade/DistUpgradeController.py:1775 +msgid "The partial upgrade was completed." +msgstr "部分升级完成。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:204 +msgid "evms in use" +msgstr "evms 使用中" + +#: ../DistUpgrade/DistUpgradeQuirks.py:205 +msgid "" +"Your system uses the 'evms' volume manager in /proc/mounts. The 'evms' " +"software is no longer supported, please switch it off and run the upgrade " +"again when this is done." +msgstr "" +"您的系统在 /proc/mounts 中使用“evms”卷管理器。“evms”软件不再被支持,请关闭它" +"并重新运行升级。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:502 +msgid "Your graphics hardware may not be fully supported in Ubuntu 12.04 LTS." +msgstr "Ubuntu 12.04 LTS 可能无法完全支持您的显卡。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:504 +msgid "" +"The support in Ubuntu 12.04 LTS for your Intel graphics hardware is limited " +"and you may encounter problems after the upgrade. For more information see " +"https://wiki.ubuntu.com/X/Bugs/UpdateManagerWarningForI8xx Do you want to " +"continue with the upgrade?" +msgstr "" +"Ubuntu 12.04 LTS 对 Intel 图形硬件的支持有限制,升级后您可能会遭遇问题。更多" +"信息,请查看 https://wiki.ubuntu.com/X/Bugs/UpdateManagerWarningForI8xx 。仍" +"然想要继续升级吗?" + +#: ../DistUpgrade/DistUpgradeQuirks.py:526 +#: ../DistUpgrade/DistUpgradeQuirks.py:554 +#: ../DistUpgrade/DistUpgradeQuirks.py:581 +msgid "" +"Upgrading may reduce desktop effects, and performance in games and other " +"graphically intensive programs." +msgstr "升级过程可能会降低桌面特效,游戏性能以及对显示要求高的程序。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:530 +#: ../DistUpgrade/DistUpgradeQuirks.py:558 +msgid "" +"This computer is currently using the NVIDIA 'nvidia' graphics driver. No " +"version of this driver is available that works with your video card in " +"Ubuntu 10.04 LTS.\n" +"\n" +"Do you want to continue?" +msgstr "" +"此计算机正在使用 NVIDIA “nvidia”显卡驱动。该驱动在 Ubuntu 10.04 LTS 中没有与" +"您的硬件相适应的版本。\n" +"\n" +"您想继续吗?" + +#: ../DistUpgrade/DistUpgradeQuirks.py:585 +msgid "" +"This computer is currently using the AMD 'fglrx' graphics driver. No version " +"of this driver is available that works with your hardware in Ubuntu 10.04 " +"LTS.\n" +"\n" +"Do you want to continue?" +msgstr "" +"此计算机正在使用 AMD “fglrx”显卡驱动。该驱动在 Ubuntu 10.04 LTS 中没有与您的" +"硬件相适应的版本。\n" +"\n" +"您想继续吗?" + +#: ../DistUpgrade/DistUpgradeQuirks.py:615 +msgid "No i686 CPU" +msgstr "非 i686 CPU" + +#: ../DistUpgrade/DistUpgradeQuirks.py:616 +msgid "" +"Your system uses an i586 CPU or a CPU that does not have the 'cmov' " +"extension. All packages were built with optimizations requiring i686 as the " +"minimal architecture. It is not possible to upgrade your system to a new " +"Ubuntu release with this hardware." +msgstr "" +"您的系统使用的是 i586 或是没有“cmov”扩展的 CPU。所有优化生成的软件包都需要最" +"低 i686 的架构。这样的硬件无法升级到新的 Ubuntu 发行版。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:652 +msgid "No ARMv6 CPU" +msgstr "没有 ARMv6 CPU" + +#: ../DistUpgrade/DistUpgradeQuirks.py:653 +msgid "" +"Your system uses an ARM CPU that is older than the ARMv6 architecture. All " +"packages in karmic were built with optimizations requiring ARMv6 as the " +"minimal architecture. It is not possible to upgrade your system to a new " +"Ubuntu release with this hardware." +msgstr "" +"您的系统使用的是比 ARMv6 架构旧的 ARM CPU。karmic 中所有软件包构建时进行的优" +"化都需要至少 ARMv6 的 CPU 架构。在这种硬件基础上无法将您的系统升级到一个新的 " +"Ubuntu 发行版。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:673 +msgid "No init available" +msgstr "无可用的 init" + +#: ../DistUpgrade/DistUpgradeQuirks.py:674 +msgid "" +"Your system appears to be a virtualised environment without an init daemon, " +"e.g. Linux-VServer. Ubuntu 10.04 LTS cannot function within this type of " +"environment, requiring an update to your virtual machine configuration " +"first.\n" +"\n" +"Are you sure you want to continue?" +msgstr "" +"您的系统似乎运行在一个没有 init daemon 的虚拟化环境中(如 Linux-VServer)," +"Ubuntu 10.04 LTS 无法在这种环境中运行,您需要先更新您的虚拟机配置。\n" +"\n" +"您确定要继续吗?" + +#: ../DistUpgrade/DistUpgradeMain.py:65 +msgid "Sandbox upgrade using aufs" +msgstr "使用 aufs 进行沙盒升级" + +#: ../DistUpgrade/DistUpgradeMain.py:67 +msgid "Use the given path to search for a cdrom with upgradable packages" +msgstr "使用所给的路径查找带升级包的 CD-ROM" + +#: ../DistUpgrade/DistUpgradeMain.py:73 +msgid "" +"Use frontend. Currently available: \n" +"DistUpgradeViewText, DistUpgradeViewGtk, DistUpgradeViewKDE" +msgstr "" +"使用前端。当前可用的有: \n" +"DistUpgradeViewText, DistUpgradeViewGtk, DistUpgradeViewKDE" + +#: ../DistUpgrade/DistUpgradeMain.py:76 +msgid "*DEPRECATED* this option will be ignored" +msgstr "*已废弃* 这个选项将被忽略" + +#: ../DistUpgrade/DistUpgradeMain.py:79 +msgid "Perform a partial upgrade only (no sources.list rewriting)" +msgstr "仅执行部分升级(不重写 sources.list)" + +#: ../DistUpgrade/DistUpgradeMain.py:82 +msgid "Disable GNU screen support" +msgstr "关闭对 GNU screen 的支持" + +#: ../DistUpgrade/DistUpgradeMain.py:84 +msgid "Set datadir" +msgstr "设置数据目录" + +#. print("mediaChange %s %s" % (medium, drive)) +#: ../DistUpgrade/DistUpgradeViewGtk.py:114 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:117 +#: ../DistUpgrade/DistUpgradeViewKDE.py:195 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:155 +#, python-format +msgid "Please insert '%s' into the drive '%s'" +msgstr "请将 '%s' 插入光驱 '%s'" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:135 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:138 +#: ../DistUpgrade/DistUpgradeViewKDE.py:209 +msgid "Fetching is complete" +msgstr "下载完成" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:146 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:149 +#: ../DistUpgrade/DistUpgradeViewKDE.py:222 +#, python-format +msgid "Fetching file %li of %li at %sB/s" +msgstr "正在下载文件 %li/%li 速度 %s/s" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:149 +#: ../DistUpgrade/DistUpgradeViewGtk.py:296 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:152 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:309 +#: ../DistUpgrade/DistUpgradeViewKDE.py:223 +#: ../DistUpgrade/DistUpgradeViewKDE.py:371 +#, python-format +msgid "About %s remaining" +msgstr "大约还要 %s" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:152 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:155 +#: ../DistUpgrade/DistUpgradeViewKDE.py:225 +#, python-format +msgid "Fetching file %li of %li" +msgstr "下载第 %li 个文件(共 %li 个文件)" + +#. FIXME: add support for the timeout +#. of the terminal (to display something useful then) +#. -> longer term, move this code into python-apt +#: ../DistUpgrade/DistUpgradeViewGtk.py:183 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:186 +#: ../DistUpgrade/DistUpgradeViewKDE.py:262 +msgid "Applying changes" +msgstr "正在应用更改" + +#. we do not report followup errors from earlier failures +#: ../DistUpgrade/DistUpgradeViewGtk.py:208 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:212 +#: ../DistUpgrade/DistUpgradeViewKDE.py:275 +msgid "dependency problems - leaving unconfigured" +msgstr "依赖关系问题 - 保持未配置状态" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:213 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:217 +#: ../DistUpgrade/DistUpgradeViewKDE.py:277 +#, python-format +msgid "Could not install '%s'" +msgstr "无法安装“%s”" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:214 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:218 +#: ../DistUpgrade/DistUpgradeViewKDE.py:278 +#, python-format +msgid "" +"The upgrade will continue but the '%s' package may not be in a working " +"state. Please consider submitting a bug report about it." +msgstr "升级将继续进行,但“%s”可能没有工作。请考虑提交关于它的错误报告。" + +#. self.expander.set_expanded(True) +#: ../DistUpgrade/DistUpgradeViewGtk.py:231 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:235 +#: ../DistUpgrade/DistUpgradeViewKDE.py:299 +#, python-format +msgid "" +"Replace the customized configuration file\n" +"'%s'?" +msgstr "" +"替换自定义配置文件\n" +"“%s”吗?" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:232 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:236 +#: ../DistUpgrade/DistUpgradeViewKDE.py:300 +msgid "" +"You will lose any changes you have made to this configuration file if you " +"choose to replace it with a newer version." +msgstr "如果选择替换为新版本的配置文件,您将会失去所有已做的修改。" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:251 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:256 +#: ../DistUpgrade/DistUpgradeViewKDE.py:323 +msgid "The 'diff' command was not found" +msgstr "找不到 diff 命令" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:464 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:477 +#: ../DistUpgrade/DistUpgradeViewText.py:92 +msgid "A fatal error occurred" +msgstr "出现致命错误" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:465 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:478 +msgid "" +"Please report this as a bug (if you haven't already) and include the files /" +"var/log/dist-upgrade/main.log and /var/log/dist-upgrade/apt.log in your " +"report. The upgrade has aborted.\n" +"Your original sources.list was saved in /etc/apt/sources.list.distUpgrade." +msgstr "" +"请报告这个错误(如果还没有的话)并在报告中包括文件 /var/log/dist-upgrade/" +"main.log 和 /var/log/dist-upgrade/apt.log 。升级已取消。\n" +"您的原始 sources.list 已保存在 /etc/apt/sources.list.distUpgrade 。" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:482 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:495 +msgid "Ctrl-c pressed" +msgstr "Crtl+C 被按下" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:483 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:496 +msgid "" +"This will abort the operation and may leave the system in a broken state. " +"Are you sure you want to do that?" +msgstr "这将取消本次操作且可能使系统处于被破坏的状态。您确定要这样做?" + +#. append warning +#: ../DistUpgrade/DistUpgradeViewGtk.py:631 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:629 +msgid "To prevent data loss close all open applications and documents." +msgstr "为防止数据丢失,请关闭所有打开的应用程序和文档。" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:645 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:643 +#, python-format +msgid "No longer supported by Canonical (%s)" +msgstr "不再被 Canonical 支持 (%s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:646 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:644 +#, python-format +msgid "Downgrade (%s)" +msgstr "降级 (%s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:647 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:645 +#, python-format +msgid "Remove (%s)" +msgstr "卸载 (%s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:648 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:646 +#, python-format +msgid "No longer needed (%s)" +msgstr "不再需要 (%s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:649 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:647 +#, python-format +msgid "Install (%s)" +msgstr "安装 (%s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:650 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:648 +#, python-format +msgid "Upgrade (%s)" +msgstr "升级 (%s)" + +#. change = QMessageBox.question(None, _("Media Change"), msg, QMessageBox.Ok, QMessageBox.Cancel) +#: ../DistUpgrade/DistUpgradeViewKDE.py:196 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:157 +msgid "Media Change" +msgstr "改变介质" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:335 +msgid "Show Difference >>>" +msgstr "显示差别 >>>" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:338 +msgid "<<< Hide Difference" +msgstr "<<< 隐藏差别" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:554 +msgid "Error" +msgstr "错误" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:568 +msgid "&Cancel" +msgstr "取消(&C)" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:572 +#: ../DistUpgrade/DistUpgradeViewKDE.py:813 +msgid "&Close" +msgstr "关闭(&C)" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:618 +msgid "Show Terminal >>>" +msgstr "显示终端 >>>" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:621 +msgid "<<< Hide Terminal" +msgstr "<<< 隐藏终端" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:701 +msgid "Information" +msgstr "信息" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:751 +#: ../DistUpgrade/DistUpgradeViewKDE.py:796 +#: ../DistUpgrade/DistUpgradeViewKDE.py:799 ../DistUpgrade/DistUpgrade.ui.h:7 +msgid "Details" +msgstr "细节" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:777 +#, python-format +msgid "No longer supported %s" +msgstr "不再支持 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:779 +#, python-format +msgid "Remove %s" +msgstr "移除 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:781 +#: ../DistUpgrade/DistUpgradeViewText.py:182 +#, python-format +msgid "Remove (was auto installed) %s" +msgstr "移除(被自动安装的) %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:783 +#, python-format +msgid "Install %s" +msgstr "安装 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:785 +#, python-format +msgid "Upgrade %s" +msgstr "升级 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:809 +#: ../DistUpgrade/DistUpgradeViewText.py:230 +msgid "Restart required" +msgstr "需要重启" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:809 +msgid "Restart the system to complete the upgrade" +msgstr "重新启动系统以完成升级" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:812 ../DistUpgrade/DistUpgrade.ui.h:14 +#: ../data/gtkbuilder/UpdateManager.ui.h:26 +msgid "_Restart Now" +msgstr "现在重启(_R)" + +#. FIXME make this user friendly +#: ../DistUpgrade/DistUpgradeViewKDE.py:830 +msgid "" +"Cancel the running upgrade?\n" +"\n" +"The system could be in an unusable state if you cancel the upgrade. You are " +"strongly advised to resume the upgrade." +msgstr "" +"取消正在运行的升级?\n" +"\n" +"如果取消升级系统可能不稳定,强烈建议您继续升级。" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:834 +msgid "Cancel Upgrade?" +msgstr "取消升级?" + +#: ../DistUpgrade/DistUpgradeView.py:61 +#, python-format +msgid "%li day" +msgid_plural "%li days" +msgstr[0] "%li 天" + +#: ../DistUpgrade/DistUpgradeView.py:63 +#, python-format +msgid "%li hour" +msgid_plural "%li hours" +msgstr[0] "%li 小时" + +#: ../DistUpgrade/DistUpgradeView.py:65 +#, python-format +msgid "%li minute" +msgid_plural "%li minutes" +msgstr[0] "%li 分钟" + +#: ../DistUpgrade/DistUpgradeView.py:66 +#, python-format +msgid "%li second" +msgid_plural "%li seconds" +msgstr[0] "%li 秒" + +#. TRANSLATORS: you can alter the ordering of the remaining time +#. information here if you shuffle %(str_days)s %(str_hours)s %(str_minutes)s +#. around. Make sure to keep all '$(str_*)s' in the translated string +#. and do NOT change anything appart from the ordering. +#. +#. %(str_hours)s will be either "1 hour" or "2 hours" depending on the +#. plural form +#. +#. Note: most western languages will not need to change this +#: ../DistUpgrade/DistUpgradeView.py:82 +#, python-format +msgid "%(str_days)s %(str_hours)s" +msgstr "%(str_days)s %(str_hours)s" + +#. TRANSLATORS: you can alter the ordering of the remaining time +#. information here if you shuffle %(str_hours)s %(str_minutes)s +#. around. Make sure to keep all '$(str_*)s' in the translated string +#. and do NOT change anything appart from the ordering. +#. +#. %(str_hours)s will be either "1 hour" or "2 hours" depending on the +#. plural form +#. +#. Note: most western languages will not need to change this +#: ../DistUpgrade/DistUpgradeView.py:100 +#, python-format +msgid "%(str_hours)s %(str_minutes)s" +msgstr "%(str_hours)s %(str_minutes)s" + +#. 56 kbit +#. 1Mbit = 1024 kbit +#: ../DistUpgrade/DistUpgradeView.py:151 +#, python-format +msgid "" +"This download will take about %s with a 1Mbit DSL connection and about %s " +"with a 56k modem." +msgstr "" +"使用 1Mbit 的 DSL 连接下载需要大约 %s 秒的时间, 使用 56K 的调制解调器连接大" +"约需要 %s 秒时间。" + +#. if we have a estimated speed, use it +#: ../DistUpgrade/DistUpgradeView.py:155 +#, python-format +msgid "This download will take about %s with your connection. " +msgstr "根据您的连接速度,这次下载将要用大约 %s 的时间 " + +#. Declare these translatable strings from the .ui files here so that +#. xgettext picks them up. +#: ../DistUpgrade/DistUpgradeView.py:259 ../DistUpgrade/DistUpgrade.ui.h:21 +msgid "Preparing to upgrade" +msgstr "正在准备升级" + +#: ../DistUpgrade/DistUpgradeView.py:260 +msgid "Getting new software channels" +msgstr "获得新的软件源" + +#: ../DistUpgrade/DistUpgradeView.py:261 ../DistUpgrade/DistUpgrade.ui.h:23 +msgid "Getting new packages" +msgstr "获取新的软件包" + +#: ../DistUpgrade/DistUpgradeView.py:262 ../DistUpgrade/DistUpgrade.ui.h:26 +msgid "Installing the upgrades" +msgstr "正在安装升级" + +#: ../DistUpgrade/DistUpgradeView.py:263 ../DistUpgrade/DistUpgrade.ui.h:25 +msgid "Cleaning up" +msgstr "正在清理" + +#: ../DistUpgrade/DistUpgradeView.py:348 +#, python-format +msgid "" +"%(amount)d installed package is no longer supported by Canonical. You can " +"still get support from the community." +msgid_plural "" +"%(amount)d installed packages are no longer supported by Canonical. You can " +"still get support from the community." +msgstr[0] "" +"%(amount)d 个已安装的软件包不再被 Canonical 支持。您仍然可以获得社区支持。" + +#. FIXME: make those two separate lines to make it clear +#. that the "%" applies to the result of ngettext +#: ../DistUpgrade/DistUpgradeView.py:357 +#, python-format +msgid "%d package is going to be removed." +msgid_plural "%d packages are going to be removed." +msgstr[0] "将删除 %d 个软件包。" + +#: ../DistUpgrade/DistUpgradeView.py:362 +#, python-format +msgid "%d new package is going to be installed." +msgid_plural "%d new packages are going to be installed." +msgstr[0] "将安装 %d 个新的软件包。" + +#: ../DistUpgrade/DistUpgradeView.py:368 +#, python-format +msgid "%d package is going to be upgraded." +msgid_plural "%d packages are going to be upgraded." +msgstr[0] "将升级 %d 个软件包。" + +#: ../DistUpgrade/DistUpgradeView.py:373 +#, python-format +msgid "" +"\n" +"\n" +"You have to download a total of %s. " +msgstr "" +"\n" +"\n" +"您共需下载 %s。 " + +#: ../DistUpgrade/DistUpgradeView.py:378 +msgid "" +"Installing the upgrade can take several hours. Once the download has " +"finished, the process cannot be canceled." +msgstr "安装升级可能会耗费几小时的时间。一旦下载完毕就不能取消该进程。" + +#: ../DistUpgrade/DistUpgradeView.py:382 +msgid "" +"Fetching and installing the upgrade can take several hours. Once the " +"download has finished, the process cannot be canceled." +msgstr "" +"升级文件的获取和安装可能会耗费几小时的时间。一旦下载完毕就不能取消该进程。" + +#: ../DistUpgrade/DistUpgradeView.py:387 +msgid "Removing the packages can take several hours. " +msgstr "包的删除可能会耗费几小时的时间。 " + +#. FIXME: this should go into DistUpgradeController +#: ../DistUpgrade/DistUpgradeView.py:392 ../UpdateManager/UpdateManager.py:676 +msgid "The software on this computer is up to date." +msgstr "此计算机软件是最新的" + +#: ../DistUpgrade/DistUpgradeView.py:393 +msgid "" +"There are no upgrades available for your system. The upgrade will now be " +"canceled." +msgstr "你的系统没有可用升级。升级被取消。" + +#: ../DistUpgrade/DistUpgradeView.py:406 +msgid "Reboot required" +msgstr "需要重启" + +#: ../DistUpgrade/DistUpgradeView.py:407 +msgid "" +"The upgrade is finished and a reboot is required. Do you want to do this now?" +msgstr "升级已经完成并需要重启。您要现在重启么?" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:72 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:72 +#, python-format +msgid "authenticate '%(file)s' against '%(signature)s' " +msgstr "使用 '%(signature)s' 对 '%(file)s' 进行验证 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:131 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:131 +#, python-format +msgid "extracting '%s'" +msgstr "正在提取 '%s'" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:151 +#: ../DistUpgrade/DistUpgradeFetcherCore.py:152 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:151 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:152 +msgid "Could not run the upgrade tool" +msgstr "不能运行升级工具" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:152 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:152 +msgid "" +"This is most likely a bug in the upgrade tool. Please report it as a bug " +"using the command 'ubuntu-bug update-manager'." +msgstr "" +"这可能是升级工具的一个 bug,请使用命令‘ubuntu-bug update-manager’来报告这个 " +"bug。" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:227 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:227 +msgid "Upgrade tool signature" +msgstr "升级工具签名" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:234 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:234 +msgid "Upgrade tool" +msgstr "升级工具" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:268 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:268 +msgid "Failed to fetch" +msgstr "下载失败" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:269 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:269 +msgid "Fetching the upgrade failed. There may be a network problem. " +msgstr "获取升级信息失败。可能网络有问题。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:273 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:273 +msgid "Authentication failed" +msgstr "认证失败" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:274 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:274 +msgid "" +"Authenticating the upgrade failed. There may be a problem with the network " +"or with the server. " +msgstr "认证升级信息失败。可能是网络或服务器的问题。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:279 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:279 +msgid "Failed to extract" +msgstr "提取失败" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:280 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:280 +msgid "" +"Extracting the upgrade failed. There may be a problem with the network or " +"with the server. " +msgstr "提取升级信息失败。可能是网络或服务器的问题。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:285 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:285 +msgid "Verification failed" +msgstr "验证失败" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:286 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:286 +msgid "" +"Verifying the upgrade failed. There may be a problem with the network or " +"with the server. " +msgstr "验证升级程序失败。可能是网络或服务器的问题。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:300 +#: ../DistUpgrade/DistUpgradeFetcherCore.py:306 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:300 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:306 +msgid "Can not run the upgrade" +msgstr "不能运行升级" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:301 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:301 +msgid "" +"This usually is caused by a system where /tmp is mounted noexec. Please " +"remount without noexec and run the upgrade again." +msgstr "" +"可能的原因是系统的 /tmp 目录无可执行权限。请以可执行权限重新挂载该目录,重新" +"升级。" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:307 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:307 +#, python-format +msgid "The error message is '%s'." +msgstr "错误信息是“%s”。" + +#: ../DistUpgrade/DistUpgradeViewText.py:93 +msgid "" +"Please report this as a bug and include the files /var/log/dist-upgrade/main." +"log and /var/log/dist-upgrade/apt.log in your report. The upgrade has " +"aborted.\n" +"Your original sources.list was saved in /etc/apt/sources.list.distUpgrade." +msgstr "" +"请报告这个错误并在报告中包括文件 /var/log/dist-upgrade/main.log 和 /var/log/" +"dist-upgrade/apt.log 。升级已取消。\n" +"您的原始 sources.list 已保存在 /etc/apt/sources.list.distUpgrade 。" + +#: ../DistUpgrade/DistUpgradeViewText.py:117 +msgid "Aborting" +msgstr "中止" + +#: ../DistUpgrade/DistUpgradeViewText.py:122 +msgid "Demoted:\n" +msgstr "降级:\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:129 +msgid "To continue please press [ENTER]" +msgstr "按 [ENTER] 键以继续" + +#: ../DistUpgrade/DistUpgradeViewText.py:157 +#: ../DistUpgrade/DistUpgradeViewText.py:196 +#: ../DistUpgrade/DistUpgradeViewText.py:203 +msgid "Continue [yN] " +msgstr "继续 [yN] " + +#: ../DistUpgrade/DistUpgradeViewText.py:157 +#: ../DistUpgrade/DistUpgradeViewText.py:196 +msgid "Details [d]" +msgstr "详细信息[d]" + +#. TRANSLATORS: the "y" is "yes" +#. TRANSLATORS: first letter of a positive (yes) answer +#: ../DistUpgrade/DistUpgradeViewText.py:162 +#: ../DistUpgrade/DistUpgradeViewText.py:206 +msgid "y" +msgstr "y" + +#. TRANSLATORS: the "n" is "no" +#. TRANSLATORS: first letter of a negative (no) answer +#: ../DistUpgrade/DistUpgradeViewText.py:165 +#: ../DistUpgrade/DistUpgradeViewText.py:213 +msgid "n" +msgstr "n" + +#. TRANSLATORS: the "d" is "details" +#: ../DistUpgrade/DistUpgradeViewText.py:168 +msgid "d" +msgstr "详" + +#: ../DistUpgrade/DistUpgradeViewText.py:173 +#, python-format +msgid "No longer supported: %s\n" +msgstr "不再支持:%s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:178 +#, python-format +msgid "Remove: %s\n" +msgstr "移除:%s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:188 +#, python-format +msgid "Install: %s\n" +msgstr "安装:%s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:193 +#, python-format +msgid "Upgrade: %s\n" +msgstr "升级:%s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:210 +msgid "Continue [Yn] " +msgstr "继续 [Yn] " + +#: ../DistUpgrade/DistUpgradeViewText.py:231 +msgid "" +"To finish the upgrade, a restart is required.\n" +"If you select 'y' the system will be restarted." +msgstr "" +"为了完成升级,必须重新启动。\n" +"如果你选择“是”,系统将会重新启动。" + +#: ../DistUpgrade/DistUpgrade.ui.h:1 +msgid "_Cancel Upgrade" +msgstr "取消升级" + +#: ../DistUpgrade/DistUpgrade.ui.h:2 +msgid "_Resume Upgrade" +msgstr "恢复升级(_R)" + +#: ../DistUpgrade/DistUpgrade.ui.h:3 +msgid "" +"Cancel the running upgrade?\n" +"\n" +"The system could be in an unusable state if you cancel the upgrade. You are " +"strongly adviced to resume the upgrade." +msgstr "" +"取消正在运行的升级?\n" +"如果您取消升级,系统可能不稳定。强烈建议您继续升级。" + +#: ../DistUpgrade/DistUpgrade.ui.h:6 +msgid "_Start Upgrade" +msgstr "开始升级(_R)" + +#: ../DistUpgrade/DistUpgrade.ui.h:9 +msgid "_Replace" +msgstr "替换(_R)" + +#: ../DistUpgrade/DistUpgrade.ui.h:10 +msgid "Difference between the files" +msgstr "文件之间的差别" + +#: ../DistUpgrade/DistUpgrade.ui.h:11 +msgid "_Report Bug" +msgstr "报告 Bug(_R)" + +#: ../DistUpgrade/DistUpgrade.ui.h:12 +msgid "_Continue" +msgstr "继续(_C)" + +#: ../DistUpgrade/DistUpgrade.ui.h:13 +msgid "Start the upgrade?" +msgstr "开始升级?" + +#: ../DistUpgrade/DistUpgrade.ui.h:15 +msgid "" +"Restart the system to complete the upgrade\n" +"\n" +"Please save your work before continuing." +msgstr "" +"重启系统以完成升级\n" +"\n" +"请在继续前保存你的工作。" + +#: ../DistUpgrade/DistUpgrade.ui.h:18 +msgid "Distribution Upgrade" +msgstr "发行版升级" + +#: ../DistUpgrade/DistUpgrade.ui.h:19 +#, fuzzy +msgid "Upgrading Ubuntu to version 12.10" +msgstr "把 Ubuntu 升级到 11.10" + +#: ../DistUpgrade/DistUpgrade.ui.h:20 +msgid " " +msgstr " " + +#: ../DistUpgrade/DistUpgrade.ui.h:22 +msgid "Setting new software channels" +msgstr "设定新的软件源" + +#: ../DistUpgrade/DistUpgrade.ui.h:24 +msgid "Restarting the computer" +msgstr "系统正在重新启动" + +#: ../DistUpgrade/DistUpgrade.ui.h:27 +msgid "Terminal" +msgstr "终端" + +#: ../UpdateManager/backend/InstallBackendSynaptic.py:64 +msgid "Please wait, this can take some time." +msgstr "请稍候,这需要花一些时间。" + +#: ../UpdateManager/backend/InstallBackendSynaptic.py:66 +msgid "Update is complete" +msgstr "更新完成" + +#: ../UpdateManager/DistUpgradeFetcher.py:114 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:109 +msgid "Could not find the release notes" +msgstr "无法找到发行注记" + +#: ../UpdateManager/DistUpgradeFetcher.py:115 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:110 +msgid "The server may be overloaded. " +msgstr "服务器可能已过载。 " + +#: ../UpdateManager/DistUpgradeFetcher.py:125 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:114 +msgid "Could not download the release notes" +msgstr "无法下载发行说明" + +#: ../UpdateManager/DistUpgradeFetcher.py:126 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:115 +msgid "Please check your internet connection." +msgstr "请检查您的互联网连接。" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:68 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:91 +msgid "Upgrade" +msgstr "升级" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:95 +#: ../data/gtkbuilder/UpdateManager.ui.h:20 +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:2 +msgid "Release Notes" +msgstr "发行注记" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:134 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:148 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:150 +msgid "Downloading additional package files..." +msgstr "正在下载附加软件包..." + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:148 +#, python-format +msgid "File %s of %s at %sB/s" +msgstr "文件 %s/%s 速度: %sB/s" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:150 +#, python-format +msgid "File %s of %s" +msgstr "文件 %s/%s" + +#: ../UpdateManager/ChangelogViewer.py:75 +msgid "Open Link in Browser" +msgstr "在浏览器中打开链接" + +#: ../UpdateManager/ChangelogViewer.py:78 +msgid "Copy Link to Clipboard" +msgstr "复制链接到剪贴板" + +#: ../UpdateManager/GtkProgress.py:162 +#, python-format +msgid "Downloading file %(current)li of %(total)li with %(speed)s/s" +msgstr "正在下载文件 %(current)li/%(total)li 速度 %(speed)s/s" + +#: ../UpdateManager/GtkProgress.py:167 +#, python-format +msgid "Downloading file %(current)li of %(total)li" +msgstr "正在下载文件 %(current)li/%(total)li" + +#: ../UpdateManager/UpdateManager.py:106 ../do-release-upgrade:100 +msgid "Your Ubuntu release is not supported anymore." +msgstr "不再提供您的 Ubuntu 版本的支持。" + +#: ../UpdateManager/UpdateManager.py:107 +msgid "" +"You will not get any further security fixes or critical updates. Please " +"upgrade to a later version of Ubuntu." +msgstr "不会得到进一步的安全修补程序或关键更新。请升级到较新的 Ubuntu 版本。" + +#: ../UpdateManager/UpdateManager.py:115 +msgid "Upgrade information" +msgstr "升级信息" + +#: ../UpdateManager/UpdateManager.py:233 +#: ../UpdateManagerText/UpdateManagerText.py:35 +msgid "Install" +msgstr "安装" + +#: ../UpdateManager/UpdateManager.py:235 +msgid "Name" +msgstr "名称" + +#. upload_archive = version_match.group(2).strip() +#: ../UpdateManager/UpdateManager.py:395 +#, python-format +msgid "Version %s: \n" +msgstr "版本 %s: \n" + +#: ../UpdateManager/UpdateManager.py:453 +msgid "" +"No network connection detected, you can not download changelog information." +msgstr "未检测到网络连接,您无法下载更新日志。" + +#: ../UpdateManager/UpdateManager.py:463 +msgid "Downloading list of changes..." +msgstr "正在下载更新列表..." + +#: ../UpdateManager/UpdateManager.py:507 +msgid "_Deselect All" +msgstr "全部不选(_D)" + +#: ../UpdateManager/UpdateManager.py:513 +msgid "Select _All" +msgstr "选择全部(_A)" + +#: ../UpdateManager/UpdateManager.py:572 +#, python-format +msgid "%s will be downloaded." +msgstr "将会下载 %s。" + +#: ../UpdateManager/UpdateManager.py:584 +#, fuzzy +msgid "The update has already been downloaded." +msgid_plural "The updates have already been downloaded." +msgstr[0] "该更新已下载完毕但未安装。" + +#: ../UpdateManager/UpdateManager.py:589 +msgid "There are no updates to install." +msgstr "" + +#: ../UpdateManager/UpdateManager.py:598 +msgid "Unknown download size." +msgstr "未知下载大小。" + +#: ../UpdateManager/UpdateManager.py:624 +msgid "" +"It is unknown when the package information was updated last. Please click " +"the 'Check' button to update the information." +msgstr "软件包信息上次更新的时间未知。请点击“检查”按钮更新软件包信息。" + +#: ../UpdateManager/UpdateManager.py:630 +#, python-format +msgid "" +"The package information was last updated %(days_ago)s days ago.\n" +"Press the 'Check' button below to check for new software updates." +msgstr "" +"%(days_ago)s 天前最后一次更新软件包信息。\n" +"要检查软件更新,点击下面的“检查”按钮。" + +#: ../UpdateManager/UpdateManager.py:635 +#, python-format +msgid "The package information was last updated %(days_ago)s day ago." +msgid_plural "The package information was last updated %(days_ago)s days ago." +msgstr[0] "软件包信息上次是在 %(days_ago)s 天前更新的。" + +#: ../UpdateManager/UpdateManager.py:639 +#, python-format +msgid "The package information was last updated %(hours_ago)s hour ago." +msgid_plural "" +"The package information was last updated %(hours_ago)s hours ago." +msgstr[0] "软件包信息上次是在 %(hours_ago)s 小时前更新的。" + +#. TRANSLATORS: only in plural form, as %s minutes ago is one of 15, 30, 45 minutes ago +#: ../UpdateManager/UpdateManager.py:644 ../UpdateManager/UpdateManager.py:646 +#: ../UpdateManager/UpdateManager.py:648 +#, python-format +msgid "The package information was last updated about %s minutes ago." +msgstr "该软件包信息已在 %s 分钟之前更新。" + +#: ../UpdateManager/UpdateManager.py:650 +msgid "The package information was just updated." +msgstr "该软件包信息刚刚更新。" + +#: ../UpdateManager/UpdateManager.py:689 +msgid "Software updates may be available for your computer." +msgstr "您的计算机有可用的软件更新。" + +#: ../UpdateManager/UpdateManager.py:697 +#, python-format +msgid "" +"Updated software has been issued since %s was released. Do you want to " +"install it now?" +msgstr "" + +#: ../UpdateManager/UpdateManager.py:700 +msgid "" +"Updated software is available for this computer. Do you want to install it " +"now?" +msgstr "" + +#: ../UpdateManager/UpdateManager.py:758 +#, python-format +msgid "" +"The upgrade needs a total of %s free space on disk '%s'. Please free at " +"least an additional %s of disk space on '%s'. Empty your trash and remove " +"temporary packages of former installations using 'sudo apt-get clean'." +msgstr "" +"这个更新需要花去 %s 磁盘上总计 %s 的空间。请在 %s 磁盘上留出 %s 空间。清空您" +"的回收站和临时文件,用“sudo apt-get clean”清理以前的安装文件。" + +#: ../UpdateManager/UpdateManager.py:783 +msgid "" +"The computer needs to restart to finish installing updates. Please save your " +"work before continuing." +msgstr "计算机需要重新启动来完成更新。请在继续操作前保存好您的工作。" + +#: ../UpdateManager/UpdateManager.py:847 +msgid "Reading package information" +msgstr "读取软件包信息" + +#: ../UpdateManager/UpdateManager.py:862 +msgid "Connecting..." +msgstr "正在连接..." + +#: ../UpdateManager/UpdateManager.py:879 +msgid "You may not be able to check for updates or download new updates." +msgstr "您可能无法检查更新或下载新的更新。" + +#: ../UpdateManager/UpdateManager.py:1002 +msgid "Could not initialize the package information" +msgstr "无法初始化软件包信息" + +msgid "An unresolvable problem occurred while initializing the package." +msgstr "初始化包信息时遇到无法解决的问题。" + +#: ../UpdateManager/UpdateManager.py:1003 +msgid "" +"An unresolvable problem occurred while initializing the package " +"information.\n" +"\n" +"Please report this bug against the 'kylin-system-updater' package and include the " +"following error message:\n" +msgstr "" +"初始化包信息时遇到无法解决的问题。\n" +"\n" +"请汇报这个“kylin-system-updater”软件包的错误,并且将如下信息包含在报告中:\n" + +#: ../UpdateManager/UpdateManager.py:1032 +msgid "" +"An unresolvable problem occurred while calculating the upgrade.\n" +"\n" +"Please report this bug against the 'kylin-system-updater' package and include the " +"following error message:" +msgstr "" +"在计算升级时遇到一个无法解决的问题。\n" +"\n" +"请汇报这个有关“update-manager”软件包的错误,并且将下列错误信息包含在错误报告" +"中:" + +#: ../UpdateManager/UpdateManager.py:1056 +msgid " (New install)" +msgstr " (新安装)" + +#. TRANSLATORS: the b stands for Bytes +#: ../UpdateManager/UpdateManager.py:1063 +#, python-format +msgid "(Size: %s)" +msgstr "(大小:%s)" + +#: ../UpdateManager/UpdateManager.py:1067 +#, python-format +msgid "From version %(old_version)s to %(new_version)s" +msgstr "从版本 %(old_version)s 到 %(new_version)s" + +#: ../UpdateManager/UpdateManager.py:1071 +#, python-format +msgid "Version %s" +msgstr "版本 %s" + +#: ../UpdateManager/UpdateManager.py:1104 ../do-release-upgrade:112 +msgid "Release upgrade not possible right now" +msgstr "版本升级目前不可用" + +#: ../UpdateManager/UpdateManager.py:1105 ../do-release-upgrade:113 +#, c-format, python-format +msgid "" +"The release upgrade can not be performed currently, please try again later. " +"The server reported: '%s'" +msgstr "发行版目前无法更新,请再试一次。服务器报告:'%s'" + +#: ../UpdateManager/UpdateManager.py:1107 ../check-new-release-gtk:126 +msgid "Downloading the release upgrade tool" +msgstr "正在下载发布升级工具" + +#: ../UpdateManager/UpdateManager.py:1114 +#, python-format +msgid "New Ubuntu release '%s' is available" +msgstr "新的 Ubuntu 发行版 %s 可用" + +#. we assert a clean cache +#: ../UpdateManager/UpdateManager.py:1153 +msgid "Software index is broken" +msgstr "软件索引已经损坏" + +#: ../UpdateManager/UpdateManager.py:1154 +msgid "" +"It is impossible to install or remove any software. Please use the package " +"manager \"Synaptic\" or run \"sudo apt-get install -f\" in a terminal to fix " +"this issue at first." +msgstr "" +"无法安装或删除任何软件。请在终端运行 \"sudo apt-get " +"install -f\" 来修正这个问题。" + +#: ../UpdateManager/UnitySupport.py:57 +msgid "Check for Updates" +msgstr "检查更新" + +#: ../UpdateManager/UnitySupport.py:66 +msgid "Install All Available Updates" +msgstr "安装全部可利用的更新" + +#: ../UpdateManagerText/UpdateManagerText.py:34 +msgid "Cancel" +msgstr "取消" + +#: ../UpdateManagerText/UpdateManagerText.py:37 +msgid "Changelog" +msgstr "变更日志" + +#: ../UpdateManagerText/UpdateManagerText.py:40 +msgid "Updates" +msgstr "更新" + +#: ../UpdateManagerText/UpdateManagerText.py:53 +msgid "Building Updates List" +msgstr "正在建立更新列表" + +#: ../UpdateManagerText/UpdateManagerText.py:56 +msgid "" +"\n" +"A normal upgrade can not be calculated, please run: \n" +" sudo apt-get dist-upgrade\n" +"\n" +"\n" +"This can be caused by:\n" +" * A previous upgrade which didn't complete\n" +" * Problems with some of the installed software\n" +" * Unofficial software packages not provided by Ubuntu\n" +" * Normal changes of a pre-release version of Ubuntu" +msgstr "" +"\n" +"一个普通升级无法被计算,请运行: \n" +" sudo apt-get dist-upgrade\n" +"\n" +"\n" +"这可能是因为:\n" +" * 先前的升级尚未完成\n" +" * 已安装软件引起的问题\n" +" * 不是由 Ubuntu 提供的非官方软件包\n" +" * Ubuntu 预发行版本的普通更改" + +#: ../UpdateManagerText/UpdateManagerText.py:125 +msgid "Downloading changelog" +msgstr "正在下载变更日志" + +#: ../UpdateManager/Core/MyCache.py:147 +#, python-format +msgid "Other updates (%s)" +msgstr "其它更新 (%s)" + +#: ../UpdateManager/Core/MyCache.py:325 +msgid "This update does not come from a source that supports changelogs." +msgstr "" + +#: ../UpdateManager/Core/MyCache.py:331 ../UpdateManager/Core/MyCache.py:359 +msgid "" +"Failed to download the list of changes. \n" +"Please check your Internet connection." +msgstr "" + +#: ../UpdateManager/Core/MyCache.py:338 +#, python-format +msgid "" +"Changes for the versions:\n" +"Installed version: %s\n" +"Available version: %s\n" +"\n" +msgstr "" +"版本变更:\n" +"已安装的版本:%s\n" +"可用版本:%s\n" +"\n" + +#: ../UpdateManager/Core/MyCache.py:348 +#, python-format +msgid "" +"The changelog does not contain any relevant changes.\n" +"\n" +"Please use http://launchpad.net/ubuntu/+source/%s/%s/+changelog\n" +"until the changes become available or try again later." +msgstr "" + +#: ../UpdateManager/Core/MyCache.py:353 +#, python-format +msgid "" +"The list of changes is not available yet.\n" +"\n" +"Please use http://launchpad.net/ubuntu/+source/%s/%s/+changelog\n" +"until the changes become available or try again later." +msgstr "" + +#: ../UpdateManager/Core/UpdateList.py:51 +msgid "Failed to detect distribution" +msgstr "检测发行版失败" + +#: ../UpdateManager/Core/UpdateList.py:52 +#, python-format +msgid "A error '%s' occurred while checking what system you are using." +msgstr "检查您正在使用哪种系统的时候发生一个错误 “%s”。" + +#: ../UpdateManager/Core/UpdateList.py:63 +msgid "Important security updates" +msgstr "重要安全更新" + +#: ../UpdateManager/Core/UpdateList.py:64 +msgid "Recommended updates" +msgstr "推荐更新" + +#: ../UpdateManager/Core/UpdateList.py:65 +msgid "Proposed updates" +msgstr "提前释放的更新" + +#: ../UpdateManager/Core/UpdateList.py:66 +msgid "Backports" +msgstr "Backports" + +#: ../UpdateManager/Core/UpdateList.py:67 +msgid "Distribution updates" +msgstr "发行版升级" + +#: ../UpdateManager/Core/UpdateList.py:72 +msgid "Other updates" +msgstr "其它更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:1 +#, fuzzy +msgid "Starting Software Updater" +msgstr "运行升级管理器" + +#: ../data/gtkbuilder/UpdateManager.ui.h:2 +msgid "" +"Software updates correct errors, eliminate security vulnerabilities and " +"provide new features." +msgstr "软件更新可以为您修复错误,消除安全漏洞及提供新的特性" + +#: ../data/gtkbuilder/UpdateManager.ui.h:3 +msgid "_Partial Upgrade" +msgstr "部分升级(_P)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:4 +msgid "Not all updates can be installed" +msgstr "没有更新可供安装" + +#: ../data/gtkbuilder/UpdateManager.ui.h:5 +msgid "" +"Run a partial upgrade, to install as many updates as possible. \n" +"\n" +"This can be caused by:\n" +" * A previous upgrade which didn't complete\n" +" * Problems with some of the installed software\n" +" * Unofficial software packages not provided by Ubuntu\n" +" * Normal changes of a pre-release version of Ubuntu" +msgstr "" +"执行一次部分升级,安装尽可能多的更新。 \n" +"\n" +"这可能是下面的情况引起的:\n" +" * 前一次升级没有完成\n" +" * 一些已经安装的软件的问题\n" +" *Ubuntu 不支持的非官方软件包\n" +" *Ubuntu 预发布版本的正常变动" + +#: ../data/gtkbuilder/UpdateManager.ui.h:12 +msgid "Chec_k" +msgstr "检查(_K)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:13 +msgid "" +"You must check for updates manually\n" +"\n" +"Your system does not check for updates automatically. You can configure this " +"behavior in Software Sources on the Updates tab." +msgstr "" +"您必须手动检测升级\n" +"\n" +"您的系统未自动检测升级,可以通过编辑软件源网络升级中更改。" + +#: ../data/gtkbuilder/UpdateManager.ui.h:16 +msgid "_Hide this information in the future" +msgstr "不再显示该信息(_H)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:17 +msgid "Co_ntinue" +msgstr "继续(_N)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:18 +msgid "Running on battery" +msgstr "电池供电" + +#: ../data/gtkbuilder/UpdateManager.ui.h:19 +msgid "Your system is running on battery. Are you sure you want to continue?" +msgstr "您的系统正在使用电池。 您确定要继续吗?" + +#: ../data/gtkbuilder/UpdateManager.ui.h:21 +msgid "_Upgrade" +msgstr "升级(_U)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:22 +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:8 +msgid "Show progress of individual files" +msgstr "显示单个文件进度" + +#: ../data/gtkbuilder/UpdateManager.ui.h:23 +#: ../data/update-manager.desktop.in.h:1 +#, fuzzy +msgid "Software Updater" +msgstr "软件更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:24 +#, fuzzy +msgid "Starting Software Updater" +msgstr "软件更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:25 +msgid "U_pgrade" +msgstr "升级(_P)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:27 +msgid "updates" +msgstr "更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:28 +msgid "Changes" +msgstr "变更" + +#: ../data/gtkbuilder/UpdateManager.ui.h:29 +msgid "Description" +msgstr "描述" + +#: ../data/gtkbuilder/UpdateManager.ui.h:30 +#, fuzzy +msgid "Details of updates" +msgstr "升级描述" + +#: ../data/gtkbuilder/UpdateManager.ui.h:31 +msgid "" +"You are connected via roaming and may be charged for the data consumed by " +"this update." +msgstr "您正在使用漫游连接,更新可能会耗费很多流量。" + +#: ../data/gtkbuilder/UpdateManager.ui.h:32 +msgid "" +"You may want to wait until you’re not using a mobile broadband connection." +msgstr "" + +#: ../data/gtkbuilder/UpdateManager.ui.h:33 +msgid "It’s safer to connect the computer to AC power before updating." +msgstr "在更新前最好将计算机连接电源。" + +#: ../data/gtkbuilder/UpdateManager.ui.h:34 +msgid "_Settings..." +msgstr "设置(_S)..." + +#: ../data/gtkbuilder/UpdateManager.ui.h:35 +#, fuzzy +msgid "_Install Now" +msgstr "安装" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:1 +msgid "A new version of Ubuntu is available. Would you like to upgrade?" +msgstr "有新版本的 Ubuntu 可用。您想升级吗?" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:3 +msgid "Don't Upgrade" +msgstr "不升级" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:4 +msgid "Ask Me Later" +msgstr "稍后询问" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:5 +msgid "Yes, Upgrade Now" +msgstr "是的,现在就升级" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:6 +msgid "You have declined to upgrade to the new Ubuntu" +msgstr "您已经拒绝升级到新版本的 Ubuntu" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:7 +#, fuzzy +msgid "" +"You can upgrade at a later time by opening Software Updater and click on " +"\"Upgrade\"." +msgstr "如果您希望以后再升级,可以打开更新管理器,然后点击“升级”。" + +#: ../data/update-manager.desktop.in.h:2 +msgid "Software Updates" +msgstr "软件更新" + +#: ../data/update-manager.desktop.in.h:3 +msgid "Show and install available updates" +msgstr "显示并安装可用更新" + +#: ../update-manager:66 ../update-manager-text:55 ../do-release-upgrade:51 +msgid "Show version and exit" +msgstr "显示版本并退出" + +#: ../update-manager:69 +msgid "Directory that contains the data files" +msgstr "包含数据文件的文件夹" + +#: ../update-manager:72 +msgid "Check if a new Ubuntu release is available" +msgstr "检查是否有新的 Ubuntu 发行版可用" + +#: ../update-manager:75 ../do-release-upgrade:54 ../check-new-release-gtk:186 +msgid "Check if upgrading to the latest devel release is possible" +msgstr "验证是否能够升级到最新版本" + +#: ../update-manager:79 +msgid "Upgrade using the latest proposed version of the release upgrader" +msgstr "使用最新 proposed 版本的升级器升级" + +#: ../update-manager:86 +msgid "Do not focus on map when starting" +msgstr "开始时焦点不要定位在图上" + +#: ../update-manager:89 +msgid "Try to run a dist-upgrade" +msgstr "尝试进行版本升级" + +#: ../update-manager:92 +msgid "Do not check for updates when starting" +msgstr "启动时不检查更新" + +#: ../update-manager:96 ../do-release-upgrade:70 +msgid "Test upgrade with a sandbox aufs overlay" +msgstr "进行带沙盒 aufs 隔离层的测试升级" + +#: ../update-manager:116 +msgid "Running partial upgrade" +msgstr "运行部分升级" + +msgid " Will be deleted Due to Upgrade " +msgstr " 将要被删除,由于升级 " + +msgid " Will be deleted Due to Install or Upgrade " +msgstr " 将要被删除,由于安装 " + +#: ../update-manager-text:59 +msgid "Show description of the package instead of the changelog" +msgstr "显示包的说明而不是变更日志" + +#: ../do-release-upgrade:58 ../check-new-release-gtk:190 +msgid "" +"Try upgrading to the latest release using the upgrader from $distro-proposed" +msgstr "尝试通过 $distro-proposed 更新到最新版本。" + +#: ../do-release-upgrade:62 +msgid "" +"Run in a special upgrade mode.\n" +"Currently 'desktop' for regular upgrades of a desktop system and 'server' " +"for server systems are supported." +msgstr "" +"以特定模式升级。\n" +"目前支持:用“桌面”为桌面系统,“服务器”为服务器系统升级。" + +#: ../do-release-upgrade:68 +msgid "Run the specified frontend" +msgstr "运行指定的前端" + +#: ../do-release-upgrade:73 +msgid "" +"Check only if a new distribution release is available and report the result " +"via the exit code" +msgstr "仅在有新的发行版发布时检查,并通过退出码(exit code)报告结果" + +#: ../do-release-upgrade:87 +msgid "Checking for a new Ubuntu release" +msgstr "正在检查新版 Ubuntu" + +#: ../do-release-upgrade:101 +msgid "" +"For upgrade information, please visit:\n" +"%(url)s\n" +msgstr "" +"要获得关于升级的信息,请访问:\n" +"%(url)s\n" + +#: ../do-release-upgrade:107 +msgid "No new release found" +msgstr "未发现新版本" + +#: ../do-release-upgrade:119 +#, c-format +msgid "New release '%s' available." +msgstr "有新版本“%s”可供使用" + +#: ../do-release-upgrade:120 +msgid "Run 'do-release-upgrade' to upgrade to it." +msgstr "运行“do-release-upgrade”来升级到新版本。" + +#: ../check-new-release-gtk:101 +msgid "Ubuntu %(version)s Upgrade Available" +msgstr "Ubuntu %(version)s 升级可用" + +#: ../check-new-release-gtk:143 +#, c-format +msgid "You have declined the upgrade to Ubuntu %s" +msgstr "您已经拒绝升级到 Ubuntu %s" + +#: ../check-new-release-gtk:196 +msgid "Add debug output" +msgstr "添加调试输出" + +#: ../ubuntu-support-status:91 +msgid "Show unsupported packages on this machine" +msgstr "显示此计算机中不受支持的包" + +#: ../ubuntu-support-status:94 +msgid "Show supported packages on this machine" +msgstr "显示此计算机中受支持的包" + +#: ../ubuntu-support-status:97 +msgid "Show all packages with their status" +msgstr "显示所有包及其状态" + +#: ../ubuntu-support-status:100 +msgid "Show all packages in a list" +msgstr "列出所有包" + +#: ../ubuntu-support-status:142 +#, c-format +msgid "Support status summary of '%s':" +msgstr "'%s' 的支持状态摘要:" + +#: ../ubuntu-support-status:145 +msgid "You have %(num)s packages (%(percent).1f%%) supported until %(time)s" +msgstr "有 %(num)s 个包(%(percent).1f%%)的支持截止时间是 %(time)s" + +#: ../ubuntu-support-status:151 +msgid "" +"You have %(num)s packages (%(percent).1f%%) that can not/no-longer be " +"downloaded" +msgstr "有 %(num)s 个包(%(percent).1f%%) (已)不能不能下载" + +#: ../ubuntu-support-status:154 +msgid "You have %(num)s packages (%(percent).1f%%) that are unsupported" +msgstr "有 %(num)s 个包(%(percent).1f%%)不受支持" + +#: ../ubuntu-support-status:162 +msgid "" +"Run with --show-unsupported, --show-supported or --show-all to see more " +"details" +msgstr "" +"通过 --show-unsupported、--show-supported 或 --show-all 运行以查看更多详细信" +"息" + +#: ../ubuntu-support-status:166 +msgid "No longer downloadable:" +msgstr "已不能下载:" + +#: ../ubuntu-support-status:169 +msgid "Unsupported: " +msgstr "不受支持: " + +#: ../ubuntu-support-status:174 +#, c-format +msgid "Supported until %s:" +msgstr "支持截止时间 %s:" + +#: ../ubuntu-support-status:183 +msgid "Unsupported" +msgstr "不受支持" + +#. Why do we use %s here instead of $strings or {} format placeholders? +#. It's because we don't want to break existing translations. +#: ../janitor/plugincore/exceptions.py:42 +#, python-format +msgid "Unimplemented method: %s" +msgstr "尚未实现的方法:%s" + +#: ../janitor/plugincore/core/file_cruft.py:41 +msgid "A file on disk" +msgstr "磁盘上的文件" + +#: ../janitor/plugincore/core/missing_package_cruft.py:39 +msgid "Install missing package." +msgstr "安装缺失的软件包。" + +#. 2012-06-08 BAW: i18n string; don't use {} or PEP 292. +#: ../janitor/plugincore/core/missing_package_cruft.py:49 +#, python-format +msgid "Package %s should be installed." +msgstr "包 %s 应当被安装。" + +#: ../janitor/plugincore/core/package_cruft.py:49 +msgid ".deb package" +msgstr ".deb 软件包" + +#: ../janitor/plugincore/plugins/langpack_manual_plugin.py:46 +#, python-format +msgid "%s needs to be marked as manually installed." +msgstr "%s 需要标记为手动安装。" + +#: ../janitor/plugincore/plugins/kdelibs4to5_plugin.py:49 +msgid "" +"When upgrading, if kdelibs4-dev is installed, kdelibs5-dev needs to be " +"installed. See bugs.launchpad.net, bug #279621 for details." +msgstr "" +"在升级时, 如果已经安装了 kdelibs4-dev, 那么需要安装 kdelibs5-dev。参阅 " +"bugs.launchpad.net 上的 bug #279621 来了解详情。" + +#: ../janitor/plugincore/plugins/dpkg_status_plugin.py:44 +#, python-format +msgid "%i obsolete entries in the status file" +msgstr "状态文件中包含 %i 项过期条目" + +#: ../janitor/plugincore/plugins/dpkg_status_plugin.py:47 +msgid "Obsolete entries in dpkg status" +msgstr "dpkg 状态中的过期条目" + +#. pragma: no cover +#: ../janitor/plugincore/plugins/dpkg_status_plugin.py:50 +msgid "Obsolete dpkg status entries" +msgstr "过期的 dpkg 状态条目" + +#: ../janitor/plugincore/plugins/remove_lilo_plugin.py:42 +msgid "Remove lilo since grub is also installed.(See bug #314004 for details.)" +msgstr "由于 grub 已安装,移除 lilo。(详情参见 bug #314004 。)" + +msgid "" +"After your package information was updated the essential package '%s' can " +"not be found anymore so a bug reporting process is being started." +msgstr "" +"更新包信息后就已找不到基本包(essential) '%s',因此即将启动错误报告进程。" + +msgid "Upgrading Ubuntu to version 12.04" +msgstr "正在将 Ubuntu 升级到 12.04 版" + +msgid "%(count)s update has been selected." +msgid_plural "%(count)s updates have been selected." +msgstr[0] "已选择 %(count)s 个更新。" + +msgid "%(count_str)s %(download_str)s" +msgstr "%(count_str)s %(download_str)s" + +msgid "Welcome to Ubuntu" +msgstr "欢迎使用 Ubuntu" + +msgid "" +"These software updates have been issued since this version of Ubuntu was " +"released." +msgstr "自正式发布起,Ubuntu 已提供了下列软件更新。" + +msgid "Software updates are available for this computer." +msgstr "当前计算机有可用软件更新。" + +msgid "Update Manager" +msgstr "更新管理器" + +msgid "Starting Update Manager" +msgstr "正在启动更新管理器" + +msgid "You are connected via a wireless modem." +msgstr "您在使用无线调制解调器连接网络。" + +msgid "_Install Updates" +msgstr "安装更新(_I)" + +msgid "Checking for a new ubuntu release" +msgstr "检查新的 Ubuntu 发行版本" + +msgid "Your system is up-to-date" +msgstr "您的系统已为最新" + +msgid "" +"Fetching and installing the upgrade can take several hours. Once the " +"download has finished, the process cannot be cancelled." +msgstr "获取并安装升级可能会持续几个小时。一旦下载结束,进程将不能被取消。" + +msgid "" +"If you don't want to install them now, choose \"Update Manager\" from the " +"Administration menu later." +msgstr "如果您不想现在安装,稍后可以在“系统管理”菜单选择“更新管理器”进行安装。" + +msgid "" +"This upgrade is running in sandbox (test) mode. All changes are written to " +"'%s' and will be lost on the next reboot.\n" +"\n" +"*No* changes written to a systemdir from now until the next reboot are " +"permanent." +msgstr "" +"这个升级正运行在沙盒(测试)模式下。所有的变更都被写入“%s”并在下次重启时丢" +"失。\n" +"\n" +"从现在起直到下次重启,被写入 systemdir 的变更*不是*永久的。" + +msgid "Software updates are available for this computer" +msgstr "有适合当前系统的软件更新" + +msgid "There are no updates to install" +msgstr "没有可安装的更新" + +msgid "The update has already been downloaded, but not installed" +msgid_plural "The updates have already been downloaded, but not installed" +msgstr[0] "更新已经下载,但还没有安装。" + +msgid "" +"You will not get any further security fixes or critical updates. Please " +"upgrade to a later version of Ubuntu Linux." +msgstr "您不会再收到任何安全更新。请升级到更新版本的 Ubuntu。" + +msgid "" +"If you don't want to install them now, choose \"Update Manager\" from " +"Applications later." +msgstr "如果您不想现在安装,可以今后再从应用程序中打开“更新管理器”。" + +msgid "0 kB" +msgstr "0 kB" + +msgid "1 kB" +msgstr "1 kB" + +msgid "" +"The system was unable to get the prerequisites for the upgrade. The upgrade " +"will abort now and restore the original system state.\n" +"\n" +"Please report this as a bug using the command 'ubuntu-bug update-manager' in " +"a terminal and include the files in /var/log/dist-upgrade/ in the bug report." +msgstr "" +"系统无法获取升级所必需的文件。将放弃升级并恢复到原始系统状态。\n" +"\n" +"请在终端里输入命令‘ubuntu-bug update-manager’来报告这个 bug。在报告 bug 时附" +"上位于 /var/log/dist-upgrade/ 的文件。" + +msgid "" +"After your package information was updated the essential package '%s' can " +"not be found anymore.\n" +"This indicates a serious error, please report this bug using the command " +"'ubuntu-bug update-manager' in a terminal and include the files in /var/log/" +"dist-upgrade/ in the bug report." +msgstr "" +"在更新了您的软件包信息之后,系统找不到‘%s’这个必需的软件包。\n" +"这预示着一个严重错误。在终端里输入命令‘ubuntu-bug update-manager’来报告这个 " +"bug。在报告 bug 时附上位于 /var/log/dist-upgrade/ 的文件。" + +msgid "Your graphics hardware may not be fully supported in Ubuntu 11.04." +msgstr "您的显卡可能无法完整地被 Ubuntu 11.04 支持。" + +msgid "" +"The support in Ubuntu 11.04 for your intel graphics hardware is limited and " +"you may encounter problems after the upgrade. Do you want to continue with " +"the upgrade?" +msgstr "" +"Ubuntu 11.04 对您的 intel 显卡支持有限。您可能在升级之后会遇到问题,确定要继" +"续升级吗?" + +msgid "" +"\n" +"\n" +"Please report this bug using the command 'ubuntu-bug update-manager' in a " +"terminal and include the files in /var/log/dist-upgrade/ in the bug report.\n" +"%s" +msgstr "" +"\n" +"\n" +"请在终端里输入命令‘ubuntu-bug update-manager’来报告这个 bug。在报告 bug 时附" +"上位于 /var/log/dist-upgrade/ 的文件。\n" +"%s" + +msgid "" +"Preparing the system for the upgrade failed. Please report this using the " +"command 'ubuntu-bug update-manager' in a terminal and include the files in /" +"var/log/dist-upgrade/ in the bug report." +msgstr "" +"无法获取要升级的系统。请在终端里输入命令‘ubuntu-bug update-manager’来报告这" +"个 bug。在报告 bug 时附上位于 /var/log/dist-upgrade/ 的文件。" + +msgid "" +"Upgrading the repository information resulted in a invalid file. Please " +"report this as a bug using the command 'ubuntu-bug update-manager' in a " +"terminal." +msgstr "" +"升级目录信息得到一个非法的文件。请在终端里输入命令‘ubuntu-bug update-" +"manager’来报告这个 bug。" + +msgid "" +"These software updates have been issued since this version of Ubuntu was " +"released. If you don't want to install them now, choose \"Update Manager\" " +"from Applications later." +msgstr "" +"这些软件的更新在该版本的 Ubuntu 发行之后被证明有问题。如果您不想现在安装它" +"们,可以在稍后从系统应用“更新管理器”中选择安装。" + +msgid "" +"These software updates have been issued since this version of Ubuntu was " +"released. If you don't want to install them now, choose \"Update Manager\" " +"from the Administration Menu later." +msgstr "" +"这些软件的更新在该版本的 Ubuntu 发行之后被证明有问题。如果您不想现在安装它" +"们,可以在稍后从管理员菜单“更新管理器”中选择安装。" + +msgid "%.0f kB" +msgstr "%.0f kB" + +msgid "Unable to access the source management server" +msgstr "无法访问源管理服务器,请稍后再试" + +msgid "Check if your network requires authentication?" +msgstr "检查您的网络需要认证吗?" + +msgid "Check your source public key signature" +msgstr "检查您的源数字签名" + +msgid "update important list occur Exception" +msgstr "获取推送出现异常,请稍后再试" + +msgid "You need to be root to run this application" +msgstr "你需要root权限运行" + +msgid "There is an exception in the update package." +msgstr "更新包存在异常!" + +msgid "You request the removal of a system-essential package." +msgstr "您要求删除一个系统必要的软件包。" + +msgid "This update cannot detect the upgradeable package." +msgstr "本次更新无法检测到可升级的软件包。" + +msgid "read important list failed" +msgstr "无法读取推送升级列表,请稍后再试" + +msgid "Priority Upgrade Package being updated" +msgstr "正在更新分组配置" + +msgid "Exceptions of Priority Upgrade." +msgstr "优先升级异常" + +msgid "Due to the presence of deleted packages." +msgstr "由于存在删除的软件包" + +msgid "The system update configuration file is read abnormally, please check if the system update configuration file format is correct." +msgstr "读取系统更新配置文件异常,请检查系统更新配置文件格式是否正确。" + +msgid "Installation progress: " +msgstr "安装进度: " + +msgid "Installation successful, about to shut down" +msgstr "安装成功,即将关机" + +msgid "Installation failed, about to shut down" +msgstr "安装失败,即将关机" + +msgid "groups JSON ConfigPkgs install failed" +msgstr "无法安装分组配置文件" + +msgid "Installtion timeout to exit Due to inactivity" +msgstr "安装超时退出由于" + +msgid "Command execution error" +msgstr "命令执行报错" + +msgid "Unsupported architecture" +msgstr "架构不符合" + +msgid "Other Error" +msgstr "其他错误" + +msgid "dependency is not satisfied" +msgstr "依赖关系不满足" + +msgid "dependency is not satisfied will download" +msgstr "依赖关系不满足" + +msgid "Disk space is insufficient, please clean the disk and then upgrade" +msgstr "磁盘空间不足,请清理磁盘后进行升级更新" + +msgid "Network anomaly, can't check for updates!" +msgstr "网络异常,无法检查更新!" + +msgid "Check for update exceptions!" +msgstr "检查更新异常!" + +msgid "Check for update exceptions,fix system APT environment error." +msgstr "检查更新异常,修复系统APT环境出现错误。" + +msgid "The system APT environment is abnormal, please check the system APT environment." +msgstr "修复系统APT环境异常,请检查系统APT环境。" + +msgid "Priority upgrade status exception." +msgstr "优先升级状态异常。" + +msgid "Upgrade configuration acquisition exception." +msgstr "升级配置获取异常。" + +msgid "Please check your network connection and retry." +msgstr "请检查您的网络连接后再试。" + +msgid "Please check your source list and retry." +msgstr "请检查您的源列表后再试。" + +msgid "Checking network connection" +msgstr "检查网络连接中" + +msgid "Updating Source Template" +msgstr "更新源模板中" + +msgid "Update Manager upgrade is complete, please restart the setting panel before performing the system update." +msgstr "更新管理器升级完成,请重启设置-更新后再进行系统更新。" + +msgid "Uninstallation completed" +msgstr "卸载完成。" + +msgid "Package validation failed and installation was rejected." +msgstr "软件包验证失败,拒绝安装。" + +msgid "Other tasks are being updated and upgraded, please uninstall them later." +msgstr "其他任务正在更新升级中,请稍后再卸载。" + +#: ../aptdaemon/worker/aptworker.py:1353 +msgid "The following packages have unmet dependencies:" +msgstr "下列软件包未满足的依赖关系:" + +#: ../aptdaemon/worker/aptworker.py:1406 +msgid "but it is a virtual package" +msgstr "但是它是虚拟软件包" + +#: ../aptdaemon/worker/aptworker.py:1409 +msgid "but it is not installed" +msgstr "但是 %s 没有安装" + +#: ../aptdaemon/worker/aptworker.py:1411 +msgid "but it is not going to be installed" +msgstr "但是无法安装 %s" + +#. TRANSLATORS: %s is a version number +#: ../aptdaemon/worker/aptworker.py:1415 +#, python-format +msgid "but %s is installed" +msgstr "但是 %s 已经安装" + +#. TRANSLATORS: %s is a version number +#: ../aptdaemon/worker/aptworker.py:1419 +#, python-format +msgid "but %s is to be installed" +msgstr "但是将要安装 %s" + +#: ../SystemUpdater/Core/enums.py:763 +msgid "Kylin System Updater" +msgstr "麒麟更新器" + +#: ../SystemUpdater/Core/enums.py:609 +msgid "Kylin Installer" +msgstr "麒麟安装器" + +#: ../SystemUpdater/Core/enums.py:610 +msgid "Kylin Uninstaller" +msgstr "麒麟卸载器" + +#: ../SystemUpdater/Core/enums.py:611 +msgid "Kylin Background Upgrade" +msgstr "静默更新" + +#: ../SystemUpdater/Core/enums.py:612 +msgid "Kylin Software Center" +msgstr "软件商店" + +#: ../SystemUpdater/UpdateManagerDbus.py:355 +msgid " requires authentication to uninstall software packages." +msgstr "卸载软件包需要认证" + +#. 验签失败,提权 +#: ../SystemUpdater/UpdateManager.py:463 +msgid " requires authentication to install software packages." +msgstr "安装软件包需要认证" + +#: ../SystemUpdater/Core/utils.py:750 +msgid "Authentication success." +msgstr "认证成功" + +#: ../SystemUpdater/Core/utils.py:753 +msgid "Authentication failure." +msgstr "认证失败" + +#: ../SystemUpdater/Core/enums.py:101 +msgid "Deb format exception, read local deb file error." +msgstr "软件包格式异常,读取失败。" + +#: ../SystemUpdater/Core/enums.py:102 +msgid "Install deb error." +msgstr "安装软件包失败。" + +msgid "Upgrade System" +msgstr "全盘升级" + +msgid "kylin-unattended-upgrade" +msgstr "自动更新" + +msgid "Please check the system time and synchronize the system time before updating." +msgstr "请检查系统时间,同步系统时间后再进行更新。" + +msgid "The package is unsigned, refuses to install." +msgstr "软件包未签名,拒绝安装。" \ No newline at end of file diff --git a/backend/po/zh_HK.po b/backend/po/zh_HK.po new file mode 100644 index 0000000..8aaec3e --- /dev/null +++ b/backend/po/zh_HK.po @@ -0,0 +1,2696 @@ +msgid "" +msgstr "" +"Project-Id-Version: update-manager 0.41.1\n" +"Report-Msgid-Bugs-To: sebastian.heinlein@web.de\n" +"POT-Creation-Date: 2012-06-14 00:53+0100\n" +"PO-Revision-Date: 2012-04-13 00:58+0000\n" +"Last-Translator: Walter Cheuk \n" +"Language-Team: Chinese (Hong Kong) \n" +"Language: zh_HK\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Launchpad-Export-Date: 2012-04-17 13:41+0000\n" +"X-Generator: Launchpad (build 15099)\n" + +#. TRANSLATORS: download size of small updates, e.g. "250 kB" +#: ../DistUpgrade/utils.py:433 ../UpdateManager/Core/utils.py:433 +#, python-format +msgid "%(size).0f kB" +msgid_plural "%(size).0f kB" +msgstr[0] "" +msgstr[1] "" + +#. TRANSLATORS: download size of updates, e.g. "2.3 MB" +#: ../DistUpgrade/utils.py:436 ../UpdateManager/Core/utils.py:436 +#, python-format +msgid "%.1f MB" +msgstr "%.1f MB" + +#. TRANSLATORS: %s is a country +#: ../DistUpgrade/distro.py:206 ../DistUpgrade/distro.py:436 +#, python-format +msgid "Server for %s" +msgstr "%s伺服器" + +#. More than one server is used. Since we don't handle this case +#. in the user interface we set "custom servers" to true and +#. append a list of all used servers +#: ../DistUpgrade/distro.py:224 ../DistUpgrade/distro.py:230 +#: ../DistUpgrade/distro.py:246 +msgid "Main server" +msgstr "主要伺服器" + +#: ../DistUpgrade/distro.py:250 +msgid "Custom servers" +msgstr "自訂伺服器" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:142 +msgid "Could not calculate sources.list entry" +msgstr "無法推算 sources.list 項目" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:251 +msgid "" +"Unable to locate any package files, perhaps this is not a Ubuntu Disc or the " +"wrong architecture?" +msgstr "找不到套件檔案;也許這不是 Ubuntu 光碟,又或者處理器架構不對?" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:294 +msgid "Failed to add the CD" +msgstr "無法加入光碟" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:295 +#, python-format +msgid "" +"There was a error adding the CD, the upgrade will abort. Please report this " +"as a bug if this is a valid Ubuntu CD.\n" +"\n" +"The error message was:\n" +"'%s'" +msgstr "" +"加入光碟時發生錯誤,升級將終止。若確定光碟沒有問題,請將此匯報為臭蟲。\n" +"\n" +"錯誤訊息:\n" +"「%s」" + +#: ../DistUpgrade/DistUpgradeCache.py:151 +msgid "Remove package in bad state" +msgid_plural "Remove packages in bad state" +msgstr[0] "移除有問題套件" + +#: ../DistUpgrade/DistUpgradeCache.py:154 +#, python-format +msgid "" +"The package '%s' is in an inconsistent state and needs to be reinstalled, " +"but no archive can be found for it. Do you want to remove this package now " +"to continue?" +msgid_plural "" +"The packages '%s' are in an inconsistent state and need to be reinstalled, " +"but no archives can be found for them. Do you want to remove these packages " +"now to continue?" +msgstr[0] "" +"這個套件 '%s' 狀態不一致而需要重新安裝,但找不到它的存檔。您想移除這個套件然" +"後繼續嗎?" + +#. FIXME: not ideal error message, but we just reuse a +#. existing one here to avoid a new string +#: ../DistUpgrade/DistUpgradeCache.py:255 +msgid "The server may be overloaded" +msgstr "伺服器可能負載過大" + +#: ../DistUpgrade/DistUpgradeCache.py:368 +msgid "Broken packages" +msgstr "損毀的套件" + +#: ../DistUpgrade/DistUpgradeCache.py:369 +msgid "" +"Your system contains broken packages that couldn't be fixed with this " +"software. Please fix them first using synaptic or apt-get before proceeding." +msgstr "" +"系統有一些本軟件無法修正的損毀套件。請先以「Synaptic 套件管理員」或 apt-get " +"修正再繼續。" + +#. FIXME: change the text to something more useful +#: ../DistUpgrade/DistUpgradeCache.py:693 +#, python-format +msgid "" +"An unresolvable problem occurred while calculating the upgrade:\n" +"%s\n" +"\n" +" This can be caused by:\n" +" * Upgrading to a pre-release version of Ubuntu\n" +" * Running the current pre-release version of Ubuntu\n" +" * Unofficial software packages not provided by Ubuntu\n" +"\n" +msgstr "" +"為升級進行推算時有問題無法解決:\n" +"%s\n" +"\n" +" 原因可能是:\n" +" * 要升級至測試版 Ubuntu\n" +" * 正使用的是測試版 Ubuntu\n" +" * 非 Ubuntu 官方軟件套件的問題\n" +"\n" + +#: ../DistUpgrade/DistUpgradeCache.py:703 +msgid "This is most likely a transient problem, please try again later." +msgstr "這通常只是暫時性的問題。請稍候再試。" + +#: ../DistUpgrade/DistUpgradeCache.py:706 +msgid "" +"If none of this applies, then please report this bug using the command " +"'ubuntu-bug update-manager' in a terminal." +msgstr "" +"如全都不對,請在終端機內輸入指令「ubuntu-bug update-manager」回報錯誤。" + +#: ../DistUpgrade/DistUpgradeCache.py:711 +#: ../UpdateManager/UpdateManager.py:1031 +msgid "Could not calculate the upgrade" +msgstr "無法為升級進行推算" + +#: ../DistUpgrade/DistUpgradeCache.py:762 +msgid "Error authenticating some packages" +msgstr "認證一些套件時發生錯誤" + +#: ../DistUpgrade/DistUpgradeCache.py:763 +msgid "" +"It was not possible to authenticate some packages. This may be a transient " +"network problem. You may want to try again later. See below for a list of " +"unauthenticated packages." +msgstr "" +"系統無法認證一些套件。這可能是由於短暫的網絡問題;您可以稍後再試。以下為沒有" +"認證的套件。" + +#: ../DistUpgrade/DistUpgradeCache.py:783 +#, python-format +msgid "" +"The package '%s' is marked for removal but it is in the removal blacklist." +msgstr "套件「%s」標記作移除,但它在移除黑名單中。" + +#: ../DistUpgrade/DistUpgradeCache.py:787 +#, python-format +msgid "The essential package '%s' is marked for removal." +msgstr "必備套件「%s」被標記作移除。" + +#: ../DistUpgrade/DistUpgradeCache.py:796 +#, python-format +msgid "Trying to install blacklisted version '%s'" +msgstr "正在嘗試安裝黑名單版本「%s」" + +#: ../DistUpgrade/DistUpgradeCache.py:914 +#, python-format +msgid "Can't install '%s'" +msgstr "無法安裝「%s」" + +#: ../DistUpgrade/DistUpgradeCache.py:915 +msgid "" +"It was impossible to install a required package. Please report this as a bug " +"using 'ubuntu-bug update-manager' in a terminal." +msgstr "" +"無法安裝必要的套件。請在終端機內輸入「ubuntu-bug update-manager」回報錯誤。" + +#. FIXME: provide a list +#: ../DistUpgrade/DistUpgradeCache.py:926 +msgid "Can't guess meta-package" +msgstr "無法估計元套件 (meta-package)" + +#: ../DistUpgrade/DistUpgradeCache.py:927 +msgid "" +"Your system does not contain a ubuntu-desktop, kubuntu-desktop, xubuntu-" +"desktop or edubuntu-desktop package and it was not possible to detect which " +"version of Ubuntu you are running.\n" +" Please install one of the packages above first using synaptic or apt-get " +"before proceeding." +msgstr "" +"您的系統沒有安裝 ubuntu-desktop、kubuntu-desktop、xubuntu-desktop 或 " +"edubuntu-desktop 套件,因此無法偵測正在使用那個版本的 Ubuntu。\n" +" 請先以「Synaptic 套件管理員」或 apt-get 安裝上述其中一個套件再繼續。" + +#: ../DistUpgrade/DistUpgradeController.py:114 +msgid "Reading cache" +msgstr "正在讀取快取" + +#: ../DistUpgrade/DistUpgradeController.py:223 +msgid "Unable to get exclusive lock" +msgstr "無法取得(使用)排他鎖定" + +#: ../DistUpgrade/DistUpgradeController.py:224 +msgid "" +"This usually means that another package management application (like apt-get " +"or aptitude) already running. Please close that application first." +msgstr "" +"這通常表示有其他的套件管理員程式(如 apt-get 或 aptitude)正在執行。請先關閉" +"這些應用程式。" + +#: ../DistUpgrade/DistUpgradeController.py:257 +msgid "Upgrading over remote connection not supported" +msgstr "不支援透過遠端連線升級" + +#: ../DistUpgrade/DistUpgradeController.py:258 +msgid "" +"You are running the upgrade over a remote ssh connection with a frontend " +"that does not support this. Please try a text mode upgrade with 'do-release-" +"upgrade'.\n" +"\n" +"The upgrade will abort now. Please try without ssh." +msgstr "" +"您正在透過遠端 ssh 連線的前端介面執行更新,而此介面不支援。請試試純文字模式下" +"以 'do-release-upgrade' 進行更新。\n" +"\n" +"目前更新將會中止。請透過非 ssh 連線重試。" + +#: ../DistUpgrade/DistUpgradeController.py:272 +msgid "Continue running under SSH?" +msgstr "繼續執行於 SSH 中?" + +#: ../DistUpgrade/DistUpgradeController.py:273 +#, python-format +msgid "" +"This session appears to be running under ssh. It is not recommended to " +"perform a upgrade over ssh currently because in case of failure it is harder " +"to recover.\n" +"\n" +"If you continue, an additional ssh daemon will be started at port '%s'.\n" +"Do you want to continue?" +msgstr "" +"此連線階段似乎是在 ssh 下執行。目前不建議在 ssh 連線下進行升級,因為若發生失" +"敗將會較難以修復。\n" +"\n" +"若您繼續,一個額外的 ssh 背景程序將會啟動在 '%s' 連接埠。\n" +"您想要繼續嗎?" + +#: ../DistUpgrade/DistUpgradeController.py:287 +msgid "Starting additional sshd" +msgstr "啟動後備 sshd 中" + +#: ../DistUpgrade/DistUpgradeController.py:288 +#, python-format +msgid "" +"To make recovery in case of failure easier, an additional sshd will be " +"started on port '%s'. If anything goes wrong with the running ssh you can " +"still connect to the additional one.\n" +msgstr "" +"為在失敗時更易進行修復,一個後備 sshd 將在‘%s’埠被啟動。如果使用中的 ssh 有任" +"何問題,您仍可以連接後備的 sshd 。\n" + +#: ../DistUpgrade/DistUpgradeController.py:296 +#, python-format +msgid "" +"If you run a firewall, you may need to temporarily open this port. As this " +"is potentially dangerous it's not done automatically. You can open the port " +"with e.g.:\n" +"'%s'" +msgstr "" +"若有執行防火牆,可能需要暫時開啟此連接埠。由於此動作有潛在危險,系統不會自動" +"執行。可以這樣開啟連接埠:\n" +"「%s」" + +#: ../DistUpgrade/DistUpgradeController.py:368 +#: ../DistUpgrade/DistUpgradeController.py:413 +msgid "Can not upgrade" +msgstr "無法升級" + +#: ../DistUpgrade/DistUpgradeController.py:369 +#, python-format +msgid "An upgrade from '%s' to '%s' is not supported with this tool." +msgstr "這個工具不支援從‘%s’到‘%s’的升級" + +#: ../DistUpgrade/DistUpgradeController.py:378 +msgid "Sandbox setup failed" +msgstr "沙堆(Sandbox)架設失敗" + +#: ../DistUpgrade/DistUpgradeController.py:379 +msgid "It was not possible to create the sandbox environment." +msgstr "不可能建立沙堆(sandbox)環境。" + +#: ../DistUpgrade/DistUpgradeController.py:385 +msgid "Sandbox mode" +msgstr "沙堆(Sandbox)模式" + +#: ../DistUpgrade/DistUpgradeController.py:386 +#, python-format +msgid "" +"This upgrade is running in sandbox (test) mode. All changes are written to " +"'%s' and will be lost on the next reboot.\n" +"\n" +"*No* changes written to a system directory from now until the next reboot " +"are permanent." +msgstr "" + +#: ../DistUpgrade/DistUpgradeController.py:414 +msgid "" +"Your python install is corrupted. Please fix the '/usr/bin/python' symlink." +msgstr "您的 python 安裝已毀損。請修正 ‘/usr/bin/python’ 的符號連結。" + +#: ../DistUpgrade/DistUpgradeController.py:440 +msgid "Package 'debsig-verify' is installed" +msgstr "套件 'debsig-verify' 安裝完成。" + +#: ../DistUpgrade/DistUpgradeController.py:441 +msgid "" +"The upgrade can not continue with that package installed.\n" +"Please remove it with synaptic or 'apt-get remove debsig-verify' first and " +"run the upgrade again." +msgstr "" +"該已安裝套件令升級無法繼續,請先以「Synaptic 套件管理員」或「apt-get remove " +"debsig-verify」指令將其移除再進行升級。" + +#: ../DistUpgrade/DistUpgradeController.py:453 +#, python-format +msgid "Can not write to '%s'" +msgstr "" + +#: ../DistUpgrade/DistUpgradeController.py:454 +#, python-format +msgid "" +"Its not possible to write to the system directory '%s' on your system. The " +"upgrade can not continue.\n" +"Please make sure that the system directory is writable." +msgstr "" + +#: ../DistUpgrade/DistUpgradeController.py:465 +msgid "Include latest updates from the Internet?" +msgstr "包括互聯網上的最新更新嗎?" + +#: ../DistUpgrade/DistUpgradeController.py:466 +msgid "" +"The upgrade system can use the internet to automatically download the latest " +"updates and install them during the upgrade. If you have a network " +"connection this is highly recommended.\n" +"\n" +"The upgrade will take longer, but when it is complete, your system will be " +"fully up to date. You can choose not to do this, but you should install the " +"latest updates soon after upgrading.\n" +"If you answer 'no' here, the network is not used at all." +msgstr "" +"升級系統可自動由互聯網下載最新更新並在升級時安裝。如有網絡連線,建議使用這方" +"法。\n" +"\n" +"升級的時間會比較久,但完成後,系統就會完全在最新狀態。您可以選擇現在不進行這" +"工作,但是在升級後,應該要盡快安裝最新的更新。\n" +"如在此回答「否」,將完全不會使用網絡。" + +#: ../DistUpgrade/DistUpgradeController.py:686 +#, python-format +msgid "disabled on upgrade to %s" +msgstr "因升級至 %s 停用" + +#: ../DistUpgrade/DistUpgradeController.py:713 +msgid "No valid mirror found" +msgstr "找不到有效的鏡像站" + +#: ../DistUpgrade/DistUpgradeController.py:714 +#, python-format +msgid "" +"While scanning your repository information no mirror entry for the upgrade " +"was found. This can happen if you run a internal mirror or if the mirror " +"information is out of date.\n" +"\n" +"Do you want to rewrite your 'sources.list' file anyway? If you choose 'Yes' " +"here it will update all '%s' to '%s' entries.\n" +"If you select 'No' the upgrade will cancel." +msgstr "" +"掃描套件庫資料時沒有發現可提供升級的鏡像站。如果使用的是內部鏡像站或鏡像站資" +"料已經過時,就會發生這種情況。\n" +"\n" +"是否無論如何都希望覆寫 'sources.list' 檔案?如果在此選擇「是」則會將所有 " +"'%s' 項目更新成 '%s'。\n" +"如果選擇「否」,則會取消更新。" + +#. hm, still nothing useful ... +#: ../DistUpgrade/DistUpgradeController.py:734 +msgid "Generate default sources?" +msgstr "產生預設的來源?" + +#: ../DistUpgrade/DistUpgradeController.py:735 +#, python-format +msgid "" +"After scanning your 'sources.list' no valid entry for '%s' was found.\n" +"\n" +"Should default entries for '%s' be added? If you select 'No', the upgrade " +"will cancel." +msgstr "" +"在掃描您的 'sources.list' 後,沒找到與 '%s' 有效的項目。\n" +"\n" +"要新增 '%s' 的預設項目嗎?如果選擇「否」,則會取消升級。" + +#: ../DistUpgrade/DistUpgradeController.py:770 +msgid "Repository information invalid" +msgstr "套件庫資料無效" + +#: ../DistUpgrade/DistUpgradeController.py:771 +msgid "" +"Upgrading the repository information resulted in a invalid file so a bug " +"reporting process is being started." +msgstr "因更新套件庫資料導致檔案無效,故啟動錯誤報告程序。" + +#: ../DistUpgrade/DistUpgradeController.py:778 +msgid "Third party sources disabled" +msgstr "已停用第三方來源" + +#: ../DistUpgrade/DistUpgradeController.py:779 +msgid "" +"Some third party entries in your sources.list were disabled. You can re-" +"enable them after the upgrade with the 'software-properties' tool or your " +"package manager." +msgstr "" +"部份在 sources.list 的第三方項目已經停用。升級完成後可以「軟件來源(software-" +"properties)」工具或套件管理員重新啟用。" + +#: ../DistUpgrade/DistUpgradeController.py:819 +msgid "Package in inconsistent state" +msgid_plural "Packages in inconsistent state" +msgstr[0] "套件在不一致狀態" + +#: ../DistUpgrade/DistUpgradeController.py:822 +#, python-format +msgid "" +"The package '%s' is in an inconsistent state and needs to be reinstalled, " +"but no archive can be found for it. Please reinstall the package manually or " +"remove it from the system." +msgid_plural "" +"The packages '%s' are in an inconsistent state and need to be reinstalled, " +"but no archive can be found for them. Please reinstall the packages manually " +"or remove them from the system." +msgstr[0] "" +"這個套件 '%s' 狀態不一致而需要重新安裝,但找不到其存檔套件。請手動重新安裝或" +"將其由系統移除。" + +#: ../DistUpgrade/DistUpgradeController.py:870 +msgid "Error during update" +msgstr "更新時發生錯誤" + +#: ../DistUpgrade/DistUpgradeController.py:871 +msgid "" +"A problem occurred during the update. This is usually some sort of network " +"problem, please check your network connection and retry." +msgstr "更新時發生錯誤。這可能是某些網絡問題,請檢查網絡連線後再試。" + +#. print("on_button_install_clicked") +#: ../DistUpgrade/DistUpgradeController.py:880 +#: ../UpdateManager/UpdateManager.py:757 +msgid "Not enough free disk space" +msgstr "磁碟空間不足" + +#: ../DistUpgrade/DistUpgradeController.py:881 +#, python-format +msgid "" +"The upgrade has aborted. The upgrade needs a total of %s free space on disk " +"'%s'. Please free at least an additional %s of disk space on '%s'. Empty " +"your trash and remove temporary packages of former installations using 'sudo " +"apt-get clean'." +msgstr "" +"已放棄升級。升級程序總共需要 %s 可用空間於磁碟「%s」。請釋放至少額外 %s 的磁" +"碟空間於磁碟「%s」。清理您的回收筒,並使用 'sudo apt-get clean' 來移除之前安" +"裝時的暫時性套件。" + +#. calc the dist-upgrade and see if the removals are ok/expected +#. do the dist-upgrade +#: ../DistUpgrade/DistUpgradeController.py:910 +#: ../DistUpgrade/DistUpgradeController.py:1692 +msgid "Calculating the changes" +msgstr "正在推算改動" + +#. ask the user +#: ../DistUpgrade/DistUpgradeController.py:942 +msgid "Do you want to start the upgrade?" +msgstr "想要開始更新嗎?" + +#: ../DistUpgrade/DistUpgradeController.py:1008 +msgid "Upgrade canceled" +msgstr "升級取消了" + +#: ../DistUpgrade/DistUpgradeController.py:1009 +msgid "" +"The upgrade will cancel now and the original system state will be restored. " +"You can resume the upgrade at a later time." +msgstr "現在將取消升級,並將系統還原至原來狀態。您可以在之後繼續升級。" + +#: ../DistUpgrade/DistUpgradeController.py:1015 +#: ../DistUpgrade/DistUpgradeController.py:1149 +msgid "Could not download the upgrades" +msgstr "無法下載升級套件" + +#: ../DistUpgrade/DistUpgradeController.py:1016 +msgid "" +"The upgrade has aborted. Please check your Internet connection or " +"installation media and try again. All files downloaded so far have been kept." +msgstr "" +"升級已中止。請檢查您的互聯網連線,或安裝媒體並重試。所有目前已下載的檔案都會" +"被保留。" + +#. FIXME: strings are not good, but we are in string freeze +#. currently +#: ../DistUpgrade/DistUpgradeController.py:1100 +#: ../DistUpgrade/DistUpgradeController.py:1137 +#: ../DistUpgrade/DistUpgradeController.py:1242 +msgid "Error during commit" +msgstr "提交時發生錯誤" + +#. generate a new cache +#: ../DistUpgrade/DistUpgradeController.py:1102 +#: ../DistUpgrade/DistUpgradeController.py:1139 +#: ../DistUpgrade/DistUpgradeController.py:1281 +msgid "Restoring original system state" +msgstr "回復原有系統狀態" + +#: ../DistUpgrade/DistUpgradeController.py:1103 +#: ../DistUpgrade/DistUpgradeController.py:1118 +#: ../DistUpgrade/DistUpgradeController.py:1140 +msgid "Could not install the upgrades" +msgstr "無法安裝升級套件" + +#. invoke the frontend now and show a error message +#: ../DistUpgrade/DistUpgradeController.py:1108 +msgid "" +"The upgrade has aborted. Your system could be in an unusable state. A " +"recovery will run now (dpkg --configure -a)." +msgstr "" +"已放棄升級。系統可能會處於不穩定狀態。現在會執行復原程序 (dpkg --configure -" +"a)。" + +#: ../DistUpgrade/DistUpgradeController.py:1113 +#, python-format +msgid "" +"\n" +"\n" +"Please report this bug in a browser at http://bugs.launchpad.net/ubuntu/" +"+source/update-manager/+filebug and attach the files in /var/log/dist-" +"upgrade/ to the bug report.\n" +"%s" +msgstr "" + +#: ../DistUpgrade/DistUpgradeController.py:1150 +msgid "" +"The upgrade has aborted. Please check your Internet connection or " +"installation media and try again. " +msgstr "已放棄升級。請檢查您的互聯網連線或安裝媒體,接著再試一次。 " + +#: ../DistUpgrade/DistUpgradeController.py:1230 +msgid "Remove obsolete packages?" +msgstr "移除廢棄的套件?" + +#: ../DistUpgrade/DistUpgradeController.py:1231 +#: ../DistUpgrade/DistUpgrade.ui.h:8 +msgid "_Keep" +msgstr "保留(_K)" + +#: ../DistUpgrade/DistUpgradeController.py:1231 +msgid "_Remove" +msgstr "移除(_R)" + +#: ../DistUpgrade/DistUpgradeController.py:1243 +msgid "" +"A problem occurred during the clean-up. Please see the below message for " +"more information. " +msgstr "進行清理時發生問題。詳情請參閱以下訊息。 " + +#. FIXME: instead of error out, fetch and install it +#. here +#: ../DistUpgrade/DistUpgradeController.py:1319 +msgid "Required depends is not installed" +msgstr "必需的相依套件未安裝" + +#: ../DistUpgrade/DistUpgradeController.py:1320 +#, python-format +msgid "The required dependency '%s' is not installed. " +msgstr "必需的相依套件「%s」未安裝。 " + +#. sanity check (check for ubuntu-desktop, brokenCache etc) +#. then open the cache (again) +#: ../DistUpgrade/DistUpgradeController.py:1588 +#: ../DistUpgrade/DistUpgradeController.py:1653 +msgid "Checking package manager" +msgstr "正在檢查套件管理員" + +#: ../DistUpgrade/DistUpgradeController.py:1593 +msgid "Preparing the upgrade failed" +msgstr "準備升級失敗" + +#: ../DistUpgrade/DistUpgradeController.py:1594 +msgid "" +"Preparing the system for the upgrade failed so a bug reporting process is " +"being started." +msgstr "" + +#: ../DistUpgrade/DistUpgradeController.py:1608 +msgid "Getting upgrade prerequisites failed" +msgstr "取得升級先決元件失敗" + +#: ../DistUpgrade/DistUpgradeController.py:1609 +msgid "" +"The system was unable to get the prerequisites for the upgrade. The upgrade " +"will abort now and restore the original system state.\n" +"\n" +"Additionally, a bug reporting process is being started." +msgstr "" + +#: ../DistUpgrade/DistUpgradeController.py:1637 +msgid "Updating repository information" +msgstr "更新套件庫資料" + +#: ../DistUpgrade/DistUpgradeController.py:1644 +msgid "Failed to add the cdrom" +msgstr "無法加入光碟" + +#: ../DistUpgrade/DistUpgradeController.py:1645 +msgid "Sorry, adding the cdrom was not successful." +msgstr "很抱歉,沒有成功加入光碟。" + +#: ../DistUpgrade/DistUpgradeController.py:1673 +msgid "Invalid package information" +msgstr "套件資訊無效" + +#: ../DistUpgrade/DistUpgradeController.py:1674 +msgid "After updating your package " +msgstr "" + +#: ../DistUpgrade/DistUpgradeController.py:1698 +#: ../DistUpgrade/DistUpgradeController.py:1750 +msgid "Fetching" +msgstr "提取中" + +#: ../DistUpgrade/DistUpgradeController.py:1704 +#: ../DistUpgrade/DistUpgradeController.py:1754 +msgid "Upgrading" +msgstr "升級中" + +#. don't abort here, because it would restore the sources.list +#: ../DistUpgrade/DistUpgradeController.py:1709 +#: ../DistUpgrade/DistUpgradeController.py:1756 +#: ../DistUpgrade/DistUpgradeController.py:1763 +#: ../DistUpgrade/DistUpgradeController.py:1774 +msgid "Upgrade complete" +msgstr "升級完成" + +#: ../DistUpgrade/DistUpgradeController.py:1710 +#: ../DistUpgrade/DistUpgradeController.py:1757 +#: ../DistUpgrade/DistUpgradeController.py:1764 +msgid "" +"The upgrade has completed but there were errors during the upgrade process." +msgstr "升級已經完成,但在升級過程中有發生錯誤。" + +#: ../DistUpgrade/DistUpgradeController.py:1717 +msgid "Searching for obsolete software" +msgstr "搜尋廢棄的軟件中" + +#: ../DistUpgrade/DistUpgradeController.py:1726 +msgid "System upgrade is complete." +msgstr "系統升級完成。" + +#: ../DistUpgrade/DistUpgradeController.py:1775 +msgid "The partial upgrade was completed." +msgstr "部份升級完成。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:204 +msgid "evms in use" +msgstr "有軟件正使用 evms" + +#: ../DistUpgrade/DistUpgradeQuirks.py:205 +msgid "" +"Your system uses the 'evms' volume manager in /proc/mounts. The 'evms' " +"software is no longer supported, please switch it off and run the upgrade " +"again when this is done." +msgstr "" +"您的系統於 /proc/mounts 使用 'evms' volume 管理程式。'evms' 軟件已不受支援," +"請將其關閉再執行升級工作。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:502 +msgid "Your graphics hardware may not be fully supported in Ubuntu 12.04 LTS." +msgstr "" + +#: ../DistUpgrade/DistUpgradeQuirks.py:504 +msgid "" +"The support in Ubuntu 12.04 LTS for your Intel graphics hardware is limited " +"and you may encounter problems after the upgrade. For more information see " +"https://wiki.ubuntu.com/X/Bugs/UpdateManagerWarningForI8xx Do you want to " +"continue with the upgrade?" +msgstr "" + +#: ../DistUpgrade/DistUpgradeQuirks.py:526 +#: ../DistUpgrade/DistUpgradeQuirks.py:554 +#: ../DistUpgrade/DistUpgradeQuirks.py:581 +msgid "" +"Upgrading may reduce desktop effects, and performance in games and other " +"graphically intensive programs." +msgstr "升級可能減低桌面特效和遊戲及其他著重圖形程式的表現。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:530 +#: ../DistUpgrade/DistUpgradeQuirks.py:558 +msgid "" +"This computer is currently using the NVIDIA 'nvidia' graphics driver. No " +"version of this driver is available that works with your video card in " +"Ubuntu 10.04 LTS.\n" +"\n" +"Do you want to continue?" +msgstr "" +"這個電腦目前正使用 NVIDIA 的「nvidia」圖形驅動程式。這個驅動程式沒有任何版本" +"可在 Ubuntu 10.04 LTS 中正常驅動您的硬件。\n" +"\n" +"是否要繼續?" + +#: ../DistUpgrade/DistUpgradeQuirks.py:585 +msgid "" +"This computer is currently using the AMD 'fglrx' graphics driver. No version " +"of this driver is available that works with your hardware in Ubuntu 10.04 " +"LTS.\n" +"\n" +"Do you want to continue?" +msgstr "" +"這個電腦目前正使用 AMD 的「fglrx」圖形驅動程式。這個驅動程式沒有任何版本可在 " +"Ubuntu 10.04 LTS 中正常驅動您的硬件。\n" +"\n" +"是否要繼續?" + +#: ../DistUpgrade/DistUpgradeQuirks.py:615 +msgid "No i686 CPU" +msgstr "非 i686 處理器" + +#: ../DistUpgrade/DistUpgradeQuirks.py:616 +msgid "" +"Your system uses an i586 CPU or a CPU that does not have the 'cmov' " +"extension. All packages were built with optimizations requiring i686 as the " +"minimal architecture. It is not possible to upgrade your system to a new " +"Ubuntu release with this hardware." +msgstr "" +"您的系統使用 i586 處理器,或是不支援「cmov」擴充功能的處理器。所有套件都以 " +"i686 架構為最佳化目標來建置,所以處理器至少需要有 i686 等級。對於目前的硬件來" +"說,無法將系統升級到新的 Ubuntu 發行版。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:652 +msgid "No ARMv6 CPU" +msgstr "非 ARMv6 處理器" + +#: ../DistUpgrade/DistUpgradeQuirks.py:653 +msgid "" +"Your system uses an ARM CPU that is older than the ARMv6 architecture. All " +"packages in karmic were built with optimizations requiring ARMv6 as the " +"minimal architecture. It is not possible to upgrade your system to a new " +"Ubuntu release with this hardware." +msgstr "" +"您系統使用的 ARM 處理器舊於 ARMv6 架構。所有 karmic 套件都以 ARMv6 架構為最佳" +"化目標來建置,所以處理器至少需要有 ARMv6 等級。對於目前的硬件來說,無法將系統" +"升級到新的 Ubuntu 發行版。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:673 +msgid "No init available" +msgstr "無法初始化" + +#: ../DistUpgrade/DistUpgradeQuirks.py:674 +msgid "" +"Your system appears to be a virtualised environment without an init daemon, " +"e.g. Linux-VServer. Ubuntu 10.04 LTS cannot function within this type of " +"environment, requiring an update to your virtual machine configuration " +"first.\n" +"\n" +"Are you sure you want to continue?" +msgstr "" +"您的系統似乎在虛擬化環境中且無初始化程序,例如 Linux-VServer。Ubuntu 10.04 " +"LTS 無法在此環境中執行,需要先更新您的虛擬機器設定。\n" +"\n" +"您確定想要繼續?" + +#: ../DistUpgrade/DistUpgradeMain.py:65 +msgid "Sandbox upgrade using aufs" +msgstr "使用 aufs 作為沙堆升級" + +#: ../DistUpgrade/DistUpgradeMain.py:67 +msgid "Use the given path to search for a cdrom with upgradable packages" +msgstr "使用指定路徑搜尋附有升級套件的光碟" + +#: ../DistUpgrade/DistUpgradeMain.py:73 +msgid "" +"Use frontend. Currently available: \n" +"DistUpgradeViewText, DistUpgradeViewGtk, DistUpgradeViewKDE" +msgstr "" +"使用前端介面。可供選擇的有: \n" +"DistUpgradeViewText (純文字), DistUpgradeViewGtk (GTK+), DistUpgradeViewKDE " +"(KDE)" + +#: ../DistUpgrade/DistUpgradeMain.py:76 +msgid "*DEPRECATED* this option will be ignored" +msgstr "*已棄用* 這個選項會被忽略" + +#: ../DistUpgrade/DistUpgradeMain.py:79 +msgid "Perform a partial upgrade only (no sources.list rewriting)" +msgstr "只進行部份更新 (無須修改 sources.list)" + +#: ../DistUpgrade/DistUpgradeMain.py:82 +msgid "Disable GNU screen support" +msgstr "停用 GNU screen 支援" + +#: ../DistUpgrade/DistUpgradeMain.py:84 +msgid "Set datadir" +msgstr "設定資料目錄" + +#. print("mediaChange %s %s" % (medium, drive)) +#: ../DistUpgrade/DistUpgradeViewGtk.py:114 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:117 +#: ../DistUpgrade/DistUpgradeViewKDE.py:195 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:155 +#, python-format +msgid "Please insert '%s' into the drive '%s'" +msgstr "請將‘%s’放入光碟機‘%s’" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:135 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:138 +#: ../DistUpgrade/DistUpgradeViewKDE.py:209 +msgid "Fetching is complete" +msgstr "提取完成" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:146 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:149 +#: ../DistUpgrade/DistUpgradeViewKDE.py:222 +#, python-format +msgid "Fetching file %li of %li at %sB/s" +msgstr "正提取第 %li 個檔案 (共 %li 個),速度為 %sB/秒" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:149 +#: ../DistUpgrade/DistUpgradeViewGtk.py:296 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:152 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:309 +#: ../DistUpgrade/DistUpgradeViewKDE.py:223 +#: ../DistUpgrade/DistUpgradeViewKDE.py:371 +#, python-format +msgid "About %s remaining" +msgstr "大約還有 %s" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:152 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:155 +#: ../DistUpgrade/DistUpgradeViewKDE.py:225 +#, python-format +msgid "Fetching file %li of %li" +msgstr "正提取第 %li 個檔案 (共 %li 個)" + +#. FIXME: add support for the timeout +#. of the terminal (to display something useful then) +#. -> longer term, move this code into python-apt +#: ../DistUpgrade/DistUpgradeViewGtk.py:183 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:186 +#: ../DistUpgrade/DistUpgradeViewKDE.py:262 +msgid "Applying changes" +msgstr "正在套用改動" + +#. we do not report followup errors from earlier failures +#: ../DistUpgrade/DistUpgradeViewGtk.py:208 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:212 +#: ../DistUpgrade/DistUpgradeViewKDE.py:275 +msgid "dependency problems - leaving unconfigured" +msgstr "相依關係問題 - 仍未被設定" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:213 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:217 +#: ../DistUpgrade/DistUpgradeViewKDE.py:277 +#, python-format +msgid "Could not install '%s'" +msgstr "無法安裝‘%s’" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:214 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:218 +#: ../DistUpgrade/DistUpgradeViewKDE.py:278 +#, python-format +msgid "" +"The upgrade will continue but the '%s' package may not be in a working " +"state. Please consider submitting a bug report about it." +msgstr "更新會繼續,但 '%s' 套件可能無法運作。請考慮提交關於該套件的臭蟲報告。" + +#. self.expander.set_expanded(True) +#: ../DistUpgrade/DistUpgradeViewGtk.py:231 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:235 +#: ../DistUpgrade/DistUpgradeViewKDE.py:299 +#, python-format +msgid "" +"Replace the customized configuration file\n" +"'%s'?" +msgstr "" +"是否取代已有的設定檔案\n" +"「%s」?" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:232 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:236 +#: ../DistUpgrade/DistUpgradeViewKDE.py:300 +msgid "" +"You will lose any changes you have made to this configuration file if you " +"choose to replace it with a newer version." +msgstr "如選擇以新版取代,會失去您改動過的設定。" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:251 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:256 +#: ../DistUpgrade/DistUpgradeViewKDE.py:323 +msgid "The 'diff' command was not found" +msgstr "找不到‘diff’指令" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:464 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:477 +#: ../DistUpgrade/DistUpgradeViewText.py:92 +msgid "A fatal error occurred" +msgstr "發生嚴重錯誤" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:465 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:478 +msgid "" +"Please report this as a bug (if you haven't already) and include the files /" +"var/log/dist-upgrade/main.log and /var/log/dist-upgrade/apt.log in your " +"report. The upgrade has aborted.\n" +"Your original sources.list was saved in /etc/apt/sources.list.distUpgrade." +msgstr "" +"請回報此錯誤 (若您尚未回報) 並將檔案 /var/log/dist-upgrade/main.log 與 /var/" +"log/dist-upgrade/apt.log 附在報告。升級程序已中止。\n" +"您原先的 sources.list 儲存於 /etc/apt/sources.list.distUpgrade" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:482 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:495 +msgid "Ctrl-c pressed" +msgstr "按下 Ctrl+c" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:483 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:496 +msgid "" +"This will abort the operation and may leave the system in a broken state. " +"Are you sure you want to do that?" +msgstr "這將會中止操作並可能令系統在不完整的狀態。您確定要進行嗎?" + +#. append warning +#: ../DistUpgrade/DistUpgradeViewGtk.py:631 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:629 +msgid "To prevent data loss close all open applications and documents." +msgstr "為避免遺失資料,請關閉所有已開啟的程式及文件。" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:645 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:643 +#, python-format +msgid "No longer supported by Canonical (%s)" +msgstr "不再受 Canonical 支援 (%s 個)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:646 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:644 +#, python-format +msgid "Downgrade (%s)" +msgstr "降級 (%s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:647 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:645 +#, python-format +msgid "Remove (%s)" +msgstr "移除 (%s 個)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:648 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:646 +#, python-format +msgid "No longer needed (%s)" +msgstr "不再需要 (%s 個)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:649 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:647 +#, python-format +msgid "Install (%s)" +msgstr "安裝 (%s 個)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:650 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:648 +#, python-format +msgid "Upgrade (%s)" +msgstr "升級 (%s 個)" + +#. change = QMessageBox.question(None, _("Media Change"), msg, QMessageBox.Ok, QMessageBox.Cancel) +#: ../DistUpgrade/DistUpgradeViewKDE.py:196 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:157 +msgid "Media Change" +msgstr "媒體變更" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:335 +msgid "Show Difference >>>" +msgstr "顯示差異 >>>" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:338 +msgid "<<< Hide Difference" +msgstr "<<< 隱藏差異" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:554 +msgid "Error" +msgstr "錯誤" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:568 +msgid "&Cancel" +msgstr "取消(&C)" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:572 +#: ../DistUpgrade/DistUpgradeViewKDE.py:813 +msgid "&Close" +msgstr "關閉(&C)" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:618 +msgid "Show Terminal >>>" +msgstr "顯示終端畫面 >>>" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:621 +msgid "<<< Hide Terminal" +msgstr "<<< 隱藏終端畫面" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:701 +msgid "Information" +msgstr "資訊" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:751 +#: ../DistUpgrade/DistUpgradeViewKDE.py:796 +#: ../DistUpgrade/DistUpgradeViewKDE.py:799 ../DistUpgrade/DistUpgrade.ui.h:7 +msgid "Details" +msgstr "詳情" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:777 +#, python-format +msgid "No longer supported %s" +msgstr "不再受支援 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:779 +#, python-format +msgid "Remove %s" +msgstr "移除 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:781 +#: ../DistUpgrade/DistUpgradeViewText.py:182 +#, python-format +msgid "Remove (was auto installed) %s" +msgstr "移除 (自動安裝的) %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:783 +#, python-format +msgid "Install %s" +msgstr "安裝 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:785 +#, python-format +msgid "Upgrade %s" +msgstr "升級 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:809 +#: ../DistUpgrade/DistUpgradeViewText.py:230 +msgid "Restart required" +msgstr "需要重新開機" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:809 +msgid "Restart the system to complete the upgrade" +msgstr "重新啟動系統以完成更新" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:812 ../DistUpgrade/DistUpgrade.ui.h:14 +#: ../data/gtkbuilder/UpdateManager.ui.h:26 +msgid "_Restart Now" +msgstr "現在重新啟動(_R)" + +#. FIXME make this user friendly +#: ../DistUpgrade/DistUpgradeViewKDE.py:830 +msgid "" +"Cancel the running upgrade?\n" +"\n" +"The system could be in an unusable state if you cancel the upgrade. You are " +"strongly advised to resume the upgrade." +msgstr "" +"取消進行中的升級嗎?\n" +"\n" +"如果您取消升級,系統可能會在不穩定的狀態。強烈建議您繼續升級工作。" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:834 +msgid "Cancel Upgrade?" +msgstr "要取消升級嗎?" + +#: ../DistUpgrade/DistUpgradeView.py:61 +#, python-format +msgid "%li day" +msgid_plural "%li days" +msgstr[0] "%li 日" + +#: ../DistUpgrade/DistUpgradeView.py:63 +#, python-format +msgid "%li hour" +msgid_plural "%li hours" +msgstr[0] "%li 小時" + +#: ../DistUpgrade/DistUpgradeView.py:65 +#, python-format +msgid "%li minute" +msgid_plural "%li minutes" +msgstr[0] "%li 分鐘" + +#: ../DistUpgrade/DistUpgradeView.py:66 +#, python-format +msgid "%li second" +msgid_plural "%li seconds" +msgstr[0] "%li 秒" + +#. TRANSLATORS: you can alter the ordering of the remaining time +#. information here if you shuffle %(str_days)s %(str_hours)s %(str_minutes)s +#. around. Make sure to keep all '$(str_*)s' in the translated string +#. and do NOT change anything appart from the ordering. +#. +#. %(str_hours)s will be either "1 hour" or "2 hours" depending on the +#. plural form +#. +#. Note: most western languages will not need to change this +#: ../DistUpgrade/DistUpgradeView.py:82 +#, python-format +msgid "%(str_days)s %(str_hours)s" +msgstr "%(str_days)s零 %(str_hours)s" + +#. TRANSLATORS: you can alter the ordering of the remaining time +#. information here if you shuffle %(str_hours)s %(str_minutes)s +#. around. Make sure to keep all '$(str_*)s' in the translated string +#. and do NOT change anything appart from the ordering. +#. +#. %(str_hours)s will be either "1 hour" or "2 hours" depending on the +#. plural form +#. +#. Note: most western languages will not need to change this +#: ../DistUpgrade/DistUpgradeView.py:100 +#, python-format +msgid "%(str_hours)s %(str_minutes)s" +msgstr "%(str_hours)s又 %(str_minutes)s" + +#. 56 kbit +#. 1Mbit = 1024 kbit +#: ../DistUpgrade/DistUpgradeView.py:151 +#, python-format +msgid "" +"This download will take about %s with a 1Mbit DSL connection and about %s " +"with a 56k modem." +msgstr "這次下載所需時間在 1M 寬頻連線大約要 %s,用 56k 數據機大約要 %s。" + +#. if we have a estimated speed, use it +#: ../DistUpgrade/DistUpgradeView.py:155 +#, python-format +msgid "This download will take about %s with your connection. " +msgstr "按照您的連線速度,此下載會花約 %s。 " + +#. Declare these translatable strings from the .ui files here so that +#. xgettext picks them up. +#: ../DistUpgrade/DistUpgradeView.py:259 ../DistUpgrade/DistUpgrade.ui.h:21 +msgid "Preparing to upgrade" +msgstr "準備升級" + +#: ../DistUpgrade/DistUpgradeView.py:260 +msgid "Getting new software channels" +msgstr "取得新軟件頻道中" + +#: ../DistUpgrade/DistUpgradeView.py:261 ../DistUpgrade/DistUpgrade.ui.h:23 +msgid "Getting new packages" +msgstr "取得新套件" + +#: ../DistUpgrade/DistUpgradeView.py:262 ../DistUpgrade/DistUpgrade.ui.h:26 +msgid "Installing the upgrades" +msgstr "安裝升級" + +#: ../DistUpgrade/DistUpgradeView.py:263 ../DistUpgrade/DistUpgrade.ui.h:25 +msgid "Cleaning up" +msgstr "清理" + +#: ../DistUpgrade/DistUpgradeView.py:348 +#, python-format +msgid "" +"%(amount)d installed package is no longer supported by Canonical. You can " +"still get support from the community." +msgid_plural "" +"%(amount)d installed packages are no longer supported by Canonical. You can " +"still get support from the community." +msgstr[0] "" +"已安裝套件中有 %(amount)d 個不再受 Canonical 支援。您仍可以取得來自社羣的支" +"援。" + +#. FIXME: make those two separate lines to make it clear +#. that the "%" applies to the result of ngettext +#: ../DistUpgrade/DistUpgradeView.py:357 +#, python-format +msgid "%d package is going to be removed." +msgid_plural "%d packages are going to be removed." +msgstr[0] "即將移除 %d 個套件。" + +#: ../DistUpgrade/DistUpgradeView.py:362 +#, python-format +msgid "%d new package is going to be installed." +msgid_plural "%d new packages are going to be installed." +msgstr[0] "即將安裝 %d 個新套件。" + +#: ../DistUpgrade/DistUpgradeView.py:368 +#, python-format +msgid "%d package is going to be upgraded." +msgid_plural "%d packages are going to be upgraded." +msgstr[0] "即將升級 %d 個套件。" + +#: ../DistUpgrade/DistUpgradeView.py:373 +#, python-format +msgid "" +"\n" +"\n" +"You have to download a total of %s. " +msgstr "" +"\n" +"\n" +"您必須下載全部的%s。 " + +#: ../DistUpgrade/DistUpgradeView.py:378 +msgid "" +"Installing the upgrade can take several hours. Once the download has " +"finished, the process cannot be canceled." +msgstr "" + +#: ../DistUpgrade/DistUpgradeView.py:382 +msgid "" +"Fetching and installing the upgrade can take several hours. Once the " +"download has finished, the process cannot be canceled." +msgstr "" + +#: ../DistUpgrade/DistUpgradeView.py:387 +msgid "Removing the packages can take several hours. " +msgstr "" + +#. FIXME: this should go into DistUpgradeController +#: ../DistUpgrade/DistUpgradeView.py:392 ../UpdateManager/UpdateManager.py:676 +msgid "The software on this computer is up to date." +msgstr "" + +#: ../DistUpgrade/DistUpgradeView.py:393 +msgid "" +"There are no upgrades available for your system. The upgrade will now be " +"canceled." +msgstr "您的系統已經在最新狀態。現在將取消升級的動作。" + +#: ../DistUpgrade/DistUpgradeView.py:406 +msgid "Reboot required" +msgstr "需要重新開機" + +#: ../DistUpgrade/DistUpgradeView.py:407 +msgid "" +"The upgrade is finished and a reboot is required. Do you want to do this now?" +msgstr "升級已經完成及需要重新啟動。現在要重新啟動嗎?" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:72 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:72 +#, python-format +msgid "authenticate '%(file)s' against '%(signature)s' " +msgstr "" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:131 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:131 +#, python-format +msgid "extracting '%s'" +msgstr "" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:151 +#: ../DistUpgrade/DistUpgradeFetcherCore.py:152 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:151 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:152 +msgid "Could not run the upgrade tool" +msgstr "無法執行升級工具" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:152 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:152 +msgid "" +"This is most likely a bug in the upgrade tool. Please report it as a bug " +"using the command 'ubuntu-bug update-manager'." +msgstr "" +"這可能是升級工具的錯誤,請使用指令「ubuntu-bug update-manager」回報錯誤。" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:227 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:227 +msgid "Upgrade tool signature" +msgstr "升級工具簽署" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:234 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:234 +msgid "Upgrade tool" +msgstr "升級工具" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:268 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:268 +msgid "Failed to fetch" +msgstr "提取失敗" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:269 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:269 +msgid "Fetching the upgrade failed. There may be a network problem. " +msgstr "提取升級套件失敗。可能是網絡問題。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:273 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:273 +msgid "Authentication failed" +msgstr "認證失敗" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:274 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:274 +msgid "" +"Authenticating the upgrade failed. There may be a problem with the network " +"or with the server. " +msgstr "認證升級套件失敗。可能是因為跟伺服器的網絡連線出現問題。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:279 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:279 +msgid "Failed to extract" +msgstr "解壓失敗" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:280 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:280 +msgid "" +"Extracting the upgrade failed. There may be a problem with the network or " +"with the server. " +msgstr "升級套件解壓失敗。可能是因為網絡或伺服器出現問題。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:285 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:285 +msgid "Verification failed" +msgstr "驗證失敗" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:286 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:286 +msgid "" +"Verifying the upgrade failed. There may be a problem with the network or " +"with the server. " +msgstr "驗證升級套件失敗。可能是因為網絡或伺服器出現問題。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:300 +#: ../DistUpgrade/DistUpgradeFetcherCore.py:306 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:300 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:306 +msgid "Can not run the upgrade" +msgstr "不能進行升級" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:301 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:301 +msgid "" +"This usually is caused by a system where /tmp is mounted noexec. Please " +"remount without noexec and run the upgrade again." +msgstr "" +"這通常是由使用 noexec 掛載 /tmp 的系統引致的。請不要使用 noexec 重新掛載,並" +"再次進行升級。" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:307 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:307 +#, python-format +msgid "The error message is '%s'." +msgstr "錯誤訊息 '%s'。" + +#: ../DistUpgrade/DistUpgradeViewText.py:93 +msgid "" +"Please report this as a bug and include the files /var/log/dist-upgrade/main." +"log and /var/log/dist-upgrade/apt.log in your report. The upgrade has " +"aborted.\n" +"Your original sources.list was saved in /etc/apt/sources.list.distUpgrade." +msgstr "" +"請回報此錯誤並將檔案 /var/log/dist-upgrade/main.log 與 /var/log/dist-upgrade/" +"apt.log 附在報告。升級程序已中止。\n" +"您原先的 sources.list 儲存於 /etc/apt/sources.list.distUpgrade" + +#: ../DistUpgrade/DistUpgradeViewText.py:117 +msgid "Aborting" +msgstr "正在中止" + +#: ../DistUpgrade/DistUpgradeViewText.py:122 +msgid "Demoted:\n" +msgstr "降等:\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:129 +msgid "To continue please press [ENTER]" +msgstr "若要繼續請按 [ENTER]" + +#: ../DistUpgrade/DistUpgradeViewText.py:157 +#: ../DistUpgrade/DistUpgradeViewText.py:196 +#: ../DistUpgrade/DistUpgradeViewText.py:203 +msgid "Continue [yN] " +msgstr "繼續 [yN] " + +#: ../DistUpgrade/DistUpgradeViewText.py:157 +#: ../DistUpgrade/DistUpgradeViewText.py:196 +msgid "Details [d]" +msgstr "詳情 [d]" + +#. TRANSLATORS: the "y" is "yes" +#. TRANSLATORS: first letter of a positive (yes) answer +#: ../DistUpgrade/DistUpgradeViewText.py:162 +#: ../DistUpgrade/DistUpgradeViewText.py:206 +msgid "y" +msgstr "y" + +#. TRANSLATORS: the "n" is "no" +#. TRANSLATORS: first letter of a negative (no) answer +#: ../DistUpgrade/DistUpgradeViewText.py:165 +#: ../DistUpgrade/DistUpgradeViewText.py:213 +msgid "n" +msgstr "n" + +#. TRANSLATORS: the "d" is "details" +#: ../DistUpgrade/DistUpgradeViewText.py:168 +msgid "d" +msgstr "d" + +#: ../DistUpgrade/DistUpgradeViewText.py:173 +#, python-format +msgid "No longer supported: %s\n" +msgstr "不再支援:%s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:178 +#, python-format +msgid "Remove: %s\n" +msgstr "移除: %s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:188 +#, python-format +msgid "Install: %s\n" +msgstr "安裝: %s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:193 +#, python-format +msgid "Upgrade: %s\n" +msgstr "升級: %s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:210 +msgid "Continue [Yn] " +msgstr "繼續 [Yn] " + +#: ../DistUpgrade/DistUpgradeViewText.py:231 +msgid "" +"To finish the upgrade, a restart is required.\n" +"If you select 'y' the system will be restarted." +msgstr "" +"需要重新開機才能完成更新。\n" +"如果您選擇 'y' 系統將會重新開機。" + +#: ../DistUpgrade/DistUpgrade.ui.h:1 +msgid "_Cancel Upgrade" +msgstr "取消升級(_C)" + +#: ../DistUpgrade/DistUpgrade.ui.h:2 +msgid "_Resume Upgrade" +msgstr "繼續升級(_R)" + +#: ../DistUpgrade/DistUpgrade.ui.h:3 +msgid "" +"Cancel the running upgrade?\n" +"\n" +"The system could be in an unusable state if you cancel the upgrade. You are " +"strongly adviced to resume the upgrade." +msgstr "" +"取消正在執行的升級?\n" +"\n" +"如果您取消升級可能會導致系統不穩定。強烈建議您繼續升級。" + +#: ../DistUpgrade/DistUpgrade.ui.h:6 +msgid "_Start Upgrade" +msgstr "開始升級(_S)" + +#: ../DistUpgrade/DistUpgrade.ui.h:9 +msgid "_Replace" +msgstr "取代(_R)" + +#: ../DistUpgrade/DistUpgrade.ui.h:10 +msgid "Difference between the files" +msgstr "檔案差異" + +#: ../DistUpgrade/DistUpgrade.ui.h:11 +msgid "_Report Bug" +msgstr "匯報錯誤(_R)" + +#: ../DistUpgrade/DistUpgrade.ui.h:12 +msgid "_Continue" +msgstr "繼續(_C)" + +#: ../DistUpgrade/DistUpgrade.ui.h:13 +msgid "Start the upgrade?" +msgstr "開始升級嗎?" + +#: ../DistUpgrade/DistUpgrade.ui.h:15 +msgid "" +"Restart the system to complete the upgrade\n" +"\n" +"Please save your work before continuing." +msgstr "" +"重新啟動系統以完成更新\n" +"\n" +"請在繼續前先儲存您的作業。" + +#: ../DistUpgrade/DistUpgrade.ui.h:18 +msgid "Distribution Upgrade" +msgstr "發行版升級" + +#: ../DistUpgrade/DistUpgrade.ui.h:19 +#, fuzzy +msgid "Upgrading Ubuntu to version 12.10" +msgstr "將 Ubuntu 升級至 11.10 版" + +#: ../DistUpgrade/DistUpgrade.ui.h:20 +msgid " " +msgstr " " + +#: ../DistUpgrade/DistUpgrade.ui.h:22 +msgid "Setting new software channels" +msgstr "設定新的軟件來源頻道" + +#: ../DistUpgrade/DistUpgrade.ui.h:24 +msgid "Restarting the computer" +msgstr "重新啟動系統" + +#: ../DistUpgrade/DistUpgrade.ui.h:27 +msgid "Terminal" +msgstr "終端" + +#: ../UpdateManager/backend/InstallBackendSynaptic.py:64 +msgid "Please wait, this can take some time." +msgstr "請稍候,這可能需要一點時間。" + +#: ../UpdateManager/backend/InstallBackendSynaptic.py:66 +msgid "Update is complete" +msgstr "更新完成" + +#: ../UpdateManager/DistUpgradeFetcher.py:114 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:109 +msgid "Could not find the release notes" +msgstr "找不到發行公告" + +#: ../UpdateManager/DistUpgradeFetcher.py:115 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:110 +msgid "The server may be overloaded. " +msgstr "伺服器可能負荷過重。 " + +#: ../UpdateManager/DistUpgradeFetcher.py:125 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:114 +msgid "Could not download the release notes" +msgstr "無法下載發行公告" + +#: ../UpdateManager/DistUpgradeFetcher.py:126 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:115 +msgid "Please check your internet connection." +msgstr "請檢查您的互聯網連線。" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:68 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:91 +msgid "Upgrade" +msgstr "升級" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:95 +#: ../data/gtkbuilder/UpdateManager.ui.h:20 +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:2 +msgid "Release Notes" +msgstr "發行公告" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:134 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:148 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:150 +msgid "Downloading additional package files..." +msgstr "正在下載額外的套件檔案..." + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:148 +#, python-format +msgid "File %s of %s at %sB/s" +msgstr "檔案 %s / %s (速度:%sB/秒)" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:150 +#, python-format +msgid "File %s of %s" +msgstr "檔案 %s / %s" + +#: ../UpdateManager/ChangelogViewer.py:75 +msgid "Open Link in Browser" +msgstr "用瀏覽器開啟連結" + +#: ../UpdateManager/ChangelogViewer.py:78 +msgid "Copy Link to Clipboard" +msgstr "複製連結至剪貼簿" + +#: ../UpdateManager/GtkProgress.py:162 +#, python-format +msgid "Downloading file %(current)li of %(total)li with %(speed)s/s" +msgstr "共 %(total)li 個檔案,正下載第 %(current)li 個 (速度:%(speed)s/秒)" + +#: ../UpdateManager/GtkProgress.py:167 +#, python-format +msgid "Downloading file %(current)li of %(total)li" +msgstr "共 %(total)li 個檔案,正下載第 %(current)li 個" + +#: ../UpdateManager/UpdateManager.py:106 ../do-release-upgrade:100 +msgid "Your Ubuntu release is not supported anymore." +msgstr "您的 Ubuntu 發行版本已經不再支援。" + +#: ../UpdateManager/UpdateManager.py:107 +msgid "" +"You will not get any further security fixes or critical updates. Please " +"upgrade to a later version of Ubuntu." +msgstr "將無法再取得保安修正或重要更新。請升級至最新版本 Ubuntu。" + +#: ../UpdateManager/UpdateManager.py:115 +msgid "Upgrade information" +msgstr "升級資訊" + +#: ../UpdateManager/UpdateManager.py:233 +#: ../UpdateManagerText/UpdateManagerText.py:35 +msgid "Install" +msgstr "安裝" + +#: ../UpdateManager/UpdateManager.py:235 +msgid "Name" +msgstr "" + +#. upload_archive = version_match.group(2).strip() +#: ../UpdateManager/UpdateManager.py:395 +#, python-format +msgid "Version %s: \n" +msgstr "版本 %s: \n" + +#: ../UpdateManager/UpdateManager.py:453 +msgid "" +"No network connection detected, you can not download changelog information." +msgstr "偵測不到網絡連線,故無法下載改動記錄。" + +#: ../UpdateManager/UpdateManager.py:463 +msgid "Downloading list of changes..." +msgstr "正在下載改動清單..." + +#: ../UpdateManager/UpdateManager.py:507 +msgid "_Deselect All" +msgstr "取消所有選取(_D)" + +#: ../UpdateManager/UpdateManager.py:513 +msgid "Select _All" +msgstr "全部選取(_A)" + +#: ../UpdateManager/UpdateManager.py:572 +#, python-format +msgid "%s will be downloaded." +msgstr "會下載 %s。" + +#: ../UpdateManager/UpdateManager.py:584 +#, fuzzy +msgid "The update has already been downloaded." +msgid_plural "The updates have already been downloaded." +msgstr[0] "更新已下載,但尚未安裝" + +#: ../UpdateManager/UpdateManager.py:589 +msgid "There are no updates to install." +msgstr "" + +#: ../UpdateManager/UpdateManager.py:598 +msgid "Unknown download size." +msgstr "下載大小不詳。" + +#: ../UpdateManager/UpdateManager.py:624 +msgid "" +"It is unknown when the package information was updated last. Please click " +"the 'Check' button to update the information." +msgstr "未知套件資訊的上次更新時間。請點擊「檢查」按鈕來更新資訊。" + +#: ../UpdateManager/UpdateManager.py:630 +#, python-format +msgid "" +"The package information was last updated %(days_ago)s days ago.\n" +"Press the 'Check' button below to check for new software updates." +msgstr "" +"%(days_ago)s 天前更新過套件資訊。\n" +"請按下方「檢查」鈕看看有否新資訊。" + +#: ../UpdateManager/UpdateManager.py:635 +#, python-format +msgid "The package information was last updated %(days_ago)s day ago." +msgid_plural "The package information was last updated %(days_ago)s days ago." +msgstr[0] "上次更新套件資訊是 %(days_ago)s 天前。" + +#: ../UpdateManager/UpdateManager.py:639 +#, python-format +msgid "The package information was last updated %(hours_ago)s hour ago." +msgid_plural "" +"The package information was last updated %(hours_ago)s hours ago." +msgstr[0] "上次更新套件資訊是 %(hours_ago)s 小時前。" + +#. TRANSLATORS: only in plural form, as %s minutes ago is one of 15, 30, 45 minutes ago +#: ../UpdateManager/UpdateManager.py:644 ../UpdateManager/UpdateManager.py:646 +#: ../UpdateManager/UpdateManager.py:648 +#, python-format +msgid "The package information was last updated about %s minutes ago." +msgstr "套件資訊約 %s 分鐘前更新。" + +#: ../UpdateManager/UpdateManager.py:650 +msgid "The package information was just updated." +msgstr "套件資訊剛更新。" + +#: ../UpdateManager/UpdateManager.py:689 +msgid "Software updates may be available for your computer." +msgstr "可能有軟體更新提供予閣下之電腦。" + +#: ../UpdateManager/UpdateManager.py:697 +#, python-format +msgid "" +"Updated software has been issued since %s was released. Do you want to " +"install it now?" +msgstr "" + +#: ../UpdateManager/UpdateManager.py:700 +msgid "" +"Updated software is available for this computer. Do you want to install it " +"now?" +msgstr "" + +#: ../UpdateManager/UpdateManager.py:758 +#, python-format +msgid "" +"The upgrade needs a total of %s free space on disk '%s'. Please free at " +"least an additional %s of disk space on '%s'. Empty your trash and remove " +"temporary packages of former installations using 'sudo apt-get clean'." +msgstr "" +"升級工作需要總共 %s 可用空間於硬碟 ‘%s’。請額外空出最少 %s 的空間於 ‘%s’。清" +"理清理您的回收筒或使用 ‘sudo apt-get clean’ 移除上次安裝的暫存套件。" + +#: ../UpdateManager/UpdateManager.py:783 +msgid "" +"The computer needs to restart to finish installing updates. Please save your " +"work before continuing." +msgstr "必須重新啟動電腦來完成安裝更新,在繼續之前請先儲存您的工作。" + +#: ../UpdateManager/UpdateManager.py:847 +msgid "Reading package information" +msgstr "正在讀取套件資訊" + +#: ../UpdateManager/UpdateManager.py:862 +msgid "Connecting..." +msgstr "連線中..." + +#: ../UpdateManager/UpdateManager.py:879 +msgid "You may not be able to check for updates or download new updates." +msgstr "可能無法檢查是否有、或下載新的更新。" + +#: ../UpdateManager/UpdateManager.py:1002 +msgid "Could not initialize the package information" +msgstr "無法為套件資訊進行初始化" + +#: ../UpdateManager/UpdateManager.py:1003 +msgid "" +"An unresolvable problem occurred while initializing the package " +"information.\n" +"\n" +"Please report this bug against the 'update-manager' package and include the " +"following error message:\n" +msgstr "" +"為套件資訊進行初始化時發生不能解決的問題。\n" +"\n" +"請將此匯報為「update-manager」套件的問題並附上以下錯誤訊息:\n" + +#: ../UpdateManager/UpdateManager.py:1032 +msgid "" +"An unresolvable problem occurred while calculating the upgrade.\n" +"\n" +"Please report this bug against the 'update-manager' package and include the " +"following error message:" +msgstr "" +"為升級進行推算時有問題無法解決。\n" +"\n" +"請為「update-manager」套件匯報此問題並附上以下錯誤訊息:" + +#: ../UpdateManager/UpdateManager.py:1056 +msgid " (New install)" +msgstr " (新安裝)" + +#. TRANSLATORS: the b stands for Bytes +#: ../UpdateManager/UpdateManager.py:1063 +#, python-format +msgid "(Size: %s)" +msgstr "(大小:%s)" + +#: ../UpdateManager/UpdateManager.py:1067 +#, python-format +msgid "From version %(old_version)s to %(new_version)s" +msgstr "由 %(old_version)s 版更新至 %(new_version)s 版" + +#: ../UpdateManager/UpdateManager.py:1071 +#, python-format +msgid "Version %s" +msgstr "%s 版" + +#: ../UpdateManager/UpdateManager.py:1104 ../do-release-upgrade:112 +msgid "Release upgrade not possible right now" +msgstr "目前不能升級發行版" + +#: ../UpdateManager/UpdateManager.py:1105 ../do-release-upgrade:113 +#, c-format, python-format +msgid "" +"The release upgrade can not be performed currently, please try again later. " +"The server reported: '%s'" +msgstr "目前無法升級發行版,請稍後重試。該伺服器回報:「%s」。" + +#: ../UpdateManager/UpdateManager.py:1107 ../check-new-release-gtk:126 +msgid "Downloading the release upgrade tool" +msgstr "正在下載發行版更新工具" + +#: ../UpdateManager/UpdateManager.py:1114 +#, python-format +msgid "New Ubuntu release '%s' is available" +msgstr "有新的 Ubuntu 發行版 '%s' 可供升級" + +#. we assert a clean cache +#: ../UpdateManager/UpdateManager.py:1153 +msgid "Software index is broken" +msgstr "軟件索引已損壞" + +#: ../UpdateManager/UpdateManager.py:1154 +msgid "" +"It is impossible to install or remove any software. Please use the package " +"manager \"Synaptic\" or run \"sudo apt-get install -f\" in a terminal to fix " +"this issue at first." +msgstr "" +"無法安裝或移除套件。請先以「Synaptic 套件管理員」或在終端機執行「sudo apt-" +"get install -f」修正問題。" + +#: ../UpdateManager/UnitySupport.py:57 +msgid "Check for Updates" +msgstr "檢查有否更新" + +#: ../UpdateManager/UnitySupport.py:66 +msgid "Install All Available Updates" +msgstr "安裝所有可進行的更新" + +#: ../UpdateManagerText/UpdateManagerText.py:34 +msgid "Cancel" +msgstr "取消" + +#: ../UpdateManagerText/UpdateManagerText.py:37 +msgid "Changelog" +msgstr "" + +#: ../UpdateManagerText/UpdateManagerText.py:40 +msgid "Updates" +msgstr "" + +#: ../UpdateManagerText/UpdateManagerText.py:53 +msgid "Building Updates List" +msgstr "正在建立更新清單" + +#: ../UpdateManagerText/UpdateManagerText.py:56 +msgid "" +"\n" +"A normal upgrade can not be calculated, please run: \n" +" sudo apt-get dist-upgrade\n" +"\n" +"\n" +"This can be caused by:\n" +" * A previous upgrade which didn't complete\n" +" * Problems with some of the installed software\n" +" * Unofficial software packages not provided by Ubuntu\n" +" * Normal changes of a pre-release version of Ubuntu" +msgstr "" +"\n" +"無法為標準升級進行推算,請執行:\n" +" sudo apt-get dist-upgrade\n" +"\n" +"\n" +" 原因可能是:\n" +" * 前次升級程序未完成\n" +" * 某些已安裝的軟件有問題\n" +" * 非 Ubuntu 官方軟件套件的問題\n" +" * 測試版 Ubuntu 的正常改動" + +#: ../UpdateManagerText/UpdateManagerText.py:125 +msgid "Downloading changelog" +msgstr "正在下載改動記錄" + +#: ../UpdateManager/Core/MyCache.py:147 +#, python-format +msgid "Other updates (%s)" +msgstr "其他更新 (%s)" + +#: ../UpdateManager/Core/MyCache.py:325 +msgid "This update does not come from a source that supports changelogs." +msgstr "此更新來源不支援改動記錄。" + +#: ../UpdateManager/Core/MyCache.py:331 ../UpdateManager/Core/MyCache.py:359 +msgid "" +"Failed to download the list of changes. \n" +"Please check your Internet connection." +msgstr "" +"未能下載改動清單。\n" +"請檢查互聯網連線。" + +#: ../UpdateManager/Core/MyCache.py:338 +#, python-format +msgid "" +"Changes for the versions:\n" +"Installed version: %s\n" +"Available version: %s\n" +"\n" +msgstr "" +"版本改動:\n" +"已安裝版本:%s\n" +"新版本:%s\n" +"\n" + +#: ../UpdateManager/Core/MyCache.py:348 +#, python-format +msgid "" +"The changelog does not contain any relevant changes.\n" +"\n" +"Please use http://launchpad.net/ubuntu/+source/%s/%s/+changelog\n" +"until the changes become available or try again later." +msgstr "" +"改動記錄並未包含相關資料。\n" +"\n" +"有改動紀錄提供前請看 http://launchpad.net/ubuntu/+source/%s/%s/" +"+changelog ,\n" +"或稍候再嘗試。" + +#: ../UpdateManager/Core/MyCache.py:353 +#, python-format +msgid "" +"The list of changes is not available yet.\n" +"\n" +"Please use http://launchpad.net/ubuntu/+source/%s/%s/+changelog\n" +"until the changes become available or try again later." +msgstr "" +"仍未有改動清單提供。\n" +"\n" +"有改動清單提供前請看 http://launchpad.net/ubuntu/+source/%s/%s/" +"+changelog ,\n" +"或稍候再嘗試。" + +#: ../UpdateManager/Core/UpdateList.py:51 +msgid "Failed to detect distribution" +msgstr "無法偵測出版本" + +#: ../UpdateManager/Core/UpdateList.py:52 +#, python-format +msgid "A error '%s' occurred while checking what system you are using." +msgstr "當檢查您所使用的系統時有錯誤 \"%s\" 發生。" + +#: ../UpdateManager/Core/UpdateList.py:63 +msgid "Important security updates" +msgstr "重要保安更新" + +#: ../UpdateManager/Core/UpdateList.py:64 +msgid "Recommended updates" +msgstr "重要更新" + +#: ../UpdateManager/Core/UpdateList.py:65 +msgid "Proposed updates" +msgstr "建議更新" + +#: ../UpdateManager/Core/UpdateList.py:66 +msgid "Backports" +msgstr "回植套件" + +#: ../UpdateManager/Core/UpdateList.py:67 +msgid "Distribution updates" +msgstr "發行版更新" + +#: ../UpdateManager/Core/UpdateList.py:72 +msgid "Other updates" +msgstr "其他更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:1 +#, fuzzy +msgid "Starting Software Updater" +msgstr "啟動更新管理員" + +#: ../data/gtkbuilder/UpdateManager.ui.h:2 +msgid "" +"Software updates correct errors, eliminate security vulnerabilities and " +"provide new features." +msgstr "軟件更新會更正錯誤、排除安全隱患並提供新功能。" + +#: ../data/gtkbuilder/UpdateManager.ui.h:3 +msgid "_Partial Upgrade" +msgstr "部份升級(_P)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:4 +msgid "Not all updates can be installed" +msgstr "並非所有更新都可以安裝" + +#: ../data/gtkbuilder/UpdateManager.ui.h:5 +msgid "" +"Run a partial upgrade, to install as many updates as possible. \n" +"\n" +"This can be caused by:\n" +" * A previous upgrade which didn't complete\n" +" * Problems with some of the installed software\n" +" * Unofficial software packages not provided by Ubuntu\n" +" * Normal changes of a pre-release version of Ubuntu" +msgstr "" +"執行部份升級,會儘可能安裝最多更新。 \n" +"\n" +"原因可能是:\n" +" * 前次升級程序未完成\n" +" * 某些已安裝的軟件有問題\n" +" * 非 Ubuntu 官方軟件套件的問題\n" +" * 測試版 Ubuntu 的正常改動" + +#: ../data/gtkbuilder/UpdateManager.ui.h:12 +msgid "Chec_k" +msgstr "檢查(_K)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:13 +msgid "" +"You must check for updates manually\n" +"\n" +"Your system does not check for updates automatically. You can configure this " +"behavior in Software Sources on the Updates tab." +msgstr "" +"必須手動檢查更新\n" +"\n" +"系統不會自動檢查更新。可在更新 分頁的軟件來源 設定。" + +#: ../data/gtkbuilder/UpdateManager.ui.h:16 +msgid "_Hide this information in the future" +msgstr "以後不要再顯示此訊息(_H)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:17 +msgid "Co_ntinue" +msgstr "繼續(_N)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:18 +msgid "Running on battery" +msgstr "使用電池運行" + +#: ../data/gtkbuilder/UpdateManager.ui.h:19 +msgid "Your system is running on battery. Are you sure you want to continue?" +msgstr "您的系統正在使用電池。確定要繼續嗎?" + +#: ../data/gtkbuilder/UpdateManager.ui.h:21 +msgid "_Upgrade" +msgstr "升級(_U)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:22 +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:8 +msgid "Show progress of individual files" +msgstr "顯示個別檔案進度" + +#: ../data/gtkbuilder/UpdateManager.ui.h:23 +#: ../data/update-manager.desktop.in.h:1 +#, fuzzy +msgid "Software Updater" +msgstr "軟件更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:24 +#, fuzzy +msgid "Starting Software Updater" +msgstr "軟件更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:25 +msgid "U_pgrade" +msgstr "升級(_P)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:27 +msgid "updates" +msgstr "更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:28 +msgid "Changes" +msgstr "改動" + +#: ../data/gtkbuilder/UpdateManager.ui.h:29 +msgid "Description" +msgstr "說明" + +#: ../data/gtkbuilder/UpdateManager.ui.h:30 +#, fuzzy +msgid "Details of updates" +msgstr "更新說明" + +#: ../data/gtkbuilder/UpdateManager.ui.h:31 +msgid "" +"You are connected via roaming and may be charged for the data consumed by " +"this update." +msgstr "正以漫遊連上網絡,可能要對本次更新所下載的資料量付費。" + +#: ../data/gtkbuilder/UpdateManager.ui.h:32 +msgid "" +"You may want to wait until you’re not using a mobile broadband connection." +msgstr "" + +#: ../data/gtkbuilder/UpdateManager.ui.h:33 +msgid "It’s safer to connect the computer to AC power before updating." +msgstr "在更新前先將電腦接上變壓器比較安全。" + +#: ../data/gtkbuilder/UpdateManager.ui.h:34 +msgid "_Settings..." +msgstr "設定(_S)..." + +#: ../data/gtkbuilder/UpdateManager.ui.h:35 +#, fuzzy +msgid "_Install Now" +msgstr "安裝" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:1 +msgid "A new version of Ubuntu is available. Would you like to upgrade?" +msgstr "有新版本 Ubuntu。是否升級?" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:3 +msgid "Don't Upgrade" +msgstr "不升級" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:4 +msgid "Ask Me Later" +msgstr "稍後再問我" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:5 +msgid "Yes, Upgrade Now" +msgstr "是,現在升級" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:6 +msgid "You have declined to upgrade to the new Ubuntu" +msgstr "您已拒絕升級至新版 Ubuntu" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:7 +#, fuzzy +msgid "" +"You can upgrade at a later time by opening Software Updater and click on " +"\"Upgrade\"." +msgstr "您稍後仍可以透過開啟更新管理員並按下『升級』進行升級。" + +#: ../data/update-manager.desktop.in.h:2 +msgid "Software Updates" +msgstr "軟件更新" + +#: ../data/update-manager.desktop.in.h:3 +msgid "Show and install available updates" +msgstr "顯示並安裝現有的軟件更新" + +#: ../update-manager:66 ../update-manager-text:55 ../do-release-upgrade:51 +msgid "Show version and exit" +msgstr "顯示版本並結束" + +#: ../update-manager:69 +msgid "Directory that contains the data files" +msgstr "含有資料檔案的目錄" + +#: ../update-manager:72 +msgid "Check if a new Ubuntu release is available" +msgstr "檢查有否新 Ubuntu 發行版可供升級" + +#: ../update-manager:75 ../do-release-upgrade:54 ../check-new-release-gtk:186 +msgid "Check if upgrading to the latest devel release is possible" +msgstr "檢查能否升級至最新測試版" + +#: ../update-manager:79 +msgid "Upgrade using the latest proposed version of the release upgrader" +msgstr "以最新建議版本的發行升級工具進行升級" + +#: ../update-manager:86 +msgid "Do not focus on map when starting" +msgstr "啟動時不預先選取圖錄" + +#: ../update-manager:89 +msgid "Try to run a dist-upgrade" +msgstr "試著執行 dist-upgrade" + +#: ../update-manager:92 +msgid "Do not check for updates when starting" +msgstr "啟動時不要檢查更新" + +#: ../update-manager:96 ../do-release-upgrade:70 +msgid "Test upgrade with a sandbox aufs overlay" +msgstr "使用沙堆 aufs 層測試升級" + +#: ../update-manager:116 +msgid "Running partial upgrade" +msgstr "執行部份升級" + +#: ../update-manager-text:59 +msgid "Show description of the package instead of the changelog" +msgstr "" + +#: ../do-release-upgrade:58 ../check-new-release-gtk:190 +msgid "" +"Try upgrading to the latest release using the upgrader from $distro-proposed" +msgstr "嘗試使用 $distro-proposed 的升級程式來升級至最新的發行版本" + +#: ../do-release-upgrade:62 +msgid "" +"Run in a special upgrade mode.\n" +"Currently 'desktop' for regular upgrades of a desktop system and 'server' " +"for server systems are supported." +msgstr "" +"以特殊升級模式進行。\n" +"目前只支援以 'desktop' 模式升級桌面版本的系統,以及以 'server' 模式升級伺服器" +"版的系統。" + +#: ../do-release-upgrade:68 +msgid "Run the specified frontend" +msgstr "執行指定的前端" + +#: ../do-release-upgrade:73 +msgid "" +"Check only if a new distribution release is available and report the result " +"via the exit code" +msgstr "檢查是否有新的發行版並以結束碼報告結果" + +#: ../do-release-upgrade:87 +msgid "Checking for a new Ubuntu release" +msgstr "" + +#: ../do-release-upgrade:101 +msgid "" +"For upgrade information, please visit:\n" +"%(url)s\n" +msgstr "" +"若要取得升級資訊,請參訪:\n" +"%(url)s\n" + +#: ../do-release-upgrade:107 +msgid "No new release found" +msgstr "找不到新發行版" + +#: ../do-release-upgrade:119 +#, c-format +msgid "New release '%s' available." +msgstr "有新發行版 '%s' 提供。" + +#: ../do-release-upgrade:120 +msgid "Run 'do-release-upgrade' to upgrade to it." +msgstr "執行 ‘do-release-upgrade’ 進行升級工作。" + +#: ../check-new-release-gtk:101 +msgid "Ubuntu %(version)s Upgrade Available" +msgstr "可以升級至 Ubuntu %(version)s" + +#: ../check-new-release-gtk:143 +#, c-format +msgid "You have declined the upgrade to Ubuntu %s" +msgstr "您已拒絕升級至 Ubuntu %s" + +#: ../check-new-release-gtk:196 +msgid "Add debug output" +msgstr "" + +#: ../ubuntu-support-status:91 +msgid "Show unsupported packages on this machine" +msgstr "" + +#: ../ubuntu-support-status:94 +msgid "Show supported packages on this machine" +msgstr "" + +#: ../ubuntu-support-status:97 +msgid "Show all packages with their status" +msgstr "" + +#: ../ubuntu-support-status:100 +msgid "Show all packages in a list" +msgstr "" + +#: ../ubuntu-support-status:142 +#, c-format +msgid "Support status summary of '%s':" +msgstr "" + +#: ../ubuntu-support-status:145 +msgid "You have %(num)s packages (%(percent).1f%%) supported until %(time)s" +msgstr "" + +#: ../ubuntu-support-status:151 +msgid "" +"You have %(num)s packages (%(percent).1f%%) that can not/no-longer be " +"downloaded" +msgstr "" + +#: ../ubuntu-support-status:154 +msgid "You have %(num)s packages (%(percent).1f%%) that are unsupported" +msgstr "" + +#: ../ubuntu-support-status:162 +msgid "" +"Run with --show-unsupported, --show-supported or --show-all to see more " +"details" +msgstr "" + +#: ../ubuntu-support-status:166 +msgid "No longer downloadable:" +msgstr "" + +#: ../ubuntu-support-status:169 +msgid "Unsupported: " +msgstr "" + +#: ../ubuntu-support-status:174 +#, c-format +msgid "Supported until %s:" +msgstr "" + +#: ../ubuntu-support-status:183 +msgid "Unsupported" +msgstr "" + +#. Why do we use %s here instead of $strings or {} format placeholders? +#. It's because we don't want to break existing translations. +#: ../janitor/plugincore/exceptions.py:42 +#, python-format +msgid "Unimplemented method: %s" +msgstr "未實作的方法: %s" + +#: ../janitor/plugincore/core/file_cruft.py:41 +msgid "A file on disk" +msgstr "磁碟上之檔案" + +#: ../janitor/plugincore/core/missing_package_cruft.py:39 +msgid "Install missing package." +msgstr "安裝缺少的套件。" + +#. 2012-06-08 BAW: i18n string; don't use {} or PEP 292. +#: ../janitor/plugincore/core/missing_package_cruft.py:49 +#, python-format +msgid "Package %s should be installed." +msgstr "應安裝 %s 套件。" + +#: ../janitor/plugincore/core/package_cruft.py:49 +msgid ".deb package" +msgstr ".deb 套件" + +#: ../janitor/plugincore/plugins/langpack_manual_plugin.py:46 +#, python-format +msgid "%s needs to be marked as manually installed." +msgstr "%s 要標記為手動安裝。" + +#: ../janitor/plugincore/plugins/kdelibs4to5_plugin.py:49 +msgid "" +"When upgrading, if kdelibs4-dev is installed, kdelibs5-dev needs to be " +"installed. See bugs.launchpad.net, bug #279621 for details." +msgstr "" +"升級時如已安裝 kdelibs4-dev,那就必須安裝 kdelibs5-dev。詳情請見 bugs." +"launchpad.net,bug #279621。" + +#: ../janitor/plugincore/plugins/dpkg_status_plugin.py:44 +#, python-format +msgid "%i obsolete entries in the status file" +msgstr "狀態檔中有 %i 條廢棄項目" + +#: ../janitor/plugincore/plugins/dpkg_status_plugin.py:47 +msgid "Obsolete entries in dpkg status" +msgstr "dpkg 狀態中有廢棄條目" + +#. pragma: no cover +#: ../janitor/plugincore/plugins/dpkg_status_plugin.py:50 +msgid "Obsolete dpkg status entries" +msgstr "廢棄的 dpkg 狀態項目" + +#: ../janitor/plugincore/plugins/remove_lilo_plugin.py:42 +msgid "Remove lilo since grub is also installed.(See bug #314004 for details.)" +msgstr "因 grub 已安裝,移除 lilo (詳情見 bug #314004)" + +#~ msgid "Upgrading Ubuntu to version 12.04" +#~ msgstr "將 Ubuntu 升級至 12.04 版" + +#~ msgid "%(count)s update has been selected." +#~ msgid_plural "%(count)s updates have been selected." +#~ msgstr[0] "已選取 %(count)s 項更新。" + +#~ msgid "%(count_str)s %(download_str)s" +#~ msgstr "%(count_str)s %(download_str)s" + +#~ msgid "Welcome to Ubuntu" +#~ msgstr "歡迎使用 Ubuntu" + +#~ msgid "Update Manager" +#~ msgstr "更新管理員" + +#~ msgid "Starting Update Manager" +#~ msgstr "正在啟動更新管理員" + +#~ msgid "You are connected via a wireless modem." +#~ msgstr "您正以無線數據機連線。" + +#~ msgid "_Install Updates" +#~ msgstr "安裝更新套件(_I)" + +#~ msgid "Your system is up-to-date" +#~ msgstr "系統已經在最新狀態" + +#~ msgid "Software updates are available for this computer" +#~ msgstr "有軟件更新適用於此電腦" + +#~ msgid "There are no updates to install" +#~ msgstr "沒有要安裝的升級" + +#~ msgid "%.0f kB" +#~ msgstr "%.0f kB" + +#~ msgid "0 kB" +#~ msgstr "0 kB" + +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "Please report this bug using the command 'ubuntu-bug update-manager' in a " +#~ "terminal and include the files in /var/log/dist-upgrade/ in the bug " +#~ "report.\n" +#~ "%s" +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "請在終端機內輸入指令「ubuntu-bug update-manager」回報錯誤,並在錯誤報告中" +#~ "附上 /var/log/dist-upgrade/ 內之檔案。\n" +#~ "%s" + +#~ msgid "" +#~ "Preparing the system for the upgrade failed. Please report this using the " +#~ "command 'ubuntu-bug update-manager' in a terminal and include the files " +#~ "in /var/log/dist-upgrade/ in the bug report." +#~ msgstr "" +#~ "準備系統升級失敗。請在終端機內輸入指令「ubuntu-bug update-manager」回報錯" +#~ "誤,並在錯誤報告中附上 /var/log/dist-upgrade/ 內之檔案。" + +#~ msgid "" +#~ "Upgrading the repository information resulted in a invalid file. Please " +#~ "report this as a bug using the command 'ubuntu-bug update-manager' in a " +#~ "terminal." +#~ msgstr "" +#~ "升級套件庫時導致無效的檔案。請在終端機內輸入指令「ubuntu-bug update-" +#~ "manager」回報錯誤。" + +#~ msgid "1 kB" +#~ msgstr "1 kB" + +#~ msgid "Your graphics hardware may not be fully supported in Ubuntu 11.04." +#~ msgstr "您的繪圖硬件可能無法在 Ubuntu 11.04 獲得完整的支援。" + +#~ msgid "" +#~ "The support in Ubuntu 11.04 for your intel graphics hardware is limited " +#~ "and you may encounter problems after the upgrade. Do you want to continue " +#~ "with the upgrade?" +#~ msgstr "" +#~ "您的 Intel 繪圖硬件在 Ubuntu 11.04 內的支援有限,且可能會在升級之後碰到一" +#~ "些問題。您要繼續進行升級嗎?" + +#~ msgid "" +#~ "The system was unable to get the prerequisites for the upgrade. The " +#~ "upgrade will abort now and restore the original system state.\n" +#~ "\n" +#~ "Please report this as a bug using the command 'ubuntu-bug update-manager' " +#~ "in a terminal and include the files in /var/log/dist-upgrade/ in the bug " +#~ "report." +#~ msgstr "" +#~ "此系統無法達到升級之要求。現在將中斷升級並將系統回復至原來狀態。\n" +#~ "\n" +#~ "請在終端機內輸入指令「ubuntu-bug update-manager」回報錯誤,並在錯誤報告中" +#~ "附上 /var/log/dist-upgrade/ 內之檔案。" + +#~ msgid "" +#~ "After your package information was updated the essential package '%s' can " +#~ "not be found anymore.\n" +#~ "This indicates a serious error, please report this bug using the command " +#~ "'ubuntu-bug update-manager' in a terminal and include the files in /var/" +#~ "log/dist-upgrade/ in the bug report." +#~ msgstr "" +#~ "更新套件資訊後再找不到必備套件「%s」。\n" +#~ "此為嚴重錯誤,請在終端機內輸入指令「ubuntu-bug update-manager」回報錯誤," +#~ "並在錯誤報告附上 /var/log/dist-upgrade/ 內的檔案。" + +#~ msgid "" +#~ "You will not get any further security fixes or critical updates. Please " +#~ "upgrade to a later version of Ubuntu Linux." +#~ msgstr "無法再取得保安修正與重大更新。請升級至新版 Ubuntu Linux。" + +#~ msgid "" +#~ "If you don't want to install them now, choose \"Update Manager\" from " +#~ "Applications later." +#~ msgstr "若現在不想安裝,可稍後於「應用程式」選單開啟「更新管理員」再安裝。" + +#~ msgid "" +#~ "These software updates have been issued since this version of Ubuntu was " +#~ "released. If you don't want to install them now, choose \"Update Manager" +#~ "\" from Applications later." +#~ msgstr "" +#~ "本 Ubuntu 版本自發行已發布此等軟件更新。若現在不想安裝,可稍後於「應用程" +#~ "式」選單開啟「更新管理員」再安裝。" + +#~ msgid "" +#~ "These software updates have been issued since this version of Ubuntu was " +#~ "released. If you don't want to install them now, choose \"Update Manager" +#~ "\" from the Administration Menu later." +#~ msgstr "" +#~ "本 Ubuntu 版本自發行已發布此等軟件更新。若現在不想安裝,可稍後於「管理」選" +#~ "單開啟「更新管理員」再安裝。" + +#~ msgid "" +#~ "If you don't want to install them now, choose \"Update Manager\" from the " +#~ "Administration menu later." +#~ msgstr "若現在不想安裝,可稍後於「管理」選單開啟「更新管理員」再安裝。" + +#~ msgid "" +#~ "This upgrade is running in sandbox (test) mode. All changes are written " +#~ "to '%s' and will be lost on the next reboot.\n" +#~ "\n" +#~ "*No* changes written to a systemdir from now until the next reboot are " +#~ "permanent." +#~ msgstr "" +#~ "這次升級是在沙堆 (測試) 模式執行。所有改動都會寫入至「%s」並在下次重新開機" +#~ "時消失。\n" +#~ "\n" +#~ "由現在起至下次重新開機,寫入至系統目錄之改動都*不會*保留。" + +#~ msgid "Checking for a new ubuntu release" +#~ msgstr "檢查有否新 Ubuntu 發行版" + +#~ msgid "" +#~ "Fetching and installing the upgrade can take several hours. Once the " +#~ "download has finished, the process cannot be cancelled." +#~ msgstr "提取檔案及安裝升級可能要花數小時。一旦下載完成即不能取消升級程序。" + + +msgid "Unable to access the source management server" +msgstr "無法存取來源管理伺服器,請稍後再試" + +msgid "Check if your network requires authentication?" +msgstr "檢查您的網路需要認證嗎?" + +msgid "Check your source public key signature" +msgstr "檢查您的源數字簽名" + +msgid "update important list occur Exception" +msgstr "獲取推送出現異常,請稍後再試" + +msgid "You need to be root to run this application" +msgstr "你需要root許可權運行" + +msgid "There is an exception in the update package." +msgstr "更新包存在異常!" + +msgid "You request the removal of a system-essential package." +msgstr "您要求刪除系統必要的套件。" + +msgid "This update cannot detect the upgradeable package." +msgstr "本次更新無法檢測到可升級的軟體包。" + +msgid "read important list failed" +msgstr "無法讀取推送升級清單,請稍後再試" + +msgid "Priority Upgrade Package being updated" +msgstr "正在更新分組配置" + +msgid "Exceptions of Priority Upgrade." +msgstr "優先升級異常" + +msgid "Due to the presence of deleted packages." +msgstr "由於存在刪除的套件" + +msgid "The system update configuration file is read abnormally, please check if the system update configuration file format is correct." +msgstr "讀取系統更新設定檔異常,請檢查系統更新配置檔格式是否正確。" + +msgid "Installation progress: " +msgstr "安裝進度: " + +msgid "Installation successful, about to shut down" +msgstr "安裝成功,即將關機" + +msgid "Installation failed, about to shut down" +msgstr "安裝失敗,即將關機" + +msgid "groups JSON ConfigPkgs install failed" +msgstr "無法安裝分組配置檔" + +msgid "Installtion timeout to exit Due to inactivity" +msgstr "安裝超時退出由於" + +msgid "Command execution error" +msgstr "命令執行報錯" + +msgid "Unsupported architecture" +msgstr "架構不符合" + +msgid "Other Error" +msgstr "其他錯誤" + +msgid "dependency is not satisfied" +msgstr "依賴關係不滿足" + +msgid "dependency is not satisfied will download" +msgstr "依賴關係不滿足" + +msgid "Disk space is insufficient, please clean the disk and then upgrade" +msgstr "磁碟空間不足,請清理磁碟後進行升級更新。" + +msgid "Network anomaly, can't check for updates!" +msgstr "網路異常,無法檢查更新!" + +msgid "Check for update exceptions!" +msgstr "檢查更新異常!" + +msgid "Check for update exceptions,fix system APT environment error." +msgstr "檢查更新異常,修復系統APT環境出現錯誤。" + +msgid "The system APT environment is abnormal, please check the system APT environment." +msgstr "修復系統APT環境異常,請檢查系統APT環境。" + +msgid "Priority upgrade status exception." +msgstr "優先升級狀態異常。" + +msgid "Upgrade configuration acquisition exception." +msgstr "升級配置獲取異常。" + +msgid "Please check your network connection and retry." +msgstr "請檢查您的網路連接后再試。" + +msgid "Please check your source list and retry." +msgstr "請檢查您的源清單後再試。" + +msgid "Checking network connection" +msgstr "檢查網路連接中" + +msgid "Updating Source Template" +msgstr "更新源範本中" + +msgid "Update Manager upgrade is complete, please restart the setting panel before performing the system update." +msgstr "更新管理器升級完成,請重啟設置-更新后再進行系統更新。" + +msgid "Uninstallation completed" +msgstr "卸載完成。" + +msgid "Package validation failed and installation was rejected." +msgstr "套件驗證失敗,拒絕安裝。" + +msgid "Other tasks are being updated and upgraded, please uninstall them later." +msgstr "其他任務正在更新升級中,請稍後再卸載。" + +#: ../SystemUpdater/Core/enums.py:763 +msgid "Kylin System Updater" +msgstr "麒麟更新器" + +#: ../SystemUpdater/Core/enums.py:609 +msgid "Kylin Installer" +msgstr "麒麟安裝器" + +#: ../SystemUpdater/Core/enums.py:610 +msgid "Kylin Uninstaller" +msgstr "麒麟卸載器" + +#: ../SystemUpdater/Core/enums.py:611 +msgid "Kylin Background Upgrade" +msgstr "靜默更新" + +#: ../SystemUpdater/Core/enums.py:612 +msgid "Kylin Software Center" +msgstr "軟體商店" + +#: ../SystemUpdater/UpdateManagerDbus.py:355 +msgid " requires authentication to uninstall software packages." +msgstr "卸載套件需要認證。" + +#: ../SystemUpdater/UpdateManager.py:463 +msgid " requires authentication to install software packages." +msgstr "安裝套件需要認證。" + +#: ../SystemUpdater/Core/utils.py:750 +msgid "Authentication success." +msgstr "認證成功。" + +#: ../SystemUpdater/Core/utils.py:753 +msgid "Authentication failure." +msgstr "認證失敗。" + +#: ../SystemUpdater/Core/enums.py:101 +msgid "Deb format exception, read local deb file error." +msgstr "軟體包格式異常,讀取失敗。" + +#: ../SystemUpdater/Core/enums.py:102 +msgid "Install deb error." +msgstr "安裝套件失敗。" + +msgid "Upgrade System" +msgstr "全盤升級" + +msgid "kylin-unattended-upgrade" +msgstr "自動更新" + +msgid "Please check the system time and synchronize the system time before updating." +msgstr "請檢查系統時間,同步系統時間后再進行更新。" + +msgid "The package is unsigned, refuses to install." +msgstr "軟體包未簽名,拒絕安裝。" \ No newline at end of file diff --git a/backend/po/zh_TW.po b/backend/po/zh_TW.po new file mode 100644 index 0000000..c69880b --- /dev/null +++ b/backend/po/zh_TW.po @@ -0,0 +1,2728 @@ +msgid "" +msgstr "" +"Project-Id-Version: update-manager 0.41.1\n" +"Report-Msgid-Bugs-To: sebastian.heinlein@web.de\n" +"POT-Creation-Date: 2012-06-14 00:53+0100\n" +"PO-Revision-Date: 2012-03-23 02:57+0000\n" +"Last-Translator: Cheng-Chia Tseng \n" +"Language-Team: Chinese (Taiwan) \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Launchpad-Export-Date: 2012-04-17 13:41+0000\n" +"X-Generator: Launchpad (build 15099)\n" + +#. TRANSLATORS: download size of small updates, e.g. "250 kB" +#: ../DistUpgrade/utils.py:433 ../UpdateManager/Core/utils.py:433 +#, python-format +msgid "%(size).0f kB" +msgid_plural "%(size).0f kB" +msgstr[0] "%(size).0f kB" + +#. TRANSLATORS: download size of updates, e.g. "2.3 MB" +#: ../DistUpgrade/utils.py:436 ../UpdateManager/Core/utils.py:436 +#, python-format +msgid "%.1f MB" +msgstr "%.1f MB" + +#. TRANSLATORS: %s is a country +#: ../DistUpgrade/distro.py:206 ../DistUpgrade/distro.py:436 +#, python-format +msgid "Server for %s" +msgstr "%s伺服器" + +#. More than one server is used. Since we don't handle this case +#. in the user interface we set "custom servers" to true and +#. append a list of all used servers +#: ../DistUpgrade/distro.py:224 ../DistUpgrade/distro.py:230 +#: ../DistUpgrade/distro.py:246 +msgid "Main server" +msgstr "主伺服器" + +#: ../DistUpgrade/distro.py:250 +msgid "Custom servers" +msgstr "自訂伺服器" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:142 +msgid "Could not calculate sources.list entry" +msgstr "無法計算 sources.list 項目" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:251 +msgid "" +"Unable to locate any package files, perhaps this is not a Ubuntu Disc or the " +"wrong architecture?" +msgstr "找不到套件檔,也許這不是 Ubuntu 光碟,或者處理器架構不對。" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:294 +msgid "Failed to add the CD" +msgstr "無法加入光碟" + +#: ../DistUpgrade/DistUpgradeAptCdrom.py:295 +#, python-format +msgid "" +"There was a error adding the CD, the upgrade will abort. Please report this " +"as a bug if this is a valid Ubuntu CD.\n" +"\n" +"The error message was:\n" +"'%s'" +msgstr "" +"加入光碟時發生錯誤,升級將終止。若您確定光碟沒有問題,請將此匯報為臭蟲。\n" +"\n" +"錯誤訊息:\n" +"「%s」" + +#: ../DistUpgrade/DistUpgradeCache.py:151 +msgid "Remove package in bad state" +msgid_plural "Remove packages in bad state" +msgstr[0] "移除有問題套件" + +#: ../DistUpgrade/DistUpgradeCache.py:154 +#, python-format +msgid "" +"The package '%s' is in an inconsistent state and needs to be reinstalled, " +"but no archive can be found for it. Do you want to remove this package now " +"to continue?" +msgid_plural "" +"The packages '%s' are in an inconsistent state and need to be reinstalled, " +"but no archives can be found for them. Do you want to remove these packages " +"now to continue?" +msgstr[0] "" +"「%s」套件狀態不一致而需要重新安裝,但找不到其存檔。是否移除此套件並繼續?" + +#. FIXME: not ideal error message, but we just reuse a +#. existing one here to avoid a new string +#: ../DistUpgrade/DistUpgradeCache.py:255 +msgid "The server may be overloaded" +msgstr "伺服器可能負載過大" + +#: ../DistUpgrade/DistUpgradeCache.py:368 +msgid "Broken packages" +msgstr "損毀套件" + +#: ../DistUpgrade/DistUpgradeCache.py:369 +msgid "" +"Your system contains broken packages that couldn't be fixed with this " +"software. Please fix them first using synaptic or apt-get before proceeding." +msgstr "" +"本軟體無法修正閣下系統某有損毀套件。請先使用 synaptic 或 apt-get 修正才繼續。" + +#. FIXME: change the text to something more useful +#: ../DistUpgrade/DistUpgradeCache.py:693 +#, python-format +msgid "" +"An unresolvable problem occurred while calculating the upgrade:\n" +"%s\n" +"\n" +" This can be caused by:\n" +" * Upgrading to a pre-release version of Ubuntu\n" +" * Running the current pre-release version of Ubuntu\n" +" * Unofficial software packages not provided by Ubuntu\n" +"\n" +msgstr "" +"準備升級時發生不能解決的問題:\n" +"%s\n" +"\n" +" 這可能由以下原因造成:\n" +" * 正升級至非正式發佈版本的 Ubuntu\n" +" * 正使用非正式發佈版本的 Ubuntu\n" +" * 安裝了非由 Ubuntu 官方提供的軟體套件\n" +"\n" + +#: ../DistUpgrade/DistUpgradeCache.py:703 +msgid "This is most likely a transient problem, please try again later." +msgstr "很可能只是暫時有問題。請稍候再試。" + +#: ../DistUpgrade/DistUpgradeCache.py:706 +msgid "" +"If none of this applies, then please report this bug using the command " +"'ubuntu-bug update-manager' in a terminal." +msgstr "" +"如全都不對,請在終端機內輸入指令「ubuntu-bug update-manager」回報錯誤。" + +#: ../DistUpgrade/DistUpgradeCache.py:711 +#: ../UpdateManager/UpdateManager.py:1031 +msgid "Could not calculate the upgrade" +msgstr "無法計算升級" + +#: ../DistUpgrade/DistUpgradeCache.py:762 +msgid "Error authenticating some packages" +msgstr "認證一些套件時發生錯誤" + +#: ../DistUpgrade/DistUpgradeCache.py:763 +msgid "" +"It was not possible to authenticate some packages. This may be a transient " +"network problem. You may want to try again later. See below for a list of " +"unauthenticated packages." +msgstr "" +"無法驗證部份套件。可能是因為短時間的網路問題。您可以稍候再試一次。下面是未被" +"驗證的套件清單。" + +#: ../DistUpgrade/DistUpgradeCache.py:783 +#, python-format +msgid "" +"The package '%s' is marked for removal but it is in the removal blacklist." +msgstr "套件「%s」標記作移除,但它在移除黑名單中。" + +#: ../DistUpgrade/DistUpgradeCache.py:787 +#, python-format +msgid "The essential package '%s' is marked for removal." +msgstr "必要套件「%s」被標記作移除。" + +#: ../DistUpgrade/DistUpgradeCache.py:796 +#, python-format +msgid "Trying to install blacklisted version '%s'" +msgstr "正在嘗試安裝黑名單版本「%s」" + +#: ../DistUpgrade/DistUpgradeCache.py:914 +#, python-format +msgid "Can't install '%s'" +msgstr "無法安裝「%s」" + +#: ../DistUpgrade/DistUpgradeCache.py:915 +msgid "" +"It was impossible to install a required package. Please report this as a bug " +"using 'ubuntu-bug update-manager' in a terminal." +msgstr "" +"無法安裝必要的套件。請在終端機內輸入「ubuntu-bug update-manager」回報錯誤。" + +#. FIXME: provide a list +#: ../DistUpgrade/DistUpgradeCache.py:926 +msgid "Can't guess meta-package" +msgstr "無法估計元套件 (meta-package)" + +#: ../DistUpgrade/DistUpgradeCache.py:927 +msgid "" +"Your system does not contain a ubuntu-desktop, kubuntu-desktop, xubuntu-" +"desktop or edubuntu-desktop package and it was not possible to detect which " +"version of Ubuntu you are running.\n" +" Please install one of the packages above first using synaptic or apt-get " +"before proceeding." +msgstr "" +"您的系統沒有安裝 ubuntu-desktop,kubuntu-desktop 或 edubuntu-desktop 套件,因" +"此無法偵測正在使用那個版本的 Ubuntu。\n" +" 請先使用 synaptic 或 apt-get 安裝上述其中一個套件再繼續作業" + +#: ../DistUpgrade/DistUpgradeController.py:114 +msgid "Reading cache" +msgstr "正在讀取快取" + +#: ../DistUpgrade/DistUpgradeController.py:223 +msgid "Unable to get exclusive lock" +msgstr "無法取得(使用)排他鎖定" + +#: ../DistUpgrade/DistUpgradeController.py:224 +msgid "" +"This usually means that another package management application (like apt-get " +"or aptitude) already running. Please close that application first." +msgstr "" +"這通常表示有其他的套件管理員程式(如 apt-get 或 aptitude)正在執行。請先關閉" +"這些應用程式。" + +#: ../DistUpgrade/DistUpgradeController.py:257 +msgid "Upgrading over remote connection not supported" +msgstr "不支援透過遠端連線升級" + +#: ../DistUpgrade/DistUpgradeController.py:258 +msgid "" +"You are running the upgrade over a remote ssh connection with a frontend " +"that does not support this. Please try a text mode upgrade with 'do-release-" +"upgrade'.\n" +"\n" +"The upgrade will abort now. Please try without ssh." +msgstr "" +"您正在透過遠端 ssh 連線的前端介面執行更新,而此介面不支援。請試試純文字模式下" +"以 'do-release-upgrade' 進行更新。\n" +"\n" +"目前更新將會中止。請透過非 ssh 連線重試。" + +#: ../DistUpgrade/DistUpgradeController.py:272 +msgid "Continue running under SSH?" +msgstr "繼續執行於 SSH 中?" + +#: ../DistUpgrade/DistUpgradeController.py:273 +#, python-format +msgid "" +"This session appears to be running under ssh. It is not recommended to " +"perform a upgrade over ssh currently because in case of failure it is harder " +"to recover.\n" +"\n" +"If you continue, an additional ssh daemon will be started at port '%s'.\n" +"Do you want to continue?" +msgstr "" +"此連線階段似乎是在 ssh 下執行。目前不建議在 ssh 連線下進行升級,因為若發生失" +"敗將會較難以修復。\n" +"\n" +"若您繼續,一個額外的 ssh 背景程序將會啟動在 '%s' 連接埠。\n" +"您想要繼續嗎?" + +#: ../DistUpgrade/DistUpgradeController.py:287 +msgid "Starting additional sshd" +msgstr "啟動後備 sshd 中" + +#: ../DistUpgrade/DistUpgradeController.py:288 +#, python-format +msgid "" +"To make recovery in case of failure easier, an additional sshd will be " +"started on port '%s'. If anything goes wrong with the running ssh you can " +"still connect to the additional one.\n" +msgstr "" +"為在失敗時更易進行修復,一個後備 sshd 將在‘%s’埠被啟動。如果使用中的 ssh 有任" +"何問題,您仍可以連接後備的 sshd 。\n" + +#: ../DistUpgrade/DistUpgradeController.py:296 +#, python-format +msgid "" +"If you run a firewall, you may need to temporarily open this port. As this " +"is potentially dangerous it's not done automatically. You can open the port " +"with e.g.:\n" +"'%s'" +msgstr "" +"若您有執行防火牆,您可能需要暫時開啟此連接埠。由於此動作存在潛在危險,系統不" +"會自動執行。您可以這樣開啟連接埠:\n" +"「%s」" + +#: ../DistUpgrade/DistUpgradeController.py:368 +#: ../DistUpgrade/DistUpgradeController.py:413 +msgid "Can not upgrade" +msgstr "無法升級" + +#: ../DistUpgrade/DistUpgradeController.py:369 +#, python-format +msgid "An upgrade from '%s' to '%s' is not supported with this tool." +msgstr "這個工具不支援從‘%s’到‘%s’的升級" + +#: ../DistUpgrade/DistUpgradeController.py:378 +msgid "Sandbox setup failed" +msgstr "沙堆(Sandbox)架設失敗" + +#: ../DistUpgrade/DistUpgradeController.py:379 +msgid "It was not possible to create the sandbox environment." +msgstr "不可能建立沙堆(sandbox)環境。" + +#: ../DistUpgrade/DistUpgradeController.py:385 +msgid "Sandbox mode" +msgstr "沙堆(Sandbox)模式" + +#: ../DistUpgrade/DistUpgradeController.py:386 +#, python-format +msgid "" +"This upgrade is running in sandbox (test) mode. All changes are written to " +"'%s' and will be lost on the next reboot.\n" +"\n" +"*No* changes written to a system directory from now until the next reboot " +"are permanent." +msgstr "" +"此升級程序正以沙箱 (測試) 模式執行。所有的變更會寫入「%s」,下次重新開機後會" +"消失。\n" +"\n" +"從現在起直到下次重新開機前,任何寫入系統目錄的變更都「不會」被留存。" + +#: ../DistUpgrade/DistUpgradeController.py:414 +msgid "" +"Your python install is corrupted. Please fix the '/usr/bin/python' symlink." +msgstr "您的 python 安裝已毀損。請修正 ‘/usr/bin/python’ 的符號連結。" + +#: ../DistUpgrade/DistUpgradeController.py:440 +msgid "Package 'debsig-verify' is installed" +msgstr "套件 'debsig-verify' 安裝完成。" + +#: ../DistUpgrade/DistUpgradeController.py:441 +msgid "" +"The upgrade can not continue with that package installed.\n" +"Please remove it with synaptic or 'apt-get remove debsig-verify' first and " +"run the upgrade again." +msgstr "" +"此已安裝的套件令升級無法繼續,請使用 synaptic 或命令 'apt-get remove debsig-" +"verify' 移除它後再次進行升級。" + +#: ../DistUpgrade/DistUpgradeController.py:453 +#, python-format +msgid "Can not write to '%s'" +msgstr "無法寫入「%s」" + +#: ../DistUpgrade/DistUpgradeController.py:454 +#, python-format +msgid "" +"Its not possible to write to the system directory '%s' on your system. The " +"upgrade can not continue.\n" +"Please make sure that the system directory is writable." +msgstr "" +"無法寫入您的系統目錄「%s」。升級程序無法繼續。\n" +"請確認該系統目錄可以寫入。" + +#: ../DistUpgrade/DistUpgradeController.py:465 +msgid "Include latest updates from the Internet?" +msgstr "要否包括來自網際網路的最新更新?" + +#: ../DistUpgrade/DistUpgradeController.py:466 +msgid "" +"The upgrade system can use the internet to automatically download the latest " +"updates and install them during the upgrade. If you have a network " +"connection this is highly recommended.\n" +"\n" +"The upgrade will take longer, but when it is complete, your system will be " +"fully up to date. You can choose not to do this, but you should install the " +"latest updates soon after upgrading.\n" +"If you answer 'no' here, the network is not used at all." +msgstr "" +"升級系統可自動由網際網路下載最新更新並於升級時安裝。如您有網路連線,建議使用" +"這方法。\n" +"\n" +"升級時間會較長,但完成後,您的系統就會完全在最新狀態。您可以選擇現在不進行這" +"工作,但是在升級後,您應該要盡快安裝最新的更新。\n" +"如果您在此回答「否」,將完全不會使用網路。" + +#: ../DistUpgrade/DistUpgradeController.py:686 +#, python-format +msgid "disabled on upgrade to %s" +msgstr "因升級至 %s 停用" + +#: ../DistUpgrade/DistUpgradeController.py:713 +msgid "No valid mirror found" +msgstr "找不到有效的鏡像站" + +#: ../DistUpgrade/DistUpgradeController.py:714 +#, python-format +msgid "" +"While scanning your repository information no mirror entry for the upgrade " +"was found. This can happen if you run a internal mirror or if the mirror " +"information is out of date.\n" +"\n" +"Do you want to rewrite your 'sources.list' file anyway? If you choose 'Yes' " +"here it will update all '%s' to '%s' entries.\n" +"If you select 'No' the upgrade will cancel." +msgstr "" +"掃描閣下套件庫時沒有發現升級映射項目資訊。如果您運行內部映射或者映射資訊已經" +"過期,就會發生這種情況。\n" +"\n" +"是否無論如何都希望覆寫您的「sources.list」檔案?如選「是」則將會將所有「%s」" +"更新成「%s」項目。\n" +"如選「否」則會取消更新。" + +#. hm, still nothing useful ... +#: ../DistUpgrade/DistUpgradeController.py:734 +msgid "Generate default sources?" +msgstr "產生預設的來源?" + +#: ../DistUpgrade/DistUpgradeController.py:735 +#, python-format +msgid "" +"After scanning your 'sources.list' no valid entry for '%s' was found.\n" +"\n" +"Should default entries for '%s' be added? If you select 'No', the upgrade " +"will cancel." +msgstr "" +"在掃描您的「sources.list」後,沒找到與「%s」有效的項目。\n" +"\n" +"要新增「%s」的預設項目嗎?如果您選擇「否」則會取消升級。" + +#: ../DistUpgrade/DistUpgradeController.py:770 +msgid "Repository information invalid" +msgstr "套件庫資料無效" + +#: ../DistUpgrade/DistUpgradeController.py:771 +msgid "" +"Upgrading the repository information resulted in a invalid file so a bug " +"reporting process is being started." +msgstr "升級套件庫資訊導致檔案無效,因此正在啟動臭蟲回報程序" + +#: ../DistUpgrade/DistUpgradeController.py:778 +msgid "Third party sources disabled" +msgstr "已停用第三方來源" + +#: ../DistUpgrade/DistUpgradeController.py:779 +msgid "" +"Some third party entries in your sources.list were disabled. You can re-" +"enable them after the upgrade with the 'software-properties' tool or your " +"package manager." +msgstr "" +"sources.list 中的某些第三方項目已停用。以「軟體來源」工具或套件管理員升級後可" +"將之重新啟用。" + +#: ../DistUpgrade/DistUpgradeController.py:819 +msgid "Package in inconsistent state" +msgid_plural "Packages in inconsistent state" +msgstr[0] "套件在不一致狀態" + +#: ../DistUpgrade/DistUpgradeController.py:822 +#, python-format +msgid "" +"The package '%s' is in an inconsistent state and needs to be reinstalled, " +"but no archive can be found for it. Please reinstall the package manually or " +"remove it from the system." +msgid_plural "" +"The packages '%s' are in an inconsistent state and need to be reinstalled, " +"but no archive can be found for them. Please reinstall the packages manually " +"or remove them from the system." +msgstr[0] "" +"這個套件 '%s' 狀態不一致而需要重新安裝,但找不到它的套件。請手動地重新安裝套" +"件或將它由系統中移除。" + +#: ../DistUpgrade/DistUpgradeController.py:870 +msgid "Error during update" +msgstr "更新時發生錯誤" + +#: ../DistUpgrade/DistUpgradeController.py:871 +msgid "" +"A problem occurred during the update. This is usually some sort of network " +"problem, please check your network connection and retry." +msgstr "更新時發生錯誤。這可能是某些網路問題,試檢查網路連線後再試。" + +#. print("on_button_install_clicked") +#: ../DistUpgrade/DistUpgradeController.py:880 +#: ../UpdateManager/UpdateManager.py:757 +msgid "Not enough free disk space" +msgstr "磁碟空間不足" + +#: ../DistUpgrade/DistUpgradeController.py:881 +#, python-format +msgid "" +"The upgrade has aborted. The upgrade needs a total of %s free space on disk " +"'%s'. Please free at least an additional %s of disk space on '%s'. Empty " +"your trash and remove temporary packages of former installations using 'sudo " +"apt-get clean'." +msgstr "" +"已放棄升級。升級程序總共需要 %s 可用空間於磁碟「%s」。請釋放至少額外 %s 的磁" +"碟空間於磁碟「%s」。清理您的回收筒,並使用 'sudo apt-get clean' 來移除之前安" +"裝時的暫時性套件。" + +#. calc the dist-upgrade and see if the removals are ok/expected +#. do the dist-upgrade +#: ../DistUpgrade/DistUpgradeController.py:910 +#: ../DistUpgrade/DistUpgradeController.py:1692 +msgid "Calculating the changes" +msgstr "正在計算所有的更動" + +#. ask the user +#: ../DistUpgrade/DistUpgradeController.py:942 +msgid "Do you want to start the upgrade?" +msgstr "要開始升級嗎?" + +#: ../DistUpgrade/DistUpgradeController.py:1008 +msgid "Upgrade canceled" +msgstr "升級取消了" + +#: ../DistUpgrade/DistUpgradeController.py:1009 +msgid "" +"The upgrade will cancel now and the original system state will be restored. " +"You can resume the upgrade at a later time." +msgstr "現在將取消升級,並且還原至原始系統。您可以在之後繼續升級。" + +#: ../DistUpgrade/DistUpgradeController.py:1015 +#: ../DistUpgrade/DistUpgradeController.py:1149 +msgid "Could not download the upgrades" +msgstr "無法下載升級套件" + +#: ../DistUpgrade/DistUpgradeController.py:1016 +msgid "" +"The upgrade has aborted. Please check your Internet connection or " +"installation media and try again. All files downloaded so far have been kept." +msgstr "" +"升級已中止。請檢查您的網際網路連線,或安裝媒體並重試。所有目前已下載的檔案都" +"會被保留。" + +#. FIXME: strings are not good, but we are in string freeze +#. currently +#: ../DistUpgrade/DistUpgradeController.py:1100 +#: ../DistUpgrade/DistUpgradeController.py:1137 +#: ../DistUpgrade/DistUpgradeController.py:1242 +msgid "Error during commit" +msgstr "提交時發生錯誤" + +#. generate a new cache +#: ../DistUpgrade/DistUpgradeController.py:1102 +#: ../DistUpgrade/DistUpgradeController.py:1139 +#: ../DistUpgrade/DistUpgradeController.py:1281 +msgid "Restoring original system state" +msgstr "回復原有系統狀態" + +#: ../DistUpgrade/DistUpgradeController.py:1103 +#: ../DistUpgrade/DistUpgradeController.py:1118 +#: ../DistUpgrade/DistUpgradeController.py:1140 +msgid "Could not install the upgrades" +msgstr "無法安裝升級" + +#. invoke the frontend now and show a error message +#: ../DistUpgrade/DistUpgradeController.py:1108 +msgid "" +"The upgrade has aborted. Your system could be in an unusable state. A " +"recovery will run now (dpkg --configure -a)." +msgstr "" +"已放棄升級。您的系統可能處於不穩定狀態。現在將執行復原程序 (dpkg --configure " +"-a)。" + +#: ../DistUpgrade/DistUpgradeController.py:1113 +#, python-format +msgid "" +"\n" +"\n" +"Please report this bug in a browser at http://bugs.launchpad.net/ubuntu/" +"+source/update-manager/+filebug and attach the files in /var/log/dist-" +"upgrade/ to the bug report.\n" +"%s" +msgstr "" +"\n" +"\n" +"請透過瀏覽器回報此臭蟲於 http://bugs.launchpad.net/ubuntu/+source/update-" +"manager/+filebug ,並請附加 /var/log/dist-upgrade/ 內的檔案至臭蟲回報中。\n" +"%s" + +#: ../DistUpgrade/DistUpgradeController.py:1150 +msgid "" +"The upgrade has aborted. Please check your Internet connection or " +"installation media and try again. " +msgstr "已放棄升級。請檢查您的網際網路連線或安裝媒體,接著再試一次。 " + +#: ../DistUpgrade/DistUpgradeController.py:1230 +msgid "Remove obsolete packages?" +msgstr "移除廢棄的套件?" + +#: ../DistUpgrade/DistUpgradeController.py:1231 +#: ../DistUpgrade/DistUpgrade.ui.h:8 +msgid "_Keep" +msgstr "保留(_K)" + +#: ../DistUpgrade/DistUpgradeController.py:1231 +msgid "_Remove" +msgstr "移除(_R)" + +#: ../DistUpgrade/DistUpgradeController.py:1243 +msgid "" +"A problem occurred during the clean-up. Please see the below message for " +"more information. " +msgstr "在清理時發生一些問題。詳情請參閱以下訊息。 " + +#. FIXME: instead of error out, fetch and install it +#. here +#: ../DistUpgrade/DistUpgradeController.py:1319 +msgid "Required depends is not installed" +msgstr "必需的相依套件未被安裝" + +#: ../DistUpgrade/DistUpgradeController.py:1320 +#, python-format +msgid "The required dependency '%s' is not installed. " +msgstr "必需的相依套件‘%s’未被安裝。 " + +#. sanity check (check for ubuntu-desktop, brokenCache etc) +#. then open the cache (again) +#: ../DistUpgrade/DistUpgradeController.py:1588 +#: ../DistUpgrade/DistUpgradeController.py:1653 +msgid "Checking package manager" +msgstr "正在檢查套件管理員" + +#: ../DistUpgrade/DistUpgradeController.py:1593 +msgid "Preparing the upgrade failed" +msgstr "準備升級失敗" + +#: ../DistUpgrade/DistUpgradeController.py:1594 +msgid "" +"Preparing the system for the upgrade failed so a bug reporting process is " +"being started." +msgstr "準備系統以供升級的動作失敗,所以正在啟動臭蟲回報程序。" + +#: ../DistUpgrade/DistUpgradeController.py:1608 +msgid "Getting upgrade prerequisites failed" +msgstr "取得升級先決元件失敗" + +#: ../DistUpgrade/DistUpgradeController.py:1609 +msgid "" +"The system was unable to get the prerequisites for the upgrade. The upgrade " +"will abort now and restore the original system state.\n" +"\n" +"Additionally, a bug reporting process is being started." +msgstr "" +"本系統無法達到升級的先決條件。升級將立刻中止並且還原至原始系統狀態。\n" +"\n" +"此外,臭蟲回報程序正在啟動中。" + +#: ../DistUpgrade/DistUpgradeController.py:1637 +msgid "Updating repository information" +msgstr "正在更新套件庫資訊" + +#: ../DistUpgrade/DistUpgradeController.py:1644 +msgid "Failed to add the cdrom" +msgstr "無法加入光碟" + +#: ../DistUpgrade/DistUpgradeController.py:1645 +msgid "Sorry, adding the cdrom was not successful." +msgstr "很抱歉,沒有成功加入光碟。" + +#: ../DistUpgrade/DistUpgradeController.py:1673 +msgid "Invalid package information" +msgstr "無效的套件資訊" + +#: ../DistUpgrade/DistUpgradeController.py:1674 +msgid "After updating your package " +msgstr "" + +#: ../DistUpgrade/DistUpgradeController.py:1698 +#: ../DistUpgrade/DistUpgradeController.py:1750 +msgid "Fetching" +msgstr "接收中" + +#: ../DistUpgrade/DistUpgradeController.py:1704 +#: ../DistUpgrade/DistUpgradeController.py:1754 +msgid "Upgrading" +msgstr "升級中" + +#. don't abort here, because it would restore the sources.list +#: ../DistUpgrade/DistUpgradeController.py:1709 +#: ../DistUpgrade/DistUpgradeController.py:1756 +#: ../DistUpgrade/DistUpgradeController.py:1763 +#: ../DistUpgrade/DistUpgradeController.py:1774 +msgid "Upgrade complete" +msgstr "升級完成" + +#: ../DistUpgrade/DistUpgradeController.py:1710 +#: ../DistUpgrade/DistUpgradeController.py:1757 +#: ../DistUpgrade/DistUpgradeController.py:1764 +msgid "" +"The upgrade has completed but there were errors during the upgrade process." +msgstr "升級已經完成,但在升級過程中有發生錯誤。" + +#: ../DistUpgrade/DistUpgradeController.py:1717 +msgid "Searching for obsolete software" +msgstr "搜尋廢棄的軟體中" + +#: ../DistUpgrade/DistUpgradeController.py:1726 +msgid "System upgrade is complete." +msgstr "系統升級完成。" + +#: ../DistUpgrade/DistUpgradeController.py:1775 +msgid "The partial upgrade was completed." +msgstr "部份升級完成。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:204 +msgid "evms in use" +msgstr "有軟體正使用 evms" + +#: ../DistUpgrade/DistUpgradeQuirks.py:205 +msgid "" +"Your system uses the 'evms' volume manager in /proc/mounts. The 'evms' " +"software is no longer supported, please switch it off and run the upgrade " +"again when this is done." +msgstr "" +"您的系統於 /proc/mounts 使用 'evms' volume 管理程式。'evms' 軟體已不受支援," +"請將其關閉再執行升級工作。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:502 +msgid "Your graphics hardware may not be fully supported in Ubuntu 12.04 LTS." +msgstr "您的繪圖硬體可能無法被 Ubuntu 12.04 LTS 完整支援。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:504 +msgid "" +"The support in Ubuntu 12.04 LTS for your Intel graphics hardware is limited " +"and you may encounter problems after the upgrade. For more information see " +"https://wiki.ubuntu.com/X/Bugs/UpdateManagerWarningForI8xx Do you want to " +"continue with the upgrade?" +msgstr "" +"Ubuntu 12.04 LTS 對您的 Intel 繪圖硬體的支援有限,升級之後您可能會碰到一些問" +"題。更多資訊可以查看 \"https://wiki.ubuntu.com/X/Bugs/" +"UpdateManagerWarningForI8xx\"。您是否要繼續升級?" + +#: ../DistUpgrade/DistUpgradeQuirks.py:526 +#: ../DistUpgrade/DistUpgradeQuirks.py:554 +#: ../DistUpgrade/DistUpgradeQuirks.py:581 +msgid "" +"Upgrading may reduce desktop effects, and performance in games and other " +"graphically intensive programs." +msgstr "升級可能減低桌面特效和遊戲及其他著重圖形程式的表現。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:530 +#: ../DistUpgrade/DistUpgradeQuirks.py:558 +msgid "" +"This computer is currently using the NVIDIA 'nvidia' graphics driver. No " +"version of this driver is available that works with your video card in " +"Ubuntu 10.04 LTS.\n" +"\n" +"Do you want to continue?" +msgstr "" +"這個電腦目前正使用 NVIDIA 的「nvidia」圖形驅動程式。這個驅動程式沒有任何版本" +"可在 Ubuntu 10.04 LTS 中正常驅動您的硬體。\n" +"\n" +"是否要繼續?" + +#: ../DistUpgrade/DistUpgradeQuirks.py:585 +msgid "" +"This computer is currently using the AMD 'fglrx' graphics driver. No version " +"of this driver is available that works with your hardware in Ubuntu 10.04 " +"LTS.\n" +"\n" +"Do you want to continue?" +msgstr "" +"這個電腦目前正使用 AMD 的「fglrx」圖形驅動程式。這個驅動程式沒有任何版本可在 " +"Ubuntu 10.04 LTS 中正常驅動您的硬體。\n" +"\n" +"是否要繼續?" + +#: ../DistUpgrade/DistUpgradeQuirks.py:615 +msgid "No i686 CPU" +msgstr "無 i686 CPU" + +#: ../DistUpgrade/DistUpgradeQuirks.py:616 +msgid "" +"Your system uses an i586 CPU or a CPU that does not have the 'cmov' " +"extension. All packages were built with optimizations requiring i686 as the " +"minimal architecture. It is not possible to upgrade your system to a new " +"Ubuntu release with this hardware." +msgstr "" +"您的系統使用 i586 CPU,或是不支援 'cmov' 擴充功能的 CPU。所有套件都以 i686 架" +"構為最佳化目標來建置,所以 CPU 至少需要有 i686 等級。對於您目前的硬體來說,您" +"無法將系統升級到最新的 Ubuntu 發行。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:652 +msgid "No ARMv6 CPU" +msgstr "無 ARMv6 處理器" + +#: ../DistUpgrade/DistUpgradeQuirks.py:653 +msgid "" +"Your system uses an ARM CPU that is older than the ARMv6 architecture. All " +"packages in karmic were built with optimizations requiring ARMv6 as the " +"minimal architecture. It is not possible to upgrade your system to a new " +"Ubuntu release with this hardware." +msgstr "" +"您系統使用的 ARM 處理器舊於 ARMv6 架構。所有 karmic 套件都是以 ARMv6 為最低架" +"構優化建造的。所以伴隨此硬體無法升級您的系統到新的 Ubuntu 版本。" + +#: ../DistUpgrade/DistUpgradeQuirks.py:673 +msgid "No init available" +msgstr "無法初始化" + +#: ../DistUpgrade/DistUpgradeQuirks.py:674 +msgid "" +"Your system appears to be a virtualised environment without an init daemon, " +"e.g. Linux-VServer. Ubuntu 10.04 LTS cannot function within this type of " +"environment, requiring an update to your virtual machine configuration " +"first.\n" +"\n" +"Are you sure you want to continue?" +msgstr "" +"您的系統似乎處於虛擬化環境且無初始化程序,例如 Linux-VServer。Ubuntu 10.04 " +"LTS 無法於此環境執行,需要先更新您的虛擬機器設定。\n" +"\n" +"您確定想要繼續?" + +#: ../DistUpgrade/DistUpgradeMain.py:65 +msgid "Sandbox upgrade using aufs" +msgstr "使用 aufs 作為沙堆升級" + +#: ../DistUpgrade/DistUpgradeMain.py:67 +msgid "Use the given path to search for a cdrom with upgradable packages" +msgstr "使用指定路徑搜尋附有升級套件的光碟" + +#: ../DistUpgrade/DistUpgradeMain.py:73 +msgid "" +"Use frontend. Currently available: \n" +"DistUpgradeViewText, DistUpgradeViewGtk, DistUpgradeViewKDE" +msgstr "" +"使用前端介面。可供選擇的有: \n" +"DistUpgradeViewText (純文字), DistUpgradeViewGtk (GTK+), DistUpgradeViewKDE " +"(KDE)" + +#: ../DistUpgrade/DistUpgradeMain.py:76 +msgid "*DEPRECATED* this option will be ignored" +msgstr "*已棄用* 這個選項會被忽略" + +#: ../DistUpgrade/DistUpgradeMain.py:79 +msgid "Perform a partial upgrade only (no sources.list rewriting)" +msgstr "只進行部份升級 (無須修改 sources.list)" + +#: ../DistUpgrade/DistUpgradeMain.py:82 +msgid "Disable GNU screen support" +msgstr "停用 GNU 螢幕支援" + +#: ../DistUpgrade/DistUpgradeMain.py:84 +msgid "Set datadir" +msgstr "設定資料目錄" + +#. print("mediaChange %s %s" % (medium, drive)) +#: ../DistUpgrade/DistUpgradeViewGtk.py:114 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:117 +#: ../DistUpgrade/DistUpgradeViewKDE.py:195 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:155 +#, python-format +msgid "Please insert '%s' into the drive '%s'" +msgstr "請將‘%s’放入光碟機‘%s’" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:135 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:138 +#: ../DistUpgrade/DistUpgradeViewKDE.py:209 +msgid "Fetching is complete" +msgstr "接收完成" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:146 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:149 +#: ../DistUpgrade/DistUpgradeViewKDE.py:222 +#, python-format +msgid "Fetching file %li of %li at %sB/s" +msgstr "接收第 %li 個檔案 (共 %li 個檔案,速度為 %sB/s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:149 +#: ../DistUpgrade/DistUpgradeViewGtk.py:296 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:152 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:309 +#: ../DistUpgrade/DistUpgradeViewKDE.py:223 +#: ../DistUpgrade/DistUpgradeViewKDE.py:371 +#, python-format +msgid "About %s remaining" +msgstr "大約剩下 %s" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:152 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:155 +#: ../DistUpgrade/DistUpgradeViewKDE.py:225 +#, python-format +msgid "Fetching file %li of %li" +msgstr "接收第 %li 個檔案 (共 %li 個)" + +#. FIXME: add support for the timeout +#. of the terminal (to display something useful then) +#. -> longer term, move this code into python-apt +#: ../DistUpgrade/DistUpgradeViewGtk.py:183 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:186 +#: ../DistUpgrade/DistUpgradeViewKDE.py:262 +msgid "Applying changes" +msgstr "正在套用變更" + +#. we do not report followup errors from earlier failures +#: ../DistUpgrade/DistUpgradeViewGtk.py:208 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:212 +#: ../DistUpgrade/DistUpgradeViewKDE.py:275 +msgid "dependency problems - leaving unconfigured" +msgstr "相依問題 - 保留為未設定" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:213 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:217 +#: ../DistUpgrade/DistUpgradeViewKDE.py:277 +#, python-format +msgid "Could not install '%s'" +msgstr "無法安裝‘%s’" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:214 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:218 +#: ../DistUpgrade/DistUpgradeViewKDE.py:278 +#, python-format +msgid "" +"The upgrade will continue but the '%s' package may not be in a working " +"state. Please consider submitting a bug report about it." +msgstr "" +"此次更新將繼續但是 '%s' 套件可能無法運作。請考慮提交關於該套件的臭蟲報告。" + +#. self.expander.set_expanded(True) +#: ../DistUpgrade/DistUpgradeViewGtk.py:231 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:235 +#: ../DistUpgrade/DistUpgradeViewKDE.py:299 +#, python-format +msgid "" +"Replace the customized configuration file\n" +"'%s'?" +msgstr "" +"是否要覆蓋自訂的設定檔案\n" +"‘%s’?" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:232 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:236 +#: ../DistUpgrade/DistUpgradeViewKDE.py:300 +msgid "" +"You will lose any changes you have made to this configuration file if you " +"choose to replace it with a newer version." +msgstr "如果選擇以新版覆蓋,那麼將會失去您改變過的設定。" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:251 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:256 +#: ../DistUpgrade/DistUpgradeViewKDE.py:323 +msgid "The 'diff' command was not found" +msgstr "找不到‘diff’指令" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:464 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:477 +#: ../DistUpgrade/DistUpgradeViewText.py:92 +msgid "A fatal error occurred" +msgstr "發生嚴重錯誤" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:465 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:478 +msgid "" +"Please report this as a bug (if you haven't already) and include the files /" +"var/log/dist-upgrade/main.log and /var/log/dist-upgrade/apt.log in your " +"report. The upgrade has aborted.\n" +"Your original sources.list was saved in /etc/apt/sources.list.distUpgrade." +msgstr "" +"請回報此錯誤 (若您尚未回報) 並將檔案 /var/log/dist-upgrade/main.log 與 /var/" +"log/dist-upgrade/apt.log 附在您的報告中。升級程序已取消。\n" +"您原先的 sources.list 儲存於 /etc/apt/sources.list.distUpgrade。" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:482 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:495 +msgid "Ctrl-c pressed" +msgstr "按下 Ctrl+c" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:483 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:496 +msgid "" +"This will abort the operation and may leave the system in a broken state. " +"Are you sure you want to do that?" +msgstr "操作將被中止。這可能會造成系統的不完整。你確定要進行嗎?" + +#. append warning +#: ../DistUpgrade/DistUpgradeViewGtk.py:631 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:629 +msgid "To prevent data loss close all open applications and documents." +msgstr "為避免遺失資料,請關閉所有開啟的程式及文件。" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:645 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:643 +#, python-format +msgid "No longer supported by Canonical (%s)" +msgstr "不再受 Canonical 支援 (%s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:646 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:644 +#, python-format +msgid "Downgrade (%s)" +msgstr "降級 (%s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:647 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:645 +#, python-format +msgid "Remove (%s)" +msgstr "移除 (%s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:648 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:646 +#, python-format +msgid "No longer needed (%s)" +msgstr "不再需要 (%s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:649 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:647 +#, python-format +msgid "Install (%s)" +msgstr "安裝 (%s)" + +#: ../DistUpgrade/DistUpgradeViewGtk.py:650 +#: ../DistUpgrade/DistUpgradeViewGtk3.py:648 +#, python-format +msgid "Upgrade (%s)" +msgstr "升級 (%s)" + +#. change = QMessageBox.question(None, _("Media Change"), msg, QMessageBox.Ok, QMessageBox.Cancel) +#: ../DistUpgrade/DistUpgradeViewKDE.py:196 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:157 +msgid "Media Change" +msgstr "媒體變更" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:335 +msgid "Show Difference >>>" +msgstr "顯示差異 >>>" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:338 +msgid "<<< Hide Difference" +msgstr "<<< 隱藏差異" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:554 +msgid "Error" +msgstr "錯誤" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:568 +msgid "&Cancel" +msgstr "取消(&C)" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:572 +#: ../DistUpgrade/DistUpgradeViewKDE.py:813 +msgid "&Close" +msgstr "關閉(&C)" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:618 +msgid "Show Terminal >>>" +msgstr "顯示終端畫面 >>>" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:621 +msgid "<<< Hide Terminal" +msgstr "<<< 隱藏終端畫面" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:701 +msgid "Information" +msgstr "資訊" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:751 +#: ../DistUpgrade/DistUpgradeViewKDE.py:796 +#: ../DistUpgrade/DistUpgradeViewKDE.py:799 ../DistUpgrade/DistUpgrade.ui.h:7 +msgid "Details" +msgstr "詳情" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:777 +#, python-format +msgid "No longer supported %s" +msgstr "不再支援 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:779 +#, python-format +msgid "Remove %s" +msgstr "移除 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:781 +#: ../DistUpgrade/DistUpgradeViewText.py:182 +#, python-format +msgid "Remove (was auto installed) %s" +msgstr "移除 (曾是自動安裝的) %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:783 +#, python-format +msgid "Install %s" +msgstr "安裝 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:785 +#, python-format +msgid "Upgrade %s" +msgstr "升級 %s" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:809 +#: ../DistUpgrade/DistUpgradeViewText.py:230 +msgid "Restart required" +msgstr "需要重新開機" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:809 +msgid "Restart the system to complete the upgrade" +msgstr "重新啟動系統以完成更新" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:812 ../DistUpgrade/DistUpgrade.ui.h:14 +#: ../data/gtkbuilder/UpdateManager.ui.h:26 +msgid "_Restart Now" +msgstr "現在重新啟動(_R)" + +#. FIXME make this user friendly +#: ../DistUpgrade/DistUpgradeViewKDE.py:830 +msgid "" +"Cancel the running upgrade?\n" +"\n" +"The system could be in an unusable state if you cancel the upgrade. You are " +"strongly advised to resume the upgrade." +msgstr "" +"是否取消正進行的升級?\n" +"\n" +"如果您取消升級,系統可能會在不穩定的狀態。強烈建議您繼續升級工作。" + +#: ../DistUpgrade/DistUpgradeViewKDE.py:834 +msgid "Cancel Upgrade?" +msgstr "要取消升級嗎?" + +#: ../DistUpgrade/DistUpgradeView.py:61 +#, python-format +msgid "%li day" +msgid_plural "%li days" +msgstr[0] "%li 日" + +#: ../DistUpgrade/DistUpgradeView.py:63 +#, python-format +msgid "%li hour" +msgid_plural "%li hours" +msgstr[0] "%li 小時" + +#: ../DistUpgrade/DistUpgradeView.py:65 +#, python-format +msgid "%li minute" +msgid_plural "%li minutes" +msgstr[0] "%li 分鐘" + +#: ../DistUpgrade/DistUpgradeView.py:66 +#, python-format +msgid "%li second" +msgid_plural "%li seconds" +msgstr[0] "%li 秒" + +#. TRANSLATORS: you can alter the ordering of the remaining time +#. information here if you shuffle %(str_days)s %(str_hours)s %(str_minutes)s +#. around. Make sure to keep all '$(str_*)s' in the translated string +#. and do NOT change anything appart from the ordering. +#. +#. %(str_hours)s will be either "1 hour" or "2 hours" depending on the +#. plural form +#. +#. Note: most western languages will not need to change this +#: ../DistUpgrade/DistUpgradeView.py:82 +#, python-format +msgid "%(str_days)s %(str_hours)s" +msgstr "%(str_days)s %(str_hours)s" + +#. TRANSLATORS: you can alter the ordering of the remaining time +#. information here if you shuffle %(str_hours)s %(str_minutes)s +#. around. Make sure to keep all '$(str_*)s' in the translated string +#. and do NOT change anything appart from the ordering. +#. +#. %(str_hours)s will be either "1 hour" or "2 hours" depending on the +#. plural form +#. +#. Note: most western languages will not need to change this +#: ../DistUpgrade/DistUpgradeView.py:100 +#, python-format +msgid "%(str_hours)s %(str_minutes)s" +msgstr "%(str_hours)s又 %(str_minutes)s" + +#. 56 kbit +#. 1Mbit = 1024 kbit +#: ../DistUpgrade/DistUpgradeView.py:151 +#, python-format +msgid "" +"This download will take about %s with a 1Mbit DSL connection and about %s " +"with a 56k modem." +msgstr "這次下載所需時間在 1M DSL 連線大約要 %s,用 56k 數據機大約要 %s。" + +#. if we have a estimated speed, use it +#: ../DistUpgrade/DistUpgradeView.py:155 +#, python-format +msgid "This download will take about %s with your connection. " +msgstr "依照您的連線速率,此下載將要約 %s 的時間。 " + +#. Declare these translatable strings from the .ui files here so that +#. xgettext picks them up. +#: ../DistUpgrade/DistUpgradeView.py:259 ../DistUpgrade/DistUpgrade.ui.h:21 +msgid "Preparing to upgrade" +msgstr "準備升級中" + +#: ../DistUpgrade/DistUpgradeView.py:260 +msgid "Getting new software channels" +msgstr "正取得新軟體頻道" + +#: ../DistUpgrade/DistUpgradeView.py:261 ../DistUpgrade/DistUpgrade.ui.h:23 +msgid "Getting new packages" +msgstr "取得新套件" + +#: ../DistUpgrade/DistUpgradeView.py:262 ../DistUpgrade/DistUpgrade.ui.h:26 +msgid "Installing the upgrades" +msgstr "安裝升級中" + +#: ../DistUpgrade/DistUpgradeView.py:263 ../DistUpgrade/DistUpgrade.ui.h:25 +msgid "Cleaning up" +msgstr "清理中" + +#: ../DistUpgrade/DistUpgradeView.py:348 +#, python-format +msgid "" +"%(amount)d installed package is no longer supported by Canonical. You can " +"still get support from the community." +msgid_plural "" +"%(amount)d installed packages are no longer supported by Canonical. You can " +"still get support from the community." +msgstr[0] "" +"%(amount)d 個已安裝套件不再受 Canonical 支援。您仍可以取得來自社群的支援。" + +#. FIXME: make those two separate lines to make it clear +#. that the "%" applies to the result of ngettext +#: ../DistUpgrade/DistUpgradeView.py:357 +#, python-format +msgid "%d package is going to be removed." +msgid_plural "%d packages are going to be removed." +msgstr[0] "即將移除 %d 個套件。" + +#: ../DistUpgrade/DistUpgradeView.py:362 +#, python-format +msgid "%d new package is going to be installed." +msgid_plural "%d new packages are going to be installed." +msgstr[0] "即將安裝 %d 個新套件。" + +#: ../DistUpgrade/DistUpgradeView.py:368 +#, python-format +msgid "%d package is going to be upgraded." +msgid_plural "%d packages are going to be upgraded." +msgstr[0] "即將升級 %d 個套件。" + +#: ../DistUpgrade/DistUpgradeView.py:373 +#, python-format +msgid "" +"\n" +"\n" +"You have to download a total of %s. " +msgstr "" +"\n" +"\n" +"要下載共%s。 " + +#: ../DistUpgrade/DistUpgradeView.py:378 +msgid "" +"Installing the upgrade can take several hours. Once the download has " +"finished, the process cannot be canceled." +msgstr "安裝升級可能會花上幾個鐘頭。一旦下載完成,程序便無法取消。" + +#: ../DistUpgrade/DistUpgradeView.py:382 +msgid "" +"Fetching and installing the upgrade can take several hours. Once the " +"download has finished, the process cannot be canceled." +msgstr "升級的擷取與安裝可能花上數個鐘頭。一旦下載完成,升級程序無法取消。" + +#: ../DistUpgrade/DistUpgradeView.py:387 +msgid "Removing the packages can take several hours. " +msgstr "移除套件可能會花上幾個鐘頭。 " + +#. FIXME: this should go into DistUpgradeController +#: ../DistUpgrade/DistUpgradeView.py:392 ../UpdateManager/UpdateManager.py:676 +msgid "The software on this computer is up to date." +msgstr "這臺電腦上的軟體已處最新狀態。" + +#: ../DistUpgrade/DistUpgradeView.py:393 +msgid "" +"There are no upgrades available for your system. The upgrade will now be " +"canceled." +msgstr "您的系統已在最新狀態。現在將取消升級動作。" + +#: ../DistUpgrade/DistUpgradeView.py:406 +msgid "Reboot required" +msgstr "需要重新開機" + +#: ../DistUpgrade/DistUpgradeView.py:407 +msgid "" +"The upgrade is finished and a reboot is required. Do you want to do this now?" +msgstr "升級已經完成並需要重新開機。是否現在重新開機?" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:72 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:72 +#, python-format +msgid "authenticate '%(file)s' against '%(signature)s' " +msgstr "以「%(signature)s」驗證「%(file)s」 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:131 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:131 +#, python-format +msgid "extracting '%s'" +msgstr "正在抽出「%s」" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:151 +#: ../DistUpgrade/DistUpgradeFetcherCore.py:152 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:151 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:152 +msgid "Could not run the upgrade tool" +msgstr "無法執行升級工具" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:152 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:152 +msgid "" +"This is most likely a bug in the upgrade tool. Please report it as a bug " +"using the command 'ubuntu-bug update-manager'." +msgstr "" +"這可能是升級工具的錯誤,請使用指令「ubuntu-bug update-manager」回報錯誤。" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:227 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:227 +msgid "Upgrade tool signature" +msgstr "升級工具簽署" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:234 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:234 +msgid "Upgrade tool" +msgstr "升級工具" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:268 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:268 +msgid "Failed to fetch" +msgstr "接收失敗" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:269 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:269 +msgid "Fetching the upgrade failed. There may be a network problem. " +msgstr "接收升級套件失敗。可能是網路問題。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:273 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:273 +msgid "Authentication failed" +msgstr "認證失敗" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:274 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:274 +msgid "" +"Authenticating the upgrade failed. There may be a problem with the network " +"or with the server. " +msgstr "認證升級套件失敗。可能是因為跟伺服器的網路連線出現問題。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:279 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:279 +msgid "Failed to extract" +msgstr "解壓失敗" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:280 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:280 +msgid "" +"Extracting the upgrade failed. There may be a problem with the network or " +"with the server. " +msgstr "升級套件解壓失敗。可能是因為網路或伺服器出現問題。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:285 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:285 +msgid "Verification failed" +msgstr "檢驗失敗" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:286 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:286 +msgid "" +"Verifying the upgrade failed. There may be a problem with the network or " +"with the server. " +msgstr "檢驗升級套件失敗。可能是因為網路或伺服器出現問題。 " + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:300 +#: ../DistUpgrade/DistUpgradeFetcherCore.py:306 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:300 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:306 +msgid "Can not run the upgrade" +msgstr "不能進行升級" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:301 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:301 +msgid "" +"This usually is caused by a system where /tmp is mounted noexec. Please " +"remount without noexec and run the upgrade again." +msgstr "" +"這通常是由使用 noexec 掛載 /tmp 的系統引致的。請不要使用 noexec 重新掛載,並" +"再次進行升級。" + +#: ../DistUpgrade/DistUpgradeFetcherCore.py:307 +#: ../UpdateManager/Core/DistUpgradeFetcherCore.py:307 +#, python-format +msgid "The error message is '%s'." +msgstr "錯誤訊息 '%s'。" + +#: ../DistUpgrade/DistUpgradeViewText.py:93 +msgid "" +"Please report this as a bug and include the files /var/log/dist-upgrade/main." +"log and /var/log/dist-upgrade/apt.log in your report. The upgrade has " +"aborted.\n" +"Your original sources.list was saved in /etc/apt/sources.list.distUpgrade." +msgstr "" +"請回報此錯誤並將檔案 /var/log/dist-upgrade/main.log 與 /var/log/dist-upgrade/" +"apt.log 附在您的報告中。升級程序已取消。\n" +"您原先的 sources.list 儲存於 /etc/apt/sources.list.distUpgrade。" + +#: ../DistUpgrade/DistUpgradeViewText.py:117 +msgid "Aborting" +msgstr "正在中止" + +#: ../DistUpgrade/DistUpgradeViewText.py:122 +msgid "Demoted:\n" +msgstr "降級:\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:129 +msgid "To continue please press [ENTER]" +msgstr "若要繼續請按 [ENTER]" + +#: ../DistUpgrade/DistUpgradeViewText.py:157 +#: ../DistUpgrade/DistUpgradeViewText.py:196 +#: ../DistUpgrade/DistUpgradeViewText.py:203 +msgid "Continue [yN] " +msgstr "繼續 [yN] " + +#: ../DistUpgrade/DistUpgradeViewText.py:157 +#: ../DistUpgrade/DistUpgradeViewText.py:196 +msgid "Details [d]" +msgstr "詳情 [d]" + +#. TRANSLATORS: the "y" is "yes" +#. TRANSLATORS: first letter of a positive (yes) answer +#: ../DistUpgrade/DistUpgradeViewText.py:162 +#: ../DistUpgrade/DistUpgradeViewText.py:206 +msgid "y" +msgstr "y" + +#. TRANSLATORS: the "n" is "no" +#. TRANSLATORS: first letter of a negative (no) answer +#: ../DistUpgrade/DistUpgradeViewText.py:165 +#: ../DistUpgrade/DistUpgradeViewText.py:213 +msgid "n" +msgstr "n" + +#. TRANSLATORS: the "d" is "details" +#: ../DistUpgrade/DistUpgradeViewText.py:168 +msgid "d" +msgstr "d" + +#: ../DistUpgrade/DistUpgradeViewText.py:173 +#, python-format +msgid "No longer supported: %s\n" +msgstr "不再支援:%s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:178 +#, python-format +msgid "Remove: %s\n" +msgstr "移除: %s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:188 +#, python-format +msgid "Install: %s\n" +msgstr "安裝: %s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:193 +#, python-format +msgid "Upgrade: %s\n" +msgstr "升級: %s\n" + +#: ../DistUpgrade/DistUpgradeViewText.py:210 +msgid "Continue [Yn] " +msgstr "繼續 [Yn] " + +#: ../DistUpgrade/DistUpgradeViewText.py:231 +msgid "" +"To finish the upgrade, a restart is required.\n" +"If you select 'y' the system will be restarted." +msgstr "" +"需要重新開機才能完成升級。\n" +"如果您選擇「y」系統將會重新開機。" + +#: ../DistUpgrade/DistUpgrade.ui.h:1 +msgid "_Cancel Upgrade" +msgstr "取消升級(_C)" + +#: ../DistUpgrade/DistUpgrade.ui.h:2 +msgid "_Resume Upgrade" +msgstr "繼續升級(_R)" + +#: ../DistUpgrade/DistUpgrade.ui.h:3 +msgid "" +"Cancel the running upgrade?\n" +"\n" +"The system could be in an unusable state if you cancel the upgrade. You are " +"strongly adviced to resume the upgrade." +msgstr "" +"是否取消正執行的升級?\n" +"\n" +"如果取消升級可能會導致系統不穩定。強烈建議您繼續升級。" + +#: ../DistUpgrade/DistUpgrade.ui.h:6 +msgid "_Start Upgrade" +msgstr "開始升級(_S)" + +#: ../DistUpgrade/DistUpgrade.ui.h:9 +msgid "_Replace" +msgstr "取代(_R)" + +#: ../DistUpgrade/DistUpgrade.ui.h:10 +msgid "Difference between the files" +msgstr "檔案間的差別" + +#: ../DistUpgrade/DistUpgrade.ui.h:11 +msgid "_Report Bug" +msgstr "回報錯誤(_R)" + +#: ../DistUpgrade/DistUpgrade.ui.h:12 +msgid "_Continue" +msgstr "繼續(_C)" + +#: ../DistUpgrade/DistUpgrade.ui.h:13 +msgid "Start the upgrade?" +msgstr "開始升級嗎?" + +#: ../DistUpgrade/DistUpgrade.ui.h:15 +msgid "" +"Restart the system to complete the upgrade\n" +"\n" +"Please save your work before continuing." +msgstr "" +"重新啟動系統以完成升級\n" +"\n" +"請在繼續前先儲存您的作業。" + +#: ../DistUpgrade/DistUpgrade.ui.h:18 +msgid "Distribution Upgrade" +msgstr "發行版升級" + +#: ../DistUpgrade/DistUpgrade.ui.h:19 +#, fuzzy +msgid "Upgrading Ubuntu to version 12.10" +msgstr "將 Ubuntu 升級至 11.10 版" + +#: ../DistUpgrade/DistUpgrade.ui.h:20 +msgid " " +msgstr " " + +#: ../DistUpgrade/DistUpgrade.ui.h:22 +msgid "Setting new software channels" +msgstr "設定新軟體頻道" + +#: ../DistUpgrade/DistUpgrade.ui.h:24 +msgid "Restarting the computer" +msgstr "重新啟動系統" + +#: ../DistUpgrade/DistUpgrade.ui.h:27 +msgid "Terminal" +msgstr "終端" + +#: ../UpdateManager/backend/InstallBackendSynaptic.py:64 +msgid "Please wait, this can take some time." +msgstr "請稍候,這需要一點時間。" + +#: ../UpdateManager/backend/InstallBackendSynaptic.py:66 +msgid "Update is complete" +msgstr "更新完成" + +#: ../UpdateManager/DistUpgradeFetcher.py:114 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:109 +msgid "Could not find the release notes" +msgstr "找不到發行公告" + +#: ../UpdateManager/DistUpgradeFetcher.py:115 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:110 +msgid "The server may be overloaded. " +msgstr "伺服器可能負荷過重。 " + +#: ../UpdateManager/DistUpgradeFetcher.py:125 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:114 +msgid "Could not download the release notes" +msgstr "無法下載發行公告" + +#: ../UpdateManager/DistUpgradeFetcher.py:126 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:115 +msgid "Please check your internet connection." +msgstr "請檢查您的網際網路連線。" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:68 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:91 +msgid "Upgrade" +msgstr "升級" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:95 +#: ../data/gtkbuilder/UpdateManager.ui.h:20 +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:2 +msgid "Release Notes" +msgstr "發行公告" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:134 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:148 +#: ../UpdateManager/DistUpgradeFetcherKDE.py:150 +msgid "Downloading additional package files..." +msgstr "正在下載額外的套件檔案..." + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:148 +#, python-format +msgid "File %s of %s at %sB/s" +msgstr "檔案 %s / %s (速度:%s 位元組/秒)" + +#: ../UpdateManager/DistUpgradeFetcherKDE.py:150 +#, python-format +msgid "File %s of %s" +msgstr "檔案 %s / %s" + +#: ../UpdateManager/ChangelogViewer.py:75 +msgid "Open Link in Browser" +msgstr "用瀏覽器開啟連結" + +#: ../UpdateManager/ChangelogViewer.py:78 +msgid "Copy Link to Clipboard" +msgstr "複製連結至剪貼簿" + +#: ../UpdateManager/GtkProgress.py:162 +#, python-format +msgid "Downloading file %(current)li of %(total)li with %(speed)s/s" +msgstr "共 %(total)li 個檔案,正下載第 %(current)li 個 (速度: %(speed)s/秒)" + +#: ../UpdateManager/GtkProgress.py:167 +#, python-format +msgid "Downloading file %(current)li of %(total)li" +msgstr "共 %(total)li 個檔案,正下載第 %(current)li 個" + +#: ../UpdateManager/UpdateManager.py:106 ../do-release-upgrade:100 +msgid "Your Ubuntu release is not supported anymore." +msgstr "您的 Ubuntu 發行版本已經不再支援。" + +#: ../UpdateManager/UpdateManager.py:107 +msgid "" +"You will not get any further security fixes or critical updates. Please " +"upgrade to a later version of Ubuntu." +msgstr "您將無法再取得安全性修正或重大更新。請升級至較新版本的 Ubuntu。" + +#: ../UpdateManager/UpdateManager.py:115 +msgid "Upgrade information" +msgstr "升級資訊" + +#: ../UpdateManager/UpdateManager.py:233 +#: ../UpdateManagerText/UpdateManagerText.py:35 +msgid "Install" +msgstr "安裝" + +#: ../UpdateManager/UpdateManager.py:235 +msgid "Name" +msgstr "名稱" + +#. upload_archive = version_match.group(2).strip() +#: ../UpdateManager/UpdateManager.py:395 +#, python-format +msgid "Version %s: \n" +msgstr "版本 %s: \n" + +#: ../UpdateManager/UpdateManager.py:453 +msgid "" +"No network connection detected, you can not download changelog information." +msgstr "未偵測到網路連線,您無法下載更動紀錄資訊。" + +#: ../UpdateManager/UpdateManager.py:463 +msgid "Downloading list of changes..." +msgstr "正下載更動清單..." + +#: ../UpdateManager/UpdateManager.py:507 +msgid "_Deselect All" +msgstr "全部不選(_D)" + +#: ../UpdateManager/UpdateManager.py:513 +msgid "Select _All" +msgstr "全部選取(_A)" + +#: ../UpdateManager/UpdateManager.py:572 +#, python-format +msgid "%s will be downloaded." +msgstr "將下載 %s。" + +#: ../UpdateManager/UpdateManager.py:584 +#, fuzzy +msgid "The update has already been downloaded." +msgid_plural "The updates have already been downloaded." +msgstr[0] "更新已經下載,但尚未安裝。" + +#: ../UpdateManager/UpdateManager.py:589 +msgid "There are no updates to install." +msgstr "沒有要安裝的更新。" + +#: ../UpdateManager/UpdateManager.py:598 +msgid "Unknown download size." +msgstr "未知下載大小。" + +#: ../UpdateManager/UpdateManager.py:624 +msgid "" +"It is unknown when the package information was updated last. Please click " +"the 'Check' button to update the information." +msgstr "未知套件資訊的上次更新時間。請點擊「檢查」按鈕來更新資訊。" + +#: ../UpdateManager/UpdateManager.py:630 +#, python-format +msgid "" +"The package information was last updated %(days_ago)s days ago.\n" +"Press the 'Check' button below to check for new software updates." +msgstr "" +"%(days_ago)s 天前更新過套件資訊。\n" +"請按下方「檢查」鈕看看有否新資訊。" + +#: ../UpdateManager/UpdateManager.py:635 +#, python-format +msgid "The package information was last updated %(days_ago)s day ago." +msgid_plural "The package information was last updated %(days_ago)s days ago." +msgstr[0] "%(days_ago)s 天前更新過套件資訊。" + +#: ../UpdateManager/UpdateManager.py:639 +#, python-format +msgid "The package information was last updated %(hours_ago)s hour ago." +msgid_plural "" +"The package information was last updated %(hours_ago)s hours ago." +msgstr[0] "%(hours_ago)s 小時前更新過套件資訊。" + +#. TRANSLATORS: only in plural form, as %s minutes ago is one of 15, 30, 45 minutes ago +#: ../UpdateManager/UpdateManager.py:644 ../UpdateManager/UpdateManager.py:646 +#: ../UpdateManager/UpdateManager.py:648 +#, python-format +msgid "The package information was last updated about %s minutes ago." +msgstr "套件資訊約 %s 分鐘前更新。" + +#: ../UpdateManager/UpdateManager.py:650 +msgid "The package information was just updated." +msgstr "套件資訊剛更新。" + +#: ../UpdateManager/UpdateManager.py:689 +msgid "Software updates may be available for your computer." +msgstr "可能有軟體更新提供予閣下之電腦。" + +#: ../UpdateManager/UpdateManager.py:697 +#, python-format +msgid "" +"Updated software has been issued since %s was released. Do you want to " +"install it now?" +msgstr "" + +#: ../UpdateManager/UpdateManager.py:700 +msgid "" +"Updated software is available for this computer. Do you want to install it " +"now?" +msgstr "" + +#: ../UpdateManager/UpdateManager.py:758 +#, python-format +msgid "" +"The upgrade needs a total of %s free space on disk '%s'. Please free at " +"least an additional %s of disk space on '%s'. Empty your trash and remove " +"temporary packages of former installations using 'sudo apt-get clean'." +msgstr "" +"升級工作需要總共 %s 可用空間於硬碟 ‘%s’。請額外空出最少 %s 的空間於 ‘%s’。清" +"理清理您的回收筒或使用 ‘sudo apt-get clean’ 移除上次安裝的暫存套件。" + +#: ../UpdateManager/UpdateManager.py:783 +msgid "" +"The computer needs to restart to finish installing updates. Please save your " +"work before continuing." +msgstr "必須重啟電腦以完成安裝更新。繼續前請先儲存工作。" + +#: ../UpdateManager/UpdateManager.py:847 +msgid "Reading package information" +msgstr "正在讀取套件資訊" + +#: ../UpdateManager/UpdateManager.py:862 +msgid "Connecting..." +msgstr "連線中..." + +#: ../UpdateManager/UpdateManager.py:879 +msgid "You may not be able to check for updates or download new updates." +msgstr "您可能無法檢查是否有更新,或是下載新的更新。" + +#: ../UpdateManager/UpdateManager.py:1002 +msgid "Could not initialize the package information" +msgstr "無法初始化套件資訊" + +#: ../UpdateManager/UpdateManager.py:1003 +msgid "" +"An unresolvable problem occurred while initializing the package " +"information.\n" +"\n" +"Please report this bug against the 'update-manager' package and include the " +"following error message:\n" +msgstr "" +"初始套件資訊時發生不能解決的問題。\n" +"\n" +"請匯報此為『update-manager』套件的問題並附上以下的錯誤訊息:\n" + +#: ../UpdateManager/UpdateManager.py:1032 +msgid "" +"An unresolvable problem occurred while calculating the upgrade.\n" +"\n" +"Please report this bug against the 'update-manager' package and include the " +"following error message:" +msgstr "" +"計算升級時發生不能解決的問題。\n" +"\n" +"請匯報此為『update-manager』套件的問題並附上以下的錯誤訊息:" + +#: ../UpdateManager/UpdateManager.py:1056 +msgid " (New install)" +msgstr " (新安裝)" + +#. TRANSLATORS: the b stands for Bytes +#: ../UpdateManager/UpdateManager.py:1063 +#, python-format +msgid "(Size: %s)" +msgstr "(大小:%s)" + +#: ../UpdateManager/UpdateManager.py:1067 +#, python-format +msgid "From version %(old_version)s to %(new_version)s" +msgstr "由版本 %(old_version)s 更新至 %(new_version)s" + +#: ../UpdateManager/UpdateManager.py:1071 +#, python-format +msgid "Version %s" +msgstr "版本 %s" + +#: ../UpdateManager/UpdateManager.py:1104 ../do-release-upgrade:112 +msgid "Release upgrade not possible right now" +msgstr "目前不能升級發行版" + +#: ../UpdateManager/UpdateManager.py:1105 ../do-release-upgrade:113 +#, c-format, python-format +msgid "" +"The release upgrade can not be performed currently, please try again later. " +"The server reported: '%s'" +msgstr "發行版升級升級目前無法執行,請稍後重試。該伺服器回報:「%s」。" + +#: ../UpdateManager/UpdateManager.py:1107 ../check-new-release-gtk:126 +msgid "Downloading the release upgrade tool" +msgstr "正下載發行版更新工具" + +#: ../UpdateManager/UpdateManager.py:1114 +#, python-format +msgid "New Ubuntu release '%s' is available" +msgstr "有新 Ubuntu 發行版 '%s' 可供升級" + +#. we assert a clean cache +#: ../UpdateManager/UpdateManager.py:1153 +msgid "Software index is broken" +msgstr "軟體索引損壞" + +#: ../UpdateManager/UpdateManager.py:1154 +msgid "" +"It is impossible to install or remove any software. Please use the package " +"manager \"Synaptic\" or run \"sudo apt-get install -f\" in a terminal to fix " +"this issue at first." +msgstr "" +"無法安裝或移除軟體。請先用 Synaptic 套件管理員或在終端機執行「sudo apt-get " +"install -f」修正。" + +#: ../UpdateManager/UnitySupport.py:57 +msgid "Check for Updates" +msgstr "檢查更新" + +#: ../UpdateManager/UnitySupport.py:66 +msgid "Install All Available Updates" +msgstr "安裝所有可進行的更新" + +#: ../UpdateManagerText/UpdateManagerText.py:34 +msgid "Cancel" +msgstr "取消" + +#: ../UpdateManagerText/UpdateManagerText.py:37 +msgid "Changelog" +msgstr "變更記錄" + +#: ../UpdateManagerText/UpdateManagerText.py:40 +msgid "Updates" +msgstr "更新" + +#: ../UpdateManagerText/UpdateManagerText.py:53 +msgid "Building Updates List" +msgstr "正建立更新清單" + +#: ../UpdateManagerText/UpdateManagerText.py:56 +msgid "" +"\n" +"A normal upgrade can not be calculated, please run: \n" +" sudo apt-get dist-upgrade\n" +"\n" +"\n" +"This can be caused by:\n" +" * A previous upgrade which didn't complete\n" +" * Problems with some of the installed software\n" +" * Unofficial software packages not provided by Ubuntu\n" +" * Normal changes of a pre-release version of Ubuntu" +msgstr "" +"\n" +"無法計算標準升級,請執行: \n" +" sudo apt-get dist-upgrade\n" +"\n" +"\n" +" 這可能由以下原因造成:\n" +" * 前次升級程序未完成\n" +" * 某些已安裝的軟體有問題\n" +" * 安裝了非由 Ubuntu 官方提供的軟體套件\n" +" * Ubuntu 非正式發佈版本的正常更動" + +#: ../UpdateManagerText/UpdateManagerText.py:125 +msgid "Downloading changelog" +msgstr "正下載變更記錄" + +#: ../UpdateManager/Core/MyCache.py:147 +#, python-format +msgid "Other updates (%s)" +msgstr "其他更新 (%s)" + +#: ../UpdateManager/Core/MyCache.py:325 +msgid "This update does not come from a source that supports changelogs." +msgstr "這份更新的來源不支援變更記錄 (changelog)。" + +#: ../UpdateManager/Core/MyCache.py:331 ../UpdateManager/Core/MyCache.py:359 +msgid "" +"Failed to download the list of changes. \n" +"Please check your Internet connection." +msgstr "" +"下載更動清單失敗。\n" +"請檢查網際網路連線。" + +#: ../UpdateManager/Core/MyCache.py:338 +#, python-format +msgid "" +"Changes for the versions:\n" +"Installed version: %s\n" +"Available version: %s\n" +"\n" +msgstr "" +"版本的更動情況:\n" +"已安裝版本:%s\n" +"可用的版本:%s\n" +"\n" + +#: ../UpdateManager/Core/MyCache.py:348 +#, python-format +msgid "" +"The changelog does not contain any relevant changes.\n" +"\n" +"Please use http://launchpad.net/ubuntu/+source/%s/%s/+changelog\n" +"until the changes become available or try again later." +msgstr "" +"變更記錄並未包含相關更動。\n" +"\n" +"有變更記錄提供前請至 http://launchpad.net/ubuntu/+source/%s/%s/+changelog,\n" +"或稍候再試。" + +#: ../UpdateManager/Core/MyCache.py:353 +#, python-format +msgid "" +"The list of changes is not available yet.\n" +"\n" +"Please use http://launchpad.net/ubuntu/+source/%s/%s/+changelog\n" +"until the changes become available or try again later." +msgstr "" +"仍未有更動清單。\n" +"\n" +"有更動清單提供前請至 http://launchpad.net/ubuntu/+source/%s/%s/+changelog,\n" +"或稍候再試。" + +#: ../UpdateManager/Core/UpdateList.py:51 +msgid "Failed to detect distribution" +msgstr "無法偵測出版本" + +#: ../UpdateManager/Core/UpdateList.py:52 +#, python-format +msgid "A error '%s' occurred while checking what system you are using." +msgstr "當檢查您所使用的系統時有錯誤 \"%s\" 發生。" + +#: ../UpdateManager/Core/UpdateList.py:63 +msgid "Important security updates" +msgstr "重要的安全性更新" + +#: ../UpdateManager/Core/UpdateList.py:64 +msgid "Recommended updates" +msgstr "推薦更新" + +#: ../UpdateManager/Core/UpdateList.py:65 +msgid "Proposed updates" +msgstr "建議更新" + +#: ../UpdateManager/Core/UpdateList.py:66 +msgid "Backports" +msgstr "回殖套件 (Backports)" + +#: ../UpdateManager/Core/UpdateList.py:67 +msgid "Distribution updates" +msgstr "發行版更新" + +#: ../UpdateManager/Core/UpdateList.py:72 +msgid "Other updates" +msgstr "其他更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:1 +#, fuzzy +msgid "Starting Software Updater" +msgstr "啟動更新管理員" + +#: ../data/gtkbuilder/UpdateManager.ui.h:2 +msgid "" +"Software updates correct errors, eliminate security vulnerabilities and " +"provide new features." +msgstr "軟體更新會修正錯誤、消除安全隱患並提供新功能。" + +#: ../data/gtkbuilder/UpdateManager.ui.h:3 +msgid "_Partial Upgrade" +msgstr "部份升級(_P)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:4 +msgid "Not all updates can be installed" +msgstr "並非所有更新都可安裝" + +#: ../data/gtkbuilder/UpdateManager.ui.h:5 +msgid "" +"Run a partial upgrade, to install as many updates as possible. \n" +"\n" +"This can be caused by:\n" +" * A previous upgrade which didn't complete\n" +" * Problems with some of the installed software\n" +" * Unofficial software packages not provided by Ubuntu\n" +" * Normal changes of a pre-release version of Ubuntu" +msgstr "" +"執行部份升級,將儘可能安裝最多的更新。 \n" +" 這可能由以下原因造成:\n" +" * 前次升級程序未完成\n" +" * 某些已安裝的軟體有問題\n" +" * 安裝了非由 Ubuntu 官方提供的軟體套件\n" +" * Ubuntu 非正式發佈版本的正常更動" + +#: ../data/gtkbuilder/UpdateManager.ui.h:12 +msgid "Chec_k" +msgstr "檢查(_K)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:13 +msgid "" +"You must check for updates manually\n" +"\n" +"Your system does not check for updates automatically. You can configure this " +"behavior in Software Sources on the Updates tab." +msgstr "" +"您必須手動檢查更新\n" +"\n" +"您的系統不會自動檢查更新。您可以 更新 分頁之 軟體來源 設定。" + +#: ../data/gtkbuilder/UpdateManager.ui.h:16 +msgid "_Hide this information in the future" +msgstr "以後不要再顯示此訊息(_H)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:17 +msgid "Co_ntinue" +msgstr "繼續(_N)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:18 +msgid "Running on battery" +msgstr "使用電池運行" + +#: ../data/gtkbuilder/UpdateManager.ui.h:19 +msgid "Your system is running on battery. Are you sure you want to continue?" +msgstr "您的系統正在使用電池。確定要繼續嗎?" + +#: ../data/gtkbuilder/UpdateManager.ui.h:21 +msgid "_Upgrade" +msgstr "升級(_U)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:22 +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:8 +msgid "Show progress of individual files" +msgstr "顯示單一檔案的進度" + +#: ../data/gtkbuilder/UpdateManager.ui.h:23 +#: ../data/update-manager.desktop.in.h:1 +#, fuzzy +msgid "Software Updater" +msgstr "軟體更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:24 +#, fuzzy +msgid "Starting Software Updater" +msgstr "軟體更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:25 +msgid "U_pgrade" +msgstr "升級(_P)" + +#: ../data/gtkbuilder/UpdateManager.ui.h:27 +msgid "updates" +msgstr "更新" + +#: ../data/gtkbuilder/UpdateManager.ui.h:28 +msgid "Changes" +msgstr "變更" + +#: ../data/gtkbuilder/UpdateManager.ui.h:29 +msgid "Description" +msgstr "說明" + +#: ../data/gtkbuilder/UpdateManager.ui.h:30 +#, fuzzy +msgid "Details of updates" +msgstr "更新說明" + +#: ../data/gtkbuilder/UpdateManager.ui.h:31 +msgid "" +"You are connected via roaming and may be charged for the data consumed by " +"this update." +msgstr "您已透過漫遊連上網路,可能要對本次更新所下載的資料量付費。" + +#: ../data/gtkbuilder/UpdateManager.ui.h:32 +msgid "" +"You may want to wait until you’re not using a mobile broadband connection." +msgstr "" + +#: ../data/gtkbuilder/UpdateManager.ui.h:33 +msgid "It’s safer to connect the computer to AC power before updating." +msgstr "在更新前先將電腦接上 AC 電源比較安全。" + +#: ../data/gtkbuilder/UpdateManager.ui.h:34 +msgid "_Settings..." +msgstr "設定(_S)..." + +#: ../data/gtkbuilder/UpdateManager.ui.h:35 +#, fuzzy +msgid "_Install Now" +msgstr "安裝" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:1 +msgid "A new version of Ubuntu is available. Would you like to upgrade?" +msgstr "有新版本 Ubuntu。要否升級?" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:3 +msgid "Don't Upgrade" +msgstr "不升級" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:4 +msgid "Ask Me Later" +msgstr "稍後再問我" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:5 +msgid "Yes, Upgrade Now" +msgstr "是,現在升級" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:6 +msgid "You have declined to upgrade to the new Ubuntu" +msgstr "您已拒絕升級至新的 Ubuntu" + +#: ../data/gtkbuilder/UpgradePromptDialog.ui.h:7 +#, fuzzy +msgid "" +"You can upgrade at a later time by opening Software Updater and click on " +"\"Upgrade\"." +msgstr "您稍後仍可以透過開啟更新管理員並按下『升級』進行升級。" + +#: ../data/update-manager.desktop.in.h:2 +msgid "Software Updates" +msgstr "軟體更新" + +#: ../data/update-manager.desktop.in.h:3 +msgid "Show and install available updates" +msgstr "顯示並安裝現有軟體更新" + +#: ../update-manager:66 ../update-manager-text:55 ../do-release-upgrade:51 +msgid "Show version and exit" +msgstr "顯示版本後結束" + +#: ../update-manager:69 +msgid "Directory that contains the data files" +msgstr "含有資料檔案的目錄" + +#: ../update-manager:72 +msgid "Check if a new Ubuntu release is available" +msgstr "檢查有否新 Ubuntu 發行版可供升級" + +#: ../update-manager:75 ../do-release-upgrade:54 ../check-new-release-gtk:186 +msgid "Check if upgrading to the latest devel release is possible" +msgstr "檢查能否升級至最新開發發行版" + +#: ../update-manager:79 +msgid "Upgrade using the latest proposed version of the release upgrader" +msgstr "使用最新建議版本的發行升級工具升級" + +#: ../update-manager:86 +msgid "Do not focus on map when starting" +msgstr "啟動時不預先選取圖錄" + +#: ../update-manager:89 +msgid "Try to run a dist-upgrade" +msgstr "試著執行 dist-upgrade" + +#: ../update-manager:92 +msgid "Do not check for updates when starting" +msgstr "當啟動時不要檢查更新" + +#: ../update-manager:96 ../do-release-upgrade:70 +msgid "Test upgrade with a sandbox aufs overlay" +msgstr "使用沙堆 aufs 層測試升級" + +#: ../update-manager:116 +msgid "Running partial upgrade" +msgstr "執行部份升級" + +#: ../update-manager-text:59 +msgid "Show description of the package instead of the changelog" +msgstr "顯示套件描述而不是變更紀錄" + +#: ../do-release-upgrade:58 ../check-new-release-gtk:190 +msgid "" +"Try upgrading to the latest release using the upgrader from $distro-proposed" +msgstr "試著使用 $distro-proposed 的套件升級程式來升級到最新的發行版本" + +#: ../do-release-upgrade:62 +msgid "" +"Run in a special upgrade mode.\n" +"Currently 'desktop' for regular upgrades of a desktop system and 'server' " +"for server systems are supported." +msgstr "" +"在特殊升級模式執行。\n" +"目前只支援以 'desktop' 模式升級桌面版本的系統,以及以 'server' 模式升級伺服器" +"版的系統。" + +#: ../do-release-upgrade:68 +msgid "Run the specified frontend" +msgstr "執行指定的前端" + +#: ../do-release-upgrade:73 +msgid "" +"Check only if a new distribution release is available and report the result " +"via the exit code" +msgstr "檢查有否新發行版並以結束碼報告結果" + +#: ../do-release-upgrade:87 +msgid "Checking for a new Ubuntu release" +msgstr "檢查是否有新的 Ubuntu 發行" + +#: ../do-release-upgrade:101 +msgid "" +"For upgrade information, please visit:\n" +"%(url)s\n" +msgstr "" +"若要取得升級資訊,請參訪:\n" +"%(url)s\n" + +#: ../do-release-upgrade:107 +msgid "No new release found" +msgstr "沒找到新發行版" + +#: ../do-release-upgrade:119 +#, c-format +msgid "New release '%s' available." +msgstr "有新版「%s」提供。" + +#: ../do-release-upgrade:120 +msgid "Run 'do-release-upgrade' to upgrade to it." +msgstr "執行 ‘do-release-upgrade’ 進行升級工作。" + +#: ../check-new-release-gtk:101 +msgid "Ubuntu %(version)s Upgrade Available" +msgstr "可以升級至 Ubuntu %(version)s" + +#: ../check-new-release-gtk:143 +#, c-format +msgid "You have declined the upgrade to Ubuntu %s" +msgstr "您已拒絕升級至 Ubuntu %s" + +#: ../check-new-release-gtk:196 +msgid "Add debug output" +msgstr "加入除錯輸出" + +#: ../ubuntu-support-status:91 +msgid "Show unsupported packages on this machine" +msgstr "顯示此機器上不再支援的套件" + +#: ../ubuntu-support-status:94 +msgid "Show supported packages on this machine" +msgstr "顯示此機器上受支援的套件" + +#: ../ubuntu-support-status:97 +msgid "Show all packages with their status" +msgstr "顯示所有套件及其狀態" + +#: ../ubuntu-support-status:100 +msgid "Show all packages in a list" +msgstr "將所有套件以清單顯示" + +#: ../ubuntu-support-status:142 +#, c-format +msgid "Support status summary of '%s':" +msgstr "「%s」的支援狀態摘要:" + +#: ../ubuntu-support-status:145 +msgid "You have %(num)s packages (%(percent).1f%%) supported until %(time)s" +msgstr "您現在有 %(num)s 個套件 (%(percent).1f%%) 將支援直到 %(time)s" + +#: ../ubuntu-support-status:151 +msgid "" +"You have %(num)s packages (%(percent).1f%%) that can not/no-longer be " +"downloaded" +msgstr "您有 %(num)s 個套件 (%(percent).1f%%) 無法/不再能下載" + +#: ../ubuntu-support-status:154 +msgid "You have %(num)s packages (%(percent).1f%%) that are unsupported" +msgstr "您有 %(num)s 個套件 (%(percent).1f%%) 不再支援" + +#: ../ubuntu-support-status:162 +msgid "" +"Run with --show-unsupported, --show-supported or --show-all to see more " +"details" +msgstr "" +"加上 --show-unsupported、--show-supported 或是 --show-all 作為參數執行以查看" +"更多細節" + +#: ../ubuntu-support-status:166 +msgid "No longer downloadable:" +msgstr "不再能下載:" + +#: ../ubuntu-support-status:169 +msgid "Unsupported: " +msgstr "不再支援: " + +#: ../ubuntu-support-status:174 +#, c-format +msgid "Supported until %s:" +msgstr "支援直至 %s:" + +#: ../ubuntu-support-status:183 +msgid "Unsupported" +msgstr "不再支援" + +#. Why do we use %s here instead of $strings or {} format placeholders? +#. It's because we don't want to break existing translations. +#: ../janitor/plugincore/exceptions.py:42 +#, python-format +msgid "Unimplemented method: %s" +msgstr "未實作的方法: %s" + +#: ../janitor/plugincore/core/file_cruft.py:41 +msgid "A file on disk" +msgstr "磁碟上之檔案" + +#: ../janitor/plugincore/core/missing_package_cruft.py:39 +msgid "Install missing package." +msgstr "安裝缺少的套件。" + +#. 2012-06-08 BAW: i18n string; don't use {} or PEP 292. +#: ../janitor/plugincore/core/missing_package_cruft.py:49 +#, python-format +msgid "Package %s should be installed." +msgstr "應安裝 %s 套件。" + +#: ../janitor/plugincore/core/package_cruft.py:49 +msgid ".deb package" +msgstr ".deb 套件" + +#: ../janitor/plugincore/plugins/langpack_manual_plugin.py:46 +#, python-format +msgid "%s needs to be marked as manually installed." +msgstr "%s 要標記為手動安裝。" + +#: ../janitor/plugincore/plugins/kdelibs4to5_plugin.py:49 +msgid "" +"When upgrading, if kdelibs4-dev is installed, kdelibs5-dev needs to be " +"installed. See bugs.launchpad.net, bug #279621 for details." +msgstr "" +"在升級時,如果有安裝 kdelibs4-dev,那 kdelibs5-dev 就必須安裝。詳情請見 bugs." +"launchpad.net, bug #279621。" + +#: ../janitor/plugincore/plugins/dpkg_status_plugin.py:44 +#, python-format +msgid "%i obsolete entries in the status file" +msgstr "狀態檔有 %i 個廢棄條目" + +#: ../janitor/plugincore/plugins/dpkg_status_plugin.py:47 +msgid "Obsolete entries in dpkg status" +msgstr "dpkg 狀態中之廢棄條目" + +#. pragma: no cover +#: ../janitor/plugincore/plugins/dpkg_status_plugin.py:50 +msgid "Obsolete dpkg status entries" +msgstr "廢棄的 dpkg 狀態條目" + +#: ../janitor/plugincore/plugins/remove_lilo_plugin.py:42 +msgid "Remove lilo since grub is also installed.(See bug #314004 for details.)" +msgstr "因已安裝 grub,移除 lilo。(詳情見 bug #314004。)" + +#~ msgid "" +#~ "After your package information was updated the essential package '%s' can " +#~ "not be found anymore so a bug reporting process is being started." +#~ msgstr "" +#~ "在您的套件資訊更新之後,我們無法再找到必要的「%s」套件,因此正在啟動臭蟲回" +#~ "報程序。" + +#~ msgid "Upgrading Ubuntu to version 12.04" +#~ msgstr "升級 Ubuntu 至 12.04 版" + +#~ msgid "%(count)s update has been selected." +#~ msgid_plural "%(count)s updates have been selected." +#~ msgstr[0] "已選取 %(count)s 項更新。" + +#~ msgid "%(count_str)s %(download_str)s" +#~ msgstr "%(count_str)s %(download_str)s" + +#~ msgid "Welcome to Ubuntu" +#~ msgstr "歡迎使用 Ubuntu" + +#~ msgid "" +#~ "These software updates have been issued since this version of Ubuntu was " +#~ "released." +#~ msgstr "自從這個版本的 Ubuntu 發行後,已經釋出這些軟體更新" + +#~ msgid "Software updates are available for this computer." +#~ msgstr "有此電腦可用的軟體更新。" + +#~ msgid "Update Manager" +#~ msgstr "更新管理員" + +#~ msgid "Starting Update Manager" +#~ msgstr "正在啟動更新管理員" + +#~ msgid "You are connected via a wireless modem." +#~ msgstr "您正透過無線數據機連線。" + +#~ msgid "_Install Updates" +#~ msgstr "安裝更新套件(_I)" + +#~ msgid "Checking for a new ubuntu release" +#~ msgstr "檢查有沒有新的 ubuntu 發行版" + +#~ msgid "Your system is up-to-date" +#~ msgstr "系統已經在最新狀態" + +#~ msgid "" +#~ "This upgrade is running in sandbox (test) mode. All changes are written " +#~ "to '%s' and will be lost on the next reboot.\n" +#~ "\n" +#~ "*No* changes written to a systemdir from now until the next reboot are " +#~ "permanent." +#~ msgstr "" +#~ "這次升級是在沙堆(sandbox, 測試)模式中執行。所有更動都會寫入至「%s」並且會" +#~ "在下次重新開機時消失。\n" +#~ "\n" +#~ "現在起到下次重新開機前將*不會*有任何變更寫入至系統目錄。" + +#~ msgid "The update has already been downloaded, but not installed" +#~ msgid_plural "The updates have already been downloaded, but not installed" +#~ msgstr[0] "更新已下載,但尚未安裝" + +#~ msgid "" +#~ "Fetching and installing the upgrade can take several hours. Once the " +#~ "download has finished, the process cannot be cancelled." +#~ msgstr "下載和安裝升級會花上數小時。一旦下載完成,程序將不能取消。" + +#~ msgid "There are no updates to install" +#~ msgstr "沒有要安裝的更新" + +#~ msgid "Software updates are available for this computer" +#~ msgstr "有軟體更新提供予此電腦。" + +#~ msgid "" +#~ "If you don't want to install them now, choose \"Update Manager\" from the " +#~ "Administration menu later." +#~ msgstr "若目前不想安裝,可稍後於管理選單開啟「更新管理員」。" + +#~ msgid "" +#~ "If you don't want to install them now, choose \"Update Manager\" from " +#~ "Applications later." +#~ msgstr "" +#~ "若現在您不想要安裝它們,您可以稍後從「應用程式」選擇「更新管理員」再安裝。" + +#~ msgid "" +#~ "You will not get any further security fixes or critical updates. Please " +#~ "upgrade to a later version of Ubuntu Linux." +#~ msgstr "您將無法再取得安全性修正與重大更新。請升級至新版 Ubuntu Linux。" + +#~ msgid "" +#~ "Upgrading the repository information resulted in a invalid file. Please " +#~ "report this as a bug using the command 'ubuntu-bug update-manager' in a " +#~ "terminal." +#~ msgstr "" +#~ "升級套件庫時導致無效的檔案。請在終端機內輸入指令「ubuntu-bug update-" +#~ "manager」回報錯誤。" + +#~ msgid "" +#~ "\n" +#~ "\n" +#~ "Please report this bug using the command 'ubuntu-bug update-manager' in a " +#~ "terminal and include the files in /var/log/dist-upgrade/ in the bug " +#~ "report.\n" +#~ "%s" +#~ msgstr "" +#~ "\n" +#~ "\n" +#~ "請在終端機內輸入指令「ubuntu-bug update-manager」回報錯誤,並在錯誤報告中" +#~ "附上 /var/log/dist-upgrade/ 內之檔案。\n" +#~ "%s" + +#~ msgid "" +#~ "The system was unable to get the prerequisites for the upgrade. The " +#~ "upgrade will abort now and restore the original system state.\n" +#~ "\n" +#~ "Please report this as a bug using the command 'ubuntu-bug update-manager' " +#~ "in a terminal and include the files in /var/log/dist-upgrade/ in the bug " +#~ "report." +#~ msgstr "" +#~ "此系統無法達到升級之要求。現在將中斷升級並回復系統至原始狀態。\n" +#~ "\n" +#~ "請在終端機內輸入指令「ubuntu-bug update-manager」回報錯誤,並在錯誤報告中" +#~ "附上 /var/log/dist-upgrade/ 內之檔案。" + +#~ msgid "" +#~ "Preparing the system for the upgrade failed. Please report this using the " +#~ "command 'ubuntu-bug update-manager' in a terminal and include the files " +#~ "in /var/log/dist-upgrade/ in the bug report." +#~ msgstr "" +#~ "準備系統升級失敗。請在終端機內輸入指令「ubuntu-bug update-manager」回報錯" +#~ "誤,並在錯誤報告中附上 /var/log/dist-upgrade/ 內之檔案。" + +#~ msgid "" +#~ "After your package information was updated the essential package '%s' can " +#~ "not be found anymore.\n" +#~ "This indicates a serious error, please report this bug using the command " +#~ "'ubuntu-bug update-manager' in a terminal and include the files in /var/" +#~ "log/dist-upgrade/ in the bug report." +#~ msgstr "" +#~ "套件資訊更新後無法找到必要套件「%s」。\n" +#~ "此為嚴重錯誤,請在終端機內輸入指令「ubuntu-bug update-manager」回報錯誤," +#~ "並在錯誤報告中附上 /var/log/dist-upgrade/ 內的檔案。" + +#~ msgid "Your graphics hardware may not be fully supported in Ubuntu 11.04." +#~ msgstr "您的繪圖硬體可能無法在 Ubuntu 11.04 獲得完整的支援。" + +#~ msgid "" +#~ "The support in Ubuntu 11.04 for your intel graphics hardware is limited " +#~ "and you may encounter problems after the upgrade. Do you want to continue " +#~ "with the upgrade?" +#~ msgstr "" +#~ "您的 Intel 繪圖硬體在 Ubuntu 11.04 內的支援有限,且可能會在升級之後碰到一" +#~ "些問題。您要繼續進行升級嗎?" + +#~ msgid "%.0f kB" +#~ msgstr "%.0f kB" + +#~ msgid "0 kB" +#~ msgstr "0 kB" + +#~ msgid "1 kB" +#~ msgstr "1 kB" + +#~ msgid "" +#~ "These software updates have been issued since this version of Ubuntu was " +#~ "released. If you don't want to install them now, choose \"Update Manager" +#~ "\" from Applications later." +#~ msgstr "" +#~ "自從這個 Ubuntu 版本發行後已經發布這些軟體更新。如果您現在還不想安裝它們," +#~ "請稍後從「應用程式」選單選擇「更新管理員」。" + +#~ msgid "" +#~ "These software updates have been issued since this version of Ubuntu was " +#~ "released. If you don't want to install them now, choose \"Update Manager" +#~ "\" from the Administration Menu later." +#~ msgstr "" +#~ "自從這個 Ubuntu 版本發行後已經發布這些軟體更新。如果您現在還不想安裝它們," +#~ "請稍後從「管理」選單選擇「更新管理員」。" + + +msgid "Unable to access the source management server" +msgstr "無法存取來源管理伺服器,請稍後再試" + +msgid "Check if your network requires authentication?" +msgstr "檢查您的網路需要認證嗎?" + +msgid "Check your source public key signature" +msgstr "檢查您的源數字簽名" + +msgid "update important list occur Exception" +msgstr "獲取推送出現異常,請稍後再試" + +msgid "You need to be root to run this application" +msgstr "你需要root許可權運行" + +msgid "There is an exception in the update package." +msgstr "更新包存在異常!" + +msgid "You request the removal of a system-essential package." +msgstr "您要求刪除系統必要的套件。" + +msgid "This update cannot detect the upgradeable package." +msgstr "本次更新無法檢測到可升級的軟體包。" + +msgid "read important list failed" +msgstr "無法讀取推送升級清單,請稍後再試" + +msgid "Priority Upgrade Package being updated" +msgstr "正在更新分組配置" + +msgid "Exceptions of Priority Upgrade." +msgstr "優先升級異常" + +msgid "Due to the presence of deleted packages." +msgstr "由於存在刪除的套件" + +msgid "The system update configuration file is read abnormally, please check if the system update configuration file format is correct." +msgstr "讀取系統更新設定檔異常,請檢查系統更新配置檔格式是否正確。" + +msgid "Installation progress: " +msgstr "安裝進度: " + +msgid "Installation successful, about to shut down" +msgstr "安裝成功,即將關機" + +msgid "Installation failed, about to shut down" +msgstr "安裝失敗,即將關機" + +msgid "groups JSON ConfigPkgs install failed" +msgstr "無法安裝分組配置檔" + +msgid "Installtion timeout to exit Due to inactivity" +msgstr "安裝超時退出由於" + +msgid "Command execution error" +msgstr "命令執行報錯" + +msgid "Unsupported architecture" +msgstr "架構不符合" + +msgid "Other Error" +msgstr "其他錯誤" + +msgid "dependency is not satisfied" +msgstr "依賴關係不滿足" + +msgid "dependency is not satisfied will download" +msgstr "依賴關係不滿足" + +msgid "Disk space is insufficient, please clean the disk and then upgrade" +msgstr "磁碟空間不足,請清理磁碟後進行升級更新。" + +msgid "Network anomaly, can't check for updates!" +msgstr "網路異常,無法檢查更新!" + +msgid "Check for update exceptions!" +msgstr "檢查更新異常!" + +msgid "Check for update exceptions,fix system APT environment error." +msgstr "檢查更新異常,修復系統APT環境出現錯誤。" + +msgid "The system APT environment is abnormal, please check the system APT environment." +msgstr "修復系統APT環境異常,請檢查系統APT環境。" + +msgid "Priority upgrade status exception." +msgstr "優先升級狀態異常。" + +msgid "Upgrade configuration acquisition exception." +msgstr "升級配置獲取異常。" + +msgid "Please check your network connection and retry." +msgstr "請檢查您的網路連接后再試。" + +msgid "Please check your source list and retry." +msgstr "請檢查您的源清單後再試。" + +msgid "Checking network connection" +msgstr "檢查網路連接中" + +msgid "Updating Source Template" +msgstr "更新源範本中" + +msgid "Update Manager upgrade is complete, please restart the setting panel before performing the system update." +msgstr "更新管理器升級完成,請重啟設置-更新后再進行系統更新。" + +msgid "Uninstallation completed" +msgstr "卸載完成。" + +msgid "Package validation failed and installation was rejected." +msgstr "套件驗證失敗,拒絕安裝。" + +msgid "Other tasks are being updated and upgraded, please uninstall them later." +msgstr "其他任務正在更新升級中,請稍後再卸載。" + +#: ../SystemUpdater/Core/enums.py:763 +msgid "Kylin System Updater" +msgstr "麒麟更新器" + +#: ../SystemUpdater/Core/enums.py:609 +msgid "Kylin Installer" +msgstr "麒麟安裝器" + +#: ../SystemUpdater/Core/enums.py:610 +msgid "Kylin Uninstaller" +msgstr "麒麟卸載器" + +#: ../SystemUpdater/Core/enums.py:611 +msgid "Kylin Background Upgrade" +msgstr "靜默更新" + +#: ../SystemUpdater/Core/enums.py:612 +msgid "Kylin Software Center" +msgstr "軟體商店" + +#: ../SystemUpdater/UpdateManagerDbus.py:355 +msgid " requires authentication to uninstall software packages." +msgstr "卸載套件需要認證。" + +#: ../SystemUpdater/UpdateManager.py:463 +msgid " requires authentication to install software packages." +msgstr "安裝套件需要認證。" + +#: ../SystemUpdater/Core/utils.py:750 +msgid "Authentication success." +msgstr "認證成功。" + +#: ../SystemUpdater/Core/utils.py:753 +msgid "Authentication failure." +msgstr "認證失敗。" + +#: ../SystemUpdater/Core/enums.py:101 +msgid "Deb format exception, read local deb file error." +msgstr "軟體包格式異常,讀取失敗。" + +#: ../SystemUpdater/Core/enums.py:102 +msgid "Install deb error." +msgstr "安裝套件失敗。" + +msgid "Upgrade System" +msgstr "全盤升級" + +msgid "kylin-unattended-upgrade" +msgstr "自動更新" + +msgid "Please check the system time and synchronize the system time before updating." +msgstr "請檢查系統時間,同步系統時間后再進行更新。" + +msgid "The package is unsigned, refuses to install." +msgstr "軟體包未簽名,拒絕安裝。" diff --git a/backend/report-updater-bug b/backend/report-updater-bug new file mode 100755 index 0000000..6fa4452 --- /dev/null +++ b/backend/report-updater-bug @@ -0,0 +1,50 @@ +#!/bin/sh +#系统升级收集bug日志使用 +echo "系统升级收集BUG日志使用..." + +#建立收集的log目录 +mkdir updaterlog +#记录一些基本信息 +date >> updaterlog/base-info +dpkg -l | grep kylin-system-updater >> updaterlog/base-info +dpkg -l | grep ukui-control-center >> updaterlog/base-info +dpkg -l | grep aptdaemon >> updaterlog/base-info +echo $1 >> updaterlog/base-info +echo "记录BUG产生时间(系统当前时间)以及升级相关的版本信息:" +cat updaterlog/base-info + +cp /etc/apt/sources.list updaterlog || true + +cp -r /usr/share/kylin-update-desktop-config/config/ updaterlog || true +#复制后端的日志 +cp -r /var/log/kylin-system-updater/ updaterlog || true + +#收集apt的日志 +cp -r /var/log/apt/term.log updaterlog || true + +cp -r /var/log/apt/history.log updaterlog || true + +#收集aptdamon的日志 +cp -r /var/log/kylin-unattended-upgrades/ updaterlog || true + +outputName="$(date +%m-%d,%H-%M-%S)-updaterLog.tar.gz" + +#将所有的日志进行打包 +tar -czvf updaterLog.tar.gz updaterlog >/dev/null + +#删除收集的日志目录 +rm -rf updaterlog + +#将文件存储到桌面 +if [ ! -d ~/桌面 ]; then + mv updaterLog.tar.gz ~/Desktop/$outputName + echo 输出位置:~/Desktop/$outputName +else + mv updaterLog.tar.gz ~/桌面/$outputName + echo 输出位置:~/桌面/$outputName +fi + +echo "系统更新日志收集完毕..." +echo "\033[1;31m注意:\033[0m 1、请确保Bug复现的时间与执行脚本收集日志时间相近,以此能根据脚本执行时间快速定位到问题的相关日志..." +echo " 2、若Bug复现的时间与现在时间相差较远时,请手动输入大概复现时间。例如 report-updater-bug 月-日,时-分" +echo "请将桌面下\033[5;32;49;1m $outputName \033[0m日志文件提交到禅道... " \ No newline at end of file diff --git a/backend/setup.cfg b/backend/setup.cfg new file mode 100644 index 0000000..a9eefe5 --- /dev/null +++ b/backend/setup.cfg @@ -0,0 +1,15 @@ +[build_i18n] +domain=kylin-system-updater + +# xml_files=[("share/metainfo/", +# ("data/update-manager.appdata.xml.in",)), +# ] + +[sdist] +formats = bztar + +[nosetests] +match=test + +# [install] +# skip-build=0 diff --git a/backend/setup.py b/backend/setup.py new file mode 100755 index 0000000..1824eb0 --- /dev/null +++ b/backend/setup.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*- +from distutils.core import setup +from DistUtilsExtra.command import ( + build_extra, build_i18n, build_help) + +disabled = [] +class CustomBuild(build_extra.build_extra): + def run(self): + build_extra.build_extra.run(self) + +setup( + packages=[ 'SystemUpdater', + 'SystemUpdater.backend', + 'SystemUpdater.Core' + ], + cmdclass={ "build": CustomBuild, + "build_i18n": build_i18n.build_i18n + # "build_help": build_help.build_help + } +) diff --git a/unattended-upgrades/kylin-unattended-upgrade b/unattended-upgrades/kylin-unattended-upgrade new file mode 100644 index 0000000..fbeeef1 --- /dev/null +++ b/unattended-upgrades/kylin-unattended-upgrade @@ -0,0 +1,4082 @@ +#!/usr/bin/python3 +# Copyright (c) 2005-2018 Canonical Ltd +# +# AUTHOR: +# Michael Vogt +# Balint Reczey + +# This file is part of unattended-upgrades +# +# unattended-upgrades 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. +# +# unattended-upgrades 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 unattended-upgrades; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +#from backend.build.lib.SystemUpdater.backend.DownloadBackend import FetchProgress +# from email.policy import default +import stat +import atexit +import copy +import datetime +import errno +# import email.charset +import fcntl +import fnmatch +import gettext + +# from backend.SystemUpdater.Core.utils import error +#from zoneinfo import ZoneInfoNotFoundError +''' +try: + from gi.repository.Gio import NetworkMonitor +except ImportError: + pass +''' +import grp +import io +import locale +import logging +import logging.handlers +import re +import os +import select +import signal +import socket +import string +import subprocess +import sys +import syslog +import shutil +try: + from typing import AbstractSet, cast, DefaultDict, Dict, Iterable, List + AbstractSet # pyflakes + DefaultDict # pyflakes + Dict # pyflakes + Iterable # pyflakes + List # pyflakes + from typing import Set, Tuple, Union + Set # pyflakes + Tuple # pyflakes + Union # pyflakes +except ImportError: + pass + +from collections import defaultdict, namedtuple +from datetime import date +from email.message import Message +import email +from gettext import gettext as _ +from io import StringIO +from optparse import ( + OptionParser, + SUPPRESS_HELP, +) + +from subprocess import ( + Popen, + PIPE, +) +from textwrap import wrap + +import apt +import apt_inst +import apt_pkg +import dbus +from dbus.mainloop.glib import DBusGMainLoop +from gi.repository import GLib +#import distro_info +from apt.progress.base import InstallProgress +import configparser +import fcntl +import time +import subprocess +import json + +SOURCESLIST = "/etc/apt/sources.list" +RELEASEOFFSET = 1 +ORIGINOFFSET = 2 +HTTPTYPE = "HTTP" +FTPTYPE = "FTP" +ARCHITECTUREMAP = ['arm64','amd64','armhf','i386','loongarch64','mips64el','sw64'] + +KYLIN_VERSION_FILE = "/etc/kylin-version/kylin-system-version.conf" +OTA_RESULT_FILE_PATH="/opt/apt_result/" +OTA_RESULT_FILE="/opt/apt_result/ota_result" +SYSTEM_UPDATER_CORE_LIB_PATH="/usr/share/kylin-system-updater/SystemUpdater/Core" +# sys.path.append(SYSTEM_UPDATER_CORE_LIB_PATH) +# from OriginFilter import UnattendUpgradeFilter +CONFIG_FILE_ROOT_PATH="/var/lib/unattended-upgrades" +UNATTENDED_UPGRADE_CONFIG_FILE_PATH="/var/lib/unattended-upgrades/unattended-upgrade.conf" +WHITE_LIST_FILE_PATH="/var/lib/kylin-system-updater/system-updater.conf" +TIMESTAMP_PATH="/var/lib/kylin-software-properties/template/kylin-source-status" +CONTROL_PANEL_LOCK_FILE = "/tmp/auto-upgrade/ukui-control-center.lock" +# the reboot required flag file used by packages +REBOOT_REQUIRED_FILE = "/var/run/reboot-required" +KEPT_PACKAGES_FILE = "var/lib/unattended-upgrades/kept-back" +MAIL_BINARY = "/usr/bin/mail" +SENDMAIL_BINARY = "/usr/sbin/sendmail" +USERS = "/usr/bin/users" +# no py3 lsb_release in debian :/ +DISTRO_CODENAME = subprocess.check_output( + ["lsb_release", "-c", "-s"], universal_newlines=True).strip() # type: str +DISTRO_DESC = subprocess.check_output( + ["lsb_release", "-d", "-s"], universal_newlines=True).strip() # type: str +DISTRO_ID = subprocess.check_output( + ["lsb_release", "-i", "-s"], universal_newlines=True).strip() # type: str + +# Number of days before release of devel where we enable unattended +# upgrades. +DEVEL_UNTIL_RELEASE = datetime.timedelta(days=21) + +# progress information is written here +PROGRESS_LOG = "/var/run/unattended-upgrades.progress" +PID_FILE = "/var/run/unattended-upgrades.pid" +LOCK_FILE = "/var/run/kylin-unattended-upgrade.lock" +NOTIFICATION_PIPE = '/tmp/notification.pipe' +TIME_STAMP = "/var/lib/unattended-upgrades/unattended-upgrades-timestamp" +UNATTENDED_UPGRADE_PKG_LIST_FILE_PATH="/var/lib/kylin-system-updater/json/auto-upgrade-list.json" +OTA_PKGS_TO_INSTALL_LIST="/var/lib/unattended-upgrades/ota_pkgs_to_install_list" +# 禁止关机锁文件路径 +FILELOCK_PATH = "/tmp/lock/" +SHUTDOWN_BLOCK_FILELOCK = "kylin-update.lock" +pidfile = None +# set from the sigint signal handler +SIGNAL_STOP_REQUEST = False + +class PackageComponent(): + def MarkUpgrade(self): + pass + + def MarkInstall(self): + pass + + +class PackageGroup(PackageComponent): + def __init__(self,name) -> None: + self.name = name + self.upgrade_list = [] + self.install_list = [] + + def AddCache(self,cache): + for pkg in self.upgrade_list: + pkg.AddCache(cache) + for pkg in self.install_list: + pkg.AddCache(cache) + + def FindPackage(self,name): + for pkg in self.upgrade_list: + if pkg.FindPackage(name): + return True + for pkg in self.install_list: + if pkg.FindPackage(name): + return True + return False + + def AddPackageToUpgradeList(self,Package): + self.upgrade_list.append(Package) + + def RemovePackageFromUpgradeList(self,Package): + if Package in self.upgrade_list: + self.upgrade_list.remove(Package) + + def AddPackageToInstallList(self,Package): + self.install_list.append(Package) + + def RemovePackageFromInstallList(self,Package): + if Package in self.install_list: + self.install_list.remove(Package) + + def MarkUpgrade(self): + for package in self.upgrade_list: + package.MarkUpgrade() + + def MarkInstall(self): + for package in self.install_list: + package.MarkInstall() + +class Package(PackageComponent): + def __init__(self,name,version) -> None: + self.name = name + self.candidate_version = version + self.deps = [] + + def FindPackage(self,name): + if name in self.deps: + return True + return False + + def AddCache(self,cache): + self.cache = cache + + def MarkUpgrade(self): + return + + def MarkInstall(self): + return + +def reload_options_config(): + #添加默认保留旧配置 + apt_pkg.config["DPkg::Options::"] = "--force-confold" + options_new = list(set(apt_pkg.config.value_list("DPkg::Options"))) + for option in ("--force-confnew","--force-confdef"): + if option in options_new: + options_new.remove(option) + #清除所有配置重新加载 + apt_pkg.config.clear("DPkg::Options") + for option in options_new: + apt_pkg.config["DPkg::Options::"] = option + #去除安装推荐和建议的软件包 + if apt_pkg.config.find_b("APT::Install-Recommends",False) == True: + apt_pkg.config.clear("APT::Install-Recommends") + if apt_pkg.config.find_b("APT::Install-Suggests",False) == True: + apt_pkg.config.clear("APT::Install-Suggests") + if apt_pkg.config.find("Dir::Etc::sourceparts","")!="": + apt_pkg.config["Dir::Etc::sourceparts"]="" + apt_pkg.init_system() + + +def get_default_version(): + version = "" + data = {'version':""} + INPUT_CONFIG_PATH = '/usr/share/kylin-update-desktop-config/config/kylin-update-desktop-system.json' + if os.path.isfile(INPUT_CONFIG_PATH): # 存在 + # 读取JSON文件 + with open(INPUT_CONFIG_PATH, "r") as f: + try : + data = json.load(f) + version = data['version'] + except json.JSONDecodeError as e: + logging.error(str(e)) + return version + +def ReadOsRelease(file): + osreleasedict={} + try: + with open(file) as f: + lines = f.readlines() + for line in lines: + ls = line.strip().split('=',1) + osreleasedict.update({ls[0]:ls[1].strip('"')}) + except Exception as e: + pass + if 'SUB_PROJECT_CODENAME' not in osreleasedict.keys(): + osreleasedict.update({'SUB_PROJECT_CODENAME':''}) + return osreleasedict + + +#安装时禁止关机 进行加锁 +def LockedPreventShutdown(): + global pidfile + + #不为空是表示以及被锁 + if pidfile != None: + logging.error("pidfile file disc not is None,Has been locked...") + return False + + if not os.path.exists(FILELOCK_PATH): + #不存在创建 + logging.info("File(%s) is not exists and will be create",FILELOCK_PATH) + os.makedirs(FILELOCK_PATH) + else: + #当目录存在时进行删除 不删除进行创建文件的话会报错 + # file cannot be locked.[Errno 11] Resource temporarily unavailable + # 资源被占用报错 + shutil.rmtree(FILELOCK_PATH) + logging.info("File(%s) is exists and will be delete and create",FILELOCK_PATH) + os.makedirs(FILELOCK_PATH) + + try: + pidfile = open(os.path.join(FILELOCK_PATH, SHUTDOWN_BLOCK_FILELOCK), "w+") + fcntl.flock(pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB) + logging.info("Shutdown Has been locked...") + return True + except Exception as e: + logging.error("file cannot be locked." + str(e)) + pidfile.close() + pidfile = None + return False + +#解锁禁止关机 +def unLockedEnableShutdown(): + global pidfile + #未加锁退出 + if not pidfile: + logging.info("Not locked and Quitting ...") + return False + try: + fcntl.flock(pidfile, fcntl.LOCK_UN) + logging.info("Shutdown Has been unlocked...") + pidfile.close() + pidfile = None + + # Fix 修复权限问题 当普通用户无法使用 所以直接删除目录 + if os.path.exists(FILELOCK_PATH): + shutil.rmtree(FILELOCK_PATH) + logging.info('Emptying the lockPath(%s) is complete...',FILELOCK_PATH) + else: + logging.info("Emptying the lockPath(%s) is Failed...",FILELOCK_PATH) + + return True + except Exception as e: + logging.error("unlock failed." + str(e)) + pidfile.close() + pidfile = None + return False + +def is_dpkg_journal_dirty(): + # type: () -> bool + """ + Return True if the dpkg journal is dirty + (similar to debSystem::CheckUpdates) + """ + logging.debug("checking whether dpkg journal is dirty") + d = os.path.join("/var/lib/dpkg/", + #os.path.dirname(apt_pkg.config.find_file("Dir::State::status")), + "updates") + for f in os.listdir(d): + if re.match("[0-9]+", f) or re.match("tmp.i",f): + return True + return False + +def get_abnormally_installed_pkg_count(): + output = subprocess.check_output('dpkg -l|grep ^i[^i]|wc -l',shell=True) + return output.decode().strip() + +def get_white_list_with_version(srclist,list,namelist): + for name_with_version in srclist: + nvlist = name_with_version.strip().split('=',1) + if nvlist[0] != '' and nvlist[1] != '': + list.append(nvlist) + namelist.append(nvlist[0]) + +#global timeStamp + +def get_timestamp(): + global timeStamp + config=configparser.ConfigParser(allow_no_value=True) + config.read(TIMESTAMP_PATH) + time_value=time.localtime(int(config.get("Server","UpdateTime"))) + logging.debug(("获取软件源时间戳:%s"),time_value) + timeStamp="自动备份:"+time.strftime("%Y-%m-%d %H:%M:%S",time_value)+" "+config.get("Server","UpdateTime") + return timeStamp + + +def WriteValueToFile(file,section,option,value): + config=configparser.ConfigParser(allow_no_value=True) + config.add_section(section) + config.set(section,option,value) + config.write(open(file,"w")) + + +def signal_handler_int(signal,frame): + # type: (int, object) -> None + logging.warning("SIGINT received, will stop") + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + os._exit(1) + +def signal_handler_usr1(signal,frame): + # type: (int, object) -> None + logging.warning("SIGUSR1 received, will stop") + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + os._exit(1) + +def signal_handler_term(signal,frame): + # type: (int, object) -> None + logging.warning("SIGTERM received, will stop") + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + os._exit(1) + +# messages to be logged only once +logged_msgs = set() # type: AbstractSet[str] + +NEVER_PIN = -32768 + +class InhibitShutdownLock(): + + def __init__(self): + self.inhibit_lock = None + + #安装时禁止关机 进行加锁 + def lock(self, caller='Kylin System Updater'): + """ + 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', + caller, 'Installing Packages', + 'block')), + None, 0, -1, None, None) + self.inhibit_lock = Gio.UnixInputStream(fd=fdlist.steal_fds()[var[0]]) + logging.info("Shutdown Has been locked...") + except Exception as e: + logging.error(e) + + #解锁禁止关机 + def unlock(self): + try: + if self.inhibit_lock != None: + self.inhibit_lock.close() + self.inhibit_lock == None + logging.info("Shutdown Has been unlocked...") + else: + logging.info("Not locked and Quitting ...") + except Exception as e: + logging.error("unlock failed." + str(e)) + +class LoggingDateTime: + """The date/time representation for the dpkg log file timestamps""" + LOG_DATE_TIME_FMT = "%Y-%m-%d %H:%M:%S" + + @classmethod + def as_string(cls): + # type: () -> str + """Return the current date and time as LOG_DATE_TIME_FMT string""" + return datetime.datetime.now().strftime(cls.LOG_DATE_TIME_FMT) + + @classmethod + def from_string(cls, logstr): + # type: (str) -> datetime.datetime + """Take a LOG_DATE_TIME_FMT string and return datetime object""" + return datetime.datetime.strptime(logstr, cls.LOG_DATE_TIME_FMT) + + +class UnknownMatcherError(ValueError): + pass + + +class NoAllowedOriginError(ValueError): + pass + +class FILE_LOCK(object): + def __init__(self,name): + self.fobj = open(name,'w') + self.fd = self.fobj.fileno() + + def get_lock(self): + try: + fcntl.flock(self.fd,fcntl.LOCK_EX|fcntl.LOCK_NB) + return True + except: + return False + + def unlock(self): + self.fobj.close() + +class ConfigFileManager: + def __init__(self,rootdir): + self.filenamelist = [] + if not os.path.exists(rootdir): + os.mkdirs(rootdir) + self.rootdir = rootdir + + def SetRootDir(self,rootdir): + if not os.path.exists(rootdir): + os.mkdirs(rootdir) + self.rootdir=rootdir + + def AddFileName(self,filename): + self.filenamelist.append(filename) + file = os.path.join(self.rootdir,filename) + if not os.path.exists(file): + with open(file,'w+') as f: + f.close() + + def RemoveFileName(self,filename): + file = os.path.join(self.rootdir,filename) + if os.path.exists(file): + os.remove(file) + if filename in self.filenamelist: + self.filenamelist.remove(filename) + + def CheckFileNameExistence(self,filename): + if filename in self.filenamelist: + return True + else: + return False + + def WriteListToFile(self,list,filename): + file = os.path.join(self.rootdir,filename) + if os.path.exists(file): + with open(file,'w+') as f: + f.write(" ".join(list)) + return 0 + else: + return 1 + + def ReadListFromFile(self,file,section,option): + config = configparser.ConfigParser(allow_no_value=True) + if os.path.exists(file): + config.read(file) + try: + str=config[section][option] + list=str.strip().split(",") + return list + except Exception as e: + logging.error(e) + return False + else: + logging.error("no config file") + return True + +def not_empty(s): + return s and s.strip() + +class OriginProperty(): + + def __init__(self): + # 包含了本地所有源 http & ftp + self.local_sourcelist = {"http":[],"ftp":[]} + # 经过解析后的本地源,获取所有的分发属性 + self.local_origin = {"http":[],"ftp":[]} + # 允许的源列表 + self.allow_sources = [] + # 允许的源+属性 + self.allow_origin = {"http":[],"ftp":[]} + # 加载本地所有源 + self.init_local_origin() + # 进行属性解析 + self.analytic_properties(self.local_sourcelist) + + def init_local_origin(self): + http_origin = {} + ftp_orgin = {} + #apt policy + sh_retval = os.popen("apt-cache policy").read().split("\n") + # policy = [ rv for rv in sh_retval if "http" in rv or "ftp" in rv or "release" in rv or "origin" in rv] + for rv in sh_retval: + if "http" in rv: + http_origin['sources'] = rv + http_origin['release'] = sh_retval[sh_retval.index(rv) + RELEASEOFFSET] + http_origin['origin'] = sh_retval[sh_retval.index(rv) + ORIGINOFFSET] + self.local_sourcelist['http'].append(http_origin.copy()) + elif "ftp" in rv: + ftp_orgin['sources'] = rv + ftp_orgin['release'] = sh_retval[sh_retval.index(rv) + RELEASEOFFSET] + ftp_orgin['origin'] = sh_retval[sh_retval.index(rv) + ORIGINOFFSET] + self.local_sourcelist['ftp'].append(ftp_orgin.copy()) + + def merge_origin(self, source_type, source_origin): + is_append = True + if source_type == HTTPTYPE: + if self.local_origin['http']: + for lo in self.local_origin['http']: + if lo['origin_source'] == source_origin['origin_source'] and lo['dist'] == source_origin['dist']: + lo['component'] = list(set(lo['component']).union(set(source_origin['component']))) + is_append = False + if is_append: + self.local_origin['http'].append(source_origin.copy()) + else: + self.local_origin['http'].append(source_origin.copy()) + elif source_type == FTPTYPE: + if self.local_origin['ftp']: + for lo in self.local_origin['ftp']: + if lo['origin_source'] == source_origin['origin_source'] and lo['dist'] == source_origin['dist']: + lo['component'] = list(set(lo['component']).union(set(source_origin['component']))) + is_append = False + if is_append: + self.local_origin['ftp'].append(source_origin.copy()) + else: + self.local_origin['ftp'].append(source_origin.copy()) + + def analytic_properties(self, local_sourcelist): + http_origin = {"component":[],"release":{}} + ftp_orgin = {"component":[],"release":{}} + dist_list = [] + # 经过解析后的本地源,获取所有的分发属性 + for ls in local_sourcelist['http']: + for item in filter(not_empty, ls['sources'].split(' ')): + if item.isdigit(): + http_origin['policy_priority'] = item + elif "http" in item: + http_origin['origin_source'] = item + elif "/" in item: + dist_list = item.split("/") + dist_list.pop() + http_origin['dist'] = "/".join(dist_list) + http_origin['component'].append(item.split("/")[1]) + elif item not in ARCHITECTUREMAP and item != "Packages": + http_origin['component'].append(item) + release_list = ls['release'].split(',') + release_list = [ rl.strip() for rl in release_list ] + if "release" in release_list[0]: + release_list[0] = release_list[0].lstrip("release").strip() + for rl in release_list: + if "=" in rl: + self.generate_dict(http_origin['release'], rl) + for item in filter(not_empty, ls['origin'].split(' ')): + if "origin" not in ls['origin']: + break + elif "origin" != item: + http_origin['origin'] = item + self.merge_origin(HTTPTYPE, http_origin) + http_origin = {"component":[],"release":{}} + + for ls in local_sourcelist['ftp']: + for item in filter(not_empty, ls['sources'].split(' ')): + if item.isdigit(): + ftp_orgin['policy_priority'] = item + elif "ftp" in item: + ftp_orgin['origin_source'] = item + elif "/" in item: + ftp_orgin['dist'] = item.split("/")[0] + ftp_orgin['component'].append(item.split("/")[1]) + elif item not in ARCHITECTUREMAP and item != "Packages": + ftp_orgin['component'].append(item) + release_list = ls['release'].split(',') + if "release " in release_list[0]: + release_list[0] = release_list[0].lstrip("release ") + for rl in release_list: + if "=" in rl: + self.generate_dict(ftp_orgin['release'], rl) + for item in filter(not_empty, ls['origin'].split(' ')): + if "origin" not in ls['origin']: + break + elif "origin" != item: + ftp_orgin['origin'] = item + self.merge_origin(FTPTYPE, ftp_orgin) + ftp_orgin = {"component":[],"release":{}} + + def generate_dict(self, dict, item): + item = item.strip() + if item == "": + logging.warning("empty match string matches nothing") + return False + (what, value) = [ s for s in item.split("=")] + if what in ('o', 'origin'): + dict['origin'] = value + elif what in ("l", "label"): + dict['label'] = value + elif what in ("a", "suite", "archive"): + dict['archive'] = value + elif what in ("c", "component"): + dict['component'] = value + elif what in ("site",): + dict['site'] = value + elif what in ("n", "codename",): + dict['codename'] = value + else: + dict[what] = value + # raise UnknownMatcherError( + # "Unknown whitelist entry for matcher %s (value %s)" % ( + # what, value)) + + def get_allowed_sources(self): + # 源地址,在本地源列表中查找. 源服务器下发source.list为允许的源, 本模块屏蔽了sources.list.d下的源 + # 获取允许的源 + try: + old_sources_list = apt_pkg.config.find("Dir::Etc::sourcelist") + old_sources_list_d = apt_pkg.config.find("Dir::Etc::sourceparts") + old_cleanup = apt_pkg.config.find("APT::List-Cleanup") + apt_pkg.config.set("Dir::Etc::sourcelist", + os.path.abspath(SOURCESLIST)) + apt_pkg.config.set("Dir::Etc::sourceparts", "xxx") + apt_pkg.config.set("APT::List-Cleanup", "0") + slist = apt_pkg.SourceList() + slist.read_main_list() + self.allow_sources = slist.list + except Exception as e: + logging.error(str(e)) + finally: + apt_pkg.config.set("Dir::Etc::sourcelist", + old_sources_list) + apt_pkg.config.set("Dir::Etc::sourceparts", + old_sources_list_d) + apt_pkg.config.set("APT::List-Cleanup", + old_cleanup) + + def get_allowed_origin(self): + # 获取允许的源 + # 生成源与属性 + self.local_origin + self.allow_sources + self.allow_origin + try: + for item in self.allow_sources: + for lo in self.local_origin['http']: + if item.uri.strip('/') == lo['origin_source'].strip('/') and item.dist == lo['dist']: + self.allow_origin['http'].append(lo) + for lo in self.local_origin['ftp']: + if item.uri.strip('/') == lo['origin_source'].strip('/') and item.dist == lo['dist']: + self.allow_origin['ftp'].append(lo) + except Exception as e: + logging.error(str(e)) + +def get_allowed_origins(allow_origin): + """ return a list of allowed origins + """ + allowed_origins = [] + origin = '' + archive = '' + uri = '' + label = '' + for ao in (allow_origin['http']+allow_origin['ftp']): + if 'origin' in ao['release']: + origin = 'o='+ao['release']['origin'] + else: + origin = 'o=' + if 'archive' in ao['release']: + archive = 'a='+ao['release']['archive'] + else: + archive = 'a=' + if 'label' in ao['release']: + label = 'l='+ao['release']['label'] + else: + label = 'l=' + if 'origin_source' in ao: + uri = 'uri='+ao['origin_source'] + else: + uri = 'uri=' + allowed_origins.append(origin+","+archive+","+label+","+uri) + return allowed_origins + +def deleteDuplicatedElementFromList(list): + resultList = [] + for item in list: + if not item in resultList: + resultList.append(item) + return resultList + +class UnattendUpgradeFilter(): + def __init__(self) -> None: + pass + + def GetAllowOrigins(self): + # 获取源属性 + self.origin_property = OriginProperty() + self.origin_property.get_allowed_sources() + self.origin_property.get_allowed_origin() + + self.allowed_origins = get_allowed_origins(self.origin_property.allow_origin) + + self.allowed_origins = deleteDuplicatedElementFromList(self.allowed_origins) + # logging.info(_("Allowed origins: %s"), + # self.allowed_origins) + return self.allowed_origins + +class AcquireStatistics: + def __init__(self,fetcher) -> None: + self.fetcher = fetcher + self.local_pkg_amount = 0 + self.remote_pkg_amount = 0 + self.incomplete_pkg_amount = 0 + self.local_pkg_paths = [] + + def GetAquireStatisticsOfPkgs(self): + for item in self.fetcher.items: + self.local_pkg_paths.append(item.destfile) + if not item.complete: + self.incomplete_pkg_amount+=1 + if item.local: + self.local_pkg_amount+=1 + else: + self.remote_pkg_amount+=1 + + + def ResetFetcher(self,fetcher): + self.fetcher=fetcher + self.local_pkg_paths=[] + self.local_pkg_amount = 0 + self.remote_pkg_amount = 0 + self.incomplete_pkg_amount = 0 + +class KylinSystemUpdater: + def __init__(self) -> None: + DBusGMainLoop(set_as_default=True) + self.loop = GLib.MainLoop() + self.system_bus = dbus.SystemBus() + self.update_proxy = self.system_bus.get_object('com.kylin.systemupgrade','/com/kylin/systemupgrade') + self.data_collect_proxy = self.system_bus.get_object('com.kylin.systemupgrade','/com/kylin/systemupgrade/utils') + self.data_collect_interface = dbus.Interface(self.data_collect_proxy,dbus_interface='com.kylin.systemupgrade.interface') + self.update_interface = dbus.Interface(self.update_proxy,dbus_interface='com.kylin.systemupgrade.interface') + self.success = False + self.whitelist_with_candidate_version = [] + self.update_group = [] + # self.group_rec=[] + # self.single_rec=[] + self.errdict = {} + + + def AddPackageInstallErrorRecord(self,pkg,errmsg): + self.errdict.update({pkg:errmsg}) + + def DumpInstallErrorRecord(self): + errlist = [] + for key in self.errdict.keys(): + errlist.append("%s,%s"%(key,self.errdict[key])) + return errlist + + def DataBackendCollect(self,updateinfo,json_file): + self.data_collect_interface.DataBackendCollect(updateinfo,json_file) + + def InsertUpgradeHistory(self,history): + return self.data_collect_interface.InsertUpgradeHistory(history) + + def GetConfigValue(self,section,value): + return self.update_interface.GetConfigValue(section,value) + + def SetConfigValue(self,section,option,value): + return self.update_interface.SetConfigValue(section,option,value) + + def UpdateDetect(self): + ret = self.update_interface.UpdateDetect() + return ret + + def GetUnattendedUpgradeValue(self): + ret = self.update_interface.UnattendedUpgradeValue('get','') + return ret[0] + + def CheckRebootRequired(self,msg): + ret = self.update_interface.CheckRebootRequired(msg) + + def GetDatabaseInfo(self,section,value): + return self.update_interface.GetSetDatabaseInfo(1,section,value) + + def ConnectToSignals(self): + def update_detect_finished_handler(success,updatelist,error_status,error_cause): + logging.debug(updatelist) + if success: + logging.info("update detect success,quiting main loop") + self.update_group = updatelist + try: + for update_group in self.update_group: + json_file_path = ("/var/lib/kylin-system-updater/json/%s.json"%(update_group)) + if os.path.exists(json_file_path): + with open(json_file_path,'r') as f: + data = json.load(f) + # package_name = data['package'] + # upgrade_list = [] + # install_list = [] + ''' + gp = PackageGroup(package_name) + for key in data['upgrade_list'].keys(): + PackageGroup.AddPackageToUpgradeList(Package(key,data['upgrade_list'][key]['new_version'])) + # upgrade_list.append((key,data['upgrade_list'][key]['new_version'])) + for key in data['install_list'].keys(): + PackageGroup.AddPackageToInstallList(Package(key,data['install_list'][key]['new_version'])) + # install_list.append((key,data['install_list'][key]['new_version'])) + self.group_list.append(gp) + + for key in data['upgrade_list'].keys(): + self.whitelist_with_candidate_version.append((key,data['upgrade_list'][key]['new_version'])) + for key in data['install_list'].keys(): + self.whitelist_with_candidate_version.append((key,data['install_list'][key]['new_version'])) + ''' + for key in data['upgrade_list'].keys(): + if key in ["total_download_size","total_install_size"]: + pass + else: + self.whitelist_with_candidate_version.append((key,data['upgrade_list'][key]['new_version'])) + for key in data['install_list'].keys(): + if key in ["total_download_size","total_install_size"]: + pass + else: + self.whitelist_with_candidate_version.append((key,data['install_list'][key]['new_version'])) + ''' + if os.path.exists(UNATTENDED_UPGRADE_PKG_LIST_FILE_PATH): + with open(UNATTENDED_UPGRADE_PKG_LIST_FILE_PATH, "r") as f: + row_data = json.load(f) + for key in row_data['upgrade_list'].keys(): + self.whitelist_with_candidate_version.append((key,row_data['upgrade_list'][key]['new_version'])) + for key in row_data['install_list'].keys(): + self.whitelist_with_candidate_version.append((key,row_data['install_list'][key]['new_version'])) + for key in row_data['group_json'].keys(): + self.group_rec.append((key,row_data['group_json'][key]['new_version'],row_data['group_json'][key]['changelog'])) + for key in row_data['single_json'].keys(): + self.single_rec.append((key,row_data['single_json'][key]['new_version'],row_data['single_json'][key]['changelog'])) + ''' + except Exception as e: + logging.error(e) + self.loop.quit() + else: + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + logging.error("update detect failed:%s,%s"%(error_status,error_cause)) + os._exit(0) + return success + + self.update_proxy.connect_to_signal('UpdateDetectFinished',update_detect_finished_handler, + dbus_interface='com.kylin.systemupgrade.interface') + return + + + def RunMainloop(self): + logging.info("update manager:running mainloop") + self.loop.run() + + def QuitMainloop(self): + logging.info("update manager:quiting mainloop") + self.loop.quit() + + +class LoginManager: + def __init__(self) -> None: + DBusGMainLoop(set_as_default=True) + self.loop = GLib.MainLoop() + self.system_bus = dbus.SystemBus() + self.login_proxy = self.system_bus.get_object('org.freedesktop.login1', '/org/freedesktop/login1') + self.login_interface = dbus.Interface(self.login_proxy,dbus_interface='org.freedesktop.login1.Manager') + + def SetExtraInhibitShutdownDelaySec(self,time): + try: + self.login_interface.SetExtraInhibitShutdownDelaySec(time) + except Exception as e: + logging.error(e) + +class KylinBackupManager: + def __init__(self) -> None: + DBusGMainLoop(set_as_default=True) + self.loop = GLib.MainLoop() + self.system_bus = dbus.SystemBus() + self.backup_proxy = self.system_bus.get_object('com.kylin.backup','/') + self.backup_interface = dbus.Interface(self.backup_proxy,dbus_interface='com.kylin.backup.manager') + self.success = False + + def mount_backup_partition(self): + return self.backup_interface.Mount_backup_partition() + + def get_backup_state(self): + return self.backup_interface.getBackupState() + + def get_backup_comment_for_systemupdate(self): + return self.backup_interface.getBackupCommentForSystemUpdate() + + def auto_backup_for_system_update_noreturn(self,timeStamp,create_note,inc_note,userName,uid): + self.backup_interface.autoBackUpForSystemUpdate_noreturn(timeStamp,create_note,inc_note,userName,uid) + return + + def ConnectToSignals(self): + def backup_start_handler(result): + logging.debug("backup start result:%d"%result) + if result == 31 or result == 30: + logging.debug("backup start success") + else: + logging.error("backup start failed") + UpdateInfos = {} + UpdateInfos.update({"packageName":"kylin-unattended-upgrade"}) + UpdateInfos.update({"source":"kylin unattended upgrade"}) + UpdateInfos.update({"status":0}) + UpdateInfos.update({"errorCode":"backup start failed"}) + json_file = json.dumps(UpdateInfos.copy()) + UpdateInfos.clear() + kylin_system_updater.DataBackendCollect("UpdateInfos",json_file) + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + os._exit(1) + + def backup_result_handler(result): + if result: + logging.debug("backup success,quiting main loop") + self.loop.quit() + else: + logging.error("backup failed") + UpdateInfos = {} + UpdateInfos.update({"packageName":"kylin-unattended-upgrade"}) + UpdateInfos.update({"source":"kylin unattended upgrade"}) + UpdateInfos.update({"status":0}) + UpdateInfos.update({"errorCode":"backup failed"}) + json_file = json.dumps(UpdateInfos.copy()) + UpdateInfos.clear() + kylin_system_updater.DataBackendCollect("UpdateInfos",json_file) + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + subprocess.Popen('dbus-send --system --type=signal / com.kylin.install.notification.BackupFailure',shell=True) + os._exit(1) + + def send_rate_handler(sta,pro): + logging.debug(("receive backup_rate_signal_handler 状态:%d 进度:%d"),sta,pro) + if pro == 100: + logging.debug("backup finished, quiting mainloop") + self.loop.quit() + + self.backup_proxy.connect_to_signal('sendStartBackupResult',backup_start_handler, + dbus_interface='com.kylin.backup.manager') + self.backup_proxy.connect_to_signal('sendBackupResult',backup_result_handler, + dbus_interface='com.kylin.backup.manager') + self.backup_proxy.connect_to_signal('sendRate',send_rate_handler, + dbus_interface='com.kylin.backup.manager') + return + + def RunMainloop(self): + logging.info("backup manager:running mainloop") + self.loop.run() + + def QuitMainloop(self): + logging.info("backup manager:quiting mainloop") + self.loop.quit() + +def ReadValueFromFile(file,section,option): + config=configparser.ConfigParser(allow_no_value=True) + try: + config.read(file) + value = config[section][option] + except Exception as e: + return '' + return value + +def Backup(): + # do backup + kylin_backup_manager = KylinBackupManager() + backup_partition_status = kylin_backup_manager.mount_backup_partition() + if backup_partition_status not in [0,5]: + logging.error("backup partition error:%d"%backup_partition_status) + return UnattendedUpgradesResult(False,"backup partition error") + status_code,result = kylin_backup_manager.get_backup_state() + if result == 0 and status_code == 99: + pass + else: + logging.error("backup state error:",status_code,result) + return UnattendedUpgradesResult(False,"backup state error") + #node_name,node_status = kylin_backup_manager.get_backup_comment_for_systemupdate() + ts = get_timestamp() + kylin_backup_manager.ConnectToSignals() + create_note = "系统升级新建备份" + inc_note="系统升级增量备份" + userName="root" + uid=os.getuid() + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","backup") + kylin_backup_manager.auto_backup_for_system_update_noreturn(ts,create_note,inc_note,userName,uid) + kylin_backup_manager.RunMainloop() + ''' + if node_name != timeStamp: + logging.info("need backup") + #do actual backup + kylin_backup_manager.ConnectToSignals() + create_note = "系统升级新建备份" + inc_note="系统升级增量备份" + userName="root" + uid=os.getuid() + kylin_backup_manager.auto_backup_for_system_update_noreturn(timeStamp,create_note,inc_note,userName,uid) + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","backup") + kylin_backup_manager.RunMainloop() + ''' + +PkgPin = namedtuple('PkgPin', ['pkg', 'priority']) +PkgFilePin = namedtuple('PkgFilePin', ['id', 'priority']) + + +class UnattendedUpgradesCache(apt.Cache): + + def __init__(self, rootdir, whitelist_with_version,blacklist): + + self._cached_candidate_pkgnames = set() # type: Set[str] + + self.origin_filter = UnattendUpgradeFilter() + + self.allowed_origins = self.origin_filter.GetAllowOrigins() + logging.info(_("Allowed origins are: %s"), + ", ".join(self.allowed_origins)) + + self.blacklist = blacklist + ''' + apt_pkg.config.value_list( + "Unattended-Upgrade::Package-Blacklist") + ''' + # logging.info(_("Initial blacklist: %s"), " ".join(self.blacklist)) + # logging.info("pkg list with version:",whitelist_with_version) + self.whitelist_with_version = whitelist_with_version + self.whitelist = [] + self.get_white_list() + # self.whitelist_with_version = [] + # self.get_white_list_with_version() + # self.whitelist = apt_pkg.config.value_list( + # "Unattended-Upgrade::Package-Whitelist") + self.strict_whitelist = False + ''' + apt_pkg.config.find_b( + "Unattended-Upgrade::Package-Whitelist-Strict", False) + ''' + # logging.info(_("Initial whitelist (%s): %s"), + # "strict" if self.strict_whitelist else "not strict", + # " ".join(self.whitelist)) + apt.Cache.__init__(self, rootdir=rootdir) + + # pre-heat lazy-loaded modules to avoid crash on python upgrade + # datetime.datetime.strptime("", "") + + # generate versioned_kernel_pkgs_regexp for later use + # self.versioned_kernel_pkgs_regexp = versioned_kernel_pkgs_regexp() + # self.running_kernel_pkgs_regexp = running_kernel_pkgs_regexp() + ''' + if self.versioned_kernel_pkgs_regexp: + logging.debug("Using %s regexp to find kernel packages", + self.versioned_kernel_pkgs_regexp.pattern) + else: + logging.debug("APT::VersionedKernelPackages is not set") + if self.running_kernel_pkgs_regexp: + logging.debug("Using %s regexp to find running kernel packages", + self.running_kernel_pkgs_regexp.pattern) + ''' + def get_white_list(self): + for name_with_version in self.whitelist_with_version: + self.whitelist.append(name_with_version[0]) + + def find_better_version(self, pkg): + # type (apt.Package) -> apt.package.Version + if pkg.is_installed and pkg.versions[0] > pkg.installed: + logging.debug( + "Package %s has a higher version available, checking if it is " + "from an allowed origin and is not pinned down.", pkg.name) + for v in pkg.versions: + if pkg.installed < v \ + and pkg.installed.policy_priority <= v.policy_priority \ + and is_in_allowed_origin(v, self.allowed_origins): + return v + return None + + def find_kept_packages(self, dry_run): + # type: (bool) -> KeptPkgs + """ Find kept packages not collected already """ + + kept_packages = KeptPkgs(set) + if dry_run: + logging.info(_("The list of kept packages can't be calculated in " + "dry-run mode.")) + return kept_packages + for pkg in self: + better_version = self.find_better_version(pkg) + if better_version: + logging.info(self.kept_package_excuse(pkg._pkg, + self.blacklist, + self.whitelist, + self.strict_whitelist, + better_version)) + kept_packages.add(pkg, better_version, self) + return kept_packages + + def kept_package_excuse(self, pkg, # apt.Package + blacklist, # type: List[str] + whitelist, # type: List[str] + strict_whitelist, # type: bool + better_version # type: apt.package.Version + ): + # type: (...) -> str + """ Log the excuse the package is kept back for """ + if pkg.selected_state == apt_pkg.SELSTATE_HOLD: + return _("Package %s is marked to be held back.") % pkg.name + elif is_pkgname_in_blacklist(pkg.name, blacklist): + return _("Package %s is blacklisted.") % pkg.name + elif whitelist: + if strict_whitelist: + if not is_pkgname_in_whitelist(pkg.name, whitelist): + return (_( + "Package %s is not on the strict whitelist.") + % pkg.name) + else: + if not is_pkgname_in_whitelist(pkg.name, whitelist): + return (_( + "Package %s is not whitelisted and it is not a" + " dependency of a whitelisted package.") + % pkg.name) + elif not any([o.trusted for o in better_version.origins]): + return _("Package %s's origin is not trusted.") % pkg.name + return (_("Package %s is kept back because a related package" + " is kept back or due to local apt_preferences(5).") + % pkg.name) + + def pinning_from_regex_list(self, regexps, priority): + # type: (List[str], int) -> List[PkgPin] + """ Represent blacklist as Python regexps as list of pkg pinnings""" + + pins = [] # type: List[PkgPin] + for regex in regexps: + if python_regex_is_posix(regex): + pins.append(PkgPin('/^' + regex + '/', priority)) + else: + # Python regex is not also an equivalent POSIX regexp. + # This is expected to be rare. Go through all the package names + # and pin all the matching ones. + for pkg in self._cache.packages: + if re.match(regex, pkg.name): + pins.append(PkgPin(pkg.name, priority)) + return pins + + def pinning_from_config(self): + # type: () -> List[Union[PkgPin, PkgFilePin]] + """ Represent configuration as list of pinnings + + Assumes self.allowed_origins to be already set. + """ + + pins = [] # type: List[Union[PkgPin, PkgFilePin]] + + # mark not allowed origins with 'never' pin + for pkg_file in self._cache.file_list: # type: ignore + if not is_allowed_origin(pkg_file, self.allowed_origins): + # Set the magic 'never' pin on not allowed origins + logging.debug("Marking not allowed %s with %s pin", pkg_file, + NEVER_PIN) + pins.append(PkgFilePin(pkg_file.id, NEVER_PIN)) + # TODO(rbalint) pin not trusted origins with NEVER_PIN + elif self.strict_whitelist: + # set even allowed origins to -1 and set individual package + # priorities up later + pins.append(PkgFilePin(pkg_file.id, -1)) + + # mark blacklisted packages with 'never' pin + pins.extend(self.pinning_from_regex_list( # type: ignore + self.blacklist, NEVER_PIN)) + # set priority of whitelisted packages to high + pins.extend(self.pinning_from_regex_list( # type: ignore + self.whitelist, 900)) + if self.strict_whitelist: + policy = self._depcache.policy + # pin down already pinned packages which are not on the whitelist + # to not install locally pinned up packages accidentally + for pkg in self._cache.packages: + if pkg.has_versions: + pkg_ver = policy.get_candidate_ver(pkg) # type: ignore + if pkg_ver is not None \ + and policy.get_priority(pkg_ver) > -1: + # the pin is higher than set for allowed origins, thus + # there is extra pinning configuration + if not is_pkgname_in_whitelist(pkg.name, + self.whitelist): + pins.append(PkgPin(pkg.name, NEVER_PIN)) + + return pins + + def apply_pinning(self, pins): + # type: (List[Union[PkgPin, PkgFilePin]]) -> None + """ Apply the list of pins """ + + policy = self._depcache.policy + pkg_files = {f.id: f for f in self._cache.file_list} # type: ignore + for pin in pins: + logging.debug("Applying pinning: %s" % str(pin)) + if isinstance(pin, PkgPin): + policy.create_pin('Version', pin.pkg, '*', # type: ignore + pin.priority) + elif isinstance(pin, PkgFilePin): + logging.debug("Applying pin %s to package_file: %s" + % (pin.priority, str(pkg_files[pin.id]))) + policy.set_priority(pkg_files[pin.id], # type: ignore + pin.priority) + + def open(self, progress=None): + apt.Cache.open(self, progress) + # apply pinning generated from unattended-upgrades configuration + # self.apply_pinning(self.pinning_from_config()) + + + def adjust_candidate_with_version(self,pkg,version): + for v in pkg.versions: + if v.version == version and is_in_allowed_origin(v,self.allowed_origins): + #logging.debug("pkg %s adjusting candidate version: %s" %(pkg.name,v)) + pkg.candidate = v + return True + return False + + def adjust_candidate(self, pkg): + # type: (apt.Package) -> bool + """ Adjust origin and return True if adjustment took place + + This is needed when e.g. a package is available in + the security pocket but there is also a package in the + updates pocket with a higher version number + """ + try: + new_cand = ver_in_allowed_origin(pkg, self.allowed_origins) + # Only adjust to lower versions to avoid flipping back and forth + # and to avoid picking a newer version, not selected by apt. + # This helps avoiding upgrades to experimental's packages. + if pkg.candidate is not None: #and new_cand < pkg.candidate: + logging.debug("adjusting candidate version: %s" % new_cand) + pkg.candidate = new_cand + return True + else: + return False + except NoAllowedOriginError: + return False + + def call_checked(self, function, pkg, **kwargs): + """ Call function and check if package is in the wanted state + """ + try: + function(pkg, **kwargs) + except SystemError as e: + logging.warning( + _("package %s upgradable but fails to " + "be marked for upgrade (%s)"), pkg.name, e) + self.clear() + return False + + return ((function == apt.package.Package.mark_upgrade + or function == apt.package.Package.mark_install) + and (pkg.marked_upgrade or pkg.marked_install)) + + def call_adjusted(self, function, pkg, **kwargs): + """Call function, but with adjusting + packages in changes to come from allowed origins + + Note that as a side effect more package's candidate can be + adjusted than only the one's in the final changes set. + """ + new_pkgs_to_adjust = [] # List[str] + + # if not is_pkg_change_allowed(pkg, self.blacklist, self.whitelist, + # self.strict_whitelist): + # return + + # if function == apt.package.Package.mark_upgrade \ + # and not pkg.is_upgradable: + # if not apt_pkg.config.find_b("Unattended-Upgrade::Allow-downgrade", + # False): + # return + # else: + # function = apt.package.Package.mark_install + + marking_succeeded = self.call_checked(function, pkg, **kwargs) + + if not marking_succeeded: + logging.error("%s mark failed"%pkg.name) + + return marking_succeeded + ''' + if (not marking_succeeded + or not check_changes_for_sanity(self, desired_pkg=pkg)) \ + and allow_marking_fallback(): + logging.debug("falling back to adjusting %s's dependencies" + % pkg.name) + self.clear() + # adjust candidates in advance if needed + for pkg_name in self._cached_candidate_pkgnames: + self.adjust_candidate(self[pkg_name]) + + self.adjust_candidate(pkg) + for dep in transitive_dependencies(pkg, self, level=1): + try: + self.adjust_candidate(self[dep]) + except KeyError: + pass + + self.call_checked(function, pkg, **kwargs) + + for marked_pkg in self.get_changes(): + if marked_pkg.name in self._cached_candidate_pkgnames: + continue + if not is_in_allowed_origin(marked_pkg.candidate, + self.allowed_origins): + try: + ver_in_allowed_origin(marked_pkg, + self.allowed_origins) + # important! this avoids downgrades below + if pkg.is_installed and not pkg.is_upgradable and \ + apt_pkg.config.find_b("Unattended-Upgrade::Allow-" + "downgrade", False): + continue + new_pkgs_to_adjust.append(marked_pkg) + except NoAllowedOriginError: + pass + + if new_pkgs_to_adjust: + new_pkg_adjusted = False + for pkg_to_adjust in new_pkgs_to_adjust: + if self.adjust_candidate(pkg_to_adjust): + self._cached_candidate_pkgnames.add(pkg_to_adjust.name) + new_pkg_adjusted = True + if new_pkg_adjusted: + self.call_adjusted(function, pkg, **kwargs) + ''' + def mark_upgrade_adjusted(self, pkg, **kwargs): + self.call_adjusted(apt.package.Package.mark_upgrade, pkg, **kwargs) + + def mark_install_adjusted(self, pkg, **kwargs): + self.call_adjusted(apt.package.Package.mark_install, pkg, **kwargs) + + +class LogInstallProgress(apt.progress.base.InstallProgress): + """ Install progress that writes to self.progress_log + (/var/run/unattended-upgrades.progress by default) + """ + + def __init__(self, logfile_dpkg, verbose=False, + progress_log=PROGRESS_LOG): + # type: (str, bool, str) -> None + apt.progress.base.InstallProgress.__init__(self) + self.logfile_dpkg = logfile_dpkg + self.progress_log = progress_log + # self.progress_log = os.path.join(apt_pkg.config.find_dir("Dir"), + # progress_log) + self.verbose = verbose + self.output_logfd = None # type: int + self.start_time = None + self.max_delay = 3600 + + def status_change(self, pkg, percent, status): + ''' + if self.start_time is None: + self.start_time = time.time() + else: + if (time.time() - self.start_time) > self.max_delay: + logging.warning(_( + "Giving up on lockfile after %s minutes of delay"), + self.max_delay / 60) + sys.exit(1) + ''' + + # type: (str, float, str) -> None + with open(self.progress_log, "w") as f: + per=str(int(float(percent))) + f.write("%s"%per) + #f.write(_("Progress: %s %% (%s)") % (percent, pkg)) + ''' + if re.search("Installed",status): + UpdateInfos = {} + UpdateInfos.update({"packageName":"kylin-unattended-upgrade"}) + UpdateInfos.update({"appname":str(pkg)}) + UpdateInfos.update({"source":"kylin unattended upgrade"}) + UpdateInfos.update({"status":1}) + json_file = json.dumps(UpdateInfos.copy()) + UpdateInfos.clear() + kylin_system_updater.DataBackendCollect("UpdateInfos",json_file) + ''' + logging.info("%s:%s:%s"%(pkg,percent,status)) + + def error(self,pkg, errormsg): + ''' + for key in package_deps.keys(): + if str(pkg) in package_deps[key]: + group_name = kylin_system_updater.FindPackageGroup(key) + UpdateInfos = {} + UpdateInfos.update({"packageName":"kylin-unattended-upgrade"}) + UpdateInfos.update({"appname":group_name}) + UpdateInfos.update({"source":"kylin unattended upgrade"}) + UpdateInfos.update({"status":0}) + UpdateInfos.update({"errorCode":str(errormsg)}) + json_file = json.dumps(UpdateInfos.copy()) + UpdateInfos.clear() + kylin_system_updater.DataBackendCollect("UpdateInfos",json_file) + kylin_system_updater.AddPackageInstallErrorRecord(str(pkg),str(errormsg)) + ''' + logging.error("%s:%s"%(pkg,errormsg)) + kylin_system_updater.AddPackageInstallErrorRecord(str(pkg),str(errormsg)) + + + def _fixup_fds(self): + # () -> None + required_fds = [0, 1, 2, # stdin, stdout, stderr + self.writefd, + self.write_stream.fileno(), + self.statusfd, + self.status_stream.fileno() + ] + # ensure that our required fds close on exec + for fd in required_fds[3:]: + old_flags = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC) + # close all fds + proc_fd = "/proc/self/fd" + if os.path.exists(proc_fd): + error_count = 0 + for fdname in os.listdir(proc_fd): + try: + fd = int(fdname) + except Exception: + print("ERROR: can not get fd for %s" % fdname) + if fd in required_fds: + continue + try: + os.close(fd) + # print("closed: ", fd) + except OSError as e: + # there will be one fd that can not be closed + # as its the fd from pythons internal diropen() + # so its ok to ignore one close error + error_count += 1 + if error_count > 1: + print("ERROR: os.close(%s): %s" % (fd, e)) + + def _redirect_stdin(self): + # type: () -> None + REDIRECT_INPUT = os.devnull + fd = os.open(REDIRECT_INPUT, os.O_RDWR) + os.dup2(fd, 0) + + def _redirect_output(self): + # type: () -> None + # do not create log in dry-run mode, just output to stdout/stderr + if not apt_pkg.config.find_b("Debug::pkgDPkgPM", False): + logfd = self._get_logfile_dpkg_fd() + os.dup2(logfd, 1) + os.dup2(logfd, 2) + + def _get_logfile_dpkg_fd(self): + # type: () -> int + logfd = os.open( + self.logfile_dpkg, os.O_RDWR | os.O_APPEND | os.O_CREAT, 0o640) + try: + adm_gid = grp.getgrnam("adm").gr_gid + os.fchown(logfd, 0, adm_gid) + except (KeyError, OSError): + pass + return logfd + + def update_interface(self): + # type: () -> None + # call super class first + apt.progress.base.InstallProgress.update_interface(self) + self._do_verbose_output_if_needed() + + def _do_verbose_output_if_needed(self): + # type: () -> None + # if we are in debug mode, nothing to be more verbose about + if apt_pkg.config.find_b("Debug::pkgDPkgPM", False): + return + # handle verbose + if self.verbose: + if self.output_logfd is None: + self.output_logfd = os.open(self.logfile_dpkg, os.O_RDONLY) + os.lseek(self.output_logfd, 0, os.SEEK_END) + try: + select.select([self.output_logfd], [], [], 0) + # FIXME: this should be OSError, but in py2.7 it is still + # select.error + except select.error as e: + if e.errno != errno.EINTR: # type: ignore + logging.exception("select failed") + # output to stdout in verbose mode only + os.write(1, os.read(self.output_logfd, 1024)) + + def _log_in_dpkg_log(self, msg): + # type: (str) -> None + logfd = self._get_logfile_dpkg_fd() + os.write(logfd, msg.encode("utf-8")) + os.close(logfd) + + def finish_update(self): + # type: () -> None + self._log_in_dpkg_log("Log ended: %s\n\n" + % LoggingDateTime.as_string()) + + def fork(self): + # type: () -> int + self._log_in_dpkg_log("Log started: %s\n" + % LoggingDateTime.as_string()) + pid = os.fork() + if pid == 0: + self._fixup_fds() + self._redirect_stdin() + self._redirect_output() + return pid + + +class Unlocked: + """ + Context manager for unlocking the apt lock while cache.commit() is run + """ + + def __enter__(self): + # type: () -> None + try: + apt_pkg.pkgsystem_unlock_inner() + except Exception: + # earlier python-apt used to leak lock + logging.warning("apt_pkg.pkgsystem_unlock() failed due to not " + "holding the lock but trying to continue") + pass + + def __exit__(self, exc_type, exc_value, exc_tb): + # type: (object, object, object) -> None + apt_pkg.pkgsystem_lock_inner() + + +class KeptPkgs(defaultdict): + """ + Packages to keep by highest allowed pretty-printed origin + + """ + def add(self, pkg, # type: apt.Package + version, # type: apt.package.Version + cache # type: UnattendedUpgradesCache + ): + # type: (...) -> None + for origin in version.origins: + if is_allowed_origin(origin, cache.allowed_origins): + self[origin.origin + " " + origin.archive].add(pkg.name) + return + + +class UnattendedUpgradesResult: + """ + Represent the (potentially partial) results of an unattended-upgrades + run + """ + def __init__(self, + success, # type: bool + result_str="", # type: str + pkgs=[], # type: List[str] + pkgs_kept_back=KeptPkgs(set), # type: KeptPkgs + pkgs_removed=[], # type: List[str] + pkgs_kept_installed=[], # type: List[str] + update_stamp=False # type: bool + ): + # type: (...) -> None + self.success = success + self.result_str = result_str + self.pkgs = pkgs + self.pkgs_kept_back = pkgs_kept_back + self.pkgs_removed = pkgs_removed + self.pkgs_kept_installed = pkgs_kept_installed + self.update_stamp = update_stamp + + +def is_dpkg_journal_dirty(): + # type: () -> bool + """ + Return True if the dpkg journal is dirty + (similar to debSystem::CheckUpdates) + """ + logging.debug("checking whether dpkg journal is dirty") + d = os.path.join("/var/lib/dpkg/", + #os.path.dirname(apt_pkg.config.find_file("Dir::State::status")), + "updates") + for f in os.listdir(d): + if re.match("[0-9]+", f) or re.match("tmp.i",f): + return True + return False + +def get_abnormally_installed_pkg_count(): + output = subprocess.check_output('dpkg -l|grep ^i[^i]|wc -l',shell=True) + return output.decode().strip() + +def signal_handler(signal, frame): + # type: (int, object) -> None + logging.warning("SIGTERM received, will stop") + global SIGNAL_STOP_REQUEST + SIGNAL_STOP_REQUEST = True + + +def log_once(msg): + # type: (str) -> None + global logged_msgs + if msg not in logged_msgs: + logging.info(msg) + logged_msgs.add(msg) # type: ignore + + +def should_stop(): + # type: () -> bool + """ + Return True if u-u needs to stop due to signal received or due to the + system started to run on battery. + """ + if SIGNAL_STOP_REQUEST: + logging.warning("SIGNAL received, stopping") + return True + ''' + try: + if apt_pkg.config.find_b("Unattended-Upgrade::OnlyOnACPower", True) \ + and subprocess.call("on_ac_power") == 1: + logging.warning("System is on battery power, stopping") + return True + except FileNotFoundError: + log_once( + _("Checking if system is running on battery is skipped. Please " + "install powermgmt-base package to check power status and skip " + "installing updates when the system is running on battery.")) + if apt_pkg.config.find_b( + "Unattended-Upgrade::Skip-Updates-On-Metered-Connections", True): + try: + if NetworkMonitor.get_network_metered( + NetworkMonitor.get_default()): + logging.warning(_("System is on metered connection, stopping")) + return True + except NameError: + log_once(_("Checking if connection is metered is skipped. Please " + "install python3-gi package to detect metered " + "connections and skip downloading updates.")) + ''' + return False + + +def substitute(line): + # type: (str) -> str + """ substitude known mappings and return a new string + + Currently supported ${distro-release} + """ + mapping = {"distro_codename": get_distro_codename(), + "distro_id": get_distro_id()} + return string.Template(line).substitute(mapping) + + +def get_distro_codename(): + # type: () -> str + return DISTRO_CODENAME + + +def get_distro_id(): + # type: () -> str + return DISTRO_ID + + +def allow_marking_fallback(): + # type: () -> bool + return apt_pkg.config.find_b( + "Unattended-Upgrade::Allow-APT-Mark-Fallback", + get_distro_codename() != "sid") + + +def versioned_kernel_pkgs_regexp(): + apt_versioned_kernel_pkgs = apt_pkg.config.value_list( + "APT::VersionedKernelPackages") + if apt_versioned_kernel_pkgs: + return re.compile("(" + "|".join( + ["^" + p + "-[1-9][0-9]*\\.[0-9]+\\.[0-9]+-[0-9]+(-.+)?$" + for p in apt_versioned_kernel_pkgs]) + ")") + else: + return None + + +def running_kernel_pkgs_regexp(): + apt_versioned_kernel_pkgs = apt_pkg.config.value_list( + "APT::VersionedKernelPackages") + if apt_versioned_kernel_pkgs: + running_kernel_version = subprocess.check_output( + ["uname", "-r"], universal_newlines=True).rstrip() + kernel_escaped = re.escape(running_kernel_version) + try: + kernel_noflavor_escaped = re.escape( + re.match("[1-9][0-9]*\\.[0-9]+\\.[0-9]+-[0-9]+", + running_kernel_version)[0]) + return re.compile("(" + "|".join( + [("^" + p + "-" + kernel_escaped + "$|^" + + p + "-" + kernel_noflavor_escaped + "$") + for p in apt_versioned_kernel_pkgs]) + ")") + except TypeError: + # flavor could not be cut from version + return re.compile("(" + "|".join( + [("^" + p + "-" + kernel_escaped + "$") + for p in apt_versioned_kernel_pkgs]) + ")") + else: + return None + + +def get_allowed_origins_legacy(): + # type: () -> List[str] + """ legacy support for old Allowed-Origins var """ + allowed_origins = [] # type: List[str] + key = "Unattended-Upgrade::Allowed-Origins" + try: + for s in apt_pkg.config.value_list(key): + # if there is a ":" use that as seperator, else use spaces + if re.findall(r'(? List[str] + uuf = UnattendUpgradeFilter() + allowed_origins = uuf.GetAllowOrigins() + """ return a list of allowed origins from apt.conf + + This will take substitutions (like distro_id) into account. + """ + + allowed_origins = get_allowed_origins_legacy() + key = "Unattended-Upgrade::Origins-Pattern" + try: + for s in apt_pkg.config.value_list(key): + allowed_origins.append(substitute(s)) + except ValueError: + logging.error(_("Unable to parse %s." % key)) + raise + + #logging.info("allowed origins are:%s"%"\n".join(allowed_origins)) + return allowed_origins +''' + +def match_whitelist_string(whitelist, origin): + # type: (str, Union[apt.package.Origin, apt_pkg.PackageFile]) -> bool + """ + take a whitelist string in the form "origin=Debian,label=Debian-Security" + and match against the given python-apt origin. A empty whitelist string + never matches anything. + """ + whitelist = whitelist.strip() + if whitelist == "": + logging.warning("empty match string matches nothing") + return False + res = True + # make "\," the html quote equivalent + whitelist = whitelist.replace("\\,", "%2C") + for token in whitelist.split(","): + # strip and unquote the "," back + (what, value) = [s.strip().replace("%2C", ",") + for s in token.split("=")] + # logging.debug("matching %s=%s against %s" % ( + # what, value, origin)) + # support substitution here as well + value = substitute(value) + # first char is apt-cache policy output, send is the name + # in the Release file + if what in ("o", "origin"): + match = fnmatch.fnmatch(origin.origin, value) + elif what in ("l", "label"): + match = fnmatch.fnmatch(origin.label, value) + elif what in ("a", "suite", "archive"): + match = fnmatch.fnmatch(origin.archive, value) + elif what in ("c", "component"): + match = fnmatch.fnmatch(origin.component, value) + elif what in ("site",): + match = fnmatch.fnmatch(origin.site, value) + elif what in ("n", "codename",): + match = fnmatch.fnmatch(origin.codename, value) + elif what in ("uri"): + pass + else: + raise UnknownMatcherError( + "Unknown whitelist entry for matcher %s (token %s)" % ( + what, token)) + # update res + res = res and match + # logging.debug("matching %s=%s against %s" % ( + # what, value, origin)) + return res + + +def python_regex_is_posix(expression): + # type: (str) -> bool + """ Returns if the Python regex is also an equivalent POSIX regex """ + return re.match("^[-a-zA-Z0-9\\^\\$\\+\\.:]*$", expression) is not None + + +def cache_commit(cache, # type: apt.Cache + logfile_dpkg, # type: str + verbose, # type: bool + iprogress=None, # type: apt.progress.base.InstallProgress + ): + # type: (...) -> Tuple[bool, Exception] + """Commit the changes from the given cache to the system""" + + error = None + res = False + if iprogress is None: + iprogress = LogInstallProgress(logfile_dpkg, verbose,progress_log=PROGRESS_LOG) + try: + # with Unlocked(): + res = cache.commit(fetch_progress=None,install_progress=iprogress,allow_unauthenticated=True) + #cache.open() + except SystemError as e: + error = e + if verbose: + logging.exception("Exception happened during upgrade.") + cache.clear() + return res, error + + +def upgrade_normal(cache, logfile_dpkg, verbose): + # type: (apt.Cache, str, bool) -> bool + res, error = cache_commit(cache, logfile_dpkg, verbose) + if res: + logging.info(_("All upgrades installed")) + else: + logging.error(_("Installing the upgrades failed!")) + logging.error(_("error message: %s"), error) + logging.error(_("dpkg returned a error! See %s for details"), + logfile_dpkg) + return res + + +def upgrade_in_minimal_steps(cache, # type: UnattendedUpgradesCache + pkgs_to_upgrade, # type: List[str] + logfile_dpkg="", # type: str + verbose=False, # type: bool + ): + # type: (...) -> bool + install_log = LogInstallProgress(logfile_dpkg, verbose) + + res = True + + # to upgrade contains the package names + to_upgrade = set(pkgs_to_upgrade) + for pkgname in upgrade_order(to_upgrade, cache): + # upgrade packages and dependencies in increasing expected size of + # package sets to upgrade/install together + if pkgname not in to_upgrade: + # pkg is upgraded in a previous set + continue + if should_stop(): + return False + try: + pkg = cache[pkgname] + except KeyError: + continue + + try: + if pkg.is_upgradable \ + or candidate_version_changed(pkg): + cache.mark_upgrade_adjusted( + pkg, from_user=not pkg.is_auto_installed) + elif not pkg.is_installed: + cache.mark_install_adjusted(pkg, from_user=False) + else: + continue + except Exception as e: + logging.warning( + _("package %s upgradable but fails to " + "be marked for upgrade (%s)"), pkgname, e) + cache.clear() + res = False + continue + + # double check that we are not running into side effects like + # what could have been caused LP: #1020680 + if not check_changes_for_sanity(cache): + logging.info("While building minimal partition: " + "cache has not allowed changes") + cache.clear() + continue + changes = [p.name for p in cache.get_changes()] + if not changes: + continue + + # write progress log information + if len(pkgs_to_upgrade) > 0: + all_count = len(pkgs_to_upgrade) + remaining_count = all_count - len(to_upgrade) + percent = remaining_count / float(all_count * 100.0) + else: + percent = 100.0 + install_log.status_change(pkg=",".join(changes), + percent=percent, + status="") + # apply changes + logging.debug("applying set %s" % changes) + + res, error = cache_commit(cache, logfile_dpkg, verbose, install_log) + if error: + if verbose: + logging.exception("Exception happened during upgrade.") + logging.error(_("Installing the upgrades failed!")) + logging.error(_("error message: %s"), error) + logging.error(_("dpkg returned a error! See %s for details"), + logfile_dpkg) + return False + to_upgrade = to_upgrade - set(changes) + logging.debug("left to upgrade %s" % to_upgrade) + if len(to_upgrade) == 0: + logging.info(_("All upgrades installed")) + break + return res + + +def is_allowed_origin(origin, allowed_origins): + # type: (Union[apt.package.Origin, apt_pkg.PackageFile], List[str]) -> bool + + # local origin is allowed by default + if origin.component == 'now' and origin.archive == 'now' and \ + not origin.label and not origin.site: + return True + for allowed in allowed_origins: + if match_whitelist_string(allowed, origin): + return True + return False + + +def is_in_allowed_origin(ver, allowed_origins): + # type: (apt.package.Version, List[str]) -> bool + if not ver: + return False + for origin in ver.origins: + if is_allowed_origin(origin, allowed_origins): + return True + return False + + +def ver_in_allowed_origin(pkg, allowed_origins): + # type: (apt.Package, List[str]) -> apt.package.Version + for ver in pkg.versions: + if is_in_allowed_origin(ver, allowed_origins): + # leave as soon as we have the highest new candidate + return ver + raise NoAllowedOriginError() + + +def is_pkgname_in_blacklist(pkgname, blacklist): + # type: (str, List[str]) -> bool + for blacklist_regexp in blacklist: + if re.match(blacklist_regexp, pkgname): + logging.debug("skipping blacklisted package %s" % pkgname) + return True + return False + + +def is_pkgname_in_whitelist(pkgname, whitelist): + # type: (str, List[str]) -> bool + # a empty whitelist means the user does not want to use this feature + if not whitelist: + return True + for whitelist_regexp in whitelist: + if re.match(whitelist_regexp, pkgname): + logging.debug("only upgrading the following package %s" % + pkgname) + return True + return False + + +def is_pkg_change_allowed(pkg, blacklist, whitelist, strict_whitelist): + # type: (apt.Package, List[str], List[str], bool) -> bool + if is_pkgname_in_blacklist(pkg.name, blacklist): + logging.debug("pkg %s package has been blacklisted" % pkg.name) + return False + # a strict whitelist will not allow any changes not in the + # whitelist, most people will want the relaxed whitelist + # that whitelists a package but pulls in the package + # dependencies + + if strict_whitelist and \ + not is_pkgname_in_whitelist(pkg.name, whitelist): + + logging.debug("pkg %s package is not whitelisted" % + pkg.name) + return False + + if pkg._pkg.selected_state == apt_pkg.SELSTATE_HOLD: + logging.debug("pkg %s is on hold" % pkg.name) + return False + return True + + +def transitive_dependencies(pkg, # type: apt.Package + cache, # type: apt.Cache + acc=set(), # type AbstractSet[str] + valid_types=None, # type: AbstractSet[str] + level=None # type: int + ): + # type (...) -> AbstractSet[str] + """ All (transitive) dependencies of the package + + Note that alternative (|) dependencies are collected, too + """ + if not pkg.candidate or level is not None and level < 1: + return acc + + for dep in pkg.candidate.dependencies: + for base_dep in dep: + if base_dep.name not in acc: + if not valid_types or base_dep.rawtype in valid_types: + acc.add(base_dep.name) + try: + transitive_dependencies( + cache[base_dep.name], cache, acc, valid_types, + level=(level - 1 if level is not None else None)) + except KeyError: + pass + return acc + + +def upgrade_order(to_upgrade, cache): + # type: (AbstractSet[str], apt.Cache) -> List[str] + """ Sort pkg names by the expected number of other packages to be upgraded + with it. The calculation is not 100% accurate, it is an approximation. + """ + + upgrade_set_sizes = {} + # calculate upgrade sets + follow_deps = {'Depends', 'PreDepends', 'Recommends'} + for pkgname in to_upgrade: + try: + pkg = cache[pkgname] + except KeyError: + continue + upgrade_set_sizes[pkgname] = len(transitive_dependencies( + pkg, cache, valid_types=follow_deps).intersection(to_upgrade)) + return sorted(upgrade_set_sizes, key=upgrade_set_sizes.get) + + +def check_changes_for_sanity(cache, desired_pkg=None): + # type: (UnattendedUpgradesCache, apt.Package) -> bool + sanity_check_result = sanity_problem(cache, desired_pkg) + if sanity_check_result is None: + return True + else: + logging.debug("sanity check failed for: %s:%s : %s" + % (desired_pkg.name,str({str(p.candidate) for p in cache.get_changes()}), + sanity_check_result)) + return False + + +def sanity_problem(cache, desired_pkg): + # type: (UnattendedUpgradesCache, apt.Package) -> str + # if cache._depcache.broken_count != 0: + # return ("there are broken packages in the cache") + # If there are no packages to be installed they were kept back + # if cache.install_count == 0: + # return ("no package is selected to be upgraded or installed") + + changes = cache.get_changes() + + for pkg in changes: + if os_release_info['PROJECT_CODENAME'] == 'V10SP1-edu' and os_release_info['SUB_PROJECT_CODENAME']=='mavis': + pass + elif pkg.marked_delete: + return ("pkg %s is marked to be deleted" % pkg.name) + + if pkg.marked_install or pkg.marked_upgrade: + # apt will never fallback from a trusted to a untrusted + # origin so its good enough if we have a single trusted one + + # if not any([o.trusted for o in pkg.candidate.origins]): + # return ("pkg %s is not from a trusted origin" % pkg.name) + if not is_in_allowed_origin(pkg.candidate, cache.allowed_origins): + return ("pkg %s is not in an allowed origin" % pkg.name) + ''' + if not is_pkg_change_allowed(pkg, + cache.blacklist, + cache.whitelist, + cache.strict_whitelist): + return ("pkg %s is blacklisted or is not whitelisted" + % pkg.name) + ''' + # check if the package is unsafe to upgrade unattended + ''' + ignore_require_restart = apt_pkg.config.find_b( + "Unattended-Upgrade::IgnoreAppsRequireRestart", False) + upgrade_requires = pkg.candidate.record.get("Upgrade-Requires") + + if pkg.marked_upgrade and ignore_require_restart is False \ + and upgrade_requires == "app-restart": + return ("pkg %s requires app-restart, it is not safe to " + "upgrade it unattended") + ''' + # check that the package we want to upgrade is in the change set + if desired_pkg and desired_pkg not in changes: + return ("pkg %s to be marked for upgrade/install is not marked " + "accordingly" % desired_pkg.name) + return None + + +def is_deb(file): + # type: (str) -> bool + if file.endswith(".deb"): + return True + else: + return False + + +def pkgname_from_deb(debfile): + # type: (str) -> str + # FIXME: add error checking here + try: + control = apt_inst.DebFile(debfile).control.extractdata("control") + sections = apt_pkg.TagSection(control) + return sections["Package"] + except (IOError, SystemError) as e: + logging.error("failed to read deb file %s (%s)" % (debfile, e)) + # dumb fallback + return debfile.split("_")[0] + + +def get_md5sum_for_file_in_deb(deb_file, conf_file): + # type: (str, str) -> str + dpkg_cmd = ["dpkg-deb", "--fsys-tarfile", deb_file] + tar_cmd = ["tar", "-x", "-O", "-f", "-", "." + conf_file] + md5_cmd = ["md5sum"] + dpkg_p = Popen(dpkg_cmd, stdout=PIPE) + tar_p = Popen(tar_cmd, stdin=dpkg_p.stdout, stdout=PIPE, + universal_newlines=True) + md5_p = Popen(md5_cmd, stdin=tar_p.stdout, stdout=PIPE, + universal_newlines=True) + pkg_md5sum = md5_p.communicate()[0].split()[0] + for __p in [dpkg_p, tar_p, md5_p]: + p = cast(Popen, __p) + p.stdout.close() + p.wait() + return pkg_md5sum + + +def get_md5sum_for_file_installed(conf_file, prefix): + # type: (str, str) -> str + try: + with open(prefix + conf_file, 'rb') as fb: + for hash_string in apt_pkg.Hashes(fb).hashes: # type: ignore + if hash_string.hashtype == 'MD5Sum': + return hash_string.hashvalue + return None + except IsADirectoryError: + # the package replaces a directory wih a configuration file + # + # if the package changed this way it is safe to assume that + # the transition happens without showing a prompt but if the admin + # created the directory the admin will need to resolve it after + # being notified about the unexpected prompt + logging.debug("found conffile %s is a directory on the system " + % conf_file) + return "dir" + except FileNotFoundError: + # if the local file got deleted by the admin thats ok but it may still + # trigger a conffile promp (see debian #788049) + logging.debug("conffile %s in missing on the system" % conf_file) + return "" + + +def map_conf_file(conf_file, conffiles): + # type: (str, Union[AbstractSet[str], Dict[str, str]]) -> str + """Find respective conffile in a set of conffiles with some heuristics + """ + if conf_file in conffiles: + return conf_file + elif os.path.join(conf_file, os.path.basename(conf_file)) in conffiles: + # new /etc/foo may be old /etc/foo/foo, like in LP: #1822745 + return os.path.join(conf_file, os.path.basename(conf_file)) + elif os.path.dirname(conf_file) in conffiles: + # new /etc/foo/foo may be old /etc/foo, probably by accident + return os.path.dirname(conf_file) + # TODO: peek into package's dpkg-maintscript-helper mv_conffile usage + else: + return None + + +# prefix is *only* needed for the build-in tests +def conffile_prompt(destFile, prefix=""): + # type: (str, str) -> bool + logging.debug("check_conffile_prompt(%s)" % destFile) + pkgname = pkgname_from_deb(destFile) + + # get the conffiles for the /var/lib/dpkg/status file + status_file = apt_pkg.config.find("Dir::State::status") + with open(status_file, "r") as f: + tagfile = apt_pkg.TagFile(f) + conffiles = "" + for section in tagfile: + if section.get("Package") == pkgname: + logging.debug("found pkg: %s" % pkgname) + if "Conffiles" in section: + conffiles = section.get("Conffiles") + break + + # get conffile value from pkg, its ok if the new version + # does not have conffiles anymore + pkg_conffiles = set() # type: AbstractSet[str] + try: + deb = apt_inst.DebFile(destFile) + pkg_conffiles = set(deb.control.extractdata( + "conffiles").strip().decode("utf-8").split("\n")) + except SystemError as e: + print(_("Apt returned an error, exiting")) + print(_("error message: %s") % e) + logging.error(_("Apt returned an error, exiting")) + logging.error(_("error message: %s"), e) + raise + except LookupError as e: + logging.debug("No conffiles in deb %s (%s)" % (destFile, e)) + if not pkg_conffiles: + return False + + # Conffiles: + # /etc/bash_completion.d/m-a c7780fab6b14d75ca54e11e992a6c11c + dpkg_status_conffiles = {} + for line in conffiles.splitlines(): + # ignore empty lines + line = line.strip() + if not line: + continue + # show what we do + logging.debug("conffile line: %s", line) + li = line.split() + conf_file = li[0] + md5 = li[1] + if len(li) > 2: + obs = li[2] + else: + obs = None + # ignore if conffile is obsolete + if obs == "obsolete": + continue + # ignore state "newconffile" until its clearer if there + # might be a dpkg prompt (LP: #936870) + if md5 == "newconffile": + continue + new_conf_file = map_conf_file(conf_file, pkg_conffiles) + if not new_conf_file: + logging.debug("%s not in package conffiles %s" % ( + conf_file, pkg_conffiles)) + continue + # record for later + dpkg_status_conffiles[conf_file] = md5 + + # test against the installed file, if the local file got deleted + # by the admin thats ok but it may still trigger a conffile prompt + # (see debian #788049) + current_md5 = get_md5sum_for_file_installed(conf_file, prefix) + logging.debug("current md5: %s" % current_md5) + + # hashes are the same, no conffile prompt + if current_md5 == md5: + continue + # calculate md5sum from the deb (may take a bit) + pkg_md5sum = get_md5sum_for_file_in_deb(destFile, new_conf_file) + logging.debug("pkg_md5sum: %s" % pkg_md5sum) + # the md5sum in the deb is unchanged, this will not + # trigger a conffile prompt + if pkg_md5sum == md5: + continue + # if we made it to this point: + # current_md5 != pkg_md5sum != md5 + # and that will trigger a conffile prompt, we can + # stop processing at this point and just return True + return True + + # now check if there are conffiles in the pkg that where not there + # in the previous version in the dpkg status file + if pkg_conffiles: + for conf_file in pkg_conffiles: + old_conf_file = map_conf_file(conf_file, dpkg_status_conffiles) + if not old_conf_file: + pkg_md5sum = get_md5sum_for_file_in_deb(destFile, conf_file) + current_md5 = get_md5sum_for_file_installed(conf_file, prefix) + if current_md5 != "" and pkg_md5sum != current_md5: + return True + return False + + +def dpkg_conffile_prompt(): + # type: () -> bool + if "DPkg::Options" not in apt_pkg.config: + return True + options = apt_pkg.config.value_list("DPkg::Options") + for option in options: + option = option.strip() + if option in ["--force-confold", "--force-confnew"]: + return False + return True + + +def rewind_cache(cache, pkgs_to_upgrade): + # type: (UnattendedUpgradesCache, List[apt.Package]) -> None + """ set the cache back to the state with packages_to_upgrade """ + cache.clear() + for pkg2 in pkgs_to_upgrade: + cache.mark_install_adjusted(pkg2, from_user=not pkg2.is_auto_installed) + if cache.broken_count > 0: + raise AssertionError("rewind_cache created a broken cache") + + +def host(): + # type: () -> str + return socket.getfqdn() + + +def wrap_indent(t, subsequent_indent=" "): + # type: (str, str) -> str + return "\n".join(wrap(t, break_on_hyphens=False, + subsequent_indent=subsequent_indent)) + + +def setup_apt_listchanges(conf="/etc/apt/listchanges.conf"): + # type: (str) -> None + """ deal with apt-listchanges """ + # apt-listchanges will always send a mail if there is a mail address + # set in the config regardless of the frontend used, so set it to + # mail if we have a sendmail and to none if not (as it appears to + # not check if sendmail is there or not), debian bug #579733 + if os.path.exists(SENDMAIL_BINARY): + os.environ["APT_LISTCHANGES_FRONTEND"] = "mail" + else: + os.environ["APT_LISTCHANGES_FRONTEND"] = "none" + + +def _send_mail_using_mailx(from_address, to_address, subject, body): + # type: (str, str, str, str) -> int + # ensure that the body is a byte stream and that we do not + # break on encoding errors (the default error mode is "strict") + encoded_body = body.encode( + locale.getpreferredencoding(False), errors="replace") + # we use a binary pipe to stdin to ensure we do not break on + # unicode encoding errors (e.g. because the user is running a + # ascii only system like the buildds) + mail = subprocess.Popen( + [MAIL_BINARY, "-r", from_address, "-s", subject, to_address], + stdin=subprocess.PIPE, universal_newlines=False) + mail.stdin.write(encoded_body) + mail.stdin.close() + ret = mail.wait() + return ret + + +def _send_mail_using_sendmail(from_address, to_address, subject, body): + # type: (str, str, str, str) -> int + # format as a proper mail + msg = Message() + msg['Subject'] = subject + msg['From'] = from_address + msg['To'] = to_address + msg['Auto-Submitted'] = "auto-generated" + # order is important here, Message() first, then Charset() + # then msg.set_charset() + charset = email.charset.Charset("utf-8") + charset.body_encoding = email.charset.QP # type: ignore + msg.set_payload(body, charset) + # and send it away + sendmail = subprocess.Popen( + [SENDMAIL_BINARY, "-oi", "-t"], + stdin=subprocess.PIPE, universal_newlines=True) + sendmail.stdin.write(msg.as_string()) + sendmail.stdin.close() + ret = sendmail.wait() + return ret + + +def send_summary_mail(pkgs, # type: List[str] + res, # type: bool + result_str, # type: str + pkgs_kept_back, # type: KeptPkgs + pkgs_removed, # type: List[str] + pkgs_kept_installed, # type: List[str] + mem_log, # type: StringIO + dpkg_log_content, # type: str + ): + # type: (...) -> None + """ send mail (if configured in Unattended-Upgrade::Mail) """ + to_email = apt_pkg.config.find("Unattended-Upgrade::Mail", "") + if not to_email: + return + if not os.path.exists(MAIL_BINARY) and not os.path.exists(SENDMAIL_BINARY): + logging.error(_("No /usr/bin/mail or /usr/sbin/sendmail, " + "can not send mail. " + "You probably want to install the mailx package.")) + return + + # The admin may well wish to get a mail report regardless of what was done. + # This is now set by Unattended-Upgrade::MailReport values of: + # "always", "only-on-error" or "on-change" + # (you can achieve "never" by not setting Unattended-Upgrade::Mail). + # If this is not set, then set it using any legacy MailOnlyOnError + # setting (default True) + # + mail_opt = apt_pkg.config.find("Unattended-Upgrade::MailReport") + if (mail_opt == ""): # None set - map from legacy value + if apt_pkg.config.find_b("Unattended-Upgrade::MailOnlyOnError", False): + mail_opt = "only-on-error" + else: + mail_opt = "on-change" + + # if the operation was successful and the user has requested to get + # mails only on errors, just exit here + if (res and (mail_opt == "only-on-error")): + return + + # if the run was successful but nothing had to be done skip sending email + # unless the admin wants it anyway + if (((mail_opt != "always") and res and not pkgs and not pkgs_kept_back + and not pkgs_removed)): + return + + # Check if reboot-required flag is present + reboot_flag_str = _( + "[reboot required]") if os.path.isfile(REBOOT_REQUIRED_FILE) else "" + # Check if packages are kept on hold + hold_flag_str = (_("[package on hold]") if pkgs_kept_back + or pkgs_kept_installed else "") + logging.debug("Sending mail to %s" % to_email) + subject = _( + "{hold_flag}{reboot_flag} unattended-upgrades result for " + "{machine}: {result}").format( + hold_flag=hold_flag_str, reboot_flag=reboot_flag_str, + machine=host(), result="SUCCESS" if res else "FAILURE").strip() + body = wrap_indent(_("Unattended upgrade result: %s") % result_str) + body += "\n\n" + if os.path.isfile(REBOOT_REQUIRED_FILE): + body += _( + "Warning: A reboot is required to complete this upgrade, " + "or a previous one.\n\n") + if pkgs: + if res: + body += _("Packages that were upgraded:\n") + else: + body += _("Packages that attempted to upgrade:\n") + body += " " + wrap_indent(" ".join(pkgs)) + body += "\n\n" + if pkgs_kept_back: + body += _("Packages with upgradable origin but kept back:\n") + for origin, origin_pkgs in pkgs_kept_back.items(): + body += " " + origin + ":\n" + body += " " + wrap_indent(" ".join(origin_pkgs), + subsequent_indent=" ") + "\n" + body += "\n" + if pkgs_removed: + body += _("Packages that were auto-removed:\n") + body += " " + wrap_indent(" ".join(pkgs_removed)) + body += "\n\n" + if pkgs_kept_installed: + body += _("Packages that were kept from being auto-removed:\n") + body += " " + wrap_indent(" ".join(pkgs_kept_installed)) + body += "\n\n" + if dpkg_log_content: + body += _("Package installation log:") + "\n" + body += dpkg_log_content + body += "\n\n" + body += _("Unattended-upgrades log:\n") + body += mem_log.getvalue() + + from_email = apt_pkg.config.find("Unattended-Upgrade::Sender", "root") + + if os.path.exists(SENDMAIL_BINARY): + ret = _send_mail_using_sendmail(from_email, to_email, subject, body) + elif os.path.exists(MAIL_BINARY): + ret = _send_mail_using_mailx(from_email, to_email, subject, body) + else: + raise AssertionError( + "This should never be reached as we previously validated that we " + "either have sendmail or mailx. Maybe they've been removed in " + "this right moment?") + logging.debug("mail returned: %s", ret) + + +def do_install(cache, # type: UnattendedUpgradesCache + pkgs_to_upgrade, # type: List[str] + options, # type: Options + logfile_dpkg, # type: str + ): + # type: (...) -> bool + + #setup_apt_listchanges() + + logging.info(_("Writing dpkg log to %s"), logfile_dpkg) + + # if cache.get_changes(): + # cache.clear() + + pkg_install_success = False + + iprogress = LogInstallProgress(logfile_dpkg, verbose=True,progress_log=PROGRESS_LOG) + try: + pkg_install_success = cache.commit(fetch_progress=apt.progress.text.AcquireProgress(outfile = logfile_fd),install_progress=iprogress,allow_unauthenticated=True) + except Exception as e: + logging.error("cache commit error:%s"%e) + + ''' + try: + if options.minimal_upgrade_steps: + # try upgrade all "pkgs" in minimal steps + pkg_install_success = upgrade_in_minimal_steps( + cache, pkgs_to_upgrade, + logfile_dpkg, + options.verbose or options.debug) + else: + mark_pkgs_to_upgrade(cache, pkgs_to_upgrade) + pkg_install_success = upgrade_normal( + cache, logfile_dpkg, options.verbose or options.debug) + except Exception as e: + # print unhandled exceptions here this way, while stderr is redirected + os.write(2, ("Exception: %s\n" % e).encode('utf-8')) + pkg_install_success = False + ''' + return pkg_install_success + + +def _setup_alternative_rootdir(rootdir): + # type: (str) -> None + # clear system unattended-upgrade stuff + apt_pkg.config.clear("Unattended-Upgrade") + # read rootdir (taken from apt.Cache, but we need to run it + # here before the cache gets initialized + if os.path.exists(rootdir + "/etc/apt/apt.conf"): + apt_pkg.read_config_file(apt_pkg.config, + rootdir + "/etc/apt/apt.conf") + if os.path.isdir(rootdir + "/etc/apt/apt.conf.d"): + apt_pkg.read_config_dir(apt_pkg.config, + rootdir + "/etc/apt/apt.conf.d") + logdir = os.path.join(rootdir, "var", "log", "unattended-upgrades") + if not os.path.exists(logdir): + os.makedirs(logdir) + apt.apt_pkg.config.set("Unattended-Upgrade::LogDir", logdir) + + +def _get_logdir(): + # type: () -> str + logdir = apt_pkg.config.find_dir( + "Unattended-Upgrade::LogDir", + # COMPAT only + apt_pkg.config.find_dir("APT::UnattendedUpgrades::LogDir", + "/var/log/unattended-upgrades/")) + return logdir + + +def _setup_logging(options,logfile): + + + # ensure this is run only once + if len(logging.root.handlers) > 0: + return None + + # init the logging + # logdir = _get_logdir() + # logfile = os.path.join( + # logdir, + # apt_pkg.config.find( + # "Unattended-Upgrade::LogFile", + # # COMPAT only + # apt_pkg.config.find("APT::UnattendedUpgrades::LogFile", + # "unattended-upgrades.log"))) + # if not options.dry_run and not os.path.exists(logdir): + # os.makedirs(logdir) + + logging.basicConfig(level=logging.INFO, + format='%(asctime)s %(levelname)s %(message)s', + filename=logfile) + + # additional logging + logger = logging.getLogger() + # mem_log = StringIO() + # if options.apt_debug: + # apt_pkg.config.set("Debug::pkgProblemResolver", "1") + # apt_pkg.config.set("Debug::pkgDepCache::AutoInstall", "1") + if options.debug: + logger.setLevel(logging.DEBUG) + stdout_handler = logging.StreamHandler(sys.stdout) + logger.addHandler(stdout_handler) + elif options.verbose: + logger.setLevel(logging.INFO) + stdout_handler = logging.StreamHandler(sys.stdout) + logger.addHandler(stdout_handler) + # if apt_pkg.config.find("Unattended-Upgrade::Mail", ""): + # mem_log_handler = logging.StreamHandler(mem_log) + # logger.addHandler(mem_log_handler) + # Configure syslog if necessary + # syslogEnable = apt_pkg.config.find_b("Unattended-Upgrade::SyslogEnable", + # False) + # if syslogEnable: + # syslogFacility = apt_pkg.config.find( + # "Unattended-Upgrade::SyslogFacility", + # "daemon") + # syslogHandler = logging.handlers.SysLogHandler( + # address='/dev/log', + # facility=syslogFacility) # type: ignore + # syslogHandler.setFormatter( + # logging.Formatter("unattended-upgrade: %(message)s")) + # known = syslogHandler.facility_names.keys() # type: ignore + # if syslogFacility.lower() in known: + # logger.addHandler(syslogHandler) + # logging.info("Enabled logging to syslog via %s facility " + # % syslogFacility) + # else: + # logging.warning("Syslog facility %s was not found" + # % syslogFacility) + # return mem_log + + +def logged_in_users(): + # type: () -> AbstractSet[str] + """Return a list of logged in users""" + # the "users" command always returns a single line with: + # "user1, user1, user2" + users = subprocess.check_output( + USERS, universal_newlines=True).rstrip('\n') + return set(users.split()) + + +def reboot_if_requested_and_needed(): + # type: () -> None + """auto-reboot (if required and the config for this is set)""" + if not os.path.exists(REBOOT_REQUIRED_FILE): + return + if not apt_pkg.config.find_b( + "Unattended-Upgrade::Automatic-Reboot", False): + return + # see if we need to check for logged in users + if not apt_pkg.config.find_b( + "Unattended-Upgrade::Automatic-Reboot-WithUsers", True): + users = logged_in_users() + if users: + msg = gettext.ngettext( + "Found %s, but not rebooting because %s is logged in." % ( + REBOOT_REQUIRED_FILE, users), + "Found %s, but not rebooting because %s are logged in." % ( + REBOOT_REQUIRED_FILE, users), + len(users)) + logging.warning(msg) + return + # reboot at the specified time + when = apt_pkg.config.find( + "Unattended-Upgrade::Automatic-Reboot-Time", "now") + logging.warning("Found %s, rebooting" % REBOOT_REQUIRED_FILE) + cmd = ["/sbin/shutdown", "-r", when] + try: + shutdown_msg = subprocess.check_output(cmd, stderr=subprocess.STDOUT) + if shutdown_msg.strip(): + logging.warning("Shutdown msg: %s", shutdown_msg.strip()) + except Exception as e: + logging.error("Failed to issue shutdown: %s", e) + + +def write_stamp_file(): + # type: () -> None + statedir = os.path.join(apt_pkg.config.find_dir("Dir::State"), "periodic") + if not os.path.exists(statedir): + os.makedirs(statedir) + with open(os.path.join(statedir, "unattended-upgrades-stamp"), "w"): + pass + + +def try_to_upgrade(pkg, # type: apt.Package + pkgs_to_upgrade, # type: List[apt.Package] + cache, # type: UnattendedUpgradesCache + version): + # type: (...) -> None + try: + + try: + # try to adjust pkg itself first, if that throws an exception it + # can't be upgraded on its own + cache.adjust_candidate_with_version(pkg,version) + ''' + if not pkg.is_upgradable and not apt_pkg.config.find_b( + "Unattended-Upgrade::Allow-downgrade", False): + return + ''' + except NoAllowedOriginError: + return + + cache._cached_candidate_pkgnames.add(pkg.name) + if not pkg.installed: + cache.mark_install_adjusted(pkg,from_user=True) + else: + cache.mark_upgrade_adjusted(pkg, from_user=not pkg.is_auto_installed) + if check_changes_for_sanity(cache, pkg): + # add to packages to upgrade + pkgs_to_upgrade.append(pkg) + else: + rewind_cache(cache, pkgs_to_upgrade) + except (SystemError, NoAllowedOriginError) as e: + # can't upgrade + logging.warning( + _("package %s upgradable but fails to " + "be marked for upgrade (%s)"), pkg.name, e) + rewind_cache(cache, pkgs_to_upgrade) + + +def candidate_version_changed(pkg): + '''type: apt.Package''' + return (pkg.is_installed and pkg.candidate + and pkg.candidate.version != pkg.installed.version) + # and apt_pkg.config.find_b( + # 'Unattended-Upgrade::Allow-downgrade', False) + # ) + + +def calculate_upgradable_pkgs(cache, # type: UnattendedUpgradesCache + options, # type: Options + whitelist): + # type: (...) -> List[apt.Package] + pkgs_to_upgrade = [] # type: List[apt.Package] + + # now do the actual upgrade + for pkgname in whitelist: + try: + pkg = cache[pkgname[0]] + except Exception as e: + logging.error("error checking pkg:%s"%e) + continue + #for pkg in cache: + # if pkg.name not in cache.whitelist: + # logging.debug("%s not in whitelist skipping..."%(pkg.name)) + # continue + ''' + if options.debug and pkg.is_upgradable \ + or candidate_version_changed(pkg): + logging.debug("Checking: %s (%s)" % ( + pkg.name, getattr(pkg.candidate, "origins", []))) + ''' + if (pkg.is_upgradable or candidate_version_changed(pkg) or not pkg.is_installed): + try: + ver_in_allowed_origin(pkg, cache.allowed_origins) + except NoAllowedOriginError: + continue + + + try_to_upgrade(pkg, + pkgs_to_upgrade, + cache,pkgname[1]) + + # logging.debug("Checking: %s (%s)" % ( + # pkg.name, getattr(pkg.candidate, "origins", []))) + #pkgs_to_upgrade.append(pkg) + if cache.get_changes(): + cache.clear() + + return pkgs_to_upgrade + + +def get_dpkg_log_content(logfile_dpkg, install_start_time): + # type: (str, datetime.datetime) -> str + logging.debug("Extracting content from %s since %s" % ( + logfile_dpkg, install_start_time)) + content = [] + found_start = False + try: + with io.open(logfile_dpkg, encoding='utf-8', errors='replace') as fp: + # read until we find the last "Log started: " + for line in fp.readlines(): + # scan for the first entry we need (minimal-step mode + # creates a new stanza for each individual install) + if not found_start and line.startswith("Log started: "): + stanza_start = LoggingDateTime.from_string( + line[len("Log started: "):-1]) + if stanza_start >= install_start_time: + found_start = True + if found_start: + # skip progress indicator until #860931 is fixed in apt + # and dpkg + if re.match( + "^\\(Reading database \\.\\.\\. ()|([0-9]+%)$", + line): + continue + content.append(line) + return "".join(content) + except FileNotFoundError: + return "" + + +def get_auto_removable(cache): + # type: (apt.Cache) -> AbstractSet[str] + return {pkg.name for pkg in cache + if pkg.is_auto_removable} + + +def is_autoremove_valid(cache, # type: UnattendedUpgradesCache + pkgname, # type: str + auto_removable, # type: AbstractSet[str] + ): + # type: (...) -> bool + changes = cache.get_changes() + if not changes: + # package is already removed + return True + pkgnames = {pkg.name for pkg in changes} + for pkg in changes: + if not is_pkg_change_allowed(pkg, cache.blacklist, cache.whitelist, + cache.strict_whitelist): + logging.warning( + _("Keeping the following auto-removable package(s) because " + "they include %s which is set to be kept unmodified: %s"), + pkg.name, " ".join(sorted(pkgnames))) + return False + if not pkgnames.issubset(auto_removable): + if pkgname != "": + logging.warning( + _("Keeping auto-removable %s package(s) because it would" + " also remove the following packages which should " + "be kept in this step: %s"), pkgname, + " ".join(sorted(pkgnames - auto_removable))) + else: + logging.warning( + _("Keeping %s auto-removable package(s) because it would" + " also remove the following packages which should " + "be kept in this step: %s"), len(auto_removable), + " ".join(sorted(pkgnames - auto_removable))) + + return False + for packagename in pkgnames: + if cache.running_kernel_pkgs_regexp and \ + cache.running_kernel_pkgs_regexp.match(packagename): + logging.warning( + _("Keeping the following auto-removable package(s) because " + "they include %s which package is related to the running " + "kernel: %s"), packagename, " ".join(sorted(pkgnames))) + return False + if cache.install_count > 0: + logging.error( + "The following packages are marked for installation or upgrade " + "which is not allowed when performing autoremovals: %s", + " ".join([pkg.name for pkg in changes if not pkg.marked_delete])) + return False + return True + + +def do_auto_remove(cache, # type: UnattendedUpgradesCache + auto_removable, # type: AbstractSet[str] + logfile_dpkg, # type: str + minimal_steps, # type: bool + verbose=False, # type: bool + dry_run=False # type: bool + ): + # type: (...) -> Tuple[bool, List[str], List[str]] + res = True + if not auto_removable: + return (res, [], []) + + pkgs_removed = [] # type: List[str] + pkgs_kept_installed = [] # type: List[str] + if minimal_steps: + for pkgname in auto_removable: + if should_stop(): + pkgs_kept_installed = list(auto_removable - set(pkgs_removed)) + return (False, pkgs_removed, pkgs_kept_installed) + logging.debug("marking %s for removal" % pkgname) + if pkgname in pkgs_removed: + continue + try: + pkg = cache[pkgname] + except KeyError: + continue + pkg.mark_delete() + if not is_autoremove_valid(cache, pkgname, auto_removable): + # this situation can occur when removing newly unused packages + # would also remove old unused packages which are not set + # for removal, thus getting there is not handled as an error + pkgs_kept_installed.append(pkgname) + cache.clear() + continue + if not dry_run: + changes = cache.get_changes() + pkgnames = {pkg.name for pkg in changes} + res, error = cache_commit(cache, logfile_dpkg, verbose) + if not res: + break + pkgs_removed.extend(pkgnames) + else: + cache.clear() + else: + for pkgname in auto_removable: + try: + pkg = cache[pkgname] + except KeyError: + continue + pkg.mark_delete() + if is_autoremove_valid(cache, "", auto_removable): + # do it in one step + if not dry_run: + res, error = cache_commit(cache, logfile_dpkg, verbose) + else: + cache.clear() + else: + cache.clear() + + if res: + logging.info(_("Packages that were successfully auto-removed: %s"), + " ".join(sorted(pkgs_removed))) + logging.info(_("Packages that are kept back: %s"), + " ".join(sorted(pkgs_kept_installed))) + if not res: + cache.clear() + logging.error(_("Auto-removing the packages failed!")) + logging.error(_("Error message: %s"), error) + logging.error(_("dpkg returned an error! See %s for details"), + logfile_dpkg) + return (res, pkgs_removed, pkgs_kept_installed) + + +def clean_downloaded_packages(fetcher): + # type: (apt_pkg.Acquire) -> None + for item in fetcher.items: + try: + os.unlink(item.destfile) + except OSError: + pass + ''' + archivedir = os.path.dirname( + apt_pkg.config.find_dir("Dir::Cache::archives")) + for item in fetcher.items: + if os.path.dirname(os.path.abspath(item.destfile)) == archivedir: + try: + os.unlink(item.destfile) + except OSError: + pass + ''' + + +def is_update_day(): + # type: () -> bool + # check if patch days are configured + patch_days = apt_pkg.config.value_list("Unattended-Upgrade::Update-Days") + if not patch_days: + return True + # validate patch days + today = date.today() + # abbreviated localized dayname + if today.strftime("%a") in patch_days: + return True + # full localized dayname + if today.strftime("%A") in patch_days: + return True + # by number (Sun: 0, Mon: 1, ...) + if today.strftime("%w") in patch_days: + return True + # today is not a patch day + logging.info( + "Skipping update check: today is %s,%s,%s but patch days are %s", + today.strftime("%w"), today.strftime("%a"), today.strftime("%A"), + ", ".join(patch_days)) + return False + + +def update_kept_pkgs_file(kept_pkgs, kept_file): + # type: (DefaultDict[str, List[str]], str) -> None + if kept_pkgs: + pkgs_all_origins = set() + for origin_pkgs in kept_pkgs.values(): + pkgs_all_origins.update(origin_pkgs) + try: + with open(kept_file, "w") as kf: + kf.write(" ".join(sorted(pkgs_all_origins))) + except FileNotFoundError: + logging.error(_("Could not open %s for saving list of packages " + "kept back." % kept_file)) + else: + if os.path.exists(kept_file): + os.remove(kept_file) + + +def main(options, rootdir="/"): + # type: (Options, str) -> int + # useful for testing + # if not rootdir == "/": + # _setup_alternative_rootdir(rootdir) + + # see debian #776752 + # install_start_time = datetime.datetime.now().replace(microsecond=0) + # logging.info("unattended-upgrades start time:%s"%install_start_time) + # get log + ''' + dpkg_journal_dirty = is_dpkg_journal_dirty() + abnormal_pkg_count = get_abnormally_installed_pkg_count() + logging.info("abnormal pkg count:%s,dpkg dirty:%s"%(abnormal_pkg_count,dpkg_journal_dirty)) + if os_release_info['PROJECT_CODENAME'] == 'V10SP1-edu' and os_release_info['SUB_PROJECT_CODENAME']=='mavis': + if dpkg_journal_dirty or abnormal_pkg_count != '0': + ret = subprocess.run("dpkg --configure -a",shell=True,stdout=open(logfile,'a+'),stderr=open(logfile,'a+')) + logging.info("dpkg fix return :%s"%ret.returncode) + ''' + # lock for the shutdown check + # uu_lock = apt_pkg.get_lock(LOCK_FILE) + # if uu_lock < 0: + # logging.error("Lock file is already taken, exiting") + # WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + # return 1 + + try: + res = run(options, rootdir, logfile_dpkg) + logging.info("result:%s,%s"%(res.success,res.result_str)) + release = '' + version = '' + os_release_info = ReadOsRelease('/etc/os-release') + if 'KYLIN_RELEASE_ID' in os_release_info: + release = os_release_info['KYLIN_RELEASE_ID'] + version = get_default_version() + logging.debug("release:%s,version:%s"%(release,version)) + if options.install_only: + #history record + history = {} + date = time.strftime("%Y-%m-%d %H:%M:%S") + history.update({"date":date}) + history.update({"appname":"kylin-unattended-upgrade"}) + history.update({"appname_cn":"自动更新"}) + history.update({"version":""}) + history.update({"description":"download and install security upgrades automatically"}) + history.update({"keyword":"1"}) + history.update({"changelog":""}) + history.update({"status":"success"}) + history.update({"errorcode":"cache commit error"}) + history.update({"status_cn":"成功"}) + #data collect info + UpdateInfos = {} + UpdateInfos.update({"packageName":"kylin-unattended-upgrade"}) + UpdateInfos.update({"appname":"kylin-unattended-upgrade"}) + UpdateInfos.update({"source":"kylin unattended upgrade"}) + UpdateInfos.update({"status":1}) + UpdateInfos.update({"errorCode":"cache commit error"}) + if res.success and len(res.pkgs) > 0 : + if res.result_str == "total_install": + # with open(TIME_STAMP,'w') as f: + # f.write(time.time()) + config=configparser.ConfigParser(allow_no_value=True) + config.read(KYLIN_VERSION_FILE) + config.set("SYSTEM","os_version",release) + config.set("SYSTEM","update_version",version) + with open(KYLIN_VERSION_FILE,'w') as f: + config.write(f) + kylin_system_updater.InsertUpgradeHistory(history) + json_file = json.dumps(UpdateInfos.copy()) + kylin_system_updater.DataBackendCollect("UpdateInfos",json_file) + elif not res.success: + errorlist = kylin_system_updater.DumpInstallErrorRecord() + errorlist.append("cache commit error") + errcode = "\n".join(errorlist) + if options.install_only: + history.update({"status":"failed"}) + history.update({"status_cn":"失败"}) + history.update({"errorcode":errcode}) + kylin_system_updater.InsertUpgradeHistory(history) + UpdateInfos.update({"status":0}) + UpdateInfos.update({"errorCode":errcode}) + json_file = json.dumps(UpdateInfos.copy()) + kylin_system_updater.DataBackendCollect("UpdateInfos",json_file) + else: + logging.info("no pkgs to install") + + + if 'PROJECT_CODENAME' in os_release_info: + if os_release_info['PROJECT_CODENAME']=='V10SP1-edu': + if 'SUB_PROJECT_CODENAME' in os_release_info: + if os_release_info['SUB_PROJECT_CODENAME']=='mavis': + localtime = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time())) + config_to_result = configparser.ConfigParser(allow_no_value=True) + config_to_result.add_section("OTA") + config_to_result.set("OTA","time",localtime) + config_to_result.set("OTA","version","1.0") + config_to_result.set("OTA","upgrade","0") + config_to_result.set("OTA","status","failed") + if res.success: + if options.mode == 'shutdown': + config_to_result.set("OTA","status","success") + if len(res.pkgs) > 0 : + config_to_result.set("OTA","upgrade","1") + if not os.path.exists(OTA_RESULT_FILE_PATH): + os.makedirs(OTA_RESULT_FILE_PATH) + # os.chmod(OTA_RESULT_FILE_PATH,stat.S_IRUSR|stat.S_IWUSR|stat.S_IWGRP|stat.S_IRGRP|stat.S_IWOTH|stat.S_IROTH) + if not os.path.exists(OTA_RESULT_FILE): + f = open(OTA_RESULT_FILE,'w') + f.close() + with open(OTA_RESULT_FILE,"w+") as f: + config_to_result.write(f) + subprocess.Popen("chmod -R 777 %s"%(OTA_RESULT_FILE_PATH),shell=True) + # os.chmod(OTA_RESULT_FILE,stat.S_IRUSR|stat.S_IWUSR|stat.S_IWGRP|stat.S_IRGRP|stat.S_IWOTH|stat.S_IROTH) + # os.chmod(OTA_RESULT_FILE,stat.S_IRWXU|stat.S_IRWXG|stat.S_IRWXO) + + + # WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + ''' + if res.success and res.result_str: + # complete, successful run + update_kept_pkgs_file(res.pkgs_kept_back, + os.path.join(rootdir, KEPT_PACKAGES_FILE)) + + if res.result_str and not options.dry_run: + # there is some meaningful result which is worth an email + log_content = get_dpkg_log_content(logfile_dpkg, + install_start_time) + + send_summary_mail(res.pkgs, res.success, res.result_str, + res.pkgs_kept_back, res.pkgs_removed, + res.pkgs_kept_installed, mem_log, + log_content) + + if res.update_stamp: + # write timestamp file + write_stamp_file() + if not options.dry_run: + # check if the user wants a reboot + reboot_if_requested_and_needed() + ''' + os.close(shutdown_lock) + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + if res.success: + return 0 + else: + return 1 + + except Exception as e: + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + logging.error(e) + # logger = logging.getLogger() + # logger.exception(_("An error occurred: %s"), e) + # log_content = get_dpkg_log_content(logfile_dpkg, + # install_start_time) + # if not options.dry_run: + # send_summary_mail([""], False, _("An error occurred"), + # None, [], [], mem_log, log_content) + # Re-raise exceptions for apport + # raise + + +def mark_pkgs_to_upgrade(cache, pkgs_to_upgrade): + # type (apt.Cache, List[str]) -> None + for pkg_name in pkgs_to_upgrade: + try: + pkg = cache[pkg_name] + except KeyError: + continue + + if pkg.is_upgradable \ + or (pkg.is_installed + and pkg.candidate.version != pkg.installed.version): + cache.mark_upgrade_adjusted(pkg, + from_user=not pkg.is_auto_installed) + # pkg.mark_upgrade() + + elif not pkg.is_installed: + cache.mark_install_adjusted(pkg, from_user=True) + # pkg.mark_install() + else: + pass + + +def adjust_candidate_with_version(cache,namelistwithversion): + for pkgname in namelistwithversion: + try: + pkg = cache[pkgname[0]] + except KeyError: + continue + for v in pkg.versions: + if v.version == pkgname[1] and is_in_allowed_origin(v,cache.allowed_origins): + logging.info("package:%s , candidate version:%s"%(pkgname[0],pkgname[1])) + pkg.candidate = v + # if str(v) in versionlist: + # pkg.candidate = v + ''' + dep_list = [] + dep = pkg.candidate.get_dependencies("PreDepends") + for d in dep: + dep_list.append(d.or_dependencies[0].name) + dep = pkg.candidate.get_dependencies("Depends") + for d in dep: + dep_list.append(d.or_dependencies[0].name) + package_deps.update({pkg:dep_list}) + ''' + +def run(options, # type: Options + rootdir, # type: str + # mem_log, # type: StringIO + logfile_dpkg, # type: str + # install_start_time, # type: datetime.datetime + ): + # type: (...) -> UnattendedUpgradesResult + + # check if today is a patch day + # if not is_update_day(): + # return UnattendedUpgradesResult(True) + logging.info(_("Starting unattended upgrades script")) + reload_options_config() + # check if u-u should be stopped already + if should_stop(): + return UnattendedUpgradesResult(False) + + #global os_release_info + # check to see if want to auto-upgrade the devel release + ''' + if apt_pkg.config.find("Unattended-Upgrade::DevRelease") == "auto": + try: + if DISTRO_ID.lower() == 'ubuntu': + devel = (distro_info.UbuntuDistroInfo() . + devel(result="object")) + elif DISTRO_ID.lower() == 'debian': + devel = (distro_info.DebianDistroInfo() . + devel(result="object")) + else: + devel = (distro_info.DistroInfo(DISTRO_ID) . + devel(result="object")) + except Exception as e: + logging.warning("Could not figure out development release: %s" % e) + else: + if ((devel.series == DISTRO_CODENAME + and devel.release is not None + and devel.release - date.today() > DEVEL_UNTIL_RELEASE)): + syslog.syslog((_("Not running on this development " + "release before %s") % + (devel.release - DEVEL_UNTIL_RELEASE + - datetime.timedelta(days=1)))) + logging.warning(_("Not running on this development " + "release before %s") % + (devel.release - DEVEL_UNTIL_RELEASE + - datetime.timedelta(days=1))) + return UnattendedUpgradesResult(True) + + logging.debug("Running on the development release") + elif "(development branch)" in DISTRO_DESC and not\ + apt_pkg.config.find_b("Unattended-Upgrade::DevRelease", True): + syslog.syslog(_("Not running on the development release.")) + logging.info(_("Not running on the development release.")) + return UnattendedUpgradesResult(True) + ''' + + #kylin_system_updater = KylinSystemUpdater() + ''' + if kylin_system_updater.GetUnattendedUpgradeValue: + pass + else: + return UnattendedUpgradesResult(False) + kylin_system_updater.ConnectToSignals() + kylin_system_updater.GetWhiteList() + kylin_system_updater.RunMainloop() + ''' + # check and get lock + try: + apt_pkg.pkgsystem_lock() + except SystemError: + logging.error(_("Lock could not be acquired (another package " + "manager running?)")) + #print(_("Cache lock can not be acquired, exiting")) + return UnattendedUpgradesResult( + False, _("Lock could not be acquired")) + + # check if the journal is dirty and if so, take emergceny action + # the alternative is to leave the system potentially unsecure until + # the user comes in and fixes + ''' + if is_dpkg_journal_dirty() and \ + apt_pkg.config.find_b("Unattended-Upgrade::AutoFixInterruptedDpkg", + False): + logging.warning( + _("Unclean dpkg state detected, trying to correct")) + print(_("Unclean dpkg state detected, trying to correct")) + env = copy.copy(os.environ) + env["DPKG_FRONTEND_LOCKED"] = "1" + try: + with Unlocked(): + output = subprocess.check_output( + ["dpkg", "--force-confold", "--configure", "-a"], + env=env, + universal_newlines=True) + except subprocess.CalledProcessError as e: + output = e.output + logging.warning(_("dpkg --configure -a output:\n%s"), output) + ''' + + white_list_with_version = kylin_system_updater.whitelist_with_candidate_version#config_manager.ReadListFromFile(WHITE_LIST_FILE_PATH,'AutoUpgrade','upgradelist') + logging.info("upgrade list from kylin system updater:") + logging.debug(white_list_with_version) + ''' + for w in white_list_with_version: + whitelistwithversion.append('-'.join(w)) + logging.debug("whitelist from kylin system updater:%s"%("\n".join(whitelistwithversion))) + ''' + # namelist = [] + # namelist_with_version = [] + + # get_white_list_with_version(white_list_with_version,namelist_with_version,namelist) + # get a cache + try: + cache = UnattendedUpgradesCache(rootdir=rootdir,whitelist_with_version=white_list_with_version,blacklist=[]) + #cache.whitelist=white_list + except SystemError as error: + # print(_("Apt returned an error, exiting")) + # print(_("error message: %s") % error) + logging.error(_("Apt returned an error, exiting")) + logging.error(_("error message: %s"), error) + return UnattendedUpgradesResult( + False, _("Apt returned an error, exiting")) + + if cache._depcache.broken_count > 0: + #print(_("Cache has broken packages, exiting")) + logging.error(_("Cache has broken packages, exiting")) + return UnattendedUpgradesResult( + False, _("Cache has broken packages, exiting")) + + # FIXME: make this into a ContextManager + # be nice when calculating the upgrade as its pretty CPU intensive + ''' + old_priority = os.nice(0) + try: + # Check that we will be able to restore the priority + os.nice(-1) + os.nice(20) + except OSError as e: + if e.errno in (errno.EPERM, errno.EACCES): + pass + else: + raise + ''' + #auto_removable = get_auto_removable(cache) + + + # find out about the packages that are upgradable (in an allowed_origin) + pkgs_to_upgrade = calculate_upgradable_pkgs(cache, options,white_list_with_version) + pkgs_to_upgrade.sort(key=lambda p: p.name) + pkgs = [pkg.name for pkg in pkgs_to_upgrade] + logging.debug("%d pkgs that look like they should be upgraded or installed: %s" + % (len(pkgs),"\n".join(pkgs))) + + + # FIXME: make this into a ContextManager + # stop being nice + #os.nice(old_priority - os.nice(0)) + #adjust candidate versions + logging.info("adjusting candidate from kylin update manager...") + adjust_candidate_with_version(cache,white_list_with_version) + #sanity check + + # download what looks good + mark_pkgs_to_upgrade(cache, pkgs) + + if options.debug: + fetcher = apt_pkg.Acquire(apt.progress.text.AcquireProgress()) + else: + fetcher = apt_pkg.Acquire() + list = apt_pkg.SourceList() + list.read_main_list() + recs = cache._records + pm = apt_pkg.PackageManager(cache._depcache) + configfilemanager = ConfigFileManager("/var/lib/unattended-upgrades") + # don't start downloading during shutdown + # TODO: download files one by one and check for stop request after each of + # them + if should_stop(): + return UnattendedUpgradesResult(False, _("Upgrade was interrupted")) + try: + pm.get_archives(fetcher, list, recs) + except SystemError as e: + logging.error(_("GetArchives() failed: %s"), e) + + if get_abnormally_installed_pkg_count() == '0' and not is_dpkg_journal_dirty(): + local_pkgs_to_install = [] + for item in fetcher.items: + local_pkgs_to_install.append(item.destfile) + with open(OTA_PKGS_TO_INSTALL_LIST,'w+') as f: + f.write(" ".join(local_pkgs_to_install)) + + fetcher_statistics = AcquireStatistics(fetcher=fetcher) + fetcher_statistics.GetAquireStatisticsOfPkgs() + logging.debug("%d local,%d remote"%(fetcher_statistics.local_pkg_amount,fetcher_statistics.remote_pkg_amount)) + + if options.install_only: + if options.mode == 'shutdown': + if kylin_system_updater.GetConfigValue('InstallMode','shutdown_install'): + pass + else: + try: + apt_pkg.pkgsystem_unlock() + except SystemError: + logging.error(_("lock release failed")) + logging.info("system updater need to run shutdown install quiting...") + return UnattendedUpgradesResult(False,_("system updater install override")) + if fetcher_statistics.local_pkg_amount == 0: + logging.warning("no local pkgs to install") + try: + apt_pkg.pkgsystem_unlock() + except SystemError: + logging.error(_("lock release failed")) + return UnattendedUpgradesResult(True,_("no local pkgs to install")) + elif fetcher_statistics.remote_pkg_amount >0: + logging.warning("there're pkgs to download") + try: + apt_pkg.pkgsystem_unlock() + except SystemError: + logging.error(_("lock release failed")) + return UnattendedUpgradesResult(False,_("there're pkgs to download")) + else: + #only write the pkg list when dpkg journal is clean + # if not is_dpkg_journal_dirty(): + # configfilemanager.WriteListToFile(pkgs,"OTA_PKGS_TO_INSTALL_LIST") + try: + res = fetcher.run() + logging.debug("fetch.run() result: %s", res) + except SystemError as e: + logging.error("fetch.run() result: %s", e) + + # if cache.get_changes(): + # cache.clear() + + pkg_install_success = True + install_result = '' + if len(pkgs_to_upgrade) > 0: + if 'PROJECT_CODENAME' in os_release_info: + if os_release_info['PROJECT_CODENAME']=='V10SP1-edu': + if 'SUB_PROJECT_CODENAME' in os_release_info: + if os_release_info['SUB_PROJECT_CODENAME']=='mavis': + pass + else: + logging.info("neeed backup") + #Backup() + # do install + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","install") + #send install start msg to notify + # if os.path.exists(NOTIFICATION_PIPE): + # with open(NOTIFICATION_PIPE,'w') as p: + # p.write('install start') + with open(PROGRESS_LOG,'w+') as f: + f.write('0') + subprocess.Popen('dbus-send --system --type=signal / com.kylin.install.notification.InstallStart',shell=True) + inhibitshutdownlock.lock() + # if LockedPreventShutdown(): + # pass + # else: + # logging.error("cannot get shutdown lock,exiting...") + # WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + # sys.exit(1) + + logging.debug("InstCount=%i DelCount=%i BrokenCount=%i" + % (cache._depcache.inst_count, + cache._depcache.del_count, + cache._depcache.broken_count)) + + pkg_install_success = do_install(cache, + pkgs, + options, + logfile_dpkg) + + # unLockedEnableShutdown() + inhibitshutdownlock.unlock() + subprocess.Popen('dbus-send --system --type=signal / com.kylin.install.notification.InstallFinish',shell=True) + if pkg_install_success: + clean_downloaded_packages(fetcher) + kylin_system_updater.CheckRebootRequired("unattended-upgrades") + + logging.debug("pkg number:%d,pkg in whitelist number:%d"%(len(pkgs),len(white_list_with_version))) + if len(pkgs) == len(white_list_with_version): + install_result = "total_install" + else: + install_result = "partial_install" + logging.debug("install result:%s"%install_result) + try: + apt_pkg.pkgsystem_unlock() + except SystemError: + logging.error(_("lock release failed")) + return UnattendedUpgradesResult(pkg_install_success, + install_result, + pkgs) + + elif options.download_only: + + if fetcher_statistics.remote_pkg_amount>0: + pass + else: + logging.info("no pkgs need to download") + #return UnattendedUpgradesResult(True,_("there're no pkgs to download")) + retry_times=10 + if retry_times<0: + retry_times = 1 + while retry_times >0: + try: + res = fetcher.run() + logging.debug("fetch.run() result: %s", res) + except SystemError as e: + logging.error("fetch.run() result: %s", e) + fetcher_statistics.ResetFetcher(fetcher) + fetcher_statistics.GetAquireStatisticsOfPkgs() + logging.debug("incomplete download pkg number:%d"%fetcher_statistics.incomplete_pkg_amount) + retry_times-=1 + if fetcher_statistics.incomplete_pkg_amount >0: + logging.debug("%d incomplete pkgs,%d try times left") + fetcher.shutdown() + try: + pm.get_archives(fetcher, list, recs) + except SystemError as e: + logging.error(_("GetArchives() failed: %s"), e) + else: + break + #fetcher_statistics.ResetFetcher(fetcher) + #fetcher_statistics.GetAquireStatisticsOfPkgs() + insmod = ReadValueFromFile("/var/lib/unattended-upgrades/unattended-upgrades-policy.conf","autoUpgradePolicy","installmode") + if fetcher_statistics.incomplete_pkg_amount == 0 and len(pkgs_to_upgrade) > 0: + if os_release_info['PROJECT_CODENAME'] == 'V10SP1-edu' and os_release_info['SUB_PROJECT_CODENAME']=='mavis': + docker_image_fetch_result = 0 + #docker image fetch for mavis and laika + if os.path.exists("/usr/bin/service_runtime_ota.sh"): + docker_image_fetch_result = subprocess.run(["/usr/bin/service_runtime_ota.sh"], shell=True) + if docker_image_fetch_result.returncode == 0: + logging.info("all pkgs downloaded") + else: + return UnattendedUpgradesResult(False,_("docker fetch failed")) + logging.info("pkg number:%d"%(len(pkgs))) + login_manager.SetExtraInhibitShutdownDelaySec(1800) + configfilemanager.AddFileName("OTA_PKGS_TO_INSTALL") + subprocess.Popen('dbus-send --system --type=signal / com.kylin.update.notification.DownloadFinish', shell=True) + kylin_system_updater.SetConfigValue('InstallMode','auto_install','True') + elif insmod == 'bshutdown': + logging.info("pkg number:%d"%(len(pkgs))) + login_manager.SetExtraInhibitShutdownDelaySec(1800) + configfilemanager.AddFileName("OTA_PKGS_TO_INSTALL") + kylin_system_updater.SetConfigValue('InstallMode','auto_install','True') + elif insmod == 'timing': + pass + else: + pass + try: + apt_pkg.pkgsystem_unlock() + except SystemError: + logging.error(_("lock release failed")) + return UnattendedUpgradesResult(True,_("all pkgs downloaded")) + else: + try: + apt_pkg.pkgsystem_unlock() + except SystemError: + logging.error(_("lock release failed")) + return UnattendedUpgradesResult(False,_("some pkgs incompletely fetched")) + else: + try: + apt_pkg.pkgsystem_unlock() + except SystemError: + logging.error(_("lock release failed")) + logging.debug(_("option is not install-only or download-only")) + try: + res = fetcher.run() + logging.debug("fetch.run() result: %s", res) + except SystemError as e: + logging.error("fetch.run() result: %s", e) + return UnattendedUpgradesResult(False,_("option is not install-only or download-only")) + + + ''' + pkg_conffile_prompt = False + if dpkg_conffile_prompt(): + # now check the downloaded debs for conffile conflicts and build + # a blacklist + conffile_blacklist = [] # type: List[str] + for item in fetcher.items: + logging.debug("%s" % item) + if item.status == item.STAT_ERROR: + print(_("An error occurred: %s") % item.error_text) + logging.error(_("An error occurred: %s"), item.error_text) + if not item.complete: + print(_("The URI %s failed to download, aborting") % + item.desc_uri) + logging.error(_("The URI %s failed to download, aborting"), + item.desc_uri) + return UnattendedUpgradesResult( + False, (_("The URI %s failed to download, aborting") % + item.desc_uri)) + if not os.path.exists(item.destfile): + print(_("Download finished, but file %s not there?!?") % + item.destfile) + logging.error("Download finished, but file %s not " + "there?!?", item.destfile) + return UnattendedUpgradesResult( + False, (_("Download finished, but file %s not there?!?") % + item.destfile)) + if not item.is_trusted and not apt_pkg.config.find_b( + "APT::Get::AllowUnauthenticated", False): + logging.debug("%s is blacklisted because it is not trusted") + pkg_name = pkgname_from_deb(item.destfile) + if not is_pkgname_in_blacklist(pkg_name, cache.blacklist): + conffile_blacklist.append("%s$" % re.escape(pkg_name)) + if not is_deb(item.destfile): + logging.debug("%s is not a .deb file" % item) + continue + if conffile_prompt(item.destfile): + # skip package (means to re-run the whole marking again + # and making sure that the package will not be pulled in by + # some other package again!) + # + # print to stdout to ensure that this message is part of + # the cron mail (only if no summary mail is requested) + email = apt_pkg.config.find("Unattended-Upgrade::Mail", "") + if not email: + print(_("Package %s has conffile prompt and needs " + "to be upgraded manually") % + pkgname_from_deb(item.destfile)) + # log to the logfile + logging.warning(_("Package %s has conffile prompt and " + "needs to be upgraded manually"), + pkgname_from_deb(item.destfile)) + pkg_name = pkgname_from_deb(item.destfile) + if not is_pkgname_in_blacklist(pkg_name, cache.blacklist): + conffile_blacklist.append("%s$" % re.escape(pkg_name)) + pkg_conffile_prompt = True + + # redo the selection about the packages to upgrade based on the new + # blacklist + logging.debug("Packages blacklist due to conffile prompts: %s" + % conffile_blacklist) + # find out about the packages that are upgradable (in a allowed_origin) + if len(conffile_blacklist) > 0: + for regex in conffile_blacklist: + cache.blacklist.append(regex) + cache.apply_pinning(cache.pinning_from_regex_list( + conffile_blacklist, NEVER_PIN)) # type: ignore + old_pkgs_to_upgrade = pkgs_to_upgrade[:] + pkgs_to_upgrade = [] + for pkg in old_pkgs_to_upgrade: + logging.debug("Checking the black and whitelist: %s" % + (pkg.name)) + cache.mark_upgrade_adjusted( + pkg, from_user=not pkg.is_auto_installed) + if check_changes_for_sanity(cache): + pkgs_to_upgrade.append(pkg) + else: + logging.info(_("package %s not upgraded"), pkg.name) + cache.clear() + for pkg2 in pkgs_to_upgrade: + cache.call_adjusted( + apt.package.Package.mark_upgrade, pkg2, + from_user=not pkg2.is_auto_installed) + if cache.get_changes(): + cache.clear() + + else: + logging.debug("dpkg is configured not to cause conffile prompts") + + # auto-removals + kernel_pkgs_remove_success = True # type: bool + kernel_pkgs_removed = [] # type: List[str] + kernel_pkgs_kept_installed = [] # type: List[str] + if (auto_removable and apt_pkg.config.find_b( + "Unattended-Upgrade::Remove-Unused-Kernel-Packages", True)): + # remove unused kernels before installing new ones because the newly + # installed ones may fill up /boot and break the system right before + # removing old ones could take place + # + # this step may also remove _auto-removable_ reverse dependencies + # of kernel packages + auto_removable_kernel_pkgs = { + p for p in auto_removable + if (cache.versioned_kernel_pkgs_regexp + and cache.versioned_kernel_pkgs_regexp.match(p) + and not cache.running_kernel_pkgs_regexp.match(p))} + if auto_removable_kernel_pkgs: + logging.info(_("Removing unused kernel packages: %s"), + " ".join(auto_removable_kernel_pkgs)) + (kernel_pkgs_remove_success, + kernel_pkgs_removed, + kernel_pkgs_kept_installed) = do_auto_remove( + cache, auto_removable_kernel_pkgs, logfile_dpkg, + options.minimal_upgrade_steps, + options.verbose or options.debug, options.dry_run) + auto_removable = get_auto_removable(cache) + + previous_autoremovals = auto_removable + if apt_pkg.config.find_b( + "Unattended-Upgrade::Remove-Unused-Dependencies", False): + pending_autoremovals = previous_autoremovals + else: + pending_autoremovals = set() + + # exit if there is nothing to do and nothing to report + if (len(pending_autoremovals) == 0 + and len(pkgs_to_upgrade) == 0): + logging.info(_("No packages found that can be upgraded unattended " + "and no pending auto-removals")) + + pkgs_kept_back = cache.find_kept_packages(options.dry_run) + return UnattendedUpgradesResult( + kernel_pkgs_remove_success, + _("No packages found that can be upgraded unattended and no " + "pending auto-removals"), + pkgs_removed=kernel_pkgs_removed, + pkgs_kept_back=pkgs_kept_back, + pkgs_kept_installed=kernel_pkgs_kept_installed, + update_stamp=True) + + # check if its configured for install on shutdown, if so, the + # environment UNATTENDED_UPGRADES_FORCE_INSTALL_ON_SHUTDOWN will + # be set by the unatteded-upgrades-shutdown script + if ("UNATTENDED_UPGRADES_FORCE_INSTALL_ON_SHUTDOWN" not in os.environ + and apt_pkg.config.find_b( + "Unattended-Upgrade::InstallOnShutdown", False)): + logger = logging.getLogger() + logger.debug("Configured to install on shutdown, so exiting now") + return UnattendedUpgradesResult(True) + + # check if we are in dry-run mode + if options.dry_run: + logging.info("Option --dry-run given, *not* performing real actions") + apt_pkg.config.set("Debug::pkgDPkgPM", "1") + + # do the install based on the new list of pkgs + pkgs = [pkg.name for pkg in pkgs_to_upgrade] + logging.info(_("Packages that will be upgraded: %s"), " ".join(pkgs)) + + # only perform install step if we actually have packages to install + pkg_install_success = True + if len(pkgs_to_upgrade) > 0: + if 'PROJECT_CODENAME' in os_release_info: + if os_release_info['PROJECT_CODENAME']=='V10SP1-edu': + if 'SUB_PROJECT_CODENAME' in os_release_info: + if os_release_info['SUB_PROJECT_CODENAME']=='mavis': + pass + else: + Backup() + # do install + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","install") + #send install start msg to notify + # if os.path.exists(NOTIFICATION_PIPE): + # with open(NOTIFICATION_PIPE,'w') as p: + # p.write('install start') + with open(PROGRESS_LOG,'w+') as f: + f.write('0') + subprocess.Popen('dbus-send --system --type=signal / com.kylin.install.notification.InstallStart',shell=True) + if LockedPreventShutdown(): + pass + else: + logging.error("cannot get shutdown lock,exiting...") + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + sys.exit(1) + + logging.debug("InstCount=%i DelCount=%i BrokenCount=%i" + % (cache._depcache.inst_count, + cache._depcache.del_count, + cache._depcache.broken_count)) + + pkg_install_success = do_install(cache, + pkgs, + options, + logfile_dpkg) + unLockedEnableShutdown() + subprocess.Popen('dbus-send --system --type=signal / com.kylin.install.notification.InstallFinish',shell=True) + if pkg_install_success: + kylin_system_updater.CheckRebootRequired("unattended-upgrades") + # Was the overall run succesful: only if everything installed + # fine and nothing was held back because of a conffile prompt. + # successful_run = (kernel_pkgs_remove_success and pkg_install_success + # and not pkg_conffile_prompt) + #send install finish msg to notify + # if successful_run and os.path.exists(NOTIFICATION_PIPE): + # with open(NOTIFICATION_PIPE,'w') as p: + # p.write('install finish') + # now check if any auto-removing needs to be done + + if cache._depcache.broken_count > 0: + print(_("Cache has broken packages, exiting")) + logging.error(_("Cache has broken packages, exiting")) + return UnattendedUpgradesResult( + False, _("Cache has broken packages, exiting"), pkgs=pkgs) + + # make sure we start autoremovals with a clear cache + # if cache.get_changes(): + # cache.clear() + + + # the user wants *all* auto-removals to be removed + # (unless u-u got signalled to stop gracefully quickly) + pkgs_removed = [] # type: List[str] + pkgs_kept_installed = [] # type: List[str] + if ((apt_pkg.config.find_b( + "Unattended-Upgrade::Remove-Unused-Dependencies", False) + and not SIGNAL_STOP_REQUEST)): + auto_removals = get_auto_removable(cache) + (pkg_remove_success, + pkgs_removed, + pkgs_kept_installed) = do_auto_remove( + cache, auto_removals, logfile_dpkg, options.minimal_upgrade_steps, + options.verbose or options.debug, + options.dry_run) + successful_run = successful_run and pkg_remove_success + # the user wants *only new* auto-removals to be removed + elif apt_pkg.config.find_b( + "Unattended-Upgrade::Remove-New-Unused-Dependencies", False): + # calculate the new auto-removals + new_pending_autoremovals = get_auto_removable(cache) + auto_removals = new_pending_autoremovals - previous_autoremovals + (pkg_remove_success, + pkgs_removed, + pkgs_kept_installed) = do_auto_remove( + cache, auto_removals, logfile_dpkg, options.minimal_upgrade_steps, + options.verbose or options.debug, + options.dry_run) + successful_run = successful_run and pkg_remove_success + + logging.debug("InstCount=%i DelCount=%i BrokenCount=%i" + % (cache._depcache.inst_count, + cache._depcache.del_count, + cache._depcache.broken_count)) + + clean after success install (if needed) + keep_key = "Unattended-Upgrade::Keep-Debs-After-Install" + + if (not apt_pkg.config.find_b(keep_key, False) + and not options.dry_run + and pkg_install_success): + clean_downloaded_packages(fetcher) + + pkgs_kept_back = cache.find_kept_packages(options.dry_run) + return UnattendedUpgradesResult( + successful_run, _("All upgrades installed"), pkgs, + pkgs_kept_back, + kernel_pkgs_removed + pkgs_removed, + kernel_pkgs_kept_installed + pkgs_kept_installed, + update_stamp=True) + + install_result = '' + logging.debug("pkg number:%d,pkg in whitelist number:%d"%(len(pkgs),len(namelist_with_version))) + if len(pkgs) == len(namelist_with_version): + install_result = "total_install" + else: + install_result = "partial_install" + logging.debug("install result:%s"%install_result) + return UnattendedUpgradesResult(pkg_install_success, + install_result, + pkgs) + ''' + +class Options: + def __init__(self): + self.download_only = False + self.install_only = False + self.dry_run = False + self.debug = False + self.apt_debug = False + self.verbose = False + self.minimal_upgrade_steps = False + self.mode = None + +shutdown_lock = -1 + +if __name__ == "__main__": + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + # lock for the shutdown check + shutdown_lock = apt_pkg.get_lock(LOCK_FILE) + if shutdown_lock < 0: + logging.error("Lock file is already taken, exiting") + #WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + sys.exit(1) + localesApp = "unattended-upgrades" + localesDir = "/usr/share/locale" + gettext.bindtextdomain(localesApp, localesDir) + gettext.textdomain(localesApp) + + # set debconf to NON_INTERACTIVE + os.environ["DEBIAN_FRONTEND"] = "noninteractive" + apt_pkg.init_config() + #remove sources in sources.list.d from indexes search + apt_pkg.config.set("Dir::Etc::sourceparts", "") + # this ensures the commandline is logged in /var/log/apt/history.log + apt_pkg.config.set("Commandline::AsString", " ".join(sys.argv)) + + # COMPAT with the mispelling + # minimal_steps_default = ( + # apt_pkg.config.find_b("Unattended-Upgrades::MinimalSteps", False) + # and apt_pkg.config.find_b("Unattended-Upgrade::MinimalSteps", False)) + minimal_steps_default = False + # init the options + parser = OptionParser() + parser.add_option("-d", "--debug", + action="store_true", + default=True, + # default=apt_pkg.config.find_b( + # "Unattended-Upgrade::Debug", False), + help=_("print debug messages")) + parser.add_option("", "--apt-debug", + action="store_true", default=False, + help=_("make apt/libapt print verbose debug messages")) + parser.add_option("-v", "--verbose", + action="store_true", + default=apt_pkg.config.find_b( + "Unattended-Upgrade::Verbose", False), + help=_("print info messages")) + parser.add_option("", "--dry-run", + action="store_true", default=False, + help=_("Simulation, download but do not install")) + parser.add_option("", "--download-only", + action="store_true", default=False, + help=_("Only download, do not even try to install.")) + parser.add_option("", "--install-only", + action="store_true", default=False, + help=_("Only install, do not even try to download.")) + parser.add_option("", "--minimal-upgrade-steps", + action="store_true", default=minimal_steps_default, + help=_("Upgrade in minimal steps (and allow " + "interrupting with SIGTERM) (default)")) + parser.add_option("", "--no-minimal-upgrade-steps", + action="store_false", default=minimal_steps_default, + dest="minimal_upgrade_steps", + help=_("Upgrade all packages together instead of in " + "smaller sets")) + parser.add_option("", "--minimal_upgrade_steps", + action="store_true", + help=SUPPRESS_HELP, + default=minimal_steps_default) + parser.add_option(""''"","--mode",action = "store",type = "string" , + dest = "mode",help="start mode.") + options = cast(Options, (parser.parse_args())[0]) + + if os.getuid() != 0: + print(_("You need to be root to run this application")) + sys.exit(1) + + # ensure that we are not killed when the terminal goes away e.g. on + # shutdown + signal.signal(signal.SIGHUP, signal.SIG_IGN) + + # setup signal handler for graceful stopping + signal.signal(signal.SIGTERM, signal_handler) + + signal.signal(signal.SIGINT,signal_handler_int) + + signal.signal(signal.SIGUSR1,signal_handler_usr1) + # write pid to let other processes find this one + + # pidf = os.path.join(apt_pkg.config.find_dir("Dir"), + # "var", "run", "unattended-upgrades.pid") + # clean up pid file on exit + with open(PID_FILE, "w") as fp: + fp.write("%s" % os.getpid()) + atexit.register(os.remove, PID_FILE) + #setup log dir + # logdir = os.path.join("var", "log", "kylin-unattended-upgrades") + logdir = "/var/log/kylin-unattended-upgrades" + if not os.path.exists(logdir): + os.makedirs(logdir) + #setup log + logfile = os.path.join(logdir, 'unattended-upgrades.log') + if not os.path.exists(logfile): + with open(logfile, 'w'): + pass + logfile_fd = open(logfile,'a+') + #setup dpkg log + logfile_dpkg = os.path.join(logdir, 'unattended-upgrades-dpkg.log') + if not os.path.exists(logfile_dpkg): + with open(logfile_dpkg, 'w'): + pass + + # setup logging + _setup_logging(options,logfile) + #get os release info + os_release_info = ReadOsRelease('/etc/os-release') + #print(os_release_info) + config_manager = ConfigFileManager(CONFIG_FILE_ROOT_PATH) + login_manager = LoginManager() + kylin_system_updater = KylinSystemUpdater() + ''' + if os_release_info['PROJECT_CODENAME'] == 'V10SP1-edu' and os_release_info['SUB_PROJECT_CODENAME']=='mavis': + pass + else: + allow_autoupdate = kylin_system_updater.GetDatabaseInfo("display","autoupdate_allow") + if allow_autoupdate == "true": + pass + else: + logging.info("auto upgrade not allow, exit") + sys.exit(0) + ''' + #check control center lock + ''' + if os.path.exists(CONTROL_PANEL_LOCK_FILE): + file_lock = FILE_LOCK(CONTROL_PANEL_LOCK_FILE) + if file_lock.get_lock(): + logging.debug("control center not running") + file_lock.unlock() + else: + logging.warning("control center running ,exiting...") + sys.exit(1) + ''' + # package_deps = {} + kylin_system_updater.ConnectToSignals() + kylin_system_updater.UpdateDetect() + kylin_system_updater.RunMainloop() + inhibitshutdownlock = InhibitShutdownLock() + if options.download_only: + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","download") + elif options.install_only: + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","preinstall") + # run the main code + install_start_time = datetime.datetime.now().replace(microsecond=0) + logging.info("unattended-upgrades start time:%s"%install_start_time) + # get log + ''' + dpkg_journal_dirty = is_dpkg_journal_dirty() + abnormal_pkg_count = get_abnormally_installed_pkg_count() + logging.info("abnormal pkg count:%s,dpkg dirty:%s"%(abnormal_pkg_count,dpkg_journal_dirty)) + if os_release_info['PROJECT_CODENAME'] == 'V10SP1-edu' and os_release_info['SUB_PROJECT_CODENAME']=='mavis': + if dpkg_journal_dirty or abnormal_pkg_count != '0': + ret = subprocess.run("dpkg --configure -a",shell=True,stdout=open(logfile,'a+'),stderr=open(logfile,'a+')) + logging.info("dpkg fix return :%s"%ret.returncode) + ''' + + sys.exit(main(options)) diff --git a/unattended-upgrades/kylin-unattended-upgrade-shutdown b/unattended-upgrades/kylin-unattended-upgrade-shutdown new file mode 100644 index 0000000..8a4ce3e --- /dev/null +++ b/unattended-upgrades/kylin-unattended-upgrade-shutdown @@ -0,0 +1,1240 @@ +#!/usr/bin/python3 +# Copyright (c) 2009-2018 Canonical Ltd +# +# AUTHOR: +# Michael Vogt +# Balint Reczey +# +# unattended-upgrade-shutdown - helper that checks if a +# unattended-upgrade is in progress and waits until it exists +# +# This file is part of unattended-upgrades +# +# unattended-upgrades 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. +# +# unattended-upgrades 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 unattended-upgrades; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import copy +import dbus +import signal +import sys +import time +import datetime +import logging +import logging.handlers +import gettext +import subprocess +import os.path +import os +import configparser +import psutil +from pytz import timezone +# for dbus signal handling +try: + from dbus.mainloop.glib import DBusGMainLoop + from gi.repository import GLib +except ImportError: + pass + +from optparse import OptionParser, Values +Values # pyflakes +from gettext import gettext as _ +from threading import Event +from enum import IntEnum, Enum +from apscheduler.schedulers.blocking import BlockingScheduler +from apscheduler.schedulers.background import BackgroundScheduler +import random +import threading +import re + +try: + import apt_pkg +except Exception: + # if there is no python-apt no unattended-upgrades can run so not + # need to stop the shutdown + logging.exception("importing of apt_pkg failed, exiting") + sys.exit(0) + +# progress information is written here +PROGRESS_LOG = "/var/run/unattended-upgrades.progress" +PID_FILE = "/var/run/unattended-upgrades.pid" +LOCK_FILE = "/var/run/kylin-unattended-upgrades.lock" +PKGS_TO_INSTALL_FLAG_FILE="/var/lib/unattended-upgrades/OTA_PKGS_TO_INSTALL" +TIME_STAMP = "/var/lib/unattended-upgrades/unattended-upgrades-timestamp" +OTA_PKGS_TO_INSTALL_LIST="/var/lib/unattended-upgrades/ota_pkgs_to_install_list" + +## analytic unattended-upgrades-policy.conf start +POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY = "autoUpgradePolicy" +AUTO_UPGRADE_POLICY_OPTION_PREDOWNLOAD = "preDownload" +AUTO_UPGRADE_POLICY_OPTION_AUTOUPGRADE = "autoUpgradeState" +AUTO_UPGRADE_POLICY_OPTION_DOWNLOAD_MODE = "downloadMode" +AUTO_UPGRADE_POLICY_OPTION_DOWNLOAD_TIME = "downloadTime" +AUTO_UPGRADE_POLICY_OPTION_INSTALL_MODE = "installMode" +AUTO_UPGRADE_POLICY_OPTION_UPGRADE_INTERVAL = "upgradeInverval" +INTERVAL_DOWN_INSTALL = 120 # 下载安装的间隔 分钟 +INSTALL_RANDOM = 5 # 安装时间随机数范围0-INSTALL_RANDOM 分钟 +DOWNLOAD_RANDOM = 180 # 下载时间随机数范围0-DOWNLOAD_RANDOM 分钟 +PREDOWNLOAD_RANDOM = 180 + +class FeatureSwitch(Enum): + ON = 'on' + OFF = 'off' + +class DownloadMode(Enum): # 下载模式 + TIMING_DOWNLOAD = 'timing' # 定时下载 + MANUAL_DOWNLOAD = 'manual' # 手动下载 + +class InstallMode(Enum): # 安装模式 + TIMING_INSTALL = 'timing' # 定时安装 + MANUAL_INSTALL = 'manual' # 手动安装 + BEFORE_SHUTDOWN_INSTALL = 'bshutdown' # 关机前安装 + +class TimeElement(IntEnum): + TIME_HOUR = 0 + TIME_MINUTE = 1 + TIME_NUM = 2 + +## analytic unattended-upgrades-policy.conf end + +UNATTENDED_UPGRADE_CONFIG_FILE_PATH="/var/lib/unattended-upgrades/unattended-upgrade.conf" +UNATTENDED_UPGRADE_POLICY_FILE_PATH="/var/lib/unattended-upgrades/unattended-upgrades-policy.conf" +NOTIFICATION_PIPE = '/tmp/notification.pipe' +UNATTENDED_UPGRADE_TIMESTAMP = "/var/lib/unattended-upgrades/unattended-upgrades-timestamp" + +flag_file_list = ["/var/lib/unattended-upgrades/OTA_PKGS_TO_INSTALL",\ + "/var/lib/kylin-auto-upgrade/kylin-autoupgrade.conf","/tmp/notify.pid"] + +def reload_options_config(): + #添加默认保留旧配置 + apt_pkg.config["DPkg::Options::"] = "--force-confold" + options_new = list(set(apt_pkg.config.value_list("DPkg::Options"))) + for option in ("--force-confnew","--force-confdef"): + if option in options_new: + options_new.remove(option) + #清除所有配置重新加载 + apt_pkg.config.clear("DPkg::Options") + for option in options_new: + apt_pkg.config["DPkg::Options::"] = option + #去除安装推荐和建议的软件包 + if apt_pkg.config.find_b("APT::Install-Recommends",False) == True: + apt_pkg.config.clear("APT::Install-Recommends") + if apt_pkg.config.find_b("APT::Install-Suggests",False) == True: + apt_pkg.config.clear("APT::Install-Suggests") + if apt_pkg.config.find("Dir::Etc::sourceparts","")!="": + apt_pkg.config["Dir::Etc::sourceparts"]="" + apt_pkg.init_system() + +def get_mavis_capacity(): + batterycapacity = 100 + try: + with open("/sys/class/power_supply/BAT1/capacity", 'r') as f: + batterycapacity = int(f.readline()) + except: + logging.error("no battery device") + return batterycapacity + + +def is_dpkg_journal_dirty(): + # type: () -> bool + """ + Return True if the dpkg journal is dirty + (similar to debSystem::CheckUpdates) + """ + logging.debug("checking whether dpkg journal is dirty") + d = os.path.join("/var/lib/dpkg/", + #os.path.dirname(apt_pkg.config.find_file("Dir::State::status")), + "updates") + for f in os.listdir(d): + if re.match("[0-9]+", f) or re.match("tmp.i",f): + return True + return False + +def get_abnormally_installed_pkg_count(): + output = subprocess.check_output('dpkg -l|grep ^i[^i]|wc -l',shell=True) + return output.decode().strip() + + +def GetDateTime(): + return datetime.datetime.now().replace(microsecond=0) + +def ReadOsRelease(file): + osreleasedict = {} + try: + with open(file) as f: + lines = f.readlines() + for line in lines: + ls = line.strip().split('=',1) + osreleasedict.update({ls[0]:ls[1].strip('"')}) + except Exception as e: + pass + if 'SUB_PROJECT_CODENAME' not in osreleasedict.keys(): + osreleasedict.update({'SUB_PROJECT_CODENAME':''}) + return osreleasedict + +def FindRuningUnattendedUpgrades(): + if os.path.exists(PID_FILE): + pid = open(PID_FILE).readline().strip() + logging.info("runing unattended-upgrades pid:%s"%pid) + try: + ps = psutil.Process(int(pid)) + logging.debug("process name:%s,process status:%s"%(ps.name(),ps.status())) + return ps.is_running() + except Exception as e: + logging.error(e) + return False + +def ReadValueFromFile(file,section,option): + config=configparser.ConfigParser(allow_no_value=True) + try: + config.read(file) + value = config[section][option] + except Exception as e: + return '' + return value + +def WriteValueToFile(file,section,option,value): + config=configparser.ConfigParser(allow_no_value=True) + config.add_section(section) + config.set(section,option,value) + config.write(open(file,"w")) + +def clean_flag_files(filelist): + WriteValueToFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status","idle") + for file in filelist: + if os.path.exists(file): + os.remove(file) +def init(): + if not os.path.exists(NOTIFICATION_PIPE): + os.mkfifo(NOTIFICATION_PIPE) + +def get_random_time(stime,random_range): + now = datetime.datetime.now() + delta = random.randint(0,random_range) + actual_time = now + datetime.timedelta(minutes=delta) + try: + start_time = datetime.datetime.strptime(stime,"%H:%M") + start=datetime.datetime(now.year,now.month,now.day,start_time.hour,start_time.minute,0,0) + actual_time = start+datetime.timedelta(minutes=delta) + except Exception as e: + logging.error(e) + return actual_time + +def task(task): + env = copy.copy(os.environ) + cmd = "date" + if task in ["predownload","download"]: + cmd = "kylin-unattended-upgrade --download-only" + elif task == "install": + cmd = "kylin-unattended-upgrade --install-only --mode=timing" + elif task == "download_and_install": + cmd = "kylin-unattended-upgrade --download-only&&kylin-unattended-upgrade --install-only --mode=timing" + #do not check updgrade period when download and install + else: + pass + ret = subprocess.run([cmd], shell=True,env=env) + logging.debug("task:%s return code:%d"%(task,ret.returncode)) + return ret.returncode + +def background_scheduler_init(background_scheduler): + + background_scheduler.start() + + random_time = get_random_time(autoupgradepolicy.GetOptionValue('downloadTime'),DOWNLOAD_RANDOM) + background_scheduler.add_job(task,'cron', args=['download'],id='download', \ + hour = random_time.hour,minute = random_time.minute,replace_existing=True) + + random_time = random_time + datetime.timedelta(minutes=INTERVAL_DOWN_INSTALL) + background_scheduler.add_job(task,'cron', args=['install'],id='install', \ + hour = random_time.hour,minute = random_time.minute,replace_existing=True) + + random_time = get_random_time(autoupgradepolicy.GetOptionValue('preDownloadTime'),PREDOWNLOAD_RANDOM) + background_scheduler.add_job(task,'cron', args=['predownload'],id='predownload', \ + hour = random_time.hour,minute = random_time.minute,replace_existing=True) + + if autoupgradepolicy.GetOptionValue('autoUpgradeState') == 'on': + if autoupgradepolicy.GetOptionValue('downloadMode') != 'timing': + background_scheduler.pause_job('download') + if autoupgradepolicy.GetOptionValue('installMode') != 'timing': + background_scheduler.pause_job('install') + else: + background_scheduler.pause_job('download') + background_scheduler.pause_job('install') + + if autoupgradepolicy.GetOptionValue('preDownload') != 'on': + background_scheduler.pause_job('predownload') + + + joblist = background_scheduler.get_jobs() + + for job in joblist: + logging.debug("job:%s,next run time:%s"%(job.id,job.next_run_time)) +''' +def do_usplash(msg): + # type: (str) -> None + if os.path.exists("/sbin/usplash_write"): + logging.debug("Running usplash_write") + subprocess.call(["/sbin/usplash_write", "TEXT", msg]) + subprocess.call(["/sbin/usplash_write", "PULSATE"]) +''' + +def do_plymouth(msg): + # type: (str) -> None + if os.path.exists("/bin/plymouth"): + for line in msg.split("\n"): + logging.debug("Running plymouth --text") + subprocess.call(["/bin/plymouth", "message", "--text", line]) + +def do_plymouth_splash(): + if os.path.exists("/bin/plymouth"): + logging.debug("Running plymouth --splash") + subprocess.run(["/sbin/plymouthd", "--mode=shutdown","--attach-to-session"]) + #subprocess.run(["/sbin/plymouthd", "--mode=update","--attach-to-session"]) + #subprocess.run(["/bin/plymouth","--update=kylin update"]) + subprocess.Popen(["/bin/plymouth", "show-splash","--wait"]) + subprocess.call(["/bin/plymouth","system-update","--progress=0"]) + +def log_msg(msg, level=logging.WARN): + # type: (str, int) -> None + """ helper that will print msg to usplash, plymouth, console """ + logging.log(level, msg) + #do_plymouth(msg) + #do_usplash(msg) + + +def log_progress(): + # type: () -> None + """ helper to log the install progress (if any) """ + # wait a some seconds and try again + ''' + msg = _("Unattended-upgrade in progress during shutdown, " + "please don't turn off the computer") + ''' + # progress info + progress_file = PROGRESS_LOG + if os.path.exists(progress_file): + progress_text = open(progress_file).read() + logging.debug("progress text content %s"%progress_text) + if len(progress_text): + progress = int(float(progress_text)) + subprocess.call(["/bin/plymouth","system-update","--progress=%d"%progress]) + msg = "upgrage progress %s"%progress_text + log_msg(msg) + +def signal_term_handler(signal,frame): + # type: (int, object) -> None + logging.warning("SIGTERM received, will stop") + os._exit(1) + +def signal_stop_unattended_upgrade(): + """ send SIGTERM to running unattended-upgrade if there is any """ + pidfile = PID_FILE#"/var/run/unattended-upgrades.pid" + if os.path.exists(pidfile): + pid = int(open(pidfile).read()) + logging.debug("found running unattended-upgrades pid %s" % pid) + try: + os.kill(pid, signal.SIGTERM) + except ProcessLookupError: + logging.debug("sending SIGTERM failed because unattended-upgrades " + "already stopped") + +def exit_log_result(success): + if os.path.exists(PKGS_TO_INSTALL_FLAG_FILE): + os.remove(PKGS_TO_INSTALL_FLAG_FILE) + subprocess.call(["/bin/plymouth","system-update","--progress=100"]) + time.sleep(3) + subprocess.run(["/bin/plymouth","quit","--retain-splash"]) + if success: + logging.debug("All upgrades installed") + #log_msg(_("All upgrades installed"), logging.INFO) + os._exit(0) + #sys.exit(0) + else: + log_msg(_("Unattended-upgrades stopped. There may be upgrades" + " left to be installed in the next run."), logging.INFO) + os._exit(1) + #sys.exit(1) + +# 检查时间安全性 -- minute > 59; hour > 23; +def check_time_safety(inTime): + if inTime['m'] > 59 : + inTime['h'] = inTime['h'] + inTime['m']//60 + inTime['m'] = inTime['m']%60 + + if inTime['h'] > 23 : + inTime['h'] = inTime['h'] - 24 + + outTime = inTime + return outTime + +# 时间添加随机数 +def convert_time_by_random(inTime, inRandom): + diff = random.randint(0,inRandom) + inTime['h']=inTime['h'] + diff // 60 + inTime['m']=inTime['m'] + diff % 60 + outTime = check_time_safety(inTime) + return outTime + +class TimerThread(threading.Thread): + def __init__(self, scheduler): + threading.Thread.__init__(self) + self.scheduler = scheduler + def run(self): + self.scheduler.start() + +class KylinSystemUpdater: + def __init__(self) -> None: + DBusGMainLoop(set_as_default=True) + self.loop = GLib.MainLoop() + self.system_bus = dbus.SystemBus() + self.update_proxy = self.system_bus.get_object('com.kylin.systemupgrade','/com/kylin/systemupgrade') + self.update_interface = dbus.Interface(self.update_proxy,dbus_interface='com.kylin.systemupgrade.interface') + + def GetConfigValue(self,section,value): + return self.update_interface.GetConfigValue(section,value) + + def SetConfigValue(self,section,option,value): + return self.update_interface.SetConfigValue(section,option,value) + +class AutoUpgradePolicy(): + def __init__(self) -> None: + self.autoupgradepolicy = {} + if os.path.exists(UNATTENDED_UPGRADE_POLICY_FILE_PATH): + config=configparser.ConfigParser(allow_no_value=True) + config.optionxform = str + config.read(UNATTENDED_UPGRADE_POLICY_FILE_PATH) + for option in config.options('autoUpgradePolicy'): + self.autoupgradepolicy.update({option:config['autoUpgradePolicy'][option]}) + for key in self.autoupgradepolicy.keys(): + logging.debug("%s:%s"%(key,self.autoupgradepolicy[key])) + + def SetOptionValue(self,option,value): + self.autoupgradepolicy.update({option:value}) + + def GetOptionValue(self,option): + try: + return self.autoupgradepolicy[option] + except Exception: + return '' + + def reload_config(self): + if os.path.exists(UNATTENDED_UPGRADE_POLICY_FILE_PATH): + config=configparser.ConfigParser(allow_no_value=True) + config.optionxform = str + config.read(UNATTENDED_UPGRADE_POLICY_FILE_PATH) + for option in config.options('autoUpgradePolicy'): + self.autoupgradepolicy.update({option:config['autoUpgradePolicy'][option]}) + for key in self.autoupgradepolicy.keys(): + logging.debug("%s:%s"%(key,self.autoupgradepolicy[key])) + +class UnattendedUpgradesShutdown(): + # 加载配置文件 unattended-upgrades-policy.conf + ''' + def loadcfg(self): + if os.path.exists(UNATTENDED_UPGRADE_POLICY_FILE_PATH): + self.preDownload = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_PREDOWNLOAD) + self.autoUpgrade = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_AUTOUPGRADE) + self.download_mode = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_DOWNLOAD_MODE) + self.install_mode = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_INSTALL_MODE) + download_time = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_DOWNLOAD_TIME) + # self.download_random = int(kylin_system_updater.GetConfigValue('AutoUpgradeConfig','downloadRandom')[1]) + # self.upgrade_interval = int(kylin_system_updater.GetConfigValue('AutoUpgradeConfig','upgradeInterval')[1]) + # logging.info("download random:%s,upgrade interval:%s"%(self.download_random,self.upgrade_interval)) + # upgradeInterval = int(ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, 'upgradeInverval')) + + if os_release_info['PROJECT_CODENAME'] == 'V10SP1-edu' and os_release_info['SUB_PROJECT_CODENAME']=='mavis': + self.download_time['h'] = 10 + self.download_time['m'] = 0 + self.download_time_r = convert_time_by_random(self.download_time, 5) + logging.debug("upgrade time: [%d] [%d] predown[%s] autoupgrade[%s] d-mode[%s] i-mode[%s]", + self.download_time_r['h'], self.download_time_r['m'],self.preDownload, self.autoUpgrade, \ + self.download_mode, self.install_mode) + return + + timelist = download_time.strip().split(':') + + if len(timelist) != TimeElement.TIME_NUM: + logging.debug("unattended-upgrades-policy.conf time err %s",download_time) + return + + # 检查 传入时间 安全性 + try: + tmphour = int(timelist[TimeElement.TIME_HOUR]) + except ValueError: + logging.debug("unattended-upgrades-policy.conf download_time h error") + return + try: + tmpminute = int(timelist[TimeElement.TIME_MINUTE]) + except ValueError: + logging.debug("unattended-upgrades-policy.conf download_time m error") + return + + self.download_time['h'] = tmphour + self.download_time['m'] = tmpminute + self.download_time_r = convert_time_by_random(self.download_time, self.download_random) + self.install_time['h'] = self.download_time_r['h'] + self.install_time['m'] = self.download_time_r['m'] + INTERVAL_DOWN_INSTALL + self.install_time_r = convert_time_by_random(self.install_time, INSTALL_RANDOM) + logging.debug("upgrade time: [%d:%d] [%d:%d] predown[%s] autoupgrade[%s] d-mode[%s] i-mode[%s]", + self.download_time_r['h'], self.download_time_r['m'],self.install_time_r['h'],self.install_time_r['m'], + self.preDownload, self.autoUpgrade, self.download_mode, self.install_mode) + else: + logging.debug("unattended-upgrades-policy.conf not exist") + return + ''' + def __init__(self, options): + # type: (Values) -> None + self.options = options + self.max_delay = options.delay * 60 + self.mainloop = GLib.MainLoop() + self.iter_timer_set = False + self.apt_pkg_reinit_done = None + self.shutdown_pending = False + self.on_shutdown_mode = None + self.on_shutdown_mode_uu_proc = None + self.start_time = None + self.lock_was_taken = False + self.signal_sent = False + self.stop_signal_received = Event() + ''' + self.download_mode = DownloadMode.TIMING_DOWNLOAD.value #下载模式 + self.install_mode = InstallMode.TIMING_INSTALL.value #安装模式 + self.download_time = {'h':9, 'm':0} #定时下载时间 09:00 + self.install_time = {'h':12, 'm':0} #定时安装时间 12:00 + self.download_time_r = convert_time_by_random(self.download_time, DOWNLOAD_RANDOM) #随机化定时下载时间 + self.install_time_r = convert_time_by_random(self.install_time, INSTALL_RANDOM) #随机化定时安装时间 + self.preDownload = 'off' #预下载开关 + self.autoUpgrade = 'off' #自动更新开关 + self.download_job = None + self.install_job = None + self.startup_download_job = None + self.scheduler = BlockingScheduler(timezone = "Asia/Shanghai") + ''' + try: + hasattr(GLib, "MainLoop") + DBusGMainLoop(set_as_default=True) + except NameError: + logging.error("DBusGMainLoop error") + pass + + try: + self.inhibit_lock = self.get_inhibit_shutdown_lock() + except dbus.exceptions.DBusException: + logging.warning("Could not get delay inhibitor lock") + self.inhibit_lock = None + self.logind_proxy = None + self.update_proxy = None + self.wait_period = min(3, self.get_inhibit_max_delay() / 3) + self.preparing_for_shutdown = False + #self.loadcfg() + + def get_update_proxy(self): + if not self.update_proxy: + bus = dbus.SystemBus() + self.update_proxy = bus.get_object('com.kylin.systemupgrade','/com/kylin/systemupgrade') + return self.update_proxy + + def get_update_interface(self): + self.update_interface = dbus.Interface(self.update_proxy,dbus_interface='com.kylin.systemupgrade.interface') + return self.update_interface + ''' + def set_max_inhibit_time(self,time): + login_proxy = self.get_logind_proxy() + #首先设置systemd默认延长时间为1800 + try: + getter_interface = dbus.Interface( + login_proxy, + dbus_interface='org.freedesktop.login1.Manager') + ret = getter_interface.SetInhibitDelayMaxSec(time) + except Exception as e: + logging.error(e) + ''' + + def get_logind_proxy(self): + """ Get logind dbus proxy object """ + if not self.logind_proxy: + bus = dbus.SystemBus() + if self.inhibit_lock is None: + # try to get inhibit_lock or throw exception quickly when + # logind is down + self.inhibit_lock = self.get_inhibit_shutdown_lock() + self.logind_proxy = bus.get_object( + 'org.freedesktop.login1', '/org/freedesktop/login1') + return self.logind_proxy + + def get_inhibit_shutdown_lock(self): + """ Take delay inhibitor lock """ + bus = dbus.SystemBus() + return bus.call_blocking( + 'org.freedesktop.login1', '/org/freedesktop/login1', + 'org.freedesktop.login1.Manager', 'Inhibit', 'ssss', + ('shutdown', 'Unattended Upgrades Shutdown', + _('Stop ongoing upgrades or perform upgrades before shutdown'), + 'delay'), timeout=2.0) + + def get_inhibit_max_delay(self): + try: + logind_proxy = self.get_logind_proxy() + getter_interface = dbus.Interface( + logind_proxy, + dbus_interface='org.freedesktop.DBus.Properties') + return (getter_interface.Get( + "org.freedesktop.login1.Manager", "InhibitDelayMaxUSec") + / (1000 * 1000)) + except dbus.exceptions.DBusException: + return 3 + + def is_preparing_for_shutdown(self): + if not self.shutdown_pending: + try: + logind_proxy = self.get_logind_proxy() + getter_interface = dbus.Interface( + logind_proxy, + dbus_interface='org.freedesktop.DBus.Properties') + self.shutdown_pending = getter_interface.Get( + "org.freedesktop.login1.Manager", "PreparingForShutdown") + except dbus.exceptions.DBusException: + return False + return self.shutdown_pending + + def start_iterations(self): + while self.iter(): + time.sleep(1) + ''' + if not self.iter_timer_set: + try: + GLib.timeout_add(self.wait_period * 1000, self.iter) + # schedule first iteration immediately + GLib.timeout_add(0, lambda: self.iter() and False) + except NameError: + pass + ''' + return True + ''' + def run_polling(self, signal_handler): + logging.warning( + _("Unable to monitor PrepareForShutdown() signal, polling " + "instead.")) + logging.warning( + _("To enable monitoring the PrepareForShutdown() signal " + "instead of polling please install the python3-gi package")) + + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGHUP, signal_handler) + + # poll for PrepareForShutdown then run final iterations + if self.options.wait_for_signal: + logging.debug("Waiting for signal to start operation ") + while (not self.stop_signal_received.is_set() + and not self.is_preparing_for_shutdown()): + self.stop_signal_received.wait(self.wait_period) + else: + logging.debug("Skip waiting for signals, starting operation " + "now") + while not self.iter(): + # TODO iter on sigterm and sighup, too + time.sleep(self.wait_period) + ''' + # 定时下载 执行函数 + def timing_download(self): + env = copy.copy(os.environ) + logging.debug("starting unattended-upgrades in timing download mode") + timing_download_ret = subprocess.run(["kylin-unattended-upgrade","--download-only"], env=env) + if timing_download_ret.returncode == 0: + logging.debug("kylin-unattended-upgrade download success.") + else: + logging.debug("kylin-unattended-upgrade download %d .",timing_download_ret.returncode) + + # 定时安装 执行函数 + def timing_install(self): + env = copy.copy(os.environ) + logging.debug("starting unattended-upgrades in timing install mode") + timing_install_ret = subprocess.run(["kylin-unattended-upgrade","--install-only","--mode=timing"], env=env) + if timing_install_ret.returncode == 0: + logging.debug("kylin-unattended-upgrade install success.") + else: + logging.debug("kylin-unattended-upgrade install %d .",timing_install_ret.returncode) + + + def _wait_for_unattended_upgrade_finish(self): + max_wait_time = 300 + wait_time = 0 + #read unattended-upgrade status + status = ReadValueFromFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status") + while (status != "idle"): + ReadValueFromFile(UNATTENDED_UPGRADE_CONFIG_FILE_PATH,"UNATTENDED_UPGRADE","autoupdate_run_status") + time.sleep(1) + wait_time += 1 + if wait_time >max_wait_time: + logging.info("wait for uu time out") + return + return 0 + + def _pause_timer(self): + if self.download_job is not None: + self.download_job.pause() + + if self.install_job is not None: + self.install_job.pause() + + def _resume_timer(self): + if self.download_job is not None: + self.download_job.resume() + + if self.install_job is not None: + self.install_job.resume() + + def run(self): + """ delay shutdown and wait for PrepareForShutdown or other signals""" + # if os_release_info['PROJECT_CODENAME'] == 'V10SP1-edu' and os_release_info['SUB_PROJECT_CODENAME']=='mavis': + # pass + # elif time.time() - float(time_stamp) < float(upgrade_interval): + # time_str1 = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(float(time_stamp))) + # time_str2 = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(time.time())) + # logging.info("upgrade interval not satisfied:%s-%s"%(time_str1,time_str2)) + # return 0 + + # set signal handlers + ''' + def signal_handler(signum, frame): + + logging.warning( + "SIGTERM or SIGHUP received, stopping unattended-upgrades " + "only if it is running") + self.stop_signal_received.set() + #self.start_iterations() + + # fall back to polling without GLib + try: + hasattr(GLib, "MainLoop") + except NameError: + logging.error("MainLoop Not Found") + #self.run_polling(signal_handler) + return + + for sig in (signal.SIGTERM, signal.SIGHUP): + GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, sig, + signal_handler, None, None) + ''' + if self.options.wait_for_signal: + def change_upgrade_policy_handler(): + if os.path.exists(UNATTENDED_UPGRADE_POLICY_FILE_PATH): + autoupgradepolicy.reload_config() + + if autoupgradepolicy.GetOptionValue('autoUpgradeState') == 'on': + random_time = get_random_time(autoupgradepolicy.GetOptionValue('downloadTime'),DOWNLOAD_RANDOM) + if autoupgradepolicy.GetOptionValue('downloadMode') == 'timing': + background_scheduler.add_job(task,'cron', args=['download'],id='download', \ + hour = random_time.hour,minute = random_time.minute,replace_existing=True) + if autoupgradepolicy.GetOptionValue('installMode') == 'timing': + random_time = random_time + datetime.timedelta(minutes=INTERVAL_DOWN_INSTALL) + background_scheduler.add_job(task,'cron', args=['install'],id='install', \ + hour = random_time.hour,minute = random_time.minute,replace_existing=True) + else: + background_scheduler.pause_job('download') + background_scheduler.pause_job('install') + + if autoupgradepolicy.GetOptionValue('preDownload') == 'on': + random_time = get_random_time(autoupgradepolicy.GetOptionValue('preDownloadTime'),PREDOWNLOAD_RANDOM) + background_scheduler.add_job(task,'cron', args=['predownload'],id='download', \ + hour = random_time.hour,minute = random_time.minute,replace_existing=True) + else: + background_scheduler.pause_job('predownload') + + joblist = background_scheduler.get_jobs() + + for job in joblist: + logging.debug("job:%s,next run time:%s"%(job.id,job.next_run_time)) + ''' + self.download_mode = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_DOWNLOAD_MODE) + self.install_mode = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_INSTALL_MODE) + self.preDownload = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_PREDOWNLOAD) + self.autoUpgrade = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_AUTOUPGRADE) + self.download_random = int(kylin_system_updater.GetConfigValue('AutoUpgradeConfig','downloadRandom')[1]) + self.upgrade_interval = int(kylin_system_updater.GetConfigValue('AutoUpgradeConfig','upgradeInterval')[1]) + download_time = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_DOWNLOAD_TIME) + timelist = download_time.strip().split(':') + if len(timelist) != TimeElement.TIME_NUM: + logging.debug("unattended-upgrades-policy.conf time err %s",download_time) + return + # 检查 传入时间 安全性 + try: + tmphour = int(timelist[TimeElement.TIME_HOUR]) + except ValueError: + logging.debug("unattended-upgrades-policy.conf download_time h error") + return + try: + tmpminute = int(timelist[TimeElement.TIME_MINUTE]) + except ValueError: + logging.debug("unattended-upgrades-policy.conf download_time m error") + return + + self.download_time['h'] = tmphour + self.download_time['m'] = tmpminute + self.download_time_r = convert_time_by_random(self.download_time, self.download_random) + self.install_time['h'] = self.download_time_r['h'] + self.install_time['m'] = self.download_time_r['m'] + INTERVAL_DOWN_INSTALL + self.install_time_r = convert_time_by_random(self.install_time, INSTALL_RANDOM) + + logging.info("download random:%d,upgrade interval:%d"%(self.download_random,self.upgrade_interval)) + + if self.preDownload == FeatureSwitch.ON.value: #open download timing + download_time_tmp = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_DOWNLOAD_TIME) + timelist = download_time_tmp.split(':') + if len(timelist) != TimeElement.TIME_NUM: + logging.debug("unattended-upgrades-policy.conf time err %s",download_time_tmp) + return + # 检查 传入时间 安全性 + try: + tmphour = int(timelist[TimeElement.TIME_HOUR]) + except ValueError: + logging.debug("unattended-upgrades-policy.conf download_time h error") + else: + self.download_time['h'] = tmphour + try: + tmpminute = int(timelist[TimeElement.TIME_MINUTE]) + except ValueError: + logging.debug("unattended-upgrades-policy.conf download_time m error") + else: + self.download_time['m'] = tmpminute + self.download_time_r = convert_time_by_random(self.download_time, self.download_random) + try: + if self.download_job is not None: + self.download_job.remove() + except Exception as e: + logging.error(e) + logging.info("pre-download time:%d:%d"%(self.download_time_r['h'], self.download_time_r['m'])) + self.download_job = self.scheduler.add_job(self.timing_download, 'cron', hour=self.download_time_r['h'], minute=self.download_time_r['m']) + elif self.preDownload == FeatureSwitch.OFF.value: + pass + + else: #close download timing + try: + self.download_job.pause() + except Exception as e: + logging.error(e) + + + if self.autoUpgrade == FeatureSwitch.OFF.value: + logging.info("auto upgrade turned off,removing download and instal jobs...") + try: + if self.download_job.pending: + self.download_job.remove() + except Exception as e: + pass + # logging.error(e) + try: + if self.install_job.pending: + self.install_job.remove() + except Exception as e: + pass + # logging.error(e) + return + else: + if self.download_mode == DownloadMode.TIMING_DOWNLOAD.value: + try: + if self.download_job.pending: + self.download_job.remove() + except Exception as e: + pass + # logging.error(e) + logging.info("download time:%d:%d"%(self.download_time_r['h'], self.download_time_r['m'])) + self.download_job = self.scheduler.add_job(self.timing_download, 'cron', hour=self.download_time_r['h'], minute=self.download_time_r['m']) + else: + try: + if self.download_job.pending: + self.download_job.remove() + except Exception as e: + pass + # logging.error(e) + + if self.install_mode == InstallMode.TIMING_INSTALL.value: + try: + if self.install_job.pending: + self.install_job.remove() + except Exception as e: + pass + # logging.error(e) + logging.info("install time:%d:%d"%(self.install_time_r['h'], self.install_time_r['m'])) + self.install_job = self.scheduler.add_job(self.timing_install, 'cron', hour=self.install_time_r['h'], minute=self.install_time_r['m']) + elif self.install_mode == InstallMode.BEFORE_SHUTDOWN_INSTALL.value: + try: + if self.install_job.pending: + self.install_job.remove() + except Exception as e: + pass + # logging.error(e) + logging.debug("install job removed,installation will conduct before shutdown") + else: #close install timing + try: + if self.download_job.pending: + self.install_job.remove() + except Exception as e: + pass + # logging.error(e) + + + logging.info("upgrade time: [%d:%d] [%d:%d] predown[%s] autoupgrade[%s] d-mode[%s] i-mode[%s]", + self.download_time_r['h'], self.download_time_r['m'],self.install_time_r['h'],self.install_time_r['m'], + self.preDownload, self.autoUpgrade, self.download_mode, self.install_mode) + ''' + else: + logging.debug("unattended-upgrades-policy.conf not exist") + + def upgrade_all_now_handler(): + now=datetime.datetime.now() + random_time = now + datetime.timedelta(minutes=DOWNLOAD_RANDOM) + background_scheduler.add_job(task,'date', args=['download_and_install'],id='download', \ + hour = random_time.hour,minute = random_time.minute,replace_existing=True) + + joblist = background_scheduler.get_jobs() + + for job in joblist: + logging.debug("job:%s,next run time:%s"%(job.id,job.next_run_time)) + #self._wait_for_unattended_upgrade_finish() + ''' + if FindRuningUnattendedUpgrades(): + logging.warning("find runing unattended-upgrades,please wait") + return False + else: + self._pause_timer() + env = copy.copy(os.environ) + retdownload = subprocess.run(["kylin-unattended-upgrade","--download-only"], env=env) + retinstall = subprocess.run(["kylin-unattended-upgrade","--install-only"], env=env) + self._resume_timer() + if retdownload == 0 and retinstall == 0: + return True + else: + return False + ''' + + def prepare_for_shutdown_handler(active): + """ Handle PrepareForShutdown() """ + if not active: + logging.warning("PrepareForShutdown(false) received, " + "this should not happen") + # PrepareForShutdown arrived, starting final iterations + self.install_mode = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH,POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY,AUTO_UPGRADE_POLICY_OPTION_INSTALL_MODE) + autoUpgrade = ReadValueFromFile(UNATTENDED_UPGRADE_POLICY_FILE_PATH, POLICY_CONF_SECTION_AUTO_UPGRADE_POLICY, AUTO_UPGRADE_POLICY_OPTION_AUTOUPGRADE) + if os_release_info['PROJECT_CODENAME'] == 'V10SP1-edu' and os_release_info['SUB_PROJECT_CODENAME']=='mavis': + bat = get_mavis_capacity() + logging.info("battery capacity:%d"%bat) + if os.path.exists(PKGS_TO_INSTALL_FLAG_FILE) and bat >=25: + logging.info("mavis shutdown install") + do_plymouth_splash() + self.start_iterations() + logging.info("finished iteration") + elif autoUpgrade == FeatureSwitch.ON.value and self.install_mode == InstallMode.BEFORE_SHUTDOWN_INSTALL.value: + if self.update_interface.GetConfigValue('AutoUpgradeConfig','shutdown_install'): + #show plymouth splash if bsshutdown is set + if os.path.exists(PKGS_TO_INSTALL_FLAG_FILE): + do_plymouth_splash() + self.start_iterations() + logging.info("finished iteration") + else: + pass + self.mainloop.quit() + self.get_update_proxy() + self.get_update_interface() + self.update_proxy.connect_to_signal("ChangeUpgradePolicy",change_upgrade_policy_handler) + self.update_proxy.connect_to_signal("UpgradeAllNow",upgrade_all_now_handler) + + try: + self.get_logind_proxy().connect_to_signal( + "PrepareForShutdown", prepare_for_shutdown_handler) + except dbus.exceptions.DBusException: + logging.warning( + _("Unable to monitor PrepareForShutdown() signal, polling " + "instead.")) + logging.warning( + _("Maybe systemd-logind service is not running.")) + # self.run_polling(signal_handler) + return + #self.set_max_inhibit_time(1800) + logging.debug("Waiting for signal to start operation ") + else: + # starting final iterations immediately + logging.debug("Skip waiting for signals, starting operation " + "now") + # self.start_iterations() + ''' + if os_release_info['PROJECT_CODENAME'] == 'V10SP1-edu' and os_release_info['SUB_PROJECT_CODENAME']=='mavis': + logging.info("setting startup download timer") + GLib.timeout_add(300*1000, lambda: self.timing_download() and False) + #local_time =time.localtime(time.time()+300) + self.startup_download_job = self.scheduler.add_job(self.timing_download,'cron',hour=self.download_time_r['h'],minute = self.download_time_r['m']) + + else: + if self.autoUpgrade == FeatureSwitch.ON.value: + logging.debug("download time:[%d:%d] install time:[%d:%d]", self.download_time_r['h'], self.download_time_r['m'],self.install_time_r['h'],self.install_time_r['m']) + self.download_job = self.scheduler.add_job(self.timing_download, 'cron', hour=self.download_time_r['h'], minute=self.download_time_r['m']) + self.install_job = self.scheduler.add_job(self.timing_install, 'cron', hour=self.install_time_r['h'], minute=self.install_time_r['m']) + elif self.autoUpgrade == FeatureSwitch.OFF.value: + logging.info("auto upgrade turned off") + ''' + #TimerThread(self.scheduler).start() + self.mainloop.run() + logging.info("quit mainloop") + os._exit(0) + #GLib.MainLoop().run() + + def try_iter_on_shutdown(self): + # check if we need to run unattended-upgrades on shutdown and if + # so, run it + try: + if self.apt_pkg_reinit_done is None: + logging.debug("Initializing apt_pkg configuration") + apt_pkg.init_config() + self.apt_pkg_reinit_done = True + except apt_pkg.Error as error: + # apt may be in a transient state due to unattended-upgrades + # running, thus assuming non shutdown mode is reasonable + logging.error(_("Apt returned an error thus shutdown mode is " + "disabled")) + logging.error(_("error message: '%s'"), error) + self.apt_pkg_reinit_done = False + + if self.on_shutdown_mode is None: + self.on_shutdown_mode = True + #( + #not self.options.stop_only + #and not self.stop_signal_received.is_set() + #and self.apt_pkg_reinit_done + # and apt_pkg.config.find_b( + # "Unattended-Upgrade::InstallOnShutdown", False) + #) + if self.on_shutdown_mode: + env = copy.copy(os.environ) + #env["UNATTENDED_UPGRADES_FORCE_INSTALL_ON_SHUTDOWN"] = "1" + logging.info("starting unattended-upgrades in shutdown mode") + if FindRuningUnattendedUpgrades(): + logging.warning("another unattended-upgrade is running , quit") + return False + self.on_shutdown_mode_uu_proc = subprocess.Popen( + ["kylin-unattended-upgrade","--install-only","--mode=shutdown"], env=env) + #log_msg(_("Running unattended-upgrades in shutdown mode")) + # run u-u, but switch to stopping when receiving stop signal + # because it means shutdown progressed despite holding the lock + + if self.on_shutdown_mode: + log_progress() + if self.on_shutdown_mode_uu_proc.poll() is not None: + # unattended-upgrades stopped on its own + #exit_log_result(True) + if os.path.exists(PKGS_TO_INSTALL_FLAG_FILE): + os.remove(PKGS_TO_INSTALL_FLAG_FILE) + subprocess.call(["/bin/plymouth","system-update","--progress=100"]) + time.sleep(1) + subprocess.run(["/bin/plymouth","quit","--retain-splash"]) + return False + else: + return True + return False + + + def iter(self): + ''' + if self.start_time is None: + self.start_time = time.time() + logging.debug("Starting countdown of %s minutes", + self.max_delay / 60) + else: + if (time.time() - self.start_time) > self.max_delay: + logging.warning(_( + "Giving up on lockfile after %s minutes of delay"), + self.max_delay / 60) + #os._exit(1) + #sys.exit(1) + return False + + if not self.stop_signal_received.is_set(): + if self.try_iter_on_shutdown(): + return True + else: + return False + + + # run monitoring and keep "UI" updated + res = apt_pkg.get_lock(self.options.lock_file) + logging.debug("get_lock returned %i" % res) + # exit here if there is no lock + if res > 0: + logging.debug("lock not taken") + #os._exit(0) + if self.lock_was_taken: + exit_log_result(self.signal_sent) + else: + sys.exit(0) + self.lock_was_taken = True + signal_stop_unattended_upgrade() + self.signal_sent = True + # show log + log_progress() + ''' + return self.try_iter_on_shutdown() + #return True + + +def main(): + # setup gettext + localesApp = "unattended-upgrades" + localesDir = "/usr/share/locale" + gettext.bindtextdomain(localesApp, localesDir) + gettext.textdomain(localesApp) + + # use a normal logfile instead of syslog too as on shutdown its too + # easy to get syslog killed + logdir = "/var/log/kylin-unattended-upgrades/" + try: + apt_pkg.init_config() + # logdir = apt_pkg.config.find_dir( + # "Unattended-Upgrade::LogDir", logdir) + except apt_pkg.Error as error: + logging.error(_("Apt returned an error when loading configuration, " + "using default values")) + logging.error(_("error message: '%s'"), error) + + parser = OptionParser() + parser.add_option("", "--debug", + action="store_true", dest="debug", + default=apt_pkg.config.find_b( + "Unattended-Upgrade::Debug", True), + help="print debug messages") + parser.add_option("", "--delay", default=25, type="int", + help="delay in minutes to wait for unattended-upgrades") + parser.add_option("", "--lock-file", + default="/var/run/kylin-unattended-upgrades.lock", + help="lock file location") + parser.add_option("", "--stop-only", + action="store_true", dest="stop_only", default=False, + help="only stop running unattended-upgrades, don't " + "start it even when " + "Unattended-Upgrade::InstallOnShutdown is true") + parser.add_option("", "--wait-for-signal", + action="store_true", dest="wait_for_signal", + default=False, + help="wait for TERM signal before starting operation") + (options, args) = parser.parse_args() + + # setup logging + level = logging.INFO + if options.debug: + level = logging.DEBUG + if not os.path.exists('/var/lib/unattended-upgrades'): + os.makedirs('/var/lib/unattended-upgrades') + if not os.path.exists(logdir): + os.makedirs(logdir) + logfile = os.path.join(logdir, "unattended-upgrades-shutdown.log") + logging.basicConfig(filename=logfile, + level=level, + format="%(asctime)s %(levelname)s - %(message)s") + clean_flag_files(flag_file_list) + signal.signal(signal.SIGTERM, signal_term_handler) + signal.signal(signal.SIGHUP, signal.SIG_IGN) + # init() + + UnattendedUpgradesShutdown(options).run() + + +if __name__ == "__main__": + parser = OptionParser() + parser.add_option("", "--debug", + action="store_true", dest="debug", + default=True,#apt_pkg.config.find_b( + #"Unattended-Upgrade::Debug", True), + help="print debug messages") + parser.add_option("", "--delay", default=25, type="int", + help="delay in minutes to wait for unattended-upgrades") + parser.add_option("", "--lock-file", + default="/var/run/kylin-unattended-upgrades.lock", + help="lock file location") + parser.add_option("", "--stop-only", + action="store_true", dest="stop_only", default=False, + help="only stop running unattended-upgrades, don't " + "start it even when " + "Unattended-Upgrade::InstallOnShutdown is true") + parser.add_option("", "--wait-for-signal", + action="store_true", dest="wait_for_signal", + default=False, + help="wait for TERM signal before starting operation") + (options, args) = parser.parse_args() + logdir = "/var/log/kylin-unattended-upgrades/" + # setup logging + level = logging.INFO + if options.debug: + level = logging.DEBUG + if not os.path.exists('/var/lib/unattended-upgrades'): + os.makedirs('/var/lib/unattended-upgrades') + if not os.path.exists(logdir): + os.makedirs(logdir) + logfile = os.path.join(logdir, "unattended-upgrades-shutdown.log") + logging.basicConfig(filename=logfile, + level=level, + format="%(asctime)s %(levelname)s - %(message)s") + logging.getLogger('apscheduler').setLevel(logging.DEBUG) + os_release_info = ReadOsRelease('/etc/os-release') + logging.info("project id:%s,sub-project id:%s"%(os_release_info['PROJECT_CODENAME'],os_release_info['SUB_PROJECT_CODENAME'])) + + time_stamp = "0" + if os.path.exists(TIME_STAMP): + with open(TIME_STAMP,'r') as f: + time_stamp = f.readline() + logging.info("time stamp:%s"%time_stamp) + # setup gettext + localesApp = "unattended-upgrades" + localesDir = "/usr/share/locale" + gettext.bindtextdomain(localesApp, localesDir) + gettext.textdomain(localesApp) + + # use a normal logfile instead of syslog too as on shutdown its too + # easy to get syslog killed + + try: + # apt_pkg.init_config() + reload_options_config() + # logdir = apt_pkg.config.find_dir( + # "Unattended-Upgrade::LogDir", logdir) + except apt_pkg.Error as error: + logging.error(_("Apt returned an error when loading configuration, " + "using default values")) + logging.error(_("error message: '%s'"), error) + + + clean_flag_files(flag_file_list) + signal.signal(signal.SIGTERM, signal_term_handler) + signal.signal(signal.SIGHUP, signal.SIG_IGN) + dpkg_fix=None + if os_release_info['PROJECT_CODENAME'] == 'V10SP1-edu' and os_release_info['SUB_PROJECT_CODENAME']=='mavis': + dpkg_journal_dirty = is_dpkg_journal_dirty() + logging.info("dpkg dirty:%s"%(dpkg_journal_dirty)) + if dpkg_journal_dirty: + try: + with open(OTA_PKGS_TO_INSTALL_LIST,'r') as f: + pkgs = f.read() + ret = subprocess.run(["dpkg -i %s"%pkgs],shell=True,stdout=open(logfile,'a+'),stderr=open(logfile,'a+')) + except Exception as e: + logging.error(e) + if ret.returncode == 0: + logging.info("dpkg fix success") + subprocess.Popen('dbus-send --system --type=signal / com.kylin.update.notification.FixFinish', shell=True) + # dpkg_fix = subprocess.run("dpkg --configure -a",shell=True,stdout=open(logfile,'a+'),stderr=open(logfile,'a+')) + abnormal_pkg_count = get_abnormally_installed_pkg_count() + logging.info("abnormal pkg count:%s"%(abnormal_pkg_count)) + if abnormal_pkg_count != '0': + apt_fix = subprocess.run("echo y|apt install -f",shell=True,stdout=open(logfile,'a+'),stderr=open(logfile,'a+')) + kylin_system_updater = KylinSystemUpdater() + autoupgradepolicy = AutoUpgradePolicy() + background_scheduler = BackgroundScheduler(timezone = "Asia/Shanghai") + background_scheduler_init(background_scheduler) + UnattendedUpgradesShutdown(options).run() + #main() diff --git a/unattended-upgrades/kylin-unattended-upgrades b/unattended-upgrades/kylin-unattended-upgrades new file mode 100644 index 0000000..11ea646 --- /dev/null +++ b/unattended-upgrades/kylin-unattended-upgrades @@ -0,0 +1,153 @@ +// Automatically upgrade packages from these (origin:archive) pairs +// +// Note that in Ubuntu security updates may pull in new dependencies +// from non-security sources (e.g. chromium). By allowing the release +// pocket these get automatically pulled in. +Unattended-Upgrade::Allowed-Origins { + "Kylin:10.1"; + ":default"; + //"${distro_id}:${distro_codename}"; + //"${distro_id}:${distro_codename}-security"; + // Extended Security Maintenance; doesn't necessarily exist for + // every release and this system may not have it installed, but if + // available, the policy for updates is such that unattended-upgrades + // should also install from here by default. + //"${distro_id}ESMApps:${distro_codename}-apps-security"; + //"${distro_id}ESM:${distro_codename}-infra-security"; +// "${distro_id}:${distro_codename}-updates"; +// "${distro_id}:${distro_codename}-proposed"; +// "${distro_id}:${distro_codename}-backports"; +}; + +// Python regular expressions, matching packages to exclude from upgrading +Unattended-Upgrade::Package-Blacklist { + // The following matches all packages starting with linux- +// "linux-"; + + // Use $ to explicitely define the end of a package name. Without + // the $, "libc6" would match all of them. +// "libc6$"; +// "libc6-dev$"; +// "libc6-i686$"; + + // Special characters need escaping +// "libstdc\+\+6$"; + + // The following matches packages like xen-system-amd64, xen-utils-4.1, + // xenstore-utils and libxenstore3.0 +// "(lib)?xen(store)?"; + + // For more information about Python regular expressions, see + // https://docs.python.org/3/howto/regex.html +}; + +// This option controls whether the development release of Ubuntu will be +// upgraded automatically. Valid values are "true", "false", and "auto". +Unattended-Upgrade::DevRelease "false"; + +// This option allows you to control if on a unclean dpkg exit +// unattended-upgrades will automatically run +// dpkg --force-confold --configure -a +// The default is true, to ensure updates keep getting installed +//Unattended-Upgrade::AutoFixInterruptedDpkg "true"; + +//tell dpkg not to cause conffile prompts +DPkg:Options{ +"--force-confnew"; +}; + +// Split the upgrade into the smallest possible chunks so that +// they can be interrupted with SIGTERM. This makes the upgrade +// a bit slower but it has the benefit that shutdown while a upgrade +// is running is possible (with a small delay) +//Unattended-Upgrade::MinimalSteps "false"; + +// Install all updates when the machine is shutting down +// instead of doing it in the background while the machine is running. +// This will (obviously) make shutdown slower. +// Unattended-upgrades increases logind's InhibitDelayMaxSec to 30s. +// This allows more time for unattended-upgrades to shut down gracefully +// or even install a few packages in InstallOnShutdown mode, but is still a +// big step back from the 30 minutes allowed for InstallOnShutdown previously. +// Users enabling InstallOnShutdown mode are advised to increase +// InhibitDelayMaxSec even further, possibly to 30 minutes. +//Unattended-Upgrade::InstallOnShutdown "False"; + +// Send email to this address for problems or packages upgrades +// If empty or unset then no email is sent, make sure that you +// have a working mail setup on your system. A package that provides +// 'mailx' must be installed. E.g. "user@example.com" +//Unattended-Upgrade::Mail ""; + +// Set this value to one of: +// "always", "only-on-error" or "on-change" +// If this is not set, then any legacy MailOnlyOnError (boolean) value +// is used to chose between "only-on-error" and "on-change" +//Unattended-Upgrade::MailReport "on-change"; + +// Remove unused automatically installed kernel-related packages +// (kernel images, kernel headers and kernel version locked tools). +//Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; + +// Do automatic removal of newly unused dependencies after the upgrade +//Unattended-Upgrade::Remove-New-Unused-Dependencies "true"; + +// Do automatic removal of unused packages after the upgrade +// (equivalent to apt-get autoremove) +//Unattended-Upgrade::Remove-Unused-Dependencies "false"; + +// Automatically reboot *WITHOUT CONFIRMATION* if +// the file /var/run/reboot-required is found after the upgrade +//Unattended-Upgrade::Automatic-Reboot "false"; + +// Automatically reboot even if there are users currently logged in +// when Unattended-Upgrade::Automatic-Reboot is set to true +//Unattended-Upgrade::Automatic-Reboot-WithUsers "true"; + +// If automatic reboot is enabled and needed, reboot at the specific +// time instead of immediately +// Default: "now" +//Unattended-Upgrade::Automatic-Reboot-Time "02:00"; + +// Use apt bandwidth limit feature, this example limits the download +// speed to 70kb/sec +//Acquire::http::Dl-Limit "70"; + +//Retry times after each download failure +Unattended-Upgrade::RetryTimes 3; + +// Enable logging to syslog. Default is False +//Unattended-Upgrade::SyslogEnable "True"; + +// Specify syslog facility. Default is daemon +// Unattended-Upgrade::SyslogFacility "daemon"; + +// Download and install upgrades only on AC power +// (i.e. skip or gracefully stop updates on battery) +Unattended-Upgrade::OnlyOnACPower "True"; + +// Download and install upgrades only on non-metered connection +// (i.e. skip or gracefully stop updates on a metered connection) +// Unattended-Upgrade::Skip-Updates-On-Metered-Connections "true"; + +// Verbose logging +//Unattended-Upgrade::Verbose "True"; + +// Print debugging information both in unattended-upgrades and +// in unattended-upgrade-shutdown +Unattended-Upgrade::Debug "True"; + +// Allow package downgrade if Pin-Priority exceeds 1000 +// Unattended-Upgrade::Allow-downgrade "false"; + +// When APT fails to mark a package to be upgraded or installed try adjusting +// candidates of related packages to help APT's resolver in finding a solution +// where the package can be upgraded or installed. +// This is a workaround until APT's resolver is fixed to always find a +// solution if it exists. (See Debian bug #711128.) +// The fallback is enabled by default, except on Debian's sid release because +// uninstallable packages are frequent there. +// Disabling the fallback speeds up unattended-upgrades when there are +// uninstallable packages at the expense of rarely keeping back packages which +// could be upgraded or installed. +// Unattended-Upgrade::Allow-APT-Mark-Fallback "true"; diff --git a/unattended-upgrades/kylin-unattended-upgrades.service b/unattended-upgrades/kylin-unattended-upgrades.service new file mode 100644 index 0000000..0779142 --- /dev/null +++ b/unattended-upgrades/kylin-unattended-upgrades.service @@ -0,0 +1,14 @@ +[Unit] +Description=Unattended Upgrades Shutdown +After=network.target local-fs.target systemd-logind.service kylin-system-updater.service +RequiresMountsFor=/run /var/log /var/run /var/lib /boot +Documentation=man:unattended-upgrade(8) + +[Service] +ExecStart=/usr/bin/kylin-unattended-upgrade-shutdown --wait-for-signal +Type=idle +#KillMode=process +#TimeoutStopSec=1800 + +[Install] +WantedBy=multi-user.target diff --git a/unattended-upgrades/logrotate.d/kylin-unattended-upgrade b/unattended-upgrades/logrotate.d/kylin-unattended-upgrade new file mode 100644 index 0000000..8393767 --- /dev/null +++ b/unattended-upgrades/logrotate.d/kylin-unattended-upgrade @@ -0,0 +1,10 @@ +/var/log/unattended-upgrades/unattended-upgrades.log +/var/log/unattended-upgrades/unattended-upgrades-dpkg.log +/var/log/unattended-upgrades/unattended-upgrades-shutdown.log +{ + rotate 6 + monthly + compress + missingok + notifempty +} diff --git a/unattended-upgrades/notify b/unattended-upgrades/notify new file mode 100755 index 0000000..703a9cb --- /dev/null +++ b/unattended-upgrades/notify @@ -0,0 +1,72 @@ +#!/usr/bin/python3 +import os +import sys +import subprocess +import dbus +import re +import atexit +import logging +import signal + +NOTIFICATION_PIPE = '/tmp/notification.pipe' +PID_FILE = '/tmp/notify.pid' + +def signal_handler_term(): + logging.warning("SIGTERM received, will stop") + sys.exit(1) + +def signal_handler_int(): + logging.warning("SIGINT received, will stop") + sys.exit(1) + +def translate(text): + env = os.environ['LANG'] + if re.match('zh_CN',env): + if text == 'install start': + return '安装开始' + elif text == 'install finish': + return '安装结束' + else: + return text + +def notify(iface,msg): + msg = msg.rstrip() + title = ' ' + content = ' ' + if msg =='install start': + title = translate('install start') + content = translate('install start') + elif msg == 'install finish': + title = translate('install finish') + content = translate('install start') + #print(title,content) + iface.Notify(' ',1,' ',title,content,[],{},3) + +def main(): + bus = dbus.SessionBus() + proxy_object = bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications') + iface = dbus.Interface(proxy_object,dbus_interface='org.freedesktop.Notifications') + f=open(NOTIFICATION_PIPE,'r') + while True: + msg = f.read() + if len(msg)>0: + bus = dbus.SessionBus() + proxy_object = bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications') + iface = dbus.Interface(proxy_object,dbus_interface='org.freedesktop.Notifications') + notify(iface,msg) + f.flush() + + +if __name__ == "__main__": + signal.signal(signal.SIGTERM, signal_handler_term) + signal.signal(signal.SIGINT,signal_handler_int) + if os.path.exists(PID_FILE): + logging.info("notify already exists, exiting...") + sys.exit(0) + else: + # clean up pid file on exit + with open(PID_FILE, "w+") as fp: + fp.write("%s" % os.getpid()) + atexit.register(os.remove, PID_FILE) + + main() \ No newline at end of file diff --git a/unattended-upgrades/notify.desktop b/unattended-upgrades/notify.desktop new file mode 100644 index 0000000..cdb45ba --- /dev/null +++ b/unattended-upgrades/notify.desktop @@ -0,0 +1,7 @@ +[Desktop Entry] +Name=update-notify +Exec=/usr/bin/notify +Type=Application +NoDisplay=true +Comment=update-notify +