fix(sync): reject truncated native artifacts
This commit is contained in:
parent
d29532a735
commit
2c5a8be282
9 changed files with 854 additions and 446 deletions
|
|
@ -5,6 +5,7 @@ import json, os, signal, socket, sqlite3, subprocess, tempfile, time, unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
BUILD = ROOT / os.environ.get("TRAINLOG_WEB_E2E_BUILD", "build") / "tui"
|
||||||
GECKO = Path(
|
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")
|
||||||
)
|
)
|
||||||
|
|
@ -114,13 +115,13 @@ class BrowserSyncTest(unittest.TestCase):
|
||||||
env.update(
|
env.update(
|
||||||
{
|
{
|
||||||
"TRAINLOG_SYNC_MTP_ADAPTER": str(
|
"TRAINLOG_SYNC_MTP_ADAPTER": str(
|
||||||
ROOT / "build/tui/generation-mtp-adapter-double"
|
BUILD / "generation-mtp-adapter-double"
|
||||||
),
|
),
|
||||||
"TRAINLOG_MTP_DOUBLE_ROOT": str(root / "device"),
|
"TRAINLOG_MTP_DOUBLE_ROOT": str(root / "device"),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
server = subprocess.Popen(
|
server = subprocess.Popen(
|
||||||
[str(ROOT / "build/tui/trainlog"), "--web", "--port", str(port)],
|
[str(BUILD / "trainlog"), "--web", "--port", str(port)],
|
||||||
env=env,
|
env=env,
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,11 @@ def run_adapter(
|
||||||
diagnostic = (
|
diagnostic = (
|
||||||
result.stderr.decode("utf-8", "replace")[:1024] or "MTP adapter failed"
|
result.stderr.decode("utf-8", "replace")[:1024] or "MTP adapter failed"
|
||||||
).strip()
|
).strip()
|
||||||
if allow_missing_peer and "expected peer was not found" in diagnostic:
|
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:
|
||||||
return False
|
return False
|
||||||
raise RuntimeError(diagnostic)
|
raise RuntimeError(diagnostic)
|
||||||
return True
|
return True
|
||||||
|
|
@ -276,4 +280,8 @@ def main() -> int:
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
raise SystemExit(main())
|
raise SystemExit(main())
|
||||||
|
except RuntimeError as error:
|
||||||
|
print(f"sync peer failed: {error}", file=sys.stderr)
|
||||||
|
raise SystemExit(1) from None
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,25 @@
|
||||||
#define TRAINLOG_WEB_SERVER_H
|
#define TRAINLOG_WEB_SERVER_H
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "trainlog/database.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
|
/* Blocking, single-threaded local HTTP adapter. Port zero is reserved for
|
||||||
* controlled test harnesses and is rejected by the CLI. The database remains
|
* controlled test harnesses and is rejected by the CLI. The database remains
|
||||||
* borrowed and process-owned; this infrastructure route never reads it. */
|
* borrowed and process-owned; this infrastructure route never reads it. */
|
||||||
int trainlog_web_server_run(TrainlogDatabase *database, const char *database_path,
|
int trainlog_web_server_run(TrainlogDatabase *database,
|
||||||
|
const char *database_path,
|
||||||
uint16_t port,
|
uint16_t port,
|
||||||
const volatile sig_atomic_t *stop_requested,
|
const volatile sig_atomic_t *stop_requested,
|
||||||
char *diagnostic, size_t diagnostic_capacity);
|
char *diagnostic,
|
||||||
|
size_t diagnostic_capacity);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
1029
tui/src/web_server.c
1029
tui/src/web_server.c
File diff suppressed because it is too large
Load diff
33
tui/tests/bounded_path.h
Normal file
33
tui/tests/bounded_path.h
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#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,10 +1,12 @@
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "trainlog/generation_mtp.h"
|
#include "trainlog/generation_mtp.h"
|
||||||
|
#include "bounded_path.h"
|
||||||
|
|
||||||
typedef struct Node {
|
typedef struct Node {
|
||||||
uint32_t id, parent;
|
uint32_t id, parent;
|
||||||
|
|
@ -15,13 +17,41 @@ typedef struct Node {
|
||||||
static Node nodes[2048];
|
static Node nodes[2048];
|
||||||
static size_t node_count;
|
static size_t node_count;
|
||||||
static const char *remote;
|
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) {
|
static uint32_t scan(const char *path, uint32_t parent) {
|
||||||
DIR *d;
|
DIR *d;
|
||||||
struct dirent *e;
|
struct dirent *e;
|
||||||
uint32_t self = (uint32_t)(node_count + 1U);
|
uint32_t self;
|
||||||
Node *n = &nodes[node_count++];
|
Node *n;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
lstat(path, &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++];
|
||||||
n->id = self;
|
n->id = self;
|
||||||
n->parent = parent;
|
n->parent = parent;
|
||||||
n->folder = S_ISDIR(st.st_mode);
|
n->folder = S_ISDIR(st.st_mode);
|
||||||
|
|
@ -33,17 +63,23 @@ static uint32_t scan(const char *path, uint32_t parent) {
|
||||||
while ((e = readdir(d))) {
|
while ((e = readdir(d))) {
|
||||||
if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) {
|
if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) {
|
||||||
char child[1024];
|
char child[1024];
|
||||||
snprintf(child, sizeof(child), "%s/%s", path, e->d_name);
|
if (!trainlog_test_join_path(child, sizeof(child), path, e->d_name)) {
|
||||||
|
scan_failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
scan(child, self);
|
scan(child, self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
closedir(d);
|
closedir(d);
|
||||||
|
} else if (n->folder && errno != ENOENT) {
|
||||||
|
scan_failed = true;
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
static void rebuild(void) {
|
static bool rebuild(void) {
|
||||||
node_count = 0U;
|
node_count = 0U;
|
||||||
scan(remote, UINT32_MAX);
|
scan_failed = false;
|
||||||
|
return scan(remote, UINT32_MAX) != 0U && !scan_failed;
|
||||||
}
|
}
|
||||||
static Node *node(uint32_t id) {
|
static Node *node(uint32_t id) {
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
@ -87,7 +123,9 @@ static TrainlogStatus children(unsigned int b,
|
||||||
(void)b;
|
(void)b;
|
||||||
(void)d;
|
(void)d;
|
||||||
(void)s;
|
(void)s;
|
||||||
rebuild();
|
if (!rebuild()) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
if (p == UINT32_MAX) {
|
if (p == UINT32_MAX) {
|
||||||
p = nodes[0].id;
|
p = nodes[0].id;
|
||||||
}
|
}
|
||||||
|
|
@ -121,12 +159,16 @@ static TrainlogStatus ensure(unsigned int b,
|
||||||
(void)b;
|
(void)b;
|
||||||
(void)d;
|
(void)d;
|
||||||
(void)s;
|
(void)s;
|
||||||
rebuild();
|
if (!rebuild()) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
parent = node(p);
|
parent = node(p);
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
return TRAINLOG_STATUS_NOT_FOUND;
|
return TRAINLOG_STATUS_NOT_FOUND;
|
||||||
}
|
}
|
||||||
snprintf(path, sizeof(path), "%s/%s", parent->path, name);
|
if (!trainlog_test_join_path(path, sizeof(path), parent->path, name)) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
if (mkdir(path, 0700) == 0) {
|
if (mkdir(path, 0700) == 0) {
|
||||||
*created = true;
|
*created = true;
|
||||||
} else if (access(path, F_OK) == 0) {
|
} else if (access(path, F_OK) == 0) {
|
||||||
|
|
@ -134,7 +176,9 @@ static TrainlogStatus ensure(unsigned int b,
|
||||||
} else {
|
} else {
|
||||||
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
}
|
}
|
||||||
rebuild();
|
if (!rebuild()) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i < node_count; i++) {
|
for (i = 0; i < node_count; i++) {
|
||||||
if (strcmp(nodes[i].path, path) == 0) {
|
if (strcmp(nodes[i].path, path) == 0) {
|
||||||
|
|
@ -162,7 +206,9 @@ static TrainlogStatus receive(unsigned int b, unsigned int d, uint32_t id, const
|
||||||
Node *n;
|
Node *n;
|
||||||
(void)b;
|
(void)b;
|
||||||
(void)d;
|
(void)d;
|
||||||
rebuild();
|
if (!rebuild()) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
n = node(id);
|
n = node(id);
|
||||||
return n && copy(n->path, path) == 0 ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_SYSTEM_ERROR;
|
return n && copy(n->path, path) == 0 ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
}
|
}
|
||||||
|
|
@ -178,16 +224,22 @@ static TrainlogStatus send_file(unsigned int b,
|
||||||
(void)b;
|
(void)b;
|
||||||
(void)d;
|
(void)d;
|
||||||
(void)s;
|
(void)s;
|
||||||
rebuild();
|
if (!rebuild()) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
parent = node(p);
|
parent = node(p);
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
return TRAINLOG_STATUS_NOT_FOUND;
|
return TRAINLOG_STATUS_NOT_FOUND;
|
||||||
}
|
}
|
||||||
snprintf(path, sizeof(path), "%s/%s", parent->path, name);
|
if (!trainlog_test_join_path(path, sizeof(path), parent->path, name)) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
if (copy(local, path) != 0) {
|
if (copy(local, path) != 0) {
|
||||||
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
}
|
}
|
||||||
rebuild();
|
if (!rebuild()) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i < node_count; i++) {
|
for (i = 0; i < node_count; i++) {
|
||||||
if (strcmp(nodes[i].path, path) == 0) {
|
if (strcmp(nodes[i].path, path) == 0) {
|
||||||
|
|
@ -201,26 +253,38 @@ static TrainlogStatus remove_object(unsigned int b, unsigned int d, uint32_t id)
|
||||||
Node *n;
|
Node *n;
|
||||||
(void)b;
|
(void)b;
|
||||||
(void)d;
|
(void)d;
|
||||||
rebuild();
|
if (!rebuild()) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
n = node(id);
|
n = node(id);
|
||||||
return n && unlink(n->path) == 0 ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_SYSTEM_ERROR;
|
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) {
|
static TrainlogStatus rename_object(unsigned int b, unsigned int d, uint32_t id, const char *name) {
|
||||||
Node *n;
|
Node *n;
|
||||||
char path[1024], *slash;
|
char parent[1024];
|
||||||
|
char path[1024];
|
||||||
|
char *slash;
|
||||||
(void)b;
|
(void)b;
|
||||||
(void)d;
|
(void)d;
|
||||||
rebuild();
|
if (!rebuild()) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
n = node(id);
|
n = node(id);
|
||||||
if (!n) {
|
if (!n) {
|
||||||
return TRAINLOG_STATUS_NOT_FOUND;
|
return TRAINLOG_STATUS_NOT_FOUND;
|
||||||
}
|
}
|
||||||
snprintf(path, sizeof(path), "%s", n->path);
|
if (strlen(n->path) >= sizeof(parent)) {
|
||||||
slash = strrchr(path, '/');
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
(void)memcpy(parent, n->path, strlen(n->path) + 1U);
|
||||||
|
slash = strrchr(parent, '/');
|
||||||
if (!slash) {
|
if (!slash) {
|
||||||
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
return TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
}
|
}
|
||||||
snprintf(slash + 1, (size_t)(path + sizeof(path) - (slash + 1)), "%s", name);
|
*slash = '\0';
|
||||||
|
if (!trainlog_test_join_path(path, sizeof(path), parent, name)) {
|
||||||
|
return TRAINLOG_STATUS_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
return rename(n->path, path) == 0 ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_SYSTEM_ERROR;
|
return rename(n->path, path) == 0 ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_SYSTEM_ERROR;
|
||||||
}
|
}
|
||||||
static const TrainlogGenerationMtpIo io = {
|
static const TrainlogGenerationMtpIo io = {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "trainlog/generation_mtp.h"
|
#include "trainlog/generation_mtp.h"
|
||||||
|
#include "bounded_path.h"
|
||||||
|
|
||||||
#define CHECK(x) \
|
#define CHECK(x) \
|
||||||
do { \
|
do { \
|
||||||
|
|
@ -239,21 +240,22 @@ static int test_pull_and_manifest_last(void) {
|
||||||
CHECK(trainlog_generation_mtp_pull(
|
CHECK(trainlog_generation_mtp_pull(
|
||||||
&io, "peer_11111111-1111-4111-8111-111111111111", root, diag, sizeof(diag)) ==
|
&io, "peer_11111111-1111-4111-8111-111111111111", root, diag, sizeof(diag)) ==
|
||||||
TRAINLOG_STATUS_OK);
|
TRAINLOG_STATUS_OK);
|
||||||
snprintf(path,
|
CHECK(trainlog_test_join_path(
|
||||||
|
path,
|
||||||
sizeof(path),
|
sizeof(path),
|
||||||
"%s/android-objects/generations/gen_11111111-1111-4111-8111-111111111111/payload.json",
|
root,
|
||||||
root);
|
"android-objects/generations/gen_11111111-1111-4111-8111-111111111111/payload.json"));
|
||||||
CHECK(access(path, R_OK) == 0);
|
CHECK(access(path, R_OK) == 0);
|
||||||
snprintf(path, sizeof(path), "%s/out", root);
|
CHECK(trainlog_test_join_path(path, sizeof(path), root, "out"));
|
||||||
CHECK(mkdir(path, 0700) == 0);
|
CHECK(mkdir(path, 0700) == 0);
|
||||||
char gen[512];
|
char gen[512];
|
||||||
snprintf(gen, sizeof(gen), "%s/generation", path);
|
CHECK(trainlog_test_join_path(gen, sizeof(gen), path, "generation"));
|
||||||
CHECK(mkdir(gen, 0700) == 0);
|
CHECK(mkdir(gen, 0700) == 0);
|
||||||
char artifact[512];
|
char artifact[512];
|
||||||
snprintf(artifact, sizeof(artifact), "%s/artifact.json", gen);
|
CHECK(trainlog_test_join_path(artifact, sizeof(artifact), gen, "artifact.json"));
|
||||||
CHECK(write_file(artifact, "a") == 0);
|
CHECK(write_file(artifact, "a") == 0);
|
||||||
char marker[512];
|
char marker[512];
|
||||||
snprintf(marker, sizeof(marker), "%s/manifest.json", gen);
|
CHECK(trainlog_test_join_path(marker, sizeof(marker), gen, "manifest.json"));
|
||||||
CHECK(write_file(marker, "m") == 0);
|
CHECK(write_file(marker, "m") == 0);
|
||||||
CHECK(trainlog_generation_mtp_push(
|
CHECK(trainlog_generation_mtp_push(
|
||||||
&io, "peer_11111111-1111-4111-8111-111111111111", root, diag, sizeof(diag)) ==
|
&io, "peer_11111111-1111-4111-8111-111111111111", root, diag, sizeof(diag)) ==
|
||||||
|
|
@ -290,11 +292,36 @@ static int test_ambiguity_and_truncation(void) {
|
||||||
status = trainlog_generation_mtp_pull(
|
status = trainlog_generation_mtp_pull(
|
||||||
&io, "peer_11111111-1111-4111-8111-111111111111", root, diag, sizeof(diag));
|
&io, "peer_11111111-1111-4111-8111-111111111111", root, diag, sizeof(diag));
|
||||||
CHECK(status != TRAINLOG_STATUS_OK);
|
CHECK(status != TRAINLOG_STATUS_OK);
|
||||||
snprintf(path, sizeof(path), "%s/bad.bin", root);
|
CHECK(trainlog_test_join_path(path, sizeof(path), root, "bad.bin"));
|
||||||
CHECK(access(path, F_OK) != 0);
|
CHECK(access(path, F_OK) != 0);
|
||||||
return 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) {
|
int main(void) {
|
||||||
|
CHECK(test_bounded_path_join() == 0);
|
||||||
CHECK(test_pull_and_manifest_last() == 0);
|
CHECK(test_pull_and_manifest_last() == 0);
|
||||||
CHECK(test_ambiguity_and_truncation() == 0);
|
CHECK(test_ambiguity_and_truncation() == 0);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ static bool run_test(void)
|
||||||
char path[] = "/tmp/trainlog-v16-XXXXXX";
|
char path[] = "/tmp/trainlog-v16-XXXXXX";
|
||||||
int fd = mkstemp(path); sqlite3 *raw = NULL; TrainlogDatabase *db = NULL;
|
int fd = mkstemp(path); sqlite3 *raw = NULL; TrainlogDatabase *db = NULL;
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
TrainlogPersistedExerciseDetail old[1] = {{0}};
|
TrainlogPersistedExerciseDetail old[1];
|
||||||
TrainlogSessionSummary summary;
|
TrainlogSessionSummary summary;
|
||||||
size_t count = 0U; int version = 0;
|
size_t count = 0U; int version = 0;
|
||||||
TrainlogExercise changed;
|
TrainlogExercise changed;
|
||||||
|
|
@ -75,6 +75,7 @@ static bool run_test(void)
|
||||||
TrainlogSessionExerciseInput input = {0};
|
TrainlogSessionExerciseInput input = {0};
|
||||||
TrainlogSessionInput session = {0};
|
TrainlogSessionInput session = {0};
|
||||||
char diagnostic[512] = {0};
|
char diagnostic[512] = {0};
|
||||||
|
(void)memset(old, 0, sizeof(old));
|
||||||
CHECK(fd >= 0); CHECK(close(fd) == 0); fd = -1;
|
CHECK(fd >= 0); CHECK(close(fd) == 0); fd = -1;
|
||||||
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
||||||
CHECK(sqlite3_exec(raw, FIXTURE, NULL, NULL, NULL) == SQLITE_OK);
|
CHECK(sqlite3_exec(raw, FIXTURE, NULL, NULL, NULL) == SQLITE_OK);
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,31 @@ static bool response_header(const char *response, const char *name, char *output
|
||||||
return true;
|
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) {
|
static bool test_http_contract(TrainlogDatabase *database) {
|
||||||
char response[32768];
|
char response[32768];
|
||||||
char large_request[12000];
|
char large_request[12000];
|
||||||
|
|
@ -476,8 +501,8 @@ int main(void) {
|
||||||
sizeof(diagnostic)) != 0 &&
|
sizeof(diagnostic)) != 0 &&
|
||||||
strstr(diagnostic, "frontend") != NULL;
|
strstr(diagnostic, "frontend") != NULL;
|
||||||
} else {
|
} else {
|
||||||
passed = test_http_contract(database) && test_port_in_use(database) &&
|
passed = test_sync_accepted_serialization() && test_http_contract(database) &&
|
||||||
test_dashboard_core_error_translation();
|
test_port_in_use(database) && test_dashboard_core_error_translation();
|
||||||
}
|
}
|
||||||
trainlog_database_close(database);
|
trainlog_database_close(database);
|
||||||
return passed ? 0 : 1;
|
return passed ? 0 : 1;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue