refactor(c): clarify control flow and MTP callbacks

This commit is contained in:
fy59 2026-09-17 14:46:23 +02:00
parent 7b769076c1
commit 7d29dc83a2
9 changed files with 484 additions and 246 deletions

View file

@ -40,15 +40,18 @@ static const TrainlogDashboardTileLayout defaults[TRAINLOG_DASHBOARD_LAYOUT_TILE
static int contract_index(const char *id) { static int contract_index(const char *id) {
size_t index; size_t index;
for (index = 0U; index < TRAINLOG_DASHBOARD_LAYOUT_TILE_COUNT; ++index) for (index = 0U; index < TRAINLOG_DASHBOARD_LAYOUT_TILE_COUNT; ++index) {
if (strcmp(id, contracts[index].id) == 0) if (strcmp(id, contracts[index].id) == 0) {
return (int)index; return (int)index;
}
}
return -1; return -1;
} }
void trainlog_dashboard_layout_default(TrainlogDashboardLayout *layout) { void trainlog_dashboard_layout_default(TrainlogDashboardLayout *layout) {
if (layout == NULL) if (layout == NULL) {
return; return;
}
layout->revision = 0U; layout->revision = 0U;
(void)memcpy(layout->tiles, defaults, sizeof(defaults)); (void)memcpy(layout->tiles, defaults, sizeof(defaults));
} }
@ -61,26 +64,31 @@ static bool overlap(const TrainlogDashboardTileLayout *a, const TrainlogDashboar
bool trainlog_dashboard_layout_validate(const TrainlogDashboardLayout *layout) { bool trainlog_dashboard_layout_validate(const TrainlogDashboardLayout *layout) {
bool seen[TRAINLOG_DASHBOARD_LAYOUT_TILE_COUNT] = {false}; bool seen[TRAINLOG_DASHBOARD_LAYOUT_TILE_COUNT] = {false};
size_t index; size_t index;
if (layout == NULL || layout->revision > TRAINLOG_DASHBOARD_LAYOUT_MAX_REVISION) if (layout == NULL || layout->revision > TRAINLOG_DASHBOARD_LAYOUT_MAX_REVISION) {
return false; return false;
}
for (index = 0U; index < TRAINLOG_DASHBOARD_LAYOUT_TILE_COUNT; ++index) { for (index = 0U; index < TRAINLOG_DASHBOARD_LAYOUT_TILE_COUNT; ++index) {
const TrainlogDashboardTileLayout *tile = &layout->tiles[index]; const TrainlogDashboardTileLayout *tile = &layout->tiles[index];
int found = contract_index(tile->id); int found = contract_index(tile->id);
const TileContract *contract; const TileContract *contract;
size_t other; size_t other;
if (found < 0 || seen[(size_t)found]) if (found < 0 || seen[(size_t)found]) {
return false; return false;
}
seen[(size_t)found] = true; seen[(size_t)found] = true;
contract = &contracts[(size_t)found]; contract = &contracts[(size_t)found];
if (tile->y > TRAINLOG_DASHBOARD_LAYOUT_MAX_Y || tile->width < contract->min_width || if (tile->y > TRAINLOG_DASHBOARD_LAYOUT_MAX_Y || tile->width < contract->min_width ||
tile->width > contract->max_width || tile->height < contract->min_height || tile->width > contract->max_width || tile->height < contract->min_height ||
tile->height > contract->max_height || tile->height > contract->max_height ||
tile->x + tile->width > TRAINLOG_DASHBOARD_LAYOUT_COLUMNS) tile->x + tile->width > TRAINLOG_DASHBOARD_LAYOUT_COLUMNS) {
return false; return false;
for (other = index + 1U; other < TRAINLOG_DASHBOARD_LAYOUT_TILE_COUNT; ++other) }
if (overlap(tile, &layout->tiles[other])) for (other = index + 1U; other < TRAINLOG_DASHBOARD_LAYOUT_TILE_COUNT; ++other) {
if (overlap(tile, &layout->tiles[other])) {
return false; return false;
} }
}
}
return true; return true;
} }
@ -90,11 +98,13 @@ static bool exact_object(const yyjson_val *value, size_t expected) {
static bool read_u16(yyjson_val *value, uint16_t *output) { static bool read_u16(yyjson_val *value, uint16_t *output) {
uint64_t number; uint64_t number;
if (!yyjson_is_uint(value)) if (!yyjson_is_uint(value)) {
return false; return false;
}
number = yyjson_get_uint(value); number = yyjson_get_uint(value);
if (number > UINT16_MAX) if (number > UINT16_MAX) {
return false; return false;
}
*output = (uint16_t)number; *output = (uint16_t)number;
return true; return true;
} }
@ -111,11 +121,13 @@ trainlog_dashboard_layout_parse(const char *json, size_t size, TrainlogDashboard
yyjson_val *tiles; yyjson_val *tiles;
size_t index; size_t index;
if (json == NULL || layout == NULL || size == 0U || if (json == NULL || layout == NULL || size == 0U ||
size >= TRAINLOG_DASHBOARD_LAYOUT_JSON_CAPACITY) size >= TRAINLOG_DASHBOARD_LAYOUT_JSON_CAPACITY) {
return TRAINLOG_DASHBOARD_LAYOUT_INVALID; return TRAINLOG_DASHBOARD_LAYOUT_INVALID;
}
document = yyjson_read_opts((char *)json, size, YYJSON_READ_NOFLAG, NULL, &error); document = yyjson_read_opts((char *)json, size, YYJSON_READ_NOFLAG, NULL, &error);
if (document == NULL) if (document == NULL) {
return TRAINLOG_DASHBOARD_LAYOUT_INVALID; return TRAINLOG_DASHBOARD_LAYOUT_INVALID;
}
root = yyjson_doc_get_root(document); root = yyjson_doc_get_root(document);
if (!exact_object(root, 5U) || if (!exact_object(root, 5U) ||
!yyjson_equals_str(yyjson_obj_get(root, "format"), "trainlog-dashboard-layout") || !yyjson_equals_str(yyjson_obj_get(root, "format"), "trainlog-dashboard-layout") ||
@ -162,14 +174,16 @@ bool trainlog_dashboard_layout_path(char *output, size_t capacity) {
const char *base = getenv("XDG_CONFIG_HOME"); const char *base = getenv("XDG_CONFIG_HOME");
const char *home; const char *home;
int length; int length;
if (output == NULL || capacity == 0U) if (output == NULL || capacity == 0U) {
return false; return false;
if (base != NULL && base[0] != '\0') }
if (base != NULL && base[0] != '\0') {
length = snprintf(output, capacity, "%s/trainlog/web/dashboard-layout-v1.json", base); length = snprintf(output, capacity, "%s/trainlog/web/dashboard-layout-v1.json", base);
else { } else {
home = getenv("HOME"); home = getenv("HOME");
if (home == NULL || home[0] == '\0') if (home == NULL || home[0] == '\0') {
return false; return false;
}
length = length =
snprintf(output, capacity, "%s/.config/trainlog/web/dashboard-layout-v1.json", home); snprintf(output, capacity, "%s/.config/trainlog/web/dashboard-layout-v1.json", home);
} }
@ -181,32 +195,40 @@ static bool ensure_private_directories(const char *path) {
char trainlog[4096]; char trainlog[4096];
char *last; char *last;
struct stat status; struct stat status;
if (path == NULL || strlen(path) >= sizeof(web)) if (path == NULL || strlen(path) >= sizeof(web)) {
return false; return false;
}
(void)snprintf(web, sizeof(web), "%s", path); (void)snprintf(web, sizeof(web), "%s", path);
last = strrchr(web, '/'); last = strrchr(web, '/');
if (last == NULL) if (last == NULL) {
return false; return false;
}
*last = '\0'; *last = '\0';
(void)snprintf(trainlog, sizeof(trainlog), "%s", web); (void)snprintf(trainlog, sizeof(trainlog), "%s", web);
last = strrchr(trainlog, '/'); last = strrchr(trainlog, '/');
if (last == NULL) if (last == NULL) {
return false; return false;
}
*last = '\0'; *last = '\0';
/* CONTRACT: only the two Trainlog-owned directories are created/chmodded; /* CONTRACT: only the two Trainlog-owned directories are created/chmodded;
* an existing symlink or non-directory is never followed. */ * an existing symlink or non-directory is never followed. */
{ {
if (mkdir(trainlog, 0700) != 0 && errno != EEXIST) if (mkdir(trainlog, 0700) != 0 && errno != EEXIST) {
return false;
if (lstat(trainlog, &status) != 0 || !S_ISDIR(status.st_mode))
return false;
if (chmod(trainlog, 0700) != 0)
return false; return false;
} }
if (mkdir(web, 0700) != 0 && errno != EEXIST) if (lstat(trainlog, &status) != 0 || !S_ISDIR(status.st_mode)) {
return false; return false;
if (lstat(web, &status) != 0 || !S_ISDIR(status.st_mode)) }
if (chmod(trainlog, 0700) != 0) {
return false; return false;
}
}
if (mkdir(web, 0700) != 0 && errno != EEXIST) {
return false;
}
if (lstat(web, &status) != 0 || !S_ISDIR(status.st_mode)) {
return false;
}
return chmod(web, 0700) == 0; return chmod(web, 0700) == 0;
} }
@ -215,19 +237,23 @@ static bool sync_parent(const char *path) {
char *slash; char *slash;
int fd; int fd;
bool ok; bool ok;
if (strlen(path) >= sizeof(parent)) if (strlen(path) >= sizeof(parent)) {
return false; return false;
}
(void)snprintf(parent, sizeof(parent), "%s", path); (void)snprintf(parent, sizeof(parent), "%s", path);
slash = strrchr(parent, '/'); slash = strrchr(parent, '/');
if (slash == NULL) if (slash == NULL) {
return false; return false;
}
*slash = '\0'; *slash = '\0';
fd = open(parent, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW); fd = open(parent, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
if (fd < 0) if (fd < 0) {
return false; return false;
}
ok = fsync(fd) == 0; ok = fsync(fd) == 0;
if (close(fd) != 0) if (close(fd) != 0) {
ok = false; ok = false;
}
return ok; return ok;
} }
@ -244,8 +270,9 @@ bool trainlog_dashboard_layout_serialize(const TrainlogDashboardLayout *layout,
: source == TRAINLOG_DASHBOARD_LAYOUT_INVALID_PERSISTED : source == TRAINLOG_DASHBOARD_LAYOUT_INVALID_PERSISTED
? "invalid_persisted" ? "invalid_persisted"
: "default"; : "default";
if (!trainlog_dashboard_layout_validate(layout) || output == NULL || size == NULL) if (!trainlog_dashboard_layout_validate(layout) || output == NULL || size == NULL) {
return false; return false;
}
#define APPEND(...) \ #define APPEND(...) \
do { \ do { \
count = snprintf(output + used, capacity - used, __VA_ARGS__); \ count = snprintf(output + used, capacity - used, __VA_ARGS__); \
@ -256,8 +283,9 @@ bool trainlog_dashboard_layout_serialize(const TrainlogDashboardLayout *layout,
APPEND("{\"format\":\"trainlog-dashboard-layout\",\"version\":1," APPEND("{\"format\":\"trainlog-dashboard-layout\",\"version\":1,"
"\"revision\":%llu,\"columns\":12", "\"revision\":%llu,\"columns\":12",
(unsigned long long)layout->revision); (unsigned long long)layout->revision);
if (include_source) if (include_source) {
APPEND(",\"source\":\"%s\"", source_name); APPEND(",\"source\":\"%s\"", source_name);
}
APPEND(",\"tiles\":["); APPEND(",\"tiles\":[");
for (index = 0U; index < TRAINLOG_DASHBOARD_LAYOUT_TILE_COUNT; ++index) { for (index = 0U; index < TRAINLOG_DASHBOARD_LAYOUT_TILE_COUNT; ++index) {
const TrainlogDashboardTileLayout *tile = &layout->tiles[index]; const TrainlogDashboardTileLayout *tile = &layout->tiles[index];
@ -284,15 +312,17 @@ trainlog_dashboard_layout_load(TrainlogDashboardLayout *layout,
ssize_t count; ssize_t count;
int fd; int fd;
TrainlogDashboardLayoutResult result; TrainlogDashboardLayoutResult result;
if (layout == NULL || source == NULL || !trainlog_dashboard_layout_path(path, sizeof(path))) if (layout == NULL || source == NULL || !trainlog_dashboard_layout_path(path, sizeof(path))) {
return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR; return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR;
}
trainlog_dashboard_layout_default(layout); trainlog_dashboard_layout_default(layout);
*source = TRAINLOG_DASHBOARD_LAYOUT_DEFAULT; *source = TRAINLOG_DASHBOARD_LAYOUT_DEFAULT;
fd = open(path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK); fd = open(path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK);
if (fd < 0) { if (fd < 0) {
if (errno == ENOENT || errno == ELOOP) { if (errno == ENOENT || errno == ELOOP) {
if (errno == ELOOP) if (errno == ELOOP) {
*source = TRAINLOG_DASHBOARD_LAYOUT_INVALID_PERSISTED; *source = TRAINLOG_DASHBOARD_LAYOUT_INVALID_PERSISTED;
}
return TRAINLOG_DASHBOARD_LAYOUT_OK; return TRAINLOG_DASHBOARD_LAYOUT_OK;
} }
return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR; return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR;
@ -312,8 +342,9 @@ trainlog_dashboard_layout_load(TrainlogDashboardLayout *layout,
if (result != TRAINLOG_DASHBOARD_LAYOUT_OK) { if (result != TRAINLOG_DASHBOARD_LAYOUT_OK) {
trainlog_dashboard_layout_default(layout); trainlog_dashboard_layout_default(layout);
*source = TRAINLOG_DASHBOARD_LAYOUT_INVALID_PERSISTED; *source = TRAINLOG_DASHBOARD_LAYOUT_INVALID_PERSISTED;
} else } else {
*source = TRAINLOG_DASHBOARD_LAYOUT_PERSISTED; *source = TRAINLOG_DASHBOARD_LAYOUT_PERSISTED;
}
return TRAINLOG_DASHBOARD_LAYOUT_OK; return TRAINLOG_DASHBOARD_LAYOUT_OK;
} }
@ -321,10 +352,12 @@ static bool write_all(int fd, const char *data, size_t size) {
size_t used = 0U; size_t used = 0U;
while (used < size) { while (used < size) {
ssize_t count = write(fd, data + used, size - used); ssize_t count = write(fd, data + used, size - used);
if (count < 0 && errno == EINTR) if (count < 0 && errno == EINTR) {
continue; continue;
if (count <= 0) }
if (count <= 0) {
return false; return false;
}
used += (size_t)count; used += (size_t)count;
} }
return true; return true;
@ -341,26 +374,32 @@ TrainlogDashboardLayoutResult trainlog_dashboard_layout_save(const TrainlogDashb
bool ok; bool ok;
if (layout == NULL || saved == NULL || !trainlog_dashboard_layout_validate(layout) || if (layout == NULL || saved == NULL || !trainlog_dashboard_layout_validate(layout) ||
layout->revision != expected_revision || layout->revision != expected_revision ||
expected_revision >= TRAINLOG_DASHBOARD_LAYOUT_MAX_REVISION) expected_revision >= TRAINLOG_DASHBOARD_LAYOUT_MAX_REVISION) {
return TRAINLOG_DASHBOARD_LAYOUT_INVALID; return TRAINLOG_DASHBOARD_LAYOUT_INVALID;
if (trainlog_dashboard_layout_load(&current, &source) != TRAINLOG_DASHBOARD_LAYOUT_OK) }
if (trainlog_dashboard_layout_load(&current, &source) != TRAINLOG_DASHBOARD_LAYOUT_OK) {
return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR; return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR;
if (current.revision != expected_revision) }
if (current.revision != expected_revision) {
return TRAINLOG_DASHBOARD_LAYOUT_CONFLICT; return TRAINLOG_DASHBOARD_LAYOUT_CONFLICT;
}
*saved = *layout; *saved = *layout;
saved->revision = expected_revision + 1U; saved->revision = expected_revision + 1U;
if (!trainlog_dashboard_layout_path(path, sizeof(path)) || !ensure_private_directories(path) || if (!trainlog_dashboard_layout_path(path, sizeof(path)) || !ensure_private_directories(path) ||
!trainlog_dashboard_layout_serialize( !trainlog_dashboard_layout_serialize(
saved, TRAINLOG_DASHBOARD_LAYOUT_PERSISTED, false, json, sizeof(json), &size) || saved, TRAINLOG_DASHBOARD_LAYOUT_PERSISTED, false, json, sizeof(json), &size) ||
snprintf(temporary, sizeof(temporary), "%s.tmp.XXXXXX", path) >= (int)sizeof(temporary)) snprintf(temporary, sizeof(temporary), "%s.tmp.XXXXXX", path) >= (int)sizeof(temporary)) {
return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR; return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR;
}
fd = mkstemp(temporary); fd = mkstemp(temporary);
if (fd < 0) if (fd < 0) {
return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR; return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR;
}
ok = fchmod(fd, 0600) == 0 && fcntl(fd, F_SETFD, FD_CLOEXEC) == 0 && ok = fchmod(fd, 0600) == 0 && fcntl(fd, F_SETFD, FD_CLOEXEC) == 0 &&
write_all(fd, json, size) && fsync(fd) == 0; write_all(fd, json, size) && fsync(fd) == 0;
if (close(fd) != 0) if (close(fd) != 0) {
ok = false; ok = false;
}
if (!ok || rename(temporary, path) != 0) { if (!ok || rename(temporary, path) != 0) {
(void)unlink(temporary); (void)unlink(temporary);
return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR; return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR;
@ -376,12 +415,15 @@ TrainlogDashboardLayoutResult trainlog_dashboard_layout_delete(uint64_t expected
TrainlogDashboardLayout current; TrainlogDashboardLayout current;
TrainlogDashboardLayoutSource source; TrainlogDashboardLayoutSource source;
char path[4096]; char path[4096];
if (trainlog_dashboard_layout_load(&current, &source) != TRAINLOG_DASHBOARD_LAYOUT_OK) if (trainlog_dashboard_layout_load(&current, &source) != TRAINLOG_DASHBOARD_LAYOUT_OK) {
return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR; return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR;
if (current.revision != expected_revision) }
if (current.revision != expected_revision) {
return TRAINLOG_DASHBOARD_LAYOUT_CONFLICT; return TRAINLOG_DASHBOARD_LAYOUT_CONFLICT;
if (!trainlog_dashboard_layout_path(path, sizeof(path))) }
if (!trainlog_dashboard_layout_path(path, sizeof(path))) {
return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR; return TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR;
}
if (unlink(path) != 0) { if (unlink(path) != 0) {
return errno == ENOENT ? TRAINLOG_DASHBOARD_LAYOUT_OK : TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR; return errno == ENOENT ? TRAINLOG_DASHBOARD_LAYOUT_OK : TRAINLOG_DASHBOARD_LAYOUT_IO_ERROR;
} }

View file

@ -18,8 +18,9 @@ typedef struct Selection {
} Selection; } Selection;
static void diag(char *output, size_t size, const char *value) { static void diag(char *output, size_t size, const char *value) {
if (output != NULL && size > 0U) if (output != NULL && size > 0U) {
(void)snprintf(output, size, "%s", value); (void)snprintf(output, size, "%s", value);
}
} }
static bool safe_leaf(const char *name) { static bool safe_leaf(const char *name) {
@ -50,16 +51,18 @@ static TrainlogStatus one_named(const TrainlogGenerationMtpIo *io,
TrainlogMtpEntry entries[TRAINLOG_GENERATION_MTP_MAX_CHILDREN]; TrainlogMtpEntry entries[TRAINLOG_GENERATION_MTP_MAX_CHILDREN];
size_t count = 0U, index, matches = 0U; size_t count = 0U, index, matches = 0U;
TrainlogStatus status = children(io, selection, parent, entries, &count); TrainlogStatus status = children(io, selection, parent, entries, &count);
if (status != TRAINLOG_STATUS_OK) if (status != TRAINLOG_STATUS_OK) {
return status; return status;
}
for (index = 0U; index < count; ++index) { for (index = 0U; index < count; ++index) {
if (entries[index].folder == folder && strcmp(entries[index].name, name) == 0) { if (entries[index].folder == folder && strcmp(entries[index].name, name) == 0) {
*result = entries[index]; *result = entries[index];
++matches; ++matches;
} }
} }
if (matches > 1U) if (matches > 1U) {
return TRAINLOG_STATUS_CONFLICT; return TRAINLOG_STATUS_CONFLICT;
}
return matches == 1U ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_NOT_FOUND; return matches == 1U ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_NOT_FOUND;
} }
@ -71,16 +74,19 @@ static bool peer_document_matches(const char *path, const char *expected) {
yyjson_val *root, *capabilities, *item; yyjson_val *root, *capabilities, *item;
size_t index, maximum; size_t index, maximum;
bool manifest = false, acknowledgement = false, valid; bool manifest = false, acknowledgement = false, valid;
if (fd < 0) if (fd < 0) {
return false; return false;
}
count = read(fd, buffer, sizeof(buffer)); count = read(fd, buffer, sizeof(buffer));
(void)close(fd); (void)close(fd);
if (count <= 0 || (size_t)count >= sizeof(buffer)) if (count <= 0 || (size_t)count >= sizeof(buffer)) {
return false; return false;
}
buffer[count] = '\0'; buffer[count] = '\0';
document = yyjson_read(buffer, (size_t)count, YYJSON_READ_NOFLAG); document = yyjson_read(buffer, (size_t)count, YYJSON_READ_NOFLAG);
if (document == NULL) if (document == NULL) {
return false; return false;
}
root = yyjson_doc_get_root(document); root = yyjson_doc_get_root(document);
capabilities = yyjson_obj_get(root, "capabilities"); capabilities = yyjson_obj_get(root, "capabilities");
valid = yyjson_is_obj(root) && yyjson_obj_size(root) == 4U && valid = yyjson_is_obj(root) && yyjson_obj_size(root) == 4U &&
@ -95,12 +101,14 @@ static bool peer_document_matches(const char *path, const char *expected) {
valid = false; valid = false;
break; break;
} }
if (yyjson_equals_str(item, "generation-manifest-v1")) if (yyjson_equals_str(item, "generation-manifest-v1")) {
manifest = true; manifest = true;
if (yyjson_equals_str(item, "generation-ack-v1")) }
if (yyjson_equals_str(item, "generation-ack-v1")) {
acknowledgement = true; acknowledgement = true;
} }
} }
}
yyjson_doc_free(document); yyjson_doc_free(document);
return valid && manifest && acknowledgement; return valid && manifest && acknowledgement;
} }
@ -131,8 +139,9 @@ static TrainlogStatus select_peer(const TrainlogGenerationMtpIo *io,
storages, storages,
TRAINLOG_GENERATION_MTP_MAX_STORAGES, TRAINLOG_GENERATION_MTP_MAX_STORAGES,
&storage_count); &storage_count);
if (status != TRAINLOG_STATUS_OK) if (status != TRAINLOG_STATUS_OK) {
continue; continue;
}
for (si = 0U; si < storage_count; ++si) { for (si = 0U; si < storage_count; ++si) {
Selection candidate = { Selection candidate = {
devices[di].bus_number, devices[di].device_number, storages[si].storage_id, 0U}; devices[di].bus_number, devices[di].device_number, storages[si].storage_id, 0U};
@ -140,18 +149,21 @@ static TrainlogStatus select_peer(const TrainlogGenerationMtpIo *io,
if (one_named(io, &candidate, UINT32_MAX, "Documents", true, &documents) != if (one_named(io, &candidate, UINT32_MAX, "Documents", true, &documents) !=
TRAINLOG_STATUS_OK || TRAINLOG_STATUS_OK ||
one_named(io, &candidate, documents.item_id, "Trainlog", true, &root) != one_named(io, &candidate, documents.item_id, "Trainlog", true, &root) !=
TRAINLOG_STATUS_OK) TRAINLOG_STATUS_OK) {
continue; continue;
}
candidate.root = root.item_id; candidate.root = root.item_id;
if (one_named(io, &candidate, candidate.root, "android-peer-v1.json", false, &peer) != if (one_named(io, &candidate, candidate.root, "android-peer-v1.json", false, &peer) !=
TRAINLOG_STATUS_OK) TRAINLOG_STATUS_OK) {
continue; continue;
}
(void)unlink(peer_path); (void)unlink(peer_path);
if (peer.size_bytes > 16384U || if (peer.size_bytes > 16384U ||
io->receive(candidate.bus, candidate.device, peer.item_id, peer_path) != io->receive(candidate.bus, candidate.device, peer.item_id, peer_path) !=
TRAINLOG_STATUS_OK || TRAINLOG_STATUS_OK ||
!peer_document_matches(peer_path, expected)) !peer_document_matches(peer_path, expected)) {
continue; continue;
}
*output = candidate; *output = candidate;
++matches; ++matches;
} }
@ -169,8 +181,9 @@ static TrainlogStatus select_peer(const TrainlogGenerationMtpIo *io,
} }
static TrainlogStatus mkdir_private(const char *path) { static TrainlogStatus mkdir_private(const char *path) {
if (mkdir(path, 0700) == 0 || errno == EEXIST) if (mkdir(path, 0700) == 0 || errno == EEXIST) {
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
}
return TRAINLOG_STATUS_SYSTEM_ERROR; return TRAINLOG_STATUS_SYSTEM_ERROR;
} }
@ -184,70 +197,89 @@ static TrainlogStatus pull_folder(const TrainlogGenerationMtpIo *io,
TrainlogMtpEntry entries[TRAINLOG_GENERATION_MTP_MAX_CHILDREN]; TrainlogMtpEntry entries[TRAINLOG_GENERATION_MTP_MAX_CHILDREN];
size_t count = 0U, index; size_t count = 0U, index;
TrainlogStatus status; TrainlogStatus status;
if (depth > TRAINLOG_GENERATION_MTP_MAX_DEPTH) if (depth > TRAINLOG_GENERATION_MTP_MAX_DEPTH) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
status = mkdir_private(local); status = mkdir_private(local);
if (status != TRAINLOG_STATUS_OK) if (status != TRAINLOG_STATUS_OK) {
return status; return status;
}
status = children(io, selection, parent, entries, &count); status = children(io, selection, parent, entries, &count);
if (status != TRAINLOG_STATUS_OK) if (status != TRAINLOG_STATUS_OK) {
return status; return status;
if (*objects > TRAINLOG_GENERATION_MTP_MAX_CHILDREN - count) }
if (*objects > TRAINLOG_GENERATION_MTP_MAX_CHILDREN - count) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
*objects += count; *objects += count;
for (index = 0U; index < count; ++index) { for (index = 0U; index < count; ++index) {
char path[1024], temporary[1060]; char path[1024], temporary[1060];
int written; int written;
written = snprintf(path, sizeof(path), "%s/%s", local, entries[index].name); written = snprintf(path, sizeof(path), "%s/%s", local, entries[index].name);
if (!safe_leaf(entries[index].name) || written < 0 || (size_t)written >= sizeof(path)) if (!safe_leaf(entries[index].name) || written < 0 || (size_t)written >= sizeof(path)) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
if (entries[index].folder) { if (entries[index].folder) {
status = pull_folder( status = pull_folder(
io, selection, entries[index].item_id, path, depth + 1U, total, objects); io, selection, entries[index].item_id, path, depth + 1U, total, objects);
} else { } else {
struct stat value; struct stat value;
if (entries[index].size_bytes > TRAINLOG_GENERATION_MTP_MAX_TOTAL_BYTES || if (entries[index].size_bytes > TRAINLOG_GENERATION_MTP_MAX_TOTAL_BYTES ||
*total > TRAINLOG_GENERATION_MTP_MAX_TOTAL_BYTES - entries[index].size_bytes) *total > TRAINLOG_GENERATION_MTP_MAX_TOTAL_BYTES - entries[index].size_bytes) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
*total += entries[index].size_bytes; *total += entries[index].size_bytes;
written = snprintf(temporary, sizeof(temporary), "%s.part.%ld", path, (long)getpid()); written = snprintf(temporary, sizeof(temporary), "%s.part.%ld", path, (long)getpid());
if (written < 0 || (size_t)written >= sizeof(temporary)) if (written < 0 || (size_t)written >= sizeof(temporary)) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
(void)unlink(temporary); (void)unlink(temporary);
status = status =
io->receive(selection->bus, selection->device, entries[index].item_id, temporary); io->receive(selection->bus, selection->device, entries[index].item_id, temporary);
if (status == TRAINLOG_STATUS_OK && if (status == TRAINLOG_STATUS_OK &&
(stat(temporary, &value) != 0 || value.st_size < 0 || (stat(temporary, &value) != 0 || value.st_size < 0 ||
(uint64_t)value.st_size != entries[index].size_bytes || (uint64_t)value.st_size != entries[index].size_bytes ||
rename(temporary, path) != 0)) rename(temporary, path) != 0)) {
status = TRAINLOG_STATUS_SYSTEM_ERROR; status = TRAINLOG_STATUS_SYSTEM_ERROR;
if (status != TRAINLOG_STATUS_OK) }
if (status != TRAINLOG_STATUS_OK) {
(void)unlink(temporary); (void)unlink(temporary);
} }
if (status != TRAINLOG_STATUS_OK) }
if (status != TRAINLOG_STATUS_OK) {
return status; return status;
} }
}
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
} }
static bool same_files(const char *left, const char *right) { static bool same_files(const char *left, const char *right) {
int a = open(left, O_RDONLY | O_CLOEXEC), b = open(right, O_RDONLY | O_CLOEXEC); int left_fd = open(left, O_RDONLY | O_CLOEXEC);
char ab[8192], bb[8192]; int right_fd = open(right, O_RDONLY | O_CLOEXEC);
ssize_t ac = 0, bc; char left_buffer[8192];
char right_buffer[8192];
ssize_t left_count = 0;
ssize_t right_count;
bool same = true; bool same = true;
if (a < 0 || b < 0) if (left_fd < 0 || right_fd < 0) {
same = false;
while (same && (ac = read(a, ab, sizeof(ab))) > 0) {
bc = read(b, bb, (size_t)ac);
if (bc != ac || memcmp(ab, bb, (size_t)ac) != 0)
same = false; same = false;
} }
if (same && (ac < 0 || read(b, bb, 1U) != 0)) while (same && (left_count = read(left_fd, left_buffer, sizeof(left_buffer))) > 0) {
right_count = read(right_fd, right_buffer, (size_t)left_count);
if (right_count != left_count ||
memcmp(left_buffer, right_buffer, (size_t)left_count) != 0) {
same = false; same = false;
if (a >= 0) }
(void)close(a); }
if (b >= 0) if (same && (left_count < 0 || read(right_fd, right_buffer, 1U) != 0)) {
(void)close(b); same = false;
}
if (left_fd >= 0) {
(void)close(left_fd);
}
if (right_fd >= 0) {
(void)close(right_fd);
}
return same; return same;
} }
@ -265,8 +297,9 @@ static TrainlogStatus push_file(const TrainlogGenerationMtpIo *io,
(void)snprintf(comparison, sizeof(comparison), "%s.compare.%ld", local, (long)getpid()); (void)snprintf(comparison, sizeof(comparison), "%s.compare.%ld", local, (long)getpid());
(void)unlink(comparison); (void)unlink(comparison);
status = io->receive(selection->bus, selection->device, existing.item_id, comparison); status = io->receive(selection->bus, selection->device, existing.item_id, comparison);
if (status != TRAINLOG_STATUS_OK) if (status != TRAINLOG_STATUS_OK) {
return status; return status;
}
if (same_files(local, comparison)) { if (same_files(local, comparison)) {
(void)unlink(comparison); (void)unlink(comparison);
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
@ -281,8 +314,9 @@ static TrainlogStatus push_file(const TrainlogGenerationMtpIo *io,
local, local,
temporary_name, temporary_name,
&uploaded); &uploaded);
if (status != TRAINLOG_STATUS_OK) if (status != TRAINLOG_STATUS_OK) {
return status; return status;
}
(void)snprintf(comparison, sizeof(comparison), "%s.verify.%ld", local, (long)getpid()); (void)snprintf(comparison, sizeof(comparison), "%s.verify.%ld", local, (long)getpid());
(void)unlink(comparison); (void)unlink(comparison);
status = io->receive(selection->bus, selection->device, uploaded, comparison); status = io->receive(selection->bus, selection->device, uploaded, comparison);
@ -293,16 +327,19 @@ static TrainlogStatus push_file(const TrainlogGenerationMtpIo *io,
} }
(void)unlink(comparison); (void)unlink(comparison);
status = io->remove(selection->bus, selection->device, existing.item_id); status = io->remove(selection->bus, selection->device, existing.item_id);
if (status == TRAINLOG_STATUS_OK) if (status == TRAINLOG_STATUS_OK) {
status = io->rename(selection->bus, selection->device, uploaded, name); status = io->rename(selection->bus, selection->device, uploaded, name);
}
return status; return status;
} }
if (status != TRAINLOG_STATUS_NOT_FOUND) if (status != TRAINLOG_STATUS_NOT_FOUND) {
return status; return status;
}
status = io->send( status = io->send(
selection->bus, selection->device, selection->storage, parent, local, name, &uploaded); selection->bus, selection->device, selection->storage, parent, local, name, &uploaded);
if (status != TRAINLOG_STATUS_OK) if (status != TRAINLOG_STATUS_OK) {
return status; return status;
}
(void)snprintf(comparison, sizeof(comparison), "%s.verify.%ld", local, (long)getpid()); (void)snprintf(comparison, sizeof(comparison), "%s.verify.%ld", local, (long)getpid());
(void)unlink(comparison); (void)unlink(comparison);
status = io->receive(selection->bus, selection->device, uploaded, comparison); status = io->receive(selection->bus, selection->device, uploaded, comparison);
@ -324,17 +361,20 @@ static TrainlogStatus push_folder(const TrainlogGenerationMtpIo *io,
DIR *directory; DIR *directory;
struct dirent *entry; struct dirent *entry;
TrainlogStatus status = TRAINLOG_STATUS_OK; TrainlogStatus status = TRAINLOG_STATUS_OK;
if (depth > TRAINLOG_GENERATION_MTP_MAX_DEPTH || (directory = opendir(local)) == NULL) if (depth > TRAINLOG_GENERATION_MTP_MAX_DEPTH || (directory = opendir(local)) == NULL) {
return TRAINLOG_STATUS_SYSTEM_ERROR; return TRAINLOG_STATUS_SYSTEM_ERROR;
}
while ((entry = readdir(directory)) != NULL && status == TRAINLOG_STATUS_OK) { while ((entry = readdir(directory)) != NULL && status == TRAINLOG_STATUS_OK) {
char path[1024]; char path[1024];
struct stat value; struct stat value;
uint32_t child; uint32_t child;
bool created; bool created;
if (!safe_leaf(entry->d_name) || entry->d_name[0] == '.') if (!safe_leaf(entry->d_name) || entry->d_name[0] == '.') {
continue; continue;
if ((strcmp(entry->d_name, "manifest.json") == 0) != manifests) }
if ((strcmp(entry->d_name, "manifest.json") == 0) != manifests) {
continue; continue;
}
int written = snprintf(path, sizeof(path), "%s/%s", local, entry->d_name); int written = snprintf(path, sizeof(path), "%s/%s", local, entry->d_name);
if (written < 0 || (size_t)written >= sizeof(path) || lstat(path, &value) != 0 || if (written < 0 || (size_t)written >= sizeof(path) || lstat(path, &value) != 0 ||
S_ISLNK(value.st_mode)) { S_ISLNK(value.st_mode)) {
@ -342,8 +382,9 @@ static TrainlogStatus push_folder(const TrainlogGenerationMtpIo *io,
break; break;
} }
if (S_ISDIR(value.st_mode)) { if (S_ISDIR(value.st_mode)) {
if (manifests) if (manifests) {
continue; continue;
}
status = io->ensure_folder(selection->bus, status = io->ensure_folder(selection->bus,
selection->device, selection->device,
selection->storage, selection->storage,
@ -351,13 +392,16 @@ static TrainlogStatus push_folder(const TrainlogGenerationMtpIo *io,
entry->d_name, entry->d_name,
&child, &child,
&created); &created);
if (status == TRAINLOG_STATUS_OK) if (status == TRAINLOG_STATUS_OK) {
status = push_folder(io, selection, child, path, depth + 1U, false); status = push_folder(io, selection, child, path, depth + 1U, false);
if (status == TRAINLOG_STATUS_OK) }
if (status == TRAINLOG_STATUS_OK) {
status = push_folder(io, selection, child, path, depth + 1U, true); status = push_folder(io, selection, child, path, depth + 1U, true);
} else if (S_ISREG(value.st_mode)) }
} else if (S_ISREG(value.st_mode)) {
status = push_file(io, selection, parent, path, entry->d_name); status = push_file(io, selection, parent, path, entry->d_name);
} }
}
(void)closedir(directory); (void)closedir(directory);
return status; return status;
} }
@ -367,14 +411,16 @@ validate_local_folder(const char *local, unsigned int depth, uint64_t *total, si
DIR *directory; DIR *directory;
struct dirent *entry; struct dirent *entry;
TrainlogStatus status = TRAINLOG_STATUS_OK; TrainlogStatus status = TRAINLOG_STATUS_OK;
if (depth > TRAINLOG_GENERATION_MTP_MAX_DEPTH || (directory = opendir(local)) == NULL) if (depth > TRAINLOG_GENERATION_MTP_MAX_DEPTH || (directory = opendir(local)) == NULL) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
while ((entry = readdir(directory)) != NULL && status == TRAINLOG_STATUS_OK) { while ((entry = readdir(directory)) != NULL && status == TRAINLOG_STATUS_OK) {
char path[1024]; char path[1024];
struct stat value; struct stat value;
int written; int written;
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue; continue;
}
if (!safe_leaf(entry->d_name) || entry->d_name[0] == '.') { if (!safe_leaf(entry->d_name) || entry->d_name[0] == '.') {
status = TRAINLOG_STATUS_INVALID_ARGUMENT; status = TRAINLOG_STATUS_INVALID_ARGUMENT;
break; break;
@ -390,17 +436,19 @@ validate_local_folder(const char *local, unsigned int depth, uint64_t *total, si
break; break;
} }
++*objects; ++*objects;
if (S_ISDIR(value.st_mode)) if (S_ISDIR(value.st_mode)) {
status = validate_local_folder(path, depth + 1U, total, objects); status = validate_local_folder(path, depth + 1U, total, objects);
else if (S_ISREG(value.st_mode)) { } else if (S_ISREG(value.st_mode)) {
if (value.st_size < 0 || if (value.st_size < 0 ||
(uint64_t)value.st_size > TRAINLOG_GENERATION_MTP_MAX_TOTAL_BYTES - *total) (uint64_t)value.st_size > TRAINLOG_GENERATION_MTP_MAX_TOTAL_BYTES - *total) {
status = TRAINLOG_STATUS_INVALID_ARGUMENT; status = TRAINLOG_STATUS_INVALID_ARGUMENT;
else } else {
*total += (uint64_t)value.st_size; *total += (uint64_t)value.st_size;
} else }
} else {
status = TRAINLOG_STATUS_INVALID_ARGUMENT; status = TRAINLOG_STATUS_INVALID_ARGUMENT;
} }
}
(void)closedir(directory); (void)closedir(directory);
return status; return status;
} }
@ -414,13 +462,16 @@ TrainlogStatus trainlog_generation_mtp_pull(const TrainlogGenerationMtpIo *io,
uint64_t total = 0U; uint64_t total = 0U;
size_t objects = 0U; size_t objects = 0U;
TrainlogStatus status; TrainlogStatus status;
if (io == NULL || expected == NULL || local == NULL) if (io == NULL || expected == NULL || local == NULL) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
status = mkdir_private(local); status = mkdir_private(local);
if (status == TRAINLOG_STATUS_OK) if (status == TRAINLOG_STATUS_OK) {
status = select_peer(io, expected, local, &selection, diagnostic, diagnostic_size); status = select_peer(io, expected, local, &selection, diagnostic, diagnostic_size);
if (status == TRAINLOG_STATUS_OK) }
if (status == TRAINLOG_STATUS_OK) {
status = pull_folder(io, &selection, selection.root, local, 0U, &total, &objects); status = pull_folder(io, &selection, selection.root, local, 0U, &total, &objects);
}
return status; return status;
} }
@ -433,26 +484,32 @@ TrainlogStatus trainlog_generation_mtp_push(const TrainlogGenerationMtpIo *io,
TrainlogStatus status; TrainlogStatus status;
uint64_t total = 0U; uint64_t total = 0U;
size_t objects = 0U; size_t objects = 0U;
if (io == NULL || expected == NULL || local == NULL) if (io == NULL || expected == NULL || local == NULL) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
status = validate_local_folder(local, 0U, &total, &objects); status = validate_local_folder(local, 0U, &total, &objects);
if (status == TRAINLOG_STATUS_OK) if (status == TRAINLOG_STATUS_OK) {
status = select_peer(io, expected, local, &selection, diagnostic, diagnostic_size); status = select_peer(io, expected, local, &selection, diagnostic, diagnostic_size);
if (status == TRAINLOG_STATUS_OK) }
if (status == TRAINLOG_STATUS_OK) {
status = push_folder(io, &selection, selection.root, local, 0U, false); status = push_folder(io, &selection, selection.root, local, 0U, false);
if (status == TRAINLOG_STATUS_OK) }
if (status == TRAINLOG_STATUS_OK) {
status = push_folder(io, &selection, selection.root, local, 0U, true); status = push_folder(io, &selection, selection.root, local, 0U, true);
}
return status; return status;
} }
static const TrainlogGenerationMtpIo production = {trainlog_usb_list_mtp_devices, static const TrainlogGenerationMtpIo production = {
trainlog_mtp_list_storages, .devices = trainlog_usb_list_mtp_devices,
trainlog_mtp_list_folder, .storages = trainlog_mtp_list_storages,
trainlog_mtp_ensure_folder, .children = trainlog_mtp_list_folder,
trainlog_mtp_receive_file, .ensure_folder = trainlog_mtp_ensure_folder,
trainlog_mtp_send_text_file, .receive = trainlog_mtp_receive_file,
trainlog_mtp_delete_object, .send = trainlog_mtp_send_text_file,
trainlog_mtp_rename_object}; .remove = trainlog_mtp_delete_object,
.rename = trainlog_mtp_rename_object,
};
const TrainlogGenerationMtpIo *trainlog_generation_mtp_production_io(void) { const TrainlogGenerationMtpIo *trainlog_generation_mtp_production_io(void) {
return &production; return &production;
} }

View file

@ -7,8 +7,9 @@
static bool append(TrainlogJsonWriter *w, const char *s, size_t n) { static bool append(TrainlogJsonWriter *w, const char *s, size_t n) {
if (w == NULL || s == NULL || w->failed || if (w == NULL || s == NULL || w->failed ||
n > w->capacity - (w->size < w->capacity ? w->size : w->capacity) - 1U) { n > w->capacity - (w->size < w->capacity ? w->size : w->capacity) - 1U) {
if (w != NULL) if (w != NULL) {
w->failed = true; w->failed = true;
}
return false; return false;
} }
(void)memcpy(w->data + w->size, s, n); (void)memcpy(w->data + w->size, s, n);
@ -17,14 +18,16 @@ static bool append(TrainlogJsonWriter *w, const char *s, size_t n) {
return true; return true;
} }
void trainlog_json_init(TrainlogJsonWriter *w, char *data, size_t capacity) { void trainlog_json_init(TrainlogJsonWriter *w, char *data, size_t capacity) {
if (w == NULL) if (w == NULL) {
return; return;
}
w->data = data; w->data = data;
w->capacity = capacity; w->capacity = capacity;
w->size = 0U; w->size = 0U;
w->failed = data == NULL || capacity == 0U; w->failed = data == NULL || capacity == 0U;
if (!w->failed) if (!w->failed) {
data[0] = '\0'; data[0] = '\0';
}
} }
bool trainlog_json_raw(TrainlogJsonWriter *w, const char *v) { bool trainlog_json_raw(TrainlogJsonWriter *w, const char *v) {
return v != NULL && append(w, v, strlen(v)); return v != NULL && append(w, v, strlen(v));
@ -32,8 +35,9 @@ bool trainlog_json_raw(TrainlogJsonWriter *w, const char *v) {
bool trainlog_json_string(TrainlogJsonWriter *w, const char *v) { bool trainlog_json_string(TrainlogJsonWriter *w, const char *v) {
const unsigned char *p = (const unsigned char *)v; const unsigned char *p = (const unsigned char *)v;
char escape[7]; char escape[7];
if (v == NULL || !append(w, "\"", 1U)) if (v == NULL || !append(w, "\"", 1U)) {
return false; return false;
}
while (*p != '\0') { while (*p != '\0') {
utf8proc_int32_t codepoint; utf8proc_int32_t codepoint;
utf8proc_ssize_t sequence = utf8proc_iterate(p, -1, &codepoint); utf8proc_ssize_t sequence = utf8proc_iterate(p, -1, &codepoint);
@ -47,8 +51,9 @@ bool trainlog_json_string(TrainlogJsonWriter *w, const char *v) {
if (*p == '\"' || *p == '\\') { if (*p == '\"' || *p == '\\') {
escape[0] = '\\'; escape[0] = '\\';
escape[1] = (char)*p; escape[1] = (char)*p;
if (!append(w, escape, 2U)) if (!append(w, escape, 2U)) {
return false; return false;
}
} else if (*p == '\b' || *p == '\f' || *p == '\n' || *p == '\r' || *p == '\t') { } else if (*p == '\b' || *p == '\f' || *p == '\n' || *p == '\r' || *p == '\t') {
const char codes[] = "bfnrt"; const char codes[] = "bfnrt";
const char chars[] = "\b\f\n\r\t"; const char chars[] = "\b\f\n\r\t";
@ -57,14 +62,17 @@ bool trainlog_json_string(TrainlogJsonWriter *w, const char *v) {
for (i = 0U; i < 5U && chars[i] != (char)*p; ++i) { for (i = 0U; i < 5U && chars[i] != (char)*p; ++i) {
} }
escape[1] = codes[i]; escape[1] = codes[i];
if (!append(w, escape, 2U)) if (!append(w, escape, 2U)) {
return false; return false;
}
} else if (*p < 0x20U) { } else if (*p < 0x20U) {
int n = snprintf(escape, sizeof(escape), "\\u%04x", (unsigned int)*p); int n = snprintf(escape, sizeof(escape), "\\u%04x", (unsigned int)*p);
if (n != 6 || !append(w, escape, 6U)) if (n != 6 || !append(w, escape, 6U)) {
return false; return false;
} else if (!append(w, (const char *)p, (size_t)sequence)) }
} else if (!append(w, (const char *)p, (size_t)sequence)) {
return false; return false;
}
p += (size_t)sequence; p += (size_t)sequence;
} }
return append(w, "\"", 1U); return append(w, "\"", 1U);

View file

@ -18,23 +18,27 @@
static bool copy_column(sqlite3_stmt *s, int column, char *out, size_t cap) { static bool copy_column(sqlite3_stmt *s, int column, char *out, size_t cap) {
const unsigned char *text; const unsigned char *text;
int bytes; int bytes;
if (sqlite3_column_type(s, column) != SQLITE_TEXT) if (sqlite3_column_type(s, column) != SQLITE_TEXT) {
return false; return false;
}
text = sqlite3_column_text(s, column); text = sqlite3_column_text(s, column);
bytes = sqlite3_column_bytes(s, column); bytes = sqlite3_column_bytes(s, column);
if (text == NULL || bytes < 0 || (size_t)bytes >= cap) if (text == NULL || bytes < 0 || (size_t)bytes >= cap) {
return false; return false;
}
(void)memcpy(out, text, (size_t)bytes); (void)memcpy(out, text, (size_t)bytes);
out[bytes] = '\0'; out[bytes] = '\0';
return true; return true;
} }
static bool read_size(sqlite3_stmt *s, int c, size_t *out) { static bool read_size(sqlite3_stmt *s, int c, size_t *out) {
sqlite3_int64 v; sqlite3_int64 v;
if (sqlite3_column_type(s, c) != SQLITE_INTEGER) if (sqlite3_column_type(s, c) != SQLITE_INTEGER) {
return false; return false;
}
v = sqlite3_column_int64(s, c); v = sqlite3_column_int64(s, c);
if (v < 0 || (uint64_t)v > (uint64_t)SIZE_MAX) if (v < 0 || (uint64_t)v > (uint64_t)SIZE_MAX) {
return false; return false;
}
*out = (size_t)v; *out = (size_t)v;
return true; return true;
} }
@ -45,8 +49,9 @@ static bool prepare(TrainlogDatabase *d, const char *sql, sqlite3_stmt **s) {
static bool format_generated(int64_t now, char out[TRAINLOG_TIMESTAMP_MAX + 1U]) { static bool format_generated(int64_t now, char out[TRAINLOG_TIMESTAMP_MAX + 1U]) {
time_t value = (time_t)now; time_t value = (time_t)now;
struct tm utc; struct tm utc;
if ((int64_t)value != now || gmtime_r(&value, &utc) == NULL) if ((int64_t)value != now || gmtime_r(&value, &utc) == NULL) {
return false; return false;
}
return strftime(out, TRAINLOG_TIMESTAMP_MAX + 1U, "%Y-%m-%dT%H:%M:%SZ", &utc) > 0U; return strftime(out, TRAINLOG_TIMESTAMP_MAX + 1U, "%Y-%m-%dT%H:%M:%SZ", &utc) > 0U;
} }
static bool format_local_date(time_t value, char out[11]) { static bool format_local_date(time_t value, char out[11]) {
@ -59,8 +64,9 @@ static bool load_user(char out[TRAINLOG_NAME_MAX + 1U]) {
char buffer[4096]; char buffer[4096];
int rc = getpwuid_r(geteuid(), &value, buffer, sizeof(buffer), &result); int rc = getpwuid_r(geteuid(), &value, buffer, sizeof(buffer), &result);
if (rc != 0 || result == NULL || result->pw_name == NULL || result->pw_name[0] == '\0' || if (rc != 0 || result == NULL || result->pw_name == NULL || result->pw_name[0] == '\0' ||
strlen(result->pw_name) > TRAINLOG_NAME_MAX) strlen(result->pw_name) > TRAINLOG_NAME_MAX) {
return false; return false;
}
(void)snprintf(out, TRAINLOG_NAME_MAX + 1U, "%s", result->pw_name); (void)snprintf(out, TRAINLOG_NAME_MAX + 1U, "%s", result->pw_name);
return true; return true;
} }
@ -83,10 +89,12 @@ load_activity(TrainlogDatabase *d, int64_t now, TrainlogWebDashboardSnapshot *ou
size_t i; size_t i;
time_t reference = (time_t)now; time_t reference = (time_t)now;
struct tm current; struct tm current;
if ((int64_t)reference != now) if ((int64_t)reference != now) {
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
if (localtime_r(&reference, &current) == NULL) }
if (localtime_r(&reference, &current) == NULL) {
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
}
out->activity_count = TRAINLOG_WEB_ACTIVITY_DAYS; out->activity_count = TRAINLOG_WEB_ACTIVITY_DAYS;
for (i = 0U; i < TRAINLOG_WEB_ACTIVITY_DAYS; ++i) { for (i = 0U; i < TRAINLOG_WEB_ACTIVITY_DAYS; ++i) {
struct tm day = current; struct tm day = current;
@ -94,19 +102,22 @@ load_activity(TrainlogDatabase *d, int64_t now, TrainlogWebDashboardSnapshot *ou
day.tm_mday -= (int)(TRAINLOG_WEB_ACTIVITY_DAYS - 1U - i); day.tm_mday -= (int)(TRAINLOG_WEB_ACTIVITY_DAYS - 1U - i);
day.tm_isdst = -1; day.tm_isdst = -1;
normalized = mktime(&day); normalized = mktime(&day);
if (normalized == (time_t)-1 || !format_local_date(normalized, out->activity[i].date)) if (normalized == (time_t)-1 || !format_local_date(normalized, out->activity[i].date)) {
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
} }
}
if (!prepare(d, SQL, &s) || sqlite3_bind_int64(s, 1, now) != SQLITE_OK || if (!prepare(d, SQL, &s) || sqlite3_bind_int64(s, 1, now) != SQLITE_OK ||
sqlite3_bind_text(s, 2, out->activity[0].date, -1, SQLITE_TRANSIENT) != SQLITE_OK) sqlite3_bind_text(s, 2, out->activity[0].date, -1, SQLITE_TRANSIENT) != SQLITE_OK) {
goto fail; goto fail;
}
while ((step = sqlite3_step(s)) == SQLITE_ROW) { while ((step = sqlite3_step(s)) == SQLITE_ROW) {
char date[11]; char date[11];
size_t sessions, sets; size_t sessions, sets;
if (!copy_column(s, 0, date, sizeof(date)) || !read_size(s, 1, &sessions) || if (!copy_column(s, 0, date, sizeof(date)) || !read_size(s, 1, &sessions) ||
!read_size(s, 2, &sets)) !read_size(s, 2, &sets)) {
goto fail; goto fail;
for (i = 0U; i < out->activity_count; ++i) }
for (i = 0U; i < out->activity_count; ++i) {
if (strcmp(date, out->activity[i].date) == 0) { if (strcmp(date, out->activity[i].date) == 0) {
out->activity[i].active = true; out->activity[i].active = true;
out->activity[i].session_count = sessions; out->activity[i].session_count = sessions;
@ -114,12 +125,15 @@ load_activity(TrainlogDatabase *d, int64_t now, TrainlogWebDashboardSnapshot *ou
break; break;
} }
} }
if (step != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) }
if (step != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) {
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
}
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
fail: fail:
if (s != NULL) if (s != NULL) {
(void)sqlite3_finalize(s); (void)sqlite3_finalize(s);
}
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
} }
@ -146,8 +160,9 @@ static TrainlogStatus load_last(TrainlogDatabase *d, int64_t now, TrainlogWebLas
sqlite3_stmt *s = NULL; sqlite3_stmt *s = NULL;
int step; int step;
TrainlogTimestampKey start, end; TrainlogTimestampKey start, end;
if (!prepare(d, SESSION_SQL, &s) || sqlite3_bind_int64(s, 1, now) != SQLITE_OK) if (!prepare(d, SESSION_SQL, &s) || sqlite3_bind_int64(s, 1, now) != SQLITE_OK) {
goto fail; goto fail;
}
step = sqlite3_step(s); step = sqlite3_step(s);
if (step == SQLITE_DONE) { if (step == SQLITE_DONE) {
(void)sqlite3_finalize(s); (void)sqlite3_finalize(s);
@ -157,8 +172,9 @@ static TrainlogStatus load_last(TrainlogDatabase *d, int64_t now, TrainlogWebLas
!copy_column(s, 1, out->started_at, sizeof(out->started_at)) || !copy_column(s, 1, out->started_at, sizeof(out->started_at)) ||
!copy_column(s, 2, out->ended_at, sizeof(out->ended_at)) || !copy_column(s, 2, out->ended_at, sizeof(out->ended_at)) ||
!read_size(s, 3, &out->exercise_count) || !read_size(s, 4, &out->set_count) || !read_size(s, 3, &out->exercise_count) || !read_size(s, 4, &out->set_count) ||
!read_size(s, 5, &out->continuous_count) || !read_size(s, 6, &out->max_count)) !read_size(s, 5, &out->continuous_count) || !read_size(s, 6, &out->max_count)) {
goto fail; goto fail;
}
out->available = true; out->available = true;
out->has_ended_at = out->ended_at[0] != '\0'; out->has_ended_at = out->ended_at[0] != '\0';
if (!trainlog_timestamp_parse(out->started_at, strlen(out->started_at), &start)) { if (!trainlog_timestamp_parse(out->started_at, strlen(out->started_at), &start)) {
@ -167,17 +183,20 @@ static TrainlogStatus load_last(TrainlogDatabase *d, int64_t now, TrainlogWebLas
} }
if (out->has_ended_at) { if (out->has_ended_at) {
if (!trainlog_timestamp_parse(out->ended_at, strlen(out->ended_at), &end) || if (!trainlog_timestamp_parse(out->ended_at, strlen(out->ended_at), &end) ||
end.utc_second < start.utc_second) end.utc_second < start.utc_second) {
goto invalid; goto invalid;
}
out->has_duration = true; out->has_duration = true;
out->duration_seconds = (uint64_t)(end.utc_second - start.utc_second); out->duration_seconds = (uint64_t)(end.utc_second - start.utc_second);
} }
if (sqlite3_step(s) != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) if (sqlite3_step(s) != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) {
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
}
s = NULL; s = NULL;
if (!prepare(d, ZONE_SQL, &s) || if (!prepare(d, ZONE_SQL, &s) ||
sqlite3_bind_text(s, 1, out->session_id, -1, SQLITE_TRANSIENT) != SQLITE_OK) sqlite3_bind_text(s, 1, out->session_id, -1, SQLITE_TRANSIENT) != SQLITE_OK) {
goto fail; goto fail;
}
while ((step = sqlite3_step(s)) == SQLITE_ROW) { while ((step = sqlite3_step(s)) == SQLITE_ROW) {
TrainlogWebWorkedZone *z; TrainlogWebWorkedZone *z;
const TrainlogBodyZone *catalog; const TrainlogBodyZone *catalog;
@ -186,25 +205,30 @@ static TrainlogStatus load_last(TrainlogDatabase *d, int64_t now, TrainlogWebLas
} }
z = &out->zones[out->zone_count]; z = &out->zones[out->zone_count];
if (!copy_column(s, 0, z->zone_id, sizeof(z->zone_id)) || if (!copy_column(s, 0, z->zone_id, sizeof(z->zone_id)) ||
!read_size(s, 1, &z->occurrence_count) || !read_size(s, 2, &z->set_count)) !read_size(s, 1, &z->occurrence_count) || !read_size(s, 2, &z->set_count)) {
goto fail; goto fail;
}
z->session_count = 1U; z->session_count = 1U;
catalog = trainlog_body_zone_catalog_lookup(z->zone_id); catalog = trainlog_body_zone_catalog_lookup(z->zone_id);
if (catalog == NULL || strlen(catalog->display_name) > TRAINLOG_NAME_MAX) if (catalog == NULL || strlen(catalog->display_name) > TRAINLOG_NAME_MAX) {
goto invalid; goto invalid;
}
(void)snprintf(z->label, sizeof(z->label), "%s", catalog->display_name); (void)snprintf(z->label, sizeof(z->label), "%s", catalog->display_name);
++out->zone_count; ++out->zone_count;
} }
if (step != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) if (step != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) {
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
}
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
invalid: invalid:
if (s != NULL) if (s != NULL) {
(void)sqlite3_finalize(s); (void)sqlite3_finalize(s);
}
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
fail: fail:
if (s != NULL) if (s != NULL) {
(void)sqlite3_finalize(s); (void)sqlite3_finalize(s);
}
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
} }
@ -220,8 +244,9 @@ load_maxima(TrainlogDatabase *d, int64_t now, TrainlogWebDashboardSnapshot *out)
sqlite3_stmt *s = NULL; sqlite3_stmt *s = NULL;
int step; int step;
if (!prepare(d, SQL, &s) || sqlite3_bind_int64(s, 1, now) != SQLITE_OK || if (!prepare(d, SQL, &s) || sqlite3_bind_int64(s, 1, now) != SQLITE_OK ||
sqlite3_bind_int64(s, 2, (sqlite3_int64)(TRAINLOG_WEB_MAX_RECORDS + 1U)) != SQLITE_OK) sqlite3_bind_int64(s, 2, (sqlite3_int64)(TRAINLOG_WEB_MAX_RECORDS + 1U)) != SQLITE_OK) {
goto fail; goto fail;
}
while ((step = sqlite3_step(s)) == SQLITE_ROW) { while ((step = sqlite3_step(s)) == SQLITE_ROW) {
TrainlogWebMaxRecord *r; TrainlogWebMaxRecord *r;
if (out->max_record_count == TRAINLOG_WEB_MAX_RECORDS) { if (out->max_record_count == TRAINLOG_WEB_MAX_RECORDS) {
@ -244,12 +269,14 @@ load_maxima(TrainlogDatabase *d, int64_t now, TrainlogWebDashboardSnapshot *out)
r->weight_kg = sqlite3_column_double(s, 5); r->weight_kg = sqlite3_column_double(s, 5);
++out->max_record_count; ++out->max_record_count;
} }
if (step != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) if (step != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) {
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
}
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
fail: fail:
if (s != NULL) if (s != NULL) {
(void)sqlite3_finalize(s); (void)sqlite3_finalize(s);
}
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
} }
@ -271,8 +298,9 @@ load_zones(TrainlogDatabase *d, int64_t now, TrainlogWebDashboardSnapshot *out)
sqlite3_bind_text( sqlite3_bind_text(
s, 1, out->activity[TRAINLOG_WEB_ACTIVITY_DAYS - 30U].date, -1, SQLITE_TRANSIENT) != s, 1, out->activity[TRAINLOG_WEB_ACTIVITY_DAYS - 30U].date, -1, SQLITE_TRANSIENT) !=
SQLITE_OK || SQLITE_OK ||
sqlite3_bind_int64(s, 2, now) != SQLITE_OK) sqlite3_bind_int64(s, 2, now) != SQLITE_OK) {
goto fail; goto fail;
}
while ((step = sqlite3_step(s)) == SQLITE_ROW) { while ((step = sqlite3_step(s)) == SQLITE_ROW) {
TrainlogWebWorkedZone *z; TrainlogWebWorkedZone *z;
const TrainlogBodyZone *catalog; const TrainlogBodyZone *catalog;
@ -283,8 +311,9 @@ load_zones(TrainlogDatabase *d, int64_t now, TrainlogWebDashboardSnapshot *out)
z = &out->muscle_zones[out->muscle_zone_count]; z = &out->muscle_zones[out->muscle_zone_count];
if (!copy_column(s, 0, z->zone_id, sizeof(z->zone_id)) || if (!copy_column(s, 0, z->zone_id, sizeof(z->zone_id)) ||
!read_size(s, 1, &z->session_count) || !read_size(s, 2, &z->occurrence_count) || !read_size(s, 1, &z->session_count) || !read_size(s, 2, &z->occurrence_count) ||
!read_size(s, 3, &z->set_count)) !read_size(s, 3, &z->set_count)) {
goto fail; goto fail;
}
catalog = trainlog_body_zone_catalog_lookup(z->zone_id); catalog = trainlog_body_zone_catalog_lookup(z->zone_id);
if (catalog == NULL || strlen(catalog->display_name) > TRAINLOG_NAME_MAX) { if (catalog == NULL || strlen(catalog->display_name) > TRAINLOG_NAME_MAX) {
out->invalid_data = true; out->invalid_data = true;
@ -293,12 +322,14 @@ load_zones(TrainlogDatabase *d, int64_t now, TrainlogWebDashboardSnapshot *out)
(void)snprintf(z->label, sizeof(z->label), "%s", catalog->display_name); (void)snprintf(z->label, sizeof(z->label), "%s", catalog->display_name);
++out->muscle_zone_count; ++out->muscle_zone_count;
} }
if (step != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) if (step != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) {
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
}
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
fail: fail:
if (s != NULL) if (s != NULL) {
(void)sqlite3_finalize(s); (void)sqlite3_finalize(s);
}
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
} }
@ -313,8 +344,9 @@ static TrainlogStatus scan_quality(TrainlogDatabase *d, TrainlogWebDashboardSnap
sqlite3_stmt *s = NULL; sqlite3_stmt *s = NULL;
int step; int step;
size_t count = 0U; size_t count = 0U;
if (!prepare(d, SQL, &s)) if (!prepare(d, SQL, &s)) {
goto fail; goto fail;
}
while ((step = sqlite3_step(s)) == SQLITE_ROW) { while ((step = sqlite3_step(s)) == SQLITE_ROW) {
char started[TRAINLOG_TIMESTAMP_MAX + 1U], ended[TRAINLOG_TIMESTAMP_MAX + 1U]; char started[TRAINLOG_TIMESTAMP_MAX + 1U], ended[TRAINLOG_TIMESTAMP_MAX + 1U];
TrainlogTimestampKey start_key, end_key; TrainlogTimestampKey start_key, end_key;
@ -327,15 +359,18 @@ static TrainlogStatus scan_quality(TrainlogDatabase *d, TrainlogWebDashboardSnap
!copy_column(s, 1, ended, sizeof(ended)) || !copy_column(s, 1, ended, sizeof(ended)) ||
!trainlog_timestamp_parse(started, strlen(started), &start_key) || !trainlog_timestamp_parse(started, strlen(started), &start_key) ||
(ended[0] != '\0' && (!trainlog_timestamp_parse(ended, strlen(ended), &end_key) || (ended[0] != '\0' && (!trainlog_timestamp_parse(ended, strlen(ended), &end_key) ||
trainlog_timestamp_compare(&end_key, &start_key) < 0))) trainlog_timestamp_compare(&end_key, &start_key) < 0))) {
out->invalid_data = true; out->invalid_data = true;
} }
if (step != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) }
if (step != SQLITE_DONE || sqlite3_finalize(s) != SQLITE_OK) {
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
}
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
fail: fail:
if (s != NULL) if (s != NULL) {
(void)sqlite3_finalize(s); (void)sqlite3_finalize(s);
}
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
} }
@ -344,20 +379,24 @@ TrainlogStatus trainlog_web_dashboard_load(TrainlogDatabase *d,
TrainlogWebDashboardSnapshot *out) { TrainlogWebDashboardSnapshot *out) {
TrainlogStatus status, end_status; TrainlogStatus status, end_status;
TrainlogDashboardQuery progression_query; TrainlogDashboardQuery progression_query;
if (d == NULL || q == NULL || out == NULL) if (d == NULL || q == NULL || out == NULL) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
(void)memset(out, 0, sizeof(*out)); (void)memset(out, 0, sizeof(*out));
out->next_session_reason = "no_persisted_executable_plan"; out->next_session_reason = "no_persisted_executable_plan";
out->cardio_reason = "no_cardio_data_source"; out->cardio_reason = "no_cardio_data_source";
if (!format_generated(q->reference_unix_second, out->generated_at) || if (!format_generated(q->reference_unix_second, out->generated_at) ||
!load_user(out->user_display_name)) !load_user(out->user_display_name)) {
return TRAINLOG_STATUS_DATABASE_ERROR; return TRAINLOG_STATUS_DATABASE_ERROR;
}
status = trainlog_database_read_snapshot_begin(d); status = trainlog_database_read_snapshot_begin(d);
if (status != TRAINLOG_STATUS_OK) if (status != TRAINLOG_STATUS_OK) {
return status; return status;
}
status = load_activity(d, q->reference_unix_second, out); status = load_activity(d, q->reference_unix_second, out);
if (status == TRAINLOG_STATUS_OK) if (status == TRAINLOG_STATUS_OK) {
status = scan_quality(d, out); status = scan_quality(d, out);
}
if (status == TRAINLOG_STATUS_OK) { if (status == TRAINLOG_STATUS_OK) {
progression_query.period = TRAINLOG_DASHBOARD_90_DAYS; progression_query.period = TRAINLOG_DASHBOARD_90_DAYS;
progression_query.reference_unix_second = q->reference_unix_second; progression_query.reference_unix_second = q->reference_unix_second;
@ -372,12 +411,15 @@ TrainlogStatus trainlog_web_dashboard_load(TrainlogDatabase *d,
status = TRAINLOG_STATUS_OK; status = TRAINLOG_STATUS_OK;
} }
} }
if (status == TRAINLOG_STATUS_OK) if (status == TRAINLOG_STATUS_OK) {
status = load_maxima(d, q->reference_unix_second, out); status = load_maxima(d, q->reference_unix_second, out);
if (status == TRAINLOG_STATUS_OK) }
if (status == TRAINLOG_STATUS_OK) {
status = load_zones(d, q->reference_unix_second, out); status = load_zones(d, q->reference_unix_second, out);
}
end_status = trainlog_database_read_snapshot_end(d, status == TRAINLOG_STATUS_OK); end_status = trainlog_database_read_snapshot_end(d, status == TRAINLOG_STATUS_OK);
if (end_status != TRAINLOG_STATUS_OK) if (end_status != TRAINLOG_STATUS_OK) {
status = end_status; status = end_status;
}
return status; return status;
} }

View file

@ -23,25 +23,31 @@ static const char *tracking_name(TrainlogTrackingMode value) {
return value == TRAINLOG_TRACKING_DURATION ? "duration" : "reps"; return value == TRAINLOG_TRACKING_DURATION ? "duration" : "reps";
} }
static const char *load_name(TrainlogLoadMode value) { static const char *load_name(TrainlogLoadMode value) {
if (value == TRAINLOG_LOAD_EXTERNAL) if (value == TRAINLOG_LOAD_EXTERNAL) {
return "external"; return "external";
if (value == TRAINLOG_LOAD_ASSISTANCE) }
if (value == TRAINLOG_LOAD_ASSISTANCE) {
return "assistance"; return "assistance";
}
return "none"; return "none";
} }
static bool point_improved(const TrainlogDashboardSnapshot *p, size_t i) { static bool point_improved(const TrainlogDashboardSnapshot *p, size_t i) {
const TrainlogExercisePerformancePoint *a, *b; const TrainlogExercisePerformancePoint *a, *b;
if (i == 0U) if (i == 0U) {
return false; return false;
}
a = &p->performance[i - 1U]; a = &p->performance[i - 1U];
b = &p->performance[i]; b = &p->performance[i];
if (!a->has_weight && !b->has_weight) if (!a->has_weight && !b->has_weight) {
return b->metric_value > a->metric_value; return b->metric_value > a->metric_value;
if (a->has_weight != b->has_weight) }
if (a->has_weight != b->has_weight) {
return false; return false;
if (p->performance_load_mode == TRAINLOG_LOAD_ASSISTANCE) }
if (p->performance_load_mode == TRAINLOG_LOAD_ASSISTANCE) {
return b->weight_kg < a->weight_kg || return b->weight_kg < a->weight_kg ||
(b->weight_kg == a->weight_kg && b->metric_value > a->metric_value); (b->weight_kg == a->weight_kg && b->metric_value > a->metric_value);
}
return b->weight_kg > a->weight_kg || return b->weight_kg > a->weight_kg ||
(b->weight_kg == a->weight_kg && b->metric_value > a->metric_value); (b->weight_kg == a->weight_kg && b->metric_value > a->metric_value);
} }
@ -72,8 +78,9 @@ TrainlogStatus trainlog_web_dashboard_serialize(const TrainlogWebDashboardSnapsh
size_t *out_size) { size_t *out_size) {
TrainlogJsonWriter w; TrainlogJsonWriter w;
size_t i; size_t i;
if (s == NULL || out == NULL || out_size == NULL) if (s == NULL || out == NULL || out_size == NULL) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
*out_size = 0U; *out_size = 0U;
trainlog_json_init(&w, out, cap); trainlog_json_init(&w, out, cap);
RAW("{\"api_version\":1,\"data\":{\"footer\":{\"user\":"); RAW("{\"api_version\":1,\"data\":{\"footer\":{\"user\":");
@ -84,12 +91,14 @@ TrainlogStatus trainlog_web_dashboard_serialize(const TrainlogWebDashboardSnapsh
(void)memcpy(date, s->last_session.started_at, 10U); (void)memcpy(date, s->last_session.started_at, 10U);
date[10] = '\0'; date[10] = '\0';
STR(date); STR(date);
} else } else {
RAW("null"); RAW("null");
}
RAW(",\"last_zones\":["); RAW(",\"last_zones\":[");
for (i = 0U; i < s->last_session.zone_count; ++i) { for (i = 0U; i < s->last_session.zone_count; ++i) {
if (i > 0U) if (i > 0U) {
RAW(","); RAW(",");
}
STR(s->last_session.zones[i].label); STR(s->last_session.zones[i].label);
} }
RAW("]},"); RAW("]},");
@ -99,8 +108,9 @@ TrainlogStatus trainlog_web_dashboard_serialize(const TrainlogWebDashboardSnapsh
RAW("\"activity\":{\"available\":true,\"window_days\":90,\"days\":["); RAW("\"activity\":{\"available\":true,\"window_days\":90,\"days\":[");
for (i = 0U; i < s->activity_count; ++i) { for (i = 0U; i < s->activity_count; ++i) {
const TrainlogWebActivityDay *d = &s->activity[i]; const TrainlogWebActivityDay *d = &s->activity[i];
if (i > 0U) if (i > 0U) {
RAW(","); RAW(",");
}
RAW("{\"date\":"); RAW("{\"date\":");
STR(d->date); STR(d->date);
RAW(",\"active\":"); RAW(",\"active\":");
@ -129,26 +139,31 @@ TrainlogStatus trainlog_web_dashboard_serialize(const TrainlogWebDashboardSnapsh
RAW(",\"load_mode\":"); RAW(",\"load_mode\":");
STR(load_name(s->progression.performance_load_mode)); STR(load_name(s->progression.performance_load_mode));
RAW(",\"dose\":"); RAW(",\"dose\":");
if (!trainlog_json_i64(&w, (long long)s->progression.performance_dose)) if (!trainlog_json_i64(&w, (long long)s->progression.performance_dose)) {
goto overflow; goto overflow;
}
RAW("},\"points\":["); RAW("},\"points\":[");
for (i = 0U; i < s->progression.performance_count; ++i) { for (i = 0U; i < s->progression.performance_count; ++i) {
const TrainlogExercisePerformancePoint *p = &s->progression.performance[i]; const TrainlogExercisePerformancePoint *p = &s->progression.performance[i];
if (i > 0U) if (i > 0U) {
RAW(","); RAW(",");
}
RAW("{\"session_id\":"); RAW("{\"session_id\":");
STR(p->session_id); STR(p->session_id);
RAW(",\"timestamp\":"); RAW(",\"timestamp\":");
STR(p->started_at); STR(p->started_at);
RAW(",\"metric_value\":"); RAW(",\"metric_value\":");
if (!trainlog_json_i64(&w, (long long)p->metric_value)) if (!trainlog_json_i64(&w, (long long)p->metric_value)) {
goto overflow; goto overflow;
}
RAW(",\"weight_kg\":"); RAW(",\"weight_kg\":");
if (p->has_weight) { if (p->has_weight) {
if (!trainlog_json_double(&w, p->weight_kg)) if (!trainlog_json_double(&w, p->weight_kg)) {
goto overflow; goto overflow;
} else }
} else {
RAW("null"); RAW("null");
}
RAW(",\"improved\":"); RAW(",\"improved\":");
RAW(point_improved(&s->progression, i) ? "true" : "false"); RAW(point_improved(&s->progression, i) ? "true" : "false");
RAW("}"); RAW("}");
@ -166,16 +181,19 @@ TrainlogStatus trainlog_web_dashboard_serialize(const TrainlogWebDashboardSnapsh
RAW(",\"started_at\":"); RAW(",\"started_at\":");
STR(l->started_at); STR(l->started_at);
RAW(",\"ended_at\":"); RAW(",\"ended_at\":");
if (l->has_ended_at) if (l->has_ended_at) {
STR(l->ended_at); STR(l->ended_at);
else } else {
RAW("null"); RAW("null");
}
RAW(",\"duration_seconds\":"); RAW(",\"duration_seconds\":");
if (l->has_duration) { if (l->has_duration) {
if (!trainlog_json_i64(&w, (long long)l->duration_seconds)) if (!trainlog_json_i64(&w, (long long)l->duration_seconds)) {
goto overflow; goto overflow;
} else }
} else {
RAW("null"); RAW("null");
}
RAW(",\"exercise_count\":"); RAW(",\"exercise_count\":");
NUM(l->exercise_count); NUM(l->exercise_count);
RAW(",\"set_count\":"); RAW(",\"set_count\":");
@ -186,23 +204,27 @@ TrainlogStatus trainlog_web_dashboard_serialize(const TrainlogWebDashboardSnapsh
NUM(l->max_count); NUM(l->max_count);
RAW(",\"primary_zones\":["); RAW(",\"primary_zones\":[");
for (i = 0U; i < l->zone_count; ++i) { for (i = 0U; i < l->zone_count; ++i) {
if (i > 0U) if (i > 0U) {
RAW(","); RAW(",");
if (!write_zone(&w, &l->zones[i])) }
if (!write_zone(&w, &l->zones[i])) {
goto overflow; goto overflow;
} }
}
RAW("]"); RAW("]");
} }
RAW("},"); RAW("},");
RAW("\"max_records\":{\"available\":"); RAW("\"max_records\":{\"available\":");
RAW(s->max_record_count > 0U ? "true" : "false"); RAW(s->max_record_count > 0U ? "true" : "false");
if (s->max_record_count == 0U) if (s->max_record_count == 0U) {
RAW(",\"reason\":\"no_explicit_max_results\""); RAW(",\"reason\":\"no_explicit_max_results\"");
}
RAW(",\"records\":["); RAW(",\"records\":[");
for (i = 0U; i < s->max_record_count; ++i) { for (i = 0U; i < s->max_record_count; ++i) {
const TrainlogWebMaxRecord *r = &s->max_records[i]; const TrainlogWebMaxRecord *r = &s->max_records[i];
if (i > 0U) if (i > 0U) {
RAW(","); RAW(",");
}
RAW("{\"session_id\":"); RAW("{\"session_id\":");
STR(r->session_id); STR(r->session_id);
RAW(",\"entry_id\":"); RAW(",\"entry_id\":");
@ -214,8 +236,9 @@ TrainlogStatus trainlog_web_dashboard_serialize(const TrainlogWebDashboardSnapsh
RAW(",\"equipment_id\":"); RAW(",\"equipment_id\":");
STR(r->equipment_id); STR(r->equipment_id);
RAW(",\"weight_kg\":"); RAW(",\"weight_kg\":");
if (!trainlog_json_double(&w, r->weight_kg)) if (!trainlog_json_double(&w, r->weight_kg)) {
goto overflow; goto overflow;
}
RAW(",\"timestamp\":"); RAW(",\"timestamp\":");
STR(r->timestamp); STR(r->timestamp);
RAW("}"); RAW("}");
@ -223,15 +246,18 @@ TrainlogStatus trainlog_web_dashboard_serialize(const TrainlogWebDashboardSnapsh
RAW("]},"); RAW("]},");
RAW("\"muscle_distribution\":{\"available\":"); RAW("\"muscle_distribution\":{\"available\":");
RAW(s->muscle_zone_count > 0U ? "true" : "false"); RAW(s->muscle_zone_count > 0U ? "true" : "false");
if (s->muscle_zone_count == 0U) if (s->muscle_zone_count == 0U) {
RAW(",\"reason\":\"no_worked_primary_zones\""); RAW(",\"reason\":\"no_worked_primary_zones\"");
}
RAW(",\"window_days\":30,\"primary_zones\":["); RAW(",\"window_days\":30,\"primary_zones\":[");
for (i = 0U; i < s->muscle_zone_count; ++i) { for (i = 0U; i < s->muscle_zone_count; ++i) {
if (i > 0U) if (i > 0U) {
RAW(","); RAW(",");
if (!write_zone(&w, &s->muscle_zones[i])) }
if (!write_zone(&w, &s->muscle_zones[i])) {
goto overflow; goto overflow;
} }
}
RAW("]},"); RAW("]},");
RAW("\"cardio\":{\"available\":false,\"reason\":"); RAW("\"cardio\":{\"available\":false,\"reason\":");
STR(s->cardio_reason); STR(s->cardio_reason);
@ -242,12 +268,14 @@ TrainlogStatus trainlog_web_dashboard_serialize(const TrainlogWebDashboardSnapsh
RAW(",\"generated_at\":"); RAW(",\"generated_at\":");
STR(s->generated_at); STR(s->generated_at);
RAW("}}\n"); RAW("}}\n");
if (w.failed) if (w.failed) {
goto overflow; goto overflow;
}
*out_size = w.size; *out_size = w.size;
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
overflow: overflow:
if (cap > 0U) if (cap > 0U) {
out[0] = '\0'; out[0] = '\0';
}
return TRAINLOG_STATUS_SYSTEM_ERROR; return TRAINLOG_STATUS_SYSTEM_ERROR;
} }

View file

@ -30,12 +30,13 @@ static uint32_t scan(const char *path, uint32_t parent) {
const char *slash = strrchr(path, '/'); const char *slash = strrchr(path, '/');
snprintf(n->name, sizeof(n->name), "%s", slash ? slash + 1 : path); snprintf(n->name, sizeof(n->name), "%s", slash ? slash + 1 : path);
if (n->folder && (d = opendir(path)) != NULL) { if (n->folder && (d = opendir(path)) != NULL) {
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); snprintf(child, sizeof(child), "%s/%s", path, e->d_name);
scan(child, self); scan(child, self);
} }
}
closedir(d); closedir(d);
} }
return self; return self;
@ -46,14 +47,17 @@ static void rebuild(void) {
} }
static Node *node(uint32_t id) { static Node *node(uint32_t id) {
size_t i; size_t i;
for (i = 0; i < node_count; i++) for (i = 0; i < node_count; i++) {
if (nodes[i].id == id) if (nodes[i].id == id) {
return &nodes[i]; return &nodes[i];
}
}
return NULL; return NULL;
} }
static TrainlogStatus devices(TrainlogUsbDevice *o, size_t c, size_t *n) { static TrainlogStatus devices(TrainlogUsbDevice *o, size_t c, size_t *n) {
if (c < 1U) if (c < 1U) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
memset(o, 0, sizeof(*o)); memset(o, 0, sizeof(*o));
o->bus_number = 1; o->bus_number = 1;
o->device_number = 1; o->device_number = 1;
@ -64,8 +68,9 @@ static TrainlogStatus
storages(unsigned int b, unsigned int d, TrainlogMtpStorage *o, size_t c, size_t *n) { storages(unsigned int b, unsigned int d, TrainlogMtpStorage *o, size_t c, size_t *n) {
(void)b; (void)b;
(void)d; (void)d;
if (c < 1U) if (c < 1U) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
memset(o, 0, sizeof(*o)); memset(o, 0, sizeof(*o));
o->storage_id = 1; o->storage_id = 1;
*n = 1; *n = 1;
@ -83,12 +88,14 @@ static TrainlogStatus children(unsigned int b,
(void)d; (void)d;
(void)s; (void)s;
rebuild(); rebuild();
if (p == UINT32_MAX) if (p == UINT32_MAX) {
p = nodes[0].id; p = nodes[0].id;
for (i = 0; i < node_count; i++) }
for (i = 0; i < node_count; i++) {
if (nodes[i].parent == p) { if (nodes[i].parent == p) {
if (k >= c) if (k >= c) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
memset(&o[k], 0, sizeof(o[k])); memset(&o[k], 0, sizeof(o[k]));
o[k].item_id = nodes[i].id; o[k].item_id = nodes[i].id;
o[k].parent_id = p; o[k].parent_id = p;
@ -98,6 +105,7 @@ static TrainlogStatus children(unsigned int b,
snprintf(o[k].name, sizeof(o[k].name), "%s", nodes[i].name); snprintf(o[k].name, sizeof(o[k].name), "%s", nodes[i].name);
k++; k++;
} }
}
*n = k; *n = k;
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
} }
@ -115,33 +123,39 @@ static TrainlogStatus ensure(unsigned int b,
(void)s; (void)s;
rebuild(); rebuild();
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); snprintf(path, sizeof(path), "%s/%s", parent->path, name);
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) {
*created = false; *created = false;
else } else {
return TRAINLOG_STATUS_SYSTEM_ERROR; return TRAINLOG_STATUS_SYSTEM_ERROR;
}
rebuild(); rebuild();
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) {
*id = nodes[i].id; *id = nodes[i].id;
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
} }
}
return TRAINLOG_STATUS_SYSTEM_ERROR; return TRAINLOG_STATUS_SYSTEM_ERROR;
} }
static int copy(const char *a, const char *b) { static int copy(const char *a, const char *b) {
FILE *in = fopen(a, "rb"), *out = fopen(b, "wb"); FILE *in = fopen(a, "rb"), *out = fopen(b, "wb");
char buf[8192]; char buf[8192];
size_t n; size_t n;
if (!in || !out) if (!in || !out) {
return -1; return -1;
while ((n = fread(buf, 1, sizeof(buf), in)) > 0) }
if (fwrite(buf, 1, n, out) != n) while ((n = fread(buf, 1, sizeof(buf), in)) > 0) {
if (fwrite(buf, 1, n, out) != n) {
return -1; return -1;
}
}
return fclose(in) | fclose(out); return fclose(in) | fclose(out);
} }
static TrainlogStatus receive(unsigned int b, unsigned int d, uint32_t id, const char *path) { static TrainlogStatus receive(unsigned int b, unsigned int d, uint32_t id, const char *path) {
@ -166,18 +180,21 @@ static TrainlogStatus send_file(unsigned int b,
(void)s; (void)s;
rebuild(); rebuild();
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); snprintf(path, sizeof(path), "%s/%s", parent->path, name);
if (copy(local, path) != 0) if (copy(local, path) != 0) {
return TRAINLOG_STATUS_SYSTEM_ERROR; return TRAINLOG_STATUS_SYSTEM_ERROR;
}
rebuild(); rebuild();
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) {
*id = nodes[i].id; *id = nodes[i].id;
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
} }
}
return TRAINLOG_STATUS_SYSTEM_ERROR; return TRAINLOG_STATUS_SYSTEM_ERROR;
} }
static TrainlogStatus remove_object(unsigned int b, unsigned int d, uint32_t id) { static TrainlogStatus remove_object(unsigned int b, unsigned int d, uint32_t id) {
@ -195,29 +212,41 @@ static TrainlogStatus rename_object(unsigned int b, unsigned int d, uint32_t id,
(void)d; (void)d;
rebuild(); rebuild();
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); snprintf(path, sizeof(path), "%s", n->path);
slash = strrchr(path, '/'); slash = strrchr(path, '/');
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); 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; return rename(n->path, path) == 0 ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_SYSTEM_ERROR;
} }
static const TrainlogGenerationMtpIo io = { static const TrainlogGenerationMtpIo io = {
devices, storages, children, ensure, receive, send_file, remove_object, rename_object}; .devices = devices,
.storages = storages,
.children = children,
.ensure_folder = ensure,
.receive = receive,
.send = send_file,
.remove = remove_object,
.rename = rename_object,
};
int main(int argc, char **argv) { int main(int argc, char **argv) {
char diag[1024] = {0}; char diag[1024] = {0};
TrainlogStatus status; TrainlogStatus status;
remote = getenv("TRAINLOG_MTP_DOUBLE_ROOT"); remote = getenv("TRAINLOG_MTP_DOUBLE_ROOT");
if (argc != 4 || !remote) if (argc != 4 || !remote) {
return 64; return 64;
if (strcmp(argv[1], "pull") == 0) }
if (strcmp(argv[1], "pull") == 0) {
status = trainlog_generation_mtp_pull(&io, argv[2], argv[3], diag, sizeof(diag)); status = trainlog_generation_mtp_pull(&io, argv[2], argv[3], diag, sizeof(diag));
else if (strcmp(argv[1], "push") == 0) } else if (strcmp(argv[1], "push") == 0) {
status = trainlog_generation_mtp_push(&io, argv[2], argv[3], diag, sizeof(diag)); status = trainlog_generation_mtp_push(&io, argv[2], argv[3], diag, sizeof(diag));
else } else {
return 64; return 64;
}
if (status != TRAINLOG_STATUS_OK) { if (status != TRAINLOG_STATUS_OK) {
fprintf(stderr, "double failed %d %s\n", status, diag); fprintf(stderr, "double failed %d %s\n", status, diag);
return 2; return 2;

View file

@ -50,8 +50,9 @@ static void reset(void) {
} }
static TrainlogStatus devices(TrainlogUsbDevice *out, size_t cap, size_t *count) { static TrainlogStatus devices(TrainlogUsbDevice *out, size_t cap, size_t *count) {
size_t i; size_t i;
if (cap < device_count) if (cap < device_count) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
*count = device_count; *count = device_count;
for (i = 0; i < device_count; i++) { for (i = 0; i < device_count; i++) {
memset(&out[i], 0, sizeof(out[i])); memset(&out[i], 0, sizeof(out[i]));
@ -63,8 +64,9 @@ static TrainlogStatus devices(TrainlogUsbDevice *out, size_t cap, size_t *count)
static TrainlogStatus static TrainlogStatus
storages(unsigned int bus, unsigned int dev, TrainlogMtpStorage *out, size_t cap, size_t *count) { storages(unsigned int bus, unsigned int dev, TrainlogMtpStorage *out, size_t cap, size_t *count) {
(void)bus; (void)bus;
if (cap < 1U) if (cap < 1U) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
memset(out, 0, sizeof(*out)); memset(out, 0, sizeof(*out));
out->storage_id = dev; out->storage_id = dev;
*count = 1U; *count = 1U;
@ -80,10 +82,11 @@ static TrainlogStatus children(unsigned int bus,
size_t i, n = 0; size_t i, n = 0;
(void)bus; (void)bus;
(void)storage; (void)storage;
for (i = 0; i < object_count; i++) for (i = 0; i < object_count; i++) {
if (objects[i].parent == parent && (dev == 1U || objects[i].id <= 3U)) { if (objects[i].parent == parent && (dev == 1U || objects[i].id <= 3U)) {
if (n >= cap) if (n >= cap) {
return TRAINLOG_STATUS_INVALID_ARGUMENT; return TRAINLOG_STATUS_INVALID_ARGUMENT;
}
memset(&out[n], 0, sizeof(out[n])); memset(&out[n], 0, sizeof(out[n]));
out[n].item_id = objects[i].id; out[n].item_id = objects[i].id;
out[n].parent_id = parent; out[n].parent_id = parent;
@ -93,6 +96,7 @@ static TrainlogStatus children(unsigned int bus,
snprintf(out[n].name, sizeof(out[n].name), "%s", objects[i].name); snprintf(out[n].name, sizeof(out[n].name), "%s", objects[i].name);
n++; n++;
} }
}
*count = n; *count = n;
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
} }
@ -107,12 +111,13 @@ static TrainlogStatus ensure_folder(unsigned int b,
(void)b; (void)b;
(void)d; (void)d;
(void)s; (void)s;
for (i = 0; i < object_count; i++) for (i = 0; i < object_count; i++) {
if (objects[i].parent == p && objects[i].folder && strcmp(objects[i].name, name) == 0) { if (objects[i].parent == p && objects[i].folder && strcmp(objects[i].name, name) == 0) {
*id = objects[i].id; *id = objects[i].id;
*created = false; *created = false;
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
} }
}
*id = (uint32_t)(100U + object_count); *id = (uint32_t)(100U + object_count);
*created = true; *created = true;
add(*id, p, true, name, NULL); add(*id, p, true, name, NULL);
@ -120,9 +125,11 @@ static TrainlogStatus ensure_folder(unsigned int b,
} }
static Object *find(uint32_t id) { static Object *find(uint32_t id) {
size_t i; size_t i;
for (i = 0; i < object_count; i++) for (i = 0; i < object_count; i++) {
if (objects[i].id == id) if (objects[i].id == id) {
return &objects[i]; return &objects[i];
}
}
return NULL; return NULL;
} }
static TrainlogStatus receive(unsigned int b, unsigned int d, uint32_t id, const char *path) { static TrainlogStatus receive(unsigned int b, unsigned int d, uint32_t id, const char *path) {
@ -131,11 +138,13 @@ static TrainlogStatus receive(unsigned int b, unsigned int d, uint32_t id, const
size_t bytes; size_t bytes;
(void)b; (void)b;
(void)d; (void)d;
if (!o) if (!o) {
return TRAINLOG_STATUS_NOT_FOUND; return TRAINLOG_STATUS_NOT_FOUND;
}
f = fopen(path, "wb"); f = fopen(path, "wb");
if (!f) if (!f) {
return TRAINLOG_STATUS_SYSTEM_ERROR; return TRAINLOG_STATUS_SYSTEM_ERROR;
}
bytes = id == 9U ? 3U : o->size; bytes = id == 9U ? 3U : o->size;
if (fwrite(o->data, 1, bytes, f) != bytes) { if (fwrite(o->data, 1, bytes, f) != bytes) {
fclose(f); fclose(f);
@ -157,8 +166,9 @@ static TrainlogStatus send_file(unsigned int b,
(void)d; (void)d;
(void)s; (void)s;
f = fopen(path, "rb"); f = fopen(path, "rb");
if (!f) if (!f) {
return TRAINLOG_STATUS_SYSTEM_ERROR; return TRAINLOG_STATUS_SYSTEM_ERROR;
}
fseek(f, 0, SEEK_END); fseek(f, 0, SEEK_END);
size = ftell(f); size = ftell(f);
rewind(f); rewind(f);
@ -183,8 +193,9 @@ static TrainlogStatus remove_object(unsigned int b, unsigned int d, uint32_t id)
Object *o = find(id); Object *o = find(id);
(void)b; (void)b;
(void)d; (void)d;
if (!o) if (!o) {
return TRAINLOG_STATUS_NOT_FOUND; return TRAINLOG_STATUS_NOT_FOUND;
}
o->name[0] = '\0'; o->name[0] = '\0';
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
} }
@ -192,13 +203,22 @@ static TrainlogStatus rename_object(unsigned int b, unsigned int d, uint32_t id,
Object *o = find(id); Object *o = find(id);
(void)b; (void)b;
(void)d; (void)d;
if (!o) if (!o) {
return TRAINLOG_STATUS_NOT_FOUND; return TRAINLOG_STATUS_NOT_FOUND;
}
snprintf(o->name, sizeof(o->name), "%s", name); snprintf(o->name, sizeof(o->name), "%s", name);
return TRAINLOG_STATUS_OK; return TRAINLOG_STATUS_OK;
} }
static const TrainlogGenerationMtpIo io = { static const TrainlogGenerationMtpIo io = {
devices, storages, children, ensure_folder, receive, send_file, remove_object, rename_object}; .devices = devices,
.storages = storages,
.children = children,
.ensure_folder = ensure_folder,
.receive = receive,
.send = send_file,
.remove = remove_object,
.rename = rename_object,
};
static int write_file(const char *path, const char *value) { static int write_file(const char *path, const char *value) {
FILE *f = fopen(path, "wb"); FILE *f = fopen(path, "wb");
CHECK(f != NULL); CHECK(f != NULL);

View file

@ -39,8 +39,9 @@ static int reserve_port(uint16_t *port, int keep_open) {
struct sockaddr_in address; struct sockaddr_in address;
socklen_t length = (socklen_t)sizeof(address); socklen_t length = (socklen_t)sizeof(address);
int socket_fd = socket(AF_INET, SOCK_STREAM, 0); int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0) if (socket_fd < 0) {
return -1; return -1;
}
(void)memset(&address, 0, sizeof(address)); (void)memset(&address, 0, sizeof(address));
address.sin_family = AF_INET; address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
@ -70,10 +71,12 @@ static int connect_with_retry(uint16_t port) {
for (attempt = 0U; attempt < 200U; ++attempt) { for (attempt = 0U; attempt < 200U; ++attempt) {
int socket_fd = socket(AF_INET, SOCK_STREAM, 0); int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd >= 0 && if (socket_fd >= 0 &&
connect(socket_fd, (const struct sockaddr *)&address, sizeof(address)) == 0) connect(socket_fd, (const struct sockaddr *)&address, sizeof(address)) == 0) {
return socket_fd; return socket_fd;
if (socket_fd >= 0) }
if (socket_fd >= 0) {
(void)close(socket_fd); (void)close(socket_fd);
}
(void)nanosleep(&delay, NULL); (void)nanosleep(&delay, NULL);
} }
return -1; return -1;
@ -93,8 +96,9 @@ static bool exchange(uint16_t port, const char *request, char *response, size_t
(void)shutdown(socket_fd, SHUT_WR); (void)shutdown(socket_fd, SHUT_WR);
while (used + 1U < capacity) { while (used + 1U < capacity) {
ssize_t count = recv(socket_fd, response + used, capacity - used - 1U, 0); ssize_t count = recv(socket_fd, response + used, capacity - used - 1U, 0);
if (count == 0) if (count == 0) {
break; break;
}
CHECK(count > 0); CHECK(count > 0);
used += (size_t)count; used += (size_t)count;
} }
@ -109,8 +113,9 @@ static bool asset_path(const char *html, const char *suffix, char *output, size_
while ((start = strstr(start, "/assets/")) != NULL) { while ((start = strstr(start, "/assets/")) != NULL) {
const char *end = start; const char *end = start;
size_t length; size_t length;
while (*end != '\0' && *end != '"' && *end != '\'' && *end != '<' && *end != '>') while (*end != '\0' && *end != '"' && *end != '\'' && *end != '<' && *end != '>') {
++end; ++end;
}
length = (size_t)(end - start); length = (size_t)(end - start);
if (length >= suffix_length && memcmp(end - suffix_length, suffix, suffix_length) == 0 && if (length >= suffix_length && memcmp(end - suffix_length, suffix, suffix_length) == 0 &&
length < capacity) { length < capacity) {
@ -127,15 +132,18 @@ static bool response_header(const char *response, const char *name, char *output
const char *start = strstr(response, name); const char *start = strstr(response, name);
const char *end; const char *end;
size_t length; size_t length;
if (start == NULL) if (start == NULL) {
return false; return false;
}
start += strlen(name); start += strlen(name);
end = strstr(start, "\r\n"); end = strstr(start, "\r\n");
if (end == NULL) if (end == NULL) {
return false; return false;
}
length = (size_t)(end - start); length = (size_t)(end - start);
if (length >= capacity) if (length >= capacity) {
return false; return false;
}
(void)memcpy(output, start, length); (void)memcpy(output, start, length);
output[length] = '\0'; output[length] = '\0';
return true; return true;
@ -451,10 +459,12 @@ int main(void) {
char config_root[] = "/tmp/trainlog-web-config-XXXXXX"; char config_root[] = "/tmp/trainlog-web-config-XXXXXX";
TrainlogDatabase *database = NULL; TrainlogDatabase *database = NULL;
bool passed; bool passed;
if (mkdtemp(config_root) == NULL || setenv("XDG_CONFIG_HOME", config_root, 1) != 0) if (mkdtemp(config_root) == NULL || setenv("XDG_CONFIG_HOME", config_root, 1) != 0) {
return 1; return 1;
if (trainlog_database_open(":memory:", &database) != TRAINLOG_STATUS_OK) }
if (trainlog_database_open(":memory:", &database) != TRAINLOG_STATUS_OK) {
return 1; return 1;
}
if (!trainlog_web_assets_available()) { if (!trainlog_web_assets_available()) {
volatile sig_atomic_t stop = 0; volatile sig_atomic_t stop = 0;
char diagnostic[256]; char diagnostic[256];

View file

@ -9,22 +9,24 @@ int main(int argc, char **argv) {
(void)puts("usage: trainlog-generation-mtp-adapter pull|push EXPECTED_PEER LOCAL_ROOT"); (void)puts("usage: trainlog-generation-mtp-adapter pull|push EXPECTED_PEER LOCAL_ROOT");
return 0; return 0;
} }
if (argc != 4) if (argc != 4) {
return 64; return 64;
if (strcmp(argv[1], "pull") == 0) }
if (strcmp(argv[1], "pull") == 0) {
status = trainlog_generation_mtp_pull(trainlog_generation_mtp_production_io(), status = trainlog_generation_mtp_pull(trainlog_generation_mtp_production_io(),
argv[2], argv[2],
argv[3], argv[3],
diagnostic, diagnostic,
sizeof(diagnostic)); sizeof(diagnostic));
else if (strcmp(argv[1], "push") == 0) } else if (strcmp(argv[1], "push") == 0) {
status = trainlog_generation_mtp_push(trainlog_generation_mtp_production_io(), status = trainlog_generation_mtp_push(trainlog_generation_mtp_production_io(),
argv[2], argv[2],
argv[3], argv[3],
diagnostic, diagnostic,
sizeof(diagnostic)); sizeof(diagnostic));
else } else {
return 64; return 64;
}
if (status != TRAINLOG_STATUS_OK) { if (status != TRAINLOG_STATUS_OK) {
(void)fprintf( (void)fprintf(
stderr, "MTP adapter failed status=%d diagnostic=%s\n", (int)status, diagnostic); stderr, "MTP adapter failed status=%d diagnostic=%s\n", (int)status, diagnostic);