diff options
Diffstat (limited to 'core/module-save.c')
| -rw-r--r-- | core/module-save.c | 191 |
1 files changed, 176 insertions, 15 deletions
diff --git a/core/module-save.c b/core/module-save.c index e737276..414d6b7 100644 --- a/core/module-save.c +++ b/core/module-save.c @@ -30,6 +30,13 @@ * its image and empties it. */ static const unsigned char values_magic[4] = {'P', 'V', 'S', '1'}, image_magic[4] = {'P', 'L', 'I', '1'}, list_magic[4] = {'P', 'L', 'S', '2'}, list_magic_1[4] = {'P', 'L', 'S', '1'}; +/* The outbox's image names the fields its sends were laid out with: "PLO1" + * in the place of "PLI1", and after the save it holds u8 outputs, each {u8 + * name length, name, u8 field count, each field's {u8 type, u8 name length, + * name}} (the module's outputs when it was written). The sends logged after + * it are laid out so too: a module whose outputs differ writes it again + * first. So what they mean moves with them, whatever the links hold. */ +static const unsigned char sends_magic[4] = {'P', 'L', 'O', '1'}; /* Logged changes a list keeps before its image is written again: at least * JOURNAL_MIN, else as many bytes as its items; and whatever the lists, the * log is kept within as many bytes as all their items (and LOG_KEPT) by @@ -149,7 +156,8 @@ PURE_COLD PureResult pure_save_open_text(PureApp *app, const PureModule *m, uint /* ---- Lists: images and the log */ -enum { IMAGE_HEADER = 5 + PURE_RECORD_FIELDS * (2 + PURE_MODULE_NAME) + 8 }; +enum { OUTPUTS_NAMED = 1 + PURE_MODULE_INPUTS * (2 + PURE_MODULE_NAME) + PURE_MODULE_FIELDS * (2 + PURE_MODULE_NAME), + IMAGE_HEADER = 5 + PURE_RECORD_FIELDS * (2 + PURE_MODULE_NAME) + 8 + OUTPUTS_NAMED }; static size_t header_of(unsigned count, const uint8_t *text, const char (*names)[PURE_MODULE_NAME], uint64_t seq, unsigned char *out) { @@ -181,6 +189,71 @@ static size_t runtime_header(uint32_t place, uint64_t seq, unsigned char *out) { return header_of(r->fields, r->kinds, r->names, seq, out); } +/* The outbox's image header: m's outputs named after the save it holds. */ +static size_t outbox_header(const PureModule *m, uint64_t seq, unsigned char *out) { + size_t at = runtime_header(PURE_LIST_OUTBOX, seq, out); + memcpy(out, sends_magic, 4); + unsigned char *count = out + at++; + *count = 0; + for (uint32_t i = 0; i < m->inputs; ++i) { + if (m->input[i].kind != PURE_KIND_OUTPUT) continue; + ++*count; + size_t length = strlen(m->input_names[i]); + out[at++] = (unsigned char)length; + memcpy(out + at, m->input_names[i], length); + at += length; + out[at++] = (unsigned char)m->input[i].field_count; + for (uint32_t f = 0; f < m->input[i].field_count; ++f) { + const PureField *field = &m->field[m->input[i].field_start + f]; + size_t named = strlen(field->name); + out[at++] = field->type; + out[at++] = (unsigned char)named; + memcpy(out + at, field->name, named); + at += named; + } + } + return at; +} + +/* What an output's sends were laid out with, as a store names it: its + * fields (laid out), or their types alone (named false: links before PLK4). */ +typedef struct { char name[PURE_MODULE_NAME]; PureField fields[PURE_MODULE_FIELDS]; uint32_t count; bool named; } SentShape; + +/* The outputs an outbox image names (at, size bytes): how many, or -1 when + * they are not well formed; shapes (when not NULL) takes them. */ +static int read_outputs(const unsigned char *at, size_t size, SentShape *shapes, size_t *used) { + size_t k = 0; + if (size < 1) return -1; + unsigned outputs = at[k++]; + for (unsigned t = 0; t < outputs; ++t) { + size_t length = k < size ? at[k] : 0; + if (!length || length >= PURE_MODULE_NAME || size - k < 2 + length) return -1; + unsigned fields = at[k + 1 + length]; + if (fields > PURE_MODULE_FIELDS) return -1; + if (shapes) { + memcpy(shapes[t].name, at + k + 1, length); + shapes[t].name[length] = 0; + shapes[t].count = fields; + shapes[t].named = true; + } + k += 2 + length; + for (unsigned f = 0; f < fields; ++f) { + size_t named = size - k >= 2 ? at[k + 1] : 0; + if (size - k < 2 || !named || named >= PURE_MODULE_NAME || size - k < 2 + named || !at[k] || + at[k] >= PURE_FIELD_ROWS) + return -1; + if (shapes) { + shapes[t].fields[f] = (PureField){.type = at[k]}; + memcpy(shapes[t].fields[f].name, at + k + 2, named); + } + k += 2 + named; + } + if (shapes) pure_fields_lay_out(shapes[t].fields, fields); + } + *used = k; + return (int)outputs; +} + /* A stored list by its place: a list state, or one of the runtime's. */ static PureList *stored_list(const PureApp *app, uint32_t i) { if (i == PURE_LIST_HEARD) return app->heard; @@ -204,6 +277,8 @@ struct PureStartLog { size_t count, capacity; uint64_t tree, last; /* the tree's last save; the last entry that counts */ bool stopped, damaged; + SentShape *sent; /* the outputs the outbox's image names (PLO1), sent_count of them */ + int sent_count; /* -1: its image names none */ }; static struct Named *named_list(struct PureStartLog *index, const char *name, size_t length, bool add) { @@ -259,6 +334,7 @@ void pure_save_close_start(PureApp *app) { if (!index) return; for (size_t i = 0; i < index->count; ++i) free(index->lists[i].changes); free(index->lists); + free(index->sent); free(index); app->start_log = NULL; } @@ -270,6 +346,7 @@ PURE_COLD PureResult pure_save_open_log(PureApp *app) { if (result == PURE_OK) result = open_state(app, "", "seq", &app->stored_seq); struct PureStartLog *index = result == PURE_OK ? calloc(1, sizeof(*index)) : NULL; if (result == PURE_OK && !index) result = PURE_ALLOCATION; + if (index) index->sent_count = -1; if (result == PURE_OK && pure_draft_revision(app->stored_seq)) { unsigned char *bytes; size_t size; @@ -455,14 +532,31 @@ PURE_COLD PureResult pure_save_open_runtime(PureApp *app, uint32_t place, PureLi *out = NULL; if (!directory_of(app)) return PURE_STORE_ERROR; unsigned char *bytes = NULL, header[IMAGE_HEADER]; - size_t size = 0, length = runtime_header(place, 0, header) - 8; + size_t size = 0, length = runtime_header(place, 0, header) - 8, at = length + 8; PureResult result = pure_image_read(directory_of(app), name, &bytes, &size); bool image = result == PURE_OK; - uint64_t holds = image && size >= length + 8 && !memcmp(bytes, header, length) ? get_le(bytes + length, 8) : 0; - if (image && (size < length + 8 || memcmp(bytes, header, length))) result = PURE_STORE_ERROR; + bool named = image && place == PURE_LIST_OUTBOX && size >= 4 && !memcmp(bytes, sends_magic, 4); + bool formed = image && size >= at && (named || !memcmp(bytes, image_magic, 4)) && + !memcmp(bytes + 4, header + 4, length - 4); + uint64_t holds = formed ? get_le(bytes + length, 8) : 0; + if (formed && named) { /* the outputs its sends were laid out with */ + size_t used = 0; + int outputs = read_outputs(bytes + at, size - at, NULL, &used); + SentShape *shapes = outputs > 0 ? calloc((size_t)outputs, sizeof(*shapes)) : NULL; + if (outputs < 0 || (outputs > 0 && !shapes)) formed = false; + else if (app->start_log) { + read_outputs(bytes + at, size - at, shapes, &used); + free(app->start_log->sent); + app->start_log->sent = shapes, app->start_log->sent_count = outputs; + shapes = NULL; + } + free(shapes); + at += used; + } + if (image && !formed) result = PURE_STORE_ERROR; else if (image || result == PURE_NOT_FOUND) { result = (*out = pure_runtime_list_new(place)) ? PURE_OK : PURE_ALLOCATION; - if (result == PURE_OK && image) result = pure_list_read(*out, bytes + length + 8, size - length - 8); + if (result == PURE_OK && image) result = pure_list_read(*out, bytes + at, size - at); } free(bytes); /* Then the changes logged after the save it holds (an image is written @@ -487,11 +581,12 @@ PURE_COLD PureResult pure_save_open_runtime(PureApp *app, uint32_t place, PureLi return PURE_OK; } -PureResult pure_save_runtime_image(PureApp *app, uint32_t place) { +PureResult pure_save_runtime_image(PureApp *app, const PureModule *m, uint32_t place, const PureList *list) { unsigned char header[IMAGE_HEADER]; - size_t header_size = runtime_header(place, app->seq, header); + size_t header_size = place == PURE_LIST_OUTBOX ? outbox_header(m, app->seq, header) : + runtime_header(place, app->seq, header); return write_image(directory_of(app), runtime_lists[place - PURE_LIST_HEARD].name, header, header_size, - stored_list(app, place)); + list ? list : stored_list(app, place)); } /* ---- Checkpoints */ @@ -536,7 +631,8 @@ PureCheckpoint *pure_app_checkpoint(PureApp *app) { c->state = (uint32_t)pick; snprintf(c->name, sizeof(c->name), "%s", stored_name(app, (uint32_t)pick)); c->seq = app->seq; - c->header_size = pick >= PURE_LIST_HEARD ? runtime_header((uint32_t)pick, c->seq, c->header) : + c->header_size = pick == PURE_LIST_OUTBOX ? outbox_header(m, c->seq, c->header) : + pick >= PURE_LIST_HEARD ? runtime_header((uint32_t)pick, c->seq, c->header) : image_header(m, m->state_ref[pick], c->seq, c->header); c->logged = app->logged[pick]; c->since = app->logged_since[pick]; @@ -722,6 +818,24 @@ PURE_COLD static PureResult sent_before(PureApp *app, const char *output, uint64 * for later); the sources and sends links kept before the runtime's lists * go to them (heard_moved, sent_moved: they need images). Links kept * before PLK4 have their fields' types alone. */ +/* The shape of the output of that name among shapes, or NULL. */ +static const SentShape *sent_shape(const SentShape *shapes, unsigned count, const char *name) { + for (unsigned t = 0; t < count; ++t) + if (!strcmp(shapes[t].name, name)) return &shapes[t]; + return NULL; +} + +/* Whether sends laid out as was are laid out as m's output i: the same + * fields, names and types in order (types alone when was names none). */ +static bool same_shape(const SentShape *was, const PureModule *m, uint32_t i) { + if (was->count != m->input[i].field_count) return false; + for (uint32_t f = 0; f < was->count; ++f) { + const PureField *field = &m->field[m->input[i].field_start + f]; + if (field->type != was->fields[f].type || (was->named && strcmp(field->name, was->fields[f].name))) return false; + } + return true; +} + PURE_COLD PureResult pure_save_open_links(PureApp *app, const PureModule *m, bool *heard_moved, bool *sent_moved) { *heard_moved = *sent_moved = false; PureResult result = app->stored_links ? PURE_OK : open_state(app, "", "links", &app->stored_links); @@ -738,6 +852,8 @@ PURE_COLD PureResult pure_save_open_links(PureApp *app, const PureModule *m, boo known = first || second || named || (size >= 5 && !memcmp(bytes, links_magic_3, 4)); unsigned outputs = known ? bytes[4] : 0; result = known ? PURE_OK : PURE_STORE_ERROR; + SentShape *linked = calloc(outputs ? outputs : 1, sizeof(*linked)); /* what each was sent with, as the links say */ + if (!linked) result = PURE_ALLOCATION; for (unsigned t = 0; t < outputs && result == PURE_OK; ++t) { char *name = names[t]; size_t length = at < size ? bytes[at] : 0; @@ -749,14 +865,24 @@ PURE_COLD PureResult pure_save_open_links(PureApp *app, const PureModule *m, boo int i = pure_module_input(m, name); output[t] = i >= 0 && m->input[i].kind == PURE_KIND_OUTPUT ? i : -1; fits[t] = output[t] >= 0 && m->input[i].field_count == fields; + memcpy(linked[t].name, name, length + 1); + linked[t].count = fields; + linked[t].named = named; + if (fields > PURE_MODULE_FIELDS) result = PURE_STORE_ERROR; for (unsigned f = 0; f < fields && result == PURE_OK; ++f) { /* its type, then (PLK4) its name */ size_t named_length = named && size - at >= 2 ? bytes[at + 1] : 0; - if (size - at < (named ? 2 + named_length : 1)) { result = PURE_STORE_ERROR; break; } + if (size - at < (named ? 2 + named_length : 1) || named_length >= PURE_MODULE_NAME) { + result = PURE_STORE_ERROR; + break; + } const PureField *field = fits[t] ? &m->field[m->input[i].field_start + f] : NULL; fits[t] = field && field->type == bytes[at] && (!named || (strlen(field->name) == named_length && !memcmp(field->name, bytes + at + 2, named_length))); + linked[t].fields[f] = (PureField){.type = bytes[at]}; + if (named) memcpy(linked[t].fields[f].name, bytes + at + 2, named_length); at += named ? 2 + named_length : 1; } + if (result == PURE_OK) pure_fields_lay_out(linked[t].fields, fields); if (result != PURE_OK || size - at < 8) { result = PURE_STORE_ERROR; break; } uint64_t last = get_le(bytes + at, 8); at += 8; @@ -795,13 +921,48 @@ PURE_COLD PureResult pure_save_open_links(PureApp *app, const PureModule *m, boo app->links_changed = true; } if (result == PURE_OK && !first && !second && at != size) result = PURE_STORE_ERROR; - /* Every send left goes out on an output of this module with its fields. */ + /* Every send left goes out on an output of this module, laid out with + * its fields: one laid out otherwise (as the outbox's image names them, + * else as the links do) is recast by name. */ + bool imaged = app->start_log && app->start_log->sent_count >= 0; + const SentShape *shapes = imaged ? app->start_log->sent : linked; + unsigned shape_count = imaged ? (unsigned)app->start_log->sent_count : outputs; for (size_t k = 0, count = pure_list_count(app->outbox); result == PURE_OK && k < count; ++k) { - PureCell name = pure_list_get(app->outbox, k, 0); - unsigned t = 0; - while (t < outputs && (strlen(names[t]) != name.size || memcmp(names[t], name.text, name.size))) ++t; - if (t == outputs || !fits[t]) result = PURE_CONFLICT; + PureCell cell = pure_list_get(app->outbox, k, 0); + char name[PURE_MODULE_NAME] = ""; + if (cell.size < PURE_MODULE_NAME) memcpy(name, cell.text, cell.size); + int i = pure_module_input(m, name); + const SentShape *was = sent_shape(shapes, shape_count, name); + if (i < 0 || m->input[i].kind != PURE_KIND_OUTPUT) { + snprintf(app->why, sizeof(app->why), "sends wait on the output '%s', which this module does not have", name); + result = PURE_CONFLICT; + } else if (!was) result = PURE_STORE_ERROR; /* sent on an output its store does not name */ + else if (!same_shape(was, m, (uint32_t)i)) { + PureCell payload = pure_list_get(app->outbox, k, 2); + unsigned char *made = NULL; + size_t made_size = 0; + if (!was->named) { + snprintf(app->why, sizeof(app->why), + "sends wait on '%s', laid out with fields its store names by type alone: start the old module once", + name); + result = PURE_CONFLICT; + } else if ((result = pure_send_recast(was->fields, was->count, &m->field[m->input[i].field_start], + m->input[i].field_count, (PureSpan){payload.text, payload.size}, + &made, &made_size)) == PURE_OK) { + result = pure_list_set(app->outbox, k, 2, (PureCell){.text = made, .size = made_size}, NULL); + *sent_moved = true; + } + free(made); + } + } + /* Sends from now on are laid out with this module's fields: an image + * naming others is written again, naming these. */ + for (uint32_t i = 0; result == PURE_OK && i < m->inputs && !*sent_moved; ++i) { + if (m->input[i].kind != PURE_KIND_OUTPUT) continue; + const SentShape *was = sent_shape(shapes, shape_count, m->input_names[i]); + *sent_moved = !was || !same_shape(was, m, i); } + free(linked); free(bytes); app->sent_saved = pure_list_count(app->outbox); memcpy(app->saved_sent, app->last_sent, sizeof(app->saved_sent)); |
