// vim: ts=4 sw=4 expandtab ft=c

// Copyright (C) 2001-2002, 2006 The University of Melbourne.
// Copyright (C) 2014, 2016, 2018, 2023 The Mercury team.
// This file is distributed under the terms specified in COPYING.LIB.

// This module contains utility functions for the rest of the Mercury runtime.
//
// Author: petdr

#include    "mercury_imp.h"
#include    "mercury_runtime_util.h"
#include    "mercury_string.h"
#include    "mercury_windows.h"

#include    <stdio.h>
#include    <string.h>
#if defined(MR_WIN32) && !defined(MR_CYGWIN)
   #include <wchar.h>
#endif

#ifdef MR_HAVE_UNISTD_H
  #include  <unistd.h>
#endif

#include    <errno.h>

static void
generic_strerror(char *buf, size_t buflen, int errnum)
{
    MR_snprintf(buf, buflen, "Error %d", errnum);
}

const char *
MR_strerror(int errnum, char *buf, size_t buflen)
{
#if defined(MR_HAVE_STRERROR_S) && !defined(MR_MINGW)
    // MSVC has strerror_s. It also exists in C11 Annex K and is enabled by
    // defining a preprocessor macro __STDC_WANT_LIB_EXT1__
    //
    // On MinGW-w64, strerror_s results in an undefined reference to strerror_s
    // in MSVCRT.DLL on Windows XP. Avoid it until we drop support for XP.

    if (strerror_s(buf, buflen, errnum) != 0) {
        generic_strerror(buf, buflen, errnum);
    }
    return buf;
#elif defined(MR_HAVE_STRERROR_R)
    // The XSI-compliant and Mac OS X strerror_r populates buf unless it fails.
    // The GNU-specific strerror_r does not always populate buf.

  #if !defined(__GNU_LIBRARY__) || \
       ((_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE)
    int x = strerror_r(errnum, buf, buflen);
    if (x != 0) {
        generic_strerror(buf, buflen, errnum);
    }
    return buf;
  #else
    const char *s = strerror_r(errnum, buf, buflen);
    return s;
  #endif
#else
    // Fallback using deprecated variables. This is used on MinGW at least.
    //
    // strerror_l is another thread-safe alternative, specified in POSIX.
    // It is locale-sensitive and takes a locale argument so we don't use it
    // for now.

    if (errnum >= 0 && errnum < sys_nerr && sys_errlist[errnum] != NULL) {
        return sys_errlist[errnum];
    } else {
        generic_strerror(buf, buflen, errnum);
        return buf;
    }
#endif
}

FILE *
MR_checked_fopen(const char *filename, const char *message, const char *mode)
{
    FILE *file;
    char errbuf[MR_STRERROR_BUF_SIZE];

    errno = 0;
    file = fopen(filename, mode);
    if (file == NULL) {
        fprintf(stderr, "Mercury runtime: couldn't %s file `%s': %s\n",
            message, filename, MR_strerror(errno, errbuf, sizeof(errbuf)));
        exit(EXIT_FAILURE);
    }
    return file;
}

void
MR_checked_fclose(FILE *file, const char *filename)
{
    char errbuf[MR_STRERROR_BUF_SIZE];

    errno = 0;
    if (fclose(file) != 0) {
        fprintf(stderr, "Mercury runtime: error closing file `%s': %s\n",
            filename, MR_strerror(errno, errbuf, sizeof(errbuf)));
        exit(EXIT_FAILURE);
    }
}

void
MR_checked_atexit(void (*func)(void))
{
    char errbuf[MR_STRERROR_BUF_SIZE];

    errno = 0;
    if (atexit(func) != 0) {
        fprintf(stderr, "Mercury runtime: error in call to atexit: %s\n",
            MR_strerror(errno, errbuf, sizeof(errbuf)));
        exit(EXIT_FAILURE);
    }
}

#if ! defined(MR_HAVE_PUTENV) && defined(MR_HAVE__PUTENV)
  #define putenv _putenv
#endif

int
MR_setenv(const char *name, const char *value, int overwrite)
{
#if defined(MR_HAVE_SETENV)
    return setenv(name, value, overwrite);
#elif defined(MR_HAVE_PUTENV) || defined(MR_HAVE__PUTENV)
    char *env;
    int length;
    int result;

    if (!overwrite && getenv(name) != NULL) {
        return 0;
    }

    length = strlen(name) + strlen(value) + 2;
    env = MR_NEW_ARRAY(char, length);

    env[0] = '\0';
    strcat(env, name);
    strcat(env, "=");
    strcat(env, value);

    result = putenv(env);

    MR_free(env);

    return result;
#else
  #error "MR_setenv: unable to define"
#endif
}

const char *
MR_get_program_basename(const char *program_name)
{
    const char  *basename;

    #if defined(MR_WIN32) && !defined(MR_CYGWIN)

        wchar_t wname[_MAX_FNAME];

        errno_t err = _wsplitpath_s(MR_utf8_to_wide(program_name),
            NULL, 0,  // Ignore drive.
            NULL, 0,  // Ignore directories.
            wname, _MAX_FNAME,
            NULL, 0   // Ignore .exe extension.
        );
        if (err != 0) {
            MR_fatal_error("MR_get_program_basename: could not split path");
        }
        basename = MR_wide_to_utf8(wname, NULL);

    #else

        // NOTE: on Cygwin, the .exe extension, if present, will not be
        // included in argv[0], so we do not need to strip it here.

        char    *slash;

        basename = MR_copy_string(program_name);
        slash = strrchr(basename, '/');
        if (slash != NULL) {
            basename = slash + 1;
        }

    #endif

    return basename;
}
