Home
Reading
Searching
Subscribe
Sponsors
Statistics
Posting
Contact
Spam
Lists
Links
About
Hosting
Filtering
Features Download
Marketing
Archives
FAQ
Blog
 
Gmane
From: Tetsuo Handa <penguin-kernel <at> I-love.SAKURA.ne.jp>
Subject: [RFC] A logger specialized for receiving from netconsole.
Newsgroups: gmane.linux.kernel
Date: Thursday 24th October 2013 15:02:20 UTC (over 2 years ago)
Background:

  When troubleshooting system freeze problems, obtaining SysRq messages
  is commonly used. However, sometimes it is impossible to let syslog
  daemon save the messages. In that case, serial console can be used.

  However, in already-deployed enterprise servers, it is difficult to
  reboot the system in order to add the serial console to the list of
  consoles. Also, speak of virtualized servers in the cloud environment,
  it may be difficult to add the virtual serial console which would
  require FTP-like access to the log files saved on the virtualization
  host.

  As I'm working at NTT Open Source Software Center, I'm having
  difficulties with obtaining SysRq messages from already-deployed
  RHEL 3/4/5/6 servers where the serial console is not attached yet.

  I started to consider utilizing the netconsole kernel module which
  can be used without rebooting the system and can be used without access
  to the virtualization host.

  It would be easy to configure the sender side; just load the netconsole
  kernel module with appropriate arguments. However, it is not always easy
  to configure the receiver side.

  Basically, syslog daemon would be able to receive traffic from netconsole
  (e.g. rsyslog can do it by creating a configuration file and restarting
  the rsyslog daemon), there are a few problems.

  ---------- An example /etc/rsyslog.d/netconsole.conf start ----------
  $template NetconsoleFile,"/var/log/netconsole.log"
  $template NetconsoleFormat,"%rawmsg%"
  
  $EscapeControlCharactersOnReceive off
  $DropTrailingLFOnReception off
  
  $RuleSet NetconsoleRuleset
  *.* ?NetconsoleFile;NetconsoleFormat
  $RuleSet RSYSLOG_DefaultRuleset
  
  $ModLoad imudp
  $InputUDPServerBindRuleset NetconsoleRuleset
  $UDPServerRun 6666
  ---------- An example /etc/rsyslog.d/netconsole.conf end ----------

  Problem 1:

  The messages from netconsole is not in the form of syslog format.
  Therefore, syslog daemon cannot assume that there is hostname and/or
  timestamp in the received message, which results in use of %rawmsg%
  format.

  ---------- An example log start ----------
  SysRq : HELP : loglevel(0-9) reBoot Crash terminate-all-tasks(E)
memory-full-oom-kill(F) kill-all-tasks(I) thaw-filesystems(J) saK
show-backtrace-all-active-cpus(L) \
  show-memory-usage(M) nice-all-RT-tasks(N) powerOff show-registers(P)
show-all-timers(Q) unRaw Sync show-task-states(T) Unmount
show-blocked-tasks(W) dump-ftrace-buffer(Z)
  ---------- An example log end ----------

  Problem 2:

  On the other hand, in order to determine when a problem occurred, it is
  important that hostname and timestamp are recorded with the messages
  received. Although changing from

    $template NetconsoleFormat,"%rawmsg%"

  to

    $template NetconsoleFormat,"[%timegenerated% %fromhost-ip%] %rawmsg%"

  would record a sort of hostname and timestamp, this format sometimes
  emits unnecessary [ ... ] part because a line of message can be received
  as a several UDP packets. The [ ... ] part should be emitted only once
  per a line.

  ---------- An example log start ----------
  [Oct 23 21:49:19 192.168.0.5] SysRq : [Oct 23 21:49:19 192.168.0.5] HELP
: [Oct 23 21:49:19 192.168.0.5] loglevel(0-9) [Oct 23 21:49:19 192.168.0.5]
reBoot \
  [Oct 23 21:49:19 192.168.0.5] Crash [Oct 23 21:49:19 192.168.0.5]
terminate-all-tasks(E) [Oct 23 21:49:19 192.168.0.5]
memory-full-oom-kill(F) \
  [Oct 23 21:49:19 192.168.0.5] kill-all-tasks(I) [Oct 23 21:49:19
192.168.0.5] thaw-filesystems(J) [Oct 23 21:49:19 192.168.0.5] saK \
  [Oct 23 21:49:19 192.168.0.5] show-backtrace-all-active-cpus(L) [Oct 23
21:49:19 192.168.0.5] show-memory-usage(M) [Oct 23 21:49:19 192.168.0.5] \
  nice-all-RT-tasks(N) [Oct 23 21:49:19 192.168.0.5] powerOff [Oct 23
21:49:19 192.168.0.5] show-registers(P) [Oct 23 21:49:19 192.168.0.5]
show-all-timers(Q) \
  [Oct 23 21:49:19 192.168.0.5] unRaw [Oct 23 21:49:19 192.168.0.5] Sync
[Oct 23 21:49:19 192.168.0.5] show-task-states(T) [Oct 23 21:49:19
192.168.0.5] \
  Unmount [Oct 23 21:49:19 192.168.0.5] show-blocked-tasks(W) [Oct 23
21:49:19 192.168.0.5] dump-ftrace-buffer(Z) [Oct 23 21:49:19 192.168.0.5]
  ---------- An example log end ----------

  Problem 3:

  SysRq-T tends to generate a lot of messages. If the receiver's buffer
  size is not configurable, the receiver program might drop many of lines.

  Speak of my targeted servers, buffer size is not configurable; but
  upgrading the rsyslog daemon to a version which is not supported by the
  distributor in order to enlarge the buffer size will be unacceptable.

  ---------- An example log start ----------
  [Oct 23 21:52:52 192.168.0.5] SysRq : [Oct 23 21:52:52 192.168.0.5] Show
State
  [Oct 23 21:52:52 192.168.0.5]   task                        PC stack  
pid father
  [Oct 23 21:52:52 192.168.0.5] init          S[Oct 23 21:52:52
192.168.0.5]  0000000000000001 [Oct 23 21:52:52 192.168.0.5]     0     1   
  0 0x00000000
  (...snipped...)
  [Oct 23 21:52:53 192.168.0.5] md_misc/1     S[Oct 23 21:52:53
192.168.0.5]  0000000000000001 [Oct 23 21:52:53 192.168.0.5]     0    36   
  2 0x00000000
  [Oct 23 21:52:53 192.168.0.5]  ffff88001d0fde30[Oct 23 21:52:53
192.168.0.5]  0000000000000046[Oct 23 21:52:53 192.168.0.5] 
0000000000000000[Oct 23 21:52:53 192.168.0.5]  ffff880002296768[Oct 23
21:52:53 192.168.0.5]
  [Oct 23 21:52:53 192.168.0.5]  0000000000000001[Oct 23 21:52:53
192.168.0.5]  ffff880002296700[Oct 23 21:52:53 192.168.0.5] 
ffff88001d0fdde0[Oct 23 21:52:53 192.168.0.5]  ffffffff8106669b[Oct 23
21:52:53 192.168.0.5]
  [Oct 23 21:52:53 192.168.0.5]  ffff88001d0fbab8[Oct 23 21:52:53
192.168.0.5]  ffff88001d0fdfd8[Oct 23 21:52:53 192.168.0.5] 
000000000000fb88[Oct 23 21:52:53 192.168.0.5]  ffff88001d0fbab8[Oct 23
21:52:53 192.168.0.5]
  [Oct 23 21:52:53 192.168.0.5] Call Trace:
  [Oct 23 21:52:53 192.168.0.5]  [] ?
dequeue_task_fair+0x12b/0x130
  [Oct 23 21:52:53 192.168.0.5]  [] ?
prepare_to_wait+0x4e/0x80
  [Oct 23 21:52:53 192.168.0.5]  [] ?
worker_thread+0x0/0x2a0
  [Oct 23 21:52:53 192.168.0.5]  []
worker_thread+0x1fc/0x2a0
  (...oops, all subsequent lines are dropped due to burst...)
  ---------- An example log end ----------

What I did:

  I thought that developing a standalone, trivial logger which is
  dedicated for receiving traffic from netconsole.

  I wrote one and would like to share it with you. Comments are welcome.

Features:

  This utility records timestamp and source IP:port in front of each line,
  allowing you to monitor multiple servers with single log file.
  You can easily pick up specific server's messages using awk(1).

  This utility automatically switches the log file if the latest log
  crosses the midnight, allowing you to rotate log files without
  restarting this utility.

  This utility waits for the newline of each message, allowing you to
  record messages without emitting unnecessary timestamp and IP:port.

  This utility uses larger receive buffer, allowing you to catch
  burst messages like SysRq-T.

  This utility can be run as an unprivileged user provided that the
  receive buffer size is sufficient.

  ---------- An example log start ----------
  2013-10-23 21:54:45 192.168.0.5:6666 SysRq : Show State
  2013-10-23 21:54:45 192.168.0.5:6666   task                        PC
stack   pid father
  2013-10-23 21:54:45 192.168.0.5:6666 init          S 0000000000000001    
0     1      0 0x00000000
  2013-10-23 21:54:45 192.168.0.5:6666  ffff88001d4a7908 0000000000000082
ffff88001b332cf8 ffff88001db75b38
  2013-10-23 21:54:45 192.168.0.5:6666  0000000000000000 ffff88001b3d9558
ffff88001d4a7958 ffffffffa029f1b4
  2013-10-23 21:54:45 192.168.0.5:6666  ffff88001d4a5ab8 ffff88001d4a7fd8
000000000000fb88 ffff88001d4a5ab8
  2013-10-23 21:54:45 192.168.0.5:6666 Call Trace:
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
do_get_write_access+0x3b4/0x520 [jbd2]
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
get_page_from_freelist+0x3d1/0x830
  2013-10-23 21:54:45 192.168.0.5:6666  []
schedule_hrtimeout_range+0x13d/0x160
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
add_wait_queue+0x46/0x60
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
__pollwait+0x75/0xf0
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
__pollwait+0x75/0xf0
  2013-10-23 21:54:45 192.168.0.5:6666  []
poll_schedule_timeout+0x39/0x60
  2013-10-23 21:54:45 192.168.0.5:6666  []
do_select+0x57c/0x6c0
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
__pollwait+0x0/0xf0
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
pollwake+0x0/0x60
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
pollwake+0x0/0x60
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
pollwake+0x0/0x60
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
pollwake+0x0/0x60
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
do_wp_page+0x493/0x920
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
current_fs_time+0x27/0x30
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
handle_pte_fault+0x2cd/0xb50
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
mutex_lock+0x1e/0x50
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
pipe_read+0x2a7/0x4e0
  2013-10-23 21:54:45 192.168.0.5:6666  []
core_sys_select+0x18a/0x2c0
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
security_task_wait+0x16/0x20
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
wait_consider_task+0x9d/0xb20
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
remove_wait_queue+0x3c/0x50
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
do_wait+0x17f/0x240
  2013-10-23 21:54:45 192.168.0.5:6666  []
sys_select+0x47/0x110
  2013-10-23 21:54:45 192.168.0.5:6666  [] ?
child_wait_callback+0x0/0x70
  2013-10-23 21:54:45 192.168.0.5:6666  []
system_call_fastpath+0x16/0x1b
  (...snipped...)
  2013-10-23 21:54:48 192.168.0.5:6666 runnable tasks:
  2013-10-23 21:54:48 192.168.0.5:6666             task   PID        
tree-key  switches  prio     exec-runtime         sum-exec        sum-sleep
  2013-10-23 21:54:48 192.168.0.5:6666
----------------------------------------------------------------------------------------------------------
  2013-10-23 21:54:48 192.168.0.5:6666
  (...OK. all lines are recorded...)
  ---------- An example log end ----------

How to use:

  Just compile the source code shown below and run the program. This
  program would run on any 2.4.x/2.6.x/3.x kernels with default libc.

    [[email protected] ~]# ./udplogger
    Started at 2013-10-23 21:54:38 from /root/2013-10-23.log
    Options: ip=0.0.0.0 port=6666 dir=/root timeout=10 clients=1024
wbuf=65536 rbuf=16777216

  All parameters has a default value; log file is by default created in the
  current directory; you need to specify only parameters you want to
  override.

  You can test the reachability using "echo h > /proc/sysrq-trigger" from
  the sender side. Be sure to update the iptables rules as needed. ;-)

Source code: (Total 409 lines)

---------- udplogger.c start ----------
/*
 * Simple UDP logger - A utility for receiving output from netconsole.
 *
 *    Written by Tetsuo Handa 
 */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define round_up(size) ((((size) + 4095u) / 4096u) * 4096u)

/* Structure for tracking partially received data. */
static struct client {
	struct sockaddr_in addr; /* Sender's IPv4 address and port. */
	char *buffer; /* Buffer for holding received data. */
	int avail; /* Valid bytes in @buffer . */
	char addr_str[24]; /* String representation of @addr . */
	time_t stamp; /* Timestamp of receiving the first byte in @buffer . */
} *clients = NULL;

/* Current clients. */
static int num_clients = 0;
/* Max clients. */
static int max_clients = 1024;
/* Max write buffer per a client. */
static int wbuf_size = 65536;
/* Max seconds to wait for new line. */
static int wait_timeout = 10;
/* Try to release unused memory? */
static _Bool try_drop_memory_usage = 0;
/* Name of today's log file. */
static char filename[16] = { };
/* Handle for today's log file. */
static FILE *log_fp = NULL;
/* Previous time. */
static struct tm last_tm = { .tm_year = 70, .tm_mday = 1 };

/**
 * switch_logfile - Close yesterday's log file and open today's log file.
 *
 * @tm: Pointer to "struct tm" holding current time.
 *
 * Returns nothing.
 */
static void switch_logfile(struct tm *tm)
{
	FILE *fp = log_fp;
	snprintf(filename, sizeof(filename) - 1, "%04u-%02u-%02u.log",
		 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
	log_fp = fopen(filename, "a");
	if (!fp)
		return;
	/* If open() failed, continue using old one. */
	if (log_fp)
		fclose(fp);
	else
		log_fp = fp;
	try_drop_memory_usage = 1;
}

/**
 * write_logfile - Write to today's log file.
 *
 * @ptr:    Pointer to "struct client".
 * @forced: True if the partial line should be written.
 *
 * Returns nothing.
 */
static void write_logfile(struct client *ptr, const _Bool forced)
{
	static time_t last_time = 0;
	static char stamp[24] = { };
	char *buffer = ptr->buffer;
	int avail = ptr->avail;
	const time_t now_time = ptr->stamp;
	if (last_time != now_time) {
		struct tm *tm = localtime(&now_time);
		if (!tm)
			tm = &last_tm;
		snprintf(stamp, sizeof(stamp) - 1, "%04u-%02u-%02u "
			 "%02u:%02u:%02u ", tm->tm_year + 1900, tm->tm_mon + 1,
			 tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
		/*
		 * Switch log file if the day has changed. We can't use
		 * (last_time / 86400 != now_time / 86400) in order to allow
		 * switching at 00:00:00 of the local time.
		 */
		if (tm->tm_mday != last_tm.tm_mday ||
		    tm->tm_mon != last_tm.tm_mon ||
		    tm->tm_year != last_tm.tm_year) {
			last_tm = *tm;
			switch_logfile(tm);
		}
		last_time = now_time;
	}
	/* Write the completed lines. */
	while (1) {
		char *cp = memchr(buffer, '\n', avail);
		const int len = cp - buffer + 1;
		if (!cp)
			break;
		fprintf(log_fp, "%s%s", stamp, ptr->addr_str);
		fwrite(buffer, 1, len, log_fp);
		avail -= len;
		buffer += len;
	}
	/* Write the incomplete line if forced. */
	if (forced && avail) {
		fprintf(log_fp, "%s%s", stamp, ptr->addr_str);
		fwrite(buffer, 1, avail, log_fp);
		fprintf(log_fp, "\n");
		avail = 0;
	}
	/* Discard the written data. */
	if (ptr->buffer != buffer)
		memmove(ptr->buffer, buffer, avail);
	ptr->avail = avail;
}

/**
 * drop_memory_usage - Try to reduce memory usage.
 *
 * Returns nothing.
 */
static void drop_memory_usage(void)
{
	struct client *ptr;
	int i = 0;
	if (!try_drop_memory_usage)
		return;
	try_drop_memory_usage = 0;
	while (i < num_clients) {
		ptr = &clients[i];
		if (ptr->avail) {
			char *tmp = realloc(ptr->buffer, round_up(ptr->avail));
			if (tmp)
				ptr->buffer = tmp;
			i++;
			continue;
		}
		free(ptr->buffer);
		num_clients--;
		memmove(ptr, ptr + 1, (num_clients - i) * sizeof(*ptr));
	}
	if (num_clients) {
		ptr = realloc(clients, round_up(sizeof(*ptr) * num_clients));
		if (ptr)
			clients = ptr;
	} else {
		free(clients);
		clients = NULL;
	}
}

/**
 * flush_all_and_abort - Clean up upon out of memory.
 *
 * This function does not return.
 */
static void flush_all_and_abort(void)
{
	int i;
	for (i = 0; i < num_clients; i++)
		if (clients[i].avail) {
			write_logfile(&clients[i], 1);
			free(clients[i].buffer);
		}
	fprintf(log_fp, "[aborted due to memory allocation failure]\n");
	fflush(log_fp);
	exit(1);
}

/**
 * find_client - Find the structure for given address.
 *
 * @addr: Pointer to "struct sockaddr_in".
 *
 * Returns "struct client" for @addr on success, NULL otherwise.
 */
static struct client *find_client(struct sockaddr_in *addr)
{
	struct client *ptr;
	int i;
	for (i = 0; i < num_clients; i++)
		if (!memcmp(&clients[i].addr, addr, sizeof(*addr)))
			return &clients[i];
	if (i >= max_clients) {
		try_drop_memory_usage = 1;
		drop_memory_usage();
		if (i >= max_clients)
			return NULL;
	}
	ptr = realloc(clients, round_up(sizeof(*ptr) * (num_clients + 1)));
	if (!ptr)
		return NULL;
	clients = ptr;
	ptr = &clients[num_clients++];
	memset(ptr, 0, sizeof(*ptr));
	ptr->addr = *addr;
	snprintf(ptr->addr_str, sizeof(ptr->addr_str) - 1, "%s:%u ",
		 inet_ntoa(addr->sin_addr), htons(addr->sin_port));
	return ptr;
}

/**
 * do_main - The main loop.
 *
 * @fd: Receiver socket's file descriptor.
 *
 * Returns nothing.
 */
static void do_main(const int fd)
{
	static char buf[65536];
	struct sockaddr_in addr;
	while (1) {
		struct pollfd pfd = { fd, POLLIN, 0 };
		socklen_t size = sizeof(addr);
		int i;
		time_t now;
		/* Don't wait forever if checking for timeout. */
		for (i = 0; i < num_clients; i++)
			if (clients[i].avail)
				break;
		/* Flush log file and wait for data. */
		fflush(log_fp);
		poll(&pfd, 1, i < num_clients ? 1000 : -1);
		now = time(NULL);
		/* Check for timeout. */
		for (i = 0; i < num_clients; i++)
			if (clients[i].avail &&
			    now - clients[i].stamp >= wait_timeout)
				write_logfile(&clients[i], 1);
		/* Don't receive forever in order to check for timeout. */
		while (now == time(NULL)) {
			struct client *ptr;
			char *tmp;
			int len = recvfrom(fd, buf, sizeof(buf), MSG_DONTWAIT,
					   (struct sockaddr *) &addr, &size);
			if (len <= 0 || size != sizeof(addr))
				break;
			ptr = find_client(&addr);
			if (!ptr)
				continue;
			/* Save current time if receiving the first byte. */
			if (!ptr->avail)
				ptr->stamp = now;
			/* Append data to the line. */
			tmp = realloc(ptr->buffer, round_up(ptr->avail + len));
			if (!tmp)
				flush_all_and_abort();
			memmove(tmp + ptr->avail, buf, len);
			ptr->avail += len;
			ptr->buffer = tmp;
			/* Write if at least one line completed. */
			if (memchr(buf, '\n', len))
				write_logfile(ptr, 0);
			/* Write if the line is too long. */
			if (ptr->avail >= wbuf_size)
				write_logfile(ptr, 1);
		}
		drop_memory_usage();
	}
}

/**
 * usage - Print usage and exit.
 *
 * @name: Program's name.
 *
 * This function does not return.
 */
static void usage(const char *name)
{
	fprintf(stderr, "Simple UDP logger\n\n"
		"Usage:\n  %s [ip=$listen_ip] [port=$listen_port] "
		"[dir=$log_dir] [timeout=$seconds_waiting_for_newline] "
		"[clients=$max_clients] [wbuf=$write_buffer_size] "
		"[rbuf=$receive_buffer_size]\n\n"
		"The value of $seconds_waiting_for_newline should be between "
		"5 and 600.\nThe value of $max_clients should be between 10 "
		"and 65536.\nThe value of $write_buffer_size should be "
		"between 1024 and 1048576.\nThe value of $receive_buffer_size "
		"should be 65536 and 1073741824 (though actual size might be "
		"adjusted by the kernel).\n", name);
	exit (1);
}

/**
 * do_init - Initialization function.
 *
 * @argc: Number of arguments.
 * @argv: Arguments.
 *
 * Returns the listener socket's file descriptor.
 */
static int do_init(int argc, char *argv[])
{
	struct sockaddr_in addr = { };
	char pwd[4096];
	/* Max receive buffer size. */
	int rbuf_size = 8 * 1048576;
	socklen_t size;
	int fd;
	int i;
	/* Directory to save logs. */
	const char *log_dir = ".";
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(6666);
	for (i = 1; i < argc; i++) {
		char *arg = argv[i];
		if (!strncmp(arg, "ip=", 3))
			addr.sin_addr.s_addr = inet_addr(arg + 3);
		else if (!strncmp(arg, "port=", 5))
			addr.sin_port = htons(atoi(arg + 5));
		else if (!strncmp(arg, "dir=", 4))
			log_dir = arg + 4;
		else if (!strncmp(arg, "timeout=", 8))
			wait_timeout = atoi(arg + 8);
		else if (!strncmp(arg, "clients=", 8))
			max_clients = atoi(arg + 8);
		else if (!strncmp(arg, "wbuf=", 5))
			wbuf_size = atoi(arg + 5);
		else if (!strncmp(arg, "rbuf=", 5))
			rbuf_size = atoi(arg + 5);
		else
			usage(argv[0]);
	}
	/* Sanity check. */
	if (max_clients < 10)
		max_clients = 10;
	if (max_clients > 65536)
		max_clients = 65536;
	if (wait_timeout < 5)
		wait_timeout = 5;
	if (wait_timeout > 600)
		wait_timeout = 600;
	if (wbuf_size < 1024)
		wbuf_size = 1024;
	if (wbuf_size > 1048576)
		wbuf_size = 1048576;
	if (rbuf_size < 65536)
		rbuf_size = 65536;
	if (rbuf_size > 1024 * 1048576)
		rbuf_size = 1024 * 1048576;
	/* Create the listener socket and configure it. */
	fd = socket(AF_INET, SOCK_DGRAM, 0);
#ifdef SO_RCVBUFFORCE
	if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &rbuf_size,
		       sizeof(rbuf_size))) {
#endif
		if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rbuf_size,
			       sizeof(rbuf_size))) {
			fprintf(stderr, "Can't set receive buffer size.\n");
			exit(1);
		}
#ifdef SO_RCVBUFFORCE
	}
#endif
	size = sizeof(rbuf_size);
	if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rbuf_size, &size)) {
		fprintf(stderr, "Can't get receive buffer size.\n");
		exit(1);
	}
	size = sizeof(addr);
	if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) ||
	    getsockname(fd, (struct sockaddr *) &addr, &size) ||
	    size != sizeof(addr)) {
		fprintf(stderr, "Can't bind to %s:%u .\n",
			inet_ntoa(addr.sin_addr), htons(addr.sin_port));
		exit(1);
	}
	/* Open the initial log file. */
	memset(pwd, 0, sizeof(pwd));
	if (chdir(log_dir) || !getcwd(pwd, sizeof(pwd) - 1)) {
		fprintf(stderr, "Can't change directory to %s .\n", log_dir);
		exit(1);
	} else {
		const time_t now = time(NULL);
		struct tm *tm = localtime(&now);
		if (!tm)
			tm = &last_tm;
		switch_logfile(tm);
		if (!log_fp) {
			fprintf(stderr, "Can't create log file.\n");
			exit(1);
		}
		printf("Started at %04u-%02u-%02u %02u:%02u:%02u from %s/%s\n",
		       tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
		       tm->tm_hour, tm->tm_min, tm->tm_sec, pwd, filename);
	}
	/* Successfully initialized. */
	printf("Options: ip=%s port=%u dir=%s timeout=%u clients=%u wbuf=%u "
	       "rbuf=%u\n", inet_ntoa(addr.sin_addr), htons(addr.sin_port),
	       pwd, wait_timeout, max_clients, wbuf_size, rbuf_size);
	return fd;
}

int main(int argc, char *argv[])
{
	const int fd = do_init(argc, argv);
	do_main(fd);
	return 0;
}
---------- udplogger.c end ----------
 
CD: 3ms