Cleanup mmap consuming
[lttng-tools.git] / ltt-sessiond / main.c
index b3dc0e3a6f08ee397935c8a672e2470179633980..5a73b68dfed46fb8c1878725b3727bb2c16e2787 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2011 - David Goulet <david.goulet@polymtl.ca>
+ * Copyright (C) 2011 - Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -40,9 +41,9 @@
 
 #include <urcu/list.h>         /* URCU list library (-lurcu) */
 #include <lttng/lttng.h>
+#include <lttng-sessiond-comm.h>
 
 #include "context.h"
-#include "liblttsessiondcomm.h"
 #include "ltt-sessiond.h"
 #include "lttngerr.h"
 #include "kernel-ctl.h"
@@ -60,6 +61,7 @@ const char default_global_apps_pipe[] = DEFAULT_GLOBAL_APPS_PIPE;
 
 /* Variables */
 int opt_verbose;    /* Not static for lttngerr.h */
+int opt_verbose_kconsumerd;    /* Not static for lttngerr.h */
 int opt_quiet;      /* Not static for lttngerr.h */
 
 const char *progname;
@@ -113,6 +115,22 @@ static int modprobe_remove_kernel_modules(void);
  */
 static struct ltt_session_list *session_list_ptr;
 
+static gid_t allowed_group(void)
+{
+       struct group *grp;
+
+       if (opt_tracing_group) {
+               grp = getgrnam(opt_tracing_group);
+       } else {
+               grp = getgrnam(default_tracing_group);
+       }
+       if (!grp) {
+               return -1;
+       } else {
+               return grp->gr_gid;
+       }
+}
+
 /*
  * Init quit pipe.
  *
@@ -146,10 +164,18 @@ static void teardown_kernel_session(struct ltt_session *session)
        }
 }
 
+static void stop_threads(void)
+{
+       /* Stopping all threads */
+       DBG("Terminating all threads");
+       close(thread_quit_pipe[0]);
+       close(thread_quit_pipe[1]);
+}
+
 /*
  * Cleanup the daemon
  */
-static void cleanup()
+static void cleanup(void)
 {
        int ret;
        char *cmd;
@@ -163,11 +189,6 @@ static void cleanup()
                27, 1, 31, 27, 0, 27, 1, 33, 27, 0);
        /* </fun> */
 
-       /* Stopping all threads */
-       DBG("Terminating all threads");
-       close(thread_quit_pipe[0]);
-       close(thread_quit_pipe[1]);
-
        DBG("Removing %s directory", LTTNG_RUNDIR);
        ret = asprintf(&cmd, "rm -rf " LTTNG_RUNDIR);
        if (ret < 0) {
@@ -267,7 +288,9 @@ static int send_kconsumerd_channel_fds(int sock, struct ltt_kernel_channel *chan
                        lkm.fd = stream->fd;
                        lkm.state = stream->state;
                        lkm.max_sb_size = channel->channel->attr.subbuf_size;
+                       lkm.output = DEFAULT_KERNEL_CHANNEL_OUTPUT;
                        strncpy(lkm.path_name, stream->pathname, PATH_MAX);
+                       lkm.path_name[PATH_MAX - 1] = '\0';
 
                        DBG("Sending fd %d to kconsumerd", lkm.fd);
 
@@ -316,7 +339,9 @@ static int send_kconsumerd_fds(int sock, struct ltt_kernel_session *session)
                lkm.fd = session->metadata_stream_fd;
                lkm.state = ACTIVE_FD;
                lkm.max_sb_size = session->metadata->conf->attr.subbuf_size;
+               lkm.output = DEFAULT_KERNEL_CHANNEL_OUTPUT;
                strncpy(lkm.path_name, session->metadata->pathname, PATH_MAX);
+               lkm.path_name[PATH_MAX - 1] = '\0';
 
                ret = lttcomm_send_fds_unix_sock(sock, &lkm, &lkm.fd, 1, sizeof(lkm));
                if (ret < 0) {
@@ -862,6 +887,23 @@ error:
        return ret;
 }
 
+static int join_kconsumerd_thread(void)
+{
+       void *status;
+       int ret;
+
+       if (kconsumerd_pid != 0) {
+               ret = kill(kconsumerd_pid, SIGTERM);
+               if (ret) {
+                       ERR("Error killing kconsumerd");
+                       return ret;
+               }
+               return pthread_join(kconsumerd_thread, &status);
+       } else {
+               return 0;
+       }
+}
+
 /*
  * Fork and exec a kernel consumer daemon (kconsumerd).
  *
@@ -880,7 +922,7 @@ static pid_t spawn_kconsumerd(void)
                /*
                 * Exec kconsumerd.
                 */
-               if (opt_verbose > 1) {
+               if (opt_verbose > 1 || opt_verbose_kconsumerd) {
                        verbosity = "--verbose";
                } else {
                        verbosity = "--quiet";
@@ -1024,7 +1066,7 @@ static int mount_debugfs(char *path)
        int ret;
        char *type = "debugfs";
 
-       ret = mkdir_recursive(path, S_IRWXU | S_IRWXG);
+       ret = mkdir_recursive(path, S_IRWXU | S_IRWXG, geteuid(), getegid());
        if (ret < 0) {
                goto error;
        }
@@ -1205,9 +1247,17 @@ static int create_kernel_session(struct ltt_session *session)
                goto error;
        }
 
-       ret = mkdir_recursive(session->path, S_IRWXU | S_IRWXG );
+       ret = asprintf(&session->kernel_session->trace_path, "%s/kernel",
+                       session->path);
        if (ret < 0) {
-               if (ret != EEXIST) {
+               perror("asprintf kernel traces path");
+               goto error;
+       }
+
+       ret = mkdir_recursive(session->kernel_session->trace_path,
+                       S_IRWXU | S_IRWXG, geteuid(), allowed_group());
+       if (ret < 0) {
+               if (ret != -EEXIST) {
                        ERR("Trace directory creation error");
                        goto error;
                }
@@ -1236,7 +1286,9 @@ static void list_lttng_sessions(struct lttng_session *sessions)
         */
        cds_list_for_each_entry(session, &session_list_ptr->head, list) {
                strncpy(sessions[i].path, session->path, PATH_MAX);
+               sessions[i].path[PATH_MAX - 1] = '\0';
                strncpy(sessions[i].name, session->name, NAME_MAX);
+               sessions[i].name[NAME_MAX - 1] = '\0';
                i++;
        }
 }
@@ -1282,6 +1334,7 @@ static void list_lttng_events(struct ltt_kernel_channel *kchan,
        /* Kernel channels */
        cds_list_for_each_entry(event, &kchan->events_list.head , list) {
                strncpy(events[i].name, event->event->name, LTTNG_SYMBOL_NAME_LEN);
+               events[i].name[LTTNG_SYMBOL_NAME_LEN - 1] = '\0';
                events[i].enabled = event->enabled;
                switch (event->event->instrumentation) {
                        case LTTNG_KERNEL_TRACEPOINT:
@@ -1316,11 +1369,14 @@ static int process_client_msg(struct command_ctx *cmd_ctx)
 
        DBG("Processing client command %d", cmd_ctx->lsm->cmd_type);
 
-       /* Listing commands don't need a session */
+       /*
+        * Commands that DO NOT need a session.
+        */
        switch (cmd_ctx->lsm->cmd_type) {
        case LTTNG_CREATE_SESSION:
        case LTTNG_LIST_SESSIONS:
        case LTTNG_LIST_TRACEPOINTS:
+       case LTTNG_CALIBRATE:
                break;
        default:
                DBG("Getting session %s by name", cmd_ctx->lsm->session.name);
@@ -1341,7 +1397,7 @@ static int process_client_msg(struct command_ctx *cmd_ctx)
        }
 
        /*
-        * Check domain type for specifif "pre-action".
+        * Check domain type for specific "pre-action".
         */
        switch (cmd_ctx->lsm->domain.type) {
        case LTTNG_DOMAIN_KERNEL:
@@ -1355,21 +1411,27 @@ static int process_client_msg(struct command_ctx *cmd_ctx)
                }
 
                /* Need a session for kernel command */
-               if (cmd_ctx->lsm->cmd_type != LTTNG_LIST_TRACEPOINTS &&
-                               cmd_ctx->session->kernel_session == NULL) {
-
-                       ret = create_kernel_session(cmd_ctx->session);
-                       if (ret < 0) {
-                               ret = LTTCOMM_KERN_SESS_FAIL;
-                               goto error;
-                       }
-
-                       /* Start the kernel consumer daemon */
-                       if (kconsumerd_pid == 0) {
-                               ret = start_kconsumerd();
+               switch (cmd_ctx->lsm->cmd_type) {
+               case LTTNG_CREATE_SESSION:
+               case LTTNG_LIST_SESSIONS:
+               case LTTNG_LIST_TRACEPOINTS:
+               case LTTNG_CALIBRATE:
+                       break;
+               default:
+                       if (cmd_ctx->session->kernel_session == NULL) {
+                               ret = create_kernel_session(cmd_ctx->session);
                                if (ret < 0) {
+                                       ret = LTTCOMM_KERN_SESS_FAIL;
                                        goto error;
                                }
+
+                               /* Start the kernel consumer daemon */
+                               if (kconsumerd_pid == 0) {
+                                       ret = start_kconsumerd();
+                                       if (ret < 0) {
+                                               goto error;
+                                       }
+                               }
                        }
                }
                break;
@@ -1398,6 +1460,7 @@ static int process_client_msg(struct command_ctx *cmd_ctx)
                        strncpy(kctx.u.perf_counter.name,
                                        cmd_ctx->lsm->u.context.ctx.u.perf_counter.name,
                                        LTTNG_SYMBOL_NAME_LEN);
+                       kctx.u.perf_counter.name[LTTNG_SYMBOL_NAME_LEN - 1] = '\0';
 
                        /* Add kernel context to kernel tracer. See context.c */
                        ret = add_kernel_context(cmd_ctx->session->kernel_session, &kctx,
@@ -1557,7 +1620,8 @@ static int process_client_msg(struct command_ctx *cmd_ctx)
                                DBG("Creating kernel channel");
 
                                ret = kernel_create_channel(cmd_ctx->session->kernel_session,
-                                               &cmd_ctx->lsm->u.channel.chan, cmd_ctx->session->path);
+                                               &cmd_ctx->lsm->u.channel.chan,
+                                               cmd_ctx->session->kernel_session->trace_path);
                                if (ret < 0) {
                                        ret = LTTCOMM_KERN_CHAN_FAIL;
                                        goto error;
@@ -1620,7 +1684,7 @@ static int process_client_msg(struct command_ctx *cmd_ctx)
                                        }
 
                                        ret = kernel_create_channel(cmd_ctx->session->kernel_session,
-                                                       chan, cmd_ctx->session->path);
+                                                       chan, cmd_ctx->session->kernel_session->trace_path);
                                        if (ret < 0) {
                                                ret = LTTCOMM_KERN_CHAN_FAIL;
                                                goto error;
@@ -1692,7 +1756,7 @@ static int process_client_msg(struct command_ctx *cmd_ctx)
                                        }
 
                                        ret = kernel_create_channel(cmd_ctx->session->kernel_session,
-                                                       chan, cmd_ctx->session->path);
+                                                       chan, cmd_ctx->session->kernel_session->trace_path);
                                        if (ret < 0) {
                                                ret = LTTCOMM_KERN_CHAN_FAIL;
                                                goto error;
@@ -1797,7 +1861,7 @@ static int process_client_msg(struct command_ctx *cmd_ctx)
                        if (cmd_ctx->session->kernel_session->metadata == NULL) {
                                DBG("Open kernel metadata");
                                ret = kernel_open_metadata(cmd_ctx->session->kernel_session,
-                                               cmd_ctx->session->path);
+                                               cmd_ctx->session->kernel_session->trace_path);
                                if (ret < 0) {
                                        ret = LTTCOMM_KERN_META_FAIL;
                                        goto error;
@@ -2016,7 +2080,7 @@ static int process_client_msg(struct command_ctx *cmd_ctx)
                        goto setup_error;
                }
 
-               DBG("Listing events (%ld events)", nb_event);
+               DBG("Listing events (%zu events)", nb_event);
 
                list_lttng_events(kchan,
                                (struct lttng_event *)(cmd_ctx->llm->payload));
@@ -2049,6 +2113,37 @@ static int process_client_msg(struct command_ctx *cmd_ctx)
                ret = LTTCOMM_OK;
                break;
        }
+
+       case LTTNG_CALIBRATE:
+       {
+               /* Setup lttng message with no payload */
+               ret = setup_lttng_msg(cmd_ctx, 0);
+               if (ret < 0) {
+                       goto setup_error;
+               }
+
+               switch (cmd_ctx->lsm->domain.type) {
+               case LTTNG_DOMAIN_KERNEL:
+               {
+                       struct lttng_kernel_calibrate kcalibrate;
+
+                       kcalibrate.type = cmd_ctx->lsm->u.calibrate.type;
+                       ret = kernel_calibrate(kernel_tracer_fd, &kcalibrate);
+                       if (ret < 0) {
+                               ret = LTTCOMM_KERN_ENABLE_FAIL;
+                               goto error;
+                       }
+                       break;
+               }
+               default:
+                       /* TODO: Userspace tracing */
+                       ret = LTTCOMM_NOT_IMPLEMENTED;
+                       goto error;
+               }
+               ret = LTTCOMM_OK;
+               break;
+       }
+
        default:
                /* Undefined command */
                ret = setup_lttng_msg(cmd_ctx, 0);
@@ -2222,6 +2317,7 @@ static void usage(void)
        fprintf(stderr, "  -S, --sig-parent                   Send SIGCHLD to parent pid to notify readiness.\n");
        fprintf(stderr, "  -q, --quiet                        No output at all.\n");
        fprintf(stderr, "  -v, --verbose                      Verbose mode. Activate DBG() macro.\n");
+       fprintf(stderr, "      --verbose-kconsumerd           Verbose mode for kconsumerd. Activate DBG() macro.\n");
 }
 
 /*
@@ -2243,12 +2339,13 @@ static int parse_args(int argc, char **argv)
                { "version", 0, 0, 'V' },
                { "quiet", 0, 0, 'q' },
                { "verbose", 0, 0, 'v' },
+               { "verbose-kconsumerd", 0, 0, 'Z' },
                { NULL, 0, 0, 0 }
        };
 
        while (1) {
                int option_index = 0;
-               c = getopt_long(argc, argv, "dhqvVS" "a:c:g:s:E:C:", long_options, &option_index);
+               c = getopt_long(argc, argv, "dhqvVS" "a:c:g:s:E:C:Z", long_options, &option_index);
                if (c == -1) {
                        break;
                }
@@ -2294,6 +2391,9 @@ static int parse_args(int argc, char **argv)
                        /* Verbose level can increase using multiple -v */
                        opt_verbose += 1;
                        break;
+               case 'Z':
+                       opt_verbose_kconsumerd += 1;
+                       break;
                default:
                        /* Unknown option or other error.
                         * Error is printed by getopt, just return */
@@ -2309,7 +2409,7 @@ static int parse_args(int argc, char **argv)
  *         apps_sock - The communication socket for all UST apps.
  *         client_sock - The communication of the cli tool (lttng).
  */
-static int init_daemon_socket()
+static int init_daemon_socket(void)
 {
        int ret = 0;
        mode_t old_umask;
@@ -2354,18 +2454,19 @@ end:
 }
 
 /*
- * Check if the global socket is available.  If yes, error is returned.
+ * Check if the global socket is available, and if a daemon is answering
+ * at the other side. If yes, error is returned.
  */
-static int check_existing_daemon()
+static int check_existing_daemon(void)
 {
-       int ret;
-
-       ret = access(client_unix_sock_path, F_OK);
-       if (ret == 0) {
-               ret = access(apps_unix_sock_path, F_OK);
-       }
-
-       return ret;
+       if (access(client_unix_sock_path, F_OK) < 0 &&
+           access(apps_unix_sock_path, F_OK) < 0)
+               return 0;
+       /* Is there anybody out there ? */
+       if (lttng_session_daemon_alive())
+               return -EEXIST;
+       else
+               return 0;
 }
 
 /*
@@ -2377,14 +2478,10 @@ static int check_existing_daemon()
 static int set_permissions(void)
 {
        int ret;
-       struct group *grp;
+       gid_t gid;
 
-       /* Decide which group name to use */
-       (opt_tracing_group != NULL) ?
-               (grp = getgrnam(opt_tracing_group)) :
-               (grp = getgrnam(default_tracing_group));
-
-       if (grp == NULL) {
+       gid = allowed_group();
+       if (gid < 0) {
                if (is_root) {
                        WARN("No tracing group detected");
                        ret = 0;
@@ -2396,21 +2493,21 @@ static int set_permissions(void)
        }
 
        /* Set lttng run dir */
-       ret = chown(LTTNG_RUNDIR, 0, grp->gr_gid);
+       ret = chown(LTTNG_RUNDIR, 0, gid);
        if (ret < 0) {
                ERR("Unable to set group on " LTTNG_RUNDIR);
                perror("chown");
        }
 
        /* lttng client socket path */
-       ret = chown(client_unix_sock_path, 0, grp->gr_gid);
+       ret = chown(client_unix_sock_path, 0, gid);
        if (ret < 0) {
                ERR("Unable to set group on %s", client_unix_sock_path);
                perror("chown");
        }
 
        /* kconsumerd error socket path */
-       ret = chown(kconsumerd_err_unix_sock_path, 0, grp->gr_gid);
+       ret = chown(kconsumerd_err_unix_sock_path, 0, gid);
        if (ret < 0) {
                ERR("Unable to set group on %s", kconsumerd_err_unix_sock_path);
                perror("chown");
@@ -2498,26 +2595,27 @@ error:
 
 /*
  * Signal handler for the daemon
+ *
+ * Simply stop all worker threads, leaving main() return gracefully
+ * after joining all threads and calling cleanup().
  */
 static void sighandler(int sig)
 {
        switch (sig) {
-               case SIGPIPE:
-                       DBG("SIGPIPE catched");
-                       return;
-               case SIGINT:
-                       DBG("SIGINT catched");
-                       cleanup();
-                       break;
-               case SIGTERM:
-                       DBG("SIGTERM catched");
-                       cleanup();
-                       break;
-               default:
-                       break;
+       case SIGPIPE:
+               DBG("SIGPIPE catched");
+               return;
+       case SIGINT:
+               DBG("SIGINT catched");
+               stop_threads();
+               break;
+       case SIGTERM:
+               DBG("SIGTERM catched");
+               stop_threads();
+               break;
+       default:
+               break;
        }
-
-       exit(EXIT_SUCCESS);
 }
 
 /*
@@ -2587,14 +2685,14 @@ int main(int argc, char **argv)
        const char *home_path;
 
        /* Create thread quit pipe */
-       if (init_thread_quit_pipe() < 0) {
-               goto exit;
+       if ((ret = init_thread_quit_pipe()) < 0) {
+               goto error;
        }
 
        /* Parse arguments */
        progname = argv[0];
        if ((ret = parse_args(argc, argv) < 0)) {
-               goto exit;
+               goto error;
        }
 
        /* Daemonize */
@@ -2602,7 +2700,7 @@ int main(int argc, char **argv)
                ret = daemon(0, 0);
                if (ret < 0) {
                        perror("daemon");
-                       goto exit;
+                       goto error;
                }
        }
 
@@ -2612,7 +2710,7 @@ int main(int argc, char **argv)
        if (is_root) {
                ret = create_lttng_rundir();
                if (ret < 0) {
-                       goto exit;
+                       goto error;
                }
 
                if (strlen(apps_unix_sock_path) == 0) {
@@ -2629,7 +2727,8 @@ int main(int argc, char **argv)
                if (home_path == NULL) {
                        /* TODO: Add --socket PATH option */
                        ERR("Can't get HOME directory for sockets creation.");
-                       goto exit;
+                       ret = -EPERM;
+                       goto error;
                }
 
                if (strlen(apps_unix_sock_path) == 0) {
@@ -2648,17 +2747,15 @@ int main(int argc, char **argv)
        DBG("Application socket path %s", apps_unix_sock_path);
 
        /*
-        * See if daemon already exist. If any of the two socket needed by the
-        * daemon are present, this test fails. However, if the daemon is killed
-        * with a SIGKILL, those unix socket must be unlinked by hand.
+        * See if daemon already exist.
         */
-       if ((ret = check_existing_daemon()) == 0) {
+       if ((ret = check_existing_daemon()) < 0) {
                ERR("Already running daemon.\n");
                /*
-                * We do not goto error because we must not cleanup() because a daemon
-                * is already running.
+                * We do not goto exit because we must not cleanup()
+                * because a daemon is already running.
                 */
-               goto exit;
+               goto error;
        }
 
        /* After this point, we can safely call cleanup() so goto error is used */
@@ -2672,7 +2769,7 @@ int main(int argc, char **argv)
        if (is_root) {
                ret = set_kconsumerd_sockets();
                if (ret < 0) {
-                       goto error;
+                       goto exit;
                }
 
                /* Setup kernel tracer */
@@ -2682,18 +2779,18 @@ int main(int argc, char **argv)
                set_ulimit();
        }
 
-       if (set_signal_handler() < 0) {
-               goto error;
+       if ((ret = set_signal_handler()) < 0) {
+               goto exit;
        }
 
        /* Setup the needed unix socket */
-       if (init_daemon_socket() < 0) {
-               goto error;
+       if ((ret = init_daemon_socket()) < 0) {
+               goto exit;
        }
 
        /* Set credentials to socket */
-       if (is_root && (set_permissions() < 0)) {
-               goto error;
+       if (is_root && ((ret = set_permissions()) < 0)) {
+               goto exit;
        }
 
        /* Get parent pid if -S, --sig-parent is specified. */
@@ -2702,8 +2799,8 @@ int main(int argc, char **argv)
        }
 
        /* Setup the kernel pipe for waking up the kernel thread */
-       if (create_kernel_poll_pipe() < 0) {
-               goto error;
+       if ((ret = create_kernel_poll_pipe()) < 0) {
+               goto exit;
        }
 
        /*
@@ -2712,41 +2809,61 @@ int main(int argc, char **argv)
         */
        session_list_ptr = get_session_list();
 
-       while (1) {
-               /* Create thread to manage the client socket */
-               ret = pthread_create(&client_thread, NULL, thread_manage_clients, (void *) NULL);
-               if (ret != 0) {
-                       perror("pthread_create");
-                       goto error;
-               }
+       /* Create thread to manage the client socket */
+       ret = pthread_create(&client_thread, NULL, thread_manage_clients, (void *) NULL);
+       if (ret != 0) {
+               perror("pthread_create");
+               goto exit_client;
+       }
 
-               /* Create thread to manage application socket */
-               ret = pthread_create(&apps_thread, NULL, thread_manage_apps, (void *) NULL);
-               if (ret != 0) {
-                       perror("pthread_create");
-                       goto error;
-               }
+       /* Create thread to manage application socket */
+       ret = pthread_create(&apps_thread, NULL, thread_manage_apps, (void *) NULL);
+       if (ret != 0) {
+               perror("pthread_create");
+               goto exit_apps;
+       }
 
-               /* Create kernel thread to manage kernel event */
-               ret = pthread_create(&kernel_thread, NULL, thread_manage_kernel, (void *) NULL);
-               if (ret != 0) {
-                       perror("pthread_create");
-                       goto error;
-               }
+       /* Create kernel thread to manage kernel event */
+       ret = pthread_create(&kernel_thread, NULL, thread_manage_kernel, (void *) NULL);
+       if (ret != 0) {
+               perror("pthread_create");
+               goto exit_kernel;
+       }
 
-               ret = pthread_join(client_thread, &status);
-               if (ret != 0) {
-                       perror("pthread_join");
-                       goto error;
-               }
+       ret = pthread_join(kernel_thread, &status);
+       if (ret != 0) {
+               perror("pthread_join");
+               goto error;     /* join error, exit without cleanup */
        }
 
-       cleanup();
-       exit(EXIT_SUCCESS);
+exit_kernel:
+       ret = pthread_join(apps_thread, &status);
+       if (ret != 0) {
+               perror("pthread_join");
+               goto error;     /* join error, exit without cleanup */
+       }
 
-error:
-       cleanup();
+exit_apps:
+       ret = pthread_join(client_thread, &status);
+       if (ret != 0) {
+               perror("pthread_join");
+               goto error;     /* join error, exit without cleanup */
+       }
+
+       ret = join_kconsumerd_thread();
+       if (ret != 0) {
+               perror("join_kconsumerd");
+               goto error;     /* join error, exit without cleanup */
+       }
 
+exit_client:
 exit:
+       /*
+        * cleanup() is called when no other thread is running.
+        */
+       cleanup();
+       if (!ret)
+               exit(EXIT_SUCCESS);
+error:
        exit(EXIT_FAILURE);
 }
This page took 0.034076 seconds and 4 git commands to generate.