1 : /*
2 : * Copyright (C) 2004, 2005, 2006 James Antill
3 : *
4 : * This library is free software; you can redistribute it and/or
5 : * modify it under the terms of the GNU Lesser General Public
6 : * License as published by the Free Software Foundation; either
7 : * version 2 of the License, or (at your option) any later version.
8 : *
9 : * This library is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : * Lesser General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU Lesser General Public
15 : * License along with this library; if not, write to the Free Software
16 : * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 : *
18 : * email: james@and.org
19 : */
20 : /* Vectored logging APIs */
21 :
22 : #define CONF_USE_HEXDUMP TRUE
23 :
24 : #define _GNU_SOURCE 1
25 :
26 : #include <vstr.h>
27 :
28 : #include <stdlib.h>
29 : #include <unistd.h>
30 : #include <syslog.h>
31 : #include <err.h>
32 :
33 : #include <sys/types.h>
34 : #include <sys/stat.h>
35 : #include <fcntl.h>
36 :
37 : #include <sys/socket.h>
38 : #include <arpa/inet.h>
39 : #include <netinet/in.h>
40 : #include <sys/un.h>
41 :
42 : #include <limits.h>
43 :
44 : #include <signal.h>
45 :
46 :
47 : #define VLG_COMPILE_INLINE 0
48 : #include "vlg.h"
49 :
50 : /* FIXME: could possibly work on other OSes ? */
51 : #ifdef __linux__
52 : # include <sys/mount.h>
53 : #endif
54 :
55 : #ifdef MS_BIND
56 : # define CONF_USE_MOUNT_BIND TRUE
57 : # define BIND_MOUNT(x, y) mount(x, y, "", MS_BIND, "")
58 : #else
59 : # define BIND_MOUNT(x, y) -1 /* do nothing */
60 : # define CONF_USE_MOUNT_BIND FALSE
61 : #endif
62 :
63 : #define CONF_USE_INTERNAL_SYSLOG TRUE
64 :
65 : #define EX_UTILS_NO_FUNCS 1
66 : #include "ex_utils.h"
67 :
68 : /* how much memory should we preallocate so it's "unlikely" we'll get mem errors
69 : * when writting a log entry */
70 : #define VLG_MEM_PREALLOC (4 * 1024)
71 :
72 : #ifndef FALSE
73 : # define FALSE 0
74 : #endif
75 :
76 : #ifndef TRUE
77 : # define TRUE 1
78 : #endif
79 :
80 : #if CONF_USE_HEXDUMP
81 : # include "hexdump.h"
82 : #else
83 : # define ex_hexdump_reset() /* do nothing */
84 : # define ex_hexdump_process(x1, x2, x3, x4, x5, x6, x7, x8, x9) FALSE
85 : #endif
86 :
87 : static Vstr_conf *vlg__conf = NULL;
88 : static Vstr_conf *vlg__sig_conf = NULL;
89 : static int vlg__done_syslog_init = FALSE;
90 :
91 :
92 : static int vlg__syslog_con(Vlg *vlg, int alt)
93 0 : {
94 0 : int type = alt ? SOCK_STREAM : SOCK_DGRAM;
95 0 : int fd = -1;
96 0 : const char *fname = _PATH_LOG;
97 0 : size_t len = strlen(fname) + 1;
98 : struct sockaddr_un tmp_sun;
99 0 : struct sockaddr *sa = (struct sockaddr *)&tmp_sun;
100 0 : socklen_t alloc_len = 0;
101 :
102 : if (!CONF_USE_INTERNAL_SYSLOG)
103 : goto conf_fail;
104 0 : if (vlg->syslog_fd != -1)
105 0 : return (TRUE);
106 :
107 0 : tmp_sun.sun_path[0] = 0;
108 0 : alloc_len = SUN_LEN(&tmp_sun) + len;
109 0 : tmp_sun.sun_family = AF_LOCAL;
110 0 : memcpy(tmp_sun.sun_path, fname, len);
111 :
112 0 : if (vlg->syslog_stream)
113 0 : type = alt ? SOCK_DGRAM : SOCK_STREAM;
114 :
115 0 : if ((fd = socket(PF_LOCAL, type, 0)) == -1)
116 0 : goto sock_fail;
117 :
118 0 : if (fcntl(fd, F_SETFD, TRUE) == -1)
119 0 : goto fcntl_fail;
120 :
121 0 : if (connect(fd, sa, alloc_len) == -1)
122 0 : goto connect_fail;
123 :
124 0 : vlg->syslog_fd = fd;
125 :
126 0 : return (TRUE);
127 :
128 0 : connect_fail:
129 0 : if (!alt)
130 0 : return (vlg__syslog_con(vlg, TRUE));
131 :
132 0 : fcntl_fail:
133 0 : close(fd);
134 0 : sock_fail:
135 0 : conf_fail:
136 0 : if (!vlg__done_syslog_init)
137 0 : openlog(vlg->prog_name, LOG_PID | LOG_NDELAY, vlg->syslog_facility);
138 0 : vlg__done_syslog_init = TRUE;
139 0 : return (FALSE);
140 : }
141 :
142 : static void vlg__syslog_close(Vlg *vlg)
143 0 : {
144 0 : if (vlg->syslog_fd != -1)
145 0 : close(vlg->syslog_fd);
146 0 : vlg->syslog_fd = -1;
147 0 : }
148 :
149 : static void vlg__flush(Vlg *vlg, int type, int out_err)
150 67913 : {
151 67913 : Vstr_base *dlg = vlg->out_vstr;
152 67913 : time_t now = (*vlg->tm_get)();
153 67913 : const char *tm_data = NULL;
154 :
155 33971 : ASSERT(vstr_export_chr(dlg, dlg->len) == '\n');
156 33971 : ASSERT((vlg->date_fmt_type == VLG_DATE_FMT_SYSLOG_TRAD) ||
157 : (vlg->date_fmt_type == VLG_DATE_FMT_SYSLOG_YR));
158 :
159 67913 : if (vlg->daemon_mode)
160 : {
161 0 : if (vlg->log_syslog_native || !vlg__syslog_con(vlg, 0))
162 : {
163 0 : const char *tmp = NULL;
164 :
165 0 : if (vlg->log_max_sz && (dlg->len > vlg->log_max_sz))
166 : { /* note that this _just_ does the message */
167 0 : size_t rm = (dlg->len - vlg->log_max_sz) + strlen("...\n");
168 :
169 0 : vstr_sc_reduce(dlg, 1, dlg->len, rm);
170 0 : vstr_add_cstr_ptr(dlg, dlg->len, "...\n");
171 : }
172 :
173 0 : if (!(tmp = vstr_export_cstr_ptr(dlg, 1, dlg->len - 1))) /* remove \n */
174 0 : errno = ENOMEM, err(EXIT_FAILURE, "vlog__flush");
175 :
176 0 : syslog(type | vlg->syslog_facility, "%s", tmp);
177 : }
178 : else
179 : {
180 0 : pid_t pid = getpid();
181 0 : int fd = vlg->syslog_fd;
182 0 : size_t beg_len = 0;
183 :
184 : /* syslog doesn't like years, so don't let that happen atm. */
185 0 : tm_data = date_syslog_trad(vlg->dt, now);
186 :
187 0 : vstr_add_fmt(dlg, 0, "<%u>%s %s[%lu]: ", type | vlg->syslog_facility,
188 : tm_data, vlg->prog_name, (unsigned long)pid);
189 :
190 0 : if (vlg->syslog_stream)
191 0 : vstr_sub_buf(dlg, dlg->len, 1, "", 1);
192 : else
193 0 : vstr_sc_reduce(dlg, 1, dlg->len, 1); /* remove "\n" */
194 :
195 0 : if (vlg->log_max_sz && (dlg->len > vlg->log_max_sz))
196 : {
197 0 : size_t rm = (dlg->len - vlg->log_max_sz) + strlen("...\n");
198 :
199 0 : vstr_sc_reduce(dlg, 1, dlg->len, rm);
200 0 : vstr_add_cstr_ptr(dlg, dlg->len, "...\n");
201 : }
202 :
203 0 : if (dlg->conf->malloc_bad)
204 0 : errno = ENOMEM, err(EXIT_FAILURE, "vlog__flush");
205 :
206 0 : beg_len = dlg->len;
207 0 : while (dlg->len)
208 0 : if (!vstr_sc_write_fd(dlg, 1, dlg->len, fd, NULL) && (errno != EAGAIN))
209 : {
210 0 : vlg__syslog_close(vlg);
211 0 : if (beg_len != dlg->len) /* we have sent _some_ data, and it died */
212 0 : break;
213 0 : if (!vlg__syslog_con(vlg, 0))
214 0 : err(EXIT_FAILURE, "vlg__syslog_con");
215 : }
216 : }
217 :
218 0 : vstr_del(dlg, 1, dlg->len);
219 : }
220 : else
221 : {
222 67913 : int fd = out_err ? STDERR_FILENO : STDOUT_FILENO;
223 :
224 67913 : if (vlg->log_prefix_console)
225 : {
226 : /* Note: we add the begining backwards, it's easier that way */
227 67913 : if (type == LOG_WARNING) vstr_add_cstr_ptr(dlg, 0, "WARN: ");
228 67913 : if (type == LOG_ALERT) vstr_add_cstr_ptr(dlg, 0, "ERR: ");
229 67913 : if (type == LOG_DEBUG) vstr_add_cstr_ptr(dlg, 0, "DEBUG: ");
230 :
231 67913 : if (!vlg->log_pid)
232 62576 : vstr_add_cstr_ptr(dlg, 0, "]: ");
233 : else
234 5337 : vstr_add_fmt(dlg, 0, "] %lu: ", (unsigned long)getpid());
235 :
236 67913 : tm_data = date_syslog_yr(vlg->dt, now);
237 67913 : vstr_add_cstr_ptr(dlg, 0, tm_data);
238 67913 : vstr_add_cstr_ptr(dlg, 0, "[");
239 : }
240 :
241 67913 : if (vlg->log_max_sz && (dlg->len > vlg->log_max_sz))
242 : {
243 0 : size_t rm = (dlg->len - vlg->log_max_sz) + strlen("...\n");
244 :
245 0 : vstr_sc_reduce(dlg, 1, dlg->len, rm);
246 0 : vstr_add_cstr_ptr(dlg, dlg->len, "...\n");
247 : }
248 :
249 67913 : if (dlg->conf->malloc_bad)
250 0 : errno = ENOMEM, err(EXIT_FAILURE, "vlog__flush");
251 :
252 169768 : while (dlg->len)
253 67913 : if (!vstr_sc_write_fd(dlg, 1, dlg->len, fd, NULL) && (errno != EAGAIN))
254 0 : err(EXIT_FAILURE, "vlg__flush");
255 : }
256 67913 : }
257 :
258 : static void vlg__add_chk_flush(Vlg *vlg, const char *fmt, va_list ap,
259 : int type, int out_err)
260 205690 : {
261 205690 : Vstr_base *dlg = vlg->out_vstr;
262 :
263 205690 : if (!vstr_add_vfmt(dlg, dlg->len, fmt, ap))
264 : {
265 0 : if (dlg->conf->malloc_bad)
266 0 : errno = ENOMEM, err(EXIT_FAILURE, "chk_flush");
267 0 : return;
268 : }
269 :
270 205690 : if (vstr_export_chr(dlg, dlg->len) == '\n')
271 67913 : vlg__flush(vlg, type, out_err);
272 : }
273 :
274 :
275 : /* because vlg goes away quickly it's likely we'll want to just use _BUF_PTR
276 : * for Vstr data to save the copying.
277 : * Note that if we have ability to go async in vlg, we can tweak this so it
278 : * "just works" for the user. */
279 : static int vlg__fmt__add_vstr_add_vstr(Vstr_base *base, size_t pos,
280 : Vstr_fmt_spec *spec)
281 128 : {
282 128 : Vstr_base *sf = VSTR_FMT_CB_ARG_PTR(spec, 0);
283 128 : size_t sf_pos = VSTR_FMT_CB_ARG_VAL(spec, size_t, 1);
284 128 : size_t sf_len = VSTR_FMT_CB_ARG_VAL(spec, size_t, 2);
285 128 : unsigned int sf_flags = VSTR_TYPE_ADD_BUF_PTR; /* OK because it's "sync" */
286 :
287 128 : if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &sf_len,
288 : VSTR_FLAG_SC_FMT_CB_BEG_OBJ_STR))
289 0 : return (FALSE);
290 :
291 128 : if (!vstr_add_vstr(base, pos, sf, sf_pos, sf_len, sf_flags))
292 0 : return (FALSE);
293 :
294 128 : if (!vstr_sc_fmt_cb_end(base, pos, spec, sf_len))
295 0 : return (FALSE);
296 :
297 128 : return (TRUE);
298 : }
299 :
300 : static int vlg__fmt_add_vstr_add_vstr(Vstr_conf *conf, const char *name)
301 4592 : {
302 4592 : return (vstr_fmt_add(conf, name, vlg__fmt__add_vstr_add_vstr,
303 : VSTR_TYPE_FMT_PTR_VOID,
304 : VSTR_TYPE_FMT_SIZE_T,
305 : VSTR_TYPE_FMT_SIZE_T,
306 : VSTR_TYPE_FMT_END));
307 : }
308 :
309 : /* because you have to do (size_t)1 for varargs it's annoying when you want
310 : the entire Vstr ... helper */
311 : static int vlg__fmt__add_vstr_add_all_vstr(Vstr_base *base, size_t pos,
312 : Vstr_fmt_spec *spec)
313 988 : {
314 988 : Vstr_base *sf = VSTR_FMT_CB_ARG_PTR(spec, 0);
315 988 : size_t sf_pos = 1;
316 988 : size_t sf_len = sf->len;
317 988 : unsigned int sf_flags = VSTR_TYPE_ADD_BUF_PTR; /* OK because it's "sync" */
318 :
319 988 : if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &sf_len,
320 : VSTR_FLAG_SC_FMT_CB_BEG_OBJ_STR))
321 0 : return (FALSE);
322 :
323 988 : if (!vstr_add_vstr(base, pos, sf, sf_pos, sf_len, sf_flags))
324 0 : return (FALSE);
325 :
326 988 : if (!vstr_sc_fmt_cb_end(base, pos, spec, sf_len))
327 0 : return (FALSE);
328 :
329 988 : return (TRUE);
330 : }
331 :
332 : static int vlg__fmt_add_vstr_add_all_vstr(Vstr_conf *conf, const char *name)
333 4592 : {
334 4592 : return (vstr_fmt_add(conf, name, vlg__fmt__add_vstr_add_all_vstr,
335 : VSTR_TYPE_FMT_PTR_VOID,
336 : VSTR_TYPE_FMT_END));
337 : }
338 :
339 : /* also a helper for printing sects --
340 : * should probably have some in Vstr itself */
341 : static int vlg__fmt__add_vstr_add_sect_vstr(Vstr_base *base, size_t pos,
342 : Vstr_fmt_spec *spec)
343 60466 : {
344 60466 : Vstr_base *sf = VSTR_FMT_CB_ARG_PTR(spec, 0);
345 60466 : Vstr_sects *sects = VSTR_FMT_CB_ARG_PTR(spec, 1);
346 60466 : unsigned int num = VSTR_FMT_CB_ARG_VAL(spec, unsigned int, 2);
347 60466 : size_t sf_pos = VSTR_SECTS_NUM(sects, num)->pos;
348 60466 : size_t sf_len = VSTR_SECTS_NUM(sects, num)->len;
349 60466 : unsigned int sf_flags = VSTR_TYPE_ADD_BUF_PTR; /* OK because it's "sync" */
350 :
351 60466 : if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &sf_len,
352 : VSTR_FLAG_SC_FMT_CB_BEG_OBJ_STR))
353 0 : return (FALSE);
354 :
355 60466 : if (!vstr_add_vstr(base, pos, sf, sf_pos, sf_len, sf_flags))
356 0 : return (FALSE);
357 :
358 60466 : if (!vstr_sc_fmt_cb_end(base, pos, spec, sf_len))
359 0 : return (FALSE);
360 :
361 60466 : return (TRUE);
362 : }
363 :
364 : static int vlg__fmt_add_vstr_add_sect_vstr(Vstr_conf *conf, const char *name)
365 4592 : {
366 4592 : return (vstr_fmt_add(conf, name, vlg__fmt__add_vstr_add_sect_vstr,
367 : VSTR_TYPE_FMT_PTR_VOID,
368 : VSTR_TYPE_FMT_PTR_VOID,
369 : VSTR_TYPE_FMT_UINT,
370 : VSTR_TYPE_FMT_END));
371 : }
372 :
373 : static int vlg__fmt__add_vstr_add_hexdump_vstr(Vstr_base *base, size_t pos,
374 : Vstr_fmt_spec *spec)
375 128 : {
376 128 : Vstr_base *sf = VSTR_FMT_CB_ARG_PTR(spec, 0);
377 128 : size_t sf_pos = VSTR_FMT_CB_ARG_VAL(spec, size_t, 1);
378 128 : size_t sf_len = VSTR_FMT_CB_ARG_VAL(spec, size_t, 2);
379 128 : size_t orig_len = base->len;
380 :
381 128 : ex_hexdump_reset();
382 128 : ex_hexdump_process(base, pos, sf, sf_pos, sf_len, PRNT_NONE,
383 : UINT_MAX, FALSE, TRUE);
384 128 : if (base->conf->malloc_bad)
385 0 : return (FALSE);
386 :
387 128 : sf_len = base->len - orig_len;
388 :
389 128 : if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &sf_len,
390 : VSTR_FLAG_SC_FMT_CB_BEG_OBJ_ATOM))
391 0 : return (FALSE);
392 :
393 128 : if (!vstr_sc_fmt_cb_end(base, pos, spec, sf_len))
394 0 : return (FALSE);
395 :
396 128 : return (TRUE);
397 : }
398 :
399 : static int vlg__fmt_add_vstr_add_hexdump_vstr(Vstr_conf *conf, const char *name)
400 4592 : {
401 4592 : return (vstr_fmt_add(conf, name, vlg__fmt__add_vstr_add_hexdump_vstr,
402 : VSTR_TYPE_FMT_PTR_VOID,
403 : VSTR_TYPE_FMT_SIZE_T,
404 : VSTR_TYPE_FMT_SIZE_T,
405 : VSTR_TYPE_FMT_END));
406 : }
407 :
408 : /* easy way to print socket options... */
409 : static int vlg__fmt__add_vstr_add_sockopt_s(Vstr_base *base, size_t pos,
410 : Vstr_fmt_spec *spec)
411 16 : {
412 16 : int fd = VSTR_FMT_CB_ARG_VAL(spec, int, 0);
413 16 : int level = VSTR_FMT_CB_ARG_VAL(spec, int, 1);
414 16 : int optname = VSTR_FMT_CB_ARG_VAL(spec, int, 2);
415 : char buf[1024];
416 16 : const char *ptr = buf;
417 16 : size_t obj_len = 0;
418 :
419 16 : if (!optname) /* assume 0 is invalid */
420 : {
421 0 : ptr = "<unknown>";
422 0 : obj_len = strlen(ptr);
423 : }
424 : else
425 : {
426 16 : socklen_t len = sizeof(buf);
427 16 : int ret = -1;
428 :
429 16 : if ((ret = getsockopt(fd, level, optname, buf, &len)) != -1)
430 16 : obj_len = len;
431 : else
432 : {
433 0 : ptr = "<error>";
434 0 : obj_len = strlen(ptr);
435 : }
436 : }
437 :
438 16 : if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &obj_len,
439 : VSTR_FLAG_SC_FMT_CB_BEG_OBJ_STR))
440 0 : return (FALSE);
441 :
442 16 : if (!vstr_add_buf(base, pos, ptr, obj_len))
443 0 : return (FALSE);
444 :
445 16 : if (!vstr_sc_fmt_cb_end(base, pos, spec, obj_len))
446 0 : return (FALSE);
447 :
448 16 : return (TRUE);
449 : }
450 :
451 : static int vlg__fmt_add_vstr_add_sockopt_s(Vstr_conf *conf, const char *name)
452 4592 : {
453 4592 : return (vstr_fmt_add(conf, name, vlg__fmt__add_vstr_add_sockopt_s,
454 : VSTR_TYPE_FMT_INT,
455 : VSTR_TYPE_FMT_INT,
456 : VSTR_TYPE_FMT_INT,
457 : VSTR_TYPE_FMT_END));
458 : }
459 :
460 : /* also a helper for printing any network address */
461 : static int vlg__fmt__add_vstr_add_sa(Vstr_base *base, size_t pos,
462 : Vstr_fmt_spec *spec)
463 68129 : {
464 68129 : struct sockaddr *sa = VSTR_FMT_CB_ARG_PTR(spec, 0);
465 68129 : size_t obj_len = 0;
466 : char buf1[128 + 1];
467 : char buf2[sizeof(short) * CHAR_BIT + 1];
468 68129 : const char *ptr1 = NULL;
469 68129 : size_t len1 = 0;
470 68129 : const char *ptr2 = NULL;
471 68129 : size_t len2 = 0;
472 :
473 : assert(sizeof(buf1) >= INET_ADDRSTRLEN);
474 : assert(sizeof(buf1) >= INET6_ADDRSTRLEN);
475 :
476 68129 : if (!sa)
477 : {
478 8 : ptr1 = "<none>";
479 8 : len1 = strlen(ptr1);
480 : }
481 : else
482 68121 : switch (sa->sa_family)
483 : {
484 : case AF_INET:
485 : {
486 67141 : struct sockaddr_in *sin4 = (void *)sa;
487 67141 : ptr1 = inet_ntop(AF_INET, &sin4->sin_addr, buf1, sizeof(buf1));
488 67141 : if (!ptr1) ptr1 = "<unknown>";
489 67141 : len1 = strlen(ptr1);
490 67141 : ptr2 = buf2;
491 67141 : len2 = vstr_sc_conv_num10_uint(buf2, sizeof(buf2), ntohs(sin4->sin_port));
492 : }
493 67141 : break;
494 :
495 : case AF_INET6:
496 : {
497 0 : struct sockaddr_in6 *sin6 = (void *)sa;
498 0 : ptr1 = inet_ntop(AF_INET6, &sin6->sin6_addr, buf1, sizeof(buf1));
499 0 : if (!ptr1) ptr1 = "<unknown>";
500 0 : len1 = strlen(ptr1);
501 0 : ptr2 = buf2;
502 0 : len2 = vstr_sc_conv_num10_uint(buf2,sizeof(buf2), ntohs(sin6->sin6_port));
503 : }
504 0 : break;
505 :
506 : case AF_LOCAL:
507 : { /* struct sockaddr_un *sun = (void *)sa; */
508 980 : struct sockaddr_un *sun = (void *)sa;
509 980 : ptr1 = "local";
510 980 : len1 = strlen(ptr1);
511 980 : ptr2 = sun->sun_path;
512 980 : len2 = strlen(ptr2);
513 : }
514 490 : break;
515 :
516 0 : default: ASSERT_NOT_REACHED();
517 : }
518 :
519 68129 : obj_len = len1 + !!len2 + len2;
520 :
521 68129 : if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &obj_len,
522 : VSTR_FLAG_SC_FMT_CB_BEG_OBJ_ATOM))
523 0 : return (FALSE);
524 34091 : ASSERT(obj_len == (len1 + !!len2 + len2));
525 :
526 68129 : if (!vstr_add_buf(base, pos, ptr1, len1))
527 0 : return (FALSE);
528 68129 : if (ptr2 && (!vstr_add_rep_chr(base, pos + len1, '@', 1) ||
529 : !vstr_add_buf( base, pos + len1 + 1, ptr2, len2)))
530 0 : return (FALSE);
531 :
532 68129 : if (!vstr_sc_fmt_cb_end(base, pos, spec, obj_len))
533 0 : return (FALSE);
534 :
535 68129 : return (TRUE);
536 : }
537 :
538 : static int vlg__fmt_add_vstr_add_sa(Vstr_conf *conf, const char *name)
539 4592 : {
540 4592 : return (vstr_fmt_add(conf, name, vlg__fmt__add_vstr_add_sa,
541 : VSTR_TYPE_FMT_PTR_VOID,
542 : VSTR_TYPE_FMT_END));
543 : }
544 :
545 : int vlg_sc_fmt_add_all(Vstr_conf *conf)
546 656 : {
547 656 : return (VSTR_SC_FMT_ADD(conf, vlg__fmt_add_vstr_add_vstr,
548 : "<vstr", "p%zu%zu", ">") &&
549 : VSTR_SC_FMT_ADD(conf, vlg__fmt_add_vstr_add_all_vstr,
550 : "<vstr.all", "p", ">") &&
551 : VSTR_SC_FMT_ADD(conf, vlg__fmt_add_vstr_add_hexdump_vstr,
552 : "<vstr.hexdump", "p%zu%zu", ">") &&
553 : VSTR_SC_FMT_ADD(conf, vlg__fmt_add_vstr_add_sect_vstr,
554 : "<vstr.sect", "p%p%u", ">") &&
555 : VSTR_SC_FMT_ADD(conf, vlg__fmt_add_vstr_add_sa,
556 : "<sa", "p", ">") &&
557 : VSTR_SC_FMT_ADD(conf, vlg__fmt_add_vstr_add_sockopt_s,
558 : "<sockopt.s", "d%d%d", ">"));
559 : }
560 :
561 : static void vlg__mkdir_p(const char *dst, Vstr_base *tmp, size_t len)
562 0 : {
563 0 : const char *bn = strrchr(dst, '/');
564 :
565 0 : ASSERT(strlen(dst) == len);
566 :
567 0 : if (!bn || (bn == dst))
568 0 : err(EXIT_FAILURE, "stat(%s)", dst);
569 :
570 0 : len -= strlen(bn);
571 0 : if ((dst = vstr_export_cstr_ptr(tmp, 1, len)))
572 : {
573 0 : if (mkdir(dst, 0700) == -1 && (errno == ENOENT))
574 : {
575 0 : vlg__mkdir_p(dst, tmp, len);
576 0 : if ((dst = vstr_export_cstr_ptr(tmp, 1, len)))
577 0 : mkdir(dst, 0700);
578 : }
579 : }
580 0 : }
581 :
582 : void vlg_sc_bind_mount(const char *chroot_dir)
583 0 : { /* make sure we can reconnect to syslog */
584 0 : Vstr_base *tmp = NULL;
585 0 : const char *src = _PATH_LOG;
586 0 : const char *dst = NULL;
587 : struct stat64 st_src[1];
588 : struct stat64 st_dst[1];
589 :
590 0 : if (!CONF_USE_MOUNT_BIND || !chroot_dir)
591 0 : return;
592 :
593 0 : if (!(tmp = vstr_make_base(NULL)))
594 0 : errno = ENOMEM, err(EXIT_FAILURE, "bind-mount");
595 :
596 0 : vstr_add_fmt(tmp, 0, "%s%s", chroot_dir, _PATH_LOG);
597 0 : dst = vstr_export_cstr_ptr(tmp, 1, tmp->len);
598 0 : if (tmp->conf->malloc_bad)
599 0 : errno = ENOMEM, err(EXIT_FAILURE, "bind-mount");
600 :
601 0 : if (stat64(src, st_src) == -1)
602 0 : err(EXIT_FAILURE, "stat(%s)", src);
603 :
604 0 : if (stat64(dst, st_dst) == -1)
605 : { /* if it fails, try creating the X in /path/X chroot file... */
606 0 : vlg__mkdir_p(dst, tmp, tmp->len);
607 :
608 0 : if ((dst = vstr_export_cstr_ptr(tmp, 1, tmp->len)))
609 : {
610 0 : int fd = open(dst, O_TRUNC | O_CREAT | O_EXCL, 0600);
611 0 : if (fd != -1)
612 0 : close(fd);
613 : }
614 :
615 : /* try again, and fail this time... */
616 0 : if (stat64(dst, st_dst) == -1)
617 0 : err(EXIT_FAILURE, "stat(%s)", dst);
618 : }
619 :
620 0 : if ((st_src->st_ino != st_dst->st_ino) ||
621 : (st_src->st_dev != st_dst->st_dev))
622 : {
623 0 : umount(dst); /* NOTE: You can't bind mount over a bind mount,
624 : * so if syslog is restarted we need to try this */
625 0 : if (BIND_MOUNT(src, dst) == -1)
626 0 : err(EXIT_FAILURE, "bind-mount(%s, %s)", src, dst);
627 : }
628 :
629 0 : vstr_free_base(tmp);
630 : }
631 :
632 : void vlg_init(void)
633 296 : {
634 296 : unsigned int buf_sz = 0;
635 :
636 296 : if (!(vlg__conf = vstr_make_conf()))
637 0 : goto malloc_err_vstr_conf;
638 296 : if (!(vlg__sig_conf = vstr_make_conf()))
639 0 : goto malloc_err_vstr_conf;
640 :
641 296 : if (!vstr_cntl_conf(vlg__conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '$') ||
642 : !vstr_cntl_conf(vlg__conf, VSTR_CNTL_CONF_SET_LOC_CSTR_THOU_SEP, "_") ||
643 : !vstr_cntl_conf(vlg__conf, VSTR_CNTL_CONF_SET_LOC_CSTR_THOU_GRP, "\3") ||
644 : !vstr_sc_fmt_add_all(vlg__conf) ||
645 : !vlg_sc_fmt_add_all(vlg__conf) ||
646 : FALSE)
647 0 : goto malloc_err_vstr_fmt_all;
648 296 : if (!vstr_cntl_conf(vlg__sig_conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '$') ||
649 : !vstr_cntl_conf(vlg__sig_conf, VSTR_CNTL_CONF_SET_LOC_CSTR_THOU_SEP,"_")||
650 : !vstr_cntl_conf(vlg__sig_conf,VSTR_CNTL_CONF_SET_LOC_CSTR_THOU_GRP,"\3")||
651 : !vstr_sc_fmt_add_all(vlg__sig_conf) ||
652 : !vlg_sc_fmt_add_all(vlg__sig_conf) ||
653 : FALSE)
654 0 : goto malloc_err_vstr_fmt_all;
655 :
656 296 : vstr_cntl_conf(vlg__conf, VSTR_CNTL_CONF_GET_NUM_BUF_SZ, &buf_sz);
657 296 : vstr_cntl_conf(vlg__sig_conf, VSTR_CNTL_CONF_GET_NUM_BUF_SZ, &buf_sz);
658 :
659 : /* don't bother with _NON nodes */
660 296 : if (!vstr_make_spare_nodes(vlg__conf, VSTR_TYPE_NODE_BUF,
661 : (VLG_MEM_PREALLOC / buf_sz) + 1) ||
662 : !vstr_make_spare_nodes(vlg__conf, VSTR_TYPE_NODE_PTR,
663 : (VLG_MEM_PREALLOC / buf_sz) + 1) ||
664 : !vstr_make_spare_nodes(vlg__conf, VSTR_TYPE_NODE_REF,
665 : (VLG_MEM_PREALLOC / buf_sz) + 1))
666 : goto malloc_err_vstr_spare;
667 296 : if (!vstr_make_spare_nodes(vlg__sig_conf, VSTR_TYPE_NODE_BUF,
668 : (VLG_MEM_PREALLOC / buf_sz) + 1) ||
669 : !vstr_make_spare_nodes(vlg__sig_conf, VSTR_TYPE_NODE_PTR,
670 : (VLG_MEM_PREALLOC / buf_sz) + 1) ||
671 : !vstr_make_spare_nodes(vlg__sig_conf, VSTR_TYPE_NODE_REF,
672 : (VLG_MEM_PREALLOC / buf_sz) + 1))
673 : goto malloc_err_vstr_spare;
674 :
675 : return;
676 :
677 0 : malloc_err_vstr_spare:
678 0 : malloc_err_vstr_fmt_all:
679 0 : vstr_free_conf(vlg__conf);
680 0 : vstr_free_conf(vlg__sig_conf);
681 0 : malloc_err_vstr_conf:
682 0 : errno = ENOMEM; err(EXIT_FAILURE, "vlg_init");
683 : }
684 :
685 : void vlg_exit(void)
686 276 : {
687 276 : if (vlg__done_syslog_init)
688 0 : closelog();
689 :
690 276 : vstr_free_conf(vlg__conf); vlg__conf = NULL;
691 276 : vstr_free_conf(vlg__sig_conf); vlg__sig_conf = NULL;
692 276 : }
693 :
694 : static time_t vlg__tm_get(void)
695 0 : {
696 0 : return time(NULL);
697 : }
698 :
699 : Vlg *vlg_make(void)
700 296 : {
701 296 : Vlg *vlg = malloc(sizeof(Vlg));
702 :
703 296 : if (!vlg)
704 0 : goto malloc_err_vlg;
705 :
706 296 : if (!(vlg->out_vstr = vstr_make_base(vlg__conf)))
707 0 : goto malloc_err_vstr_base;
708 :
709 296 : if (!(vlg->sig_out_vstr = vstr_make_base(vlg__sig_conf)))
710 0 : goto malloc_err_sig_vstr_base;
711 :
712 296 : if (!(vlg->dt = date_make()))
713 0 : goto malloc_err_date_store;
714 :
715 296 : if (!(vlg->sig_dt = date_make()))
716 0 : goto malloc_err_sig_date_store;
717 :
718 296 : vlg->prog_name = NULL;
719 296 : vlg->syslog_fd = -1;
720 :
721 296 : vlg->tm_get = vlg__tm_get;
722 :
723 296 : vlg->syslog_facility = LOG_DAEMON;
724 :
725 296 : vlg->log_max_sz = 0; /* no size limit */
726 :
727 296 : vlg->syslog_stream = FALSE;
728 296 : vlg->log_pid = FALSE;
729 296 : vlg->out_dbg = 0;
730 296 : vlg->daemon_mode = FALSE;
731 296 : vlg->log_prefix_console = TRUE;
732 296 : vlg->date_fmt_type = VLG_DATE_FMT_SYSLOG_TRAD;
733 :
734 296 : return (vlg);
735 :
736 0 : malloc_err_sig_date_store:
737 0 : date_free(vlg->dt);
738 0 : malloc_err_date_store:
739 0 : vstr_free_base(vlg->sig_out_vstr);
740 0 : malloc_err_sig_vstr_base:
741 0 : vstr_free_base(vlg->out_vstr);
742 0 : malloc_err_vstr_base:
743 0 : free(vlg);
744 0 : malloc_err_vlg:
745 :
746 0 : return (NULL);
747 : }
748 :
749 : /* don't actually free ... this shouldn't happen until exit time anyway */
750 : void vlg_free(Vlg *vlg)
751 276 : {
752 276 : vstr_free_base(vlg->out_vstr); vlg->out_vstr = NULL;
753 276 : vstr_free_base(vlg->sig_out_vstr); vlg->sig_out_vstr = NULL;
754 276 : date_free(vlg->dt); vlg->dt = NULL;
755 276 : date_free(vlg->sig_dt); vlg->sig_dt = NULL;
756 276 : }
757 :
758 : void vlg_daemon(Vlg *vlg, const char *name)
759 0 : {
760 0 : ASSERT(name);
761 :
762 0 : vlg->prog_name = name;
763 0 : vlg->log_pid = TRUE;
764 0 : vlg->daemon_mode = TRUE;
765 :
766 0 : vlg__syslog_con(vlg, 0);
767 0 : }
768 :
769 : void vlg_debug(Vlg *vlg)
770 6 : {
771 6 : if (vlg->out_dbg >= 3)
772 0 : return;
773 :
774 6 : ++vlg->out_dbg;
775 : }
776 :
777 : void vlg_undbg(Vlg *vlg)
778 0 : {
779 0 : if (!vlg->out_dbg)
780 0 : return;
781 :
782 0 : --vlg->out_dbg;
783 : }
784 :
785 : int vlg_pid_set(Vlg *vlg, int pid)
786 4 : {
787 4 : vlg->log_pid = !!pid;
788 :
789 4 : return (TRUE);
790 : }
791 :
792 : int vlg_prefix_set(Vlg *vlg, int prefix)
793 0 : {
794 0 : vlg->log_prefix_console = prefix;
795 0 : return (TRUE);
796 : }
797 :
798 : #if 0 /* syslog doesn't allow this... */
799 : int vlg_date_set(Vlg *vlg, unsigned int date_fmt)
800 : {
801 : ASSERT((vlg->date_fmt_type == VLG_DATE_FMT_SYSLOG_TRAD) ||
802 : (vlg->date_fmt_type == VLG_DATE_FMT_SYSLOG_YR));
803 : ASSERT((date_fmt == VLG_DATE_FMT_SYSLOG_TRAD) ||
804 : (date_fmt == VLG_DATE_FMT_SYSLOG_YR));
805 :
806 : if ((date_fmt != VLG_DATE_FMT_SYSLOG_TRAD) &&
807 : (date_fmt != VLG_DATE_FMT_SYSLOG_YR))
808 : return (FALSE);
809 :
810 : vlg->date_fmt_type = date_fmt;
811 :
812 : return (TRUE);
813 : }
814 : #endif
815 :
816 : int vlg_syslog_native_set(Vlg *vlg, int prefix)
817 40 : {
818 40 : vlg->log_syslog_native = prefix;
819 40 : return (TRUE);
820 : }
821 :
822 : int vlg_syslog_facility_set(Vlg *vlg, int fac)
823 40 : {
824 40 : vlg->syslog_facility = fac;
825 40 : return (TRUE);
826 : }
827 :
828 : int vlg_size_set(Vlg *vlg, size_t sz)
829 40 : {
830 40 : if (sz && (sz <= 3))
831 0 : return (FALSE);
832 :
833 40 : vlg->log_max_sz = sz;
834 :
835 40 : return (TRUE);
836 : }
837 :
838 : int vlg_time_set(Vlg *vlg, time_t (*func)(void))
839 64 : {
840 64 : vlg->tm_get = func;
841 64 : return (TRUE);
842 : }
843 :
844 : void vlg_pid_file(Vlg *vlg, const char *pid_file)
845 20 : {
846 20 : Vstr_base *out = vlg->out_vstr;
847 :
848 20 : if (out->len)
849 0 : vlg_err(vlg, EXIT_FAILURE, "Data in vlg for pid_file\n");
850 :
851 20 : if (!vstr_add_fmt(out, out->len, "%lu\n", (unsigned long)getpid()))
852 0 : vlg_err(vlg, EXIT_FAILURE, "vlg_pid_file: %m\n");
853 :
854 20 : if (!vstr_sc_write_file(out, 1, out->len,
855 : pid_file, O_WRONLY | O_CREAT | O_TRUNC, 0644, 0,NULL))
856 : {
857 0 : vstr_del(out, 1, out->len);
858 0 : vlg_err(vlg, EXIT_FAILURE, "vlg_pid_file(%s): %m\n", pid_file);
859 : }
860 20 : }
861 :
862 : /* ================== actual logging functions ================== */
863 :
864 : /* ---------- va_list ---------- */
865 : void vlg_vabort(Vlg *vlg, const char *fmt, va_list ap)
866 0 : {
867 0 : vlg__add_chk_flush(vlg, fmt, ap, LOG_ALERT, TRUE);
868 0 : abort();
869 : }
870 :
871 : void vlg_verr(Vlg *vlg, int exit_code, const char *fmt, va_list ap)
872 0 : {
873 0 : vlg__add_chk_flush(vlg, fmt, ap, LOG_ALERT, TRUE);
874 0 : _exit(exit_code);
875 : }
876 :
877 : void vlg_vwarn(Vlg *vlg, const char *fmt, va_list ap)
878 28 : {
879 28 : vlg__add_chk_flush(vlg, fmt, ap, LOG_WARNING, TRUE);
880 28 : }
881 :
882 : void vlg_vinfo(Vlg *vlg, const char *fmt, va_list ap)
883 203008 : {
884 203008 : vlg__add_chk_flush(vlg, fmt, ap, LOG_NOTICE, FALSE);
885 203008 : }
886 :
887 : void vlg_vdbg1(Vlg *vlg, const char *fmt, va_list ap)
888 54551 : {
889 54551 : if (vlg->out_dbg < 1)
890 54279 : return;
891 :
892 272 : vlg__add_chk_flush(vlg, fmt, ap, LOG_DEBUG, TRUE);
893 : }
894 :
895 : void vlg_vdbg2(Vlg *vlg, const char *fmt, va_list ap)
896 53191828 : {
897 53191828 : if (vlg->out_dbg < 2)
898 53191196 : return;
899 :
900 632 : vlg__add_chk_flush(vlg, fmt, ap, LOG_DEBUG, TRUE);
901 : }
902 :
903 : void vlg_vdbg3(Vlg *vlg, const char *fmt, va_list ap)
904 29723878 : {
905 29723878 : if (vlg->out_dbg < 3)
906 29722128 : return;
907 :
908 1750 : vlg__add_chk_flush(vlg, fmt, ap, LOG_DEBUG, TRUE);
909 : }
910 :
911 : /* ---------- ... ---------- */
912 : void vlg_abort(Vlg *vlg, const char *fmt, ... )
913 0 : {
914 : va_list ap;
915 :
916 0 : va_start(ap, fmt);
917 0 : vlg_vabort(vlg, fmt, ap);
918 0 : va_end(ap);
919 :
920 0 : ASSERT_NOT_REACHED();
921 0 : }
922 :
923 : void vlg_err(Vlg *vlg, int exit_code, const char *fmt, ... )
924 0 : {
925 : va_list ap;
926 :
927 0 : va_start(ap, fmt);
928 0 : vlg_verr(vlg, exit_code, fmt, ap);
929 0 : va_end(ap);
930 :
931 0 : ASSERT_NOT_REACHED();
932 0 : }
933 :
934 : void vlg_warn(Vlg *vlg, const char *fmt, ... )
935 28 : {
936 : va_list ap;
937 :
938 28 : va_start(ap, fmt);
939 28 : vlg_vwarn(vlg, fmt, ap);
940 28 : va_end(ap);
941 28 : }
942 :
943 : void vlg_info(Vlg *vlg, const char *fmt, ... )
944 203008 : {
945 : va_list ap;
946 :
947 203008 : va_start(ap, fmt);
948 203008 : vlg_vinfo(vlg, fmt, ap);
949 203008 : va_end(ap);
950 203008 : }
951 :
952 : void vlg_dbg1(Vlg *vlg, const char *fmt, ... )
953 54551 : {
954 : va_list ap;
955 :
956 54551 : va_start(ap, fmt);
957 54551 : vlg_vdbg1(vlg, fmt, ap);
958 54551 : va_end(ap);
959 54551 : }
960 :
961 : void vlg_dbg2(Vlg *vlg, const char *fmt, ... )
962 53191828 : {
963 : va_list ap;
964 :
965 53191828 : va_start(ap, fmt);
966 53191828 : vlg_vdbg2(vlg, fmt, ap);
967 53191828 : va_end(ap);
968 53191828 : }
969 :
970 : void vlg_dbg3(Vlg *vlg, const char *fmt, ... )
971 29723878 : {
972 : va_list ap;
973 :
974 29723878 : va_start(ap, fmt);
975 29723878 : vlg_vdbg3(vlg, fmt, ap);
976 29723878 : va_end(ap);
977 29723878 : }
978 :
979 : /* ---------- signal ... ---------- */
980 :
981 : static volatile sig_atomic_t vlg__in_signal = FALSE;
982 :
983 : /* due to multiple signals hitting while we are inside vlg_*() we have
984 : signal safe varients, that:
985 :
986 : block all signals (apart from SEGV and ABRT)
987 : do their thing
988 : make sure they have flushed
989 : restore signal mask
990 :
991 : ...note that this does mean if we've crashed inside vlg, we are screwed
992 : and just abort().
993 : */
994 :
995 : #define VLG__SIG_BLOCK_BEG() do { \
996 : sigset_t oset; \
997 : sigset_t nset; \
998 : \
999 : if (sigfillset(&nset) == -1) abort(); \
1000 : if (sigdelset(&nset, SIGSEGV) == -1) abort(); \
1001 : if (sigdelset(&nset, SIGABRT) == -1) abort(); \
1002 : if (sigprocmask(SIG_SETMASK, &nset, &oset) == -1) abort(); \
1003 : \
1004 : if (vlg__in_signal) abort(); \
1005 : else { \
1006 : Vstr_base *tmp_out = NULL; \
1007 : Date_store *tmp_dt = NULL; \
1008 : \
1009 : vlg__in_signal = TRUE; \
1010 : \
1011 : tmp_out = vlg->out_vstr; \
1012 : vlg->out_vstr = vlg->sig_out_vstr; \
1013 : vlg->sig_out_vstr = NULL; \
1014 : \
1015 : tmp_dt = vlg->dt; \
1016 : vlg->dt = vlg->sig_dt; \
1017 : vlg->sig_dt = NULL; \
1018 : \
1019 : if (vlg->out_vstr->len) abort()
1020 :
1021 : #define VLG__SIG_BLOCK_END() \
1022 : if (vlg->out_vstr->len) abort(); \
1023 : vlg->sig_out_vstr = vlg->out_vstr; \
1024 : vlg->out_vstr = tmp_out; \
1025 : \
1026 : vlg->sig_dt = vlg->dt; \
1027 : vlg->dt = tmp_dt; \
1028 : \
1029 : vlg__in_signal = FALSE; \
1030 : } \
1031 : \
1032 : if (sigprocmask(SIG_SETMASK, &oset, NULL) == -1) abort(); \
1033 : } while (FALSE)
1034 :
1035 : void vlg_sig_abort(Vlg *vlg, const char *fmt, ... )
1036 0 : {
1037 : va_list ap;
1038 :
1039 0 : VLG__SIG_BLOCK_BEG();
1040 :
1041 0 : va_start(ap, fmt);
1042 0 : vlg_vabort(vlg, fmt, ap);
1043 0 : va_end(ap);
1044 :
1045 0 : VLG__SIG_BLOCK_END();
1046 :
1047 0 : ASSERT_NOT_REACHED();
1048 0 : }
1049 :
1050 : void vlg_sig_err(Vlg *vlg, int exit_code, const char *fmt, ... )
1051 0 : {
1052 : va_list ap;
1053 :
1054 0 : VLG__SIG_BLOCK_BEG();
1055 :
1056 0 : va_start(ap, fmt);
1057 0 : vlg_verr(vlg, exit_code, fmt, ap);
1058 0 : va_end(ap);
1059 :
1060 0 : VLG__SIG_BLOCK_END();
1061 :
1062 0 : ASSERT_NOT_REACHED();
1063 0 : }
1064 :
1065 : void vlg_sig_warn(Vlg *vlg, const char *fmt, ... )
1066 0 : {
1067 : va_list ap;
1068 :
1069 0 : VLG__SIG_BLOCK_BEG();
1070 :
1071 0 : va_start(ap, fmt);
1072 0 : vlg_vwarn(vlg, fmt, ap);
1073 0 : va_end(ap);
1074 :
1075 0 : VLG__SIG_BLOCK_END();
1076 0 : }
1077 :
1078 : void vlg_sig_info(Vlg *vlg, const char *fmt, ... )
1079 0 : {
1080 : va_list ap;
1081 :
1082 0 : VLG__SIG_BLOCK_BEG();
1083 :
1084 0 : va_start(ap, fmt);
1085 0 : vlg_vinfo(vlg, fmt, ap);
1086 0 : va_end(ap);
1087 :
1088 0 : VLG__SIG_BLOCK_END();
1089 0 : }
1090 :
1091 : void vlg_sig_dbg1(Vlg *vlg, const char *fmt, ... )
1092 0 : {
1093 : va_list ap;
1094 :
1095 0 : VLG__SIG_BLOCK_BEG();
1096 :
1097 0 : va_start(ap, fmt);
1098 0 : vlg_vdbg1(vlg, fmt, ap);
1099 0 : va_end(ap);
1100 :
1101 0 : VLG__SIG_BLOCK_END();
1102 0 : }
1103 :
1104 : void vlg_sig_dbg2(Vlg *vlg, const char *fmt, ... )
1105 0 : {
1106 : va_list ap;
1107 :
1108 0 : VLG__SIG_BLOCK_BEG();
1109 :
1110 0 : va_start(ap, fmt);
1111 0 : vlg_vdbg2(vlg, fmt, ap);
1112 0 : va_end(ap);
1113 :
1114 0 : VLG__SIG_BLOCK_END();
1115 0 : }
1116 :
1117 : void vlg_sig_dbg3(Vlg *vlg, const char *fmt, ... )
1118 0 : {
1119 : va_list ap;
1120 :
1121 0 : VLG__SIG_BLOCK_BEG();
1122 :
1123 0 : va_start(ap, fmt);
1124 0 : vlg_vdbg3(vlg, fmt, ap);
1125 0 : va_end(ap);
1126 :
1127 0 : VLG__SIG_BLOCK_END();
1128 0 : }
1129 :
|