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 printf("Completed with"); 110 111 if (notifiable->notifications & NOTIFY_TASK_ERROR) 112 printf(" error"); 113 114 if (notifiable->notifications & NOTIFY_TASK_SIGNAL) 115 printf(" signal %ld", notifiable->values.sig); 116 117 printf(" value %ld\n", notifiable->values.val); 118 } 119 120 /* Wait for a program to finish, showing its output. */ 121 122 static int _wait_program(file_t *reader, process_t *process) 123 { 124 notifier_t *notifier = client_notifier_task(); 125 notifiable_t *notifiable; 126 int exitcode; 127 long err; 128 129 /* Subscribe to reader and process notifications. */ 130 131 if (reader != NULL) 132 { 133 err = client_subscribe(reader, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED, notifier); 134 135 if (err) 136 { 137 printf("Could not subscribe to pipe notifications: %s\n", l4sys_errtostr(err)); 138 return -1; 139 } 140 } 141 142 err = notify_subscribe(process_notifiable(process), NOTIFY_TASK_ALL, notifier); 143 144 if (err) 145 { 146 printf("Could not subscribe to process notifications: %s\n", l4sys_errtostr(err)); 147 client_unsubscribe(reader, notifier); 148 return -1; 149 } 150 151 /* Read from and write to pipes until the program terminates. */ 152 153 while (1) 154 { 155 err = notify_wait_many(¬ifiable, notifier); 156 157 if (err) 158 { 159 printf("Notification error: %s\n", l4sys_errtostr(err)); 160 161 if (reader != NULL) 162 client_unsubscribe(reader, notifier); 163 164 notify_unsubscribe(process_notifiable(process), notifier); 165 return -1; 166 } 167 168 /* Handle input from the reader. */ 169 170 if ((reader != NULL) && (file_notifications(reader) & (NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED))) 171 _show_output(reader); 172 173 /* Handle process termination, obtaining the process state. */ 174 175 if (process_terminated(notifiable)) 176 { 177 if ((reader != NULL) && ((process_t *) notifiable->base == process)) 178 _show_output(reader); 179 180 _show_status(notifiable); 181 182 /* If explicitly waiting for this process, remove it from the job list and 183 return. Otherwise, just remove it from the job list and keep waiting 184 for the indicated process. */ 185 186 if ((process_t *) notifiable->base == process) 187 { 188 _remove_program(next_job); 189 break; 190 } 191 else 192 _remove_program(_find_program((process_t *) notifiable->base)); 193 } 194 } 195 196 exitcode = notifiable->values.val; 197 198 if (reader != NULL) 199 client_unsubscribe(reader, notifier); 200 201 notify_unsubscribe(process_notifiable(process), notifier); 202 203 /* Close the process and pipe. */ 204 205 process_free(process); 206 207 if (reader != NULL) 208 client_close(reader); 209 210 return exitcode; 211 } 212 213 /* Run the given program. */ 214 215 static int _run_program(int argc, char *argv[], file_t *input_reader) 216 { 217 process_t *process; 218 file_t *output_reader, *output_writer; 219 int last_job; 220 long err; 221 222 /* Create a pipe for process output. */ 223 224 err = client_pipe(&output_reader, &output_writer, O_NONBLOCK); 225 226 if (err) 227 { 228 printf("Could not obtain pipe for output: %s\n", l4sys_errtostr(err)); 229 return -1; 230 } 231 232 /* Start the process. */ 233 234 err = process_spawn(argc, (const char **) argv, input_reader, output_writer, &process); 235 236 if (err) 237 { 238 printf("Could not start process: %s\n", l4sys_errtostr(err)); 239 return -1; 240 } 241 242 /* Release the relinquished end of the pipe. */ 243 244 client_close(output_writer); 245 246 /* Record the output stream. */ 247 248 last_job = next_job; 249 250 while (processes[next_job] != NULL) 251 { 252 next_job++; 253 254 if (next_job >= NUMBER_OF_JOBS) 255 next_job = 0; 256 257 /* Return an error if no more job slots are available. */ 258 259 if (next_job == last_job) 260 { 261 printf("No job slots available.\n"); 262 return -1; 263 } 264 } 265 266 readers[next_job] = output_reader; 267 processes[next_job] = process; 268 programs[next_job] = strdup(argv[0]); 269 270 return 0; 271 } 272 273 274 275 /* Run the given program, providing input from a file. */ 276 277 int file_to_program(int argc, char *argv[]) 278 { 279 file_t *reader; 280 281 /* Obtain a file reader and run the program with this as its input reader. */ 282 283 if (argc < 1) 284 return -1; 285 286 reader = client_open(argv[0], O_RDONLY); 287 288 if (!client_opened(reader)) 289 { 290 printf("Could not open file: %s\n", argv[0]); 291 return -1; 292 } 293 294 return _run_program(argc - 1, &argv[1], reader); 295 } 296 297 /* Run the given program, connecting input from another program. */ 298 299 int pipe_to_program(int argc, char *argv[]) 300 { 301 int job_number; 302 int exitcode; 303 304 /* Obtain the job number for the output writer and run the program with this 305 as its input reader. */ 306 307 if (argc < 1) 308 return -1; 309 310 if (!strcmp(argv[0], "+")) 311 job_number = next_job; 312 else 313 job_number = atoi(argv[0]); 314 315 if (readers[job_number] == NULL) 316 { 317 printf("No output available for this job: %s\n", argv[0]); 318 return -1; 319 } 320 321 exitcode = _run_program(argc - 1, &argv[1], readers[job_number]); 322 323 /* Remove the job's reader to prevent direct access to it and to allow it to 324 be closed by the receiving program. */ 325 326 client_close(readers[job_number]); 327 readers[job_number] = NULL; 328 329 /* Subscribe to signals from the process. */ 330 331 notify_subscribe(process_notifiable(processes[job_number]), NOTIFY_TASK_ALL, client_notifier_task()); 332 333 return exitcode; 334 } 335 336 /* Run the given program and wait for it to finish. */ 337 338 int run_program(int argc, char *argv[]) 339 { 340 int exitcode = _run_program(argc, argv, NULL); 341 342 if (exitcode) 343 return exitcode; 344 345 return _wait_program(readers[next_job], processes[next_job]); 346 } 347 348 /* Show initiated programs. */ 349 350 int show_programs(int argc, char *argv[]) 351 { 352 (void) argc; (void) argv; 353 354 int job_number; 355 356 for (job_number = 0; job_number < NUMBER_OF_JOBS; job_number++) 357 { 358 if (processes[job_number] != NULL) 359 _show_program(job_number); 360 } 361 362 return 0; 363 } 364 365 /* Run the given program but do not wait for it to finish, instead allowing the 366 program to produce output and potentially block until an operation to wait 367 for its completion is performed. */ 368 369 int spawn_program(int argc, char *argv[]) 370 { 371 int exitcode = _run_program(argc, argv, NULL); 372 373 if (!exitcode) 374 _show_program(next_job); 375 376 return exitcode; 377 } 378 379 /* Wait for the indicated program (or the last program to be spawned) to finish, 380 showing its output. */ 381 382 int wait_program(int argc, char *argv[]) 383 { 384 int job_number; 385 386 if (argc < 1) 387 job_number = next_job; 388 else 389 job_number = atoi(argv[0]); 390 391 if (processes[job_number] == NULL) 392 { 393 printf("No such job: %s\n", argv[0]); 394 return -1; 395 } 396 397 return _wait_program(readers[job_number], processes[job_number]); 398 } 399 400 /* vim: tabstop=2 expandtab shiftwidth=2 401 */