root/usr/src/lib/libproc/common/Pfdinfo.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2012 DEY Storage Systems, Inc.  All rights reserved.
 */
/*
 * Copyright (c) 2013 Joyent, Inc.  All Rights reserved.
 * Copyright 2020 OmniOS Community Edition (OmniOSce) Association.
 * Copyright 2021 Oxide Computer Company
 */

#include <limits.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>
#include <ctype.h>
#include <string.h>
#include <stddef.h>
#include <sys/mkdev.h>

#include "libproc.h"
#include "Pcontrol.h"
#include "proc_fd.h"

/*
 * Pfdinfo.c - obtain open file information.
 */

void
Pinitfd(struct ps_prochandle *P)
{
        list_create(&P->fd_head, sizeof (fd_info_t),
            offsetof(fd_info_t, fd_list));
}

/*
 * Allocate an fd_info structure and stick it on the list.
 * (Unless one already exists.)  The list is sorted in
 * reverse order.  We will traverse it in that order later.
 * This makes the usual ordered insert *fast*.
 */
fd_info_t *
Pfd2info(struct ps_prochandle *P, int fd)
{
        fd_info_t       *fip, *next = NULL;

        for (fip = list_head(&P->fd_head); fip != NULL;
            fip = list_next(&P->fd_head, fip)) {
                if (fip->fd_info == NULL)
                        continue;

                if (fip->fd_info->pr_fd == fd) {
                        return (fip);
                }
                if (fip->fd_info->pr_fd < fd) {
                        break;
                }
        }

        next = fip;
        if ((fip = calloc(1, sizeof (*fip))) == NULL)
                return (NULL);

        list_insert_before(&P->fd_head, next, fip);
        return (fip);
}

static int
fdwalk_cb(const prfdinfo_t *info, void *arg)
{
        struct ps_prochandle *P = arg;
        fd_info_t *fip;

        fip = Pfd2info(P, info->pr_fd);
        if (fip == NULL) {
                errno = ENOMEM;
                return (-1);
        }

        if (fip->fd_info == NULL)
                fip->fd_info = proc_fdinfo_dup(info);

        if (fip->fd_info == NULL) {
                errno = ENOMEM;
                return (-1);
        }

        return (0);
}

/*
 * Attempt to load the open file information from a live process.
 */
static void
load_fdinfo(struct ps_prochandle *P)
{
        /*
         * In the unlikely case there are *no* file descriptors open,
         * we will keep rescanning the proc directory, which will be empty.
         * This is an edge case it isn't worth adding additional state to
         * to eliminate.
         */
        if (!list_is_empty(&P->fd_head))
                return;

        if (P->state == PS_DEAD || P->state == PS_IDLE)
                return;

        proc_fdwalk(P->pid, fdwalk_cb, P);
}

int
Pfdinfo_iter(struct ps_prochandle *P, proc_fdinfo_f *func, void *cd)
{
        fd_info_t *fip;
        int rv;

        /* Make sure we have live data, if appropriate */
        load_fdinfo(P);

        /* NB: We walk the list backwards. */

        for (fip = list_tail(&P->fd_head); fip != NULL;
            fip = list_prev(&P->fd_head, fip)) {
                if ((rv = func(cd, fip->fd_info)) != 0)
                        return (rv);
        }
        return (0);
}