/*
 * main
 */
int main(int argc, char **argv)
{
	int ret = 0, retval = 0;
	void *status;

	if (set_signal_handler()) {
		retval = -1;
		goto exit_set_signal_handler;
	}

	/* Parse arguments */
	progname = argv[0];
	if (parse_args(argc, argv)) {
		retval = -1;
		goto exit_options;
	}

	/* Daemonize */
	if (opt_daemon) {
		int i;

		/*
		 * fork
		 * child: setsid, close FD 0, 1, 2, chdir /
		 * parent: exit (if fork is successful)
		 */
		ret = daemon(0, 0);
		if (ret < 0) {
			PERROR("daemon");
			retval = -1;
			goto exit_options;
		}
		/*
		 * We are in the child. Make sure all other file
		 * descriptors are closed, in case we are called with
		 * more opened file descriptors than the standard ones.
		 */
		for (i = 3; i < sysconf(_SC_OPEN_MAX); i++) {
			(void) close(i);
		}
	}

	/*
	 * Starting from here, we can create threads. This needs to be after
	 * lttng_daemonize due to RCU.
	 */

	health_consumerd = health_app_create(NR_HEALTH_CONSUMERD_TYPES);
	if (!health_consumerd) {
		retval = -1;
		goto exit_health_consumerd_cleanup;
	}

	/* Set up max poll set size */
	if (lttng_poll_set_max_size()) {
		retval = -1;
		goto exit_init_data;
	}

	if (*command_sock_path == '\0') {
		switch (opt_type) {
		case LTTNG_CONSUMER_KERNEL:
			ret = snprintf(command_sock_path, PATH_MAX,
					DEFAULT_KCONSUMERD_CMD_SOCK_PATH,
					DEFAULT_LTTNG_RUNDIR);
			if (ret < 0) {
				retval = -1;
				goto exit_init_data;
			}
			break;
		case LTTNG_CONSUMER64_UST:
			ret = snprintf(command_sock_path, PATH_MAX,
					DEFAULT_USTCONSUMERD64_CMD_SOCK_PATH,
					DEFAULT_LTTNG_RUNDIR);
			if (ret < 0) {
				retval = -1;
				goto exit_init_data;
			}
			break;
		case LTTNG_CONSUMER32_UST:
			ret = snprintf(command_sock_path, PATH_MAX,
					DEFAULT_USTCONSUMERD32_CMD_SOCK_PATH,
					DEFAULT_LTTNG_RUNDIR);
			if (ret < 0) {
				retval = -1;
				goto exit_init_data;
			}
			break;
		default:
			ERR("Unknown consumerd type");
			retval = -1;
			goto exit_init_data;
		}
	}

	/* Init */
	if (lttng_consumer_init()) {
		retval = -1;
		goto exit_init_data;
	}

	/* Initialize communication library */
	lttcomm_init();
	/* Initialize TCP timeout values */
	lttcomm_inet_init();

	if (!getuid()) {
		/* Set limit for open files */
		set_ulimit();
	}

	/* create the consumer instance with and assign the callbacks */
	ctx = lttng_consumer_create(opt_type, lttng_consumer_read_subbuffer,
		NULL, lttng_consumer_on_recv_stream, NULL);
	if (!ctx) {
		retval = -1;
		goto exit_init_data;
	}

	lttng_consumer_set_command_sock_path(ctx, command_sock_path);
	if (*error_sock_path == '\0') {
		switch (opt_type) {
		case LTTNG_CONSUMER_KERNEL:
			ret = snprintf(error_sock_path, PATH_MAX,
					DEFAULT_KCONSUMERD_ERR_SOCK_PATH,
					DEFAULT_LTTNG_RUNDIR);
			if (ret < 0) {
				retval = -1;
				goto exit_init_data;
			}
			break;
		case LTTNG_CONSUMER64_UST:
			ret = snprintf(error_sock_path, PATH_MAX,
					DEFAULT_USTCONSUMERD64_ERR_SOCK_PATH,
					DEFAULT_LTTNG_RUNDIR);
			if (ret < 0) {
				retval = -1;
				goto exit_init_data;
			}
			break;
		case LTTNG_CONSUMER32_UST:
			ret = snprintf(error_sock_path, PATH_MAX,
					DEFAULT_USTCONSUMERD32_ERR_SOCK_PATH,
					DEFAULT_LTTNG_RUNDIR);
			if (ret < 0) {
				retval = -1;
				goto exit_init_data;
			}
			break;
		default:
			ERR("Unknown consumerd type");
			retval = -1;
			goto exit_init_data;
		}
	}

	/* Connect to the socket created by lttng-sessiond to report errors */
	DBG("Connecting to error socket %s", error_sock_path);
	ret = lttcomm_connect_unix_sock(error_sock_path);
	/*
	 * Not a fatal error, but all communication with lttng-sessiond will
	 * fail.
	 */
	if (ret < 0) {
		WARN("Cannot connect to error socket (is lttng-sessiond started?)");
	}
	lttng_consumer_set_error_sock(ctx, ret);

	/*
	 * Block RT signals used for UST periodical metadata flush and the live
	 * timer in main, and create a dedicated thread to handle these signals.
	 */
	if (consumer_signal_init()) {
		retval = -1;
		goto exit_init_data;
	}

	ctx->type = opt_type;

	if (utils_create_pipe(health_quit_pipe)) {
		retval = -1;
		goto exit_health_pipe;
	}

	/* Create thread to manage the client socket */
	ret = pthread_create(&health_thread, NULL,
			thread_manage_health, (void *) NULL);
	if (ret) {
		errno = ret;
		PERROR("pthread_create health");
		retval = -1;
		goto exit_health_thread;
	}

	/*
	 * Wait for health thread to be initialized before letting the
	 * sessiond thread reply to the sessiond that we are ready.
	 */
	while (uatomic_read(&lttng_consumer_ready)) {
		usleep(100000);
	}
	cmm_smp_mb();	/* Read ready before following operations */

	/* Create thread to manage channels */
	ret = pthread_create(&channel_thread, NULL,
			consumer_thread_channel_poll,
			(void *) ctx);
	if (ret) {
		errno = ret;
		PERROR("pthread_create");
		retval = -1;
		goto exit_channel_thread;
	}

	/* Create thread to manage the polling/writing of trace metadata */
	ret = pthread_create(&metadata_thread, NULL,
			consumer_thread_metadata_poll,
			(void *) ctx);
	if (ret) {
		errno = ret;
		PERROR("pthread_create");
		retval = -1;
		goto exit_metadata_thread;
	}

	/* Create thread to manage the polling/writing of trace data */
	ret = pthread_create(&data_thread, NULL, consumer_thread_data_poll,
			(void *) ctx);
	if (ret) {
		errno = ret;
		PERROR("pthread_create");
		retval = -1;
		goto exit_data_thread;
	}

	/* Create the thread to manage the receive of fd */
	ret = pthread_create(&sessiond_thread, NULL,
			consumer_thread_sessiond_poll,
			(void *) ctx);
	if (ret) {
		errno = ret;
		PERROR("pthread_create");
		retval = -1;
		goto exit_sessiond_thread;
	}

	/*
	 * Create the thread to manage the UST metadata periodic timer and
	 * live timer.
	 */
	ret = pthread_create(&metadata_timer_thread, NULL,
			consumer_timer_thread, (void *) ctx);
	if (ret) {
		errno = ret;
		PERROR("pthread_create");
		retval = -1;
		goto exit_metadata_timer_thread;
	}

	ret = pthread_detach(metadata_timer_thread);
	if (ret) {
		errno = ret;
		PERROR("pthread_detach");
		retval = -1;
		goto exit_metadata_timer_detach;
	}

	/*
	 * This is where we start awaiting program completion (e.g. through
	 * signal that asks threads to teardown.
	 */

exit_metadata_timer_detach:
exit_metadata_timer_thread:
	ret = pthread_join(sessiond_thread, &status);
	if (ret) {
		errno = ret;
		PERROR("pthread_join sessiond_thread");
		retval = -1;
	}
exit_sessiond_thread:

	ret = pthread_join(data_thread, &status);
	if (ret) {
		errno = ret;
		PERROR("pthread_join data_thread");
		retval = -1;
	}
exit_data_thread:

	ret = pthread_join(metadata_thread, &status);
	if (ret) {
		errno = ret;
		PERROR("pthread_join metadata_thread");
		retval = -1;
	}
exit_metadata_thread:

	ret = pthread_join(channel_thread, &status);
	if (ret) {
		errno = ret;
		PERROR("pthread_join channel_thread");
		retval = -1;
	}
exit_channel_thread:

	ret = pthread_join(health_thread, &status);
	if (ret) {
		errno = ret;
		PERROR("pthread_join health_thread");
		retval = -1;
	}
exit_health_thread:

	utils_close_pipe(health_quit_pipe);
exit_health_pipe:

exit_init_data:
	lttng_consumer_destroy(ctx);
	lttng_consumer_cleanup();

	if (health_consumerd) {
		health_app_destroy(health_consumerd);
	}
exit_health_consumerd_cleanup:

exit_options:

exit_set_signal_handler:
	if (!retval) {
		exit(EXIT_SUCCESS);
	} else {
		exit(EXIT_FAILURE);
	}
}
/*
 * main
 */
int main(int argc, char **argv)
{
	int ret = 0;
	void *status;

	/* Parse arguments */
	progname = argv[0];
	parse_args(argc, argv);

	/* Daemonize */
	if (opt_daemon) {
		int i;

		/*
		 * fork
		 * child: setsid, close FD 0, 1, 2, chdir /
		 * parent: exit (if fork is successful)
		 */
		ret = daemon(0, 0);
		if (ret < 0) {
			PERROR("daemon");
			goto error;
		}
		/*
		 * We are in the child. Make sure all other file
		 * descriptors are closed, in case we are called with
		 * more opened file descriptors than the standard ones.
		 */
		for (i = 3; i < sysconf(_SC_OPEN_MAX); i++) {
			(void) close(i);
		}
	}

	/* Set up max poll set size */
	lttng_poll_set_max_size();

	if (*command_sock_path == '\0') {
		switch (opt_type) {
		case LTTNG_CONSUMER_KERNEL:
			snprintf(command_sock_path, PATH_MAX, DEFAULT_KCONSUMERD_CMD_SOCK_PATH,
					DEFAULT_LTTNG_RUNDIR);
			break;
		case LTTNG_CONSUMER64_UST:
			snprintf(command_sock_path, PATH_MAX,
					DEFAULT_USTCONSUMERD64_CMD_SOCK_PATH, DEFAULT_LTTNG_RUNDIR);
			break;
		case LTTNG_CONSUMER32_UST:
			snprintf(command_sock_path, PATH_MAX,
					DEFAULT_USTCONSUMERD32_CMD_SOCK_PATH, DEFAULT_LTTNG_RUNDIR);
			break;
		default:
			WARN("Unknown consumerd type");
			goto error;
		}
	}

	/* Init */
	lttng_consumer_init();

	if (!getuid()) {
		/* Set limit for open files */
		set_ulimit();
	}

	/* create the consumer instance with and assign the callbacks */
	ctx = lttng_consumer_create(opt_type, lttng_consumer_read_subbuffer,
		NULL, lttng_consumer_on_recv_stream, NULL);
	if (ctx == NULL) {
		goto error;
	}

	lttng_consumer_set_command_sock_path(ctx, command_sock_path);
	if (*error_sock_path == '\0') {
		switch (opt_type) {
		case LTTNG_CONSUMER_KERNEL:
			snprintf(error_sock_path, PATH_MAX, DEFAULT_KCONSUMERD_ERR_SOCK_PATH,
					DEFAULT_LTTNG_RUNDIR);
			break;
		case LTTNG_CONSUMER64_UST:
			snprintf(error_sock_path, PATH_MAX,
					DEFAULT_USTCONSUMERD64_ERR_SOCK_PATH, DEFAULT_LTTNG_RUNDIR);
			break;
		case LTTNG_CONSUMER32_UST:
			snprintf(error_sock_path, PATH_MAX,
					DEFAULT_USTCONSUMERD32_ERR_SOCK_PATH, DEFAULT_LTTNG_RUNDIR);
			break;
		default:
			WARN("Unknown consumerd type");
			goto error;
		}
	}

	if (set_signal_handler() < 0) {
		goto error;
	}

	/* Connect to the socket created by lttng-sessiond to report errors */
	DBG("Connecting to error socket %s", error_sock_path);
	ret = lttcomm_connect_unix_sock(error_sock_path);
	/* not a fatal error, but all communication with lttng-sessiond will fail */
	if (ret < 0) {
		WARN("Cannot connect to error socket (is lttng-sessiond started?)");
	}
	lttng_consumer_set_error_sock(ctx, ret);

	/*
	 * For UST consumer, we block RT signals used for periodical metadata flush
	 * in main and create a dedicated thread to handle these signals.
	 */
	switch (opt_type) {
	case LTTNG_CONSUMER32_UST:
	case LTTNG_CONSUMER64_UST:
		consumer_signal_init();
		break;
	default:
		break;
	}
	ctx->type = opt_type;

	/* Create thread to manage channels */
	ret = pthread_create(&channel_thread, NULL, consumer_thread_channel_poll,
			(void *) ctx);
	if (ret != 0) {
		perror("pthread_create");
		goto error;
	}

	/* Create thread to manage the polling/writing of trace metadata */
	ret = pthread_create(&metadata_thread, NULL, consumer_thread_metadata_poll,
			(void *) ctx);
	if (ret != 0) {
		perror("pthread_create");
		goto metadata_error;
	}

	/* Create thread to manage the polling/writing of trace data */
	ret = pthread_create(&data_thread, NULL, consumer_thread_data_poll,
			(void *) ctx);
	if (ret != 0) {
		perror("pthread_create");
		goto data_error;
	}

	/* Create the thread to manage the receive of fd */
	ret = pthread_create(&sessiond_thread, NULL, consumer_thread_sessiond_poll,
			(void *) ctx);
	if (ret != 0) {
		perror("pthread_create");
		goto sessiond_error;
	}

	switch (opt_type) {
	case LTTNG_CONSUMER32_UST:
	case LTTNG_CONSUMER64_UST:
		/* Create the thread to manage the metadata periodic timers */
		ret = pthread_create(&metadata_timer_thread, NULL,
				consumer_timer_metadata_thread, (void *) ctx);
		if (ret != 0) {
			perror("pthread_create");
			goto metadata_timer_error;
		}

		ret = pthread_detach(metadata_timer_thread);
		if (ret) {
			errno = ret;
			perror("pthread_detach");
		}
		break;
	default:
		break;
	}

metadata_timer_error:
	ret = pthread_join(sessiond_thread, &status);
	if (ret != 0) {
		perror("pthread_join");
		goto error;
	}

sessiond_error:
	ret = pthread_join(data_thread, &status);
	if (ret != 0) {
		perror("pthread_join");
		goto error;
	}

data_error:
	ret = pthread_join(metadata_thread, &status);
	if (ret != 0) {
		perror("pthread_join");
		goto error;
	}

metadata_error:
	ret = pthread_join(channel_thread, &status);
	if (ret != 0) {
		perror("pthread_join");
		goto error;
	}

	if (!ret) {
		ret = EXIT_SUCCESS;
		lttng_consumer_send_error(ctx, LTTCOMM_CONSUMERD_EXIT_SUCCESS);
		goto end;
	}

error:
	ret = EXIT_FAILURE;
	if (ctx) {
		lttng_consumer_send_error(ctx, LTTCOMM_CONSUMERD_EXIT_FAILURE);
	}

end:
	lttng_consumer_destroy(ctx);
	lttng_consumer_cleanup();

	return ret;
}