489 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			489 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
| /* syslog.cc
 | |
| 
 | |
| This file is part of Cygwin.
 | |
| 
 | |
| This software is a copyrighted work licensed under the terms of the
 | |
| Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
 | |
| details. */
 | |
| 
 | |
| #define  __INSIDE_CYGWIN_NET__
 | |
| #define USE_SYS_TYPES_FD_SET
 | |
| #include "winsup.h"
 | |
| #include <ws2tcpip.h>
 | |
| #include <iphlpapi.h>
 | |
| #include <stdlib.h>
 | |
| #include <stdio.h>
 | |
| #include <syslog.h>
 | |
| #include <unistd.h>
 | |
| #include <sys/un.h>
 | |
| #include "cygerrno.h"
 | |
| #include "security.h"
 | |
| #include "path.h"
 | |
| #include "fhandler.h"
 | |
| #include "dtable.h"
 | |
| #include "cygheap.h"
 | |
| #include "cygtls.h"
 | |
| #include "tls_pbuf.h"
 | |
| 
 | |
| #define CYGWIN_LOG_NAME L"Cygwin"
 | |
| 
 | |
| static struct
 | |
| {
 | |
|   wchar_t *process_ident;
 | |
|   int process_logopt;
 | |
|   int process_facility;
 | |
|   int process_logmask;
 | |
| } syslog_globals = { NULL, 0, 0, LOG_UPTO (LOG_DEBUG) };
 | |
| 
 | |
| /* openlog: save the passed args. Don't open the system log or /dev/log yet.  */
 | |
| extern "C" void
 | |
| openlog (const char *ident, int logopt, int facility)
 | |
| {
 | |
|     wchar_t *new_ident = NULL;
 | |
| 
 | |
|     debug_printf ("openlog called with (%s, %d, %d)",
 | |
| 		  ident ? ident : "<NULL>", logopt, facility);
 | |
| 
 | |
|     if (ident)
 | |
|       {
 | |
| 	sys_mbstowcs_alloc (&new_ident, HEAP_NOTHEAP, ident);
 | |
| 	if (!new_ident)
 | |
| 	    debug_printf ("failed to allocate memory for "
 | |
| 			  "syslog_globals.process_ident");
 | |
| 	else
 | |
| 	  {
 | |
| 	    wchar_t *old_ident = syslog_globals.process_ident;
 | |
| 	    syslog_globals.process_ident = new_ident;
 | |
| 	    if (old_ident)
 | |
| 	      free (old_ident);
 | |
| 	  }
 | |
|       }
 | |
|     syslog_globals.process_logopt = logopt;
 | |
|     syslog_globals.process_facility = facility;
 | |
| }
 | |
| 
 | |
| /* setlogmask: set the log priority mask and return previous mask.
 | |
|    If maskpri is zero, just return previous. */
 | |
| int
 | |
| setlogmask (int maskpri)
 | |
| {
 | |
|   if (maskpri == 0)
 | |
|     return syslog_globals.process_logmask;
 | |
| 
 | |
|   int old_mask = syslog_globals.process_logmask;
 | |
|   syslog_globals.process_logmask = maskpri;
 | |
| 
 | |
|   return old_mask;
 | |
| }
 | |
| 
 | |
| /* Private class used to handle formatting of syslog message
 | |
|    It is named pass_handler because it does a two-pass handling of log
 | |
|    strings.  The first pass counts the length of the string, and the second
 | |
|    one builds the string. */
 | |
| 
 | |
| class pass_handler
 | |
| {
 | |
|   private:
 | |
|     FILE *fp_;
 | |
|     char *message_;
 | |
|     int total_len_;
 | |
| 
 | |
|     void shutdown ();
 | |
| 
 | |
|     /* Explicitly disallow copies */
 | |
|     pass_handler (const pass_handler &);
 | |
|     pass_handler & operator = (const pass_handler &);
 | |
| 
 | |
|   public:
 | |
|     pass_handler ();
 | |
|     ~pass_handler ();
 | |
| 
 | |
|     int initialize (int);
 | |
| 
 | |
|     int print (const char *,...);
 | |
|     int print_va (const char *, va_list);
 | |
|     char *get_message () const { return message_; }
 | |
|     void set_message (char *s) { message_ = s; *message_ = '\0'; }
 | |
| };
 | |
| 
 | |
| pass_handler::pass_handler () : fp_ (0), message_ (0), total_len_ (0)
 | |
| {
 | |
|   ;
 | |
| }
 | |
| 
 | |
| pass_handler::~pass_handler ()
 | |
| {
 | |
|   shutdown ();
 | |
| }
 | |
| 
 | |
| void
 | |
| pass_handler::shutdown ()
 | |
| {
 | |
|   if (fp_ != NULL)
 | |
|     {
 | |
|       fclose (fp_);
 | |
|       fp_ = 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| int
 | |
| pass_handler::initialize (int pass_number)
 | |
| {
 | |
|     shutdown ();
 | |
|     if (pass_number)
 | |
|       return total_len_ + 1;
 | |
| 
 | |
|     fp_ = fopen ("/dev/null", "wb");
 | |
|     setbuf (fp_, NULL);
 | |
|     if (fp_ == NULL)
 | |
|       {
 | |
| 	debug_printf ("failed to open /dev/null");
 | |
| 	return -1;
 | |
|       }
 | |
|     total_len_ = 0;
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| int
 | |
| pass_handler::print (const char *fmt, ...)
 | |
| {
 | |
|     va_list ap;
 | |
|     va_start (ap, fmt);
 | |
|     int ret = print_va (fmt, ap);
 | |
|     va_end (ap);
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| int
 | |
| pass_handler::print_va (const char *fmt, va_list list)
 | |
| {
 | |
|     if (fp_ != NULL)
 | |
|       {
 | |
| 	int len = vfprintf (fp_, fmt, list);
 | |
| 	if (len < 0)
 | |
| 	  return -1;
 | |
| 	total_len_ += len;
 | |
| 	return 0;
 | |
|       }
 | |
|     else if (message_ != NULL)
 | |
|       {
 | |
| 	char *printpos = &message_[strlen (message_)];
 | |
| 	vsprintf (printpos, fmt, list);
 | |
| 	return 0;
 | |
|       }
 | |
|     debug_printf ("FAILURE ! fp_ and message_ both 0!! ");
 | |
|     return -1;
 | |
| }
 | |
| 
 | |
| static NO_COPY muto try_connect_guard;
 | |
| static enum {
 | |
|   not_inited,
 | |
|   inited_failed,
 | |
|   inited_dgram,
 | |
|   inited_stream
 | |
| } syslogd_inited;
 | |
| static int syslogd_sock = -1;
 | |
| extern "C" int cygwin_socket (int, int, int);
 | |
| extern "C" int cygwin_connect (int, const struct sockaddr *, int);
 | |
| extern int get_inet_addr (const struct sockaddr *, int,
 | |
| 			  struct sockaddr_storage *, int *,
 | |
| 			  int * = NULL, int * = NULL);
 | |
| 
 | |
| static void
 | |
| connect_syslogd ()
 | |
| {
 | |
|   int fd;
 | |
|   struct sockaddr_un sun;
 | |
|   struct sockaddr_storage sst;
 | |
|   int len, type;
 | |
| 
 | |
|   if (syslogd_inited != not_inited && syslogd_sock >= 0)
 | |
|     close (syslogd_sock);
 | |
|   syslogd_inited = inited_failed;
 | |
|   syslogd_sock = -1;
 | |
|   sun.sun_family = AF_LOCAL;
 | |
|   strncpy (sun.sun_path, _PATH_LOG, sizeof sun.sun_path);
 | |
|   if (get_inet_addr ((struct sockaddr *) &sun, sizeof sun, &sst, &len, &type))
 | |
|     return;
 | |
|   if ((fd = cygwin_socket (AF_LOCAL, type, 0)) < 0)
 | |
|     return;
 | |
|   if (cygwin_connect (fd, (struct sockaddr *) &sun, sizeof sun) == 0)
 | |
|     {
 | |
|       /* connect on a dgram socket always succeeds.  We still don't know
 | |
| 	 if syslogd is actually listening. */
 | |
|       if (type == SOCK_DGRAM)
 | |
| 	{
 | |
| 	  tmp_pathbuf tp;
 | |
| 	  PMIB_UDPTABLE tab = (PMIB_UDPTABLE) tp.w_get ();
 | |
| 	  DWORD size = 65536;
 | |
| 	  bool found = false;
 | |
| 	  struct sockaddr_in *sa = (struct sockaddr_in *) &sst;
 | |
| 
 | |
| 	  if (GetUdpTable (tab, &size, FALSE) == NO_ERROR)
 | |
| 	    {
 | |
| 	      for (DWORD i = 0; i < tab->dwNumEntries; ++i)
 | |
| 		if (tab->table[i].dwLocalAddr == sa->sin_addr.s_addr
 | |
| 		    && tab->table[i].dwLocalPort == sa->sin_port)
 | |
| 		  {
 | |
| 		    found = true;
 | |
| 		    break;
 | |
| 		  }
 | |
| 	      if (!found)
 | |
| 		{
 | |
| 		  /* No syslogd is listening. */
 | |
| 		  close (fd);
 | |
| 		  return;
 | |
| 		}
 | |
| 	    }
 | |
| 	}
 | |
|       syslogd_inited = type == SOCK_DGRAM ? inited_dgram : inited_stream;
 | |
|     }
 | |
|   syslogd_sock = fd;
 | |
|   fcntl64 (syslogd_sock, F_SETFD, FD_CLOEXEC);
 | |
|   debug_printf ("found /dev/log, fd = %d, type = %s",
 | |
| 		fd, syslogd_inited == inited_stream ? "STREAM" : "DGRAM");
 | |
|   return;
 | |
| }
 | |
| 
 | |
| static int
 | |
| try_connect_syslogd (int priority, const char *msg, size_t len)
 | |
| {
 | |
|   ssize_t ret = -1;
 | |
| 
 | |
|   try_connect_guard.init ("try_connect_guard")->acquire ();
 | |
|   if (syslogd_inited == not_inited)
 | |
|     connect_syslogd ();
 | |
|   if (syslogd_inited != inited_failed)
 | |
|     {
 | |
|       char pribuf[16];
 | |
|       sprintf (pribuf, "<%d>", priority);
 | |
|       struct iovec iv[2] =
 | |
|       {
 | |
| 	{ pribuf, strlen (pribuf) },
 | |
| 	{ (char *) msg, len }
 | |
|       };
 | |
| 
 | |
|       ret = writev (syslogd_sock, iv, 2);
 | |
|       /* If the syslog daemon has been restarted and /dev/log was
 | |
| 	 a stream socket, the connection is broken.  In this case,
 | |
| 	 try to reopen the socket and try again. */
 | |
|       if (ret < 0 && syslogd_inited == inited_stream)
 | |
| 	{
 | |
| 	  connect_syslogd ();
 | |
| 	  if (syslogd_sock >= 0)
 | |
| 	    ret = writev (syslogd_sock, iv, 2);
 | |
| 	}
 | |
|       /* If write fails and LOG_CONS is set, return failure to vsyslog so
 | |
| 	 it falls back to the usual logging method for this OS. */
 | |
|       if (ret >= 0 || !(syslog_globals.process_logopt & LOG_CONS))
 | |
| 	ret = syslogd_sock;
 | |
|     }
 | |
|   try_connect_guard.release ();
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| /* syslog: creates the log message and writes to /dev/log, or to the
 | |
|    NT system log if /dev/log isn't available.
 | |
| 
 | |
|    FIXME. WinNT system log messages don't look pretty, but in order to
 | |
|    fix this we have to embed resources in the code and tell the NT
 | |
|    registry where we are, blech (what happens if we move ?).  We could,
 | |
|    however, add the resources in Cygwin and always point to that. */
 | |
| 
 | |
| extern "C" void
 | |
| vsyslog (int priority, const char *message, va_list ap)
 | |
| {
 | |
|   debug_printf ("%y %s", priority, message);
 | |
|   /* If the priority fails the current mask, reject */
 | |
|   if ((LOG_MASK (LOG_PRI (priority)) & syslog_globals.process_logmask) == 0)
 | |
|     {
 | |
|       debug_printf ("failing message %y due to priority mask %y",
 | |
| 		    priority, syslog_globals.process_logmask);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   /* Set default facility to LOG_USER if not yet set via openlog. */
 | |
|   if (!syslog_globals.process_facility)
 | |
|     syslog_globals.process_facility = LOG_USER;
 | |
| 
 | |
|   /* Add default facility if not in the given priority. */
 | |
|   if (!(priority & LOG_FACMASK))
 | |
|     priority |= syslog_globals.process_facility;
 | |
| 
 | |
|   /* Translate %m in the message to error text */
 | |
|   char *errtext = strerror (get_errno ());
 | |
|   int errlen = strlen (errtext);
 | |
|   int numfound = 0;
 | |
| 
 | |
|   for (const char *cp = message; *cp; cp++)
 | |
|     if (*cp == '%' && cp[1] == 'm')
 | |
|       numfound++;
 | |
| 
 | |
|   char *newmessage = (char *) alloca (strlen (message) +
 | |
| 				      (errlen * numfound) + 1);
 | |
| 
 | |
|   if (newmessage == NULL)
 | |
|     {
 | |
|       debug_printf ("failed to allocate newmessage");
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   char *dst = newmessage;
 | |
|   for (const char *cp2 = message; *cp2; cp2++)
 | |
|     if (*cp2 == '%' && cp2[1] == 'm')
 | |
|       {
 | |
| 	cp2++;
 | |
| 	strcpy (dst, errtext);
 | |
| 	while (*dst)
 | |
| 	  dst++;
 | |
|       }
 | |
|     else
 | |
|       *dst++ = *cp2;
 | |
| 
 | |
|   *dst = '\0';
 | |
|   message = newmessage;
 | |
| 
 | |
|   /* Work out the priority type - we ignore the facility for now.. */
 | |
|   WORD eventType;
 | |
|   switch (LOG_PRI (priority))
 | |
|     {
 | |
|     case LOG_EMERG:
 | |
|     case LOG_ALERT:
 | |
|     case LOG_CRIT:
 | |
|     case LOG_ERR:
 | |
|       eventType = EVENTLOG_ERROR_TYPE;
 | |
|       break;
 | |
|     case LOG_WARNING:
 | |
|       eventType = EVENTLOG_WARNING_TYPE;
 | |
|       break;
 | |
|     case LOG_NOTICE:
 | |
|     case LOG_INFO:
 | |
|     case LOG_DEBUG:
 | |
|       eventType = EVENTLOG_INFORMATION_TYPE;
 | |
|       break;
 | |
|     default:
 | |
|       eventType = EVENTLOG_ERROR_TYPE;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|   /* We need to know how long the buffer needs to be.
 | |
|      The only legal way I can see of doing this is to
 | |
|      do a vfprintf to /dev/null, and count the bytes
 | |
|      output, then do it again to a malloc'ed string. This
 | |
|      is ugly, slow, but prevents core dumps :-).
 | |
|    */
 | |
|   pass_handler pass;
 | |
|   for (int pass_number = 0; pass_number < 2; ++pass_number)
 | |
|     {
 | |
|       int n = pass.initialize (pass_number);
 | |
|       if (n == -1)
 | |
| 	return;
 | |
|       else if (n > 0)
 | |
| 	pass.set_message ((char *) alloca (n));
 | |
| 
 | |
|       /* Deal with ident_string */
 | |
|       if (syslog_globals.process_ident != NULL)
 | |
| 	{
 | |
| 	  if (pass.print ("%ls: ", syslog_globals.process_ident) == -1)
 | |
| 	    return;
 | |
| 	}
 | |
|       if (syslog_globals.process_logopt & LOG_PID)
 | |
| 	{
 | |
| 	  if (pass.print ("PID %u: ", getpid ()) == -1)
 | |
| 	    return;
 | |
| 	}
 | |
| 
 | |
|       /* Print out the variable part */
 | |
|       if (pass.print_va (message, ap) == -1)
 | |
| 	return;
 | |
| 
 | |
|     }
 | |
|   char *total_msg = pass.get_message ();
 | |
|   size_t len = strlen (total_msg);
 | |
|   if (len != 0 && (total_msg[len - 1] == '\n'))
 | |
|     total_msg[--len] = '\0';
 | |
| 
 | |
|   if (syslog_globals.process_logopt & LOG_PERROR)
 | |
|     {
 | |
|       write (STDERR_FILENO, total_msg, len);
 | |
|       write (STDERR_FILENO, "\n", 1);
 | |
|     }
 | |
| 
 | |
|   int fd;
 | |
|   if ((fd = try_connect_syslogd (priority, total_msg, len + 1)) < 0)
 | |
|     {
 | |
|       /* If syslogd isn't present, open the event log and send the message */
 | |
|       HANDLE hEventSrc;
 | |
| 
 | |
|       hEventSrc = RegisterEventSourceW (NULL, syslog_globals.process_ident
 | |
| 					      ?: CYGWIN_LOG_NAME);
 | |
|       if (!hEventSrc)
 | |
| 	debug_printf ("RegisterEventSourceW, %E");
 | |
|       else
 | |
| 	{
 | |
| 	  wchar_t *msg_strings[1];
 | |
| 	  tmp_pathbuf tp;
 | |
| 	  msg_strings[0] = tp.w_get ();
 | |
| 	  sys_mbstowcs (msg_strings[0], NT_MAX_PATH, total_msg);
 | |
| 	  if (!ReportEventW (hEventSrc, eventType, 0, 0, cygheap->user.sid (),
 | |
| 			     1, 0, (const wchar_t **) msg_strings, NULL))
 | |
| 	    debug_printf ("ReportEventW, %E");
 | |
| 	  DeregisterEventSource (hEventSrc);
 | |
| 	}
 | |
|     }
 | |
| }
 | |
| 
 | |
| extern "C" void
 | |
| syslog (int priority, const char *message, ...)
 | |
| {
 | |
|   va_list ap;
 | |
|   va_start (ap, message);
 | |
|   vsyslog (priority, message, ap);
 | |
|   va_end (ap);
 | |
| }
 | |
| 
 | |
| static NO_COPY muto klog_guard;
 | |
| fhandler_mailslot *dev_kmsg;
 | |
| 
 | |
| extern "C" void
 | |
| vklog (int priority, const char *message, va_list ap)
 | |
| {
 | |
|   /* TODO: kernel messages are under our control entirely and they should
 | |
|      be quick.  No playing with /dev/null, but a fixed upper size for now. */
 | |
|   char buf[2060];	/* 2048 + a prority */
 | |
|   if (!(priority & ~LOG_PRIMASK))
 | |
|     priority = LOG_KERN | LOG_PRI (priority);
 | |
|   __small_sprintf (buf, "<%d>", priority);
 | |
|   __small_vsprintf (buf + strlen (buf), message, ap);
 | |
|   klog_guard.init ("klog_guard")->acquire ();
 | |
|   if (!dev_kmsg)
 | |
|     dev_kmsg = (fhandler_mailslot *) build_fh_name ("/dev/kmsg");
 | |
|   if (dev_kmsg && !dev_kmsg->get_handle ())
 | |
|     dev_kmsg->open (O_WRONLY, 0);
 | |
|   if (dev_kmsg && dev_kmsg->get_handle ())
 | |
|     dev_kmsg->write (buf, strlen (buf) + 1);
 | |
|   klog_guard.release ();
 | |
| }
 | |
| 
 | |
| extern "C" void
 | |
| klog (int priority, const char *message, ...)
 | |
| {
 | |
|   va_list ap;
 | |
|   va_start (ap, message);
 | |
|   vklog (priority, message, ap);
 | |
|   va_end (ap);
 | |
| }
 | |
| 
 | |
| extern "C" void
 | |
| closelog (void)
 | |
| {
 | |
|   try_connect_guard.init ("try_connect_guard")->acquire ();
 | |
|   if (syslogd_inited != not_inited && syslogd_sock >= 0)
 | |
|     {
 | |
|       close (syslogd_sock);
 | |
|       syslogd_sock = -1;
 | |
|       syslogd_inited = not_inited;
 | |
|     }
 | |
|   try_connect_guard.release ();
 | |
| }
 |