diff --git a/mkosi/__init__.py b/mkosi/__init__.py index c2f480e17..b754bd7da 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -72,6 +72,7 @@ from mkosi.util import ( flatten, flock, + flock_or_die, format_rlimit, make_executable, one_zero, @@ -3633,7 +3634,7 @@ def run_shell(args: Args, config: Config) -> None: if config.ephemeral: fname = stack.enter_context(copy_ephemeral(config, config.output_dir_or_cwd() / config.output)) else: - fname = config.output_dir_or_cwd() / config.output + fname = stack.enter_context(flock_or_die(config.output_dir_or_cwd() / config.output)) if config.output_format == OutputFormat.disk and args.verb == Verb.boot: run( @@ -3955,7 +3956,12 @@ def run_clean(args: Args, config: Config, *, resources: Path) -> None: remove_package_cache = args.force > 2 if outputs := list(config.output_dir_or_cwd().glob(f"{config.output}*")): - with complete_step(f"Removing output files of {config.name()} image…"): + with ( + complete_step(f"Removing output files of {config.name()} image…"), + flock_or_die(config.output_dir_or_cwd() / config.output) + if (config.output_dir_or_cwd() / config.output).exists() + else contextlib.nullcontext() + ): rmtree(*outputs) if remove_build_cache: diff --git a/mkosi/qemu.py b/mkosi/qemu.py index 3c67dca82..4239dde74 100644 --- a/mkosi/qemu.py +++ b/mkosi/qemu.py @@ -40,7 +40,7 @@ from mkosi.tree import copy_tree, rmtree from mkosi.types import PathString from mkosi.user import INVOKING_USER, become_root -from mkosi.util import StrEnum, flatten +from mkosi.util import StrEnum, flatten, flock, flock_or_die from mkosi.versioncomp import GenericVersion QEMU_KVM_DEVICE_VERSION = GenericVersion("9.0") @@ -426,7 +426,8 @@ def copy() -> None: sandbox=config.sandbox, ) - fork_and_wait(copy) + with flock(src): + fork_and_wait(copy) yield tmp finally: def rm() -> None: @@ -705,7 +706,7 @@ def run_qemu(args: Args, config: Config) -> None: copy_ephemeral(config, config.output_dir_or_cwd() / config.output_with_compression) ) else: - fname = config.output_dir_or_cwd() / config.output_with_compression + fname = stack.enter_context(flock_or_die(config.output_dir_or_cwd() / config.output_with_compression)) if config.output_format == OutputFormat.disk and config.runtime_size: run( diff --git a/mkosi/util.py b/mkosi/util.py index 0e3c598e8..812c33e53 100644 --- a/mkosi/util.py +++ b/mkosi/util.py @@ -4,6 +4,7 @@ import contextlib import copy import enum +import errno import fcntl import functools import importlib @@ -20,6 +21,7 @@ from types import ModuleType from typing import Any, Callable, Optional, TypeVar, no_type_check +from mkosi.log import die from mkosi.types import PathString T = TypeVar("T") @@ -142,6 +144,19 @@ def flock(path: Path, flags: int = fcntl.LOCK_EX) -> Iterator[int]: os.close(fd) +@contextlib.contextmanager +def flock_or_die(path: Path) -> Iterator[Path]: + try: + with flock(path, fcntl.LOCK_EX|fcntl.LOCK_NB): + yield path + except OSError as e: + if e.errno != errno.EWOULDBLOCK: + raise e + + die(f"Cannot lock {path} as it is locked by another process", + hint="Maybe another mkosi process is still using it?") + + @contextlib.contextmanager def scopedenv(env: Mapping[str, Any]) -> Iterator[None]: old = copy.deepcopy(os.environ) diff --git a/mkosi/vmspawn.py b/mkosi/vmspawn.py index d4fa3716d..8ed0fe879 100644 --- a/mkosi/vmspawn.py +++ b/mkosi/vmspawn.py @@ -21,6 +21,7 @@ ) from mkosi.run import run from mkosi.types import PathString +from mkosi.util import flock_or_die def run_vmspawn(args: Args, config: Config) -> None: @@ -79,7 +80,7 @@ def run_vmspawn(args: Args, config: Config) -> None: if config.ephemeral: fname = stack.enter_context(copy_ephemeral(config, config.output_dir_or_cwd() / config.output)) else: - fname = config.output_dir_or_cwd() / config.output + fname = stack.enter_context(flock_or_die(config.output_dir_or_cwd() / config.output)) if config.output_format == OutputFormat.disk and config.runtime_size: run(