blob: 1633b27c1c275e30d9fc270775f0fc30ad79a961 [file] [log] [blame]
Radek Krejci5aeea3a2018-09-05 13:29:36 +02001/**
2 * @file log.c
3 * @author Radek Krejci <rkrejci@cesnet.cz>
4 * @brief Logger routines implementations
5 *
6 * Copyright (c) 2015 - 2018 CESNET, z.s.p.o.
7 *
8 * This source code is licensed under BSD 3-Clause License (the "License").
9 * You may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * https://opensource.org/licenses/BSD-3-Clause
13 */
14
Radek Krejci535ea9f2020-05-29 16:01:05 +020015#define _GNU_SOURCE
16
17#include "log.h"
Radek Krejcib7db73a2018-10-24 14:18:40 +020018
Radek Krejci5aeea3a2018-09-05 13:29:36 +020019#include <assert.h>
Radek Krejcic04f0a22018-09-21 15:49:45 +020020#include <inttypes.h>
Radek Krejcie7b95092019-05-15 11:03:07 +020021#include <pthread.h>
Radek Krejci5aeea3a2018-09-05 13:29:36 +020022#include <stdarg.h>
Radek Krejcie7b95092019-05-15 11:03:07 +020023#include <stdint.h>
Radek Krejci5aeea3a2018-09-05 13:29:36 +020024#include <stdio.h>
Radek Krejcie7b95092019-05-15 11:03:07 +020025#include <stdlib.h>
26#include <string.h>
Radek Krejci5aeea3a2018-09-05 13:29:36 +020027
Radek Krejci535ea9f2020-05-29 16:01:05 +020028#include "common.h"
Radek Krejciaa45bda2020-07-20 07:43:38 +020029#include "compat.h"
Radek Krejci0935f412019-08-20 16:15:18 +020030#include "plugins_exts.h"
Radek Krejci535ea9f2020-05-29 16:01:05 +020031#include "tree_data.h"
32#include "tree_schema.h"
Radek Krejci5aeea3a2018-09-05 13:29:36 +020033
Radek Krejci52b6d512020-10-12 12:33:17 +020034volatile LY_LOG_LEVEL ly_ll = LY_LLWRN;
Radek Krejci1deb5be2020-08-26 16:43:36 +020035volatile uint32_t ly_log_opts = LY_LOLOG | LY_LOSTORE_LAST;
Michal Vaskod8085612020-08-21 12:55:23 +020036static ly_log_clb log_clb;
Radek Krejci857189e2020-09-01 13:26:36 +020037static volatile ly_bool path_flag = 1;
Radek Krejci5aeea3a2018-09-05 13:29:36 +020038#ifndef NDEBUG
Radek Krejci68433c92020-10-12 17:03:55 +020039volatile uint32_t ly_ldbg_groups = 0;
Radek Krejci5aeea3a2018-09-05 13:29:36 +020040#endif
41
Radek Krejci94aa9942018-09-07 17:12:17 +020042/* how many bytes add when enlarging buffers */
43#define LY_BUF_STEP 128
44
Radek Krejcid33273d2018-10-25 14:55:52 +020045API LY_ERR
46ly_errcode(const struct ly_ctx *ctx)
47{
48 struct ly_err_item *i;
49
Radek Krejci572ee602020-09-16 14:35:08 +020050 i = ly_err_last(ctx);
Radek Krejcid33273d2018-10-25 14:55:52 +020051 if (i) {
Radek Krejci572ee602020-09-16 14:35:08 +020052 return i->no;
Radek Krejcid33273d2018-10-25 14:55:52 +020053 }
54
55 return LY_SUCCESS;
56}
57
Radek Krejci5aeea3a2018-09-05 13:29:36 +020058API LY_VECODE
59ly_vecode(const struct ly_ctx *ctx)
60{
61 struct ly_err_item *i;
62
Radek Krejci572ee602020-09-16 14:35:08 +020063 i = ly_err_last(ctx);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020064 if (i) {
Radek Krejci572ee602020-09-16 14:35:08 +020065 return i->vecode;
Radek Krejci5aeea3a2018-09-05 13:29:36 +020066 }
67
68 return LYVE_SUCCESS;
69}
70
71API const char *
72ly_errmsg(const struct ly_ctx *ctx)
73{
74 struct ly_err_item *i;
75
Michal Vaskob3d0d6b2018-09-07 10:17:33 +020076 LY_CHECK_ARG_RET(NULL, ctx, NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020077
Radek Krejci572ee602020-09-16 14:35:08 +020078 i = ly_err_last(ctx);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020079 if (i) {
Radek Krejci572ee602020-09-16 14:35:08 +020080 return i->msg;
Radek Krejci5aeea3a2018-09-05 13:29:36 +020081 }
82
83 return NULL;
84}
85
86API const char *
87ly_errpath(const struct ly_ctx *ctx)
88{
89 struct ly_err_item *i;
90
Michal Vaskob3d0d6b2018-09-07 10:17:33 +020091 LY_CHECK_ARG_RET(NULL, ctx, NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020092
Radek Krejci572ee602020-09-16 14:35:08 +020093 i = ly_err_last(ctx);
Radek Krejci5aeea3a2018-09-05 13:29:36 +020094 if (i) {
Radek Krejci572ee602020-09-16 14:35:08 +020095 return i->path;
Radek Krejci5aeea3a2018-09-05 13:29:36 +020096 }
97
98 return NULL;
99}
100
101API const char *
102ly_errapptag(const struct ly_ctx *ctx)
103{
104 struct ly_err_item *i;
105
Michal Vaskob3d0d6b2018-09-07 10:17:33 +0200106 LY_CHECK_ARG_RET(NULL, ctx, NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200107
Radek Krejci572ee602020-09-16 14:35:08 +0200108 i = ly_err_last(ctx);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200109 if (i) {
Radek Krejci572ee602020-09-16 14:35:08 +0200110 return i->apptag;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200111 }
112
113 return NULL;
114}
115
116API struct ly_err_item *
Radek Krejcie7b95092019-05-15 11:03:07 +0200117ly_err_new(LY_LOG_LEVEL level, LY_ERR no, LY_VECODE vecode, char *msg, char *path, char *apptag)
118{
119 struct ly_err_item *eitem;
120
121 eitem = malloc(sizeof *eitem);
122 LY_CHECK_ERR_RET(!eitem, LOGMEM(NULL), NULL);
123 eitem->prev = eitem;
124 eitem->next = NULL;
125
126 /* fill in the information */
127 eitem->level = level;
128 eitem->no = no;
129 eitem->vecode = vecode;
130 eitem->msg = msg;
131 eitem->path = path;
132 eitem->apptag = apptag;
133
134 return eitem;
135}
136
137API struct ly_err_item *
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200138ly_err_first(const struct ly_ctx *ctx)
139{
Michal Vaskob3d0d6b2018-09-07 10:17:33 +0200140 LY_CHECK_ARG_RET(NULL, ctx, NULL);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200141
142 return pthread_getspecific(ctx->errlist_key);
143}
144
Radek Krejci572ee602020-09-16 14:35:08 +0200145API struct ly_err_item *
146ly_err_last(const struct ly_ctx *ctx)
147{
148 const struct ly_err_item *e;
149
150 LY_CHECK_ARG_RET(NULL, ctx, NULL);
151
152 e = pthread_getspecific(ctx->errlist_key);
153 return e ? e->prev : NULL;
154}
155
Radek Krejcie7b95092019-05-15 11:03:07 +0200156API void
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200157ly_err_free(void *ptr)
158{
159 struct ly_err_item *i, *next;
160
161 /* clean the error list */
162 for (i = (struct ly_err_item *)ptr; i; i = next) {
163 next = i->next;
164 free(i->msg);
165 free(i->path);
166 free(i->apptag);
167 free(i);
168 }
169}
170
171API void
172ly_err_clean(struct ly_ctx *ctx, struct ly_err_item *eitem)
173{
174 struct ly_err_item *i, *first;
175
176 first = ly_err_first(ctx);
177 if (first == eitem) {
178 eitem = NULL;
179 }
180 if (eitem) {
181 /* disconnect the error */
Radek Krejci1e008d22020-08-17 11:37:37 +0200182 for (i = first; i && (i->next != eitem); i = i->next) {}
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200183 assert(i);
184 i->next = NULL;
185 first->prev = i;
186 /* free this err and newer */
187 ly_err_free(eitem);
188 } else {
189 /* free all err */
190 ly_err_free(first);
191 pthread_setspecific(ctx->errlist_key, NULL);
192 }
193}
194
195API LY_LOG_LEVEL
Radek Krejci52b6d512020-10-12 12:33:17 +0200196ly_log_level(LY_LOG_LEVEL level)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200197{
Radek Krejci52b6d512020-10-12 12:33:17 +0200198 LY_LOG_LEVEL prev = ly_ll;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200199
Radek Krejci52b6d512020-10-12 12:33:17 +0200200 ly_ll = level;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200201 return prev;
202}
203
Radek Krejci1deb5be2020-08-26 16:43:36 +0200204API uint32_t
205ly_log_options(uint32_t opts)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200206{
Radek Krejci1deb5be2020-08-26 16:43:36 +0200207 uint32_t prev = ly_log_opts;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200208
209 ly_log_opts = opts;
210 return prev;
211}
212
Radek Krejciebdaed02020-11-09 13:05:06 +0100213API uint32_t
Radek Krejci68433c92020-10-12 17:03:55 +0200214ly_log_dbg_groups(uint32_t dbg_groups)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200215{
216#ifndef NDEBUG
Radek Krejciebdaed02020-11-09 13:05:06 +0100217 uint32_t prev = ly_ldbg_groups;
218
Radek Krejci68433c92020-10-12 17:03:55 +0200219 ly_ldbg_groups = dbg_groups;
Radek Krejciebdaed02020-11-09 13:05:06 +0100220 return prev;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200221#else
222 (void)dbg_groups;
Radek Krejciebdaed02020-11-09 13:05:06 +0100223 return 0;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200224#endif
225}
226
227API void
Radek Krejci857189e2020-09-01 13:26:36 +0200228ly_set_log_clb(ly_log_clb clb, ly_bool path)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200229{
Michal Vaskod8085612020-08-21 12:55:23 +0200230 log_clb = clb;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200231 path_flag = path;
232}
233
Michal Vaskod8085612020-08-21 12:55:23 +0200234API ly_log_clb
235ly_get_log_clb(void)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200236{
Michal Vaskod8085612020-08-21 12:55:23 +0200237 return log_clb;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200238}
239
240static LY_ERR
241log_store(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, LY_VECODE vecode, char *msg, char *path, char *apptag)
242{
243 struct ly_err_item *eitem, *last;
244
245 assert(ctx && (level < LY_LLVRB));
246
247 eitem = pthread_getspecific(ctx->errlist_key);
248 if (!eitem) {
249 /* if we are only to fill in path, there must have been an error stored */
250 assert(msg);
251 eitem = malloc(sizeof *eitem);
252 LY_CHECK_GOTO(!eitem, mem_fail);
253 eitem->prev = eitem;
254 eitem->next = NULL;
255
256 pthread_setspecific(ctx->errlist_key, eitem);
257 } else if (!msg) {
258 /* only filling the path */
259 assert(path);
260
261 /* find last error */
262 eitem = eitem->prev;
263 do {
264 if (eitem->level == LY_LLERR) {
265 /* fill the path */
266 free(eitem->path);
267 eitem->path = path;
268 return LY_SUCCESS;
269 }
270 eitem = eitem->prev;
271 } while (eitem->prev->next);
272 /* last error was not found */
273 assert(0);
Michal Vaskoed94a292019-11-06 15:43:41 +0100274 } else if ((ly_log_opts & LY_LOSTORE_LAST) == LY_LOSTORE_LAST) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200275 /* overwrite last message */
276 free(eitem->msg);
277 free(eitem->path);
278 free(eitem->apptag);
279 } else {
280 /* store new message */
281 last = eitem->prev;
282 eitem->prev = malloc(sizeof *eitem);
283 LY_CHECK_GOTO(!eitem->prev, mem_fail);
284 eitem = eitem->prev;
285 eitem->prev = last;
286 eitem->next = NULL;
287 last->next = eitem;
288 }
289
290 /* fill in the information */
291 eitem->level = level;
292 eitem->no = no;
293 eitem->vecode = vecode;
294 eitem->msg = msg;
295 eitem->path = path;
296 eitem->apptag = apptag;
297 return LY_SUCCESS;
298
299mem_fail:
300 LOGMEM(NULL);
301 free(msg);
302 free(path);
303 free(apptag);
304 return LY_EMEM;
305}
306
307static void
308log_vprintf(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, LY_VECODE vecode, char *path,
Radek Krejci0f969882020-08-21 16:56:47 +0200309 const char *format, va_list args)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200310{
311 char *msg = NULL;
Radek Krejci857189e2020-09-01 13:26:36 +0200312 ly_bool free_strs;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200313
Radek Krejci52b6d512020-10-12 12:33:17 +0200314 if (level > ly_ll) {
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200315 /* do not print or store the message */
316 free(path);
317 return;
318 }
319
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200320 /* store the error/warning (if we need to store errors internally, it does not matter what are the user log options) */
Michal Vaskoed94a292019-11-06 15:43:41 +0100321 if ((level < LY_LLVRB) && ctx && (ly_log_opts & LY_LOSTORE)) {
Michal Vasko004d3152020-06-11 19:59:22 +0200322 assert(format);
323 if (vasprintf(&msg, format, args) == -1) {
324 LOGMEM(ctx);
325 free(path);
326 return;
327 }
Radek Krejcic9e64a62020-09-18 20:08:12 +0200328 if (((no & ~LY_EPLUGIN) == LY_EVALID) && (vecode == LYVE_SUCCESS)) {
329 /* assume we are inheriting the error, so inherit vecode as well */
330 vecode = ly_vecode(ctx);
331 }
Michal Vasko004d3152020-06-11 19:59:22 +0200332 if (log_store(ctx, level, no, vecode, msg, path, NULL)) {
333 return;
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200334 }
335 free_strs = 0;
336 } else {
337 if (vasprintf(&msg, format, args) == -1) {
338 LOGMEM(ctx);
339 free(path);
340 return;
341 }
342 free_strs = 1;
343 }
344
345 /* if we are only storing errors internally, never print the message (yet) */
Michal Vaskoed94a292019-11-06 15:43:41 +0100346 if (ly_log_opts & LY_LOLOG) {
Michal Vaskod8085612020-08-21 12:55:23 +0200347 if (log_clb) {
348 log_clb(level, msg, path);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200349 } else {
350 fprintf(stderr, "libyang[%d]: %s%s", level, msg, path ? " " : "\n");
351 if (path) {
352 fprintf(stderr, "(path: %s)\n", path);
353 }
354 }
355 }
356
357 if (free_strs) {
358 free(path);
359 free(msg);
360 }
361}
362
Radek Krejci4ab61562018-09-05 15:00:37 +0200363#ifndef NDEBUG
364
365void
Radek Krejci1deb5be2020-08-26 16:43:36 +0200366ly_log_dbg(uint32_t group, const char *format, ...)
Radek Krejci4ab61562018-09-05 15:00:37 +0200367{
368 char *dbg_format;
369 const char *str_group;
370 va_list ap;
371
Radek Krejci68433c92020-10-12 17:03:55 +0200372 if (!(ly_ldbg_groups & group)) {
Radek Krejci4ab61562018-09-05 15:00:37 +0200373 return;
374 }
375
376 switch (group) {
377 case LY_LDGDICT:
378 str_group = "DICT";
379 break;
Radek Krejci4ab61562018-09-05 15:00:37 +0200380 case LY_LDGXPATH:
381 str_group = "XPATH";
382 break;
Radek Krejci4ab61562018-09-05 15:00:37 +0200383 default:
384 LOGINT(NULL);
385 return;
386 }
387
388 if (asprintf(&dbg_format, "%s: %s", str_group, format) == -1) {
389 LOGMEM(NULL);
390 return;
391 }
392
393 va_start(ap, format);
394 log_vprintf(NULL, LY_LLDBG, 0, 0, NULL, dbg_format, ap);
395 va_end(ap);
396}
397
398#endif
399
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200400void
401ly_log(const struct ly_ctx *ctx, LY_LOG_LEVEL level, LY_ERR no, const char *format, ...)
402{
403 va_list ap;
404
405 va_start(ap, format);
406 log_vprintf(ctx, level, no, 0, NULL, format, ap);
407 va_end(ap);
408}
409
Radek Krejci94aa9942018-09-07 17:12:17 +0200410static LY_ERR
Radek Krejcic04f0a22018-09-21 15:49:45 +0200411ly_vlog_build_path(const struct ly_ctx *ctx, enum LY_VLOG_ELEM elem_type, const void *elem, char **path)
Radek Krejci94aa9942018-09-07 17:12:17 +0200412{
Radek Krejcic04f0a22018-09-21 15:49:45 +0200413 int rc;
Radek Krejci94aa9942018-09-07 17:12:17 +0200414
Radek Krejcic04f0a22018-09-21 15:49:45 +0200415 switch (elem_type) {
416 case LY_VLOG_STR:
Michal Vaskof6e51882019-12-16 09:59:45 +0100417 *path = strdup(elem);
Radek Krejcic04f0a22018-09-21 15:49:45 +0200418 LY_CHECK_ERR_RET(!(*path), LOGMEM(ctx), LY_EMEM);
419 break;
420 case LY_VLOG_LINE:
Michal Vasko22df3f02020-08-24 13:29:22 +0200421 rc = asprintf(path, "Line number %" PRIu64 ".", *((uint64_t *)elem));
Radek Krejcic04f0a22018-09-21 15:49:45 +0200422 LY_CHECK_ERR_RET(rc == -1, LOGMEM(ctx), LY_EMEM);
423 break;
Michal Vaskof6e51882019-12-16 09:59:45 +0100424 case LY_VLOG_LYSC:
425 *path = lysc_path(elem, LYSC_PATH_LOG, NULL, 0);
426 LY_CHECK_ERR_RET(!(*path), LOGMEM(ctx), LY_EMEM);
427 break;
Michal Vasko9b368d32020-02-14 13:53:31 +0100428 case LY_VLOG_LYD:
429 *path = lyd_path(elem, LYD_PATH_LOG, NULL, 0);
430 LY_CHECK_ERR_RET(!(*path), LOGMEM(ctx), LY_EMEM);
431 break;
Radek Krejcic04f0a22018-09-21 15:49:45 +0200432 default:
433 /* shouldn't be here */
434 LOGINT_RET(ctx);
Radek Krejci94aa9942018-09-07 17:12:17 +0200435 }
436
Radek Krejci94aa9942018-09-07 17:12:17 +0200437 return LY_SUCCESS;
438}
439
440void
441ly_vlog(const struct ly_ctx *ctx, enum LY_VLOG_ELEM elem_type, const void *elem, LY_VECODE code, const char *format, ...)
442{
443 va_list ap;
Michal Vasko22df3f02020-08-24 13:29:22 +0200444 char *path = NULL;
Radek Krejci94aa9942018-09-07 17:12:17 +0200445 const struct ly_err_item *first;
446
447 if (path_flag && (elem_type != LY_VLOG_NONE)) {
448 if (elem_type == LY_VLOG_PREV) {
449 /* use previous path */
450 first = ly_err_first(ctx);
451 if (first && first->prev->path) {
452 path = strdup(first->prev->path);
453 }
454 } else {
455 /* print path */
456 if (!elem) {
457 /* top-level */
458 path = strdup("/");
459 } else {
Radek Krejcic04f0a22018-09-21 15:49:45 +0200460 ly_vlog_build_path(ctx, elem_type, elem, &path);
Radek Krejci94aa9942018-09-07 17:12:17 +0200461 }
462 }
463 }
464
465 va_start(ap, format);
466 log_vprintf(ctx, LY_LLERR, LY_EVALID, code, path, format, ap);
467 /* path is spent and should not be freed! */
468 va_end(ap);
469}
470
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200471API void
Radek Krejci0935f412019-08-20 16:15:18 +0200472lyext_log(const struct lysc_ext_instance *ext, LY_LOG_LEVEL level, LY_ERR err_no, const char *path, const char *format, ...)
473{
474 va_list ap;
475 char *plugin_msg;
476 int ret;
477
Radek Krejci52b6d512020-10-12 12:33:17 +0200478 if (ly_ll < level) {
Radek Krejci0935f412019-08-20 16:15:18 +0200479 return;
480 }
481 ret = asprintf(&plugin_msg, "Extension plugin \"%s\": %s)", ext->def->plugin->id, format);
482 if (ret == -1) {
Radek Krejci28681fa2019-09-06 13:08:45 +0200483 LOGMEM(ext->module->ctx);
Radek Krejci0935f412019-08-20 16:15:18 +0200484 return;
485 }
486
487 va_start(ap, format);
Radek Krejcia4614e62020-05-15 14:19:28 +0200488 log_vprintf(ext->module->ctx, level, (level == LY_LLERR ? LY_EPLUGIN : 0) | err_no, LYVE_OTHER, path ? strdup(path) : NULL, plugin_msg, ap);
Radek Krejci0935f412019-08-20 16:15:18 +0200489 va_end(ap);
490
491 free(plugin_msg);
492}
493
Michal Vasko177d0ed2020-11-23 16:43:03 +0100494/**
495 * @brief Exact same functionality as ::ly_err_print() but has variable arguments so va_start() can
496 * be used and an empty va_list created.
497 */
498static void
499_ly_err_print(const struct ly_ctx *ctx, struct ly_err_item *eitem, ...)
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200500{
Michal Vasko177d0ed2020-11-23 16:43:03 +0100501 va_list ap;
502 char *path_dup = NULL;
503
504 LY_CHECK_ARG_RET(ctx, eitem, );
505
506 if (eitem->path) {
507 /* duplicate path because it will be freed */
508 path_dup = strdup(eitem->path);
509 LY_CHECK_ERR_RET(!path_dup, LOGMEM(ctx), );
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200510 }
Michal Vasko177d0ed2020-11-23 16:43:03 +0100511
512 va_start(ap, eitem);
513 log_vprintf(ctx, eitem->level, eitem->no, eitem->vecode, eitem->path, eitem->msg, ap);
514 va_end(ap);
515
516 if (path_dup) {
517 eitem->path = path_dup;
518 }
519}
520
521API void
522ly_err_print(const struct ly_ctx *ctx, struct ly_err_item *eitem)
523{
524 _ly_err_print(ctx, eitem);
Radek Krejci5aeea3a2018-09-05 13:29:36 +0200525}