|
|
Jump to this file's LXR Page |
|
|
File: [CENS] / misc / emlog / emlog.c
(download)
/
(as text)
Revision: 1.6, Mon Aug 13 20:12:48 2001 UTC (8 years, 3 months ago) by jelson Branch: MAIN Changes since 1.5: +29 -27 lines Took out the call to schedule() that we were doing after a write to the log, since it didn't seem to have any effect on the reliability of streaming data but still ends up costing the calling process some time. Banner including version number now emitted to kernel log when module is inserted. Added version number constant to header file. General cleanup: more consistent use of macros, etc. Now compiles cleanly with -Wall (why didn't I do this before?) |
/*
* EMLOG: the EMbedded-device LOGger
*
* Jeremy Elson <jelson@circlemud.org>
* USC/Information Sciences Institute
*
* This code is released under the GPL
*
* This is emlog version 0.40, released 13 August 2001
* For more information see http://www.circlemud.org/~jelson/software/emlog
*
* $Id: emlog.c,v 1.6 2001/08/13 21:12:48 jelson Exp $
*/
#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif
#include <linux/config.h>
#include <linux/stddef.h>
#include <linux/tqueue.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#ifdef MODULE
#include <linux/module.h>
#endif
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/malloc.h>
#include <linux/vmalloc.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include "emlog.h"
MODULE_PARM(emlog_debug, "i");
struct emlog_info *emlog_info_list = NULL;
static int emlog_debug;
#define MIN(x, y) ((x) < (y) ? (x) : y)
/* find the emlog-info structure associated with an inode. returns a
* pointer to the structure if found, NULL if not found */
static struct emlog_info *get_einfo(struct inode *inode)
{
struct emlog_info *einfo;
if (inode == NULL)
return NULL;
for (einfo = emlog_info_list; einfo != NULL; einfo = einfo->next)
if (einfo->i_ino == inode->i_ino)
return einfo;
return NULL;
}
/* create a new emlog buffer and its associated info structure.
* returns an errno on failure, or 0 on success. on success, the
* pointer to the new struct is passed back using peinfo */
static int create_einfo(struct inode *inode, int minor,
struct emlog_info **peinfo)
{
struct emlog_info *einfo;
/* make sure the memory requirement is legal */
if (minor < 1 || minor > EMLOG_MAX_SIZE)
return -EINVAL;
/* allocate space for our metadata and initialize it */
if ((einfo = kmalloc(sizeof(struct emlog_info), GFP_KERNEL)) == NULL)
goto struct_malloc_failed;
memset(einfo, 0, sizeof(struct emlog_info));
einfo->i_ino = inode->i_ino;
#if defined(DECLARE_WAIT_QUEUE_HEAD)
init_waitqueue_head(EMLOG_READQ(einfo));
#else
init_waitqueue(EMLOG_READQ(einfo));
#endif
/* figure out how much of a buffer this should be and allocate the buffer */
einfo->size = 1024 * minor;
if ((einfo->data = (char *) vmalloc(sizeof(char) * einfo->size)) == NULL)
goto data_malloc_failed;
/* add it to our linked list */
einfo->next = emlog_info_list;
emlog_info_list = einfo;
if (emlog_debug)
printk("allocating resources associated with inode %ld\n", einfo->i_ino);
/* pass the struct back */
*peinfo = einfo;
return 0;
#if 0
other_failure: /* if we check for other errors later, jump here */
#endif
vfree(einfo->data);
data_malloc_failed:
kfree(einfo);
struct_malloc_failed:
return -ENOMEM;
}
/* this frees all data associated with an emlog_info buffer, including
* the struct that you pass to the function. don't dereference this
* structure after calling free_einfo! */
void free_einfo(struct emlog_info *einfo)
{
struct emlog_info **ptr;
if (einfo == NULL) {
printk("null passed to free_einfo... which is bad\n");
return;
}
if (emlog_debug)
printk("freeing resources associated with inode %ld\n", einfo->i_ino);
vfree(einfo->data);
/* now delete the 'einfo' structure from the linked list. 'ptr' is
* the pointer that needs to be changed... which is either the list
* head or one of the 'next' pointers on the list. */
ptr = &emlog_info_list;
while (*ptr != einfo) {
if (!*ptr) {
printk("corrupt einfo list!\n");
break;
} else
ptr = &((**ptr).next);
}
*ptr = einfo->next;
}
/************************ File Interface Functions ************************/
static int emlog_open(struct inode *inode, struct file *file)
{
int minor = MINOR(inode->i_rdev);
struct emlog_info *einfo = NULL;
int retval;
if ((einfo = get_einfo(inode)) == NULL) {
/* never heard of this inode before... create a new record */
if ((retval = create_einfo(inode, minor, &einfo)) < 0)
return retval;
}
if (einfo == NULL) {
printk("BUG IN EMLOG!\n");
return -EIO;
}
einfo->refcount++;
MOD_INC_USE_COUNT;
return 0;
}
/* this is called when a file is closed */
static int emlog_release(struct inode *inode, struct file *file)
{
struct emlog_info *einfo;
int retval = 0;
/* get the buffer info */
if ((einfo = get_einfo(inode)) == NULL) {
printk("emlog: releasing unknown file! zoinks!\n");
return -EINVAL;
goto out;
}
/* decrement the reference count. if no one has this file open and
* it's not holding any data, delete the record. */
einfo->refcount--;
if (einfo->refcount == 0 && EMLOG_QLEN(einfo) == 0)
free_einfo(einfo);
out:
MOD_DEC_USE_COUNT;
return retval;
}
/* read_from_emlog reads bytes out of a circular buffer with
* wraparound. returns caddr_t, pointer to data read, which the
* caller must free. length is (a pointer to) the number of bytes to
* be read, which will be set by this function to be the number of
* bytes actually returned */
caddr_t read_from_emlog(struct emlog_info *einfo, int *length, loff_t *offset)
{
caddr_t retval;
int bytes_copied = 0, n, start_point, remaining;
/* is the user trying to read data that has already scrolled off? */
if (*offset < einfo->offset)
*offset = einfo->offset;
/* is the user trying to read past EOF? */
if (*offset >= EMLOG_FIRST_EMPTY_BYTE(einfo))
return NULL;
/* find the smaller of the total bytes we have available and what
* the user is asking for */
*length = MIN(*length, EMLOG_FIRST_EMPTY_BYTE(einfo) - *offset);
remaining = *length;
/* figure out where to start based on user's offset */
start_point = einfo->read_point + (*offset - einfo->offset);
start_point = start_point % einfo->size;
/* allocate memory to return */
if ((retval = kmalloc(sizeof(char) * remaining, GFP_KERNEL)) == NULL)
return NULL;
/* copy the (possibly noncontiguous) data to our buffer */
while (remaining) {
n = MIN(remaining, einfo->size - start_point);
memcpy(retval + bytes_copied, einfo->data + start_point, n);
bytes_copied += n;
remaining -= n;
start_point = (start_point + n) % einfo->size;
}
/* advance user's file pointer */
*offset += *length;
return retval;
}
static ssize_t emlog_read(struct file *file,
char *buffer, /* The buffer to fill with data */
size_t length, /* The length of the buffer */
loff_t *offset) /* Our offset in the file */
{
int retval;
caddr_t data_to_return;
struct emlog_info *einfo;
/* get the metadata about this emlog */
if ((einfo = get_einfo(file->f_dentry->d_inode)) == NULL) {
printk("emlog_read: record not found\n");
return -EIO;
}
/* wait until there's data available (unless we do nonblocking reads) */
while (*offset >= EMLOG_FIRST_EMPTY_BYTE(einfo)) {
if (file->f_flags & O_NONBLOCK)
return -EAGAIN;
interruptible_sleep_on(EMLOG_READQ(einfo));
/* see if a signal woke us up */
if (signal_pending(current))
return -ERESTARTSYS;
}
if ((data_to_return = read_from_emlog(einfo, &length, offset)) == NULL)
return 0;
if (copy_to_user(buffer, data_to_return, length) > 0)
retval = -EFAULT;
else
retval = length;
kfree(data_to_return);
return retval;
}
/* write_to_emlog writes to a circular buffer with wraparound. in the
* case of an overflow, it overwrites the oldest unread data. */
void write_to_emlog(struct emlog_info *einfo, caddr_t buf, int length)
{
int bytes_copied = 0;
int overflow = 0;
int n;
if (length + EMLOG_QLEN(einfo) >= (einfo->size-1)) {
overflow = 1;
/* in case of overflow, figure out where the new buffer will
* begin. we start by figuring out where the current buffer ENDS:
* einfo->offset + EMLOG_QLEN. we then advance the end-offset
* by the length of the current write, and work backwards to
* figure out what the oldest unoverwritten data will be (i.e.,
* size of the buffer). was that all quite clear? :-) */
einfo->offset = einfo->offset + EMLOG_QLEN(einfo) + length
- einfo->size + 1;
}
while (length) {
/* how many contiguous bytes are available from the write point to
* the end of the circular buffer? */
n = MIN(length, einfo->size - einfo->write_point);
memcpy(einfo->data + einfo->write_point, buf + bytes_copied, n);
bytes_copied += n;
length -= n;
einfo->write_point = (einfo->write_point + n) % einfo->size;
}
/* if there is an overflow, reset the read point to read whatever is
* the oldest data that we have, that has not yet been
* overwritten. */
if (overflow)
einfo->read_point = (einfo->write_point + 1) % einfo->size;
}
static ssize_t emlog_write(struct file *file,
const char *buffer,
size_t length,
loff_t *offset)
{
caddr_t message = NULL;
int n;
struct emlog_info *einfo;
/* get the metadata about this emlog */
if ((einfo = get_einfo(file->f_dentry->d_inode)) == NULL)
return -EIO;
/* if the message is longer than the buffer, just take the beginning
* of it, in hopes that the reader (if any) will have time to read
* before we wrap around and obliterate it */
n = MIN(length, einfo->size - 1);
/* make sure we have the memory for it */
if ((message = kmalloc(n, GFP_KERNEL)) == NULL)
return -ENOMEM;
/* copy into our temp buffer */
if (copy_from_user(message, buffer, n) > 0) {
kfree(message);
return -EFAULT;
}
/* now copy it into the circular buffer and free our temp copy */
write_to_emlog(einfo, message, n);
kfree(message);
/* wake up any readers that might be waiting for the data. we call
* schedule in the vague hope that a reader will run before the
* writer's next write, to avoid losing data. */
wake_up_interruptible(EMLOG_READQ(einfo));
return n;
}
static unsigned int emlog_poll(struct file *file, poll_table *wait)
{
struct emlog_info *einfo;
/* get the metadata about this emlog */
if ((einfo = get_einfo(file->f_dentry->d_inode)) == NULL)
return -EIO;
poll_wait(file, EMLOG_READQ(einfo), wait);
if (file->f_pos < EMLOG_FIRST_EMPTY_BYTE(einfo))
return POLLIN | POLLRDNORM;
else
return 0;
}
static struct file_operations emlog_fops = {
read : emlog_read,
write : emlog_write,
open : emlog_open,
release: emlog_release,
poll : emlog_poll,
};
int init_module(void)
{
if (register_chrdev(EMLOG_MAJOR_NUMBER, "emlog", &emlog_fops) < 0) {
printk("emlog: unable to register character device %d\n",
EMLOG_MAJOR_NUMBER);
return -EIO;
} else {
printk("emlog: version %s running, using major number %d\n", EMLOG_VERSION,
EMLOG_MAJOR_NUMBER);
}
return 0;
}
void cleanup_module(void)
{
unregister_chrdev(EMLOG_MAJOR_NUMBER, "emlog");
/* clean up any still-allocated memory */
while (emlog_info_list != NULL)
free_einfo(emlog_info_list);
printk("emlog: unloading\n");
}
| CENS CVS Mailing List |
Powered by ViewCVS 0.9.2 |