0:/usr was a Unix transplant in an otherwise Montauk-native layout.
The SDK now lives at 0:/sdk/{bin,include,lib} (+ ldscripts under
0:/sdk/x86_64-montauk): Montauk-flavored, and a clean unit for a
future per-component installer tickbox. Native binutils is rebuilt
with --prefix=/sdk so ld's compiled-in search paths follow; future
ports configure with --prefix=/sdk.
Also add pathconf() with _PC_* names to the libc: now that realpath
exists, libiberty's lrealpath.c compiles its pathconf fallback path
unconditionally.
Shell command resolution and tab completion updated to 0:/sdk/bin.
Boot-verified in QEMU: setup, login, desktop on the new image with
zero usr/ remnants in the ramdisk.
Co-Authored-By: Claude Fable 5 <[email protected]>
224 lines
4.2 KiB
C++
224 lines
4.2 KiB
C++
/*
|
|
* tabcompletion.cpp
|
|
* Tab completion module
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#include <montauk/string.h>
|
|
#include <montauk/syscall.h>
|
|
#include "shell.h"
|
|
|
|
#define RD_MAX 128
|
|
|
|
/*
|
|
Does the file path end with .elf?
|
|
*/
|
|
bool is_executable(const char* path) {
|
|
size_t len = montauk::slen(path);
|
|
|
|
if (len < 4) {
|
|
return false;
|
|
}
|
|
|
|
if (path[len - 1] == 'f' &&
|
|
path[len - 2] == 'l' &&
|
|
path[len - 3] == 'e' &&
|
|
path[len - 4] == '.')
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int find_first_slash_pos(const char* string) {
|
|
int i = 0;
|
|
|
|
while (string[i] != '\0') {
|
|
if (string[i] == '/') {
|
|
return i;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
i.e., "shell.elf" => "shell"
|
|
*/
|
|
const char* strip_elf_extension(char* path) {
|
|
size_t len = montauk::slen(path);
|
|
|
|
path[len - 4] = '\0';
|
|
|
|
return (const char*)path;
|
|
}
|
|
|
|
const char* build_montauk_path(
|
|
char* buffer,
|
|
size_t max,
|
|
|
|
int volume_number,
|
|
const char* subpath
|
|
) {
|
|
|
|
char volume_number_str[4]; // 3 digits (max. 999) + null byte
|
|
int_to_str(volume_number, volume_number_str, 4);
|
|
|
|
// example path:
|
|
size_t vn_length = slen(volume_number_str); // 0
|
|
size_t subpath_length = slen(subpath); // users/admin
|
|
size_t bytes_required = vn_length + subpath_length + 2; // + ":" and "/" (full path 0:/users/admin)
|
|
|
|
if (max < bytes_required) {
|
|
return nullptr;
|
|
}
|
|
|
|
int pos = 0;
|
|
|
|
for (size_t i = 0; i < vn_length; i++) {
|
|
buffer[pos] = volume_number_str[i];
|
|
pos++;
|
|
}
|
|
|
|
buffer[pos] = ':'; pos++;
|
|
buffer[pos] = '/'; pos++;
|
|
|
|
for (size_t i = 0; i < subpath_length; i++) {
|
|
buffer[pos] = subpath[i];
|
|
pos++;
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
const char* remove_path_prefix(
|
|
const char* prefix, // users/admin
|
|
const char* string // users/admin/config/
|
|
) { // result: config/
|
|
int pos = 0;
|
|
|
|
while (string[pos] == prefix[pos]) {
|
|
pos++;
|
|
}
|
|
|
|
return &string[pos];
|
|
}
|
|
|
|
int get_completions( // returns no. of results
|
|
char* string, // String to match/complete
|
|
char** array, // Array of strings containing completions
|
|
int arrayMax,
|
|
|
|
char* cwd,
|
|
int volume_number
|
|
) {
|
|
|
|
// --- System commands (0:/os/*.elf) ----
|
|
const char* names[RD_MAX] = { };
|
|
|
|
int n = montauk::readdir("0:/os", names, RD_MAX);
|
|
int r = 0;
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
/*
|
|
Shift by n characters (names[i] + n),
|
|
n = position of first slash in path + 1:
|
|
|
|
"os/shell.elf" => "shell.elf"
|
|
|
|
This is somewhat of a workaround.
|
|
*/
|
|
int inc = find_first_slash_pos(names[i]) + 1;
|
|
|
|
if (montauk::starts_with(names[i] + inc, (const char*)string)) {
|
|
if (r >= arrayMax)
|
|
break;
|
|
|
|
if (!is_executable(names[i]))
|
|
continue;
|
|
|
|
char* p = (char *)names[i] + inc;
|
|
strip_elf_extension(p);
|
|
|
|
array[r] = (char *)p;
|
|
r++;
|
|
}
|
|
}
|
|
|
|
// ---- Montauk SDK tools (0:/sdk/bin/*.elf) ----
|
|
|
|
const char* usr_names[RD_MAX] = { };
|
|
|
|
int un = montauk::readdir("0:/sdk/bin", usr_names, RD_MAX);
|
|
|
|
for (int i = 0; i < un; i++) {
|
|
/* Entries come back as "sdk/bin/as.elf"; strip to the last
|
|
slash, not the first. */
|
|
int inc = 0;
|
|
for (int j = 0; usr_names[i][j]; j++) {
|
|
if (usr_names[i][j] == '/')
|
|
inc = j + 1;
|
|
}
|
|
|
|
if (montauk::starts_with(usr_names[i] + inc, (const char*)string)) {
|
|
if (r >= arrayMax)
|
|
break;
|
|
|
|
if (!is_executable(usr_names[i]))
|
|
continue;
|
|
|
|
char* p = (char *)usr_names[i] + inc;
|
|
strip_elf_extension(p);
|
|
|
|
array[r] = (char *)p;
|
|
r++;
|
|
}
|
|
}
|
|
|
|
// ---- Shell builtins ----
|
|
|
|
size_t builtins_count = sizeof(shell_builtins) / sizeof(const char*);
|
|
|
|
for (size_t i = 0; i < builtins_count; i++) {
|
|
if (montauk::starts_with(shell_builtins[i], (const char*)string)) {
|
|
if (r >= arrayMax)
|
|
break;
|
|
|
|
array[r] = (char *)shell_builtins[i];
|
|
r++;
|
|
}
|
|
}
|
|
|
|
// ---- cwd file matching ----
|
|
|
|
if (cwd != nullptr) {
|
|
char full_path[RD_MAX] = { };
|
|
build_montauk_path(full_path, RD_MAX, volume_number, cwd);
|
|
|
|
const char* cwd_entries[RD_MAX] = { };
|
|
|
|
int n = montauk::readdir(full_path, cwd_entries, RD_MAX);
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
const char* cwd_entry_path = remove_path_prefix(cwd, cwd_entries[i]);
|
|
|
|
if (cwd_entry_path[0] == '/') {
|
|
cwd_entry_path++;
|
|
}
|
|
|
|
if (montauk::starts_with(cwd_entry_path, (const char*)string)) {
|
|
if (r >= arrayMax)
|
|
break;
|
|
|
|
array[r] = (char *)cwd_entry_path;
|
|
r++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return r;
|
|
}
|