diff --git a/src/rrdCommon.h b/src/rrdCommon.h index b91ff6f7..e2ee5a20 100644 --- a/src/rrdCommon.h +++ b/src/rrdCommon.h @@ -67,6 +67,7 @@ extern "C" #define BUF_LEN_128 128 #define APPEND_SUFFIX "_apnd" +#define RRD_SUFFIX_PATH "/tmp/rrd_suffix.txt" /* Enum for Messages Queue*/ typedef enum { diff --git a/src/rrdEventProcess.c b/src/rrdEventProcess.c index 5164e783..e704076e 100644 --- a/src/rrdEventProcess.c +++ b/src/rrdEventProcess.c @@ -79,7 +79,17 @@ void processIssueTypeEvent(data_buf *rbuf) cmdBuff = (data_buf *)malloc(sizeof(data_buf)); if (cmdBuff) { - dataMsgLen = strlen(cmdMap[index]) + 1; + char base[BUF_LEN_128] = {0}; + char local_suffix[BUF_LEN_128] = {0}; + split_issue_type(cmdMap[index], base, sizeof(base), local_suffix, sizeof(local_suffix)); + if (base[0] == '\0') + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Invalid IssueType [%s] - empty base after split, skipping\n", __FUNCTION__, __LINE__, cmdMap[index]); + free(cmdBuff); + cmdBuff = NULL; + continue; + } + dataMsgLen = strlen(base) + 1; RRD_data_buff_init(cmdBuff, EVENT_MSG, RRD_DEEPSLEEP_INVALID_DEFAULT); /* Setting Deafult Values*/ cmdBuff->inDynamic = rbuf->inDynamic; if(cmdBuff->inDynamic) @@ -88,9 +98,20 @@ void processIssueTypeEvent(data_buf *rbuf) } cmdBuff->appendMode = rbuf->appendMode; cmdBuff->mdata = (char *)calloc(1, dataMsgLen); + /* Suffix is now persisted via file, no struct field needed */ if (cmdBuff->mdata) { - strncpy((char *)cmdBuff->mdata, cmdMap[index], dataMsgLen); + strncpy((char *)cmdBuff->mdata, base, dataMsgLen); + /* Only persist suffix if input contains an underscore (i.e., is not just the base name) */ + if (strchr(cmdMap[index], '_') && local_suffix[0] != '\0') + { + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: [DEBUG] Persisting suffix: '%s' from input: '%s' (index=%d)\n", __FUNCTION__, __LINE__, local_suffix, cmdMap[index], index); + persist_suffix_to_file(RRD_SUFFIX_PATH,local_suffix); + } + else + { + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: [DEBUG] Not persisting suffix for input: '%s' (index=%d)\n", __FUNCTION__, __LINE__, cmdMap[index], index); + } processIssueType(cmdBuff); } else @@ -628,7 +649,7 @@ static void freeParsedJson(cJSON *jsonParsed) /* * @function removeSpecialCharacterfromIssueTypeList * @brief Removes special characters from the issue type list, retaining only alphanumeric - * characters, commas, and periods. + * characters, commas, periods, underscores and hyphens * @param char *str - The string from which special characters will be removed. * @return void */ @@ -639,7 +660,7 @@ static void removeSpecialCharacterfromIssueTypeList(char *str) while (str[source] != '\0') { - if (isalnum(str[source]) || str[source] == ',' || str[source] == '.') + if (isalnum((unsigned char)str[source]) || str[source] == ',' || str[source] == '.' || str[source] == '_'|| str[source] == '-') { str[destination] = str[source]; ++destination; diff --git a/src/rrdJsonParser.c b/src/rrdJsonParser.c index e06d93ac..dd1a8f14 100644 --- a/src/rrdJsonParser.c +++ b/src/rrdJsonParser.c @@ -24,6 +24,8 @@ #include #include #include +#include +#include /* * @function removeSpecialChar @@ -46,6 +48,143 @@ void removeSpecialChar(char *str) } } +void persist_suffix_to_file(const char *filename, const char *suffix) +{ + int fd; + if (!filename) + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: persist_suffix_to_file called with NULL filename\n", __FUNCTION__, __LINE__); + return; + } + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, 0600); + if (fd < 0) + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to open '%s' for writing: %s\n", __FUNCTION__, __LINE__, filename, strerror(errno)); + return; + } + struct stat st; + if (fstat(fd, &st) != 0) + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: fstat failed on '%s': %s\n", __FUNCTION__, __LINE__, filename, strerror(errno)); + close(fd); + return; + } + if (!S_ISREG(st.st_mode)) + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: '%s' is not a regular file!\n", __FUNCTION__, __LINE__, filename); + close(fd); + return; + } + FILE *fp = fdopen(fd, "w"); + if (!fp) + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: fdopen failed on '%s': %s\n", __FUNCTION__, __LINE__, filename, strerror(errno)); + close(fd); + return; + } + if (suffix) + { + if (fputs(suffix, fp) == EOF) + { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: failed to write suffix to file '%s': %s\n", __FUNCTION__, __LINE__, filename, strerror(errno)); + } + } + fclose(fp); // This also closes the underlying fd +} + +void read_suffix_from_file_to_buf(const char *filename, char *buf, size_t buflen) +{ + if (!buf || buflen == 0 || !filename) { + return; + } + int fd = open(filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); + if (fd < 0) { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: Failed to open '%s' for reading: %s\n", __FUNCTION__, __LINE__, filename, strerror(errno)); + buf[0] = '\0'; + return; + } + struct stat st; + if (fstat(fd, &st) != 0) { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: fstat failed on '%s': %s\n", __FUNCTION__, __LINE__, filename, strerror(errno)); + close(fd); + buf[0] = '\0'; + return; + } + if (!S_ISREG(st.st_mode)) { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: '%s' is not a regular file!\n", __FUNCTION__, __LINE__, filename); + close(fd); + buf[0] = '\0'; + return; + } + FILE *fp = fdopen(fd, "r"); + if (!fp) { + RDK_LOG(RDK_LOG_ERROR, LOG_REMDEBUG, "[%s:%d]: fdopen failed on '%s': %s\n", __FUNCTION__, __LINE__, filename, strerror(errno)); + close(fd); + buf[0] = '\0'; + return; + } + if (fgets(buf, buflen, fp) == NULL) { + buf[0] = '\0'; + fclose(fp); + return; + } + fclose(fp); + size_t len = strlen(buf); + if (len > 0 && buf[len-1] == '\n') buf[len-1] = '\0'; +} + +/* + * @function split_issue_type + * @brief Utility to split base and suffix from issue type string. + * Example: Input: Device.DeviceTime_Search-b6877385-9463-45fc-b19d-a24d77fd0790 + * Output: base = Device.DeviceTime, suffix = _Search-b6877385-9463-45fc-b19d-a24d77fd0790 + * @param const char *input - The input string to split. + * @param char *base - Buffer to store the base part (before the first underscore). + * @param size_t base_len - Size of the base buffer. + * @param char *suffix - Buffer to store the suffix part (from the first underscore onwards). + * @param size_t suffix_len - Size of the suffix buffer. + * @return void + */ +void split_issue_type(const char *input, char *base, size_t base_len, char *suffix, size_t suffix_len) { + if (base && base_len > 0) + { + base[0] = '\0'; + } + if (suffix && suffix_len > 0) + { + suffix[0] = '\0'; + } + + if (!input || !base || !suffix) + { + return; + } + + if (base_len == 0 || suffix_len == 0) + { + return; + } + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: split_issue_type called with input='%s'\n", __FUNCTION__, __LINE__, input); + const char *underscore = strchr(input, '_'); + if (underscore) + { + size_t b_len = underscore - input; + if (b_len >= base_len) b_len = base_len - 1; + strncpy(base, input, b_len); + base[b_len] = '\0'; + strncpy(suffix, underscore, suffix_len - 1); + suffix[suffix_len - 1] = '\0'; + } + else + { + strncpy(base, input, base_len - 1); + base[base_len - 1] = '\0'; + suffix[0] = '\0'; + } + RDK_LOG(RDK_LOG_DEBUG, LOG_REMDEBUG, "[%s:%d]: split_issue_type result: base='%s', suffix='%s'\n", __FUNCTION__, __LINE__, base, suffix); +} + + /* * @function getParamcount * @brief Calculates the total number of nodes (elements) in the input string, excluding delimiters. @@ -516,6 +655,7 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Memory allocation failed for rfcbuf\n",__FUNCTION__,__LINE__); free(buff->mdata); // free rfc data free(buff->jsonPath); // free rrd path info + persist_suffix_to_file(RRD_SUFFIX_PATH,""); // Clear the suffix file on early exit return; } @@ -536,6 +676,7 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data free(buff->jsonPath); // free rrd path info + persist_suffix_to_file(RRD_SUFFIX_PATH,""); // Clear the suffix file on early exit return; } else @@ -576,7 +717,29 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf else { RDK_LOG(RDK_LOG_DEBUG,LOG_REMDEBUG,"[%s:%d]: Continue uploading Debug Report for %s from %s... \n",__FUNCTION__,__LINE__,buff->mdata,outdir); - status = uploadDebugoutput(outdir,buff->mdata); + // Use the persisted suffix from file for upload + char suffix[128] = {0}; + read_suffix_from_file_to_buf(RRD_SUFFIX_PATH , suffix, sizeof(suffix)); + char tarName[512] = {0}; + int tar_name_len = 0; + if (suffix[0] != '\0') { + tar_name_len = snprintf(tarName, sizeof(tarName), "%s%s", buff->mdata, suffix); + } + else + { + tar_name_len = snprintf(tarName, sizeof(tarName), "%s", buff->mdata); + } + if ((tar_name_len < 0) || ((size_t)tar_name_len >= sizeof(tarName))) + { + RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: Failed to build upload file name for %s. snprintf result:%d, buffer size:%zu\n", __FUNCTION__,__LINE__,buff->mdata,tar_name_len,sizeof(tarName)); + status = -1; + } + else + { + RDK_LOG(RDK_LOG_INFO, LOG_REMDEBUG, "[%s:%d]: [INFO] Tar file name for upload: '%s'\n", __FUNCTION__, __LINE__, tarName); + status = uploadDebugoutput(outdir, tarName); + } + if(status != 0) { RDK_LOG(RDK_LOG_ERROR,LOG_REMDEBUG,"[%s:%d]: RRD Upload Script Execution Failed!!! status:%d\n",__FUNCTION__,__LINE__,status); @@ -589,6 +752,7 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data free(buff->jsonPath); // free rrd path info + persist_suffix_to_file(RRD_SUFFIX_PATH,""); // Clear the suffix file after upload } else { @@ -596,6 +760,7 @@ void checkIssueNodeInfo(issueNodeData *issuestructNode, cJSON *jsoncfg, data_buf free(rfcbuf); // free duplicated rfc data free(buff->mdata); // free rfc data free(buff->jsonPath); // free rrd path info + persist_suffix_to_file(RRD_SUFFIX_PATH,""); // Clear the suffix file } } } diff --git a/src/rrdJsonParser.h b/src/rrdJsonParser.h index 29943610..d9d69315 100644 --- a/src/rrdJsonParser.h +++ b/src/rrdJsonParser.h @@ -47,6 +47,11 @@ issueData* getIssueCommandInfo(issueNodeData *issuestructNode, cJSON *jsoncfg,ch bool processAllDebugCommand(cJSON *jsoncfg, issueNodeData *issuestructNode, char *rfcbuf); bool processAllDeepSleepAwkMetricsCommands(cJSON *jsoncfg, issueNodeData *issuestructNode, char *rfcbuf); + +void persist_suffix_to_file(const char *filename, const char *suffix); +void read_suffix_from_file_to_buf(const char *filename, char *buf, size_t buflen); +void split_issue_type(const char *input, char *base, size_t base_len, char *suffix, size_t suffix_len); + #ifdef __cplusplus } #endif diff --git a/src/unittest/rrdUnitTestRunner.cpp b/src/unittest/rrdUnitTestRunner.cpp index 7b8c6c83..cecc2f01 100644 --- a/src/unittest/rrdUnitTestRunner.cpp +++ b/src/unittest/rrdUnitTestRunner.cpp @@ -5817,6 +5817,208 @@ TEST_F(RRDProfileHandlerTest, SetHandler_MaxLengthString) EXPECT_STREQ(RRDProfileCategory, maxString.c_str()); } +/* ====================== split_issue_type / persist_suffix / read_suffix ================*/ + +class SuffixUtilsTest : public ::testing::Test { +protected: + void TearDown() override { + // Remove suffix temp file to avoid state leakage between tests + remove("/tmp/rrd_suffix.txt"); + } +}; + +/* --------------- Test split_issue_type() from rrdJsonParser --------------- */ + +TEST_F(SuffixUtilsTest, SplitIssueType_WithUnderscore) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime_Search123", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_Search123"); +} + +TEST_F(SuffixUtilsTest, SplitIssueType_WithNoUnderscore) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("Device.DeviceTime", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, ""); +} + +TEST_F(SuffixUtilsTest, SplitIssueType_WithMultipleUnderscores) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("foo_bar_baz", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "foo"); + EXPECT_STREQ(suffix, "_bar_baz"); +} + +TEST_F(SuffixUtilsTest, SplitIssueType_StartsWithUnderscore) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("_suffix_only", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, "_suffix_only"); +} + +TEST_F(SuffixUtilsTest, SplitIssueType_EmptyString) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type("", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST_F(SuffixUtilsTest, SplitIssueType_NullInputDoesNotCrash) +{ + char base[64] = {0}; + char suffix[64] = {0}; + split_issue_type(NULL, base, sizeof(base), suffix, sizeof(suffix)); + // base and suffix should remain unmodified (empty) + EXPECT_STREQ(base, ""); + EXPECT_STREQ(suffix, ""); +} + +TEST_F(SuffixUtilsTest, SplitIssueType_NullBaseDoesNotCrash) +{ + char suffix[64] = {0}; + // Should not crash when base is NULL + split_issue_type("Device.DeviceTime_Search", NULL, 64, suffix, sizeof(suffix)); +} + +TEST_F(SuffixUtilsTest, SplitIssueType_NullSuffixDoesNotCrash) +{ + char base[64] = {0}; + // Should not crash when suffix is NULL + split_issue_type("Device.DeviceTime_Search", base, sizeof(base), NULL, 64); +} + +TEST_F(SuffixUtilsTest, SplitIssueType_BaseTruncation) +{ + char base[5] = {0}; + char suffix[64] = {0}; + // base_len=5 means max 4 chars + null terminator + split_issue_type("ABCDEF_suffix", base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "ABCD"); + EXPECT_STREQ(suffix, "_suffix"); +} + +TEST_F(SuffixUtilsTest, SplitIssueType_RealWorldInput) +{ + char base[128] = {0}; + char suffix[128] = {0}; + split_issue_type("Device.DeviceTime_Search-b6877385-9463-45fc-b19d-a24d77fd0790", + base, sizeof(base), suffix, sizeof(suffix)); + EXPECT_STREQ(base, "Device.DeviceTime"); + EXPECT_STREQ(suffix, "_Search-b6877385-9463-45fc-b19d-a24d77fd0790"); +} + +/* --------------- Test persist_suffix_to_file() from rrdJsonParser --------------- */ + +TEST_F(SuffixUtilsTest, PersistSuffix_NormalValue) +{ + persist_suffix_to_file("/tmp/rrd_suffix.txt","_Search123"); + // Verify the file was written + FILE *fp = fopen("/tmp/rrd_suffix.txt", "r"); + ASSERT_NE(fp, nullptr); + char buf[64] = {0}; + ASSERT_NE(fgets(buf, sizeof(buf), fp), nullptr); + fclose(fp); + EXPECT_STREQ(buf, "_Search123"); +} + +TEST_F(SuffixUtilsTest, PersistSuffix_EmptyString) +{ + persist_suffix_to_file("/tmp/rrd_suffix.txt",""); + FILE *fp = fopen("/tmp/rrd_suffix.txt", "r"); + ASSERT_NE(fp, nullptr); + char buf[64] = {0}; + // fgets may return NULL for an empty file - that's fine, buf stays empty + fgets(buf, sizeof(buf), fp); + fclose(fp); + EXPECT_STREQ(buf, ""); +} + +TEST_F(SuffixUtilsTest, PersistSuffix_NullDoesNotCrash) +{ + // Passing NULL should not crash; file should be created but empty + persist_suffix_to_file("/tmp/rrd_suffix.txt",NULL); + FILE *fp = fopen("/tmp/rrd_suffix.txt", "r"); + ASSERT_NE(fp, nullptr); + char buf[64] = {0}; + fgets(buf, sizeof(buf), fp); + fclose(fp); + EXPECT_STREQ(buf, ""); +} + +TEST_F(SuffixUtilsTest, PersistSuffix_OverwritesPreviousValue) +{ + persist_suffix_to_file("/tmp/rrd_suffix.txt","_OldSuffix"); + persist_suffix_to_file("/tmp/rrd_suffix.txt","_NewSuffix"); + char buf[64] = {0}; + read_suffix_from_file_to_buf("/tmp/rrd_suffix.txt",buf, sizeof(buf)); + EXPECT_STREQ(buf, "_NewSuffix"); +} + +/* --------------- Test read_suffix_from_file_to_buf() from rrdJsonParser --------------- */ + +TEST_F(SuffixUtilsTest, ReadSuffix_AfterPersist) +{ + persist_suffix_to_file("/tmp/rrd_suffix.txt","_Search123"); + char buf[64] = {0}; + read_suffix_from_file_to_buf("/tmp/rrd_suffix.txt",buf, sizeof(buf)); + EXPECT_STREQ(buf, "_Search123"); +} + +TEST_F(SuffixUtilsTest, ReadSuffix_WhenFileAbsent) +{ + remove("/tmp/rrd_suffix.txt"); + char buf[64] = {0}; + read_suffix_from_file_to_buf("/tmp/rrd_suffix.txt",buf, sizeof(buf)); + EXPECT_STREQ(buf, ""); +} + +TEST_F(SuffixUtilsTest, ReadSuffix_NullBufDoesNotCrash) +{ + persist_suffix_to_file("/tmp/rrd_suffix.txt","_test"); + // Should not crash when buf is NULL + read_suffix_from_file_to_buf("/tmp/rrd_suffix.txt",NULL, 64); +} + +TEST_F(SuffixUtilsTest, ReadSuffix_ZeroBuflenDoesNotCrash) +{ + persist_suffix_to_file("/tmp/rrd_suffix.txt","_test"); + char buf[64] = {0}; + // Should not crash when buflen is 0 + read_suffix_from_file_to_buf("/tmp/rrd_suffix.txt",buf, 0); +} + +TEST_F(SuffixUtilsTest, ReadSuffix_RoundTrip) +{ + const char *expected = "_Search-b6877385-9463-45fc-b19d-a24d77fd0790"; + persist_suffix_to_file("/tmp/rrd_suffix.txt",expected); + char buf[128] = {0}; + read_suffix_from_file_to_buf("/tmp/rrd_suffix.txt",buf, sizeof(buf)); + EXPECT_STREQ(buf, expected); +} + +TEST_F(SuffixUtilsTest, ReadSuffix_StripsTrailingNewline) +{ + // Write a value with a trailing newline to the file + FILE *fp = fopen("/tmp/rrd_suffix.txt", "w"); + ASSERT_NE(fp, nullptr); + fputs("_suffix\n", fp); + fclose(fp); + char buf[64] = {0}; + read_suffix_from_file_to_buf("/tmp/rrd_suffix.txt",buf, sizeof(buf)); + EXPECT_STREQ(buf, "_suffix"); +} +