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: b0fb41a3602d804f61750a0b09fa1b38a6716d15 $
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: b0fb41a3602d804f61750a0b09fa1b38a6716d15 $")
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
120 }
121 }
122}
123
125{
126 int ret, written, fd = -1;
127 bool with_delim;
128 size_t len = 0, header_len = 0;
129 off_t offset, file_size;
130 int write_error = 0;
131 struct stat stat_buf;
132 struct iovec to_write[3];
133 struct iovec *to_write_p = to_write;
135
136 fr_assert(inst->file.buffer_write);
138 fr_assert(file->filename);
139
140 if (file->write) {
141 FR_TIMER_DISARM(file->write);
142 }
143
144 with_delim = (inst->delimiter_len > 0);
145
146 fd = exfile_open(inst->file.ef, file->filename, inst->file.permissions, 0, &offset);
147 if (fd < 0) {
148 PERROR("Failed to open %s", file->filename);
149 error:
150 batching_mark_entries_failed(file, file->entry, errno);
151 goto done;
152 }
153
154 if (inst->file.group_str && (chown(file->filename, -1, inst->file.group) == -1)) {
155 ERROR("Unable to change system group of \"%s\" - %s", file->filename, fr_syserror(errno));
156 goto error;
157 }
158
159 /*
160 * If a header format is defined and we are at the beginning
161 * of the file then expand the format and write it out before
162 * writing the actual log entries.
163 */
164 if (!fr_type_is_null(file->log_header->type) && (offset == 0)) {
165 to_write_p->iov_base = UNCONST(char *, file->log_header->vb_strvalue);
166 header_len = to_write_p->iov_len = file->log_header->vb_length;
167 to_write_p++;
168
169 if (with_delim) {
170 to_write_p->iov_base = UNCONST(char *, inst->delimiter);
171 header_len += to_write_p->iov_len = inst->delimiter_len;
172 to_write_p++;
173 }
174 }
175
176 to_write_p->iov_base = fr_dbuff_start(&file->dbuff);
177 to_write_p->iov_len = fr_dbuff_used(&file->dbuff);
178 to_write_p++;
179
180 if (fstat(fd, &stat_buf) < 0) {
181 ERROR("Failed to stat file %s - %s", file->filename, fr_syserror(errno));
182 goto error;
183 }
184 file_size = stat_buf.st_size;
185
186 errno = 0;
187 ret = fr_writev(fd, to_write, to_write_p - to_write, fr_time_delta_from_sec(0));
188 written = ret - header_len;
189 write_error = errno;
190 if (ret >= 0) {
191 if (ret < (ssize_t)(header_len)) {
192 ret = ftruncate(fd, 0);
193 if (ret < 0) {
194 ERROR("Failed truncating file \"%s\" after partial header write - %s",
195 file->filename, fr_syserror(errno));
196 }
197
198 PERROR("Failed writing header to \"%s\"", file->filename);
199 goto error;
200 }
201 } else {
202 /*
203 * Check for partial write
204 */
205 if (fstat(fd, &stat_buf) < 0) {
206 ERROR("Failed to stat file %s - %s", file->filename, fr_syserror(errno));
207 goto error;
208 }
209
210 written = stat_buf.st_size - file_size - header_len;
211
212 if ((errno == ENOSPC) && (written > 0)) {
213 ERROR("No space left on device when writing to \"%s\". Not all data was written",
214 file->filename);
215 } else {
216 ERROR("Failed writing to \"%s\" - %s", file->filename, fr_syserror(errno));
217 goto error;
218 }
219 }
220
221 if (fsync(fd) < 0) {
222 ERROR("Failed syncing \"%s\" to persistent storage - %s", file->filename, fr_syserror(errno));
223 goto error;
224 }
225
226 for (rlm_linelog_file_entry_t *entry = file->entry; entry < file->entry_p; entry++) {
227 len += entry->data_len;
228
229 if (len > (size_t)written) {
230 request_t *request = entry->request;
231 ROPTIONAL(RWARN, WARN, "Buffered log write failed. Expected %zu bytes, but only %zu bytes were written", len, (size_t)written);
232 ret = ftruncate(fd, offset + (len - entry->data_len + header_len));
233 if (ret < 0) {
234 ERROR("Failed truncating file \"%s\" after partial write - %s",
235 file->filename, fr_syserror(errno));
236 }
237
238 batching_mark_entries_failed(file, entry, write_error);
239 break;
240 }
241
242 /*
243 * Needed because the write function could be called either from a timer, or directly
244 * when the buffer is full. In which case the last entry would not have been yielded
245 */
246 if (unlang_interpret_is_resumable(entry->request)) {
247 unlang_interpret_mark_runnable(entry->request);
248 }
249 }
250
251done:
252 if ((fd >= 0) && (exfile_close(inst->file.ef, fd) < 0)) {
253 PERROR("Failed closing file %s", file->filename);
254 }
255
257 file->entry_p = file->entry;
258
259 if (fr_time_delta_gt(inst->file.buffer_expiry, fr_time_delta_wrap(0))) {
260 (void) fr_timer_in(file, file->thread_inst->tl, &file->expiry, inst->file.buffer_expiry,
262 }
263}
264
266{
268}
269
271 linelog_call_env_t const *call_env, request_t *request,
272 struct iovec *vector_p, size_t vector_len)
273{
274 int ret;
275 char const *path;
277 rlm_linelog_thread_t *thread = talloc_get_type_abort(mctx->thread, rlm_linelog_thread_t);
279
280 fr_assert(inst->file.buffer_write);
281
282 path = call_env->filename->vb_strvalue;
283
285 &(rlm_linelog_file_t){ .filename = path });
286
287 if (!file) {
288 MEM(file = talloc_size(mctx->thread, sizeof(rlm_linelog_file_t) + (sizeof(rlm_linelog_file_entry_t) * inst->file.buffer_count)));
289 talloc_set_name_const(file, "rlm_linelog_file_t");
290
291 file->filename = talloc_strdup(file, path);
292 MEM(file->log_header = fr_value_box_alloc_null(file));
293 file->mod_inst = inst;
294 file->thread_inst = thread;
295
296 if (call_env->log_head && fr_value_box_copy(file, file->log_header, call_env->log_head) < 0) {
297 RPERROR("Failed to copy log header for buffered log file %pV", call_env->filename);
298 error:
301 }
302
303 file->entry_p = file->entry;
304 file->entry_last = file->entry + inst->file.buffer_count;
305 file->write = NULL;
306 file->expiry = NULL;
307 fr_dbuff_init_talloc(file, &file->dbuff, &file->tctx, 1024, SIZE_MAX);
308
309 if (!fr_hash_table_insert(thread->file_table, file)) {
310 RPERROR("Failed tracking buffered log file %pV", call_env->filename);
311 goto error;
312 }
313
314 talloc_set_destructor(file, _file_free);
315 }
316
317 if (file->expiry) {
318 FR_TIMER_DISARM(file->expiry);
319 }
320
321 fr_assert(file->entry_p < file->entry_last);
322 *file->entry_p = (rlm_linelog_file_entry_t) {
323 .request = request,
324 .file = file,
325 .failed = false,
326 .data_len = 0,
327 .error = 0
328 };
329
330 ret = fr_concatv(&file->dbuff, vector_p, vector_len);
331 if (ret < 0) {
332 RERROR("Failed to buffer log entry for %pV", call_env->filename);
333 file->entry_p->failed = true;
335 }
336
337 *entry_p = file->entry_p;
338
339 if (fr_time_delta_gt(inst->file.buffer_delay, fr_time_delta_wrap(0)) && !fr_timer_armed(file->write) ) {
340 if (unlikely(fr_timer_in(file, file->thread_inst->tl, &file->write, inst->file.buffer_delay,
341 false, _batching_handle_timeout, file)) < 0) {
342 RWARN("Failed adding timer to write logs for %pV", call_env->filename);
343 }
344 }
345
346 file->entry_p->data_len = ret;
347 file->entry_p++;
348
349 if (file->entry_p == file->entry_last) {
351
353 }
354
356}
357
359{
361 thread->tl = tl;
362}
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:704
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:167
#define RCSID(id)
Definition build.h:487
#define NDEBUG_UNUSED
Definition build.h:328
#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:112
#define unlikely(_x)
Definition build.h:383
#define UNUSED
Definition build.h:317
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:777
#define fr_dbuff_start(_dbuff_or_marker)
Return the 'start' position of a dbuff or marker.
Definition dbuff.h:908
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:421
static int fr_dcursor_insert(fr_dcursor_t *cursor, void *v)
Insert directly after the current item.
Definition dcursor.h:437
#define MEM(x)
Definition debug.h:36
#define ERROR(fmt,...)
Definition dhcpclient.c:41
static fr_slen_t in
Definition dict.h:884
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:533
int exfile_close(exfile_t *ef, int fd)
Close the log file.
Definition exfile.c:600
uint32_t fr_hash_case_string(char const *p)
Hash a C string, converting all chars to lowercase.
Definition hash.c:885
bool fr_hash_table_insert(fr_hash_table_t *ht, void const *data)
Insert data into a hash table.
Definition hash.c:469
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:449
uint32_t fr_hash_string(char const *p)
Definition hash.c:869
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:595
#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:1639
bool unlang_interpret_is_resumable(request_t *request)
Check if a request as resumable.
Definition interpret.c:1621
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:270
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:358
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:265
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:124
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:38
#define REDEBUG(fmt,...)
#define WARN(fmt,...)
static bool done
Definition radclient.c:83
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:291
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:245
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:50
#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:348
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:4409
#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:1334
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:1024
void * rctx
Resume context.
Definition xlat_ctx.h:54
An xlat calling ctx.
Definition xlat_ctx.h:49