/* ** Copyright (C) 2006-2007 by Carnegie Mellon University. ** ** @OPENSOURCE_HEADER_START@ ** ** Use of the SILK system and related source code is subject to the terms ** of the following licenses: ** ** GNU Public License (GPL) Rights pursuant to Version 2, June 1991 ** Government Purpose License Rights (GPLR) pursuant to DFARS 252.225-7013 ** ** NO WARRANTY ** ** ANY INFORMATION, MATERIALS, SERVICES, INTELLECTUAL PROPERTY OR OTHER ** PROPERTY OR RIGHTS GRANTED OR PROVIDED BY CARNEGIE MELLON UNIVERSITY ** PURSUANT TO THIS LICENSE (HEREINAFTER THE "DELIVERABLES") ARE ON AN ** "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY ** KIND, EITHER EXPRESS OR IMPLIED AS TO ANY MATTER INCLUDING, BUT NOT ** LIMITED TO, WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE, ** MERCHANTABILITY, INFORMATIONAL CONTENT, NONINFRINGEMENT, OR ERROR-FREE ** OPERATION. CARNEGIE MELLON UNIVERSITY SHALL NOT BE LIABLE FOR INDIRECT, ** SPECIAL OR CONSEQUENTIAL DAMAGES, SUCH AS LOSS OF PROFITS OR INABILITY ** TO USE SAID INTELLECTUAL PROPERTY, UNDER THIS LICENSE, REGARDLESS OF ** WHETHER SUCH PARTY WAS AWARE OF THE POSSIBILITY OF SUCH DAMAGES. ** LICENSEE AGREES THAT IT WILL NOT MAKE ANY WARRANTY ON BEHALF OF ** CARNEGIE MELLON UNIVERSITY, EXPRESS OR IMPLIED, TO ANY PERSON ** CONCERNING THE APPLICATION OF OR THE RESULTS TO BE OBTAINED WITH THE ** DELIVERABLES UNDER THIS LICENSE. ** ** Licensee hereby agrees to defend, indemnify, and hold harmless Carnegie ** Mellon University, its trustees, officers, employees, and agents from ** all claims or demands made against them (and any related losses, ** expenses, or attorney's fees) arising out of, or relating to Licensee's ** and/or its sub licensees' negligent use or willful misuse of or ** negligent conduct or willful misconduct regarding the Software, ** facilities, or other rights or assistance granted by Carnegie Mellon ** University under this License, including, but not limited to, any ** claims of product liability, personal injury, death, damage to ** property, or violation of any laws or regulations. ** ** Carnegie Mellon University Software Engineering Institute authored ** documents are sponsored by the U.S. Department of Defense under ** Contract F19628-00-C-0003. Carnegie Mellon University retains ** copyrights in all material produced under this contract. The U.S. ** Government retains a non-exclusive, royalty-free license to publish or ** reproduce these documents, or allow others to do so, for U.S. ** Government purposes only pursuant to the copyright license under the ** contract clause at 252.227.7013. ** ** @OPENSOURCE_HEADER_END@ */ #ifndef _SKSITE_H #define _SKSITE_H #include "silk.h" RCSIDENTVAR(rcsID_SKSITE_H, "$SiLK: sksite.h 6072 2007-01-22 18:11:23Z mthomas $"); #include "rwpack.h" #include "skvector.h" /* ** sksite.h ** ** An interface to site-specific settings. ** */ /* Add an option for processing the silk.conf configuration file */ #define SK_SITE_FLAG_CONFIG_FILE 0x01 /** Configuration and Initialization **********************************/ /* * Initialize everything *except* the sensor configuration from the * config file. */ int sksiteInitialize(int levels); /* * Register options according to the value of 'flags'. * * The parameter 'flags' is the bitsize-OR of the the SK_SITE_FLAG_* * values listed above. */ int sksiteOptionsRegister(uint32_t flags); /* * Print usage information for any options that were registered by * sksiteOptionsRegister(). */ void sksiteOptionsUsage(FILE *fh); /* * Read the configuration file from the default location, or the * location set with sksiteSetConfigPath. If this is called more * than once, nothing will happen on later calls. * * Returns 0 if the configuration has properly loaded; or non-zero * otherwise. Returns -1 to indicate errors parsing the file, or -2 * if the site configuration file cannot be found. * * If verbose is 1, this function will report failures to open the * file as errors. If verbose is 0, only parsing failures will be * reported. (For example, if you intend to try opening several * files in a row to find the correct file, verbose should be 0 to * avoid several reports of open failures.) * * NOTE: This function should be called single-threaded. */ int sksiteConfigure(int verbose); /* * Set the configuration file path. Attempting to call this after * the config file has been read will result in an error and * the program exiting. */ int sksiteSetConfigPath(const char *filename); /* * Return the config file path, setting it to the default location * if it has not yet been set. */ const char *sksiteGetConfigPath(void); /* * Destroy all the data structures and deallocate all memory * associated with the site. */ void sksiteTeardown(void); /** Iterators *********************************************************/ /* * Iterators for the various types of things. These take a pointer to * an iterator, and a pointer to a pointer of the type being iterated * over. If the iterator is empty, 0 is returned. Otherwise, *out_X_id * is set to the next valid ID and 1 is returned. */ typedef struct { /* position in the vector */ int si_index; /* vector of candidates */ sk_vector_t *si_vector; /* 1 if vector contains pointers, 0 if it contains IDs */ int si_contains_pointers; } sensor_iter_t; typedef struct { /* position in the vector */ int ci_index; /* vector of candidates */ sk_vector_t *ci_vector; /* 1 if vector contains pointers, 0 if it contains IDs */ int ci_contains_pointers; } class_iter_t; typedef struct { /* position in the vector */ int gi_index; /* vector of candidates */ sk_vector_t *gi_vector; /* 1 if vector contains pointers, 0 if it contains IDs */ int gi_contains_pointers; } sensorgroup_iter_t; typedef struct { /* position in the vector */ int fi_index; /* vector of candidates */ sk_vector_t *fi_vector; /* 1 if vector contains pointers, 0 if it contains IDs */ int fi_contains_pointers; } flowtype_iter_t; /* * Return the next value in iterator 'iter' in 'out_sensor_id'. If * the iterator is finished, returns 0, otherwise returns 1. * Other iterators work identically. */ int sksiteSensorIteratorNext( sensor_iter_t *iter, sensorID_t *out_sensor_id); int sksiteClassIteratorNext( class_iter_t *iter, classID_t *out_class_id); int sksiteSensorgroupIteratorNext( sensorgroup_iter_t *iter, sensorgroupID_t *out_sensorgroup_id); int sksiteFlowtypeIteratorNext( flowtype_iter_t *iter, flowtypeID_t *out_flowtype_id); /** Sensors ***********************************************************/ /* * Create the sensor 'sensor_name' with id 'sensor_id'. It is an error * to create a sensor with an ID that is already allocated. If any * error occurs (sensor ID already in use, illegal sensor name, * out of memory), returns -1. Otherwise returns 0 on success. */ int sksiteSensorCreate( sensorID_t sensor_id, const char *sensor_name); /* * Find the sensor ID for a sensor given its name. Returns * SK_INVALID_SENSOR if no sensor is found with the given name. */ sensorID_t sksiteSensorLookup( const char *sensor_name); /* * Return 1 if a sensor with the given sensor ID exists, 0 if no * such sensor has been defined. */ int sksiteSensorExists( sensorID_t sensor_id); /* * Returns the maximum sensor ID that has been allocated to a * sensor. Add one to this value to get the lowest ID that is * certain not to have been allocated. (Note that not all IDs up to * the maximum must have been allocated.) Returns (sensorID_t)(-1) * if no sensors yet exist. */ sensorID_t sksiteSensorGetMaxID(void); /* * Returns the length of the longest currently known sensor name, or * a minimum value. This result is suitable for choosing the size * of a display column for sensor names and/or IDs. */ size_t sksiteSensorGetMaxNameStrLen(void); /* * Get the name of the sensor with the given sensor ID into the * given buffer of size buffer_size. If the name is longer than * buffer_size, the value returns is truncated with a '\0' in the * final position. * * Returns the number of characters that would have been written if * the buffer had been long enough. */ int sksiteSensorGetName( char *buffer, size_t buffer_size, sensorID_t sensor_id); /* * Returns 1 if the sensor with ID sensor_id is defined to be in the * class with ID class_id. Returns 0 otherwise. */ int sksiteIsSensorInClass( sensorID_t sensor_id, classID_t class_id); /* * Sets iter to be an iterator that loops over all defined sensors. */ void sksiteSensorIterator( sensor_iter_t *iter); /* * Sets iter to be an iterator that loops over all of the classes that * are possessed by the given sensor. */ void sksiteSensorClassIterator( sensorID_t sensor_id, class_iter_t *iter); /* * Returns 0 if the given name is a legal sensor name containing * no illegal characters and of the proper length. Returns -1 if * the name does not begin with an alpha character, -2 if the name * is too short, -3 if it is too long. A positive return value * indicates that the character at position n is invalid. */ int sksiteSensorNameIsLegal( const char *name); /* * Returns the number of classes that the given sensor belongs to. */ int sksiteSensorGetClassCount( sensorID_t sensor_id); /** Classes ***********************************************************/ /* * Create the class 'class_name' with id 'class_id'. It is an error * to create a class with an ID that is already allocated. If any * error occurs (class ID already in use, out of memory), returns * -1. Otherwise returns 0 on success. */ int sksiteClassCreate( classID_t class_id, const char *class_name); /* * Sets the default class for fglobbing. Returns 0 on success. */ int sksiteClassSetDefault( classID_t class_id); /* * Returns the default class for fglobbing. */ classID_t sksiteClassGetDefault(void); /* * Find the class ID for a class given its name. Returns * SK_INVALID_CLASS if no class is found with the given name. */ classID_t sksiteClassLookup( const char *class_name); /* * Return 1 if a class with the given class ID exists, 0 if no * such class has been defined. */ int sksiteClassExists( classID_t class_id); /* * Returns the maximum class ID that has been allocated to a * class. Add one to this value to get the lowest ID that is * certain not to have been allocated. (Note that not all IDs up to * the maximum must have been allocated.) Returns (classID_t)(-1) * if no classes yet exist. */ classID_t sksiteClassGetMaxID(void); /* * Returns the length of the longest currently known class name, or * a minimum value. This result is suitable for choosing the size * of a display column for class names and/or IDs. */ size_t sksiteClassGetMaxNameStrLen(void); /* * Get the name of the class with the given class ID into the * given buffer of size buffer_size. If the name is longer than * buffer_size, the value returns is truncated with a '\0' in the * final position. * * Returns the number of characters that would have been written if * the buffer had been long enough. */ int sksiteClassGetName( char *buffer, size_t buffer_size, classID_t class_id); /* * Adds the given sensor to the given class. Returns 0 on success, * -1 if an error occurred. */ int sksiteClassAddSensor( classID_t class_id, sensorID_t sensor_id); /* * Adds every sensor in the given sensorgroup to the given class. * Returns 0 on success, -1 if an error occurred. */ int sksiteClassAddSensorgroup( classID_t class_id, sensorgroupID_t sensorgroup_id); /* * Sets iter to be an iterator that loops over all defined classes. */ void sksiteClassIterator( class_iter_t *iter); /* * Sets iter to be an iterator that loops over all sensors in the * given class. */ void sksiteClassSensorIterator( classID_t class_id, sensor_iter_t *iter); /* * Sets iter to be an iterator that loops over all flowtypes in the * given class. */ void sksiteClassFlowtypeIterator( classID_t class_id, flowtype_iter_t *iter); /* * Sets iter to be an iterator that loops over all default flowtypes * for the given class. */ void sksiteClassDefaultFlowtypeIterator( classID_t class_id, flowtype_iter_t *iter); /* * Returns the number of sensors in the given class. */ int sksiteClassGetSensorCount( classID_t class_id); /* * Adds the given flowtype to the list of default flowtypes for the * given class. The flowtype should be a part of this class already. * (i.e. it should have been created with class class_id.) * * Returns 0 on success, -1 on any error. */ int sksiteClassAddDefaultFlowtype( classID_t class_id, flowtypeID_t flowtype_id); /** Sensorgroups ******************************************************/ /* * Create the group 'sensorgroup_name' with id 'sensorgroup_id'. It * is an error to create a group with an ID that is already * allocated. If any error occurs (class ID already in use, out of * memory), returns -1. Otherwise returns 0 on success. */ int sksiteSensorgroupCreate( sensorgroupID_t sensorgroup_id, const char *sensorgroup_name); /* * Find the sensorgroup ID for a group given its name. Returns * SK_INVALID_SENSORGROUP if no group is found with the given name. */ sensorgroupID_t sksiteSensorgroupLookup( const char *sensorgroup_name); /* * Return 1 if a sensorgroup with the given ID exists, 0 if no such * group has been defined. */ int sksiteSensorgroupExists( sensorgroupID_t sensorgroup_id); /* * Returns the maximum ID that has been allocated to a sensorgroup. * Add one to this value to get the lowest ID that is certain not to * have been allocated. (Note that not all IDs up to the maximum * must have been allocated.) Returns (sensorgroupID_t)(-1) if no * classes yet exist. */ sensorgroupID_t sksiteSensorgroupGetMaxID(void); /* * Returns the length of the longest currently known group name, or * a minimum value. This result is suitable for choosing the size * of a display column for group names and/or IDs. */ size_t sksiteSensorgroupGetMaxNameStrLen(void); /* * Get the name of the group with the given ID into the given buffer * of size buffer_size. If the name is longer than buffer_size, the * value returns is truncated with a '\0' in the final position. * * Returns the number of characters that would have been written if * the buffer had been long enough. */ int sksiteSensorgroupGetName( char *buffer, size_t buffer_size, sensorgroupID_t sensorgroup_id); /* * Adds the given sensor to the given class. Returns 0 on success, * -1 if an error occurred. */ int sksiteSensorgroupAddSensor( sensorgroupID_t sensorgroup_id, sensorID_t sensor_id); /* * Adds every sensor in the given sensorgroup to the given class. * Returns 0 on success, -1 if an error occurred. */ int sksiteSensorgroupAddSensorgroup( sensorgroupID_t dest, sensorgroupID_t src); /* * Sets iter to be an iterator that loops over all defined * sensorgroups. */ void sksiteSensorgroupIterator( sensorgroup_iter_t *iter); /* * Sets iter to be an iterator that loops over all sensors in the * given sensorgroup. */ void sksiteSensorgroupSensorIterator( sensorgroupID_t group_id, sensorgroup_iter_t *iter); /** Flowtypes *********************************************************/ /* * Flowtypes represent individual class/type pairs. Flowtype IDs * are actually recorded in files, while class IDs and types are * not. */ /* * Create the flowtype 'flowtype_name' with id 'flowtype_id'. * Associate it with the given class ID and type name. It is an * error to create a flowtype with an ID that is already allocated. * If any error occurs (class ID already in use, out of memory), * returns -1. Otherwise returns 0 on success. */ int sksiteFlowtypeCreate( flowtypeID_t flowtype_id, const char *flowtype_name, classID_t class_id, const char *type_name); /* * Find the flowtype ID for a flowtype given its name. Returns * SK_INVALID_FLOWTYPE if no flowtype is found with the given name. */ flowtypeID_t sksiteFlowtypeLookup( const char *flowtype_name); /* * Find the flowtype ID for a flowtype given its class and type. * Returns SK_INVALID_FLOWTYPE if no flowtype is found with the * given class and type. */ flowtypeID_t sksiteFlowtypeLookupByClassType( classID_t class, const char *type); /* * Return 1 if a flowtype with the given ID exists, 0 if no such * flowtype has been defined. */ int sksiteFlowtypeExists( flowtypeID_t flowtype_id); /* * Returns the maximum flowtype ID that has been allocated. Add one * to this value to get the lowest ID that is certain not to have * been allocated. (Note that not all IDs up to the maximum must * have been allocated.) Returns (flowtypeID_t)(-1) if no flowtypes * yet exist. */ flowtypeID_t sksiteFlowtypeGetMaxID(void); /* * Returns the class ID of this flowtype's class. Returns * SK_INVALID_CLASS if the flowtype does not exist. */ classID_t sksiteFlowtypeGetClass( flowtypeID_t flowtype_id); /* * Returns the length of the longest currently known flowtype name, * or a minimum value. This result is suitable for choosing the * size of a display column for flowtype names and/or IDs. */ size_t sksiteFlowtypeGetMaxNameStrLen(void); /* * Get the name of the flowtype with the given ID into the given * buffer of size buffer_size. If the name is longer than * buffer_size, the value returns is truncated with a '\0' in the * final position. * * Returns the number of characters that would have been written if * the buffer had been long enough. */ int sksiteFlowtypeGetName( char *buffer, size_t buffer_size, flowtypeID_t flowtype_id); /* * Returns the length of the longest currently known flowtype type, * or a minimum value. This result is suitable for choosing the * size of a display column for flowtype types. */ size_t sksiteFlowtypeGetMaxTypeStrLen(void); /* * Get the type of the flowtype with the given ID into the given * buffer of size buffer_size. If the name is longer than * buffer_size, the value returns is truncated with a '\0' in the * final position. * * Returns the number of characters that would have been written if * the buffer had been long enough. */ int sksiteFlowtypeGetType( char *buffer, size_t buffer_size, flowtypeID_t flowtype_id); /* * Sets iter to be an iterator that loops over all defined flowtypes. */ void sksiteFlowtypeIterator( flowtype_iter_t *iter, flowtypeID_t *out_flowtype_id); /* * Asserts that the given flowtype exists, and is associated with * the given class name and class type. If this is not the case, an * error message is printed and the program aborts. */ void sksiteFlowtypeAssert( flowtypeID_t flowtype_id, const char *class_name, const char *type); /** File Formats ******************************************************/ /* * Get the name of the file format with the given ID into the given * buffer of size buffer_size. If the name is longer than * buffer_size, the value returns is truncated with a '\0' in the * final position. * * Returns the number of characters that would have been written if * the buffer had been long enough. */ int sksiteFileformatGetName( char *buffer, size_t buffer_size, fileFormat_t id); /* * Return 1 if 'id' is a valid file output format, or 0 if it is * not. */ int sksiteFileformatIsValid( fileFormat_t id); /** Compression Methods ***********************************************/ /* * Given the compress method 'id', write the name of that method * into 'out_buffer' whose length is 'bufsize'. The function * returns a pointer to 'out_buffer', or NULL for an invalid * compression method. */ int sksiteCompmethodGetName( char *buffer, size_t buffer_size, sk_compmethod_t id); /* * Return 1 if the compression method 'id' is available, or 0 * otherwise. * * Since compression is supported by external libraries, a value * may be valid but unavailable when SiLK is built without support * for an external library. */ int sksiteCompmethodIsAvailable( sk_compmethod_t id); /* * Return 1 if 'id' is a valid compression method, or 0 if it is * not. See also sksiteCompmethodIsAvailable(). */ int sksiteCompmethodIsValid( sk_compmethod_t id); /* * Return the generically "best" compression method from all those * that are available. */ sk_compmethod_t sksiteCompmethodGetBest(void); /* * Return the default compression method. */ sk_compmethod_t sksiteCompmethodGetDefault(void); /* * Add an option that will allow the user to set the compression * method of binary output files. After optionsParse() sucessfully * returns, the variable pointed to by 'compression_method' will * contain the compression method to use. * * If the user does not exercise the compression-method option, * 'compression_method' will be set to the default compression * method. */ int sksiteCompmethodOptionsRegister( sk_compmethod_t *compression_method); /* * Include the compression method option in the list of options for * --help output. */ void sksiteCompmethodOptionsUsage( FILE *fh); /** Paths *************************************************************/ /* * Fill 'buffer' with the data root directory. 'buffer' is a * character arra whose length is 'bufsize'. Returns a pointer to * 'buffer', or NULL if 'buffer' is too small to hold the root * directory. */ char *sksiteGetRootDir( char *buffer, size_t bufsize); /* * Sets the data root directory to 'rootdir', overriding the value * that was set when SiLK was configured. Returns 0 on success, or * -1 if 'rootdir' is NULL, the empty string, or longer than * PATH_MAX characters. */ int sksiteSetRootDir( const char *rootdir); /* * Sets the path format for output files, overriding the current * value. Returns 0 on success, or -1 if 'format' is NULL, the empty * string, or longer than PATH_MAX characters. * * NOTE: At the moment, all path formats are required to end in %x, * so that filenames have the default format. This restriction may * be lifted in the future. * * The following strings have special meaning in path formats: * * %C - Class name * %F - Flowtype name * %H - Hour, two digits, zero-padded * %N - Sensor name * %T - Type name * %Y - Year, four digits, zero-padded * %d - Day of month, two digits, zero-padded * %f - Flowtype ID * %m - Month of year, two digits, zero-padded * %n - Sensor ID * %x - Default filename, which is the same as "%F-%N_%Y%m%d.%H" * %% - a single percent sign * * The default path format is "%T/%Y/%m/%d/%x" */ int sksiteSetPathFormat( const char *format); /* * Fill in 'buffer', a C-array of size 'bufsize', with the complete * path of the file having the specified 'flowtype', 'sensor', and * 'timestamp'. The name will be of the form specified by the * current path format. * * When 'suffix' is non-NULL and not the empty string, it will be * appended to the name of the generated file. If 'suffix' does * not begin with a period ("."), one will be added between the * filename and the suffix. * * When 'reldir_begin' is non-NULL, its value is set to the * beginning of the relative directory; i.e., to the character * following the '/' that ends the root-directory. * * When 'filename_begin' is non-NULL, its value is set to the * beginning of the filename; i.e., to the character following the * final '/'. */ char *sksiteGeneratePathname( char *buffer, size_t bufsize, flowtypeID_t flowtype, sensorID_t sensor, uint32_t timestamp, const char *suffix, char **reldir_begin, char **filename_begin); /* * Extract the flowtype, sensor, and timestamp from 'filename', the * name of a SiLK Flow file, and put the values into * 'out_flowtype', 'out_sensor' and 'out_timestamp', respectively. * Any of those values may be NULL, indicating that the caller does * not want the value returned. The function returns the flowtype, * or SK_INVALID_FLOWTYPE if the name is not in the proper form. * * The standard format for the name of a SiLK flow file is: * * flowtypeString-SensorName_YYYYMMDD.HH[.*] * * with a hyphen separating the flowtype from the sensor, and an * underscore separating the sensor from the time. The filename * may include a suffix made up of a period and additional text. * When 'filename' has a suffix and 'out_suffix' is non-NULL, * 'out_suffix' will be set to the location of the '.' in * 'filename' that begins the suffix. * * The function will take the basename of 'filename', so 'filename' * may include directory compontents. * */ flowtypeID_t sksiteParseFilename( flowtypeID_t *out_flowtype, sensorID_t *out_sensor, uint32_t *out_timestamp, char **out_suffix, const char *filename); /* * Extract the flowtype, sensor, and timestamp from 'filename', the * name of a SiLK Flow file, and use those values to fill in * 'buffer', a C-array of size 'bufsize', with the complete path of * the file having that flowtype, sensor, and timestamp. * * Returns a pointer to 'buffer' on success, or NULL if an error * occurs or if the filename is not of the expected form. * * When 'suffix' is NULL and 'filename' has a suffix, the path name * put into 'buffer' will have the same suffix appended. To have * the suffix removed from the new path, pass in the empty string * as the value of 'suffix'. * * The parameters 'buffer' and 'filename' may point to the same * location. * * This function works as a convenience wrapper around * sksiteParseFilename() and sksiteGeneratePathname(). See the * documentation of those functions for additional information. */ char *sksiteParseGeneratePath( char *buffer, size_t bufsize, const char *filename, const char *suffix, char **reldir_begin, char **filename_begin); /** Special Support Functions *****************************************/ /* * Treats 'string' as a comma separated list of tokens representing * the NAMES of sensors---does not accept sensor IDs. For each * token that is a valid sensor name, the function appends an * element to the 'out_sensors' vector, where the element holds the * sensorID_t that the token maps to. * * Returns the number of matches found, 0 for no matches, or -1 for * memory error, NULL input, or if the vector's element's size is * not sizeof(sensorID_t). * * If out_sensors is NULL, the function still returns the number of * matches found. */ int sksiteParseSensorList( sk_vector_t *out_sensors, const char *string); /* * Create a new sensor with the given sensor name, associated with * the given class names. Returns 0 on success. Returns -2 if the * sensor name is not legal, -3 if a sensor with that name already * exists, -1 if the input is otherwise invalid, and some other * negative number on other failures. A positive return value i * indicates that a non-existent class was found at position i-1 in * the class_names vector. */ int sksiteCreateTemporarySensor( const char *sensor_name, const sk_vector_t *class_names); #endif /* _SKSITE_H */ /* ** Local variables: ** mode:c ** indent-tabs-mode:nil ** c-basic-offset:4 ** End: */