Files
2026-08-24 10:03:24 +08:00

212 lines
4.8 KiB
C

/*
* spawn.inc
* vim: ft=c
*
* SPDX-FileCopyrightText: 2022-2025 BlueALSA developers
* SPDX-License-Identifier: MIT
*/
#pragma once
#ifndef BLUEALSA_TEST_INC_SPAWN_H_
#define BLUEALSA_TEST_INC_SPAWN_H_
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include "shared/defs.h"
#define SPAWN_FLAG_NONE 0
#define SPAWN_FLAG_REDIRECT_STDOUT (1 << 0)
#define SPAWN_FLAG_REDIRECT_STDERR (1 << 1)
struct spawn_process {
/* PID of newly spawned process */
pid_t pid;
/* stdout from the process */
FILE *f_stdout;
/* stderr from the process */
FILE *f_stderr;
/* async termination */
unsigned int term_delay_msec;
pthread_t term_thread;
};
/**
* Spawn new process using fork() and exec().
*
* @param sp Pointer to the structure which will be filled with spawned process
* information, i.e. PID, stdout and stderr file descriptors.
* @param argv List of arguments to be passed to the process. The list shall be
* terminated by NULL. The first argument is the name of the executable.
* @param f_stdin FILE stream to be used as stdin for the process. If NULL,
* then the stdin from the parent process will be used.
* @param flags Bitwise OR of the SPAWN_FLAG_* flags.
* @return On success this function returns 0. Otherwise -1 is returned and
* errno is set appropriately. */
int spawn(struct spawn_process *sp, char *argv[], FILE *f_stdin, int flags) {
int pipe_stdout[2] = { -1, -1 };
int pipe_stderr[2] = { -1, -1 };
sp->pid = -1;
sp->f_stderr = NULL;
sp->f_stdout = NULL;
sp->term_delay_msec = 0;
if (flags & SPAWN_FLAG_REDIRECT_STDOUT) {
if (pipe(pipe_stdout) == -1)
goto fail;
if ((sp->f_stdout = fdopen(pipe_stdout[0], "r")) == NULL)
goto fail;
}
if (flags & SPAWN_FLAG_REDIRECT_STDERR) {
if (pipe(pipe_stderr) == -1)
goto fail;
if ((sp->f_stderr = fdopen(pipe_stderr[0], "r")) == NULL)
goto fail;
}
if ((sp->pid = fork()) == 0) {
if (f_stdin != NULL)
dup2(fileno(f_stdin), 0);
if (flags & SPAWN_FLAG_REDIRECT_STDOUT) {
dup2(pipe_stdout[1], 1);
fclose(sp->f_stdout);
close(pipe_stdout[1]);
}
if (flags & SPAWN_FLAG_REDIRECT_STDERR) {
dup2(pipe_stderr[1], 2);
fclose(sp->f_stderr);
close(pipe_stderr[1]);
}
return execv(argv[0], argv);
}
close(pipe_stdout[1]);
close(pipe_stderr[1]);
return 0;
fail:
if (sp->f_stdout != NULL)
fclose(sp->f_stdout);
else if (pipe_stdout[0] != -1)
close(pipe_stdout[0]);
if (pipe_stdout[1] != -1)
close(pipe_stdout[1]);
if (sp->f_stderr != NULL)
fclose(sp->f_stderr);
else if (pipe_stderr[0] != -1)
close(pipe_stderr[0]);
if (pipe_stderr[1] != -1)
close(pipe_stderr[1]);
return -1;
}
struct span_io_forwarder {
/* source stream */
FILE *f_in;
/* forward destination stream */
FILE *f_out;
/* optional buffer for data */
char *buffer;
size_t size;
};
static void *span_io_forwarder_thread(void *arg) {
struct span_io_forwarder *ff = arg;
char buffer[4096];
while (!feof(ff->f_in))
if (fgets(buffer, sizeof(buffer), ff->f_in) != NULL) {
fprintf(ff->f_out, "%s", buffer);
if (ff->buffer == NULL)
continue;
size_t n = strlen(buffer);
snprintf(ff->buffer, ff->size, "%s", buffer);
ff->buffer += n;
ff->size -= n;
}
return NULL;
}
/**
* Read output from the spawned process. */
ssize_t spawn_read(struct spawn_process *sp,
char *stdout_buf, size_t stdout_size,
char *stderr_buf, size_t stderr_size) {
struct span_io_forwarder ff_stdout = {
.f_in = sp->f_stdout, .f_out = stdout,
.buffer = stdout_buf, .size = stdout_size };
pthread_t t_stdout;
struct span_io_forwarder ff_stderr = {
.f_in = sp->f_stderr, .f_out = stderr,
.buffer = stderr_buf, .size = stderr_size };
pthread_t t_stderr;
if (sp->f_stdout != NULL)
pthread_create(&t_stdout, NULL, span_io_forwarder_thread, &ff_stdout);
if (sp->f_stderr != NULL)
pthread_create(&t_stderr, NULL, span_io_forwarder_thread, &ff_stderr);
if (sp->f_stdout != NULL)
pthread_join(t_stdout, NULL);
if (sp->f_stderr != NULL)
pthread_join(t_stderr, NULL);
return stdout_size - ff_stdout.size + stderr_size - ff_stderr.size;
}
static void *spawn_timeout_thread(void *arg) {
struct spawn_process *sp = arg;
usleep(sp->term_delay_msec * 1000);
kill(sp->pid, SIGTERM);
return NULL;
}
int spawn_terminate(struct spawn_process *sp, unsigned int delay_msec) {
if (delay_msec == 0)
return kill(sp->pid, SIGTERM);
sp->term_delay_msec = delay_msec;
if (pthread_create(&sp->term_thread, NULL, spawn_timeout_thread, sp) != 0)
return -1;
return 0;
}
void spawn_close(struct spawn_process *sp, int *wstatus) {
if (sp->term_delay_msec != 0)
pthread_join(sp->term_thread, NULL);
if (sp->pid != -1)
waitpid(sp->pid, wstatus, 0);
if (sp->f_stdout != NULL)
fclose(sp->f_stdout);
if (sp->f_stderr != NULL)
fclose(sp->f_stderr);
}
#endif