From b020910f786fd7033c7e905961886072e6533755 Mon Sep 17 00:00:00 2001 From: ethanxxxl Date: Wed, 14 Jan 2026 01:00:34 -0500 Subject: [PATCH 1/7] added support for reading stdout and stderr separately from processes --- src/async-process.c | 402 +++++++++++++++++++++++++++++++++----------- src/async-process.h | 37 +++- src/test.c | 37 ++++ 3 files changed, 367 insertions(+), 109 deletions(-) create mode 100644 src/test.c diff --git a/src/async-process.c b/src/async-process.c index ad58157..ec73da7 100644 --- a/src/async-process.c +++ b/src/async-process.c @@ -1,133 +1,331 @@ #include "async-process.h" -static const char* open_pty(int *out_fd) -{ - int fd = posix_openpt(O_RDWR | O_CLOEXEC | O_NOCTTY); - if (fd < 0) return NULL; - if (grantpt(fd) == -1 || unlockpt(fd) == -1) return NULL; - fcntl(fd, F_SETFD, FD_CLOEXEC); - const char *name = ptsname(fd); - if (name == NULL) { - close(fd); - return NULL; - } - *out_fd = fd; - return name; +int init_str(struct str *str) { + str->buf = malloc(sizeof(char) * 256); + if (str->buf == NULL) + return -1; + + str->len = 0; + str->cap = 256; + + return 0; } -static struct process* allocate_process(int fd, const char *pts_name, int pid) -{ - struct process *process = malloc(sizeof(struct process)); - if (process == NULL) +void del_str(struct str *str) { + free(str->buf); +} + +static struct process* allocate_process(int fd_io, + int fd_er, + const char *pts_io_name, + const char *pts_er_name, + int pid) { + int stdout_ret = 0, stderr_ret = 0; + char *io_str = NULL, *er_str = NULL; + + struct process *process = malloc(sizeof(struct process)); + if (process == NULL) + return NULL; + + stdout_ret = init_str(&process->stdout); + if (stdout_ret == -1) + goto FAILED_MALLOC; + + stderr_ret = init_str(&process->stderr); + if (stderr_ret == -1) + goto FAILED_MALLOC; + + io_str = malloc(strlen(pts_io_name) + 1); + if (io_str == NULL) + goto FAILED_MALLOC; + + er_str = malloc(strlen(pts_er_name) + 1); + if (er_str == NULL) + goto FAILED_MALLOC; + + strcpy(io_str, pts_io_name); + strcpy(er_str, pts_er_name); + + process->pts_io_name = io_str; + process->pts_er_name = er_str; + process->fd_io = fd_io; + process->fd_er = fd_er; + process->pid = pid; + + return process; + +FAILED_MALLOC: + if (process != NULL) free(process); + if (stdout_ret == -1) del_str(&process->stdout); + if (stderr_ret == -1) del_str(&process->stderr); + if (io_str != NULL) free(io_str); + if (er_str != NULL) free(er_str); return NULL; - process->fd = fd; - process->pty_name = malloc(strlen(pts_name) + 1); - process->pid = pid; - strcpy(process->pty_name, pts_name); - return process; +} + +void delete_process(struct process *process) { + kill(process->pid, 9); + close(process->fd_io); + close(process->fd_er); + free(process->stdout.buf); + free(process->stderr.buf); + free(process->pts_io_name); + free(process->pts_er_name); + free(process); } void my_exit(int status) { - // exitを使うとatexitで動作に影響を与えられる、これが原因でプロセスを終了できなくなる事があるので使うのを避ける - // 例えばSDL2はat_exitを使っているせいか、lemのSDL2 frontendでasync_processが動作しなくなっていた - _exit(status); + // exitを使うとatexitで動作に影響を与えられる、これが原因でプロセスを終了できなくなる事があるので使うのを避ける + // 例えばSDL2はat_exitを使っているせいか、lemのSDL2 frontendでasync_processが動作しなくなっていた + _exit(status); } -struct process* create_process(char *const command[], bool nonblock, const char *path) -{ - int pty_master; - const char *pts_name = open_pty(&pty_master); - if (pts_name == NULL) - return NULL; +// opens a PTY and assigns master and slave file descriptors to fdm and fds +// respectively. Name will be malloced and it is the callers responsibility +// to free name. On success, return 0. On fail, returns -1. All references +// will also be either initialized or set -1/NULL appropriately. +int open_pty(int *fdm, int *fds, char **name) { + *fdm = -1; + *fds = -1; + *name = NULL; + + // gets a PTY, and initializes the attached slave PTS. grantpt and unlockpt + // are required before opening the slave device. + *fdm = posix_openpt(O_RDWR | O_NOCTTY); + if (*fdm == -1 || grantpt(*fdm) == -1 || unlockpt(*fdm) == -1) + goto FAILED_SETUP; + + // ptsname returns a string that must be copied, as it is overwritten + // on subsequent calls. + const char *tmp = ptsname(*fdm); + if (tmp == NULL) + goto FAILED_SETUP; + + size_t tmp_len = strlen(tmp); + *name = malloc(tmp_len * sizeof(char)); + if (*name == NULL) + goto FAILED_SETUP; + + memcpy(*name, tmp, tmp_len+1); + + *fds = open(*name, O_RDWR | O_NOCTTY); + if (*fds == -1) + goto FAILED_SETUP; + + // ensure both slave and master close after program finishes + fcntl(*fdm, F_SETFD, FD_CLOEXEC); + fcntl(*fds, F_SETFD, FD_CLOEXEC); + + // set master as non-blocking (for get_process_output functions) + fcntl(*fdm, F_SETFL, O_NONBLOCK); + + // Set raw mode + struct termios tty; + tcgetattr(*fds, &tty); + cfmakeraw(&tty); + tcsetattr(*fds, TCSANOW, &tty); + + return 0; + +FAILED_SETUP: + if (*fdm != -1) close(*fdm); + if (*fds != -1) close(*fds); + if (*name != NULL) free(*name); + + *fdm = -1; + *fds = -1; + *name = NULL; + return -1; +} + +struct process* create_process(char *const command[], const char *path) { + // Unix PTYs are bi-directional communication streams. Typically, a terminal will + // combine stdout and stderr and display them in the same output. We want to + // keep the outputs separate at this level so master_pty_er is created just to carry + // the stderr stream. + // + // There is a potential bug here, if the process tries to set terminal attributes (like + // with stty), these updates won't be propogated across both terminals. + + int master_pty_io, slave_pts_io, master_pty_er, slave_pts_er; + char *pts_io_name, *pts_er_name; + int ret; + + ret = open_pty(&master_pty_io, &slave_pts_io, &pts_io_name); + if (ret == -1) + goto FAILED_SETUP; + + ret = open_pty(&master_pty_er, &slave_pts_er, &pts_er_name); + if (ret == -1) + goto FAILED_SETUP; + + // START CHILD PROCESS AND RETURN ITS PID + pid_t pid = fork(); + + if (pid == -1) { + goto FAILED_SETUP; + } else if (pid != 0) { + close(slave_pts_io); + close(slave_pts_er); + // parent process, return process structure. + struct process *p = allocate_process(master_pty_io, master_pty_er, + pts_io_name, pts_er_name, pid); + + // allocate_process copies the strings it is passed, open_pty mallocs strings + // so we need to free them here before we exit. + free(pts_io_name); + free(pts_er_name); + return p; + } + + // VVV CHILD PROCESS VVV + setsid(); + + // we don't need these in the child process. + free(pts_io_name); + free(pts_er_name); + close(master_pty_io); + close(master_pty_er); - if (nonblock) - fcntl(pty_master, F_SETFL, O_NONBLOCK); - - int pipefd[2]; - - if (pipe(pipefd) == -1) return NULL; - - pid_t pid = fork(); - - if (pid == 0) { - close(pipefd[0]); - pid = fork(); - if (pid == 0) { - close(pipefd[1]); - setsid(); - int pty_slave = open(pts_name, O_RDWR | O_NOCTTY); - close(pty_master); - - // Set raw mode - struct termios tty; - tcgetattr(pty_slave, &tty); - cfmakeraw(&tty); - tcsetattr(pty_slave, TCSANOW, &tty); - - dup2(pty_slave, STDIN_FILENO); - dup2(pty_slave, STDOUT_FILENO); - dup2(pty_slave, STDERR_FILENO); - close(pty_slave); - if (path != NULL) chdir(path); - execvp(command[0], command); - int error_status = errno; - if (error_status == ENOENT) { + dup2(slave_pts_io, STDIN_FILENO); + dup2(slave_pts_io, STDOUT_FILENO); + dup2(slave_pts_er, STDERR_FILENO); + + close(slave_pts_io); + close(slave_pts_er); + + if (path != NULL) chdir(path); + + // run command, the current fork process will switch to + // the command. + execvp(command[0], command); + + // if execution reaches here, there was a problem starting + // the program. execvp does not return on success. + int error_status = errno; + if (error_status == ENOENT) { char str[128]; sprintf(str, "%s: command not found", command[0]); write(STDIN_FILENO, str, strlen(str)); - } else { + } else { char *str = strerror(error_status); write(STDIN_FILENO, str, strlen(str)); - } - my_exit(error_status); - } else { - char buf[12]; - sprintf(buf, "%d", pid); - write(pipefd[1], buf, strlen(buf)+1); - close(pipefd[1]); - my_exit(0); } - } else { - close(pipefd[1]); - if (waitpid(pid, NULL, 0) == -1) - return NULL; - char buf[12]; - read(pipefd[0], buf, sizeof(buf)); - close(pipefd[0]); - return allocate_process(pty_master, pts_name, atoi(buf)); - } + my_exit(error_status); - return NULL; + // ERROR HANDLING +FAILED_SETUP: + // we can assume at this point that any FD that is not -1 needs closed. + if (master_pty_io != -1) close(master_pty_io); + if (master_pty_er != -1) close(master_pty_er); + if (slave_pts_io != -1) close(slave_pts_io); + if (slave_pts_er != -1) close(slave_pts_er); + + // we can assume that any name pointer that is not NULL needs free. + if (pts_io_name != NULL) free(pts_io_name); + if (pts_er_name != NULL) free(pts_er_name); + + return NULL; } -void delete_process(struct process *process) -{ - kill(process->pid, 9); - close(process->fd); - free(process->pty_name); - free(process); +int process_pid(struct process *process) { + return process->pid; } -int process_pid(struct process *process) -{ - return process->pid; +void process_write_string(struct process *process, const char *string) { + write(process->fd_io, string, strlen(string)); } -void process_send_input(struct process *process, const char *string) -{ - write(process->fd, string, strlen(string)); +void process_write(struct process *process, const char *buf, size_t n) { + write(process->fd_io, buf, n); } -const char* process_receive_output(struct process *process) -{ - int n = read(process->fd, process->buffer, sizeof(process->buffer)-1); - if (n == -1) - return NULL; - process->buffer[n] = '\0'; - return process->buffer; +// reads all data available in fd (should be non-blocking) into str, +// returns number of bytes read on success, -1 on error. +int str_read_fd(struct str *str, int fd) { + int total_read = 0; + while (true) { + // resize buffer if it is too small + if (str->cap - str->len <= 1) { + char *new_ptr = realloc(str->buf, 2*str->cap); + if (new_ptr == NULL) + return -1; + str->buf = new_ptr; + str->cap *= 2; + } + + // read as much data from fd as possible. + int n = read(fd, str->buf + str->len, str->cap - str->len); + + if (total_read == 0 && n == -1) { + return -1; // an error occured on first read + } else if (n <= 0) { + return total_read; + } + + total_read += n; + str->len += n; + } + + return -1; // control flow shouldn't reach here. +} + +char* _process_receive_fd(struct str *s, int fd) { + int n = str_read_fd(s, fd); + if (n == -1) + return NULL; + + char *ret = malloc((s->len + 1) * sizeof(char)); + if (ret == NULL) + return NULL; + + memcpy(ret, s->buf, s->len); + ret[s->len] = '\0'; + + s->len = 0; + return ret; +} + +char* process_receive_stdout(struct process *p) { + return _process_receive_fd(&p->stdout, p->fd_io); +} + +char* process_receive_stderr(struct process *p) { + return _process_receive_fd(&p->stderr, p->fd_er); +} + +char* process_receive_output(struct process *process) { + char *stdout = process_receive_stdout(process); + char *stderr = process_receive_stderr(process); + + if (stdout == NULL && stderr == NULL) + return NULL; + + if (stdout != NULL && stderr == NULL) + return stdout; + + if (stdout == NULL && stderr != NULL) + return stderr; + + size_t o_len = strlen(stdout); + size_t e_len = strlen(stderr); + size_t length = o_len + e_len + 1; + char *ret = malloc(length * sizeof(char)); + if (ret == NULL) + return NULL; + + memcpy(ret, stdout, o_len); + memcpy(ret + o_len, stderr, e_len); + + ret[length] = '\0'; + + free(stdout); + free(stderr); + + return ret; } int process_alive_p(struct process *process) { - return kill(process->pid, 0) == 0; + return kill(process->pid, 0) == 0; } diff --git a/src/async-process.h b/src/async-process.h index dd2b26f..ed2f8f0 100644 --- a/src/async-process.h +++ b/src/async-process.h @@ -18,18 +18,41 @@ #include #include +struct str { + char* buf; + size_t len; + size_t cap; +}; + +int init_str(struct str *str); +void del_str(struct str *str); +int str_read_fd(struct str *str, int fd); + struct process { - char buffer[1024*4]; - int fd; - char *pty_name; - pid_t pid; + struct str stdout; + struct str stderr; + + int fd_io; + int fd_er; + char *pts_io_name; + char *pts_er_name; + pid_t pid; }; -struct process* create_process(char *const command[], bool nonblock, const char *path); +struct process* create_process(char *const command[], const char *path); void delete_process(struct process *process); int process_pid(struct process *process); -void process_send_input(struct process *process, const char *string); -const char* process_receive_output(struct process *process); + +void process_write(struct process *process, const char* buf, size_t n); +void process_write_string(struct process *process, const char *string); + +/** receive process stdout. MUST FREE RETURNED PONTER */ +char* process_receive_stdout(struct process *process); +/** receive process stderr. MUST FREE RETURNED PONTER */ +char* process_receive_stderr(struct process *process); +/** receive process stdout and stderr (one after another). +MUST FREE RETURNED PONTER */ +char* process_receive_output(struct process *process); int process_alive_p(struct process *process); #endif diff --git a/src/test.c b/src/test.c new file mode 100644 index 0000000..2dc7fb6 --- /dev/null +++ b/src/test.c @@ -0,0 +1,37 @@ +#include "async-process.h" + +int main() { + char *cmd[] = {"clangd"}; + struct process *p = create_process(cmd, NULL); + + struct str s; + init_str(&s); + + while (true) { + int n = str_read_fd(&s, STDIN_FILENO); + if (n > 0) { + process_write(p, s.buf, n); + s.len = 0; + } + + + char *out = NULL; + char *err = NULL; + + out = process_receive_stdout(p); + err = process_receive_stderr(p); + + if (out != NULL) { + printf("%s", out); + free(out); + } + + if (err != NULL) { + printf("\033[31m%s\033[0m", err); + free(err); + } + } + + delete_process(p); + return 0; +} From a479cac5ed095a7c108bbca87384c6cc7f1251a4 Mon Sep 17 00:00:00 2001 From: ethanxxxl Date: Wed, 14 Jan 2026 02:40:26 -0500 Subject: [PATCH 2/7] updated cffi bindings to match new C lib API --- src/async-process.lisp | 49 +++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/src/async-process.lisp b/src/async-process.lisp index a88b3c6..3d104e9 100644 --- a/src/async-process.lisp +++ b/src/async-process.lisp @@ -5,7 +5,8 @@ :process-send-input :process-receive-output :process-alive-p - :create-process)) + :create-process + :cffi-test)) (in-package :async-process) (eval-when (:compile-toplevel :load-toplevel :execute) @@ -48,7 +49,7 @@ (:unix "libasyncprocess.so") (:windows "libasyncprocess.dll")) -(cffi:use-foreign-library async-process) +(cffi:use-foreign-library #P"/home/ethan/Documents/async-process/.libs/libasyncprocess.so") (defclass process () ((process :reader process-process :initarg :process) @@ -56,7 +57,6 @@ (cffi:defcfun ("create_process" %create-process) :pointer (command :pointer) - (nonblock :boolean) (path :string)) (cffi:defcfun ("delete_process" %delete-process) :void @@ -65,17 +65,30 @@ (cffi:defcfun ("process_pid" %process-pid) :int (process :pointer)) -(cffi:defcfun ("process_send_input" %process-send-input) :void +(cffi:defcfun ("process_write" %process-write) :void + (process :pointer) + (string :string) + (n :size)) + +(cffi:defcfun ("process_write_string" %process-write-string) :void (process :pointer) (string :string)) +(cffi:defcfun ("process_receive_stdout" %process-receive-stdout) :pointer + (process :pointer)) + +(cffi:defcfun ("process_receive_stderr" %process-receive-stderr) :pointer + (process :pointer)) + (cffi:defcfun ("process_receive_output" %process-receive-output) :pointer (process :pointer)) (cffi:defcfun ("process_alive_p" %process-alive-p) :boolean (process :pointer)) -(defun create-process (command &key nonblock (encode cffi:*default-foreign-encoding*) directory) +(cffi:defcfun "cffi_test" :string) + +(defun create-process (command &key (encode cffi:*default-foreign-encoding*) directory) (when (and directory (not (uiop:directory-exists-p directory))) (error "Directory ~S does not exist" directory)) (let* ((command (uiop:ensure-list command)) @@ -85,9 +98,9 @@ :for c :in command :do (setf (cffi:mem-aref argv :string i) c)) (setf (cffi:mem-aref argv :string length) (cffi:null-pointer)) - (let ((p (%create-process argv nonblock (if directory - (namestring directory) - (cffi:null-pointer))))) + (let ((p (%create-process argv (if directory + (namestring directory) + (cffi:null-pointer))))) (if (cffi:null-pointer-p p) (error "create-process failed: ~S" command) (make-instance 'process :process p :encode encode)))))) @@ -100,7 +113,7 @@ (defun process-send-input (process string) (let ((cffi:*default-foreign-encoding* (process-encode process))) - (%process-send-input (process-process process) string))) + (%process-write-string (process-process process) string))) (defun pointer-to-string (pointer) (unless (cffi:null-pointer-p pointer) @@ -116,9 +129,21 @@ ;; Fallback when an error occurs with UTF-8 encoding (map 'string #'code-char octets)))))) -(defun process-receive-output (process) - (let ((cffi:*default-foreign-encoding* (process-encode process))) - (pointer-to-string (%process-receive-output (process-process process))))) +(defun process-receive-output (process &optional (source :both)) + "`source` can be either `:stdout`, `:stderr`, or `:both`. It specifies the stream +to read from." + (flet ((call-cfun (read-func) + "helper function to call one of the three cffi functions for receiving output." + (let ((cffi:*default-foreign-encoding* (process-encode process)) + (output (funcall read-func (process-process process)))) + (prog1 + (pointer-to-string output) + (cffi:foreign-free output))))) + + (case source + (:stdout (call-cfun '%process-receive-stdout)) + (:stderr (call-cfun '%process-receive-stderr)) + (:both (call-cfun '%process-receive-output))))) (defun process-alive-p (process) (%process-alive-p (process-process process))) From 3cc06ef51de2fff8f5f193996fc4b26667ff645f Mon Sep 17 00:00:00 2001 From: ethanxxxl Date: Fri, 16 Jan 2026 00:44:21 -0500 Subject: [PATCH 3/7] fixed memory leaks/errors identified with valgrind --- src/async-process.c | 21 ++++++++++++--------- src/test.c | 8 +++++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/async-process.c b/src/async-process.c index ec73da7..846fae2 100644 --- a/src/async-process.c +++ b/src/async-process.c @@ -22,6 +22,8 @@ static struct process* allocate_process(int fd_io, int pid) { int stdout_ret = 0, stderr_ret = 0; char *io_str = NULL, *er_str = NULL; + size_t io_strlen = strlen(pts_io_name) + 1; + size_t er_strlen = strlen(pts_er_name) + 1; struct process *process = malloc(sizeof(struct process)); if (process == NULL) @@ -35,16 +37,16 @@ static struct process* allocate_process(int fd_io, if (stderr_ret == -1) goto FAILED_MALLOC; - io_str = malloc(strlen(pts_io_name) + 1); + io_str = malloc(io_strlen * sizeof(char)); if (io_str == NULL) goto FAILED_MALLOC; - er_str = malloc(strlen(pts_er_name) + 1); + er_str = malloc(er_strlen * sizeof(char)); if (er_str == NULL) goto FAILED_MALLOC; - strcpy(io_str, pts_io_name); - strcpy(er_str, pts_er_name); + memcpy(io_str, pts_io_name, io_strlen); + memcpy(er_str, pts_er_name, er_strlen); process->pts_io_name = io_str; process->pts_er_name = er_str; @@ -67,8 +69,8 @@ void delete_process(struct process *process) { kill(process->pid, 9); close(process->fd_io); close(process->fd_er); - free(process->stdout.buf); - free(process->stderr.buf); + del_str(&process->stdout); + del_str(&process->stderr); free(process->pts_io_name); free(process->pts_er_name); free(process); @@ -101,12 +103,12 @@ int open_pty(int *fdm, int *fds, char **name) { if (tmp == NULL) goto FAILED_SETUP; - size_t tmp_len = strlen(tmp); + size_t tmp_len = strlen(tmp) + 1; *name = malloc(tmp_len * sizeof(char)); if (*name == NULL) goto FAILED_SETUP; - memcpy(*name, tmp, tmp_len+1); + memcpy(*name, tmp, tmp_len); *fds = open(*name, O_RDWR | O_NOCTTY); if (*fds == -1) @@ -245,7 +247,7 @@ void process_write(struct process *process, const char *buf, size_t n) { int str_read_fd(struct str *str, int fd) { int total_read = 0; while (true) { - // resize buffer if it is too small + // resize buffer if it is too small (include space for null terminator) if (str->cap - str->len <= 1) { char *new_ptr = realloc(str->buf, 2*str->cap); if (new_ptr == NULL) @@ -260,6 +262,7 @@ int str_read_fd(struct str *str, int fd) { if (total_read == 0 && n == -1) { return -1; // an error occured on first read } else if (n <= 0) { + str->buf[str->len] = '\0'; return total_read; } diff --git a/src/test.c b/src/test.c index 2dc7fb6..783a310 100644 --- a/src/test.c +++ b/src/test.c @@ -1,7 +1,7 @@ #include "async-process.h" int main() { - char *cmd[] = {"clangd"}; + char *cmd[] = {"tee", "ima-cool-file", NULL}; struct process *p = create_process(cmd, NULL); struct str s; @@ -12,8 +12,9 @@ int main() { if (n > 0) { process_write(p, s.buf, n); s.len = 0; + if (strcmp(s.buf, "exit\n") == 0) + break; } - char *out = NULL; char *err = NULL; @@ -29,9 +30,10 @@ int main() { if (err != NULL) { printf("\033[31m%s\033[0m", err); free(err); - } + } } delete_process(p); + del_str(&s); return 0; } From 69aad356a2e3a77474d53685a68b384c95bb7b66 Mon Sep 17 00:00:00 2001 From: ethanxxxl Date: Fri, 16 Jan 2026 01:25:02 -0500 Subject: [PATCH 4/7] process_write handles large amounts of data better, returns bytes written --- src/async-process.c | 21 +++++++++++++++++---- src/async-process.h | 4 ++-- src/test.c | 23 ++++++++++++++++++++++- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/async-process.c b/src/async-process.c index 846fae2..0ce25c1 100644 --- a/src/async-process.c +++ b/src/async-process.c @@ -234,12 +234,25 @@ int process_pid(struct process *process) { return process->pid; } -void process_write_string(struct process *process, const char *string) { - write(process->fd_io, string, strlen(string)); +ssize_t process_write_string(struct process *process, const char *string) { + return process_write(process, string, strlen(string)); } -void process_write(struct process *process, const char *buf, size_t n) { - write(process->fd_io, buf, n); +ssize_t process_write(struct process *process, const char *buf, size_t n) { + ssize_t bytes_written = 0; + + while (bytes_written < n) { + ssize_t sent = write(process->fd_io, buf + bytes_written, n - bytes_written); + if (bytes_written != 0 && sent == -1) { + return bytes_written; + } else if (sent == -1) { + return -1; + } + + bytes_written += sent; + } + + return bytes_written; } // reads all data available in fd (should be non-blocking) into str, diff --git a/src/async-process.h b/src/async-process.h index ed2f8f0..a68273a 100644 --- a/src/async-process.h +++ b/src/async-process.h @@ -43,8 +43,8 @@ struct process* create_process(char *const command[], const char *path); void delete_process(struct process *process); int process_pid(struct process *process); -void process_write(struct process *process, const char* buf, size_t n); -void process_write_string(struct process *process, const char *string); +ssize_t process_write(struct process *process, const char* buf, size_t n); +ssize_t process_write_string(struct process *process, const char *string); /** receive process stdout. MUST FREE RETURNED PONTER */ char* process_receive_stdout(struct process *process); diff --git a/src/test.c b/src/test.c index 783a310..765babd 100644 --- a/src/test.c +++ b/src/test.c @@ -7,6 +7,27 @@ int main() { struct str s; init_str(&s); + #define TEST_INPUT_SIZE 50000 + char test_input[TEST_INPUT_SIZE]; + + for (size_t i = 0; i < TEST_INPUT_SIZE; i++) { + test_input[i] = '0' + (i % 10); + } + + size_t n = 0; + while (n != TEST_INPUT_SIZE) { + ssize_t bytes = process_write(p, test_input+n, TEST_INPUT_SIZE-n); + if (bytes > 0) { + printf("writing %d/%d bytes...\n", bytes, n); + n += bytes; + } else { + printf("error: %s\n", strerror(errno)); + } + + } + + printf("I just attempted to write %d.\nI wrote %d bytes.\n", TEST_INPUT_SIZE, n); + while (true) { int n = str_read_fd(&s, STDIN_FILENO); if (n > 0) { @@ -15,7 +36,7 @@ int main() { if (strcmp(s.buf, "exit\n") == 0) break; } - + char *out = NULL; char *err = NULL; From bb48843777da29e75a2028cfee472f30e9fb11ee Mon Sep 17 00:00:00 2001 From: ethanxxxl Date: Fri, 16 Jan 2026 20:47:01 -0500 Subject: [PATCH 5/7] process_write can now send arbitrarily large amounts of data --- src/async-process.c | 140 +++++++++++++++++++++++++------------------- src/async-process.h | 59 ++++++++++++++++--- src/test.c | 18 +++--- 3 files changed, 141 insertions(+), 76 deletions(-) diff --git a/src/async-process.c b/src/async-process.c index 0ce25c1..abadc17 100644 --- a/src/async-process.c +++ b/src/async-process.c @@ -20,7 +20,7 @@ static struct process* allocate_process(int fd_io, const char *pts_io_name, const char *pts_er_name, int pid) { - int stdout_ret = 0, stderr_ret = 0; + int stdout_ret = -1, stderr_ret = -1, both_ret = -1; char *io_str = NULL, *er_str = NULL; size_t io_strlen = strlen(pts_io_name) + 1; size_t er_strlen = strlen(pts_er_name) + 1; @@ -36,6 +36,10 @@ static struct process* allocate_process(int fd_io, stderr_ret = init_str(&process->stderr); if (stderr_ret == -1) goto FAILED_MALLOC; + + both_ret = init_str(&process->both); + if (both_ret == -1) + goto FAILED_MALLOC; io_str = malloc(io_strlen * sizeof(char)); if (io_str == NULL) @@ -58,8 +62,9 @@ static struct process* allocate_process(int fd_io, FAILED_MALLOC: if (process != NULL) free(process); - if (stdout_ret == -1) del_str(&process->stdout); - if (stderr_ret == -1) del_str(&process->stderr); + if (stdout_ret != -1) del_str(&process->stdout); + if (stderr_ret != -1) del_str(&process->stderr); + if (both_ret != -1) del_str(&process->both); if (io_str != NULL) free(io_str); if (er_str != NULL) free(er_str); return NULL; @@ -71,6 +76,7 @@ void delete_process(struct process *process) { close(process->fd_er); del_str(&process->stdout); del_str(&process->stderr); + del_str(&process->both); free(process->pts_io_name); free(process->pts_er_name); free(process); @@ -234,27 +240,6 @@ int process_pid(struct process *process) { return process->pid; } -ssize_t process_write_string(struct process *process, const char *string) { - return process_write(process, string, strlen(string)); -} - -ssize_t process_write(struct process *process, const char *buf, size_t n) { - ssize_t bytes_written = 0; - - while (bytes_written < n) { - ssize_t sent = write(process->fd_io, buf + bytes_written, n - bytes_written); - if (bytes_written != 0 && sent == -1) { - return bytes_written; - } else if (sent == -1) { - return -1; - } - - bytes_written += sent; - } - - return bytes_written; -} - // reads all data available in fd (should be non-blocking) into str, // returns number of bytes read on success, -1 on error. int str_read_fd(struct str *str, int fd) { @@ -286,61 +271,98 @@ int str_read_fd(struct str *str, int fd) { return -1; // control flow shouldn't reach here. } -char* _process_receive_fd(struct str *s, int fd) { +const char* _process_receive_fd(struct str *s, int fd) { int n = str_read_fd(s, fd); if (n == -1) return NULL; - char *ret = malloc((s->len + 1) * sizeof(char)); - if (ret == NULL) - return NULL; - - memcpy(ret, s->buf, s->len); - ret[s->len] = '\0'; - - s->len = 0; - return ret; + return s->buf; } -char* process_receive_stdout(struct process *p) { - return _process_receive_fd(&p->stdout, p->fd_io); +const char* process_receive_stdout(struct process *p) { + const char *r = _process_receive_fd(&p->stdout, p->fd_io); + p->stdout.len = 0; + return r; } -char* process_receive_stderr(struct process *p) { - return _process_receive_fd(&p->stderr, p->fd_er); +const char* process_receive_stderr(struct process *p) { + const char *r = _process_receive_fd(&p->stderr, p->fd_er); + p->stderr.len = 0; + return r; } -char* process_receive_output(struct process *process) { - char *stdout = process_receive_stdout(process); - char *stderr = process_receive_stderr(process); +const char* process_receive_output(struct process *p) { + _process_receive_fd(&p->stdout, p->fd_io); + _process_receive_fd(&p->stderr, p->fd_er); - if (stdout == NULL && stderr == NULL) - return NULL; + // these lengths include null terminators + size_t stdout_len = p->stdout.len; + size_t stderr_len = p->stderr.len; + + if (p->both.cap < (stdout_len + stderr_len)) { + char *new_ptr = realloc(p->both.buf, stdout_len + stderr_len); + if (new_ptr == NULL) + return NULL; + p->both.buf = new_ptr; + p->both.cap = stdout_len + stderr_len; + } + + // don't copy null terminator + if (stdout_len > 0 && p->stdout.buf[stdout_len] == '\0') { + stdout_len--; + } - if (stdout != NULL && stderr == NULL) - return stdout; + memcpy(p->both.buf, p->stdout.buf, stdout_len); + memcpy(p->both.buf + stdout_len, p->stderr.buf, stderr_len); // copy null terminator this time. - if (stdout == NULL && stderr != NULL) - return stderr; + p->both.len = 0; + p->stdout.len = 0; + p->stderr.len = 0; - size_t o_len = strlen(stdout); - size_t e_len = strlen(stderr); - size_t length = o_len + e_len + 1; - char *ret = malloc(length * sizeof(char)); - if (ret == NULL) - return NULL; + return p->both.buf; +} - memcpy(ret, stdout, o_len); - memcpy(ret + o_len, stderr, e_len); +ssize_t _process_write(struct process *process, const char *buf, size_t n, bool readp) { + ssize_t bytes_written = 0; - ret[length] = '\0'; + while (bytes_written < n) { + ssize_t sent = write(process->fd_io, buf + bytes_written, n - bytes_written); + + if (readp) { + _process_receive_fd(&process->stdout, process->fd_io); + _process_receive_fd(&process->stderr, process->fd_er); + } - free(stdout); - free(stderr); + if (bytes_written != 0 && sent == -1) { + return bytes_written; + } else if (sent == -1) { + return -1; + } + + bytes_written += sent; + } + + return bytes_written; +} + +ssize_t process_write(struct process *process, const char *buf, size_t n) { + return _process_write(process, buf, n, true); +} - return ret; +ssize_t process_write_string(struct process *process, const char *string) { + return _process_write(process, string, strlen(string), true); +} + +ssize_t process_write_noread(struct process *process, const char *buf, size_t n) { + return _process_write(process, buf, n, false); } +ssize_t process_write_string_noread(struct process *process, const char *string) { + return _process_write(process, string, strlen(string), false); +} + + + int process_alive_p(struct process *process) { return kill(process->pid, 0) == 0; diff --git a/src/async-process.h b/src/async-process.h index a68273a..d5467c0 100644 --- a/src/async-process.h +++ b/src/async-process.h @@ -31,6 +31,7 @@ int str_read_fd(struct str *str, int fd); struct process { struct str stdout; struct str stderr; + struct str both; int fd_io; int fd_er; @@ -43,16 +44,60 @@ struct process* create_process(char *const command[], const char *path); void delete_process(struct process *process); int process_pid(struct process *process); +/** Sends n bytes to process. + +returns the number of bytes written, or -1 indicating an error occurred. An +error will typically occur when the operating system cannot send all n bytes +because the PTY buffer is full. The process will have to read the buffer in +to make space for more data to be written. + +These functions read from the process STDOUT and STDERR buffers to keep +the process from being blocked. The results are buffered and will be returned +on the next call to a process_receive function. If these functions are used +in a separate thread from the process_receive functions, a race condition may +occur when this function is reading from STDOUT/STDERR (possibly realloc'ing) +while `process-receive_*` is reading from the same buffer. +*/ ssize_t process_write(struct process *process, const char* buf, size_t n); ssize_t process_write_string(struct process *process, const char *string); -/** receive process stdout. MUST FREE RETURNED PONTER */ -char* process_receive_stdout(struct process *process); -/** receive process stderr. MUST FREE RETURNED PONTER */ -char* process_receive_stderr(struct process *process); -/** receive process stdout and stderr (one after another). -MUST FREE RETURNED PONTER */ -char* process_receive_output(struct process *process); +/** Sends n bytes to process. + +returns the number of bytes written, or -1 indicating an error occurred. An +error will typically occur when the operating system cannot send all n bytes +because the PTY buffer is full. The process will have to read the buffer in +to make space for more data to be written. + +Doesn't read devices STDOUT/STDERR file descriptors. If `process_receive* +functions are not called regularly, the internal PTY buffers may fill and +prevent the attached process from continuing to run. `process_write` and +`process_write_string` prevent this from happening, but their usage requires +other considerations. +*/ +ssize_t process_write_noread(struct process *process, const char* buf, size_t n); +ssize_t process_write_string_noread(struct process *process, const char *string); + +/** Return Process STDOUT. +Returns pointer to a buffer containing data returned by process STDOUT buffer. +this buffer will be overwritten by subsequent calls to this function; if +this output is meant to be kept, it should be copied out. +*/ +const char* process_receive_stdout(struct process *process); + +/** Return Process STDERR. +Returns pointer to a buffer containing data returned by process STDERR buffer. +this buffer will be overwritten by subsequent calls to this function; if +this output is meant to be kept, it should be copied out. +*/ +const char* process_receive_stderr(struct process *process); + +/** Receive Process STDOUT and STDERR (one after another). +Returns pointer to a buffer containing data returned by process STDERR and +STDOUT buffer. this buffer will be overwritten by subsequent calls to this + function; if this output is meant to be kept, it should be copied out. +*/ +const char* process_receive_output(struct process *process); + int process_alive_p(struct process *process); #endif diff --git a/src/test.c b/src/test.c index 765babd..37e6ecb 100644 --- a/src/test.c +++ b/src/test.c @@ -7,25 +7,25 @@ int main() { struct str s; init_str(&s); - #define TEST_INPUT_SIZE 50000 + #define TEST_INPUT_SIZE 500000 char test_input[TEST_INPUT_SIZE]; - for (size_t i = 0; i < TEST_INPUT_SIZE; i++) { - test_input[i] = '0' + (i % 10); + for (size_t i = 0; i < TEST_INPUT_SIZE; i+=10) { + memcpy(test_input+i, "123456789\n", 10); } size_t n = 0; while (n != TEST_INPUT_SIZE) { ssize_t bytes = process_write(p, test_input+n, TEST_INPUT_SIZE-n); if (bytes > 0) { - printf("writing %d/%d bytes...\n", bytes, n); + printf("wrote %d/%d bytes...\n", n, TEST_INPUT_SIZE); n += bytes; } else { - printf("error: %s\n", strerror(errno)); + printf("%s: %s\n", strerrorname_np(errno), strerror(errno)); } } - + printf("I just attempted to write %d.\nI wrote %d bytes.\n", TEST_INPUT_SIZE, n); while (true) { @@ -37,20 +37,18 @@ int main() { break; } - char *out = NULL; - char *err = NULL; + const char *out = NULL; + const char *err = NULL; out = process_receive_stdout(p); err = process_receive_stderr(p); if (out != NULL) { printf("%s", out); - free(out); } if (err != NULL) { printf("\033[31m%s\033[0m", err); - free(err); } } From 6f3f9369499393100341be176679b66bd17c4dce Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Thu, 22 Jan 2026 16:33:44 -0500 Subject: [PATCH 6/7] fixed null terminator bug in receive-output functions --- src/async-process.c | 61 +++++++++++++++++++++++------------------- src/async-process.h | 8 +++--- src/async-process.lisp | 60 +++++++++++++++++++---------------------- src/test.lisp | 31 +++++++++++++++++++++ 4 files changed, 96 insertions(+), 64 deletions(-) create mode 100644 src/test.lisp diff --git a/src/async-process.c b/src/async-process.c index abadc17..771bbe0 100644 --- a/src/async-process.c +++ b/src/async-process.c @@ -92,7 +92,8 @@ void my_exit(int status) { // respectively. Name will be malloced and it is the callers responsibility // to free name. On success, return 0. On fail, returns -1. All references // will also be either initialized or set -1/NULL appropriately. -int open_pty(int *fdm, int *fds, char **name) { +// nonblock will set nonblock mode on the master PTY FD if nonblock == true +int open_pty(int *fdm, int *fds, char **name, bool nonblock) { *fdm = -1; *fds = -1; *name = NULL; @@ -125,7 +126,9 @@ int open_pty(int *fdm, int *fds, char **name) { fcntl(*fds, F_SETFD, FD_CLOEXEC); // set master as non-blocking (for get_process_output functions) - fcntl(*fdm, F_SETFL, O_NONBLOCK); + if (nonblock) { + fcntl(*fdm, F_SETFL, O_NONBLOCK); + } // Set raw mode struct termios tty; @@ -146,7 +149,7 @@ int open_pty(int *fdm, int *fds, char **name) { return -1; } -struct process* create_process(char *const command[], const char *path) { +struct process* create_process(char *const command[], const char *path, bool nonblock) { // Unix PTYs are bi-directional communication streams. Typically, a terminal will // combine stdout and stderr and display them in the same output. We want to // keep the outputs separate at this level so master_pty_er is created just to carry @@ -159,11 +162,11 @@ struct process* create_process(char *const command[], const char *path) { char *pts_io_name, *pts_er_name; int ret; - ret = open_pty(&master_pty_io, &slave_pts_io, &pts_io_name); + ret = open_pty(&master_pty_io, &slave_pts_io, &pts_io_name, nonblock); if (ret == -1) goto FAILED_SETUP; - ret = open_pty(&master_pty_er, &slave_pts_er, &pts_er_name); + ret = open_pty(&master_pty_er, &slave_pts_er, &pts_er_name, nonblock); if (ret == -1) goto FAILED_SETUP; @@ -254,13 +257,13 @@ int str_read_fd(struct str *str, int fd) { str->cap *= 2; } - // read as much data from fd as possible. + // read as much data from fd as possible. (read doesn't add '\0') int n = read(fd, str->buf + str->len, str->cap - str->len); if (total_read == 0 && n == -1) { return -1; // an error occured on first read } else if (n <= 0) { - str->buf[str->len] = '\0'; + str->buf[str->len] = '\0'; // cap is always len+1 return total_read; } @@ -271,49 +274,51 @@ int str_read_fd(struct str *str, int fd) { return -1; // control flow shouldn't reach here. } -const char* _process_receive_fd(struct str *s, int fd) { +const char* _process_receive_fd(struct str *s, int fd, size_t *bytes) { int n = str_read_fd(s, fd); - if (n == -1) + + if (n == -1 || s->len == 0) return NULL; + if (bytes != NULL) + *bytes = s->len; // length of str, not including '\0' return s->buf; } -const char* process_receive_stdout(struct process *p) { - const char *r = _process_receive_fd(&p->stdout, p->fd_io); +const char* process_receive_stdout(struct process *p, size_t *bytes) { + const char *r = _process_receive_fd(&p->stdout, p->fd_io, bytes); p->stdout.len = 0; return r; } -const char* process_receive_stderr(struct process *p) { - const char *r = _process_receive_fd(&p->stderr, p->fd_er); +const char* process_receive_stderr(struct process *p, size_t *bytes) { + const char *r = _process_receive_fd(&p->stderr, p->fd_er, bytes); p->stderr.len = 0; return r; } -const char* process_receive_output(struct process *p) { - _process_receive_fd(&p->stdout, p->fd_io); - _process_receive_fd(&p->stderr, p->fd_er); - +const char* process_receive_output(struct process *p, size_t *bytes) { // these lengths include null terminators - size_t stdout_len = p->stdout.len; - size_t stderr_len = p->stderr.len; + size_t stdout_len = 0; + size_t stderr_len = 0; + + _process_receive_fd(&p->stdout, p->fd_io, &stdout_len); + _process_receive_fd(&p->stderr, p->fd_er, &stderr_len); - if (p->both.cap < (stdout_len + stderr_len)) { + if (p->both.cap < (stdout_len + stderr_len + 1)) { char *new_ptr = realloc(p->both.buf, stdout_len + stderr_len); if (new_ptr == NULL) return NULL; p->both.buf = new_ptr; p->both.cap = stdout_len + stderr_len; } - - // don't copy null terminator - if (stdout_len > 0 && p->stdout.buf[stdout_len] == '\0') { - stdout_len--; - } memcpy(p->both.buf, p->stdout.buf, stdout_len); - memcpy(p->both.buf + stdout_len, p->stderr.buf, stderr_len); // copy null terminator this time. + memcpy(p->both.buf + stdout_len, p->stderr.buf, stderr_len); + p->both.buf[stdout_len + stderr_len] = '\0'; + + if (bytes != NULL) + *bytes = stdout_len + stderr_len; p->both.len = 0; p->stdout.len = 0; @@ -329,8 +334,8 @@ ssize_t _process_write(struct process *process, const char *buf, size_t n, bool ssize_t sent = write(process->fd_io, buf + bytes_written, n - bytes_written); if (readp) { - _process_receive_fd(&process->stdout, process->fd_io); - _process_receive_fd(&process->stderr, process->fd_er); + _process_receive_fd(&process->stdout, process->fd_io, NULL); + _process_receive_fd(&process->stderr, process->fd_er, NULL); } if (bytes_written != 0 && sent == -1) { diff --git a/src/async-process.h b/src/async-process.h index d5467c0..8ca5974 100644 --- a/src/async-process.h +++ b/src/async-process.h @@ -40,7 +40,7 @@ struct process { pid_t pid; }; -struct process* create_process(char *const command[], const char *path); +struct process* create_process(char *const command[], const char *path, bool nonblock); void delete_process(struct process *process); int process_pid(struct process *process); @@ -82,21 +82,21 @@ Returns pointer to a buffer containing data returned by process STDOUT buffer. this buffer will be overwritten by subsequent calls to this function; if this output is meant to be kept, it should be copied out. */ -const char* process_receive_stdout(struct process *process); +const char* process_receive_stdout(struct process *process, size_t *bytes); /** Return Process STDERR. Returns pointer to a buffer containing data returned by process STDERR buffer. this buffer will be overwritten by subsequent calls to this function; if this output is meant to be kept, it should be copied out. */ -const char* process_receive_stderr(struct process *process); +const char* process_receive_stderr(struct process *process, size_t *bytes); /** Receive Process STDOUT and STDERR (one after another). Returns pointer to a buffer containing data returned by process STDERR and STDOUT buffer. this buffer will be overwritten by subsequent calls to this function; if this output is meant to be kept, it should be copied out. */ -const char* process_receive_output(struct process *process); +const char* process_receive_output(struct process *process, size_t *bytes); int process_alive_p(struct process *process); diff --git a/src/async-process.lisp b/src/async-process.lisp index 3d104e9..d7ad235 100644 --- a/src/async-process.lisp +++ b/src/async-process.lisp @@ -57,7 +57,8 @@ (cffi:defcfun ("create_process" %create-process) :pointer (command :pointer) - (path :string)) + (path :string) + (noblock :bool)) (cffi:defcfun ("delete_process" %delete-process) :void (process :pointer)) @@ -65,30 +66,33 @@ (cffi:defcfun ("process_pid" %process-pid) :int (process :pointer)) -(cffi:defcfun ("process_write" %process-write) :void +(cffi:defcfun ("process_write" %process-write) :ssize (process :pointer) (string :string) (n :size)) -(cffi:defcfun ("process_write_string" %process-write-string) :void +(cffi:defcfun ("process_write_string" %process-write-string) :ssize (process :pointer) (string :string)) -(cffi:defcfun ("process_receive_stdout" %process-receive-stdout) :pointer - (process :pointer)) +(cffi:defcfun ("process_receive_stdout" %process-receive-stdout) :string + (process :pointer) + (bytes :pointer)) -(cffi:defcfun ("process_receive_stderr" %process-receive-stderr) :pointer - (process :pointer)) +(cffi:defcfun ("process_receive_stderr" %process-receive-stderr) :string + (process :pointer) + (bytes :pointer)) (cffi:defcfun ("process_receive_output" %process-receive-output) :pointer - (process :pointer)) + (process :pointer) + (bytes :pointer)) (cffi:defcfun ("process_alive_p" %process-alive-p) :boolean (process :pointer)) (cffi:defcfun "cffi_test" :string) -(defun create-process (command &key (encode cffi:*default-foreign-encoding*) directory) +(defun create-process (command &key nonblock (encode cffi:*default-foreign-encoding*) directory) (when (and directory (not (uiop:directory-exists-p directory))) (error "Directory ~S does not exist" directory)) (let* ((command (uiop:ensure-list command)) @@ -98,9 +102,11 @@ :for c :in command :do (setf (cffi:mem-aref argv :string i) c)) (setf (cffi:mem-aref argv :string length) (cffi:null-pointer)) - (let ((p (%create-process argv (if directory - (namestring directory) - (cffi:null-pointer))))) + (let ((p (%create-process argv + (if directory + (namestring directory) + (cffi:null-pointer)) + nonblock))) (if (cffi:null-pointer-p p) (error "create-process failed: ~S" command) (make-instance 'process :process p :encode encode)))))) @@ -115,30 +121,20 @@ (let ((cffi:*default-foreign-encoding* (process-encode process))) (%process-write-string (process-process process) string))) -(defun pointer-to-string (pointer) - (unless (cffi:null-pointer-p pointer) - (let* ((bytes (loop :for i :from 0 - :for code := (cffi:mem-aref pointer :unsigned-char i) - :until (zerop code) - :collect code)) - (octets (make-array (length bytes) - :element-type '(unsigned-byte 8) - :initial-contents bytes))) - (handler-case (babel:octets-to-string octets) - (error () - ;; Fallback when an error occurs with UTF-8 encoding - (map 'string #'code-char octets)))))) - (defun process-receive-output (process &optional (source :both)) - "`source` can be either `:stdout`, `:stderr`, or `:both`. It specifies the stream + "`source` can be either `:stdout`, `:stderr`, or `:both`. It specifies the stream to read from." + (declare (optimize (debug 3))) (flet ((call-cfun (read-func) "helper function to call one of the three cffi functions for receiving output." - (let ((cffi:*default-foreign-encoding* (process-encode process)) - (output (funcall read-func (process-process process)))) - (prog1 - (pointer-to-string output) - (cffi:foreign-free output))))) + (cffi:with-foreign-pointer (bytes 8) + (let ((cffi:*default-foreign-encoding* (process-encode process)) + (output (funcall read-func + (process-process process) + bytes))) + (cffi:foreign-string-to-lisp + output + :count (cffi:mem-ref bytes :size)))))) (case source (:stdout (call-cfun '%process-receive-stdout)) diff --git a/src/test.lisp b/src/test.lisp new file mode 100644 index 0000000..859f36b --- /dev/null +++ b/src/test.lisp @@ -0,0 +1,31 @@ +asdf:*central-registry* +(ql:quickload "alexandria") +(ql:quickload "babel") + +(setf asdf:*central-registry* (list #P"/home/ethan/Documents/async-process/src/")) +(asdf:load-asd #P"/home/ethan/Documents/async-process/src/async-process.asd") + +(asdf:load-system "async-process") + +(defvar *proc* nil) +(setf *proc* (async-process:create-process '("tee" "/home/ethan/test.log") + :nonblock t)) + +(format t "~&~a" + (with-output-to-string (s) + (async-process:process-send-input *proc* (format nil "ima bot~%")) + (sleep 0.1) + (format s "~A" (async-process:process-receive-output *proc*)))) + +(async-process:process-send-input *proc* (format nil "bop~%")) +(format t (async-process:process-receive-output *proc* :both)) + +(format t "~&~S" (async-process:process-receive-output *proc* :both)) + +(defun cffi-null-string-test () + (format t "~&~S" + (cffi:with-pointer-to-vector-data + (p (make-array 10 + :element-type '(unsigned-byte 8) + :initial-element 0)) + (cffi:foreign-string-to-lisp p)))) \ No newline at end of file From 5e3b447e661f17777a36af10d461150ddad1ab3b Mon Sep 17 00:00:00 2001 From: Ethan Smith Date: Fri, 23 Jan 2026 13:48:44 -0500 Subject: [PATCH 7/7] removed testing library path --- src/async-process.lisp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/async-process.lisp b/src/async-process.lisp index d7ad235..03d6b53 100644 --- a/src/async-process.lisp +++ b/src/async-process.lisp @@ -49,7 +49,7 @@ (:unix "libasyncprocess.so") (:windows "libasyncprocess.dll")) -(cffi:use-foreign-library #P"/home/ethan/Documents/async-process/.libs/libasyncprocess.so") +; (cffi:use-foreign-library #P"/home/ethan/Documents/async-process/.libs/libasyncprocess.so") (defclass process () ((process :reader process-process :initarg :process)