Skip to content

Commit e778553

Browse files
matteiusclaude
andcommitted
Address Copilot review feedback on PR #353
- Validate clock_gettime() return and handle unexpected pthread_cond_timedwait() errors in pop_event() to prevent tight loops - Restore sanitize_stream_name() in generate_recording_path() to prevent path traversal via crafted stream names - Remove racy read of ctx->enabled under contexts_mutex (update_recording_state already checks it under the per-context mutex) - Remove commented-out OLD CODE blocks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 34a8b34 commit e778553

1 file changed

Lines changed: 29 additions & 42 deletions

File tree

src/video/onvif_motion_recording.c

Lines changed: 29 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "video/stream_manager.h"
1919
#include "core/logger.h"
2020
#include "core/config.h"
21+
#include "core/path_utils.h"
2122
#include "core/shutdown_coordinator.h"
2223
#include "database/database_manager.h"
2324
#include "database/db_recordings.h"
@@ -125,27 +126,6 @@ static int push_event(const motion_event_t *event) {
125126
// 0 = event successfully dequeued
126127
// -1 = shutting down (no event)
127128
// -2 = timeout (no event within 1 s), caller should tick the state machine
128-
//
129-
// FIX: recording stop bug - the old implementation used pthread_cond_wait
130-
// (blocking forever), so update_recording_state() was never called again
131-
// after a "no motion" event, and recordings never stopped.
132-
// The new implementation uses a 1-second timedwait so the event loop can
133-
// periodically tick the state machine even when no new events arrive.
134-
//
135-
// OLD CODE (kept for reference):
136-
// static int pop_event(motion_event_t *event) {
137-
// if (!event) {
138-
// return -1;
139-
// }
140-
// pthread_mutex_lock(&event_queue.mutex);
141-
// // Wait for events if queue is empty
142-
// while (event_queue.count == 0 && event_processor_running) {
143-
// pthread_cond_wait(&event_queue.cond, &event_queue.mutex); // <-- bug: blocks forever
144-
// }
145-
// if (event_queue.count == 0) {
146-
// pthread_mutex_unlock(&event_queue.mutex);
147-
// return -1;
148-
// }
149129
static int pop_event(motion_event_t *event) {
150130
if (!event) {
151131
return -1;
@@ -158,13 +138,23 @@ static int pop_event(motion_event_t *event) {
158138
// RECORDING -> FINALIZING -> stop after a "no motion" event).
159139
while (event_queue.count == 0 && event_processor_running) {
160140
struct timespec ts;
161-
clock_gettime(CLOCK_REALTIME, &ts);
141+
if (clock_gettime(CLOCK_REALTIME, &ts) != 0) {
142+
// clock_gettime failed — fall back to a short sleep
143+
pthread_mutex_unlock(&event_queue.mutex);
144+
return -2;
145+
}
162146
ts.tv_sec += 1;
163147
int rc = pthread_cond_timedwait(&event_queue.cond, &event_queue.mutex, &ts);
164148
if (rc == ETIMEDOUT) {
165149
pthread_mutex_unlock(&event_queue.mutex);
166150
return -2; // periodic tick
167151
}
152+
if (rc != 0 && rc != ETIMEDOUT) {
153+
// Unexpected error (e.g. EINVAL) — treat as a tick to avoid tight loop
154+
log_warn("pthread_cond_timedwait unexpected error: %d", rc);
155+
pthread_mutex_unlock(&event_queue.mutex);
156+
return -2;
157+
}
168158
}
169159

170160
if (event_queue.count == 0) {
@@ -270,31 +260,35 @@ static int generate_recording_path(const char *stream_name, char *path, size_t p
270260
char timestamp[32];
271261
strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", tm_info);
272262

263+
// Sanitize the stream name to prevent path traversal and handle spaces.
264+
char sanitized_name[MAX_STREAM_NAME];
265+
sanitize_stream_name(stream_name, sanitized_name, MAX_STREAM_NAME);
266+
273267
// Create directory structure: /recordings/camera_name/YYYY/MM/DD/
274268
char year[8], month[4], day[4];
275269
strftime(year, sizeof(year), "%Y", tm_info);
276270
strftime(month, sizeof(month), "%m", tm_info);
277271
strftime(day, sizeof(day), "%d", tm_info);
278-
272+
279273
char dir_path[MAX_PATH_LENGTH];
280274
snprintf(dir_path, sizeof(dir_path), "%s/%s/%s/%s/%s",
281-
config->storage_path, stream_name, year, month, day);
282-
275+
config->storage_path, sanitized_name, year, month, day);
276+
283277
// Create directories if they don't exist
284278
char temp_path[MAX_PATH_LENGTH];
285-
snprintf(temp_path, sizeof(temp_path), "%s/%s", config->storage_path, stream_name);
279+
snprintf(temp_path, sizeof(temp_path), "%s/%s", config->storage_path, sanitized_name);
286280
mkdir(temp_path, 0755);
287-
288-
snprintf(temp_path, sizeof(temp_path), "%s/%s/%s", config->storage_path, stream_name, year);
281+
282+
snprintf(temp_path, sizeof(temp_path), "%s/%s/%s", config->storage_path, sanitized_name, year);
289283
mkdir(temp_path, 0755);
290-
291-
snprintf(temp_path, sizeof(temp_path), "%s/%s/%s/%s", config->storage_path, stream_name, year, month);
284+
285+
snprintf(temp_path, sizeof(temp_path), "%s/%s/%s/%s", config->storage_path, sanitized_name, year, month);
292286
mkdir(temp_path, 0755);
293-
287+
294288
mkdir(dir_path, 0755);
295-
289+
296290
// Generate full file path
297-
snprintf(path, path_size, "%s/%s_%s_motion.mp4", dir_path, stream_name, timestamp);
291+
snprintf(path, path_size, "%s/%s_%s_motion.mp4", dir_path, sanitized_name, timestamp);
298292

299293
log_info("Generated recording path: %s", path);
300294
return 0;
@@ -497,14 +491,6 @@ static void* event_processor_thread_func(void *arg) {
497491
// as "shutting down" and continued/broke without ever ticking the
498492
// state machine for already-active recordings.
499493
//
500-
// OLD CODE (kept for reference):
501-
// if (pop_event(&event) != 0) {
502-
// // Queue is empty or we're shutting down
503-
// if (!event_processor_running) {
504-
// break;
505-
// }
506-
// continue; // <-- bug: skips update_recording_state entirely
507-
// }
508494
int pop_ret = pop_event(&event);
509495
if (pop_ret == -1) {
510496
// Shutting down
@@ -518,8 +504,9 @@ static void* event_processor_thread_func(void *arg) {
518504
time_t now = time(NULL);
519505
pthread_mutex_lock(&contexts_mutex);
520506
for (int i = 0; i < g_config.max_streams; i++) {
521-
if (recording_contexts[i].active && recording_contexts[i].enabled) {
507+
if (recording_contexts[i].active) {
522508
pthread_mutex_unlock(&contexts_mutex);
509+
// update_recording_state() checks ctx->enabled under ctx->mutex
523510
update_recording_state(&recording_contexts[i], now);
524511
pthread_mutex_lock(&contexts_mutex);
525512
}

0 commit comments

Comments
 (0)