#include "package.h" #include "index.h" #include #include #include #include #include #include #include #include #define PKG_HTTP_MAX_HOST 128 #define PKG_HTTP_MAX_PATH 256 #define PKG_HTTP_MAX_HEADER 4096 #define PKG_HTTP_RECV_BUF 1536 #define PKG_HTTP_CHUNK_BUF 4096 #define PKG_HTTP_IDLE_RECV_LIMIT 1500 #define PKG_DOWNLOAD_DEFAULT_MAX (8u * 1024u * 1024u) #define PKG_INSTALL_PATH_CAP 256 #define PKG_TEMP_PATH_CAP 320 #define PKG_IO_CHUNK 1024 #define PKG_LOCAL_CACHE_ROOT "/cache" #define PKG_LOCAL_PACKAGE_CACHE_DIR "/cache/pkg" #define PKG_LOCAL_BASE_ARCHIVE "/cache/base.pkg" #define PKG_INSTALLER_MEDIA_PACKAGE_CACHE_DIR "RAM:/installer/pkg" #define PKG_EYNFS_MAX_NAME_CHARS 31 #define PKG_SHA256_BLOCK_SIZE 64 #define PKG_SHA256_DIGEST_SIZE 32 typedef struct { char host[PKG_HTTP_MAX_HOST]; char path[PKG_HTTP_MAX_PATH]; uint16_t port; } pkg_http_url_t; typedef int (*pkg_body_writer_fn)(const uint8_t* data, size_t len, void* ctx); typedef struct { uint32_t state[8]; uint64_t total_len; uint8_t buffer[PKG_SHA256_BLOCK_SIZE]; size_t buffer_len; } pkg_sha256_ctx_t; int install_embedded_extract_main(int argc, char** argv); static int g_package_confirm_mode = PACKAGE_CONFIRM_PROMPT; static int pkg_path_exists(const char* path); static const uint32_t pkg_sha256_k[64] = { 0x428a2f98u, 0x71374491u, 0xb5c0fbcfu, 0xe9b5dba5u, 0x3956c25bu, 0x59f111f1u, 0x923f82a4u, 0xab1c5ed5u, 0xd807aa98u, 0x12835b01u, 0x243185beu, 0x550c7dc3u, 0x72be5d74u, 0x80deb1feu, 0x9bdc06a7u, 0xc19bf174u, 0xe49b69c1u, 0xefbe4786u, 0x0fc19dc6u, 0x240ca1ccu, 0x2de92c6fu, 0x4a7484aau, 0x5cb0a9dcu, 0x76f988dau, 0x983e5152u, 0xa831c66du, 0xb00327c8u, 0xbf597fc7u, 0xc6e00bf3u, 0xd5a79147u, 0x06ca6351u, 0x14292967u, 0x27b70a85u, 0x2e1b2138u, 0x4d2c6dfcu, 0x53380d13u, 0x650a7354u, 0x766a0abbu, 0x81c2c92eu, 0x92722c85u, 0xa2bfe8a1u, 0xa81a664bu, 0xc24b8b70u, 0xc76c51a3u, 0xd192e819u, 0xd6990624u, 0xf40e3585u, 0x106aa070u, 0x19a4c116u, 0x1e376c08u, 0x2748774cu, 0x34b0bcb5u, 0x391c0cb3u, 0x4ed8aa4au, 0x5b9cca4fu, 0x682e6ff3u, 0x748f82eeu, 0x78a5636fu, 0x84c87814u, 0x8cc70208u, 0x90befffau, 0xa4506cebu, 0xbef9a3f7u, 0xc67178f2u, }; static int pkg_ascii_lower(int ch) { if (ch >= 'A' && ch <= 'Z') return ch - 'A' + 'a'; return ch; } int package_set_confirm_mode(int mode) { int prev = g_package_confirm_mode; if (mode == PACKAGE_CONFIRM_AUTO_ACCEPT) { g_package_confirm_mode = PACKAGE_CONFIRM_AUTO_ACCEPT; } else { g_package_confirm_mode = PACKAGE_CONFIRM_PROMPT; } return prev; } static int pkg_prompt_confirm(const char* warning) { if (!warning || !warning[0]) return 0; if (g_package_confirm_mode == PACKAGE_CONFIRM_AUTO_ACCEPT) { printf("install: warning: %s (auto-accepted)\n", warning); return 1; } printf("install: warning: %s\n", warning); printf("install: continue? [y/N]: "); fflush(stdout); char answer[8]; int used = 0; while (used + 1 < (int)sizeof(answer)) { char c = 0; ssize_t n = read(0, &c, 1); if (n <= 0) break; if (c == '\r' || c == '\n') break; answer[used++] = c; } answer[used] = '\0'; return answer[0] == 'y' || answer[0] == 'Y'; } static int pkg_path_is_directory(const char* path) { if (!path || !path[0]) return 0; int fd = open(path, O_RDONLY, 0); if (fd < 0) return 0; eyn_dirent_t entries[1]; int rc = getdents(fd, entries, sizeof(entries)); close(fd); return rc >= 0; } static int pkg_is_first_level_dir(const char* path) { if (!path || path[0] != '/') return 0; if (path[1] == '\0') return 0; size_t i = 1; while (path[i] && path[i] != '/') i++; if (path[i] == '\0') return 1; return path[i] == '/' && path[i + 1] == '\0'; } static int pkg_install_name_is_valid(const char* name) { if (!name || !name[0]) return 0; for (size_t i = 0; name[i]; i++) { if (name[i] == '/' || name[i] == '\\' || name[i] == ':') return 0; } return 1; } static int pkg_install_dir_is_valid(const char* path) { if (!path || path[0] != '/') return 0; if (path[1] == '\0') return 0; size_t seg_len = 0; for (size_t i = 1; path[i]; i++) { if (path[i] == '\\' || path[i] == ':') return 0; if (path[i] == '/') { if (seg_len == 0) return 0; if (seg_len == 1 && path[i - 1] == '.') return 0; if (seg_len == 2 && path[i - 1] == '.' && path[i - 2] == '.') return 0; seg_len = 0; continue; } seg_len++; } if (seg_len == 1 && path[strlen(path) - 1] == '.') return 0; if (seg_len == 2 && path[strlen(path) - 1] == '.' && path[strlen(path) - 2] == '.') return 0; return 1; } static int pkg_mkdir_recursive(const char* path) { if (!path || !path[0]) return -1; char work[PKG_INSTALL_PATH_CAP]; int needed = snprintf(work, sizeof(work), "%s", path); if (needed <= 0 || needed >= (int)sizeof(work)) return -1; size_t len = strlen(work); while (len > 1 && work[len - 1] == '/') { work[len - 1] = '\0'; len--; } for (size_t i = 1; work[i]; i++) { if (work[i] != '/') continue; work[i] = '\0'; if (mkdir(work, 0) != 0 && !pkg_path_is_directory(work)) { work[i] = '/'; return -1; } work[i] = '/'; } if (mkdir(work, 0) != 0 && !pkg_path_is_directory(work)) return -1; return 0; } static int pkg_parse_ipv4_str(const char* s, uint8_t out[4]) { if (!s || !out) return -1; for (int part = 0; part < 4; part++) { if (*s < '0' || *s > '9') return -1; int v = 0; while (*s >= '0' && *s <= '9') { v = (v * 10) + (*s - '0'); if (v > 255) return -1; s++; } out[part] = (uint8_t)v; if (part != 3) { if (*s != '.') return -1; s++; } } return (*s == '\0') ? 0 : -1; } static int pkg_parse_http_url(const char* url, pkg_http_url_t* out) { if (!url || !out) return -1; const char* prefix = "http://"; size_t prefix_len = strlen(prefix); if (strncmp(url, prefix, prefix_len) != 0) return -1; const char* p = url + prefix_len; const char* host_start = p; while (*p && *p != '/' && *p != ':') p++; size_t host_len = (size_t)(p - host_start); if (host_len == 0 || host_len >= sizeof(out->host)) return -1; memcpy(out->host, host_start, host_len); out->host[host_len] = '\0'; out->port = 80; if (*p == ':') { p++; int port = 0; while (*p >= '0' && *p <= '9') { port = (port * 10) + (*p - '0'); if (port > 65535) return -1; p++; } if (port == 0) return -1; out->port = (uint16_t)port; } if (*p == '\0') { strncpy(out->path, "/", sizeof(out->path) - 1); out->path[sizeof(out->path) - 1] = '\0'; return 0; } if (*p != '/') return -1; size_t path_len = strlen(p); if (path_len >= sizeof(out->path)) return -1; memcpy(out->path, p, path_len + 1); return 0; } static int pkg_header_key_match(const char* line, const char* key) { while (*line && *key) { if (pkg_ascii_lower(*line) != pkg_ascii_lower(*key)) return 0; line++; key++; } return (*key == '\0'); } static int pkg_header_get_value(const char* headers, const char* key, char* out, size_t out_cap) { if (!headers || !key || !out || out_cap == 0) return -1; const char* line = headers; while (*line) { const char* line_end = strstr(line, "\r\n"); if (!line_end) break; if (line_end == line) break; if (pkg_header_key_match(line, key)) { const char* p = line + strlen(key); if (*p != ':') { line = line_end + 2; continue; } p++; while (*p == ' ' || *p == '\t') p++; size_t len = (size_t)(line_end - p); if (len >= out_cap) len = out_cap - 1; memcpy(out, p, len); out[len] = '\0'; return 0; } line = line_end + 2; } return -1; } static int pkg_parse_status_code(const char* headers) { if (!headers) return -1; const char* sp = strchr(headers, ' '); if (!sp) return -1; sp++; int code = 0; while (*sp >= '0' && *sp <= '9') { code = (code * 10) + (*sp - '0'); sp++; } return code; } static int pkg_parse_hex_size(const char* s, size_t len, size_t* out) { if (!s || !out) return -1; size_t v = 0; int any = 0; for (size_t i = 0; i < len; i++) { char c = s[i]; int d = -1; if (c >= '0' && c <= '9') d = c - '0'; else if (c >= 'a' && c <= 'f') d = 10 + (c - 'a'); else if (c >= 'A' && c <= 'F') d = 10 + (c - 'A'); else if (c == ';' || c == ' ' || c == '\t') break; else return -1; any = 1; v = (v << 4) | (size_t)d; } if (!any) return -1; *out = v; return 0; } static int pkg_string_contains_ci(const char* haystack, const char* needle) { if (!haystack || !needle || !needle[0]) return 0; size_t needle_len = strlen(needle); size_t hay_len = strlen(haystack); if (needle_len > hay_len) return 0; for (size_t i = 0; i + needle_len <= hay_len; i++) { size_t j = 0; while (j < needle_len) { if (pkg_ascii_lower(haystack[i + j]) != pkg_ascii_lower(needle[j])) break; j++; } if (j == needle_len) return 1; } return 0; } static int pkg_tcp_send_all(const void* buf, size_t len) { const uint8_t* p = (const uint8_t*)buf; size_t sent = 0; while (sent < len) { size_t chunk = len - sent; if (chunk > 512) chunk = 512; int rc = eyn_sys_net_tcp_send(p + sent, (uint32_t)chunk); if (rc < 0) return -1; sent += chunk; } return 0; } static int pkg_http_get_stream(const char* url, pkg_body_writer_fn writer, void* writer_ctx, size_t* out_bytes) { if (!url || !writer || !out_bytes) return -1; pkg_http_url_t parts; if (pkg_parse_http_url(url, &parts) != 0) { printf("install: invalid URL: %s\n", url); return -1; } uint8_t dst_ip[4]; if (pkg_parse_ipv4_str(parts.host, dst_ip) != 0) { if (eyn_sys_net_dns_resolve(parts.host, dst_ip) != 0) { printf("install: DNS failed for %s\n", parts.host); return -1; } } if (eyn_sys_net_tcp_connect(dst_ip, parts.port, 0) != 0) { printf("install: TCP connect failed for %s:%u\n", parts.host, (unsigned)parts.port); return -1; } char req[512]; int req_len = snprintf(req, sizeof(req), "GET %s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: EYN-OS/install\r\n" "Connection: close\r\n\r\n", parts.path, parts.host); if (req_len <= 0 || req_len >= (int)sizeof(req)) { (void)eyn_sys_net_tcp_close(); puts("install: HTTP request too large"); return -1; } if (pkg_tcp_send_all(req, (size_t)req_len) != 0) { (void)eyn_sys_net_tcp_close(); puts("install: failed to send request"); return -1; } char header[PKG_HTTP_MAX_HEADER]; size_t header_len = 0; int header_done = 0; int status = 0; int chunked = 0; long content_length = -1; uint8_t chunk_buf[PKG_HTTP_CHUNK_BUF]; size_t chunk_len = 0; size_t chunk_need = 0; int chunk_have_size = 0; int chunk_done = 0; size_t total_written = 0; int idle_recv_polls = 0; for (;;) { uint8_t rx_buf[PKG_HTTP_RECV_BUF]; int rc = eyn_sys_net_tcp_recv(rx_buf, sizeof(rx_buf)); if (rc == -2) break; if (rc < 0) { puts("install: HTTP receive failed"); (void)eyn_sys_net_tcp_close(); return -1; } if (rc == 0) { idle_recv_polls++; if (idle_recv_polls >= PKG_HTTP_IDLE_RECV_LIMIT) { puts("install: HTTP receive timeout"); (void)eyn_sys_net_tcp_close(); return -1; } usleep(10000); continue; } idle_recv_polls = 0; if (!header_done) { if (header_len + (size_t)rc >= sizeof(header)) { puts("install: response headers too large"); (void)eyn_sys_net_tcp_close(); return -1; } memcpy(header + header_len, rx_buf, (size_t)rc); header_len += (size_t)rc; header[header_len] = '\0'; char* marker = strstr(header, "\r\n\r\n"); if (!marker) continue; size_t header_end = (size_t)(marker - header) + 4; *marker = '\0'; header_done = 1; status = pkg_parse_status_code(header); if (status != 200 && status != 206) { printf("install: HTTP status %d for %s\n", status, url); (void)eyn_sys_net_tcp_close(); return -1; } char transfer_encoding[64]; transfer_encoding[0] = '\0'; if (pkg_header_get_value(header, "Transfer-Encoding", transfer_encoding, sizeof(transfer_encoding)) == 0) { if (pkg_string_contains_ci(transfer_encoding, "chunked")) { chunked = 1; } } char content_len_str[32]; content_len_str[0] = '\0'; if (pkg_header_get_value(header, "Content-Length", content_len_str, sizeof(content_len_str)) == 0) { content_length = strtol(content_len_str, NULL, 10); if (content_length < 0) content_length = -1; } size_t body_len = header_len - header_end; if (body_len > 0) { if (!chunked) { if (writer((const uint8_t*)(header + header_end), body_len, writer_ctx) != 0) { puts("install: body write failed"); (void)eyn_sys_net_tcp_close(); return -1; } total_written += body_len; } else { if (body_len > sizeof(chunk_buf)) { puts("install: chunk buffer overflow"); (void)eyn_sys_net_tcp_close(); return -1; } memcpy(chunk_buf, header + header_end, body_len); chunk_len = body_len; } } } else { if (!chunked) { if (writer(rx_buf, (size_t)rc, writer_ctx) != 0) { puts("install: body write failed"); (void)eyn_sys_net_tcp_close(); return -1; } total_written += (size_t)rc; } else { if (chunk_len + (size_t)rc > sizeof(chunk_buf)) { puts("install: chunk buffer overflow"); (void)eyn_sys_net_tcp_close(); return -1; } memcpy(chunk_buf + chunk_len, rx_buf, (size_t)rc); chunk_len += (size_t)rc; } } if (chunked) { while (!chunk_done) { if (!chunk_have_size) { size_t line_end = 0; while (line_end + 1 < chunk_len) { if (chunk_buf[line_end] == '\r' && chunk_buf[line_end + 1] == '\n') break; line_end++; } if (line_end + 1 >= chunk_len) break; if (pkg_parse_hex_size((const char*)chunk_buf, line_end, &chunk_need) != 0) { puts("install: invalid chunk size"); (void)eyn_sys_net_tcp_close(); return -1; } size_t consume = line_end + 2; memmove(chunk_buf, chunk_buf + consume, chunk_len - consume); chunk_len -= consume; chunk_have_size = 1; if (chunk_need == 0) { chunk_done = 1; break; } } if (chunk_have_size) { if (chunk_len < chunk_need + 2) break; if (writer(chunk_buf, chunk_need, writer_ctx) != 0) { puts("install: body write failed"); (void)eyn_sys_net_tcp_close(); return -1; } total_written += chunk_need; size_t consume = chunk_need + 2; memmove(chunk_buf, chunk_buf + consume, chunk_len - consume); chunk_len -= consume; chunk_have_size = 0; chunk_need = 0; } } } if (!chunked && content_length >= 0 && total_written >= (size_t)content_length) { break; } if (chunked && chunk_done) break; } (void)eyn_sys_net_tcp_close(); if (chunked && !chunk_done) { puts("install: incomplete chunked transfer"); return -1; } *out_bytes = total_written; return 0; } typedef struct { uint8_t* data; size_t len; size_t cap; size_t max_bytes; } pkg_mem_sink_t; static int pkg_mem_sink_write(const uint8_t* data, size_t len, void* ctx) { pkg_mem_sink_t* sink = (pkg_mem_sink_t*)ctx; if (!sink || !data) return -1; if (sink->len + len > sink->max_bytes) { puts("install: download exceeded max allowed size"); return -1; } size_t needed = sink->len + len; while (needed > sink->cap) { size_t next = sink->cap * 2; if (next < needed) next = needed; if (next > sink->max_bytes) next = sink->max_bytes; if (next < needed) return -1; uint8_t* bigger = (uint8_t*)realloc(sink->data, next + 1); if (!bigger) return -1; sink->data = bigger; sink->cap = next; } memcpy(sink->data + sink->len, data, len); sink->len += len; return 0; } int package_download_url_to_buffer(const char* url, uint8_t** out_data, size_t* out_len, size_t max_bytes) { if (!url || !out_data || !out_len) return -1; if (max_bytes == 0) { max_bytes = PKG_DOWNLOAD_DEFAULT_MAX; } pkg_mem_sink_t sink; sink.data = (uint8_t*)malloc(4097); sink.len = 0; sink.cap = 4096; sink.max_bytes = max_bytes; if (!sink.data) return -1; size_t downloaded = 0; if (pkg_http_get_stream(url, pkg_mem_sink_write, &sink, &downloaded) != 0) { free(sink.data); return -1; } sink.data[sink.len] = '\0'; *out_data = sink.data; *out_len = sink.len; (void)downloaded; return 0; } static uint32_t pkg_sha_rotr32(uint32_t x, uint32_t n) { return (x >> n) | (x << (32u - n)); } static uint32_t pkg_sha_ch(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (~x & z); } static uint32_t pkg_sha_maj(uint32_t x, uint32_t y, uint32_t z) { return (x & y) ^ (x & z) ^ (y & z); } static uint32_t pkg_sha_big_sigma0(uint32_t x) { return pkg_sha_rotr32(x, 2u) ^ pkg_sha_rotr32(x, 13u) ^ pkg_sha_rotr32(x, 22u); } static uint32_t pkg_sha_big_sigma1(uint32_t x) { return pkg_sha_rotr32(x, 6u) ^ pkg_sha_rotr32(x, 11u) ^ pkg_sha_rotr32(x, 25u); } static uint32_t pkg_sha_small_sigma0(uint32_t x) { return pkg_sha_rotr32(x, 7u) ^ pkg_sha_rotr32(x, 18u) ^ (x >> 3u); } static uint32_t pkg_sha_small_sigma1(uint32_t x) { return pkg_sha_rotr32(x, 17u) ^ pkg_sha_rotr32(x, 19u) ^ (x >> 10u); } static uint32_t pkg_sha_read_be32(const uint8_t* p) { return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | ((uint32_t)p[3]); } static void pkg_sha_write_be32(uint8_t* p, uint32_t v) { p[0] = (uint8_t)(v >> 24); p[1] = (uint8_t)(v >> 16); p[2] = (uint8_t)(v >> 8); p[3] = (uint8_t)v; } static void pkg_sha256_transform(pkg_sha256_ctx_t* ctx, const uint8_t block[PKG_SHA256_BLOCK_SIZE]) { uint32_t w[64]; for (int i = 0; i < 16; ++i) { w[i] = pkg_sha_read_be32(block + (size_t)i * 4u); } for (int i = 16; i < 64; ++i) { w[i] = pkg_sha_small_sigma1(w[i - 2]) + w[i - 7] + pkg_sha_small_sigma0(w[i - 15]) + w[i - 16]; } uint32_t a = ctx->state[0]; uint32_t b = ctx->state[1]; uint32_t c = ctx->state[2]; uint32_t d = ctx->state[3]; uint32_t e = ctx->state[4]; uint32_t f = ctx->state[5]; uint32_t g = ctx->state[6]; uint32_t h = ctx->state[7]; for (int i = 0; i < 64; ++i) { uint32_t t1 = h + pkg_sha_big_sigma1(e) + pkg_sha_ch(e, f, g) + pkg_sha256_k[i] + w[i]; uint32_t t2 = pkg_sha_big_sigma0(a) + pkg_sha_maj(a, b, c); h = g; g = f; f = e; e = d + t1; d = c; c = b; b = a; a = t1 + t2; } ctx->state[0] += a; ctx->state[1] += b; ctx->state[2] += c; ctx->state[3] += d; ctx->state[4] += e; ctx->state[5] += f; ctx->state[6] += g; ctx->state[7] += h; } static void pkg_sha256_init(pkg_sha256_ctx_t* ctx) { ctx->state[0] = 0x6a09e667u; ctx->state[1] = 0xbb67ae85u; ctx->state[2] = 0x3c6ef372u; ctx->state[3] = 0xa54ff53au; ctx->state[4] = 0x510e527fu; ctx->state[5] = 0x9b05688cu; ctx->state[6] = 0x1f83d9abu; ctx->state[7] = 0x5be0cd19u; ctx->total_len = 0; ctx->buffer_len = 0; } static void pkg_sha256_update(pkg_sha256_ctx_t* ctx, const uint8_t* data, size_t len) { if (!data || len == 0) return; ctx->total_len += (uint64_t)len; size_t off = 0; while (off < len) { size_t space = PKG_SHA256_BLOCK_SIZE - ctx->buffer_len; size_t take = len - off; if (take > space) take = space; memcpy(ctx->buffer + ctx->buffer_len, data + off, take); ctx->buffer_len += take; off += take; if (ctx->buffer_len == PKG_SHA256_BLOCK_SIZE) { pkg_sha256_transform(ctx, ctx->buffer); ctx->buffer_len = 0; } } } static void pkg_sha256_final(pkg_sha256_ctx_t* ctx, uint8_t out_digest[PKG_SHA256_DIGEST_SIZE]) { uint64_t bits = ctx->total_len * 8u; ctx->buffer[ctx->buffer_len++] = 0x80; if (ctx->buffer_len > 56) { while (ctx->buffer_len < PKG_SHA256_BLOCK_SIZE) { ctx->buffer[ctx->buffer_len++] = 0; } pkg_sha256_transform(ctx, ctx->buffer); ctx->buffer_len = 0; } while (ctx->buffer_len < 56) { ctx->buffer[ctx->buffer_len++] = 0; } for (int i = 7; i >= 0; --i) { ctx->buffer[ctx->buffer_len++] = (uint8_t)(bits >> (i * 8)); } pkg_sha256_transform(ctx, ctx->buffer); for (int i = 0; i < 8; ++i) { pkg_sha_write_be32(out_digest + (size_t)i * 4u, ctx->state[i]); } } static void pkg_sha256_hex(const uint8_t digest[PKG_SHA256_DIGEST_SIZE], char out_hex[MAX_SHA]) { static const char hex[] = "0123456789abcdef"; for (int i = 0; i < PKG_SHA256_DIGEST_SIZE; i++) { out_hex[(size_t)i * 2] = hex[(digest[i] >> 4) & 0x0f]; out_hex[(size_t)i * 2 + 1] = hex[digest[i] & 0x0f]; } out_hex[64] = '\0'; } static int pkg_sha256_match_ci(const char* expected, const char* got) { if (!expected || !got) return 0; if (strlen(expected) != 64 || strlen(got) != 64) return 0; for (int i = 0; i < 64; i++) { if (pkg_ascii_lower(expected[i]) != pkg_ascii_lower(got[i])) return 0; } return 1; } typedef struct { int stream_handle; pkg_sha256_ctx_t sha; size_t total_written; } pkg_install_sink_t; static int pkg_install_sink_write(const uint8_t* data, size_t len, void* ctx) { pkg_install_sink_t* sink = (pkg_install_sink_t*)ctx; if (!sink || !data) return -1; if (eynfs_stream_write(sink->stream_handle, data, len) != (ssize_t)len) { return -1; } pkg_sha256_update(&sink->sha, data, len); sink->total_written += len; return 0; } static int pkg_get_install_dir(const Package* pkg, char out_dir[PKG_INSTALL_PATH_CAP]) { if (!pkg || !out_dir) return -1; const char* raw = pkg->install_dir[0] ? pkg->install_dir : "/binaries"; int needed = snprintf(out_dir, PKG_INSTALL_PATH_CAP, "%s", raw); if (needed <= 0 || needed >= PKG_INSTALL_PATH_CAP) return -1; size_t len = strlen(out_dir); while (len > 1 && out_dir[len - 1] == '/') { out_dir[len - 1] = '\0'; len--; } if (!pkg_install_dir_is_valid(out_dir)) return -1; return 0; } static int pkg_get_install_name(const Package* pkg, char out_name[MAX_NAME]) { if (!pkg || !out_name) return -1; const char* raw = pkg->install_name[0] ? pkg->install_name : pkg->name; int needed = snprintf(out_name, MAX_NAME, "%s", raw); if (needed <= 0 || needed >= MAX_NAME) return -1; if (!pkg_install_name_is_valid(out_name)) return -1; return 0; } static int pkg_prepare_install_dir(const Package* pkg, const char* install_dir) { if (!pkg || !install_dir || !install_dir[0]) return -1; if (!pkg_is_first_level_dir(install_dir)) { char warning[200]; int needed = snprintf(warning, sizeof(warning), "target directory '%s' is not first-level", install_dir); if (needed <= 0 || needed >= (int)sizeof(warning)) return -1; if (!pkg_prompt_confirm(warning)) { puts("install: cancelled by user"); return -1; } } if (pkg_path_exists(install_dir) && !pkg_path_is_directory(install_dir)) { printf("install: target path is not a directory: %s\n", install_dir); return -1; } if (!pkg_path_is_directory(install_dir)) { char warning[220]; int needed = snprintf(warning, sizeof(warning), "target directory '%s' does not exist and will be created", install_dir); if (needed <= 0 || needed >= (int)sizeof(warning)) return -1; if (!pkg_prompt_confirm(warning)) { puts("install: cancelled by user"); return -1; } if (pkg_mkdir_recursive(install_dir) != 0) { printf("install: failed to create install directory: %s\n", install_dir); return -1; } } return 0; } static int pkg_build_install_path(const Package* pkg, char out_path[PKG_INSTALL_PATH_CAP]) { if (!pkg || !out_path) return -1; char install_dir[PKG_INSTALL_PATH_CAP]; char install_name[MAX_NAME]; if (pkg_get_install_dir(pkg, install_dir) != 0) return -1; if (pkg_get_install_name(pkg, install_name) != 0) return -1; int needed = snprintf(out_path, PKG_INSTALL_PATH_CAP, "%s/%s", install_dir, install_name); if (needed <= 0 || needed >= PKG_INSTALL_PATH_CAP) return -1; return 0; } static int pkg_records_equal(const Package* left, const Package* right) { if (!left || !right) return 0; if (strcmp(left->name, right->name) != 0) return 0; if (strcmp(left->version, right->version) != 0) return 0; if (strcmp(left->url, right->url) != 0) return 0; if (strcmp(left->sha256, right->sha256) != 0) return 0; if (strcmp(left->install_dir, right->install_dir) != 0) return 0; if (strcmp(left->install_name, right->install_name) != 0) return 0; return 1; } static int pkg_has_suffix_ci(const char* value, const char* suffix) { if (!value || !suffix) return 0; size_t value_len = strlen(value); size_t suffix_len = strlen(suffix); if (suffix_len > value_len) return 0; const char* at = value + value_len - suffix_len; for (size_t i = 0; i < suffix_len; i++) { if (pkg_ascii_lower(at[i]) != pkg_ascii_lower(suffix[i])) return 0; } return 1; } static int pkg_url_is_archive(const char* url) { if (!url) return 0; return pkg_has_suffix_ci(url, ".tar") || pkg_has_suffix_ci(url, ".tar.gz") || pkg_has_suffix_ci(url, ".tgz"); } static int pkg_path_exists(const char* path) { if (!path || !path[0]) return 0; int fd = open(path, O_RDONLY, 0); if (fd < 0) return 0; close(fd); return 1; } static int pkg_build_cache_archive_name(const Package* pkg, char out_name[PKG_INSTALL_PATH_CAP]) { if (!pkg || !out_name || !pkg->name[0]) return -1; int needed = snprintf(out_name, PKG_INSTALL_PATH_CAP, "%s.pkg", pkg->name); if (needed <= 0 || needed >= PKG_INSTALL_PATH_CAP) return -1; if (needed > PKG_EYNFS_MAX_NAME_CHARS) return -1; return 0; } static int pkg_build_cached_archive_path(const Package* pkg, char out_path[PKG_TEMP_PATH_CAP]) { if (!pkg || !out_path) return -1; char archive_name[PKG_INSTALL_PATH_CAP]; if (pkg_build_cache_archive_name(pkg, archive_name) != 0) { return -1; } int needed = snprintf(out_path, PKG_TEMP_PATH_CAP, "%s/%s", PKG_LOCAL_PACKAGE_CACHE_DIR, archive_name); if (needed <= 0 || needed >= PKG_TEMP_PATH_CAP) return -1; return 0; } static int pkg_ensure_dir_exists(const char* path) { if (!path || !path[0]) return -1; if (pkg_path_exists(path)) return 0; if (mkdir(path, 0) == 0) return 0; return pkg_path_exists(path) ? 0 : -1; } static int pkg_build_temp_archive_path(const char* name, char out_path[PKG_TEMP_PATH_CAP]) { if (!name || !name[0] || !out_path) return -1; int needed = snprintf(out_path, PKG_TEMP_PATH_CAP, "/tmp/install-%s.pkg", name); if (needed <= 0 || needed >= PKG_TEMP_PATH_CAP) return -1; return 0; } static int pkg_measure_file_size(const char* path, size_t* out_size) { if (!path || !out_size) return -1; int fd = open(path, O_RDONLY, 0); if (fd < 0) return -1; size_t total = 0; uint8_t buf[PKG_IO_CHUNK]; for (;;) { ssize_t rd = read(fd, buf, sizeof(buf)); if (rd < 0) { close(fd); return -1; } if (rd == 0) break; total += (size_t)rd; } close(fd); *out_size = total; return 0; } static int pkg_copy_file_to_path(const char* src_path, const char* dst_path) { if (!src_path || !dst_path) return -1; int src_fd = open(src_path, O_RDONLY, 0); if (src_fd < 0) return -1; int stream_handle = eynfs_stream_begin(dst_path); if (stream_handle < 0) { close(src_fd); return -1; } uint8_t buf[PKG_IO_CHUNK]; for (;;) { ssize_t rd = read(src_fd, buf, sizeof(buf)); if (rd < 0) { (void)close(src_fd); (void)eynfs_stream_end(stream_handle); (void)unlink(dst_path); return -1; } if (rd == 0) break; if (eynfs_stream_write(stream_handle, buf, (size_t)rd) != rd) { (void)close(src_fd); (void)eynfs_stream_end(stream_handle); (void)unlink(dst_path); return -1; } } (void)close(src_fd); if (eynfs_stream_end(stream_handle) != 0) { (void)unlink(dst_path); return -1; } return 0; } static int pkg_build_media_archive_path(const Package* pkg, char out_path[PKG_TEMP_PATH_CAP]) { if (!pkg || !out_path) return -1; char archive_name[PKG_INSTALL_PATH_CAP]; if (pkg_build_cache_archive_name(pkg, archive_name) != 0) { return -1; } int needed = snprintf(out_path, PKG_TEMP_PATH_CAP, "%s/%s", PKG_INSTALLER_MEDIA_PACKAGE_CACHE_DIR, archive_name); if (needed <= 0 || needed >= PKG_TEMP_PATH_CAP) return -1; return 0; } static int pkg_extract_archive_embedded(const char* archive_path, const char* dest_dir) { if (!archive_path || !dest_dir) return -1; char* embedded_argv[3]; embedded_argv[0] = (char*)"extract"; embedded_argv[1] = (char*)archive_path; embedded_argv[2] = (char*)dest_dir; int embedded_rc = install_embedded_extract_main(3, embedded_argv); if (embedded_rc != 0) { printf("install: embedded extract failed (status=%d)\n", embedded_rc); return -1; } return 0; } static int pkg_extract_archive(const char* archive_path, const char* dest_dir) { if (!archive_path || !dest_dir) return -1; #ifdef PKG_EXTRACT_EMBEDDED_ONLY return pkg_extract_archive_embedded(archive_path, dest_dir); #endif const char* argv_local[2]; argv_local[0] = archive_path; argv_local[1] = dest_dir; int pid = spawn("/binaries/extract", argv_local, 2); if (pid <= 0) { pid = spawn("extract", argv_local, 2); } if (pid <= 0) { return pkg_extract_archive_embedded(archive_path, dest_dir); } int status = 0; if (waitpid(pid, &status, 0) <= 0) { puts("install: failed to wait for extract command"); return -1; } if (status != 0) { printf("install: extract failed (status=%d)\n", status); return -1; } return 0; } static int pkg_download_verified_to_path(const Package* pkg, const char* out_path, size_t* out_bytes) { if (!pkg || !out_path) return -1; int stream_handle = eynfs_stream_begin(out_path); if (stream_handle < 0) { printf("install: failed to create %s\n", out_path); return -1; } pkg_install_sink_t sink; sink.stream_handle = stream_handle; sink.total_written = 0; pkg_sha256_init(&sink.sha); size_t downloaded = 0; if (pkg_http_get_stream(pkg->url, pkg_install_sink_write, &sink, &downloaded) != 0) { (void)eynfs_stream_end(stream_handle); (void)unlink(out_path); return -1; } if (eynfs_stream_end(stream_handle) != 0) { (void)unlink(out_path); return -1; } uint8_t digest[PKG_SHA256_DIGEST_SIZE]; char digest_hex[MAX_SHA]; pkg_sha256_final(&sink.sha, digest); pkg_sha256_hex(digest, digest_hex); if (!pkg_sha256_match_ci(pkg->sha256, digest_hex)) { printf("install: checksum mismatch for %s\n", pkg->name); printf("install: expected %s\n", pkg->sha256); printf("install: got %s\n", digest_hex); (void)unlink(out_path); return -1; } if (sink.total_written != downloaded) { printf("install: short write for %s\n", pkg->name); (void)unlink(out_path); return -1; } if (out_bytes) *out_bytes = sink.total_written; return 0; } static int pkg_sha256_file_hex(const char* path, char out_hex[MAX_SHA]) { if (!path || !out_hex) return -1; int fd = open(path, O_RDONLY, 0); if (fd < 0) return -1; pkg_sha256_ctx_t sha; pkg_sha256_init(&sha); uint8_t buf[PKG_IO_CHUNK]; for (;;) { ssize_t rd = read(fd, buf, sizeof(buf)); if (rd < 0) { close(fd); return -1; } if (rd == 0) break; pkg_sha256_update(&sha, buf, (size_t)rd); } close(fd); uint8_t digest[PKG_SHA256_DIGEST_SIZE]; pkg_sha256_final(&sha, digest); pkg_sha256_hex(digest, out_hex); return 0; } static int pkg_try_seed_local_cache_from_base_archive(void) { static int seed_attempted = 0; if (seed_attempted) return 0; seed_attempted = 1; if (!pkg_path_exists(PKG_LOCAL_BASE_ARCHIVE)) { return 0; } if (pkg_ensure_dir_exists(PKG_LOCAL_CACHE_ROOT) != 0 || pkg_ensure_dir_exists(PKG_LOCAL_PACKAGE_CACHE_DIR) != 0) { puts("install: failed to prepare local cache directories"); return 0; } puts("install: unpacking local base cache ..."); if (pkg_extract_archive(PKG_LOCAL_BASE_ARCHIVE, PKG_LOCAL_PACKAGE_CACHE_DIR) != 0) { puts("install: failed to unpack local base cache; falling back to per-package media cache"); return 0; } return 0; } static int pkg_try_seed_archive_from_installer_media(const Package* pkg, const char* cache_archive_path) { if (!pkg || !cache_archive_path) return -1; char media_archive_path[PKG_TEMP_PATH_CAP]; if (pkg_build_media_archive_path(pkg, media_archive_path) != 0) { return 1; } if (!pkg_path_exists(media_archive_path)) { return 1; } if (pkg_ensure_dir_exists(PKG_LOCAL_CACHE_ROOT) != 0 || pkg_ensure_dir_exists(PKG_LOCAL_PACKAGE_CACHE_DIR) != 0) { printf("install: failed to prepare cache for %s\n", pkg->name); return 1; } if (pkg_copy_file_to_path(media_archive_path, cache_archive_path) != 0) { printf("install: failed to seed %s from installer media\n", pkg->name); return 1; } printf("install: seeded cached archive for %s from installer media\n", pkg->name); return 0; } static int pkg_try_install_from_local_cache(const Package* pkg, const char* out_path, const char* install_dir, size_t* out_bytes) { if (!pkg || !out_path || !install_dir) return -1; int installer_media_pkg_cache_available = pkg_path_exists(PKG_INSTALLER_MEDIA_PACKAGE_CACHE_DIR); char cache_archive_path[PKG_TEMP_PATH_CAP]; if (pkg_build_cached_archive_path(pkg, cache_archive_path) != 0) { return 1; } if (!pkg_path_exists(cache_archive_path)) { (void)pkg_try_seed_archive_from_installer_media(pkg, cache_archive_path); } if (!pkg_path_exists(cache_archive_path)) { if (!installer_media_pkg_cache_available) { (void)pkg_try_seed_local_cache_from_base_archive(); } } if (!pkg_path_exists(cache_archive_path)) { return 1; } char digest_hex[MAX_SHA]; if (pkg_sha256_file_hex(cache_archive_path, digest_hex) != 0) { printf("install: failed to hash cached archive for %s\n", pkg->name); return 1; } if (!pkg_sha256_match_ci(pkg->sha256, digest_hex)) { printf("install: cached archive checksum mismatch for %s; using network\n", pkg->name); return 1; } if (pkg_extract_archive(cache_archive_path, install_dir) != 0) { printf("install: cached archive extraction failed for %s; using network\n", pkg->name); return 1; } if (!pkg_path_exists(out_path)) { printf("install: cached archive missing payload for %s; using network\n", pkg->name); return 1; } if (pkg_measure_file_size(out_path, out_bytes) != 0) { printf("install: failed to inspect cached payload for %s\n", pkg->name); return -1; } printf("install: using cached package for %s\n", pkg->name); return 0; } int install_package(const struct PackageIndex* index, const Package* pkg) { if (!index || !pkg || !pkg->name[0]) return -1; const Package* in_index = index_find_package((const PackageIndex*)index, pkg->name); if (!in_index) { printf("install: package '%s' not found in index\n", pkg->name); return -1; } if (!pkg_records_equal(in_index, pkg)) { printf("install: package metadata mismatch for %s\n", pkg->name); return -1; } if (strlen(pkg->sha256) != 64) { printf("install: invalid sha256 for %s\n", pkg->name); return -1; } char out_path[PKG_INSTALL_PATH_CAP]; char install_dir[PKG_INSTALL_PATH_CAP]; char install_name[MAX_NAME]; if (pkg_get_install_dir(pkg, install_dir) != 0 || pkg_get_install_name(pkg, install_name) != 0 || pkg_build_install_path(pkg, out_path) != 0) { printf("install: output path too long for %s\n", pkg->name); return -1; } if (pkg_prepare_install_dir(pkg, install_dir) != 0) { return -1; } size_t installed_bytes = 0; if (pkg_url_is_archive(pkg->url)) { int cache_rc = pkg_try_install_from_local_cache(pkg, out_path, install_dir, &installed_bytes); if (cache_rc == 0) { printf("install: installed %s@%s (%lu bytes)\n", pkg->name, pkg->version, (unsigned long)installed_bytes); return 0; } char archive_path[PKG_TEMP_PATH_CAP]; if (pkg_build_temp_archive_path(pkg->name, archive_path) != 0) { printf("install: temporary path too long for %s\n", pkg->name); return -1; } if (pkg_ensure_dir_exists("/tmp") != 0 || pkg_ensure_dir_exists(install_dir) != 0) { puts("install: failed to prepare temporary install directory"); return -1; } if (pkg_download_verified_to_path(pkg, archive_path, NULL) != 0) { printf("install: download failed for %s\n", pkg->name); return -1; } if (pkg_extract_archive(archive_path, install_dir) != 0) { (void)unlink(archive_path); printf("install: extraction failed for %s\n", pkg->name); return -1; } if (!pkg_path_exists(out_path)) { if (strcmp(install_name, pkg->name) != 0) { char legacy_path[PKG_INSTALL_PATH_CAP]; int legacy_needed = snprintf(legacy_path, PKG_INSTALL_PATH_CAP, "%s/%s", install_dir, pkg->name); if (legacy_needed > 0 && legacy_needed < PKG_INSTALL_PATH_CAP && pkg_path_exists(legacy_path) && pkg_copy_file_to_path(legacy_path, out_path) == 0) { (void)unlink(legacy_path); } } } if (!pkg_path_exists(out_path)) { (void)unlink(archive_path); printf("install: no extracted payload found for %s\n", pkg->name); printf("install: package archive did not provide %s/%s\n", install_dir, install_name); return -1; } if (pkg_measure_file_size(out_path, &installed_bytes) != 0) { (void)unlink(archive_path); printf("install: failed to inspect extracted payload for %s\n", pkg->name); return -1; } (void)unlink(archive_path); } else { if (pkg_download_verified_to_path(pkg, out_path, &installed_bytes) != 0) { printf("install: download failed for %s\n", pkg->name); return -1; } } printf("install: installed %s@%s (%lu bytes)\n", pkg->name, pkg->version, (unsigned long)installed_bytes); return 0; }