/*
 * Copyright (c) 1987 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that: (1) source distributions retain this entire copyright
 * notice and comment, and (2) distributions including binaries display
 * the following acknowledgement:  ``This product includes software
 * developed by the University of California, Berkeley and its contributors''
 * in the documentation or other materials provided with the distribution.
 * Neither the name of the University nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */
/* This is file MKTEMP.C */
/* This file may have been modified by DJ Delorie (Jan 1991).  If so,
** these modifications are Copyright (C) 1991 DJ Delorie.
*/

/*
FUNCTION
<<mktemp>>, <<mkstemp>>, <<mkstemps>>,
<<mkostemps>>---generate unused file name

INDEX
        mktemp
INDEX
        mkstemp
INDEX
        mkstemps
INDEX
        mkostemps

SYNOPSIS
        #include <stdlib.h>
        char *mktemp(char *<[path]>);
        int mkstemp(char *<[path]>);
        int mkstemps(char *<[path]>, int suffixlen);
        int mkostemps(char *<[path]>, int suffixlen, int flags);

DESCRIPTION
<<mktemp>>, <<mkstemp>>, <<mkstemps>>, and <<mkostemps>> attempt to
generate a file name that is not yet in use for any existing file.
<<mkstemp>>, <<mkstemps>> and <mkostemps>> create the file and open it
for reading and writing; <<mktemp>> simply generates the file name
(making <<mktemp>> a security risk). <<mkostemps>> allow the addition
of other <<open>> flags, such as <<O_CLOEXEC>>, <<O_APPEND>>, or
<<O_SYNC>>.  On platforms with a separate text mode, <<mkstemp>>
forces <<O_BINARY>>, while <<mkostemp>> allows the choice between
<<O_BINARY>>, <<O_TEXT>>, or 0 for default.

You supply a simple pattern for the generated file name, as the string
at <[path]>.  The pattern should be a valid filename (including path
information if you wish) ending with at least six `<<X>>' characters.
The generated filename will match the leading part of the name you
supply, with the trailing `<<X>>' characters replaced by some
combination of digits and letters.  With <<mkstemps>> and
<mkostemps>>, the `<<X>>' characters end <[suffixlen]> bytes before
the end of the string.

RETURNS
<<mktemp>> returns the pointer <[path]> to the modified string
representing an unused filename, unless it could not generate one, or
the pattern you provided is not suitable for a filename; in that case,
it returns <<NULL>>.  Be aware that there is an inherent race between
generating the name and attempting to create a file by that name;
you are advised to use <<O_EXCL|O_CREAT>>.

<<mkstemp>>, <<mkstemps>> and <<mkostemps>> return a file descriptor
to the newly created file, unless it could not generate an unused
filename, or the pattern you provided is not suitable for a filename;
in that case, it returns <<-1>>.

NOTES
Never use <<mktemp>>.  The generated filenames are easy to guess and
there's a race between the test if the file exists and the creation
of the file.  In combination this makes <<mktemp>> prone to attacks
and using it is a security risk.  Whenever possible use <<mkstemp>>
instead.  It doesn't suffer the race condition.

PORTABILITY
ANSI C does not require either <<mktemp>> or <<mkstemp>>; the System V
Interface Definition requires <<mktemp>> as of Issue 2.  POSIX 2001
requires <<mkstemp>> while deprecating <<mktemp>>.  <<mkstemps>>, and
<<mkostemps>> are not standardized.

Supporting OS subroutines required: <<open>>
*/

#include "stdio_private.h"

/* Our names are six characters from [0-9a-z] */
#define NUM_NAMES (36UL * 36UL * 36UL * 36UL * 36UL * 36UL)

/* Attempts */
#define NUM_ATTEMPT (36UL * 36UL * 36UL)

static int
_gettemp(char *path, int suffixlen, int *doopen, int flags)
{
    char    *start, *trv;
    char    *end;
    uint32_t attempt;

    end = path + strlen(path) - suffixlen;
    trv = end;

    /* Replace 'X' with 'a' */
    while (path < trv && *--trv == 'X')
        *trv = 'a';

    /* Make sure we got six Xs */
    if (end - trv < 6) {
        errno = EINVAL;
        return 0;
    }

    start = trv + 1;

    for (attempt = 0; attempt < NUM_ATTEMPT; attempt++) {
        /* Generate a random filename index */
        long   filename = random() % NUM_NAMES;
        size_t i;

        /* Convert the random index into characters */
        for (i = 0; i < 6; i++) {
            char c = filename % 36;
            filename /= 36;

            if (c < 10)
                c += '0';
            else
                c += 'a' - 10;

            start[i] = c;
        }

        /*
         * Use open to check if the file exists to avoid depending on
         * stat or access. Don't rely on O_EXCL working, although if it
         * doesn't, this introduces a race condition
         */
        int fd = open(path, O_RDONLY);
        if (fd < 0) {
            if (errno != EACCES) {
                if (errno != ENOENT)
                    return 0;
                if (doopen) {
                    fd = open(path, flags | O_CREAT | O_EXCL | O_RDWR, 0600);
                    if (fd >= 0) {
                        *doopen = fd;
                        return 1;
                    }
                    if (errno != EEXIST)
                        return 0;
                } else {
                    return 1;
                }
            }
        } else
            close(fd);
    }
    return 0;
}

int
mkstemp(char *template)
{
    int fd;

    return (_gettemp(template, 0, &fd, 0) ? fd : -1);
}

char *
mktemp(char *template)
{
    return (_gettemp(template, 0, (int *)NULL, 0) ? template : (char *)NULL);
}

#ifndef O_BINARY
#define O_BINARY 0
#endif
#ifndef O_TEXT
#define O_TEXT 0
#endif

int
mkstemps(char *template, int suffixlen)
{
    int fd;

    return (_gettemp(template, suffixlen, &fd, O_BINARY) ? fd : -1);
}

int
mkostemps(char *template, int suffixlen, int flags)
{
    int fd;

    flags &= (O_APPEND | O_CLOEXEC | O_SYNC | O_BINARY | O_TEXT);
    return (_gettemp(template, suffixlen, &fd, flags) ? fd : -1);
}
