diff --git a/.github/workflows/L1-Test.yaml b/.github/workflows/L1-Test.yaml
new file mode 100644
index 0000000..951bdc3
--- /dev/null
+++ b/.github/workflows/L1-Test.yaml
@@ -0,0 +1,70 @@
+name: L1 Testing & Coverage
+
+on:
+ pull_request:
+ branches: [ main, develop ]
+ push:
+ branches: [ main, develop ]
+
+env:
+ CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: 1
+
+jobs:
+ test-and-coverage:
+ name: Test & Coverage
+ runs-on: ubuntu-latest
+ container:
+ image: ghcr.io/rdkcentral/docker-device-mgt-service-test/native-platform:latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Rust Toolchain components
+ run: |
+ if ! command -v cargo &> /dev/null; then
+ curl https://sh.rustup.rs -sSf | sh -s -- -y
+ export PATH="$HOME/.cargo/bin:$PATH"
+ fi
+ rustup update stable
+ rustup default stable
+ rustup component add llvm-tools-preview
+
+ - name: Add Rust to PATH
+ run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
+
+ - name: Install cargo-llvm-cov
+ run: cargo install cargo-llvm-cov
+
+ - name: Install additional dependencies
+ run: |
+ apt-get update && apt-get install -y git pkg-config libssl-dev || true
+
+ - name: Run all tests
+ run: cargo test --all-features --workspace --verbose
+
+ - name: Run tests with llvm-cov for coverage
+ run: |
+ mkdir -p ./coverage
+ # Generate both XML and summary report
+ cargo llvm-cov --all-features --workspace --cobertura --output-path ./coverage/cobertura.xml
+ # Print coverage summary to console
+ cargo llvm-cov --all-features --workspace --summary-only
+
+ # - name: Upload coverage to Codecov
+ # uses: codecov/codecov-action@v3
+ # with:
+ # file: ./coverage/cobertura.xml
+ # flags: unittests
+ # name: codecov-umbrella
+ # fail_ci_if_error: false
+
+ # - name: Archive code coverage results
+ # uses: actions/upload-artifact@v4
+ # with:
+ # name: code-coverage-report
+ # path: coverage/
+
+ - name: Clean target directory (ensure it's not persisted)
+ run: rm -rf target/
diff --git a/.github/workflows/native_full_build.yml b/.github/workflows/native_full_build.yml
new file mode 100644
index 0000000..23b9bf7
--- /dev/null
+++ b/.github/workflows/native_full_build.yml
@@ -0,0 +1,52 @@
+name: Build Crash Upload Component in Native Environment
+
+on:
+ pull_request:
+ branches: [ main, develop ]
+ push:
+ branches: [ main, develop ]
+
+env:
+ CARGO_TERM_COLOR: always
+ RUST_BACKTRACE: 1
+
+jobs:
+ build:
+ name: Build Check
+ runs-on: ubuntu-latest
+ container:
+ image: ghcr.io/rdkcentral/docker-device-mgt-service-test/native-platform:latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Rust Toolchain components
+ run: |
+ if ! command -v cargo &> /dev/null; then
+ curl https://sh.rustup.rs -sSf | sh -s -- -y
+ export PATH="$HOME/.cargo/bin:$PATH"
+ fi
+ rustup update stable
+ rustup default stable
+
+ - name: Add Rust to PATH
+ run: echo "$HOME/.cargo/bin" >> $GITHUB_PATH
+
+ - name: Install additional components
+ run: rustup component add clippy
+
+ - name: Run cargo check
+ run: cargo check --all-targets --all-features
+
+ - name: Run clippy (do not fail on warnings)
+ run: cargo clippy --all-targets --all-features -- -D warnings || true
+
+ - name: Build all libraries and binaries (debug)
+ run: cargo build --all-targets --all-features
+
+ - name: Build all libraries and binaries (release)
+ run: cargo build --release --all-targets --all-features
+
+ - name: Clean target directory (ensure it's not persisted)
+ run: rm -rf target/
diff --git a/.github/workflows/release_docs.yaml b/.github/workflows/release_docs.yaml
new file mode 100644
index 0000000..2dcdb69
--- /dev/null
+++ b/.github/workflows/release_docs.yaml
@@ -0,0 +1,175 @@
+name: Release Documentation
+
+#on:
+ push:
+ branches: [ main ]
+ release:
+ types: [ published ]
+
+env:
+ CARGO_TERM_COLOR: always
+
+permissions:
+ contents: write
+ pages: write
+ id-token: write
+
+jobs:
+ docs:
+ name: Generate and Deploy Documentation
+ runs-on: ubuntu-latest
+ container:
+ image: rust:latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install dependencies
+ run: |
+ apt-get update && apt-get install -y git pkg-config libssl-dev
+ cargo install cargo-readme
+
+ - name: Generate documentation
+ run: |
+ cargo doc --all-features --no-deps --workspace
+ echo '' > target/doc/index.html
+
+ - name: Copy additional files to docs
+ run: |
+ cp README.md target/doc/ 2>/dev/null || true
+ cp LICENSE* target/doc/ 2>/dev/null || true
+
+ - name: Setup Pages
+ uses: actions/configure-pages@v4
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: 'target/doc'
+
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
+
+ - name: Generate README from lib.rs
+ run: |
+ # Configure git for commit
+ git config --global --add safe.directory /__w/*/
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+
+ # Find the main library crate and generate README
+ if [ -f "src/lib.rs" ]; then
+ cargo readme --input src/lib.rs --output README.md
+ elif find . -name "lib.rs" -path "*/src/lib.rs" | head -1 | xargs -I{} dirname {} | xargs -I{} dirname {} | head -1; then
+ # Handle workspace with multiple crates - use first one found
+ CRATE_DIR=$(find . -name "lib.rs" -path "*/src/lib.rs" | head -1 | xargs -I{} dirname {} | xargs -I{} dirname {})
+ cd "$CRATE_DIR"
+ cargo readme --input src/lib.rs --output ../README.md
+ cd - > /dev/null
+ fi
+
+ - name: Check if README changed and commit
+ run: |
+ if [ -f "README.md" ]; then
+ if ! git diff --quiet README.md 2>/dev/null; then
+ git add README.md
+ git commit -m "docs: auto-update README.md from lib.rs documentation" || echo "No changes to commit"
+ git push || echo "Nothing to push"
+ else
+ echo "README.md unchanged"
+ fi
+ else
+ echo "README.md not found"
+ fi
+
+ - name: Clean target directory (ensure it's not persisted)
+ run: rm -rf target/ }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Cache cargo
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cargo/registry/index/
+ ~/.cargo/registry/cache/
+ ~/.cargo/git/db/
+ target/
+ key: ${{ runner.os }}-cargo-docs-${{ hashFiles('**/Cargo.lock') }}
+
+ - name: Install cargo-readme
+ run: cargo install cargo-readme
+
+ - name: Generate documentation
+ run: |
+ cargo doc --all-features --no-deps --workspace
+ echo '' > target/doc/index.html
+
+ - name: Copy additional files to docs
+ run: |
+ cp README.md target/doc/ || true
+ cp LICENSE* target/doc/ || true
+
+ - name: Setup Pages
+ uses: actions/configure-pages@v4
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: 'target/doc'
+
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
+
+ - name: Generate README from lib.rs
+ run: |
+ # Find the main library crate
+ if [ -f "src/lib.rs" ]; then
+ cargo readme --input src/lib.rs --output README.md
+ elif [ -f "*/src/lib.rs" ]; then
+ # Handle workspace with multiple crates
+ for crate_dir in */; do
+ if [ -f "${crate_dir}src/lib.rs" ] && [ -f "${crate_dir}Cargo.toml" ]; then
+ cd "$crate_dir"
+ cargo readme --input src/lib.rs --output ../README.md
+ cd ..
+ break
+ fi
+ done
+ fi
+
+ - name: Check if README changed
+ id: readme_check
+ run: |
+ if git diff --quiet README.md; then
+ echo "changed=false" >> $GITHUB_OUTPUT
+ else
+ echo "changed=true" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Commit updated README
+ if: steps.readme_check.outputs.changed == 'true'
+ run: |
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+ git add README.md
+ git commit -m "docs: auto-update README.md from lib.rs documentation"
+ git push
+
+ - name: Clean target directory
+ run: cargo clean
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b83d222
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..7ae65eb
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,352 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "bumpalo"
+version = "3.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee"
+
+[[package]]
+name = "cc"
+version = "1.2.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "956a5e21988b87f372569b66183b78babf23ebc2e744b733e4350a752c4dafac"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+
+[[package]]
+name = "chrono"
+version = "0.4.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "crash-upload"
+version = "0.1.0"
+dependencies = [
+ "chrono",
+ "curl-interface",
+ "json-parser-interface",
+ "platform-interface",
+ "utils",
+]
+
+[[package]]
+name = "crash-upload-test"
+version = "0.1.0"
+dependencies = [
+ "crash-upload",
+]
+
+[[package]]
+name = "curl-interface"
+version = "0.1.0"
+
+[[package]]
+name = "curl-interface-test"
+version = "0.1.0"
+dependencies = [
+ "curl-interface",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json-parser-interface"
+version = "0.1.0"
+
+[[package]]
+name = "json-parser-interface-test"
+version = "0.1.0"
+dependencies = [
+ "json-parser-interface",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.172"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "platform-interface"
+version = "0.1.0"
+
+[[package]]
+name = "platform-interface-test"
+version = "0.1.0"
+dependencies = [
+ "platform-interface",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "syn"
+version = "2.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6397daf94fa90f058bd0fd88429dd9e5738999cca8d701813c80723add80462"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "utils"
+version = "0.1.0"
+
+[[package]]
+name = "utils-test"
+version = "0.1.0"
+dependencies = [
+ "utils",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3bfe459f85da17560875b8bf1423d6f113b7a87a5d942e7da0ac71be7c61f8b"
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link",
+]
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..de5fc8e
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,18 @@
+[workspace]
+members = [
+ "crash-upload",
+ "crates/curl-interface",
+ "crates/json-parser-interface",
+ "crates/platform-interface",
+ "crates/utils",
+ "test/crash-upload-test",
+ "test/curl-interface-test",
+ "test/json-parser-interface-test",
+ "test/platform-interface-test",
+ "test/utils-test",
+]
+
+[profile.release]
+opt-level = "z"
+codegen-units = 1
+lto = true
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..6c1df62
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,44 @@
+TODO:
+- Capture existing uploadDumps.sh and uploadDumpsToS3.sh functionality wise in detail
+- Same process multiple crashes, Multiple process crashes (corner cases)
+- Separate Logging log.rs, with WARN, INFO, ERROR, etc log levels
+- get_sha1() -> using sha1 crate/ process::Command() if single use
+- get_last_modified_time_of_file() -> using chrono crate/ process::Command() if single use
+- ~~args parsing in main app~~
+
+#### Function Implementations
+----
+**COMPLETED**
+- ~~setLogFile()~~
+- ~~create_lock_or_exit()~~
+- ~~create_lock_or_wait()~~
+- ~~remove_lock()~~
+- ~~sanitize()~~
+- ~~deleteAllButTheMostRecentFiles()~~
+- ~~cleanup()~~
+- ~~finalize()~~
+- ~~sigkill_function()~~
+- ~~sigterm_function()~~
+- ~~logUploadTimestamp()~~
+- ~~truncateTimeStampFile()~~
+- ~~isUploadLimitReached()~~
+- ~~setRecoveryTime()~~
+- ~~isRecoveryTimeReached()~~
+- ~~removePendingDumps()~~
+- ~~is_box_rebooting()~~
+- ~~shouldProcessFile()~~
+- ~~get_crashed_log_file()~~
+- ~~processCrashTelemtryInfo()~~
+- ~~add_crashed_log_file()~~ - optional args
+- ~~copy_log_files_tmp_dir()~~ - optional args
+- ~~processDumps()~~ - requires add_crashed_log_file() and copy_log_files_tmp_dir()
+- ~~saveDump()~~
+- ~~markAsCrashLoopedAndUpload()~~
+- checkParameter() - NA
+- logMessage() - Not req
+- tlsLog() - Not req
+- logStdout() - Not req
+----
+**TODO**
+- Signal Handling TRAPs
+----
\ No newline at end of file
diff --git a/coredump-upload.service b/coredump-upload.service
index 25ca2f0..2e9fc18 100644
--- a/coredump-upload.service
+++ b/coredump-upload.service
@@ -26,4 +26,4 @@ Requires=network-online.target
#Environment="TS="
#Type=oneshot
#RemainAfterExit=yes
-ExecStart=/bin/busybox sh -c '/lib/rdk/uploadDumps.sh "" 0 '
+ExecStart=/usr/bin/crash-upload 1 "" ""
diff --git a/crash-upload/Cargo.toml b/crash-upload/Cargo.toml
new file mode 100644
index 0000000..c984544
--- /dev/null
+++ b/crash-upload/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "crash-upload"
+version = "0.1.0"
+edition = "2021"
+description = "Crash dump uploader"
+repository = "https://github.com/rdkcentral/crashupload/tree/feature-sample-rust/crash-upload"
+homepage = "https://github.com/rdkcentral/crashupload/tree/feature-sample-rust/crash-upload"
+
+[dependencies]
+# Internal crates
+curl-interface = { path = "../crates/curl-interface" }
+json-parser-interface = { path = "../crates/json-parser-interface" }
+platform-interface = { path = "../crates/platform-interface" }
+utils = { path = "../crates/utils" }
+chrono = "0.4"
diff --git a/crash-upload/src/constants.rs b/crash-upload/src/constants.rs
new file mode 100644
index 0000000..17eba72
--- /dev/null
+++ b/crash-upload/src/constants.rs
@@ -0,0 +1,230 @@
+//! Crash upload system constants and configuration structures.
+//!
+//! This module defines constants and configuration structs used throughout the crash upload system,
+//! including file paths, RFC names, and device/dump metadata.
+//!
+//! ## Constants Categories
+//!
+//! * **Log Files & Paths**: Constants for log file locations and related configurations
+//! * **Property Files**: Paths to device and system property files
+//! * **Feature Toggle Files**: Files that enable/disable certain features like secure dump
+//! * **Network & System Time**: Constants related to network availability and system time checks
+//! * **TR-181 Parameters**: RFC parameter paths for various configuration options
+//!
+//! ## Configuration Structures
+//!
+//! * [`DumpPaths`]: Holds paths and file extensions for dump processing operations
+//! * [`DeviceData`]: Contains device-specific metadata like MAC address, model number, etc.
+
+/// Path to the log mapper configuration file.
+pub const LOGMAPPER_FILE: &str = "/etc/breakpad-logmapper.conf";
+/// Path to the file listing minidump log files.
+pub const LOG_FILES: &str = "/tmp/minidump_log_files.txt";
+
+/// Base path for RDK logs.
+pub const LOG_PATH: &str = "/opt/rdk";
+/// Path to the core dump log file.
+pub const CORE_LOG: &str = "/opt/logs/core_log.txt";
+
+/// Path to the device properties file.
+pub const DEVICE_PROP_FILE: &str = "/etc/device.properties";
+
+/// Path to the Telemetry2 shared script.
+pub const T2_SHARED_SCRIPT: &str = "/lib/rdk/t2Shared_api.sh";
+/// Maximum number of core files to process.
+pub const MAX_CORE_FILES: usize = 4;
+/// Path to the script for uploading dumps to S3.
+pub const S3_UPLOAD_SCRIPT: &str = "/lib/rdk/uploadDumpsToS3.sh";
+
+/// TR-181 parameter name for secure dump feature toggle.
+pub const SECUREDUMP_TR181_NAME: &str = "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.SecDump.Enable";
+/// File indicating secure dump is enabled.
+pub const SECUREDUMP_ENABLE_FILE: &str = "/tmp/.SecureDumpEnable";
+/// File indicating secure dump is disabled.
+pub const SECUREDUMP_DISABLE_FILE: &str = "/tmp/.SecureDumpDisable";
+/// Flag file to trigger reboot after crash upload.
+pub const CRASH_UPLOAD_REBOOT_FLAG: &str = "/tmp/set_crash_reboot_flag";
+/// File containing timestamp until which dump uploads are denied.
+pub const DENY_UPLOAD_FILE: &str = "/tmp/.deny_dump_uploads_till";
+/// File containing parameters for S3 uploads.
+pub const S3_UPLOAD_PARAM_FILE: &str = "/tmp/uploadtos3params";
+
+/// Flag file to indicate crash uploads should happen on startup.
+pub const UPLOAD_ON_STARTUP: &str = "/opt/.upload_on_startup";
+/// Base path for file indicating startup dumps were cleaned up.
+pub const ON_STARTUP_DUMPS_CLEANED_UP_BASE: &str = "/tmp/.on_startup_dumps_cleaned_up";
+/// File to flag crash loop detection (TODO: implement).
+pub const CRASH_LOOP_FLAG_FILE: &str = ""; // TODO
+
+/// File used for coredump mutex coordination.
+pub const COREDUMP_MTX_FILE: &str = "/tmp/coredump_mutex_release";
+
+/// File indicating network route availability.
+pub const NETWORK_FILE: &str = "/tmp/route_available";
+/// File indicating system time has been received.
+pub const SYSTEM_TIME_FILE: &str = "/tmp/stt_received";
+/// Number of iterations for network availability check.
+pub const NETWORK_CHECK_ITERATION: usize = 18;
+/// Timeout in seconds between network check iterations.
+pub const NETWORK_CHECK_TIMEOUT: usize = 10;
+/// Number of iterations for system time check.
+pub const SYSTEM_TIME_ITERATION: usize = 10;
+/// Timeout in seconds between system time check iterations.
+pub const SYSTEM_TIME_TIMEOUT: usize = 1;
+
+pub const ENCRYPTION_RFC: &str = "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.CrashUpload.encryptionEnabled";
+pub const CRASH_PORTAL_URL_RFC: &str = "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.CrashUpload.crashPortalSTBUrl";
+pub const ENABLE_OSCP_STAPLING: &str = "/tmp/.EnableOCSPStapling";
+pub const ENABLE_OSCP: &str = "/tmp/.EnableOCSPCA";
+
+/* UNUSED
+pub const INCLUDE_PROP_FILE: &str = "/opt/include.properties";
+pub const COREDUMP_PROP_FILE: &str = "/opt/coredump.properties";
+pub const HTTP_CODE_FILE: &str = "/tmp/httpcode";
+pub const CURL_UPLOAD_TIMEOUT: usize = 45;
+pub const S3_FILENAME: &str = "s3filename";
+
+pub const POTOMAC_USER: &str = "ccpstbscp";
+pub const SHA1_DEFAULT_VALUE: &str = "0000000000000000000000000000000000000000";
+pub const TIMESTAMP_DEFAULT_VALUE: &str = "2000-01-01-00-00-00";
+pub const MAC_DEFAULT_VALUE: &str = "000000000000";
+pub const MODEL_NUM_DEFAULT_VALUE: &str = "UNKNOWN";
+*/
+
+/// Holds all relevant paths and extensions for dump processing.
+///
+/// This structure stores all the path information needed for processing crash dumps,
+/// including source and destination directories, file extensions, and working directories.
+#[derive(Debug)]
+pub struct DumpPaths {
+ pub dump_name: String,
+ pub core_path: String,
+ pub minidumps_path: String,
+ pub core_back_path: String,
+ pub persistent_sec_path: String,
+ pub working_dir: String,
+ pub dumps_extn: String,
+ pub tar_extn: String,
+ pub lock_dir_prefix: String,
+ pub crash_portal_path: String,
+ pub ts_file: String,
+}
+
+impl DumpPaths {
+ /// Creates a new `DumpPaths` instance with default values.
+ pub fn new() -> Self {
+ Self {
+ dump_name: String::new(),
+ core_path: String::new(),
+ minidumps_path: String::new(),
+ core_back_path: String::new(),
+ persistent_sec_path: String::new(),
+ working_dir: String::new(),
+ dumps_extn: String::new(),
+ tar_extn: String::new(),
+ lock_dir_prefix: String::new(),
+ crash_portal_path: String::new(),
+ ts_file: String::new(),
+ }
+ }
+ // Getters
+ pub fn get_dump_name(&self) -> &str { &self.dump_name }
+ pub fn get_core_path(&self) -> &str { &self.core_path }
+ pub fn get_minidumps_path(&self) -> &str { &self.minidumps_path }
+ pub fn get_core_back_path(&self) -> &str { &self.core_back_path }
+ pub fn get_persistent_sec_path(&self) -> &str { &self.persistent_sec_path }
+ pub fn get_working_dir(&self) -> &str { &self.working_dir }
+ pub fn get_dumps_extn(&self) -> &str { &self.dumps_extn }
+ pub fn get_tar_extn(&self) -> &str { &self.tar_extn }
+ pub fn get_lock_dir_prefix(&self) -> &str { &self.lock_dir_prefix }
+ pub fn get_crash_portal_path(&self) -> &str { &self.crash_portal_path }
+ pub fn get_ts_file(&self) -> &str { &self.ts_file }
+
+ // Setters
+ pub fn set_dump_name(&mut self, value: impl Into) { self.dump_name = value.into(); }
+ pub fn set_core_path(&mut self, value: impl Into) { self.core_path = value.into(); }
+ pub fn set_minidumps_path(&mut self, value: impl Into) { self.minidumps_path = value.into(); }
+ pub fn set_core_back_path(&mut self, value: impl Into) { self.core_back_path = value.into(); }
+ pub fn set_persistent_sec_path(&mut self, value: impl Into) { self.persistent_sec_path = value.into(); }
+ pub fn set_working_dir(&mut self, value: impl Into) { self.working_dir = value.into(); }
+ pub fn set_dumps_extn(&mut self, value: impl Into) { self.dumps_extn = value.into(); }
+ pub fn set_tar_extn(&mut self, value: impl Into) { self.tar_extn = value.into(); }
+ pub fn set_lock_dir_prefix(&mut self, value: impl Into) { self.lock_dir_prefix = value.into(); }
+ pub fn set_crash_portal_path(&mut self, value: impl Into) { self.crash_portal_path = value.into(); }
+ pub fn set_ts_file(&mut self, value: impl Into) { self.ts_file = value.into(); }
+}
+
+/// Holds device-specific metadata and configuration.
+///
+/// This structure contains information about the device itself, such as its
+/// type, model, identifiers, and configuration settings that affect the
+/// crash upload process.
+#[derive(Debug)]
+pub struct DeviceData {
+ pub device_type: String,
+ pub box_type: String,
+ pub model_num: String,
+ pub sha1: String,
+ pub mac_addr: String,
+ pub is_t2_enabled: bool,
+ pub is_tls_enabled: String,
+ pub is_encryption_enabled: bool,
+ pub build_type: String,
+ pub portal_url: String,
+ //pub device_name: String,
+}
+
+impl DeviceData {
+ /// Creates a new `DeviceData` instance with default values.
+ pub fn new() -> Self {
+ Self {
+ device_type: String::new(),
+ box_type: String::new(),
+ model_num: String::new(),
+ sha1: String::new(),
+ mac_addr: String::new(),
+ is_t2_enabled: false,
+ is_tls_enabled: String::new(),
+ is_encryption_enabled: false,
+ build_type: String::new(),
+ portal_url: String::new(),
+ //device_name: String::new(),
+ }
+ }
+ // Getters
+ pub fn get_device_type(&self) -> &str { &self.device_type }
+ pub fn get_box_type(&self) -> &str { &self.box_type }
+ pub fn get_model_num(&self) -> &str { &self.model_num }
+ pub fn get_sha1(&self) -> &str { &self.sha1 }
+ pub fn get_mac_addr(&self) -> &str { &self.mac_addr }
+ pub fn get_is_t2_enabled(&self) -> bool { self.is_t2_enabled }
+ pub fn get_is_tls_enabled(&self) -> &str { &self.is_tls_enabled }
+ pub fn get_is_encryption_enabled(&self) -> bool { self.is_encryption_enabled }
+ pub fn get_build_type(&self) -> &str { &self.build_type }
+ pub fn get_portal_url(&self) -> &str { &self.portal_url }
+ //pub fn device_name(&self) -> &str { &self.device_name }
+
+ // Setters
+ pub fn set_device_type(&mut self, value: impl Into) { self.device_type = value.into(); }
+ pub fn set_box_type(&mut self, value: impl Into) { self.box_type = value.into(); }
+ pub fn set_model_num(&mut self, value: impl Into) { self.model_num = value.into(); }
+ pub fn set_sha1(&mut self, value: impl Into) { self.sha1 = value.into(); }
+ pub fn set_mac_addr(&mut self, value: impl Into) { self.mac_addr = value.into(); }
+ pub fn set_t2_enabled(&mut self, value: bool) { self.is_t2_enabled = value; }
+ pub fn set_tls(&mut self, value: impl Into) { self.is_tls_enabled = value.into(); }
+ pub fn set_encryption_enabled(&mut self, value: bool) { self.is_encryption_enabled = value; }
+ pub fn set_build_type(&mut self, value: impl Into) { self.build_type = value.into(); }
+ pub fn set_portal_url(&mut self, value: impl Into) { self.portal_url = value.into(); }
+ //pub fn set_device_name(&mut self, value: impl Into) { self.device_name = value.into(); }
+}
+
+/// Enum representing the type of signal received.
+///
+/// Used to differentiate between different types of termination signals
+/// that may be received by the crash upload process.
+pub enum CrashSignal {
+ /// Normal termination signal (SIGTERM)
+ Term,
+ /// Kill signal (SIGKILL)
+ Kill,
+}
diff --git a/crash-upload/src/crashupload_utils.rs b/crash-upload/src/crashupload_utils.rs
new file mode 100644
index 0000000..6388ada
--- /dev/null
+++ b/crash-upload/src/crashupload_utils.rs
@@ -0,0 +1,2310 @@
+//! Crash upload utility functions and implementation.
+//!
+//! This module provides the core functionality for the crash upload system, including:
+//!
+//! - **Dump file detection and processing**: Functions to identify, sanitize, and process
+//! various types of crash dumps (minidumps and coredumps)
+//!
+//! - **Tarball creation and management**: Utilities to compress dump files with relevant
+//! logs into tarballs for upload
+//!
+//! - **Upload control and rate limiting**: Functions to manage upload rates, recovery time,
+//! and crash loop detection
+//!
+//! - **Security and privacy controls**: Implementation of secure dump handling and privacy
+//! mode checks
+//!
+//! - **Lock management**: Mutex-like functionality to prevent concurrent execution of
+//! critical sections
+//!
+//! - **File and path utilities**: Specialized functions for file manipulation, path handling,
+//! and directory management
+//!
+//! - **Telemetry integration**: Functions to report crash events and upload status to the
+//! Telemetry2 (T2) system
+//!
+//! ## Key Workflows
+//!
+//! The main workflow in this module is the dump processing pipeline:
+//! 1. Detecting crash dumps in configured directories
+//! 2. Sanitizing file names and paths
+//! 3. Collecting relevant log files and device information
+//! 4. Creating compressed archives (tarballs) containing dumps and logs
+//! 5. Uploading archives to cloud storage (S3) with retries
+//! 6. Managing cleanup and retention of local dumps
+//!
+//! ## Integration Points
+//!
+//! This module integrates with several platform components:
+//! - **TR-181/RFC system** (via platform-interface): For configuration parameters
+//! - **Telemetry2 (T2)**: For reporting crash and upload events
+//! - **S3 storage**: For uploading crash dumps to the cloud
+//! - **Device property system**: For obtaining device metadata
+//!
+//! Most functions in this module are designed to be self-contained and follow
+//! the single responsibility principle for maintainability.
+
+// crashupload/src/crashupload_utils.rs
+use chrono::{DateTime, Local};
+use std::fs::{self, File, OpenOptions};
+use std::io::{self, BufRead, BufReader, Write};
+use std::os::unix::fs::PermissionsExt;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
+use std::process;
+use std::{thread, time};
+
+use crate::constants::*;
+use platform_interface::*;
+use utils::*;
+
+/// Determines if a file is a minidump based on its name.
+///
+/// Checks if the filename contains the substring ".dmp" anywhere in the string,
+/// which matches the shell pattern "*.dmp*".
+///
+/// # Arguments
+/// * `name` - The filename to check.
+///
+/// # Returns
+/// * `true` if the filename contains ".dmp", `false` otherwise.
+#[inline]
+fn is_minidump_file(name: &str) -> bool {
+ // *.dmp* matches any file with ".dmp" after the first character
+ name.find(".dmp").is_some()
+}
+
+/// Determines if a file is a core pattern file based on its name.
+///
+/// A file is considered a core pattern file if it contains "_core" followed by
+/// a dot somewhere after the "_core" substring. This matches the shell pattern
+/// "*_core*.*".
+///
+/// # Arguments
+/// * `name` - The filename to check.
+///
+/// # Returns
+/// * `true` if the filename matches the core pattern format, `false` otherwise.
+pub fn is_core_pattern_file(name: &str) -> bool {
+ if let Some(core_idx) = name.find("_core") {
+ // There must be a '.' after the '_core'
+ let after_core = &name[core_idx + 5..]; // 5 = len("_core")
+ after_core.contains('.')
+ } else {
+ false
+ }
+}
+
+/// Checks if a directory is empty or cannot be read.
+///
+/// This function tries to read the directory entries. If the directory cannot be read
+/// (due to permissions or other issues), it's treated as if it were empty.
+///
+/// # Arguments
+/// * `dir` - Path to the directory to check.
+///
+/// # Returns
+/// * `true` if the directory is empty or unreadable, `false` otherwise.
+pub fn is_dir_empty_or_unreadable>(dir: P) -> bool {
+ match fs::read_dir(dir) {
+ Ok(mut entries) => entries.next().is_none(),
+ Err(_) => true, // treat unreadable as empty
+ }
+}
+
+/// Safely renames a file, with fallback to copy-and-delete if rename fails with EXDEV error.
+///
+/// This function attempts to rename a file from `src` to `dst`. If the rename fails with
+/// error code 18 (EXDEV, "Cross-device link"), it falls back to copying the file and then
+/// deleting the original. This handles cases where the source and destination are on
+/// different filesystems.
+///
+/// # Arguments
+/// * `src` - Source path.
+/// * `dst` - Destination path. If relative, it's resolved relative to `src`'s parent.
+///
+/// # Returns
+/// * `Ok(())` on success, or an error if both rename and copy-delete fail.
+pub fn safe_rename, D: AsRef>(src: S, dst: D) -> io::Result<()> {
+ let src_path = src.as_ref();
+ let dst_path = {
+ let dst_ref = dst.as_ref();
+ if dst_ref.is_absolute() {
+ dst_ref.to_path_buf()
+ } else {
+ src_path.parent().unwrap_or_else(|| Path::new("")).join(dst_ref)
+ }
+ };
+ match fs::rename(&src_path, &dst_path) {
+ Ok(_) => Ok(()),
+ Err(e) if e.raw_os_error() == Some(18) => {
+ fs::copy(&src_path, &dst_path)?;
+ fs::remove_file(&src_path)?;
+ Ok(())
+ }
+ Err(e) => Err(e),
+ }
+}
+
+/// Extracts the basename (filename) from a path.
+///
+/// This function extracts just the filename portion from a path. If the path doesn't
+/// have a valid filename component, returns the entire path as a string.
+///
+/// # Arguments
+/// * `path` - Path to extract the basename from.
+///
+/// # Returns
+/// * A `String` containing the basename (filename without directory components).
+#[inline]
+pub fn basename>(path: P) -> String {
+ path.as_ref()
+ .file_name()
+ .and_then(|n| n.to_str())
+ .map(|s| s.to_string())
+ .unwrap_or_else(|| path.as_ref().to_string_lossy().to_string())
+}
+
+/// Ensures the core log file exists, creating it with appropriate permissions if needed.
+///
+/// This function checks if the core log file exists at the path defined by `CORE_LOG`.
+/// If not, it creates the file with 0666 permissions (readable and writable by everyone).
+/// It then uses `touch` to update the file's timestamp regardless.
+///
+/// # Side Effects
+/// - May create a new file at `CORE_LOG` path.
+/// - Updates the file's timestamp.
+fn ensure_core_log_exists() {
+ if !Path::new(CORE_LOG).exists() {
+ if let Ok(f) = OpenOptions::new().create(true).write(true).open(CORE_LOG) {
+ let _ = f.set_permissions(fs::Permissions::from_mode(0o666));
+ }
+ }
+ let _ = Command::new("touch").arg(CORE_LOG).status();
+}
+
+#[cfg(not(feature = "shared_api"))]
+/// Writes parameters for S3 upload to a parameter file for shell script consumption.
+///
+/// Creates a space-separated parameter string with all necessary arguments for the S3 upload
+/// process and writes it to the file specified by `S3_UPLOAD_PARAM_FILE`. Used when the
+/// `shared_api` feature is not enabled.
+///
+/// # Arguments
+/// * `dump_paths` - Reference to crash dump path configuration.
+/// * `device_data` - Reference to device metadata.
+/// * `partner_id` - Partner identifier string.
+/// * `enable_ocsp_stapling` - OCSP stapling configuration flag.
+/// * `enable_ocsp` - OCSP configuration flag.
+/// * `curl_log_option` - Curl logging option string.
+///
+/// # Returns
+/// * `Ok(())` on success, or an error if file write fails.
+fn write_uploadtos3params(
+ dump_paths: &DumpPaths,
+ device_data: &DeviceData,
+ partner_id: &str,
+ enable_ocsp_stapling: &str,
+ enable_ocsp: &str,
+ curl_log_option: &str,
+) -> std::io::Result<()> {
+ let params = format!(
+ "{} {} {} {} {} {} {} {} {} {} {} {}",
+ dump_paths.get_working_dir(),
+ partner_id,
+ dump_paths.get_dump_name(),
+ device_data.get_device_type(),
+ VERSION_FILE,
+ if device_data.get_is_encryption_enabled() { "true" } else { "false" },
+ enable_ocsp_stapling,
+ enable_ocsp,
+ device_data.get_is_tls_enabled(),
+ device_data.get_build_type(),
+ device_data.get_model_num(),
+ curl_log_option
+ );
+ std::fs::write(S3_UPLOAD_PARAM_FILE, params)
+}
+
+/// Populates a [`DeviceData`] struct with device properties from system files and TR-181.
+///
+/// This function reads various device properties from multiple sources:
+/// - Device properties file (box type, model number, device type, build type)
+/// - Version file (SHA1 value)
+/// - Network configuration (MAC address)
+/// - System files (T2 enablement, TLS configuration, encryption settings)
+/// - TR-181 parameters (portal URL)
+///
+/// The collected information is essential for crash upload identification, processing, and routing.
+///
+/// # Arguments
+/// * `device_data` - Mutable reference to a [`DeviceData`] struct to populate.
+///
+/// # Side Effects
+/// - Reads from filesystem and TR-181 parameters
+/// - May set RFC parameters if encryption is enabled
+/// - No files are modified
+pub fn set_device_data(device_data: &mut DeviceData) {
+ // Populate from device.properties
+ let mut box_type = String::new();
+ let mut model_num = String::new();
+ let mut device_type = String::new();
+ let mut build_type = String::new();
+ get_property_value_from_file(DEVICE_PROP_FILE, "BOX_TYPE", &mut box_type);
+ get_property_value_from_file(DEVICE_PROP_FILE, "MODEL_NUM", &mut model_num);
+ get_property_value_from_file(DEVICE_PROP_FILE, "DEVICE_TYPE", &mut device_type);
+ get_property_value_from_file(DEVICE_PROP_FILE, "BUILD_TYPE", &mut build_type);
+ device_data.set_box_type(box_type);
+ device_data.set_model_num(model_num);
+ device_data.set_device_type(device_type);
+ device_data.set_build_type(build_type);
+
+ // SHA1 from version.txt
+ let mut sha1 = String::new();
+ get_sha1_value(&mut sha1);
+ device_data.set_sha1(sha1);
+
+ // MAC address
+ let mut mac_addr = String::new();
+ let _ = get_device_mac(&mut mac_addr);
+ device_data.set_mac_addr(mac_addr);
+
+ // T2 enabled if script exists
+ device_data.set_t2_enabled(Path::new(T2_SHARED_SCRIPT).exists()); // TODO: Check T2 Binary instead of script?
+
+ // TLS flag for Yocto
+ let tls = if Path::new("/etc/os-release").exists() { "--tlsv1.2" } else { "" };
+ device_data.set_tls(tls);
+
+ // Encryption enabled if file exists, and set RFC param
+ let encryption_enabled = if Path::new("/etc/os-release").exists() {
+ if set_rfc_param(ENCRYPTION_RFC, "true") { true }
+ else { false }
+ } else {
+ false
+ };
+ device_data.set_encryption_enabled(encryption_enabled);
+
+ // Portal URL from TR-181
+ let mut portal_url = String::new();
+ get_rfc_param(CRASH_PORTAL_URL_RFC, &mut portal_url);
+ device_data.set_portal_url(portal_url);
+}
+
+/// Checks if any crash dump files exist in the specified directories.
+///
+/// This function examines two types of dumps in their respective directories:
+/// - Minidumps: Files containing ".dmp" in their name in the minidumps directory
+/// - Core dumps: Files matching the "*_core*.*" pattern in the core dump directory
+///
+/// # Arguments
+/// * `minidumps_path` - Directory path to search for minidump files
+/// * `core_path` - Directory path to search for core dump files
+///
+/// # Returns
+/// * `true` if at least one dump file (of either type) exists, `false` otherwise
+pub fn check_dumps_exist(minidumps_path: &str, core_path: &str) -> bool {
+ let minidumps_dir = std::path::Path::new(minidumps_path);
+ let core_dir = std::path::Path::new(core_path);
+
+ let minidump_exists = minidumps_dir.is_dir() && std::fs::read_dir(minidumps_dir)
+ .map(|iter| iter.flatten().any(|entry| {
+ let name = entry.file_name();
+ let name = name.to_string_lossy();
+ // Faithful to *.dmp* shell pattern
+ is_minidump_file(&name)
+ }))
+ .unwrap_or(false);
+
+ let core_exists = core_dir.is_dir() && std::fs::read_dir(core_dir)
+ .map(|iter| iter.flatten().any(|entry| {
+ let name = entry.file_name();
+ let name = name.to_string_lossy();
+ // Faithful to *_core*.* shell pattern
+ is_core_pattern_file(&name)
+ }))
+ .unwrap_or(false);
+
+ minidump_exists || core_exists
+}
+
+/// Configures secure or non-secure dump paths based on SecureDump enablement status.
+///
+/// This function determines if SecureDump is enabled by checking flag files and TR-181 parameters,
+/// then updates the dump paths accordingly:
+/// - When enabled: Uses paths under /opt/secure/
+/// - When disabled: Uses standard paths under /opt/ and /var/
+///
+/// It also manages the appropriate flag files to maintain the system's secure dump state.
+///
+/// # Arguments
+/// * `dump_paths` - Mutable reference to a [`DumpPaths`] struct to update with appropriate paths
+///
+/// # Side Effects
+/// - Creates or removes SecureDump enable/disable flag files
+/// - Updates all path settings in the provided `dump_paths` struct
+/// - Logs the SecureDump status
+pub fn get_secure_dump_status(dump_paths: &mut DumpPaths) {
+ println!("get_secure_dump_status: Checking SecureDump status...");
+ let mut is_sec_dump_enabled = false;
+ set_secure_dump_flag(&mut is_sec_dump_enabled);
+
+ if !is_sec_dump_enabled {
+ if Path::new(SECUREDUMP_ENABLE_FILE).exists() {
+ touch(SECUREDUMP_DISABLE_FILE);
+ println!("get_secure_dump_status: Disabled");
+ }
+ if Path::new(SECUREDUMP_ENABLE_FILE).exists() {
+ let _ = fs::remove_file(SECUREDUMP_ENABLE_FILE);
+ }
+ dump_paths.set_core_path("/var/lib/systemd/coredump");
+ dump_paths.set_minidumps_path("/opt/minidumps");
+ dump_paths.set_core_back_path("/opt/corefiles_back");
+ dump_paths.set_persistent_sec_path("/opt");
+ println!("get_secure_dump_status: Dump location changed /opt.");
+ } else {
+ if !Path::new(SECUREDUMP_ENABLE_FILE).exists() {
+ touch(SECUREDUMP_ENABLE_FILE);
+ println!("get_secure_dump_status: Enabled");
+ }
+ if Path::new(SECUREDUMP_DISABLE_FILE).exists() {
+ let _ = fs::remove_file(SECUREDUMP_DISABLE_FILE);
+ }
+ dump_paths.set_core_path("/opt/secure/corefiles");
+ dump_paths.set_minidumps_path("/opt/secure/minidumps");
+ dump_paths.set_core_back_path("/opt/secure/corefiles_back");
+ dump_paths.set_persistent_sec_path("/opt/secure");
+ println!("get_secure_dump_status: Dump location changed /opt/secure.");
+ }
+}
+
+/// Determines if SecureDump is enabled by checking flag files or TR-181 parameters.
+///
+/// This function implements a priority-based check for SecureDump enablement:
+/// 1. If SECUREDUMP_ENABLE_FILE exists, SecureDump is enabled
+/// 2. If SECUREDUMP_DISABLE_FILE exists, SecureDump is disabled
+/// 3. Otherwise, check the TR-181 parameter for the setting
+///
+/// # Arguments
+/// * `is_sec_dump_enabled` - Mutable reference to a boolean to set based on the determination
+///
+/// # Side Effects
+/// - Reads from the filesystem and possibly TR-181 configuration
+/// - Modifies the passed boolean reference
+fn set_secure_dump_flag(is_sec_dump_enabled: &mut bool) {
+ if Path::new(SECUREDUMP_ENABLE_FILE).exists() {
+ *is_sec_dump_enabled = true;
+ } else if Path::new(SECUREDUMP_DISABLE_FILE).exists() {
+ *is_sec_dump_enabled = false;
+ } else {
+ let mut rfc_value = String::new();
+ if get_rfc_param(SECUREDUMP_TR181_NAME, &mut rfc_value) {
+ *is_sec_dump_enabled = rfc_value.trim().eq_ignore_ascii_case("true");
+ }
+ }
+}
+
+/// Creates a standard lock directory path for a given resource path.
+///
+/// This function converts any path into a corresponding lock directory path
+/// by changing its extension to ".lock.d". This lock directory is used
+/// to implement a filesystem-based mutex mechanism.
+///
+/// # Arguments
+/// * `path` - Path to be locked (can be any file or directory path)
+///
+/// # Returns
+/// * `PathBuf` representing the derived lock directory path
+#[inline]
+fn lock_path>(path: P) -> PathBuf {
+ let mut p = path.as_ref().to_path_buf();
+ p.set_extension("lock.d");
+ p
+}
+
+/// Checks if another process instance is running by testing for an existing lock directory.
+///
+/// This function is used to implement mutual exclusion between process instances.
+/// It determines if another instance is running by checking for the existence of
+/// a lock directory associated with the given path.
+///
+/// # Arguments
+/// * `path` - Path to check for an existing lock
+/// # Returns
+/// * `true` if a lock directory exists (another instance is running), `false` otherwise
+#[inline]
+fn is_another_instance_running>(path: P) -> bool {
+ lock_path(path).is_dir()
+}
+
+/// Creates a lock directory or exits the process if already locked.
+///
+/// This function implements an "exit on contention" locking strategy:
+/// - If another instance is running (lock exists), it logs a message,
+/// potentially sends a T2 notification, and exits the process
+/// - Otherwise, it creates the lock directory and returns true on success
+///
+/// # Arguments
+/// * `path` - Path to lock
+/// * `is_t2_enabled` - Whether T2 telemetry is enabled (for sending notifications)
+///
+/// # Returns
+/// * `true` if lock was created successfully
+/// * `false` if lock creation failed due to filesystem errors
+///
+/// # Side Effects
+/// - May exit the process if lock already exists
+/// - May send T2 telemetry notification if enabled
+/// - Creates a lock directory on the filesystem if successful
+pub fn create_lock_or_exit>(path: P, is_t2_enabled: bool) -> bool {
+ let lock = lock_path(&path);
+ if is_another_instance_running(&path) {
+ if is_t2_enabled {
+ t2_count_notify("SYST_WARN_NoMinidump", Some("1"));
+ }
+ println!("create_lock_or_exit: crash-upload is already running. {:?}. Skip launching another instance...", lock);
+ // TODO: add wait
+ process::exit(0);
+ }
+
+ match fs::create_dir(&lock) {
+ Ok(_) => true,
+ Err(err) => {
+ println!("create_lock_or_exit: Error creating {:?}: {}", lock, err);
+ false
+ }
+ }
+}
+
+/// Creates a lock directory, waiting and retrying if already locked.
+///
+/// This function implements a "wait on contention" locking strategy:
+/// - If another instance is running (lock exists), it waits and retries every 2 seconds
+/// - It continues retrying indefinitely until either the lock is acquired or creation fails
+///
+/// # Arguments
+/// * `path` - Path to lock
+///
+/// # Returns
+/// * `true` if lock was created successfully
+/// * `false` if lock creation failed due to filesystem errors
+///
+/// # Side Effects
+/// - May block thread execution while waiting for lock
+/// - Creates a lock directory on the filesystem if successful
+pub fn create_lock_or_wait>(path: P) -> bool {
+ let lock = lock_path(&path);
+ loop {
+ if is_another_instance_running(&path) {
+ println!("create_lock_or_wait: crash-upload is already running. {:?}. Waiting to launch another instance...", lock);
+ thread::sleep(time::Duration::from_secs(2));
+ continue;
+ }
+
+ match fs::create_dir(&lock) {
+ Ok(_) => return true,
+ Err(err) => {
+ println!("create_lock_or_wait: Error creating {:?}: {}", lock, err);
+ return false;
+ }
+ }
+ }
+}
+
+/// Removes a lock directory for the given resource path.
+///
+/// This function cleans up the lock directory created by `create_lock_or_exit` or
+/// `create_lock_or_wait`, effectively releasing the lock. It silently handles the
+/// case where the lock directory doesn't exist.
+///
+/// # Arguments
+/// * `path` - Path whose associated lock should be removed
+///
+/// # Side Effects
+/// - Deletes the lock directory from the filesystem if it exists
+/// - Logs any errors that occur during removal
+pub fn remove_lock>(path: P) {
+ let lock = lock_path(&path);
+ if lock.is_dir() {
+ if let Err(err) = fs::remove_dir(&lock) {
+ println!("remove_lock: Error deleting {:?}: {}", lock, err);
+ }
+ }
+}
+
+/// Generates a standardized log file name for a crash, or returns the original if already processed.
+///
+/// If the file name already contains any of the tags `_mac`, `_dat`, `_box`, or `_mod`,
+/// it is considered already processed and returned as-is. Otherwise, constructs a new
+/// file name using device metadata and the original file name.
+///
+/// # Arguments
+/// * `device_data` - Reference to the device metadata.
+/// * `log_mod_ts` - Last modified timestamp string.
+/// * `line` - The original file path or name.
+///
+/// # Returns
+/// * A `String` with the new or original file name.
+pub fn set_log_file(device_data: &DeviceData, log_mod_ts: &str, line: &str) -> String {
+ let file_name = line.split('/').next_back().unwrap_or(line);
+ if file_name.contains("_mac")
+ || file_name.contains("_dat")
+ || file_name.contains("_box")
+ || file_name.contains("_mod")
+ {
+ println!("set_log_file: Core name is already processed: {}", file_name);
+ return String::from(file_name);
+ }
+ format!(
+ "{}_mac{}_dat{}_box{}_mod{}_{}",
+ device_data.sha1,
+ device_data.mac_addr,
+ log_mod_ts,
+ device_data.box_type,
+ device_data.model_num,
+ file_name
+ )
+}
+
+/// Checks if the box is currently rebooting by looking for the reboot flag file.
+///
+/// If the reboot flag exists, logs a message, sends a T2 notification, and returns `true`.
+/// Otherwise, returns `false`.
+///
+/// # Arguments
+/// * `is_t2_enabled` - Whether T2 telemetry is enabled (for sending notifications)
+///
+/// # Returns
+/// * `true` if the box is rebooting (flag file exists), `false` otherwise.
+/// # Side Effects
+/// - May send T2 telemetry notification if enabled
+pub fn is_box_rebooting(is_t2_enabled: bool) -> bool {
+ if Path::new(CRASH_UPLOAD_REBOOT_FLAG).exists() {
+ println!("is_box_rebooting: Skipping Upload, Since the box is rebooting now...");
+ if is_t2_enabled {
+ t2_count_notify("SYST_INFO_CoreUpldSkipped", Some("1"));
+ }
+ println!("is_box_rebooting: Upload will happen on next reboot");
+ // TODO: Add wait
+ return true;
+ }
+ false
+}
+
+/// Sanitizes a string by removing all characters except a safe set.
+///
+/// Only allows alphanumeric characters and the following: `/ :+._,=-`
+/// This matches the shell's `sed` logic for cleaning file/process names.
+///
+/// # Arguments
+/// * `input` - The input string to sanitize.
+///
+/// # Returns
+/// * A sanitized `String` containing only allowed characters.
+pub fn sanitize(input: &str) -> String {
+ input
+ .chars()
+ .filter(|c| matches!(
+ c,
+ 'a'..='z'
+ | 'A'..='Z'
+ | '0'..='9'
+ | '/'
+ | ' '
+ | ':'
+ | '+'
+ | '.'
+ | '_'
+ | ','
+ | '='
+ | '-'
+ ))
+ .collect()
+}
+
+/// Checks if the recovery time (upload denial window) has been reached or expired.
+///
+/// Reads the timestamp from the deny upload file. If the file does not exist or contains
+/// invalid data, returns `true` (uploads allowed). If the current time is greater than the
+/// stored timestamp, returns `true` (uploads allowed). Otherwise, returns `false`.
+///
+/// # Returns
+/// * `true` if uploads are allowed, `false` if still in the denial window.
+///
+/// # Side Effects
+/// - Reads from the deny upload file on the filesystem
+pub fn is_recovery_time_reached() -> bool {
+ let path = Path::new(DENY_UPLOAD_FILE);
+ if !path.exists() {
+ return true;
+ }
+ let content = match fs::read_to_string(path) {
+ Ok(val) => val.trim().to_string(),
+ Err(_) => return true,
+ };
+ let upload_denied_till = match content.parse::() {
+ Ok(val) => val,
+ Err(_) => return true,
+ };
+ let now = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .unwrap_or_default()
+ .as_secs();
+ now > upload_denied_till
+}
+
+/// Sets the recovery time (upload denial window) to now + 10 minutes.
+///
+/// Writes the future timestamp to the deny upload file. This prevents further uploads
+/// until the specified time has passed.
+///
+/// # Side Effects
+/// - Overwrites the deny upload file with the new timestamp.
+pub fn set_recovery_time() {
+ const DONT_UPLOAD_FOR_SEC: u64 = 600;
+ let now = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("SystemTime before UNIX_EPOCH")
+ .as_secs();
+ let recovery_time = now + DONT_UPLOAD_FOR_SEC;
+ let _ = fs::write(DENY_UPLOAD_FILE, recovery_time.to_string());
+}
+
+/// Determines if the upload rate limit has been reached (10 uploads in 10 minutes).
+///
+/// Checks the timestamp file for the last 10 upload times. If there are fewer than 10,
+/// returns `false`. If the 10th newest timestamp is less than 10 minutes ago, returns `true`.
+///
+/// # Arguments
+/// * `ts_file` - Path to the timestamp file.
+///
+/// # Returns
+/// * `true` if the upload rate limit is reached, `false` otherwise.
+///
+/// # Side Effects
+/// - Acquires and releases a lock on the timestamp file
+/// - Reads from the timestamp file
+pub fn is_upload_limit_reached(ts_file: &str) -> bool {
+ create_lock_or_wait(ts_file);
+
+ const LIMIT_SECONDS: u64 = 600;
+ let path = Path::new(ts_file);
+
+
+ if OpenOptions::new().create(true).append(true).open(path).is_err() {
+ println!("is_upload_limit_reached: Failed to create or open {}", ts_file);
+ remove_lock(ts_file);
+ return false;
+ }
+
+ let file = match File::open(path) {
+ Ok(f) => f,
+ Err(_) => {
+ remove_lock(ts_file);
+ return false;
+ }
+ };
+ let reader = BufReader::new(&file);
+
+ // Collect upto 10 timestamps (oldest first)
+ let lines: Vec = reader
+ .lines()
+ .filter_map(|line| line.ok()?.split_whitespace().next()?.parse().ok())
+ .collect();
+
+ if lines.len() < 10 {
+ remove_lock(ts_file);
+ return false;
+ }
+
+ let tenth_newest_crash_time = lines[0];
+ let now = SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("SystemTime before UNIX_EPOCH")
+ .as_secs();
+
+ let limit_reached = now.saturating_sub(tenth_newest_crash_time) < LIMIT_SECONDS;
+ if limit_reached {
+ println!("is_upload_limit_reached: Not uploading the dump. Too many dumps.");
+ }
+ remove_lock(ts_file);
+ limit_reached
+}
+
+/// Removes the crash loop flag and all relevant lock files for the given dump paths.
+///
+/// This function is inlined for performance, as it is small and called from multiple places.
+///
+/// # Arguments
+/// * `dump_paths` - Reference to the dump paths struct.
+///
+/// # Side Effects
+/// - Deletes the crash loop flag file if it exists
+/// - Removes lock directories associated with the dump paths
+#[inline]
+fn remove_crash_locks_and_flag(dump_paths: &DumpPaths) {
+ let loop_file = Path::new(CRASH_LOOP_FLAG_FILE);
+ if loop_file.exists() {
+ rm_rf(loop_file);
+ }
+ remove_lock(dump_paths.get_lock_dir_prefix());
+ remove_lock(dump_paths.get_ts_file());
+}
+
+/// Finalizes the crash upload process by cleaning up dumps, removing locks, and clearing the crash loop flag.
+///
+/// This function should be called on normal exit to ensure all resources are released and
+/// any temporary files or locks are cleaned up. Uses only getters for `DumpPaths`.
+///
+/// # Arguments
+/// * `dump_paths` - Reference to the dump paths struct.
+///
+/// # Side Effects
+/// - Cleans up dump files in the working directory
+/// - Removes lock files and crash loop flags
+pub fn finalize(dump_paths: &DumpPaths) {
+ println!("finalize: ##DEBUG## Skipping cleanup and removing crash locks and flag for debugging purposes");
+ if false {
+ let _ = cleanup(
+ dump_paths.get_working_dir(),
+ dump_paths.get_dump_name(),
+ dump_paths.get_dumps_extn(),
+ );
+ remove_crash_locks_and_flag(dump_paths);
+}
+}
+
+/// Handles cleanup on receiving a crash-related signal (SIGTERM or SIGKILL).
+///
+/// Logs the signal type, removes the crash loop flag, and removes all relevant locks.
+/// Uses only getters for `DumpPaths`.
+///
+/// # Arguments
+/// * `signal` - The signal type (CrashSignal::Term or CrashSignal::Kill).
+/// * `dump_paths` - Reference to the dump paths struct.
+///
+/// # Side Effects
+/// - Logs the signal type
+/// - Removes crash loop flag and lock files
+pub fn handle_crash_signal(signal: CrashSignal, dump_paths: &DumpPaths) {
+ match signal {
+ CrashSignal::Term => println!("systemd terminating, Removing the script locks"),
+ CrashSignal::Kill => println!("systemd killing, Removing the script locks"),
+ }
+ remove_crash_locks_and_flag(dump_paths);
+}
+
+/// Determines if a dump file should be processed/uploaded based on dump type, device type, and file name.
+///
+/// - Always returns `true` for minidumps.
+/// - For coredumps, returns `true` if not a "prod" build or if the file name does not contain "Receiver".
+/// - Otherwise, logs and returns `false`.
+///
+/// # Arguments
+/// * `dump_name` - The dump type ("coredump" or "minidump").
+/// * `build_type` - The build type (e.g., "prod").
+/// * `file_name` - The file name to check.
+///
+/// # Returns
+/// * `true` if the file should be processed, `false` otherwise.
+pub fn should_process_dump(dump_name: &str, build_type: &str, file_name: &str) -> bool {
+ if dump_name == "minidump" || build_type != "prod" || !file_name.contains("Receiver") {
+ true
+ } else {
+ println!("should_process_dump: Not processing dump file {}", file_name);
+ false
+ }
+}
+
+/// Determines if a file name matches a specified pattern.
+///
+/// This function implements shell-like wildcard pattern matching for specific crash-related
+/// file patterns. It supports the following patterns:
+/// - "*.dmp*": Files containing ".dmp" anywhere in the name
+/// - "*.dmp.tgz": Files ending with ".dmp.tgz"
+/// - "*core.prog*.gz*": Files containing "core.prog" followed by ".gz" somewhere later
+/// - "*.core.tgz": Files ending with ".core.tgz"
+///
+/// # Arguments
+/// * `name` - The file name to check against the pattern.
+/// * `pattern` - The pattern to match against (one of the supported patterns above).
+///
+/// # Returns
+/// * `true` if the file name matches the pattern, `false` otherwise.
+fn matches_pattern(name: &str, pattern: &str) -> bool {
+ match pattern {
+ "*.dmp*" => name.contains(".dmp"),
+ "*.dmp.tgz" => name.ends_with(".dmp.tgz"),
+ "*core.prog*.gz*" => {
+ if let Some(core_idx) = name.find("core.prog") {
+ name[core_idx + "core.prog".len()..].contains(".gz")
+ } else {
+ false
+ }
+ },
+ "*.core.tgz" => name.ends_with(".core.tgz"),
+ _ => false,
+ }
+}
+
+/// Counts the number of dump files in a directory matching a given pattern.
+///
+/// # Arguments
+/// * `dir` - Directory path to search.
+/// * `pattern` - Pattern to match in file names (e.g., "*.dmp*" or "*.core.tgz").
+///
+/// # Returns
+/// * `Ok(count)` with the number of matching files, or an error if the directory can't be read.
+pub fn get_file_count(dir: &str, pattern: &str) -> std::io::Result {
+ let dir_path = std::path::Path::new(dir);
+ if !dir_path.is_dir() {
+ return Ok(0);
+ }
+ let mut count = 0;
+ for entry in std::fs::read_dir(dir_path)? {
+ let entry = entry?;
+ let name = entry.file_name();
+ let name = name.to_string_lossy();
+ if matches_pattern(&name, pattern) {
+ count += 1;
+ }
+ }
+ Ok(count)
+}
+
+/// Removes pending dump files (matching extension or .tgz) from the given directory.
+///
+/// This function is used when the upload limit is reached, the build is blacklisted,
+/// or TelemetryOptOut is set. It removes all files matching the given extension or `.tgz`.
+///
+/// # Arguments
+/// * `path` - Directory path to search.
+/// * `extn` - Extension to match (e.g., "dmp" or "core").
+///
+/// # Returns
+/// * `Ok(())` on success, or an error if file operations fail.
+///
+/// # Side Effects
+/// - Deletes files from the filesystem that match the criteria
+pub fn remove_pending_dumps(path: &str, extn: &str) -> io::Result<()> {
+ println!("remove_pending_dumps: ###DEBUG### Skipping pending dumps removal for debugging purposes");
+ if false {
+ let dir_path = Path::new(path);
+ if !dir_path.exists() || !dir_path.is_dir() {
+ return Ok(());
+ }
+ for entry in fs::read_dir(dir_path)? {
+ let entry = entry?;
+ let file_path = entry.path();
+ if file_path.is_file() {
+ let file_name = file_path.file_name().and_then(|n| n.to_str()).unwrap_or("");
+ if file_name.ends_with(extn) || file_name.ends_with(".tgz") {
+ println!("remove_pending_dumps: Removing {} because upload limit has reached or build is blacklisted or TelemetryOptOut is set", basename(&file_path));
+ let _ = fs::remove_file(&file_path);
+ }
+ }
+ }
+ }
+ Ok(())
+}
+
+/// Processes crash telemetry info for a given dump file.
+///
+/// - Detects if the file is a tarball and logs accordingly.
+/// - Extracts container crash info if present in the filename.
+/// - Sends T2 notifications for container/app/process info.
+/// - Calls `get_crashed_log_file` for further log mapping.
+///
+/// # Arguments
+/// * `file_path` - Path to the crash dump file (as &str).
+/// * `is_t2_enabled` - Whether T2 telemetry is enabled (for sending notifications)
+///
+/// # Side Effects
+/// - May send multiple T2 telemetry notifications
+/// - Logs crash information
+/// - Calls `get_crashed_log_file` which may write to log files
+pub fn process_crash_t2_info(file_path: &str, is_t2_enabled: bool) {
+ println!("process_crash_t2_info: Processing the crash telemetry info");
+ let file = Path::new(file_path);
+ let mut file_name_str = file_path.to_string();
+ let container_delimiter = "<#=#>";
+
+ // Check if file is a tarball
+ if file.extension().and_then(|e| e.to_str()) == Some("tgz") {
+ println!("process_crash_t2_info: The File is already a tarball, this might be a retry or crash during shutdown");
+ if let Some(pos) = file_name_str.find("_mod_") {
+ file_name_str = file_name_str.split_off(pos + "_mod_".len());
+ }
+ println!("process_crash_t2_info: Original Filename: {}", basename(file));
+ println!("process_crash_t2_info: Removing the meta information New Filename: {}", basename(&file_name_str));
+ println!("process_crash_t2_info: This could be a retry or crash from previous boot; the appname can be truncated");
+ t2_count_notify("SYS_INFO_TGZDUMP", Some("1"));
+ }
+
+ // Check for container delimiter in file name
+ if file_name_str.contains(container_delimiter) {
+ let parts: Vec<&str> = file_name_str.split(container_delimiter).collect();
+ println!("process_crash_t2_info: From the file name crashed process is a container");
+
+ if parts.len() >= 2 {
+ let container_name = parts[0];
+ let container_status = if parts.len() > 2 { parts[1] } else { "unknown" };
+ let app_name = container_name.split('_').nth(1).unwrap_or(container_name);
+ let process_name = container_name.split('_').next().unwrap_or(container_name);
+
+ t2_count_notify("SYS_INFO_CrashedContainer", Some("1"));
+
+ println!("process_crash_t2_info: Container crash info Basic: {}, {}", app_name, process_name);
+ println!("process_crash_t2_info: Container crash info Advanced: {}, {}", container_name, container_status);
+ println!("process_crash_t2_info: NEW Appname, Process_Crashed, Status = {}, {}, {}", app_name, process_name, container_status);
+
+ t2_val_notify("APP_ERROR_Crashed_split", &[app_name, process_name, container_status]);
+ t2_val_notify("APP_ERROR_Crashed_accum", &[app_name, process_name, container_status]);
+
+ println!("process_crash_t2_info: NEW Processname, App Name, AppState = {}, {}, {}", process_name, app_name, container_status);
+ println!("process_crash_t2_info: ContainerName, ContainerStatus = {}, {}", container_name, container_status);
+ t2_val_notify("APP_ERROR_CrashInfo_accum", &[container_name, container_status]);
+ }
+ }
+ let _ = get_crashed_log_file(&file_name_str, is_t2_enabled);
+
+}
+
+/// Renames a tarball to mark it as crashlooped and (optionally) uploads it to the crash portal.
+///
+/// # Arguments
+/// * `tgz_file` - Path to the tarball file.
+///
+/// # Side Effects
+/// - Renames the file to `.crashloop.dmp.tgz`.
+/// - Logs the rename operation and any errors.
+pub fn mark_as_crash_loop_and_upload(tgz_file: &str) { // portal_url: &str, crash_portal_path: &str) {
+ let tgz_path = Path::new(tgz_file);
+ let new_tgz_name = tgz_path.with_extension("crashloop.dmp.tgz");
+ println!("mark_as_crash_loop_and_upload: Renaming {} to {}", tgz_path.display(), new_tgz_name.display());
+ if let Err(e) = safe_rename(tgz_path, &new_tgz_name) {
+ println!("mark_as_crash_loop_and_upload: Failed to rename crashloop tarball: {}", e);
+ }
+}
+
+/// Finds the oldest file in a directory.
+///
+/// Scans a directory for files, sorts them by modification time, and returns the oldest one.
+/// This is typically used for retention policies and cleanup of old dumps.
+///
+/// # Arguments
+/// * `dir` - Directory path to search.
+///
+/// # Returns
+/// * `Ok(Some(path))` with the oldest file, or `Ok(None)` if no files found or directory is empty.
+/// * `Err` if there was an error reading the directory or file metadata.
+pub fn find_oldest_dump(dir: &str) -> io::Result