1 /* 2 * Run programs residing in the filesystem. 3 * 4 * Copyright (C) 2022, 2023, 2024 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU General Public License as 8 * published by the Free Software Foundation; either version 2 of 9 * the License, or (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA 20 */ 21 22 #include <l4/sys/err.h> 23 24 #include <fsclient/client.h> 25 #include <fsclient/process.h> 26 #include <systypes/fcntl.h> 27 #include <systypes/format.h> 28 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 33 #include "ops.h" 34 35 36 37 /* Transfer size for communication. */ 38 39 static const offset_t TO_TRANSFER = 1024; 40 41 42 43 /* Initiated programs (jobs). 44 NOTE: Arbitrary limit. */ 45 46 #define NUMBER_OF_JOBS 32 47 48 static file_t *readers[NUMBER_OF_JOBS] = {NULL}; 49 static process_t *processes[NUMBER_OF_JOBS] = {NULL}; 50 static char *programs[NUMBER_OF_JOBS] = {NULL}; 51 static int next_job = 0; 52 53 54 55 /* Find the given program in the job list. */ 56 57 static int _find_program(process_t *process) 58 { 59 int job_number; 60 61 for (job_number = 0; job_number < NUMBER_OF_JOBS; job_number++) 62 { 63 if (processes[job_number] == process) 64 return job_number; 65 } 66 67 return -1; 68 } 69 70 /* Remove the details of a program from the job list. */ 71 72 static void _remove_program(int job_number) 73 { 74 readers[job_number] = NULL; 75 processes[job_number] = NULL; 76 77 free(programs[job_number]); 78 programs[job_number] = NULL; 79 } 80 81 /* Show the details of a running program. */ 82 83 static void _show_program(int job_number) 84 { 85 /* Employ the Unix convention of a "+" for the default job to be restored upon 86 the appropriate command. */ 87 88 printf("[%d]%s %s%s\n", job_number, job_number == next_job ? "+" : " ", 89 programs[job_number], readers[job_number] != NULL ? " [!]" : ""); 90 } 91 92 93 94 /* Show output from a program. */ 95 96 static void _show_output(file_t *reader) 97 { 98 char buffer[TO_TRANSFER]; 99 offset_t nread; 100 101 while ((nread = client_read(reader, buffer, TO_TRANSFER))) 102 fwrite(buffer, sizeof(char), nread, stdout); 103 } 104 105 /* Show the exit status of a program. */ 106 107 static void _show_status(notifiable_t *notifiable) 108 { 109 int job_number = _find_program((process_t *) notifiable->base); 110 111 if (job_number == -1) 112 return; 113 114 printf("[%d] Completed with", job_number); 115 116 if (notifiable->notifications & NOTIFY_TASK_ERROR) 117 printf(" error"); 118 119 if (notifiable->notifications & NOTIFY_TASK_SIGNAL) 120 printf(" signal %ld", notifiable->values.sig); 121 122 printf(" value %ld\n", notifiable->values.val); 123 } 124 125 /* Wait for a program to finish, showing its output. */ 126 127 static int _wait_program(file_t *reader, process_t *process) 128 { 129 notifier_t *notifier = client_notifier_task(); 130 notifiable_t *notifiable; 131 int exitcode; 132 long err; 133 134 /* Subscribe to reader and process notifications. */ 135 136 if (reader != NULL) 137 { 138 err = client_subscribe(reader, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED, notifier); 139 140 if (err) 141 { 142 printf("Could not subscribe to pipe notifications: %s\n", l4sys_errtostr(err)); 143 return -1; 144 } 145 } 146 147 err = notify_subscribe(process_notifiable(process), NOTIFY_TASK_ALL, notifier); 148 149 if (err) 150 { 151 printf("Could not subscribe to process notifications: %s\n", l4sys_errtostr(err)); 152 client_unsubscribe(reader, notifier); 153 return -1; 154 } 155 156 /* Read from and write to pipes until the program terminates. */ 157 158 while (1) 159 { 160 err = notify_wait_many(¬ifiable, notifier); 161 162 if (err) 163 { 164 printf("Notification error: %s\n", l4sys_errtostr(err)); 165 166 if (reader != NULL) 167 client_unsubscribe(reader, notifier); 168 169 notify_unsubscribe(process_notifiable(process), notifier); 170 return -1; 171 } 172 173 /* Handle input from the reader. */ 174 175 if ((reader != NULL) && (file_notifications(reader) & (NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED))) 176 _show_output(reader); 177 178 /* Handle process termination, obtaining the process state. */ 179 180 if (process_terminated(notifiable)) 181 { 182 if ((reader != NULL) && ((process_t *) notifiable->base == process)) 183 _show_output(reader); 184 185 _show_status(notifiable); 186 187 /* If explicitly waiting for this process, remove it from the job list and 188 return. Otherwise, just remove it from the job list and keep waiting 189 for the indicated process. */ 190 191 _remove_program(_find_program((process_t *) notifiable->base)); 192 193 if ((process_t *) notifiable->base == process) 194 break; 195 } 196 } 197 198 exitcode = notifiable->values.val; 199 200 if (reader != NULL) 201 client_unsubscribe(reader, notifier); 202 203 notify_unsubscribe(process_notifiable(process), notifier); 204 205 /* Close the process and pipe. */ 206 207 process_free(process); 208 209 if (reader != NULL) 210 client_close(reader); 211 212 return exitcode; 213 } 214 215 /* Run the given program. */ 216 217 static int _run_program(int argc, char *argv[], file_t *input_reader) 218 { 219 process_t *process; 220 file_t *output_reader, *output_writer; 221 int last_job; 222 long err; 223 224 /* Find a job slot. */ 225 226 last_job = next_job; 227 228 while (processes[next_job] != NULL) 229 { 230 next_job++; 231 232 if (next_job >= NUMBER_OF_JOBS) 233 next_job = 0; 234 235 /* Return an error if no more job slots are available. */ 236 237 if (next_job == last_job) 238 { 239 printf("No job slots available.\n"); 240 return -1; 241 } 242 } 243 244 /* Create a pipe for process output. */ 245 246 err = client_pipe(&output_reader, &output_writer, O_NONBLOCK); 247 248 if (err) 249 { 250 printf("Could not obtain pipe for output: %s\n", l4sys_errtostr(err)); 251 return -1; 252 } 253 254 /* Start the process. */ 255 256 err = process_spawn(argc, (const char **) argv, input_reader, output_writer, &process); 257 258 if (err) 259 { 260 printf("Could not start process: %s\n", l4sys_errtostr(err)); 261 process_free(process); 262 return -1; 263 } 264 265 /* Release the relinquished end of the output pipe. */ 266 267 client_close(output_writer); 268 269 /* Record the output stream, process and command details. */ 270 271 readers[next_job] = output_reader; 272 processes[next_job] = process; 273 programs[next_job] = strdup(argv[0]); 274 275 return 0; 276 } 277 278 279 280 /* Run the given program, providing input from a file. */ 281 282 int file_to_program(int argc, char *argv[]) 283 { 284 file_t *reader; 285 int exitcode; 286 287 /* Obtain a file reader and run the program with this as its input reader. */ 288 289 if (argc < 1) 290 return -1; 291 292 reader = client_open(argv[0], O_RDONLY); 293 294 if (!client_opened(reader)) 295 { 296 printf("Could not open file: %s\n", argv[0]); 297 return -1; 298 } 299 300 exitcode = _run_program(argc - 1, &argv[1], reader); 301 302 /* Close the file now that the program should be able to read it. */ 303 304 client_close(reader); 305 return exitcode; 306 } 307 308 /* Run the given program, connecting input from another program. */ 309 310 int pipe_to_program(int argc, char *argv[]) 311 { 312 int job_number; 313 int exitcode; 314 315 /* Obtain the job number for the output writer and run the program with this 316 as its input reader. */ 317 318 if (argc < 1) 319 return -1; 320 321 if (!strcmp(argv[0], "+")) 322 job_number = next_job; 323 else 324 job_number = atoi(argv[0]); 325 326 if (readers[job_number] == NULL) 327 { 328 printf("No output available for this job: %s\n", argv[0]); 329 return -1; 330 } 331 332 exitcode = _run_program(argc - 1, &argv[1], readers[job_number]); 333 334 /* Remove the job's reader to prevent direct access to it and to allow it to 335 be closed by the receiving program. */ 336 337 client_close(readers[job_number]); 338 readers[job_number] = NULL; 339 340 /* Subscribe to signals from the process. */ 341 342 notify_subscribe(process_notifiable(processes[job_number]), NOTIFY_TASK_ALL, client_notifier_task()); 343 344 return exitcode; 345 } 346 347 /* Run the given program and wait for it to finish. */ 348 349 int run_program(int argc, char *argv[]) 350 { 351 int exitcode = _run_program(argc, argv, NULL); 352 353 if (exitcode) 354 return exitcode; 355 356 return _wait_program(readers[next_job], processes[next_job]); 357 } 358 359 /* Show initiated programs. */ 360 361 int show_programs(int argc, char *argv[]) 362 { 363 (void) argc; (void) argv; 364 365 int job_number; 366 367 for (job_number = 0; job_number < NUMBER_OF_JOBS; job_number++) 368 { 369 if (processes[job_number] != NULL) 370 _show_program(job_number); 371 } 372 373 return 0; 374 } 375 376 /* Run the given program but do not wait for it to finish, instead allowing the 377 program to produce output and potentially block until an operation to wait 378 for its completion is performed. */ 379 380 int spawn_program(int argc, char *argv[]) 381 { 382 int exitcode = _run_program(argc, argv, NULL); 383 384 if (!exitcode) 385 _show_program(next_job); 386 387 return exitcode; 388 } 389 390 /* Wait for the indicated program (or the last program to be spawned) to finish, 391 showing its output. */ 392 393 int wait_program(int argc, char *argv[]) 394 { 395 int job_number; 396 397 if ((argc < 1) || !strcmp(argv[0], "+")) 398 job_number = next_job; 399 else 400 job_number = atoi(argv[0]); 401 402 if (processes[job_number] == NULL) 403 { 404 printf("No such job: %s\n", argv[0]); 405 return -1; 406 } 407 408 return _wait_program(readers[job_number], processes[job_number]); 409 } 410 411 /* vim: tabstop=2 expandtab shiftwidth=2 412 */