blob: 3594cb1c9194a0545acbc7c354c372141ae08c0f [file] [log] [blame]
# Copyright 2018 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
from portage import os
from portage.repository.storage.interface import (
RepoStorageException,
RepoStorageInterface,
)
from portage.util.futures import asyncio
from portage.util.futures.compat_coroutine import (
coroutine,
coroutine_return,
)
from _emerge.SpawnProcess import SpawnProcess
class HardlinkQuarantineRepoStorage(RepoStorageInterface):
"""
This is the default storage module, since its quite compatible with
most configurations.
It's desirable to be able to create shared hardlinks between the
download directory and 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.
"""
def __init__(self, repo, spawn_kwargs):
self._user_location = repo.location
self._update_location = None
self._spawn_kwargs = spawn_kwargs
self._current_update = None
@coroutine
def _check_call(self, cmd):
"""
Run cmd and raise RepoStorageException on failure.
@param cmd: command to executre
@type cmd: list
"""
p = SpawnProcess(args=cmd, scheduler=asyncio._wrap_loop(), **self._spawn_kwargs)
p.start()
if (yield p.async_wait()) != os.EX_OK:
raise RepoStorageException('command exited with status {}: {}'.\
format(p.returncode, ' '.join(cmd)))
@coroutine
def init_update(self):
update_location = os.path.join(self._user_location, '.tmp-unverified-download-quarantine')
yield self._check_call(['rm', '-rf', update_location])
# Use rsync --link-dest to hardlink a files into self._update_location,
# since cp -l is not portable.
yield self._check_call(['rsync', '-a', '--link-dest', self._user_location,
'--exclude=/distfiles', '--exclude=/local', '--exclude=/lost+found', '--exclude=/packages',
'--exclude', '/{}'.format(os.path.basename(update_location)),
self._user_location + '/', update_location + '/'])
self._update_location = update_location
coroutine_return(self._update_location)
@property
def current_update(self):
if self._update_location is None:
raise RepoStorageException('current update does not exist')
return self._update_location
@coroutine
def commit_update(self):
update_location = self.current_update
self._update_location = None
yield self._check_call(['rsync', '-a', '--delete',
'--exclude=/distfiles', '--exclude=/local', '--exclude=/lost+found', '--exclude=/packages',
'--exclude', '/{}'.format(os.path.basename(update_location)),
update_location + '/', self._user_location + '/'])
yield self._check_call(['rm', '-rf', update_location])
@coroutine
def abort_update(self):
if self._update_location is not None:
update_location = self._update_location
self._update_location = None
yield self._check_call(['rm', '-rf', update_location])
@coroutine
def garbage_collection(self):
yield self.abort_update()