Line data Source code
1 : /* SPDX-License-Identifier: GPL-2.0-only */
2 : /*
3 : * Copyright (c) 2022 Meta Platforms, Inc. and affiliates.
4 : */
5 :
6 : #include <errno.h>
7 :
8 : #include "bpfilter/cgen/cgen.h"
9 : #include "bpfilter/cgen/prog/link.h"
10 : #include "bpfilter/cgen/prog/map.h"
11 : #include "bpfilter/cgen/program.h"
12 : #include "bpfilter/ctx.h"
13 : #include "bpfilter/xlate/front.h"
14 : #include "core/chain.h"
15 : #include "core/counter.h"
16 : #include "core/front.h"
17 : #include "core/helper.h"
18 : #include "core/hook.h"
19 : #include "core/io.h"
20 : #include "core/list.h"
21 : #include "core/logger.h"
22 : #include "core/marsh.h"
23 : #include "core/request.h"
24 : #include "core/response.h"
25 : #include "core/rule.h"
26 :
27 : static int _bf_cli_setup(void);
28 : static int _bf_cli_teardown(void);
29 : static int _bf_cli_request_handler(struct bf_request *request,
30 : struct bf_response **response);
31 : static int _bf_cli_marsh(struct bf_marsh **marsh);
32 : static int _bf_cli_unmarsh(struct bf_marsh *marsh);
33 :
34 : const struct bf_front_ops cli_front = {
35 : .setup = _bf_cli_setup,
36 : .teardown = _bf_cli_teardown,
37 : .request_handler = _bf_cli_request_handler,
38 : .marsh = _bf_cli_marsh,
39 : .unmarsh = _bf_cli_unmarsh,
40 : };
41 :
42 0 : static int _bf_cli_setup(void)
43 : {
44 0 : return 0;
45 : }
46 :
47 0 : static int _bf_cli_teardown(void)
48 : {
49 0 : return 0;
50 : }
51 :
52 0 : int _bf_cli_ruleset_flush(const struct bf_request *request,
53 : struct bf_response **response)
54 : {
55 : UNUSED(request);
56 : UNUSED(response);
57 :
58 0 : bf_ctx_flush(BF_FRONT_CLI);
59 :
60 0 : return 0;
61 : }
62 :
63 0 : static int _bf_cli_ruleset_get(const struct bf_request *request,
64 : struct bf_response **response)
65 : {
66 0 : _free_bf_marsh_ struct bf_marsh *marsh = NULL;
67 0 : _free_bf_marsh_ struct bf_marsh *chain_marsh = NULL;
68 0 : _free_bf_marsh_ struct bf_marsh *hookopts_marsh = NULL;
69 0 : _free_bf_marsh_ struct bf_marsh *counters_marsh = NULL;
70 0 : _clean_bf_list_ bf_list cgens = bf_list_default(NULL, NULL);
71 0 : _clean_bf_list_ bf_list chains = bf_list_default(NULL, bf_chain_marsh);
72 0 : _clean_bf_list_ bf_list hookopts = bf_list_default(NULL, bf_hookopts_marsh);
73 0 : _clean_bf_list_ bf_list counters =
74 : bf_list_default(bf_list_free, bf_list_marsh);
75 : int r;
76 :
77 : UNUSED(request);
78 :
79 0 : r = bf_marsh_new(&marsh, NULL, 0);
80 0 : if (r < 0)
81 0 : return bf_err_r(r, "failed to get new marsh");
82 :
83 0 : r = bf_ctx_get_cgens_for_front(&cgens, BF_FRONT_CLI);
84 0 : if (r < 0)
85 0 : return bf_err_r(r, "failed to get cgen list");
86 :
87 0 : bf_list_foreach (&cgens, cgen_node) {
88 0 : struct bf_cgen *cgen = bf_list_node_get_data(cgen_node);
89 0 : _free_bf_list_ bf_list *cgen_counters = NULL;
90 :
91 0 : r = bf_list_add_tail(&chains, cgen->chain);
92 0 : if (r)
93 0 : return bf_err_r(r, "failed to add chain to list");
94 :
95 0 : r = bf_list_add_tail(&hookopts, cgen->program->link->hookopts);
96 0 : if (r)
97 0 : return bf_err_r(r, "failed to add hookopts to list");
98 :
99 0 : r = bf_list_new(&cgen_counters, &bf_list_ops_default(bf_counter_free,
100 : bf_counter_marsh));
101 0 : if (r)
102 : return r;
103 :
104 0 : r = bf_cgen_get_counters(cgen, cgen_counters);
105 0 : if (r)
106 : return r;
107 :
108 0 : r = bf_list_add_tail(&counters, cgen_counters);
109 0 : if (r)
110 : return r;
111 :
112 0 : TAKE_PTR(cgen_counters);
113 : }
114 :
115 : // Marsh the chain list
116 0 : r = bf_list_marsh(&chains, &chain_marsh);
117 0 : if (r < 0)
118 0 : return bf_err_r(r, "failed to marshal chains list");
119 :
120 0 : r = bf_marsh_add_child_obj(&marsh, chain_marsh);
121 0 : if (r < 0)
122 0 : return bf_err_r(r, "failed to add chain list to marsh");
123 :
124 : // Marsh the hookopts list
125 0 : r = bf_marsh_new(&hookopts_marsh, NULL, 0);
126 0 : bf_list_foreach (&hookopts, hookopts_node) {
127 0 : struct bf_hookopts *hookopts = bf_list_node_get_data(hookopts_node);
128 0 : _free_bf_marsh_ struct bf_marsh *child = NULL;
129 :
130 0 : if (hookopts) {
131 0 : r = bf_hookopts_marsh(bf_list_node_get_data(hookopts_node), &child);
132 0 : if (r < 0)
133 : return r;
134 :
135 0 : r = bf_marsh_add_child_obj(&hookopts_marsh, child);
136 0 : if (r < 0)
137 : return r;
138 : } else {
139 0 : r = bf_marsh_add_child_raw(&hookopts_marsh, NULL, 0);
140 0 : if (r)
141 : return r;
142 : }
143 : }
144 :
145 0 : r = bf_marsh_add_child_obj(&marsh, hookopts_marsh);
146 0 : if (r < 0)
147 0 : return bf_err_r(r, "failed to add chain list to marsh");
148 :
149 : // Marsh the counters list
150 0 : r = bf_list_marsh(&counters, &counters_marsh);
151 0 : if (r < 0)
152 0 : return bf_err_r(r, "failed to marshal counters list");
153 :
154 0 : r = bf_marsh_add_child_obj(&marsh, counters_marsh);
155 0 : if (r < 0)
156 0 : return bf_err_r(r, "failed to add counters list to marsh");
157 :
158 0 : return bf_response_new_success(response, (void *)marsh,
159 : bf_marsh_size(marsh));
160 : }
161 :
162 0 : int _bf_cli_ruleset_set(const struct bf_request *request,
163 : struct bf_response **response)
164 : {
165 0 : _clean_bf_list_ bf_list cgens = bf_list_default(NULL, NULL);
166 : struct bf_marsh *marsh, *list_elem = NULL;
167 : int r;
168 :
169 0 : bf_assert(request && response);
170 :
171 : // Unmarsh the list of chains
172 0 : marsh = (struct bf_marsh *)request->data;
173 0 : if (bf_marsh_size(marsh) != request->data_len) {
174 0 : return bf_err_r(
175 : -EINVAL,
176 : "request payload is expected to have the same size as the marsh");
177 : }
178 :
179 0 : bf_ctx_flush(BF_FRONT_CLI);
180 :
181 0 : while ((list_elem = bf_marsh_next_child(marsh, list_elem))) {
182 0 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
183 0 : _free_bf_chain_ struct bf_chain *chain = NULL;
184 0 : _free_bf_hookopts_ struct bf_hookopts *hookopts = NULL;
185 : struct bf_marsh *child = NULL;
186 :
187 0 : child = bf_marsh_next_child(list_elem, child);
188 0 : if (!child)
189 0 : return bf_err_r(-ENOENT, "expecting marsh for chain, none found");
190 :
191 0 : r = bf_chain_new_from_marsh(&chain, child);
192 0 : if (r)
193 0 : goto err_load;
194 :
195 0 : child = bf_marsh_next_child(list_elem, child);
196 0 : if (!child)
197 0 : return bf_err_r(-ENOENT, "expecting marsh for hook, none found");
198 0 : if (child->data_len) {
199 0 : r = bf_hookopts_new_from_marsh(&hookopts, child);
200 0 : if (r)
201 0 : goto err_load;
202 : }
203 :
204 0 : r = bf_cgen_new(&cgen, BF_FRONT_CLI, &chain);
205 0 : if (r)
206 0 : goto err_load;
207 :
208 0 : r = bf_cgen_set(cgen, request->ns, hookopts ? &hookopts : NULL);
209 0 : if (r) {
210 0 : bf_err_r(r, "failed to set chain '%s'", cgen->chain->name);
211 0 : goto err_load;
212 : }
213 :
214 0 : r = bf_ctx_set_cgen(cgen);
215 0 : if (r) {
216 : /* The codegen is loaded already, if the daemon runs in persistent
217 : * mode, cleaning the codegen won't be sufficient to discard the
218 : * chain, it must be unpinned. */
219 0 : bf_cgen_unload(cgen);
220 0 : goto err_load;
221 : }
222 :
223 0 : TAKE_PTR(cgen);
224 : }
225 :
226 : return 0;
227 :
228 : err_load:
229 0 : bf_ctx_flush(BF_FRONT_CLI);
230 0 : return r;
231 : }
232 :
233 0 : int _bf_cli_chain_set(const struct bf_request *request,
234 : struct bf_response **response)
235 : {
236 : struct bf_cgen *old_cgen;
237 : struct bf_marsh *marsh, *child = NULL;
238 0 : _free_bf_cgen_ struct bf_cgen *new_cgen = NULL;
239 0 : _free_bf_chain_ struct bf_chain *chain = NULL;
240 0 : _free_bf_hookopts_ struct bf_hookopts *hookopts = NULL;
241 : int r;
242 :
243 0 : bf_assert(request && response);
244 :
245 0 : marsh = (struct bf_marsh *)request->data;
246 0 : if (bf_marsh_size(marsh) != request->data_len) {
247 0 : return bf_err_r(
248 : -EINVAL,
249 : "request payload is expected to have the same size as the marsh");
250 : }
251 :
252 0 : child = bf_marsh_next_child(marsh, child);
253 0 : if (!child)
254 0 : return bf_err_r(-ENOENT, "expecting marsh for chain, none found");
255 0 : r = bf_chain_new_from_marsh(&chain, child);
256 0 : if (r)
257 : return r;
258 :
259 0 : child = bf_marsh_next_child(marsh, child);
260 0 : if (!child)
261 0 : return bf_err_r(-ENOENT, "expecting marsh for hookopts, none found");
262 0 : if (child->data_len) {
263 0 : r = bf_hookopts_new_from_marsh(&hookopts, child);
264 0 : if (r)
265 : return r;
266 : }
267 :
268 0 : r = bf_cgen_new(&new_cgen, BF_FRONT_CLI, &chain);
269 0 : if (r)
270 : return r;
271 :
272 0 : old_cgen = bf_ctx_get_cgen(new_cgen->chain->name);
273 0 : if (old_cgen) {
274 : /* bf_ctx_delete_cgen() can only fail if the codegen is not found,
275 : * but we know this codegen exist. */
276 0 : (void)bf_ctx_delete_cgen(old_cgen, true);
277 : }
278 :
279 0 : r = bf_cgen_set(new_cgen, request->ns, hookopts ? &hookopts : NULL);
280 0 : if (r)
281 : return r;
282 :
283 0 : r = bf_ctx_set_cgen(new_cgen);
284 0 : if (r) {
285 0 : bf_cgen_unload(new_cgen);
286 0 : return r;
287 : }
288 :
289 0 : TAKE_PTR(new_cgen);
290 :
291 0 : return r;
292 : }
293 :
294 0 : static int _bf_cli_chain_get(const struct bf_request *request,
295 : struct bf_response **response)
296 : {
297 0 : _free_bf_marsh_ struct bf_marsh *marsh = NULL;
298 0 : _clean_bf_list_ bf_list counters =
299 : bf_list_default(bf_counter_free, bf_counter_marsh);
300 : struct bf_cgen *cgen;
301 : struct bf_marsh *req_marsh, *child = NULL;
302 : int r;
303 :
304 0 : req_marsh = (struct bf_marsh *)request->data;
305 0 : if (bf_marsh_size(req_marsh) != request->data_len) {
306 0 : return bf_err_r(
307 : -EINVAL,
308 : "request payload is expected to have the same size as the marsh");
309 : }
310 :
311 0 : if (!(child = bf_marsh_next_child(req_marsh, child)))
312 : return -EINVAL;
313 0 : if (child->data_len < 2)
314 0 : return bf_err_r(-EINVAL, "_bf_cli_chain_get: chain name is empty");
315 0 : if (child->data[child->data_len - 1]) {
316 0 : return bf_err_r(-EINVAL,
317 : "_bf_cli_chain_get: chain name if not nul-terminated");
318 : }
319 :
320 0 : cgen = bf_ctx_get_cgen(child->data);
321 0 : if (!cgen)
322 0 : return bf_err_r(-ENOENT, "chain '%s' not found", req_marsh->data);
323 :
324 0 : r = bf_marsh_new(&marsh, NULL, 0);
325 0 : if (r < 0)
326 0 : return bf_err_r(r, "failed to get new marsh");
327 :
328 : {
329 0 : _free_bf_marsh_ struct bf_marsh *child = NULL;
330 :
331 0 : r = bf_chain_marsh(cgen->chain, &child);
332 0 : if (r)
333 : return r;
334 :
335 0 : r = bf_marsh_add_child_obj(&marsh, child);
336 0 : if (r)
337 : return r;
338 : }
339 :
340 0 : if (cgen->program->link->hookopts) {
341 0 : _free_bf_marsh_ struct bf_marsh *child = NULL;
342 :
343 0 : r = bf_hookopts_marsh(cgen->program->link->hookopts, &child);
344 0 : if (r)
345 : return r;
346 :
347 0 : r = bf_marsh_add_child_obj(&marsh, child);
348 0 : if (r < 0)
349 : return r;
350 : } else {
351 0 : r = bf_marsh_add_child_raw(&marsh, NULL, 0);
352 0 : if (r)
353 : return r;
354 : }
355 :
356 : {
357 0 : _free_bf_marsh_ struct bf_marsh *child = NULL;
358 :
359 0 : r = bf_cgen_get_counters(cgen, &counters);
360 0 : if (r)
361 : return r;
362 :
363 0 : r = bf_list_marsh(&counters, &child);
364 0 : if (r)
365 : return r;
366 :
367 0 : r = bf_marsh_add_child_obj(&marsh, child);
368 0 : if (r)
369 : return r;
370 : }
371 :
372 0 : return bf_response_new_success(response, (void *)marsh,
373 : bf_marsh_size(marsh));
374 : }
375 :
376 0 : int _bf_cli_chain_logs_fd(const struct bf_request *request,
377 : struct bf_response **response)
378 : {
379 : struct bf_cgen *cgen;
380 : struct bf_marsh *marsh, *child = NULL;
381 : int r;
382 :
383 : UNUSED(response);
384 :
385 0 : marsh = (struct bf_marsh *)request->data;
386 0 : if (bf_marsh_size(marsh) != request->data_len) {
387 0 : return bf_err_r(
388 : -EINVAL,
389 : "request payload is expected to have the same size as the marsh");
390 : }
391 :
392 0 : if (!(child = bf_marsh_next_child(marsh, child)))
393 : return -EINVAL;
394 0 : if (child->data_len < 2)
395 0 : return bf_err_r(-EINVAL, "_bf_cli_chain_logs_fd: chain name is empty");
396 0 : if (child->data[child->data_len - 1]) {
397 0 : return bf_err_r(
398 : -EINVAL, "_bf_cli_chain_logs_fd: chain name if not nul-terminated");
399 : }
400 :
401 0 : cgen = bf_ctx_get_cgen(child->data);
402 0 : if (!cgen)
403 0 : return bf_err_r(-ENOENT, "failed to find chain '%s'", child->data);
404 :
405 0 : if (!cgen->program || !cgen->program->lmap->fd)
406 0 : return bf_err_r(-ENOENT, "chain '%s' has no logs buffer", child->data);
407 :
408 0 : r = bf_send_fd(request->fd, cgen->program->lmap->fd);
409 0 : if (r < 0)
410 0 : return bf_err_r(errno, "failed to send logs FD for '%s'", child->data);
411 :
412 : return 0;
413 : }
414 :
415 0 : int _bf_cli_chain_load(const struct bf_request *request,
416 : struct bf_response **response)
417 : {
418 : struct bf_marsh *marsh, *child = NULL;
419 0 : _free_bf_cgen_ struct bf_cgen *cgen = NULL;
420 0 : _free_bf_chain_ struct bf_chain *chain = NULL;
421 : int r;
422 :
423 0 : bf_assert(request && response);
424 :
425 0 : marsh = (struct bf_marsh *)request->data;
426 0 : if (bf_marsh_size(marsh) != request->data_len) {
427 0 : return bf_err_r(
428 : -EINVAL,
429 : "request payload is expected to have the same size as the marsh");
430 : }
431 :
432 0 : child = bf_marsh_next_child(marsh, child);
433 0 : if (!child)
434 0 : return bf_err_r(-ENOENT, "expecting marsh for chain, none found");
435 :
436 0 : r = bf_chain_new_from_marsh(&chain, child);
437 0 : if (r)
438 : return r;
439 :
440 0 : if (bf_ctx_get_cgen(chain->name)) {
441 0 : return bf_err_r(-EEXIST,
442 : "_bf_cli_chain_load: chain '%s' already exists",
443 : chain->name);
444 : }
445 :
446 0 : r = bf_cgen_new(&cgen, BF_FRONT_CLI, &chain);
447 0 : if (r)
448 : return r;
449 :
450 0 : r = bf_cgen_load(cgen);
451 0 : if (r)
452 : return r;
453 :
454 0 : r = bf_ctx_set_cgen(cgen);
455 0 : if (r) {
456 0 : bf_cgen_unload(cgen);
457 0 : return bf_err_r(
458 : r, "bf_ctx_set_cgen: failed to add cgen to the runtime context");
459 : }
460 :
461 0 : TAKE_PTR(cgen);
462 :
463 0 : return r;
464 : }
465 :
466 0 : int _bf_cli_chain_attach(const struct bf_request *request,
467 : struct bf_response **response)
468 : {
469 : struct bf_marsh *marsh, *child = NULL;
470 0 : _free_bf_chain_ struct bf_chain *chain = NULL;
471 : struct bf_cgen *cgen = NULL;
472 : const char *name;
473 0 : _free_bf_hookopts_ struct bf_hookopts *hookopts = NULL;
474 : int r;
475 :
476 0 : bf_assert(request && response);
477 :
478 0 : marsh = (struct bf_marsh *)request->data;
479 0 : if (bf_marsh_size(marsh) != request->data_len) {
480 0 : return bf_err_r(
481 : -EINVAL,
482 : "request payload is expected to have the same size as the marsh");
483 : }
484 :
485 0 : if (!(child = bf_marsh_next_child(marsh, child)))
486 : return -EINVAL;
487 0 : if (marsh->data[marsh->data_len - 1]) {
488 0 : return bf_err_r(
489 : -EINVAL, "_bf_cli_chain_attach: chain name if not nul-terminated");
490 : }
491 0 : name = child->data;
492 :
493 0 : if (!(child = bf_marsh_next_child(marsh, child)))
494 : return -EINVAL;
495 0 : r = bf_hookopts_new_from_marsh(&hookopts, child);
496 0 : if (r)
497 : return r;
498 :
499 0 : cgen = bf_ctx_get_cgen(name);
500 0 : if (!cgen)
501 0 : return bf_err_r(-ENOENT, "chain '%s' does not exist", name);
502 0 : if (cgen->program->link->hookopts)
503 0 : return bf_err_r(-EBUSY, "chain '%s' is already linked to a hook", name);
504 :
505 0 : r = bf_hookopts_validate(hookopts, cgen->chain->hook);
506 0 : if (r)
507 0 : return bf_err_r(r, "failed to validate hook options");
508 :
509 0 : r = bf_cgen_attach(cgen, request->ns, &hookopts);
510 0 : if (r)
511 0 : return bf_err_r(r, "failed to attach codegen to hook");
512 :
513 : return r;
514 : }
515 :
516 0 : int _bf_cli_chain_update(const struct bf_request *request,
517 : struct bf_response **response)
518 : {
519 : struct bf_marsh *marsh, *child = NULL;
520 0 : _free_bf_chain_ struct bf_chain *chain = NULL;
521 : struct bf_cgen *cgen = NULL;
522 : int r;
523 :
524 0 : bf_assert(request && response);
525 :
526 0 : marsh = (struct bf_marsh *)request->data;
527 0 : if (bf_marsh_size(marsh) != request->data_len) {
528 0 : return bf_err_r(
529 : -EINVAL,
530 : "request payload is expected to have the same size as the marsh");
531 : }
532 :
533 0 : child = bf_marsh_next_child(marsh, child);
534 0 : if (!child)
535 : return -EINVAL;
536 0 : r = bf_chain_new_from_marsh(&chain, child);
537 0 : if (r)
538 : return r;
539 :
540 0 : cgen = bf_ctx_get_cgen(chain->name);
541 0 : if (!cgen)
542 : return -ENOENT;
543 :
544 0 : if (!cgen->program->link->hookopts) {
545 0 : return bf_err_r(-EINVAL, "chain '%s' is not attached", chain->name);
546 : }
547 :
548 0 : r = bf_cgen_update(cgen, &chain);
549 0 : if (r)
550 : return -EINVAL;
551 :
552 : return r;
553 : }
554 :
555 0 : int _bf_cli_chain_flush(const struct bf_request *request,
556 : struct bf_response **response)
557 : {
558 : struct bf_marsh *marsh, *child = NULL;
559 : struct bf_cgen *cgen = NULL;
560 :
561 0 : bf_assert(request && response);
562 :
563 0 : marsh = (struct bf_marsh *)request->data;
564 0 : if (bf_marsh_size(marsh) != request->data_len) {
565 0 : return bf_err_r(
566 : -EINVAL,
567 : "request payload is expected to have the same size as the marsh");
568 : }
569 :
570 0 : if (!(child = bf_marsh_next_child(marsh, child)))
571 : return -EINVAL;
572 0 : if (child->data_len < 2)
573 0 : return bf_err_r(-EINVAL, "_bf_cli_chain_flush: chain name is empty");
574 0 : if (child->data[child->data_len - 1]) {
575 0 : return bf_err_r(
576 : -EINVAL, "_bf_cli_chain_flush: chain name if not nul-terminated");
577 : }
578 :
579 0 : cgen = bf_ctx_get_cgen(child->data);
580 0 : if (!cgen)
581 : return -ENOENT;
582 :
583 0 : return bf_ctx_delete_cgen(cgen, true);
584 : }
585 :
586 0 : static int _bf_cli_request_handler(struct bf_request *request,
587 : struct bf_response **response)
588 : {
589 : int r;
590 :
591 0 : bf_assert(request);
592 0 : bf_assert(response);
593 :
594 0 : switch (request->cmd) {
595 0 : case BF_REQ_RULESET_FLUSH:
596 0 : r = _bf_cli_ruleset_flush(request, response);
597 0 : break;
598 0 : case BF_REQ_RULESET_SET:
599 0 : r = _bf_cli_ruleset_set(request, response);
600 0 : break;
601 0 : case BF_REQ_RULESET_GET:
602 0 : r = _bf_cli_ruleset_get(request, response);
603 0 : break;
604 0 : case BF_REQ_CHAIN_SET:
605 0 : r = _bf_cli_chain_set(request, response);
606 0 : break;
607 0 : case BF_REQ_CHAIN_GET:
608 0 : r = _bf_cli_chain_get(request, response);
609 0 : break;
610 0 : case BF_REQ_CHAIN_LOGS_FD:
611 0 : r = _bf_cli_chain_logs_fd(request, response);
612 0 : break;
613 0 : case BF_REQ_CHAIN_LOAD:
614 0 : r = _bf_cli_chain_load(request, response);
615 0 : break;
616 0 : case BF_REQ_CHAIN_ATTACH:
617 0 : r = _bf_cli_chain_attach(request, response);
618 0 : break;
619 0 : case BF_REQ_CHAIN_UPDATE:
620 0 : r = _bf_cli_chain_update(request, response);
621 0 : break;
622 0 : case BF_REQ_CHAIN_FLUSH:
623 0 : r = _bf_cli_chain_flush(request, response);
624 0 : break;
625 0 : default:
626 0 : r = bf_err_r(-EINVAL, "unsupported command %d for CLI front-end",
627 : request->cmd);
628 : break;
629 : }
630 :
631 : /* If the callback don't need to send data back to the client, it can skip
632 : * the response creation and return a status code instead (0 on success,
633 : * negative errno value on error). The response is created based on the
634 : * status code. */
635 0 : if (!*response) {
636 0 : if (!r)
637 0 : r = bf_response_new_success(response, NULL, 0);
638 : else
639 0 : r = bf_response_new_failure(response, r);
640 : }
641 :
642 0 : return r;
643 : }
644 :
645 0 : static int _bf_cli_marsh(struct bf_marsh **marsh)
646 : {
647 : UNUSED(marsh);
648 :
649 0 : return 0;
650 : }
651 :
652 0 : static int _bf_cli_unmarsh(struct bf_marsh *marsh)
653 : {
654 : UNUSED(marsh);
655 :
656 0 : return 0;
657 : }
|