Shell.cpp 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681
  1. /*
  2. * Copyright (c) 2020, The SerenityOS developers.
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include "Shell.h"
  27. #include "Execution.h"
  28. #include <AK/FileSystemPath.h>
  29. #include <AK/Function.h>
  30. #include <AK/ScopeGuard.h>
  31. #include <AK/StringBuilder.h>
  32. #include <LibCore/ArgsParser.h>
  33. #include <LibCore/DirIterator.h>
  34. #include <LibCore/ElapsedTimer.h>
  35. #include <LibCore/Event.h>
  36. #include <LibCore/EventLoop.h>
  37. #include <LibCore/File.h>
  38. #include <LibLine/Editor.h>
  39. #include <errno.h>
  40. #include <fcntl.h>
  41. #include <pwd.h>
  42. #include <signal.h>
  43. #include <stdio.h>
  44. #include <stdlib.h>
  45. #include <string.h>
  46. #include <sys/mman.h>
  47. #include <sys/stat.h>
  48. #include <sys/utsname.h>
  49. #include <sys/wait.h>
  50. #include <termios.h>
  51. #include <unistd.h>
  52. // FIXME: We do not expand variables inside strings
  53. // if we want to be more sh-like, we should do that some day
  54. static constexpr bool HighlightVariablesInsideStrings = false;
  55. static bool s_disable_hyperlinks = false;
  56. extern Line::Editor editor;
  57. //#define SH_DEBUG
  58. void Shell::print_path(const String& path)
  59. {
  60. if (s_disable_hyperlinks) {
  61. printf("%s", path.characters());
  62. return;
  63. }
  64. printf("\033]8;;file://%s%s\033\\%s\033]8;;\033\\", hostname, path.characters(), path.characters());
  65. }
  66. String Shell::prompt() const
  67. {
  68. auto build_prompt = [&]() -> String {
  69. auto* ps1 = getenv("PROMPT");
  70. if (!ps1) {
  71. if (uid == 0)
  72. return "# ";
  73. StringBuilder builder;
  74. builder.appendf("\033]0;%s@%s:%s\007", username.characters(), hostname, cwd.characters());
  75. builder.appendf("\033[31;1m%s\033[0m@\033[37;1m%s\033[0m:\033[32;1m%s\033[0m$> ", username.characters(), hostname, cwd.characters());
  76. return builder.to_string();
  77. }
  78. StringBuilder builder;
  79. for (char* ptr = ps1; *ptr; ++ptr) {
  80. if (*ptr == '\\') {
  81. ++ptr;
  82. if (!*ptr)
  83. break;
  84. switch (*ptr) {
  85. case 'X':
  86. builder.append("\033]0;");
  87. break;
  88. case 'a':
  89. builder.append(0x07);
  90. break;
  91. case 'e':
  92. builder.append(0x1b);
  93. break;
  94. case 'u':
  95. builder.append(username);
  96. break;
  97. case 'h':
  98. builder.append(hostname);
  99. break;
  100. case 'w': {
  101. String home_path = getenv("HOME");
  102. if (cwd.starts_with(home_path)) {
  103. builder.append('~');
  104. builder.append(cwd.substring_view(home_path.length(), cwd.length() - home_path.length()));
  105. } else {
  106. builder.append(cwd);
  107. }
  108. break;
  109. }
  110. case 'p':
  111. builder.append(uid == 0 ? '#' : '$');
  112. break;
  113. }
  114. continue;
  115. }
  116. builder.append(*ptr);
  117. }
  118. return builder.to_string();
  119. };
  120. auto the_prompt = build_prompt();
  121. auto prompt_length = editor.actual_rendered_string_length(the_prompt);
  122. if (m_should_continue != ExitCodeOrContinuationRequest::Nothing) {
  123. const auto format_string = "\033[34m%.*-s\033[m";
  124. switch (m_should_continue) {
  125. case ExitCodeOrContinuationRequest::Pipe:
  126. return String::format(format_string, prompt_length, "pipe> ");
  127. case ExitCodeOrContinuationRequest::DoubleQuotedString:
  128. return String::format(format_string, prompt_length, "dquote> ");
  129. case ExitCodeOrContinuationRequest::SingleQuotedString:
  130. return String::format(format_string, prompt_length, "squote> ");
  131. default:
  132. break;
  133. }
  134. }
  135. return the_prompt;
  136. }
  137. int Shell::builtin_bg(int argc, const char** argv)
  138. {
  139. int job_id = -1;
  140. Core::ArgsParser parser;
  141. parser.add_positional_argument(job_id, "Job id to run in background", "job_id", Core::ArgsParser::Required::No);
  142. if (!parser.parse(argc, const_cast<char**>(argv), false))
  143. return 1;
  144. if (job_id == -1)
  145. job_id = jobs.size() - 1;
  146. Job* job = nullptr;
  147. for (auto& entry : jobs) {
  148. if (entry.value->job_id() == (u64)job_id) {
  149. job = entry.value;
  150. break;
  151. }
  152. }
  153. if (!job) {
  154. if (job_id == -1) {
  155. printf("bg: no current job\n");
  156. } else {
  157. printf("bg: job with id %d not found\n", job_id);
  158. }
  159. return 1;
  160. }
  161. job->set_running_in_background(true);
  162. dbg() << "Resuming " << job->pid() << " (" << job->cmd() << ")";
  163. printf("Resuming job %llu - %s\n", job->job_id(), job->cmd().characters());
  164. if (killpg(job->pgid(), SIGCONT) < 0) {
  165. perror("killpg");
  166. return 1;
  167. }
  168. return 0;
  169. }
  170. int Shell::builtin_cd(int argc, const char** argv)
  171. {
  172. const char* arg_path = nullptr;
  173. Core::ArgsParser parser;
  174. parser.add_positional_argument(arg_path, "Path to change to", "path", Core::ArgsParser::Required::No);
  175. if (!parser.parse(argc, const_cast<char**>(argv), false))
  176. return 1;
  177. String new_path;
  178. if (!arg_path) {
  179. new_path = home;
  180. if (cd_history.is_empty() || cd_history.last() != home)
  181. cd_history.enqueue(home);
  182. } else {
  183. if (cd_history.is_empty() || cd_history.last() != arg_path)
  184. cd_history.enqueue(arg_path);
  185. if (strcmp(argv[1], "-") == 0) {
  186. char* oldpwd = getenv("OLDPWD");
  187. if (oldpwd == nullptr)
  188. return 1;
  189. new_path = oldpwd;
  190. } else if (arg_path[0] == '/') {
  191. new_path = argv[1];
  192. } else {
  193. StringBuilder builder;
  194. builder.append(cwd);
  195. builder.append('/');
  196. builder.append(arg_path);
  197. new_path = builder.to_string();
  198. }
  199. }
  200. FileSystemPath canonical_path(new_path);
  201. if (!canonical_path.is_valid()) {
  202. printf("FileSystemPath failed to canonicalize '%s'\n", new_path.characters());
  203. return 1;
  204. }
  205. const char* path = canonical_path.string().characters();
  206. struct stat st;
  207. int rc = stat(path, &st);
  208. if (rc < 0) {
  209. printf("stat(%s) failed: %s\n", path, strerror(errno));
  210. return 1;
  211. }
  212. if (!S_ISDIR(st.st_mode)) {
  213. printf("Not a directory: %s\n", path);
  214. return 1;
  215. }
  216. rc = chdir(path);
  217. if (rc < 0) {
  218. printf("chdir(%s) failed: %s\n", path, strerror(errno));
  219. return 1;
  220. }
  221. setenv("OLDPWD", cwd.characters(), 1);
  222. cwd = canonical_path.string();
  223. setenv("PWD", cwd.characters(), 1);
  224. return 0;
  225. }
  226. int Shell::builtin_cdh(int argc, const char** argv)
  227. {
  228. int index = -1;
  229. Core::ArgsParser parser;
  230. parser.add_positional_argument(index, "Index of the cd history entry (leave out for a list)", "index", Core::ArgsParser::Required::No);
  231. if (!parser.parse(argc, const_cast<char**>(argv), false))
  232. return 1;
  233. if (index == -1) {
  234. if (cd_history.size() == 0) {
  235. printf("cdh: no history available\n");
  236. return 0;
  237. }
  238. for (int i = cd_history.size() - 1; i >= 0; --i)
  239. printf("%lu: %s\n", cd_history.size() - i, cd_history.at(i).characters());
  240. return 0;
  241. }
  242. if (index < 1 || (size_t)index > cd_history.size()) {
  243. fprintf(stderr, "cdh: history index out of bounds: %d not in (0, %zu)\n", index, cd_history.size());
  244. return 1;
  245. }
  246. const char* path = cd_history.at(cd_history.size() - index).characters();
  247. const char* cd_args[] = { "cd", path };
  248. return Shell::builtin_cd(2, cd_args);
  249. }
  250. int Shell::builtin_dirs(int argc, const char** argv)
  251. {
  252. // The first directory in the stack is ALWAYS the current directory
  253. directory_stack.at(0) = cwd.characters();
  254. if (argc == 1) {
  255. for (auto& directory : directory_stack) {
  256. print_path(directory);
  257. fputc(' ', stdout);
  258. }
  259. printf("\n");
  260. return 0;
  261. }
  262. bool clear = false;
  263. bool print = false;
  264. bool number_when_printing = false;
  265. Vector<const char*> paths;
  266. Core::ArgsParser parser;
  267. parser.add_option(clear, "Clear the directory stack", "clear", 'c');
  268. parser.add_option(print, "Print directory entries", "print", 'p');
  269. parser.add_option(number_when_printing, "Number the directories in the stack when printing", "number", 'v');
  270. parser.add_positional_argument(paths, "Extra paths to put on the stack", "paths", Core::ArgsParser::Required::No);
  271. if (!parser.parse(argc, const_cast<char**>(argv), false))
  272. return 1;
  273. // -v implies -p
  274. print = print || number_when_printing;
  275. if (clear) {
  276. for (size_t i = 1; i < directory_stack.size(); i++)
  277. directory_stack.remove(i);
  278. }
  279. for (auto& path : paths)
  280. directory_stack.append(path);
  281. if (print) {
  282. auto idx = 0;
  283. for (auto& directory : directory_stack) {
  284. if (number_when_printing)
  285. printf("%d ", idx++);
  286. print_path(directory);
  287. fputc('\n', stdout);
  288. }
  289. }
  290. return 0;
  291. }
  292. int Shell::builtin_exit(int, const char**)
  293. {
  294. printf("Good-bye!\n");
  295. exit(0);
  296. return 0;
  297. }
  298. int Shell::builtin_export(int argc, const char** argv)
  299. {
  300. Vector<const char*> vars;
  301. Core::ArgsParser parser;
  302. parser.add_positional_argument(vars, "List of variable[=value]'s", "values", Core::ArgsParser::Required::No);
  303. if (!parser.parse(argc, const_cast<char**>(argv), false))
  304. return 1;
  305. if (vars.size() == 0) {
  306. for (int i = 0; environ[i]; ++i)
  307. puts(environ[i]);
  308. return 0;
  309. }
  310. int return_value = 0;
  311. for (auto& value : vars) {
  312. auto parts = String { value }.split_limit('=', 2);
  313. if (parts.size() == 1) {
  314. parts.append("");
  315. }
  316. int setenv_return = setenv(parts[0].characters(), parts[1].characters(), 1);
  317. if (setenv_return != 0) {
  318. perror("setenv");
  319. return_value = 1;
  320. break;
  321. }
  322. if (parts[0] == "PATH")
  323. cache_path();
  324. }
  325. return return_value;
  326. }
  327. int Shell::builtin_fg(int argc, const char** argv)
  328. {
  329. int job_id = -1;
  330. Core::ArgsParser parser;
  331. parser.add_positional_argument(job_id, "Job id to bring to foreground", "job_id", Core::ArgsParser::Required::No);
  332. if (!parser.parse(argc, const_cast<char**>(argv), false))
  333. return 1;
  334. if (job_id == -1)
  335. job_id = jobs.size() - 1;
  336. Job* job = nullptr;
  337. for (auto& entry : jobs) {
  338. if (entry.value->job_id() == (u64)job_id) {
  339. job = entry.value;
  340. break;
  341. }
  342. }
  343. if (!job) {
  344. if (job_id == -1) {
  345. printf("fg: no current job\n");
  346. } else {
  347. printf("fg: job with id %d not found\n", job_id);
  348. }
  349. return 1;
  350. }
  351. job->set_running_in_background(false);
  352. dbg() << "Resuming " << job->pid() << " (" << job->cmd() << ")";
  353. printf("Resuming job %llu - %s\n", job->job_id(), job->cmd().characters());
  354. if (killpg(job->pgid(), SIGCONT) < 0) {
  355. perror("killpg");
  356. return 1;
  357. }
  358. int return_value = 0;
  359. auto current_pid = getpid();
  360. auto current_pgid = getpgid(current_pid);
  361. setpgid(job->pid(), job->pgid());
  362. tcsetpgrp(0, job->pgid());
  363. SpawnedProcess process { job->cmd(), job->pid() };
  364. do {
  365. if (wait_for_pid(process, true, return_value) == IterationDecision::Break)
  366. break;
  367. } while (errno == EINTR);
  368. setpgid(current_pid, current_pgid);
  369. tcsetpgrp(0, current_pgid);
  370. return return_value;
  371. }
  372. int Shell::builtin_history(int, const char**)
  373. {
  374. for (size_t i = 0; i < editor.history().size(); ++i) {
  375. printf("%6zu %s\n", i, editor.history()[i].characters());
  376. }
  377. return 0;
  378. }
  379. int Shell::builtin_jobs(int argc, const char** argv)
  380. {
  381. bool list = false, show_pid = false;
  382. Core::ArgsParser parser;
  383. parser.add_option(list, "List all information about jobs", "list", 'l');
  384. parser.add_option(show_pid, "Display the PID of the jobs", "pid", 'p');
  385. if (!parser.parse(argc, const_cast<char**>(argv), false))
  386. return 1;
  387. enum {
  388. Basic,
  389. OnlyPID,
  390. ListAll,
  391. } mode { Basic };
  392. if (show_pid)
  393. mode = OnlyPID;
  394. if (list)
  395. mode = ListAll;
  396. for (auto& job : jobs) {
  397. auto pid = job.value->pid();
  398. int wstatus;
  399. auto rc = waitpid(pid, &wstatus, WNOHANG);
  400. if (rc == -1) {
  401. perror("waitpid");
  402. return 1;
  403. }
  404. auto status = "running";
  405. if (rc != 0) {
  406. if (WIFEXITED(wstatus))
  407. status = "exited";
  408. if (WIFSTOPPED(wstatus))
  409. status = "stopped";
  410. if (WIFSIGNALED(wstatus))
  411. status = "signaled";
  412. }
  413. char background_indicator = '-';
  414. if (job.value->is_running_in_background())
  415. background_indicator = '+';
  416. switch (mode) {
  417. case Basic:
  418. printf("[%llu] %c %s %s\n", job.value->job_id(), background_indicator, status, job.value->cmd().characters());
  419. break;
  420. case OnlyPID:
  421. printf("[%llu] %c %d %s %s\n", job.value->job_id(), background_indicator, pid, status, job.value->cmd().characters());
  422. break;
  423. case ListAll:
  424. printf("[%llu] %c %d %d %s %s\n", job.value->job_id(), background_indicator, pid, job.value->pgid(), status, job.value->cmd().characters());
  425. break;
  426. }
  427. }
  428. return 0;
  429. }
  430. int Shell::builtin_popd(int argc, const char** argv)
  431. {
  432. if (directory_stack.size() <= 1) {
  433. fprintf(stderr, "Shell: popd: directory stack empty\n");
  434. return 1;
  435. }
  436. bool should_not_switch = false;
  437. String path = directory_stack.take_last();
  438. Core::ArgsParser parser;
  439. parser.add_option(should_not_switch, "Do not switch dirs", "no-switch", 'n');
  440. if (!parser.parse(argc, const_cast<char**>(argv), false))
  441. return 1;
  442. bool should_switch = !should_not_switch;
  443. // When no arguments are given, popd removes the top directory from the stack and performs a cd to the new top directory.
  444. if (argc == 1) {
  445. int rc = chdir(path.characters());
  446. if (rc < 0) {
  447. fprintf(stderr, "chdir(%s) failed: %s\n", path.characters(), strerror(errno));
  448. return 1;
  449. }
  450. cwd = path;
  451. return 0;
  452. }
  453. FileSystemPath canonical_path(path.characters());
  454. if (!canonical_path.is_valid()) {
  455. fprintf(stderr, "FileSystemPath failed to canonicalize '%s'\n", path.characters());
  456. return 1;
  457. }
  458. const char* real_path = canonical_path.string().characters();
  459. struct stat st;
  460. int rc = stat(real_path, &st);
  461. if (rc < 0) {
  462. fprintf(stderr, "stat(%s) failed: %s\n", real_path, strerror(errno));
  463. return 1;
  464. }
  465. if (!S_ISDIR(st.st_mode)) {
  466. fprintf(stderr, "Not a directory: %s\n", real_path);
  467. return 1;
  468. }
  469. if (should_switch) {
  470. int rc = chdir(real_path);
  471. if (rc < 0) {
  472. fprintf(stderr, "chdir(%s) failed: %s\n", real_path, strerror(errno));
  473. return 1;
  474. }
  475. cwd = canonical_path.string();
  476. }
  477. return 0;
  478. }
  479. int Shell::builtin_pushd(int argc, const char** argv)
  480. {
  481. StringBuilder path_builder;
  482. bool should_switch = true;
  483. // From the BASH reference manual: https://www.gnu.org/software/bash/manual/html_node/Directory-Stack-Builtins.html
  484. // With no arguments, pushd exchanges the top two directories and makes the new top the current directory.
  485. if (argc == 1) {
  486. if (directory_stack.size() < 2) {
  487. fprintf(stderr, "pushd: no other directory\n");
  488. return 1;
  489. }
  490. String dir1 = directory_stack.take_first();
  491. String dir2 = directory_stack.take_first();
  492. directory_stack.insert(0, dir2);
  493. directory_stack.insert(1, dir1);
  494. int rc = chdir(dir2.characters());
  495. if (rc < 0) {
  496. fprintf(stderr, "chdir(%s) failed: %s\n", dir2.characters(), strerror(errno));
  497. return 1;
  498. }
  499. cwd = dir2;
  500. return 0;
  501. }
  502. // Let's assume the user's typed in 'pushd <dir>'
  503. if (argc == 2) {
  504. directory_stack.append(cwd.characters());
  505. if (argv[1][0] == '/') {
  506. path_builder.append(argv[1]);
  507. } else {
  508. path_builder.appendf("%s/%s", cwd.characters(), argv[1]);
  509. }
  510. } else if (argc == 3) {
  511. directory_stack.append(cwd.characters());
  512. for (int i = 1; i < argc; i++) {
  513. const char* arg = argv[i];
  514. if (arg[0] != '-') {
  515. if (arg[0] == '/') {
  516. path_builder.append(arg);
  517. } else
  518. path_builder.appendf("%s/%s", cwd.characters(), arg);
  519. }
  520. if (!strcmp(arg, "-n"))
  521. should_switch = false;
  522. }
  523. }
  524. FileSystemPath canonical_path(path_builder.to_string());
  525. if (!canonical_path.is_valid()) {
  526. fprintf(stderr, "FileSystemPath failed to canonicalize '%s'\n", path_builder.to_string().characters());
  527. return 1;
  528. }
  529. const char* real_path = canonical_path.string().characters();
  530. struct stat st;
  531. int rc = stat(real_path, &st);
  532. if (rc < 0) {
  533. fprintf(stderr, "stat(%s) failed: %s\n", real_path, strerror(errno));
  534. return 1;
  535. }
  536. if (!S_ISDIR(st.st_mode)) {
  537. fprintf(stderr, "Not a directory: %s\n", real_path);
  538. return 1;
  539. }
  540. if (should_switch) {
  541. int rc = chdir(real_path);
  542. if (rc < 0) {
  543. fprintf(stderr, "chdir(%s) failed: %s\n", real_path, strerror(errno));
  544. return 1;
  545. }
  546. cwd = canonical_path.string();
  547. }
  548. return 0;
  549. }
  550. int Shell::builtin_pwd(int, const char**)
  551. {
  552. print_path(cwd);
  553. fputc('\n', stdout);
  554. return 0;
  555. }
  556. int Shell::builtin_time(int argc, const char** argv)
  557. {
  558. Vector<const char*> args;
  559. Core::ArgsParser parser;
  560. parser.add_positional_argument(args, "Command to execute with arguments", "command", Core::ArgsParser::Required::Yes);
  561. if (!parser.parse(argc, const_cast<char**>(argv), false))
  562. return 1;
  563. StringBuilder builder;
  564. builder.join(' ', args);
  565. Core::ElapsedTimer timer;
  566. timer.start();
  567. auto exit_code = run_command(builder.string_view());
  568. if (!exit_code.has_value()) {
  569. printf("Shell: Incomplete command: %s\n", builder.to_string().characters());
  570. exit_code = 1;
  571. }
  572. printf("Time: %d ms\n", timer.elapsed());
  573. return exit_code.value();
  574. }
  575. int Shell::builtin_umask(int argc, const char** argv)
  576. {
  577. const char* mask_text = nullptr;
  578. Core::ArgsParser parser;
  579. parser.add_positional_argument(mask_text, "New mask (omit to get current mask)", "octal-mask", Core::ArgsParser::Required::No);
  580. if (!parser.parse(argc, const_cast<char**>(argv), false))
  581. return 1;
  582. if (!mask_text) {
  583. mode_t old_mask = umask(0);
  584. printf("%#o\n", old_mask);
  585. umask(old_mask);
  586. return 0;
  587. }
  588. unsigned mask;
  589. int matches = sscanf(mask_text, "%o", &mask);
  590. if (matches == 1) {
  591. umask(mask);
  592. return 0;
  593. }
  594. return 0;
  595. }
  596. int Shell::builtin_unset(int argc, const char** argv)
  597. {
  598. Vector<const char*> vars;
  599. Core::ArgsParser parser;
  600. parser.add_positional_argument(vars, "List of variables", "variables", Core::ArgsParser::Required::Yes);
  601. if (!parser.parse(argc, const_cast<char**>(argv), false))
  602. return 1;
  603. for (auto& value : vars)
  604. unsetenv(value);
  605. return 0;
  606. }
  607. bool Shell::run_builtin(int argc, const char** argv, int& retval)
  608. {
  609. if (argc == 0)
  610. return false;
  611. StringView name { argv[0] };
  612. #define __ENUMERATE_SHELL_BUILTIN(builtin) \
  613. if (name == #builtin) { \
  614. retval = builtin_##builtin(argc, argv); \
  615. return true; \
  616. }
  617. ENUMERATE_SHELL_BUILTINS();
  618. #undef __ENUMERATE_SHELL_BUILTIN
  619. return false;
  620. }
  621. String Shell::expand_tilde(const String& expression)
  622. {
  623. ASSERT(expression.starts_with('~'));
  624. StringBuilder login_name;
  625. size_t first_slash_index = expression.length();
  626. for (size_t i = 1; i < expression.length(); ++i) {
  627. if (expression[i] == '/') {
  628. first_slash_index = i;
  629. break;
  630. }
  631. login_name.append(expression[i]);
  632. }
  633. StringBuilder path;
  634. for (size_t i = first_slash_index; i < expression.length(); ++i)
  635. path.append(expression[i]);
  636. if (login_name.is_empty()) {
  637. const char* home = getenv("HOME");
  638. if (!home) {
  639. auto passwd = getpwuid(getuid());
  640. ASSERT(passwd && passwd->pw_dir);
  641. return String::format("%s/%s", passwd->pw_dir, path.to_string().characters());
  642. }
  643. return String::format("%s/%s", home, path.to_string().characters());
  644. }
  645. auto passwd = getpwnam(login_name.to_string().characters());
  646. if (!passwd)
  647. return expression;
  648. ASSERT(passwd->pw_dir);
  649. return String::format("%s/%s", passwd->pw_dir, path.to_string().characters());
  650. }
  651. bool Shell::is_glob(const StringView& s)
  652. {
  653. for (size_t i = 0; i < s.length(); i++) {
  654. char c = s.characters_without_null_termination()[i];
  655. if (c == '*' || c == '?')
  656. return true;
  657. }
  658. return false;
  659. }
  660. Vector<StringView> Shell::split_path(const StringView& path)
  661. {
  662. Vector<StringView> parts;
  663. size_t substart = 0;
  664. for (size_t i = 0; i < path.length(); i++) {
  665. char ch = path.characters_without_null_termination()[i];
  666. if (ch != '/')
  667. continue;
  668. size_t sublen = i - substart;
  669. if (sublen != 0)
  670. parts.append(path.substring_view(substart, sublen));
  671. parts.append(path.substring_view(i, 1));
  672. substart = i + 1;
  673. }
  674. size_t taillen = path.length() - substart;
  675. if (taillen != 0)
  676. parts.append(path.substring_view(substart, taillen));
  677. return parts;
  678. }
  679. Vector<String> Shell::expand_globs(const StringView& path, const StringView& base)
  680. {
  681. auto parts = split_path(path);
  682. StringBuilder builder;
  683. builder.append(base);
  684. Vector<String> res;
  685. for (size_t i = 0; i < parts.size(); ++i) {
  686. auto& part = parts[i];
  687. if (!is_glob(part)) {
  688. builder.append(part);
  689. continue;
  690. }
  691. // Found a glob.
  692. String new_base = builder.to_string();
  693. StringView new_base_v = new_base;
  694. if (new_base_v.is_empty())
  695. new_base_v = ".";
  696. Core::DirIterator di(new_base_v, Core::DirIterator::SkipParentAndBaseDir);
  697. if (di.has_error()) {
  698. return res;
  699. }
  700. while (di.has_next()) {
  701. String name = di.next_path();
  702. // Dotfiles have to be explicitly requested
  703. if (name[0] == '.' && part[0] != '.')
  704. continue;
  705. if (name.matches(part, CaseSensitivity::CaseSensitive)) {
  706. StringBuilder nested_base;
  707. nested_base.append(new_base);
  708. nested_base.append(name);
  709. StringView remaining_path = path.substring_view_starting_after_substring(part);
  710. Vector<String> nested_res = expand_globs(remaining_path, nested_base.to_string());
  711. for (auto& s : nested_res)
  712. res.append(s);
  713. }
  714. }
  715. return res;
  716. }
  717. // Found no globs.
  718. String new_path = builder.to_string();
  719. if (access(new_path.characters(), F_OK) == 0)
  720. res.append(new_path);
  721. return res;
  722. }
  723. Vector<String> Shell::expand_parameters(const StringView& param) const
  724. {
  725. if (!param.starts_with('$'))
  726. return { param };
  727. String variable_name = String(param.substring_view(1, param.length() - 1));
  728. if (variable_name == "?")
  729. return { String::number(last_return_code) };
  730. else if (variable_name == "$")
  731. return { String::number(getpid()) };
  732. char* env_value = getenv(variable_name.characters());
  733. if (env_value == nullptr)
  734. return { "" };
  735. Vector<String> res;
  736. String str_env_value = String(env_value);
  737. const auto& split_text = str_env_value.split_view(' ');
  738. for (auto& part : split_text)
  739. res.append(part);
  740. return res;
  741. }
  742. Vector<String> Shell::process_arguments(const Vector<Token>& args)
  743. {
  744. Vector<String> argv_string;
  745. for (auto& arg : args) {
  746. if (arg.type == Token::Comment)
  747. continue;
  748. // This will return the text passed in if it wasn't a variable
  749. // This lets us just loop over its values
  750. auto expanded_parameters = expand_parameters(arg.text);
  751. for (auto& exp_arg : expanded_parameters) {
  752. if (exp_arg.starts_with('~'))
  753. exp_arg = expand_tilde(exp_arg);
  754. auto expanded_globs = expand_globs(exp_arg, "");
  755. for (auto& path : expanded_globs)
  756. argv_string.append(path);
  757. if (expanded_globs.is_empty())
  758. argv_string.append(exp_arg);
  759. }
  760. }
  761. return argv_string;
  762. }
  763. ContinuationRequest Shell::is_complete(const Vector<Command>& commands)
  764. {
  765. // check if the last command ends with a pipe, or an unterminated string
  766. auto& last_command = commands.last();
  767. auto& subcommands = last_command.subcommands;
  768. if (subcommands.size() == 0)
  769. return ContinuationRequest::Nothing;
  770. auto& last_subcommand = subcommands.last();
  771. if (!last_subcommand.redirections.find([](auto& redirection) { return redirection.type == Redirection::Pipe; }).is_end())
  772. return ContinuationRequest::Pipe;
  773. if (!last_subcommand.args.find([](auto& token) { return token.type == Token::UnterminatedSingleQuoted; }).is_end())
  774. return ContinuationRequest::SingleQuotedString;
  775. if (!last_subcommand.args.find([](auto& token) { return token.type == Token::UnterminatedDoubleQuoted; }).is_end())
  776. return ContinuationRequest::DoubleQuotedString;
  777. return ContinuationRequest::Nothing;
  778. }
  779. IterationDecision Shell::wait_for_pid(const Shell::SpawnedProcess& process, bool is_first_command_in_chain, int& return_value)
  780. {
  781. // Disable signal handler for the first command, as we actively wait for it
  782. sighandler_t chld_handler = nullptr;
  783. if (is_first_command_in_chain) {
  784. chld_handler = signal(SIGCHLD, nullptr);
  785. dbg() << "Waiting for " << process.name;
  786. }
  787. int wstatus = 0;
  788. int rc = waitpid(process.pid, &wstatus, WSTOPPED);
  789. auto errno_save = errno;
  790. if (is_first_command_in_chain) {
  791. signal(SIGCHLD, chld_handler);
  792. dbg() << process.name << " is probably dead now (" << rc << ", " << strerror(errno_save) << ") -> exited " << WIFEXITED(wstatus) << " stopped " << WIFSTOPPED(wstatus);
  793. }
  794. errno = errno_save;
  795. if (rc < 0 && errno != EINTR) {
  796. if (errno != ECHILD)
  797. perror("waitpid");
  798. return IterationDecision::Break;
  799. }
  800. const Job* job = nullptr;
  801. u64 job_id = 0;
  802. auto maybe_job = jobs.get(process.pid);
  803. if (maybe_job.has_value()) {
  804. job = maybe_job.value();
  805. job_id = job->job_id();
  806. }
  807. if (WIFEXITED(wstatus)) {
  808. if (WEXITSTATUS(wstatus) != 0)
  809. dbg() << "Shell: " << process.name << ":" << process.pid << " exited with status " << WEXITSTATUS(wstatus);
  810. if (is_first_command_in_chain)
  811. return_value = WEXITSTATUS(wstatus);
  812. if (job) {
  813. auto* mutable_job = const_cast<Job*>(job);
  814. mutable_job->set_has_exit(return_value);
  815. Core::EventLoop::current().post_event(*this, make<Core::CustomEvent>(ChildExited, mutable_job));
  816. }
  817. return IterationDecision::Break;
  818. }
  819. if (WIFSTOPPED(wstatus)) {
  820. fprintf(stderr, "Shell: [%llu] %s(%d) %s\n", job_id, process.name.characters(), process.pid, strsignal(WSTOPSIG(wstatus)));
  821. return IterationDecision::Continue;
  822. }
  823. if (WIFSIGNALED(wstatus)) {
  824. printf("Shell: [%llu] %s(%d) exited due to signal '%s'\n", job_id, process.name.characters(), process.pid, strsignal(WTERMSIG(wstatus)));
  825. } else {
  826. printf("Shell: [%llu] %s(%d) exited abnormally\n", job_id, process.name.characters(), process.pid);
  827. }
  828. if (job) {
  829. auto* mutable_job = const_cast<Job*>(job);
  830. mutable_job->set_has_exit(-1);
  831. Core::EventLoop::current().post_event(*this, make<Core::CustomEvent>(ChildExited, mutable_job));
  832. }
  833. return IterationDecision::Break;
  834. }
  835. ExitCodeOrContinuationRequest Shell::run_command(const StringView& cmd)
  836. {
  837. if (cmd.is_empty())
  838. return 0;
  839. if (cmd.starts_with("#"))
  840. return 0;
  841. auto commands = Parser(cmd).parse();
  842. if (!commands.size())
  843. return 1;
  844. auto needs_more = is_complete(commands);
  845. if (needs_more != ExitCodeOrContinuationRequest::Nothing)
  846. return needs_more;
  847. #ifdef SH_DEBUG
  848. for (auto& command : commands) {
  849. for (size_t i = 0; i < command.subcommands.size(); ++i) {
  850. for (size_t j = 0; j < i; ++j)
  851. dbgprintf(" ");
  852. for (auto& arg : command.subcommands[i].args) {
  853. switch (arg.type) {
  854. case Token::Bare:
  855. dbgprintf("<%s> ", arg.text.characters());
  856. break;
  857. case Token::SingleQuoted:
  858. dbgprintf("'<%s>' ", arg.text.characters());
  859. break;
  860. case Token::DoubleQuoted:
  861. dbgprintf("\"<%s>\" ", arg.text.characters());
  862. break;
  863. case Token::UnterminatedSingleQuoted:
  864. dbgprintf("\'<%s> ", arg.text.characters());
  865. break;
  866. case Token::UnterminatedDoubleQuoted:
  867. dbgprintf("\"<%s> ", arg.text.characters());
  868. break;
  869. case Token::Special:
  870. dbgprintf("<%s> ", arg.text.characters());
  871. break;
  872. case Token::Comment:
  873. dbgprintf("<%s> ", arg.text.characters());
  874. break;
  875. }
  876. }
  877. dbgprintf("\n");
  878. for (auto& redirecton : command.subcommands[i].redirections) {
  879. for (size_t j = 0; j < i; ++j)
  880. dbgprintf(" ");
  881. dbgprintf(" ");
  882. switch (redirecton.type) {
  883. case Redirection::Pipe:
  884. dbgprintf("Pipe\n");
  885. break;
  886. case Redirection::FileRead:
  887. dbgprintf("fd:%d = FileRead: %s\n", redirecton.fd, redirecton.path.characters());
  888. break;
  889. case Redirection::FileWrite:
  890. dbgprintf("fd:%d = FileWrite: %s\n", redirecton.fd, redirecton.path.characters());
  891. break;
  892. case Redirection::FileWriteAppend:
  893. dbgprintf("fd:%d = FileWriteAppend: %s\n", redirecton.fd, redirecton.path.characters());
  894. break;
  895. default:
  896. break;
  897. }
  898. }
  899. }
  900. dbgprintf("\n");
  901. }
  902. #endif
  903. struct termios trm;
  904. tcgetattr(0, &trm);
  905. int return_value = 0;
  906. for (auto& command : commands) {
  907. if (command.subcommands.is_empty())
  908. continue;
  909. FileDescriptionCollector fds;
  910. for (size_t i = 0; i < command.subcommands.size(); ++i) {
  911. auto& subcommand = command.subcommands[i];
  912. for (auto& redirection : subcommand.redirections) {
  913. switch (redirection.type) {
  914. case Redirection::Pipe: {
  915. int pipefd[2];
  916. int rc = pipe(pipefd);
  917. if (rc < 0) {
  918. perror("pipe");
  919. return 1;
  920. }
  921. subcommand.rewirings.append({ STDOUT_FILENO, pipefd[1] });
  922. auto& next_command = command.subcommands[i + 1];
  923. next_command.rewirings.append({ STDIN_FILENO, pipefd[0] });
  924. fds.add(pipefd[0]);
  925. fds.add(pipefd[1]);
  926. break;
  927. }
  928. case Redirection::FileWriteAppend: {
  929. int fd = open(redirection.path.characters(), O_WRONLY | O_CREAT | O_APPEND, 0666);
  930. if (fd < 0) {
  931. perror("open");
  932. return 1;
  933. }
  934. subcommand.rewirings.append({ redirection.fd, fd });
  935. fds.add(fd);
  936. break;
  937. }
  938. case Redirection::FileWrite: {
  939. int fd = open(redirection.path.characters(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
  940. if (fd < 0) {
  941. perror("open");
  942. return 1;
  943. }
  944. subcommand.rewirings.append({ redirection.fd, fd });
  945. fds.add(fd);
  946. break;
  947. }
  948. case Redirection::FileRead: {
  949. int fd = open(redirection.path.characters(), O_RDONLY);
  950. if (fd < 0) {
  951. perror("open");
  952. return 1;
  953. }
  954. subcommand.rewirings.append({ redirection.fd, fd });
  955. fds.add(fd);
  956. break;
  957. }
  958. }
  959. }
  960. }
  961. Vector<SpawnedProcess> children;
  962. for (size_t i = 0; i < command.subcommands.size(); ++i) {
  963. auto& subcommand = command.subcommands[i];
  964. Vector<String> argv_string = process_arguments(subcommand.args);
  965. Vector<const char*> argv;
  966. argv.ensure_capacity(argv_string.size());
  967. for (const auto& s : argv_string) {
  968. argv.append(s.characters());
  969. }
  970. argv.append(nullptr);
  971. #ifdef SH_DEBUG
  972. for (auto& arg : argv) {
  973. dbgprintf("<%s> ", arg);
  974. }
  975. dbgprintf("\n");
  976. #endif
  977. int retval = 0;
  978. if (run_builtin(argv.size() - 1, argv.data(), retval))
  979. return retval;
  980. pid_t child = fork();
  981. if (!child) {
  982. setpgid(0, 0);
  983. tcsetpgrp(0, getpid());
  984. tcsetattr(0, TCSANOW, &default_termios);
  985. for (auto& rewiring : subcommand.rewirings) {
  986. #ifdef SH_DEBUG
  987. dbgprintf("in %s<%d>, dup2(%d, %d)\n", argv[0], getpid(), rewiring.rewire_fd, rewiring.fd);
  988. #endif
  989. int rc = dup2(rewiring.rewire_fd, rewiring.fd);
  990. if (rc < 0) {
  991. perror("dup2");
  992. return 1;
  993. }
  994. }
  995. fds.collect();
  996. int rc = execvp(argv[0], const_cast<char* const*>(argv.data()));
  997. if (rc < 0) {
  998. if (errno == ENOENT) {
  999. int shebang_fd = open(argv[0], O_RDONLY);
  1000. auto close_argv = ScopeGuard([shebang_fd]() { if (shebang_fd >= 0) close(shebang_fd); });
  1001. char shebang[256] {};
  1002. ssize_t num_read = -1;
  1003. if ((shebang_fd >= 0) && ((num_read = read(shebang_fd, shebang, sizeof(shebang))) >= 2) && (StringView(shebang).starts_with("#!"))) {
  1004. StringView shebang_path_view(&shebang[2], num_read - 2);
  1005. Optional<size_t> newline_pos = shebang_path_view.find_first_of("\n\r");
  1006. shebang[newline_pos.has_value() ? (newline_pos.value() + 2) : num_read] = '\0';
  1007. fprintf(stderr, "%s: Invalid interpreter \"%s\": %s\n", argv[0], &shebang[2], strerror(ENOENT));
  1008. } else
  1009. fprintf(stderr, "%s: Command not found.\n", argv[0]);
  1010. } else {
  1011. int saved_errno = errno;
  1012. struct stat st;
  1013. if (stat(argv[0], &st) == 0 && S_ISDIR(st.st_mode)) {
  1014. fprintf(stderr, "Shell: %s: Is a directory\n", argv[0]);
  1015. _exit(126);
  1016. }
  1017. fprintf(stderr, "execvp(%s): %s\n", argv[0], strerror(saved_errno));
  1018. }
  1019. _exit(126);
  1020. }
  1021. ASSERT_NOT_REACHED();
  1022. }
  1023. children.append({ argv[0], child });
  1024. StringBuilder cmd;
  1025. cmd.join(" ", argv_string);
  1026. auto job = make<Job>(child, (unsigned)child, cmd.build(), jobs.size());
  1027. jobs.set((u64)child, move(job));
  1028. }
  1029. #ifdef SH_DEBUG
  1030. dbgprintf("Closing fds in shell process:\n");
  1031. #endif
  1032. fds.collect();
  1033. #ifdef SH_DEBUG
  1034. dbgprintf("Now we gotta wait on children:\n");
  1035. for (auto& child : children)
  1036. dbgprintf(" %d (%s)\n", child.pid, child.name.characters());
  1037. #endif
  1038. for (size_t i = 0; i < children.size(); ++i) {
  1039. auto& child = children[i];
  1040. dbg() << "Now waiting for " << child.name << " (" << child.pid << ")";
  1041. do {
  1042. if (wait_for_pid(child, i != children.size() - 1, return_value) == IterationDecision::Break)
  1043. break;
  1044. } while (errno == EINTR);
  1045. }
  1046. }
  1047. last_return_code = return_value;
  1048. // FIXME: Should I really have to tcsetpgrp() after my child has exited?
  1049. // Is the terminal controlling pgrp really still the PGID of the dead process?
  1050. tcsetpgrp(0, getpid());
  1051. tcsetattr(0, TCSANOW, &trm);
  1052. return return_value;
  1053. }
  1054. String Shell::get_history_path()
  1055. {
  1056. StringBuilder builder;
  1057. builder.append(home);
  1058. builder.append("/.history");
  1059. return builder.to_string();
  1060. }
  1061. void Shell::load_history()
  1062. {
  1063. auto history_file = Core::File::construct(get_history_path());
  1064. if (!history_file->open(Core::IODevice::ReadOnly))
  1065. return;
  1066. while (history_file->can_read_line()) {
  1067. auto b = history_file->read_line(1024);
  1068. // skip the newline and terminating bytes
  1069. editor.add_to_history(String(reinterpret_cast<const char*>(b.data()), b.size() - 2));
  1070. }
  1071. }
  1072. void Shell::save_history()
  1073. {
  1074. auto file_or_error = Core::File::open(get_history_path(), Core::IODevice::WriteOnly, 0600);
  1075. if (file_or_error.is_error())
  1076. return;
  1077. auto& file = *file_or_error.value();
  1078. for (const auto& line : editor.history()) {
  1079. file.write(line);
  1080. file.write("\n");
  1081. }
  1082. }
  1083. String Shell::escape_token(const String& token)
  1084. {
  1085. StringBuilder builder;
  1086. for (auto c : token) {
  1087. switch (c) {
  1088. case '\'':
  1089. case '"':
  1090. case '$':
  1091. case '|':
  1092. case '>':
  1093. case '<':
  1094. case '&':
  1095. case '\\':
  1096. case ' ':
  1097. builder.append('\\');
  1098. break;
  1099. default:
  1100. break;
  1101. }
  1102. builder.append(c);
  1103. }
  1104. return builder.build();
  1105. }
  1106. String Shell::unescape_token(const String& token)
  1107. {
  1108. StringBuilder builder;
  1109. enum {
  1110. Free,
  1111. Escaped
  1112. } state { Free };
  1113. for (auto c : token) {
  1114. switch (state) {
  1115. case Escaped:
  1116. builder.append(c);
  1117. state = Free;
  1118. break;
  1119. case Free:
  1120. if (c == '\\')
  1121. state = Escaped;
  1122. else
  1123. builder.append(c);
  1124. break;
  1125. }
  1126. }
  1127. if (state == Escaped)
  1128. builder.append('\\');
  1129. return builder.build();
  1130. }
  1131. void Shell::cache_path()
  1132. {
  1133. if (!cached_path.is_empty())
  1134. cached_path.clear_with_capacity();
  1135. String path = getenv("PATH");
  1136. if (path.is_empty())
  1137. return;
  1138. auto directories = path.split(':');
  1139. for (const auto& directory : directories) {
  1140. Core::DirIterator programs(directory.characters(), Core::DirIterator::SkipDots);
  1141. while (programs.has_next()) {
  1142. auto program = programs.next_path();
  1143. String program_path = String::format("%s/%s", directory.characters(), program.characters());
  1144. if (access(program_path.characters(), X_OK) == 0)
  1145. cached_path.append(escape_token(program.characters()));
  1146. }
  1147. }
  1148. // add shell builtins to the cache
  1149. for (const auto& builtin_name : builtin_names)
  1150. cached_path.append(escape_token(builtin_name));
  1151. quick_sort(cached_path);
  1152. }
  1153. void Shell::highlight(Line::Editor&) const
  1154. {
  1155. StringBuilder builder;
  1156. if (m_should_continue == ExitCodeOrContinuationRequest::DoubleQuotedString) {
  1157. builder.append('"');
  1158. }
  1159. if (m_should_continue == ExitCodeOrContinuationRequest::SingleQuotedString) {
  1160. builder.append('\'');
  1161. }
  1162. builder.append(editor.line());
  1163. auto commands = Parser { builder.string_view() }.parse();
  1164. auto first_command { true };
  1165. for (auto& command : commands) {
  1166. for (auto& subcommand : command.subcommands) {
  1167. auto first { true };
  1168. for (auto& arg : subcommand.args) {
  1169. auto start = arg.end - arg.length;
  1170. if (arg.type == Token::Comment) {
  1171. editor.stylize({ start, arg.end }, { Line::Style::Foreground(150, 150, 150) }); // light gray
  1172. continue;
  1173. }
  1174. if (m_should_continue == ExitCodeOrContinuationRequest::DoubleQuotedString || m_should_continue == ExitCodeOrContinuationRequest::SingleQuotedString) {
  1175. if (!first_command)
  1176. --start;
  1177. --arg.end;
  1178. }
  1179. if (first) {
  1180. first = false;
  1181. // only treat this as a command name if we're not continuing strings
  1182. if (!first_command || (m_should_continue == ExitCodeOrContinuationRequest::Nothing || m_should_continue == ExitCodeOrContinuationRequest::Pipe)) {
  1183. editor.stylize({ start, arg.end }, { Line::Style::Bold });
  1184. first_command = false;
  1185. continue;
  1186. }
  1187. first_command = false;
  1188. }
  1189. if (arg.type == Token::SingleQuoted || arg.type == Token::UnterminatedSingleQuoted) {
  1190. editor.stylize({ start - 1, arg.end + (arg.type != Token::UnterminatedSingleQuoted) }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
  1191. continue;
  1192. }
  1193. if (arg.type == Token::DoubleQuoted || arg.type == Token::UnterminatedDoubleQuoted) {
  1194. editor.stylize({ start - 1, arg.end + (arg.type != Token::UnterminatedDoubleQuoted) }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow) });
  1195. if constexpr (HighlightVariablesInsideStrings)
  1196. goto highlight_variables;
  1197. else
  1198. continue;
  1199. }
  1200. if (is_glob(arg.text)) {
  1201. editor.stylize({ start, arg.end }, { Line::Style::Foreground(59, 142, 234) }); // bright-ish blue
  1202. continue;
  1203. }
  1204. if (arg.text.starts_with("--")) {
  1205. if (arg.length == 2)
  1206. editor.stylize({ start, arg.end }, { Line::Style::Foreground(Line::Style::XtermColor::Green) });
  1207. else
  1208. editor.stylize({ start, arg.end }, { Line::Style::Foreground(Line::Style::XtermColor::Cyan) });
  1209. } else if (arg.text.starts_with("-") && arg.length > 1) {
  1210. editor.stylize({ start, arg.end }, { Line::Style::Foreground(Line::Style::XtermColor::Cyan) });
  1211. }
  1212. highlight_variables:;
  1213. size_t slice_index = 0;
  1214. Optional<size_t> maybe_index;
  1215. while (slice_index < arg.length) {
  1216. maybe_index = arg.text.substring_view(slice_index, arg.length - slice_index).find_first_of('$');
  1217. if (!maybe_index.has_value())
  1218. break;
  1219. auto index = maybe_index.value() + 1;
  1220. auto end_index = index;
  1221. if (index >= arg.length)
  1222. break;
  1223. for (; end_index < arg.length; ++end_index) {
  1224. if (!is_word_character(arg.text[end_index]))
  1225. break;
  1226. }
  1227. editor.stylize({ index + start - 1, end_index + start }, { Line::Style::Foreground(214, 112, 214) });
  1228. slice_index = end_index + 1;
  1229. }
  1230. }
  1231. }
  1232. }
  1233. }
  1234. Vector<Line::CompletionSuggestion> Shell::complete(const Line::Editor& editor)
  1235. {
  1236. auto line = editor.line(editor.cursor());
  1237. Parser parser(line);
  1238. auto commands = parser.parse();
  1239. if (commands.size() == 0)
  1240. return {};
  1241. // get the last token and whether it's the first in its subcommand
  1242. String token;
  1243. bool is_first_in_subcommand = false;
  1244. auto& subcommand = commands.last().subcommands;
  1245. if (subcommand.size() == 0) {
  1246. // foo bar; <tab>
  1247. token = "";
  1248. is_first_in_subcommand = true;
  1249. } else {
  1250. auto& last_command = subcommand.last();
  1251. if (last_command.args.size() == 0) {
  1252. // foo bar | <tab>
  1253. token = "";
  1254. is_first_in_subcommand = true;
  1255. } else {
  1256. auto& args = last_command.args;
  1257. if (args.last().type == Token::Comment) // we cannot complete comments
  1258. return {};
  1259. is_first_in_subcommand = args.size() == 1;
  1260. token = last_command.args.last().text;
  1261. }
  1262. }
  1263. Vector<Line::CompletionSuggestion> suggestions;
  1264. bool should_suggest_only_executables = false;
  1265. if (is_first_in_subcommand) {
  1266. auto match = binary_search(cached_path.data(), cached_path.size(), token, [](const String& token, const String& program) -> int {
  1267. return strncmp(token.characters(), program.characters(), token.length());
  1268. });
  1269. if (match) {
  1270. String completion = *match;
  1271. editor.suggest(escape_token(token).length(), 0);
  1272. // Now that we have a program name starting with our token, we look at
  1273. // other program names starting with our token and cut off any mismatching
  1274. // characters.
  1275. int index = match - cached_path.data();
  1276. for (int i = index - 1; i >= 0 && cached_path[i].starts_with(token); --i) {
  1277. suggestions.append({ cached_path[i], " " });
  1278. }
  1279. for (size_t i = index + 1; i < cached_path.size() && cached_path[i].starts_with(token); ++i) {
  1280. suggestions.append({ cached_path[i], " " });
  1281. }
  1282. suggestions.append({ cached_path[index], " " });
  1283. return suggestions;
  1284. }
  1285. // fallthrough to suggesting local files, but make sure to only suggest executables
  1286. should_suggest_only_executables = true;
  1287. }
  1288. String path;
  1289. ssize_t last_slash = token.length() - 1;
  1290. while (last_slash >= 0 && token[last_slash] != '/')
  1291. --last_slash;
  1292. if (last_slash >= 0) {
  1293. // Split on the last slash. We'll use the first part as the directory
  1294. // to search and the second part as the token to complete.
  1295. path = token.substring(0, last_slash + 1);
  1296. if (path[0] != '/')
  1297. path = String::format("%s/%s", cwd.characters(), path.characters());
  1298. path = canonicalized_path(path);
  1299. token = token.substring(last_slash + 1, token.length() - last_slash - 1);
  1300. } else {
  1301. // We have no slashes, so the directory to search is the current
  1302. // directory and the token to complete is just the original token.
  1303. path = cwd;
  1304. }
  1305. // the invariant part of the token is actually just the last segment
  1306. // e. in `cd /foo/bar', 'bar' is the invariant
  1307. // since we are not suggesting anything starting with
  1308. // `/foo/', but rather just `bar...'
  1309. editor.suggest(escape_token(token).length(), 0);
  1310. // only suggest dot-files if path starts with a dot
  1311. Core::DirIterator files(path,
  1312. token.starts_with('.') ? Core::DirIterator::SkipParentAndBaseDir : Core::DirIterator::SkipDots);
  1313. while (files.has_next()) {
  1314. auto file = files.next_path();
  1315. if (file.starts_with(token)) {
  1316. struct stat program_status;
  1317. String file_path = String::format("%s/%s", path.characters(), file.characters());
  1318. int stat_error = stat(file_path.characters(), &program_status);
  1319. if (!stat_error) {
  1320. if (S_ISDIR(program_status.st_mode)) {
  1321. if (!should_suggest_only_executables)
  1322. suggestions.append({ escape_token(file), "/" });
  1323. } else {
  1324. suggestions.append({ escape_token(file), " " });
  1325. }
  1326. }
  1327. }
  1328. }
  1329. return suggestions;
  1330. }
  1331. void Shell::read_single_line()
  1332. {
  1333. auto line = editor.get_line(prompt());
  1334. if (m_should_break_current_command) {
  1335. m_complete_line_builder.clear();
  1336. m_should_continue = ContinuationRequest::Nothing;
  1337. m_should_break_current_command = false;
  1338. return;
  1339. }
  1340. if (line.is_empty())
  1341. return;
  1342. // FIXME: This might be a bit counter-intuitive, since we put nothing
  1343. // between the two lines, even though the user has pressed enter
  1344. // but since the LineEditor cannot yet handle literal newlines
  1345. // inside the text, we opt to do this the wrong way (for the time being)
  1346. m_complete_line_builder.append(line);
  1347. auto complete_or_exit_code = run_command(m_complete_line_builder.string_view());
  1348. m_should_continue = complete_or_exit_code.continuation;
  1349. if (!complete_or_exit_code.has_value())
  1350. return;
  1351. editor.add_to_history(m_complete_line_builder.build());
  1352. m_complete_line_builder.clear();
  1353. }
  1354. void Shell::custom_event(Core::CustomEvent& event)
  1355. {
  1356. if (event.custom_type() == ReadLine) {
  1357. read_single_line();
  1358. Core::EventLoop::current().post_event(*this, make<Core::CustomEvent>(ShellEventType::ReadLine));
  1359. return;
  1360. }
  1361. if (event.custom_type() == ChildExited) {
  1362. auto* job_ptr = event.data();
  1363. if (job_ptr) {
  1364. auto& job = *(Job*)job_ptr;
  1365. if (job.is_running_in_background())
  1366. fprintf(stderr, "Shell: Job %d(%s) exited\n", job.pid(), job.cmd().characters());
  1367. jobs.remove(job.pid());
  1368. }
  1369. return;
  1370. }
  1371. event.ignore();
  1372. }
  1373. Shell::Shell()
  1374. {
  1375. uid = getuid();
  1376. tcsetpgrp(0, getpgrp());
  1377. int rc = gethostname(hostname, Shell::HostNameSize);
  1378. if (rc < 0)
  1379. perror("gethostname");
  1380. rc = ttyname_r(0, ttyname, Shell::TTYNameSize);
  1381. if (rc < 0)
  1382. perror("ttyname_r");
  1383. {
  1384. auto* cwd = getcwd(nullptr, 0);
  1385. this->cwd = cwd;
  1386. setenv("PWD", cwd, 1);
  1387. free(cwd);
  1388. }
  1389. {
  1390. auto* pw = getpwuid(getuid());
  1391. if (pw) {
  1392. username = pw->pw_name;
  1393. home = pw->pw_dir;
  1394. setenv("HOME", pw->pw_dir, 1);
  1395. }
  1396. endpwent();
  1397. }
  1398. directory_stack.append(cwd);
  1399. load_history();
  1400. cache_path();
  1401. }
  1402. Shell::~Shell()
  1403. {
  1404. save_history();
  1405. }