This commit is contained in:
lw-everestlinux committed 2026-07-19 17:49:23 -04:00
1 parent 0d8859b063
commit 7b22e3746a
29 files changed
+3949 -193

No files matched your search

+401 -178
View File
@@ -21,6 +21,7 @@
#include "istoreutils.h"
#include "log.h"
#include "pkg.h"
#include "transaction.h"
static const dir_t usr_index = { "/glacier/usr/index", 0700 };
static const dir_t sys_index = { "/glacier/sys/index", 0700 };
@@ -29,6 +30,17 @@ static const dir_t usr_store = { "/glacier/usr/store", 0700 };
static const dir_t sys_store = { "/glacier/sys/store", 0700 };
static const dir_t usr_links = { "/glacier/usr/links", 0700 };
static const dir_t sys_links = { "/glacier/sys/links", 0700 };
/* Base-system exception: packages in the "base" repo under system
* scope get their symlinks placed directly in /usr rather than the
* default isolated /glacier/sys/links, so boot/init/scripts that
* hardcode /usr/bin, /usr/lib, etc. work without glacier-aware PATH
* setup. Everything else installed under system scope (an operator
* running `gpkg -s` on something that isn't part of the base system)
* stays under /glacier/sys/links. See gl_link_pkg / gl_relink_store. */
#define GL_BASE_REPO "base"
#define GL_BASE_LINKS_DEST "/usr"
bool
gl_usr_istore_exists(index_store_t index_or_store, int uid)
@@ -115,6 +127,36 @@ create_index(const char *ind_path, uid_t uid)
return 0;
}
static bool
is_dir(const char *d)
{
struct stat st;
return stat(d, &st) == 0 && S_ISDIR(st.st_mode);
}
/*
* is_glacier_symlink
* Returns true only if `path` is a symlink whose target lives under
* `store_prefix` — i.e. a symlink glacier itself created, as opposed
* to some unrelated file, directory, or foreign symlink that happens
* to occupy that path. This distinction only matters once a scope's
* links_path can alias a directory glacier doesn't exclusively own
* (e.g. /usr for system-scope packages) rather than always being an
* isolated glacier-only tree. Everywhere this is used, the rule is the
* same: if this returns false, never touch the path — not overwrite
* it, not prune it, nothing.
*/
static bool
is_glacier_symlink(const char *path, const char *store_prefix)
{
char target[PATH_MAX];
ssize_t n = readlink(path, target, sizeof(target) - 1);
if (n < 0) { return false; } /* not a symlink, or doesn't exist */
target[n] = '\0';
return strncmp(target, store_prefix, strlen(store_prefix)) == 0;
}
int
gl_init_user(uid_t uid, bool isVerbose)
{
@@ -253,6 +295,46 @@ gl_init_user(uid_t uid, bool isVerbose)
return 0;
}
int
gl_init_sys(bool isVerbose)
{
const char *paths[] = {
sys_index.path,
sys_store.path,
sys_links.path,
};
mode_t modes[] = {
sys_index.mode,
sys_store.mode,
sys_links.mode,
};
for (int i = 0; i < 3; i++) {
if (isVerbose) { lg_printf(0, "Creating %s", paths[i]); }
if (mkdir(paths[i], modes[i]) == -1) {
switch (errno) {
case EEXIST:
break;
case EACCES:
lg_printf(2, "Cannot create %s: permission denied", paths[i]);
return 1;
case ENOSPC:
lg_printf(2, "Cannot create %s: no space left on device", paths[i]);
return 1;
case EROFS:
lg_printf(2, "Cannot create %s: read-only filesystem", paths[i]);
return 1;
default:
lg_printf(2, "Cannot create %s: mkdir failed", paths[i]);
return -1;
}
}
}
return 0;
}
static void
trim(char *s)
{
@@ -313,17 +395,34 @@ gl_parse_index(struct gindex *idx, FILE *f)
char *first = strstr(line, "::");
if (!first) { continue; }
*first = 0;
char *repo = line;
char *pkg = first + 2;
char *rest = first + 2;
char *second = strstr(rest, "::");
char *pkg;
char *ver;
if (second) {
*second = 0;
pkg = rest;
ver = second + 2;
} else {
/* Older index files may only have repo::pkg with no
* version field — tolerate that rather than reject it. */
pkg = rest;
ver = "unknown";
}
trim(repo);
trim(pkg);
trim(ver);
struct gpkg_entry ent;
ent.repo = dupstr(repo);
ent.pkg = dupstr(pkg);
if (!ent.repo || !ent.pkg) { return -1; }
ent.ver = dupstr(ver);
if (!ent.repo || !ent.pkg || !ent.ver) { return -1; }
struct gpkg_entry *tmp =
realloc(idx->entries, sizeof(*idx->entries) * (idx->count + 1));
@@ -341,80 +440,19 @@ gl_free_index(struct gindex *idx)
for (size_t i = 0; i < idx->count; i++) {
free(idx->entries[i].repo);
free(idx->entries[i].pkg);
free(idx->entries[i].ver);
}
free(idx->entries);
idx->entries = NULL;
idx->count = 0;
}
/*
int
gl_rebuild_index(uid_t uid, const char *out_dir)
gl_rebuild_index(gl_context_t *ctx)
{
char store_path[PATH_MAX];
snprintf(store_path, sizeof(store_path), "%s/%d", usr_store.path, uid);
if (!ctx) { return -1; }
DIR *store_dir = opendir(store_path);
if (!store_dir) return -1;
FILE *out = fopen(out_dir, "w");
if (!out) return -1;
size_t listed = 0;
struct dirent *repo_ent;
while ((repo_ent = readdir(store_dir))) {
if (repo_ent->d_name[0] == '.') { continue; }
char repo_path[PATH_MAX];
snprintf(repo_path, sizeof(repo_path), "%s/%s",
store_path, repo_ent->d_name);
struct stat rst;
if (stat(repo_path, &rst) != 0 || !S_ISDIR(rst.st_mode)) {
continue;
}
DIR *repo_dir = opendir(repo_path);
if (!repo_dir) continue;
struct dirent *pkg_ent;
while ((pkg_ent = readdir(repo_dir))) {
if (pkg_ent->d_name[0] == '.') { continue; }
char pkg_path[PATH_MAX];
snprintf(pkg_path, sizeof(pkg_path), "%s/%s",
repo_path, pkg_ent->d_name);
struct stat pst;
if (stat(pkg_path, &pst) != 0 || !S_ISDIR(pst.st_mode)) {
continue;
}
fprintf(out, "%s::%s\n",
repo_ent->d_name,
pkg_ent->d_name);
listed++;
}
closedir(repo_dir);
}
rewind(out);
fprintf(out, "uid = %d\n", uid);
closedir(store_dir);
fclose(out);
return 0;
}
*/
int
gl_rebuild_index(uid_t uid, const char *out_dir)
{
char store_path[PATH_MAX];
snprintf(store_path, sizeof(store_path), "%s/%d", usr_store.path, uid);
DIR *store_dir = opendir(store_path);
DIR *store_dir = opendir(ctx->store_path);
if (!store_dir) return -1;
/* Write to a new timestamped index file */
@@ -422,7 +460,8 @@ gl_rebuild_index(uid_t uid, const char *out_dir)
make_index_name(fname, sizeof(fname));
char new_index_path[PATH_MAX];
snprintf(new_index_path, sizeof(new_index_path), "%s/%s", out_dir, fname);
snprintf(new_index_path, sizeof(new_index_path), "%s/%s",
ctx->index_path, fname);
FILE *out = fopen(new_index_path, "w");
if (!out) {
@@ -438,7 +477,7 @@ gl_rebuild_index(uid_t uid, const char *out_dir)
char repo_path[PATH_MAX];
snprintf(repo_path, sizeof(repo_path), "%s/%s",
store_path, repo_ent->d_name);
ctx->store_path, repo_ent->d_name);
struct stat rst;
if (stat(repo_path, &rst) != 0 || !S_ISDIR(rst.st_mode)) {
@@ -461,9 +500,33 @@ gl_rebuild_index(uid_t uid, const char *out_dir)
continue;
}
fprintf(out, "%s::%s\n",
/* One version dir per package name, named
* "<pkg_name>-<version>" (see gl_cat_dir_and_ver
* in pkg.c, which is what created it). Strip the
* known "<pkg_name>-" prefix to recover just the
* version part — safe even if pkg_name itself
* contains dashes, since we already know its
* exact length from pkg_ent->d_name. */
char pkg_ver[64] = "unknown";
DIR *ver_dir = opendir(pkg_path);
if (ver_dir) {
struct dirent *ver_ent;
while ((ver_ent = readdir(ver_dir))) {
if (ver_ent->d_name[0] == '.') { continue; }
size_t prefix_len = strlen(pkg_ent->d_name) + 1;
if (strlen(ver_ent->d_name) > prefix_len) {
snprintf(pkg_ver, sizeof(pkg_ver), "%s",
ver_ent->d_name + prefix_len);
}
break;
}
closedir(ver_dir);
}
fprintf(out, "%s::%s::%s\n",
repo_ent->d_name,
pkg_ent->d_name);
pkg_ent->d_name,
pkg_ver);
listed++;
}
closedir(repo_dir);
@@ -476,14 +539,15 @@ gl_rebuild_index(uid_t uid, const char *out_dir)
if (!tmp_in) return -1;
char tmp_path[PATH_MAX];
snprintf(tmp_path, sizeof(tmp_path), "%s/.index_tmp_XXXXXX", out_dir);
snprintf(tmp_path, sizeof(tmp_path), "%s/.index_tmp_XXXXXX",
ctx->index_path);
int tmp_fd = mkstemp(tmp_path);
if (tmp_fd < 0) { fclose(tmp_in); return -1; }
FILE *tmp_out = fdopen(tmp_fd, "w");
if (!tmp_out) { close(tmp_fd); fclose(tmp_in); return -1; }
fprintf(tmp_out, "uid = %d\n", uid);
fprintf(tmp_out, "uid = %d\n", ctx->uid);
fprintf(tmp_out, "listed = %zu\n\n", listed);
char line[512];
@@ -494,15 +558,15 @@ gl_rebuild_index(uid_t uid, const char *out_dir)
fclose(tmp_out);
rename(tmp_path, new_index_path);
chown(new_index_path, uid, (gid_t)-1);
chown(new_index_path, ctx->uid, (gid_t)-1);
/* Update the "current" symlink to point at the new index */
char link_path[PATH_MAX];
snprintf(link_path, sizeof(link_path), "%s/current", out_dir);
snprintf(link_path, sizeof(link_path), "%s/current", ctx->index_path);
unlink(link_path);
if (symlink(fname, link_path) != 0) { return -1; }
lchown(link_path, uid, (gid_t)-1);
lchown(link_path, ctx->uid, (gid_t)-1);
return 0;
}
@@ -672,62 +736,14 @@ gl_backup_istore(const char *tar_path,
}
static int
gl_mkdirp(const char *path, mode_t mode)
{
char tmp[PATH_MAX];
strncpy(tmp, path, sizeof(tmp));
tmp[sizeof(tmp) - 1] = '\0';
for (char *p = tmp + 1; *p; p++) {
if (*p == '/') {
*p = '\0';
if (mkdir(tmp, mode) == -1 && errno != EEXIST) {
return -1;
}
*p = '/';
}
}
if (mkdir(tmp, mode) == -1 && errno != EEXIST) {
return -1;
}
return 0;
}
int
gl_rmdir_recursive(const char *path)
{
DIR *dir = opendir(path);
if (!dir) { return -1; }
struct dirent *ent;
while ((ent = readdir(dir))) {
if (ent->d_name[0] == '.') { continue; }
char full[PATH_MAX];
snprintf(full, sizeof(full), "%s/%s", path, ent->d_name);
struct stat st;
if (lstat(full, &st) != 0) { continue; }
if (S_ISDIR(st.st_mode)) {
gl_rmdir_recursive(full);
}
else {
unlink(full);
}
}
closedir(dir);
return rmdir(path);
}
static int
gl_unlink_pkg(const char *pkg_store_final, uid_t uid)
gl_unlink_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo)
{
char links_base[PATH_MAX];
snprintf(links_base, sizeof(links_base), "%s/%d", usr_links.path, uid);
if (ctx->scope == GL_SCOPE_SYS && strcmp(pkg_repo, GL_BASE_REPO) == 0) {
snprintf(links_base, sizeof(links_base), "%s", GL_BASE_LINKS_DEST);
} else {
snprintf(links_base, sizeof(links_base), "%s", ctx->links_path);
}
size_t strip_len = strlen(pkg_store_final);
@@ -781,12 +797,14 @@ gl_unlink_pkg(const char *pkg_store_final, uid_t uid)
}
gl_remove_status_t
gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid)
gl_remove_pkg(gl_context_t *ctx, const char *pkg_name, const char *pkg_repo)
{
if (!ctx) { return GL_REMOVE_ERR_NOT_FOUND; }
/* find the package base directory in the store */
char pkg_store_base[PATH_MAX];
snprintf(pkg_store_base, sizeof(pkg_store_base),
"%s/%d/%s/%s", usr_store.path, uid, pkg_repo, pkg_name);
"%s/%s/%s", ctx->store_path, pkg_repo, pkg_name);
struct stat st;
if (stat(pkg_store_base, &st) != 0 || !S_ISDIR(st.st_mode)) {
@@ -811,9 +829,14 @@ gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid)
return GL_REMOVE_ERR_NOT_FOUND;
}
/* remove symlinks from links tree */
if (gl_unlink_pkg(pkg_store_final, uid) != 0) {
return GL_REMOVE_ERR_UNLINK;
/* remove symlinks from links tree. GL_CTX_STAGE: skip — the
* stage links tree isn't seeded (links are fully derived from
* the store post-commit by relink_store), so there's nothing
* there to unlink. */
if (ctx->mode == GL_CTX_LIVE) {
if (gl_unlink_pkg(ctx, pkg_store_final, pkg_repo) != 0) {
return GL_REMOVE_ERR_UNLINK;
}
}
/* remove package from store */
@@ -822,10 +845,7 @@ gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid)
}
/* rebuild index */
char index_dir[PATH_MAX];
snprintf(index_dir, sizeof(index_dir), "%s/%d", usr_index.path, uid);
if (gl_rebuild_index(uid, index_dir) != 0) {
if (gl_rebuild_index(ctx) != 0) {
return GL_REMOVE_ERR_INDEX;
}
@@ -833,10 +853,14 @@ gl_remove_pkg(const char *pkg_name, const char *pkg_repo, uid_t uid)
}
static int
gl_link_pkg(const char *pkg_store_final, uid_t uid)
gl_link_pkg(gl_context_t *ctx, const char *pkg_store_final, const char *pkg_repo)
{
char links_base[PATH_MAX];
snprintf(links_base, sizeof(links_base), "%s/%d", usr_links.path, uid);
if (ctx->scope == GL_SCOPE_SYS && strcmp(pkg_repo, GL_BASE_REPO) == 0) {
snprintf(links_base, sizeof(links_base), "%s", GL_BASE_LINKS_DEST);
} else {
snprintf(links_base, sizeof(links_base), "%s", ctx->links_path);
}
size_t strip_len = strlen(pkg_store_final);
@@ -895,18 +919,29 @@ gl_link_pkg(const char *pkg_store_final, uid_t uid)
gl_mkdirp(parent, 0700);
}
/* remove existing symlink if present */
unlink(link_path);
/* Only ever replace an existing entry if it's a
* symlink we created ourselves. See
* is_glacier_symlink's comment for why this
* check exists at all. */
struct stat existing_st;
if (lstat(link_path, &existing_st) == 0 &&
!is_glacier_symlink(link_path, ctx->store_path)) {
lg_printf(1,
"Refusing to overwrite non-glacier path: %s",
link_path);
} else {
unlink(link_path);
/* symlink store path -> links tree */
if (symlink(full_path, link_path) != 0) {
closedir(dir);
free(current);
while (top > 0) { free(stack[--top]); }
return -1;
/* symlink store path -> links tree */
if (symlink(full_path, link_path) != 0) {
closedir(dir);
free(current);
while (top > 0) { free(stack[--top]); }
return -1;
}
lchown(link_path, ctx->uid, (gid_t)-1);
}
lchown(link_path, uid, (gid_t)-1);
}
}
@@ -949,6 +984,78 @@ gl_delete_user(uid_t uid, bool isVerbose)
return 0;
}
char
*gl_find_pkg_repo(gl_context_t *ctx, const char *pkg_name)
{
char store_path[PATH_MAX];
snprintf(store_path, sizeof(store_path), "%s", ctx->store_path);
DIR *uid_dir = opendir(store_path);
if (!uid_dir) { return NULL; }
struct dirent *repo_entry;
while ((repo_entry = readdir(uid_dir)) != NULL) {
if (repo_entry->d_name[0] == '.') { continue; }
char pkg_path[PATH_MAX];
snprintf(pkg_path, sizeof(pkg_path), "%s/%s/%s", store_path, repo_entry->d_name, pkg_name);
struct stat st;
if (stat(pkg_path, &st) == 0 && S_ISDIR(st.st_mode)) {
char *repo = strdup(repo_entry->d_name);
closedir(uid_dir);
return repo;
}
}
closedir(uid_dir);
return NULL;
}
gl_repo_list_t
gl_find_pkg_repos(gl_context_t *ctx, const char *pkg_name)
{
gl_repo_list_t result = { NULL, 0 };
char store_path[PATH_MAX];
snprintf(store_path, sizeof(store_path), "%s", ctx->store_path);
DIR *uid_dir = opendir(store_path);
if (!uid_dir) { return result; }
struct dirent *repo_entry;
while ((repo_entry = readdir(uid_dir)) != NULL) {
if (repo_entry->d_name[0] == '.') { continue; }
char pkg_path[PATH_MAX];
snprintf(pkg_path, sizeof(pkg_path),
"%s/%s/%s", store_path, repo_entry->d_name, pkg_name);
struct stat st;
if (stat(pkg_path, &st) == 0 && S_ISDIR(st.st_mode)) {
char **tmp = realloc(result.repos,
(result.count + 1) * sizeof(char *));
if (!tmp) { break; }
result.repos = tmp;
result.repos[result.count] = strdup(repo_entry->d_name);
result.count++;
}
}
closedir(uid_dir);
return result;
}
void
gl_free_repo_list(gl_repo_list_t *list)
{
if (!list) { return; }
for (size_t i = 0; i < list->count; i++) { free(list->repos[i]); }
free(list->repos);
list->repos = NULL;
list->count = 0;
}
gl_restore_status_t
gl_restore_istore(const char *tar_path, const char *glacier_root, const char *uid)
{
@@ -1011,8 +1118,9 @@ gl_restore_istore(const char *tar_path, const char *glacier_root, const char *ui
}
gl_install_status_t
gl_install_pkg(const char *gpkg_path, uid_t uid)
gl_install_pkg(gl_context_t *ctx, const char *gpkg_path)
{
if (!ctx) { return GL_INSTALL_ERR_OPEN; }
char original_cwd[PATH_MAX];
if (getcwd(original_cwd, sizeof(original_cwd)) == NULL) {
return GL_INSTALL_ERR_OPEN;
@@ -1086,7 +1194,7 @@ gl_install_pkg(const char *gpkg_path, uid_t uid)
/* construct the directory for the package in the store */
snprintf(pkg_store_base, sizeof(pkg_store_base),
"%s/%d/%s/%s", usr_store.path, uid, pkg.pkg_repo, pkg.pkg_name);
"%s/%s/%s", ctx->store_path, pkg.pkg_repo, pkg.pkg_name);
snprintf(pkg_store_final, sizeof(pkg_store_final),
"%s/%s-%d.%d.%d", pkg_store_base, pkg.pkg_name,
@@ -1186,15 +1294,13 @@ gl_install_pkg(const char *gpkg_path, uid_t uid)
archive_read_free(a2);
/* now the index will be updated */
/* now the index will be updated */
char index_dir[PATH_MAX];
snprintf(index_dir, sizeof(index_dir),
"%s/%d", usr_index.path, uid);
/* Resolve "current" symlink to find the actual index file to append to */
/* Resolve "current" symlink to confirm an index exists to rebuild
* against. The actual write happens in gl_rebuild_index below —
* anything written here would just be overwritten by that call,
* since it creates a fresh timestamped index file and repoints
* "current" at it rather than editing this one in place. */
char current_link[PATH_MAX];
snprintf(current_link, sizeof(current_link), "%s/current", index_dir);
snprintf(current_link, sizeof(current_link), "%s/current", ctx->index_path);
char current_target[PATH_MAX];
ssize_t len = readlink(current_link, current_target, sizeof(current_target) - 1);
@@ -1204,31 +1310,148 @@ gl_install_pkg(const char *gpkg_path, uid_t uid)
}
current_target[len] = '\0';
char index_path[PATH_MAX];
snprintf(index_path, sizeof(index_path), "%s/%s", index_dir, current_target);
FILE *idx = fopen(index_path, "a");
if (!idx) {
if (gl_rebuild_index(ctx) != 0) {
gl_free_pkg(&pkg);
return GL_INSTALL_ERR_INDEX;
}
fprintf(idx, "%s::%s\n", pkg.pkg_repo, pkg.pkg_name);
fclose(idx);
if (gl_rebuild_index(uid, index_dir) != 0) {
gl_free_pkg(&pkg);
return GL_INSTALL_ERR_INDEX;
}
if (gl_link_pkg(pkg_store_final, uid) != 0) {
gl_free_pkg(&pkg);
return GL_INSTALL_ERR_EXTRACT;
if (ctx->mode == GL_CTX_LIVE) {
if (gl_link_pkg(ctx, pkg_store_final, pkg.pkg_repo) != 0) {
gl_free_pkg(&pkg);
return GL_INSTALL_ERR_EXTRACT;
}
}
/* GL_CTX_STAGE: skip. A symlink written here would target
* ctx->store_path, which is the stage path — it goes stale the
* moment gl_commit_transaction renames the store into its live
* location. The links tree is fully derived from the store, so
* it's regenerated wholesale post-commit instead of tracked
* incrementally here (see relink_store in transaction.c). */
gl_free_pkg(&pkg);
chdir(original_cwd);
return GL_INSTALL_OK;
}
/*
* prune_stale_links
* Recursively removes only the symlinks glacier itself created (per
* is_glacier_symlink) whose store target no longer exists — i.e. links
* left behind by a package that's since been removed. Directories,
* regular files, and symlinks glacier didn't create are never touched,
* recursed into for directories aside. This must stay this
* conservative because links_path is not always glacier-exclusive
* territory: for system-scope packages it can be /usr itself, which
* holds plenty of content glacier has no business deleting.
*/
static void
prune_stale_links(const char *dir_path, const char *store_prefix)
{
DIR *dir = opendir(dir_path);
if (!dir) { return; }
struct dirent *ent;
while ((ent = readdir(dir))) {
if (ent->d_name[0] == '.') { continue; }
char full[PATH_MAX];
snprintf(full, sizeof(full), "%s/%s", dir_path, ent->d_name);
struct stat lst;
if (lstat(full, &lst) != 0) { continue; }
if (S_ISLNK(lst.st_mode)) {
if (!is_glacier_symlink(full, store_prefix)) {
continue; /* not ours - never touch */
}
struct stat target_st;
if (stat(full, &target_st) != 0 && errno == ENOENT) {
/* our symlink, but its target is gone */
unlink(full);
}
} else if (S_ISDIR(lst.st_mode)) {
prune_stale_links(full, store_prefix);
}
/* regular files: never ours, never touched */
}
closedir(dir);
}
int
gl_relink_store(gl_context_t *ctx)
{
/* This function's contract is "derive the links tree entirely
* from the store" (same as gl_rebuild_index does for the index) -
* but "derive fully" now means prune-then-refresh, not
* wipe-then-rebuild. links_path may be a directory glacier
* doesn't exclusively own (e.g. /usr for system scope), so a
* recursive delete of the whole tree is off the table; only
* glacier's own stale symlinks get removed. gl_mkdirp is
* non-destructive (mkdir tolerates EEXIST) so this is still safe
* to call even if links_path already exists. */
if (gl_mkdirp(ctx->links_path, 0700) != 0) {
return -1;
}
prune_stale_links(ctx->links_path, ctx->store_path);
/* Base-repo packages under system scope route to /usr instead of
* ctx->links_path (see GL_BASE_REPO) — prune there too, since a
* package that WAS in the base repo and got removed/moved leaves
* its stale symlink in /usr, not in ctx->links_path. Only relevant
* for system scope; /usr is never a valid destination for
* per-uid packages. */
if (ctx->scope == GL_SCOPE_SYS) {
prune_stale_links(GL_BASE_LINKS_DEST, ctx->store_path);
}
DIR *repo_dir = opendir(ctx->store_path);
if (!repo_dir) return (errno == ENOENT) ? 0 : -1;
struct dirent *repo_ent;
while ((repo_ent = readdir(repo_dir))) {
if (repo_ent->d_name[0] == '.') continue;
char repo_path[PATH_MAX];
snprintf(repo_path, sizeof(repo_path), "%s/%s",
ctx->store_path, repo_ent->d_name);
if (!is_dir(repo_path)) continue;
DIR *name_dir = opendir(repo_path);
if (!name_dir) continue;
struct dirent *name_ent;
while ((name_ent = readdir(name_dir))) {
if (name_ent->d_name[0] == '.') continue;
char name_path[PATH_MAX];
snprintf(name_path, sizeof(name_path), "%s/%s",
repo_path, name_ent->d_name);
if (!is_dir(name_path)) continue;
/* one version dir per package name today */
DIR *ver_dir = opendir(name_path);
if (!ver_dir) continue;
struct dirent *ver_ent;
while ((ver_ent = readdir(ver_dir))) {
if (ver_ent->d_name[0] == '.') continue;
char ver_path[PATH_MAX];
snprintf(ver_path, sizeof(ver_path), "%s/%s",
name_path, ver_ent->d_name);
if (!is_dir(ver_path)) continue;
if (gl_link_pkg(ctx, ver_path, repo_ent->d_name) != 0) {
closedir(ver_dir); closedir(name_dir);
closedir(repo_dir);
return -1;
}
}
closedir(ver_dir);
}
closedir(name_dir);
}
closedir(repo_dir);
return 0;
}