2 This file is part of systemd.
4 Copyright 2012 Lennart Poettering
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
22 #include <microhttpd.h>
28 #include "sd-daemon.h"
29 #include "sd-journal.h"
31 #include "alloc-util.h"
35 #include "hostname-util.h"
37 #include "logs-show.h"
38 #include "microhttpd-util.h"
39 #include "parse-util.h"
43 #define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC)
45 static char *arg_key_pem
= NULL
;
46 static char *arg_cert_pem
= NULL
;
47 static char *arg_trust_pem
= NULL
;
48 static char *arg_directory
= NULL
;
50 typedef struct RequestMeta
{
63 int argument_parse_error
;
72 static const char* const mime_types
[_OUTPUT_MODE_MAX
] = {
73 [OUTPUT_SHORT
] = "text/plain",
74 [OUTPUT_JSON
] = "application/json",
75 [OUTPUT_JSON_SSE
] = "text/event-stream",
76 [OUTPUT_EXPORT
] = "application/vnd.fdo.journal",
79 static RequestMeta
*request_meta(void **connection_cls
) {
82 assert(connection_cls
);
84 return *connection_cls
;
86 m
= new0(RequestMeta
, 1);
94 static void request_meta_free(
96 struct MHD_Connection
*connection
,
97 void **connection_cls
,
98 enum MHD_RequestTerminationCode toe
) {
100 RequestMeta
*m
= *connection_cls
;
105 sd_journal_close(m
->journal
);
113 static int open_journal(RequestMeta
*m
) {
120 return sd_journal_open_directory(&m
->journal
, arg_directory
, 0);
122 return sd_journal_open(&m
->journal
, SD_JOURNAL_LOCAL_ONLY
|SD_JOURNAL_SYSTEM
);
125 static int request_meta_ensure_tmp(RequestMeta
*m
) {
133 fd
= open_tmpfile_unlinkable("/tmp", O_RDWR
|O_CLOEXEC
);
137 m
->tmp
= fdopen(fd
, "w+");
147 static ssize_t
request_reader_entries(
153 RequestMeta
*m
= cls
;
160 assert(pos
>= m
->delta
);
164 while (pos
>= m
->size
) {
167 /* End of this entry, so let's serialize the next
170 if (m
->n_entries_set
&&
172 return MHD_CONTENT_READER_END_OF_STREAM
;
175 r
= sd_journal_previous_skip(m
->journal
, (uint64_t) -m
->n_skip
+ 1);
176 else if (m
->n_skip
> 0)
177 r
= sd_journal_next_skip(m
->journal
, (uint64_t) m
->n_skip
+ 1);
179 r
= sd_journal_next(m
->journal
);
182 log_error_errno(r
, "Failed to advance journal pointer: %m");
183 return MHD_CONTENT_READER_END_WITH_ERROR
;
187 r
= sd_journal_wait(m
->journal
, (uint64_t) JOURNAL_WAIT_TIMEOUT
);
189 log_error_errno(r
, "Couldn't wait for journal event: %m");
190 return MHD_CONTENT_READER_END_WITH_ERROR
;
192 if (r
== SD_JOURNAL_NOP
)
198 return MHD_CONTENT_READER_END_OF_STREAM
;
204 r
= sd_journal_test_cursor(m
->journal
, m
->cursor
);
206 log_error_errno(r
, "Failed to test cursor: %m");
207 return MHD_CONTENT_READER_END_WITH_ERROR
;
211 return MHD_CONTENT_READER_END_OF_STREAM
;
217 if (m
->n_entries_set
)
222 r
= request_meta_ensure_tmp(m
);
224 log_error_errno(r
, "Failed to create temporary file: %m");
225 return MHD_CONTENT_READER_END_WITH_ERROR
;
228 r
= output_journal(m
->tmp
, m
->journal
, m
->mode
, 0, OUTPUT_FULL_WIDTH
, NULL
);
230 log_error_errno(r
, "Failed to serialize item: %m");
231 return MHD_CONTENT_READER_END_WITH_ERROR
;
235 if (sz
== (off_t
) -1) {
236 log_error_errno(errno
, "Failed to retrieve file position: %m");
237 return MHD_CONTENT_READER_END_WITH_ERROR
;
240 m
->size
= (uint64_t) sz
;
243 if (m
->tmp
== NULL
&& m
->follow
)
246 if (fseeko(m
->tmp
, pos
, SEEK_SET
) < 0) {
247 log_error_errno(errno
, "Failed to seek to position: %m");
248 return MHD_CONTENT_READER_END_WITH_ERROR
;
258 k
= fread(buf
, 1, n
, m
->tmp
);
260 log_error("Failed to read from file: %s", errno
? strerror(errno
) : "Premature EOF");
261 return MHD_CONTENT_READER_END_WITH_ERROR
;
267 static int request_parse_accept(
269 struct MHD_Connection
*connection
) {
276 header
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Accept");
280 if (streq(header
, mime_types
[OUTPUT_JSON
]))
281 m
->mode
= OUTPUT_JSON
;
282 else if (streq(header
, mime_types
[OUTPUT_JSON_SSE
]))
283 m
->mode
= OUTPUT_JSON_SSE
;
284 else if (streq(header
, mime_types
[OUTPUT_EXPORT
]))
285 m
->mode
= OUTPUT_EXPORT
;
287 m
->mode
= OUTPUT_SHORT
;
292 static int request_parse_range(
294 struct MHD_Connection
*connection
) {
296 const char *range
, *colon
, *colon2
;
302 range
= MHD_lookup_connection_value(connection
, MHD_HEADER_KIND
, "Range");
306 if (!startswith(range
, "entries="))
310 range
+= strspn(range
, WHITESPACE
);
312 colon
= strchr(range
, ':');
314 m
->cursor
= strdup(range
);
318 colon2
= strchr(colon
+ 1, ':');
320 _cleanup_free_
char *t
;
322 t
= strndup(colon
+ 1, colon2
- colon
- 1);
326 r
= safe_atoi64(t
, &m
->n_skip
);
331 p
= (colon2
? colon2
: colon
) + 1;
333 r
= safe_atou64(p
, &m
->n_entries
);
337 if (m
->n_entries
<= 0)
340 m
->n_entries_set
= true;
343 m
->cursor
= strndup(range
, colon
- range
);
349 m
->cursor
[strcspn(m
->cursor
, WHITESPACE
)] = 0;
350 if (isempty(m
->cursor
))
351 m
->cursor
= mfree(m
->cursor
);
356 static int request_parse_arguments_iterator(
358 enum MHD_ValueKind kind
,
362 RequestMeta
*m
= cls
;
363 _cleanup_free_
char *p
= NULL
;
369 m
->argument_parse_error
= -EINVAL
;
373 if (streq(key
, "follow")) {
374 if (isempty(value
)) {
379 r
= parse_boolean(value
);
381 m
->argument_parse_error
= r
;
389 if (streq(key
, "discrete")) {
390 if (isempty(value
)) {
395 r
= parse_boolean(value
);
397 m
->argument_parse_error
= r
;
405 if (streq(key
, "boot")) {
409 r
= parse_boolean(value
);
411 m
->argument_parse_error
= r
;
417 char match
[9 + 32 + 1] = "_BOOT_ID=";
420 r
= sd_id128_get_boot(&bid
);
422 log_error_errno(r
, "Failed to get boot ID: %m");
426 sd_id128_to_string(bid
, match
+ 9);
427 r
= sd_journal_add_match(m
->journal
, match
, sizeof(match
)-1);
429 m
->argument_parse_error
= r
;
437 p
= strjoin(key
, "=", strempty(value
));
439 m
->argument_parse_error
= log_oom();
443 r
= sd_journal_add_match(m
->journal
, p
, 0);
445 m
->argument_parse_error
= r
;
452 static int request_parse_arguments(
454 struct MHD_Connection
*connection
) {
459 m
->argument_parse_error
= 0;
460 MHD_get_connection_values(connection
, MHD_GET_ARGUMENT_KIND
, request_parse_arguments_iterator
, m
);
462 return m
->argument_parse_error
;
465 static int request_handler_entries(
466 struct MHD_Connection
*connection
,
467 void *connection_cls
) {
469 struct MHD_Response
*response
;
470 RequestMeta
*m
= connection_cls
;
478 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
480 if (request_parse_accept(m
, connection
) < 0)
481 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.");
483 if (request_parse_range(m
, connection
) < 0)
484 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Range header.");
486 if (request_parse_arguments(m
, connection
) < 0)
487 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse URL arguments.");
491 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Discrete seeks require a cursor specification.");
494 m
->n_entries_set
= true;
498 r
= sd_journal_seek_cursor(m
->journal
, m
->cursor
);
499 else if (m
->n_skip
>= 0)
500 r
= sd_journal_seek_head(m
->journal
);
501 else if (m
->n_skip
< 0)
502 r
= sd_journal_seek_tail(m
->journal
);
504 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to seek in journal.");
506 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_entries
, m
, NULL
);
508 return respond_oom(connection
);
510 MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
]);
512 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
513 MHD_destroy_response(response
);
518 static int output_field(FILE *f
, OutputMode m
, const char *d
, size_t l
) {
522 eq
= memchr(d
, '=', l
);
526 j
= l
- (eq
- d
+ 1);
528 if (m
== OUTPUT_JSON
) {
529 fprintf(f
, "{ \"%.*s\" : ", (int) (eq
- d
), d
);
530 json_escape(f
, eq
+1, j
, OUTPUT_FULL_WIDTH
);
533 fwrite(eq
+1, 1, j
, f
);
540 static ssize_t
request_reader_fields(
546 RequestMeta
*m
= cls
;
553 assert(pos
>= m
->delta
);
557 while (pos
>= m
->size
) {
562 /* End of this field, so let's serialize the next
565 if (m
->n_fields_set
&&
567 return MHD_CONTENT_READER_END_OF_STREAM
;
569 r
= sd_journal_enumerate_unique(m
->journal
, &d
, &l
);
571 log_error_errno(r
, "Failed to advance field index: %m");
572 return MHD_CONTENT_READER_END_WITH_ERROR
;
574 return MHD_CONTENT_READER_END_OF_STREAM
;
582 r
= request_meta_ensure_tmp(m
);
584 log_error_errno(r
, "Failed to create temporary file: %m");
585 return MHD_CONTENT_READER_END_WITH_ERROR
;
588 r
= output_field(m
->tmp
, m
->mode
, d
, l
);
590 log_error_errno(r
, "Failed to serialize item: %m");
591 return MHD_CONTENT_READER_END_WITH_ERROR
;
595 if (sz
== (off_t
) -1) {
596 log_error_errno(errno
, "Failed to retrieve file position: %m");
597 return MHD_CONTENT_READER_END_WITH_ERROR
;
600 m
->size
= (uint64_t) sz
;
603 if (fseeko(m
->tmp
, pos
, SEEK_SET
) < 0) {
604 log_error_errno(errno
, "Failed to seek to position: %m");
605 return MHD_CONTENT_READER_END_WITH_ERROR
;
613 k
= fread(buf
, 1, n
, m
->tmp
);
615 log_error("Failed to read from file: %s", errno
? strerror(errno
) : "Premature EOF");
616 return MHD_CONTENT_READER_END_WITH_ERROR
;
622 static int request_handler_fields(
623 struct MHD_Connection
*connection
,
625 void *connection_cls
) {
627 struct MHD_Response
*response
;
628 RequestMeta
*m
= connection_cls
;
636 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
638 if (request_parse_accept(m
, connection
) < 0)
639 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to parse Accept header.");
641 r
= sd_journal_query_unique(m
->journal
, field
);
643 return mhd_respond(connection
, MHD_HTTP_BAD_REQUEST
, "Failed to query unique fields.");
645 response
= MHD_create_response_from_callback(MHD_SIZE_UNKNOWN
, 4*1024, request_reader_fields
, m
, NULL
);
647 return respond_oom(connection
);
649 MHD_add_response_header(response
, "Content-Type", mime_types
[m
->mode
== OUTPUT_JSON
? OUTPUT_JSON
: OUTPUT_SHORT
]);
651 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
652 MHD_destroy_response(response
);
657 static int request_handler_redirect(
658 struct MHD_Connection
*connection
,
659 const char *target
) {
662 struct MHD_Response
*response
;
668 if (asprintf(&page
, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target
) < 0)
669 return respond_oom(connection
);
671 response
= MHD_create_response_from_buffer(strlen(page
), page
, MHD_RESPMEM_MUST_FREE
);
674 return respond_oom(connection
);
677 MHD_add_response_header(response
, "Content-Type", "text/html");
678 MHD_add_response_header(response
, "Location", target
);
680 ret
= MHD_queue_response(connection
, MHD_HTTP_MOVED_PERMANENTLY
, response
);
681 MHD_destroy_response(response
);
686 static int request_handler_file(
687 struct MHD_Connection
*connection
,
689 const char *mime_type
) {
691 struct MHD_Response
*response
;
693 _cleanup_close_
int fd
= -1;
700 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
702 return mhd_respondf(connection
, errno
, MHD_HTTP_NOT_FOUND
, "Failed to open file %s: %m", path
);
704 if (fstat(fd
, &st
) < 0)
705 return mhd_respondf(connection
, errno
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to stat file: %m");
707 response
= MHD_create_response_from_fd_at_offset64(st
.st_size
, fd
, 0);
709 return respond_oom(connection
);
713 MHD_add_response_header(response
, "Content-Type", mime_type
);
715 ret
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
716 MHD_destroy_response(response
);
721 static int get_virtualization(char **v
) {
722 _cleanup_(sd_bus_unrefp
) sd_bus
*bus
= NULL
;
726 r
= sd_bus_default_system(&bus
);
730 r
= sd_bus_get_property_string(
732 "org.freedesktop.systemd1",
733 "/org/freedesktop/systemd1",
734 "org.freedesktop.systemd1.Manager",
751 static int request_handler_machine(
752 struct MHD_Connection
*connection
,
753 void *connection_cls
) {
755 struct MHD_Response
*response
;
756 RequestMeta
*m
= connection_cls
;
758 _cleanup_free_
char* hostname
= NULL
, *os_name
= NULL
;
759 uint64_t cutoff_from
= 0, cutoff_to
= 0, usage
= 0;
762 _cleanup_free_
char *v
= NULL
;
769 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to open journal: %m");
771 r
= sd_id128_get_machine(&mid
);
773 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine machine ID: %m");
775 r
= sd_id128_get_boot(&bid
);
777 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine boot ID: %m");
779 hostname
= gethostname_malloc();
781 return respond_oom(connection
);
783 r
= sd_journal_get_usage(m
->journal
, &usage
);
785 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %m");
787 r
= sd_journal_get_cutoff_realtime_usec(m
->journal
, &cutoff_from
, &cutoff_to
);
789 return mhd_respondf(connection
, r
, MHD_HTTP_INTERNAL_SERVER_ERROR
, "Failed to determine disk usage: %m");
791 if (parse_env_file("/etc/os-release", NEWLINE
, "PRETTY_NAME", &os_name
, NULL
) == -ENOENT
)
792 (void) parse_env_file("/usr/lib/os-release", NEWLINE
, "PRETTY_NAME", &os_name
, NULL
);
794 get_virtualization(&v
);
797 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR
"\","
798 "\"boot_id\" : \"" SD_ID128_FORMAT_STR
"\","
799 "\"hostname\" : \"%s\","
800 "\"os_pretty_name\" : \"%s\","
801 "\"virtualization\" : \"%s\","
802 "\"usage\" : \"%"PRIu64
"\","
803 "\"cutoff_from_realtime\" : \"%"PRIu64
"\","
804 "\"cutoff_to_realtime\" : \"%"PRIu64
"\" }\n",
805 SD_ID128_FORMAT_VAL(mid
),
806 SD_ID128_FORMAT_VAL(bid
),
807 hostname_cleanup(hostname
),
808 os_name
? os_name
: "Linux",
815 return respond_oom(connection
);
817 response
= MHD_create_response_from_buffer(strlen(json
), json
, MHD_RESPMEM_MUST_FREE
);
820 return respond_oom(connection
);
823 MHD_add_response_header(response
, "Content-Type", "application/json");
824 r
= MHD_queue_response(connection
, MHD_HTTP_OK
, response
);
825 MHD_destroy_response(response
);
830 static int request_handler(
832 struct MHD_Connection
*connection
,
836 const char *upload_data
,
837 size_t *upload_data_size
,
838 void **connection_cls
) {
842 assert(connection_cls
);
846 if (!streq(method
, "GET"))
847 return mhd_respond(connection
, MHD_HTTP_NOT_ACCEPTABLE
, "Unsupported method.");
850 if (!*connection_cls
) {
851 if (!request_meta(connection_cls
))
852 return respond_oom(connection
);
857 r
= check_permissions(connection
, &code
, NULL
);
863 return request_handler_redirect(connection
, "/browse");
865 if (streq(url
, "/entries"))
866 return request_handler_entries(connection
, *connection_cls
);
868 if (startswith(url
, "/fields/"))
869 return request_handler_fields(connection
, url
+ 8, *connection_cls
);
871 if (streq(url
, "/browse"))
872 return request_handler_file(connection
, DOCUMENT_ROOT
"/browse.html", "text/html");
874 if (streq(url
, "/machine"))
875 return request_handler_machine(connection
, *connection_cls
);
877 return mhd_respond(connection
, MHD_HTTP_NOT_FOUND
, "Not found.");
880 static void help(void) {
881 printf("%s [OPTIONS...] ...\n\n"
882 "HTTP server for journal events.\n\n"
883 " -h --help Show this help\n"
884 " --version Show package version\n"
885 " --cert=CERT.PEM Server certificate in PEM format\n"
886 " --key=KEY.PEM Server key in PEM format\n"
887 " --trust=CERT.PEM Certificate authority certificate in PEM format\n"
888 " -D --directory=PATH Serve journal files in directory\n",
889 program_invocation_short_name
);
892 static int parse_argv(int argc
, char *argv
[]) {
902 static const struct option options
[] = {
903 { "help", no_argument
, NULL
, 'h' },
904 { "version", no_argument
, NULL
, ARG_VERSION
},
905 { "key", required_argument
, NULL
, ARG_KEY
},
906 { "cert", required_argument
, NULL
, ARG_CERT
},
907 { "trust", required_argument
, NULL
, ARG_TRUST
},
908 { "directory", required_argument
, NULL
, 'D' },
915 while ((c
= getopt_long(argc
, argv
, "hD:", options
, NULL
)) >= 0)
928 log_error("Key file specified twice");
931 r
= read_full_file(optarg
, &arg_key_pem
, NULL
);
933 return log_error_errno(r
, "Failed to read key file: %m");
939 log_error("Certificate file specified twice");
942 r
= read_full_file(optarg
, &arg_cert_pem
, NULL
);
944 return log_error_errno(r
, "Failed to read certificate file: %m");
945 assert(arg_cert_pem
);
951 log_error("CA certificate file specified twice");
954 r
= read_full_file(optarg
, &arg_trust_pem
, NULL
);
956 return log_error_errno(r
, "Failed to read CA certificate file: %m");
957 assert(arg_trust_pem
);
960 log_error("Option --trust is not available.");
964 arg_directory
= optarg
;
971 assert_not_reached("Unhandled option");
975 log_error("This program does not take arguments.");
979 if (!!arg_key_pem
!= !!arg_cert_pem
) {
980 log_error("Certificate and key files must be specified together");
984 if (arg_trust_pem
&& !arg_key_pem
) {
985 log_error("CA certificate can only be used with certificate file");
992 int main(int argc
, char *argv
[]) {
993 struct MHD_Daemon
*d
= NULL
;
996 log_set_target(LOG_TARGET_AUTO
);
997 log_parse_environment();
1000 r
= parse_argv(argc
, argv
);
1002 return EXIT_FAILURE
;
1004 return EXIT_SUCCESS
;
1008 r
= setup_gnutls_logger(NULL
);
1010 return EXIT_FAILURE
;
1012 n
= sd_listen_fds(1);
1014 log_error_errno(n
, "Failed to determine passed sockets: %m");
1017 log_error("Can't listen on more than one socket.");
1020 struct MHD_OptionItem opts
[] = {
1021 { MHD_OPTION_NOTIFY_COMPLETED
,
1022 (intptr_t) request_meta_free
, NULL
},
1023 { MHD_OPTION_EXTERNAL_LOGGER
,
1024 (intptr_t) microhttpd_logger
, NULL
},
1025 { MHD_OPTION_END
, 0, NULL
},
1026 { MHD_OPTION_END
, 0, NULL
},
1027 { MHD_OPTION_END
, 0, NULL
},
1028 { MHD_OPTION_END
, 0, NULL
},
1029 { MHD_OPTION_END
, 0, NULL
}};
1032 /* We force MHD_USE_ITC here, in order to make sure
1033 * libmicrohttpd doesn't use shutdown() on our listening
1034 * socket, which would break socket re-activation. See
1036 * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html
1037 * https://github.com/systemd/systemd/pull/1286
1042 MHD_USE_DUAL_STACK
|
1044 MHD_USE_POLL_INTERNAL_THREAD
|
1045 MHD_USE_THREAD_PER_CONNECTION
;
1048 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1049 {MHD_OPTION_LISTEN_SOCKET
, SD_LISTEN_FDS_START
};
1051 assert(arg_cert_pem
);
1052 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1053 {MHD_OPTION_HTTPS_MEM_KEY
, 0, arg_key_pem
};
1054 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1055 {MHD_OPTION_HTTPS_MEM_CERT
, 0, arg_cert_pem
};
1056 flags
|= MHD_USE_TLS
;
1058 if (arg_trust_pem
) {
1059 assert(flags
& MHD_USE_TLS
);
1060 opts
[opts_pos
++] = (struct MHD_OptionItem
)
1061 {MHD_OPTION_HTTPS_MEM_TRUST
, 0, arg_trust_pem
};
1064 d
= MHD_start_daemon(flags
, 19531,
1066 request_handler
, NULL
,
1067 MHD_OPTION_ARRAY
, opts
,
1072 log_error("Failed to start daemon!");