xargs.cpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /*
  2. * Copyright (c) 2020, The SerenityOS developers.
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include <AK/Function.h>
  27. #include <AK/StdLibExtras.h>
  28. #include <AK/StringBuilder.h>
  29. #include <LibCore/ArgsParser.h>
  30. #include <errno.h>
  31. #include <fcntl.h>
  32. #include <stdio.h>
  33. #include <stdlib.h>
  34. #include <string.h>
  35. #include <sys/wait.h>
  36. bool run_command(Vector<char*>&& child_argv, bool verbose, bool is_stdin, int devnull_fd);
  37. enum Decision {
  38. Unget,
  39. Continue,
  40. Stop,
  41. };
  42. bool read_items(FILE* fp, char entry_separator, Function<Decision(StringView)>);
  43. class ParsedInitialArguments {
  44. public:
  45. ParsedInitialArguments(Vector<const char*>&, const StringView& placeholder);
  46. void for_each_joined_argument(const StringView&, Function<void(const String&)>) const;
  47. size_t size() const { return m_all_parts.size(); }
  48. private:
  49. Vector<Vector<StringView>> m_all_parts;
  50. };
  51. int main(int argc, char** argv)
  52. {
  53. if (pledge("stdio rpath proc exec", nullptr) < 0) {
  54. perror("pledge");
  55. return 1;
  56. }
  57. const char* placeholder = nullptr;
  58. bool split_with_nulls = false;
  59. const char* specified_delimiter = "\n";
  60. Vector<const char*> arguments;
  61. bool verbose = false;
  62. const char* file_to_read = "-";
  63. int max_lines_for_one_command = 0;
  64. int max_bytes_for_one_command = ARG_MAX;
  65. Core::ArgsParser args_parser;
  66. args_parser.add_option(placeholder, "Placeholder string to be replaced in arguments", "replace", 'I', "placeholder");
  67. args_parser.add_option(split_with_nulls, "Split input items with the null character instead of newline", "null", '0');
  68. args_parser.add_option(specified_delimiter, "Split the input items with the specified character", "delimiter", 'd', "delim");
  69. args_parser.add_option(verbose, "Display each command before executing it", "verbose", 'v');
  70. args_parser.add_option(file_to_read, "Read arguments from the specified file instead of stdin", "arg-file", 'a', "file");
  71. args_parser.add_option(max_lines_for_one_command, "Use at most max-lines lines to create a command", "line-limit", 'L', "max-lines");
  72. args_parser.add_option(max_bytes_for_one_command, "Use at most max-chars characters to create a command", "char-limit", 's', "max-chars");
  73. args_parser.add_positional_argument(arguments, "Command and any initial arguments for it", "command", Core::ArgsParser::Required::No);
  74. args_parser.parse(argc, argv);
  75. size_t max_bytes = min(ARG_MAX, max_bytes_for_one_command);
  76. size_t max_lines = max(max_lines_for_one_command, 0);
  77. if (!split_with_nulls && strlen(specified_delimiter) > 1) {
  78. fprintf(stderr, "xargs: the delimiter must be a single byte\n");
  79. return 1;
  80. }
  81. char entry_separator = split_with_nulls ? '\0' : *specified_delimiter;
  82. StringView placeholder_view { placeholder };
  83. if (!placeholder_view.is_empty())
  84. max_lines = 1;
  85. if (arguments.is_empty())
  86. arguments.append("echo");
  87. ParsedInitialArguments initial_arguments(arguments, placeholder_view);
  88. FILE* fp = stdin;
  89. bool is_stdin = true;
  90. if (StringView { "-" } != file_to_read) {
  91. // A file was specified, try to open it.
  92. fp = fopen(file_to_read, "re");
  93. if (!fp) {
  94. perror("fopen");
  95. return 1;
  96. }
  97. is_stdin = false;
  98. }
  99. StringBuilder builder;
  100. Vector<char*> child_argv;
  101. int devnull_fd = 0;
  102. if (is_stdin) {
  103. devnull_fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
  104. if (devnull_fd < 0) {
  105. perror("open");
  106. return 1;
  107. }
  108. }
  109. size_t total_command_length = 0;
  110. size_t items_used_for_this_command = 0;
  111. auto fail = read_items(fp, entry_separator, [&](StringView item) {
  112. if (item.ends_with('\n'))
  113. item = item.substring_view(0, item.length() - 1);
  114. if (item.is_empty())
  115. return Continue;
  116. // The first item is processed differently, as all the initial-arguments are processed _with_ that item
  117. // as their substitution target (assuming that substitution is enabled).
  118. // Note that if substitution is not enabled, we manually insert a substitution target at the end of initial-arguments,
  119. // so this item has a place to go.
  120. if (items_used_for_this_command == 0) {
  121. child_argv.ensure_capacity(initial_arguments.size());
  122. initial_arguments.for_each_joined_argument(item, [&](const String& string) {
  123. total_command_length += string.length();
  124. child_argv.append(strdup(string.characters()));
  125. });
  126. ++items_used_for_this_command;
  127. } else {
  128. if ((max_lines > 0 && items_used_for_this_command + 1 > max_lines) || total_command_length + item.length() + 1 >= max_bytes) {
  129. // Note: This `move' does not actually move-construct a new Vector at the callsite, it only allows perfect-forwarding
  130. // and does not invalidate `child_argv' in this scope.
  131. // The same applies for the one below.
  132. if (!run_command(move(child_argv), verbose, is_stdin, devnull_fd))
  133. return Stop;
  134. items_used_for_this_command = 0;
  135. total_command_length = 0;
  136. return Unget;
  137. } else {
  138. child_argv.append(strndup(item.characters_without_null_termination(), item.length()));
  139. total_command_length += item.length();
  140. ++items_used_for_this_command;
  141. }
  142. }
  143. return Continue;
  144. });
  145. if (!fail && !child_argv.is_empty())
  146. fail = !run_command(move(child_argv), verbose, is_stdin, devnull_fd);
  147. if (!is_stdin)
  148. fclose(fp);
  149. return fail ? 1 : 0;
  150. }
  151. bool read_items(FILE* fp, char entry_separator, Function<Decision(StringView)> callback)
  152. {
  153. bool fail = false;
  154. for (;;) {
  155. char* item = nullptr;
  156. size_t buffer_size = 0;
  157. auto item_size = getdelim(&item, &buffer_size, entry_separator, fp);
  158. if (item_size < 0) {
  159. // getdelim() will return -1 and set errno to 0 on EOF.
  160. if (errno != 0) {
  161. perror("getdelim");
  162. fail = true;
  163. }
  164. break;
  165. }
  166. Decision decision;
  167. do {
  168. decision = callback(item);
  169. if (decision == Stop) {
  170. free(item);
  171. return true;
  172. }
  173. } while (decision == Unget);
  174. free(item);
  175. }
  176. return fail;
  177. }
  178. bool run_command(Vector<char*>&& child_argv, bool verbose, bool is_stdin, int devnull_fd)
  179. {
  180. child_argv.append(nullptr);
  181. if (verbose) {
  182. StringBuilder builder;
  183. builder.join(" ", child_argv);
  184. fprintf(stderr, "xargs: %s\n", builder.to_string().characters());
  185. fflush(stderr);
  186. }
  187. auto pid = fork();
  188. if (pid < 0) {
  189. perror("fork");
  190. return false;
  191. }
  192. if (pid == 0) {
  193. if (is_stdin)
  194. dup2(devnull_fd, STDIN_FILENO);
  195. execvp(child_argv[0], child_argv.data());
  196. exit(1);
  197. }
  198. for (auto* ptr : child_argv)
  199. free(ptr);
  200. child_argv.clear_with_capacity();
  201. int wstatus = 0;
  202. if (waitpid(pid, &wstatus, 0) < 0) {
  203. perror("waitpid");
  204. return false;
  205. }
  206. if (WIFEXITED(wstatus)) {
  207. if (WEXITSTATUS(wstatus) != 0)
  208. return false;
  209. } else {
  210. return false;
  211. }
  212. return true;
  213. }
  214. ParsedInitialArguments::ParsedInitialArguments(Vector<const char*>& arguments, const StringView& placeholder)
  215. {
  216. m_all_parts.ensure_capacity(arguments.size());
  217. bool some_argument_has_placeholder = false;
  218. for (auto argument : arguments) {
  219. StringView arg { argument };
  220. if (placeholder.is_empty()) {
  221. m_all_parts.append({ arg });
  222. } else {
  223. auto parts = arg.split_view(placeholder, true);
  224. some_argument_has_placeholder = some_argument_has_placeholder || parts.size() > 1;
  225. m_all_parts.append(move(parts));
  226. }
  227. }
  228. // Append an implicit placeholder at the end if no argument has any placeholders.
  229. if (!some_argument_has_placeholder) {
  230. Vector<StringView> parts;
  231. parts.append("");
  232. parts.append("");
  233. m_all_parts.append(move(parts));
  234. }
  235. }
  236. void ParsedInitialArguments::for_each_joined_argument(const StringView& separator, Function<void(const String&)> callback) const
  237. {
  238. StringBuilder builder;
  239. for (auto& parts : m_all_parts) {
  240. builder.clear();
  241. builder.join(separator, parts);
  242. callback(builder.to_string());
  243. }
  244. }