main.cpp 45 KB

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