event-rule: kernel probe: rename set/get_name to set/get_event_name
[lttng-tools.git] / src / bin / lttng / commands / create.c
... / ...
CommitLineData
1/*
2 * Copyright (C) 2011 David Goulet <david.goulet@polymtl.ca>
3 * Copyright (C) 2019 Jérémie Galarneau <jeremie.galarneau@efficios.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-only
6 *
7 */
8
9#define _LGPL_SOURCE
10#include <assert.h>
11#include <ctype.h>
12#include <popt.h>
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h>
16#include <sys/stat.h>
17#include <sys/types.h>
18#include <common/compat/time.h>
19#include <unistd.h>
20#include <signal.h>
21#include <sys/wait.h>
22
23#include <common/mi-lttng.h>
24
25#include "../command.h"
26#include "../utils.h"
27
28#include <common/defaults.h>
29#include <common/sessiond-comm/sessiond-comm.h>
30#include <common/uri.h>
31#include <common/utils.h>
32#include <lttng/lttng.h>
33
34static char *opt_output_path;
35static char *opt_session_name;
36static char *opt_url;
37static char *opt_ctrl_url;
38static char *opt_data_url;
39static char *opt_shm_path;
40static int opt_no_consumer;
41static int opt_no_output;
42static int opt_snapshot;
43static uint32_t opt_live_timer;
44
45#ifdef LTTNG_EMBED_HELP
46static const char help_msg[] =
47#include <lttng-create.1.h>
48;
49#endif
50
51enum {
52 OPT_HELP = 1,
53 OPT_LIST_OPTIONS,
54 OPT_LIVE_TIMER,
55};
56
57enum output_type {
58 OUTPUT_NONE,
59 OUTPUT_LOCAL,
60 OUTPUT_NETWORK,
61 OUTPUT_UNSPECIFIED,
62};
63
64static struct mi_writer *writer;
65static struct poptOption long_options[] = {
66 /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */
67 {"help", 'h', POPT_ARG_NONE, NULL, OPT_HELP, NULL, NULL},
68 {"output", 'o', POPT_ARG_STRING, &opt_output_path, 0, NULL, NULL},
69 {"list-options", 0, POPT_ARG_NONE, NULL, OPT_LIST_OPTIONS, NULL, NULL},
70 {"set-url", 'U', POPT_ARG_STRING, &opt_url, 0, 0, 0},
71 {"ctrl-url", 'C', POPT_ARG_STRING, &opt_ctrl_url, 0, 0, 0},
72 {"data-url", 'D', POPT_ARG_STRING, &opt_data_url, 0, 0, 0},
73 {"no-output", 0, POPT_ARG_VAL, &opt_no_output, 1, 0, 0},
74 {"no-consumer", 0, POPT_ARG_VAL, &opt_no_consumer, 1, 0, 0},
75 {"snapshot", 0, POPT_ARG_VAL, &opt_snapshot, 1, 0, 0},
76 {"live", 0, POPT_ARG_INT | POPT_ARGFLAG_OPTIONAL, 0, OPT_LIVE_TIMER, 0, 0},
77 {"shm-path", 0, POPT_ARG_STRING, &opt_shm_path, 0, 0, 0},
78 {0, 0, 0, 0, 0, 0, 0}
79};
80
81/*
82 * Retrieve the created session and mi output it based on provided argument
83 * This is currently a summary of what was pretty printed and is subject to
84 * enhancements.
85 */
86static int mi_created_session(const char *session_name)
87{
88 int ret, i, count, found;
89 struct lttng_session *sessions;
90
91 /* session_name should not be null */
92 assert(session_name);
93 assert(writer);
94
95 count = lttng_list_sessions(&sessions);
96 if (count < 0) {
97 ret = count;
98 ERR("%s", lttng_strerror(ret));
99 goto error;
100 }
101
102 if (count == 0) {
103 ERR("Error session creation failed: session %s not found", session_name);
104 ret = -LTTNG_ERR_SESS_NOT_FOUND;
105 goto end;
106 }
107
108 found = 0;
109 for (i = 0; i < count; i++) {
110 if (strncmp(sessions[i].name, session_name, NAME_MAX) == 0) {
111 found = 1;
112 ret = mi_lttng_session(writer, &sessions[i], 0);
113 if (ret) {
114 goto error;
115 }
116 break;
117 }
118 }
119
120 if (!found) {
121 ret = -LTTNG_ERR_SESS_NOT_FOUND;
122 } else {
123 ret = CMD_SUCCESS;
124 }
125
126error:
127 free(sessions);
128end:
129 return ret;
130}
131
132static
133struct lttng_session_descriptor *create_session_descriptor(void)
134{
135 int ret;
136 ssize_t uri_count;
137 enum output_type output_type;
138 struct lttng_uri *uris = NULL;
139 struct lttng_session_descriptor *descriptor = NULL;
140 const char *uri_str1 = NULL, *uri_str2 = NULL;
141 char local_output_path[LTTNG_PATH_MAX] = {};
142
143 if (opt_no_output) {
144 output_type = OUTPUT_NONE;
145 } else if (opt_output_path) {
146 char *expanded_output_path;
147
148 output_type = OUTPUT_LOCAL;
149 expanded_output_path = utils_expand_path(opt_output_path);
150 if (!expanded_output_path) {
151 ERR("Failed to expand output path.");
152 goto end;
153 }
154 ret = lttng_strncpy(local_output_path, expanded_output_path,
155 sizeof(local_output_path));
156 free(expanded_output_path);
157 if (ret) {
158 ERR("Output path exceeds the maximal supported length (%zu bytes)",
159 sizeof(local_output_path));
160 goto end;
161 }
162 } else if (opt_url || opt_ctrl_url) {
163 uri_str1 = opt_ctrl_url ? opt_ctrl_url : opt_url;
164 uri_str2 = opt_data_url;
165
166 uri_count = uri_parse_str_urls(uri_str1, uri_str2, &uris);
167 if (uri_count != 1 && uri_count != 2) {
168 ERR("Unrecognized URL format.");
169 goto end;
170 }
171
172 switch (uri_count) {
173 case 1:
174 output_type = OUTPUT_LOCAL;
175 if (uris[0].dtype != LTTNG_DST_PATH) {
176 ERR("Unrecognized URL format.");
177 goto end;
178 }
179 ret = lttng_strncpy(local_output_path, uris[0].dst.path,
180 sizeof(local_output_path));
181 if (ret) {
182 ERR("Output path exceeds the maximal supported length (%zu bytes)",
183 sizeof(local_output_path));
184 }
185 break;
186 case 2:
187 output_type = OUTPUT_NETWORK;
188 break;
189 default:
190 /* Already checked. */
191 abort();
192 }
193 } else {
194 output_type = OUTPUT_UNSPECIFIED;
195 }
196
197 if (opt_snapshot) {
198 /* Snapshot session. */
199 switch (output_type) {
200 case OUTPUT_UNSPECIFIED:
201 case OUTPUT_LOCAL:
202 descriptor = lttng_session_descriptor_snapshot_local_create(
203 opt_session_name,
204 output_type == OUTPUT_LOCAL ?
205 local_output_path : NULL);
206 break;
207 case OUTPUT_NONE:
208 descriptor = lttng_session_descriptor_snapshot_create(
209 opt_session_name);
210 break;
211 case OUTPUT_NETWORK:
212 descriptor = lttng_session_descriptor_snapshot_network_create(
213 opt_session_name, uri_str1, uri_str2);
214 break;
215 default:
216 abort();
217 }
218 } else if (opt_live_timer) {
219 /* Live session. */
220 if (output_type != OUTPUT_UNSPECIFIED &&
221 output_type != OUTPUT_NETWORK) {
222 ERR("Unsupported output type specified for live session.");
223 goto end;
224 }
225 descriptor = lttng_session_descriptor_live_network_create(
226 opt_session_name, uri_str1, uri_str2,
227 opt_live_timer);
228 } else {
229 /* Regular session. */
230 switch (output_type) {
231 case OUTPUT_UNSPECIFIED:
232 case OUTPUT_LOCAL:
233 descriptor = lttng_session_descriptor_local_create(
234 opt_session_name,
235 output_type == OUTPUT_LOCAL ?
236 local_output_path : NULL);
237 break;
238 case OUTPUT_NONE:
239 descriptor = lttng_session_descriptor_create(
240 opt_session_name);
241 break;
242 case OUTPUT_NETWORK:
243 descriptor = lttng_session_descriptor_network_create(
244 opt_session_name, uri_str1, uri_str2);
245 break;
246 default:
247 abort();
248 }
249 }
250 if (!descriptor) {
251 ERR("Failed to initialize session creation command.");
252 } else {
253 /*
254 * Auto-launch the relay daemon when a live session
255 * is created using default URLs.
256 */
257 if (!opt_url && !opt_ctrl_url && !opt_data_url &&
258 opt_live_timer && !check_relayd()) {
259 int ret;
260 const char *pathname = opt_relayd_path ? :
261 INSTALL_BIN_PATH "/lttng-relayd";
262
263 ret = spawn_relayd(pathname, 0);
264 if (ret < 0) {
265 lttng_session_descriptor_destroy(descriptor);
266 descriptor = NULL;
267 }
268 }
269 }
270end:
271 free(uris);
272 return descriptor;
273}
274
275/*
276 * Create a tracing session.
277 * If no name is specified, a default name is generated.
278 *
279 * Returns one of the CMD_* result constants.
280 */
281static int create_session(void)
282{
283 int ret, i;
284 char shm_path[LTTNG_PATH_MAX] = {};
285 struct lttng_session_descriptor *session_descriptor = NULL;
286 enum lttng_session_descriptor_status descriptor_status;
287 enum lttng_error_code ret_code;
288 struct lttng_session *sessions = NULL;
289 const struct lttng_session *created_session = NULL;
290 const char *created_session_name;
291
292 /* Validate options. */
293 if (opt_session_name) {
294 if (strlen(opt_session_name) > NAME_MAX) {
295 ERR("Session name too long. Length must be lower or equal to %d",
296 NAME_MAX);
297 ret = CMD_ERROR;
298 goto error;
299 }
300 /*
301 * Check if the session name begins with "auto-" or is exactly "auto".
302 * Both are reserved for the default session name. See bug #449 to
303 * understand why we need to check both here.
304 */
305 if ((strncmp(opt_session_name, DEFAULT_SESSION_NAME "-",
306 strlen(DEFAULT_SESSION_NAME) + 1) == 0) ||
307 (strncmp(opt_session_name, DEFAULT_SESSION_NAME,
308 strlen(DEFAULT_SESSION_NAME)) == 0 &&
309 strlen(opt_session_name) == strlen(DEFAULT_SESSION_NAME))) {
310 ERR("%s is a reserved keyword for default session(s)",
311 DEFAULT_SESSION_NAME);
312 ret = CMD_ERROR;
313 goto error;
314 }
315 }
316
317 if (opt_snapshot && opt_live_timer) {
318 ERR("Snapshot and live modes are mutually exclusive.");
319 ret = CMD_ERROR;
320 goto error;
321 }
322
323 if ((!opt_ctrl_url && opt_data_url) || (opt_ctrl_url && !opt_data_url)) {
324 ERR("Both control and data URLs must be specified.");
325 ret = CMD_ERROR;
326 goto error;
327 }
328
329 session_descriptor = create_session_descriptor();
330 if (!session_descriptor) {
331 ret = CMD_ERROR;
332 goto error;
333 }
334 ret_code = lttng_create_session_ext(session_descriptor);
335 if (ret_code != LTTNG_OK) {
336 ERR("%s", lttng_strerror(-ret_code));
337 ret = CMD_ERROR;
338 goto error;
339 }
340
341 descriptor_status = lttng_session_descriptor_get_session_name(
342 session_descriptor, &created_session_name);
343 if (descriptor_status != LTTNG_SESSION_DESCRIPTOR_STATUS_OK) {
344 ERR("Failed to obtain created session name");
345 ret = CMD_ERROR;
346 goto error;
347 }
348
349 ret = lttng_list_sessions(&sessions);
350 if (ret < 0) {
351 ERR("Failed to fetch properties of created session: %s",
352 lttng_strerror(ret));
353 ret = CMD_ERROR;
354 goto error;
355 }
356 for (i = 0; i < ret; i++) {
357 if (!strcmp(created_session_name, sessions[i].name)) {
358 created_session = &sessions[i];
359 break;
360 }
361 }
362 if (!created_session) {
363 ERR("Failed to fetch properties of created session");
364 ret = CMD_ERROR;
365 goto error;
366 }
367
368 if (opt_shm_path) {
369 char datetime_suffix[17] = {};
370
371 /*
372 * An auto-generated session name already includes the creation
373 * timestamp.
374 */
375 if (opt_session_name) {
376 uint64_t creation_time;
377 struct tm *timeinfo;
378 time_t creation_time_t;
379 size_t strftime_ret;
380
381 ret_code = lttng_session_get_creation_time(
382 created_session,
383 &creation_time);
384 if (ret_code != LTTNG_OK) {
385 ERR("%s", lttng_strerror(-ret_code));
386 ret = CMD_ERROR;
387 goto error;
388 }
389 creation_time_t = (time_t) creation_time;
390 timeinfo = localtime(&creation_time_t);
391 if (!timeinfo) {
392 PERROR("Failed to interpret session creation time");
393 ret = CMD_ERROR;
394 goto error;
395 }
396 strftime_ret = strftime(datetime_suffix,
397 sizeof(datetime_suffix),
398 "-%Y%m%d-%H%M%S", timeinfo);
399 if (strftime_ret == 0) {
400 ERR("Failed to format session creation time.");
401 ret = CMD_ERROR;
402 goto error;
403 }
404 }
405
406 ret = snprintf(shm_path, sizeof(shm_path),
407 "%s/%s%s", opt_shm_path, created_session_name,
408 datetime_suffix);
409 if (ret < 0 || ret >= sizeof(shm_path)) {
410 ERR("Failed to format the shared memory path.");
411 ret = CMD_ERROR;
412 goto error;
413 }
414 ret = lttng_set_session_shm_path(created_session_name,
415 shm_path);
416 if (ret < 0) {
417 lttng_destroy_session(created_session_name);
418 ret = CMD_ERROR;
419 goto error;
420 }
421 }
422
423 if (opt_snapshot) {
424 MSG("Snapshot session %s created.", created_session_name);
425 } else if (opt_live_timer) {
426 MSG("Live session %s created.", created_session_name);
427 } else {
428 MSG("Session %s created.", created_session_name);
429 }
430
431 if (*created_session->path && !opt_snapshot) {
432 MSG("Traces will be output to %s", created_session->path);
433
434 if (opt_live_timer) {
435 MSG("Live timer interval set to %u %s", opt_live_timer,
436 USEC_UNIT);
437 }
438 } else if (opt_snapshot) {
439 struct lttng_snapshot_output_list *list;
440 struct lttng_snapshot_output *iter;
441 char snapshot_url[LTTNG_PATH_MAX] = {};
442
443 ret = lttng_snapshot_list_output(created_session_name, &list);
444 if (ret < 0) {
445 ERR("Failed to list snapshot outputs.");
446 ret = CMD_ERROR;
447 goto error;
448 }
449
450 while ((iter = lttng_snapshot_output_list_get_next(list))) {
451 const char *url = NULL;
452
453 url = lttng_snapshot_output_get_ctrl_url(
454 iter);
455 ret = lttng_strncpy(snapshot_url, url,
456 sizeof(snapshot_url));
457 if (ret) {
458 snapshot_url[0] = '\0';
459 ERR("Failed to retrieve snapshot output destination");
460 }
461 break;
462 }
463 lttng_snapshot_output_list_destroy(list);
464
465 if (*snapshot_url) {
466 MSG("Default snapshot output set to %s",
467 snapshot_url);
468 }
469 MSG("Every channel enabled for this session will be set to mmap output and default to overwrite mode.");
470 }
471 if (opt_shm_path) {
472 MSG("Shared memory path set to %s", shm_path);
473 }
474
475 /* Mi output */
476 if (lttng_opt_mi) {
477 ret = mi_created_session(created_session_name);
478 if (ret) {
479 ret = CMD_ERROR;
480 goto error;
481 }
482 }
483
484 /* Init lttng session config */
485 ret = config_init(created_session_name);
486 if (ret < 0) {
487 ret = CMD_ERROR;
488 goto error;
489 }
490
491 ret = CMD_SUCCESS;
492error:
493 lttng_session_descriptor_destroy(session_descriptor);
494 free(sessions);
495 return ret;
496}
497
498/*
499 * spawn_sessiond
500 *
501 * Spawn a session daemon by forking and execv.
502 */
503static int spawn_sessiond(const char *pathname)
504{
505 int ret = 0;
506 pid_t pid;
507
508 MSG("Spawning a session daemon");
509 pid = fork();
510 if (pid == 0) {
511 /*
512 * Spawn session daemon in daemon mode.
513 */
514 execlp(pathname, "lttng-sessiond",
515 "--daemonize", NULL);
516 /* execlp only returns if error happened */
517 if (errno == ENOENT) {
518 ERR("No session daemon found. Use --sessiond-path.");
519 } else {
520 PERROR("execlp");
521 }
522 kill(getppid(), SIGTERM); /* wake parent */
523 exit(EXIT_FAILURE);
524 } else if (pid > 0) {
525 /*
526 * In daemon mode (--daemonize), sessiond only exits when
527 * it's ready to accept commands.
528 */
529 for (;;) {
530 int status;
531 pid_t wait_pid_ret = waitpid(pid, &status, 0);
532
533 if (wait_pid_ret < 0) {
534 if (errno == EINTR) {
535 continue;
536 }
537 PERROR("waitpid");
538 ret = -errno;
539 goto end;
540 }
541
542 if (WIFSIGNALED(status)) {
543 ERR("Session daemon was killed by signal %d",
544 WTERMSIG(status));
545 ret = -1;
546 goto end;
547 } else if (WIFEXITED(status)) {
548 DBG("Session daemon terminated normally (exit status: %d)",
549 WEXITSTATUS(status));
550
551 if (WEXITSTATUS(status) != 0) {
552 ERR("Session daemon terminated with an error (exit status: %d)",
553 WEXITSTATUS(status));
554 ret = -1;
555 goto end;
556 }
557 break;
558 }
559 }
560
561 goto end;
562 } else {
563 PERROR("fork");
564 ret = -1;
565 goto end;
566 }
567
568end:
569 return ret;
570}
571
572/*
573 * launch_sessiond
574 *
575 * Check if the session daemon is available using
576 * the liblttngctl API for the check. If not, try to
577 * spawn a daemon.
578 */
579static int launch_sessiond(void)
580{
581 int ret;
582 const char *pathname = NULL;
583
584 ret = lttng_session_daemon_alive();
585 if (ret) {
586 /* Sessiond is alive, not an error */
587 ret = 0;
588 goto end;
589 }
590
591 /* Try command line option path */
592 pathname = opt_sessiond_path;
593
594 /* Try LTTNG_SESSIOND_PATH env variable */
595 if (pathname == NULL) {
596 pathname = getenv(DEFAULT_SESSIOND_PATH_ENV);
597 }
598
599 /* Try with configured path */
600 if (pathname == NULL) {
601 if (CONFIG_SESSIOND_BIN[0] != '\0') {
602 pathname = CONFIG_SESSIOND_BIN;
603 }
604 }
605
606 /* Try the default path */
607 if (pathname == NULL) {
608 pathname = INSTALL_BIN_PATH "/lttng-sessiond";
609 }
610
611 DBG("Session daemon binary path: %s", pathname);
612
613 /* Check existence and permissions */
614 ret = access(pathname, F_OK | X_OK);
615 if (ret < 0) {
616 ERR("No such file or access denied: %s", pathname);
617 goto end;
618 }
619
620 ret = spawn_sessiond(pathname);
621end:
622 if (ret) {
623 ERR("Problem occurred while launching session daemon (%s)",
624 pathname);
625 }
626 return ret;
627}
628
629static
630int validate_url_option_combination(void)
631{
632 int ret = 0;
633 int used_count = 0;
634
635 used_count += !!opt_url;
636 used_count += !!opt_output_path;
637 used_count += (opt_data_url || opt_ctrl_url);
638 if (used_count > 1) {
639 ERR("Only one of the --set-url, --ctrl-url/data-url, or --output options may be used at once.");
640 ret = -1;
641 }
642
643 return ret;
644}
645
646/*
647 * The 'create <options>' first level command
648 *
649 * Returns one of the CMD_* result constants.
650 */
651int cmd_create(int argc, const char **argv)
652{
653 int opt, ret = CMD_SUCCESS, command_ret = CMD_SUCCESS, success = 1;
654 char *opt_arg = NULL;
655 const char *leftover = NULL;
656 static poptContext pc;
657
658 pc = poptGetContext(NULL, argc, argv, long_options, 0);
659 poptReadDefaultConfig(pc, 0);
660
661 while ((opt = poptGetNextOpt(pc)) != -1) {
662 switch (opt) {
663 case OPT_HELP:
664 SHOW_HELP();
665 goto end;
666 case OPT_LIST_OPTIONS:
667 list_cmd_options(stdout, long_options);
668 goto end;
669 case OPT_LIVE_TIMER:
670 {
671 uint64_t v;
672
673 errno = 0;
674 opt_arg = poptGetOptArg(pc);
675 if (!opt_arg) {
676 /* Set up default values. */
677 opt_live_timer = (uint32_t) DEFAULT_LTTNG_LIVE_TIMER;
678 DBG("Session live timer interval set to default value %d",
679 opt_live_timer);
680 break;
681 }
682
683 if (utils_parse_time_suffix(opt_arg, &v) < 0) {
684 ERR("Wrong value for --live parameter: %s", opt_arg);
685 ret = CMD_ERROR;
686 goto end;
687 }
688
689 if (v != (uint32_t) v) {
690 ERR("32-bit overflow in --live parameter: %s", opt_arg);
691 ret = CMD_ERROR;
692 goto end;
693 }
694
695 if (v == 0) {
696 ERR("Live timer interval must be greater than zero");
697 ret = CMD_ERROR;
698 goto end;
699 }
700
701 opt_live_timer = (uint32_t) v;
702 DBG("Session live timer interval set to %d", opt_live_timer);
703 break;
704 }
705 default:
706 ret = CMD_UNDEFINED;
707 goto end;
708 }
709 }
710
711 if (opt_no_consumer) {
712 MSG("The option --no-consumer is obsolete. Use --no-output now.");
713 ret = CMD_WARNING;
714 goto end;
715 }
716
717 ret = validate_url_option_combination();
718 if (ret) {
719 ret = CMD_ERROR;
720 goto end;
721 }
722
723 /* Spawn a session daemon if needed */
724 if (!opt_no_sessiond) {
725 ret = launch_sessiond();
726 if (ret) {
727 ret = CMD_ERROR;
728 goto end;
729 }
730 }
731
732 /* MI initialization */
733 if (lttng_opt_mi) {
734 writer = mi_lttng_writer_create(fileno(stdout), lttng_opt_mi);
735 if (!writer) {
736 ret = -LTTNG_ERR_NOMEM;
737 goto end;
738 }
739
740 /* Open command element */
741 ret = mi_lttng_writer_command_open(writer,
742 mi_lttng_element_command_create);
743 if (ret) {
744 ret = CMD_ERROR;
745 goto end;
746 }
747
748 /* Open output element */
749 ret = mi_lttng_writer_open_element(writer,
750 mi_lttng_element_command_output);
751 if (ret) {
752 ret = CMD_ERROR;
753 goto end;
754 }
755 }
756 opt_session_name = (char*) poptGetArg(pc);
757
758 leftover = poptGetArg(pc);
759 if (leftover) {
760 ERR("Unknown argument: %s", leftover);
761 ret = CMD_ERROR;
762 goto end;
763 }
764
765 command_ret = create_session();
766 if (command_ret) {
767 success = 0;
768 }
769
770 if (lttng_opt_mi) {
771 /* Close output element */
772 ret = mi_lttng_writer_close_element(writer);
773 if (ret) {
774 ret = CMD_ERROR;
775 goto end;
776 }
777
778 /* Success ? */
779 ret = mi_lttng_writer_write_element_bool(writer,
780 mi_lttng_element_command_success, success);
781 if (ret) {
782 ret = CMD_ERROR;
783 goto end;
784 }
785
786 /* Command element close */
787 ret = mi_lttng_writer_command_close(writer);
788 if (ret) {
789 ret = CMD_ERROR;
790 goto end;
791 }
792 }
793
794end:
795 /* Mi clean-up */
796 if (writer && mi_lttng_writer_destroy(writer)) {
797 /* Preserve original error code */
798 ret = ret ? ret : -LTTNG_ERR_MI_IO_FAIL;
799 }
800
801 /* Overwrite ret if an error occurred in create_session() */
802 ret = command_ret ? command_ret : ret;
803
804 poptFreeContext(pc);
805 return ret;
806}
This page took 0.025605 seconds and 4 git commands to generate.