The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
file.c
Go to the documentation of this file.
1/*
2 * file.c
3 *
4 * Version: $Id: 625db65c04c4680bd91d7d77285c50717ed65659 $
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 *
20 * @copyright 2025 Network RADIUS SAS (legal@networkradius.com)
21 */
22
23RCSID("$Id: 625db65c04c4680bd91d7d77285c50717ed65659 $")
24
25#include <freeradius-devel/util/iovec.h>
26
27#include <sys/stat.h>
28
29#include "file.h"
30
31static uint32_t filename_hash(void const *data)
32{
34
35 return fr_hash_string(file->filename);
36}
37
38static int8_t filename_cmp(void const *one, void const *two)
39{
40 rlm_linelog_file_t const *a = one;
41 rlm_linelog_file_t const *b = two;
42
43 return CMP(strcmp(a->filename, b->filename), 0);
44}
45
47{
49
50 if (entry->failed) {
51 REDEBUG("Write failed - %s", fr_syserror(entry->error));
52 return UNLANG_ACTION_FAIL;
53 }
54
56}
57
59 request_t *request, UNUSED fr_value_box_list_t *in)
60{
61 fr_value_box_t *wrote;
63
64 if (entry->failed) {
65 REDEBUG("Write failed - %s", fr_syserror(entry->error));
66 return XLAT_ACTION_FAIL;
67 }
68
69 MEM(wrote = fr_value_box_alloc(ctx, FR_TYPE_SIZE, NULL));
70 wrote->vb_size = entry->data_len;
71
72 fr_dcursor_insert(out, wrote);
73
74 return XLAT_ACTION_DONE;
75}
76
77static inline CC_HINT(always_inline)
80{
81 fr_assert(action == FR_SIGNAL_CANCEL);
82 fr_assert(entry->request == request);
83
84 entry->request = NULL;
85}
86
88{
89 _batching_handle_signal(request, action, (rlm_linelog_file_entry_t *)mctx->rctx);
90}
91
93{
94 _batching_handle_signal(request, action, (rlm_linelog_file_entry_t *)xctx->rctx);
95}
96
98{
99 fr_hash_table_delete(file->thread_inst->file_table, file);
100
101 return 0;
102}
103
105{
106 talloc_free(uctx);
107}
108
109static inline CC_HINT(always_inline)
111{
113
114 for (entry = start_entry; entry < file->entry_p; entry++) {
115 entry->failed = true;
116 entry->error = error;
117
118 if (!entry->request) continue;
121 }
122 }
123}
124
126{
127 int ret, written, fd = -1;
128 bool with_delim;
129 size_t len = 0, header_len = 0;
130 off_t offset, file_size;
131 int write_error = 0;
132 struct stat stat_buf;
133 struct iovec to_write[3];
134 struct iovec *to_write_p = to_write;
136
137 fr_assert(inst->file.buffer_write);
139 fr_assert(file->filename);
140
141 if (file->write) {
142 FR_TIMER_DISARM(file->write);
143 }
144
145 with_delim = (inst->delimiter_len > 0);
146
147 fd = exfile_open(inst->file.ef, file->filename, inst->file.permissions, 0, &offset);
148 if (fd < 0) {
149 PERROR("Failed to open %s", file->filename);
150 error:
151 batching_mark_entries_failed(file, file->entry, errno);
152 goto done;
153 }
154
155 if (inst->file.group_str && (chown(file->filename, -1, inst->file.group) == -1)) {
156 ERROR("Unable to change system group of \"%s\" - %s", file->filename, fr_syserror(errno));
157 goto error;
158 }
159
160 /*
161 * If a header format is defined and we are at the beginning
162 * of the file then expand the format and write it out before
163 * writing the actual log entries.
164 */
165 if (!fr_type_is_null(file->log_header->type) && (offset == 0)) {
166 to_write_p->iov_base = UNCONST(char *, file->log_header->vb_strvalue);
167 header_len = to_write_p->iov_len = file->log_header->vb_length;
168 to_write_p++;
169
170 if (with_delim) {
171 to_write_p->iov_base = UNCONST(char *, inst->delimiter);
172 header_len += to_write_p->iov_len = inst->delimiter_len;
173 to_write_p++;
174 }
175 }
176
177 to_write_p->iov_base = fr_dbuff_start(&file->dbuff);
178 to_write_p->iov_len = fr_dbuff_used(&file->dbuff);
179 to_write_p++;
180
181 if (fstat(fd, &stat_buf) < 0) {
182 ERROR("Failed to stat file %s - %s", file->filename, fr_syserror(errno));
183 goto error;
184 }
185 file_size = stat_buf.st_size;
186
187 errno = 0;
188 ret = fr_writev(fd, to_write, to_write_p - to_write, fr_time_delta_from_sec(0));
189 written = ret - header_len;
190 write_error = errno;
191 if (ret >= 0) {
192 if (ret < (ssize_t)(header_len)) {
193 if (ftruncate(fd, 0) < 0) {
194 ERROR("Failed truncating file \"%s\" after partial header write - %s",
195 file->filename, fr_syserror(errno));
196 }
197 errno = write_error;
198
199 PERROR("Failed writing header to \"%s\"", file->filename);
200 goto error;
201 }
202 } else {
203 /*
204 * Check for partial write
205 */
206 if (fstat(fd, &stat_buf) < 0) {
207 ERROR("Failed to stat file %s - %s", file->filename, fr_syserror(errno));
208 goto error;
209 }
210
211 written = stat_buf.st_size - file_size - header_len;
212
213 if ((errno == ENOSPC) && (written > 0)) {
214 ERROR("No space left on device when writing to \"%s\". Not all data was written",
215 file->filename);
216 } else {
217 ERROR("Failed writing to \"%s\" - %s", file->filename, fr_syserror(errno));
218 goto error;
219 }
220 }
221
222 if (fsync(fd) < 0) {
223 ERROR("Failed syncing \"%s\" to persistent storage - %s", file->filename, fr_syserror(errno));
224 goto error;
225 }
226
227 for (rlm_linelog_file_entry_t *entry = file->entry; entry < file->entry_p; entry++) {
228 len += entry->data_len;
229
230 if (len > (size_t)written) {
231 request_t *request = entry->request;
232 ROPTIONAL(RWARN, WARN, "Buffered log write failed. Expected %zu bytes, but only %zu bytes were written", len, (size_t)written);
233 ret = ftruncate(fd, offset + (len - entry->data_len + header_len));
234 if (ret < 0) {
235 ERROR("Failed truncating file \"%s\" after partial write - %s",
236 file->filename, fr_syserror(errno));
237 }
238
239 batching_mark_entries_failed(file, entry, write_error);
240 break;
241 }
242
243 /*
244 * Needed because the write function could be called either from a timer, or directly
245 * when the buffer is full. In which case the last entry would not have been yielded
246 */
247 if (!entry->request) continue;
248 if (unlang_interpret_is_resumable(entry->request)) {
249 unlang_interpret_mark_runnable(entry->request);
250 }
251 }
252
253done:
254 if ((fd >= 0) && (exfile_close(inst->file.ef, fd) < 0)) {
255 PERROR("Failed closing file %s", file->filename);
256 }
257
259 file->entry_p = file->entry;
260
261 if (fr_time_delta_gt(inst->file.buffer_expiry, fr_time_delta_wrap(0))) {
262 (void) fr_timer_in(file, file->thread_inst->tl, &file->expiry, inst->file.buffer_expiry,
264 }
265}
266
268{
270}
271
273 linelog_call_env_t const *call_env, request_t *request,
274 struct iovec *vector_p, size_t vector_len)
275{
276 ssize_t ret;
277 char const *path;
279 rlm_linelog_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_linelog_thread_t);
281
282 fr_assert(inst->file.buffer_write);
283
284 path = call_env->filename->vb_strvalue;
285
287 &(rlm_linelog_file_t){ .filename = path });
288
289 if (!file) {
290 MEM(file = talloc_size(mctx->thread, sizeof(rlm_linelog_file_t) + (sizeof(rlm_linelog_file_entry_t) * inst->file.buffer_count)));
291 talloc_set_name_const(file, "rlm_linelog_file_t");
292
293 file->filename = talloc_strdup(file, path);
294 MEM(file->log_header = fr_value_box_alloc_null(file));
295 file->mod_inst = inst;
296 file->thread_inst = thread;
297
298 if (call_env->log_head && fr_value_box_copy(file, file->log_header, call_env->log_head) < 0) {
299 RPERROR("Failed to copy log header for buffered log file %pV", call_env->filename);
300 error:
303 }
304
305 file->entry_p = file->entry;
306 file->entry_last = file->entry + inst->file.buffer_count;
307 file->write = NULL;
308 file->expiry = NULL;
309 fr_dbuff_init_talloc(file, &file->dbuff, &file->tctx, 1024, SIZE_MAX);
310
311 if (!fr_hash_table_insert(thread->file_table, file)) {
312 RPERROR("Failed tracking buffered log file %pV", call_env->filename);
313 goto error;
314 }
315
316 talloc_set_destructor(file, _file_free);
317 }
318
319 if (file->expiry) {
320 FR_TIMER_DISARM(file->expiry);
321 }
322
323 fr_assert(file->entry_p < file->entry_last);
324 *file->entry_p = (rlm_linelog_file_entry_t) {
325 .request = request,
326 .file = file,
327 .failed = false,
328 .data_len = 0,
329 .error = 0
330 };
331
332 ret = fr_concatv(&file->dbuff, vector_p, vector_len);
333 if (ret < 0) {
334 RERROR("Failed to buffer log entry for %pV", call_env->filename);
335 file->entry_p->failed = true;
337 }
338
339 *entry_p = file->entry_p;
340
341 if (fr_time_delta_gt(inst->file.buffer_delay, fr_time_delta_wrap(0)) && !fr_timer_armed(file->write) ) {
342 if (unlikely(fr_timer_in(file, file->thread_inst->tl, &file->write, inst->file.buffer_delay,
343 false, _batching_handle_timeout, file)) < 0) {
344 RWARN("Failed adding timer to write logs for %pV", call_env->filename);
345 }
346 }
347
348 file->entry_p->data_len = ret;
349 file->entry_p++;
350
351 if (file->entry_p == file->entry_last) {
353
355 }
356
358}
359
361{
363 thread->tl = tl;
364}
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ UNLANG_ACTION_EXECUTE_NEXT
Execute the next unlang_t.
Definition action.h:38
@ UNLANG_ACTION_FAIL
Encountered an unexpected error.
Definition action.h:36
int const char * file
Definition acutest.h:702
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:186
#define RCSID(id)
Definition build.h:506
#define NDEBUG_UNUSED
Definition build.h:347
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition build.h:113
#define unlikely(_x)
Definition build.h:402
#define UNUSED
Definition build.h:336
int fr_dbuff_reset_talloc(fr_dbuff_t *dbuff)
Reset a talloced buffer to its initial length, clearing any data stored.
Definition dbuff.c:332
#define fr_dbuff_used(_dbuff_or_marker)
Return the number of bytes remaining between the start of the dbuff or marker and the current positio...
Definition dbuff.h:775
#define fr_dbuff_start(_dbuff_or_marker)
Return the 'start' position of a dbuff or marker.
Definition dbuff.h:906
static fr_dbuff_t * fr_dbuff_init_talloc(TALLOC_CTX *ctx, fr_dbuff_t *dbuff, fr_dbuff_uctx_talloc_t *tctx, size_t init, size_t max)
Initialise a special dbuff which automatically extends as additional data is written.
Definition dbuff.h:419
static int fr_dcursor_insert(fr_dcursor_t *cursor, void *v)
Insert directly after the current item.
Definition dcursor.h:435
#define MEM(x)
Definition debug.h:46
#define ERROR(fmt,...)
Definition dhcpclient.c:40
static fr_slen_t in
Definition dict.h:882
int exfile_open(exfile_t *ef, char const *filename, mode_t permissions, int flags, off_t *offset)
Open a new log file, or maybe an existing one.
Definition exfile.c:529
int exfile_close(exfile_t *ef, int fd)
Close the log file.
Definition exfile.c:596
bool fr_hash_table_insert(fr_hash_table_t *ht, void const *data)
Insert data into a hash table.
Definition hash.c:489
void * fr_hash_table_find_by_key(fr_hash_table_t *ht, uint32_t key, void const *data)
Hash table lookup with pre-computed key.
Definition hash.c:469
uint32_t fr_hash_string(char const *p)
Definition hash.c:900
bool fr_hash_table_delete(fr_hash_table_t *ht, void const *data)
Remove and free data (if a free function was specified)
Definition hash.c:617
#define fr_hash_table_alloc(_ctx, _hash_node, _cmp_node, _free_node)
Definition hash.h:61
talloc_free(hp)
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
Definition interpret.c:1636
bool unlang_interpret_is_resumable(request_t *request)
Check if a request as resumable.
Definition interpret.c:1618
ssize_t fr_writev(int fd, struct iovec vector[], int iovcnt, fr_time_delta_t timeout)
Write out a vector to a file descriptor.
Definition iovec.c:68
fr_slen_t fr_concatv(fr_dbuff_t *out, struct iovec vector[], int iovcnt)
Concatenate an iovec into a dbuff.
Definition iovec.c:38
#define PERROR(_fmt,...)
Definition log.h:228
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Definition log.h:540
#define RWARN(fmt,...)
Definition log.h:309
#define RERROR(fmt,...)
Definition log.h:310
#define RPERROR(fmt,...)
Definition log.h:314
@ FR_TYPE_SIZE
Unsigned integer capable of representing any memory address on the local system.
unsigned int uint32_t
long int ssize_t
long long int off_t
module_instance_t const * mi
Instance of the module being instantiated.
Definition module_ctx.h:42
void * thread
Thread specific instance data.
Definition module_ctx.h:43
void * rctx
Resume ctx that a module previously set.
Definition module_ctx.h:45
Temporary structure to hold arguments for module calls.
Definition module_ctx.h:41
linelog_buffer_action_t file_enqueue_write(rlm_linelog_file_entry_t **entry_p, module_ctx_t const *mctx, linelog_call_env_t const *call_env, request_t *request, struct iovec *vector_p, size_t vector_len)
Definition file.c:272
void file_batching_mod_handle_signal(module_ctx_t const *mctx, request_t *request, fr_signal_t action)
Definition file.c:87
static uint32_t filename_hash(void const *data)
Definition file.c:31
void file_thread_init(rlm_linelog_thread_t *thread, fr_timer_list_t *tl)
Definition file.c:360
void file_batching_xlat_handle_signal(xlat_ctx_t const *xctx, request_t *request, fr_signal_t action)
Definition file.c:92
unlang_action_t file_batching_mod_resume(UNUSED unlang_result_t *p_result, module_ctx_t const *mctx, request_t *request)
Definition file.c:46
static int8_t filename_cmp(void const *one, void const *two)
Definition file.c:38
static void _batching_handle_timeout(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
Definition file.c:267
static void _batching_cleanup_timer(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
Definition file.c:104
static int _file_free(rlm_linelog_file_t *file)
Definition file.c:97
static void _batch_write(rlm_linelog_file_t *file)
Definition file.c:125
static void batching_mark_entries_failed(rlm_linelog_file_t *file, rlm_linelog_file_entry_t *start_entry, int error)
Definition file.c:110
static void _batching_handle_signal(NDEBUG_UNUSED request_t *request, NDEBUG_UNUSED fr_signal_t action, rlm_linelog_file_entry_t *entry)
Definition file.c:78
xlat_action_t file_batching_xlat_resume(TALLOC_CTX *ctx, fr_dcursor_t *out, xlat_ctx_t const *xctx, request_t *request, UNUSED fr_value_box_list_t *in)
Definition file.c:58
char const * filename
Talloced filename string.
Definition file.h:45
bool failed
Write failed.
Definition file.h:37
fr_timer_list_t * tl
Timer list for this thread.
Definition file.h:29
request_t * request
The request that created the data.
Definition file.h:35
fr_hash_table_t * file_table
Hash table of files.
Definition file.h:28
size_t data_len
How much data this entry holds in the sbuff.
Definition file.h:38
int error
Error code if the write failed.
Definition file.h:39
linelog_buffer_action_t
Definition file.h:59
@ LINELOG_BUFFER_WRITE_YIELD
Writing buffered data yielded.
Definition file.h:61
@ LINELOG_BUFFER_WRITE_DONE
Writing buffered data completed.
Definition file.h:62
@ LINELOG_BUFFER_WRITE_FAIL
Writing buffered data failed.
Definition file.h:60
Definition file.h:34
linelog module thread specific structure
Definition file.h:27
#define fr_assert(_expr)
Definition rad_assert.h:37
#define REDEBUG(fmt,...)
#define WARN(fmt,...)
static bool done
Definition radclient.c:80
fr_value_box_t * log_head
Header to add to each new log file.
Definition rlm_linelog.h:97
fr_value_box_t * filename
File name, if output is to a file.
Definition rlm_linelog.h:99
linelog module instance
Definition rlm_linelog.h:47
void * data
Module's instance data.
Definition module.h:293
fr_signal_t
Signals that can be generated/processed by request signal handlers.
Definition signal.h:38
@ FR_SIGNAL_CANCEL
Request has been cancelled.
Definition signal.h:40
eap_aka_sim_process_conf_t * inst
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
#define talloc_get_type_abort_const
Definition talloc.h:110
#define talloc_strdup(_ctx, _str)
Definition talloc.h:142
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition time.h:590
#define fr_time_delta_wrap(_time)
Definition time.h:152
#define fr_time_delta_gt(_a, _b)
Definition time.h:283
"server local" time.
Definition time.h:69
An event timer list.
Definition timer.c:49
#define fr_timer_in(...)
Definition timer.h:87
#define FR_TIMER_DISARM(_ev)
Definition timer.h:91
static bool fr_timer_armed(fr_timer_t *ev)
Definition timer.h:120
xlat_action_t
Definition xlat.h:37
@ XLAT_ACTION_FAIL
An xlat function failed.
Definition xlat.h:44
@ XLAT_ACTION_DONE
We're done evaluating this level of nesting.
Definition xlat.h:43
#define fr_type_is_null(_x)
Definition types.h:347
int fr_value_box_copy(TALLOC_CTX *ctx, fr_value_box_t *dst, const fr_value_box_t *src)
Copy value data verbatim duplicating any buffers.
Definition value.c:4379
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition value.h:644
static fr_slen_t data
Definition value.h:1340
int nonnull(2, 5))
#define fr_value_box_alloc_null(_ctx)
Allocate a value box for later use with a value assignment function.
Definition value.h:655
static size_t char ** out
Definition value.h:1030
void * rctx
Resume context.
Definition xlat_ctx.h:54
An xlat calling ctx.
Definition xlat_ctx.h:49