/src/freeradius-server/src/lib/unlang/child_request.c
Line | Count | Source |
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: 47dbf6292397779aaf1d60d67379d095bc5b2eac $ |
19 | | * |
20 | | * @file unlang/child_request.c |
21 | | * @brief Common child request management code. |
22 | | * |
23 | | * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
24 | | */ |
25 | | #include <freeradius-devel/server/state.h> |
26 | | #include <freeradius-devel/unlang/mod_action.h> |
27 | | |
28 | | #include "unlang_priv.h" |
29 | | #include "child_request_priv.h" |
30 | | |
31 | | typedef struct { |
32 | | unlang_child_request_t *cr; //!< A pointer to memory in the parent's frame state |
33 | | ///< allocated for this child to write results to. |
34 | | } unlang_frame_state_child_request_t; |
35 | | |
36 | | fr_table_num_ordered_t const unlang_child_states_table[] = { |
37 | | { L("CANCELLED"), CHILD_CANCELLED }, |
38 | | { L("DETACH"), CHILD_DETACHED }, |
39 | | { L("DONE"), CHILD_DONE }, |
40 | | { L("EXITED"), CHILD_EXITED }, |
41 | | { L("INIT"), CHILD_INIT }, |
42 | | { L("RUNNABLE"), CHILD_RUNNABLE } |
43 | | }; |
44 | | size_t unlang_child_states_table_len = NUM_ELEMENTS(unlang_child_states_table); |
45 | | |
46 | | /** Process a detach signal in the child |
47 | | * |
48 | | * This processes any detach signals the child receives |
49 | | * The child doesn't actually do the detaching |
50 | | */ |
51 | | static void unlang_child_request_signal(request_t *request, UNUSED unlang_stack_frame_t *frame, fr_signal_t action) |
52 | 0 | { |
53 | 0 | unlang_frame_state_child_request_t *state; |
54 | 0 | unlang_child_request_t *cr; |
55 | | |
56 | | /* |
57 | | * We're already detached so we don't |
58 | | * need to notify the parent we're |
59 | | * waking up, and we don't need to detach |
60 | | * again... |
61 | | */ |
62 | 0 | if (request_is_detached(request)) return; |
63 | | |
64 | 0 | state = talloc_get_type_abort(frame->state, unlang_frame_state_child_request_t); |
65 | 0 | cr = state->cr; /* Can't use talloc_get_type_abort, may be an array element */ |
66 | | |
67 | | /* |
68 | | * Ignore signals which aren't detached, and |
69 | | * are ignore the signal if we have no parent. |
70 | | */ |
71 | 0 | switch (action) { |
72 | 0 | case FR_SIGNAL_DETACH: |
73 | | /* |
74 | | * Place child's state back inside the parent |
75 | | */ |
76 | 0 | if (cr->config.session_unique_ptr) fr_state_store_in_parent(request, |
77 | 0 | cr->config.session_unique_ptr, |
78 | 0 | cr->num); |
79 | |
|
80 | 0 | RDEBUG3("Detached - Removing subrequest from parent, and marking parent as runnable"); |
81 | | |
82 | | /* |
83 | | * Indicate to the parent there's no longer a child |
84 | | */ |
85 | 0 | cr->state = CHILD_DETACHED; |
86 | | |
87 | | /* |
88 | | * Don't run the request-done frame, the request will have been |
89 | | * detached by whatever signalled us, so we can't inform the parent |
90 | | * when we exit. |
91 | | */ |
92 | 0 | repeatable_clear(frame); |
93 | |
|
94 | 0 | frame->p_result = &frame->scratch_result; |
95 | | |
96 | | /* |
97 | | * Tell the parent to resume if all the request's siblings are done |
98 | | */ |
99 | 0 | if (!cr->sibling_count || (--(*cr->sibling_count) == 0)) unlang_interpret_mark_runnable(request->parent); |
100 | 0 | break; |
101 | | |
102 | | /* |
103 | | * This frame is not cancellable, so FR_SIGNAL_CANCEL |
104 | | * does nothing. If the child is cancelled in its |
105 | | * entirety, then its stack will unwind up to this point |
106 | | * and unlang_subrequest_child_done will mark the |
107 | | * parent as runnable. We don't need to do anything here. |
108 | | * |
109 | | * We set the state for debugging purposes, and to reduce |
110 | | * the amount of work we do in unlang_subrequest_child_done. |
111 | | */ |
112 | 0 | case FR_SIGNAL_CANCEL: |
113 | 0 | cr->state = CHILD_CANCELLED; |
114 | 0 | break; |
115 | | |
116 | 0 | default: |
117 | 0 | return; |
118 | 0 | } |
119 | 0 | } |
120 | | |
121 | | /** When the child is done, tell the parent that we've exited. |
122 | | * |
123 | | * This is pushed as a frame at the top of the child's stack, so when |
124 | | * the child is done executing, it runs this to inform the parent |
125 | | * that its done. |
126 | | */ |
127 | | static unlang_action_t unlang_child_request_done(UNUSED unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame) |
128 | 0 | { |
129 | 0 | unlang_frame_state_child_request_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_child_request_t); |
130 | 0 | unlang_child_request_t *cr = state->cr; /* Can't use talloc_get_type_abort, may be an array element */ |
131 | | |
132 | | /* |
133 | | * The repeat function for this frame should've |
134 | | * been cleared, so this function should not run |
135 | | * for detached requests. |
136 | | */ |
137 | 0 | fr_assert(!request_is_detached(request)); |
138 | |
|
139 | 0 | switch (cr->state) { |
140 | 0 | case CHILD_RUNNABLE: |
141 | | /* |
142 | | * Place child state back inside the parent |
143 | | */ |
144 | 0 | if (cr->config.session_unique_ptr) { |
145 | 0 | fr_state_store_in_parent(request, |
146 | 0 | cr->config.session_unique_ptr, |
147 | 0 | cr->num); |
148 | 0 | } |
149 | 0 | if (cr->p_result) *cr->p_result = cr->result; |
150 | 0 | break; |
151 | | |
152 | 0 | case CHILD_CANCELLED: |
153 | | /* |
154 | | * Child session state is no longer consistent |
155 | | * after cancellation, so discard it. |
156 | | */ |
157 | 0 | if (cr->config.session_unique_ptr) { |
158 | 0 | fr_state_discard_child(request->parent, cr->config.session_unique_ptr, cr->num); |
159 | 0 | } |
160 | 0 | break; |
161 | | |
162 | 0 | default: |
163 | 0 | fr_assert_msg(0, "child %s resumed top frame with invalid state %s", |
164 | 0 | request->name, |
165 | 0 | fr_table_str_by_value(unlang_child_states_table, cr->state, "<INVALID>")); |
166 | 0 | } |
167 | | |
168 | 0 | cr->state = CHILD_EXITED; |
169 | | |
170 | | /* |
171 | | * Tell the parent to resume if all the request's siblings are done |
172 | | */ |
173 | 0 | if (!cr->sibling_count || (--(*cr->sibling_count) == 0)) { |
174 | 0 | RDEBUG3("All children have exited, marking parent %s as runnable", request->parent->name); |
175 | 0 | unlang_interpret_mark_runnable(request->parent); |
176 | 0 | } else { |
177 | 0 | RDEBUG3("Child %s exited, %u sibling(s) remaining", |
178 | 0 | request->name, |
179 | 0 | *cr->sibling_count); |
180 | 0 | } |
181 | |
|
182 | 0 | return UNLANG_ACTION_CALCULATE_RESULT; |
183 | 0 | } |
184 | | |
185 | | /** Push a resumption frame onto a child's stack |
186 | | * |
187 | | * Push a frame onto the stack of the child to inform the parent when it's complete. |
188 | | * An additional frame is pushed onto the child's stack by the 'run' function which |
189 | | * executes in the context of the parent. |
190 | | * |
191 | | * @param[in] cr state for this child request. This is a pointer |
192 | | * to a structure in the parent's frame state. |
193 | | * @return |
194 | | * - 0 on success. |
195 | | * - -1 on failure. |
196 | | */ |
197 | | static int unlang_child_request_stack_init(unlang_child_request_t *cr) |
198 | 0 | { |
199 | 0 | request_t *child = cr->request; |
200 | 0 | unlang_frame_state_child_request_t *state; |
201 | 0 | unlang_stack_frame_t *frame; |
202 | |
|
203 | 0 | static unlang_t inform_parent = { |
204 | 0 | .type = UNLANG_TYPE_CHILD_REQUEST, |
205 | 0 | .name = "child-request", |
206 | 0 | .debug_name = "child-request-resume", |
207 | 0 | .actions = DEFAULT_MOD_ACTIONS, |
208 | 0 | }; |
209 | | |
210 | | /* Sets up the frame for us to use immediately */ |
211 | 0 | if (unlikely(unlang_interpret_push_instruction(&cr->result, child, &inform_parent, |
212 | 0 | FRAME_CONF(RLM_MODULE_NOOP, true)) < 0)) { |
213 | 0 | return -1; |
214 | 0 | } |
215 | | |
216 | 0 | frame = frame_current(child); |
217 | 0 | state = frame->state; |
218 | 0 | state->cr = cr; |
219 | 0 | repeatable_set(frame); /* Run this on the way back up */ |
220 | |
|
221 | 0 | return 0; |
222 | 0 | } |
223 | | |
224 | | /** Initialize a child request |
225 | | * |
226 | | * This initializes the child request result and configuration structure, |
227 | | * and pushes a resumption frame onto the child's stack. |
228 | | * |
229 | | * @param[in] ctx Memory to use for any additional memory allocated |
230 | | * to the unlang_child_request_t. |
231 | | * @param[out] out Child request to initialize. |
232 | | * @param[in] child The child request to initialize. |
233 | | * @param[in] p_result Where to write out the rcode from the child. |
234 | | * @param[in,out] sibling_count If non-null the number of siblings. This is incremented |
235 | | * for each child created. |
236 | | * @param[in] unique_session_ptr Unique session pointer for this child. |
237 | | * If NULL session data won't be stored/restored for the child. |
238 | | * @param[in] free_child Free the child when done? |
239 | | * @return |
240 | | * - 0 on success. |
241 | | * - -1 on failure. |
242 | | */ |
243 | | int unlang_child_request_init(TALLOC_CTX *ctx, unlang_child_request_t *out, request_t *child, |
244 | | unlang_result_t *p_result, unsigned int *sibling_count, void const *unique_session_ptr, bool free_child) |
245 | 0 | { |
246 | 0 | *out = (unlang_child_request_t){ |
247 | 0 | .name = talloc_bstrdup(ctx, child->name), |
248 | 0 | .num = sibling_count ? (*sibling_count)++ : 0, |
249 | 0 | .request = child, |
250 | 0 | .state = CHILD_INIT, |
251 | 0 | .sibling_count = sibling_count, |
252 | 0 | .config = { |
253 | 0 | .session_unique_ptr = unique_session_ptr, |
254 | 0 | .free_child = free_child |
255 | 0 | }, |
256 | 0 | .p_result = p_result |
257 | 0 | }; |
258 | |
|
259 | 0 | return unlang_child_request_stack_init(out); |
260 | 0 | } |
261 | | |
262 | | int unlang_child_request_op_init(void) |
263 | 0 | { |
264 | 0 | unlang_register(&(unlang_op_t){ |
265 | 0 | .name = "child-request", |
266 | 0 | .type = UNLANG_TYPE_CHILD_REQUEST, |
267 | | |
268 | | /* |
269 | | * Frame can't be cancelled, because children need to |
270 | | * write out status to the parent. If we don't do this, |
271 | | * then all children must be detachable and must detach |
272 | | * so they don't try and write out status to a "done" |
273 | | * parent. |
274 | | * |
275 | | * It's easier to allow the child/parent relationship |
276 | | * to end normally so that non-detachable requests are |
277 | | * guaranteed the parent still exists. |
278 | | */ |
279 | 0 | .flag = UNLANG_OP_FLAG_NO_FORCE_UNWIND | UNLANG_OP_FLAG_INTERNAL, |
280 | |
|
281 | 0 | .interpret = unlang_child_request_done, |
282 | 0 | .signal = unlang_child_request_signal, |
283 | |
|
284 | 0 | .frame_state_size = sizeof(unlang_frame_state_child_request_t), |
285 | 0 | .frame_state_type = "unlang_frame_state_child_request_t" |
286 | 0 | }); |
287 | |
|
288 | 0 | return 0; |
289 | 0 | } |