aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/portage/sync/modules')
-rw-r--r--lib/portage/sync/modules/__init__.py0
-rw-r--r--lib/portage/sync/modules/cvs/__init__.py47
-rw-r--r--lib/portage/sync/modules/cvs/cvs.py67
-rw-r--r--lib/portage/sync/modules/git/__init__.py65
-rw-r--r--lib/portage/sync/modules/git/git.py286
-rw-r--r--lib/portage/sync/modules/rsync/__init__.py37
-rw-r--r--lib/portage/sync/modules/rsync/rsync.py782
-rw-r--r--lib/portage/sync/modules/svn/__init__.py33
-rw-r--r--lib/portage/sync/modules/svn/svn.py89
-rw-r--r--lib/portage/sync/modules/webrsync/__init__.py51
-rw-r--r--lib/portage/sync/modules/webrsync/webrsync.py70
11 files changed, 1527 insertions, 0 deletions
diff --git a/lib/portage/sync/modules/__init__.py b/lib/portage/sync/modules/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/lib/portage/sync/modules/__init__.py
diff --git a/lib/portage/sync/modules/cvs/__init__.py b/lib/portage/sync/modules/cvs/__init__.py
new file mode 100644
index 000000000..8025a2907
--- /dev/null
+++ b/lib/portage/sync/modules/cvs/__init__.py
@@ -0,0 +1,47 @@
+# Copyright 2014-2015 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+doc = """CVS plug-in module for portage.
+Performs a cvs up on repositories."""
+__doc__ = doc[:]
+
+from portage.localization import _
+from portage.sync.config_checks import CheckSyncConfig
+from portage.util import writemsg_level
+
+
+class CheckCVSConfig(CheckSyncConfig):
+
+ def __init__(self, repo, logger):
+ CheckSyncConfig.__init__(self, repo, logger)
+ self.checks.append('check_cvs_repo')
+
+
+ def check_cvs_repo(self):
+ if self.repo.module_specific_options.get('sync-cvs-repo') is None:
+ writemsg_level("!!! %s\n" %
+ _("Repository '%s' has sync-type=cvs, but is missing sync-cvs-repo attribute")
+ % self.repo.name, level=self.logger.ERROR, noiselevel=-1)
+
+
+module_spec = {
+ 'name': 'cvs',
+ 'description': doc,
+ 'provides':{
+ 'cvs-module': {
+ 'name': "cvs",
+ 'sourcefile': "cvs",
+ 'class': "CVSSync",
+ 'description': doc,
+ 'functions': ['sync', 'new', 'exists'],
+ 'func_desc': {
+ 'sync': 'Performs a cvs up on the repository',
+ 'new': 'Creates the new repository at the specified location',
+ 'exists': 'Returns a boolean of whether the specified dir ' +
+ 'exists and is a valid CVS repository',
+ },
+ 'validate_config': CheckCVSConfig,
+ 'module_specific_options': ("sync-cvs-repo",),
+ }
+ }
+}
diff --git a/lib/portage/sync/modules/cvs/cvs.py b/lib/portage/sync/modules/cvs/cvs.py
new file mode 100644
index 000000000..e202560c3
--- /dev/null
+++ b/lib/portage/sync/modules/cvs/cvs.py
@@ -0,0 +1,67 @@
+# Copyright 1999-2014 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import logging
+
+import portage
+from portage import os
+from portage.util import writemsg_level
+from portage.sync.syncbase import NewBase
+
+
+class CVSSync(NewBase):
+ '''CVS sync module'''
+
+ short_desc = "Perform sync operations on CVS repositories"
+
+ @staticmethod
+ def name():
+ return "CVSSync"
+
+
+ def __init__(self):
+ NewBase.__init__(self, "cvs", portage.const.CVS_PACKAGE_ATOM)
+
+
+ def exists(self, **kwargs):
+ '''Tests whether the repo is checked out'''
+ return os.path.exists(os.path.join(self.repo.location, 'CVS'))
+
+
+ def new(self, **kwargs):
+ if kwargs:
+ self._kwargs(kwargs)
+ #initial checkout
+ cvs_root = self.repo.sync_uri
+ if portage.process.spawn_bash(
+ "cd %s; exec cvs -z0 -d %s co -P -d %s %s" %
+ (portage._shell_quote(os.path.dirname(self.repo.location)), portage._shell_quote(cvs_root),
+ portage._shell_quote(os.path.basename(self.repo.location)),
+ portage._shell_quote(self.repo.module_specific_options["sync-cvs-repo"])),
+ **self.spawn_kwargs) != os.EX_OK:
+ msg = "!!! cvs checkout error; exiting."
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", noiselevel=-1, level=logging.ERROR)
+ return (1, False)
+ return (0, False)
+
+
+ def update(self):
+ """
+ Internal function to update an existing CVS repository
+
+ @return: tuple of return code (0=success), whether the cache
+ needs to be updated
+ @rtype: (int, bool)
+ """
+
+ #cvs update
+ exitcode = portage.process.spawn_bash(
+ "cd %s; exec cvs -z0 -q update -dP" % \
+ (portage._shell_quote(self.repo.location),),
+ **self.spawn_kwargs)
+ if exitcode != os.EX_OK:
+ msg = "!!! cvs update error; exiting."
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", noiselevel=-1, level=logging.ERROR)
+ return (exitcode, False)
diff --git a/lib/portage/sync/modules/git/__init__.py b/lib/portage/sync/modules/git/__init__.py
new file mode 100644
index 000000000..270d97186
--- /dev/null
+++ b/lib/portage/sync/modules/git/__init__.py
@@ -0,0 +1,65 @@
+# Copyright 2014-2018 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+doc = """Git plug-in module for portage.
+Performs a git pull on repositories."""
+__doc__ = doc[:]
+
+from portage.localization import _
+from portage.sync.config_checks import CheckSyncConfig
+from portage.util import writemsg_level
+
+
+class CheckGitConfig(CheckSyncConfig):
+ def __init__(self, repo, logger):
+ CheckSyncConfig.__init__(self, repo, logger)
+ self.checks.append('check_depth')
+
+ def check_depth(self):
+ for attr in ('clone_depth', 'sync_depth'):
+ self._check_depth(attr)
+
+ def _check_depth(self, attr):
+ d = getattr(self.repo, attr)
+
+ if d is not None:
+ try:
+ d = int(d)
+ except ValueError:
+ writemsg_level("!!! %s\n" %
+ _("%s value is not a number: '%s'")
+ % (attr.replace('_', '-'), d),
+ level=self.logger.ERROR, noiselevel=-1)
+ else:
+ setattr(self.repo, attr, d)
+
+
+module_spec = {
+ 'name': 'git',
+ 'description': doc,
+ 'provides':{
+ 'git-module': {
+ 'name': "git",
+ 'sourcefile': "git",
+ 'class': "GitSync",
+ 'description': doc,
+ 'functions': ['sync', 'new', 'exists', 'retrieve_head'],
+ 'func_desc': {
+ 'sync': 'Performs a git pull on the repository',
+ 'new': 'Creates the new repository at the specified location',
+ 'exists': 'Returns a boolean of whether the specified dir ' +
+ 'exists and is a valid Git repository',
+ 'retrieve_head': 'Returns the head commit hash',
+ },
+ 'validate_config': CheckGitConfig,
+ 'module_specific_options': (
+ 'sync-git-clone-env',
+ 'sync-git-clone-extra-opts',
+ 'sync-git-env',
+ 'sync-git-pull-env',
+ 'sync-git-pull-extra-opts',
+ 'sync-git-verify-commit-signature',
+ ),
+ }
+ }
+}
diff --git a/lib/portage/sync/modules/git/git.py b/lib/portage/sync/modules/git/git.py
new file mode 100644
index 000000000..2fb82c600
--- /dev/null
+++ b/lib/portage/sync/modules/git/git.py
@@ -0,0 +1,286 @@
+# Copyright 2005-2018 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import io
+import logging
+import subprocess
+
+import portage
+from portage import os
+from portage.util import writemsg_level, shlex_split
+from portage.util.futures import asyncio
+from portage.output import create_color_func, EOutput
+good = create_color_func("GOOD")
+bad = create_color_func("BAD")
+warn = create_color_func("WARN")
+from portage.sync.syncbase import NewBase
+
+try:
+ from gemato.exceptions import GematoException
+ import gemato.openpgp
+except ImportError:
+ gemato = None
+
+
+class GitSync(NewBase):
+ '''Git sync class'''
+
+ short_desc = "Perform sync operations on git based repositories"
+
+ @staticmethod
+ def name():
+ return "GitSync"
+
+
+ def __init__(self):
+ NewBase.__init__(self, "git", portage.const.GIT_PACKAGE_ATOM)
+
+
+ def exists(self, **kwargs):
+ '''Tests whether the repo actually exists'''
+ return os.path.exists(os.path.join(self.repo.location, '.git'))
+
+
+ def new(self, **kwargs):
+ '''Do the initial clone of the repository'''
+ if kwargs:
+ self._kwargs(kwargs)
+ if not self.has_bin:
+ return (1, False)
+ try:
+ if not os.path.exists(self.repo.location):
+ os.makedirs(self.repo.location)
+ self.logger(self.xterm_titles,
+ 'Created new directory %s' % self.repo.location)
+ except IOError:
+ return (1, False)
+
+ sync_uri = self.repo.sync_uri
+ if sync_uri.startswith("file://"):
+ sync_uri = sync_uri[7:]
+
+ git_cmd_opts = ""
+ if self.repo.module_specific_options.get('sync-git-env'):
+ shlexed_env = shlex_split(self.repo.module_specific_options['sync-git-env'])
+ env = dict((k, v) for k, _, v in (assignment.partition('=') for assignment in shlexed_env) if k)
+ self.spawn_kwargs['env'].update(env)
+
+ if self.repo.module_specific_options.get('sync-git-clone-env'):
+ shlexed_env = shlex_split(self.repo.module_specific_options['sync-git-clone-env'])
+ clone_env = dict((k, v) for k, _, v in (assignment.partition('=') for assignment in shlexed_env) if k)
+ self.spawn_kwargs['env'].update(clone_env)
+
+ if self.settings.get("PORTAGE_QUIET") == "1":
+ git_cmd_opts += " --quiet"
+ if self.repo.clone_depth is not None:
+ if self.repo.clone_depth != 0:
+ git_cmd_opts += " --depth %d" % self.repo.clone_depth
+ elif self.repo.sync_depth is not None:
+ if self.repo.sync_depth != 0:
+ git_cmd_opts += " --depth %d" % self.repo.sync_depth
+ else:
+ # default
+ git_cmd_opts += " --depth 1"
+
+ if self.repo.module_specific_options.get('sync-git-clone-extra-opts'):
+ git_cmd_opts += " %s" % self.repo.module_specific_options['sync-git-clone-extra-opts']
+ git_cmd = "%s clone%s %s ." % (self.bin_command, git_cmd_opts,
+ portage._shell_quote(sync_uri))
+ writemsg_level(git_cmd + "\n")
+
+ exitcode = portage.process.spawn_bash("cd %s ; exec %s" % (
+ portage._shell_quote(self.repo.location), git_cmd),
+ **self.spawn_kwargs)
+ if exitcode != os.EX_OK:
+ msg = "!!! git clone error in %s" % self.repo.location
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1)
+ return (exitcode, False)
+ if not self.verify_head():
+ return (1, False)
+ return (os.EX_OK, True)
+
+
+ def update(self):
+ ''' Update existing git repository, and ignore the syncuri. We are
+ going to trust the user and assume that the user is in the branch
+ that he/she wants updated. We'll let the user manage branches with
+ git directly.
+ '''
+ if not self.has_bin:
+ return (1, False)
+ git_cmd_opts = ""
+ quiet = self.settings.get("PORTAGE_QUIET") == "1"
+ if self.repo.module_specific_options.get('sync-git-env'):
+ shlexed_env = shlex_split(self.repo.module_specific_options['sync-git-env'])
+ env = dict((k, v) for k, _, v in (assignment.partition('=') for assignment in shlexed_env) if k)
+ self.spawn_kwargs['env'].update(env)
+
+ if self.repo.module_specific_options.get('sync-git-pull-env'):
+ shlexed_env = shlex_split(self.repo.module_specific_options['sync-git-pull-env'])
+ pull_env = dict((k, v) for k, _, v in (assignment.partition('=') for assignment in shlexed_env) if k)
+ self.spawn_kwargs['env'].update(pull_env)
+
+ if self.settings.get("PORTAGE_QUIET") == "1":
+ git_cmd_opts += " --quiet"
+ if self.repo.module_specific_options.get('sync-git-pull-extra-opts'):
+ git_cmd_opts += " %s" % self.repo.module_specific_options['sync-git-pull-extra-opts']
+
+ try:
+ remote_branch = portage._unicode_decode(
+ subprocess.check_output([self.bin_command, 'rev-parse',
+ '--abbrev-ref', '--symbolic-full-name', '@{upstream}'],
+ cwd=portage._unicode_encode(self.repo.location))).rstrip('\n')
+ except subprocess.CalledProcessError as e:
+ msg = "!!! git rev-parse error in %s" % self.repo.location
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1)
+ return (e.returncode, False)
+
+ shallow = self.repo.sync_depth is not None and self.repo.sync_depth != 0
+ if shallow:
+ git_cmd_opts += " --depth %d" % self.repo.sync_depth
+
+ # For shallow fetch, unreachable objects may need to be pruned
+ # manually, in order to prevent automatic git gc calls from
+ # eventually failing (see bug 599008).
+ gc_cmd = ['git', '-c', 'gc.autodetach=false', 'gc', '--auto']
+ if quiet:
+ gc_cmd.append('--quiet')
+ exitcode = subprocess.call(gc_cmd,
+ cwd=portage._unicode_encode(self.repo.location))
+ if exitcode != os.EX_OK:
+ msg = "!!! git gc error in %s" % self.repo.location
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1)
+ return (exitcode, False)
+
+ git_cmd = "%s fetch %s%s" % (self.bin_command,
+ remote_branch.partition('/')[0], git_cmd_opts)
+
+ writemsg_level(git_cmd + "\n")
+
+ rev_cmd = [self.bin_command, "rev-list", "--max-count=1", "HEAD"]
+ previous_rev = subprocess.check_output(rev_cmd,
+ cwd=portage._unicode_encode(self.repo.location))
+
+ exitcode = portage.process.spawn_bash("cd %s ; exec %s" % (
+ portage._shell_quote(self.repo.location), git_cmd),
+ **self.spawn_kwargs)
+
+ if exitcode != os.EX_OK:
+ msg = "!!! git fetch error in %s" % self.repo.location
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1)
+ return (exitcode, False)
+
+ if not self.verify_head(revision='refs/remotes/%s' % remote_branch):
+ return (1, False)
+
+ if shallow:
+ # Since the default merge strategy typically fails when
+ # the depth is not unlimited, `git reset --merge`.
+ merge_cmd = [self.bin_command, 'reset', '--merge']
+ else:
+ merge_cmd = [self.bin_command, 'merge']
+ merge_cmd.append('refs/remotes/%s' % remote_branch)
+ if quiet:
+ merge_cmd.append('--quiet')
+ exitcode = subprocess.call(merge_cmd,
+ cwd=portage._unicode_encode(self.repo.location))
+
+ if exitcode != os.EX_OK:
+ msg = "!!! git merge error in %s" % self.repo.location
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1)
+ return (exitcode, False)
+
+ current_rev = subprocess.check_output(rev_cmd,
+ cwd=portage._unicode_encode(self.repo.location))
+
+ return (os.EX_OK, current_rev != previous_rev)
+
+ def verify_head(self, revision='-1'):
+ if (self.repo.module_specific_options.get(
+ 'sync-git-verify-commit-signature', 'false') != 'true'):
+ return True
+
+ if self.repo.sync_openpgp_key_path is not None:
+ if gemato is None:
+ writemsg_level("!!! Verifying against specified key requires gemato-11.0+ installed\n",
+ level=logging.ERROR, noiselevel=-1)
+ return False
+ openpgp_env = gemato.openpgp.OpenPGPEnvironment()
+ else:
+ openpgp_env = None
+
+ try:
+ out = EOutput()
+ env = None
+ if openpgp_env is not None:
+ try:
+ out.einfo('Using keys from %s' % (self.repo.sync_openpgp_key_path,))
+ with io.open(self.repo.sync_openpgp_key_path, 'rb') as f:
+ openpgp_env.import_key(f)
+ self._refresh_keys(openpgp_env)
+ except (GematoException, asyncio.TimeoutError) as e:
+ writemsg_level("!!! Verification impossible due to keyring problem:\n%s\n"
+ % (e,),
+ level=logging.ERROR, noiselevel=-1)
+ return (1, False)
+
+ env = os.environ.copy()
+ env['GNUPGHOME'] = openpgp_env.home
+
+ rev_cmd = [self.bin_command, "log", "-n1", "--pretty=format:%G?", revision]
+ try:
+ status = (portage._unicode_decode(
+ subprocess.check_output(rev_cmd,
+ cwd=portage._unicode_encode(self.repo.location),
+ env=env))
+ .strip())
+ except subprocess.CalledProcessError:
+ return False
+
+ if status == 'G': # good signature is good
+ out.einfo('Trusted signature found on top commit')
+ return True
+ elif status == 'U': # untrusted
+ out.ewarn('Top commit signature is valid but not trusted')
+ return True
+ else:
+ if status == 'B':
+ expl = 'bad signature'
+ elif status == 'X':
+ expl = 'expired signature'
+ elif status == 'Y':
+ expl = 'expired key'
+ elif status == 'R':
+ expl = 'revoked key'
+ elif status == 'E':
+ expl = 'unable to verify signature (missing key?)'
+ elif status == 'N':
+ expl = 'no signature'
+ else:
+ expl = 'unknown issue'
+ out.eerror('No valid signature found: %s' % (expl,))
+ return False
+ finally:
+ if openpgp_env is not None:
+ openpgp_env.close()
+
+ def retrieve_head(self, **kwargs):
+ '''Get information about the head commit'''
+ if kwargs:
+ self._kwargs(kwargs)
+ if self.bin_command is None:
+ # return quietly so that we don't pollute emerge --info output
+ return (1, False)
+ rev_cmd = [self.bin_command, "rev-list", "--max-count=1", "HEAD"]
+ try:
+ ret = (os.EX_OK,
+ portage._unicode_decode(subprocess.check_output(rev_cmd,
+ cwd=portage._unicode_encode(self.repo.location))))
+ except subprocess.CalledProcessError:
+ ret = (1, False)
+ return ret
diff --git a/lib/portage/sync/modules/rsync/__init__.py b/lib/portage/sync/modules/rsync/__init__.py
new file mode 100644
index 000000000..cb80f6d66
--- /dev/null
+++ b/lib/portage/sync/modules/rsync/__init__.py
@@ -0,0 +1,37 @@
+# Copyright 2014-2018 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+doc = """Rsync plug-in module for portage.
+ Performs rsync transfers on repositories."""
+__doc__ = doc[:]
+
+from portage.sync.config_checks import CheckSyncConfig
+
+
+module_spec = {
+ 'name': 'rsync',
+ 'description': doc,
+ 'provides':{
+ 'rsync-module': {
+ 'name': "rsync",
+ 'sourcefile': "rsync",
+ 'class': "RsyncSync",
+ 'description': doc,
+ 'functions': ['sync', 'new', 'exists', 'retrieve_head'],
+ 'func_desc': {
+ 'sync': 'Performs rsync transfers on the repository',
+ 'new': 'Creates the new repository at the specified location',
+ 'exists': 'Returns a boolean if the specified directory exists',
+ 'retrieve_head': 'Returns the head commit based on metadata/timestamp.commit',
+ },
+ 'validate_config': CheckSyncConfig,
+ 'module_specific_options': (
+ 'sync-rsync-extra-opts',
+ 'sync-rsync-vcs-ignore',
+ 'sync-rsync-verify-jobs',
+ 'sync-rsync-verify-max-age',
+ 'sync-rsync-verify-metamanifest',
+ ),
+ }
+ }
+ }
diff --git a/lib/portage/sync/modules/rsync/rsync.py b/lib/portage/sync/modules/rsync/rsync.py
new file mode 100644
index 000000000..fb1960a3c
--- /dev/null
+++ b/lib/portage/sync/modules/rsync/rsync.py
@@ -0,0 +1,782 @@
+# Copyright 1999-2018 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import sys
+import logging
+import time
+import signal
+import socket
+import datetime
+import io
+import re
+import random
+import subprocess
+import tempfile
+
+import portage
+from portage import os
+from portage import _unicode_decode
+from portage.exception import CommandNotFound
+from portage.util import writemsg_level
+from portage.output import create_color_func, yellow, blue, bold
+good = create_color_func("GOOD")
+bad = create_color_func("BAD")
+warn = create_color_func("WARN")
+from portage.const import VCS_DIRS, TIMESTAMP_FORMAT, RSYNC_PACKAGE_ATOM
+from portage.util import writemsg, writemsg_stdout
+from portage.util.futures import asyncio
+from portage.sync.getaddrinfo_validate import getaddrinfo_validate
+from _emerge.UserQuery import UserQuery
+from portage.sync.syncbase import NewBase
+
+try:
+ from gemato.exceptions import GematoException
+ import gemato.openpgp
+ import gemato.recursiveloader
+except ImportError:
+ gemato = None
+
+if sys.hexversion >= 0x3000000:
+ # pylint: disable=W0622
+ _unicode = str
+else:
+ _unicode = unicode
+
+SERVER_OUT_OF_DATE = -1
+EXCEEDED_MAX_RETRIES = -2
+
+
+class RsyncSync(NewBase):
+ '''Rsync sync module'''
+
+ short_desc = "Perform sync operations on rsync based repositories"
+
+ @staticmethod
+ def name():
+ return "RsyncSync"
+
+
+ def __init__(self):
+ NewBase.__init__(self, "rsync", RSYNC_PACKAGE_ATOM)
+
+ def _select_download_dir(self):
+ '''
+ Select and return the download directory. It's desirable to be able
+ to create shared hardlinks between the download directory to the
+ normal repository, and this is facilitated by making the download
+ directory be a subdirectory of the normal repository location
+ (ensuring that no mountpoints are crossed). Shared hardlinks are
+ created by using the rsync --link-dest option.
+
+ Since the download is initially unverified, it is safest to save
+ it in a quarantine directory. The quarantine directory is also
+ useful for making the repository update more atomic, so that it
+ less likely that normal repository location will be observed in
+ a partially synced state.
+
+ This method returns a quarantine directory if sync-allow-hardlinks
+ is enabled in repos.conf, and otherwise it returne the normal
+ repository location.
+ '''
+ if self.repo.sync_allow_hardlinks:
+ return os.path.join(self.repo.location, '.tmp-unverified-download-quarantine')
+ else:
+ return self.repo.location
+
+ def _commit_download(self, download_dir):
+ '''
+ Commit changes from download_dir if it does not refer to the
+ normal repository location.
+ '''
+ exitcode = 0
+ if self.repo.location != download_dir:
+ rsynccommand = [self.bin_command] + self.rsync_opts + self.extra_rsync_opts
+ rsynccommand.append('--exclude=/%s' % os.path.basename(download_dir))
+ rsynccommand.append('%s/' % download_dir.rstrip('/'))
+ rsynccommand.append('%s/' % self.repo.location)
+ exitcode = subprocess.call(rsynccommand)
+
+ return exitcode
+
+ def _remove_download(self, download_dir):
+ """
+ Remove download_dir if it does not refer to the normal repository
+ location.
+ """
+ exitcode = 0
+ if self.repo.location != download_dir:
+ exitcode = subprocess.call(['rm', '-rf', download_dir])
+ return exitcode
+
+ def update(self):
+ '''Internal update function which performs the transfer'''
+ opts = self.options.get('emerge_config').opts
+ self.usersync_uid = self.options.get('usersync_uid', None)
+ enter_invalid = '--ask-enter-invalid' in opts
+ quiet = '--quiet' in opts
+ out = portage.output.EOutput(quiet=quiet)
+ syncuri = self.repo.sync_uri
+ if self.repo.module_specific_options.get(
+ 'sync-rsync-vcs-ignore', 'false').lower() == 'true':
+ vcs_dirs = ()
+ else:
+ vcs_dirs = frozenset(VCS_DIRS)
+ vcs_dirs = vcs_dirs.intersection(os.listdir(self.repo.location))
+
+ for vcs_dir in vcs_dirs:
+ writemsg_level(("!!! %s appears to be under revision " + \
+ "control (contains %s).\n!!! Aborting rsync sync "
+ "(override with \"sync-rsync-vcs-ignore = true\" in repos.conf).\n") % \
+ (self.repo.location, vcs_dir), level=logging.ERROR, noiselevel=-1)
+ return (1, False)
+ self.timeout=180
+
+ rsync_opts = []
+ if self.settings["PORTAGE_RSYNC_OPTS"] == "":
+ rsync_opts = self._set_rsync_defaults()
+ else:
+ rsync_opts = self._validate_rsync_opts(rsync_opts, syncuri)
+ self.rsync_opts = self._rsync_opts_extend(opts, rsync_opts)
+
+ self.extra_rsync_opts = list()
+ if self.repo.module_specific_options.get('sync-rsync-extra-opts'):
+ self.extra_rsync_opts.extend(portage.util.shlex_split(
+ self.repo.module_specific_options['sync-rsync-extra-opts']))
+
+ download_dir = self._select_download_dir()
+ exitcode = 0
+
+ # Process GLEP74 verification options.
+ # Default verification to 'no'; it's enabled for ::gentoo
+ # via default repos.conf though.
+ self.verify_metamanifest = (
+ self.repo.module_specific_options.get(
+ 'sync-rsync-verify-metamanifest', 'no') in ('yes', 'true'))
+ # Support overriding job count.
+ self.verify_jobs = self.repo.module_specific_options.get(
+ 'sync-rsync-verify-jobs', None)
+ if self.verify_jobs is not None:
+ try:
+ self.verify_jobs = int(self.verify_jobs)
+ if self.verify_jobs < 0:
+ raise ValueError(self.verify_jobs)
+ except ValueError:
+ writemsg_level("!!! sync-rsync-verify-jobs not a positive integer: %s\n" % (self.verify_jobs,),
+ level=logging.WARNING, noiselevel=-1)
+ self.verify_jobs = None
+ else:
+ if self.verify_jobs == 0:
+ # Use the apparent number of processors if gemato
+ # supports it.
+ self.verify_jobs = None
+ # Support overriding max age.
+ self.max_age = self.repo.module_specific_options.get(
+ 'sync-rsync-verify-max-age', '')
+ if self.max_age:
+ try:
+ self.max_age = int(self.max_age)
+ if self.max_age < 0:
+ raise ValueError(self.max_age)
+ except ValueError:
+ writemsg_level("!!! sync-rsync-max-age must be a non-negative integer: %s\n" % (self.max_age,),
+ level=logging.WARNING, noiselevel=-1)
+ self.max_age = 0
+ else:
+ self.max_age = 0
+
+ openpgp_env = None
+ if self.verify_metamanifest and gemato is not None:
+ # Use isolated environment if key is specified,
+ # system environment otherwise
+ if self.repo.sync_openpgp_key_path is not None:
+ openpgp_env = gemato.openpgp.OpenPGPEnvironment()
+ else:
+ openpgp_env = gemato.openpgp.OpenPGPSystemEnvironment()
+
+ try:
+ # Load and update the keyring early. If it fails, then verification
+ # will not be performed and the user will have to fix it and try again,
+ # so we may as well bail out before actual rsync happens.
+ if openpgp_env is not None and self.repo.sync_openpgp_key_path is not None:
+ try:
+ out.einfo('Using keys from %s' % (self.repo.sync_openpgp_key_path,))
+ with io.open(self.repo.sync_openpgp_key_path, 'rb') as f:
+ openpgp_env.import_key(f)
+ self._refresh_keys(openpgp_env)
+ except (GematoException, asyncio.TimeoutError) as e:
+ writemsg_level("!!! Manifest verification impossible due to keyring problem:\n%s\n"
+ % (e,),
+ level=logging.ERROR, noiselevel=-1)
+ return (1, False)
+
+ # Real local timestamp file.
+ self.servertimestampfile = os.path.join(
+ self.repo.location, "metadata", "timestamp.chk")
+
+ content = portage.util.grabfile(self.servertimestampfile)
+ timestamp = 0
+ if content:
+ try:
+ timestamp = time.mktime(time.strptime(content[0],
+ TIMESTAMP_FORMAT))
+ except (OverflowError, ValueError):
+ pass
+ del content
+
+ try:
+ self.rsync_initial_timeout = \
+ int(self.settings.get("PORTAGE_RSYNC_INITIAL_TIMEOUT", "15"))
+ except ValueError:
+ self.rsync_initial_timeout = 15
+
+ try:
+ maxretries=int(self.settings["PORTAGE_RSYNC_RETRIES"])
+ except SystemExit as e:
+ raise # Needed else can't exit
+ except:
+ maxretries = -1 #default number of retries
+
+ if syncuri.startswith("file://"):
+ self.proto = "file"
+ dosyncuri = syncuri[7:]
+ unchanged, is_synced, exitcode, updatecache_flg = self._do_rsync(
+ dosyncuri, timestamp, opts, download_dir)
+ self._process_exitcode(exitcode, dosyncuri, out, 1)
+ if exitcode == 0 and not unchanged:
+ self._commit_download(download_dir)
+ return (exitcode, updatecache_flg)
+
+ retries=0
+ try:
+ self.proto, user_name, hostname, port = re.split(
+ r"(rsync|ssh)://([^:/]+@)?(\[[:\da-fA-F]*\]|[^:/]*)(:[0-9]+)?",
+ syncuri, maxsplit=4)[1:5]
+ except ValueError:
+ writemsg_level("!!! sync-uri is invalid: %s\n" % syncuri,
+ noiselevel=-1, level=logging.ERROR)
+ return (1, False)
+
+ self.ssh_opts = self.settings.get("PORTAGE_SSH_OPTS")
+
+ if port is None:
+ port=""
+ if user_name is None:
+ user_name=""
+ if re.match(r"^\[[:\da-fA-F]*\]$", hostname) is None:
+ getaddrinfo_host = hostname
+ else:
+ # getaddrinfo needs the brackets stripped
+ getaddrinfo_host = hostname[1:-1]
+ updatecache_flg = False
+ all_rsync_opts = set(self.rsync_opts)
+ all_rsync_opts.update(self.extra_rsync_opts)
+
+ family = socket.AF_UNSPEC
+ if "-4" in all_rsync_opts or "--ipv4" in all_rsync_opts:
+ family = socket.AF_INET
+ elif socket.has_ipv6 and \
+ ("-6" in all_rsync_opts or "--ipv6" in all_rsync_opts):
+ family = socket.AF_INET6
+
+ addrinfos = None
+ uris = []
+
+ try:
+ addrinfos = getaddrinfo_validate(
+ socket.getaddrinfo(getaddrinfo_host, None,
+ family, socket.SOCK_STREAM))
+ except socket.error as e:
+ writemsg_level(
+ "!!! getaddrinfo failed for '%s': %s\n"
+ % (_unicode_decode(hostname), _unicode(e)),
+ noiselevel=-1, level=logging.ERROR)
+
+ if addrinfos:
+
+ AF_INET = socket.AF_INET
+ AF_INET6 = None
+ if socket.has_ipv6:
+ AF_INET6 = socket.AF_INET6
+
+ ips_v4 = []
+ ips_v6 = []
+
+ for addrinfo in addrinfos:
+ if addrinfo[0] == AF_INET:
+ ips_v4.append("%s" % addrinfo[4][0])
+ elif AF_INET6 is not None and addrinfo[0] == AF_INET6:
+ # IPv6 addresses need to be enclosed in square brackets
+ ips_v6.append("[%s]" % addrinfo[4][0])
+
+ random.shuffle(ips_v4)
+ random.shuffle(ips_v6)
+
+ # Give priority to the address family that
+ # getaddrinfo() returned first.
+ if AF_INET6 is not None and addrinfos and \
+ addrinfos[0][0] == AF_INET6:
+ ips = ips_v6 + ips_v4
+ else:
+ ips = ips_v4 + ips_v6
+
+ for ip in ips:
+ uris.append(syncuri.replace(
+ "//" + user_name + hostname + port + "/",
+ "//" + user_name + ip + port + "/", 1))
+
+ if not uris:
+ # With some configurations we need to use the plain hostname
+ # rather than try to resolve the ip addresses (bug #340817).
+ uris.append(syncuri)
+
+ # reverse, for use with pop()
+ uris.reverse()
+ uris_orig = uris[:]
+
+ effective_maxretries = maxretries
+ if effective_maxretries < 0:
+ effective_maxretries = len(uris) - 1
+
+ local_state_unchanged = True
+ while (1):
+ if uris:
+ dosyncuri = uris.pop()
+ elif maxretries < 0 or retries > maxretries:
+ writemsg("!!! Exhausted addresses for %s\n"
+ % _unicode_decode(hostname), noiselevel=-1)
+ return (1, False)
+ else:
+ uris.extend(uris_orig)
+ dosyncuri = uris.pop()
+
+ if (retries==0):
+ if "--ask" in opts:
+ uq = UserQuery(opts)
+ if uq.query("Do you want to sync your Portage tree " + \
+ "with the mirror at\n" + blue(dosyncuri) + bold("?"),
+ enter_invalid) == "No":
+ print()
+ print("Quitting.")
+ print()
+ sys.exit(128 + signal.SIGINT)
+ self.logger(self.xterm_titles,
+ ">>> Starting rsync with " + dosyncuri)
+ if "--quiet" not in opts:
+ print(">>> Starting rsync with "+dosyncuri+"...")
+ else:
+ self.logger(self.xterm_titles,
+ ">>> Starting retry %d of %d with %s" % \
+ (retries, effective_maxretries, dosyncuri))
+ writemsg_stdout(
+ "\n\n>>> Starting retry %d of %d with %s\n" % \
+ (retries, effective_maxretries, dosyncuri), noiselevel=-1)
+
+ if dosyncuri.startswith('ssh://'):
+ dosyncuri = dosyncuri[6:].replace('/', ':/', 1)
+
+ unchanged, is_synced, exitcode, updatecache_flg = self._do_rsync(
+ dosyncuri, timestamp, opts, download_dir)
+ if not unchanged:
+ local_state_unchanged = False
+ if is_synced:
+ break
+
+ retries=retries+1
+
+ if maxretries < 0 or retries <= maxretries:
+ print(">>> Retrying...")
+ else:
+ # over retries
+ # exit loop
+ exitcode = EXCEEDED_MAX_RETRIES
+ break
+ self._process_exitcode(exitcode, dosyncuri, out, maxretries)
+
+ if local_state_unchanged:
+ # The quarantine download_dir is not intended to exist
+ # in this case, so refer gemato to the normal repository
+ # location.
+ download_dir = self.repo.location
+
+ # if synced successfully, verify now
+ if exitcode == 0 and self.verify_metamanifest:
+ if gemato is None:
+ writemsg_level("!!! Unable to verify: gemato-11.0+ is required\n",
+ level=logging.ERROR, noiselevel=-1)
+ exitcode = 127
+ else:
+ try:
+ # we always verify the Manifest signature, in case
+ # we had to deal with key revocation case
+ m = gemato.recursiveloader.ManifestRecursiveLoader(
+ os.path.join(download_dir, 'Manifest'),
+ verify_openpgp=True,
+ openpgp_env=openpgp_env,
+ max_jobs=self.verify_jobs)
+ if not m.openpgp_signed:
+ raise RuntimeError('OpenPGP signature not found on Manifest')
+
+ ts = m.find_timestamp()
+ if ts is None:
+ raise RuntimeError('Timestamp not found in Manifest')
+ if (self.max_age != 0 and
+ (datetime.datetime.utcnow() - ts.ts).days > self.max_age):
+ out.quiet = False
+ out.ewarn('Manifest is over %d days old, this is suspicious!' % (self.max_age,))
+ out.ewarn('You may want to try using another mirror and/or reporting this one:')
+ out.ewarn(' %s' % (dosyncuri,))
+ out.ewarn('')
+ out.quiet = quiet
+
+ out.einfo('Manifest timestamp: %s UTC' % (ts.ts,))
+ out.einfo('Valid OpenPGP signature found:')
+ out.einfo('- primary key: %s' % (
+ m.openpgp_signature.primary_key_fingerprint))
+ out.einfo('- subkey: %s' % (
+ m.openpgp_signature.fingerprint))
+ out.einfo('- timestamp: %s UTC' % (
+ m.openpgp_signature.timestamp))
+
+ # if nothing has changed, skip the actual Manifest
+ # verification
+ if not local_state_unchanged:
+ out.ebegin('Verifying %s' % (download_dir,))
+ m.assert_directory_verifies()
+ out.eend(0)
+ except GematoException as e:
+ writemsg_level("!!! Manifest verification failed:\n%s\n"
+ % (e,),
+ level=logging.ERROR, noiselevel=-1)
+ exitcode = 1
+
+ if exitcode == 0 and not local_state_unchanged:
+ exitcode = self._commit_download(download_dir)
+
+ return (exitcode, updatecache_flg)
+ finally:
+ if exitcode == 0:
+ self._remove_download(download_dir)
+ if openpgp_env is not None:
+ openpgp_env.close()
+
+ def _process_exitcode(self, exitcode, syncuri, out, maxretries):
+ if (exitcode==0):
+ pass
+ elif exitcode == SERVER_OUT_OF_DATE:
+ exitcode = 1
+ elif exitcode == EXCEEDED_MAX_RETRIES:
+ sys.stderr.write(
+ ">>> Exceeded PORTAGE_RSYNC_RETRIES: %s\n" % maxretries)
+ exitcode = 1
+ elif (exitcode>0):
+ msg = []
+ if exitcode==1:
+ msg.append("Rsync has reported that there is a syntax error. Please ensure")
+ msg.append("that sync-uri attribute for repository '%s' is proper." % self.repo.name)
+ msg.append("sync-uri: '%s'" % self.repo.sync_uri)
+ elif exitcode==11:
+ msg.append("Rsync has reported that there is a File IO error. Normally")
+ msg.append("this means your disk is full, but can be caused by corruption")
+ msg.append("on the filesystem that contains repository '%s'. Please investigate" % self.repo.name)
+ msg.append("and try again after the problem has been fixed.")
+ msg.append("Location of repository: '%s'" % self.repo.location)
+ elif exitcode==20:
+ msg.append("Rsync was killed before it finished.")
+ else:
+ msg.append("Rsync has not successfully finished. It is recommended that you keep")
+ msg.append("trying or that you use the 'emerge-webrsync' option if you are unable")
+ msg.append("to use rsync due to firewall or other restrictions. This should be a")
+ msg.append("temporary problem unless complications exist with your network")
+ msg.append("(and possibly your system's filesystem) configuration.")
+ for line in msg:
+ out.eerror(line)
+
+
+ def new(self, **kwargs):
+ if kwargs:
+ self._kwargs(kwargs)
+ try:
+ if not os.path.exists(self.repo.location):
+ os.makedirs(self.repo.location)
+ self.logger(self.self.xterm_titles,
+ 'Created New Directory %s ' % self.repo.location )
+ except IOError:
+ return (1, False)
+ return self.update()
+
+ def retrieve_head(self, **kwargs):
+ '''Get information about the head commit'''
+ if kwargs:
+ self._kwargs(kwargs)
+ last_sync = portage.grabfile(os.path.join(self.repo.location, "metadata", "timestamp.commit"))
+ ret = (1, False)
+ if last_sync:
+ try:
+ ret = (os.EX_OK, last_sync[0].split()[0])
+ except IndexError:
+ pass
+ return ret
+
+ def _set_rsync_defaults(self):
+ portage.writemsg("PORTAGE_RSYNC_OPTS empty or unset, using hardcoded defaults\n")
+ rsync_opts = [
+ "--recursive", # Recurse directories
+ "--links", # Consider symlinks
+ "--safe-links", # Ignore links outside of tree
+ "--perms", # Preserve permissions
+ "--times", # Preserive mod times
+ "--omit-dir-times",
+ "--compress", # Compress the data transmitted
+ "--force", # Force deletion on non-empty dirs
+ "--whole-file", # Don't do block transfers, only entire files
+ "--delete", # Delete files that aren't in the master tree
+ "--stats", # Show final statistics about what was transfered
+ "--human-readable",
+ "--timeout="+str(self.timeout), # IO timeout if not done in X seconds
+ "--exclude=/distfiles", # Exclude distfiles from consideration
+ "--exclude=/local", # Exclude local from consideration
+ "--exclude=/packages", # Exclude packages from consideration
+ ]
+ return rsync_opts
+
+
+ def _validate_rsync_opts(self, rsync_opts, syncuri):
+ # The below validation is not needed when using the above hardcoded
+ # defaults.
+
+ portage.writemsg("Using PORTAGE_RSYNC_OPTS instead of hardcoded defaults\n", 1)
+ rsync_opts.extend(portage.util.shlex_split(
+ self.settings.get("PORTAGE_RSYNC_OPTS", "")))
+ for opt in ("--recursive", "--times"):
+ if opt not in rsync_opts:
+ portage.writemsg(yellow("WARNING:") + " adding required option " + \
+ "%s not included in PORTAGE_RSYNC_OPTS\n" % opt)
+ rsync_opts.append(opt)
+
+ for exclude in ("distfiles", "local", "packages"):
+ opt = "--exclude=/%s" % exclude
+ if opt not in rsync_opts:
+ portage.writemsg(yellow("WARNING:") + \
+ " adding required option %s not included in " % opt + \
+ "PORTAGE_RSYNC_OPTS (can be overridden with --exclude='!')\n")
+ rsync_opts.append(opt)
+
+ if syncuri.rstrip("/").endswith(".gentoo.org/gentoo-portage"):
+ def rsync_opt_startswith(opt_prefix):
+ for x in rsync_opts:
+ if x.startswith(opt_prefix):
+ return (1, False)
+ return (0, False)
+
+ if not rsync_opt_startswith("--timeout="):
+ rsync_opts.append("--timeout=%d" % self.timeout)
+
+ for opt in ("--compress", "--whole-file"):
+ if opt not in rsync_opts:
+ portage.writemsg(yellow("WARNING:") + " adding required option " + \
+ "%s not included in PORTAGE_RSYNC_OPTS\n" % opt)
+ rsync_opts.append(opt)
+ return rsync_opts
+
+
+ @staticmethod
+ def _rsync_opts_extend(opts, rsync_opts):
+ if "--quiet" in opts:
+ rsync_opts.append("--quiet") # Shut up a lot
+ else:
+ rsync_opts.append("--verbose") # Print filelist
+
+ if "--verbose" in opts:
+ rsync_opts.append("--progress") # Progress meter for each file
+
+ if "--debug" in opts:
+ rsync_opts.append("--checksum") # Force checksum on all files
+ return rsync_opts
+
+
+ def _do_rsync(self, syncuri, timestamp, opts, download_dir):
+ updatecache_flg = False
+ is_synced = False
+ if timestamp != 0 and "--quiet" not in opts:
+ print(">>> Checking server timestamp ...")
+
+ rsynccommand = [self.bin_command] + self.rsync_opts + self.extra_rsync_opts
+
+ if self.proto == 'ssh' and self.ssh_opts:
+ rsynccommand.append("--rsh=ssh " + self.ssh_opts)
+
+ if "--debug" in opts:
+ print(rsynccommand)
+
+ local_state_unchanged = False
+ exitcode = os.EX_OK
+ servertimestamp = 0
+ # Even if there's no timestamp available locally, fetch the
+ # timestamp anyway as an initial probe to verify that the server is
+ # responsive. This protects us from hanging indefinitely on a
+ # connection attempt to an unresponsive server which rsync's
+ # --timeout option does not prevent.
+
+ #if True:
+ # Temporary file for remote server timestamp comparison.
+ # NOTE: If FEATURES=usersync is enabled then the tempfile
+ # needs to be in a directory that's readable by the usersync
+ # user. We assume that PORTAGE_TMPDIR will satisfy this
+ # requirement, since that's not necessarily true for the
+ # default directory used by the tempfile module.
+ if self.usersync_uid is not None:
+ tmpdir = self.settings['PORTAGE_TMPDIR']
+ else:
+ # use default dir from tempfile module
+ tmpdir = None
+ fd, tmpservertimestampfile = \
+ tempfile.mkstemp(dir=tmpdir)
+ os.close(fd)
+ if self.usersync_uid is not None:
+ portage.util.apply_permissions(tmpservertimestampfile,
+ uid=self.usersync_uid)
+ command = rsynccommand[:]
+ command.append(syncuri.rstrip("/") + \
+ "/metadata/timestamp.chk")
+ command.append(tmpservertimestampfile)
+ content = None
+ pids = []
+ try:
+ # Timeout here in case the server is unresponsive. The
+ # --timeout rsync option doesn't apply to the initial
+ # connection attempt.
+ try:
+ if self.rsync_initial_timeout:
+ portage.exception.AlarmSignal.register(
+ self.rsync_initial_timeout)
+
+ pids.extend(portage.process.spawn(
+ command, returnpid=True,
+ **self.spawn_kwargs))
+ exitcode = os.waitpid(pids[0], 0)[1]
+ if self.usersync_uid is not None:
+ portage.util.apply_permissions(tmpservertimestampfile,
+ uid=os.getuid())
+ content = portage.grabfile(tmpservertimestampfile)
+ finally:
+ if self.rsync_initial_timeout:
+ portage.exception.AlarmSignal.unregister()
+ try:
+ os.unlink(tmpservertimestampfile)
+ except OSError:
+ pass
+ except portage.exception.AlarmSignal:
+ # timed out
+ print('timed out')
+ # With waitpid and WNOHANG, only check the
+ # first element of the tuple since the second
+ # element may vary (bug #337465).
+ if pids and os.waitpid(pids[0], os.WNOHANG)[0] == 0:
+ os.kill(pids[0], signal.SIGTERM)
+ os.waitpid(pids[0], 0)
+ # This is the same code rsync uses for timeout.
+ exitcode = 30
+ else:
+ if exitcode != os.EX_OK:
+ if exitcode & 0xff:
+ exitcode = (exitcode & 0xff) << 8
+ else:
+ exitcode = exitcode >> 8
+
+ if content:
+ try:
+ servertimestamp = time.mktime(time.strptime(
+ content[0], TIMESTAMP_FORMAT))
+ except (OverflowError, ValueError):
+ pass
+ del command, pids, content
+
+ if exitcode == os.EX_OK:
+ if (servertimestamp != 0) and (servertimestamp == timestamp):
+ local_state_unchanged = True
+ is_synced = True
+ self.logger(self.xterm_titles,
+ ">>> Cancelling sync -- Already current.")
+ print()
+ print(">>>")
+ print(">>> Timestamps on the server and in the local repository are the same.")
+ print(">>> Cancelling all further sync action. You are already up to date.")
+ print(">>>")
+ print(">>> In order to force sync, remove '%s'." % self.servertimestampfile)
+ print(">>>")
+ print()
+ elif (servertimestamp != 0) and (servertimestamp < timestamp):
+ self.logger(self.xterm_titles,
+ ">>> Server out of date: %s" % syncuri)
+ print()
+ print(">>>")
+ print(">>> SERVER OUT OF DATE: %s" % syncuri)
+ print(">>>")
+ print(">>> In order to force sync, remove '%s'." % self.servertimestampfile)
+ print(">>>")
+ print()
+ exitcode = SERVER_OUT_OF_DATE
+ elif (servertimestamp == 0) or (servertimestamp > timestamp):
+ # actual sync
+ command = rsynccommand[:]
+
+ if self.repo.location != download_dir:
+ # Use shared hardlinks for files that are identical
+ # in the previous snapshot of the repository.
+ command.append('--link-dest=%s' % self.repo.location)
+
+ submodule_paths = self._get_submodule_paths()
+ if submodule_paths:
+ # The only way to select multiple directories to
+ # sync, without calling rsync multiple times, is
+ # to use --relative.
+ command.append("--relative")
+ for path in submodule_paths:
+ # /./ is special syntax supported with the
+ # rsync --relative option.
+ command.append(syncuri + "/./" + path)
+ else:
+ command.append(syncuri + "/")
+
+ command.append(download_dir)
+
+ exitcode = None
+ try:
+ exitcode = portage.process.spawn(command,
+ **self.spawn_kwargs)
+ finally:
+ if exitcode is None:
+ # interrupted
+ exitcode = 128 + signal.SIGINT
+
+ # 0 Success
+ # 1 Syntax or usage error
+ # 2 Protocol incompatibility
+ # 5 Error starting client-server protocol
+ # 35 Timeout waiting for daemon connection
+ if exitcode not in (0, 1, 2, 5, 35):
+ # If the exit code is not among those listed above,
+ # then we may have a partial/inconsistent sync
+ # state, so our previously read timestamp as well
+ # as the corresponding file can no longer be
+ # trusted.
+ timestamp = 0
+ try:
+ os.unlink(self.servertimestampfile)
+ except OSError:
+ pass
+ else:
+ updatecache_flg = True
+
+ if exitcode in [0,1,3,4,11,14,20,21]:
+ is_synced = True
+ elif exitcode in [1,3,4,11,14,20,21]:
+ is_synced = True
+ else:
+ # Code 2 indicates protocol incompatibility, which is expected
+ # for servers with protocol < 29 that don't support
+ # --prune-empty-directories. Retry for a server that supports
+ # at least rsync protocol version 29 (>=rsync-2.6.4).
+ pass
+
+ return local_state_unchanged, is_synced, exitcode, updatecache_flg
diff --git a/lib/portage/sync/modules/svn/__init__.py b/lib/portage/sync/modules/svn/__init__.py
new file mode 100644
index 000000000..c7ae3b87c
--- /dev/null
+++ b/lib/portage/sync/modules/svn/__init__.py
@@ -0,0 +1,33 @@
+# Copyright 2014 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+doc = """SVN plug-in module for portage.
+Performs a svn up on repositories."""
+__doc__ = doc[:]
+
+from portage.localization import _
+from portage.sync.config_checks import CheckSyncConfig
+from portage.util import writemsg_level
+
+
+module_spec = {
+ 'name': 'svn',
+ 'description': doc,
+ 'provides':{
+ 'svn-module': {
+ 'name': "svn",
+ 'sourcefile': "svn",
+ 'class': "SVNSync",
+ 'description': doc,
+ 'functions': ['sync', 'new', 'exists'],
+ 'func_desc': {
+ 'sync': 'Performs a svn up on the repository',
+ 'new': 'Creates the new repository at the specified location',
+ 'exists': 'Returns a boolean of whether the specified dir ' +
+ 'exists and is a valid SVN repository',
+ },
+ 'validate_config': CheckSyncConfig,
+ 'module_specific_options': (),
+ }
+ }
+}
diff --git a/lib/portage/sync/modules/svn/svn.py b/lib/portage/sync/modules/svn/svn.py
new file mode 100644
index 000000000..723beedcb
--- /dev/null
+++ b/lib/portage/sync/modules/svn/svn.py
@@ -0,0 +1,89 @@
+# Copyright 1999-2014 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import logging
+
+import portage
+from portage import os
+from portage.util import writemsg_level
+from portage.sync.syncbase import NewBase
+
+
+class SVNSync(NewBase):
+ '''SVN sync module'''
+
+ short_desc = "Perform sync operations on SVN repositories"
+
+ @staticmethod
+ def name():
+ return "SVNSync"
+
+
+ def __init__(self):
+ NewBase.__init__(self, "svn", "dev-vcs/subversion")
+
+
+ def exists(self, **kwargs):
+ '''Tests whether the repo actually exists'''
+ return os.path.exists(os.path.join(self.repo.location, '.svn'))
+
+
+ def new(self, **kwargs):
+ if kwargs:
+ self._kwargs(kwargs)
+ #initial checkout
+ svn_root = self.repo.sync_uri
+ exitcode = portage.process.spawn_bash(
+ "cd %s; exec svn co %s ." %
+ (portage._shell_quote(self.repo.location),
+ portage._shell_quote(svn_root)),
+ **self.spawn_kwargs)
+ if exitcode != os.EX_OK:
+ msg = "!!! svn checkout error; exiting."
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", noiselevel=-1, level=logging.ERROR)
+ return (exitcode, False)
+
+
+ def update(self):
+ """
+ Internal function to update an existing SVN repository
+
+ @return: tuple of return code (0=success), whether the cache
+ needs to be updated
+ @rtype: (int, bool)
+ """
+
+ exitcode = self._svn_upgrade()
+ if exitcode != os.EX_OK:
+ return (exitcode, False)
+
+ #svn update
+ exitcode = portage.process.spawn_bash(
+ "cd %s; exec svn update" % \
+ (portage._shell_quote(self.repo.location),),
+ **self.spawn_kwargs)
+ if exitcode != os.EX_OK:
+ msg = "!!! svn update error; exiting."
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", noiselevel=-1, level=logging.ERROR)
+ return (exitcode, False)
+
+
+ def _svn_upgrade(self):
+ """
+ Internal function which performs an svn upgrade on the repo
+
+ @return: tuple of return code (0=success), whether the cache
+ needs to be updated
+ @rtype: (int, bool)
+ """
+ exitcode = portage.process.spawn_bash(
+ "cd %s; exec svn upgrade" %
+ (portage._shell_quote(self.repo.location),),
+ **self.spawn_kwargs)
+ if exitcode != os.EX_OK:
+ msg = "!!! svn upgrade error; exiting."
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", noiselevel=-1, level=logging.ERROR)
+ return exitcode
diff --git a/lib/portage/sync/modules/webrsync/__init__.py b/lib/portage/sync/modules/webrsync/__init__.py
new file mode 100644
index 000000000..dc7def20c
--- /dev/null
+++ b/lib/portage/sync/modules/webrsync/__init__.py
@@ -0,0 +1,51 @@
+# Copyright 2014 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+doc = """WebRSync plug-in module for portage.
+Performs a http download of a portage snapshot, verifies and
+unpacks it to the repo location."""
+__doc__ = doc[:]
+
+
+import os
+
+from portage.sync.config_checks import CheckSyncConfig
+
+
+DEFAULT_CLASS = "WebRsync"
+AVAILABLE_CLASSES = [ "WebRsync", "PyWebsync"]
+options = {"1": "WebRsync", "2": "PyWebsync"}
+
+
+config_class = DEFAULT_CLASS
+try:
+ test_param = os.environ["TESTIT"]
+ if test_param in options:
+ config_class = options[test_param]
+except KeyError:
+ pass
+
+
+module_spec = {
+ 'name': 'webrsync',
+ 'description': doc,
+ 'provides':{
+ 'webrsync-module': {
+ 'name': "webrsync",
+ 'sourcefile': "webrsync",
+ 'class': config_class,
+ 'description': doc,
+ 'functions': ['sync', 'new', 'exists'],
+ 'func_desc': {
+ 'sync': 'Performs an archived http download of the ' +
+ 'repository, then unpacks it. Optionally it performs a ' +
+ 'gpg verification of the downloaded file(s)',
+ 'new': 'Creates the new repository at the specified location',
+ 'exists': 'Returns a boolean of whether the specified dir ' +
+ 'exists and is a valid repository',
+ },
+ 'validate_config': CheckSyncConfig,
+ 'module_specific_options': (),
+ },
+ }
+}
diff --git a/lib/portage/sync/modules/webrsync/webrsync.py b/lib/portage/sync/modules/webrsync/webrsync.py
new file mode 100644
index 000000000..3d79f4557
--- /dev/null
+++ b/lib/portage/sync/modules/webrsync/webrsync.py
@@ -0,0 +1,70 @@
+
+'''WebRsync module for portage'''
+
+import logging
+
+import portage
+from portage import os
+from portage.util import writemsg_level
+from portage.output import create_color_func
+good = create_color_func("GOOD")
+bad = create_color_func("BAD")
+warn = create_color_func("WARN")
+from portage.sync.syncbase import SyncBase
+
+
+class WebRsync(SyncBase):
+ '''WebRSync sync class'''
+
+ short_desc = "Perform sync operations on webrsync based repositories"
+
+ @staticmethod
+ def name():
+ return "WebRSync"
+
+
+ def __init__(self):
+ SyncBase.__init__(self, 'emerge-webrsync', '>=sys-apps/portage-2.3')
+
+
+ def sync(self, **kwargs):
+ '''Sync the repository'''
+ if kwargs:
+ self._kwargs(kwargs)
+
+ if not self.has_bin:
+ return (1, False)
+
+ # filter these out to prevent gpg errors
+ for var in ['uid', 'gid', 'groups']:
+ self.spawn_kwargs.pop(var, None)
+
+ exitcode = portage.process.spawn_bash("%s" % \
+ (self.bin_command),
+ **self.spawn_kwargs)
+ if exitcode != os.EX_OK:
+ msg = "!!! emerge-webrsync error in %s" % self.repo.location
+ self.logger(self.xterm_titles, msg)
+ writemsg_level(msg + "\n", level=logging.ERROR, noiselevel=-1)
+ return (exitcode, False)
+ return (exitcode, True)
+
+
+class PyWebRsync(SyncBase):
+ '''WebRSync sync class'''
+
+ short_desc = "Perform sync operations on webrsync based repositories"
+
+ @staticmethod
+ def name():
+ return "WebRSync"
+
+
+ def __init__(self):
+ SyncBase.__init__(self, None, '>=sys-apps/portage-2.3')
+
+
+ def sync(self, **kwargs):
+ '''Sync the repository'''
+ pass
+