/* -*- mode: c++; indent-tabs-mode: nil -*- */
/*
    ql_lib.qpp

    Qore Programming Language

    Copyright (C) 2003 - 2023 Qore Technologies, s.r.o.

    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    Note that the Qore library is released under a choice of three open-source
    licenses: MIT (as above), LGPL 2+, or GPL 2+; see README-LICENSE for more
    information.
*/

#include <qore/Qore.h>
#include "qore/intern/ql_lib.h"
#include "qore/intern/ExecArgList.h"
#include "qore/intern/QoreSignal.h"
#include "qore/intern/QoreHashNodeIntern.h"
#include <qore/minitest.hpp>

#include <cerrno>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/types.h>
#include <unistd.h>

#ifdef HAVE_GRP_H
#include <grp.h>
#endif

#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif

#ifdef HAVE_GLOB_H
#include <glob.h>
#else
#include "qore/intern/qore_glob.h"
#endif

#ifdef HAVE_GETIFADDRS
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#include <ifaddrs.h>
#include <sys/socket.h>
#include <sys/types.h>
#ifdef AF_LINK
#include <net/if_dl.h>
#endif
#ifdef AF_PACKET
#include <linux/if_packet.h>
#endif
#endif

#ifndef SIGHUP
#define SIGHUP 0
#endif

#define MAX_GIDS 100

extern bool threads_initialized;

#ifdef DEBUG_TESTS
// Unsorted unit tests are put here
#  include "tests/ReferenceHolder_tests.cpp"
#endif

AbstractQoreNode* missing_function_error(const char* func, ExceptionSink* xsink) {
   QoreString have(func);
   have.toupr();
   return xsink->raiseException("MISSING-FEATURE-ERROR", "this system does not implement %s(); for maximum portability use the constant Option::HAVE_%s to check if this function is implemented before calling", func, have.getBuffer());
}

AbstractQoreNode* missing_function_error(const char* func, const char* opt, ExceptionSink* xsink) {
   return xsink->raiseException("MISSING-FEATURE-ERROR", "this system does not implement %s(); for maximum portability use the constant Option::HAVE_%s to check if this function is implemented before calling", func, opt);
}

#ifdef DEBUG_X
static AbstractQoreNode* runQoreTests(const QoreListNode* args, ExceptionSink* xsink) {
   minitest::result res = minitest::execute_all_tests();
   if (res.all_tests_succeeded) {
      printf("Qore runtime: %d tests succeeded\n", res.sucessful_tests_count);
      return 0;
   }

   return xsink->raiseException("A Qore test failed", "Qore test in file %s, line %d threw an exception.",
                                res.failed_test_file, res.failed_test_line);
}

static AbstractQoreNode* runRecentQoreTests(const QoreListNode* args, ExceptionSink* xsink) {
   minitest::result res = minitest::test_last_changed_files(3); // 3 last modified files
   if (res.all_tests_succeeded) {
      printf("Qore runtime: %d recent tests succeeded\n", res.sucessful_tests_count);
      return 0;
   }

   return xsink->raiseException("A Qore test failed", "Qore test in file %s, line %d threw an exception.",
                                res.failed_test_file, res.failed_test_line);
}
namespace {
TEST() {
  // just an example of empty test
}
}
#endif

/** @defgroup library_option_constants Qore Library Option Constants

    @since %Qore 0.9.3.1
*/
///@{
//! disable qore signal handling entirely (init only)
const QLO_DISABLE_SIGNAL_HANDLING = QLO_DISABLE_SIGNAL_HANDLING;

//! do not initialize the openssl library (= is initialized before the qore library is initialized; init only)
const QLO_DISABLE_OPENSSL_INIT = QLO_DISABLE_OPENSSL_INIT;

//! do not perform cleanup on the openssl library (= is cleaned up manually; init only)
const QLO_DISABLE_OPENSSL_CLEANUP = QLO_DISABLE_OPENSSL_CLEANUP;

//! disable garbage collection / recursive object reference detection (init only)
const QLO_DISABLE_GARBAGE_COLLECTION = QLO_DISABLE_GARBAGE_COLLECTION;

//! disable seeding the random number generator when the Qore library is initialized (init only)
const QLO_DO_NOT_SEED_RNG = QLO_DO_NOT_SEED_RNG;

//! disable TLS v1.3 (may be set at runtime with qore_set_library_options())
/** conflicts with @ref QLO_MINIMUM_TLS_13
*/
const QLO_DISABLE_TLS_13 = QLO_DISABLE_TLS_13;

//! enable minimum TLS v1.3 (may be set at runtime with qore_set_library_options())
/** conflicts with and overrides @ref QLO_DISABLE_TLS_13
*/
const QLO_MINIMUM_TLS_13 = QLO_MINIMUM_TLS_13;

//! bitmask for library options that can be set at runtime
/** the following options can be set at runtime:
    - @ref Qore::QLO_DISABLE_TLS_13
*/
const QLO_RUNTIME_OPTS = QLO_RUNTIME_OPTS;
///@}

//! network interface info hash
/** @since %Qore 0.9.0
*/
hashdecl NetIfInfo {
    //! interface name
    string interface;

    //! interface address if available
    *string address;

    //! interface address description (if the address is available)
    *string address_desc;

    //! interface family type; see @ref network_address_family_constants for possible values
    int family;

    //! interface family type string
    string familystr;
}

//! exception information hash
/** @since %Qore 0.8.13
*/
hashdecl ExceptionInfo {
    //! exception type: \c "User" or \c "System"; see @ref exception_type_constants
    string type;

    //! position info: the parse label where exception occurred; this is normally the file name; this corresponds to the \a label parameter of the @ref Qore::Program::parse() "Program::parse()" and @ref Qore::Program::parsePending() "Program::parsePending()" methods and the @ref Qore::parse() function, for example
    string file;

    //! position info: start line number
    int line;

    //! position info: end line number
    int endline;

    //! position info: an optional source string; if multiple sections of a file were parsed with different parse labels, then the source file name will normally go here and the "file" key will have the parse label; this corresponds to the source parameter of the Program::parse() and Program::parsePending() methods, for example (if known, for user code only)
    string source;

    //! position info: line number offset for the \c "source" key (if known, for user code only)
    int offset;

    //! call stack backtrace information
    list<hash<CallStackInfo>> callstack;

    //! this key is populated with the value of the first expression of the @ref throw "throw statement". For system exceptions, this is a string giving the exception code.
    auto err;

    //! this key is populated with the value of the second expression of the @ref throw "throw statement" (if a list was thrown). For system exceptions, this is a string giving a text description of the error.
    auto desc;

    //! this key is populated with the value of the third expression of the @ref throw "throw statement" (if a list was thrown). For system exceptions, this is populated for some exceptions where additional information is provided.
    auto arg;

    //! the language of the source code location; normally \c "Qore"
    string lang;

    //! chained exception info
    hash<ExceptionInfo> next;
}

/** @defgroup library_functions Library Functions
    Library functions

    @section host_information_hash Host Information Hash
    |!Key|!Type|!Description
    |\c name|@ref string_type|The official fully-qualified name of the host
    |\c aliases|@ref list_type of @ref string_type|Any hostname aliases for the host
    |\c typename|@ref string_type|The type of network address (either \c "ipv4" or \c "ipv6")
    |\c type|@ref int_type|One of the @ref network_address_family_constants (either Qore::AF_INET or Qore::AF_INET6) corresponding to the type of network addresses given
    |\c len|@ref int_type|The length of the addresses in bytes when represented in binary form
    |\c addresses|@ref list_type of @ref string_type|All addresses corresponding to the host; the list should have at least 1 element

    @section address_information_hash Address Information Hash
    |!Key|!Type|!Description
    |\c address|@ref string_type|A valid address of the host, for example: \c "::1".
    |\c address_desc|@ref string_type|A descriptive string of the address containing the address family, for example: \c "ipv6[::1]"
    |\c family|@ref int_type|The network address family; see Network Address Family Constants.
    |\c familystr|@ref string_type|A descriptive string for the network address family, for example: \c "ipv6".
    |\c addrlen|@ref int_type|The length of the internal network address data structure (not normally needed in %Qore but provided anyway)
    |[\c port]|@ref int_type|The port number corresponding to the service (if applicable)
 */
///@{
//! Exits the program with the return code passed (this function does not return)
/** @param rc the return code for the process (0 = no error; success)

    @par Example:
    @code{.py}
exit(0);
    @endcode

    @see abort()
 */
nothing exit(softint rc = 0) [dom=PROCESS] {
   qore_exit_process(rc);
}

//! Aborts the current program (this function does not return)
/** This function causes the current process to terminate abnormally; a core dump or crash report may be generated if enabled

    @par Example:
    @code{.py}
abort();
    @endcode

    @see exit()
 */
nothing abort() [dom=PROCESS] {
   threads_initialized = false;
   abort();
}

//! Replaces the current process image with another; this function does not return
/** @param command the executable to execute and any command-line arguments; the command is executed internally by a call to execvp() (3)

    @par Example:
    @code{.py}
exec("/usr/bin/xterm -bg black -fg white -sb -sl 2000");
    @endcode
 */
nothing exec(string command) [dom=EXTERNAL_PROCESS,PROCESS] {
   ExecArgList exec_args(command->getBuffer());
   execvp(exec_args.getFile(), exec_args.getArgs());

   xsink->raiseErrnoException("EXEC-ERROR", errno, "execvp() failed in child process for target '%s'", exec_args.getFile());
}

//! executes a command and returns the exit code of the process
/** @par Platform Availability:
    @ref Qore::Option::HAVE_SYSTEM

    @param command the command to execute with %system() (3)

    @return the exit code of the process executed

    @par Example:
    @code{.py}
int rc = system("ls -l");
    @endcode

    @see
    - backquote()
    - @ref backquote_operator "the backquote operator"
 */
int system(string command) [dom=EXTERNAL_PROCESS] {
#if defined(HAVE_FORK) && defined(HAVE_SIGNAL_HANDLING)
   // on platforms with fork(2) and signal handling, we need to fork, enable all signals, then execvp()
   pid_t pid;
   if (!(pid = fork())) {
      // enable all signals
      sigset_t mask;
      sigemptyset(&mask);
      sigprocmask(SIG_SETMASK, &mask, 0);

      // exec pgm like system(): sh -c "command"
      execl("/bin/sh", "sh", "-c", command->getBuffer(), NULL);
      fprintf(stderr, "execvp() failed in child process for target '/bin/sh' with error code %d: %s\n", errno, strerror(errno));
      _Exit(-1);
      //qore_exit_process(-1);
   }
   if (pid == -1)
      return -1;

   // wait for child to exit
   int status;
   wait(&status);
   return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
#elif defined(HAVE_SYSTEM)
   return system(command->getBuffer());
#else
   return missing_function_error("system", xsink);
#endif
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing system() [flags=RUNTIME_NOOP;dom=EXTERNAL_PROCESS] {
}

//! Returns the real user ID of the current process
/** @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @return the real user ID of the current process

    @par Example:
    @code{.py}
int uid = getuid();
    @endcode
 */
int getuid() [dom=EXTERNAL_INFO] {
#ifdef HAVE_GETUID
   return getuid();
#else
   return missing_function_error("getuid", "UNIX_USERMGT", xsink);
#endif
}

//! Returns the effective user ID of the current process
/** @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @return the effective user ID of the current process

    @par Example:
    @code{.py}
int euid = geteuid();
    @endcode
 */
int geteuid() [dom=EXTERNAL_INFO] {
#ifdef HAVE_GETEUID
   return getuid();
#else
   return missing_function_error("geteuid", "UNIX_USERMGT", xsink);
#endif
}

//! Returns the real group ID of the current process
/** @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @return the real group ID of the current process

    @par Example:
    @code{.py}
int gid = getgid();
    @endcode
 */
int getgid() [dom=EXTERNAL_INFO] {
#ifdef HAVE_GETGID
   return getgid();
#else
   return missing_function_error("getgid", "UNIX_USERMGT", xsink);
#endif
}

//! Returns the effective group ID of the current process
/** @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @return the effective group ID of the current process

    @par Example:
    @code{.py}
int egid = getegid();
    @endcode
 */
int getegid() [dom=EXTERNAL_INFO] {
#ifdef HAVE_GETEGID
   return getgid();
#else
   return missing_function_error("getegid", "UNIX_USERMGT", xsink);
#endif
}

//! returns a list of group IDs that the user is a member of
/** @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @par Example:
    @code{.py}
list<int> l = getgroups();
    @endcode

    @return a list of group IDs that the user is a member of

    @since %Qore 0.8.11.1
*/
list<int> getgroups() [dom=EXTERNAL_INFO] {
#ifdef HAVE_GETGROUPS
   gid_t l[MAX_GIDS];

   int n = getgroups(MAX_GIDS, l);
   if (n == -1)
      return xsink->raiseErrnoException("GETGROUPS-ERROR", errno, "error calling getgroups()");

   QoreListNode* rv = new QoreListNode(bigIntTypeInfo);
   for (int i = 0; i < n; ++i) {
      rv->push(l[i], xsink);
   }
   return rv;
#else
   missing_function_error("getgroups", "UNIX_USERMGT", xsink);
#endif
}

//! sets the list of supplementary group IDs for the current process
/** @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @par Example:
    @code{.py}
setgroups(l);
    @endcode

    @param gids a list of supplementary group IDs for the current process

    @since %Qore 0.8.11.1
*/
nothing setgroups(softlist<softint> gids) [dom=PROCESS] {
#ifdef HAVE_SETGROUPS
    if (gids->empty())
        return QoreValue();

#ifdef HAVE_LOCAL_VARIADIC_ARRAYS_X
    gid_t l[gids->size()];
#else
    gid_t* l = (gid_t*)malloc(sizeof(gid_t) * gids->size());
    if (!l) {
        xsink->outOfMemory();
        return QoreValue();
    }
    ON_BLOCK_EXIT(free, l);
#endif
    for (size_t i = 0, e = gids->size(); i < e; ++i) {
        l[i] = (int)gids->retrieveEntry(i).getAsBigInt();
    }
    if (setgroups(gids->size(), l))
        xsink->raiseErrnoException("SETGROUPS-ERROR", errno, "error calling setgroups()");
#else
    missing_function_error("setgroups", "UNIX_USERMGT", xsink);
#endif
}

//! Causes the current thread to sleep for a certain number of seconds
/** @param seconds The amount of time in seconds to sleep; integer arguments are interpreted literally as a number of seconds to sleep, however a @ref relative_dates "relative date/time value" can be given instead of an integer to make the source more readable (ex: \c 5s), however as this function only supports a resolution of 1 second, milliseconds and microseconds are ignored if passed in a @ref relative_dates "relative date/time value"

    @return should always return 0 for success

    @par Example:
    @code{.py}
sleep(10s);
    @endcode

    @see usleep() for a similar function supporting microsecond resolution
 */
int sleep(softint seconds) [dom=PROCESS] {
   if (!seconds)
      return 0;

   return qore_usleep(seconds * 1000000ll);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing sleep() [flags=RUNTIME_NOOP;dom=PROCESS] {
}

//! Causes the current thread to sleep for a certain number of microseconds
/** @param usecs The amount of time in microseconds to sleep; integer arguments are interpreted literally as a number of microseconds to sleep

    @return should always return 0 for success

    @par Example:
    @code{.py}
usleep(5000);
    @endcode

    @see sleep() for a similar function supporting second resolution

    @note both sleep() and usleep() are implemented internally by calls to nanosleep() (2) if available on the current platform, otherwise both are implemented by calls to usleep() (3)
 */
int usleep(softint usecs) [dom=PROCESS] {
   if (!usecs)
      return 0;

   return qore_usleep(usecs);
}

//! Causes the current thread to sleep for a certain number of microseconds
/** @param d A time value giving the amount of time in microseconds to sleep; this should be a @ref relative_dates "relative date/time value" to make the source more readable (ex: \c 250ms)

    @return should always return 0 for success

    @par Example:
    @code{.py}
usleep(250ms);
    @endcode

    @see sleep() for a similar function supporting second resolution

    @note both sleep() and usleep() are implemented internally by calls to nanosleep() (2) if available on the current platform, otherwise both are implemented by calls to usleep() (3)
 */
int usleep(date d) [dom=PROCESS] {
   return qore_usleep(d->getRelativeMicroseconds());
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing usleep() [flags=RUNTIME_NOOP;dom=PROCESS] {
}

//! Returns the PID (process ID) of the current process
/** @return the PID (process ID) of the current process

    @par Example:
    @code{.py}
int pid = getpid();
    @endcode
 */
int getpid() [flags=RET_VALUE_ONLY;dom=EXTERNAL_INFO] {
   return getpid();
}

//! Returns the PID (process ID) of the parent process of the current process
/** @par Platform Availability:
    @ref Qore::Option::HAVE_GETPPID

    @return the PID (process ID) of the parent process of the current process

    @par Example:
    @code{.py}
int ppid = HAVE_GETPPID ? getppid() : -1;
    @endcode
 */
int getppid() [flags=RET_VALUE_ONLY;dom=EXTERNAL_INFO] {
#ifdef HAVE_GETPPID
   return getppid();
#else
   return missing_function_error("getppid", xsink);
#endif
}

//! Creates a copy of the current process with a new PID; returns 0 in the child process; returns the child's PID in the parent process
/** This function will throw an \c ILLEGAL-FORK exception if more than one thread is running

    @par Platform Availability:
    @ref Qore::Option::HAVE_FORK

    @return 0 in the child process; returns the child's PID in the parent process; if an error occurs, then -1 is returned, in this case no child process was started and the error number can be retrieved with the errno() function

    @par Example:
    @code{.py}
int pid;
if (pid = fork())
    printf("child has PID %d\n", pid);
    @endcode

    @throw ILLEGAL-FORK Cannot fork if more than one thread is running

    @note
    - after a fork(), the only safe call is @ref exec(), many system calls are not async-signal safe, including pthread_create() which is
      used to start new threads.
    - the %Qore process will crash if unsafe operations are called after a fork()
    - the signal-handling thread cannot be reliably started on many platforms (ex: FreeBSD) after a fork(), therefore signal handling
      is disabled in the child process after a fork()
    - on Darwin (OS/X) threading primitives are unusable in the child process after a fork()
 */
int fork() [dom=PROCESS] {
#ifdef HAVE_FORK
#ifdef HAVE_SIGNAL_HANDLING
   int QORE_THREAD_LIMIT = 1 + (QSM.thread_running ? 1 : 0);
#else
#define QORE_THREAD_LIMIT 1
#endif
   if (thread_list.getNumThreads() > (unsigned)(QORE_THREAD_LIMIT))
      return xsink->raiseException("ILLEGAL-FORK", "cannot fork() when other threads are running");

#ifdef HAVE_SIGNAL_HANDLING
   // we may not fork from within a signal handler
   if (QSM.thread_running && q_gettid() == QSM.q_gettid())
      return xsink->raiseException("ILLEGAL-FORK", "cannot fork() within a signal handler");

   // stop signal handling thread and make sure it can't be restarted until fork is done
   QSM.preFork();
#endif

   //printd(5, "stopped signal thread, about to fork pid %d\n", getpid()); fflush(stdout);
   int pid = fork();

#ifdef HAVE_SIGNAL_HANDLING
   // release signal handler lock
   QSM.postFork(!pid, xsink);
#endif

   return pid;
#else
   return missing_function_error("fork", xsink);
#endif
}

//! Sends a signal to a process, if no signal number is given, then Qore::SIGHUP is sent by default
/** @par Platform Availability:
    @ref Qore::Option::HAVE_KILL

    @param pid the pid argument can have the following interpretations:
    - <tt>\> 0</tt>: the signal is sent to the process ID given
    - \c 0: the signal is sent to all processes whose group ID is equal to the process group ID of the sender, and for which the process has permission
    - \c -1: if the user has super-user privileges, the signal is sent to all processes excluding system processes and the process sending the signal.  If the user is not the super user, the signal is sent to all processes with the same uid as the user, excluding the process sending the signal.  No error is returned if any process could be signaled.
    @param sig the @ref signal_constants "signal number" to send to the process or processes

    @return 0 means success (no error), or -1 meaning an error occurred; in this case check errno() for the error

    @par Example:
    @code{.py}
if (kill(pid, SIGTERM))
    printf("error sending signal to pid %d: %s\n", pid, strerror());
    @endcode
 */
int kill(softint pid, softint sig = SIGHUP) [dom=EXTERNAL_PROCESS] {
#ifdef HAVE_KILL
   return kill(pid, sig);
#else
   return missing_function_error("kill", xsink);
#endif
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing kill() [flags=RUNTIME_NOOP;dom=EXTERNAL_PROCESS] {
}

//! Returns a random non-negative 64-bit integer number
/** This function uses the C library function \c random() to generate the number if available on the current platform,
    otherwise uses \c %rand().

    @return a random non-negative 64-bit integer number

    @par Example:
    @code{.py}
int num = rand();
    @endcode

    @see srand() for a function to seed the random number generator

    @since %Qore 0.8.13 the random number generator is seeded randomly when the %Qore library is initialized
 */
int rand() [flags=CONSTANT] {
#ifdef HAVE_RANDOM
   // return a random 64-bit integer by calling random() twice
   return random() | (((int64)random()) << 32);
#else
   return rand() | (((int64)rand()) << 32);
#endif
}

//! Seeds the random number generator with the integer passed
/** This function uses the C library function srandom() if available on the current platform, otherwise uses %srand()

    @param seed the seed for the random number generator (only the least-significant 32-bits are used)

    @par Example:
    @code{.py}
srand(now());
    @endcode

    @see rand() for a function that returns a random number related to the seed given here

    @since %Qore 0.8.13 the random number generator is seeded randomly when the %Qore library is initialized
 */
nothing srand(softint seed) [dom=PROCESS] {
#ifdef HAVE_RANDOM
   srandom((int)seed);
#else
   srand((int)seed);
#endif
   return QoreValue();
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing srand() [flags=RUNTIME_NOOP;dom=PROCESS] {
}

//! Returns the hostname of the system
/** @return the hostname of the system

    @par Example:
    @code{.py}
string host = gethostname();
    @endcode
 */
string gethostname() [flags=CONSTANT;dom=EXTERNAL_INFO] {
   char buf[HOSTNAMEBUFSIZE + 1];

   if (gethostname(buf, HOSTNAMEBUFSIZE))
      return xsink->raiseErrnoException("GETHOSTNAME-ERROR", errno, "gethostname() failed");
   return new QoreStringNode(buf);
}

//! Returns the error code of the last error that occurred in the current thread
/** @return the error code of the last error that occurred in the current thread (see @ref error_constants for possible error code values)

    @par Example:
    @code{.py}
if (unlink(path))
    printf("%s: %s\n", path, strerror(errno()));
    @endcode

    @see strerror() for a function that gives the string description for the error number returned by this function
 */
int errno() {
   return errno;
}

//! Returns the string corresponding to the error code passed (generally retrieved with errno())
/** @param err the error code to retrieve the description for (generally provided by errno())

    @return the string corresponding to the error code passed; if the error code is unknown, then a string like \c "Unknown error: -1" is returned

    @par Example:
    @code{.py}
if (unlink(path))
    printf("%s: %s\n", path, strerror(errno()));
    @endcode
 */
string strerror(softint err) [flags=CONSTANT] {
   return q_strerror(err);
}

//! Returns the string corresponding to the last error that occurred in the current thread
/** @return the string corresponding to the last error that occurred in the current thread

    @par Example:
    @code{.py}
if (unlink(path))
    printf("%s: %s\n", path, strerror());
    @endcode

    @since %Qore 0.8.4 this variant of the function automatically uses the last error value for the current thread
 */
string strerror() [flags=CONSTANT] {
   return q_strerror(errno);
}

//! Returns a string giving the last element of a file path (meant to be the filename)
/** @param path the path to process

    @return a string giving the last element of the given file path (meant to be the filename)

    @par Example:
    @code{.py}
string fn = basename("/usr/local/bin/file_name");
    @endcode

    @see dirname()
 */
string basename(string path) [flags=CONSTANT] {
   char* p = q_basename(path->getBuffer());
   int len = strlen(p);
   return new QoreStringNode(p, len, len + 1, path->getEncoding());
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing basename() [flags=RUNTIME_NOOP] {
}

//! Returns a string giving the path up to a file but not the filename itself
/** @param path the path to process

    @return a string giving the path up to a file but not the filename itself; if no directory separator characters can be found in the path, \c "." is returned (meaning the current directory)

    @par Example:
    @code{.py}
string dir = dirname("/usr/local/bin/file_name");
    @endcode

    @see basename()
 */
string dirname(string path) [flags=CONSTANT] {
   char* p = q_dirname(path->getBuffer());
   int len = strlen(p);
   return new QoreStringNode(p, len, len + 1, path->getEncoding());
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing dirname() [flags=RUNTIME_NOOP] {
}

//! Changes the process user ID according to the argument passed
/** @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @param uid the user ID to set

    @return 0 if no error occurred; -1 if an error occurred, in which case errno() and/or strerror() can be used to retrieve the error

    @par Example:
    @code{.py}
if (setuid(uid))
    printf("setuid %d: %s\n", uid, strerror());
    @endcode
 */
int setuid(softint uid) [dom=PROCESS] {
#ifdef HAVE_SETUID
   return setuid((int)uid);
#else
   return missing_function_error("setuid", "UNIX_USERMGT", xsink);
#endif
}

//! Changes the effective process user ID according to the argument passed
/** @par Platform Availability:
    @ref Qore::Option::HAVE_SETEUID

    @param uid the user ID to set

    @return 0 if no error occurred; -1 if an error occurred, in which case errno() and/or strerror() can be used to retrieve the error

    @par Example:
    @code{.py}
if (seteuid(uid))
    printf("seteuid %d: %s\n", uid, strerror());
    @endcode
 */
int seteuid(softint uid) [dom=PROCESS] {
#ifdef HAVE_SETEUID
   return seteuid((int)uid);
#else
   return missing_function_error("seteuid", xsink);
#endif
}

//! Changes the process group ID according to the argument passed
/** @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @param gid the group ID to set

    @return 0 if no error occurred; -1 if an error occurred, in which case errno() and/or strerror() can be used to retrieve the error

    @par Example:
    @code{.py}
if (setgid(gid))
    printf("setgid %d: %s\n", gid, strerror());
    @endcode
 */
int setgid(softint gid) [dom=PROCESS] {
#ifdef HAVE_SETGID
   return setgid((int)gid);
#else
   return missing_function_error("setgid", "UNIX_USERMGT", xsink);
#endif
}

//! Changes the process effective group ID according to the argument passed
/** @par Platform Availability:
    @ref Qore::Option::HAVE_UNIX_USERMGT

    @param gid the group ID to set

    @return 0 if no error occurred; -1 if an error occurred, in which case errno() and/or strerror() can be used to retrieve the error

    @par Example:
    @code{.py}
if (setegid(gid))
    printf("setegid %d: %s\n", gid, strerror());
    @endcode
 */
int setegid(softint gid) [dom=PROCESS] {
#ifdef HAVE_SETEGID
   return setegid((int)gid);
#else
   return missing_function_error("setegid", xsink);
#endif
}

//! Creates a new session lead by the calling process
/** The calling process is the session leader of the new session, is the process group leader of a new process group and has no controlling terminal. The calling process is the only process in either the session or the process group.

    @return Upon successful completion, the setsid() function returns the value of the process group ID of the new process group, which is the same as the process ID of the calling process; if an error occurs, setsid() returns -1 and errno() and/or strerror() can be used to check the error.

    @par Example:
    @code{.py}
if (setsid() == -1)
    printf("setsid(): %s\n", strerror());
    @endcode
 */
int setsid() [dom=PROCESS] {
#ifdef HAVE_SETSID
   return setsid();
#else
   return missing_function_error("setsid", xsink);
#endif
}

//! Returns the first address corresponding to the hostname passed as an argument or @ref nothing if the lookup fails
/** @param name the name to look up

    @return the first address corresponding to the hostname passed as an argument or @ref nothing if the lookup fails

    @par Example:
    @code{.py}
*string addr = gethostbyname(host);
if (!exists host)
    printf("address lookup on %y failed; hostname unknown\n", host);
    @endcode

    @see gethostbyname_long() for a version of this function that returns all host information, including all hostname aliases and all addresses
 */
*string gethostbyname(string name) [flags=CONSTANT;dom=EXTERNAL_INFO] {
   return q_gethostbyname_to_string(name->getBuffer());
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing gethostbyname() [flags=RUNTIME_NOOP] {
}

//! Returns the official hostname corresponding to the network address passed as an argument
/** If the address family is invalid or the address string is not a valid address for the given family a \c GETHOSTBYADDR-ERROR exception will be thrown.

    @param addr the address to look up
    @param type the network address family; see @ref network_address_family_constants for valid values

    @return the official hostname corresponding to the network addressed passed as an argument

    @par Example:
    @code{.py}
*string hostname = gethostbyaddr("192.168.0.33");
if (!exists hostname)
    printf("address lookup on 192.168.0.33 failed\n", host);
    @endcode

    @throw GETHOSTBYADDR-ERROR invalid address for the given family or invalid address family

    @see gethostbyaddr_long() for a version of this function that returns all host information, including all hostname aliases and all addresses
 */
*string gethostbyaddr(string addr, softint type = AF_INET) [flags=CONSTANT;dom=EXTERNAL_INFO] {
   return q_gethostbyaddr_to_string(xsink, addr->getBuffer(), type);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing gethostbyaddr() [flags=RUNTIME_NOOP] {
}

//! Returns @ref host_information_hash "a hash" representing all host and address information corresponding to the hostname passed as an argument
/**
    @param name the name to look up

    @return @ref host_information_hash "a hash" representing all host and address information corresponding to the hostname passed as an argument

    @par Example:
    @code{.py}
*hash<auto> ah = gethostbyname_long(host);
if (!ah)
    printf("address lookup on %y failed; hostname unknown\n", host);
    @endcode

    @see gethostbyname() for a version of this function that returns just the first network address corresponding to the hostname
 */
*hash<auto> gethostbyname_long(string name) [flags=CONSTANT;dom=EXTERNAL_INFO] {
   return q_gethostbyname_to_hash(name->getBuffer());
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing gethostbyname_long() [flags=RUNTIME_NOOP] {
}

//! Returns @ref host_information_hash "a hash" representing all host and address information corresponding to the address and address type passed as arguments
/** If the address family is invalid or the address string is not a valid address for the given family a \c GETHOSTBYADDR-ERROR exception will be thrown.

    @param addr the address to look up
    @param type the network address family; see @ref network_address_family_constants for valid values

    @return @ref host_information_hash "a hash" representing all host and address information corresponding to the address and address type passed as arguments

    @par Example:
    @code{.py}
*hash<auto> ah = gethostbyaddr_long("192.168.0.33");
if (!ah)
    printf("address lookup on 192.168.0.33 failed\n", host);
    @endcode

    @throw GETHOSTBYADDR-ERROR invalid address for the given family or invalid address family

    @see gethostbyaddr() for a simpler version of this function that returns just a single hostname for the address
 */
*hash<auto> gethostbyaddr_long(string addr, softint type = AF_INET) [flags=CONSTANT;dom=EXTERNAL_INFO] {
   return q_gethostbyaddr_to_hash(xsink, addr->getBuffer(), type);
}

//! This function variant does nothing at all; it is only included for backwards-compatibility with qore prior to version 0.8.0 for functions that would ignore type errors in arguments
/**
 */
nothing gethostbyaddr_long() [flags=RUNTIME_NOOP] {
}

//! Returns a list of @ref address_information_hash for the given node name or string address; if no lookup can be performed then an exception is thrown
/** Either node or service may be @ref nothing but not both or a \c QOREADDRINFO-GETINFO-ERROR exception will be thrown

    @param node The host name or node name to look up
    @param service The service name to look up to a port number
    @param family The @ref network_address_family_constants "address family" for the lookup, must be one of Qore::AF_INET, Qore::AF_INET6, or Qore::AF_UNSPEC, meaning to return all possible addresses
    @param flags see @ref network_address_information_constants for possible values to be combined with @ref bitwise_or_operator "binary or"

    @return a list of @ref address_information_hash for the given node name or string address; if no lookup can be performed then an exception is thrown

    @par Example:
    @code{.py}
list<hash<auto>> l = getaddrinfo("localhost");
    @endcode

    @throw QOREADDRINFO-GETINFO-ERROR nodename nor servname provided, or not known
 */
list<hash<auto>> getaddrinfo(*string node, *softstring service, softint family = AF_UNSPEC, softint flags = 0) [flags=RET_VALUE_ONLY;dom=EXTERNAL_INFO] {
   return q_getaddrinfo_to_list(xsink, node ? node->getBuffer() : 0, service ? service->getBuffer() : 0, (int)family, (int)flags);
}

//! closes all possible file descriptors; useful in "daemon" processes that may have inherited open file descriptors
/** @par Platform Availability:
    @ref Qore::Option::HAVE_CLOSE_ALL_FD

    @param strd if @ref True "True" then also stdin, stdout, and stderr are closed, otherwise fds > 2 are closed

    @par Example:
    @code{.py}
close_all_fd();
    @endcode

    @note if there are file descriptors open by Qore when this function is called, this function will also close those file descriptors; normally this function should only be used when starting a new process after a fork() and exec() to close any inherited descriptors, for example
 */
nothing close_all_fd(*softbool strd) [dom=PROCESS] {
#if defined(HAVE_UNISTD_H) && defined(_SC_OPEN_MAX)
   int start = strd ? 0 : 2;
   // get max file descriptor
   int max = (int)sysconf(_SC_OPEN_MAX);
   printd(5, "close_all_fd() start: %d max: %d\n", start, max);

   for (;start < max; ++start)
      close(start);
#else
   missing_function_error("close_all_fd", xsink);
#endif // HAVE_UNISTD
}

//! returns a list of network interfaces
/** @par Platform Availability:
    @ref Qore::Option::HAVE_GET_NETIF_LIST

    @par Example:
    @code{.py}
list<hash<NetIfInfo>> l = get_netif_list();

results for example into:
list: (8 elements)
  [0]=hash: (5 members)
    interface : "lo"
    address : "00:00:00:00:00:00"
    address_desc : "mac<00:00:00:00:00:00>"
    family : 17
    familystr : "mac"
  [1]=hash: (5 members)
    interface : "eth0"
    address : "44:8a:5b:ba:13:50"
    address_desc : "mac<44:8a:5b:ba:13:50>"
    family : 17
    familystr : "mac"
  [2]=hash: (5 members)
    interface : "lo"
    address : "127.0.0.1"
    address_desc : "ipv4(127.0.0.1)"
    family : 2
    familystr : "ipv4"
  [3]=hash: (5 members)
    interface : "eth0"
    address : "192.168.20.4"
    address_desc : "ipv4(192.168.20.4)"
    family : 2
    familystr : "ipv4"
  [4]=hash: (5 members)
    interface : "lo"
    address : "::1"
    address_desc : "ipv6[::1]"
    family : 10
    familystr : "ipv6"
  [5]=hash: (5 members)
    interface : "eth0"
    address : "fe80::213:3bff:fe0f:d5e1"
    address_desc : "ipv6[fe80::213:3bff:fe0f:d5e1]"
    family : 10
    familystr : "ipv6"
    @endcode

    @return returns a list of network interfaces available on the local machine
*/
list<hash<NetIfInfo>> get_netif_list() [dom=EXTERNAL_INFO] {
#ifdef HAVE_GETIFADDRS
    struct ifaddrs* ifap;
    if (getifaddrs(&ifap)) {
        xsink->raiseErrnoException("GETGETIFLIST-ERROR", errno, "getidaddrs() failed");
        return QoreValue();
    }
    ON_BLOCK_EXIT(freeifaddrs, ifap);

    ReferenceHolder<QoreListNode> rv(new QoreListNode(hashdeclNetIfInfo->getTypeInfo()), xsink);

    for (struct ifaddrs* ifa = ifap; ifa; ifa = ifa->ifa_next) {
        // skip interfaces with no address
        if (!ifa->ifa_addr)
            continue;

        ReferenceHolder<QoreHashNode> h(new QoreHashNode(hashdeclNetIfInfo, xsink), xsink);
        qore_hash_private* hh = qore_hash_private::get(**h);

        hh->setKeyValueIntern("interface", new QoreStringNode(ifa->ifa_name));

        switch (ifa->ifa_addr->sa_family) {
            case AF_INET:
            case AF_INET6: {
                // get ipv4 or ipv6 address
                char ifname[INET6_ADDRSTRLEN];
                if (inet_ntop(ifa->ifa_addr->sa_family, qore_get_in_addr(ifa->ifa_addr), ifname, sizeof(ifname))) {
                    QoreStringNode* addrstr = new QoreStringNode(ifname);
                    hh->setKeyValueIntern("address", addrstr);
                    hh->setKeyValueIntern("address_desc", QoreAddrInfo::getAddressDesc(ifa->ifa_addr->sa_family, addrstr->c_str()));
                }
                break;
            }
#ifdef AF_PACKET
            case AF_PACKET: {
                struct sockaddr_ll* s = (struct sockaddr_ll*)ifa->ifa_addr;
                QoreStringNode* addrstr = new QoreStringNode;
                for (unsigned i = 0; i < s->sll_halen; ++i) {
                    addrstr->sprintf("%02x%s", s->sll_addr[i], static_cast<int>(i) < (s->sll_halen - 1) ? ":" : "");
                }
                hh->setKeyValueIntern("address", addrstr);
                hh->setKeyValueIntern("address_desc", QoreAddrInfo::getAddressDesc(ifa->ifa_addr->sa_family, addrstr->c_str()));
                break;
            }
#endif
#ifdef AF_LINK
            case AF_LINK: {
                QoreStringNode* addrstr = new QoreStringNode(link_ntoa((struct sockaddr_dl*)ifa->ifa_addr));
                hh->setKeyValueIntern("address", addrstr);
                hh->setKeyValueIntern("address_desc", QoreAddrInfo::getAddressDesc(ifa->ifa_addr->sa_family, addrstr->c_str()));
                break;
            }
#endif
        }

         hh->setKeyValueIntern("family", ifa->ifa_addr->sa_family);
         hh->setKeyValueIntern("familystr", new QoreStringNode(QoreAddrInfo::getFamilyName(ifa->ifa_addr->sa_family)));

         rv->push(h.release(), xsink);
    }

    return rv.release();
#else
    missing_function_error("getifaddrs", xsink);
#endif
}

//! sets %Qore runtime library options
/** @param opts the runtime options to set, binary-anded with @ref Qore::QLO_RUNTIME_OPTS; see
    @ref library_option_constants and @ref Qore::QLO_RUNTIME_OPTS "QLO_RUNTIME_OPTS" for more information

    @return the options used

    @see qore_get_library_options()

    @since %Qore 0.9.3.1
*/
int qore_set_library_options(int opts) [dom=PROCESS] {
    return qore_set_library_options(opts);
}

//! returns all %Qore library options
/** @return all %Qore library options; see @ref library_option_constants for more information

    @see qore_set_library_options()

    @since %Qore 0.9.3.1
*/
int qore_get_library_options() [dom=PROCESS] {
    return qore_library_options;
}
///@}
