]> git.ipfire.org Git - pakfire.git/commitdiff
pty: Major refactor
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 22 Mar 2025 15:02:12 +0000 (15:02 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 22 Mar 2025 15:02:12 +0000 (15:02 +0000)
This adds loads of comments to hopefully make this thing more robust. So
far we don't really seem to be able to implement all the features we
need without destroying others.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/pakfire/pty.c

index 8b4d5210569c510682da6cc570dea9da215c328c..4f3c1ab9f8c126d386ec9d32e58b05c6d779bf7e 100644 (file)
@@ -210,7 +210,30 @@ static int pakfire_pty_store_attrs(struct pakfire_pty* pty,
        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;
+
+       return 0;
+}
+
+static int pakfire_pty_buffer_is_empty(struct pakfire_pty* pty, const struct pakfire_pty_stdio* stdio) {
+       if (stdio->buffered == 0)
+               return 1;
+
+       return 0;
+}
+
+static int pakfire_pty_buffer_has_data(struct pakfire_pty* pty, const struct pakfire_pty_stdio* stdio) {
+       if (stdio->buffered)
+               return 1;
+
+       return 0;
+}
+
 static int pakfire_pty_disconnect(struct pakfire_pty* pty) {
+       DEBUG(pty->ctx, "Disconnecting the PTY\n");
+
        // Clear events
        if (pty->master.event)
                pty->master.event = sd_event_source_unref(pty->master.event);
@@ -220,8 +243,6 @@ static int pakfire_pty_disconnect(struct pakfire_pty* pty) {
                pty->stdout.event = sd_event_source_unref(pty->stdout.event);
        if (pty->sigwinch_event)
                pty->sigwinch_event = sd_event_source_unref(pty->sigwinch_event);
-       if (pty->exit_event)
-               pty->exit_event = sd_event_source_unref(pty->exit_event);
 
        // Close the PTY
        if (pty->master.fd >= 0) {
@@ -240,18 +261,18 @@ static int pakfire_pty_drained(struct pakfire_pty* pty) {
        int q;
        int r;
 
+       // We are drained if the file descriptor is closed
+       if (pty->master.fd < 0)
+               return 1;
+
        // We still have data in the buffer
-       if (pty->stdout.buffered)
+       if (pakfire_pty_buffer_has_data(pty, &pty->stdout))
                return 0;
 
        // There is still data in the PTY buffer
        if (pty->master.io & PAKFIRE_PTY_READY_TO_READ)
                return 0;
 
-       // We are drained if the file descriptor is closed
-       if (pty->master.fd < 0)
-               return 1;
-
        // Is there anything in the input buffer?
        r = ioctl(pty->master.fd, TIOCINQ, &q);
        if (r < 0) {
@@ -272,6 +293,8 @@ static int pakfire_pty_drained(struct pakfire_pty* pty) {
        if (q)
                return 0;
 
+       DEBUG(pty->ctx, "We are fully drained\n");
+
        // We are fully drained
        return 1;
 }
@@ -281,6 +304,11 @@ static int pakfire_pty_done(struct pakfire_pty* pty, int code) {
        if (pty->state == PAKFIRE_PTY_STATE_DONE)
                return 0;
 
+       // Mark as done
+       pty->state = PAKFIRE_PTY_STATE_DONE;
+
+       DEBUG(pty->ctx, "PTY is done, code=%d\n", code);
+
        // Close the output
        if (pty->output) {
                fclose(pty->output);
@@ -288,7 +316,11 @@ static int pakfire_pty_done(struct pakfire_pty* pty, int code) {
        }
 
        // Disconnect
-       pakfire_pty_disconnect(pty);
+       //pakfire_pty_disconnect(pty);
+
+       // If there has been an error, we will terminate the event loop
+       if (code)
+               return sd_event_exit(pty->loop, code < 0 ? EXIT_FAILURE : code);
 
        return 0;
 }
@@ -307,27 +339,6 @@ static int pakfire_pty_send_eof(struct pakfire_pty* pty, int fd) {
        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;
-
-       return 0;
-}
-
-static int pakfire_pty_buffer_is_empty(struct pakfire_pty* pty, const struct pakfire_pty_stdio* stdio) {
-       if (stdio->buffered == 0)
-               return 1;
-
-       return 0;
-}
-
-static int pakfire_pty_buffer_has_data(struct pakfire_pty* pty, const struct pakfire_pty_stdio* stdio) {
-       if (stdio->buffered)
-               return 1;
-
-       return 0;
-}
-
 /*
        Maps any CRNL in the buffer to just NL
 */
@@ -580,152 +591,262 @@ static int pakfire_pty_drain_buffer(struct pakfire_pty* pty, int fd, struct pakf
        This function handles the forwarding between the different pipes...
 */
 static int pakfire_pty_forward(struct pakfire_pty* pty) {
+       int did_something;
        int r;
 
-       // Make sure we are in the correct state
-       switch (pty->state) {
-               case PAKFIRE_PTY_STATE_FORWARDING:
-               case PAKFIRE_PTY_STATE_DRAINING:
-                       break;
+       // Forward data for as long as we can...
+       for (;;) {
+               did_something = 0;
+
+#if 0
+               // Print status
+               DEBUG(pty->ctx,
+                       "PTY forward drained=%d\n"
+                       "    master = %c%c%c\n"
+                       "    stdin  = %c%c%c buffered = %zu\n"
+                       "    stdout = %c%c%c buffered = %zu\n",
+                       pakfire_pty_drained(pty),
+                       (pty->master.io & PAKFIRE_PTY_READY_TO_READ ) ? 'R' : '-',
+                       (pty->master.io & PAKFIRE_PTY_READY_TO_WRITE) ? 'W' : '-',
+                       (pty->master.io & PAKFIRE_PTY_HANGUP)         ? 'H' : '-',
+                       (pty->stdin.io  & PAKFIRE_PTY_READY_TO_READ ) ? 'R' : '-',
+                       (pty->stdin.io  & PAKFIRE_PTY_READY_TO_WRITE) ? 'W' : '-',
+                       (pty->stdin.io  & PAKFIRE_PTY_HANGUP)         ? 'H' : '-',
+                       pty->stdin.buffered,
+                       (pty->stdout.io & PAKFIRE_PTY_READY_TO_READ ) ? 'R' : '-',
+                       (pty->stdout.io & PAKFIRE_PTY_READY_TO_WRITE) ? 'W' : '-',
+                       (pty->stdout.io & PAKFIRE_PTY_HANGUP)         ? 'H' : '-',
+                       pty->stdout.buffered
+               );
+#endif
+
+               // Read from standard input if we are asked to read and
+               // there is still space in the buffer..
+               if ((pty->stdin.io & PAKFIRE_PTY_READY_TO_READ)
+                               && !pakfire_pty_buffer_is_full(pty, &pty->stdin)) {
+                       DEBUG(pty->ctx, "Reading from standard input...\n");
+
+                       // Fill up the buffer
+                       r = pakfire_pty_fill_buffer(pty, pty->stdin.fd, &pty->stdin);
+                       if (r < 0) {
+                               switch (-r) {
+                                       // We have been signaled, that currently there is no data available to read
+                                       case EAGAIN:
+                                               pty->stdin.io &= ~PAKFIRE_PTY_READY_TO_READ;
+                                               break;
 
-               default:
-                       return -EINVAL;
-       }
+                                       // There has been a problem reading the data
+                                       case EIO:
+                                               // We are no longer readable
+                                               pty->stdin.io &= ~PAKFIRE_PTY_READY_TO_READ;
 
-       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))
-               ) {
-               // DEBUG(pty->ctx, "PTY forward stdin=%x %zu, stdout=%x %zu, %x, draining=%d, drained=%d\n",
-               //      pty->stdin.io, pty->stdin.buffered, pty->stdout.io, pty->stdout.buffered, pty->master.io,
-               //      pty->state == PAKFIRE_PTY_STATE_DRAINING, pakfire_pty_drained(pty));
-
-               // Read from standard input
-               if (pty->stdin.io & PAKFIRE_PTY_READY_TO_READ) {
-                       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:
-                                                       ERROR(pty->ctx, "Failed reading from standard input: %s\n",
-                                                               strerror(-r));
-                                                       goto ERROR;
-                                       }
+                                               // We want to terminate as soon as possible
+                                               pty->stdin.io |= PAKFIRE_PTY_HANGUP;
 
-                               // EOF?
-                               } else if (r == 0) {
-                                       DEBUG(pty->ctx, "Received EOF from standard input\n");
+                                               // We don't want to be called again
+                                               if (pty->stdin.event) {
+                                                       sd_event_source_unref(pty->stdin.event);
+                                                       pty->stdin.event = NULL;
+                                               }
+                                               break;
 
-                                       // We are done reading
-                                       pty->stdin.io &= ~PAKFIRE_PTY_READY_TO_READ;
+                                       // Any other unexpected error
+                                       default:
+                                               ERROR(pty->ctx, "Failed reading from standard input: %s\n", strerror(-r));
+                                               goto ERROR;
+                               }
+
+                       // EOF?
+                       } else if (r == 0) {
+                               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;
+                               // We want to terminate as soon as possible
+                               pty->stdin.io |= PAKFIRE_PTY_HANGUP;
+
+                               // And we have reached EOF
+                               pty->stdin.io |= PAKFIRE_PTY_EOF;
+
+                               // We don't want to be called again
+                               if (pty->stdin.event) {
+                                       sd_event_source_unref(pty->stdin.event);
+                                       pty->stdin.event = NULL;
                                }
                        }
+
+                       did_something = 1;
                }
 
-               // Write to the master
-               if (pty->master.io & PAKFIRE_PTY_READY_TO_WRITE) {
-                       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:
-                                                       ERROR(pty->ctx, "Failed writing to the PTY: %s\n", strerror(-r));
-                                                       goto ERROR;
-                                       }
-                               }
-                       }
+               // Write to the master if there is any input data
+               if ((pty->master.io & PAKFIRE_PTY_READY_TO_WRITE)
+                               && pakfire_pty_buffer_has_data(pty, &pty->stdin)) {
+                       DEBUG(pty->ctx, "Writing to master...\n");
+
+                       // Drain the input buffer...
+                       r = pakfire_pty_drain_buffer(pty, pty->master.fd, &pty->stdin);
+                       if (r < 0) {
+                               switch (-r) {
+                                       // We have been signaled that we cannot write any more data...
+                                       case EAGAIN:
+                                               pty->master.io &= ~PAKFIRE_PTY_READY_TO_WRITE;
+                                               break;
 
-                       // 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;
+                                       // There hs been a problem writing the data
+                                       case EIO:
+                                       case EPIPE:
+                                       case ECONNRESET:
+                                               // We are no longer readable or writable
+                                               pty->master.io &= ~PAKFIRE_PTY_READY_TO_READ;
+                                               pty->master.io &= ~PAKFIRE_PTY_READY_TO_WRITE;
+
+                                               // We want to hang up as soon as possible
+                                               pty->master.io |= PAKFIRE_PTY_HANGUP;
+
+                                               // We don't want to be called again
+                                               if (pty->master.event) {
+                                                       sd_event_source_unref(pty->master.event);
+                                                       pty->master.event = NULL;
+                                               }
+                                               break;
 
-                                       // Don't send EOF again
-                                       pty->stdin.io &= ~PAKFIRE_PTY_EOF;
+                                       // Log any other errors
+                                       default:
+                                               ERROR(pty->ctx, "Failed writing to the PTY: %s\n", strerror(-r));
+                                               goto ERROR;
                                }
                        }
+
+                       did_something = 1;
                }
 
-               // Read from the master
-               if (pty->master.io & PAKFIRE_PTY_READY_TO_READ) {
-                       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:
-                                                       ERROR(pty->ctx, "Failed reading from 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)
+                               && pakfire_pty_buffer_is_empty(pty, &pty->stdin)) {
+                       // Send EOF
+                       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 until the buffer is full
+               if ((pty->master.io & PAKFIRE_PTY_READY_TO_READ)
+                               && !pakfire_pty_buffer_is_full(pty, &pty->stdout)) {
+                       DEBUG(pty->ctx, "Reading from master...\n");
+
+                       r = pakfire_pty_fill_buffer(pty, pty->master.fd, &pty->stdout);
+                       if (r < 0) {
+                               switch (-r) {
+                                       // We have been signalled that we cannot send any more data...
+                                       case EAGAIN:
+                                               pty->master.io &= ~PAKFIRE_PTY_READY_TO_READ;
+                                               break;
+
+                                       // An problem has occurred...
+                                       case EIO:
+                                       case EPIPE:
+                                       case ECONNRESET:
+                                               // We are no longer readable or writable
+                                               pty->master.io &= ~PAKFIRE_PTY_READY_TO_READ;
+                                               pty->master.io &= ~PAKFIRE_PTY_READY_TO_WRITE;
+
+                                               // We want to hang up as soon as possible
+                                               pty->master.io |= PAKFIRE_PTY_HANGUP;
+
+                                               // We don't want to be called again
+                                               if (pty->master.event) {
+                                                       sd_event_source_unref(pty->master.event);
+                                                       pty->master.event = NULL;
+                                               }
+                                               break;
+
+                                       default:
+                                               ERROR(pty->ctx, "Failed reading from the PTY: %s\n", strerror(-r));
+                                               goto ERROR;
+                               }
+
+                       // EOF?
+                       } else if (r == 0) {
+                               // We are no longer readable
+                               pty->master.io &= ~PAKFIRE_PTY_READY_TO_READ;
+
+                               // We want to hang up as soon as possible
+                               pty->master.io |= PAKFIRE_PTY_HANGUP;
+
+                               // We don't want to be called again
+                               if (pty->master.event) {
+                                       sd_event_source_unref(pty->master.event);
+                                       pty->master.event = NULL;
                                }
                        }
+
+                       did_something = 1;
                }
 
-               // Write to standard output
-               if (pty->stdout.io & PAKFIRE_PTY_READY_TO_WRITE) {
-                       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;
-
-                                               // This is a special hack for when we have data in the buffer
-                                               // but cannot send it to the callback, yet, because we have not
-                                               // read to the end of the line.
-                                               // To avoid getting stuck in this loop for forever, we simply
-                                               // exit and let the loop call us again.
-                                               case ENOMSG:
-                                                       return 0;
-
-                                               default:
-                                                       ERROR(pty->ctx, "Failed writing to standard output: %s\n", strerror(-r));
-                                                       goto ERROR;
-                                       }
+               // Write to standard output until the buffer is drained
+               if ((pty->stdout.io & PAKFIRE_PTY_READY_TO_WRITE)
+                               && pakfire_pty_buffer_has_data(pty, &pty->stdout)) {
+                       DEBUG(pty->ctx, "Writing to standard output...\n");
+
+                       r = pakfire_pty_drain_buffer(pty, pty->stdout.fd, &pty->stdout);
+                       if (r < 0) {
+                               switch (-r) {
+                                       // We have been signalled that we are no longer able to write...
+                                       case EAGAIN:
+                                               pty->stdout.io &= ~PAKFIRE_PTY_READY_TO_WRITE;
+                                               break;
+
+                                       // A problem has occurred...
+                                       case EIO:
+                                               // We are no longer readable or writable
+                                               pty->master.io &= ~PAKFIRE_PTY_READY_TO_READ;
+                                               pty->master.io &= ~PAKFIRE_PTY_READY_TO_WRITE;
+
+                                               // We want to hang up as soon as possible
+                                               pty->master.io |= PAKFIRE_PTY_HANGUP;
+
+                                               // We don't want to be called again
+                                               if (pty->master.event) {
+                                                       sd_event_source_unref(pty->master.event);
+                                                       pty->master.event = NULL;
+                                               }
+                                               break;
+
+                                       // This is a special hack for when we have data in the buffer
+                                       // but cannot send it to the callback, yet, because we have not
+                                       // read to the end of the line.
+                                       // To avoid getting stuck in this loop for forever, we simply
+                                       // exit and let the loop call us again.
+                                       case ENOMSG:
+                                               break;
+                                               // return 0;
+
+                                       default:
+                                               ERROR(pty->ctx, "Failed writing to standard output: %s\n", strerror(-r));
+                                               goto ERROR;
                                }
                        }
+
+                       did_something = 1;
                }
 
-               if ((pty->master.io|pty->stdin.io|pty->stdout.io) & PAKFIRE_PTY_HANGUP)
+               // Terminate the loop if we found nothing to do
+               if (!did_something)
+                       break;
+       }
+
+       if ((pty->master.io|pty->stdin.io|pty->stdout.io) & PAKFIRE_PTY_HANGUP) {
+               DEBUG(pty->ctx, "Something hung up...\n");
+
+               // Terminate the event loop after all buffers have been emptied or cannot be emptied
+               if ((!pakfire_pty_buffer_has_data(pty, &pty->stdout)
+                               || (pty->stdout.io & PAKFIRE_PTY_HANGUP))
+                       && (!pakfire_pty_buffer_has_data(pty, &pty->stdin)
+                               || (pty->stdin.io & PAKFIRE_PTY_HANGUP)))
                        return pakfire_pty_done(pty, 0);
        }
 
@@ -782,7 +903,7 @@ static int pakfire_pty_exit(sd_event_source* source, void* data) {
        DEBUG(self->ctx, "Exiting the PTY...\n");
 
        // Unless we are already drained, try to drain all buffers
-       if (!pakfire_pty_drain(self)) {
+       if (pakfire_pty_drain(self)) {
                if (!(self->master.io & PAKFIRE_PTY_HANGUP))
                        self->master.io |= PAKFIRE_PTY_READY_TO_READ | PAKFIRE_PTY_READY_TO_WRITE;
 
@@ -1215,6 +1336,9 @@ static void pakfire_pty_free(struct pakfire_pty* pty) {
        if (pty->socket[1] >= 0)
                close(pty->socket[1]);
 
+       if (pty->exit_event)
+               sd_event_source_unref(pty->exit_event);
+
        if (pty->loop)
                sd_event_unref(pty->loop);
        if (pty->ctx)