From 3aea1e1efabaef3b822eac846428b7f660a0983b Mon Sep 17 00:00:00 2001 From: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Date: Sat, 16 May 2026 00:21:37 +0530 Subject: [PATCH 1/9] RDKEMW-18510: [develop]Log upload success logs not observed after scheduled reboot (#127) * Update strategies.c * Update strategies.c * Update strategies.c * Update uploadstblogs.c * Update uploadstblogs.c * Update strategies.c * Update strategies.c * Update strategies.c * Update strategies.c * Update strategies.c * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- uploadstblogs/src/strategies.c | 18 ++++++------------ uploadstblogs/src/uploadstblogs.c | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/uploadstblogs/src/strategies.c b/uploadstblogs/src/strategies.c index ce880373a..18cce20f0 100644 --- a/uploadstblogs/src/strategies.c +++ b/uploadstblogs/src/strategies.c @@ -850,13 +850,7 @@ static int reboot_upload(RuntimeContext* ctx, SessionState* session) "[%s:%d] Non-DCM mode (dcm_flag=0), will always upload logs\n", __FUNCTION__, __LINE__); } - // DCM mode (DCM_FLAG=1): Check upload_on_reboot flag - else if (ctx->upload_on_reboot) { - should_upload = true; - RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, - "[%s:%d] DCM mode: Upload enabled from settings (upload_on_reboot=true)\n", - __FUNCTION__, __LINE__); - } else { + else { // Check reboot reason file for scheduled reboot (grep -i "Scheduled Reboot\|MAINTENANCE_REBOOT") bool is_scheduled_reboot = false; FILE* reboot_file = fopen(reboot_info_path, "r"); @@ -887,12 +881,12 @@ static int reboot_upload(RuntimeContext* ctx, SessionState* session) RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, "[%s:%d] uploadLog:%s and UploadLogsOnUnscheduledReboot.Disable RFC: %s\n", __FUNCTION__, __LINE__, ctx->upload_on_reboot ? "true" : "false", disable_unscheduled_upload ? "true" : "false"); - // Upload if: reboot reason is empty (unscheduled) AND RFC doesn't disable it - // Script logic: [ -z "$reboot_reason" -a "$DISABLE_UPLOAD_LOGS_UNSHEDULED_REBOOT" == "false" ] - if (!is_scheduled_reboot && !disable_unscheduled_upload) { + // Upload if upload_on_reboot is enabled, OR if the reboot is unscheduled + // and the UploadLogsOnUnscheduledReboot.Disable RFC does not disable it. + // Script logic for the unscheduled reboot path: + // [ -z "$reboot_reason" -a "$DISABLE_UPLOAD_LOGS_UNSHEDULED_REBOOT" == "false" ] + if ( ctx->upload_on_reboot==1 || (!is_scheduled_reboot && !disable_unscheduled_upload)) { should_upload = true; - RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, - "[%s:%d] Unscheduled reboot and RFC allows upload\n", __FUNCTION__, __LINE__); } } diff --git a/uploadstblogs/src/uploadstblogs.c b/uploadstblogs/src/uploadstblogs.c index 411db6315..7f29b2eb5 100755 --- a/uploadstblogs/src/uploadstblogs.c +++ b/uploadstblogs/src/uploadstblogs.c @@ -127,7 +127,7 @@ bool parse_args(int argc, char** argv, RuntimeContext* ctx) if (argc >= 5 && argv[4]) { // Parse UploadOnReboot - ctx->upload_on_reboot = (strcmp(argv[4], "true") == 0) ? 1 : 0; + ctx->upload_on_reboot = (strcmp(argv[4], "true") == 0 || strcmp(argv[4], "1") == 0) ? 1 : 0; fprintf(stderr, "DEBUG: UploadOnReboot (argv[4]) = '%s' -> %d\n", argv[4], ctx->upload_on_reboot); } From fe804982965fb1db55917fb9d1f91573b65e6e0c Mon Sep 17 00:00:00 2001 From: nhanas001c Date: Mon, 18 May 2026 14:38:12 +0000 Subject: [PATCH 2/9] DCM Agent 2.1.3 release changelog updates --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d894dfd14..9bd28e1df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,22 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [2.1.3](https://github.com/rdkcentral/dcm-agent/compare/2.1.2...2.1.3) + +- RDKEMW-18510: [develop]Log upload success logs not observed after scheduled reboot [`#127`](https://github.com/rdkcentral/dcm-agent/pull/127) +- Merge tag '2.1.2' into develop [`7461693`](https://github.com/rdkcentral/dcm-agent/commit/7461693af0d9c1c8fbd3fb4fd374ef8858041608) + #### [2.1.2](https://github.com/rdkcentral/dcm-agent/compare/2.1.1...2.1.2) +> 29 April 2026 + - RDKEMW-17638 : [RDKEMW][ALPACA IT] Device not Going to Deepsleep. [`#122`](https://github.com/rdkcentral/dcm-agent/pull/122) - RDKEMW-17582: [develop]UploadLogsOnUnscheduledReboot.Disable RFC state not logged / not honored in 8.5 builds after Scheduled Reboot [`#119`](https://github.com/rdkcentral/dcm-agent/pull/119) - [RDKEMW-17616] Log-backup generated is named with local timestamp instead of UTC timestamp [`#114`](https://github.com/rdkcentral/dcm-agent/pull/114) - RDKEMW-14842 [Logupload] Sha value need to be print in dcmscript.log for all types of logupload [`#111`](https://github.com/rdkcentral/dcm-agent/pull/111) - DCM Agent Documentaion updated for the module [`#110`](https://github.com/rdkcentral/dcm-agent/pull/110) - RDKEMW-17026 : Remove OEM/SOC references from the module [`#113`](https://github.com/rdkcentral/dcm-agent/pull/113) +- DCM Agent 2.1.2 release changelog updates [`00ac749`](https://github.com/rdkcentral/dcm-agent/commit/00ac749be80b9d7cbfc47e60e0dcfc11005e9886) - Merge tag '2.1.1' into develop [`be1a984`](https://github.com/rdkcentral/dcm-agent/commit/be1a9843bd5631ee54d0c1d750e827b50e9ba848) #### [2.1.1](https://github.com/rdkcentral/dcm-agent/compare/2.1.0...2.1.1) From 5297f53231d345bff7544dd479bad35716ca1c8a Mon Sep 17 00:00:00 2001 From: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Date: Thu, 28 May 2026 18:51:26 +0530 Subject: [PATCH 3/9] RDKEMW-17622 : Analyze and Compare Log Upload Script and C module Logs (#131) * Update strategies.c * Update strategies.c * Update strategies.c * Update uploadstblogs.c * Update uploadstblogs.c * Update strategies.c * Update strategies.c * Update strategies.c * Update strategies.c * Update strategies.c * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update event_manager.c * Update strategies.c * Update path_handler.c * Update path_handler.c * Update path_handler.c * Update event_manager.c * Update event_manager_gtest.cpp * Update event_manager_gtest.cpp * Update path_handler.c * Update path_handler.c * Update path_handler.c * Update strategies.c * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update strategies.c --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- uploadstblogs/src/event_manager.c | 4 ++-- uploadstblogs/src/path_handler.c | 1 + uploadstblogs/src/strategies.c | 1 + uploadstblogs/unittest/event_manager_gtest.cpp | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/uploadstblogs/src/event_manager.c b/uploadstblogs/src/event_manager.c index b06c00dca..ff9c3cb94 100755 --- a/uploadstblogs/src/event_manager.c +++ b/uploadstblogs/src/event_manager.c @@ -431,7 +431,7 @@ void emit_folder_missing_error(void) RDK_LOG(RDK_LOG_ERROR, LOG_UPLOADSTB, "[%s:%d] Required folder missing for log upload\n", __FUNCTION__, __LINE__); - // Send maintenance error event (matches script behavior) - send_iarm_event_maintenance(MAINT_LOGUPLOAD_ERROR); + // Send maintenance complete event (matches script behavior) + send_iarm_event_maintenance(MAINT_LOGUPLOAD_COMPLETE); } diff --git a/uploadstblogs/src/path_handler.c b/uploadstblogs/src/path_handler.c index 8f61c7acd..162b97a21 100755 --- a/uploadstblogs/src/path_handler.c +++ b/uploadstblogs/src/path_handler.c @@ -563,6 +563,7 @@ static UploadResult perform_s3_put_with_fallback(RuntimeContext* ctx, SessionSta if (s3_verified == UPLOADSTB_SUCCESS) { t2_count_notify("TEST_lu_success"); // Script line 616 session->success = true; + RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, "[%s:%d] Direct log upload Success: httpcode= %d\n", __FUNCTION__, __LINE__, session->http_code); return UPLOADSTB_SUCCESS; } diff --git a/uploadstblogs/src/strategies.c b/uploadstblogs/src/strategies.c index 18cce20f0..2ea48736a 100644 --- a/uploadstblogs/src/strategies.c +++ b/uploadstblogs/src/strategies.c @@ -835,6 +835,7 @@ static int reboot_upload(RuntimeContext* ctx, SessionState* session) { RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, "[%s:%d] REBOOT/NON_DCM: Starting upload phase\n", __FUNCTION__, __LINE__); + RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, "[%s:%d] UploadOnReboot set to %s\n", __FUNCTION__, __LINE__, ctx->upload_on_reboot ? "true" : "false"); // Check reboot reason and RFC settings (matches script logic) // Script: if [ "$uploadLog" == "true" ] || [ -z "$reboot_reason" -a "$DISABLE_UPLOAD_LOGS_UNSHEDULED_REBOOT" == "false" ] diff --git a/uploadstblogs/unittest/event_manager_gtest.cpp b/uploadstblogs/unittest/event_manager_gtest.cpp index d790f5588..9ccb86dbd 100755 --- a/uploadstblogs/unittest/event_manager_gtest.cpp +++ b/uploadstblogs/unittest/event_manager_gtest.cpp @@ -448,10 +448,10 @@ TEST_F(EventManagerTest, SendIarmEventMaintenance_Success) { TEST_F(EventManagerTest, EmitFolderMissingError_Success) { emit_folder_missing_error(); - // Should send MaintenanceMGR error event + // Should send MaintenanceMGR Complete event EXPECT_EQ(mock_iarm_event_calls, 1); EXPECT_STREQ(mock_last_event_name, "MaintenanceMGR"); - EXPECT_EQ(mock_last_event_code, 5); // MAINT_LOGUPLOAD_ERROR + EXPECT_EQ(mock_last_event_code, 4); // MAINT_LOGUPLOAD_COMPLETE } // Integration tests From dcb80c1df85db4a0c3cf29c95d5a36d61923b6e2 Mon Sep 17 00:00:00 2001 From: Abhinavpv28 <162570454+Abhinavpv28@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:07:37 +0530 Subject: [PATCH 4/9] RDKEMW-19238: Cleanup of stale archives and log backups to the uploadSTBLogs (#134) * Update cleanup_handler.c * Update strategies.c * Update strategy_handler.c * Update cleanup_handler.c * Update strategies.c * Update strategies.c * Update strategies.c * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update strategies.c * Update cleanup_handler.c * Update strategies_gtest.cpp * Update strategy_handler_gtest.cpp * Update cleanup_handler_gtest.cpp * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update cleanup_handler.c * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update cleanup_handler.c * Update strategies.c --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- uploadstblogs/src/cleanup_handler.c | 70 ++++++++++++---- uploadstblogs/src/strategies.c | 80 ++++++++----------- uploadstblogs/src/strategy_handler.c | 4 +- .../unittest/cleanup_handler_gtest.cpp | 37 +++++++++ uploadstblogs/unittest/strategies_gtest.cpp | 5 ++ .../unittest/strategy_handler_gtest.cpp | 8 +- 6 files changed, 140 insertions(+), 64 deletions(-) diff --git a/uploadstblogs/src/cleanup_handler.c b/uploadstblogs/src/cleanup_handler.c index e99fd144b..25087a4d3 100755 --- a/uploadstblogs/src/cleanup_handler.c +++ b/uploadstblogs/src/cleanup_handler.c @@ -37,6 +37,7 @@ #include #include #include +#include #include "cleanup_handler.h" #include "context_manager.h" #include "event_manager.h" @@ -224,36 +225,71 @@ int cleanup_old_archives(const char *log_path) return -1; } + int dfd = dirfd(dir); + if (dfd < 0) { + RDK_LOG(RDK_LOG_ERROR, LOG_UPLOADSTB, "[%s:%d] dirfd() failed for: %s\n", __FUNCTION__, __LINE__, log_path); + closedir(dir); + return -1; + } + int removed_count = 0; struct dirent *entry; char fullpath[512]; while ((entry = readdir(dir)) != NULL) { - // Check if file ends with .tgz - size_t len = strlen(entry->d_name); - if (len < 5 || strcmp(entry->d_name + len - 4, ".tgz") != 0) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } - snprintf(fullpath, sizeof(fullpath), "%s/%s", log_path, entry->d_name); - - RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, - "[%s:%d] Removing old archive: %s\n", - __FUNCTION__, __LINE__, fullpath); - - // Use unlink to remove file (more explicit than remove) - if (unlink(fullpath) == 0) { - removed_count++; - } else { - RDK_LOG(RDK_LOG_WARN, LOG_UPLOADSTB, - "[%s:%d] Failed to remove: %s\n", - __FUNCTION__, __LINE__, fullpath); + struct stat st; + /* fstatat with AT_SYMLINK_NOFOLLOW on the open dir FD: check and subsequent + * unlinkat both refer to the same dir entry, eliminating the TOCTOU race. */ + if (fstatat(dfd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0) { + continue; + } + + if (S_ISDIR(st.st_mode)) { + /* Recurse into subdirectories (matches shell: find $LOG_PATH -name "*.tgz") */ + snprintf(fullpath, sizeof(fullpath), "%s/%s", log_path, entry->d_name); + int sub_count = cleanup_old_archives(fullpath); + if (sub_count > 0) { + removed_count += sub_count; + } + } else if (S_ISREG(st.st_mode)) { + size_t len = strlen(entry->d_name); + if (len < 5 || strcmp(entry->d_name + len - 4, ".tgz") != 0) { + continue; + } + + int path_written = snprintf(fullpath, sizeof(fullpath), "%s/%s", log_path, entry->d_name); + + if (path_written < 0 || path_written >= (int)sizeof(fullpath)) { + + RDK_LOG(RDK_LOG_WARN, LOG_UPLOADSTB, + + "[%s:%d] Path too long, skipping file: %s/%s\n", + + __FUNCTION__, __LINE__, log_path, entry->d_name); + + continue; + + } + + RDK_LOG(RDK_LOG_DEBUG, LOG_UPLOADSTB, "[%s:%d] Removing old archive: %s\n", __FUNCTION__, __LINE__, fullpath); + /* unlinkat operates on the same dir FD — no path race possible */ + if (unlinkat(dfd, entry->d_name, 0) == 0) { + removed_count++; + } else { + RDK_LOG(RDK_LOG_WARN, LOG_UPLOADSTB, + "[%s:%d] Failed to remove: %s\n", + __FUNCTION__, __LINE__, fullpath); + } } } closedir(dir); - RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, + RDK_LOG(RDK_LOG_DEBUG, LOG_UPLOADSTB, "[%s:%d] Archive cleanup complete: removed %d .tgz files from %s\n", __FUNCTION__, __LINE__, removed_count, log_path); diff --git a/uploadstblogs/src/strategies.c b/uploadstblogs/src/strategies.c index 2ea48736a..0a4fb0902 100644 --- a/uploadstblogs/src/strategies.c +++ b/uploadstblogs/src/strategies.c @@ -47,6 +47,7 @@ #include "rbus_interface.h" #include "rdk_debug.h" #include "event_manager.h" +#include "cleanup_handler.h" #define ONDEMAND_TEMP_DIR "/tmp/log_on_demand" @@ -686,30 +687,29 @@ static int reboot_setup(RuntimeContext* ctx, SessionState* session) __FUNCTION__, __LINE__); } - // Delete old backup files (3+ days old) - // Remove old timestamp directories and logbackup directories - RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, - "[%s:%d] Cleaning old backups (3+ days)\n", __FUNCTION__, __LINE__); - - int removed = remove_old_directories(ctx->log_path, "*-*-*-*-*M-", 3); - if (removed > 0) { - RDK_LOG(RDK_LOG_DEBUG, LOG_UPLOADSTB, - "[%s:%d] Removed %d old timestamp directories\n", - __FUNCTION__, __LINE__, removed); + // Clean up old log backup directories (older than 3 days) + RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, "[%s:%d] Cleaning old log backup directories (3+ days)\n", __FUNCTION__, __LINE__); + int removed_dirs = cleanup_old_log_backups(ctx->log_path, 3); + if (removed_dirs > 0) { + RDK_LOG(RDK_LOG_DEBUG, LOG_UPLOADSTB, "[%s:%d] Removed %d old log backup directories\n", __FUNCTION__, __LINE__, removed_dirs); + } else { + RDK_LOG(RDK_LOG_DEBUG, LOG_UPLOADSTB, "[%s:%d] No old log backup directories removed\n", __FUNCTION__, __LINE__); } - removed = remove_old_directories(ctx->log_path, "*-*-*-*-*M-logbackup", 3); - if (removed > 0) { - RDK_LOG(RDK_LOG_DEBUG, LOG_UPLOADSTB, - "[%s:%d] Removed %d old logbackup directories\n", - __FUNCTION__, __LINE__, removed); - } - // Create timestamp for permanent log path char timestamp[64]; time_t now = time(NULL); - struct tm* tm_info = localtime(&now); - strftime(timestamp, sizeof(timestamp), "%m-%d-%y-%I-%M%p-logbackup", tm_info); + struct tm tm_utc; + size_t timestamp_len; + if (gmtime_r(&now, &tm_utc) == NULL) { + RDK_LOG(RDK_LOG_ERROR, LOG_UPLOADSTB, "[%s:%d] Failed to get UTC time\n", __FUNCTION__, __LINE__); + return -1; + } + timestamp_len = strftime(timestamp, sizeof(timestamp), "%m-%d-%y-%I-%M%p-logbackup", &tm_utc); + if (timestamp_len == 0U) { + RDK_LOG(RDK_LOG_ERROR, LOG_UPLOADSTB, "[%s:%d] Failed to format timestamp for permanent log path\n", __FUNCTION__, __LINE__); + return -1; + } char perm_log_path[MAX_PATH_LENGTH]; int written = snprintf(perm_log_path, sizeof(perm_log_path), "%s/%s", @@ -890,25 +890,26 @@ static int reboot_upload(RuntimeContext* ctx, SessionState* session) should_upload = true; } } - - if (!should_upload) { - RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, - "[%s:%d] Upload not allowed based on reboot reason and RFC settings\n", - __FUNCTION__, __LINE__); - emit_upload_aborted(); - return 0; - } - // Construct full archive path using session archive filename + // Construct full archive path using session archive filename char archive_path[MAX_PATH_LENGTH]; - int written = snprintf(archive_path, sizeof(archive_path), "%s/%s", - ctx->prev_log_path, session->archive_file); + int written = snprintf(archive_path, sizeof(archive_path), "%s/%s", ctx->prev_log_path, session->archive_file); if (written >= (int)sizeof(archive_path)) { RDK_LOG(RDK_LOG_ERROR, LOG_UPLOADSTB, "[%s:%d] Archive path too long\n", __FUNCTION__, __LINE__); return -1; } + + if (!should_upload) { + RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, + "[%s:%d] Upload not allowed based on reboot reason and RFC settings\n", + __FUNCTION__, __LINE__); + strncpy(session->archive_file, archive_path, sizeof(session->archive_file) - 1); + session->archive_file[sizeof(session->archive_file) - 1] = '\0'; + emit_upload_aborted(); + return 0; + } RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, "[%s:%d] Uploading main logs: %s\n", @@ -1011,21 +1012,11 @@ static int reboot_cleanup(RuntimeContext* ctx, SessionState* session, bool uploa sleep(5); // Delete tar file - char tar_path[MAX_PATH_LENGTH]; - int written = snprintf(tar_path, sizeof(tar_path), "%s/%s", - ctx->prev_log_path, session->archive_file); - - if (written >= (int)sizeof(tar_path)) { - RDK_LOG(RDK_LOG_ERROR, LOG_UPLOADSTB, - "[%s:%d] Tar path too long\n", __FUNCTION__, __LINE__); - return -1; - } - - if (file_exists(tar_path)) { + if (file_exists(session->archive_file)) { RDK_LOG(RDK_LOG_DEBUG, LOG_UPLOADSTB, "[%s:%d] Removing tar file: %s\n", - __FUNCTION__, __LINE__, tar_path); - remove_file(tar_path); + __FUNCTION__, __LINE__, session->archive_file); + remove_file(session->archive_file); } // Remove timestamps from filenames (restore original names) @@ -1076,7 +1067,7 @@ static int reboot_cleanup(RuntimeContext* ctx, SessionState* session, bool uploa // Script lines 900-902: rm -rf + mkdir -p PREV_LOG_BACKUP_PATH // PREV_LOG_BACKUP_PATH = $LOG_PATH/PreviousLogs_backup/ char prev_log_backup_path[MAX_PATH_LENGTH]; - written = snprintf(prev_log_backup_path, sizeof(prev_log_backup_path), "%s/PreviousLogs_backup", + int written = snprintf(prev_log_backup_path, sizeof(prev_log_backup_path), "%s/PreviousLogs_backup", ctx->log_path); if (written >= (int)sizeof(prev_log_backup_path)) { @@ -1121,4 +1112,3 @@ static int reboot_cleanup(RuntimeContext* ctx, SessionState* session, bool uploa return 0; } - diff --git a/uploadstblogs/src/strategy_handler.c b/uploadstblogs/src/strategy_handler.c index 0496026a9..9a0086a2c 100755 --- a/uploadstblogs/src/strategy_handler.c +++ b/uploadstblogs/src/strategy_handler.c @@ -24,6 +24,7 @@ #include #include "strategy_handler.h" +#include "cleanup_handler.h" #include "rdk_debug.h" #include @@ -70,6 +71,8 @@ int execute_strategy_workflow(RuntimeContext* ctx, SessionState* session) return -1; } + // Remove stale .tgz archives from log path before any strategy runs. + cleanup_old_archives(ctx->log_path); // Verify context has valid data RDK_LOG(RDK_LOG_DEBUG, LOG_UPLOADSTB, "[%s:%d] Context check: ctx=%p, MAC='%s', device_type='%s'\n", @@ -157,4 +160,3 @@ int execute_strategy_workflow(RuntimeContext* ctx, SessionState* session) return ret; } - diff --git a/uploadstblogs/unittest/cleanup_handler_gtest.cpp b/uploadstblogs/unittest/cleanup_handler_gtest.cpp index f558f65fe..c491b9e1b 100755 --- a/uploadstblogs/unittest/cleanup_handler_gtest.cpp +++ b/uploadstblogs/unittest/cleanup_handler_gtest.cpp @@ -68,7 +68,10 @@ void regfree(regex_t *preg) { DIR* opendir(const char *dirname); struct dirent* readdir(DIR *dirp); int closedir(DIR *dirp); +int dirfd(DIR *dirp); int stat(const char *pathname, struct stat *statbuf); +int fstatat(int dfd, const char *pathname, struct stat *statbuf, int flags); +int unlinkat(int dfd, const char *pathname, int flags); int remove(const char *pathname); int rmdir(const char *pathname); @@ -126,6 +129,40 @@ int closedir(DIR *dirp) { return 0; } +int dirfd(DIR *dirp) { + // Return a dummy fd for the fake DIR pointer + return 5; +} + +int fstatat(int dfd, const char *pathname, struct stat *statbuf, int flags) { + if (stat_fail || !pathname || !statbuf) { + return -1; + } + memset(statbuf, 0, sizeof(struct stat)); + + time_t now = time(NULL); + if (strstr(pathname, "11-30-25-03-45PM") || strstr(pathname, "old_archive")) { + statbuf->st_mtime = now - (5 * 24 * 60 * 60); // 5 days ago + } else { + statbuf->st_mtime = now - (1 * 24 * 60 * 60); // 1 day ago + } + + if (strstr(pathname, "logbackup") || strstr(pathname, "normal_folder")) { + statbuf->st_mode = S_IFDIR | 0755; + } else { + statbuf->st_mode = S_IFREG | 0644; + } + + return 0; +} + +int unlinkat(int dfd, const char *pathname, int flags) { + if (remove_fail || !pathname) { + return -1; + } + return 0; +} + int stat(const char *pathname, struct stat *statbuf) { if (stat_fail || !pathname || !statbuf) { return -1; diff --git a/uploadstblogs/unittest/strategies_gtest.cpp b/uploadstblogs/unittest/strategies_gtest.cpp index 5ea7852d2..38ccab8c2 100755 --- a/uploadstblogs/unittest/strategies_gtest.cpp +++ b/uploadstblogs/unittest/strategies_gtest.cpp @@ -60,6 +60,7 @@ bool rbus_get_bool_param(const char* param_name, bool* value); bool generate_archive_name(char* buffer, size_t buffer_size, const char* type, const char* timestamp); int create_dri_archive(RuntimeContext* ctx, const char* archive_path); void t2_count_notify(char* marker); +int cleanup_old_log_backups(const char* log_path, int max_age_days); // Mock sleep function to avoid delays in tests unsigned int sleep(unsigned int seconds); @@ -204,6 +205,10 @@ void t2_count_notify(char* marker) { // No-op for tests } +int cleanup_old_log_backups(const char* log_path, int max_age_days) { + return 0; // Success +} + // Include the actual implementation for testing #ifdef GTEST_ENABLE #include "../src/strategies.c" diff --git a/uploadstblogs/unittest/strategy_handler_gtest.cpp b/uploadstblogs/unittest/strategy_handler_gtest.cpp index 6bbcf05c8..7b26499ec 100755 --- a/uploadstblogs/unittest/strategy_handler_gtest.cpp +++ b/uploadstblogs/unittest/strategy_handler_gtest.cpp @@ -28,6 +28,7 @@ extern "C" { #include "uploadstblogs_types.h" #include "strategy_handler.h" +int cleanup_old_archives(const char* log_path); } // Mock strategy handlers for testing @@ -97,6 +98,11 @@ static const StrategyHandler mock_dcm_handler = { .cleanup_phase = mock_cleanup_phase }; +// Mock implementation for cleanup_old_archives +extern "C" int cleanup_old_archives(const char* log_path) { + return 0; // Success +} + // Override the external strategy handlers const StrategyHandler ondemand_strategy_handler = mock_ondemand_handler; const StrategyHandler reboot_strategy_handler = mock_reboot_handler; @@ -439,4 +445,4 @@ TEST_F(StrategyHandlerTest, ExecuteWorkflow_PhaseSequencing) { int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); -} \ No newline at end of file +} From d41b60bdb2c09b424bb1273f8c9e029184226547 Mon Sep 17 00:00:00 2001 From: nhanasi Date: Wed, 3 Jun 2026 15:23:39 -0400 Subject: [PATCH 5/9] Integrate Openspec skills for DCM (#138) Co-authored-by: Hanasi --- .github/prompts/opsx-apply.prompt.md | 149 +++++++++ .github/prompts/opsx-archive.prompt.md | 154 ++++++++++ .github/prompts/opsx-explore.prompt.md | 170 +++++++++++ .github/prompts/opsx-propose.prompt.md | 103 +++++++ .github/skills/openspec-apply-change/SKILL.md | 156 ++++++++++ .../skills/openspec-archive-change/SKILL.md | 114 +++++++ .github/skills/openspec-explore/SKILL.md | 288 ++++++++++++++++++ .github/skills/openspec-propose/SKILL.md | 110 +++++++ openspec/config.yaml | 20 ++ 9 files changed, 1264 insertions(+) create mode 100644 .github/prompts/opsx-apply.prompt.md create mode 100644 .github/prompts/opsx-archive.prompt.md create mode 100644 .github/prompts/opsx-explore.prompt.md create mode 100644 .github/prompts/opsx-propose.prompt.md create mode 100644 .github/skills/openspec-apply-change/SKILL.md create mode 100644 .github/skills/openspec-archive-change/SKILL.md create mode 100644 .github/skills/openspec-explore/SKILL.md create mode 100644 .github/skills/openspec-propose/SKILL.md create mode 100644 openspec/config.yaml diff --git a/.github/prompts/opsx-apply.prompt.md b/.github/prompts/opsx-apply.prompt.md new file mode 100644 index 000000000..e23ec64d1 --- /dev/null +++ b/.github/prompts/opsx-apply.prompt.md @@ -0,0 +1,149 @@ +--- +description: Implement tasks from an OpenSpec change (Experimental) +--- + +Implement tasks from an OpenSpec change. + +**Input**: Optionally specify a change name (e.g., `/opsx:apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **Select the change** + + If a name is provided, use it. Otherwise: + - Infer from conversation context if the user mentioned a change + - Auto-select if only one active change exists + - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select + + Always announce: "Using change: " and how to override (e.g., `/opsx:apply `). + +2. **Check status to understand the schema** + ```bash + openspec status --change "" --json + ``` + Parse the JSON to understand: + - `schemaName`: The workflow being used (e.g., "spec-driven") + - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) + +3. **Get apply instructions** + + ```bash + openspec instructions apply --change "" --json + ``` + + This returns: + - `contextFiles`: artifact ID -> array of concrete file paths (varies by schema) + - Progress (total, complete, remaining) + - Task list with status + - Dynamic instruction based on current state + + **Handle states:** + - If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx:continue` + - If `state: "all_done"`: congratulate, suggest archive + - Otherwise: proceed to implementation + +4. **Read context files** + + Read every file path listed under `contextFiles` from the apply instructions output. + The files depend on the schema being used: + - **spec-driven**: proposal, specs, design, tasks + - Other schemas: follow the contextFiles from CLI output + +5. **Show current progress** + + Display: + - Schema being used + - Progress: "N/M tasks complete" + - Remaining tasks overview + - Dynamic instruction from CLI + +6. **Implement tasks (loop until done or blocked)** + + For each pending task: + - Show which task is being worked on + - Make the code changes required + - Keep changes minimal and focused + - Mark task complete in the tasks file: `- [ ]` → `- [x]` + - Continue to next task + + **Pause if:** + - Task is unclear → ask for clarification + - Implementation reveals a design issue → suggest updating artifacts + - Error or blocker encountered → report and wait for guidance + - User interrupts + +7. **On completion or pause, show status** + + Display: + - Tasks completed this session + - Overall progress: "N/M tasks complete" + - If all done: suggest archive + - If paused: explain why and wait for guidance + +**Output During Implementation** + +``` +## Implementing: (schema: ) + +Working on task 3/7: +[...implementation happening...] +✓ Task complete + +Working on task 4/7: +[...implementation happening...] +✓ Task complete +``` + +**Output On Completion** + +``` +## Implementation Complete + +**Change:** +**Schema:** +**Progress:** 7/7 tasks complete ✓ + +### Completed This Session +- [x] Task 1 +- [x] Task 2 +... + +All tasks complete! You can archive this change with `/opsx:archive`. +``` + +**Output On Pause (Issue Encountered)** + +``` +## Implementation Paused + +**Change:** +**Schema:** +**Progress:** 4/7 tasks complete + +### Issue Encountered + + +**Options:** +1.