#include "base.h" #include "log.h" #include "array.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYSLOG_H # include #endif #ifdef HAVE_VALGRIND_VALGRIND_H # include #endif #ifndef O_LARGEFILE # define O_LARGEFILE 0 #endif /* Close fd and _try_ to get a /dev/null for it instead. * close() alone may trigger some bugs when a * process opens another file and gets fd = STDOUT_FILENO or STDERR_FILENO * and later tries to just print on stdout/stderr * * Returns 0 on success and -1 on failure (fd gets closed in all cases) */ int openDevNull(int fd) { int tmpfd; close(fd); #if defined(__WIN32) /* Cygwin should work with /dev/null */ tmpfd = open("nul", O_RDWR); #else tmpfd = open("/dev/null", O_RDWR); #endif if (tmpfd != -1 && tmpfd != fd) { dup2(tmpfd, fd); close(tmpfd); } return (tmpfd != -1) ? 0 : -1; } int open_logfile_or_pipe(server *srv, const char* logfile) { int fd; if (logfile[0] == '|') { #ifdef HAVE_FORK /* create write pipe and spawn process */ int to_log_fds[2]; if (pipe(to_log_fds)) { log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed: ", strerror(errno)); return -1; } /* fork, execve */ switch (fork()) { case 0: /* child */ close(STDIN_FILENO); /* dup the filehandle to STDIN */ if (to_log_fds[0] != STDIN_FILENO) { if (STDIN_FILENO != dup2(to_log_fds[0], STDIN_FILENO)) { log_error_write(srv, __FILE__, __LINE__, "ss", "dup2 failed: ", strerror(errno)); exit(-1); } close(to_log_fds[0]); } close(to_log_fds[1]); #ifndef FD_CLOEXEC { int i; /* we don't need the client socket */ for (i = 3; i < 256; i++) { close(i); } } #endif /* close old stderr */ openDevNull(STDERR_FILENO); /* exec the log-process (skip the | ) */ execl("/bin/sh", "sh", "-c", logfile + 1, NULL); log_error_write(srv, __FILE__, __LINE__, "sss", "spawning log process failed: ", strerror(errno), logfile + 1); exit(-1); break; case -1: /* error */ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed: ", strerror(errno)); return -1; default: close(to_log_fds[0]); fd = to_log_fds[1]; break; } #else return -1; #endif } else if (-1 == (fd = open(logfile, O_APPEND | O_WRONLY | O_CREAT | O_LARGEFILE, 0644))) { log_error_write(srv, __FILE__, __LINE__, "SSSS", "opening errorlog '", logfile, "' failed: ", strerror(errno)); return -1; } #ifdef FD_CLOEXEC fcntl(fd, F_SETFD, FD_CLOEXEC); #endif return fd; } /** * open the errorlog * * we have 4 possibilities: * - stderr (default) * - syslog * - logfile * - pipe * * if the open failed, report to the user and die * */ int log_error_open(server *srv) { #ifdef HAVE_SYSLOG_H /* perhaps someone wants to use syslog() */ openlog("lighttpd", LOG_CONS | LOG_PID, LOG_DAEMON); #endif srv->errorlog_mode = ERRORLOG_FD; srv->errorlog_fd = STDERR_FILENO; if (srv->srvconf.errorlog_use_syslog) { srv->errorlog_mode = ERRORLOG_SYSLOG; } else if (!buffer_is_empty(srv->srvconf.errorlog_file)) { const char *logfile = srv->srvconf.errorlog_file->ptr; if (-1 == (srv->errorlog_fd = open_logfile_or_pipe(srv, logfile))) { return -1; } srv->errorlog_mode = (logfile[0] == '|') ? ERRORLOG_PIPE : ERRORLOG_FILE; } log_error_write(srv, __FILE__, __LINE__, "s", "server started"); if (srv->errorlog_mode == ERRORLOG_FD && !srv->srvconf.dont_daemonize) { /* We can only log to stderr in dont-daemonize mode; * if we do daemonize and no errorlog file is specified, we log into /dev/null */ srv->errorlog_fd = -1; } if (!buffer_is_empty(srv->srvconf.breakagelog_file)) { int breakage_fd; const char *logfile = srv->srvconf.breakagelog_file->ptr; if (srv->errorlog_mode == ERRORLOG_FD) { srv->errorlog_fd = dup(STDERR_FILENO); #ifdef FD_CLOEXEC fcntl(srv->errorlog_fd, F_SETFD, FD_CLOEXEC); #endif } if (-1 == (breakage_fd = open_logfile_or_pipe(srv, logfile))) { return -1; } if (STDERR_FILENO != breakage_fd) { dup2(breakage_fd, STDERR_FILENO); close(breakage_fd); } } else if (!srv->srvconf.dont_daemonize) { /* move stderr to /dev/null */ openDevNull(STDERR_FILENO); } return 0; } /** * open the errorlog * * if the open failed, report to the user and die * if no filename is given, use syslog instead * */ int log_error_cycle(server *srv) { /* only cycle if the error log is a file */ if (srv->errorlog_mode == ERRORLOG_FILE) { const char *logfile = srv->srvconf.errorlog_file->ptr; /* already check of opening time */ int new_fd; if (-1 == (new_fd = open_logfile_or_pipe(srv, logfile))) { /* write to old log */ log_error_write(srv, __FILE__, __LINE__, "SSSSS", "cycling errorlog '", logfile, "' failed: ", strerror(errno), ", falling back to syslog()"); close(srv->errorlog_fd); srv->errorlog_fd = -1; #ifdef HAVE_SYSLOG_H srv->errorlog_mode = ERRORLOG_SYSLOG; #endif } else { /* ok, new log is open, close the old one */ close(srv->errorlog_fd); srv->errorlog_fd = new_fd; #ifdef FD_CLOEXEC /* close fd on exec (cgi) */ fcntl(srv->errorlog_fd, F_SETFD, FD_CLOEXEC); #endif } } return 0; } int log_error_close(server *srv) { switch(srv->errorlog_mode) { case ERRORLOG_PIPE: case ERRORLOG_FILE: case ERRORLOG_FD: if (-1 != srv->errorlog_fd) { /* don't close STDERR */ if (STDERR_FILENO != srv->errorlog_fd) close(srv->errorlog_fd); srv->errorlog_fd = -1; } break; case ERRORLOG_SYSLOG: #ifdef HAVE_SYSLOG_H closelog(); #endif break; } return 0; } /* lowercase: append space, uppercase: don't */ static void log_buffer_append_printf(buffer *out, const char *fmt, va_list ap) { for(; *fmt; fmt++) { int d; char *s; buffer *b; off_t o; switch(*fmt) { case 's': /* string */ s = va_arg(ap, char *); buffer_append_string(out, s); buffer_append_string_len(out, CONST_STR_LEN(" ")); break; case 'b': /* buffer */ b = va_arg(ap, buffer *); buffer_append_string_buffer(out, b); buffer_append_string_len(out, CONST_STR_LEN(" ")); break; case 'd': /* int */ d = va_arg(ap, int); buffer_append_long(out, d); buffer_append_string_len(out, CONST_STR_LEN(" ")); break; case 'o': /* off_t */ o = va_arg(ap, off_t); buffer_append_off_t(out, o); buffer_append_string_len(out, CONST_STR_LEN(" ")); break; case 'x': /* int (hex) */ d = va_arg(ap, int); buffer_append_string_len(out, CONST_STR_LEN("0x")); buffer_append_long_hex(out, d); buffer_append_string_len(out, CONST_STR_LEN(" ")); break; case 'S': /* string */ s = va_arg(ap, char *); buffer_append_string(out, s); break; case 'B': /* buffer */ b = va_arg(ap, buffer *); buffer_append_string_buffer(out, b); break; case 'D': /* int */ d = va_arg(ap, int); buffer_append_long(out, d); break; case 'O': /* off_t */ o = va_arg(ap, off_t); buffer_append_off_t(out, o); break; case 'X': /* int (hex) */ d = va_arg(ap, int); buffer_append_string_len(out, CONST_STR_LEN("0x")); buffer_append_long_hex(out, d); break; case '(': case ')': case '<': case '>': case ',': case ' ': buffer_append_string_len(out, fmt, 1); break; } } } static int log_buffer_prepare(buffer *b, server *srv, const char *filename, unsigned int line) { switch(srv->errorlog_mode) { case ERRORLOG_PIPE: case ERRORLOG_FILE: case ERRORLOG_FD: if (-1 == srv->errorlog_fd) return -1; /* cache the generated timestamp */ if (srv->cur_ts != srv->last_generated_debug_ts) { buffer_prepare_copy(srv->ts_debug_str, 255); strftime(srv->ts_debug_str->ptr, srv->ts_debug_str->size - 1, "%Y-%m-%d %H:%M:%S", localtime(&(srv->cur_ts))); srv->ts_debug_str->used = strlen(srv->ts_debug_str->ptr) + 1; srv->last_generated_debug_ts = srv->cur_ts; } buffer_copy_string_buffer(b, srv->ts_debug_str); buffer_append_string_len(b, CONST_STR_LEN(": (")); break; case ERRORLOG_SYSLOG: /* syslog is generating its own timestamps */ buffer_copy_string_len(b, CONST_STR_LEN("(")); break; } buffer_append_string(b, filename); buffer_append_string_len(b, CONST_STR_LEN(".")); buffer_append_long(b, line); buffer_append_string_len(b, CONST_STR_LEN(") ")); return 0; } static void log_write(server *srv, buffer *b) { switch(srv->errorlog_mode) { case ERRORLOG_PIPE: case ERRORLOG_FILE: case ERRORLOG_FD: buffer_append_string_len(b, CONST_STR_LEN("\n")); write(srv->errorlog_fd, b->ptr, b->used - 1); break; case ERRORLOG_SYSLOG: syslog(LOG_ERR, "%s", b->ptr); break; } } int log_error_write(server *srv, const char *filename, unsigned int line, const char *fmt, ...) { va_list ap; if (-1 == log_buffer_prepare(srv->errorlog_buf, srv, filename, line)) return 0; va_start(ap, fmt); log_buffer_append_printf(srv->errorlog_buf, fmt, ap); va_end(ap); log_write(srv, srv->errorlog_buf); return 0; } int log_error_write_multiline_buffer(server *srv, const char *filename, unsigned int line, buffer *multiline, const char *fmt, ...) { va_list ap; size_t prefix_used; buffer *b = srv->errorlog_buf; char *pos, *end, *current_line; if (multiline->used < 2) return 0; if (-1 == log_buffer_prepare(b, srv, filename, line)) return 0; va_start(ap, fmt); log_buffer_append_printf(b, fmt, ap); va_end(ap); prefix_used = b->used; current_line = pos = multiline->ptr; end = multiline->ptr + multiline->used; for ( ; pos < end ; ++pos) { switch (*pos) { case '\n': case '\r': case '\0': /* handles end of string */ if (current_line < pos) { /* truncate to prefix */ b->used = prefix_used; b->ptr[b->used - 1] = '\0'; buffer_append_string_len(b, current_line, pos - current_line); log_write(srv, b); } current_line = pos + 1; break; default: break; } } return 0; }