diff --git a/backup_logs/src/backup_engine.c b/backup_logs/src/backup_engine.c index 57b7050a..ac416620 100644 --- a/backup_logs/src/backup_engine.c +++ b/backup_logs/src/backup_engine.c @@ -373,8 +373,14 @@ int backup_and_recover_logs(const char* source, const char* dest, return BACKUP_ERROR_INVALID_PARAM; } /* Open source directory */ - DIR* dir = opendir(source); + int dirfd = open(source, O_RDONLY | O_DIRECTORY); + if (dirfd < 0) { + RDK_LOG(RDK_LOG_ERROR, LOG_BACKUP_LOGS, "Failed to open source directory: %s\n", source); + return BACKUP_ERROR_FILESYSTEM; + } + DIR* dir = fdopendir(dirfd); if (!dir) { + close(dirfd); RDK_LOG(RDK_LOG_ERROR, LOG_BACKUP_LOGS, "Failed to open source directory: %s\n", source); return BACKUP_ERROR_FILESYSTEM; } @@ -402,29 +408,24 @@ int backup_and_recover_logs(const char* source, const char* dest, continue; } - /* Check if it's a regular file (match shell script -type f). - * Use open(O_NOFOLLOW) + fstat() to eliminate TOCTOU (CWE-367): - * opening with O_NOFOLLOW refuses symlinks, and fstat() on the - * resulting fd operates on the same inode already held open, - * so no race window exists between the check and the use. */ + /* Use fstatat with AT_SYMLINK_NOFOLLOW on the directory fd (same pattern + * as archive_manager.c) to detect file type without TOCTOU races. + * Symlinks whose target is a regular file are allowed through. */ struct stat file_stat; - int check_fd = open(source_file, O_RDONLY | O_NOFOLLOW); - if (check_fd < 0) { - /* Skip if file cannot be opened (e.g. symlink or permission denied) */ + if (fstatat(dirfd, entry->d_name, &file_stat, AT_SYMLINK_NOFOLLOW) != 0) { continue; } - if (fstat(check_fd, &file_stat) != 0) { - close(check_fd); - continue; - } - close(check_fd); if (S_ISDIR(file_stat.st_mode)) { - /* Skip directories - we don't want to backup directories to PreviousLogs */ RDK_LOG(RDK_LOG_DEBUG, LOG_BACKUP_LOGS, "Skipping directory: %s\n", source_file); continue; } - if (!S_ISREG(file_stat.st_mode)) { - /* Skip non-regular files (symlinks, devices, etc.) */ + if (S_ISLNK(file_stat.st_mode)) { + /* Symlink: verify target is a regular file before allowing copy */ + struct stat target_stat; + if (fstatat(dirfd, entry->d_name, &target_stat, 0) != 0 || !S_ISREG(target_stat.st_mode)) { + continue; + } + } else if (!S_ISREG(file_stat.st_mode)) { continue; } diff --git a/dcm_utils.c b/dcm_utils.c index a48e0d18..41ed65b3 100644 --- a/dcm_utils.c +++ b/dcm_utils.c @@ -33,6 +33,7 @@ #include #include #include +#include "rdk_logger.h" #include "dcm_types.h" #include "dcm_utils.h" @@ -47,9 +48,30 @@ INT32 g_rdk_logger_enabled = 0; void DCMLOGInit() { #ifdef RDK_LOGGER_ENABLED - if (0 == rdk_logger_init(DEBUG_INI_NAME)) { + // Initialize RDK Logger + rdk_LogOutput_File filelog; + strncpy(filelog.fileName, "dcmscript.log", sizeof(filelog.fileName)-1); + filelog.fileName[sizeof(filelog.fileName) - 1] = '\0'; + strncpy(filelog.fileLocation, "/opt/logs/", sizeof(filelog.fileLocation)-1); + filelog.fileLocation[sizeof(filelog.fileLocation) - 1] = '\0'; + + /* Extended initialization with programmatic configuration */ + rdk_logger_ext_config_t config = { + .pModuleName = "LOG.RDK.DCM", /* Module name */ + .loglevel = RDK_LOG_INFO, /* Default log level */ + //.output = RDKLOG_OUTPUT_FILE, + .output = RDKLOG_OUTPUT_CONSOLE, + .format = RDKLOG_FORMAT_WITH_TS, /* Timestamped format */ + .pFilePolicy = NULL /* using file output */ + }; + + if (rdk_logger_ext_init(&config) != RDK_SUCCESS) { + printf("UPLOADSTB : ERROR - Extended logger init failed\n"); + } + else { g_rdk_logger_enabled = 1; } + #endif } diff --git a/uploadstblogs/include/cleanup_handler.h b/uploadstblogs/include/cleanup_handler.h index 98bdaa3f..78243929 100755 --- a/uploadstblogs/include/cleanup_handler.h +++ b/uploadstblogs/include/cleanup_handler.h @@ -130,4 +130,15 @@ int cleanup_old_archives(const char *log_path); */ bool is_timestamped_backup(const char *filename); +/** + * @brief Remove stale timestamped files from log directory + * + * Removes regular files matching timestamp pattern but excludes + * logbackup directories and moca.pcap files. + * + * @param log_path Log directory path + * @return Number of files removed + */ +int remove_stale_timestamped_files(const char *log_path); + #endif /* CLEANUP_HANDLER_H */ diff --git a/uploadstblogs/include/uploadstblogs.h b/uploadstblogs/include/uploadstblogs.h index 3cb16fa2..41b2b7ae 100755 --- a/uploadstblogs/include/uploadstblogs.h +++ b/uploadstblogs/include/uploadstblogs.h @@ -28,6 +28,10 @@ #ifndef UPLOADSTBLOGS_H #define UPLOADSTBLOGS_H +#ifdef __cplusplus +extern "C" { +#endif + #include "uploadstblogs_types.h" /** @@ -117,4 +121,8 @@ int uploadstblogs_execute(int argc, char** argv); */ int main(int argc, char** argv); +#ifdef __cplusplus +} +#endif + #endif /* UPLOADSTBLOGS_H */ diff --git a/uploadstblogs/include/uploadstblogs_types.h b/uploadstblogs/include/uploadstblogs_types.h index d2ef38fd..55114ef0 100755 --- a/uploadstblogs/include/uploadstblogs_types.h +++ b/uploadstblogs/include/uploadstblogs_types.h @@ -145,6 +145,7 @@ typedef struct { TriggerType trigger_type; /**< Trigger type (TRIGGER_SCHEDULED, TRIGGER_ONDEMAND, etc.) */ bool rrd_flag; /**< RRD flag */ const char* rrd_file; /**< RRD upload log file path (optional) */ + bool uploadlogsnow_mode; /**< When true, execute UploadLogsNow workflow */ } UploadSTBLogsParams; diff --git a/uploadstblogs/src/Makefile.am b/uploadstblogs/src/Makefile.am index aa82bd8d..b68645bc 100755 --- a/uploadstblogs/src/Makefile.am +++ b/uploadstblogs/src/Makefile.am @@ -41,5 +41,9 @@ logupload_CFLAGS = -Wall -DEN_MAINTENANCE_MANAGER -DIARM_ENABLED -DT2_EVENT_ENAB logupload_LDADD = libuploadstblogs.la -lrdkloggers -lfwutils -lt2utils -ltelemetry_msgsender - +# Install public headers for external consumers (e.g. tr69hostif) +uploadstblogsincludedir = $(includedir)/uploadstblogs +uploadstblogsinclude_HEADERS = \ + $(top_srcdir)/uploadstblogs/include/uploadstblogs.h \ + $(top_srcdir)/uploadstblogs/include/uploadstblogs_types.h diff --git a/uploadstblogs/src/archive_manager.c b/uploadstblogs/src/archive_manager.c index 430d71b9..7f44c61a 100755 --- a/uploadstblogs/src/archive_manager.c +++ b/uploadstblogs/src/archive_manager.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include "archive_manager.h" #include "file_operations.h" @@ -478,42 +479,36 @@ static unsigned int calculate_tar_checksum(struct tar_header* header) } /** - * @brief Write TAR header for a file + * @brief Write TAR header for a file or symlink */ -static int write_tar_header(gzFile gz, const char* filename, struct stat* st) +static int write_tar_header(gzFile gz, const char* filename, struct stat* st, const char* link_target) { struct tar_header header; memset(&header, 0, sizeof(header)); - // Filename (strip leading path for archive) strncpy(header.name, filename, sizeof(header.name) - 1); - - // File mode snprintf(header.mode, sizeof(header.mode), "%07o", (unsigned int)st->st_mode & 0777); - - // UID and GID snprintf(header.uid, sizeof(header.uid), "%07o", 0); snprintf(header.gid, sizeof(header.gid), "%07o", 0); - - // File size - snprintf(header.size, sizeof(header.size), "%011lo", (unsigned long)st->st_size); - - // Modification time snprintf(header.mtime, sizeof(header.mtime), "%011lo", (unsigned long)st->st_mtime); - - // Type flag (regular file) - header.typeflag = '0'; - - // Magic and version (ustar) memcpy(header.magic, "ustar", 5); header.magic[5] = '\0'; memcpy(header.version, "00", 2); + + if (S_ISLNK(st->st_mode)) { + header.typeflag = '2'; + snprintf(header.size, sizeof(header.size), "%011o", 0); + if (link_target) { + strncpy(header.linkname, link_target, sizeof(header.linkname) - 1); + } + } else { + header.typeflag = '0'; + snprintf(header.size, sizeof(header.size), "%011lo", (unsigned long)st->st_size); + } - // Calculate and write checksum unsigned int checksum = calculate_tar_checksum(&header); snprintf(header.checksum, sizeof(header.checksum), "%06o", checksum); - // Write header to gzip file if (gzwrite(gz, &header, sizeof(header)) != sizeof(header)) { return -1; } @@ -528,10 +523,9 @@ static int add_file_to_tar(gzFile gz, const char* filepath, const char* arcname) { struct stat st; - // Open file first with O_NOFOLLOW to prevent symlink attacks (TOCTOU fix) int fd = open(filepath, O_RDONLY | O_NOFOLLOW); if (fd < 0) { - if (errno != ELOOP) { // ELOOP = symlink detected + if (errno != ELOOP) { RDK_LOG(RDK_LOG_ERROR, LOG_UPLOADSTB, "[%s:%d] Failed to open file: %s (errno=%d)\n", __FUNCTION__, __LINE__, filepath, errno); @@ -554,7 +548,7 @@ static int add_file_to_tar(gzFile gz, const char* filepath, const char* arcname) } // Write TAR header - if (write_tar_header(gz, arcname, &st) != 0) { + if (write_tar_header(gz, arcname, &st, NULL) != 0) { RDK_LOG(RDK_LOG_ERROR, LOG_UPLOADSTB, "[%s:%d] Failed to write TAR header\n", __FUNCTION__, __LINE__); close(fd); @@ -601,8 +595,14 @@ static int add_file_to_tar(gzFile gz, const char* filepath, const char* arcname) */ static int add_directory_to_tar(gzFile gz, const char* dirpath, const char* base_path, const char* exclude_file) { - DIR* dir = opendir(dirpath); + int dirfd = open(dirpath, O_RDONLY | O_DIRECTORY); + if (dirfd < 0) { + return -1; + } + + DIR* dir = fdopendir(dirfd); if (!dir) { + close(dirfd); return -1; } @@ -623,7 +623,7 @@ static int add_directory_to_tar(gzFile gz, const char* dirpath, const char* base } struct stat st; - if (stat(fullpath, &st) != 0) { + if (fstatat(dirfd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0) { continue; } @@ -634,13 +634,28 @@ static int add_directory_to_tar(gzFile gz, const char* dirpath, const char* base } if (S_ISDIR(st.st_mode)) { - // Recursively process subdirectory if (add_directory_to_tar(gz, fullpath, base_path, exclude_file) != 0) { closedir(dir); return -1; } + } else if (S_ISLNK(st.st_mode)) { + char target[PATH_MAX]; + ssize_t len = readlinkat(dirfd, entry->d_name, target, sizeof(target) - 1); + if (len < 0) { + RDK_LOG(RDK_LOG_WARN, LOG_UPLOADSTB, + "[%s:%d] Failed to readlink: %s\n", __FUNCTION__, __LINE__, fullpath); + continue; + } + target[len] = '\0'; + RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, + "Processing file...%s\n", arcname); + if (write_tar_header(gz, arcname, &st, target) != 0) { + RDK_LOG(RDK_LOG_WARN, LOG_UPLOADSTB, + "[%s:%d] Failed to add symlink: %s\n", __FUNCTION__, __LINE__, fullpath); + } } else if (S_ISREG(st.st_mode)) { - // Add file + RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, + "Processing file...%s\n", arcname); if (add_file_to_tar(gz, fullpath, arcname) != 0) { RDK_LOG(RDK_LOG_WARN, LOG_UPLOADSTB, "[%s:%d] Failed to add file: %s\n", __FUNCTION__, __LINE__, fullpath); diff --git a/uploadstblogs/src/cleanup_handler.c b/uploadstblogs/src/cleanup_handler.c index 25087a4d..e7eff550 100755 --- a/uploadstblogs/src/cleanup_handler.c +++ b/uploadstblogs/src/cleanup_handler.c @@ -296,6 +296,49 @@ int cleanup_old_archives(const char *log_path) return removed_count; } +int remove_stale_timestamped_files(const char *log_path) +{ + if (!log_path) { + return -1; + } + + DIR *dir = opendir(log_path); + if (!dir) { + return -1; + } + + int removed_count = 0; + struct dirent *entry; + int dfd = dirfd(dir); + + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + struct stat st; + if (fstatat(dfd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0 || !S_ISREG(st.st_mode)) { + continue; + } + if (!is_timestamped_backup(entry->d_name)) { + continue; + } + if (strstr(entry->d_name, "logbackup") || strstr(entry->d_name, "moca.pcap")) { + continue; + } + + RDK_LOG(RDK_LOG_DEBUG, LOG_UPLOADSTB, + "[%s:%d] Removing stale timestamped file: %s\n", + __FUNCTION__, __LINE__, entry->d_name); + if (unlinkat(dfd, entry->d_name, 0) == 0) { + removed_count++; + } + } + + closedir(dir); + return removed_count; +} + /* ========================== Upload Finalization Functions ========================== */ diff --git a/uploadstblogs/src/context_manager.c b/uploadstblogs/src/context_manager.c index a713ed2e..9d27e813 100755 --- a/uploadstblogs/src/context_manager.c +++ b/uploadstblogs/src/context_manager.c @@ -155,13 +155,20 @@ bool is_codebig_blocked(int block_time) bool init_context(RuntimeContext* ctx) { // Initialize RDK Logger + rdk_LogOutput_File filelog; + strncpy(filelog.fileName, "dcmscript.log", sizeof(filelog.fileName)-1); + filelog.fileName[sizeof(filelog.fileName) - 1] = '\0'; + strncpy(filelog.fileLocation, "/opt/logs/", sizeof(filelog.fileLocation)-1); + filelog.fileLocation[sizeof(filelog.fileLocation) - 1] = '\0'; + /* Extended initialization with programmatic configuration */ rdk_logger_ext_config_t config = { .pModuleName = "LOG.RDK.UPLOADSTB", /* Module name */ .loglevel = RDK_LOG_INFO, /* Default log level */ - .output = RDKLOG_OUTPUT_CONSOLE, /* Output to console (stdout/stderr) */ + .output = RDKLOG_OUTPUT_CONSOLE, .format = RDKLOG_FORMAT_WITH_TS, /* Timestamped format */ - .pFilePolicy = NULL /* Not using file output, so NULL */ + .pFilePolicy = NULL + //.pFilePolicy = &filelog /* using file output */ }; if (rdk_logger_ext_init(&config) != RDK_SUCCESS) { diff --git a/uploadstblogs/src/strategy_handler.c b/uploadstblogs/src/strategy_handler.c index 9a0086a2..d394d057 100755 --- a/uploadstblogs/src/strategy_handler.c +++ b/uploadstblogs/src/strategy_handler.c @@ -23,10 +23,12 @@ */ #include +#include +#include #include "strategy_handler.h" #include "cleanup_handler.h" +#include "file_operations.h" #include "rdk_debug.h" -#include // Forward declarations of strategy handlers extern const StrategyHandler ondemand_strategy_handler; @@ -71,8 +73,10 @@ int execute_strategy_workflow(RuntimeContext* ctx, SessionState* session) return -1; } - // Remove stale .tgz archives from log path before any strategy runs. + // Pre-strategy cleanup (script lines 967-984) cleanup_old_archives(ctx->log_path); + clear_old_packet_captures(ctx->log_path); + remove_stale_timestamped_files(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", diff --git a/uploadstblogs/src/uploadstblogs.c b/uploadstblogs/src/uploadstblogs.c index c766773c..203237bb 100755 --- a/uploadstblogs/src/uploadstblogs.c +++ b/uploadstblogs/src/uploadstblogs.c @@ -282,6 +282,24 @@ int uploadstblogs_run(const UploadSTBLogsParams* params) strncpy(ctx.rrd_file, params->rrd_file, sizeof(ctx.rrd_file) - 1); } + ctx.uploadlogsnow_mode = params->uploadlogsnow_mode; + + /* Handle UploadLogsNow mode */ + if (ctx.uploadlogsnow_mode) { + RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, + "[%s:%d] UploadLogsNow mode detected via API, executing custom workflow\n", + __FUNCTION__, __LINE__); + + ret = execute_uploadlogsnow_workflow(&ctx); + +#ifdef T2_EVENT_ENABLED + t2_uninit(); +#endif + cleanup_iarm_connection(); + release_lock(); + return ret; + } + /* Validate system prerequisites */ if (!validate_system(&ctx)) { fprintf(stderr, "System validation failed\n"); @@ -404,6 +422,14 @@ int uploadstblogs_execute(int argc, char** argv) return ret; } + /* Limit attempts to 1 when called from plugin (deepsleep) */ + if (ctx.trigger_type == TRIGGER_MANUAL) { + ctx.direct_max_attempts = 1; + RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, "Called from Plugin with 1 attempt\n"); + } else { + RDK_LOG(RDK_LOG_INFO, LOG_UPLOADSTB, "Called with %d attempts\n", ctx.direct_max_attempts); + } + /* Verify context after parse_args */ RDK_LOG(RDK_LOG_DEBUG, LOG_UPLOADSTB, "[main] Context after parse_args: MAC='%s', device_type='%s'\n",