commit 12670a1d14b664c94b403255368880acf880ccf2 from: Isaac Meerleo date: Sat Jul 18 17:53:19 2026 UTC Strengthen Artifact boundary and cancellation proofs commit - d5e57bf6e1f3359aa57a5535d382d3509e1515a3 commit + 12670a1d14b664c94b403255368880acf880ccf2 blob - dfeb75234cd47546b69f831bdd53ad9733c02a09 blob + 635b94127c330ea75a825fa89c393fbaa65b18c3 --- regress/artifact_result/artifact_result_test.c +++ regress/artifact_result/artifact_result_test.c @@ -21,6 +21,7 @@ #include "json.h" #include "log.h" #include "regress.h" +#include "xmalloc.h" static const char good_id[] = "0123456789abcdef0123456789abcdef"; @@ -412,10 +413,7 @@ test_exact_inline_boundaries(void) u_char *raw; size_t i, max; - raw = malloc(ARTIFACT_RESULT_MAX + 1); - CHECK(raw != NULL); - if (raw == NULL) - return; + raw = xmalloc(ARTIFACT_RESULT_MAX + 1); buf_init(&out); for (i = 0; i < sizeof(shapes) / sizeof(shapes[0]); i++) { max = shapes[i].head_raw_max + shapes[i].tail_raw_max; @@ -472,10 +470,7 @@ test_multibyte_inline_boundary(void) tail_len = ARTIFACT_PREVIEW_TAIL_ENCODED_MAX / sizeof(snowman) * sizeof(snowman); max = head_len + tail_len; - raw = malloc(max + sizeof(heart)); - CHECK(raw != NULL); - if (raw == NULL) - return; + raw = xmalloc(max + sizeof(heart)); for (i = 0; i < head_len; i += sizeof(euro)) memcpy(raw + i, euro, sizeof(euro)); for (i = 0; i < tail_len; i += sizeof(snowman)) @@ -521,6 +516,38 @@ test_multibyte_inline_boundary(void) ARTIFACT_PREVIEW_HEAD_ENCODED_MAX / 4 * 3, ARTIFACT_PREVIEW_TAIL_ENCODED_MAX / 4 * 3, "base64", 1); streamed_matches_contiguous(raw, in.raw_len, 12289); + + /* Fill the two bytes left by whole three-byte runes with ASCII. Inserting + * one more ASCII byte is still valid UTF-8 but must cross the exact encoded + * head-plus-tail admission boundary. */ + head_len = ARTIFACT_PREVIEW_HEAD_ENCODED_MAX; + tail_len = ARTIFACT_PREVIEW_TAIL_ENCODED_MAX; + max = head_len + tail_len; + for (i = 0; i < head_len - 2; i += sizeof(euro)) + memcpy(raw + i, euro, sizeof(euro)); + raw[head_len - 2] = 'x'; + raw[head_len - 1] = 'y'; + for (i = 0; i < tail_len; i += sizeof(snowman)) + memcpy(raw + head_len + i, snowman, sizeof(snowman)); + CHECK(artifact_result_inline_fits(raw, max)); + in.raw_len = max; + in.observed_bytes = max; + in.retained_bytes = max; + in.id = NULL; + CHECK(artifact_result_build(&out, &in) == 0); + check_preview_result(&out, raw, max, head_len, tail_len, "utf-8", 0); + streamed_matches_contiguous(raw, max, 4093); + memmove(raw + head_len + 1, raw + head_len, tail_len); + raw[head_len] = 'z'; + CHECK(!artifact_result_inline_fits(raw, max + 1)); + in.raw_len = max + 1; + in.observed_bytes = in.raw_len; + in.retained_bytes = in.raw_len; + in.id = good_id; + CHECK(artifact_result_build(&out, &in) == 0); + check_preview_result(&out, raw, in.raw_len, head_len, tail_len, + "utf-8", 1); + streamed_matches_contiguous(raw, in.raw_len, 8191); buf_free(&out); free(raw); } blob - c82aa3744f59ce154cc0ce1ad52633a12258b09c blob + 604fbce56358f2809e76af29679909716fe1be11 --- regress/artifact_store/artifact_store_test.c +++ regress/artifact_store/artifact_store_test.c @@ -61,26 +61,28 @@ exists(const char *root, const char *session, const ch } static int +artifact_namespace_name(const struct dirent *de) +{ + return (strncmp(de->d_name, ".pending-", 9) == 0 || + strncmp(de->d_name, ".order-", 7) == 0 || + artifact_id_valid(de->d_name)); +} + +static int artifact_namespace_empty(const char *root, const char *session) { char path[PATH_MAX]; - struct dirent *de; - DIR *dir; - int empty = 1; + struct dirent **names = NULL; + int count, i; if ((size_t)snprintf(path, sizeof(path), "%s/%s", root, session) >= - sizeof(path) || (dir = opendir(path)) == NULL) + sizeof(path) || (count = scandirat(AT_FDCWD, path, &names, + artifact_namespace_name, NULL)) == -1) return (0); - while ((de = readdir(dir)) != NULL) { - if (strncmp(de->d_name, ".pending-", 9) == 0 || - strncmp(de->d_name, ".order-", 7) == 0 || - artifact_id_valid(de->d_name)) { - empty = 0; - break; - } - } - closedir(dir); - return (empty); + for (i = 0; i < count; i++) + free(names[i]); + free(names); + return (count == 0); } static void @@ -714,10 +716,14 @@ test_enospc_storage_errors(const char *root) close(write_fd); CHECK(rc == -1 && terminal.status == ARTIFACT_STORE_ERROR && terminal.retained_bytes == 0); - (void)snprintf(name, sizeof(name), ".pending-%s", stream.id); + if ((size_t)snprintf(name, sizeof(name), ".pending-%s", stream.id) >= + sizeof(name)) + errx(1, "pending name too long"); CHECK(exists(root, "enospc-write", name) && !exists(root, "enospc-write", stream.id)); - (void)snprintf(name, sizeof(name), ".order-%s", stream.id); + if ((size_t)snprintf(name, sizeof(name), ".order-%s", stream.id) >= + sizeof(name)) + errx(1, "order name too long"); CHECK(exists(root, "enospc-write", name)); CHECK(artifact_store_retain(store, 1, stream.id) == -1 && artifact_store_turn_prepare(store) == -1); blob - b316e27616cc32b9578a41eb88eb89cb87e15434 blob + 4aa6cbfc990bfd2484d1c0005273f56e13d04b82 --- regress/turn/run.sh +++ regress/turn/run.sh @@ -334,6 +334,7 @@ ok "Session lock: owner reaches an idle line-mode prom cp "$journal" "$dir/session-journal.before" cp "$dir/reqlog" "$dir/session-requests.before" +LC_ALL=C ls -1A "$sessions" > "$dir/session-namespace.before" run_fugu -r "$sid" -p "losing same-Session contender" ok "Session lock: contender is refused" \ $([ "$rc" -ne 0 ]; echo $?) @@ -343,6 +344,9 @@ kill -0 "$fugupid" 2>/dev/null ok "Session lock: original owner remains live" $? cmp -s "$dir/session-journal.before" "$journal" ok "Session lock: losing contender leaves the Journal byte-identical" $? +LC_ALL=C ls -1A "$sessions" > "$dir/session-namespace.after" +cmp -s "$dir/session-namespace.before" "$dir/session-namespace.after" +ok "Session lock: losing contender leaves the Journal namespace unchanged" $? cmp -s "$dir/session-requests.before" "$dir/reqlog" && ! test -s "$dir/reqlog" ok "Session lock: losing contender sends zero Provider requests" $? @@ -738,20 +742,38 @@ stop_stub # foreground executor, reports storage_error to the next Generation, and rolls # back every private staging/final name instead of journaling an Artifact. rm -rf "$HOME/.fugu" +mkdir -p "$dir/enospc-project" start_stub "STORAGEFULL OK" write_conf "$stubport" t0=$(date +%s) -FUGU_SELFTEST=1 FUGU_ARTIFACT_ENOSPC_WRITE=1 \ +( cd "$dir/enospc-project" && \ + FUGU_SELFTEST=1 FUGU_ARTIFACT_ENOSPC_WRITE=1 \ FUGU_CONF=$dir/fugu.conf FUGU_LIBEXEC=$dir/libexec/fugu \ FUGU_CA_FILE=$dir/cert.pem FUGU_RETRY_BASE_MS=50 \ "$fugu" -p "exercise storage full" "$dir/out" 2>"$dir/errout" + >"$dir/out" 2>"$dir/errout" ) rc=$? t1=$(date +%s) ok "Artifact ENOSPC: Turn recovers through the next Generation" \ $([ "$rc" -eq 0 ]; echo $?) ok "Artifact ENOSPC: live foreground executor is cancelled promptly" \ $([ $((t1 - t0)) -lt 15 ]; echo $?) +enospc_pid=$(cat "$dir/enospc-project/enospc.pid" 2>/dev/null || true) +case "$enospc_pid" in +''|*[!0-9]*) fail "Artifact ENOSPC: fixture records the live child pid" ;; +*) pass "Artifact ENOSPC: fixture records the live child pid" ;; +esac +i=0 +while [ -n "$enospc_pid" ] && /bin/kill -0 "$enospc_pid" 2>/dev/null && + [ "$i" -lt 100 ]; do + sleep 0.02 + i=$((i + 1)) +done +if [ -n "$enospc_pid" ] && /bin/kill -0 "$enospc_pid" 2>/dev/null; then + fail "Artifact ENOSPC: executor process group has no surviving child" +else + pass "Artifact ENOSPC: executor process group has no surviving child" +fi request_log_entry 2 "$dir/enospc-request" grep -Fq '\"termination\":\"storage_error\"' "$dir/enospc-request" ok "Artifact ENOSPC: the continuation receives storage_error" $? blob - 6fbb99b07377c4135999435bef4335f28887d4fc blob + 45c12b71a2088f40f83ca91b6dd43a40fc6122f5 --- regress/turn/stub.c +++ regress/turn/stub.c @@ -1072,8 +1072,9 @@ static const char sse_storagefull[] = "event: content_block_delta\n" "data: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":" "{\"type\":\"input_json_delta\",\"partial_json\":" - "\"{\\\"command\\\":\\\"printf ENOSPC-WRITE-START; sleep 30; " - "printf ENOSPC-WRITE-NOT-CANCELLED\\\"}\"}}\n\n" + "\"{\\\"command\\\":\\\"sleep 30 & child=$!; echo $child > " + "enospc.pid; printf ENOSPC-WRITE-START; wait $child; printf " + "ENOSPC-WRITE-NOT-CANCELLED\\\"}\"}}\n\n" "event: content_block_stop\n" "data: {\"type\":\"content_block_stop\",\"index\":0}\n\n" "event: message_delta\n"