_ _
| |_ ___| |_ _
| | . | | | |
|_|_|___|_|_ |
|___|
git mirror - github.com/owenewans/holy - branch master
file src/change.c
#define _POSIX_C_SOURCE 200809L
#include "change.h"
#include "install.h"
#include "package.h"
#include "config.h"
#include <archive.h>
#include <archive_entry.h>
#include <errno.h>
#include <openssl/evp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct entries {
struct holy_manifest_entry *items;
size_t count, capacity;
};
static void free_entry(struct holy_manifest_entry *e)
{
free((char *)e->path); free((char *)e->link);
free((char *)e->hardlink); free((char *)e->group); free((unsigned char *)e->hash);
}
void holy_file_plan_free(struct holy_file_plan *plan)
{
size_t i;
for (i = 0; i < plan->old_count; ++i) free_entry(&plan->old_entries[i]);
for (i = 0; i < plan->new_count; ++i) free_entry(&plan->new_entries[i]);
for (i = 0; i < plan->count; ++i) free(plan->changes[i].relocated_path);
free(plan->old_entries); free(plan->new_entries); free(plan->changes);
memset(plan, 0, sizeof *plan);
}
static int collect(void *context, const struct holy_manifest_entry *entry)
{
struct entries *entries = context;
struct holy_manifest_entry *copy;
if (entries->count == entries->capacity) {
size_t capacity = entries->capacity ? entries->capacity * 2 : 32;
void *next;
if (capacity < entries->capacity || capacity > SIZE_MAX / sizeof *copy) return 0;
next = realloc(entries->items, capacity * sizeof *copy);
if (!next) return 0;
entries->items = next; entries->capacity = capacity;
}
copy = &entries->items[entries->count++];
memset(copy, 0, sizeof *copy);
copy->path = strdup(entry->path);
copy->link = entry->link ? strdup(entry->link) : NULL;
copy->hardlink = entry->hardlink ? strdup(entry->hardlink) : NULL;
copy->group = entry->group ? strdup(entry->group) : NULL;
copy->hash = entry->hash ? malloc(32) : NULL;
if (copy->hash) memcpy((unsigned char *)copy->hash, entry->hash, 32);
copy->size = entry->size; copy->mode = entry->mode;
copy->uid = entry->uid; copy->gid = entry->gid; copy->directory = entry->directory;
copy->config = entry->config; copy->mutable = entry->mutable;
return copy->path && (!entry->link || copy->link) && (!entry->hardlink || copy->hardlink) &&
(!entry->group || copy->group) && (!entry->hash || copy->hash);
}
static int entry_order(const void *left, const void *right)
{
return strcmp(((const struct holy_manifest_entry *)left)->path,
((const struct holy_manifest_entry *)right)->path);
}
static int same_string(const char *a, const char *b)
{
return a && b ? !strcmp(a, b) : a == b;
}
static int same_entry(const struct holy_manifest_entry *a, const struct holy_manifest_entry *b)
{
return a->directory == b->directory && a->config == b->config &&
a->mutable == b->mutable && a->mode == b->mode && a->uid == b->uid &&
a->gid == b->gid && a->size == b->size && same_string(a->link, b->link) &&
same_string(a->hardlink, b->hardlink) && same_string(a->group, b->group) &&
(a->directory || a->link || (a->hash && b->hash && !memcmp(a->hash, b->hash, 32)));
}
static int change_id(const struct holy_file_plan *plan, const char *path, char output[65])
{
unsigned char bytes[32];
unsigned length;
size_t i;
int ok;
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
if (!ctx) return 0;
ok = EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) == 1 &&
EVP_DigestUpdate(ctx, "holy-file-change-1", sizeof "holy-file-change-1") == 1 &&
EVP_DigestUpdate(ctx, plan->old_artifact, 65) == 1 &&
EVP_DigestUpdate(ctx, plan->new_artifact, 65) == 1 &&
EVP_DigestUpdate(ctx, path, strlen(path) + 1) == 1 &&
EVP_DigestFinal_ex(ctx, bytes, &length) == 1 && length == 32;
if (ok) for (i = 0; i < 32; ++i) snprintf(output + i * 2, 3, "%02x", bytes[i]);
EVP_MD_CTX_free(ctx);
return ok;
}
int holy_file_plan_collect(const char *old_snapshot, const char *new_snapshot,
struct holy_file_plan *plan)
{
struct holy_package_identity old = {0}, next = {0};
struct entries before = {0}, after = {0};
size_t i = 0, j = 0, maximum;
int ok = 0;
memset(plan, 0, sizeof *plan);
if (!holy_package_identity(old_snapshot, &old) || !holy_package_identity(new_snapshot, &next) ||
!holy_verify_visit(old_snapshot, collect, &before) ||
!holy_verify_visit(new_snapshot, collect, &after)) goto done;
memcpy(plan->old_artifact, old.digest, 65); memcpy(plan->new_artifact, next.digest, 65);
if (before.count) qsort(before.items, before.count, sizeof *before.items, entry_order);
if (after.count) qsort(after.items, after.count, sizeof *after.items, entry_order);
if (before.count > SIZE_MAX - after.count) goto done;
maximum = before.count + after.count;
if (maximum > SIZE_MAX / sizeof *plan->changes) goto done;
plan->changes = calloc(maximum ? maximum : 1, sizeof *plan->changes);
if (!plan->changes) goto done;
while (i < before.count || j < after.count) {
struct holy_file_change *change = &plan->changes[plan->count++];
int order = i == before.count ? 1 : j == after.count ? -1 :
strcmp(before.items[i].path, after.items[j].path);
if (order <= 0) change->before = &before.items[i++];
if (order >= 0) change->after = &after.items[j++];
change->kind = !change->before ? HOLY_ADD : !change->after ? HOLY_REMOVE :
same_entry(change->before, change->after) ? HOLY_RETAIN : HOLY_REPLACE;
change->source_path = change->after ? change->after->path : change->before->path;
if (!change_id(plan, change->after ? change->after->path : change->before->path, change->id)) goto done;
}
ok = 1;
done:
plan->old_entries = before.items; plan->old_count = before.count;
plan->new_entries = after.items; plan->new_count = after.count;
holy_package_identity_free(&old); holy_package_identity_free(&next);
return ok;
}
int holy_file_plan_preserve_configs(struct holy_file_plan *plan, int root,
const char *saved_record)
{
size_t i;
for (i = 0; i < plan->count; ++i) {
struct holy_file_change *c = &plan->changes[i];
struct holy_manifest_entry observed;
size_t length, j;
int prior;
if (!c->before || !c->after || !c->before->config || !c->after->config ||
c->before->directory || c->before->link || c->before->hardlink ||
c->before->group || c->after->directory || c->after->link ||
c->after->hardlink || c->after->group) continue;
length = strlen(c->source_path);
if (length > SIZE_MAX - sizeof ".holy-new") return 6;
c->relocated_path = malloc(length + sizeof ".holy-new");
if (!c->relocated_path) return 1;
memcpy(c->relocated_path, c->source_path, length);
memcpy(c->relocated_path + length, ".holy-new", sizeof ".holy-new");
c->relocated_before = *c->before;
c->relocated_before.path = c->relocated_path;
c->relocated_before.config = c->relocated_before.mutable = 0;
prior = holy_install_check_entry(root, c->before);
if (prior == 1) {
if (holy_install_check_entry(root, &c->relocated_before) != 2) return 4;
continue;
}
if (prior != 0 || !holy_install_observe_regular(root, c->before, &observed,
c->preserved_hash)) return 4;
for (j = 0; j < plan->new_count; ++j)
if (!strcmp(plan->new_entries[j].path, c->relocated_path)) return 4;
c->preserved = observed;
c->preserved.hash = c->preserved_hash;
c->relocated_after = *c->after;
c->relocated_after.path = c->relocated_path;
c->relocated_after.config = c->relocated_after.mutable = 0;
prior = holy_install_check_entry(root, &c->relocated_before);
if (saved_record) {
char marker[96];
const char *section = strstr(saved_record, "\n[files]\n"), *position;
int n = snprintf(marker, sizeof marker, "\nchange %s ", c->id);
if (!section || n < 0 || (size_t)n >= sizeof marker ||
!(position = strstr(section, marker))) return 4;
position += strlen(marker);
if (!strncmp(position, "add\nbefore absent\n", sizeof "add\nbefore absent\n" - 1)) prior = 2;
else if (!strncmp(position, "replace\nbefore file ", sizeof "replace\nbefore file " - 1)) prior = 1;
else return 4;
} else if (prior != 1 && prior != 2) return 4;
c->before = prior == 1 ? &c->relocated_before : NULL;
c->after = &c->relocated_after;
c->kind = prior == 1 ? HOLY_REPLACE : HOLY_ADD;
c->keep_config = 1;
}
return 0;
}
static size_t find_change(const struct holy_file_plan *plan, const char *path)
{
size_t low = 0, high = plan->count;
while (low < high) {
size_t middle = low + (high - low) / 2;
const struct holy_file_change *c = &plan->changes[middle];
const char *name = c->source_path;
int order = strcmp(name, path);
if (!order) return middle;
if (order < 0) low = middle + 1;
else high = middle;
}
return plan->count;
}
static void group_name(const struct holy_file_change *anchor, char name[84])
{
snprintf(name, 84, ".holy-update-%s-group", anchor->id);
}
static const struct holy_file_change *group_anchor(const struct holy_file_plan *plan,
const struct holy_file_change *change)
{
size_t index;
if (!change->after || !change->after->group) return NULL;
if (!change->after->hardlink) return change;
index = find_change(plan, change->after->hardlink);
return index < plan->count ? &plan->changes[index] : NULL;
}
static int group_state(const struct holy_file_plan *plan, int root,
const struct holy_file_change *change, struct stat *observed)
{
const struct holy_file_change *anchor = group_anchor(plan, change);
char name[84];
if (!anchor) return 0;
group_name(anchor, name);
return holy_install_entry_state(root, anchor->after, name, observed);
}
struct inode_fact { const char *group, *path; struct stat state; };
static int inode_order(const void *left, const void *right)
{
const struct inode_fact *a = left, *b = right;
if (a->state.st_dev != b->state.st_dev) return a->state.st_dev < b->state.st_dev ? -1 : 1;
if (a->state.st_ino != b->state.st_ino) return a->state.st_ino < b->state.st_ino ? -1 : 1;
return strcmp(a->path, b->path);
}
static int old_group_order(const void *left, const void *right)
{
return strcmp(((const struct inode_fact *)left)->group,
((const struct inode_fact *)right)->group);
}
static int same_inode(const struct stat *a, const struct stat *b)
{
return a->st_dev == b->st_dev && a->st_ino == b->st_ino;
}
static int partial_groups(const struct holy_file_plan *plan, int root)
{
struct inode_fact *old;
size_t i, used = 0;
int ok = 0;
if (plan->count > SIZE_MAX / sizeof *old) return 0;
old = calloc(plan->count ? plan->count : 1, sizeof *old);
if (!old) return 0;
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
struct stat current, next;
if (!c->before || !c->before->group ||
holy_install_entry_state(root, c->before, NULL, ¤t) != 1) continue;
if (c->after && holy_install_entry_state(root, c->after, NULL, &next) == 1 &&
(!c->after->group || (group_state(plan, root, c, &next) == 1 && same_inode(¤t, &next)))) continue;
old[used].group = c->before->group; old[used].path = c->before->path;
old[used++].state = current;
}
qsort(old, used, sizeof *old, old_group_order);
for (i = 1; i < used; ++i)
if (!strcmp(old[i-1].group, old[i].group) && !same_inode(&old[i-1].state, &old[i].state)) goto done;
ok = 1;
done:
free(old);
return ok;
}
static int topology(const struct holy_file_plan *plan, int after, int root)
{
const struct holy_manifest_entry *entries = after ? plan->new_entries : plan->old_entries;
size_t count = after ? plan->new_count : plan->old_count;
struct inode_fact *facts;
size_t i, used = 0;
int ok = 0;
if (count > SIZE_MAX - plan->count || count + plan->count > SIZE_MAX / sizeof *facts) return 0;
facts = calloc(count + plan->count ? count + plan->count : 1, sizeof *facts);
if (!facts) return 0;
for (i = 0; i < count; ++i) {
const struct holy_manifest_entry *e = &entries[i];
size_t index = find_change(plan, e->path);
if (index < plan->count && plan->changes[index].keep_config)
e = &plan->changes[index].preserved;
if (e->directory || e->link) continue;
if (holy_install_entry_state(root, e, NULL, &facts[used].state) != 1) goto done;
facts[used].group = e->group; facts[used++].path = e->path;
}
for (i = 0; i < plan->count; ++i) if (plan->changes[i].keep_config) {
const struct holy_manifest_entry *e = after ? plan->changes[i].after : plan->changes[i].before;
if (!e) continue;
if (holy_install_entry_state(root, e, NULL, &facts[used].state) != 1) goto done;
facts[used].group = e->group; facts[used++].path = e->path;
}
qsort(facts, used, sizeof *facts, inode_order);
for (i = 1; i < used; ++i) if (same_inode(&facts[i-1].state, &facts[i].state) &&
(!facts[i-1].group || !facts[i].group || strcmp(facts[i-1].group, facts[i].group))) goto done;
ok = 1;
done:
free(facts);
return ok;
}
static void quote(FILE *out, const char *value)
{
const unsigned char *p = (const unsigned char *)value;
fputc('"', out);
for (; *p; ++p) {
if (*p == '"' || *p == '\\') fprintf(out, "\\%c", *p);
else if (*p < 32 || *p >= 127) fprintf(out, "\\x%02x", *p);
else fputc(*p, out);
}
fputc('"', out);
}
static void entry_record(FILE *out, const char *side, const struct holy_manifest_entry *e)
{
size_t i;
fprintf(out, "%s ", side);
if (!e) { fputs("absent\n", out); return; }
fprintf(out, "%s ", e->directory ? "dir" : e->link ? "symlink" : e->hardlink ? "hardlink" : "file");
quote(out, e->path);
fprintf(out, " %o %lld %lld %lld ", e->mode, e->uid, e->gid, e->size);
if (e->directory || e->link) fputc('-', out);
else for (i = 0; i < 32; ++i) fprintf(out, "%02x", e->hash[i]);
fputc(' ', out); quote(out, e->link ? e->link : e->hardlink ? e->hardlink : "-");
fputc(' ', out); quote(out, e->group ? e->group : "-");
fprintf(out, " %s\n", e->config ? e->mutable ? "config,mutable" : "config" :
e->mutable ? "mutable" : "none");
}
static int manifest_entry(FILE *out, const struct holy_manifest_entry *e,
const char *owner, const char *group)
{
size_t i;
fputs("file ", out); quote(out, e->path);
fprintf(out, " %o ", e->mode); quote(out, owner);
fputc(' ', out); quote(out, group);
fprintf(out, " %lld %lld %lld ", e->uid, e->gid, e->size);
for (i = 0; i < 32; ++i) fprintf(out, "%02x", e->hash[i]);
fprintf(out, " %s - -\n", e->config ? e->mutable ? "config,mutable" : "config" :
e->mutable ? "mutable" : "none");
return !ferror(out);
}
int holy_file_plan_installed_manifest(const struct holy_file_plan *plan,
const char *source, size_t length,
char **record, size_t *size)
{
FILE *out;
size_t start = 0, line = 0, mapped = 0;
int ok = 0;
*record = NULL; *size = 0;
out = open_memstream(record, size);
if (!out) return 0;
while (start < length) {
const char *end = memchr(source + start, '\n', length - start);
size_t bytes = end ? (size_t)(end - source - start) : length - start;
char **v = NULL, *error = NULL;
size_t count = 0, i;
++line;
if (!holy_lex(source + start, bytes, &v, &count, "HOLY/files", line, &error)) {
free(error); goto done;
}
free(error);
for (i = 0; count >= 2 && i < plan->count; ++i)
if (plan->changes[i].keep_config &&
!strcmp(v[1], plan->changes[i].source_path)) break;
if (count >= 2 && i < plan->count && plan->changes[i].keep_config) {
const struct holy_file_change *c = &plan->changes[i];
if (count != 12 || strcmp(v[0], "file") ||
!manifest_entry(out, &c->preserved, v[3], v[4]) ||
!manifest_entry(out, c->after, v[3], v[4])) {
holy_tokens_free(v, count); goto done;
}
++mapped;
} else if (fwrite(source + start, 1, bytes + !!end, out) != bytes + !!end) {
holy_tokens_free(v, count); goto done;
}
holy_tokens_free(v, count);
start += bytes + !!end;
}
ok = mapped != 0 && !ferror(out);
for (start = 0; ok && start < plan->count; ++start)
if (plan->changes[start].keep_config) --mapped;
ok = ok && mapped == 0;
done:
if (fclose(out)) ok = 0;
if (!ok) { free(*record); *record = NULL; *size = 0; }
return ok;
}
int holy_file_plan_record(const struct holy_file_plan *plan, char **record, size_t *size)
{
static const char *const names[] = {"retain", "add", "replace", "remove"};
FILE *out;
size_t i;
int ok;
*record = NULL; *size = 0;
out = open_memstream(record, size);
if (!out) return 0;
fprintf(out, "format holy-file-plan-1\nold %s\nnew %s\n", plan->old_artifact, plan->new_artifact);
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *change = &plan->changes[i];
fprintf(out, "change %s %s\n", change->id, names[change->kind]);
entry_record(out, "before", change->before); entry_record(out, "after", change->after);
if (change->keep_config) entry_record(out, "preserved", &change->preserved);
}
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
char name[84];
if (!c->after || !c->after->group || c->after->hardlink) continue;
group_name(c, name);
fputs("group-stage ", out); quote(out, c->after->path);
fputc(' ', out); quote(out, name); fputc('\n', out);
}
ok = !ferror(out);
if (fclose(out)) ok = 0;
if (!ok) { free(*record); *record = NULL; *size = 0; }
return ok;
}
static int supported(const struct holy_manifest_entry *entry, int privileged)
{
return !entry || ((!(entry->mode & 07000) ||
(privileged && (entry->mode & 07000) == 04000 &&
(entry->mode & 0111) && !entry->directory &&
!entry->link && !entry->hardlink && !entry->group)) &&
entry->uid == (long long)geteuid() && entry->gid == (long long)getegid() &&
(!entry->link || entry->link[0] != '/'));
}
int holy_file_plan_check(const struct holy_file_plan *plan, int root, int recovering, size_t *failed)
{
size_t i;
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
if (failed) *failed = i;
if (!supported(c->before, plan->before_privileged) ||
!supported(c->after, plan->after_privileged) ||
(c->keep_config && !supported(&c->preserved, plan->before_privileged)) ||
(c->kind == HOLY_REPLACE && ((c->before && c->before->directory) ||
(c->after && c->after->directory)))) return 6;
}
if (failed) *failed = 0;
if (!holy_install_directory_plan(root, plan->new_entries, plan->new_count, 0, recovering)) return 4;
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
int result = 0;
if (failed) *failed = i;
if (c->keep_config && holy_install_check_entry(root, &c->preserved) != 1) return 4;
if ((c->before && c->before->directory) || (c->after && c->after->directory)) {
const struct holy_manifest_entry *e = c->before ? c->before : c->after;
result = holy_install_check_entry(root, e);
if (result != 1 && !(c->kind == HOLY_ADD && result == 2)) return 4;
} else if (!holy_install_transition_check(root, c->before, c->after, recovering) ||
(!recovering && c->before && holy_install_check_entry(root, c->before) != 1)) return 4;
}
if ((!recovering && !topology(plan, 0, root)) ||
(recovering && holy_file_plan_finished(plan, root) && !partial_groups(plan, root))) return 4;
if (failed) *failed = plan->count;
return 0;
}
static void temporary_name(const struct holy_file_change *change, char name[78])
{
snprintf(name, 78, ".holy-update-%s", change->id);
}
int holy_file_plan_reservations(const struct holy_file_plan *plan, int root)
{
size_t i;
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
char name[78];
if (c->after && c->after->group && !c->after->hardlink) {
char group[84];
group_name(c, group);
if (holy_install_temporary_state(root, c->after, group) != 2) return 4;
}
if (!c->after || c->after->directory || c->kind == HOLY_RETAIN) continue;
temporary_name(c, name);
if (holy_install_temporary_state(root, c->after, name) != 2) return 4;
}
return 0;
}
static int group_witnesses(const struct holy_file_plan *plan, int root, int completed)
{
size_t i;
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
struct stat witness, published, old;
int state;
if (!c->after || !c->after->group) continue;
state = group_state(plan, root, c, &witness);
if (state == 2 && completed) continue;
if (state != 1) return 4;
state = holy_install_entry_state(root, c->after, NULL, &published);
if (state == 1 && (completed || c->kind == HOLY_RETAIN || !c->before ||
holy_install_entry_state(root, c->before, NULL, &old) != 1) &&
!same_inode(&witness, &published)) return 4;
if (completed && state != 1) return 4;
}
return 0;
}
static int discard_staging(const struct holy_file_plan *plan, int root)
{
size_t i;
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
char name[78];
struct stat staged, published;
int state;
if (!c->after || c->after->directory || c->kind == HOLY_RETAIN) continue;
temporary_name(c, name);
state = holy_install_entry_state(root, c->after, name, &staged);
if (state == 2) continue;
if (state != 1 || (c->after->group &&
(holy_install_entry_state(root, c->after, NULL, &published) != 1 ||
!same_inode(&staged, &published))) ||
!holy_install_remove_temporary(root, c->after, name)) return 4;
}
return 0;
}
int holy_file_plan_stage(const struct holy_file_plan *plan, const char *snapshot,
int root, int recovering, size_t *failed)
{
struct archive *archive = NULL;
struct archive_entry *entry;
struct holy_package_identity identity = {0};
FILE *content = NULL;
unsigned char *seen = NULL;
int result = 1, status;
size_t i;
result = holy_file_plan_check(plan, root, recovering, failed);
if (result) return result;
if (recovering && !holy_file_plan_finished(plan, root)) return group_witnesses(plan, root, 1);
if (!recovering && (result = holy_file_plan_reservations(plan, root))) return result;
result = 6;
if (!holy_package_identity(snapshot, &identity) || strcmp(identity.digest, plan->new_artifact)) goto done;
result = 1;
if (!holy_install_directory_plan(root, plan->new_entries, plan->new_count, 1, recovering)) goto done;
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
char name[84];
if (!c->after || !c->after->group || c->after->hardlink || c->kind != HOLY_RETAIN) continue;
group_name(c, name);
if (!holy_install_prepare_link(root, c->after, name, c->after, NULL, recovering)) goto done;
}
seen = calloc(plan->count ? plan->count : 1, 1);
archive = archive_read_new();
if (!seen || !archive || archive_read_support_filter_lz4(archive) != ARCHIVE_OK ||
archive_read_support_format_tar(archive) != ARCHIVE_OK ||
archive_read_open_filename(archive, snapshot, 65536) != ARCHIVE_OK) goto done;
while ((status = archive_read_next_header(archive, &entry)) == ARCHIVE_OK) {
const char *path = archive_entry_pathname(entry);
const struct holy_file_change *c;
char name[84];
int prepared;
if (!path || strncmp(path, "DATA/", 5)) {
if (archive_read_data_skip(archive) != ARCHIVE_OK) goto done;
continue;
}
i = find_change(plan, path + 5);
if (i == plan->count || !(c = &plan->changes[i])->after ||
c->after->directory || c->kind == HOLY_RETAIN) {
if (archive_read_data_skip(archive) != ARCHIVE_OK) goto done;
continue;
}
if (failed) *failed = i;
if (seen[i]++) goto done;
if (c->after->hardlink) {
if (archive_read_data_skip(archive) != ARCHIVE_OK) goto done;
continue;
}
if (c->after->group) group_name(c, name);
else temporary_name(c, name);
prepared = holy_install_temporary_state(root, c->after, name);
if (prepared == 0 || (prepared == 1 && !recovering)) {
fputs("holypkg: reserved update object requires inspection: ", stderr);
quote(stderr, c->after->path);
fprintf(stderr, " (%s)\n", name);
result = 4; goto done;
}
if (prepared == 1 || (recovering && !c->after->group &&
(!c->before || (!c->before->group && !c->before->hardlink)) &&
holy_install_check_entry(root, c->after) == 1)) {
if (archive_read_data_skip(archive) != ARCHIVE_OK) goto done;
continue;
}
if (!c->after->link) {
char buffer[65536];
la_ssize_t got;
long long total = 0;
content = tmpfile();
if (!content) goto done;
while ((got = archive_read_data(archive, buffer, sizeof buffer)) > 0) {
if (got > c->after->size - total || fwrite(buffer, 1, (size_t)got, content) != (size_t)got) goto done;
total += got;
}
if (got < 0 || total != c->after->size || fflush(content) || fsync(fileno(content))) goto done;
}
if (!holy_install_prepare_file(root, c->after, content ? fileno(content) : -1, name)) goto done;
if (content) { fclose(content); content = NULL; }
}
if (status != ARCHIVE_EOF) goto done;
result = group_witnesses(plan, root, 0);
if (result) goto done;
result = 1;
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i], *anchor;
char name[78], group[84];
if (!c->after || !c->after->group || c->kind == HOLY_RETAIN) continue;
if (failed) *failed = i;
anchor = group_anchor(plan, c);
if (!anchor) goto done;
temporary_name(c, name); group_name(anchor, group);
if (!holy_install_prepare_link(root, c->after, name, anchor->after, group, recovering)) goto done;
}
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
if (c->after && !c->after->directory && c->kind != HOLY_RETAIN && !seen[i]) goto done;
}
if (failed) *failed = plan->count;
result = 0;
done:
if (content) fclose(content);
if (archive) archive_read_free(archive);
holy_package_identity_free(&identity);
free(seen);
return result;
}
int holy_file_plan_finished(const struct holy_file_plan *plan, int root)
{
size_t i;
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
const struct holy_manifest_entry *e = c->after ? c->after : c->before;
int expected = c->after || e->directory ? 1 : 2;
if (c->keep_config && holy_install_check_entry(root, &c->preserved) != 1) return 4;
if (holy_install_check_entry(root, e) != expected) return 4;
}
return topology(plan, 1, root) ? 0 : 4;
}
int holy_file_plan_apply(const struct holy_file_plan *plan, int root,
holy_change_progress progress, void *context, size_t *failed)
{
size_t i;
int result = holy_file_plan_check(plan, root, 1, failed);
if (result) return result;
if (!holy_file_plan_finished(plan, root)) {
result = group_witnesses(plan, root, 1);
return result ? result : discard_staging(plan, root);
}
if ((result = group_witnesses(plan, root, 0))) return result;
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
char name[78];
int staged;
if (!c->after || c->after->directory || c->kind == HOLY_RETAIN) continue;
temporary_name(c, name);
staged = holy_install_temporary_state(root, c->after, name);
if (c->after->group && staged == 1) {
struct stat witness, current;
if (group_state(plan, root, c, &witness) != 1 ||
holy_install_entry_state(root, c->after, name, ¤t) != 1 ||
!same_inode(&witness, ¤t)) staged = 0;
}
if (staged == 0 || (staged == 2 &&
holy_install_entry_state(root, c->after, NULL, NULL) != 1)) {
if (failed) *failed = i;
return 4;
}
}
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
char name[78];
if (failed) *failed = i;
if (c->kind == HOLY_RETAIN || (c->after && c->after->directory) ||
(c->before && c->before->directory)) continue;
temporary_name(c, name);
if (progress && !progress(context, i, 0)) return 1;
if (!holy_install_transition(root, c->before, c->after, name)) return 4;
if (progress && !progress(context, i, 1)) return 1;
}
if (failed) *failed = plan->count;
return holy_file_plan_finished(plan, root);
}
int holy_file_plan_cleanup(const struct holy_file_plan *plan, int root)
{
size_t i;
int result = holy_file_plan_finished(plan, root);
if (result || (result = group_witnesses(plan, root, 1)) ||
(result = discard_staging(plan, root))) return result;
for (i = 0; i < plan->count; ++i) {
const struct holy_file_change *c = &plan->changes[i];
char name[84];
if (!c->after || !c->after->group || c->after->hardlink) continue;
group_name(c, name);
if (!holy_install_remove_temporary(root, c->after, name)) return 4;
}
return 0;
}