2006-09-25 01:09:00 +00:00
/*
2006-10-17 18:20:44 +00:00
* wslua_listener . c
2006-09-25 01:09:00 +00:00
*
* Wireshark ' s interface to the Lua Programming Language
*
2006-10-17 18:20:44 +00:00
* Implementation of tap Listeners
*
2008-08-05 21:03:46 +00:00
* ( c ) 2006 , Luis E . Garcia Ontanon < luis @ ontanon . org >
2006-09-25 01:09:00 +00:00
*
* Wireshark - Network traffic analyzer
* By Gerald Combs < gerald @ wireshark . org >
* Copyright 1998 Gerald Combs
*
2018-02-08 16:33:09 +00:00
* SPDX - License - Identifier : GPL - 2.0 - or - later
2006-09-25 01:09:00 +00:00
*/
2011-04-21 13:13:39 +00:00
# include "config.h"
2020-03-30 15:57:16 +00:00
/* WSLUA_MODULE Listener Post-Dissection Packet Analysis */
2006-10-18 18:45:24 +00:00
2006-09-25 01:09:00 +00:00
# include "wslua.h"
2016-04-03 08:58:44 +00:00
WSLUA_CLASS_DEFINE ( Listener , FAIL_ON_NULL ( " Listener " ) ) ;
2006-09-25 01:09:00 +00:00
/*
2014-03-23 15:01:12 +00:00
A ` Listener ` is called once for every packet that matches a certain filter or has a certain tap .
2020-03-29 18:40:01 +00:00
It can read the tree , the packet ' s < < lua_class_Tvb , ` Tvb ` > > buffer as well as the tapped data , but it cannot add elements to the tree .
2006-09-25 01:09:00 +00:00
*/
2010-10-21 12:41:15 +00:00
static int tap_packet_cb_error_handler ( lua_State * L ) {
2006-09-25 01:09:00 +00:00
const gchar * error = lua_tostring ( L , 1 ) ;
static gchar * last_error = NULL ;
static int repeated = 0 ;
static int next = 2 ;
2015-12-31 22:33:48 +00:00
gchar * where = ( lua_pinfo ) ?
2020-10-29 15:01:27 +00:00
wmem_strdup_printf ( NULL , " Lua: on packet %i Error during execution of Listener packet callback " , lua_pinfo - > num ) :
wmem_strdup_printf ( NULL , " Lua: Error during execution of Listener packet callback " ) ;
2010-10-21 02:50:27 +00:00
/* show the error the 1st, 3rd, 5th, 9th, 17th, 33th... time it appears to avoid window flooding */
2006-09-25 01:09:00 +00:00
/* XXX the last series of identical errors won't be shown (the user however gets at least one message) */
2010-10-21 02:50:27 +00:00
2006-09-25 01:09:00 +00:00
if ( ! last_error ) {
report_failure ( " %s: \n %s " , where , error ) ;
last_error = g_strdup ( error ) ;
repeated = 0 ;
next = 2 ;
2015-12-31 22:33:48 +00:00
wmem_free ( NULL , where ) ;
2006-09-25 01:09:00 +00:00
return 0 ;
}
2010-10-21 02:50:27 +00:00
2006-09-25 01:09:00 +00:00
if ( g_str_equal ( last_error , error ) ) {
repeated + + ;
if ( repeated = = next ) {
report_failure ( " %s happened %i times: \n %s " , where , repeated , error ) ;
next * = 2 ;
}
} else {
report_failure ( " %s happened %i times: \n %s " , where , repeated , last_error ) ;
g_free ( last_error ) ;
last_error = g_strdup ( error ) ;
repeated = 0 ;
next = 2 ;
report_failure ( " %s: \n %s " , where , error ) ;
}
2010-10-21 02:50:27 +00:00
2015-12-31 22:33:48 +00:00
wmem_free ( NULL , where ) ;
2010-10-21 02:50:27 +00:00
return 0 ;
2006-09-25 01:09:00 +00:00
}
2019-01-01 03:36:12 +00:00
static tap_packet_status lua_tap_packet ( void * tapdata , packet_info * pinfo , epan_dissect_t * edt , const void * data ) {
2013-03-16 08:12:29 +00:00
Listener tap = ( Listener ) tapdata ;
2019-01-01 03:36:12 +00:00
tap_packet_status retval = TAP_PACKET_DONT_REDRAW ;
2019-01-25 17:02:46 +00:00
TreeItem lua_tree_tap ;
2010-10-21 02:50:27 +00:00
2019-01-01 03:36:12 +00:00
if ( tap - > packet_ref = = LUA_NOREF ) return TAP_PACKET_DONT_REDRAW ; /* XXX - report error and return TAP_PACKET_FAILED? */
2006-09-25 01:09:00 +00:00
lua_settop ( tap - > L , 0 ) ;
lua_pushcfunction ( tap - > L , tap_packet_cb_error_handler ) ;
lua_rawgeti ( tap - > L , LUA_REGISTRYINDEX , tap - > packet_ref ) ;
2010-10-21 02:50:27 +00:00
2006-09-25 01:09:00 +00:00
push_Pinfo ( tap - > L , pinfo ) ;
push_Tvb ( tap - > L , edt - > tvb ) ;
2010-10-21 02:50:27 +00:00
2009-06-05 22:42:47 +00:00
if ( tap - > extractor ) {
tap - > extractor ( tap - > L , data ) ;
} else {
lua_pushnil ( tap - > L ) ;
}
2010-10-21 02:50:27 +00:00
lua_pinfo = pinfo ;
2006-09-25 01:09:00 +00:00
lua_tvb = edt - > tvb ;
2019-01-25 17:02:46 +00:00
lua_tree_tap = create_TreeItem ( edt - > tree , NULL ) ;
lua_tree = lua_tree_tap ;
2010-10-21 02:50:27 +00:00
2006-09-25 01:09:00 +00:00
switch ( lua_pcall ( tap - > L , 3 , 1 , 1 ) ) {
case 0 :
2019-01-01 03:36:12 +00:00
/* XXX - treat 2 as TAP_PACKET_FAILED? */
retval = luaL_optinteger ( tap - > L , - 1 , 1 ) = = 0 ? TAP_PACKET_DONT_REDRAW : TAP_PACKET_REDRAW ;
2006-09-25 01:09:00 +00:00
break ;
case LUA_ERRRUN :
2019-01-01 03:36:12 +00:00
/* XXX - TAP_PACKET_FAILED? */
2006-09-25 01:09:00 +00:00
break ;
case LUA_ERRMEM :
2021-06-14 23:06:02 +00:00
ws_warning ( " Memory alloc error while calling listener tap callback packet " ) ;
2019-01-01 03:36:12 +00:00
/* XXX - TAP_PACKET_FAILED? */
2006-09-25 01:09:00 +00:00
break ;
2020-10-29 15:01:27 +00:00
case LUA_ERRERR :
2021-06-14 23:06:02 +00:00
ws_warning ( " Error while running the error handler function for listener tap callback " ) ;
2020-10-29 15:01:27 +00:00
break ;
2006-09-25 01:09:00 +00:00
default :
2021-03-20 01:58:26 +00:00
ws_assert_not_reached ( ) ;
2006-09-25 01:09:00 +00:00
break ;
}
2010-10-21 02:50:27 +00:00
2008-07-26 23:41:31 +00:00
clear_outstanding_Pinfo ( ) ;
clear_outstanding_Tvb ( ) ;
2010-10-21 02:50:27 +00:00
lua_pinfo = NULL ;
2006-09-25 01:09:00 +00:00
lua_tvb = NULL ;
lua_tree = NULL ;
2019-01-25 17:02:46 +00:00
g_free ( lua_tree_tap ) ;
2010-10-21 02:50:27 +00:00
2006-09-25 01:09:00 +00:00
return retval ;
}
2010-10-21 12:41:15 +00:00
static int tap_reset_cb_error_handler ( lua_State * L ) {
2020-10-29 15:01:27 +00:00
const gchar * error = lua_tostring ( L , 1 ) ;
report_failure ( " Lua: Error during execution of Listener reset callback: \n %s " , error ) ;
return 0 ;
2006-09-25 01:09:00 +00:00
}
2010-10-21 12:41:15 +00:00
static void lua_tap_reset ( void * tapdata ) {
2013-03-16 08:12:29 +00:00
Listener tap = ( Listener ) tapdata ;
2010-10-21 02:50:27 +00:00
2014-02-19 08:22:55 +00:00
if ( tap - > reset_ref = = LUA_NOREF ) return ;
2010-10-21 02:50:27 +00:00
2006-09-25 01:09:00 +00:00
lua_pushcfunction ( tap - > L , tap_reset_cb_error_handler ) ;
2014-02-19 08:22:55 +00:00
lua_rawgeti ( tap - > L , LUA_REGISTRYINDEX , tap - > reset_ref ) ;
2010-10-21 02:50:27 +00:00
2020-10-29 15:01:27 +00:00
switch ( lua_pcall ( tap - > L , 0 , 0 , lua_gettop ( tap - > L ) - 1 ) ) {
2006-09-25 01:09:00 +00:00
case 0 :
break ;
case LUA_ERRRUN :
2021-06-14 23:06:02 +00:00
ws_warning ( " Runtime error while calling a listener's init() " ) ;
2006-09-25 01:09:00 +00:00
break ;
case LUA_ERRMEM :
2021-06-14 23:06:02 +00:00
ws_warning ( " Memory alloc error while calling a listener's init() " ) ;
2006-09-25 01:09:00 +00:00
break ;
2020-10-29 15:01:27 +00:00
case LUA_ERRERR :
2021-06-14 23:06:02 +00:00
ws_warning ( " Error while running the error handler function for a listener's init() " ) ;
2020-10-29 15:01:27 +00:00
break ;
2006-09-25 01:09:00 +00:00
default :
2021-03-20 01:58:26 +00:00
ws_assert_not_reached ( ) ;
2006-09-25 01:09:00 +00:00
break ;
}
}
2020-10-29 15:01:27 +00:00
static int tap_draw_cb_error_handler ( lua_State * L ) {
const gchar * error = lua_tostring ( L , 1 ) ;
report_failure ( " Lua: Error during execution of Listener draw callback: \n %s " , error ) ;
return 0 ;
}
2010-10-21 12:41:15 +00:00
static void lua_tap_draw ( void * tapdata ) {
2013-03-16 08:12:29 +00:00
Listener tap = ( Listener ) tapdata ;
2006-09-25 01:09:00 +00:00
const gchar * error ;
2020-10-29 15:01:27 +00:00
2006-09-25 01:09:00 +00:00
if ( tap - > draw_ref = = LUA_NOREF ) return ;
2010-10-21 02:50:27 +00:00
2020-10-29 15:01:27 +00:00
lua_pushcfunction ( tap - > L , tap_draw_cb_error_handler ) ;
2006-09-25 01:09:00 +00:00
lua_rawgeti ( tap - > L , LUA_REGISTRYINDEX , tap - > draw_ref ) ;
2010-10-21 02:50:27 +00:00
2020-10-29 15:01:27 +00:00
switch ( lua_pcall ( tap - > L , 0 , 0 , lua_gettop ( tap - > L ) - 1 ) ) {
2006-09-25 01:09:00 +00:00
case 0 :
/* OK */
break ;
case LUA_ERRRUN :
error = lua_tostring ( tap - > L , - 1 ) ;
2021-06-14 23:06:02 +00:00
ws_warning ( " Runtime error while calling a listener's draw(): %s " , error ) ;
2006-09-25 01:09:00 +00:00
break ;
case LUA_ERRMEM :
2021-06-14 23:06:02 +00:00
ws_warning ( " Memory alloc error while calling a listener's draw() " ) ;
2006-09-25 01:09:00 +00:00
break ;
2020-10-29 15:01:27 +00:00
case LUA_ERRERR :
2021-06-14 23:06:02 +00:00
ws_warning ( " Error while running the error handler function for a listener's draw() " ) ;
2020-10-29 15:01:27 +00:00
break ;
2006-09-25 01:09:00 +00:00
default :
2021-03-20 01:58:26 +00:00
ws_assert_not_reached ( ) ;
2006-09-25 01:09:00 +00:00
break ;
}
}
2015-08-11 12:08:08 +00:00
/* TODO: we should probably use a Lua table here */
static GPtrArray * listeners = NULL ;
static void deregister_Listener ( lua_State * L _U_ , Listener tap ) {
if ( tap - > all_fields ) {
epan_set_always_visible ( FALSE ) ;
tap - > all_fields = FALSE ;
}
remove_tap_listener ( tap ) ;
2015-11-17 21:36:01 +00:00
g_free ( tap - > filter ) ;
g_free ( tap - > name ) ;
g_free ( tap ) ;
2015-08-11 12:08:08 +00:00
}
2010-10-21 12:41:15 +00:00
WSLUA_CONSTRUCTOR Listener_new ( lua_State * L ) {
2020-03-29 18:40:01 +00:00
/* Creates a new `Listener` tap object. */
# define WSLUA_OPTARG_Listener_new_TAP 1 /* The name of this tap. See <<lua_fn_Listener_list__,`Listener.list()`>> for a way to print valid listener names. */
# define WSLUA_OPTARG_Listener_new_FILTER 2 / *
A display filter to apply to the tap .
The ` tap . packet ` function will be called for each matching packet .
The default is ` nil ` , which matches every packet .
Example : " m2tp " .
*/
# define WSLUA_OPTARG_Listener_new_ALLFIELDS 3 / *
Whether to generate all fields .
The default is ` false ` .
Note : This impacts performance . */
2006-09-25 01:09:00 +00:00
const gchar * tap_type = luaL_optstring ( L , WSLUA_OPTARG_Listener_new_TAP , " frame " ) ;
const gchar * filter = luaL_optstring ( L , WSLUA_OPTARG_Listener_new_FILTER , NULL ) ;
2014-02-21 06:11:41 +00:00
const gboolean all_fields = wslua_optbool ( L , WSLUA_OPTARG_Listener_new_ALLFIELDS , FALSE ) ;
2009-06-05 22:42:47 +00:00
Listener tap ;
2016-06-19 09:01:56 +00:00
GString * error ;
2006-09-25 01:09:00 +00:00
2013-03-16 08:12:29 +00:00
tap = ( Listener ) g_malloc ( sizeof ( struct _wslua_tap ) ) ;
2010-10-21 02:50:27 +00:00
2009-06-05 22:42:47 +00:00
tap - > name = g_strdup ( tap_type ) ;
2017-08-26 08:30:47 +00:00
tap - > filter = g_strdup ( filter ) ;
2006-10-17 18:20:44 +00:00
tap - > extractor = wslua_get_tap_extractor ( tap_type ) ;
2006-09-25 01:09:00 +00:00
tap - > L = L ;
tap - > packet_ref = LUA_NOREF ;
tap - > draw_ref = LUA_NOREF ;
2014-02-19 08:22:55 +00:00
tap - > reset_ref = LUA_NOREF ;
2014-02-21 06:11:41 +00:00
tap - > all_fields = all_fields ;
2010-10-21 02:50:27 +00:00
2009-06-05 22:42:47 +00:00
/*
* XXX - do all Lua taps require the protocol tree ? If not , it might
* be useful to have a way to indicate whether any do .
*
* XXX - do any Lua taps require the columns ? If so , we either need
* to request them for this tap , or do so if any Lua taps require them .
*/
2018-07-21 00:07:19 +00:00
error = register_tap_listener ( tap_type , tap , tap - > filter , TL_REQUIRES_PROTO_TREE , lua_tap_reset , lua_tap_packet , lua_tap_draw , NULL ) ;
2006-09-25 01:09:00 +00:00
if ( error ) {
2009-03-13 22:06:48 +00:00
g_free ( tap - > filter ) ;
2006-09-25 01:09:00 +00:00
g_free ( tap - > name ) ;
g_free ( tap ) ;
2009-06-05 22:42:47 +00:00
/* WSLUA_ERROR(new_tap,"tap registration error"); */
2016-06-19 09:01:56 +00:00
lua_pushfstring ( L , " Error while registering tap: \n %s " , error - > str ) ;
g_string_free ( error , TRUE ) ;
2017-12-15 20:38:30 +00:00
return luaL_error ( L , lua_tostring ( L , - 1 ) ) ;
2006-09-25 01:09:00 +00:00
}
2010-10-21 02:50:27 +00:00
2014-02-21 06:11:41 +00:00
if ( all_fields ) {
epan_set_always_visible ( TRUE ) ;
}
2015-08-11 12:08:08 +00:00
g_ptr_array_add ( listeners , tap ) ;
2006-09-25 01:09:00 +00:00
pushListener ( L , tap ) ;
WSLUA_RETURN ( 1 ) ; /* The newly created Listener listener object */
}
2014-02-18 15:03:04 +00:00
/* Allow dissector key names to be sorted alphabetically */
static gint
compare_dissector_key_name ( gconstpointer dissector_a , gconstpointer dissector_b )
{
return strcmp ( ( const char * ) dissector_a , ( const char * ) dissector_b ) ;
}
2020-03-29 18:40:01 +00:00
WSLUA_CONSTRUCTOR Listener_list ( lua_State * L ) { /*
Gets a Lua array table of all registered ` Listener ` tap names .
2014-03-23 15:01:12 +00:00
2020-03-29 18:40:01 +00:00
Note : This is an expensive operation , and should only be used for troubleshooting .
2014-03-23 15:01:12 +00:00
2020-03-29 18:40:01 +00:00
@ since 1.11 .3
= = = = = Example
[ source , lua ]
- - - -
- - Print a list of tap listeners to stdout .
for _ , tap_name in pairs ( Listener . list ( ) ) do
print ( tap_name )
end
- - - -
*/
2014-02-18 15:03:04 +00:00
GList * list = get_tap_names ( ) ;
GList * elist = NULL ;
int i = 1 ;
if ( ! list ) return luaL_error ( L , " Cannot retrieve tap name list " ) ;
list = g_list_sort ( list , ( GCompareFunc ) compare_dissector_key_name ) ;
elist = g_list_first ( list ) ;
lua_newtable ( L ) ;
for ( i = 1 ; elist ; i + + , elist = g_list_next ( elist ) ) {
lua_pushstring ( L , ( const char * ) elist - > data ) ;
2017-08-25 18:19:06 +00:00
lua_rawseti ( L , - 2 , i ) ;
2014-02-18 15:03:04 +00:00
}
g_list_free ( list ) ;
WSLUA_RETURN ( 1 ) ; /* The array table of registered tap names */
}
2010-10-21 12:41:15 +00:00
WSLUA_METHOD Listener_remove ( lua_State * L ) {
2014-03-23 15:01:12 +00:00
/* Removes a tap `Listener`. */
2006-09-25 01:09:00 +00:00
Listener tap = checkListener ( L , 1 ) ;
2010-10-21 02:50:27 +00:00
2015-09-03 12:10:40 +00:00
if ( listeners & & g_ptr_array_remove ( listeners , tap ) ) {
2015-08-11 12:08:08 +00:00
deregister_Listener ( L , tap ) ;
2014-02-21 06:11:41 +00:00
}
2006-09-25 01:09:00 +00:00
return 0 ;
}
2013-02-25 22:05:28 +00:00
WSLUA_METAMETHOD Listener__tostring ( lua_State * L ) {
2014-03-23 15:01:12 +00:00
/* Generates a string of debug info for the tap `Listener`. */
2006-09-25 01:09:00 +00:00
Listener tap = checkListener ( L , 1 ) ;
2010-10-21 02:50:27 +00:00
2014-12-29 05:21:15 +00:00
lua_pushfstring ( L , " Listener(%s) filter: %s " , tap - > name , tap - > filter ? tap - > filter : " NONE " ) ;
2010-10-21 02:50:27 +00:00
2006-09-25 01:09:00 +00:00
return 1 ;
}
2014-03-23 15:01:12 +00:00
/* WSLUA_ATTRIBUTE Listener_packet WO A function that will be called once every packet matches the
` Listener ` listener filter .
When later called by Wireshark , the ` packet ` function will be given :
1. A ` Pinfo ` object
2. A ` Tvb ` object
3. A ` tapinfo ` table
2010-10-21 12:41:15 +00:00
2018-02-09 20:56:58 +00:00
[ source , lua ]
- - - -
function tap . packet ( pinfo , tvb , tapinfo ) . . . end
- - - -
2014-03-23 15:01:12 +00:00
2018-02-09 20:56:58 +00:00
[ NOTE ]
= = = =
` tapinfo ` is a table of info based on the ` Listener ` ' s type , or nil .
= = = =
2014-02-19 08:22:55 +00:00
*/
WSLUA_ATTRIBUTE_FUNC_SETTER ( Listener , packet ) ;
2010-10-21 12:41:15 +00:00
2014-03-23 15:01:12 +00:00
/* WSLUA_ATTRIBUTE Listener_draw WO A function that will be called once every few seconds to redraw the GUI objects;
in Tshark this funtion is called only at the very end of the capture file .
When later called by Wireshark , the ` draw ` function will not be given any arguments .
2010-10-21 02:50:27 +00:00
2018-02-09 20:56:58 +00:00
[ source , lua ]
- - - -
function tap . draw ( ) . . . end
- - - -
2014-02-19 08:22:55 +00:00
*/
WSLUA_ATTRIBUTE_FUNC_SETTER ( Listener , draw ) ;
2010-10-21 02:50:27 +00:00
2014-02-19 08:22:55 +00:00
/* WSLUA_ATTRIBUTE Listener_reset WO A function that will be called at the end of the capture run.
2010-10-21 02:50:27 +00:00
2014-03-23 15:01:12 +00:00
When later called by Wireshark , the ` reset ` function will not be given any arguments .
2018-02-09 20:56:58 +00:00
[ source , lua ]
- - - -
function tap . reset ( ) . . . end
- - - -
2014-02-19 08:22:55 +00:00
*/
WSLUA_ATTRIBUTE_FUNC_SETTER ( Listener , reset ) ;
2010-10-21 02:50:27 +00:00
2006-09-25 01:09:00 +00:00
2013-02-25 22:05:28 +00:00
static int Listener__gc ( lua_State * L _U_ ) {
2015-11-17 21:36:01 +00:00
/* do NOT free Listener here, only in deregister_Listener */
2013-02-25 22:05:28 +00:00
return 0 ;
}
2006-09-25 01:09:00 +00:00
2014-02-19 08:22:55 +00:00
/* This table is ultimately registered as a sub-table of the class' metatable,
* and if __index / __newindex is invoked then it calls the appropriate function
* from this table for getting / setting the members .
*/
WSLUA_ATTRIBUTES Listener_attributes [ ] = {
WSLUA_ATTRIBUTE_WOREG ( Listener , packet ) ,
WSLUA_ATTRIBUTE_WOREG ( Listener , draw ) ,
WSLUA_ATTRIBUTE_WOREG ( Listener , reset ) ,
{ NULL , NULL , NULL }
} ;
WSLUA_METHODS Listener_methods [ ] = {
WSLUA_CLASS_FNREG ( Listener , new ) ,
WSLUA_CLASS_FNREG ( Listener , remove ) ,
2014-02-18 15:03:04 +00:00
WSLUA_CLASS_FNREG ( Listener , list ) ,
2008-04-25 18:59:20 +00:00
{ NULL , NULL }
2006-09-25 01:09:00 +00:00
} ;
2014-02-19 08:22:55 +00:00
WSLUA_META Listener_meta [ ] = {
WSLUA_CLASS_MTREG ( Listener , tostring ) ,
2008-04-25 18:59:20 +00:00
{ NULL , NULL }
2006-09-25 01:09:00 +00:00
} ;
2010-10-21 12:41:15 +00:00
int Listener_register ( lua_State * L ) {
2009-06-05 22:42:47 +00:00
wslua_set_tap_enums ( L ) ;
2015-08-11 12:08:08 +00:00
listeners = g_ptr_array_new ( ) ;
2019-01-06 14:03:01 +00:00
WSLUA_REGISTER_CLASS_WITH_ATTRS ( Listener ) ;
2014-01-31 07:25:42 +00:00
return 0 ;
2006-09-25 01:09:00 +00:00
}
2015-08-11 12:08:08 +00:00
static void deregister_tap_listener ( gpointer data , gpointer userdata ) {
lua_State * L = ( lua_State * ) userdata ;
Listener tap = ( Listener ) data ;
deregister_Listener ( L , tap ) ;
}
int wslua_deregister_listeners ( lua_State * L ) {
g_ptr_array_foreach ( listeners , deregister_tap_listener , L ) ;
2018-05-09 20:28:57 +00:00
g_ptr_array_free ( listeners , TRUE ) ;
2015-08-11 12:08:08 +00:00
listeners = NULL ;
return 0 ;
}
2015-02-13 18:25:19 +00:00
/*
2019-07-26 18:43:17 +00:00
* Editor modelines - https : //www.wireshark.org/tools/modelines.html
2015-02-13 18:25:19 +00:00
*
* Local variables :
* c - basic - offset : 4
* tab - width : 8
* indent - tabs - mode : nil
* End :
*
* vi : set shiftwidth = 4 tabstop = 8 expandtab :
* : indentSize = 4 : tabSize = 8 : noTabs = true :
*/