Exemplo n.º 1
0
/** Handle a network control message callback for a new "watch directory"
 *
 * @param[in] ctx the network
 * @param[in] data the message
 * @param[in] data_size size of the data
 * @param[in] now the current time
 */
static void fr_network_directory_callback(void *ctx, void const *data, size_t data_size, UNUSED fr_time_t now)
{
	int			num_messages;
	fr_network_t		*nr = ctx;
	fr_network_socket_t	*s;
	fr_app_io_t const	*app_io;
	fr_event_vnode_func_t	funcs = { .extend = fr_network_vnode_extend };

	rad_assert(data_size == sizeof(s->listen));

	if (data_size != sizeof(s->listen)) return;

	s = talloc_zero(nr, fr_network_socket_t);
	rad_assert(s != NULL);

	s->nr = nr;
	memcpy(&s->listen, data, sizeof(s->listen));
	s->number = nr->num_sockets++;

	MEM(s->waiting = fr_heap_create(s, waiting_cmp, fr_channel_data_t, channel.heap_id));

	talloc_set_destructor(s, _network_socket_free);

	/*
	 *	Allocate the ring buffer for messages and packets.
	 */
	num_messages = s->listen->num_messages;
	if (num_messages < 8) num_messages = 8;

	s->ms = fr_message_set_create(s, num_messages,
				      sizeof(fr_channel_data_t),
				      s->listen->default_message_size * s->listen->num_messages);
	if (!s->ms) {
		fr_log(nr->log, L_ERR, "Failed creating message buffers for directory IO: %s", fr_strerror());
		talloc_free(s);
		return;
	}

	app_io = s->listen->app_io;

	if (app_io->event_list_set) app_io->event_list_set(s->listen->app_io_instance, nr->el, nr);

	rad_assert(app_io->fd);
	s->fd = app_io->fd(s->listen->app_io_instance);
	s->filter = FR_EVENT_FILTER_VNODE;

	if (fr_event_filter_insert(nr, nr->el, s->fd, s->filter,
				   &funcs,
				   app_io->error ? fr_network_error : NULL,
				   s) < 0) {
		PERROR("Failed adding new socket to event loop");
		talloc_free(s);
		return;
	}

	(void) rbtree_insert(nr->sockets, s);
	(void) rbtree_insert(nr->sockets_by_num, s);

	DEBUG3("Using new socket with FD %d", s->fd);
}
/*
 *	The "detail.work" file exists, and is open in the 'fd'.
 */
static int work_exists(proto_detail_file_thread_t *thread, int fd)
{
	proto_detail_file_t const *inst = thread->inst;
	bool			opened = false;
	proto_detail_work_thread_t     *work;
	fr_listen_t		*li = NULL;
	struct stat		st;

	fr_event_vnode_func_t	funcs = { .delete = mod_vnode_delete };

	DEBUG3("proto_detail (%s): Trying to lock %s", thread->name, inst->filename_work);

	/*
	 *	"detail.work" exists, try to lock it.
	 */
	if (rad_lockfd_nonblock(fd, 0) < 0) {
		struct timeval when, now;

		DEBUG3("proto_detail (%s): Failed locking %s: %s",
		       thread->name, inst->filename_work, fr_syserror(errno));

		close(fd);

		when.tv_usec = thread->lock_interval % USEC;
		when.tv_sec = thread->lock_interval / USEC;

		/*
		 *	Ensure that we don't do massive busy-polling.
		 */
		thread->lock_interval += thread->lock_interval / 2;
		if (thread->lock_interval > (30 * USEC)) thread->lock_interval = 30 * USEC;

		DEBUG3("proto_detail (%s): Waiting %d.%06ds for lock on file %s",
		       thread->name, (int) when.tv_sec, (int) when.tv_usec, inst->filename_work);

		gettimeofday(&now, NULL);
		fr_timeval_add(&when, &when, &now);

		if (fr_event_timer_insert(thread, thread->el, &thread->ev,
					  &when, work_retry_timer, thread) < 0) {
			ERROR("Failed inserting retry timer for %s", inst->filename_work);
		}
		return 0;
	}

	DEBUG3("proto_detail (%s): Obtained lock and starting to process file %s",
	       thread->name, inst->filename_work);

	/*
	 *	Ignore empty files.
	 */
	if (fstat(fd, &st) < 0) {
		ERROR("Failed opening %s: %s", inst->filename_work,
		      fr_syserror(errno));
		unlink(inst->filename_work);
		close(fd);
		return 1;
	}

	if (!st.st_size) {
		DEBUG3("proto_detail (%s): %s file is empty, ignoring it.",
		       thread->name, inst->filename_work);
		unlink(inst->filename_work);
		close(fd);
		return 1;
	}

	/*
	 *	This listener is allocated in a thread-specific
	 *	context, so it doesn't need a destructor,
	 */
	MEM(li = talloc_zero(NULL, fr_listen_t));

	/*
	 *	Create a new listener, and insert it into the
	 *	scheduler.  Shamelessly copied from proto_detail.c
	 *	mod_open(), with changes.
	 *
	 *	This listener is parented from the worker.  So that
	 *	when the worker goes away, so does the listener.
	 */
	li->app_io = inst->parent->work_io;

	li->app = inst->parent->self;
	li->app_instance = inst->parent;
	li->server_cs = inst->parent->server_cs;

	/*
	 *	The worker may be in a different thread, so avoid
	 *	talloc threading issues by using a NULL TALLOC_CTX.
	 */
	MEM(li->thread_instance = work = talloc_zero(li, proto_detail_work_thread_t));

	li->app_io_instance = inst->parent->work_io_instance;
	work->inst = li->app_io_instance;
	work->file_parent = thread;
	work->ev = NULL;

	li->fd = work->fd = dup(fd);
	if (work->fd < 0) {
		DEBUG("proto_detail (%s): Failed opening %s: %s",
		      thread->name, inst->filename_work, fr_syserror(errno));

		close(fd);
		talloc_free(li);
		return -1;
	}

	/*
	 *	Don't do anything until the file has been deleted.
	 *
	 *	@todo - ensure that proto_detail_work is done the file...
	 *	maybe by creating a new instance?
	 */
	if (fr_event_filter_insert(thread, thread->el, fd, FR_EVENT_FILTER_VNODE,
				   &funcs, NULL, thread) < 0) {
		PERROR("Failed adding work socket to event loop");
		close(fd);
		talloc_free(li);
		return -1;
	}

	/*
	 *	Remember this for later.
	 */
	thread->vnode_fd = fd;

	/*
	 *	For us, this is the worker listener.
	 *	For the worker, this is it's own parent
	 */
	thread->listen = li;

	work->filename_work = talloc_strdup(work, inst->filename_work);

	/*
	 *	Set configurable parameters for message ring buffer.
	 */
	li->default_message_size = inst->parent->max_packet_size;
	li->num_messages = inst->parent->num_messages;

	pthread_mutex_lock(&thread->worker_mutex);
	thread->num_workers++;
	pthread_mutex_unlock(&thread->worker_mutex);

	/*
	 *	Open the detail.work file.
	 */
	if (li->app_io->open(li) < 0) {
		ERROR("Failed opening %s", li->app_io->name);
		goto error;
	}
	opened = true;

	rad_assert(li->app_io->get_name);
	li->name = li->app_io->get_name(li);

	if (!fr_schedule_listen_add(inst->parent->sc, li)) {
	error:
		if (fr_event_fd_delete(thread->el, thread->vnode_fd, FR_EVENT_FILTER_VNODE) < 0) {
			PERROR("Failed removing DELETE callback when opening work file");
		}
		close(thread->vnode_fd);
		thread->vnode_fd = -1;

		if (opened) {
			(void) li->app_io->close(li);
			thread->listen = NULL;
			li = NULL;
		}

		talloc_free(li);
		return -1;
	}

	/*
	 *	Tell the worker to clean itself up.
	 */
	work->listen = li;

	return 0;
}