From df656ae3376f089045b5c94b8de63740cfc150ee Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Thu, 17 Sep 2026 11:21:56 +0530 Subject: [PATCH 01/14] RDKB-63850: Migrate Upload Dumps Functionality from Shell Script to C [Extender] --- c_sourcecode/common/types.h | 5 +++++ c_sourcecode/src/config/config_manager.c | 3 ++- c_sourcecode/src/platform/platform.c | 15 +++++++++++++-- c_sourcecode/src/upload/upload.c | 20 ++++++++++++++++++-- unittest/config_manager_gtest.cpp | 2 ++ unittest/upload_gmock.cpp | 12 ++++++++++++ 6 files changed, 52 insertions(+), 5 deletions(-) diff --git a/c_sourcecode/common/types.h b/c_sourcecode/common/types.h index 97da589b..0395a2b2 100644 --- a/c_sourcecode/common/types.h +++ b/c_sourcecode/common/types.h @@ -44,6 +44,11 @@ #define MAX_CORE_FILES 4 #define LOGMAPPER_FILE_PATH "/etc/breakpad-logmapper.conf" #define LOG_FILES_PATH "/tmp/minidump_log_files.txt" +/* Default persistent store; overridden by include.properties PERSISTENT_PATH. */ +#define PERSISTENT_PATH "/opt/persistent" +/* Extender getWanInterfaceName() defaults to br-home. Do not reuse ARM_INTERFACE — + * that string is a broadband device.properties key in get_interface_value(). */ +#define EXTENDER_WAN_INTERFACE "br-home" /* Device types */ typedef enum { diff --git a/c_sourcecode/src/config/config_manager.c b/c_sourcecode/src/config/config_manager.c index 01f60693..a6e0b80b 100644 --- a/c_sourcecode/src/config/config_manager.c +++ b/c_sourcecode/src/config/config_manager.c @@ -162,7 +162,8 @@ int config_init_load(config_t *config, int argc, char *argv[]) config->device_type = DEVICE_TYPE_EXTENDER; CRASHUPLOAD_INFO("device type=%d\n", config->device_type); strcpy(config->core_log_file, "/var/log/messages"); - CRASHUPLOAD_INFO("core log=%s\n", config->core_log_file); + snprintf(config->comm_interface, sizeof(config->comm_interface), "%s", EXTENDER_WAN_INTERFACE); + CRASHUPLOAD_INFO("core log=%s comm_interface=%s\n", config->core_log_file, config->comm_interface); } else if (0 == (strncmp(device_prop_data, "XHC1", 4))) { diff --git a/c_sourcecode/src/platform/platform.c b/c_sourcecode/src/platform/platform.c index c13df3f7..0661cda3 100644 --- a/c_sourcecode/src/platform/platform.c +++ b/c_sourcecode/src/platform/platform.c @@ -160,14 +160,25 @@ int platform_initialize(const config_t *config, platform_config_t *platform) { CRASHUPLOAD_ERROR("GetEstbMac is failed. Trying to get mac from wan interface\n"); char wan_if[32] = {0}; - snprintf(wan_if, sizeof(wan_if), "%s", get_interface_value()); + if (config->device_type == DEVICE_TYPE_EXTENDER) + { + if (config->comm_interface[0] != '\0') + snprintf(wan_if, sizeof(wan_if), "%s", config->comm_interface); + else + snprintf(wan_if, sizeof(wan_if), "%s", EXTENDER_WAN_INTERFACE); + } + else + { + snprintf(wan_if, sizeof(wan_if), "%s", get_interface_value()); + } if (wan_if[0] != '\0' && strcmp(wan_if, "unknown") != 0) { ret = GetHwMacAddress(wan_if, platform->mac_address, sizeof(platform->mac_address)); if (ret) { NormalizeMac(platform->mac_address, sizeof(platform->mac_address)); - CRASHUPLOAD_INFO("Broadband MAC fallback via %s: %s\n", wan_if, platform->mac_address); + CRASHUPLOAD_INFO("%s MAC fallback via %s: %s\n", + device_type_to_str(config->device_type), wan_if, platform->mac_address); } } } diff --git a/c_sourcecode/src/upload/upload.c b/c_sourcecode/src/upload/upload.c index 3a07c6bc..5f1b5249 100644 --- a/c_sourcecode/src/upload/upload.c +++ b/c_sourcecode/src/upload/upload.c @@ -408,8 +408,24 @@ int upload_process(archive_info_t *archive, const config_t *config, const platfo if (config->device_type == DEVICE_TYPE_EXTENDER) { /* Extender: partnerId sourced from account JSON, not from partner_id file */ - /* TODO: read PERSISTENT_PATH from device.properties if /opt/persistent is not universal */ - FILE *fp = fopen("/opt/persistent/account", "r"); + char persistent_path[64] = {0}; + char account_file[80] = {0}; + + if (getIncludePropertyData("PERSISTENT_PATH", persistent_path, (int)sizeof(persistent_path)) != UTILS_SUCCESS || + persistent_path[0] == '\0') + { + snprintf(persistent_path, sizeof(persistent_path), "%s", PERSISTENT_PATH); + CRASHUPLOAD_WARN("Extender: PERSISTENT_PATH property missing, default=%s\n", persistent_path); + } + else + { + size_t plen = strlen(persistent_path); + if (plen > 0 && persistent_path[plen - 1] == '\n') + persistent_path[plen - 1] = '\0'; + CRASHUPLOAD_INFO("Extender: PERSISTENT_PATH=%s\n", persistent_path); + } + snprintf(account_file, sizeof(account_file), "%s/account", persistent_path); + FILE *fp = fopen(account_file, "r"); if (fp) { char line[512] = {0}; diff --git a/unittest/config_manager_gtest.cpp b/unittest/config_manager_gtest.cpp index ff93b13d..7cd192eb 100644 --- a/unittest/config_manager_gtest.cpp +++ b/unittest/config_manager_gtest.cpp @@ -186,6 +186,7 @@ TEST_F(ConfigManagerTest, ConfigInitLoad_ExtenderDevice_Success) { EXPECT_EQ(result, CONFIG_SUCCESS); EXPECT_EQ(test_config.device_type, DEVICE_TYPE_EXTENDER); EXPECT_STREQ(test_config.core_log_file, "/var/log/messages"); + EXPECT_STREQ(test_config.comm_interface, "br-home"); } TEST_F(ConfigManagerTest, ConfigInitLoad_ProdBuildType_Success) { @@ -289,6 +290,7 @@ TEST_F(ConfigManagerTest, ConfigInitLoad_MinidumpExtender_WorkingDirOverride) { EXPECT_EQ(result, CONFIG_SUCCESS); EXPECT_EQ(test_config.dump_type, DUMP_TYPE_MINIDUMP); EXPECT_STREQ(test_config.working_dir_path, "/minidumps"); + EXPECT_STREQ(test_config.comm_interface, "br-home"); } TEST_F(ConfigManagerTest, ConfigInitLoad_T2Enabled_Success) { diff --git a/unittest/upload_gmock.cpp b/unittest/upload_gmock.cpp index 5f81943c..7b39e902 100644 --- a/unittest/upload_gmock.cpp +++ b/unittest/upload_gmock.cpp @@ -384,6 +384,18 @@ int read_RFCProperty(const char* type, const char* key, char *data, size_t datas return -1; } +/** + * Mock: Include-property reader (PERSISTENT_PATH, LOG_PATH, ...) + * Default fail so extender partner-id uses PERSISTENT_PATH fallback. + */ +int getIncludePropertyData(const char* param, char* value, int len) { + if (!param || !value || len <= 0) { + return -1; + } + value[0] = '\0'; + return -1; +} + /** * Mock: Get device property data */ From 14bfea8bb8cb155e2ddb1a27d36377d194fa43a6 Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Thu, 17 Sep 2026 12:02:40 +0530 Subject: [PATCH 02/14] Address build failure & L1 failure --- c_sourcecode/src/config/config_manager.c | 11 ----------- c_sourcecode/src/rfcInterface/rfcinterface.c | 2 -- c_sourcecode/src/upload/upload.c | 6 +++--- c_sourcecode/src/utils/prerequisites.c | 2 +- unittest/upload_gmock.cpp | 5 +++-- 5 files changed, 7 insertions(+), 19 deletions(-) diff --git a/c_sourcecode/src/config/config_manager.c b/c_sourcecode/src/config/config_manager.c index a6e0b80b..2a4bda75 100644 --- a/c_sourcecode/src/config/config_manager.c +++ b/c_sourcecode/src/config/config_manager.c @@ -143,17 +143,6 @@ int config_init_load(config_t *config, int argc, char *argv[]) } CRASHUPLOAD_INFO("COMM_INTERFACE=%s\n", config->comm_interface); } - - /* TODO: During broadband we have to implement - * CORE_PATH="/minidumps" - LOG_PATH="/rdklogs/logs" - if [ ! -d $LOG_PATH ];then mkdir -p $LOG_PATH; fi - if [ "$MULTI_CORE" = "yes" ] ;then - COMM_INTERFACE=`get_interface_value` - else - COMM_INTERFACE=$INTERFACE - fi - */ /* NOTE: /nvram/coredump.properties and /opt/coredump.properties do not exist on RDKB. * Non-prod build override via /opt/coredump.properties applies only to non-broadband devices. */ } diff --git a/c_sourcecode/src/rfcInterface/rfcinterface.c b/c_sourcecode/src/rfcInterface/rfcinterface.c index 30f42230..0b16bf45 100644 --- a/c_sourcecode/src/rfcInterface/rfcinterface.c +++ b/c_sourcecode/src/rfcInterface/rfcinterface.c @@ -171,7 +171,6 @@ int write_RFCProperty(char *type, const char *key, const char *value, RFCVALDATA * */ int read_RFCProperty(const char *type, const char *key, char *out_value, size_t datasize) { - // TODO: Need to implement for RDK-M (void)type; (void)key; (void)out_value; @@ -189,7 +188,6 @@ int read_RFCProperty(const char *type, const char *key, char *out_value, size_t * */ int write_RFCProperty(const char *type, const char *key, const char *value, RFCVALDATATYPE datatype) { - // TODO: Need to implement for RDK-M (void)type; (void)key; (void)value; diff --git a/c_sourcecode/src/upload/upload.c b/c_sourcecode/src/upload/upload.c index 5f1b5249..29d95fc9 100644 --- a/c_sourcecode/src/upload/upload.c +++ b/c_sourcecode/src/upload/upload.c @@ -363,7 +363,7 @@ int upload_file(const char *filepath, const char *url, const char *dump_name, co else { CRASHUPLOAD_ERROR("post field buffer corrupted. Total write bytes=%zu and total buf size=%zu\n", totlen, szPostFieldOut); - CRASHUPLOAD_ERROR("postfield data=%s\n", post_filed); // TODO: Need to remove + CRASHUPLOAD_ERROR("postfield data=%s\n", post_filed); break; } } @@ -411,7 +411,7 @@ int upload_process(archive_info_t *archive, const config_t *config, const platfo char persistent_path[64] = {0}; char account_file[80] = {0}; - if (getIncludePropertyData("PERSISTENT_PATH", persistent_path, (int)sizeof(persistent_path)) != UTILS_SUCCESS || + if (getIncludePropertyData("PERSISTENT_PATH", persistent_path, sizeof(persistent_path)) != UTILS_SUCCESS || persistent_path[0] == '\0') { snprintf(persistent_path, sizeof(persistent_path), "%s", PERSISTENT_PATH); @@ -472,7 +472,7 @@ int upload_process(archive_info_t *archive, const config_t *config, const platfo ret = read_RFCProperty("EncryptCloudUpload", RFC_DMP_ENCRYPT_UPLOAD, encryptionEnable, sizeof(encryptionEnable)); if ((ret == READ_RFC_FAILURE) || (encryptionEnable[0] == '\0')) { - strcpy(encryptionEnable, "false"); // TODO: Need check what should be default value + strcpy(encryptionEnable, "false"); CRASHUPLOAD_WARN("Read rfc failed EncryptCloudUpload:%s\n", encryptionEnable); } else diff --git a/c_sourcecode/src/utils/prerequisites.c b/c_sourcecode/src/utils/prerequisites.c index 910f3f65..f6e938f8 100644 --- a/c_sourcecode/src/utils/prerequisites.c +++ b/c_sourcecode/src/utils/prerequisites.c @@ -171,7 +171,7 @@ int prerequisites_wait(config_t *config, int timeout_sec) if ((config->dump_type == DUMP_TYPE_COREDUMP) && (0 != (filePresentCheck("/tmp/coredump_mutex_release")))) { CRASHUPLOAD_INFO("Waiting for Coredump Completion\n"); - sleep(21); // TODO: How this number arive?? + sleep(21); // NMI: How this number arive?? } return PREREQUISITES_SUCCESS; } diff --git a/unittest/upload_gmock.cpp b/unittest/upload_gmock.cpp index 7b39e902..dc2176e1 100644 --- a/unittest/upload_gmock.cpp +++ b/unittest/upload_gmock.cpp @@ -387,9 +387,10 @@ int read_RFCProperty(const char* type, const char* key, char *data, size_t datas /** * Mock: Include-property reader (PERSISTENT_PATH, LOG_PATH, ...) * Default fail so extender partner-id uses PERSISTENT_PATH fallback. + * Signature must match rdk_fwdl_utils.h. */ -int getIncludePropertyData(const char* param, char* value, int len) { - if (!param || !value || len <= 0) { +int getIncludePropertyData(const char* param, char* value, unsigned int buff_size) { + if (!param || !value || buff_size == 0) { return -1; } value[0] = '\0'; From 15d3d3ac60f941e1b5787a4584d1a8b4097bf65f Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Mon, 21 Sep 2026 11:41:52 +0530 Subject: [PATCH 03/14] Add inotify watcher to src --- .gitignore | 8 +- c_sourcecode/Makefile.am | 2 +- c_sourcecode/README.md | 92 ++---- c_sourcecode/configure.ac | 3 +- c_sourcecode/src/Makefile.am | 2 + c_sourcecode/src/README.md | 47 +++ c_sourcecode/src/archive/archive.c | 2 +- c_sourcecode/src/scanner/scanner.c | 2 +- c_sourcecode/src/utils/cleanup_batch.c | 2 +- c_sourcecode/src/utils/file_utils.c | 2 +- .../watcher/Makefile.am | 28 +- c_sourcecode/watcher/README.md | 58 ++++ .../watcher}/inotify-minidump-watcher.c | 40 ++- cov_build.sh | 11 +- .../crashupload-binary-architecture.md | 28 +- docs/migration/updatedREADME.md | 2 +- openspec/config.yaml | 2 +- openspec/project.md | 3 +- openspec/runtime/component-catalog.md | 2 +- run_ut.sh | 30 +- unittest/Makefile.am | 47 +-- unittest/archive_gtest.cpp | 4 +- unittest/scanner_gtest.cpp | 4 +- unittest/watcher_gmock.cpp | 308 ++++++++++++++++++ unittest/watcher_gtest.cpp | 230 +++++++++++++ 25 files changed, 818 insertions(+), 141 deletions(-) create mode 100644 c_sourcecode/src/README.md rename src/Makefile => c_sourcecode/watcher/Makefile.am (55%) create mode 100644 c_sourcecode/watcher/README.md rename {src => c_sourcecode/watcher}/inotify-minidump-watcher.c (88%) create mode 100644 unittest/watcher_gmock.cpp create mode 100644 unittest/watcher_gtest.cpp diff --git a/.gitignore b/.gitignore index 6997acf2..d9744df7 100644 --- a/.gitignore +++ b/.gitignore @@ -21,10 +21,10 @@ autom4te.cache/ # ============================================================================= # Generated by ./configure (from Makefile.am / Makefile.in) -# NOTE: c_sourcecode/src/Makefile.am is a source file and is NOT ignored. -# src/Makefile (root src/) is a hand-written file and is NOT ignored. +# NOTE: c_sourcecode/src/Makefile.am and c_sourcecode/watcher/Makefile.am are source files. c_sourcecode/Makefile c_sourcecode/src/Makefile +c_sourcecode/watcher/Makefile # Autoconf output files c_sourcecode/config.status @@ -33,8 +33,9 @@ c_sourcecode/config.h c_sourcecode/config.h.in c_sourcecode/stamp-h1 -# Compiled crashupload binary +# Compiled binaries c_sourcecode/src/crashupload +c_sourcecode/watcher/inotify-minidump-watcher # Core dump produced if the binary crashes during local development/testing c_sourcecode/src/core @@ -80,3 +81,4 @@ unittest/ratelimit_gtest unittest/scanner_gtest unittest/upload_gtest unittest/utils_gtest +unittest/watcher_gtest diff --git a/c_sourcecode/Makefile.am b/c_sourcecode/Makefile.am index 0ff88c36..9a140832 100644 --- a/c_sourcecode/Makefile.am +++ b/c_sourcecode/Makefile.am @@ -17,7 +17,7 @@ # limitations under the License. ########################################################################## -SUBDIRS = src +SUBDIRS = src watcher EXTRA_DIST = README.md diff --git a/c_sourcecode/README.md b/c_sourcecode/README.md index 8bba8617..7bbf5450 100644 --- a/c_sourcecode/README.md +++ b/c_sourcecode/README.md @@ -1,76 +1,54 @@ -# Crashupload C Implementation - Skeleton Code +# Crashupload C source (`c_sourcecode/`) -This directory contains skeleton C implementation for the crashupload migration from shell scripts to C. +This directory is the autotools tree for the C binaries: -## Architecture +- **crashupload** — dump discovery, archive, rate-limit, and upload (`src/`) +- **inotify-minidump-watcher** — directory watcher that starts upload on matching file creates (`watcher/`) -Based on **optimized design** from: -- `docs/migration/hld/updateduploadDumps-hld.md` -- `docs/migration/lld/updateduploadDumps-lld.md` -- `docs/migration/diagrams/flowcharts/optimizeduploadDumps-flowcharts.md` -- `docs/migration/diagrams/sequence/updateuploadDumps-sequence.md` -- `docs/migration/requirements/uploadDumps-requirements.md` - -## Structure +## Layout ``` c_sourcecode/ -├── common/ # Common type definitions, constants, errors -│ ├── types.h -│ ├── constants.h -│ └── errors.h -├── src/ # Source code -│ ├── main.c # Main entry point (7-step optimized flow) -│ ├── init/ # Consolidated initialization -│ ├── config/ # Configuration management -│ ├── platform/ # Platform abstraction -│ ├── core/ # Core processing modules -│ │ ├── scanner.* # Dump file scanner -│ │ ├── archive_smart.* # Smart archive creator -│ │ ├── upload_typeaware.* # Type-aware upload -│ │ └── ratelimit_unified.* # Unified rate limiter -│ ├── utils/ # Utility modules -│ │ ├── prerequisites.* # Combined network+time check -│ │ ├── privacy.* # Unified privacy check -│ │ ├── cleanup_batch.* # Batch cleanup -│ │ ├── lock_manager.* # Process locking -│ │ └── logger.* # Logging -│ └── Makefile # Build system +├── configure.ac +├── Makefile.am +├── common/ # Shared types, constants, errors +├── include/ # Public headers used by crashupload +├── src/ # crashupload binary +└── watcher/ # inotify-minidump-watcher binary ``` -## Key Optimizations - -1. **Consolidated Initialization** - Single `system_initialize()` call (3 steps → 1) -2. **Combined Prerequisites** - `prerequisites_wait()` checks network + time together -3. **Unified Privacy** - `privacy_uploads_blocked()` combines opt-out + privacy mode -4. **Smart Archive** - Direct compression first, /tmp fallback if needed -5. **Type-Aware Upload** - Minidump (5 retries, 3s delay) vs Coredump (3 retries, 10s delay) -6. **Unified Rate Limit** - Single check for recovery + 10/10min limit -7. **Batch Cleanup** - Single directory scan for all cleanup operations +## Build -## Building +From this directory: ```bash -cd src +autoreconf -i +./configure make +make install ``` -## Status +`make` produces: + +- `src/crashupload` +- `watcher/inotify-minidump-watcher` + +Both are `bin_PROGRAMS` and install to `$prefix/bin` (typically `/usr/bin` on device images). -**SKELETON**: All files contain function signatures and data structures from the design documents, but function bodies need implementation. Each TODO comment indicates what needs to be implemented. +Yocto/RDK builds pass extra `CFLAGS`/`LDFLAGS` (for example `-DYOCTO_BUILD` and `-lsecure_wrapper`). Do not hard-code those in a way that breaks the existing recipe flags. -## Next Steps +Local coverage-style builds used by L1 go through `crashupload/cov_build.sh`, which configures and installs both binaries. -1. Implement function bodies following TODO markers -2. Add unit tests (GTest framework recommended) -3. Build and test incrementally -4. Validate against shell script behavior -5. Performance test on target platforms +## Tests + +L1 (GTest) lives in `../unittest/`. Watcher tests are `watcher_gtest` and are included in `../run_ut.sh`. + +```bash +cd .. +./run_ut.sh +``` -## Performance Targets +## Notes -Based on optimized design: -- Startup: 100-120ms (vs 150-200ms standard) -- Memory: 6-8MB (vs 8-10MB standard) -- Binary: ~35KB (vs ~45KB standard) -- Decision points: 22 (vs 35 standard) - 37% reduction +- `src/` and `watcher/` are separate subdirectories so crashupload link flags (curl, rbus, archive, telemetry) are not applied to the watcher. +- The watcher only links `libsecure_wrapper`, matching the legacy `crashupload/src/Makefile`. diff --git a/c_sourcecode/configure.ac b/c_sourcecode/configure.ac index 2243f900..a72df331 100644 --- a/c_sourcecode/configure.ac +++ b/c_sourcecode/configure.ac @@ -52,7 +52,8 @@ CFLAGS="$CFLAGS -Wall -Werror -O2 -DT2_EVENT_ENABLED" #CFLAGS="$CFLAGS -D_POSIX_C_SOURCE=200809L" AC_CONFIG_FILES([Makefile - src/Makefile]) + src/Makefile + watcher/Makefile]) AC_OUTPUT echo "" diff --git a/c_sourcecode/src/Makefile.am b/c_sourcecode/src/Makefile.am index 7319f81b..b4bbc0a5 100644 --- a/c_sourcecode/src/Makefile.am +++ b/c_sourcecode/src/Makefile.am @@ -76,3 +76,5 @@ crashupload_SOURCES += \ # Clean up CLEANFILES = *~ + +EXTRA_DIST = README.md diff --git a/c_sourcecode/src/README.md b/c_sourcecode/src/README.md new file mode 100644 index 00000000..6d537028 --- /dev/null +++ b/c_sourcecode/src/README.md @@ -0,0 +1,47 @@ +# crashupload (`src/`) + +This directory is the source for the **crashupload** binary. It is built by autotools from `c_sourcecode/` (`SUBDIRS = src watcher`). + +## What it does + +`crashupload` is the C replacement path for dump packaging and upload. The entry point is `main.c`, which initializes logging/config/platform, waits for prerequisites, takes the process lock, scans for dumps, archives, uploads, rate-limits, and cleans up. + +## Modules + +| Path | Role | +|------|------| +| `main.c` | Process entry and main flow | +| `init/` | Consolidated system initialization | +| `config/` | Runtime configuration load | +| `platform/` | Device/platform specifics | +| `scanner/` | Dump file discovery | +| `archive/` | Archive creation | +| `upload/` | Upload to crash portal | +| `ratelimit/` | Upload rate limiting | +| `rfcInterface/` | RFC parameter access | +| `rbusInterface/` | RBUS init/uninit | +| `t2Interface/` | Telemetry markers | +| `utils/` | Logger, locks, files, prerequisites, batch cleanup | + +Headers used across modules also live in `../common/` and `../include/`. + +## Build + +Do not run a standalone Makefile in this folder. Build from the parent tree: + +```bash +cd .. +autoreconf -i +./configure +make +``` + +The binary is `src/crashupload` (installed as `/usr/bin/crashupload`). + +## Tests + +L1 coverage for these sources is under `../../unittest/` (`*_gtest` binaries). Run from the crashupload repo root: + +```bash +./run_ut.sh +``` diff --git a/c_sourcecode/src/archive/archive.c b/c_sourcecode/src/archive/archive.c index 69185000..59907cf1 100644 --- a/c_sourcecode/src/archive/archive.c +++ b/c_sourcecode/src/archive/archive.c @@ -30,7 +30,7 @@ #include "../utils/logger.h" // For unit testing: allow static functions to be visible -#ifdef UNIT_TEST +#ifdef GTEST_ENABLE #define STATIC_TESTABLE #else #define STATIC_TESTABLE static diff --git a/c_sourcecode/src/scanner/scanner.c b/c_sourcecode/src/scanner/scanner.c index 81d6c809..f6a002b5 100644 --- a/c_sourcecode/src/scanner/scanner.c +++ b/c_sourcecode/src/scanner/scanner.c @@ -31,7 +31,7 @@ #include "../utils/logger.h" // For unit testing: allow static functions to be visible -#ifdef UNIT_TEST +#ifdef GTEST_ENABLE #define STATIC_TESTABLE #else #define STATIC_TESTABLE static diff --git a/c_sourcecode/src/utils/cleanup_batch.c b/c_sourcecode/src/utils/cleanup_batch.c index 2b1339ae..7a899117 100644 --- a/c_sourcecode/src/utils/cleanup_batch.c +++ b/c_sourcecode/src/utils/cleanup_batch.c @@ -31,7 +31,7 @@ #include "logger.h" // For unit testing: allow static functions to be visible -#ifdef UNIT_TEST +#ifdef GTEST_ENABLE #define STATIC_TESTABLE #else #define STATIC_TESTABLE static diff --git a/c_sourcecode/src/utils/file_utils.c b/c_sourcecode/src/utils/file_utils.c index dfb022d1..7c90769c 100644 --- a/c_sourcecode/src/utils/file_utils.c +++ b/c_sourcecode/src/utils/file_utils.c @@ -34,7 +34,7 @@ #include "logger.h" // For unit testing: allow static functions to be visible -#ifdef UNIT_TEST +#ifdef GTEST_ENABLE #define STATIC_TESTABLE #else #define STATIC_TESTABLE static diff --git a/src/Makefile b/c_sourcecode/watcher/Makefile.am similarity index 55% rename from src/Makefile rename to c_sourcecode/watcher/Makefile.am index 829c5e19..ce63fb13 100644 --- a/src/Makefile +++ b/c_sourcecode/watcher/Makefile.am @@ -1,8 +1,8 @@ ########################################################################## -# If not stated otherwise in this file or this component's Licenses.txt +# If not stated otherwise in this file or this component's LICENSE # file the following copyright and licenses apply: # -# Copyright 2016 RDK Management +# Copyright 2025 RDK Management # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,21 +16,17 @@ # See the License for the specific language governing permissions and # limitations under the License. ########################################################################## -BASE_PATH=${BUILD_PATH} -CROSS_TOOLCHAIN=$(BASE_PATH)/workXG1/tools/stbgcc-4.5.4-2.8 -CROSS_COMPILE=$(CROSS_TOOLCHAIN)/bin/mipsel-linux-uclibc -SDK_FSROOT=$(BASE_PATH)/sdk/fsroot/ramdisk -LDFLAGS+=$(GLIBS) -LDFLAGS+=$(DS_LIBS) -lsecure_wrapper -CXXFLAGS=-Wall ${ADDED_INCLUDE} +bin_PROGRAMS = inotify-minidump-watcher -all : inotify-minidump-watcher +# Match legacy crashupload/src/Makefile: link only secure_wrapper. +# Extra defines such as -DYOCTO_BUILD continue to come from the +# environment / configure CFLAGS (same as crashupload). +AM_LDFLAGS = -L$(PKG_CONFIG_SYSROOT_DIR)/$(libdir) -inotify-minidump-watcher: - @echo "Building $@ ...." - $(CC) $(CFLAGS) -o inotify-minidump-watcher inotify-minidump-watcher.c $(LDFLAGS) -install : - mkdir bin - mv inotify-minidump-watcher bin +inotify_minidump_watcher_SOURCES = inotify-minidump-watcher.c +inotify_minidump_watcher_LDADD = -lsecure_wrapper +EXTRA_DIST = README.md + +CLEANFILES = *~ diff --git a/c_sourcecode/watcher/README.md b/c_sourcecode/watcher/README.md new file mode 100644 index 00000000..278a1a81 --- /dev/null +++ b/c_sourcecode/watcher/README.md @@ -0,0 +1,58 @@ +# inotify-minidump-watcher (`watcher/`) + +Standalone helper binary that watches a directory with inotify (`IN_CREATE`) and runs a command when a created filename matches one or more glob patterns (typically `*.dmp`). + +It is **not** the uploader. On a match it executes the configured command (historically `/lib/rdk/uploadDumps.sh`). If `COMMAND_TO_RUN` starts with `NULL`, it exits after the matching flag file is created. + +Extender/Yocto builds do **not** set `GTEST_ENABLE`. All GTest-only symbols and syscall redirects are behind that flag. + +## Files + +| File | Role | +|------|------| +| `inotify-minidump-watcher.c` | Watcher implementation and `main` | +| `Makefile.am` | Builds `inotify-minidump-watcher` | +| `README.md` | This file | + +## Usage + +``` +inotify-minidump-watcher DIRECTORY COMMAND_TO_RUN COMMAND_ARGS PATTERN... +``` + +Example: + +``` +/usr/bin/inotify-minidump-watcher /minidumps /lib/rdk/uploadDumps.sh "" 0 *.dmp +``` + +`SIGINT` stops a blocking `read` cleanly. + +## Build + +Built with the rest of `c_sourcecode` (no `GTEST_ENABLE`): + +```bash +cd .. +autoreconf -i +./configure +make +``` + +Output: `watcher/inotify-minidump-watcher` + +Install name remains `/usr/bin/inotify-minidump-watcher`. + +Link flags stay limited to `-lsecure_wrapper` (plus environment `LDFLAGS`). `-DYOCTO_BUILD` continues to come from the recipe `CFLAGS` and switches `system()` to `v_secure_system()`. + +## L1 tests + +`unittest/watcher_gtest` compiles this file with `-DGTEST_ENABLE` (same L1 flag as the rest of crashupload): + +- `main` is built as `watcher_main`; inotify/libc calls go to mocks +- `STATIC_TESTABLE` makes `directory_watcher` / `process_interrupt_handler` visible (same pattern as `archive.c`) + +```bash +cd ../.. +./run_ut.sh +``` diff --git a/src/inotify-minidump-watcher.c b/c_sourcecode/watcher/inotify-minidump-watcher.c similarity index 88% rename from src/inotify-minidump-watcher.c rename to c_sourcecode/watcher/inotify-minidump-watcher.c index 8ae5d69f..7160c880 100755 --- a/src/inotify-minidump-watcher.c +++ b/c_sourcecode/watcher/inotify-minidump-watcher.c @@ -25,12 +25,35 @@ #include #include #include +#include #include #include #ifdef YOCTO_BUILD #include "secure_wrapper.h" #endif +#ifdef GTEST_ENABLE +#define STATIC_TESTABLE +int mock_inotify_init(void); +int mock_inotify_add_watch(int fd, const char *pathname, uint32_t mask); +ssize_t mock_read(int fd, void *buf, size_t count); +int mock_close(int fd); +int mock_system(const char *command); +int mock_printf(const char *fmt, ...); +int mock_fnmatch(const char *pattern, const char *string, int flags); +int mock_sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); +#define inotify_init mock_inotify_init +#define inotify_add_watch mock_inotify_add_watch +#define read mock_read +#define close mock_close +#define system mock_system +#define printf mock_printf +#define fnmatch mock_fnmatch +#define sigaction mock_sigaction +#else +#define STATIC_TESTABLE static +#endif + /** * @defgroup Crashupload Crashupload * @@ -57,6 +80,13 @@ static volatile int interrupted = 0; +#ifdef GTEST_ENABLE +void +watcher_test_reset(void) +{ + interrupted = 0; +} +#endif /** * @addtogroup Crashupload_API @@ -70,7 +100,7 @@ static volatile int interrupted = 0; * */ -static void +STATIC_TESTABLE void process_interrupt_handler(const int s) { if (s == SIGINT) @@ -88,7 +118,7 @@ process_interrupt_handler(const int s) * @param[in] pattern_count Number of patterns to be verified. */ -static int +STATIC_TESTABLE int directory_watcher(const char *const directory, const char* command_to_run, const char* command_args, @@ -221,8 +251,13 @@ directory_watcher(const char *const directory, * Eg: /usr/bin/inotify-minidump-watcher /minidumps /lib/rdk/uploadDumps.sh "" 0 *.dmp */ +#ifndef GTEST_ENABLE int main(const int argc, const char *const *const argv) +#else +int +watcher_main(const int argc, const char *const *const argv) +#endif { if (argc < 5) { @@ -245,4 +280,3 @@ main(const int argc, const char *const *const argv) /** * @} // End of Doxygen */ - diff --git a/cov_build.sh b/cov_build.sh index 56a5cc8c..d1b824e4 100755 --- a/cov_build.sh +++ b/cov_build.sh @@ -90,7 +90,7 @@ clean_build_artifacts() { rm -f stamp-h1 2>/dev/null || true rm -f libtool 2>/dev/null || true rm -f configure 2>/dev/null || true - rm -f Makefile.in Makefile src/Makefile.in 2>/dev/null || true + rm -f Makefile.in Makefile src/Makefile.in watcher/Makefile.in 2>/dev/null || true rm -f aclocal.m4 2>/dev/null || true rm -f compile depcomp install-sh missing 2>/dev/null || true rm -f config.sub config.guess 2>/dev/null || true @@ -168,18 +168,19 @@ fi # Post-configure: Remove T2_EVENT_ENABLED from generated Makefiles # This avoids dependency on telemetry_busmessage_sender.h which isn't available echo "Removing T2_EVENT_ENABLED and telemetry libraries from generated Makefiles..." -sed -i 's/-DT2_EVENT_ENABLED//g' Makefile src/Makefile -sed -i 's/-ltelemetry_msgsender//g' Makefile src/Makefile -sed -i 's/-lt2utils//g' Makefile src/Makefile +sed -i 's/-DT2_EVENT_ENABLED//g' Makefile src/Makefile watcher/Makefile +sed -i 's/-ltelemetry_msgsender//g' Makefile src/Makefile watcher/Makefile +sed -i 's/-lt2utils//g' Makefile src/Makefile watcher/Makefile # Build and install the binary -echo "[3/3] Building and installing crashupload binary..." +echo "[3/3] Building and installing crashupload and inotify-minidump-watcher binaries..." if make && make install; then echo "" echo "========================================" echo "Build completed successfully!" echo "========================================" echo "Binary installed to: ${INSTALL_DIR}/bin/crashupload" + echo "Binary installed to: ${INSTALL_DIR}/bin/inotify-minidump-watcher" echo "========================================" else echo "" diff --git a/docs/migration/diagrams/flowcharts/crashupload-binary-architecture.md b/docs/migration/diagrams/flowcharts/crashupload-binary-architecture.md index 2731f572..efb1f83c 100644 --- a/docs/migration/diagrams/flowcharts/crashupload-binary-architecture.md +++ b/docs/migration/diagrams/flowcharts/crashupload-binary-architecture.md @@ -134,7 +134,7 @@ graph TD - Compiler: GCC/G++ with autotools build system - Standards: C99/C++11 - **C** - Inotify watcher and legacy utilities - - Source: `src/inotify-minidump-watcher.c` + - Source: `c_sourcecode/watcher/inotify-minidump-watcher.c` - **Shell Script** - Orchestration and legacy implementation - Main: `uploadDumps.sh`, `runDumpUpload.sh`, `uploadDumpsUtils.sh` - Shell: BusyBox sh @@ -501,21 +501,21 @@ crashupload/ ├── c_sourcecode/ # C implementation │ ├── common/ # Type definitions, constants, errors │ ├── include/ # Public header files -│ └── src/ # Source code (main, modules) -│ ├── archive/ # Archive creation -│ ├── config/ # Configuration management -│ ├── init/ # System initialization -│ ├── platform/ # Platform abstraction -│ ├── ratelimit/ # Rate limiting -│ ├── rfcInterface/ # RFC API wrapper -│ ├── scanner/ # Dump file scanner -│ ├── t2Interface/ # T2 telemetry wrapper -│ ├── upload/ # Upload logic -│ └── utils/ # Utilities (logger, file ops, etc.) +│ ├── src/ # Source code (main, modules) +│ │ ├── archive/ # Archive creation +│ │ ├── config/ # Configuration management +│ │ ├── init/ # System initialization +│ │ ├── platform/ # Platform abstraction +│ │ ├── ratelimit/ # Rate limiting +│ │ ├── rfcInterface/ # RFC API wrapper +│ │ ├── scanner/ # Dump file scanner +│ │ ├── t2Interface/ # T2 telemetry wrapper +│ │ ├── upload/ # Upload logic +│ │ └── utils/ # Utilities (logger, file ops, etc.) +│ └── watcher/ # Notifier C Code +│ └── inotify-minidump-watcher.c ├── docs/ # Documentation │ └── migration/ # Migration design docs (HLD, LLD, flowcharts) -├── src/ # Notifier C Code -│ └── inotify-minidump-watcher.c ├── test/ # Functional tests (Python pytest) ├── unittest/ # Unit tests (GTest) ├── uploadDumps.sh # Main orchestrator script diff --git a/docs/migration/updatedREADME.md b/docs/migration/updatedREADME.md index 76de3074..34e5b3cf 100644 --- a/docs/migration/updatedREADME.md +++ b/docs/migration/updatedREADME.md @@ -366,7 +366,7 @@ When updating documentation: ## References - Original shell scripts: `src/uploadDumps.sh`, `src/uploadDumpsUtils.sh` -- Existing C implementation: `src/inotify-minidump-watcher.c` +- Existing C implementation: `c_sourcecode/watcher/inotify-minidump-watcher.c` - RDK device specifications: See platform documentation - C11 standard: ISO/IEC 9899:2011 diff --git a/openspec/config.yaml b/openspec/config.yaml index 38557b7e..eb8808e1 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -6,7 +6,7 @@ context: | Use this context strategy to keep SDLC artifact generation accurate and token-efficient: - Read OpenSpec docs first: openspec/project.md, openspec/runtime/**, openspec/pipeline/**, openspec/subsystems/**, openspec/integrations/**, openspec/diagrams/**, openspec/gaps/**. - Read documentation second: README.md, c_sourcecode/README.md, unittest/README.md, docs/migration/**, and other repo README files relevant to the touched area. - - Read implementation third, but only the narrow slice needed for the task: uploadDumps.sh, runDumpUpload.sh, uploadDumpsUtils.sh, c_sourcecode/src/**, c_sourcecode/include/**, c_sourcecode/common/**, src/inotify-minidump-watcher.c, and related service files. + - Read implementation third, but only the narrow slice needed for the task: uploadDumps.sh, runDumpUpload.sh, uploadDumpsUtils.sh, c_sourcecode/src/**, c_sourcecode/include/**, c_sourcecode/common/**, c_sourcecode/watcher/**, and related service files. - Read verification and delivery context only when relevant: run_ut.sh, run_l2.sh, cov_build.sh, rdk_build.sh, test/L2_TESTS.md, unittest/**, test/functional-tests/**, and .github/workflows/*.yml. Repository reality to preserve in artifacts: diff --git a/openspec/project.md b/openspec/project.md index 64290fee..54988a5f 100644 --- a/openspec/project.md +++ b/openspec/project.md @@ -113,6 +113,7 @@ Supporting documentation: - minidump-on-bootup-upload.timer - uploadDumps.sh - runDumpUpload.sh -- src/inotify-minidump-watcher.c +- c_sourcecode/watcher/inotify-minidump-watcher.c - c_sourcecode/src/Makefile.am +- c_sourcecode/watcher/Makefile.am - c_sourcecode/configure.ac diff --git a/openspec/runtime/component-catalog.md b/openspec/runtime/component-catalog.md index c6792dec..7d424a13 100644 --- a/openspec/runtime/component-catalog.md +++ b/openspec/runtime/component-catalog.md @@ -70,7 +70,7 @@ This section catalogs meaningful runtime components and analyzes startup path, i - Unknown: full effective runtime depends on sourced scripts not fully analyzed here. ## Component D: inotify-minidump-watcher -- Binary: src/inotify-minidump-watcher.c built by src/Makefile +- Binary: c_sourcecode/watcher/inotify-minidump-watcher.c built with crashupload by c_sourcecode autotools - Intended invoker: service or manual process configured to monitor a directory. - Operational model: persistent watcher using inotify IN_CREATE. - Startup path: directory_watcher(directory, command, args, patterns) diff --git a/run_ut.sh b/run_ut.sh index a8a68353..e0c87b48 100644 --- a/run_ut.sh +++ b/run_ut.sh @@ -44,9 +44,11 @@ NC='\033[0m' # No Color SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" UNITTEST_DIR="$SCRIPT_DIR/unittest" SRC_DIR="$SCRIPT_DIR/c_sourcecode/src" +WATCHER_DIR="$SCRIPT_DIR/c_sourcecode/watcher" +SOURCECODE_DIR="$SCRIPT_DIR/c_sourcecode" # Test binaries to run (space-separated list) -TEST_BINARIES="config_manager_gtest platform_gtest scanner_gtest archive_gtest utils_gtest upload_gtest mainapp_gtest ratelimit_gtest lock_manager_gtest prerequisites_gtest logger_gtest" +TEST_BINARIES="config_manager_gtest platform_gtest scanner_gtest archive_gtest utils_gtest upload_gtest mainapp_gtest ratelimit_gtest lock_manager_gtest prerequisites_gtest logger_gtest watcher_gtest" # Test results tracking (using temp file instead of associative array) TEST_RESULTS_FILE="/tmp/crashupload_test_results_$$.tmp" @@ -177,7 +179,7 @@ print_coverage_hotspots() { /^LF:/ { lf = substr($0, 4) + 0 } /^LH:/ { lh = substr($0, 4) + 0 } /^end_of_record/ { - if (sf ~ /\/c_sourcecode\/src\//) { + if (sf ~ /\/c_sourcecode\/(src|watcher)\//) { lp = pct(lh, lf) fp = pct(fnh, fnf) if ((lp + 0.0) < (line_thr + 0.0) || (fp + 0.0) < (fn_thr + 0.0)) { @@ -196,7 +198,7 @@ print_coverage_hotspots() { split(raw, a, ",") cnt = a[1] + 0 fn = a[2] - if (cnt == 0 && sf ~ /\/c_sourcecode\/src\//) { + if (cnt == 0 && sf ~ /\/c_sourcecode\/(src|watcher)\//) { printf(" - %s :: %s\n", sf, fn) } } @@ -278,7 +280,7 @@ print_filewise_coverage() { fi print_header "File-wise Coverage (Lines / Functions)" - echo "Filtering to c_sourcecode/src for actionable module coverage" + echo "Filtering to c_sourcecode/src and c_sourcecode/watcher for actionable module coverage" echo "" awk ' @@ -299,7 +301,7 @@ print_filewise_coverage() { /^FNF:/ { fnf = substr($0, 5) + 0 } /^FNH:/ { fnh = substr($0, 5) + 0 } /^end_of_record/ { - if (sf ~ /c_sourcecode\/src\// && sf ~ /\.c$/) { + if (sf ~ /c_sourcecode\/(src|watcher)\// && sf ~ /\.c$/) { lp = pct(lh, lf) fp = pct(fnh, fnf) printf("%s | %.1f%% (%d/%d) | %.1f%% (%d/%d)\n", sf, lp, lh, lf, fp, fnh, fnf) @@ -443,6 +445,9 @@ clean_build() { find "$SRC_DIR" -type f -name "*.gcda" -delete 2>/dev/null || true find "$SRC_DIR" -type f -name "*.gcno" -delete 2>/dev/null || true find "$SRC_DIR" -type f -name "*.gcov" -delete 2>/dev/null || true + find "$WATCHER_DIR" -type f -name "*.gcda" -delete 2>/dev/null || true + find "$WATCHER_DIR" -type f -name "*.gcno" -delete 2>/dev/null || true + find "$WATCHER_DIR" -type f -name "*.gcov" -delete 2>/dev/null || true # Remove object files from c_sourcecode print_step "Removing object files from c_sourcecode..." @@ -450,6 +455,10 @@ clean_build() { find "$SRC_DIR" -type f -name "*.lo" -delete 2>/dev/null || true find "$SRC_DIR" -type d -name ".libs" -exec rm -rf {} + 2>/dev/null || true find "$SRC_DIR" -type d -name ".deps" -exec rm -rf {} + 2>/dev/null || true + find "$WATCHER_DIR" -type f -name "*.o" -delete 2>/dev/null || true + find "$WATCHER_DIR" -type f -name "*.lo" -delete 2>/dev/null || true + find "$WATCHER_DIR" -type d -name ".libs" -exec rm -rf {} + 2>/dev/null || true + find "$WATCHER_DIR" -type d -name ".deps" -exec rm -rf {} + 2>/dev/null || true # Remove dirstamp files find "$SCRIPT_DIR" -type f -name ".dirstamp" -delete 2>/dev/null || true @@ -732,8 +741,8 @@ generate_coverage() { cd "$UNITTEST_DIR" print_step "Checking for coverage data files..." - gcda_count=$(find ../c_sourcecode/src -name "*.gcda" 2>/dev/null | wc -l) - gcno_count=$(find ../c_sourcecode/src -name "*.gcno" 2>/dev/null | wc -l) + gcda_count=$(find "$SOURCECODE_DIR" -name "*.gcda" 2>/dev/null | wc -l) + gcno_count=$(find "$SOURCECODE_DIR" -name "*.gcno" 2>/dev/null | wc -l) echo "Found $gcda_count .gcda files and $gcno_count .gcno files" @@ -745,18 +754,18 @@ generate_coverage() { print_step "Capturing coverage data..." lcov --capture \ - --directory "$SRC_DIR" \ + --directory "$SOURCECODE_DIR" \ --output-file coverage.info \ --rc lcov_branch_coverage=1 2>/dev/null || \ lcov --capture \ - --directory "$SRC_DIR" \ + --directory "$SOURCECODE_DIR" \ --output-file coverage.info \ --rc lcov_branch_coverage=1 \ --ignore-errors gcov,source || true if [ -f coverage.info ]; then print_step "Filtering coverage data..." - lcov --extract coverage.info '*/c_sourcecode/src/*.c' \ + lcov --extract coverage.info '*/c_sourcecode/src/*.c' '*/c_sourcecode/watcher/*.c' '*/inotify-minidump-watcher.c' \ --output-file coverage.filtered.info \ --rc lcov_branch_coverage=1 --quiet 2>/dev/null || true @@ -820,6 +829,7 @@ main() { echo "Script directory: $SCRIPT_DIR" echo "Unit test directory: $UNITTEST_DIR" echo "Source directory: $SRC_DIR" + echo "Watcher directory: $WATCHER_DIR" # Show mode if [ "$COVERAGE_LIST_ONLY" = "true" ]; then diff --git a/unittest/Makefile.am b/unittest/Makefile.am index 8f2f4f68..fa4d220e 100644 --- a/unittest/Makefile.am +++ b/unittest/Makefile.am @@ -23,10 +23,8 @@ AUTOMAKE_OPTIONS = foreign subdir-objects # Test programs -TESTS = config_manager_gtest platform_gtest scanner_gtest archive_gtest utils_gtest upload_gtest mainapp_gtest ratelimit_gtest lock_manager_gtest prerequisites_gtest logger_gtest -#TESTS = config_manager_gtest platform_gtest mainapp_gtest -#check_PROGRAMS = config_manager_gtest platform_gtest mainapp_gtest -check_PROGRAMS = config_manager_gtest platform_gtest scanner_gtest archive_gtest utils_gtest upload_gtest mainapp_gtest ratelimit_gtest lock_manager_gtest prerequisites_gtest logger_gtest +TESTS = config_manager_gtest platform_gtest scanner_gtest archive_gtest utils_gtest upload_gtest mainapp_gtest ratelimit_gtest lock_manager_gtest prerequisites_gtest logger_gtest watcher_gtest +check_PROGRAMS = config_manager_gtest platform_gtest scanner_gtest archive_gtest utils_gtest upload_gtest mainapp_gtest ratelimit_gtest lock_manager_gtest prerequisites_gtest logger_gtest watcher_gtest # Common include directories AM_CPPFLAGS = \ @@ -40,7 +38,8 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/../c_sourcecode/src/archive \ -I$(top_srcdir)/../c_sourcecode/src/utils \ -I$(top_srcdir)/../c_sourcecode/src/upload \ - -I$(top_srcdir)/../c_sourcecode/src/t2Interface \ + -I$(top_srcdir)/../c_sourcecode/src/t2Interface \ + -I$(top_srcdir)/../c_sourcecode/watcher \ -I/usr/include \ -I/usr/local/include @@ -110,9 +109,6 @@ scanner_gtest_SOURCES = \ ../c_sourcecode/src/scanner/scanner.c \ ../c_sourcecode/src/t2Interface/telemetryinterface.c -scanner_gtest_CFLAGS = -DUNIT_TEST -scanner_gtest_CXXFLAGS = -DUNIT_TEST - scanner_gtest_LDADD = \ -lgtest \ -lgtest_main \ @@ -140,9 +136,6 @@ archive_gtest_LDADD = \ -larchive \ -lgcov -archive_gtest_CFLAGS = -DUNIT_TEST -archive_gtest_CXXFLAGS = -DUNIT_TEST - archive_gtest_LDFLAGS = \ -larchive \ -fprofile-arcs @@ -154,9 +147,6 @@ utils_gtest_SOURCES = \ ../c_sourcecode/src/utils/file_utils.c \ ../c_sourcecode/src/utils/cleanup_batch.c -utils_gtest_CFLAGS = -DUNIT_TEST -utils_gtest_CXXFLAGS = -DUNIT_TEST - utils_gtest_LDADD = \ -lgtest \ -lgtest_main \ @@ -280,6 +270,23 @@ logger_gtest_LDADD = \ logger_gtest_LDFLAGS = \ -fprofile-arcs +# watcher_gtest configuration +watcher_gtest_SOURCES = \ + watcher_gtest.cpp \ + watcher_gmock.cpp \ + ../c_sourcecode/watcher/inotify-minidump-watcher.c + +watcher_gtest_LDADD = \ + -lgtest \ + -lgtest_main \ + -lgmock \ + -lgmock_main \ + -lpthread \ + -lgcov + +watcher_gtest_LDFLAGS = \ + -fprofile-arcs + # Coverage targets .PHONY: coverage coverage-clean coverage-report @@ -289,6 +296,8 @@ coverage-clean: @find . -name "*.gcno" -delete @find ../c_sourcecode/src/config -name "*.gcda" -delete 2>/dev/null || true @find ../c_sourcecode/src/config -name "*.gcno" -delete 2>/dev/null || true + @find ../c_sourcecode/watcher -name "*.gcda" -delete 2>/dev/null || true + @find ../c_sourcecode/watcher -name "*.gcno" -delete 2>/dev/null || true @rm -rf coverage_html coverage.info coverage: @@ -300,11 +309,11 @@ coverage: @echo "" @echo "Generating coverage report..." @echo "Capturing coverage from source directory..." - @lcov --capture --directory ../c_sourcecode/src --output-file coverage.info --rc lcov_branch_coverage=1 2>/dev/null || \ - lcov --capture --directory ../c_sourcecode/src --output-file coverage.info --rc lcov_branch_coverage=1 --ignore-errors gcov,source || \ - lcov --capture --directory ../c_sourcecode/src --output-file coverage.info --rc lcov_branch_coverage=1 + @lcov --capture --directory ../c_sourcecode --output-file coverage.info --rc lcov_branch_coverage=1 2>/dev/null || \ + lcov --capture --directory ../c_sourcecode --output-file coverage.info --rc lcov_branch_coverage=1 --ignore-errors gcov,source || \ + lcov --capture --directory ../c_sourcecode --output-file coverage.info --rc lcov_branch_coverage=1 @echo "Filtering coverage data..." - @lcov --extract coverage.info '*/c_sourcecode/src/*.c' --output-file coverage.info --rc lcov_branch_coverage=1 + @lcov --extract coverage.info '*/c_sourcecode/src/*.c' '*/c_sourcecode/watcher/*.c' '*/inotify-minidump-watcher.c' --output-file coverage.info --rc lcov_branch_coverage=1 @lcov --remove coverage.info '/usr/*' --output-file coverage.info --rc lcov_branch_coverage=1 @echo "Generating HTML report..." @genhtml coverage.info --output-directory coverage_html --branch-coverage 2>/dev/null || \ @@ -319,7 +328,7 @@ coverage: coverage-report: @echo "Coverage Summary for all source files:" @echo "=========================================" - @lcov --list coverage.info --rc lcov_branch_coverage=1 | grep -E "(Lines|Functions|Branches|c_sourcecode/src)" || echo "No coverage data available" + @lcov --list coverage.info --rc lcov_branch_coverage=1 | grep -E "(Lines|Functions|Branches|c_sourcecode/src|c_sourcecode/watcher)" || echo "No coverage data available" @echo "=========================================" # Clean targets diff --git a/unittest/archive_gtest.cpp b/unittest/archive_gtest.cpp index 1d7e68f7..06f8a705 100644 --- a/unittest/archive_gtest.cpp +++ b/unittest/archive_gtest.cpp @@ -57,7 +57,7 @@ extern "C" { #include "../c_sourcecode/common/errors.h" #if 0 -// Forward declarations for static functions (with UNIT_TEST flag) +// Forward declarations for static functions (exposed via STATIC_TESTABLE with GTEST_ENABLE) long get_free_space_mb(const char *path); void get_dirname(const char *path, char *dir, size_t dir_size); #endif @@ -1073,7 +1073,7 @@ TEST_F(ArchiveTest, ArchiveCreateSmart_FilesCleanedUpAfterSuccess) { #if 0 // ============================================================================ -// Static Function Tests (with UNIT_TEST flag) +// Static Function Tests (exposed via STATIC_TESTABLE with GTEST_ENABLE) // ============================================================================ // get_dirname() tests diff --git a/unittest/scanner_gtest.cpp b/unittest/scanner_gtest.cpp index a0294984..11a6b904 100644 --- a/unittest/scanner_gtest.cpp +++ b/unittest/scanner_gtest.cpp @@ -73,7 +73,7 @@ int join_path(char *dest, size_t dest_size, const char *dir, const char *name); void t2ValNotify(const char *key, const char *val); void t2CountNotify(const char *key, const char *val_or_null); -// Forward declarations for static functions in scanner.c (exposed via STATIC_TESTABLE with UNIT_TEST) +// Forward declarations for static functions in scanner.c (exposed via STATIC_TESTABLE with GTEST_ENABLE) int append_logfile_entry(const char *entry); int is_allowed_char(char c); char *sanitize_segment(const char *s); @@ -1165,7 +1165,7 @@ TEST_F(ScannerTest, ConcurrentAccess_MultipleCleanups) { } // ============================================================================ -// Static Function Tests (exposed via STATIC_TESTABLE with UNIT_TEST) +// Static Function Tests (exposed via STATIC_TESTABLE with GTEST_ENABLE) // ============================================================================ // ----------- append_logfile_entry Tests ----------- diff --git a/unittest/watcher_gmock.cpp b/unittest/watcher_gmock.cpp new file mode 100644 index 00000000..6e313f8a --- /dev/null +++ b/unittest/watcher_gmock.cpp @@ -0,0 +1,308 @@ +/** + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * @file watcher_gmock.cpp + * @brief Mock syscall wrappers for inotify-minidump-watcher L1 tests + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct FakeRead { + bool fail; + bool zero_count; + int err; + int wd; + uint32_t mask; + std::string name; +}; + +static int g_init_ret = 1; +static int g_init_err = 0; +static int g_watch_ret = 2; +static int g_watch_err = 0; +static std::vector g_reads; +static size_t g_read_i = 0; +static std::vector g_closes; +static size_t g_close_i = 0; +static int g_close_err = 0; +static int g_system_ret = 0; +static char g_last_cmd[PATH_MAX]; +static int g_printf_fail = 0; +static int g_fnmatch_override = 0; +static int g_fnmatch_value = 0; +static int g_sigaction_ret = 0; +static int g_sigaction_err = 0; + +extern "C" { + +void watcher_mock_reset(void) +{ + g_init_ret = 1; + g_init_err = 0; + g_watch_ret = 2; + g_watch_err = 0; + g_reads.clear(); + g_read_i = 0; + g_closes.clear(); + g_close_i = 0; + g_close_err = 0; + g_system_ret = 0; + memset(g_last_cmd, 0, sizeof(g_last_cmd)); + g_printf_fail = 0; + g_fnmatch_override = 0; + g_fnmatch_value = 0; + g_sigaction_ret = 0; + g_sigaction_err = 0; +} + +void watcher_mock_set_inotify_init(int ret, int err) +{ + g_init_ret = ret; + g_init_err = err; +} + +void watcher_mock_set_add_watch(int ret, int err) +{ + g_watch_ret = ret; + g_watch_err = err; +} + +void watcher_mock_push_read_event(int wd, uint32_t mask, const char *name) +{ + FakeRead ev; + ev.fail = false; + ev.zero_count = false; + ev.err = 0; + ev.wd = wd; + ev.mask = mask; + ev.name = (name != NULL) ? name : ""; + g_reads.push_back(ev); +} + +void watcher_mock_push_read_zero(void) +{ + FakeRead ev; + ev.fail = false; + ev.zero_count = true; + ev.err = 0; + ev.wd = -1; + ev.mask = 0; + ev.name.clear(); + g_reads.push_back(ev); +} + +void watcher_mock_push_read_fail(int err) +{ + FakeRead ev; + ev.fail = true; + ev.zero_count = false; + ev.err = err; + ev.wd = -1; + ev.mask = 0; + ev.name.clear(); + g_reads.push_back(ev); +} + +void watcher_mock_push_close(int ret) +{ + g_closes.push_back(ret); +} + +void watcher_mock_set_close_errno(int err) +{ + g_close_err = err; +} + +void watcher_mock_set_system_ret(int ret) +{ + g_system_ret = ret; +} + +const char *watcher_mock_last_system_cmd(void) +{ + return g_last_cmd; +} + +void watcher_mock_set_printf_fail(int fail) +{ + g_printf_fail = fail; +} + +void watcher_mock_set_fnmatch_result(int use_override, int value) +{ + g_fnmatch_override = use_override; + g_fnmatch_value = value; +} + +void watcher_mock_set_sigaction_ret(int ret, int err) +{ + g_sigaction_ret = ret; + g_sigaction_err = err; +} + +int mock_inotify_init(void) +{ + if (g_init_ret < 0) + { + errno = g_init_err ? g_init_err : EMFILE; + } + return g_init_ret; +} + +int mock_inotify_add_watch(int fd, const char *pathname, uint32_t mask) +{ + (void)fd; + (void)pathname; + (void)mask; + if (g_watch_ret < 0) + { + errno = g_watch_err ? g_watch_err : ENOENT; + } + return g_watch_ret; +} + +ssize_t mock_read(int fd, void *buf, size_t count) +{ + (void)fd; + if (g_read_i >= g_reads.size()) + { + errno = EAGAIN; + return -1; + } + + const FakeRead &ev = g_reads[g_read_i++]; + if (ev.fail) + { + errno = ev.err ? ev.err : EIO; + return -1; + } + if (ev.zero_count) + { + return 0; + } + + if (buf == NULL || count < sizeof(struct inotify_event)) + { + errno = EINVAL; + return -1; + } + + memset(buf, 0, count); + struct inotify_event *event_ptr = static_cast(buf); + event_ptr->wd = ev.wd; + event_ptr->mask = ev.mask; + event_ptr->cookie = 0; + + if (ev.name.empty()) + { + event_ptr->len = 0; + return static_cast(sizeof(struct inotify_event)); + } + + size_t name_bytes = ev.name.size() + 1; + size_t max_name = (count > sizeof(struct inotify_event)) + ? (count - sizeof(struct inotify_event)) + : 0; + if (name_bytes > max_name) + { + name_bytes = max_name; + } + event_ptr->len = static_cast(name_bytes); + if (name_bytes > 0) + { + memcpy(event_ptr->name, ev.name.c_str(), name_bytes - 1); + event_ptr->name[name_bytes - 1] = '\0'; + } + return static_cast(sizeof(struct inotify_event) + name_bytes); +} + +int mock_close(int fd) +{ + (void)fd; + if (g_close_i >= g_closes.size()) + { + return 0; + } + int ret = g_closes[g_close_i++]; + if (ret < 0) + { + errno = g_close_err ? g_close_err : EIO; + } + return ret; +} + +int mock_system(const char *command) +{ + if (command != NULL) + { + strncpy(g_last_cmd, command, sizeof(g_last_cmd) - 1); + g_last_cmd[sizeof(g_last_cmd) - 1] = '\0'; + } + if (g_system_ret < 0) + { + errno = EAGAIN; + } + return g_system_ret; +} + +int mock_printf(const char *fmt, ...) +{ + if (g_printf_fail) + { + (void)fmt; + errno = EIO; + return -1; + } + va_list ap; + va_start(ap, fmt); + int n = vprintf(fmt, ap); + va_end(ap); + return n; +} + +int mock_fnmatch(const char *pattern, const char *string, int flags) +{ + if (g_fnmatch_override) + { + return g_fnmatch_value; + } + return fnmatch(pattern, string, flags); +} + +int mock_sigaction(int signum, const struct sigaction *act, struct sigaction *oldact) +{ + (void)signum; + (void)act; + (void)oldact; + if (g_sigaction_ret < 0) + { + errno = g_sigaction_err ? g_sigaction_err : EINVAL; + } + return g_sigaction_ret; +} + +} diff --git a/unittest/watcher_gtest.cpp b/unittest/watcher_gtest.cpp new file mode 100644 index 00000000..5103ed80 --- /dev/null +++ b/unittest/watcher_gtest.cpp @@ -0,0 +1,230 @@ +/** + * Copyright 2025 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/** + * @file watcher_gtest.cpp + * @brief L1 GTest suite for inotify-minidump-watcher + * + * Compiles inotify-minidump-watcher.c with -DGTEST_ENABLE. + * Extender builds omit that flag, so production behavior is unchanged. + */ + +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +int watcher_main(int argc, const char *const *argv); +int directory_watcher(const char *directory, + const char *command_to_run, + const char *command_args, + const char *const *patterns, + size_t pattern_count); +void process_interrupt_handler(int s); +void watcher_test_reset(void); + +void watcher_mock_reset(void); +void watcher_mock_set_inotify_init(int ret, int err); +void watcher_mock_set_add_watch(int ret, int err); +void watcher_mock_push_read_event(int wd, uint32_t mask, const char *name); +void watcher_mock_push_read_fail(int err); +void watcher_mock_push_close(int ret); +void watcher_mock_set_close_errno(int err); +void watcher_mock_set_system_ret(int ret); +const char *watcher_mock_last_system_cmd(void); +void watcher_mock_set_printf_fail(int fail); +void watcher_mock_set_fnmatch_result(int use_override, int value); +void watcher_mock_set_sigaction_ret(int ret, int err); +} + +class WatcherTest : public ::testing::Test { +protected: + void SetUp() override { + watcher_mock_reset(); + watcher_test_reset(); + } +}; + +TEST_F(WatcherTest, WatcherMain_TooFewArgs_ReturnsFailure) { + const char *argv[] = {"inotify-minidump-watcher", "/tmp", "cmd", "args"}; + EXPECT_EQ(watcher_main(4, argv), EXIT_FAILURE); +} + +TEST_F(WatcherTest, WatcherMain_SigactionFails) { + watcher_mock_set_sigaction_ret(-1, EINVAL); + const char *argv[] = { + "inotify-minidump-watcher", "/minidumps", "/bin/true", "0", "*.dmp" + }; + /* Production ignores sigaction() return and still enters the watcher. */ + watcher_mock_set_inotify_init(-1, EMFILE); + EXPECT_EQ(watcher_main(5, argv), EXIT_FAILURE); +} + +TEST_F(WatcherTest, WatcherMain_DirectoryWatcherFailure) { + watcher_mock_set_inotify_init(-1, EMFILE); + const char *argv[] = { + "inotify-minidump-watcher", "/minidumps", "/bin/true", "0", "*.dmp" + }; + EXPECT_EQ(watcher_main(5, argv), EXIT_FAILURE); +} + +TEST_F(WatcherTest, WatcherMain_MatchingDump_RunsCommand) { + watcher_mock_push_read_event(2, IN_CREATE, "crash.dmp"); + process_interrupt_handler(SIGINT); + watcher_mock_push_read_fail(EINTR); + const char *argv[] = { + "inotify-minidump-watcher", "/minidumps", "/bin/true", "0", "*.dmp" + }; + EXPECT_EQ(watcher_main(5, argv), EXIT_SUCCESS); + EXPECT_STREQ(watcher_mock_last_system_cmd(), "sh -c '/bin/true 0'"); +} + +TEST_F(WatcherTest, DirectoryWatcher_InotifyInitFails) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_set_inotify_init(-1, EMFILE); + EXPECT_EQ(directory_watcher("/tmp", "cmd", "args", patterns, 1), -1); +} + +TEST_F(WatcherTest, DirectoryWatcher_AddWatchFails) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_set_add_watch(-1, ENOENT); + EXPECT_EQ(directory_watcher("/missing", "cmd", "args", patterns, 1), -1); +} + +TEST_F(WatcherTest, DirectoryWatcher_ReadErrorWithoutInterrupt) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_push_read_fail(EIO); + EXPECT_EQ(directory_watcher("/tmp", "cmd", "args", patterns, 1), -1); +} + +TEST_F(WatcherTest, DirectoryWatcher_ReadErrorWithInterrupt) { + const char *patterns[] = {"*.dmp"}; + process_interrupt_handler(SIGINT); + watcher_mock_push_read_fail(EINTR); + EXPECT_EQ(directory_watcher("/tmp", "cmd", "args", patterns, 1), 0); +} + +TEST_F(WatcherTest, DirectoryWatcher_NonSigintDoesNotInterrupt) { + const char *patterns[] = {"*.dmp"}; + process_interrupt_handler(SIGTERM); + watcher_mock_push_read_fail(EIO); + EXPECT_EQ(directory_watcher("/tmp", "cmd", "args", patterns, 1), -1); +} + +TEST_F(WatcherTest, DirectoryWatcher_EmptyNameThenNullCommand) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_push_read_event(2, IN_CREATE, ""); + watcher_mock_push_read_event(2, IN_CREATE, "flag.dmp"); + EXPECT_EQ(directory_watcher("/tmp", "NULL", "", patterns, 1), 0); +} + +TEST_F(WatcherTest, DirectoryWatcher_NoMatchThenMatch) { + const char *patterns[] = {"*.txt", "*.dmp"}; + watcher_mock_push_read_event(2, IN_CREATE, "notes.txt"); + watcher_mock_push_read_event(2, IN_CREATE, "crash.dmp"); + EXPECT_EQ(directory_watcher("/tmp", "NULL", "", patterns, 2), 0); +} + +TEST_F(WatcherTest, DirectoryWatcher_FnmatchError) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_set_fnmatch_result(1, -1); + watcher_mock_push_read_event(2, IN_CREATE, "crash.dmp"); + EXPECT_EQ(directory_watcher("/tmp", "cmd", "args", patterns, 1), -1); +} + +TEST_F(WatcherTest, DirectoryWatcher_PrintfFailure) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_set_printf_fail(1); + watcher_mock_push_read_event(2, IN_CREATE, "crash.dmp"); + EXPECT_EQ(directory_watcher("/tmp", "cmd", "args", patterns, 1), -1); +} + +TEST_F(WatcherTest, DirectoryWatcher_NullCommandArgs) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_push_read_event(2, IN_CREATE, "crash.dmp"); + EXPECT_EQ(directory_watcher("/tmp", "cmd", nullptr, patterns, 1), -1); +} + +TEST_F(WatcherTest, DirectoryWatcher_CommandOverflow) { + const char *patterns[] = {"*.dmp"}; + std::string long_cmd(60, 'A'); + watcher_mock_push_read_event(2, IN_CREATE, "crash.dmp"); + EXPECT_EQ(directory_watcher("/tmp", long_cmd.c_str(), "args", patterns, 1), -1); +} + +TEST_F(WatcherTest, DirectoryWatcher_SystemThenReadError) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_set_system_ret(-1); + watcher_mock_push_read_event(2, IN_CREATE, "crash.dmp"); + watcher_mock_push_read_fail(EIO); + EXPECT_EQ(directory_watcher("/tmp", "/bin/false", "1", patterns, 1), -1); + EXPECT_STREQ(watcher_mock_last_system_cmd(), "sh -c '/bin/false 1'"); +} + +TEST_F(WatcherTest, DirectoryWatcher_NullCommandExitsOnFirstMatch) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_push_read_event(2, IN_CREATE, "first.dmp"); + watcher_mock_push_read_event(2, IN_CREATE, "second.dmp"); + EXPECT_EQ(directory_watcher("/tmp", "NULL", "", patterns, 1), 0); +} + +TEST_F(WatcherTest, DirectoryWatcher_RunsSystemThenInterruptExits) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_push_read_event(2, IN_CREATE, "first.dmp"); + watcher_mock_set_system_ret(0); + process_interrupt_handler(SIGINT); + watcher_mock_push_read_fail(EINTR); + EXPECT_EQ(directory_watcher("/tmp", "/lib/rdk/uploadDumps.sh", "0", patterns, 1), 0); + EXPECT_STREQ(watcher_mock_last_system_cmd(), "sh -c '/lib/rdk/uploadDumps.sh 0'"); +} + +TEST_F(WatcherTest, DirectoryWatcher_CloseWatchFdFails) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_push_read_event(2, IN_CREATE, "flag.dmp"); + watcher_mock_push_close(-1); + watcher_mock_set_close_errno(EIO); + EXPECT_EQ(directory_watcher("/tmp", "NULL", "", patterns, 1), -1); +} + +TEST_F(WatcherTest, DirectoryWatcher_CloseNotifyFdFails) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_push_read_event(2, IN_CREATE, "flag.dmp"); + watcher_mock_push_close(0); + watcher_mock_push_close(-1); + watcher_mock_set_close_errno(EIO); + EXPECT_EQ(directory_watcher("/tmp", "NULL", "", patterns, 1), -1); +} + +TEST_F(WatcherTest, DirectoryWatcher_ZeroPatternCountThenInterrupt) { + const char *patterns[] = {"*.dmp"}; + watcher_mock_push_read_event(2, IN_CREATE, "crash.dmp"); + process_interrupt_handler(SIGINT); + watcher_mock_push_read_fail(EINTR); + EXPECT_EQ(directory_watcher("/tmp", "cmd", "args", patterns, 0), 0); +} + +TEST_F(WatcherTest, DirectoryWatcher_FnmatchNomatchOnlyThenInterrupt) { + const char *patterns[] = {"*.core"}; + watcher_mock_push_read_event(2, IN_CREATE, "crash.dmp"); + process_interrupt_handler(SIGINT); + watcher_mock_push_read_fail(EINTR); + EXPECT_EQ(directory_watcher("/tmp", "cmd", "args", patterns, 1), 0); +} From 8ef7082e186384d8fc1ca2ef5ba505fb6f71146c Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Mon, 21 Sep 2026 11:49:08 +0530 Subject: [PATCH 04/14] Add system() return handling --- c_sourcecode/watcher/inotify-minidump-watcher.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/c_sourcecode/watcher/inotify-minidump-watcher.c b/c_sourcecode/watcher/inotify-minidump-watcher.c index 7160c880..5029abd3 100755 --- a/c_sourcecode/watcher/inotify-minidump-watcher.c +++ b/c_sourcecode/watcher/inotify-minidump-watcher.c @@ -198,7 +198,14 @@ directory_watcher(const char *const directory, } sprintf(command,"sh -c '%s %s'",command_to_run,command_args); - system(command); + /* Native/CI builds compile this path (no YOCTO_BUILD). + * glibc marks system() warn_unused_result; ignore the + * status the same way the original watcher did. */ + if (system(command) < 0) + { + errmsg = "system"; + goto catch; + } #endif printf("The script /lib/rdk/uploadDumps.sh execution completed..!"); } From a84ec9bb7f97a89bd4ca408dc29150b5ba778944 Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Mon, 21 Sep 2026 11:54:29 +0530 Subject: [PATCH 05/14] Add system() return handling --- c_sourcecode/watcher/inotify-minidump-watcher.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/c_sourcecode/watcher/inotify-minidump-watcher.c b/c_sourcecode/watcher/inotify-minidump-watcher.c index 5029abd3..ca1f6892 100755 --- a/c_sourcecode/watcher/inotify-minidump-watcher.c +++ b/c_sourcecode/watcher/inotify-minidump-watcher.c @@ -49,7 +49,8 @@ int mock_sigaction(int signum, const struct sigaction *act, struct sigaction *ol #define system mock_system #define printf mock_printf #define fnmatch mock_fnmatch -#define sigaction mock_sigaction +/* Function-like so "struct sigaction" is not rewritten. */ +#define sigaction(signum, act, oldact) mock_sigaction((signum), (act), (oldact)) #else #define STATIC_TESTABLE static #endif From b26a3225280b3ca027917692b9294573c9ffc313 Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Mon, 21 Sep 2026 13:43:11 +0530 Subject: [PATCH 06/14] Update EVP API compatible for OpenSSL in Extender --- c_sourcecode/src/utils/file_utils.c | 10 ++++++++++ uploadDumps.sh | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/c_sourcecode/src/utils/file_utils.c b/c_sourcecode/src/utils/file_utils.c index 7c90769c..99f2973a 100644 --- a/c_sourcecode/src/utils/file_utils.c +++ b/c_sourcecode/src/utils/file_utils.c @@ -431,7 +431,11 @@ int compute_s3_md5_base64(const char *filepath, if (!fp) goto cleanup; +#if OPENSSL_VERSION_NUMBER < 0x10100000L + mdctx = EVP_MD_CTX_create(); +#else mdctx = EVP_MD_CTX_new(); +#endif if (!mdctx) goto cleanup; @@ -480,7 +484,13 @@ int compute_s3_md5_base64(const char *filepath, if (fp) fclose(fp); if (mdctx) + { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + EVP_MD_CTX_destroy(mdctx); +#else EVP_MD_CTX_free(mdctx); +#endif + } if (b64) BIO_free_all(b64); diff --git a/uploadDumps.sh b/uploadDumps.sh index 2702c44b..b76a477f 100644 --- a/uploadDumps.sh +++ b/uploadDumps.sh @@ -21,7 +21,9 @@ # #Purpose : This script is to used to create and upload dump files #Scope : RDK Devices -#Usage : Triggered by a path based systemd service +#Usage : Triggered by a path based systemd service (broadband/video) +# or by inotify-minidump-watcher via SysV init (extender): +# /lib/rdk/uploadDumps.sh "" 0 #This file is from crashupload repository #Uploads coredumps to an ftp server if there are any From ac7101fb8815721d0056d7cbb99c6ab357101509 Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Mon, 21 Sep 2026 14:06:13 +0530 Subject: [PATCH 07/14] Add bound checks for system and v_secure_system calls --- .../watcher/inotify-minidump-watcher.c | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/c_sourcecode/watcher/inotify-minidump-watcher.c b/c_sourcecode/watcher/inotify-minidump-watcher.c index ca1f6892..11999dc7 100755 --- a/c_sourcecode/watcher/inotify-minidump-watcher.c +++ b/c_sourcecode/watcher/inotify-minidump-watcher.c @@ -175,6 +175,11 @@ directory_watcher(const char *const directory, } else { + if (command_to_run == NULL || command_args == NULL) + { + errmsg = "NULL"; + goto catch; + } /* Exit from wait if the command to run is NULL */ if(strncmp(command_to_run,"NULL",4) == 0){ printf("Flag file is created. Exiting from wait \n"); @@ -182,26 +187,23 @@ directory_watcher(const char *const directory, } printf("Calling the binary %s\n",command_to_run); #ifdef YOCTO_BUILD - v_secure_system("sh -c '%s %s'",command_to_run,command_args); -#else - char command[50]; - - if(command_to_run == NULL || command_args == NULL) + if (v_secure_system("sh -c '%s %s'",command_to_run,command_args) != 0) { - errmsg = "NULL"; + errmsg = "v_secure_system"; goto catch; } +#else + char command[PATH_MAX]; + int n; - if (sizeof(command) <= (strlen(command_to_run)+strlen(command_args)+strlen("ssh -c ' '"))) + n = snprintf(command, sizeof(command), "sh -c '%s %s'", + command_to_run, command_args); + if (n < 0 || (size_t)n >= sizeof(command)) { errmsg = "command buffer overflow"; goto catch; } - sprintf(command,"sh -c '%s %s'",command_to_run,command_args); - /* Native/CI builds compile this path (no YOCTO_BUILD). - * glibc marks system() warn_unused_result; ignore the - * status the same way the original watcher did. */ if (system(command) < 0) { errmsg = "system"; From de45a680e351fafc2356a8e0e2c8d58520564454 Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Tue, 22 Sep 2026 12:56:06 +0530 Subject: [PATCH 08/14] Address review comments --- c_sourcecode/src/platform/platform.c | 9 +++++++-- c_sourcecode/src/upload/upload.c | 5 +++-- uploadDumps.sh | 8 +++++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/c_sourcecode/src/platform/platform.c b/c_sourcecode/src/platform/platform.c index 0661cda3..da6ef3c9 100644 --- a/c_sourcecode/src/platform/platform.c +++ b/c_sourcecode/src/platform/platform.c @@ -162,14 +162,19 @@ int platform_initialize(const config_t *config, platform_config_t *platform) char wan_if[32] = {0}; if (config->device_type == DEVICE_TYPE_EXTENDER) { - if (config->comm_interface[0] != '\0') + if (config->comm_interface[0] != '\0'){ snprintf(wan_if, sizeof(wan_if), "%s", config->comm_interface); - else + CRASHUPLOAD_INFO("Got WAN interface from comm_interface %s", wan_if); + } + else{ snprintf(wan_if, sizeof(wan_if), "%s", EXTENDER_WAN_INTERFACE); + CRASHUPLOAD_INFO("Got WAN interface from fallback default %s", wan_if); + } } else { snprintf(wan_if, sizeof(wan_if), "%s", get_interface_value()); + CRASHUPLOAD_INFO("Got WAN interface from get_interface_value() %s", wan_if); } if (wan_if[0] != '\0' && strcmp(wan_if, "unknown") != 0) { diff --git a/c_sourcecode/src/upload/upload.c b/c_sourcecode/src/upload/upload.c index 29d95fc9..1f35657a 100644 --- a/c_sourcecode/src/upload/upload.c +++ b/c_sourcecode/src/upload/upload.c @@ -408,14 +408,15 @@ int upload_process(archive_info_t *archive, const config_t *config, const platfo if (config->device_type == DEVICE_TYPE_EXTENDER) { /* Extender: partnerId sourced from account JSON, not from partner_id file */ - char persistent_path[64] = {0}; + char persistent_path[64]; + persistent_path[0] = 0; char account_file[80] = {0}; if (getIncludePropertyData("PERSISTENT_PATH", persistent_path, sizeof(persistent_path)) != UTILS_SUCCESS || persistent_path[0] == '\0') { snprintf(persistent_path, sizeof(persistent_path), "%s", PERSISTENT_PATH); - CRASHUPLOAD_WARN("Extender: PERSISTENT_PATH property missing, default=%s\n", persistent_path); + CRASHUPLOAD_INFO("Extender: PERSISTENT_PATH property missing, default=%s\n", persistent_path); } else { diff --git a/uploadDumps.sh b/uploadDumps.sh index b76a477f..dd7394ed 100644 --- a/uploadDumps.sh +++ b/uploadDumps.sh @@ -42,16 +42,22 @@ case "$DEVICE_TYPE" in mediaclient) LOG_DIR="/opt/logs" ;; - broadband|extender) + broadband) LOG_DIR="/rdklogs/logs" LOG_APPENDER=".0" ;; + extender) + LOG_DIR="/var/log" + ;; *) LOG_DIR="/var/log" ;; esac CORE_LOG="${LOG_DIR}/core_log.txt${LOG_APPENDER}" +if [ "$DEVICE_TYPE" = "extender" ]; then + CORE_LOG="/var/log/messages" +fi Log() { echo "`/bin/timestamp` [uploadDumps.sh] [PID:$$]: $*" >> $CORE_LOG From 8041adac0820391e9add75f8acc63dc5f27f4d7f Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Wed, 23 Sep 2026 10:41:12 +0530 Subject: [PATCH 09/14] Update model_num and partnerId parsing for extender --- c_sourcecode/src/platform/platform.c | 63 ++++++++++++++- c_sourcecode/src/upload/upload.c | 114 +++++++++++++++++++++------ unittest/platform_gtest.cpp | 85 ++++++++++++++++++++ unittest/upload_gtest.cpp | 61 ++++++++++++++ 4 files changed, 294 insertions(+), 29 deletions(-) diff --git a/c_sourcecode/src/platform/platform.c b/c_sourcecode/src/platform/platform.c index da6ef3c9..a60a67ec 100644 --- a/c_sourcecode/src/platform/platform.c +++ b/c_sourcecode/src/platform/platform.c @@ -25,6 +25,58 @@ #include "../rbusInterface/rbus_interface.h" #include #include +#include + +#ifdef GTEST_ENABLE +#define STATIC_TESTABLE +#else +#define STATIC_TESTABLE static +#endif + +#define EXTENDER_CTS_MODEL "GR-EXT02A-CTS" + +/* + * Script parity (extender utils.sh getModelNum): + * MODEL_NUM=GR-EXT02A-CTS -> strip hyphens + * else BOX_TYPE (e.g. XE2). GetModelNum() only reads MODEL_NUM=. + */ +STATIC_TESTABLE void apply_extender_model(char *model, size_t model_size, const char *box_type) +{ + size_t i; + size_t j; + + if (!model || model_size == 0) + { + return; + } + + if (strcmp(model, EXTENDER_CTS_MODEL) == 0) + { + j = 0; + for (i = 0; model[i] != '\0' && j + 1 < model_size; i++) + { + if (model[i] != '-') + { + model[j++] = model[i]; + } + } + model[j] = '\0'; + return; + } + + if (box_type && box_type[0] != '\0' && strcmp(box_type, "UNKNOWN") != 0) + { + strncpy(model, box_type, model_size - 1); + model[model_size - 1] = '\0'; + return; + } + + if (model[0] == '\0') + { + strncpy(model, "UNKNOWN", model_size - 1); + model[model_size - 1] = '\0'; + } +} /* function NormalizeMac - gets the eSTB MAC address of the device. @@ -193,16 +245,21 @@ int platform_initialize(const config_t *config, platform_config_t *platform) strcpy(platform->mac_address, "000000000000"); } } - // TODO: For broadband and extender we have change the code ret = GetModelNum(platform->model, sizeof(platform->model)); - if (ret) + if (config && config->device_type == DEVICE_TYPE_EXTENDER) + { + apply_extender_model(platform->model, sizeof(platform->model), config->box_type); + CRASHUPLOAD_INFO("Model Num=%s\n", platform->model); + } + else if (ret) { CRASHUPLOAD_INFO("Model Num=%s\n", platform->model); } else { CRASHUPLOAD_ERROR("GetModel is failed. Setting default value\n"); - strcpy(platform->model, "UNKNOWN"); + strncpy(platform->model, "UNKNOWN", sizeof(platform->model) - 1); + platform->model[sizeof(platform->model) - 1] = '\0'; } ret = file_get_sha1("/version.txt", platform->platform_sha1, sizeof(platform->platform_sha1)); if (ret == 0) diff --git a/c_sourcecode/src/upload/upload.c b/c_sourcecode/src/upload/upload.c index 1f35657a..0d8914fa 100644 --- a/c_sourcecode/src/upload/upload.c +++ b/c_sourcecode/src/upload/upload.c @@ -34,10 +34,97 @@ #include "telemetryinterface.h" #include "../utils/logger.h" +#ifdef GTEST_ENABLE +#define STATIC_TESTABLE +#else +#define STATIC_TESTABLE static +#endif + #define MAX_RETRIES 3 #define TIMEOUT_SECONDS 45 #define RETRY_DELAY_SECONDS 5 #define SIZE_POSTFIELD_BUF 2048 +#define PARTNER_ID_KEY "\"partnerId\"" +#define ACCOUNT_READ_MAX 4096 + +/* + * Account files on extender are a binary blob wrapping JSON (NULs in the + * prefix). Scan raw bytes; do not use fgets/strstr C-strings. + * Returns 1 if a non-empty partnerId was copied. + */ +STATIC_TESTABLE int extract_partner_id_from_mem(const char *buf, size_t n, char *out, size_t out_size) +{ + const size_t klen = sizeof(PARTNER_ID_KEY) - 1; + size_t i; + size_t j; + size_t v; + + if (!buf || !out || out_size == 0) + { + return 0; + } + out[0] = '\0'; + if (n < klen) + { + return 0; + } + + for (i = 0; i + klen <= n; i++) + { + if (memcmp(buf + i, PARTNER_ID_KEY, klen) != 0) + { + continue; + } + j = i + klen; + while (j < n && (buf[j] == ' ' || buf[j] == '\t' || buf[j] == '\r' || buf[j] == '\n')) + { + j++; + } + if (j >= n || buf[j] != ':') + { + continue; + } + j++; + while (j < n && (buf[j] == ' ' || buf[j] == '\t' || buf[j] == '\r' || buf[j] == '\n')) + { + j++; + } + if (j >= n || buf[j] != '"') + { + continue; + } + j++; + v = 0; + while (j < n && buf[j] != '"' && buf[j] != '\0' && v + 1 < out_size) + { + out[v++] = buf[j++]; + } + out[v] = '\0'; + return (v > 0) ? 1 : 0; + } + return 0; +} + +STATIC_TESTABLE int extract_partner_id_from_account(const char *path, char *out, size_t out_size) +{ + FILE *fp; + char buf[ACCOUNT_READ_MAX]; + size_t n; + + if (!path || !out || out_size == 0) + { + return 0; + } + out[0] = '\0'; + fp = fopen(path, "rb"); + if (!fp) + { + return 0; + } + n = fread(buf, 1, sizeof(buf), fp); + fclose(fp); + return extract_partner_id_from_mem(buf, n, out, out_size); +} #ifdef RDKC #define RDKC_PARTNER_ID_FILE "/opt/usr_config/partnerid.txt" @@ -426,32 +513,7 @@ int upload_process(archive_info_t *archive, const config_t *config, const platfo CRASHUPLOAD_INFO("Extender: PERSISTENT_PATH=%s\n", persistent_path); } snprintf(account_file, sizeof(account_file), "%s/account", persistent_path); - FILE *fp = fopen(account_file, "r"); - if (fp) - { - char line[512] = {0}; - while (fgets(line, sizeof(line), fp)) - { - char *p = strstr(line, "\"partnerId\":\""); - if (p) - { - p += 13; - char *end = strchr(p, '"'); - if (end) - { - size_t len = (size_t)(end - p); - if (len < sizeof(pPartnerId)) - { - strncpy(pPartnerId, p, len); - pPartnerId[len] = '\0'; - } - } - break; - } - } - fclose(fp); - } - ret = (pPartnerId[0] != '\0') ? 1 : 0; + ret = extract_partner_id_from_account(account_file, pPartnerId, sizeof(pPartnerId)); } else { diff --git a/unittest/platform_gtest.cpp b/unittest/platform_gtest.cpp index 7e32357f..9f6cd7ef 100644 --- a/unittest/platform_gtest.cpp +++ b/unittest/platform_gtest.cpp @@ -55,6 +55,7 @@ size_t stripinvalidchar(char* str, size_t len); //size_t GetHwMacAddress(const char* interface, char* mac, size_t len); size_t GetModelNum(char* model, size_t len); int file_get_sha1(const char* path, char* hash, size_t len); +void apply_extender_model(char *model, size_t model_size, const char *box_type); // Mock control functions void set_mock_stripinvalidchar_behavior(int return_value); @@ -983,6 +984,90 @@ TEST_F(PlatformTest, platform_initialize_MemorySafety_StructureIntegrity) { EXPECT_EQ((unsigned char)guard_buffer[sizeof(guard_buffer) - 1], 0xCC); } +TEST_F(PlatformTest, apply_extender_model_BoxTypeFallback) { + char model[64]; + memset(model, 0, sizeof(model)); + apply_extender_model(model, sizeof(model), "XE2"); + EXPECT_STREQ(model, "XE2"); +} + +TEST_F(PlatformTest, apply_extender_model_CtsStripsHyphens) { + char model[64]; + strncpy(model, "GR-EXT02A-CTS", sizeof(model) - 1); + model[sizeof(model) - 1] = '\0'; + apply_extender_model(model, sizeof(model), "XE2"); + EXPECT_STREQ(model, "GREXT02ACTS"); +} + +TEST_F(PlatformTest, apply_extender_model_EmptyBoxType_Unknown) { + char model[64]; + memset(model, 0, sizeof(model)); + apply_extender_model(model, sizeof(model), "UNKNOWN"); + EXPECT_STREQ(model, "UNKNOWN"); + model[0] = '\0'; + apply_extender_model(model, sizeof(model), NULL); + EXPECT_STREQ(model, "UNKNOWN"); +} + +TEST_F(PlatformTest, apply_extender_model_NullModel_NoCrash) { + apply_extender_model(NULL, 64, "XE2"); + SUCCEED(); +} + +TEST_F(PlatformTest, platform_initialize_Extender_UsesBoxTypeWhenModelMissing) { + create_mac_file("AA:BB:CC:DD:EE:FF\n"); + set_mock_stripinvalidchar_behavior(17); + set_mock_GetModelNum_behavior(0, NULL); + set_mock_file_get_sha1_behavior(0, "0123456789abcdef0123456789abcdef01234567"); + + config_t config; + memset(&config, 0, sizeof(config_t)); + config.device_type = DEVICE_TYPE_EXTENDER; + strncpy(config.box_type, "XE2", sizeof(config.box_type) - 1); + platform_config_t platform; + memset(&platform, 0, sizeof(platform_config_t)); + + int result = platform_initialize(&config, &platform); + EXPECT_EQ(result, PLATFORM_INIT_SUCCESS); + EXPECT_STREQ(platform.model, "XE2"); +} + +TEST_F(PlatformTest, platform_initialize_Broadband_ModelFailed_StillUnknown) { + create_mac_file("AA:BB:CC:DD:EE:FF\n"); + set_mock_stripinvalidchar_behavior(17); + set_mock_GetModelNum_behavior(0, NULL); + set_mock_file_get_sha1_behavior(0, "0123456789abcdef0123456789abcdef01234567"); + + config_t config; + memset(&config, 0, sizeof(config_t)); + config.device_type = DEVICE_TYPE_BROADBAND; + strncpy(config.box_type, "XB6", sizeof(config.box_type) - 1); + platform_config_t platform; + memset(&platform, 0, sizeof(platform_config_t)); + + int result = platform_initialize(&config, &platform); + EXPECT_EQ(result, PLATFORM_INIT_SUCCESS); + EXPECT_STREQ(platform.model, "UNKNOWN"); +} + +TEST_F(PlatformTest, platform_initialize_Extender_CtsModelStripsHyphens) { + create_mac_file("AA:BB:CC:DD:EE:FF\n"); + set_mock_stripinvalidchar_behavior(17); + set_mock_GetModelNum_behavior(13, "GR-EXT02A-CTS"); + set_mock_file_get_sha1_behavior(0, "0123456789abcdef0123456789abcdef01234567"); + + config_t config; + memset(&config, 0, sizeof(config_t)); + config.device_type = DEVICE_TYPE_EXTENDER; + strncpy(config.box_type, "XE2", sizeof(config.box_type) - 1); + platform_config_t platform; + memset(&platform, 0, sizeof(platform_config_t)); + + int result = platform_initialize(&config, &platform); + EXPECT_EQ(result, PLATFORM_INIT_SUCCESS); + EXPECT_STREQ(platform.model, "GREXT02ACTS"); +} + // ============================================================================ // Main // ============================================================================ diff --git a/unittest/upload_gtest.cpp b/unittest/upload_gtest.cpp index 213d552e..7840bd85 100644 --- a/unittest/upload_gtest.cpp +++ b/unittest/upload_gtest.cpp @@ -59,6 +59,8 @@ int upload_file(const char *filepath, const char *url, const char *dump_name, const char *model, const char *md5sum, device_type_t device_type, bool t2_enabled); int upload_process(archive_info_t *archive, const config_t *config, const platform_config_t *platform); +int extract_partner_id_from_mem(const char *buf, size_t n, char *out, size_t out_size); +int extract_partner_id_from_account(const char *path, char *out, size_t out_size); // Mock control functions void set_mock_read_rfc_property_behavior(int return_value, const char* output); @@ -1355,6 +1357,65 @@ TEST_F(UploadTest, UploadProcess_Broadband_UploadFail_CoredumpRemoved) { EXPECT_NE(result, 0); } +TEST_F(UploadTest, ExtractPartnerId_CleanJson) { + const char *json = "{\"accountId\":\"1\",\"partnerId\":\"comcast\",\"timeZone\":\"UTC\"}"; + char out[16]; + EXPECT_EQ(extract_partner_id_from_mem(json, strlen(json), out, sizeof(out)), 1); + EXPECT_STREQ(out, "comcast"); +} + +TEST_F(UploadTest, ExtractPartnerId_BinaryPrefixWithNul) { + char blob[128]; + const char *json = "{\"partnerId\":\"comcast\"}"; + memset(blob, 0, sizeof(blob)); + memcpy(blob, "PSFSzaccount", 12); + blob[5] = '\0'; + memcpy(blob + 12, json, strlen(json)); + char out[16]; + EXPECT_EQ(extract_partner_id_from_mem(blob, 12 + strlen(json), out, sizeof(out)), 1); + EXPECT_STREQ(out, "comcast"); +} + +TEST_F(UploadTest, ExtractPartnerId_SpaceAfterColon) { + const char *json = "\"partnerId\" : \"sky-uk\""; + char out[16]; + EXPECT_EQ(extract_partner_id_from_mem(json, strlen(json), out, sizeof(out)), 1); + EXPECT_STREQ(out, "sky-uk"); +} + +TEST_F(UploadTest, ExtractPartnerId_MissingKey) { + const char *json = "{\"accountId\":\"1\"}"; + char out[16]; + out[0] = 'x'; + EXPECT_EQ(extract_partner_id_from_mem(json, strlen(json), out, sizeof(out)), 0); + EXPECT_STREQ(out, ""); +} + +TEST_F(UploadTest, ExtractPartnerId_NullArgs) { + char out[16]; + EXPECT_EQ(extract_partner_id_from_mem(NULL, 10, out, sizeof(out)), 0); + EXPECT_EQ(extract_partner_id_from_mem("x", 1, NULL, 16), 0); + EXPECT_EQ(extract_partner_id_from_account(NULL, out, sizeof(out)), 0); +} + +TEST_F(UploadTest, ExtractPartnerId_FromAccountFile) { + const char *path = "/tmp/cu_account_partner_ut"; + FILE *fp = fopen(path, "wb"); + ASSERT_NE(fp, nullptr); + const unsigned char blob[] = { + 'P', 'S', 'F', 'S', 0x00, 'z', 'a', 'c', 'c', 'o', 'u', 'n', 't', + '{', '"', 'p', 'a', 'r', 't', 'n', 'e', 'r', 'I', 'd', '"', ':', + '"', 'c', 'o', 'm', 'c', 'a', 's', 't', '"', '}' + }; + fwrite(blob, 1, sizeof(blob), fp); + fclose(fp); + + char out[16]; + EXPECT_EQ(extract_partner_id_from_account(path, out, sizeof(out)), 1); + EXPECT_STREQ(out, "comcast"); + unlink(path); +} + // ============================================================================ // Main // ============================================================================ From 259a289975e14305d622e2a2061e0272c069f2aa Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Wed, 23 Sep 2026 10:47:30 +0530 Subject: [PATCH 10/14] Fix build error --- c_sourcecode/src/platform/platform.c | 7 +++---- unittest/upload_gtest.cpp | 11 +++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/c_sourcecode/src/platform/platform.c b/c_sourcecode/src/platform/platform.c index a60a67ec..bcbc71d4 100644 --- a/c_sourcecode/src/platform/platform.c +++ b/c_sourcecode/src/platform/platform.c @@ -26,6 +26,7 @@ #include #include #include +#include #ifdef GTEST_ENABLE #define STATIC_TESTABLE @@ -66,15 +67,13 @@ STATIC_TESTABLE void apply_extender_model(char *model, size_t model_size, const if (box_type && box_type[0] != '\0' && strcmp(box_type, "UNKNOWN") != 0) { - strncpy(model, box_type, model_size - 1); - model[model_size - 1] = '\0'; + snprintf(model, model_size, "%s", box_type); return; } if (model[0] == '\0') { - strncpy(model, "UNKNOWN", model_size - 1); - model[model_size - 1] = '\0'; + snprintf(model, model_size, "%s", "UNKNOWN"); } } diff --git a/unittest/upload_gtest.cpp b/unittest/upload_gtest.cpp index 7840bd85..89e1bc18 100644 --- a/unittest/upload_gtest.cpp +++ b/unittest/upload_gtest.cpp @@ -1400,8 +1400,15 @@ TEST_F(UploadTest, ExtractPartnerId_NullArgs) { TEST_F(UploadTest, ExtractPartnerId_FromAccountFile) { const char *path = "/tmp/cu_account_partner_ut"; - FILE *fp = fopen(path, "wb"); - ASSERT_NE(fp, nullptr); + int fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0600); + ASSERT_GE(fd, 0); + FILE *fp = fdopen(fd, "wb"); + if (!fp) + { + close(fd); + unlink(path); + FAIL() << "fdopen failed"; + } const unsigned char blob[] = { 'P', 'S', 'F', 'S', 0x00, 'z', 'a', 'c', 'c', 'o', 'u', 'n', 't', '{', '"', 'p', 'a', 'r', 't', 'n', 'e', 'r', 'I', 'd', '"', ':', From 6e3e8670b2ef8ff7d14cb97a882adf6fedd4993e Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Fri, 25 Sep 2026 16:12:07 +0530 Subject: [PATCH 11/14] Add url buffer for Extender --- c_sourcecode/src/upload/upload.c | 17 +++++++++++++++- unittest/upload_gmock.cpp | 7 +++++++ unittest/upload_gtest.cpp | 35 ++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/c_sourcecode/src/upload/upload.c b/c_sourcecode/src/upload/upload.c index 0d8914fa..bf1b3f64 100644 --- a/c_sourcecode/src/upload/upload.c +++ b/c_sourcecode/src/upload/upload.c @@ -651,12 +651,27 @@ int upload_process(archive_info_t *archive, const config_t *config, const platfo } if (crashportalEndpointUrl[0] == '\0') { - ret = get_crashupload_s3signed_url(crashportalEndpointUrl, sizeof(crashportalEndpointUrl)); + size_t signed_url_sz = sizeof(crashportalEndpointUrl); + + /* Extender libfwutils may reject getDevicePropertyData when + * buff_size >= MAX_DEVICE_PROP_BUFF_SIZE (80 on Extender). */ + if (config->device_type == DEVICE_TYPE_EXTENDER && + signed_url_sz >= MAX_DEVICE_PROP_BUFF_SIZE) + { + signed_url_sz = MAX_DEVICE_PROP_BUFF_SIZE - 1U; + } + ret = get_crashupload_s3signed_url(crashportalEndpointUrl, signed_url_sz); if (ret < 0) { CRASHUPLOAD_ERROR("%s: Unable to get S3 server url\n", device_type_to_str(config->device_type)); return ret; } + if (config->device_type == DEVICE_TYPE_EXTENDER && + crashportalEndpointUrl[0] == '\0') + { + CRASHUPLOAD_ERROR("Extender: S3 signing URL empty\n"); + return -1; + } } CRASHUPLOAD_INFO("%s: S3 signing URL=%s\n", device_type_to_str(config->device_type), crashportalEndpointUrl); } diff --git a/unittest/upload_gmock.cpp b/unittest/upload_gmock.cpp index dc2176e1..0af8135f 100644 --- a/unittest/upload_gmock.cpp +++ b/unittest/upload_gmock.cpp @@ -77,6 +77,7 @@ struct UploadMockState { int get_device_property_return_value; char get_device_property_output[512]; bool get_device_property_custom_behavior; + unsigned int get_device_property_last_datasize; // URL encoding char* url_encode_output; @@ -174,6 +175,10 @@ void set_mock_get_device_property_behavior(int return_value, const char* output) g_upload_mock_state.get_device_property_custom_behavior = true; } +unsigned int get_mock_last_device_property_datasize(void) { + return g_upload_mock_state.get_device_property_last_datasize; +} + /** * Set behavior for urlEncodeString mock */ @@ -299,6 +304,7 @@ void reset_upload_mocks() { memset(g_upload_mock_state.get_device_property_output, 0, sizeof(g_upload_mock_state.get_device_property_output)); g_upload_mock_state.get_device_property_custom_behavior = false; + g_upload_mock_state.get_device_property_last_datasize = 0; if (g_upload_mock_state.url_encode_output) { free(g_upload_mock_state.url_encode_output); @@ -404,6 +410,7 @@ int getDevicePropertyData(const char* key, char *data, unsigned int datasize) { if (!key || !data || datasize == 0) { return -1; } + g_upload_mock_state.get_device_property_last_datasize = datasize; if (g_upload_mock_state.get_device_property_custom_behavior) { if (g_upload_mock_state.get_device_property_output[0] != '\0') { diff --git a/unittest/upload_gtest.cpp b/unittest/upload_gtest.cpp index 89e1bc18..2c7180cd 100644 --- a/unittest/upload_gtest.cpp +++ b/unittest/upload_gtest.cpp @@ -65,6 +65,7 @@ int extract_partner_id_from_account(const char *path, char *out, size_t out_size // Mock control functions void set_mock_read_rfc_property_behavior(int return_value, const char* output); void set_mock_get_device_property_behavior(int return_value, const char* output); +unsigned int get_mock_last_device_property_datasize(void); void set_mock_url_encode_behavior(const char* output, bool return_null); void set_mock_metadata_post_behavior(int return_value, long http_code); void set_mock_upload_status(long http_code, int curl_ret); @@ -1322,6 +1323,40 @@ TEST_F(UploadTest, UploadProcess_Extender_S3UrlResolutionFailure) { EXPECT_EQ(result, -1); } +TEST_F(UploadTest, UploadProcess_Extender_EmptyS3Url_ReturnsFail) { + test_config.device_type = DEVICE_TYPE_EXTENDER; + set_mock_rbus_init_behavior(true); + set_mock_rbus_get_string_behavior(false, ""); + set_mock_read_rfc_property_behavior(-1, ""); + set_mock_get_device_property_behavior(0, ""); + + int result = upload_process(&test_archive_info, &test_config, &test_platform); + EXPECT_EQ(result, -1); +} + +TEST_F(UploadTest, UploadProcess_Extender_S3UrlBufferCappedForDeviceProp) { + test_config.device_type = DEVICE_TYPE_EXTENDER; + set_mock_rbus_init_behavior(true); + set_mock_rbus_get_string_behavior(false, ""); + set_mock_read_rfc_property_behavior(-1, ""); + set_mock_get_device_property_behavior(0, "https://s3.example.com/sign"); + set_mock_firmware_version_behavior(1, "TEST_FW_1.0"); + set_mock_metadata_post_behavior(0, 200); + set_mock_upload_status(200, 0); + set_mock_extract_s3_url_behavior(0, test_s3_url); + set_mock_s3_put_upload_behavior(0); + set_mock_file_present_behavior(-1); + + int result = upload_process(&test_archive_info, &test_config, &test_platform); + EXPECT_EQ(result, 0); + + unsigned int expected = 512; + if (expected >= MAX_DEVICE_PROP_BUFF_SIZE) + expected = MAX_DEVICE_PROP_BUFF_SIZE - 1U; + EXPECT_EQ(get_mock_last_device_property_datasize(), expected); + EXPECT_LT(get_mock_last_device_property_datasize(), MAX_DEVICE_PROP_BUFF_SIZE); +} + TEST_F(UploadTest, UploadProcess_Extender_UploadFail_CoredumpRemoved) { test_config.device_type = DEVICE_TYPE_EXTENDER; test_config.dump_type = DUMP_TYPE_COREDUMP; From f81fde89ee0d68edec612220bd8b7e16452cb290 Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Tue, 29 Sep 2026 14:58:00 +0530 Subject: [PATCH 12/14] Update core sleep logic --- c_sourcecode/src/utils/prerequisites.c | 43 ++++++++++++++++++++++---- unittest/prerequisites_gtest.cpp | 10 ++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/c_sourcecode/src/utils/prerequisites.c b/c_sourcecode/src/utils/prerequisites.c index f6e938f8..95beb2f6 100644 --- a/c_sourcecode/src/utils/prerequisites.c +++ b/c_sourcecode/src/utils/prerequisites.c @@ -149,6 +149,31 @@ static int has_required_dumps(const config_t *config) return 0; } +static int dir_is_nonempty(const char *path) +{ + DIR *dp; + struct dirent *entry; + + if (!path || path[0] == '\0') + return 0; + + dp = opendir(path); + if (!dp) + return 0; + + while ((entry = readdir(dp)) != NULL) + { + if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) + { + closedir(dp); + return 1; + } + } + + closedir(dp); + return 0; +} + int prerequisites_wait(config_t *config, int timeout_sec) { int dump_file_found = 0; @@ -160,6 +185,18 @@ int prerequisites_wait(config_t *config, int timeout_sec) return ERR_INVALID_ARGUMENT; } CRASHUPLOAD_INFO("Inside prerequisites_wait: device type=%d\n", config->device_type); + + /* runDumpUpload.sh: an empty WORKING_DIR exits before this wait. + * A non-empty coredump directory waits until the generator creates + * /tmp/coredump_mutex_release, then the dump count runs. */ + if (config->dump_type == DUMP_TYPE_COREDUMP && + dir_is_nonempty(config->core_path) && + filePresentCheck("/tmp/coredump_mutex_release") != 0) + { + CRASHUPLOAD_INFO("Waiting for Coredump Completion\n"); + sleep(21); + } + dump_file_found = has_required_dumps(config); if (1 != dump_file_found) { @@ -167,11 +204,5 @@ int prerequisites_wait(config_t *config, int timeout_sec) return NO_DUMPS_FOUND; } defer_upload_if_needed(config->device_type); - // TODO: Below mutex_release file create by core dump generation script.So using same - if ((config->dump_type == DUMP_TYPE_COREDUMP) && (0 != (filePresentCheck("/tmp/coredump_mutex_release")))) - { - CRASHUPLOAD_INFO("Waiting for Coredump Completion\n"); - sleep(21); // NMI: How this number arive?? - } return PREREQUISITES_SUCCESS; } diff --git a/unittest/prerequisites_gtest.cpp b/unittest/prerequisites_gtest.cpp index cc18d5e1..7ba53c34 100644 --- a/unittest/prerequisites_gtest.cpp +++ b/unittest/prerequisites_gtest.cpp @@ -71,12 +71,20 @@ class PrerequisitesTest : public ::testing::Test { // Clean up flag files unlink("/tmp/set_crash_reboot_flag"); + unlink("/tmp/coredump_mutex_release"); } void TearDown() override { // Clean up test directories and files system("rm -rf /tmp/test_prereq_dir /tmp/test_minidumps /tmp/test_cores"); unlink("/tmp/set_crash_reboot_flag"); + unlink("/tmp/coredump_mutex_release"); + } + + void TouchCoredumpMutexRelease() { + FILE* fp = fopen("/tmp/coredump_mutex_release", "w"); + if (fp) + fclose(fp); } void CreateDumpFile(const char* dir, const char* filename) { @@ -262,6 +270,7 @@ TEST_F(PrerequisitesTest, PrerequisitesWait_MediaClient_CoredumpFound_Success) { test_config.device_type = DEVICE_TYPE_MEDIACLIENT; test_config.dump_type = DUMP_TYPE_COREDUMP; CreateDumpFile(test_core_dir, "app_core.12345"); + TouchCoredumpMutexRelease(); int ret = prerequisites_wait(&test_config, 30); @@ -322,6 +331,7 @@ TEST_F(PrerequisitesTest, Integration_FullWorkflow_MediaClientCoredump) { test_config.device_type = DEVICE_TYPE_MEDIACLIENT; test_config.dump_type = DUMP_TYPE_COREDUMP; CreateDumpFile(test_core_dir, "receiver_core.12345"); + TouchCoredumpMutexRelease(); int ret = prerequisites_wait(&test_config, 30); From bf72f56d29241cfa7b98484f6231a8aa59fc0fda Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Tue, 29 Sep 2026 15:18:50 +0530 Subject: [PATCH 13/14] Revert "Update core sleep logic" This reverts commit f81fde89ee0d68edec612220bd8b7e16452cb290. --- c_sourcecode/src/utils/prerequisites.c | 43 ++++---------------------- unittest/prerequisites_gtest.cpp | 10 ------ 2 files changed, 6 insertions(+), 47 deletions(-) diff --git a/c_sourcecode/src/utils/prerequisites.c b/c_sourcecode/src/utils/prerequisites.c index 95beb2f6..f6e938f8 100644 --- a/c_sourcecode/src/utils/prerequisites.c +++ b/c_sourcecode/src/utils/prerequisites.c @@ -149,31 +149,6 @@ static int has_required_dumps(const config_t *config) return 0; } -static int dir_is_nonempty(const char *path) -{ - DIR *dp; - struct dirent *entry; - - if (!path || path[0] == '\0') - return 0; - - dp = opendir(path); - if (!dp) - return 0; - - while ((entry = readdir(dp)) != NULL) - { - if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) - { - closedir(dp); - return 1; - } - } - - closedir(dp); - return 0; -} - int prerequisites_wait(config_t *config, int timeout_sec) { int dump_file_found = 0; @@ -185,18 +160,6 @@ int prerequisites_wait(config_t *config, int timeout_sec) return ERR_INVALID_ARGUMENT; } CRASHUPLOAD_INFO("Inside prerequisites_wait: device type=%d\n", config->device_type); - - /* runDumpUpload.sh: an empty WORKING_DIR exits before this wait. - * A non-empty coredump directory waits until the generator creates - * /tmp/coredump_mutex_release, then the dump count runs. */ - if (config->dump_type == DUMP_TYPE_COREDUMP && - dir_is_nonempty(config->core_path) && - filePresentCheck("/tmp/coredump_mutex_release") != 0) - { - CRASHUPLOAD_INFO("Waiting for Coredump Completion\n"); - sleep(21); - } - dump_file_found = has_required_dumps(config); if (1 != dump_file_found) { @@ -204,5 +167,11 @@ int prerequisites_wait(config_t *config, int timeout_sec) return NO_DUMPS_FOUND; } defer_upload_if_needed(config->device_type); + // TODO: Below mutex_release file create by core dump generation script.So using same + if ((config->dump_type == DUMP_TYPE_COREDUMP) && (0 != (filePresentCheck("/tmp/coredump_mutex_release")))) + { + CRASHUPLOAD_INFO("Waiting for Coredump Completion\n"); + sleep(21); // NMI: How this number arive?? + } return PREREQUISITES_SUCCESS; } diff --git a/unittest/prerequisites_gtest.cpp b/unittest/prerequisites_gtest.cpp index 7ba53c34..cc18d5e1 100644 --- a/unittest/prerequisites_gtest.cpp +++ b/unittest/prerequisites_gtest.cpp @@ -71,20 +71,12 @@ class PrerequisitesTest : public ::testing::Test { // Clean up flag files unlink("/tmp/set_crash_reboot_flag"); - unlink("/tmp/coredump_mutex_release"); } void TearDown() override { // Clean up test directories and files system("rm -rf /tmp/test_prereq_dir /tmp/test_minidumps /tmp/test_cores"); unlink("/tmp/set_crash_reboot_flag"); - unlink("/tmp/coredump_mutex_release"); - } - - void TouchCoredumpMutexRelease() { - FILE* fp = fopen("/tmp/coredump_mutex_release", "w"); - if (fp) - fclose(fp); } void CreateDumpFile(const char* dir, const char* filename) { @@ -270,7 +262,6 @@ TEST_F(PrerequisitesTest, PrerequisitesWait_MediaClient_CoredumpFound_Success) { test_config.device_type = DEVICE_TYPE_MEDIACLIENT; test_config.dump_type = DUMP_TYPE_COREDUMP; CreateDumpFile(test_core_dir, "app_core.12345"); - TouchCoredumpMutexRelease(); int ret = prerequisites_wait(&test_config, 30); @@ -331,7 +322,6 @@ TEST_F(PrerequisitesTest, Integration_FullWorkflow_MediaClientCoredump) { test_config.device_type = DEVICE_TYPE_MEDIACLIENT; test_config.dump_type = DUMP_TYPE_COREDUMP; CreateDumpFile(test_core_dir, "receiver_core.12345"); - TouchCoredumpMutexRelease(); int ret = prerequisites_wait(&test_config, 30); From 6f3dd2f4909390f78c64cfa35630abdfbe96c67c Mon Sep 17 00:00:00 2001 From: gomathishankar37 Date: Tue, 29 Sep 2026 16:48:00 +0530 Subject: [PATCH 14/14] Update URL Buffer for extender --- c_sourcecode/src/upload/upload.c | 82 +++++++++++++++++++++++++------- unittest/upload_gtest.cpp | 49 ++++++++++++++++--- 2 files changed, 108 insertions(+), 23 deletions(-) diff --git a/c_sourcecode/src/upload/upload.c b/c_sourcecode/src/upload/upload.c index bf1b3f64..399b777f 100644 --- a/c_sourcecode/src/upload/upload.c +++ b/c_sourcecode/src/upload/upload.c @@ -126,6 +126,45 @@ STATIC_TESTABLE int extract_partner_id_from_account(const char *path, char *out, return extract_partner_id_from_mem(buf, n, out, out_size); } +#define EXTENDER_LIBFWUTILS_PROP_MAX 80 + +STATIC_TESTABLE int read_key_from_device_properties(const char *key, char *out, size_t out_size) +{ + FILE *fp; + char line[512]; + size_t klen; + size_t linelen; + + if (!key || !out || out_size == 0) + { + return 0; + } + out[0] = '\0'; + klen = strlen(key); + if (klen == 0 || klen >= sizeof(line) - 2) + { + return 0; + } + fp = fopen(DEVICE_PROPERTIES_FILE, "r"); + if (!fp) + { + return 0; + } + while (fgets(line, (int)sizeof(line), fp) != NULL) + { + linelen = strlen(line); + if (linelen > 0 && line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + if (strncmp(line, key, klen) != 0 || line[klen] != '=') + continue; + snprintf(out, out_size, "%s", line + klen + 1); + fclose(fp); + return (out[0] != '\0') ? 1 : 0; + } + fclose(fp); + return 0; +} + #ifdef RDKC #define RDKC_PARTNER_ID_FILE "/opt/usr_config/partnerid.txt" #endif @@ -651,26 +690,35 @@ int upload_process(archive_info_t *archive, const config_t *config, const platfo } if (crashportalEndpointUrl[0] == '\0') { - size_t signed_url_sz = sizeof(crashportalEndpointUrl); - - /* Extender libfwutils may reject getDevicePropertyData when - * buff_size >= MAX_DEVICE_PROP_BUFF_SIZE (80 on Extender). */ - if (config->device_type == DEVICE_TYPE_EXTENDER && - signed_url_sz >= MAX_DEVICE_PROP_BUFF_SIZE) + if (config->device_type == DEVICE_TYPE_EXTENDER) { - signed_url_sz = MAX_DEVICE_PROP_BUFF_SIZE - 1U; - } - ret = get_crashupload_s3signed_url(crashportalEndpointUrl, signed_url_sz); - if (ret < 0) - { - CRASHUPLOAD_ERROR("%s: Unable to get S3 server url\n", device_type_to_str(config->device_type)); - return ret; + size_t signed_url_sz = EXTENDER_LIBFWUTILS_PROP_MAX - 1U; + + if (signed_url_sz >= sizeof(crashportalEndpointUrl)) + signed_url_sz = sizeof(crashportalEndpointUrl) - 1U; + ret = get_crashupload_s3signed_url(crashportalEndpointUrl, signed_url_sz); + if (crashportalEndpointUrl[0] == '\0') + { + CRASHUPLOAD_INFO("Extender: reading S3_AMAZON_SIGNING_URL from %s\n", + DEVICE_PROPERTIES_FILE); + (void)read_key_from_device_properties("S3_AMAZON_SIGNING_URL", + crashportalEndpointUrl, + sizeof(crashportalEndpointUrl)); + } + if (crashportalEndpointUrl[0] == '\0') + { + CRASHUPLOAD_ERROR("Extender: S3 signing URL empty\n"); + return -1; + } } - if (config->device_type == DEVICE_TYPE_EXTENDER && - crashportalEndpointUrl[0] == '\0') + else { - CRASHUPLOAD_ERROR("Extender: S3 signing URL empty\n"); - return -1; + ret = get_crashupload_s3signed_url(crashportalEndpointUrl, sizeof(crashportalEndpointUrl)); + if (ret < 0) + { + CRASHUPLOAD_ERROR("%s: Unable to get S3 server url\n", device_type_to_str(config->device_type)); + return ret; + } } } CRASHUPLOAD_INFO("%s: S3 signing URL=%s\n", device_type_to_str(config->device_type), crashportalEndpointUrl); diff --git a/unittest/upload_gtest.cpp b/unittest/upload_gtest.cpp index 2c7180cd..60a85b92 100644 --- a/unittest/upload_gtest.cpp +++ b/unittest/upload_gtest.cpp @@ -61,6 +61,7 @@ int upload_process(archive_info_t *archive, const config_t *config, const platform_config_t *platform); int extract_partner_id_from_mem(const char *buf, size_t n, char *out, size_t out_size); int extract_partner_id_from_account(const char *path, char *out, size_t out_size); +int read_key_from_device_properties(const char *key, char *out, size_t out_size); // Mock control functions void set_mock_read_rfc_property_behavior(int return_value, const char* output); @@ -1314,10 +1315,11 @@ TEST_F(UploadTest, UploadProcess_Extender_RbusInitFail_FallbackS3Url_Success) { TEST_F(UploadTest, UploadProcess_Extender_S3UrlResolutionFailure) { test_config.device_type = DEVICE_TYPE_EXTENDER; + unlink(DEVICE_PROPERTIES_FILE); set_mock_rbus_init_behavior(true); set_mock_rbus_get_string_behavior(false, ""); set_mock_read_rfc_property_behavior(-1, ""); - set_mock_get_device_property_behavior(-1, ""); // get_crashupload_s3signed_url fallback fails + set_mock_get_device_property_behavior(-1, ""); int result = upload_process(&test_archive_info, &test_config, &test_platform); EXPECT_EQ(result, -1); @@ -1325,6 +1327,7 @@ TEST_F(UploadTest, UploadProcess_Extender_S3UrlResolutionFailure) { TEST_F(UploadTest, UploadProcess_Extender_EmptyS3Url_ReturnsFail) { test_config.device_type = DEVICE_TYPE_EXTENDER; + unlink(DEVICE_PROPERTIES_FILE); set_mock_rbus_init_behavior(true); set_mock_rbus_get_string_behavior(false, ""); set_mock_read_rfc_property_behavior(-1, ""); @@ -1336,6 +1339,7 @@ TEST_F(UploadTest, UploadProcess_Extender_EmptyS3Url_ReturnsFail) { TEST_F(UploadTest, UploadProcess_Extender_S3UrlBufferCappedForDeviceProp) { test_config.device_type = DEVICE_TYPE_EXTENDER; + unlink(DEVICE_PROPERTIES_FILE); set_mock_rbus_init_behavior(true); set_mock_rbus_get_string_behavior(false, ""); set_mock_read_rfc_property_behavior(-1, ""); @@ -1349,12 +1353,45 @@ TEST_F(UploadTest, UploadProcess_Extender_S3UrlBufferCappedForDeviceProp) { int result = upload_process(&test_archive_info, &test_config, &test_platform); EXPECT_EQ(result, 0); + EXPECT_EQ(get_mock_last_device_property_datasize(), 79u); +} + +TEST_F(UploadTest, UploadProcess_Extender_S3UrlFromDevicePropertiesFile) { + FILE *fp; + test_config.device_type = DEVICE_TYPE_EXTENDER; + fp = fopen(DEVICE_PROPERTIES_FILE, "w"); + ASSERT_NE(fp, nullptr); + fprintf(fp, "S3_AMAZON_SIGNING_URL=https://ssr.example.com/sign\n"); + fclose(fp); + + set_mock_rbus_init_behavior(true); + set_mock_rbus_get_string_behavior(false, ""); + set_mock_read_rfc_property_behavior(-1, ""); + set_mock_get_device_property_behavior(-1, ""); + set_mock_firmware_version_behavior(1, "TEST_FW_1.0"); + set_mock_metadata_post_behavior(0, 200); + set_mock_upload_status(200, 0); + set_mock_extract_s3_url_behavior(0, test_s3_url); + set_mock_s3_put_upload_behavior(0); + set_mock_file_present_behavior(-1); + + int result = upload_process(&test_archive_info, &test_config, &test_platform); + unlink(DEVICE_PROPERTIES_FILE); + EXPECT_EQ(result, 0); +} - unsigned int expected = 512; - if (expected >= MAX_DEVICE_PROP_BUFF_SIZE) - expected = MAX_DEVICE_PROP_BUFF_SIZE - 1U; - EXPECT_EQ(get_mock_last_device_property_datasize(), expected); - EXPECT_LT(get_mock_last_device_property_datasize(), MAX_DEVICE_PROP_BUFF_SIZE); +TEST_F(UploadTest, ReadKeyFromDeviceProperties_ParsesValue) { + FILE *fp; + char out[128] = {0}; + + fp = fopen(DEVICE_PROPERTIES_FILE, "w"); + ASSERT_NE(fp, nullptr); + fprintf(fp, "FOO=bar\nS3_AMAZON_SIGNING_URL=https://portal.example/x\n"); + fclose(fp); + EXPECT_EQ(read_key_from_device_properties("S3_AMAZON_SIGNING_URL", out, sizeof(out)), 1); + EXPECT_STREQ(out, "https://portal.example/x"); + EXPECT_EQ(read_key_from_device_properties(NULL, out, sizeof(out)), 0); + unlink(DEVICE_PROPERTIES_FILE); } TEST_F(UploadTest, UploadProcess_Extender_UploadFail_CoredumpRemoved) {