Private
Public Access
Release 0.13.0
This commit is contained in:
1 parent
7f6eeab39e
commit
c0d891b372
49 files changed
+1523
-478
No files matched your search
@@ -0,0 +1,91 @@
|
||||
#ifndef HYPERDRM_ATOMIC_FILE_H
|
||||
#define HYPERDRM_ATOMIC_FILE_H
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#ifndef ATOMIC_WRITE
|
||||
#define ATOMIC_WRITE write
|
||||
#endif
|
||||
#ifndef ATOMIC_SYNC
|
||||
#define ATOMIC_SYNC fsync
|
||||
#endif
|
||||
#ifndef ATOMIC_RENAME
|
||||
#define ATOMIC_RENAME renameat
|
||||
#endif
|
||||
typedef enum { SAVE_UNCHANGED, SAVE_DURABLE, SAVE_COMMITTED } SaveResult;
|
||||
/* Failure before rename preserves the previous file. After rename, a directory
|
||||
* sync failure cannot truthfully be called a rollback: report committed with
|
||||
* uncertain durability, so memory and disk never deliberately diverge. */
|
||||
static inline SaveResult atomic_replace(const char *path, const void *data, size_t length) {
|
||||
char parent[512], temporary[544];
|
||||
if (!path || !data || strlen(path) >= sizeof(parent))
|
||||
return SAVE_UNCHANGED;
|
||||
strcpy(parent, path);
|
||||
char *slash = strrchr(parent, '/');
|
||||
const char *name = slash ? strrchr(path, '/') + 1 : path;
|
||||
if (!*name || !strcmp(name, ".") || !strcmp(name, ".."))
|
||||
return SAVE_UNCHANGED;
|
||||
if (slash) {
|
||||
if (slash == parent)
|
||||
slash[1] = 0;
|
||||
else
|
||||
*slash = 0;
|
||||
} else
|
||||
strcpy(parent, ".");
|
||||
int dir = open(parent, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
||||
if (dir < 0)
|
||||
return SAVE_UNCHANGED;
|
||||
struct stat existing;
|
||||
int found = fstatat(dir, name, &existing, AT_SYMLINK_NOFOLLOW);
|
||||
if ((found < 0 && errno != ENOENT) || (found == 0 && !S_ISREG(existing.st_mode)) ||
|
||||
ATOMIC_SYNC(dir) != 0) {
|
||||
close(dir);
|
||||
return SAVE_UNCHANGED;
|
||||
}
|
||||
if (snprintf(temporary, sizeof(temporary), "%s/.%s.tmp.XXXXXX", parent, name) >=
|
||||
(int)sizeof(temporary)) {
|
||||
close(dir);
|
||||
return SAVE_UNCHANGED;
|
||||
}
|
||||
int fd = mkstemp(temporary);
|
||||
if (fd < 0) {
|
||||
close(dir);
|
||||
return SAVE_UNCHANGED;
|
||||
}
|
||||
fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||
const unsigned char *bytes = data;
|
||||
size_t written = 0;
|
||||
bool ok = true;
|
||||
while (written < length) {
|
||||
ssize_t n = ATOMIC_WRITE(fd, bytes + written, length - written);
|
||||
if (n < 0 && errno == EINTR)
|
||||
continue;
|
||||
if (n <= 0) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
written += (size_t)n;
|
||||
}
|
||||
if (ok)
|
||||
ok = ATOMIC_SYNC(fd) == 0;
|
||||
if (close(fd) != 0)
|
||||
ok = false;
|
||||
const char *tempname = strrchr(temporary, '/') + 1;
|
||||
if (ok)
|
||||
ok = ATOMIC_RENAME(dir, tempname, dir, name) == 0;
|
||||
SaveResult result = SAVE_UNCHANGED;
|
||||
if (ok) {
|
||||
result = ATOMIC_SYNC(dir) == 0 ? SAVE_DURABLE : SAVE_COMMITTED;
|
||||
if (result == SAVE_COMMITTED)
|
||||
fprintf(stderr, "Preference committed; directory sync failed\n");
|
||||
} else
|
||||
unlinkat(dir, tempname, 0);
|
||||
close(dir);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
Reference in new issue
Block a user