/*
- * Loadable driver which provides the ability to generate a kernel
- * protection fault. Mainly useful for testing the address translation
- * capabilities of klogd.
- *
- * Fri Oct 27 14:34:27 CDT 1995: Dr. Wettstein
- *
- * Initial version.
- */
+ oops.c - Dummy loadable module for testing klogd.
+ Copyright (c) 2007 Martin Schulze <joey@infodrom.org>
-#define NEW_MODULES
+ This file is part of the sysklogd package, a kernel and system log daemon.
-/* Kernel includes. */
-#include <linux/kernel.h>
-#include <linux/config.h>
-#include <linux/errno.h>
-#include <linux/fs.h>
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ Helpful documentation: http://www.tldp.org/LDP/lkmpg/2.6/html/
+
+ SYNOPSIS
+
+ echo TEXT > /proc/oops Emits TEXT via printk at log level
+ [<address+delta>] triggers klogd address decoding
+ echo level: info > /proc/oops Sets the log level to 'info'
+ echo oops > /proc/oops Creates a real oops, kills executing shell
+ cat /proc/oops Display current log level and last oops time
+*/
-/* Standard module stuff. */
-#if defined(NEW_MODULES)
-#include <linux/module.h>
-#else
#include <linux/module.h>
-#include <linux/version.h>
-char kernel_version[] = UTS_RELEASE;
-#endif
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/poll.h>
+#include <linux/time.h>
+
+#define MODNAME "oops"
+#define PROCNAME "oops"
+MODULE_AUTHOR("Martin Schulze <joey@infodrom.org>");
+MODULE_DESCRIPTION("Oops module from klogd");
+MODULE_LICENSE("GPL");
-static int major = 32;
+static DEFINE_MUTEX(oops_lock);
+struct oops_t {
+ unsigned long lastoops;
+ int loglevel;
+};
+static struct oops_t oops_data;
-#ifdef MODULE
-static int oops_ioctl(struct inode *, struct file *, unsigned int cmd, unsigned long arg);
-static int oops_open(struct inode * node, struct file * file);
-static void oops(void);
+static int procflag = 0;
-static struct symbol_table these_symbols = {
-#include <linux/symtab_begin.h>
- X(oops_open),
- X(oops_ioctl),
- X(oops),
-#include <linux/symtab_end.h>
+struct code {
+ char *name;
+ int level;
};
-/* driver specific module definitions */
-static struct file_operations oops_fops1 = {
- NULL, /* hw_lseek */
- NULL, /* hw_read */
- NULL, /* write */
- NULL, /* hw_readdir */
- NULL, /* hw_select */
- oops_ioctl, /* hw_ioctl */
- NULL, /* mmap */
- oops_open, /* hw_open */
- NULL, /* hw_release */
- NULL /* fsync */
+struct code priorities[] = {
+ {"emerg", 0},
+ {"panic", 0}, /* DEPRECATED */
+ {"alert", 1},
+ {"crit", 2},
+ {"err", 3},
+ {"error", 3}, /* DEPRECATED */
+ {"warning", 4},
+ {"warn", 4}, /* DEPRECATED */
+ {"notice", 5},
+ {"info", 6},
+ {"debug", 7},
+ {NULL, -1}
};
-static int oops_open(struct inode * node, struct file * file)
+void oops_decode_level (char *line)
{
- printk("Called oops_open.\n");
- return(0);
-}
+ char *p;
+ struct code *prio;
+ if (strncmp(line, "level:", 6))
+ return;
-static int oops_ioctl(struct inode * node, struct file * file, \
- unsigned int cmd, unsigned long arg)
-{
-
- printk("Called oops_ioctl.\n");
- printk("Cmd: %d, Arg: %ld\n", cmd, arg);
- if ( cmd == 1 )
- {
- oops();
- }
-
- return(0);
-}
+ for (p = (char *)(line) + 6;*p == ' ' || *p == '\t';p++);
-static void oops()
+ for (prio = priorities; prio->name; prio++)
+ if (!strcmp(p, prio->name)) {
+ oops_data.loglevel = prio->level;
+ return;
+ }
+}
+/*
+ * This routine will create a real and ugly oops
+ */
+static void oops(void)
{
auto unsigned long *p = (unsigned long *) 828282828;
*p = 5;
return;
}
-
-int
-init_module(void)
+static int oops_proc_open (struct inode *inode, struct file *file)
{
- printk("oops: Module initilization.\n");
- if (register_chrdev(major, "oops", &oops_fops1)) {
- printk("register_chrdev failed.");
- return -EIO;
- }
-
- printk("oops: Registering symbols.\n");
- register_symtab(&these_symbols);
-
- return 0;
+#ifdef DEBUG
+ printk (KERN_DEBUG "oops_proc_open().\n");
+#endif
+ return 0;
}
+static ssize_t
+oops_proc_read (struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
+{
+ char s[70];
+ int size;
+
+ struct code *prio;
+ char *level = NULL;
+
+#ifdef DEBUG
+ printk (KERN_DEBUG "oops_proc_read(%d).\n",nbytes);
+#endif
+
+ if (procflag) {
+ procflag = 0;
+ return 0;
+ }
+
+ for (prio = priorities;
+ prio->name && prio->level != oops_data.loglevel;
+ prio++);
+ level = prio->name;
+
+ if (oops_data.lastoops == 0)
+ size = sprintf (s, "Log level: %s\nLast oops: none\n", level);
+ else {
+ unsigned long now = get_seconds();
+ unsigned long delta = now - oops_data.lastoops;
+ size = sprintf (s, "Log level: %s\nLast oops: %lu (%lu second%s ago)\n",
+ level, oops_data.lastoops,
+ delta, delta == 1 ? "" : "s");
+ }
+
+ if (size < nbytes)
+ nbytes = size;
-void
-cleanup_module(void)
+ if (copy_to_user(buf, s, nbytes))
+ return -EFAULT;
+
+ *ppos += nbytes;
+
+ procflag++;
+
+ return nbytes;
+}
+
+static int
+oops_proc_release(struct inode *inode, struct file *filp)
{
- /* driver specific cleanups, ususally "unregister_*()" */
- printk("oops: Module unloadeding.\n");
- if (unregister_chrdev(major, "oops") != 0)
- printk("cleanup_module failed\n");
- else
- printk("cleanup_module succeeded\n");
+#ifdef DEBUG
+ printk (KERN_DEBUG "oops_proc_release().\n");
+#endif
+ return 0;
+}
- return;
+static ssize_t
+oops_proc_write(struct file *file, const char __user *buf,
+ size_t nbytes, loff_t *ppos)
+{
+ char input[100];
+ int len;
+
+#ifdef DEBUG
+ printk (KERN_DEBUG "oops_proc_write(%d).\n", nbytes);
+#endif
+
+ len = nbytes >= sizeof(input) ? sizeof(input)-1 : nbytes;
+
+ if (copy_from_user(input, buf, len))
+ return -EFAULT;
+
+ input[len] = '\0';
+ if (input[len-1] == '\n')
+ input[len-1] = '\0';
+
+ if (!strncmp(input, "level:", 6))
+ oops_decode_level(input);
+ else if (!strcmp(input, "oops")) {
+ oops_data.lastoops = get_seconds();
+ oops();
+ } else
+ printk ("<%d>%s\n", oops_data.loglevel, input);
+
+ return nbytes;
+}
+
+static const struct file_operations oops_proc_operations = {
+ .read = oops_proc_read,
+ .release = oops_proc_release,
+ .write = oops_proc_write,
+ .open = oops_proc_open,
+};
+
+void oops_proc_add (void)
+{
+ struct proc_dir_entry *entry;
+
+ mutex_lock (&oops_lock);
+
+ entry = create_proc_entry (PROCNAME, 0, NULL);
+
+ if (entry) {
+ entry->proc_fops = &oops_proc_operations;
+ }
+
+ mutex_unlock (&oops_lock);
+}
+
+void oops_proc_remove (void)
+{
+ mutex_lock (&oops_lock);
+ remove_proc_entry(PROCNAME, NULL);
+
+ mutex_unlock(&oops_lock);
}
-#endif /* MODULE */
+
+int oops_init (void)
+{
+ printk (KERN_INFO "Loading module " MODNAME ".\n");
+
+ oops_data.lastoops = 0;
+ oops_data.loglevel = 5;
+
+ oops_proc_add();
+
+ return 0;
+}
+
+void oops_cleanup (void)
+{
+ oops_proc_remove();
+
+ printk (KERN_INFO "Removing module " MODNAME ".\n");
+}
+
+
+module_init(oops_init);
+module_exit(oops_cleanup);
+