Commit | Line | Data |
---|---|---|
826d496d MD |
1 | /* |
2 | * Copyright (c) 2011 David Goulet <david.goulet@polymtl.ca> | |
fac6795d DG |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | * GNU General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License along | |
15 | * with this program; if not, write to the Free Software Foundation, Inc., | |
16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
fac6795d DG |
17 | */ |
18 | ||
19 | #define _GNU_SOURCE | |
20 | #include <errno.h> | |
21 | #include <fcntl.h> | |
22 | #include <getopt.h> | |
23 | #include <grp.h> | |
24 | #include <limits.h> | |
25 | #include <stdio.h> | |
26 | #include <stdlib.h> | |
27 | #include <string.h> | |
28 | #include <sys/stat.h> | |
29 | #include <sys/types.h> | |
30 | #include <sys/wait.h> | |
31 | #include <unistd.h> | |
32 | ||
5b97ec60 | 33 | #include <lttng/lttng.h> |
fac6795d DG |
34 | |
35 | #include "lttng.h" | |
36 | #include "lttngerr.h" | |
37 | ||
38 | /* Variables */ | |
39 | static char *progname; | |
7442b2ba | 40 | static char short_uuid[9]; |
fac6795d DG |
41 | |
42 | /* Prototypes */ | |
43 | static int process_client_opt(void); | |
44 | static int process_opt_list_apps(void); | |
57167058 | 45 | static int process_opt_list_sessions(void); |
1657e9bb | 46 | static int process_opt_list_traces(void); |
aaf97519 | 47 | static int process_opt_create_session(void); |
7442b2ba | 48 | static int process_opt_session_uuid(void); |
5b8719f5 | 49 | static void sighandler(int sig); |
7442b2ba | 50 | static void shorten_uuid(char *in, char *out); |
5b8719f5 | 51 | static int set_signal_handler(void); |
8548ff30 | 52 | static int validate_options(void); |
47b74d63 | 53 | static char *get_cmdline_by_pid(pid_t pid); |
fac6795d DG |
54 | |
55 | /* | |
56 | * start_client | |
57 | * | |
58 | * Process client request from the command line | |
59 | * options. Every tracing action is done by the | |
60 | * liblttngctl API. | |
61 | */ | |
62 | static int process_client_opt(void) | |
63 | { | |
64 | int ret; | |
8028d920 | 65 | uuid_t uuid; |
fac6795d | 66 | |
fac6795d DG |
67 | if (opt_list_apps) { |
68 | ret = process_opt_list_apps(); | |
69 | if (ret < 0) { | |
fac6795d DG |
70 | goto end; |
71 | } | |
72 | } | |
73 | ||
57167058 DG |
74 | if (opt_list_session) { |
75 | ret = process_opt_list_sessions(); | |
76 | if (ret < 0) { | |
77 | goto end; | |
78 | } | |
79 | } | |
80 | ||
1657e9bb DG |
81 | if (opt_list_traces) { |
82 | ret = process_opt_list_traces(); | |
83 | if (ret < 0) { | |
84 | goto end; | |
85 | } | |
86 | } | |
87 | ||
aaf97519 DG |
88 | if (opt_create_session != NULL) { |
89 | ret = process_opt_create_session(); | |
90 | if (ret < 0) { | |
91 | goto end; | |
92 | } | |
93 | } | |
94 | ||
8028d920 DG |
95 | if (opt_destroy_session != NULL) { |
96 | uuid_parse(opt_destroy_session, uuid); | |
97 | ret = lttng_destroy_session(&uuid); | |
98 | if (ret < 0) { | |
99 | goto end; | |
100 | } | |
101 | } | |
102 | ||
e8be5f4f | 103 | if (opt_session_uuid != NULL) { |
7442b2ba DG |
104 | DBG("Set session uuid to %s", short_uuid); |
105 | ret = process_opt_session_uuid(); | |
106 | if (ret < 0) { | |
107 | ERR("Session UUID %s not found", opt_session_uuid); | |
108 | goto error; | |
109 | } | |
e8be5f4f DG |
110 | } |
111 | ||
ad874cce DG |
112 | if (opt_trace_kernel) { |
113 | ERR("Not implemented yet"); | |
114 | goto end; | |
df0da139 DG |
115 | } |
116 | ||
ad874cce DG |
117 | if (opt_trace_pid != 0) { |
118 | if (opt_create_trace) { | |
119 | DBG("Create a userspace trace for pid %d", opt_trace_pid); | |
120 | ret = lttng_ust_create_trace(opt_trace_pid); | |
121 | if (ret < 0) { | |
122 | goto end; | |
123 | } | |
124 | MSG("Trace created successfully!\nUse --start to start tracing."); | |
ce3d728c | 125 | } |
ce3d728c | 126 | |
ad874cce DG |
127 | if (opt_start_trace) { |
128 | DBG("Start trace for pid %d", opt_trace_pid); | |
129 | ret = lttng_ust_start_trace(opt_trace_pid); | |
130 | if (ret < 0) { | |
131 | goto end; | |
132 | } | |
133 | MSG("Trace started successfully!"); | |
134 | } else if (opt_stop_trace) { | |
135 | DBG("Stop trace for pid %d", opt_trace_pid); | |
136 | ret = lttng_ust_stop_trace(opt_trace_pid); | |
137 | if (ret < 0) { | |
138 | goto end; | |
139 | } | |
140 | MSG("Trace stopped successfully!"); | |
520ff687 | 141 | } |
ad874cce | 142 | |
520ff687 DG |
143 | } |
144 | ||
fac6795d DG |
145 | return 0; |
146 | ||
147 | end: | |
ebafd2a5 | 148 | ERR("%s", lttng_get_readable_code(ret)); |
fac6795d | 149 | return ret; |
7442b2ba DG |
150 | |
151 | error: | |
152 | return ret; | |
153 | } | |
154 | ||
155 | /* | |
156 | * process_opt_session_uuid | |
157 | * | |
158 | * Set current session uuid to the current flow of | |
159 | * command(s) using the already shorten uuid. | |
160 | */ | |
161 | static int process_opt_session_uuid(void) | |
162 | { | |
163 | int ret, count, i; | |
164 | struct lttng_session *sessions; | |
165 | ||
166 | count = lttng_list_sessions(&sessions); | |
167 | if (count < 0) { | |
168 | ret = count; | |
169 | goto error; | |
170 | } | |
171 | ||
172 | for (i = 0; i < count; i++) { | |
173 | if (strncmp(sessions[i].uuid, short_uuid, 8) == 0) { | |
174 | lttng_set_current_session_uuid(sessions[i].uuid); | |
175 | break; | |
176 | } | |
177 | } | |
178 | ||
179 | free(sessions); | |
180 | ||
181 | return 0; | |
182 | ||
183 | error: | |
184 | return ret; | |
fac6795d DG |
185 | } |
186 | ||
1657e9bb DG |
187 | /* |
188 | * process_opt_list_traces | |
189 | * | |
190 | * Get list of all traces for a specific session uuid. | |
191 | */ | |
192 | static int process_opt_list_traces(void) | |
193 | { | |
194 | int ret, i; | |
195 | uuid_t uuid; | |
196 | struct lttng_trace *traces; | |
197 | ||
198 | uuid_parse(opt_session_uuid, uuid); | |
199 | ret = lttng_list_traces(&uuid, &traces); | |
200 | if (ret < 0) { | |
201 | goto error; | |
202 | } | |
203 | ||
204 | MSG("Userspace traces:"); | |
205 | for (i = 0; i < ret; i++) { | |
206 | if (traces[i].type == USERSPACE) { | |
47b74d63 DG |
207 | MSG("\t%d) %s (pid: %d): %s", |
208 | i, traces[i].name, traces[i].pid, | |
209 | get_cmdline_by_pid(traces[i].pid)); | |
1657e9bb DG |
210 | } else { |
211 | break; | |
212 | } | |
213 | } | |
214 | ||
215 | MSG("Kernel traces:"); | |
216 | for (;i < ret; i++) { | |
217 | if (traces[i].type == KERNEL) { | |
218 | MSG("\t%d) %s", i, traces[i].name); | |
219 | } | |
220 | } | |
221 | ||
222 | free(traces); | |
223 | ||
224 | error: | |
225 | return ret; | |
226 | } | |
227 | ||
aaf97519 DG |
228 | /* |
229 | * process_opt_create_session | |
230 | * | |
231 | * Create a new session using the name pass | |
232 | * to the command line. | |
233 | */ | |
234 | static int process_opt_create_session(void) | |
235 | { | |
236 | int ret; | |
8028d920 DG |
237 | uuid_t session_id; |
238 | char str_uuid[37]; | |
aaf97519 DG |
239 | |
240 | ret = lttng_create_session(opt_create_session, &session_id); | |
241 | if (ret < 0) { | |
242 | goto error; | |
243 | } | |
244 | ||
8028d920 DG |
245 | uuid_unparse(session_id, str_uuid); |
246 | ||
aaf97519 | 247 | MSG("Session created:"); |
8028d920 | 248 | MSG(" %s (%s)", opt_create_session, str_uuid); |
aaf97519 DG |
249 | |
250 | error: | |
251 | return ret; | |
252 | } | |
253 | ||
7442b2ba DG |
254 | /* |
255 | * extract_short_uuid | |
256 | * | |
257 | * Extract shorten uuid and copy it to out. | |
258 | * Shorten uuid format : '<name>.<short_uuid>' | |
259 | */ | |
260 | static int extract_short_uuid(char *in, char *out) | |
261 | { | |
262 | char *tok; | |
263 | ||
264 | tok = strchr(in, '.'); | |
265 | if (strlen(tok+1) == 8) { | |
266 | memcpy(out, tok+1, 8); | |
267 | out[9] = '\0'; | |
268 | return 0; | |
269 | } | |
270 | ||
271 | return -1; | |
272 | } | |
273 | ||
274 | /* | |
275 | * shorten_uuid | |
276 | * | |
277 | * Small function to shorten the 37 bytes long uuid_t | |
278 | * string representation to 8 characters. | |
279 | */ | |
280 | static void shorten_uuid(char *in, char *out) | |
281 | { | |
282 | memcpy(out, in, 8); | |
283 | out[8] = '\0'; | |
284 | } | |
285 | ||
57167058 DG |
286 | /* |
287 | * process_opt_list_sessions | |
288 | * | |
289 | * Get the list of available sessions from | |
290 | * the session daemon and print it to user. | |
291 | */ | |
292 | static int process_opt_list_sessions(void) | |
293 | { | |
294 | int ret, count, i; | |
7442b2ba | 295 | char tmp_short_uuid[9]; |
57167058 DG |
296 | struct lttng_session *sess; |
297 | ||
298 | count = lttng_list_sessions(&sess); | |
7442b2ba | 299 | DBG("Session count %d", count); |
57167058 DG |
300 | if (count < 0) { |
301 | ret = count; | |
302 | goto error; | |
303 | } | |
304 | ||
7442b2ba | 305 | MSG("Available sessions (UUIDs):"); |
57167058 | 306 | for (i = 0; i < count; i++) { |
7442b2ba DG |
307 | shorten_uuid(sess[i].uuid, tmp_short_uuid); |
308 | MSG(" %d) %s.%s", i+1, sess[i].name, tmp_short_uuid); | |
57167058 DG |
309 | } |
310 | ||
311 | free(sess); | |
7442b2ba | 312 | MSG("\nTo select a session, use -s, --session UUID."); |
57167058 DG |
313 | |
314 | return 0; | |
315 | ||
316 | error: | |
317 | return ret; | |
318 | } | |
319 | ||
fac6795d DG |
320 | /* |
321 | * process_opt_list_apps | |
322 | * | |
323 | * Get the UST traceable pid list and print | |
324 | * them to the user. | |
325 | */ | |
326 | static int process_opt_list_apps(void) | |
327 | { | |
e8f07c63 | 328 | int i, ret, count; |
fac6795d | 329 | pid_t *pids; |
1c9f7941 | 330 | char *cmdline; |
fac6795d | 331 | |
e8f07c63 DG |
332 | count = lttng_ust_list_apps(&pids); |
333 | if (count < 0) { | |
334 | ret = count; | |
fac6795d DG |
335 | goto error; |
336 | } | |
337 | ||
338 | MSG("LTTng UST traceable application [name (pid)]:"); | |
e8f07c63 | 339 | for (i=0; i < count; i++) { |
47b74d63 DG |
340 | cmdline = get_cmdline_by_pid(pids[i]); |
341 | if (cmdline == NULL) { | |
e8f07c63 | 342 | MSG("\t(not running) (%d)", pids[i]); |
fac6795d DG |
343 | continue; |
344 | } | |
fac6795d | 345 | MSG("\t%s (%d)", cmdline, pids[i]); |
1c9f7941 | 346 | free(cmdline); |
fac6795d DG |
347 | } |
348 | ||
e065084a DG |
349 | /* Allocated by lttng_ust_list_apps() */ |
350 | free(pids); | |
351 | ||
fac6795d DG |
352 | return 0; |
353 | ||
354 | error: | |
355 | return ret; | |
356 | } | |
357 | ||
1c9f7941 DG |
358 | /* |
359 | * get_cmdline_by_pid | |
360 | * | |
47b74d63 | 361 | * Get command line from /proc for a specific pid. |
1c9f7941 | 362 | * |
47b74d63 DG |
363 | * On success, return an allocated string pointer pointing to |
364 | * the proc cmdline. | |
365 | * On error, return NULL. | |
1c9f7941 | 366 | */ |
47b74d63 | 367 | static char *get_cmdline_by_pid(pid_t pid) |
1c9f7941 DG |
368 | { |
369 | int ret; | |
370 | FILE *fp; | |
47b74d63 | 371 | char *cmdline = NULL; |
1c9f7941 DG |
372 | char path[24]; /* Can't go bigger than /proc/65535/cmdline */ |
373 | ||
374 | snprintf(path, sizeof(path), "/proc/%d/cmdline", pid); | |
375 | fp = fopen(path, "r"); | |
376 | if (fp == NULL) { | |
47b74d63 | 377 | goto end; |
1c9f7941 DG |
378 | } |
379 | ||
380 | /* Caller must free() *cmdline */ | |
47b74d63 DG |
381 | cmdline = malloc(PATH_MAX); |
382 | ret = fread(cmdline, 1, PATH_MAX, fp); | |
1c9f7941 DG |
383 | fclose(fp); |
384 | ||
47b74d63 DG |
385 | end: |
386 | return cmdline; | |
1c9f7941 DG |
387 | } |
388 | ||
8548ff30 DG |
389 | /* |
390 | * validate_options | |
391 | * | |
392 | * Make sure that all options passed to the command line | |
393 | * are compatible with each others. | |
394 | * | |
395 | * On error, return -1 | |
396 | * On success, return 0 | |
397 | */ | |
398 | static int validate_options(void) | |
399 | { | |
7442b2ba DG |
400 | int ret; |
401 | ||
ad874cce DG |
402 | /* Conflicting command */ |
403 | if (opt_start_trace && opt_stop_trace) { | |
404 | ERR("Can't use --start and --stop together."); | |
405 | goto error; | |
406 | /* Must have a session UUID for trace action. */ | |
407 | } else if ((opt_session_uuid == NULL) && | |
408 | (opt_create_trace || opt_start_trace || opt_stop_trace || opt_list_traces)) { | |
1657e9bb | 409 | ERR("You need to specify a session UUID.\nPlease use --session UUID to do so."); |
8548ff30 | 410 | goto error; |
ad874cce DG |
411 | /* If no PID specified and trace_kernel is off */ |
412 | } else if ((opt_trace_pid == 0 && opt_trace_kernel == 0) && | |
413 | (opt_create_trace || opt_start_trace || opt_stop_trace)) { | |
414 | ERR("Please specify a PID using -p, --pid PID."); | |
415 | goto error; | |
8548ff30 DG |
416 | } |
417 | ||
7442b2ba DG |
418 | if (opt_session_uuid != NULL) { |
419 | ret = extract_short_uuid(opt_session_uuid, short_uuid); | |
420 | if (ret < 0) { | |
421 | ERR("Session UUID not valid. Must be <name>.<short_uuid>"); | |
422 | goto error; | |
423 | } | |
424 | } | |
425 | ||
8548ff30 DG |
426 | return 0; |
427 | ||
428 | error: | |
429 | return -1; | |
430 | } | |
431 | ||
5b8719f5 DG |
432 | /* |
433 | * spawn_sessiond | |
434 | * | |
435 | * Spawn a session daemon by forking and execv. | |
436 | */ | |
437 | static int spawn_sessiond(char *pathname) | |
438 | { | |
439 | int ret = 0; | |
440 | pid_t pid; | |
441 | ||
442 | MSG("Spawning session daemon"); | |
443 | pid = fork(); | |
444 | if (pid == 0) { | |
445 | /* Spawn session daemon and tell | |
446 | * it to signal us when ready. | |
447 | */ | |
75462a81 | 448 | ret = execlp(pathname, "ltt-sessiond", "--sig-parent", "--quiet", NULL); |
5b8719f5 DG |
449 | if (ret < 0) { |
450 | if (errno == ENOENT) { | |
451 | ERR("No session daemon found. Use --sessiond-path."); | |
452 | } else { | |
453 | perror("execlp"); | |
454 | } | |
455 | kill(getppid(), SIGTERM); | |
456 | exit(EXIT_FAILURE); | |
457 | } | |
458 | exit(EXIT_SUCCESS); | |
459 | } else if (pid > 0) { | |
460 | /* Wait for ltt-sessiond to start */ | |
461 | pause(); | |
462 | goto end; | |
463 | } else { | |
464 | perror("fork"); | |
465 | ret = -1; | |
466 | goto end; | |
467 | } | |
468 | ||
469 | end: | |
470 | return ret; | |
471 | } | |
472 | ||
fac6795d DG |
473 | /* |
474 | * check_ltt_sessiond | |
475 | * | |
476 | * Check if the session daemon is available using | |
5b8719f5 DG |
477 | * the liblttngctl API for the check. If not, try to |
478 | * spawn a daemon. | |
fac6795d DG |
479 | */ |
480 | static int check_ltt_sessiond(void) | |
481 | { | |
482 | int ret; | |
5b8719f5 | 483 | char *pathname = NULL; |
fac6795d DG |
484 | |
485 | ret = lttng_check_session_daemon(); | |
486 | if (ret < 0) { | |
5b8719f5 DG |
487 | /* Try command line option path */ |
488 | if (opt_sessiond_path != NULL) { | |
489 | ret = access(opt_sessiond_path, F_OK | X_OK); | |
490 | if (ret < 0) { | |
491 | ERR("No such file: %s", opt_sessiond_path); | |
492 | goto end; | |
493 | } | |
494 | pathname = opt_sessiond_path; | |
495 | } else { | |
496 | /* Try LTTNG_SESSIOND_PATH env variable */ | |
e8f07c63 DG |
497 | pathname = getenv(LTTNG_SESSIOND_PATH_ENV); |
498 | if (pathname != NULL) { | |
499 | /* strdup here in order to make the free() | |
500 | * not fail later on. | |
501 | */ | |
502 | pathname = strdup(pathname); | |
503 | } | |
5b8719f5 DG |
504 | } |
505 | ||
506 | /* Let's rock and roll */ | |
507 | if (pathname == NULL) { | |
508 | ret = asprintf(&pathname, "ltt-sessiond"); | |
509 | if (ret < 0) { | |
510 | goto end; | |
511 | } | |
512 | } | |
513 | ||
514 | ret = spawn_sessiond(pathname); | |
515 | free(pathname); | |
516 | if (ret < 0) { | |
517 | ERR("Problem occurs when starting %s", pathname); | |
518 | goto end; | |
519 | } | |
fac6795d DG |
520 | } |
521 | ||
5b8719f5 | 522 | end: |
fac6795d DG |
523 | return ret; |
524 | } | |
525 | ||
5b8719f5 DG |
526 | /* |
527 | * set_signal_handler | |
528 | * | |
529 | * Setup signal handler for SIGCHLD and SIGTERM. | |
530 | */ | |
531 | static int set_signal_handler(void) | |
532 | { | |
533 | int ret = 0; | |
534 | struct sigaction sa; | |
535 | sigset_t sigset; | |
536 | ||
537 | if ((ret = sigemptyset(&sigset)) < 0) { | |
538 | perror("sigemptyset"); | |
539 | goto end; | |
540 | } | |
541 | ||
542 | sa.sa_handler = sighandler; | |
543 | sa.sa_mask = sigset; | |
544 | sa.sa_flags = 0; | |
545 | if ((ret = sigaction(SIGCHLD, &sa, NULL)) < 0) { | |
546 | perror("sigaction"); | |
547 | goto end; | |
548 | } | |
fac6795d | 549 | |
5b8719f5 DG |
550 | if ((ret = sigaction(SIGTERM, &sa, NULL)) < 0) { |
551 | perror("sigaction"); | |
552 | goto end; | |
553 | } | |
554 | ||
555 | end: | |
556 | return ret; | |
557 | } | |
558 | ||
559 | /* | |
560 | * sighandler | |
561 | * | |
562 | * Signal handler for the daemon | |
563 | */ | |
564 | static void sighandler(int sig) | |
565 | { | |
5b8719f5 DG |
566 | switch (sig) { |
567 | case SIGTERM: | |
ce3d728c | 568 | DBG("SIGTERM catched"); |
5b8719f5 DG |
569 | clean_exit(EXIT_FAILURE); |
570 | break; | |
571 | case SIGCHLD: | |
572 | /* Notify is done */ | |
ce3d728c | 573 | DBG("SIGCHLD catched"); |
5b8719f5 DG |
574 | break; |
575 | default: | |
ce3d728c | 576 | DBG("Unknown signal %d catched", sig); |
5b8719f5 DG |
577 | break; |
578 | } | |
579 | ||
580 | return; | |
581 | } | |
7442b2ba | 582 | |
fac6795d DG |
583 | /* |
584 | * clean_exit | |
585 | */ | |
586 | void clean_exit(int code) | |
587 | { | |
588 | DBG("Clean exit"); | |
589 | exit(code); | |
590 | } | |
591 | ||
592 | /* | |
5b8719f5 | 593 | * main |
fac6795d DG |
594 | */ |
595 | int main(int argc, char *argv[]) | |
596 | { | |
597 | int ret; | |
598 | ||
599 | progname = argv[0] ? argv[0] : "lttng"; | |
600 | ||
601 | /* For Mathieu Desnoyers aka Dr Tracing */ | |
602 | if (strncmp(progname, "drtrace", 7) == 0) { | |
603 | MSG("%c[%d;%dmWelcome back Dr Tracing!%c[%dm\n\n", 27,1,33,27,0); | |
604 | } | |
605 | ||
606 | ret = parse_args(argc, (const char **) argv); | |
607 | if (ret < 0) { | |
87378cf5 | 608 | clean_exit(EXIT_FAILURE); |
fac6795d DG |
609 | } |
610 | ||
8548ff30 DG |
611 | ret = validate_options(); |
612 | if (ret < 0) { | |
613 | return EXIT_FAILURE; | |
614 | } | |
615 | ||
5b8719f5 DG |
616 | ret = set_signal_handler(); |
617 | if (ret < 0) { | |
87378cf5 | 618 | clean_exit(ret); |
5b8719f5 DG |
619 | } |
620 | ||
fac6795d DG |
621 | if (opt_tracing_group != NULL) { |
622 | DBG("Set tracing group to '%s'", opt_tracing_group); | |
623 | lttng_set_tracing_group(opt_tracing_group); | |
624 | } | |
625 | ||
626 | /* If ask for kernel tracing, need root perms */ | |
627 | if (opt_trace_kernel) { | |
628 | DBG("Kernel tracing activated"); | |
629 | if (getuid() != 0) { | |
630 | ERR("%s must be setuid root", progname); | |
87378cf5 | 631 | clean_exit(-EPERM); |
fac6795d DG |
632 | } |
633 | } | |
634 | ||
635 | /* Check if the lttng session daemon is running. | |
636 | * If no, a daemon will be spawned. | |
637 | */ | |
5b8719f5 | 638 | if (opt_no_sessiond == 0 && (check_ltt_sessiond() < 0)) { |
87378cf5 | 639 | clean_exit(EXIT_FAILURE); |
fac6795d DG |
640 | } |
641 | ||
642 | ret = process_client_opt(); | |
643 | if (ret < 0) { | |
87378cf5 | 644 | clean_exit(ret); |
fac6795d DG |
645 | } |
646 | ||
87378cf5 DG |
647 | clean_exit(0); |
648 | ||
fac6795d DG |
649 | return 0; |
650 | } |