]> git.ipfire.org Git - pakfire.git/commitdiff
pty: Add callbacks to stream input and output
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 12 Oct 2024 11:44:47 +0000 (11:44 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 12 Oct 2024 11:44:47 +0000 (11:44 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/libpakfire/include/pakfire/pty.h
src/libpakfire/pty.c

index 32febcf13e1ac4eb66440ad2ebed34f6e4be71ee..6662d9db4a562e4c77ba6cbf0f105d39c44c232f 100644 (file)
@@ -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 */
index 08115af63e32b2df1473729393fd7cb933ce70a0..806266ed8bc4023a6ae5fbaeb7d49368432ee544 100644 (file)
@@ -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;
+}