2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21 * @brief Allow multiple threads to write to the same set of files.
23 * @author Alan DeKok <aland@freeradius.org>
24 * @copyright 2014 The FreeRADIUS server project
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/exfile.h>
32 typedef struct exfile_entry_t {
33 int fd; //!< File descriptor associated with an entry.
35 uint32_t hash; //!< Hash for cheap comparison.
36 time_t last_used; //!< Last time the entry was used.
37 char *filename; //!< Filename.
42 uint32_t max_entries; //!< How many file descriptors we keep track of.
43 uint32_t max_idle; //!< Maximum idle time for a descriptor.
47 pthread_mutex_t mutex;
49 exfile_entry_t *entries;
55 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
56 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
60 * This is easier than ifdef's throughout the code.
62 #define PTHREAD_MUTEX_LOCK(_x)
63 #define PTHREAD_MUTEX_UNLOCK(_x)
66 #define MAX_TRY_LOCK 4 //!< How many times we attempt to acquire a lock
67 //!< before giving up.
69 static int _exfile_free(exfile_t *ef)
73 PTHREAD_MUTEX_LOCK(&ef->mutex);
75 for (i = 0; i < ef->max_entries; i++) {
76 if (!ef->entries[i].filename) continue;
78 close(ef->entries[i].fd);
81 PTHREAD_MUTEX_UNLOCK(&ef->mutex);
84 pthread_mutex_destroy(&ef->mutex);
91 /** Initialize a way for multiple threads to log to one or more files.
93 * @param ctx The talloc context
94 * @param max_entries Max file descriptors to cache, and manage locks for.
95 * @param max_idle Maximum time a file descriptor can be idle before it's closed.
96 * @param locking whether or not to lock the files.
97 * @return the new context, or NULL on error.
99 exfile_t *exfile_init(TALLOC_CTX *ctx, uint32_t max_entries, uint32_t max_idle, bool locking)
103 ef = talloc_zero(ctx, exfile_t);
104 if (!ef) return NULL;
106 ef->entries = talloc_zero_array(ef, exfile_entry_t, max_entries);
112 #ifdef HAVE_PTHREAD_H
113 if (pthread_mutex_init(&ef->mutex, NULL) != 0) {
119 ef->max_entries = max_entries;
120 ef->max_idle = max_idle;
121 ef->locking = locking;
123 talloc_set_destructor(ef, _exfile_free);
129 static void exfile_cleanup_entry(exfile_entry_t *entry)
131 TALLOC_FREE(entry->filename);
139 /** Open a new log file, or maybe an existing one.
141 * When multithreaded, the FD is locked via a mutex. This way we're
142 * sure that no other thread is writing to the file.
144 * @param ef The logfile context returned from exfile_init().
145 * @param filename the file to open.
146 * @param permissions to use.
147 * @param append If true seek to the end of the file.
148 * @return an FD used to write to the file, or -1 on error.
150 int exfile_open(exfile_t *ef, char const *filename, mode_t permissions, bool append)
152 int i, tries, unused, oldest;
154 time_t now = time(NULL);
157 if (!ef || !filename) return -1;
159 hash = fr_hash_string(filename);
162 PTHREAD_MUTEX_LOCK(&ef->mutex);
165 * Clean up idle entries.
167 if (now > (ef->last_cleaned + 1)) {
168 ef->last_cleaned = now;
170 for (i = 0; i < (int) ef->max_entries; i++) {
171 if (!ef->entries[i].filename) continue;
173 if ((ef->entries[i].last_used + ef->max_idle) >= now) continue;
176 * This will block forever if a thread is
177 * doing something stupid.
179 exfile_cleanup_entry(&ef->entries[i]);
184 * Find the matching entry, or an unused one.
186 * Also track which entry is the oldest, in case there
187 * are no unused entries.
190 for (i = 0; i < (int) ef->max_entries; i++) {
191 if (!ef->entries[i].filename) {
192 if (unused < 0) unused = i;
197 (ef->entries[i].last_used < ef->entries[oldest].last_used)) {
202 * Hash comparisons are fast. String comparisons are slow.
204 if (ef->entries[i].hash != hash) continue;
207 * But we still need to do string comparisons if
208 * the hash matches, because 1/2^16 filenames
209 * will result in a hash collision. And that's
210 * enough filenames in a long-running server to
211 * ensure that it happens.
213 if (strcmp(ef->entries[i].filename, filename) != 0) continue;
219 * There are no unused entries, free the oldest one.
222 exfile_cleanup_entry(&ef->entries[oldest]);
227 * Create a new entry.
231 ef->entries[i].hash = hash;
232 ef->entries[i].filename = talloc_strdup(ef->entries, filename);
233 ef->entries[i].fd = -1;
234 ef->entries[i].dup = -1;
236 ef->entries[i].fd = open(filename, O_RDWR | O_APPEND | O_CREAT, permissions);
237 if (ef->entries[i].fd < 0) {
242 * Maybe the directory doesn't exist. Try to
245 dir = talloc_strdup(ef, filename);
246 if (!dir) goto error;
247 p = strrchr(dir, FR_DIR_SEP);
249 fr_strerror_printf("No '/' in '%s'", filename);
255 * Ensure that the 'x' bit is set, so that we can
256 * read the directory.
258 dirperm = permissions;
259 if ((dirperm & 0600) != 0) dirperm |= 0100;
260 if ((dirperm & 0060) != 0) dirperm |= 0010;
261 if ((dirperm & 0006) != 0) dirperm |= 0001;
263 if (rad_mkdir(dir, dirperm, -1, -1) < 0) {
264 fr_strerror_printf("Failed to create directory %s: %s",
265 dir, strerror(errno));
271 ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
272 if (ef->entries[i].fd < 0) {
273 fr_strerror_printf("Failed to open file %s: %s",
274 filename, strerror(errno));
276 } /* else fall through to creating the rest of the entry */
277 } /* else the file was already opened */
281 * Lock from the start of the file.
283 if (lseek(ef->entries[i].fd, 0, SEEK_SET) < 0) {
284 fr_strerror_printf("Failed to seek in file %s: %s", filename, strerror(errno));
287 exfile_cleanup_entry(&ef->entries[i]);
289 PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
294 * Try to lock it. If we can't lock it, it's because
295 * some reader has re-named the file to "foo.work" and
296 * locked it. So, we close the current file, re-open it,
300 for (tries = 0; tries < MAX_TRY_LOCK; tries++) {
301 if (rad_lockfd_nonblock(ef->entries[i].fd, 0) >= 0) break;
303 if (errno != EAGAIN) {
304 fr_strerror_printf("Failed to lock file %s: %s", filename, strerror(errno));
308 close(ef->entries[i].fd);
309 ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
310 if (ef->entries[i].fd < 0) {
311 fr_strerror_printf("Failed to open file %s: %s",
312 filename, strerror(errno));
317 if (tries >= MAX_TRY_LOCK) {
318 fr_strerror_printf("Failed to lock file %s: too many tries", filename);
324 * Maybe someone deleted the file while we were waiting
325 * for the lock. If so, re-open it.
327 if (fstat(ef->entries[i].fd, &st) < 0) {
328 fr_strerror_printf("Failed to stat file %s: %s", filename, strerror(errno));
332 if (st.st_nlink == 0) {
333 close(ef->entries[i].fd);
334 ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
335 if (ef->entries[i].fd < 0) {
336 fr_strerror_printf("Failed to open file %s: %s",
337 filename, strerror(errno));
343 * Seek to the end of the file before returning the FD to
346 if (append) lseek(ef->entries[i].fd, 0, SEEK_END);
349 * Return holding the mutex for the entry.
351 ef->entries[i].last_used = now;
352 ef->entries[i].dup = dup(ef->entries[i].fd);
353 if (ef->entries[i].dup < 0) {
354 fr_strerror_printf("Failed calling dup(): %s", strerror(errno));
358 return ef->entries[i].dup;
361 /** Close the log file. Really just return it to the pool.
363 * When multithreaded, the FD is locked via a mutex. This way we're
364 * sure that no other thread is writing to the file. This function
365 * will unlock the mutex, so that other threads can write to the file.
367 * @param ef The logfile context returned from exfile_init()
368 * @param fd the FD to close (i.e. return to the pool)
369 * @return 0 on success, or -1 on error
371 int exfile_close(exfile_t *ef, int fd)
375 for (i = 0; i < ef->max_entries; i++) {
376 if (!ef->entries[i].filename) continue;
379 * Unlock the bytes that we had previously locked.
381 if (ef->entries[i].dup == fd) {
382 if (ef->locking) (void) rad_unlockfd(ef->entries[i].dup, 0);
383 close(ef->entries[i].dup); /* releases the fcntl lock */
384 ef->entries[i].dup = -1;
386 PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
391 PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
393 fr_strerror_printf("Attempt to unlock file which is not tracked");
397 int exfile_unlock(exfile_t *ef, int fd)
401 for (i = 0; i < ef->max_entries; i++) {
402 if (!ef->entries[i].filename) continue;
404 if (ef->entries[i].dup == fd) {
405 ef->entries[i].dup = -1;
406 PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
411 PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
413 fr_strerror_printf("Attempt to unlock file which does not exist");