blob: b32b25fd4bcfe8c7c46373207616439412aecc76 [file] [log] [blame]
Radek Krejci206fcd62015-10-07 15:42:48 +02001/**
2 * \file io.c
3 * \author Radek Krejci <rkrejci@cesnet.cz>
4 * \brief libnetconf2 - input/output functions
5 *
6 * Copyright (c) 2015 CESNET, z.s.p.o.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
16 * distribution.
17 * 3. Neither the name of the Company nor the names of its contributors
18 * may be used to endorse or promote products derived from this
19 * software without specific prior written permission.
20 *
21 */
22
Radek Krejcife0b3472015-10-12 13:43:42 +020023#define _GNU_SOURCE /* asprintf */
Radek Krejci206fcd62015-10-07 15:42:48 +020024#include <assert.h>
25#include <errno.h>
26#include <poll.h>
Radek Krejcife0b3472015-10-12 13:43:42 +020027#include <stdarg.h>
Radek Krejci206fcd62015-10-07 15:42:48 +020028#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31
32#include <libyang/libyang.h>
33
34#include "config.h"
35#include "libnetconf.h"
36#include "session_p.h"
Radek Krejcife0b3472015-10-12 13:43:42 +020037#include "messages_p.h"
Radek Krejci206fcd62015-10-07 15:42:48 +020038
Radek Krejcife0b3472015-10-12 13:43:42 +020039#define BUFFERSIZE 512
Radek Krejci206fcd62015-10-07 15:42:48 +020040
41static ssize_t
42nc_read(struct nc_session *session, char *buf, size_t count)
43{
44 size_t size = 0;
45 ssize_t r;
46
47 assert(session);
48 assert(buf);
49
50 if (!count) {
51 return 0;
52 }
53
Michal Vasko38a7c6c2015-12-04 12:29:20 +010054 switch (session->ti_type) {
55 case NC_TI_NONE:
56 return 0;
57
Radek Krejci206fcd62015-10-07 15:42:48 +020058 case NC_TI_FD:
59 /* read via standard file descriptor */
Radek Krejci206fcd62015-10-07 15:42:48 +020060 while(count) {
61 r = read(session->ti.fd.in, &(buf[size]), count);
62 if (r < 0) {
63 if (errno == EAGAIN) {
64 usleep(NC_READ_SLEEP);
65 continue;
66 } else {
67 ERR("Reading from file descriptor (%d) failed (%s).", session->ti.fd.in, strerror(errno));
68 return -1;
69 }
70 } else if (r == 0) {
71 ERR("Communication file descriptor (%d) unexpectedly closed.", session->ti.fd.in);
72 return -1;
73 }
74
75 size = size + r;
76 count = count - r;
77 }
78 break;
79
Michal Vaskofb2fb762015-10-27 11:44:32 +010080#ifdef ENABLE_SSH
Radek Krejci206fcd62015-10-07 15:42:48 +020081 case NC_TI_LIBSSH:
82 /* read via libssh */
83 while(count) {
84 r = ssh_channel_read(session->ti.libssh.channel, &(buf[size]), count, 0);
85 if (r == SSH_AGAIN) {
86 usleep (NC_READ_SLEEP);
87 continue;
88 } else if (r == SSH_ERROR) {
89 ERR("Reading from the SSH channel failed (%zd: %s)",
90 ssh_get_error_code(session->ti.libssh.session), ssh_get_error(session->ti.libssh.session));
91 return -1;
92 } else if (r == 0) {
93 if (ssh_channel_is_eof(session->ti.libssh.channel)) {
94 ERR("Communication socket unexpectedly closed (libssh).");
95 return -1;
96 }
97 usleep (NC_READ_SLEEP);
98 continue;
99 }
100
101 size = size + r;
102 count = count - r;
103 }
104 break;
105#endif
106
107#ifdef ENABLE_TLS
108 case NC_TI_OPENSSL:
109 /* read via OpenSSL */
Radek Krejcid0046592015-10-08 12:52:02 +0200110 while(count) {
111 r = SSL_read(session->ti.tls, &(buf[size]), count);
112 if (r <= 0) {
113 int x;
114 switch (x = SSL_get_error(session->ti.tls, r)) {
115 case SSL_ERROR_WANT_READ:
116 usleep(NC_READ_SLEEP);
117 continue;
118 case SSL_ERROR_ZERO_RETURN:
119 ERR("Communication socket unexpectedly closed (OpenSSL).");
120 return -1;
121 default:
122 ERR("Reading from the TLS session failed (SSL code %d)", x);
123 return -1;
124 }
Radek Krejci206fcd62015-10-07 15:42:48 +0200125 }
Radek Krejcid0046592015-10-08 12:52:02 +0200126 size = size + r;
127 count = count - r;
Radek Krejci206fcd62015-10-07 15:42:48 +0200128 }
129 break;
130#endif
131 }
132
133 return (ssize_t)size;
134}
135
136static ssize_t
137nc_read_chunk(struct nc_session *session, size_t len, char **chunk)
138{
139 ssize_t r;
140
141 assert(session);
142 assert(chunk);
143
144 if (!len) {
145 return 0;
146 }
147
148 *chunk = malloc ((len + 1) * sizeof **chunk);
149 if (!*chunk) {
150 ERRMEM;
151 return -1;
152 }
153
154 r = nc_read(session, *chunk, len);
155 if (r <= 0) {
156 free(*chunk);
157 return -1;
158 }
159
160 /* terminating null byte */
Radek Krejcife0b3472015-10-12 13:43:42 +0200161 (*chunk)[r] = 0;
Radek Krejci206fcd62015-10-07 15:42:48 +0200162
163 return r;
164}
165
166static ssize_t
167nc_read_until(struct nc_session *session, const char *endtag, size_t limit, char **result)
168{
169 char *chunk = NULL;
170 size_t size, count = 0, r, len;
171
172 assert(session);
173 assert(endtag);
174
175 if (limit && limit < BUFFERSIZE) {
176 size = limit;
177 } else {
178 size = BUFFERSIZE;
179 }
180 chunk = malloc ((size + 1) * sizeof *chunk);
Radek Krejcib791b532015-10-08 15:29:34 +0200181 if (!chunk) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200182 ERRMEM;
183 return -1;
184 }
185
186 len = strlen(endtag);
187 while(1) {
188 if (limit && count == limit) {
189 free(chunk);
190 WRN("%s: reading limit (%d) reached.", __func__, limit);
191 ERR("Invalid input data (missing \"%s\" sequence).", endtag);
192 return -1;
193 }
194
195 /* resize buffer if needed */
196 if (count == size) {
197 /* get more memory */
198 size = size + BUFFERSIZE;
199 char *tmp = realloc (chunk, (size + 1) * sizeof *tmp);
200 if (!tmp) {
201 ERRMEM;
202 free(chunk);
203 return -1;
204 }
205 chunk = tmp;
206 }
207
208 /* get another character */
209 r = nc_read(session, &(chunk[count]), 1);
210 if (r != 1) {
211 free(chunk);
212 return -1;
213 }
214
215 count++;
216
217 /* check endtag */
218 if (count >= len) {
219 if (!strncmp(endtag, &(chunk[count - len]), len)) {
220 /* endtag found */
221 break;
222 }
223 }
224 }
225
226 /* terminating null byte */
227 chunk[count] = 0;
228
229 if (result) {
230 *result = chunk;
Radek Krejcife0b3472015-10-12 13:43:42 +0200231 } else {
232 free(chunk);
Radek Krejci206fcd62015-10-07 15:42:48 +0200233 }
234 return count;
235}
236
237NC_MSG_TYPE
238nc_read_msg(struct nc_session* session, int timeout, struct lyxml_elem **data)
239{
240 int status;
241 int revents;
242 struct pollfd fds;
243 const char *emsg = NULL;
244 char *msg = NULL, *chunk, *aux;
Radek Krejcife0b3472015-10-12 13:43:42 +0200245 unsigned long int chunk_len, len = 0;
Radek Krejci206fcd62015-10-07 15:42:48 +0200246
247 assert(data);
248 *data = NULL;
249
250 /* fill fds structure for poll */
251 if (session->ti_type == NC_TI_FD) {
252 fds.fd = session->ti.fd.in;
253#ifdef ENABLE_TLS
254 } else if (session->ti_type == NC_TI_OPENSSL) {
Radek Krejcid0046592015-10-08 12:52:02 +0200255 fds.fd = SSL_get_fd(session->ti.tls);
Radek Krejci206fcd62015-10-07 15:42:48 +0200256#endif
257 }
258
Michal Vasko38a7c6c2015-12-04 12:29:20 +0100259 while (1) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200260 /* poll loop */
261
Michal Vasko38a7c6c2015-12-04 12:29:20 +0100262 switch (session->ti_type) {
263 case NC_TI_NONE:
264 return NC_MSG_ERROR;
265
Michal Vaskofb2fb762015-10-27 11:44:32 +0100266#ifdef ENABLE_SSH
Radek Krejci206fcd62015-10-07 15:42:48 +0200267 case NC_TI_LIBSSH:
268 /* we are getting data from libssh's channel */
269 status = ssh_channel_poll_timeout(session->ti.libssh.channel, timeout, 0);
270 if (status > 0) {
271 revents = POLLIN;
272 } else if (status == SSH_AGAIN) {
273 /* try again */
274 continue;
275 } else if (status == SSH_EOF) {
276 emsg = "SSH channel closed";
277 } else {
278 if (!session->ti.libssh.channel) {
279 emsg = strerror(errno);
280 } else if (session->ti.libssh.session) {
281 emsg = ssh_get_error(session->ti.libssh.session);
282 } else {
283 emsg = "description not available";
284 }
285 }
286 break;
287#endif
288
289#ifdef ENABLE_TLS
290 case NC_TI_OPENSSL:
291 /* no break - same processing as in case of standard file descriptors */
292#endif
293 case NC_TI_FD:
294 fds.events = POLLIN;
295 fds.revents = 0;
296 status = poll(&fds, 1, timeout);
297 revents = (unsigned long int) fds.revents;
298 break;
299 }
300
301 /* process the poll result */
302 if (status == 0) {
303 /* timed out */
304 return NC_MSG_WOULDBLOCK;
305 } else if ((status == -1) && (errno == EINTR)) {
306 /* poll was interrupted */
307 continue;
308 } else if (status < 0) {
309 /* poll failed - something really bad happened, close the session */
310 ERR("Input channel error (%s).", emsg ? emsg : strerror(errno));
311
312 /* TODO - destroy the session */
313
314 return NC_MSG_ERROR;
315 } else { /* status > 0 */
316 /* in case of standard (non-libssh) poll, there still can be an error */
317 if ((revents & POLLHUP) || (revents & POLLERR)) {
318 /* close client's socket (it's probably already closed by peer */
319 ERR("Input channel closed.");
320
321 /* TODO - destroy the session */
322
323 return NC_MSG_ERROR;
324 }
325
326 /* we have something to read, so get out of the loop */
327 break;
328 }
329 }
330
331 /* read the message */
332 switch (session->version) {
333 case NC_VERSION_10:
334 status = nc_read_until(session, NC_VERSION_10_ENDTAG, 0, &msg);
335 if (status == -1) {
336 goto error;
337 }
338
339 /* cut off the end tag */
340 msg[status - NC_VERSION_10_ENDTAG_LEN] = '\0';
341 break;
342 case NC_VERSION_11:
343 while(1) {
344 status = nc_read_until(session, "\n#", 0, NULL);
345 if (status == -1) {
346 goto error;
347 }
348 status = nc_read_until(session, "\n", 0, &chunk);
349 if (status == -1) {
350 goto error;
351 }
352
353 if (!strcmp(chunk, "#\n")) {
354 /* end of chunked framing message */
355 free(chunk);
356 break;
357 }
358
359 /* convert string to the size of the following chunk */
360 chunk_len = strtoul(chunk, (char **) NULL, 10);
Radek Krejcife0b3472015-10-12 13:43:42 +0200361 free (chunk);
362 if (!chunk_len) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200363 ERR("Invalid frame chunk size detected, fatal error.");
364 goto error;
365 }
Radek Krejci206fcd62015-10-07 15:42:48 +0200366
367 /* now we have size of next chunk, so read the chunk */
368 status = nc_read_chunk(session, chunk_len, &chunk);
369 if (status == -1) {
370 goto error;
371 }
372
373 /* realloc message buffer, remember to count terminating null byte */
374 aux = realloc(msg, len + chunk_len + 1);
375 if (!aux) {
376 ERRMEM;
377 goto error;
378 }
379 msg = aux;
380 memcpy(msg + len, chunk, chunk_len);
381 len += chunk_len;
382 msg[len] = '\0';
383 free(chunk);
384 }
385
386 break;
387 }
Michal Vaskoaebea602015-10-26 15:35:34 +0100388 DBG("Received message (session %u): %s", session->id, msg);
Radek Krejci206fcd62015-10-07 15:42:48 +0200389
390 /* build XML tree */
Michal Vasko0dbafd12015-12-10 10:10:37 +0100391 *data = lyxml_read_data(session->ctx, msg, 0);
Radek Krejci206fcd62015-10-07 15:42:48 +0200392 if (!*data) {
393 goto error;
394 } else if (!(*data)->ns) {
395 ERR("Invalid message root element (invalid namespace)");
396 goto error;
397 }
398 free(msg);
399 msg = NULL;
400
401 /* get and return message type */
402 if (!strcmp((*data)->ns->value, NC_NS_BASE)) {
403 if (!strcmp((*data)->name, "rpc")) {
404 return NC_MSG_RPC;
405 } else if (!strcmp((*data)->name, "rpc-reply")) {
406 return NC_MSG_REPLY;
407 } else if (!strcmp((*data)->name, "hello")) {
408 return NC_MSG_HELLO;
409 } else {
410 ERR("Invalid message root element (invalid name \"%s\")", (*data)->name);
411 goto error;
412 }
413 } else if (!strcmp((*data)->ns->value, NC_NS_NOTIF)) {
414 if (!strcmp((*data)->name, "notification")) {
Radek Krejci5686ff72015-10-09 13:33:56 +0200415 return NC_MSG_NOTIF;
Radek Krejci206fcd62015-10-07 15:42:48 +0200416 } else {
417 ERR("Invalid message root element (invalid name \"%s\")", (*data)->name);
418 goto error;
419 }
420 } else {
421 ERR("Invalid message root element (invalid namespace \"%s\")", (*data)->ns->value);
422 goto error;
423 }
424
425error:
426 /* cleanup */
427 free(msg);
428 free(*data);
429 *data = NULL;
430
Radek Krejci695d4fa2015-10-22 13:23:54 +0200431 if (session->side == NC_SERVER && session->version == NC_VERSION_11) {
Radek Krejci206fcd62015-10-07 15:42:48 +0200432 /* NETCONF version 1.1 define sending error reply from the server */
433 /* TODO
434 reply = nc_reply_error(nc_err_new(NC_ERR_MALFORMED_MSG));
435 if (reply == NULL) {
436 ERROR("Unable to create the \'Malformed message\' reply");
437 nc_session_close(session, NC_SESSION_TERM_OTHER);
438 return (NC_MSG_UNKNOWN);
439 }
440
441 if (nc_session_send_reply(session, NULL, reply) == 0) {
442 ERROR("Unable to send the \'Malformed message\' reply");
443 nc_session_close(session, NC_SESSION_TERM_OTHER);
444 return (NC_MSG_UNKNOWN);
445 }
446 nc_reply_free(reply);
447 */
448 }
449
450 ERR("Malformed message received, closing the session %u.", session->id);
451 /* TODO - destroy the session */
452
453 return NC_MSG_ERROR;
454}
Radek Krejcife0b3472015-10-12 13:43:42 +0200455
456#define WRITE_BUFSIZE (2 * BUFFERSIZE)
457struct wclb_arg {
458 struct nc_session *session;
459 char buf[WRITE_BUFSIZE];
460 size_t len;
461};
462
463static ssize_t
Michal Vasko086311b2016-01-08 09:53:11 +0100464write_text_(struct nc_session *session, const void *buf, size_t count)
Radek Krejcife0b3472015-10-12 13:43:42 +0200465{
Radek Krejcife0b3472015-10-12 13:43:42 +0200466 switch (session->ti_type) {
Michal Vasko38a7c6c2015-12-04 12:29:20 +0100467 case NC_TI_NONE:
468 return -1;
469
Radek Krejcife0b3472015-10-12 13:43:42 +0200470 case NC_TI_FD:
Michal Vasko086311b2016-01-08 09:53:11 +0100471 return write(session->ti.fd.out, buf, count);
Radek Krejcife0b3472015-10-12 13:43:42 +0200472
Michal Vaskofb2fb762015-10-27 11:44:32 +0100473#ifdef ENABLE_SSH
Radek Krejcife0b3472015-10-12 13:43:42 +0200474 case NC_TI_LIBSSH:
Michal Vasko086311b2016-01-08 09:53:11 +0100475 return ssh_channel_write(session->ti.libssh.channel, buf, count);
Radek Krejcife0b3472015-10-12 13:43:42 +0200476#endif
Radek Krejcife0b3472015-10-12 13:43:42 +0200477#ifdef ENABLE_TLS
478 case NC_TI_OPENSSL:
Michal Vasko086311b2016-01-08 09:53:11 +0100479 return SSL_write(session->ti.tls, buf, count);
Radek Krejcife0b3472015-10-12 13:43:42 +0200480#endif
481 }
482
483 return -1;
484}
485
Michal Vasko086311b2016-01-08 09:53:11 +0100486static ssize_t
487write_starttag_and_msg(struct nc_session *session, const void *buf, size_t count)
488{
489 int c = 0;
490 char chunksize[20];
491
492 if (session->version == NC_VERSION_11) {
493 sprintf(chunksize, "\n#%zu\n", count);
494 c = write_text_(session, chunksize, strlen(chunksize));
495 }
496 return write_text_(session, buf, count) + c;
497}
498
Radek Krejcife0b3472015-10-12 13:43:42 +0200499static int
500write_endtag(struct nc_session *session)
501{
502 switch(session->ti_type) {
Michal Vasko38a7c6c2015-12-04 12:29:20 +0100503 case NC_TI_NONE:
504 return 0;
505
Radek Krejcife0b3472015-10-12 13:43:42 +0200506 case NC_TI_FD:
507 if (session->version == NC_VERSION_11) {
508 write(session->ti.fd.out, "\n##\n", 4);
509 } else {
510 write(session->ti.fd.out, "]]>]]>", 6);
511 }
512 break;
513
Michal Vaskofb2fb762015-10-27 11:44:32 +0100514#ifdef ENABLE_SSH
Radek Krejcife0b3472015-10-12 13:43:42 +0200515 case NC_TI_LIBSSH:
516 if (session->version == NC_VERSION_11) {
517 ssh_channel_write(session->ti.libssh.channel, "\n##\n", 4);
518 } else {
519 ssh_channel_write(session->ti.libssh.channel, "]]>]]>", 6);
520 }
521 break;
522#endif
523
524#ifdef ENABLE_TLS
525 case NC_TI_OPENSSL:
526 if (session->version == NC_VERSION_11) {
527 SSL_write(session->ti.tls, "\n##\n", 4);
528 } else {
529 SSL_write(session->ti.tls, "]]>]]>", 6);
530 }
531 break;
532#endif
533 }
534
535 return 0;
536}
537
538static void
539write_clb_flush(struct wclb_arg *warg)
540{
541 /* flush current buffer */
542 if (warg->len) {
Michal Vasko086311b2016-01-08 09:53:11 +0100543 write_starttag_and_msg(warg->session, warg->buf, warg->len);
Radek Krejcife0b3472015-10-12 13:43:42 +0200544 warg->len = 0;
545 }
546}
547
548static ssize_t
549write_clb(void *arg, const void *buf, size_t count)
550{
551 struct wclb_arg *warg = (struct wclb_arg *)arg;
552
553 if (!buf) {
554 write_clb_flush(warg);
555
556 /* endtag */
557 write_endtag(warg->session);
558 return 0;
559 }
560
561 if (warg->len && (warg->len + count > WRITE_BUFSIZE)) {
562 /* dump current buffer */
563 write_clb_flush(warg);
564 }
565 if (count > WRITE_BUFSIZE) {
566 /* write directly */
Michal Vasko086311b2016-01-08 09:53:11 +0100567 write_starttag_and_msg(warg->session, buf, count);
Radek Krejcife0b3472015-10-12 13:43:42 +0200568 } else {
569 /* keep in buffer and write later */
570 memcpy(&warg->buf[warg->len], buf, count);
571 warg->len += count; /* is <= WRITE_BUFSIZE */
572 }
573
574 return (ssize_t)count;
575}
576
Radek Krejcid116db42016-01-08 15:36:30 +0100577int
578nc_write_msg(struct nc_session *session, NC_MSG_TYPE type, ...)
Radek Krejcife0b3472015-10-12 13:43:42 +0200579{
Radek Krejcid116db42016-01-08 15:36:30 +0100580 va_list ap;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200581 int count, i;
Radek Krejcife0b3472015-10-12 13:43:42 +0200582 const char *attrs;
583 struct lyd_node *content;
Michal Vaskoad611702015-12-03 13:41:51 +0100584 struct nc_server_rpc *rpc;
Radek Krejcid116db42016-01-08 15:36:30 +0100585 char *buf = NULL;
586 struct wclb_arg arg;
Radek Krejci695d4fa2015-10-22 13:23:54 +0200587 const char **capabilities;
588 uint32_t *sid = NULL;
Radek Krejcife0b3472015-10-12 13:43:42 +0200589
Radek Krejcid116db42016-01-08 15:36:30 +0100590 va_start(ap, type);
Radek Krejcife0b3472015-10-12 13:43:42 +0200591
592 arg.session = session;
593 arg.len = 0;
594
595 switch (type) {
596 case NC_MSG_RPC:
597 content = va_arg(ap, struct lyd_node *);
598 attrs = va_arg(ap, const char *);
599 count = asprintf(&buf, "<rpc xmlns=\"%s\" message-id=\"%"PRIu64"\"%s>",
600 NC_NS_BASE, session->msgid + 1, attrs ? attrs : "");
601 write_clb((void *)&arg, buf, count);
602 free(buf);
603 lyd_print_clb(write_clb, (void *)&arg, content, LYD_XML);
604 write_clb((void *)&arg, "</rpc>", 6);
605
606 session->msgid++;
607 break;
608 case NC_MSG_REPLY:
Michal Vaskoad611702015-12-03 13:41:51 +0100609 rpc = va_arg(ap, struct nc_server_rpc *);
Radek Krejcife0b3472015-10-12 13:43:42 +0200610 write_clb((void *)&arg, "<rpc-reply", 10);
611 lyxml_dump_clb(write_clb, (void *)&arg, rpc->root, LYXML_DUMP_ATTRS);
612 write_clb((void *)&arg, ">", 1);
613 /* TODO content */
614 write_clb((void *)&arg, "</rpc-reply>", 12);
615 break;
616 case NC_MSG_NOTIF:
617 write_clb((void *)&arg, "<notification xmlns=\""NC_NS_NOTIF"\"/>", 21 + 47 + 3);
618 /* TODO content */
619 write_clb((void *)&arg, "</notification>", 12);
620 break;
Radek Krejcid116db42016-01-08 15:36:30 +0100621 case NC_MSG_HELLO:
622 if (session->version != NC_VERSION_10) {
623 va_end(ap);
624 return -1;
625 }
626 capabilities = va_arg(ap, const char **);
627 sid = va_arg(ap, uint32_t*);
628 count = asprintf(&buf, "<hello xmlns=\"%s\"><capabilities>", NC_NS_BASE);
629 write_clb((void *)&arg, buf, count);
630 free(buf);
631 for (i = 0; capabilities[i]; i++) {
632 count = asprintf(&buf, "<capability>%s</capability>", capabilities[i]);
633 write_clb((void *)&arg, buf, count);
634 free(buf);
635 }
636 if (sid) {
637 asprintf(&buf, "</capabilities><session-id>%u</session-id></hello>", *sid);
638 write_clb((void *)&arg, buf, count);
639 free(buf);
640 } else {
641 write_clb((void *)&arg, "</capabilities></hello>", 23);
642 }
Radek Krejcid116db42016-01-08 15:36:30 +0100643 break;
Michal Vaskoed462342016-01-12 12:33:48 +0100644
Radek Krejcife0b3472015-10-12 13:43:42 +0200645 default:
Radek Krejcid116db42016-01-08 15:36:30 +0100646 va_end(ap);
Radek Krejcife0b3472015-10-12 13:43:42 +0200647 return -1;
648 }
649
650 /* flush message */
651 write_clb((void *)&arg, NULL, 0);
Radek Krejcife0b3472015-10-12 13:43:42 +0200652
653 va_end(ap);
Radek Krejcid116db42016-01-08 15:36:30 +0100654 return 0;
Radek Krejcife0b3472015-10-12 13:43:42 +0200655}