diff --git a/source/dcautil/dca.c b/source/dcautil/dca.c index c31df381..80c481ee 100644 --- a/source/dcautil/dca.c +++ b/source/dcautil/dca.c @@ -386,28 +386,31 @@ int processTopPattern(char* profileName, Vector* topMarkerList, int profileExec * @return Returns the status of the operation. * @retval Returns -1 on failure, appropriate errorcode otherwise. */ -static int getLogSeekValue(hash_map_t *logSeekMap, const char *name, long *seek_value) +static int getLogSeekValue(hash_map_t *logSeekMap, const char *name, long *seek_value, ino_t *stored_inode) { T2Debug("%s ++in for file %s \n", __FUNCTION__, name); int rc = 0; if (logSeekMap) { - long *data = (long*) hash_map_get(logSeekMap, name) ; + LogSeekInfo *data = (LogSeekInfo*) hash_map_get(logSeekMap, name) ; if (data) { - *seek_value = *data ; + *seek_value = data->seekValue ; + *stored_inode = data->inode ; } else { T2Debug("data is null .. Setting seek value to 0 from getLogSeekValue \n"); *seek_value = 0 ; + *stored_inode = 0 ; } } else { T2Debug("logSeekMap is null .. Setting seek value to 0 \n"); *seek_value = 0 ; + *stored_inode = 0 ; } T2Debug("seekvalue for file %s is %ld\n", name, *seek_value); T2Debug("%s --out \n", __FUNCTION__); @@ -422,7 +425,7 @@ static int getLogSeekValue(hash_map_t *logSeekMap, const char *name, long *seek_ * @return Returns the status of the operation. * @retval Returns -1 on failure, appropriate errorcode otherwise. */ -static T2ERROR updateLogSeek(hash_map_t *logSeekMap, const char* logFileName, const long logfileSize) +static T2ERROR updateLogSeek(hash_map_t *logSeekMap, const char* logFileName, const long logfileSize, const ino_t inode) { T2Debug("%s ++in\n", __FUNCTION__); if(logSeekMap == NULL || logFileName == NULL) @@ -430,12 +433,13 @@ static T2ERROR updateLogSeek(hash_map_t *logSeekMap, const char* logFileName, co T2Error("Invalid or NULL arguments\n"); return T2ERROR_FAILURE; } - T2Debug("Adding seekvalue of %ld for %s to logSeekMap \n", logfileSize, logFileName); - long* val = (long *) malloc(sizeof(long)); + T2Debug("Adding seekvalue of %ld (inode: %lu) for %s to logSeekMap \n", logfileSize, (unsigned long)inode, logFileName); + LogSeekInfo* val = (LogSeekInfo *) malloc(sizeof(LogSeekInfo)); if(NULL != val) { - memset(val, 0, sizeof(long)); - *val = logfileSize; + memset(val, 0, sizeof(LogSeekInfo)); + val->seekValue = logfileSize; + val->inode = inode; hash_map_put(logSeekMap, strdup(logFileName), (void *)val, free); } else @@ -867,10 +871,11 @@ static int processPatternWithOptimizedFunction(GrepMarker* marker, FileDescripto return 0; } -static int getLogFileDescriptor(GrepSeekProfile* gsProfile, const char* logPath, const char* logFile, int old_fd, off_t* out_seek_value) +static int getLogFileDescriptor(GrepSeekProfile* gsProfile, const char* logPath, const char* logFile, int old_fd, off_t* out_seek_value, bool* out_inode_changed, ino_t* out_stored_inode) { long seek_value_from_map = 0; - getLogSeekValue(gsProfile->logFileSeekMap, logFile, &seek_value_from_map); + ino_t stored_inode = 0; + getLogSeekValue(gsProfile->logFileSeekMap, logFile, &seek_value_from_map, &stored_inode); if (old_fd != -1) { close(old_fd); @@ -912,14 +917,25 @@ static int getLogFileDescriptor(GrepSeekProfile* gsProfile, const char* logPath, } // Check if the file size matches the seek value from the map - if (sb.st_size == seek_value_from_map) + if (sb.st_size == seek_value_from_map && sb.st_ino == stored_inode) { T2Debug("The logfile size matches the seek value (%ld) for %s\n", seek_value_from_map, logFile); close(fd); return -1; // Consistent error return value } - updateLogSeek(gsProfile->logFileSeekMap, logFile, sb.st_size); + + // Detect inode change: if inode differs from stored value, the file was rotated + // This covers the corner case where new file grows past seek_value after rotation + *out_inode_changed = (stored_inode != 0 && sb.st_ino != stored_inode); + if (*out_inode_changed) + { + T2Info("Inode changed for %s (stored: %lu, current: %lu) - log rotation detected\n", + logFile, (unsigned long)stored_inode, (unsigned long)sb.st_ino); + } + + updateLogSeek(gsProfile->logFileSeekMap, logFile, sb.st_size, sb.st_ino); *out_seek_value = seek_value_from_map; + *out_stored_inode = stored_inode; return fd; } @@ -1000,7 +1016,7 @@ static void freeFileDescriptor(FileDescriptor* fileDescriptor) } } -static FileDescriptor* getFileDeltaInMemMapAndSearch(const int fd, const off_t seek_value, const char* logPath, const char* logFile, bool check_rotated ) +static FileDescriptor* getFileDeltaInMemMapAndSearch(const int fd, const off_t seek_value, const char* logPath, const char* logFile, bool check_rotated, bool inode_changed, ino_t stored_inode) { char *addrcf = NULL; char *addrrf = NULL; @@ -1065,7 +1081,9 @@ static FileDescriptor* getFileDeltaInMemMapAndSearch(const int fd, const off_t s return NULL; } - if(seek_value > sb.st_size || check_rotated == true) + // Inode change means rotation happened even if current file size > seek_value + // In this case, read the rotated file from seek_value and the entire current file + if(seek_value > sb.st_size || check_rotated == true || inode_changed == true) { int rd = getRotatedLogFileDescriptor(logPath, logFile); if (rd != -1 && fstat(rd, &rb) == 0 && rb.st_size > 0) @@ -1113,23 +1131,59 @@ static FileDescriptor* getFileDeltaInMemMapAndSearch(const int fd, const off_t s return NULL; } - if(rb.st_size > seek_value) + // Determine if the rotated file is the ORIGINAL file we previously read + // (its inode matches our stored inode) or an intermediate file from + // multiple rotations (different inode — we've never seen this content). + bool rotated_is_original = (stored_inode != 0 && rb.st_ino == stored_inode); + + if(rotated_is_original && rb.st_size > seek_value) { + // Rotated file IS the original — apply seek_value to skip already-read data + // Read unread tail of rotated + entire new current file rotated_fsize = rb.st_size - seek_value; main_fsize = sb.st_size; bytes_ignored_rotated = bytes_ignored; addrcf = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, tmp_fd, 0); addrrf = mmap(NULL, rb.st_size, PROT_READ, MAP_PRIVATE, tmp_rd, offset_in_page_size_multiple); } + else if(rotated_is_original && rb.st_size <= seek_value) + { + // Rotated file is original but we already read past its end — skip it + // Read entire new current file + rotated_fsize = 0; + main_fsize = sb.st_size; + bytes_ignored_main = 0; + addrcf = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, tmp_fd, 0); + addrrf = NULL; + close(tmp_rd); + tmp_rd = -1; + } + else if(inode_changed) + { + // Rotated file is NOT the original (multi-rotation: intermediate file) + // We have never seen this content — read BOTH files entirely from 0 + T2Info("Multi-rotation detected: rotated file inode %lu != stored %lu, reading both from 0\n", + (unsigned long)rb.st_ino, (unsigned long)stored_inode); + rotated_fsize = rb.st_size; + main_fsize = sb.st_size; + bytes_ignored_main = 0; + addrcf = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, tmp_fd, 0); + addrrf = mmap(NULL, rb.st_size, PROT_READ, MAP_PRIVATE, tmp_rd, 0); + } else { + // No inode change (seek_value > sb.st_size or check_rotated path) + // Legacy behavior: read entire rotated + current from seek rotated_fsize = rb.st_size; main_fsize = sb.st_size - seek_value; bytes_ignored_main = bytes_ignored; addrcf = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, tmp_fd, offset_in_page_size_multiple); addrrf = mmap(NULL, rb.st_size, PROT_READ, MAP_PRIVATE, tmp_rd, 0); } - close(tmp_rd); + if(tmp_rd != -1) + { + close(tmp_rd); + } close(rd); rd = -1; } @@ -1137,7 +1191,15 @@ static FileDescriptor* getFileDeltaInMemMapAndSearch(const int fd, const off_t s { T2Debug("Error opening rotated file. Start search in current file\n"); T2Debug("File size rounded to nearest page size used for offset read: %jd bytes\n", (intmax_t)offset_in_page_size_multiple); - if(seek_value < sb.st_size) + if(inode_changed) + { + // Inode changed but rotated file unavailable - read entire current file + T2Info("Rotated file unavailable after inode change, reading entire current file\n"); + addrcf = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, tmp_fd, 0); + bytes_ignored_main = 0; + main_fsize = sb.st_size; + } + else if(seek_value < sb.st_size) { addrcf = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, tmp_fd, offset_in_page_size_multiple); bytes_ignored_main = bytes_ignored; @@ -1316,7 +1378,9 @@ static int parseMarkerListOptimized(GrepSeekProfile *gsProfile, Vector * ip_vMar // Get a valid file descriptor for the current log file off_t seek_value = 0; - fd = getLogFileDescriptor(gsProfile, logPath, log_file_for_this_iteration, fd, &seek_value); + bool inode_changed = false; + ino_t prev_stored_inode = 0; + fd = getLogFileDescriptor(gsProfile, logPath, log_file_for_this_iteration, fd, &seek_value, &inode_changed, &prev_stored_inode); prevfile = updateFilename(prevfile, log_file_for_this_iteration); if (fd == -1) { @@ -1324,7 +1388,7 @@ static int parseMarkerListOptimized(GrepSeekProfile *gsProfile, Vector * ip_vMar continue; } - fileDescriptor = getFileDeltaInMemMapAndSearch(fd, seek_value, logPath, log_file_for_this_iteration, check_rotated_logs); + fileDescriptor = getFileDeltaInMemMapAndSearch(fd, seek_value, logPath, log_file_for_this_iteration, check_rotated_logs, inode_changed, prev_stored_inode); if (fd != -1) { @@ -1412,7 +1476,7 @@ extractUnixTimestampFunc extractUnixTimestampFuncCallback(void) return extractUnixTimestamp; } -typedef T2ERROR (*updateLogSeekFunc)(hash_map_t *, const char *, const long); +typedef T2ERROR (*updateLogSeekFunc)(hash_map_t *, const char *, const long, const ino_t); updateLogSeekFunc updateLogSeekFuncCallback(void) { return updateLogSeek; diff --git a/source/dcautil/dcautil.c b/source/dcautil/dcautil.c index 5f7185c6..a6cc0c95 100644 --- a/source/dcautil/dcautil.c +++ b/source/dcautil/dcautil.c @@ -104,13 +104,30 @@ T2ERROR saveSeekConfigtoFile(char* profileName, GrepSeekProfile *ProfileSeekMap) cJSON *valArray = cJSON_CreateArray(); for (unsigned int i = 0; i < count ; i++) { - char *logFileName = NULL; - long *seekvalue = NULL; - logFileName = hash_map_lookupKey(logfileMap, i); - seekvalue = hash_map_lookup(logfileMap, i); + char *logFileName = hash_map_lookupKey(logfileMap, i); + LogSeekInfo *seekInfo = (LogSeekInfo *)hash_map_lookup(logfileMap, i); + if (!logFileName || !seekInfo) + { + T2Warning("Skipping entry %u: NULL key or value in logFileSeekMap\n", i); + continue; + } cJSON *logFileObj = cJSON_CreateObject(); - cJSON_AddNumberToObject(logFileObj, logFileName, (double)*seekvalue); - cJSON_AddItemToArray(valArray, logFileObj); + if (!logFileObj) + { + T2Error("Failed to allocate cJSON object for seek entry\n"); + continue; + } + cJSON_AddNumberToObject(logFileObj, "seekValue", (double)seekInfo->seekValue); + cJSON_AddNumberToObject(logFileObj, "inode", (double)seekInfo->inode); + cJSON *wrapper = cJSON_CreateObject(); + if (!wrapper) + { + T2Error("Failed to allocate cJSON wrapper object\n"); + cJSON_Delete(logFileObj); + continue; + } + cJSON_AddItemToObject(wrapper, logFileName, logFileObj); + cJSON_AddItemToArray(valArray, wrapper); } char *jsonReport = cJSON_PrintUnformatted(valArray); if(T2ERROR_SUCCESS != saveConfigToFile(SEEKFOLDER, profileName, jsonReport)) @@ -170,15 +187,32 @@ T2ERROR loadSavedSeekConfig(char *profileName, GrepSeekProfile *ProfileSeekMap) if (key != NULL) { - // Check the value type and print it - if (cJSON_IsNumber(value)) + // New format: {"logFileName": {"seekValue": N, "inode": M}} + if (cJSON_IsObject(value)) + { + cJSON *seekVal = cJSON_GetObjectItem(value, "seekValue"); + cJSON *inodeVal = cJSON_GetObjectItem(value, "inode"); + if (cJSON_IsNumber(seekVal)) + { + LogSeekInfo *info = (LogSeekInfo *)malloc(sizeof(LogSeekInfo)); + if (info) + { + info->seekValue = (long)seekVal->valuedouble; + info->inode = (inodeVal && cJSON_IsNumber(inodeVal)) ? (ino_t)inodeVal->valuedouble : 0; + hash_map_put(ProfileSeekMap->logFileSeekMap, strdup(key), info, free); + } + } + } + // Legacy format: {"logFileName": N} + else if (cJSON_IsNumber(value)) { - long *tempnum; - double val = value->valuedouble; - tempnum = (long *)malloc(sizeof(long)); - *tempnum = (long)val; - hash_map_put(ProfileSeekMap->logFileSeekMap, strdup(key), tempnum, NULL); - //printf("Key: %s, Value: %ld\n", key, *tempnum); + LogSeekInfo *info = (LogSeekInfo *)malloc(sizeof(LogSeekInfo)); + if (info) + { + info->seekValue = (long)value->valuedouble; + info->inode = 0; // No inode info in legacy format + hash_map_put(ProfileSeekMap->logFileSeekMap, strdup(key), info, free); + } } } diff --git a/source/dcautil/legacyutils.h b/source/dcautil/legacyutils.h index b9214cb2..3dfe83ab 100644 --- a/source/dcautil/legacyutils.h +++ b/source/dcautil/legacyutils.h @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "t2collection.h" @@ -63,10 +64,17 @@ #endif /* - * Used to store the log file seek position for each profile. + * Used to store the log file seek position and inode for each profile. * The key is the profile name and - * value is a hash_map_t which contains the log file name as key and the seek position as value. + * value is a hash_map_t which contains the log file name as key and LogSeekInfo as value. + * The inode is used to detect log rotation even when the new file grows past the previous seek value. */ +typedef struct _LogSeekInfo +{ + long seekValue; + ino_t inode; +} LogSeekInfo; + typedef struct _GrepSeekProfile { hash_map_t *logFileSeekMap; diff --git a/source/test/dcautils/dcautilTest.cpp b/source/test/dcautils/dcautilTest.cpp index 1f1daadd..247b1e6a 100644 --- a/source/test/dcautils/dcautilTest.cpp +++ b/source/test/dcautils/dcautilTest.cpp @@ -640,10 +640,10 @@ TEST_F(dcaTestFixture, saveSeekConfigtoFile) GrepSeekProfile *gsProfile = (GrepSeekProfile *)malloc(sizeof(GrepSeekProfile)); gsProfile->logFileSeekMap = hash_map_create(); gsProfile->execCounter = 0; - long *tempnum; - double val = 123456; - tempnum = (long *)malloc(sizeof(long)); - *tempnum = (long)val; + LogSeekInfo *tempnum; + tempnum = (LogSeekInfo *)malloc(sizeof(LogSeekInfo)); + tempnum->seekValue = 123456; + tempnum->inode = 12345; hash_map_put(gsProfile->logFileSeekMap, strdup("t2_log.txt"), (void*)tempnum, free); FILE* fp = (FILE*)NULL; EXPECT_CALL(*g_fileIOMock, fopen(_,_)) @@ -1170,10 +1170,10 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_1) GrepSeekProfile *gsProfile = (GrepSeekProfile *)malloc(sizeof(GrepSeekProfile)); gsProfile->logFileSeekMap = hash_map_create(); gsProfile->execCounter = 0; - long *tempnum; - double val = 1234; - tempnum = (long *)malloc(sizeof(long)); - *tempnum = (long)val; + LogSeekInfo *tempnum; + tempnum = (LogSeekInfo *)malloc(sizeof(LogSeekInfo)); + tempnum->seekValue = 1234; + tempnum->inode = 12345; hash_map_put(gsProfile->logFileSeekMap, strdup("t2_log.txt"), (void*)tempnum, free); Vector* vecMarkerList = NULL; @@ -1249,10 +1249,10 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_2) GrepSeekProfile *gsProfile = (GrepSeekProfile *)malloc(sizeof(GrepSeekProfile)); gsProfile->logFileSeekMap = hash_map_create(); gsProfile->execCounter = 0; - long *tempnum; - double val = 1234; - tempnum = (long *)malloc(sizeof(long)); - *tempnum = (long)val; + LogSeekInfo *tempnum; + tempnum = (LogSeekInfo *)malloc(sizeof(LogSeekInfo)); + tempnum->seekValue = 1234; + tempnum->inode = 12345; hash_map_put(gsProfile->logFileSeekMap, strdup("t2_log.txt"), (void*)tempnum, free); Vector* vecMarkerList = NULL; @@ -1330,10 +1330,10 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_3) GrepSeekProfile *gsProfile = (GrepSeekProfile *)malloc(sizeof(GrepSeekProfile)); gsProfile->logFileSeekMap = hash_map_create(); gsProfile->execCounter = 1; - long *tempnum; - double val = 1234; - tempnum = (long *)malloc(sizeof(long)); - *tempnum = (long)val; + LogSeekInfo *tempnum; + tempnum = (LogSeekInfo *)malloc(sizeof(LogSeekInfo)); + tempnum->seekValue = 1234; + tempnum->inode = 12345; hash_map_put(gsProfile->logFileSeekMap, strdup("t2_log.txt"), (void*)tempnum, free); Vector* vecMarkerList = NULL; @@ -1436,10 +1436,10 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_Accum) GrepSeekProfile *gsProfile = (GrepSeekProfile *)malloc(sizeof(GrepSeekProfile)); gsProfile->logFileSeekMap = hash_map_create(); gsProfile->execCounter = 1; - long *tempnum; - double val = 1234; - tempnum = (long *)malloc(sizeof(long)); - *tempnum = (long)val; + LogSeekInfo *tempnum; + tempnum = (LogSeekInfo *)malloc(sizeof(LogSeekInfo)); + tempnum->seekValue = 1234; + tempnum->inode = 12345; hash_map_put(gsProfile->logFileSeekMap, strdup("t2_log.txt"), (void*)tempnum, free); Vector* vecMarkerList = NULL; @@ -1527,6 +1527,250 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_Accum) Vector_Destroy(vecMarkerList, freeGMarker); } +/** + * Test: Log rotation corner case — new file grows past seek_value after rotation. + * + * Scenario: + * - Stored: seekValue=5000, inode=11111 for "Consolelog.txt.0" + * - Current "Consolelog.txt.0": size=8000, inode=22222 (DIFFERENT inode → rotation detected) + * - Rotated "Consolelog.txt.1": size=7000 (> 5000 → has unread data from offset 5000) + * + * Expected: reads rotated file from seek_value (5000) AND reads entire new current file. + * This verifies the inode-based rotation detection handles the flooding-logs corner case. + */ +TEST_F(dcaTestFixture, getDCAResultsInVector_InodeChanged_NewFileGrowsPastSeek) +{ + GrepSeekProfile *gsProfile = (GrepSeekProfile *)malloc(sizeof(GrepSeekProfile)); + gsProfile->logFileSeekMap = hash_map_create(); + gsProfile->execCounter = 1; + LogSeekInfo *tempnum; + tempnum = (LogSeekInfo *)malloc(sizeof(LogSeekInfo)); + tempnum->seekValue = 5000; + tempnum->inode = 11111; // Old inode before rotation + hash_map_put(gsProfile->logFileSeekMap, strdup("Consolelog.txt.0"), (void*)tempnum, free); + + Vector* vecMarkerList = NULL; + Vector_Create(&vecMarkerList); + GrepMarker* marker = (GrepMarker*) malloc(sizeof(GrepMarker)); + memset(marker, 0, sizeof(GrepMarker)); + marker->markerName = strdup("SYS_INFO_ROTATION_TEST"); + marker->searchString = strdup("RotationMarker"); + marker->trimParam = true; + marker->regexParam = strdup("[0-9]+"); + marker->logFile = strdup("Consolelog.txt.0"); // Must match hash_map key + marker->skipFreq = 0; + marker->paramType = strdup("grep"); + marker->mType = MTYPE_COUNTER; + marker->u.count = 0; + marker->reportEmptyParam = true; + Vector_PushBack(vecMarkerList, (void*) marker); + + // munmap for both main and rotated mmap regions + EXPECT_CALL(*g_fileIOMock, munmap(_, _)) + .Times(2) + .WillRepeatedly(Return(0)); + + // close: tmp_rd, rd (rotated), tmp_fd (main), main fd + EXPECT_CALL(*g_fileIOMock, close(_)) + .Times(4) + .WillRepeatedly(Return(0)); + + // open: main file in getLogFileDescriptor, rotated file in getRotatedLogFileDescriptor + EXPECT_CALL(*g_fileIOMock, open(_,_)) + .Times(2) + .WillRepeatedly(Return(3)); + + // fstat calls: + // 1: getLogFileDescriptor on main → size=8000, inode=22222 (different from stored 11111) + // 2: getFileDeltaInMemMapAndSearch on main → size=8000 + // 3: getRotatedLogFileDescriptor on rotated → size=7000 + // 4: getFileDeltaInMemMapAndSearch on rotated → size=7000 + EXPECT_CALL(*g_fileIOMock, fstat(_, _)) + .Times(4) + .WillOnce([](int fd, struct stat* statbuf) { + statbuf->st_size = 8000; + statbuf->st_ino = 22222; // Different inode → rotation detected + return 0; + }) + .WillOnce([](int fd, struct stat* statbuf) { + statbuf->st_size = 8000; + statbuf->st_ino = 22222; + return 0; + }) + .WillOnce([](int fd, struct stat* statbuf) { + statbuf->st_size = 7000; // Rotated file (old log) > seek_value (5000) + statbuf->st_ino = 11111; // Old inode + return 0; + }) + .WillOnce([](int fd, struct stat* statbuf) { + statbuf->st_size = 7000; + statbuf->st_ino = 11111; + return 0; + }); + + // mkstemp: one for main tmp, one for rotated tmp + EXPECT_CALL(*g_fileIOMock, mkstemp(_)) + .Times(2) + .WillRepeatedly(Return(4)); + EXPECT_CALL(*g_systemMock, unlink(_)) + .Times(2) + .WillRepeatedly(Return(0)); + + // sendfile: main (8000 bytes), rotated (7000 bytes) + EXPECT_CALL(*g_fileIOMock, sendfile(_,_,_,_)) + .Times(2) + .WillOnce(Return(8000)) + .WillOnce(Return(7000)); + + // mmap: main file (entire, offset 0) + rotated file (from page-aligned seek offset) + // Both contain the marker — verifies both regions are searched + EXPECT_CALL(*g_fileIOMock, mmap(_,_,_,_,_,_)) + .Times(2) + .WillOnce([](void *addr, size_t length, int prot, int flags, int fd, off_t offset) { + // Main file (new): mapped from offset 0 (entire file read) + const char* test_str = "New log data: RotationMarker value 42 found in new file\n"; + char* mapped_mem = (char*)malloc(length); + memset(mapped_mem, 0, length); + strncpy(mapped_mem, test_str, length - 1); + return (void*)mapped_mem; + }) + .WillOnce([](void *addr, size_t length, int prot, int flags, int fd, off_t offset) { + // Rotated file: mapped from seek offset (unread portion of old file) + const char* test_str = "Old unread data: RotationMarker value 99 in rotated file\n"; + char* mapped_mem = (char*)malloc(length); + memset(mapped_mem, 0, length); + strncpy(mapped_mem, test_str, length - 1); + return (void*)mapped_mem; + }); + + EXPECT_EQ(0, getDCAResultsInVector(gsProfile, vecMarkerList, false, "/opt/logs")); + + // Verify the marker was found in BOTH files (count should be 2) + EXPECT_EQ(2, marker->u.count); + + hash_map_destroy(gsProfile->logFileSeekMap, free); + gsProfile->logFileSeekMap = NULL; + free(gsProfile); + Vector_Destroy(vecMarkerList, freeGMarker); +} + +/** + * Test: Multi-rotation corner case — rotated file is NOT the original. + * + * Scenario (from real device logs): + * - Stored: seekValue=5000, inode=11111 (original file, now at .log.2 or deeper) + * - Current "Consolelog.txt.0": size=8000, inode=22222 (newest file, rotation detected) + * - Rotated "Consolelog.txt.1": size=3000, inode=33333 (INTERMEDIATE file, NOT the original) + * + * Expected: reads ENTIRE rotated file from 0 AND ENTIRE new current file from 0. + * Without the fix, seek_value would be incorrectly applied to the intermediate + * rotated file, causing data loss (as seen with missing WPE_INFO_MigStatus_split). + */ +TEST_F(dcaTestFixture, getDCAResultsInVector_MultiRotation_IntermediateFile) +{ + GrepSeekProfile *gsProfile = (GrepSeekProfile *)malloc(sizeof(GrepSeekProfile)); + gsProfile->logFileSeekMap = hash_map_create(); + gsProfile->execCounter = 1; + LogSeekInfo *tempnum; + tempnum = (LogSeekInfo *)malloc(sizeof(LogSeekInfo)); + tempnum->seekValue = 5000; + tempnum->inode = 11111; // Original file inode (now at .log.2 or deeper) + hash_map_put(gsProfile->logFileSeekMap, strdup("Consolelog.txt.0"), (void*)tempnum, free); + + Vector* vecMarkerList = NULL; + Vector_Create(&vecMarkerList); + GrepMarker* marker = (GrepMarker*) malloc(sizeof(GrepMarker)); + memset(marker, 0, sizeof(GrepMarker)); + marker->markerName = strdup("WPE_INFO_MigStatus"); + marker->searchString = strdup("Migration Status"); + marker->trimParam = true; + marker->regexParam = strdup("[A-Z_]+"); + marker->logFile = strdup("Consolelog.txt.0"); + marker->skipFreq = 0; + marker->paramType = strdup("grep"); + marker->mType = MTYPE_COUNTER; + marker->u.count = 0; + marker->reportEmptyParam = true; + Vector_PushBack(vecMarkerList, (void*) marker); + + EXPECT_CALL(*g_fileIOMock, munmap(_, _)) + .Times(2) + .WillRepeatedly(Return(0)); + EXPECT_CALL(*g_fileIOMock, close(_)) + .Times(4) + .WillRepeatedly(Return(0)); + EXPECT_CALL(*g_fileIOMock, open(_,_)) + .Times(2) + .WillRepeatedly(Return(3)); + + // fstat: main inode=22222, rotated inode=33333 (DIFFERENT from stored 11111) + EXPECT_CALL(*g_fileIOMock, fstat(_, _)) + .Times(4) + .WillOnce([](int fd, struct stat* statbuf) { + statbuf->st_size = 8000; + statbuf->st_ino = 22222; // Current file — different from stored + return 0; + }) + .WillOnce([](int fd, struct stat* statbuf) { + statbuf->st_size = 8000; + statbuf->st_ino = 22222; + return 0; + }) + .WillOnce([](int fd, struct stat* statbuf) { + statbuf->st_size = 3000; // Intermediate rotated file + statbuf->st_ino = 33333; // DIFFERENT from stored 11111 — multi-rotation! + return 0; + }) + .WillOnce([](int fd, struct stat* statbuf) { + statbuf->st_size = 3000; + statbuf->st_ino = 33333; + return 0; + }); + + EXPECT_CALL(*g_fileIOMock, mkstemp(_)) + .Times(2) + .WillRepeatedly(Return(4)); + EXPECT_CALL(*g_systemMock, unlink(_)) + .Times(2) + .WillRepeatedly(Return(0)); + EXPECT_CALL(*g_fileIOMock, sendfile(_,_,_,_)) + .Times(2) + .WillOnce(Return(8000)) + .WillOnce(Return(3000)); + + // Both files mapped from offset 0 — multi-rotation reads everything + EXPECT_CALL(*g_fileIOMock, mmap(_,_,_,_,_,_)) + .Times(2) + .WillOnce([](void *addr, size_t length, int prot, int flags, int fd, off_t offset) { + // Main file: entire new file read from offset 0 + EXPECT_EQ(0, offset); + const char* test_str = "New file: Migration Status is COMPLETED in current\n"; + char* mapped_mem = (char*)malloc(length); + memset(mapped_mem, 0, length); + strncpy(mapped_mem, test_str, length - 1); + return (void*)mapped_mem; + }) + .WillOnce([](void *addr, size_t length, int prot, int flags, int fd, off_t offset) { + // Rotated (intermediate) file: read entirely from offset 0 + EXPECT_EQ(0, offset); + const char* test_str = "Intermediate: Migration Status is STARTED in rotated\n"; + char* mapped_mem = (char*)malloc(length); + memset(mapped_mem, 0, length); + strncpy(mapped_mem, test_str, length - 1); + return (void*)mapped_mem; + }); + + EXPECT_EQ(0, getDCAResultsInVector(gsProfile, vecMarkerList, false, "/opt/logs")); + + // Marker found in BOTH files (no data lost from intermediate file) + EXPECT_EQ(2, marker->u.count); + + hash_map_destroy(gsProfile->logFileSeekMap, free); + gsProfile->logFileSeekMap = NULL; + free(gsProfile); + Vector_Destroy(vecMarkerList, freeGMarker); +} + TEST_F(dcaTestFixture, T2InitProperties) { EXPECT_CALL(*g_fileIOMock, fopen(_,_)) @@ -1553,10 +1797,10 @@ TEST_F(dcaTestFixture, getGrepResults_success) GrepSeekProfile *gsProfile = (GrepSeekProfile *)malloc(sizeof(GrepSeekProfile)); gsProfile->logFileSeekMap = hash_map_create(); gsProfile->execCounter = 0; - long *tempnum; - double val = 1234; - tempnum = (long *)malloc(sizeof(long)); - *tempnum = (long)val; + LogSeekInfo *tempnum; + tempnum = (LogSeekInfo *)malloc(sizeof(LogSeekInfo)); + tempnum->seekValue = 1234; + tempnum->inode = 12345; hash_map_put(gsProfile->logFileSeekMap, strdup("t2_log.txt"), (void*)tempnum, free); Vector* vecMarkerList = NULL; @@ -1632,7 +1876,7 @@ typedef const char *(*strnstrFunc)(const char *, const char *, size_t); strnstrFunc strnstrFuncCallback(void); typedef time_t (*extractUnixTimestampFunc)(const char*, size_t); extractUnixTimestampFunc extractUnixTimestampFuncCallback(void); -typedef T2ERROR (*updateLogSeekFunc)(hash_map_t *, const char *, const long); +typedef T2ERROR (*updateLogSeekFunc)(hash_map_t *, const char *, const long, const ino_t); updateLogSeekFunc updateLogSeekFuncCallback(void); } TEST(StaticStrnstrFunc, CoversMainBranches) @@ -1703,11 +1947,11 @@ TEST(StaticUpdateLogSeekFunc, CoversNullBranches) ASSERT_NE(fn, nullptr); // First branch: logSeekMap == NULL - EXPECT_EQ(fn(NULL, "dummy.log", 100), T2ERROR_FAILURE); + EXPECT_EQ(fn(NULL, "dummy.log", 100, 12345), T2ERROR_FAILURE); // Second branch: logFileName == NULL hash_map_t dummyMap; - EXPECT_EQ(fn(&dummyMap, NULL, 100), T2ERROR_FAILURE); + EXPECT_EQ(fn(&dummyMap, NULL, 100, 12345), T2ERROR_FAILURE); // Additional test for a stub/empty map and filename to reach further code // (Will continue past the NULL check; optionally extend to later logic in the function)