Add a processing module that reads events from a text file
authorBenjamin Poirier <benjamin.poirier@polymtl.ca>
Fri, 27 Nov 2009 16:09:44 +0000 (11:09 -0500)
committerBenjamin Poirier <benjamin.poirier@polymtl.ca>
Fri, 18 Dec 2009 19:04:17 +0000 (14:04 -0500)
This is particularly useful for unitests.

Signed-off-by: Benjamin Poirier <benjamin.poirier@polymtl.ca>
lttv/lttv/sync/Makefile.am
lttv/lttv/sync/README
lttv/lttv/sync/event_analysis_chull.c
lttv/lttv/sync/event_processing_lttng_standard.c
lttv/lttv/sync/event_processing_text.c [new file with mode: 0644]
lttv/lttv/sync/event_processing_text.h [new file with mode: 0644]
lttv/lttv/sync/sync_chain_unittest.c

index cd08bb8d08bb4897e56b276b76b2b17a3646a291..a6b0b8a35b8a8e54e893573f0db9554d7dadfe7d 100644 (file)
@@ -8,6 +8,7 @@ unittest_SOURCES = \
        graph_functions.c\
        sync_chain.c\
        sync_chain_unittest.c\
+       event_processing_text.c\
        event_matching_broadcast.c\
        event_matching_distributor.c\
        event_matching_tcp.c\
index 4e6b19cea82af8e2c9b0091f4fb859201f01f20d..6e76ec17a24a62039d403ca7c295f7bd8ec9b546 100644 (file)
@@ -195,7 +195,7 @@ become relevant to have many modules at the same stage simultaneously. This
 will require some modifications. It is already partly supported at the
 matching stage through encapsulation of other matching modules.
 
-sync_chain_unitest provides a fairly simple example of sync chain
+sync_chain_unitest:main() provides a fairly simple example of sync chain
 implementation.
 
 ++ Stage 1: Event processing
index d0dd0a70c88839d5b4564966d982358859e8a788..e77eabd221048738aafb84f3dc0ee71a5c7bfb87 100644 (file)
@@ -927,7 +927,7 @@ void calculateFactorsMiddle(FactorsCHull* const factors)
        bmin= factors->min->drift;
        bmax= factors->max->drift;
 
-       g_assert_cmpfloat(bmax, >, bmin);
+       g_assert_cmpfloat(bmax, >=, bmin);
 
        factors->approx= malloc(sizeof(Factors));
        bhat= (bmax * bmin - 1. + sqrt(1. + pow(bmax, 2.) * pow(bmin, 2.) +
index e6457fa4e96a0278b6457834e5639b7b87928e36..76a754b82e2a9650109d86944a2220b67dde2cdf 100644 (file)
@@ -700,14 +700,6 @@ static void writeProcessingGraphVariablesLTTVStandard(SyncState* const
 static void writeProcessingTraceTraceOptionsLTTVStandard(SyncState* const
        syncState, const unsigned int i, const unsigned int j)
 {
-       ProcessingDataLTTVStandard* processingData;
-       ProcessingGraphsLTTVStandard* traceI, * traceJ;
-
-       processingData= (ProcessingDataLTTVStandard*) syncState->processingData;
-
-       traceI= &processingData->graphs[i];
-       traceJ= &processingData->graphs[j];
-
        fprintf(syncState->graphsStream,
         "set key inside right bottom\n"
         "set xlabel \"Clock %1$u\"\n"
@@ -734,14 +726,6 @@ static void writeProcessingTraceTraceOptionsLTTVStandard(SyncState* const
 static void writeProcessingTraceTimeOptionsLTTVStandard(SyncState* const
        syncState, const unsigned int i, const unsigned int j)
 {
-       ProcessingDataLTTVStandard* processingData;
-       ProcessingGraphsLTTVStandard* traceI, * traceJ;
-
-       processingData= (ProcessingDataLTTVStandard*) syncState->processingData;
-
-       traceI= &processingData->graphs[i];
-       traceJ= &processingData->graphs[j];
-
        fprintf(syncState->graphsStream,
         "set key inside right bottom\n"
         "set xlabel \"Clock %1$u\"\n"
diff --git a/lttv/lttv/sync/event_processing_text.c b/lttv/lttv/sync/event_processing_text.c
new file mode 100644 (file)
index 0000000..9dd5d30
--- /dev/null
@@ -0,0 +1,476 @@
+/* This file is part of the Linux Trace Toolkit viewer
+ * Copyright (C) 2009 Benjamin Poirier <benjamin.poirier@polymtl.ca>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation;
+ *
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#define NANOSECONDS_PER_SECOND 1000000000
+#define CPU_FREQ 1e9
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "sync_chain.h"
+
+#include "event_processing_text.h"
+
+
+// Functions common to all processing modules
+static void initProcessingText(SyncState* const syncState, ...);
+static void destroyProcessingText(SyncState* const syncState);
+static void finalizeProcessingText(SyncState* const syncState);
+static void printProcessingStatsText(SyncState* const syncState);
+static void writeProcessingTraceTimeOptionsText(SyncState* const syncState,
+       const unsigned int i, const unsigned int j);
+static void writeProcessingTraceTraceOptionsText(SyncState* const syncState,
+       const unsigned int i, const unsigned int j);
+static void writeProcessingGraphVariablesText(SyncState* const syncState,
+       const unsigned int i);
+
+// Functions specific to this module
+static void registerProcessingText() __attribute__((constructor (102)));
+
+static unsigned int readTraceNb(FILE* testCase);
+static void skipCommentLines(FILE* testCase);
+
+
+static ProcessingModule processingModuleText = {
+       .name= "text",
+       .initProcessing= &initProcessingText,
+       .destroyProcessing= &destroyProcessingText,
+       .finalizeProcessing= &finalizeProcessingText,
+       .printProcessingStats= &printProcessingStatsText,
+       .graphFunctions= {
+               .writeVariables= &writeProcessingGraphVariablesText,
+               .writeTraceTraceOptions= &writeProcessingTraceTraceOptionsText,
+               .writeTraceTimeOptions= &writeProcessingTraceTimeOptionsText,
+       },
+};
+
+
+/*
+ * Processing Module registering function
+ */
+static void registerProcessingText()
+{
+       g_queue_push_tail(&processingModules, &processingModuleText);
+}
+
+
+/*
+ * Allocate and initialize data structures for synchronizing a traceset.
+ * Open test case file.
+ *
+ * Args:
+ *   syncState:    container for synchronization data.
+ *   testCaseName: const char*, test case file name
+ */
+static void initProcessingText(SyncState* const syncState, ...)
+{
+       ProcessingDataText* processingData;
+       const char* testCaseName;
+       va_list ap;
+
+       processingData= malloc(sizeof(ProcessingDataText));
+       syncState->processingData= processingData;
+       va_start(ap, syncState);
+       testCaseName= va_arg(ap, const char*);
+       va_end(ap);
+
+       processingData->testCase= fopen(testCaseName, "r");
+       if (processingData->testCase == NULL)
+       {
+               g_error(strerror(errno));
+       }
+       syncState->traceNb= readTraceNb(processingData->testCase);
+
+       if (syncState->stats)
+       {
+               processingData->factors= NULL;
+       }
+}
+
+
+static void destroyProcessingText(SyncState* const syncState)
+{
+       ProcessingDataText* processingData= (ProcessingDataText*)
+               syncState->processingData;
+
+       if (processingData == NULL)
+       {
+               return;
+       }
+
+       if (syncState->stats && processingData->factors)
+       {
+               g_array_free(processingData->factors, TRUE);
+       }
+
+       free(syncState->processingData);
+       syncState->processingData= NULL;
+}
+
+
+/*
+ * Read the test case file and make up events. Dispatch those events to the
+ * matching module.
+ *
+ * Args:
+ *   syncState:    container for synchronization data.
+ */
+static void finalizeProcessingText(SyncState* const syncState)
+{
+       size_t len;
+       int retval;
+       unsigned int* seq;
+       GArray* factors;
+       ProcessingDataText* processingData= (ProcessingDataText*)
+               syncState->processingData;
+       FILE* testCase= processingData->testCase;
+       char* line= NULL;
+
+       seq= calloc(syncState->traceNb, sizeof(unsigned int));
+
+       skipCommentLines(testCase);
+       retval= getline(&line, &len, testCase);
+       while(!feof(testCase))
+       {
+               unsigned int sender, receiver;
+               double sendTime, recvTime;
+               char tmp;
+               unsigned int i;
+
+               if (retval == -1 && !feof(testCase))
+               {
+                       g_error(strerror(errno));
+               }
+
+               if (line[len - 1] == '\n')
+               {
+                       line[len - 1]= '\0';
+               }
+
+               retval= sscanf(line, " %u %u %lf %lf %c", &sender, &receiver,
+                       &sendTime, &recvTime, &tmp);
+               if (retval == EOF)
+               {
+                       g_error(strerror(errno));
+               }
+               else if (retval != 4)
+               {
+                       g_error("Error parsing test file while looking for data point, line was '%s'", line);
+               }
+
+               if (sender + 1 > syncState->traceNb)
+               {
+                       g_error("Error parsing test file, sender is out of range, line was '%s'", line);
+               }
+
+               if (receiver + 1 > syncState->traceNb)
+               {
+                       g_error("Error parsing test file, receiver is out of range, line was '%s'", line);
+               }
+
+               if (sendTime < 0)
+               {
+                       g_error("Error parsing test file, send time is negative, line was '%s'", line);
+               }
+
+               if (recvTime < 0)
+               {
+                       g_error("Error parsing test file, receive time is negative, line was '%s'", line);
+               }
+
+               // Generate ouput and input events
+               {
+                       unsigned int addressOffset;
+                       struct {
+                               unsigned int traceNum;
+                               double time;
+                               enum Direction direction;
+                       } loopValues[]= {
+                               {sender, sendTime, OUT},
+                               {receiver, recvTime, IN},
+                       };
+
+                       /* addressOffset is added to a traceNum to convert it to an address so
+                        * that the address is not plainly the same as the traceNb. */
+                       if (syncState->traceNb > 1)
+                       {
+                               addressOffset= pow(10, floor(log(syncState->traceNb - 1) /
+                                               log(10)) + 1);
+                       }
+                       else
+                       {
+                               addressOffset= 0;
+                       }
+
+                       for (i= 0; i < sizeof(loopValues) / sizeof(*loopValues); i++)
+                       {
+                               Event* event;
+
+                               event= malloc(sizeof(Event));
+                               event->traceNum= loopValues[i].traceNum;
+                               event->wallTime.seconds= floor(loopValues[i].time);
+                               event->wallTime.nanosec= floor((loopValues[i].time -
+                                               floor(loopValues[i].time)) * NANOSECONDS_PER_SECOND);
+                               event->cpuTime= round(loopValues[i].time * CPU_FREQ);
+                               event->type= TCP;
+                               event->destroy= &destroyTCPEvent;
+                               event->event.tcpEvent= malloc(sizeof(TCPEvent));
+                               event->event.tcpEvent->direction= loopValues[i].direction;
+                               event->event.tcpEvent->segmentKey= malloc(sizeof(SegmentKey));
+                               event->event.tcpEvent->segmentKey->ihl= 5;
+                               event->event.tcpEvent->segmentKey->tot_len= 40;
+                               event->event.tcpEvent->segmentKey->connectionKey.saddr= sender +
+                                       addressOffset;
+                               event->event.tcpEvent->segmentKey->connectionKey.daddr= receiver +
+                                       addressOffset;
+                               event->event.tcpEvent->segmentKey->connectionKey.source= 57645;
+                               event->event.tcpEvent->segmentKey->connectionKey.dest= 80;
+                               event->event.tcpEvent->segmentKey->seq= seq[sender];
+                               event->event.tcpEvent->segmentKey->ack_seq= 0;
+                               event->event.tcpEvent->segmentKey->doff= 5;
+                               event->event.tcpEvent->segmentKey->ack= 0;
+                               event->event.tcpEvent->segmentKey->rst= 0;
+                               event->event.tcpEvent->segmentKey->syn= 1;
+                               event->event.tcpEvent->segmentKey->fin= 0;
+
+                               syncState->matchingModule->matchEvent(syncState, event);
+                       }
+               }
+
+               seq[sender]++;
+
+               skipCommentLines(testCase);
+               retval= getline(&line, &len, testCase);
+       }
+
+       free(seq);
+
+       if (line)
+       {
+               free(line);
+       }
+
+       factors= syncState->matchingModule->finalizeMatching(syncState);
+       if (syncState->stats)
+       {
+               processingData->factors= factors;
+       }
+       else
+       {
+               g_array_free(factors, TRUE);
+       }
+}
+
+
+/*
+ * Print statistics related to processing. Must be called after
+ * finalizeProcessing.
+ *
+ * Args:
+ *   syncState     container for synchronization data.
+ */
+static void printProcessingStatsText(SyncState* const syncState)
+{
+       unsigned int i;
+
+       printf("Resulting synchronization factors:\n");
+       for (i= 0; i < syncState->traceNb; i++)
+       {
+               Factors* factors= &g_array_index(((ProcessingDataText*)
+                               syncState->processingData)->factors, Factors, i);
+
+               printf("\ttrace %u drift= %g offset= %g (%f)\n", i, factors->drift,
+                       factors->offset, factors->offset / CPU_FREQ);
+       }
+}
+
+
+/*
+ * Read trace number from the test case stream. The trace number should be the
+ * first non-comment line and should be an unsigned int by itself on a line.
+ *
+ * Args:
+ *   testCase:     test case stream
+ *
+ * Returns:
+ *   The trace number
+ */
+static unsigned int readTraceNb(FILE* testCase)
+{
+       unsigned int result;
+       int retval;
+       char* line= NULL;
+       size_t len;
+       char tmp;
+
+       skipCommentLines(testCase);
+       retval= getline(&line, &len, testCase);
+       if (retval == -1)
+       {
+               if (feof(testCase))
+               {
+                       g_error("Unexpected end of file while looking for number of traces");
+               }
+               else
+               {
+                       g_error(strerror(errno));
+               }
+       }
+       if (line[retval - 1] == '\n')
+       {
+               line[retval - 1]= '\0';
+       }
+
+       retval= sscanf(line, " %u %c", &result, &tmp);
+       if (retval == EOF || retval != 1)
+       {
+               g_error("Error parsing test file while looking for number of traces, line was '%s'", line);
+
+               // Not really needed but avoids warning from gcc
+               abort();
+       }
+
+       return result;
+}
+
+
+/*
+ * Advance testCase stream over empty space, empty lines and lines that begin
+ * with '#'
+ *
+ * Args:
+ *   testCase:     test case stream
+ */
+static void skipCommentLines(FILE* testCase)
+{
+       int firstChar;
+       ssize_t retval;
+       char* line= NULL;
+       size_t len;
+
+       do
+       {
+               firstChar= fgetc(testCase);
+               if (firstChar == (int) '#')
+               {
+                       retval= getline(&line, &len, testCase);
+                       if (retval == -1)
+                       {
+                               if (feof(testCase))
+                               {
+                                       goto outEof;
+                               }
+                               else
+                               {
+                                       g_error(strerror(errno));
+                               }
+                       }
+               }
+               else if (firstChar == (int) '\n' || firstChar == (int) ' ')
+               {}
+               else if (firstChar == EOF)
+               {
+                       goto outEof;
+               }
+               else
+               {
+                       break;
+               }
+       } while (true);
+       retval= ungetc(firstChar, testCase);
+       if (retval == EOF)
+       {
+               g_error("Error: ungetc()");
+       }
+
+outEof:
+       if (line)
+       {
+               free(line);
+       }
+}
+
+
+/*
+ * Write the processing-specific variables in the gnuplot script.
+ *
+ * Args:
+ *   syncState:    container for synchronization data
+ *   i:            trace number
+ */
+static void writeProcessingGraphVariablesText(SyncState* const syncState,
+       const unsigned int i)
+{
+       fprintf(syncState->graphsStream, "clock_freq_%u= %.3f\n", i, CPU_FREQ);
+}
+
+
+/*
+ * Write the processing-specific options in the gnuplot script.
+ *
+ * Args:
+ *   syncState:    container for synchronization data
+ *   i:            first trace number
+ *   j:            second trace number, garanteed to be larger than i
+ */
+static void writeProcessingTraceTraceOptionsText(SyncState* const syncState,
+       const unsigned int i, const unsigned int j)
+{
+       fprintf(syncState->graphsStream,
+        "set key inside right bottom\n"
+        "set xlabel \"Clock %1$u\"\n"
+        "set xtics nomirror\n"
+        "set ylabel \"Clock %2$u\"\n"
+        "set ytics nomirror\n"
+               "set x2label \"Clock %1$d (s)\"\n"
+               "set x2range [GPVAL_X_MIN / clock_freq_%1$u : GPVAL_X_MAX / clock_freq_%1$u]\n"
+               "set x2tics\n"
+               "set y2label \"Clock %2$d (s)\"\n"
+               "set y2range [GPVAL_Y_MIN / clock_freq_%2$u : GPVAL_Y_MAX / clock_freq_%2$u]\n"
+               "set y2tics\n", i, j);
+}
+
+
+/*
+ * Write the processing-specific options in the gnuplot script.
+ *
+ * Args:
+ *   syncState:    container for synchronization data
+ *   i:            first trace number
+ *   j:            second trace number, garanteed to be larger than i
+ */
+static void writeProcessingTraceTimeOptionsText(SyncState* const syncState,
+       const unsigned int i, const unsigned int j)
+{
+       fprintf(syncState->graphsStream,
+        "set key inside right bottom\n"
+        "set xlabel \"Clock %1$u\"\n"
+        "set xtics nomirror\n"
+        "set ylabel \"time (s)\"\n"
+        "set ytics nomirror\n"
+               "set x2label \"Clock %1$d (s)\"\n"
+               "set x2range [GPVAL_X_MIN / clock_freq_%1$u : GPVAL_X_MAX / clock_freq_%1$u]\n"
+               "set x2tics\n", i);
+}
diff --git a/lttv/lttv/sync/event_processing_text.h b/lttv/lttv/sync/event_processing_text.h
new file mode 100644 (file)
index 0000000..0fda778
--- /dev/null
@@ -0,0 +1,32 @@
+/* This file is part of the Linux Trace Toolkit viewer
+ * Copyright (C) 2009 Benjamin Poirier <benjamin.poirier@polymtl.ca>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation;
+ *
+ * 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., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#ifndef EVENT_PROCESSING_TEXT_H
+#define EVENT_PROCESSING_TEXT_H
+
+#include "event_processing.h"
+
+typedef struct
+{
+       // Factors factors[traceNb], only used for stats
+       GArray* factors;
+
+       FILE* testCase;
+} ProcessingDataText;
+
+#endif
index 3a6de5710e7a7e71ed3eb03d6a2ee2c0d8b23e1f..9b4b869c7f32bac3b622ac71a115e05050b7aaee 100644 (file)
@@ -42,7 +42,8 @@ struct OptionsInfo
 {
        GArray* longOptions;
        GString* optionString;
-       GQueue* index;
+       GQueue* longIndex;
+       GHashTable* shortIndex;
 };
 
 
@@ -52,6 +53,8 @@ static void gfPrintModuleOption(gpointer data, gpointer user_data);
 static void nullLog(const gchar *log_domain, GLogLevelFlags log_level, const
        gchar *message, gpointer user_data);
 static void gfAddModuleOption(gpointer data, gpointer user_data);
+static guint ghfCharHash(gconstpointer key);
+static gboolean gefCharEqual(gconstpointer a, gconstpointer b);
 
 
 static ModuleOption optionSyncStats= {
@@ -244,34 +247,34 @@ const char* processOptions(const int argc, char* const argv[])
        extern int optind, opterr, optopt;
        GArray* longOptions;
        GString* optionString;
-       GQueue* index;
+       GQueue* longIndex;
+       int longOption;
+       GHashTable* shortIndex;
 
        longOptions= g_array_sized_new(TRUE, FALSE, sizeof(struct option),
                g_queue_get_length(&moduleOptions));
        optionString= g_string_new("");
-       index= g_queue_new();
+       longIndex= g_queue_new();
+       shortIndex= g_hash_table_new(&ghfCharHash, &gefCharEqual);
 
        g_queue_foreach(&moduleOptions, &gfAddModuleOption, &(struct OptionsInfo)
-               {longOptions, optionString, index});
+               {longOptions, optionString, longIndex, shortIndex});
 
        do
        {
                int optionIndex= 0;
+               ModuleOption* moduleOption;
 
+               longOption= -1;
                c= getopt_long(argc, argv, optionString->str, (struct option*)
                        longOptions->data, &optionIndex);
 
-               if (c >= 0 && c < g_queue_get_length(index))
+               if (longOption >= 0 && longOption < g_queue_get_length(longIndex))
+               {
+                       moduleOption= g_queue_peek_nth(longIndex, longOption);
+               }
+               else if ((moduleOption= g_hash_table_lookup(shortIndex, &c)) != NULL)
                {
-                       ModuleOption* moduleOption= g_queue_peek_nth(index, c);
-
-                       moduleOption->present= true;
-
-                       if (moduleOption->hasArg == REQUIRED_ARG || moduleOption->hasArg
-                               == OPTIONAL_ARG)
-                       {
-                               moduleOption->arg= optarg;
-                       }
                }
                else if (c == -1)
                {
@@ -286,10 +289,23 @@ const char* processOptions(const int argc, char* const argv[])
                {
                        g_error("Option parse error");
                }
+
+               moduleOption->present= true;
+
+               if (moduleOption->hasArg == REQUIRED_ARG)
+               {
+                       moduleOption->arg= optarg;
+               }
+               if (moduleOption->hasArg == OPTIONAL_ARG && optarg)
+               {
+                       moduleOption->arg= optarg;
+               }
        } while (c != -1);
 
        g_array_free(longOptions, TRUE);
        g_string_free(optionString, TRUE);
+       g_queue_free(longIndex);
+       g_hash_table_destroy(shortIndex);
 
        if (argc <= optind)
        {
@@ -403,9 +419,50 @@ static void gfAddModuleOption(gpointer data, gpointer user_data)
        newOption.name= option->longName;
        newOption.has_arg= conversion[option->hasArg];
        newOption.flag= NULL;
-       newOption.val= g_queue_get_length(optionsInfo->index);
+       newOption.val= g_queue_get_length(optionsInfo->longIndex);
 
        g_array_append_val(optionsInfo->longOptions, newOption);
-       g_string_append(optionsInfo->optionString, colons[option->hasArg]);
-       g_queue_push_tail(optionsInfo->index, option);
+       if (option->shortName)
+       {
+               g_string_append_c(optionsInfo->optionString, option->shortName);
+               g_string_append(optionsInfo->optionString, colons[option->hasArg]);
+
+               g_hash_table_insert(optionsInfo->shortIndex, &option->shortName,
+                       option);
+       }
+       g_queue_push_tail(optionsInfo->longIndex, option);
+}
+
+
+/*
+ * A GHashFunc for g_hash_table_new()
+ *
+ * Args:
+ *    key        char*, just one character
+ */
+static guint ghfCharHash(gconstpointer key)
+{
+    return *(char*) key;
+}
+
+
+/*
+ * A GEqualFunc for g_hash_table_new()
+ *
+ * Args:
+ *   a, b          char*, just one character each
+ *
+ * Returns:
+ *   TRUE if both values are equal
+ */
+static gboolean gefCharEqual(gconstpointer a, gconstpointer b)
+{
+       if (*(char*) a == *(char*) b)
+       {
+               return TRUE;
+       }
+       else
+       {
+               return FALSE;
+       }
 }
This page took 0.032121 seconds and 4 git commands to generate.