From db5aef12c2d28788bd72b652b9d6a30592865bda Mon Sep 17 00:00:00 2001 From: Aleksandr Lebedev Date: Tue, 21 Apr 2026 01:36:40 +0200 Subject: [PATCH] PS1 variable support, ~ expansion and pretty pwd - if arsh has a PS1 env var, it will use it to display prompt. It supports esc sequences, \u, \w - if '~' is used, it will be expanded to $HOME env var value - if PS1 env var uses \w, and $PWD is somewhere inside $HOME, then it will show '~' in prompt instead of a long path - arsh sets $SHELL env var on startup --- main.c | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 177 insertions(+), 5 deletions(-) diff --git a/main.c b/main.c index 3bf9475..95c60ee 100644 --- a/main.c +++ b/main.c @@ -9,13 +9,21 @@ #include constexpr size_t max_length = 1024; -constexpr size_t max_env_var_lenght = 256; +constexpr size_t max_env_var_length = 256; const char* token_separators = " \t\n\r"; +char* esc = "\x1B"; + +char* default_prompt = "arsh> "; static sigjmp_buf restart_jmp_buf; static constexpr int restart_value = 69; static volatile sig_atomic_t jump_active = 0; +size_t min_size_t(size_t a, size_t b) +{ + return a > b ? b : a; +} + void sigint_handler(int signo) { if(!jump_active) @@ -168,6 +176,7 @@ static void append_str(char** buf, size_t* len, size_t* cap, const char* s) struct pipeline* parse_pipeline(char *str); int execute_pipeline(struct pipeline* pl, int capture, char** out); +char* get_home(); static char* run_subcommand(const char* cmd) { @@ -222,6 +231,13 @@ char* parse_token(char** input) p++; continue; } + + if (!in_single && *p == '~') + { + p++; + append_str(&buf, &len, &cap, get_home()); + continue; + } if (!in_single && *p == '\\') { @@ -288,7 +304,7 @@ char* parse_token(char** input) if (*p == '{') { p++; - char name[max_env_var_lenght]; + char name[max_env_var_length]; int i = 0; while (*p && *p != '}') name[i++] = *p++; @@ -299,7 +315,7 @@ char* parse_token(char** input) continue; } - char name[max_env_var_lenght]; + char name[max_env_var_length]; int i = 0; while (*p && (isalnum(*p) || *p == '_')) name[i++] = *p++; @@ -519,7 +535,13 @@ pid_t run_with_redir(struct command* command, int n_pipes, int (*pipes)[2]) int cd(char* path) { - return chdir(path); + int result = chdir(path); + char cwd[max_length]; + if (getcwd(cwd, sizeof(cwd)) != NULL) + { + setenv("PWD", cwd, 1); + } + return result; } int execute_pipeline(struct pipeline* pl, int capture, char** out) @@ -618,10 +640,155 @@ int execute_pipeline(struct pipeline* pl, int capture, char** out) return WEXITSTATUS(status); } +char username_buf[max_env_var_length] = {0}; +char* get_user() +{ + char* result = getenv("USER"); + if(!result) + { + return "UNKNOWN"; + } + size_t length = min_size_t(sizeof(username_buf), strlen(result) + 1); + memcpy(username_buf, result, length); + return username_buf; +} + +char home_buf[max_length] = {0}; +char* get_home() +{ + char* result = getenv("HOME"); + if(!result) + { + return "UNKNOWN_HOME_PATH"; + } + size_t length = min_size_t(sizeof(home_buf), strlen(result) + 1); + memcpy(home_buf, result, length); + return home_buf; +} + +char pwd_buf[max_length] = {0}; +char* get_pwd() +{ + char* result = getenv("PWD"); + if(!result) + { + return "UNKNOWN"; + } + size_t length = min_size_t(sizeof(pwd_buf), strlen(result) + 1); + memcpy(pwd_buf, result, length); + return pwd_buf; +} + +char* prettify_pwd(char* pwd) +{ + if (!pwd) + { + fprintf(stderr, "Internal Error: pwd can't be null"); + exit(1); + } + char* home = get_home(); + size_t home_len = strlen(home); + size_t pwd_len = strlen(pwd); + if(pwd_len < home_len) + { + return pwd; + } + if(memcmp(home, pwd, home_len) == 0) + { + char* ret = ccalloc(sizeof(char), pwd_len - home_len + 1 + 1); + parser_allocated(ret); + ret[0] = '~'; + memcpy(ret + 1, pwd + home_len, pwd_len - home_len); + ret[pwd_len - home_len + 1] = '\0'; + return ret; + } + return pwd; +} + +char prompt_buf[max_length] = {0}; +char* generate_ps1_prompt() +{ + char env_buf[max_env_var_length] = {0}; + char* ps1 = getenv("PS1"); + if(!ps1) + { + return default_prompt; + } + size_t ps1_len = min_size_t(sizeof(env_buf), strlen(ps1) + 1) - 1; + if(ps1_len == 0) + { + return default_prompt; + } + memcpy(env_buf, ps1, ps1_len); + env_buf[ps1_len + 1] = '\0'; + if(ps1_len < 2) + { + memcpy(prompt_buf, env_buf, ps1_len + 1); + return prompt_buf; + } + size_t i = 1, j = 0; + size_t start = 0; + while(env_buf[i] != '\0' && j < sizeof(prompt_buf)) + { + if(env_buf[i - 1] != '\\') + { + i++; + continue; + } + char* data = 0; + switch (env_buf[i]) + { + case 'u': + data = get_user(); + break; + case 'w': + data = prettify_pwd(get_pwd()); + break; + case 'e': + data = esc; + break; + case ']': + case '[': + data = ""; + break; + default: + i++; + continue; + } + if(!data) + { + i++; + continue; + } + size_t len = strlen(data); + if(j + i - 1 - start + len >= sizeof(prompt_buf)) + { + fprintf(stderr, "Out of memory(1) for prompt: %d >= %d", j + i - start + len, sizeof(prompt_buf)); + break; + } + size_t start_len = min_size_t(sizeof(prompt_buf) - j, i - start - 1); + memcpy(prompt_buf + j, env_buf + start, start_len); + j += start_len; + if(j + len >= sizeof(prompt_buf)) + { + fprintf(stderr, "Out of memory(2) for prompt: %d >= %d", j + len, sizeof(prompt_buf)); + break; + } + memcpy(prompt_buf + j, data, len); + i += 1; start = i; j += len; + } + size_t start_len = min_size_t(sizeof(prompt_buf) - j, i - start); + memcpy(prompt_buf + j, env_buf + start, start_len); + j += start_len; + prompt_buf[j] = '\0'; + return prompt_buf; +} + int main(int argc, const char* argv[]) { + setenv("SHELL", argv[0], 1); FILE* stream = stdin; - char* prompt = "arsh> "; + char* prompt = generate_ps1_prompt(); const bool spawn_command = argc >= 2 && strcmp(argv[1], "-c") == 0; if(spawn_command) { @@ -674,5 +841,10 @@ int main(int argc, const char* argv[]) execute_pipeline(pipeline, 0, NULL); parser_free(); + + if(prompt) + { + prompt = generate_ps1_prompt(); + } } }