L4Re/departure

Annotated fsaccess/op_run.c

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