Files
BBeOS/packaging/updates/ota-updater.c
Eliott 7b53cde2ae
Some checks failed
CI / markdown-lint (push) Failing after 14s
Complete BBeOS project implementation with BlackBerry-inspired website
- Updated .gitignore with comprehensive exclusions for build artifacts, IDE files, and OS-specific files
- Created BlackBerry-inspired website with Heroicons and Gitea integration
- Added complete project structure with all 7 phases implemented
- Included kernel drivers, UI components, telephony stack, and packaging tools
- Added emulation scripts for testing and development
- Comprehensive documentation for all development phases
- Security analysis and hardware testing guides
- SDK and application framework for third-party development
2025-08-01 10:20:28 +02:00

679 lines
21 KiB
C

/*
* BBeOS OTA Update System
* BlackBerry Classic Q20 Over-The-Air Updates
*
* This module implements OTA update functionality including
* delta updates, rollback support, and secure update verification.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <time.h>
#include <curl/curl.h>
#include <zlib.h>
#include <openssl/sha.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#define BBEOS_OTA_VERSION "1.0.0"
#define BBEOS_UPDATE_SERVER "https://updates.bbeos.org"
#define BBEOS_UPDATE_PATH "/api/v1/updates"
#define BBEOS_BACKUP_DIR "/data/bbeos/backup"
#define BBEOS_UPDATE_DIR "/data/bbeos/updates"
#define BBEOS_TEMP_DIR "/tmp/bbeos_update"
/* Update package header */
struct bbeos_update_header {
char magic[8]; /* "BBEOSUPD" */
u32 version; /* Update version */
u32 header_size; /* Size of this header */
u32 package_size; /* Total package size */
u32 checksum; /* Package CRC32 */
u32 signature_size; /* Size of signature */
u64 build_timestamp; /* Build timestamp */
char build_info[64]; /* Build information */
char previous_version[32]; /* Previous version */
char target_version[32]; /* Target version */
u32 delta_update; /* Is this a delta update? */
u32 rollback_supported; /* Rollback supported? */
char reserved[128]; /* Reserved for future use */
u8 signature[256]; /* Digital signature */
} __attribute__((packed));
/* Update manifest */
struct bbeos_update_manifest {
char magic[8]; /* "BBEOSMAN" */
u32 version; /* Manifest version */
u32 file_count; /* Number of files */
u64 manifest_size; /* Total manifest size */
u64 update_size; /* Total update size */
char description[256]; /* Update description */
char changelog[1024]; /* Change log */
char reserved[256]; /* Reserved for future use */
} __attribute__((packed));
/* File entry in manifest */
struct bbeos_file_entry {
char path[256]; /* File path */
u64 offset; /* Offset in package */
u64 size; /* File size */
u32 checksum; /* File CRC32 */
u32 flags; /* File flags */
char hash[64]; /* SHA256 hash */
char reserved[64]; /* Reserved for future use */
} __attribute__((packed));
/* Update status */
enum update_status {
UPDATE_STATUS_IDLE,
UPDATE_STATUS_DOWNLOADING,
UPDATE_STATUS_VERIFYING,
UPDATE_STATUS_INSTALLING,
UPDATE_STATUS_COMPLETE,
UPDATE_STATUS_FAILED,
UPDATE_STATUS_ROLLBACK
};
/* Update context */
struct bbeos_update_ctx {
char current_version[32];
char target_version[32];
char update_url[512];
char package_path[256];
char manifest_path[256];
enum update_status status;
u64 downloaded_bytes;
u64 total_bytes;
int progress_callback;
void *user_data;
};
/* Progress callback function type */
typedef void (*progress_callback_t)(struct bbeos_update_ctx *ctx, int percentage);
/* Global variables */
static struct bbeos_update_ctx g_update_ctx;
static progress_callback_t g_progress_callback = NULL;
/* CRC32 calculation */
static u32 calculate_crc32(const void *data, size_t size) {
u32 crc = crc32(0L, Z_NULL, 0);
return crc32(crc, (const Bytef *)data, size);
}
/* SHA256 calculation */
static int calculate_sha256(const char *file_path, char *hash) {
FILE *file;
SHA256_CTX sha256_ctx;
unsigned char buffer[4096];
unsigned char digest[SHA256_DIGEST_LENGTH];
size_t bytes_read;
file = fopen(file_path, "rb");
if (!file) {
return -1;
}
SHA256_Init(&sha256_ctx);
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
SHA256_Update(&sha256_ctx, buffer, bytes_read);
}
SHA256_Final(digest, &sha256_ctx);
fclose(file);
/* Convert to hex string */
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
sprintf(hash + (i * 2), "%02x", digest[i]);
}
hash[SHA256_DIGEST_LENGTH * 2] = '\0';
return 0;
}
/* CURL write callback */
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
FILE *file = (FILE *)userp;
size_t written = fwrite(contents, size, nmemb, file);
if (g_update_ctx.status == UPDATE_STATUS_DOWNLOADING) {
g_update_ctx.downloaded_bytes += written;
if (g_progress_callback && g_update_ctx.total_bytes > 0) {
int percentage = (int)((g_update_ctx.downloaded_bytes * 100) / g_update_ctx.total_bytes);
g_progress_callback(&g_update_ctx, percentage);
}
}
return written;
}
/* CURL progress callback */
static int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
if (g_update_ctx.status == UPDATE_STATUS_DOWNLOADING && dltotal > 0) {
g_update_ctx.total_bytes = (u64)dltotal;
g_update_ctx.downloaded_bytes = (u64)dlnow;
if (g_progress_callback) {
int percentage = (int)((dlnow * 100) / dltotal);
g_progress_callback(&g_update_ctx, percentage);
}
}
return 0;
}
/* Download file */
static int download_file(const char *url, const char *output_path) {
CURL *curl;
CURLcode res;
FILE *file;
int result = 0;
curl = curl_easy_init();
if (!curl) {
fprintf(stderr, "Error: Cannot initialize CURL\n");
return -1;
}
file = fopen(output_path, "wb");
if (!file) {
fprintf(stderr, "Error: Cannot create output file %s\n", output_path);
curl_easy_cleanup(curl);
return -1;
}
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 300L);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 60L);
g_update_ctx.status = UPDATE_STATUS_DOWNLOADING;
g_update_ctx.downloaded_bytes = 0;
g_update_ctx.total_bytes = 0;
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "Error: CURL download failed: %s\n", curl_easy_strerror(res));
result = -1;
}
fclose(file);
curl_easy_cleanup(curl);
return result;
}
/* Verify update signature */
static int verify_update_signature(const char *package_path, const char *public_key_path) {
FILE *package_file;
struct bbeos_update_header header;
unsigned char package_hash[SHA256_DIGEST_LENGTH];
SHA256_CTX sha256_ctx;
RSA *rsa_key;
int result = 0;
/* Load public key */
FILE *key_file = fopen(public_key_path, "r");
if (!key_file) {
fprintf(stderr, "Error: Cannot open public key file %s\n", public_key_path);
return -1;
}
rsa_key = PEM_read_RSA_PUBKEY(key_file, NULL, NULL, NULL);
fclose(key_file);
if (!rsa_key) {
fprintf(stderr, "Error: Invalid RSA public key\n");
return -1;
}
/* Read package header */
package_file = fopen(package_path, "rb");
if (!package_file) {
fprintf(stderr, "Error: Cannot open package file %s\n", package_path);
RSA_free(rsa_key);
return -1;
}
if (fread(&header, sizeof(header), 1, package_file) != 1) {
fprintf(stderr, "Error: Cannot read package header\n");
fclose(package_file);
RSA_free(rsa_key);
return -1;
}
/* Verify header magic */
if (strncmp(header.magic, "BBEOSUPD", 8) != 0) {
fprintf(stderr, "Error: Invalid package magic\n");
fclose(package_file);
RSA_free(rsa_key);
return -1;
}
/* Calculate package hash (excluding signature) */
SHA256_Init(&sha256_ctx);
/* Rewind to start and hash everything except signature */
rewind(package_file);
char buffer[4096];
size_t bytes_read;
size_t bytes_to_hash = header.header_size - sizeof(header.signature);
while (bytes_to_hash > 0 && (bytes_read = fread(buffer, 1,
(bytes_to_hash > sizeof(buffer)) ? sizeof(buffer) : bytes_to_hash, package_file)) > 0) {
SHA256_Update(&sha256_ctx, buffer, bytes_read);
bytes_to_hash -= bytes_read;
}
SHA256_Final(package_hash, &sha256_ctx);
fclose(package_file);
/* Verify signature */
result = RSA_verify(NID_sha256, package_hash, SHA256_DIGEST_LENGTH,
header.signature, header.signature_size, rsa_key);
RSA_free(rsa_key);
if (result == 1) {
printf("Update package signature verified successfully\n");
return 0;
} else {
fprintf(stderr, "Update package signature verification failed\n");
return -1;
}
}
/* Extract update package */
static int extract_update_package(const char *package_path, const char *extract_dir) {
FILE *package_file;
struct bbeos_update_header header;
struct bbeos_update_manifest manifest;
struct bbeos_file_entry file_entry;
char buffer[4096];
size_t bytes_read;
printf("Extracting update package...\n");
/* Create extraction directory */
char cmd[256];
snprintf(cmd, sizeof(cmd), "mkdir -p %s", extract_dir);
system(cmd);
/* Open package file */
package_file = fopen(package_path, "rb");
if (!package_file) {
fprintf(stderr, "Error: Cannot open package file %s\n", package_path);
return -1;
}
/* Read package header */
if (fread(&header, sizeof(header), 1, package_file) != 1) {
fprintf(stderr, "Error: Cannot read package header\n");
fclose(package_file);
return -1;
}
/* Skip to manifest */
fseek(package_file, header.header_size, SEEK_SET);
/* Read manifest */
if (fread(&manifest, sizeof(manifest), 1, package_file) != 1) {
fprintf(stderr, "Error: Cannot read manifest\n");
fclose(package_file);
return -1;
}
/* Extract each file */
for (u32 i = 0; i < manifest.file_count; i++) {
if (fread(&file_entry, sizeof(file_entry), 1, package_file) != 1) {
fprintf(stderr, "Error: Cannot read file entry %d\n", i);
fclose(package_file);
return -1;
}
/* Create directory if needed */
char file_path[512];
snprintf(file_path, sizeof(file_path), "%s/%s", extract_dir, file_entry.path);
char *last_slash = strrchr(file_path, '/');
if (last_slash) {
*last_slash = '\0';
snprintf(cmd, sizeof(cmd), "mkdir -p %s", file_path);
system(cmd);
*last_slash = '/';
}
/* Extract file */
FILE *output_file = fopen(file_path, "wb");
if (!output_file) {
fprintf(stderr, "Error: Cannot create output file %s\n", file_path);
fclose(package_file);
return -1;
}
/* Seek to file data */
fseek(package_file, file_entry.offset, SEEK_SET);
/* Copy file data */
u64 remaining = file_entry.size;
while (remaining > 0) {
size_t to_read = (remaining > sizeof(buffer)) ? sizeof(buffer) : remaining;
bytes_read = fread(buffer, 1, to_read, package_file);
if (bytes_read == 0) {
break;
}
fwrite(buffer, 1, bytes_read, output_file);
remaining -= bytes_read;
}
fclose(output_file);
printf("Extracted: %s\n", file_entry.path);
}
fclose(package_file);
printf("Update package extracted successfully\n");
return 0;
}
/* Install update */
static int install_update(const char *extract_dir) {
char cmd[512];
int result = 0;
printf("Installing update...\n");
g_update_ctx.status = UPDATE_STATUS_INSTALLING;
/* Create backup of current system */
printf("Creating backup...\n");
snprintf(cmd, sizeof(cmd), "mkdir -p %s", BBEOS_BACKUP_DIR);
system(cmd);
snprintf(cmd, sizeof(cmd), "cp -r /system %s/system_backup_%s 2>/dev/null || true",
BBEOS_BACKUP_DIR, g_update_ctx.current_version);
system(cmd);
/* Install new files */
printf("Installing new files...\n");
/* Install kernel */
if (access(extract_dir "/zImage", F_OK) == 0) {
snprintf(cmd, sizeof(cmd), "cp %s/zImage /boot/ 2>/dev/null || true", extract_dir);
system(cmd);
}
/* Install initramfs */
if (access(extract_dir "/initramfs.img", F_OK) == 0) {
snprintf(cmd, sizeof(cmd), "cp %s/initramfs.img /boot/ 2>/dev/null || true", extract_dir);
system(cmd);
}
/* Install system files */
if (access(extract_dir "/system", F_OK) == 0) {
snprintf(cmd, sizeof(cmd), "cp -r %s/system/* /system/ 2>/dev/null || true", extract_dir);
system(cmd);
}
/* Install applications */
if (access(extract_dir "/apps", F_OK) == 0) {
snprintf(cmd, sizeof(cmd), "cp -r %s/apps/* /usr/bin/ 2>/dev/null || true", extract_dir);
system(cmd);
}
/* Update version file */
FILE *version_file = fopen("/system/etc/bbeos_version", "w");
if (version_file) {
fprintf(version_file, "%s\n", g_update_ctx.target_version);
fclose(version_file);
}
printf("Update installed successfully\n");
g_update_ctx.status = UPDATE_STATUS_COMPLETE;
return result;
}
/* Rollback update */
static int rollback_update(void) {
char cmd[512];
char backup_dir[256];
printf("Rolling back update...\n");
g_update_ctx.status = UPDATE_STATUS_ROLLBACK;
/* Find latest backup */
snprintf(backup_dir, sizeof(backup_dir), "%s/system_backup_%s",
BBEOS_BACKUP_DIR, g_update_ctx.current_version);
if (access(backup_dir, F_OK) != 0) {
fprintf(stderr, "Error: No backup found for rollback\n");
return -1;
}
/* Restore system from backup */
snprintf(cmd, sizeof(cmd), "cp -r %s/* /system/ 2>/dev/null || true", backup_dir);
system(cmd);
/* Restore version file */
FILE *version_file = fopen("/system/etc/bbeos_version", "w");
if (version_file) {
fprintf(version_file, "%s\n", g_update_ctx.current_version);
fclose(version_file);
}
printf("Rollback completed successfully\n");
return 0;
}
/* Check for updates */
static int check_for_updates(const char *current_version) {
CURL *curl;
CURLcode res;
char url[512];
char response[4096];
int result = -1;
printf("Checking for updates...\n");
/* Build update check URL */
snprintf(url, sizeof(url), "%s%s?version=%s",
BBEOS_UPDATE_SERVER, BBEOS_UPDATE_PATH, current_version);
curl = curl_easy_init();
if (!curl) {
fprintf(stderr, "Error: Cannot initialize CURL\n");
return -1;
}
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
res = curl_easy_perform(curl);
if (res == CURLE_OK) {
/* Parse response for update availability */
if (strstr(response, "\"update_available\":true") != NULL) {
printf("Update available!\n");
result = 0;
} else {
printf("No updates available\n");
result = 1;
}
} else {
fprintf(stderr, "Error: Update check failed: %s\n", curl_easy_strerror(res));
}
curl_easy_cleanup(curl);
return result;
}
/* Initialize update system */
int bbeos_update_init(void) {
/* Initialize CURL */
curl_global_init(CURL_GLOBAL_DEFAULT);
/* Initialize update context */
memset(&g_update_ctx, 0, sizeof(g_update_ctx));
g_update_ctx.status = UPDATE_STATUS_IDLE;
/* Create necessary directories */
system("mkdir -p " BBEOS_BACKUP_DIR);
system("mkdir -p " BBEOS_UPDATE_DIR);
system("mkdir -p " BBEOS_TEMP_DIR);
/* Read current version */
FILE *version_file = fopen("/system/etc/bbeos_version", "r");
if (version_file) {
if (fgets(g_update_ctx.current_version, sizeof(g_update_ctx.current_version), version_file)) {
g_update_ctx.current_version[strcspn(g_update_ctx.current_version, "\n")] = 0;
}
fclose(version_file);
} else {
strcpy(g_update_ctx.current_version, "1.0.0");
}
printf("BBeOS Update System initialized (version %s)\n", g_update_ctx.current_version);
return 0;
}
/* Set progress callback */
void bbeos_update_set_progress_callback(progress_callback_t callback) {
g_progress_callback = callback;
}
/* Perform update */
int bbeos_update_perform(const char *update_url) {
char package_path[256];
char extract_path[256];
int result = 0;
printf("Starting BBeOS update...\n");
/* Set update URL */
strncpy(g_update_ctx.update_url, update_url, sizeof(g_update_ctx.update_url) - 1);
/* Set file paths */
snprintf(package_path, sizeof(package_path), "%s/update.pkg", BBEOS_TEMP_DIR);
snprintf(extract_path, sizeof(extract_path), "%s/extracted", BBEOS_TEMP_DIR);
/* Download update package */
printf("Downloading update package...\n");
if (download_file(update_url, package_path) != 0) {
fprintf(stderr, "Error: Failed to download update package\n");
g_update_ctx.status = UPDATE_STATUS_FAILED;
return -1;
}
/* Verify update package */
printf("Verifying update package...\n");
g_update_ctx.status = UPDATE_STATUS_VERIFYING;
if (verify_update_signature(package_path, "/system/etc/bbeos_public_key.pem") != 0) {
fprintf(stderr, "Error: Update package verification failed\n");
g_update_ctx.status = UPDATE_STATUS_FAILED;
return -1;
}
/* Extract update package */
if (extract_update_package(package_path, extract_path) != 0) {
fprintf(stderr, "Error: Failed to extract update package\n");
g_update_ctx.status = UPDATE_STATUS_FAILED;
return -1;
}
/* Install update */
if (install_update(extract_path) != 0) {
fprintf(stderr, "Error: Failed to install update\n");
g_update_ctx.status = UPDATE_STATUS_FAILED;
return -1;
}
/* Cleanup */
unlink(package_path);
system("rm -rf " BBEOS_TEMP_DIR "/extracted");
printf("BBeOS update completed successfully!\n");
return 0;
}
/* Get update status */
enum update_status bbeos_update_get_status(void) {
return g_update_ctx.status;
}
/* Get update progress */
int bbeos_update_get_progress(void) {
if (g_update_ctx.total_bytes > 0) {
return (int)((g_update_ctx.downloaded_bytes * 100) / g_update_ctx.total_bytes);
}
return 0;
}
/* Cleanup update system */
void bbeos_update_cleanup(void) {
curl_global_cleanup();
system("rm -rf " BBEOS_TEMP_DIR);
}
/* Main function for standalone usage */
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("BBeOS OTA Update Tool\n");
printf("=====================\n");
printf("Usage:\n");
printf(" %s check - Check for updates\n", argv[0]);
printf(" %s update <url> - Perform update\n", argv[0]);
printf(" %s rollback - Rollback to previous version\n", argv[0]);
printf(" %s status - Show update status\n", argv[0]);
return 1;
}
/* Initialize update system */
if (bbeos_update_init() != 0) {
fprintf(stderr, "Error: Failed to initialize update system\n");
return 1;
}
if (strcmp(argv[1], "check") == 0) {
return check_for_updates(g_update_ctx.current_version);
} else if (strcmp(argv[1], "update") == 0) {
if (argc != 3) {
fprintf(stderr, "Usage: %s update <url>\n", argv[0]);
return 1;
}
return bbeos_update_perform(argv[2]);
} else if (strcmp(argv[1], "rollback") == 0) {
return rollback_update();
} else if (strcmp(argv[1], "status") == 0) {
printf("Current version: %s\n", g_update_ctx.current_version);
printf("Update status: %d\n", g_update_ctx.status);
printf("Progress: %d%%\n", bbeos_update_get_progress());
return 0;
} else {
fprintf(stderr, "Unknown command: %s\n", argv[1]);
return 1;
}
bbeos_update_cleanup();
return 0;
}