Line | Count | Source |
1 | | /* $OpenBSD: tmux.c,v 1.221 2026/06/29 18:17:28 nicm Exp $ */ |
2 | | |
3 | | /* |
4 | | * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 | | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 | | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | */ |
18 | | |
19 | | #include <sys/types.h> |
20 | | #include <sys/stat.h> |
21 | | #include <sys/utsname.h> |
22 | | |
23 | | #include <errno.h> |
24 | | #include <fcntl.h> |
25 | | #include <langinfo.h> |
26 | | #include <locale.h> |
27 | | #include <pwd.h> |
28 | | #include <signal.h> |
29 | | #include <stdlib.h> |
30 | | #include <string.h> |
31 | | #include <time.h> |
32 | | #include <unistd.h> |
33 | | |
34 | | #include "tmux.h" |
35 | | |
36 | | struct options *global_options; /* server options */ |
37 | | struct options *global_s_options; /* session options */ |
38 | | struct options *global_w_options; /* window options */ |
39 | | struct environ *global_environ; |
40 | | |
41 | | struct timeval start_time; |
42 | | const char *socket_path; |
43 | | int ptm_fd = -1; |
44 | | const char *shell_command; |
45 | | |
46 | | static __dead void usage(int); |
47 | | static char *make_label(const char *, char **); |
48 | | |
49 | | static int areshell(const char *); |
50 | | static const char *getshell(void); |
51 | | |
52 | | #ifdef ASAN |
53 | | __attribute__((used)) const char *__asan_default_options(void); |
54 | | __attribute__((used)) const char * |
55 | | __asan_default_options(void) |
56 | | { |
57 | | return ( |
58 | | "abort_on_error=1:" |
59 | | "halt_on_error=1:" |
60 | | "detect_leaks=0:" |
61 | | "detect_stack_use_after_return=1:" |
62 | | "strict_string_checks=1:" |
63 | | "check_initialization_order=1:" |
64 | | "log_path=/tmp/tmux-asan:" |
65 | | "log_exe_name=1" |
66 | | ); |
67 | | } |
68 | | #endif |
69 | | |
70 | | static __dead void |
71 | | usage(int status) |
72 | 0 | { |
73 | 0 | fprintf(status ? stderr : stdout, |
74 | 0 | "usage: %s [-2CDhlNuVv] [-c shell-command] [-f file] [-L socket-name]\n" |
75 | 0 | " [-S socket-path] [-T features] [command [flags]]\n", |
76 | 0 | getprogname()); |
77 | 0 | exit(status); |
78 | 0 | } |
79 | | |
80 | | static const char * |
81 | | getshell(void) |
82 | 0 | { |
83 | 0 | struct passwd *pw; |
84 | 0 | const char *shell; |
85 | |
|
86 | 0 | shell = getenv("SHELL"); |
87 | 0 | if (checkshell(shell)) |
88 | 0 | return (shell); |
89 | | |
90 | 0 | pw = getpwuid(getuid()); |
91 | 0 | if (pw != NULL && checkshell(pw->pw_shell)) |
92 | 0 | return (pw->pw_shell); |
93 | | |
94 | 0 | return (_PATH_BSHELL); |
95 | 0 | } |
96 | | |
97 | | int |
98 | | checkshell(const char *shell) |
99 | 0 | { |
100 | 0 | if (shell == NULL || *shell != '/') |
101 | 0 | return (0); |
102 | 0 | if (areshell(shell)) |
103 | 0 | return (0); |
104 | 0 | if (access(shell, X_OK) != 0) |
105 | 0 | return (0); |
106 | 0 | return (1); |
107 | 0 | } |
108 | | |
109 | | static int |
110 | | areshell(const char *shell) |
111 | 0 | { |
112 | 0 | const char *progname, *ptr; |
113 | |
|
114 | 0 | if ((ptr = strrchr(shell, '/')) != NULL) |
115 | 0 | ptr++; |
116 | 0 | else |
117 | 0 | ptr = shell; |
118 | 0 | progname = getprogname(); |
119 | 0 | if (*progname == '-') |
120 | 0 | progname++; |
121 | 0 | if (strcmp(ptr, progname) == 0) |
122 | 0 | return (1); |
123 | 0 | return (0); |
124 | 0 | } |
125 | | |
126 | | static char * |
127 | | expand_path(const char *path, const char *home) |
128 | 0 | { |
129 | 0 | char *expanded, *name; |
130 | 0 | const char *end; |
131 | 0 | struct environ_entry *value; |
132 | |
|
133 | 0 | if (strncmp(path, "~/", 2) == 0) { |
134 | 0 | if (home == NULL) |
135 | 0 | return (NULL); |
136 | 0 | xasprintf(&expanded, "%s%s", home, path + 1); |
137 | 0 | return (expanded); |
138 | 0 | } |
139 | | |
140 | 0 | if (*path == '$') { |
141 | 0 | end = strchr(path, '/'); |
142 | 0 | if (end == NULL) |
143 | 0 | name = xstrdup(path + 1); |
144 | 0 | else |
145 | 0 | name = xstrndup(path + 1, end - path - 1); |
146 | 0 | value = environ_find(global_environ, name); |
147 | 0 | free(name); |
148 | 0 | if (value == NULL) |
149 | 0 | return (NULL); |
150 | 0 | if (end == NULL) |
151 | 0 | end = ""; |
152 | 0 | xasprintf(&expanded, "%s%s", value->value, end); |
153 | 0 | return (expanded); |
154 | 0 | } |
155 | | |
156 | 0 | return (xstrdup(path)); |
157 | 0 | } |
158 | | |
159 | | static void |
160 | | expand_paths(const char *s, char ***paths, u_int *n, int no_realpath) |
161 | 0 | { |
162 | 0 | const char *home = find_home(); |
163 | 0 | char *copy, *next, *tmp, resolved[PATH_MAX], *expanded; |
164 | 0 | char *path; |
165 | 0 | u_int i; |
166 | |
|
167 | 0 | *paths = NULL; |
168 | 0 | *n = 0; |
169 | |
|
170 | 0 | copy = tmp = xstrdup(s); |
171 | 0 | while ((next = strsep(&tmp, ":")) != NULL) { |
172 | 0 | expanded = expand_path(next, home); |
173 | 0 | if (expanded == NULL) { |
174 | 0 | log_debug("%s: invalid path: %s", __func__, next); |
175 | 0 | continue; |
176 | 0 | } |
177 | 0 | if (no_realpath) |
178 | 0 | path = expanded; |
179 | 0 | else { |
180 | 0 | if (realpath(expanded, resolved) == NULL) { |
181 | 0 | log_debug("%s: realpath(\"%s\") failed: %s", __func__, |
182 | 0 | expanded, strerror(errno)); |
183 | 0 | free(expanded); |
184 | 0 | continue; |
185 | 0 | } |
186 | 0 | path = xstrdup(resolved); |
187 | 0 | free(expanded); |
188 | 0 | } |
189 | 0 | for (i = 0; i < *n; i++) { |
190 | 0 | if (strcmp(path, (*paths)[i]) == 0) |
191 | 0 | break; |
192 | 0 | } |
193 | 0 | if (i != *n) { |
194 | 0 | log_debug("%s: duplicate path: %s", __func__, path); |
195 | 0 | free(path); |
196 | 0 | continue; |
197 | 0 | } |
198 | 0 | *paths = xreallocarray(*paths, (*n) + 1, sizeof *paths); |
199 | 0 | (*paths)[(*n)++] = path; |
200 | 0 | } |
201 | 0 | free(copy); |
202 | 0 | } |
203 | | |
204 | | static char * |
205 | | make_label(const char *label, char **cause) |
206 | 0 | { |
207 | 0 | char **paths, *path, *base; |
208 | 0 | u_int i, n; |
209 | 0 | struct stat sb; |
210 | 0 | uid_t uid; |
211 | |
|
212 | 0 | *cause = NULL; |
213 | 0 | if (label == NULL) |
214 | 0 | label = "default"; |
215 | 0 | uid = getuid(); |
216 | |
|
217 | 0 | expand_paths(TMUX_SOCK, &paths, &n, 0); |
218 | 0 | if (n == 0) { |
219 | 0 | xasprintf(cause, "no suitable socket path"); |
220 | 0 | return (NULL); |
221 | 0 | } |
222 | 0 | path = paths[0]; /* can only have one socket! */ |
223 | 0 | for (i = 1; i < n; i++) |
224 | 0 | free(paths[i]); |
225 | 0 | free(paths); |
226 | |
|
227 | 0 | xasprintf(&base, "%s/tmux-%ld", path, (long)uid); |
228 | 0 | free(path); |
229 | 0 | if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) { |
230 | 0 | xasprintf(cause, "couldn't create directory %s (%s)", base, |
231 | 0 | strerror(errno)); |
232 | 0 | goto fail; |
233 | 0 | } |
234 | 0 | if (lstat(base, &sb) != 0) { |
235 | 0 | xasprintf(cause, "couldn't read directory %s (%s)", base, |
236 | 0 | strerror(errno)); |
237 | 0 | goto fail; |
238 | 0 | } |
239 | 0 | if (!S_ISDIR(sb.st_mode)) { |
240 | 0 | xasprintf(cause, "%s is not a directory", base); |
241 | 0 | goto fail; |
242 | 0 | } |
243 | 0 | if (sb.st_uid != uid || (sb.st_mode & TMUX_SOCK_PERM) != 0) { |
244 | 0 | xasprintf(cause, "directory %s has unsafe permissions", base); |
245 | 0 | goto fail; |
246 | 0 | } |
247 | 0 | xasprintf(&path, "%s/%s", base, label); |
248 | 0 | free(base); |
249 | 0 | return (path); |
250 | | |
251 | 0 | fail: |
252 | 0 | free(base); |
253 | 0 | return (NULL); |
254 | 0 | } |
255 | | |
256 | | char * |
257 | | shell_argv0(const char *shell, int is_login) |
258 | 0 | { |
259 | 0 | const char *slash, *name; |
260 | 0 | char *argv0; |
261 | |
|
262 | 0 | slash = strrchr(shell, '/'); |
263 | 0 | if (slash != NULL && slash[1] != '\0') |
264 | 0 | name = slash + 1; |
265 | 0 | else |
266 | 0 | name = shell; |
267 | 0 | if (is_login) |
268 | 0 | xasprintf(&argv0, "-%s", name); |
269 | 0 | else |
270 | 0 | xasprintf(&argv0, "%s", name); |
271 | 0 | return (argv0); |
272 | 0 | } |
273 | | |
274 | | void |
275 | | setblocking(int fd, int state) |
276 | 0 | { |
277 | 0 | int mode; |
278 | |
|
279 | 0 | if ((mode = fcntl(fd, F_GETFL)) != -1) { |
280 | 0 | if (!state) |
281 | 0 | mode |= O_NONBLOCK; |
282 | 0 | else |
283 | 0 | mode &= ~O_NONBLOCK; |
284 | 0 | fcntl(fd, F_SETFL, mode); |
285 | 0 | } |
286 | 0 | } |
287 | | |
288 | | uint64_t |
289 | | get_timer(void) |
290 | 0 | { |
291 | 0 | struct timespec ts; |
292 | | |
293 | | /* |
294 | | * We want a timestamp in milliseconds suitable for time measurement, |
295 | | * so prefer the monotonic clock. |
296 | | */ |
297 | 0 | if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) |
298 | 0 | clock_gettime(CLOCK_REALTIME, &ts); |
299 | 0 | return ((ts.tv_sec * 1000ULL) + (ts.tv_nsec / 1000000ULL)); |
300 | 0 | } |
301 | | |
302 | | char * |
303 | | clean_name(const char *name, int untrusted) |
304 | 0 | { |
305 | 0 | char *copy, *cp, *new_name; |
306 | |
|
307 | 0 | if (!utf8_isvalid(name)) |
308 | 0 | return (NULL); |
309 | 0 | copy = xstrdup(name); |
310 | 0 | for (cp = copy; *cp != '\0'; cp++) { |
311 | 0 | if (untrusted && cp[0] == '#' && cp[1] == '(') |
312 | 0 | *cp = '_'; |
313 | 0 | } |
314 | 0 | utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); |
315 | 0 | free(copy); |
316 | 0 | return (new_name); |
317 | 0 | } |
318 | | |
319 | | int |
320 | | check_name(const char *name) |
321 | 0 | { |
322 | 0 | if (!utf8_isvalid(name)) |
323 | 0 | return (0); |
324 | 0 | return (1); |
325 | 0 | } |
326 | | |
327 | | const char * |
328 | | sig2name(int signo) |
329 | 0 | { |
330 | 0 | static char s[11]; |
331 | |
|
332 | | #ifdef HAVE_SYS_SIGNAME |
333 | | if (signo > 0 && signo < NSIG) |
334 | | return (sys_signame[signo]); |
335 | | #endif |
336 | 0 | xsnprintf(s, sizeof s, "%d", signo); |
337 | 0 | return (s); |
338 | 0 | } |
339 | | |
340 | | const char * |
341 | | find_cwd(void) |
342 | 0 | { |
343 | 0 | char resolved1[PATH_MAX], resolved2[PATH_MAX]; |
344 | 0 | static char cwd[PATH_MAX]; |
345 | 0 | const char *pwd; |
346 | |
|
347 | 0 | if (getcwd(cwd, sizeof cwd) == NULL) |
348 | 0 | return (NULL); |
349 | 0 | if ((pwd = getenv("PWD")) == NULL || *pwd == '\0') |
350 | 0 | return (cwd); |
351 | | |
352 | | /* |
353 | | * We want to use PWD so that symbolic links are maintained, |
354 | | * but only if it matches the actual working directory. |
355 | | */ |
356 | 0 | if (realpath(pwd, resolved1) == NULL) |
357 | 0 | return (cwd); |
358 | 0 | if (realpath(cwd, resolved2) == NULL) |
359 | 0 | return (cwd); |
360 | 0 | if (strcmp(resolved1, resolved2) != 0) |
361 | 0 | return (cwd); |
362 | 0 | return (pwd); |
363 | 0 | } |
364 | | |
365 | | const char * |
366 | | find_home(void) |
367 | 0 | { |
368 | 0 | struct passwd *pw; |
369 | 0 | static const char *home; |
370 | |
|
371 | 0 | if (home != NULL) |
372 | 0 | return (home); |
373 | | |
374 | 0 | home = getenv("HOME"); |
375 | 0 | if (home == NULL || *home == '\0') { |
376 | 0 | pw = getpwuid(getuid()); |
377 | 0 | if (pw != NULL) |
378 | 0 | home = pw->pw_dir; |
379 | 0 | else |
380 | 0 | home = NULL; |
381 | 0 | } |
382 | |
|
383 | 0 | return (home); |
384 | 0 | } |
385 | | |
386 | | const char * |
387 | | getversion(void) |
388 | 0 | { |
389 | 0 | return (TMUX_VERSION); |
390 | 0 | } |
391 | | |
392 | | int |
393 | | main(int argc, char **argv) |
394 | 0 | { |
395 | 0 | char *path = NULL, *label = NULL; |
396 | 0 | char *cause, **var; |
397 | 0 | const char *s, *cwd; |
398 | 0 | int opt, keys, feat = 0, fflag = 0; |
399 | 0 | uint64_t flags = 0; |
400 | 0 | const struct options_table_entry *oe; |
401 | 0 | u_int i; |
402 | |
|
403 | 0 | if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL && |
404 | 0 | setlocale(LC_CTYPE, "C.UTF-8") == NULL) { |
405 | 0 | if (setlocale(LC_CTYPE, "") == NULL) |
406 | 0 | errx(1, "invalid LC_ALL, LC_CTYPE or LANG"); |
407 | 0 | s = nl_langinfo(CODESET); |
408 | 0 | if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0) |
409 | 0 | errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s); |
410 | 0 | } |
411 | |
|
412 | 0 | setlocale(LC_TIME, ""); |
413 | 0 | tzset(); |
414 | |
|
415 | 0 | if (**argv == '-') |
416 | 0 | flags = CLIENT_LOGIN; |
417 | |
|
418 | 0 | global_environ = environ_create(); |
419 | 0 | for (var = environ; *var != NULL; var++) |
420 | 0 | environ_put(global_environ, *var, 0); |
421 | 0 | if ((cwd = find_cwd()) != NULL) |
422 | 0 | environ_set(global_environ, "PWD", 0, "%s", cwd); |
423 | 0 | expand_paths(TMUX_CONF, &cfg_files, &cfg_nfiles, 1); |
424 | |
|
425 | 0 | while ((opt = getopt(argc, argv, "2c:CDdf:hlL:NqS:T:uUvV")) != -1) { |
426 | 0 | switch (opt) { |
427 | 0 | case '2': |
428 | 0 | tty_add_features(&feat, "256", ":,"); |
429 | 0 | break; |
430 | 0 | case 'c': |
431 | 0 | shell_command = optarg; |
432 | 0 | break; |
433 | 0 | case 'D': |
434 | 0 | flags |= CLIENT_NOFORK; |
435 | 0 | break; |
436 | 0 | case 'C': |
437 | 0 | if (flags & CLIENT_CONTROL) |
438 | 0 | flags |= CLIENT_CONTROLCONTROL; |
439 | 0 | else |
440 | 0 | flags |= CLIENT_CONTROL; |
441 | 0 | break; |
442 | 0 | case 'f': |
443 | 0 | if (!fflag) { |
444 | 0 | fflag = 1; |
445 | 0 | for (i = 0; i < cfg_nfiles; i++) |
446 | 0 | free(cfg_files[i]); |
447 | 0 | cfg_nfiles = 0; |
448 | 0 | } |
449 | 0 | cfg_files = xreallocarray(cfg_files, cfg_nfiles + 1, |
450 | 0 | sizeof *cfg_files); |
451 | 0 | cfg_files[cfg_nfiles++] = xstrdup(optarg); |
452 | 0 | cfg_quiet = 0; |
453 | 0 | break; |
454 | 0 | case 'h': |
455 | 0 | usage(0); |
456 | 0 | case 'V': |
457 | 0 | printf("tmux %s\n", getversion()); |
458 | 0 | exit(0); |
459 | 0 | case 'l': |
460 | 0 | flags |= CLIENT_LOGIN; |
461 | 0 | break; |
462 | 0 | case 'L': |
463 | 0 | free(label); |
464 | 0 | label = xstrdup(optarg); |
465 | 0 | break; |
466 | 0 | case 'N': |
467 | 0 | flags |= CLIENT_NOSTARTSERVER; |
468 | 0 | break; |
469 | 0 | case 'q': |
470 | 0 | break; |
471 | 0 | case 'S': |
472 | 0 | free(path); |
473 | 0 | path = xstrdup(optarg); |
474 | 0 | break; |
475 | 0 | case 'T': |
476 | 0 | tty_add_features(&feat, optarg, ":,"); |
477 | 0 | break; |
478 | 0 | case 'u': |
479 | 0 | flags |= CLIENT_UTF8; |
480 | 0 | break; |
481 | 0 | case 'v': |
482 | 0 | log_add_level(); |
483 | 0 | break; |
484 | 0 | default: |
485 | 0 | usage(1); |
486 | 0 | } |
487 | 0 | } |
488 | 0 | argc -= optind; |
489 | 0 | argv += optind; |
490 | |
|
491 | 0 | if (shell_command != NULL && argc != 0) |
492 | 0 | usage(1); |
493 | 0 | if ((flags & CLIENT_NOFORK) && argc != 0) |
494 | 0 | usage(1); |
495 | | |
496 | 0 | if ((ptm_fd = getptmfd()) == -1) |
497 | 0 | err(1, "getptmfd"); |
498 | 0 | if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd " |
499 | 0 | "recvfd proc exec tty ps", NULL) != 0) |
500 | 0 | err(1, "pledge"); |
501 | | |
502 | | /* |
503 | | * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8. |
504 | | * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain |
505 | | * UTF-8, it is a safe assumption that either they are using a UTF-8 |
506 | | * terminal, or if not they know that output from UTF-8-capable |
507 | | * programs may be wrong. |
508 | | */ |
509 | 0 | if (getenv("TMUX") != NULL) |
510 | 0 | flags |= CLIENT_UTF8; |
511 | 0 | else { |
512 | 0 | s = getenv("LC_ALL"); |
513 | 0 | if (s == NULL || *s == '\0') |
514 | 0 | s = getenv("LC_CTYPE"); |
515 | 0 | if (s == NULL || *s == '\0') |
516 | 0 | s = getenv("LANG"); |
517 | 0 | if (s == NULL || *s == '\0') |
518 | 0 | s = ""; |
519 | 0 | if (strcasestr(s, "UTF-8") != NULL || |
520 | 0 | strcasestr(s, "UTF8") != NULL) |
521 | 0 | flags |= CLIENT_UTF8; |
522 | 0 | } |
523 | |
|
524 | 0 | global_options = options_create(NULL); |
525 | 0 | global_s_options = options_create(NULL); |
526 | 0 | global_w_options = options_create(NULL); |
527 | 0 | for (oe = options_table; oe->name != NULL; oe++) { |
528 | 0 | if (oe->scope & OPTIONS_TABLE_SERVER) |
529 | 0 | options_default(global_options, oe); |
530 | 0 | if (oe->scope & OPTIONS_TABLE_SESSION) |
531 | 0 | options_default(global_s_options, oe); |
532 | 0 | if (oe->scope & OPTIONS_TABLE_WINDOW) |
533 | 0 | options_default(global_w_options, oe); |
534 | 0 | } |
535 | | |
536 | | /* |
537 | | * The default shell comes from SHELL or from the user's passwd entry |
538 | | * if available. |
539 | | */ |
540 | 0 | options_set_string(global_s_options, "default-shell", 0, "%s", |
541 | 0 | getshell()); |
542 | | |
543 | | /* Override keys to vi if VISUAL or EDITOR are set. */ |
544 | 0 | if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) { |
545 | 0 | options_set_string(global_options, "editor", 0, "%s", s); |
546 | 0 | if (strrchr(s, '/') != NULL) |
547 | 0 | s = strrchr(s, '/') + 1; |
548 | 0 | if (strstr(s, "vi") != NULL) |
549 | 0 | keys = MODEKEY_VI; |
550 | 0 | else |
551 | 0 | keys = MODEKEY_EMACS; |
552 | 0 | options_set_number(global_s_options, "status-keys", keys); |
553 | 0 | options_set_number(global_w_options, "mode-keys", keys); |
554 | 0 | } |
555 | | |
556 | | /* |
557 | | * If socket is specified on the command-line with -S or -L, it is |
558 | | * used. Otherwise, $TMUX is checked and if that fails "default" is |
559 | | * used. |
560 | | */ |
561 | 0 | if (path == NULL && label == NULL) { |
562 | 0 | s = getenv("TMUX"); |
563 | 0 | if (s != NULL && *s != '\0' && *s != ',') { |
564 | 0 | path = xstrdup(s); |
565 | 0 | path[strcspn(path, ",")] = '\0'; |
566 | 0 | } |
567 | 0 | } |
568 | 0 | if (path == NULL) { |
569 | 0 | if ((path = make_label(label, &cause)) == NULL) { |
570 | 0 | if (cause != NULL) { |
571 | 0 | fprintf(stderr, "%s\n", cause); |
572 | 0 | free(cause); |
573 | 0 | } |
574 | 0 | exit(1); |
575 | 0 | } |
576 | 0 | flags |= CLIENT_DEFAULTSOCKET; |
577 | 0 | } |
578 | 0 | socket_path = path; |
579 | 0 | free(label); |
580 | | |
581 | | /* Pass control to the client. */ |
582 | 0 | exit(client_main(osdep_event_init(), argc, argv, flags, feat)); |
583 | 0 | } |