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