/* * 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; }
/** Open a new log file, or maybe an existing one. * * When multithreaded, the FD is locked via a mutex. This way we're * sure that no other thread is writing to the file. * * @param ef The logfile context returned from exfile_init(). * @param filename the file to open. * @param permissions to use. * @param append If true seek to the end of the file. * @return an FD used to write to the file, or -1 on error. */ int exfile_open(exfile_t *ef, char const *filename, mode_t permissions, bool append) { uint32_t i, tries; uint32_t hash; time_t now = time(NULL); struct stat st; if (!ef || !filename) return -1; hash = fr_hash_string(filename); PTHREAD_MUTEX_LOCK(&ef->mutex); /* * Clean up old entries. */ for (i = 0; i < ef->max_entries; i++) { if (!ef->entries[i].filename) continue; if ((ef->entries[i].last_used + ef->max_idle) < now) { /* * This will block forever if a thread is * doing something stupid. */ TALLOC_FREE(ef->entries[i].filename); close(ef->entries[i].fd); } } /* * Find the matching entry. */ for (i = 0; i < ef->max_entries; i++) { if (!ef->entries[i].filename) continue; if (ef->entries[i].hash == hash) { /* * Same hash but different filename. Give up. */ if (strcmp(ef->entries[i].filename, filename) != 0) { PTHREAD_MUTEX_UNLOCK(&ef->mutex); return -1; } /* * Someone else failed to create the entry. */ if (!ef->entries[i].filename) { PTHREAD_MUTEX_UNLOCK(&ef->mutex); return -1; } goto do_return; } } /* * Find an unused entry */ for (i = 0; i < ef->max_entries; i++) { if (!ef->entries[i].filename) break; } if (i >= ef->max_entries) { fr_strerror_printf("Too many different filenames"); PTHREAD_MUTEX_UNLOCK(&(ef->mutex)); return -1; } /* * Create a new entry. */ ef->entries[i].hash = hash; ef->entries[i].filename = talloc_strdup(ef->entries, filename); ef->entries[i].fd = -1; ef->entries[i].fd = open(filename, O_RDWR | O_APPEND | O_CREAT, permissions); if (ef->entries[i].fd < 0) { mode_t dirperm; char *p, *dir; /* * Maybe the directory doesn't exist. Try to * create it. */ dir = talloc_strdup(ef, filename); if (!dir) goto error; p = strrchr(dir, FR_DIR_SEP); if (!p) { fr_strerror_printf("No '/' in '%s'", filename); goto error; } *p = '\0'; /* * Ensure that the 'x' bit is set, so that we can * read the directory. */ dirperm = permissions; if ((dirperm & 0600) != 0) dirperm |= 0100; if ((dirperm & 0060) != 0) dirperm |= 0010; if ((dirperm & 0006) != 0) dirperm |= 0001; if (rad_mkdir(dir, dirperm, -1, -1) < 0) { fr_strerror_printf("Failed to create directory %s: %s", dir, strerror(errno)); talloc_free(dir); goto error; } talloc_free(dir); ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions); if (ef->entries[i].fd < 0) { fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno)); goto error; } /* else fall through to creating the rest of the entry */ } /* else the file was already opened */ do_return: /* * Lock from the start of the file. */ if (lseek(ef->entries[i].fd, 0, SEEK_SET) < 0) { fr_strerror_printf("Failed to seek in file %s: %s", filename, strerror(errno)); error: ef->entries[i].hash = 0; TALLOC_FREE(ef->entries[i].filename); close(ef->entries[i].fd); ef->entries[i].fd = -1; PTHREAD_MUTEX_UNLOCK(&(ef->mutex)); return -1; } /* * Try to lock it. If we can't lock it, it's because * some reader has re-named the file to "foo.work" and * locked it. So, we close the current file, re-open it, * and try again/ */ tries = 0; while ((rad_lockfd_nonblock(ef->entries[i].fd, 0) < 0) && (tries < 4)) { if (errno != EAGAIN) { fr_strerror_printf("Failed to lock file %s: %s", filename, strerror(errno)); goto error; } close(ef->entries[i].fd); ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions); if (ef->entries[i].fd < 0) { fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno)); goto error; } } if (tries >= 4) { fr_strerror_printf("Failed to lock file %s: too many tries", filename); goto error; } /* * Maybe someone deleted the file while we were waiting * for the lock. If so, re-open it. */ if (fstat(ef->entries[i].fd, &st) < 0) { fr_strerror_printf("Failed to stat file %s: %s", filename, strerror(errno)); goto error; } if (st.st_nlink == 0) { close(ef->entries[i].fd); ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions); if (ef->entries[i].fd < 0) { fr_strerror_printf("Failed to open file %s: %s", filename, strerror(errno)); goto error; } } /* * Seek to the end of the file before returning the FD to * the caller. */ if (append) lseek(ef->entries[i].fd, 0, SEEK_END); /* * Return holding the mutex for the entry. */ ef->entries[i].last_used = now; ef->entries[i].dup = dup(ef->entries[i].fd); if (ef->entries[i].dup < 0) { fr_strerror_printf("Failed calling dup(): %s", strerror(errno)); goto error; } return ef->entries[i].dup; }