/*
 * fontconfig/src/fccompat.c
 *
 * Copyright © 2012 Red Hat, Inc.
 *
 * Author(s):
 *  Akira TAGOH
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the author(s) not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  The authors make no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#include <errno.h>

#include "fcint.h"
#if HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef O_CLOEXEC
#define FC_O_CLOEXEC O_CLOEXEC
#else
#define FC_O_CLOEXEC 0
#endif
#ifdef O_LARGEFILE
#define FC_O_LARGEFILE O_LARGEFILE
#else
#define FC_O_LARGEFILE 0
#endif
#ifdef O_BINARY
#define FC_O_BINARY O_BINARY
#else
#define FC_O_BINARY 0
#endif
#ifdef O_TEMPORARY
#define FC_O_TEMPORARY O_TEMPORARY
#else
#define FC_O_TEMPORARY 0
#endif
#ifdef O_NOINHERIT
#define FC_O_NOINHERIT O_NOINHERIT
#else
#define FC_O_NOINHERIT 0
#endif

#if !defined(HAVE_MKOSTEMP) && !defined(HAVE_MKSTEMP) &&                       \
    !defined(HAVE__MKTEMP_S)
static int mkstemp(char* template)
{
    static const char s[] =
        "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    int fd, i;
    size_t l;

    if (template == NULL)
    {
        errno = EINVAL;
        return -1;
    }
    l = strlen(template);
    if (l < 6 || strcmp(&template[l - 6], "XXXXXX") != 0)
    {
        errno = EINVAL;
        return -1;
    }
    do
    {
        errno = 0;
        for (i = l - 6; i < l; i++)
        {
            int r = FcRandom();
            template[i] = s[r % 62];
        }
        fd = FcOpen(template,
                    FC_O_BINARY | O_CREAT | O_EXCL | FC_O_TEMPORARY |
                        FC_O_NOINHERIT | O_RDWR,
                    0600);
    } while (fd < 0 && errno == EEXIST);
    if (fd >= 0)
        errno = 0;

    return fd;
}
#define HAVE_MKSTEMP 1
#endif

int FcOpen(const char* pathname, int flags, ...)
{
    int fd = -1;

    if (flags & O_CREAT)
    {
        va_list ap;
        mode_t mode;

        va_start(ap, flags);
        mode = (mode_t)va_arg(ap, int);
        va_end(ap);

        fd = open(pathname, flags | FC_O_CLOEXEC | FC_O_LARGEFILE, mode);
    }
    else
    {
        fd = open(pathname, flags | FC_O_CLOEXEC | FC_O_LARGEFILE);
    }

    return fd;
}

int FcMakeTempfile(char* template)
{
    int fd = -1;

#if HAVE_MKOSTEMP
    fd = mkostemp(template, FC_O_CLOEXEC);
#elif HAVE_MKSTEMP
    fd = mkstemp(template);
#ifdef F_DUPFD_CLOEXEC
    if (fd != -1)
    {
        int newfd = fcntl(fd, F_DUPFD_CLOEXEC, STDIN_FILENO);

        close(fd);
        fd = newfd;
    }
#elif defined(FD_CLOEXEC)
    if (fd != -1)
    {
        fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
    }
#endif
#elif HAVE__MKTEMP_S
    if (_mktemp_s(template, strlen(template) + 1) != 0)
        return -1;
    fd = FcOpen(template, O_RDWR | O_EXCL | O_CREAT, 0600);
#endif

    return fd;
}

int32_t FcRandom(void)
{
    int32_t result;

#if HAVE_RANDOM_R
    static struct random_data fcrandbuf;
    static char statebuf[256];
    static FcBool initialized = FcFalse;
#ifdef _AIX
    static char* retval;
    long res;
#endif

    if (initialized != FcTrue)
    {
#ifdef _AIX
        initstate_r(time(NULL), statebuf, 256, &retval, &fcrandbuf);
#else
        initstate_r(time(NULL), statebuf, 256, &fcrandbuf);
#endif
        initialized = FcTrue;
    }

#ifdef _AIX
    random_r(&res, &fcrandbuf);
    result = (int32_t)res;
#else
    random_r(&fcrandbuf, &result);
#endif
#elif HAVE_RANDOM
    static char statebuf[256];
    char* state;
    static FcBool initialized = FcFalse;

    if (initialized != FcTrue)
    {
        state = initstate(time(NULL), statebuf, 256);
        initialized = FcTrue;
    }
    else
        state = setstate(statebuf);

    result = random();

    setstate(state);
#elif HAVE_LRAND48
    result = lrand48();
#elif HAVE_RAND_R
    static unsigned int seed = time(NULL);

    result = rand_r(&seed);
#elif HAVE_RAND
    static FcBool initialized = FcFalse;

    if (initialized != FcTrue)
    {
        srand(time(NULL));
        initialized = FcTrue;
    }
    result = rand();
#else
#error no random number generator function available.
#endif

    return result;
}

#ifdef _WIN32
#include <direct.h>
#define mkdir(path, mode) _mkdir(path)
#endif

FcBool FcMakeDirectory(const FcChar8* dir)
{
    FcChar8* parent;
    FcBool ret;

    if (strlen((char*)dir) == 0)
        return FcFalse;

    parent = FcStrDirname(dir);
    if (!parent)
        return FcFalse;
    if (access((char*)parent, F_OK) == 0)
        ret = mkdir((char*)dir, 0755) == 0 && chmod((char*)dir, 0755) == 0;
    else if (access((char*)parent, F_OK) == -1)
        ret = FcMakeDirectory(parent) && (mkdir((char*)dir, 0755) == 0) &&
              chmod((char*)dir, 0755) == 0;
    else
        ret = FcFalse;
    FcStrFree(parent);
    return ret;
}

ssize_t FcReadLink(const FcChar8* pathname, FcChar8* buf, size_t bufsiz)
{
#ifdef HAVE_READLINK
    return readlink((const char*)pathname, (char*)buf, bufsiz);
#else
    /* XXX: this function is only used for FcConfigRealFilename() so far
     * and returning -1 as an error still just works.
     */
    errno = ENOSYS;
    return -1;
#endif
}

#define __fccompat__
#include "fcaliastail.h"
#undef __fccompat__
