locks: add configuration and command-line options to enable/disable locks (#7692)
- spack.util.lock behaves the same as llnl.util.lock, but Lock._lock and Lock._unlock do nothing. - can be disabled with a control variable. - configuration options can enable/disable locking: - `locks` option in spack configuration controls whether Spack will use filesystem locks or not. - `-l` and `-L` command-line options can force-disable or force-enable locking. - Spack will check for group- and world-writability before disabling locks, and it will not allow a group- or world-writable instance to have locks disabled. - update documentation
This commit is contained in:
parent
780cc9d72d
commit
54201e3c02
@ -18,13 +18,16 @@ config:
|
|||||||
# You can use $spack here to refer to the root of the spack instance.
|
# You can use $spack here to refer to the root of the spack instance.
|
||||||
install_tree: $spack/opt/spack
|
install_tree: $spack/opt/spack
|
||||||
|
|
||||||
|
|
||||||
# Locations where templates should be found
|
# Locations where templates should be found
|
||||||
template_dirs:
|
template_dirs:
|
||||||
- $spack/templates
|
- $spack/templates
|
||||||
|
|
||||||
|
|
||||||
# default directory layout
|
# default directory layout
|
||||||
directory_layout: "${ARCHITECTURE}/${COMPILERNAME}-${COMPILERVER}/${PACKAGE}-${VERSION}-${HASH}"
|
directory_layout: "${ARCHITECTURE}/${COMPILERNAME}-${COMPILERVER}/${PACKAGE}-${VERSION}-${HASH}"
|
||||||
|
|
||||||
|
|
||||||
# Locations where different types of modules should be installed.
|
# Locations where different types of modules should be installed.
|
||||||
module_roots:
|
module_roots:
|
||||||
tcl: $spack/share/spack/modules
|
tcl: $spack/share/spack/modules
|
||||||
@ -74,6 +77,15 @@ config:
|
|||||||
dirty: false
|
dirty: false
|
||||||
|
|
||||||
|
|
||||||
|
# When set to true, concurrent instances of Spack will use locks to
|
||||||
|
# avoid modifying the install tree, database file, etc. If false, Spack
|
||||||
|
# will disable all locking, but you must NOT run concurrent instances
|
||||||
|
# of Spack. For filesystems that don't support locking, you should set
|
||||||
|
# this to false and run one Spack at a time, but otherwise we recommend
|
||||||
|
# enabling locks.
|
||||||
|
locks: true
|
||||||
|
|
||||||
|
|
||||||
# The default number of jobs to use when running `make` in parallel.
|
# The default number of jobs to use when running `make` in parallel.
|
||||||
# If set to 4, for example, `spack install` will run `make -j4`.
|
# If set to 4, for example, `spack install` will run `make -j4`.
|
||||||
# If not set, all available cores are used by default.
|
# If not set, all available cores are used by default.
|
||||||
|
@ -1093,22 +1093,43 @@ several variants:
|
|||||||
Filesystem requirements
|
Filesystem requirements
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
Spack currently needs to be run from a filesystem that supports
|
By default, Spack needs to be run from a filesystem that supports
|
||||||
``flock`` locking semantics. Nearly all local filesystems and recent
|
``flock`` locking semantics. Nearly all local filesystems and recent
|
||||||
versions of NFS support this, but parallel filesystems may be mounted
|
versions of NFS support this, but parallel filesystems or NFS volumes may
|
||||||
without ``flock`` support enabled. You can determine how your
|
be configured without ``flock`` support enabled. You can determine how
|
||||||
filesystems are mounted with ``mount -p``. The output for a Lustre
|
your filesystems are mounted with ``mount``. The output for a Lustre
|
||||||
filesystem might look like this:
|
filesystem might look like this:
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
$ mount -l | grep lscratch
|
$ mount | grep lscratch
|
||||||
pilsner-mds1-lnet0@o2ib100:/lsd on /p/lscratchd type lustre (rw,nosuid,noauto,_netdev,lazystatfs,flock)
|
mds1-lnet0@o2ib100:/lsd on /p/lscratchd type lustre (rw,nosuid,lazystatfs,flock)
|
||||||
porter-mds1-lnet0@o2ib100:/lse on /p/lscratche type lustre (rw,nosuid,noauto,_netdev,lazystatfs,flock)
|
mds2-lnet0@o2ib100:/lse on /p/lscratche type lustre (rw,nosuid,lazystatfs,flock)
|
||||||
|
|
||||||
Note the ``flock`` option on both Lustre mounts. If you do not see
|
Note the ``flock`` option on both Lustre mounts.
|
||||||
this or a similar option for your filesystem, you may need ot ask your
|
|
||||||
system administrator to enable ``flock``.
|
If you do not see this or a similar option for your filesystem, you have
|
||||||
|
a few options. First, you can move your Spack installation to a
|
||||||
|
filesystem that supports locking. Second, you could ask your system
|
||||||
|
administrator to enable ``flock`` for your filesystem.
|
||||||
|
|
||||||
|
If none of those work, you can disable locking in one of two ways:
|
||||||
|
|
||||||
|
1. Run Spack with the ``-L`` or ``--disable-locks`` option to disable
|
||||||
|
locks on a call-by-call basis.
|
||||||
|
2. Edit :ref:`config.yaml <config-yaml>` and set the ``locks`` option
|
||||||
|
to ``false`` to always disable locking.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
If you disable locking, concurrent instances of Spack will have no way
|
||||||
|
to avoid stepping on each other. You must ensure that there is only
|
||||||
|
**one** instance of Spack running at a time. Otherwise, Spack may end
|
||||||
|
up with a corrupted database file, or you may not be able to see all
|
||||||
|
installed packages in commands like ``spack find``.
|
||||||
|
|
||||||
|
If you are unfortunate enough to run into this situation, you may be
|
||||||
|
able to fix it by running ``spack reindex``.
|
||||||
|
|
||||||
This issue typically manifests with the error below:
|
This issue typically manifests with the error below:
|
||||||
|
|
||||||
|
@ -150,6 +150,17 @@ checksum, and will refuse to build packages that it cannot verify. Set
|
|||||||
to ``false`` to disable these checks. Disabling this can expose you to
|
to ``false`` to disable these checks. Disabling this can expose you to
|
||||||
attacks. Use at your own risk.
|
attacks. Use at your own risk.
|
||||||
|
|
||||||
|
--------------------
|
||||||
|
``locks``
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
When set to ``true``, concurrent instances of Spack will use locks to
|
||||||
|
avoid modifying the install tree, database file, etc. If false, Spack
|
||||||
|
will disable all locking, but you must **not** run concurrent instances
|
||||||
|
of Spack. For filesystems that don't support locking, you should set
|
||||||
|
this to ``false`` and run one Spack at a time, but otherwise we recommend
|
||||||
|
enabling locks.
|
||||||
|
|
||||||
--------------------
|
--------------------
|
||||||
``dirty``
|
``dirty``
|
||||||
--------------------
|
--------------------
|
||||||
|
@ -195,10 +195,12 @@ def acquire_read(self, timeout=_default_timeout):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if self._reads == 0 and self._writes == 0:
|
if self._reads == 0 and self._writes == 0:
|
||||||
tty.debug('READ LOCK: {0.path}[{0._start}:{0._length}] [Acquiring]'
|
self._debug(
|
||||||
|
'READ LOCK: {0.path}[{0._start}:{0._length}] [Acquiring]'
|
||||||
.format(self))
|
.format(self))
|
||||||
self._lock(fcntl.LOCK_SH, timeout=timeout) # can raise LockError.
|
self._lock(fcntl.LOCK_SH, timeout=timeout) # can raise LockError.
|
||||||
tty.debug('READ LOCK: {0.path}[{0._start}:{0._length}] [Acquired]'
|
self._debug(
|
||||||
|
'READ LOCK: {0.path}[{0._start}:{0._length}] [Acquired]'
|
||||||
.format(self))
|
.format(self))
|
||||||
self._reads += 1
|
self._reads += 1
|
||||||
return True
|
return True
|
||||||
@ -218,11 +220,12 @@ def acquire_write(self, timeout=_default_timeout):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if self._writes == 0:
|
if self._writes == 0:
|
||||||
tty.debug(
|
self._debug(
|
||||||
'WRITE LOCK: {0.path}[{0._start}:{0._length}] [Acquiring]'
|
'WRITE LOCK: {0.path}[{0._start}:{0._length}] [Acquiring]'
|
||||||
.format(self))
|
.format(self))
|
||||||
self._lock(fcntl.LOCK_EX, timeout=timeout) # can raise LockError.
|
self._lock(fcntl.LOCK_EX, timeout=timeout) # can raise LockError.
|
||||||
tty.debug('WRITE LOCK: {0.path}[{0._start}:{0._length}] [Acquired]'
|
self._debug(
|
||||||
|
'WRITE LOCK: {0.path}[{0._start}:{0._length}] [Acquired]'
|
||||||
.format(self))
|
.format(self))
|
||||||
self._writes += 1
|
self._writes += 1
|
||||||
return True
|
return True
|
||||||
@ -243,7 +246,8 @@ def release_read(self):
|
|||||||
assert self._reads > 0
|
assert self._reads > 0
|
||||||
|
|
||||||
if self._reads == 1 and self._writes == 0:
|
if self._reads == 1 and self._writes == 0:
|
||||||
tty.debug('READ LOCK: {0.path}[{0._start}:{0._length}] [Released]'
|
self._debug(
|
||||||
|
'READ LOCK: {0.path}[{0._start}:{0._length}] [Released]'
|
||||||
.format(self))
|
.format(self))
|
||||||
self._unlock() # can raise LockError.
|
self._unlock() # can raise LockError.
|
||||||
self._reads -= 1
|
self._reads -= 1
|
||||||
@ -265,7 +269,8 @@ def release_write(self):
|
|||||||
assert self._writes > 0
|
assert self._writes > 0
|
||||||
|
|
||||||
if self._writes == 1 and self._reads == 0:
|
if self._writes == 1 and self._reads == 0:
|
||||||
tty.debug('WRITE LOCK: {0.path}[{0._start}:{0._length}] [Released]'
|
self._debug(
|
||||||
|
'WRITE LOCK: {0.path}[{0._start}:{0._length}] [Released]'
|
||||||
.format(self))
|
.format(self))
|
||||||
self._unlock() # can raise LockError.
|
self._unlock() # can raise LockError.
|
||||||
self._writes -= 1
|
self._writes -= 1
|
||||||
@ -274,6 +279,9 @@ def release_write(self):
|
|||||||
self._writes -= 1
|
self._writes -= 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _debug(self, *args):
|
||||||
|
tty.debug(*args)
|
||||||
|
|
||||||
|
|
||||||
class LockTransaction(object):
|
class LockTransaction(object):
|
||||||
"""Simple nested transaction context manager that uses a file lock.
|
"""Simple nested transaction context manager that uses a file lock.
|
||||||
|
@ -52,7 +52,6 @@
|
|||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
from llnl.util.filesystem import mkdirp
|
from llnl.util.filesystem import mkdirp
|
||||||
from llnl.util.lock import Lock, WriteTransaction, ReadTransaction
|
|
||||||
|
|
||||||
import spack.store
|
import spack.store
|
||||||
import spack.repo
|
import spack.repo
|
||||||
@ -63,6 +62,7 @@
|
|||||||
from spack.directory_layout import DirectoryLayoutError
|
from spack.directory_layout import DirectoryLayoutError
|
||||||
from spack.error import SpackError
|
from spack.error import SpackError
|
||||||
from spack.version import Version
|
from spack.version import Version
|
||||||
|
from spack.util.lock import Lock, WriteTransaction, ReadTransaction
|
||||||
|
|
||||||
|
|
||||||
# DB goes in this directory underneath the root
|
# DB goes in this directory underneath the root
|
||||||
|
@ -250,7 +250,7 @@ def add_subcommand_group(title, commands):
|
|||||||
# epilog
|
# epilog
|
||||||
formatter.add_text("""\
|
formatter.add_text("""\
|
||||||
{help}:
|
{help}:
|
||||||
spack help --all list all available commands
|
spack help --all list all commands and options
|
||||||
spack help <command> help on a specific command
|
spack help <command> help on a specific command
|
||||||
spack help --spec help on the spec syntax
|
spack help --spec help on the spec syntax
|
||||||
spack docs open http://spack.rtfd.io/ in a browser"""
|
spack docs open http://spack.rtfd.io/ in a browser"""
|
||||||
@ -311,32 +311,49 @@ def make_argument_parser(**kwargs):
|
|||||||
# stat names in groups of 7, for nice wrapping.
|
# stat names in groups of 7, for nice wrapping.
|
||||||
stat_lines = list(zip(*(iter(stat_names),) * 7))
|
stat_lines = list(zip(*(iter(stat_names),) * 7))
|
||||||
|
|
||||||
parser.add_argument('-h', '--help', action='store_true',
|
parser.add_argument(
|
||||||
|
'-h', '--help', action='store_true',
|
||||||
help="show this help message and exit")
|
help="show this help message and exit")
|
||||||
parser.add_argument('--color', action='store', default='auto',
|
parser.add_argument(
|
||||||
|
'--color', action='store', default='auto',
|
||||||
choices=('always', 'never', 'auto'),
|
choices=('always', 'never', 'auto'),
|
||||||
help="when to colorize output (default: auto)")
|
help="when to colorize output (default: auto)")
|
||||||
parser.add_argument('-d', '--debug', action='store_true',
|
parser.add_argument(
|
||||||
|
'-d', '--debug', action='store_true',
|
||||||
help="write out debug logs during compile")
|
help="write out debug logs during compile")
|
||||||
parser.add_argument('-D', '--pdb', action='store_true',
|
parser.add_argument(
|
||||||
|
'-D', '--pdb', action='store_true',
|
||||||
help="run spack under the pdb debugger")
|
help="run spack under the pdb debugger")
|
||||||
parser.add_argument('-k', '--insecure', action='store_true',
|
parser.add_argument(
|
||||||
|
'-k', '--insecure', action='store_true',
|
||||||
help="do not check ssl certificates when downloading")
|
help="do not check ssl certificates when downloading")
|
||||||
parser.add_argument('-m', '--mock', action='store_true',
|
parser.add_argument(
|
||||||
|
'-l', '--enable-locks', action='store_true', dest='locks',
|
||||||
|
default=None, help="use filesystem locking (default)")
|
||||||
|
parser.add_argument(
|
||||||
|
'-L', '--disable-locks', action='store_false', dest='locks',
|
||||||
|
help="do not use filesystem locking (unsafe)")
|
||||||
|
parser.add_argument(
|
||||||
|
'-m', '--mock', action='store_true',
|
||||||
help="use mock packages instead of real ones")
|
help="use mock packages instead of real ones")
|
||||||
parser.add_argument('-p', '--profile', action='store_true',
|
parser.add_argument(
|
||||||
dest='spack_profile',
|
'-p', '--profile', action='store_true', dest='spack_profile',
|
||||||
help="profile execution using cProfile")
|
help="profile execution using cProfile")
|
||||||
parser.add_argument('-P', '--sorted-profile', default=None, metavar="STAT",
|
parser.add_argument(
|
||||||
|
'-P', '--sorted-profile', default=None, metavar="STAT",
|
||||||
help="profile and sort by one or more of:\n[%s]" %
|
help="profile and sort by one or more of:\n[%s]" %
|
||||||
',\n '.join([', '.join(line) for line in stat_lines]))
|
',\n '.join([', '.join(line) for line in stat_lines]))
|
||||||
parser.add_argument('--lines', default=20, action='store',
|
parser.add_argument(
|
||||||
|
'--lines', default=20, action='store',
|
||||||
help="lines of profile output or 'all' (default: 20)")
|
help="lines of profile output or 'all' (default: 20)")
|
||||||
parser.add_argument('-v', '--verbose', action='store_true',
|
parser.add_argument(
|
||||||
|
'-v', '--verbose', action='store_true',
|
||||||
help="print additional output during builds")
|
help="print additional output during builds")
|
||||||
parser.add_argument('-s', '--stacktrace', action='store_true',
|
parser.add_argument(
|
||||||
|
'-s', '--stacktrace', action='store_true',
|
||||||
help="add stacktraces to all printed statements")
|
help="add stacktraces to all printed statements")
|
||||||
parser.add_argument('-V', '--version', action='store_true',
|
parser.add_argument(
|
||||||
|
'-V', '--version', action='store_true',
|
||||||
help='show version number and exit')
|
help='show version number and exit')
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@ -348,6 +365,11 @@ def setup_main_options(args):
|
|||||||
tty.set_debug(args.debug)
|
tty.set_debug(args.debug)
|
||||||
tty.set_stacktrace(args.stacktrace)
|
tty.set_stacktrace(args.stacktrace)
|
||||||
|
|
||||||
|
# override lock configuration if passed on command line
|
||||||
|
if args.locks is not None:
|
||||||
|
spack.util.lock.check_lock_safety(spack.paths.prefix)
|
||||||
|
spack.config.set('config:locks', False, scope='command_line')
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
spack.util.debug.register_interrupt_handler()
|
spack.util.debug.register_interrupt_handler()
|
||||||
spack.config.set('config:debug', True, scope='command_line')
|
spack.config.set('config:debug', True, scope='command_line')
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
'verify_ssl': {'type': 'boolean'},
|
'verify_ssl': {'type': 'boolean'},
|
||||||
'debug': {'type': 'boolean'},
|
'debug': {'type': 'boolean'},
|
||||||
'checksum': {'type': 'boolean'},
|
'checksum': {'type': 'boolean'},
|
||||||
|
'locks': {'type': 'boolean'},
|
||||||
'dirty': {'type': 'boolean'},
|
'dirty': {'type': 'boolean'},
|
||||||
'build_jobs': {'type': 'integer', 'minimum': 1},
|
'build_jobs': {'type': 'integer', 'minimum': 1},
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
from six.moves.urllib.parse import urljoin
|
from six.moves.urllib.parse import urljoin
|
||||||
|
|
||||||
import llnl.util.tty as tty
|
import llnl.util.tty as tty
|
||||||
import llnl.util.lock
|
|
||||||
from llnl.util.filesystem import mkdirp, can_access
|
from llnl.util.filesystem import mkdirp, can_access
|
||||||
from llnl.util.filesystem import remove_if_dead_link, remove_linked_tree
|
from llnl.util.filesystem import remove_if_dead_link, remove_linked_tree
|
||||||
|
|
||||||
@ -42,6 +41,7 @@
|
|||||||
import spack.caches
|
import spack.caches
|
||||||
import spack.config
|
import spack.config
|
||||||
import spack.error
|
import spack.error
|
||||||
|
import spack.util.lock
|
||||||
import spack.fetch_strategy as fs
|
import spack.fetch_strategy as fs
|
||||||
import spack.util.pattern as pattern
|
import spack.util.pattern as pattern
|
||||||
from spack.util.path import canonicalize_path
|
from spack.util.path import canonicalize_path
|
||||||
@ -231,7 +231,7 @@ def __init__(
|
|||||||
lock_id = prefix_bits(sha1, bit_length(sys.maxsize))
|
lock_id = prefix_bits(sha1, bit_length(sys.maxsize))
|
||||||
stage_lock_path = os.path.join(spack.paths.stage_path, '.lock')
|
stage_lock_path = os.path.join(spack.paths.stage_path, '.lock')
|
||||||
|
|
||||||
Stage.stage_locks[self.name] = llnl.util.lock.Lock(
|
Stage.stage_locks[self.name] = spack.util.lock.Lock(
|
||||||
stage_lock_path, lock_id, 1)
|
stage_lock_path, lock_id, 1)
|
||||||
|
|
||||||
self._lock = Stage.stage_locks[self.name]
|
self._lock = Stage.stage_locks[self.name]
|
||||||
|
@ -73,8 +73,11 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from llnl.util.filesystem import touch
|
from llnl.util.filesystem import touch
|
||||||
|
|
||||||
|
import spack.util.lock
|
||||||
|
from spack.util.executable import which
|
||||||
from spack.util.multiproc import Barrier
|
from spack.util.multiproc import Barrier
|
||||||
from llnl.util.lock import Lock, WriteTransaction, ReadTransaction, LockError
|
from spack.util.lock import Lock, WriteTransaction, ReadTransaction, LockError
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -183,15 +186,23 @@ def private_lock_path(lock_dir):
|
|||||||
lock_file = os.path.join(lock_dir, 'lockfile')
|
lock_file = os.path.join(lock_dir, 'lockfile')
|
||||||
if mpi:
|
if mpi:
|
||||||
lock_file += '.%s' % comm.rank
|
lock_file += '.%s' % comm.rank
|
||||||
|
|
||||||
yield lock_file
|
yield lock_file
|
||||||
|
|
||||||
|
if os.path.exists(lock_file):
|
||||||
|
os.unlink(lock_file)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def lock_path(lock_dir):
|
def lock_path(lock_dir):
|
||||||
"""This lock is shared among all processes in a multiproc test."""
|
"""This lock is shared among all processes in a multiproc test."""
|
||||||
lock_file = os.path.join(lock_dir, 'lockfile')
|
lock_file = os.path.join(lock_dir, 'lockfile')
|
||||||
|
|
||||||
yield lock_file
|
yield lock_file
|
||||||
|
|
||||||
|
if os.path.exists(lock_file):
|
||||||
|
os.unlink(lock_file)
|
||||||
|
|
||||||
|
|
||||||
def local_multiproc_test(*functions):
|
def local_multiproc_test(*functions):
|
||||||
"""Order some processes using simple barrier synchronization."""
|
"""Order some processes using simple barrier synchronization."""
|
||||||
@ -900,3 +911,95 @@ def do_write_with_exception(exit_fn):
|
|||||||
assert vals['exception']
|
assert vals['exception']
|
||||||
assert not vals['exited_fn']
|
assert not vals['exited_fn']
|
||||||
assert not vals['exception_fn']
|
assert not vals['exception_fn']
|
||||||
|
|
||||||
|
|
||||||
|
def test_disable_locking(private_lock_path):
|
||||||
|
"""Ensure that locks do no real locking when disabled."""
|
||||||
|
old_value = spack.config.get('config:locks')
|
||||||
|
|
||||||
|
with spack.config.override('config:locks', False):
|
||||||
|
lock = Lock(private_lock_path)
|
||||||
|
|
||||||
|
lock.acquire_read()
|
||||||
|
assert not os.path.exists(private_lock_path)
|
||||||
|
|
||||||
|
lock.acquire_write()
|
||||||
|
assert not os.path.exists(private_lock_path)
|
||||||
|
|
||||||
|
lock.release_write()
|
||||||
|
assert not os.path.exists(private_lock_path)
|
||||||
|
|
||||||
|
lock.release_read()
|
||||||
|
assert not os.path.exists(private_lock_path)
|
||||||
|
|
||||||
|
assert old_value == spack.config.get('config:locks')
|
||||||
|
|
||||||
|
|
||||||
|
def test_lock_checks_user(tmpdir):
|
||||||
|
"""Ensure lock checks work."""
|
||||||
|
path = str(tmpdir)
|
||||||
|
uid = os.getuid()
|
||||||
|
|
||||||
|
# self-owned, own group
|
||||||
|
tmpdir.chown(uid, uid)
|
||||||
|
|
||||||
|
# safe
|
||||||
|
tmpdir.chmod(0o744)
|
||||||
|
spack.util.lock.check_lock_safety(path)
|
||||||
|
|
||||||
|
# safe
|
||||||
|
tmpdir.chmod(0o774)
|
||||||
|
spack.util.lock.check_lock_safety(path)
|
||||||
|
|
||||||
|
# unsafe
|
||||||
|
tmpdir.chmod(0o777)
|
||||||
|
with pytest.raises(spack.error.SpackError):
|
||||||
|
spack.util.lock.check_lock_safety(path)
|
||||||
|
|
||||||
|
# safe
|
||||||
|
tmpdir.chmod(0o474)
|
||||||
|
spack.util.lock.check_lock_safety(path)
|
||||||
|
|
||||||
|
# safe
|
||||||
|
tmpdir.chmod(0o477)
|
||||||
|
spack.util.lock.check_lock_safety(path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_lock_checks_group(tmpdir):
|
||||||
|
path = str(tmpdir)
|
||||||
|
uid = os.getuid()
|
||||||
|
|
||||||
|
id_cmd = which('id')
|
||||||
|
if not id_cmd:
|
||||||
|
pytest.skip("Can't determine user's groups.")
|
||||||
|
|
||||||
|
# find a legal gid to user that is NOT the user's uid
|
||||||
|
gids = [int(gid) for gid in id_cmd('-G', output=str).split(' ')]
|
||||||
|
gid = next((g for g in gids if g != uid), None)
|
||||||
|
if gid is None:
|
||||||
|
pytest.skip("Can't determine user's groups.")
|
||||||
|
|
||||||
|
# self-owned, another group
|
||||||
|
tmpdir.chown(uid, gid)
|
||||||
|
|
||||||
|
# safe
|
||||||
|
tmpdir.chmod(0o744)
|
||||||
|
spack.util.lock.check_lock_safety(path)
|
||||||
|
|
||||||
|
# unsafe
|
||||||
|
tmpdir.chmod(0o774)
|
||||||
|
with pytest.raises(spack.error.SpackError):
|
||||||
|
spack.util.lock.check_lock_safety(path)
|
||||||
|
|
||||||
|
# unsafe
|
||||||
|
tmpdir.chmod(0o777)
|
||||||
|
with pytest.raises(spack.error.SpackError):
|
||||||
|
spack.util.lock.check_lock_safety(path)
|
||||||
|
|
||||||
|
# safe
|
||||||
|
tmpdir.chmod(0o474)
|
||||||
|
spack.util.lock.check_lock_safety(path)
|
||||||
|
|
||||||
|
# safe
|
||||||
|
tmpdir.chmod(0o477)
|
||||||
|
spack.util.lock.check_lock_safety(path)
|
||||||
|
@ -26,9 +26,9 @@
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from llnl.util.filesystem import mkdirp
|
from llnl.util.filesystem import mkdirp
|
||||||
from llnl.util.lock import Lock, ReadTransaction, WriteTransaction
|
|
||||||
|
|
||||||
from spack.error import SpackError
|
from spack.error import SpackError
|
||||||
|
from spack.util.lock import Lock, ReadTransaction, WriteTransaction
|
||||||
|
|
||||||
|
|
||||||
class FileCache(object):
|
class FileCache(object):
|
||||||
|
91
lib/spack/spack/util/lock.py
Normal file
91
lib/spack/spack/util/lock.py
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
##############################################################################
|
||||||
|
# Copyright (c) 2013-2018, Lawrence Livermore National Security, LLC.
|
||||||
|
# Produced at the Lawrence Livermore National Laboratory.
|
||||||
|
#
|
||||||
|
# This file is part of Spack.
|
||||||
|
# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
|
||||||
|
# LLNL-CODE-647188
|
||||||
|
#
|
||||||
|
# For details, see https://github.com/spack/spack
|
||||||
|
# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Lesser General Public License (as
|
||||||
|
# published by the Free Software Foundation) version 2.1, February 1999.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
|
||||||
|
# conditions of the GNU Lesser General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
|
# License along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
##############################################################################
|
||||||
|
"""Wrapper for ``llnl.util.lock`` allows locking to be enabled/disabled."""
|
||||||
|
import os
|
||||||
|
import stat
|
||||||
|
|
||||||
|
import llnl.util.lock
|
||||||
|
from llnl.util.lock import * # noqa
|
||||||
|
|
||||||
|
import spack.config
|
||||||
|
import spack.error
|
||||||
|
import spack.paths
|
||||||
|
|
||||||
|
|
||||||
|
class Lock(llnl.util.lock.Lock):
|
||||||
|
"""Lock that can be disabled.
|
||||||
|
|
||||||
|
This overrides the ``_lock()`` and ``_unlock()`` methods from
|
||||||
|
``llnl.util.lock`` so that all the lock API calls will succeed, but
|
||||||
|
the actual locking mechanism can be disabled via ``_enable_locks``.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Lock, self).__init__(*args, **kwargs)
|
||||||
|
self._enable = spack.config.get('config:locks', True)
|
||||||
|
|
||||||
|
def _lock(self, op, timeout=0):
|
||||||
|
if self._enable:
|
||||||
|
super(Lock, self)._lock(op, timeout)
|
||||||
|
|
||||||
|
def _unlock(self):
|
||||||
|
"""Unlock call that always succeeds."""
|
||||||
|
if self._enable:
|
||||||
|
super(Lock, self)._unlock()
|
||||||
|
|
||||||
|
def _debug(self, *args):
|
||||||
|
if self._enable:
|
||||||
|
super(Lock, self)._debug(*args)
|
||||||
|
|
||||||
|
|
||||||
|
def check_lock_safety(path):
|
||||||
|
"""Do some extra checks to ensure disabling locks is safe.
|
||||||
|
|
||||||
|
This will raise an error if ``path`` can is group- or world-writable
|
||||||
|
AND the current user can write to the directory (i.e., if this user
|
||||||
|
AND others could write to the path).
|
||||||
|
|
||||||
|
This is intended to run on the Spack prefix, but can be run on any
|
||||||
|
path for testing.
|
||||||
|
"""
|
||||||
|
if os.access(path, os.W_OK):
|
||||||
|
stat_result = os.stat(path)
|
||||||
|
uid, gid = stat_result.st_uid, stat_result.st_gid
|
||||||
|
mode = stat_result[stat.ST_MODE]
|
||||||
|
|
||||||
|
writable = None
|
||||||
|
if (mode & stat.S_IWGRP) and (uid != gid):
|
||||||
|
# spack is group-writeable and the group is not the owner
|
||||||
|
writable = 'group'
|
||||||
|
elif (mode & stat.S_IWOTH):
|
||||||
|
# spack is world-writeable
|
||||||
|
writable = 'world'
|
||||||
|
|
||||||
|
if writable:
|
||||||
|
msg = "Refusing to disable locks: spack is {0}-writable.".format(
|
||||||
|
writable)
|
||||||
|
long_msg = (
|
||||||
|
"Running a shared spack without locks is unsafe. You must "
|
||||||
|
"restrict permissions on {0} or enable locks.").format(path)
|
||||||
|
raise spack.error.SpackError(msg, long_msg)
|
Loading…
Reference in New Issue
Block a user