1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <stdint.h>
5 #include <stdbool.h>
6 #include <unistd.h>
7 #include <getopt.h>
8 #include <signal.h>
9 #include <errno.h>
10 
11 #include <sys/socket.h>
12 #include <sys/un.h>
13 #include <sys/stat.h>
14 
15 #include <tllist.h>
16 
17 #define LOG_MODULE "foot-client"
18 #define LOG_ENABLE_DBG 0
19 #include "log.h"
20 #include "client-protocol.h"
21 #include "debug.h"
22 #include "foot-features.h"
23 #include "macros.h"
24 #include "util.h"
25 #include "version.h"
26 #include "xmalloc.h"
27 
28 struct override {
29     size_t len;
30     char *str;
31 };
32 typedef tll(struct override) override_list_t;
33 
34 static volatile sig_atomic_t aborted = 0;
35 
36 static void
sig_handler(int signo)37 sig_handler(int signo)
38 {
39     aborted = 1;
40 }
41 
42 static ssize_t
sendall(int sock,const void * _buf,size_t len)43 sendall(int sock, const void *_buf, size_t len)
44 {
45     const uint8_t *buf = _buf;
46     size_t left = len;
47 
48     while (left > 0) {
49         ssize_t r = send(sock, buf, left, MSG_NOSIGNAL);
50         if (r < 0) {
51             if (errno == EINTR)
52                 continue;
53             return r;
54         }
55 
56         buf += r;
57         left -= r;
58     }
59 
60     return len;
61 }
62 
63 static const char *
version_and_features(void)64 version_and_features(void)
65 {
66     static char buf[256];
67     snprintf(buf, sizeof(buf), "version: %s %cpgo %cime %cgraphemes %cassertions",
68              FOOT_VERSION,
69              feature_pgo() ? '+' : '-',
70              feature_ime() ? '+' : '-',
71              feature_graphemes() ? '+' : '-',
72              feature_assertions() ?  '+' : '-');
73     return buf;
74 }
75 
76 static void
print_usage(const char * prog_name)77 print_usage(const char *prog_name)
78 {
79     static const char options[] =
80         "\nOptions:\n"
81         "  -t,--term=TERM                           value to set the environment variable TERM to (" FOOT_DEFAULT_TERM ")\n"
82         "  -T,--title=TITLE                         initial window title (foot)\n"
83         "  -a,--app-id=ID                           window application ID (foot)\n"
84         "  -w,--window-size-pixels=WIDTHxHEIGHT     initial width and height, in pixels\n"
85         "  -W,--window-size-chars=WIDTHxHEIGHT      initial width and height, in characters\n"
86         "  -m,--maximized                           start in maximized mode\n"
87         "  -F,--fullscreen                          start in fullscreen mode\n"
88         "  -L,--login-shell                         start shell as a login shell\n"
89         "  -D,--working-directory=DIR               directory to start in (CWD)\n"
90         "  -s,--server-socket=PATH                  path to the server UNIX domain socket (default=$XDG_RUNTIME_DIR/foot-$WAYLAND_DISPLAY.sock)\n"
91         "  -H,--hold                                remain open after child process exits\n"
92         "  -N,--no-wait                             detach the client process from the running terminal, exiting immediately\n"
93         "  -o,--override=[section.]key=value        override configuration option\n"
94         "  -d,--log-level={info|warning|error|none} log level (info)\n"
95         "  -l,--log-colorize=[{never|always|auto}]  enable/disable colorization of log output on stderr\n"
96         "  -v,--version                             show the version number and quit\n"
97         "  -e                                       ignored (for compatibility with xterm -e)\n";
98 
99     printf("Usage: %s [OPTIONS...]\n", prog_name);
100     printf("Usage: %s [OPTIONS...] command [ARGS...]\n", prog_name);
101     puts(options);
102 }
103 
104 static bool NOINLINE
push_override(override_list_t * overrides,const char * s,uint64_t * total_len)105 push_override(override_list_t *overrides, const char *s, uint64_t *total_len)
106 {
107     size_t len = strlen(s) + 1;
108     if (len >= 1 << (8 * sizeof(struct client_string))) {
109         LOG_ERR("override length overflow");
110         return false;
111     }
112 
113     struct override o = {len, xstrdup(s)};
114     tll_push_back(*overrides, o);
115     *total_len += sizeof(struct client_string) + o.len;
116     return true;
117 }
118 
119 int
main(int argc,char * const * argv)120 main(int argc, char *const *argv)
121 {
122     /* Custom exit code, to enable users to differentiate between foot
123      * itself failing, and the client application failiing */
124     static const int foot_exit_failure = -36;
125     int ret = foot_exit_failure;
126 
127     const char *const prog_name = argv[0];
128 
129     static const struct option longopts[] =  {
130         {"term",               required_argument, NULL, 't'},
131         {"title",              required_argument, NULL, 'T'},
132         {"app-id",             required_argument, NULL, 'a'},
133         {"window-size-pixels", required_argument, NULL, 'w'},
134         {"window-size-chars",  required_argument, NULL, 'W'},
135         {"maximized",          no_argument,       NULL, 'm'},
136         {"fullscreen",         no_argument,       NULL, 'F'},
137         {"login-shell",        no_argument,       NULL, 'L'},
138         {"working-directory",  required_argument, NULL, 'D'},
139         {"server-socket",      required_argument, NULL, 's'},
140         {"hold",               no_argument,       NULL, 'H'},
141         {"no-wait",            no_argument,       NULL, 'N'},
142         {"override",           required_argument, NULL, 'o'},
143         {"log-level",          required_argument, NULL, 'd'},
144         {"log-colorize",       optional_argument, NULL, 'l'},
145         {"version",            no_argument,       NULL, 'v'},
146         {"help",               no_argument,       NULL, 'h'},
147         {NULL,                 no_argument,       NULL,   0},
148     };
149 
150     const char *custom_cwd = NULL;
151     const char *server_socket_path = NULL;
152     enum log_class log_level = LOG_CLASS_INFO;
153     enum log_colorize log_colorize = LOG_COLORIZE_AUTO;
154     bool hold = false;
155 
156     /* Used to format overrides */
157     bool no_wait = false;
158 
159     /* For XDG activation */
160     const char *token = getenv("XDG_ACTIVATION_TOKEN");
161     bool xdga_token = token != NULL;
162     size_t token_len = xdga_token ? strlen(token) + 1 : 0;
163 
164     char buf[1024];
165 
166     /* Total packet length, not (yet) including overrides or argv[] */
167     uint64_t total_len = 0;
168 
169     /* malloc:ed and needs to be in scope of all goto's */
170     int fd = -1;
171     char *_cwd = NULL;
172     override_list_t overrides = tll_init();
173     struct client_string *cargv = NULL;
174 
175     while (true) {
176         int c = getopt_long(argc, argv, "+t:T:a:w:W:mFLD:s:HNo:d:l::veh", longopts, NULL);
177         if (c == -1)
178             break;
179 
180         switch (c) {
181         case 't':
182             snprintf(buf, sizeof(buf), "term=%s", optarg);
183             if (!push_override(&overrides, buf, &total_len))
184                 goto err;
185             break;
186 
187         case 'T':
188             snprintf(buf, sizeof(buf), "title=%s", optarg);
189             if (!push_override(&overrides, buf, &total_len))
190                 goto err;
191             break;
192 
193         case 'a':
194             snprintf(buf, sizeof(buf), "app-id=%s", optarg);
195             if (!push_override(&overrides, buf, &total_len))
196                 goto err;
197             break;
198 
199         case 'L':
200             if (!push_override(&overrides, "login-shell=yes", &total_len))
201                 goto err;
202             break;
203 
204         case 'D': {
205             struct stat st;
206             if (stat(optarg, &st) < 0 || !(st.st_mode & S_IFDIR)) {
207                 fprintf(stderr, "error: %s: not a directory\n", optarg);
208                 goto err;
209             }
210             custom_cwd = optarg;
211             break;
212         }
213 
214         case 'w': {
215             unsigned width, height;
216             if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
217                 fprintf(stderr, "error: invalid window-size-pixels: %s\n", optarg);
218                 goto err;
219             }
220 
221             snprintf(buf, sizeof(buf), "initial-window-size-pixels=%ux%u", width, height);
222             if (!push_override(&overrides, buf, &total_len))
223                 goto err;
224             break;
225         }
226 
227         case 'W': {
228             unsigned width, height;
229             if (sscanf(optarg, "%ux%u", &width, &height) != 2 || width == 0 || height == 0) {
230                 fprintf(stderr, "error: invalid window-size-chars: %s\n", optarg);
231                 goto err;
232             }
233 
234             snprintf(buf, sizeof(buf), "initial-window-size-chars=%ux%u", width, height);
235             if (!push_override(&overrides, buf, &total_len))
236                 goto err;
237             break;
238         }
239 
240         case 'm':
241             if (!push_override(&overrides, "initial-window-mode=maximized", &total_len))
242                 goto err;
243             break;
244 
245         case 'F':
246             if (!push_override(&overrides, "initial-window-mode=fullscreen", &total_len))
247                 goto err;
248             break;
249 
250         case 's':
251             server_socket_path = optarg;
252             break;
253 
254         case 'H':
255             hold = true;
256             break;
257 
258         case 'N':
259             no_wait = true;
260             break;
261 
262         case 'o':
263             if (!push_override(&overrides, optarg, &total_len))
264                 goto err;
265             break;
266 
267         case 'd': {
268             int lvl = log_level_from_string(optarg);
269             if (unlikely(lvl < 0)) {
270                 fprintf(
271                     stderr,
272                     "-d,--log-level: %s: argument must be one of %s\n",
273                     optarg,
274                     log_level_string_hint());
275                 goto err;
276             }
277             log_level = lvl;
278             break;
279         }
280 
281         case 'l':
282             if (optarg == NULL || strcmp(optarg, "auto") == 0)
283                 log_colorize = LOG_COLORIZE_AUTO;
284             else if (strcmp(optarg, "never") == 0)
285                 log_colorize = LOG_COLORIZE_NEVER;
286             else if (strcmp(optarg, "always") == 0)
287                 log_colorize = LOG_COLORIZE_ALWAYS;
288             else {
289                 fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg);
290                 goto err;
291             }
292             break;
293 
294         case 'v':
295             printf("footclient %s\n", version_and_features());
296             ret = EXIT_SUCCESS;
297             goto err;
298 
299         case 'h':
300             print_usage(prog_name);
301             ret = EXIT_SUCCESS;
302             goto err;
303 
304         case 'e':
305             break;
306 
307         case '?':
308             goto err;
309         }
310     }
311 
312     argc -= optind;
313     argv += optind;
314 
315     log_init(log_colorize, false, LOG_FACILITY_USER, log_level);
316 
317     fd = socket(AF_UNIX, SOCK_STREAM, 0);
318     if (fd == -1) {
319         LOG_ERRNO("failed to create socket");
320         goto err;
321     }
322 
323     struct sockaddr_un addr = {.sun_family = AF_UNIX};
324 
325     if (server_socket_path != NULL) {
326         strncpy(addr.sun_path, server_socket_path, sizeof(addr.sun_path) - 1);
327         if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
328             LOG_ERR("%s: failed to connect (is 'foot --server' running?)", server_socket_path);
329             goto err;
330         }
331     } else {
332         bool connected = false;
333 
334         const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
335         if (xdg_runtime != NULL) {
336             const char *wayland_display = getenv("WAYLAND_DISPLAY");
337             if (wayland_display != NULL)
338                 snprintf(addr.sun_path, sizeof(addr.sun_path),
339                          "%s/foot-%s.sock", xdg_runtime, wayland_display);
340             else
341                 snprintf(addr.sun_path, sizeof(addr.sun_path),
342                          "%s/foot.sock", xdg_runtime);
343 
344             if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0)
345                 connected = true;
346             else
347                 LOG_WARN("%s: failed to connect, will now try /tmp/foot.sock", addr.sun_path);
348         }
349 
350         if (!connected) {
351             strncpy(addr.sun_path, "/tmp/foot.sock", sizeof(addr.sun_path) - 1);
352             if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
353                 LOG_ERRNO("failed to connect (is 'foot --server' running?)");
354                 goto err;
355             }
356         }
357     }
358 
359     const char *cwd = custom_cwd;
360     if (cwd == NULL) {
361         errno = 0;
362         size_t buf_len = 1024;
363         do {
364             _cwd = xrealloc(_cwd, buf_len);
365             if (getcwd(_cwd, buf_len) == NULL && errno != ERANGE) {
366                 LOG_ERRNO("failed to get current working directory");
367                 goto err;
368             }
369             buf_len *= 2;
370         } while (errno == ERANGE);
371         cwd = _cwd;
372     }
373 
374     /* String lengths, including NULL terminator */
375     const size_t cwd_len = strlen(cwd) + 1;
376     const size_t override_count = tll_length(overrides);
377 
378     const struct client_data data = {
379         .hold = hold,
380         .no_wait = no_wait,
381         .xdga_token = xdga_token,
382         .token_len = token_len,
383         .cwd_len = cwd_len,
384         .override_count = override_count,
385         .argc = argc,
386     };
387 
388     /* Total packet length, not (yet) including argv[] */
389     total_len += sizeof(data) + cwd_len + token_len;
390 
391     /* Add argv[] size to total packet length */
392     cargv = xmalloc(argc * sizeof(cargv[0]));
393     for (size_t i = 0; i < argc; i++) {
394         const size_t arg_len = strlen(argv[i]) + 1;
395 
396         if (arg_len >= 1 << (8 * sizeof(cargv[i].len))) {
397             LOG_ERR("argv length overflow");
398             goto err;
399         }
400 
401         cargv[i].len = arg_len;
402         total_len += sizeof(cargv[i]) + cargv[i].len;
403     }
404 
405     /* Check for size overflows */
406     if (total_len >= 1llu << (8 * sizeof(uint32_t)) ||
407         cwd_len >= 1 << (8 * sizeof(data.cwd_len)) ||
408         token_len >= 1 << (8 * sizeof(data.token_len)) ||
409         override_count > (size_t)(unsigned int)data.override_count ||
410         argc > (int)(unsigned int)data.argc)
411     {
412         LOG_ERR("size overflow");
413         goto err;
414     }
415 
416     /* Send everything except argv[] */
417     if (sendall(fd, &(uint32_t){total_len}, sizeof(uint32_t)) < 0 ||
418         sendall(fd, &data, sizeof(data)) < 0 ||
419         sendall(fd, cwd, cwd_len) < 0)
420     {
421         LOG_ERRNO("failed to send setup packet to server");
422         goto err;
423     }
424 
425     /* Send XDGA token, if we have one */
426     if (xdga_token) {
427         if (sendall(fd, token, token_len) != token_len)
428         {
429             LOG_ERRNO("failed to send xdg activation token to server");
430             goto err;
431         }
432     }
433 
434     /* Send overrides */
435     tll_foreach(overrides, it) {
436         const struct override *o = &it->item;
437         struct client_string s = {o->len};
438         if (sendall(fd, &s, sizeof(s)) < 0 ||
439             sendall(fd, o->str, o->len) < 0)
440         {
441             LOG_ERRNO("failed to send setup packet (overrides) to server");
442             goto err;
443         }
444     }
445 
446     /* Send argv[] */
447     for (size_t i = 0; i < argc; i++) {
448         if (sendall(fd, &cargv[i], sizeof(cargv[i])) < 0 ||
449             sendall(fd, argv[i], cargv[i].len) < 0)
450         {
451             LOG_ERRNO("failed to send setup packet (argv) to server");
452             goto err;
453         }
454     }
455 
456     const struct sigaction sa = {.sa_handler = &sig_handler};
457     if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) {
458         LOG_ERRNO("failed to register signal handlers");
459         goto err;
460     }
461 
462     int exit_code;
463     ssize_t rcvd = recv(fd, &exit_code, sizeof(exit_code), 0);
464 
465     if (rcvd == -1 && errno == EINTR)
466         xassert(aborted);
467     else if (rcvd != sizeof(exit_code))
468         LOG_ERRNO("failed to read server response");
469     else
470         ret = exit_code;
471 
472 err:
473     tll_foreach(overrides, it) {
474         free(it->item.str);
475         tll_remove(overrides, it);
476     }
477     free(cargv);
478     free(_cwd);
479     if (fd != -1)
480         close(fd);
481     log_deinit();
482     return ret;
483 }
484