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 {
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) {
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;
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);
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;
+ }
}
}
}
// 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));
// 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);
}
// 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)
}
// 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;
}
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;
+}