/** 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; }