// SPDX-License-Identifier: GPL-3.0-or-later

#include "../libnetdata.h"

static int fd_is_valid(int fd) {
    errno_clear();
    return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}

static void setcloexec(int fd) {
    int flags = fcntl(fd, F_GETFD);
    if (flags != -1)
        (void) fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
}

int os_get_fd_open_max(void) {
    static int fd_open_max = CLOSE_RANGE_FD_MAX;

    if(fd_open_max != CLOSE_RANGE_FD_MAX)
        return fd_open_max;

    if(fd_open_max == CLOSE_RANGE_FD_MAX || fd_open_max == -1) {
        struct rlimit rl;
        if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_max != RLIM_INFINITY)
            fd_open_max = rl.rlim_max;
    }

#ifdef _SC_OPEN_MAX
    if(fd_open_max == CLOSE_RANGE_FD_MAX || fd_open_max == -1) {
        fd_open_max = sysconf(_SC_OPEN_MAX);
    }
#endif

    if(fd_open_max == CLOSE_RANGE_FD_MAX || fd_open_max == -1) {
        // Arbitrary default if everything else fails
        fd_open_max = 65535;
    }

    return fd_open_max;
}

void os_close_range(int first, int last, int flags) {
#if defined(HAVE_CLOSE_RANGE)
    if(close_range(first, last, flags) == 0) return;
#endif

#if defined(OS_LINUX)
    DIR *dir = opendir("/proc/self/fd");
    if (dir != NULL) {
        struct dirent *entry;
        while ((entry = readdir(dir)) != NULL) {
            int fd = str2i(entry->d_name);
            if (fd >= first && (last == CLOSE_RANGE_FD_MAX || fd <= last) && fd_is_valid(fd)) {
                if(flags & CLOSE_RANGE_CLOEXEC)
                    setcloexec(fd);
                else
                    (void)close(fd);
            }
        }
        closedir(dir);
        return;
    }
#endif

    // Fallback to looping through all file descriptors if necessary
    if (last == CLOSE_RANGE_FD_MAX)
        last = os_get_fd_open_max();

    for (int fd = first; fd <= last; fd++) {
        if (fd_is_valid(fd)) {
            if(flags & CLOSE_RANGE_CLOEXEC)
                setcloexec(fd);
            else
                (void)close(fd);
        }
    }
}

static int compare_ints(const void *a, const void *b) {
    int int_a = *((int*)a);
    int int_b = *((int*)b);
    return (int_a > int_b) - (int_a < int_b);
}

void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num, int flags) {
    if (fds_num == 0 || fds == NULL) {
        os_close_range(STDERR_FILENO + 1, CLOSE_RANGE_FD_MAX, flags);
        return;
    }

    // copy the fds array to ensure we will not alter them
    int *fds_copy = mallocz(fds_num * sizeof(*fds_copy));
    memcpy(fds_copy, fds, fds_num * sizeof(*fds_copy));

    qsort(fds_copy, fds_num, sizeof(int), compare_ints);

    int start = STDERR_FILENO + 1;
    size_t i = 0;

    // filter out all fds with a number smaller than our start
    for (; i < fds_num; i++)
        if(fds_copy[i] >= start) break;

    // call os_close_range() as many times as needed
    for (; i < fds_num; i++) {
        if (fds_copy[i] > start)
            os_close_range(start, fds_copy[i] - 1, flags);

        start = fds_copy[i] + 1;
    }

    os_close_range(start, CLOSE_RANGE_FD_MAX, flags);
    freez(fds_copy);
}
