/*-
 * Copyright (c) 2014-present MongoDB, Inc.
 * Copyright (c) 2008-2014 WiredTiger, Inc.
 *	All rights reserved.
 *
 * See the file LICENSE for redistribution information.
 */

#include "wt_internal.h"

/*
 * __nfilename --
 *     Build a file name in a scratch buffer. If the name is already an absolute path duplicate it,
 *     otherwise generate a path relative to the connection home directory.
 */
static int
__nfilename(WT_SESSION_IMPL *session, const char *name, size_t namelen, char **path)
{
    WT_DECL_RET;
    size_t len;
    char *buf;

    *path = NULL;

    /*
     * Needs to work with a NULL session handle - since this is called via the exists API which is
     * used by the test utilities.
     */
    if (session == NULL || __wt_absolute_path(name))
        return (__wt_strndup(session, name, namelen, path));

    len = strlen(S2C(session)->home) + 1 + namelen + 1;
    WT_RET(__wt_calloc(session, 1, len, &buf));
    WT_ERR(__wt_snprintf(
      buf, len, "%s%s%.*s", S2C(session)->home, __wt_path_separator(), (int)namelen, name));
    *path = buf;
    return (0);

err:
    __wt_free(session, buf);
    return (ret);
}

/*
 * __wt_filename --
 *     Build a file name in a scratch buffer, automatically calculate the length of the file name.
 */
int
__wt_filename(WT_SESSION_IMPL *session, const char *name, char **path)
{
    return (__nfilename(session, name, strlen(name), path));
}

/*
 * __wt_filename_construct --
 *     Given unique identifiers, return a WT_ITEM of a generated file name of the given prefix type.
 *     Any identifier that is 0 will be skipped.
 */
int
__wt_filename_construct(WT_SESSION_IMPL *session, const char *path, const char *file_prefix,
  uintmax_t id_1, uint32_t id_2, WT_ITEM *buf)
{
    if (path != NULL && path[0] != '\0')
        WT_RET(__wt_buf_catfmt(session, buf, "%s%s", path, __wt_path_separator()));
    WT_RET(__wt_buf_catfmt(session, buf, "%s", file_prefix));
    if (id_1 != UINTMAX_MAX)
        WT_RET(__wt_buf_catfmt(session, buf, ".%010" PRIuMAX, id_1));
    if (id_2 != UINT32_MAX)
        WT_RET(__wt_buf_catfmt(session, buf, ".%010" PRIu32, id_2));

    return (0);
}

/*
 * __wt_remove_if_exists --
 *     Remove a file if it exists and return error if WT_CONN_READONLY is set.
 */
int
__wt_remove_if_exists(WT_SESSION_IMPL *session, const char *name, bool durable)
{
    bool exist;

    WT_RET(__wt_fs_exist(session, name, &exist));
    if (exist) {
        /* Modifications are not allowed on a read only database. */
        if (F_ISSET(S2C(session), WT_CONN_READONLY))
            return (EACCES);
        WT_RET(__wt_fs_remove(session, name, durable, false));
    }
    return (0);
}

/*
 * __wt_copy_and_sync --
 *     Copy a file safely.
 */
int
__wt_copy_and_sync(WT_SESSION *wt_session, const char *from, const char *to)
  WT_GCC_FUNC_ATTRIBUTE((visibility("default")))
{
    WT_DECL_ITEM(tmp);
    WT_DECL_RET;
    WT_FH *ffh, *tfh;
    WT_SESSION_IMPL *session;
    wt_off_t n, offset, size;
    char *buf;

    session = (WT_SESSION_IMPL *)wt_session;
    ffh = tfh = NULL;
    buf = NULL;

    /*
     * Remove the target file if it exists, then create a temporary file, copy the original into it
     * and rename it into place. I don't think its necessary to remove the file, or create a copy
     * and do a rename, it's likely safe to overwrite the backup file directly. I'm doing the remove
     * and rename to insulate us from errors in other programs that might not detect a corrupted
     * backup file; it's cheap insurance in a path where undetected failure is very bad.
     */
    WT_ERR(__wt_scr_alloc(session, 0, &tmp));
    WT_ERR(__wt_buf_fmt(session, tmp, "%s.copy", to));

    WT_ERR(__wt_remove_if_exists(session, to, false));
    WT_ERR(__wt_remove_if_exists(session, tmp->data, false));

    /* Open the from and temporary file handles. */
    WT_ERR(__wt_open(session, from, WT_FS_OPEN_FILE_TYPE_REGULAR, 0, &ffh));
    WT_ERR(__wt_open(session, tmp->data, WT_FS_OPEN_FILE_TYPE_REGULAR,
      WT_FS_OPEN_CREATE | WT_FS_OPEN_EXCLUSIVE, &tfh));

/*
 * Allocate a copy buffer. Don't use a scratch buffer, this thing is big, and we don't want it
 * hanging around.
 */
#define WT_BACKUP_COPY_SIZE (128 * 1024)
    WT_ERR(__wt_malloc(session, WT_BACKUP_COPY_SIZE, &buf));

    /* Get the file's size, then copy the bytes. */
    WT_ERR(__wt_filesize(session, ffh, &size));
    for (offset = 0; size > 0; size -= n, offset += n) {
        n = WT_MIN(size, WT_BACKUP_COPY_SIZE);
        WT_ERR(__wt_read(session, ffh, offset, (size_t)n, buf));
        WT_ERR(__wt_write(session, tfh, offset, (size_t)n, buf));
    }

    /* Close the from handle, then swap the temporary file into place. */
    WT_ERR(__wt_close(session, &ffh));
    WT_ERR(__wt_fsync(session, tfh, true));
    WT_ERR(__wt_close(session, &tfh));

    ret = __wt_fs_rename(session, tmp->data, to, true);

err:
    WT_TRET(__wt_close(session, &ffh));
    WT_TRET(__wt_close(session, &tfh));

    __wt_free(session, buf);
    __wt_scr_free(session, &tmp);
    return (ret);
}
