isdn4k-utils/isdnlog/isdnlog/ilp/modilp.c

487 lines
13 KiB
C

/* modilp.c - isdnlog procfs entry /proc/isdnlog
*
* Copyright 2000 by Leopold Toetsch <lt@toetsch.at>
*
* 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, 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.
*
* Changes:
*
* 0.10 15.12.2000 lt Initial Version
* 0.11 21.12.2000 lt calculate duration
* 0.12 05.04.2002 lt adapted for 2.4.x
* 0.13 05.04.2002 lt 2. try, thanks Achim Steinmetz
* 0.14 08.04.2002 lt fixed module unload
* 0.15 11.04.2002 lt make it pager compatible for 2.4.x
*/
/* based on code found in lkmpg/node17.html, which is: */
/* Copyright (C) 1998-1999 by Ori Pomerantz */
/* The necessary header files */
/* Standard in kernel modules */
#include <linux/module.h> /* Specifically, a module */
#include <linux/kernel.h> /* We're doing kernel work */
/* Necessary because we use proc fs */
#include <linux/proc_fs.h>
#include <linux/time.h> /* get time */
#define MODULE_VERSION "0.15"
#define MODULE_NAME "modilp"
/* In 2.2.3 /usr/include/linux/version.h includes a
* macro for this, but 2.0.35 doesn't - so I add it
* here if necessary. */
#ifndef KERNEL_VERSION
#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
#include <linux/init.h> /* __init macros */
#define get_fast_time do_gettimeofday
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,1,4)
#include <asm/uaccess.h> /* for copy_user */
#else
static inline unsigned long
copy_from_user (void *to, const void *from, unsigned long n)
{
int i;
if ((i = verify_area (VERIFY_READ, from, n)) != 0)
return i;
memcpy_fromfs (to, from, n);
return 0;
}
static inline unsigned long
copy_to_user (void *to, const void *from, unsigned long n)
{
int i;
if ((i = verify_area (VERIFY_WRITE, to, n)) != 0)
return i;
memcpy_tofs (to, from, n);
return 0;
}
#define get_fast_time do_gettimeofday
#endif
/* Here we keep the last message received */
#define MESSAGE_LENGTH 80
#define N_CHANS 2
static struct mes_t {
char text[MESSAGE_LENGTH];
time_t start;
} message[N_CHANS];
/* string in Connect-messages */
#define Connect "CON"
/* store duration d as text at p */
static void calc_diff(ulong d, char *p) {
int h,m;
char s[10];
h = d / 3600;
d %= 3600;
m = d / 60;
d %= 60;
if (h > 99) /* forgotten connection ? */
h = 99;
sprintf(s, "%02d:%02d:%02d", h, m, (int)d);
memcpy(p, s, 8);
}
/* The module's file functions ********************** */
/* Since we use the file operations struct, we can't
* use the special proc output provisions - we have to
* use a standard read function, which is this function */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
static int proc_calc_metrics(char *page, char **start, off_t off,
int count, int *eof, int len)
{
if (len <= off+count) *eof = 1;
*start = page + off;
len -= off;
if (len>count) len = count;
if (len<0) len = 0;
return len;
}
/* new proc-fs interface */
static int
read_func(
char *page, /* data in user_space */
char **start, /* where to write, ignored */
off_t off, /* offset ignored */
int count, /* maximum */
int *eof, /* eof marker */
void *data) /* N/U */
{
int i;
int len;
struct timeval tv;
get_fast_time(&tv);
for (i=0 ; i<N_CHANS; i++)
if (message[i].text &&
strlen(message[i].text) >= 70 &&
strstr(message[i].text, Connect))
calc_diff(tv.tv_sec-message[i].start, message[i].text+62);
len = sprintf (page,
/*2345678901234567890123456789012345678901234567890123456789012345678901234567890 */
/* 1 2 3 4 5 6 7 */
"Ch State Msn - Number Alias Duration Cost\n%s%s",
message[0].text, message[1].text);
/* BUG_ON(len > count); ? */
#if 1
return proc_calc_metrics(page, start, off, count, eof, len);
#else
*eof = 1;
return len;
#endif
}
#else
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t
module_output (
struct file *file, /* The file read */
char *buf, /* The buffer to put data to (in the
* user segment) */
size_t len, /* The length of the buffer */
loff_t * offset) /* Offset in the file - ignore */
#else
static int
module_output (
struct inode *inode, /* The inode read */
struct file *file, /* The file read */
char *buf, /* The buffer to put data to (in the
* user segment) */
int len) /* The length of the buffer */
#endif
{
static int finished = 0;
int i;
char output[MESSAGE_LENGTH * (N_CHANS + 1)];
char *p;
struct timeval tv;
/* We return 0 to indicate end of file, that we have
* no more information. Otherwise, processes will
* continue to read from us in an endless loop. */
if (finished)
{
finished = 0;
return 0;
}
get_fast_time(&tv);
for (i=0 ; i<N_CHANS; i++)
if (message[i].text &&
strlen(message[i].text) >= 70 &&
strstr(message[i].text, Connect))
calc_diff(tv.tv_sec-message[i].start, message[i].text+62);
sprintf (output,
/*2345678901234567890123456789012345678901234567890123456789012345678901234567890 */
/* 1 2 3 4 5 6 7 */
"Ch State Msn - Number Alias Duration Cost\n%s%s",
message[0].text, message[1].text);
for (p = output, i = 1; *p && i < len; p++, i++)
;
len = i;
copy_to_user (buf, output, len);
/* Notice, we assume here that the size of the message
* is below len, or it will be received cut. In a real
* life situation, if the size of the message is less
* than len then we'd return len and on the second call
* start filling the buffer with the len+1'th byte of
* the message. */
finished = 1;
return len; /* Return the number of bytes "read" */
}
#endif
/* This function receives input from the user when the
* user writes to the /proc file. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
static ssize_t
module_input (
struct file *file, /* The file itself */
const char *buf, /* The buffer with input */
size_t length, /* The buffer's length */
loff_t * offset) /* offset to file - ignore */
#else
static int
module_input (
struct inode *inode, /* The file's inode */
struct file *file, /* The file itself */
const char *buf, /* The buffer with the input */
int length) /* The buffer's length */
#endif
{
int n;
char c;
struct timeval tv;
/* get prefix "1" or "2" */
copy_from_user (&c, buf, 1);
length--;
if (c == '1' || c == '2')
{
n = c - '1';
/* Put the input into Message[n], where module_output
* will later be able to use it */
if (length > MESSAGE_LENGTH - 1)
length = MESSAGE_LENGTH - 1;
copy_from_user (message[n].text, buf+1, length);
message[n].text[length] = '\0';
/* remember connect time */
if (strstr(message[n].text, Connect)) {
get_fast_time(&tv);
message[n].start = tv.tv_sec;
}
return length + 1;
}
return 0;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
static int
write_func(
struct file *file, /* ignored */
const char *buffer, /* data */
unsigned long count, /* len */
void *data) /* ignored */
{
return module_input(file, buffer, count, 0);
}
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,0)
static struct proc_dir_entry *modilp_file;
static int __init init_modilp(void)
{
modilp_file = create_proc_entry("isdnlog", 0644, NULL);
if (modilp_file == NULL)
return -ENOMEM;
modilp_file->read_proc = read_func;
modilp_file->write_proc = write_func;
modilp_file->owner = THIS_MODULE;
return 0;
}
static void __exit exit_modilp(void)
{
remove_proc_entry("isdnlog", NULL);
}
module_init(init_modilp);
module_exit(exit_modilp);
MODULE_AUTHOR("Leopold Totsch");
MODULE_DESCRIPTION("line status for isdnlog");
MODULE_LICENSE("GPL");
EXPORT_NO_SYMBOLS;
#else
/* This function decides whether to allow an operation
* (return zero) or not allow it (return a non-zero
* which indicates why it is not allowed).
*
* The operation can be one of the following values:
* 0 - Execute (run the "file" - meaningless in our case)
* 2 - Write (input to the kernel module)
* 4 - Read (output from the kernel module)
*
* This is the real function that checks file
* permissions. The permissions returned by ls -l are
* for referece only, and can be overridden here.
*/
static int
module_permission (struct inode *inode, int op)
{
/* We allow everybody to read from our module, but
* only root (uid 0) may write to it */
if (op == 4 || (op == 2 && current->euid == 0))
return 0;
/* If it's anything else, access is denied */
return -EACCES;
}
/* The file is opened - we don't really care about
* that, but it does mean we need to increment the
* module's reference count. */
int
module_open (struct inode *inode, struct file *file)
{
MOD_INC_USE_COUNT;
return 0;
}
/* The file is closed - again, interesting only because
* of the reference count. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
int
module_close (struct inode *inode, struct file *file)
#else
void
module_close (struct inode *inode, struct file *file)
#endif
{
MOD_DEC_USE_COUNT;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
return 0; /* success */
#endif
}
/* Structures to register as the /proc file, with
* pointers to all the relevant functions. ********** */
/* File operations for our proc file. This is where we
* place pointers to all the functions called when
* somebody tries to do something to our file. NULL
* means we don't want to deal with something. */
static struct file_operations File_Ops_4_Our_Proc_File =
{
NULL, /* lseek */
module_output, /* "read" from the file */
module_input, /* "write" to the file */
NULL, /* readdir */
NULL, /* select */
NULL, /* ioctl */
NULL, /* mmap */
module_open, /* Somebody opened the file */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
NULL, /* flush, added here in version 2.2 */
#endif
module_close, /* Somebody closed the file */
/* etc. etc. etc. (they are all given in
* /usr/include/linux/fs.h). Since we don't put
* anything here, the system will keep the default
* data, which in Unix is zeros (NULLs when taken as
* pointers). */
};
/* Inode operations for our proc file. We need it so
* we'll have some place to specify the file operations
* structure we want to use, and the function we use for
* permissions. It's also possible to specify functions
* to be called for anything else which could be done to
* an inode (although we don't bother, we just put
* NULL). */
static struct inode_operations Inode_Ops_4_Our_Proc_File =
{
&File_Ops_4_Our_Proc_File,
NULL, /* create */
NULL, /* lookup */
NULL, /* link */
NULL, /* unlink */
NULL, /* symlink */
NULL, /* mkdir */
NULL, /* rmdir */
NULL, /* mknod */
NULL, /* rename */
NULL, /* readlink */
NULL, /* follow_link */
NULL, /* readpage */
NULL, /* writepage */
NULL, /* bmap */
NULL, /* truncate */
module_permission /* check for permissions */
};
/* Directory entry */
static struct proc_dir_entry Our_Proc_File =
{
0, /* Inode number - ignore, it will be filled by
* proc_register[_dynamic] */
7, /* Length of the file name */
"isdnlog", /* The file name */
S_IFREG | S_IRUGO | S_IWUSR,
/* File mode - this is a regular file which
* can be read by its owner, its group, and everybody
* else. Also, its owner can write to it.
*
* Actually, this field is just for reference, it's
* module_permission that does the actual check. It
* could use this field, but in our implementation it
* doesn't, for simplicity. */
1, /* Number of links (directories where the
* file is referenced) */
0, 0, /* The uid and gid for the file -
* we give it to root */
80, /* The size of the file reported by ls. */
&Inode_Ops_4_Our_Proc_File,
/* A pointer to the inode structure for
* the file, if we need it. In our case we
* do, because we need a write function. */
NULL
/* The read function for the file. Irrelevant,
* because we put it in the inode structure above */
};
/* Module initialization and cleanup ******************* */
/* Initialize the module - register the proc file */
int
init_module ()
{
/* Success if proc_register[_dynamic] is a success,
* failure otherwise */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
/* In version 2.2, proc_register assign a dynamic
* inode number automatically if it is zero in the
* structure , so there's no more need for
* proc_register_dynamic
*/
return proc_register (&proc_root, &Our_Proc_File);
#else
return proc_register_dynamic (&proc_root, &Our_Proc_File);
#endif
}
/* Cleanup - unregister our file from /proc */
void
cleanup_module ()
{
proc_unregister (&proc_root, Our_Proc_File.low_ino);
}
#endif