commit 182137c5fdd095a8a308391fb6ecc9fe4174510c Author: Randy Jordan Date: Wed Jul 1 20:26:10 2026 -0500 initial commit diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..074265c --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..59c73fb --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# bi_popen + +## Description +`bi_popen` + +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. + +## Table of Contents + +* [Features](#features) +* [Todos](#todos) +* [Usage](#usage) +* [Acknowledgments](#acknowledgments) +* [License](#license) + + +## Features + +## Todos + +## Usage + +```c +int main(void) +{ + 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); + + // 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 0; +} + +``` + +## Acknowledgments +[Unix Stack Exchange](https://unix.stackexchange.com/questions/606861/programming-communicating-with-chess-engine-stockfish-fifos-bash-redirecti)
+## License +This project is licensed under the MIT License - see the [MIT License](LICENSE.md) file for details. + diff --git a/bi_popen.c b/bi_popen.c new file mode 100644 index 0000000..82d9d0f --- /dev/null +++ b/bi_popen.c @@ -0,0 +1,214 @@ +/* - | 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 +#include +#include +#include +#include + +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; +} diff --git a/bi_popen.h b/bi_popen.h new file mode 100644 index 0000000..de343a6 --- /dev/null +++ b/bi_popen.h @@ -0,0 +1,40 @@ +/* - | 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. + + * --------------------------------------------------------------------------*/ +#ifndef BI_POPEN_H +#define BI_POPEN_H + +#include +#include + +extern pid_t +bi_popen(const char* const command, FILE** const in, FILE** const out); + +#endif // bi_popen.h