Commit 7a32dcd0 authored by Manuel Stoeckl's avatar Manuel Stoeckl
Browse files

Add a 'waypipe ssh' subcommand

parent d26bfabf
......@@ -16,12 +16,16 @@ may not be the same on the remote system.
SRV=localhost
/usr/bin/waypipe client /tmp/socket-local &
/usr/bin/waypipe -s /tmp/socket-local client &
ssh -R/tmp/socket-remote:/tmp/socket-local -t $SRV \
/usr/bin/waypipe server /tmp/socket-remote -- /usr/bin/weston-terminal
/usr/bin/waypipe -s /tmp/socket-remote server -- /usr/bin/weston-terminal
kill %1
`waypipe` also provides a more abbreviated syntax for the above:
/usr/bin/waypipe ssh $SRV /usr/bin/weston-terminal
## Installing
Build with meson[0]. A recent version of wayland is required. Man pages are
......
......@@ -28,12 +28,15 @@
#include "util.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client-core.h>
......@@ -190,71 +193,122 @@ static int run_client_child(int chanfd, const char *socket_path)
return EXIT_SUCCESS;
}
int run_client(const char *socket_path)
int run_client(const char *socket_path, bool oneshot, pid_t eol_pid)
{
if (verify_connection() == -1) {
wp_log(WP_ERROR,
"Failed to connect to a wayland compositor.\n");
if (eol_pid) {
waitpid(eol_pid, NULL, 0);
}
return EXIT_FAILURE;
}
wp_log(WP_DEBUG, "A wayland compositor is available. Proceeding.\n");
struct sockaddr_un saddr;
int channelsock;
if (strlen(socket_path) >= sizeof(saddr.sun_path)) {
wp_log(WP_ERROR,
"Socket path is too long and would be truncated: %s\n",
socket_path);
return EXIT_FAILURE;
}
saddr.sun_family = AF_UNIX;
strncpy(saddr.sun_path, socket_path, sizeof(saddr.sun_path) - 1);
channelsock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
int nmaxclients = oneshot ? 1 : 3; // << todo, increase
int channelsock = setup_nb_socket(socket_path, nmaxclients);
if (channelsock == -1) {
wp_log(WP_ERROR, "Error creating socket: %s\n",
strerror(errno));
return EXIT_FAILURE;
}
if (bind(channelsock, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) {
wp_log(WP_ERROR, "Error binding socket: %s\n", strerror(errno));
close(channelsock);
return EXIT_FAILURE;
}
if (listen(channelsock, 3) == -1) {
wp_log(WP_ERROR, "Error listening to socket: %s\n",
strerror(errno));
close(channelsock);
unlink(socket_path);
// Error messages already made
if (eol_pid) {
waitpid(eol_pid, NULL, 0);
}
return EXIT_FAILURE;
}
int retcode = EXIT_SUCCESS;
while (true) {
int chanclient = accept(channelsock, NULL, NULL);
if (chanclient == -1) {
wp_log(WP_DEBUG,
"Connection failure -- too many clients\n");
struct kstack *children = NULL;
/* A large fraction of the logic here is needed if we run in
* 'ssh' mode, but the ssh invocation itself fails while we
* are waiting for a socket accept */
struct pollfd cs;
cs.fd = channelsock;
cs.events = POLL_IN;
cs.revents = 0;
while (1) {
// TODO: figure out a safe, non-polling solution
int r = poll(&cs, 1, 1000);
if (r == -1) {
if (errno == EINTR) {
continue;
}
retcode = EXIT_FAILURE;
break;
}
if (eol_pid) {
int stat;
int wp = waitpid(eol_pid, &stat, WNOHANG);
if (wp > 0) {
wp_log(WP_ERROR, "Child (ssh) died early\n");
eol_pid = 0; // < recycled
retcode = EXIT_FAILURE;
break;
}
}
// scan stack for children, and clean them up!
wait_on_children(&children, WNOHANG);
if (r == 0) {
// Nothing to read
continue;
}
pid_t npid = fork();
if (npid == 0) {
// Run forked process, with the only shared state being
// the new channel socket
close(channelsock);
return run_client_child(chanclient, socket_path);
} else if (npid == -1) {
wp_log(WP_DEBUG, "Fork failure\n");
int chanclient = accept(channelsock, NULL, NULL);
if (chanclient == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// The wakeup may have been spurious
continue;
}
wp_log(WP_ERROR, "Connection failure: %s\n",
strerror(errno));
retcode = EXIT_FAILURE;
break;
} else {
// todo: make an option in which only one client is
// permitted
if (oneshot) {
retcode = run_client_child(
chanclient, socket_path);
break;
} else {
pid_t npid = fork();
if (npid == 0) {
// Run forked process, with the only
// shared state being the new channel
// socket
while (children) {
struct kstack *nxt =
children->nxt;
free(children);
children = nxt;
}
close(channelsock);
run_client_child(chanclient,
socket_path);
// exit path?
return EXIT_SUCCESS;
} else if (npid == -1) {
wp_log(WP_DEBUG, "Fork failure\n");
retcode = EXIT_FAILURE;
break;
} else {
struct kstack *kd = calloc(1,
sizeof(struct kstack));
kd->pid = npid;
kd->nxt = children;
children = kd;
}
continue;
}
}
}
close(channelsock);
unlink(socket_path);
if (eol_pid) {
// Don't return until the child process completes
int status;
waitpid(eol_pid, &status, 0);
}
wait_on_children(&children, 0);
return retcode;
}
......@@ -63,4 +63,3 @@ custom_target(
install_dir: '@0@/man1'.format(mandir)
)
# TODO: version!
......@@ -32,6 +32,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/time.h>
......@@ -42,75 +43,9 @@
#include <unistd.h>
#include <wayland-server-core.h>
int run_server(const char *socket_path, int app_argc, char *const app_argv[])
/* Closes both provided file descriptors */
static int run_server_child(int chanfd, int appfd)
{
wp_log(WP_DEBUG, "I'm a server on %s!\n", socket_path);
wp_log(WP_DEBUG, "Trying to run %d:", app_argc);
for (int i = 0; i < app_argc; i++) {
fprintf(stderr, " %s", app_argv[i]);
}
fprintf(stderr, "\n");
// create another socketpair; one goes to display; one goes to child
int csockpair[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, csockpair);
int flags = fcntl(csockpair[0], F_GETFD);
fcntl(csockpair[0], F_SETFD, flags | FD_CLOEXEC);
pid_t pid = fork();
if (pid == -1) {
wp_log(WP_ERROR, "Fork failed\n");
return EXIT_FAILURE;
} else if (pid == 0) {
char bufs2[16];
sprintf(bufs2, "%d", csockpair[1]);
// Provide the other socket in the pair to child application
unsetenv("WAYLAND_DISPLAY");
setenv("WAYLAND_SOCKET", bufs2, 0);
execv(app_argv[0], app_argv);
wp_log(WP_ERROR, "Failed to execv: %s\n", strerror(errno));
return EXIT_FAILURE;
}
// struct wl_display *display = wl_display_create();
// if (wl_display_add_socket_fd(display, csockpair[0]) == -1) {
// wp_log(WP_ERROR, "Failed to add socket to display
// object\n"); wl_display_destroy(display); return
// EXIT_FAILURE;
// }
int status;
struct sockaddr_un saddr;
int chanfd;
if (strlen(socket_path) >= sizeof(saddr.sun_path)) {
wp_log(WP_ERROR,
"Socket path is too long and would be truncated: %s\n",
socket_path);
return EXIT_FAILURE;
}
saddr.sun_family = AF_UNIX;
strncpy(saddr.sun_path, socket_path, sizeof(saddr.sun_path) - 1);
chanfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (chanfd == -1) {
wp_log(WP_ERROR, "Error creating socket: %s\n",
strerror(errno));
return EXIT_FAILURE;
}
if (connect(chanfd, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) {
wp_log(WP_ERROR, "Error connecting socket: %s\n",
strerror(errno));
close(chanfd);
return EXIT_FAILURE;
}
// A connection has already been established
int appfd = csockpair[0];
/** Main select loop:
* fd -> csockpair[0]
* csockpair[0] -> fd
......@@ -227,18 +162,205 @@ int run_server(const char *socket_path, int app_argc, char *const app_argv[])
break;
}
}
if (waitpid(pid, &status, WNOHANG) > 0) {
break;
}
}
cleanup_translation_map(&fdtransmap);
close(chanfd);
close(appfd);
free(buffer);
return EXIT_SUCCESS;
}
static int connect_to_channel(const char *socket_path)
{
struct sockaddr_un saddr;
int chanfd;
saddr.sun_family = AF_UNIX;
strncpy(saddr.sun_path, socket_path, sizeof(saddr.sun_path) - 1);
chanfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (chanfd == -1) {
wp_log(WP_ERROR, "Error creating socket: %s\n",
strerror(errno));
return -1;
}
if (connect(chanfd, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) {
wp_log(WP_ERROR, "Error connecting to socket (%s): %s\n",
socket_path, strerror(errno));
close(chanfd);
return -1;
}
return chanfd;
}
int run_server(const char *socket_path, bool oneshot, char *const app_argv[])
{
wp_log(WP_DEBUG, "I'm a server on %s, running: %s\n", socket_path,
app_argv[0]);
if (strlen(socket_path) >=
sizeof(((struct sockaddr_un *)NULL)->sun_path)) {
wp_log(WP_ERROR,
"Socket path is too long and would be truncated: %s\n",
socket_path);
return EXIT_FAILURE;
}
// Setup connection to program
char displaypath[256];
sprintf(displaypath, "%s.disp.sock", socket_path);
int wayland_socket = -1, server_link = -1, wdisplay_socket = -1;
if (oneshot) {
int csockpair[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, csockpair) == -1) {
wp_log(WP_ERROR, "Socketpair failed: %s\n",
strerror(errno));
return EXIT_FAILURE;
}
if (set_fnctl_flag(csockpair[0], FD_CLOEXEC) == -1) {
wp_log(WP_ERROR, "Fnctl failed: %s\n", strerror(errno));
return EXIT_FAILURE;
}
wayland_socket = csockpair[1];
server_link = csockpair[0];
} else {
// Bind a socket for WAYLAND_DISPLAY, and listen
int nmaxclients = 128;
wdisplay_socket = setup_nb_socket(displaypath, nmaxclients);
if (wdisplay_socket == -1) {
// Error messages already made
return EXIT_FAILURE;
}
}
// Launch program
pid_t pid = fork();
if (pid == -1) {
wp_log(WP_ERROR, "Fork failed\n");
if (!oneshot) {
unlink(displaypath);
}
return EXIT_FAILURE;
} else if (pid == 0) {
if (oneshot) {
char bufs2[16];
sprintf(bufs2, "%d", wayland_socket);
// Provide the other socket in the pair to child
// application
unsetenv("WAYLAND_DISPLAY");
setenv("WAYLAND_SOCKET", bufs2, 1);
} else {
// Since Wayland 1.15, absolute paths are supported in
// WAYLAND_DISPLAY
unsetenv("WAYLAND_SOCKET");
setenv("WAYLAND_DISPLAY", displaypath, 1);
close(wdisplay_socket);
}
execvp(app_argv[0], app_argv);
wp_log(WP_ERROR, "Failed to execvp \'%s\': %s\n", app_argv[0],
strerror(errno));
return EXIT_FAILURE;
}
if (oneshot) {
// We no longer need to see this side
close(wayland_socket);
}
wp_log(WP_DEBUG, "Server main!\n");
int retval = EXIT_SUCCESS;
if (oneshot) {
int chanfd = connect_to_channel(socket_path);
wp_log(WP_DEBUG, "Oneshot connected\n");
if (chanfd != -1) {
retval = run_server_child(chanfd, server_link);
} else {
retval = EXIT_FAILURE;
}
} else {
struct kstack *children = NULL;
// Poll loop - 1s poll, either child dies, or we have a
// connection
struct pollfd pf;
pf.fd = wdisplay_socket;
pf.events = POLL_IN;
pf.revents = 0;
while (1) {
int r = poll(&pf, 1, 1000);
if (r == -1) {
if (errno == EINTR) {
continue;
}
retval = EXIT_FAILURE;
break;
}
int stat;
if (waitpid(pid, &stat, WNOHANG) > 0) {
wp_log(WP_ERROR, "Child program died early\n");
retval = EXIT_FAILURE;
break;
}
// scan stack for children, and clean them up!
wait_on_children(&children, WNOHANG);
if (r == 0) {
continue;
}
int appfd = accept(wdisplay_socket, NULL, NULL);
if (appfd == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// The wakeup may have been spurious
continue;
}
wp_log(WP_ERROR, "Connection failure: %s\n",
strerror(errno));
retval = EXIT_FAILURE;
break;
} else {
pid_t npid = fork();
if (npid == 0) {
// Run forked process, with the only
// shared state being the new channel
// socket
while (children) {
struct kstack *nxt =
children->nxt;
free(children);
children = nxt;
}
close(wdisplay_socket);
int chanfd = connect_to_channel(
socket_path);
run_server_child(chanfd, appfd);
return EXIT_SUCCESS;
} else if (npid == -1) {
wp_log(WP_DEBUG, "Fork failure\n");
retval = EXIT_FAILURE;
break;
} else {
struct kstack *kd = calloc(1,
sizeof(struct kstack));
kd->pid = npid;
kd->nxt = children;
children = kd;
}
continue;
}
}
// Wait for child processes to exit
wp_log(WP_DEBUG, "Waiting for child handlers\n");
wait_on_children(&children, 0);
}
// todo: scope manipulation, to ensure all cleanups are done
wp_log(WP_DEBUG, "Waiting for child process\n");
int status;
waitpid(pid, &status, 0);
wp_log(WP_DEBUG, "Program ended\n");
return EXIT_SUCCESS;
return retval;
}
......@@ -12,10 +12,11 @@ program=`which weston-terminal`
debug=
debug=-d
# Orange=client, purple=server
($waypipe $debug client /tmp/socket-client 2>&1 | sed 's/.*/\x1b[33m&\x1b[0m/') &
($waypipe -o $debug client 2>&1 | sed 's/.*/\x1b[33m&\x1b[0m/') &
# ssh-to-self; should have a local keypair set up
(ssh -R/tmp/socket-server:/tmp/socket-client localhost $waypipe $debug server /tmp/socket-server -- $program) 2>&1 | sed 's/.*/\x1b[35m&\x1b[0m/'
(ssh -R /tmp/waypipe-server.sock:/tmp/waypipe-client.sock localhost $waypipe -o $debug server -- $program) 2>&1 | sed 's/.*/\x1b[35m&\x1b[0m/'
kill %1
rm -f /tmp/socket-client
rm -f /tmp/socket-server
rm -f /tmp/waypipe-server.sock
rm -f /tmp/waypipe-client.sock
......@@ -39,11 +39,62 @@
#include <sys/time.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
log_cat_t waypipe_loglevel = WP_ERROR;
int set_fnctl_flag(int fd, int the_flag)
{
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
return -1;
}
return fcntl(fd, F_SETFL, flags | the_flag);
}
int setup_nb_socket(const char *socket_path, int nmaxclients)
{
struct sockaddr_un saddr;
int sock;
if (strlen(socket_path) >= sizeof(saddr.sun_path)) {
wp_log(WP_ERROR,
"Socket path is too long and would be truncated: %s\n",
socket_path);
return -1;
}
saddr.sun_family = AF_UNIX;
strncpy(saddr.sun_path, socket_path, sizeof(saddr.sun_path) - 1);
sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (sock == -1) {
wp_log(WP_ERROR, "Error creating socket: %s\n",
strerror(errno));
return -1;
}
if (set_fnctl_flag(sock, O_NONBLOCK | O_CLOEXEC) == -1) {
wp_log(WP_ERROR, "Error making socket nonblocking: %s\n",
strerror(errno));
close(sock);
return -1;
}
if (bind(sock, (struct sockaddr *)&saddr, sizeof(saddr)) == -1) {
wp_log(WP_ERROR, "Error binding socket: %s\n", strerror(errno));
close(sock);
return -1;
}
if (listen(sock, nmaxclients) == -1) {
wp_log(WP_ERROR, "Error listening to socket: %s\n",
strerror(errno));
close(sock);
unlink(socket_path);
return -1;
}
return sock;
}
const char *static_timestamp(void)
{
static char msg[64];
......@@ -189,6 +240,8 @@ void translate_fds(struct fd_translation_map *map, int nfds, const int fds[],
struct stat fsdata;
memset(&fsdata, 0, sizeof(fsdata));
int ret = fstat(the_fd, &fsdata);
// TODO: pipe support, also use fctnl(F_GETFL)
if (ret != -1) {
// We have a file-like object
shadow->memsize = fsdata.st_size;
......@@ -290,6 +343,7 @@ static void apply_diff(size_t size, char *__restrict__ base, size_t diffsize,
uint64_t block = diff_blocks[i];
uint64_t nfrom = block >> 32;
uint64_t nto = (block << 32) >> 32;
// TODO: validation, lest this be even more of a security risk
memcpy(base_blocks + nfrom, diff_blocks + i + 1,
8 * (nto - nfrom));
i += nto - nfrom + 1;
......@@ -483,7 +537,9 @@ static void apply_update(
shadow->mem_mirror = calloc(shadow->memsize, 1);
// The first time only, the transfer data is a direct copy of the source
memcpy(shadow->mem_mirror, transf->data, transf->size);
sprintf(shadow->shm_buf_name, "/waypipe-data_%d", shadow->remote_id);
// The PID should be unique during the lifetime of the program
sprintf(shadow->shm_buf_name, "/waypipe%d-data_%d", getpid(),
shadow->remote_id);
shadow->fd_local = shm_open(
shadow->shm_buf_name, O_RDWR | O_CREAT | O_TRUNC, 0644);
......@@ -539,3 +595,23 @@ ssize_t read_size_then_buf(int fd, char **msg)
*msg = tmpbuf;
return nbytes;
}
void wait_on_children(struct kstack **children, int options)
{
struct kstack *cur = *children;
struct kstack **prv = children;
while (cur) {
int stat;
if (waitpid(cur->pid, &stat, options) > 0) {
wp_log(WP_ERROR, "Child handler %d has died\n",
cur->pid);
struct kstack *nxt = cur->nxt;
free(cur);
cur = nxt;
*prv = nxt;
} else {
prv = &cur->nxt;
cur = cur->nxt;
}
}