s4/torture: Fix misplaced positional arguments for u64 comparison
[gd/samba-autobuild/.git] / source3 / torture / wbc_async.c
1 /*
2    Unix SMB/CIFS implementation.
3    Infrastructure for async winbind requests
4    Copyright (C) Volker Lendecke 2008
5
6      ** NOTE! The following LGPL license applies to the wbclient
7      ** library. This does NOT imply that all of Samba is released
8      ** under the LGPL
9
10    This library is free software; you can redistribute it and/or
11    modify it under the terms of the GNU Lesser General Public
12    License as published by the Free Software Foundation; either
13    version 3 of the License, or (at your option) any later version.
14
15    This library is distributed in the hope that it will be useful,
16    but WITHOUT ANY WARRANTY; without even the implied warranty of
17    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18    Library General Public License for more details.
19
20    You should have received a copy of the GNU Lesser General Public License
21    along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include "replace.h"
25 #include "system/filesys.h"
26 #include "system/network.h"
27 #include <talloc.h>
28 #include <tevent.h>
29 #include "lib/async_req/async_sock.h"
30 #include "nsswitch/winbind_struct_protocol.h"
31 #include "nsswitch/libwbclient/wbclient.h"
32 #include "wbc_async.h"
33 #include "lib/util/blocking.h"
34
35 wbcErr map_wbc_err_from_errno(int error)
36 {
37         switch(error) {
38         case EPERM:
39         case EACCES:
40                 return WBC_ERR_AUTH_ERROR;
41         case ENOMEM:
42                 return WBC_ERR_NO_MEMORY;
43         case EIO:
44         default:
45                 return WBC_ERR_UNKNOWN_FAILURE;
46         }
47 }
48
49 bool tevent_req_is_wbcerr(struct tevent_req *req, wbcErr *pwbc_err)
50 {
51         enum tevent_req_state state;
52         uint64_t error;
53         if (!tevent_req_is_error(req, &state, &error)) {
54                 *pwbc_err = WBC_ERR_SUCCESS;
55                 return false;
56         }
57
58         switch (state) {
59         case TEVENT_REQ_USER_ERROR:
60                 *pwbc_err = error;
61                 break;
62         case TEVENT_REQ_TIMED_OUT:
63                 *pwbc_err = WBC_ERR_UNKNOWN_FAILURE;
64                 break;
65         case TEVENT_REQ_NO_MEMORY:
66                 *pwbc_err = WBC_ERR_NO_MEMORY;
67                 break;
68         default:
69                 *pwbc_err = WBC_ERR_UNKNOWN_FAILURE;
70                 break;
71         }
72         return true;
73 }
74
75 wbcErr tevent_req_simple_recv_wbcerr(struct tevent_req *req)
76 {
77         wbcErr wbc_err;
78
79         if (tevent_req_is_wbcerr(req, &wbc_err)) {
80                 return wbc_err;
81         }
82
83         return WBC_ERR_SUCCESS;
84 }
85
86 struct wbc_debug_ops {
87         void (*debug)(void *context, enum wbcDebugLevel level,
88                       const char *fmt, va_list ap) PRINTF_ATTRIBUTE(3,0);
89         void *context;
90 };
91
92 struct wb_context {
93         struct tevent_queue *queue;
94         int fd;
95         bool is_priv;
96         const char *dir;
97         struct wbc_debug_ops debug_ops;
98 };
99
100 static int make_nonstd_fd(int fd)
101 {
102         size_t i;
103         int sys_errno = 0;
104         int fds[3];
105         size_t num_fds = 0;
106
107         if (fd == -1) {
108                 return -1;
109         }
110         while (fd < 3) {
111                 fds[num_fds++] = fd;
112                 fd = dup(fd);
113                 if (fd == -1) {
114                         sys_errno = errno;
115                         break;
116                 }
117         }
118         for (i=0; i<num_fds; i++) {
119                 close(fds[i]);
120         }
121         if (fd == -1) {
122                 errno = sys_errno;
123         }
124         return fd;
125 }
126
127 /****************************************************************************
128  Set a fd into blocking/nonblocking mode.
129  Set close on exec also.
130 ****************************************************************************/
131
132 static int make_safe_fd(int fd)
133 {
134         int result, flags;
135         int new_fd = make_nonstd_fd(fd);
136
137         if (new_fd == -1) {
138                 goto fail;
139         }
140
141         result = set_blocking(new_fd, false);
142         if (result == -1) {
143                 goto fail;
144         }
145
146         /* Socket should be closed on exec() */
147 #ifdef FD_CLOEXEC
148         result = flags = fcntl(new_fd, F_GETFD, 0);
149         if (flags >= 0) {
150                 flags |= FD_CLOEXEC;
151                 result = fcntl( new_fd, F_SETFD, flags );
152         }
153         if (result < 0) {
154                 goto fail;
155         }
156 #endif
157         return new_fd;
158
159  fail:
160         if (new_fd != -1) {
161                 int sys_errno = errno;
162                 close(new_fd);
163                 errno = sys_errno;
164         }
165         return -1;
166 }
167
168 /* Just put a prototype to avoid moving the whole function around */
169 static const char *winbindd_socket_dir(void);
170
171 struct wb_context *wb_context_init(TALLOC_CTX *mem_ctx, const char* dir)
172 {
173         struct wb_context *result;
174
175         result = talloc_zero(mem_ctx, struct wb_context);
176         if (result == NULL) {
177                 return NULL;
178         }
179         result->queue = tevent_queue_create(result, "wb_trans");
180         if (result->queue == NULL) {
181                 TALLOC_FREE(result);
182                 return NULL;
183         }
184         result->fd = -1;
185         result->is_priv = false;
186
187         if (dir != NULL) {
188                 result->dir = talloc_strdup(result, dir);
189         } else {
190                 result->dir = winbindd_socket_dir();
191         }
192         if (result->dir == NULL) {
193                 TALLOC_FREE(result);
194                 return NULL;
195         }
196         return result;
197 }
198
199 struct wb_connect_state {
200         int dummy;
201 };
202
203 static void wbc_connect_connected(struct tevent_req *subreq);
204
205 static struct tevent_req *wb_connect_send(TALLOC_CTX *mem_ctx,
206                                           struct tevent_context *ev,
207                                           struct wb_context *wb_ctx,
208                                           const char *dir)
209 {
210         struct tevent_req *result, *subreq;
211         struct wb_connect_state *state;
212         struct sockaddr_un sunaddr;
213         struct stat st;
214         char *path = NULL;
215         wbcErr wbc_err;
216
217         result = tevent_req_create(mem_ctx, &state, struct wb_connect_state);
218         if (result == NULL) {
219                 return NULL;
220         }
221
222         if (wb_ctx->fd != -1) {
223                 close(wb_ctx->fd);
224                 wb_ctx->fd = -1;
225         }
226
227         /* Check permissions on unix socket directory */
228
229         if (lstat(dir, &st) == -1) {
230                 wbc_err = WBC_ERR_WINBIND_NOT_AVAILABLE;
231                 goto post_status;
232         }
233
234         if (!S_ISDIR(st.st_mode) ||
235             (st.st_uid != 0 && st.st_uid != geteuid())) {
236                 wbc_err = WBC_ERR_WINBIND_NOT_AVAILABLE;
237                 goto post_status;
238         }
239
240         /* Connect to socket */
241
242         path = talloc_asprintf(mem_ctx, "%s/%s", dir,
243                                WINBINDD_SOCKET_NAME);
244         if (path == NULL) {
245                 goto nomem;
246         }
247
248         sunaddr.sun_family = AF_UNIX;
249         strlcpy(sunaddr.sun_path, path, sizeof(sunaddr.sun_path));
250         TALLOC_FREE(path);
251
252         /* If socket file doesn't exist, don't bother trying to connect
253            with retry.  This is an attempt to make the system usable when
254            the winbindd daemon is not running. */
255
256         if ((lstat(sunaddr.sun_path, &st) == -1)
257             || !S_ISSOCK(st.st_mode)
258             || (st.st_uid != 0 && st.st_uid != geteuid())) {
259                 wbc_err = WBC_ERR_WINBIND_NOT_AVAILABLE;
260                 goto post_status;
261         }
262
263         wb_ctx->fd = make_safe_fd(socket(AF_UNIX, SOCK_STREAM, 0));
264         if (wb_ctx->fd == -1) {
265                 wbc_err = map_wbc_err_from_errno(errno);
266                 goto post_status;
267         }
268
269         subreq = async_connect_send(mem_ctx, ev, wb_ctx->fd,
270                                     (struct sockaddr *)(void *)&sunaddr,
271                                     sizeof(sunaddr), NULL, NULL, NULL);
272         if (subreq == NULL) {
273                 goto nomem;
274         }
275         tevent_req_set_callback(subreq, wbc_connect_connected, result);
276         return result;
277
278  post_status:
279         tevent_req_error(result, wbc_err);
280         return tevent_req_post(result, ev);
281  nomem:
282         TALLOC_FREE(result);
283         return NULL;
284 }
285
286 static void wbc_connect_connected(struct tevent_req *subreq)
287 {
288         struct tevent_req *req = tevent_req_callback_data(
289                 subreq, struct tevent_req);
290         int res, err;
291
292         res = async_connect_recv(subreq, &err);
293         TALLOC_FREE(subreq);
294         if (res == -1) {
295                 tevent_req_error(req, map_wbc_err_from_errno(err));
296                 return;
297         }
298         tevent_req_done(req);
299 }
300
301 static wbcErr wb_connect_recv(struct tevent_req *req)
302 {
303         return tevent_req_simple_recv_wbcerr(req);
304 }
305
306 static const char *winbindd_socket_dir(void)
307 {
308         if (nss_wrapper_enabled()) {
309                 const char *env_dir;
310
311                 env_dir = getenv("SELFTEST_WINBINDD_SOCKET_DIR");
312                 if (env_dir != NULL) {
313                         return env_dir;
314                 }
315         }
316
317         return WINBINDD_SOCKET_DIR;
318 }
319
320 struct wb_open_pipe_state {
321         struct wb_context *wb_ctx;
322         struct tevent_context *ev;
323         bool need_priv;
324         struct winbindd_request wb_req;
325 };
326
327 static void wb_open_pipe_connect_nonpriv_done(struct tevent_req *subreq);
328 static void wb_open_pipe_ping_done(struct tevent_req *subreq);
329 static void wb_open_pipe_getpriv_done(struct tevent_req *subreq);
330 static void wb_open_pipe_connect_priv_done(struct tevent_req *subreq);
331
332 static struct tevent_req *wb_open_pipe_send(TALLOC_CTX *mem_ctx,
333                                             struct tevent_context *ev,
334                                             struct wb_context *wb_ctx,
335                                             bool need_priv)
336 {
337         struct tevent_req *result, *subreq;
338         struct wb_open_pipe_state *state;
339
340         result = tevent_req_create(mem_ctx, &state, struct wb_open_pipe_state);
341         if (result == NULL) {
342                 return NULL;
343         }
344         state->wb_ctx = wb_ctx;
345         state->ev = ev;
346         state->need_priv = need_priv;
347
348         if (wb_ctx->fd != -1) {
349                 close(wb_ctx->fd);
350                 wb_ctx->fd = -1;
351         }
352
353         subreq = wb_connect_send(state, ev, wb_ctx, wb_ctx->dir);
354         if (subreq == NULL) {
355                 goto fail;
356         }
357         tevent_req_set_callback(subreq, wb_open_pipe_connect_nonpriv_done,
358                                 result);
359         return result;
360
361  fail:
362         TALLOC_FREE(result);
363         return NULL;
364 }
365
366 static void wb_open_pipe_connect_nonpriv_done(struct tevent_req *subreq)
367 {
368         struct tevent_req *req = tevent_req_callback_data(
369                 subreq, struct tevent_req);
370         struct wb_open_pipe_state *state = tevent_req_data(
371                 req, struct wb_open_pipe_state);
372         wbcErr wbc_err;
373
374         wbc_err = wb_connect_recv(subreq);
375         TALLOC_FREE(subreq);
376         if (!WBC_ERROR_IS_OK(wbc_err)) {
377                 state->wb_ctx->is_priv = true;
378                 tevent_req_error(req, wbc_err);
379                 return;
380         }
381
382         ZERO_STRUCT(state->wb_req);
383         state->wb_req.cmd = WINBINDD_INTERFACE_VERSION;
384         state->wb_req.pid = getpid();
385         (void)snprintf(state->wb_req.client_name,
386                        sizeof(state->wb_req.client_name),
387                        "%s",
388                        "TORTURE");
389
390         subreq = wb_simple_trans_send(state, state->ev, NULL,
391                                       state->wb_ctx->fd, &state->wb_req);
392         if (tevent_req_nomem(subreq, req)) {
393                 return;
394         }
395         tevent_req_set_callback(subreq, wb_open_pipe_ping_done, req);
396 }
397
398 static void wb_open_pipe_ping_done(struct tevent_req *subreq)
399 {
400         struct tevent_req *req = tevent_req_callback_data(
401                 subreq, struct tevent_req);
402         struct wb_open_pipe_state *state = tevent_req_data(
403                 req, struct wb_open_pipe_state);
404         struct winbindd_response *wb_resp;
405         int ret, err;
406
407         ret = wb_simple_trans_recv(subreq, state, &wb_resp, &err);
408         TALLOC_FREE(subreq);
409         if (ret == -1) {
410                 tevent_req_error(req, map_wbc_err_from_errno(err));
411                 return;
412         }
413
414         if (!state->need_priv) {
415                 tevent_req_done(req);
416                 return;
417         }
418
419         state->wb_req.cmd = WINBINDD_PRIV_PIPE_DIR;
420         state->wb_req.pid = getpid();
421
422         subreq = wb_simple_trans_send(state, state->ev, NULL,
423                                       state->wb_ctx->fd, &state->wb_req);
424         if (tevent_req_nomem(subreq, req)) {
425                 return;
426         }
427         tevent_req_set_callback(subreq, wb_open_pipe_getpriv_done, req);
428 }
429
430 static void wb_open_pipe_getpriv_done(struct tevent_req *subreq)
431 {
432         struct tevent_req *req = tevent_req_callback_data(
433                 subreq, struct tevent_req);
434         struct wb_open_pipe_state *state = tevent_req_data(
435                 req, struct wb_open_pipe_state);
436         struct winbindd_response *wb_resp = NULL;
437         int ret, err;
438
439         ret = wb_simple_trans_recv(subreq, state, &wb_resp, &err);
440         TALLOC_FREE(subreq);
441         if (ret == -1) {
442                 tevent_req_error(req, map_wbc_err_from_errno(err));
443                 return;
444         }
445
446         close(state->wb_ctx->fd);
447         state->wb_ctx->fd = -1;
448
449         subreq = wb_connect_send(state, state->ev, state->wb_ctx,
450                                   (char *)wb_resp->extra_data.data);
451         TALLOC_FREE(wb_resp);
452         if (tevent_req_nomem(subreq, req)) {
453                 return;
454         }
455         tevent_req_set_callback(subreq, wb_open_pipe_connect_priv_done, req);
456 }
457
458 static void wb_open_pipe_connect_priv_done(struct tevent_req *subreq)
459 {
460         struct tevent_req *req = tevent_req_callback_data(
461                 subreq, struct tevent_req);
462         struct wb_open_pipe_state *state = tevent_req_data(
463                 req, struct wb_open_pipe_state);
464         wbcErr wbc_err;
465
466         wbc_err = wb_connect_recv(subreq);
467         TALLOC_FREE(subreq);
468         if (!WBC_ERROR_IS_OK(wbc_err)) {
469                 tevent_req_error(req, wbc_err);
470                 return;
471         }
472         state->wb_ctx->is_priv = true;
473         tevent_req_done(req);
474 }
475
476 static wbcErr wb_open_pipe_recv(struct tevent_req *req)
477 {
478         return tevent_req_simple_recv_wbcerr(req);
479 }
480
481 struct wb_trans_state {
482         struct wb_trans_state *prev, *next;
483         struct wb_context *wb_ctx;
484         struct tevent_context *ev;
485         struct winbindd_request *wb_req;
486         struct winbindd_response *wb_resp;
487         bool need_priv;
488 };
489
490 static bool closed_fd(int fd)
491 {
492         struct timeval tv;
493         fd_set r_fds;
494         int selret;
495
496         if (fd == -1) {
497                 return true;
498         }
499
500         FD_ZERO(&r_fds);
501         FD_SET(fd, &r_fds);
502         ZERO_STRUCT(tv);
503
504         selret = select(fd+1, &r_fds, NULL, NULL, &tv);
505         if (selret == -1) {
506                 return true;
507         }
508         if (selret == 0) {
509                 return false;
510         }
511         return (FD_ISSET(fd, &r_fds));
512 }
513
514 static void wb_trans_trigger(struct tevent_req *req, void *private_data);
515 static void wb_trans_connect_done(struct tevent_req *subreq);
516 static void wb_trans_done(struct tevent_req *subreq);
517 static void wb_trans_retry_wait_done(struct tevent_req *subreq);
518
519 struct tevent_req *wb_trans_send(TALLOC_CTX *mem_ctx,
520                                  struct tevent_context *ev,
521                                  struct wb_context *wb_ctx, bool need_priv,
522                                  struct winbindd_request *wb_req)
523 {
524         struct tevent_req *req;
525         struct wb_trans_state *state;
526
527         req = tevent_req_create(mem_ctx, &state, struct wb_trans_state);
528         if (req == NULL) {
529                 return NULL;
530         }
531         state->wb_ctx = wb_ctx;
532         state->ev = ev;
533         state->wb_req = wb_req;
534         state->need_priv = need_priv;
535
536         if (!tevent_queue_add(wb_ctx->queue, ev, req, wb_trans_trigger,
537                               NULL)) {
538                 tevent_req_oom(req);
539                 return tevent_req_post(req, ev);
540         }
541         return req;
542 }
543
544 static void wb_trans_trigger(struct tevent_req *req, void *private_data)
545 {
546         struct wb_trans_state *state = tevent_req_data(
547                 req, struct wb_trans_state);
548         struct tevent_req *subreq;
549
550         if ((state->wb_ctx->fd != -1) && closed_fd(state->wb_ctx->fd)) {
551                 close(state->wb_ctx->fd);
552                 state->wb_ctx->fd = -1;
553         }
554
555         if ((state->wb_ctx->fd == -1)
556             || (state->need_priv && !state->wb_ctx->is_priv)) {
557                 subreq = wb_open_pipe_send(state, state->ev, state->wb_ctx,
558                                            state->need_priv);
559                 if (tevent_req_nomem(subreq, req)) {
560                         return;
561                 }
562                 tevent_req_set_callback(subreq, wb_trans_connect_done, req);
563                 return;
564         }
565
566         state->wb_req->pid = getpid();
567
568         subreq = wb_simple_trans_send(state, state->ev, NULL,
569                                       state->wb_ctx->fd, state->wb_req);
570         if (tevent_req_nomem(subreq, req)) {
571                 return;
572         }
573         tevent_req_set_callback(subreq, wb_trans_done, req);
574 }
575
576 static bool wb_trans_retry(struct tevent_req *req,
577                            struct wb_trans_state *state,
578                            wbcErr wbc_err)
579 {
580         struct tevent_req *subreq;
581
582         if (WBC_ERROR_IS_OK(wbc_err)) {
583                 return false;
584         }
585
586         if (wbc_err == WBC_ERR_WINBIND_NOT_AVAILABLE) {
587                 /*
588                  * Winbind not around or we can't connect to the pipe. Fail
589                  * immediately.
590                  */
591                 tevent_req_error(req, wbc_err);
592                 return true;
593         }
594
595         /*
596          * The transfer as such failed, retry after one second
597          */
598
599         if (state->wb_ctx->fd != -1) {
600                 close(state->wb_ctx->fd);
601                 state->wb_ctx->fd = -1;
602         }
603
604         subreq = tevent_wakeup_send(state, state->ev,
605                                     tevent_timeval_current_ofs(1, 0));
606         if (tevent_req_nomem(subreq, req)) {
607                 return true;
608         }
609         tevent_req_set_callback(subreq, wb_trans_retry_wait_done, req);
610         return true;
611 }
612
613 static void wb_trans_retry_wait_done(struct tevent_req *subreq)
614 {
615         struct tevent_req *req = tevent_req_callback_data(
616                 subreq, struct tevent_req);
617         struct wb_trans_state *state = tevent_req_data(
618                 req, struct wb_trans_state);
619         bool ret;
620
621         ret = tevent_wakeup_recv(subreq);
622         TALLOC_FREE(subreq);
623         if (!ret) {
624                 tevent_req_error(req, WBC_ERR_UNKNOWN_FAILURE);
625                 return;
626         }
627
628         subreq = wb_open_pipe_send(state, state->ev, state->wb_ctx,
629                                    state->need_priv);
630         if (tevent_req_nomem(subreq, req)) {
631                 return;
632         }
633         tevent_req_set_callback(subreq, wb_trans_connect_done, req);
634 }
635
636 static void wb_trans_connect_done(struct tevent_req *subreq)
637 {
638         struct tevent_req *req = tevent_req_callback_data(
639                 subreq, struct tevent_req);
640         struct wb_trans_state *state = tevent_req_data(
641                 req, struct wb_trans_state);
642         wbcErr wbc_err;
643
644         wbc_err = wb_open_pipe_recv(subreq);
645         TALLOC_FREE(subreq);
646
647         if (wb_trans_retry(req, state, wbc_err)) {
648                 return;
649         }
650
651         subreq = wb_simple_trans_send(state, state->ev, NULL,
652                                       state->wb_ctx->fd, state->wb_req);
653         if (tevent_req_nomem(subreq, req)) {
654                 return;
655         }
656         tevent_req_set_callback(subreq, wb_trans_done, req);
657 }
658
659 static void wb_trans_done(struct tevent_req *subreq)
660 {
661         struct tevent_req *req = tevent_req_callback_data(
662                 subreq, struct tevent_req);
663         struct wb_trans_state *state = tevent_req_data(
664                 req, struct wb_trans_state);
665         int ret, err;
666
667         ret = wb_simple_trans_recv(subreq, state, &state->wb_resp, &err);
668         TALLOC_FREE(subreq);
669         if ((ret == -1)
670             && wb_trans_retry(req, state, map_wbc_err_from_errno(err))) {
671                 return;
672         }
673
674         tevent_req_done(req);
675 }
676
677 wbcErr wb_trans_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
678                      struct winbindd_response **presponse)
679 {
680         struct wb_trans_state *state = tevent_req_data(
681                 req, struct wb_trans_state);
682         wbcErr wbc_err;
683
684         if (tevent_req_is_wbcerr(req, &wbc_err)) {
685                 return wbc_err;
686         }
687
688         *presponse = talloc_move(mem_ctx, &state->wb_resp);
689         return WBC_ERR_SUCCESS;
690 }
691
692 /********************************************************************
693  * Debug wrapper functions, modeled (with lot's of code copied as is)
694  * after the tevent debug wrapper functions
695  ********************************************************************/
696
697 /*
698   this allows the user to choose their own debug function
699 */
700 int wbcSetDebug(struct wb_context *wb_ctx,
701                 void (*debug)(void *context,
702                               enum wbcDebugLevel level,
703                               const char *fmt,
704                               va_list ap) PRINTF_ATTRIBUTE(3,0),
705                 void *context)
706 {
707         wb_ctx->debug_ops.debug = debug;
708         wb_ctx->debug_ops.context = context;
709         return 0;
710 }
711
712 /*
713   debug function for wbcSetDebugStderr
714 */
715 static void wbcDebugStderr(void *private_data,
716                            enum wbcDebugLevel level,
717                            const char *fmt,
718                            va_list ap) PRINTF_ATTRIBUTE(3,0);
719 static void wbcDebugStderr(void *private_data,
720                            enum wbcDebugLevel level,
721                            const char *fmt, va_list ap)
722 {
723         if (level <= WBC_DEBUG_WARNING) {
724                 vfprintf(stderr, fmt, ap);
725         }
726 }
727
728 /*
729   convenience function to setup debug messages on stderr
730   messages of level WBC_DEBUG_WARNING and higher are printed
731 */
732 int wbcSetDebugStderr(struct wb_context *wb_ctx)
733 {
734         return wbcSetDebug(wb_ctx, wbcDebugStderr, wb_ctx);
735 }
736
737 /*
738  * log a message
739  *
740  * The default debug action is to ignore debugging messages.
741  * This is the most appropriate action for a library.
742  * Applications using the library must decide where to
743  * redirect debugging messages
744 */
745 void wbcDebug(struct wb_context *wb_ctx, enum wbcDebugLevel level,
746               const char *fmt, ...)
747 {
748         va_list ap;
749         if (!wb_ctx) {
750                 return;
751         }
752         if (wb_ctx->debug_ops.debug == NULL) {
753                 return;
754         }
755         va_start(ap, fmt);
756         wb_ctx->debug_ops.debug(wb_ctx->debug_ops.context, level, fmt, ap);
757         va_end(ap);
758 }