// PTY
struct pakfire_pty* pty;
- // Log pipes
+ // Pipes
struct pakfire_jail_pipes {
+ int stdin[2];
+ int stdout[2];
+ int stderr[2];
+ } pipes;
+
+ // Streams
+ struct pakfire_jail_streams {
+ // Output
+ FILE* stdout;
+ FILE* stderr;
+
// Logging
struct pakfire_log_stream* INFO;
struct pakfire_log_stream* WARN;
#ifdef ENABLE_DEBUG
struct pakfire_log_stream* DEBUG;
#endif /* ENABLE_DEBUG */
- } log;
+ } streams;
// Timeout
sd_event_source* timeout;
// The function to be called once the jail has been set up
pakfire_jail_callback callback;
void* data;
+
+ // Callbacks
+ struct pakfire_jail_exec_callbacks {
+ // Input
+ pakfire_jail_input_callback input;
+ void* input_data;
+
+ // Output
+ pakfire_jail_output_callback output;
+ void* output_data;
+ } callbacks;
};
static int pivot_root(const char* new_root, const char* old_root) {
return 0;
}
+/*
+ Pipes
+*/
+
+static int pakfire_jail_setup_pipe(struct pakfire_jail* self, int (*fds)[2], const int flags) {
+ int r;
+
+ // Setup the pipe
+ r = pipe2(*fds, flags);
+ if (r < 0) {
+ ERROR(self->ctx, "Could not setup pipe: %m\n");
+ return -errno;
+ }
+
+ return 0;
+}
+
+static void pakfire_jail_close_pipe(struct pakfire_jail* self, int fds[2]) {
+ for (unsigned int i = 0; i < 2; i++)
+ if (fds[i] >= 0)
+ close(fds[i]);
+}
+
+/*
+ This is a convenience function to fetch the reading end of a pipe and
+ closes the write end.
+*/
+static int pakfire_jail_get_pipe_to_read(struct pakfire_jail* self, int (*fds)[2]) {
+ // Give the variables easier names to avoid confusion
+ int* fd_read = &(*fds)[0];
+ int* fd_write = &(*fds)[1];
+
+ // Close the write end of the pipe
+ if (*fd_write >= 0) {
+ close(*fd_write);
+ *fd_write = -EBADF;
+ }
+
+ // Return the read end
+ if (*fd_read >= 0)
+ return *fd_read;
+
+ return -EBADF;
+}
+
+static int pakfire_jail_get_pipe_to_write(struct pakfire_jail* self, int (*fds)[2]) {
+ // Give the variables easier names to avoid confusion
+ int* fd_read = &(*fds)[0];
+ int* fd_write = &(*fds)[1];
+
+ // Close the read end of the pipe
+ if (*fd_read >= 0) {
+ close(*fd_read);
+ *fd_read = -EBADF;
+ }
+
+ // Return the write end
+ if (*fd_write >= 0)
+ return *fd_write;
+
+ return -EBADF;
+}
+
+static int pakfire_jail_stdin(sd_event_source* source, int fd, unsigned int events, void* data) {
+ struct pakfire_jail_exec* exec = data;
+ struct pakfire_jail* self = exec->jail;
+ ssize_t bytes_written = 0;
+
+ // Fail if no callback is set
+ if (unlikely(!exec->callbacks.input)) {
+ ERROR(self->ctx, "Tried to call the standard input callback, but it is not set\n");
+ goto CLOSE;
+ }
+
+ // Call the callback
+ bytes_written = exec->callbacks.input(self->ctx, fd, exec->callbacks.input_data);
+ if (bytes_written < 0) {
+ ERROR(self->ctx, "The input callback failed: %s\n", strerror(-bytes_written));
+ return bytes_written;
+
+ // Close the file descriptor if we are done reading
+ } else if (bytes_written == 0) {
+ goto CLOSE;
+ }
+
+ return 0;
+
+CLOSE:
+ pakfire_jail_close_pipe(self, exec->pipes.stdin);
+
+ return 0;
+}
+
+static int pakfire_jail_output(struct pakfire_jail* self, int fd, unsigned int events, FILE* f) {
+ ssize_t bytes_written = 0;
+ ssize_t bytes_read = 0;
+ char buffer[4096];
+
+ for (;;) {
+ bytes_read = read(fd, buffer, sizeof(buffer));
+ if (bytes_read < 0) {
+ switch (errno) {
+ case EAGAIN:
+ return 0;
+
+ default:
+ return -errno;
+ }
+
+ // XXX Should we close the stream here?
+ } else if (bytes_read == 0) {
+ return 0;
+ }
+
+ // Write the buffer to the output
+ if (f) {
+ bytes_written = fwrite(buffer, 1, bytes_read, f);
+ if (bytes_written < bytes_read) {
+ ERROR(self->ctx, "Failed to write output: %m\n");
+ return -errno;
+ }
+ }
+ }
+}
+
+static int pakfire_jail_stdout(sd_event_source* source, int fd, unsigned int events, void* data) {
+ struct pakfire_jail_exec* exec = data;
+
+ return pakfire_jail_output(exec->jail, fd, events, exec->streams.stdout);
+}
+
+static int pakfire_jail_stderr(sd_event_source* source, int fd, unsigned int events, void* data) {
+ struct pakfire_jail_exec* exec = data;
+
+ return pakfire_jail_output(exec->jail, fd, events, exec->streams.stderr);
+}
+
/*
This function replaces any logging in the child process.
switch (priority) {
case LOG_INFO:
- pakfire_log_stream_vprintf(ctx->log.INFO, format, args);
+ pakfire_log_stream_vprintf(ctx->streams.INFO, format, args);
break;
case LOG_WARNING:
- pakfire_log_stream_vprintf(ctx->log.WARN, format, args);
+ pakfire_log_stream_vprintf(ctx->streams.WARN, format, args);
break;
case LOG_ERR:
- pakfire_log_stream_vprintf(ctx->log.ERROR, format, args);
+ pakfire_log_stream_vprintf(ctx->streams.ERROR, format, args);
break;
#ifdef ENABLE_DEBUG
case LOG_DEBUG:
- pakfire_log_stream_vprintf(ctx->log.DEBUG, format, args);
+ pakfire_log_stream_vprintf(ctx->streams.DEBUG, format, args);
break;
#endif /* ENABLE_DEBUG */
Performs the initialisation that needs to happen in the parent part
*/
static int pakfire_jail_parent(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
+ int fd = -EBADF;
int r;
// Register the PID file descriptor
return r;
}
+ // Prepare standard input for writing
+ fd = pakfire_jail_get_pipe_to_write(jail, &ctx->pipes.stdin);
+ if (fd >= 0) {
+ r = sd_event_add_io(ctx->loop, NULL, fd, EPOLLOUT|EPOLLHUP|EPOLLET, pakfire_jail_stdin, ctx);
+ if (r < 0) {
+ ERROR(jail->ctx, "Failed to register standard input for writing: %s\n", strerror(-r));
+ return r;
+ }
+ }
+
+ // Prepare standard output for reading
+ fd = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stdout);
+ if (fd >= 0) {
+ r = sd_event_add_io(ctx->loop, NULL, fd, EPOLLIN|EPOLLHUP|EPOLLET, pakfire_jail_stdout, ctx);
+ if (r < 0) {
+ ERROR(jail->ctx, "Failed to register standard output for reading: %s\n", strerror(-r));
+ return r;
+ }
+ }
+
+ // Prepare standard error for reading
+ fd = pakfire_jail_get_pipe_to_read(jail, &ctx->pipes.stderr);
+ if (fd >= 0) {
+ r = sd_event_add_io(ctx->loop, NULL, fd, EPOLLIN|EPOLLHUP|EPOLLET, pakfire_jail_stderr, ctx);
+ if (r < 0) {
+ ERROR(jail->ctx, "Failed to register standard error for reading: %s\n", strerror(-r));
+ return r;
+ }
+ }
+
// Setup logging
- r = pakfire_log_stream_in_parent(ctx->log.INFO, ctx->loop);
+ r = pakfire_log_stream_in_parent(ctx->streams.INFO, ctx->loop);
if (r)
return r;
- r = pakfire_log_stream_in_parent(ctx->log.ERROR, ctx->loop);
+ r = pakfire_log_stream_in_parent(ctx->streams.ERROR, ctx->loop);
if (r)
return r;
#ifdef ENABLE_DEBUG
- r = pakfire_log_stream_in_parent(ctx->log.DEBUG, ctx->loop);
+ r = pakfire_log_stream_in_parent(ctx->streams.DEBUG, ctx->loop);
if (r)
return r;
#endif /* ENABLE_DEBUG */
return 0;
}
+static int pakfire_jail_connect_null(struct pakfire_jail* self, int fileno) {
+ int fd = -EBADF;
+ int flags = 0;
+ int r;
+
+ switch (fileno) {
+ case STDIN_FILENO:
+ flags |= O_RDONLY;
+ break;
+
+ case STDOUT_FILENO:
+ case STDERR_FILENO:
+ flags |= O_WRONLY;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+
+ // Open /dev/null
+ fd = open("/dev/null", flags);
+ if (fd < 0) {
+ ERROR(self->ctx, "Failed to open /dev/null: %m\n");
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Copy to the desired file descriptor
+ r = dup2(fd, fileno);
+ if (r < 0) {
+ ERROR(self->ctx, "Failed to duplicate the file descriptor: %m\n");
+ r = -errno;
+ goto ERROR;
+ }
+
+ERROR:
+ if (fd >= 0)
+ close(fd);
+
+ return r;
+}
+
static int pakfire_jail_child(struct pakfire_jail* jail, struct pakfire_jail_exec* ctx) {
int r;
}
// Open a new PTY
- r = pakfire_pty_open(ctx->pty);
- if (r < 0) {
- ERROR(jail->ctx, "Could not open a new PTY: %s\n", strerror(-r));
- return r;
+ if (ctx->pty) {
+ r = pakfire_pty_open(ctx->pty);
+ if (r < 0) {
+ ERROR(jail->ctx, "Could not open a new PTY: %s\n", strerror(-r));
+ return r;
+ }
+
+ // If we don't have a PTY, we will connect the logging pipes
+ } else {
+ // Standard input
+ if (ctx->pipes.stdin[0] >= 0) {
+ r = dup2(ctx->pipes.stdin[0], STDIN_FILENO);
+ if (r < 0) {
+ ERROR(jail->ctx, "Failed to connect %d to stdin: %m\n", ctx->pipes.stdin[0]);
+ return -errno;
+ }
+ } else {
+ r = pakfire_jail_connect_null(jail, STDIN_FILENO);
+ if (r < 0)
+ return r;
+ }
+
+ // Standard output
+ if (ctx->pipes.stdout[1] >= 1) {
+ r = dup2(ctx->pipes.stdout[1], STDOUT_FILENO);
+ if (r < 0) {
+ ERROR(jail->ctx, "Failed to connect %d to stdout: %m\n", ctx->pipes.stdout[1]);
+ return -errno;
+ }
+ } else {
+ r = pakfire_jail_connect_null(jail, STDOUT_FILENO);
+ if (r < 0)
+ return r;
+ }
+
+ // Standard error
+ if (ctx->pipes.stderr[1] >= 1) {
+ r = dup2(ctx->pipes.stderr[1], STDERR_FILENO);
+ if (r < 0) {
+ ERROR(jail->ctx, "Failed to connect %d to stderr: %m\n", ctx->pipes.stderr[1]);
+ return -errno;
+ }
+ } else {
+ r = pakfire_jail_connect_null(jail, STDERR_FILENO);
+ if (r < 0)
+ return r;
+ }
+
+ // Close the pipes entirely as the reading/writing ends
+ // that we need have been moved to stdin/stdout/stderr.
+ pakfire_jail_close_pipe(jail, ctx->pipes.stdin);
+ pakfire_jail_close_pipe(jail, ctx->pipes.stdout);
+ pakfire_jail_close_pipe(jail, ctx->pipes.stderr);
}
// Setup logging
- r = pakfire_log_stream_in_child(ctx->log.INFO);
+ r = pakfire_log_stream_in_child(ctx->streams.INFO);
if (r)
return r;
- r = pakfire_log_stream_in_child(ctx->log.WARN);
+ r = pakfire_log_stream_in_child(ctx->streams.WARN);
if (r)
return r;
- r = pakfire_log_stream_in_child(ctx->log.ERROR);
+ r = pakfire_log_stream_in_child(ctx->streams.ERROR);
if (r)
return r;
#ifdef ENABLE_DEBUG
- r = pakfire_log_stream_in_child(ctx->log.DEBUG);
+ r = pakfire_log_stream_in_child(ctx->streams.DEBUG);
if (r)
return r;
#endif /* ENABLE_DEBUG */
pakfire_pty_stdin_callback stdin_callback, void* stdin_data,
pakfire_pty_stdout_callback stdout_callback, void* stdout_data,
char** output, size_t* output_length) {
+ struct pakfire_log_stream* stdout = NULL;
+ struct pakfire_log_stream* stderr = NULL;
int r;
// We cannot have both, an output callback and buffer
struct pakfire_jail_exec ctx = {
.jail = jail,
.flags = flags,
- .pidfd = -1,
+ .pidfd = -EBADF,
// Callback & Data
.callback = callback,
.data = data,
+
+ // Pipes
+ .pipes = {
+ .stdin = { -EBADF, -EBADF },
+ .stdout = { -EBADF, -EBADF },
+ .stderr = { -EBADF, -EBADF },
+ },
};
DEBUG(jail->ctx, "Executing jail...\n");
goto ERROR;
}
- // Do nothing if we have a callback set
- } else if (stdout_callback) {
- // Perform line-buffering on the callback
- r = pakfire_pty_log_stream(ctx.pty, stdout_callback, stdout_data);
- if (r < 0) {
- ERROR(jail->ctx, "Failed to setup log stream: %s\n", strerror(-r));
+ // Otherwise we are running a non-interactive session
+ } else {
+ // Create a pipe for the standard input only when a callback is set
+ if (stdin_callback) {
+ r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdin, O_NONBLOCK);
+ if (r < 0)
+ goto ERROR;
+ }
+
+ // Create a pipe for standard output
+ r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stdout, O_NONBLOCK);
+ if (r < 0)
+ goto ERROR;
+
+ // Create a pipe for standard error
+ r = pakfire_jail_setup_pipe(jail, &ctx.pipes.stderr, O_NONBLOCK);
+ if (r < 0)
goto ERROR;
+
+ // Setup some buffer if we want to capture the output
+ if (output) {
+ ctx.streams.stdout = open_memstream(output, output_length);
+ if (!ctx.streams.stdout) {
+ ERROR(jail->ctx, "Failed to open memory stream for output: %m\n");
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Otherwise send the output to the log streamer
+ } else {
+ // Create a new stream
+ r = pakfire_log_stream_create(&stdout, jail->ctx, stdout_callback, stdout_data);
+ if (r < 0) {
+ ERROR(jail->ctx, "Failed to create standard output stream: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ // Ask to stream our output into this
+ ctx.streams.stdout = pakfire_log_stream_as_file(stdout);
+ if (!ctx.streams.stdout) {
+ ERROR(jail->ctx, "Failed to open file stream for standard output: %m\n");
+ r = -errno;
+ goto ERROR;
+ }
}
- // Capture Output?
- } else if (output) {
-#warning This needs to be moved back internally again
-#if 0
- r = pakfire_pty_capture_output(ctx.pty, output, output_length);
+ // Send the standard error output to a log streamer, too
+ r = pakfire_log_stream_create(&stderr, jail->ctx, stdout_callback, stdout_data);
if (r < 0) {
- ERROR(jail->ctx, "Failed to set up output capture: %s\n", strerror(-r));
+ ERROR(jail->ctx, "Failed to create standard error stream: %s\n", strerror(-r));
goto ERROR;
}
-#endif
- // Otherwise we dump everything to the console
- } else {
- // XXX Need to find a solution about what to do here...
+ // Ask to stream our output into this
+ ctx.streams.stderr = pakfire_log_stream_as_file(stderr);
+ if (!ctx.streams.stderr) {
+ ERROR(jail->ctx, "Failed to open file stream for standard output: %m\n");
+ r = -errno;
+ goto ERROR;
+ }
}
/*
goto ERROR;
}
+#if 0
// Configure the callbacks
if (stdin_callback)
pakfire_pty_set_stdin_callback(ctx.pty, stdin_callback, stdin_data);
+#endif
// Setup pipes for logging
// INFO
- r = pakfire_log_stream_create(&ctx.log.INFO, jail->ctx, pakfire_jail_INFO, jail);
+ r = pakfire_log_stream_create(&ctx.streams.INFO, jail->ctx, pakfire_jail_INFO, jail);
if (r)
goto ERROR;
// WARN
- r = pakfire_log_stream_create(&ctx.log.WARN, jail->ctx, pakfire_jail_WARN, jail);
+ r = pakfire_log_stream_create(&ctx.streams.WARN, jail->ctx, pakfire_jail_WARN, jail);
if (r)
goto ERROR;
// ERROR
- r = pakfire_log_stream_create(&ctx.log.ERROR, jail->ctx, pakfire_jail_ERROR, jail);
+ r = pakfire_log_stream_create(&ctx.streams.ERROR, jail->ctx, pakfire_jail_ERROR, jail);
if (r)
goto ERROR;
#ifdef ENABLE_DEBUG
// DEBUG
- r = pakfire_log_stream_create(&ctx.log.DEBUG, jail->ctx, pakfire_jail_DEBUG, jail);
+ r = pakfire_log_stream_create(&ctx.streams.DEBUG, jail->ctx, pakfire_jail_DEBUG, jail);
if (r)
goto ERROR;
#endif /* ENABLE_DEBUG */
pakfire_cgroup_unref(ctx.cgroup);
}
+ // Close any pipes
+ pakfire_jail_close_pipe(jail, ctx.pipes.stdin);
+ pakfire_jail_close_pipe(jail, ctx.pipes.stdout);
+ pakfire_jail_close_pipe(jail, ctx.pipes.stderr);
+
// Close any file descriptors
if (ctx.pidfd >= 0)
close(ctx.pidfd);
if (ctx.pty)
pakfire_pty_unref(ctx.pty);
+ // Streams
+ if (ctx.streams.stdout)
+ fclose(ctx.streams.stdout);
+ if (ctx.streams.stderr)
+ fclose(ctx.streams.stderr);
+ if (stdout)
+ pakfire_log_stream_unref(stdout);
+ if (stderr)
+ pakfire_log_stream_unref(stderr);
+
// Logging
- if (ctx.log.INFO)
- pakfire_log_stream_unref(ctx.log.INFO);
- if (ctx.log.WARN)
- pakfire_log_stream_unref(ctx.log.WARN);
- if (ctx.log.ERROR)
- pakfire_log_stream_unref(ctx.log.ERROR);
+ if (ctx.streams.INFO)
+ pakfire_log_stream_unref(ctx.streams.INFO);
+ if (ctx.streams.WARN)
+ pakfire_log_stream_unref(ctx.streams.WARN);
+ if (ctx.streams.ERROR)
+ pakfire_log_stream_unref(ctx.streams.ERROR);
#ifdef ENABLE_DEBUG
- if (ctx.log.DEBUG)
- pakfire_log_stream_unref(ctx.log.DEBUG);
+ if (ctx.streams.DEBUG)
+ pakfire_log_stream_unref(ctx.streams.DEBUG);
#endif /* ENABLE_DEBUG */
if (ctx.timeout)