bsd process accounting

Tom Dyas (tdyas@gandalf.rutgers.edu)
Fri, 25 Aug 95 23:08:12 EDT


Here is an updated patch for bsd process accounting that corrects a few minor
errors. It also fills in all the accounting fields. There is a copy of an
accton(1) program after the patch.

****
**** patches for 1.3.20
****

diff -u --new-file linux/fs/read_write.c.old linux/fs/read_write.c
--- linux/fs/read_write.c.old Tue Aug 22 21:11:58 1995
+++ linux/fs/read_write.c Tue Aug 22 21:16:04 1995
@@ -115,7 +115,11 @@
error = verify_area(VERIFY_WRITE,buf,count);
if (error)
return error;
- return file->f_op->read(inode,file,buf,count);
+
+ error = file->f_op->read(inode,file,buf,count);
+ if (error > 0)
+ current->io_chars += error;
+ return error;
}

asmlinkage int sys_write(unsigned int fd,char * buf,unsigned int count)
@@ -147,5 +151,7 @@
newattrs.ia_valid = ATTR_MODE;
notify_change(inode, &newattrs);
}
+ if (written > 0)
+ current->io_chars += written;
return written;
}

diff -u --new-file linux/fs/exec.c.old linux/fs/exec.c
--- linux/fs/exec.c.old Tue Jul 11 23:41:58 1995
+++ linux/fs/exec.c Wed Aug 23 17:43:22 1995
@@ -711,6 +711,8 @@
if (retval >= 0) {
iput(bprm.inode);
current->did_exec = 1;
+ current->used_superuser_priv = 0;
+ current->io_chars = 0;
return retval;
}
if (retval != -ENOEXEC)

diff -u --new-file linux/kernel/sys.c.old linux/kernel/sys.c
--- linux/kernel/sys.c.old Mon Aug 21 22:35:10 1995
+++ linux/kernel/sys.c Wed Aug 23 17:55:16 1995
@@ -243,11 +243,6 @@
return 0;
}

-asmlinkage int sys_acct(void)
-{
- return -ENOSYS;
-}
-
asmlinkage int sys_phys(void)
{
return -ENOSYS;

diff -u --new-file linux/kernel/exit.c.old linux/kernel/exit.c
--- linux/kernel/exit.c.old Mon Aug 21 22:35:56 1995
+++ linux/kernel/exit.c Fri Aug 25 22:41:42 1995
@@ -15,6 +15,7 @@
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/malloc.h>
+#include <linux/acct.h>

#include <asm/segment.h>
extern void sem_exit (void);
@@ -381,6 +382,10 @@
intr_count = 0;
}
fake_volatile:
+#ifdef CONFIG_BSD_PROCESS_ACCT
+ if (acct_active)
+ acct_write_record(code);
+#endif
current->flags |= PF_EXITING;
del_timer(&current->real_timer);
sem_exit();

diff -u --new-file linux/kernel/acct.c.old linux/kernel/acct.c
--- linux/kernel/acct.c.old Mon Aug 21 22:57:42 1995
+++ linux/kernel/acct.c Fri Aug 25 22:40:46 1995
@@ -0,0 +1,364 @@
+/*
+ * linux/kernel/acct.c
+ *
+ * BSD Process Accounting for Linux
+ *
+ * Copyright (C) 1995, Thomas K. Dyas <tdyas@eden.rutgers.edu>
+ *
+ * The file implements BSD-style process accounting. Whenever any
+ * process exits, an accounting record of type "struct acct" is
+ * written to the file specified with the acct() system call. It is
+ * up to user-level programs to do useful things with the accounting
+ * log. The kernel just provides the raw accounting information.
+ *
+ * The code is provided under the GNU GPL. This software is very much
+ * ALPHA software. USE AT YOUR OWN RISK!
+ *
+ * TODO:
+ * - Make sure free space suspend/resume works.
+ */
+
+#include <linux/config.h>
+#include <linux/fs.h>
+#include <linux/vfs.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/fcntl.h>
+#include <linux/stat.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/timer.h>
+#include <linux/tty.h>
+#include <linux/acct.h>
+#include <asm/segment.h>
+
+#ifdef CONFIG_BSD_PROCESS_ACCT
+
+/*
+ * These constants control the amount of freespace that suspend and
+ * resume the process accounting system, and the time delay between
+ * each check.
+ */
+
+#define RESUME (4) /* more than 4% free space will resume */
+#define SUSPEND (2) /* less than 2% free space will suspend */
+#define TIMEOUT (30*HZ) /* 30 second timeout between checks */
+
+/*
+ * External references and all of the globals.
+ */
+
+extern int close_fp(struct file *);
+
+void acct_timeout(unsigned long);
+
+volatile int acct_active = 0;
+static volatile int acct_cancheck = 0;
+static struct file * acct_file = NULL;
+static struct timer_list acct_timer = { NULL, NULL, 0, 0, acct_timeout };
+
+/*
+ * kernelspace i/o functions
+ *
+ * The following functions allow the process accounting system to
+ * open, close, and write files from kernelspace without the need to
+ * have the file associated with a particular task. All of functions
+ * deal directly with the "struct file *".
+ */
+
+static int
+k_open(const char * filename, int flags, int mode, struct file **filp)
+{
+ struct inode * inode;
+ struct file * f;
+ int flag, error;
+
+ f = get_empty_filp();
+ if (!f)
+ return -ENFILE;
+ *filp = f;
+ f->f_flags = flag = flags;
+ f->f_mode = (flag+1) & O_ACCMODE;
+ if (f->f_mode)
+ flag++;
+ if (flag & (O_TRUNC | O_CREAT))
+ flag |= 2;
+ error = open_namei(filename,flag,mode,&inode,NULL);
+ if (!error && (f->f_mode & 2)) {
+ error = get_write_access(inode);
+ if (error)
+ iput(inode);
+ }
+ if (error) {
+ *filp=NULL;
+ f->f_count--;
+ return error;
+ }
+
+ f->f_inode = inode;
+ f->f_pos = 0;
+ f->f_reada = 0;
+ f->f_op = NULL;
+ if (inode->i_op)
+ f->f_op = inode->i_op->default_file_ops;
+ if (f->f_op && f->f_op->open) {
+ error = f->f_op->open(inode,f);
+ if (error) {
+ if (f->f_mode & 2) put_write_access(inode);
+ iput(inode);
+ f->f_count--;
+ *filp=NULL;
+ return error;
+ }
+ }
+ f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC);
+ return 0;
+}
+
+static int
+k_close(struct file *file)
+{
+ return(close_fp(file));
+}
+
+static int
+k_write(struct file *file, char * buf, unsigned int count)
+{
+ struct inode * inode;
+ int written;
+ unsigned long old_fs;
+
+ if (!file || !(inode=file->f_inode))
+ return -EBADF;
+ if (!(file->f_mode & 2))
+ return -EBADF;
+ if (!file->f_op || !file->f_op->write)
+ return -EINVAL;
+ if (!count)
+ return 0;
+
+ old_fs = get_fs();
+ set_fs(get_ds());
+ written = file->f_op->write(inode,file,buf,count);
+ set_fs(old_fs);
+ return written;
+}
+
+/* Called whenever the timer says to check the free space. */
+void acct_timeout(unsigned long unused)
+{
+ acct_cancheck = 1;
+}
+
+/* Check the amount of free space and suspend/resume accordingly. */
+static void
+check_free_space(void)
+{
+ unsigned long old_fs;
+ struct statfs sbuf;
+
+ if (!acct_file || !acct_cancheck)
+ return;
+
+ if (!(acct_file->f_inode->i_sb->s_op->statfs))
+ return;
+
+ old_fs = get_fs();
+ set_fs(get_ds());
+ acct_file->f_inode->i_sb->s_op->statfs(
+ acct_file->f_inode->i_sb,
+ &sbuf, sizeof(struct statfs));
+ set_fs(old_fs);
+
+ if (acct_active) {
+ if (sbuf.f_bavail <= SUSPEND * sbuf.f_blocks / 100) {
+ acct_active = 0;
+ printk("acct.c: process accounting paused\n");
+ }
+ } else {
+ if (sbuf.f_bavail >= RESUME * sbuf.f_blocks / 100) {
+ acct_active = 1;
+ printk("acct.c: process accounting resumed\n");
+ }
+ }
+
+ del_timer(&acct_timer);
+ acct_cancheck = 0;
+ acct_timer.expires = jiffies + TIMEOUT;
+ add_timer(&acct_timer);
+}
+
+/*
+ * sys_acct() is the only system call needed to implement process
+ * accounting. It takes the name of the file where accounting records
+ * should be written. If the filename is NULL, accouting will be
+ * shutdown.
+ */
+
+asmlinkage int sys_acct(const char * filename)
+{
+ char *tmp;
+ int error;
+
+ if (!suser())
+ return -EPERM;
+
+ if (filename == ((const char *)0)){
+ if (!acct_file)
+ return 0;
+ del_timer(&acct_timer);
+ acct_cancheck = 0;
+ acct_active = 0;
+ k_close(acct_file);
+ acct_file = NULL;
+ printk("acct.c: process accounting stopped\n");
+ return 0;
+ }
+
+ if (acct_file)
+ return -EBUSY;
+
+ error = getname(filename,&tmp);
+ if (error < 0)
+ return error;
+
+ error = k_open(tmp,O_WRONLY | O_APPEND | O_NONBLOCK,0644,&acct_file);
+ putname(tmp);
+ if (error < 0)
+ return error;
+
+ acct_cancheck = 0;
+ acct_timer.expires = jiffies + TIMEOUT;
+ add_timer(&acct_timer);
+ acct_active = 1;
+ printk("acct.c: process accounting started\n");
+ return 0;
+}
+
+/*
+ * encode an unsigned long into a comp_t
+ *
+ * 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.
+ */
+
+#define MANTSIZE 13 /* 13 bit mantissa. */
+#define EXPSIZE 3 /* Base 8 (3 bit) exponent. */
+#define MAXFRACT ((1 << MANTSIZE) - 1) /* Maximum fractional value. */
+
+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);
+}
+
+/*
+ * write an accounting entry for an exiting process
+ *
+ * The acct_write_record() call is the workhorse of the process
+ * accounting system. The struct acct is built here and then written
+ * into the log file. This function should only be called from
+ * do_exit().
+ */
+
+#define KSTK_EIP(stack) (((unsigned long *)stack)[1019])
+#define KSTK_ESP(stack) (((unsigned long *)stack)[1022])
+
+void acct_write_record(long code)
+{
+ struct acct acct;
+ unsigned long vsize, eip, esp;
+ int error;
+
+ check_free_space();
+
+ /* Make sure we can even output a record. */
+ if (!acct_active)
+ return;
+
+ if (!acct_file) {
+ printk("acct.c: told to output a record with no open file\n");
+ return;
+ }
+
+ /* Build the accounting flags (ac_flag). */
+ acct.ac_flag = 0;
+ if (!(current->did_exec)) acct.ac_flag |= AFORK;
+ if (current->used_superuser_priv) acct.ac_flag |= ASU;
+ if (((unsigned long)code) & 0x80) acct.ac_flag |= ACORE;
+ if (((unsigned long)code) & 0xFF) acct.ac_flag |= AXSIG;
+
+ /* Build mostly everything else. */
+ acct.ac_stat = ((code >> 8) & 0xFF);
+ acct.ac_uid = current->uid;
+ acct.ac_gid = current->gid;
+ if (current->tty)
+ acct.ac_tty = current->tty->device;
+ else
+ acct.ac_tty = ((dev_t)0);
+ strncpy(acct.ac_comm,current->comm,sizeof(acct.ac_comm));
+ acct.ac_comm[sizeof(acct.ac_comm) - 1] = '\0';
+
+ /* Build the timing information. */
+ acct.ac_btime = CURRENT_TIME
+ - CT_TO_SECS(jiffies - current->start_time);
+ acct.ac_etime = encode_comp_t(jiffies - current->start_time);
+ acct.ac_utime = encode_comp_t(current->utime);
+ acct.ac_stime = encode_comp_t(current->stime);
+
+ /* Buld the I/O information. */
+ acct.ac_io = acct.ac_rw = encode_comp_t(current->io_chars);
+
+ /* Build the memory size information (from linux/fs/proc/array.c). */
+ vsize = current->kernel_stack_page;
+ if (vsize) {
+ eip = KSTK_EIP(vsize);
+ esp = KSTK_ESP(vsize);
+ vsize = current->mm->brk - current->mm->start_code
+ + PAGE_SIZE - 1;
+ if (esp)
+ vsize += TASK_SIZE - esp;
+ }
+ vsize = (vsize / PAGE_SIZE) + ((vsize % PAGE_SIZE) != 0);
+ acct.ac_mem = encode_comp_t(vsize);
+
+ /* Write the record to the accounting file. */
+ error = k_write(acct_file,(char *)&acct,sizeof(acct));
+ if (error < 0)
+ printk("acct.c: unable to write accounting record, reason = %d\n",-error);
+}
+
+#else
+
+/*
+ * Dummy system call when BSD process accounting is not configured
+ * into the kernel.
+ */
+
+asmlinkage int sys_acct(const char * filename)
+{
+ return -ENOSYS;
+}
+
+#endif

diff -u --new-file linux/kernel/Makefile.old linux/kernel/Makefile
--- linux/kernel/Makefile.old Wed Aug 16 07:57:02 1995
+++ linux/kernel/Makefile Mon Aug 21 23:20:20 1995
@@ -13,7 +13,7 @@
O_TARGET := kernel.o
O_OBJS = sched.o dma.o fork.o exec_domain.o panic.o printk.o sys.o \
module.o exit.o signal.o itimer.o info.o time.o softirq.o \
- resource.o
+ resource.o acct.o

SYMTAB_OBJS = ksyms.o
O_OBJS += $(SYMTAB_OBJS)

diff -u --new-file linux/kernel/fork.c.old linux/kernel/fork.c
--- linux/kernel/fork.c.old Tue Jul 18 10:15:38 1995
+++ linux/kernel/fork.c Wed Aug 23 17:51:00 1995
@@ -186,6 +186,8 @@
if (p->binfmt && p->binfmt->use_count)
(*p->binfmt->use_count)++;

+ p->io_chars = 0;
+ p->used_superuser_priv = 0;
p->did_exec = 0;
p->kernel_stack_page = new_stack;
*(unsigned long *) p->kernel_stack_page = STACK_MAGIC;

diff -u --new-file linux/include/linux/acct.h.old linux/include/linux/acct.h
--- linux/include/linux/acct.h.old Mon Aug 21 22:58:02 1995
+++ linux/include/linux/acct.h Fri Aug 25 22:40:22 1995
@@ -0,0 +1,64 @@
+/*
+ * BSD Process Accounting for Linux - Definitions
+ *
+ * Copyright (C) 1995, Thomas K. Dyas <tdyas@eden.rutgers.edu>
+ *
+ * This header file contains the definitions needed to implement
+ * BSD-style process accounting. The kernel accounting code and all
+ * user-level programs that try to do something useful with the
+ * process accounting log must include this file.
+ */
+
+#ifndef _LINUX_ACCT_H
+#define _LINUX_ACCT_H
+
+#include <linux/types.h>
+
+/*
+ * comp_t is a 16-bit "floating" point number with a 3-bit base 8
+ * exponent and a 13-bit fraction. See linux/kernel/acct.c for the
+ * specific encoding system used.
+ */
+
+typedef unsigned short comp_t;
+
+/*
+ * accounting file record
+ *
+ * This structure contains all of the information written out to the
+ * process accounting file whenever a process exits.
+ */
+
+struct acct
+{
+ char ac_flag; /* accounting flags */
+ char ac_stat; /* exit status */
+ uid_t ac_uid; /* real user ID */
+ gid_t ac_gid; /* real group ID */
+ dev_t ac_tty; /* control terminal */
+ time_t ac_btime; /* process creation time */
+ comp_t ac_utime; /* user time */
+ comp_t ac_stime; /* system time */
+ comp_t ac_etime; /* elapsed time */
+ comp_t ac_mem; /* average memory usage */
+ comp_t ac_io; /* chars transferred */
+ comp_t ac_rw; /* blocks read or written */
+ char ac_comm[16]; /* command name */
+};
+
+/*
+ * accounting flags
+ */
+ /* bit set when the process ... */
+#define AFORK 0x01 /* ... executed fork, but did not exec */
+#define ASU 0x02 /* ... used super-user privileges */
+#define ACOMPAT 0x04 /* ... used compatibility mode ??? */
+#define ACORE 0x08 /* ... dumped core */
+#define AXSIG 0x10 /* ... was killed by a signal */
+
+#ifdef __KERNEL__
+extern volatile int acct_active;
+extern void acct_write_record(long code);
+#endif
+
+#endif

diff -u --new-file linux/include/linux/sched.h.old linux/include/linux/sched.h
--- linux/include/linux/sched.h.old Tue Aug 22 19:41:40 1995
+++ linux/include/linux/sched.h Wed Aug 23 19:42:38 1995
@@ -196,6 +196,9 @@
struct files_struct files[1];
/* memory management info */
struct mm_struct mm[1];
+/* accounting information */
+ unsigned long io_chars;
+ int used_superuser_priv:1;
};

/*
@@ -254,7 +257,8 @@
/* tss */ INIT_TSS, \
/* fs */ { INIT_FS }, \
/* files */ { INIT_FILES }, \
-/* mm */ { INIT_MM } \
+/* mm */ { INIT_MM }, \
+/* acct */ 0, 0 \
}

#ifdef __KERNEL__
@@ -425,6 +429,32 @@

#define for_each_task(p) \
for (p = &init_task ; (p = p->next_task) != &init_task ; )
+
+
+/*
+ * These are defined as inline functions, so that we can can set a
+ * flag if it returns true (to do BSD-style accounting where the
+ * process is flagged if it uses root privs). The implication of this
+ * is that you should do normal permissions checks first, and check
+ * suser() last.
+ *
+ * "suser()" checks against the effective user id, while "fsuser()"
+ * is used for file permission checking and checks against the fsuid..
+ */
+
+extern inline int suser(void)
+{
+ if (current->euid == 0)
+ current->used_superuser_priv = 1;
+ return(current->euid == 0);
+}
+
+extern inline int fsuser(void)
+{
+ if (current->fsuid == 0)
+ current->used_superuser_priv = 1;
+ return(current->fsuid == 0);
+}

#endif /* __KERNEL__ */

diff -u --new-file linux/include/linux/kernel.h.old linux/include/linux/kernel.h
--- linux/include/linux/kernel.h.old Tue Aug 22 20:21:52 1995
+++ linux/include/linux/kernel.h Wed Aug 23 00:58:18 1995
@@ -54,19 +54,6 @@
asmlinkage int printk(const char * fmt, ...)
__attribute__ ((format (printf, 1, 2)));

-/*
- * This is defined as a macro, but at some point this might become a
- * real subroutine that sets a flag if it returns true (to do
- * BSD-style accounting where the process is flagged if it uses root
- * privs). The implication of this is that you should do normal
- * permissions checks first, and check suser() last.
- *
- * "suser()" checks against the effective user id, while "fsuser()"
- * is used for file permission checking and checks against the fsuid..
- */
-#define suser() (current->euid == 0)
-#define fsuser() (current->fsuid == 0)
-
#endif /* __KERNEL__ */

#define SI_LOAD_SHIFT 16

diff -u --new-file linux/arch/i386/config.in.old linux/arch/i386/config.in
--- linux/arch/i386/config.in.old Sun Aug 13 12:39:38 1995
+++ linux/arch/i386/config.in Wed Aug 23 18:05:02 1995
@@ -29,6 +29,7 @@
bool ' PCI bridge optimisation (experimental)' CONFIG_PCI_OPTIMIZE y
fi
bool 'System V IPC' CONFIG_SYSVIPC y
+bool '*ALPHA* BSD process accounting' CONFIG_BSD_PROCESS_ACCT n
bool 'Kernel support for ELF binaries' CONFIG_BINFMT_ELF y
if [ "$CONFIG_BINFMT_ELF" = "y" ]; then
bool 'Compile kernel as ELF - if your GCC is ELF-GCC' CONFIG_KERNEL_ELF n

****
**** accton.c
****

#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
if (argc < 2)
{
if (acct((const char *)0) < 0)
perror("acct");
} else {
if (acct(argv[1]) < 0)
perror("acct");
}
return 0;
}