From cb3bbcc958a743fc50888d7e25f17ad4d06e2c61 Mon Sep 17 00:00:00 2001 From: tabbas651 Date: Wed, 30 Sep 2026 18:24:59 -0400 Subject: [PATCH] TCXB7-7251: Fix marker loss on in-place log truncation Log upload backs up and truncates monitored files (e.g. wifihealth.txt, PARODUSlog.txt) in place without always producing a rotated .1 file. The stored seek value from the previous collection cycle then points past the truncated file's new size. getFileDeltaInMemMapAndSearch() previously treated this the same as an unrecoverable rotation with no backup and returned NULL, silently dropping every marker sourced from that file for one report cycle (e.g. Total_5G_clients_split, SYS_INFO_LowQosEvntUpload). Add a truncated check (seek_value > sb.st_size) and, when no rotated file is available, read the entire current file from offset 0 instead of skipping the collection, so markers are captured in the same cycle immediately following log upload. --- source/dcautil/dca.c | 29 +++++++++- source/test/dcautils/dcautilTest.cpp | 85 ++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 3 deletions(-) diff --git a/source/dcautil/dca.c b/source/dcautil/dca.c index 498258d8..cdd07dd2 100644 --- a/source/dcautil/dca.c +++ b/source/dcautil/dca.c @@ -1055,6 +1055,9 @@ static FileDescriptor* getFileDeltaInMemMapAndSearch(const int fd, const off_t s off_t offset_in_page_size_multiple ; unsigned int bytes_ignored = 0, bytes_ignored_main = 0, bytes_ignored_rotated = 0; off_t main_fsize = 0, rotated_fsize = 0; + // Log upload backs up and truncates in place, so the stored seek no longer refers + // to any data still present in this file. + const bool truncated = (seek_value > sb.st_size); // Find the nearest multiple of page size if (seek_value > 0 && PAGESIZE > 0) { @@ -1186,9 +1189,19 @@ static FileDescriptor* getFileDeltaInMemMapAndSearch(const int fd, const off_t s // 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); + if(truncated) + { + // Stale seek points past the truncated file - read all of it + main_fsize = sb.st_size; + bytes_ignored_main = 0; + addrcf = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, tmp_fd, 0); + } + else + { + 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); } if(tmp_rd != -1) @@ -1216,6 +1229,16 @@ static FileDescriptor* getFileDeltaInMemMapAndSearch(const int fd, const off_t s bytes_ignored_main = bytes_ignored; main_fsize = sb.st_size - seek_value; } + else if(truncated) + { + // Truncated in place with no rotated file to fall back on - read the + // entire current file so its markers are still collected this cycle + T2Info("Log file %s truncated (seek %jd > size %jd), reading entire current file\n", + logFile, (intmax_t)seek_value, (intmax_t)sb.st_size); + addrcf = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, tmp_fd, 0); + bytes_ignored_main = 0; + main_fsize = sb.st_size; + } else { diff --git a/source/test/dcautils/dcautilTest.cpp b/source/test/dcautils/dcautilTest.cpp index a8113dff..f54077ab 100644 --- a/source/test/dcautils/dcautilTest.cpp +++ b/source/test/dcautils/dcautilTest.cpp @@ -1771,6 +1771,91 @@ TEST_F(dcaTestFixture, getDCAResultsInVector_MultiRotation_IntermediateFile) Vector_Destroy(vecMarkerList, freeGMarker); } +// TCXB7-7251: PARODUSlog.txt truncated in place by log upload with no rotated +// backup available - stale seek must not cause the marker to be dropped. +TEST_F(dcaTestFixture, getDCAResultsInVector_TruncatedInPlace_NoRotatedFile) +{ + GrepSeekProfile *gsProfile = (GrepSeekProfile *)malloc(sizeof(GrepSeekProfile)); + gsProfile->logFileSeekMap = hash_map_create(); + gsProfile->execCounter = 2; + long *tempnum; + double val = 5000; + tempnum = (long *)malloc(sizeof(long)); + *tempnum = (long)val; + hash_map_put(gsProfile->logFileSeekMap, strdup("PARODUSlog.txt"), (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_LowQosEvntUpload"); + marker->searchString = strdup("PARODUS: Low qos event"); + marker->trimParam = true; + marker->regexParam = strdup("[0-9]+"); + marker->logFile = strdup("PARODUSlog.txt"); + marker->skipFreq = 0; + marker->paramType = strdup("grep"); + marker->mType = MTYPE_COUNTER; + marker->u.count = 0; + marker->reportEmptyParam = true; + Vector_PushBack(vecMarkerList, (void*) marker); + + //freeFileDescriptor + EXPECT_CALL(*g_fileIOMock, munmap(_, _)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock, close(_)) + .Times(2) + .WillOnce(Return(0)) + .WillOnce(Return(0)); + + //getLogFileDescriptor opens the current (truncated) file, then + //getRotatedLogFileDescriptor fails to open the missing .1 file + EXPECT_CALL(*g_fileIOMock, open(_,_)) + .Times(2) + .WillOnce(Return(0)) + .WillOnce(Return(-1)); + EXPECT_CALL(*g_fileIOMock, fstat(_, _)) + .Times(2) + .WillOnce([](int fd, struct stat* statbuf) { + statbuf->st_size = 800; // Truncated file size, smaller than stored seek 5000 + return 0; + }) + .WillOnce([](int fd, struct stat* statbuf) { + statbuf->st_size = 800; + return 0; + }); + + //getDeltainmmapsearch - only the current file is staged, no rotated tmp file + EXPECT_CALL(*g_fileIOMock, mkstemp(_)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_systemMock, unlink(_)) + .Times(1) + .WillOnce(Return(0)); + EXPECT_CALL(*g_fileIOMock,sendfile(_,_,_,_)) + .Times(1) + .WillOnce(Return(800)); + EXPECT_CALL(*g_fileIOMock, mmap(_,_,_,_,_,_)) + .Times(1) + .WillOnce([](void *addr, size_t length, int prot, int flags, int fd, off_t offset) { + EXPECT_EQ(0, offset); // Entire current file read from offset 0 despite stale seek + const char* test_str = "PARODUS: Low qos event, send success callback and delete\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")); + EXPECT_EQ(1, marker->u.count); // Marker must still be captured despite the truncation + + hash_map_destroy(gsProfile->logFileSeekMap, free); + gsProfile->logFileSeekMap = NULL; + free(gsProfile); + Vector_Destroy(vecMarkerList, freeGMarker); +} + TEST_F(dcaTestFixture, T2InitProperties) { EXPECT_CALL(*g_fileIOMock, fopen(_,_))