xargs.cpp 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Function.h>
  7. #include <AK/StdLibExtras.h>
  8. #include <AK/StringBuilder.h>
  9. #include <LibCore/ArgsParser.h>
  10. #include <LibCore/System.h>
  11. #include <errno.h>
  12. #include <fcntl.h>
  13. #include <stdio.h>
  14. #include <stdlib.h>
  15. #include <string.h>
  16. #include <sys/wait.h>
  17. #include <unistd.h>
  18. bool run_command(Vector<char*>&& child_argv, bool verbose, bool is_stdin, int devnull_fd);
  19. enum Decision {
  20. Unget,
  21. Continue,
  22. Stop,
  23. };
  24. bool read_items(FILE* fp, char entry_separator, Function<Decision(StringView)>);
  25. class ParsedInitialArguments {
  26. public:
  27. ParsedInitialArguments(Vector<String>&, StringView placeholder);
  28. void for_each_joined_argument(StringView, Function<void(String const&)>) const;
  29. size_t size() const { return m_all_parts.size(); }
  30. private:
  31. Vector<Vector<StringView>> m_all_parts;
  32. };
  33. ErrorOr<int> serenity_main(Main::Arguments main_arguments)
  34. {
  35. TRY(Core::System::pledge("stdio rpath proc exec"));
  36. StringView placeholder;
  37. bool split_with_nulls = false;
  38. char const* specified_delimiter = "\n";
  39. Vector<String> arguments;
  40. bool verbose = false;
  41. char const* file_to_read = "-";
  42. int max_lines_for_one_command = 0;
  43. int max_bytes_for_one_command = ARG_MAX;
  44. Core::ArgsParser args_parser;
  45. args_parser.set_stop_on_first_non_option(true);
  46. args_parser.set_general_help("Read arguments from stdin and interpret them as command-line arguments for another program. See also: 'man xargs'.");
  47. args_parser.add_option(placeholder, "Placeholder string to be replaced in arguments", "replace", 'I', "placeholder");
  48. args_parser.add_option(split_with_nulls, "Split input items with the null character instead of newline", "null", '0');
  49. args_parser.add_option(specified_delimiter, "Split the input items with the specified character", "delimiter", 'd', "delim");
  50. args_parser.add_option(verbose, "Display each command before executing it", "verbose", 'v');
  51. args_parser.add_option(file_to_read, "Read arguments from the specified file instead of stdin", "arg-file", 'a', "file");
  52. args_parser.add_option(max_lines_for_one_command, "Use at most max-lines lines to create a command", "line-limit", 'L', "max-lines");
  53. args_parser.add_option(max_bytes_for_one_command, "Use at most max-chars characters to create a command", "char-limit", 's', "max-chars");
  54. args_parser.add_positional_argument(arguments, "Command and any initial arguments for it", "command", Core::ArgsParser::Required::No);
  55. args_parser.parse(main_arguments);
  56. size_t max_bytes = min(ARG_MAX, max_bytes_for_one_command);
  57. size_t max_lines = max(max_lines_for_one_command, 0);
  58. if (!split_with_nulls && strlen(specified_delimiter) > 1) {
  59. warnln("xargs: the delimiter must be a single byte");
  60. return 1;
  61. }
  62. char entry_separator = split_with_nulls ? '\0' : *specified_delimiter;
  63. if (!placeholder.is_empty())
  64. max_lines = 1;
  65. if (arguments.is_empty())
  66. arguments.append("echo");
  67. ParsedInitialArguments initial_arguments(arguments, placeholder);
  68. FILE* fp = stdin;
  69. bool is_stdin = true;
  70. if ("-"sv != file_to_read) {
  71. // A file was specified, try to open it.
  72. fp = fopen(file_to_read, "re");
  73. if (!fp) {
  74. perror("fopen");
  75. return 1;
  76. }
  77. is_stdin = false;
  78. }
  79. StringBuilder builder;
  80. Vector<char*> child_argv;
  81. int devnull_fd = 0;
  82. if (is_stdin) {
  83. devnull_fd = TRY(Core::System::open("/dev/null", O_RDONLY | O_CLOEXEC));
  84. }
  85. size_t total_command_length = 0;
  86. size_t items_used_for_this_command = 0;
  87. auto fail = read_items(fp, entry_separator, [&](StringView item) {
  88. if (item.ends_with('\n'))
  89. item = item.substring_view(0, item.length() - 1);
  90. if (item.is_empty())
  91. return Continue;
  92. // The first item is processed differently, as all the initial-arguments are processed _with_ that item
  93. // as their substitution target (assuming that substitution is enabled).
  94. // Note that if substitution is not enabled, we manually insert a substitution target at the end of initial-arguments,
  95. // so this item has a place to go.
  96. if (items_used_for_this_command == 0) {
  97. child_argv.ensure_capacity(initial_arguments.size());
  98. initial_arguments.for_each_joined_argument(item, [&](const String& string) {
  99. total_command_length += string.length();
  100. child_argv.append(strdup(string.characters()));
  101. });
  102. ++items_used_for_this_command;
  103. } else {
  104. if ((max_lines > 0 && items_used_for_this_command + 1 > max_lines) || total_command_length + item.length() + 1 >= max_bytes) {
  105. // Note: This `move' does not actually move-construct a new Vector at the callsite, it only allows perfect-forwarding
  106. // and does not invalidate `child_argv' in this scope.
  107. // The same applies for the one below.
  108. if (!run_command(move(child_argv), verbose, is_stdin, devnull_fd))
  109. return Stop;
  110. items_used_for_this_command = 0;
  111. total_command_length = 0;
  112. return Unget;
  113. } else {
  114. child_argv.append(strndup(item.characters_without_null_termination(), item.length()));
  115. total_command_length += item.length();
  116. ++items_used_for_this_command;
  117. }
  118. }
  119. return Continue;
  120. });
  121. if (!fail && !child_argv.is_empty())
  122. fail = !run_command(move(child_argv), verbose, is_stdin, devnull_fd);
  123. if (!is_stdin)
  124. fclose(fp);
  125. return fail ? 1 : 0;
  126. }
  127. bool read_items(FILE* fp, char entry_separator, Function<Decision(StringView)> callback)
  128. {
  129. bool fail = false;
  130. for (;;) {
  131. char* item = nullptr;
  132. size_t buffer_size = 0;
  133. auto item_size = getdelim(&item, &buffer_size, entry_separator, fp);
  134. if (item_size < 0) {
  135. // getdelim() will return -1 and set errno to 0 on EOF.
  136. if (errno != 0) {
  137. perror("getdelim");
  138. fail = true;
  139. }
  140. free(item);
  141. break;
  142. }
  143. Decision decision;
  144. do {
  145. decision = callback({ item, strlen(item) });
  146. if (decision == Stop) {
  147. free(item);
  148. return true;
  149. }
  150. } while (decision == Unget);
  151. free(item);
  152. }
  153. return fail;
  154. }
  155. bool run_command(Vector<char*>&& child_argv, bool verbose, bool is_stdin, int devnull_fd)
  156. {
  157. child_argv.append(nullptr);
  158. if (verbose) {
  159. StringBuilder builder;
  160. builder.join(" ", child_argv);
  161. warnln("xargs: {}", builder.to_string());
  162. }
  163. auto pid = fork();
  164. if (pid < 0) {
  165. perror("fork");
  166. return false;
  167. }
  168. if (pid == 0) {
  169. if (is_stdin)
  170. dup2(devnull_fd, STDIN_FILENO);
  171. execvp(child_argv[0], child_argv.data());
  172. exit(1);
  173. }
  174. for (auto* ptr : child_argv)
  175. free(ptr);
  176. child_argv.clear_with_capacity();
  177. int wstatus = 0;
  178. if (waitpid(pid, &wstatus, 0) < 0) {
  179. perror("waitpid");
  180. return false;
  181. }
  182. if (WIFEXITED(wstatus)) {
  183. if (WEXITSTATUS(wstatus) != 0)
  184. return false;
  185. } else {
  186. return false;
  187. }
  188. return true;
  189. }
  190. ParsedInitialArguments::ParsedInitialArguments(Vector<String>& arguments, StringView placeholder)
  191. {
  192. m_all_parts.ensure_capacity(arguments.size());
  193. bool some_argument_has_placeholder = false;
  194. for (auto arg : arguments) {
  195. if (placeholder.is_empty()) {
  196. m_all_parts.append({ arg });
  197. } else {
  198. auto parts = arg.view().split_view(placeholder, true);
  199. some_argument_has_placeholder = some_argument_has_placeholder || parts.size() > 1;
  200. m_all_parts.append(move(parts));
  201. }
  202. }
  203. // Append an implicit placeholder at the end if no argument has any placeholders.
  204. if (!some_argument_has_placeholder) {
  205. Vector<StringView> parts;
  206. parts.append("");
  207. parts.append("");
  208. m_all_parts.append(move(parts));
  209. }
  210. }
  211. void ParsedInitialArguments::for_each_joined_argument(StringView separator, Function<void(String const&)> callback) const
  212. {
  213. StringBuilder builder;
  214. for (auto& parts : m_all_parts) {
  215. builder.clear();
  216. builder.join(separator, parts);
  217. callback(builder.to_string());
  218. }
  219. }