/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2004 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.0 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_0.txt. |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Ilia Alshanetsky <ilia@php.net> |
+----------------------------------------------------------------------+
*/
/* $Id: statgrab.c,v 1.8 2006/11/07 22:14:38 iliaa Exp $ */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <statgrab.h>
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "ext/standard/php_string.h" /* needed for php_explode */
#include "php_statgrab.h"
#define PHP_STATGRAB_VERSION "0.5"
#define PHP_SG_ADD_FVAL(rtz, key, val) add_assoc_double_ex(rtz, key, sizeof(key), val);
#define PHP_SG_ADD_LVAL(rtz, key, val) add_assoc_long_ex(rtz, key, sizeof(key), val);
#define PHP_SG_ADD_LLVAL(rtz, key, val) { \
char tmp[256]; \
int tmp_len = snprintf((char *)&tmp, sizeof(tmp) - 1, "%lld", val); \
add_assoc_stringl_ex(rtz, key, sizeof(key), tmp, tmp_len, 1); \
}
#define PHP_SG_ERROR \
{ \
sg_error sg_errno = sg_get_error(); \
php_error_docref(NULL TSRMLS_CC, E_WARNING, "%d: %s", sg_errno, (char*)sg_str_error(sg_errno)); \
RETURN_FALSE; \
}
/* Process sorting methods */
typedef enum {
SG_PS_SORT_NAME,
SG_PS_SORT_PID,
SG_PS_SORT_UID,
SG_PS_SORT_GID,
SG_PS_SORT_SIZE,
SG_PS_SORT_RES,
SG_PS_SORT_CPU,
SG_PS_SORT_TIME
} php_sg_ps_sort;
#define REGISTER_SG_CONSTANT(__c) REGISTER_LONG_CONSTANT(#__c, __c, CONST_CS | CONST_PERSISTENT)
/* True global resources - no need for thread safety here
static int le_statgrab;
*/
/* {{{ statgrab_functions[]
*
* Every user visible function must have an entry in statgrab_functions[].
*/
function_entry statgrab_functions[] = {
PHP_FE(sg_cpu_percent_usage, NULL)
PHP_FE(sg_cpu_totals, NULL)
PHP_FE(sg_cpu_diff, NULL)
PHP_FE(sg_diskio_stats, NULL)
PHP_FE(sg_diskio_stats_diff, NULL)
PHP_FE(sg_fs_stats, NULL)
PHP_FE(sg_general_stats, NULL)
PHP_FE(sg_load_stats, NULL)
PHP_FE(sg_memory_stats, NULL)
PHP_FE(sg_swap_stats, NULL)
PHP_FE(sg_network_stats, NULL)
PHP_FE(sg_network_stats_diff, NULL)
PHP_FE(sg_page_stats, NULL)
PHP_FE(sg_page_stats_diff, NULL)
PHP_FE(sg_process_count, NULL)
PHP_FE(sg_process_stats, NULL)
PHP_FE(sg_user_stats, NULL)
PHP_FE(sg_network_iface_stats, NULL)
{NULL, NULL, NULL} /* Must be the last line in statgrab_functions[] */
};
/* }}} */
/* {{{ statgrab_module_entry
*/
zend_module_entry statgrab_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
"statgrab",
statgrab_functions,
PHP_MINIT(statgrab),
NULL,
NULL,
NULL,
PHP_MINFO(statgrab),
#if ZEND_MODULE_API_NO >= 20010901
PHP_STATGRAB_VERSION, /* Replace with version number for your extension */
#endif
STANDARD_MODULE_PROPERTIES
};
/* }}} */
#ifdef COMPILE_DL_STATGRAB
ZEND_GET_MODULE(statgrab)
#endif
/* {{{ PHP_MINIT_FUNCTION
*/
PHP_MINIT_FUNCTION(statgrab)
{
/* Initialise statgrab */
sg_init();
/* Drop setuid/setgid privileges. */
if (sg_drop_privileges() != 0) {
zend_error(E_ERROR, "libstatgrab: Failed to drop privileges");
}
REGISTER_LONG_CONSTANT("SG_FULL_DUPLEX", SG_IFACE_DUPLEX_FULL, CONST_CS|CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SG_HALF_DUPLEX", SG_IFACE_DUPLEX_HALF, CONST_CS|CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("SG_UNKNOWN_DUPLEX", SG_IFACE_DUPLEX_UNKNOWN, CONST_CS|CONST_PERSISTENT);
/* process sorting order */
REGISTER_SG_CONSTANT(SG_PS_SORT_NAME);
REGISTER_SG_CONSTANT(SG_PS_SORT_PID);
REGISTER_SG_CONSTANT(SG_PS_SORT_UID);
REGISTER_SG_CONSTANT(SG_PS_SORT_GID);
REGISTER_SG_CONSTANT(SG_PS_SORT_SIZE);
REGISTER_SG_CONSTANT(SG_PS_SORT_RES);
REGISTER_SG_CONSTANT(SG_PS_SORT_CPU);
REGISTER_SG_CONSTANT(SG_PS_SORT_TIME);
/* process states */
REGISTER_SG_CONSTANT(SG_PROCESS_STATE_RUNNING);
REGISTER_SG_CONSTANT(SG_PROCESS_STATE_SLEEPING);
REGISTER_SG_CONSTANT(SG_PROCESS_STATE_STOPPED);
REGISTER_SG_CONSTANT(SG_PROCESS_STATE_ZOMBIE);
REGISTER_SG_CONSTANT(SG_PROCESS_STATE_UNKNOWN);
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(statgrab)
{
php_info_print_table_start();
php_info_print_table_header(2, "statgrab support", "enabled");
php_info_print_table_header(2, "Extension version", PHP_STATGRAB_VERSION " - ($Id: statgrab.c,v 1.8 2006/11/07 22:14:38 iliaa Exp $)");
php_info_print_table_end();
}
/* }}} */
/* {{{ proto array sg_cpu_percent_usage()
Return an array indicating the cpu utilization */
PHP_FUNCTION(sg_cpu_percent_usage)
{
sg_cpu_percents *cpu;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
cpu = sg_get_cpu_percents();
array_init(return_value);
PHP_SG_ADD_FVAL(return_value, "user", cpu->user)
PHP_SG_ADD_FVAL(return_value, "kernel", cpu->kernel)
PHP_SG_ADD_FVAL(return_value, "idle", cpu->idle)
PHP_SG_ADD_FVAL(return_value, "iowait", cpu->iowait)
PHP_SG_ADD_FVAL(return_value, "swap", cpu->swap)
PHP_SG_ADD_FVAL(return_value, "nice", cpu->nice)
PHP_SG_ADD_LVAL(return_value, "previous_run", cpu->time_taken)
}
/* }}} */
static void _php_cpu_stats(INTERNAL_FUNCTION_PARAMETERS, int mode)
{
sg_cpu_stats *cpu;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
if (mode) {
cpu = sg_get_cpu_stats();
} else {
cpu = sg_get_cpu_stats_diff();
}
if (cpu == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
PHP_SG_ADD_LLVAL(return_value, "user", cpu->user)
PHP_SG_ADD_LLVAL(return_value, "kernel", cpu->kernel)
PHP_SG_ADD_LLVAL(return_value, "idle", cpu->idle)
PHP_SG_ADD_LLVAL(return_value, "iowait", cpu->iowait)
PHP_SG_ADD_LLVAL(return_value, "swap", cpu->swap)
PHP_SG_ADD_LLVAL(return_value, "nice", cpu->nice)
PHP_SG_ADD_LLVAL(return_value, "total", cpu->total)
PHP_SG_ADD_LVAL(return_value, "previous_run", cpu->systime)
}
/* {{{ proto array sg_cpu_totals()
Returns the total amount of "ticks" the system has spent in each of the different states. */
PHP_FUNCTION(sg_cpu_totals)
{
_php_cpu_stats(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */
/* {{{ proto array sg_get_cpu_diff()
Returns the total amount of "ticks" the system has spent in each of the different states since last run. */
PHP_FUNCTION(sg_cpu_diff)
{
_php_cpu_stats(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */
static void _php_sg_diskio(INTERNAL_FUNCTION_PARAMETERS, int mode)
{
sg_disk_io_stats *dst;
int ent, i;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
if (mode) {
dst = sg_get_disk_io_stats(&ent);
} else {
dst = sg_get_disk_io_stats_diff(&ent);
}
if (dst == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
for (i = 0; i < ent; i++) {
zval *tmpz;
MAKE_STD_ZVAL(tmpz);
array_init(tmpz);
PHP_SG_ADD_LLVAL(tmpz, "read", dst->read_bytes)
PHP_SG_ADD_LLVAL(tmpz, "written", dst->write_bytes)
PHP_SG_ADD_LVAL(tmpz, "time_frame", dst->systime)
add_assoc_zval(return_value, dst->disk_name, tmpz);
dst++;
}
}
/* {{{ proto array sg_diskio_stats()
Returns the disk IO stored in the kernel which holds the amount of data transferred since bootup. */
PHP_FUNCTION(sg_diskio_stats)
{
_php_sg_diskio(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */
/* {{{ proto array sg_diskio_stats_diff()
Returns the disk IO stored in the kernel which holds the amount of data transferred since last call. */
PHP_FUNCTION(sg_diskio_stats_diff)
{
_php_sg_diskio(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */
/* {{{ proto array sg_fs_stats()
Returns file system information. */
PHP_FUNCTION(sg_fs_stats)
{
sg_fs_stats *stats;
int entries, i;
zval *this_fs;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
stats = sg_get_fs_stats(&entries);
if (stats == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
for (i = 0; i < entries; i++) {
MAKE_STD_ZVAL(this_fs);
array_init(this_fs);
add_assoc_string(this_fs, "device_name", stats->device_name, 1);
add_assoc_string(this_fs, "fs_type", stats->fs_type, 1);
add_assoc_string(this_fs, "mnt_point", stats->mnt_point, 1);
PHP_SG_ADD_LLVAL(this_fs, "size", stats->size);
PHP_SG_ADD_LLVAL(this_fs, "used", stats->used);
PHP_SG_ADD_LLVAL(this_fs, "avail", stats->avail);
PHP_SG_ADD_LLVAL(this_fs, "total_inodes", stats->total_inodes);
PHP_SG_ADD_LLVAL(this_fs, "used_inodes", stats->used_inodes);
PHP_SG_ADD_LLVAL(this_fs, "free_inodes", stats->free_inodes);
PHP_SG_ADD_LLVAL(this_fs, "avail_inodes", stats->avail_inodes);
PHP_SG_ADD_LLVAL(this_fs, "io_size", stats->io_size);
PHP_SG_ADD_LLVAL(this_fs, "block_size", stats->block_size);
PHP_SG_ADD_LLVAL(this_fs, "total_blocks", stats->total_blocks);
PHP_SG_ADD_LLVAL(this_fs, "free_blocks", stats->free_blocks);
PHP_SG_ADD_LLVAL(this_fs, "used_blocks", stats->used_blocks);
PHP_SG_ADD_LLVAL(this_fs, "avail_blocks", stats->avail_blocks);
add_next_index_zval(return_value, this_fs);
*stats++;
}
}
/* }}} */
/* {{{ proto array sg_general_stats()
Returns general information about the operating system. */
PHP_FUNCTION(sg_general_stats)
{
sg_host_info *stats;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
stats = sg_get_host_info();
if (stats == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
add_assoc_string(return_value, "os_name", stats->os_name, 1);
add_assoc_string(return_value, "os_release", stats->os_release, 1);
add_assoc_string(return_value, "os_version", stats->os_version, 1);
add_assoc_string(return_value, "platform", stats->platform, 1);
add_assoc_string(return_value, "hostname", stats->hostname, 1);
PHP_SG_ADD_LVAL(return_value, "uptime", stats->uptime)
}
/* }}} */
/* {{{ proto array sg_load_stats()
Returns the average load for the last 1, 5 and 15 minutes. */
PHP_FUNCTION(sg_load_stats)
{
sg_load_stats *lst;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
lst = sg_get_load_stats();
if (lst == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
PHP_SG_ADD_FVAL(return_value, "min1", lst->min1)
PHP_SG_ADD_FVAL(return_value, "min5", lst->min5)
PHP_SG_ADD_FVAL(return_value, "min15", lst->min15)
}
/* }}} */
/* {{{ proto array sg_memory_stats()
Returns information about memory usage. */
PHP_FUNCTION(sg_memory_stats)
{
sg_mem_stats *ms;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
ms = sg_get_mem_stats();
if (ms == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
PHP_SG_ADD_LLVAL(return_value, "total", ms->total)
PHP_SG_ADD_LLVAL(return_value, "free", ms->free)
PHP_SG_ADD_LLVAL(return_value, "used", ms->used)
PHP_SG_ADD_LLVAL(return_value, "cache", ms->cache)
}
/* }}} */
/* {{{ proto array sg_swap_stats()
Returns information about swap usage. */
PHP_FUNCTION(sg_swap_stats)
{
sg_swap_stats *ss;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
ss = sg_get_swap_stats();
if (ss == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
PHP_SG_ADD_LLVAL(return_value, "total", ss->total)
PHP_SG_ADD_LLVAL(return_value, "free", ss->free)
PHP_SG_ADD_LLVAL(return_value, "used", ss->used)
}
/* }}} */
static void _php_sg_network(INTERNAL_FUNCTION_PARAMETERS, int mode)
{
sg_network_io_stats *ns;
int ent, i;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
if (!mode) {
ns = sg_get_network_io_stats(&ent);
} else {
ns = sg_get_network_io_stats_diff(&ent);
}
if (ns == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
for (i = 0; i < ent; i++) {
zval *tmpz;
MAKE_STD_ZVAL(tmpz);
array_init(tmpz);
PHP_SG_ADD_LLVAL(tmpz, "sent", ns->tx)
PHP_SG_ADD_LLVAL(tmpz, "received", ns->rx)
PHP_SG_ADD_LLVAL(tmpz, "packets_received", ns->ipackets)
PHP_SG_ADD_LLVAL(tmpz, "packets_transmitted", ns->opackets)
PHP_SG_ADD_LLVAL(tmpz, "receive_errors", ns->ierrors)
PHP_SG_ADD_LLVAL(tmpz, "transmit_errors", ns->oerrors)
PHP_SG_ADD_LLVAL(tmpz, "collisions", ns->collisions)
PHP_SG_ADD_LVAL(tmpz, "time_frame", ns->systime)
add_assoc_zval(return_value, ns->interface_name, tmpz);
ns++;
}
}
/* {{{ proto array sg_network_stats()
Returns an array containing transmition statistics for all interfaces. */
PHP_FUNCTION(sg_network_stats)
{
_php_sg_network(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */
/* {{{ proto array sg_network_stats_diff()
Returns an array containing transmition statistics for all interfaces since last call. */
PHP_FUNCTION(sg_network_stats_diff)
{
_php_sg_network(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */
static void _php_sg_pages(INTERNAL_FUNCTION_PARAMETERS, int mode)
{
sg_page_stats *ps;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
if (mode) {
ps = sg_get_page_stats();
} else {
ps = sg_get_page_stats_diff();
}
if (ps == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
PHP_SG_ADD_LLVAL(return_value, "pages_in", ps->pages_pagein)
PHP_SG_ADD_LLVAL(return_value, "pages_out", ps->pages_pageout)
PHP_SG_ADD_LVAL(return_value, "time_frame", ps->systime)
}
/* {{{ proto array sg_page_stats()
Return the number of pages the system has paged in and out since bootup. */
PHP_FUNCTION(sg_page_stats)
{
_php_sg_pages(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */
/* {{{ proto array sg_page_stats_diff()
Return the number of pages the system has paged in and out since last call to the function. */
PHP_FUNCTION(sg_page_stats_diff)
{
_php_sg_pages(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */
/* {{{ proto array sg_process_count()
Returns counts of the currently running processes. */
PHP_FUNCTION(sg_process_count)
{
sg_process_count *ps;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
ps = sg_get_process_count();
if (ps == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
PHP_SG_ADD_LVAL(return_value, "total", ps->total)
PHP_SG_ADD_LVAL(return_value, "running", ps->running)
PHP_SG_ADD_LVAL(return_value, "sleeping", ps->sleeping)
PHP_SG_ADD_LVAL(return_value, "stopped", ps->stopped)
PHP_SG_ADD_LVAL(return_value, "zombie", ps->zombie)
}
/* }}} */
/* {{{ proto array sg_user_stats()
Returns an array containing all of the currently logged in users. */
PHP_FUNCTION(sg_user_stats)
{
sg_user_stats *us;
zval delim, str;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
us = sg_get_user_stats();
if (us == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
ZVAL_STRINGL(&delim, " ", sizeof(" ") - 1, 0);
ZVAL_STRINGL(&str, us->name_list, strlen(us->name_list), 0);
php_explode(&delim, &str, return_value, us->num_entries);
}
/* }}} */
/* {{{ proto array sg_network_iface_stats()
Returns an array containing statistics about the network interfaces in the machine. */
PHP_FUNCTION(sg_network_iface_stats)
{
sg_network_iface_stats *ifs;
int ent, i;
if (ZEND_NUM_ARGS()) {
WRONG_PARAM_COUNT;
}
ifs = sg_get_network_iface_stats(&ent);
if (ifs == NULL) {
PHP_SG_ERROR;
}
array_init(return_value);
for (i = 0; i < ent; i++) {
zval *tmpz;
MAKE_STD_ZVAL(tmpz);
array_init(tmpz);
PHP_SG_ADD_LVAL(tmpz, "speed", ifs->speed)
#ifdef HAVE_OLD_STATGRAB
PHP_SG_ADD_LVAL(tmpz, "duplex", ifs->dup)
#else
PHP_SG_ADD_LVAL(tmpz, "duplex", ifs->duplex)
#endif
add_assoc_bool(tmpz, "active", ifs->up);
add_assoc_zval(return_value, ifs->interface_name, tmpz);
ifs++;
}
}
/* }}} */
/* {{{ proto array sg_process_stats([int sorting_order [, int num_entries]])
Returns an array containing information about running processes. */
PHP_FUNCTION(sg_process_stats)
{
sg_process_stats *ps;
int ent, i;
long sort_order = -1, num_entries = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &sort_order, &num_entries) == FAILURE) {
RETURN_FALSE;
}
if ((ps = sg_get_process_stats(&ent)) == NULL) {
PHP_SG_ERROR;
}
if (num_entries > ent || num_entries < 1) {
num_entries = ent;
}
if (sort_order != -1) {
switch (sort_order) {
case SG_PS_SORT_NAME:
qsort(ps, ent, sizeof *ps, sg_process_compare_name);
break;
case SG_PS_SORT_PID:
qsort(ps, ent, sizeof *ps, sg_process_compare_pid);
break;
case SG_PS_SORT_UID:
qsort(ps, ent, sizeof *ps, sg_process_compare_uid);
break;
case SG_PS_SORT_GID:
qsort(ps, ent, sizeof *ps, sg_process_compare_gid);
break;
case SG_PS_SORT_SIZE:
qsort(ps, ent, sizeof *ps, sg_process_compare_size);
break;
case SG_PS_SORT_RES:
qsort(ps, ent, sizeof *ps, sg_process_compare_res);
break;
case SG_PS_SORT_CPU:
qsort(ps, ent, sizeof *ps, sg_process_compare_cpu);
break;
case SG_PS_SORT_TIME:
qsort(ps, ent, sizeof *ps, sg_process_compare_time);
break;
default:
php_error_docref(NULL TSRMLS_CC, E_WARNING, "'%ld' is not a supported sorting mode.", sort_order);
RETURN_FALSE;
}
}
array_init(return_value);
for (i = 0; i < num_entries; i++) {
zval *tmpz;
MAKE_STD_ZVAL(tmpz);
array_init(tmpz);
add_assoc_string(tmpz, "process_name", ps->process_name ? ps->process_name : "", 1);
add_assoc_string(tmpz, "proc_title", ps->proctitle ? ps->proctitle: "", 1);
PHP_SG_ADD_LVAL(tmpz, "pid", ps->pid)
PHP_SG_ADD_LVAL(tmpz, "parent_pid", ps->parent)
PHP_SG_ADD_LVAL(tmpz, "leader_pid", ps->pgid)
PHP_SG_ADD_LVAL(tmpz, "uid", ps->uid)
PHP_SG_ADD_LVAL(tmpz, "gid", ps->uid)
PHP_SG_ADD_LVAL(tmpz, "euid", ps->euid)
PHP_SG_ADD_LVAL(tmpz, "egid", ps->euid)
PHP_SG_ADD_LVAL(tmpz, "size", ps->proc_size)
PHP_SG_ADD_LVAL(tmpz, "size_in_mem", ps->proc_resident)
PHP_SG_ADD_LVAL(tmpz, "time_spent", ps->time_spent)
PHP_SG_ADD_FVAL(tmpz, "cpu_percent", ps->cpu_percent)
PHP_SG_ADD_LVAL(tmpz, "nice", ps->nice);
PHP_SG_ADD_LVAL(tmpz, "state", ps->state);
add_next_index_zval(return_value, tmpz);
ps++;
}
}
/* }}} */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/
syntax highlighted by Code2HTML, v. 0.9.1