Files

215 lines
6.0 KiB
C
Raw Permalink Normal View History

2026-07-01 20:26:10 -05:00
/* - | Copyright / About | ----------------------------------------------------
Copyright (c) 2026 Randy Jordan
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Creates two pipes, forks, and runs the given command. One pipe is
connected between the given *in and the standard input stream of the child;
the other pipe is connected between the given *out and the standard output
stream of the child.
Returns the pid of the child on success, -1 otherwise. On error, errno
will be set accordingly.
-----------------------------------------------------------------------------*/
#define DEBUG
#include "bi_popen.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
static const int READ_END = 0;
static const int WRITE_END = 1;
static const int INVALID_FD = -1;
/* The front fell of, which is quite unusual. */
static int
bi_popen_bail(FILE** const in, FILE** const out, int child[2], int parent[2])
{
/* Save and log the error */
const int old_errno = errno;
fprintf(stderr, "bi_popen: %s\n", strerror(old_errno));
fflush(stderr);
/* Close the file pointers */
if (*in != NULL) {
fclose(*in);
}
if (*out != NULL) {
fclose(*out);
}
/* Close all parent and child file descriptors */
for (int i = 0; i < 2; ++i) {
if (child[i] != INVALID_FD) {
close(child[i]);
}
if (parent[i] != INVALID_FD) {
close(parent[i]);
}
}
errno = old_errno;
return -1;
}
/* Setup pipe redirection, and call exec process. */
static int
bi_popen_child(FILE** const in,
FILE** const out,
int child[2],
int parent[2],
const char* command)
{
/* Redirect child's stdin to the read end of to_child. */
if (dup2(child[READ_END], STDIN_FILENO) < 0) {
perror("dup2");
exit(1);
}
/* Close original pipe fds in the child after duplicating. */
close(child[READ_END]);
close(child[WRITE_END]);
/* Redirect child's stdout to the write end of to_parent */
if (dup2(parent[WRITE_END], STDOUT_FILENO) < 0) {
perror("dup2");
exit(1);
}
/* Close original pipe fds in the child after duplicating. */
close(parent[READ_END]);
close(parent[WRITE_END]);
/* Replace the child process image with the requested command. */
execlp(command, command, NULL);
/* If execlp returns, it failed. */
perror("execlp");
exit(1);
}
/* Convert remaining fd's to streams and return pid_t to wait */
static int
bi_popen_parent(FILE** const in,
FILE** const out,
int child[2],
int parent[2],
pid_t pid)
{
/* Parent process: Close ends that the parent does not use. */
close(child[READ_END]);
close(parent[WRITE_END]);
parent[WRITE_END] = INVALID_FD;
child[READ_END] = INVALID_FD;
/* Convert remaining pipe fds into stdio streams */
*out = fdopen(parent[READ_END], "r");
if (*out == NULL) {
return bi_popen_bail(in, out, child, parent);
}
parent[READ_END] = INVALID_FD;
*in = fdopen(child[WRITE_END], "w");
if (*in == NULL) {
return bi_popen_bail(in, out, child, parent);
}
child[WRITE_END] = INVALID_FD;
/* Make writes to *in unbuffered (so data is sent immediately) */
setvbuf(*in, NULL, _IONBF, BUFSIZ);
/* Success: return the child's pid, so the caller can use to wait. */
return pid;
}
int
bi_popen(const char* const command, FILE** const in, FILE** const out)
{
/* Initialize file descriptors and pointers for safe cleanup. */
int to_child[2] = { INVALID_FD, INVALID_FD };
int to_parent[2] = { INVALID_FD, INVALID_FD };
*in = NULL;
*out = NULL;
/* Validate inputs. */
if (command == NULL || in == NULL || out == NULL) {
errno = EINVAL;
return bi_popen_bail(in, out, to_child, to_parent);
}
/* Create pipe for sending data to the child (stdin of child). */
if (pipe(to_child) < 0) {
return bi_popen_bail(in, out, to_child, to_parent);
}
/* Create pipe for recieving data from the child (stdout of child). */
if (pipe(to_parent) < 0) {
return bi_popen_bail(in, out, to_child, to_parent);
}
/* Fork the process and have the child setup stdio redirection and exec */
const pid_t pid = fork();
if (pid < 0) {
return bi_popen_bail(in, out, to_child, to_parent);
}
if (pid == 0) { // Child Process - execlp shouldn't return .
return bi_popen_child(in, out, to_child, to_parent, command);
}
/* Parent process, convert the fd's to streams and return pid to wait */
return bi_popen_parent(in, out, to_child, to_parent, pid);
}
int
main(int argc, char* argv[])
{
(void)argc;
(void)argv;
FILE* in = NULL;
FILE* out = NULL;
char* line = NULL;
size_t size = 0;
const int pid = bi_popen("/bin/bash", &in, &out);
if (pid < 0) {
perror("bi_popen");
return 1;
}
fprintf(in, "date\n");
getline(&line, &size, out);
printf("-> %s", line);
free(line);
// Since in this case we can tell the child to terminate, we'll do so
// and wait for it to terminate before we close down.
fprintf(in, "exit\n");
waitpid(pid, NULL, 0);
fclose(in);
fclose(out);
return EXIT_SUCCESS;
}