From: Michael Tremer Date: Sat, 12 Oct 2024 11:44:47 +0000 (+0000) Subject: pty: Add callbacks to stream input and output X-Git-Tag: 0.9.30~1093 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=36b1781375fb389d61fe9a55462fae011c654bb7;p=pakfire.git pty: Add callbacks to stream input and output Signed-off-by: Michael Tremer --- diff --git a/src/libpakfire/include/pakfire/pty.h b/src/libpakfire/include/pakfire/pty.h index 32febcf13..6662d9db4 100644 --- a/src/libpakfire/include/pakfire/pty.h +++ b/src/libpakfire/include/pakfire/pty.h @@ -46,5 +46,16 @@ int pakfire_pty_drain(struct pakfire_pty* pty); char* pakfire_pty_output(struct pakfire_pty* pty, size_t* length); +typedef int (*pakfire_pty_stdin_callback)( + struct pakfire_ctx* ctx, void* data, char* buffer, size_t length); +typedef int (*pakfire_pty_stdout_callback)( + struct pakfire_ctx* ctx, void* data, const char* line, const size_t length); + +// Standard Input +void pakfire_pty_set_stdin_callback(struct pakfire_pty* pty, + pakfire_pty_stdin_callback callback, void* data); +void pakfire_pty_set_stdout_callback(struct pakfire_pty* pty, + pakfire_pty_stdout_callback callback, void* data); + #endif /* PAKFIRE_PRIVATE */ #endif /* PAKFIRE_PTY_H */ diff --git a/src/libpakfire/pty.c b/src/libpakfire/pty.c index 08115af63..806266ed8 100644 --- a/src/libpakfire/pty.c +++ b/src/libpakfire/pty.c @@ -52,10 +52,18 @@ struct pakfire_pty_stdio { PAKFIRE_PTY_READY_TO_READ = (1 << 0), PAKFIRE_PTY_READY_TO_WRITE = (1 << 1), PAKFIRE_PTY_HANGUP = (1 << 2), + PAKFIRE_PTY_EOF = (1 << 3), } io; // Event Source sd_event_source* event; + + // Callbacks + struct pakfire_pty_callback { + pakfire_pty_stdin_callback stdin_callback; + pakfire_pty_stdout_callback stdout_callback; + void* data; + } callbacks; }; struct pakfire_pty { @@ -132,6 +140,10 @@ static int pakfire_pty_same_inode(struct pakfire_pty* pty, int fd1, int fd2) { struct stat stat2; int r; + // Cannot do this if either file descriptor is not open + if (fd1 < 0 || fd2 < 0) + return 0; + // Stat the first file descriptor r = fstat(fd1, &stat1); if (r < 0) { @@ -305,6 +317,20 @@ static int pakfire_pty_done(struct pakfire_pty* pty, int code) { return 0; } +static int pakfire_pty_send_eof(struct pakfire_pty* pty, int fd) { + const char eof = VEOF; + int r; + + // Send EOF (Ctrl-D) + r = write(fd, &eof, sizeof(eof)); + if (r < 0) { + CTX_ERROR(pty->ctx, "Could not write EOF: %s\n", strerror(errno)); + return -errno; + } + + return 0; +} + static int pakfire_pty_buffer_is_full(struct pakfire_pty* pty, const struct pakfire_pty_stdio* stdio) { if (stdio->buffered >= sizeof(stdio->buffer)) return 1; @@ -330,43 +356,69 @@ static int pakfire_pty_buffer_has_data(struct pakfire_pty* pty, const struct pak Reads as much data as possible into the buffer */ static int pakfire_pty_fill_buffer(struct pakfire_pty* pty, int fd, struct pakfire_pty_stdio* stdio) { - int r; + ssize_t bytes_read = 0; - // Skip this if there is not space left in the buffer - if (pakfire_pty_buffer_is_full(pty, stdio)) - return -ENOBUFS; + // Call the callback (if we have one) + if (stdio->callbacks.stdin_callback) { + bytes_read = stdio->callbacks.stdin_callback(pty->ctx, stdio->callbacks.data, + stdio->buffer + stdio->buffered, sizeof(stdio->buffer) - stdio->buffered); - // Fill the buffer - r = read(fd, stdio->buffer + stdio->buffered, sizeof(stdio->buffer) - stdio->buffered); - if (r < 0) { - return -errno; + // Abort on errors + if (bytes_read < 0) + return bytes_read; - // EOF - } else if (r == 0) { - return 0; + // Otherwise read from the file descriptor + } else if (fd >= 0) { + bytes_read = read(fd, stdio->buffer + stdio->buffered, sizeof(stdio->buffer) - stdio->buffered); - // Successful read - } else { - stdio->buffered += r; + // Abort on errors + if (bytes_read < 0) + return -errno; } - return 0; + // Successful read + stdio->buffered += bytes_read; + + return bytes_read; } /* Writes as much data as possible from the buffer */ static int pakfire_pty_drain_buffer(struct pakfire_pty* pty, int fd, struct pakfire_pty_stdio* stdio) { - ssize_t bytes_written; + ssize_t bytes_written = 0; + char* eol = NULL; - // Nothing to do if the buffer is empty - if (pakfire_pty_buffer_is_empty(pty, stdio)) - return 0; + // Call the callback if possible + if (stdio->callbacks.stdout_callback) { + // Try finding the end of a line + eol = memchr(stdio->buffer, '\n', stdio->buffered); - // Drain the buffer - bytes_written = write(fd, stdio->buffer, stdio->buffered); - if (bytes_written < 0) - return -errno; + // No newline found + if (!eol) { + if (!pakfire_pty_buffer_is_full(pty, stdio)) + return 0; + + CTX_DEBUG(pty->ctx, "Buffer is full. Sending all content\n"); + eol = stdio->buffer + stdio->buffered - 1; + } + + // Call the callback + bytes_written = stdio->callbacks.stdout_callback( + pty->ctx, stdio->callbacks.data, stdio->buffer, eol - stdio->buffer + 1); + + // Abort on error + if (bytes_written < 0) + return bytes_written; + + // Otherwise we write to the file descriptor + } else if (fd >= 0) { + bytes_written = write(fd, stdio->buffer, stdio->buffered); + + // Abort on error + if (bytes_written < 0) + return -errno; + } // Successful write memmove(stdio->buffer, stdio->buffer + bytes_written, stdio->buffered - bytes_written); @@ -391,97 +443,126 @@ static int pakfire_pty_forward(struct pakfire_pty* pty) { return -EINVAL; } + //printf("GOT HERE %x, %x, %x\n", pty->stdin.io, pty->stdout.io, pty->master.io); + while ( ((pty->stdin.io & PAKFIRE_PTY_READY_TO_READ) && !pakfire_pty_buffer_is_full(pty, &pty->stdin)) || ((pty->master.io & PAKFIRE_PTY_READY_TO_WRITE) && pakfire_pty_buffer_has_data(pty, &pty->stdin)) || ((pty->master.io & PAKFIRE_PTY_READY_TO_READ) && !pakfire_pty_buffer_is_full(pty, &pty->stdout)) || ((pty->stdout.io & PAKFIRE_PTY_READY_TO_WRITE) && pakfire_pty_buffer_has_data(pty, &pty->stdout)) ) { + // Read from standard input if (pty->stdin.io & PAKFIRE_PTY_READY_TO_READ) { - r = pakfire_pty_fill_buffer(pty, pty->stdin.fd, &pty->stdin); - if (r < 0) { - switch (-r) { - case EAGAIN: - pty->stdin.io &= ~PAKFIRE_PTY_READY_TO_READ; - break; - - case EIO: - pty->stdin.io |= PAKFIRE_PTY_HANGUP; - break; - - default: - CTX_ERROR(pty->ctx, "Failed reading from standard input: %s\n", - strerror(-r)); - goto ERROR; + if (!pakfire_pty_buffer_is_full(pty, &pty->stdin)) { + r = pakfire_pty_fill_buffer(pty, pty->stdin.fd, &pty->stdin); + if (r < 0) { + switch (-r) { + case EAGAIN: + pty->stdin.io &= ~PAKFIRE_PTY_READY_TO_READ; + break; + + case EIO: + pty->stdin.io |= PAKFIRE_PTY_HANGUP; + break; + + default: + CTX_ERROR(pty->ctx, "Failed reading from standard input: %s\n", + strerror(-r)); + goto ERROR; + } + + // EOF? + } else if (r == 0) { + CTX_DEBUG(pty->ctx, "Received EOF from standard input\n"); + + // We are done reading + pty->stdin.io &= ~PAKFIRE_PTY_READY_TO_READ; + + // And we have reached EOF + pty->stdin.io |= PAKFIRE_PTY_EOF; } - - // EOF? - } else if (r == 0) { - pty->stdin.io |= PAKFIRE_PTY_HANGUP; } } // Write to the master if (pty->master.io & PAKFIRE_PTY_READY_TO_WRITE) { - r = pakfire_pty_drain_buffer(pty, pty->master.fd, &pty->stdin); - if (r < 0) { - switch (-r) { - case EAGAIN: - pty->master.io &= ~PAKFIRE_PTY_READY_TO_WRITE; - break; - - case EIO: - case EPIPE: - case ECONNRESET: - pty->master.io |= PAKFIRE_PTY_HANGUP; - break; - - default: - CTX_ERROR(pty->ctx, "Failed writing to the PTY: %s\n", strerror(-r)); + if (pakfire_pty_buffer_has_data(pty, &pty->stdin)) { + r = pakfire_pty_drain_buffer(pty, pty->master.fd, &pty->stdin); + if (r < 0) { + switch (-r) { + case EAGAIN: + pty->master.io &= ~PAKFIRE_PTY_READY_TO_WRITE; + break; + + case EIO: + case EPIPE: + case ECONNRESET: + pty->master.io |= PAKFIRE_PTY_HANGUP; + break; + + default: + CTX_ERROR(pty->ctx, "Failed writing to the PTY: %s\n", strerror(-r)); + goto ERROR; + } + } + } + + // If the buffer is full drained and we may send EOF + if (pty->stdin.io & PAKFIRE_PTY_EOF) { + if (pakfire_pty_buffer_is_empty(pty, &pty->stdin)) { + r = pakfire_pty_send_eof(pty, pty->master.fd); + if (r < 0) goto ERROR; + + // Don't send EOF again + pty->stdin.io &= ~PAKFIRE_PTY_EOF; } } } // Read from the master if (pty->master.io & PAKFIRE_PTY_READY_TO_READ) { - r = pakfire_pty_fill_buffer(pty, pty->master.fd, &pty->stdout); - if (r < 0) { - switch (-r) { - case EAGAIN: - pty->master.io &= ~PAKFIRE_PTY_READY_TO_READ; - break; - - case EIO: - case EPIPE: - case ECONNRESET: - pty->master.io |= PAKFIRE_PTY_HANGUP; - break; - - default: - CTX_ERROR(pty->ctx, "Failed reading from the PTY: %s\n", strerror(-r)); - goto ERROR; + if (!pakfire_pty_buffer_is_full(pty, &pty->stdout)) { + r = pakfire_pty_fill_buffer(pty, pty->master.fd, &pty->stdout); + if (r < 0) { + switch (-r) { + case EAGAIN: + pty->master.io &= ~PAKFIRE_PTY_READY_TO_READ; + break; + + case EIO: + case EPIPE: + case ECONNRESET: + pty->master.io |= PAKFIRE_PTY_HANGUP; + break; + + default: + CTX_ERROR(pty->ctx, "Failed reading from the PTY: %s\n", strerror(-r)); + goto ERROR; + } } } } // Write to standard output if (pty->stdout.io & PAKFIRE_PTY_READY_TO_WRITE) { - r = pakfire_pty_drain_buffer(pty, pty->stdout.fd, &pty->stdout); - if (r < 0) { - switch (-r) { - case EAGAIN: - pty->stdout.io &= ~PAKFIRE_PTY_READY_TO_WRITE; - break; - - case EIO: - pty->stdout.io |= PAKFIRE_PTY_HANGUP; - break; - - default: - CTX_ERROR(pty->ctx, "Failed writing to standard output: %s\n", strerror(-r)); - goto ERROR; + if (pakfire_pty_buffer_has_data(pty, &pty->stdout)) { + r = pakfire_pty_drain_buffer(pty, pty->stdout.fd, &pty->stdout); + if (r < 0) { + switch (-r) { + case EAGAIN: + pty->stdout.io &= ~PAKFIRE_PTY_READY_TO_WRITE; + break; + + case EIO: + pty->stdout.io |= PAKFIRE_PTY_HANGUP; + break; + + default: + CTX_ERROR(pty->ctx, "Failed writing to standard output: %s\n", strerror(-r)); + goto ERROR; + } } } } @@ -676,8 +757,16 @@ static int pakfire_pty_setup_forwarding(struct pakfire_pty* pty) { // Mark as forwarding pty->state = PAKFIRE_PTY_STATE_FORWARDING; + // Do nothing if we are in read-only mode + if (pakfire_pty_has_flag(pty, PAKFIRE_PTY_READ_ONLY)) { + // Nothing + + // Do nothing if we have a callback set + } else if (pty->stdin.callbacks.stdin_callback) { + // Nothing + // Connect to standard input - if (!pakfire_pty_has_flag(pty, PAKFIRE_PTY_READ_ONLY)) { + } else { pty->stdin.fd = pakfire_pty_reopen(pty, STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); if (pty->stdin.fd < 0) { CTX_DEBUG(pty->ctx, "Could not re-open standard input: %s. Ignoring.\n", strerror(-pty->stdin.fd)); @@ -702,6 +791,10 @@ static int pakfire_pty_setup_forwarding(struct pakfire_pty* pty) { // Close the buffer in the end pty->stdout.close_fd = 1; + // Do nothing if we have a callback set + } else if (pty->stdout.callbacks.stdout_callback) { + // Nothing + // Connect to standard output } else { pty->stdout.fd = pakfire_pty_reopen(pty, STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); @@ -735,7 +828,7 @@ static int pakfire_pty_setup_forwarding(struct pakfire_pty* pty) { } // Connect standard input unless we are in read-only mode - if (!pakfire_pty_has_flag(pty, PAKFIRE_PTY_READ_ONLY)) { + if (pty->stdin.fd >= 0) { // Enable RAW mode r = pakfire_pty_enable_raw_mode(pty); if (r) @@ -752,23 +845,25 @@ static int pakfire_pty_setup_forwarding(struct pakfire_pty* pty) { } // Add standard output to the event loop - r = sd_event_add_io(pty->loop, &pty->stdout.event, - pty->stdout.fd, EPOLLOUT|EPOLLET, pakfire_pty_stdout, pty); - if (r < 0) { - switch (-r) { - case EPERM: - pty->stdout.io |= PAKFIRE_PTY_READY_TO_WRITE; - break; - - default: - CTX_ERROR(pty->ctx, - "Could not add standard output to the event loop: %s\n", strerror(-r)); - return r; + if (pty->stdout.fd >= 0) { + r = sd_event_add_io(pty->loop, &pty->stdout.event, + pty->stdout.fd, EPOLLOUT|EPOLLET, pakfire_pty_stdout, pty); + if (r < 0) { + switch (-r) { + case EPERM: + pty->stdout.io |= PAKFIRE_PTY_READY_TO_WRITE; + break; + + default: + CTX_ERROR(pty->ctx, + "Could not add standard output to the event loop: %s\n", strerror(-r)); + return r; + } } - } - // Set description - sd_event_source_set_description(pty->stdout.event, "pty-stdout"); + // Set description + sd_event_source_set_description(pty->stdout.event, "pty-stdout"); + } return 0; } @@ -1103,3 +1198,24 @@ char* pakfire_pty_output(struct pakfire_pty* pty, size_t* length) { return buffer; } + +/* + Standard Input/Output Callbacks +*/ +void pakfire_pty_set_stdin_callback(struct pakfire_pty* pty, + pakfire_pty_stdin_callback callback, void* data) { + pty->stdin.callbacks.stdin_callback = callback; + pty->stdin.callbacks.data = data; + + // We are now ready to read + pty->stdin.io |= PAKFIRE_PTY_READY_TO_READ; +} + +void pakfire_pty_set_stdout_callback(struct pakfire_pty* pty, + pakfire_pty_stdout_callback callback, void* data) { + pty->stdout.callbacks.stdout_callback = callback; + pty->stdout.callbacks.data = data; + + // We are now ready to write + pty->stdout.io |= PAKFIRE_PTY_READY_TO_WRITE; +}