#include #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #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; }