9 n__common_8c
gullradriel edited this page 2026-10-29 20:54:40 +01:00

src/n_common.c

Common function. More...

Structures

Name
struct N_SPAWN_PIPE
A program started with its standard input on a pipe the caller writes to (n_spawn_pipe_open()), or its standard output on one the caller reads (n_spawn_pipe_open_read()).

Functions

Name
N_SPAWN_PIPE * _n_spawn_pipe_start(const char * program, const char *const * args, const char * log_path, int reading)
Start a program with one of its standard streams on a pipe.

Detailed Description

Common function.

Author: Castagnier Mickael

Version: 1.0

Date: 24/03/05

Functions Documentation

function _n_spawn_pipe_start

static N_SPAWN_PIPE * _n_spawn_pipe_start(
    const char * program,
    const char *const * args,
    const char * log_path,
    int reading
)

Start a program with one of its standard streams on a pipe.

Parameters:

  • program Program to run.
  • args Its arguments, NULL terminated, or NULL.
  • log_path Where what it says goes, or NULL to leave it.
  • reading Non-zero for the reading form.

Return: The running program, or NULL.

The body of n_spawn_pipe_open() and n_spawn_pipe_open_read(). Writing, the pipe is the program's standard input and log_path takes its output and error. Reading, the pipe is its standard output, log_path takes its error alone - what it says must not be mixed into what it produces - and its input is the null device, so that it never waits on a terminal. The caller's end is never inherited by another program.

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */


#include "nilorea/n_common.h"
#include "nilorea/n_log.h"
#include "nilorea/n_str.h"

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <libgen.h>

#include "nilorea/n_windows.h"
#include <errno.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#ifndef __windows__
#include <sys/wait.h>
#include <time.h>
#include <pthread.h>
#else
/* ShellExecuteA, which windows.h leaves out under WIN32_LEAN_AND_MEAN. */
#include <shellapi.h>
#endif

void n_abort(char const* format, ...) {
    char str[1024] = "";
    va_list args;
    va_start(args, format);

    vsnprintf(str, sizeof str, format, args);
    va_end(args);
    fprintf(stderr, "%s", str);
    exit(1);
}

int get_computer_name(char* computer_name, size_t len) {
    if (!computer_name || len == 0) return FALSE;
    memset(computer_name, 0, len);
#ifdef WIN32
    TCHAR infoBuf[len];
    DWORD bufCharCount = (DWORD)len;
    if (GetComputerName(infoBuf, &bufCharCount)) {
        memcpy(computer_name, infoBuf, len);
    } else {
        strncpy(computer_name, "Unknown_Host_Name", len - 1);
        computer_name[len - 1] = '\0';
        return FALSE;
    }
#else
    if (gethostname(computer_name, len) == -1) {
        int error = errno;
        strncpy(computer_name, "Unknown_Host_Name", len - 1);
        computer_name[len - 1] = '\0';
        n_log(LOG_ERR, "%s on gethostname !", strerror(error));
        return FALSE;
    }
#endif
    return TRUE;
} /* get_computer_name */

int file_exist(const char* filename) {
    FILE* file = NULL;
    if ((file = fopen(filename, "r")) != NULL) {
        fclose(file);
        return 1;
    }
    return 0;
} /* file_exist */

char* get_prog_dir(void) {
    char strbuf[PATH_MAX] = "";

    int error = 0;
#ifdef __windows__
    unsigned long int bytes = GetModuleFileName(NULL, strbuf, PATH_MAX);
    error = errno;
    if (bytes != 0) {
        return strdup(dirname(strbuf));
    }
#else
    char procbuf[PATH_MAX] = "";
#ifdef __linux__
    snprintf(procbuf, PATH_MAX, "/proc/%d/exe", (int)getpid());
#elif defined __sun
    snprintf(procbuf, PATH_MAX, "/proc/%d/path/a.out", (int)getpid());
#endif
    ssize_t bytes = MIN(readlink(procbuf, strbuf, PATH_MAX), PATH_MAX - 1);
    error = errno;
    if (bytes >= 0) {
        strbuf[bytes] = '\0';
        return strdup(dirname(strbuf));
    }
#endif
    fprintf(stderr, "%s", strerror(error));
    return NULL;
} /* get_prog_dir */

char* get_prog_name(void) {
    char strbuf[PATH_MAX] = "";
    int error = 0;
#ifdef __windows__
    unsigned long int bytes = GetModuleFileName(NULL, strbuf, PATH_MAX);
    error = errno;
    if (bytes != 0) {
        return strdup(basename(strbuf));
    }
#else
    char procbuf[PATH_MAX] = "";
#ifdef __linux__
    snprintf(procbuf, PATH_MAX, "/proc/%d/exe", (int)getpid());
#elif defined __sun
    snprintf(procbuf, PATH_MAX, "/proc/%d/path/a.out", (int)getpid());
#endif
    ssize_t bytes = MIN(readlink(procbuf, strbuf, PATH_MAX), PATH_MAX - 1);
    error = errno;
    if (bytes >= 0) {
        strbuf[bytes] = '\0';
        return strdup(basename(strbuf));
    }
#endif
    fprintf(stderr, "%s", strerror(error));
    return NULL;
} /* get_prog_name */

int n_popen(char* cmd, size_t read_buf_size, void** nstr_output, int* ret) {
    __n_assert(cmd, return FALSE);

    if (read_buf_size > INT_MAX) {
        n_log(LOG_ERR, "read_buf_size buffer size is too large for fgets (%zu>%d)", read_buf_size, INT_MAX);
        return FALSE;
    }
    if (read_buf_size > 65536) {
        n_log(LOG_ERR, "read_buf_size %zu exceeds safe stack limit", read_buf_size);
        return FALSE;
    }

    N_STR* output_pointer = new_nstr(read_buf_size + 1);

    FILE* fp = NULL;
    int status = 0;
    char read_buf[read_buf_size];

    fp = popen(cmd, "r");
    int err = errno;
    if (fp == NULL) {
        n_log(LOG_ERR, "popen( %s ) returned NULL , %s (%d)", cmd, strerror(err), err);
        free_nstr(&output_pointer);
        return FALSE;
    }
    while (fgets(read_buf, (int)read_buf_size, fp) != NULL) {
        size_t length = strlen(read_buf);
        if (length > 0 && read_buf[length - 1] == '\n') {
            read_buf[length - 1] = '\0';
        }
        nstrprintf_cat(output_pointer, "%s", read_buf);
    }
    status = pclose(fp);
    err = errno;
    if (status == -1) {
        n_log(LOG_ERR, "pclose( %s ) returned -1 , %s (%d)", cmd, strerror(err), err);
        free_nstr(&output_pointer);
        return FALSE;
    }
    if (ret) (*ret) = WEXITSTATUS(status);
    if (nstr_output) {
        (*nstr_output) = output_pointer;
    } else {
        free_nstr(&output_pointer);
    }
    return TRUE;
} /* n_popen( ... ) */

#ifdef __windows__
static int append_quoted(char* out, size_t out_len, size_t* used, const char* arg) {
    size_t slashes = 0;
    const char* p = NULL;

#define N_PUT(c)                  \
    do {                          \
        if (*used + 1 >= out_len) \
            return FALSE;         \
        out[(*used)++] = (c);     \
    } while (0)

    if (*used)
        N_PUT(' ');
    N_PUT('"');
    for (p = arg; *p; p++) {
        if (*p == '\\') {
            slashes++;
            continue;
        }
        if (*p == '"') {
            size_t k = 0;

            for (k = 0; k < slashes * 2 + 1; k++)
                N_PUT('\\');
        } else {
            size_t k = 0;

            for (k = 0; k < slashes; k++)
                N_PUT('\\');
        }
        slashes = 0;
        N_PUT(*p);
    }
    {
        size_t k = 0;

        for (k = 0; k < slashes * 2; k++)
            N_PUT('\\');
    }
    N_PUT('"');
    out[*used] = '\0';
#undef N_PUT
    return TRUE;
}
#endif

int n_spawn_detached(const char* program, const char* const* args) {
    __n_assert(program, return FALSE);
#ifdef __windows__
    {
        char line[32768];
        size_t used = 0;
        STARTUPINFOA si;
        PROCESS_INFORMATION pi;
        size_t i = 0;

        line[0] = '\0';
        if (!append_quoted(line, sizeof(line), &used, program))
            return FALSE;
        for (i = 0; args && args[i]; i++) {
            if (!append_quoted(line, sizeof(line), &used, args[i])) {
                n_log(LOG_ERR, "n_spawn_detached: the command line is too long");
                return FALSE;
            }
        }
        memset(&si, 0, sizeof(si));
        si.cb = sizeof(si);
        memset(&pi, 0, sizeof(pi));
        if (!CreateProcessA(NULL, line, NULL, NULL, FALSE, DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP,
                            NULL, NULL, &si, &pi)) {
            n_log(LOG_ERR, "n_spawn_detached: cannot start %s (%lu)", program, (unsigned long)GetLastError());
            return FALSE;
        }
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
        return TRUE;
    }
#else
    {
        const char* argv[64];
        size_t i = 0, n = 0;
        pid_t child = 0;
        int status = 0;

        argv[n++] = program;
        for (i = 0; args && args[i]; i++) {
            if (n >= sizeof(argv) / sizeof(argv[0]) - 1) {
                n_log(LOG_ERR, "n_spawn_detached: more than %zu arguments", sizeof(argv) / sizeof(argv[0]) - 2);
                return FALSE;
            }
            argv[n++] = args[i];
        }
        argv[n] = NULL;

        /* Twice: the first child starts a session and forks the program,
         * then leaves at once, so the program is adopted by init and never
         * waits as a zombie on a caller that will not reap it. Only calls
         * that are safe after a fork in a threaded process are made there. */
        child = fork();
        if (child < 0) {
            n_log(LOG_ERR, "n_spawn_detached: fork failed: %s", strerror(errno));
            return FALSE;
        }
        if (child == 0) {
            pid_t grandchild = 0;

            setsid();
            grandchild = fork();
            if (grandchild == 0) {
                execvp(program, (char* const*)argv);
                _exit(127);
            }
            _exit(grandchild < 0 ? 1 : 0);
        }
        if (waitpid(child, &status, 0) < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
            n_log(LOG_ERR, "n_spawn_detached: could not start %s", program);
            return FALSE;
        }
        return TRUE;
    }
#endif
}

int n_spawn_wait(const char* program, const char* const* args, int* exit_code) {
    return n_spawn_wait_cancel(program, args, exit_code, NULL);
}

int n_spawn_wait_cancel(const char* program, const char* const* args, int* exit_code, const volatile int* cancel) {
    if (exit_code)
        *exit_code = -1;
    __n_assert(program, return FALSE);
#ifdef __windows__
    {
        char line[32768];
        size_t used = 0;
        STARTUPINFOA si;
        PROCESS_INFORMATION pi;
        DWORD code = 0;
        size_t i = 0;

        line[0] = '\0';
        if (!append_quoted(line, sizeof(line), &used, program))
            return FALSE;
        for (i = 0; args && args[i]; i++) {
            if (!append_quoted(line, sizeof(line), &used, args[i])) {
                n_log(LOG_ERR, "n_spawn_wait: the command line is too long");
                return FALSE;
            }
        }
        memset(&si, 0, sizeof(si));
        si.cb = sizeof(si);
        memset(&pi, 0, sizeof(pi));
        /* No console window for a tool run in the background. */
        if (!CreateProcessA(NULL, line, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
            n_log(LOG_ERR, "n_spawn_wait: cannot start %s (%lu)", program, (unsigned long)GetLastError());
            return FALSE;
        }
        CloseHandle(pi.hThread);
        if (!cancel) {
            WaitForSingleObject(pi.hProcess, INFINITE);
        } else {
            /* A tenth of a second between looks at the flag: short enough
             * that a Stop is felt at once, long enough to cost nothing. */
            while (WaitForSingleObject(pi.hProcess, 100) == WAIT_TIMEOUT) {
                if (*cancel) {
                    TerminateProcess(pi.hProcess, 1);
                    WaitForSingleObject(pi.hProcess, INFINITE);
                    CloseHandle(pi.hProcess);
                    n_log(LOG_NOTICE, "n_spawn_wait_cancel: %s stopped", program);
                    return FALSE;
                }
            }
        }
        if (!GetExitCodeProcess(pi.hProcess, &code))
            code = (DWORD)-1;
        CloseHandle(pi.hProcess);
        if (exit_code)
            *exit_code = (int)code;
        return code == 0 ? TRUE : FALSE;
    }
#else
    {
        const char* argv[64];
        size_t i = 0, n = 0;
        pid_t child = 0;
        int status = 0;

        argv[n++] = program;
        for (i = 0; args && args[i]; i++) {
            if (n >= sizeof(argv) / sizeof(argv[0]) - 1) {
                n_log(LOG_ERR, "n_spawn_wait: more than %zu arguments", sizeof(argv) / sizeof(argv[0]) - 2);
                return FALSE;
            }
            argv[n++] = args[i];
        }
        argv[n] = NULL;

        child = fork();
        if (child < 0) {
            n_log(LOG_ERR, "n_spawn_wait: fork failed: %s", strerror(errno));
            return FALSE;
        }
        if (child == 0) {
            /* Only calls that are safe after a fork in a threaded process. */
            execvp(program, (char* const*)argv);
            _exit(127);
        }
        if (cancel) {
            /* Looked at every tenth of a second while the child runs. A
             * stop asks it to end, gives it two seconds to write nothing
             * more, then ends it, and reaps it either way so no zombie is
             * left behind. */
            int asked = 0;

            for (;;) {
                struct timespec tick = {0, 100000000L};
                pid_t got = waitpid(child, &status, WNOHANG);

                if (got == child)
                    break;
                if (got < 0 && errno != EINTR) {
                    n_log(LOG_ERR, "n_spawn_wait_cancel: waiting for %s failed: %s", program, strerror(errno));
                    return FALSE;
                }
                if (*cancel) {
                    if (asked == 0)
                        kill(child, SIGTERM);
                    else if (asked == 20)
                        kill(child, SIGKILL);
                    asked++;
                }
                nanosleep(&tick, NULL);
            }
            if (asked) {
                n_log(LOG_NOTICE, "n_spawn_wait_cancel: %s stopped", program);
                return FALSE;
            }
        } else {
            /* Retried on EINTR: a signal for another thread must not abandon
             * a child that is still running. */
            while (waitpid(child, &status, 0) < 0) {
                if (errno != EINTR) {
                    n_log(LOG_ERR, "n_spawn_wait: waiting for %s failed: %s", program, strerror(errno));
                    return FALSE;
                }
            }
        }
        if (!WIFEXITED(status)) {
            n_log(LOG_ERR, "n_spawn_wait: %s was ended by a signal", program);
            return FALSE;
        }
        if (exit_code)
            *exit_code = WEXITSTATUS(status);
        if (WEXITSTATUS(status) == 127)
            n_log(LOG_ERR, "n_spawn_wait: %s could not be found or started", program);
        return WEXITSTATUS(status) == 0 ? TRUE : FALSE;
    }
#endif
}

struct N_SPAWN_PIPE {
#ifdef __windows__
    HANDLE end;
    HANDLE process;
#else
    int end;
    pid_t child;
#endif
    int reading;
    char program[256];
};

static N_SPAWN_PIPE* _n_spawn_pipe_start(const char* program, const char* const* args, const char* log_path, int reading) {
    N_SPAWN_PIPE* sp = NULL;
    const char* who = reading ? "n_spawn_pipe_open_read" : "n_spawn_pipe_open";

    __n_assert(program, return NULL);
    Malloc(sp, N_SPAWN_PIPE, 1);
    __n_assert(sp, return NULL);
    snprintf(sp->program, sizeof(sp->program), "%s", program);
    sp->reading = reading ? 1 : 0;
#ifdef __windows__
    {
        char line[32768];
        size_t used = 0, i = 0;
        STARTUPINFOA si;
        PROCESS_INFORMATION pi;
        SECURITY_ATTRIBUTES sa;
        HANDLE read_end = NULL, write_end = NULL, child_end = NULL, log = INVALID_HANDLE_VALUE, nul = INVALID_HANDLE_VALUE;

        line[0] = '\0';
        if (!append_quoted(line, sizeof(line), &used, program)) {
            Free(sp);
            return NULL;
        }
        for (i = 0; args && args[i]; i++) {
            if (!append_quoted(line, sizeof(line), &used, args[i])) {
                n_log(LOG_ERR, "%s: the command line is too long", who);
                Free(sp);
                return NULL;
            }
        }
        memset(&sa, 0, sizeof(sa));
        sa.nLength = sizeof(sa);
        sa.bInheritHandle = TRUE;
        if (!CreatePipe(&read_end, &write_end, &sa, 0)) {
            n_log(LOG_ERR, "%s: no pipe (%lu)", who, (unsigned long)GetLastError());
            Free(sp);
            return NULL;
        }
        /* Only the program's end is inherited: an inherited copy of ours
         * would hold the pipe open after we close it. */
        sp->end = reading ? read_end : write_end;
        child_end = reading ? write_end : read_end;
        SetHandleInformation(sp->end, HANDLE_FLAG_INHERIT, 0);
        if (log_path) {
            log = CreateFileA(log_path, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
            if (log == INVALID_HANDLE_VALUE)
                n_log(LOG_ERR, "%s: cannot write %s (%lu)", who, log_path, (unsigned long)GetLastError());
        }
        if (reading)
            nul = CreateFileA("NUL", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, NULL);
        memset(&si, 0, sizeof(si));
        si.cb = sizeof(si);
        si.dwFlags = STARTF_USESTDHANDLES;
        if (reading) {
            si.hStdInput = nul != INVALID_HANDLE_VALUE ? nul : GetStdHandle(STD_INPUT_HANDLE);
            si.hStdOutput = child_end;
            si.hStdError = log != INVALID_HANDLE_VALUE ? log : GetStdHandle(STD_ERROR_HANDLE);
        } else {
            si.hStdInput = child_end;
            si.hStdOutput = log != INVALID_HANDLE_VALUE ? log : GetStdHandle(STD_OUTPUT_HANDLE);
            si.hStdError = log != INVALID_HANDLE_VALUE ? log : GetStdHandle(STD_ERROR_HANDLE);
        }
        memset(&pi, 0, sizeof(pi));
        if (!CreateProcessA(NULL, line, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
            n_log(LOG_ERR, "%s: cannot start %s (%lu)", who, program, (unsigned long)GetLastError());
            CloseHandle(read_end);
            CloseHandle(write_end);
            if (log != INVALID_HANDLE_VALUE)
                CloseHandle(log);
            if (nul != INVALID_HANDLE_VALUE)
                CloseHandle(nul);
            Free(sp);
            return NULL;
        }
        /* The program holds its own copies now. */
        CloseHandle(child_end);
        if (log != INVALID_HANDLE_VALUE)
            CloseHandle(log);
        if (nul != INVALID_HANDLE_VALUE)
            CloseHandle(nul);
        CloseHandle(pi.hThread);
        sp->process = pi.hProcess;
        return sp;
    }
#else
    {
        const char* argv[64];
        size_t i = 0, n = 0;
        int fds[2] = {-1, -1};
        int log = -1, nul = -1;
        int mine = 0, theirs = 0;

        argv[n++] = program;
        for (i = 0; args && args[i]; i++) {
            if (n >= sizeof(argv) / sizeof(argv[0]) - 1) {
                n_log(LOG_ERR, "%s: more than %zu arguments", who, sizeof(argv) / sizeof(argv[0]) - 2);
                Free(sp);
                return NULL;
            }
            argv[n++] = args[i];
        }
        argv[n] = NULL;
        if (pipe(fds) != 0) {
            n_log(LOG_ERR, "%s: no pipe: %s", who, strerror(errno));
            Free(sp);
            return NULL;
        }
        /* Both ends close in every program started from here on, this one
         * included; dup2 in the child makes the copy it keeps. */
        fcntl(fds[1], F_SETFD, FD_CLOEXEC);
        fcntl(fds[0], F_SETFD, FD_CLOEXEC);
        mine = reading ? fds[0] : fds[1];
        theirs = reading ? fds[1] : fds[0];
        if (log_path) {
            log = open(log_path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
            if (log < 0)
                n_log(LOG_ERR, "%s: cannot write %s: %s", who, log_path, strerror(errno));
        }
        if (reading)
            nul = open("/dev/null", O_RDONLY | O_CLOEXEC);
        sp->child = fork();
        if (sp->child < 0) {
            n_log(LOG_ERR, "%s: fork failed: %s", who, strerror(errno));
            close(fds[0]);
            close(fds[1]);
            if (log >= 0)
                close(log);
            if (nul >= 0)
                close(nul);
            Free(sp);
            return NULL;
        }
        if (sp->child == 0) {
            /* Only calls that are safe after a fork in a threaded process.
             * dup2 clears close-on-exec on the copies it makes. */
            if (reading) {
                if (dup2(theirs, STDOUT_FILENO) < 0)
                    _exit(127);
                if (nul >= 0)
                    dup2(nul, STDIN_FILENO);
                if (log >= 0)
                    dup2(log, STDERR_FILENO);
            } else {
                if (dup2(theirs, STDIN_FILENO) < 0)
                    _exit(127);
                if (log >= 0) {
                    dup2(log, STDOUT_FILENO);
                    dup2(log, STDERR_FILENO);
                }
            }
            execvp(program, (char* const*)argv);
            _exit(127);
        }
        close(theirs);
        if (log >= 0)
            close(log);
        if (nul >= 0)
            close(nul);
        sp->end = mine;
        return sp;
    }
#endif
}

N_SPAWN_PIPE* n_spawn_pipe_open(const char* program, const char* const* args, const char* log_path) {
    return _n_spawn_pipe_start(program, args, log_path, 0);
}

N_SPAWN_PIPE* n_spawn_pipe_open_read(const char* program, const char* const* args, const char* log_path) {
    return _n_spawn_pipe_start(program, args, log_path, 1);
}

size_t n_spawn_pipe_read(N_SPAWN_PIPE* sp, void* data, size_t len) {
    char* at = (char*)data;
    size_t got = 0;

    __n_assert(sp, return SIZE_MAX);
    if (!sp->reading) {
        n_log(LOG_ERR, "n_spawn_pipe_read: %s was started to be written to", sp->program);
        return SIZE_MAX;
    }
    if (len && !data)
        return SIZE_MAX;
#ifdef __windows__
    if (!sp->end)
        return SIZE_MAX;
    while (got < len) {
        DWORD chunk = (len - got) > 0x40000000u ? 0x40000000u : (DWORD)(len - got), read = 0;

        if (!ReadFile(sp->end, at + got, chunk, &read, NULL) || read == 0) {
            /* The writer gone is the end of the output, not an error. */
            if (GetLastError() == ERROR_BROKEN_PIPE || read == 0)
                break;
            n_log(LOG_ERR, "n_spawn_pipe_read: reading %s failed (%lu)", sp->program, (unsigned long)GetLastError());
            return SIZE_MAX;
        }
        got += read;
    }
    return got;
#else
    if (sp->end < 0)
        return SIZE_MAX;
    while (got < len) {
        ssize_t r = read(sp->end, at + got, len - got);

        if (r < 0) {
            if (errno == EINTR)
                continue;
            n_log(LOG_ERR, "n_spawn_pipe_read: reading %s failed: %s", sp->program, strerror(errno));
            return SIZE_MAX;
        }
        if (r == 0)
            break;
        got += (size_t)r;
    }
    return got;
#endif
}

int n_spawn_pipe_write(N_SPAWN_PIPE* sp, const void* data, size_t len) {
    const char* at = (const char*)data;

    __n_assert(sp, return FALSE);
    if (sp->reading) {
        n_log(LOG_ERR, "n_spawn_pipe_write: %s was started to be read from", sp->program);
        return FALSE;
    }
    if (len && !data)
        return FALSE;
#ifdef __windows__
    if (!sp->end)
        return FALSE;
    while (len) {
        DWORD chunk = len > 0x40000000u ? 0x40000000u : (DWORD)len, wrote = 0;

        if (!WriteFile(sp->end, at, chunk, &wrote, NULL) || wrote == 0) {
            n_log(LOG_ERR, "n_spawn_pipe_write: %s stopped reading (%lu)", sp->program, (unsigned long)GetLastError());
            return FALSE;
        }
        at += wrote;
        len -= wrote;
    }
    return TRUE;
#else
    {
        sigset_t pipe_set, before;
        int ok = TRUE, raised = 0;

        if (sp->end < 0)
            return FALSE;
        /* A program that has gone sends SIGPIPE to the writer, whose default
         * is to end the whole caller. Held back for this thread only, which
         * leaves the rest of the process's handling alone, and taken off the
         * pending set afterwards if it came. */
        sigemptyset(&pipe_set);
        sigaddset(&pipe_set, SIGPIPE);
        pthread_sigmask(SIG_BLOCK, &pipe_set, &before);
        while (len) {
            ssize_t wrote = write(sp->end, at, len);

            if (wrote < 0) {
                if (errno == EINTR)
                    continue;
                if (errno == EPIPE)
                    raised = 1;
                n_log(LOG_ERR, "n_spawn_pipe_write: %s stopped reading: %s", sp->program, strerror(errno));
                ok = FALSE;
                break;
            }
            at += wrote;
            len -= (size_t)wrote;
        }
        if (raised) {
            struct timespec none = {0, 0};

            while (sigtimedwait(&pipe_set, NULL, &none) < 0 && errno == EINTR);
        }
        pthread_sigmask(SIG_SETMASK, &before, NULL);
        return ok;
    }
#endif
}

int n_spawn_pipe_close(N_SPAWN_PIPE** pipe_ptr, int stop, int* exit_code) {
    N_SPAWN_PIPE* sp = NULL;
    int ok = FALSE;

    if (exit_code)
        *exit_code = -1;
    if (!pipe_ptr || !*pipe_ptr)
        return FALSE;
    sp = *pipe_ptr;
#ifdef __windows__
    {
        DWORD code = (DWORD)-1;

        if (stop)
            TerminateProcess(sp->process, 1);
        /* Closing our end is the end of the stream a reading program waits
         * for, and a broken pipe for a writing one, which ends it. */
        if (sp->end)
            CloseHandle(sp->end);
        WaitForSingleObject(sp->process, INFINITE);
        if (!stop && GetExitCodeProcess(sp->process, &code)) {
            if (exit_code)
                *exit_code = (int)code;
            ok = code == 0 ? TRUE : FALSE;
        }
        CloseHandle(sp->process);
    }
#else
    {
        int status = 0, asked = 0;

        if (stop)
            kill(sp->child, SIGTERM);
        /* Closing our end is the end of the stream a reading program waits
         * for, and a broken pipe for a writing one, which ends it. */
        if (sp->end >= 0)
            close(sp->end);
        sp->end = -1;
        for (;;) {
            pid_t got = waitpid(sp->child, &status, stop ? WNOHANG : 0);

            if (got == sp->child)
                break;
            if (got < 0) {
                if (errno == EINTR)
                    continue;
                n_log(LOG_ERR, "n_spawn_pipe_close: waiting for %s failed: %s", sp->program, strerror(errno));
                Free(sp);
                *pipe_ptr = NULL;
                return FALSE;
            }
            /* Stopping: two seconds of grace, then the end. */
            if (++asked == 20)
                kill(sp->child, SIGKILL);
            {
                struct timespec tick = {0, 100000000L};

                nanosleep(&tick, NULL);
            }
        }
        if (stop) {
            n_log(LOG_NOTICE, "n_spawn_pipe_close: %s stopped", sp->program);
        } else if (!WIFEXITED(status)) {
            n_log(LOG_ERR, "n_spawn_pipe_close: %s was ended by a signal", sp->program);
        } else {
            if (exit_code)
                *exit_code = WEXITSTATUS(status);
            if (WEXITSTATUS(status) == 127)
                n_log(LOG_ERR, "n_spawn_pipe_close: %s could not be found or started", sp->program);
            ok = WEXITSTATUS(status) == 0 ? TRUE : FALSE;
        }
    }
#endif
    Free(sp);
    *pipe_ptr = NULL;
    return ok;
}

int n_open_with_default(const char* path) {
    __n_assert(path, return FALSE);
#ifdef __windows__
    {
        HINSTANCE done = ShellExecuteA(NULL, "open", path, NULL, NULL, SW_SHOWNORMAL);

        /* ShellExecute reports success as a value above 32. */
        if ((INT_PTR)done <= 32) {
            n_log(LOG_ERR, "n_open_with_default: cannot open %s (%ld)", path, (long)(INT_PTR)done);
            return FALSE;
        }
        return TRUE;
    }
#else
    {
        const char* args[2] = {path, NULL};
#ifdef __APPLE__
        return n_spawn_detached("open", args);
#else
        return n_spawn_detached("xdg-open", args);
#endif
    }
#endif
}

void log_environment(int loglevel) {
#if defined(_WIN32) || defined(__MINGW32__) || defined(__windows__)
    LPCH env_block = GetEnvironmentStrings();
    if (!env_block) return;
    for (LPCH var = env_block; *var != '\0'; var += strlen(var) + 1) {
        n_log(loglevel, "env: %s", var);
    }
    FreeEnvironmentStrings(env_block);
#else
    extern char** environ;
    for (char** env = environ; *env != NULL; env++) {
        n_log(loglevel, "env: %s", *env);
    }
#endif
}

#ifndef __windows__
void sigchld_handler(int sig) {
    // waitpid() might overwrite errno, so we save and restore it:
    int saved_errno = errno;

    while (waitpid(-1, NULL, WNOHANG) > 0);

    errno = saved_errno;
    n_log(LOG_DEBUG, "Signal %d", sig);
}

int sigchld_handler_installer(void) {
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;  // reap all dead processes
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        int error = errno;
        n_log(LOG_ERR, "sigaction error: %s", strerror(error));
        return FALSE;
    }
    return TRUE;
}

int n_daemonize(void) {
    return n_daemonize_ex(N_DAEMON_NO_SIGCHLD_IGN | N_DAEMON_NO_SIGCHLD_HANDLER);
}

int n_daemonize_ex(int mode) {
    int error = 0;

    /* Fork off the parent process */
    pid_t pid = fork();
    error = errno;
    if (pid < 0) {
        n_log(LOG_ERR, "Error: unable to fork child: %s", strerror(error));
        exit(1);
    }
    if (pid > 0) {
        n_log(LOG_NOTICE, "Child started successfuly !");
        exit(0);
    }

    if (!(mode & N_DAEMON_NO_SETSID)) {
        /* On success: The child process becomes session leader
         * setsid is detaching the process from the terminal */
        if (setsid() < 0) {
            error = errno;
            n_log(LOG_ERR, "Error: unable to set session leader with setsid(): %s", strerror(error));
            exit(1);
        } else {
            n_log(LOG_NOTICE, "Session leader set !");
        }
    }

    if (!(mode & N_DAEMON_NO_SIGCHLD_IGN)) {
        /* Ignore signal sent from child to parent process */
        signal(SIGCHLD, SIG_IGN);
    }

    if (!(mode & N_DAEMON_NO_SIGCHLD_HANDLER)) {
        /* catching signal */
        sigchld_handler_installer();
    }

    if (!(mode & N_DAEMON_NO_DOUBLE_FORK)) {
        /* Double fork to detach from the parent, and be adopted by init process */
        pid = fork();
        error = errno;
        if (pid < 0) {
            n_log(LOG_ERR, "Error: unable to double fork child: %s", strerror(error));
            exit(1);
        }
        if (pid > 0) {
            n_log(LOG_NOTICE, "Double fork child started successfuly !");
            exit(0);
        }
    }

    if (!(mode & N_DAEMON_NO_UMASK)) {
        /* reset umask */
        umask(0);
    }

    if (!(mode & N_DAEMON_NO_CHDIR)) {
        /* set working directory */
        if (chdir("/") < 0) {
            error = errno;
            n_log(LOG_ERR, "couldn't chdir() to '/': %s", strerror(error));
            return FALSE;
        }
    }

    if (!(mode & N_DAEMON_NO_CLOSE)) {
        /* Close all open file descriptors except standard ones (stdin, stdout, stderr) */
        for (int x = 3; x < sysconf(_SC_OPEN_MAX); x++) {
            close(x);
        }
    }

    if (!(mode & N_DAEMON_NO_STD_REDIRECT)) {
        /* redirect stdin,stdout,stderr to /dev/null */
        int fd = open("/dev/null", O_RDWR, 0);
        if (fd != -1) {
            dup2(fd, STDIN_FILENO);
            dup2(fd, STDOUT_FILENO);
            dup2(fd, STDERR_FILENO);
            if (fd > 2) {
                close(fd);
            }
        } else {
            n_log(LOG_ERR, "Failed to open /dev/null: %s", strerror(errno));
        }
    }

    return TRUE;
} /* n_daemonize(...) */

pid_t system_nb(const char* command, int* infp, int* outfp) {
    __n_assert(command, return -1);

    int p_stdin[2] = {-1, -1};
    int p_stdout[2] = {-1, -1};
    pid_t pid = -1;

    if (outfp != NULL) {
        if (pipe(p_stdin) != 0)
            return -1;
    }
    if (infp != NULL) {
        if (pipe(p_stdout) != 0) {
            if (outfp != NULL) {
                close(p_stdin[0]);
                close(p_stdin[1]);
            }
            return (-1);
        }
    }

    pid = fork();

    if (pid < 0) {
        n_log(LOG_ERR, "Couldn't fork command %s", command);
        if (outfp != NULL) {
            close(p_stdin[0]);
            close(p_stdin[1]);
        }
        if (infp != NULL) {
            close(p_stdout[0]);
            close(p_stdout[1]);
        }
        return pid;
    }
    if (pid == 0) {
        if (outfp != NULL) {
            close(p_stdin[1]);
            dup2(p_stdin[0], 0);
        }
        if (infp != NULL) {
            close(p_stdout[0]);
            dup2(p_stdout[1], 1);
        }
        execlp("sh", "sh", "-c", command, (char*)NULL);
        // should never get here
        int error = errno;
        n_log(LOG_ERR, "%s:%d: exec failed: %s", __FILE__, __LINE__, strerror(error));
        exit(42);
    }
    if (infp != NULL) {
        close(p_stdout[1]);
        *infp = p_stdout[0];
    }
    if (outfp != NULL) {
        close(p_stdin[0]);
        *outfp = p_stdin[1];
    }
    return pid;
} /* system_nb */

#endif /* ifndef __windows__ */


void N_HIDE_STR(char* buf, ...) {
    size_t it = 0;
    va_list args;

    va_start(args, buf);

    int arg = 0;

    while ((arg = va_arg(args, int))) {
        buf[it] = (char)arg;
        it++;
    }
    buf[it] = '\0';
    va_end(args);
} /* N_HIDE_STR */

char* n_get_file_extension(char path[]) {
    char* result = NULL;
    size_t i = 0, n = 0;

    __n_assert(path, return "");

    n = strlen(path);
    i = n - 1;
    while ((i > 0) && (path[i] != '.') && (path[i] != '/') && (path[i] != '\\')) {
        i--;
    }
    if ((i > 0) && (i < n - 1) && (path[i] == '.') && (path[i - 1] != '/') && (path[i - 1] != '\\')) {
        result = path + i;
    } else {
        result = path + n;
    }
    return result;
}