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 /* Show output from a program. */ 56 57 static void _show_output(file_t *reader) 58 { 59 char buffer[TO_TRANSFER]; 60 offset_t nread; 61 62 while ((nread = client_read(reader, buffer, TO_TRANSFER))) 63 fwrite(buffer, sizeof(char), nread, stdout); 64 } 65 66 /* Wait for a program to finish, showing its output. */ 67 68 static int _wait_program(file_t *reader, process_t *process) 69 { 70 notifier_t *notifier = client_notifier_local(); 71 notifiable_t *notifiable; 72 int exitcode; 73 long err; 74 75 /* Subscribe to reader and process notifications. */ 76 77 if (reader != NULL) 78 { 79 err = client_subscribe(reader, NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED, notifier); 80 81 if (err) 82 { 83 printf("Could not subscribe to pipe notifications: %s\n", l4sys_errtostr(err)); 84 client_notifier_close(notifier); 85 return -1; 86 } 87 } 88 89 err = notify_subscribe(process_notifiable(process), NOTIFY_TASK_ALL, notifier); 90 91 if (err) 92 { 93 printf("Could not subscribe to process notifications: %s\n", l4sys_errtostr(err)); 94 client_unsubscribe(reader, notifier); 95 client_notifier_close(notifier); 96 return -1; 97 } 98 99 /* Read from and write to pipes until the program terminates. */ 100 101 while (1) 102 { 103 err = notify_wait_many(¬ifiable, notifier); 104 105 if (err) 106 { 107 printf("Notification error: %s\n", l4sys_errtostr(err)); 108 109 if (reader != NULL) 110 client_unsubscribe(reader, notifier); 111 112 notify_unsubscribe(process_notifiable(process), notifier); 113 client_notifier_close(notifier); 114 return -1; 115 } 116 117 /* Handle input from the reader. */ 118 119 if ((reader != NULL) && (file_notifications(reader) & (NOTIFY_CONTENT_AVAILABLE | NOTIFY_PEER_CLOSED))) 120 _show_output(reader); 121 122 /* Handle process termination, obtaining the process state. */ 123 124 if (process_terminated(notifiable)) 125 { 126 if (reader != NULL) 127 _show_output(reader); 128 129 printf("Completed with"); 130 131 if (notifiable->notifications & NOTIFY_TASK_ERROR) 132 printf(" error"); 133 134 if (notifiable->notifications & NOTIFY_TASK_SIGNAL) 135 printf(" signal %ld", notifiable->values.sig); 136 137 printf(" value %ld\n", notifiable->values.val); 138 break; 139 } 140 } 141 142 exitcode = notifiable->values.val; 143 144 if (reader != NULL) 145 client_unsubscribe(reader, notifier); 146 147 notify_unsubscribe(process_notifiable(process), notifier); 148 149 /* Close the process and pipe. */ 150 151 client_notifier_close(notifier); 152 process_free(process); 153 154 if (reader != NULL) 155 client_close(reader); 156 157 return exitcode; 158 } 159 160 /* Run the given program. */ 161 162 static int _run_program(int argc, char *argv[], file_t *input_reader) 163 { 164 process_t *process; 165 file_t *output_reader, *output_writer; 166 int last_job; 167 long err; 168 169 /* Create a pipe for process output. */ 170 171 err = client_pipe(&output_reader, &output_writer, O_NONBLOCK); 172 173 if (err) 174 { 175 printf("Could not obtain pipe for output: %s\n", l4sys_errtostr(err)); 176 return -1; 177 } 178 179 /* Start the process. */ 180 181 err = process_spawn(argc, (const char **) argv, input_reader, output_writer, &process); 182 183 if (err) 184 { 185 printf("Could not start process: %s\n", l4sys_errtostr(err)); 186 return -1; 187 } 188 189 printf("Finished program initiation.\n"); 190 191 /* Release the relinquished end of the pipe. */ 192 193 client_close(output_writer); 194 195 /* Record the output stream. */ 196 197 last_job = next_job; 198 199 while (processes[next_job] != NULL) 200 { 201 next_job++; 202 203 if (next_job >= NUMBER_OF_JOBS) 204 next_job = 0; 205 206 /* Wait for the process to complete if no more job slots are available. */ 207 208 if (next_job == last_job) 209 return _wait_program(output_reader, process); 210 } 211 212 readers[next_job] = output_reader; 213 processes[next_job] = process; 214 programs[next_job] = strdup(argv[0]); 215 216 return 0; 217 } 218 219 220 221 /* Run the given program, providing input from a file. */ 222 223 int file_to_program(int argc, char *argv[]) 224 { 225 file_t *reader; 226 227 /* Obtain a file reader and run the program with this as its input reader. */ 228 229 if (argc < 1) 230 return -1; 231 232 reader = client_open(argv[0], O_RDONLY); 233 234 if (!client_opened(reader)) 235 { 236 printf("Could not open file: %s\n", argv[0]); 237 return -1; 238 } 239 240 return _run_program(argc - 1, &argv[1], reader); 241 } 242 243 /* Run the given program, connecting input from another program. */ 244 245 int pipe_to_program(int argc, char *argv[]) 246 { 247 int job_number; 248 int exitcode; 249 250 /* Obtain the job number for the output writer and run the program with this 251 as its input reader. */ 252 253 if (argc < 1) 254 return -1; 255 256 job_number = atoi(argv[0]); 257 258 if (readers[job_number] == NULL) 259 { 260 printf("No output available for this job: %s\n", argv[0]); 261 return -1; 262 } 263 264 exitcode = _run_program(argc - 1, &argv[1], readers[job_number]); 265 266 /* Remove the job's reader to prevent direct access to it and to allow it to 267 be closed by the receiving program. */ 268 269 client_close(readers[job_number]); 270 readers[job_number] = NULL; 271 272 return exitcode; 273 } 274 275 /* Run the given program. */ 276 277 int run_program(int argc, char *argv[]) 278 { 279 return _run_program(argc, argv, NULL); 280 } 281 282 /* Show initiated programs. */ 283 284 int show_programs(int argc, char *argv[]) 285 { 286 (void) argc; (void) argv; 287 288 int job_number; 289 290 for (job_number = 0; job_number < NUMBER_OF_JOBS; job_number++) 291 { 292 if (processes[job_number] != NULL) 293 printf("[%d] %s%s\n", job_number, programs[job_number], 294 readers[job_number] != NULL ? " [!]" : ""); 295 } 296 297 return 0; 298 } 299 300 /* Wait for the indicated program to finish, showing its output. */ 301 302 int wait_program(int argc, char *argv[]) 303 { 304 int job_number; 305 int exitcode; 306 307 if (argc < 1) 308 return -1; 309 310 job_number = atoi(argv[0]); 311 312 if (processes[job_number] == NULL) 313 { 314 printf("No such job: %s\n", argv[0]); 315 return -1; 316 } 317 318 exitcode = _wait_program(readers[job_number], processes[job_number]); 319 320 readers[job_number] = NULL; 321 processes[job_number] = NULL; 322 323 free(programs[job_number]); 324 programs[job_number] = NULL; 325 326 return exitcode; 327 } 328 329 /* vim: tabstop=2 expandtab shiftwidth=2 330 */