The FreeRADIUS server $Id: a4e7f8c94e8038b7627bcf52356b7ed15955c1d7 $
Loading...
Searching...
No Matches
file.c
Go to the documentation of this file.
1/*
2 * file.c
3 *
4 * Version: $Id: 892605ad285fe4af998358f2ad1cd94534c8ba91 $
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: 892605ad285fe4af998358f2ad1cd94534c8ba91 $")
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 fr_cmp_ret_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
91
96
98{
99 (void) 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 ssize_t ret, written;
128 int fd = -1;
129 bool with_delim;
130 size_t len = 0, header_len = 0;
131 off_t offset, file_size;
132 int write_error = 0;
133 struct stat stat_buf;
134 struct iovec to_write[3];
135 struct iovec *to_write_p = to_write;
137
138 fr_assert(inst->file.buffer_write);
140 fr_assert(file->filename);
141
142 if (file->write) {
143 FR_TIMER_DISARM(file->write);
144 }
145
146 with_delim = (inst->delimiter_len > 0);
147
148 fd = exfile_open(inst->file.ef, file->filename, inst->file.permissions, 0, &offset);
149 if (fd < 0) {
150 PERROR("Failed to open %s", file->filename);
151 error:
152 batching_mark_entries_failed(file, file->entry, errno);
153 goto done;
154 }
155
156 if (inst->file.group_str && (chown(file->filename, -1, inst->file.group) == -1)) {
157 ERROR("Unable to change system group of \"%s\" - %s", file->filename, fr_syserror(errno));
158 goto error;
159 }
160
161 /*
162 * If a header format is defined and we are at the beginning
163 * of the file then expand the format and write it out before
164 * writing the actual log entries.
165 */
166 if (!fr_type_is_null(file->log_header->type) && (offset == 0)) {
167 to_write_p->iov_base = UNCONST(char *, file->log_header->vb_strvalue);
168 header_len = to_write_p->iov_len = file->log_header->vb_length;
169 to_write_p++;
170
171 if (with_delim) {
172 to_write_p->iov_base = UNCONST(char *, inst->delimiter);
173 header_len += to_write_p->iov_len = inst->delimiter_len;
174 to_write_p++;
175 }
176 }
177
178 to_write_p->iov_base = fr_dbuff_start(&file->dbuff);
179 to_write_p->iov_len = fr_dbuff_used(&file->dbuff);
180 to_write_p++;
181
182 if (fstat(fd, &stat_buf) < 0) {
183 ERROR("Failed to stat file %s - %s", file->filename, fr_syserror(errno));
184 goto error;
185 }
186 file_size = stat_buf.st_size;
187
188 errno = 0;
189 ret = fr_writev(fd, to_write, to_write_p - to_write, fr_time_delta_from_sec(0));
190 written = ret - header_len;
191 write_error = errno;
192 if (ret >= 0) {
193 if (ret < (ssize_t)(header_len)) {
194 if (ftruncate(fd, 0) < 0) {
195 ERROR("Failed truncating file \"%s\" after partial header write - %s",
196 file->filename, fr_syserror(errno));
197 }
198 errno = write_error;
199
200 PERROR("Failed writing header to \"%s\"", file->filename);
201 goto error;
202 }
203 } else {
204 /*
205 * Check for partial write
206 */
207 if (fstat(fd, &stat_buf) < 0) {
208 ERROR("Failed to stat file %s - %s", file->filename, fr_syserror(errno));
209 goto error;
210 }
211
212 written = stat_buf.st_size - file_size - header_len;
213
214 if ((write_error == ENOSPC) && (written > 0)) {
215 ERROR("No space left on device when writing to \"%s\". Not all data was written",
216 file->filename);
217 } else {
218 ERROR("Failed writing to \"%s\" - %s", file->filename, fr_syserror(write_error));
219 goto error;
220 }
221 }
222
223 if (fsync(fd) < 0) {
224 ERROR("Failed syncing \"%s\" to persistent storage - %s", file->filename, fr_syserror(errno));
225 goto error;
226 }
227
228 for (rlm_linelog_file_entry_t *entry = file->entry; entry < file->entry_p; entry++) {
229 len += entry->data_len;
230
231 if (len > (size_t)written) {
232 request_t *request = entry->request;
233 ROPTIONAL(RWARN, WARN, "Buffered log write failed. Expected %zu bytes, but only %zu bytes were written", len, (size_t)written);
234 ret = ftruncate(fd, offset + (len - entry->data_len + header_len));
235 if (ret < 0) {
236 ERROR("Failed truncating file \"%s\" after partial write - %s",
237 file->filename, fr_syserror(errno));
238 }
239
240 batching_mark_entries_failed(file, entry, write_error);
241 break;
242 }
243
244 /*
245 * Needed because the write function could be called either from a timer, or directly
246 * when the buffer is full. In which case the last entry would not have been yielded
247 */
248 if (!entry->request) continue;
249 if (unlang_interpret_is_resumable(entry->request)) {
250 unlang_interpret_mark_runnable(entry->request);
251 }
252 }
253
254done:
255 if ((fd >= 0) && (exfile_close(inst->file.ef, fd) < 0)) {
256 PERROR("Failed closing file %s", file->filename);
257 }
258
260 file->entry_p = file->entry;
261
262 if (fr_time_delta_gt(inst->file.buffer_expiry, fr_time_delta_wrap(0))) {
263 (void) fr_timer_in(file, file->thread_inst->tl, &file->expiry, inst->file.buffer_expiry,
265 }
266}
267
269{
271}
272
274 linelog_call_env_t const *call_env, request_t *request,
275 struct iovec *vector_p, size_t vector_len)
276{
277 ssize_t ret;
278 char const *path;
280 rlm_linelog_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_linelog_thread_t);
282
283 fr_assert(inst->file.buffer_write);
284
285 path = call_env->filename->vb_strvalue;
286
288 &(rlm_linelog_file_t){ .filename = path });
289
290 if (!file) {
291 MEM(file = talloc_size(mctx->thread, sizeof(rlm_linelog_file_t) + (sizeof(rlm_linelog_file_entry_t) * inst->file.buffer_count)));
292 talloc_set_name_const(file, "rlm_linelog_file_t");
293
294 file->filename = talloc_strdup(file, path);
295 MEM(file->log_header = fr_value_box_alloc_null(file));
296 file->mod_inst = inst;
297 file->thread_inst = thread;
298
299 if (call_env->log_head && fr_value_box_copy(file, file->log_header, call_env->log_head) < 0) {
300 RPERROR("Failed to copy log header for buffered log file %pV", call_env->filename);
301 error:
304 }
305
306 file->entry_p = file->entry;
307 file->entry_last = file->entry + inst->file.buffer_count;
308 file->write = NULL;
309 file->expiry = NULL;
310 fr_dbuff_init_talloc(file, &file->dbuff, &file->tctx, 1024, SIZE_MAX);
311
312 if (fr_hash_table_insert(thread->file_table, file) != 0) {
313 RPERROR("Failed tracking buffered log file %pV", call_env->filename);
314 goto error;
315 }
316
317 talloc_set_destructor(file, _file_free);
318 }
319
320 if (file->expiry) {
321 FR_TIMER_DISARM(file->expiry);
322 }
323
324 fr_assert(file->entry_p < file->entry_last);
325 *file->entry_p = (rlm_linelog_file_entry_t) {
326 .request = request,
327 .file = file,
328 .failed = false,
329 .data_len = 0,
330 .error = 0
331 };
332
333 ret = fr_concatv(&file->dbuff, vector_p, vector_len);
334 if (ret < 0) {
335 RERROR("Failed to buffer log entry for %pV", call_env->filename);
336 file->entry_p->failed = true;
338 }
339
340 *entry_p = file->entry_p;
341
342 if (fr_time_delta_gt(inst->file.buffer_delay, fr_time_delta_wrap(0)) && !fr_timer_armed(file->write) ) {
343 if (unlikely(fr_timer_in(file, file->thread_inst->tl, &file->write, inst->file.buffer_delay,
344 false, _batching_handle_timeout, file)) < 0) {
345 RWARN("Failed adding timer to write logs for %pV", call_env->filename);
346 }
347 }
348
349 file->entry_p->data_len = ret;
350 file->entry_p++;
351
352 if (file->entry_p == file->entry_last) {
354
356 }
357
359}
360
362{
364 thread->tl = tl;
365}
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:39
@ UNLANG_ACTION_FAIL
Encountered an unexpected error.
Definition action.h:36
int const char * file
Definition acutest.h:855
static uint8_t request[4]
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:203
#define RCSID(id)
Definition build.h:603
#define NDEBUG_UNUSED
Definition build.h:426
#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:130
#define unlikely(_x)
Definition build.h:486
#define UNUSED
Definition build.h:415
fr_cmp_ret_t
Result of an ordering comparison.
Definition build.h:113
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:819
#define fr_dbuff_start(_dbuff_or_marker)
Return the 'start' position of a dbuff or marker.
Definition dbuff.h:950
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:453
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:38
#define ERROR(fmt,...)
Definition dhcpclient.c:40
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:547
int exfile_close(exfile_t *ef, int fd)
Close the log file.
Definition exfile.c:624
int 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:635
uint32_t fr_hash_string(char const *p)
Definition hash.c:919
int fr_hash_table_find_by_key(void **found, fr_hash_table_t *ht, uint32_t key, void const *data)
Hash table lookup with pre-computed key.
Definition hash.c:479
int fr_hash_table_insert(fr_hash_table_t *ht, void const *data)
Insert data into a hash table.
Definition hash.c:501
#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:2022
bool unlang_interpret_is_resumable(request_t *request)
Check if a request as resumable.
Definition interpret.c:2004
ssize_t fr_writev(int fd, struct iovec vector[], size_t iovcnt, fr_time_delta_t timeout)
Write out a vector to a file descriptor.
Definition iovec.c:68
ssize_t fr_concatv(fr_dbuff_t *out, struct iovec vector[], size_t iovcnt)
Concatenate an iovec into a dbuff.
Definition iovec.c:38
#define PERROR(_fmt,...)
Definition log.h:233
#define ROPTIONAL(_l_request, _l_global, _fmt,...)
Use different logging functions depending on whether request is NULL or not.
Definition log.h:545
#define RWARN(fmt,...)
Definition log.h:314
#define RERROR(fmt,...)
Definition log.h:315
#define RPERROR(fmt,...)
Definition log.h:319
@ FR_TYPE_SIZE
Unsigned integer capable of representing any memory address on the local system.
unsigned int uint32_t
long int ssize_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:273
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:361
static fr_cmp_ret_t filename_cmp(void const *one, void const *two)
Definition file.c:38
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 void _batching_handle_timeout(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
Definition file.c:268
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
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
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
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:297
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:117
#define talloc_strdup(_ctx, _str)
Definition talloc.h:149
static fr_time_delta_t fr_time_delta_from_sec(int64_t sec)
Definition time.h:599
#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:124
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:4526
#define fr_value_box_alloc(_ctx, _type, _enumv)
Allocate a value box of a specific type.
Definition value.h:668
static fr_sbuff_err_t char size_t fr_sbuff_t * in
Definition value.h:1061
static fr_slen_t data
Definition value.h:1366
static fr_sbuff_err_t char ** out
Definition value.h:1061
static fr_sbuff_err_t char size_t * len
Definition value.h:1061
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:679
void * rctx
Resume context.
Definition xlat_ctx.h:54
An xlat calling ctx.
Definition xlat_ctx.h:49