blob: 5f00a3e5eecdf5520ba780d8f2e369f6bb5a9ffe [file] [log] [blame]
# Copyright 2025 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import shutil
import subprocess
from pathlib import Path
import logger
class CartfsError(Exception):
"""Base exception for Cartfs errors."""
class CartfsNotInstalledError(CartfsError):
"""Raised when cartfs is not installed."""
class CartfsNotRunningError(CartfsError):
"""Raised when cartfs is not running."""
class Cartfs:
"""A class to interact with a cartfs filesystem."""
def __init__(self, mount_point: Path, use_local_mock_cartfs: bool):
"""Initializes a Cartfs instance.
Note: This constructor should not be called directly. Instead, use the
`create` class method to create an instance.
"""
self.mount_point = mount_point
self.use_local_mock_cartfs = use_local_mock_cartfs
@staticmethod
def _is_installed(use_local_mock_cartfs: bool) -> bool:
"""Checks if cartfs is installed."""
if use_local_mock_cartfs:
return True
return shutil.which("cartfs") is not None
@staticmethod
def _find_mount_point(use_local_mock_cartfs: bool) -> Path | None:
"""Finds the mount point for cartfs.
Returns:
The mount point if found, None otherwise.
"""
if use_local_mock_cartfs:
local_mock_cartfs_mount_point = Path.home() / "mock_cartfs"
local_mock_cartfs_mount_point.mkdir(parents=True, exist_ok=True)
return local_mock_cartfs_mount_point
try:
cartfs_uid_process = subprocess.run(
["id", "-u", "cartfs"],
capture_output=True,
text=True,
check=True,
)
cartfs_uid = cartfs_uid_process.stdout.strip()
except (subprocess.CalledProcessError, FileNotFoundError):
logger.log_warn(
"Unable to find uid for cartfs. It is likely cartfs is not running or the user does not have permission"
)
# This is the case where cartfs is not running or user does not have permissions.
return None
try:
findmnt_process = subprocess.run(
[
"findmnt",
"-n",
"-t",
"fuse",
"-O",
f"user_id={cartfs_uid}",
"-o",
"TARGET",
],
capture_output=True,
text=True,
check=True,
)
output = findmnt_process.stdout.strip()
if not output:
return None
# Return the first mount point found.
return Path(output.splitlines()[0])
except (subprocess.CalledProcessError, FileNotFoundError):
# findmnt is not found or failed. This is unexpected on a gLinux host.
# But we can treat it as cartfs not running.
logger.log_warn(
"Unable to find cartfs mount. Either findmnt failed or cartfs is not running."
)
return None
@classmethod
def create(cls, use_local_mock_cartfs: bool = False) -> "Cartfs":
"""Creates a Cartfs instance after verifying its state.
Raises:
CartfsNotInstalledError: If cartfs is not installed.
CartfsNotRunningError: If cartfs is not running or the mount point
cannot be found.
Returns:
A new Cartfs instance.
"""
if not cls._is_installed(use_local_mock_cartfs):
raise CartfsNotInstalledError(
"cartfs is not installed. Please follow instructions at go/cartfs to install."
)
mount_point = cls._find_mount_point(use_local_mock_cartfs)
if not mount_point:
raise CartfsNotRunningError(
"cartfs is installed but not running. Please start cartfs to continue."
)
return cls(mount_point, use_local_mock_cartfs)
def suggest_cartfs_directory_name(self, workspace_name: str) -> Path:
"""Suggests a directory name within the cartfs mount point.
Args:
workspace_name: The base name for the workspace directory.
Returns:
A path to a directory that does not yet exist.
"""
path = Path(workspace_name)
counter = 1
while (self.mount_point / path).exists():
path = Path(f"{workspace_name}-{counter}")
counter += 1
return path