Source code for craft_parts.overlays.overlay_fs
# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
#
# Copyright 2021 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# 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 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, see <http://www.gnu.org/licenses/>.
"""Low level interface to OS overlayfs."""
import logging
import os
from pathlib import Path
from subprocess import CalledProcessError
from typing import List, Optional
from craft_parts.utils import os_utils
from . import errors
logger = logging.getLogger(__name__)
[docs]class OverlayFS:
"""Linux overlayfs operations."""
def __init__(self, *, lower_dirs: List[Path], upper_dir: Path, work_dir: Path):
self._lower_dirs = lower_dirs
self._upper_dir = upper_dir
self._work_dir = work_dir
self._mountpoint: Optional[Path] = None
[docs] def mount(self, mountpoint: Path) -> None:
"""Mount an overlayfs.
:param mountpoint: The filesystem mount point.
:raises OverlayMountError: on mount error.
"""
logger.debug("mount overlayfs on %s", mountpoint)
lower_dir = ":".join([str(p) for p in self._lower_dirs])
try:
os_utils.mount_overlayfs(
str(mountpoint),
f"-olowerdir={lower_dir!s},upperdir={self._upper_dir!s},"
f"workdir={self._work_dir!s}",
)
except CalledProcessError as err:
raise errors.OverlayMountError(str(mountpoint), message=str(err)) from err
self._mountpoint = mountpoint
[docs] def unmount(self) -> None:
"""Umount an overlayfs.
:raises OverlayUnmountError: on unmount error.
"""
if not self._mountpoint:
return
logger.debug("unmount overlayfs from %s", self._mountpoint)
try:
os_utils.umount(str(self._mountpoint))
except CalledProcessError as err:
raise errors.OverlayUnmountError(
str(self._mountpoint), message=str(err)
) from err
self._mountpoint = None
[docs]def is_whiteout_file(path: Path) -> bool:
"""Verify if the given path corresponds to a whiteout file.
Overlayfs whiteout files are represented as character devices
with major and minor numbers set to 0.
:param path: The path of the file to verify.
:returns: Whether the given path is an overlayfs whiteout.
"""
if not path.is_char_device() or path.is_symlink():
return False
rdev = os.stat(path).st_rdev
return os.major(rdev) == 0 and os.minor(rdev) == 0
[docs]def is_opaque_dir(path: Path) -> bool:
"""Verify if the given path corresponds to an opaque directory.
Overlayfs opaque directories are represented by directories with the
extended attribute `trusted.overlay.opaque` set to `y`.
:param path: The path of the file to verify.
:returns: Whether the given path is an overlayfs opaque directory.
"""
if not path.is_dir() or path.is_symlink():
return False
try:
value = os.getxattr(path, "trusted.overlay.opaque")
except OSError:
return False
return value == b"y"