All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
exfile.c
Go to the documentation of this file.
1 /*
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.
6  *
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.
11  *
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
15  */
16 
17 /*
18  * $Id: 4e61f2dc46f9805b250b3950372178da3cc6a757 $
19  *
20  * @file exfile.c
21  * @brief Allow multiple threads to write to the same set of files.
22  *
23  * @author Alan DeKok <aland@freeradius.org>
24  * @copyright 2014 The FreeRADIUS server project
25  */
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/exfile.h>
28 
29 #include <sys/stat.h>
30 #include <fcntl.h>
31 
32 typedef struct exfile_entry_t {
33  int fd; //!< File descriptor associated with an entry.
34  int dup;
35  uint32_t hash; //!< Hash for cheap comparison.
36  time_t last_used; //!< Last time the entry was used.
37  char *filename; //!< Filename.
39 
40 
41 struct exfile_t {
42  uint32_t max_entries; //!< How many file descriptors we keep track of.
43  uint32_t max_idle; //!< Maximum idle time for a descriptor.
44 
45 #ifdef HAVE_PTHREAD_H
46  pthread_mutex_t mutex;
47 #endif
49  bool locking;
50 };
51 
52 
53 #ifdef HAVE_PTHREAD_H
54 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
55 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
56 
57 #else
58 /*
59  * This is easier than ifdef's throughout the code.
60  */
61 #define PTHREAD_MUTEX_LOCK(_x)
62 #define PTHREAD_MUTEX_UNLOCK(_x)
63 #endif
64 
65 #define MAX_TRY_LOCK 4 //!< How many times we attempt to acquire a lock
66  //!< before giving up.
67 
68 static int _exfile_free(exfile_t *ef)
69 {
70  uint32_t i;
71 
72  PTHREAD_MUTEX_LOCK(&ef->mutex);
73 
74  for (i = 0; i < ef->max_entries; i++) {
75  if (!ef->entries[i].filename) continue;
76 
77  close(ef->entries[i].fd);
78  }
79 
80  PTHREAD_MUTEX_UNLOCK(&ef->mutex);
81 
82 #ifdef HAVE_PTHREAD_H
83  pthread_mutex_destroy(&ef->mutex);
84 #endif
85 
86  return 0;
87 }
88 
89 
90 /** Initialize a way for multiple threads to log to one or more files.
91  *
92  * @param ctx The talloc context
93  * @param max_entries Max file descriptors to cache, and manage locks for.
94  * @param max_idle Maximum time a file descriptor can be idle before it's closed.
95  * @param locking whether or not to lock the files.
96  * @return
97  * - new context.
98  * - NULL on error.
99  */
100 exfile_t *exfile_init(TALLOC_CTX *ctx, uint32_t max_entries, uint32_t max_idle, bool locking)
101 {
102  exfile_t *ef;
103 
104  ef = talloc_zero(ctx, exfile_t);
105  if (!ef) return NULL;
106 
107  ef->entries = talloc_zero_array(ef, exfile_entry_t, max_entries);
108  if (!ef->entries) {
109  talloc_free(ef);
110  return NULL;
111  }
112 
113 #ifdef HAVE_PTHREAD_H
114  if (pthread_mutex_init(&ef->mutex, NULL) != 0) {
115  talloc_free(ef);
116  return NULL;
117  }
118 #endif
119 
120  ef->max_entries = max_entries;
121  ef->max_idle = max_idle;
122  ef->locking = locking;
123 
124  talloc_set_destructor(ef, _exfile_free);
125 
126  return ef;
127 }
128 
129 /** Open a new log file, or maybe an existing one.
130  *
131  * When multithreaded, the FD is locked via a mutex. This way we're
132  * sure that no other thread is writing to the file.
133  *
134  * @param ef The logfile context returned from exfile_init().
135  * @param filename the file to open.
136  * @param permissions to use.
137  * @param append If true seek to the end of the file.
138  * @return
139  * - FD used to write to the file.
140  * - -1 on failure.
141  */
142 int exfile_open(exfile_t *ef, char const *filename, mode_t permissions, bool append)
143 {
144  uint32_t i, tries;
145  uint32_t hash;
146  time_t now = time(NULL);
147  struct stat st;
148 
149  if (!ef || !filename) return -1;
150 
151  hash = fr_hash_string(filename);
152 
153  PTHREAD_MUTEX_LOCK(&ef->mutex);
154 
155  /*
156  * Clean up old entries.
157  */
158  for (i = 0; i < ef->max_entries; i++) {
159  if (!ef->entries[i].filename) continue;
160  if ((ef->entries[i].last_used + ef->max_idle) < now) {
161  /*
162  * This will block forever if a thread is
163  * doing something stupid.
164  */
165  TALLOC_FREE(ef->entries[i].filename);
166  close(ef->entries[i].fd);
167  }
168  }
169 
170  /*
171  * Find the matching entry.
172  */
173  for (i = 0; i < ef->max_entries; i++) {
174  if (!ef->entries[i].filename) continue;
175 
176  if (ef->entries[i].hash == hash) {
177  /*
178  * Same hash but different filename. Give up.
179  */
180  if (strcmp(ef->entries[i].filename, filename) != 0) {
181  PTHREAD_MUTEX_UNLOCK(&ef->mutex);
182  return -1;
183  }
184  /*
185  * Someone else failed to create the entry.
186  */
187  if (!ef->entries[i].filename) {
188  PTHREAD_MUTEX_UNLOCK(&ef->mutex);
189  return -1;
190  }
191  goto do_return;
192  }
193  }
194 
195  /*
196  * Find an unused entry
197  */
198  for (i = 0; i < ef->max_entries; i++) {
199  if (!ef->entries[i].filename) break;
200  }
201 
202  if (i >= ef->max_entries) {
203  fr_strerror_printf("Too many different filenames");
204  PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
205  return -1;
206  }
207 
208  /*
209  * Create a new entry.
210  */
211 
212  ef->entries[i].hash = hash;
213  ef->entries[i].filename = talloc_strdup(ef->entries, filename);
214  ef->entries[i].fd = -1;
215 
216  ef->entries[i].fd = open(filename, O_RDWR | O_APPEND | O_CREAT, permissions);
217  if (ef->entries[i].fd < 0) {
218  mode_t dirperm;
219  char *p, *dir;
220 
221  /*
222  * Maybe the directory doesn't exist. Try to
223  * create it.
224  */
225  dir = talloc_strdup(ef, filename);
226  if (!dir) goto error;
227  p = strrchr(dir, FR_DIR_SEP);
228  if (!p) {
229  fr_strerror_printf("No '/' in '%s'", filename);
230  goto error;
231  }
232  *p = '\0';
233 
234  /*
235  * Ensure that the 'x' bit is set, so that we can
236  * read the directory.
237  */
238  dirperm = permissions;
239  if ((dirperm & 0600) != 0) dirperm |= 0100;
240  if ((dirperm & 0060) != 0) dirperm |= 0010;
241  if ((dirperm & 0006) != 0) dirperm |= 0001;
242 
243  if (rad_mkdir(dir, dirperm, -1, -1) < 0) {
244  fr_strerror_printf("Failed to create directory %s: %s",
245  dir, strerror(errno));
246  talloc_free(dir);
247  goto error;
248  }
249  talloc_free(dir);
250 
251  ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
252  if (ef->entries[i].fd < 0) {
253  fr_strerror_printf("Failed to open file %s: %s",
254  filename, strerror(errno));
255  goto error;
256  } /* else fall through to creating the rest of the entry */
257  } /* else the file was already opened */
258 
259 do_return:
260  /*
261  * Lock from the start of the file.
262  */
263  if (lseek(ef->entries[i].fd, 0, SEEK_SET) < 0) {
264  fr_strerror_printf("Failed to seek in file %s: %s", filename, strerror(errno));
265 
266  error:
267  ef->entries[i].hash = 0;
268  TALLOC_FREE(ef->entries[i].filename);
269  close(ef->entries[i].fd);
270  ef->entries[i].fd = -1;
271 
272  PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
273  return -1;
274  }
275 
276  /*
277  * Try to lock it. If we can't lock it, it's because
278  * some reader has re-named the file to "foo.work" and
279  * locked it. So, we close the current file, re-open it,
280  * and try again/
281  */
282  if (ef->locking) {
283  for (tries = 0; tries < MAX_TRY_LOCK; tries++) {
284  if (rad_lockfd_nonblock(ef->entries[i].fd, 0) >= 0) break;
285 
286  if (errno != EAGAIN) {
287  fr_strerror_printf("Failed to lock file %s: %s", filename, strerror(errno));
288  goto error;
289  }
290 
291  close(ef->entries[i].fd);
292  ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
293  if (ef->entries[i].fd < 0) {
294  fr_strerror_printf("Failed to open file %s: %s",
295  filename, strerror(errno));
296  goto error;
297  }
298  }
299 
300  if (tries >= MAX_TRY_LOCK) {
301  fr_strerror_printf("Failed to lock file %s: too many tries", filename);
302  goto error;
303  }
304  }
305 
306  /*
307  * Maybe someone deleted the file while we were waiting
308  * for the lock. If so, re-open it.
309  */
310  if (fstat(ef->entries[i].fd, &st) < 0) {
311  fr_strerror_printf("Failed to stat file %s: %s", filename, strerror(errno));
312  goto error;
313  }
314 
315  if (st.st_nlink == 0) {
316  close(ef->entries[i].fd);
317  ef->entries[i].fd = open(filename, O_WRONLY | O_CREAT, permissions);
318  if (ef->entries[i].fd < 0) {
319  fr_strerror_printf("Failed to open file %s: %s",
320  filename, strerror(errno));
321  goto error;
322  }
323  }
324 
325  /*
326  * Seek to the end of the file before returning the FD to
327  * the caller.
328  */
329  if (append) lseek(ef->entries[i].fd, 0, SEEK_END);
330 
331  /*
332  * Return holding the mutex for the entry.
333  */
334  ef->entries[i].last_used = now;
335  ef->entries[i].dup = dup(ef->entries[i].fd);
336  if (ef->entries[i].dup < 0) {
337  fr_strerror_printf("Failed calling dup(): %s", strerror(errno));
338  goto error;
339  }
340 
341  return ef->entries[i].dup;
342 }
343 
344 /** Close the log file. Really just return it to the pool.
345  *
346  * When multithreaded, the FD is locked via a mutex. This way we're sure that no other thread is
347  * writing to the file. This function will unlock the mutex, so that other threads can write to
348  * the file.
349  *
350  * @param ef The logfile context returned from #exfile_init.
351  * @param fd the FD to close (i.e. return to the pool).
352  * @return
353  * - 0 on success.
354  * - -1 on failure.
355  */
356 int exfile_close(exfile_t *ef, int fd)
357 {
358  uint32_t i;
359 
360  for (i = 0; i < ef->max_entries; i++) {
361  if (!ef->entries[i].filename) continue;
362 
363  /*
364  * Unlock the bytes that we had previously locked.
365  */
366  if (ef->entries[i].dup == fd) {
367  if (ef->locking) (void) rad_unlockfd(ef->entries[i].dup, 0);
368  close(ef->entries[i].dup); /* releases the fcntl lock */
369  ef->entries[i].dup = -1;
370 
371  PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
372  return 0;
373  }
374  }
375 
376  PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
377 
378  fr_strerror_printf("Attempt to unlock file which does not exist");
379  return -1;
380 }
381 
382 int exfile_unlock(exfile_t *ef, int fd)
383 {
384  uint32_t i;
385 
386  for (i = 0; i < ef->max_entries; i++) {
387  if (!ef->entries[i].filename) continue;
388 
389  if (ef->entries[i].dup == fd) {
390  ef->entries[i].dup = -1;
391  PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
392  return 0;
393  }
394  }
395 
396  PTHREAD_MUTEX_UNLOCK(&(ef->mutex));
397 
398  fr_strerror_printf("Attempt to unlock file which does not exist");
399  return -1;
400 }
bool locking
Definition: exfile.c:49
#define pthread_mutex_init(_x, _y)
Definition: rlm_eap.h:75
uint32_t max_idle
Maximum idle time for a descriptor.
Definition: exfile.c:43
#define PTHREAD_MUTEX_LOCK(_x)
Definition: exfile.c:61
exfile_t * exfile_init(TALLOC_CTX *ctx, uint32_t max_entries, uint32_t max_idle, bool locking)
Initialize a way for multiple threads to log to one or more files.
Definition: exfile.c:100
uint32_t hash
Hash for cheap comparison.
Definition: exfile.c:35
int rad_mkdir(char *directory, mode_t mode, uid_t uid, gid_t gid)
Create possibly many directories.
Definition: util.c:90
uint32_t fr_hash_string(char const *p)
Definition: hash.c:778
static unsigned int hash(char const *username, unsigned int tablesize)
Definition: rlm_passwd.c:124
static int _exfile_free(exfile_t *ef)
Definition: exfile.c:68
int exfile_open(exfile_t *ef, char const *filename, mode_t permissions, bool append)
Open a new log file, or maybe an existing one.
Definition: exfile.c:142
exfile_entry_t * entries
Definition: exfile.c:48
#define PTHREAD_MUTEX_UNLOCK(_x)
Definition: exfile.c:62
char * filename
Filename.
Definition: exfile.c:37
int fd
File descriptor associated with an entry.
Definition: exfile.c:33
int exfile_close(exfile_t *ef, int fd)
Close the log file.
Definition: exfile.c:356
int exfile_unlock(exfile_t *ef, int fd)
Definition: exfile.c:382
#define MAX_TRY_LOCK
How many times we attempt to acquire a lock.
Definition: exfile.c:65
int rad_unlockfd(int fd, int lock_len)
Definition: misc.c:191
uint32_t max_entries
How many file descriptors we keep track of.
Definition: exfile.c:42
void fr_strerror_printf(char const *,...) CC_HINT(format(printf
struct exfile_entry_t exfile_entry_t
time_t last_used
Last time the entry was used.
Definition: exfile.c:36
int rad_lockfd_nonblock(int fd, int lock_len)
Definition: misc.c:166
#define pthread_mutex_destroy(_x)
Definition: rlm_eap.h:76
Definition: exfile.c:32