Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 7054 lines (5468 sloc) 263 KB
#!/usr/bin/env python
'''
Setup script for the Harmattan SDK
Author: Ruslan Mstoi
Author: Benoit HERVIER (Modification to use mirror.thecust.net)
'''
# (C) Copyright 2007-2011 by Nokia Corporation. All rights reserved.
# (C) Copyright 2014 Benoit HERVIER. No rights reserved.
#
# Contact: khertan@khertan.net
#
# This setup script is lightly modified to point to mirror.thecust.net
# mirror instead of nokia one, as they didn t exists anymore
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
# $Id: harmattan-sdk-setup.py 2079 2011-09-26 10:12:20Z rmstoi $
import BaseHTTPServer
import subprocess
import threading
import sys
import os
import time
import shutil
import pwd
import textwrap
import urllib
import cStringIO
import ConfigParser
import traceback
import platform
import getpass
import tarfile
import urlparse
import signal
import tempfile
import re
import grp
import netrc
from optparse import OptionParser
# names shown to the user (what is installed/removed and this scripts name)
PRODUCT_NAME = "Harmattan SDK"
PRODUCT_NAME_SHORT = "SDK"
# used to name created files
PRODUCT_NAME_FILES = PRODUCT_NAME.replace(" ", "-").lower()
MY_NAME = '%s Setup' % PRODUCT_NAME
MY_VERSION = "0.2.1 HARMATTAN ($Revision: 2081 $)"
# the location of the script, to check for updates
MY_URL = "TODO"
# this is where the log file resides and all the downloaded files are saved to
DIR_TEMP = "/tmp"
# where to install the executable files
DIR_BIN = "/usr/local/bin"
# for the UI
SB_NAME = 'Scratchbox'
# Where scratchbox is found and is going to be installed to in case of tarball
# installation. This path must begin with / and end with /scratchbox, check
# check_sb_path_sane for details.
SB_PATH = "/scratchbox"
# scratchbox group
SB_GROUP = "sbox"
# where scratchbox tarballs are
SB_TARBALL_URL = "http://scratchbox.org/download/files/sbox-releases/hathor/tarball/"
# the names of the Scratchbox tarballs
SB_TARBALL_FILES = [
"scratchbox-core-1.0.26-i386.tar.gz",
"scratchbox-libs-1.0.26-i386.tar.gz",
"scratchbox-devkit-qemu-0.13.90-0rabbit1-i386.tar.gz",
"scratchbox-devkit-debian-squeeze-1.0.7-i386.tar.gz",
"scratchbox-devkit-hashutils-squeeze-sdk-1.0.12-i386.tar.gz",
"scratchbox-devkit-perl-1.0.5-i386.tar.gz",
"scratchbox-toolchain-host-gcc-1.0.26-i386.tar.gz",
"scratchbox-toolchain-cs2009q3-eglibc2.10-armv7-hard-1.0.24-18-i386.tar.gz",
"scratchbox-toolchain-cs2009q3-eglibc2.10-i486-1.0.24-12-i386.tar.gz"
]
SB_DEB_REPO = "deb http://scratchbox.org/debian harmattan main"
# the names of the Scratchbox packages on Debian based systems
SB_DEB_PACKAGES = [
"scratchbox-core",
"scratchbox-libs",
"scratchbox-devkit-qemu",
"scratchbox-devkit-debian-squeeze",
"scratchbox-devkit-hashutils-squeeze-sdk",
"scratchbox-devkit-perl",
"scratchbox-toolchain-host-gcc",
"scratchbox-toolchain-cs2009q3-eglibc2.10-armv7-hard",
"scratchbox-toolchain-cs2009q3-eglibc2.10-i486"
]
# where to put the downloaded Scratchbox packages
SB_FILE_DIR = DIR_TEMP
# the emulator
EMU_NAME = "QEMU"
# SDK installation envienvironment, for processes running under Scratchbox
# (e.g. apt)
SDK_INSTALL_ENV = {
# Debian frontend to noninteractive
"DEBIAN_FRONTEND" : "noninteractive",
"DEBIAN_PRIORITY" : "critical",
# don't start stuff
"SBOX_REDIRECT_BINARIES" :
# fake start-stop-daemon
"/sbin/start-stop-daemon:%(sb_path)s/tools/bin/true,"
"%(sb_path)s/devkits/debian/bin/start-stop-daemon:%(sb_path)s/tools/bin/true"
# fake invoke-rc.d
"/usr/sbin/invoke-rc.d:%(sb_path)s/tools/bin/true,"
"%(sb_path)s/devkits/debian/bin/invoke-rc.d:%(sb_path)s/tools/bin/true,"
# fake install-info
"/usr/sbin/install-info:%(sb_path)s/tools/bin/true,"
"%(sb_path)s/devkits/debian/bin/install-info:%(sb_path)s/tools/bin/true,"
# fake dpkg-divert
"/usr/sbin/dpkg-divert:%(sb_path)s/tools/bin/true,"
"%(sb_path)s/devkits/debian/bin/dpkg-divert:%(sb_path)s/tools/bin/true,"
# fake update-alternatives
"/usr/sbin/update-alternatives:%(sb_path)s/tools/bin/true,"
"%(sb_path)s/devkits/debian/bin/update-alternatives:%(sb_path)s/tools/bin/true"
% {"sb_path" : SB_PATH},
# without this sb throws: "ERROR: Cannot determine user. $USER is
# null. Please check your environment."
"USER" : os.environ["USER"]
}
# maintainer contact info
CONTACT_NAME = "Khertan"
CONTACT_EMAIL = "khertan@khertan.net"
CONTACT_BUGS_URL = "khertan.net"
# SDK time & space consumption: these are very rough
INST_CONS_TIME = '20 minutes'
INST_CONS_SPACE = "2GB"
# command line options and arguments
OPTIONS = None
ARGS = None
OPT_PARSER = None
# possible arguements for the user interface choice option
CHOICE_UI_QT = "q"
CHOICE_UI_CL = "c"
# global objects:
# messenger, initialized later on, after root access is verified
# because log file can exist already with root permissions
MSGR = None
IMPORTER = None
PKG_MGR = None
CREDS_MGR = None
# environment variable that must be set to start UI testing, when UI is being
# tested all the pages will be shown and tasks won't run
TESTING_UI_ENV_VAR = "HARMATTAN_SDK_SETUP_TESTING_UI"
TESTING_UI = False
# PIDs of the currently running children
CHILD_PIDS = set()
# some choices used in multiple functions
ASK_CHOICE_YES = "y"
ASK_CHOICE_NO = "n"
ASK_CHOICE_ABORT = "abort"
ASK_CHOICE_RETRY = "retry"
class Target(object):
'''Contains default target data. Any user modifications to this data (like
new target name prefix) are stored in UsersSelections.'''
# things that are common for all targets are class attributes
# default target names prefix, user can choose different prefix if default
# targets already exist in the Scratchbox
name_prefix = "HARMATTAN"
toolchain_prefix = "cs2009q3-eglibc2.10"
rootstrap_base_url = "http://mirror.thecust.net/hathor-sdk/"
rootstrap_suffix = "public-sdk-rootstrap.tgz"
# downloaded rootstrap will be saved into this directory
rootstrap_dir = DIR_TEMP
rootstrap_url_is_magic = False
def __init__(self, name_postfix, toolchain_postfix, rootstrap_arch,
devkits, cputransp):
'''Constructor.
name_postfix = postfix to identify this target, prefix is the same for
all targets
toolchain_postfix = postfix of toolchain to be used with this target
rootstrap_arch = rootstrap architecture, will be used to determine
the full rootstrap path
devkits = colon separated string of devkits for this target
cputransp = CPU transparency method to be used with this target'''
self.name_postfix = name_postfix
# this is the default target name, if this target exists the user has
# the choice to specify an alternative name
self.name = self.name_prefix + self.name_postfix
self.toolchain = self.toolchain_prefix + toolchain_postfix
self.rootstrap_arch = rootstrap_arch
self.devkits = devkits
self.cputransp = cputransp
rootstrap_basename = "%s-%s" % (rootstrap_arch , self.rootstrap_suffix)
# URL to download the target rootstrap from
self.rootstrap_url = ("%s%s" %
(self.rootstrap_base_url,
rootstrap_basename))
# where to save the rootstrap on the host
self.rootstrap_fn = os.path.join(self.rootstrap_dir,
rootstrap_basename)
def setup(self, username, install_selections):
'''Sets up a Scratchbox target.
username = user for whom set up the target
install_selections = user's selections'''
# use alternative target name prefix if specified
if install_selections.targets_prefix:
target_name = install_selections.targets_prefix + self.name_postfix
else:
target_name = self.name
setup_target(username, target_name, self.rootstrap_fn, self.toolchain,
self.devkits, self.cputransp, install_selections.proxy)
TARGET_X86 = Target(name_postfix = "_X86",
toolchain_postfix = "-i486",
rootstrap_arch = "i386",
devkits = "perl:debian-squeeze:hashutils-squeeze-sdk",
cputransp = "")
TARGET_ARMEL = Target(name_postfix = "_ARMEL",
toolchain_postfix = "-armv7-hard",
rootstrap_arch = "arm",
devkits = "qemu:perl:debian-squeeze:hashutils-squeeze-sdk",
cputransp = "qemu-arm-sb")
# SDK license text
EUSA_TEXT = '''IMPORTANT: READ CAREFULLY BEFORE INSTALLING, DOWNLOADING, OR USING THE LICENSED
SOFTWARE.
NOKIA SOFTWARE DEVELOPMENT KIT AGREEMENT
This Software Development Kit Agreement ("Agreement") is between You (either an
individual or an entity), the Licensee, and Nokia Corporation ("Nokia"). The
Agreement authorizes You to use the Licensed Software specified in Clause 1.4
below, which may made available to You by Nokia on a CD-ROM, sent to You by
electronic mail, or downloaded from Nokia's Web pages or Servers or from other
sources, as determined by Nokia, under the terms and conditions set forth
below. This is an agreement on licensee rights and not an agreement for sale.
Nokia continues to own the copies of the Licensed Software, which is the
subject of this Agreement, and any other copies that You are authorized to make
pursuant to this Agreement.
Read this Agreement carefully before installing, downloading, or using the
Licensed Software. By clicking on the "I Accept" button, or by typing "I
Accept" while installing, downloading, and/or using the Licensed Software, You
agree to the terms and conditions of this Agreement. If You do not agree to all
of the terms and conditions of this Agreement, promptly click the "Decline" or
"I Do Not Accept" button, or do not type "I Accept" and cancel the installation
or downloading, or destroy or return the Licensed Software and accompanying
documentation to Nokia. YOU AGREE THAT YOUR USE OF THE LICENSED SOFTWARE
ACKNOWLEDGES THAT YOU HAVE READ THIS AGREEMENT, UNDERSTAND IT, AND AGREE TO BE
BOUND BY ITS TERMS AND CONDITIONS.
1. Definitions
1.1 "Affiliates" of a party shall mean an entity (i) which is directly or
indirectly controlling such party; (ii) which is under the same direct or
indirect ownership or control as such party; or (iii) which is directly or
indirectly owned or controlled by such party. For these purposes an entity
shall be treated as being controlled by another if that other entity has fifty
percent (50 %) or more of the votes in such entity, is able to direct its
affairs and/or to control the composition of its board of directors or
equivalent body.
1.2 "Documentation" shall mean any documents, drawings, models, layouts and/or
any other IPR that are documented in any media and that have been provided by
Nokia or any of its Affiliates to Licensee.
1.3 "IPR" shall mean any and all artifacts and knowledge related to the
Licensed Software, to the extent either generated in connection with the
evaluation or use of the Licensed Software, incorporated and/or used in the
design or function of any result of such evaluation or use and/or developed
and/or discovered during such evaluation or use, including without limitation
the components, prototypes, documents, designs and computer software, as well
as ideas, inventions, patents (including utility models), and designs (whether
or not capable of registration), chip topography rights and other like
protection, copyrights (including software copyright of source code, object
code, executable code etc.), trademarks, know how, sui generis rights to data
or databases and any other form of statutory protection of any kind and
applications for any of the foregoing respectively as well as any trade
secrets.
1.4 As used in this Agreement, the term "Licensed Software" means,
collectively: (i) the software copyrighted by Nokia Corporation or third
parties (excluding Open Source Software), which is delivered to Licensee as
part of the Nokia Software Development Kit for the MEEGO-Platform (ii) all the
contents of the disk(s), CD-ROM(s), electronic mail and its file attachments,
or other media with which this Agreement is provided, including the object code
form of the software delivered via a CD-ROM, electronic mail, or Web page (iii)
digital images, stock photographs, clip art, or other artistic works ("Stock
Files") (iv) related explanatory written materials and any other possible
documentation related thereto ("Documentation"); (v) fonts, and (vi) upgrades,
modified versions, updates, additions, and copies of the Software (collectively
"Updates"), if any, licensed under this Agreement.
1.5 "Pre-Existing IPR" shall mean any IPR that has not been either generated in
connection with the evaluation or use of the Licensed Software, incorporated
and/or used in the design or function of any result of such evaluation or use
and/or developed and/or discovered during such evaluation or use.
1.6 "Third Party Software" shall mean software created by neither of the
Parties.
2. PERIOD AND SCOPE OF THE LICENSEE's PERMITTED USE
2.1 The Licensee shall be entitled to exploit the license granted to it by
Nokia under and in accordance with this Agreement solely for the purpose of
porting and developing software for the MEEGO-Platform (the "Purpose").
2.2 Nokia may choose to deliver, at its sole discretion, updates for the
Licensed Software. In addition Nokia may choose, at its exclusive discretion,
to make available, either as downloadable modules from a dedicated website or
as electronic deliveries via e-mail, to the Licensee the subsequent updates of
the Licensed Software as well as any subsequent directly related minor
upgrades, if any. As a prerequisite to the making available of any subsequent
upgrades and/or updates, Nokia may require, at its sole discretion, the
Licensee to accept additional terms and conditions in connection with the
delivery and prior to any use of any such subsequent upgrades and/or updates
made available to the Licensee by Nokia or its Affiliates. The Parties may also
separately agree on the licensing of any future releases of the Licensed
Software, if any, to be part of the Agreement by concluding a necessary
amendment to the Agreement and by agreeing on the respective terms and
conditions.
3. GRANT OF RIGHTS
3.1 Subject to the terms and conditions of this Agreement, Nokia grants to
Licensee, and Licensee hereby accepts, a non-transferable, non-sublicenseable,
non-exclusive, limited license to install and use Licensed Software on the
local hard disk(s) or other permanent storage media, copy, run and utilize the
Licensed Software in object code form solely for the Purpose. In addition,
Licensee may make one extra copy of the Licensed Software as an archival backup
copy. Any other copies made by the Licensee of the Licensed Software are in
violation of the Agreement.
3.2 Notwithstanding the generality of subsection 3.1 above and insofar as the
Licensee is provided with components of the Licensed Software in source code
form ("Source Code Components"), Nokia grants to Licensee and Licensee hereby
accepts, subject to the terms and conditions of this Agreement, a
non-transferable, non-sublicenseable, non-exclusive, limited license to compile
such Source Code Components, without any modification whatsoever, with Licensee
Application(s) solely for the Purpose and prepare copies of the Source Code
Components necessary for such compilation. For the sake of clarity, the
Licensee rights under this subsection 3.2 shall be subject to the limitations
set forth in Clause 4 below.
3.3 Parts of the Licensed Software may be supplied by third parties and may be
subject to separate license terms. The Licensee shall accept any such license
terms applicable to Third Party Software that may be introduced as part of the
installation of the Licensed Software.
4. LIMITATIONS OF LICENSE AND LOAN
4.1 Licensee shall have no right to disclose, sell, market, commercialise,
sub-license, rent, lease, re-license or otherwise transfer to any other party,
whatsoever, the Licensed Software or any part thereof, or use the Licensed
Software for any purpose or in any manner that is not expressly permitted in
this Agreement.
4.2 Licensee shall not modify or develop any derivative works of the Licensed
Software unless specifically authorized by this Agreement. Licensee may not
reverse engineer, decompile, or disassemble any part of the Licensed Software,
except and only to the extent that such activity is expressly permitted by
applicable law notwithstanding this limitation.
4.3 The Licensee understands that the rights and licenses granted under this
Agreement with respect to the Licensed Software are granted subject to third
party intellectual property rights and as such may not be enough to use or
develop the Licensed Software or any application created with the Licensed
Software, and therefore the Licensee and any users may need to obtain separate
licenses for necessary technologies in order to implement the necessary
solutions or use the Licensed Software.
4.4 Licensee undertakes to have written personal agreements between Licensee
and its employees (if any) having access to the Licensed Software or any
Software created using Licensed Software or any related information for the
purpose of ensuring compliance with the terms and conditions of this Agreement.
4.5 Licensee undertakes not to disclose, license or allow any use of software
created using Licensed Software, outside the permitted Purpose, without express
prior written consent of Nokia.
4.6 Misuse of the Licensed Software or data generated by the Licensed Software
is strictly prohibited by Nokia, and may violate U.S. and other laws. Licensee
is solely responsible for any misuse of the Licensed Software under this
Agreement and Licensee agrees to indemnify Nokia and its Affiliates for any
liability or damage related in any way to Licensee's use of the Licensed
Software in violation of the same. Licensee is also responsible for using the
Licensed Software in accordance with the limitations of this Agreement.
Licensee will indemnify and hold Nokia and its Affiliates harmless for any
claims arising out of the use of the Licensed Software or data generated by the
Licensed Software on devices belonging to the Licensee or any of Licensee's
customers or any third party that has been provided access to the Licensed
Software or data generated by the Licensed Software, except to the extent those
claims arise out of the Nokia's breach of this Agreement.
4.7 There are no implied licenses or other implied rights granted under this
Agreement, and all IPR and rights, save for those licenses expressly granted to
the Licensee hereunder, shall remain with and vest in Nokia. Nokia shall retain
at all times a right to license, market, develop, modify and customize the
Licensed Software. The non-exclusive rights granted to Licensee under this
Agreement should not be construed as a limitation on any activities of Nokia or
any of its Affiliates in the use of the Licensed Software and the marketing or
licensing of the Licensed Software, and Nokia retains all of its rights, title
and interest in the IPR, the Licensed Software and in any derivative works
thereof. Except for the rights and licenses granted to Licensee herein,
Licensee shall not take any action inconsistent with such title and ownership.
5. FEEDBACK
Licensee may provide Nokia comments, suggestions, opinions and the like as
feedback on the characteristic, performance, functionality and use of the
Licensed Software (the "Feedback"). Feedback includes without limitation
materials as well as ideas or know how (whether presented orally, in written
form or otherwise). Any Feedback submitted by the Licensee or any of its
employees or subcontractors shall, after submission to Nokia, be owned by
Nokia. Nokia shall receive, upon submission of Feedback, a worldwide,
perpetual, irrevocable, fully paid-up, royalty-free, exclusive, transferable,
sub-licenseable license to use, copy, modify, make available, incorporate in
any products or services of Nokia, sublicense, distribute and otherwise make
use of the Feedback and to create derivative works thereof to the extent and in
such manner as Nokia deems fit at its exclusive discretion. For the sake of
clarity, the license granted to Nokia above in this Clause 5 shall include
rights and licenses to any Pre-existing IPR of Licensee deemed necessary by
Nokia to exercise its rights relating to the Feedback.
6. EXCLUSION OF WARRANTIES
6.1 LICENSEE ACKNOWLEDGES THAT THE LICENSED SOFTWARE IS PROVIDED "AS IS" AND
NOKIA, ITS AFFILIATES AND/OR ITS LICENSORS DO NOT MAKE ANY REPRESENTATIONS OR
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE OR THAT THE LICENSED
SOFTWARE WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR
OTHER RIGHTS.
6.2 THERE IS NO WARRANTY BY NOKIA, ITS AFFILIATES AND/OR ITS LICENSORS OR BY
ANY OTHER PARTY THAT THE LICENSED SOFTWARE WILL MEET THE LICENSEE'S
REQUIREMENTS OR THAT THE OPERATION OF THE LICENSE SOFTWARE WILL BE
UNINTERRUPTED OR ERROR-FREE. LICENSEE ASSUMES ALL RESPONSIBILITY AND RISK FOR
THE SELECTION OF THE LICENSED SOFTWARE TO ACHIEVE LICENSEE'S INTENDED RESULTS
AND FOR THE INSTALLATION, USE, AND RESULTS OBTAINED FROM IT.
6.3 THIS AGREEMENT CREATES NO OBLIGATIONS ON THE PART OF NOKIA OTHER THAN AS
SPECIFICALLY SET FORTH HEREIN.
7. LIMITATION OF LIABILITY
7.1 IN NO EVENT SHALL NOKIA, ITS EMPLOYEES, LICENSORS, AFFILIATES OR AGENTS BE
LIABLE FOR ANY LOST PROFITS OR COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES, PROPERTY DAMAGE, LOSS OF PROFITS, INTERRUPTION OF BUSINESS OR FOR ANY
SPECIAL, INDIRECT, INCIDENTAL, ECONOMIC, COVER, PUNITIVE, OR CONSEQUENTIAL
DAMAGES, HOWEVER CAUSED, AND WHETHER ARISING UNDER CONTRACT, TORT, NEGLIGENCE,
OR OTHER THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THE
LICENSED SOFTWARE, EVEN IF THE OTHER PARTY IS ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
7.2 THE TOTAL AGGREGATE LIABILITY OF NOKIA SHALL, ON ALL OCCASIONS AND FOR ANY
DAMAGES OF WHATSOEVER NATURE ARISING IN WHATSOEVER MANNER, SHALL BE LIMITED TO
U.S. $10.
8. TECHNICAL SUPPORT
8.1 In connection with the delivery of the Licensed Software under this
Agreement, Nokia may make technical support relating to the Licensed Software
available to the Licensee e.g. by providing access to a dedicated support
website, but Nokia has no obligation whatsoever to furnish the Licensee with
any technical support unless separately agreed in writing between Licensee and
Nokia.
8.2 In the event Nokia provides the Licensee with any technical support under
subsection 8.1 above without a separate written agreement, such provision shall
not (i) obligate Nokia to continue to provide any technical support, (ii)
enable the Licensee to make any claims regarding the quality, timeliness or
other characteristics of the technical support being provided, (iii) create any
obligation for Nokia to enter into a written support agreement with the
Licensee.
8.3 Any technical support that may be provided by Nokia shall be provided
without warranties of any kind, unless separately agreed in writing between the
Parties.
9. TERM AND TERMINATION
9.1 The Agreement shall become effective upon Acceptance by Licensee as set
forth above. Any terms and conditions that by their nature or otherwise
reasonably should survive a termination or an expiry of the Agreement shall be
deemed to survive. Such terms and conditions include but are not limited to the
obligations set forth in Clauses 1, 5 -8, 10-12.
9.2 Nokia will have the right to terminate the Agreement for any reason at any
time, in its sole discretion, with a prior written notice of thirty (30) days.
9.3 Upon expiry or termination of the Agreement Licensee shall return all
information received and materials made available by Nokia under this
Agreement, including but not limited to Licensed Software, to Nokia
immediately.
10. EXPORT CONTROL.
The Licensed Software, including technical data, includes cryptographic
software subject to export controls under the U.S. Export Administration
Regulations ("EAR") prohibiting the use of the Licensed Software and technical
data by a Government End User, as defined hereafter, without a license from the
U.S. government and may be subject to import or export controls in other
countries. A Government End User is defined in Part 772 of the EAR as "any
foreign central, regional, or local government department, agency, or other
entity performing governmental functions; including governmental research
institutions, governmental corporations, or their separate business units (as
defined in part 772 of the EAR) which are engaged in the manufacture or
distribution of items or services controlled on the Wassenaar Munitions List,
and international governmental organizations. This term does not include:
utilities (telecommunications companies and Internet service providers; banks
and financial institutions; transportation; broadcast or entertainment;
educational organizations; civil health and medical organizations; retail or
wholesale firms; and manufacturing or industrial entities not engaged in the
manufacture or distribution of items or services controlled on the Wassenaar
Munitions List.)". Licensee agrees to strictly comply with all applicable
import and export regulations and acknowledge that Licensee has the
responsibility to obtain licenses to export, re-export, transfer, or import the
Licensed Software. Licensee further represent that Licensee is not a Government
End User as defined above, and Licensee will not transfer the Licensed Software
to any Government End User without a license.
11. NOTICES.
All notices and return of the Licensed Software and Documentation should be
delivered to:
Head of Legal Department
NOKIA CORPORATION
Devices
P.O. Box 100
FIN-00045 NOKIA GROUP
FINLAND
12. MISCELLANOUS
12.1 Licensee and Nokia are independent Parties. Nothing in this the Agreement
shall be construed to make either Party an agent, employee, franchisee, joint
venture or legal representative of the other Party. Neither Party will have nor
represent itself to have any authority to bind the other Party or act on its
behalf. Nothing in these General Conditions or the Agreement shall constitute
or imply any promise or intention by Nokia to enter into any other business
arrangement with Licensee.
12.2 If any provision of the Agreement or its Appendices is held invalid, all
other provisions shall remain valid unless such validity would frustrate the
purpose of the Agreement, this Agreement and its Appendices shall be enforced
to the full extent allowable under applicable law.
12.3 Licensee shall bear all costs that it may incur in connection with the use
of the Licensed Software.
12.4 This Agreement is governed by the laws of Finland excluding its choice of
law provisions. All disputes arising from or relating to the Agreement shall be
settled by a single arbitrator appointed by the Central Chamber of Commerce of
Finland. The arbitration procedure shall take place in Helsinki, Finland in the
English language.
'''
class ProductFile(object):
'''A file to be installed/removed with the product'''
def __init__(self, directory, name, contents, is_executable):
'''Constructor
directory = directory where to install the file
filename = name of the file to install
contents = string contents to write, with newlines included
is_executable = if True, the executable bit will be set during
installation'''
self.directory = directory
self.name = name
self.contents = contents
self.is_executable = is_executable
self.path = os.path.join(self.directory, self.name)
def install(self):
'''Installs the file to the host system.'''
MSGR.say("Installing file %s" % (self.path,), MSGR.SayTypeTask)
if not os.path.isdir(self.directory):
os.mkdir(self.directory)
fo = open(self.path, "w")
try:
fo.write(self.contents)
finally:
fo.close()
if self.is_executable:
os.chmod(self.path, 0755)
def remove(self):
'''Removes the file to the host system.'''
MSGR.say("Removing file %s" % (self.path,), MSGR.SayTypeTask)
os.remove(self.path)
# files to install/remove with the product
PRODUCT_FILES = []
def running_as_root():
'''Returns True if script has root privileges, False otherwise.'''
return os.geteuid() == 0
class FriendlyException(Exception):
'''User friendly exception, friendly because the traceback is not
shown to the user.
Raised in subroutines/methods to indicate failure. For this type of
exception the setup will normally print the message to stdout and log
the traceback.'''
pass
class AbstractMethodException(Exception):
'''Exception raised if an abstract method is called.'''
pass
class CLIProgressThread(threading.Thread):
'''Command line progress indication thread. Shows timed progress on the
command line (right now prints dot every interval seconds) while the main
thread is waiting for task processes to complete.
This class was inspired by the lack of repetitive timer functionality in
threading.Timer'''
def __init__(self, interval):
'''Constructor.
interval = time in seconds, that elapses between progress characters'''
threading.Thread.__init__(self)
self.interval = interval
self.__terminated = threading.Event() # if set must terminate self
self.__running = threading.Event() # i.e. not paused by end()
self.start()
is_running = property(lambda self: self.__running.isSet())
def begin(self):
'''Beging the progress indication'''
self.__running.set()
def end(self):
'''Stop the progress indication'''
self.__running.clear()
def join(self):
'''Can be called by threading._exit (at least with Python 2.6.5) even
before the owner object's (global Messenger) __del__ is called. So
overriden to avoid deadlock, since default implementation will wait
forever for the thread to quit the run method.'''
self.__terminate()
# for some reason on Python 2.6.5 things are garbage collected already:
# so the commented call gives AttributeError: "'NoneType' object has no
# attribute 'Thread'"
# threading.Thread.join(self)
super(self.__class__, self).join()
def __terminate(self):
'''Make this thread exit the run method.'''
# make sure not called the multiple times
if self.__terminated.isSet():
return
self.__terminated.set()
# in case end() was called, thread is waiting for running, so wake it
# up
if not self.__running.isSet():
self.__running.set()
def run(self):
'''Runs the progress indication thread, i.e. prints progress character
each time interval in seconds passes. If paused by the end() method
will wait till begin() is called.'''
while not self.__terminated.isSet():
# running, so print progress after timeout
if self.__running.isSet():
self.__terminated.wait(self.interval)
# was terminated while waiting
if self.__terminated.isSet():
break
# make sure end() wasn't called while thread was waiting
if self.__running.isSet():
sys.stdout.write(".")
sys.stdout.flush()
# not running, so wait until running is set
else:
self.__running.wait()
class Messenger(object):
'''A communication channel between the installation routines and the UI
classes. Additionally handles all logging.'''
# types of say
SayTypeNormal = "say_normal"
SayTypeTask = "say_task"
SayTypePhase = "say_phase"
# possible values of task/phase done numbers
DoneNumNotUsed = 0 # numbering is not in use
DoneNumStart = 1 # numbering was started
def __init__(self):
'''Constructor'''
# list of strings describing non-critical errors that have occurred
# during execution, there can be multiple non-critical errors
self.__errors = []
# string describing one (and only one) fatal error that has occurred,
# if fatal error occurs setup will give up
self.__fatal = ''
# last thing said
self.__last_say_msg = ''
self.__last_say_type = None
self.__override_say = None
self.__override_ask = None
# number of tasks, if total number is set, then number of done/total
# tasks will be prepended to the message passed to say method and done
# will be incremented with each say call
self.__tasks_total_num = 0
self.__tasks_done_num = self.__class__.DoneNumNotUsed
self.__tasks_increment_notify_func = None
# progress indication that is used only with tasks in command line UI
self.__cli_progress = CLIProgressThread(5.0)
script_name = os.path.basename(sys.argv[0]) # in case it is full path
script_name_no_ext = os.path.splitext(script_name)[0]
# log file name for root
if running_as_root():
log_filename = "%s.log" % (script_name_no_ext,)
# log file name for regular user
else:
log_filename = "%s-%s.log" % (script_name_no_ext,
get_default_username())
self.__fn_log = os.path.join(DIR_TEMP, log_filename)
self.__fo_log = None
while True:
try:
self.__fo_log = open(self.__fn_log, 'w')
except Exception, e:
print("Could not open log file %s (%s)!" % (self.__fn_log, e))
self.__fn_log = make_unique_filename(self.__fn_log)
print("Will try to use %s as log file" % (self.__fn_log,))
else:
break
self.log("Python version: %s" % repr(sys.version))
self.log("Setup version: %s" % MY_VERSION)
fn_log = property(lambda self: self.__fn_log)
fo_log = property(lambda self: self.__fo_log)
def __del__(self):
'''Destructor'''
if self.__fo_log:
self.__fo_log.close()
self.__cli_progress.join()
def cli_progress_stop(self):
'''Stops the progress indication if it is running. It will be running
for tasks when command line UI is used.'''
if self.__last_say_type == self.__class__.SayTypeTask:
if self.__cli_progress.is_running:
self.__cli_progress.end()
sys.stdout.write("\n")
def log(self, msg, prefix = "V:"):
'''Writes a log message.
prefix = will be written before message'''
self.__fo_log.write("%s [%s]: %s\n" %
(prefix, time.strftime("%H:%M:%S %d.%m.%Y"), msg))
self.__fo_log.flush()
def warn(self, msg):
'''Writes a warning message.'''
self.log(msg, "W:")
def log_exc(self):
'''Writes current exception information into the log file'''
fmt = '-' * 5 + " %s " + '-' * 5
self.log(fmt % "Begin logging exception")
traceback.print_exc(file = self.__fo_log)
self.log(fmt % "End logging exception")
def say(self, msg, say_type = SayTypeNormal):
'''Writes a message to the UI, by default to the command line.
Of course it is still possible to use print to write text to stdout,
but if text is to be written to either GUI and command line UI, then
this method should be used'''
self.cli_progress_stop()
# add task numbering
if say_type == self.__class__.SayTypeTask and \
(self.__tasks_done_num != self.__class__.DoneNumNotUsed):
msg = "%d/%d %s" % (self.__tasks_done_num, self.__tasks_total_num,
msg)
if self.__override_say:
self.__override_say(msg, say_type, self.__tasks_done_num,
self.__tasks_total_num)
else:
# in command line tasks get progress indication
if say_type == self.__class__.SayTypeTask:
sys.stdout.write(msg + " ")
sys.stdout.flush()
self.__cli_progress.begin()
else:
sys.stdout.write(msg + "\n")
self.log(msg, "S: (%s)" % say_type)
self.__last_say_msg = msg
self.__last_say_type = say_type
# increment tasks done number
if say_type == self.__class__.SayTypeTask and \
(self.__tasks_done_num != self.__class__.DoneNumNotUsed):
self.__increment_task_done_num()
def set_override_say(self, new_say):
'''Overrides the default UI say method. GUI can use this in order
to redirect messages to the GUI
new_say = function that takes (msg, say_type, done_num, total_num).
The last two have meaning if say_type is phase or task.
If done_num is DoneNumNotUsed, then the numbers are not
set and they should not be used at all.'''
self.__override_say = new_say
def remove_override_say(self):
'''Removes the override for UI say method'''
assert self.__override_say, "Override is not set!"
self.__override_say = None
def ask(self, title, question, choices, default_choice):
'''Asks a question in the UI, by default the command line.'''
self.cli_progress_stop()
self.log("A: '%s' '%s' %s %s" % (title, question, choices,
default_choice))
if self.__override_ask:
answer = self.__override_ask(title, question, choices,
default_choice)
else:
answer = ask(title, question, choices, default_choice)
self.log("A: Answer = %s" % answer)
return answer
def set_override_ask(self, new_ask):
'''Overrides the default UI ask method. GUI can use this in order
to redirect questions to the GUI
new_ask = new ask function'''
self.__override_ask = new_ask
def remove_override_ask(self):
'''Removes the override for UI ask method'''
assert self.__override_ask, "Override is not set!"
self.__override_ask = None
def get_fatal(self):
'''fatal getter'''
return self.__fatal
def set_fatal(self):
'''fatal setter'''
assert not self.__fatal, "Fatal is already set!"
self.__fatal = self.__last_say_msg
self.log("Set fatal to: %s" % self.__fatal)
self.cli_progress_stop()
def get_errors(self):
'''Returns sequence object containing errors'''
return self.__errors
def add_error(self):
'''Adds an error to the collection of errors'''
err_msg = self.__last_say_msg
self.__errors.append(err_msg)
self.cli_progress_stop()
self.say("Failed: %s" % err_msg)
def set_tasks_increment_notify_func(self, func):
'''Sets the notification function. This function will be called when
a task done number has been incremented.
func = function that takes tasks done number and tasks total number'''
assert callable(func), \
"Notification function %s is not callable" % (func)
self.__tasks_increment_notify_func = func
def remove_tasks_increment_notify_func(self):
'''Removes the notification function'''
assert self.__tasks_increment_notify_func, \
"Notification function is not set!"
self.__tasks_increment_notify_func = None
def set_tasks_total_num(self, num):
'''Enables writing task numbers with say
num = total number of tasks, current number will be incremented with
each say call'''
self.__tasks_total_num = num
self.__tasks_done_num = self.__class__.DoneNumStart
def __increment_task_done_num(self):
'''Increments number of done task until it reaches total, then task
numbering is disabled.'''
self.__tasks_done_num += 1
# disable task numbering
if self.__tasks_done_num > self.__tasks_total_num:
self.__tasks_done_num = self.__class__.DoneNumNotUsed
else:
if self.__tasks_increment_notify_func:
self.__tasks_increment_notify_func(self.__tasks_done_num,
self.__tasks_total_num)
class sc:
'''Common strings'''
fatal_title = "Execution was aborted by fatal error during stage:"
errors_title = "Non-critical errors occurred during these stages:"
warn_cleartext_creds = ("WARNING! It is a high security risk to have "
"credentials written in cleartext on unencrypted "
"storage media!")
@staticmethod
def user_fail_refer_admin(cmd_name):
'''Returns text that refers user to use admin command in case user
command fails'''
txt = ("Please fix the issues above, or run the %s as root with the "
"'%s' command." % (MY_NAME, cmd_name))
return txt
@staticmethod
def set_kernel_var_info(var, val):
'''Returns text on how to set kernel variables.
var = kernel variable to set
val = value var must be set to'''
txt = ("You can set a supported value for the current boot session "
"with 'sysctl %(var)s=%(val)s' as root. For a permanent "
"solution you may add '%(var)s = %(val)s' to %(file)s and run "
"'sysctl -p' as root" %
{"file" : SYS_INFO.sysctl_conf_fn, "var" : var, "val" : val})
return txt
@staticmethod
def contact_info_on_problems():
'''Returns contact info text for problematic situations.'''
msgr_exists = bool(MSGR)
txt_view_logfile = ""
txt_attach_logfile = ""
if msgr_exists:
txt_view_logfile = ("\n\nPlease view %s for more info.\n\n" %
(MSGR.fn_log,))
txt_attach_logfile = ("\n\nNOTE! In either case remember to attach "
"the logfile:\n%s" % (MSGR.fn_log))
txt = ("%sIf you think this is due to a bug please report it to:\n%s"
"\n\nIf you have questions please contact maintainer:\n%s (%s)"
"%s" % (txt_view_logfile, CONTACT_BUGS_URL, CONTACT_NAME,
CONTACT_EMAIL, txt_attach_logfile))
return txt
@staticmethod
def summary(s, status):
'''Returns summary text after command has completed its execution
s = string class of the command
status = status of the command'''
txt = ''
name_noun_cap = s.name_noun.capitalize()
if status == Command.StatusCompleted:
txt = "%s was successfully %s." % (PRODUCT_NAME,
s.name_verb_simple_past)
elif status == Command.StatusAborted:
txt = "%s was aborted by the user." % (name_noun_cap,)
elif status == Command.StatusErrors:
txt = "%s was %s despite some non-critical errors." % \
(PRODUCT_NAME, s.name_verb_simple_past)
elif status == Command.StatusFatal:
txt = "%s was aborted by a fatal error." % (name_noun_cap)
else:
txt = "ERROR: SUMMARY NOT AVAILABLE!!!"
return txt
class si:
'''Strings for installation'''
name_noun = "installation"
name_verb_simple_present = "install"
name_verb_present_continuous = "installing"
name_verb_simple_past = "installed"
intro_title = "Welcome to the %s installation wizard" % PRODUCT_NAME
# without paragraph characters (using newlines to split paragraphs in GUI
# makes the wizard too wide for some reason)
intro_subtitle = (
"%(par_begin)sThis application will guide you through the steps of "
"getting %(product_name)s installed on your machine. The installation "
"will take about %(cons_time)s and %(const_space)s of your disk "
"space.%(par_end)s"
"%(par_begin)sUpon a successful installation, you can find all the "
"%(product_name_short)s content under %(sb_path)s.%(par_end)s" %
{"product_name" : PRODUCT_NAME, "cons_time" : INST_CONS_TIME,
"const_space" : INST_CONS_SPACE,
"product_name_short" : PRODUCT_NAME_SHORT, "sb_path" : SB_PATH,
"par_begin" : "%(par_begin)s", "par_end" : "%(par_end)s" })
@staticmethod
def intro_creds_warn():
'''PKG_MGR is needed, hence this is a function'''
txt = ("READ THIS CAREFULLY: Since %s is going to be installed from "
"HTTPS repository, during installation your credentials "
"will be stored in cleartext into %s and %s package will be "
"installed into your system. This is all done to enable "
"access to the HTTPS repository. %s" %
(SB_NAME, PKG_MGR.src_file, PKG_MGR.pkg_name_https_transport,
sc.warn_cleartext_creds))
return txt
intro_subtitle_gui = intro_subtitle % {"par_begin" : "<p>", "par_end" : "</p>"}
intro_subtitle_cli = intro_subtitle % {"par_begin" : "\n", "par_end" : "\n"}
vdso_title = "VDSO support"
vdso_subtitle = ("Current VDSO settings of this host are not "
"supported by %s." % (SB_NAME))
vdso_info = (
"To enable the installation of %(sb_name)s and %(product_name)s the "
"%(my_name)s will set VDSO kernel parameter to %(sb_name)s supported "
"value, which is 0. By default VDSO parameter will be set for this "
"session only, which means the setting will persist until the next "
"boot. If you would like the %(my_name)s to set VDSO parameter "
"permanently " %
{"sb_name" : SB_NAME, "product_name" : PRODUCT_NAME,
"my_name" : MY_NAME})
vdso_info_gui = vdso_info + "please check the checkbox below."
vdso_info_cli = vdso_info + \
"please answer yes ('%s') to the following question." % ASK_CHOICE_YES
vdso_perm_text = "Permanently set VDSO kernel parameter to 0"
# text with accelerators for GUI
vdso_perm_text_acc = vdso_perm_text.replace("Permanently", "&Permanently")
vdso_perm_hint = ("If checked the VDSO kernel parameter will be "
"permanently set to %s compatible value" % (SB_NAME))
selinux_title = "SELinux support"
selinux_subtitle = ("%s cannot be used while Security-Enhanced Linux "
"(SELinux) is in Enforcing mode." % (SB_NAME))
selinux_info = (
"To enable the installation of %(sb_name)s and %(product_name)s the "
"%(my_name)s will set SELinux to Permissive mode during the "
"installation. If you would like the %(my_name)s to set SELinux to "
"Permissive mode permanently " %
{"sb_name" : SB_NAME, "product_name" : PRODUCT_NAME,
"my_name" : MY_NAME})
selinux_info_gui = selinux_info + "please check the checkbox below."
selinux_info_cli = selinux_info + \
"please answer yes ('%s') to the following question." % ASK_CHOICE_YES
selinux_perm_text = "Permanently set SELinux to Permissive mode"
# text with accelerators for GUI
selinux_perm_text_acc = \
selinux_perm_text.replace("Permanently", "&Permanently")
selinux_perm_hint = ("If checked SELinux will be permanently set to %s "
"compatible (Permissive) mode" % (SB_NAME))
mmap_mina_title = "mmap_min_addr support"
mmap_mina_subtitle = ("Host kernel mmap_min_addr value is incompatible "
"with the %s version used in %s." %
(EMU_NAME, SB_NAME))
mmap_mina_info = (
"For %(emu_name)s to be able to run, the %(my_name)s will set "
"mmap_min_addr kernel parameter to %(emu_name)s supported "
"value. By default mmap_min_addr parameter will be set for this "
"session only, which means the setting will persist until the next "
"boot. If you would like the %(my_name)s to set mmap_min_addr "
"parameter permanently " %
{"emu_name" : EMU_NAME, "my_name" : MY_NAME})
mmap_mina_info_gui = mmap_mina_info + "please check the checkbox below."
mmap_mina_info_cli = mmap_mina_info + \
"please answer yes ('%s') to the following question." % ASK_CHOICE_YES
mmap_mina_perm_text = "Permanently set mmap_min_addr kernel parameter"
# text with accelerators for GUI
mmap_mina_perm_text_acc = mmap_mina_perm_text.replace("Permanently",
"&Permanently")
mmap_mina_perm_hint = ("If checked the mmap_min_addr kernel parameter "
"will be permanently set to %s compatible value" %
(EMU_NAME))
users_title = "Users"
users_subtitle = ("Please select users to install %s targets for" % (PRODUCT_NAME_SHORT))
users_list_hint = ("List of users on the system. %s will be "
"installed for selected user." % PRODUCT_NAME)
targets_title = "Targets"
@staticmethod
def targets_subtitle(usernames):
'''Targets subtitle must be passed list of names of users that have
the targets already'''
usernames_str = seq_to_str(usernames, ", ", "")
if len(usernames) > 1:
u = "users"
else:
u = "user"
txt = ("The targets %s or %s already exist for the %s %s. Please "
"choose appropriate action." %
(TARGET_X86.name, TARGET_ARMEL.name, u, usernames_str))
return txt
targets_txt_overwrite = "Overwrite existing targets"
targets_txt_create = "Create new targets"
# text with accelerators for GUI
targets_txt_overwrite_acc = \
targets_txt_overwrite.replace("Overwrite", "&Overwrite")
targets_txt_create_acc = targets_txt_create.replace("new", "n&ew")
targets_create_title = "To create new targets using different name prefix:"
targets_create_title_acc = \
targets_create_title.replace("create", "&create")
targets_create_subtitle = (
"Specify a prefix to be used for the new targets. Ensure that it is "
"not the same as any of the existing %s targets (eg: %s). When the "
"new targets are created, the architecture (X86 or ARMEL) will be "
"automatically added to the prefix." % (SB_NAME, Target.name_prefix))
targets_exists_title = "Prefix exists!"
# must be formatted with target prefix names and list of usernames that
# already use that target name
targets_exists_txt = (
"A " + SB_NAME + " target with the prefix %s already exists for "
"the user(s) %s. Please specify a different prefix.")
license_title = "End User Software Agreement"
license_subtitle = ("To proceed, please read and accept the terms of the "
"license agreement")
summary_title = "Summary"
summary_subtitle = ("Please review installation configuration. At this "
"point, you can still go back and change settings")
summary_txt_users = "Users to install %s for:" % (PRODUCT_NAME_SHORT,)
summary_txt_vdso = "Permanently set VDSO:"
summary_txt_selinux = selinux_perm_text + ":"
summary_txt_mmap_mina = "Permanently set mmap_min_addr:"
summary_txt_eusa = "Install Nokia Binaries:"
summary_txt_targets_prefix = "Target name prefix"
progress_title = "Installing"
progress_subtitle = "Please wait, installation in progress..."
progress_subtitle_done = "Done installing"
conclusion_title = "Installation of %s completed" % PRODUCT_NAME
conclusion_subtitle = "Thank you for using %s!" %PRODUCT_NAME
class sr:
'''Strings for removal'''
name_noun = "removal"
name_verb_simple_present = "remove"
name_verb_present_continuous = "removing"
name_verb_simple_past = "removed"
intro_title = "Welcome to the %s removal wizard" % PRODUCT_NAME
intro_subtitle = ("This application will help you remove the %s "
"and the %s from your system. As a result, about %s of your disk space "
"will be freed." % (PRODUCT_NAME, SB_NAME, INST_CONS_SPACE))
intro_subtitle_gui = intro_subtitle
intro_subtitle_cli = intro_subtitle
summary_title = "Summary"
summary_subtitle = ("Please review removal configuration")
summary_txt_scratchbox = "Remove the %s:" % (SB_NAME,)
summary_txt_sb_homes = \
"Remove all user home directories in %s:" % (SB_NAME,)
summary_txt_targets = "Remove all %s targets:" % (SB_NAME,)
summary_txt_files = "Remove all %s files:" % (PRODUCT_NAME,)
summary_txt_verify = ("Are you sure? Those files and directories will be "
"completely removed!")
progress_title = "Removing"
progress_subtitle = "Please wait while removing..."
progress_subtitle_done = "Done removing"
conclusion_title = "Removal of %s completed" % PRODUCT_NAME
conclusion_subtitle = "Thank you for using %s!" % PRODUCT_NAME
class CmdTask(object):
'''A command task'''
# nop, is a name of task that does nothing. It is added to the task queue
# if some tasks in it say more than once. This is done to make the length
# of queue to be equal to the total number of say calls.
nop = 'nop'
def __init__(self, name, func, *args, **kwds):
'''Constructor.
name = Name that will be said before running the task. If set to None,
won't be said. If name is set to nop then task is nop, in which
case other arguments are not used at all.
func = function that executes the task
args = Arguments to pass to func
kwds = Keyword arguments to pass to func'''
# if name is not nop, func must be callable
assert callable(func) or name == self.__class__.nop, \
"Task function %s is not callable" % (func)
self.__name = name
self.__func = func
self.__args = args
self.__kwds = kwds
def run(self):
'''Runs a task'''
# nop task does nothing
if self.__name == self.__class__.nop:
return
if self.__name:
MSGR.say(self.__name, MSGR.SayTypeTask)
MSGR.log("Starting task function")
self.__func(*self.__args, **self.__kwds)
def __str__(self):
'''String representation method.'''
return ("%s, %s, %s, %s" %
(self.__name, self.__func, cut_strs_in_container(self.__args),
cut_strs_in_container(self.__kwds)))
class CmdTasksQ(object):
'''A queue of command tasks.'''
def __init__(self):
'''Constructor.'''
self.__tasks = []
def append_nop(self, num = 1):
'''Appends nop tasks to the queue.
num = number of nops to append'''
assert num >= 1, "Number must be more than 0!"
for i in xrange(num):
self.append_named(CmdTask.nop, CmdTask.nop)
def append(self, func, *args, **kwds):
'''Appends a task to the queue.
See task class constructor for description of the arguments.'''
self.append_named(None, func, *args, **kwds)
def append_named(self, name, func, *args, **kwds):
'''Appends a named task to the queue.
See task class constructor for description of the arguments.'''
self.__tasks.append(CmdTask(name, func, *args, **kwds))
def run_all(self, should_abort = None):
'''Runs all queued tasks
If UI is under testing, there is no point in entering the run method of
tasks since unnamed task say in their functions anyways, so say
messages cannot be retrieved without running the task functions, unless
the logic is moved to the tasks functions...'''
# print contents of tasks
tasks_str = seq_to_str(
["%s %s" % (i + 1, o) for i, o in enumerate(self.__tasks)])
MSGR.log("Running command tasks:\n%s" % tasks_str)
if TESTING_UI:
MSGR.log("Not running task because of UI testing")
return
for task in self.__tasks:
MSGR.log("Processing task %s" % task)
if should_abort and should_abort():
MSGR.log("Aborting...")
break
try:
task.run()
except CmdErrorFailure, e: # non-critical error
MSGR.log_exc()
continue
def __len__(self):
'''Returns the length of the tasks queue'''
return len(self.__tasks)
class CmdPhase(object):
'''A command is split into phases. Phases are split into tasks.
Some phases are dynamic, meaning they decide tasks on the fly. Normally,
dynamic phases require the previous phase to be completed before they can
decide their tasks.'''
def __init__(self, name, get_tasks_func):
'''Constructor.
name = name of the phase, will be said when phase is started
get_tasks_func = callable that takes the same arguments as the run
method of Command class. Most likely call to this
function won't succeed before preceding phase has
been completed.'''
assert callable(get_tasks_func), \
"Get task function %s is not callable" % (get_tasks_func)
self.__name = name
self.__get_tasks = get_tasks_func
def run(self, say_msg_prepend, users_selections, should_abort = None):
'''Runs a phase. Takes same arguments as the run method of Command.'''
MSGR.say(say_msg_prepend + self.__name, Messenger.SayTypePhase)
tasks = self.__get_tasks(users_selections)
MSGR.set_tasks_total_num(len(tasks))
tasks.run_all(should_abort)
# for command line only, to separate phases (GUI will replace it fast)
MSGR.say("")
def cfail(msg):
'''For check functions, if check fails, they call this with error
message'''
raise FriendlyException(msg)
def check_running_as_root():
'''Checks if the script is run by the root.'''
if not running_as_root():
cfail("This command should be run as root!")
def check_running_as_non_root():
'''Checks if the script is run by a regular user.'''
if running_as_root():
cfail("This command should not be run as root!")
def check_running_out_of_sb():
'''Checks that script is not running inside scratchbox.'''
if os.path.exists("/targets/links/scratchbox.config"):
cfail("This script needs to be run outside of %s." % (SB_NAME,))
def check_sb_installed():
'''Checks if scratchbox is installed.'''
if not SYS_INFO.has_scratchbox:
cfail(
"%(sb_name)s not found in installation path '%(sb_path)s'. "
"Please complete %(sb_name)s installation first." %
{"sb_name" : SB_NAME, "sb_path" : SB_PATH})
def check_sb_user_exists(username):
'''Checks if user exists in scratchbox.'''
path = os.path.join(SB_PATH, "users", username)
if not os.path.isdir(path):
cfail(
"%(sb_name)s directory for user not present. Add user with\n"
"'%(sb_path)s/sbin/sbox_adduser %(username)s'." %
{"sb_name" : SB_NAME, "sb_path" : SB_PATH,
"username" : username})
def check_sb_conf_exists():
'''Checks if sb-conf exists.'''
path = os.path.join(SB_PATH, "tools/bin/sb-conf")
if not os.path.exists(path):
cfail(
"%(sb_name)s sb-conf tool not found in '%(sb_path)s'. This is "
"most likely due to old version of %(sb_name)s. Please "
"complete %(sb_name)s installation first." %
{"sb_name" : SB_NAME, "sb_path" : SB_PATH})
def check_sb_bind_mount(username):
'''Check for scratchbox bind mount.'''
path = os.path.join(SB_PATH, "users", username, "scratchbox")
if not os.path.isdir(path):
cfail(
"%(sb_name)s bind mount for user not present. Start "
"%(sb_name)s service with "
"'sudo %(sb_path)s/sbin/sbox_ctl start'." %
{"sb_name" : SB_NAME, "sb_path" : SB_PATH})
def check_sb_dev_null(username):
'''Check if scratchbox properly set up (/dev/null is readable)'''
path = os.path.join(SB_PATH, "users", username, "dev/null")
if not os.access(path, os.R_OK):
cfail(
"%(sb_name)s user's /dev is not properly set up. Couldn't "
"read /dev/null. Start %(sb_name)s service with\n"
"'sudo %(sb_path)s/sbin/sbox_ctl start'." %
{"sb_name" : SB_NAME, "sb_path" : SB_PATH})
def check_sb_user_home(username):
'''Checks for users home directory inside scratchbox.'''
path = os.path.join(SB_PATH, "users", username, "home", username)
if not os.path.isdir(path):
cfail(
"%(sb_name)s home directory '%(sb_home)s' not found. "
"Add user with "
"'%(sb_path)s/sbin/sbox_adduser %(username)s'." %
{"sb_name" : SB_NAME, "sb_home" : path,
"sb_path" : SB_PATH, "username" : username})
def check_sb_login():
'''Checks if scratchbox login is readable and executable.
TODO: check login for each user separately?'''
path = os.path.join(SB_PATH, "login")
# check if scratchbox login is readable
if not os.access(path, os.R_OK):
cfail(
"%(sb_name)s login not found in '%(sb_path)s'. "
"Please complete %(sb_name)s installation first." %
{"sb_name" : SB_NAME, "sb_path" : SB_PATH})
# check if scratchbox login is executable (user in sbox group)
if not os.access(path, os.X_OK):
cfail(
"%(sb_name)s login found but not executable by user. Please "
"check that user is member of the group specified in "
"%(sb_name)s installation (default '%(sb_group)s'). Also "
"start a new login terminal after adding group membership." %
{"sb_name" : SB_NAME, "sb_group" : SB_GROUP})
def check_sb_version(sb_version_min_str):
'''Checks if scratchbox version is supported.
sb_version_min_str = string with the the oldest scratchbox version that
is supported'''
sb_version_ins_str = sb_get_version()
if not sb_version_ins_str:
cfail(
"Couldn't execute %(sb_name)s utility sb-conf to get "
"%(sb_name)s version. Please complete %(sb_name)s "
"installation first." % {"sb_name" : SB_NAME})
# check for scratchbox version
# use "dpkg --compare-versions" on Debian systems???
sb_version_ins_list = version_str_to_list(sb_version_ins_str, SB_NAME)
sb_version_min_list = version_str_to_list(sb_version_min_str, SB_NAME)
if sb_version_ins_list < sb_version_min_list:
cfail(
"%(sb_name)s version is too old (scratchbox-core %(version_ins)s)."
"The minimum required scratchbox-core version is %(version_min)s. "
"Please refer to http://scratchbox.org/" %
{"sb_name" : SB_NAME, "version_ins" : sb_version_ins_str,
"version_min" : sb_version_min_str})
def check_sb_cputransp():
'''Checks for CPU transparency method.'''
if not sb_has_cputransp(TARGET_ARMEL.cputransp):
cfail(
"CPU transparency method '%s' not found. Please complete %s "
"installation first." % (TARGET_ARMEL.cputransp, SB_NAME))
def check_sb_toolchains():
'''Checks that toolchains are found.'''
if (not sb_has_toolchain(TARGET_ARMEL.toolchain) or
not sb_has_toolchain(TARGET_X86.toolchain)):
cfail(
"Toolchain %s required for '%s' target. Toolchain %s required "
"for '%s' target. Please complete %s installation first." %
(TARGET_ARMEL.toolchain, TARGET_ARMEL.name, TARGET_X86.toolchain,
TARGET_X86.name, SB_NAME))
def check_sb_devkits():
'''Checks that devkits are found.'''
missing_devkits = sb_get_missing_devkits()
if missing_devkits:
cfail("%(sb_name)s devkits %(devkits)s not found. Please complete "
"%(sb_name)s installation first." %
{"sb_name" : SB_NAME,
"devkits" : seq_to_str(missing_devkits, ", ", "")})
def check_sb_sessions():
'''Checks for scratchbox sessions running for the default user.'''
if sb_has_sessions():
cfail("You must close your other %(sb_name)s sessions first." %
{"sb_name" : SB_NAME})
def check_sb_sessions_all_users():
'''Checks if any of the scratchbox users have open sessions.'''
session_usernames = sb_get_sessions_usernames()
if session_usernames:
cfail("The following users have %s sessions open: %s. These "
"sessions must be closed first." %
(SB_NAME, seq_to_str(session_usernames, ", ", "")))
def check_vdso():
'''Checks if vdso is supported'''
if SYS_INFO.has_unsup_vdso:
cfail(
si.vdso_subtitle + " " +
sc.set_kernel_var_info(SYS_INFO.vdso_var, SYS_INFO.vdso_val))
def check_selinux():
'''Checks if SELinux is supported.'''
if SYS_INFO.has_unsup_selinux:
cfail(si.selinux_subtitle +
" You can change to Permissive mode with\n'setenforce 0'")
def check_mmap_min_addr():
'''Checks if mmap_min_addr is supported.'''
if SYS_INFO.has_unsup_mmap_mina:
cfail(
si.mmap_mina_subtitle + " " +
sc.set_kernel_var_info(SYS_INFO.mmap_mina_var,
SYS_INFO.mmap_mina_val))
def check_running_out_of_fakeroot():
'''Checks that script is run out of the fakeroot environment'''
if os.environ.has_key('FAKEROOTKEY'):
cfail("This script should not be run inside fakeroot.")
def check_binfmt_misc():
'''Checks that kernel has the binfmt_misc module.'''
if not os.path.isdir("/proc/sys/fs/binfmt_misc"):
cfail("Host kernel module binfmt_misc is required for the CPU "
"transparency feature.")
def check_ipv4_port_range():
'''Checks host kernel local IPv4 port range. This check is from the legacy
Scratchbox installer, no one knows if it is really needed.'''
p = subprocess_Popen(
"cat /proc/sys/net/ipv4/ip_local_port_range | awk '{ print ($2-$1) }'",
stdout = subprocess.PIPE, stderr = subprocess.STDOUT, shell = True)
ipv4_range = int(p.communicate()[0].strip())
if ipv4_range < 10000:
cfail("Host kernel has IPv4 local port range under 10000. This "
"causes problems with fakeroot. Increase with "
"'echo \"1024 65000\" > /proc/sys/net/ipv4/ip_local_port_range'")
def check_sb_path_sane():
'''Checks that Scratchbox installation path is sane. Legacy Scratchbox
installer checks this only if installing from tarballs. This one checks
always - good assertion.'''
if not os.path.isabs(SB_PATH):
cfail("%s install path must start with '/'." % (SB_NAME,))
end = "/scratchbox"
if not SB_PATH.endswith(end):
cfail("%s install path must end with '%s'." % (SB_NAME, end,))
def check_sb_path_exists():
'''Checks that Scratchbox installation path exists.'''
if not os.path.isdir(SB_PATH):
cfail("%s is not installed: path '%s' does not exist." %
(SB_NAME, SB_PATH))
def check_binfmt_misc_arm():
'''Checks binfmt_misc contents for confilicting arm binaries.'''
arm_magic = "7f454c4600010000000000000000000000002800"
binfmt_dir = "/proc/sys/fs/binfmt_misc/"
for binfmt in os.listdir(binfmt_dir):
binfmt_path = os.path.join(binfmt_dir, binfmt)
if binfmt not in ("status", "register", "sbox-arm"):
# arm magic found
if not exec_cmd('grep -q "magic %s" %s' % (arm_magic, binfmt_path)):
txt = subprocess_Popen('grep "^interpreter " %s' % (binfmt_path),
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT,
shell = True
).communicate()[0].strip()
cfail("Conflicting registration for arm programs with "
"binfmt_misc. Please remove the conflicting package or "
"program, and try again. Conflicting\n%s" % (txt))
class Command(object):
'''A command.
A command is split into phases, which in their turn consist of tasks.'''
# execution status of a command
StatusNotStarted = "status_not_started"
StatusRunning = "status_running"
StatusCompleted = "status_completed" # successful completion
StatusAborted = "status_aborted" # aborted by the user
StatusErrors = "status_errors" # non-critical errors occured
StatusFatal = "status_fatal" # fatal error stopped the command execution
# children should override these as needed
name = "command" # name of the command as specified by the user
aliases = ["c"] # possible name aliases/alternatives
is_install = False # command installs product
is_remove = False # command removes product
needs_root_access = False # command must be run by root
s = None # UI strings class
desc = "" # short description for the command line UI usage message
def __init__(self):
'''Constructor.'''
self.is_running_last_task = False
self.is_running_last_phase = False
self.status = self.__class__.StatusNotStarted
self.run_checks()
def get_phases_list(self):
'''Returns list of phases. Should be overridden in children.'''
pass
def get_default_checks_list(self):
'''Returns list of common checks that are the same for all Command
classes.'''
checks_list = []
if self.needs_root_access: # first check for root access
checks_list.append((check_running_as_root,))
else:
checks_list.append((check_running_as_non_root,))
checks_list.append((check_running_out_of_sb,))
checks_list.append((check_sb_path_sane,))
return checks_list
def get_checks_list(self):
'''Returns a tuple containing list of of checks and a string. Checks is
a list of tuples, where each element is a function and possible
arguments. String is the error message to be appended to the message
from check, if it is empty it will not be used. This method should be
overridden in children.'''
return ([], "")
def run_checks(self):
'''Runs the checks'''
(checks_list, check_fail_info_msg) = self.get_checks_list()
checks_list = self.get_default_checks_list() + checks_list
MSGR.log("About to run %s command checks %s" % (self.name, checks_list))
assert len(checks_list) == len(set(checks_list)), \
"There are duplicate checks!"
for check in checks_list:
MSGR.log("Running check %s" % (check,))
check_func = check[0]
try:
if len(check) > 1:
check_func(*check[1:])
else:
check_func()
except FriendlyException, e: # check failed
MSGR.log("Check failed")
# add message to text if available
if check_fail_info_msg:
raise FriendlyException(
"%s\n\n%s" % (str(e), check_fail_info_msg))
else:
raise
def run(self, users_selections, should_abort = None):
'''Runs the command (gets list of phases and runs them).
users_selections = run command with these selections
should_abort = function that returns boolean value, if that value
is true then command will be aborted'''
MSGR.log("Running command with selections: %s" % (users_selections))
self.status = self.__class__.StatusRunning
phases_list = self.get_phases_list()
MSGR.set_tasks_increment_notify_func(self.on_tasks_increment)
try:
for index, phase in enumerate(phases_list):
if phase == phases_list[-1]:
self.is_running_last_phase = True
MSGR.log("Started phase is the last one")
if len(phases_list) == 1:
say_msg_prepend = ""
else:
say_msg_prepend = ("Phase %s of %s: " %
(index + 1, len(phases_list)))
phase.run(say_msg_prepend, users_selections, should_abort)
# if there is fatal this place isn't reached
if MSGR.get_errors():
self.status = self.__class__.StatusErrors
else:
self.status = self.__class__.StatusCompleted
if should_abort and should_abort():
self.status = self.__class__.StatusAborted
# catch even unpredictable exceptions
except Exception, e: # (CmdFatalFailure, FriendlyException), e:
MSGR.set_fatal()
MSGR.log(str(e))
MSGR.log_exc()
self.status = self.__class__.StatusFatal
MSGR.remove_tasks_increment_notify_func()
def on_tasks_increment(self, tasks_done_num, tasks_total_num):
'''Called by Messenger when task done number is incremented.'''
if self.is_running_last_phase:
if tasks_done_num == tasks_total_num:
MSGR.log("Started task is the last one")
self.is_running_last_task = True
class AdminInstallCommand(Command):
'''Admin install command.'''
name = "admininstall"
aliases = ["ai"]
is_install = True
needs_root_access = True
s = si
desc = ("Install the %s and the %s as root for any user" %
(SB_NAME, PRODUCT_NAME))
def __init__(self):
'''Constructor.'''
Command.__init__(self)
def get_phases_list(self):
'''See parent's description of this method'''
phase1 = CmdPhase("Installing the %s" % (PRODUCT_NAME,),
self.get_phase1_tasks)
return [phase1]
def get_phase1_tasks(self, install_selections):
'''Returns tasks for phase 1'''
tasks = CmdTasksQ()
if SYS_INFO.has_unsup_vdso:
tasks.append(set_kernel_param,
SYS_INFO.vdso_var,
SYS_INFO.vdso_val,
install_selections.set_perm_vdso)
if SYS_INFO.has_unsup_selinux:
tasks.append(set_selinux_permissive,
install_selections.set_perm_selinux)
if SYS_INFO.has_unsup_mmap_mina:
tasks.append(set_kernel_param,
SYS_INFO.mmap_mina_var,
SYS_INFO.mmap_mina_val,
install_selections.set_perm_mmap_mina)
# install needed extra packages
pkgs_to_install = [PKG_MGR.pkg_name_xephyr]
if PKG_MGR.repo_requires_authentication():
pkgs_to_install.append(PKG_MGR.pkg_name_https_transport)
for pkg in pkgs_to_install:
if not PKG_MGR.pkg_is_installed(pkg):
tasks.append(PKG_MGR.pkg_install, pkg, False)
tq_append_install_sb_tasks(tasks, install_selections)
num_users = len(install_selections.usernames)
if num_users: # instlal SDK only if users selected
tq_append_install_sdk_tasks(tasks, install_selections)
# install files
for f in PRODUCT_FILES:
tasks.append(f.install)
return tasks
def get_checks_list(self):
'''See parent's description of this method'''
checks_list = [(check_running_out_of_fakeroot,),
(check_binfmt_misc,),
(check_ipv4_port_range,),
(check_binfmt_misc_arm,),
]
if SYS_INFO.has_scratchbox:
checks_list.append((check_sb_sessions_all_users,))
return (checks_list, "")
class UserInstallCommand(Command):
'''User envrionment installation command.'''
name = "userinstall"
aliases = ["ui"]
is_install = True
s = si
desc = "Install the %s for the user running the setup" % (PRODUCT_NAME,)
# the oldest scratchbox version that this command supports
sb_version_min_str = "1.0.18"
def __init__(self):
'''Constructor.'''
Command.__init__(self)
def get_phases_list(self):
'''See parent's description of this method'''
phase1 = CmdPhase("Installing the %s" % PRODUCT_NAME,
self.get_phase1_tasks)
return [phase1]
def get_phase1_tasks(self, install_selections):
'''Returns tasks for phase 1'''
tasks = CmdTasksQ()
tq_append_install_sdk_tasks(tasks, install_selections)
return tasks
def get_checks_list(self):
'''See parent's description of this method'''
username = get_default_username()
checks_list = [(check_sb_installed,),
(check_sb_user_exists, username),
(check_sb_conf_exists,),
(check_sb_bind_mount, username),
(check_sb_dev_null, username),
(check_sb_user_home, username),
(check_sb_login,),
(check_sb_version, self.sb_version_min_str),
(check_sb_cputransp,),
(check_sb_toolchains,),
(check_sb_devkits,),
(check_sb_sessions,),
(check_vdso,),
(check_selinux,),
(check_mmap_min_addr,),
]
check_fail_info_msg = \
sc.user_fail_refer_admin(AdminInstallCommand.name)
return (checks_list, check_fail_info_msg)
class AdminRemoveCommand(Command):
'''Admin remove command.'''
name = "adminremove"
aliases = ["ar"]
is_remove = True
needs_root_access = True
s = sr
desc = "Remove the %s and the %s as root" % (SB_NAME, PRODUCT_NAME,)
def __init__(self):
'''Constructor.'''
Command.__init__(self)
def get_phases_list(self):
'''See parent's description of this method'''
phase1 = CmdPhase("Removing the %s" % (PRODUCT_NAME,),
self.get_phase1_tasks)
return [phase1]
def get_phase1_tasks(self, remove_selections):
'''Returns tasks for phase 1'''
tasks = CmdTasksQ()
# Debian system
if isinstance(PKG_MGR, AptPkgManager):
# pkg manager does not purge, hence:
tasks.append_named(
"Removing %s packages" % (SB_NAME), exec_cmd_fatal,
"apt-get remove scratchbox-\* -y --purge")
tasks.append(PKG_MGR.repo_remove)
# non-Debian system
else:
tasks.append_named("Stopping %s" % (SB_NAME,), exec_cmd_fatal,
"%s/sbin/sbox_ctl stop" % (SB_PATH,))
tasks.append(sb_run_prermdir_sanity_checks)
# remove Scratchbox directory
tasks.append(remove_dir_tree, SB_PATH)
# remove files
for f in PRODUCT_FILES:
if os.path.exists(f.path):
tasks.append(f.remove)
return tasks
def get_checks_list(self):
'''See parent's description of this method'''
checks_list = [(check_sb_path_exists,),
(check_running_out_of_fakeroot,),
(check_sb_sessions_all_users,)
]
return (checks_list, "")
def cmd_name2class(name, cmd_classes):
'''Converts a command name or alias into a respective command class.
name = name or alias of a command
cmd_classes = list of command classes to search for name'''
name = name.lower()
for cmd_class in cmd_classes:
# valid name or alias
if name == cmd_class.name or name in cmd_class.aliases:
return cmd_class
return None
def create_command(args):
'''Parses the command arguments and creates a command object based on the
arguments. If command is not properly specified will ask the user for the
command. Returns command object.
args = command line arguments (the specified command)'''
sup_cmd_classes = create_command.sup_cmd_classes
sorted_cmd_names = sorted([i.name for i in sup_cmd_classes])
num_args = len(args)
cmd_class = None # the class of the command selected by the user
lcmd_name2class = lambda name: cmd_name2class(name, sup_cmd_classes)
cmd_class_from_menu = lambda title: \
lcmd_name2class(cli_menu(title, sorted_cmd_names))
# command not specified, so ask
if num_args == 0:
title = ("You forgot to specify a command. Please choose one from "
"available commands.")
cmd_class = cmd_class_from_menu(title)
# too many commands specified, choose the first correct one
elif num_args > 1:
print "More than one command specified, first valid one will be used"
for arg in args:
cmd_class = lcmd_name2class(arg)
if cmd_class:
print "'%s' is a valid command, using it\n" % arg
break
else:
print "'%s' is a not a valid command" % arg
else: # none of the args is a valid command
title = "Valid command was not specified."
cmd_class = cmd_class_from_menu(title)
# one command is specified
else:
cmd_name = args[0]
cmd_class = lcmd_name2class(cmd_name)
if not cmd_class:
title = "'%s' is a not a valid command." % cmd_name
cmd_class = cmd_class_from_menu(title)
return cmd_class()
# list of supported commands
create_command.sup_cmd_classes = [AdminInstallCommand, AdminRemoveCommand,
UserInstallCommand]
class DynamicImporter(object):
'''Manages dynamically imported modules. Most of these are modules that are
not distributed with python. Keeps the imported modules in a dictionary, so
they could be re-used. If module cannot be imported will attempt to install
respective package using package manager.
Package manager is not used to check if the respective package is already
installed, because the user can install 3rd party modules not using package
manager.'''
def __init__(self):
'''Constructor.'''
self.__modules = {} # imported modules
# dictionary of packages that contain modules, if module cannot be
# imported attempt will be made to install respective package
self.__packages = {
'PyQt4.QtGui' : PKG_MGR.pkg_name_pyqt4,
'PyQt4.QtCore' : PKG_MGR.pkg_name_pyqt4,
'pexpect' : PKG_MGR.pkg_name_pexpect,
'pycurl' : PKG_MGR.pkg_name_pycurl
}
# import packages that are always needed, Qt packages are only needed
# when running GUI
self["pycurl"]
def __do_import(self, module_name):
'''Does the actual module import'''
self.__modules[module_name] = \
__import__(module_name, globals(), locals(), [''])
def __import_sb_module(self, module_name):
'''Imports a Scratchbox python module. This enables a cleaner approach
than to call sb-conf directly. If this approach ceases to work, will
have to go back to old school way of using sb-conf to e.g. list
sessions, then grep, count lines etc.
module_name = name of the module, check help on __import__ for
more info'''
sb_py_path = "%s/tools/lib/python2.3/" % (SB_PATH,)
try:
sys.path.append(sb_py_path)
self.__do_import(module_name)
sys.path.remove(sb_py_path)
except ImportError, e:
raise FriendlyException(
"Failed to import %s. Please complete %s installation first." %
(module_name, SB_NAME))
def __import_module(self, module_name):
'''Imports a module. If module cannot be imported will attempt to
install respective package. Raises FriendlyException if package
installation fails. The caller may catch the exception and implement
some sort of workaround.
module_name = name of the module, check help on __import__ for
more info'''
# attempt to import
try:
self.__do_import(module_name)
# the module is missing, try to install package
except ImportError:
package_name = self.__packages.get(module_name)
if not package_name:
raise FriendlyException(
"Failed to import %s and don't know how to install it! "
"You might have to install those modules yourself." %
module_name)
self_install_msg = (
"%s package is needed but not installed. Please install "
"it then try again." % package_name)
# package manager is not available on this system
if isinstance(PKG_MGR, UnknownPkgManager):
raise FriendlyException(self_install_msg)
# package installation can only be done as root
if not running_as_root():
txt = sc.user_fail_refer_admin(AdminInstallCommand.name)
raise FriendlyException("%s\n\n%s" % (self_install_msg, txt))
# user chose not to install the package
if not is_yes("Package %s is needed but not installed, install?"
% package_name):
raise FriendlyException("Not installing missing package %s" %
package_name)
# user chose to install the package
try:
print
PKG_MGR.pkg_install(package_name)
self.__do_import(module_name)
except Exception, e: # package manager failed
MSGR.log_exc()
raise FriendlyException(
"Failed installing %s. See %s for details. Please "
"install %s packages yourself." %
(package_name, MSGR.fn_log, module_name))
def get_module(self, module_name):
'''Returns requested module if module already imported. If module is
not imported yet, will import it and then return it. Might raise
exceptions as mentioned in __import_module.
module_name = see __import_module'''
if module_name not in self.__modules:
MSGR.log("Requested module %s not imported, will try to import" %
module_name)
if module_name.startswith("sb."):
self.__import_sb_module(module_name)
else:
self.__import_module(module_name)
else:
pass
# MSGR.log("Requested module %s already imported" % module_name)
return self.__modules[module_name]
def __getitem__(self, module_name):
'''Just a convenience method'''
return self.get_module(module_name)
class SysInfo(object):
'''Some information about operating system'''
def __init__(self):
'''Constructor. Will raise FriendlyException if system is not
supported.'''
self.os = None # OS type (e.g. linux)
self.machine = None # OS machine type (e.g. i686)
self.distro_id = None # lower-cased Linux distributor ID (e.g. ubuntu)
self.distro_release = None # distribution release number (e.g. 9.04)
self.distro_codename = None # lower-cased distribution release code name (e.g. jaunty)
self.has_scratchbox = False # whether host already has scratchbox installed
self.has_unsup_vdso = False # whether current VDSO settings are unsupported
self.vdso_var = "" # name of VDSO variable
self.vdso_val = 0 # correct value of VDSO variable
self.has_unsup_selinux = False # whether current SELinux mode is unsupported (Enforcing)
self.has_unsup_mmap_mina = False # whether current mmap_min_addr settings are unsupported
self.mmap_mina_var = "vm.mmap_min_addr" # name of mmap_min_addr variable
self.mmap_mina_val = 4096 # correct value of mmap_min_addr variable (according to legacy installers)
self.sysctl_conf_fn = "/etc/sysctl.conf" # location of the sysctl.conf file
self.is_64_bit = False # True if OS is 64 bit
self.__get_info()
self.__check_if_supported()
def __is_mmap_mina_unsupported(self):
'''Returns True if mmap_min_addr settings of kernel are unsupported by
QEMU.
On Lucid the value cannot be read by qemu, so must make sure it is
correct, see:
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/568844
'''
# if not Lucid, then mmap_min_addr is supported
if self.distro_codename != "lucid":
return False
val = get_kernel_param(self.mmap_mina_var, int)
if val == None: # failed reading
return False
if val > self.mmap_mina_val:
return True
else:
return False
def __is_vdso_unsupported(self):
'''Returns tuple of boolean and string. Boolean will be True if VDSO
settings of kernel are unsupported by Scratchbox, the string is the
name of the VDSO variable used by the kernel. Different kernels can use
different variable names for VDSO.
If none of the known VDSO variables are set, then it is assumed that
VDSO settings are supported.'''
# default values assume VDSO settings are supported
result_has_unsup_vdso = False
result_vdso_var = ""
# possible VDSO kernel variables
vdso_vars = ["vm.vdso_enabled",
"abi.vsyscall32", # used on 64 bit systems
"kernel.vdso"]
# values of VDSO variables supported by scratchbox
supported_vdso_list = [0, 2]
for vdso_var in vdso_vars:
val = get_kernel_param(vdso_var, int)
if val == None: # failed reading
continue
# got int value of VDSO variable, it is supported if in the list
else:
result_has_unsup_vdso = val not in supported_vdso_list
result_vdso_var = vdso_var
break
return (result_has_unsup_vdso, result_vdso_var)
def __is_selinux_unsupported(self):
'''Returns True if SELinux is enabled and currently is in the Enforcing
mode, which is not supported by scratchbox. Scratchbox currently
supports only the Permissive SELinux mode.'''
# check if SELinux is enabled
try:
returncode = subprocess_Popen(["selinuxenabled"]).wait()
except OSError, e:
MSGR.log("'%s' while checking SELinux, assuming not enabled"
% (str(e)))
return False
MSGR.log("selinuxenabled returned %s" % (returncode))
if returncode: # not enabled
return False
# SELinux is enabled, now get the mode
mode = subprocess_Popen(["getenforce"],
stdout = subprocess.PIPE
).communicate()[0].strip()
MSGR.log("getenforce returned %s" % (mode))
if mode == "Enforcing":
return True
else:
return False
def __get_info(self):
'''Gets the OS info. Raises Exception on failure.'''
self.os = platform.system().lower()
# linux specific stuff
if self.os == "linux":
self.machine = platform.machine()
platform_dist = platform.dist()
self.distro_id = platform_dist[0].lower()
self.distro_release = platform_dist[1]
self.distro_codename = platform_dist[2].lower()
# non-linux system, to be implemented when needed
else:
pass
self.is_64_bit = platform.machine() in ["x86_64"]
self.has_scratchbox = os.path.isfile("%s/etc/scratchbox-version"
%(SB_PATH))
if TESTING_UI: # when testing all pages shall be shown
self.has_unsup_vdso = True
self.has_unsup_selinux = True
self.has_unsup_mmap_mina = True
else:
# vdso is now supported by scratchbox
# (self.has_unsup_vdso, self.vdso_var) = \
# self.__is_vdso_unsupported()
self.has_unsup_selinux = self.__is_selinux_unsupported()
self.has_unsup_mmap_mina = self.__is_mmap_mina_unsupported()
MSGR.log("Got system info: %s" % self)
def __check_if_supported(self):
'''Checks if the system is supported by this script. Raises
FriendlyException if not.'''
# check OS: currently only Linux is supported
if self.os != "linux":
raise FriendlyException(
"Operating system '%s' is not supported.\nOnly Linux is "
"supported currently." % self.platform)
MSGR.log("System check done... system is supported")
def __str__(self):
'''String representation method.'''
return str(self.__dict__)
class UsersSelections(object):
'''Selections made by the user in the UI.'''
def __init__(self):
'''Constructor.'''
# names of users for whom to install/remove the targets
self._usernames = set()
self._usernames.add(get_default_username())
self.handle_usernames_update()
def get_usernames(self):
'''Usernames getter. Returns shallow copy, so that changes made to the
returned value don't change the member usernames.'''
return self._usernames.copy()
def set_usernames(self, new_usernames):
'''Usernames setter'''
if new_usernames != self._usernames: # same set no need to update
self._usernames = set(new_usernames)
self.handle_usernames_update()
usernames = property(get_usernames, set_usernames)
def handle_usernames_update(self):
'''Called whenever usernames are updated'''
pass
def __str__(self):
'''String representation method.'''
return str(self.__dict__)
def get_summary_text(self, newline = '\n', bold_begin = '', bold_end = ''):
'''Return a string with the selections summary.
newline = newline character to be used in the string
bold_begin = beginning tag of the bold text
bold_end = ending tag of the bold text'''
pass
class InstallSelections(UsersSelections):
'''User's selections in the install UI.'''
def __init__(self):
'''Constructor.'''
if SYS_INFO.has_unsup_vdso:
# if True, VDSO must be permanently set to SB supported value
self.set_perm_vdso = False
if SYS_INFO.has_unsup_selinux:
# if True, SELinux mode must be permanently set to Permissive
self.set_perm_selinux = False
if SYS_INFO.has_unsup_mmap_mina:
# if True, mmap_min_addr must be permanently set to QEMU supported
# value
self.set_perm_mmap_mina = False
# if targets don't exist they will be created with default names so
# following variables are useless
self.targets_remove = False # shall remove existing target, or not
# target name prefix, used to create new target if not removing
# existing ones
self.targets_prefix = ""
# Not really a selection, updated each time user names set is
# updated. For users that already have installation targets will store
# user name as key and a list of two Boolean flags as a value. The
# first flags is True if x86 target exists and the second one if armel
# target exists.
self.existing_targets = {}
# TODO https_proxy for extranet???
self.proxy_env_var = "http_proxy" # proxy environment variable
self.proxy = os.getenv(self.proxy_env_var, "") # the proxy server
self.display = ":2"
UsersSelections.__init__(self)
targets_exist = property(lambda self: bool(self.existing_targets),
doc = "True if installation targets already "
"exist for any of the selected users")
def handle_usernames_update(self):
'''Updates the existing_targets dictionary to reflect currently
selected usernames.'''
UsersSelections.handle_usernames_update(self)
self.existing_targets.clear()
for username in self._usernames:
target_x86_exist = sb_target_exists(username, TARGET_X86.name)
target_armel_exist = sb_target_exists(username, TARGET_ARMEL.name)
# if either exists add to the dictionary
if target_x86_exist or target_armel_exist:
self.existing_targets[username] = [target_x86_exist, target_armel_exist]
def get_summary_text(self, newline = '\n', bold_begin = '', bold_end = ''):
'''See docstring of the same method in the parent class'''
text = ""
# VDSO summary
if SYS_INFO.has_unsup_vdso:
text += "%s%s%s %s%s" % (bold_begin,
si.summary_txt_vdso,
bold_end,
bool_to_yesno(self.set_perm_vdso),
newline)
text += newline
# SELinux summary
if SYS_INFO.has_unsup_selinux:
text += "%s%s%s %s%s" % (bold_begin,
si.summary_txt_selinux,
bold_end,
bool_to_yesno(self.set_perm_selinux),
newline)
text += newline
# mmap_min_addr summary
if SYS_INFO.has_unsup_mmap_mina:
text += "%s%s%s %s%s" % (bold_begin,
si.summary_txt_mmap_mina,
bold_end,
bool_to_yesno(self.set_perm_mmap_mina),
newline)
text += newline
# users summary
text += "%s%s%s%s" % \
(bold_begin, si.summary_txt_users, bold_end, newline)
text += seq_to_str(self.usernames, newline, newline)
# these are only printed if any users are chosen
if self.usernames:
# if any targets exist
if self.targets_exist:
text += newline
# overwriting or not
text += "%s%s: %s%s%s" % (
bold_begin,
si.targets_txt_overwrite,
bold_end,
bool_to_yesno(self.targets_remove),
newline)
# target name prefix
if not self.targets_remove:
text += newline
text += "%s%s: %s%s%s" % (bold_begin,
si.summary_txt_targets_prefix,
bold_end,
self.targets_prefix,
newline)
return text
class RemoveSelections(UsersSelections):
'''User's selections in the remove UI.'''
def __init__(self):
'''Constructor.'''
UsersSelections.__init__(self)
def get_summary_text(self, newline = '\n', bold_begin = '', bold_end = ''):
'''See docstring of the same method in the parent class'''
text = ""
text += (
"%(bold_begin)sWARNING!%(bold_end)s The '%(sb_path)s' directory, "
"all of the underlying directories and their contents will be "
"completely removed. %(bold_begin)sThis means that user home "
"directories in %(sb_name)s will also be removed!%(bold_end)s " %
{"nl" : newline, "bold_begin" : bold_begin, "bold_end" : bold_end,
"sb_path" : SB_PATH, "sb_name" : SB_NAME, "my_name" : MY_NAME })
if PRODUCT_FILES:
text += (
"Additionally, the following files, installed by the "
"%(my_name)s will be removed:%(nl)s%(nl)s%(files)s%(nl)sUpon "
"completion of the removal it will not be possible to recover "
"any of those files." % {"my_name" : MY_NAME, "nl" : newline,
"files" : seq_to_str([f.path for f in PRODUCT_FILES],
newline, newline, False)})
text = get_wrapped(text)
text += newline
text += newline
# scratchbox
text += "%s%s%s %s%s" % (bold_begin,
sr.summary_txt_scratchbox,
bold_end,
bool_to_yesno(True),
newline)
# the targets
text += "%s%s%s %s%s" % (bold_begin,
sr.summary_txt_targets,
bold_end,
bool_to_yesno(True),
newline)
# scratchbox homes
text += "%s%s%s %s%s" % (bold_begin,
sr.summary_txt_sb_homes,
bold_end,
bool_to_yesno(True),
newline)
# files
if PRODUCT_FILES:
text += "%s%s%s %s%s" % (bold_begin,
sr.summary_txt_files,
bold_end,
bool_to_yesno(True),
newline)
return text
class PkgManager(object):
'''System independent interface to the package manager. This is an abstract
base class'''
CmdInstall = 0
CmdRemove = 1
def __init__(self):
'''Constructor.'''
# these are set in child classes
self.name = None # name of the package manager for the UI text
self.src_dir = None # repository sources directory
self.src_file = None # name of the repository source file
self.repo = "" # product (Scratchbox) repository
# name of the product (Scratchbox) installation package, a list of
# names if there are many packages
self.pkg_name_product = None
self.pkg_desc_product = SB_NAME # if set will be displayed instead of package name when installing/removing
self.pkg_name_pyqt4 = None # name of the PyQt4 package
self.pkg_name_pexpect = None # name of the pexpect package
self.pkg_name_pycurl = None # name of the curl bindings package
self.pkg_name_xephyr = None # name of the xephyr server package
self.pkg_name_https_transport = None # name of the package managers
# HTTPS transport package
def __run_cmd(self, cmd, name, desc, fatal):
'''Runs a package manager command.
cmd = command to run
name = string name of the package to install, if it is a list all
packages from the list will be installed
desc = description, will be used to say, if not specified package
name is used instead
fatal = True if the installation process should be executed as a
fatal command'''
cmd_data = {self.CmdInstall : ("Installing %s package%s", self._pkg_install),
self.CmdRemove : ("Removing %s package%s", self._pkg_remove)}
cmd_say_msg = cmd_data[cmd][0]
cmd_meth = cmd_data[cmd][1]
# convert list of names to string
if isinstance(name, (list, tuple)):
name = " ".join(name)
pl = "s"
else:
pl = ""
if not desc:
desc = name
try:
self.verify_is_unlocked()
MSGR.say(cmd_say_msg % (desc, pl), MSGR.SayTypeTask)
cmd_meth(name, fatal)
finally:
MSGR.cli_progress_stop()
def pkg_install(self, name, desc = None, fatal = True):
'''Installs a package. Children should not overridde this method but
provide one starting with underscore instead. For details see the
__run_cmd method.'''
self.__run_cmd(self.CmdInstall, name, desc, fatal)
def pkg_remove(self, name, desc = None, fatal = True):
'''Removes a package. Children should not overridde this method but
provide one starting with underscore instead. For details see the
__run_cmd method.'''
self.__run_cmd(self.CmdRemove, name, desc, fatal)
def _pkg_install(self, name, fatal = True):
raise AbstractMethodException()
def _pkg_remove(self, name, fatal = True):
raise AbstractMethodException()
def pkg_is_installed(self, name):
'''Returns True if package is installed, False otherwise. Children
should overridde this method.
name = name of the package'''
raise AbstractMethodException()
def is_locked(self):
'''Returns True if package manager is locked by another process. False
otherwise. Children should overridde this method.'''
raise AbstractMethodException()
def verify_is_unlocked(self):
'''Upon return of this method the package manager will not have any
locks.'''
while self.is_locked():
answer = MSGR.ask(
"",
"It appears %s is locked, please close any applications using "
"%s, then retry!" % (self.name, self.name),
[ASK_CHOICE_RETRY, ASK_CHOICE_ABORT], ASK_CHOICE_RETRY)
if answer == ASK_CHOICE_ABORT:
raise FriendlyException("Could not use %s because of lock!" %
(self.name,))
def repo_requires_authentication(self):
'''Returns True if authentication is needed to access the repository'''
return self.repo.find("https") != -1
def get_repo_uri(self):
'''Returns repository URI, that's gets rid of deb distribution and
component parts of the repo. It is assumed that repo format is
according to man 'sources.list':
deb uri distribution [component1] [component2] [...]
Hence, currently only Debian repository entries are supported.'''
return self.repo[self.repo.index("https"):self.repo.rindex(" ")]
def repo_install(self):
'''Adds product repository to the list of package manager repository
sources'''
MSGR.say("Installing repository file %s" % self.src_file,
MSGR.SayTypeTask)
if not os.path.isdir(self.src_dir):
raise FriendlyException(
"There is no %s on this system! The package manager cannot "
"be sane!" % (self.src_dir,))
# not https repo: leave as is
if not self.repo_requires_authentication():
repo = self.repo
# https repo: add credentials to it
else:
creds = CREDS_MGR.get_creds(self.get_repo_uri())
username = creds[0]
password = creds[1]
repo = self.repo.replace("https://", "https://%s:%s@" %
(username, urllib.quote(password, "")))
repo_lines = [
"# This is the %s repository for the %s" % (SB_NAME, PRODUCT_NAME),
repo
]
file_add_lines(self.src_file, repo_lines, append = False,
log_lines = True)
def repo_remove(self):
'''Removes product repository from the list of package manager
repository sources'''
MSGR.say("Removing repository file %s" % self.src_file,
MSGR.SayTypeTask)
try:
os.remove(self.src_file)
except OSError, e:
MSGR.warn("Repository source file does not exist (%s)" % e)
MSGR.cli_progress_stop()
def product_install(self):
'''Installs all the product packages'''
self.repo_install()
self.pkg_install(self.pkg_name_product, self.pkg_desc_product)
def product_remove(self):
'''Removes all the product packages'''
self.pkg_remove(self.pkg_name_product, self.pkg_desc_product)
self.repo_remove()
def _exec_cmd(self, cmd, fatal):
'''Executes a command.
cmd = command to execute
fatal = if True then command will be executed as critical'''
if fatal:
exec_cmd_fatal(cmd)
else:
exec_cmd_error(cmd)
class AptPkgManager(PkgManager):
'''Interface to the Debian apt package manager'''
def __init__(self):
'''Constructor.'''
PkgManager.__init__(self)
self.name = "apt"
self.src_dir = "/etc/apt/sources.list.d"
self.src_file = os.path.join(self.src_dir,
PRODUCT_NAME_FILES + ".list")
self.repo = SB_DEB_REPO
self.pkg_name_product = SB_DEB_PACKAGES
self.pkg_name_pyqt4 = "python-qt4"
self.pkg_name_pexpect = "python-pexpect"
self.pkg_name_pycurl = "python-pycurl"
self.pkg_name_xephyr = "xserver-xephyr"
self.pkg_name_https_transport = "apt-transport-https"
def pkg_is_installed(self, name):
status = subprocess_Popen(["dpkg", "-s", name],
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT
).communicate()[0].strip()
MSGR.log("Package %s status:\n%s" % (name, status))
p = re.compile("Status: install ok installed")
for line in status.splitlines():
m = p.match(line)
if m:
return True
return False
def is_locked(self):
return exec_cmd("apt-get check -qq")
def _pkg_install(self, name, fatal = True):
self._exec_cmd("apt-get update -qq", fatal)
self._exec_cmd("apt-get install %s --force-yes -y" % name, fatal)
def _pkg_remove(self, name, fatal = True):
self._exec_cmd("apt-get remove %s -y" % name, fatal)
# remove useless dependencies, this will silently fail on systems that
# don't support 'autoremove' (e.g. Debian Etch)
exec_cmd("apt-get autoremove -y")
class YumPkgManager(PkgManager):
'''Interface to the yum package manager'''
def __init__(self):
'''Constructor.'''
PkgManager.__init__(self)
self.name = "yum"
self.src_dir = "/etc/yum.repos.d"
self.src_file = os.path.join(self.src_dir,
PRODUCT_NAME_FILES + ".repo")
self.pkg_name_pyqt4 = "PyQt4"
self.pkg_name_pexpect = "pexpect"
self.pkg_name_pycurl = "python-pycurl"
self.pkg_name_xephyr = "xorg-x11-server-Xephyr"
def pkg_is_installed(self, name):
returncode = exec_cmd("yum list installed %s" % (name,))
if returncode:
return False
else:
return True
def is_locked(self):
# seems file will exist while yum is locked
return os.path.isfile("/var/run/yum.pid")
def _pkg_install(self, name, fatal = True):
self._exec_cmd("yum install %s -y" % name, fatal)
def _pkg_remove(self, name, fatal = True):
self._exec_cmd("yum remove %s -y" % name, fatal)
class ZyppPkgManager(PkgManager):
'''Interface to the ZYpp package manager of OpenSuse. Uses command line
interface zypper.'''
def __init__(self):
'''Constructor.'''
PkgManager.__init__(self)
self.name = "zypp"
self.src_dir = "/etc/zypp/repos.d/"
self.src_file = os.path.join(self.src_dir,
PRODUCT_NAME_FILES + ".repo")
self.pkg_name_pyqt4 = "python-qt4"
self.pkg_name_pexpect = "python-pexpect"
self.pkg_name_pycurl = "python-curl"
self.pkg_name_xephyr = "xorg-x11-server-extra"
def pkg_is_installed(self, name):
status = subprocess_Popen(["zypper", "info", name],
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT
).communicate()[0].strip()
MSGR.log("Package %s status:\n%s" % (name, status))
p = re.compile("Installed: Yes")
for line in status.splitlines():
m = p.match(line)
if m:
return True
return False
def is_locked(self):
return os.path.isfile("/var/run/zypp.pid")
def _pkg_install(self, name, fatal = True):
self._exec_cmd("zypper in -y %s" % name, fatal)
def _pkg_remove(self, name, fatal = True):
self._exec_cmd("zypper rm -y %s" % name, fatal)
class UnknownPkgManager(PkgManager):
'''Package manager interface created on systems that have no known package
manager. This class just prints errors messages if asked to install
packages.'''
def __init__(self):
'''Constructor.'''
PkgManager.__init__(self)
self.src_dir = "None"
self.src_file = os.path.join(self.src_dir,
PRODUCT_NAME_FILES + ".repo")
self.pkg_name_pyqt4 = "Python Qt4 bindings"
self.pkg_name_pexpect = "pexpect"
self.pkg_name_pycurl = "Python cURL bindings"
self.pkg_name_xephyr = "Xephyr X server"
def pkg_is_installed(self, name):
return True
def is_locked(self):
return False
def _pkg_install(self, name, fatal = True):
raise FriendlyException("Please install package %s..." % (name))
def _pkg_remove(self, name, fatal = True):
raise FriendlyException("Please remove package %s..." % (name))
def pkg_manager():
'''System dependent package manager factory function'''
if SYS_INFO.distro_id in ["ubuntu", "debian", "linuxmint"]:
return AptPkgManager()
elif SYS_INFO.distro_id in ["fedora", "redhat"]:
return YumPkgManager()
elif SYS_INFO.distro_id in ["suse"]:
return ZyppPkgManager()
else:
return UnknownPkgManager()
class ProcessWrapper(object):
'''Wrapper around the process creation classes. It is made to store the PID
of the running process, so the setup could kill it (including its children,
i.e. the whole process group) when needed. Although the process classes
(subprocess and pexpect) have kill method, they do not kill the whole
group.'''
def __init__(self, process_class, *args, **kwds):
'''Constructor.'''
# attributes can be set for this object using dot notation until this
# flag is not set
self.__dict__['__initialized'] = False
self.__process_class = process_class
MSGR.log("Creating process %s: %s %s" %
(self.__process_class, args, kwds))
self.__process_object = None
try:
self.__process_object = process_class(*args, **kwds)
except Exception, e:
if False: # logging to be done in callers
MSGR.log_exc()
MSGR.log(str(e))
if hasattr(e, 'child_traceback'): # subprocess.Popen stuff
MSGR.log("child_traceback:")
MSGR.log(e.child_traceback)
raise
MSGR.log("Created %s with pid = %s" %
(self.__process_class, self.__process_object.pid))
global CHILD_PIDS
CHILD_PIDS.add(self.__process_object.pid)
self.__dict__['__initialized'] = True
def __del__(self):
'''Destructor.'''
global CHILD_PIDS
if self.__process_object:
assert self.__process_object.pid in CHILD_PIDS, \
"PID (%s) of running child should be in the list" % \
(self.__process_object.pid)
status = "Unknown"
if hasattr(self.__process_object, "returncode"): # subprocess.Popen
status = self.__process_object.returncode
elif hasattr(self.__process_object, "exitstatus"): # pexpect
status = self.__process_object.exitstatus
MSGR.log("Destroying %s with pid = %s, exit status = %s" %
(self.__process_class, self.__process_object.pid,
status))
CHILD_PIDS.remove(self.__process_object.pid)
else:
MSGR.log("Destroying %s with no object whatsoever!" %
(self.__process_class))
def __getattr__(self, attr):
'''Delegates functionality to the process object'''
return getattr(self.__process_object, attr)
def __setattr__(self, attr, val):
'''If this object is not initialized or it has the specified attribute,
that attribute of this object will be set. Otherwise the attribute of
the delegated object will be set. NOTE: This means, after the
initialization attributes cannot be added to this object using the dot
notation.'''
if not self.__dict__['__initialized'] or \
self.__dict__.has_key(attr):
object.__setattr__(self, attr, val)
else:
setattr(self.__process_object, attr, val)
def subprocess_Popen(*args, **kwds):
'''Wrapper around subprocess.Popen. If a process is created using this
wrapper, it will be killed if setup quits before completion.'''
return ProcessWrapper(subprocess.Popen, *args, **kwds)
def pexpect_spawn(*args, **kwds):
'''Wrapper around pexpect.spawn. If a process is created using this
wrapper, it will be killed if setup quits before completion.'''
pexpect = IMPORTER['pexpect']
return ProcessWrapper(pexpect.spawn, *args, **kwds)
def signal_all(signum):
'''Sends a signal to process groups of all of the children belonging to the
different group than the script. Then the same signal is sent to the
process group of the script. The order is such so that all different
children groups are killed before the scripts group is killed.'''
MSGR.log("About to send signal %s to self and children %s" %
(signum, CHILD_PIDS))
my_pgid = os.getpgid(os.getpid())
for child_pid in CHILD_PIDS:
try:
child_pgid = os.getpgid(child_pid)
# it is possible for python object to exist (e.g. because it is not
# garbage collected), though the process has already died
except OSError, e:
MSGR.warn("Child group %d does not exist (%s)" % (child_pid, e))
continue
if child_pid != my_pgid:
MSGR.log("Sending signal %s to child group pid = %s, pgid = %s" %
(signum, child_pid, child_pgid))
os.killpg(child_pgid, signum)
MSGR.log("Sending signal %s to my group %s" % (signum, my_pgid))
os.killpg(my_pgid, signum)
def kill_all():
'''If there are children processes, kills their groups then the group of
the script, otherwise just quits.'''
if CHILD_PIDS:
MSGR.log("Committing suicide...")
signal_all(signal.SIGTERM)
time.sleep(1)
signal_all(signal.SIGKILL)
else:
sys.exit(100)
def exec_cmd(command, username = None, env = None):
'''Executes a command and returns the result. Since the command is executed
via shell exception is not raised if command is not in path. Instead shell
will return error code.
username = If set, the command will be executed with the credentials
(UID & GID) of that user.
env = If set, defines the environment variables for the command.'''
# if root privileges not needed, specify function to remove root privileges
if username:
MSGR.log("Executing as user %s: %s" % (username, command))
# root cannot run scratchbox commands, sbox group is needed to run them
need_sbox_group = False
if command.startswith(SB_PATH):
need_sbox_group = True
child_preexec_fn = lambda: set_guid(username, need_sbox_group)
else:
MSGR.log("Executing: %s" % (command,))
child_preexec_fn = None
p = subprocess_Popen("exec " + command,
stdout = MSGR.fo_log,
stderr = subprocess.STDOUT,
preexec_fn = child_preexec_fn,
shell = True,
env = env)
p.wait()
return p.returncode
def get_sudo_env_str():
'''Returns an env string for sudo command. That string will set needed
enviroment variables for sudo. By default sudo removes most of the
environment variables, so that is why this env workaround is used.'''
env_str = ""
# environment variables to pass to sudo
env_vars = ["https_proxy", "http_proxy"]
for var in env_vars:
if os.getenv(var):
env_str += " %s=%s" % (var, os.getenv(var))
if env_str:
env_str = "env" + env_str
return env_str
def exec_cmd_creds(command, username = None, env = None):
'''Executes a command and sends to it repo info credentials if the command
asks. Returns the return code of the comand.
username = If set, the command will be executed with the credentials
(UID & GID) of that user.
env = If set, defines the environment variables for the command.'''
pexpect = IMPORTER['pexpect']
if username: # run as user
env_str = get_sudo_env_str()
command = "sudo -H -u %s %s " % (username, env_str) + command
creds = CREDS_MGR.get_creds()
username = creds[0]
password = creds[1]
MSGR.log("Executing: %s" % command)
timeout_s = 6000 # in seconds, should be enough for most tasks
e = pexpect_spawn(command, logfile = MSGR.fo_log, timeout = timeout_s,
env = env)
index = e.expect(['Username:', pexpect.EOF, pexpect.TIMEOUT])
if index == 0: # credentials asked
e.logfile = None # turn off logging, not to show username/password
e.sendline(username)
e.expect('Password:')
e.sendline(password)
e.logfile = MSGR.fo_log
e.expect(pexpect.EOF)
elif index == 1: # EOF (e.g. creds were not asked, used from netrc)
MSGR.log("EOF while expecting")
elif index == 2: # TIMEOUT
MSGR.warn("TIMEOUT while expecting")
e.close()
returncode = e.exitstatus
if returncode == None: # timeout or other failure
MSGR.warn("Expect exit status is None, assuming failure")
returncode = 1
return returncode
class CmdFatalFailure(Exception):
'''Exception raised when execution of a fatal command fails.'''
pass
class CmdErrorFailure(Exception):
'''Exception raised when execution of a non-critical command fails.'''
pass
def exec_cmd_fatal(command, username = None, env = None, send_creds = False):
'''Executes a critical command and raises exception if command execution
fails. Critical commands are the ones that must succeed in order for the
setup to go on.
username = If set, the command will be executed with the credentials
(UID & GID) of that user.
send_creds = set to True if command might ask repo credentials, so
credentials will be send to the command'''
if send_creds:
returncode = exec_cmd_creds(command, username, env)
else:
returncode = exec_cmd(command, username, env)
if returncode:
raise CmdFatalFailure("Giving up, because failed to: %s" % command)
def exec_cmd_error(command, username = None, env = None, send_creds = False):
'''Executes a command and stores an error if command execution fails. This
function should be used to execute commands that are not critical, i.e. if
they fail the setup can continue.
username = If set, the command will be executed with the credentials
(UID & GID) of that user.
send_creds = set to True if command might ask repo credentials, so
credentials will be send to the command'''
if send_creds:
returncode = exec_cmd_creds(command, username, env)
else:
returncode = exec_cmd(command, username, env)
if returncode:
MSGR.add_error()
raise CmdErrorFailure("Error executing: %s" % command)
def cut_str(var):
'''Reduces the length of the passed in string and returns the cut
string. If the passed in variable is not a string, nothing is done. The
length of strings is normally reduced for logging, e.g. to avoid logging
whole file contents.'''
max_str_len = 160
# var is a too long string
if isinstance(var, str) and len(var) > max_str_len:
return var[:max_str_len] + "..."
return var
def cut_strs_in_container(cont):
'''Returns copy of the container with strings cut.'''
# container is a sequence
if isinstance(cont, (list, tuple)):
out = []
for i in cont:
out.append(cut_str(i))
# container is a mapping
elif isinstance(cont, dict):
out = cont.copy()
for k in out.iterkeys():
out[k] = cut_str(out[k])
else:
raise Exception("Unsupported type!!!")
return out
def seq_to_str(seq, sep = '\n', last_sep = '\n', sort = True):
'''Convers a sequence into a string. Returns the new string. If there are
no items in the sequence then None is returned.
sep = character to separate sequence items in the string
last_sep = character to be used after the last item
sort = whether to sort the sequence before printing'''
if not seq:
return "None" + sep
else:
text_str = ''
if sort:
fseq = sorted(seq)
else:
fseq = list(seq) # convert sets etc. to list to index later on
for i, v in enumerate(fseq):
if i == len(fseq) - 1:
nl = last_sep
else:
nl = sep
text_str += v + nl
return text_str
def bool_to_yesno(bool_value):
'''Return yes if Boolean value is true, no if it is false'''
if bool_value:
return 'Yes'
else:
return 'No'
class MyTextWrapper(textwrap.TextWrapper):
'''Child of TextWrapper that can wrap multiple paragraph in one string'''
def __init__(self):
'''Constructor.'''
# break_long_words: URLs can be long, so breaking them disabled
# break_on_hyphens: log file name contains hyphens, so disabled
textwrap.TextWrapper.__init__(self,
break_long_words = False)
# new in Python 2.6
if hasattr(self, "break_on_hyphens"):
self.break_on_hyphens = False
def wrap_multi(self, text):
'''Returns list of wrapped paragraphs. Compared to wrap() method,
this method can wrap multiple paragraphs from a single text string.
Beginning of a paragraphs is marked by a newline character.'''
# paragraphs in/out
pgphs_in = text.splitlines()
pgphs_out = []
for paragraph in pgphs_in:
if paragraph: # non-empty string
wrapped_paragraph = self.wrap(paragraph)
pgphs_out.extend(wrapped_paragraph)
else: # empty string, i.e. single newline in original text
pgphs_out.append(paragraph)
return pgphs_out
def fill_multi(self, text):
'''Returns a string, otherwise same as wrap_multi()'''
return "\n".join(self.wrap_multi(text))
TEXT_WRAPPER = MyTextWrapper()
def print_wrapped(text):
'''Prints wrapped text'''
print get_wrapped(text)
def get_wrapped(text):
'''Retrurns wrapped copy of text'''
return TEXT_WRAPPER.fill_multi(text)
def get_default_username():
'''Returns username of the user that ran this script. If the script is ran
by root returns the username of the user that invoked sudo or su. If
running as root and username cannot be found, then will return the first
available username from all usernames.'''
if get_default_username.cached_name:
return get_default_username.cached_name
# this routine can be (and currently is) used before MSGR is created
if MSGR:
log = MSGR.log
else:
# log = sys.stdout.write
log = lambda msg: None
username = ""
uid = os.geteuid()
# running non-root command get name from UID
if uid != 0:
username = pwd.getpwuid(uid).pw_name
log("Got default username from UID: %s" % (username,))
# running as root have to guess the name
else:
log("UID is 0, trying to guess default username")
all_usernames = get_all_usernames()
# environment variables to check for usernames
env_vars = ['SUDO_USER', 'USERNAME']
for var in env_vars:
name = os.getenv(var)
log("Validating username %s from %s" % (name, var))
# installing SDK as root is not allowed
if name == 'root':
log("Invalid: root")
continue
# make sure environment variables have reasonble name
elif name not in all_usernames:
log("Invalid: not in all usernames!")
continue
else:
username = name
log("Username is valid")
break
else: # was not found, use first one
log("Username not found, using first one")
username = all_usernames[0]
get_default_username.cached_name = username
return username
# will be set the first time function is executed, then it will be returned
# with all subsequent calls to save time and be consistent
get_default_username.cached_name = None
def get_default_user_ent():
'''Returns password database entry for the default user'''
return pwd.getpwnam(get_default_username())
def ask(title, question, choices, default_choice = None,
have_choices_in_question = True):
'''Command line version of ask.
title = the title
question = question to be asked (prompt)
choices = exclusive choices the user can use as an answer
default_choice = If set, choice to be selected when just return is pressed
have_choices_in_question = self explanatory
returns the selected choice'''
assert choices, "No choices specified"
assert not default_choice or \
default_choice in choices, "Default choice not in choices"
str_choices = "(%s)" % (seq_to_str(choices, "/", "", False))
# show the default choice
if default_choice:
str_choices = str_choices.replace(default_choice,
"[%s]" % default_choice)
if have_choices_in_question:
prompt = get_wrapped(question + " " + str_choices)
else:
prompt = get_wrapped(question)
while True: # loop until got answer
print
if title:
print title
answer = raw_input(prompt + ' ')
# user just pressed enter, return default answer
if default_choice and answer == '':
return default_choice
if answer in choices:
return answer
def is_yes(question, default_answer = True):
'''Ask the user for yes/no answer and returns True if user answered yes
False if no.
default_answer = In case user just presses return, if this argument is
True, yes will be the default answer, otherwise no will
be the default answer'''
if default_answer:
default_choice = ASK_CHOICE_YES
else:
default_choice = ASK_CHOICE_NO
answer = ask(None, question, [ASK_CHOICE_YES, ASK_CHOICE_NO],
default_choice)
if answer == ASK_CHOICE_YES:
return True
elif answer == ASK_CHOICE_NO:
return False
def show_eusa():
'''Shows Nokia EUSA and returns True if the user acceps it, False if the
user does not accept it.'''
p = subprocess_Popen("more", stdin = subprocess.PIPE)
p.communicate(EUSA_TEXT)
del p # remove from CHILD_PIDS before interaction
txt = "Do you accept all the terms of the preceding License Agreement?"
return is_yes(txt, False)
def scan_seq(desc_intro, desc_name, all_items, selected_items):
'''Shows list of all_items to the user and writes the selections into the
selected_items set. Also returns the selections.
desc_intro = introductory string for the list
desc_name = what an item is called (e.g. user)
all_items = list of all items, the user will be selecting from this list
selected_items = set of items selected by user, can have default values
'''
str_continue = 'c'
str_all = 'a'
str_none = 'n'
while True:
# print introduction
print "\n"
print_wrapped(desc_intro)
print
print_wrapped("Here is a list of available %ss, selected items are "
"marked with an asterisk before the index:" % desc_name)
print
# print list
for index, item in enumerate(all_items):
if item in selected_items: # mark selected items
print "*",
else:
print " ",
print index, item,
print
# user chose to modify the list
if is_yes("Would you like to change the selections in the list of %ss?"
% desc_name,
not len(selected_items)):# if any items: default answer is no
print
# read the new selections
answer = ''