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("End process (flags %" pFMTnotify_flags "x values: %ld, %ld)\n", 130 notifiable->notifications, notifiable->values.sig, notifiable->values.val); 131 break; 132 } 133 } 134 135 exitcode = notifiable->values.val; 136 137 if (reader != NULL) 138 client_unsubscribe(reader, notifier); 139 140 notify_unsubscribe(process_notifiable(process), notifier); 141 142 /* Close the process and pipe. */ 143 144 client_notifier_close(notifier); 145 process_free(process); 146 147 if (reader != NULL) 148 client_close(reader); 149 150 return exitcode; 151 } 152 153 /* Run the given program. */ 154 155 static int _run_program(int argc, char *argv[], file_t *input_reader) 156 { 157 process_t *process; 158 file_t *output_reader, *output_writer; 159 int last_job; 160 long err; 161 162 /* Create a pipe for process output. */ 163 164 err = client_pipe(&output_reader, &output_writer, O_NONBLOCK); 165 166 if (err) 167 { 168 printf("Could not obtain pipe for output: %s\n", l4sys_errtostr(err)); 169 return -1; 170 } 171 172 /* Start the process. */ 173 174 err = process_spawn(argc, (const char **) argv, input_reader, output_writer, &process); 175 176 if (err) 177 { 178 printf("Could not start process: %s\n", l4sys_errtostr(err)); 179 return -1; 180 } 181 182 printf("Finished program initiation.\n"); 183 184 /* Release the relinquished end of the pipe. */ 185 186 client_close(output_writer); 187 188 /* Record the output stream. */ 189 190 last_job = next_job; 191 192 while (processes[next_job] != NULL) 193 { 194 next_job++; 195 196 if (next_job >= NUMBER_OF_JOBS) 197 next_job = 0; 198 199 /* Wait for the process to complete if no more job slots are available. */ 200 201 if (next_job == last_job) 202 return _wait_program(output_reader, process); 203 } 204 205 readers[next_job] = output_reader; 206 processes[next_job] = process; 207 programs[next_job] = strdup(argv[0]); 208 209 return 0; 210 } 211 212 213 214 /* Run the given program, providing input from a file. */ 215 216 int file_to_program(int argc, char *argv[]) 217 { 218 file_t *reader; 219 220 /* Obtain a file reader and run the program with this as its input reader. */ 221 222 if (argc < 1) 223 return -1; 224 225 reader = client_open(argv[0], O_RDONLY); 226 227 if (!client_opened(reader)) 228 { 229 printf("Could not open file: %s\n", argv[0]); 230 return -1; 231 } 232 233 return _run_program(argc - 1, &argv[1], reader); 234 } 235 236 /* Run the given program, connecting input from another program. */ 237 238 int pipe_to_program(int argc, char *argv[]) 239 { 240 int job_number; 241 int exitcode; 242 243 /* Obtain the job number for the output writer and run the program with this 244 as its input reader. */ 245 246 if (argc < 1) 247 return -1; 248 249 job_number = atoi(argv[0]); 250 251 if (readers[job_number] == NULL) 252 { 253 printf("No output available for this job: %s\n", argv[0]); 254 return -1; 255 } 256 257 exitcode = _run_program(argc - 1, &argv[1], readers[job_number]); 258 259 /* Remove the job's reader to prevent direct access to it and to allow it to 260 be closed by the receiving program. */ 261 262 client_close(readers[job_number]); 263 readers[job_number] = NULL; 264 265 return exitcode; 266 } 267 268 /* Run the given program. */ 269 270 int run_program(int argc, char *argv[]) 271 { 272 return _run_program(argc, argv, NULL); 273 } 274 275 /* Show initiated programs. */ 276 277 int show_programs(int argc, char *argv[]) 278 { 279 (void) argc; (void) argv; 280 281 int job_number; 282 283 for (job_number = 0; job_number < NUMBER_OF_JOBS; job_number++) 284 { 285 if (processes[job_number] != NULL) 286 printf("[%d] %s%s\n", job_number, programs[job_number], 287 readers[job_number] != NULL ? " [!]" : ""); 288 } 289 290 return 0; 291 } 292 293 /* Wait for the indicated program to finish, showing its output. */ 294 295 int wait_program(int argc, char *argv[]) 296 { 297 int job_number; 298 int exitcode; 299 300 if (argc < 1) 301 return -1; 302 303 job_number = atoi(argv[0]); 304 305 if (processes[job_number] == NULL) 306 { 307 printf("No such job: %s\n", argv[0]); 308 return -1; 309 } 310 311 exitcode = _wait_program(readers[job_number], processes[job_number]); 312 313 readers[job_number] = NULL; 314 processes[job_number] = NULL; 315 316 free(programs[job_number]); 317 programs[job_number] = NULL; 318 319 return exitcode; 320 } 321 322 /* vim: tabstop=2 expandtab shiftwidth=2 323 */