sapling/eden/integration/lib/fake_edenfs.py
Wez Furlong b39f678b85 edenfs: remove use of fork from StartupLogger
Summary:
on macOS we cannot safely use `fork`.

This commit replaces the use of `fork` in the startup logger subsystem.
This was a little tricky to untangle; originally (prior to any of
the `fork` removal efforts in this diff stack), the startup flow was
to spawn a set of processes via fork:

```
edenfs (setuid)
 \-----edenfs (privhelper, as root)
  \------edenfs (daemonized)
```

The forked children take advantage of being able to implicitly pass state to
the child processes from the parent.  That data flow needs to become explicit
when removing the fork which makes some things a little awkward.

With fork removed:

* `edenfs` unconditionally spawns `edenfs_privhelper` while it has
  root privs and before most of the process has been initialized.
* That same `edenfs` process will then spawn a child `edenfs`
  process which starts from scratch, but that which needs to
  run as the real server instance
* The original `edenfs` instance needs to linger for a while
  to remain connected to the controlling tty to pass back the
  startup state to the user, before terminating.

This commit deletes the check that `edenfs` is started originally
as root; previously the logic relied on the forked startup logger
continuing past the `daemonizeIfRequested` call and simply deferring
the check until after folly::init.  With these changes we can't
easily perform such a check without adding some extra gymnastics
to pass the state around; the place where that is checked is in
the spawned child of the original edenfs, which is not a privileged
process and doesn't know the original euid.  I don't believe this
to be a great loss as we tuck `edenfs` away under the libexec dir.

Reviewed By: chadaustin

Differential Revision: D23696569

fbshipit-source-id: 55b95daf022601a4699274d696af419f0a11f6f2
2020-09-18 17:22:39 -07:00

100 lines
3.0 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 contextlib
import os
import pathlib
import signal
import subprocess
import typing
from eden.thrift.legacy import create_thrift_client
from .find_executables import FindExe
class FakeEdenFS(typing.ContextManager[int]):
"""A running fake_edenfs process."""
@classmethod
def spawn(
cls,
eden_dir: pathlib.Path,
etc_eden_dir: pathlib.Path,
home_dir: pathlib.Path,
extra_arguments: typing.Optional[typing.Sequence[str]] = None,
) -> "FakeEdenFS":
command: typing.List[str] = [
FindExe.FAKE_EDENFS,
"--configPath",
str(home_dir / ".edenrc"),
"--edenDir",
str(eden_dir),
"--etcEdenDir",
str(etc_eden_dir),
"--edenfs",
]
if extra_arguments:
command.extend(extra_arguments)
subprocess.check_call(command)
return cls.from_existing_process(eden_dir=eden_dir)
@classmethod
def spawn_via_cli(
cls,
eden_dir: pathlib.Path,
etc_eden_dir: pathlib.Path,
home_dir: pathlib.Path,
extra_arguments: typing.Optional[typing.Sequence[str]] = None,
) -> "FakeEdenFS":
command: typing.List[str] = [
FindExe.EDEN_CLI,
"--config-dir",
str(eden_dir),
"--etc-eden-dir",
str(etc_eden_dir),
"--home-dir",
str(home_dir),
"start",
"--daemon-binary",
FindExe.FAKE_EDENFS,
]
if extra_arguments:
command.append("--")
command.extend(extra_arguments)
subprocess.check_call(command)
return cls.from_existing_process(eden_dir=eden_dir)
@staticmethod
def from_existing_process(eden_dir: pathlib.Path) -> "FakeEdenFS":
edenfs_pid = int((eden_dir / "lock").read_text())
return FakeEdenFS(process_id=edenfs_pid)
def __init__(self, process_id: int) -> None:
super().__init__()
self.process_id = process_id
def __enter__(self) -> int:
return self.process_id
def __exit__(self, exc_type, exc_val, exc_tb):
with contextlib.suppress(ProcessLookupError):
os.kill(self.process_id, signal.SIGTERM)
return None
def get_fake_edenfs_argv(eden_dir: pathlib.Path) -> typing.List[str]:
with create_thrift_client(str(eden_dir)) as client:
argv = client.getDaemonInfo().commandLine
# StartupLogger may add `--startupLoggerFd 5` as a parameter.
# The 5 is a file descriptor number and has no guarantees as
# to which number is selected by the kernel.
# We perform various test assertions on these arguments.
# To make those easier, we rewrite the fd number to always be 5
if "--startupLoggerFd" in argv:
argv[argv.index("--startupLoggerFd") + 1] = "5"
return argv