summaryrefslogblamecommitdiffstats
path: root/src/logerr.c
blob: 7a650e87f2c70b7eabea7f61db7b2cebb208b5db (plain) (tree)
1
2
3
4
                                           

                            
                                                         

























                                                                             
                    

                   
                   

                   
                 
                   


                   

                                          
      
 



                 
                                                             
                                                                  
 
                                         

               
                                         
                                  

                                 
             
                                  


                                 
      


                                

                                                                  

                     

  
                      












                                                                 




                                                                               
         








                                   

                                                    
          







                                          
                          

                        




                                                                 


      
                             
                                                                             
 
                       
                  
             




                     

                                                                    




                                                     
 
                 

                                                                          


                                                     


                                                                    
         
      
 

                                                                          
                      






                                                             


                                  
 
                 
                               


                    




                                                      


                    

                         




                                       
                  

                                      
















                                                                                
                             

                                                   
                                      
                    
 
                                
                                           
                          

                                               



                                                                

                               

                                                                         


                           

                                         
                                                                   
                                                                
                                                           
 





                                                                  


                                        
                   




                                                                                
                       
















                                                         
                       

 









                                            
    
                               



                            




                                             
                                



                            




                                          
                              



                            




                                            
                               



                            




                                         
                              








                                               
                               








                                            
                             








                                           
                              



                            
                                        


                     
   
              


                                      
                           


    
                


                                      






                                                


   
                 
 
                                      






                                              



                                                           




                                       


                                                                        


                   







                                      


                             
                                      
 
                             


                                                                         
                 


                          
                   






                                      
      
 


                         
                                      
                     
 


                            
                                                                          
 






                                      

                                           
                                                             
                         
                         
 
             
                                                        
                          





                                     


    
              
 


                                      

                   



                        

                                  
                       


                              
 
/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * logerr: errx with logging
 * Copyright (c) 2006-2021 Roy Marples <roy@marples.name>
 * All rights reserved

 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/time.h>
#include <errno.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#include "logerr.h"

#ifndef	LOGERR_SYSLOG_FACILITY
#define	LOGERR_SYSLOG_FACILITY	LOG_DAEMON
#endif

#ifdef SMALL
#undef LOGERR_TAG
#endif

/* syslog protocol is 1k message max, RFC 3164 section 4.1 */
#define LOGERR_SYSLOGBUF	1024 + sizeof(int) + sizeof(pid_t)

#define UNUSED(a)		(void)(a)

struct logctx {
	char		 log_buf[BUFSIZ];
	unsigned int	 log_opts;
	int		 log_fd;
	pid_t		 log_pid;
#ifndef SMALL
	FILE		*log_file;
#ifdef LOGERR_TAG
	const char	*log_tag;
#endif
#endif
};

static struct logctx _logctx = {
	/* syslog style, but without the hostname or tag. */
	.log_opts = LOGERR_LOG | LOGERR_LOG_DATE | LOGERR_LOG_PID,
	.log_fd = -1,
	.log_pid = 0,
};

#if defined(__linux__)
/* Poor man's getprogname(3). */
static char *_logprog;
static const char *
getprogname(void)
{
	const char *p;

	/* Use PATH_MAX + 1 to avoid truncation. */
	if (_logprog == NULL) {
		/* readlink(2) does not append a NULL byte,
		 * so zero the buffer. */
		if ((_logprog = calloc(1, PATH_MAX + 1)) == NULL)
			return NULL;
		if (readlink("/proc/self/exe", _logprog, PATH_MAX + 1) == -1) {
			free(_logprog);
			_logprog = NULL;
			return NULL;
		}
	}
	if (_logprog[0] == '[')
		return NULL;
	p = strrchr(_logprog, '/');
	if (p == NULL)
		return _logprog;
	return p + 1;
}
#endif

#ifndef SMALL
/* Write the time, syslog style. month day time - */
static int
logprintdate(FILE *stream)
{
	struct timeval tv;
	time_t now;
	struct tm tmnow;
	char buf[32];

	if (gettimeofday(&tv, NULL) == -1)
		return -1;

	now = tv.tv_sec;
	if (localtime_r(&now, &tmnow) == NULL)
		return -1;
	if (strftime(buf, sizeof(buf), "%b %d %T ", &tmnow) == 0)
		return -1;
	return fprintf(stream, "%s", buf);
}
#endif

__printflike(3, 0) static int
vlogprintf_r(struct logctx *ctx, FILE *stream, const char *fmt, va_list args)
{
	int len = 0, e;
	va_list a;
#ifndef SMALL
	bool log_pid;
#ifdef LOGERR_TAG
	bool log_tag;
#endif

	if ((stream == stderr && ctx->log_opts & LOGERR_ERR_DATE) ||
	    (stream != stderr && ctx->log_opts & LOGERR_LOG_DATE))
	{
		if ((e = logprintdate(stream)) == -1)
			return -1;
		len += e;
	}

#ifdef LOGERR_TAG
	log_tag = ((stream == stderr && ctx->log_opts & LOGERR_ERR_TAG) ||
	    (stream != stderr && ctx->log_opts & LOGERR_LOG_TAG));
	if (log_tag) {
		if (ctx->log_tag == NULL)
			ctx->log_tag = getprogname();
		if ((e = fprintf(stream, "%s", ctx->log_tag)) == -1)
			return -1;
		len += e;
	}
#endif

	log_pid = ((stream == stderr && ctx->log_opts & LOGERR_ERR_PID) ||
	    (stream != stderr && ctx->log_opts & LOGERR_LOG_PID));
	if (log_pid) {
		pid_t pid;

		if (ctx->log_pid == 0)
			pid = getpid();
		else
			pid = ctx->log_pid;
		if ((e = fprintf(stream, "[%d]", pid)) == -1)
			return -1;
		len += e;
	}

#ifdef LOGERR_TAG
	if (log_tag || log_pid)
#else
	if (log_pid)
#endif
	{
		if ((e = fprintf(stream, ": ")) == -1)
			return -1;
		len += e;
	}
#else
	UNUSED(ctx);
#endif

	va_copy(a, args);
	e = vfprintf(stream, fmt, a);
	if (fputc('\n', stream) == EOF)
		e = -1;
	else if (e != -1)
		e++;
	va_end(a);

	return e == -1 ? -1 : len + e;
}

/*
 * NetBSD's gcc has been modified to check for the non standard %m in printf
 * like functions and warn noisily about it that they should be marked as
 * syslog like instead.
 * This is all well and good, but our logger also goes via vfprintf and
 * when marked as a sysloglike funcion, gcc will then warn us that the
 * function should be printflike instead!
 * This creates an infinte loop of gcc warnings.
 * Until NetBSD solves this issue, we have to disable a gcc diagnostic
 * for our fully standards compliant code in the logger function.
 */
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-format-attribute"
#endif
__printflike(2, 0) static int
vlogmessage(int pri, const char *fmt, va_list args)
{
	struct logctx *ctx = &_logctx;
	int len = 0;

	if (ctx->log_fd != -1) {
		char buf[LOGERR_SYSLOGBUF];
		pid_t pid;

		memcpy(buf, &pri, sizeof(pri));
		pid = getpid();
		memcpy(buf + sizeof(pri), &pid, sizeof(pid));
		len = vsnprintf(buf + sizeof(pri) + sizeof(pid),
		    sizeof(buf) - sizeof(pri) - sizeof(pid),
		    fmt, args);
		if (len != -1)
			len = (int)write(ctx->log_fd, buf,
			    ((size_t)++len) + sizeof(pri) + sizeof(pid));
		return len;
	}

	if (ctx->log_opts & LOGERR_ERR &&
	    (pri <= LOG_ERR ||
	    (!(ctx->log_opts & LOGERR_QUIET) && pri <= LOG_INFO) ||
	    (ctx->log_opts & LOGERR_DEBUG && pri <= LOG_DEBUG)))
		len = vlogprintf_r(ctx, stderr, fmt, args);

#ifndef SMALL
	if (ctx->log_file != NULL &&
	    (pri != LOG_DEBUG || (ctx->log_opts & LOGERR_DEBUG)))
		len = vlogprintf_r(ctx, ctx->log_file, fmt, args);
#endif

	if (ctx->log_opts & LOGERR_LOG)
		vsyslog(pri, fmt, args);

	return len;
}
#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 5))
#pragma GCC diagnostic pop
#endif

__printflike(2, 3) void
logmessage(int pri, const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vlogmessage(pri, fmt, args);
	va_end(args);
}

__printflike(2, 0) static void
vlogerrmessage(int pri, const char *fmt, va_list args)
{
	int _errno = errno;
	char buf[1024];

	vsnprintf(buf, sizeof(buf), fmt, args);
	logmessage(pri, "%s: %s", buf, strerror(_errno));
	errno = _errno;
}

__printflike(2, 3) void
logerrmessage(int pri, const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vlogerrmessage(pri, fmt, args);
	va_end(args);
}

void
log_debug(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vlogerrmessage(LOG_DEBUG, fmt, args);
	va_end(args);
}

void
log_debugx(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vlogmessage(LOG_DEBUG, fmt, args);
	va_end(args);
}

void
log_info(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vlogerrmessage(LOG_INFO, fmt, args);
	va_end(args);
}

void
log_infox(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vlogmessage(LOG_INFO, fmt, args);
	va_end(args);
}

void
log_warn(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vlogerrmessage(LOG_WARNING, fmt, args);
	va_end(args);
}

void
log_warnx(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vlogmessage(LOG_WARNING, fmt, args);
	va_end(args);
}

void
log_err(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vlogerrmessage(LOG_ERR, fmt, args);
	va_end(args);
}

void
log_errx(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vlogmessage(LOG_ERR, fmt, args);
	va_end(args);
}

int
loggetfd(void)
{
	struct logctx *ctx = &_logctx;

	return ctx->log_fd;
}

void
logsetfd(int fd)
{
	struct logctx *ctx = &_logctx;

	ctx->log_fd = fd;
#ifndef SMALL
	if (fd != -1 && ctx->log_file != NULL) {
		fclose(ctx->log_file);
		ctx->log_file = NULL;
	}
#endif
}

int
logreadfd(int fd)
{
	struct logctx *ctx = &_logctx;
	char buf[LOGERR_SYSLOGBUF];
	int len, pri;

	len = (int)read(fd, buf, sizeof(buf));
	if (len == -1)
		return -1;

	/* Ensure we have pri, pid and a terminator */
	if (len < (int)(sizeof(pri) + sizeof(pid_t) + 1) ||
	    buf[len - 1] != '\0')
	{
		errno = EINVAL;
		return -1;
	}

	memcpy(&pri, buf, sizeof(pri));
	memcpy(&ctx->log_pid, buf + sizeof(pri), sizeof(ctx->log_pid));
	logmessage(pri, "%s", buf + sizeof(pri) + sizeof(ctx->log_pid));
	ctx->log_pid = 0;
	return len;
}

unsigned int
loggetopts(void)
{
	struct logctx *ctx = &_logctx;

	return ctx->log_opts;
}

void
logsetopts(unsigned int opts)
{
	struct logctx *ctx = &_logctx;

	ctx->log_opts = opts;
	setlogmask(LOG_UPTO(opts & LOGERR_DEBUG ? LOG_DEBUG : LOG_INFO));
}

#ifdef LOGERR_TAG
void
logsettag(const char *tag)
{
#if !defined(SMALL)
	struct logctx *ctx = &_logctx;

	ctx->log_tag = tag;
#else
	UNUSED(tag);
#endif
}
#endif

int
logopen(const char *path)
{
	struct logctx *ctx = &_logctx;
	int opts = 0;

	/* Cache timezone */
	tzset();

	(void)setvbuf(stderr, ctx->log_buf, _IOLBF, sizeof(ctx->log_buf));

#ifndef SMALL
	if (ctx->log_file != NULL) {
		fclose(ctx->log_file);
		ctx->log_file = NULL;
	}
#endif

	if (ctx->log_opts & LOGERR_LOG_PID)
		opts |= LOG_PID;
	openlog(getprogname(), opts, LOGERR_SYSLOG_FACILITY);
	if (path == NULL)
		return 1;

#ifndef SMALL
	if ((ctx->log_file = fopen(path, "ae")) == NULL)
		return -1;
	setlinebuf(ctx->log_file);
	return fileno(ctx->log_file);
#else
	errno = ENOTSUP;
	return -1;
#endif
}

void
logclose(void)
{
#ifndef SMALL
	struct logctx *ctx = &_logctx;
#endif

	closelog();
#if defined(__linux__)
	free(_logprog);
	_logprog = NULL;
#endif
#ifndef SMALL
	if (ctx->log_file == NULL)
		return;
	fclose(ctx->log_file);
	ctx->log_file = NULL;
#endif
}