Coverage Report

Created: 2026-04-12 06:36

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}