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