
/*-
 * Copyright (c) 2001 Robert N. M. Watson
 * 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.
 *
 * $FreeBSD: $
 */
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/conf.h>
#include <sys/libkern.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/time.h>
#include <sys/uio.h>

#include "freebsd_dev.h"

void	committer_newstring(void);

struct committer_event {
	struct timespec	ce_basetime;
	struct timespec	ce_length;
	struct timespec	ce_interval;	/* interval, or 0 to not recur */
	struct timespec	ce_lastseen;
	char	*ce_text;	/* string: committer event text */
};

static struct committer_event	events[] = {
	/* Hourly chime. */
	{{983250000, 0}, {60, 0}, {360, 0}, {0, 0}, "Dong!\n"},
	/* Daily whining. */
	{{983250000, 0}, {60, 0}, {86400, 0}, {0, 0}, "I want to crash.\n"},
	/* Every few days, overflow a buffer. */
	{{983336400, 0}, {360, 0}, {80000, 0}, {0, 0}, "Buffer overflow.\n"},
	/* Break buildworld every every 16 hours, for about three hours. */
	{{983336400, 0}, {1080, 0}, {57600, 0}, {0, 0}, "-CURRENT compile "
	    "is broken.\n"},
};
static int num_events = (sizeof(events) / sizeof(struct committer_event));

static char *committer_random[] = {
	"BigKnife: Bad kernel, no cookie!\n",
	"That patch violates style(9).\n",
	"rwatson hands the kernel a cookie.\n",
	"Segmentation fault (core dumped)\n",
	"tmm: that's broken, obviously.\n",
	"cmc: Amazing but true: Welding bakes bearing grease.\n",
	"zb^3: T KERNEL DONT CRASH K PLZ THX\n",
	"EvilPete: AAAAAAAAAAAAAAAAAAAAAAAARRRRRRRRRRRRRRRGGGGGGGGGGGGHHHHHH!"
	    "!!!!!!!!!!!\n",
	"gsutter: that's stupid.\n",
	"fanf: ALL YOUR RFC1918 IS BELONG TO ME\n",
	"EvilPete: ALL YOUR COMMITS ARE BELONG TO CVS\n",
	"ps kills\n",
	"green_ crosses his fingers really hard\n",
	"Holocaine: SMPng Ate My Brain!\n",
	"That breaks K&R support.\n",
	"/* XXX */\n",
	"/* XXX: rather untested at this point\n",
	" * XXX FIXME\n",
	"/* XXX This algorithm is a hack.\n",
	" * XXX this is bloat\n",
	" * XXX this is probbably bogus\n",
	"/* XXX REMOVE ME */\n",
	" # XXX More checks!\n",
	"softweyr: ALL YOUR BIRTHDAYS ARE BELONG TO US\n",
};
static int num_random = (sizeof(committer_random) / sizeof(char *));

static int	committer_is_open = 0;
static int	event_text_offset = 0;
static int	event_text_left = 0;

#define	EVENT_TEXT_BUFFER_LEN	256
static char	event_text_buffer[EVENT_TEXT_BUFFER_LEN];

static d_open_t		committer_open;
static d_close_t	committer_close;
static d_read_t		committer_read;

struct cdevsw committer_cdevsw = {
	/* open */	committer_open,
	/* close */	committer_close,
	/* read */	committer_read,
	/* write */	(d_write_t *)nullop,
	/* ioctl */	noioctl,
	/* poll */	nopoll,
	/* mmap */	nommap,
	/* strategy */	nostrategy,
	/* name */	"committer",
	/* maj */	CDEV_MAJOR,
	/* dump */	nodump,
	/* psize */	nopsize,
	/* flags */	0,
	/* bmaj */	0
};

/*
 * Modulus operation for timespec; operates only at second accuracy.
 * Assumes non-zero uvp->tv_sec.
 */
#define	timespecmod(tvp, uvp)						\
	do {								\
		(tvp)->tv_sec = (tvp)->tv_sec % (uvp)->tv_sec;		\
	} while (0);

/*
 * Generate a new string in the static string buffer to replace the current
 * string.  Algorithm is to search for a scheduled event.  If none is found,
 * then generate a random statement.
 */
void
committer_newstring(void)
{
	struct timespec now;
	int i, random_comment;

	getnanotime(&now);

	for (i = 0; i < num_events; i++) {
		if (!timespecisset(&events[i].ce_interval)) {
			struct timespec end;
			/* Event that occurs only once. */
			/* Has it happened already? */
			if (timespecisset(&events[i].ce_lastseen))
				continue;
			/* Test simple overlap. */
			end = events[i].ce_basetime;
			timespecadd(&end, &events[i].ce_interval);
			if (timespeccmp(&now, &events[i].ce_basetime, <= ) ||
			    timespeccmp(&now, &end, >=))
				continue;
		} else {
			struct timespec offset, period_start;
			/* Recurring event, more complex overlap. */
			/* Are we past the base permitted time? */
			if (timespeccmp(&now, &events[i].ce_basetime, <))
				continue;
			/* Are we in an enabled period for this event? */
			offset = now;
			timespecsub(&offset, &events[i].ce_basetime);
			/* minimum 1-second granularity on mod operator. */
			if (events[i].ce_interval.tv_sec == 0) {
				events[i].ce_interval.tv_sec = 1;
				events[i].ce_interval.tv_nsec = 0;
			}
			timespecmod(&offset, &events[i].ce_interval);
			if (timespeccmp(&offset, &events[i].ce_length, >))
				continue;
			/* Has it already happened in the current period? */
			period_start = now;
			timespecsub(&period_start, &offset);
			if (timespeccmp(&period_start, &events[i].ce_lastseen,
			    <))
				continue;
		}
		/* Update text, we didn't get bumped back around. */
		getnanotime(&events[i].ce_lastseen);
		event_text_offset = 0;
		event_text_left = min(EVENT_TEXT_BUFFER_LEN - 1,
		    strlen(events[i].ce_text));
		/* truncation acceptable */
		strncpy(event_text_buffer, events[i].ce_text,
		    event_text_left+1);
		event_text_buffer[EVENT_TEXT_BUFFER_LEN - 1] = '\0';
		event_text_left = strlen(event_text_buffer);
		event_text_offset = 0;
		return;
	}

	/* truncation acceptable */
	random_comment = arc4random() % num_random;
	strncpy(event_text_buffer, committer_random[random_comment],
	    EVENT_TEXT_BUFFER_LEN - 1);
	event_text_buffer[EVENT_TEXT_BUFFER_LEN - 1] = '\0';
	event_text_left = strlen(event_text_buffer);
	event_text_offset = 0;
}

static int
committer_open(dev_t dev, int flags, int mode, struct proc *p)
{

	if (committer_is_open)
		return (EBUSY);
	committer_is_open = 1;

	/* If the previous message wasn't entirely seen, retry it rather
	 * then generate a new message. */
	if (event_text_left == 0)
		committer_newstring();

	return (0);
}

static int
committer_close(dev_t dev, int flag, int mode, struct proc *p)
{

	committer_is_open = 0;
	return (0);
}

static int
committer_read(dev_t dev, struct uio *uio, int flag)
{
	int length, error;

	length = min(event_text_left, uio->uio_resid);
	error = uiomove(event_text_buffer + event_text_offset, length, uio);
	if (error)
		return (error);
	event_text_left -= length;
	event_text_offset += length;
	return (0);
}


