#include #include #include #include #include #include #include #include #include #include #include #include EYN_CMDMETA_V1("EYN-OS graphical installer.", "installer"); #define MAX_DRIVES 8 /* * LOW-MEM INVARIANT: Keep installer copy reads small. * * Why: RAM:/ media is read through VFS demand paths under tight memory. * Smaller reads reduce transient kernel buffer pressure and avoid wide * multi-block reads that are more failure-prone in installer mode. */ #define COPY_BUF_SZ 256 #define MAX_STATUS 128 #define INSTALL_CACHE_ROOT "/cache" #define INSTALL_CACHE_INDEX "/cache/index.json" #define INSTALL_CACHE_BASE_ARCHIVE "/cache/base.pkg" #define INSTALL_CACHE_MANIFEST "/cache/base.manifest" #define INSTALL_CACHE_PACKAGE_DIR "/cache/pkg" #define INSTALLER_MEDIA_INSTALL "RAM:/binaries/install" #define INSTALLER_MEDIA_EXTRACT "RAM:/binaries/extract" #define INSTALLER_MEDIA_INSTALLER "RAM:/binaries/installer" #define INSTALLER_MEDIA_INDEX "RAM:/installer/index.json" #define INSTALLER_MEDIA_BASE_ARCHIVE "RAM:/installer/base.pkg" #define INSTALLER_MEDIA_BASE_MANIFEST "RAM:/installer/base.manifest" #define INSTALLER_MEDIA_ETC_RESOLV "RAM:/etc/resolv.conf" #define INSTALLER_MEDIA_CONFIG_DIR "RAM:/config" #define INSTALLER_MEDIA_ICONS_DIR "RAM:/icons" #define INSTALLER_MEDIA_ICONS16_DIR "RAM:/icons16" #define INSTALLER_MEDIA_VIEW_DIR "RAM:/.view" #define INSTALLER_MEDIA_FONTS_DIR "RAM:/fonts" #define INSTALLER_MEDIA_CHIBICC "RAM:/programs/chibicc" #define INSTALLER_MAX_MANIFEST_PACKAGES 256 #define INSTALLER_MAX_PACKAGE_NAME 64 #define INSTALLER_MAX_FONT_FILES 64 #define INSTALLER_MAX_FONT_NAME 64 #define INSTALLER_OPTION_VISIBLE_ROWS 10 /* * ABI-INVARIANT: Raw on-disk LBA used for installer-written kernel payload. * * Why: GRUB core image embedded by installer boots via blocklist * `multiboot (hd0)+KERNEL_RAW_LBA,` to avoid filesystem module * dependencies during early boot. * Invariant: Must stay below first partition start (currently 2048). */ #define KERNEL_RAW_LBA 1024u typedef enum { STEP_WELCOME = 0, STEP_SELECT_DRIVE = 1, STEP_OPTIONS = 2, STEP_FORMAT = 3, STEP_COPY = 4, STEP_BOOTLOADER = 5, STEP_DONE = 6, STEP_ERROR = 7, } installer_step_t; typedef struct { int drive_count; int drives[MAX_DRIVES]; int selected_idx; int selected_drive; int options_loaded; int option_cursor; int option_scroll; int package_count; char packages[INSTALLER_MAX_MANIFEST_PACKAGES][INSTALLER_MAX_PACKAGE_NAME]; unsigned char package_selected[INSTALLER_MAX_MANIFEST_PACKAGES]; int font_count; char fonts[INSTALLER_MAX_FONT_FILES][INSTALLER_MAX_FONT_NAME]; unsigned char font_selected[INSTALLER_MAX_FONT_FILES]; int include_icons; int include_fonts; int include_chibicc; installer_step_t step; int running; int copied_files; int copied_dirs; int progress_permille; int progress_total; int progress_done; char status[MAX_STATUS]; char current_item[160]; char error[MAX_STATUS]; } installer_t; static int g_installer_gui_handle = -1; static int g_gui_draw_failures = 0; static void draw_ui(int h, installer_t* s); static int path_is_directory(const char* path); static int read_manifest_packages(const char* manifest_path, char out_pkgs[INSTALLER_MAX_MANIFEST_PACKAGES][INSTALLER_MAX_PACKAGE_NAME], int* out_count); static int gui_call_ok(int rc, const char* what, installer_t* s) { if (rc >= 0) return 1; g_gui_draw_failures++; if (g_gui_draw_failures <= 16 || (g_gui_draw_failures % 64) == 0) { int step = s ? (int)s->step : -1; printf("[installer] gui call failed: %s rc=%d step=%d failures=%d\n", what ? what : "?", rc, step, g_gui_draw_failures); } return 0; } static void installer_ui_pulse(installer_t* s) { if (!s) return; if (g_installer_gui_handle >= 0) { draw_ui(g_installer_gui_handle, s); } } static int clamp_permille(int v) { if (v < 0) return 0; if (v > 1000) return 1000; return v; } static void progress_reset(installer_t* s, int total) { if (!s) return; s->progress_total = (total > 0) ? total : 0; s->progress_done = 0; s->progress_permille = 0; s->current_item[0] = '\0'; installer_ui_pulse(s); } static void progress_step(installer_t* s, const char* item) { if (!s) return; if (item) { strncpy(s->current_item, item, sizeof(s->current_item) - 1); s->current_item[sizeof(s->current_item) - 1] = '\0'; } if (s->progress_total > 0) { s->progress_done++; if (s->progress_done > s->progress_total) s->progress_done = s->progress_total; s->progress_permille = clamp_permille((s->progress_done * 1000) / s->progress_total); } installer_ui_pulse(s); } static void progress_note_item(installer_t* s, const char* item) { if (!s) return; if (!item) item = ""; strncpy(s->current_item, item, sizeof(s->current_item) - 1); s->current_item[sizeof(s->current_item) - 1] = '\0'; installer_ui_pulse(s); } static void progress_set_fraction(installer_t* s, uint32_t entry_done, uint32_t entry_total) { if (!s) return; if (s->progress_total <= 0) return; if (entry_total == 0u) return; if (entry_done > entry_total) entry_done = entry_total; /* Keep in-flight entry progress below the next completed-entry boundary. */ uint32_t entry_permille = (entry_done * 1000u) / entry_total; if (entry_permille >= 1000u) entry_permille = 999u; int combined = (int)(((uint32_t)s->progress_done * 1000u + entry_permille) / (uint32_t)s->progress_total); combined = clamp_permille(combined); if (combined > s->progress_permille) { s->progress_permille = combined; } installer_ui_pulse(s); } static void status_set(installer_t* s, const char* msg) { if (!s) return; if (!msg) msg = ""; strncpy(s->status, msg, sizeof(s->status) - 1); s->status[sizeof(s->status) - 1] = '\0'; installer_ui_pulse(s); } static void error_set(installer_t* s, const char* msg) { if (!s) return; if (!msg) msg = "Unknown error"; strncpy(s->error, msg, sizeof(s->error) - 1); s->error[sizeof(s->error) - 1] = '\0'; printf("[installer] error: %s\n", s->error); s->step = STEP_ERROR; installer_ui_pulse(s); } static void error_set_path(installer_t* s, const char* prefix, const char* path) { if (!s) return; if (!prefix) prefix = "Error"; if (!path) path = "?"; snprintf(s->error, sizeof(s->error), "%s: %s", prefix, path); s->error[sizeof(s->error) - 1] = '\0'; printf("[installer] error: %s\n", s->error); s->step = STEP_ERROR; installer_ui_pulse(s); } static void error_set_path_code(installer_t* s, const char* prefix, const char* path, int code) { if (!s) return; if (!prefix) prefix = "Error"; if (!path) path = "?"; snprintf(s->error, sizeof(s->error), "%s: %s (rc=%d)", prefix, path, code); s->error[sizeof(s->error) - 1] = '\0'; printf("[installer] error: %s\n", s->error); s->step = STEP_ERROR; installer_ui_pulse(s); } static int copy_file_stream_with_ui(installer_t* s, const char* src_ram_path, const char* dst_abs_path) { int fd = open(src_ram_path, O_RDONLY, 0); if (fd < 0) return -10; uint32_t src_size = 0; { int sz = lseek(fd, 0, SEEK_END); if (sz > 0 && lseek(fd, 0, SEEK_SET) >= 0) { src_size = (uint32_t)sz; } } int sh = eynfs_stream_begin(dst_abs_path); if (sh < 0) { close(fd); return sh; } char buf[COPY_BUF_SZ]; int chunks_since_pulse = 0; uint32_t copied_bytes = 0; for (;;) { int n = (int)read(fd, buf, sizeof(buf)); if (n < 0) { close(fd); (void)eynfs_stream_end(sh); return -30; } if (n == 0) break; int w = (int)eynfs_stream_write(sh, buf, (size_t)n); if (w != n) { close(fd); (void)eynfs_stream_end(sh); return -40; } copied_bytes += (uint32_t)n; chunks_since_pulse++; if (s && chunks_since_pulse >= 64) { if (src_size > 0u) progress_set_fraction(s, copied_bytes, src_size); else installer_ui_pulse(s); chunks_since_pulse = 0; } } if (s && src_size > 0u) { progress_set_fraction(s, copied_bytes, src_size); } if (close(fd) != 0) return -50; { int end_rc = eynfs_stream_end(sh); if (end_rc != 0) return -60; } return 0; } static int installer_target_preflight(installer_t* s) { const char* probe = "/.__install_probe"; const char marker = 'X'; int wr = writefile(probe, &marker, 1); if (wr < 0) { error_set(s, "Target write probe failed"); return -1; } (void)unlink(probe); return 0; } static int installer_ram_preflight(installer_t* s) { int fd = open("RAM:/", O_RDONLY, 0); if (fd < 0) { error_set(s, "Could not open RAM:/ root"); return -1; } eyn_dirent_t entries[16]; int bytes = getdents(fd, entries, sizeof(entries)); close(fd); if (bytes < 0) { error_set(s, "Could not list RAM:/ root"); return -1; } if (bytes == 0) { error_set(s, "RAM:/ is empty (no files)"); return -1; } fd = open("RAM:/binaries/installer", O_RDONLY, 0); if (fd < 0) { error_set(s, "RAM:/binaries/installer missing"); return -1; } close(fd); fd = open(INSTALLER_MEDIA_INSTALL, O_RDONLY, 0); if (fd < 0) { error_set(s, "RAM:/binaries/install missing"); return -1; } close(fd); fd = open(INSTALLER_MEDIA_EXTRACT, O_RDONLY, 0); if (fd < 0) { error_set(s, "RAM:/binaries/extract missing"); return -1; } close(fd); fd = open(INSTALLER_MEDIA_BASE_MANIFEST, O_RDONLY, 0); if (fd < 0) { error_set(s, "RAM:/installer/base.manifest missing"); return -1; } close(fd); fd = open(INSTALLER_MEDIA_ETC_RESOLV, O_RDONLY, 0); if (fd < 0) { error_set(s, "RAM:/etc/resolv.conf missing"); return -1; } close(fd); if (!path_is_directory(INSTALLER_MEDIA_CONFIG_DIR)) { error_set(s, "RAM:/config missing"); return -1; } if (!path_is_directory(INSTALLER_MEDIA_VIEW_DIR)) { error_set(s, "RAM:/.view missing"); return -1; } return 0; } static int 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 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 ensure_target_directory(const char* path) { if (!path || path[0] != '/') return -1; char work[256]; 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 && !path_is_directory(work)) { work[i] = '/'; return -1; } work[i] = '/'; } if (mkdir(work, 0) != 0 && !path_is_directory(work)) return -1; return 0; } static int join_path2(const char* base, const char* leaf, char* out, size_t out_cap) { if (!base || !leaf || !out || out_cap == 0) return -1; size_t base_len = strlen(base); int needs_sep = (base_len > 0 && base[base_len - 1] != '/'); int needed = snprintf(out, out_cap, needs_sep ? "%s/%s" : "%s%s", base, leaf); if (needed <= 0 || needed >= (int)out_cap) return -1; return 0; } static int read_directory_file_names(const char* dir_path, char out_names[INSTALLER_MAX_FONT_FILES][INSTALLER_MAX_FONT_NAME], int max_count, int* out_count) { if (!dir_path || !out_names || !out_count || max_count <= 0) return -1; int fd = open(dir_path, O_RDONLY, 0); if (fd < 0) { *out_count = 0; return -2; } int count = 0; for (;;) { eyn_dirent_t entries[16]; int bytes = getdents(fd, entries, sizeof(entries)); if (bytes < 0) { close(fd); return -1; } if (bytes == 0) break; int entry_count = bytes / (int)sizeof(eyn_dirent_t); for (int i = 0; i < entry_count; i++) { const char* name = entries[i].name; if (!name || !name[0]) continue; if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; if (entries[i].is_dir) continue; if (count >= max_count) { close(fd); return -1; } size_t name_len = strlen(name); if (name_len == 0 || name_len >= INSTALLER_MAX_FONT_NAME) { close(fd); return -1; } strncpy(out_names[count], name, INSTALLER_MAX_FONT_NAME - 1); out_names[count][INSTALLER_MAX_FONT_NAME - 1] = '\0'; count++; } } close(fd); for (int i = 1; i < count; i++) { char tmp[INSTALLER_MAX_FONT_NAME]; strncpy(tmp, out_names[i], sizeof(tmp) - 1); tmp[sizeof(tmp) - 1] = '\0'; int j = i - 1; while (j >= 0 && strcmp(out_names[j], tmp) > 0) { strncpy(out_names[j + 1], out_names[j], INSTALLER_MAX_FONT_NAME - 1); out_names[j + 1][INSTALLER_MAX_FONT_NAME - 1] = '\0'; j--; } strncpy(out_names[j + 1], tmp, INSTALLER_MAX_FONT_NAME - 1); out_names[j + 1][INSTALLER_MAX_FONT_NAME - 1] = '\0'; } *out_count = count; return 0; } static int option_row_icons(const installer_t* s) { if (!s) return 0; return s->package_count; } static int option_row_fonts_toggle(const installer_t* s) { if (!s) return 1; return s->package_count + 1; } static int option_row_fonts_first(const installer_t* s) { if (!s) return 2; return s->package_count + 2; } static int option_row_chibicc(const installer_t* s) { if (!s) return 2; return option_row_fonts_first(s) + (s->include_fonts ? s->font_count : 0); } static int option_row_continue(const installer_t* s) { return option_row_chibicc(s) + 1; } static int options_row_count(const installer_t* s) { return option_row_continue(s) + 1; } static int selected_package_count(const installer_t* s) { if (!s) return 0; int selected = 0; for (int i = 0; i < s->package_count; i++) { if (s->package_selected[i]) selected++; } return selected; } static int selected_font_count(const installer_t* s) { if (!s || !s->include_fonts) return 0; int selected = 0; for (int i = 0; i < s->font_count; i++) { if (s->font_selected[i]) selected++; } return selected; } static void options_ensure_cursor_visible(installer_t* s) { if (!s) return; int rows = options_row_count(s); if (rows <= 0) { s->option_cursor = 0; s->option_scroll = 0; return; } if (s->option_cursor < 0) s->option_cursor = 0; if (s->option_cursor >= rows) s->option_cursor = rows - 1; if (s->option_scroll < 0) s->option_scroll = 0; if (s->option_scroll > s->option_cursor) s->option_scroll = s->option_cursor; if (s->option_cursor >= s->option_scroll + INSTALLER_OPTION_VISIBLE_ROWS) { s->option_scroll = s->option_cursor - INSTALLER_OPTION_VISIBLE_ROWS + 1; } int max_scroll = rows - INSTALLER_OPTION_VISIBLE_ROWS; if (max_scroll < 0) max_scroll = 0; if (s->option_scroll > max_scroll) s->option_scroll = max_scroll; } static void toggle_all_packages(installer_t* s) { if (!s || s->package_count <= 0) return; int any_unselected = 0; for (int i = 0; i < s->package_count; i++) { if (!s->package_selected[i]) { any_unselected = 1; break; } } unsigned char new_value = any_unselected ? 1u : 0u; for (int i = 0; i < s->package_count; i++) { s->package_selected[i] = new_value; } } static int toggle_option_row(installer_t* s, int row) { if (!s || row < 0) return -1; if (row < s->package_count) { s->package_selected[row] = s->package_selected[row] ? 0u : 1u; return 0; } if (row == option_row_icons(s)) { s->include_icons = s->include_icons ? 0 : 1; return 0; } if (row == option_row_fonts_toggle(s)) { if (s->font_count <= 0) { s->include_fonts = 0; return 0; } s->include_fonts = s->include_fonts ? 0 : 1; options_ensure_cursor_visible(s); return 0; } if (s->include_fonts) { int font_first = option_row_fonts_first(s); int font_last = font_first + s->font_count - 1; if (row >= font_first && row <= font_last) { int font_idx = row - font_first; s->font_selected[font_idx] = s->font_selected[font_idx] ? 0u : 1u; return 0; } } if (row == option_row_chibicc(s)) { s->include_chibicc = s->include_chibicc ? 0 : 1; return 0; } if (row == option_row_continue(s)) return 1; return -1; } static int installer_prepare_options(installer_t* s) { if (!s) return -1; if (s->options_loaded) return 0; if (read_manifest_packages(INSTALLER_MEDIA_BASE_MANIFEST, s->packages, &s->package_count) != 0) { return -1; } if (s->package_count <= 0) return -1; for (int i = 0; i < s->package_count; i++) { s->package_selected[i] = 1u; } s->include_icons = 1; s->include_fonts = 0; s->include_chibicc = 0; int font_scan_rc = read_directory_file_names(INSTALLER_MEDIA_FONTS_DIR, s->fonts, INSTALLER_MAX_FONT_FILES, &s->font_count); if (font_scan_rc < 0 && font_scan_rc != -2) { return -1; } if (font_scan_rc == -2) { s->font_count = 0; } for (int i = 0; i < s->font_count; i++) { s->font_selected[i] = 1u; } s->option_cursor = 0; s->option_scroll = 0; s->options_loaded = 1; options_ensure_cursor_visible(s); return 0; } static int copy_media_file_to_target(installer_t* s, const char* src_path, const char* dst_path, const char* item_label) { if (!src_path || !dst_path) return -1; if (item_label && item_label[0]) { progress_note_item(s, item_label); } int cfr = copy_file_stream_with_ui(s, src_path, dst_path); if (cfr != 0) { if (cfr == -10) error_set_path(s, "Copy source open failed", src_path); else if (cfr == -30) error_set_path(s, "Copy source read failed", src_path); else if (cfr == -40) error_set_path(s, "Copy target write failed", dst_path); else if (cfr == -50) error_set_path(s, "Copy source close failed", src_path); else if (cfr == -60) error_set_path(s, "Copy finalize failed", dst_path); else if (cfr < 0) error_set_path_code(s, "Copy target create failed", dst_path, cfr); else error_set_path(s, "Copy failed", src_path); return -1; } if (s) s->copied_files++; return 0; } static int copy_media_tree_recursive(installer_t* s, const char* src_root, const char* dst_root) { if (!src_root || !dst_root) return -1; int fd = open(src_root, O_RDONLY, 0); if (fd < 0) return -1; if (ensure_target_directory(dst_root) != 0) { close(fd); return -1; } if (s) s->copied_dirs++; for (;;) { eyn_dirent_t entries[16]; int bytes = getdents(fd, entries, sizeof(entries)); if (bytes < 0) { close(fd); return -1; } if (bytes == 0) break; int entry_count = bytes / (int)sizeof(eyn_dirent_t); for (int i = 0; i < entry_count; i++) { const char* name = entries[i].name; if (!name || !name[0]) continue; if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; char src_child[256]; char dst_child[256]; if (join_path2(src_root, name, src_child, sizeof(src_child)) != 0 || join_path2(dst_root, name, dst_child, sizeof(dst_child)) != 0) { close(fd); return -1; } if (entries[i].is_dir) { if (copy_media_tree_recursive(s, src_child, dst_child) != 0) { close(fd); return -1; } continue; } if (copy_media_file_to_target(s, src_child, dst_child, dst_child) != 0) { close(fd); return -1; } } } close(fd); return 0; } static int copy_media_tree_to_target(installer_t* s, const char* src_root, const char* dst_root, const char* missing_msg) { if (!src_root || !dst_root) return -1; if (!path_is_directory(src_root)) { if (missing_msg && missing_msg[0]) { error_set(s, missing_msg); } return -1; } if (copy_media_tree_recursive(s, src_root, dst_root) != 0) { error_set_path(s, "Failed to copy media tree", src_root); return -1; } return 0; } static int seed_system_content(installer_t* s) { if (!s) return -1; if (ensure_target_directory("/etc") != 0) { error_set_path(s, "Failed to create target directory", "/etc"); return -1; } if (copy_media_file_to_target(s, INSTALLER_MEDIA_ETC_RESOLV, "/etc/resolv.conf", "Seeding /etc/resolv.conf") != 0) { return -1; } progress_step(s, "resolv.conf"); if (copy_media_tree_to_target(s, INSTALLER_MEDIA_CONFIG_DIR, "/config", "RAM:/config missing") != 0) { return -1; } progress_step(s, "config"); if (copy_media_tree_to_target(s, INSTALLER_MEDIA_VIEW_DIR, "/.view", "RAM:/.view missing") != 0) { return -1; } progress_step(s, ".view"); if (s->include_icons) { if (copy_media_tree_to_target(s, INSTALLER_MEDIA_ICONS_DIR, "/icons", "RAM:/icons missing") != 0) { return -1; } progress_step(s, "icons"); if (copy_media_tree_to_target(s, INSTALLER_MEDIA_ICONS16_DIR, "/icons16", "RAM:/icons16 missing") != 0) { return -1; } progress_step(s, "icons16"); } if (s->include_fonts) { int fonts_selected = selected_font_count(s); if (fonts_selected <= 0) { error_set(s, "Fonts enabled but no fonts selected"); return -1; } if (ensure_target_directory("/fonts") != 0) { error_set_path(s, "Failed to create target directory", "/fonts"); return -1; } for (int i = 0; i < s->font_count; i++) { if (!s->font_selected[i]) continue; char src_font[256]; char dst_font[256]; if (join_path2(INSTALLER_MEDIA_FONTS_DIR, s->fonts[i], src_font, sizeof(src_font)) != 0 || join_path2("/fonts", s->fonts[i], dst_font, sizeof(dst_font)) != 0) { error_set(s, "Font path too long"); return -1; } char item[160]; snprintf(item, sizeof(item), "Seeding /fonts/%s", s->fonts[i]); if (copy_media_file_to_target(s, src_font, dst_font, item) != 0) { return -1; } progress_step(s, s->fonts[i]); } } if (s->include_chibicc) { if (!path_exists(INSTALLER_MEDIA_CHIBICC)) { error_set(s, "RAM:/programs/chibicc missing"); return -1; } if (ensure_target_directory("/binaries") != 0) { error_set_path(s, "Failed to create target directory", "/binaries"); return -1; } if (copy_media_file_to_target(s, INSTALLER_MEDIA_CHIBICC, "/binaries/chibicc", "Seeding /binaries/chibicc") != 0) { return -1; } progress_step(s, "chibicc"); } return 0; } static int parse_manifest_line(const char* line, char out_name[INSTALLER_MAX_PACKAGE_NAME]) { if (!line || !out_name) return 1; size_t start = 0; size_t len = strlen(line); while (start < len && (line[start] == ' ' || line[start] == '\t')) start++; if (start >= len) return 1; if (line[start] == '#') return 1; size_t end = len; while (end > start && (line[end - 1] == ' ' || line[end - 1] == '\t')) end--; if (end <= start) return 1; size_t token_end = start; while (token_end < end && line[token_end] != ' ' && line[token_end] != '\t') token_end++; size_t token_len = token_end - start; if (token_len == 0 || token_len >= INSTALLER_MAX_PACKAGE_NAME) return -1; memcpy(out_name, line + start, token_len); out_name[token_len] = '\0'; return 0; } static int read_manifest_packages(const char* manifest_path, char out_pkgs[INSTALLER_MAX_MANIFEST_PACKAGES][INSTALLER_MAX_PACKAGE_NAME], int* out_count) { if (!manifest_path || !out_pkgs || !out_count) return -1; int fd = open(manifest_path, O_RDONLY, 0); if (fd < 0) return -1; char line[160]; int line_len = 0; int count = 0; char chunk[128]; for (;;) { int n = (int)read(fd, chunk, sizeof(chunk)); if (n < 0) { close(fd); return -1; } if (n == 0) break; for (int i = 0; i < n; i++) { char c = chunk[i]; if (c == '\r') continue; if (c == '\n') { line[line_len] = '\0'; char parsed[INSTALLER_MAX_PACKAGE_NAME]; int pr = parse_manifest_line(line, parsed); if (pr < 0) { close(fd); return -1; } if (pr == 0) { if (count >= INSTALLER_MAX_MANIFEST_PACKAGES) { close(fd); return -1; } strncpy(out_pkgs[count], parsed, INSTALLER_MAX_PACKAGE_NAME - 1); out_pkgs[count][INSTALLER_MAX_PACKAGE_NAME - 1] = '\0'; count++; } line_len = 0; continue; } if (line_len + 1 < (int)sizeof(line)) { line[line_len++] = c; } else { close(fd); return -1; } } } if (line_len > 0) { line[line_len] = '\0'; char parsed[INSTALLER_MAX_PACKAGE_NAME]; int pr = parse_manifest_line(line, parsed); if (pr < 0) { close(fd); return -1; } if (pr == 0) { if (count >= INSTALLER_MAX_MANIFEST_PACKAGES) { close(fd); return -1; } strncpy(out_pkgs[count], parsed, INSTALLER_MAX_PACKAGE_NAME - 1); out_pkgs[count][INSTALLER_MAX_PACKAGE_NAME - 1] = '\0'; count++; } } close(fd); *out_count = count; return 0; } static int install_mbr_boot_code(int logical_drive) { int fd_boot = open("RAM:/installer/grub/boot.img", O_RDONLY, 0); if (fd_boot < 0) return -1; unsigned char boot[512]; int boot_bytes = (int)read(fd_boot, boot, sizeof(boot)); close(fd_boot); if (boot_bytes != 512) return -2; int fd_core = open("RAM:/installer/grub/core.img", O_RDONLY, 0); if (fd_core < 0) return -3; int core_size = lseek(fd_core, 0, SEEK_END); if (core_size <= 0) { close(fd_core); return -4; } if (lseek(fd_core, 0, SEEK_SET) < 0) { close(fd_core); return -5; } unsigned char* core = (unsigned char*)malloc((size_t)core_size); if (!core) { close(fd_core); return -6; } int got = 0; while (got < core_size) { int n = (int)read(fd_core, core + got, (size_t)(core_size - got)); if (n <= 0) { free(core); close(fd_core); return -7; } got += n; } close(fd_core); int core_sectors = (core_size + 511) / 512; if (core_sectors <= 0 || core_sectors >= 2048) { free(core); return -8; } int fd_kernel = open("/boot/kernel.bin", O_RDONLY, 0); if (fd_kernel < 0) { free(core); return -12; } int kernel_size = lseek(fd_kernel, 0, SEEK_END); if (kernel_size <= 0 || lseek(fd_kernel, 0, SEEK_SET) < 0) { close(fd_kernel); free(core); return -13; } int kernel_sectors = (kernel_size + 511) / 512; /* Rebuild MBR with current partition entries while replacing only boot code. */ eyn_installer_partitions_t parts; memset(&parts, 0, sizeof(parts)); if (eyn_sys_installer_get_partitions((uint32_t)logical_drive, &parts) != 0) { free(core); return -9; } unsigned char mbr[512]; memset(mbr, 0, sizeof(mbr)); memcpy(mbr, boot, 446); int has_bootable = 0; for (int i = 0; i < 4; ++i) { if (!parts.partitions[i].present) continue; if (parts.partitions[i].bootable) has_bootable = 1; } for (int i = 0; i < 4; ++i) { if (!parts.partitions[i].present) continue; unsigned char* e = mbr + 0x1BE + i * 16; int mark_boot = parts.partitions[i].bootable ? 1 : 0; if (!has_bootable && i == 0) mark_boot = 1; e[0] = (unsigned char)(mark_boot ? 0x80 : 0x00); /* status */ e[1] = 0xFE; e[2] = 0xFF; e[3] = 0xFF; /* CHS start (LBA) */ e[4] = parts.partitions[i].type; /* type */ e[5] = 0xFE; e[6] = 0xFF; e[7] = 0xFF; /* CHS end (LBA) */ uint32_t lba = parts.partitions[i].lba_start; uint32_t cnt = parts.partitions[i].sector_count; e[8] = (unsigned char)(lba & 0xFFu); e[9] = (unsigned char)((lba >> 8) & 0xFFu); e[10] = (unsigned char)((lba >> 16) & 0xFFu); e[11] = (unsigned char)((lba >> 24) & 0xFFu); e[12] = (unsigned char)(cnt & 0xFFu); e[13] = (unsigned char)((cnt >> 8) & 0xFFu); e[14] = (unsigned char)((cnt >> 16) & 0xFFu); e[15] = (unsigned char)((cnt >> 24) & 0xFFu); if (i == 0) { uint32_t raw_end = KERNEL_RAW_LBA + (uint32_t)kernel_sectors; if (raw_end >= parts.partitions[i].lba_start) { close(fd_kernel); free(core); return -14; } } } mbr[510] = 0x55; mbr[511] = 0xAA; if (eyn_sys_installer_write_sector((uint32_t)logical_drive, 0, mbr) != 0) { free(core); return -10; } /* Embed GRUB core image directly after MBR (LBA 1..N). */ for (int s = 0; s < core_sectors; ++s) { unsigned char sec[512]; memset(sec, 0, sizeof(sec)); int off = s * 512; int rem = core_size - off; int take = rem > 512 ? 512 : rem; if (take > 0) memcpy(sec, core + off, (size_t)take); if (eyn_sys_installer_write_sector((uint32_t)logical_drive, (uint32_t)(1 + s), sec) != 0) { close(fd_kernel); free(core); return -11; } } /* Write raw kernel blob at fixed LBA expected by embedded GRUB config. */ for (int s = 0; s < kernel_sectors; ++s) { unsigned char sec[512]; memset(sec, 0, sizeof(sec)); int n = (int)read(fd_kernel, sec, sizeof(sec)); if (n < 0) { close(fd_kernel); free(core); return -15; } if (eyn_sys_installer_write_sector((uint32_t)logical_drive, (uint32_t)(KERNEL_RAW_LBA + (uint32_t)s), sec) != 0) { close(fd_kernel); free(core); return -16; } if (n == 0) break; } close(fd_kernel); free(core); return 0; } static int write_grub_cfg(void) { const char* cfg = "set default=0\n" "set timeout=0\n" "menuentry \"EYN-OS\" {\n" " multiboot /boot/kernel.bin\n" " boot\n" "}\n"; (void)mkdir("/boot", 0); (void)mkdir("/boot/grub", 0); /* * Use the EYNFS stream writer here as well, to match the installer's * copy path and avoid backend differences in writefile() behavior. */ int sh = eynfs_stream_begin("/boot/grub/grub.cfg"); if (sh < 0) return sh; int len = (int)strlen(cfg); int w = (int)eynfs_stream_write(sh, cfg, (size_t)len); if (w != len) { (void)eynfs_stream_end(sh); return -200; } int end_rc = eynfs_stream_end(sh); if (end_rc != 0) return -201; return 0; } static int run_install_for_package(installer_t* s, const PackageIndex* package_index, const char* pkg_name, int index, int total) { if (!s || !package_index || !pkg_name || !pkg_name[0]) return -1; char status[96]; snprintf(status, sizeof(status), "Installing package %d/%d", index + 1, total); status_set(s, status); ResolvePlan plan; if (resolve_install_plan(package_index, pkg_name, &plan) != 0) { error_set_path(s, "Failed to resolve install plan", pkg_name); return -1; } if (plan.count <= 0) { error_set_path(s, "Empty install plan", pkg_name); return -1; } for (int plan_idx = 0; plan_idx < plan.count; plan_idx++) { const Package* pkg = plan.ordered[plan_idx]; if (!pkg || !pkg->name[0]) { error_set_path(s, "Invalid package in install plan", pkg_name); return -1; } char item[160]; snprintf(item, sizeof(item), "install %s", pkg->name); progress_note_item(s, item); if (install_package(package_index, pkg) != 0) { error_set_path(s, "Package install failed", pkg->name); return -1; } } progress_step(s, pkg_name); return 0; } static int install_from_package_manifest(installer_t* s) { if (!s) return -1; if (installer_prepare_options(s) != 0) { error_set(s, "Failed to load installer options"); return -1; } int packages_selected = selected_package_count(s); if (packages_selected <= 0) { error_set(s, "Select at least one package before install"); return -1; } int system_seed_steps = 3; if (s->include_icons) system_seed_steps += 2; if (s->include_fonts) system_seed_steps += selected_font_count(s); if (s->include_chibicc) system_seed_steps += 1; (void)mkdir("/binaries", 0); (void)mkdir("/boot", 0); (void)mkdir(INSTALL_CACHE_ROOT, 0); (void)mkdir(INSTALL_CACHE_PACKAGE_DIR, 0); status_set(s, "Seeding installer tools and cache ..."); progress_reset(s, 7 + system_seed_steps); if (copy_media_file_to_target(s, INSTALLER_MEDIA_INSTALL, "/binaries/install", "Seeding /binaries/install") != 0) { return -1; } progress_step(s, "install"); if (copy_media_file_to_target(s, INSTALLER_MEDIA_EXTRACT, "/binaries/extract", "Seeding /binaries/extract") != 0) { return -1; } progress_step(s, "extract"); if (copy_media_file_to_target(s, INSTALLER_MEDIA_INSTALLER, "/binaries/installer", "Seeding /binaries/installer") != 0) { return -1; } progress_step(s, "installer"); if (copy_media_file_to_target(s, "RAM:/boot/kernel.bin", "/boot/kernel.bin", "Copying /boot/kernel.bin") != 0) { return -1; } progress_step(s, "kernel"); if (path_exists(INSTALLER_MEDIA_INDEX)) { if (copy_media_file_to_target(s, INSTALLER_MEDIA_INDEX, INSTALL_CACHE_INDEX, "Copying local index.json") != 0) { return -1; } } progress_step(s, "index"); if (path_exists(INSTALLER_MEDIA_BASE_ARCHIVE)) { if (copy_media_file_to_target(s, INSTALLER_MEDIA_BASE_ARCHIVE, INSTALL_CACHE_BASE_ARCHIVE, "Copying base.pkg") != 0) { return -1; } } progress_step(s, "base.pkg"); if (!path_exists(INSTALLER_MEDIA_BASE_MANIFEST)) { error_set(s, "RAM:/installer/base.manifest missing"); return -1; } if (copy_media_file_to_target(s, INSTALLER_MEDIA_BASE_MANIFEST, INSTALL_CACHE_MANIFEST, "Copying base.manifest") != 0) { return -1; } progress_step(s, "manifest"); if (seed_system_content(s) != 0) { return -1; } PackageIndex package_index; if (index_fetch_and_parse(&package_index) != 0) { error_set(s, "Failed to load package index"); return -1; } s->step = STEP_COPY; status_set(s, "Installing base packages ..."); progress_reset(s, packages_selected); int previous_confirm_mode = package_set_confirm_mode(PACKAGE_CONFIRM_AUTO_ACCEPT); int install_index = 0; for (int i = 0; i < s->package_count; i++) { if (!s->package_selected[i]) continue; if (run_install_for_package(s, &package_index, s->packages[i], install_index, packages_selected) != 0) { (void)package_set_confirm_mode(previous_confirm_mode); return -1; } install_index++; } (void)package_set_confirm_mode(previous_confirm_mode); return 0; } static int run_install(installer_t* s) { if (!s) return -1; status_set(s, "Checking RAM:/ installer media ..."); progress_reset(s, 100); s->progress_permille = 30; installer_ui_pulse(s); if (installer_ram_preflight(s) != 0) return -101; if (eyn_sys_installer_prepare_drive((uint32_t)s->selected_drive) != 0) { error_set(s, "Drive partition/format failed"); return -102; } s->progress_permille = 80; installer_ui_pulse(s); if (eyn_sys_drive_set_logical((uint32_t)s->selected_drive) < 0) { error_set(s, "Could not switch to target drive"); return -103; } status_set(s, "Verifying target filesystem ..."); if (installer_target_preflight(s) != 0) return -104; s->progress_permille = 120; installer_ui_pulse(s); s->step = STEP_COPY; if (install_from_package_manifest(s) != 0) { if (s->error[0] == '\0') { error_set(s, "Package-manifest install failed"); } return -109; } status_set(s, "Writing GRUB config ..."); s->step = STEP_BOOTLOADER; s->progress_total = 3; s->progress_done = 0; s->progress_permille = 0; strncpy(s->current_item, "Writing /boot/grub/grub.cfg", sizeof(s->current_item) - 1); s->current_item[sizeof(s->current_item) - 1] = '\0'; installer_ui_pulse(s); { int cfg_rc = write_grub_cfg(); if (cfg_rc != 0) { error_set_path_code(s, "Failed writing /boot/grub/grub.cfg", "/boot/grub/grub.cfg", cfg_rc); return -106; } progress_step(s, "grub.cfg written"); } status_set(s, "Installing GRUB (embedded core) ..."); strncpy(s->current_item, "Embedding boot.img/core.img and kernel blob", sizeof(s->current_item) - 1); s->current_item[sizeof(s->current_item) - 1] = '\0'; installer_ui_pulse(s); { int mbr_rc = install_mbr_boot_code(s->selected_drive); if (mbr_rc < 0) { error_set_path_code(s, "Bootloader install failed", "/installer/grub/{boot.img,core.img}", mbr_rc); return -107; } progress_step(s, "Bootloader installed"); } status_set(s, "Installation complete"); progress_step(s, "Done"); s->progress_permille = 1000; installer_ui_pulse(s); s->step = STEP_DONE; return 0; } static void draw_center_text(int h, int y, const char* text, unsigned char r, unsigned char g, unsigned char b) { gui_text_t t; t.x = 12; t.y = y; t.r = r; t.g = g; t.b = b; t._pad = 0; t.text = text; (void)gui_draw_text(h, &t); } static void draw_ui(int h, installer_t* s) { gui_size_t sz; sz.w = 640; sz.h = 360; if (!gui_call_ok(gui_get_content_size(h, &sz), "gui_get_content_size", s)) { sz.w = 640; sz.h = 360; } gui_rgb_t bg = {GUI_PAL_BG_R, GUI_PAL_BG_G, GUI_PAL_BG_B, 0}; if (!gui_call_ok(gui_begin(h), "gui_begin", s)) return; if (!gui_call_ok(gui_clear(h, &bg), "gui_clear", s)) return; gui_rect_t header = {0, 0, sz.w, 24, GUI_PAL_HEADER_R, GUI_PAL_HEADER_G, GUI_PAL_HEADER_B, 0}; if (!gui_call_ok(gui_fill_rect(h, &header), "gui_fill_rect(header)", s)) return; draw_center_text(h, 6, "EYN-OS Installer", 245, 245, 245); if (s->step == STEP_WELCOME) { draw_center_text(h, 52, "This will install EYN-OS to a selected disk.", 220, 220, 220); draw_center_text(h, 72, "Press Enter to continue or Q to cancel.", 170, 170, 170); } else if (s->step == STEP_SELECT_DRIVE) { draw_center_text(h, 44, "Select target drive (Up/Down + Enter)", 220, 220, 220); int y = 72; for (int i = 0; i < s->drive_count; ++i) { char line[64]; snprintf(line, sizeof(line), "Drive %d", s->drives[i]); if (i == s->selected_idx) { gui_rect_t sel = {8, y - 2, sz.w - 16, 18, GUI_PAL_SEL_R, GUI_PAL_SEL_G, GUI_PAL_SEL_B, 0}; if (!gui_call_ok(gui_fill_rect(h, &sel), "gui_fill_rect(select)", s)) return; } draw_center_text(h, y, line, 240, 240, 240); y += 20; } if (s->drive_count == 0) { draw_center_text(h, 72, "No drives detected", 255, 140, 140); } } else if (s->step == STEP_OPTIONS) { int selected_pkgs = selected_package_count(s); int selected_fonts = selected_font_count(s); char summary_pkgs[96]; char summary_assets[128]; snprintf(summary_pkgs, sizeof(summary_pkgs), "Packages selected: %d / %d", selected_pkgs, s->package_count); snprintf(summary_assets, sizeof(summary_assets), "Icons: %s Fonts: %s (%d) Chibicc: %s", s->include_icons ? "on" : "off", s->include_fonts ? "on" : "off", selected_fonts, s->include_chibicc ? "on" : "off"); draw_center_text(h, 42, "Configure install options (menuconfig-style)", 220, 220, 220); draw_center_text(h, 62, summary_pkgs, 180, 200, 220); draw_center_text(h, 80, summary_assets, 170, 190, 210); int rows = options_row_count(s); int first = s->option_scroll; int last = first + INSTALLER_OPTION_VISIBLE_ROWS; if (last > rows) last = rows; int y = 106; for (int row = first; row < last; row++) { int is_cursor = (row == s->option_cursor); if (is_cursor) { gui_rect_t sel = {8, y - 2, sz.w - 16, 16, GUI_PAL_SEL_R, GUI_PAL_SEL_G, GUI_PAL_SEL_B, 0}; if (!gui_call_ok(gui_fill_rect(h, &sel), "gui_fill_rect(options)", s)) return; } char line[176]; if (row < s->package_count) { snprintf(line, sizeof(line), "[%c] package: %s", s->package_selected[row] ? 'x' : ' ', s->packages[row]); } else if (row == option_row_icons(s)) { snprintf(line, sizeof(line), "[%c] seed /icons and /icons16", s->include_icons ? 'x' : ' '); } else if (row == option_row_fonts_toggle(s)) { if (s->font_count > 0) { snprintf(line, sizeof(line), "[%c] seed fonts", s->include_fonts ? 'x' : ' '); } else { snprintf(line, sizeof(line), "[ ] seed fonts (none available on media)"); } } else if (s->include_fonts && row >= option_row_fonts_first(s) && row < option_row_fonts_first(s) + s->font_count) { int font_idx = row - option_row_fonts_first(s); snprintf(line, sizeof(line), " [%c] font: %s", s->font_selected[font_idx] ? 'x' : ' ', s->fonts[font_idx]); } else if (row == option_row_chibicc(s)) { snprintf(line, sizeof(line), "[%c] seed /binaries/chibicc", s->include_chibicc ? 'x' : ' '); } else if (row == option_row_continue(s)) { snprintf(line, sizeof(line), "[>] continue to disk format"); } else { snprintf(line, sizeof(line), ""); } draw_center_text(h, y, line, 230, 230, 230); y += 18; } draw_center_text(h, 306, "Up/Down: move Enter/Space: toggle", 170, 170, 170); draw_center_text(h, 324, "A: toggle all packages B: back I: continue", 170, 170, 170); } else if (s->step == STEP_FORMAT) { char line[96]; snprintf(line, sizeof(line), "Target drive: %d", s->selected_drive); draw_center_text(h, 52, line, 235, 235, 235); draw_center_text(h, 76, "Press Enter to FORMAT + INSTALL", 255, 200, 130); draw_center_text(h, 96, "Warning: this erases target disk contents.", 255, 120, 120); } else if (s->step == STEP_COPY || s->step == STEP_BOOTLOADER) { char c1[96]; char ptxt[64]; int bar_x = 14; int bar_y = 120; int bar_w = sz.w - 28; int bar_h = 12; int fill_w = (bar_w * clamp_permille(s->progress_permille)) / 1000; snprintf(c1, sizeof(c1), "Files copied: %d Dirs: %d", s->copied_files, s->copied_dirs); snprintf(ptxt, sizeof(ptxt), "Progress: %d%%", clamp_permille(s->progress_permille) / 10); draw_center_text(h, 56, s->status[0] ? s->status : "Working...", 200, 230, 255); draw_center_text(h, 78, c1, 185, 185, 185); gui_rect_t bar_bg = {bar_x, bar_y, bar_w, bar_h, 58, 61, 72, 0}; if (!gui_call_ok(gui_fill_rect(h, &bar_bg), "gui_fill_rect(bar_bg)", s)) return; if (fill_w > 0) { gui_rect_t bar_fg = {bar_x, bar_y, fill_w, bar_h, 86, 176, 240, 0}; if (!gui_call_ok(gui_fill_rect(h, &bar_fg), "gui_fill_rect(bar_fg)", s)) return; } draw_center_text(h, 138, ptxt, 190, 210, 230); if (s->current_item[0]) { draw_center_text(h, 158, s->current_item, 170, 170, 170); } else { draw_center_text(h, 158, "Please wait...", 170, 170, 170); } } else if (s->step == STEP_DONE) { char c1[96]; snprintf(c1, sizeof(c1), "Copied files: %d directories: %d", s->copied_files, s->copied_dirs); draw_center_text(h, 56, "Install finished successfully.", 140, 255, 160); draw_center_text(h, 78, c1, 220, 220, 220); draw_center_text(h, 102, "Reboot and boot from installed disk.", 190, 190, 190); draw_center_text(h, 126, "Press Q to close installer.", 170, 170, 170); } else if (s->step == STEP_ERROR) { draw_center_text(h, 56, "Install failed", 255, 120, 120); draw_center_text(h, 78, s->error, 230, 190, 190); draw_center_text(h, 102, "Press Q to close installer.", 170, 170, 170); } else { draw_center_text(h, 56, "Installer state error", 255, 140, 140); draw_center_text(h, 78, "Unexpected installer step; operation aborted.", 210, 180, 180); draw_center_text(h, 102, "Press Q to close installer.", 170, 170, 170); } (void)gui_call_ok(gui_present(h), "gui_present", s); } static void refresh_drives(installer_t* s) { s->drive_count = 0; int n = eyn_sys_drive_get_count(); if (n < 0) n = 0; for (int i = 0; i < n && s->drive_count < MAX_DRIVES; ++i) { if (eyn_sys_drive_is_present((uint32_t)i) > 0) { s->drives[s->drive_count++] = i; } } if (s->selected_idx >= s->drive_count) s->selected_idx = 0; } int main(void) { installer_t st; memset(&st, 0, sizeof(st)); st.running = 1; st.step = STEP_WELCOME; status_set(&st, "Ready"); int h = gui_attach("Installer", "EYN-OS setup"); if (h < 0) { puts("installer: gui_attach failed"); return 1; } /* * UI-INVARIANT: Installer text must remain visible while current drive * switches from RAM:/ to the target disk during install. * * Why: The default GUI font path may resolve against the current logical * drive. During target formatting/copy, that drive can be empty, causing * glyph lookups to fail and the window to appear blank. * * Contract: Use built-in kernel font (empty path) so rendering does not * depend on filesystem availability or current-drive context. */ (void)gui_set_font(h, ""); g_installer_gui_handle = h; refresh_drives(&st); draw_ui(h, &st); while (st.running) { gui_event_t ev; int rc = gui_wait_event(h, &ev); if (rc < 0) break; if (rc == 0) continue; if (ev.type == GUI_EVENT_CLOSE) break; if (ev.type == GUI_EVENT_KEY) { unsigned ch = (unsigned)ev.a & 0xFFu; if (ch == 'q' || ch == 'Q') break; if (st.step == STEP_WELCOME) { if (ch == '\r' || ch == '\n') st.step = STEP_SELECT_DRIVE; } else if (st.step == STEP_SELECT_DRIVE) { if (ev.a == GUI_KEY_UP && st.selected_idx > 0) st.selected_idx--; if (ev.a == GUI_KEY_DOWN && st.selected_idx + 1 < st.drive_count) st.selected_idx++; if ((ch == '\r' || ch == '\n') && st.drive_count > 0) { st.selected_drive = st.drives[st.selected_idx]; if (installer_prepare_options(&st) != 0) { error_set(&st, "Failed to load installer options"); st.step = STEP_ERROR; } else { st.step = STEP_OPTIONS; } } } else if (st.step == STEP_OPTIONS) { if (ev.a == GUI_KEY_UP && st.option_cursor > 0) { st.option_cursor--; } if (ev.a == GUI_KEY_DOWN && st.option_cursor + 1 < options_row_count(&st)) { st.option_cursor++; } if (ch == 'a' || ch == 'A') { toggle_all_packages(&st); } else if (ch == 'b' || ch == 'B') { st.step = STEP_SELECT_DRIVE; } else if (ch == 'i' || ch == 'I') { if (selected_package_count(&st) <= 0) { status_set(&st, "Select at least one package"); } else { st.step = STEP_FORMAT; } } else if (ch == '\r' || ch == '\n' || ch == ' ') { int row_action = toggle_option_row(&st, st.option_cursor); if (row_action == 1) { if (selected_package_count(&st) <= 0) { status_set(&st, "Select at least one package"); } else { st.step = STEP_FORMAT; } } } options_ensure_cursor_visible(&st); } else if (st.step == STEP_FORMAT) { if (ch == '\r' || ch == '\n') { st.step = STEP_COPY; status_set(&st, "Preparing disk ..."); draw_ui(h, &st); int install_rc = run_install(&st); if (install_rc != 0) { if (st.error[0] == '\0') { snprintf(st.error, sizeof(st.error), "Installer failed (rc=%d)", install_rc); st.error[sizeof(st.error) - 1] = '\0'; } printf("[installer] failure rc=%d msg=%s\n", install_rc, st.error); st.step = STEP_ERROR; } } } } draw_ui(h, &st); } g_installer_gui_handle = -1; return 0; }