|
|
Jump to this file's LXR Page |
|
|
File: [CENS] / misc / emlog / emlog.c
(download)
/
(as text)
Revision: 1.5, Mon Aug 13 07:10:18 2001 UTC (8 years, 3 months ago) by jelson Branch: MAIN Changes since 1.4: +82 -55 lines First pass at an emlog that knows how to keep reader-specific offsets |
/*
* EMLOG: the EMbedded-device LOGger
*
* Jeremy Elson
* USC/ISI
*
* $Id: emlog.c,v 1.5 2001/08/13 08:10:18 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(&einfo->read_q);
#else
init_waitqueue(&einfo->read_q);
#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 %d\n", einfo->i_ino);
/* pass the struct back */
*peinfo = einfo;
return 0;
other_failure: /* if we check for other errors later, jump here */
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 %d\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;
MOD_DEC_USE_COUNT;
/* get the buffer info */
if ((einfo = get_einfo(inode)) == NULL) {
printk("emlog: releasing unknown file! zoinks!\n");
return -EINVAL;
}
/* 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_EMPTY(einfo))
free_einfo(einfo);
return 0;
}
/* 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 >= einfo->offset + EMLOG_QLEN(einfo))
return NULL;
/* find the smaller of the total bytes we have available and what
* the user is asking for */
*length = MIN(*length, EMLOG_QLEN(einfo) - (*offset - 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;
printk("got a read at offset %d\n", *offset);
/* 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;
}
#if 0
/* wait until there's data available (unless we do nonblocking reads) */
while (EMLOG_EMPTY(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;
}
#endif
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)
{
caddr_t retval;
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);
/* update the size in the inode, so tail -f works */
file->f_dentry->d_inode->i_size = EMLOG_QLEN(einfo) + einfo->offset - 1;
printk("size now %d\n", file->f_dentry->d_inode->i_size );
/* 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));
schedule();
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 (!EMLOG_EMPTY(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;
}
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);
}
| CENS CVS Mailing List |
Powered by ViewCVS 0.9.2 |