diff --git a/source/dcautil/dcautil.c b/source/dcautil/dcautil.c index a6cc0c95..68ceac72 100644 --- a/source/dcautil/dcautil.c +++ b/source/dcautil/dcautil.c @@ -19,7 +19,15 @@ #include #include +#include +#include +#include #include +#include +#include +#include +#include +#include #include "dca.h" #include "dcautil.h" #include "telemetry2_0.h" @@ -28,6 +36,135 @@ #include "legacyutils.h" #include "persistence.h" +/** + * @brief Wait for the backup_logs completion sentinel before grepping PreviousLogs. + * + * Strategy: + * 1. Fast path: sentinel already present → return true immediately. + * 2. Set up inotify on BACKUP_LOGS_DONE_DIR (/tmp) for IN_CREATE | IN_MOVED_TO. + * 3. Re-check after watch is established to close the creation race window. + * 4. select() loop with 2 s heartbeat; exit when sentinel appears or + * BACKUP_LOGS_SYNC_TIMEOUT_S total seconds have elapsed. + * + * This is a **soft gate**: if the function returns false (timeout or inotify + * failure) the caller still proceeds — previous-log grep runs against whatever + * files are present, and a warning is logged. + * + * @return true Sentinel detected within timeout + * @return false Timeout elapsed or sync could not be established (inotify/clock/select failure) + */ +bool waitForBackupLogsDone(void) +{ + /* Fast path: sentinel already written by backup_logs */ + if (access(BACKUP_LOGS_DONE_FLAG, F_OK) == 0) + { + T2Info("backup_logs sentinel %s already present\n", BACKUP_LOGS_DONE_FLAG); + return true; + } + + int ifd = inotify_init1(IN_CLOEXEC); + if (ifd < 0) + { + T2Error("inotify_init1 failed (errno=%d); proceeding without backup_logs sync\n", errno); + return false; + } + + int wd = inotify_add_watch(ifd, BACKUP_LOGS_DONE_DIR, IN_CREATE | IN_MOVED_TO); + if (wd < 0) + { + T2Error("inotify_add_watch on %s failed (errno=%d); proceeding without backup_logs sync\n", + BACKUP_LOGS_DONE_DIR, errno); + close(ifd); + return false; + } + + /* Re-check after watch is set — closes the race window between the first + * access() call and establishing the watch. */ + if (access(BACKUP_LOGS_DONE_FLAG, F_OK) == 0) + { + T2Info("backup_logs sentinel detected (race resolved): %s\n", BACKUP_LOGS_DONE_FLAG); + inotify_rm_watch(ifd, wd); + close(ifd); + return true; + } + + struct timespec deadline; + if (clock_gettime(CLOCK_MONOTONIC, &deadline) != 0) + { + T2Error("clock_gettime failed (errno=%d); proceeding without backup_logs sync\n", errno); + inotify_rm_watch(ifd, wd); + close(ifd); + return false; + } + deadline.tv_sec += BACKUP_LOGS_SYNC_TIMEOUT_S; + + T2Info("Waiting up to %ds for backup_logs sentinel: %s\n", + BACKUP_LOGS_SYNC_TIMEOUT_S, BACKUP_LOGS_DONE_FLAG); + + bool found = false; + char buf[sizeof(struct inotify_event) + NAME_MAX + 1]; + + while (!found) + { + /* Check deadline */ + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) + { + T2Error("clock_gettime failed (errno=%d) while waiting for backup_logs sentinel; proceeding without sync\n", errno); + break; + } + if (now.tv_sec >= deadline.tv_sec) + { + T2Warning("backup_logs sentinel not present after %ds; proceeding without sync\n", + BACKUP_LOGS_SYNC_TIMEOUT_S); + break; + } + + struct timeval tv = {2, 0}; + fd_set fds; + FD_ZERO(&fds); + FD_SET(ifd, &fds); + + int ret = select(ifd + 1, &fds, NULL, NULL, &tv); + if (ret < 0) + { + if (errno == EINTR) + { + continue; + } + T2Error("select() failed (errno=%d) waiting for backup_logs sentinel\n", errno); + break; + } + if (ret == 0) + { + continue; /* 2 s heartbeat — loop back to check deadline */ + } + + ssize_t len = read(ifd, buf, sizeof(buf)); + if (len <= 0) + { + continue; + } + + ssize_t offset = 0; + while (offset < len) + { + struct inotify_event *ev = (struct inotify_event *)(buf + offset); + if (ev->len > 0 && strcmp(ev->name, BACKUP_LOGS_DONE_FILENAME) == 0) + { + T2Info("backup_logs sentinel created: %s\n", BACKUP_LOGS_DONE_FLAG); + found = true; + break; + } + offset += (ssize_t)(sizeof(struct inotify_event) + ev->len); + } + } + + inotify_rm_watch(ifd, wd); + close(ifd); + return found; +} + /** * @brief Get the Grep Results object. Main function called by rest of the consumers. * @@ -50,6 +187,20 @@ getGrepResults (GrepSeekProfile **GSP, Vector *markerList, bool isClearSeekMap, return T2ERROR_FAILURE; } + /* Synchronize with backup_logs before grepping the previous-log directory. + * backup_logs populates PREVIOUS_LOGS_PATH; reading it before the write is + * complete yields incomplete or empty results. This is a soft gate — if the + * sentinel does not appear within BACKUP_LOGS_SYNC_TIMEOUT_S seconds we + * still proceed so telemetry is never permanently blocked. */ + if (customLogPath != NULL && strcmp(customLogPath, PREVIOUS_LOGS_PATH) == 0) + { + if (!waitForBackupLogsDone()) + { + T2Warning("%s: backup_logs sentinel absent; grepping %s with potentially incomplete data\n", + __FUNCTION__, PREVIOUS_LOGS_PATH); + } + } + getDCAResultsInVector(*GSP, markerList, check_rotated, customLogPath); if (isClearSeekMap) { @@ -57,7 +208,23 @@ getGrepResults (GrepSeekProfile **GSP, Vector *markerList, bool isClearSeekMap, freeGrepSeekProfile(*GSP); *GSP = createGrepSeekProfile(count); } - + /* Signal that telemetry previous-log grep is complete. + * Downstream consumers (e.g. uploadSTBLogs) wait for this sentinel + * before starting the log upload to avoid incomplete telemetry data. */ + if (customLogPath != NULL && strcmp(customLogPath, PREVIOUS_LOGS_PATH) == 0) + { + int fd = open(TELEMETRY_PREVLOGS_DONE_FLAG, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd >= 0) + { + close(fd); + T2Info("Created telemetry previous-logs sentinel: %s\n", TELEMETRY_PREVLOGS_DONE_FLAG); + } + else + { + T2Error("%s: Failed to create sentinel %s (errno=%d)\n", + __FUNCTION__, TELEMETRY_PREVLOGS_DONE_FLAG, errno); + } + } T2Debug("%s --out\n", __FUNCTION__); return T2ERROR_SUCCESS; } diff --git a/source/dcautil/dcautil.h b/source/dcautil/dcautil.h index 73487672..aa4bc442 100644 --- a/source/dcautil/dcautil.h +++ b/source/dcautil/dcautil.h @@ -30,10 +30,20 @@ #define TOPTEMP "/tmp/t2toplog" #define DCADONEFLAG "/tmp/.dca_done" +#define TELEMETRY_PREVLOGS_DONE_FLAG "/tmp/.telemetry_prevlogs_done" #define PREVIOUS_LOG "PREVIOUS_LOG" #define PREVIOUS_LOGS_VAL "1" #define PREVIOUS_LOGS_PATH "/opt/logs/PreviousLogs/" +#define BACKUP_LOGS_DONE_FLAG "/tmp/.backup_logs_done" +#define BACKUP_LOGS_DONE_DIR "/tmp" +#define BACKUP_LOGS_DONE_FILENAME ".backup_logs_done" + +#ifdef GTEST_ENABLE +# define BACKUP_LOGS_SYNC_TIMEOUT_S 2 +#else +# define BACKUP_LOGS_SYNC_TIMEOUT_S 60 +#endif typedef struct _GrepResult { @@ -79,7 +89,7 @@ int getMemInfo(procMemCpuInfo *pmInfo); int getCPUInfo(procMemCpuInfo *pInfo, char* filename); int getProcPidStat(int pid, procinfo * pinfo); int getTotalCpuTimes(int * totalTime); - +bool waitForBackupLogsDone(void); #ifdef PERSIST_LOG_MON_REF typedef void (*freeconfigdata)(void *data); diff --git a/source/test/dcautils/dcautilTest.cpp b/source/test/dcautils/dcautilTest.cpp index 247b1e6a..a8113dff 100644 --- a/source/test/dcautils/dcautilTest.cpp +++ b/source/test/dcautils/dcautilTest.cpp @@ -1870,6 +1870,448 @@ TEST_F(dcaTestFixture, getGrepResults_success) free(gsProfile); Vector_Destroy(vecMarkerList, freeGMarker); } + +// waitForBackupLogsDone L1 tests + +/* Fixture that enables clock_gettime/select mocking only during tests + * that need them — avoids intercepting gtest's internal timing calls. */ +class waitForBackupLogsDoneFixture : public dcaTestFixture { +protected: + void SetUp() override + { + dcaTestFixture::SetUp(); + g_mockClockGettime = true; + g_mockSelect = true; + } + void TearDown() override + { + g_mockClockGettime = false; + g_mockSelect = false; + dcaTestFixture::TearDown(); + } +}; + +TEST_F(dcaTestFixture, waitForBackupLogsDone_SentinelAlreadyPresent) +{ + // Fast path: access() returns 0 meaning sentinel file exists + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_EQ(true, waitForBackupLogsDone()); +} + +TEST_F(dcaTestFixture, waitForBackupLogsDone_InotifyInitFails) +{ + // access() returns -1 (sentinel not present), inotify_init1 fails + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(1) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_systemMock, inotify_init1(_)) + .Times(1) + .WillOnce(Return(-1)); + + EXPECT_EQ(false, waitForBackupLogsDone()); +} + +TEST_F(dcaTestFixture, waitForBackupLogsDone_InotifyAddWatchFails) +{ + // access() returns -1, inotify_init1 succeeds, inotify_add_watch fails + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(1) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_systemMock, inotify_init1(_)) + .Times(1) + .WillOnce(Return(5)); + EXPECT_CALL(*g_systemMock, inotify_add_watch(5, StrEq(BACKUP_LOGS_DONE_DIR), _)) + .Times(1) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_fileIOMock, close(5)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_EQ(false, waitForBackupLogsDone()); +} + +TEST_F(dcaTestFixture, waitForBackupLogsDone_RaceResolved) +{ + // First access() returns -1, inotify setup succeeds, second access() returns 0 (race resolved) + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(2) + .WillOnce(Return(-1)) + .WillOnce(Return(0)); + EXPECT_CALL(*g_systemMock, inotify_init1(_)) + .Times(1) + .WillOnce(Return(5)); + EXPECT_CALL(*g_systemMock, inotify_add_watch(5, StrEq(BACKUP_LOGS_DONE_DIR), _)) + .Times(1) + .WillOnce(Return(1)); + EXPECT_CALL(*g_systemMock, inotify_rm_watch(5, 1)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock, close(5)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_EQ(true, waitForBackupLogsDone()); +} + +TEST_F(waitForBackupLogsDoneFixture, waitForBackupLogsDone_ClockGettimeFails) +{ + // access() returns -1 both times, inotify setup succeeds, clock_gettime fails + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(2) + .WillOnce(Return(-1)) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_systemMock, inotify_init1(_)) + .Times(1) + .WillOnce(Return(5)); + EXPECT_CALL(*g_systemMock, inotify_add_watch(5, StrEq(BACKUP_LOGS_DONE_DIR), _)) + .Times(1) + .WillOnce(Return(1)); + EXPECT_CALL(*g_systemMock, clock_gettime(_, _)) + .Times(1) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_systemMock, inotify_rm_watch(5, 1)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock, close(5)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_EQ(false, waitForBackupLogsDone()); +} + +TEST_F(waitForBackupLogsDoneFixture, waitForBackupLogsDone_Timeout) +{ + // Sentinel never appears, select returns 0 (timeout heartbeat), deadline expires + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(2) + .WillOnce(Return(-1)) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_systemMock, inotify_init1(_)) + .Times(1) + .WillOnce(Return(5)); + EXPECT_CALL(*g_systemMock, inotify_add_watch(5, StrEq(BACKUP_LOGS_DONE_DIR), _)) + .Times(1) + .WillOnce(Return(1)); + + // First clock_gettime sets the deadline (tv_sec=100) + // Second clock_gettime returns past deadline (tv_sec >= 100 + BACKUP_LOGS_SYNC_TIMEOUT_S) + EXPECT_CALL(*g_systemMock, clock_gettime(_, _)) + .Times(2) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; + tp->tv_nsec = 0; + return 0; + }) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100 + BACKUP_LOGS_SYNC_TIMEOUT_S; + tp->tv_nsec = 0; + return 0; + }); + + EXPECT_CALL(*g_systemMock, inotify_rm_watch(5, 1)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock, close(5)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_EQ(false, waitForBackupLogsDone()); +} + +TEST_F(waitForBackupLogsDoneFixture, waitForBackupLogsDone_SelectFails) +{ + // select() fails with non-EINTR error + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(2) + .WillOnce(Return(-1)) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_systemMock, inotify_init1(_)) + .Times(1) + .WillOnce(Return(5)); + EXPECT_CALL(*g_systemMock, inotify_add_watch(5, StrEq(BACKUP_LOGS_DONE_DIR), _)) + .Times(1) + .WillOnce(Return(1)); + EXPECT_CALL(*g_systemMock, clock_gettime(_, _)) + .Times(2) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; + tp->tv_nsec = 0; + return 0; + }) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; // Not past deadline yet + tp->tv_nsec = 0; + return 0; + }); + EXPECT_CALL(*g_systemMock, select(_, _, _, _, _)) + .Times(1) + .WillOnce([](int, fd_set*, fd_set*, fd_set*, struct timeval*) { + errno = EBADF; + return -1; + }); + EXPECT_CALL(*g_systemMock, inotify_rm_watch(5, 1)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock, close(5)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_EQ(false, waitForBackupLogsDone()); +} + +TEST_F(waitForBackupLogsDoneFixture, waitForBackupLogsDone_SelectEINTR) +{ + // select() returns EINTR, then deadline expires + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(2) + .WillOnce(Return(-1)) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_systemMock, inotify_init1(_)) + .Times(1) + .WillOnce(Return(5)); + EXPECT_CALL(*g_systemMock, inotify_add_watch(5, StrEq(BACKUP_LOGS_DONE_DIR), _)) + .Times(1) + .WillOnce(Return(1)); + EXPECT_CALL(*g_systemMock, clock_gettime(_, _)) + .Times(3) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; + tp->tv_nsec = 0; + return 0; + }) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; // Not past deadline + tp->tv_nsec = 0; + return 0; + }) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100 + BACKUP_LOGS_SYNC_TIMEOUT_S; // Past deadline + tp->tv_nsec = 0; + return 0; + }); + EXPECT_CALL(*g_systemMock, select(_, _, _, _, _)) + .Times(1) + .WillOnce([](int, fd_set*, fd_set*, fd_set*, struct timeval*) { + errno = EINTR; + return -1; + }); + EXPECT_CALL(*g_systemMock, inotify_rm_watch(5, 1)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock, close(5)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_EQ(false, waitForBackupLogsDone()); +} + +TEST_F(waitForBackupLogsDoneFixture, waitForBackupLogsDone_SentinelDetectedViaInotify) +{ + // Sentinel detected via inotify event + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(2) + .WillOnce(Return(-1)) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_systemMock, inotify_init1(_)) + .Times(1) + .WillOnce(Return(5)); + EXPECT_CALL(*g_systemMock, inotify_add_watch(5, StrEq(BACKUP_LOGS_DONE_DIR), _)) + .Times(1) + .WillOnce(Return(1)); + EXPECT_CALL(*g_systemMock, clock_gettime(_, _)) + .Times(2) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; + tp->tv_nsec = 0; + return 0; + }) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; // Not past deadline + tp->tv_nsec = 0; + return 0; + }); + EXPECT_CALL(*g_systemMock, select(_, _, _, _, _)) + .Times(1) + .WillOnce(Return(1)); // Data available to read + + // Construct a fake inotify_event with the sentinel filename + struct inotify_event fake_event; + fake_event.wd = 1; + fake_event.mask = IN_CREATE; + fake_event.cookie = 0; + fake_event.len = strlen(BACKUP_LOGS_DONE_FILENAME) + 1; + + // Build the buffer: struct inotify_event + filename (null-terminated) + size_t event_size = sizeof(struct inotify_event) + fake_event.len; + char *event_buf = (char *)malloc(event_size); + memcpy(event_buf, &fake_event, sizeof(struct inotify_event)); + memcpy(event_buf + sizeof(struct inotify_event), BACKUP_LOGS_DONE_FILENAME, fake_event.len); + + EXPECT_CALL(*g_fileIOMock, read(5, _, _)) + .Times(1) + .WillOnce([event_buf, event_size](int, void* buf, size_t count) -> ssize_t { + size_t copy_size = (event_size < count) ? event_size : count; + memcpy(buf, event_buf, copy_size); + return (ssize_t)copy_size; + }); + + EXPECT_CALL(*g_systemMock, inotify_rm_watch(5, 1)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock, close(5)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_EQ(true, waitForBackupLogsDone()); + free(event_buf); +} + +TEST_F(waitForBackupLogsDoneFixture, waitForBackupLogsDone_ReadReturnsZero) +{ + // select() returns data ready, but read() returns 0 — loop back, then deadline expires + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(2) + .WillOnce(Return(-1)) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_systemMock, inotify_init1(_)) + .Times(1) + .WillOnce(Return(5)); + EXPECT_CALL(*g_systemMock, inotify_add_watch(5, StrEq(BACKUP_LOGS_DONE_DIR), _)) + .Times(1) + .WillOnce(Return(1)); + EXPECT_CALL(*g_systemMock, clock_gettime(_, _)) + .Times(3) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; + tp->tv_nsec = 0; + return 0; + }) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; + tp->tv_nsec = 0; + return 0; + }) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100 + BACKUP_LOGS_SYNC_TIMEOUT_S; + tp->tv_nsec = 0; + return 0; + }); + EXPECT_CALL(*g_systemMock, select(_, _, _, _, _)) + .Times(1) + .WillOnce(Return(1)); + EXPECT_CALL(*g_fileIOMock, read(5, _, _)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_systemMock, inotify_rm_watch(5, 1)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock, close(5)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_EQ(false, waitForBackupLogsDone()); +} + +TEST_F(waitForBackupLogsDoneFixture, waitForBackupLogsDone_UnrelatedInotifyEvent) +{ + // An inotify event for a different file, then deadline expires + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(2) + .WillOnce(Return(-1)) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_systemMock, inotify_init1(_)) + .Times(1) + .WillOnce(Return(5)); + EXPECT_CALL(*g_systemMock, inotify_add_watch(5, StrEq(BACKUP_LOGS_DONE_DIR), _)) + .Times(1) + .WillOnce(Return(1)); + EXPECT_CALL(*g_systemMock, clock_gettime(_, _)) + .Times(3) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; + tp->tv_nsec = 0; + return 0; + }) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; + tp->tv_nsec = 0; + return 0; + }) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100 + BACKUP_LOGS_SYNC_TIMEOUT_S; + tp->tv_nsec = 0; + return 0; + }); + EXPECT_CALL(*g_systemMock, select(_, _, _, _, _)) + .Times(1) + .WillOnce(Return(1)); + + // Construct an inotify event for a different filename + const char *other_file = "other_file.txt"; + struct inotify_event fake_event; + fake_event.wd = 1; + fake_event.mask = IN_CREATE; + fake_event.cookie = 0; + fake_event.len = strlen(other_file) + 1; + + size_t event_size = sizeof(struct inotify_event) + fake_event.len; + char *event_buf = (char *)malloc(event_size); + memcpy(event_buf, &fake_event, sizeof(struct inotify_event)); + memcpy(event_buf + sizeof(struct inotify_event), other_file, fake_event.len); + + EXPECT_CALL(*g_fileIOMock, read(5, _, _)) + .Times(1) + .WillOnce([event_buf, event_size](int, void* buf, size_t count) -> ssize_t { + size_t copy_size = (event_size < count) ? event_size : count; + memcpy(buf, event_buf, copy_size); + return (ssize_t)copy_size; + }); + + EXPECT_CALL(*g_systemMock, inotify_rm_watch(5, 1)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock, close(5)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_EQ(false, waitForBackupLogsDone()); + free(event_buf); +} + +TEST_F(waitForBackupLogsDoneFixture, waitForBackupLogsDone_ClockGettimeFailsInLoop) +{ + // clock_gettime succeeds initially but fails inside the while loop + EXPECT_CALL(*g_systemMock, access(StrEq(BACKUP_LOGS_DONE_FLAG), _)) + .Times(2) + .WillOnce(Return(-1)) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_systemMock, inotify_init1(_)) + .Times(1) + .WillOnce(Return(5)); + EXPECT_CALL(*g_systemMock, inotify_add_watch(5, StrEq(BACKUP_LOGS_DONE_DIR), _)) + .Times(1) + .WillOnce(Return(1)); + EXPECT_CALL(*g_systemMock, clock_gettime(_, _)) + .Times(2) + .WillOnce([](clockid_t, struct timespec *tp) { + tp->tv_sec = 100; + tp->tv_nsec = 0; + return 0; + }) + .WillOnce(Return(-1)); // Fails in the loop + EXPECT_CALL(*g_systemMock, inotify_rm_watch(5, 1)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock, close(5)) + .Times(1) + .WillOnce(Return(0)); + + EXPECT_EQ(false, waitForBackupLogsDone()); +} + #ifdef GTEST_ENABLE extern "C" { typedef const char *(*strnstrFunc)(const char *, const char *, size_t); diff --git a/source/test/mocks/SystemMock.cpp b/source/test/mocks/SystemMock.cpp index d6a39397..2d2ab56c 100644 --- a/source/test/mocks/SystemMock.cpp +++ b/source/test/mocks/SystemMock.cpp @@ -24,11 +24,28 @@ typedef int (*system_ptr) (const char * cmd); typedef int (*unlink_ptr) (const char * str); typedef int (*access_ptr) (const char * pathname, int mode); typedef int (*remove_ptr) (const char * pathname); +typedef int (*inotify_init1_ptr) (int flags); +typedef int (*inotify_add_watch_ptr) (int fd, const char *pathname, uint32_t mask); +typedef int (*inotify_rm_watch_ptr) (int fd, int wd); +typedef int (*clock_gettime_ptr) (clockid_t clk_id, struct timespec *tp); +typedef int (*select_ptr) (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); + system_ptr system_func = (system_ptr) dlsym(RTLD_NEXT, "system"); unlink_ptr unlink_func = (unlink_ptr) dlsym(RTLD_NEXT, "unlink"); access_ptr access_func = (access_ptr) dlsym(RTLD_NEXT, "access"); remove_ptr remove_func = (remove_ptr) dlsym(RTLD_NEXT, "remove"); +inotify_init1_ptr inotify_init1_func = (inotify_init1_ptr) dlsym(RTLD_NEXT, "inotify_init1"); +inotify_add_watch_ptr inotify_add_watch_func = (inotify_add_watch_ptr) dlsym(RTLD_NEXT, "inotify_add_watch"); +inotify_rm_watch_ptr inotify_rm_watch_func = (inotify_rm_watch_ptr) dlsym(RTLD_NEXT, "inotify_rm_watch"); +clock_gettime_ptr clock_gettime_func = (clock_gettime_ptr) dlsym(RTLD_NEXT, "clock_gettime"); +select_ptr select_func = (select_ptr) dlsym(RTLD_NEXT, "select"); + +/* Flags default to false — clock_gettime/select are only intercepted when + * a test explicitly enables them to avoid breaking gtest internals. */ +bool g_mockClockGettime = false; +bool g_mockSelect = false; + // Mock Method extern "C" int system(const char * cmd) @@ -65,4 +82,49 @@ extern "C" int remove(const char *pathname) return remove_func(pathname); } return g_systemMock->remove(pathname); -} +} + +extern "C" int inotify_init1(int flags) +{ + if (!g_systemMock) + { + return inotify_init1_func(flags); + } + return g_systemMock->inotify_init1(flags); +} + +extern "C" int inotify_add_watch(int fd, const char *pathname, uint32_t mask) +{ + if (!g_systemMock) + { + return inotify_add_watch_func(fd, pathname, mask); + } + return g_systemMock->inotify_add_watch(fd, pathname, mask); +} + +extern "C" int inotify_rm_watch(int fd, int wd) +{ + if (!g_systemMock) + { + return inotify_rm_watch_func(fd, wd); + } + return g_systemMock->inotify_rm_watch(fd, wd); +} + +extern "C" int clock_gettime(clockid_t clk_id, struct timespec *tp) +{ + if (g_systemMock && g_mockClockGettime) + { + return g_systemMock->clock_gettime(clk_id, tp); + } + return clock_gettime_func(clk_id, tp); +} + +extern "C" int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) +{ + if (g_systemMock && g_mockSelect) + { + return g_systemMock->select(nfds, readfds, writefds, exceptfds, timeout); + } + return select_func(nfds, readfds, writefds, exceptfds, timeout); +} diff --git a/source/test/mocks/SystemMock.h b/source/test/mocks/SystemMock.h index 0c9ce8f8..4d8e35e2 100644 --- a/source/test/mocks/SystemMock.h +++ b/source/test/mocks/SystemMock.h @@ -19,7 +19,9 @@ #include #include - +#include +#include +#include #include "telemetry2_0.h" @@ -30,11 +32,26 @@ class SystemMock MOCK_METHOD(int, unlink, (const char *str), ()); MOCK_METHOD(int, access, (const char *pathname, int mode), ()); MOCK_METHOD(int, remove, (const char *pathname), ()); + MOCK_METHOD(int, inotify_init1, (int flags), ()); + MOCK_METHOD(int, inotify_add_watch, (int fd, const char *pathname, uint32_t mask), ()); + MOCK_METHOD(int, inotify_rm_watch, (int fd, int wd), ()); + MOCK_METHOD(int, clock_gettime, (clockid_t clk_id, struct timespec *tp), ()); + MOCK_METHOD(int, select, (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout), ()); }; extern SystemMock* g_systemMock; +/* Separate enable flags for clock_gettime/select mocking — + * these must NOT be globally intercepted when g_systemMock is active + * because gtest itself calls clock_gettime for timing. */ +extern bool g_mockClockGettime; +extern bool g_mockSelect; extern "C" int system(const char* cmd); extern "C" int unlink(const char* str); extern "C" int access(const char* pathname, int mode); extern "C" int remove(const char* pathname); +extern "C" int inotify_init1(int flags); +extern "C" int inotify_add_watch(int fd, const char *pathname, uint32_t mask); +extern "C" int inotify_rm_watch(int fd, int wd); +extern "C" int clock_gettime(clockid_t clk_id, struct timespec *tp); +extern "C" int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);