diff --git a/source/dcautil/dca.c b/source/dcautil/dca.c index 498258d85..cdd07dd24 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 a8113dffb..f54077ab3 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(_,_))