Changeset 6a18893


Ignore:
Timestamp:
06/23/06 17:37:46 (14 years ago)
Author:
Perry Lorier <perry@…>
Branches:
4.0.1-hotfixes, cachetimestamps, develop, dpdk-ndag, etsilive, getfragoff, help, libtrace4, master, ndag_format, pfring, rc-4.0.1, rc-4.0.2, rc-4.0.3, rc-4.0.4, ringdecrementfix, ringperformance, ringtimestampfixes
Children:
870cad4
Parents:
362c5f2
Message:

Completely change the build system of the protocol file parser to try and
avoid issues on freebsd

Location:
libpacketdump/parser
Files:
1 added
5 edited
1 moved

Legend:

Unmodified
Added
Removed
  • libpacketdump/parser/Makefile.am

    r0404088 r6a18893  
    22#include_HEADERS = parser.h
    33
    4 #libparser_la_SOURCES = libconfig.c libconfig.h config_internal.h config.lexer.c config.parser.h configuration.h config.parser.c
    5 libparser_la_SOURCES = parser.lexer.c parser.tab.c parser.h bitbuffer.c bitbuffer.h
     4libparser_la_SOURCES = lexer.l parser.y bitbuffer.c bitbuffer.h grammer.h
    65libparser_la_LIBADD = @ADD_LIBS@ @LEXLIB@
    76libparser_la_LDFLAGS = @ADD_LDFLAGS@ -L../
    87INCLUDES = @ADD_INCLS@ -I../ -I../../lib
    98
    10 EXTRA_DIST= parser.l parser.y
    11 BUILT_SOURCES=parser.tab.h
     9EXTRA_DIST=lexer.l parser.y
     10BUILT_SOURCES=parser.h
     11AM_YFLAGS=-d
    1212
    13 
    14 %.tab.c %.tab.h: %.y
    15         @YACC@ -d -o$@ $<
    16 
    17 %.lexer.c: %.l %.tab.c
    18         @LEX@ -o$@ $<
  • libpacketdump/parser/bitbuffer.c

    r701e164 r6a18893  
     1#include "bitbuffer.h"
    12#include <inttypes.h>
    23#include "parser.h"
    34#include <stdio.h>
     5#include <netinet/in.h>
     6#include <assert.h>
     7#include <stdlib.h>
     8#include <arpa/inet.h>
     9#include "../libpacketdump.h"
    410
    511uint16_t bits;
     
    713bitbuffer_t buffer;
    814
    9 bitbuffer_t getbit(void **packet, int *packlen, uint64_t numbits)
     15static bitbuffer_t getbit(void **packet, int *packlen, uint64_t numbits)
    1016{
    1117    bitbuffer_t ret;
     
    4450}
    4551
     52element_t* parse_protocol_file(char *filename)
     53{
     54    /* hold onto this so we can put it in any error messages */
     55    file = filename;
     56
     57    /* if the protocol file doesn't exist, we return null and
     58     * it will fall back to using the generic_decode function
     59     */
     60    yyin = fopen(filename, "r");
     61    if(!yyin)
     62        return NULL;
     63
     64    el_list = NULL;
     65    lines = 1;
     66
     67    yyparse();
     68    fclose(yyin);
     69    return el_list;
     70}
     71
     72
     73static bitbuffer_t fix_byteorder(bitbuffer_t value,
     74                enum byte_order_t order, uint64_t size)
     75{
     76    bitbuffer_t one = 1;
     77    bitbuffer_t lhs;
     78    bitbuffer_t rhs;;
     79
     80    /*
     81     * XXX trial and error seems to show these numbers to work.
     82     * I've tried fields of length 1,2,3,4,8,13,16,32 and they seem to work.
     83     * Others are untested...
     84     */
     85    switch(order)
     86    {
     87        case BIGENDIAN:
     88            if(size < 16)
     89                return value;
     90            if(size < 32)
     91                return ntohs(value);
     92            if(size <= 32)
     93                return ntohl(value);
     94           
     95            lhs = ntohl(value& ((one<<32)-1));
     96            rhs = ntohl(value >> 32);
     97            return ((lhs<<32) | rhs);
     98
     99        case LITTLEENDIAN:
     100            return value;
     101
     102    };
     103
     104    /* should never get here */
     105    assert(0);
     106    return 0;
     107}
     108
     109
     110
     111void decode_protocol_file(uint16_t link_type,char *packet,int len,element_t *el)
     112{
     113    bitbuffer_t result;
     114
     115    while(el != NULL)
     116    {
     117        switch(el->type)
     118        {
     119            case FIELD:
     120                if (len*8+bits<el->data->field->size) {
     121                        printf(" [Truncated]\n");
     122                        return;
     123                }
     124                result = getbit((void*)&packet, &len, el->data->field->size);
     125
     126                switch(el->data->field->display)
     127                {
     128                    /* integers get byteswapped if needed and displayed */
     129                    case DISPLAY_INT:
     130                    {
     131                        result = fix_byteorder(result,
     132                                el->data->field->order,
     133                                el->data->field->size);
     134                               
     135                        el->data->field->value = result;
     136                        printf(" %s %lld\n",
     137                                el->data->field->identifier,
     138                                result);
     139                    }
     140                    break;
     141
     142                    /*
     143                     * hex numbers get byteswapped if needed and displayed
     144                     * without being padded with zeroes
     145                     */
     146                    case DISPLAY_HEX:
     147                    {
     148                        result = fix_byteorder(result,
     149                                el->data->field->order,
     150                                el->data->field->size);
     151                       
     152                        el->data->field->value = result;
     153                        printf(" %s 0x%llx\n",
     154                                el->data->field->identifier,
     155                                result);
     156                    }
     157                    break;
     158                   
     159                    /*
     160                     * ipv4 addresses stay in network byte order and are
     161                     * given to inet_ntoa() to deal with
     162                     */
     163                    case DISPLAY_IPV4:
     164                    {
     165                        /* assumes all ipv4 addresses are 32bit fields */
     166                        struct in_addr address;
     167                        address.s_addr = (uint32_t)result;
     168                        el->data->field->value = result;
     169                   
     170                        printf(" %s %s\n",
     171                                el->data->field->identifier,
     172                                inet_ntoa(address));
     173                    }
     174                    break;
     175
     176                    /*
     177                     * mac addresses stay in network byte order and are
     178                     * displayed byte by byte with zero padding
     179                     */
     180                    case DISPLAY_MAC:
     181                    {
     182                        /* assumes all mac addresses are 48bit fields */
     183                        uint8_t *ptr = (uint8_t*)&result;
     184                        el->data->field->value = result;
     185                        printf(" %s %02x:%02x:%02x:%02x:%02x:%02x\n",
     186                                el->data->field->identifier,
     187                                ptr[0], ptr[1], ptr[2],
     188                                ptr[3], ptr[4], ptr[5]);
     189                    }
     190                    break;
     191                   
     192                    /*
     193                     * Flag values are only displayed if their value is true
     194                     * otherwise they are ignored
     195                     */
     196                    case DISPLAY_FLAG:
     197                    {
     198                        el->data->field->value = result;
     199                        if(result)
     200                            printf(" %s\n", el->data->field->identifier);
     201                    }
     202                    break;
     203
     204                    /*
     205                     * Hidden values are not displayed at all. This is useful
     206                     * for reserved fields or information that you don't care
     207                     * about but need to read in order to get to the rest of
     208                     * the header
     209                     */
     210                    case DISPLAY_NONE:
     211                    {
     212                        result = fix_byteorder(result,
     213                                el->data->field->order,
     214                                el->data->field->size);
     215                        el->data->field->value = result;
     216                    }
     217                    break;
     218                };
     219
     220                break;
     221
     222            case NEXTHEADER:
     223                /*
     224                 * Before we move on to the next header, make sure our packet
     225                 * pointer is pointing to the first unused bytes. This may
     226                 * mean we have to backtrack to some that were put into the
     227                 * buffer but weren't used.
     228                 * - This wouldn't be a problem if all future output came
     229                 * from this buffer, but there is a good chance we will use
     230                 * some code from a shared library to output packet info
     231                 * instead, and this doesn't have access to the buffer.
     232                 */
     233                packet = packet - (bits / 8);
     234                len = len + (bits / 8);
     235                bits = 0;
     236                buffer = 0;
     237
     238                decode_next(packet, len, el->data->nextheader->prefix,
     239                        ntohs(el->data->nextheader->target->value));
     240                break;
     241        };
     242       
     243        el = el->next;
     244    }
     245    buffer = 0;
     246    bits = 0;
     247
     248}
     249
     250
     251
     252
     253
     254
     255
     256
     257int yyerror(char *s)
     258{
     259    element_t *tmp;
     260   
     261    fprintf(stderr, "XXX %s\n"
     262                    "XXX %s on line %d\n"
     263                    "XXX Falling back to generic_decode()\n",
     264                    file, s, lines);
     265    /*
     266     * Clear the list so we don't do partial matching...makes it a bit
     267     * more obvious that something is broken perhaps.
     268     * XXX Not sure if it is better to parse none of the packet, or part
     269     * of the packet in the event of error? Feel free to remove this if
     270     * that is desired.
     271     */
     272
     273    while(el_list != NULL)
     274    {
     275        tmp = el_list;
     276        el_list = el_list->next;
     277
     278        switch(tmp->type)
     279        {
     280            case FIELD: free(tmp->data->field); break;
     281            case NEXTHEADER: free(tmp->data->nextheader); break;
     282        }
     283        free(tmp->data);       
     284        free(tmp);
     285        printf("deleting...\n");
     286    }
     287
     288    return 0;
     289}
     290
     291/*
     292 * Could be shortcut with a pointer to the tail...
     293 */
     294element_t* append(element_t *list, element_t *item)
     295{
     296    if(list == NULL)
     297        return item;
     298
     299    list->next = append(list->next, item);
     300    return list;
     301}
     302/*
     303 * Testing...
     304 */
     305void print_list(element_t *list)
     306{
     307    if(list == NULL)
     308        return;
     309       
     310    switch(list->type)
     311    {
     312        case NEXTHEADER: printf("*Nextheader, prefix='%s', target='%s'\n",
     313                            list->data->nextheader->prefix,
     314                            list->data->nextheader->fieldname);
     315                            break;
     316       
     317        case FIELD: printf("*Field, order = '%d', size = '%d', "
     318                            "display='%d', name='%s'\n",
     319                            list->data->field->order,
     320                            list->data->field->size,
     321                            list->data->field->display,
     322                            list->data->field->identifier);
     323                            break;
     324    };
     325    /*printf("%s\n", list->data->identifier); */
     326    print_list(list->next);
     327}
    46328#ifdef TEST
    47329#include <stdio.h>
  • libpacketdump/parser/bitbuffer.h

    r701e164 r6a18893  
    1 bitbuffer_t getbit(void **packet, int *packlen, uint64_t numbits);
     1#include "grammer.h"
     2#include <stdio.h>
     3int yyerror(char *s);
     4element_t *append(element_t *list, element_t *item);
     5void print_list(element_t *list);
     6extern char *file;
     7extern FILE* yyin;
     8extern char* yytext;
     9extern int lines;
     10extern element_t *el_list;
     11
  • libpacketdump/parser/lexer.l

    r14b2010 r6a18893  
    55%{
    66    #include <stdio.h>
     7    #include "grammer.h"
    78    #include "parser.h"
    8     #include "parser.tab.h"
    99
    1010    int yylex(void);
  • libpacketdump/parser/parser.h

    r41ab052 r6a18893  
    1 #ifndef _PARSER_H
    2 #define _PARSER_H
     1/* A Bison parser, made by GNU Bison 1.875d.  */
    32
    4 #include <stdint.h>
     3/* Skeleton parser for Yacc-like parsing with Bison,
     4   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
    55
    6 enum node_type_t {
    7     NEXTHEADER,
    8     FIELD
    9 };
     6   This program is free software; you can redistribute it and/or modify
     7   it under the terms of the GNU General Public License as published by
     8   the Free Software Foundation; either version 2, or (at your option)
     9   any later version.
    1010
    11 enum byte_order_t {
    12     BIGENDIAN,
    13     LITTLEENDIAN
    14 };
     11   This program is distributed in the hope that it will be useful,
     12   but WITHOUT ANY WARRANTY; without even the implied warranty of
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14   GNU General Public License for more details.
    1515
    16 enum display_t {
    17     DISPLAY_NONE,
    18     DISPLAY_HEX,
    19     DISPLAY_INT,
    20     DISPLAY_IPV4,
    21     DISPLAY_MAC,
    22     DISPLAY_FLAG
    23 };
     16   You should have received a copy of the GNU General Public License
     17   along with this program; if not, write to the Free Software
     18   Foundation, Inc., 59 Temple Place - Suite 330,
     19   Boston, MA 02111-1307, USA.  */
    2420
    25 /* This is more complicated that I feel it needs to be... */
     21/* As a special exception, when this file is copied by Bison into a
     22   Bison output file, you may use that output file without restriction.
     23   This special exception was added by the Free Software Foundation
     24   in version 1.24 of Bison.  */
    2625
    27 typedef struct next {
    28     char *prefix;                   /* search prefix for nextheader file */
    29     char *fieldname;                /* name of the field whose value we use */
    30     struct field *target;           /* link to the field whose value we use */
    31 } next_t;
    32    
    33 typedef struct field {
    34     enum byte_order_t order;        /* byte order of field */
    35     uint16_t size;                  /* size of the field in bits */
    36     enum display_t display;         /* how the data should be displayed */
    37     char *identifier;               /* display prefix + field identifier */
    38     uint64_t value;                 /* calculated value for this field */
    39 } field_t;
     26/* Tokens.  */
     27#ifndef YYTOKENTYPE
     28# define YYTOKENTYPE
     29   /* Put the tokens into the symbol table, so that GDB and other debuggers
     30      know about them.  */
     31   enum yytokentype {
     32     TOK_BIGENDIAN = 258,
     33     TOK_LITTLEENDIAN = 259,
     34     TOK_NEXT = 260,
     35     TOK_OUTPUT_INT = 261,
     36     TOK_OUTPUT_HEX = 262,
     37     TOK_OUTPUT_IPV4 = 263,
     38     TOK_OUTPUT_FLAG = 264,
     39     TOK_CONSTANT = 265,
     40     TOK_IDENTIFIER = 266,
     41     TOK_OUTPUT_MAC = 267,
     42     TOK_OUTPUT_NONE = 268
     43   };
     44#endif
     45#define TOK_BIGENDIAN 258
     46#define TOK_LITTLEENDIAN 259
     47#define TOK_NEXT 260
     48#define TOK_OUTPUT_INT 261
     49#define TOK_OUTPUT_HEX 262
     50#define TOK_OUTPUT_IPV4 263
     51#define TOK_OUTPUT_FLAG 264
     52#define TOK_CONSTANT 265
     53#define TOK_IDENTIFIER 266
     54#define TOK_OUTPUT_MAC 267
     55#define TOK_OUTPUT_NONE 268
    4056
    41 typedef union node {
    42     field_t *field;
    43     next_t *nextheader;
    44 } node_t;
    4557
    46 typedef struct element {
    47     enum node_type_t type;
    48     struct element *next;
    49     node_t *data;
    50 } element_t;
    51    
    52 element_t *parse_protocol_file(char *filename);
    53 void decode_protocol_file(uint16_t link_type,char *packet,int len, element_t* el);
    5458
    55 typedef uint64_t bitbuffer_t;
     59
     60#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED)
     61#line 28 "parser.y"
     62typedef union YYSTYPE {
     63    int intval;
     64    char *textval;
     65    element_t *ptr;
     66} YYSTYPE;
     67/* Line 1285 of yacc.c.  */
     68#line 69 "parser.h"
     69# define yystype YYSTYPE /* obsolescent; will be withdrawn */
     70# define YYSTYPE_IS_DECLARED 1
     71# define YYSTYPE_IS_TRIVIAL 1
    5672#endif
     73
     74extern YYSTYPE yylval;
     75
     76
     77
  • libpacketdump/parser/parser.y

    r41ab052 r6a18893  
    77    #include <arpa/inet.h>
    88    #include <assert.h>
     9    #include "grammer.h"
    910    #include "parser.h"
    1011    #include "libpacketdump.h"
     12    #include "bitbuffer.h"
    1113
    1214    #define YYERROR_VERBOSE 1
     
    1416   
    1517    int yylex(void);
    16     int yyerror(char *s);
    17     element_t *append(element_t *list, element_t *item);
    18     void print_list(element_t *list);
    1918
    20     extern FILE* yyin;
    21     extern char* yytext;
    22     extern int lines;
    2319    char *file;
    2420    element_t *el_list = NULL;
     
    149145%%
    150146
    151 #include "bitbuffer.h"
    152 
    153 element_t* parse_protocol_file(char *filename)
    154 {
    155     /* hold onto this so we can put it in any error messages */
    156     file = filename;
    157 
    158     /* if the protocol file doesn't exist, we return null and
    159      * it will fall back to using the generic_decode function
    160      */
    161     yyin = fopen(filename, "r");
    162     if(!yyin)
    163         return NULL;
    164 
    165     el_list = NULL;
    166     lines = 1;
    167 
    168     yyparse();
    169     fclose(yyin);
    170     return el_list;
    171 }
    172 
    173 
    174 
    175 
    176 
    177 bitbuffer_t fix_byteorder(bitbuffer_t value, enum byte_order_t order, uint64_t size)
    178 {
    179     bitbuffer_t one = 1;
    180     bitbuffer_t lhs;
    181     bitbuffer_t rhs;;
    182 
    183     /*
    184      * XXX trial and error seems to show these numbers to work.
    185      * I've tried fields of length 1,2,3,4,8,13,16,32 and they seem to work.
    186      * Others are untested...
    187      */
    188     switch(order)
    189     {
    190         case BIGENDIAN:
    191             if(size < 16)
    192                 return value;
    193             if(size < 32)
    194                 return ntohs(value);
    195             if(size <= 32)
    196                 return ntohl(value);
    197            
    198             lhs = ntohl(value& ((one<<32)-1));
    199             rhs = ntohl(value >> 32);
    200             return ((lhs<<32) | rhs);
    201 
    202         case LITTLEENDIAN:
    203             return value;
    204 
    205     };
    206 
    207     /* should never get here */
    208     assert(0);
    209     return 0;
    210 }
    211 
    212 
    213 
    214 void decode_protocol_file(uint16_t link_type,char *packet,int len,element_t *el)
    215 {
    216     bitbuffer_t result;
    217 
    218     while(el != NULL)
    219     {
    220         switch(el->type)
    221         {
    222             case FIELD:
    223                 if (len*8+bits<el->data->field->size) {
    224                         printf(" [Truncated]\n");
    225                         return;
    226                 }
    227                 result = getbit((void*)&packet, &len, el->data->field->size);
    228 
    229                 switch(el->data->field->display)
    230                 {
    231                     /* integers get byteswapped if needed and displayed */
    232                     case DISPLAY_INT:
    233                     {
    234                         result = fix_byteorder(result,
    235                                 el->data->field->order,
    236                                 el->data->field->size);
    237                                
    238                         el->data->field->value = result;
    239                         printf(" %s %lld\n",
    240                                 el->data->field->identifier,
    241                                 result);
    242                     }
    243                     break;
    244 
    245                     /*
    246                      * hex numbers get byteswapped if needed and displayed
    247                      * without being padded with zeroes
    248                      */
    249                     case DISPLAY_HEX:
    250                     {
    251                         result = fix_byteorder(result,
    252                                 el->data->field->order,
    253                                 el->data->field->size);
    254                        
    255                         el->data->field->value = result;
    256                         printf(" %s 0x%llx\n",
    257                                 el->data->field->identifier,
    258                                 result);
    259                     }
    260                     break;
    261                    
    262                     /*
    263                      * ipv4 addresses stay in network byte order and are
    264                      * given to inet_ntoa() to deal with
    265                      */
    266                     case DISPLAY_IPV4:
    267                     {
    268                         /* assumes all ipv4 addresses are 32bit fields */
    269                         struct in_addr address;
    270                         address.s_addr = (uint32_t)result;
    271                         el->data->field->value = result;
    272                    
    273                         printf(" %s %s\n",
    274                                 el->data->field->identifier,
    275                                 inet_ntoa(address));
    276                     }
    277                     break;
    278 
    279                     /*
    280                      * mac addresses stay in network byte order and are
    281                      * displayed byte by byte with zero padding
    282                      */
    283                     case DISPLAY_MAC:
    284                     {
    285                         /* assumes all mac addresses are 48bit fields */
    286                         uint8_t *ptr = (uint8_t*)&result;
    287                         el->data->field->value = result;
    288                         printf(" %s %02x:%02x:%02x:%02x:%02x:%02x\n",
    289                                 el->data->field->identifier,
    290                                 ptr[0], ptr[1], ptr[2],
    291                                 ptr[3], ptr[4], ptr[5]);
    292                     }
    293                     break;
    294                    
    295                     /*
    296                      * Flag values are only displayed if their value is true
    297                      * otherwise they are ignored
    298                      */
    299                     case DISPLAY_FLAG:
    300                     {
    301                         el->data->field->value = result;
    302                         if(result)
    303                             printf(" %s\n", el->data->field->identifier);
    304                     }
    305                     break;
    306 
    307                     /*
    308                      * Hidden values are not displayed at all. This is useful
    309                      * for reserved fields or information that you don't care
    310                      * about but need to read in order to get to the rest of
    311                      * the header
    312                      */
    313                     case DISPLAY_NONE:
    314                     {
    315                         result = fix_byteorder(result,
    316                                 el->data->field->order,
    317                                 el->data->field->size);
    318                         el->data->field->value = result;
    319                     }
    320                     break;
    321                 };
    322 
    323                 break;
    324 
    325             case NEXTHEADER:
    326                 /*
    327                  * Before we move on to the next header, make sure our packet
    328                  * pointer is pointing to the first unused bytes. This may
    329                  * mean we have to backtrack to some that were put into the
    330                  * buffer but weren't used.
    331                  * - This wouldn't be a problem if all future output came
    332                  * from this buffer, but there is a good chance we will use
    333                  * some code from a shared library to output packet info
    334                  * instead, and this doesn't have access to the buffer.
    335                  */
    336                 packet = packet - (bits / 8);
    337                 len = len + (bits / 8);
    338                 bits = 0;
    339                 buffer = 0;
    340 
    341                 decode_next(packet, len, el->data->nextheader->prefix,
    342                         ntohs(el->data->nextheader->target->value));
    343                 break;
    344         };
    345        
    346         el = el->next;
    347     }
    348     buffer = 0;
    349     bits = 0;
    350 
    351 }
    352 
    353 
    354 
    355 
    356 
    357 
    358 
    359 
    360 int yyerror(char *s)
    361 {
    362     element_t *tmp;
    363    
    364     fprintf(stderr, "XXX %s\n"
    365                     "XXX %s on line %d\n"
    366                     "XXX Falling back to generic_decode()\n",
    367                     file, s, lines);
    368     /*
    369      * Clear the list so we don't do partial matching...makes it a bit
    370      * more obvious that something is broken perhaps.
    371      * XXX Not sure if it is better to parse none of the packet, or part
    372      * of the packet in the event of error? Feel free to remove this if
    373      * that is desired.
    374      */
    375 
    376     while(el_list != NULL)
    377     {
    378         tmp = el_list;
    379         el_list = el_list->next;
    380 
    381         switch(tmp->type)
    382         {
    383             case FIELD: free(tmp->data->field); break;
    384             case NEXTHEADER: free(tmp->data->nextheader); break;
    385         }
    386         free(tmp->data);       
    387         free(tmp);
    388         printf("deleting...\n");
    389     }
    390 
    391     return 0;
    392 }
    393 
    394 /*
    395  * Could be shortcut with a pointer to the tail...
    396  */
    397 element_t* append(element_t *list, element_t *item)
    398 {
    399     if(list == NULL)
    400         return item;
    401 
    402     list->next = append(list->next, item);
    403     return list;
    404 }
    405 /*
    406  * Testing...
    407  */
    408 void print_list(element_t *list)
    409 {
    410     if(list == NULL)
    411         return;
    412        
    413     switch(list->type)
    414     {
    415         case NEXTHEADER: printf("*Nextheader, prefix='%s', target='%s'\n",
    416                             list->data->nextheader->prefix,
    417                             list->data->nextheader->fieldname);
    418                             break;
    419        
    420         case FIELD: printf("*Field, order = '%d', size = '%d', "
    421                             "display='%d', name='%s'\n",
    422                             list->data->field->order,
    423                             list->data->field->size,
    424                             list->data->field->display,
    425                             list->data->field->identifier);
    426                             break;
    427     };
    428     /*printf("%s\n", list->data->identifier); */
    429     print_list(list->next);
    430 }
Note: See TracChangeset for help on using the changeset viewer.