The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
interpret_synchronous.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: d12359e471a84ab2e309616282dc79df6b212db2 $
19  *
20  * @file unlang/interpret_synchronous.c
21  * @brief Synchronous interpreter
22  *
23  * @copyright 2021 The FreeRADIUS server project
24  */
25 
26 #include "interpret_priv.h"
27 #include <freeradius-devel/server/module_rlm.h>
28 
29 typedef struct {
32  int yielded;
35 
36 /** Internal request (i.e. one generated by the interpreter) is now complete
37  *
38  */
39 static void _request_init_internal(request_t *request, void *uctx)
40 {
41  unlang_interpret_synchronous_t *intps = uctx;
42 
43  RDEBUG3("Initialising internal synchronous request");
44  unlang_interpret_set(request, intps->intp);
45  fr_heap_insert(&intps->runnable, request);
46 }
47 
48 /** External request is now complete
49  *
50  */
51 static void _request_done_external(request_t *request, UNUSED rlm_rcode_t rcode, UNUSED void *uctx)
52 {
53  if (request->master_state != REQUEST_STOP_PROCESSING) {
54  /*
55  * If we're running a real request, then the final
56  * indentation MUST be zero. Otherwise we skipped
57  * something!
58  *
59  * Also check that the request is NOT marked as
60  * "yielded", but is in fact done.
61  *
62  * @todo - check that the stack is at frame 0, otherwise
63  * more things have gone wrong.
64  */
65  fr_assert_msg(request->parent || (request->log.indent.unlang == 0),
66  "Request %s bad log indentation - expected 0 got %u", request->name, request->log.indent.unlang);
68  "Request %s is marked as yielded at end of processing", request->name);
69  }
70 
71  RDEBUG3("Synchronous done external request");
72 }
73 
74 /** Internal request (i.e. one generated by the interpreter) is now complete
75  *
76  */
77 static void _request_done_internal(request_t *request, UNUSED rlm_rcode_t rcode, UNUSED void *uctx)
78 {
79  RDEBUG3("Done synchronous internal request");
80 
81  /* Request will be cleaned up by whatever created it */
82 }
83 
84 static void _request_done_detached(request_t *request, UNUSED rlm_rcode_t rcode, UNUSED void *uctx)
85 {
86  RDEBUG3("Done synchronous detached request");
87 
88  /*
89  * Detached requests have to be freed by us
90  * as nothing else can free them.
91  *
92  * All other requests must be freed by the
93  * code which allocated them.
94  */
95  talloc_free(request);
96 }
97 
98 /** We don't need to do anything for internal -> detached
99  *
100  */
101 static void _request_detach(request_t *request, UNUSED void *uctx)
102 {
103  RDEBUG3("Synchronous request detached");
104 
105  if (request_detach(request) < 0) RPEDEBUG("Failed detaching request");
106 }
107 
108 /** Request has been stopped
109  *
110  * Clean up execution state
111  */
112 static void _request_stop(request_t *request, void *uctx)
113 {
114  unlang_interpret_synchronous_t *intps = uctx;
115 
116  RDEBUG3("Synchronous request stopped");
117 
118  /*
119  * The assumption here is that if the request
120  * not in the runnable queue, and it's not
121  * currently running, then it must be yielded.
122  */
123  if (fr_heap_extract(&intps->runnable, request) < 0) intps->yielded--;
124 }
125 
126 /** Request is now runnable
127  *
128  */
129 static void _request_runnable(request_t *request, void *uctx)
130 {
131  unlang_interpret_synchronous_t *intps = uctx;
132 
133  fr_assert(intps->yielded > 0);
134  intps->yielded--;
135 
136  fr_heap_insert(&intps->runnable, request);
137 }
138 
139 /** Interpreter yielded request
140  *
141  */
142 static void _request_yield(request_t *request, void *uctx)
143 {
144  unlang_interpret_synchronous_t *intps = uctx;
145 
146  intps->yielded++;
147 
148  RDEBUG3("synchronous request yielded");
149 }
150 
151 /** Interpreter is starting to work on request again
152  *
153  */
154 static void _request_resume(request_t *request, UNUSED void *uctx)
155 {
156  RDEBUG3("synchronous request resumed");
157 }
158 
159 static bool _request_scheduled(request_t const *request, UNUSED void *uctx)
160 {
161  return fr_heap_entry_inserted(request->runnable_id);
162 }
163 
164 /** Allocate a new temporary interpreter
165  *
166  */
168 {
170 
171  MEM(intps = talloc_zero(ctx, unlang_interpret_synchronous_t));
172  MEM(intps->runnable = fr_heap_talloc_alloc(intps, fr_pointer_cmp, request_t, runnable_id, 0));
173  if (el) {
174  intps->el = el;
175  } else {
176  MEM(intps->el = fr_event_list_alloc(intps, NULL, NULL));
177  }
178  MEM(intps->intp = unlang_interpret_init(intps, intps->el,
180  .init_internal = _request_init_internal,
181 
182  .done_external = _request_done_external,
183  .done_internal = _request_done_internal,
184  .done_detached = _request_done_detached,
185 
186  .detach = _request_detach,
187  .stop = _request_stop,
188  .yield = _request_yield,
189  .resume = _request_resume,
190  .mark_runnable = _request_runnable,
191  .scheduled = _request_scheduled
192  },
193  intps));
194 
195  return intps;
196 }
197 
198 /** Execute an unlang section synchronously
199  *
200  * Create a temporary event loop and swap it out for the one in the request.
201  * Execute unlang operations until we receive a non-yield return code then return.
202  *
203  * @note The use cases for this are very limited. If you need to use it, chances
204  * are what you're doing could be done better using one of the thread
205  * event loops.
206  *
207  * @param[in] el Event list for the temporary interpreter. If NULL a
208  * temporary list will be allocated.
209  * @param[in] request The current request.
210  * @return One of the RLM_MODULE_* macros.
211  */
213 {
214  unlang_interpret_t *old_intp;
215  char const *caller;
216 
218 
219  rlm_rcode_t rcode;
220 
221  request_t *sub_request = NULL;
222  bool dont_wait_for_event;
223  int iterations = 0;
224 
225  old_intp = unlang_interpret_get(request);
226  caller = request->module;
227 
228  intps = unlang_interpret_synchronous_alloc(request, el);
229  unlang_interpret_set(request, intps->intp);
230 
231  el = intps->el;
232 
233  rcode = unlang_interpret(request);
234 
235  while ((dont_wait_for_event = (fr_heap_num_elements(intps->runnable) > 0)) ||
236  (intps->yielded > 0)) {
237  rlm_rcode_t sub_rcode;
238  int num_events;
239 
240  /*
241  * Wait for a timer / IO event. If there's a
242  * failure, all kinds of bad things happen. Oh
243  * well.
244  */
245  DEBUG3("Gathering events - %s", dont_wait_for_event ? "Will not wait" : "will wait");
246  num_events = fr_event_corral(el, fr_time(), !dont_wait_for_event);
247  if (num_events < 0) {
248  RPERROR("fr_event_corral");
249  break;
250  }
251 
252  DEBUG3("%u event(s) pending%s",
253  num_events == -1 ? 0 : num_events, num_events == -1 ? " - event loop exiting" : "");
254 
255  /*
256  * This function ends up pushing a
257  * runnable request into the backlog, OR
258  * setting new timers.
259  */
260  if (num_events > 0) {
261  DEBUG4("Servicing event(s)");
263  }
264 
265  /*
266  * If there are no runnable requests, then go
267  * back to check the timers again. Note that we
268  * only wait if there are timer events left to
269  * service.
270  *
271  * If there WAS a timer event, but servicing that
272  * timer event did not result in a runnable
273  * request, THEN we're guaranteed that there is
274  * still a timer event left.
275  */
276  sub_request = fr_heap_pop(&intps->runnable);
277  if (!sub_request) {
278  DEBUG3("No pending requests (%u yielded)", intps->yielded);
279  continue;
280  }
281 
282  /*
283  * Continue interpretation until there's nothing
284  * in the backlog. If this request YIELDs, then
285  * do another loop around.
286  */
287  RDEBUG4(">>> interpreter (iteration %i)", ++iterations);
288  sub_rcode = unlang_interpret(sub_request);
289  RDEBUG4("<<< interpreter (iteration %i) - %s", iterations,
290  fr_table_str_by_value(rcode_table, sub_rcode, "<INVALID>"));
291 
292  /*
293  * If the request being run was the original request
294  * then update the rcode we're returning.
295  */
296  if (sub_request == request) rcode = sub_rcode;
297 
298  DEBUG3("%u runnable, %u yielded", fr_heap_num_elements(intps->runnable), intps->yielded);
299  }
300 
301  talloc_free(intps);
302  unlang_interpret_set(request, old_intp);
303  request->module = caller;
304 
305  return rcode;
306 }
#define UNUSED
Definition: build.h:313
#define fr_assert_msg(_x, _msg,...)
Calls panic_action ifndef NDEBUG, else logs error and causes the server to exit immediately with code...
Definition: debug.h:208
void * fr_heap_pop(fr_heap_t **hp)
Remove a node from the heap.
Definition: heap.c:322
int fr_heap_insert(fr_heap_t **hp, void *data)
Insert a new element into the heap.
Definition: heap.c:146
int fr_heap_extract(fr_heap_t **hp, void *data)
Remove a node from the heap.
Definition: heap.c:239
static bool fr_heap_entry_inserted(fr_heap_index_t heap_idx)
Check if an entry is inserted into a heap.
Definition: heap.h:124
static unsigned int fr_heap_num_elements(fr_heap_t *h)
Return the number of elements in the heap.
Definition: heap.h:179
#define fr_heap_talloc_alloc(_ctx, _cmp, _talloc_type, _field, _init)
Creates a heap that verifies elements are of a specific talloc type.
Definition: heap.h:115
The main heap structure.
Definition: heap.h:66
rlm_rcode_t unlang_interpret(request_t *request)
Run the interpreter for a current request.
Definition: interpret.c:760
unlang_interpret_t * unlang_interpret_get(request_t *request)
Get the interpreter set for a request.
Definition: interpret.c:1735
void unlang_interpret_set(request_t *request, unlang_interpret_t *intp)
Set a specific interpreter for a request.
Definition: interpret.c:1726
unlang_interpret_t * unlang_interpret_init(TALLOC_CTX *ctx, fr_event_list_t *el, unlang_request_func_t *funcs, void *uctx)
Initialize a unlang compiler / interpret.
Definition: interpret.c:1684
bool unlang_interpret_is_resumable(request_t *request)
Check if a request as resumable.
Definition: interpret.c:1322
External functions provided by the owner of the interpret.
Definition: interpret.h:100
Private declarations for the unlang interpreter.
static void _request_yield(request_t *request, void *uctx)
Interpreter yielded request.
static void _request_done_external(request_t *request, UNUSED rlm_rcode_t rcode, UNUSED void *uctx)
External request is now complete.
static void _request_resume(request_t *request, UNUSED void *uctx)
Interpreter is starting to work on request again.
static void _request_runnable(request_t *request, void *uctx)
Request is now runnable.
static void _request_done_internal(request_t *request, UNUSED rlm_rcode_t rcode, UNUSED void *uctx)
Internal request (i.e.
static bool _request_scheduled(request_t const *request, UNUSED void *uctx)
static void _request_detach(request_t *request, UNUSED void *uctx)
We don't need to do anything for internal -> detached.
static void _request_stop(request_t *request, void *uctx)
Request has been stopped.
static unlang_interpret_synchronous_t * unlang_interpret_synchronous_alloc(TALLOC_CTX *ctx, fr_event_list_t *el)
Allocate a new temporary interpreter.
rlm_rcode_t unlang_interpret_synchronous(fr_event_list_t *el, request_t *request)
Execute an unlang section synchronously.
static void _request_done_detached(request_t *request, UNUSED rlm_rcode_t rcode, UNUSED void *uctx)
static void _request_init_internal(request_t *request, void *uctx)
Internal request (i.e.
#define DEBUG3(_fmt,...)
Definition: log.h:266
#define RDEBUG3(fmt,...)
Definition: log.h:343
#define DEBUG4(_fmt,...)
Definition: log.h:267
#define RPERROR(fmt,...)
Definition: log.h:302
#define RPEDEBUG(fmt,...)
Definition: log.h:376
#define RDEBUG4(fmt,...)
Definition: log.h:344
void fr_event_service(fr_event_list_t *el)
Service any outstanding timer or file descriptor events.
Definition: event.c:2542
fr_event_list_t * fr_event_list_alloc(TALLOC_CTX *ctx, fr_event_status_cb_t status, void *status_uctx)
Initialise a new event list.
Definition: event.c:2892
int fr_event_corral(fr_event_list_t *el, fr_time_t now, bool wait)
Gather outstanding timer and file descriptor events.
Definition: event.c:2407
talloc_free(reap)
Stores all information relating to an event list.
Definition: event.c:411
int8_t fr_pointer_cmp(void const *a, void const *b)
Compares two pointers.
Definition: misc.c:408
fr_table_num_sorted_t const rcode_table[]
Definition: rcode.c:35
rlm_rcode_t
Return codes indicating the result of the module call.
Definition: rcode.h:40
int request_detach(request_t *child)
Unlink a subrequest from its parent.
Definition: request.c:664
@ REQUEST_STOP_PROCESSING
Definition: request.h:62
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=0)
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition: state_test.c:8
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition: table.h:253
static fr_event_list_t * el