Compare commits
No commits in common. "cc87f26b981a60724a55959e881b36accf692daf" and "d29532a7354092bb7ce0842f0383a33fae176736" have entirely different histories.
cc87f26b98
...
d29532a735
13 changed files with 456 additions and 934 deletions
|
|
@ -2,12 +2,6 @@
|
|||
|
||||
## Unreleased — 0.1.2
|
||||
|
||||
- Closed the native candidate warning gate with checked path and HTTP response
|
||||
bounds, strict GCC and Clang builds, full Clang ASan/UBSan validation, and a
|
||||
stable concurrent object-I/O MTP double. Rebuilt the desktop, current Android
|
||||
and non-installed schema-v17 bridge candidates with recorded digests; no
|
||||
service, device, user database, signing secret or deployment was touched.
|
||||
|
||||
- Normalized the recent synchronization, generation-MTP, Android backup,
|
||||
Web Dashboard serialization, tests, I/O doubles and packaging code for human
|
||||
readability. Added scoped C formatting rules and review guidance without
|
||||
|
|
|
|||
|
|
@ -8,13 +8,6 @@ non-installed schema-v17 bridge candidate is reproducibly built and tested.
|
|||
Physical-device MTP, release signing, real user backup/restore, main integration
|
||||
and deployment are not complete.
|
||||
|
||||
`TRAINLOG_NATIVE_BUILD_AND_CANDIDATE_V1` closes the native warning gate. Strict
|
||||
GCC 16.2.1 and Clang 22.1.8 builds now pass the same 81-test inventory, and the
|
||||
Clang ASan/UBSan build passes it with leak detection. Bounded path assembly and
|
||||
the Web sync accepted response reject insufficient capacity without publishing
|
||||
partial output. Fresh desktop, current-Android and schema-v17 bridge candidates
|
||||
were rebuilt from commit `2c5a8be2827e31fc5be2accb65073bfeb79d6068`.
|
||||
|
||||
Snapshot date: **2026-09-17**.
|
||||
|
||||
`TRAINLOG_CODE_READABILITY_V1` normalizes the recent generation-MTP,
|
||||
|
|
|
|||
|
|
@ -49,18 +49,6 @@ and non-destructive hardware smoke. Drive remains separate and untested here.
|
|||
This is a non-executed rollout checklist. The development branch implements the
|
||||
path; the daily installation still uses V3.
|
||||
|
||||
The latest software-only candidate set was rebuilt under the private root
|
||||
`/home/fy59/.cache/trainlog/native-build-candidate-v1.VbFjE9` from source commit
|
||||
`2c5a8be2827e31fc5be2accb65073bfeb79d6068`. Its desktop inventory records every
|
||||
packaged file digest plus schemas, protocols, entry points and runtime
|
||||
dependencies. The current debug APK SHA-256 is
|
||||
`aca2f499ee9ff53ba5913a76a2a0117274e3e5545967ce361f6f126a6af497e1`; the
|
||||
non-installed bridge APK SHA-256 is
|
||||
`0a28d81cbb0818cdf2fee72d5024a4b706fcfda39f1182c682c12f000a026cb4`; and its
|
||||
synthetic schema-v17 backup SHA-256 is
|
||||
`06eb3999c6f3ce2fd8f551b38727a0a67fe6b48dde4c3244b3d50357afa7e6a3`.
|
||||
These are unsigned development evidence, not deployment authorization.
|
||||
|
||||
## Compatible set
|
||||
|
||||
- desktop binary containing schema v21, the Web API and the generation worker;
|
||||
|
|
|
|||
|
|
@ -1317,14 +1317,12 @@ behavioral assertions. Python syntax/orchestrator tests, Android generation and
|
|||
backup tests, the I/O-boundary MTP browser proof, JSON/import validators and
|
||||
the isolated harness cover the other normalized sources.
|
||||
|
||||
GCC 16.2.1 and Clang 22.1.8 both pass separate strict C17 builds and the full
|
||||
**81/81** Meson inventory with `werror=true` and warning level 3. The former GCC
|
||||
`format-truncation` findings are closed by exact checked path construction in
|
||||
the generation-MTP tests/double and by a production accepted-response
|
||||
serializer that reports its byte count. Boundary tests cover exact fit,
|
||||
one-byte insufficiency and multibyte path components; failure leaves no partial
|
||||
path or response available to a caller. Clang ASan/UBSan passes the same
|
||||
**81/81** inventory with leak detection.
|
||||
GCC 16.2.1 now compiles beyond the former `misleading-indentation` failures in
|
||||
`json_writer.c`, `web_dashboard_json.c`, `web_dashboard.c`,
|
||||
`dashboard_layout.c`, and `generation_mtp.c`. The separate strict build remains
|
||||
blocked by distinct pre-existing `format-truncation` diagnostics in the Web
|
||||
accepted-response buffer and bounded test path buffers. Warning policy remains
|
||||
unchanged; GCC is not reported as green.
|
||||
|
||||
The readability closeout preserves the **81/81** normal Meson and **81/81**
|
||||
ASan/UBSan inventories. The isolated Android run reports **220 tests: 215
|
||||
|
|
@ -1332,14 +1330,3 @@ passed, five skipped, zero failures/errors**, and `assembleDebug` succeeds on
|
|||
JDK 17. The real Firefox/object-I/O-boundary MTP conversation also passes after
|
||||
formatting. A source comparison against the pre-readability commit confirms
|
||||
that the scoped test names and assertion counts are unchanged.
|
||||
|
||||
The isolated JDK 17 harness reports **220 Android tests: 215 passed, five
|
||||
skipped, zero failures/errors**. The skips are optional, externally supplied
|
||||
copy gates: `BridgeBackupMigrationTest.verifiedSchemaSeventeenBridgeBackupMigratesThroughCurrentOwner`,
|
||||
`RealAndroidV9BodyZonesMigrationTest.realVersionNineCopyMigratesWithoutChangingExistingTables`,
|
||||
`RealAndroidV14FeedbackRevisionMigrationTest.freshRealCopyMigratesLosslesslyToVersionFifteen`,
|
||||
and the version-thirteen and version-twelve methods in
|
||||
`RealAndroidV12MachineExerciseMigrationTest`. They require respectively the
|
||||
bridge backup input, a real v9 fixture, a real v14 fixture/output directory,
|
||||
and real v13/v12 fixtures with explicit output directories. Synthetic bridge
|
||||
and current-schema migration proofs run separately; no user database is read.
|
||||
|
|
|
|||
|
|
@ -5,11 +5,8 @@ import json, os, signal, socket, sqlite3, subprocess, tempfile, time, unittest
|
|||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
BUILD = ROOT / os.environ.get("TRAINLOG_WEB_E2E_BUILD", "build") / "tui"
|
||||
GECKO = Path(
|
||||
os.environ.get(
|
||||
"TRAINLOG_GECKODRIVER", "/home/fy59/.cache/trainlog/web-e2e-tools/geckodriver"
|
||||
)
|
||||
os.environ.get("TRAINLOG_GECKODRIVER", "/home/fy59/.cache/trainlog/web-e2e-tools/geckodriver")
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -42,9 +39,7 @@ class BrowserSyncTest(unittest.TestCase):
|
|||
transport.mkdir()
|
||||
owned = root / "owned"
|
||||
owned.mkdir()
|
||||
remote = (
|
||||
(root / "device/Documents/Trainlog") if mode == "mtp" else transport
|
||||
)
|
||||
remote = (root / "device/Documents/Trainlog") if mode == "mtp" else transport
|
||||
remote.mkdir(parents=True, exist_ok=True)
|
||||
release = root / "release-android"
|
||||
android_log = (root / "android.log").open("wb")
|
||||
|
|
@ -119,13 +114,13 @@ class BrowserSyncTest(unittest.TestCase):
|
|||
env.update(
|
||||
{
|
||||
"TRAINLOG_SYNC_MTP_ADAPTER": str(
|
||||
BUILD / "generation-mtp-adapter-double"
|
||||
ROOT / "build/tui/generation-mtp-adapter-double"
|
||||
),
|
||||
"TRAINLOG_MTP_DOUBLE_ROOT": str(root / "device"),
|
||||
}
|
||||
)
|
||||
server = subprocess.Popen(
|
||||
[str(BUILD / "trainlog"), "--web", "--port", str(port)],
|
||||
[str(ROOT / "build/tui/trainlog"), "--web", "--port", str(port)],
|
||||
env=env,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
|
|
@ -144,15 +139,11 @@ class BrowserSyncTest(unittest.TestCase):
|
|||
driver.get(f"http://127.0.0.1:{port}/")
|
||||
wait = WebDriverWait(driver, 15)
|
||||
button = wait.until(
|
||||
lambda d: d.find_element(
|
||||
By.XPATH, "//button[normalize-space()='Synchroniser']"
|
||||
)
|
||||
lambda d: d.find_element(By.XPATH, "//button[normalize-space()='Synchroniser']")
|
||||
)
|
||||
wait.until(lambda _: button.is_enabled())
|
||||
button.click()
|
||||
state_path = Path(
|
||||
str(xdg / "trainlog" / "trainlog.db") + ".sync-run.json"
|
||||
)
|
||||
state_path = Path(str(xdg / "trainlog" / "trainlog.db") + ".sync-run.json")
|
||||
observed = await_condition(
|
||||
lambda: (
|
||||
json.loads(state_path.read_text())
|
||||
|
|
@ -172,10 +163,7 @@ class BrowserSyncTest(unittest.TestCase):
|
|||
self.assertEqual(committed["result"], "running")
|
||||
with sqlite3.connect(xdg / "trainlog" / "trainlog.db") as database:
|
||||
self.assertEqual(
|
||||
database.execute(
|
||||
"SELECT COUNT(*) FROM execution_drafts"
|
||||
).fetchone()[0],
|
||||
1,
|
||||
database.execute("SELECT COUNT(*) FROM execution_drafts").fetchone()[0], 1
|
||||
)
|
||||
release.touch()
|
||||
final = await_condition(
|
||||
|
|
@ -197,21 +185,16 @@ class BrowserSyncTest(unittest.TestCase):
|
|||
)
|
||||
summary.click()
|
||||
wait.until(
|
||||
lambda d: "Brouillons reçus" in d.page_source
|
||||
and "active" in d.page_source
|
||||
lambda d: "Brouillons reçus" in d.page_source and "active" in d.page_source
|
||||
)
|
||||
driver.refresh()
|
||||
wait.until(lambda d: "Synchronisation confirmée" in d.page_source)
|
||||
state = json.loads(
|
||||
Path(
|
||||
str(xdg / "trainlog" / "trainlog.db") + ".sync-run.json"
|
||||
).read_text()
|
||||
Path(str(xdg / "trainlog" / "trainlog.db") + ".sync-run.json").read_text()
|
||||
)
|
||||
self.assertEqual(state["phase"], "completed")
|
||||
self.assertEqual(
|
||||
android.wait(timeout=30),
|
||||
0,
|
||||
(root / "android.log").read_text(errors="replace"),
|
||||
android.wait(timeout=30), 0, (root / "android.log").read_text(errors="replace")
|
||||
)
|
||||
finally:
|
||||
if driver is not None:
|
||||
|
|
|
|||
|
|
@ -72,11 +72,7 @@ def run_adapter(
|
|||
diagnostic = (
|
||||
result.stderr.decode("utf-8", "replace")[:1024] or "MTP adapter failed"
|
||||
).strip()
|
||||
missing_peer = diagnostic in {
|
||||
"double failed 6 expected Android peer not found",
|
||||
"MTP adapter failed status=6 diagnostic=expected Android peer not found",
|
||||
}
|
||||
if allow_missing_peer and missing_peer:
|
||||
if allow_missing_peer and "expected peer was not found" in diagnostic:
|
||||
return False
|
||||
raise RuntimeError(diagnostic)
|
||||
return True
|
||||
|
|
@ -139,12 +135,7 @@ def main() -> int:
|
|||
if args.mtp_adapter is None:
|
||||
raise RuntimeError("MTP adapter is required")
|
||||
pump = lambda: run_adapter(
|
||||
args.mtp_adapter,
|
||||
"pull",
|
||||
args.expected_peer,
|
||||
args.transport_root,
|
||||
deadline,
|
||||
True,
|
||||
args.mtp_adapter, "pull", args.expected_peer, args.transport_root, deadline, True
|
||||
)
|
||||
# Android publishes its durable identity only while participating. A
|
||||
# missing expected advertisement is therefore a bounded wait state;
|
||||
|
|
@ -167,9 +158,7 @@ def main() -> int:
|
|||
request_path = args.transport_root / "request-v1.json"
|
||||
publish_json(request_path, request)
|
||||
if args.mode == "mtp":
|
||||
run_adapter(
|
||||
args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline
|
||||
)
|
||||
run_adapter(args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline)
|
||||
emit(
|
||||
args.run_id,
|
||||
"waiting_android_publication",
|
||||
|
|
@ -185,9 +174,7 @@ def main() -> int:
|
|||
ack_path = args.transport_root / "desktop-consumption-ack-v1.json"
|
||||
publish_json(ack_path, ack)
|
||||
if args.mode == "mtp":
|
||||
run_adapter(
|
||||
args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline
|
||||
)
|
||||
run_adapter(args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline)
|
||||
if ack["result"] != "consumed":
|
||||
raise RuntimeError("desktop rejected Android generation")
|
||||
emit(
|
||||
|
|
@ -218,15 +205,8 @@ def main() -> int:
|
|||
}
|
||||
publish_json(args.transport_root / "desktop-generation-v1.json", ref)
|
||||
if args.mode == "mtp":
|
||||
run_adapter(
|
||||
args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline
|
||||
)
|
||||
emit(
|
||||
args.run_id,
|
||||
"published",
|
||||
outbound_generation_id=outgoing_id,
|
||||
manifest_sha256=digest,
|
||||
)
|
||||
run_adapter(args.mtp_adapter, "push", args.expected_peer, args.transport_root, deadline)
|
||||
emit(args.run_id, "published", outbound_generation_id=outgoing_id, manifest_sha256=digest)
|
||||
emit(args.run_id, "waiting_acknowledgement", outbound_generation_id=outgoing_id)
|
||||
android_ack = args.transport_root / "android-consumption-ack-v1.json"
|
||||
android_ack_value = wait_json(
|
||||
|
|
@ -296,8 +276,4 @@ def main() -> int:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except RuntimeError as error:
|
||||
print(f"sync peer failed: {error}", file=sys.stderr)
|
||||
raise SystemExit(1) from None
|
||||
raise SystemExit(main())
|
||||
|
|
|
|||
|
|
@ -2,25 +2,17 @@
|
|||
#define TRAINLOG_WEB_SERVER_H
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "trainlog/database.h"
|
||||
|
||||
/* Serialize the exact successful sync-admission document. Both identities are
|
||||
* borrowed, validated sy_<uuid-v4> values. On failure output is empty. */
|
||||
bool trainlog_web_sync_accepted_serialize(
|
||||
const char *request_id, const char *run_id, char *output, size_t capacity, size_t *output_size);
|
||||
|
||||
/* Blocking, single-threaded local HTTP adapter. Port zero is reserved for
|
||||
* controlled test harnesses and is rejected by the CLI. The database remains
|
||||
* borrowed and process-owned; this infrastructure route never reads it. */
|
||||
int trainlog_web_server_run(TrainlogDatabase *database,
|
||||
const char *database_path,
|
||||
uint16_t port,
|
||||
const volatile sig_atomic_t *stop_requested,
|
||||
char *diagnostic,
|
||||
size_t diagnostic_capacity);
|
||||
int trainlog_web_server_run(TrainlogDatabase *database, const char *database_path,
|
||||
uint16_t port,
|
||||
const volatile sig_atomic_t *stop_requested,
|
||||
char *diagnostic, size_t diagnostic_capacity);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
1035
tui/src/web_server.c
1035
tui/src/web_server.c
File diff suppressed because it is too large
Load diff
|
|
@ -1,33 +0,0 @@
|
|||
#ifndef TRAINLOG_TEST_BOUNDED_PATH_H
|
||||
#define TRAINLOG_TEST_BOUNDED_PATH_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
/* CONTRACT: failure leaves output empty and no caller may perform filesystem
|
||||
* work with a partial path. Lengths include the separator and trailing NUL. */
|
||||
static bool
|
||||
trainlog_test_join_path(char *output, size_t capacity, const char *parent, const char *leaf) {
|
||||
size_t parent_length;
|
||||
size_t leaf_length;
|
||||
|
||||
if (output == NULL || capacity == 0U || parent == NULL || leaf == NULL) {
|
||||
return false;
|
||||
}
|
||||
output[0] = '\0';
|
||||
parent_length = strlen(parent);
|
||||
leaf_length = strlen(leaf);
|
||||
if (leaf_length > SIZE_MAX - 2U || parent_length > SIZE_MAX - leaf_length - 2U ||
|
||||
parent_length + leaf_length + 2U > capacity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(void)memcpy(output, parent, parent_length);
|
||||
output[parent_length] = '/';
|
||||
(void)memcpy(output + parent_length + 1U, leaf, leaf_length);
|
||||
output[parent_length + leaf_length + 1U] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include "trainlog/generation_mtp.h"
|
||||
#include "bounded_path.h"
|
||||
|
||||
typedef struct Node {
|
||||
uint32_t id, parent;
|
||||
|
|
@ -17,41 +15,13 @@ typedef struct Node {
|
|||
static Node nodes[2048];
|
||||
static size_t node_count;
|
||||
static const char *remote;
|
||||
static bool scan_failed;
|
||||
static uint32_t stable_path_id(const char *path) {
|
||||
const unsigned char *cursor = (const unsigned char *)path;
|
||||
uint32_t value = UINT32_C(2166136261);
|
||||
while (*cursor != '\0') {
|
||||
value ^= (uint32_t)*cursor++;
|
||||
value *= UINT32_C(16777619);
|
||||
}
|
||||
return value == 0U || value == UINT32_MAX ? value ^ UINT32_C(0x80000000) : value;
|
||||
}
|
||||
|
||||
static uint32_t scan(const char *path, uint32_t parent) {
|
||||
DIR *d;
|
||||
struct dirent *e;
|
||||
uint32_t self;
|
||||
Node *n;
|
||||
uint32_t self = (uint32_t)(node_count + 1U);
|
||||
Node *n = &nodes[node_count++];
|
||||
struct stat st;
|
||||
if (lstat(path, &st) != 0) {
|
||||
if (errno != ENOENT) {
|
||||
scan_failed = true;
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
if (node_count >= sizeof(nodes) / sizeof(nodes[0])) {
|
||||
scan_failed = true;
|
||||
return 0U;
|
||||
}
|
||||
self = stable_path_id(path);
|
||||
for (size_t index = 0U; index < node_count; index++) {
|
||||
if (nodes[index].id == self && strcmp(nodes[index].path, path) != 0) {
|
||||
scan_failed = true;
|
||||
return 0U;
|
||||
}
|
||||
}
|
||||
n = &nodes[node_count++];
|
||||
lstat(path, &st);
|
||||
n->id = self;
|
||||
n->parent = parent;
|
||||
n->folder = S_ISDIR(st.st_mode);
|
||||
|
|
@ -63,23 +33,17 @@ static uint32_t scan(const char *path, uint32_t parent) {
|
|||
while ((e = readdir(d))) {
|
||||
if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) {
|
||||
char child[1024];
|
||||
if (!trainlog_test_join_path(child, sizeof(child), path, e->d_name)) {
|
||||
scan_failed = true;
|
||||
break;
|
||||
}
|
||||
snprintf(child, sizeof(child), "%s/%s", path, e->d_name);
|
||||
scan(child, self);
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
} else if (n->folder && errno != ENOENT) {
|
||||
scan_failed = true;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
static bool rebuild(void) {
|
||||
static void rebuild(void) {
|
||||
node_count = 0U;
|
||||
scan_failed = false;
|
||||
return scan(remote, UINT32_MAX) != 0U && !scan_failed;
|
||||
scan(remote, UINT32_MAX);
|
||||
}
|
||||
static Node *node(uint32_t id) {
|
||||
size_t i;
|
||||
|
|
@ -123,9 +87,7 @@ static TrainlogStatus children(unsigned int b,
|
|||
(void)b;
|
||||
(void)d;
|
||||
(void)s;
|
||||
if (!rebuild()) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
rebuild();
|
||||
if (p == UINT32_MAX) {
|
||||
p = nodes[0].id;
|
||||
}
|
||||
|
|
@ -159,16 +121,12 @@ static TrainlogStatus ensure(unsigned int b,
|
|||
(void)b;
|
||||
(void)d;
|
||||
(void)s;
|
||||
if (!rebuild()) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
rebuild();
|
||||
parent = node(p);
|
||||
if (!parent) {
|
||||
return TRAINLOG_STATUS_NOT_FOUND;
|
||||
}
|
||||
if (!trainlog_test_join_path(path, sizeof(path), parent->path, name)) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/%s", parent->path, name);
|
||||
if (mkdir(path, 0700) == 0) {
|
||||
*created = true;
|
||||
} else if (access(path, F_OK) == 0) {
|
||||
|
|
@ -176,9 +134,7 @@ static TrainlogStatus ensure(unsigned int b,
|
|||
} else {
|
||||
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||
}
|
||||
if (!rebuild()) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
rebuild();
|
||||
size_t i;
|
||||
for (i = 0; i < node_count; i++) {
|
||||
if (strcmp(nodes[i].path, path) == 0) {
|
||||
|
|
@ -206,9 +162,7 @@ static TrainlogStatus receive(unsigned int b, unsigned int d, uint32_t id, const
|
|||
Node *n;
|
||||
(void)b;
|
||||
(void)d;
|
||||
if (!rebuild()) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
rebuild();
|
||||
n = node(id);
|
||||
return n && copy(n->path, path) == 0 ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||
}
|
||||
|
|
@ -224,22 +178,16 @@ static TrainlogStatus send_file(unsigned int b,
|
|||
(void)b;
|
||||
(void)d;
|
||||
(void)s;
|
||||
if (!rebuild()) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
rebuild();
|
||||
parent = node(p);
|
||||
if (!parent) {
|
||||
return TRAINLOG_STATUS_NOT_FOUND;
|
||||
}
|
||||
if (!trainlog_test_join_path(path, sizeof(path), parent->path, name)) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/%s", parent->path, name);
|
||||
if (copy(local, path) != 0) {
|
||||
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||
}
|
||||
if (!rebuild()) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
rebuild();
|
||||
size_t i;
|
||||
for (i = 0; i < node_count; i++) {
|
||||
if (strcmp(nodes[i].path, path) == 0) {
|
||||
|
|
@ -253,38 +201,26 @@ static TrainlogStatus remove_object(unsigned int b, unsigned int d, uint32_t id)
|
|||
Node *n;
|
||||
(void)b;
|
||||
(void)d;
|
||||
if (!rebuild()) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
rebuild();
|
||||
n = node(id);
|
||||
return n && unlink(n->path) == 0 ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||
}
|
||||
static TrainlogStatus rename_object(unsigned int b, unsigned int d, uint32_t id, const char *name) {
|
||||
Node *n;
|
||||
char parent[1024];
|
||||
char path[1024];
|
||||
char *slash;
|
||||
char path[1024], *slash;
|
||||
(void)b;
|
||||
(void)d;
|
||||
if (!rebuild()) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
rebuild();
|
||||
n = node(id);
|
||||
if (!n) {
|
||||
return TRAINLOG_STATUS_NOT_FOUND;
|
||||
}
|
||||
if (strlen(n->path) >= sizeof(parent)) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
(void)memcpy(parent, n->path, strlen(n->path) + 1U);
|
||||
slash = strrchr(parent, '/');
|
||||
snprintf(path, sizeof(path), "%s", n->path);
|
||||
slash = strrchr(path, '/');
|
||||
if (!slash) {
|
||||
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||
}
|
||||
*slash = '\0';
|
||||
if (!trainlog_test_join_path(path, sizeof(path), parent, name)) {
|
||||
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
snprintf(slash + 1, (size_t)(path + sizeof(path) - (slash + 1)), "%s", name);
|
||||
return rename(n->path, path) == 0 ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||
}
|
||||
static const TrainlogGenerationMtpIo io = {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include "trainlog/generation_mtp.h"
|
||||
#include "bounded_path.h"
|
||||
|
||||
#define CHECK(x) \
|
||||
do { \
|
||||
|
|
@ -240,22 +239,21 @@ static int test_pull_and_manifest_last(void) {
|
|||
CHECK(trainlog_generation_mtp_pull(
|
||||
&io, "peer_11111111-1111-4111-8111-111111111111", root, diag, sizeof(diag)) ==
|
||||
TRAINLOG_STATUS_OK);
|
||||
CHECK(trainlog_test_join_path(
|
||||
path,
|
||||
sizeof(path),
|
||||
root,
|
||||
"android-objects/generations/gen_11111111-1111-4111-8111-111111111111/payload.json"));
|
||||
snprintf(path,
|
||||
sizeof(path),
|
||||
"%s/android-objects/generations/gen_11111111-1111-4111-8111-111111111111/payload.json",
|
||||
root);
|
||||
CHECK(access(path, R_OK) == 0);
|
||||
CHECK(trainlog_test_join_path(path, sizeof(path), root, "out"));
|
||||
snprintf(path, sizeof(path), "%s/out", root);
|
||||
CHECK(mkdir(path, 0700) == 0);
|
||||
char gen[512];
|
||||
CHECK(trainlog_test_join_path(gen, sizeof(gen), path, "generation"));
|
||||
snprintf(gen, sizeof(gen), "%s/generation", path);
|
||||
CHECK(mkdir(gen, 0700) == 0);
|
||||
char artifact[512];
|
||||
CHECK(trainlog_test_join_path(artifact, sizeof(artifact), gen, "artifact.json"));
|
||||
snprintf(artifact, sizeof(artifact), "%s/artifact.json", gen);
|
||||
CHECK(write_file(artifact, "a") == 0);
|
||||
char marker[512];
|
||||
CHECK(trainlog_test_join_path(marker, sizeof(marker), gen, "manifest.json"));
|
||||
snprintf(marker, sizeof(marker), "%s/manifest.json", gen);
|
||||
CHECK(write_file(marker, "m") == 0);
|
||||
CHECK(trainlog_generation_mtp_push(
|
||||
&io, "peer_11111111-1111-4111-8111-111111111111", root, diag, sizeof(diag)) ==
|
||||
|
|
@ -292,36 +290,11 @@ static int test_ambiguity_and_truncation(void) {
|
|||
status = trainlog_generation_mtp_pull(
|
||||
&io, "peer_11111111-1111-4111-8111-111111111111", root, diag, sizeof(diag));
|
||||
CHECK(status != TRAINLOG_STATUS_OK);
|
||||
CHECK(trainlog_test_join_path(path, sizeof(path), root, "bad.bin"));
|
||||
snprintf(path, sizeof(path), "%s/bad.bin", root);
|
||||
CHECK(access(path, F_OK) != 0);
|
||||
return 0;
|
||||
}
|
||||
static int test_bounded_path_join(void) {
|
||||
char exact[8];
|
||||
char too_small[7] = "guard";
|
||||
char multibyte[16];
|
||||
char private_root[] = "/tmp/trainlog-path-bound-XXXXXX";
|
||||
char sentinel[128];
|
||||
char refused[8] = "guard";
|
||||
|
||||
CHECK(trainlog_test_join_path(exact, sizeof(exact), "abc", "def"));
|
||||
CHECK(strcmp(exact, "abc/def") == 0);
|
||||
CHECK(!trainlog_test_join_path(too_small, sizeof(too_small), "abc", "def"));
|
||||
CHECK(too_small[0] == '\0');
|
||||
CHECK(trainlog_test_join_path(multibyte, sizeof(multibyte), "rép", "é"));
|
||||
CHECK(strcmp(multibyte, "rép/é") == 0);
|
||||
CHECK(mkdtemp(private_root) != NULL);
|
||||
CHECK(trainlog_test_join_path(sentinel, sizeof(sentinel), private_root, "sentinel"));
|
||||
CHECK(write_file(sentinel, "outside") == 0);
|
||||
CHECK(!trainlog_test_join_path(refused, sizeof(refused), private_root, "overflow"));
|
||||
CHECK(refused[0] == '\0');
|
||||
CHECK(access(sentinel, R_OK) == 0);
|
||||
CHECK(unlink(sentinel) == 0);
|
||||
CHECK(rmdir(private_root) == 0);
|
||||
return 0;
|
||||
}
|
||||
int main(void) {
|
||||
CHECK(test_bounded_path_join() == 0);
|
||||
CHECK(test_pull_and_manifest_last() == 0);
|
||||
CHECK(test_ambiguity_and_truncation() == 0);
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ static bool run_test(void)
|
|||
char path[] = "/tmp/trainlog-v16-XXXXXX";
|
||||
int fd = mkstemp(path); sqlite3 *raw = NULL; TrainlogDatabase *db = NULL;
|
||||
bool ok = false;
|
||||
TrainlogPersistedExerciseDetail old[1];
|
||||
TrainlogPersistedExerciseDetail old[1] = {{0}};
|
||||
TrainlogSessionSummary summary;
|
||||
size_t count = 0U; int version = 0;
|
||||
TrainlogExercise changed;
|
||||
|
|
@ -75,7 +75,6 @@ static bool run_test(void)
|
|||
TrainlogSessionExerciseInput input = {0};
|
||||
TrainlogSessionInput session = {0};
|
||||
char diagnostic[512] = {0};
|
||||
(void)memset(old, 0, sizeof(old));
|
||||
CHECK(fd >= 0); CHECK(close(fd) == 0); fd = -1;
|
||||
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
||||
CHECK(sqlite3_exec(raw, FIXTURE, NULL, NULL, NULL) == SQLITE_OK);
|
||||
|
|
|
|||
|
|
@ -149,31 +149,6 @@ static bool response_header(const char *response, const char *name, char *output
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool test_sync_accepted_serialization(void) {
|
||||
static const char REQUEST_ID[] = "sy_11111111-1111-4111-8111-111111111111";
|
||||
static const char RUN_ID[] = "sy_22222222-2222-4222-8222-222222222222";
|
||||
char output[256];
|
||||
char exact[256];
|
||||
char too_small[256];
|
||||
size_t output_size;
|
||||
|
||||
CHECK(trainlog_web_sync_accepted_serialize(
|
||||
REQUEST_ID, RUN_ID, output, sizeof(output), &output_size));
|
||||
CHECK(output_size == strlen(output));
|
||||
CHECK(strstr(output, "\"request_id\":\"sy_11111111-1111-4111-8111-111111111111\"") != NULL);
|
||||
CHECK(strstr(output, "\"run_id\":\"sy_22222222-2222-4222-8222-222222222222\"") != NULL);
|
||||
CHECK(strstr(output, "\"result\":\"running\"") != NULL);
|
||||
CHECK(trainlog_web_sync_accepted_serialize(
|
||||
REQUEST_ID, RUN_ID, exact, output_size + 1U, &output_size));
|
||||
CHECK(strcmp(exact, output) == 0);
|
||||
CHECK(!trainlog_web_sync_accepted_serialize(
|
||||
REQUEST_ID, RUN_ID, too_small, output_size, &output_size));
|
||||
CHECK(too_small[0] == '\0');
|
||||
CHECK(!trainlog_web_sync_accepted_serialize(
|
||||
"sy_invalid", RUN_ID, output, sizeof(output), &output_size));
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool test_http_contract(TrainlogDatabase *database) {
|
||||
char response[32768];
|
||||
char large_request[12000];
|
||||
|
|
@ -501,8 +476,8 @@ int main(void) {
|
|||
sizeof(diagnostic)) != 0 &&
|
||||
strstr(diagnostic, "frontend") != NULL;
|
||||
} else {
|
||||
passed = test_sync_accepted_serialization() && test_http_contract(database) &&
|
||||
test_port_in_use(database) && test_dashboard_core_error_translation();
|
||||
passed = test_http_contract(database) && test_port_in_use(database) &&
|
||||
test_dashboard_core_error_translation();
|
||||
}
|
||||
trainlog_database_close(database);
|
||||
return passed ? 0 : 1;
|
||||
|
|
|
|||
Loading…
Reference in a new issue