1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2010 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
30 #include <sys/signalfd.h>
31 #include <sys/socket.h>
38 #include "sd-messages.h"
40 #include "alloc-util.h"
42 #include "formats-util.h"
47 #include "parse-util.h"
48 #include "proc-cmdline.h"
49 #include "process-util.h"
50 #include "signal-util.h"
51 #include "socket-util.h"
52 #include "stdio-util.h"
53 #include "string-table.h"
54 #include "string-util.h"
55 #include "syslog-util.h"
56 #include "terminal-util.h"
57 #include "time-util.h"
60 #define SNDBUF_SIZE (8*1024*1024)
62 static LogTarget log_target
= LOG_TARGET_CONSOLE
;
63 static int log_max_level
= LOG_INFO
;
64 static int log_facility
= LOG_DAEMON
;
66 static int console_fd
= STDERR_FILENO
;
67 static int syslog_fd
= -1;
68 static int kmsg_fd
= -1;
69 static int journal_fd
= -1;
71 static bool syslog_is_stream
= false;
73 static bool show_color
= false;
74 static bool show_location
= false;
76 static bool upgrade_syslog_to_journal
= false;
78 /* Akin to glibc's __abort_msg; which is private and we hence cannot
80 static char *log_abort_msg
= NULL
;
82 void log_close_console(void) {
89 safe_close(console_fd
);
95 static int log_open_console(void) {
101 console_fd
= open_terminal("/dev/console", O_WRONLY
|O_NOCTTY
|O_CLOEXEC
);
105 console_fd
= STDERR_FILENO
;
110 void log_close_kmsg(void) {
111 kmsg_fd
= safe_close(kmsg_fd
);
114 static int log_open_kmsg(void) {
119 kmsg_fd
= open("/dev/kmsg", O_WRONLY
|O_NOCTTY
|O_CLOEXEC
);
126 void log_close_syslog(void) {
127 syslog_fd
= safe_close(syslog_fd
);
130 static int create_log_socket(int type
) {
134 fd
= socket(AF_UNIX
, type
|SOCK_CLOEXEC
, 0);
138 fd_inc_sndbuf(fd
, SNDBUF_SIZE
);
140 /* We need a blocking fd here since we'd otherwise lose
141 messages way too early. However, let's not hang forever in the
142 unlikely case of a deadlock. */
144 timeval_store(&tv
, 10 * USEC_PER_MSEC
);
146 timeval_store(&tv
, 10 * USEC_PER_SEC
);
147 (void) setsockopt(fd
, SOL_SOCKET
, SO_SNDTIMEO
, &tv
, sizeof(tv
));
152 static int log_open_syslog(void) {
154 static const union sockaddr_union sa
= {
155 .un
.sun_family
= AF_UNIX
,
156 .un
.sun_path
= "/dev/log",
164 syslog_fd
= create_log_socket(SOCK_DGRAM
);
170 if (connect(syslog_fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(sa
.un
.sun_path
)) < 0) {
171 safe_close(syslog_fd
);
173 /* Some legacy syslog systems still use stream
174 * sockets. They really shouldn't. But what can we
176 syslog_fd
= create_log_socket(SOCK_STREAM
);
182 if (connect(syslog_fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(sa
.un
.sun_path
)) < 0) {
187 syslog_is_stream
= true;
189 syslog_is_stream
= false;
198 void log_close_journal(void) {
199 journal_fd
= safe_close(journal_fd
);
202 static int log_open_journal(void) {
204 static const union sockaddr_union sa
= {
205 .un
.sun_family
= AF_UNIX
,
206 .un
.sun_path
= "/run/systemd/journal/socket",
214 journal_fd
= create_log_socket(SOCK_DGRAM
);
215 if (journal_fd
< 0) {
220 if (connect(journal_fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(sa
.un
.sun_path
)) < 0) {
235 /* If we don't use the console we close it here, to not get
236 * killed by SAK. If we don't use syslog we close it here so
237 * that we are not confused by somebody deleting the socket in
238 * the fs. If we don't use /dev/kmsg we still keep it open,
239 * because there is no reason to close it. */
241 if (log_target
== LOG_TARGET_NULL
) {
248 if ((log_target
!= LOG_TARGET_AUTO
&& log_target
!= LOG_TARGET_SAFE
) ||
250 isatty(STDERR_FILENO
) <= 0) {
252 if (log_target
== LOG_TARGET_AUTO
||
253 log_target
== LOG_TARGET_JOURNAL_OR_KMSG
||
254 log_target
== LOG_TARGET_JOURNAL
) {
255 r
= log_open_journal();
263 if (log_target
== LOG_TARGET_SYSLOG_OR_KMSG
||
264 log_target
== LOG_TARGET_SYSLOG
) {
265 r
= log_open_syslog();
273 if (log_target
== LOG_TARGET_AUTO
||
274 log_target
== LOG_TARGET_SAFE
||
275 log_target
== LOG_TARGET_JOURNAL_OR_KMSG
||
276 log_target
== LOG_TARGET_SYSLOG_OR_KMSG
||
277 log_target
== LOG_TARGET_KMSG
) {
291 return log_open_console();
294 void log_set_target(LogTarget target
) {
296 assert(target
< _LOG_TARGET_MAX
);
298 if (upgrade_syslog_to_journal
) {
299 if (target
== LOG_TARGET_SYSLOG
)
300 target
= LOG_TARGET_JOURNAL
;
301 else if (target
== LOG_TARGET_SYSLOG_OR_KMSG
)
302 target
= LOG_TARGET_JOURNAL_OR_KMSG
;
308 void log_close(void) {
315 void log_forget_fds(void) {
316 console_fd
= kmsg_fd
= syslog_fd
= journal_fd
= -1;
319 void log_set_max_level(int level
) {
320 assert((level
& LOG_PRIMASK
) == level
);
322 log_max_level
= level
;
325 void log_set_facility(int facility
) {
326 log_facility
= facility
;
329 static int write_to_console(
335 const char *object_field
,
337 const char *buffer
) {
339 char location
[64], prefix
[1 + DECIMAL_STR_MAX(int) + 2];
340 struct iovec iovec
[6] = {};
347 if (log_target
== LOG_TARGET_CONSOLE_PREFIXED
) {
348 sprintf(prefix
, "<%i>", level
);
349 IOVEC_SET_STRING(iovec
[n
++], prefix
);
352 highlight
= LOG_PRI(level
) <= LOG_ERR
&& show_color
;
355 xsprintf(location
, "(%s:%i) ", file
, line
);
356 IOVEC_SET_STRING(iovec
[n
++], location
);
360 IOVEC_SET_STRING(iovec
[n
++], ANSI_HIGHLIGHT_RED
);
361 IOVEC_SET_STRING(iovec
[n
++], buffer
);
363 IOVEC_SET_STRING(iovec
[n
++], ANSI_NORMAL
);
364 IOVEC_SET_STRING(iovec
[n
++], "\n");
366 if (writev(console_fd
, iovec
, n
) < 0) {
368 if (errno
== EIO
&& getpid() == 1) {
370 /* If somebody tried to kick us from our
371 * console tty (via vhangup() or suchlike),
372 * try to reconnect */
380 if (writev(console_fd
, iovec
, n
) < 0)
389 static int write_to_syslog(
395 const char *object_field
,
397 const char *buffer
) {
399 char header_priority
[2 + DECIMAL_STR_MAX(int) + 1],
401 header_pid
[4 + DECIMAL_STR_MAX(pid_t
) + 1];
402 struct iovec iovec
[5] = {};
403 struct msghdr msghdr
= {
405 .msg_iovlen
= ELEMENTSOF(iovec
),
413 xsprintf(header_priority
, "<%i>", level
);
415 t
= (time_t) (now(CLOCK_REALTIME
) / USEC_PER_SEC
);
420 if (strftime(header_time
, sizeof(header_time
), "%h %e %T ", tm
) <= 0)
423 xsprintf(header_pid
, "["PID_FMT
"]: ", getpid());
425 IOVEC_SET_STRING(iovec
[0], header_priority
);
426 IOVEC_SET_STRING(iovec
[1], header_time
);
427 IOVEC_SET_STRING(iovec
[2], program_invocation_short_name
);
428 IOVEC_SET_STRING(iovec
[3], header_pid
);
429 IOVEC_SET_STRING(iovec
[4], buffer
);
431 /* When using syslog via SOCK_STREAM separate the messages by NUL chars */
432 if (syslog_is_stream
)
438 n
= sendmsg(syslog_fd
, &msghdr
, MSG_NOSIGNAL
);
442 if (!syslog_is_stream
||
443 (size_t) n
>= IOVEC_TOTAL_SIZE(iovec
, ELEMENTSOF(iovec
)))
446 IOVEC_INCREMENT(iovec
, ELEMENTSOF(iovec
), n
);
452 static int write_to_kmsg(
458 const char *object_field
,
460 const char *buffer
) {
462 char header_priority
[2 + DECIMAL_STR_MAX(int) + 1],
463 header_pid
[4 + DECIMAL_STR_MAX(pid_t
) + 1];
464 struct iovec iovec
[5] = {};
469 xsprintf(header_priority
, "<%i>", level
);
470 xsprintf(header_pid
, "["PID_FMT
"]: ", getpid());
472 IOVEC_SET_STRING(iovec
[0], header_priority
);
473 IOVEC_SET_STRING(iovec
[1], program_invocation_short_name
);
474 IOVEC_SET_STRING(iovec
[2], header_pid
);
475 IOVEC_SET_STRING(iovec
[3], buffer
);
476 IOVEC_SET_STRING(iovec
[4], "\n");
478 if (writev(kmsg_fd
, iovec
, ELEMENTSOF(iovec
)) < 0)
484 static int log_do_header(
489 const char *file
, int line
, const char *func
,
490 const char *object_field
, const char *object
) {
492 snprintf(header
, size
,
494 "SYSLOG_FACILITY=%i\n"
500 "SYSLOG_IDENTIFIER=%s\n",
503 isempty(file
) ? "" : "CODE_FILE=",
504 isempty(file
) ? "" : file
,
505 isempty(file
) ? "" : "\n",
506 line
? "CODE_LINE=" : "",
507 line
? 1 : 0, line
, /* %.0d means no output too, special case for 0 */
509 isempty(func
) ? "" : "CODE_FUNCTION=",
510 isempty(func
) ? "" : func
,
511 isempty(func
) ? "" : "\n",
512 error
? "ERRNO=" : "",
513 error
? 1 : 0, error
,
515 isempty(object
) ? "" : object_field
,
516 isempty(object
) ? "" : object
,
517 isempty(object
) ? "" : "\n",
518 program_invocation_short_name
);
523 static int write_to_journal(
529 const char *object_field
,
531 const char *buffer
) {
533 char header
[LINE_MAX
];
534 struct iovec iovec
[4] = {};
535 struct msghdr mh
= {};
540 log_do_header(header
, sizeof(header
), level
, error
, file
, line
, func
, object_field
, object
);
542 IOVEC_SET_STRING(iovec
[0], header
);
543 IOVEC_SET_STRING(iovec
[1], "MESSAGE=");
544 IOVEC_SET_STRING(iovec
[2], buffer
);
545 IOVEC_SET_STRING(iovec
[3], "\n");
548 mh
.msg_iovlen
= ELEMENTSOF(iovec
);
550 if (sendmsg(journal_fd
, &mh
, MSG_NOSIGNAL
) < 0)
556 static int log_dispatch(
562 const char *object_field
,
568 if (log_target
== LOG_TARGET_NULL
)
571 /* Patch in LOG_DAEMON facility if necessary */
572 if ((level
& LOG_FACMASK
) == 0)
573 level
= log_facility
| LOG_PRI(level
);
582 buffer
+= strspn(buffer
, NEWLINE
);
587 if ((e
= strpbrk(buffer
, NEWLINE
)))
590 if (log_target
== LOG_TARGET_AUTO
||
591 log_target
== LOG_TARGET_JOURNAL_OR_KMSG
||
592 log_target
== LOG_TARGET_JOURNAL
) {
594 k
= write_to_journal(level
, error
, file
, line
, func
, object_field
, object
, buffer
);
602 if (log_target
== LOG_TARGET_SYSLOG_OR_KMSG
||
603 log_target
== LOG_TARGET_SYSLOG
) {
605 k
= write_to_syslog(level
, error
, file
, line
, func
, object_field
, object
, buffer
);
614 (log_target
== LOG_TARGET_AUTO
||
615 log_target
== LOG_TARGET_SAFE
||
616 log_target
== LOG_TARGET_SYSLOG_OR_KMSG
||
617 log_target
== LOG_TARGET_JOURNAL_OR_KMSG
||
618 log_target
== LOG_TARGET_KMSG
)) {
620 k
= write_to_kmsg(level
, error
, file
, line
, func
, object_field
, object
, buffer
);
628 (void) write_to_console(level
, error
, file
, line
, func
, object_field
, object
, buffer
);
636 int log_dump_internal(
646 /* This modifies the buffer... */
651 if (_likely_(LOG_PRI(level
) > log_max_level
))
654 return log_dispatch(level
, error
, file
, line
, func
, NULL
, NULL
, buffer
);
667 char buffer
[LINE_MAX
];
672 if (_likely_(LOG_PRI(level
) > log_max_level
))
675 /* Make sure that %m maps to the specified error */
679 vsnprintf(buffer
, sizeof(buffer
), format
, ap
);
681 return log_dispatch(level
, error
, file
, line
, func
, NULL
, NULL
, buffer
);
690 const char *format
, ...) {
695 va_start(ap
, format
);
696 r
= log_internalv(level
, error
, file
, line
, func
, format
, ap
);
702 int log_object_internalv(
708 const char *object_field
,
720 if (_likely_(LOG_PRI(level
) > log_max_level
))
723 /* Make sure that %m maps to the specified error */
727 /* Prepend the object name before the message */
732 l
= n
+ 2 + LINE_MAX
;
734 buffer
= newa(char, l
);
735 b
= stpcpy(stpcpy(buffer
, object
), ": ");
738 b
= buffer
= newa(char, l
);
741 vsnprintf(b
, l
, format
, ap
);
743 return log_dispatch(level
, error
, file
, line
, func
, object_field
, object
, buffer
);
746 int log_object_internal(
752 const char *object_field
,
754 const char *format
, ...) {
759 va_start(ap
, format
);
760 r
= log_object_internalv(level
, error
, file
, line
, func
, object_field
, object
, format
, ap
);
766 static void log_assert(
772 const char *format
) {
774 static char buffer
[LINE_MAX
];
776 if (_likely_(LOG_PRI(level
) > log_max_level
))
779 DISABLE_WARNING_FORMAT_NONLITERAL
;
780 xsprintf(buffer
, format
, text
, file
, line
, func
);
783 log_abort_msg
= buffer
;
785 log_dispatch(level
, 0, file
, line
, func
, NULL
, NULL
, buffer
);
788 noreturn
void log_assert_failed(const char *text
, const char *file
, int line
, const char *func
) {
789 log_assert(LOG_CRIT
, text
, file
, line
, func
, "Assertion '%s' failed at %s:%u, function %s(). Aborting.");
793 noreturn
void log_assert_failed_unreachable(const char *text
, const char *file
, int line
, const char *func
) {
794 log_assert(LOG_CRIT
, text
, file
, line
, func
, "Code should not be reached '%s' at %s:%u, function %s(). Aborting.");
798 void log_assert_failed_return(const char *text
, const char *file
, int line
, const char *func
) {
800 log_assert(LOG_DEBUG
, text
, file
, line
, func
, "Assertion '%s' failed at %s:%u, function %s(). Ignoring.");
803 int log_oom_internal(const char *file
, int line
, const char *func
) {
804 log_internal(LOG_ERR
, ENOMEM
, file
, line
, func
, "Out of memory.");
808 int log_struct_internal(
814 const char *format
, ...) {
824 if (_likely_(LOG_PRI(level
) > log_max_level
))
827 if (log_target
== LOG_TARGET_NULL
)
830 if ((level
& LOG_FACMASK
) == 0)
831 level
= log_facility
| LOG_PRI(level
);
833 if ((log_target
== LOG_TARGET_AUTO
||
834 log_target
== LOG_TARGET_JOURNAL_OR_KMSG
||
835 log_target
== LOG_TARGET_JOURNAL
) &&
837 char header
[LINE_MAX
];
838 struct iovec iovec
[17] = {};
843 static const char nl
= '\n';
844 bool fallback
= false;
846 /* If the journal is available do structured logging */
847 log_do_header(header
, sizeof(header
), level
, error
, file
, line
, func
, NULL
, NULL
);
848 IOVEC_SET_STRING(iovec
[n
++], header
);
850 va_start(ap
, format
);
851 while (format
&& n
+ 1 < ELEMENTSOF(iovec
)) {
855 /* We need to copy the va_list structure,
856 * since vasprintf() leaves it afterwards at
857 * an undefined location */
863 if (vasprintf(&m
, format
, aq
) < 0) {
870 /* Now, jump enough ahead, so that we point to
871 * the next format string */
872 VA_FORMAT_ADVANCE(format
, ap
);
874 IOVEC_SET_STRING(iovec
[n
++], m
);
876 iovec
[n
].iov_base
= (char*) &nl
;
877 iovec
[n
].iov_len
= 1;
880 format
= va_arg(ap
, char *);
885 (void) sendmsg(journal_fd
, &mh
, MSG_NOSIGNAL
);
889 for (i
= 1; i
< n
; i
+= 2)
890 free(iovec
[i
].iov_base
);
896 /* Fallback if journal logging is not available or didn't work. */
898 va_start(ap
, format
);
906 vsnprintf(buf
, sizeof(buf
), format
, aq
);
909 if (startswith(buf
, "MESSAGE=")) {
914 VA_FORMAT_ADVANCE(format
, ap
);
916 format
= va_arg(ap
, char *);
923 return log_dispatch(level
, error
, file
, line
, func
, NULL
, NULL
, buf
+ 8);
926 int log_set_target_from_string(const char *e
) {
929 t
= log_target_from_string(e
);
937 int log_set_max_level_from_string(const char *e
) {
940 t
= log_level_from_string(e
);
944 log_set_max_level(t
);
948 static int parse_proc_cmdline_item(const char *key
, const char *value
) {
951 * The systemd.log_xyz= settings are parsed by all tools, and
954 * However, "quiet" is only parsed by PID 1, and only turns of
955 * status output to /dev/console, but does not alter the log
959 if (streq(key
, "debug") && !value
)
960 log_set_max_level(LOG_DEBUG
);
962 else if (streq(key
, "systemd.log_target") && value
) {
964 if (log_set_target_from_string(value
) < 0)
965 log_warning("Failed to parse log target '%s'. Ignoring.", value
);
967 } else if (streq(key
, "systemd.log_level") && value
) {
969 if (log_set_max_level_from_string(value
) < 0)
970 log_warning("Failed to parse log level '%s'. Ignoring.", value
);
972 } else if (streq(key
, "systemd.log_color") && value
) {
974 if (log_show_color_from_string(value
) < 0)
975 log_warning("Failed to parse log color setting '%s'. Ignoring.", value
);
977 } else if (streq(key
, "systemd.log_location") && value
) {
979 if (log_show_location_from_string(value
) < 0)
980 log_warning("Failed to parse log location setting '%s'. Ignoring.", value
);
986 void log_parse_environment(void) {
989 if (get_ctty_devnr(0, NULL
) < 0)
990 /* Only try to read the command line in daemons.
991 We assume that anything that has a controlling
992 tty is user stuff. */
993 (void) parse_proc_cmdline(parse_proc_cmdline_item
);
995 e
= secure_getenv("SYSTEMD_LOG_TARGET");
996 if (e
&& log_set_target_from_string(e
) < 0)
997 log_warning("Failed to parse log target '%s'. Ignoring.", e
);
999 e
= secure_getenv("SYSTEMD_LOG_LEVEL");
1000 if (e
&& log_set_max_level_from_string(e
) < 0)
1001 log_warning("Failed to parse log level '%s'. Ignoring.", e
);
1003 e
= secure_getenv("SYSTEMD_LOG_COLOR");
1004 if (e
&& log_show_color_from_string(e
) < 0)
1005 log_warning("Failed to parse bool '%s'. Ignoring.", e
);
1007 e
= secure_getenv("SYSTEMD_LOG_LOCATION");
1008 if (e
&& log_show_location_from_string(e
) < 0)
1009 log_warning("Failed to parse bool '%s'. Ignoring.", e
);
1012 LogTarget
log_get_target(void) {
1016 int log_get_max_level(void) {
1017 return log_max_level
;
1020 void log_show_color(bool b
) {
1024 bool log_get_show_color(void) {
1028 void log_show_location(bool b
) {
1032 bool log_get_show_location(void) {
1033 return show_location
;
1036 int log_show_color_from_string(const char *e
) {
1039 t
= parse_boolean(e
);
1047 int log_show_location_from_string(const char *e
) {
1050 t
= parse_boolean(e
);
1054 log_show_location(t
);
1058 bool log_on_console(void) {
1059 if (log_target
== LOG_TARGET_CONSOLE
||
1060 log_target
== LOG_TARGET_CONSOLE_PREFIXED
)
1063 return syslog_fd
< 0 && kmsg_fd
< 0 && journal_fd
< 0;
1066 static const char *const log_target_table
[_LOG_TARGET_MAX
] = {
1067 [LOG_TARGET_CONSOLE
] = "console",
1068 [LOG_TARGET_CONSOLE_PREFIXED
] = "console-prefixed",
1069 [LOG_TARGET_KMSG
] = "kmsg",
1070 [LOG_TARGET_JOURNAL
] = "journal",
1071 [LOG_TARGET_JOURNAL_OR_KMSG
] = "journal-or-kmsg",
1072 [LOG_TARGET_SYSLOG
] = "syslog",
1073 [LOG_TARGET_SYSLOG_OR_KMSG
] = "syslog-or-kmsg",
1074 [LOG_TARGET_AUTO
] = "auto",
1075 [LOG_TARGET_SAFE
] = "safe",
1076 [LOG_TARGET_NULL
] = "null"
1079 DEFINE_STRING_TABLE_LOOKUP(log_target
, LogTarget
);
1081 void log_received_signal(int level
, const struct signalfd_siginfo
*si
) {
1082 if (si
->ssi_pid
> 0) {
1083 _cleanup_free_
char *p
= NULL
;
1085 get_process_comm(si
->ssi_pid
, &p
);
1088 "Received SIG%s from PID %"PRIu32
" (%s).",
1089 signal_to_string(si
->ssi_signo
),
1090 si
->ssi_pid
, strna(p
));
1094 signal_to_string(si
->ssi_signo
));
1098 void log_set_upgrade_syslog_to_journal(bool b
) {
1099 upgrade_syslog_to_journal
= b
;
1102 int log_syntax_internal(
1105 const char *config_file
,
1106 unsigned config_line
,
1111 const char *format
, ...) {
1114 char buffer
[LINE_MAX
];
1121 if (_likely_(LOG_PRI(level
) > log_max_level
))
1124 if (log_target
== LOG_TARGET_NULL
)
1130 va_start(ap
, format
);
1131 vsnprintf(buffer
, sizeof(buffer
), format
, ap
);
1135 r
= log_struct_internal(
1138 getpid() == 1 ? "UNIT=%s" : "USER_UNIT=%s", unit
,
1139 LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION
),
1140 "CONFIG_FILE=%s", config_file
,
1141 "CONFIG_LINE=%u", config_line
,
1142 LOG_MESSAGE("[%s:%u] %s", config_file
, config_line
, buffer
),
1145 r
= log_struct_internal(
1148 LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION
),
1149 "CONFIG_FILE=%s", config_file
,
1150 "CONFIG_LINE=%u", config_line
,
1151 LOG_MESSAGE("[%s:%u] %s", config_file
, config_line
, buffer
),