main.cpp 50 KB

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