/*
**  Portably determine, set or check the limit on open file descriptors.
**
**  Pretty much all platforms these days have getrlimit and setrlimit, so
**  prefer those, but for determining the current limit preserve the old
**  portability (if we don't have getrlimit, try sysconf, then
**  getdtablesize, then ulimit, and then try to find a hard-coded constant
**  in <sys/param.h> and failing that fall back to the POSIX-guaranteed
**  minimum of 20).
**
**  For setting the limit, only setrlimit is supported; if it isn't
**  available, return -1 always.  We also refuse to set the limit to
**  something higher than select can handle, checking against FD_SETSIZE.
**
**  Note that on some versions of Linux (2.2.x reported), sysconf may return
**  the wrong value for the maximum file descriptors.  getrlimit is correct,
**  so always prefer it.
**
**  Written by Russ Allbery in 2000.
**  Various bug fixes, code and documentation improvements since then
**  in 2000, 2001, 2006, 2011, 2014, 2021, 2023, 2025.
*/

#include "portable/system.h"

#include <errno.h>
#if HAVE_SYS_SELECT_H
#    include <sys/select.h>
#endif

/* FreeBSD 3.4 RELEASE needs <sys/time.h> before <sys/resource.h>. */
#if HAVE_GETRLIMIT || HAVE_SETRLIMIT
#    if HAVE_SYS_TIME_H
#        include <sys/time.h>
#    endif
#    include <sys/resource.h>
#    include <time.h>
#endif

#include "inn/libinn.h"

#if HAVE_SETRLIMIT && defined(RLIMIT_NOFILE)

int
setfdlimit(unsigned int limit)
{
    struct rlimit rl;

#    ifdef FD_SETSIZE
    if (limit > FD_SETSIZE) {
        errno = EINVAL;
        return -1;
    }
#    endif

    rl.rlim_cur = 0;
    rl.rlim_max = 0;

#    if HAVE_GETRLIMIT
    if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
        rl.rlim_cur = 0;
        rl.rlim_max = 0;
    }
#    endif

    rl.rlim_cur = limit;
    if (limit > rl.rlim_max)
        rl.rlim_max = limit;
    return setrlimit(RLIMIT_NOFILE, &rl);
}

#else /* !(HAVE_SETRLIMIT && RLIMIT_NOFILE) */

int
setfdlimit(unsigned int limit UNUSED)
{
    /* Unimplemented system call is close enough. */
    errno = ENOSYS;
    return -1;
}

#endif /* !(HAVE_SETRLIMIT && RLIMIT_NOFILE) */

#if HAVE_GETRLIMIT && defined(RLIMIT_NOFILE)

int
getfdlimit(void)
{
    struct rlimit rl;

    if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
        return -1;
    return rl.rlim_cur;
}

#elif HAVE_SYSCONF

int
getfdlimit(void)
{
    return sysconf(_SC_OPEN_MAX);
}

#elif HAVE_GETDTABLESIZE

int
getfdlimit(void)
{
    return getdtablesize();
}

#elif HAVE_ULIMIT

int
getfdlimit(void)
{
#    ifdef UL_GDESLIM
    return ulimit(UL_GDESLIM, 0);
#    else
    return ulimit(4, 0);
#    endif
}

#else /* no function mechanism available */
#    include <limits.h>
#    include <sys/param.h>

int
getfdlimit(void)
{
#    ifdef NOFILE
    return NOFILE;
#    else
    return 20;
#    endif
}

#endif

/*
 * Return true if fd is not higher than the system supports,
 * and false otherwise.
 */
bool
isvalidfd(unsigned int fd)
{
#if defined(FD_SETSIZE)
    if (fd >= FD_SETSIZE) {
        return false;
    }
#else
    if (fd >= (sizeof(fd_set) * CHAR_BIT)) {
        return false;
    }
#endif
    return true;
}
