mirror of
https://github.com/facebook/sapling.git
synced 2024-10-10 16:57:49 +03:00
d58a3c96d8
Summary: This diff removes the logic that consumes the legacy bind mount list and mounts them on startup. That functionality has been replaced with the eden redirect command. Instead of performing the bind mounts in the server, the server will now run `eden redirect fixup` to apply that configuration. This diff also changes the behavior of performBindMounts: previously, if the bind mount setup failed, we would tear down the entire repo mount. Since we're now spawning an external process, it is much more likely that something might fail and result in a bad experience, so we no longer bail out in that case: we'll continue and leave the bind mounts as-is. The user can then use `eden doctor` or `eden redirect fixup` to sort things out. Reviewed By: simpkins Differential Revision: D17236366 fbshipit-source-id: 8b004551a076216f0e5448942f00b5195ee18803
155 lines
4.8 KiB
Python
155 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2.
|
|
|
|
import abc
|
|
import errno
|
|
import logging
|
|
import os
|
|
import random
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from typing import List, NamedTuple, Union
|
|
|
|
|
|
log = logging.getLogger("eden.cli.mtab")
|
|
|
|
|
|
MountInfo = NamedTuple(
|
|
"MountInfo", [("device", bytes), ("mount_point", bytes), ("vfstype", bytes)]
|
|
)
|
|
|
|
|
|
MTStat = NamedTuple("MTStat", [("st_uid", int), ("st_dev", int), ("st_mode", int)])
|
|
|
|
|
|
class MountTable(abc.ABC):
|
|
@abc.abstractmethod
|
|
def read(self) -> List[MountInfo]:
|
|
"Returns the list of system mounts."
|
|
|
|
@abc.abstractmethod
|
|
def unmount_lazy(self, mount_point: bytes) -> bool:
|
|
"Corresponds to `umount -l` on Linux."
|
|
|
|
@abc.abstractmethod
|
|
def unmount_force(self, mount_point: bytes) -> bool:
|
|
"Corresponds to `umount -f` on Linux."
|
|
|
|
def lstat(self, path: Union[bytes, str]) -> MTStat:
|
|
"Returns a subset of the results of os.lstat."
|
|
st = os.lstat(path)
|
|
return MTStat(st_uid=st.st_uid, st_dev=st.st_dev, st_mode=st.st_mode)
|
|
|
|
def check_path_access(self, path: bytes) -> None:
|
|
"""\
|
|
Attempts to stat the given directory, bypassing the kernel's caches.
|
|
Raises OSError upon failure.
|
|
"""
|
|
# Even if the FUSE process is shut down, the lstat call will succeed if
|
|
# the stat result is cached. Append a random string to avoid that. In a
|
|
# better world, this code would bypass the cache by opening a handle
|
|
# with O_DIRECT, but Eden does not support O_DIRECT.
|
|
|
|
try:
|
|
os.lstat(os.path.join(path, hex(random.getrandbits(32))[2:].encode()))
|
|
except OSError as e:
|
|
if e.errno == errno.ENOENT:
|
|
return
|
|
raise
|
|
|
|
@abc.abstractmethod
|
|
def create_bind_mount(self, source_path, dest_path) -> bool:
|
|
"Creates a bind mount from source_path to dest_path."
|
|
|
|
|
|
def parse_mtab(contents: bytes) -> List[MountInfo]:
|
|
mounts = []
|
|
for line in contents.splitlines():
|
|
# columns split by space or tab per man page
|
|
entries = line.split()
|
|
if len(entries) != 6:
|
|
log.warning(f"mount table line has {len(entries)} entries instead of 6")
|
|
continue
|
|
device, mount_point, vfstype, opts, freq, passno = entries
|
|
mounts.append(
|
|
MountInfo(device=device, mount_point=mount_point, vfstype=vfstype)
|
|
)
|
|
return mounts
|
|
|
|
|
|
class LinuxMountTable(MountTable):
|
|
def read(self) -> List[MountInfo]:
|
|
# What's the most portable mtab path? I've seen both /etc/mtab and
|
|
# /proc/self/mounts. CentOS 6 in particular does not symlink /etc/mtab
|
|
# to /proc/self/mounts so go directly to /proc/self/mounts.
|
|
# This code could eventually fall back to /proc/mounts and /etc/mtab.
|
|
with open("/proc/self/mounts", "rb") as f:
|
|
return parse_mtab(f.read())
|
|
|
|
def unmount_lazy(self, mount_point: bytes) -> bool:
|
|
# MNT_DETACH
|
|
return 0 == subprocess.call(["sudo", "umount", "-l", mount_point])
|
|
|
|
def unmount_force(self, mount_point: bytes) -> bool:
|
|
# MNT_FORCE
|
|
return 0 == subprocess.call(["sudo", "umount", "-f", mount_point])
|
|
|
|
def create_bind_mount(self, source_path, dest_path) -> bool:
|
|
return 0 == subprocess.check_call(
|
|
["sudo", "mount", "-o", "bind", source_path, dest_path]
|
|
)
|
|
|
|
|
|
def parse_macos_mount_output(contents: bytes) -> List[MountInfo]:
|
|
mounts = []
|
|
for line in contents.splitlines():
|
|
m = re.match(b"^(\\S+) on (.+) \\(([^,]+),.*\\)$", line)
|
|
if m:
|
|
mounts.append(
|
|
MountInfo(device=m.group(1), mount_point=m.group(2), vfstype=m.group(3))
|
|
)
|
|
return mounts
|
|
|
|
|
|
class MacOSMountTable(MountTable):
|
|
def read(self) -> List[MountInfo]:
|
|
# Specifying the path is important, as sudo may have munged the path
|
|
# such that /sbin is not part of it any longer
|
|
contents = subprocess.check_output(["/sbin/mount"])
|
|
return parse_macos_mount_output(contents)
|
|
|
|
def unmount_lazy(self, mount_point: bytes) -> bool:
|
|
return False
|
|
|
|
def unmount_force(self, mount_point: bytes) -> bool:
|
|
return False
|
|
|
|
def create_bind_mount(self, source_path, dest_path) -> bool:
|
|
return False
|
|
|
|
|
|
class NopMountTable(MountTable):
|
|
def read(self) -> List[MountInfo]:
|
|
return []
|
|
|
|
def unmount_lazy(self, mount_point: bytes) -> bool:
|
|
return False
|
|
|
|
def unmount_force(self, mount_point: bytes) -> bool:
|
|
return False
|
|
|
|
def create_bind_mount(self, source_path, dest_path) -> bool:
|
|
return False
|
|
|
|
|
|
def new() -> MountTable:
|
|
if "linux" in sys.platform:
|
|
return LinuxMountTable()
|
|
if sys.platform == "darwin":
|
|
return MacOSMountTable()
|
|
return NopMountTable()
|