12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213 |
- /* vim: tabstop=4 shiftwidth=4 noexpandtab
- * This file is part of ToaruOS and is released under the terms
- * of the NCSA / University of Illinois License - see LICENSE.md
- * Copyright (C) 2013-2018 K. Lange
- *
- * E-Shell
- *
- * This is "experimental shell" - a vaguely-unix-like command
- * interface. It has a very rudimentary parser that understands
- * some things like pipes or writing out to a file. It has a
- * handful of built-in commands, including ones that implement
- * some more useful shell syntax such as loops and conditionals.
- * There is support for tab completion of filenames and commands.
- */
- #define _XOPEN_SOURCE 500
- #define _POSIX_C_SOURCE 200112L
- #include <stdio.h>
- #include <stdint.h>
- #include <string.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <time.h>
- #include <dirent.h>
- #include <signal.h>
- #include <getopt.h>
- #include <termios.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <ctype.h>
- #include <sys/time.h>
- #include <sys/wait.h>
- #include <sys/utsname.h>
- #include <sys/stat.h>
- #include <toaru/list.h>
- #include <toaru/hashmap.h>
- #include <toaru/kbd.h>
- #include <toaru/rline.h>
- #include <toaru/rline_exp.h>
- #ifndef environ
- extern char **environ;
- #endif
- #define PIPE_TOKEN "\xFF\xFFPIPE\xFF\xFF"
- #define STAR_TOKEN "\xFF\xFFSTAR\xFF\xFF"
- #define WRITE_TOKEN "\xFF\xFFWRITE\xFF\xFF"
- #define APPEND_TOKEN "\xFF\xFF""APPEND\xFF"
- /* A shell command is like a C program */
- typedef uint32_t(*shell_command_t) (int argc, char ** argv);
- /* We have a static array that fits a certain number of them. */
- int SHELL_COMMANDS = 64;
- char ** shell_commands; /* Command names */
- shell_command_t * shell_pointers; /* Command functions */
- char ** shell_descript; /* Command descriptions */
- /* This is the number of actual commands installed */
- int shell_commands_len = 0;
- int shell_interactive = 1;
- int last_ret = 0;
- char ** shell_argv = NULL;
- int shell_argc = 0;
- int experimental_rline = 1;
- static int current_line = 0;
- static char * current_file = NULL;
- int pid; /* Process ID of the shell */
- int my_pgid;
- int is_subshell = 0;
- struct semaphore {
- int fds[2];
- };
- struct semaphore create_semaphore(void) {
- struct semaphore out;
- pipe(out.fds);
- return out;
- }
- void raise_semaphore(struct semaphore s){
- close(s.fds[0]);
- close(s.fds[1]);
- }
- void wait_semaphore(struct semaphore s) {
- close(s.fds[1]);
- char buf;
- read(s.fds[0], &buf, 1);
- close(s.fds[0]);
- }
- void set_pgid(int pgid) {
- if (shell_interactive == 1) {
- setpgid(0, pgid);
- }
- }
- void set_pgrp(int pgid) {
- if (shell_interactive == 1 && !is_subshell) {
- tcsetpgrp(STDIN_FILENO, pgid);
- }
- }
- void reset_pgrp() {
- if (shell_interactive == 1 && !is_subshell) {
- tcsetpgrp(STDIN_FILENO, my_pgid);
- }
- }
- void shell_install_command(char * name, shell_command_t func, char * desc) {
- if (shell_commands_len == SHELL_COMMANDS) {
- SHELL_COMMANDS *= 2;
- shell_commands = realloc(shell_commands, sizeof(char *) * SHELL_COMMANDS);
- shell_pointers = realloc(shell_pointers, sizeof(shell_command_t) * SHELL_COMMANDS);
- shell_descript = realloc(shell_descript, sizeof(char *) * SHELL_COMMANDS);
- }
- shell_commands[shell_commands_len] = name;
- shell_pointers[shell_commands_len] = func;
- shell_descript[shell_commands_len] = desc;
- shell_commands_len++;
- }
- shell_command_t shell_find(char * str) {
- for (int i = 0; i < shell_commands_len; ++i) {
- if (!strcmp(str, shell_commands[i])) {
- return shell_pointers[i];
- }
- }
- return NULL;
- }
- void install_commands();
- /* Maximum command length */
- #define LINE_LEN 4096
- /* Current working directory */
- char cwd[1024] = {'/',0};
- /* Username */
- char username[1024];
- /* Hostname for prompt */
- char _hostname[256];
- /* function to update the cached username */
- void getuser() {
- char * tmp = getenv("USER");
- if (tmp) {
- strcpy(username, tmp);
- } else {
- sprintf(username, "%d", getuid());
- }
- }
- /* function to update the cached hostname */
- void gethost() {
- struct utsname buf;
- uname(&buf);
- int len = strlen(buf.nodename);
- memcpy(_hostname, buf.nodename, len+1);
- }
- void print_extended_ps(char * format, char * buffer, int * display_width) {
- /* Get the time */
- struct tm * timeinfo;
- struct timeval now;
- gettimeofday(&now, NULL); //time(NULL);
- timeinfo = localtime((time_t *)&now.tv_sec);
- /* Format the date and time for prompt display */
- char date_buffer[80];
- strftime(date_buffer, 80, "%m/%d", timeinfo);
- char time_buffer[80];
- strftime(time_buffer, 80, "%H:%M:%S", timeinfo);
- /* Collect the current working directory */
- getcwd(cwd, 512);
- char _cwd[512];
- strcpy(_cwd, cwd);
- /* Collect the user's home directory and apply it to cwd */
- char * home = getenv("HOME");
- if (home && strstr(cwd, home) == cwd) {
- char * c = cwd + strlen(home);
- if (*c == '/' || *c == 0) {
- sprintf(_cwd, "~%s", c);
- }
- }
- char ret[80] = {0};
- if (last_ret != 0) {
- sprintf(ret, "%d ", last_ret);
- }
- size_t offset = 0;
- int is_visible = 1;
- *display_width = 0;
- while (*format) {
- if (*format == '\\') {
- format++;
- switch (*format) {
- case '\\':
- buffer[offset++] = *format;
- (*display_width) += is_visible ? 1 : 0;
- format++;
- break;
- case '[':
- is_visible = 0;
- format++;
- break;
- case ']':
- is_visible = 1;
- format++;
- break;
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- {
- int i = (*format) - '0';
- format++;
- if (*format >= '0' && *format <= '7') {
- i *= 8;
- i += (*format) - '0';
- format++;
- if (*format >= '0' && *format <= '7') {
- i *= 8;
- i += (*format) - '0';
- format++;
- }
- }
- buffer[offset++] = i;
- (*display_width) += is_visible ? 1 : 0;
- }
- break;
- case 'e':
- buffer[offset++] = '\033';
- (*display_width) += is_visible ? 1 : 0;
- format++;
- break;
- case 'd':
- {
- int size = sprintf(buffer+offset, "%s", date_buffer);
- offset += size;
- (*display_width) += is_visible ? size : 0;
- }
- format++;
- break;
- case 't':
- {
- int size = sprintf(buffer+offset, "%s", time_buffer);
- offset += size;
- (*display_width) += is_visible ? size : 0;
- }
- format++;
- break;
- case 'h':
- {
- int size = sprintf(buffer+offset, "%s", _hostname);
- offset += size;
- (*display_width) += is_visible ? size : 0;
- }
- format++;
- break;
- case 'u':
- {
- int size = sprintf(buffer+offset, "%s", username);
- offset += size;
- (*display_width) += is_visible ? size : 0;
- }
- format++;
- break;
- case 'w':
- {
- int size = sprintf(buffer+offset, "%s", _cwd);
- offset += size;
- (*display_width) += is_visible ? size : 0;
- }
- format++;
- break;
- case '$':
- buffer[offset++] = (getuid() == 0 ? '#' : '$');
- (*display_width) += is_visible ? 1 : 0;
- format++;
- break;
- case 'U': /* prompt color string */
- {
- int size = sprintf(buffer+offset, "%s", getuid() == 0 ? "\033[1;38;5;196m" : "\033[1;38;5;47m");
- offset += size;
- /* Does not affect size */
- }
- format++;
- break;
- case 'r':
- {
- int size = sprintf(buffer+offset, "%s", ret);
- offset += size;
- (*display_width) += is_visible ? size : 0;
- }
- format++;
- break;
- default:
- {
- int size = sprintf(buffer+offset, "\\%c", *format);
- offset += size;
- (*display_width) += is_visible ? size : 0;
- }
- format++;
- break;
- }
- } else {
- buffer[offset++] = *format;
- (*display_width) += is_visible ? 1 : 0;
- format++;
- }
- }
- buffer[offset] = '\0';
- }
- #define FALLBACK_PS1 "\\u@\\h \\w\\$ "
- /* Draw the user prompt */
- void draw_prompt(void) {
- char * ps1 = getenv("PS1");
- char buf[1024];
- int display_width;
- print_extended_ps(ps1 ? ps1 : FALLBACK_PS1, buf, &display_width);
- fprintf(stdout, "%s", buf);
- fflush(stdout);
- }
- volatile int break_while = 0;
- pid_t suspended_pgid = 0;
- hashmap_t * job_hash = NULL;
- void sig_break_loop(int sig) {
- /* Interrupt handler */
- if ((shell_interactive == 1 && !is_subshell)) {
- break_while = sig;
- signal(sig, sig_break_loop);
- } else {
- signal(sig, SIG_DFL);
- raise(sig);
- }
- }
- void redraw_prompt_func(rline_context_t * context) {
- draw_prompt();
- }
- void draw_prompt_c() {
- char * ps2 = getenv("PS2");
- if (ps2) {
- char buf[1024];
- int display_width;
- print_extended_ps(ps2, buf, &display_width);
- fprintf(stdout, "%s", buf);
- } else {
- printf("> ");
- }
- fflush(stdout);
- }
- void redraw_prompt_func_c(rline_context_t * context) {
- draw_prompt_c();
- }
- void tab_complete_func(rline_context_t * c) {
- char * dup = malloc(LINE_LEN);
-
- memcpy(dup, c->buffer, LINE_LEN);
- char *pch, *save;
- char *argv[1024];
- int argc = 0;
- int cursor = 0;
- pch = strtok_r(dup, " ", &save);
- if (!pch) {
- argv[0] = "";
- argc = 0;
- }
- while (pch != NULL) {
- if (pch - dup <= c->offset) cursor = argc;
- argv[argc] = pch;
- ++argc;
- pch = strtok_r(NULL, " ", &save);
- }
- argv[argc] = NULL;
- if (c->offset && c->buffer[c->offset-1] == ' ' && argc) {
- cursor++;
- }
- char * word = argv[cursor];
- int word_offset = word ? (c->offset - (argv[cursor] - dup)) : 0;
- char * prefix = malloc(word_offset + 1);
- if (word) memcpy(prefix, word, word_offset);
- prefix[word_offset] = '\0';
- /* Complete file path */
- list_t * matches = list_create();
- char * match = NULL;
- int free_matches = 0;
- int no_space_if_only = 0;
- /* TODO custom auto-complete as a configuration file? */
- #define COMPLETE_FILE 1
- #define COMPLETE_COMMAND 2
- #define COMPLETE_CUSTOM 3
- #define COMPLETE_VARIABLE 4
- int complete_mode = COMPLETE_FILE;
- int with_dollar = 0;
- int command_adj = 0;
- int cursor_adj = cursor;
- while (command_adj < argc && strstr(argv[command_adj],"=")) {
- cursor_adj -= 1;
- command_adj += 1;
- }
- /* sudo should shift commands */
- if (command_adj < argc && (!strcmp(argv[command_adj], "sudo") || !strcmp(argv[command_adj], "gsudo"))) {
- cursor_adj -= 1;
- command_adj += 1;
- }
- /* initial tab completion should be commands, unless typing a file path */
- if (cursor_adj == 0 && !strchr(prefix,'/')) {
- complete_mode = COMPLETE_COMMAND;
- }
- if (cursor_adj >= 1 && !strcmp(argv[command_adj], "toggle-abs-mouse")) {
- complete_mode = COMPLETE_CUSTOM;
- }
- if (cursor_adj >= 1 && !strcmp(argv[command_adj], "msk")) {
- complete_mode = COMPLETE_CUSTOM;
- }
- if (cursor_adj >= 1 && !strcmp(argv[command_adj], "unset")) {
- complete_mode = COMPLETE_VARIABLE;
- }
- /* complete variable names */
- if (*prefix == '$') {
- complete_mode = COMPLETE_VARIABLE;
- with_dollar = 1;
- }
- if (complete_mode == COMPLETE_COMMAND) {
- /* Complete a command name */
- for (int i = 0; i < shell_commands_len; ++i) {
- if (strstr(shell_commands[i], prefix) == shell_commands[i]) {
- list_insert(matches, shell_commands[i]);
- match = shell_commands[i];
- }
- }
- } else if (complete_mode == COMPLETE_FILE) {
- /* Complete a file path */
- free_matches = 1;
- char * tmp = strdup(prefix);
- char * last_slash = strrchr(tmp, '/');
- DIR * dirp;
- char * compare = prefix;
- if (last_slash) {
- *last_slash = '\0';
- word = word + (last_slash - tmp) + 1;
- word_offset = word_offset - (last_slash - tmp + 1);
- compare = word;
- if (last_slash == tmp) {
- dirp = opendir("/");
- } else {
- dirp = opendir(tmp);
- }
- } else {
- dirp = opendir(".");
- }
- if (!dirp) {
- free(tmp);
- goto finish_tab;
- }
- struct dirent * ent = readdir(dirp);
- while (ent != NULL) {
- if (ent->d_name[0] != '.' || compare[0] == '.') {
- if (!word || strstr(ent->d_name, compare) == ent->d_name) {
- struct stat statbuf;
- /* stat it */
- if (last_slash) {
- char * x = malloc(strlen(tmp) + 1 + strlen(ent->d_name) + 1);
- sprintf(x,"%s/%s",tmp,ent->d_name);
- lstat(x, &statbuf);
- free(x);
- } else {
- lstat(ent->d_name, &statbuf);
- }
- char * s;
- if (S_ISDIR(statbuf.st_mode)) {
- s = malloc(strlen(ent->d_name) + 2);
- sprintf(s,"%s/", ent->d_name);
- no_space_if_only = 1;
- } else {
- s = strdup(ent->d_name);
- }
- list_insert(matches, s);
- match = s;
- }
- }
- ent = readdir(dirp);
- }
- closedir(dirp);
- free(tmp);
- } else if (complete_mode == COMPLETE_CUSTOM) {
- char * none[] = {NULL};
- char ** completions = none;
- char * toggle_abs_mouse_completions[] = {"relative","absolute",NULL};
- char * msk_commands[] = {"update","install","list","count","--version",NULL};
- if (!strcmp(argv[command_adj],"toggle-abs-mouse")) {
- completions = toggle_abs_mouse_completions;
- } else if (!strcmp(argv[command_adj], "msk")) {
- if (cursor_adj == 1) {
- completions = msk_commands;
- } else if (cursor_adj > 1 && !strcmp(argv[command_adj+1],"install")) {
- FILE * f = fopen("/var/msk/manifest","r");
- list_t * packages = list_create();
- if (f) {
- while (!feof(f)) {
- char tmp[4096] = {0};
- if (!fgets(tmp, 4096, f)) break;
- if (tmp[0] == '[') {
- char * s = strstr(tmp, "]");
- if (s) { *s = '\0'; }
- list_insert(packages, strdup(tmp+1));
- }
- }
- completions = malloc(sizeof(char *) * (packages->length + 1));
- free_matches = 1;
- size_t i = 0;
- foreach(node, packages) {
- completions[i++] = node->value;
- }
- completions[i] = NULL;
- list_free(packages);
- }
- }
- }
- while (*completions) {
- if (strstr(*completions, prefix) == *completions) {
- list_insert(matches, *completions);
- match = *completions;
- }
- completions++;
- }
- } else if (complete_mode == COMPLETE_VARIABLE) {
- char ** envvar = environ;
- free_matches = 1;
- while (*envvar) {
- char * tmp = strdup(*envvar);
- char * c = strchr(tmp, '=');
- *c = '\0';
- if (strstr(tmp, prefix+with_dollar) == tmp) {
- char * m = malloc(strlen(tmp)+1+with_dollar);
- sprintf(m, "%s%s", with_dollar ? "$" : "", tmp);
- list_insert(matches, m);
- match = m;
- }
- free(tmp);
- envvar++;
- }
- }
- if (matches->length == 1) {
- /* Insert */
- rline_insert(c, &match[word_offset]);
- if (word && word_offset == (int)strlen(word) && !no_space_if_only) {
- rline_insert(c, " ");
- }
- rline_redraw(c);
- } else if (matches->length > 1) {
- if (!c->tabbed) {
- /* see if there is a minimum subset we can fill in */
- size_t j = word_offset;
- do {
- char d = match[j];
- int diff = 0;
- foreach(node, matches) {
- char * match = (char *)node->value;
- if (match[j] != d || match[j] == '\0') diff = 1;
- }
- if (diff) break;
- j++;
- } while (j < (size_t)c->requested);
- if (j > (size_t)word_offset) {
- char * tmp = strdup(match);
- tmp[j] = '\0';
- rline_insert(c, &tmp[word_offset]);
- rline_redraw(c);
- free(tmp);
- } else {
- c->tabbed = 1;
- }
- } else {
- /* Print matches */
- fprintf(stderr,"\n\033[0m");
- size_t j = 0;
- foreach(node, matches) {
- char * match = (char *)node->value;
- fprintf(stderr, "%s", match);
- ++j;
- if (j < matches->length) {
- fprintf(stderr, ", ");
- }
- }
- fprintf(stderr,"\n");
- c->callbacks->redraw_prompt(c);
- fprintf(stderr, "\033[s");
- rline_redraw(c);
- }
- }
- finish_tab:
- if (free_matches) list_destroy(matches);
- list_free(matches);
- free(prefix);
- free(dup);
- }
- void add_argument(list_t * argv, char * buf) {
- char * c = malloc(strlen(buf) + 1);
- memcpy(c, buf, strlen(buf) + 1);
- list_insert(argv, c);
- }
- void add_environment(list_t * env) {
- if (env->length) {
- foreach (node, env) {
- char * c = node->value;
- putenv(strdup(c));
- }
- }
- }
- int read_entry(char * buffer) {
- if (experimental_rline) {
- char lprompt[1024], rprompt[1024];
- int lwidth, rwidth;
- char * ps1 = getenv("PS1_LEFT");
- print_extended_ps(ps1 ? ps1 : FALLBACK_PS1, lprompt, &lwidth);
- char * ps1r = getenv("PS1_RIGHT");
- print_extended_ps(ps1r ? ps1r : "", rprompt, &rwidth);
- rline_exit_string="exit";
- rline_exp_set_syntax("esh");
- rline_exp_set_prompts(lprompt, rprompt, lwidth, rwidth);
- rline_exp_set_shell_commands(shell_commands, shell_commands_len);
- rline_exp_set_tab_complete_func(tab_complete_func);
- return rline_experimental(buffer, LINE_LEN);
- } else {
- rline_callbacks_t callbacks = {
- tab_complete_func, redraw_prompt_func, NULL,
- NULL, NULL, NULL, NULL, NULL
- };
- draw_prompt();
- return rline((char *)buffer, LINE_LEN, &callbacks);
- }
- }
- int read_entry_continued(char * buffer) {
- if (experimental_rline) {
- rline_exit_string="exit";
- rline_exp_set_syntax("esh");
- rline_exp_set_prompts("> ", "", 2, 0);
- rline_exp_set_shell_commands(shell_commands, shell_commands_len);
- rline_exp_set_tab_complete_func(tab_complete_func);
- return rline_experimental(buffer, LINE_LEN);
- } else {
- rline_callbacks_t callbacks = {
- tab_complete_func, redraw_prompt_func_c, NULL,
- NULL, NULL, NULL, NULL, NULL
- };
- draw_prompt_c();
- return rline((char *)buffer, LINE_LEN, &callbacks);
- }
- }
- int variable_char(uint8_t c) {
- if (c >= 'A' && c <= 'Z') return 1;
- if (c >= 'a' && c <= 'z') return 1;
- if (c >= '0' && c <= '9') return 1;
- if (c == '_') return 1;
- return 0;
- }
- int variable_char_first(uint8_t c) {
- if (c == '?') return 1;
- if (c == '$') return 1;
- if (c == '#') return 1;
- return 0;
- }
- struct alternative {
- const char * command;
- const char * replacement;
- const char * description;
- };
- #define ALT_BIM "bim", "vi-like text editor"
- #define ALT_FETCH "fetch", "URL downloader"
- #define ALT_NETIF "cat /proc/netif", "to see network configuration"
- static struct alternative cmd_alternatives[] = {
- /* Propose bim as an alternative for common text editors */
- {"vim", ALT_BIM},
- {"vi", ALT_BIM},
- {"emacs", ALT_BIM},
- {"nano", ALT_BIM},
- /* Propose fetch for some common URL-getting tools */
- {"curl", ALT_FETCH},
- {"wget", ALT_FETCH},
- /* We don't have ip or ifconfig commands, suggest cat /proc/netif */
- {"ifconfig", ALT_NETIF},
- {"ipconfig", ALT_NETIF},
- {"ip", ALT_NETIF},
- /* Some random other stuff */
- {"grep", "fgrep", "non-regex-capable grep"},
- {"more", "bim -", "paging to a text editor"},
- {"less", "bim -", "paging to a text editor"},
- {NULL, NULL, NULL},
- };
- void run_cmd(char ** args) {
- int i = execvp(*args, args);
- shell_command_t func = shell_find(*args);
- if (func) {
- int argc = 0;
- while (args[argc]) {
- argc++;
- }
- i = func(argc, args);
- } else {
- if (i != 0) {
- fprintf(stderr, "%s: Command not found\n", *args);
- for (struct alternative * alt = cmd_alternatives; alt->command; alt++) {
- if (!strcmp(*args, alt->command)) {
- fprintf(stderr, "Consider this alternative:\n\n\t%s -- \033[3m%s\033[0m\n\n",
- alt->replacement,
- alt->description);
- break;
- }
- }
- i = 127;
- }
- }
- exit(i);
- }
- int is_number(const char * c) {
- while (*c) {
- if (!isdigit(*c)) return 0;
- c++;
- }
- return 1;
- }
- static char * _strsignal(int sig) {
- static char str[256];
- memset(str, 0, sizeof(str));
- switch (sig) {
- case SIGILL:
- sprintf(str, "Illegal instruction");
- break;
- case SIGSEGV:
- sprintf(str, "Segmentation fault");
- break;
- case SIGTERM:
- sprintf(str, "Terminated");
- break;
- case SIGQUIT:
- sprintf(str, "Quit");
- break;
- case SIGKILL:
- sprintf(str, "Killed");
- break;
- case SIGHUP:
- sprintf(str, "Hangup");
- break;
- case SIGUSR1:
- sprintf(str, "User defined signal 1");
- break;
- case SIGUSR2:
- sprintf(str, "User defined signal 2");
- break;
- case SIGINT:
- sprintf(str, "Interrupt");
- break;
- case SIGPIPE:
- sprintf(str, "Broken pipe");
- break;
- default:
- sprintf(str, "Killed by unhandled signal %d",sig);
- break;
- }
- return str;
- }
- /**
- * Prints "Segmentation fault", etc.
- */
- static void handle_status(int ret_code) {
- if (WIFSIGNALED(ret_code)) {
- int sig = WTERMSIG(ret_code);
- if (sig == SIGINT || sig == SIGPIPE) return;
- char * str = _strsignal(sig);
- if (shell_interactive == 1) {
- fprintf(stderr, "%s\n", str);
- } else if (shell_interactive == 2) {
- fprintf(stderr, "%s: line %d: %s\n", current_file, current_line, str);
- }
- }
- }
- int wait_for_child(int pgid, char * name) {
- int waitee = (shell_interactive == 1 && !is_subshell) ? -pgid : pgid;
- int outpid;
- int ret_code = 0;
- int e;
- do {
- outpid = waitpid(waitee, &ret_code, WSTOPPED);
- e = errno;
- if (WIFSTOPPED(ret_code)) {
- suspended_pgid = pgid;
- if (name) {
- hashmap_set(job_hash, (void*)(intptr_t)pgid, strdup(name));
- }
- fprintf(stderr, "[%d] Stopped\t\t%s\n", pgid, (char*)hashmap_get(job_hash, (void*)(intptr_t)pgid));
- break;
- } else {
- suspended_pgid = 0;
- if (hashmap_has(job_hash, (void*)(intptr_t)pgid)) {
- hashmap_remove(job_hash, (void*)(intptr_t)pgid);
- }
- }
- } while (outpid != -1 || (outpid == -1 && e != ECHILD));
- reset_pgrp();
- handle_status(ret_code);
- return WEXITSTATUS(ret_code);
- }
- int shell_exec(char * buffer, size_t size, FILE * file, char ** out_buffer) {
- *out_buffer = NULL;
- /* Read previous history entries */
- if (buffer[0] == '!') {
- int x = atoi((char *)((uintptr_t)buffer + 1));
- if (x > 0 && x <= rline_history_count) {
- buffer = rline_history_get(x - 1);
- } else {
- fprintf(stderr, "esh: !%d: event not found\n", x);
- return 0;
- }
- }
- char * argv[1024];
- int tokenid = 0;
- char quoted = 0;
- char backtick = 0;
- char buffer_[512] = {0};
- int collected = 0;
- int force_collected = 0;
- list_t * args = list_create();
- int have_star = 0;
- while (1) {
- char * p = buffer;
- while (*p) {
- switch (*p) {
- case '$':
- if (quoted == '\'') {
- goto _just_add;
- } else {
- if (backtick) {
- goto _just_add;
- }
- p++;
- char var[100];
- int coll = 0;
- if (*p == '{') {
- p++;
- while (*p != '}' && *p != '\0' && (coll < 100)) {
- var[coll] = *p;
- coll++;
- var[coll] = '\0';
- p++;
- }
- if (*p == '}') {
- p++;
- }
- } else {
- while (*p != '\0' && (variable_char(*p) || (coll == 0 && variable_char_first(*p))) && (coll < 100)) {
- var[coll] = *p;
- coll++;
- var[coll] = '\0';
- if (coll == 1 && (isdigit(*p) || *p == '?' || *p == '$' || *p == '#')) {
- p++;
- break; /* Don't let these keep going */
- }
- p++;
- }
- }
- /* Special cases */
- char *c = NULL;
- char tmp[128];
- if (!strcmp(var, "?")) {
- sprintf(tmp,"%d",last_ret);
- c = tmp;
- } else if (!strcmp(var, "$")) {
- sprintf(tmp,"%d",getpid());
- c = tmp;
- } else if (!strcmp(var, "#")) {
- sprintf(tmp,"%d",shell_argc ? shell_argc-1 : 0);
- c = tmp;
- } else if (is_number(var)) {
- int a = atoi(var);
- if (a >= 0 && a < shell_argc) {
- c = shell_argv[a];
- }
- } else if (!strcmp(var, "RANDOM")) {
- sprintf(tmp,"%d",rand()%32768); /* sure, modulo is bad for range restriction, shut up */
- c = tmp;
- } else {
- c = getenv(var);
- }
- if (c) {
- backtick = 0;
- for (int i = 0; i < (int)strlen(c); ++i) {
- if (c[i] == ' ' && !quoted) {
- /* If we are not quoted and we reach a space, it signals a new argument */
- if (collected || force_collected) {
- buffer_[collected] = '\0';
- add_argument(args, buffer_);
- buffer_[0] = '\0';
- have_star = 0;
- collected = 0;
- force_collected = 0;
- }
- } else {
- buffer_[collected] = c[i];
- collected++;
- }
- }
- buffer_[collected] = '\0';
- }
- continue;
- }
- case '~':
- if (quoted || collected || backtick) {
- goto _just_add;
- } else {
- if (p[1] == 0 || p[1] == '/' || p[1] == '\n' || p[1] == ' ' || p[1] == ';') {
- char * c = getenv("HOME");
- if (!c) {
- goto _just_add;
- }
- for (int i = 0; i < (int)strlen(c); ++i) {
- buffer_[collected] = c[i];
- collected++;
- }
- buffer_[collected] = '\0';
- goto _next;
- } else {
- goto _just_add;
- }
- }
- break;
- case '\"':
- force_collected = 1;
- if (quoted == '\"') {
- if (backtick) {
- goto _just_add;
- }
- quoted = 0;
- goto _next;
- } else if (!quoted) {
- if (backtick) {
- goto _just_add;
- }
- quoted = *p;
- goto _next;
- }
- goto _just_add;
- case '\'':
- force_collected = 1;
- if (quoted == '\'') {
- if (backtick) {
- goto _just_add;
- }
- quoted = 0;
- goto _next;
- } else if (!quoted) {
- if (backtick) {
- goto _just_add;
- }
- quoted = *p;
- goto _next;
- }
- goto _just_add;
- case '*':
- if (quoted) {
- goto _just_add;
- }
- if (backtick) {
- goto _just_add;
- }
- if (have_star) {
- goto _just_add; /* TODO multiple globs */
- }
- have_star = 1;
- collected += sprintf(&buffer_[collected], STAR_TOKEN);
- goto _next;
- case '\\':
- if (quoted == '\'') {
- goto _just_add;
- }
- if (backtick) {
- goto _just_add;
- }
- backtick = 1;
- goto _next;
- case ' ':
- if (backtick) {
- goto _just_add;
- }
- if (!quoted) {
- goto _new_arg;
- }
- goto _just_add;
- case '\n':
- if (!quoted) {
- goto _done;
- }
- goto _just_add;
- case '|':
- if (!quoted && !backtick) {
- if (collected || force_collected) {
- add_argument(args, buffer_);
- }
- force_collected = 0;
- collected = sprintf(buffer_, "%s", PIPE_TOKEN);
- goto _new_arg;
- }
- goto _just_add;
- case '>':
- if (!quoted && !backtick) {
- if (collected || force_collected) {
- add_argument(args, buffer_);
- }
- force_collected = 0;
- collected = sprintf(buffer_, "%s", WRITE_TOKEN);
- goto _new_arg;
- }
- goto _just_add;
- case ';':
- if (!quoted && !backtick) {
- *out_buffer = ++p;
- goto _done;
- }
- case '#':
- if (!quoted && !backtick) {
- goto _done; /* Support comments; must not be part of an existing arg */
- }
- goto _just_add;
- default:
- if (backtick) {
- buffer_[collected] = '\\';
- collected++;
- buffer_[collected] = '\0';
- }
- _just_add:
- backtick = 0;
- buffer_[collected] = *p;
- collected++;
- buffer_[collected] = '\0';
- goto _next;
- }
- _new_arg:
- backtick = 0;
- if (collected || force_collected) {
- add_argument(args, buffer_);
- buffer_[0] = '\0';
- have_star = 0;
- collected = 0;
- force_collected = 0;
- }
- _next:
- p++;
- }
- _done:
- if (quoted || backtick) {
- backtick = 0;
- if (shell_interactive == 1) {
- read_entry_continued(buffer);
- rline_history_append_line(buffer);
- continue;
- } else if (shell_interactive == 2) {
- fgets(buffer, size, file);
- continue;
- } else {
- fprintf(stderr, "Syntax error: Unterminated quoted string.\n");
- return 127;
- }
- }
- if (collected || force_collected) {
- add_argument(args, buffer_);
- break;
- }
- break;
- }
- int cmdi = 0;
- char ** arg_starts[100] = { &argv[0], NULL };
- char * output_files[100] = { NULL };
- int file_args[100] = {0};
- int argcs[100] = {0};
- int next_is_file = 0;
- list_t * extra_env = list_create();
- int i = 0;
- foreach(node, args) {
- char * c = node->value;
- if (next_is_file) {
- if (next_is_file == 1 && !strcmp(c, WRITE_TOKEN)) {
- next_is_file = 2;
- file_args[cmdi] = O_WRONLY | O_CREAT | O_APPEND;
- continue;
- }
- output_files[cmdi] = c;
- continue;
- }
- if (!strcmp(c, WRITE_TOKEN)) {
- next_is_file = 1;
- file_args[cmdi] = O_WRONLY | O_CREAT | O_TRUNC;
- continue;
- }
- if (!strcmp(c, PIPE_TOKEN)) {
- if (arg_starts[cmdi] == &argv[i]) {
- fprintf(stderr, "Syntax error: Unexpected pipe token\n");
- return 2;
- }
- argv[i] = 0;
- i++;
- cmdi++;
- arg_starts[cmdi] = &argv[i];
- continue;
- }
- if (i == 0 && strstr(c,"=")) {
- list_insert(extra_env, c);
- continue;
- }
- char * glob = strstr(c, STAR_TOKEN);
- if (glob) {
- /* Globbing */
- glob[0] = '\0';
- glob[1] = '\0';
- char * before = c;
- char * after = &glob[8];
- char * dir = NULL;
- int has_before = !!strlen(before);
- int has_after = !!strlen(after);
- if (1) {
- /* read current directory, add all */
- DIR * dirp;
- char * prepend = "";
- if (has_before) {
- dir = strrchr(before,'/');
- if (!dir) {
- dirp = opendir(".");
- } else if (dir == before) {
- dirp = opendir("/");
- prepend = before;
- before++;
- } else {
- *dir = '\0';
- dirp = opendir(before);
- prepend = before;
- before = dir+1;
- }
- } else {
- dirp = opendir(".");
- }
- int before_i = i;
- if (dirp) {
- struct dirent * ent = readdir(dirp);
- while (ent != NULL) {
- if (ent->d_name[0] != '.' || (dir ? (dir[1] == '.') : (before && before[0] == '.'))) {
- char * s = malloc(sizeof(char) * (strlen(ent->d_name) + 1));
- memcpy(s, ent->d_name, strlen(ent->d_name) + 1);
- char * t = s;
- if (has_before) {
- if (strstr(s,before) != s) {
- free(s);
- goto _nope;
- }
- t = &s[strlen(before)];
- }
- if (has_after) {
- if (strlen(t) >= strlen(after)) {
- if (!strcmp(after,&t[strlen(t)-strlen(after)])) {
- char * out = malloc(strlen(s) + 2 + strlen(prepend));
- sprintf(out,"%s%s%s", prepend, !!*prepend ? "/" : "", s);
- argv[i] = out;
- i++;
- argcs[cmdi]++;
- }
- }
- } else {
- char * out = malloc(strlen(s) + 2 + strlen(prepend));
- sprintf(out,"%s%s%s", prepend, !!*prepend ? "/" : "", s);
- argv[i] = out;
- i++;
- argcs[cmdi]++;
- }
- free(s);
- }
- _nope:
- ent = readdir(dirp);
- }
- closedir(dirp);
- }
- if (before_i == i) {
- /* no matches */
- glob[0] = '*';
- if (dir) {
- *dir = '/';
- }
- memmove(&glob[1], after, strlen(after)+1);
- argv[i] = c;
- i++;
- argcs[cmdi]++;
- } else {
- free(c);
- }
- }
- } else {
- argv[i] = c;
- i++;
- argcs[cmdi]++;
- }
- }
- argv[i] = NULL;
- if (i == 0) {
- add_environment(extra_env);
- list_free(extra_env);
- free(extra_env);
- list_destroy(args);
- free(args);
- return -1;
- }
- if (!*arg_starts[cmdi]) {
- fprintf(stderr, "Syntax error: Unexpected end of input\n");
- list_free(extra_env);
- free(extra_env);
- list_destroy(args);
- free(args);
- return 2;
- }
- tokenid = i;
- unsigned int child_pid;
- int last_child;
- int nowait = (!strcmp(argv[tokenid-1],"&"));
- if (nowait) {
- argv[tokenid-1] = NULL;
- }
- int pgid = 0;
- if (cmdi > 0) {
- int last_output[2];
- pipe(last_output);
- struct semaphore s = create_semaphore();
- child_pid = fork();
- if (!child_pid) {
- set_pgid(0);
- if (!nowait) set_pgrp(getpid());
- raise_semaphore(s);
- is_subshell = 1;
- dup2(last_output[1], STDOUT_FILENO);
- close(last_output[0]);
- add_environment(extra_env);
- run_cmd(arg_starts[0]);
- }
- wait_semaphore(s);
- pgid = child_pid;
- for (int j = 1; j < cmdi; ++j) {
- int tmp_out[2];
- pipe(tmp_out);
- if (!fork()) {
- is_subshell = 1;
- set_pgid(pgid);
- dup2(tmp_out[1], STDOUT_FILENO);
- dup2(last_output[0], STDIN_FILENO);
- close(tmp_out[0]);
- close(last_output[1]);
- add_environment(extra_env);
- run_cmd(arg_starts[j]);
- }
- close(last_output[0]);
- close(last_output[1]);
- last_output[0] = tmp_out[0];
- last_output[1] = tmp_out[1];
- }
- last_child = fork();
- if (!last_child) {
- is_subshell = 1;
- set_pgid(pgid);
- if (output_files[cmdi]) {
- int fd = open(output_files[cmdi], file_args[cmdi], 0666);
- if (fd < 0) {
- fprintf(stderr, "sh: %s: %s\n", output_files[cmdi], strerror(errno));
- return -1;
- } else {
- dup2(fd, STDOUT_FILENO);
- }
- }
- dup2(last_output[0], STDIN_FILENO);
- close(last_output[1]);
- add_environment(extra_env);
- run_cmd(arg_starts[cmdi]);
- }
- close(last_output[0]);
- close(last_output[1]);
- /* Now execute the last piece and wait on all of them */
- } else {
- shell_command_t func = shell_find(*arg_starts[0]);
- if (func) {
- return func(argcs[0], arg_starts[0]);
- } else {
- struct semaphore s = create_semaphore();
- child_pid = fork();
- if (!child_pid) {
- set_pgid(0);
- if (!nowait) set_pgrp(getpid());
- raise_semaphore(s);
- is_subshell = 1;
- if (output_files[cmdi]) {
- int fd = open(output_files[cmdi], file_args[cmdi], 0666);
- if (fd < 0) {
- fprintf(stderr, "sh: %s: %s\n", output_files[cmdi], strerror(errno));
- return -1;
- } else {
- dup2(fd, STDOUT_FILENO);
- }
- }
- add_environment(extra_env);
- run_cmd(arg_starts[0]);
- }
- wait_semaphore(s);
- pgid = child_pid;
- last_child = child_pid;
- }
- }
- if (nowait) {
- if (shell_interactive == 1) {
- fprintf(stderr, "[%d] %s\n", pgid, arg_starts[0][0]);
- hashmap_set(job_hash, (void*)(intptr_t)pgid, strdup(arg_starts[0][0]));
- } else {
- hashmap_set(job_hash, (void*)(intptr_t)last_child, strdup(arg_starts[0][0]));
- }
- list_free(extra_env);
- free(extra_env);
- list_destroy(args);
- free(args);
- return 0;
- }
- int ret = wait_for_child(shell_interactive == 1 ? pgid : last_child, arg_starts[0][0]);
- list_free(extra_env);
- free(extra_env);
- list_destroy(args);
- free(args);
- return ret;
- }
- void add_path_contents(char * path) {
- DIR * dirp = opendir(path);
- if (!dirp) return; /* Failed to load directly */
- struct dirent * ent = readdir(dirp);
- while (ent != NULL) {
- if (ent->d_name[0] != '.') {
- char * s = malloc(sizeof(char) * (strlen(ent->d_name) + 1));
- memcpy(s, ent->d_name, strlen(ent->d_name) + 1);
- shell_install_command(s, NULL, NULL);
- }
- ent = readdir(dirp);
- }
- closedir(dirp);
- }
- struct command {
- char * string;
- void * func;
- char * desc;
- };
- static int comp_shell_commands(const void *p1, const void *p2) {
- return strcmp(((struct command *)p1)->string, ((struct command *)p2)->string);
- }
- void sort_commands() {
- struct command commands[SHELL_COMMANDS];
- for (int i = 0; i < shell_commands_len; ++i) {
- commands[i].string = shell_commands[i];
- commands[i].func = shell_pointers[i];
- commands[i].desc = shell_descript[i];
- }
- qsort(&commands, shell_commands_len, sizeof(struct command), comp_shell_commands);
- for (int i = 0; i < shell_commands_len; ++i) {
- shell_commands[i] = commands[i].string;
- shell_pointers[i] = commands[i].func;
- shell_descript[i] = commands[i].desc;
- }
- }
- void show_version(void) {
- printf("esh 1.10.0\n");
- }
- void show_usage(int argc, char * argv[]) {
- printf(
- "Esh: The Experimental Shell\n"
- "\n"
- "usage: %s [-lha] [path]\n"
- "\n"
- " -c \033[4mcmd\033[0m \033[3mparse and execute cmd\033[0m\n"
- //-c cmd \033[...
- " -R \033[3mdisable experimental line editor\033[0m\n"
- " -v \033[3mshow version information\033[0m\n"
- " -? \033[3mshow this help text\033[0m\n"
- "\n", argv[0]);
- }
- void add_path(void) {
- char * envvar = getenv("PATH");
- if (!envvar) {
- add_path_contents("/bin");
- return;
- }
- char * tmp = strdup(envvar);
- do {
- char * end = strstr(tmp,":");
- if (end) {
- *end = '\0';
- end++;
- }
- add_path_contents(tmp);
- tmp = end;
- } while (tmp);
- free(tmp);
- }
- int run_script(FILE * f) {
- current_line = 1;
- while (!feof(f)) {
- char buf[LINE_LEN] = {0};
- fgets(buf, LINE_LEN, f);
- int ret;
- char * out = NULL;
- char * b = buf;
- do {
- ret = shell_exec(b, LINE_LEN, f, &out);
- b = out;
- } while (b);
- current_line++;
- if (ret >= 0) last_ret = ret;
- }
- fclose(f);
- return last_ret;
- }
- void source_eshrc(void) {
- char * home = getenv("HOME");
- if (!home) return;
- char tmp[512];
- sprintf(tmp, "%s/.eshrc", home);
- FILE * f = fopen(tmp, "r");
- if (!f) return;
- current_file = tmp;
- run_script(f);
- }
- int main(int argc, char ** argv) {
- pid = getpid();
- signal(SIGINT, sig_break_loop);
- srand(getpid() + time(0));
- job_hash = hashmap_create_int(10);
- getuser();
- gethost();
- install_commands();
- if (argc > 1) {
- int c;
- while ((c = getopt(argc, argv, "Rc:v?")) != -1) {
- switch (c) {
- case 'R':
- experimental_rline = 0;
- break;
- case 'c':
- shell_interactive = 0;
- {
- char * out = NULL;
- do {
- last_ret = shell_exec(optarg, strlen(optarg), NULL, &out);
- optarg = out;
- } while (optarg);
- }
- return (last_ret == -1) ? 0 : last_ret;
- case 'v':
- show_version();
- return 0;
- case '?':
- show_usage(argc, argv);
- return 0;
- }
- }
- }
- if (optind < argc) {
- shell_interactive = 2;
- FILE * f = fopen(argv[optind],"r");
- if (!f) {
- fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno));
- return 1;
- }
- shell_argc = argc - 1;
- shell_argv = &argv[optind];
- current_file = argv[optind];
- return run_script(f);
- }
- shell_interactive = 1;
- my_pgid = getpgid(0);
- signal(SIGTTOU, SIG_IGN);
- signal(SIGTTIN, SIG_IGN);
- source_eshrc();
- add_path();
- sort_commands();
- while (1) {
- char buffer[LINE_LEN] = {0};
- list_t * keys = hashmap_keys(job_hash);
- foreach(node, keys) {
- int pid = (intptr_t)node->value;
- int status = 0;
- if (waitpid(-pid, &status, WNOHANG) > 0) {
- char * desc = "Done";
- if (WTERMSIG(status) != 0) {
- desc = _strsignal(WTERMSIG(status));
- }
- fprintf(stderr, "[%d] %s\t\t%s\n", pid, desc, (char*)hashmap_get(job_hash, (void*)(intptr_t)pid));
- if (hashmap_has(job_hash, (void*)(intptr_t)pid)) {
- hashmap_remove(job_hash, (void*)(intptr_t)pid);
- }
- }
- }
- list_free(keys);
- free(keys);
- read_entry(buffer);
- char * history = malloc(strlen(buffer) + 1);
- memcpy(history, buffer, strlen(buffer) + 1);
- if (buffer[0] != ' ' && buffer[0] != '\n' && buffer[0] != '!') {
- rline_history_insert(history);
- } else {
- free(history);
- }
- int ret;
- char * out = NULL;
- char * b = buffer;
- do {
- ret = shell_exec(b, LINE_LEN, stdin, &out);
- b = out;
- } while (b);
- if (ret >= 0) last_ret = ret;
- rline_scroll = 0;
- }
- return 0;
- }
- /*
- * cd [path]
- */
- uint32_t shell_cmd_cd(int argc, char * argv[]) {
- if (argc > 1) {
- if (chdir(argv[1])) {
- goto cd_error;
- } /* else success */
- } else /* argc < 2 */ {
- char * home = getenv("HOME");
- if (home) {
- if (chdir(home)) {
- goto cd_error;
- }
- } else {
- char home_path[512];
- sprintf(home_path, "/home/%s", username);
- if (chdir(home_path)) {
- goto cd_error;
- }
- }
- }
- return 0;
- cd_error:
- fprintf(stderr, "%s: could not cd '%s': %s\n", argv[0], argv[1], strerror(errno));
- return 1;
- }
- /*
- * history
- */
- uint32_t shell_cmd_history(int argc, char * argv[]) {
- for (int i = 0; i < rline_history_count; ++i) {
- printf("%d\t%s\n", i + 1, rline_history_get(i));
- }
- return 0;
- }
- uint32_t shell_cmd_export(int argc, char * argv[]) {
- if (argc > 1) {
- putenv(argv[1]);
- }
- return 0;
- }
- uint32_t shell_cmd_exit(int argc, char * argv[]) {
- if (argc > 1) {
- exit(atoi(argv[1]));
- } else {
- exit(0);
- }
- return -1;
- }
- uint32_t shell_cmd_help(int argc, char * argv[]) {
- show_version();
- printf("\nThis shell is not POSIX-compliant, please be careful.\n\n");
- printf("Built-in commands:\n");
- /* First, determine max width of command names */
- unsigned int max_len = 0;
- for (int i = 0; i < shell_commands_len; ++i) {
- if (!shell_descript[i]) continue;
- if (strlen(shell_commands[i]) > max_len) {
- max_len = strlen(shell_commands[i]);
- }
- }
- /* Then print the commands their help text */
- for (int i = 0; i < shell_commands_len; ++i) {
- if (!shell_descript[i]) continue;
- printf(" %-*s - %s\n", max_len + 1, shell_commands[i], shell_descript[i]);
- }
- return 0;
- }
- uint32_t shell_cmd_if(int argc, char * argv[]) {
- char ** if_args = &argv[1];
- char ** then_args = NULL;
- char ** else_args = NULL;
- for (int i = 2; i < argc; ++i) {
- if (!strcmp(argv[i],"then")) {
- argv[i] = NULL;
- then_args = &argv[i+1];
- } else if (!strcmp(argv[i],"else")) {
- argv[i] = NULL;
- else_args = &argv[i+1];
- }
- }
- if (!then_args) {
- fprintf(stderr, "%s: syntax error: expected 'then' clause\n", argv[0]);
- return 1;
- }
- if (else_args && else_args < then_args) {
- fprintf(stderr, "%s: syntax error: 'else' clause before 'then' clase\n", argv[0]);
- return 1;
- }
- pid_t child_pid = fork();
- if (!child_pid) {
- set_pgid(0);
- set_pgrp(getpid());
- is_subshell = 1;
- run_cmd(if_args);
- }
- int pid, ret_code = 0;
- do {
- pid = waitpid(child_pid, &ret_code, 0);
- } while (pid != -1 || (pid == -1 && errno != ECHILD));
- handle_status(ret_code);
- if (WEXITSTATUS(ret_code) == 0) {
- shell_command_t func = shell_find(*then_args);
- if (func) {
- int argc = 0;
- while (then_args[argc]) {
- argc++;
- }
- return func(argc, then_args);
- } else {
- child_pid = fork();
- if (!child_pid) {
- set_pgid(0);
- set_pgrp(getpid());
- is_subshell = 1;
- run_cmd(then_args);
- }
- do {
- pid = waitpid(child_pid, &ret_code, 0);
- } while (pid != -1 || (pid == -1 && errno != ECHILD));
- reset_pgrp();
- handle_status(ret_code);
- return WEXITSTATUS(ret_code);
- }
- } else if (else_args) {
- shell_command_t func = shell_find(*else_args);
- if (func) {
- int argc = 0;
- while (else_args[argc]) {
- argc++;
- }
- return func(argc, else_args);
- } else {
- child_pid = fork();
- if (!child_pid) {
- set_pgid(0);
- set_pgrp(getpid());
- is_subshell = 1;
- run_cmd(else_args);
- }
- do {
- pid = waitpid(child_pid, &ret_code, 0);
- } while (pid != -1 || (pid == -1 && errno != ECHILD));
- handle_status(ret_code);
- return WEXITSTATUS(ret_code);
- }
- }
- reset_pgrp();
- return 0;
- }
- uint32_t shell_cmd_while(int argc, char * argv[]) {
- char ** while_args = &argv[1];
- char ** do_args = NULL;
- for (int i = 2; i < argc; ++i) {
- if (!strcmp(argv[i],"do")) {
- argv[i] = NULL;
- do_args = &argv[i+1];
- }
- }
- if (!do_args) {
- fprintf(stderr, "%s: syntax error: expected 'do' clause\n", argv[0]);
- return 1;
- }
- break_while = 0;
- reset_pgrp();
- do {
- pid_t child_pid = fork();
- if (!child_pid) {
- set_pgid(0);
- set_pgrp(getpid());
- is_subshell = 1;
- run_cmd(while_args);
- }
- int pid, ret_code = 0;
- do {
- pid = waitpid(child_pid, &ret_code, 0);
- } while (pid != -1 || (pid == -1 && errno != ECHILD));
- handle_status(ret_code);
- if (WEXITSTATUS(ret_code) == 0) {
- child_pid = fork();
- if (!child_pid) {
- set_pgid(0);
- set_pgrp(getpid());
- is_subshell = 1;
- run_cmd(do_args);
- }
- do {
- pid = waitpid(child_pid, &ret_code, 0);
- if (pid != -1 && WIFSIGNALED(ret_code)) {
- int sig = WTERMSIG(ret_code);
- if (sig == SIGINT) return 127; /* break */
- }
- } while (pid != -1 || (pid == -1 && errno != ECHILD));
- } else {
- reset_pgrp();
- return WEXITSTATUS(ret_code);
- }
- } while (!break_while);
- return 127;
- }
- uint32_t shell_cmd_export_cmd(int argc, char * argv[]) {
- if (argc < 3) {
- fprintf(stderr, "%s: syntax error: not enough arguments\n", argv[0]);
- return 1;
- }
- int pipe_fds[2];
- pipe(pipe_fds);
- pid_t child_pid = fork();
- if (!child_pid) {
- set_pgid(0);
- set_pgrp(getpid());
- is_subshell = 1;
- dup2(pipe_fds[1], STDOUT_FILENO);
- close(pipe_fds[0]);
- run_cmd(&argv[2]);
- }
- close(pipe_fds[1]);
- char buf[1024];
- size_t accum = 0;
- do {
- int r = read(pipe_fds[0], buf+accum, 1023-accum);
- if (r == 0) break;
- if (r < 0) {
- return -r;
- }
- accum += r;
- } while (accum < 1023);
- waitpid(child_pid, NULL, 0);
- reset_pgrp();
- buf[accum] = '\0';
- if (accum && buf[accum-1] == '\n') {
- buf[accum-1] = '\0';
- }
- setenv(argv[1], buf, 1);
- return 0;
- }
- uint32_t shell_cmd_empty(int argc, char * argv[]) {
- for (int i = 1; i < argc; i++) {
- if (argv[i] && *argv[i]) return 1;
- }
- return 0;
- }
- uint32_t shell_cmd_equals(int argc, char * argv[]) {
- if (argc < 3) return 1;
- return !!strcmp(argv[1], argv[2]);
- }
- uint32_t shell_cmd_return(int argc, char * argv[]) {
- if (argc < 2) return 0;
- return atoi(argv[1]);
- }
- uint32_t shell_cmd_source(int argc, char * argv[]) {
- if (argc < 2) return 0;
- FILE * f = fopen(argv[1], "r");
- if (!f) {
- fprintf(stderr, "%s: %s: %s\n", argv[0], argv[1], strerror(errno));
- return 1;
- }
- current_file = argv[1];
- return run_script(f);
- }
- uint32_t shell_cmd_exec(int argc, char * argv[]) {
- if (argc < 2) return 1;
- return execvp(argv[1], &argv[1]);
- }
- uint32_t shell_cmd_not(int argc, char * argv[]) {
- if (argc < 2) {
- fprintf(stderr, "%s: expected command argument\n", argv[0]);
- return 1;
- }
- int ret_code = 0;
- pid_t child_pid = fork();
- if (!child_pid) {
- set_pgid(0);
- set_pgrp(getpid());
- is_subshell = 1;
- run_cmd(&argv[1]);
- }
- do {
- pid = waitpid(child_pid, &ret_code, 0);
- } while (pid != -1 || (pid == -1 && errno != ECHILD));
- reset_pgrp();
- handle_status(ret_code);
- return !WEXITSTATUS(ret_code);
- }
- uint32_t shell_cmd_unset(int argc, char * argv[]) {
- if (argc < 2) {
- fprintf(stderr, "%s: expected command argument\n", argv[0]);
- return 1;
- }
- return unsetenv(argv[1]);
- }
- uint32_t shell_cmd_read(int argc, char * argv[]) {
- int raw = 0;
- int i = 1;
- char * var = "REPLY";
- if (i < argc && !strcmp(argv[i], "-r")) {
- raw = 1;
- i++;
- }
- if (i < argc) {
- var = argv[i];
- }
- char tmp[4096];
- fgets(tmp, 4096, stdin);
- if (*tmp && tmp[strlen(tmp)-1] == '\n') {
- tmp[strlen(tmp)-1] = '\0';
- }
- if (raw) {
- setenv(var, tmp, 1);
- return 0;
- }
- char tmp2[4096] = {0};
- char * out = tmp2;
- char * in = tmp;
- /* TODO: This needs to actually read more if a \ at the end of the line is found */
- while (*in) {
- if (*in == '\\') {
- in++;
- if (*in == '\n') {
- in++;
- }
- } else {
- *out = *in;
- out++;
- in++;
- }
- }
- setenv(var, tmp2, 1);
- return 0;
- }
- int get_available_job(int argc, char * argv[]) {
- if (argc < 2) {
- if (!suspended_pgid) {
- list_t * keys = hashmap_keys(job_hash);
- foreach(node, keys) {
- suspended_pgid = (intptr_t)node->value;
- break;
- }
- list_free(keys);
- free(keys);
- if (!suspended_pgid) {
- return 0;
- }
- }
- return suspended_pgid;
- } else {
- return atoi(argv[1]);
- }
- }
- uint32_t shell_cmd_fg(int argc, char * argv[]) {
- int pid = get_available_job(argc,argv);
- if (!pid || !hashmap_has(job_hash, (void*)(intptr_t)pid)) {
- fprintf(stderr, "no current job\n");
- return 1;
- }
- set_pgrp(pid);
- if (kill(-pid, SIGCONT) < 0) {
- fprintf(stderr, "no current job / bad pid\n");
- if (hashmap_has(job_hash, (void*)(intptr_t)pid)) {
- hashmap_remove(job_hash, (void*)(intptr_t)pid);
- }
- return 1;
- }
- return wait_for_child(pid, NULL);
- }
- uint32_t shell_cmd_bg(int argc, char * argv[]) {
- int pid = get_available_job(argc,argv);
- if (!pid || !hashmap_has(job_hash, (void*)(intptr_t)pid)) {
- fprintf(stderr, "no current job\n");
- return 1;
- }
- if (kill(-pid, SIGCONT) < 0) {
- fprintf(stderr, "no current job / bad pid\n");
- if (hashmap_has(job_hash, (void*)(intptr_t)pid)) {
- hashmap_remove(job_hash, (void*)(intptr_t)pid);
- }
- return 1;
- }
- fprintf(stderr, "[%d] %s\n", pid, (char*)hashmap_get(job_hash, (void*)(intptr_t)pid));
- return 0;
- }
- uint32_t shell_cmd_jobs(int argc, char * argv[]) {
- list_t * keys = hashmap_keys(job_hash);
- foreach(node, keys) {
- int pid = (intptr_t)node->value;
- char * c = hashmap_get(job_hash, (void*)(intptr_t)pid);
- fprintf(stdout, "%5d %s\n", pid, c);
- }
- list_free(keys);
- free(keys);
- return 0;
- }
- uint32_t shell_cmd_rehash(int argc, char * argv[]) {
- /* PATH commands are malloc'd */
- for (int i = 0; i < shell_commands_len; ++i) {
- if (!shell_pointers[i]) {
- free(shell_commands[i]);
- }
- }
- /* Clear array */
- shell_commands_len = 0;
- /* Reset capacity */
- SHELL_COMMANDS = 64;
- /* Free existing */
- free(shell_commands);
- free(shell_pointers);
- free(shell_descript);
- /* Reallocate + install builtins */
- install_commands();
- /* Reload PATH */
- add_path();
- return 0;
- }
- void install_commands() {
- shell_commands = malloc(sizeof(char *) * SHELL_COMMANDS);
- shell_pointers = malloc(sizeof(shell_command_t) * SHELL_COMMANDS);
- shell_descript = malloc(sizeof(char *) * SHELL_COMMANDS);
- shell_install_command("cd", shell_cmd_cd, "change directory");
- shell_install_command("exit", shell_cmd_exit, "exit the shell");
- shell_install_command("export", shell_cmd_export, "set environment variables: export VAR=value");
- shell_install_command("help", shell_cmd_help, "display this help text");
- shell_install_command("history", shell_cmd_history, "list command history");
- shell_install_command("if", shell_cmd_if, "if ... then ... [else ...]");
- shell_install_command("while", shell_cmd_while, "while ... do ...");
- shell_install_command("empty?", shell_cmd_empty, "empty? args...");
- shell_install_command("equals?", shell_cmd_equals, "equals? arg1 arg2");
- shell_install_command("return", shell_cmd_return, "return status code");
- shell_install_command("export-cmd", shell_cmd_export_cmd, "set variable to result of command: export-cmd VAR command...");
- shell_install_command("source", shell_cmd_source, "run a shell script in the context of this shell");
- shell_install_command(".", shell_cmd_source, "alias for 'source'");
- shell_install_command("exec", shell_cmd_exec, "replace shell (or subshell) with command");
- shell_install_command("not", shell_cmd_not, "invert status of command");
- shell_install_command("unset", shell_cmd_unset, "unset variable");
- shell_install_command("read", shell_cmd_read, "read user input");
- shell_install_command("fg", shell_cmd_fg, "resume a suspended job");
- shell_install_command("jobs", shell_cmd_jobs, "list stopped jobs");
- shell_install_command("bg", shell_cmd_bg, "restart suspended job in the background");
- shell_install_command("rehash", shell_cmd_rehash, "reset shell command memory");
- }
|