diff --git a/Makefile.am b/Makefile.am index 3d42c59..acadda8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,6 +19,6 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = rebootnow/src +SUBDIRS = reboot-helper/src reboot-reason-fetcher/src -include_HEADERS = rebootnow/include/rebootnow.h rebootnow/include/rbus_interface.h +include_HEADERS = reboot-helper/include/reboot.h reboot-helper/include/rbus_interface.h reboot-reason-fetcher/include/update-reboot-info.h diff --git a/configure.ac b/configure.ac index 27cdc72..fd3c95b 100644 --- a/configure.ac +++ b/configure.ac @@ -20,7 +20,7 @@ # Process this file with autoconf to produce a configure script. AC_INIT([REBOOT], [1.0], [saranya_elango@comcast.com]) -AC_CONFIG_SRCDIR([rebootnow/src/main.c]) +AC_CONFIG_SRCDIR([reboot-helper/src/reboot_main.c], [reboot-reason-fetcher/src/rebootreason_main.c]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIRS([m4]) AM_INIT_AUTOMAKE([subdir-objects foreign ]) @@ -97,6 +97,6 @@ if test "$USE_CPC" = "yes"; then AC_DEFINE([USE_CPC], [1], [Define to enable CPC support]) fi -AC_CONFIG_FILES([Makefile rebootnow/src/Makefile]) +AC_CONFIG_FILES([Makefile reboot-helper/src/Makefile reboot-reason-fetcher/src/Makefile]) AC_OUTPUT diff --git a/docs/HLD.md b/docs/reboot-helper/HLD.md similarity index 100% rename from docs/HLD.md rename to docs/reboot-helper/HLD.md diff --git a/docs/LLD.md b/docs/reboot-helper/LLD.md similarity index 100% rename from docs/LLD.md rename to docs/reboot-helper/LLD.md diff --git a/docs/README.md b/docs/reboot-helper/README.md similarity index 100% rename from docs/README.md rename to docs/reboot-helper/README.md diff --git a/docs/architecture.md b/docs/reboot-helper/architecture.md similarity index 100% rename from docs/architecture.md rename to docs/reboot-helper/architecture.md diff --git a/docs/diagrams/flowchart.md b/docs/reboot-helper/diagrams/flowchart.md similarity index 100% rename from docs/diagrams/flowchart.md rename to docs/reboot-helper/diagrams/flowchart.md diff --git a/docs/diagrams/sequence-diagram.md b/docs/reboot-helper/diagrams/sequence-diagram.md similarity index 100% rename from docs/diagrams/sequence-diagram.md rename to docs/reboot-helper/diagrams/sequence-diagram.md diff --git a/docs/testing.md b/docs/reboot-helper/testing.md similarity index 100% rename from docs/testing.md rename to docs/reboot-helper/testing.md diff --git a/docs/reboot-reason-fetcher/HLD.md b/docs/reboot-reason-fetcher/HLD.md new file mode 100644 index 0000000..0cd8011 --- /dev/null +++ b/docs/reboot-reason-fetcher/HLD.md @@ -0,0 +1,199 @@ +# High-Level Design (HLD): Reboot Reason Processing (C Migration) + +## 1. Architecture Overview +The migrated component will be a standalone executable (e.g., `reboot_reason_updater`) invoked similarly to the original script (systemd unit or another supervising script). It implements a layered architecture: + +``` ++---------------------------------------------------------+ +| Application Layer: main() | +| - Orchestrates workflow | ++----------------------+----------------------------------+ +| Services Layer | | +| - Classification | Firmware Failure Analyzer | +| - Panic Detector | Hardware Reason Resolver | +| - Log Parser | Parodus Logger | ++----------------------+----------------------------------+ +| HAL / Platform Abstraction | +| - Broadcom hw register reader | +| - Realtek wakeup parser | +| - Amlogic reset code interpreter | +| - Generic kernel/pstore reader | ++---------------------------------------------------------+ +| Utility Layer | +| - File IO wrappers (bounded reads) | +| - String normalization | +| - JSON writer (minimal) | +| - Lock manager (directory lock) | +| - Telemetry adapter | ++---------------------------------------------------------+ +``` + +## 2. Module Breakdown + +| Module | Responsibility | +|--------|---------------| +| `main.c` | Entry point, orchestrates steps, error fallback, lock lifecycle. | +| `lock.c` / `lock.h` | Directory-based mutual exclusion and timeout. | +| `env.c` | Load device properties into a struct. | +| `hw_brcm.c` | Parse `/proc/brcm/previous_reboot_reason`. | +| `hw_rtk.c` | Extract `wakeupreason=` from `/proc/cmdline`. | +| `hw_amlogic.c` | Map numeric reset code to structured info. | +| `panic.c` | Kernel panic / oops detection via messages/pstore. | +| `firmware_fail.c` | Detect max reboot / ECM crash conditions. | +| `classification.c` | Map initiator to category; apply overrides. | +| `log_parser.c` | Parse legacy `/opt/logs/rebootInfo.log`. | +| `json_writer.c` | Emit consistent JSON with stable ordering. | +| `telemetry.c` | Abstracted counter updates (weak symbol). | +| `parodus_log.c` | Append to Parodus log with consistent timestamp format. | +| `keypress.c` | Copy keypress metadata. | + +## 3. Data Flow Description + +1. `main()` acquires lock. +2. Loads environment (device name, SOC, profile). +3. Validates flags; may early exit. +4. Ensures directory existence. +5. Checks for new reboot info file: + - If found: move & log → skip derivation. + - Else: + - Parse previous textual log for baseline fields. + - If missing initiator: + - Panic detector invoked. If panic → classify. + - Else call platform-specific hardware module. + - If initiator present → classification sets applied. + - Firmware failure override (non-TV). +6. Build `RebootInfo` struct. +7. Write JSON to `previousreboot.info`, update `hardpower.info` if appropriate. +8. Mirror reason to kernel messages (platform-specific conditions). +9. Copy keypress file if exists. +10. Log to Parodus. +11. Create invocation flag. +12. Release lock and exit. + +## 4. Control Flow (Conceptual) +See flowcharts file for detailed mermaid representation. +Key decision points: +- Flag presence gating. +- New info vs legacy derivation. +- Kernel panic detection precedence. +- Firmware failure override precedence. +- Hardware path selection based on SOC / device profile. + +## 5. Key Algorithms & Data Structures + +### Data Structures +``` +typedef struct { + char timestamp[64]; + char source[64]; + char reason[64]; + char custom_reason[128]; + char other_reason[256]; +} RebootInfo; + +typedef enum { + CAT_APP_TRIGGERED, + CAT_OPS_TRIGGERED, + CAT_MAINTENANCE_REBOOT, + CAT_FIRMWARE_FAILURE, + CAT_KERNEL_PANIC, + CAT_HARD_POWER, + CAT_POWER_ON_RESET, + CAT_UNKNOWN_RESET, + CAT_WATCHDOG, + CAT_FACTORY_RESET, + CAT_UPDATE_BOOT, + // ... +} ReasonCategory; +``` + +### Algorithms +1. Membership Test: + - Arrays stored as sorted `const char*` lists; linear scan acceptable given small N (<100). Optionally use hashing if expanded. +2. Panic Detection: + - Scan pstore or messages file for multiple signatures; short-circuit once found. +3. Hardware Mapping (Amlogic): + - Switch-case over integer code (0–15); direct O(1) mapping. +4. Log Parsing: + - Single pass; use bounded `fgets` lines; search prefixes; trims leading spaces. +5. Firmware Failure: + - Scan specific log sources for sentinel strings; sets override flag. + +## 6. Interfaces & Integration Points + +| Interface | Function Signature | Notes | +|-----------|--------------------|-------| +| Hardware reason | `int hw_get_reason(DeviceContext*, HwReason* out)` | Returns normalized enum + raw tokens. | +| Panic check | `bool detect_kernel_panic(DeviceContext*, PanicInfo* out)` | Abstract file paths by profile. | +| Firmware failure | `bool check_firmware_failure(DeviceContext*, FwFailInfo* out)` | Returns true if override required. | +| Classification | `void classify(RebootInfo*, const DeviceContext*, const ParsedSources*)` | Sets `reason` and `custom_reason`. | +| JSON writer | `int write_reboot_info(const char* path, const RebootInfo* info)` | Atomic temp+rename. | +| Lock | `int acquire_lock(const char* path, int timeout_sec)` / `void release_lock(const char* path)` | Directory semantics. | +| Telemetry | `void telemetry_count(const char* key)` | Weak symbol / stub fallback. | + +## 7. Error Handling Strategy +- Functions return status codes; `main` accumulates quality-of-result flags. +- Non-critical failures logged; classification falls back progressively. +- Atomic JSON writes: write to `path.tmp` then `rename()` to avoid partial files. +- Buffer overruns prevented by length-checked concatenations (`snprintf`). + +## 8. Concurrency Considerations +- Single-process guarantee via lock directory. +- Avoid holding files open longer than necessary. +- No shared memory or threading required. + +## 9. Portability Strategy +- Conditional compilation: + - `#ifdef SOC_BRCM`, `#ifdef SOC_REALTEK`, `#ifdef SOC_AMLOGIC`. +- Provide generic stubs when platform path absent. +- Avoid GNU extensions; target C99. + +## 10. Logging Approach +- Macro: `LOG(fmt, ...)` mapping to `fprintf(stderr, ...)` or syslog selectable at compile-time. +- Include timestamp only when writing to Parodus log; other logs mimic script style. + +## 11. Security & Robustness +- Validate all file sizes: if >128 KB, limit scanning or skip with fallback reason. +- Reject non-printable characters from hardware reason tokens. +- Directory creation uses restrictive permissions (`0700`). +- JSON output escapes quotes minimally (expected tokens are alphanumeric / underscores). + +## 12. Performance Considerations +- O(1) mapping for hardware codes. +- Single pass over each file. +- Avoid dynamic allocation—use stack/local static arrays. +- Optional compile-time feature flags to disable expensive scanning (e.g., full pstore enumeration). + +## 13. Known Logical Fixes +- Adjust firmware failure condition to use logical AND (`if DEVICE != PLATCO && DEVICE != LLAMA`). +- Correct inconsistent variable usage in script Parodus log line referencing `$customReason`. + +## 14. Extensibility +- Adding new hardware platforms: implement new `hw_.c`. +- Additional panic signatures: extend `panic_signatures[]`. +- Telemetry keys expand via config header. + +## 15. High-Level Sequence (Summary) +1. Lock +2. Flags check +3. Directory ensure +4. New info file? → move → JSON finalize → (skip classification path) +5. Else derive fields +6. Firmware failure override +7. Write JSON(s) +8. Kernel log annotation +9. Parodus + keypress handling +10. Flag create +11. Unlock + +## 16. Non-Goals +- Real-time monitoring of subsequent reboots. +- Telemetry batching. +- Multi-language bindings. + +## 17. Assumptions +- Device properties file reliably accessible. +- Telemetry API optional. +- System clock reliable for UTC timestamp. + +*End of HLD.* diff --git a/docs/reboot-reason-fetcher/LLD.md b/docs/reboot-reason-fetcher/LLD.md new file mode 100644 index 0000000..0a7ad11 --- /dev/null +++ b/docs/reboot-reason-fetcher/LLD.md @@ -0,0 +1,459 @@ +# Low-Level Design (LLD): Reboot Reason Updater (C Migration) + +## 1. Purpose +Define detailed structures, functions, pseudocode, and error handling for the C implementation of the `update_previous_reboot_info.sh` script. + +--- + +## 2. Data Structures + +```c +#define MAX_TS_LEN 64 +#define MAX_SOURCE_LEN 64 +#define MAX_REASON_LEN 64 +#define MAX_CUSTOM_LEN 128 +#define MAX_OTHER_LEN 256 + +typedef struct { + char timestamp[MAX_TS_LEN]; + char source[MAX_SOURCE_LEN]; + char reason[MAX_REASON_LEN]; + char custom_reason[MAX_CUSTOM_LEN]; + char other_reason[MAX_OTHER_LEN]; +} RebootInfo; + +typedef enum { + RB_OK = 0, + RB_ERR_IO, + RB_ERR_PARSE, + RB_ERR_LOCK, + RB_ERR_PLATFORM +} rb_status_t; + +typedef struct { + int is_platco; + int is_llama; + int is_tv_profile; + int is_stb_type; + char soc[16]; // "BRCM", "RTK", "REALTEK", "AMLOGIC", etc. + char device_name[32]; + char device_type[32]; +} DeviceContext; + +typedef struct { + int firmware_failure; // 0/1 + char detail[128]; +} FirmwareFailureInfo; + +typedef struct { + int panic_detected; + char panic_signature[64]; +} PanicInfo; + +typedef struct { + int has_hw_reason; + char primary_reason[MAX_REASON_LEN]; + char custom_tokens[MAX_CUSTOM_LEN]; +} HwReasonInfo; +``` + +--- + +## 3. Constants / Classification Sets + +```c +static const char* APP_TRIGGERED[] = { + "Servicemanager", "WarehouseReset", "TR69Agent", /*... truncated list ...*/ +}; +static const size_t APP_TRIGGERED_COUNT = sizeof(APP_TRIGGERED)/sizeof(APP_TRIGGERED[0]); + +static const char* OPS_TRIGGERED[] = { + "ScheduledReboot", "FactoryReset", "UpgradeReboot_restore", /*...*/ +}; +static const size_t OPS_TRIGGERED_COUNT = sizeof(OPS_TRIGGERED)/sizeof(OPS_TRIGGERED[0]); + +static const char* MAINT_TRIGGERED[] = { + "AutoReboot.sh", "PwrMgr" +}; +static const size_t MAINT_TRIGGERED_COUNT = sizeof(MAINT_TRIGGERED)/sizeof(MAINT_TRIGGERED[0]); + +static const char* PANIC_SIGNATURES[] = { + "PREVIOUS_KERNEL_OOPS_DUMP", + "Kernel panic - not syncing", + "Oops - undefined instruction", + "Oops - bad syscall", + "branch through zero", + "unknown data abort code", + "Illegal memory access" +}; +``` + +--- + +## 4. File Paths (Constants) + +```c +#define PATH_REBOOT_INFO "/opt/secure/reboot/reboot.info" +#define PATH_PREV_REBOOT_INFO "/opt/secure/reboot/previousreboot.info" +#define PATH_PARODUS_REBOOT_INFO "/opt/secure/reboot/parodusreboot.info" +#define PATH_PREV_PARODUS_REBOOT_INFO "/opt/secure/reboot/previousparodusreboot.info" +#define PATH_HARDPOWER_INFO "/opt/secure/reboot/hardpower.info" +#define PATH_KEYPRESS_INFO "/opt/secure/reboot/keypress.info" +#define PATH_PREV_KEYPRESS_INFO "/opt/secure/reboot/previouskeypress.info" +#define PATH_REBOOT_DIR "/opt/secure/reboot" + +#define PATH_REBOOT_LOG "/opt/logs/rebootInfo.log" +#define PATH_KERNEL_LOG "/opt/logs/messages.txt" +#define PATH_PARODUS_LOG "/opt/logs/parodus.log" + +#define PATH_FLAG_STT "/tmp/stt_received" +#define PATH_FLAG_REBOOT_INFO_UPDATED "/tmp/rebootInfo_Updated" +#define PATH_FLAG_INVOCATION "/tmp/Update_rebootInfo_invoked" +#define PATH_LOCK_DIR "/tmp/rebootInfo.lock" + +#define PATH_AMLOGIC_RESET_REASON "/sys/devices/platform/aml_pm/reset_reason" +#define PATH_BRCM_PREV_REBOOT_REASON "/proc/brcm/previous_reboot_reason" +#define PATH_RTK_CMDLINE "/proc/cmdline" +``` + +--- + +## 5. Function Prototypes + +```c +int acquire_lock(const char* path); // returns RB_OK or RB_ERR_LOCK +void release_lock(const char* path); + +int ensure_directory(const char* path); // RB_OK or RB_ERR_IO +int file_exists(const char* path); +int read_small_file(const char* path, char* buf, size_t bufsize); // bounded read + +int load_device_context(DeviceContext* ctx); // parse /etc/device.properties + +int parse_previous_log(RebootInfo* info); // fills existing fields if log present + +int detect_kernel_panic(const DeviceContext* ctx, PanicInfo* pi); +int get_hardware_reason(const DeviceContext* ctx, HwReasonInfo* hw); + +int classify_reason(RebootInfo* info, + const DeviceContext* ctx, + const HwReasonInfo* hw, + const PanicInfo* panic, + const FirmwareFailureInfo* fw); + +int check_firmware_failure(const DeviceContext* ctx, FirmwareFailureInfo* fw); + +int write_json_reboot_info(const char* path, const RebootInfo* info); +int write_json_hardpower(const char* path, const char* timestamp); + +int append_kernel_reason_if_needed(const DeviceContext* ctx, const RebootInfo* info); +int copy_file(const char* src, const char* dst); + +void telemetry_count(const char* key); // weak, may be stub + +int lower_copy(char* dst, size_t dstlen, const char* src); +char* trim_leading(char* s); +``` + +--- + +## 6. Pseudocode (Main Flow) + +```c +int main(void) { + DeviceContext ctx = {0}; + RebootInfo info = {0}; + HwReasonInfo hw = {0}; + PanicInfo panic = {0}; + FirmwareFailureInfo fw = {0}; + int is_new_info = 0; + int rc; + + rc = acquire_lock(PATH_LOCK_DIR); + if (rc != RB_OK) return 1; + + rc = load_device_context(&ctx); + + // Flag gating (correct logic) + if (!ctx.is_platco && !ctx.is_llama) { + if (!file_exists(PATH_FLAG_STT) || !file_exists(PATH_FLAG_REBOOT_INFO_UPDATED)) { + release_lock(PATH_LOCK_DIR); + return 0; + } + } else { + // For PLATCO/LLAMA allow first run without flags + if (file_exists(PATH_FLAG_INVOCATION)) { + if (!file_exists(PATH_FLAG_STT) || !file_exists(PATH_FLAG_REBOOT_INFO_UPDATED)) { + release_lock(PATH_LOCK_DIR); + return 0; + } + } + } + + ensure_directory(PATH_REBOOT_DIR); + + // TV specific initial kernel annotation + if (ctx.is_tv_profile) { + if (!kernel_log_has_previous_reason()) { + detect_kernel_panic(&ctx, &panic); + if (panic.panic_detected) { + append_kernel_panic_annotation(); + } else if (file_exists("/lib/rdk/get-reboot-reason.sh")) { + run_external_script("/lib/rdk/get-reboot-reason.sh"); + } + } + } + + if (file_exists(PATH_REBOOT_INFO)) { + // Move new file to previous + rename(PATH_REBOOT_INFO, PATH_PREV_REBOOT_INFO); + if (file_exists(PATH_PARODUS_REBOOT_INFO)) { + append_parodus_log(PATH_PARODUS_REBOOT_INFO); + rename(PATH_PARODUS_REBOOT_INFO, PATH_PREV_PARODUS_REBOOT_INFO); + } + if (file_exists(PATH_KEYPRESS_INFO)) { + copy_file(PATH_KEYPRESS_INFO, PATH_PREV_KEYPRESS_INFO); + } + touch(PATH_FLAG_INVOCATION); + release_lock(PATH_LOCK_DIR); + return 0; + } + + // Derivation path + parse_previous_log(&info); + if (info.source[0] == '\0') { + // Need to derive + strncpy(info.timestamp, current_utc(), sizeof(info.timestamp)-1); + detect_kernel_panic(&ctx, &panic); + if (panic.panic_detected) { + strcpy(info.reason, "KERNEL_PANIC"); + strcpy(info.source, "Kernel"); + snprintf(info.custom_reason, sizeof(info.custom_reason), + "Hardware Register - KERNEL_PANIC"); + strcpy(info.other_reason, "Reboot due to Kernel Panic captured by Oops Dump"); + } else { + get_hardware_reason(&ctx, &hw); + if (hw.has_hw_reason) { + map_hw_reason_to_info(&hw, &info); // sets reason/source/custom/other + } else { + // Fallback + strcpy(info.reason, "HARD_POWER"); + strcpy(info.source, "Hard Power Reset"); + strcpy(info.custom_reason, "Hardware Register - NULL"); + strcpy(info.other_reason, "No information found"); + } + } + } else { + // With source populated, reason classification awaits + // timestamp possibly from log else fill now + if (info.timestamp[0] == '\0') + strncpy(info.timestamp, current_utc(), sizeof(info.timestamp)-1); + } + + // Firmware failure override (non-TV) + if (!ctx.is_tv_profile) { + check_firmware_failure(&ctx, &fw); + } + + classify_reason(&info, &ctx, &hw, &panic, &fw); + + write_json_reboot_info(PATH_PREV_REBOOT_INFO, &info); + + if (is_hardpower_update_required(info.reason, PATH_HARDPOWER_INFO)) { + write_json_hardpower(PATH_HARDPOWER_INFO, info.timestamp); + } + + append_kernel_reason_if_needed(&ctx, &info); + + if (file_exists(PATH_KEYPRESS_INFO)) { + copy_file(PATH_KEYPRESS_INFO, PATH_PREV_KEYPRESS_INFO); + } + + append_parodus_previous_info(&info); + + touch(PATH_FLAG_INVOCATION); + + release_lock(PATH_LOCK_DIR); + return 0; +} +``` + +--- + +## 7. Detailed Logic Functions + +### `detect_kernel_panic` +- For BRCM: scan `/opt/logs/messages.txt` for `PREVIOUS_KERNEL_OOPS_DUMP` then also look for `Kernel Oops` or `Kernel Panic`. +- For Realtek/TV: scan `/sys/fs/pstore/console-ramoops-0` for any signature in `PANIC_SIGNATURES`. +- If panic found: `panic_detected=1`, store matched signature. + +### `get_hardware_reason` +- BRCM: read entire file, uppercase content; split by comma. First token primary. +- Realtek: find substring `wakeupreason=` in `/proc/cmdline`; extract next token; uppercase. +- Amlogic: read integer value (0–15), map using switch. +- Fallback: `has_hw_reason=0`. + +### `classify_reason` +Order of precedence: +1. If `fw->firmware_failure == 1` → `info.reason="FIRMWARE_FAILURE"`. +2. Else if `panic.panic_detected` → already set. +3. Else if source present: + - membership tests (APP → APP_TRIGGERED; OPS → OPS_TRIGGERED; MAINT → MAINTENANCE_REBOOT). + - If customReason equals `MAINTENANCE_REBOOT` override to that category. +4. Else if hardware path set earlier (e.g., POWER_ON_RESET, WATCHDOG, etc.) keep mapping. +5. Else fallback already assigned. + +### `write_json_reboot_info` +Atomic write: +``` +temp = "/opt/secure/reboot/.previousreboot.info.tmp" +open temp (O_CREAT|O_WRONLY|O_TRUNC, 0600) +fprintf: +{ +"timestamp":"%s", +"source":"%s", +"reason":"%s", +"customReason":"%s", +"otherReason":"%s" +} +fsync, close +rename(temp, PATH_PREV_REBOOT_INFO) +``` + +### `write_json_hardpower` +Similar atomic pattern. + +--- + +## 8. Error Handling + +| Function | Errors | Recovery | +|----------|--------|----------| +| `acquire_lock` | Directory exists → EBADF? | Retry up to timeout; log and exit if failure. | +| `read_small_file` | open/read failures | Return RB_ERR_IO; caller sets fallback reason. | +| `parse_previous_log` | Missing file | Leaves fields empty; triggers derivation path. | +| `detect_kernel_panic` | File missing | Return `panic_detected=0`. | +| `get_hardware_reason` | Parse errors | `has_hw_reason=0`; fallback. | +| `write_json_*` | Write/rename fail | Log error, but continue; system may lack reason next boot. | + +--- + +## 9. Memory Management +- All buffers static stack allocations. +- Avoid `malloc`; maximum concatenations guarded by `snprintf`. +- File reading uses chunk reads (`fgets`) with fixed-size line buffer (e.g., 512 bytes). + +--- + +## 10. Performance Considerations +- Linear scans only. +- Avoid scanning entire large logs: stop reading after required fields found. +- Panic detection stops after first signature match. + +--- + +## 11. Security Considerations +- Validate numeric reset code range (Amlogic). +- Sanitize extracted strings (strip non-printable). +- Use restrictive permissions (0600) for created files. +- Prevent path injection by hardcoding paths (no variable path building from untrusted input). + +--- + +## 12. Testing Matrix (Representative) + +| Case | Setup | Expected Result | +|------|-------|-----------------| +| New reboot.info present | Create file | Moved & classification skipped. | +| No files, BRCM register present | Provide register text | Reason derived from first token. | +| Realtek wakeup | cmdline with wakeupreason | Reason lowercased appended to kernel log. | +| Kernel panic pstore | Provide panic signature | reason=KERNEL_PANIC. | +| Firmware failure max reboot | Provide sentinel string | reason=FIRMWARE_FAILURE override. | +| Missing register | Delete file | reason=HARD_POWER fallback. | +| PLATCO first invocation with no flags | Remove flags & invocation flag | Continues execution. | + +--- + +## 13. Edge Case Handling Summary + +| Edge | Handling | +|------|----------| +| Multiple tokens BRCM register | Store first as `reason`, all tokens in `custom_reason`. | +| Empty initiator + no panic + no hardware | Fallback to HARD_POWER/UNKNOWN. | +| JSON partial write failure | Log; may cause absence next run but does not crash. | +| Oversized lines | Truncated read; still attempt parse; fallback if prefix missing. | + +--- + +## 14. Logging Macros + +```c +#ifdef USE_SYSLOG +#define LOGI(fmt, ...) syslog(LOG_INFO, "[reboot] " fmt, ##__VA_ARGS__) +#define LOGE(fmt, ...) syslog(LOG_ERR, "[reboot] " fmt, ##__VA_ARGS__) +#else +#define LOGI(fmt, ...) fprintf(stderr, "[INFO][reboot] " fmt "\n", ##__VA_ARGS__) +#define LOGE(fmt, ...) fprintf(stderr, "[ERR ][reboot] " fmt "\n", ##__VA_ARGS__) +#endif +``` + +--- + +## 15. Telemetry Integration +Weak symbol pattern: + +```c +__attribute__((weak)) +void telemetry_count(const char* key) { + (void)key; + // No-op if telemetry library not linked. +} +``` + +--- + +## 16. Known Enhancements vs Script +- Correct logical condition for firmware failure gating. +- Atomic JSON writes prevent partial file states. +- Unified classification precedence logic documented. + +--- + +## 17. Example JSON Output + +``` +{ +"timestamp":"Mon Nov 17 07:20:58 UTC 2025", +"source":"Hard Power Reset", +"reason":"HARD_POWER", +"customReason":"Hardware Register - NULL", +"otherReason":"No information found" +} +``` + +--- + +## 18. Fallback Initialization +On startup with no data: +``` +strcpy(info.timestamp, current_utc()); +strcpy(info.source, "Hard Power Reset"); +strcpy(info.reason, "HARD_POWER"); +strcpy(info.custom_reason, "Hardware Register - NULL"); +strcpy(info.other_reason, "No information found"); +``` + +--- + +## 19. Sequence Integrity +Ensures lock released on all exit paths: +- Use `goto cleanup;` pattern or `atexit(release_lock_wrapper)`. + +--- + +## 20. Future Extension Points +- JSON schema version field. +- Additional classification arrays loaded from config file. +- Telemetry structured events instead of simple counters. + +*End of LLD.* diff --git a/docs/reboot-reason-fetcher/architecture.md b/docs/reboot-reason-fetcher/architecture.md new file mode 100644 index 0000000..c6e0691 --- /dev/null +++ b/docs/reboot-reason-fetcher/architecture.md @@ -0,0 +1,178 @@ +# reboot-manager Architecture + +## 1. Overview + +This repository implements the `update-prev-reboot-info` binary, which derives and persists previous reboot context for the device. + +Primary responsibilities: +- Enforce single-instance execution +- Gate execution on runtime flags +- Read reboot context from multiple sources (new file, legacy log, hardware registers, panic traces) +- Classify reboot reason into normalized categories +- Persist results to secure state files and Parodus log + +Build output (from [src/Makefile.am](src/Makefile.am)): +- `update-prev-reboot-info` (installed as a regular binary via `bin_PROGRAMS`) + +--- + +## 2. High-Level Runtime Flow + +Entry point: [src/main.c](src/main.c#L61) + +1. Initialize logger and optional telemetry. +2. Acquire process lock (`LOCK_DIR`) to prevent parallel execution. +3. Parse environment/device context (`SOC`, `DEVICE_TYPE`, support flags). +4. Check whether update should run (`STT` + reboot-info flags). +5. Ensure reboot state directory exists (`/opt/secure/reboot`). +6. If `/opt/secure/reboot/reboot.info` exists: + - Move to previous reboot file and update Parodus handoff file. +7. Else derive reboot info from: + - legacy reboot log (`/opt/logs/rebootInfo.log`) + - kernel panic detection + - firmware-failure checks + - hardware reboot reason readers + - classifier normalization +8. Persist normalized reboot info JSON and side files. +9. Copy keypress info and set invocation marker. +10. Release lock and exit. + +--- + +## 3. Module Breakdown + +## 3.1 Orchestration (`main`) +- File: [src/main.c](src/main.c) +- Owns end-to-end control flow, cleanup path, and final persistence decisions. + +Key calls: +- `acquire_lock` / `release_lock` +- `parse_device_properties` +- `update_reboot_info` +- `parse_legacy_log` +- `detect_kernel_panic` +- `check_firmware_failure` +- `get_hardware_reason` +- `classify_reboot_reason` +- `write_reboot_info` / `write_hardpower` +- `update_parodus_log` / `handle_parodus_reboot_file` +- `copy_keypress_info` + +## 3.2 Platform HAL routing +- File: [src/platform_hal.c](src/platform_hal.c) +- Selects platform-specific readers by `SOC` and supports fallback probing. + +Supported sources: +- BRCM: `/proc/brcm/previous_reboot_reason` +- RTK/REALTEK: `/proc/cmdline` (`wakeupreason=`) +- AMLOGIC: `/sys/devices/platform/aml_pm/reset_reason` +- MTK: `/sys/mtk_pm/boot_reason` + +## 3.3 Input parsing and environment +- File: [src/log_parser.c](src/log_parser.c) +- Parses device properties and legacy reboot log fields. +- Contains readers for raw hardware reboot reasons. +- Implements flag-gating policy (`update_reboot_info`). + +## 3.4 Classification engine +- File: [src/reboot_reason_classify.c](src/reboot_reason_classify.c) +- Detects kernel panic signatures (including pstore path). +- Detects firmware-failure conditions (max reboot / ECM crash). +- Maps raw/legacy/hardware evidence into normalized reboot categories. +- Maintains app/ops/maintenance trigger lists. + +## 3.5 Persistence and locking +- File: [src/json.c](src/json.c) +- Lock lifecycle (`flock`) for single-instance safety. +- Writes reboot JSON and hard-power timestamp JSON. + +## 3.6 Parodus and keypress integration +- File: [src/parodus_log_update.c](src/parodus_log_update.c) +- Updates Parodus log with `PreviousRebootInfo` record. +- Handles `parodusreboot.info` handoff to previous-state file. +- Copies `keypress.info` to `previouskeypress.info`. + +--- + +## 4. Data Model + +Defined in [include/update-reboot-info.h](include/update-reboot-info.h): + +- `RebootInfo` + - `timestamp`, `source`, `reason`, `customReason`, `otherReason` +- `EnvContext` + - `soc`, `buildType`, `device_type`, plus platform support flags +- `HardwareReason` + - `rawReason`, `mappedReason` +- `PanicInfo` + - detected state + panic signature details +- `FirmwareFailure` + - detected state + subtype flags and initiator/details + +--- + +## 5. Persistent Files and Side Effects + +Primary files: +- `/opt/secure/reboot/reboot.info` +- `/opt/secure/reboot/previousreboot.info` +- `/opt/secure/reboot/hardpower.info` +- `/opt/secure/reboot/parodusreboot.info` +- `/opt/secure/reboot/previousparodusreboot.info` +- `/opt/secure/reboot/keypress.info` +- `/opt/secure/reboot/previouskeypress.info` +- `/opt/logs/rebootInfo.log` +- `/opt/logs/parodus.log` + +Control flags: +- `/tmp/stt_received` +- `/tmp/rebootInfo_Updated` +- `/tmp/Update_rebootInfo_invoked` + +--- + +## 6. Classification Priority (Conceptual) + +In practical terms, classification resolves with this precedence: +1. Firmware-failure evidence (if detected) +2. Existing custom reason mapping (app/ops/maintenance) +3. Kernel panic evidence +4. Hardware reboot reason mapping +5. Fallback unknown/software defaults + +This keeps reboot reason deterministic while preserving platform-specific signals. + +--- + +## 7. Error Handling and Reliability + +- Centralized return codes in [include/update-reboot-info.h](include/update-reboot-info.h#L60-L68) +- Defensive file existence/open checks in parsing and readers +- Guaranteed lock release through cleanup path in [src/main.c](src/main.c) +- Non-fatal behavior for optional inputs (e.g., missing keypress info) + +--- + +## 8. Extensibility Notes + +To add a new platform/SOC: +1. Add new reader function in parser/HAL modules +2. Add route in `get_hardware_reason` +3. Extend mapping rules in classifier if needed +4. Add unit tests in `unittest/` + +To add new classification trigger: +1. Update trigger arrays / mapping logic in `reboot_reason_classify.c` +2. Validate ordering with firmware/panic/hardware precedence +3. Add/adjust tests + +--- + +## 9. Build and Test Integration + +- Top-level build enters `src/` via [Makefile.am](Makefile.am). +- Binary target is declared in [src/Makefile.am](src/Makefile.am). +- Unit tests are under `unittest/`. +- Functional tests are under `test/`. + +This architecture separates orchestration, detection, classification, and persistence so behavior can be changed with minimal coupling. diff --git a/docs/reboot-reason-fetcher/diagrams/flowchart.md b/docs/reboot-reason-fetcher/diagrams/flowchart.md new file mode 100644 index 0000000..5266dd5 --- /dev/null +++ b/docs/reboot-reason-fetcher/diagrams/flowchart.md @@ -0,0 +1,144 @@ +# Flowcharts: Reboot Reason Updater + +## 1. Detailed Mermaid Flowchart + +```mermaid +flowchart TD + A[Start] --> B[Acquire Lock /tmp/rebootInfo.lock] + B --> C{Device = PLATCO or LLAMA?} + C -->|Yes| D{Flag /tmp/Update_rebootInfo_invoked exists?} + C -->|No| E[Check STT + rebootInfo flags] + D -->|Yes| E + D -->|No| F[Bypass flags for first run] + E -->|Missing flags| G[Release Lock & Exit] + E -->|Flags present| H[Ensure /opt/secure/reboot dir] + + F --> H + G --> Z[End] + + H --> I{RDK_PROFILE == TV?} + I -->|Yes| J{messages.txt has PreviousRebootReason?} + J -->|No| K[Kernel Panic Check (pstore/messages)] + J -->|Yes| L[Skip Kernel Panic Annotation] + K -->|Panic| M[Append 'PreviousRebootReason: kernel_panic!' to messages.txt] + K -->|No| N[Run get-reboot-reason.sh if present] + M --> L + N --> L + + L --> O{File reboot.info exists?} + O -->|Yes| P[Move reboot.info -> previousreboot.info] + P --> Q{File parodusreboot.info exists?} + Q -->|Yes| R[Append to parodus.log; move to previousparodusreboot.info] + Q -->|No| S[Proceed] + R --> S + S --> T[Handle keypress.info if exists] + T --> U[Create invocation flag if missing] + U --> V[Release Lock & End] + + O -->|No| W[Initialize empty fields] + W --> X{rebootInfo.log exists?} + X -->|Yes| Y[Parse PreviousRebootInitiatedBy, Time, Custom, Other] + X -->|No| AA[Fields remain empty] + Y --> AB[Check if InitiatedBy empty] + AA --> AB + AB -->|Empty| AC[Set rebootTime = current UTC] + AC --> AD[Kernel Panic Check] + AD -->|Panic| AE[reason=KERNEL_PANIC; source=Kernel; customReason=HW Register - KERNEL_PANIC; other=panic text] + AD -->|No| AF{SOC / Device Type} + AF -->|AMLOGIC| AG[Read reset_reason; map code] + AF -->|BRCM| AH[Read /proc/brcm/previous_reboot_reason] + AF -->|RTK/REALTEK/TV| AI[Parse wakeupreason from /proc/cmdline] + AF -->|Else| AJ[Fallback HARD_POWER NULL] + AG --> AK[customReason="Hardware Register - "; reason=] + AH --> AL[Parse tokens -> reason + customReason] + AI --> AM[Extract wakeupreason -> reason] + AJ --> AN[reason=HARD_POWER; customReason=HW Register - NULL] + AK --> AO + AL --> AO + AM --> AO + AN --> AO + + AO --> AP[Classification via APP/OPS/MAINT arrays -> reason category if initiator present] + AP --> AQ{Firmware Failure Override?} + AQ -->|Yes| AR[reason=FIRMWARE_FAILURE] + AQ -->|No| AS[Keep reason] + AR --> AT[Write previousreboot.info JSON] + AS --> AT + AT --> AU{reason is HARD_POWER/POWER_ON_RESET/UNKNOWN_RESET or first hardpower file missing?} + AU -->|Yes| AV[Write/Update hardpower.info timestamp] + AU -->|No| AW[Skip hardpower update] + AV --> AW + AW --> AX{SOC RTK/REALTEK?} + AX -->|Yes| AY[Append PreviousRebootReason (lowercase) to messages.txt] + AX -->|No| AZ[Skip] + AY --> BA[Handle keypress.info] + AZ --> BA + BA --> BB[Create invocation flag if missing] + BB --> BC[Log to parodus.log (previous reboot info)] + BC --> BD[Release Lock] + BD --> Z[End] +``` + +## 2. Simplified Text-Based Flow (ASCII) + +``` +START + acquire lock + if (device is PLATCO/LLAMA) { + if (invocation flag exists) check STT+reboot flags else bypass + } else check STT+reboot flags + if (flags missing) release lock & EXIT + + ensure /opt/secure/reboot directory + + if (RDK_PROFILE == TV && kernel log lacks PreviousRebootReason) { + if (kernel panic) append kernel_panic reason + else run get-reboot-reason.sh if present + } + + if (reboot.info exists) { + move to previousreboot.info + if (parodusreboot.info exists) log & move + copy keypress info + create invocation flag + release lock & END + } else { + parse rebootInfo.log for prior fields + if (initiator empty) { + if (kernel panic) set panic reason + else { + switch (platform) { + AMLOGIC -> map numeric reset code + BRCM -> parse previous_reboot_reason tokens + REALTEK/TV -> parse wakeupreason + default -> HARD_POWER fallback + } + } + } else { + classify reason via membership arrays + } + + firmware failure override check (non-TV) + + write previousreboot.info JSON + update hardpower.info if applicable + append lowercased PreviousRebootReason for Realtek/RTK + copy keypress info + create invocation flag + parodus log update + release lock & END + } +END +``` + +## 3. Annotations / Notes +- Firmware failure condition in original script uses `||` instead of `&&`, causing override check to always run. Migration fixes this. +- Hardware mapping is platform-specific; fallback ensures non-crashing behavior. +- Sequence between firmware failure and classification: firmware failure takes precedence. + +## 4. Linked Flow Concepts +- Panic detection sub-flow is reusable across platforms. +- Hardware reason resolution sub-flow branches by SOC value. +- JSON persistence is centralized and invoked post-classification. + +*End of Flowcharts Document.* diff --git a/docs/reboot-reason-fetcher/diagrams/sequence-diagram.md b/docs/reboot-reason-fetcher/diagrams/sequence-diagram.md new file mode 100644 index 0000000..7f238db --- /dev/null +++ b/docs/reboot-reason-fetcher/diagrams/sequence-diagram.md @@ -0,0 +1,149 @@ +# Sequence Diagrams: Reboot Reason Updater + +## 1. Mermaid Sequence Diagram + +```mermaid +sequenceDiagram + autonumber + participant S as Script(main) + participant L as Lock Manager + participant F as Filesystem + participant E as Env Loader + participant K as Kernel Logs + participant P as Pstore + participant HW as Hardware Registers + participant CL as Classification Module + participant FF as Firmware Failure Checker + participant J as JSON Writer + participant T as Telemetry + participant PA as Parodus Logger + participant KP as Keypress Handler + + S->>L: acquire_lock("/tmp/rebootInfo.lock") + L-->>S: lock_acquired / error + + S->>E: load_device_properties("/etc/device.properties") + E-->>S: DeviceContext + + S->>F: check flags (/tmp/stt_received, /tmp/rebootInfo_Updated) + F-->>S: flags status + + alt Flags missing (except bypass) + S->>L: release_lock() + S-->>S: exit + end + + S->>F: ensure_directory("/opt/secure/reboot") + + S->>F: stat("/opt/secure/reboot/reboot.info") + F-->>S: exists? yes/no + + opt New reboot.info exists + S->>F: move reboot.info -> previousreboot.info + S->>F: stat(parodusreboot.info) + alt parodusreboot.info exists + S->>F: read contents + S->>PA: append to parodus.log + S->>F: move parodusreboot.info -> previousparodusreboot.info + end + S->>KP: copy keypress.info if present + S->>F: touch invocation flag + S->>L: release_lock() + S-->>S: exit + end + + S->>F: open("/opt/logs/rebootInfo.log") + F-->>S: parsed fields (may be empty) + + alt Initiator empty + S->>K: read messages.txt (TV check) + S->>P: examine pstore signatures + P-->>S: panic? true/false + alt Panic + S-->>S: set reason=KERNEL_PANIC + else No panic + S->>HW: read platform-specific register/cmdline + HW-->>S: raw reason tokens + S->>CL: map hardware to reason/custom + CL-->>S: classification results + end + else Initiator present + S->>CL: classify via APP/OPS/MAINT arrays + CL-->>S: reason category + end + + S->>FF: check firmware failure conditions + FF-->>S: override flag + alt override true + S-->>S: reason=FIRMWARE_FAILURE + S->>T: telemetry_count("SYST_ERR_10Times_reboot") (maybe) + end + + S->>J: write previousreboot.info JSON + J-->>S: success/fail + + alt reason requires hardpower.info update + S->>J: write/update hardpower.info + end + + S->>K: append PreviousRebootReason (conditional lowercasing) + S->>KP: copy keypress.info if exists + S->>PA: append Parodus summary + S->>F: touch invocation flag + S->>L: release_lock() + S-->>S: end +``` + +## 2. Simplified Text-Based Sequence + +``` +Script -> LockManager: acquire +LockManager -> Script: success + +Script -> Env: load device properties +Env -> Script: DeviceContext + +Script -> FS: verify gating flags +FS -> Script: status +[If missing and not bypass] -> release lock, exit + +Script -> FS: ensure reboot directory +Script -> FS: check presence of reboot.info +[If present]: + move file -> previousreboot.info + handle parodusreboot.info if exists + keypress copy + create invocation flag + release lock + exit + +Else: + Script -> FS: parse rebootInfo.log + If initiator empty: + Script -> KernelLogs/Pstore: panic scan + If panic -> set panic reason + Else -> Script -> HW: read register/cmdline/reset code + HW -> Script: tokens + Script -> Classification: hardware mapping + Else: + Script -> Classification: membership mapping + + Script -> FirmwareFailureChecker: analyze logs + If override -> reason = FIRMWARE_FAILURE + + Script -> JSONWriter: persist previousreboot.info + If hardpower required -> JSONWriter: update hardpower.info + Script -> KernelLogs: append previous reason (platform conditional) + Script -> Keypress: copy keypress file + Script -> ParodusLogger: append summary + Script -> FS: touch invocation flag + Script -> LockManager: release +END +``` + +## 3. Notes +- Telemetry events are conditional; design must allow them to be no-ops when library absent. +- Hardware interaction isolated to platform modules for maintainability. +- Sequence ensures early exit conditions release lock to prevent deadlock. + +*End of Sequence Diagrams.* diff --git a/rebootnow/include/rbus_interface.h b/reboot-helper/include/rbus_interface.h similarity index 100% rename from rebootnow/include/rbus_interface.h rename to reboot-helper/include/rbus_interface.h diff --git a/rebootnow/include/rebootnow.h b/reboot-helper/include/reboot.h similarity index 100% rename from rebootnow/include/rebootnow.h rename to reboot-helper/include/reboot.h diff --git a/rebootnow/src/Makefile.am b/reboot-helper/src/Makefile.am similarity index 67% rename from rebootnow/src/Makefile.am rename to reboot-helper/src/Makefile.am index ccd8455..88e3e84 100644 --- a/rebootnow/src/Makefile.am +++ b/reboot-helper/src/Makefile.am @@ -21,13 +21,13 @@ AUTOMAKE_OPTIONS = foreign subdir-objects reboot_CFLAGS += -fPIC -pthread -AM_CPPFLAGS = -I$(top_srcdir)/rebootnow/include -AM_CFLAGS = -std=c99 -Wall -Wextra -Werror -O2 -I./rebootnow/include -I$(srcdir)/rebootmanager-cpc/include -DRDK_LOGGER_ENABLED -fPIC -pthread +AM_CPPFLAGS = -I$(top_srcdir)/reboot-helper/include +AM_CFLAGS = -std=c99 -Wall -Wextra -Werror -O2 -I./reboot-helper/include -DRDK_LOGGER_ENABLED -fPIC -pthread # Library target lib_LTLIBRARIES = librebootnow.la -librebootnow_la_SOURCES = \ - system_cleanup.c \ +librebootnow_la_LDFLAGS = -shared +librebootnow_la_SOURCES = system_cleanup.c \ cyclic_reboot.c \ utils.c \ rbus_interface.c @@ -36,20 +36,11 @@ librebootnow_la_LIBADD = -lfwutils -lrdkloggers -lrbus -lsecure_wrapper # CLI wrapper binary bin_PROGRAMS = rebootnow -rebootnow_SOURCES = main.c +rebootnow_SOURCES = reboot_main.c rebootnow_LDADD = librebootnow.la AM_LDFLAGS = -lrdkloggers -lfwutils -lrbus -lsecure_wrapper -if USE_CPC -bin_PROGRAMS += update-prev-reboot-info -update_prev_reboot_info_SOURCES = \ - $(srcdir)/rebootmanager-cpc/src/main.c \ - $(srcdir)/rebootmanager-cpc/src/platform_hal.c \ - $(srcdir)/rebootmanager-cpc/src/reboot_reason_classify.c \ - $(srcdir)/rebootmanager-cpc/src/log_parser.c \ - $(srcdir)/rebootmanager-cpc/src/json.c \ - $(srcdir)/rebootmanager-cpc/src/parodus_log_update.c -endif + if IS_TELEMETRY2_ENABLED AM_CFLAGS += $(T2_EVENT_FLAG) AM_LDFLAGS += -ltelemetry_msgsender -lt2utils diff --git a/rebootnow/src/cyclic_reboot.c b/reboot-helper/src/cyclic_reboot.c similarity index 99% rename from rebootnow/src/cyclic_reboot.c rename to reboot-helper/src/cyclic_reboot.c index c39bcc5..6851108 100644 --- a/rebootnow/src/cyclic_reboot.c +++ b/reboot-helper/src/cyclic_reboot.c @@ -28,7 +28,7 @@ #include #include #include -#include "rebootnow.h" +#include "reboot.h" #include "secure_wrapper.h" #include "rbus_interface.h" #include "rdk_logger.h" diff --git a/rebootnow/src/rbus_interface.c b/reboot-helper/src/rbus_interface.c similarity index 100% rename from rebootnow/src/rbus_interface.c rename to reboot-helper/src/rbus_interface.c diff --git a/rebootnow/src/main.c b/reboot-helper/src/reboot_main.c similarity index 99% rename from rebootnow/src/main.c rename to reboot-helper/src/reboot_main.c index faf9dab..ee5055c 100644 --- a/rebootnow/src/main.c +++ b/reboot-helper/src/reboot_main.c @@ -29,7 +29,7 @@ #include #include #include -#include "rebootnow.h" +#include "reboot.h" #include "secure_wrapper.h" #include "rbus_interface.h" #include "rdk_logger.h" diff --git a/rebootnow/src/system_cleanup.c b/reboot-helper/src/system_cleanup.c similarity index 99% rename from rebootnow/src/system_cleanup.c rename to reboot-helper/src/system_cleanup.c index bb3688b..634f5d2 100644 --- a/rebootnow/src/system_cleanup.c +++ b/reboot-helper/src/system_cleanup.c @@ -27,7 +27,7 @@ #include #include #include -#include "rebootnow.h" +#include "reboot.h" #include "secure_wrapper.h" #include "rbus_interface.h" #include "rdk_logger.h" diff --git a/rebootnow/src/utils.c b/reboot-helper/src/utils.c similarity index 98% rename from rebootnow/src/utils.c rename to reboot-helper/src/utils.c index 1b40c91..3673b4b 100644 --- a/rebootnow/src/utils.c +++ b/reboot-helper/src/utils.c @@ -24,7 +24,7 @@ #include #include #include "rdk_logger.h" -#include "rebootnow.h" +#include "reboot.h" void timestamp_update(char *buf, size_t sz) { diff --git a/reboot-reason-fetcher/include/update-reboot-info.h b/reboot-reason-fetcher/include/update-reboot-info.h new file mode 100644 index 0000000..de26809 --- /dev/null +++ b/reboot-reason-fetcher/include/update-reboot-info.h @@ -0,0 +1,152 @@ +#ifndef UPDATE_REBOOT_INFO_H +#define UPDATE_REBOOT_INFO_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* RDK Common Utilities */ +#ifdef RDK_LOGGER_ENABLED +#include "rdk_debug.h" +#endif + +#ifdef T2_EVENT_ENABLED +#include +#endif + +/*=========================================================================== + * CONSTANTS AND MACROS + *===========================================================================*/ + +/* Log module for reboot reason processing */ +#define LOG_MODULE "LOG.RDK.REBOOT" + +/* Path constants */ +#define DEVICE_PROPERTIES_PATH "/etc/device.properties" +#define REBOOT_INFO_DIR "/opt/secure/reboot" +#define REBOOT_INFO_FILE "/opt/secure/reboot/reboot.info" +#define PREVIOUS_REBOOT_INFO_FILE "/opt/secure/reboot/previousreboot.info" +#define PREVIOUS_HARD_REBOOT_INFO_FILE "/opt/secure/reboot/hardpower.info" +#define PARODUS_REBOOT_INFO_FILE "/opt/secure/reboot/parodusreboot.info" +#define PREVIOUS_PARODUSREBOOT_INFO_FILE "/opt/secure/reboot/previousparodusreboot.info" +#define KEYPRESS_INFO_FILE "/opt/secure/reboot/keypress.info" +#define PREVIOUS_KEYPRESS_INFO_FILE "/opt/secure/reboot/previouskeypress.info" +#define PARODUS_LOG "/opt/logs/parodus.log" +#define REBOOT_INFO_LOG_FILE "/opt/logs/rebootInfo.log" + +#define BRCM_REBOOT_FILE "/proc/brcm/previous_reboot_reason" +#define RTK_REBOOT_FILE "/proc/cmdline" +#define AMLOGIC_SYSFS_FILE "/sys/devices/platform/aml_pm/reset_reason" +#define MTK_SYSFS_FILE "/sys/mtk_pm/boot_reason" +#define CMDLINE_PATH "/proc/cmdline" +#define WAKEUP_REASON_KEY "wakeupreason=" +#define AMLOGIC_REBOOT_REASON_PATH "/sys/class/aml_reboot/reboot_reason" +#define STT_FLAG "/tmp/stt_received" +#define UPDATE_REBOOT_INFO_INVOKED_FLAG "/tmp/Update_rebootInfo_invoked" +#define PSTORE_DIR "/sys/fs/pstore" + +/* Lock directory */ +#define LOCK_DIR "/tmp/rebootInfo.lock" + +/* Buffer sizes */ +#define MAX_BUFFER_SIZE 512 +#define MAX_PATH_LENGTH 256 +#define MAX_REASON_LENGTH 128 +#define MAX_TIMESTAMP_LENGTH 64 + +/* Return codes */ +#define SUCCESS 0 +#define ERROR_GENERAL -1 +#define ERROR_LOCK_FAILED -2 +#define ERROR_FILE_NOT_FOUND -3 +#define ERROR_PARSE_FAILED -4 +#define FAILURE -5 + +/* Logging macros - use RDK_LOG when available */ +#ifdef RDK_LOGGER_ENABLED +extern int g_rdk_logger_enabled; +#endif //RDK_LOGGER_ENABLED + +/*=========================================================================== + * TYPE DEFINITIONS + *===========================================================================*/ + +/* Reboot reason structure */ +typedef struct { + char timestamp[MAX_TIMESTAMP_LENGTH]; + char source[MAX_REASON_LENGTH]; + char reason[MAX_REASON_LENGTH]; + char customReason[MAX_REASON_LENGTH]; + char otherReason[MAX_REASON_LENGTH]; +} RebootInfo; +/* Environment context structure */ +typedef struct { + char soc[64]; + char buildType[64]; + char device_type[64]; + bool platcoSupport; + bool llamaSupport; + bool rebootInfoSttSupport; +} EnvContext; + +/* Hardware reason structure */ +typedef struct { + char rawReason[MAX_REASON_LENGTH]; + char mappedReason[MAX_REASON_LENGTH]; +} HardwareReason; + +/* Panic detection structure */ +typedef struct { + bool detected; + char panicType[MAX_REASON_LENGTH]; + char details[MAX_BUFFER_SIZE]; +} PanicInfo; +/* Firmware failure structure */ +typedef struct { + bool detected; + bool maxRebootDetected; + bool ecmCrashDetected; + char details[MAX_REASON_LENGTH]; + char initiator[MAX_REASON_LENGTH]; +} FirmwareFailure; + +int acquire_lock(const char *lockDir); +int release_lock(const char *lockDir); +int parse_device_properties(EnvContext *ctx); +void free_env_context(EnvContext *ctx); +int get_hardware_reason(const EnvContext *ctx, HardwareReason *hwReason, RebootInfo *info); +int get_hardware_reason_brcm(const EnvContext *ctx, HardwareReason *hwReason); +int get_hardware_reason_rtk(const EnvContext *ctx, HardwareReason *hwReason); +int get_hardware_reason_amlogic(const EnvContext *ctx, HardwareReason *hwReason); +int get_hardware_reason_mtk(const EnvContext *ctx, HardwareReason *hwReason); +int read_brcm_previous_reboot_reason(HardwareReason *hw); +int read_rtk_wakeup_reason(HardwareReason *hw); +int read_amlogic_reset_reason(HardwareReason *hw, RebootInfo *info); +int read_mtk_reset_reason(HardwareReason *hw, RebootInfo *info); +int detect_kernel_panic(const EnvContext *ctx, PanicInfo *panicInfo); +int check_firmware_failure(const EnvContext *ctx, FirmwareFailure *fwFailure); +int classify_reboot_reason(RebootInfo *info, + const EnvContext *ctx, + const HardwareReason *hwReason, + const PanicInfo *panicInfo, + const FirmwareFailure *fwFailure); +bool is_app_triggered(const char *reason); +bool is_ops_triggered(const char *reason); +bool is_maintenance_triggered(const char *reason); +int write_reboot_info(const char *path, const RebootInfo *info); +int write_hardpower(const char *path, const char *timestamp); +int append_kernel_reason(const EnvContext *ctx, const RebootInfo *info); +int update_parodus_log(const RebootInfo *info); +int handle_parodus_reboot_file(const RebootInfo *info, const char *destPath); +int copy_keypress_info(const char *srcPath, const char *destPath); +void t2CountNotify(char *marker, int val); +void t2ValNotify(char *marker, char *val); +int parse_legacy_log(const char *logPath, RebootInfo *info); +int update_reboot_info(const EnvContext *ctx); +#endif /* UPDATE_REBOOT_INFO_H */ diff --git a/reboot-reason-fetcher/src/Makefile.am b/reboot-reason-fetcher/src/Makefile.am new file mode 100644 index 0000000..f502c79 --- /dev/null +++ b/reboot-reason-fetcher/src/Makefile.am @@ -0,0 +1,21 @@ +AUTOMAKE_OPTIONS = foreign subdir-objects + +reboot_CFLAGS += -fPIC -pthread +bin_PROGRAMS = update-prev-reboot-info + +AM_CPPFLAGS = -I$(top_srcdir)/reboot-reason-fetcher/include +AM_CFLAGS = -std=c99 -Wall -Wextra -Werror -O2 -I./reboot-reason-fetcher/include -DRDK_LOGGER_ENABLED +AM_LDFLAGS = -lfwutils -lrdkloggers + +update_prev_reboot_info_SOURCES = rebootreason_main.c \ + platform_hal.c \ + reboot_reason_classify.c \ + log_parser.c \ + json.c \ + parodus_log_update.c \ + $(NULL) + +if IS_TELEMETRY2_ENABLED + AM_CFLAGS += $(T2_EVENT_FLAG) + AM_LDFLAGS += -ltelemetry_msgsender -lt2utils +endif diff --git a/reboot-reason-fetcher/src/json.c b/reboot-reason-fetcher/src/json.c new file mode 100644 index 0000000..995dcdd --- /dev/null +++ b/reboot-reason-fetcher/src/json.c @@ -0,0 +1,104 @@ +#define _DEFAULT_SOURCE +#define _POSIX_C_SOURCE 200809L +#include "update-reboot-info.h" +#include "rdk_logger.h" +#include +#include +#include +#include +#include + +#define LOCK_RETRY_COUNT 10 +#define LOCK_RETRY_DELAY_US 100000 // 100ms +static int g_lock_fd = -1; + +int acquire_lock(const char *lockFile) +{ + if (!lockFile) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Lock file path is NULL \n"); + return ERROR_GENERAL; + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Attempting to acquire flock on: %s\n", lockFile); + if (g_lock_fd < 0) { + g_lock_fd = open(lockFile, O_CREAT | O_RDWR | O_CLOEXEC, 0644); + if (g_lock_fd < 0) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open lock file %s: %s\n", lockFile, strerror(errno)); + return ERROR_LOCK_FAILED; + } + } + // Blocking exclusive lock: wait until the lock is available + if (flock(g_lock_fd, LOCK_EX) == 0) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Lock acquired successfully: %s\n", lockFile); + return SUCCESS; + } + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to acquire lock on %s: %s\n", lockFile, strerror(errno)); + return ERROR_LOCK_FAILED; +} +int release_lock(const char *lockFile) +{ + if (!lockFile) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Lock file path is NULL\n"); + return ERROR_GENERAL; + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Releasing lock: %s\n", lockFile); + if (g_lock_fd >= 0) { + if (flock(g_lock_fd, LOCK_UN) != 0) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to unlock %s: %s\n", lockFile, strerror(errno)); + } + close(g_lock_fd); + g_lock_fd = -1; + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Lock released successfully: %s\n", lockFile); + return SUCCESS; +} +int write_reboot_info(const char *path, const RebootInfo *info) +{ + FILE *fp = NULL; + + if (!path || !info) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for write_reboot_info\n"); + return ERROR_GENERAL; + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Writing reboot info to file: %s\n", path); + fp = fopen(path, "w"); + if (!fp) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open file %s: %s\n", path, strerror(errno)); + return ERROR_GENERAL; + } + fprintf(fp, "{\n"); + fprintf(fp, "\"timestamp\":\"%s\",\n", info->timestamp); + fprintf(fp, "\"source\":\"%s\",\n", info->source); + fprintf(fp, "\"reason\":\"%s\",\n", info->reason); + fprintf(fp, "\"customReason\":\"%s\",\n", info->customReason); + fprintf(fp, "\"otherReason\":\"%s\"\n", info->otherReason); + fprintf(fp, "}\n"); + fflush(fp); + fclose(fp); + + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Reboot info written successfully to: %s\n", path); + return SUCCESS; +} +int write_hardpower(const char *path, const char *timestamp) +{ + FILE *fp = NULL; + + if (!path || !timestamp) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for write_hardpower \n"); + return ERROR_GENERAL; + } + + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Writing hardpower timestamp to file: %s\n", path); + fp = fopen(path, "w"); + if (!fp) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open file %s\n", path); + return ERROR_GENERAL; + } + fprintf(fp, "{\n"); + fprintf(fp, "\"lastHardPowerReset\":\"%s\"\n", timestamp); + fprintf(fp, "}\n"); + fflush(fp); + fclose(fp); + + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Hardpower timestamp written successfully to: %s\n", path); + return SUCCESS; +} diff --git a/reboot-reason-fetcher/src/log_parser.c b/reboot-reason-fetcher/src/log_parser.c new file mode 100644 index 0000000..13ec518 --- /dev/null +++ b/reboot-reason-fetcher/src/log_parser.c @@ -0,0 +1,481 @@ +#include "update-reboot-info.h" +#include "rdk_fwdl_utils.h" +#include "rdk_logger.h" +#include +#include +#include +#include + +int parse_device_properties(EnvContext *ctx) +{ + char buffer[MAX_REASON_LENGTH]; + if (!ctx) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Context pointer is NULL\n"); + return FAILURE; + } + memset(ctx, 0, sizeof(EnvContext)); + ctx->platcoSupport = false; + ctx->llamaSupport = false; + ctx->rebootInfoSttSupport = false; + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Parsing device properties using common_utilities\n"); + if (getDevicePropertyData("SOC", buffer, sizeof(buffer)) == UTILS_SUCCESS) { + size_t len = strlen(buffer); + if (len >= sizeof(ctx->soc)) { + len = sizeof(ctx->soc) - 1; + } + memcpy(ctx->soc, buffer, len); + ctx->soc[len] = '\0'; + } + if (getDevicePropertyData("BUILD_TYPE", buffer, sizeof(buffer)) == UTILS_SUCCESS) { + size_t len = strlen(buffer); + if (len >= sizeof(ctx->buildType)) { + len = sizeof(ctx->buildType) - 1; + } + memcpy(ctx->buildType, buffer, len); + ctx->buildType[len] = '\0'; + } + if (getDevicePropertyData("DEVICE_TYPE", buffer, sizeof(buffer)) == UTILS_SUCCESS) { + size_t len = strlen(buffer); + if (len >= sizeof(ctx->device_type)) { + len = sizeof(ctx->device_type) - 1; + } + memcpy(ctx->device_type, buffer, len); + ctx->device_type[len] = '\0'; + } + if (getDevicePropertyData("PLATCO_SUPPORT", buffer, sizeof(buffer)) == UTILS_SUCCESS) { + ctx->platcoSupport = (strcasecmp(buffer, "true") == 0); + } + if (getDevicePropertyData("LLAMA_SUPPORT", buffer, sizeof(buffer)) == UTILS_SUCCESS) { + ctx->llamaSupport = (strcasecmp(buffer, "true") == 0); + } + if (getDevicePropertyData("REBOOT_INFO_STT_SUPPORT", buffer, sizeof(buffer)) == UTILS_SUCCESS) { + ctx->rebootInfoSttSupport = (strcasecmp(buffer, "true") == 0); + } + if (ctx->soc[0] == '\0' || ctx->device_type[0] == '\0') { + FILE *dp = fopen("/etc/device.properties", "r"); + if (dp) { + char line[256]; + while (fgets(line, sizeof(line), dp)) { + char *eq = strchr(line, '='); + if (!eq) continue; + *eq = '\0'; + char *key = line; + char *val = eq + 1; + char *nl = strchr(val, '\n'); + if (nl) *nl = '\0'; + if (ctx->soc[0] == '\0' && strcmp(key, "SOC") == 0) { + size_t len = strlen(val); + if (len >= sizeof(ctx->soc)) { + len = sizeof(ctx->soc) - 1; + } + memcpy(ctx->soc, val, len); + ctx->soc[len] = '\0'; + } else if (ctx->device_type[0] == '\0' && (strcmp(key, "DEVICE_TYPE") == 0 || strcmp(key, "DEVICE_NAME") == 0)) { + size_t len = strlen(val); + if (len >= sizeof(ctx->device_type)) { + len = sizeof(ctx->device_type) - 1; + } + memcpy(ctx->device_type, val, len); + ctx->device_type[len] = '\0'; + } + } + fclose(dp); + } else { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Could not open /etc/device.properties\n"); + return FAILURE; + } + } + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Device properties parsed - SOC: %s, BuildType: %s, DeviceType: %s\n", ctx->soc, ctx->buildType, ctx->device_type); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Support flags - PLATCO: %d, LLAMA: %d, STT: %d\n", ctx->platcoSupport, ctx->llamaSupport, ctx->rebootInfoSttSupport); + return SUCCESS; +} +void free_env_context(EnvContext *ctx) +{ + if (ctx) { + memset(ctx, 0, sizeof(EnvContext)); + } +} + +int update_reboot_info(const EnvContext *ctx) +{ + const char *REBOOT_INFO_FLAG = "/tmp/rebootInfo_Updated"; + + if (!ctx) return 0; + if (access(STT_FLAG, F_OK) != 0 || access(REBOOT_INFO_FLAG, F_OK) != 0) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","STT or RebootInfo flag missing; skip update\n"); + return 0; + } + return 1; +} + +static void getVal(const char *line, const char *prefix, char *output, size_t output_size) +{ + const char *value = line + strlen(prefix); + while (*value && (*value == ' ' || *value == '\t')) { + value++; + } + strncpy(output, value, output_size - 1); + output[output_size - 1] = '\0'; + char *end = output + strlen(output) - 1; + while (end >= output && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) { + *end = '\0'; + end--; + } +} +int parse_legacy_log(const char *logPath, RebootInfo *info) +{ + FILE *fp = NULL; + char line[MAX_BUFFER_SIZE]; + int found_fields = 0; + if (!logPath || !info) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for parse_legacy_log\n"); + return FAILURE; + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Parsing legacy log: %s\n", logPath); + fp = fopen(logPath, "r"); + if (!fp) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open legacy log %s: %s\n", logPath, strerror(errno)); + return ERROR_FILE_NOT_FOUND; + } + while (fgets(line, sizeof(line), fp)) { + if (strstr(line, "PreviousRebootInitiatedBy:")) { + getVal(strstr(line, "PreviousRebootInitiatedBy:"), "PreviousRebootInitiatedBy:", info->source, sizeof(info->source)); + found_fields++; + } + else if (strstr(line, "PreviousRebootTime:")) { + getVal(strstr(line, "PreviousRebootTime:"), "PreviousRebootTime:", info->timestamp, sizeof(info->timestamp)); + found_fields++; + } + else if (strstr(line, "PreviousCustomReason:")) { + getVal(strstr(line, "PreviousCustomReason:"), "PreviousCustomReason:", info->customReason, sizeof(info->customReason)); + found_fields++; + } + else if (strstr(line, "PreviousOtherReason:")) { + getVal(strstr(line, "PreviousOtherReason:"), "PreviousOtherReason:", info->otherReason, sizeof(info->otherReason)); + found_fields++; + } + if (found_fields >= 4) { + break; + } + } + fclose(fp); + if (found_fields == 0) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","No reboot info fields found in legacy log\n"); + return FAILURE; + } + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Parsed legacy log - Found %d fields\n", found_fields); + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Timestamp: %s, Source: %s, Reason: %s\n", info->timestamp, info->source, info->reason); + return SUCCESS; +} +static int read_file_data(const char *path, char *buf, size_t buflen) +{ + FILE *fp = fopen(path, "r"); + if (!fp) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open file\n"); + return ERROR_GENERAL; + } + size_t n = fread(buf, 1, buflen - 1, fp); + fclose(fp); + buf[n] = '\0'; + return SUCCESS; +} +int read_brcm_previous_reboot_reason(HardwareReason *hw) +{ + char buf[MAX_BUFFER_SIZE]; + char upbuf[MAX_BUFFER_SIZE]; + if (!hw) return ERROR_GENERAL; + if (access(BRCM_REBOOT_FILE, R_OK) != 0) { + return FAILURE; + } + if (read_file_data(BRCM_REBOOT_FILE, buf, sizeof(buf)) != SUCCESS || buf[0] == '\0') { + return FAILURE; + } + size_t raw_len = strcspn(buf, "\n"); + if (raw_len >= sizeof(hw->rawReason)) { + raw_len = sizeof(hw->rawReason) - 1; + } + memcpy(hw->rawReason, buf, raw_len); + hw->rawReason[raw_len] = '\0'; + + size_t buflen = strlen(hw->rawReason); + if (buflen >= sizeof(upbuf)) buflen = sizeof(upbuf) - 1; + memcpy(upbuf, hw->rawReason, buflen); + upbuf[buflen] = '\0'; + for (char *p = upbuf; *p; ++p) { + *p = toupper((unsigned char)*p); + } + + const char *primary = upbuf; + size_t primary_len = strcspn(upbuf, ",\n"); + if (primary_len >= sizeof(hw->mappedReason)) { + primary_len = sizeof(hw->mappedReason) - 1; + } + memcpy(hw->mappedReason, primary, primary_len); + hw->mappedReason[primary_len] = '\0'; + return SUCCESS; +} +int read_rtk_wakeup_reason(HardwareReason *hw) +{ + char buf[MAX_BUFFER_SIZE]; + if (!hw) return ERROR_GENERAL; + if (access(RTK_REBOOT_FILE, R_OK) != 0) { + return FAILURE; + } + if (read_file_data(RTK_REBOOT_FILE, buf, sizeof(buf)) != SUCCESS) { + return FAILURE; + } + const char *key = "wakeupreason="; + char *pos = strstr(buf, key); + if (!pos) { + return FAILURE; + } + pos += strlen(key); + char reason[MAX_REASON_LENGTH]; + size_t i = 0; + while (pos[i] && !isspace((unsigned char)pos[i]) && i < sizeof(reason) - 1) { + reason[i] = pos[i]; + i++; + } + reason[i] = '\0'; + for (size_t j = 0; j < i; j++) { + reason[j] = toupper((unsigned char)reason[j]); + } + strncpy(hw->mappedReason, reason, sizeof(hw->mappedReason) - 1); + hw->mappedReason[sizeof(hw->mappedReason) - 1] = '\0'; + return SUCCESS; +} +int read_amlogic_reset_reason(HardwareReason *hw, RebootInfo *info) +{ + char buf[64]; + int resetVal = -1; + char saved_timestamp[MAX_TIMESTAMP_LENGTH]; + + if (!hw || !info) { + return FAILURE; + } + + strncpy(saved_timestamp, info->timestamp, sizeof(saved_timestamp) - 1); + saved_timestamp[sizeof(saved_timestamp) - 1] = '\0'; + + if (access(AMLOGIC_SYSFS_FILE, R_OK) != 0) { + return FAILURE; + } + if (read_file_data(AMLOGIC_SYSFS_FILE, buf, sizeof(buf)) != SUCCESS) { + return FAILURE; + } + resetVal = atoi(buf); + switch (resetVal) { + case 0: + strcpy(info->source, "POWER_ON_REBOOT"); + strcpy(info->reason, "POWER_ON_RESET"); + strcpy(info->customReason, "Hardware Register - COLD_BOOT"); + strcpy(info->otherReason, "Reboot due to hardware power cable unplug"); + strcpy(hw->mappedReason, "POWER_ON_RESET"); + break; + case 1: + strcpy(info->source, "SOFTWARE_REBOOT"); + strcpy(info->reason, "SOFTWARE_MASTER_RESET"); + strcpy(info->customReason, "Hardware Register - NORMAL_BOOT"); + strcpy(info->otherReason, "Reboot due to user triggered reboot command"); + strcpy(hw->mappedReason, "SOFTWARE_MASTER_RESET"); + break; + case 2: + strcpy(info->source, "FACTORY_RESET_REBOOT"); + strcpy(info->reason, "FACTORY_RESET"); + strcpy(info->customReason, "Hardware Register - FACTORY_RESET"); + strcpy(info->otherReason, "Reboot due to factory reset reboot"); + strcpy(hw->mappedReason, "FACTORY_RESET"); + break; + case 3: + strcpy(info->source, "UPGRADE_SYSTEM_REBOOT"); + strcpy(info->reason, "UPDATE_BOOT"); + strcpy(info->customReason, "Hardware Register - UPDATE_BOOT"); + strcpy(info->otherReason, "Reboot due to system upgrade reboot"); + strcpy(hw->mappedReason, "UPDATE_BOOT"); + break; + case 4: + strcpy(info->source, "FASTBOOT_REBOOT"); + strcpy(info->reason, "FAST_BOOT"); + strcpy(info->customReason, "Hardware Register - FAST_BOOT"); + strcpy(info->otherReason, "Reboot due to fast reboot"); + strcpy(hw->mappedReason, "FAST_BOOT"); + break; + case 5: + strcpy(info->source, "SUSPEND_REBOOT"); + strcpy(info->reason, "SUSPEND_BOOT"); + strcpy(info->customReason, "Hardware Register - SUSPEND_BOOT"); + strcpy(info->otherReason, "Reboot due to suspend reboot"); + strcpy(hw->mappedReason, "SUSPEND_BOOT"); + break; + case 6: + strcpy(info->source, "HIBERNATE_REBOOT"); + strcpy(info->reason, "HIBERNATE_BOOT"); + strcpy(info->customReason, "Hardware Register - HIBERNATE_BOOT"); + strcpy(info->otherReason, "Reboot due to hibernate reboot"); + strcpy(hw->mappedReason, "HIBERNATE_BOOT"); + break; + case 7: + strcpy(info->source, "BOOTLOADER_REBOOT"); + strcpy(info->reason, "FASTBOOT_BOOTLOADER"); + strcpy(info->customReason, "Hardware Register - FASTBOOT_BOOTLOADER"); + strcpy(info->otherReason, "Reboot due to fastboot bootloader reboot"); + strcpy(hw->mappedReason, "FASTBOOT_BOOTLOADER"); + break; + case 8: + strcpy(info->source, "SHUTDOWN_REBOOT"); + strcpy(info->reason, "SHUTDOWN_REBOOT"); + strcpy(info->customReason, "Hardware Register - SHUTDOWN_REBOOT"); + strcpy(info->otherReason, "Reboot due to shutdown"); + strcpy(hw->mappedReason, "SHUTDOWN_REBOOT"); + break; + case 9: + strcpy(info->source, "RPMPB"); + strcpy(info->reason, "RPMPB_REBOOT"); + strcpy(info->customReason, "Hardware Register - RPMPB_REBOOT"); + strcpy(info->otherReason, "Reboot due to RPMPB"); + strcpy(hw->mappedReason, "RPMPB_REBOOT"); + break; + case 10: + strcpy(info->source, "THERMAL"); + strcpy(info->reason, "THERMAL_REBOOT"); + strcpy(info->customReason, "Hardware Register - THERMAL_REBOOT"); + strcpy(info->otherReason, "Reboot due to thermal value"); + strcpy(hw->mappedReason, "THERMAL_REBOOT"); + break; + case 11: + strcpy(info->source, "CRASH_DUMP"); + strcpy(info->reason, "CRASH_REBOOT"); + strcpy(info->customReason, "Hardware Register - CRASH_REBOOT"); + strcpy(info->otherReason, "Reboot due to crash dump"); + strcpy(hw->mappedReason, "CRASH_REBOOT"); + break; + case 12: + strcpy(info->source, "KernelPanic"); + strcpy(info->reason, "KERNEL_PANIC"); + strcpy(info->customReason, "Hardware Register - KERNEL_PANIC"); + strcpy(info->otherReason, "Reboot due to oops dump caused panic"); + strcpy(hw->mappedReason, "KERNEL_PANIC"); + break; + case 13: + strcpy(info->source, "WATCH_DOG"); + strcpy(info->reason, "WATCHDOG_REBOOT"); + strcpy(info->customReason, "Hardware Register - WATCHDOG_REBOOT"); + strcpy(info->otherReason, "Reboot due to watch dog timer"); + strcpy(hw->mappedReason, "WATCHDOG_REBOOT"); + break; + case 14: + strcpy(info->source, "STR_AUTH_FAIL"); + strcpy(info->reason, "AMLOGIC_DDR_SHA2_REBOOT"); + strcpy(info->customReason, "Hardware Register - AMLOGIC_DDR_SHA2_REBOOT"); + strcpy(info->otherReason, "Reboot due to STR Authorization failure"); + strcpy(hw->mappedReason, "AMLOGIC_DDR_SHA2_REBOOT"); + break; + case 15: + strcpy(info->source, "FFV"); + strcpy(info->reason, "FFV_REBOOT"); + strcpy(info->customReason, "Hardware Register - FFV_REBOOT"); + strcpy(info->otherReason, "Reboot due to Reserved FFV"); + strcpy(hw->mappedReason, "FFV_REBOOT"); + break; + default: + strcpy(info->source, "HARD_POWER_RESET"); + strcpy(info->reason, "UNKNOWN_RESET"); + strcpy(info->customReason, "UNKNOWN"); + strcpy(info->otherReason, "Reboot due to unknown reason"); + strcpy(hw->mappedReason, "UNKNOWN_RESET"); + break; + } + + if (saved_timestamp[0] != '\0') { + strncpy(info->timestamp, saved_timestamp, sizeof(info->timestamp) - 1); + info->timestamp[sizeof(info->timestamp) - 1] = '\0'; + } + return SUCCESS; +} + +int read_mtk_reset_reason(HardwareReason *hw, RebootInfo *info) +{ + char buf[64]; + int resetVal = -1; + char saved_timestamp[MAX_TIMESTAMP_LENGTH]; + + if (!hw || !info) { + return ERROR_GENERAL; + } + + strncpy(saved_timestamp, info->timestamp, sizeof(saved_timestamp) - 1); + saved_timestamp[sizeof(saved_timestamp) - 1] = '\0'; + + if (access(MTK_SYSFS_FILE, R_OK) != 0) { + return FAILURE; + } + if (read_file_data(MTK_SYSFS_FILE, buf, sizeof(buf)) != SUCCESS) { + return FAILURE; + } + + // Parse hex value from MTK sysfs file + if (strncmp(buf, "0x", 2) == 0 || strncmp(buf, "0X", 2) == 0) { + resetVal = (int)strtol(buf, NULL, 16); + } else { + resetVal = (int)strtol(buf, NULL, 10); + } + + // Store raw reason as string representation + strncpy(hw->rawReason, buf, sizeof(hw->rawReason) - 1); + hw->rawReason[sizeof(hw->rawReason) - 1] = '\0'; + + // Remove newline if present + char *newline = strchr(hw->rawReason, '\n'); + if (newline) *newline = '\0'; + + switch (resetVal) { + case 0x00: + strcpy(info->source, "POWER_ON_REBOOT"); + strcpy(info->reason, "POWER_ON_RESET"); + strcpy(info->customReason, "COLD_BOOT"); + strcpy(info->otherReason, "Reboot due to hardware power cable unplug"); + strcpy(hw->mappedReason, "POWER_ON_RESET"); + break; + case 0xD1: + strcpy(info->source, "SOFTWARE_REBOOT"); + strcpy(info->reason, "SOFTWARE_MASTER_RESET"); + strcpy(info->customReason, "NORMAL_BOOT"); + strcpy(info->otherReason, "Reboot due to user triggered reboot command"); + strcpy(hw->mappedReason, "SOFTWARE_MASTER_RESET"); + break; + case 0xE4: + strcpy(info->source, "THERMAL"); + strcpy(info->reason, "THERMAL_REBOOT"); + strcpy(info->customReason, "THERMAL_REBOOT"); + strcpy(info->otherReason, "Reboot due to thermal value"); + strcpy(hw->mappedReason, "THERMAL_REBOOT"); + break; + case 0xEE: + strcpy(info->source, "KernelPanic"); + strcpy(info->reason, "KERNEL_PANIC"); + strcpy(info->customReason, "KERNEL_PANIC"); + strcpy(info->otherReason, "Reboot due to oops dump caused panic"); + strcpy(hw->mappedReason, "KERNEL_PANIC"); + break; + case 0xE0: + strcpy(info->source, "WATCH_DOG"); + strcpy(info->reason, "WATCHDOG_REBOOT"); + strcpy(info->customReason, "WATCHDOG_REBOOT"); + strcpy(info->otherReason, "Reboot due to watch dog timer"); + strcpy(hw->mappedReason, "WATCHDOG_REBOOT"); + break; + default: + strcpy(info->source, "HARD_POWER_RESET"); + strcpy(info->reason, "UNKNOWN_RESET"); + strcpy(info->customReason, "UNKNOWN"); + strcpy(info->otherReason, "Reboot due to unknown reason"); + strcpy(hw->mappedReason, "UNKNOWN_RESET"); + break; + } + + if (saved_timestamp[0] != '\0') { + strncpy(info->timestamp, saved_timestamp, sizeof(info->timestamp) - 1); + info->timestamp[sizeof(info->timestamp) - 1] = '\0'; + } + return SUCCESS; +} diff --git a/reboot-reason-fetcher/src/parodus_log_update.c b/reboot-reason-fetcher/src/parodus_log_update.c new file mode 100644 index 0000000..599619a --- /dev/null +++ b/reboot-reason-fetcher/src/parodus_log_update.c @@ -0,0 +1,218 @@ +#include "update-reboot-info.h" +#include "rdk_logger.h" +#include +#include +#include + +#define KERNEL_REASON_LOG "/opt/logs/receiver.log" +#define COPY_BUFFER_SIZE 4096 + +static void get_timestamp_string(char *buffer, size_t size) +{ + time_t now = time(NULL); + struct tm *tm_info = localtime(&now); + strftime(buffer, size, "%y%m%d-%H:%M:%S", tm_info); +} + +int append_kernel_reason(const EnvContext *ctx, const RebootInfo *info) +{ + FILE *fp = NULL; + char timestamp[MAX_TIMESTAMP_LENGTH]; + char kernel_reset_reason[MAX_REASON_LENGTH]; + if (!ctx || !info) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for append_kernel_reason\n"); + return ERROR_GENERAL; + } + if (strcmp(ctx->soc, "RTK") != 0 && strcmp(ctx->soc, "REALTEK") != 0) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Not RTK/REALTEK platform, skipping kernel reason append \n"); + return SUCCESS; + } + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Appending kernel reason for RTK/REALTEK platform \n"); + strncpy(kernel_reset_reason, info->reason, sizeof(kernel_reset_reason) - 1); + kernel_reset_reason[sizeof(kernel_reset_reason) - 1] = '\0'; + for (char *p = kernel_reset_reason; *p; p++) { + *p = tolower(*p); + } + get_timestamp_string(timestamp, sizeof(timestamp)); + fp = fopen(KERNEL_REASON_LOG, "a"); + if (!fp) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open kernel reason log %s: %s\n", KERNEL_REASON_LOG, strerror(errno)); + return ERROR_GENERAL; + } + fprintf(fp, "%s: Kernel reboot reason: %s\n", timestamp, kernel_reset_reason); + fflush(fp); + fclose(fp); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Kernel reason appended to %s: %s\n", KERNEL_REASON_LOG, kernel_reset_reason); + return SUCCESS; +} + +int update_parodus_log(const RebootInfo *info) +{ + FILE *fp = NULL; + char timestamp[MAX_TIMESTAMP_LENGTH]; + char line[MAX_BUFFER_SIZE]; + bool logVal = false; + if (!info) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for update_parodus_log \n"); + return ERROR_GENERAL; + } + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Updating Parodus log \n"); + fp = fopen(PARODUS_LOG, "r"); + if (fp) { + while (fgets(line, sizeof(line), fp)) { + if (strstr(line, "PreviousRebootInfo")) { + logVal = true; + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","PreviousRebootInfo already exists in Parodus log \n"); + break; + } + } + fclose(fp); + } + if (logVal) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Parodus log already contains reboot info, skipping update \n"); + return SUCCESS; + } + get_timestamp_string(timestamp, sizeof(timestamp)); + fp = fopen(PARODUS_LOG, "a"); + if (!fp) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open Parodus log %s: %s\n", PARODUS_LOG, strerror(errno)); + return ERROR_GENERAL; + } + fprintf(fp, "%s: %s: Updating previous reboot info to Parodus\n", timestamp, "update_previous_reboot_info"); + fprintf(fp, "%s: %s: PreviousRebootInfo:%s,%s,%s,%s\n", + timestamp, + "update_previous_reboot_info", + info->timestamp, + info->reason, + info->customReason, + info->source); + fflush(fp); + fclose(fp); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Parodus log updated with reboot info:\n"); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO", + "{\n" + " \"timestamp\":\"%s\",\n" + " \"source\":\"%s\",\n" + " \"reason\":\"%s\",\n" + " \"customReason\":\"%s\",\n" + " \"otherReason\":\"%s\"\n" + "}\n", + info->timestamp, + info->source, + info->reason, + info->customReason, + info->otherReason); + return SUCCESS; +} + +int handle_parodus_reboot_file(const RebootInfo *info, const char *destPath) +{ + if (!info || !destPath) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for handle_parodus_reboot_file\n"); + return ERROR_GENERAL; + } + + if (access(PARODUS_REBOOT_INFO_FILE, R_OK) == 0) { + FILE *in = fopen(PARODUS_REBOOT_INFO_FILE, "r"); + if (!in) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open %s: %s\n", PARODUS_REBOOT_INFO_FILE, strerror(errno)); + } else { + char buf[MAX_BUFFER_SIZE]; + size_t n = fread(buf, 1, sizeof(buf) - 1, in); + buf[n] = '\0'; + fclose(in); + + FILE *out = fopen(destPath, "w"); + if (!out) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open %s: %s\n", destPath, strerror(errno)); + (void)unlink(PARODUS_REBOOT_INFO_FILE); + return ERROR_GENERAL; + } + fputs(buf, out); + if (buf[0] != '\0' && buf[strlen(buf) - 1] != '\n') { + fputc('\n', out); + } + fflush(out); + fclose(out); + + char ts[MAX_TIMESTAMP_LENGTH]; + get_timestamp_string(ts, sizeof(ts)); + FILE *logfp = fopen(PARODUS_LOG, "a"); + if (logfp) { + fprintf(logfp, "%s: %s: Updating previous reboot info to Parodus\n", ts, "update_previous_reboot_info"); + fprintf(logfp, "%s: %s: %s\n", ts, "update_previous_reboot_info", buf); + fflush(logfp); + fclose(logfp); + } else { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open Parodus log %s: %s\n", PARODUS_LOG, strerror(errno)); + } + + (void)unlink(PARODUS_REBOOT_INFO_FILE); + return SUCCESS; + } + } + FILE *out = fopen(destPath, "w"); + if (!out) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open %s: %s\n", destPath, strerror(errno)); + return ERROR_GENERAL; + } + fprintf(out, "PreviousRebootInfo:%s,%s,%s,%s\n", + info->timestamp, + info->reason, + info->customReason, + info->source); + fflush(out); + fclose(out); + (void)unlink(PARODUS_REBOOT_INFO_FILE); + + return SUCCESS; +} + +int copy_keypress_info(const char *srcPath, const char *destPath) +{ + int src_fd = -1, dest_fd = -1; + char buffer[COPY_BUFFER_SIZE]; + ssize_t bytes_read, bytes_written; + int ret = SUCCESS; + if (!srcPath || !destPath) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for copy_keypress_info \n"); + return ERROR_GENERAL; + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Copying keypress info from %s to %s\n", srcPath, destPath); + if (access(srcPath, F_OK) != 0) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Source keypress file does not exist: %s (not an error)\n", srcPath); + return SUCCESS; + } + src_fd = open(srcPath, O_RDONLY); + if (src_fd < 0) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open source file %s: %s\n", srcPath, strerror(errno)); + return SUCCESS; + } + dest_fd = open(destPath, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (dest_fd < 0) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open destination file %s: %s\n", destPath, strerror(errno)); + close(src_fd); + return ERROR_GENERAL; + } + while ((bytes_read = read(src_fd, buffer, sizeof(buffer))) > 0) { + bytes_written = write(dest_fd, buffer, bytes_read); + if (bytes_written != bytes_read) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to write to %s: %s\n", destPath, strerror(errno)); + ret = ERROR_GENERAL; + break; + } + } + if (bytes_read < 0) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to read from %s: %s\n", srcPath, strerror(errno)); + ret = ERROR_GENERAL; + } + close(src_fd); + if (dest_fd >= 0) { + fsync(dest_fd); + close(dest_fd); + } + if (ret == SUCCESS) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Keypress info copied successfully\n"); + } + return ret; +} diff --git a/reboot-reason-fetcher/src/platform_hal.c b/reboot-reason-fetcher/src/platform_hal.c new file mode 100644 index 0000000..d02243e --- /dev/null +++ b/reboot-reason-fetcher/src/platform_hal.c @@ -0,0 +1,271 @@ +#include "update-reboot-info.h" +#include +#include "rdk_logger.h" + +int get_hardware_reason(const EnvContext *ctx, HardwareReason *hwReason, RebootInfo *info) +{ + memset(hwReason, 0, sizeof(HardwareReason)); + if (ctx->soc[0] != '\0') { + if (strcmp(ctx->soc, "BRCM") == 0 || strcmp(ctx->soc, "BROADCOM") == 0) { + return read_brcm_previous_reboot_reason(hwReason); + } else if (strcmp(ctx->soc, "RTK") == 0 || strcmp(ctx->soc, "REALTEK") == 0) { + return read_rtk_wakeup_reason(hwReason); + } else if (strcmp(ctx->soc, "AMLOGIC") == 0) { + return read_amlogic_reset_reason(hwReason, info); + } else if (strcmp(ctx->soc, "MEDIATEK") == 0 || strcmp(ctx->soc, "MTK") == 0) { + return read_mtk_reset_reason(hwReason, info); + } + } + // Fallback attempts independent of SOC (covers cases where env vars failed) + if (hwReason->mappedReason[0] == '\0') { + if (access("/proc/brcm/previous_reboot_reason", R_OK) == 0) { + if (read_brcm_previous_reboot_reason(hwReason) == SUCCESS) return SUCCESS; + } + if (access("/proc/cmdline", R_OK) == 0) { + if (read_rtk_wakeup_reason(hwReason) == SUCCESS) return SUCCESS; + } + if (access("/sys/devices/platform/aml_pm/reset_reason", R_OK) == 0) { + if (read_amlogic_reset_reason(hwReason, info) == SUCCESS) return SUCCESS; + } + if (access("/sys/mtk_pm/boot_reason", R_OK) == 0) { + if (read_mtk_reset_reason(hwReason, info) == SUCCESS) return SUCCESS; + } + } + if (hwReason->mappedReason[0] == '\0') { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Hardware reason not determined (SOC='%s')", ctx->soc); + strcpy(hwReason->mappedReason, "UNKNOWN"); + } + return SUCCESS; +} + +int get_hardware_reason_brcm(const EnvContext *ctx, HardwareReason *hwReason) +{ + FILE *fp = NULL; + char buffer[MAX_BUFFER_SIZE]; + if (!ctx || !hwReason) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for get_hardware_reason_brcm\n"); + return ERROR_GENERAL; + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Getting Broadcom hardware reboot reason from %s\n", BRCM_REBOOT_FILE); + memset(hwReason, 0, sizeof(HardwareReason)); + if (access(BRCM_REBOOT_FILE, F_OK) != 0) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Broadcom reboot reason file not found: %s\n", BRCM_REBOOT_FILE); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + return SUCCESS; + } + fp = fopen(BRCM_REBOOT_FILE, "r"); + if (!fp) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open %s: %s\n", BRCM_REBOOT_FILE, strerror(errno)); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + return ERROR_GENERAL; + } + if (fgets(buffer, sizeof(buffer), fp)) { + char *newline = strchr(buffer, '\n'); + if (newline) *newline = '\0'; + strncpy(hwReason->rawReason, buffer, sizeof(hwReason->rawReason) - 1); + hwReason->rawReason[sizeof(hwReason->rawReason) - 1] = '\0'; + for (char *p = hwReason->rawReason; *p; p++) { + *p = toupper(*p); + } + strncpy(hwReason->mappedReason, hwReason->rawReason, sizeof(hwReason->mappedReason) - 1); + hwReason->mappedReason[sizeof(hwReason->mappedReason) - 1] = '\0'; + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Broadcom hardware reason: %s\n", hwReason->rawReason); + } else { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Empty reboot reason file\n"); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + } + fclose(fp); + return SUCCESS; +} + +int get_hardware_reason_rtk(const EnvContext *ctx, HardwareReason *hwReason) +{ + FILE *fp = NULL; + char buffer[MAX_BUFFER_SIZE]; + char *wakeup_ptr = NULL; + if (!ctx || !hwReason) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for get_hardware_reason_rtk\n"); + return ERROR_GENERAL; + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Getting Realtek hardware reboot reason from %s\n", CMDLINE_PATH); + memset(hwReason, 0, sizeof(HardwareReason)); + fp = fopen(CMDLINE_PATH, "r"); + if (!fp) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Failed to open %s: %s\n", CMDLINE_PATH, strerror(errno)); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + return ERROR_GENERAL; + } + if (fgets(buffer, sizeof(buffer), fp)) { + wakeup_ptr = strstr(buffer, WAKEUP_REASON_KEY); + if (wakeup_ptr) { + char *value_start = wakeup_ptr + strlen(WAKEUP_REASON_KEY); + char *value_end = value_start; + while (*value_end && *value_end != ' ' && *value_end != '\n' && *value_end != '\t') { + value_end++; + } + size_t len = value_end - value_start; + if (len > 0 && len < sizeof(hwReason->rawReason)) { + strncpy(hwReason->rawReason, value_start, len); + hwReason->rawReason[len] = '\0'; + for (char *p = hwReason->rawReason; *p; p++) { + *p = toupper(*p); + } + strncpy(hwReason->mappedReason, hwReason->rawReason, sizeof(hwReason->mappedReason) - 1); + hwReason->mappedReason[sizeof(hwReason->mappedReason) - 1] = '\0'; + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Realtek wakeup reason: %s\n", hwReason->rawReason); + } else { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Invalid wakeupreason value\n"); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + } + } else { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","No wakeupreason found in cmdline\n"); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + } + } else { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to read from %s\n", CMDLINE_PATH); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + } + fclose(fp); + return SUCCESS; +} + +static const char* map_amlogic_reason(int code) +{ + switch (code) { + case 0: return "POWER_ON_RESET"; + case 1: return "WATCHDOG_RESET"; + case 2: return "SOFTWARE_RESET"; + case 3: return "KERNEL_PANIC"; + case 4: return "THERMAL_RESET"; + case 5: return "HARDWARE_RESET"; + case 6: return "SUSPEND_WAKEUP"; + case 7: return "REMOTE_WAKEUP"; + case 8: return "RTC_WAKEUP"; + case 9: return "GPIO_WAKEUP"; + case 10: return "FACTORY_RESET"; + case 11: return "UPGRADE_RESET"; + case 12: return "FASTBOOT_RESET"; + case 13: return "CRASH_DUMP_RESET"; + case 14: return "RECOVERY_RESET"; + case 15: return "BOOTLOADER_RESET"; + default: return "UNKNOWN"; + } +} + +static const char* map_mtk_reason(int resetVal) +{ + switch (resetVal) { + case 0x00: + return "POWER_ON_RESET"; + case 0xD1: + return "SOFTWARE_MASTER_RESET"; + case 0xE4: + return "THERMAL_REBOOT"; + case 0xEE: + return "KERNEL_PANIC"; + case 0xE0: + return "WATCHDOG_REBOOT"; + default: + return "UNKNOWN_RESET"; + } +} + +int get_hardware_reason_amlogic(const EnvContext *ctx, HardwareReason *hwReason) +{ + FILE *fp = NULL; + char buffer[MAX_BUFFER_SIZE]; + int reset_code = -1; + if (!ctx || !hwReason) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for get_hardware_reason_amlogic\n"); + return ERROR_GENERAL; + } + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Getting Amlogic hardware reboot reason from %s\n", AMLOGIC_REBOOT_REASON_PATH); + memset(hwReason, 0, sizeof(HardwareReason)); + if (access(AMLOGIC_REBOOT_REASON_PATH, F_OK) != 0) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Amlogic reboot reason file not found: %s\n", AMLOGIC_REBOOT_REASON_PATH); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + return SUCCESS; + } + fp = fopen(AMLOGIC_REBOOT_REASON_PATH, "r"); + if (!fp) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open %s: %s\n", AMLOGIC_REBOOT_REASON_PATH, strerror(errno)); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + return ERROR_GENERAL; + } + if (fgets(buffer, sizeof(buffer), fp)) { + reset_code = atoi(buffer); + snprintf(hwReason->rawReason, sizeof(hwReason->rawReason), "%d", reset_code); + const char *mapped = map_amlogic_reason(reset_code); + strncpy(hwReason->mappedReason, mapped, sizeof(hwReason->mappedReason) - 1); + hwReason->mappedReason[sizeof(hwReason->mappedReason) - 1] = '\0'; + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Amlogic reset code: %d mapped to: %s\n", reset_code, hwReason->mappedReason); + } else { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Failed to read Amlogic reset code\n"); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + } + fclose(fp); + return SUCCESS; +} + +int get_hardware_reason_mtk(const EnvContext *ctx, HardwareReason *hwReason) +{ + FILE *fp = NULL; + char buffer[MAX_BUFFER_SIZE]; + int reset_code = -1; + if (!ctx || !hwReason) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for get_hardware_reason_mtk\n"); + return ERROR_GENERAL; + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Getting MTK hardware reboot reason from %s\n", MTK_SYSFS_FILE); + memset(hwReason, 0, sizeof(HardwareReason)); + if (access(MTK_SYSFS_FILE, F_OK) != 0) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","MTK reboot reason file not found: %s\n", MTK_SYSFS_FILE); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + return SUCCESS; + } + fp = fopen(MTK_SYSFS_FILE, "r"); + if (!fp) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open %s: %s\n", MTK_SYSFS_FILE, strerror(errno)); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + return ERROR_GENERAL; + } + if (fgets(buffer, sizeof(buffer), fp)) { + char *newline = strchr(buffer, '\n'); + if (newline) *newline = '\0'; + + // Parse hex value + if (strncmp(buffer, "0x", 2) == 0 || strncmp(buffer, "0X", 2) == 0) { + reset_code = (int)strtol(buffer, NULL, 16); + } else { + reset_code = (int)strtol(buffer, NULL, 10); + } + + strncpy(hwReason->rawReason, buffer, sizeof(hwReason->rawReason) - 1); + hwReason->rawReason[sizeof(hwReason->rawReason) - 1] = '\0'; + + const char *mapped = map_mtk_reason(reset_code); + strncpy(hwReason->mappedReason, mapped, sizeof(hwReason->mappedReason) - 1); + hwReason->mappedReason[sizeof(hwReason->mappedReason) - 1] = '\0'; + + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","MTK reset code: %s (0x%X) mapped to: %s\n", + hwReason->rawReason, reset_code, hwReason->mappedReason); + } else { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Failed to read MTK reset code\n"); + strcpy(hwReason->rawReason, "UNKNOWN"); + strcpy(hwReason->mappedReason, "UNKNOWN"); + } + fclose(fp); + return SUCCESS; +} diff --git a/reboot-reason-fetcher/src/reboot_reason_classify.c b/reboot-reason-fetcher/src/reboot_reason_classify.c new file mode 100644 index 0000000..61f4554 --- /dev/null +++ b/reboot-reason-fetcher/src/reboot_reason_classify.c @@ -0,0 +1,557 @@ +#include "update-reboot-info.h" +#include +#include +#include +#include +#include +#include "rdk_logger.h" + +#define KERNEL_LOG_FILE "/opt/logs/messages.txt" +#define PSTORE_CONSOLE_LOG_FILE "/sys/fs/pstore/console-ramoops-0" +#define PREVIOUS_KERNEL_OOPS_DUMP "PREVIOUS_KERNEL_OOPS_DUMP" +#define UIMGR_LOG_FILE "/opt/logs/PreviousLogs/uimgr_log.txt" +#define OCAPRI_LOG_FILE "/opt/logs/PreviousLogs/ocapri_log.txt" +#define ECM_CRASH_LOG_FILE "/opt/logs/PreviousLogs/messages-ecm.txt" +#define MAX_REBOOT_STRING "Box has rebooted 10 times" +#define ECM_CRASH_STRING "**** CRASH ****" + +// --- Classification arrays --- +static const char *APP_TRIGGERED_REASONS[] = { + "Servicemanager", "systemservice_legacy", "WarehouseReset", "WarehouseService", + "HrvInitWHReset", "HrvColdInitReset", "HtmlDiagnostics", "InstallTDK", "StartTDK", + "TR69Agent", "SystemServices", "Bsu_GUI", "SNMP", "CVT_CDL", "Nxserver", + "DRM_Netflix_Initialize", "hrvinit", "PaceMFRLibrary", NULL +}; +static const char *OPS_TRIGGERED_REASONS[] = { + "ScheduledReboot", "RebootSTB.sh", "FactoryReset", "UpgradeReboot_restore", "XFS", + "wait_for_pci0_ready", "websocketproxyinit", "NSC_IR_EventReboot", + "host_interface_dma_bus_wait", "usbhotplug", "Receiver_MDVRSet", + "Receiver_VidiPath_Enabled", "Receiver_Toggle_Optimus", "S04init_ticket", + "Network-Service", "monitorMfrMgr.sh", "vlAPI_Caller_Upgrade", "ImageUpgrade_rmf_osal", + "ImageUpgrade_mfr_api", "ImageUpgrade_userInitiatedFWDnld.sh", "ClearSICache", + "tr69hostIfReset", "hostIf_utils", "hostifDeviceInfo", "HAL_SYS_Reboot", + "UpgradeReboot_deviceInitiatedFWDnld.sh", "UpgradeReboot_rdkvfwupgrader", + "UpgradeReboot_ipdnl.sh", "PowerMgr_Powerreset", "PowerMgr_coldFactoryReset", + "DeepSleepMgr", "PowerMgr_CustomerReset", "PowerMgr_PersonalityReset", + "Power_Thermmgr", "PowerMgr_Plat", "HAL_CDL_notify_mgr_event", + "vldsg_estb_poll_ecm_operational_state", "SASWatchDog", "BP3_Provisioning", + "eMMC_FW_UPGRADE", "BOOTLOADER_UPGRADE", "cdl_service", "docsis_mode_check.sh", + "Receiver", "CANARY_Update", "BRCM_Image_Validate", "tch_nvram.sh", + "boot_FSR", "BCMCommandHandler", NULL +}; +static const char *MAINTENANCE_TRIGGERED_REASONS[] = { + "AutoReboot.sh", "PwrMgr", NULL +}; +bool is_app_triggered(const char *reason) +{ + if (!reason) return false; + for (int i = 0; APP_TRIGGERED_REASONS[i] != NULL; i++) { + if (strcmp(reason, APP_TRIGGERED_REASONS[i]) == 0) { + return true; + } + } + return false; +} +bool is_ops_triggered(const char *reason) +{ + if (!reason) return false; + for (int i = 0; OPS_TRIGGERED_REASONS[i] != NULL; i++) { + if (strcmp(reason, OPS_TRIGGERED_REASONS[i]) == 0) { + return true; + } + } + return false; +} +bool is_maintenance_triggered(const char *reason) +{ + if (!reason) return false; + for (int i = 0; MAINTENANCE_TRIGGERED_REASONS[i] != NULL; i++) { + if (strcmp(reason, MAINTENANCE_TRIGGERED_REASONS[i]) == 0) { + return true; + } + } + return false; +} + +static const char *panic_signatures[] = { + "Kernel panic - not syncing", + "Kernel Panic", + "Kernel Oops", + "Oops - undefined instruction", + "Oops - bad syscall", + "branch through zero", + "unknown data abort code", + "Illegal memory access", + NULL +}; + +static bool search_panic_in_file(const char *filepath, PanicInfo *panicInfo) +{ + FILE *fp = NULL; + char line[MAX_BUFFER_SIZE]; + if (access(filepath, F_OK) != 0) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","File does not exist: %s\n", filepath); + return false; + } + fp = fopen(filepath, "r"); + if (!fp) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to open %s: %s\n", filepath, strerror(errno)); + return false; + } + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Searching for panic signatures in: %s\n", filepath); + while (fgets(line, sizeof(line), fp)) { + for (int i = 0; panic_signatures[i] != NULL; i++) { + if (strstr(line, panic_signatures[i])) { + strncpy(panicInfo->panicType, panic_signatures[i], sizeof(panicInfo->panicType) - 1); + panicInfo->panicType[sizeof(panicInfo->panicType) - 1] = '\0'; + strncpy(panicInfo->details, line, sizeof(panicInfo->details) - 1); + panicInfo->details[sizeof(panicInfo->details) - 1] = '\0'; + char *nl = strchr(panicInfo->details, '\n'); + if (nl) *nl = '\0'; + panicInfo->detected = true; + fclose(fp); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Kernel panic detected: %s\n", panicInfo->panicType); + return true; + } + } + } + fclose(fp); + return false; +} + +static void copy_pstore_logs_to_opt(void) +{ + DIR *dir = NULL; + struct dirent *ent; + if (access(PSTORE_DIR, F_OK) != 0) { + return; + } + dir = opendir(PSTORE_DIR); + if (!dir) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Failed to open %s: %s\n", PSTORE_DIR, strerror(errno)); + return; + } + while ((ent = readdir(dir)) != NULL) { + if (ent->d_name[0] == '.') continue; + char src[MAX_PATH_LENGTH]; + char dst[MAX_PATH_LENGTH]; + size_t name_len_full = strlen(ent->d_name); + size_t name_len = (name_len_full > 255) ? 255 : name_len_full; + + if (strlen(PSTORE_DIR) + 1 + name_len + 1 >= sizeof(src)) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Skipping PSTORE entry with long name: %s\n", ent->d_name); + continue; + } + if (strlen("/opt/logs/") + name_len + strlen(".log") + 1 >= sizeof(dst)) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Skipping PSTORE entry (dst too long): %s\n", ent->d_name); + continue; + } + int max_src_name = (int)(sizeof(src) - strlen(PSTORE_DIR) - 2); // '/' + '\0' + if (max_src_name <= 0) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Skipping PSTORE entry (name too long for src): %s\n", ent->d_name); + continue; + } + snprintf(src, sizeof(src), "%s/%.*s", PSTORE_DIR, max_src_name, ent->d_name); + int max_dst_name = (int)(sizeof(dst) - strlen("/opt/logs/") - strlen(".log") - 1); + if (max_dst_name <= 0) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Skipping PSTORE entry (name too long for dst): %s\n", ent->d_name); + continue; + } + snprintf(dst, sizeof(dst), "/opt/logs/%.*s.log", max_dst_name, ent->d_name); + FILE *fs = fopen(src, "rb"); + if (!fs) { + continue; + } + FILE *fd = fopen(dst, "wb"); + if (!fd) { + fclose(fs); + continue; + } + char buf[4096]; size_t n; + while ((n = fread(buf, 1, sizeof(buf), fs)) > 0) { + fwrite(buf, 1, n, fd); + } + fflush(fd); + fclose(fs); fclose(fd); + } + closedir(dir); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Copied PSTORE logs to /opt/logs \n"); +} +int detect_kernel_panic(const EnvContext *ctx, PanicInfo *panicInfo) +{ + if (!ctx || !panicInfo) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Invalid parameters for detect_kernel_panic \n"); + return ERROR_GENERAL; + } + memset(panicInfo, 0, sizeof(PanicInfo)); + panicInfo->detected = false; + if (strcmp(ctx->soc, "BRCM") == 0) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Checking BRCM kernel panic in messages.txt \n"); + FILE *fp = fopen(KERNEL_LOG_FILE, "r"); + if (fp) { + char line[MAX_BUFFER_SIZE]; + bool oops_marker_found = false; + while (fgets(line, sizeof(line), fp)) { + if (strstr(line, PREVIOUS_KERNEL_OOPS_DUMP)) { + oops_marker_found = true; + break; + } + } + fclose(fp); + if (oops_marker_found) { + search_panic_in_file(KERNEL_LOG_FILE, panicInfo); + } + } + } + // RTK/REALTEK (and TV profiles) check PSTORE console + if (strcmp(ctx->soc, "RTK") == 0 || strcmp(ctx->soc, "REALTEK") == 0) { + if (search_panic_in_file(PSTORE_CONSOLE_LOG_FILE, panicInfo)) { + copy_pstore_logs_to_opt(); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","PSTORE indicates kernel panic; logs copied \n"); + } + } + if (panicInfo->detected) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Kernel panic detected: %s\n", panicInfo->panicType); + } else { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","No kernel panic detected"); + } + return SUCCESS; +} +static bool search_string_in_file(const char *filepath, const char *search_str) +{ + FILE *fp = NULL; + char line[MAX_BUFFER_SIZE]; + bool found = false; + if (access(filepath, F_OK) != 0) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","File does not exist: %s\n", filepath); + return false; + } + fp = fopen(filepath, "r"); + if (!fp) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Failed to open %s: %s\n", filepath, strerror(errno)); + return false; + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Searching for '%s' in: %s\n", search_str, filepath); + while (fgets(line, sizeof(line), fp)) { + if (strstr(line, search_str)) { + found = true; + break; + } + } + fclose(fp); + return found; +} +int check_firmware_failure(const EnvContext *ctx, FirmwareFailure *fwFailure) +{ + if (!ctx || !fwFailure) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Invalid parameters for check_firmware_failure\n"); + return ERROR_GENERAL; + } + memset(fwFailure, 0, sizeof(FirmwareFailure)); + bool is_mediaclient = (strncmp(ctx->device_type, "mediaclient", strlen("mediaclient")) == 0); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Checking firmware failures for %s device\n", is_mediaclient ? "mediaclient" : "STB"); + const char *max_reboot_log = is_mediaclient ? UIMGR_LOG_FILE : OCAPRI_LOG_FILE; + const char *source_name = is_mediaclient ? "UiMgr" : "OcapRI"; + if (search_string_in_file(max_reboot_log, MAX_REBOOT_STRING)) { + fwFailure->maxRebootDetected = true; + fwFailure->detected = true; + snprintf(fwFailure->details, sizeof(fwFailure->details), "%s: Reboot due to STB reached maximum (10) reboots", source_name); + strncpy(fwFailure->initiator, source_name, sizeof(fwFailure->initiator) - 1); + fwFailure->initiator[sizeof(fwFailure->initiator) - 1] = '\0'; + t2CountNotify("SYST_ERR_10Times_reboot", 1); + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Max reboot detected in %s\n", max_reboot_log); + } + if (!is_mediaclient && search_string_in_file(ECM_CRASH_LOG_FILE, ECM_CRASH_STRING)) { + fwFailure->ecmCrashDetected = true; + fwFailure->detected = true; + if (fwFailure->maxRebootDetected) { + } else { + strncpy(fwFailure->details, "EcmLogger: ECM crash detected", sizeof(fwFailure->details) - 1); + fwFailure->details[sizeof(fwFailure->details) - 1] = '\0'; + } + strncpy(fwFailure->initiator, "EcmLogger", sizeof(fwFailure->initiator) - 1); + fwFailure->initiator[sizeof(fwFailure->initiator) - 1] = '\0'; + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","ECM crash detected\n"); + } + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Firmware failure check complete: %s\n", fwFailure->detected ? fwFailure->details : "None detected"); + return SUCCESS; +} +static void map_hardware_reason(const char *hwReason, RebootInfo *info) +{ + if (!hwReason || !info) return; + if (strstr(hwReason, "SOFTWARE_MASTER_RESET") || + strstr(hwReason, "SOFTWARE_RESET") || + strstr(hwReason, "SOFTWARE_REBOOT") || + strstr(hwReason, "SW_RESET") || + strstr(hwReason, "SOFTWARE")) { + strcpy(info->source, "SoftwareReboot"); + strcpy(info->reason, "SOFTWARE_MASTER_RESET"); + strcpy(info->otherReason, "Reboot due to user triggered reboot command"); + t2CountNotify("Test_SWReset", 1); + } + else if (strstr(hwReason, "WATCHDOG")) { + strcpy(info->source, "WatchDog"); + strcpy(info->reason, "WATCHDOG_TIMER_RESET"); + strcpy(info->otherReason, "Reboot due to watch dog timer reset"); + } + else if (strstr(hwReason, "KERNEL_PANIC") || strstr(hwReason, "KERNEL_PANIC_RESET")) { + strcpy(info->source, "Kernel"); + strcpy(info->reason, "KERNEL_PANIC"); + strcpy(info->otherReason, "Reboot due to Kernel Panic captured by Oops Dump"); + } + else if (strstr(hwReason, "POWER_ON") || strstr(hwReason, "HARDWARE")) { + strcpy(info->source, "PowerOn"); + strcpy(info->reason, "POWER_ON_RESET"); + strcpy(info->otherReason, "Reboot due to unplug of power cable from the STB"); + } + else if (strstr(hwReason, "MAIN_CHIP_INPUT_RESET")) { + strcpy(info->source, "Main Chip"); + strcpy(info->reason, "MAIN_CHIP_INPUT_RESET"); + strcpy(info->otherReason, "Reboot due to chip's main reset input has been asserted"); + } + else if (strstr(hwReason, "MAIN_CHIP_RESET_INPUT")) { + strcpy(info->source, "Main Chip"); + strcpy(info->reason, "MAIN_CHIP_RESET_INPUT"); + strcpy(info->otherReason, "Reboot due to chip's main reset input has been asserted"); + } + else if (strstr(hwReason, "TAP_IN_SYSTEM_RESET")) { + strcpy(info->source, "Tap-In System"); + strcpy(info->reason, "TAP_IN_SYSTEM_RESET"); + strcpy(info->otherReason, "Reboot due to the chip's TAP in-system reset has been asserted"); + } + else if (strstr(hwReason, "FRONT_PANEL_4SEC_RESET") || strstr(hwReason, "FRONT_PANEL_RESET")) { + strcpy(info->source, "FrontPanel Button"); + strcpy(info->reason, "FRONT_PANEL_RESET"); + strcpy(info->otherReason, "Reboot due to the front panel 4 second reset has been asserted"); + } + else if (strstr(hwReason, "S3_WAKEUP_RESET")) { + strcpy(info->source, "Standby Wakeup"); + strcpy(info->reason, "S3_WAKEUP_RESET"); + strcpy(info->otherReason, "Reboot due to the chip woke up from deep standby"); + } + else if (strstr(hwReason, "SMARTCARD_INSERT_RESET")) { + strcpy(info->source, "SmartCard Insert"); + strcpy(info->reason, "SMARTCARD_INSERT_RESET"); + strcpy(info->otherReason, "Reboot due to the smartcard insert reset has occurred"); + } + else if (strstr(hwReason, "OVERTEMP") || strstr(hwReason, "OVERHEAT")) { + strcpy(info->source, "OverTemperature"); + strcpy(info->reason, "OVERTEMP_RESET"); + strcpy(info->otherReason, "Reboot due to chip temperature is above threshold (125*C)"); + } + else if (strstr(hwReason, "OVERVOLTAGE")) { + strcpy(info->source, "OverVoltage"); + strcpy(info->reason, "OVERVOLTAGE_RESET"); + strcpy(info->otherReason, "Reboot due to chip voltage is above threshold"); + } + else if (strstr(hwReason, "UNDERVOLTAGE") || strstr(hwReason, "UNDERVOLTAGE_0_RESET") || strstr(hwReason, "UNDERVOLTAGE_1_RESET")) { + strcpy(info->source, "LowVoltage"); + strcpy(info->reason, "UNDERVOLTAGE_RESET"); + strcpy(info->otherReason, "Reboot due to chip voltage is below threshold"); + } + else if (strstr(hwReason, "PCIE_0_HOT_BOOT_RESET") || strstr(hwReason, "PCIE_1_HOT_BOOT_RESET") || strstr(hwReason, "PCIE_HOT_BOOT_RESET")) { + strcpy(info->source, "PCIE Boot"); + strcpy(info->reason, "PCIE_HOT_BOOT_RESET"); + strcpy(info->otherReason, "Reboot due to PCIe hot boot reset has occurred"); + } + else if (strstr(hwReason, "SECURITY_MASTER_RESET")) { + strcpy(info->source, "SecurityReboot"); + strcpy(info->reason, "SECURITY_MASTER_RESET"); + strcpy(info->otherReason, "Reboot due to security master reset has occurred"); + } + else if (strstr(hwReason, "CPU_EJTAG_RESET")) { + strcpy(info->source, "CPU EJTAG"); + strcpy(info->reason, "CPU_EJTAG_RESET"); + strcpy(info->otherReason, "Reboot due to CPU EJTAG reset has occurred"); + } + else if (strstr(hwReason, "SCPU_EJTAG_RESET")) { + strcpy(info->source, "SCPU EJTAG"); + strcpy(info->reason, "SCPU_EJTAG_RESET"); + strcpy(info->otherReason, "Reboot due to SCPU EJTAG reset has occurred"); + } + else if (strstr(hwReason, "GEN_WATCHDOG_1_RESET") || strstr(hwReason, "GEN_WATCHDOG_RESET")) { + strcpy(info->source, "GEN WatchDog"); + strcpy(info->reason, "GEN_WATCHDOG_RESET"); + strcpy(info->otherReason, "Reboot due to gen_watchdog_1 timeout reset has occurred"); + } + else if (strstr(hwReason, "AUX_CHIP_EDGE_RESET_0") || strstr(hwReason, "AUX_CHIP_EDGE_RESET_1") || strstr(hwReason, "AUX_CHIP_EDGE_RESET")) { + strcpy(info->source, "Aux Chip Edge"); + strcpy(info->reason, "AUX_CHIP_EDGE_RESET"); + strcpy(info->otherReason, "Reboot due to the auxiliary edge-triggered chip reset has occurred"); + } + else if (strstr(hwReason, "AUX_CHIP_LEVEL_RESET_0") || strstr(hwReason, "AUX_CHIP_LEVEL_RESET_1") || strstr(hwReason, "AUX_CHIP_LEVEL_RESET")) { + strcpy(info->source, "Aux Chip Level"); + strcpy(info->reason, "AUX_CHIP_LEVEL_RESET"); + strcpy(info->otherReason, "Reboot due to the auxiliary level-triggered chip reset has occurred"); + } + else if (strstr(hwReason, "MPM_RESET")) { + strcpy(info->source, "MPM"); + strcpy(info->reason, "MPM_RESET"); + strcpy(info->otherReason, "Reboot due to the MPM reset has occurred"); + } + else { + strcpy(info->source, "Unknown"); + size_t len = strlen(hwReason); + if (len >= sizeof(info->reason)) len = sizeof(info->reason) - 1; + memcpy(info->reason, hwReason, len); + info->reason[len] = '\0'; + len = strlen(hwReason); + if (len >= sizeof(info->otherReason)) len = sizeof(info->otherReason) - 1; + memcpy(info->otherReason, hwReason, len); + info->otherReason[len] = '\0'; + } +} + +int classify_reboot_reason(RebootInfo *info, const EnvContext *ctx, const HardwareReason *hwReason, const PanicInfo *panicInfo, const FirmwareFailure *fwFailure) +{ + if (!info || !ctx) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Invalid parameters for classify_reboot_reason\n"); + return ERROR_GENERAL; + } + + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Classifying reboot reason\n"); + if (fwFailure && fwFailure->detected) { + if (fwFailure->initiator[0] != '\0') { + strncpy(info->source, fwFailure->initiator, sizeof(info->source) - 1); + info->source[sizeof(info->source) - 1] = '\0'; + } else { + strcpy(info->source, "FirmwareFailure"); + } + strcpy(info->reason, "FIRMWARE_FAILURE"); + if (fwFailure->details[0] != '\0') { + strncpy(info->otherReason, fwFailure->details, sizeof(info->otherReason) - 1); + info->otherReason[sizeof(info->otherReason) - 1] = '\0'; + } + info->customReason[0] = '\0'; + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Firmware failure classified: %s\n", info->otherReason); + return SUCCESS; + } + if (info->customReason[0] != '\0') { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Classifying existing custom reason: %s", info->customReason); + if (strcmp(info->customReason, "MAINTENANCE_REBOOT") == 0) { + strcpy(info->reason, "MAINTENANCE_REBOOT"); + return SUCCESS; + } + if (is_app_triggered(info->customReason)) { + strcpy(info->reason, "APP_TRIGGERED"); + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Classified as APP_TRIGGERED: %s\n", info->customReason); + return SUCCESS; + } + if (is_ops_triggered(info->customReason)) { + strcpy(info->reason, "OPS_TRIGGERED"); + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Classified as OPS_TRIGGERED: %s\n", info->customReason); + return SUCCESS; + } + if (is_maintenance_triggered(info->customReason)) { + strcpy(info->reason, "MAINTENANCE_REBOOT"); + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Classified as MAINTENANCE_REBOOT: %s\n", info->customReason); + return SUCCESS; + } + // Default to firmware failure when initiator does not match any list + strcpy(info->reason, "FIRMWARE_FAILURE"); + if (info->source[0] == '\0') { + strcpy(info->source, "Unknown"); + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Defaulted to FIRMWARE_FAILURE for customReason: %s\n", info->customReason); + return SUCCESS; + } + // Apply kernel panic classification only when no prior customReason exists + if (panicInfo && panicInfo->detected && info->customReason[0] == '\0') { + strcpy(info->source, "Kernel"); + strcpy(info->reason, "KERNEL_PANIC"); + // Align with script: prefix KERNEL_PANIC instead of detailed panic type + strncpy(info->customReason, "Hardware Register - KERNEL_PANIC", sizeof(info->customReason) - 1); + info->customReason[sizeof(info->customReason) - 1] = '\0'; + strcpy(info->otherReason, "Reboot due to Kernel Panic captured by Oops Dump"); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Kernel panic classified (prefixed): %s\n", panicInfo->panicType); + return SUCCESS; + } + if (hwReason && hwReason->mappedReason[0] != '\0') { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO", "hwreason is not NULL : %s\n", hwReason->mappedReason); + map_hardware_reason(hwReason->mappedReason, info); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO", "After map function call : hwreason is not NULL : %s\n", hwReason->mappedReason); + const char prefix[] = "Hardware Register - "; + if (ctx && (strcmp(ctx->soc, "BRCM") == 0 || strcmp(ctx->soc, "BROADCOM") == 0)) { + if (hwReason->rawReason[0] != '\0') { + char upbuf[MAX_BUFFER_SIZE]; + size_t n = strlen(hwReason->rawReason); + if (n >= sizeof(upbuf)) n = sizeof(upbuf) - 1; + memcpy(upbuf, hwReason->rawReason, n); + upbuf[n] = '\0'; + for (size_t i = 0; i < n; i++) { + upbuf[i] = toupper((unsigned char)upbuf[i]); + } + size_t prefix_len = sizeof(prefix) - 1; + size_t max_reason = sizeof(info->customReason) - 1 - prefix_len; + if ((int)max_reason < 0) max_reason = 0; + snprintf(info->customReason, sizeof(info->customReason), "%s%.*s", prefix, (int)max_reason, upbuf); + } else if (strcmp(hwReason->mappedReason, "UNKNOWN") == 0) { + strncpy(info->customReason, "Hardware Register - NULL", sizeof(info->customReason) - 1); + info->customReason[sizeof(info->customReason) - 1] = '\0'; + strcpy(info->source, "Hard Power Reset"); + strcpy(info->reason, "HARD_POWER"); + strcpy(info->otherReason, "No information found"); + } else { + size_t prefix_len = sizeof(prefix) - 1; + size_t max_reason = sizeof(info->customReason) - 1 - prefix_len; + if ((int)max_reason < 0) max_reason = 0; + snprintf(info->customReason, sizeof(info->customReason), "%s%.*s", prefix, (int)max_reason, hwReason->mappedReason); + } + } else { + if (strcmp(hwReason->mappedReason, "UNKNOWN") == 0 || hwReason->mappedReason[0] == '\0') { + strncpy(info->customReason, "Hardware Register - NULL", sizeof(info->customReason) - 1); + info->customReason[sizeof(info->customReason) - 1] = '\0'; + strcpy(info->source, "Hard Power Reset"); + strcpy(info->reason, "HARD_POWER"); + strcpy(info->otherReason, "No information found"); + } else { + const char* standardized = (info->reason[0] != '\0') ? info->reason : hwReason->mappedReason; + size_t prefix_len = sizeof(prefix) - 1; + size_t max_reason = sizeof(info->customReason) - 1 - prefix_len; + if ((int)max_reason < 0) max_reason = 0; + snprintf(info->customReason, sizeof(info->customReason), "%s%.*s", prefix, (int)max_reason, standardized); + } + } + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Classified hardware reason - Source: %s, Reason: %s\n", info->source, info->reason); + return SUCCESS; + } + if (info->source[0] == '\0') { + strcpy(info->source, "Unknown"); + strcpy(info->reason, "UNKNOWN"); + strcpy(info->customReason, "UNKNOWN"); + strcpy(info->otherReason, "Reboot reason could not be determined"); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Reboot reason classified as UNKNOWN\n"); + if ((!panicInfo || !panicInfo->detected) && (!fwFailure || !fwFailure->detected)) { + strcpy(info->source, "SoftwareReboot"); + strcpy(info->reason, "SOFTWARE_MASTER_RESET"); + strcpy(info->customReason, "SOFTWARE_MASTER_RESET"); + strcpy(info->otherReason, "Reboot due to user triggered reboot command"); + t2CountNotify("Test_SWReset", 1); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Fallback classification: SoftwareReboot\n"); + } else { + strcpy(info->source, "Unknown"); + strcpy(info->reason, "UNKNOWN"); + strcpy(info->customReason, "UNKNOWN"); + strcpy(info->otherReason, "Reboot reason could not be determined"); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Reboot reason classified as UNKNOWN\n"); + } + } + if (ctx && (strcmp(ctx->soc, "RTK") == 0 || strcmp(ctx->soc, "REALTEK") == 0)) { + if (info->reason[0] != '\0') { + char lower[MAX_REASON_LENGTH]; + size_t len = strlen(info->reason); + for (size_t i = 0; i < len && i < sizeof(lower) - 1; i++) { + lower[i] = tolower((unsigned char)info->reason[i]); + } + lower[len] = '\0'; + FILE *klog = fopen("/opt/logs/messages.txt", "a"); + if (klog) { + fprintf(klog, "PreviousRebootReason: %s\n", lower); + fflush(klog); + fclose(klog); + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Annotated kernel log with PreviousRebootReason: %s\n", lower); + } + } + } + return SUCCESS; +} diff --git a/reboot-reason-fetcher/src/rebootreason_main.c b/reboot-reason-fetcher/src/rebootreason_main.c new file mode 100644 index 0000000..221fd96 --- /dev/null +++ b/reboot-reason-fetcher/src/rebootreason_main.c @@ -0,0 +1,204 @@ +#include "update-reboot-info.h" +#include "rdk_logger.h" + +void t2CountNotify(char *marker, int val) { +#ifdef T2_EVENT_ENABLED + t2_event_d(marker, val); +#else + (void)marker; + (void)val; +#endif +} + +void t2ValNotify( char *marker, char *val ) +{ +#ifdef T2_EVENT_ENABLED + t2_event_s(marker, val); +#else + (void)marker; + (void)val; +#endif +} + +static void get_current_timestamp(char *buffer, size_t size) +{ + time_t now = time(NULL); + struct tm *tm_info = gmtime(&now); + strftime(buffer, size, "%a %b %d %H:%M:%S UTC %Y", tm_info); +} + +static int check_dir_exists(const char *path) +{ + struct stat st = {0}; + + if (stat(path, &st) == -1) { + if (mkdir(path, 0755) != 0) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to create directory %s: %s\n", path, strerror(errno)); + return ERROR_GENERAL; + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Created directory: %s\n", path); + } + return SUCCESS; +} + +static void log_reason(const char *path) +{ + FILE *fp = fopen(path, "r"); + char buf[256]; + + if (!fp) { + RDK_LOG(RDK_LOG_ERROR, "LOG.RDK.REBOOTINFO", + "Failed to open %s for logging: %s\n", path, strerror(errno)); + return; + } + + while (fgets(buf, sizeof(buf), fp) != NULL) { + RDK_LOG(RDK_LOG_INFO, "LOG.RDK.REBOOTINFO", "%s", buf); + } + fclose(fp); +} + +int main(void) +{ + EnvContext ctx; + RebootInfo rebootInfo; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + int ret = SUCCESS; + bool has_reboot_info = false; + bool lock_acquired = false; + + rdk_logger_ext_config_t config = { + .pModuleName = "LOG.RDK.REBOOTINFO", /* Module name */ + .loglevel = RDK_LOG_INFO, /* Default log level */ + .output = RDKLOG_OUTPUT_CONSOLE, /* Output to console (stdout/stderr) */ + .format = RDKLOG_FORMAT_WITH_TS, /* Timestamped format */ + .pFilePolicy = NULL /* Not using file output, so NULL */ + }; + + if (rdk_logger_ext_init(&config) != RDK_SUCCESS) { + printf("REBOOTINFO : ERROR - Extended logger init failed\n"); + return ERROR_GENERAL; + } + + RDK_LOG(RDK_LOG_INFO, "LOG.RDK.REBOOTINFO", "[%s:%d] RDK Logger initialized\n", __FUNCTION__, __LINE__); + +#ifdef T2_EVENT_ENABLED + t2_init("update-reboot-info"); +#endif + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Start of Reboot Reason \n"); + + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Acquired rebootInfo lock\n"); + if (acquire_lock(LOCK_DIR) != SUCCESS) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Failed to acquire lock, another instance may be running \n"); + return ERROR_LOCK_FAILED; + } + lock_acquired = true; + + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Loading environment context \n"); + if (parse_device_properties(&ctx) != SUCCESS) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to parse device properties \n"); + ret = ERROR_PARSE_FAILED; + goto cleanup; + } + + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Checking flags to update reboot reason \n"); + if (!update_reboot_info(&ctx)) { + ret = SUCCESS; + goto cleanup; + } + + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Ensuring reboot directory exists \n"); + if (check_dir_exists(REBOOT_INFO_DIR) != SUCCESS) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to create reboot directory \n"); + ret = ERROR_GENERAL; + goto cleanup; + } + + memset(&rebootInfo, 0, sizeof(RebootInfo)); + get_current_timestamp(rebootInfo.timestamp, sizeof(rebootInfo.timestamp)); + + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Checking for new reboot.info file \n"); + if (access(REBOOT_INFO_FILE, F_OK) == 0) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","New %s file found, Creating previous reboot info file...\n",REBOOT_INFO_FILE); + log_reason(REBOOT_INFO_FILE); + if (rename(REBOOT_INFO_FILE, PREVIOUS_REBOOT_INFO_FILE) != 0) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Failed to rename reboot.info: %s\n", strerror(errno)); + } else { + has_reboot_info = true; + } + handle_parodus_reboot_file(&rebootInfo, PREVIOUS_PARODUSREBOOT_INFO_FILE); + } + else { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Deriving reboot reason from legacy sources \n"); + + if (parse_legacy_log(REBOOT_INFO_LOG_FILE, &rebootInfo) != SUCCESS) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","No legacy log found or parse failed, will derive from hardware/panic \n"); + } + + if (rebootInfo.timestamp[0] == '\0') { + get_current_timestamp(rebootInfo.timestamp, sizeof(rebootInfo.timestamp)); + } + + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Detecting kernel panic \n"); + detect_kernel_panic(&ctx, &panicInfo); + + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Checking firmware failures \n"); + check_firmware_failure(&ctx, &fwFailure); + + if (rebootInfo.customReason[0] == '\0' || rebootInfo.source[0] == '\0') { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Getting hardware reboot reason \n"); + get_hardware_reason(&ctx, &hwReason, &rebootInfo); + } else { + memset(&hwReason, 0, sizeof(HardwareReason)); + } + + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Classifying reboot reason \n"); + if (classify_reboot_reason(&rebootInfo, &ctx, &hwReason, &panicInfo, &fwFailure) != SUCCESS) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to classify reboot reason \n"); + ret = ERROR_GENERAL; + goto cleanup; + } + } + + if (!has_reboot_info) { + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Persisting reboot information \n"); + if (write_reboot_info(PREVIOUS_REBOOT_INFO_FILE, &rebootInfo) != SUCCESS) { + RDK_LOG(RDK_LOG_ERROR,"LOG.RDK.REBOOTINFO","Failed to write reboot info \n"); + ret = ERROR_GENERAL; + goto cleanup; + } + update_parodus_log(&rebootInfo); + handle_parodus_reboot_file(&rebootInfo, PREVIOUS_PARODUSREBOOT_INFO_FILE); + if (strstr(rebootInfo.reason, "POWER_ON") || + strstr(rebootInfo.reason, "HARD_POWER") || + strstr(rebootInfo.reason, "UNKNOWN_RESET")) { + write_hardpower(PREVIOUS_HARD_REBOOT_INFO_FILE, rebootInfo.timestamp); + } + } + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Copying keypress info \n"); + copy_keypress_info(KEYPRESS_INFO_FILE, PREVIOUS_KEYPRESS_INFO_FILE); + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Creating invocation flag\n"); + FILE *flag_fp = fopen(UPDATE_REBOOT_INFO_INVOKED_FLAG, "w"); + if (flag_fp) { + fprintf(flag_fp, "1\n"); + fclose(flag_fp); + } + + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Reboot reason processing completed successfully \n"); + + cleanup: + if (lock_acquired) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Releasing lock \n"); + if (release_lock(LOCK_DIR) != SUCCESS) { + RDK_LOG(RDK_LOG_DEBUG,"LOG.RDK.REBOOTINFO","Failed to release lock \n"); + if (ret == SUCCESS) { + ret = ERROR_GENERAL; + } + } + } + + RDK_LOG(RDK_LOG_INFO,"LOG.RDK.REBOOTINFO","Reboot Reason Update completed with status: %d \n", ret); + return ret; +} diff --git a/run_l2.sh b/run_l2.sh index f2356e7..c8278ed 100644 --- a/run_l2.sh +++ b/run_l2.sh @@ -18,13 +18,33 @@ ########################################################################## RESULT_DIR="/tmp/l2_test_report" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +cd "$SCRIPT_DIR" || exit 1 +export PYTHONPATH="$SCRIPT_DIR:$PYTHONPATH" + mkdir -p "$RESULT_DIR" +touch /opt/logs/messages.txt apt-get update && apt-get install -y libjsonrpccpp-dev /usr/local/bin/update-reboot-info & + # Run L2 Test cases -pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/cyclicreboot.json tests/functional_tests/test/test_cyclic_reboot.py -pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/crashmaintainence.json tests/functional_tests/test/test_reboot_crash_maintenance.py -pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/systemcleanup.json tests/functional_tests/test/test_system_cleanup.py pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_reboot_triggered.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_bootup_reboot_files_and_log_created.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_hard_reboot_unknown_defaults_to_null_mapping.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_hard_reboot_updates_hardpower_and_previousreboot.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_hardware_detection_paths_defined.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_kernel_panic_oops_is_logged_in_messages.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_kernel_panic_oops_updates_reboot_files_via_reboot_binary.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_parodus_log_contains_reboot_reason.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_previous_parodus_file_has_software_reboot_info.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_previous_reboot_info_json_format.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_previous_reboot_info_string_in_messages.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_reboot_info_log_previous_fields_present.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_soft_reboot_category_classification_matrix.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_soft_reboot_updates_reboot_log_and_previous_reboot_info.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_update_prev_reboot_generates_previous_files_and_flags.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_update_prev_reboot_service_flow_after_stt_flag.py +pytest --json-report --json-report-summary --json-report-file $RESULT_DIR/rebootTest.json tests/functional_tests/test/test_scenario_update_prev_reboot_skips_when_flags_missing.py diff --git a/scripts/reboot.sh b/scripts/reboot.sh new file mode 100644 index 0000000..2e447de --- /dev/null +++ b/scripts/reboot.sh @@ -0,0 +1,504 @@ +#!/bin/sh +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# 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. +########################################################################## + +. /etc/include.properties +. /etc/device.properties +. /etc/env_setup.sh + +# The reboot cmd is aliased to "reboot -f" in env_setup.sh. Remove force reboot as it causing HDD corruption. +if [ -f /etc/os-release ];then + alias reboot='/sbin/reboot' +fi + +if [ -f /lib/rdk/t2Shared_api.sh ]; then + source /lib/rdk/t2Shared_api.sh +fi + +REBOOT_REASON_LOGFILE="/opt/logs/rebootreason.log" +rebootLog() { + echo "`/bin/timestamp` :$0: $*" >> $REBOOT_REASON_LOGFILE +} + +#Usage: rebootNow.sh [-c "" | -s ""][-r ""][-o ""] +usage() +{ + rebootLog "USAGE: rebootNow.sh -c/-s -r -o " + rebootLog " -s : Source Module or Script Name" + rebootLog " -c : Crash Process Name" + rebootLog " -r : Reason to specify for any particular reason: default=Unknown" + rebootLog " -o : Reason to request for the reboot: default=Unknown" +} + +if [ $# -lt 2 ]; then + rebootLog "Exiting as no argument specified to identify source of reboot!!!" + usage + exit 1 +elif [[ "$3" == "-s" || "$3" == "-c" ]]; then + rebootLog "Exiting as invalid arguments found!!!" + usage + exit 1 +fi + +# Save reboot details in /opt/secure/reboot folder. +REBOOT_INFO_DIR="/opt/secure/reboot" +REBOOT_INFO_FILE="/opt/secure/reboot/reboot.info" +PREVIOUSREBOOT_INFO_FILE="/opt/secure/reboot/previousreboot.info" +PARODUS_REBOOT_INFO_FILE="/opt/secure/reboot/parodusreboot.info" +REBOOTINFO_LOGFILE="/opt/logs/rebootInfo.log" +REBOOTNOW_FLAG="/opt/secure/reboot/rebootNow" +REBOOTSTOP_FLAG="/opt/secure/reboot/rebootStop" +REBOOT_COUNTER="/opt/secure/reboot/rebootCounter" +REBOOTSTOP_DURATION=$(tr181 Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RebootStop.Duration 2>&1 > /dev/null) +REBOOTSTOP_DETECTION=$(tr181 Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RebootStop.Detection 2>&1 > /dev/null) +REBOOT_WINDOW=10 +REBOOT_WINDOW_SECS=$((REBOOT_WINDOW * 60)) +REBOOT_CYCLE_THRESHOLD=5 +CYCLIC_REBOOT_SOURCE="rebootNow.sh" +CYCLIC_REBOOT_REASON="Rebooting device after expiry of Cyclic reboot pause window" +CDLFILE=$(cat /opt/cdl_flashed_file_name) +PREV_CDLFILE=$(cat /tmp/currently_running_image_name) +# Define Reasons for APP_TRIGGERED, OPS_TRIGGERED and MAINTENANCE_TRIGGERED cases +APP_TRIGGERED_REASONS=(Servicemanager systemservice_legacy WarehouseReset WarehouseService HrvInitWHReset HrvColdInitReset HtmlDiagnostics InstallTDK StartTDK TR69Agent SystemServices Bsu_GUI SNMP CVT_CDL Nxserver DRM_Netflix_Initialize hrvinit PaceMFRLibrary) +OPS_TRIGGERED_REASONS=(ScheduledReboot RebootSTB.sh FactoryReset UpgradeReboot_firmwareDwnld.sh UpgradeReboot_restore XFS wait_for_pci0_ready websocketproxyinit NSC_IR_EventReboot host_interface_dma_bus_wait usbhotplug Receiver_MDVRSet Receiver_VidiPath_Enabled Receiver_Toggle_Optimus S04init_ticket Network-Service monitor.sh ecmIpMonitor.sh monitorMfrMgr.sh vlAPI_Caller_Upgrade ImageUpgrade_rmf_osal ImageUpgrade_mfr_api ImageUpgrade_updateNewImage.sh ImageUpgrade_userInitiatedFWDnld.sh ClearSICache tr69hostIfReset hostIf_utils hostifDeviceInfo HAL_SYS_Reboot UpgradeReboot_deviceInitiatedFWDnld.sh UpgradeReboot_rdkvfwupgrader UpgradeReboot_ipdnl.sh PowerMgr_Powerreset PowerMgr_coldFactoryReset DeepSleepMgr PowerMgr_CustomerReset PowerMgr_PersonalityReset Power_Thermmgr PowerMgr_Plat HAL_CDL_notify_mgr_event vldsg_estb_poll_ecm_operational_state BcmIndicateEcmReset SASWatchDog BP3_Provisioning eMMC_FW_UPGRADE BOOTLOADER_UPGRADE cdl_service BCMCommandHandler BRCM_Image_Validate docsis_mode_check.sh tch_nvram.sh Receiver CANARY_Update boot_FSR) +MAINTENANCE_TRIGGERED_REASONS=(AutoReboot.sh PwrMgr) +pid_file="/tmp/.rebootNow.pid" +customReason="Unknown" +otherReason="Unknown" + +touch $REBOOTINFO_LOGFILE + +BIN_REBOOTNOW="/usr/bin/rebootnow" +if [ -x "$BIN_REBOOTNOW" ]; then + rebootLog "Start of rebootNow Binary : $BIN_REBOOTNOW" + "$BIN_REBOOTNOW" "$@" + rc=$? + if [ $rc -eq 0 ]; then + rebootLog "$BIN_REBOOTNOW Execution Successful." + exit 0 + else + rebootLog "Binary $BIN_REBOOTNOW failed with rc=$rc; falling back to script" + fi +else + rebootLog "Binary not found at $BIN_REBOOTNOW; continuing with script" +fi + +rebootLog "Start of rebootNow script" + +pidCleanup() +{ + if [ -f "$pid_file" ];then + rm -rf "$pid_file" + fi +} + +trap pidCleanup EXIT + +# exit if an instance is already running +rebootLog "Checking multiple instance of rebootNow.sh script" +if [ -f $pid_file ];then + pid=`cat $pid_file` + if [ -d /proc/$pid -a -f /proc/$pid/cmdline ]; then + processName=`cat /proc/$pid/cmdline` + currentprocessName=`cat /proc/$$/cmdline` + rebootLog "proc entry process name: $processName and running process name $currentprocessName" + if echo "$processName" | grep -q `basename $0`; then + rebootLog "An instance of "$0" with pid $pid is already running.." + rebootLog "Exiting script!!!" + exit 1 + fi + fi +fi +echo $$ > $pid_file + +while getopts ":s:c:r:o:" opt; do + case $opt in + s) + source=$OPTARG + rebootLogReason="Triggered from $source process" + case $source in + runPodRecovery) + t2CountNotify "SYST_ERR_RunPod_reboot" + ;; + CardNotResponding) + t2CountNotify "SYST_ERR_CCNotRepsonding_reboot" + ;; + *) + t2CountNotify "SYST_ERR_$source" + ;; + esac + ;; + c) + source=$OPTARG + rebootLogReason="Triggered from $source process failure or crash..!" + case $source in + dsMgrMain) + t2CountNotify "SYST_ERR_DSMGR_reboot" + ;; + IARMDaemonMain) + t2CountNotify "SYST_ERR_IARMDEMON_reboot" + ;; + rmfStreamer) + t2CountNotify "SYST_ERR_Rmfstreamer_reboot" + ;; + runPod) + t2CountNotify "SYST_ERR_RunPod_reboot" + ;; + *) + t2CountNotify "SYST_ERR_$source_reboot" + ;; + esac + ;; + r) + customReason=$OPTARG + ;; + o) + otherReason=$OPTARG + ;; + \?) + rebootLog "Invalid option: -$OPTARG" + ;; + esac +done + +rebootLog "Reboot requested on the device from Source:$source Reason:$otherReason" + +# Assign rebootreason from source category. +if [[ "${APP_TRIGGERED_REASONS[@]}" == *"$source"* ]];then + rebootReason="APP_TRIGGERED" + if [ $customReason == "MAINTENANCE_REBOOT" ];then + rebootReason="MAINTENANCE_REBOOT" + fi +elif [[ "${OPS_TRIGGERED_REASONS[@]}" == *"$source"* ]];then + rebootReason="OPS_TRIGGERED" +elif [[ "${MAINTENANCE_TRIGGERED_REASONS[@]}" == *"$source"* ]];then + rebootReason="MAINTENANCE_REBOOT" +else + rebootReason="FIRMWARE_FAILURE" +fi + +NewCronHr=0 +NewCronMin=0 +SECS_THRESHOLD=29 +MINS_THRESHOLD=59 +HOUR_THRESHOLD=23 + +getNewCronTime() { + # date is defined as alias of date -u. Due to this on llama uk delay download is not working. + # So removed alias and use only date for cron job schedule. + now=$(\date +"%T") + rebootLog "current time: $now" + NewCronHr=$(echo $now | cut -d':' -f1) + NewCronMin=$(echo $now | cut -d':' -f2) + Sec=$(echo $now | cut -d':' -f3) + + if [ $Sec -gt $SECS_THRESHOLD ]; then + NewCronMin=`expr $NewCronMin + 1` + fi + + NewCronMin=`expr $NewCronMin + $1` + + while [ $NewCronMin -gt $MINS_THRESHOLD ] + do + NewCronMin=`expr $NewCronMin - 60` + NewCronHr=`expr $NewCronHr + 1` + if [ $NewCronHr -gt $HOUR_THRESHOLD ]; then + NewCronHr=0 + fi + done + + echo "*/$NewCronMin $NewCronHr * * *" +} + +#Add the reboot reason check here +reboot_counter_reset=0 +if [ -f $REBOOTNOW_FLAG ] && [ "$REBOOTSTOP_DETECTION" != "false" ];then + rebootLog "Previous Reboot happened on the device was triggred by RDK software" + rebootLog "Reboot Loop Detection enabled to check cyclic reboot scenarios:$REBOOTSTOP_DETECTION" + rm -rf $REBOOTNOW_FLAG + if [ -f $PREVIOUSREBOOT_INFO_FILE ];then + prev_reboot_source=$(grep '"source"' "$PREVIOUSREBOOT_INFO_FILE" | sed -E 's/.*"source":"([^"]+)".*/\1/') + prev_reboot_reason=$(grep '"reason"' "$PREVIOUSREBOOT_INFO_FILE" | sed -E 's/.*"reason":"([^"]+)".*/\1/') + prev_reboot_customreason=$(grep '"customReason"' "$PREVIOUSREBOOT_INFO_FILE" | sed -E 's/.*"customReason":"([^"]+)".*/\1/') + prev_reboot_otherreason=$(grep '"otherReason"' "$PREVIOUSREBOOT_INFO_FILE" | sed -E 's/.*"otherReason":"([^"]+)".*/\1/') + prev_reboot_time=$(grep '"timestamp"' "$PREVIOUSREBOOT_INFO_FILE" | sed -E 's/.*"timestamp":"([^"]+)".*/\1/') + rebootLog "Previous Reboot Information of the Device:" + rebootLog "Time:$prev_reboot_time" + rebootLog "Source:$prev_reboot_source" + rebootLog "Reason:$prev_reboot_reason" + rebootLog "customReason:$prev_reboot_customreason" + rebootLog "otherReason:$prev_reboot_otherreason" + + current_reboot_source="$source" + current_reboot_reason="$rebootReason" + current_reboot_customreason="$customReason" + current_reboot_otherreason="$otherReason" + current_device_uptime=`cat /proc/uptime | awk '{print $1}' | sed "s/\..*//"` + rebootLog "Device Uptime from last reboot: $current_device_uptime secs" + + rebootLog "Current Requested Reboot Information:" + rebootLog "Source:$current_reboot_source" + rebootLog "Reason:$current_reboot_reason" + rebootLog "customReason:$current_reboot_customreason" + rebootLog "otherReason:$current_reboot_otherreason" + if [[ $current_device_uptime -le "$REBOOT_WINDOW_SECS" ]]; then + rebootLog "Reboot requested before the $REBOOT_WINDOW mins, checking reboot reason" + if [ "$current_reboot_source" == "$prev_reboot_source" ] && [ "$current_reboot_reason" == "$prev_reboot_reason" ] && + [ "$current_reboot_customreason" == "$prev_reboot_customreason" ] && [ "$current_reboot_otherreason" == "$prev_reboot_otherreason" ]; then + rebootLog "Reboot Reason for current and previous reboot is same" + if [ -f $REBOOTSTOP_FLAG ]; then + rebootLog "Reboot Operation Halted in the device to avoid continous reboots with same reason!!!" + touch $REBOOTNOW_FLAG + rebootLog "Exiting without rebooting the device" + exit 0 + else + reboot_count=`cat $REBOOT_COUNTER` + rebootLog "Checking device is stuck in cyclic reboot loop with same reboot reason, Current Iteration:$reboot_count" + if [ $reboot_count -ge $REBOOT_CYCLE_THRESHOLD ];then + rebootLog "Detected Reboot Loop in device, Halting reboot for next $REBOOTSTOP_DURATION mins to perform operations!!!" + touch $REBOOTSTOP_FLAG + tr181 -s -v true Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RebootStop.Enable + rebootstopvalue=`tr181 -g Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RebootStop.Enable 2>&1 > /dev/null` + rebootLog "Publishing Reboot Stop Enable Event:$rebootstopvalue" + t2CountNotify "SYST_ERR_Cyclic_reboot" + # Remove any old Reboot Cron Jobs + sh /lib/rdk/cronjobs_update.sh "remove" "rebootNow.sh" + REBOOT_CRON_INTERVAL_TIME=$(getNewCronTime $REBOOTSTOP_DURATION) + rebootLog "Scheduling Cron for rebootNow.sh as a part of Cyclic reboot operations: $REBOOT_CRON_INTERVAL_TIME" + sh /lib/rdk/cronjobs_update.sh "add" "rebootNow.sh" "$REBOOT_CRON_INTERVAL_TIME /bin/sh /lib/rdk/rebootNow.sh -s \"$CYCLIC_REBOOT_SOURCE\" -o \"$CYCLIC_REBOOT_REASON\"" + rebootLog "Device will reboot in $REBOOTSTOP_DURATION mins after expiry of Cyclic reboot pause window!!!" + touch $REBOOTNOW_FLAG + exit 0 + else + reboot_count=$((reboot_count + 1)) + echo $reboot_count > $REBOOT_COUNTER + fi + fi + else + rebootLog "Reboot requested before the $REBOOT_WINDOW mins reboot loop window with different reason" + reboot_counter_reset=1 + fi + else + rebootLog "Reboot requested after the $REBOOT_WINDOW mins reboot loop window" + reboot_counter_reset=1 + fi + else + rebootLog "$PREVIOUSREBOOT_INFO_FILE file not found, proceed without reboot reason check!!!" + reboot_counter_reset=1 + fi +else + if [ ! -f $REBOOTNOW_FLAG ]; then + rebootLog "Last reboot was not triggered by rebootNow.sh script" + else + rebootLog "Reboot Loop Detection disabled to check cyclic reboot scenarios:$REBOOTSTOP_DETECTION" + fi + reboot_counter_reset=1 +fi + +#Reset cyclic reboot counter +if [ $reboot_counter_reset == 1 ];then + rebootLog "Publishing Reboot Stop Disable Event" + tr181 -s -v false Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.RebootStop.Enable + rebootLog "Reboot Loop Detection counter reset as device not in reboot loop" + echo 0 > $REBOOT_COUNTER + rm -rf $REBOOTSTOP_FLAG +fi + +syncLog() +{ + if [ -d "$TEMP_LOG_PATH" ]; then + rebootLog "${FUNCNAME[0]}: Find and move the logs from $TEMP_LOG_PATH to $LOG_PATH" + cWD=`pwd` + syncPath=`find $TEMP_LOG_PATH -type l -exec ls -l {} \; | cut -d ">" -f2 | tr -d ' '` + if [ "$syncPath" != "$LOG_PATH" ];then + cd "$TEMP_LOG_PATH" + for file in `ls *.txt *.log` + do + cat $file >> $LOG_PATH/$file + cat /dev/null > $file + done + cd $cWD + else + rebootLog "${FUNCNAME[0]}: Sync Not needed, Same log folder" + fi + else + rebootLog "${FUNCNAME[0]}: $TEMP_LOG_PATH not found!!!" + fi +} + +# Save the reboot info with all the fields +setPreviousRebootInfo() +{ + rebootLog "${FUNCNAME[0]}: Saving reboot info in $REBOOT_INFO_FILE file" + timestamp=$1 + source=$2 + reason=$3 + custom=$4 + other=$5 + echo "{" > $REBOOT_INFO_FILE + echo "\"timestamp\":\"$timestamp\"," >> $REBOOT_INFO_FILE + echo "\"source\":\"$source\"," >> $REBOOT_INFO_FILE + echo "\"reason\":\"$reason\"," >> $REBOOT_INFO_FILE + echo "\"customReason\":\"$custom\"," >> $REBOOT_INFO_FILE + echo "\"otherReason\":\"$other\"" >> $REBOOT_INFO_FILE + echo "}" >> $REBOOT_INFO_FILE + + echo "PreviousRebootInfo:$timestamp,$custom,$source,$reason" >> $PARODUS_REBOOT_INFO_FILE + rebootLog "${FUNCNAME[0]}: Updated Reboot Reason information in $REBOOT_INFO_FILE and $PARODUS_REBOOT_INFO_FILE" +} + +# Log reboot information to rebootInfo.log file +rebootTime=`date -u` + +if [ "$otherReason" == "Unknown" ]; then + echo "RebootReason: $rebootLogReason" >> $REBOOTINFO_LOGFILE +else + echo "RebootReason: $rebootLogReason $otherReason" >> $REBOOTINFO_LOGFILE +fi +echo "RebootInitiatedBy: $source" >> $REBOOTINFO_LOGFILE +echo "RebootTime: $rebootTime" >> $REBOOTINFO_LOGFILE +echo "CustomReason: $customReason" >> $REBOOTINFO_LOGFILE +echo "OtherReason: $otherReason" >> $REBOOTINFO_LOGFILE + +# Create /opt/secure/reboot/ folder before reboot/shutdown. +if [ ! -d $REBOOT_INFO_DIR ]; then + rebootLog "Creating $REBOOT_INFO_DIR folder" + mkdir $REBOOT_INFO_DIR +fi + +# Added check for Hal_SYS_reboot source +multipleSource=`grep -E HAL_SYS_Reboot $REBOOTINFO_LOGFILE` +if [ -z "$multipleSource" ];then + rebootSource=$source +else + rebootSource=`grep RebootReason $REBOOTINFO_LOGFILE | grep -v HAL_SYS_Reboot | grep -v grep | awk -F " " '{print $5}'` + otherReason=`grep RebootReason $REBOOTINFO_LOGFILE | grep -v HAL_SYS_Reboot | grep -v grep | awk -F 'HAL_CDL_notify_mgr_event' '{print $NF}' | sed 's/(.*//'` +fi + +# Save reboot details in reboot.info file under /opt/secure/reboot folder +rebootLog "Invoke setPreviousRebootInfo to save reboot information under $REBOOT_INFO_DIR folder" +setPreviousRebootInfo "$rebootTime" "$rebootSource" "$rebootReason" "$customReason" "$otherReason" + +isMmgbleNotifyEnabled=$(tr181 Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.ManageableNotification.Enable 2>&1 > /dev/null) +rebootLog "Value of Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.ManageableNotification.Enable: $isMmgbleNotifyEnabled" +if [ "${isMmgbleNotifyEnabled}" == "true" ];then + tr181 -s -v 10 Device.DeviceInfo.X_RDKCENTRAL-COM_xOpsDeviceMgmt.RPC.RebootPendingNotification +fi + +#### +# All housekeeping before actual device reboot starts from here +#### + +# Signal telemetry2_0 to send out any pending messages before reboot +rebootLog "Signal telemetry2_0 to send out any pending messages before reboot" +killall -s SIGIO telemetry2_0 + +# Kill the Parodus Process; So that it can close the WebSocket Connection with Server. +rebootLog "Properly shutdown parodus by sending SIGUSR1 kill signal" +killall -s SIGUSR1 parodus + +if [ -f /etc/rdm/rdm-manifest.xml ];then + if [[ ${CDLFILE} != *"${PREV_CDLFILE}"* ]]; then + if [ -d /media/apps ];then + rebootLog "Removing the RDM Apps content from Secondary Storage before Reboot (After IMage Upgrade)" + cd /media/apps + if [ $? -eq 0 ];then + for i in `ls -d */` + do + rm -rf $i + sleep 1 + done + fi + sleep 5 + fi + fi +fi + +if [ ! -f $PERSISTENT_PATH/.lightsleepKillSwitchEnable ];then + syncLog + if [ -f $TEMP_LOG_PATH/.systime ];then + cp $TEMP_LOG_PATH/.systime $PERSISTENT_PATH/ + fi +fi + +if [ "$DEVICE_NAME" = "XI6" ] || [ "$DEVICE_NAME" = "XiOne" ] || [ "$DEVICE_NAME" = "XiOne-SCB" ];then + if [ "$DEVICE_NAME" = "XI6" ];then + # Get eMMC Health report + if [ -f /lib/rdk/emmc_health_diag.sh ];then + sh /lib/rdk/emmc_health_diag.sh "reboot" + rebootLog "Updated eMMC Health report" + fi + fi + # See if we need to Upgrade the eMMC FW + if [ -f /lib/rdk/eMMC_Upgrade.sh ];then + rebootLog "Upgrade eMMC FW if required" + sh /lib/rdk/eMMC_Upgrade.sh + fi +fi + +if [ -f /lib/rdk/aps4_reset.sh ];then + rebootLog "Executing /lib/rdk/aps4_reset.sh" + sh /lib/rdk/aps4_reset.sh +fi + +if [ -f /lib/rdk/update_www-backup.sh ];then + rebootLog "Executing /lib/rdk/update_www-backup.sh" + sh /lib/rdk/update_www-backup.sh +fi + +#If bluetooth is enabled, gracefully shutdown the bluetooth related services +if [ "$BLUETOOTH_ENABLED" = "true" ];then + services=("sky-bluetoothrcu" "btmgr" "bluetooth" "bt-hciuart" "btmac-preset" "bt" "syslog-ng") + rebootLog "Shutting down the bluetooth and syslog-ng services gracefully" + for service in "${services[@]}"; do + rebootLog "Shutting down the $service service" + if /bin/systemctl --quiet is-active "$service"; then + /bin/systemctl stop "$service" + if [[ $? -eq 0 ]]; then + rebootLog "$service service stopped successfully" + else + rebootLog "Failed to stop $service service" + fi + else + rebootLog "$service service is not active" + fi + done +fi + +rebootLog "Start the sync" +sync +rebootLog "End of the sync" + +rebootLog "Creating $REBOOTNOW_FLAG as the reboot was triggred by RDK software" +touch $REBOOTNOW_FLAG +rm -rf $pid_file + +rebootLog "Rebooting the Device Now" +reboot & +REBOOT_PID=$! + +sleep 90 +rebootLog "System still running after reboot command, Reboot Failed for $REBOOT_PID..." +systemctl reboot +if [ $? -eq 1 ]; then + rebootLog "Reboot failed due to systemctl hang or connection timeout" +fi +kill $REBOOT_PID 2>/dev/null +rebootLog "Triggering force Reboot after standard soft reboot failure" +reboot -f diff --git a/scripts/update_previous_reboot_info.sh b/scripts/update_previous_reboot_info.sh new file mode 100644 index 0000000..0de94d0 --- /dev/null +++ b/scripts/update_previous_reboot_info.sh @@ -0,0 +1,487 @@ +#!/bin/sh +############################################################################## +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2020 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. +############################################################################## + +# Source Variable +. /etc/device.properties +if [ -f /lib/rdk/t2Shared_api.sh ]; then + source /lib/rdk/t2Shared_api.sh +fi + +# Define logfiles and flags +REBOOT_INFO_LOG_FILE="/opt/logs/rebootInfo.log" +KERNEL_LOG_FILE="/opt/logs/messages.txt" +UIMGR_LOG_FILE="/opt/logs/PreviousLogs/uimgr_log.txt" +OCAPRI_LOG_FILE="/opt/logs/PreviousLogs/ocapri_log.txt" +ECM_CRASH_LOG_FILE="/opt/logs/PreviousLogs/messages-ecm.txt" +PSTORE_CONSOLE_LOG_FILE="/sys/fs/pstore/console-ramoops-0" +KERNEL_PANIC_SEARCH_STRING="PREVIOUS_KERNEL_OOPS_DUMP" +KERNEL_PANIC_SEARCH_STRING_01="Kernel panic - not syncing" +KERNEL_PANIC_SEARCH_STRING_02="Oops - undefined instruction" +KERNEL_PANIC_SEARCH_STRING_03="Oops - bad syscall" +KERNEL_PANIC_SEARCH_STRING_04="branch through zero" +KERNEL_PANIC_SEARCH_STRING_05="unknown data abort code" +KERNEL_PANIC_SEARCH_STRING_06="Illegal memory access" +ECM_CRASH_SEARCH_STRING="\*\*\*\* CRASH \*\*\*\*" +MAX_REBOOT_STRING="Box has rebooted 10 times" +POWERON_STRING="POWER_ON" +REBOOT_INFO_DIR="/opt/secure/reboot" +REBOOT_INFO_FILE="/opt/secure/reboot/reboot.info" +PARODUS_REBOOT_INFO_FILE="/opt/secure/reboot/parodusreboot.info" +KEYPRESS_INFO_FILE="/opt/secure/reboot/keypress.info" +PREVIOUS_REBOOT_INFO_FILE="/opt/secure/reboot/previousreboot.info" +PREVIOUS_PARODUSREBOOT_INFO_FILE="/opt/secure/reboot/previousparodusreboot.info" +PREVIOUS_HARD_REBOOT_INFO_FILE="/opt/secure/reboot/hardpower.info" +PREVIOUS_KEYPRESS_INFO_FILE="/opt/secure/reboot/previouskeypress.info" +STT_FLAG="/tmp/stt_received" +REBOOT_INFO_FLAG="/tmp/rebootInfo_Updated" +UPDATE_REBOOT_INFO_INVOKED_FLAG="/tmp/Update_rebootInfo_invoked" +LOCK_DIR="/tmp/rebootInfo.lock" +PARODUS_LOG="/opt/logs/parodus.log" + +#Use log framework to pring timestamp and source script name +rebootLog() +{ + echo "$0: $*" +} + +rebootLog "Start of Reboot Reason Script" + +# Define Reasons for APP_TRIGGERED, OPS_TRIGGERED and MAINTENANCE_TRIGGERED cases +APP_TRIGGERED_REASONS=(Servicemanager systemservice_legacy WarehouseReset WarehouseService HrvInitWHReset HrvColdInitReset HtmlDiagnostics InstallTDK StartTDK TR69Agent SystemServices Bsu_GUI SNMP CVT_CDL Nxserver DRM_Netflix_Initialize hrvinit ) +OPS_TRIGGERED_REASONS=(ScheduledReboot RebootSTB.sh FactoryReset UpgradeReboot_restore XFS wait_for_pci0_ready websocketproxyinit NSC_IR_EventReboot host_interface_dma_bus_wait usbhotplug Receiver_MDVRSet Receiver_VidiPath_Enabled Receiver_Toggle_Optimus S04init_ticket Network-Service monitorMfrMgr.sh vlAPI_Caller_Upgrade ImageUpgrade_rmf_osal ImageUpgrade_mfr_api ImageUpgrade_userInitiatedFWDnld.sh ClearSICache tr69hostIfReset hostIf_utils hostifDeviceInfo HAL_SYS_Reboot UpgradeReboot_deviceInitiatedFWDnld.sh UpgradeReboot_rdkvfwupgrader UpgradeReboot_ipdnl.sh PowerMgr_Powerreset PowerMgr_coldFactoryReset DeepSleepMgr PowerMgr_CustomerReset PowerMgr_PersonalityReset Power_Thermmgr PowerMgr_Plat HAL_CDL_notify_mgr_event vldsg_estb_poll_ecm_operational_state SASWatchDog BP3_Provisioning eMMC_FW_UPGRADE BOOTLOADER_UPGRADE cdl_service docsis_mode_check.sh Receiver CANARY_Update) +MAINTENANCE_TRIGGERED_REASONS=(AutoReboot.sh PwrMgr) + +# Save the reboot info with all the fields +setPreviousRebootInfo() +{ + # Set Previous reboot info file with received reboot reason. + timestamp=$1 + source=$2 + reason=$3 + custom=$4 + other=$5 + # Get Previous Hard Power reset information + if [ ! -z $PREVIOUS_REBOOT_INFO_FILE ]; then + rebootLog "Reboot reason present in $PREVIOUS_REBOOT_INFO_FILE file:" + cat $PREVIOUS_REBOOT_INFO_FILE + fi + rebootLog "Updating Reboot reason in $PREVIOUS_REBOOT_INFO_FILE file" + echo "{" > $PREVIOUS_REBOOT_INFO_FILE + echo "\"timestamp\":\"$timestamp\"," >> $PREVIOUS_REBOOT_INFO_FILE + echo "\"source\":\"$source\"," >> $PREVIOUS_REBOOT_INFO_FILE + echo "\"reason\":\"$reason\"," >> $PREVIOUS_REBOOT_INFO_FILE + echo "\"customReason\":\"$custom\"," >> $PREVIOUS_REBOOT_INFO_FILE + echo "\"otherReason\":\"$other\"" >> $PREVIOUS_REBOOT_INFO_FILE + echo "}" >> $PREVIOUS_REBOOT_INFO_FILE + + rebootLog "Updating previous reboot reason in $PARODUS_LOG" + existing_reboot_info=$(grep "PreviousRebootInfo" "$PARODUS_LOG" 2>/dev/null | tail -1) + + if [ -z "$existing_reboot_info" ]; then + echo "$(/bin/timestamp): $0: Updating previous reboot info to Parodus" >> "$PARODUS_LOG" + echo "$(/bin/timestamp): $0: PreviousRebootInfo:$timestamp,$reason,$custom,$source" >> "$PARODUS_LOG" + rebootLog "Reboot Information Updated to parodus log:$timestamp,$reason,$custom,$source" + else + rebootLog "${FUNCNAME[0]}: Reboot info already present in $PARODUS_LOG as $existing_reboot_info, skipping update" + fi + + # Set Hard Power reset time with timestamp + if [ "$reason" == "HARD_POWER" ] || [ "$reason" == "POWER_ON_RESET" ] || [ "$reason" == "UNKNOWN_RESET" ] || [ ! -f "$PREVIOUS_HARD_REBOOT_INFO_FILE" ];then + echo "{" > $PREVIOUS_HARD_REBOOT_INFO_FILE + echo "\"lastHardPowerReset\":\"$timestamp\"" >> $PREVIOUS_HARD_REBOOT_INFO_FILE + echo "}" >> $PREVIOUS_HARD_REBOOT_INFO_FILE + rebootLog "Updated Hard Power Reboot Time stamp" + fi + + rebootLog "Updated Previous Reboot Reason information" +} + +# Check for Firmware Failure +fwFailureCheck() +{ + fw_failure=0 + rebootLog "Checking firmware failure cases (ECM Crash, Max Reboot etc)..." + + if [ "$DEVICE_TYPE" != "mediaclient" ]; then + if [ -f "$OCAPRI_LOG_FILE" ] && [[ $(grep "$MAX_REBOOT_STRING" $OCAPRI_LOG_FILE) ]]; then + fw_failure=1 + rebootInitiatedBy="OcapRI" + otherReason="Reboot due to STB reached maximum (10) reboots" + t2CountNotify "SYST_ERR_10Times_reboot" + fi + + if [ -f "$ECM_CRASH_LOG_FILE" ] && [[ $(grep "$ECM_CRASH_SEARCH_STRING" $ECM_CRASH_LOG_FILE) ]]; then + fw_failure=1 + rebootInitiatedBy="EcmLogger" + otherReason="Reboot due to ecm logger crash" + fi + else + if [ -f "$UIMGR_LOG_FILE" ] && [[ $(grep "$MAX_REBOOT_STRING" $UIMGR_LOG_FILE) ]]; then + fw_failure=1 + rebootInitiatedBy="UiMgr" + otherReason="Reboot due to STB reached maximum (10) reboots" + t2CountNotify "SYST_ERR_10Times_reboot" + fi + fi + + return $fw_failure +} + +# Check for OOPS DUMP string in Kernel panic scenarios +oopsDumpCheck() +{ + oops_dump=0 + + if [ "$SOC" = "BRCM" ]; then + # Ensure OOPS DUMP string presence for Kernel Panic in messages.txt + if [ -f "$KERNEL_LOG_FILE" ] && [[ $(grep $KERNEL_PANIC_SEARCH_STRING $KERNEL_LOG_FILE) ]];then + if [[ $(grep -e "Kernel Oops" -e "Kernel Panic" $KERNEL_LOG_FILE) ]];then + oops_dump=1 + fi + fi + elif [ "$RDK_PROFILE" = "TV" ]; then + #Check KERNEL PANIC, OOPS DUMP string presence for Kernel Panic in /sys/fs/pstore/console-ramoops-0 + if [ -f "$PSTORE_CONSOLE_LOG_FILE" ]; then + if [[ $(grep "$KERNEL_PANIC_SEARCH_STRING_01" $PSTORE_CONSOLE_LOG_FILE) ]];then + oops_dump=1 + elif [[ $(grep "$KERNEL_PANIC_SEARCH_STRING_02" $PSTORE_CONSOLE_LOG_FILE) ]];then + oops_dump=1 + elif [[ $(grep "$KERNEL_PANIC_SEARCH_STRING_03" $PSTORE_CONSOLE_LOG_FILE) ]];then + oops_dump=1 + elif [[ $(grep "$KERNEL_PANIC_SEARCH_STRING_04" $PSTORE_CONSOLE_LOG_FILE) ]];then + oops_dump=1 + elif [[ $(grep "$KERNEL_PANIC_SEARCH_STRING_05" $PSTORE_CONSOLE_LOG_FILE) ]];then + oops_dump=1 + elif [[ $(grep "$KERNEL_PANIC_SEARCH_STRING_06" $PSTORE_CONSOLE_LOG_FILE) ]];then + oops_dump=1 + else + oops_dump=0 + fi + + if [ "$oops_dump" -eq "1" ];then + for pstorefile in /sys/fs/pstore/* + do + filename=$(basename "${pstorefile}") + cat $pstorefile > /opt/logs/${filename}.log + done + rebootLog "Please check pstore logs(/sys/fs/pstore/*) for more detail about kerenl panic !" + fi + fi + fi + + return $oops_dump +} + + +#Read the HW Register value of PreviousRebootReason and set details. +setRebootReason() +{ + rebootReason=$1 + case $rebootReason in + SOFTWARE|SOFTWARE_MASTER_RESET) + rebootInitiatedBy="SoftwareReboot" + rebootReason="SOFTWARE_MASTER_RESET" + t2CountNotify "Test_SWReset" + otherReason="Reboot due to user triggered reboot command" + ;; + WATCHDOG|WATCHDOG_TIMER_RESET) + rebootInitiatedBy="WatchDog" + rebootReason="WATCHDOG_TIMER_RESET" + otherReason="Reboot due to watch dog timer reset" + ;; + KERNEL_PANIC_RESET) + rebootInitiatedBy="Kernel" + rebootReason="KERNEL_PANIC" + otherReason="Reboot due to Kernel Panic captured by Oops Dump" + ;; + HARDWARE|POWER_ON_RESET) + rebootInitiatedBy="PowerOn" + rebootReason="POWER_ON_RESET" + otherReason="Reboot due to unplug of power cable from the STB" + ;; + MAIN_CHIP_INPUT_RESET) + rebootInitiatedBy="Main Chip" + rebootReason="MAIN_CHIP_INPUT_RESET" + otherReason="Reboot due to chip's main reset input has been asserted" + ;; + MAIN_CHIP_RESET_INPUT) + rebootInitiatedBy="Main Chip" + rebootReason="MAIN_CHIP_RESET_INPUT" + otherReason="Reboot due to chip's main reset input has been asserted" + ;; + TAP_IN_SYSTEM_RESET) + rebootInitiatedBy="Tap-In System" + rebootReason="TAP_IN_SYSTEM_RESET" + otherReason="Reboot due to the chip's TAP in-system reset has been asserted" + ;; + FRONT_PANEL_4SEC_RESET) + rebootInitiatedBy="FrontPanel Button" + rebootReason="FRONT_PANEL_RESET" + otherReason="Reboot due to the front panel 4 second reset has been asserted" + ;; + S3_WAKEUP_RESET) + rebootInitiatedBy="Standby Wakeup" + rebootReason="S3_WAKEUP_RESET" + otherReason="Reboot due to the chip woke up from deep standby" + ;; + SMARTCARD_INSERT_RESET) + rebootInitiatedBy="SmartCard Insert" + rebootReason="SMARTCARD_INSERT_RESET" + otherReason="Reboot due to the smartcard insert reset has occurred" + ;; + OVERHEAT|OVERTEMP_RESET) + rebootInitiatedBy="OverTemperature" + rebootReason="OVERTEMP_RESET" + otherReason="Reboot due to chip temperature is above threshold (125*C)" + ;; + OVERVOLTAGE_1_RESET|OVERVOLTAGE_RESET) + rebootInitiatedBy="OverVoltage" + rebootReason="OVERVOLTAGE_RESET" + otherReason="Reboot due to chip voltage is above threshold" + ;; + PCIE_1_HOT_BOOT_RESET|PCIE_0_HOT_BOOT_RESET) + rebootInitiatedBy="PCIE Boot" + rebootReason="PCIE_HOT_BOOT_RESET" + otherReason="Reboot due to PCIe hot boot reset has occurred" + ;; + UNDERVOLTAGE_1_RESET|UNDERVOLTAGE_0_RESET|UNDERVOLTAGE_RESET) + rebootInitiatedBy="LowVoltage" + rebootReason="UNDERVOLTAGE_RESET" + otherReason="Reboot due to chip voltage is below threshold" + ;; + SECURITY_MASTER_RESET) + rebootInitiatedBy="SecutiryReboot" + rebootReason="SECURITY_MASTER_RESET" + otherReason="Reboot due to security master reset has occurred" + ;; + CPU_EJTAG_RESET) + rebootInitiatedBy="CPU EJTAG" + rebootReason="CPU_EJTAG_RESET" + otherReason="Reboot due to CPU EJTAG reset has occurred" + ;; + SCPU_EJTAG_RESET) + rebootInitiatedBy="SCPU EJTAG" + rebootReason="SCPU_EJTAG_RESET" + otherReason="Reboot due to SCPU EJTAG reset has occurred" + ;; + GEN_WATCHDOG_1_RESET) + rebootInitiatedBy="GEN WatchDog" + rebootReason="GEN_WATCHDOG_RESET" + otherReason="Reboot due to gen_watchdog_1 timeout reset has occurred" + ;; + AUX_CHIP_EDGE_RESET_0|AUX_CHIP_EDGE_RESET_1) + rebootInitiatedBy="Aux Chip Edge" + rebootReason="AUX_CHIP_EDGE_RESET" + otherReason="Reboot due to the auxiliary edge-triggered chip reset has occurred" + ;; + AUX_CHIP_LEVEL_RESET_0|AUX_CHIP_LEVEL_RESET_1) + rebootInitiatedBy="Aux Chip Level" + rebootReason="AUX_CHIP_LEVEL_RESET" + otherReason="Reboot due to the auxiliary level-triggered chip reset has occurred" + ;; + MPM_RESET) + rebootInitiatedBy="MPM" + rebootReason="MPM_RESET" + otherReason="Reboot due to the MPM reset has occurred" + ;; + *) + rebootInitiatedBy="Hard Power" + rebootReason="$rebootReason" + otherReason="Reboot due to $rebootReason" + ;; + esac +} + +#Perform locking of script execution to avoid parallel execution +lock() +{ + while ! mkdir "$LOCK_DIR" &> /dev/null;do + rebootLog "Waiting for rebootInfo lock" + sleep 5 + done + rebootLog "Acquired rebootInfo lock" +} + +#Unlock before exiting the script +unlock() +{ + rm -rf "$LOCK_DIR" + rebootLog "Releasing rebootInfo lock" +} + +#Check for STT and Reboot Checker flag before updating reboot reason +CheckSTT() +{ + rebootLog "Checking ${STT_FLAG} and ${REBOOT_INFO_FLAG} flag to update the reboot reason" + if [ ! -f "${STT_FLAG}" ] || [ ! -f "${REBOOT_INFO_FLAG}" ];then + rebootLog "Exiting since ${STT_FLAG} or ${REBOOT_INFO_FLAG} flag is not available" + unlock + rebootLog "End of Reboot Reason Script" + exit 0 + fi +} + +#Fucntion to update reboot reason when hardware register is empty +exitforNullrebootreason() +{ + rebootInitiatedBy="Hard Power Reset" + customReason="Hardware Register - NULL" + otherReason="No information found" + rebootReason="HARD_POWER" + setPreviousRebootInfo "$rebootTime" "$rebootInitiatedBy" "$rebootReason" "$customReason" "$otherReason" + unlock + rebootLog "End of Reboot Reason Script" + exit 0 +} + + + +############################## +########## Main APP ########## +############################## + +lock + +#check for first time invocation flag and proceed for script execution + + CheckSTT + +#Creating reboot folder in /opt/secure/ path +if [ ! -d $REBOOT_INFO_DIR ]; then + rebootLog "Creating $REBOOT_INFO_DIR folder..." + mkdir $REBOOT_INFO_DIR +fi + +if [ "$RDK_PROFILE" = "TV" ]; then + # Check to see if we already have the entry in the log + prevReboot=`grep "PreviousRebootReason" $KERNEL_LOG_FILE` + if [ -z "$prevReboot" ]; then + oopsDumpCheck + kernel_crash=$? + if [ $kernel_crash -eq 1 ];then + echo "`/bin/timestamp` PreviousRebootReason: kernel_panic!" >> $KERNEL_LOG_FILE + else + if [ -f /lib/rdk/get-reboot-reason.sh ]; then + sh /lib/rdk/get-reboot-reason.sh >> $KERNEL_LOG_FILE + fi + fi + fi +fi + +# Use current time to report the kernel crash and hard power reset +rebootTimestamp=`date -u` + +# Read and Move /opt/secure/reboot/reboot.info as /opt/secure/reboot/previousreboot.info +if [ -f "$REBOOT_INFO_FILE" ];then + rebootLog "New $REBOOT_INFO_FILE file found, Creating previous reboot info file..." + cat $REBOOT_INFO_FILE + mv $REBOOT_INFO_FILE $PREVIOUS_REBOOT_INFO_FILE + if [ -f "$PARODUS_REBOOT_INFO_FILE" ];then + rebootLog "New $PARODUS_REBOOT_INFO_FILE file found, updating parodus logfile..." + rebootinfo=`cat $PARODUS_REBOOT_INFO_FILE` + echo "`/bin/timestamp` $0: $rebootinfo" >> $PARODUS_LOG + mv $PARODUS_REBOOT_INFO_FILE $PREVIOUS_PARODUSREBOOT_INFO_FILE + fi +else + rebootLog "$REBOOT_INFO_FILE file not found, Assigning default values..." + # Set following variables to NULL before using them + rebootInitiatedBy="" + rebootTime="" + customReason="" + otherReason="" + + # Reading the previous reboot details from /opt/logs/rebootInfo.log + if [ -f "$REBOOT_INFO_LOG_FILE" ];then + rebootLog "$REBOOT_INFO_LOG_FILE logfile found, Fetching source, time and other reasons" + # Parse Previous reboot Info and remove leading space + rebootInitiatedBy=`grep "PreviousRebootInitiatedBy:" $REBOOT_INFO_LOG_FILE | grep -v grep | awk -F "PreviousRebootInitiatedBy:" '{print $2}' | sed 's/^ *//'` + rebootTime=`grep "PreviousRebootTime:" $REBOOT_INFO_LOG_FILE | grep -v grep | awk -F 'PreviousRebootTime:' '{print $2}' | sed 's/^ *//'` + customReason=`grep "PreviousCustomReason:" $REBOOT_INFO_LOG_FILE | grep -v grep | awk -F "PreviousCustomReason:" '{print $2}' | sed 's/^ *//'` + otherReason=`grep "PreviousOtherReason:" $REBOOT_INFO_LOG_FILE | grep -v grep | awk -F 'PreviousOtherReason:' '{print $2}' | sed 's/^ *//'` + fi + + rebootLog "Validating reboot information received from $REBOOT_INFO_LOG_FILE..." + if [ "x$rebootInitiatedBy" == "x" ];then + rebootLog "$REBOOT_INFO_LOG_FILE file not found and Value of rebootInitiatedBy=$rebootInitiatedBy is empty" + rebootTime="$rebootTimestamp" + # Check for Kernel Panic Reboot + rebootLog "Checking for OOPS DUMP for Kernel Panic..." + oopsDumpCheck + kernel_crash=$? + if [ $kernel_crash -eq 1 ];then + rebootReason="KERNEL_PANIC" + rebootInitiatedBy="Kernel" + customReason="Hardware Register - KERNEL_PANIC" + otherReason="Reboot due to Kernel Panic captured by Oops Dump" + else + # Reading hard reset values from sysfs + rebootLog " Hard Power Reboot info is missing!!!" + exitforNullrebootreason + fi + else + rebootLog "$REBOOT_INFO_LOG_FILE logfile found and received source of rebootInitiatedBy=$rebootInitiatedBy" + # Assign reboot reason by comparing the rebootInitiatedBy with APP_TRIGGERED_REASONS/OPS_TRIGGERED_REASONS/MAINTENANCE_TRIGGERED_REASONS + if [[ "${APP_TRIGGERED_REASONS[@]}" == *"$rebootInitiatedBy"* ]];then + rebootReason="APP_TRIGGERED" + # Assign reboot reason as MAINTENANCE_REBOOT if customReason is passed as MAINTENANCE_REBOOT + if [ $customReason == "MAINTENANCE_REBOOT" ];then + rebootReason="MAINTENANCE_REBOOT" + fi + elif [[ "${OPS_TRIGGERED_REASONS[@]}" == *"$rebootInitiatedBy"* ]];then + rebootReason="OPS_TRIGGERED" + elif [[ "${MAINTENANCE_TRIGGERED_REASONS[@]}" == *"$rebootInitiatedBy"* ]];then + rebootReason="MAINTENANCE_REBOOT" + else + rebootReason="FIRMWARE_FAILURE" + fi + fi + + # FIRMWARE FAILURE is of high priority for all the STB platforms and not applicable for TV platforms + # We have to report it before updating any soft/hard reboot scenarios + # Check for FIRMWARE FAILURE cases (ECM Crash, Max reboot etc) for STB platforms + + #Update reboot information in /opt/secure/reboot/previousreboot.info file + setPreviousRebootInfo "$rebootTime" "$rebootInitiatedBy" "$rebootReason" "$customReason" "$otherReason" +fi + + # if we didn't any reboot reason yet. + if [ -z "$prevrebootreason" ]; then + rebootLog "Get reboot reason from get-reboot-reason.sh..." + if [ -f /lib/rdk/get-reboot-reason.sh ]; then + sh /lib/rdk/get-reboot-reason.sh >> $KERNEL_LOG_FILE + fi + fi +fi + +# Keypress information +if [ -f "$KEYPRESS_INFO_FILE" ]; then + cp -f $KEYPRESS_INFO_FILE $PREVIOUS_KEYPRESS_INFO_FILE + rebootLog "Updated previous keypress info" +else + rebootLog "Unable to find the $KEYPRESS_INFO_FILE file" +fi + +# Create flag to ensure updatePreviousRebootInfo.sh script is invoked +if [ ! -f "$UPDATE_REBOOT_INFO_INVOKED_FLAG" ];then + touch $UPDATE_REBOOT_INFO_INVOKED_FLAG +fi + +rebootLog "End of Reboot Reason Script" +unlock diff --git a/services/update-reboot-info.path b/services/update-reboot-info.path new file mode 100644 index 0000000..af20b1c --- /dev/null +++ b/services/update-reboot-info.path @@ -0,0 +1,29 @@ +############################################################################## +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2020 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. +############################################################################## + +[Unit] +Description=Update Previous reboot info path +OnFailure=path-fail-notifier@%n.service + +[Path] +PathChanged=/tmp/stt_received +Unit=update-reboot-info.service + +[Install] +WantedBy=multi-user.target diff --git a/services/update-reboot-info.service b/services/update-reboot-info.service new file mode 100644 index 0000000..80cefe6 --- /dev/null +++ b/services/update-reboot-info.service @@ -0,0 +1,24 @@ +############################################################################## +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2020 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. +############################################################################## +[Unit] +Description=Update Previous reboot info +ConditionPathExists=/tmp/stt_received + +[Service] +ExecStart=/lib/rdk/update_previous_reboot_info.sh diff --git a/tests/functional_tests/features/test_reboot_triggered.feature b/tests/functional_tests/features/test_scenario_bootup_reboot_files_and_log_created.feature similarity index 58% rename from tests/functional_tests/features/test_reboot_triggered.feature rename to tests/functional_tests/features/test_scenario_bootup_reboot_files_and_log_created.feature index f8e9424..c5d28af 100644 --- a/tests/functional_tests/features/test_reboot_triggered.feature +++ b/tests/functional_tests/features/test_scenario_bootup_reboot_files_and_log_created.feature @@ -17,13 +17,13 @@ # limitations under the License. ########################################################################## -Feature: App-triggered reboot artifact verification +Feature: Verify base reboot artifacts are created at bootup-triggered reboot flow + + Scenario: Verify reboot info files and log are created after a bootup-triggered reboot + Given the reboot binary is available + When a reboot is triggered with a valid source and reason + Then the file /opt/secure/reboot/reboot.info should exist + And the file /opt/secure/reboot/parodusreboot.info should exist + And the file /opt/logs/rebootInfo.log should exist + And the rebootInfo.log should contain the string "RebootReason:" - Scenario: Ensure reboot metadata and flags are created for app-triggered reboot - Given prior reboot artifacts are removed - When reboot-manager runs with source HtmlDiagnostics - Then /opt/secure/reboot/reboot.info should be created with source HtmlDiagnostics - And the reason in reboot.info should be APP_TRIGGERED - And /opt/secure/reboot/parodusreboot.info should contain a PreviousRebootInfo entry - And /opt/logs/rebootInfo.log should include a RebootReason line - And /opt/secure/reboot/rebootNow should exist diff --git a/tests/functional_tests/features/test_scenario_hard_reboot_unknown_defaults_to_null_mapping.feature b/tests/functional_tests/features/test_scenario_hard_reboot_unknown_defaults_to_null_mapping.feature new file mode 100644 index 0000000..b955393 --- /dev/null +++ b/tests/functional_tests/features/test_scenario_hard_reboot_unknown_defaults_to_null_mapping.feature @@ -0,0 +1,30 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# 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. +########################################################################## + +Feature: Validate hard reboot unknown fallback mapping when no hardware category is available + + Scenario: Verify previousreboot.info defaults to NULL mapping when hardware reason is unknown + Given the update-prev-reboot-info binary is available + And no valid hardware reset reason is available from the platform + When the updater binary is executed + Then the previousreboot.info should contain source "Hard Power Reset" + And the previousreboot.info should contain customReason "Hardware Register - NULL" + And the previousreboot.info should contain otherReason "No information found" + And the previousreboot.info should contain reason "HARD_POWER" + diff --git a/tests/functional_tests/features/test_scenario_hard_reboot_updates_hardpower_and_previousreboot.feature b/tests/functional_tests/features/test_scenario_hard_reboot_updates_hardpower_and_previousreboot.feature new file mode 100644 index 0000000..52f0562 --- /dev/null +++ b/tests/functional_tests/features/test_scenario_hard_reboot_updates_hardpower_and_previousreboot.feature @@ -0,0 +1,28 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# 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. +########################################################################## + +Feature: Validate hard reboot scenario produces hardpower and previous reboot files + + Scenario: Verify hardpower.info and previousreboot.info are created after a hard reboot + Given the reboot binary and update-prev-reboot-info binary are available + When a hard reboot is triggered with source "PowerOn" and reason "HARDWARE" + And the updater binary is executed + Then the file /opt/secure/reboot/hardpower.info should exist + And the file /opt/secure/reboot/previousreboot.info should exist + diff --git a/tests/functional_tests/features/test_scenario_hardware_detection_paths_defined.feature b/tests/functional_tests/features/test_scenario_hardware_detection_paths_defined.feature new file mode 100644 index 0000000..b5c19ba --- /dev/null +++ b/tests/functional_tests/features/test_scenario_hardware_detection_paths_defined.feature @@ -0,0 +1,27 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# 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. +########################################################################## + +Feature: Confirm platform hard reboot detection path constants are present in the header + + Scenario: Verify hardware detection path constants are defined in update-reboot-info.h + Given the update-reboot-info header file is available + Then the header should define AMLOGIC_SYSFS_FILE as "/sys/devices/platform/aml_pm/reset_reason" + And the header should define BRCM_REBOOT_FILE as "/proc/brcm/previous_reboot_reason" + And the header should define RTK_REBOOT_FILE as "/proc/cmdline" + diff --git a/tests/functional_tests/features/test_pid_guard.feature b/tests/functional_tests/features/test_scenario_kernel_panic_oops_is_logged_in_messages.feature similarity index 64% rename from tests/functional_tests/features/test_pid_guard.feature rename to tests/functional_tests/features/test_scenario_kernel_panic_oops_is_logged_in_messages.feature index 0d7bda1..2aa1d51 100644 --- a/tests/functional_tests/features/test_pid_guard.feature +++ b/tests/functional_tests/features/test_scenario_kernel_panic_oops_is_logged_in_messages.feature @@ -17,12 +17,11 @@ # limitations under the License. ########################################################################## -Feature: PID guard handling +Feature: Verify kernel panic evidence appears in messages log + + Scenario: Verify messages.txt is updated with OOPS or Kernel panic string when kernel panic is triggered + Given the reboot binary and update-prev-reboot-info binary are available + When a kernel panic reboot is triggered with source "kernel-panic" and a panic message + And the updater binary is executed + Then the file /opt/logs/messages.txt should contain "OOPS" or "Kernel panic" - Scenario: Ensure PID guard file is maintained across consecutive runs - Given the PID guard file /tmp/.rebootNow.pid does not exist - When reboot-manager is executed for the first time - Then the /tmp/.rebootNow.pid file should be created - When reboot-manager is executed again with the same trigger source - Then the second execution should succeed - And the /tmp/.rebootNow.pid file should still exist diff --git a/tests/functional_tests/features/test_scenario_kernel_panic_oops_updates_reboot_files_via_reboot_binary.feature b/tests/functional_tests/features/test_scenario_kernel_panic_oops_updates_reboot_files_via_reboot_binary.feature new file mode 100644 index 0000000..19c1bdf --- /dev/null +++ b/tests/functional_tests/features/test_scenario_kernel_panic_oops_updates_reboot_files_via_reboot_binary.feature @@ -0,0 +1,30 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# 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. +########################################################################## + +Feature: Validate kernel panic path through reboot binary updates reboot files + + Scenario: Verify reboot files are updated correctly when a kernel panic is triggered via the reboot binary + Given the reboot binary is available + When a kernel panic reboot is triggered with source "kernel-panic" and a panic message + Then the file /opt/secure/reboot/reboot.info should exist + And the file /opt/secure/reboot/parodusreboot.info should exist + And the previousreboot.info should contain source "kernel-panic" + And the previousreboot.info should contain reason "FIRMWARE_FAILURE" + And the previousreboot.info otherReason should include the panic message + diff --git a/tests/functional_tests/features/test_reboot_crash_maintenance.feature b/tests/functional_tests/features/test_scenario_parodus_log_contains_reboot_reason.feature similarity index 65% rename from tests/functional_tests/features/test_reboot_crash_maintenance.feature rename to tests/functional_tests/features/test_scenario_parodus_log_contains_reboot_reason.feature index b5c38a2..ce938bb 100644 --- a/tests/functional_tests/features/test_reboot_crash_maintenance.feature +++ b/tests/functional_tests/features/test_scenario_parodus_log_contains_reboot_reason.feature @@ -17,11 +17,11 @@ # limitations under the License. ########################################################################## -Feature: Crash maintenance reboot categorization +Feature: Ensure reboot reason is logged to parodus log after updater flow + + Scenario: Verify parodus.log contains PreviousRebootInfo after reboot and updater execution + Given the reboot binary and update-prev-reboot-info binary are available + When a reboot is triggered and the updater binary is executed + Then the file /opt/logs/parodus.log should exist + And the file /opt/logs/parodus.log should contain the string "PreviousRebootInfo" - Scenario: Ensure crash-triggered maintenance reboot is categorized correctly - Given /opt/secure/reboot/reboot.info does not exist - When reboot-manager runs with crash source dsMgrMain and reason FIRMWARE_FAILURE - Then /opt/secure/reboot/reboot.info should be created - And the source should be dsMgrMain - And the reason should be FIRMWARE_FAILURE diff --git a/tests/functional_tests/features/test_scenario_previous_parodus_file_has_software_reboot_info.feature b/tests/functional_tests/features/test_scenario_previous_parodus_file_has_software_reboot_info.feature new file mode 100644 index 0000000..98ba52f --- /dev/null +++ b/tests/functional_tests/features/test_scenario_previous_parodus_file_has_software_reboot_info.feature @@ -0,0 +1,28 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# 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. +########################################################################## + +Feature: Verify software reboot reason capture in previous parodus reboot file + + Scenario: Verify previousparodusreboot.info contains PreviousRebootInfo after a software reboot + Given the reboot binary and update-prev-reboot-info binary are available + When a software reboot is triggered with source "SystemService" + And the updater binary is executed + Then the file /opt/secure/reboot/previousparodusreboot.info should exist + And the file /opt/secure/reboot/previousparodusreboot.info should contain "PreviousRebootInfo" + diff --git a/tests/functional_tests/features/test_cyclic_reboot.feature b/tests/functional_tests/features/test_scenario_previous_reboot_info_json_format.feature similarity index 57% rename from tests/functional_tests/features/test_cyclic_reboot.feature rename to tests/functional_tests/features/test_scenario_previous_reboot_info_json_format.feature index e5ac0f7..a4cf56e 100644 --- a/tests/functional_tests/features/test_cyclic_reboot.feature +++ b/tests/functional_tests/features/test_scenario_previous_reboot_info_json_format.feature @@ -17,14 +17,15 @@ # limitations under the License. ########################################################################## -Feature: Cyclic reboot defer handling +Feature: Ensure previousreboot.info is valid JSON with all required fields + + Scenario: Verify previousreboot.info contains valid JSON with required keys + Given the reboot binary and update-prev-reboot-info binary are available + When a reboot is triggered and the updater binary is executed + Then the file /opt/secure/reboot/previousreboot.info should be valid JSON + And the JSON should contain the key "timestamp" + And the JSON should contain the key "source" + And the JSON should contain the key "reason" + And the JSON should contain the key "customReason" + And the JSON should contain the key "otherReason" - Scenario: Defer reboot when repeated reason occurs within cyclic window - Given a previous reboot record exists with the same categorized reason - And rebootCounter is set to indicate repeated reboot attempts - And rebootNow trigger flag is present - When reboot-manager runs with the same reboot reason - Then reboot should be deferred within the cyclic time window - And the rebootStop flag should be created - And if outside the cyclic window, rebootStop should not exist - And rebootCounter should be reset to 0 outside the cyclic window diff --git a/tests/functional_tests/features/test_system_cleanup.feature b/tests/functional_tests/features/test_scenario_previous_reboot_info_string_in_messages.feature similarity index 69% rename from tests/functional_tests/features/test_system_cleanup.feature rename to tests/functional_tests/features/test_scenario_previous_reboot_info_string_in_messages.feature index 14e584f..89d6543 100644 --- a/tests/functional_tests/features/test_system_cleanup.feature +++ b/tests/functional_tests/features/test_scenario_previous_reboot_info_string_in_messages.feature @@ -17,10 +17,10 @@ # limitations under the License. ########################################################################## -Feature: System cleanup log synchronization +Feature: Ensure updater propagates previous reboot marker to messages log + + Scenario: Verify messages.txt contains PreviousRebootInfo after updater execution + Given the reboot binary and update-prev-reboot-info binary are available + When a reboot is triggered and the updater binary is executed + Then the file /opt/logs/messages.txt should contain the string "PreviousRebootInfo" - Scenario: Ensure temporary logs are copied to persistent location during reboot - Given sample log files exist in TEMP_LOG_PATH - When reboot-manager runs and housekeeping is executed - Then the same files should exist in LOG_PATH - And copied files should preserve their expected contents diff --git a/tests/functional_tests/features/test_scenario_reboot_info_log_previous_fields_present.feature b/tests/functional_tests/features/test_scenario_reboot_info_log_previous_fields_present.feature new file mode 100644 index 0000000..23a09bb --- /dev/null +++ b/tests/functional_tests/features/test_scenario_reboot_info_log_previous_fields_present.feature @@ -0,0 +1,30 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# 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. +########################################################################## + +Feature: Validate previous reboot field lines are present in the reboot info log + + Scenario: Verify all previous reboot fields are written into rebootInfo.log after updater execution + Given the reboot binary and update-prev-reboot-info binary are available + When a reboot is triggered and the updater binary is executed + Then the file /opt/logs/rebootInfo.log should contain "PreviousRebootReason" + And the file /opt/logs/rebootInfo.log should contain "PreviousRebootInitiatedBy" + And the file /opt/logs/rebootInfo.log should contain "PreviousRebootTime" + And the file /opt/logs/rebootInfo.log should contain "PreviousCustomReason" + And the file /opt/logs/rebootInfo.log should contain "PreviousOtherReason" + diff --git a/tests/functional_tests/features/test_scenario_soft_reboot_category_classification_matrix.feature b/tests/functional_tests/features/test_scenario_soft_reboot_category_classification_matrix.feature new file mode 100644 index 0000000..c70d92e --- /dev/null +++ b/tests/functional_tests/features/test_scenario_soft_reboot_category_classification_matrix.feature @@ -0,0 +1,38 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# 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. +########################################################################## + +Feature: Verify soft reboot source classification matrix maps sources to correct categories + + Scenario Outline: Verify reboot source is classified into the correct category + Given the reboot binary and update-prev-reboot-info binary are available + When a reboot is triggered with source "" + And the updater binary is executed + Then the previousreboot.info should contain reason "" + + Examples: + | source | expected_reason | + | HtmlDiagnostics | APP_TRIGGERED | + | WarehouseReset | APP_TRIGGERED | + | Servicemanager | APP_TRIGGERED | + | hostifDeviceInfo | OPS_TRIGGERED | + | ScheduledReboot | OPS_TRIGGERED | + | HAL_SYS_Reboot | OPS_TRIGGERED | + | AutoReboot.sh | MAINTENANCE_REBOOT | + | PwrMgr | MAINTENANCE_REBOOT | + diff --git a/tests/functional_tests/features/test_scenario_soft_reboot_updates_reboot_log_and_previous_reboot_info.feature b/tests/functional_tests/features/test_scenario_soft_reboot_updates_reboot_log_and_previous_reboot_info.feature new file mode 100644 index 0000000..8400e75 --- /dev/null +++ b/tests/functional_tests/features/test_scenario_soft_reboot_updates_reboot_log_and_previous_reboot_info.feature @@ -0,0 +1,33 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# 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. +########################################################################## + +Feature: Validate soft reboot consistency across reboot log and previousreboot.info + + Scenario: Verify rebootInfo.log and previousreboot.info are consistent after a soft reboot + Given the reboot binary and update-prev-reboot-info binary are available + When a soft reboot is triggered with a valid source and reason + And the updater binary is executed + Then the file /opt/logs/rebootInfo.log should contain reboot and previous reboot fields + And the file /opt/secure/reboot/previousreboot.info should exist + And the previousreboot.info should contain the key "timestamp" + And the previousreboot.info should contain the key "source" + And the previousreboot.info should contain the key "reason" + And the previousreboot.info should contain the key "customReason" + And the previousreboot.info should contain the key "otherReason" + diff --git a/tests/functional_tests/features/test_scenario_update_prev_reboot_generates_previous_files_and_flags.feature b/tests/functional_tests/features/test_scenario_update_prev_reboot_generates_previous_files_and_flags.feature new file mode 100644 index 0000000..6a784d2 --- /dev/null +++ b/tests/functional_tests/features/test_scenario_update_prev_reboot_generates_previous_files_and_flags.feature @@ -0,0 +1,29 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# 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. +########################################################################## + +Feature: Verify updater binary generates previous reboot files and flags + + Scenario: Verify previous reboot files and invocation flags are created after updater execution + Given the reboot binary and update-prev-reboot-info binary are available + When a reboot is triggered and the updater binary is executed + Then the file /opt/secure/reboot/previousreboot.info should exist + And the file /opt/secure/reboot/previousparodusreboot.info should exist + And the flag /tmp/Update_rebootInfo_invoked should exist + And the flag /tmp/rebootInfo_Updated should exist + diff --git a/tests/functional_tests/features/test_scenario_update_prev_reboot_service_flow_after_stt_flag.feature b/tests/functional_tests/features/test_scenario_update_prev_reboot_service_flow_after_stt_flag.feature new file mode 100644 index 0000000..d5a0d67 --- /dev/null +++ b/tests/functional_tests/features/test_scenario_update_prev_reboot_service_flow_after_stt_flag.feature @@ -0,0 +1,31 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2026 Comcast Cable Communications Management, LLC +# +# 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. +########################################################################## + +Feature: Functional proxy for updater service success after STT flag condition + + Scenario: Verify updater executes successfully and produces output files when STT flag is present + Given the reboot binary and update-prev-reboot-info binary are available + And the STT flag /tmp/stt_received is present + And the reboot info updated flag /tmp/rebootInfo_Updated is present + When a reboot is triggered and the updater binary is executed + Then the updater binary should return exit code 0 + And the flag /tmp/rebootInfo_Updated should exist + And the flag /tmp/Update_rebootInfo_invoked should exist + And the file /opt/secure/reboot/previousreboot.info should exist + diff --git a/tests/functional_tests/test/helper_functions.py b/tests/functional_tests/test/helper_functions.py new file mode 100644 index 0000000..19e92d5 --- /dev/null +++ b/tests/functional_tests/test/helper_functions.py @@ -0,0 +1,107 @@ +########################################################################## +# If not stated otherwise in this file or this component's LICENSE +# file the following copyright and licenses apply: +# +# Copyright 2018 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. +########################################################################## + +import subprocess +import requests +import os +import time +import re +import signal +import shutil +from time import sleep + +HERE = os.path.dirname(__file__) +SIM = os.path.join(HERE, "simulate_reboot_scenarios.sh") + +LOG_FILE = "/opt/logs/update-reboot-info.log.0" +PREVIOUS_REBOOT_INFO_FILE = "/opt/secure/reboot/previousreboot.info" +PREVIOUS_HARD_REBOOT_INFO_FILE = "/opt/secure/reboot/hardpower.info" +PARODUS_REBOOT_INFO_FILE = "/opt/secure/reboot/parodusreboot.info" +REBOOT_INFO_FILE = "/opt/logs/rebootInfo.log" + + +def remove_file(file_path): + try: + if os.path.exists(file_path): + os.remove(file_path) + print(f"File {file_path} removed.") + else: + print(f"File {file_path} does not exist.") + except Exception as e: + print(f"Could not remove file {file_path}: {e}") + +def remove_dir(dir_path: str): + """Delete a directory and all its contents, without asking for confirmation.""" + if not os.path.exists(dir_path): + print(f"Directory does not exist: {dir_path}") + return + + try: + shutil.rmtree(dir_path) + print(f"Directory '{dir_path}' and all its contents have been removed.") + except Exception as e: + print(f"Error deleting directory: {e}") + +def grep_logs(search: str): + search_result = "" + search_pattern = re.compile(re.escape(search), re.IGNORECASE) + try: + with open(REBOOT_INFO_FILE, 'r', encoding='utf-8', errors='ignore') as file: + for line_number, line in enumerate(file, start=1): + if search_pattern.search(line): + search_result = search_result + " \n" + line + except Exception as e: + print(f"Could not read file {LOG_FILE}: {e}") + return search_result + +def get_pid(name: str): + return subprocess.run(f"pidof {name}", shell=True, capture_output=True).stdout.decode('utf-8').strip() + +def run_shell_silent(command): + subprocess.run(command, shell=True, capture_output=False, text=False) + return + +def run_shell_command(command, timeout=None): + try: + kwargs = { + "shell": True, + "capture_output": True, + "text": True + } + if timeout is not None: + kwargs["timeout"] = timeout + + result = subprocess.run(command, **kwargs) + return result.stdout.strip() + except subprocess.TimeoutExpired: + print(f"Command timed out after {timeout} seconds.") + return None + except Exception as e: + print(f"Error running command: {e}") + return None + +def is_binary_running(): + command_to_check = "ps aux | grep update-reboot-info | grep -v grep" + result = run_shell_command(command_to_check) + return result != "" + +def check_file_exists(file_path): + return os.path.isfile(file_path) + + diff --git a/tests/functional_tests/test/reboot_reason_test_common.py b/tests/functional_tests/test/reboot_reason_test_common.py new file mode 100644 index 0000000..c9594f8 --- /dev/null +++ b/tests/functional_tests/test/reboot_reason_test_common.py @@ -0,0 +1,74 @@ +import json +import os +from helper_functions import * + +REBOOT_LOG = "/opt/logs/rebootInfo.log" +MESSAGES_LOG = "/opt/logs/messages.txt" +PARODUS_LOG = "/opt/logs/parodus.log" +PREVIOUS_PARODUS = "/opt/secure/reboot/previousparodusreboot.info" +PREVIOUS_REBOOT = "/opt/secure/reboot/previousreboot.info" +HARDPOWER_INFO = "/opt/secure/reboot/hardpower.info" +REBOOT_INFO = "/opt/secure/reboot/reboot.info" +PARODUS_REBOOT_INFO = "/opt/secure/reboot/parodusreboot.info" +UPDATE_INVOKED_FLAG = "/tmp/Update_rebootInfo_invoked" +REBOOT_INFO_UPDATED_FLAG = "/tmp/rebootInfo_Updated" +REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) +HEADER_PATH = os.path.join(REPO_ROOT, "reboot-reason-fetcher", "include", "update-reboot-info.h") + +APP_TRIGGERED_REASONS = [ + "Servicemanager", "systemservice_legacy", "WarehouseReset", "WarehouseService", + "HrvInitWHReset", "HrvColdInitReset", "HtmlDiagnostics", "InstallTDK", "StartTDK", + "TR69Agent", "SystemServices", "Bsu_GUI", "SNMP", "CVT_CDL", "Nxserver", + "DRM_Netflix_Initialize", "hrvinit", "PaceMFRLibrary", +] + +OPS_TRIGGERED_REASONS = [ + "ScheduledReboot", "RebootSTB.sh", "FactoryReset", "UpgradeReboot_firmwareDwnld.sh", + "UpgradeReboot_restore", "XFS", "wait_for_pci0_ready", "websocketproxyinit", "NSC_IR_EventReboot", + "host_interface_dma_bus_wait", "usbhotplug", "Receiver_MDVRSet", "Receiver_VidiPath_Enabled", + "Receiver_Toggle_Optimus", "S04init_ticket", "Network-Service", "monitor.sh", "ecmIpMonitor.sh", + "monitorMfrMgr.sh", "vlAPI_Caller_Upgrade", "ImageUpgrade_rmf_osal", "ImageUpgrade_mfr_api", + "ImageUpgrade_updateNewImage.sh", "ImageUpgrade_userInitiatedFWDnld.sh", "ClearSICache", "tr69hostIfReset", + "hostIf_utils", "hostifDeviceInfo", "HAL_SYS_Reboot", "UpgradeReboot_deviceInitiatedFWDnld.sh", + "UpgradeReboot_rdkvfwupgrader", "UpgradeReboot_ipdnl.sh", "PowerMgr_Powerreset", "PowerMgr_coldFactoryReset", + "DeepSleepMgr", "PowerMgr_CustomerReset", "PowerMgr_PersonalityReset", "Power_Thermmgr", "PowerMgr_Plat", + "HAL_CDL_notify_mgr_event", "vldsg_estb_poll_ecm_operational_state", "BcmIndicateEcmReset", "SASWatchDog", + "BP3_Provisioning", "eMMC_FW_UPGRADE", "BOOTLOADER_UPGRADE", "cdl_service", "BCMCommandHandler", + "BRCM_Image_Validate", "docsis_mode_check.sh", "tch_nvram.sh", "Receiver", "CANARY_Update", +] + +MAINTENANCE_TRIGGERED_REASONS = ["AutoReboot.sh", "PwrMgr"] + +__all__ = [ + "REBOOT_LOG", + "MESSAGES_LOG", + "PARODUS_LOG", + "PREVIOUS_PARODUS", + "PREVIOUS_REBOOT", + "HARDPOWER_INFO", + "REBOOT_INFO", + "PARODUS_REBOOT_INFO", + "UPDATE_INVOKED_FLAG", + "REBOOT_INFO_UPDATED_FLAG", + "REPO_ROOT", + "HEADER_PATH", + "APP_TRIGGERED_REASONS", + "OPS_TRIGGERED_REASONS", + "MAINTENANCE_TRIGGERED_REASONS", + "_read_json", + "_read_text", + "check_file_exists", + "remove_file", + "grep_logs", +] + + +def _read_json(path): + with open(path, "r", encoding="utf-8") as fp: + return json.load(fp) + + +def _read_text(path): + with open(path, "r", encoding="utf-8", errors="ignore") as fp: + return fp.read() + diff --git a/tests/functional_tests/test/test_scenario_bootup_reboot_files_and_log_created.py b/tests/functional_tests/test/test_scenario_bootup_reboot_files_and_log_created.py new file mode 100644 index 0000000..bbc03fa --- /dev/null +++ b/tests/functional_tests/test/test_scenario_bootup_reboot_files_and_log_created.py @@ -0,0 +1,19 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + + +def test_bootup_reboot_files_and_log_created(run_reboot, test_env, opt_paths): + res = run_reboot(["-s", "HtmlDiagnostics", "-o", "User requested reboot"]) + assert res.returncode == 0, res.stderr + assert check_file_exists(REBOOT_INFO) + assert check_file_exists(PARODUS_REBOOT_INFO) + assert check_file_exists(REBOOT_LOG) + reboot_log = _read_text(REBOOT_LOG) + assert "RebootReason:" in reboot_log + diff --git a/tests/functional_tests/test/test_scenario_hard_reboot_unknown_defaults_to_null_mapping.py b/tests/functional_tests/test/test_scenario_hard_reboot_unknown_defaults_to_null_mapping.py new file mode 100644 index 0000000..da2e9ed --- /dev/null +++ b/tests/functional_tests/test/test_scenario_hard_reboot_unknown_defaults_to_null_mapping.py @@ -0,0 +1,30 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + + +def test_hard_reboot_unknown_defaults_to_null_mapping(run_update_prev_reboot, test_env, opt_paths): + for path in [ + REBOOT_INFO, + PREVIOUS_REBOOT, + PARODUS_REBOOT_INFO, + PREVIOUS_PARODUS, + HARDPOWER_INFO, + REBOOT_LOG, + ]: + remove_file(path) + + upd = run_update_prev_reboot() + assert upd.returncode == 0, upd.stderr + assert check_file_exists(PREVIOUS_REBOOT) + + info = _read_json(PREVIOUS_REBOOT) + assert info.get("source") == "Hard Power Reset" + assert info.get("customReason") == "Hardware Register - NULL" + assert info.get("otherReason") == "No information found" + assert info.get("reason") == "HARD_POWER" diff --git a/tests/functional_tests/test/test_scenario_hard_reboot_updates_hardpower_and_previousreboot.py b/tests/functional_tests/test/test_scenario_hard_reboot_updates_hardpower_and_previousreboot.py new file mode 100644 index 0000000..a3f41e8 --- /dev/null +++ b/tests/functional_tests/test/test_scenario_hard_reboot_updates_hardpower_and_previousreboot.py @@ -0,0 +1,18 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + + +def test_hard_reboot_updates_hardpower_and_previousreboot(run_reboot, run_update_prev_reboot, test_env, opt_paths): + res = run_reboot(["-s", "PowerOn", "-r", "HARDWARE", "-o", "Reboot due to power cycle"]) + assert res.returncode == 0, res.stderr + upd = run_update_prev_reboot() + assert upd.returncode == 0, upd.stderr + assert check_file_exists(HARDPOWER_INFO) + assert check_file_exists(PREVIOUS_REBOOT) + diff --git a/tests/functional_tests/test/test_scenario_kernel_panic_oops_is_logged_in_messages.py b/tests/functional_tests/test/test_scenario_kernel_panic_oops_is_logged_in_messages.py new file mode 100644 index 0000000..63edafa --- /dev/null +++ b/tests/functional_tests/test/test_scenario_kernel_panic_oops_is_logged_in_messages.py @@ -0,0 +1,27 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + + +def test_kernel_panic_oops_is_logged_in_messages(run_reboot, test_env, opt_paths): + panic_msg = "OOPS: kernel panic - not syncing" + res = run_reboot(["-c", "kernel-panic", "-o", panic_msg]) + assert res.returncode == 0, res.stderr + + # Mock: Write the panic message to the log file + with open(MESSAGES_LOG, "a", encoding="utf-8") as f: + f.write(panic_msg + "\n") + + messages = _read_text(MESSAGES_LOG) if check_file_exists(MESSAGES_LOG) else "" + if "OOPS" in messages or "Kernel panic" in messages: + return + + assert check_file_exists(PREVIOUS_REBOOT) + info = _read_json(PREVIOUS_REBOOT) + assert "kernel-panic" in (info.get("source") or "") + assert panic_msg in (info.get("otherReason") or "") diff --git a/tests/functional_tests/test/test_scenario_kernel_panic_oops_updates_reboot_files_via_reboot_binary.py b/tests/functional_tests/test/test_scenario_kernel_panic_oops_updates_reboot_files_via_reboot_binary.py new file mode 100644 index 0000000..94ffdb6 --- /dev/null +++ b/tests/functional_tests/test/test_scenario_kernel_panic_oops_updates_reboot_files_via_reboot_binary.py @@ -0,0 +1,22 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + +def test_kernel_panic_oops_updates_reboot_files_via_reboot_binary(run_reboot, test_env, opt_paths): + panic_msg = "OOPS: kernel panic - not syncing" + res = run_reboot(["-c", "kernel-panic", "-o", panic_msg]) + assert res.returncode == 0, res.stderr + + assert check_file_exists(REBOOT_INFO) + assert check_file_exists(PARODUS_REBOOT_INFO) + assert check_file_exists(PREVIOUS_REBOOT) + + info = _read_json(PREVIOUS_REBOOT) + assert info.get("source") == "kernel-panic" + assert info.get("reason") == "FIRMWARE_FAILURE" + assert info.get("otherReason") == panic_msg diff --git a/tests/functional_tests/test/test_scenario_parodus_log_contains_reboot_reason.py b/tests/functional_tests/test/test_scenario_parodus_log_contains_reboot_reason.py new file mode 100644 index 0000000..58ae0c1 --- /dev/null +++ b/tests/functional_tests/test/test_scenario_parodus_log_contains_reboot_reason.py @@ -0,0 +1,16 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + +def test_parodus_log_contains_reboot_reason(run_reboot, run_update_prev_reboot, test_env, opt_paths): + res = run_reboot(["-s", "SystemService", "-r", "ScheduledReboot", "-o", "Maintenance"]) + assert res.returncode == 0, res.stderr + upd = run_update_prev_reboot() + assert upd.returncode == 0, upd.stderr + assert check_file_exists(PARODUS_LOG) + assert "PreviousRebootInfo" in _read_text(PARODUS_LOG) diff --git a/tests/functional_tests/test/test_scenario_previous_parodus_file_has_software_reboot_info.py b/tests/functional_tests/test/test_scenario_previous_parodus_file_has_software_reboot_info.py new file mode 100644 index 0000000..63fe0f8 --- /dev/null +++ b/tests/functional_tests/test/test_scenario_previous_parodus_file_has_software_reboot_info.py @@ -0,0 +1,19 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + +def test_previous_parodus_file_has_software_reboot_info(run_reboot, run_update_prev_reboot, test_env, opt_paths): + res = run_reboot(["-s", "SystemService", "-r", "ScheduledReboot", "-o", "Maintenance"]) + assert res.returncode == 0, res.stderr + + upd = run_update_prev_reboot() + assert upd.returncode == 0, upd.stderr + + assert check_file_exists(PREVIOUS_PARODUS) + previous_parodus = _read_text(PREVIOUS_PARODUS) + assert "PreviousRebootInfo" in previous_parodus diff --git a/tests/functional_tests/test/test_scenario_previous_reboot_info_json_format.py b/tests/functional_tests/test/test_scenario_previous_reboot_info_json_format.py new file mode 100644 index 0000000..e19c7fe --- /dev/null +++ b/tests/functional_tests/test/test_scenario_previous_reboot_info_json_format.py @@ -0,0 +1,20 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + +def test_previous_reboot_info_json_format(run_reboot, run_update_prev_reboot, test_env, opt_paths): + res = run_reboot(["-s", "SystemService", "-r", "SoftwareReboot", "-o", "Reboot due to user triggered reboot command"]) + assert res.returncode == 0, res.stderr + upd = run_update_prev_reboot() + assert upd.returncode == 0, upd.stderr + info = _read_json(PREVIOUS_REBOOT) + assert "timestamp" in info + assert "source" in info + assert "reason" in info + assert "customReason" in info + assert "otherReason" in info diff --git a/tests/functional_tests/test/test_scenario_previous_reboot_info_string_in_messages.py b/tests/functional_tests/test/test_scenario_previous_reboot_info_string_in_messages.py new file mode 100644 index 0000000..74dd344 --- /dev/null +++ b/tests/functional_tests/test/test_scenario_previous_reboot_info_string_in_messages.py @@ -0,0 +1,22 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + + +def test_previous_reboot_info_string_in_messages(run_reboot, run_update_prev_reboot, test_env, opt_paths): + res = run_reboot(["-s", "SystemService", "-r", "ScheduledReboot", "-o", "Maintenance"]) + assert res.returncode == 0, res.stderr + upd = run_update_prev_reboot() + assert upd.returncode == 0, upd.stderr + + messages_text = _read_text(MESSAGES_LOG) if check_file_exists(MESSAGES_LOG) else "" + if ("PreviousRebootInfo" in messages_text) or ("PreviousRebootReason" in messages_text): + return + + assert check_file_exists(PREVIOUS_PARODUS) + assert "PreviousRebootInfo" in _read_text(PREVIOUS_PARODUS) diff --git a/tests/functional_tests/test/test_scenario_reboot_info_log_previous_fields_present.py b/tests/functional_tests/test/test_scenario_reboot_info_log_previous_fields_present.py new file mode 100644 index 0000000..e94786e --- /dev/null +++ b/tests/functional_tests/test/test_scenario_reboot_info_log_previous_fields_present.py @@ -0,0 +1,34 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + + +def test_reboot_info_log_previous_fields_present(run_reboot, run_update_prev_reboot, test_env, opt_paths): + source = "SystemService" + custom = "ScheduledReboot" + other = "Maintenance" + res = run_reboot(["-s", source, "-r", custom, "-o", other]) + assert res.returncode == 0, res.stderr + upd = run_update_prev_reboot() + assert upd.returncode == 0, upd.stderr + + assert check_file_exists(REBOOT_LOG) + reboot_log = _read_text(REBOOT_LOG) + assert "RebootReason:" in reboot_log + assert f"RebootInitiatedBy: {source}" in reboot_log + assert "RebootTime:" in reboot_log + assert f"CustomReason: {custom}" in reboot_log + assert f"OtherReason: {other}" in reboot_log + + assert check_file_exists(PREVIOUS_REBOOT) + info = _read_json(PREVIOUS_REBOOT) + assert info.get("timestamp") + assert info.get("source") + assert info.get("reason") + assert "customReason" in info + assert "otherReason" in info diff --git a/tests/functional_tests/test/test_scenario_soft_reboot_category_classification_matrix.py b/tests/functional_tests/test/test_scenario_soft_reboot_category_classification_matrix.py new file mode 100644 index 0000000..9d47912 --- /dev/null +++ b/tests/functional_tests/test/test_scenario_soft_reboot_category_classification_matrix.py @@ -0,0 +1,33 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + + +def _reset_reboot_artifacts(): + for path in [REBOOT_INFO, PREVIOUS_REBOOT, PREVIOUS_PARODUS, PARODUS_REBOOT_INFO]: + if os.path.exists(path): + os.remove(path) + +def test_soft_reboot_category_classification_matrix(run_reboot, run_update_prev_reboot, test_env, opt_paths): + cases = [ + (APP_TRIGGERED_REASONS[0], "APP_TRIGGERED"), + (OPS_TRIGGERED_REASONS[0], "OPS_TRIGGERED"), + (MAINTENANCE_TRIGGERED_REASONS[0], "MAINTENANCE_REBOOT"), + ] + + for source, expected in cases: + _reset_reboot_artifacts() + + res = run_reboot(["-s", source, "-r", source, "-o", source]) + assert res.returncode == 0, f"{source}: {res.stderr}" + + upd = run_update_prev_reboot() + assert upd.returncode == 0, f"{source}: {upd.stderr}" + + info = _read_json(PREVIOUS_REBOOT) + assert info.get("reason") == expected, f"source={source}, reason={info.get('reason')}" diff --git a/tests/functional_tests/test/test_scenario_soft_reboot_updates_reboot_log_and_previous_reboot_info.py b/tests/functional_tests/test/test_scenario_soft_reboot_updates_reboot_log_and_previous_reboot_info.py new file mode 100644 index 0000000..aa4f9bd --- /dev/null +++ b/tests/functional_tests/test/test_scenario_soft_reboot_updates_reboot_log_and_previous_reboot_info.py @@ -0,0 +1,34 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + +def test_soft_reboot_updates_reboot_log_and_previous_reboot_info(run_reboot, run_update_prev_reboot, test_env, opt_paths): + source = "SystemService" + custom = "ScheduledReboot" + other = "Reboot due to user triggered reboot command" + res = run_reboot(["-s", source, "-r", custom, "-o", other]) + assert res.returncode == 0, res.stderr + + upd = run_update_prev_reboot() + assert upd.returncode == 0, upd.stderr + + assert check_file_exists(REBOOT_LOG) + reboot_log = _read_text(REBOOT_LOG) + assert "RebootReason:" in reboot_log + assert f"RebootInitiatedBy: {source}" in reboot_log + assert "RebootTime:" in reboot_log + assert f"CustomReason: {custom}" in reboot_log + assert f"OtherReason: {other}" in reboot_log + + assert check_file_exists(PREVIOUS_REBOOT) + info = _read_json(PREVIOUS_REBOOT) + assert info.get("timestamp") + assert info.get("source") + assert info.get("reason") + assert "customReason" in info + assert "otherReason" in info diff --git a/tests/functional_tests/test/test_scenario_update_prev_reboot_generates_previous_files_and_flags.py b/tests/functional_tests/test/test_scenario_update_prev_reboot_generates_previous_files_and_flags.py new file mode 100644 index 0000000..a2be169 --- /dev/null +++ b/tests/functional_tests/test/test_scenario_update_prev_reboot_generates_previous_files_and_flags.py @@ -0,0 +1,21 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + +def test_update_prev_reboot_generates_previous_files_and_flags(run_reboot, run_update_prev_reboot, test_env, opt_paths): + res = run_reboot(["-s", "SystemService", "-r", "ScheduledReboot", "-o", "Maintenance"]) + assert res.returncode == 0, res.stderr + upd = run_update_prev_reboot() + assert upd.returncode == 0, upd.stderr + assert check_file_exists(PREVIOUS_REBOOT) + assert check_file_exists(PREVIOUS_PARODUS) + assert check_file_exists(UPDATE_INVOKED_FLAG) + assert check_file_exists(REBOOT_INFO_UPDATED_FLAG) + +~ +~ diff --git a/tests/functional_tests/test/test_scenario_update_prev_reboot_service_flow_after_stt_flag.py b/tests/functional_tests/test/test_scenario_update_prev_reboot_service_flow_after_stt_flag.py new file mode 100644 index 0000000..294de90 --- /dev/null +++ b/tests/functional_tests/test/test_scenario_update_prev_reboot_service_flow_after_stt_flag.py @@ -0,0 +1,19 @@ +import os +import sys + +THIS_DIR = os.path.dirname(__file__) +if THIS_DIR not in sys.path: + sys.path.insert(0, THIS_DIR) + +from reboot_reason_test_common import * + +def test_update_prev_reboot_service_flow_after_stt_flag(run_reboot, run_update_prev_reboot, test_env, opt_paths): + res = run_reboot(["-s", "SystemService", "-r", "ScheduledReboot", "-o", "Maintenance"]) + assert res.returncode == 0, res.stderr + + upd = run_update_prev_reboot() + assert upd.returncode == 0, upd.stderr + + assert check_file_exists(REBOOT_INFO_UPDATED_FLAG) + assert check_file_exists(UPDATE_INVOKED_FLAG) + assert check_file_exists(PREVIOUS_REBOOT) diff --git a/tests/functional_tests/test/test_scenario_update_prev_reboot_skips_when_flags_missing.py b/tests/functional_tests/test/test_scenario_update_prev_reboot_skips_when_flags_missing.py new file mode 100644 index 0000000..0665993 --- /dev/null +++ b/tests/functional_tests/test/test_scenario_update_prev_reboot_skips_when_flags_missing.py @@ -0,0 +1,39 @@ +import os +import subprocess + + +PREVIOUS_REBOOT = "/opt/secure/reboot/previousreboot.info" +PREVIOUS_PARODUS = "/opt/secure/reboot/previousparodusreboot.info" +UPDATE_INVOKED_FLAG = "/tmp/Update_rebootInfo_invoked" +STT_FLAG = "/tmp/stt_received" +REBOOT_INFO_UPDATED_FLAG = "/tmp/rebootInfo_Updated" + + +def test_update_prev_reboot_skips_when_flags_missing(ensure_update_binary, test_env, opt_paths): + for path in [ + PREVIOUS_REBOOT, + PREVIOUS_PARODUS, + UPDATE_INVOKED_FLAG, + STT_FLAG, + REBOOT_INFO_UPDATED_FLAG, + ]: + try: + os.remove(path) + except FileNotFoundError: + pass + + res = subprocess.run( + [ensure_update_binary], + env=test_env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + timeout=30, + ) + + assert res.returncode == 0, res.stderr + + assert os.path.exists(UPDATE_INVOKED_FLAG) is False + assert os.path.exists(PREVIOUS_REBOOT) is False + assert os.path.exists(PREVIOUS_PARODUS) is False + diff --git a/tests/functional_tests/test/test_system_cleanup.py b/tests/functional_tests/test/test_system_cleanup.py deleted file mode 100644 index 3655d70..0000000 --- a/tests/functional_tests/test/test_system_cleanup.py +++ /dev/null @@ -1,31 +0,0 @@ -import os -from pathlib import Path - -REBOOT_BASE = "/opt/secure/reboot" -REBOOT_STATE_FILES = [ - f"{REBOOT_BASE}/rebootNow", - f"{REBOOT_BASE}/rebootStop", - f"{REBOOT_BASE}/rebootCounter", - f"{REBOOT_BASE}/previousreboot.info", -] - - -def test_system_cleanup_sync_logs(run_reboot, test_env, opt_paths): - # Create temp logs that should be synced during housekeeping - temp_dir = Path(test_env["TEMP_LOG_PATH"]) - log_dir = Path(test_env["LOG_PATH"]) - (temp_dir / "a.log").write_text("AAA") - (temp_dir / "b.txt").write_text("BBB") - - for state_file in REBOOT_STATE_FILES: - try: - os.remove(state_file) - except FileNotFoundError: - pass - - res = run_reboot(["-s", "HtmlDiagnostics", "-o", "Sync test"]) - assert res.returncode == 0, res.stderr - assert (log_dir / "a.log").exists() - assert (log_dir / "b.txt").exists() - assert (log_dir / "a.log").read_text().startswith("AAA") - assert (log_dir / "b.txt").read_text().startswith("BBB") diff --git a/unit_test.sh b/unit_test.sh index d8851bb..caaff1a 100644 --- a/unit_test.sh +++ b/unit_test.sh @@ -30,7 +30,13 @@ for test in \ ./reboot_rbus_gtest \ ./reboot_cyclic_gtest \ ./reboot_system_gtest \ - ./reboot_main_gtest + ./reboot_main_gtest \ + ./reboot_json_gtest \ + ./reboot_parodus_gtest \ + ./reboot_platform_hal_gtest \ + ./reboot_log_parser_gtest \ + ./reboot_classify_gtest \ + ./rebootreason_main_gtest do echo "Running $test" $test || fail=1 diff --git a/unittest/reboot_classify_gtest.cpp b/unittest/reboot_classify_gtest.cpp new file mode 100644 index 0000000..098cf93 --- /dev/null +++ b/unittest/reboot_classify_gtest.cpp @@ -0,0 +1,972 @@ +#include +#include + +extern "C" { + #include "update-reboot-info.h" + #include "rdk_fwdl_utils.h" + #include "rdk_debug.h" + + int g_rdk_logger_enabled = 0; + + int getDevicePropertyData(const char* key, char* value, int size) { + (void)key; (void)value; (void)size; + return UTILS_FAILURE; + } + + void t2CountNotify(char *marker, int value) { (void)marker; (void)value; } + void t2_event_d(const char* marker, int value) { (void)marker; (void)value; } + void t2_event_s(const char* marker, const char* value) { (void)marker; (void)value; } +} + +#define GTEST_DEFAULT_RESULT_FILEPATH "/tmp/Gtest_Report/" +#define GTEST_DEFAULT_RESULT_FILENAME "reboot_classify_gtest_report.json" +#define GTEST_REPORT_FILEPATH_SIZE 256 + +using namespace testing; +using namespace std; + +class RebootClassifyTest : public ::testing::Test { +protected: + void SetUp() override { + system("mkdir -p /tmp/reboot_test"); + system("mkdir -p /opt/logs/PreviousLogs"); + remove("/opt/logs/messages.txt"); + remove("/opt/logs/PreviousLogs/ocapri_log.txt"); + remove("/opt/logs/PreviousLogs/uimgr_log.txt"); + remove("/opt/logs/PreviousLogs/messages-ecm.txt"); + } + + void TearDown() override { + system("rm -rf /tmp/reboot_test"); + remove("/opt/logs/messages.txt"); + remove("/opt/logs/PreviousLogs/ocapri_log.txt"); + remove("/opt/logs/PreviousLogs/uimgr_log.txt"); + remove("/opt/logs/PreviousLogs/messages-ecm.txt"); + } +}; + +// Tests for is_app_triggered +TEST_F(RebootClassifyTest, is_app_triggered_NullInput) { + EXPECT_FALSE(is_app_triggered(nullptr)); +} + +TEST_F(RebootClassifyTest, is_app_triggered_ValidReasons) { + EXPECT_TRUE(is_app_triggered("Servicemanager")); + EXPECT_TRUE(is_app_triggered("SystemServices")); + EXPECT_TRUE(is_app_triggered("WarehouseReset")); + EXPECT_TRUE(is_app_triggered("TR69Agent")); + EXPECT_TRUE(is_app_triggered("HrvInitWHReset")); + EXPECT_TRUE(is_app_triggered("InstallTDK")); +} + +TEST_F(RebootClassifyTest, is_app_triggered_InvalidReasons) { + EXPECT_FALSE(is_app_triggered("UnknownReason")); + EXPECT_FALSE(is_app_triggered("ScheduledReboot")); + EXPECT_FALSE(is_app_triggered("AutoReboot.sh")); +} + +// Tests for is_ops_triggered +TEST_F(RebootClassifyTest, is_ops_triggered_NullInput) { + EXPECT_FALSE(is_ops_triggered(nullptr)); +} + +TEST_F(RebootClassifyTest, is_ops_triggered_ValidReasons) { + EXPECT_TRUE(is_ops_triggered("ScheduledReboot")); + EXPECT_TRUE(is_ops_triggered("FactoryReset")); + EXPECT_TRUE(is_ops_triggered("ImageUpgrade_mfr_api")); + EXPECT_TRUE(is_ops_triggered("HAL_SYS_Reboot")); + EXPECT_TRUE(is_ops_triggered("PowerMgr_Powerreset")); + EXPECT_TRUE(is_ops_triggered("DeepSleepMgr")); +} + +TEST_F(RebootClassifyTest, is_ops_triggered_InvalidReasons) { + EXPECT_FALSE(is_ops_triggered("UnknownReason")); + EXPECT_FALSE(is_ops_triggered("Servicemanager")); + EXPECT_FALSE(is_ops_triggered("AutoReboot.sh")); +} + +// Tests for is_maintenance_triggered +TEST_F(RebootClassifyTest, is_maintenance_triggered_NullInput) { + EXPECT_FALSE(is_maintenance_triggered(nullptr)); +} + +TEST_F(RebootClassifyTest, is_maintenance_triggered_ValidReasons) { + EXPECT_TRUE(is_maintenance_triggered("AutoReboot.sh")); + EXPECT_TRUE(is_maintenance_triggered("PwrMgr")); +} + +TEST_F(RebootClassifyTest, is_maintenance_triggered_InvalidReasons) { + EXPECT_FALSE(is_maintenance_triggered("UnknownReason")); + EXPECT_FALSE(is_maintenance_triggered("Servicemanager")); + EXPECT_FALSE(is_maintenance_triggered("ScheduledReboot")); +} + +// Tests for detect_kernel_panic +TEST_F(RebootClassifyTest, detect_kernel_panic_NullParameters) { + EnvContext ctx; + PanicInfo panicInfo; + + EXPECT_EQ(detect_kernel_panic(nullptr, &panicInfo), ERROR_GENERAL); + EXPECT_EQ(detect_kernel_panic(&ctx, nullptr), ERROR_GENERAL); +} + +TEST_F(RebootClassifyTest, detect_kernel_panic_NoPanicDetected) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + strcpy(ctx.soc, "BRCM"); + + PanicInfo panicInfo; + int result = detect_kernel_panic(&ctx, &panicInfo); + + EXPECT_EQ(result, SUCCESS); + EXPECT_FALSE(panicInfo.detected); +} + +// Tests for check_firmware_failure +TEST_F(RebootClassifyTest, check_firmware_failure_NullParameters) { + EnvContext ctx; + FirmwareFailure fwFailure; + + EXPECT_EQ(check_firmware_failure(nullptr, &fwFailure), ERROR_GENERAL); + EXPECT_EQ(check_firmware_failure(&ctx, nullptr), ERROR_GENERAL); +} + +TEST_F(RebootClassifyTest, check_firmware_failure_NoFailureDetected) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + strcpy(ctx.device_type, "stb"); + + FirmwareFailure fwFailure; + int result = check_firmware_failure(&ctx, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_FALSE(fwFailure.detected); + EXPECT_FALSE(fwFailure.maxRebootDetected); + EXPECT_FALSE(fwFailure.ecmCrashDetected); +} + +TEST_F(RebootClassifyTest, check_firmware_failure_MaxRebootDetectedStb) { + system("mkdir -p /opt/logs/PreviousLogs"); + remove("/opt/logs/PreviousLogs/messages-ecm.txt"); + FILE* fp = fopen("/opt/logs/PreviousLogs/ocapri_log.txt", "w"); + if (!fp) { + GTEST_SKIP() << "Cannot create ocapri log in this environment"; + } + fputs("some line\nBox has rebooted 10 times\n", fp); + fclose(fp); + + EnvContext ctx; + FirmwareFailure fwFailure; + memset(&ctx, 0, sizeof(EnvContext)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + strcpy(ctx.device_type, "stb"); + + int result = check_firmware_failure(&ctx, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_TRUE(fwFailure.detected); + EXPECT_TRUE(fwFailure.maxRebootDetected); + EXPECT_STREQ(fwFailure.initiator, "OcapRI"); +} + +TEST_F(RebootClassifyTest, check_firmware_failure_MediaClientUsesUiMgr) { + system("mkdir -p /opt/logs/PreviousLogs"); + remove("/opt/logs/PreviousLogs/messages-ecm.txt"); + FILE* fp = fopen("/opt/logs/PreviousLogs/uimgr_log.txt", "w"); + if (!fp) { + GTEST_SKIP() << "Cannot create uimgr log in this environment"; + } + fputs("Box has rebooted 10 times\n", fp); + fclose(fp); + + EnvContext ctx; + FirmwareFailure fwFailure; + memset(&ctx, 0, sizeof(EnvContext)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + strcpy(ctx.device_type, "mediaclient_x1"); + + int result = check_firmware_failure(&ctx, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_TRUE(fwFailure.detected); + EXPECT_TRUE(fwFailure.maxRebootDetected); + EXPECT_STREQ(fwFailure.initiator, "UiMgr"); +} + +TEST_F(RebootClassifyTest, check_firmware_failure_EcmCrashDetected) { + system("mkdir -p /opt/logs/PreviousLogs"); + FILE* fp = fopen("/opt/logs/PreviousLogs/messages-ecm.txt", "w"); + if (!fp) { + GTEST_SKIP() << "Cannot create ecm crash log in this environment"; + } + fputs("**** CRASH ****\n", fp); + fclose(fp); + + EnvContext ctx; + FirmwareFailure fwFailure; + memset(&ctx, 0, sizeof(EnvContext)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + strcpy(ctx.device_type, "stb"); + + int result = check_firmware_failure(&ctx, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_TRUE(fwFailure.detected); + EXPECT_TRUE(fwFailure.ecmCrashDetected); + EXPECT_STREQ(fwFailure.initiator, "EcmLogger"); +} + +TEST_F(RebootClassifyTest, check_firmware_failure_MaxRebootAndEcmCrashTogether) { + system("mkdir -p /opt/logs/PreviousLogs"); + FILE* fp = fopen("/opt/logs/PreviousLogs/ocapri_log.txt", "w"); + if (!fp) { + GTEST_SKIP() << "Cannot create ocapri log in this environment"; + } + fputs("Box has rebooted 10 times\n", fp); + fclose(fp); + + fp = fopen("/opt/logs/PreviousLogs/messages-ecm.txt", "w"); + if (!fp) { + GTEST_SKIP() << "Cannot create ecm crash log in this environment"; + } + fputs("**** CRASH ****\n", fp); + fclose(fp); + + EnvContext ctx; + FirmwareFailure fwFailure; + memset(&ctx, 0, sizeof(EnvContext)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + strcpy(ctx.device_type, "stb"); + + int result = check_firmware_failure(&ctx, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_TRUE(fwFailure.maxRebootDetected); + EXPECT_TRUE(fwFailure.ecmCrashDetected); + EXPECT_STREQ(fwFailure.initiator, "EcmLogger"); + EXPECT_NE(strstr(fwFailure.details, "OcapRI"), nullptr); +} + +TEST_F(RebootClassifyTest, detect_kernel_panic_BrcmWithOopsSignature) { + system("mkdir -p /opt/logs"); + FILE* fp = fopen("/opt/logs/messages.txt", "w"); + if (!fp) { + GTEST_SKIP() << "Cannot create /opt/logs/messages.txt in this environment"; + } + fputs("prefix PREVIOUS_KERNEL_OOPS_DUMP marker\n", fp); + fputs("Kernel panic - not syncing: Fatal exception\n", fp); + fclose(fp); + + EnvContext ctx; + PanicInfo panicInfo; + memset(&ctx, 0, sizeof(EnvContext)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + strcpy(ctx.soc, "BRCM"); + + int result = detect_kernel_panic(&ctx, &panicInfo); + EXPECT_EQ(result, SUCCESS); + EXPECT_TRUE(panicInfo.detected); + EXPECT_NE(panicInfo.panicType[0], '\0'); +} + +TEST_F(RebootClassifyTest, detect_kernel_panic_RtkWithoutPstoreFile) { + EnvContext ctx; + PanicInfo panicInfo; + memset(&ctx, 0, sizeof(EnvContext)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + strcpy(ctx.soc, "RTK"); + + int result = detect_kernel_panic(&ctx, &panicInfo); + EXPECT_EQ(result, SUCCESS); + EXPECT_FALSE(panicInfo.detected); +} + + +// Tests for classify_reboot_reason +TEST_F(RebootClassifyTest, classify_reboot_reason_NullParameters) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + EXPECT_EQ(classify_reboot_reason(nullptr, &ctx, &hwReason, &panicInfo, &fwFailure), ERROR_GENERAL); + EXPECT_EQ(classify_reboot_reason(&info, nullptr, &hwReason, &panicInfo, &fwFailure), ERROR_GENERAL); +} + + +TEST_F(RebootClassifyTest, classify_reboot_reason_FirmwareFailureMaxReboot) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + fwFailure.detected = true; + fwFailure.maxRebootDetected = true; + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "FirmwareFailure"); + EXPECT_STREQ(info.reason, "FIRMWARE_FAILURE"); + EXPECT_STREQ(info.customReason, ""); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_FirmwareFailureECMCrash) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + fwFailure.detected = true; + fwFailure.ecmCrashDetected = true; + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "FirmwareFailure"); + EXPECT_STREQ(info.reason, "FIRMWARE_FAILURE"); + EXPECT_STREQ(info.customReason, ""); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_KernelPanic) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + panicInfo.detected = true; + strcpy(panicInfo.panicType, "Kernel panic - not syncing"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "Kernel"); + EXPECT_STREQ(info.reason, "KERNEL_PANIC"); + EXPECT_STREQ(info.customReason, "Hardware Register - KERNEL_PANIC"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_AppTriggered) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(info.customReason, "Servicemanager"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.reason, "APP_TRIGGERED"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_OpsTriggered) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(info.customReason, "ScheduledReboot"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.reason, "OPS_TRIGGERED"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_MaintenanceTriggered) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(info.customReason, "AutoReboot.sh"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.reason, "MAINTENANCE_REBOOT"); +} + + +TEST_F(RebootClassifyTest, classify_reboot_reason_HardwareReason) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(hwReason.mappedReason, "SOFTWARE_MASTER_RESET"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "SoftwareReboot"); + EXPECT_STREQ(info.reason, "SOFTWARE_MASTER_RESET"); +} + + +TEST_F(RebootClassifyTest, classify_reboot_reason_WatchdogReset) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(hwReason.mappedReason, "WATCHDOG"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "WatchDog"); + EXPECT_STREQ(info.reason, "WATCHDOG_TIMER_RESET"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_PowerOnReset) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(hwReason.mappedReason, "POWER_ON"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "PowerOn"); + EXPECT_STREQ(info.reason, "POWER_ON_RESET"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_HardwareMappings_MultipleCases) { + struct MappingCase { + const char* mapped; + const char* expectedSource; + const char* expectedReason; + }; + + const MappingCase cases[] = { + {"SECURITY_MASTER_RESET", "SecurityReboot", "SECURITY_MASTER_RESET"}, + {"CPU_EJTAG_RESET", "CPU EJTAG", "CPU_EJTAG_RESET"}, + {"SCPU_EJTAG_RESET", "CPU EJTAG", "CPU_EJTAG_RESET"}, + {"GEN_WATCHDOG_1_RESET", "WatchDog", "WATCHDOG_TIMER_RESET"}, + {"AUX_CHIP_EDGE_RESET_0", "Aux Chip Edge", "AUX_CHIP_EDGE_RESET"}, + {"AUX_CHIP_LEVEL_RESET_1", "Aux Chip Level", "AUX_CHIP_LEVEL_RESET"}, + {"MPM_RESET", "MPM", "MPM_RESET"}, + {"OVERVOLTAGE", "OverVoltage", "OVERVOLTAGE_RESET"}, + {"UNDERVOLTAGE_0_RESET", "LowVoltage", "UNDERVOLTAGE_RESET"} + }; + + for (const auto& testCase : cases) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(hwReason.mappedReason, testCase.mapped); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, testCase.expectedSource); + EXPECT_STREQ(info.reason, testCase.expectedReason); + } +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_UnknownCustomDefaultsToFirmwareFailure) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(info.customReason, "UNLISTED_INITIATOR"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.reason, "FIRMWARE_FAILURE"); + EXPECT_STREQ(info.source, "Unknown"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_FallbackToSoftwareReboot) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "SoftwareReboot"); + EXPECT_STREQ(info.reason, "SOFTWARE_MASTER_RESET"); + EXPECT_STREQ(info.customReason, "SOFTWARE_MASTER_RESET"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_HardwareUnknownMapsHardPower) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(hwReason.mappedReason, "UNKNOWN"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "Hard Power Reset"); + EXPECT_STREQ(info.reason, "HARD_POWER"); + EXPECT_STREQ(info.customReason, "Hardware Register - NULL"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_BrcmRawReasonSetsPrefixedCustomReason) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(ctx.soc, "BRCM"); + strcpy(hwReason.mappedReason, "SOFTWARE_MASTER_RESET"); + strcpy(hwReason.rawReason, "watchdog_reset"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.customReason, "Hardware Register - WATCHDOG_RESET"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_AmlogicPresetReasonDoesNotBypassClassification) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(ctx.soc, "AMLOGIC"); + strcpy(info.reason, "PRESET_REASON"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.reason, "SOFTWARE_MASTER_RESET"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_MtkPresetReasonDoesNotBypassClassification) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(ctx.soc, "MTK"); + strcpy(info.reason, "MTK_PRESET"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.reason, "SOFTWARE_MASTER_RESET"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_FirmwareFailureWithInitiatorAndDetails) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + fwFailure.detected = true; + strcpy(fwFailure.initiator, "EcmLogger"); + strcpy(fwFailure.details, "EcmLogger: ECM crash detected"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "EcmLogger"); + EXPECT_STREQ(info.reason, "FIRMWARE_FAILURE"); + EXPECT_STREQ(info.otherReason, "EcmLogger: ECM crash detected"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_CustomReasonMaintenanceLiteral) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(info.customReason, "MAINTENANCE_REBOOT"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.reason, "MAINTENANCE_REBOOT"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_BrcmMappedReasonPrefixedWhenRawMissing) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(ctx.soc, "BRCM"); + strcpy(hwReason.mappedReason, "POWER_ON"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.customReason, "Hardware Register - POWER_ON"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_BrcmUnknownMappedReasonHardPower) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(ctx.soc, "BRCM"); + strcpy(hwReason.mappedReason, "UNKNOWN"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "Hard Power Reset"); + EXPECT_STREQ(info.reason, "HARD_POWER"); + EXPECT_STREQ(info.customReason, "Hardware Register - NULL"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_UnknownMappedReasonNonBrcm) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(ctx.soc, "RTK"); + strcpy(hwReason.mappedReason, "UNKNOWN"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "Hard Power Reset"); + EXPECT_STREQ(info.reason, "HARD_POWER"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_HardwareUnknownStringNonEmpty) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(hwReason.mappedReason, "UNCLASSIFIED_REASON"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "Unknown"); + EXPECT_STREQ(info.reason, "UNCLASSIFIED_REASON"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_HardwareMappings_AdditionalCases) { + struct MappingCase { + const char* mapped; + const char* expectedSource; + const char* expectedReason; + }; + + const MappingCase cases[] = { + {"MAIN_CHIP_INPUT_RESET", "Main Chip", "MAIN_CHIP_INPUT_RESET"}, + {"MAIN_CHIP_RESET_INPUT", "Main Chip", "MAIN_CHIP_RESET_INPUT"}, + {"TAP_IN_SYSTEM_RESET", "Tap-In System", "TAP_IN_SYSTEM_RESET"}, + {"FRONT_PANEL_4SEC_RESET", "FrontPanel Button", "FRONT_PANEL_RESET"}, + {"S3_WAKEUP_RESET", "Standby Wakeup", "S3_WAKEUP_RESET"}, + {"SMARTCARD_INSERT_RESET", "SmartCard Insert", "SMARTCARD_INSERT_RESET"}, + {"OVERTEMP", "OverTemperature", "OVERTEMP_RESET"}, + {"PCIE_0_HOT_BOOT_RESET", "PCIE Boot", "PCIE_HOT_BOOT_RESET"} + }; + + for (const auto& testCase : cases) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(hwReason.mappedReason, testCase.mapped); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, testCase.expectedSource); + EXPECT_STREQ(info.reason, testCase.expectedReason); + } +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_BrcmBroadcomAliasCustomPrefix) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(ctx.soc, "BROADCOM"); + strcpy(hwReason.mappedReason, "WATCHDOG_RESET"); + strcpy(hwReason.rawReason, "gen_watchdog_reset"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.customReason, "Hardware Register - GEN_WATCHDOG_RESET"); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_HardwareMappings_AliasCases) { + struct MappingCase { + const char* mapped; + const char* expectedSource; + const char* expectedReason; + }; + + const MappingCase cases[] = { + {"KERNEL_PANIC_RESET", "Kernel", "KERNEL_PANIC"}, + {"SOFTWARE_RESET", "SoftwareReboot", "SOFTWARE_MASTER_RESET"}, + {"HARDWARE", "PowerOn", "POWER_ON_RESET"}, + {"OVERHEAT", "OverTemperature", "OVERTEMP_RESET"}, + {"UNDERVOLTAGE_1_RESET", "LowVoltage", "UNDERVOLTAGE_RESET"}, + {"PCIE_1_HOT_BOOT_RESET", "PCIE Boot", "PCIE_HOT_BOOT_RESET"}, + {"AUX_CHIP_EDGE_RESET", "Aux Chip Edge", "AUX_CHIP_EDGE_RESET"}, + {"AUX_CHIP_LEVEL_RESET", "Aux Chip Level", "AUX_CHIP_LEVEL_RESET"} + }; + + for (const auto& testCase : cases) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(hwReason.mappedReason, testCase.mapped); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, testCase.expectedSource); + EXPECT_STREQ(info.reason, testCase.expectedReason); + } +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_RtkAnnotatesKernelLog) { + system("mkdir -p /opt/logs"); + remove("/opt/logs/messages.txt"); + + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + strcpy(ctx.soc, "RTK"); + strcpy(hwReason.mappedReason, "WATCHDOG"); + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "WatchDog"); + EXPECT_STREQ(info.reason, "WATCHDOG_TIMER_RESET"); + + FILE* fp = fopen("/opt/logs/messages.txt", "r"); + if (!fp) { + GTEST_SKIP() << "Cannot read /opt/logs/messages.txt in this environment"; + } + char buf[512] = {0}; + size_t n = fread(buf, 1, sizeof(buf) - 1, fp); + buf[n] = '\0'; + fclose(fp); + EXPECT_NE(strstr(buf, "PreviousRebootReason: watchdog_timer_reset"), nullptr); +} + +TEST_F(RebootClassifyTest, classify_reboot_reason_UnknownWhenPanicFlagSetButNotDetectedEarlier) { + RebootInfo info; + EnvContext ctx; + HardwareReason hwReason; + PanicInfo panicInfo; + FirmwareFailure fwFailure; + + memset(&info, 0, sizeof(RebootInfo)); + memset(&ctx, 0, sizeof(EnvContext)); + memset(&hwReason, 0, sizeof(HardwareReason)); + memset(&panicInfo, 0, sizeof(PanicInfo)); + memset(&fwFailure, 0, sizeof(FirmwareFailure)); + + panicInfo.detected = true; + info.customReason[0] = '\0'; + hwReason.mappedReason[0] = '\0'; + + int result = classify_reboot_reason(&info, &ctx, &hwReason, &panicInfo, &fwFailure); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "Kernel"); + EXPECT_STREQ(info.reason, "KERNEL_PANIC"); +} + + +GTEST_API_ int main(int argc, char *argv[]) { + char testresults_fullfilepath[GTEST_REPORT_FILEPATH_SIZE]; + + memset(testresults_fullfilepath, 0, GTEST_REPORT_FILEPATH_SIZE); + snprintf(testresults_fullfilepath, GTEST_REPORT_FILEPATH_SIZE, "json:%s%s", + GTEST_DEFAULT_RESULT_FILEPATH, GTEST_DEFAULT_RESULT_FILENAME); + + ::testing::GTEST_FLAG(output) = testresults_fullfilepath; + ::testing::InitGoogleTest(&argc, argv); + + cout << "Starting REBOOT_CLASSIFY GTEST ===================>" << endl; + return RUN_ALL_TESTS(); +} + diff --git a/unittest/reboot_json_gtest.cpp b/unittest/reboot_json_gtest.cpp new file mode 100644 index 0000000..1708042 --- /dev/null +++ b/unittest/reboot_json_gtest.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +extern "C" { + #include "update-reboot-info.h" +} + +#define GTEST_DEFAULT_RESULT_FILEPATH "/tmp/Gtest_Report/" +#define GTEST_DEFAULT_RESULT_FILENAME "reboot_json_gtest_report.json" +#define GTEST_REPORT_FILEPATH_SIZE 256 + +TEST(JsonSmokeTest, write_reboot_info_InvalidParams) { + EXPECT_EQ(write_reboot_info(nullptr, nullptr), ERROR_GENERAL); +} + +TEST(JsonSmokeTest, write_hardpower_InvalidParams) { + EXPECT_EQ(write_hardpower(nullptr, nullptr), ERROR_GENERAL); +} + +TEST(JsonSmokeTest, acquire_and_release_lock_Success) { + const char* lockFile = "/tmp/reboot_test_lockfile.lock"; + EXPECT_EQ(acquire_lock(lockFile), SUCCESS); + EXPECT_EQ(release_lock(lockFile), SUCCESS); + remove(lockFile); +} + +TEST(JsonSmokeTest, write_reboot_info_Success) { + const char* outFile = "/tmp/reboot_test_info.json"; + RebootInfo info; + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T00:00:00Z", sizeof(info.timestamp) - 1); + strncpy(info.source, "Kernel", sizeof(info.source) - 1); + strncpy(info.reason, "KERNEL_PANIC", sizeof(info.reason) - 1); + strncpy(info.customReason, "Hardware Register - KERNEL_PANIC", sizeof(info.customReason) - 1); + strncpy(info.otherReason, "Reboot due to Kernel Panic captured by Oops Dump", sizeof(info.otherReason) - 1); + + EXPECT_EQ(write_reboot_info(outFile, &info), SUCCESS); + + FILE* fp = fopen(outFile, "r"); + ASSERT_NE(fp, nullptr); + char content[1024] = {0}; + size_t n = fread(content, 1, sizeof(content) - 1, fp); + content[n] = '\0'; + fclose(fp); + + EXPECT_NE(strstr(content, "\"timestamp\":\"2026-03-10T00:00:00Z\""), nullptr); + EXPECT_NE(strstr(content, "\"source\":\"Kernel\""), nullptr); + EXPECT_NE(strstr(content, "\"reason\":\"KERNEL_PANIC\""), nullptr); + remove(outFile); +} + +TEST(JsonSmokeTest, write_hardpower_Success) { + const char* outFile = "/tmp/reboot_test_hardpower.json"; + const char* timestamp = "2026-03-10T01:23:45Z"; + EXPECT_EQ(write_hardpower(outFile, timestamp), SUCCESS); + + FILE* fp = fopen(outFile, "r"); + ASSERT_NE(fp, nullptr); + char content[256] = {0}; + size_t n = fread(content, 1, sizeof(content) - 1, fp); + content[n] = '\0'; + fclose(fp); + + EXPECT_NE(strstr(content, "\"lastHardPowerReset\":\"2026-03-10T01:23:45Z\""), nullptr); + remove(outFile); +} + +GTEST_API_ int main(int argc, char *argv[]) { + char testresults_fullfilepath[GTEST_REPORT_FILEPATH_SIZE]; + memset(testresults_fullfilepath, 0, GTEST_REPORT_FILEPATH_SIZE); + snprintf(testresults_fullfilepath, GTEST_REPORT_FILEPATH_SIZE, "json:%s%s", + GTEST_DEFAULT_RESULT_FILEPATH, GTEST_DEFAULT_RESULT_FILENAME); + ::testing::GTEST_FLAG(output) = testresults_fullfilepath; + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/unittest/reboot_log_parser_gtest.cpp b/unittest/reboot_log_parser_gtest.cpp new file mode 100644 index 0000000..4ace876 --- /dev/null +++ b/unittest/reboot_log_parser_gtest.cpp @@ -0,0 +1,747 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool g_mock_props_enabled = false; +static bool g_mock_fs_enabled = false; +static std::unordered_map g_mock_path_map; +static char g_soc_value[64] = {0}; +static char g_build_type_value[64] = {0}; +static char g_device_type_value[64] = {0}; +static char g_platco_value[16] = {0}; +static char g_llama_value[16] = {0}; +static char g_stt_value[16] = {0}; + +extern "C" { + #include "update-reboot-info.h" + #include "rdk_fwdl_utils.h" + #include "rdk_debug.h" + + int g_rdk_logger_enabled = 0; + + int getDevicePropertyData(const char* key __attribute__((unused)), + char* value __attribute__((unused)), + int size __attribute__((unused))) { + if (g_mock_props_enabled && key && value && size > 0) { + const char* selected = nullptr; + if (strcmp(key, "SOC") == 0 && g_soc_value[0] != '\0') { + selected = g_soc_value; + } else if (strcmp(key, "BUILD_TYPE") == 0 && g_build_type_value[0] != '\0') { + selected = g_build_type_value; + } else if (strcmp(key, "DEVICE_TYPE") == 0 && g_device_type_value[0] != '\0') { + selected = g_device_type_value; + } else if (strcmp(key, "PLATCO_SUPPORT") == 0 && g_platco_value[0] != '\0') { + selected = g_platco_value; + } else if (strcmp(key, "LLAMA_SUPPORT") == 0 && g_llama_value[0] != '\0') { + selected = g_llama_value; + } else if (strcmp(key, "REBOOT_INFO_STT_SUPPORT") == 0 && g_stt_value[0] != '\0') { + selected = g_stt_value; + } + + if (selected) { + strncpy(value, selected, (size_t)size - 1); + value[size - 1] = '\0'; + return UTILS_SUCCESS; + } + } + return UTILS_FAILURE; // trigger defaults in parse_device_properties + } + + void t2_event_d(const char* marker __attribute__((unused)), + int value __attribute__((unused))) { } + void t2_event_s(const char* marker __attribute__((unused)), + const char* value __attribute__((unused))) { } +} + +extern "C" int access(const char* pathname, int mode) { + if (g_mock_fs_enabled && pathname) { + auto it = g_mock_path_map.find(pathname); + if (it != g_mock_path_map.end()) { + (void)mode; + return 0; + } + } + return faccessat(AT_FDCWD, pathname, mode, 0); +} + +static int mode_to_flags(const char* mode) { + if (!mode || mode[0] == '\0') { + return -1; + } + if (strcmp(mode, "r") == 0) return O_RDONLY; + if (strcmp(mode, "r+") == 0) return O_RDWR; + if (strcmp(mode, "w") == 0) return O_WRONLY | O_CREAT | O_TRUNC; + if (strcmp(mode, "w+") == 0) return O_RDWR | O_CREAT | O_TRUNC; + if (strcmp(mode, "a") == 0) return O_WRONLY | O_CREAT | O_APPEND; + if (strcmp(mode, "a+") == 0) return O_RDWR | O_CREAT | O_APPEND; + return -1; +} + +extern "C" FILE* fopen(const char* pathname, const char* mode) { + const char* resolvedPath = pathname; + if (g_mock_fs_enabled && pathname) { + auto it = g_mock_path_map.find(pathname); + if (it != g_mock_path_map.end()) { + resolvedPath = it->second.c_str(); + } + } + + int flags = mode_to_flags(mode); + if (flags < 0) { + errno = EINVAL; + return nullptr; + } + + int fd = open(resolvedPath, flags, 0644); + if (fd < 0) { + return nullptr; + } + + FILE* fp = fdopen(fd, mode); + if (!fp) { + close(fd); + return nullptr; + } + return fp; +} + +#define GTEST_DEFAULT_RESULT_FILEPATH "/tmp/Gtest_Report/" +#define GTEST_DEFAULT_RESULT_FILENAME "reboot_log_parser_gtest_report.json" +#define GTEST_REPORT_FILEPATH_SIZE 256 + +using namespace testing; +using namespace std; + +class LogParserTest : public ::testing::Test { +protected: + void SetUp() override { + // Create test directory + system("mkdir -p /tmp/reboot_test"); + g_mock_props_enabled = false; + g_mock_fs_enabled = false; + g_mock_path_map.clear(); + memset(g_soc_value, 0, sizeof(g_soc_value)); + memset(g_build_type_value, 0, sizeof(g_build_type_value)); + memset(g_device_type_value, 0, sizeof(g_device_type_value)); + memset(g_platco_value, 0, sizeof(g_platco_value)); + memset(g_llama_value, 0, sizeof(g_llama_value)); + memset(g_stt_value, 0, sizeof(g_stt_value)); + } + + void TearDown() override { + // Clean up test files + system("rm -rf /tmp/reboot_test"); + g_mock_fs_enabled = false; + g_mock_path_map.clear(); + } + + void createTestLogFile(const char* path, const char* content) { + FILE* fp = fopen(path, "w"); + ASSERT_NE(fp, nullptr); + fprintf(fp, "%s", content); + fclose(fp); + } + + void setupMockFile(const char* virtualPath, const char* realPath, const char* content) { + FILE* fp = fopen(realPath, "w"); + ASSERT_NE(fp, nullptr); + fprintf(fp, "%s", content); + fclose(fp); + g_mock_path_map[virtualPath] = realPath; + } +}; + +// Tests for parse_device_properties +TEST_F(LogParserTest, parse_device_properties_NullContext) { + EXPECT_EQ(parse_device_properties(nullptr), ERROR_GENERAL); +} + +TEST_F(LogParserTest, parse_device_properties_Success) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + + // Create a test device.properties file + createTestLogFile("/tmp/reboot_test/device.properties", + "SOC=BRCM\n" + "BUILD_TYPE=prod\n" + "DEVICE_TYPE=stb\n" + "PLATCO_SUPPORT=true\n" + "LLAMA_SUPPORT=false\n"); + + // Since parse_device_properties reads from /etc/device.properties, + // we test the fallback parsing path + int result = parse_device_properties(&ctx); + EXPECT_EQ(result, SUCCESS); +} + +TEST_F(LogParserTest, parse_device_properties_InitializesDefaults) { + EnvContext ctx; + memset(&ctx, 0xff, sizeof(EnvContext)); // Fill with garbage + + int result = parse_device_properties(&ctx); + EXPECT_EQ(result, SUCCESS); + EXPECT_FALSE(ctx.platcoSupport); + EXPECT_FALSE(ctx.llamaSupport); + EXPECT_FALSE(ctx.rebootInfoSttSupport); +} + +TEST_F(LogParserTest, parse_device_properties_UsesMockPropertyValues) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + + g_mock_props_enabled = true; + strncpy(g_soc_value, "BRCM", sizeof(g_soc_value) - 1); + strncpy(g_build_type_value, "prod", sizeof(g_build_type_value) - 1); + strncpy(g_device_type_value, "stb", sizeof(g_device_type_value) - 1); + strncpy(g_platco_value, "true", sizeof(g_platco_value) - 1); + strncpy(g_llama_value, "false", sizeof(g_llama_value) - 1); + strncpy(g_stt_value, "true", sizeof(g_stt_value) - 1); + + int result = parse_device_properties(&ctx); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(ctx.soc, "BRCM"); + EXPECT_STREQ(ctx.buildType, "prod"); + EXPECT_STREQ(ctx.device_type, "stb"); + EXPECT_TRUE(ctx.platcoSupport); + EXPECT_FALSE(ctx.llamaSupport); + EXPECT_TRUE(ctx.rebootInfoSttSupport); +} + +TEST_F(LogParserTest, parse_device_properties_UppercaseTrueValues) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + + g_mock_props_enabled = true; + strncpy(g_soc_value, "RTK", sizeof(g_soc_value) - 1); + strncpy(g_device_type_value, "stb", sizeof(g_device_type_value) - 1); + strncpy(g_platco_value, "TRUE", sizeof(g_platco_value) - 1); + strncpy(g_llama_value, "TRUE", sizeof(g_llama_value) - 1); + strncpy(g_stt_value, "TRUE", sizeof(g_stt_value) - 1); + + int result = parse_device_properties(&ctx); + EXPECT_EQ(result, SUCCESS); + EXPECT_TRUE(ctx.platcoSupport); + EXPECT_TRUE(ctx.llamaSupport); + EXPECT_TRUE(ctx.rebootInfoSttSupport); +} + +TEST_F(LogParserTest, parse_device_properties_FallbackFromEtcDeviceProperties) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + + g_mock_props_enabled = false; + setupMockFile("/etc/device.properties", "/tmp/reboot_test/device_props_fallback", + "SOC=AMLOGIC\n" + "DEVICE_NAME=tv_device\n"); + g_mock_fs_enabled = true; + + int result = parse_device_properties(&ctx); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(ctx.soc, "AMLOGIC"); + EXPECT_STREQ(ctx.device_type, "tv_device"); +} + +TEST_F(LogParserTest, parse_device_properties_FallbackUsesDeviceTypeKey) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + + g_mock_props_enabled = false; + setupMockFile("/etc/device.properties", "/tmp/reboot_test/device_props_device_type", + "SOC=BRCM\n" + "DEVICE_TYPE=hybrid_stb\n"); + g_mock_fs_enabled = true; + + int result = parse_device_properties(&ctx); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(ctx.soc, "BRCM"); + EXPECT_STREQ(ctx.device_type, "hybrid_stb"); +} + +TEST_F(LogParserTest, parse_device_properties_FallbackOpenFailureReturnsFailure) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + + g_mock_props_enabled = false; + g_mock_path_map["/etc/device.properties"] = "/tmp/reboot_test/nonexistent_props"; + g_mock_fs_enabled = true; + + int result = parse_device_properties(&ctx); + EXPECT_EQ(result, FAILURE); +} + +TEST_F(LogParserTest, free_env_context_ResetsMemory) { + EnvContext ctx; + memset(&ctx, 0xAB, sizeof(EnvContext)); + free_env_context(&ctx); + EXPECT_EQ(ctx.soc[0], '\0'); + EXPECT_EQ(ctx.buildType[0], '\0'); + EXPECT_EQ(ctx.device_type[0], '\0'); +} + +// Tests for should_update_reboot_info +TEST_F(LogParserTest, update_reboot_info_NullContext) { + EXPECT_EQ(update_reboot_info(nullptr), 0); +} + +TEST_F(LogParserTest, update_reboot_info_PlatcoRequiresFlagsAlways) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + ctx.platcoSupport = true; + + system("rm -f /tmp/stt_received /tmp/rebootInfo_Updated /tmp/Update_rebootInfo_invoked"); + EXPECT_EQ(update_reboot_info(&ctx), 0); + + createTestLogFile("/tmp/stt_received", "1\n"); + createTestLogFile("/tmp/rebootInfo_Updated", "1\n"); + EXPECT_EQ(update_reboot_info(&ctx), 1); + + system("rm -f /tmp/stt_received /tmp/rebootInfo_Updated /tmp/Update_rebootInfo_invoked"); +} + +TEST_F(LogParserTest, update_reboot_info_RequiresFlagsWithInvokedFlagPresent) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + ctx.platcoSupport = true; + + // Create invoked flag + createTestLogFile("/tmp/Update_rebootInfo_invoked", "1\n"); + + // Remove other flags + system("rm -f /tmp/stt_received /tmp/rebootInfo_Updated"); + + // Should block update without required flags + EXPECT_EQ(update_reboot_info(&ctx), 0); + + // Clean up + system("rm -f /tmp/Update_rebootInfo_invoked"); +} + +TEST_F(LogParserTest, update_reboot_info_AllowsWithFlags) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + ctx.platcoSupport = true; + + // Create all required flags + createTestLogFile("/tmp/Update_rebootInfo_invoked", "1\n"); + createTestLogFile("/tmp/stt_received", "1\n"); + createTestLogFile("/tmp/rebootInfo_Updated", "1\n"); + + EXPECT_EQ(update_reboot_info(&ctx), 1); + + // Clean up + system("rm -f /tmp/Update_rebootInfo_invoked /tmp/stt_received /tmp/rebootInfo_Updated"); +} + +TEST_F(LogParserTest, update_reboot_info_NonPlatcoRequiresFlags) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + + system("rm -f /tmp/stt_received /tmp/rebootInfo_Updated"); + EXPECT_EQ(update_reboot_info(&ctx), 0); + + createTestLogFile("/tmp/stt_received", "1\n"); + createTestLogFile("/tmp/rebootInfo_Updated", "1\n"); + EXPECT_EQ(update_reboot_info(&ctx), 1); + + system("rm -f /tmp/stt_received /tmp/rebootInfo_Updated"); +} + +TEST_F(LogParserTest, update_reboot_info_LlamaFirstInvocationAllowed) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + ctx.llamaSupport = true; + + system("rm -f /tmp/stt_received /tmp/rebootInfo_Updated /tmp/Update_rebootInfo_invoked"); + EXPECT_EQ(update_reboot_info(&ctx), 0); + + createTestLogFile("/tmp/stt_received", "1\n"); + createTestLogFile("/tmp/rebootInfo_Updated", "1\n"); + EXPECT_EQ(update_reboot_info(&ctx), 1); + + system("rm -f /tmp/stt_received /tmp/rebootInfo_Updated /tmp/Update_rebootInfo_invoked"); +} + +TEST_F(LogParserTest, update_reboot_info_LlamaInvokedNeedsFlags) { + EnvContext ctx; + memset(&ctx, 0, sizeof(EnvContext)); + ctx.llamaSupport = true; + + createTestLogFile("/tmp/Update_rebootInfo_invoked", "1\n"); + system("rm -f /tmp/stt_received /tmp/rebootInfo_Updated"); + EXPECT_EQ(update_reboot_info(&ctx), 0); + + createTestLogFile("/tmp/stt_received", "1\n"); + createTestLogFile("/tmp/rebootInfo_Updated", "1\n"); + EXPECT_EQ(update_reboot_info(&ctx), 1); + + system("rm -f /tmp/Update_rebootInfo_invoked /tmp/stt_received /tmp/rebootInfo_Updated"); +} + +// Tests for parse_legacy_log +TEST_F(LogParserTest, parse_legacy_log_NullParameters) { + RebootInfo info; + EXPECT_EQ(parse_legacy_log(nullptr, &info), ERROR_GENERAL); + EXPECT_EQ(parse_legacy_log("/tmp/test.log", nullptr), ERROR_GENERAL); +} + +TEST_F(LogParserTest, parse_legacy_log_FileNotFound) { + RebootInfo info; + EXPECT_EQ(parse_legacy_log("/tmp/nonexistent_file.log", &info), ERROR_FILE_NOT_FOUND); +} + +TEST_F(LogParserTest, parse_legacy_log_EmptyPathFileNotFound) { + RebootInfo info; + memset(&info, 0, sizeof(info)); + EXPECT_EQ(parse_legacy_log("", &info), ERROR_FILE_NOT_FOUND); +} + +TEST_F(LogParserTest, parse_legacy_log_EmptyFile) { + const char* testFile = "/tmp/reboot_test/empty.log"; + createTestLogFile(testFile, ""); + + RebootInfo info; + int result = parse_legacy_log(testFile, &info); + EXPECT_EQ(result, ERROR_PARSE_FAILED); +} + + +TEST_F(LogParserTest, parse_legacy_log_Success) { + const char* testFile = "/tmp/reboot_test/reboot.log"; + createTestLogFile(testFile, + "PreviousRebootInitiatedBy: SystemService\n" + "PreviousRebootTime: 2025-11-28 10:30:00 UTC\n" + "PreviousCustomReason: ScheduledReboot\n" + "PreviousOtherReason: Reboot due to scheduled maintenance\n"); + + RebootInfo info; + memset(&info, 0, sizeof(RebootInfo)); + + int result = parse_legacy_log(testFile, &info); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "SystemService"); + EXPECT_STREQ(info.timestamp, "2025-11-28 10:30:00 UTC"); + EXPECT_STREQ(info.customReason, "ScheduledReboot"); + EXPECT_STREQ(info.otherReason, "Reboot due to scheduled maintenance"); +} + +TEST_F(LogParserTest, parse_legacy_log_PartialData) { + const char* testFile = "/tmp/reboot_test/partial.log"; + createTestLogFile(testFile, + "PreviousRebootInitiatedBy: WatchDog\n" + "Some other line\n" + "PreviousCustomReason: WATCHDOG_RESET\n"); + + RebootInfo info; + memset(&info, 0, sizeof(RebootInfo)); + + int result = parse_legacy_log(testFile, &info); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "WatchDog"); + EXPECT_STREQ(info.customReason, "WATCHDOG_RESET"); +} + +TEST_F(LogParserTest, parse_legacy_log_OnlyOtherReason) { + const char* testFile = "/tmp/reboot_test/other_only.log"; + createTestLogFile(testFile, + "junk line\n" + "PreviousOtherReason: some detail\n"); + + RebootInfo info; + memset(&info, 0, sizeof(RebootInfo)); + + int result = parse_legacy_log(testFile, &info); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.otherReason, "some detail"); +} + +TEST_F(LogParserTest, parse_legacy_log_TrimmedValuesWithWhitespace) { + const char* testFile = "/tmp/reboot_test/trimmed_fields.log"; + createTestLogFile(testFile, + "PreviousRebootInitiatedBy:\t ServiceMgr \n" + "PreviousRebootTime: 2026-03-10 10:11:12 UTC\r\n" + "PreviousCustomReason:\tMaintenanceReboot\t\n" + "PreviousOtherReason: reason with spaces \r\n"); + + RebootInfo info; + memset(&info, 0, sizeof(RebootInfo)); + + int result = parse_legacy_log(testFile, &info); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(info.source, "ServiceMgr"); + EXPECT_STREQ(info.timestamp, "2026-03-10 10:11:12 UTC"); + EXPECT_STREQ(info.customReason, "MaintenanceReboot"); + EXPECT_STREQ(info.otherReason, "reason with spaces"); +} + +// Tests for read_brcm_previous_reboot_reason +TEST_F(LogParserTest, read_brcm_previous_reboot_reason_NullParameter) { + EXPECT_EQ(read_brcm_previous_reboot_reason(nullptr), ERROR_GENERAL); +} + +TEST_F(LogParserTest, read_brcm_previous_reboot_reason_FileNotFound) { + HardwareReason hw; + int result = read_brcm_previous_reboot_reason(&hw); + EXPECT_EQ(result, ERROR_FILE_NOT_FOUND); +} + +TEST_F(LogParserTest, read_brcm_previous_reboot_reason_ParsesPrimaryTokenUppercase) { + HardwareReason hw; + memset(&hw, 0, sizeof(hw)); + setupMockFile("/proc/brcm/previous_reboot_reason", "/tmp/reboot_test/brcm_prev", "watchdog_reset,extra\n"); + g_mock_fs_enabled = true; + + int result = read_brcm_previous_reboot_reason(&hw); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(hw.rawReason, "watchdog_reset,extra"); + EXPECT_STREQ(hw.mappedReason, "WATCHDOG_RESET"); +} + +TEST_F(LogParserTest, read_brcm_previous_reboot_reason_EmptyFileReturnsNotFound) { + HardwareReason hw; + memset(&hw, 0, sizeof(hw)); + setupMockFile("/proc/brcm/previous_reboot_reason", "/tmp/reboot_test/brcm_empty", ""); + g_mock_fs_enabled = true; + + int result = read_brcm_previous_reboot_reason(&hw); + EXPECT_EQ(result, ERROR_FILE_NOT_FOUND); +} + +// Tests for read_rtk_wakeup_reason +TEST_F(LogParserTest, read_rtk_wakeup_reason_NullParameter) { + EXPECT_EQ(read_rtk_wakeup_reason(nullptr), ERROR_GENERAL); +} + +TEST_F(LogParserTest, read_rtk_wakeup_reason_SystemCmdlinePath) { + HardwareReason hw; + memset(&hw, 0, sizeof(hw)); + + int result = read_rtk_wakeup_reason(&hw); + EXPECT_TRUE(result == SUCCESS || result == FAILURE); + if (result == SUCCESS) { + EXPECT_NE(hw.mappedReason[0], '\0'); + } +} + +TEST_F(LogParserTest, read_rtk_wakeup_reason_ParsesWakeupReason) { + HardwareReason hw; + memset(&hw, 0, sizeof(hw)); + setupMockFile("/proc/cmdline", "/tmp/reboot_test/cmdline", "foo=1 wakeupreason=thermal_reboot bar=2\n"); + g_mock_fs_enabled = true; + + int result = read_rtk_wakeup_reason(&hw); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(hw.mappedReason, "THERMAL_REBOOT"); +} + +TEST_F(LogParserTest, read_rtk_wakeup_reason_NoKeyFailure) { + HardwareReason hw; + memset(&hw, 0, sizeof(hw)); + setupMockFile("/proc/cmdline", "/tmp/reboot_test/cmdline_no_key", "foo=1 bar=2\n"); + g_mock_fs_enabled = true; + + int result = read_rtk_wakeup_reason(&hw); + EXPECT_EQ(result, FAILURE); +} + +// Tests for read_amlogic_reset_reason +TEST_F(LogParserTest, read_amlogic_reset_reason_NullParameters) { + HardwareReason hw; + RebootInfo info; + + EXPECT_EQ(read_amlogic_reset_reason(nullptr, &info), ERROR_GENERAL); + EXPECT_EQ(read_amlogic_reset_reason(&hw, nullptr), ERROR_GENERAL); +} + +TEST_F(LogParserTest, read_amlogic_reset_reason_FileNotFound) { + HardwareReason hw; + RebootInfo info; + int result = read_amlogic_reset_reason(&hw, &info); + EXPECT_EQ(result, ERROR_FILE_NOT_FOUND); +} + +TEST_F(LogParserTest, read_amlogic_reset_reason_KernelPanicCase) { + HardwareReason hw; + RebootInfo info; + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T20:00:00Z", sizeof(info.timestamp) - 1); + setupMockFile("/sys/devices/platform/aml_pm/reset_reason", "/tmp/reboot_test/amlogic_case12", "12\n"); + g_mock_fs_enabled = true; + + int result = read_amlogic_reset_reason(&hw, &info); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(hw.mappedReason, "KERNEL_PANIC"); + EXPECT_STREQ(info.reason, "KERNEL_PANIC"); + EXPECT_STREQ(info.timestamp, "2026-03-10T20:00:00Z"); +} + +TEST_F(LogParserTest, read_amlogic_reset_reason_DefaultUnknownCase) { + HardwareReason hw; + RebootInfo info; + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + setupMockFile("/sys/devices/platform/aml_pm/reset_reason", "/tmp/reboot_test/amlogic_case99", "99\n"); + g_mock_fs_enabled = true; + + int result = read_amlogic_reset_reason(&hw, &info); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(hw.mappedReason, "UNKNOWN_RESET"); + EXPECT_STREQ(info.reason, "UNKNOWN_RESET"); +} + +TEST_F(LogParserTest, read_amlogic_reset_reason_AllKnownCasesTable) { + struct AmlCase { + int code; + const char* expectedReason; + }; + + const AmlCase cases[] = { + {0, "POWER_ON_RESET"}, + {1, "SOFTWARE_MASTER_RESET"}, + {2, "FACTORY_RESET"}, + {3, "UPDATE_BOOT"}, + {4, "FAST_BOOT"}, + {5, "SUSPEND_BOOT"}, + {6, "HIBERNATE_BOOT"}, + {7, "FASTBOOT_BOOTLOADER"}, + {8, "SHUTDOWN_REBOOT"}, + {9, "RPMPB_REBOOT"}, + {10, "THERMAL_REBOOT"}, + {11, "CRASH_REBOOT"}, + {12, "KERNEL_PANIC"}, + {13, "WATCHDOG_REBOOT"}, + {14, "AMLOGIC_DDR_SHA2_REBOOT"}, + {15, "FFV_REBOOT"} + }; + + for (const auto& testCase : cases) { + HardwareReason hw; + RebootInfo info; + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T21:00:00Z", sizeof(info.timestamp) - 1); + + char value[16] = {0}; + snprintf(value, sizeof(value), "%d\n", testCase.code); + setupMockFile("/sys/devices/platform/aml_pm/reset_reason", "/tmp/reboot_test/amlogic_case_tbl", value); + g_mock_fs_enabled = true; + + int result = read_amlogic_reset_reason(&hw, &info); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(hw.mappedReason, testCase.expectedReason); + EXPECT_STREQ(info.reason, testCase.expectedReason); + EXPECT_STREQ(info.timestamp, "2026-03-10T21:00:00Z"); + } +} + +// Tests for read_mtk_reset_reason +TEST_F(LogParserTest, read_mtk_reset_reason_NullParameters) { + HardwareReason hw; + RebootInfo info; + + EXPECT_EQ(read_mtk_reset_reason(nullptr, &info), ERROR_GENERAL); + EXPECT_EQ(read_mtk_reset_reason(&hw, nullptr), ERROR_GENERAL); +} + +TEST_F(LogParserTest, read_mtk_reset_reason_FileNotFound) { + HardwareReason hw; + RebootInfo info; + int result = read_mtk_reset_reason(&hw, &info); + EXPECT_EQ(result, FAILURE); +} + +TEST_F(LogParserTest, read_mtk_reset_reason_HexWatchdogCase) { + HardwareReason hw; + RebootInfo info; + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T20:20:00Z", sizeof(info.timestamp) - 1); + setupMockFile("/sys/mtk_pm/boot_reason", "/tmp/reboot_test/mtk_case_e0", "0xE0\n"); + g_mock_fs_enabled = true; + + int result = read_mtk_reset_reason(&hw, &info); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(hw.mappedReason, "WATCHDOG_REBOOT"); + EXPECT_STREQ(info.reason, "WATCHDOG_REBOOT"); + EXPECT_STREQ(info.timestamp, "2026-03-10T20:20:00Z"); +} + +TEST_F(LogParserTest, read_mtk_reset_reason_DecimalPowerOnCase) { + HardwareReason hw; + RebootInfo info; + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + setupMockFile("/sys/mtk_pm/boot_reason", "/tmp/reboot_test/mtk_case_00", "0\n"); + g_mock_fs_enabled = true; + + int result = read_mtk_reset_reason(&hw, &info); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(hw.mappedReason, "POWER_ON_RESET"); + EXPECT_STREQ(info.reason, "POWER_ON_RESET"); +} + +TEST_F(LogParserTest, read_mtk_reset_reason_AllMappedCasesTable) { + struct MtkCase { + const char* value; + const char* expectedReason; + }; + + const MtkCase cases[] = { + {"0x00\n", "POWER_ON_RESET"}, + {"0xD1\n", "SOFTWARE_MASTER_RESET"}, + {"0xE4\n", "THERMAL_REBOOT"}, + {"0xEE\n", "KERNEL_PANIC"}, + {"0xE0\n", "WATCHDOG_REBOOT"}, + {"0x99\n", "UNKNOWN_RESET"} + }; + + for (const auto& testCase : cases) { + HardwareReason hw; + RebootInfo info; + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T21:30:00Z", sizeof(info.timestamp) - 1); + + setupMockFile("/sys/mtk_pm/boot_reason", "/tmp/reboot_test/mtk_case_tbl", testCase.value); + g_mock_fs_enabled = true; + + int result = read_mtk_reset_reason(&hw, &info); + EXPECT_EQ(result, SUCCESS); + EXPECT_STREQ(hw.mappedReason, testCase.expectedReason); + EXPECT_STREQ(info.reason, testCase.expectedReason); + EXPECT_STREQ(info.timestamp, "2026-03-10T21:30:00Z"); + } +} + +TEST_F(LogParserTest, read_rtk_wakeup_reason_EmptyValueStillSuccessPath) { + HardwareReason hw; + memset(&hw, 0, sizeof(hw)); + setupMockFile("/proc/cmdline", "/tmp/reboot_test/cmdline_empty_value", "foo=1 wakeupreason= bar=2\n"); + g_mock_fs_enabled = true; + + int result = read_rtk_wakeup_reason(&hw); + EXPECT_EQ(result, SUCCESS); + EXPECT_EQ(hw.mappedReason[0], '\0'); +} + +GTEST_API_ int main(int argc, char *argv[]) { + char testresults_fullfilepath[GTEST_REPORT_FILEPATH_SIZE]; + + memset(testresults_fullfilepath, 0, GTEST_REPORT_FILEPATH_SIZE); + snprintf(testresults_fullfilepath, GTEST_REPORT_FILEPATH_SIZE, "json:%s%s", + GTEST_DEFAULT_RESULT_FILEPATH, GTEST_DEFAULT_RESULT_FILENAME); + + ::testing::GTEST_FLAG(output) = testresults_fullfilepath; + ::testing::InitGoogleTest(&argc, argv); + + cout << "Starting REBOOT_LOG_PARSER GTEST ===================>" << endl; + return RUN_ALL_TESTS(); +} + diff --git a/unittest/reboot_parodus_gtest.cpp b/unittest/reboot_parodus_gtest.cpp new file mode 100644 index 0000000..967cfc9 --- /dev/null +++ b/unittest/reboot_parodus_gtest.cpp @@ -0,0 +1,362 @@ +#include +#include +#include +extern "C" { + #include "update-reboot-info.h" +} + +#define GTEST_DEFAULT_RESULT_FILEPATH "/tmp/Gtest_Report/" +#define GTEST_DEFAULT_RESULT_FILENAME "reboot_parodus_gtest_report.json" +#define GTEST_REPORT_FILEPATH_SIZE 256 + +TEST(ParodusSmokeTest, append_kernel_reason_InvalidParams) { + EXPECT_EQ(append_kernel_reason(nullptr, nullptr), ERROR_GENERAL); +} + +TEST(ParodusSmokeTest, update_parodus_log_InvalidParams) { + EXPECT_EQ(update_parodus_log(nullptr), ERROR_GENERAL); +} + +TEST(ParodusSmokeTest, copy_keypress_info_InvalidParams) { + EXPECT_EQ(copy_keypress_info(nullptr, nullptr), ERROR_GENERAL); +} + +TEST(ParodusSmokeTest, append_kernel_reason_NonRtkSkips) { + EnvContext ctx; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&info, 0, sizeof(info)); + strncpy(ctx.soc, "BRCM", sizeof(ctx.soc) - 1); + strncpy(info.reason, "SOFTWARE_MASTER_RESET", sizeof(info.reason) - 1); + EXPECT_EQ(append_kernel_reason(&ctx, &info), SUCCESS); +} + +TEST(ParodusSmokeTest, handle_parodus_reboot_file_FallbackWritesDest) { + RebootInfo info; + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T12:00:00Z", sizeof(info.timestamp) - 1); + strncpy(info.reason, "APP_TRIGGERED", sizeof(info.reason) - 1); + strncpy(info.customReason, "Servicemanager", sizeof(info.customReason) - 1); + strncpy(info.source, "Servicemanager", sizeof(info.source) - 1); + + const char* destPath = "/tmp/reboot_test_parodus.out"; + EXPECT_EQ(handle_parodus_reboot_file(&info, destPath), SUCCESS); + + FILE* fp = fopen(destPath, "r"); + ASSERT_NE(fp, nullptr); + char buf[512] = {0}; + size_t n = fread(buf, 1, sizeof(buf) - 1, fp); + buf[n] = '\0'; + fclose(fp); + + EXPECT_NE(strstr(buf, "PreviousRebootInfo:2026-03-10T12:00:00Z,APP_TRIGGERED,Servicemanager,Servicemanager"), nullptr); + remove(destPath); +} + +TEST(ParodusSmokeTest, copy_keypress_info_SourceMissingReturnsSuccess) { + EXPECT_EQ(copy_keypress_info("/tmp/nonexistent_keypress.info", "/tmp/unused_dest.info"), SUCCESS); +} + +TEST(ParodusSmokeTest, copy_keypress_info_Success) { + const char* src = "/tmp/reboot_test_keypress_src.info"; + const char* dst = "/tmp/reboot_test_keypress_dst.info"; + FILE* fp = fopen(src, "w"); + ASSERT_NE(fp, nullptr); + fputs("KEY=OK\n", fp); + fclose(fp); + + EXPECT_EQ(copy_keypress_info(src, dst), SUCCESS); + + fp = fopen(dst, "r"); + ASSERT_NE(fp, nullptr); + char buf[64] = {0}; + size_t n = fread(buf, 1, sizeof(buf) - 1, fp); + buf[n] = '\0'; + fclose(fp); + EXPECT_NE(strstr(buf, "KEY=OK"), nullptr); + + remove(src); + remove(dst); +} + +TEST(ParodusSmokeTest, handle_parodus_reboot_file_InvalidParams) { + RebootInfo info; + memset(&info, 0, sizeof(info)); + EXPECT_EQ(handle_parodus_reboot_file(nullptr, "/tmp/out"), ERROR_GENERAL); + EXPECT_EQ(handle_parodus_reboot_file(&info, nullptr), ERROR_GENERAL); +} + +TEST(ParodusSmokeTest, update_parodus_log_ValidInfoPath) { + RebootInfo info; + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T10:00:00Z", sizeof(info.timestamp) - 1); + strncpy(info.source, "Kernel", sizeof(info.source) - 1); + strncpy(info.reason, "KERNEL_PANIC", sizeof(info.reason) - 1); + strncpy(info.customReason, "Hardware Register - KERNEL_PANIC", sizeof(info.customReason) - 1); + strncpy(info.otherReason, "Oops dump", sizeof(info.otherReason) - 1); + + int rc = update_parodus_log(&info); + EXPECT_TRUE(rc == SUCCESS || rc == ERROR_GENERAL); +} + +TEST(ParodusSmokeTest, handle_parodus_reboot_file_InvalidDestinationPath) { + RebootInfo info; + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T12:30:00Z", sizeof(info.timestamp) - 1); + strncpy(info.reason, "OPS_TRIGGERED", sizeof(info.reason) - 1); + strncpy(info.customReason, "ScheduledReboot", sizeof(info.customReason) - 1); + strncpy(info.source, "ScheduledReboot", sizeof(info.source) - 1); + + int rc = handle_parodus_reboot_file(&info, "/this/path/does/not/exist/out.info"); + EXPECT_EQ(rc, ERROR_GENERAL); +} + +TEST(ParodusSmokeTest, append_kernel_reason_RtkPathExecutes) { + EnvContext ctx; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&info, 0, sizeof(info)); + strncpy(ctx.soc, "RTK", sizeof(ctx.soc) - 1); + strncpy(info.reason, "SOFTWARE_MASTER_RESET", sizeof(info.reason) - 1); + + int rc = append_kernel_reason(&ctx, &info); + EXPECT_TRUE(rc == SUCCESS || rc == ERROR_GENERAL); +} + +TEST(ParodusSmokeTest, update_parodus_log_SkipsWhenPreviousInfoExists) { + RebootInfo info; + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T15:00:00Z", sizeof(info.timestamp) - 1); + strncpy(info.source, "Kernel", sizeof(info.source) - 1); + strncpy(info.reason, "KERNEL_PANIC", sizeof(info.reason) - 1); + strncpy(info.customReason, "Hardware Register - KERNEL_PANIC", sizeof(info.customReason) - 1); + + system("mkdir -p /opt/logs"); + FILE* fp = fopen("/opt/logs/parodus.log", "w"); + if (!fp) { + GTEST_SKIP() << "Cannot create /opt/logs/parodus.log in this environment"; + } + fputs("PreviousRebootInfo:existing\n", fp); + fclose(fp); + + EXPECT_EQ(update_parodus_log(&info), SUCCESS); + + fp = fopen("/opt/logs/parodus.log", "r"); + ASSERT_NE(fp, nullptr); + char buf[512] = {0}; + size_t n = fread(buf, 1, sizeof(buf) - 1, fp); + buf[n] = '\0'; + fclose(fp); + EXPECT_NE(strstr(buf, "PreviousRebootInfo:existing"), nullptr); +} + +TEST(ParodusSmokeTest, handle_parodus_reboot_file_UsesParodusInputFile) { + RebootInfo info; + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T16:00:00Z", sizeof(info.timestamp) - 1); + strncpy(info.reason, "APP_TRIGGERED", sizeof(info.reason) - 1); + strncpy(info.customReason, "DeviceInitiated", sizeof(info.customReason) - 1); + strncpy(info.source, "DeviceInitiated", sizeof(info.source) - 1); + + system("mkdir -p /opt/secure/reboot"); + FILE* src = fopen("/opt/secure/reboot/parodusreboot.info", "w"); + if (!src) { + GTEST_SKIP() << "Cannot create /opt/secure/reboot/parodusreboot.info in this environment"; + } + fputs("PreviousRebootInfo:from_input_file", src); + fclose(src); + + const char* destPath = "/tmp/reboot_test_parodus_from_input.out"; + EXPECT_EQ(handle_parodus_reboot_file(&info, destPath), SUCCESS); + + FILE* out = fopen(destPath, "r"); + ASSERT_NE(out, nullptr); + char outBuf[256] = {0}; + size_t n = fread(outBuf, 1, sizeof(outBuf) - 1, out); + outBuf[n] = '\0'; + fclose(out); + EXPECT_NE(strstr(outBuf, "PreviousRebootInfo:from_input_file"), nullptr); + EXPECT_NE(access("/opt/secure/reboot/parodusreboot.info", F_OK), 0); + + remove(destPath); +} + +TEST(ParodusSmokeTest, copy_keypress_info_InvalidDestinationPath) { + const char* src = "/tmp/reboot_test_keypress_src2.info"; + FILE* fp = fopen(src, "w"); + ASSERT_NE(fp, nullptr); + fputs("KEY=BADDEST\n", fp); + fclose(fp); + + int rc = copy_keypress_info(src, "/this/path/does/not/exist/keypress.out"); + EXPECT_EQ(rc, ERROR_GENERAL); + + remove(src); +} + +TEST(ParodusSmokeTest, append_kernel_reason_RtkWritesLowercaseToLog) { + system("mkdir -p /opt/logs"); + remove("/opt/logs/receiver.log"); + + EnvContext ctx; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&info, 0, sizeof(info)); + strncpy(ctx.soc, "REALTEK", sizeof(ctx.soc) - 1); + strncpy(info.reason, "SOFTWARE_MASTER_RESET", sizeof(info.reason) - 1); + + int rc = append_kernel_reason(&ctx, &info); + EXPECT_EQ(rc, SUCCESS); + + FILE* fp = fopen("/opt/logs/receiver.log", "r"); + ASSERT_NE(fp, nullptr); + char buf[512] = {0}; + size_t n = fread(buf, 1, sizeof(buf) - 1, fp); + buf[n] = '\0'; + fclose(fp); + + EXPECT_NE(strstr(buf, "software_master_reset"), nullptr); +} + +TEST(ParodusSmokeTest, update_parodus_log_WritesWhenNoPreviousInfo) { + system("mkdir -p /opt/logs"); + remove("/opt/logs/parodus.log"); + + RebootInfo info; + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T22:00:00Z", sizeof(info.timestamp) - 1); + strncpy(info.source, "WatchDog", sizeof(info.source) - 1); + strncpy(info.reason, "WATCHDOG_TIMER_RESET", sizeof(info.reason) - 1); + strncpy(info.customReason, "Hardware Register - WATCHDOG", sizeof(info.customReason) - 1); + strncpy(info.otherReason, "watchdog timeout", sizeof(info.otherReason) - 1); + + EXPECT_EQ(update_parodus_log(&info), SUCCESS); + + FILE* fp = fopen("/opt/logs/parodus.log", "r"); + ASSERT_NE(fp, nullptr); + char buf[1024] = {0}; + size_t n = fread(buf, 1, sizeof(buf) - 1, fp); + buf[n] = '\0'; + fclose(fp); + EXPECT_NE(strstr(buf, "PreviousRebootInfo:2026-03-10T22:00:00Z,WATCHDOG_TIMER_RESET,Hardware Register - WATCHDOG,WatchDog"), nullptr); +} + +TEST(ParodusSmokeTest, handle_parodus_reboot_file_EmptyInputFilePath) { + RebootInfo info; + memset(&info, 0, sizeof(info)); + + system("mkdir -p /opt/secure/reboot"); + FILE* src = fopen("/opt/secure/reboot/parodusreboot.info", "w"); + if (!src) { + GTEST_SKIP() << "Cannot create /opt/secure/reboot/parodusreboot.info in this environment"; + } + fclose(src); + + const char* destPath = "/tmp/reboot_test_parodus_empty.out"; + EXPECT_EQ(handle_parodus_reboot_file(&info, destPath), SUCCESS); + + FILE* out = fopen(destPath, "r"); + ASSERT_NE(out, nullptr); + char outBuf[8] = {0}; + size_t n = fread(outBuf, 1, sizeof(outBuf) - 1, out); + fclose(out); + EXPECT_EQ(n, 0u); + + remove(destPath); +} + +TEST(ParodusSmokeTest, handle_parodus_reboot_file_InputExistsButDestOpenFails) { + RebootInfo info; + memset(&info, 0, sizeof(info)); + + system("mkdir -p /opt/secure/reboot"); + FILE* src = fopen("/opt/secure/reboot/parodusreboot.info", "w"); + if (!src) { + GTEST_SKIP() << "Cannot create /opt/secure/reboot/parodusreboot.info in this environment"; + } + fputs("PreviousRebootInfo:content", src); + fclose(src); + + int rc = handle_parodus_reboot_file(&info, "/this/path/does/not/exist/out.info"); + EXPECT_EQ(rc, ERROR_GENERAL); + EXPECT_NE(access("/opt/secure/reboot/parodusreboot.info", F_OK), 0); +} + +TEST(ParodusSmokeTest, copy_keypress_info_ReadErrorFromDirectorySource) { + system("mkdir -p /tmp/reboot_test_keypress_dir"); + const char* dst = "/tmp/reboot_test_keypress_dir_out.info"; + + int rc = copy_keypress_info("/tmp/reboot_test_keypress_dir", dst); + EXPECT_EQ(rc, ERROR_GENERAL); + + remove(dst); + system("rmdir /tmp/reboot_test_keypress_dir"); +} + +TEST(ParodusSmokeTest, handle_parodus_reboot_file_InputPathOpenFailsThenFallbackWrites) { + RebootInfo info; + memset(&info, 0, sizeof(info)); + strncpy(info.timestamp, "2026-03-10T23:10:00Z", sizeof(info.timestamp) - 1); + strncpy(info.reason, "SOFTWARE_MASTER_RESET", sizeof(info.reason) - 1); + strncpy(info.customReason, "SOFTWARE_MASTER_RESET", sizeof(info.customReason) - 1); + strncpy(info.source, "SoftwareReboot", sizeof(info.source) - 1); + + system("mkdir -p /opt/secure/reboot/parodusreboot.info"); + const char* destPath = "/tmp/reboot_test_parodus_fallback_after_open_fail.out"; + + int rc = handle_parodus_reboot_file(&info, destPath); + EXPECT_EQ(rc, SUCCESS); + + FILE* fp = fopen(destPath, "r"); + ASSERT_NE(fp, nullptr); + char buf[512] = {0}; + size_t n = fread(buf, 1, sizeof(buf) - 1, fp); + buf[n] = '\0'; + fclose(fp); + + const char* expectedFallback = "PreviousRebootInfo:2026-03-10T23:10:00Z,SOFTWARE_MASTER_RESET,SOFTWARE_MASTER_RESET,SoftwareReboot"; + bool fallbackWritten = (strstr(buf, expectedFallback) != nullptr); + bool emptyCopied = (buf[0] == '\0'); + EXPECT_TRUE(fallbackWritten || emptyCopied); + + remove(destPath); + system("rmdir /opt/secure/reboot/parodusreboot.info"); +} + +TEST(ParodusSmokeTest, handle_parodus_reboot_file_AppendsNewlineWhenMissingInInput) { + RebootInfo info; + memset(&info, 0, sizeof(info)); + + system("mkdir -p /opt/secure/reboot"); + FILE* src = fopen("/opt/secure/reboot/parodusreboot.info", "w"); + if (!src) { + GTEST_SKIP() << "Cannot create /opt/secure/reboot/parodusreboot.info in this environment"; + } + fputs("PreviousRebootInfo:line_without_newline", src); + fclose(src); + + const char* destPath = "/tmp/reboot_test_parodus_newline.out"; + EXPECT_EQ(handle_parodus_reboot_file(&info, destPath), SUCCESS); + + FILE* out = fopen(destPath, "r"); + ASSERT_NE(out, nullptr); + char outBuf[256] = {0}; + size_t n = fread(outBuf, 1, sizeof(outBuf) - 1, out); + outBuf[n] = '\0'; + fclose(out); + ASSERT_GT(n, 0u); + EXPECT_EQ(outBuf[n - 1], '\n'); + + remove(destPath); +} + +GTEST_API_ int main(int argc, char *argv[]) { + char testresults_fullfilepath[GTEST_REPORT_FILEPATH_SIZE]; + memset(testresults_fullfilepath, 0, GTEST_REPORT_FILEPATH_SIZE); + snprintf(testresults_fullfilepath, GTEST_REPORT_FILEPATH_SIZE, "json:%s%s", + GTEST_DEFAULT_RESULT_FILEPATH, GTEST_DEFAULT_RESULT_FILENAME); + ::testing::GTEST_FLAG(output) = testresults_fullfilepath; + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + diff --git a/unittest/reboot_platform_hal_gtest.cpp b/unittest/reboot_platform_hal_gtest.cpp new file mode 100644 index 0000000..7f87197 --- /dev/null +++ b/unittest/reboot_platform_hal_gtest.cpp @@ -0,0 +1,552 @@ +#include +#include +#include +#include +#include +#include +#include + +static bool g_mock_fs_enabled = false; +static std::unordered_map g_mock_path_map; + +extern "C" int access(const char* pathname, int mode) { + if (g_mock_fs_enabled && pathname) { + auto it = g_mock_path_map.find(pathname); + if (it != g_mock_path_map.end()) { + (void)mode; + return 0; + } + } + + return faccessat(AT_FDCWD, pathname, mode, 0); +} + +static int mode_to_flags(const char* mode) { + if (!mode || mode[0] == '\0') { + return -1; + } + if (strcmp(mode, "r") == 0) return O_RDONLY; + if (strcmp(mode, "r+") == 0) return O_RDWR; + if (strcmp(mode, "w") == 0) return O_WRONLY | O_CREAT | O_TRUNC; + if (strcmp(mode, "w+") == 0) return O_RDWR | O_CREAT | O_TRUNC; + if (strcmp(mode, "a") == 0) return O_WRONLY | O_CREAT | O_APPEND; + if (strcmp(mode, "a+") == 0) return O_RDWR | O_CREAT | O_APPEND; + return -1; +} + +extern "C" FILE* fopen(const char* pathname, const char* mode) { + const char* resolvedPath = pathname; + if (g_mock_fs_enabled && pathname) { + auto it = g_mock_path_map.find(pathname); + if (it != g_mock_path_map.end()) { + resolvedPath = it->second.c_str(); + } + } + + int flags = mode_to_flags(mode); + if (flags < 0) { + errno = EINVAL; + return nullptr; + } + + int fd = open(resolvedPath, flags, 0644); + if (fd < 0) { + return nullptr; + } + + FILE* fp = fdopen(fd, mode); + if (!fp) { + close(fd); + return nullptr; + } + return fp; +} + +extern "C" { + #include "update-reboot-info.h" +} + +#define GTEST_DEFAULT_RESULT_FILEPATH "/tmp/Gtest_Report/" +#define GTEST_DEFAULT_RESULT_FILENAME "reboot_platform_hal_gtest_report.json" +#define GTEST_REPORT_FILEPATH_SIZE 256 + +enum class TestSoc { + Brcm, + Broadcom, + Rtk, + Realtek, + Amlogic, + Mtk, + Mediatek, + Unknown +}; + +static const char* test_soc_name(TestSoc soc) { + switch (soc) { + case TestSoc::Brcm: return "BRCM"; + case TestSoc::Broadcom: return "BROADCOM"; + case TestSoc::Rtk: return "RTK"; + case TestSoc::Realtek: return "REALTEK"; + case TestSoc::Amlogic: return "AMLOGIC"; + case TestSoc::Mtk: return "MTK"; + case TestSoc::Mediatek: return "MEDIATEK"; + case TestSoc::Unknown: return "UNKNOWN_SOC"; + } + return "UNKNOWN_SOC"; +} + +static void set_test_soc(EnvContext* ctx, TestSoc soc) { + if (!ctx) { + return; + } + memset(ctx->soc, 0, sizeof(ctx->soc)); + strncpy(ctx->soc, test_soc_name(soc), sizeof(ctx->soc) - 1); +} + +static void setup_mock_file(const char* virtualPath, const char* realPath, const char* content) { + FILE* fp = fopen(realPath, "w"); + ASSERT_NE(fp, nullptr); + fputs(content, fp); + fclose(fp); + g_mock_path_map[virtualPath] = realPath; +} + +class PlatformHalMockFsTest : public ::testing::Test { +protected: + void SetUp() override { + system("mkdir -p /tmp/reboot_test"); + g_mock_path_map.clear(); + g_mock_fs_enabled = false; + } + + void TearDown() override { + g_mock_fs_enabled = false; + g_mock_path_map.clear(); + system("rm -rf /tmp/reboot_test"); + } +}; + +TEST(PlatformHalSmokeTest, get_hardware_reason_brcm_InvalidParams) { + EXPECT_EQ(get_hardware_reason_brcm(nullptr, nullptr), ERROR_GENERAL); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_rtk_InvalidParams) { + EXPECT_EQ(get_hardware_reason_rtk(nullptr, nullptr), ERROR_GENERAL); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_amlogic_InvalidParams) { + EXPECT_EQ(get_hardware_reason_amlogic(nullptr, nullptr), ERROR_GENERAL); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_mtk_InvalidParams) { + EXPECT_EQ(get_hardware_reason_mtk(nullptr, nullptr), ERROR_GENERAL); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_DispatchBrcmPath) { + EnvContext ctx; + HardwareReason hw; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + set_test_soc(&ctx, TestSoc::Brcm); + + int rc = get_hardware_reason(&ctx, &hw, &info); + EXPECT_TRUE(rc == ERROR_FILE_NOT_FOUND || rc == ERROR_GENERAL); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_DispatchAmlogicPath) { + EnvContext ctx; + HardwareReason hw; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + set_test_soc(&ctx, TestSoc::Amlogic); + + int rc = get_hardware_reason(&ctx, &hw, &info); + EXPECT_TRUE(rc == ERROR_FILE_NOT_FOUND || rc == ERROR_GENERAL); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_UnknownSocFallsBackToUnknown) { + EnvContext ctx; + HardwareReason hw; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + set_test_soc(&ctx, TestSoc::Unknown); + + int rc = get_hardware_reason(&ctx, &hw, &info); + EXPECT_EQ(rc, SUCCESS); + EXPECT_NE(hw.mappedReason[0], '\0'); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_brcm_FilePathBehavior) { + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Brcm); + + int rc = get_hardware_reason_brcm(&ctx, &hw); + EXPECT_TRUE(rc == SUCCESS || rc == ERROR_GENERAL); + EXPECT_NE(hw.mappedReason[0], '\0'); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_rtk_SystemCmdlineBehavior) { + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Rtk); + + int rc = get_hardware_reason_rtk(&ctx, &hw); + EXPECT_TRUE(rc == SUCCESS || rc == ERROR_GENERAL); + EXPECT_NE(hw.mappedReason[0], '\0'); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_amlogic_FilePathBehavior) { + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Amlogic); + + int rc = get_hardware_reason_amlogic(&ctx, &hw); + EXPECT_TRUE(rc == SUCCESS || rc == ERROR_GENERAL); + EXPECT_NE(hw.mappedReason[0], '\0'); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_mtk_FilePathBehavior) { + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Mtk); + + int rc = get_hardware_reason_mtk(&ctx, &hw); + EXPECT_TRUE(rc == SUCCESS || rc == ERROR_GENERAL); + EXPECT_NE(hw.mappedReason[0], '\0'); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_DispatchRtkAliasPath) { + EnvContext ctx; + HardwareReason hw; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + set_test_soc(&ctx, TestSoc::Realtek); + + int rc = get_hardware_reason(&ctx, &hw, &info); + EXPECT_TRUE(rc == SUCCESS || rc == FAILURE || rc == ERROR_GENERAL); + if (rc == SUCCESS) { + EXPECT_NE(hw.mappedReason[0], '\0'); + } +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_DispatchMtkPath) { + EnvContext ctx; + HardwareReason hw; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + set_test_soc(&ctx, TestSoc::Mtk); + + int rc = get_hardware_reason(&ctx, &hw, &info); + EXPECT_TRUE(rc == SUCCESS || rc == FAILURE || rc == ERROR_GENERAL); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_DispatchBroadcomAliasPath) { + EnvContext ctx; + HardwareReason hw; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + set_test_soc(&ctx, TestSoc::Broadcom); + + int rc = get_hardware_reason(&ctx, &hw, &info); + EXPECT_TRUE(rc == ERROR_FILE_NOT_FOUND || rc == ERROR_GENERAL); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_DispatchMediatekAliasPath) { + EnvContext ctx; + HardwareReason hw; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + set_test_soc(&ctx, TestSoc::Mediatek); + + int rc = get_hardware_reason(&ctx, &hw, &info); + EXPECT_TRUE(rc == SUCCESS || rc == FAILURE || rc == ERROR_GENERAL); +} + +TEST(PlatformHalSmokeTest, get_hardware_reason_EmptySocFallbackPath) { + EnvContext ctx; + HardwareReason hw; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + + int rc = get_hardware_reason(&ctx, &hw, &info); + EXPECT_EQ(rc, SUCCESS); + EXPECT_NE(hw.mappedReason[0], '\0'); +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_brcm_ReadsAndUppercases) { + setup_mock_file("/proc/brcm/previous_reboot_reason", "/tmp/reboot_test/brcm_reason", "watchdog_reset\n"); + g_mock_fs_enabled = true; + + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Brcm); + + EXPECT_EQ(get_hardware_reason_brcm(&ctx, &hw), SUCCESS); + EXPECT_STREQ(hw.rawReason, "WATCHDOG_RESET"); + EXPECT_STREQ(hw.mappedReason, "WATCHDOG_RESET"); +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_rtk_ParsesWakeupReason) { + setup_mock_file("/proc/cmdline", "/tmp/reboot_test/cmdline", "console=ttyS0 wakeupreason=kernel_panic root=/dev/mmcblk0\n"); + g_mock_fs_enabled = true; + + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Rtk); + + EXPECT_EQ(get_hardware_reason_rtk(&ctx, &hw), SUCCESS); + EXPECT_STREQ(hw.rawReason, "KERNEL_PANIC"); + EXPECT_STREQ(hw.mappedReason, "KERNEL_PANIC"); +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_amlogic_MapsResetCode) { + setup_mock_file("/sys/class/aml_reboot/reboot_reason", "/tmp/reboot_test/amlogic_reason", "3\n"); + g_mock_fs_enabled = true; + + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Amlogic); + + EXPECT_EQ(get_hardware_reason_amlogic(&ctx, &hw), SUCCESS); + EXPECT_STREQ(hw.rawReason, "3"); + EXPECT_STREQ(hw.mappedReason, "KERNEL_PANIC"); +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_mtk_MapsHexCode) { + setup_mock_file("/sys/mtk_pm/boot_reason", "/tmp/reboot_test/mtk_reason", "0xE0\n"); + g_mock_fs_enabled = true; + + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Mtk); + + EXPECT_EQ(get_hardware_reason_mtk(&ctx, &hw), SUCCESS); + EXPECT_STREQ(hw.rawReason, "0xE0"); + EXPECT_STREQ(hw.mappedReason, "WATCHDOG_REBOOT"); +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_DispatchAmlogicUsesReadPath) { + setup_mock_file("/sys/devices/platform/aml_pm/reset_reason", "/tmp/reboot_test/amlogic_sysfs", "12\n"); + g_mock_fs_enabled = true; + + EnvContext ctx; + HardwareReason hw; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + set_test_soc(&ctx, TestSoc::Amlogic); + + EXPECT_EQ(get_hardware_reason(&ctx, &hw, &info), ERROR_GENERAL); + EXPECT_EQ(hw.mappedReason[0], '\0'); + EXPECT_EQ(info.reason[0], '\0'); +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_DispatchMtkUsesReadPath) { + setup_mock_file("/sys/mtk_pm/boot_reason", "/tmp/reboot_test/mtk_sysfs", "0xD1\n"); + g_mock_fs_enabled = true; + + EnvContext ctx; + HardwareReason hw; + RebootInfo info; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + memset(&info, 0, sizeof(info)); + set_test_soc(&ctx, TestSoc::Mediatek); + strncpy(info.timestamp, "2026-03-10T11:22:33Z", sizeof(info.timestamp) - 1); + + EXPECT_EQ(get_hardware_reason(&ctx, &hw, &info), ERROR_GENERAL); + EXPECT_EQ(hw.mappedReason[0], '\0'); + EXPECT_EQ(info.reason[0], '\0'); + EXPECT_STREQ(info.timestamp, "2026-03-10T11:22:33Z"); +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_amlogic_AllMapCasesTable) { + struct Case { + int code; + const char* expected; + }; + const Case cases[] = { + {0, "POWER_ON_RESET"}, + {1, "WATCHDOG_RESET"}, + {2, "SOFTWARE_RESET"}, + {3, "KERNEL_PANIC"}, + {4, "THERMAL_RESET"}, + {5, "HARDWARE_RESET"}, + {6, "SUSPEND_WAKEUP"}, + {7, "REMOTE_WAKEUP"}, + {8, "RTC_WAKEUP"}, + {9, "GPIO_WAKEUP"}, + {10, "FACTORY_RESET"}, + {11, "UPGRADE_RESET"}, + {12, "FASTBOOT_RESET"}, + {13, "CRASH_DUMP_RESET"}, + {14, "RECOVERY_RESET"}, + {15, "BOOTLOADER_RESET"}, + {99, "UNKNOWN"} + }; + + EnvContext ctx; + memset(&ctx, 0, sizeof(ctx)); + set_test_soc(&ctx, TestSoc::Amlogic); + + for (const auto& testCase : cases) { + HardwareReason hw; + memset(&hw, 0, sizeof(hw)); + char value[16] = {0}; + snprintf(value, sizeof(value), "%d\n", testCase.code); + setup_mock_file("/sys/class/aml_reboot/reboot_reason", "/tmp/reboot_test/amlogic_tbl", value); + g_mock_fs_enabled = true; + + int rc = get_hardware_reason_amlogic(&ctx, &hw); + EXPECT_EQ(rc, SUCCESS); + EXPECT_STREQ(hw.mappedReason, testCase.expected); + } +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_mtk_AllMapCasesTable) { + struct Case { + const char* value; + const char* expected; + }; + const Case cases[] = { + {"0x00\n", "POWER_ON_RESET"}, + {"0xD1\n", "SOFTWARE_MASTER_RESET"}, + {"0xE4\n", "THERMAL_REBOOT"}, + {"0xEE\n", "KERNEL_PANIC"}, + {"0xE0\n", "WATCHDOG_REBOOT"}, + {"0x99\n", "UNKNOWN_RESET"} + }; + + EnvContext ctx; + memset(&ctx, 0, sizeof(ctx)); + set_test_soc(&ctx, TestSoc::Mtk); + + for (const auto& testCase : cases) { + HardwareReason hw; + memset(&hw, 0, sizeof(hw)); + setup_mock_file("/sys/mtk_pm/boot_reason", "/tmp/reboot_test/mtk_tbl", testCase.value); + g_mock_fs_enabled = true; + + int rc = get_hardware_reason_mtk(&ctx, &hw); + EXPECT_EQ(rc, SUCCESS); + EXPECT_STREQ(hw.mappedReason, testCase.expected); + } +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_brcm_EmptyFileUnknown) { + setup_mock_file("/proc/brcm/previous_reboot_reason", "/tmp/reboot_test/brcm_empty", ""); + g_mock_fs_enabled = true; + + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Brcm); + + EXPECT_EQ(get_hardware_reason_brcm(&ctx, &hw), SUCCESS); + EXPECT_STREQ(hw.mappedReason, "UNKNOWN"); +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_rtk_NoWakeupReasonUnknown) { + setup_mock_file("/proc/cmdline", "/tmp/reboot_test/cmdline_no_wakeup", "console=ttyS0 root=/dev/mmcblk0\n"); + g_mock_fs_enabled = true; + + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Rtk); + + EXPECT_EQ(get_hardware_reason_rtk(&ctx, &hw), SUCCESS); + EXPECT_STREQ(hw.mappedReason, "UNKNOWN"); +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_brcm_OpenFailsReturnsErrorGeneral) { + g_mock_path_map["/proc/brcm/previous_reboot_reason"] = "/tmp/reboot_test/does_not_exist_brcm"; + g_mock_fs_enabled = true; + + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Brcm); + + int rc = get_hardware_reason_brcm(&ctx, &hw); + EXPECT_EQ(rc, ERROR_GENERAL); + EXPECT_STREQ(hw.mappedReason, "UNKNOWN"); +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_amlogic_OpenFailsReturnsErrorGeneral) { + g_mock_path_map["/sys/class/aml_reboot/reboot_reason"] = "/tmp/reboot_test/does_not_exist_amlogic"; + g_mock_fs_enabled = true; + + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Amlogic); + + int rc = get_hardware_reason_amlogic(&ctx, &hw); + EXPECT_EQ(rc, ERROR_GENERAL); + EXPECT_STREQ(hw.mappedReason, "UNKNOWN"); +} + +TEST_F(PlatformHalMockFsTest, get_hardware_reason_mtk_OpenFailsReturnsErrorGeneral) { + g_mock_path_map["/sys/mtk_pm/boot_reason"] = "/tmp/reboot_test/does_not_exist_mtk"; + g_mock_fs_enabled = true; + + EnvContext ctx; + HardwareReason hw; + memset(&ctx, 0, sizeof(ctx)); + memset(&hw, 0, sizeof(hw)); + set_test_soc(&ctx, TestSoc::Mtk); + + int rc = get_hardware_reason_mtk(&ctx, &hw); + EXPECT_EQ(rc, ERROR_GENERAL); + EXPECT_STREQ(hw.mappedReason, "UNKNOWN"); +} + +GTEST_API_ int main(int argc, char *argv[]) { + char testresults_fullfilepath[GTEST_REPORT_FILEPATH_SIZE]; + memset(testresults_fullfilepath, 0, GTEST_REPORT_FILEPATH_SIZE); + snprintf(testresults_fullfilepath, GTEST_REPORT_FILEPATH_SIZE, "json:%s%s", + GTEST_DEFAULT_RESULT_FILEPATH, GTEST_DEFAULT_RESULT_FILENAME); + ::testing::GTEST_FLAG(output) = testresults_fullfilepath; + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + diff --git a/unittest/rebootreason_main_gtest.cpp b/unittest/rebootreason_main_gtest.cpp new file mode 100644 index 0000000..e70040f --- /dev/null +++ b/unittest/rebootreason_main_gtest.cpp @@ -0,0 +1,30 @@ +/** + * Minimal gtest for main.c + */ +#include +extern "C" { + #include "update-reboot-info.h" +} + +#define GTEST_DEFAULT_RESULT_FILEPATH "/tmp/Gtest_Report/" +#define GTEST_DEFAULT_RESULT_FILENAME "reboot_main_gtest_report.json" +#define GTEST_REPORT_FILEPATH_SIZE 256 + +// Smoke tests for helpers used by main.c +TEST(MainSmokeTest, acquire_release_lock_InvalidParams) { + EXPECT_EQ(acquire_lock(nullptr), ERROR_GENERAL); + EXPECT_EQ(release_lock(nullptr), ERROR_GENERAL); +} + +// Note: We don't call main() directly; this suite ensures build/link of main.c +// via Makefile.am target using the same compilation unit. + +GTEST_API_ int main(int argc, char *argv[]) { + char testresults_fullfilepath[GTEST_REPORT_FILEPATH_SIZE]; + memset(testresults_fullfilepath, 0, GTEST_REPORT_FILEPATH_SIZE); + snprintf(testresults_fullfilepath, GTEST_REPORT_FILEPATH_SIZE, "json:%s%s", + GTEST_DEFAULT_RESULT_FILEPATH, GTEST_DEFAULT_RESULT_FILENAME); + ::testing::GTEST_FLAG(output) = testresults_fullfilepath; + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}