#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;
char *lra_altname;
int lra_dirfd;
int lra_tmpfd;
mutex_t lra_lock;
librename_atomic_state_t lra_state;
};
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);
}
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);
}