7/19
This commit is contained in:
420
src/transaction.c
Normal file
420
src/transaction.c
Normal file
@@ -0,0 +1,420 @@
|
||||
#include <linux/limits.h>
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "istoreutils.h"
|
||||
#include "log.h"
|
||||
#include "transaction.h"
|
||||
|
||||
#define GL_STAGE_BASE "/glacier/usr/stage"
|
||||
#define GL_LOCK_BASE "/glacier/usr/lock"
|
||||
#define GL_OLD_BASE "/glacier/usr/old"
|
||||
|
||||
#define GL_USR_INDEX "/glacier/usr/index"
|
||||
#define GL_USR_STORE "/glacier/usr/store"
|
||||
#define GL_USR_LINKS "/glacier/usr/links"
|
||||
|
||||
#define GL_SYS_INDEX "/glacier/sys/index"
|
||||
#define GL_SYS_STORE "/glacier/sys/store"
|
||||
#define GL_SYS_LINKS "/glacier/sys/links"
|
||||
|
||||
static int
|
||||
fsync_dir(const char *path)
|
||||
{
|
||||
int fd = open(path, O_RDONLY | O_DIRECTORY);
|
||||
if (fd < 0) {
|
||||
return -1;
|
||||
}
|
||||
int r = fsync(fd);
|
||||
close(fd);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
atomic_swap(const char *live, const char *stage, const char *backup)
|
||||
{
|
||||
if (rename(live, backup) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rename(stage, live) != 0) {
|
||||
rename(backup, live);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
derive_base_paths(gl_ctx_scope_t scope, uid_t uid, char *index_out,
|
||||
char *store_out, char *links_out)
|
||||
{
|
||||
switch (scope) {
|
||||
case GL_SCOPE_USR:
|
||||
snprintf(index_out, PATH_MAX, "/glacier/usr/index/%u", uid);
|
||||
snprintf(store_out, PATH_MAX, "/glacier/usr/store/%u", uid);
|
||||
snprintf(links_out, PATH_MAX, "/glacier/usr/links/%u", uid);
|
||||
break;
|
||||
case GL_SCOPE_SYS:
|
||||
strncpy(index_out, "/glacier/sys/index", PATH_MAX - 1);
|
||||
strncpy(store_out, "/glacier/sys/store", PATH_MAX - 1);
|
||||
/* This is the DEFAULT system-scope links location — for
|
||||
* packages the operator explicitly installs under system
|
||||
* scope that aren't part of the base system. Base-repo
|
||||
* packages get routed to /usr instead, but that's a
|
||||
* per-package decision made in gl_link_pkg/gl_relink_store
|
||||
* (which know a package's repo), not something the context
|
||||
* can decide up front. See GL_BASE_REPO in istoreutils.c. */
|
||||
strncpy(links_out, "/glacier/sys/links", PATH_MAX - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
check_same_filesystem(const char *a, const char *b)
|
||||
{
|
||||
struct stat sa, sb;
|
||||
if (stat(a, &sa) != 0 || stat(b, &sb) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return sa.st_dev == sb.st_dev;
|
||||
}
|
||||
|
||||
static int
|
||||
hardlink_tree(const char *src, const char *dest)
|
||||
{
|
||||
DIR *dir = opendir(src);
|
||||
if (!dir) {
|
||||
return (errno == ENOENT) ? 0 : -1;
|
||||
}
|
||||
|
||||
struct dirent *ent;
|
||||
while ((ent = readdir(dir))) {
|
||||
if (ent->d_name[0] == '.') { continue; }
|
||||
|
||||
char s[PATH_MAX], d[PATH_MAX];
|
||||
snprintf(s, sizeof(s), "%s/%s", src, ent->d_name);
|
||||
snprintf(d, sizeof(d), "%s/%s", dest, ent->d_name);
|
||||
|
||||
struct stat st;
|
||||
if(lstat(s, &st) != 0) { continue; }
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
if (mkdir(d, st.st_mode & 0777) != 0 && errno != EEXIST) {
|
||||
closedir(dir);
|
||||
return -1;
|
||||
}
|
||||
if (hardlink_tree(s, d) != 0) {
|
||||
closedir(dir);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (S_ISLNK(st.st_mode)) {
|
||||
char target[PATH_MAX];
|
||||
ssize_t n = readlink(s, target, sizeof(target) - 1);
|
||||
if (n < 0) { continue; }
|
||||
target[n] = '\0';
|
||||
symlink(target, d);
|
||||
}
|
||||
else {
|
||||
if (link(s, d) != 0 && errno != EEXIST) {
|
||||
closedir(dir);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
gl_init_live_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid)
|
||||
{
|
||||
if (!ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ctx->uid = uid;
|
||||
ctx->mode = GL_CTX_LIVE;
|
||||
ctx->scope = scope;
|
||||
ctx->lock_fd = -1;
|
||||
|
||||
strncpy(ctx->root_path, "/glacier", PATH_MAX - 1);
|
||||
derive_base_paths(scope, uid, ctx->index_path, ctx->store_path,
|
||||
ctx->links_path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
gl_txn_status_t
|
||||
gl_init_stage_context(gl_context_t *ctx, gl_ctx_scope_t scope, uid_t uid)
|
||||
{
|
||||
if (!ctx) {
|
||||
return GL_TXN_ERR_NO_TXN;
|
||||
}
|
||||
|
||||
ctx->uid = uid;
|
||||
ctx->mode = GL_CTX_STAGE;
|
||||
ctx->scope = scope;
|
||||
ctx->lock_fd = -1;
|
||||
|
||||
strncpy(ctx->root_path, "/glacier", PATH_MAX - 1);
|
||||
|
||||
char stage_base[PATH_MAX - 7];
|
||||
|
||||
switch (scope) {
|
||||
case GL_SCOPE_USR:
|
||||
snprintf(stage_base, sizeof(stage_base), "/glacier/usr/stage/%u", uid);
|
||||
break;
|
||||
case GL_SCOPE_SYS:
|
||||
strncpy(stage_base, "/glacier/sys/stage", sizeof(stage_base));
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(ctx->index_path, PATH_MAX, "%s/index", stage_base);
|
||||
snprintf(ctx->store_path, PATH_MAX, "%s/store", stage_base);
|
||||
snprintf(ctx->links_path, PATH_MAX, "%s/links", stage_base);
|
||||
|
||||
struct stat st;
|
||||
if (stat(stage_base, &st) == 0) {
|
||||
if (gl_rmdir_recursive(stage_base) != 0) {
|
||||
return GL_TXN_ERR_MKDIR;
|
||||
}
|
||||
}
|
||||
|
||||
if (gl_mkdirp(GL_LOCK_BASE, 0700) != 0 && errno != EEXIST) {
|
||||
return GL_TXN_ERR_MKDIR;
|
||||
}
|
||||
|
||||
gl_txn_status_t lock_status = gl_begin_transaction(ctx);
|
||||
if (lock_status != GL_TXN_OK) {
|
||||
return lock_status;
|
||||
}
|
||||
|
||||
if (gl_mkdirp(ctx->index_path, 0700) != 0) {
|
||||
gl_abort_transaction(ctx);
|
||||
return GL_TXN_ERR_MKDIR;
|
||||
}
|
||||
|
||||
if (gl_mkdirp(ctx->store_path, 0700) != 0) {
|
||||
gl_abort_transaction(ctx);
|
||||
return GL_TXN_ERR_MKDIR;
|
||||
}
|
||||
|
||||
if (gl_mkdirp(ctx->links_path, 0700) != 0) {
|
||||
gl_abort_transaction(ctx);
|
||||
return GL_TXN_ERR_MKDIR;
|
||||
}
|
||||
|
||||
if (chown(stage_base, uid, (gid_t)-1) != 0) {
|
||||
gl_abort_transaction(ctx);
|
||||
return GL_TXN_ERR_PERM;
|
||||
}
|
||||
|
||||
char live_index[PATH_MAX], live_store[PATH_MAX], live_links[PATH_MAX];
|
||||
derive_base_paths(scope, uid, live_index, live_store, live_links);
|
||||
/* live_links is populated (derive_base_paths always fills all
|
||||
* three) but intentionally unused below — see the comment on the
|
||||
* hardlink_tree call. */
|
||||
|
||||
/* Only index and store need copy-on-write seeding — the staged
|
||||
* links tree is never actually read from or written to (gl_link_pkg
|
||||
* skips GL_CTX_STAGE entirely; gl_relink_store always builds its
|
||||
* own fresh live context rather than touching ctx->links_path of a
|
||||
* stage context). Seeding it was always wasted work; now that
|
||||
* live_links can be /usr for system scope, it would also be
|
||||
* actively dangerous — hardlinking the entire /usr tree on every
|
||||
* transaction, and risking EXDEV outright if /usr and the stage
|
||||
* area are on different filesystems. */
|
||||
if (hardlink_tree(live_index, ctx->index_path) != 0 ||
|
||||
hardlink_tree(live_store, ctx->store_path) != 0) {
|
||||
gl_abort_transaction(ctx);
|
||||
return GL_TXN_ERR_MKDIR;
|
||||
}
|
||||
|
||||
return GL_TXN_OK;
|
||||
}
|
||||
|
||||
gl_txn_status_t
|
||||
gl_begin_transaction(gl_context_t *ctx)
|
||||
{
|
||||
if (!ctx) {
|
||||
return GL_TXN_ERR_NO_TXN;
|
||||
}
|
||||
|
||||
char lock_path[PATH_MAX];
|
||||
switch (ctx->scope) {
|
||||
case GL_SCOPE_USR:
|
||||
snprintf(lock_path, PATH_MAX, "%s/%u.lock", GL_LOCK_BASE,
|
||||
ctx->uid);
|
||||
break;
|
||||
case GL_SCOPE_SYS:
|
||||
snprintf(lock_path, PATH_MAX, "%s/sys.lock", GL_LOCK_BASE);
|
||||
break;
|
||||
}
|
||||
|
||||
int fd = open(lock_path, O_WRONLY | O_CREAT, 0600);
|
||||
if (fd < 0) {
|
||||
return GL_TXN_ERR_LOCK;
|
||||
}
|
||||
|
||||
if (flock(fd, LOCK_EX | LOCK_NB) != 0) {
|
||||
close(fd);
|
||||
return GL_TXN_ERR_LOCK;
|
||||
}
|
||||
|
||||
ctx->lock_fd = fd;
|
||||
return GL_TXN_OK;
|
||||
}
|
||||
|
||||
gl_txn_status_t
|
||||
gl_commit_transaction(gl_context_t *ctx)
|
||||
{
|
||||
if (!ctx || ctx->lock_fd < 0) {
|
||||
return GL_TXN_ERR_NO_TXN;
|
||||
}
|
||||
if (ctx->mode != GL_CTX_STAGE) {
|
||||
return GL_TXN_ERR_NO_TXN;
|
||||
}
|
||||
|
||||
char stage_base[PATH_MAX];
|
||||
switch (ctx->scope) {
|
||||
case GL_SCOPE_USR:
|
||||
snprintf(stage_base, PATH_MAX, "/glacier/usr/stage/%u",
|
||||
ctx->uid);
|
||||
break;
|
||||
case GL_SCOPE_SYS:
|
||||
strncpy(stage_base, "/glacier/sys/stage", PATH_MAX - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
int same = check_same_filesystem(ctx->root_path, stage_base);
|
||||
if (same < 0) {
|
||||
return GL_TXN_ERR_RENAME; /* failed to stat either side */
|
||||
}
|
||||
if (!same) {
|
||||
return GL_TXN_ERR_XDEV;
|
||||
}
|
||||
|
||||
/* Derive live paths the same way gl_init_live_context would */
|
||||
char live_index[PATH_MAX], live_store[PATH_MAX], live_links[PATH_MAX];
|
||||
derive_base_paths(ctx->scope, ctx->uid, live_index, live_store,
|
||||
live_links);
|
||||
|
||||
/* Derive old/backup paths */
|
||||
char old_base[PATH_MAX];
|
||||
switch (ctx->scope) {
|
||||
case GL_SCOPE_USR:
|
||||
snprintf(old_base, PATH_MAX, "/glacier/usr/old/%u", ctx->uid);
|
||||
break;
|
||||
case GL_SCOPE_SYS:
|
||||
strncpy(old_base, "/glacier/sys/old", PATH_MAX - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
char old_index[PATH_MAX], old_store[PATH_MAX], old_links[PATH_MAX];
|
||||
snprintf(old_index, PATH_MAX, "%s/index", old_base);
|
||||
snprintf(old_store, PATH_MAX, "%s/store", old_base);
|
||||
snprintf(old_links, PATH_MAX, "%s/links", old_base);
|
||||
|
||||
struct stat old_st;
|
||||
if (stat(old_base, &old_st) == 0) {
|
||||
if (gl_rmdir_recursive(old_base) != 0) {
|
||||
return GL_TXN_ERR_RENAME;
|
||||
}
|
||||
}
|
||||
|
||||
if (gl_mkdirp(old_base, 0700) != 0) {
|
||||
return GL_TXN_ERR_RENAME;
|
||||
}
|
||||
|
||||
if (atomic_swap(live_index, ctx->index_path, old_index) != 0) {
|
||||
return GL_TXN_ERR_RENAME;
|
||||
}
|
||||
if (atomic_swap(live_store, ctx->store_path, old_store) != 0) {
|
||||
rename(live_index, ctx->index_path);
|
||||
rename(old_index, live_index);
|
||||
return GL_TXN_ERR_RENAME;
|
||||
}
|
||||
if (atomic_swap(live_links, ctx->links_path, old_links) != 0) {
|
||||
rename(live_store, ctx->store_path);
|
||||
rename(old_store, live_store);
|
||||
rename(live_index, ctx->index_path);
|
||||
rename(old_index, live_index);
|
||||
return GL_TXN_ERR_RENAME;
|
||||
}
|
||||
|
||||
/* fsync the parent directories, not the uid subdirs */
|
||||
switch (ctx->scope) {
|
||||
case GL_SCOPE_USR:
|
||||
fsync_dir("/glacier/usr/index");
|
||||
fsync_dir("/glacier/usr/store");
|
||||
fsync_dir("/glacier/usr/links");
|
||||
break;
|
||||
case GL_SCOPE_SYS:
|
||||
fsync_dir("/glacier/sys/index");
|
||||
fsync_dir("/glacier/sys/store");
|
||||
fsync_dir("/glacier/sys/links");
|
||||
fsync_dir("/usr"); /* base-repo packages land here instead */
|
||||
break;
|
||||
}
|
||||
|
||||
gl_context_t live_ctx;
|
||||
gl_init_live_context(&live_ctx, ctx->scope, ctx->uid);
|
||||
|
||||
if (gl_relink_store(&live_ctx) != 0) {
|
||||
lg_printf(2, "Post-commit relink incomplete for uid %u", ctx->uid);
|
||||
}
|
||||
|
||||
gl_rmdir_recursive(old_base);
|
||||
|
||||
/* Remove stage base */
|
||||
gl_rmdir_recursive(stage_base);
|
||||
|
||||
close(ctx->lock_fd);
|
||||
ctx->lock_fd = -1;
|
||||
|
||||
return GL_TXN_OK;
|
||||
}
|
||||
|
||||
gl_txn_status_t
|
||||
gl_abort_transaction(gl_context_t *ctx)
|
||||
{
|
||||
if (!ctx) {
|
||||
return GL_TXN_ERR_NO_TXN;
|
||||
}
|
||||
|
||||
/* Remove stage area — live is untouched */
|
||||
char stage_uid[PATH_MAX];
|
||||
switch (ctx->scope) {
|
||||
case GL_SCOPE_USR:
|
||||
snprintf(stage_uid, PATH_MAX, "/glacier/usr/stage/%u",
|
||||
ctx->uid);
|
||||
break;
|
||||
case GL_SCOPE_SYS:
|
||||
strncpy(stage_uid, "/glacier/sys/stage", PATH_MAX - 1);
|
||||
break;
|
||||
}
|
||||
|
||||
gl_rmdir_recursive(stage_uid); /* best-effort; ignore errors */
|
||||
|
||||
/* Release lock */
|
||||
|
||||
if (ctx->lock_fd >= 0) {
|
||||
close(ctx->lock_fd);
|
||||
ctx->lock_fd = -1;
|
||||
}
|
||||
|
||||
return GL_TXN_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user