class TestPathMapper(unittest.TestCase):
    def setUp(self):
        unittest.TestCase.setUp(self);
        self._var_root = "";
        self._var_cygwin_p = sys.platform.startswith("cygwin");
        self.obj = PathMapper(self._var_root, self._var_cygwin_p);

    def test__init__(self):
        self.assertTrue(isinstance(self.obj, PathMapper));
        self.assertEqual(self._var_root, self.obj.getRoot());

    def testAddMapping(self):
        mount = (
        "C:/cygwin/bin on /usr/bin type ntfs (binary,auto){LF}"
        "C:/cygwin/lib on /usr/lib type ntfs (binary,auto){LF}"
        "C:/cygwin on / type ntfs (binary,auto){LF}"
        "C: on /cygdrive/c type ntfs (binary,posix=0,user,noumount,auto){LF}"
        "".format(LF="\n")
        );
        f = TemporaryFile(mode='w+');
        f.writelines(mount);
        f.seek(0);
        mtab = f.readlines();
        f.close();

        mapping = {
            '/usr/bin/': "C:/cygwin/bin/",
            '/usr/lib/': "C:/cygwin/lib/",
            '/cygdrive/c/': "C:/",
        };
        self.obj._addMapping(mtab);
        self.assertEqual(self.obj.getMap(), mapping);
        self.assertEqual(self.obj.getMountRoot(), "C:/cygwin/");

    def testMapPath(self):
        if self._var_cygwin_p:
            self.assertEqual(self.obj.mapPath("/usr/bin/"), "/usr/bin/");
            return;

        mapping = {
             '/usr/bin/': 'C:/cygwin/bin/',
             '/usr/lib/': 'C:/cygwin/lib/',
             '/cygdrive/c/': 'C:/',
        };

        self.obj.setMap(mapping);

        for cyg in list(mapping.keys()):
            self.assertEqual(self.obj.mapPath(cyg), mapping[cyg]);
Exemple #2
0
class CygApt:
    INSTALLED_DB_MAGIC = "INSTALLED.DB 2\n"
    DIST_NAMES = ('curr', 'test', 'prev')
    FORCE_BARRED = ["python", "python-argparse", "gnupg", "xz"]
    SH_OPTIONS = ["--norc", "--noprofile"]
    DASH_OPTIONS = []
    CMD_OPTIONS = ["/V:ON", "/E:ON", "/C"]
    CYG_POSTINSTALL_DIR = "/etc/postinstall"
    CYG_PREREMOVE_DIR = "/etc/preremove"
    CYG_POSTREMOVE_DIR = "/etc/postremove"

    def __init__(
        self,
        main_packagename,
        main_files,
        main_cyg_apt_rc,
        main_cygwin_p,
        main_download_p,
        main_mirror,
        main_downloads,
        main_distname,
        main_nodeps_p,
        main_regex_search,
        main_nobarred,
        main_nopostinstall,
        main_nopostremove,
        main_dists,
        main_installed,
        main_scriptname,
        main_verbose,
        arch,
        setupDir="/etc/setup",
    ):

        # Define private properties
        self.__ballTarget = 'install'
        self.__regexSearch = main_regex_search
        self.__noBarred = main_nobarred
        self.__noPostInstall = main_nopostinstall
        self.__noPostRemove = main_nopostremove
        self.__appName = main_scriptname
        self.__files = main_files
        self.__downloadOnly = main_download_p
        self.__downloadDir = main_downloads
        self.__noDeps = main_nodeps_p
        self.__rcFile = main_cyg_apt_rc
        self.__verbose = main_verbose
        self.__setupDir = setupDir
        self.__rc = ConfigStructure()
        self.__setupIniPath = None
        self.__arch = arch

        # Init
        self.setPkgName(main_packagename)
        self.setCygwinPlatform(main_cygwin_p)
        self.setDists(main_dists)
        self.setInstalled(main_installed)

        # Read in our configuration
        self.getRessource(self.__rcFile)

        # Now we have a path mapper, check setup.exe is not running
        self._checkForSetupExe()

        # DOS specific
        if not self.__cygwinPlatform:
            self.__lnExists = os.path.exists("{0}/bin/ln.exe".format(
                self.__prefixRoot))
        else:
            self.__lnExists = True

        # Overrides to the .rc
        if (main_mirror):
            self.__rc.mirror = main_mirror
            self.__downloadDir = os.path.join(
                self.__rc.cache,
                urllib.quote(
                    self.__rc.mirror +
                    ('' if self.__rc.mirror.endswith('/') else '/'),
                    '').lower())

        if (main_distname):
            self.__rc.distname = main_distname

        if not (os.path.isfile(self.__installedDbFile) \
                or os.path.isfile(self.__setupIniPath)):
            msg = "{0} no such file, run {1} setup?".format(
                self.__installedDbFile, self.__appName)
            raise PackageCacheException(msg)
        else:
            self._getSetupIni()
            self.getInstalled()

    def getPkgName(self):
        return self.__pkgName

    def setPkgName(self, pkg_name):
        self.__pkgName = str(pkg_name)

    def getCygwinPlatform(self):
        return self.__cygwinPlatform

    def setCygwinPlatform(self, cygwin_p):
        self.__cygwinPlatform = bool(cygwin_p)

    def getDownloadDir(self):
        return self.__downloadDir

    def setDownlaodDir(self, download_dir):
        self.__downloadDir = str(download_dir)

    def getDists(self):
        return self.__dists

    def setDists(self, dists):
        self.__dists = dists

    def getSetupDir(self):
        return self.__setupDir

    def setSetupDir(self, setup_dir):
        self.__setupDir = str(setup_dir)

    def getInstalledDbFile(self):
        return self.__installedDbFile

    def setInstalledDbFile(self, installed_db_file):
        self.__installedDbFile = str(installed_db_file)

    def getDosBash(self):
        return self.__dosBash

    def setDosBash(self, dos_bash):
        self.__dosBash = str(dos_bash)

    def getDosLn(self):
        return self.__dosLn

    def setDosLn(self, dos_ln):
        self.__dosLn = str(dos_ln)

    def getDosXz(self):
        return self.__dosXz

    def setDosXz(self, dos_xz):
        self.__dosXz = str(dos_xz)

    def getDosDash(self):
        return self.__dosDash

    def setDosDash(self, dosDash):
        self.__dosDash = str(dosDash)

    def getPrefixRoot(self):
        return self.__prefixRoot

    def setPrefixRoot(self, prefix_root):
        self.__prefixRoot = prefix_root

    def getAbsRoot(self):
        return self.__absRoot

    def setAbsRoot(self, absolute_root):
        self.__absRoot = absolute_root

    def getPathMapper(self):
        return self.__pm

    def setPathMapper(self, path_mapper):
        assert isinstance(path_mapper, PathMapper)
        self.__pm = path_mapper

    def getRC(self):
        return self.__rc

    def setRC(self, rc_structure):
        assert isinstance(rc_structure, ConfigStructure)
        self.__rc = rc_structure

    def _checkForSetupExe(self):
        # It's far from bulletproof, but it's surprisingly hard to detect
        # setup.exe running since it doesn't lock any files.
        p = Process([self.__pm.mapPath("/bin/ps"), "-W"])
        p.run()
        psout = p.getOutput().splitlines(True)
        setup_re = re.compile(
            r"(?<![a-z0-9_ -])setup(|-1\.7|-x86|-x86_64)\.exe", re.IGNORECASE)
        for l in psout:
            m = setup_re.search(l)
            if m:
                raise AppConflictException("Please close {0} while "
                                           "running {1}".format(
                                               m.group(0), self.__appName))

    def _versionToString(self, t):
        def try_itoa(x):
            if isinstance(x, int):
                return "{0:d}".format(x)
            return x

        return "{0}-{1}".format(".".join(list(map(try_itoa, t[:-1]))), t[-1])

    def _stringToVersion(self, s):
        s = re.sub(r"([^0-9][^0-9]*)", " \\1 ", s)
        s = re.sub(r"[ _.-][ _.-]*", " ", s)

        def try_atoi(x):
            if re.match(r"^[0-9]*$", x):
                return int(x)
            return x

        return tuple(map(try_atoi, (s.split(' '))))

    def _splitBall(self, p):
        m = re.match(r"^(.*)-([0-9].*-[0-9]+)(.tar.(bz2|xz))?$", p)
        if not m:
            print("splitBall: {0}".format(p))
            return (p[:2], (0, 0))
        t = (m.group(1), self._stringToVersion(m.group(2)))
        return t

    def _joinBall(self, t):
        return "{0}-{1}".format(t[0], self._versionToString(t[1]))

    def _debug(self, s):
        s

    def help(self, ):
        """this help message"""
        pass

    def _getSetupIni(self):
        if self.__dists:
            return
        self.__dists = {
            'test': {},
            'curr': {},
            'prev': {}
        }
        f = open(self.__setupIniPath)
        contents = f.read()
        f.close()
        chunks = contents.split("\n\n@ ")
        for i in chunks[1:]:
            lines = i.split("\n")
            name = lines[0].strip()
            self._debug("package: {0}".format(name))
            packages = self.__dists['curr']
            records = {
                'sdesc': name
            }
            j = 1
            while j < len(lines) and lines[j].strip():
                self._debug("raw: {0}".format(lines[j]))
                if lines[j][0] == '#':
                    j = j + 1
                    continue
                elif lines[j][0] == '[':
                    self._debug('dist: {0}'.format(lines[j][1:5]))
                    packages[name] = records.copy()
                    packages = self.__dists[lines[j][1:5]]
                    j = j + 1
                    continue

                duo = lines[j].split(": ", 1)
                key = duo[0].strip()
                value = duo[1].strip()

                if value.find('"') != -1 \
                   and value.find('"', value.find('"') + 1) == -1:
                    while True:
                        j = j + 1
                        value += "\n" + lines[j]
                        if lines[j].find('"') != -1:
                            break
                records[key] = value
                j = j + 1
            packages[name] = records

    def _getUrl(self):
        if self.__pkgName not in self.__dists[self.__rc.distname] \
           or self.__ballTarget not in self.__dists[self.__rc.distname][self.__pkgName]:
            self._printErr(self._noPackage())
            install = 0
            for d in self.DIST_NAMES:
                if self.__pkgName in self.__dists[d] \
                   and self.__ballTarget in self.__dists[d][self.__pkgName]:
                    install = self.__dists[d][self.__pkgName][
                        self.__ballTarget]
                    print("warning: using [{0}]\n".format(d), file=sys.stderr)
                    break
            if not install:
                raise PackageException("{0} is not in {1}".format(
                    self.__pkgName, self.__setupIniPath))
        else:
            install = self.__dists[self.__rc.distname][self.__pkgName][
                self.__ballTarget]
        filename, size, md5 = install.split()
        return filename, md5

    def url(self):
        """print tarball url"""
        if not self.__pkgName:
            raise CommandLineException("url command requires a package name")
        print("{0}/{1}".format(self.__rc.mirror,
                               self._getUrl()[0]))

    def getBall(self):
        url, md5 = self._getUrl()
        return os.path.join(self.__downloadDir, url)

    def ball(self):
        """print tarball name"""
        print(self.getBall())

    def _doDownload(self):
        url, md5 = self._getUrl()
        directory = os.path.join(self.__downloadDir,
                                 os.path.split(url)[0])
        if not os.path.exists(self.getBall()) or not self._checkMd5():
            if not os.path.exists(directory):
                os.makedirs(directory)
            status = cautils.uri_get(directory,
                                     "{0}/{1}".format(self.__rc.mirror, url))
            if status:
                raise PackageCacheException(
                    "didn't find {0} "
                    "on mirror {1}: possible mismatch between setup.ini and "
                    "mirror requiring {2} update?".format(
                        self.__pkgName, self.__rc.mirror, self.__appName))

    def download(self):
        """download package (only, do not install)"""
        self._doDownload()
        self.ball()
        self.checksum()

    def _noPackage(self):
        return "{0} is not on mirror {1} in [{2}].".format(
            self.__pkgName, self.__rc.mirror, self.__rc.distname)

    # return an array contents all dependencies of self.__pkgName
    def getRequires(self):
        # Looking for dependencies on curr not prev or test
        dist = self.__dists['curr']
        if self.__pkgName not in self.__dists[self.__rc.distname]:
            raise PackageException(self._noPackage())
        if self.__noDeps:
            return []
        reqs = {
            self.__pkgName: 0
        }
        if self.__ballTarget == 'source' \
            and 'external-source' in dist[self.__pkgName]:
            reqs[dist[self.__pkgName]['external-source']] = 0
        n = 0
        while len(reqs) > n:
            n = len(reqs)
            for i in list(reqs.keys()):
                if i not in dist:
                    sys.stderr.write("error: {0} not in [{1}]\n".format(
                        i, self.__rc.distname))
                    if i != self.__pkgName:
                        del reqs[i]
                    continue
                reqs[i] = '0'
                p = dist[i]
                if 'requires' not in p:
                    continue
                update_list = [(x, 0) for x in p['requires'].split()]
                reqs.update(update_list)
        # Delete the ask package it is not require by it self (joke)
        reqs.pop(self.__pkgName)
        rlist = sorted(reqs.keys())
        return rlist

    def requires(self):
        """print requires: for package"""
        reqs = self.getRequires()
        if len(reqs) == 0:
            print("No dependencies for package {0}".format(self.__pkgName))
        else:
            print("\n".join(reqs))

    def getInstalled(self):
        if self.__installed:
            return self.__installed
        self.__installed = {
            0: {}
        }
        f = open(self.__installedDbFile)
        lines = f.readlines()
        f.close()
        for i in lines[1:]:
            name, ball, status = i.split()
            self.__installed[int(status)][name] = ball
        return self.__installed

    def setInstalled(self, installed):
        self.__installed = installed

    def _writeInstalled(self):
        file_db = open(self.__installedDbFile, 'w')
        file_db.write(self.INSTALLED_DB_MAGIC)
        lines = []
        for x in list(self.__installed[0].keys()):
            lines.append("{0} {1} 0\n".format(x, self.__installed[0][x]))
        file_db.writelines(lines)
        if file_db.close():
            raise IOError(self.__installedDbFile)

    def getField(self, field, default=''):
        for d in (self.__rc.distname, ) + self.DIST_NAMES:
            if self.__pkgName in self.__dists[d] \
               and field in self.__dists[d][self.__pkgName]:
                return self.__dists[d][self.__pkgName][field]
        return default

    def _psort(self, lst):
        lst.sort()
        return lst

    def _preverse(self, lst):
        lst.reverse()
        return lst

    def list(self):
        """list installed packages"""
        print("--- Installed packages ---")
        for self.__pkgName in self._psort(list(self.__installed[0].keys())):
            ins = self.getInstalledVersion()
            new = 0
            if self.__pkgName in self.__dists[self.__rc.distname] \
               and self.__ballTarget in self.__dists[self.__rc.distname][self.__pkgName]:
                new = self.getVersion()
            s = "{0:<19} {1:<15}".format(self.__pkgName,
                                         self._versionToString(ins))
            if new and new != ins:
                s += "({0})".format(self._versionToString(new))
            print(s)

    def filelist(self):
        """list files installed by given packages"""
        if not self.__pkgName:
            msg = "no package name given."
            raise CommandLineException(msg)
        else:
            print("\n".join(self.getFileList()))

    def _postInstall(self):
        self._runAll(self.CYG_POSTINSTALL_DIR)

    def _postRemove(self):
        if len(self.__files[1:]) == 0:
            msg = "must specify package to run postremove."
            raise CommandLineException(msg)
        else:
            for self.__pkgName in self.__files[1:]:
                preremove_sh = os.path.join(self.CYG_PREREMOVE_DIR,
                                            "{0}.sh".format(self.__pkgName))
                postremove_sh = os.path.join(self.CYG_POSTREMOVE_DIR,
                                             "{0}.sh".format(self.__pkgName))
                self._runScript(preremove_sh)
                self._runScript(postremove_sh)

    def getVersion(self):
        if self.__pkgName not in self.__dists[self.__rc.distname] \
           or self.__ballTarget not in self.__dists[self.__rc.distname][self.__pkgName]:
            self._printErr(self._noPackage())
            return (0, 0)
        package = self.__dists[self.__rc.distname][self.__pkgName]
        if 'ver' not in package:
            filename = package[self.__ballTarget].split()[0]
            ball = os.path.split(filename)[1]
            package['ver'] = self._splitBall(ball)[1]
        return package['ver']

    def getInstalledVersion(self):
        return self._splitBall(self.__installed[0][self.__pkgName])[1]

    def version(self):
        """print installed version"""
        if self.__pkgName:
            if self.__pkgName not in self.__installed[0]:
                msg = "{0} is not installed".format(self.__pkgName)
                raise PackageException(msg)
            print(self._versionToString(self.getInstalledVersion()))
        else:
            for self.__pkgName in self._psort(list(
                    self.__installed[0].keys())):
                if self.__pkgName not in self.__installed[0]:
                    self.__rc.distname = 'installed'
                    raise PackageException(self._noPackage())
                print("{0:<20}{1:<12}".format(
                    self.__pkgName,
                    self._versionToString(self.getInstalledVersion())))

    def getNew(self):
        lst = []
        for self.__pkgName in list(self.__installed[0].keys()):
            new = self.getVersion()
            ins = self.getInstalledVersion()
            if new > ins:
                self._debug(" {0} > {1}".format(new, ins))
                lst.append(self.__pkgName)
        return lst

    def new(self):
        """list new (upgradable) packages in distribution"""
        for self.__pkgName in self._psort(self.getNew()):
            print("{0:<20}{1:<12}".format(
                self.__pkgName, self._versionToString(self.getVersion())))

    def getMd5(self):
        url, md5 = self._getUrl()
        f = open(os.path.join(self.__downloadDir, url), 'rb')
        data = f.read()
        f.close()
        m = hashlib.md5()
        if 64 == len(md5):
            m = hashlib.sha256()
        if 128 == len(md5):
            m = hashlib.sha512()
        m.update(data)
        digest = m.hexdigest()
        return digest

    def _checkMd5(self):
        return self._getUrl()[1] == self.getMd5()

    def checksum(self):
        """check digest of cached package against database"""
        if not os.path.exists(self.getBall()):
            msg = "{0} not downloaded.".format(self.__pkgName)
            raise PackageCacheException(msg)
        url, md5 = self._getUrl()
        ball = os.path.basename(url)
        print("{0}  {1}".format(md5, ball))
        actual_md5 = self.getMd5()
        print("{0}  {1}".format(actual_md5, ball))
        if actual_md5 != md5:
            raise HashException(
                "digest of cached package doesn't match digest "
                "in setup.ini from mirror")

    def search(self):
        """search all package descriptions for string"""
        if not self.__pkgName:
            raise CommandLineException(
                "search command requires a string to search for")
        if not self.__regexSearch:
            regexp = re.escape(self.__pkgName)
        else:
            regexp = self.__pkgName
        packages = []
        keys = []
        if self.__rc.distname in self.__dists:
            keys = list(self.__dists[self.__rc.distname].keys())
        else:
            for i in list(self.__dists.keys()):
                for j in list(self.__dists[i].keys()):
                    if not j in keys:
                        keys.append(j)
        for i in keys:
            self.__pkgName = i
            if not regexp or re.search(regexp, i) \
               or re.search(regexp, self.getField('sdesc'), re.IGNORECASE) \
               or re.search(regexp, self.getField('ldesc'), re.IGNORECASE):
                if self.__rc.distname in self.__dists:
                    if self.__ballTarget in self.__dists[
                            self.__rc.distname][i]:
                        packages.append(i)
                else:
                    packages.append(i)
        for self.__pkgName in self._psort(packages):
            s = self.__pkgName
            d = self.getField('sdesc')
            if d:
                s += " - {0}".format(d[1:-1])
            print(s)

    def show(self):
        """print package description"""
        s = self.__pkgName
        d = self.getField('sdesc')
        if d:
            s += ' - {0}'.format(d[1:-1])
        ldesc = self.getField('ldesc')
        if ldesc != "":
            print(s + "\n")
            print(ldesc)
        else:
            print("{0}: not found in setup.ini: {1}".format(self.__appName, s))

    # return an array with all packages that must to be install
    def getMissing(self):
        reqs = self.getRequires()
        missingreqs = []
        # List of missing package on requires list
        for i in reqs:
            if i not in self.__installed[0]:
                missingreqs.append(i)
        if self.__pkgName not in self.__installed[0]:
            missingreqs.append(self.__pkgName)
        if missingreqs and self.__pkgName not in missingreqs:
            sys.stderr.write("warning: missing packages: {0}\n".format(
                " ".join(missingreqs)))
        elif self.__pkgName in self.__installed[0]:  # Check version
            ins = self.getInstalledVersion()
            new = self.getVersion()
            if ins >= new:
                sys.stderr.write("{0} is already the newest version\n".format(
                    self.__pkgName))
                # missingreqs.remove(self.__pkgName)
            elif self.__pkgName not in missingreqs:
                missingreqs.append(self.__pkgName)
        return missingreqs

    def missing(self):
        """print missing dependencies for package"""
        missing = self.getMissing()
        if len(missing) > 0:
            print("\n".join(missing))
        else:
            print("All dependent packages for {0} installed".format(
                self.__pkgName))

    def _runScript(self, file_name, optional=True):
        mapped_file = self.__pm.mapPath(file_name)
        mapped_file_done = mapped_file + ".done"
        if os.path.isfile(mapped_file):
            sys.stderr.write("running: {0}\n".format(file_name))
            cmd = ["bash"] + self.SH_OPTIONS + [mapped_file]
            if not self.__cygwinPlatform:
                cmd[0] = self.__dosBash

            cwd = None
            extension = os.path.splitext(mapped_file)[1]

            if ".dash" == extension:
                cmd = ["dash"] + self.DASH_OPTIONS + [mapped_file]
                if not self.__cygwinPlatform:
                    cmd[0] = self.__dosDash

            if extension in [".bat", ".cmd"]:
                cmd = ["cmd"
                       ] + self.CMD_OPTIONS + [os.path.basename(mapped_file)]
                cwd = os.path.dirname(mapped_file)

            retval = Process(cmd, cwd).run(True)

            if os.path.exists(mapped_file_done):
                os.remove(mapped_file_done)
            if retval == 0 and os.path.basename(file_name)[:3] not in [
                    '0p_', 'zp_'
            ]:
                shutil.move(mapped_file, mapped_file_done)
        else:
            if not optional:
                sys.stderr.write("{0}: WARNING couldn't find {1}.\n".format(
                    self.__appName, mapped_file))

    def _runAll(self, dirname):
        dirname = self.__pm.mapPath(dirname)

        if os.path.isdir(dirname):
            lst = list()
            for filename in os.listdir(dirname):
                if os.path.splitext(filename)[1] in [
                        '.sh', '.dash', '.bat', '.cmd'
                ]:
                    lst.append(filename)

            perpetualScripts = list()
            regularScripts = list()
            for filename in lst:
                if filename[:3] in ['0p_', 'zp_']:
                    perpetualScripts.append(filename)
                else:
                    regularScripts.append(filename)

            perpetualScripts.sort()
            lst = perpetualScripts + regularScripts

            for i in lst:
                self._runScript("{0}/{1}".format(dirname, i))

    def _doInstallExternal(self, ball):
        # Currently we use a temporary directory and extractall() then copy:
        # this is very slow. The Python documentation warns more sophisticated
        # approaches have pitfalls without specifying what they are.
        tf = cautils.open_tarfile(ball, self.__dosXz)
        members = tf.getmembers()
        tempdir = tempfile.mkdtemp()
        try:
            tf.extractall(tempdir)
            for m in members:
                if m.isdir():
                    path = self.__pm.mapPath("/" + m.name)
                    if not os.path.exists(path):
                        os.makedirs(path, m.mode)
            for m in members:
                if m.isdir():
                    path = self.__pm.mapPath("/" + m.name)
                    if not os.path.exists(path):
                        os.makedirs(path, m.mode)
                else:
                    path = self.__pm.mapPath("/" + m.name)
                    dirname = os.path.dirname(path)
                    if not os.path.exists(dirname):
                        os.makedirs(dirname)
                    if os.path.exists(path):
                        os.chmod(path, 0o777)
                        os.remove(path)
                    # Windows extract() is robust but can't form Cygwin links
                    # (It produces copies instead: bulky and bugbait.)
                    # Convert to links if possible -- depends on coreutils being installed
                    if m.issym() and self.__lnExists:
                        link_target = m.linkname
                        Process([self.__dosLn, "-s", link_target,
                                 path]).run(True)
                    elif m.islnk() and self.__lnExists:
                        # Hard link -- expect these to be very rare
                        link_target = m.linkname
                        mapped_target = self.__pm.mapPath("/" + m.linkname)
                        # Must ensure target exists before forming hard link
                        if not os.path.exists(mapped_target):
                            shutil.move(os.path.join(tempdir, link_target),
                                        mapped_target)
                        Process([self.__dosLn, mapped_target, path]).run(True)
                    else:
                        shutil.move(os.path.join(tempdir, m.name), path)
        finally:
            tf.close()
            cautils.rmtree(tempdir)

    def _doInstall(self):
        ball = self.getBall()
        if cautils.is_tarfile(ball):
            if not self.__cygwinPlatform:
                self._doInstallExternal(ball)
            tf = cautils.open_tarfile(ball, self.__dosXz)
            if self.__cygwinPlatform:
                tf.extractall(self.__absRoot)
            # Force slash to the end of each directories
            members = tf.getmembers()
            tf.close()
            lst = []
            for m in members:
                if m.isdir() and not m.name.endswith("/"):
                    lst.append(m.name + "/")
                else:
                    lst.append(m.name)
        else:
            print("{0}: bad tarball {1}. Install failed.".format(
                self.__appName, ball),
                  file=sys.stderr)
            return
        self._writeFileList(lst)

        status = 1
        if not self.__pkgName in self._integrityControl():
            status = 0
        self.__installed[status][self.__pkgName] = os.path.basename(ball)

        self._writeInstalled()

    def getFileList(self):
        filelist_file = os.path.join(self.__setupDir,
                                     "{0}.lst.gz".format(self.__pkgName))
        if not os.path.exists(filelist_file):
            if self.__pkgName not in self.__installed[0]:
                raise PackageException("{0} is not installed".format(
                    self.__pkgName))
            else:
                raise PackageException(
                    "{0} is installed, but {1} is missing".format(
                        self.__pkgName, filelist_file))
        gzf = gzip.GzipFile(filelist_file)
        lst = gzf.readlines()
        gzf.close()
        lst = [x.decode().strip() for x in lst]
        return lst

    def _touch(self, fname, times=None):
        f = open(fname, 'a')
        os.utime(fname, times)
        f.close()

    def _writeFileList(self, lst):
        gz_filename = os.path.join(self.__setupDir,
                                   "{0}.lst.gz".format(self.__pkgName))

        lst_cr = [x + "\n" for x in lst]

        # create iostring and write in gzip
        lst_io = io.BytesIO()
        lst_io_gz = gzip.GzipFile(fileobj=lst_io, mode='w')
        lst_io_gz.writelines([x.encode() for x in lst_cr])
        lst_io_gz.close()

        # save it in the file
        lst_gz = open(gz_filename, 'wb')
        lst_gz.write(lst_io.getvalue())
        lst_gz.close()
        lst_io.close()

        stat_struct = os.stat(self.__setupIniPath)
        atime = stat_struct[7]
        mtime = stat_struct[8]
        self._touch(gz_filename, (atime, mtime))

    def _removeFileList(self):
        lst_name = os.path.join(self.__setupDir,
                                "{0}.lst.gz".format(self.__pkgName))
        if os.path.exists(lst_name):
            os.remove(lst_name)
        else:
            sys.stderr.write("{0}: warning {1} no such file\n".format(
                sys.argv[0], lst_name))

    def _uninstallWantFileRemoved(self, filename, noremoves, nowarns):
        # Returns true if the path from the tarball should result in a file
        # removal operation, false if not.
        if not os.path.exists(filename) and not os.path.islink(filename):
            if filename not in nowarns:
                sys.stderr.write(
                    "warning: {0} no such file\n".format(filename))
            return False
        elif not os.path.isdir(filename) and filename not in noremoves:
            return True

    def _doUninstall(self):
        postremove_sh = "{0}/{1}.sh".format(self.CYG_POSTREMOVE_DIR,
                                            self.__pkgName)
        postinstall_sh = "{0}/{1}.sh".format(self.CYG_POSTINSTALL_DIR,
                                             self.__pkgName)
        preremove_sh = "{0}/{1}.sh".format(self.CYG_PREREMOVE_DIR,
                                           self.__pkgName)

        postinstall_done = "{0}.done".format(postinstall_sh)
        suppression_msg = ("{0}: postremove suppressed: "
                           "\"{0} postremove {1}\" to complete.".format(
                               self.__appName, self.__pkgName))

        lst = self.getFileList()
        expect_preremove = preremove_sh[1:] in lst
        expect_postremove = postremove_sh[1:] in lst

        if not self.__noPostRemove:
            if expect_preremove:
                self._runScript(preremove_sh, optional=False)
        else:
            print("{0}".format(suppression_msg), file=sys.stderr)

        # We don't expect these to be present: they are executed
        # and moved to $(packagename).sh.done
        nowarns = []
        nowarns.append(self.__pm.mapPath(postinstall_sh))
        nowarns.append(self.__pm.mapPath(preremove_sh))

        noremoves = []
        if self.__noPostRemove:
            noremoves.append(self.__pm.mapPath(preremove_sh))
        noremoves.append(self.__pm.mapPath(postremove_sh))

        # remove files
        for i in lst:
            filename = self.__pm.mapPath("/" + i)
            if os.path.islink(filename):
                os.remove(filename)
                continue
            if os.path.isdir(filename):
                continue
            if (self._uninstallWantFileRemoved(filename, noremoves, nowarns)):
                if os.path.exists(filename):
                    if self.__cygwinPlatform:
                        os.chmod(filename, 0o777)
                    if os.remove(filename):
                        raise IOError(filename)
                else:
                    if os.path.islink(filename):
                        os.remove(filename)
                    else:
                        print("{0}: warning: expected to remove {1} but it was"
                              " not there.".format(self.__appName, filename))
        if not self.__noPostRemove:
            if expect_postremove:
                self._runScript(postremove_sh, optional=False)
            postremove_sh_mapped = self.__pm.mapPath(postremove_sh)
            if os.path.isfile(postremove_sh_mapped):
                if os.remove(postremove_sh_mapped):
                    raise IOError(postremove_sh_mapped)

        # We don't remove empty directories: the problem is are we sure no other
        # package is depending on them.

        # setup.exe removes the filelist when a package is uninstalled: we try to be
        # as much like setup.exe as possible
        self._removeFileList()

        # Clean up the postintall script: it's been moved to .done
        if os.path.isfile(self.__pm.mapPath(postinstall_done)):
            os.remove(self.__pm.mapPath(postinstall_done))

        # update installed[]
        del (self.__installed[0][self.__pkgName])
        self._writeInstalled()

    def remove(self):
        """uninstall packages"""
        barred = []
        for self.__pkgName in self.__files[1:]:
            if self.__pkgName not in self.__installed[0]:
                sys.stderr.write("warning: {0} not installed\n".format(
                    self.__pkgName))
                continue
            if self._isBarredPackage(self.__pkgName):
                barred.append(self.__pkgName)
                continue
            sys.stderr.write("uninstalling {0} {1}\n".format(
                self.__pkgName,
                self._versionToString(self.getInstalledVersion())))
            self._doUninstall()
        self._barredWarnIfNeed(barred, "removing")

    def purge(self):
        """uninstall packages and delete from cache"""
        barred = []

        for self.__pkgName in self.__files[1:]:
            if self.__pkgName in self.__installed[0]:
                if self._isBarredPackage(self.__pkgName):
                    barred.append(self.__pkgName)
                    continue
                sys.stderr.write("uninstalling {0} {1}\n".format(
                    self.__pkgName,
                    self._versionToString(self.getInstalledVersion())))
                self._doUninstall()
            scripts = [
                self.CYG_POSTINSTALL_DIR + "/{0}.sh.done",
                self.CYG_PREREMOVE_DIR + "/{0}.sh.done",
                self.CYG_POSTREMOVE_DIR + "/{0}.sh.done"
            ]
            scripts = [s.format(self.__pkgName) for s in scripts]
            scripts = [self.__pm.mapPath(s) for s in scripts]
            for s in scripts:
                if os.path.exists(s):
                    os.remove(s)
            ball = self.getBall()
            if os.path.exists(ball):
                print("{0}: removing {1}".format(self.__appName, ball))
                os.remove(ball)

        self._barredWarnIfNeed(barred, "purging")

    def _barredWarnIfNeed(self, barred, command):
        num_barred = len(barred)
        if num_barred > 0:
            if num_barred == 1:
                this_these = "this package"
            else:
                this_these = "these packages"
            barredstr = ", ".join(barred)
            helpfull = ""
            close_all_cygwin_programs = ""
            if command == "installing":
                helpfull += (
                    "You can force the installation with the option -f. But it is recommended\n"
                    "to upgrade the Cygwin distribution, with the official Setup program\n"
                    "(e.g., setup.exe).")
                if "_autorebase" in barred:
                    close_all_cygwin_programs += (
                        "\n"
                        "Before that, you must close all Cygwin programs to perform rebasing\n"
                        "(e.g., rebaseall).")
            print("BarredWarning: NOT {1}:"
                  "    {2}\n{0} is dependent on {3} under Cygwin."
                  "{4}{5}".format(self.__appName, command, barredstr,
                                  this_these, helpfull,
                                  close_all_cygwin_programs),
                  file=sys.stderr)
            if not self.__cygwinPlatform:
                print("Use -f to override but proceed with caution.",
                      file=sys.stderr)

    def install(self):
        """download and install packages with dependencies"""
        suppression_msg = ("{0}: postinstall suppressed: "
                           "\"{0} postinstall\" to complete.".format(
                               self.__appName))
        missing = {}
        barred = []
        for self.__pkgName in self.__files[1:]:
            missing.update(dict([(x, 0) for x in self.getMissing()]))

        if len(missing) > 1:
            sys.stderr.write("to install: \n")
            sys.stderr.write("    {0}".format(" ".join(list(missing.keys()))))
            sys.stderr.write("\n")

        for self.__pkgName in list(missing.keys()):
            if not self._getUrl():
                del missing[self.__pkgName]

        for self.__pkgName in list(missing.keys()):
            if self._isBarredPackage(self.__pkgName):
                barred.append(self.__pkgName)
                del missing[self.__pkgName]

        for self.__pkgName in list(missing.keys()):
            self.download()
        if self.__downloadOnly:
            return
        for self.__pkgName in list(missing.keys()):
            if self.__pkgName in self.__installed[0]:
                sys.stderr.write("preparing to replace {0} {1}\n".format(
                    self.__pkgName,
                    self._versionToString(self.getInstalledVersion())))
                self._doUninstall()
            sys.stderr.write("installing {0} {1}\n".format(
                self.__pkgName, self._versionToString(self.getVersion())))
            self._doInstall()

        if self.__noPostInstall:
            print(suppression_msg, file=sys.stderr)
        else:
            self._postInstall()

        self._barredWarnIfNeed(barred, "installing")

    def postinstall(self):
        """Executes all undone postinstall scripts."""
        self._postInstall()

    def postremove(self):
        """Executes all undone preremove and postremove scripts."""
        self._postRemove()

    def _integrityControl(self, checklist=None):
        if None is checklist:
            checklist = list()

        options = ["-c"]
        if self.__verbose:
            options.append("-v")

        if len(checklist) == 0:
            checklist.append(self.__pkgName)

        command = ["/bin/cygcheck"] + options + checklist
        if not self.__cygwinPlatform:
            command = subprocess.list2cmdline(command)
            command = [self.__dosBash] + self.SH_OPTIONS + ["-c"] + [command]

        p = Process(command)
        p.run()
        outputlines = p.getOutput().splitlines(True)

        unformat = ""
        start = False
        incomplete = []
        for res in outputlines:
            try:
                res_split = res.split()
                package, version, status = res_split
            except ValueError:
                if len(res_split) > 0:
                    unformat += res
                continue

            if package == 'Package' \
               and version == 'Version' \
               and status == 'Status':
                start = True
                unformat = ''
            elif not start:
                continue

            if start and status == 'Incomplete':
                print(res[:-2])
                print(unformat)
                unformat = ""
                incomplete.append(package)

        return incomplete

    def upgrade(self):
        """all installed packages"""
        self.__files[1:] = self.getNew()
        self.install()

    def _printErr(self, err):
        print("{0}: {1}".format(self.__appName, err), file=sys.stderr)

    def _doUnpack(self):
        ball = self.getBall()
        basename = os.path.basename(ball)
        self.__pkgName = re.sub(r"(-src)*\.tar\.(bz2|gz)", '', basename)
        if os.path.exists(self.__pkgName):
            self._printErr("{0} already exists. Not overwriting.".format(
                self.__pkgName))
            return 1
        os.mkdir(self.__pkgName)
        if cautils.is_tarfile(ball):
            tf = cautils.open_tarfile(ball, self.__dosXz)
            tf.extractall(self.__pkgName)
            tf.close()
        else:
            raise InvalidFileException("Bad source tarball {0}, \n"
                                       "SOURCE UNPACK FAILED".format(ball))
        if not os.path.exists(self.__pkgName):
            raise IOError(self.__pkgName)
        print(self.__pkgName)

    def source(self):
        """download source package"""
        self.__ballTarget = 'source'
        for self.__pkgName in self.__files[1:]:
            self.download()
            self._doUnpack()

    def find(self):
        """find package containing file"""
        if self.__regexSearch:
            file_to_find = self.__pkgName
        else:
            file_to_find = re.escape(self.__pkgName)
        hits = []
        for self.__pkgName in self._psort(list(self.__installed[0].keys())):
            filenames_file = os.path.join(self.__setupDir,
                                          "{0}.lst.gz".format(self.__pkgName))
            if not os.path.exists(filenames_file):
                continue
            files = self.getFileList()
            for i in files:
                if re.search(file_to_find, "/{0}".format(i)):
                    hits.append("{0}: /{1}".format(self.__pkgName, i))
        print("\n".join(hits))

    def setRoot(self, root):
        if len(root) < 1 or root[-1] != "/":
            raise InvalidArgumentException("ROOT must end in a slash.")
        self.__prefixRoot = root[:-1]
        self.__absRoot = root

    def getRessource(self, filename):
        self.__rc = cautils.parse_rc(filename)

        if not self.__rc.cache:
            msg = "{0} doesn't define cache.".format(self.__rcFile)
            raise UnexpectedValueException(msg)
        if not self.__rc.mirror:
            msg = "{0} doesn't define mirror.".format(self.__rcFile)
            raise UnexpectedValueException(msg)

        # We want ROOT + "/etc/setup" and cd(ROOT) to work:
        # necessitates two different forms, prefix and absolute
        if (self.__cygwinPlatform):
            self.setRoot("/")
        else:
            self.setRoot(self.__rc.ROOT)
        self.__rc.ROOT = None
        self.__pm = PathMapper(self.__prefixRoot, self.__cygwinPlatform)
        self.__setupDir = self.__pm.mapPath(self.__setupDir)
        self.__rc.cache = self.__pm.mapPath(self.__rc.cache)
        self.__downloadDir = os.path.join(
            self.__rc.cache,
            urllib.quote(
                self.__rc.mirror +
                ('' if self.__rc.mirror.endswith('/') else '/'), '').lower())
        self.__installedDbFile = os.path.join(self.__setupDir, "installed.db")

        self.__setupIniPath = os.path.join(
            self.__downloadDir,
            self.__arch,
            "setup.ini",
        )
        self.__dosBash = "{0}bin/bash".format(self.__pm.getMountRoot())
        self.__dosLn = "{0}bin/ln".format(self.__pm.getMountRoot())
        self.__dosXz = self.__pm.mapPath("/usr/bin/xz")
        self.__dosDash = "{0}bin/dash".format(self.__pm.getMountRoot())
        return 0

    def _isBarredPackage(self, package):
        barred = []
        # add user barred package
        barred.extend(self.__rc.barred.split())
        # add force barred package
        barred.extend(self.FORCE_BARRED)

        # store current package name
        curr_pkgname = self.__pkgName

        # get barred package requires
        depbarred = []
        for self.__pkgName in barred:
            try:
                depbarred.extend(self.getRequires())
            except PackageException:
                pass

        barred.extend(depbarred)

        # set current package name
        self.__pkgName = curr_pkgname

        return (not self.__noBarred) and package in barred
Exemple #3
0
class CygApt:
    INSTALLED_DB_MAGIC = "INSTALLED.DB 2\n";
    DIST_NAMES = ('curr', 'test', 'prev');
    FORCE_BARRED = ["python", "python-argparse", "gnupg", "xz"];
    SH_OPTIONS = ["--norc", "--noprofile"];
    DASH_OPTIONS = [];
    CMD_OPTIONS = ["/V:ON", "/E:ON", "/C"];
    CYG_POSTINSTALL_DIR = "/etc/postinstall";
    CYG_PREREMOVE_DIR = "/etc/preremove";
    CYG_POSTREMOVE_DIR = "/etc/postremove";

    def __init__(self,
        main_packagename,
        main_files,
        main_cyg_apt_rc,
        main_cygwin_p,
        main_download_p,
        main_mirror,
        main_downloads,
        main_distname,
        main_nodeps_p,
        main_regex_search,
        main_nobarred,
        main_nopostinstall,
        main_nopostremove,
        main_dists,
        main_installed,
        main_scriptname,
        main_verbose,
        arch,
        setupDir="/etc/setup",
    ):

        # Define private properties
        self.__ballTarget = 'install';
        self.__regexSearch = main_regex_search;
        self.__noBarred = main_nobarred;
        self.__noPostInstall = main_nopostinstall;
        self.__noPostRemove = main_nopostremove;
        self.__appName = main_scriptname;
        self.__files = main_files;
        self.__downloadOnly = main_download_p;
        self.__downloadDir = main_downloads;
        self.__noDeps = main_nodeps_p;
        self.__rcFile = main_cyg_apt_rc;
        self.__verbose = main_verbose;
        self.__setupDir = setupDir;
        self.__rc = ConfigStructure();
        self.__setupIniPath = None;
        self.__arch = arch;

        # Init
        self.setPkgName(main_packagename);
        self.setCygwinPlatform(main_cygwin_p);
        self.setDists(main_dists);
        self.setInstalled(main_installed);

        # Read in our configuration
        self.getRessource(self.__rcFile);

        # Now we have a path mapper, check setup.exe is not running
        self._checkForSetupExe();

        # DOS specific
        if not self.__cygwinPlatform:
            self.__lnExists = os.path.exists(
                "{0}/bin/ln.exe".format(self.__prefixRoot)
            );
        else:
            self.__lnExists = True;

        # Overrides to the .rc
        if (main_mirror):
            self.__rc.mirror = main_mirror;
            self.__downloadDir = os.path.join(
                self.__rc.cache,
                urllib.quote(self.__rc.mirror+('' if self.__rc.mirror.endswith('/') else '/'), '').lower()
            );

        if (main_distname):
            self.__rc.distname = main_distname;

        if not (os.path.isfile(self.__installedDbFile) \
                or os.path.isfile(self.__setupIniPath)):
            msg = "{0} no such file, run {1} setup?".format(
                self.__installedDbFile,
                self.__appName
            );
            raise PackageCacheException(msg);
        else:
            self._getSetupIni();
            self.getInstalled();

    def getPkgName(self):
        return self.__pkgName;

    def setPkgName(self, pkg_name):
        self.__pkgName = str(pkg_name);

    def getCygwinPlatform(self):
        return self.__cygwinPlatform;

    def setCygwinPlatform(self, cygwin_p):
        self.__cygwinPlatform = bool(cygwin_p);

    def getDownloadDir(self):
        return self.__downloadDir;

    def setDownlaodDir(self, download_dir):
        self.__downloadDir = str(download_dir);

    def getDists(self):
        return self.__dists;

    def setDists(self, dists):
        self.__dists = dists;

    def getSetupDir(self):
        return self.__setupDir;

    def setSetupDir(self, setup_dir):
        self.__setupDir = str(setup_dir);

    def getInstalledDbFile(self):
        return self.__installedDbFile;

    def setInstalledDbFile(self, installed_db_file):
        self.__installedDbFile = str(installed_db_file);

    def getDosBash(self):
        return self.__dosBash;

    def setDosBash(self, dos_bash):
        self.__dosBash = str(dos_bash);

    def getDosLn(self):
        return self.__dosLn;

    def setDosLn(self, dos_ln):
        self.__dosLn = str(dos_ln);

    def getDosXz(self):
        return self.__dosXz;

    def setDosXz(self, dos_xz):
        self.__dosXz = str(dos_xz);

    def getDosDash(self):
        return self.__dosDash;

    def setDosDash(self, dosDash):
        self.__dosDash = str(dosDash);

    def getPrefixRoot(self):
        return self.__prefixRoot;

    def setPrefixRoot(self, prefix_root):
        self.__prefixRoot = prefix_root;

    def getAbsRoot(self):
        return self.__absRoot;

    def setAbsRoot(self, absolute_root):
        self.__absRoot = absolute_root;

    def getPathMapper(self):
        return self.__pm;

    def setPathMapper(self, path_mapper):
        assert isinstance(path_mapper, PathMapper);
        self.__pm = path_mapper;

    def getRC(self):
        return self.__rc;

    def setRC(self, rc_structure):
        assert isinstance(rc_structure, ConfigStructure);
        self.__rc = rc_structure;

    def _checkForSetupExe(self):
        # It's far from bulletproof, but it's surprisingly hard to detect
        # setup.exe running since it doesn't lock any files.
        p = Process([self.__pm.mapPath("/bin/ps"), "-W"]);
        p.run();
        psout = p.getOutput().splitlines(True);
        setup_re = re.compile(r"(?<![a-z0-9_ -])setup(|-1\.7|-x86|-x86_64)\.exe", re.IGNORECASE);
        for l in psout:
            m = setup_re.search(l);
            if m:
                raise AppConflictException(
                    "Please close {0} while "
                    "running {1}".format(m.group(0), self.__appName)
                );

    def _versionToString(self, t):
        def try_itoa(x):
            if isinstance(x, int):
                return "{0:d}".format(x);
            return x
        return "{0}-{1}".format(
            ".".join(list(map(try_itoa, t[:-1]))),
            t[-1]
        );

    def _stringToVersion(self, s):
        s = re.sub(r"([^0-9][^0-9]*)", " \\1 ", s);
        s = re.sub(r"[ _.-][ _.-]*", " ", s);
        def try_atoi(x):
            if re.match(r"^[0-9]*$", x):
                return int(x);
            return x
        return tuple(map(try_atoi, (s.split(' '))));

    def _splitBall(self, p):
        m = re.match(r"^(.*)-([0-9].*-[0-9]+)(.tar.(bz2|xz))?$", p);
        if not m:
            print("splitBall: {0}".format(p));
            return (p[:2], (0, 0));
        t = (m.group(1), self._stringToVersion(m.group(2)));
        return t;

    def _joinBall(self, t):
        return "{0}-{1}".format(t[0], self._versionToString(t[1]));

    def _debug(self, s):
        s;

    def help(self,):
        """this help message"""
        pass;

    def _getSetupIni(self):
        if self.__dists:
            return;
        self.__dists = {'test': {}, 'curr': {}, 'prev' : {}};
        f = open(self.__setupIniPath);
        contents = f.read();
        f.close();
        chunks = contents.split("\n\n@ ");
        for i in chunks[1:]:
            lines = i.split("\n");
            name = lines[0].strip();
            self._debug("package: {0}".format(name));
            packages = self.__dists['curr'];
            records = {'sdesc': name};
            j = 1;
            while j < len(lines) and lines[j].strip():
                self._debug("raw: {0}".format(lines[j]));
                if lines[j][0] == '#':
                    j = j + 1;
                    continue;
                elif lines[j][0] == '[':
                    self._debug('dist: {0}'.format(lines[j][1:5]));
                    packages[name] = records.copy();
                    packages = self.__dists[lines[j][1:5]];
                    j = j + 1;
                    continue;

                duo = lines[j].split(": ", 1);
                key = duo[0].strip();
                value = duo[1].strip();

                if value.find('"') != -1 \
                   and value.find('"', value.find('"') + 1) == -1:
                    while True:
                        j = j + 1;
                        value += "\n" + lines[j];
                        if lines[j].find('"') != -1:
                            break;
                records[key] = value;
                j = j + 1;
            packages[name] = records;

    def _getUrl(self):
        if self.__pkgName not in self.__dists[self.__rc.distname] \
           or self.__ballTarget not in self.__dists[self.__rc.distname][self.__pkgName]:
            self._printErr(self._noPackage());
            install = 0;
            for d in self.DIST_NAMES:
                if self.__pkgName in self.__dists[d] \
                   and self.__ballTarget in self.__dists[d][self.__pkgName]:
                    install = self.__dists[d][self.__pkgName][self.__ballTarget];
                    print("warning: using [{0}]\n".format(d), file=sys.stderr);
                    break;
            if not install:
                raise PackageException("{0} is not in {1}".format(
                    self.__pkgName,
                    self.__setupIniPath
                ));
        else:
            install = self.__dists[self.__rc.distname][self.__pkgName][self.__ballTarget];
        filename, size, md5 = install.split();
        return filename, md5;

    def url(self):
        """print tarball url"""
        if not self.__pkgName:
            raise CommandLineException("url command requires a package name");
        print("{0}/{1}".format(self.__rc.mirror, self._getUrl()[0]));

    def getBall(self):
        url, md5 = self._getUrl();
        return os.path.join(self.__downloadDir, url);

    def ball(self):
        """print tarball name"""
        print(self.getBall());

    def _doDownload(self):
        url, md5 = self._getUrl();
        directory = os.path.join(self.__downloadDir, os.path.split(url)[0]);
        if not os.path.exists(self.getBall()) or not self._checkMd5():
            if not os.path.exists(directory):
                os.makedirs(directory);
            status = cautils.uri_get(
                directory,
                "{0}/{1}".format(self.__rc.mirror, url)
            );
            if status:
                raise PackageCacheException(
                    "didn't find {0} "
                    "on mirror {1}: possible mismatch between setup.ini and "
                    "mirror requiring {2} update?".format(
                    self.__pkgName,
                    self.__rc.mirror,
                    self.__appName
                ));

    def download(self):
        """download package (only, do not install)"""
        self._doDownload();
        self.ball();
        self.checksum();

    def _noPackage(self):
        return "{0} is not on mirror {1} in [{2}].".format(
            self.__pkgName,
            self.__rc.mirror,
            self.__rc.distname
        );

    # return an array contents all dependencies of self.__pkgName
    def getRequires(self):
        # Looking for dependencies on curr not prev or test
        dist = self.__dists['curr'];
        if self.__pkgName not in self.__dists[self.__rc.distname]:
            raise PackageException(self._noPackage());
        if self.__noDeps:
            return [];
        reqs = {self.__pkgName:0};
        if self.__ballTarget == 'source' \
            and 'external-source' in dist[self.__pkgName]:
            reqs[dist[self.__pkgName]['external-source']] = 0;
        n = 0;
        while len(reqs) > n:
            n = len(reqs);
            for i in list(reqs.keys()):
                if i not in dist:
                    sys.stderr.write("error: {0} not in [{1}]\n".format(
                        i, self.__rc.distname
                    ));
                    if i != self.__pkgName:
                        del reqs[i];
                    continue;
                reqs[i] = '0';
                p = dist[i];
                if 'requires' not in p:
                    continue;
                update_list = [(x, 0) for x in p['requires'].split()];
                reqs.update(update_list);
        # Delete the ask package it is not require by it self (joke)
        reqs.pop(self.__pkgName);
        rlist = sorted(reqs.keys());
        return rlist;

    def requires(self):
        """print requires: for package"""
        reqs = self.getRequires();
        if len(reqs) == 0:
            print("No dependencies for package {0}".format(self.__pkgName));
        else:
            print("\n".join(reqs));

    def getInstalled(self):
        if self.__installed:
            return self.__installed;
        self.__installed = {0:{}};
        f = open(self.__installedDbFile);
        lines = f.readlines();
        f.close();
        for i in lines[1:]:
            name, ball, status = i.split();
            self.__installed[int(status)][name] = ball;
        return self.__installed;

    def setInstalled(self, installed):
        self.__installed = installed;

    def _writeInstalled(self):
        file_db = open(self.__installedDbFile, 'w');
        file_db.write(self.INSTALLED_DB_MAGIC);
        lines = [];
        for x in list(self.__installed[0].keys()):
            lines.append("{0} {1} 0\n".format(x, self.__installed[0][x]));
        file_db.writelines(lines);
        if file_db.close():
            raise IOError(self.__installedDbFile);

    def getField(self, field, default=''):
        for d in (self.__rc.distname,) + self.DIST_NAMES:
            if self.__pkgName in self.__dists[d] \
               and field in self.__dists[d][self.__pkgName]:
                return self.__dists[d][self.__pkgName][field];
        return default;

    def _psort(self, lst):
        lst.sort();
        return lst;

    def _preverse(self, lst):
        lst.reverse();
        return lst;

    def list(self):
        """list installed packages"""
        print("--- Installed packages ---");
        for self.__pkgName in self._psort(list(self.__installed[0].keys())):
            ins = self.getInstalledVersion();
            new = 0;
            if self.__pkgName in self.__dists[self.__rc.distname] \
               and self.__ballTarget in self.__dists[self.__rc.distname][self.__pkgName]:
                new = self.getVersion();
            s = "{0:<19} {1:<15}".format(
                self.__pkgName,
                self._versionToString(ins)
            );
            if new and new != ins:
                s += "({0})".format(self._versionToString(new));
            print(s);

    def filelist(self):
        """list files installed by given packages"""
        if not self.__pkgName:
            msg = "no package name given.";
            raise CommandLineException(msg);
        else:
            print("\n".join(self.getFileList()));

    def _postInstall(self):
        self._runAll(self.CYG_POSTINSTALL_DIR);

    def _postRemove(self):
        if len(self.__files[1:]) == 0:
            msg = "must specify package to run postremove.";
            raise CommandLineException(msg);
        else:
            for self.__pkgName in self.__files[1:]:
                preremove_sh = os.path.join(
                    self.CYG_PREREMOVE_DIR,
                    "{0}.sh".format(self.__pkgName)
                );
                postremove_sh = os.path.join(
                    self.CYG_POSTREMOVE_DIR,
                    "{0}.sh".format(self.__pkgName)
                );
                self._runScript(preremove_sh);
                self._runScript(postremove_sh);

    def getVersion(self):
        if self.__pkgName not in self.__dists[self.__rc.distname] \
           or self.__ballTarget not in self.__dists[self.__rc.distname][self.__pkgName]:
            self._printErr(self._noPackage());
            return (0, 0);
        package = self.__dists[self.__rc.distname][self.__pkgName];
        if 'ver' not in package:
            filename = package[self.__ballTarget].split()[0];
            ball = os.path.split(filename)[1];
            package['ver'] = self._splitBall(ball)[1];
        return package['ver'];

    def getInstalledVersion(self):
        return self._splitBall(self.__installed[0][self.__pkgName])[1];

    def version(self):
        """print installed version"""
        if self.__pkgName:
            if self.__pkgName not in self.__installed[0]:
                msg = "{0} is not installed".format(self.__pkgName);
                raise PackageException(msg);
            print(self._versionToString(self.getInstalledVersion()));
        else:
            for self.__pkgName in self._psort(list(self.__installed[0].keys())):
                if self.__pkgName not in self.__installed[0]:
                    self.__rc.distname = 'installed';
                    raise PackageException(self._noPackage());
                print("{0:<20}{1:<12}".format(
                    self.__pkgName,
                    self._versionToString(self.getInstalledVersion())
                ));

    def getNew(self):
        lst = [];
        for self.__pkgName in list(self.__installed[0].keys()):
            new = self.getVersion();
            ins = self.getInstalledVersion();
            if new > ins:
                self._debug(" {0} > {1}".format(new, ins));
                lst.append(self.__pkgName);
        return lst;

    def new(self):
        """list new (upgradable) packages in distribution"""
        for self.__pkgName in self._psort(self.getNew()):
            print("{0:<20}{1:<12}".format(
                self.__pkgName,
                self._versionToString(self.getVersion())
            ));

    def getMd5(self):
        url, md5 = self._getUrl();
        f = open(os.path.join(self.__downloadDir, url), 'rb');
        data = f.read();
        f.close();
        m = hashlib.md5();
        if 64 == len(md5) :
            m = hashlib.sha256();
        if 128 == len(md5) :
            m = hashlib.sha512();
        m.update(data);
        digest = m.hexdigest();
        return digest;

    def _checkMd5(self):
        return self._getUrl()[1] == self.getMd5();

    def checksum(self):
        """check digest of cached package against database"""
        if not os.path.exists(self.getBall()):
            msg = "{0} not downloaded.".format(self.__pkgName);
            raise PackageCacheException(msg);
        url, md5 = self._getUrl();
        ball = os.path.basename(url);
        print("{0}  {1}".format(md5, ball));
        actual_md5 = self.getMd5();
        print("{0}  {1}".format(actual_md5, ball));
        if actual_md5 != md5:
            raise HashException(
                "digest of cached package doesn't match digest "
                "in setup.ini from mirror"
            );

    def search(self):
        """search all package descriptions for string"""
        if not self.__pkgName:
            raise CommandLineException(
                "search command requires a string to search for"
            );
        if not self.__regexSearch:
            regexp = re.escape(self.__pkgName);
        else:
            regexp = self.__pkgName;
        packages = [];
        keys = [];
        if self.__rc.distname in self.__dists:
            keys = list(self.__dists[self.__rc.distname].keys());
        else:
            for i in list(self.__dists.keys()):
                for j in list(self.__dists[i].keys()):
                    if not j in keys:
                        keys.append(j);
        for i in keys:
            self.__pkgName = i;
            if not regexp or re.search(regexp, i) \
               or re.search(regexp, self.getField('sdesc'), re.IGNORECASE) \
               or re.search(regexp, self.getField('ldesc'), re.IGNORECASE):
                if self.__rc.distname in self.__dists:
                    if self.__ballTarget in self.__dists[self.__rc.distname][i]:
                        packages.append(i);
                else:
                    packages.append(i);
        for self.__pkgName in self._psort(packages):
            s = self.__pkgName;
            d = self.getField('sdesc');
            if d:
                s += " - {0}".format(d[1:-1]);
            print(s);

    def show(self):
        """print package description"""
        s = self.__pkgName;
        d = self.getField('sdesc');
        if d:
            s += ' - {0}'.format(d[1:-1]);
        ldesc = self.getField('ldesc');
        if ldesc != "":
            print(s + "\n");
            print(ldesc);
        else:
            print("{0}: not found in setup.ini: {1}".format(
                self.__appName,
                s
            ));

    # return an array with all packages that must to be install
    def getMissing(self):
        reqs = self.getRequires();
        missingreqs = [];  # List of missing package on requires list
        for i in reqs:
            if i not in self.__installed[0]:
                missingreqs.append(i);
        if self.__pkgName not in self.__installed[0]:
            missingreqs.append(self.__pkgName);
        if missingreqs and self.__pkgName not in missingreqs:
            sys.stderr.write("warning: missing packages: {0}\n".format(
                " ".join(missingreqs)
            ));
        elif self.__pkgName in self.__installed[0]:  # Check version
            ins = self.getInstalledVersion();
            new = self.getVersion();
            if ins >= new:
                sys.stderr.write("{0} is already the newest version\n".format(
                    self.__pkgName
                ));
                # missingreqs.remove(self.__pkgName)
            elif self.__pkgName not in missingreqs:
                missingreqs.append(self.__pkgName);
        return missingreqs;

    def missing(self):
        """print missing dependencies for package"""
        missing = self.getMissing();
        if len(missing) > 0:
            print("\n".join(missing));
        else:
            print("All dependent packages for {0} installed".format(
                self.__pkgName
            ));

    def _runScript(self, file_name, optional=True):
        mapped_file = self.__pm.mapPath(file_name);
        mapped_file_done = mapped_file + ".done";
        if os.path.isfile(mapped_file):
            sys.stderr.write("running: {0}\n".format(file_name));
            cmd = ["bash"] + self.SH_OPTIONS + [mapped_file];
            if not self.__cygwinPlatform:
                cmd[0] = self.__dosBash;

            cwd = None;
            extension = os.path.splitext(mapped_file)[1];

            if ".dash" == extension :
                cmd = ["dash"] + self.DASH_OPTIONS + [mapped_file];
                if not self.__cygwinPlatform:
                    cmd[0] = self.__dosDash;

            if extension in [".bat", ".cmd"] :
                cmd = ["cmd"] + self.CMD_OPTIONS + [os.path.basename(mapped_file)];
                cwd = os.path.dirname(mapped_file);

            retval = Process(cmd, cwd).run(True);

            if os.path.exists(mapped_file_done):
                os.remove(mapped_file_done);
            if retval == 0 and os.path.basename(file_name)[:3] not in ['0p_', 'zp_']:
                shutil.move(mapped_file, mapped_file_done);
        else:
            if not optional:
                sys.stderr.write("{0}: WARNING couldn't find {1}.\n".format(
                    self.__appName,
                    mapped_file
                ));

    def _runAll(self, dirname):
        dirname = self.__pm.mapPath(dirname);

        if os.path.isdir(dirname):
            lst = list();
            for filename in os.listdir(dirname) :
                if os.path.splitext(filename)[1] in ['.sh', '.dash', '.bat', '.cmd'] :
                    lst.append(filename);

            perpetualScripts = list();
            regularScripts = list();
            for filename in lst:
                if filename[:3] in ['0p_', 'zp_'] :
                    perpetualScripts.append(filename);
                else:
                    regularScripts.append(filename);

            perpetualScripts.sort();
            lst = perpetualScripts + regularScripts;

            for i in lst:
                self._runScript("{0}/{1}".format(dirname, i));

    def _doInstallExternal(self, ball):
        # Currently we use a temporary directory and extractall() then copy:
        # this is very slow. The Python documentation warns more sophisticated
        # approaches have pitfalls without specifying what they are.
        tf = cautils.open_tarfile(ball, self.__dosXz);
        members = tf.getmembers();
        tempdir = tempfile.mkdtemp();
        try:
            tf.extractall(tempdir);
            for m in members:
                if m.isdir():
                    path = self.__pm.mapPath("/" + m.name);
                    if not os.path.exists(path):
                        os.makedirs(path, m.mode);
            for m in members:
                if m.isdir():
                    path = self.__pm.mapPath("/" + m.name);
                    if not os.path.exists(path):
                        os.makedirs(path, m.mode);
                else:
                    path = self.__pm.mapPath("/" + m.name);
                    dirname = os.path.dirname(path);
                    if not os.path.exists(dirname):
                        os.makedirs(dirname);
                    if os.path.exists(path):
                        os.chmod(path, 0o777);
                        os.remove(path);
                    # Windows extract() is robust but can't form Cygwin links
                    # (It produces copies instead: bulky and bugbait.)
                    # Convert to links if possible -- depends on coreutils being installed
                    if m.issym() and self.__lnExists:
                        link_target = m.linkname;
                        Process([
                            self.__dosLn,
                            "-s",
                            link_target,
                            path
                        ]).run(True);
                    elif m.islnk() and self.__lnExists:
                        # Hard link -- expect these to be very rare
                        link_target = m.linkname;
                        mapped_target = self.__pm.mapPath("/" + m.linkname);
                        # Must ensure target exists before forming hard link
                        if not os.path.exists(mapped_target):
                            shutil.move(
                                os.path.join(tempdir, link_target),
                                mapped_target
                            );
                        Process([
                            self.__dosLn,
                            mapped_target,
                            path
                        ]).run(True);
                    else:
                        shutil.move(os.path.join(tempdir, m.name), path);
        finally:
            tf.close();
            cautils.rmtree(tempdir);

    def _doInstall(self):
        ball = self.getBall();
        if cautils.is_tarfile(ball):
            if not self.__cygwinPlatform:
                self._doInstallExternal(ball);
            tf = cautils.open_tarfile(ball, self.__dosXz);
            if self.__cygwinPlatform:
                tf.extractall(self.__absRoot);
            # Force slash to the end of each directories
            members = tf.getmembers();
            tf.close();
            lst = [];
            for m in members:
                if m.isdir() and not m.name.endswith("/"):
                    lst.append(m.name + "/");
                else:
                    lst.append(m.name);
        else:
            print("{0}: bad tarball {1}. Install failed.".format(
                self.__appName,
                ball
            ), file=sys.stderr);
            return;
        self._writeFileList(lst);

        status = 1;
        if not self.__pkgName in self._integrityControl():
            status = 0;
        self.__installed[status][self.__pkgName] = os.path.basename(ball);

        self._writeInstalled();

    def getFileList(self):
        filelist_file = os.path.join(
            self.__setupDir,
            "{0}.lst.gz".format(self.__pkgName)
        );
        if not os.path.exists(filelist_file):
            if self.__pkgName not in self.__installed[0]:
                raise PackageException(
                    "{0} is not installed".format(self.__pkgName)
                );
            else:
                raise PackageException(
                    "{0} is installed, but {1} is missing".format(
                    self.__pkgName,
                    filelist_file
                ));
        gzf = gzip.GzipFile(filelist_file);
        lst = gzf.readlines();
        gzf.close();
        lst = [x.decode().strip() for x in lst];
        return lst;

    def _touch(self, fname, times=None):
        f = open(fname, 'a');
        os.utime(fname, times);
        f.close();

    def _writeFileList(self, lst):
        gz_filename = os.path.join(
            self.__setupDir,
            "{0}.lst.gz".format(self.__pkgName)
        );

        lst_cr = [x + "\n" for x in lst];

        # create iostring and write in gzip
        lst_io = io.BytesIO();
        lst_io_gz = gzip.GzipFile(fileobj=lst_io, mode='w');
        lst_io_gz.writelines([x.encode() for x in lst_cr]);
        lst_io_gz.close();

        # save it in the file
        lst_gz = open(gz_filename, 'wb');
        lst_gz.write(lst_io.getvalue());
        lst_gz.close();
        lst_io.close();

        stat_struct = os.stat(self.__setupIniPath);
        atime = stat_struct[7];
        mtime = stat_struct[8];
        self._touch(gz_filename, (atime, mtime));

    def _removeFileList(self):
        lst_name = os.path.join(
            self.__setupDir,
            "{0}.lst.gz".format(self.__pkgName)
        );
        if os.path.exists(lst_name):
            os.remove(lst_name);
        else:
            sys.stderr.write("{0}: warning {1} no such file\n".format(
                 sys.argv[0], lst_name
            ));

    def _uninstallWantFileRemoved(self, filename, noremoves, nowarns):
        # Returns true if the path from the tarball should result in a file 
        # removal operation, false if not.
        if not os.path.exists(filename) and not os.path.islink(filename):
            if filename not in nowarns:
                sys.stderr.write("warning: {0} no such file\n".format(
                    filename
                ));
            return False;
        elif not os.path.isdir(filename) and filename not in noremoves:
            return True;

    def _doUninstall(self):
        postremove_sh = "{0}/{1}.sh".format(
            self.CYG_POSTREMOVE_DIR,
            self.__pkgName
        );
        postinstall_sh = "{0}/{1}.sh".format(
            self.CYG_POSTINSTALL_DIR,
            self.__pkgName
        );
        preremove_sh = "{0}/{1}.sh".format(
            self.CYG_PREREMOVE_DIR,
            self.__pkgName
        );

        postinstall_done = "{0}.done".format(postinstall_sh);
        suppression_msg = (
            "{0}: postremove suppressed: "
            "\"{0} postremove {1}\" to complete.".format(
            self.__appName,
            self.__pkgName
        ));

        lst = self.getFileList();
        expect_preremove = preremove_sh[1:] in lst;
        expect_postremove = postremove_sh[1:] in lst;

        if not self.__noPostRemove:
            if expect_preremove:
                self._runScript(preremove_sh, optional=False);
        else:
            print("{0}".format(suppression_msg),
                  file=sys.stderr);

        # We don't expect these to be present: they are executed
        # and moved to $(packagename).sh.done
        nowarns = [];
        nowarns.append(self.__pm.mapPath(postinstall_sh));
        nowarns.append(self.__pm.mapPath(preremove_sh));

        noremoves = [];
        if self.__noPostRemove:
            noremoves.append(self.__pm.mapPath(preremove_sh));
        noremoves.append(self.__pm.mapPath(postremove_sh));

        # remove files
        for i in lst:
            filename = self.__pm.mapPath("/" + i);
            if os.path.islink(filename):
                os.remove(filename);
                continue;
            if os.path.isdir(filename):
                continue;
            if (self._uninstallWantFileRemoved(filename, noremoves, nowarns)):
                if os.path.exists(filename):
                    if self.__cygwinPlatform:
                        os.chmod(filename, 0o777);
                    if os.remove(filename):
                        raise IOError(filename);
                else:
                    if os.path.islink(filename):
                        os.remove(filename);
                    else:
                        print(
                            "{0}: warning: expected to remove {1} but it was"
                            " not there.".format(
                            self.__appName,
                            filename
                        ));
        if not self.__noPostRemove:
            if expect_postremove:
                self._runScript(postremove_sh, optional=False);
            postremove_sh_mapped = self.__pm.mapPath(postremove_sh)
            if os.path.isfile(postremove_sh_mapped):
                if os.remove(postremove_sh_mapped):
                    raise IOError(postremove_sh_mapped);

        # We don't remove empty directories: the problem is are we sure no other
        # package is depending on them.

        # setup.exe removes the filelist when a package is uninstalled: we try to be
        # as much like setup.exe as possible
        self._removeFileList();

        # Clean up the postintall script: it's been moved to .done
        if os.path.isfile(self.__pm.mapPath(postinstall_done)):
            os.remove(self.__pm.mapPath(postinstall_done));

        # update installed[]
        del(self.__installed[0][self.__pkgName]);
        self._writeInstalled();

    def remove(self):
        """uninstall packages"""
        barred = [];
        for self.__pkgName in self.__files[1:]:
            if self.__pkgName not in self.__installed[0]:
                sys.stderr.write("warning: {0} not installed\n".format(
                    self.__pkgName
                ));
                continue;
            if self._isBarredPackage(self.__pkgName):
                barred.append(self.__pkgName);
                continue;
            sys.stderr.write("uninstalling {0} {1}\n".format(
                self.__pkgName,
                self._versionToString(self.getInstalledVersion())
            ));
            self._doUninstall();
        self._barredWarnIfNeed(barred, "removing");

    def purge(self):
        """uninstall packages and delete from cache"""
        barred = [];

        for self.__pkgName in self.__files[1:]:
            if self.__pkgName in self.__installed[0]:
                if self._isBarredPackage(self.__pkgName):
                    barred.append(self.__pkgName);
                    continue;
                sys.stderr.write("uninstalling {0} {1}\n".format(
                    self.__pkgName,
                    self._versionToString(self.getInstalledVersion())
                ));
                self._doUninstall();
            scripts = [
                self.CYG_POSTINSTALL_DIR + "/{0}.sh.done",
                self.CYG_PREREMOVE_DIR + "/{0}.sh.done",
                self.CYG_POSTREMOVE_DIR + "/{0}.sh.done"
            ];
            scripts = [s.format(self.__pkgName) for s in scripts];
            scripts = [self.__pm.mapPath(s) for s in scripts];
            for s in scripts:
                if os.path.exists(s):
                    os.remove(s);
            ball = self.getBall();
            if os.path.exists(ball):
                print("{0}: removing {1}".format(self.__appName, ball));
                os.remove(ball);

        self._barredWarnIfNeed(barred, "purging");

    def _barredWarnIfNeed(self, barred, command):
        num_barred = len(barred);
        if num_barred > 0:
            if num_barred == 1:
                this_these = "this package";
            else:
                this_these = "these packages";
            barredstr = ", ".join(barred);
            helpfull = "";
            close_all_cygwin_programs = "";
            if command == "installing":
                helpfull += (
"You can force the installation with the option -f. But it is recommended\n"
"to upgrade the Cygwin distribution, with the official Setup program\n"
"(e.g., setup.exe)."
                );
                if "_autorebase" in barred:
                    close_all_cygwin_programs += (
"\n"
"Before that, you must close all Cygwin programs to perform rebasing\n"
"(e.g., rebaseall)."
                    );
            print(
                "BarredWarning: NOT {1}:"
                "    {2}\n{0} is dependent on {3} under Cygwin."
                "{4}{5}".format(
                    self.__appName,
                    command,
                    barredstr,
                    this_these,
                    helpfull,
                    close_all_cygwin_programs
            ), file=sys.stderr);
            if not self.__cygwinPlatform:
                print(
                    "Use -f to override but proceed with caution.",
                    file=sys.stderr
                );
    def install(self):
        """download and install packages with dependencies"""
        suppression_msg = (
            "{0}: postinstall suppressed: "
            "\"{0} postinstall\" to complete.".format(
            self.__appName
        ));
        missing = {};
        barred = [];
        for self.__pkgName in self.__files[1:]:
            missing.update(dict([(x, 0) for x in self.getMissing()]));

        if len(missing) > 1:
            sys.stderr.write("to install: \n");
            sys.stderr.write("    {0}".format(" ".join(list(missing.keys()))));
            sys.stderr.write("\n");

        for self.__pkgName in list(missing.keys()):
            if not self._getUrl():
                del missing[self.__pkgName];

        for self.__pkgName in list(missing.keys()):
            if self._isBarredPackage(self.__pkgName):
                barred.append(self.__pkgName);
                del missing[self.__pkgName];

        for self.__pkgName in list(missing.keys()):
            self.download();
        if self.__downloadOnly:
            return;
        for self.__pkgName in list(missing.keys()):
            if self.__pkgName in self.__installed[0]:
                sys.stderr.write("preparing to replace {0} {1}\n".format(
                    self.__pkgName,
                    self._versionToString(self.getInstalledVersion())
                ));
                self._doUninstall();
            sys.stderr.write("installing {0} {1}\n".format(
                self.__pkgName,
                self._versionToString(self.getVersion())
            ));
            self._doInstall();

        if self.__noPostInstall:
            print(suppression_msg, file=sys.stderr);
        else:
            self._postInstall();

        self._barredWarnIfNeed(barred, "installing");

    def postinstall(self):
        """Executes all undone postinstall scripts."""
        self._postInstall();

    def postremove(self):
        """Executes all undone preremove and postremove scripts."""
        self._postRemove();

    def _integrityControl(self, checklist=None):
        if None is checklist :
            checklist = list();

        options = ["-c"];
        if self.__verbose:
            options.append("-v");

        if len(checklist) == 0:
            checklist.append(self.__pkgName);

        command = ["/bin/cygcheck"] + options + checklist;
        if not self.__cygwinPlatform:
            command = subprocess.list2cmdline(command);
            command = [self.__dosBash] + self.SH_OPTIONS + ["-c"] + [command];

        p = Process(command);
        p.run();
        outputlines = p.getOutput().splitlines(True);

        unformat = "";
        start = False;
        incomplete = [];
        for res in outputlines:
            try:
                res_split = res.split();
                package, version, status = res_split;
            except ValueError:
                if len(res_split) > 0:
                    unformat += res;
                continue;

            if package == 'Package' \
               and version == 'Version' \
               and status == 'Status':
                start = True;
                unformat = '';
            elif not start:
                continue;

            if start and status == 'Incomplete':
                print(res[:-2]);
                print(unformat);
                unformat = "";
                incomplete.append(package);

        return incomplete;

    def upgrade(self):
        """all installed packages"""
        self.__files[1:] = self.getNew();
        self.install();

    def _printErr(self, err):
        print("{0}: {1}".format(self.__appName, err), file=sys.stderr);

    def _doUnpack(self):
        ball = self.getBall();
        basename = os.path.basename(ball);
        self.__pkgName = re.sub(r"(-src)*\.tar\.(bz2|gz)", '', basename);
        if os.path.exists(self.__pkgName):
            self._printErr("{0} already exists. Not overwriting.".format(
                self.__pkgName
            ));
            return 1;
        os.mkdir(self.__pkgName);
        if cautils.is_tarfile(ball):
            tf = cautils.open_tarfile(ball, self.__dosXz);
            tf.extractall(self.__pkgName);
            tf.close();
        else:
            raise InvalidFileException(
                "Bad source tarball {0}, \n"
                "SOURCE UNPACK FAILED".format(ball)
            );
        if not os.path.exists(self.__pkgName):
            raise IOError(self.__pkgName);
        print(self.__pkgName);

    def source(self):
        """download source package"""
        self.__ballTarget = 'source';
        for self.__pkgName in self.__files[1:]:
            self.download();
            self._doUnpack();

    def find(self):
        """find package containing file"""
        if self.__regexSearch:
            file_to_find = self.__pkgName;
        else:
            file_to_find = re.escape(self.__pkgName);
        hits = [];
        for self.__pkgName in self._psort(list(self.__installed[0].keys())):
            filenames_file = os.path.join(
                self.__setupDir,
                "{0}.lst.gz".format(self.__pkgName)
            );
            if not os.path.exists(filenames_file):
                continue;
            files = self.getFileList();
            for i in files:
                if re.search(file_to_find, "/{0}".format(i)):
                    hits.append("{0}: /{1}".format(self.__pkgName, i));
        print("\n".join(hits));

    def setRoot(self, root):
        if len(root) < 1 or root[-1] != "/":
            raise InvalidArgumentException("ROOT must end in a slash.");
        self.__prefixRoot = root[:-1];
        self.__absRoot = root;

    def getRessource(self, filename):
        self.__rc = cautils.parse_rc(filename);

        if not self.__rc.cache:
            msg = "{0} doesn't define cache.".format(self.__rcFile);
            raise UnexpectedValueException(msg);
        if not self.__rc.mirror:
            msg = "{0} doesn't define mirror.".format(self.__rcFile);
            raise UnexpectedValueException(msg);

        # We want ROOT + "/etc/setup" and cd(ROOT) to work:
        # necessitates two different forms, prefix and absolute
        if(self.__cygwinPlatform):
            self.setRoot("/");
        else:
            self.setRoot(self.__rc.ROOT);
        self.__rc.ROOT = None;
        self.__pm = PathMapper(self.__prefixRoot, self.__cygwinPlatform);
        self.__setupDir = self.__pm.mapPath(self.__setupDir);
        self.__rc.cache = self.__pm.mapPath(self.__rc.cache);
        self.__downloadDir = os.path.join(
            self.__rc.cache,
            urllib.quote(self.__rc.mirror+('' if self.__rc.mirror.endswith('/') else '/'), '').lower()
        );
        self.__installedDbFile = os.path.join(self.__setupDir, "installed.db");

        self.__setupIniPath = os.path.join(
            self.__downloadDir,
            self.__arch,
            "setup.ini",
        );
        self.__dosBash = "{0}bin/bash".format(self.__pm.getMountRoot());
        self.__dosLn = "{0}bin/ln".format(self.__pm.getMountRoot());
        self.__dosXz = self.__pm.mapPath("/usr/bin/xz");
        self.__dosDash = "{0}bin/dash".format(self.__pm.getMountRoot());
        return 0;

    def _isBarredPackage(self, package):
        barred = [];
        # add user barred package
        barred.extend(self.__rc.barred.split());
        # add force barred package
        barred.extend(self.FORCE_BARRED);

        # store current package name
        curr_pkgname = self.__pkgName;

        # get barred package requires
        depbarred = [];
        for self.__pkgName in barred:
            try:
                depbarred.extend(self.getRequires());
            except PackageException:
                pass;

        barred.extend(depbarred);

        # set current package name
        self.__pkgName = curr_pkgname;

        return (not self.__noBarred) and package in barred;