root/usr/src/lib/librename/common/librename.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright (c) 2014 Joyent, Inc.  All rights reserved.
 */

#include <librename.h>

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/debug.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <synch.h>

typedef enum librename_atomic_state {
        LIBRENAME_ATOMIC_INITIAL = 0x0,
        LIBRENAME_ATOMIC_FSYNC,
        LIBRENAME_ATOMIC_RENAME,
        LIBRENAME_ATOMIC_POSTSYNC,
        LIBRENAME_ATOMIC_COMPLETED
} librename_atomic_state_t;

struct librename_atomic {
        char *lra_fname;                        /* RO */
        char *lra_altname;                      /* RO */
        int lra_dirfd;                          /* RO */
        int lra_tmpfd;                          /* RO */
        mutex_t lra_lock;
        librename_atomic_state_t lra_state;     /* lra_lock */
};

int
librename_atomic_fdinit(int fd, const char *file, const char *prefix,
    int mode, int flags, librename_atomic_t **outp)
{
        int ret;
        int oflags;
        librename_atomic_t *lrap;
        struct stat st;

        if (fd < 0 || file == NULL || outp == NULL)
                return (EINVAL);

        if (flags & ~(LIBRENAME_ATOMIC_NOUNLINK | LIBRENAME_ATOMIC_CLOEXEC))
                return (EINVAL);

        if (strchr(file, '/') != NULL)
                return (EINVAL);

        if (prefix != NULL && strchr(prefix, '/') != NULL)
                return (EINVAL);

        *outp = NULL;
        lrap = malloc(sizeof (librename_atomic_t));
        if (lrap == NULL)
                return (errno);

        if (fstat(fd, &st) != 0) {
                ret = errno;
                free(lrap);
                return (ret);
        }

        if (!S_ISDIR(st.st_mode)) {
                free(lrap);
                return (ENOTDIR);
        }

        if ((lrap->lra_dirfd = dup(fd)) == -1) {
                ret = errno;
                free(lrap);
                return (ret);
        }

        lrap->lra_fname = strdup(file);
        if (lrap->lra_fname == NULL) {
                ret = errno;
                VERIFY0(close(lrap->lra_dirfd));
                free(lrap);
                return (ret);
        }

        if (prefix == NULL) {
                ret = asprintf(&lrap->lra_altname, ".%d.%s", (int)getpid(),
                    file);
        } else {
                ret = asprintf(&lrap->lra_altname, "%s%s", prefix, file);
        }
        if (ret == -1) {
                ret = errno;
                free(lrap->lra_fname);
                VERIFY0(close(lrap->lra_dirfd));
                free(lrap);
                return (errno);
        }

        oflags = O_CREAT | O_TRUNC | O_RDWR | O_NOFOLLOW;
        if (flags & LIBRENAME_ATOMIC_NOUNLINK)
                oflags |= O_EXCL;

        if (flags & LIBRENAME_ATOMIC_CLOEXEC)
                oflags |= O_CLOEXEC;

        lrap->lra_tmpfd = openat(lrap->lra_dirfd, lrap->lra_altname,
            oflags, mode);
        if (lrap->lra_tmpfd < 0) {
                ret = errno;
                free(lrap->lra_altname);
                free(lrap->lra_fname);
                VERIFY0(close(lrap->lra_dirfd));
                free(lrap);
                return (ret);
        }

        VERIFY0(mutex_init(&lrap->lra_lock, USYNC_THREAD, NULL));

        lrap->lra_state = LIBRENAME_ATOMIC_INITIAL;
        *outp = lrap;
        return (0);
}

int
librename_atomic_init(const char *dir, const char *file, const char *prefix,
    int mode, int flags, librename_atomic_t **outp)
{
        int fd, ret;

        if ((fd = open(dir, O_RDONLY)) < 0)
                return (errno);

        ret = librename_atomic_fdinit(fd, file, prefix, mode, flags, outp);
        VERIFY0(close(fd));

        return (ret);
}

int
librename_atomic_fd(librename_atomic_t *lrap)
{
        return (lrap->lra_tmpfd);
}

/*
 * To atomically commit a file, we need to go through and do the following:
 *
 *  o fsync the source
 *  o run rename
 *  o fsync the source again and the directory.
 */
int
librename_atomic_commit(librename_atomic_t *lrap)
{
        int ret = 0;

        VERIFY0(mutex_lock(&lrap->lra_lock));
        if (lrap->lra_state == LIBRENAME_ATOMIC_COMPLETED) {
                ret = EINVAL;
                goto out;
        }

        if (fsync(lrap->lra_tmpfd) != 0) {
                ret = errno;
                goto out;
        }
        lrap->lra_state = LIBRENAME_ATOMIC_FSYNC;

        if (renameat(lrap->lra_dirfd, lrap->lra_altname, lrap->lra_dirfd,
            lrap->lra_fname) != 0) {
                ret = errno;
                goto out;
        }
        lrap->lra_state = LIBRENAME_ATOMIC_RENAME;

        if (fsync(lrap->lra_tmpfd) != 0) {
                ret = errno;
                goto out;
        }
        lrap->lra_state = LIBRENAME_ATOMIC_POSTSYNC;

        if (fsync(lrap->lra_dirfd) != 0) {
                ret = errno;
                goto out;
        }
        lrap->lra_state = LIBRENAME_ATOMIC_COMPLETED;

out:
        VERIFY0(mutex_unlock(&lrap->lra_lock));
        return (ret);
}

void
librename_atomic_fini(librename_atomic_t *lrap)
{

        free(lrap->lra_altname);
        free(lrap->lra_fname);
        VERIFY0(close(lrap->lra_tmpfd));
        VERIFY0(close(lrap->lra_dirfd));
        VERIFY0(mutex_destroy(&lrap->lra_lock));
        free(lrap);
}