/****************************************************************************** __ ____ __ / / ___ ____ _/ __/_____________ _/ /__ / / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \ / /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/ /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/ (C)opyright 2025, Leafscale, LLC - https://www.leafscale.com Project: Zygaena Filename: database.reef Authors: Chris Tusa License: Description: Installed package database management ******************************************************************************/ module core.database import types import io.file import io.dir import io.path import core.str import encoding.toml import util.mtree import sys.process export // Check if a package is installed fn is_installed(name: string): bool // List all installed packages (returns count, fills names array) fn list_installed(names: [string], max_count: int): int // Get installed package details fn get_installed(name: string): types.InstalledPackage // Register a new installed package fn register_package(pkg: types.InstalledPackage): bool // Register package with file list copied from extract dir (more efficient) fn register_from_extract_dir(pkg: types.InstalledPackage, extract_dir: string): bool // Unregister (remove) a package from database fn unregister_package(name: string): bool // Get list of files owned by a package (returns count, fills files array) fn get_files(name: string, files: [string], max_count: int): int // Find which package owns a given file path fn find_owner(file_path: string): string // Find which package provides an alternative fn find_provider(alternative: string): string // Ensure database directories exist fn init_db(): bool // Scripts support fn get_scripts_dir(name: string): string fn store_scripts(name: string, extract_dir: string): bool fn has_stored_scripts(name: string): bool // Config files support fn store_config_files(name: string, extract_dir: string): bool fn get_config_files(name: string, config_files: [string], max_count: int): int // Manifest support fn get_manifest_path(name: string): string // Root-aware versions for alternate root installation fn init_db_rooted(root: string): bool fn register_from_extract_dir_rooted(pkg: types.InstalledPackage, extract_dir: string, root: string): bool fn store_scripts_rooted(name: string, extract_dir: string, root: string): bool fn store_config_files_rooted(name: string, extract_dir: string, root: string): bool fn is_installed_rooted(name: string, root: string): bool fn list_installed_rooted(names: [string], max_count: int, root: string): int fn get_installed_rooted(name: string, root: string): types.InstalledPackage fn get_files_rooted(name: string, files: [string], max_count: int, root: string): int fn find_owner_rooted(file_path: string, root: string): string fn unregister_package_rooted(name: string, root: string): bool end export // Get the package database directory for a specific package fn pkg_db_dir(name: string): string return path.join_path(types.get_db_dir(), name) end pkg_db_dir // Get the pkg.toml path for a package fn pkg_toml_path(name: string): string return path.join_path(pkg_db_dir(name), "pkg.toml") end pkg_toml_path // Get the files.txt path for a package fn pkg_files_path(name: string): string return path.join_path(pkg_db_dir(name), "files.txt") end pkg_files_path // Get the manifest.mtree path for a package fn get_manifest_path(name: string): string return path.join_path(pkg_db_dir(name), "manifest.mtree") end get_manifest_path // Initialize the database directory structure fn init_db(): bool if not dir.dir_exists(types.get_db_dir()) return dir.create_dir_all(types.get_db_dir()) end if return true end init_db // Check if a package is installed by looking for its database entry fn is_installed(name: string): bool let pkg_dir = pkg_db_dir(name) if not dir.dir_exists(pkg_dir) return false end if // Also verify pkg.toml exists return file.fileExists(pkg_toml_path(name)) end is_installed // List all installed packages by scanning the database directory fn list_installed(names: [string], max_count: int): int if not dir.dir_exists(types.get_db_dir()) return 0 end if mut entries: [string] = new [string](256) let entry_count = dir.list_dir(types.get_db_dir(), entries, 256) mut count = 0 mut i = 0 while i < entry_count and count < max_count let entry = entries[i] // Skip hidden files if str.length(entry) > 0 and entry[0] != '.' // Verify it's a valid package (has pkg.toml) let toml_path = pkg_toml_path(entry) if file.fileExists(toml_path) names[count] = entry count = count + 1 end if end if i = i + 1 end while return count end list_installed // Create an empty InstalledPackage fn new_installed_package(): types.InstalledPackage return types.new_installed_package() end new_installed_package // Get details of an installed package fn get_installed(name: string): types.InstalledPackage if not is_installed(name) return new_installed_package() end if // Read pkg.toml let toml_path = pkg_toml_path(name) let content = file.readFile(toml_path) if str.length(content) == 0 return new_installed_package() end if // Parse TOML let keys = toml.toml_alloc_keys() let vals = toml.toml_alloc_values() let entry_count = toml.toml_parse(content, keys, vals) if entry_count == 0 return new_installed_package() end if // Build package info mut pkg = new_installed_package() pkg.info.name = toml.toml_get(keys, vals, entry_count, "package.name") pkg.info.version = toml.toml_get(keys, vals, entry_count, "package.version") pkg.info.release = toml.toml_get_int(keys, vals, entry_count, "package.release") pkg.info.description = toml.toml_get(keys, vals, entry_count, "package.description") pkg.info.url = toml.toml_get(keys, vals, entry_count, "package.url") pkg.info.license = toml.toml_get(keys, vals, entry_count, "package.license") pkg.info.maintainer = toml.toml_get(keys, vals, entry_count, "package.maintainer") pkg.info.arch = toml.toml_get(keys, vals, entry_count, "package.arch") // Install metadata pkg.install_date = toml.toml_get(keys, vals, entry_count, "install.date") pkg.install_reason = toml.toml_get(keys, vals, entry_count, "install.reason") // Read files list let files_path = pkg_files_path(name) if file.fileExists(files_path) let files_content = file.readFile(files_path) if str.length(files_content) > 0 let alloc_size = count_newlines(files_content) + 16 mut files_arr: [string] = new [string](alloc_size) let files_count = str.split(files_content, '\n', files_arr, alloc_size) // Filter out empty lines mut valid_count = 0 mut j = 0 while j < files_count if str.length(files_arr[j]) > 0 valid_count = valid_count + 1 end if j = j + 1 end while pkg.files = files_arr pkg.files_count = valid_count end if end if return pkg end get_installed // Generate TOML content for a package fn generate_pkg_toml(pkg: types.InstalledPackage): string mut content = "[package]\n" content = content + "name = \"" + pkg.info.name + "\"\n" content = content + "version = \"" + pkg.info.version + "\"\n" content = content + "release = " + int_to_str(pkg.info.release) + "\n" content = content + "description = \"" + pkg.info.description + "\"\n" content = content + "url = \"" + pkg.info.url + "\"\n" content = content + "license = \"" + pkg.info.license + "\"\n" content = content + "maintainer = \"" + pkg.info.maintainer + "\"\n" content = content + "arch = \"" + pkg.info.arch + "\"\n" content = content + "\n" content = content + "[install]\n" content = content + "date = \"" + pkg.install_date + "\"\n" content = content + "reason = \"" + pkg.install_reason + "\"\n" return content end generate_pkg_toml // Helper: convert int to string fn int_to_str(n: int): string if n == 0 return "0" end if mut negative = false mut value = n if n < 0 negative = true value = 0 - n end if mut result = "" while value > 0 let digit = value % 10 result = str.concat(str.substring("0123456789", digit, 1), result) value = value / 10 end while if negative result = str.concat("-", result) end if return result end int_to_str // Register a newly installed package fn register_package(pkg: types.InstalledPackage): bool // Ensure database directory exists if not init_db() return false end if // Create package directory let pkg_dir = pkg_db_dir(pkg.info.name) if not dir.dir_exists(pkg_dir) if not dir.create_dir_all(pkg_dir) return false end if end if // Write pkg.toml let toml_content = generate_pkg_toml(pkg) let toml_path = pkg_toml_path(pkg.info.name) if not file.writeFile(toml_path, toml_content) return false end if // Note: files.txt is written by register_from_extract_dir for performance return true end register_package // Count newline characters in a string to estimate line count fn count_newlines(s: string): int let slen = str.length(s) mut count = 0 mut i = 0 while i < slen if s[i] == '\n' count = count + 1 end if i = i + 1 end while return count end count_newlines // Process a .FOOTPRINT file content: filter and normalize paths // Uses str.join() for O(n) single-allocation string building fn process_footprint(content: string): string let newline_count = count_newlines(content) let alloc_size = newline_count + 16 mut lines: [string] = new [string](alloc_size) let line_count = str.split(content, '\n', lines, alloc_size) mut result_lines: [string] = new [string](alloc_size) mut result_count = 0 mut i = 0 while i < line_count mut line = str.trim_ws(lines[i]) if str.length(line) == 0 i = i + 1 continue end if // Strip leading ./ if str.starts_with(line, "./") line = str.substring(line, 2, str.length(line) - 2) end if // Skip metadata files if str.equals(line, ".PKGINFO") or str.equals(line, ".FOOTPRINT") i = i + 1 continue end if if str.starts_with(line, ".SCRIPTS") i = i + 1 continue end if if str.equals(line, ".CONFIG") i = i + 1 continue end if // Skip directories (end with /) let len = str.length(line) if len > 0 and line[len - 1] == '/' i = i + 1 continue end if result_lines[result_count] = line result_count = result_count + 1 i = i + 1 end while return str.join(result_lines, result_count, "\n") end process_footprint // Extract file paths from mtree manifest content for files.txt // Parses manifest entries and returns a newline-joined list of relative paths // (files and symlinks only, no directories, no metadata) fn extract_paths_from_manifest(content: string): string mut entries: [mtree.ManifestEntry] = new [mtree.ManifestEntry](8192) let count = mtree.parse_manifest(content, entries, 8192) mut result_lines: [string] = new [string](8192) mut result_count = 0 mut i = 0 while i < count let etype = mtree.entry_type(entries[i]) let epath = mtree.entry_path(entries[i]) // Only include files and symlinks (matches legacy files.txt behavior) if str.equals(etype, "file") or str.equals(etype, "link") // Strip leading ./ if str.starts_with(epath, "./") result_lines[result_count] = str.substring(epath, 2, str.length(epath) - 2) else result_lines[result_count] = epath end if result_count = result_count + 1 end if i = i + 1 end while return str.join(result_lines, result_count, "\n") end extract_paths_from_manifest // Register package and copy files list from extracted package directory fn register_from_extract_dir(pkg: types.InstalledPackage, extract_dir: string): bool // Register the package metadata if not register_package(pkg) return false end if let files_path = pkg_files_path(pkg.info.name) // Try .MANIFEST first (v2 format), fall back to .FOOTPRINT (legacy) let manifest_path = path.join_path(extract_dir, ".MANIFEST") if file.fileExists(manifest_path) // Store the full manifest for future use (verify, manifest-based remove) let manifest_store = path.join_path(pkg_db_dir(pkg.info.name), "manifest.mtree") let manifest_content = file.readFile(manifest_path) file.writeFile(manifest_store, manifest_content) // Also generate files.txt for backward compatibility let result = extract_paths_from_manifest(manifest_content) return file.writeFile(files_path, result) end if // Legacy: .FOOTPRINT let footprint_path = path.join_path(extract_dir, ".FOOTPRINT") if not file.fileExists(footprint_path) file.writeFile(files_path, "") return true end if let content = file.readFile(footprint_path) if str.length(content) == 0 file.writeFile(files_path, "") return true end if let result = process_footprint(content) return file.writeFile(files_path, result) end register_from_extract_dir // Helper to remove a file fn remove_file(file_path: string): bool let cmd = "rm -f \"" + file_path + "\"" let pid = process.process_spawn_shell(cmd) if pid < 0 return false end if let exit_code = process.process_wait(pid) return exit_code == 0 end remove_file // Unregister a package from the database fn unregister_package(name: string): bool if not is_installed(name) return true // Already not installed end if let pkg_dir = pkg_db_dir(name) // Remove pkg.toml let toml_path = pkg_toml_path(name) if file.fileExists(toml_path) if not remove_file(toml_path) return false end if end if // Remove files.txt let files_path = pkg_files_path(name) if file.fileExists(files_path) if not remove_file(files_path) return false end if end if // Remove manifest.mtree let manifest_path = get_manifest_path(name) if file.fileExists(manifest_path) remove_file(manifest_path) end if // Remove scripts directory if exists let scripts_dir = get_scripts_dir(name) if dir.dir_exists(scripts_dir) let cmd = "rm -rf \"" + scripts_dir + "\"" let pid = process.process_spawn_shell(cmd) if pid > 0 process.process_wait(pid) end if end if // Remove config.txt if exists let config_path = pkg_config_path(name) if file.fileExists(config_path) remove_file(config_path) end if // Remove the package directory return dir.remove_dir(pkg_dir) end unregister_package // Get list of files owned by a package fn get_files(name: string, files: [string], max_count: int): int if not is_installed(name) return 0 end if let files_path = pkg_files_path(name) if not file.fileExists(files_path) return 0 end if let content = file.readFile(files_path) if str.length(content) == 0 return 0 end if // Split by newlines — size dynamically let alloc_size = count_newlines(content) + 16 mut all_lines: [string] = new [string](alloc_size) let line_count = str.split(content, '\n', all_lines, alloc_size) // Copy non-empty lines to output mut count = 0 mut i = 0 while i < line_count and count < max_count let line = str.trim_ws(all_lines[i]) if str.length(line) > 0 files[count] = line count = count + 1 end if i = i + 1 end while return count end get_files // Find which package owns a given file fn find_owner(file_path: string): string // Normalize the path - strip leading "/" if present mut search_path = file_path if str.length(search_path) > 0 and search_path[0] == '/' search_path = str.substring(search_path, 1, str.length(search_path) - 1) end if // Get list of all installed packages mut packages: [string] = new [string](256) let pkg_count = list_installed(packages, 256) mut i = 0 while i < pkg_count let pkg_name = packages[i] // Get files for this package mut files: [string] = new [string](4096) let file_count = get_files(pkg_name, files, 4096) // Search for matching file mut j = 0 while j < file_count if files[j] == search_path return pkg_name end if j = j + 1 end while i = i + 1 end while return "" // No owner found end find_owner // Find which package provides an alternative fn find_provider(alternative: string): string // Get list of all installed packages mut packages: [string] = new [string](256) let pkg_count = list_installed(packages, 256) mut i = 0 while i < pkg_count let pkg_name = packages[i] let pkg = get_installed(pkg_name) // Check alternatives array // Note: We'd need to parse the alternatives array from pkg.toml // For now, check if this package provides the alternative let toml_path = pkg_toml_path(pkg_name) let content = file.readFile(toml_path) if str.length(content) > 0 let keys = toml.toml_alloc_keys() let vals = toml.toml_alloc_values() let entry_count = toml.toml_parse(content, keys, vals) if toml.toml_has_key(keys, vals, entry_count, "package.alternatives") let alts_val = toml.toml_get(keys, vals, entry_count, "package.alternatives") // Check if the alternative is in the array if str.contains(alts_val, alternative) return pkg_name end if end if end if i = i + 1 end while return "" // No provider found end find_provider // Get the scripts directory path for a package in the database fn get_scripts_dir(name: string): string return path.join_path(pkg_db_dir(name), "scripts") end get_scripts_dir // Check if a package has stored scripts in the database fn has_stored_scripts(name: string): bool let scripts_path = get_scripts_dir(name) return dir.dir_exists(scripts_path) end has_stored_scripts // Store scripts from an extracted package to the database fn store_scripts(name: string, extract_dir: string): bool let src_scripts = path.join_path(extract_dir, ".SCRIPTS") // Check if source has scripts if not dir.dir_exists(src_scripts) return true // No scripts is not an error end if let dest_scripts = get_scripts_dir(name) // Create scripts directory in database if not dir.dir_exists(dest_scripts) if not dir.create_dir_all(dest_scripts) return false end if end if // Copy all script files let cmd = "cp -r \"" + src_scripts + "/\"* \"" + dest_scripts + "/\" 2>/dev/null" let pid = process.process_spawn_shell(cmd) if pid < 0 return false end if let exit_code = process.process_wait(pid) // Don't fail if cp fails (might be empty directory) return true end store_scripts // Get the config.txt path for a package fn pkg_config_path(name: string): string return path.join_path(pkg_db_dir(name), "config.txt") end pkg_config_path // Store config files list from an extracted package to the database fn store_config_files(name: string, extract_dir: string): bool let src_config = path.join_path(extract_dir, ".CONFIG") // Check if source has config files list if not file.fileExists(src_config) return true // No config files is not an error end if let dest_config = pkg_config_path(name) // Copy the config files list let content = file.readFile(src_config) if str.length(content) > 0 return file.writeFile(dest_config, content) end if return true end store_config_files // Get list of config files for a package fn get_config_files(name: string, config_files: [string], max_count: int): int let config_path = pkg_config_path(name) if not file.fileExists(config_path) return 0 end if let content = file.readFile(config_path) if str.length(content) == 0 return 0 end if // Split by newlines mut lines: [string] = new [string](max_count) let line_count = str.split(content, '\n', lines, max_count) // Filter out empty lines and comments mut count = 0 mut i = 0 while i < line_count and count < max_count let line = str.trim_ws(lines[i]) if str.length(line) > 0 and line[0] != '#' config_files[count] = line count = count + 1 end if i = i + 1 end while return count end get_config_files // ============================================================================ // Root-aware functions for alternate root installation // ============================================================================ // Get the rooted database directory fn get_db_dir_rooted(root: string): string if str.length(root) == 0 return types.get_db_dir() end if return root + types.get_db_dir() end get_db_dir_rooted // Get the rooted package database directory fn pkg_db_dir_rooted(name: string, root: string): string return path.join_path(get_db_dir_rooted(root), name) end pkg_db_dir_rooted // Get the rooted pkg.toml path fn pkg_toml_path_rooted(name: string, root: string): string return path.join_path(pkg_db_dir_rooted(name, root), "pkg.toml") end pkg_toml_path_rooted // Get the rooted files.txt path fn pkg_files_path_rooted(name: string, root: string): string return path.join_path(pkg_db_dir_rooted(name, root), "files.txt") end pkg_files_path_rooted // Initialize database with alternate root fn init_db_rooted(root: string): bool let db_dir = get_db_dir_rooted(root) if not dir.dir_exists(db_dir) return dir.create_dir_all(db_dir) end if return true end init_db_rooted // Check if package is installed in alternate root fn is_installed_rooted(name: string, root: string): bool let pkg_dir = pkg_db_dir_rooted(name, root) if not dir.dir_exists(pkg_dir) return false end if return file.fileExists(pkg_toml_path_rooted(name, root)) end is_installed_rooted // Register package with alternate root fn register_package_rooted(pkg: types.InstalledPackage, root: string): bool // Ensure database directory exists if not init_db_rooted(root) return false end if // Create package directory let pkg_dir = pkg_db_dir_rooted(pkg.info.name, root) if not dir.dir_exists(pkg_dir) if not dir.create_dir_all(pkg_dir) return false end if end if // Write pkg.toml let toml_content = generate_pkg_toml(pkg) let toml_path = pkg_toml_path_rooted(pkg.info.name, root) if not file.writeFile(toml_path, toml_content) return false end if return true end register_package_rooted // Register package from extract dir with alternate root fn register_from_extract_dir_rooted(pkg: types.InstalledPackage, extract_dir: string, root: string): bool // Register the package metadata if not register_package_rooted(pkg, root) return false end if let files_path = pkg_files_path_rooted(pkg.info.name, root) // Try .MANIFEST first (v2 format), fall back to .FOOTPRINT (legacy) let manifest_path = path.join_path(extract_dir, ".MANIFEST") if file.fileExists(manifest_path) // Store the full manifest let manifest_store = path.join_path(pkg_db_dir_rooted(pkg.info.name, root), "manifest.mtree") let manifest_content = file.readFile(manifest_path) file.writeFile(manifest_store, manifest_content) // Also generate files.txt for backward compatibility let result = extract_paths_from_manifest(manifest_content) return file.writeFile(files_path, result) end if // Legacy: .FOOTPRINT let footprint_path = path.join_path(extract_dir, ".FOOTPRINT") if not file.fileExists(footprint_path) file.writeFile(files_path, "") return true end if let content = file.readFile(footprint_path) if str.length(content) == 0 file.writeFile(files_path, "") return true end if let result = process_footprint(content) return file.writeFile(files_path, result) end register_from_extract_dir_rooted // Get scripts directory with alternate root fn get_scripts_dir_rooted(name: string, root: string): string return path.join_path(pkg_db_dir_rooted(name, root), "scripts") end get_scripts_dir_rooted // Store scripts with alternate root fn store_scripts_rooted(name: string, extract_dir: string, root: string): bool let src_scripts = path.join_path(extract_dir, ".SCRIPTS") if not dir.dir_exists(src_scripts) return true end if let dest_scripts = get_scripts_dir_rooted(name, root) if not dir.dir_exists(dest_scripts) if not dir.create_dir_all(dest_scripts) return false end if end if let cmd = "cp -r \"" + src_scripts + "/\"* \"" + dest_scripts + "/\" 2>/dev/null" let pid = process.process_spawn_shell(cmd) if pid < 0 return false end if process.process_wait(pid) return true end store_scripts_rooted // Get config path with alternate root fn pkg_config_path_rooted(name: string, root: string): string return path.join_path(pkg_db_dir_rooted(name, root), "config.txt") end pkg_config_path_rooted // Store config files with alternate root fn store_config_files_rooted(name: string, extract_dir: string, root: string): bool let src_config = path.join_path(extract_dir, ".CONFIG") if not file.fileExists(src_config) return true end if let dest_config = pkg_config_path_rooted(name, root) let content = file.readFile(src_config) if str.length(content) > 0 return file.writeFile(dest_config, content) end if return true end store_config_files_rooted // List all installed packages in alternate root fn list_installed_rooted(names: [string], max_count: int, root: string): int let db_dir = get_db_dir_rooted(root) if not dir.dir_exists(db_dir) return 0 end if mut entries: [string] = new [string](256) let entry_count = dir.list_dir(db_dir, entries, 256) mut count = 0 mut i = 0 while i < entry_count and count < max_count let entry = entries[i] // Skip hidden files if str.length(entry) > 0 and entry[0] != '.' // Verify it's a valid package (has pkg.toml) let toml_path = pkg_toml_path_rooted(entry, root) if file.fileExists(toml_path) names[count] = entry count = count + 1 end if end if i = i + 1 end while return count end list_installed_rooted // Get details of an installed package in alternate root fn get_installed_rooted(name: string, root: string): types.InstalledPackage if not is_installed_rooted(name, root) return new_installed_package() end if // Read pkg.toml let toml_path = pkg_toml_path_rooted(name, root) let content = file.readFile(toml_path) if str.length(content) == 0 return new_installed_package() end if // Parse TOML let keys = toml.toml_alloc_keys() let vals = toml.toml_alloc_values() let entry_count = toml.toml_parse(content, keys, vals) if entry_count == 0 return new_installed_package() end if // Build package info mut pkg = new_installed_package() pkg.info.name = toml.toml_get(keys, vals, entry_count, "package.name") pkg.info.version = toml.toml_get(keys, vals, entry_count, "package.version") pkg.info.release = toml.toml_get_int(keys, vals, entry_count, "package.release") pkg.info.description = toml.toml_get(keys, vals, entry_count, "package.description") pkg.info.url = toml.toml_get(keys, vals, entry_count, "package.url") pkg.info.license = toml.toml_get(keys, vals, entry_count, "package.license") pkg.info.maintainer = toml.toml_get(keys, vals, entry_count, "package.maintainer") pkg.info.arch = toml.toml_get(keys, vals, entry_count, "package.arch") // Install metadata pkg.install_date = toml.toml_get(keys, vals, entry_count, "install.date") pkg.install_reason = toml.toml_get(keys, vals, entry_count, "install.reason") // Read files list let files_path = pkg_files_path_rooted(name, root) if file.fileExists(files_path) let files_content = file.readFile(files_path) if str.length(files_content) > 0 let alloc_size = count_newlines(files_content) + 16 mut files_arr: [string] = new [string](alloc_size) let files_count = str.split(files_content, '\n', files_arr, alloc_size) // Filter out empty lines mut valid_count = 0 mut j = 0 while j < files_count if str.length(files_arr[j]) > 0 valid_count = valid_count + 1 end if j = j + 1 end while pkg.files = files_arr pkg.files_count = valid_count end if end if return pkg end get_installed_rooted // Get list of files owned by a package in alternate root fn get_files_rooted(name: string, files: [string], max_count: int, root: string): int if not is_installed_rooted(name, root) return 0 end if let files_path = pkg_files_path_rooted(name, root) if not file.fileExists(files_path) return 0 end if let content = file.readFile(files_path) if str.length(content) == 0 return 0 end if // Split by newlines — size dynamically let alloc_size = count_newlines(content) + 16 mut all_lines: [string] = new [string](alloc_size) let line_count = str.split(content, '\n', all_lines, alloc_size) // Copy non-empty lines to output mut count = 0 mut i = 0 while i < line_count and count < max_count let line = str.trim_ws(all_lines[i]) if str.length(line) > 0 files[count] = line count = count + 1 end if i = i + 1 end while return count end get_files_rooted // Find which package owns a given file in alternate root fn find_owner_rooted(file_path: string, root: string): string // Normalize the path - strip leading "/" if present mut search_path = file_path if str.length(search_path) > 0 and search_path[0] == '/' search_path = str.substring(search_path, 1, str.length(search_path) - 1) end if // Get list of all installed packages in this root mut packages: [string] = new [string](256) let pkg_count = list_installed_rooted(packages, 256, root) mut i = 0 while i < pkg_count let pkg_name = packages[i] // Get files for this package mut files: [string] = new [string](4096) let file_count = get_files_rooted(pkg_name, files, 4096, root) // Search for matching file mut j = 0 while j < file_count if files[j] == search_path return pkg_name end if j = j + 1 end while i = i + 1 end while return "" // No owner found end find_owner_rooted // Unregister a package from the database in alternate root fn unregister_package_rooted(name: string, root: string): bool if not is_installed_rooted(name, root) return true // Already not installed end if let pkg_dir = pkg_db_dir_rooted(name, root) // Remove pkg.toml let toml_path = pkg_toml_path_rooted(name, root) if file.fileExists(toml_path) if not remove_file(toml_path) return false end if end if // Remove files.txt let files_path = pkg_files_path_rooted(name, root) if file.fileExists(files_path) if not remove_file(files_path) return false end if end if // Remove scripts directory if exists let scripts_dir = get_scripts_dir_rooted(name, root) if dir.dir_exists(scripts_dir) let cmd = "rm -rf \"" + scripts_dir + "\"" let pid = process.process_spawn_shell(cmd) if pid > 0 process.process_wait(pid) end if end if // Remove config.txt if exists let config_path = pkg_config_path_rooted(name, root) if file.fileExists(config_path) remove_file(config_path) end if // Remove the package directory return dir.remove_dir(pkg_dir) end unregister_package_rooted end module