[PATCH] Enhanced Linux System Accounting
From: Guillaume Thouvenin
Date: Wed May 12 2004 - 07:23:23 EST
Hello,
This is a new patch "patch-2.6.6-elsa" for 2.6.6 kernel. It can be
downloaded from:
http://sourceforge.net/project/showfiles.php?group_id=105806
I also attach it at the end of this file. In this patch we rewrite
functions and we made a clean separation between different parts of ELSA
(thank you very much to Chris Wright for its usefull comments). This
patch allows you to test BSD-like accounting for a group of process.
Informations are dumped in /var/log/bank (need to change that). "BANK"
can be manipulated via ioctl() and /dev/elsacct. A C program, called
"elsa_cmd.c", that allows to add/remove process to/from a bank can be
downloaded from the CVS:
http://cvs.sourceforge.net/viewcvs.py/elsa/tests/
We need to improve lock between the main structure called "elsa_broot"
and add new accounting informations. Our goal is to provide an
environment that can support System V accounting and a unify interface
from existing accounting tools (like sar) to be used by existing Linux
administrative tools (like WebMin).
Any feedbacks, any comments are welcome
Best,
Guillaume
diff -uprN -X elsa_import/dontdiff linux-2.6.6/drivers/elsacct/bank.c linux-2.6.6-elsa/drivers/elsacct/bank.c
--- linux-2.6.6/drivers/elsacct/bank.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.6-elsa/drivers/elsacct/bank.c 2004-05-12 07:24:41.380161848 +0200
@@ -0,0 +1,607 @@
+/*
+ * driver/elsacct/bank.c
+ *
+ * ELSA - Enhanced Linux System Accounting
+ * Guillaume Thouvenin - 26/04/2004
+ *
+ * This file implements Enhanced Linux System Accounting. It
+ * provides structure and functions to manipulate "BANK".
+ *
+ *
+ * This code is licenced under GPL.
+ */
+
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/seq_file.h>
+#include <linux/module.h>
+
+#include <linux/bank.h>
+
+#include <asm/semaphore.h>
+#include <asm/bug.h>
+
+ /*********************************************
+ * structures and macros used to manage banks *
+ *********************************************/
+
+#ifdef CONFIG_BANK_DEBUG
+#define dprintk(format...) printk(format)
+#else
+#define dprintk(format)
+#endif
+
+/* It's the head on the list of banks */
+static struct bank_root elsa_broot = BANK_ROOT_INIT(elsa_broot);
+
+/* semaphore that protect access to bank id */
+static DECLARE_MUTEX(elsa_bid_sem);
+//SPIN_LOCK_UNLOCKED(elsalist_lock);
+
+ /********************************************************************
+ * Following functions can be use by a module (=> they are exported) *
+ ********************************************************************/
+
+/**
+ * elsa_bank_alloc - Allocates a new bank
+ * @fn : callback used when a data or a bank are destroyed
+ * @info : generic information attached to a bank (used for accounting)
+ *
+ * Allocates a new bank and returns the new created bank.
+ * If an error is encountered NULL is returned.
+ *
+ * Here are different steps of the operation
+ *
+ * 1) Allocate space for the new bank
+ * 2) Give it an identifier
+ * 3) Initialize the head of the list of items (items will point to process)
+ * 4) Add it to the list of available bank
+ */
+struct elsa_bank
+*elsa_bank_alloc(int (*fn) (int, struct elsa_bank *, struct elsa_data *),
+ void *info)
+{
+ struct elsa_bank *b;
+ int bid;
+
+ /* allocate space for the new bank */
+ b = (struct elsa_bank *)kmalloc(sizeof(struct elsa_bank), GFP_KERNEL);
+ if (!b) {
+ printk("elsa_bank_alloc: cannot allocate space\n");
+ return NULL;
+ }
+
+ /*
+ * give it an id
+ * Currently we only used next_bid field but this mechnism
+ * can give problems if we use many banks and the bid go through
+ * its maximum
+ */
+ down(&elsa_bid_sem);
+ bid = elsa_broot.next_bid++;
+ up(&elsa_bid_sem);
+
+ if (bid <= 0) {
+ /* There is no available id == ERROR */
+ kfree(b);
+ printk("elsa_bank_alloc: can not find bank identifier\n");
+ return NULL;
+ } else {
+ b->bid = bid;
+ }
+
+ /* Initialize fields */
+ b->info = info;
+ b->callback = fn;
+ INIT_LIST_HEAD(&(b->data_head));
+
+ /* add the new bank to the list of banks --- NEED LOCK */
+ list_add(&(b->bank_list), &(elsa_broot.bank_head));
+
+ dprintk("elsa_bank_alloc: bank #%d created\n", b->bid);
+
+ return b;
+}
+
+/**
+ * elsa_data_alloc - Allocates a new data
+ *
+ * Allocates a new data and returns the new created item.
+ * If an error is encountered NULL is returned.
+ *
+ * Here are different steps of the operation
+ *
+ * 1) Allocate space for the new data
+ * 2) Initialize different fields
+ */
+struct elsa_data *elsa_data_alloc(void)
+{
+ struct elsa_data *d;
+
+ /* allocate space for the new data */
+ d = (struct elsa_data *)kmalloc(sizeof(struct elsa_data), GFP_KERNEL);
+ if (!d) {
+ printk("elsa_data_alloc: cannot allocate space\n");
+ return NULL;
+ }
+
+ /* Initialize fields */
+ d->bid = 0;
+ d->process = NULL;
+ INIT_LIST_HEAD(&(d->data_list));
+ INIT_LIST_HEAD(&(d->bank_list));
+
+ return d;
+}
+
+/**
+ * elsa_bank_free - Frees space occupied by a bank.
+ * @bank: a pointer to the bank to delete
+ *
+ * Removes a bank and returns the identifiers of the removed bank.
+ * When this function is called bank *MUST* be empty.
+ *
+ * Here are different steps of the operation
+ *
+ * 1) Write accounting information
+ * 2) Remove it from the list of banks
+ * 3) Free space used by bank
+ */
+void elsa_bank_free(struct elsa_bank *bank)
+{
+ BUG_ON(bank == NULL);
+ BUG_ON(!list_empty(&bank->data_head));
+
+ /* Before deleting the bank, we can do some action */
+ /* callback */
+ if (bank->callback) {
+ (bank->callback) (ELSA_BANK_CALLBACK, bank, NULL);
+ }
+
+ /* remove bank from the bank's list */
+ list_del(&(bank->bank_list));
+ dprintk("elsa_bank_free: bank #%d removed from the list\n", bank->bid);
+ kfree(bank);
+}
+
+/**
+ * elsa_data_free - Free data
+ * @data: data to be removed
+ *
+ * Frees memory space used by data.
+ */
+void elsa_data_free(struct elsa_data *data)
+{
+ struct elsa_bank *b = NULL;
+
+ BUG_ON(data == NULL);
+
+ /*
+ * if data->bid is equal to 0, it means that an error occured between
+ * data's allocation and data's addition in a bank. So, it isn't inserted
+ * in any list and there is no need to call the "callback"
+ */
+ if (data->bid != 0) {
+ /* callback, we are in the child so callback code */
+ b = elsa_get_bank(data->bid);
+ if (b && b->callback) {
+ (b->callback) (ELSA_DATA_CALLBACK, b, data);
+ }
+
+ list_del(&data->data_list);
+
+ /* Need access to head_list in task_struct */
+ write_lock_irq(&tasklist_lock);
+ list_del(&data->bank_list);
+ write_unlock_irq(&tasklist_lock);
+
+ dprintk
+ ("elsa_data_free: process #%d removed from the bank#%d\n",
+ data->process->pid, b->bid);
+ }
+
+ kfree(data);
+}
+
+/**
+ * elsa_bank_add - Add a process to a given bank
+ * @b : pointer to a bank
+ * @p : pointer to a process
+ *
+ * add a data to a bank by setting lists. Other fields are unchanged
+ *
+ * If an error is encountered, a negative value is returned.
+ * It can not return 0.
+ *
+ * Here are steps to perform
+ *
+ * 1) Check arguments
+ * 2) Check if process is already in the bank
+ * 3) Initialize its fields
+ * 4) Return BID
+ */
+int elsa_bank_add(struct elsa_bank *b, struct elsa_data *d)
+{
+ if (!b || !d || !(d->process)) {
+ dprintk("elsa_bank_add: Wrong parameters\n");
+ return -EINVAL;
+ }
+
+ /* Bank ID cannot be equal to 0 */
+ BUG_ON(b->bid == 0);
+
+ /* check if process is already present */
+ if (elsa_process_present(d->process, b))
+ /* Process already in the bank, so we can not add it */
+ return -EPERM;
+
+ /* Set BID */
+ d->bid = b->bid;
+
+ /* Add data in the list of bank's data */
+ list_add(&(d->data_list), &(b->data_head));
+
+ /* Add data in the list of process's bank */
+ write_lock_irq(&tasklist_lock);
+ list_add(&(d->bank_list), &(d->process->bank_head));
+ write_unlock_irq(&tasklist_lock);
+
+ dprintk("elsa_bank_add: Add process #%d to bank #%d\n", d->process->pid,
+ b->bid);
+
+ return b->bid;
+}
+
+/**
+ * elsa_bank_remove - Remove a process to a given bank
+ * @b: pointer to the bank
+ * @d: pointer to the process
+ *
+ * Removes a given process from a given bank.
+ * Return 0 if succedeed, negative value otherwise
+ */
+int elsa_bank_remove(struct elsa_data *d)
+{
+ int b_empty = 0;
+ struct elsa_bank *b = NULL; /* Used if d is the last
+ item of the bank */
+
+ if (!d)
+ return -EINVAL;
+
+ /* bank will be empty if d is the last item in the bank */
+ if (d->data_list.next == d->data_list.prev) {
+ b_empty = 1;
+ b = elsa_get_bank(d->bid);
+ }
+
+ /* kfree memory */
+ elsa_data_free(d);
+
+ /*
+ * We must free bank after data otherwise we will miss
+ * accounting informations
+ */
+ if (b_empty)
+ elsa_bank_free(b);
+
+ return 0;
+}
+
+/**
+ * elsa_bank_clean - Removes all datas in a given bank
+ * @b: pointer to the bank to be removed
+ *
+ * Removes data found in a bank given as paramater. It go through the
+ * list, removes link and free space occupied by the data.
+ * Return 0 if succedeed, negative value otherwise
+ */
+int elsa_bank_clean(struct elsa_bank *b)
+{
+ if (!b) {
+ /* bank doesn't exist */
+ dprintk("elsa_bank_clean: bank doesn't exist\n");
+ return -EAGAIN;
+ }
+
+ /* release all datas and remove the bank */
+ while (!list_empty(&(b->data_head))) {
+ /* get pointer to the first element in the list */
+ struct elsa_data *d = list_entry(b->data_head.next,
+ struct elsa_data, data_list);
+ elsa_data_free(d);
+ }
+
+ elsa_bank_free(b);
+
+ return 0;
+}
+
+/**
+ * elsa_process_remove - Remove a process from all banks that it belongs
+ * @p: pointer to the process to be removed
+ *
+ * Removes process from all banks
+ * Return 0 if succedeed, negative value otherwise
+ */
+int elsa_process_remove(struct task_struct *p)
+{
+
+ if (!p) {
+ /* bank doesn't exist */
+ dprintk("elsa_process_remove: process doesn't exist\n");
+ return -EAGAIN;
+ }
+
+ /* release all datas and remove the bank */
+ while (!list_empty(&(p->bank_head))) {
+ /* get pointer to the first element in the list */
+ struct elsa_data *d = list_entry(p->bank_head.next,
+ struct elsa_data, bank_list);
+ elsa_bank_remove(d);
+ }
+
+ return 0;
+}
+
+/**
+ * elsa_get_bank - Returns a pointer to a bank
+ * @bid: The identifier of a bank
+ *
+ * Finds the bank with given ID in the list of banks
+ */
+struct elsa_bank *elsa_get_bank(unsigned int bid)
+{
+ struct list_head *entry;
+
+ list_for_each(entry, &(elsa_broot.bank_head)) {
+ struct elsa_bank *b =
+ list_entry(entry, struct elsa_bank, bank_list);
+ if (b->bid == bid)
+ return b;
+ }
+
+ return NULL;
+}
+
+/**
+ * elsa_get_data - Returns a pointer to the container of a
+ * given pid in a given bank.
+ * @pid: The identifier of the process
+ *
+ * Find the data that holds process #PID in a given bank. If
+ * not found, NULL is returned.
+ */
+struct elsa_data *elsa_get_data(unsigned int pid, unsigned int bid)
+{
+ struct list_head *entry;
+ struct task_struct *p;
+
+ read_lock(&tasklist_lock);
+ p = find_task_by_pid(pid);
+ read_unlock(&tasklist_lock);
+
+ list_for_each(entry, &(p->bank_head)) {
+ struct elsa_data *d =
+ list_entry(entry, struct elsa_data, bank_list);
+ if (d->bid == bid)
+ /* Got it */
+ return d;
+ }
+
+ return NULL;
+}
+
+/**
+ * elsa_process_present - Returns 1 if process is in the bank
+ * @p: the process identifier to look for
+ * @b: the bank in which we must look.
+ *
+ * Return 1 if process is present in a bank, 0 otherwise
+ */
+int elsa_process_present(struct task_struct *p, struct elsa_bank *b)
+{
+ struct list_head *entry;
+
+ list_for_each(entry, &(b->data_head)) {
+ struct elsa_data *d =
+ list_entry(entry, struct elsa_data, data_list);
+ if (d->process == p) {
+ /* Found it */
+ return 1;
+ }
+ }
+
+ /* Process not found in b */
+ return 0;
+}
+
+/* memory management */
+EXPORT_SYMBOL(elsa_bank_alloc);
+EXPORT_SYMBOL(elsa_data_alloc);
+EXPORT_SYMBOL(elsa_bank_free);
+EXPORT_SYMBOL(elsa_data_free);
+
+/* data manipulation */
+EXPORT_SYMBOL(elsa_bank_add);
+EXPORT_SYMBOL(elsa_bank_remove);
+EXPORT_SYMBOL(elsa_bank_clean);
+EXPORT_SYMBOL(elsa_process_remove);
+
+/* get informations */
+EXPORT_SYMBOL(elsa_get_bank);
+EXPORT_SYMBOL(elsa_get_data);
+EXPORT_SYMBOL(elsa_process_present);
+
+ /*****************************************************************************
+ * Following functions are called from kernel function. *
+ * elsa_copy_parent_bank() is used when child is created (kernel/fork.c) *
+ *****************************************************************************/
+
+/**
+ * elsa_copy_parent_bank - Add a given process to the same banks
+ * as another one
+ * @from: Process from where we will copy information
+ * @to: Process to where we will copy information
+ *
+ * Goes through the banks to which "from" process belong and add
+ * the process in those banks.
+ *
+ * It is used when doing a fork (kernel/fork.c - copy_process()).
+ * This function is used by the kernel to update child's banks.
+ */
+void elsa_copy_parent_bank(struct task_struct *from, struct task_struct *to)
+{
+ struct list_head *entry;
+
+ /*
+ * First, initialize to->bank_head otherwise, if
+ * from->bank_head is NULL it won't be initialize
+ */
+ write_lock_irq(&tasklist_lock);
+ INIT_LIST_HEAD(&(to->bank_head));
+ write_unlock_irq(&tasklist_lock);
+
+ list_for_each(entry, &(from->bank_head)) {
+ struct elsa_bank *b;
+ struct elsa_data *d_from;
+ struct elsa_data *d_to;
+
+ dprintk("elsa_copy_parent_bank: from pid#%d to pid#%d\n",
+ from->pid, to->pid);
+
+ d_from = list_entry(entry, struct elsa_data, bank_list);
+ if (!d_from)
+ return;
+
+ /* allocat space to new data */
+ d_to = elsa_data_alloc();
+ if (!d_to) {
+ printk("elsa_copy_parent_bank: Cannot create data\n");
+ return;
+ }
+
+ d_to->bid = d_from->bid;
+ d_to->process = to;
+
+ /* d_to->process is set so we can call elsa_bank_add */
+ b = elsa_get_bank(d_from->bid);
+ if (!b) {
+ elsa_data_free(d_to);
+ printk
+ ("elsa_copy_parent_bank: Destination not found\n");
+ return;
+ }
+
+ if (b->bid != elsa_bank_add(b, d_to)) {
+ elsa_data_free(d_to);
+ printk("elsa_copy_parent_bank: Copy error\n");
+ return;
+ }
+ }
+}
+
+ /*********************************
+ * functions used to manage /proc *
+ * *
+ * The entry is /proc/bankinfo *
+ *********************************/
+
+/***
+ * Add an entry in /proc to get informations concerning
+ * banks. This entry is called /proc/bankinfo
+ ****/
+#ifdef CONFIG_PROC_FS
+static void *b_start(struct seq_file *m, loff_t * pos)
+{
+ loff_t n = *pos;
+ struct list_head *p;
+
+ /* Header is displaying just during the first called */
+ if (!n) {
+ seq_puts(m, "# - bankinfo -\n");
+ seq_puts(m, "# bankid:\t<process> <process> ...\n");
+ }
+
+ if (list_empty(&elsa_broot.bank_head))
+ return NULL;
+
+ p = elsa_broot.bank_head.next;
+ while (n--) {
+ p = p->next;
+ if (p == &elsa_broot.bank_head)
+ return NULL;
+ }
+
+ /* we return a pointer to the data in the bank */
+ return list_entry(p, struct elsa_bank, bank_list);
+}
+
+static void b_stop(struct seq_file *m, void *v)
+{
+}
+
+static void *b_next(struct seq_file *m, void *v, loff_t * pos)
+{
+ struct elsa_bank *bank = v;
+
+ ++*pos;
+ return bank->bank_list.next == &elsa_broot.bank_head ? NULL :
+ list_entry(bank->bank_list.next, struct elsa_bank, bank_list);
+}
+
+static int show_bankinfo(struct seq_file *m, void *v)
+{
+ struct elsa_bank *bank = v;
+ struct elsa_data *data;
+
+ if (!bank) {
+ seq_printf(m, "There is no banks\n");
+ } else {
+ /* display bank identifier */
+ seq_printf(m, "%d:", bank->bid);
+ /* add a tabulation */
+ seq_printf(m, "\t");
+ /* display list of processus */
+ if (list_empty(&(bank->data_head))) {
+ seq_printf(m, "Empty");
+ } else {
+ list_for_each_entry(data, &(bank->data_head), data_list)
+ seq_printf(m, "%d ", data->process->pid);
+ }
+ /* add EOL */
+ seq_printf(m, "\n");
+ }
+
+ return 0;
+}
+
+/* bankinfo_op - iterator that generates /proc/bankinfo
+ *
+ * Output layout:
+ * bankID pid ...
+ */
+struct seq_operations bankinfo_op = {
+ .start = b_start,
+ .stop = b_stop,
+ .next = b_next,
+ .show = show_bankinfo,
+};
+
+int bankinfo_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &bankinfo_op);
+}
+
+struct file_operations proc_bankinfo_ops = {
+ .open = bankinfo_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+
+EXPORT_SYMBOL(proc_bankinfo_ops);
+
+#endif
diff -uprN -X elsa_import/dontdiff linux-2.6.6/drivers/elsacct/elsacct.c linux-2.6.6-elsa/drivers/elsacct/elsacct.c
--- linux-2.6.6/drivers/elsacct/elsacct.c 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.6-elsa/drivers/elsacct/elsacct.c 2004-05-12 07:24:41.389160480 +0200
@@ -0,0 +1,438 @@
+/*
+ * driver/elsacct/elsacct.c
+ *
+ * ELSA - Enhanced Linux System Accounting
+ * Guillaume Thouvenin - 26/04/2004
+ *
+ * This module implements Enhanced Linux System Accounting.
+ * We implement a character driver to transfer data between
+ * BANK that are in the kernel adress space and the user
+ * adress space.
+ *
+ * This code is licenced under GPL.
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/blkdev.h>
+#include <linux/times.h>
+
+#include <linux/elsacct.h>
+#include <linux/bank.h>
+
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_ELSACCT_DEBUG
+#define dprintk(format...) printk(format)
+#else
+#define dprintk(format...)
+#endif
+
+static int elsa_major;
+static struct elsa_acct acct_info = ELSA_ACCT_INIT;
+
+/**
+ * encode_comp_t - encode an unsigned long into a comp_t
+ * @value: value to encode
+ *
+ * This routine has been adopted from the encode_comp_t() function in
+ * the kern_acct.c file of the FreeBSD operating system. The encoding
+ * is a 13-bit fraction with a 3-bit (base 8) exponent.
+ *
+ * This routine is taken from kernel/acct.c
+ */
+#define MANTSIZE 13 /* 13 bit mantissa. */
+#define EXPSIZE 3 /* Base 8 (3 bit) exponent. */
+#define MAXFRACT ((1 << MANTSIZE) - 1) /* Maximum fractional value. */
+
+typedef __u16 comp_t;
+
+static comp_t encode_comp_t(unsigned long value)
+{
+ int exp, rnd;
+
+ exp = rnd = 0;
+ while (value > MAXFRACT) {
+ rnd = value & (1 << (EXPSIZE - 1)); /* Round up? */
+ value >>= EXPSIZE; /* Base 8 exponent == 3 bit shift. */
+ exp++;
+ }
+
+ /*
+ * If we need to round up, do it (and handle overflow correctly).
+ */
+ if (rnd && (++value > MAXFRACT)) {
+ value >>= EXPSIZE;
+ exp++;
+ }
+
+ /*
+ * Clean it up and polish it off.
+ */
+ exp <<= MANTSIZE; /* Shift the exponent into place */
+ exp += value; /* and add on the mantissa. */
+ return exp;
+}
+
+ /**************************
+ * Accounting is done here *
+ **************************/
+/*
+ * It's just for testing (Can not choose file)
+ */
+/**
+ * do_elsacct_data - Update accounting information when bank is removed
+ * @b: pointer to bank's data
+ *
+ * We copy code from BSD accounting. It's just to test accounting
+ * Result is written in a file (you can not choose it for now).
+ * It returns 0 if succeded, negative value otherwise
+ */
+static int do_elsacct_bank(struct elsa_bank *b)
+{
+ struct elsa_acct *info = (struct elsa_acct *)b->info;
+ struct file *file = NULL;
+ mm_segment_t old_fs;
+
+ if (!info)
+ return -EINVAL;
+
+ /* Set bank ID */
+ info->eac_bid = b->bid;
+
+ /* Open file where accounting information will be written */
+ file = filp_open(ELSACCT_FILE, O_CREAT | O_WRONLY | O_APPEND, 0666);
+
+ if (IS_ERR(file)) {
+ return (PTR_ERR(file));
+ }
+
+ if (!S_ISREG(file->f_dentry->d_inode->i_mode)) {
+ filp_close(file, NULL);
+ return (-EACCES);
+ }
+
+ if (!file->f_op->write) {
+ filp_close(file, NULL);
+ return (-EIO);
+ }
+
+ /*
+ * Kernel segment override to datasegment and write it
+ * to the accounting file.
+ */
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ file->f_op->write(file, (char *)info, sizeof(struct elsa_acct),
+ &file->f_pos);
+
+ set_fs(old_fs);
+
+ filp_close(file, NULL);
+ return 0;
+}
+
+/**
+ * do_elsacct_data - Update accounting information when data is removed
+ * @b: pointer to bank's data
+ * @d: pointer to the data
+ *
+ * We copy code from BSD accounting. It's just to test accounting
+ * It returns 0 if succeded, negative value otherwise
+ */
+static int do_elsacct_data(struct elsa_bank *b, struct elsa_data *d)
+{
+ u64 elapsed;
+ struct elsa_acct *info = (struct elsa_acct *)b->info;
+ struct task_struct *p = d->process;
+
+ if (!info)
+ return -EINVAL;
+
+ /* One more process in the bank */
+ info->eac_ptot++;
+
+ /* elapsed time */
+ elapsed = jiffies_64_to_clock_t(get_jiffies_64() - p->start_time);
+ info->eac_etime += encode_comp_t(elapsed < (unsigned long)-1l ?
+ (unsigned long)elapsed : (unsigned
+ long)-1l);
+
+ /* user time and system time */
+ info->eac_utime += encode_comp_t(jiffies_to_clock_t(p->utime));
+ info->eac_stime += encode_comp_t(jiffies_to_clock_t(p->stime));
+
+ /* minor and major page faults */
+ info->eac_minflt += encode_comp_t(p->min_flt);
+ info->eac_majflt += encode_comp_t(p->maj_flt);
+
+ return 0;
+}
+
+int do_elsacct(int opcode, struct elsa_bank *b, struct elsa_data *d)
+{
+ int retval = 0;
+
+ switch (opcode) {
+ case ELSA_BANK_CALLBACK:
+ BUG_ON(b == NULL);
+ retval = do_elsacct_bank(b);
+ break;
+ case ELSA_DATA_CALLBACK:
+ BUG_ON(b == NULL);
+ BUG_ON(d == NULL);
+ retval = do_elsacct_data(b, d);
+ break;
+ default:
+ printk("do_elsacct: unknown opcode\n");
+ retval = -ENOIOCTLCMD;
+ break;
+ }
+
+ return retval;
+}
+
+ /**************************************************************
+ * functions used to manipulate the device *
+ * *
+ * The enhanced linux system accounting device is /dev/elsacct *
+ * and the major number is dynamically given by OS *
+ **************************************************************/
+
+/*
+ * The process context, represented as a typical driver method - ioctl(), must
+ * use spin_lock_irq() because it knows that interrupts are always enabled while
+ * executing the device ioctl() method.
+ */
+spinlock_t elsa_lock = SPIN_LOCK_UNLOCKED;
+
+/**
+ * elsacct_ioctl - issue a device specific command
+ * @inode: pointer to inode structure
+ * @file : file pointer
+ * @cmd : specific command to perform.
+ * @arg : arguments that depend of cmd.
+ *
+ * Perform some actions on banks that depend of command passed to the
+ * system call
+ */
+int elsacct_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ int retval = 0;
+ struct elsa_ioctl_args args;
+ struct task_struct *p;
+ struct elsa_bank *b;
+ struct elsa_data *d;
+
+ /* Recover ioctl parameters */
+ if (copy_from_user(&args, (struct elsa_ioctl_args *)arg,
+ sizeof(struct elsa_ioctl_args)))
+ return -EFAULT;
+
+ switch (cmd) {
+ case ELSACCT_BANK_REMOVE:
+ /*
+ * To remove a data from a bank, we need to:
+ * 1) if #BID == 0
+ * get pointer to process
+ * remove from all banks that it belongs
+ * else
+ * get pointer to data
+ * remove from bank #BID
+ * endif
+ */
+ if (args.bid == 0) {
+ read_lock(&tasklist_lock);
+ p = find_task_by_pid(args.pid);
+ read_unlock(&tasklist_lock);
+ if (!p) {
+ dprintk("elsacct_ioct: process #%d not found\n",
+ args.pid);
+ return -EAGAIN;
+ }
+
+ spin_lock_irq(&elsa_lock);
+ retval = elsa_process_remove(p);
+ spin_unlock_irq(&elsa_lock);
+ } else {
+
+ d = elsa_get_data(args.pid, args.bid);
+ if (!d) {
+ dprintk("elsacct_ioct: data not found\n");
+ return -EAGAIN;
+ }
+
+ spin_lock_irq(&elsa_lock);
+ retval = elsa_bank_remove(d);
+ spin_unlock_irq(&elsa_lock);
+ }
+ break;
+
+ case ELSACCT_BANK_CLEAN:
+ /*
+ * To clean a bank (remove all data), we need to:
+ * 1) Get pointer to the bank
+ * 2) Remove it
+ */
+ b = elsa_get_bank(args.bid);
+ if (!b) {
+ dprintk("elsacct_ioct: BANK not found\n");
+ return -EAGAIN;
+ }
+
+ spin_lock_irq(&elsa_lock);
+ retval = elsa_bank_clean(b);
+ spin_unlock_irq(&elsa_lock);
+ break;
+
+ case ELSACCT_BANK_ADD:
+ /*
+ * To add a process to a bank we need to perform
+ * following actions:
+ * 1) Allocate memory space to data (container)
+ * 2) Update process in data field
+ * 3) If #BID == 0
+ * create a new bank;
+ * else
+ * get pointer to bank #BID
+ * 4) Add the container to the bank
+ */
+ d = elsa_data_alloc();
+ if (!d)
+ return -ENOMEM;
+
+ /* We need the pointer to task_struct */
+ read_lock(&tasklist_lock);
+ p = find_task_by_pid(args.pid);
+ read_unlock(&tasklist_lock);
+ if (!p) {
+ dprintk("elsacct_ioct: PID#%d not found\n", args.pid);
+ /* Don't forget to release memory */
+ elsa_data_free(d);
+ return -EAGAIN;
+ }
+ d->process = p;
+
+ if (args.bid == 0)
+ b = elsa_bank_alloc(&do_elsacct, &acct_info);
+ else
+ b = elsa_get_bank(args.bid);
+
+ if (!b) {
+ dprintk("elsacct_ioct: BID#%d not found\n", args.bid);
+ /* Don't forget to release memory */
+ elsa_data_free(d);
+ return -EAGAIN;
+ }
+
+ spin_lock_irq(&elsa_lock);
+ retval = elsa_bank_add(b, d);
+ spin_unlock_irq(&elsa_lock);
+ break;
+
+ default:
+ dprintk(KERN_WARNING
+ "elsacct_ioctl: 0x%x unsupported ioctl command\n", cmd);
+ retval = -ENOIOCTLCMD;
+ };
+
+ return retval;
+}
+
+struct file_operations elsa_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = elsacct_ioctl,
+};
+
+/**
+ * elsacct_init - Initialize enhanced linux system accounting
+ *
+ * Initializes "callbacks" to bank and data. Those
+ * functions are called a bank or a data are destroyed.
+ * It allows to perform some actions in the module.
+ */
+static int __init elsacct_init(void)
+{
+ int retval = 0;
+ struct proc_dir_entry *entry;
+
+ /* register character device with a dynamic major number */
+ elsa_major = register_chrdev(0, "elsacct", &elsa_fops);
+ if (!elsa_major) {
+ dprintk(KERN_WARNING
+ "elsacct_init: can't get major %d\n", elsa_major);
+ return -EIO;
+ }
+
+ /*
+ * To get the major number we use a script that parse /proc/devices
+ * and get the correct major number. We don't use elsa_major except
+ * to test if the device is registered.
+ */
+ dprintk(KERN_INFO "ELSACCT: device driver is recorded\n");
+ /*
+ * From here, dev is registered. Thus, if there is a problem
+ * we must unregister the device.
+ */
+
+#ifdef CONFIG_PROC_FS
+ /* create /proc entry */
+ entry = create_proc_entry("bankinfo", 0, NULL);
+ if (entry) {
+ entry->proc_fops = &proc_bankinfo_ops;
+ } else {
+ unregister_chrdev(elsa_major, "elsacct");
+ return -EIO;
+ }
+#endif
+
+ /* succeed */
+ return retval;
+}
+
+ /************************
+ * Module initialization *
+ ************************/
+
+/**
+ * elsacct_init_module - called when module is loaded
+ *
+ * Display a message in dmesg and call the real
+ * initialization function.
+ */
+static int __init elsacct_init_module(void)
+{
+ dprintk(KERN_INFO "ELSA accounting started\n");
+ return elsacct_init();
+}
+
+/**
+ * elsacct_cleanup - called when module is unloaded
+ *
+ * Display a message in dmesg
+ */
+static void __exit elsacct_cleanup_module(void)
+{
+ /* release the major number when module is unloaded */
+ if (unregister_chrdev(elsa_major, "elsacct"))
+ dprintk(KERN_WARNING
+ "elsacct_cleanup: cannot unregister blkdev\n");
+ else
+ dprintk(KERN_INFO "elsacct_cleanup: accounting terminated\n");
+}
+
+module_init(elsacct_init_module);
+module_exit(elsacct_cleanup_module);
+
+MODULE_DESCRIPTION("Enhanced Linux System Accounting.");
+MODULE_AUTHOR("Guillaume Thouvenin <guillaume.thouvenin@xxxxxxxx>");
+
+MODULE_LICENSE("GPL");
diff -uprN -X elsa_import/dontdiff linux-2.6.6/drivers/elsacct/Kconfig linux-2.6.6-elsa/drivers/elsacct/Kconfig
--- linux-2.6.6/drivers/elsacct/Kconfig 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.6-elsa/drivers/elsacct/Kconfig 2004-05-11 14:19:56.000000000 +0200
@@ -0,0 +1,61 @@
+#
+# For a description of the syntax of this configuration file,
+# see Documentation/kbuild/kconfig-language.txt.
+#
+
+menu "ELSAcct Driver"
+
+config BANK
+ bool "ELSA bank support"
+ depends on EXPERIMENTAL
+ default n
+ ---help---
+ BANK structures are needed if you want to do enhanced
+ linux system accounting
+
+ say Y if you want to use Enhanced Linux Sytem Accounting
+
+config BANK_DEBUG
+ bool "ELSA bank debugging support"
+ depends on BANK
+ default n
+ ---help---
+ This option allows you to enable debugging output when using
+ banks structure. Currently, such structure is used by Enhanced
+ Linux System Accounting (ELSA). Informations are sent to the
+ console.
+
+config ELSACCT
+ tristate "Enhanced Linux System Accounting"
+ depends on EXPERIMENTAL
+ requires BANK
+ default n
+ ---help---
+ The goal of accounting is to collect and report the use of various
+ system resources by applications. Informations, like process time,
+ CPU usage, connect time or disk space usage, provides data that helps
+ the system to adjust the use of resources between processes.
+
+ The current BSD-like process accounting that already exists in Linux
+ collects informations on individual users or groups of users. The ELSA
+ project aims to improve and extend the monitoring of resources with
+ different criteria like groups of processes. Another target for this
+ project is to give Linux an homogeneous set of commands for all kinds
+ of accounting (memory, CPU and I/O).
+
+ To compile this driver as a module, choose M here: the module will be
+ called elsa-acct.
+
+ Documentation about ELSA is available from
+ <http://elsa.sourceforge.net>
+
+config ELSACCT_DEBUG
+ bool "ELSA accounting debugging support"
+ depends on ELSACCT
+ default n
+ ---help---
+ This option allows you to enable debugging output when using
+ enhanced linux system accounting. Informations are sent to the
+ console.
+
+endmenu
diff -uprN -X elsa_import/dontdiff linux-2.6.6/drivers/elsacct/Makefile linux-2.6.6-elsa/drivers/elsacct/Makefile
--- linux-2.6.6/drivers/elsacct/Makefile 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.6-elsa/drivers/elsacct/Makefile 2004-05-06 12:39:50.000000000 +0200
@@ -0,0 +1,5 @@
+#
+# Makefile for the Enhanced Linux System Accounting
+#
+obj-$(CONFIG_BANK) += bank.o
+obj-$(CONFIG_ELSACCT) += elsacct.o
diff -uprN -X elsa_import/dontdiff linux-2.6.6/drivers/Kconfig linux-2.6.6-elsa/drivers/Kconfig
--- linux-2.6.6/drivers/Kconfig 2004-05-10 04:33:20.000000000 +0200
+++ linux-2.6.6-elsa/drivers/Kconfig 2004-05-10 09:45:43.000000000 +0200
@@ -4,6 +4,8 @@ menu "Device Drivers"
source "drivers/base/Kconfig"
+source "drivers/elsacct/Kconfig"
+
source "drivers/mtd/Kconfig"
source "drivers/parport/Kconfig"
diff -uprN -X elsa_import/dontdiff linux-2.6.6/drivers/Makefile linux-2.6.6-elsa/drivers/Makefile
--- linux-2.6.6/drivers/Makefile 2004-05-10 04:33:13.000000000 +0200
+++ linux-2.6.6-elsa/drivers/Makefile 2004-05-10 09:46:10.000000000 +0200
@@ -50,3 +50,4 @@ obj-$(CONFIG_MCA) += mca/
obj-$(CONFIG_EISA) += eisa/
obj-$(CONFIG_CPU_FREQ) += cpufreq/
obj-y += firmware/
+obj-y += elsacct/
diff -uprN -X elsa_import/dontdiff linux-2.6.6/include/linux/bank.h linux-2.6.6-elsa/include/linux/bank.h
--- linux-2.6.6/include/linux/bank.h 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.6-elsa/include/linux/bank.h 2004-05-12 07:24:35.766015328 +0200
@@ -0,0 +1,164 @@
+/*
+ * include/linux/elsa_bank.h
+ *
+ * ELSA - Enhanced Linux System Accounting
+ * Guillaume Thouvenin - 26/04/2004
+ *
+ * Provides structures and functions to manipulate "BANK".
+ * They are containers that store a set of processes.
+ * When a "BANK" is empty it is destroy and accounting infor-
+ * mations are stored in a file. Informations are about
+ * all process contained in a "BANK"
+ *
+ * The idea is to provide a mechanism that allows to group
+ * chosen process together. "BANK" must have some properties like:
+ * o If a process belongs to a bank, its children belong to the same bank
+ * o A process can be placed in several different banks
+ * o When the user adds a process to an non-existent bank, the container
+ * must automatically be created
+ * o When the last process of a bank exits, informations about all processes
+ * that belonged to the bank must be stored (maybe in a file) and the
+ * container must be destroyed
+ *
+ * To do that, we use a double linked list provides by the kernel
+ * (include/linux/list.h) and we do the following thing:
+ *
+ * BANK #1 BANK #2
+ * elsa_bank elsa_bank
+ * elsa_broot ----------- -----------
+ * bank_root | bid = 1 | | bid = 2 |
+ * -------------- |-----------| |-----------|
+ * | next_bid = 3 | | info | | info |
+ * |--------------| |-----------| |-----------|
+ * | freebid_head | | callback | | callback |
+ * |--------------| |-----------| |-----------|
+ * | bank_head |<===>| bank_list |<===>| bank_list |<===>...
+ * -------------- |-----------| |-----------|
+ * | data_head | | data_head |<===>...
+ * ---------- -----------
+ * ^ ^
+ * | |
+ * | =================
+ * PROCESS DATA #1 | DATA #2 |
+ * task_struct elsa_data | elsa_data |
+ * --------- ----------- | ----------- |
+ * | | | bid = 1 | | | bid = 1 | |
+ * | | |-----------| | |-----------| |
+ * | |<------| process | | | process | |
+ * | | |-----------| | |-----------| |
+ * | | | data_list |<= | data_list |<===
+ * ... ... | |<===>| |
+ * |---------| |-----------| |-----------|
+ * |bank_head|<=====>| bank_list | | bank_list |
+ * --------- ----------- -----------
+ *
+ *
+ * Field "bank_list" in the elsa_data is used to know which are banks with
+ * a given process. Structure "task_struc" is modified to use this field.
+ */
+
+#ifndef __LINUX_BANK_H
+#define __LINUX_BANK_H
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+
+#define ELSA_BANK_CALLBACK 0
+#define ELSA_DATA_CALLBACK 1
+
+extern rwlock_t elsalist_lock;
+
+/***
+ * Internal structures
+ ***/
+
+struct bank_root {
+ /*
+ * We need to protect ID by a semaphore since it must be
+ * a unique number. We can't have to bank with same ID
+ */
+ unsigned int next_bid; /* next available bank identifier */
+ struct list_head freebid_head; /* a list of free bank identifier - Not used yet */
+ struct list_head bank_head; /* head of the list of bank */
+};
+
+#define BANK_ROOT_INIT(root) \
+{ \
+ .next_bid = 1, \
+ .freebid_head = LIST_HEAD_INIT(root.freebid_head), \
+ .bank_head = LIST_HEAD_INIT(root.bank_head), \
+}
+
+struct elsa_freebid {
+ int bid;
+ struct list_head bid_list;
+};
+
+struct elsa_data; /* Needed in struct elsa_bank */
+
+struct elsa_bank {
+ int bid; /* the bank identifier */
+ void *info; /* allow to hang information to banks */
+ int (*callback) (int, struct elsa_bank *, struct elsa_data *); /* callback */
+ struct list_head bank_list; /* list of available banks */
+ struct list_head data_head; /* head of the list of datas in the
+ bank */
+};
+
+struct elsa_data {
+ int bid; /* the bank to which data belong */
+ struct task_struct *process; /* the process information */
+ struct list_head data_list; /* link between datas in a bank */
+ struct list_head bank_list; /* used by a process to link banks
+ that contains it */
+};
+
+#ifdef CONFIG_PROC_FS
+extern struct file_operations proc_bankinfo_ops;
+#endif
+
+ /*********************************************
+ * Following functions can be use by a module *
+ * (=> they are exported) *
+ *********************************************/
+
+/* allocate memory space: (call to kmalloc) */
+struct elsa_bank
+*elsa_bank_alloc(int (*fn) (int, struct elsa_bank *, struct elsa_data *),
+ void *);
+struct elsa_data *elsa_data_alloc(void);
+void elsa_bank_free(struct elsa_bank *bank);
+void elsa_data_free(struct elsa_data *data);
+
+/*
+ * manipulate datas:
+ * Memory space can be released (call to kfree) in the last three
+ * functions.
+ */
+int elsa_bank_add(struct elsa_bank *b, struct elsa_data *d);
+int elsa_bank_remove(struct elsa_data *d);
+int elsa_bank_clean(struct elsa_bank *b);
+/* elsa_process_remove() is used by the kernel so it is declared below */
+
+/* get information */
+struct elsa_bank *elsa_get_bank(unsigned int bid);
+struct elsa_data *elsa_get_data(unsigned int pid, unsigned int bid);
+int elsa_process_present(struct task_struct *p, struct elsa_bank *b);
+
+ /*****************************************************************************
+ * Following functions are called from kernel function. *
+ * elsa_copy_parent() is used when child is created (kernel/fork.c) *
+ * elsa_process_remove() is used when a process terminates (kernel/exit.c) *
+ *****************************************************************************/
+#ifdef CONFIG_BANK
+extern void elsa_copy_parent_bank(struct task_struct *from,
+ struct task_struct *to);
+extern int elsa_process_remove(struct task_struct *p);
+#else
+#define elsa_copy_parent_bank(a,b) do {} while(0)
+#define elsa_bank_remove_all(a) do {} while(0)
+#endif /* !(CONFIG_BANK) */
+
+#endif /* !(__LINUX_BANK_H) */
diff -uprN -X elsa_import/dontdiff linux-2.6.6/include/linux/elsacct.h linux-2.6.6-elsa/include/linux/elsacct.h
--- linux-2.6.6/include/linux/elsacct.h 1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.6-elsa/include/linux/elsacct.h 2004-05-12 07:24:32.067577576 +0200
@@ -0,0 +1,55 @@
+/*
+ * include/linux/elsa_acct.h
+ *
+ * ELSA - Enhanced Linux System Accounting
+ * Guillaume Thouvenin - 26/04/2004
+ *
+ */
+
+#ifndef __LINUX_ELSACCT_H
+#define __LINUX_ELSACCT_H
+
+/* IOCTL numbers */
+#define ELSACCT_MAGIC 0x3538
+
+#define ELSACCT_BANK_REMOVE 0x353801 /* Remove one data from a bank,
+ * if #BID == 0 remove from all banks */
+#define ELSACCT_BANK_CLEAN 0x353802 /* Remove all data from a bank */
+#define ELSACCT_BANK_ADD 0x353803 /* Add a process to a bank,
+ * if #BID == 0 create the bank */
+
+/* Currently, informations are stored in the same file and we cannot chose it */
+#define ELSACCT_FILE "/var/log/bank"
+
+struct elsa_ioctl_args {
+ unsigned int bid; /* bank identifier */
+ unsigned int pid; /* process identifier */
+};
+
+/*
+ * Currently, we take as a starting point the BSD-style
+ * process accounting and we write values into dmesg
+ * buffer. (of course we need to change this)
+ */
+struct elsa_acct {
+ int eac_bid; /* Bank id */
+ int eac_ptot; /* Total number of process that are added to a bank */
+ int eac_utime; /* Accounting User Time */
+ int eac_stime; /* Accounting System Time */
+ int eac_etime; /* Accounting Elapsed Time */
+ int eac_minflt; /* Accounting Minor Pagefaults */
+ int eac_majflt; /* Accounting Major Pagefaults */
+};
+
+#define ELSA_ACCT_INIT \
+{ \
+ .eac_bid = 0, \
+ .eac_ptot = 0, \
+ .eac_utime = 0, \
+ .eac_stime = 0, \
+ .eac_etime = 0, \
+ .eac_minflt = 0, \
+ .eac_majflt = 0, \
+}
+
+#endif /* !(__LINUX_ELSACCT_H) */
diff -uprN -X elsa_import/dontdiff linux-2.6.6/include/linux/init_task.h linux-2.6.6-elsa/include/linux/init_task.h
--- linux-2.6.6/include/linux/init_task.h 2004-05-10 04:32:00.000000000 +0200
+++ linux-2.6.6-elsa/include/linux/init_task.h 2004-05-10 09:48:18.000000000 +0200
@@ -112,6 +112,7 @@ extern struct group_info init_groups;
.proc_lock = SPIN_LOCK_UNLOCKED, \
.switch_lock = SPIN_LOCK_UNLOCKED, \
.journal_info = NULL, \
+ .bank_head = LIST_HEAD_INIT(tsk.bank_head), \
}
diff -uprN -X elsa_import/dontdiff linux-2.6.6/include/linux/sched.h linux-2.6.6-elsa/include/linux/sched.h
--- linux-2.6.6/include/linux/sched.h 2004-05-10 04:32:00.000000000 +0200
+++ linux-2.6.6-elsa/include/linux/sched.h 2004-05-10 09:50:04.000000000 +0200
@@ -504,6 +504,9 @@ struct task_struct {
unsigned long ptrace_message;
siginfo_t *last_siginfo; /* For ptrace use. */
+
+/* List of BANK to which the process belong -- Used by ELSA */
+ struct list_head bank_head;
};
static inline pid_t process_group(struct task_struct *tsk)
diff -uprN -X elsa_import/dontdiff linux-2.6.6/kernel/exit.c linux-2.6.6-elsa/kernel/exit.c
--- linux-2.6.6/kernel/exit.c 2004-05-10 04:33:19.000000000 +0200
+++ linux-2.6.6-elsa/kernel/exit.c 2004-05-10 09:55:57.000000000 +0200
@@ -59,6 +59,7 @@ repeat:
atomic_dec(&p->user->processes);
spin_lock(&p->proc_lock);
proc_dentry = proc_pid_unhash(p);
+ elsa_process_remove(p);
write_lock_irq(&tasklist_lock);
if (unlikely(p->ptrace))
__ptrace_unlink(p);
diff -uprN -X elsa_import/dontdiff linux-2.6.6/kernel/fork.c linux-2.6.6-elsa/kernel/fork.c
--- linux-2.6.6/kernel/fork.c 2004-05-10 04:32:00.000000000 +0200
+++ linux-2.6.6-elsa/kernel/fork.c 2004-05-10 09:57:36.000000000 +0200
@@ -1011,6 +1011,19 @@ struct task_struct *copy_process(unsigne
INIT_LIST_HEAD(&p->ptrace_children);
INIT_LIST_HEAD(&p->ptrace_list);
+ /*
+ * Child is in the same BANK as parent. So we copy the list of banks
+ * from current to p. We can not just counting reference on the structure
+ * because the may evoluate in differents way. I think it's easier to
+ * do like this but maybe I'm wrong... -- guillaume
+ *
+ * We put that call outside lock_irq because elsa_copy_parent_bank
+ * uses kmalloc(GFP_KERNEL). Therefore, as we modify task_struct,
+ * we manage lock_irq ourselves. -- guillaume
+ *
+ */
+ elsa_copy_parent_bank(current, p);
+
/* Need tasklist lock for parent etc handling! */
write_lock_irq(&tasklist_lock);
/*
diff -uprN -X elsa_import/dontdiff linux-2.6.6/Makefile linux-2.6.6-elsa/Makefile
--- linux-2.6.6/Makefile 2004-05-10 04:32:53.000000000 +0200
+++ linux-2.6.6-elsa/Makefile 2004-05-10 09:58:01.000000000 +0200
@@ -1,7 +1,7 @@
VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 6
-EXTRAVERSION =
+EXTRAVERSION = -elsa
NAME=Zonked Quokka
# *DOCUMENTATION*