mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
* Client terminal initialization data is now received
asynchronously. To facilitate this, a couple of minor changes to the
protocol was made:
- The client now starts with sending a 4-byte unsigned integer
with the *total* size of the initialization data (*not*
including the size field itself.
- Strings (TERM variable + argv) are now sent NULL-terminated
* The server allocates a single buffer, and fills it
asynchronously. When full (as indicated by the initial 'total size'
integer), we parse it (taking care not to read outside boundaries
etc, and verifies the lengths (of the TERM variable and argv array)
indicated by the client matches the actual lengths of the strings
received.
* The server now ignores 'unexpected' data received from the client,
after the terminal has been instantiated.
176 lines
4.4 KiB
C
176 lines
4.4 KiB
C
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
#include <getopt.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
|
|
#include <sys/socket.h>
|
|
#include <linux/un.h>
|
|
|
|
#define LOG_MODULE "foot-client"
|
|
#define LOG_ENABLE_DBG 0
|
|
#include "log.h"
|
|
#include "version.h"
|
|
|
|
static volatile sig_atomic_t aborted = 0;
|
|
|
|
static void
|
|
sig_handler(int signo)
|
|
{
|
|
aborted = 1;
|
|
}
|
|
|
|
static void
|
|
print_usage(const char *prog_name)
|
|
{
|
|
printf("Usage: %s [OPTIONS]...\n", prog_name);
|
|
printf("\n");
|
|
printf("Options:\n");
|
|
printf(" -t,--term=TERM value to set the environment variable TERM to (foot)\n"
|
|
" -v,--version show the version number and quit\n");
|
|
}
|
|
|
|
int
|
|
main(int argc, char *const *argv)
|
|
{
|
|
int ret = EXIT_FAILURE;
|
|
|
|
const char *const prog_name = argv[0];
|
|
|
|
static const struct option longopts[] = {
|
|
{"term", required_argument, 0, 't'},
|
|
{"version", no_argument, 0, 'v'},
|
|
{"help", no_argument, 0, 'h'},
|
|
{NULL, no_argument, 0, 0},
|
|
};
|
|
|
|
const char *term = "";
|
|
|
|
while (true) {
|
|
int c = getopt_long(argc, argv, ":t:hv", longopts, NULL);
|
|
if (c == -1)
|
|
break;
|
|
|
|
switch (c) {
|
|
case 't':
|
|
term = optarg;
|
|
break;
|
|
|
|
case 'v':
|
|
printf("footclient version %s\n", FOOT_VERSION);
|
|
return EXIT_SUCCESS;
|
|
|
|
case 'h':
|
|
print_usage(prog_name);
|
|
return EXIT_SUCCESS;
|
|
|
|
case ':':
|
|
fprintf(stderr, "error: -%c: missing required argument\n", optopt);
|
|
return EXIT_FAILURE;
|
|
|
|
case '?':
|
|
fprintf(stderr, "error: -%c: invalid option\n", optopt);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd == -1) {
|
|
LOG_ERRNO("failed to create socket");
|
|
goto err;
|
|
}
|
|
|
|
bool connected = false;
|
|
struct sockaddr_un addr = {.sun_family = AF_UNIX};
|
|
|
|
const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
|
|
if (xdg_runtime != NULL) {
|
|
snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/foot.sock", xdg_runtime);
|
|
|
|
if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0)
|
|
connected = true;
|
|
}
|
|
|
|
if (!connected) {
|
|
strncpy(addr.sun_path, "/tmp/foot.sock", sizeof(addr.sun_path) - 1);
|
|
if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
LOG_ERRNO("failed to connect (is 'foot --server' running?)");
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
const uint16_t term_len = strlen(term) + 1;
|
|
uint32_t total_len = 0;
|
|
|
|
/* Calculate total length */
|
|
total_len += sizeof(term_len) + term_len;
|
|
total_len += sizeof(argc);
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
uint16_t len = strlen(argv[i]) + 1;
|
|
total_len += sizeof(len) + len;
|
|
}
|
|
|
|
LOG_DBG("term-len: %hu, argc: %d, total-len: %u",
|
|
term_len, argc, total_len);
|
|
|
|
if (send(fd, &total_len, sizeof(total_len), 0) != sizeof(total_len)) {
|
|
LOG_ERRNO("failed to send total length to server");
|
|
goto err;
|
|
}
|
|
|
|
if (send(fd, &term_len, sizeof(term_len), 0) != sizeof(term_len) ||
|
|
send(fd, term, term_len, 0) != term_len)
|
|
{
|
|
LOG_ERRNO("failed to send TERM to server");
|
|
goto err;
|
|
}
|
|
|
|
LOG_DBG("argc = %d", argc);
|
|
if (send(fd, &argc, sizeof(argc), 0) != sizeof(argc)) {
|
|
LOG_ERRNO("failed to send argc/argv to server");
|
|
goto err;
|
|
}
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
uint16_t len = strlen(argv[i]) + 1;
|
|
|
|
LOG_DBG("argv[%d] = %s (%hu)", i, argv[i], len);
|
|
|
|
if (send(fd, &len, sizeof(len), 0) != sizeof(len) ||
|
|
send(fd, argv[i], len, 0) != len)
|
|
{
|
|
LOG_ERRNO("failed to send argc/argv to server");
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
const struct sigaction sa = {.sa_handler = &sig_handler};
|
|
if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) {
|
|
LOG_ERRNO("failed to register signal handlers");
|
|
goto err;
|
|
}
|
|
|
|
int exit_code;
|
|
ssize_t rcvd = recv(fd, &exit_code, sizeof(exit_code), 0);
|
|
|
|
if (rcvd == -1 && errno == EINTR)
|
|
assert(aborted);
|
|
else if (rcvd != sizeof(exit_code))
|
|
LOG_ERRNO("failed to read server response");
|
|
else
|
|
ret = exit_code;
|
|
|
|
err:
|
|
if (fd != -1)
|
|
close(fd);
|
|
return ret;
|
|
}
|