Esempio n. 1
0
def run_operating_system_checks(tracker: ProblemTracker,
                                instance: EdenInstance,
                                out: ui.Output) -> None:
    if platform.system() != "Linux":
        return

    # get kernel version string; same as "uname -r"
    current_kernel_release = platform.release()

    # check if version too low
    result = _os_is_kernel_version_too_old(instance, current_kernel_release)
    if result:
        tracker.add_problem(
            OSProblem(
                # TODO: Reword these messages prior to public release
                description=f"Kernel version {current_kernel_release} too low.",
                remediation=f"Reboot to upgrade kernel version.",
            ))
        # if the kernel version is too low, return here as continuing to
        # further checks has no benefit
        return

    # check against known bad versions
    result = _os_is_bad_release(instance, current_kernel_release)
    if result:
        tracker.add_problem(
            OSProblem(
                # TODO: Reword these messages prior to public release
                description=f"Kernel {current_kernel_release} is a known " +
                "bad kernel.",
                remediation="Reboot to upgrade kernel version.",
            ))
        return
Esempio n. 2
0
def run_kerberos_certificate_checks(tracker: ProblemTracker) -> None:
    # Skip these checks on Windows machines and on Sandcastle
    if platform.system() == "Windows" or os.environ.get("SANDCASTLE"):
        return

    result = subprocess.call(["klist", "-s"])
    if result:
        tracker.add_problem(KerberosProblem())
Esempio n. 3
0
def check_many_edenfs_are_running(
    tracker: ProblemTracker, proc_utils: ProcUtils, uid: Optional[int] = None
) -> None:
    rogue_processes = find_rogue_processes(proc_utils, uid=uid)
    if len(rogue_processes) > 0:
        rogue_pids = [p.pid for p in rogue_processes]
        rogue_pids_problem = ManyEdenFsRunning(rogue_pids)
        tracker.add_problem(rogue_pids_problem)
Esempio n. 4
0
def read_shared_path(tracker: ProblemTracker, shared_path: Path) -> str:
    try:
        return shared_path.read_text()
    except (FileNotFoundError, IsADirectoryError):
        raise
    except Exception as e:
        tracker.add_problem(Problem(f"Failed to read .hg/sharedpath: {e}"))
        raise
Esempio n. 5
0
def read_shared_path(tracker: ProblemTracker, shared_path: Path) -> str:
    try:
        return shared_path.read_text()
    except (FileNotFoundError, IsADirectoryError):
        raise
    except Exception as e:
        tracker.add_problem(UnreadableSharedpath(e))
        raise
Esempio n. 6
0
def check_shared_path(tracker: ProblemTracker, mount_path: Path) -> None:
    shared_path = get_shared_path(mount_path)
    try:
        dst_shared_path = read_shared_path(tracker, shared_path)
    except Exception:
        return

    if is_nfs_mounted(dst_shared_path):
        tracker.add_problem(MercurialDataOnNFS(shared_path, dst_shared_path))
Esempio n. 7
0
    def run_kerberos_certificate_checks(self, instance: EdenInstance,
                                        tracker: ProblemTracker) -> None:
        if not instance.get_config_bool("doctor.enable-kerberos-check",
                                        default=False):
            return

        result = subprocess.call(["klist", "-s"])
        if result:
            tracker.add_problem(KerberosProblem())
Esempio n. 8
0
def check_in_progress_checkout(tracker: ProblemTracker,
                               checkout: EdenCheckout) -> None:
    try:
        checkout.get_snapshot()
    except InProgressCheckoutError as ex:
        if proc_utils.new().is_edenfs_process(ex.pid):
            return

        tracker.add_problem(PreviousEdenFSCrashedDuringCheckout(checkout, ex))
Esempio n. 9
0
def check_watchman_subscriptions(tracker: ProblemTracker, path: str,
                                 info: WatchmanCheckInfo) -> None:
    if path not in info.watchman_roots:
        return

    watch_details = _call_watchman(["watch-project", path])
    watcher = watch_details.get("watcher")
    if watcher == "eden":
        return

    tracker.add_problem(IncorrectWatchmanWatch(path, watcher))
Esempio n. 10
0
def check_redirections(
    tracker: ProblemTracker,
    instance: EdenInstance,
    checkout: EdenCheckout,
    mount_table: mtab.MountTable,
) -> None:
    redirs = get_effective_redirections(checkout, mount_table)

    for redir in redirs.values():
        if redir.state == RedirectionState.MATCHES_CONFIGURATION:
            continue
        tracker.add_problem(MisconfiguredRedirection(redir, checkout))
Esempio n. 11
0
def check_disk_usage(
    tracker: ProblemTracker,
    mount_paths: List[str],
    instance: EdenInstance,
    fs_util: FsUtil,
) -> None:
    prob_advice_space_used_ratio_threshold = 0.90
    prob_error_absolute_space_used_threshold = 1024 * 1024 * 1024  # 1GB

    eden_mount_pts_set = get_mount_pts_set(tracker, mount_paths, instance)

    for eden_mount_pt in eden_mount_pts_set:
        if eden_mount_pt and os.path.exists(eden_mount_pt):
            disk_status = fs_util.statvfs(eden_mount_pt)

            avail = disk_status.f_frsize * disk_status.f_bavail
            size = disk_status.f_frsize * disk_status.f_blocks
            if size == 0:
                continue

            used = size - avail
            used_percent = float(used) / size

            message = (
                "Eden lazily loads your files and needs enough disk space to "
                "store these files when loaded."
            )
            extra_message = instance.get_config_value(
                "doctor.low-disk-space-message", ""
            )
            if extra_message:
                message = f"{message} {extra_message}"

            if avail <= prob_error_absolute_space_used_threshold:
                tracker.add_problem(
                    Problem(
                        f"{eden_mount_pt} "
                        f"has only {str(avail)} bytes available. "
                        f"{message}",
                        severity=ProblemSeverity.ERROR,
                    )
                )
            elif used_percent >= prob_advice_space_used_ratio_threshold:
                tracker.add_problem(
                    Problem(
                        f"{eden_mount_pt} "
                        f"is {used_percent:.2%} full. "
                        f"{message}",
                        severity=ProblemSeverity.ADVICE,
                    )
                )
Esempio n. 12
0
def check_materialized_are_accessible(
    tracker: ProblemTracker,
    instance: EdenInstance,
    checkout: EdenCheckout,
) -> None:
    mismatched_mode = []
    inaccessible_inodes = []

    with instance.get_thrift_client_legacy() as client:
        materialized = client.debugInodeStatus(
            bytes(checkout.path),
            b"",
            flags=DIS_REQUIRE_MATERIALIZED,
            sync=SyncBehavior(),
        )

    for materialized_dir in materialized:
        path = Path(os.fsdecode(materialized_dir.path))
        try:
            st = os.lstat(checkout.path / path)
        except OSError:
            inaccessible_inodes += [path]
            continue

        if not stat.S_ISDIR(st.st_mode):
            mismatched_mode += [(path, stat.S_IFDIR, st.st_mode)]

        for dirent in materialized_dir.entries:
            if dirent.materialized:
                dirent_path = path / Path(os.fsdecode(dirent.name))
                try:
                    dirent_stat = os.lstat(checkout.path / dirent_path)
                except OSError:
                    inaccessible_inodes += [dirent_path]
                    continue

                # TODO(xavierd): Symlinks are for now recognized as files.
                dirent_mode = (stat.S_IFREG if stat.S_ISLNK(
                    dirent_stat.st_mode) else stat.S_IFMT(dirent_stat.st_mode))
                if dirent_mode != stat.S_IFMT(dirent.mode):
                    mismatched_mode += [(dirent_path, dirent_stat.st_mode,
                                         dirent.mode)]

    if inaccessible_inodes != []:
        tracker.add_problem(
            MaterializedInodesAreInaccessible(inaccessible_inodes))

    if mismatched_mode != []:
        tracker.add_problem(
            MaterializedInodesHaveDifferentModeOnDisk(mismatched_mode))
Esempio n. 13
0
def check_shared_path(tracker: ProblemTracker, mount_path: Path) -> None:
    shared_path = get_shared_path(mount_path)
    try:
        dst_shared_path = read_shared_path(tracker, shared_path)
    except Exception:
        return

    if is_nfs_mounted(dst_shared_path):
        msg = (
            f"The Mercurial data directory for {shared_path} is at"
            f" {dst_shared_path} which is on a NFS filesystem."
            f" Accessing files and directories in this repository will be slow."
        )
        problem = Problem(msg, severity=ProblemSeverity.ADVICE)
        tracker.add_problem(problem)
Esempio n. 14
0
def check_loaded_content(
    tracker: ProblemTracker,
    instance: EdenInstance,
    checkout: EdenCheckout,
    query_prjfs_file: Callable[[Path], PRJ_FILE_STATE],
) -> None:
    with instance.get_thrift_client_legacy() as client:
        loaded = client.debugInodeStatus(
            bytes(checkout.path),
            b"",
            flags=DIS_REQUIRE_LOADED,
            sync=SyncBehavior(),
        )

        errors = []
        for loaded_dir in loaded:
            path = Path(os.fsdecode(loaded_dir.path))

            for dirent in loaded_dir.entries:
                if not stat.S_ISREG(dirent.mode) or dirent.materialized:
                    continue

                dirent_path = path / Path(os.fsdecode(dirent.name))
                filestate = query_prjfs_file(checkout.path / dirent_path)
                if (filestate & PRJ_FILE_STATE.HydratedPlaceholder !=
                        PRJ_FILE_STATE.HydratedPlaceholder):
                    # We should only compute the sha1 of files that have been read.
                    continue

                def compute_file_sha1(file: Path) -> bytes:
                    hasher = hashlib.sha1()
                    with open(checkout.path / dirent_path, "rb") as f:
                        while True:
                            buf = f.read(1024 * 1024)
                            if buf == b"":
                                break
                            hasher.update(buf)
                    return hasher.digest()

                sha1 = client.getSHA1(bytes(checkout.path),
                                      [bytes(dirent_path)],
                                      sync=SyncBehavior())[0].get_sha1()
                on_disk_sha1 = compute_file_sha1(checkout.path / dirent_path)
                if sha1 != on_disk_sha1:
                    errors += [(dirent_path, sha1, on_disk_sha1)]

        if errors != []:
            tracker.add_problem(LoadedFileHasDifferentContentOnDisk(errors))
Esempio n. 15
0
def check_eden_directory(tracker: ProblemTracker,
                         instance: EdenInstance) -> None:
    if not is_nfs_mounted(str(instance.state_dir)):
        return

    msg = (
        f"Eden's state directory is on an NFS file system: {instance.state_dir}\n"
        f"  This will likely cause performance problems and/or other errors.")

    # On FB devservers the default Eden state directory path is ~/local/.eden
    # Normally ~/local is expected to be a symlink to local disk (for users who are
    # still using NFS home directories in the first place).  The most common cause of
    # the Eden state directory being on NFS is for users that somehow have a regular
    # directory at ~/local rather than a symlink.  Suggest checking this as a
    # remediation.
    remediation = (
        "The most common cause for this is if your ~/local symlink does not point "
        "to local disk.  Make sure that ~/local is a symlink pointing to local disk "
        "and then restart Eden.")
    tracker.add_problem(Problem(msg, remediation))
Esempio n. 16
0
def check_hg(tracker: ProblemTracker, checkout: EdenCheckout) -> None:
    file_checker_classes: List[Type[HgChecker]] = [
        DirstateChecker,
        HgrcChecker,
        RequiresChecker,
        SharedPathChecker,
        SharedChecker,
        BookmarksChecker,
        BranchChecker,
    ]
    # `AbandonedTransactionChecker` is looking for the existence of the journal
    # file as indicator of a potential problem. The rest is check if files are
    # missing.
    other_checker_classes: List[Type[HgChecker]] = [
        AbandonedTransactionChecker
    ]

    # pyre-fixme[45]: Cannot instantiate abstract class `HgChecker`.
    file_checkers = [
        checker_class(checkout) for checker_class in file_checker_classes
    ]
    checkers = file_checkers + [
        # pyre-fixme[45]: Cannot instantiate abstract class `HgChecker`.
        checker_class(checkout) for checker_class in other_checker_classes
    ]

    hg_path = checkout.path / ".hg"
    if not os.path.exists(hg_path):
        description = f"Missing hg directory: {checkout.path}/.hg"
        tracker.add_problem(HgDirectoryError(checkout, checkers, description))
        return

    check_in_progress_checkout(tracker, checkout)

    bad_checkers: List[HgChecker] = []
    for checker in checkers:
        try:
            if checker.check():
                continue
            bad_checkers.append(checker)
        except Exception:
            tracker.add_problem(UnexpectedCheckError())

    if bad_checkers:
        # if all the file checkers fail, it indicates we are seeing an empty
        # `.hg` directory
        msg = (f"No contents present in hg directory: {checkout.path}/.hg"
               if len(bad_checkers) == len(file_checkers) else None)
        tracker.add_problem(HgDirectoryError(checkout, bad_checkers, msg))
Esempio n. 17
0
def check_nuclide_subscriptions(tracker: ProblemTracker, path: str,
                                info: WatchmanCheckInfo) -> None:
    if info.nuclide_roots is None:
        return

    # Note that nuclide_roots is a set, but each entry in the set
    # could appear as a root folder multiple times if the user uses multiple
    # Atom windows.
    path_prefix = path + "/"
    connected_nuclide_roots = [
        nuclide_root for nuclide_root in info.nuclide_roots
        if path == nuclide_root or nuclide_root.startswith(path_prefix)
    ]
    if not connected_nuclide_roots:
        # There do not appear to be any Nuclide connections for path.
        return

    subscriptions = _call_watchman(["debug-get-subscriptions", path])
    subscribers = subscriptions.get("subscribers", [])
    subscription_counts: Dict[str, int] = {}
    for subscriber in subscribers:
        subscriber_info = subscriber.get("info", {})
        name = subscriber_info.get("name")
        if name is None:
            continue
        elif name in subscription_counts:
            subscription_counts[name] += 1
        else:
            subscription_counts[name] = 1

    missing_or_duplicate_subscriptions = []
    for nuclide_root in connected_nuclide_roots:
        filewatcher_subscription = f"filewatcher-{nuclide_root}"
        # Note that even if the user has `nuclide_root` opened in multiple
        # Nuclide windows, the Nuclide server should not create the
        # "filewatcher-" subscription multiple times.
        if subscription_counts.get(filewatcher_subscription) != 1:
            missing_or_duplicate_subscriptions.append(filewatcher_subscription)

    # Today, Nuclide creates a number of Watchman subscriptions per root
    # folder that is under an Hg working copy. (It should probably
    # consolidate these subscriptions, though it will take some work to
    # refactor things to do that.) Because each of connected_nuclide_roots
    # is a root folder in at least one Atom window, there must be at least
    # as many instances of each subscription as there are
    # connected_nuclide_roots.
    #
    # TODO(mbolin): Come up with a more stable contract than including a
    # hardcoded list of Nuclide subscription names in here because Eden and
    # Nuclide releases are not synced. This is admittedly a stopgap measure:
    # the primary objective is to figure out how Eden/Nuclide gets into
    # this state to begin with and prevent it.
    #
    # Further, Nuclide should probably rename these subscriptions so that:
    # (1) It is clear that Nuclide is the one who created the subscription.
    # (2) The subscription can be ascribed to an individual Nuclide client
    #     if we are going to continue to create the same subscription
    #     multiple times.
    num_roots = len(connected_nuclide_roots)
    for hg_subscription in NUCLIDE_HG_SUBSCRIPTIONS:
        if subscription_counts.get(hg_subscription, 0) < num_roots:
            missing_or_duplicate_subscriptions.append(hg_subscription)

    if missing_or_duplicate_subscriptions:

        def format_paths(paths: List[str]) -> str:
            return "\n  ".join(paths)

        missing_subscriptions = [
            sub for sub in missing_or_duplicate_subscriptions
            if 0 == subscription_counts.get(sub, 0)
        ]
        duplicate_subscriptions = [
            sub for sub in missing_or_duplicate_subscriptions
            if 1 < subscription_counts.get(sub, 0)
        ]

        output = io.StringIO()
        output.write(
            "Nuclide appears to be used to edit the following directories\n"
            f"under {path}:\n\n"
            f"  {format_paths(connected_nuclide_roots)}\n\n")
        if missing_subscriptions:
            output.write(
                "but the following Watchman subscriptions appear to be missing:\n\n"
                f"  {format_paths(missing_subscriptions)}\n\n")
        if duplicate_subscriptions:
            conj = "and" if missing_subscriptions else "but"
            output.write(
                f"{conj} the following Watchman subscriptions have duplicates:\n\n"
                f"  {format_paths(duplicate_subscriptions)}\n\n")
        output.write(
            "This can cause file changes to fail to show up in Nuclide.\n"
            "Currently, the only workaround for this is to run\n"
            '"Nuclide Remote Projects: Kill And Restart" from the\n'
            "command palette in Atom.\n")
        tracker.add_problem(Problem(output.getvalue()))
Esempio n. 18
0
def check_for_stale_mounts(tracker: ProblemTracker,
                           mount_table: mtab.MountTable) -> None:
    stale_mounts = get_all_stale_eden_mount_points(mount_table)
    if stale_mounts:
        tracker.add_problem(StaleMountsFound(stale_mounts, mount_table))
Esempio n. 19
0
def check_eden_directory(tracker: ProblemTracker,
                         instance: EdenInstance) -> None:
    if not is_nfs_mounted(str(instance.state_dir)):
        return

    tracker.add_problem(StateDirOnNFS(instance))