* Copyright (C) 2011 - David Goulet <david.goulet@polymtl.ca>
* 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 as published by the Free
- * Software Foundation; only version 2 of the License.
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2 only,
+ * as published by the Free Software Foundation.
*
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along with
- * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
- * Place - Suite 330, Boston, MA 02111-1307, USA.
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
-#include <urcu/futex.h>
+#include <urcu/uatomic.h>
#include <unistd.h>
#include <config.h>
#include <common/defaults.h>
#include <common/kernel-consumer/kernel-consumer.h>
#include <common/ust-consumer/ust-consumer.h>
+#include <common/futex.h>
#include "lttng-sessiond.h"
#include "channel.h"
#include "context.h"
#include "event.h"
-#include "futex.h"
#include "kernel.h"
#include "modprobe.h"
#include "shm.h"
#include "ust-ctl.h"
#include "utils.h"
+#include "fd-limit.h"
#define CONSUMERD_FILE "lttng-consumerd"
static const char *consumerd32_libdir = CONFIG_CONSUMERD32_LIBDIR;
static const char *consumerd64_libdir = CONFIG_CONSUMERD64_LIBDIR;
+/*
+ * Consumer daemon state which is changed when spawning it, killing it or in
+ * case of a fatal error.
+ */
+enum consumerd_state {
+ CONSUMER_STARTED = 1,
+ CONSUMER_STOPPED = 2,
+ CONSUMER_ERROR = 3,
+};
+
+/*
+ * This consumer daemon state is used to validate if a client command will be
+ * able to reach the consumer. If not, the client is informed. For instance,
+ * doing a "lttng start" when the consumer state is set to ERROR will return an
+ * error to the client.
+ *
+ * The following example shows a possible race condition of this scheme:
+ *
+ * consumer thread error happens
+ * client cmd arrives
+ * client cmd checks state -> still OK
+ * consumer thread exit, sets error
+ * client cmd try to talk to consumer
+ * ...
+ *
+ * However, since the consumer is a different daemon, we have no way of making
+ * sure the command will reach it safely even with this state flag. This is why
+ * we consider that up to the state validation during command processing, the
+ * command is safe. After that, we can not guarantee the correctness of the
+ * client request vis-a-vis the consumer.
+ */
+static enum consumerd_state ust_consumerd_state;
+static enum consumerd_state kernel_consumerd_state;
+
static
void setup_consumerd_path(void)
{
if (ret) {
PERROR("close");
}
-
}
}
for (i = 0; i < 2; i++) {
lkm.u.channel.channel_key = session->metadata->fd;
lkm.u.channel.max_sb_size = session->metadata->conf->attr.subbuf_size;
lkm.u.channel.mmap_len = 0; /* for kernel */
- DBG("Sending metadata channel %d to consumer", lkm.u.stream.stream_key);
+ DBG("Sending metadata channel %d to consumer", lkm.u.channel.channel_key);
ret = lttcomm_send_unix_sock(sock, &lkm, sizeof(lkm));
if (ret < 0) {
PERROR("send consumer channel");
ERR("consumer return code : %s", lttcomm_get_readable_code(-code));
error:
+ /* Immediately set the consumerd state to stopped */
+ if (consumer_data->type == LTTNG_CONSUMER_KERNEL) {
+ uatomic_set(&kernel_consumerd_state, CONSUMER_ERROR);
+ } else if (consumer_data->type == LTTNG_CONSUMER64_UST ||
+ consumer_data->type == LTTNG_CONSUMER32_UST) {
+ uatomic_set(&ust_consumerd_state, CONSUMER_ERROR);
+ } else {
+ /* Code flow error... */
+ assert(0);
+ }
+
if (consumer_data->err_sock >= 0) {
ret = close(consumer_data->err_sock);
if (ret) {
* Using message-based transmissions to ensure we don't
* have to deal with partially received messages.
*/
+ ret = lttng_fd_get(LTTNG_FD_APPS, 1);
+ if (ret < 0) {
+ ERR("Exhausted file descriptors allowed for applications.");
+ free(ust_cmd);
+ ret = close(sock);
+ if (ret) {
+ PERROR("close");
+ }
+ sock = -1;
+ continue;
+ }
ret = lttcomm_recv_unix_sock(sock, &ust_cmd->reg_msg,
sizeof(struct ust_register_msg));
if (ret < 0 || ret < sizeof(struct ust_register_msg)) {
if (ret) {
PERROR("close");
}
+ lttng_fd_put(LTTNG_FD_APPS, 1);
sock = -1;
continue;
}
if (ret) {
PERROR("close");
}
+ lttng_fd_put(LTTNG_FD_APPS, 1);
}
unlink(apps_unix_sock_path);
return -ret;
}
+/*
+ * Command LTTNG_LIST_TRACEPOINT_FIELDS processed by the client thread.
+ */
+static ssize_t cmd_list_tracepoint_fields(int domain,
+ struct lttng_event_field **fields)
+{
+ int ret;
+ ssize_t nb_fields = 0;
+
+ switch (domain) {
+ case LTTNG_DOMAIN_UST:
+ nb_fields = ust_app_list_event_fields(fields);
+ if (nb_fields < 0) {
+ ret = LTTCOMM_UST_LIST_FAIL;
+ goto error;
+ }
+ break;
+ case LTTNG_DOMAIN_KERNEL:
+ default: /* fall-through */
+ ret = LTTCOMM_UND;
+ goto error;
+ }
+
+ return nb_fields;
+
+error:
+ /* Return negative value to differentiate return code */
+ return -ret;
+}
+
/*
* Command LTTNG_START_TRACE processed by the client thread.
*/
switch(cmd_ctx->lsm->cmd_type) {
case LTTNG_LIST_SESSIONS:
case LTTNG_LIST_TRACEPOINTS:
+ case LTTNG_LIST_TRACEPOINT_FIELDS:
case LTTNG_LIST_DOMAINS:
case LTTNG_LIST_CHANNELS:
case LTTNG_LIST_EVENTS:
case LTTNG_CALIBRATE:
case LTTNG_LIST_SESSIONS:
case LTTNG_LIST_TRACEPOINTS:
+ case LTTNG_LIST_TRACEPOINT_FIELDS:
need_tracing_session = 0;
break;
default:
DBG("Getting session %s by name", cmd_ctx->lsm->session.name);
+ /*
+ * We keep the session list lock across _all_ commands
+ * for now, because the per-session lock does not
+ * handle teardown properly.
+ */
session_lock_list();
cmd_ctx->session = session_find_by_name(cmd_ctx->lsm->session.name);
- session_unlock_list();
if (cmd_ctx->session == NULL) {
if (cmd_ctx->lsm->session.name != NULL) {
ret = LTTCOMM_SESS_NOT_FOUND;
}
}
+ /* Consumer is in an ERROR state. Report back to client */
+ if (uatomic_read(&kernel_consumerd_state) == CONSUMER_ERROR) {
+ ret = LTTCOMM_NO_KERNCONSUMERD;
+ goto error;
+ }
+
/* Need a session for kernel command */
if (need_tracing_session) {
if (cmd_ctx->session->kernel_session == NULL) {
ret = LTTCOMM_KERN_CONSUMER_FAIL;
goto error;
}
+ uatomic_set(&kernel_consumerd_state, CONSUMER_STARTED);
} else {
pthread_mutex_unlock(&kconsumer_data.pid_mutex);
}
}
+
break;
case LTTNG_DOMAIN_UST:
{
+ /* Consumer is in an ERROR state. Report back to client */
+ if (uatomic_read(&ust_consumerd_state) == CONSUMER_ERROR) {
+ ret = LTTCOMM_NO_USTCONSUMERD;
+ goto error;
+ }
+
if (need_tracing_session) {
if (cmd_ctx->session->ust_session == NULL) {
ret = create_ust_session(cmd_ctx->session,
}
ust_consumerd64_fd = ustconsumer64_data.cmd_sock;
+ uatomic_set(&ust_consumerd_state, CONSUMER_STARTED);
} else {
pthread_mutex_unlock(&ustconsumer64_data.pid_mutex);
}
ust_consumerd32_fd = -EINVAL;
goto error;
}
+
ust_consumerd32_fd = ustconsumer32_data.cmd_sock;
+ uatomic_set(&ust_consumerd_state, CONSUMER_STARTED);
} else {
pthread_mutex_unlock(&ustconsumer32_data.pid_mutex);
}
}
skip_domain:
+ /* Validate consumer daemon state when start/stop trace command */
+ if (cmd_ctx->lsm->cmd_type == LTTNG_START_TRACE ||
+ cmd_ctx->lsm->cmd_type == LTTNG_STOP_TRACE) {
+ switch (cmd_ctx->lsm->domain.type) {
+ case LTTNG_DOMAIN_UST:
+ if (uatomic_read(&ust_consumerd_state) != CONSUMER_STARTED) {
+ ret = LTTCOMM_NO_USTCONSUMERD;
+ goto error;
+ }
+ break;
+ case LTTNG_DOMAIN_KERNEL:
+ if (uatomic_read(&kernel_consumerd_state) != CONSUMER_STARTED) {
+ ret = LTTCOMM_NO_KERNCONSUMERD;
+ goto error;
+ }
+ break;
+ }
+ }
+
/*
* Check that the UID or GID match that of the tracing session.
* The root user can interact with all sessions.
ret = LTTCOMM_OK;
break;
}
+ case LTTNG_LIST_TRACEPOINT_FIELDS:
+ {
+ struct lttng_event_field *fields;
+ ssize_t nb_fields;
+
+ nb_fields = cmd_list_tracepoint_fields(cmd_ctx->lsm->domain.type, &fields);
+ if (nb_fields < 0) {
+ ret = -nb_fields;
+ goto error;
+ }
+
+ /*
+ * Setup lttng message with payload size set to the event list size in
+ * bytes and then copy list into the llm payload.
+ */
+ ret = setup_lttng_msg(cmd_ctx, sizeof(struct lttng_event_field) * nb_fields);
+ if (ret < 0) {
+ free(fields);
+ goto setup_error;
+ }
+
+ /* Copy event list into message payload */
+ memcpy(cmd_ctx->llm->payload, fields,
+ sizeof(struct lttng_event_field) * nb_fields);
+
+ free(fields);
+
+ ret = LTTCOMM_OK;
+ break;
+ }
+
case LTTNG_START_TRACE:
{
ret = cmd_start_trace(cmd_ctx->session);
{
ret = cmd_destroy_session(cmd_ctx->session,
cmd_ctx->lsm->session.name);
+ /*
+ * Set session to NULL so we do not unlock it after
+ * free.
+ */
+ cmd_ctx->session = NULL;
break;
}
case LTTNG_LIST_DOMAINS:
}
case LTTNG_LIST_CHANNELS:
{
- size_t nb_chan;
+ int nb_chan;
struct lttng_channel *channels;
nb_chan = cmd_list_channels(cmd_ctx->lsm->domain.type,
if (cmd_ctx->session) {
session_unlock(cmd_ctx->session);
}
+ if (need_tracing_session) {
+ session_unlock_list();
+ }
init_setup_error:
return ret;
}
PERROR("close");
}
sock = -1;
- free(cmd_ctx);
+ clean_command_ctx(&cmd_ctx);
continue;
}
int ret;
gid_t gid;
- gid = allowed_group();
- if (gid < 0) {
+ ret = allowed_group();
+ if (ret < 0) {
WARN("No tracing group detected");
ret = 0;
goto end;
}
+ gid = ret;
+
/* Set lttng run dir */
ret = chown(rundir, 0, gid);
if (ret < 0) {
rcu_register_thread();
- /* Create thread quit pipe */
- if ((ret = init_thread_quit_pipe()) < 0) {
- goto error;
- }
-
setup_consumerd_path();
/* Parse arguments */
/* 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);
+ }
+ }
+
+ /* Create thread quit pipe */
+ if ((ret = init_thread_quit_pipe()) < 0) {
+ goto error;
}
/* Check if daemon is UID = 0 */
}
}
+ /* Set consumer initial state */
+ kernel_consumerd_state = CONSUMER_STOPPED;
+ ust_consumerd_state = CONSUMER_STOPPED;
+
DBG("Client socket path %s", client_unix_sock_path);
DBG("Application socket path %s", apps_unix_sock_path);
DBG("LTTng run directory path: %s", rundir);
goto error;
}
+ /*
+ * Init UST app hash table. Alloc hash table before this point since
+ * cleanup() can get called after that point.
+ */
+ ust_app_ht_alloc();
+
/* After this point, we can safely call cleanup() with "goto exit" */
/*
/* Set ulimit for open files */
set_ulimit();
}
+ /* init lttng_fd tracking must be done after set_ulimit. */
+ lttng_fd_init();
ret = set_consumer_sockets(&ustconsumer64_data, rundir);
if (ret < 0) {
/* Init UST command queue. */
cds_wfq_init(&ust_cmd_queue.queue);
- /* Init UST app hash table */
- ust_app_ht_alloc();
-
/*
* Get session list pointer. This pointer MUST NOT be free(). This list is
* statically declared in session.c