## modules/rpsl/syntax_api.c

/* [<][>][^][v][top][bottom][index][help] */

## FUNCTIONS

This source file includes following functions.

1. chk_obj
2. chk_attr
3. chk_err
4. chk_obj
5. chk_attr
6. chk_err
7. is_rpsl_line_cont
8. my_g_str_hash
9. my_g_strcasecmp
10. attribute_clean
11. str_ends_with
12. generic_list_split
13. attribute_list_split
14. ripe_list_split
15. rpsl_error_assign
16. rpsl_error_add
17. rpsl_attr_syntax_check
18. rpsl_attr_init
19. rpsl_error_copy
20. rpsl_attr_copy
21. rpsl_attr_clean_copy
22. rpsl_attr_delete
23. rpsl_attr_delete_list
24. rpsl_attr_get_name
25. rpsl_attr_get_ofs
26. rpsl_attr_get_value
27. rpsl_attr_get_clean_value
28. rpsl_attr_get_split_list
29. rpsl_attr_replace_value
30. rpsl_attr_errors
31. object_is_comment
32. template_check_needed
33. renumber_attr
34. rpsl_empty_attr
35. rpsl_object_init
36. rpsl_object_copy
37. rpsl_object_copy_flattened
38. rpsl_object_delete_helper
39. rpsl_object_delete
40. rpsl_object_get_class
41. next_tabstop
42. separate_leading_whitespace
43. whitespace_length
44. remove_columns
45. add_aligned_val
46. rpsl_object_get_text
47. rpsl_object_get_num_attr
48. rpsl_object_get_all_attr
49. rpsl_object_get_attr
50. rpsl_object_get_attr_by_ofs
51. add_attr_to_object
52. rpsl_object_append_attr
53. rpsl_object_add_attr
54. rpsl_object_remove_attr
55. rpsl_object_remove_attr_name
56. rpsl_object_errors
57. rpsl_attr_is_required
58. rpsl_attr_is_generated
59. rpsl_attr_is_multivalued
60. rpsl_attr_is_lookup
61. rpsl_attr_is_key
62. rpsl_object_is_deleted
63. search_errors
64. rpsl_attr_has_error
65. rpsl_object_has_error
66. rpsl_get_attr_id
67. rpsl_get_class_id
68. rpsl_load_dictionary
69. rpsl_read_dictionary
70. rpsl_error_check
71. rpsl_attr_check
72. count_attr_in_list
73. rpsl_object_check
74. main

       1 /******************
       2   Copyright (c) 2002                                        RIPE NCC
       3
       4   All Rights Reserved
       5
       6   Permission to use, copy, modify, and distribute this software and its
       7   documentation for any purpose and without fee is hereby granted,
       8   provided that the above copyright notice appear in all copies and that
       9   both that copyright notice and this permission notice appear in
      10   supporting documentation, and that the name of the author not be
      11   used in advertising or publicity pertaining to distribution of the
      12   software without specific, written prior permission.
      13
      14   THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
      15   ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL
      16   AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
      17   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
      18   AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
      19   OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
      20   ***************************************/
      21
      22 #include <ctype.h>
      23 #include <string.h>
      24 #include <stdlib.h>
      25 #include <stdarg.h>
      26 #include <glib.h>
      27 #include "syntax_api.h"
      28 #include "syntax.h"
      29 #include "attribute.h"
      30 #include "class.h"
      31
      32 #include <assert.h>
      33
      34 #define RUNTIME_CHECK 0
      35
      36 #if RUNTIME_CHECK
      37 #include <stdio.h>
      38 #define chk_obj(o)  rpsl_object_check((o),__FILE__,__LINE__)
         /* [<][>][^][v][top][bottom][index][help] */
      39 #define chk_attr(a) rpsl_attr_check((a),__FILE__,__LINE__)
         /* [<][>][^][v][top][bottom][index][help] */
      40 #define chk_err(e)  rpsl_error_check((e),__FILE__,__LINE__)
         /* [<][>][^][v][top][bottom][index][help] */
      41
      42 void rpsl_error_check(const GList *errors, const char *file, int line);
      43 void rpsl_attr_check(const rpsl_attr_t *attr, const char *file, int line);
      44 void rpsl_object_check(const rpsl_object_t *obj, const char *file, int line);
      45 #else
      46 #define chk_obj(o)
         /* [<][>][^][v][top][bottom][index][help] */
      47 #define chk_attr(a)
         /* [<][>][^][v][top][bottom][index][help] */
      48 #define chk_err(e)
         /* [<][>][^][v][top][bottom][index][help] */
      49 #endif /* RUNTIME_CHECK */
      50
      51 /* type of check currently in place */
      52 static int rpsl_level = RPSL_DICT_FRONT_END;
      53
      54 /* return true if the character is an RPSL line-continuation */
      55 #define is_rpsl_line_cont(c) (((c)==' ') || ((c) == '\t')  || ((c) == '+'))
         /* [<][>][^][v][top][bottom][index][help] */
      56
      57 /* needed by hash table */
      58 static guint
      59 my_g_str_hash (gconstpointer v)
         /* [<][>][^][v][top][bottom][index][help] */
      60 {
      61     gchar *s;
      62     guint hash;
      63
      64     s = g_strdup(v);
      65     g_strdown(s);
      66     hash = g_str_hash(s);
      67     g_free(s);
      68     return hash;
      69 }
      70
      71 static gint
      72 my_g_strcasecmp (gconstpointer a, gconstpointer b)
         /* [<][>][^][v][top][bottom][index][help] */
      73 {
      74     return (g_strcasecmp(a, b) == 0);
      75 }
      76
      77
      78 /* remove comments, join lines, and compress whitespace */
      79 static gchar *
      80 attribute_clean (const gchar *val)
         /* [<][>][^][v][top][bottom][index][help] */
      81 {
      82     gchar **lines;
      83     int i;
      84     guchar *p;
      85     guchar *q;
      86     guchar *ret_val;
      87
      88     /* split our value up into lines */
      89     lines = g_strsplit(val, "\n", 0);
      90
      91     for (i=0; lines[i] != NULL; i++) {
      92         /* remove comments */
      93         p = (guchar *)strchr(lines[i], '#');
      94         if (p != NULL) {
      95             *p = '\0';
      96         }
      97
      98         /* convert line continuation characters to whitespace */
      99         if (i > 0) {
     100             /* XXX: error in attribute */
     101             assert(is_rpsl_line_cont(lines[i][0]));
     102             lines[i][0] = ' ';
     103         }
     104     }
     105
     106     /* join the lines into a single string */
     107     ret_val = (guchar *)g_strjoinv("", lines);
     108     g_strfreev(lines);
     109
     110     /* we read from p, and write to q */
     111     p = ret_val;
     112     q = ret_val;
     113
     114     /* skip leading whitespace */
     115     while (isspace((int)*p)) {
     116         p++;
     117     }
     118
     119     /* convert all series of whitespace into a single space */
     120     /*  (this will happily convert '\n' into ' ') */
     121     while (*p != '\0') {
     122         if (isspace((int)*p)) {
     123             *q = ' ';
     124             q++;
     125             do {
     126                 p++;
     127             } while (isspace((int)*p));
     128         } else {
     129             *q = *p;
     130             q++;
     131             p++;
     132         }
     133     }
     134
     135     /* remove any trailing whitespace (there will be at most one space,
     136        because of the whitespace compression already performed above) */
     137     if ((q > ret_val) && isspace((int)*(q-1))) {
     138         q--;
     139     }
     140
     141     /* NUL-terminate our string */
     142     *q = '\0';
     143
     144     /* return our new line */
     145     return (gchar *)ret_val;
     146 }
     147
     148 /* see if the given string ends with the other string */
     149 static gboolean
     150 str_ends_with (const char *s1, const char *s2)
         /* [<][>][^][v][top][bottom][index][help] */
     151 {
     152     int s1_len;
     153     int s2_len;
     154
     155     s1_len = strlen(s1);
     156     s2_len = strlen(s2);
     157     if (s1_len < s2_len) {
     158         return FALSE;
     159     }
     160     if (strcmp(s1 + s1_len - s2_len, s2) == 0) {
     161         return TRUE;
     162     } else {
     163         return FALSE;
     164     }
     165 }
     166
     167 /* return each element in the list as a seperate gchar* */
     168 /* be sure to call g_strfreev() when done with this result!!! */
     169 static gchar **
     170 generic_list_split (const char *val, const char *separator_char)
         /* [<][>][^][v][top][bottom][index][help] */
     171 {
     172     gchar *tmp_str;
     173     gchar **ret_val;
     174     gboolean has_empty_last_element;
     175     int i;
     176     int list_size;
     177
     178     /* clean up to remove comments and newlines */
     179     tmp_str = attribute_clean(val);
     180
     181     /* check for empty last element, which g_strsplit() will silently drop */
     182     has_empty_last_element = str_ends_with(tmp_str, separator_char);
     183
     184     /* split based on separator character */
     185     ret_val = g_strsplit(tmp_str, separator_char, 0);
     186
     187     /* free our temporary variable */
     188     g_free(tmp_str);
     189
     190     /* clean whitespace from each element */
     191     list_size = 0;
     192     for (i=0; ret_val[i] != NULL; i++) {
     193         g_strstrip(ret_val[i]);
     194         list_size++;
     195     }
     196
     197     /* if we have an empty last element, add it back in */
     198     if (has_empty_last_element) {
     199         ret_val = g_renew(gchar*, ret_val, list_size+2);
     200         ret_val[list_size] = g_strdup("");
     201         ret_val[list_size+1] = NULL;
     202     }
     203
     204     /* return our array */
     205     return ret_val;
     206 }
     207
     208
     209 static gchar **
     210 attribute_list_split (const char *val)
         /* [<][>][^][v][top][bottom][index][help] */
     211 {
     212     return generic_list_split(val, ",");
     213 }
     214
     215 static gchar **
     216 ripe_list_split (const char *val)
         /* [<][>][^][v][top][bottom][index][help] */
     217 {
     218     /*
     219        We can call generic_list_split() because it calls
     220        attribute_clean() before using g_strsplit() to divide the string,
     221        and attribute_clean() converts any runs of whitespace into a
     222        single space.
     223      */
     224     return generic_list_split(val, " ");
     225 }
     226
     227 static void
     228 rpsl_error_assign (rpsl_error_t *error,
         /* [<][>][^][v][top][bottom][index][help] */
     229                    gint level,
     230                    gint code,
     231                    gchar *descr_fmt,
     232                    ...)
     233 {
     234     va_list args;
     235
     236     if (error != NULL) {
     237         error->level = level;
     238         error->code = code;
     239         va_start(args, descr_fmt);
     240         error->descr = g_strdup_vprintf(descr_fmt, args);
     241         va_end(args);
     242         error->attr_num = -1;
     243     }
     244 }
     245
     246 static void
     247 rpsl_error_add (GList **errors, gint level, gint code, gint attr_num,
         /* [<][>][^][v][top][bottom][index][help] */
     248                 gchar *descr_fmt, ...)
     249 {
     250     rpsl_error_t *error;
     251     va_list args;
     252
     253     error = g_new(rpsl_error_t, 1);
     254     error->level = level;
     255     error->code = code;
     256     va_start(args, descr_fmt);
     257     error->descr = g_strdup_vprintf(descr_fmt, args);
     258     va_end(args);
     259     error->attr_num = attr_num;
     260     *errors = g_list_append(*errors, error);
     261 }
     262
     263 /* returns TRUE if okay, else FALSE */
     264 static gboolean
     265 rpsl_attr_syntax_check (const attribute_t *attr_info,
         /* [<][>][^][v][top][bottom][index][help] */
     266                         const gchar *value,
     267                         GList **errors)
     268 {
     269     int level;
     270     gchar **split_val;
     271     int i;
     272     int error_cnt;
     273     GPtrArray *parse_errors;
     274     gchar *parse_descr;
     275
     276     /* set up for exit */
     277     split_val = NULL;
     278
     279     /* set our syntax checking level */
     280     if (rpsl_level == RPSL_DICT_CORE) {
     281         level = SYNTAX_CHECK_CORE;
     282     } else {
     283         level = SYNTAX_CHECK_FRONT_END;
     284     }
     285
     286     error_cnt = 0;
     287
     288     /* check the syntax */
     289     if ((attr_info->is_list) || (attr_info->is_ripe_list)) {
     290         if (attr_info->is_list) {
     291             split_val = attribute_list_split(value);
     292         } else {
     293             split_val = ripe_list_split(value);
     294         }
     295         if (split_val[0] == NULL) {
     296             rpsl_error_add(errors,
     297                            RPSL_ERRLVL_ERROR,
     298                            RPSL_ERR_EMPTYLIST,
     299                            -1,
     300                            "Attribute \"%s\" has an empty list",
     301                            attr_info->name);
     302             goto exit_rpsl_syntax;
     303         }
     304     } else {
     305         split_val = g_new(gchar*, 2);
     306         split_val[0] = attribute_clean(value);
     307         split_val[1] = NULL;
     308     }
     309
     310     for (i=0; split_val[i] != NULL; i++) {
     311         parse_errors = syntax_check_by_offset(attr_info->syntax_offset,
     312                                               level,
     313                                               split_val[i]);
     314         error_cnt += parse_errors->len;
     315         while (parse_errors->len > 0) {
     316             parse_descr = g_ptr_array_remove_index(parse_errors, 0);
     317             rpsl_error_add(errors,
     318                            RPSL_ERRLVL_ERROR,
     319                            RPSL_ERR_SYNERR,
     320                            -1,
     321                            "%s",
     322                            parse_descr);
     323             g_free(parse_descr);
     324         }
     325         g_ptr_array_free(parse_errors, TRUE);
     326     }
     327
     328 exit_rpsl_syntax:
     329     if (split_val != NULL) {
     330         g_strfreev(split_val);
     331     }
     332     if (error_cnt == 0) {
     333         return TRUE;
     334     } else {
     335         return FALSE;
     336     }
     337 }
     338
     339 /*
     340    returns NULL on *coding errors*
     341       non-existant class specified
     342       attribute does not exist for class
     343       attribute without class in ambiguous
     344    returns a structure otherwise
     345       on *syntax errors* errors are in the rpsl_attr_t structure
     346  */
     347
     348 /* XXX: there should be a way to preserve the original text, so
     349         that garbage attributes still retain meaning
     350  */
     351 rpsl_attr_t *
     352 rpsl_attr_init (const gchar *s, const gchar *class)
         /* [<][>][^][v][top][bottom][index][help] */
     353 {
     354     rpsl_attr_t *retval;
     355     gchar **attr_val;
     356     const class_t *class_info;
     357     const class_attr_t *class_attr_info;
     358     const attribute_t *attr_info;
     359     gboolean is_ambiguous;
     360
     361     /* return NULL if string is empty */
     362     if ((s == NULL) || (s[0] == '\0')) {
     363         return NULL;
     364     }
     365
     366     /* get class info as early as possible */
     367     if (class != NULL) {
     368         class_info = class_lookup(class);
     369         if (class_info == NULL) {
     370             return NULL;
     371         }
     372     } else {
     373         class_info = NULL;
     374     }
     375
     376     /* initialize the structure */
     377     retval = g_new(rpsl_attr_t, 1);
     378     retval->name = NULL;
     379     retval->lcase_name = NULL;
     380     retval->value = NULL;
     381     retval->errors = NULL;
     382     retval->num = -1;
     383     retval->attr_info = NULL;
     384
     385     /* prepare for early return */
     386     attr_val = NULL;
     387
     388     /* split into attribute and value */
     389     /* g_strsplit() puts max # of tokens + the rest of the string */
     390     /* so in this case we will have 1 token (attr name maybe) and the rest */
     391     if (strchr(s, ':') == NULL) {
     392         /* this is a critical error - very bad if it is a class attribute */
     393         rpsl_error_add(&retval->errors,
     394                        RPSL_ERRLVL_CRIT,
     395                        RPSL_ERR_BADATTR,
     396                        -1,
     397                        "Attribute missing colon, ':'");
     398         retval->name = g_strdup("");
     399         retval->lcase_name = g_strdup("");
     400         retval->value = g_strdup("");
     401         goto exit_rpsl_attr_init;
     402
     403     }
     404     attr_val = g_strsplit(s, ":", 1);
     405     assert(attr_val[0] != NULL);
     406
     407     /* assign our name and value */
     408     retval->name = g_strdup(attr_val[0]);
     409     retval->lcase_name = g_strdup(attr_val[0]);
     410     g_strdown(retval->lcase_name);
     411     if (attr_val[1] == NULL) {
     412         /* possible if nothing after the ':' */
     413         retval->value = g_strdup("");
     414     } else {
     415         /* the usual case, copy our data */
     416         retval->value = g_strdup(attr_val[1]);
     417         assert(attr_val[2] == NULL);
     418     }
     419
     420     /* get our attribute information */
     421     if (class_info != NULL) {
     422         class_attr_info = class_attr_lookup(class_info, retval->name);
     423     } else {
     424         class_attr_info = NULL;
     425     }
     426     if ((class_info != NULL) && (class_attr_info != NULL)) {
     427         attr_info = attribute_lookup_by_offset(class_attr_info->offset);
     428         assert(attr_info != NULL);
     429     } else {
     430         attr_info = attribute_lookup(retval->name, &is_ambiguous);
     431         if (is_ambiguous) {
     432             rpsl_attr_delete(retval);
     433             retval = NULL;
     434             goto exit_rpsl_attr_init;
     435         }
     436         if (attr_info == NULL) {
     437             /* this is a critical error - bad if it is a class attribute */
     438             rpsl_error_add(&retval->errors,
     439                            RPSL_ERRLVL_CRIT,
     440                            RPSL_ERR_UNKNOWNATTR,
     441                            -1,
     442                            "\"%s\" is not a known RPSL attribute",
     443                            retval->name);
     444             goto exit_rpsl_attr_init;
     445         }
     446     }
     447     /* dangerous, but we promise not to make any changes to this value! */
     448     retval->attr_info = (void *)attr_info;
     449
     450     /* check for errors (adds to the error list) */
     451     rpsl_attr_syntax_check(attr_info, retval->value, &retval->errors);
     452
     453     /* clean up and leave */
     454 exit_rpsl_attr_init:
     455     if (attr_val != NULL) {
     456         g_strfreev(attr_val);
     457     }
     458
     459     chk_attr(retval);
     460
     461     return retval;
     462 }
     463
     464 static rpsl_error_t *
     465 rpsl_error_copy (const rpsl_error_t *err)
         /* [<][>][^][v][top][bottom][index][help] */
     466 {
     467     rpsl_error_t *retval;
     468
     469     retval = g_new(rpsl_error_t, 1);
     470     retval->level = err->level;
     471     retval->code = err->code;
     472     retval->descr = g_strdup(err->descr);
     473     retval->attr_num = err->attr_num;
     474     return retval;
     475 }
     476
     477 rpsl_attr_t *
     478 rpsl_attr_copy (const rpsl_attr_t *attr)
         /* [<][>][^][v][top][bottom][index][help] */
     479 {
     480     rpsl_attr_t *retval;
     481     GList *ptr;
     482
     483     chk_attr(attr);
     484
     485     retval = g_new(rpsl_attr_t, 1);
     486     retval->name = g_strdup(attr->name);
     487     retval->lcase_name = g_strdup(attr->lcase_name);
     488     retval->value = g_strdup(attr->value);
     489     retval->errors = NULL;
     490     for (ptr=attr->errors; ptr != NULL; ptr = g_list_next(ptr)) {
     491         retval->errors = g_list_append(retval->errors,
     492                                        rpsl_error_copy(ptr->data));
     493     }
     494     retval->num = attr->num;
     495     retval->attr_info = attr->attr_info;
     496
     497     chk_attr(retval);
     498
     499     return retval;
     500 }
     501
     502 rpsl_attr_t *
     503 rpsl_attr_clean_copy (const rpsl_attr_t *attr)
         /* [<][>][^][v][top][bottom][index][help] */
     504 {
     505     rpsl_attr_t *retval;
     506
     507     chk_attr(attr);
     508
     509     retval = g_new(rpsl_attr_t, 1);
     510     retval->name = g_strdup(attr->name);
     511     retval->lcase_name = g_strdup(attr->lcase_name);
     512     retval->value = attribute_clean(attr->value);
     513     retval->errors = NULL;
     514     retval->num = attr->num;
     515     retval->attr_info = attr->attr_info;
     516
     517     chk_attr(retval);
     518
     519     return retval;
     520 }
     521
     522
     523 void
     524 rpsl_attr_delete (rpsl_attr_t *attr)
         /* [<][>][^][v][top][bottom][index][help] */
     525 {
     526     GList *ptr;
     527     rpsl_error_t *err;
     528
     529     chk_attr(attr);
     530
     531     g_free(attr->name);
     532     attr->name = NULL;
     533     g_free(attr->lcase_name);
     534     attr->lcase_name = NULL;
     535     g_free(attr->value);
     536     attr->value = NULL;
     537     for (ptr=attr->errors; ptr != NULL; ptr = g_list_next(ptr)) {
     538         err = ptr->data;
     539         g_free(err->descr);
     540         g_free(err);
     541     }
     542     g_list_free(attr->errors);
     543     attr->errors = NULL;
     544     attr->num = -1;
     545     attr->attr_info = NULL;
     546     g_free(attr);
     547 }
     548
     549 void
     550 rpsl_attr_delete_list (GList *attributes)
         /* [<][>][^][v][top][bottom][index][help] */
     551 {
     552     GList *ptr;
     553
     554     for (ptr=attributes; ptr != NULL; ptr = g_list_next(ptr)) {
     555         rpsl_attr_delete(ptr->data);
     556     }
     557     g_list_free(attributes);
     558 }
     559
     560 const gchar *
     561 rpsl_attr_get_name (const rpsl_attr_t *attr)
         /* [<][>][^][v][top][bottom][index][help] */
     562 {
     563     chk_attr(attr);
     564
     565     /* XXX: there should be a way to get the original name */
     566     /*return attr->name;*/
     567     return attr->lcase_name;
     568 }
     569
     570 gint
     571 rpsl_attr_get_ofs (const rpsl_attr_t *attr)
         /* [<][>][^][v][top][bottom][index][help] */
     572 {
     573     chk_attr(attr);
     574
     575     return attr->num;
     576 }
     577
     578 const gchar *
     579 rpsl_attr_get_value (const rpsl_attr_t *attr)
         /* [<][>][^][v][top][bottom][index][help] */
     580 {
     581     chk_attr(attr);
     582
     583     return attr->value;
     584 }
     585
     586 gchar *
     587 rpsl_attr_get_clean_value (const rpsl_attr_t *attr)
         /* [<][>][^][v][top][bottom][index][help] */
     588 {
     589     gchar *tmp;
     590     gchar *retval;
     591
     592     chk_attr(attr);
     593
     594     /* don't just return the value from attribute_clean(), since we
     595        need to return memory that can be freed via free(), and the
     596        gchar* returned by attribute_clean needs to be freed via g_free() */
     597     tmp = attribute_clean(attr->value);
     598     retval = strdup(tmp);
     599     g_free(tmp);
     600     return retval;
     601 }
     602
     603 GList *
     604 rpsl_attr_get_split_list (const rpsl_attr_t *attr)
         /* [<][>][^][v][top][bottom][index][help] */
     605 {
     606     const attribute_t *attr_info;
     607     GList *retval;
     608     gchar **split;
     609     int i;
     610     rpsl_attr_t *newattr;
     611
     612     chk_attr(attr);
     613
     614     attr_info = attr->attr_info;
     615     if ((attr_info!=NULL) && (attr_info->is_list || attr_info->is_ripe_list)) {
     616         if (attr_info->is_list) {
     617             split = attribute_list_split(attr->value);
     618         } else {
     619             split = ripe_list_split(attr->value);
     620         }
     621         retval = NULL;
     622         for (i=0; split[i] != NULL; i++) {
     623             /* XXX: perpaps consolidate this with rpsl_attr_init()? */
     624             newattr = g_new(rpsl_attr_t, 1);
     625             assert(newattr != NULL);
     626             newattr->name = g_strdup(attr->name);
     627             newattr->lcase_name = g_strdup(attr->lcase_name);
     628             newattr->value = g_strdup(split[i]);
     629             newattr->errors = NULL;
     630             newattr->num = attr->num;
     631             newattr->attr_info = attr->attr_info;
     632             retval = g_list_append(retval, newattr);
     633         }
     634         g_strfreev(split);
     635         return retval;
     636     } else {
     637         return g_list_append(NULL, rpsl_attr_clean_copy(attr));
     638     }
     639 }
     640
     641 void
     642 rpsl_attr_replace_value (rpsl_attr_t *attr, const gchar *value)
         /* [<][>][^][v][top][bottom][index][help] */
     643 {
     644     chk_attr(attr);
     645
     646     /* perform check to add any new errors */
     647     if (attr->attr_info != NULL) {
     648         rpsl_attr_syntax_check(attr->attr_info, value, &attr->errors);
     649     }
     650
     651     /* copy the value */
     652     g_free(attr->value);
     653     attr->value = g_strdup(value);
     654
     655     chk_attr(attr);
     656 }
     657
     658 const GList *
     659 rpsl_attr_errors (const rpsl_attr_t *attr)
         /* [<][>][^][v][top][bottom][index][help] */
     660 {
     661     chk_attr(attr);
     662
     663     return attr->errors;
     664 }
     665
     666 static gboolean
     667 object_is_comment (const gchar *s)
         /* [<][>][^][v][top][bottom][index][help] */
     668 {
     669     const gchar *p, *q;
     670
     671     /* skip blank lines */
     672     p = s;
     673     for (;;) {
     674         while ((*p == ' ') || (*p == '\t')) {
     675             p++;
     676         }
     677         /* if object is only blank lines, then we are *not* a comment */
     678         if (*p == '\0') {
     679             return FALSE;
     680         }
     681         if (*p != '\n') {
     682             break;
     683         }
     684         p++;
     685     }
     686     /* skip comment lines */
     687     for (;;) {
     688         if ((*p != '%') && (*p != '#')) {
     689             break;
     690         }
     691         q = strchr(p, '\n');
     692         /* if we end on a comment without newline, we *are* a comment */
     693         if (q == NULL) {
     694             return TRUE;
     695         }
     696         p = q + 1;
     697     }
     698     /* skip trailing blank lines */
     699     for (;;) {
     700         while ((*p == ' ') || (*p == '\t')) {
     701             p++;
     702         }
     703         if (*p != '\n') {
     704             break;
     705         }
     706         p++;
     707     }
     708     /* see if we skipped the whole object */
     709     if (*p == '\0') {
     710         return TRUE;
     711     } else {
     712         return FALSE;
     713     }
     714 }
     715
     716 /* we don't want to check whether an attribute belongs in the template
     717    if it is a bad attribute, or and unknown attribute */
     718 static gboolean
     719 template_check_needed (const rpsl_attr_t *attr)
         /* [<][>][^][v][top][bottom][index][help] */
     720 {
     721     const GList *p;
     722     const rpsl_error_t *error;
     723
     724     p = rpsl_attr_errors(attr);
     725     while (p != NULL) {
     726         error = p->data;
     727         if (error->code == RPSL_ERR_BADATTR) {
     728             return FALSE;
     729         }
     730         if (error->code == RPSL_ERR_UNKNOWNATTR) {
     731             return FALSE;
     732         }
     733         p = g_list_next(p);
     734     }
     735     return TRUE;
     736 }
     737
     738 static void
     739 renumber_attr (rpsl_attr_t *attr, int num)
         /* [<][>][^][v][top][bottom][index][help] */
     740 {
     741     attr->num = num;
     742 }
     743
     744 static rpsl_attr_t *
     745 rpsl_empty_attr ()
         /* [<][>][^][v][top][bottom][index][help] */
     746 {
     747     rpsl_attr_t *retval;
     748
     749     retval = g_new(rpsl_attr_t, 1);
     750     retval->name = g_strdup("");
     751     retval->lcase_name = g_strdup("");
     752     retval->value = g_strdup("");
     753     retval->errors = NULL;
     754     retval->num = -1;
     755     retval->attr_info = NULL;
     756     return retval;
     757 }
     758
     759 rpsl_object_t *
     760 rpsl_object_init (const gchar *s)
         /* [<][>][^][v][top][bottom][index][help] */
     761 {
     762     rpsl_object_t *retval;
     763     GPtrArray *lines;
     764     const gchar *p, *q;
     765     gchar *line;
     766     rpsl_attr_t *attr;
     767     const class_t *class_info;
     768     GList *attr_list;
     769     const class_attr_t *class_attr_info;
     770     const attribute_t *attr_info;
     771     const gchar *attr_name;
     772     const gchar *class_name;
     773     int i;
     774     gboolean removed_trailing_empty_lines;
     775
     776     /* initialize the structure */
     777     retval = g_new(rpsl_object_t, 1);
     778     retval->attributes = NULL;
     779     retval->attr_lookup = g_hash_table_new(my_g_str_hash, my_g_strcasecmp);
     780     retval->errors = NULL;
     781     retval->class_info = NULL;
     782
     783     /* make some lines */
     784     lines = g_ptr_array_new();
     785
     786     /* see if entire string is comments */
     787     if (object_is_comment(s)) {
     788         rpsl_error_add(&retval->errors,
     789                        RPSL_ERRLVL_WARN,
     790                        RPSL_ERR_ONLYCOMMENTS,
     791                        -1,
     792                        "Object contains only comments");
     793         goto exit_rpsl_object_init;
     794     }
     795
     796     /* p is the beginning of the current attribute, q searches for the end */
     797     p = s;
     798     for (;;) {
     799         /* done with string, finish */
     800         if (*p == '\0') {
     801             break;
     802         }
     803
     804         /* search for end of attribute */
     805         q = strchr(p, '\n');
     806         while ((q != NULL) && is_rpsl_line_cont(q[1])) {
     807             q = strchr(q+1, '\n');
     808         }
     809
     810         if (q == NULL) {
     811             /* add the final attribute */
     812             g_ptr_array_add(lines, g_strdup(p));
     813             /* and exit */
     814             break;
     815         } else {
     816             /* add this attribute */
     817             g_ptr_array_add(lines, g_strndup(p, q-p));
     818             /* and proceed to the next one */
     819             p = q+1;
     820         }
     821     }
     822
     823     /* be nice and strip empty lines at the end */
     824     removed_trailing_empty_lines = FALSE;
     825     while (lines->len > 0) {
     826         line = g_ptr_array_index(lines, lines->len - 1);
     827         if (line[0] != '\0') {
     828             break;
     829         }
     830         g_ptr_array_remove_index_fast(lines, lines->len - 1);
     831         g_free(line);
     832         removed_trailing_empty_lines = TRUE;
     833     }
     834     if (removed_trailing_empty_lines) {
     835         rpsl_error_add(&retval->errors,
     836                        RPSL_ERRLVL_INFO,
     837                        RPSL_ERR_EMPTYATTR,
     838                        -1,
     839                        "Trailing blank lines ignored");
     840     }
     841
     842     /* verify we have at least one line */
     843     if (lines->len <= 0) {
     844         rpsl_error_add(&retval->errors,
     845                        RPSL_ERRLVL_CRIT,
     846                        RPSL_ERR_BADCLASS,
     847                        -1,
     848                        "Empty object");
     849         goto exit_rpsl_object_init;
     850     }
     851
     852     /* create the magic first attribute, which is the class */
     853     attr = rpsl_attr_init(g_ptr_array_index(lines, 0), NULL);
     854     if (attr == NULL) {
     855         rpsl_error_add(&retval->errors,
     856                        RPSL_ERRLVL_CRIT,
     857                        RPSL_ERR_BADCLASS,
     858                        -1,
     859                        "Error with class attribute, class invalid");
     860         goto exit_rpsl_object_init;
     861     }
     862     renumber_attr(attr, 0);
     863
     864     /* check for errors with the class attribute */
     865     /* only critical errors matter - let innocent syntax errors pass through */
     866     if (rpsl_attr_has_error(attr, RPSL_ERRLVL_CRIT)) {
     867         rpsl_error_add(&retval->errors,
     868                        RPSL_ERRLVL_CRIT,
     869                        RPSL_ERR_BADCLASS,
     870                        -1,
     871                        "Error with class attribute, class invalid");
     872         rpsl_attr_delete(attr);
     873         goto exit_rpsl_object_init;
     874     }
     875
     876
     877     /* get the class information */
     878     class_name = rpsl_attr_get_name(attr);
     879     class_info = class_lookup(class_name);
     880     if (class_info == NULL) {
     881         rpsl_error_add(&retval->errors,
     882                        RPSL_ERRLVL_CRIT,
     883                        RPSL_ERR_UNKNOWNCLASS,
     884                        -1,
     885                        "First attribute, \"%s\", is not a known RPSL class",
     886                        class_name);
     887         rpsl_attr_delete(attr);
     888         goto exit_rpsl_object_init;
     889     }
     890
     891     /* check for syntax errors with the class attribute */
     892     if (rpsl_attr_errors(attr) != NULL) {
     893                 rpsl_error_add(&retval->errors,
     894                                RPSL_ERRLVL_ERROR,
     895                                RPSL_ERR_BADATTR,
     896                                0,
     897                                "Error with attribute \"%s\"",
     898                                class_name);
     899     }
     900
     901     /* possibly dangerous, but we promise only to read this value! */
     902     retval->class_info = (void *)class_info;
     903
     904     /* add class attribute */
     905     retval->attributes = g_list_append(NULL, attr);
     906     attr_list = g_list_append(NULL, attr);
     907     g_hash_table_insert(retval->attr_lookup,
     908                         (void *)rpsl_attr_get_name(attr),
     909                         attr_list);
     910
     911     /* valid class, process each attribute */
     912     for (i=1; i < lines->len; i++) {
     913         attr = rpsl_attr_init(g_ptr_array_index(lines, i), class_name);
     914         if (attr == NULL) {
     915             /* XXX: we should preserve the original information somehow */
     916             attr = rpsl_empty_attr();
     917             rpsl_error_add(&(attr->errors),
     918                            RPSL_ERRLVL_ERROR,
     919                            RPSL_ERR_BADATTR,
     920                            -1,
     921                            "Attribute not valid in this class");
     922         }
     923         assert(attr != NULL);
     924         renumber_attr(attr, i);
     925
     926         /* add the attribute to the list of attributes for this object */
     927         retval->attributes = g_list_append(retval->attributes, attr);
     928
     929         /* check for errors at attribute level */
     930         if (rpsl_attr_errors(attr) != NULL) {
     931             attr_name = rpsl_attr_get_name(attr);
     932             if (attr_name != NULL) {
     933                 rpsl_error_add(&retval->errors,
     934                                RPSL_ERRLVL_ERROR,
     935                                RPSL_ERR_BADATTR,
     936                                i,
     937                                "Error with attribute \"%s\"",
     938                                attr_name);
     939             } else {
     940                 rpsl_error_add(&retval->errors,
     941                                RPSL_ERRLVL_ERROR,
     942                                RPSL_ERR_BADATTR,
     943                                i,
     944                                "Error with attribute");
     945                 /* no name - no sense to process this attr further */
     946                 continue;
     947             }
     948         }
     949
     950
     951         /* get list of existing attributes of this name, if any */
     952         attr_list = g_hash_table_lookup(retval->attr_lookup,
     953                                         rpsl_attr_get_name(attr));
     954
     955
     956         /* perform template checking if attribute is known type */
     957         if (template_check_needed(attr)) {
     958
     959             /* verify this attribute can go in this class */
     960             class_attr_info = class_attr_lookup(class_info,
     961                                                 rpsl_attr_get_name(attr));
     962             if (class_attr_info == NULL) {
     963                 rpsl_error_add(&retval->errors,
     964                                RPSL_ERRLVL_ERROR,
     965                                RPSL_ERR_ATTRNOTALLOWED,
     966                                i,
     967                                "Attribute \"%s\" is not allowed in this class",
     968                                rpsl_attr_get_name(attr));
     969             } else {
     970                 /* if we have added a "single" attribute more than once */
     971                 if ((class_attr_info->number == ATTR_SINGLE) &&
     972                     (attr_list != NULL))
     973                 {
     974                     rpsl_error_add(&retval->errors,
     975                                    RPSL_ERRLVL_ERROR,
     976                                    RPSL_ERR_ATTRSINGLE,
     977                                    i,
     978                                    "Attribute \"%s\" appears more than once",
     979                                    rpsl_attr_get_name(attr));
     980                 }
     981                 /* if we have tried to initialize a "generated" attribute */
     982                 if (class_attr_info->choice == ATTR_GENERATED) {
     983                     rpsl_error_add(&retval->errors,
     984                                    RPSL_ERRLVL_ERROR,
     985                                    RPSL_ERR_ATTRGENERATED,
     986                                    i,
     987                                    "Attribute \"%s\" is generated automatically",
     988                                    rpsl_attr_get_name(attr));
     989                 }
     990             }
     991
     992         } /* template_check_required(attr)) */
     993
     994         /* add the attribute to the hash table for the class */
     995         attr_list = g_list_append(attr_list, attr);
     996         g_hash_table_insert(retval->attr_lookup,
     997                             (void *)rpsl_attr_get_name(attr),
     998                             attr_list);         /* replaces any old value */
     999     }
    1000
    1001     /* check for missing required attributes */
    1002     for (i=0; i<class_info->num_attr; i++) {
    1003         class_attr_info = &class_info->attr[i];
    1004         if (class_attr_info->choice == ATTR_MANDATORY) {
    1005             attr_info = attribute_lookup_by_offset(class_attr_info->offset);
    1006             attr_list = g_hash_table_lookup(retval->attr_lookup,
    1007                                             attr_info->name);
    1008             if (attr_list == NULL) {
    1009                 rpsl_error_add(&retval->errors,
    1010                                RPSL_ERRLVL_ERROR,
    1011                                RPSL_ERR_MISSINGATTR,
    1012                                -1,
    1013                                "Required attribute \"%s\" is missing",
    1014                                attr_info->name);
    1015                 if (attr_info->is_primary) {
    1016                     rpsl_error_add(&retval->errors,
    1017                                    RPSL_ERRLVL_ERROR,
    1018                                    RPSL_ERR_MISSINGKEY,
    1019                                    -1,
    1020                                    "Primary key \"%s\" is missing",
    1021                                    attr_info->name);
    1022                 }
    1023             }
    1024         }
    1025     }
    1026
    1027     /* done - enjoy your new object */
    1028
    1029 exit_rpsl_object_init:
    1030     /* free memory used by split lines */
    1031     for (i=0; i<lines->len; i++) {
    1032         g_free(g_ptr_array_index(lines, i));
    1033     }
    1034     g_ptr_array_free(lines, TRUE);
    1035     lines = NULL;
    1036
    1037     chk_obj(retval);
    1038
    1039     /* return our object */
    1040     return retval;
    1041 }
    1042
    1043 rpsl_object_t *
    1044 rpsl_object_copy (const rpsl_object_t *object)
         /* [<][>][^][v][top][bottom][index][help] */
    1045 {
    1046     rpsl_object_t *retval;
    1047     GList *p;
    1048     rpsl_attr_t *attr;
    1049     GList *attr_list;
    1050
    1051     chk_obj(object);
    1052
    1053     retval = g_new(rpsl_object_t, 1);
    1054     retval->attributes = NULL;
    1055     retval->attr_lookup = g_hash_table_new(my_g_str_hash, my_g_strcasecmp);
    1056     retval->errors = NULL;
    1057     retval->class_info = object->class_info;
    1058
    1059     /* copy attributes */
    1060     for (p=object->attributes; p != NULL; p = g_list_next(p)) {
    1061         /* insert copy of attribute into list */
    1062         attr = rpsl_attr_copy(p->data);
    1063         retval->attributes = g_list_append(retval->attributes, attr);
    1064
    1065         /* insert copy of attribute into hash table */
    1066         attr_list = g_hash_table_lookup(retval->attr_lookup,
    1067                                         rpsl_attr_get_name(attr));
    1068         attr_list = g_list_append(attr_list, attr);  /* works for NULL too */
    1069         g_hash_table_insert(retval->attr_lookup,
    1070                             (void *)rpsl_attr_get_name(attr),
    1071                             attr_list);          /* replaces any old value */
    1072     }
    1073
    1074     /* copy errors */
    1075     for (p=object->errors; p != NULL; p = g_list_next(p)) {
    1076         retval->errors = g_list_append(retval->errors,
    1077                                        rpsl_error_copy(p->data));
    1078     }
    1079
    1080     chk_obj(retval);
    1081
    1082     /* return the copy */
    1083     return retval;
    1084 }
    1085
    1086 rpsl_object_t *
    1087 rpsl_object_copy_flattened (const rpsl_object_t *object)
         /* [<][>][^][v][top][bottom][index][help] */
    1088 {
    1089     rpsl_object_t *retval;
    1090     GList *p1, *p2;
    1091     GList *split_attr;
    1092     rpsl_attr_t *attr;
    1093     GList *attr_list;
    1094     int num_attr;
    1095
    1096     chk_obj(object);
    1097
    1098     retval = g_new(rpsl_object_t, 1);
    1099     retval->attributes = NULL;
    1100     retval->attr_lookup = g_hash_table_new(my_g_str_hash, my_g_strcasecmp);
    1101     retval->errors = NULL;
    1102     retval->class_info = object->class_info;
    1103
    1104     /* copy attributes */
    1105     num_attr = 0;
    1106     for (p1=object->attributes; p1 != NULL; p1 = g_list_next(p1)) {
    1107         /* split the attribute into separate values (may be 1) */
    1108         split_attr = rpsl_attr_get_split_list(p1->data);
    1109
    1110         /* add each resulting attribute */
    1111         for (p2=split_attr; p2 != NULL; p2 = g_list_next(p2)) {
    1112             attr = p2->data;
    1113
    1114             /* renumber attribute */
    1115             renumber_attr(attr, num_attr);
    1116             num_attr++;
    1117
    1118             /* insert split attribute into list */
    1119             retval->attributes = g_list_append(retval->attributes, attr);
    1120
    1121             /* insert split attribute into hash table */
    1122             attr_list = g_hash_table_lookup(retval->attr_lookup,
    1123                                             rpsl_attr_get_name(attr));
    1124             attr_list = g_list_append(attr_list, attr); /* works for NULL too */
    1125             g_hash_table_insert(retval->attr_lookup,
    1126                                 (void *)rpsl_attr_get_name(attr),
    1127                                 attr_list);         /* replaces any old value */
    1128         }
    1129
    1130         /* free the list */
    1131         g_list_free(split_attr);
    1132     }
    1133
    1134     chk_obj(retval);
    1135
    1136     /* return the copy */
    1137     return retval;
    1138 }
    1139
    1140 static void
    1141 rpsl_object_delete_helper (gpointer attr_name,
         /* [<][>][^][v][top][bottom][index][help] */
    1142                            gpointer attr_list,
    1143                            gpointer null)
    1144 {
    1145     g_list_free((GList *)attr_list);
    1146 }
    1147
    1148 void
    1149 rpsl_object_delete (rpsl_object_t *object)
         /* [<][>][^][v][top][bottom][index][help] */
    1150 {
    1151     GList *p;
    1152     rpsl_error_t *err;
    1153
    1154     chk_obj(object);
    1155
    1156     /* free the attributes */
    1157     for (p=object->attributes; p != NULL; p = g_list_next(p)) {
    1158         rpsl_attr_delete(p->data);
    1159     }
    1160     g_list_free(object->attributes);
    1161     object->attributes = NULL;
    1162
    1163     /* remove the lists from the hash table */
    1164     g_hash_table_foreach(object->attr_lookup, rpsl_object_delete_helper, NULL);
    1165     g_hash_table_destroy(object->attr_lookup);
    1166     object->attr_lookup = NULL;
    1167
    1168     /* free the errors */
    1169     for (p=object->errors; p != NULL; p = g_list_next(p)) {
    1170         err = p->data;
    1171         g_free(err->descr);
    1172         g_free(err);
    1173     }
    1174     g_list_free(object->errors);
    1175     object->errors = NULL;
    1176
    1177     /* free the object itself */
    1178     g_free(object);
    1179 }
    1180
    1181 const char *
    1182 rpsl_object_get_class (const rpsl_object_t *object)
         /* [<][>][^][v][top][bottom][index][help] */
    1183 {
    1184     rpsl_attr_t *attr;
    1185
    1186     chk_obj(object);
    1187
    1188     if (object->attributes != NULL) {
    1189         attr = (rpsl_attr_t *)object->attributes->data;
    1190         return attr->lcase_name;
    1191     } else {
    1192         return NULL;
    1193     }
    1194 }
    1195
    1196 /* default number of spaces per tab character */
    1197 #define TABSTOP 8
    1198
    1199 /* returns the position of the next tab stop */
    1200 static guint
    1201 next_tabstop (guint col)
         /* [<][>][^][v][top][bottom][index][help] */
    1202 {
    1203     guint tab;
    1204
    1205     tab = (col / TABSTOP) + 1;
    1206     return tab * TABSTOP;
    1207 }
    1208
    1209 /* gets the leading whitespace from the given string */
    1210 static void
    1211 separate_leading_whitespace (const gchar *str, GString **ws, GString **non_ws)
         /* [<][>][^][v][top][bottom][index][help] */
    1212 {
    1213     int n;
    1214
    1215     n = 0;
    1216     while ((str[n] == ' ') || (str[n] == '\t')) {
    1217         n++;
    1218     }
    1219
    1220     *ws = g_string_new(str);
    1221     g_string_truncate(*ws, n);
    1222     *non_ws = g_string_new(str + n);
    1223 }
    1224
    1225 /* gets the length of a string of whitespace */
    1226 static int
    1227 whitespace_length (const gchar *str, int start_col)
         /* [<][>][^][v][top][bottom][index][help] */
    1228 {
    1229     int len;
    1230
    1231     len = start_col;
    1232     for (;;) {
    1233         if (*str == ' ') {
    1234             len++;
    1235         } else if (*str == '\t') {
    1236             len = next_tabstop(len);
    1237         } else {
    1238             break;
    1239         }
    1240         str++;
    1241     }
    1242     return len;
    1243 }
    1244
    1245 /* removes the number of columns specified from the string, from the right */
    1246 static void
    1247 remove_columns (GString *s, int col, int start_col)
         /* [<][>][^][v][top][bottom][index][help] */
    1248 {
    1249     int old_len;
    1250     int new_len;
    1251     int col_removed;
    1252
    1253     col_removed = 0;
    1254
    1255     /* first, remove characters until we've removed enough */
    1256     while ((s->len > 0) && (col_removed < col)) {
    1257         old_len = whitespace_length(s->str, start_col);
    1258         g_string_truncate(s, s->len-1);
    1259         new_len = whitespace_length(s->str, start_col);
    1260         col_removed += old_len - new_len;
    1261     }
    1262
    1263     /* if we've removed too many, add some spaces back */
    1264     while (col_removed > col) {
    1265         g_string_append_c(s, ' ');
    1266         col_removed--;
    1267     }
    1268 }
    1269
    1270 /* align the text of the attribute to the specific column */
    1271 static void
    1272 add_aligned_val (GString *s, const rpsl_attr_t *attr, int col)
         /* [<][>][^][v][top][bottom][index][help] */
    1273 {
    1274     const gchar *name;
    1275     const gchar *val;
    1276     int start_col;
    1277     const gchar *p, *q;
    1278     int col_to_add;
    1279     int col_to_sub;
    1280     gchar **lines;
    1281     int i, j;
    1282     GString *ws;
    1283     GString *non_ws;
    1284
    1285     /* get the information from the attribute */
    1286     name = rpsl_attr_get_name(attr);
    1287     val = rpsl_attr_get_value(attr);
    1288
    1289     /* calculate the column we're at after the attribute name */
    1290     start_col = strlen(name) + 1;
    1291
    1292     /* if the desired column is too small based on the name of the
    1293        attribute, set to the smallest allowed column */
    1294     if (col < (start_col + 1)) {
    1295         col = start_col + 1;
    1296     }
    1297
    1298
    1299     /* find out what column the attribute value currently starts at */
    1300     p = val;
    1301     for (;;) {
    1302         if (*p == ' ') {
    1303             start_col++;
    1304         } else if (*p == '\t') {
    1305             start_col = next_tabstop(start_col);
    1306         } else {
    1307             break;
    1308         }
    1309         p++;
    1310     }
    1311
    1312     /* special case:
    1313        if there are *only* whitespace on the first line, or if it only
    1314        contains a comment, then use "as-is" */
    1315     if ((*p == '\0') || (*p == '\n') || (*p == '#')) {
    1316           g_string_append(s, val);
    1317           g_string_append_c(s, '\n');
    1318           /* EARLY RETURN */
    1319           return;
    1320     }
    1321
    1322     /* next, see how many columns we need to add or subtract */
    1323     col_to_add = col - start_col;
    1324
    1325     /* adding is (relatively) easy */
    1326     if (col_to_add > 0) {
    1327         lines = g_strsplit(val, "\n", 0);
    1328         /* for the first line, append the spaces and the line itself */
    1329         q = lines[0];
    1330         while ((*q == ' ') || (*q == '\t')) {
    1331             g_string_append_c(s, *q);
    1332             q++;
    1333         }
    1334         for (j=0; j<col_to_add; j++) {
    1335             g_string_append_c(s, ' ');
    1336         }
    1337         g_string_append(s, q);
    1338         g_string_append_c(s, '\n');
    1339         for (i=1; lines[i] != NULL; i++) {
    1340             /* for subsequent lines... */
    1341             /* append the first (line continuation) character */
    1342             g_string_append_c(s, lines[i][0]);
    1343             /* append any leading whitespace */
    1344             q = lines[i]+1;
    1345             while ((*q == ' ') || (*q == '\t')) {
    1346                 g_string_append_c(s, *q);
    1347                 q++;
    1348             }
    1349             /* now append the spaces and the remainder of the line */
    1350             for (j=0; j<col_to_add; j++) {
    1351                 g_string_append_c(s, ' ');
    1352             }
    1353             g_string_append(s, q);
    1354             g_string_append_c(s, '\n');
    1355         }
    1356         g_strfreev(lines);
    1357     }
    1358     /* subtracting is a bit more tricky, due to tabs (AKA "minions of evil") */
    1359     else if (col_to_add < 0) {
    1360         col_to_sub = -col_to_add;
    1361
    1362         lines = g_strsplit(val, "\n", 0);
    1363
    1364         /* add first line after subtracting columns */
    1365         separate_leading_whitespace(lines[0], &ws, &non_ws);
    1366         remove_columns(ws, col_to_sub, strlen(name)+1);
    1367         g_string_append(s, ws->str);
    1368         g_string_append(s, non_ws->str);
    1369         g_string_append_c(s, '\n');
    1370         g_string_free(ws, TRUE);
    1371         g_string_free(non_ws, TRUE);
    1372
    1373         for (i=1; lines[i] != NULL; i++) {
    1374             separate_leading_whitespace(lines[i]+1, &ws, &non_ws);
    1375              /* if the line continuation character is a tab and
    1376                 we don't have enough columns, convert it to spaces */
    1377             if (lines[i][0] == '\t') {
    1378                 if (whitespace_length(ws->str, 0) < col_to_sub) {
    1379                     lines[i][0] = ' ';
    1380                     for (j=1; j<TABSTOP; j++) {
    1381                         g_string_prepend_c(ws, ' ');
    1382                     }
    1383                 }
    1384             }
    1385             remove_columns(ws, col_to_sub, 0);
    1386             g_string_append_c(s, lines[i][0]);
    1387             g_string_append(s, ws->str);
    1388             g_string_append(s, non_ws->str);
    1389             g_string_append_c(s, '\n');
    1390             g_string_free(ws, TRUE);
    1391             g_string_free(non_ws, TRUE);
    1392         }
    1393         g_strfreev(lines);
    1394     }
    1395     /* and if no adjustment is necessary, it's trivial */
    1396     else {
    1397         g_string_append(s, val);
    1398         g_string_append_c(s, '\n');
    1399     }
    1400 }
    1401
    1402 gchar *
    1403 rpsl_object_get_text (const rpsl_object_t *object, guint data_column)
         /* [<][>][^][v][top][bottom][index][help] */
    1404 {
    1405     GString *s;
    1406     GList *p;
    1407     rpsl_attr_t *attr;
    1408     gchar *retval;
    1409     const gchar *name;
    1410
    1411     chk_obj(object);
    1412
    1413     /* return NULL on empty object, as promised */
    1414     if (object->attributes == NULL) {
    1415         return NULL;
    1416     }
    1417
    1418     /* concatinate attribute names and values together */
    1419     s = g_string_new("");
    1420     for (p=object->attributes; p != NULL; p = g_list_next(p)) {
    1421         attr = p->data;
    1422         name = rpsl_attr_get_name(attr);
    1423         if (name != NULL) {
    1424             g_string_append(s, name);
    1425             g_string_append_c(s, ':');
    1426             if (data_column > 0) {
    1427                 add_aligned_val(s, attr, data_column);
    1428             } else {
    1429                 g_string_append(s, rpsl_attr_get_value(attr));
    1430                 g_string_append_c(s, '\n');
    1431             }
    1432         }
    1433     }
    1434
    1435     /* copy value to return */
    1436     retval = (gchar *)malloc(s->len + 1);
    1437     if (retval != NULL) {
    1438         strcpy(retval, s->str);
    1439     }
    1440
    1441     /* free string */
    1442     g_string_free(s, TRUE);
    1443
    1444     /* return result (returns NULL if memory allocation failed) */
    1445     return retval;
    1446 }
    1447
    1448 gint
    1449 rpsl_object_get_num_attr (const rpsl_object_t *object)
         /* [<][>][^][v][top][bottom][index][help] */
    1450 {
    1451     chk_obj(object);
    1452
    1453     return g_list_length(object->attributes);
    1454 }
    1455
    1456 const GList *
    1457 rpsl_object_get_all_attr (const rpsl_object_t *object)
         /* [<][>][^][v][top][bottom][index][help] */
    1458 {
    1459     chk_obj(object);
    1460
    1461     return object->attributes;
    1462 }
    1463
    1464 GList *
    1465 rpsl_object_get_attr (const rpsl_object_t *object, const gchar *name)
         /* [<][>][^][v][top][bottom][index][help] */
    1466 {
    1467     GList *attr_list;
    1468     GList *retval;
    1469
    1470     chk_obj(object);
    1471
    1472     retval = NULL;
    1473     attr_list = g_hash_table_lookup(object->attr_lookup, name);
    1474     while (attr_list != NULL) {
    1475         retval = g_list_append(retval, rpsl_attr_copy(attr_list->data));
    1476         attr_list = g_list_next(attr_list);
    1477     }
    1478     return retval;
    1479 }
    1480
    1481 const rpsl_attr_t *
    1482 rpsl_object_get_attr_by_ofs (const rpsl_object_t *object, gint ofs)
         /* [<][>][^][v][top][bottom][index][help] */
    1483 {
    1484     rpsl_attr_t *attr;
    1485
    1486     chk_obj(object);
    1487     attr = g_list_nth_data(object->attributes, ofs);
    1488     chk_attr(attr);
    1489
    1490     return attr;
    1491 }
    1492
    1493 /* using -1 for offset (ofs) to append to the end of the object */
    1494 static int
    1495 add_attr_to_object(rpsl_object_t *object,
         /* [<][>][^][v][top][bottom][index][help] */
    1496                 rpsl_attr_t *attr,
    1497                 gint ofs,
    1498                 rpsl_error_t *error)
    1499 {
    1500     const gchar *attr_name;
    1501     class_t *class_info;
    1502     const class_attr_t *class_attr_info;
    1503     GList *attr_list;
    1504     gint num_attr;
    1505     gint i;
    1506     GList *p;
    1507     rpsl_attr_t *tmp;
    1508     GList *err_list;
    1509     rpsl_error_t *err;
    1510
    1511     chk_obj(object);
    1512     chk_attr(attr);
    1513
    1514     /* empty object - bogus, reject, abort, error */
    1515     if (object->attributes == NULL) {
    1516         rpsl_error_assign(error,
    1517                           RPSL_ERRLVL_ERROR,
    1518                           RPSL_ERR_BADCLASS,
    1519                           "Empty class");
    1520         chk_obj(object);
    1521         chk_attr(attr);
    1522         return 0;
    1523     }
    1524
    1525     /* check our offset number */
    1526     num_attr = rpsl_object_get_num_attr(object);
    1527     if ((ofs == 0) || (ofs > num_attr)) {
    1528         rpsl_error_assign(error,
    1529                           RPSL_ERRLVL_ERROR,
    1530                           RPSL_ERR_BADOFFSET,
    1531                           "Offset %d not between 1 and %d", ofs, num_attr);
    1532         chk_obj(object);
    1533         chk_attr(attr);
    1534         return 0;
    1535     }
    1536
    1537     /* get attributes with this name (may be NULL, which is okay) */
    1538     attr_name = rpsl_attr_get_name(attr);
    1539     attr_list = g_hash_table_lookup(object->attr_lookup, attr_name);
    1540
    1541     /* get class info */
    1542     class_info = object->class_info;
    1543     if (class_info != NULL) {    /* we can only check for valid classes... */
    1544
    1545         /* verify this attribute can go in this class */
    1546         class_attr_info = class_attr_lookup(class_info, attr_name);
    1547         if (class_attr_info == NULL) {
    1548             rpsl_error_assign(error,
    1549                               RPSL_ERRLVL_ERROR,
    1550                               RPSL_ERR_ATTRNOTALLOWED,
    1551                               "Attribute \"%s\" is not allowed in this class",
    1552                               attr_name);
    1553             chk_obj(object);
    1554             chk_attr(attr);
    1555             return 0;
    1556         }
    1557
    1558         /* check to see if it is "single" and already found */
    1559         if ((class_attr_info->number == ATTR_SINGLE) && (attr_list != NULL)) {
    1560             rpsl_error_assign(error,
    1561                               RPSL_ERRLVL_ERROR,
    1562                               RPSL_ERR_ATTRSINGLE,
    1563                               "Attribute \"%s\" already appears in this class",
    1564                               attr_name);
    1565             chk_obj(object);
    1566             chk_attr(attr);
    1567             return 0;
    1568         }
    1569
    1570         /* otherwise we can safely add this attribute */
    1571     }
    1572
    1573     /* update any attribute offsets in the error list */
    1574     err_list = object->errors;
    1575     while (err_list != NULL) {
    1576         err = err_list->data;
    1577         if (err->attr_num >= ofs) {
    1578             /* increment errors from later attributes */
    1579             err->attr_num++;
    1580         }
    1581         err_list = g_list_next(err_list);
    1582     }
    1583
    1584     /* add attribute to attribute list */
    1585     if ((ofs < 0) || (ofs >= num_attr)) {
    1586         renumber_attr(attr, num_attr);
    1587         object->attributes = g_list_append(object->attributes, attr);
    1588     } else {
    1589         /* insert the entry at the appriate offset */
    1590         renumber_attr(attr, ofs);
    1591         object->attributes = g_list_insert(object->attributes, attr, ofs);
    1592         num_attr++;
    1593
    1594         /* renumber entries moved down */
    1595         p = g_list_nth(object->attributes, ofs+1);
    1596         for (i=ofs+1; p != NULL; i++, p = g_list_next(p)) {
    1597             tmp = p->data;
    1598             renumber_attr(tmp, i);
    1599         }
    1600     }
    1601
    1602     /* add attribute to hash table */
    1603     attr_list = g_list_append(attr_list, attr);
    1604     g_hash_table_insert(object->attr_lookup, (void *)attr_name, attr_list);
    1605
    1606     chk_obj(object);
    1607     chk_attr(attr);
    1608
    1609     return 1;
    1610 }
    1611
    1612 int
    1613 rpsl_object_append_attr (rpsl_object_t *object,
         /* [<][>][^][v][top][bottom][index][help] */
    1614                          rpsl_attr_t *attr,
    1615                          rpsl_error_t *error)
    1616 {
    1617     return add_attr_to_object(object, attr, -1, error);
    1618 }
    1619
    1620 int
    1621 rpsl_object_add_attr (rpsl_object_t *object,
         /* [<][>][^][v][top][bottom][index][help] */
    1622                       rpsl_attr_t *attr,
    1623                       gint ofs,
    1624                       rpsl_error_t *error)
    1625 {
    1626     if (ofs <= 0) {
    1627         rpsl_error_assign(error,
    1628                           RPSL_ERRLVL_ERROR,
    1629                           RPSL_ERR_BADOFFSET,
    1630                           "Offset %d is less than 1", ofs);
    1631         return 0;
    1632     } else {
    1633         return add_attr_to_object(object, attr, ofs, error);
    1634     }
    1635 }
    1636
    1637 rpsl_attr_t *
    1638 rpsl_object_remove_attr (rpsl_object_t *object, gint ofs, rpsl_error_t *error)
         /* [<][>][^][v][top][bottom][index][help] */
    1639 {
    1640     gint num_attr;
    1641     rpsl_attr_t *attr;
    1642     const gchar *attr_name;
    1643     const gchar *new_attr_name;
    1644     class_t *class_info;
    1645     const class_attr_t *class_attr_info;
    1646     GList *attr_list;
    1647     GList *p;
    1648     gint i;
    1649     rpsl_attr_t *tmp;
    1650     GList *err_list, *tmp_err_list;
    1651     rpsl_error_t *err;
    1652
    1653     chk_obj(object);
    1654
    1655     num_attr = rpsl_object_get_num_attr(object);
    1656     if ((ofs <= 0) || (ofs >= num_attr)) {
    1657         rpsl_error_assign(error,
    1658                           RPSL_ERRLVL_ERROR,
    1659                           RPSL_ERR_BADOFFSET,
    1660                           "Offset %d not between 1 and %d", ofs, num_attr);
    1661         chk_obj(object);
    1662         return NULL;
    1663     }
    1664     attr = g_list_nth_data(object->attributes, ofs);
    1665     attr_name = rpsl_attr_get_name(attr);
    1666
    1667     /* get class info */
    1668     class_info = object->class_info;
    1669     if (class_info != NULL) {    /* we must check valid classes... */
    1670
    1671         /* verify this attribute can be removed */
    1672         class_attr_info = class_attr_lookup(class_info, attr_name);
    1673         if ((class_attr_info != NULL) &&
    1674             (class_attr_info->choice == ATTR_MANDATORY))
    1675         {
    1676             rpsl_error_assign(error,
    1677                               RPSL_ERRLVL_ERROR,
    1678                               RPSL_ERR_ATTRNOTALLOWED,
    1679                               "Attribute \"%s\" is required in this class",
    1680                               attr_name);
    1681         }
    1682     }
    1683
    1684     /* remove from list and renumber */
    1685     object->attributes = g_list_remove(object->attributes, attr);
    1686     for (i=0, p=object->attributes; p != NULL; i++, p = g_list_next(p)) {
    1687         tmp = p->data;
    1688         renumber_attr(tmp, i);
    1689     }
    1690
    1691     /* remove from hash table */
    1692     attr_list = g_hash_table_lookup(object->attr_lookup, attr_name);
    1693     assert(attr_list != NULL);
    1694     g_hash_table_remove(object->attr_lookup, attr_name);
    1695     attr_list = g_list_remove(attr_list, attr);
    1696     if (attr_list != NULL) {
    1697         new_attr_name = rpsl_attr_get_name((rpsl_attr_t *)attr_list->data);
    1698         g_hash_table_insert(object->attr_lookup,
    1699                             (void *)new_attr_name,
    1700                             attr_list);
    1701     }
    1702
    1703     /* fix any attribute offsets in the error list */
    1704     err_list = object->errors;
    1705     while (err_list != NULL) {
    1706         err = err_list->data;
    1707         if (err->attr_num == ofs) {
    1708             /* remove errors from this attribute */
    1709             /* XXX: is this safe? should I just scan from the beginning? */
    1710             tmp_err_list = g_list_next(err_list);
    1711             object->errors = g_list_remove_link(object->errors, err_list);
    1712             g_free(err->descr);
    1713             g_free(err);
    1714             g_list_free(err_list);
    1715             err_list = tmp_err_list;
    1716         } else if (err->attr_num > ofs) {
    1717             /* decrement errors from later attributes */
    1718             err->attr_num--;
    1719             err_list = g_list_next(err_list);
    1720         } else {
    1721             /* ignore earlier attributes */
    1722             err_list = g_list_next(err_list);
    1723         }
    1724     }
    1725
    1726     chk_obj(object);
    1727     chk_attr(attr);
    1728
    1729     return attr;
    1730 }
    1731
    1732 rpsl_attr_t *
    1733 rpsl_object_remove_attr_name (rpsl_object_t *object,
         /* [<][>][^][v][top][bottom][index][help] */
    1734                               const gchar *name,
    1735                               rpsl_error_t *error)
    1736 {
    1737     GList *attr_list;
    1738     rpsl_attr_t *attr;
    1739     rpsl_attr_t *retval;
    1740
    1741     chk_obj(object);
    1742
    1743     attr_list = g_hash_table_lookup(object->attr_lookup, name);
    1744     if (attr_list == NULL) {
    1745         rpsl_error_assign(error,
    1746                           RPSL_ERRLVL_ERROR,
    1747                           RPSL_ERR_NOSUCHATTR,
    1748                           "Attribute \"%s\" not in this object",
    1749                           name);
    1750         return NULL;
    1751     }
    1752     attr = attr_list->data;
    1753
    1754     retval = rpsl_object_remove_attr(object, attr->num, error);
    1755
    1756     chk_obj(object);
    1757     if (retval != NULL) {
    1758         chk_attr(retval);
    1759     }
    1760
    1761     return retval;
    1762 }
    1763
    1764 const GList *
    1765 rpsl_object_errors (const rpsl_object_t *object)
         /* [<][>][^][v][top][bottom][index][help] */
    1766 {
    1767     chk_obj(object);
    1768
    1769     return object->errors;
    1770 }
    1771
    1772 gboolean
    1773 rpsl_attr_is_required (const rpsl_object_t *object, const gchar *attr)
         /* [<][>][^][v][top][bottom][index][help] */
    1774 {
    1775     const class_attr_t *class_attr_info;
    1776
    1777     chk_obj(object);
    1778
    1779     class_attr_info = class_attr_lookup(object->class_info, attr);
    1780     return (class_attr_info != NULL) &&
    1781            (class_attr_info->choice == ATTR_MANDATORY);
    1782 }
    1783
    1784 gboolean
    1785 rpsl_attr_is_generated (const rpsl_object_t *object, const gchar *attr)
         /* [<][>][^][v][top][bottom][index][help] */
    1786 {
    1787     const class_attr_t *class_attr_info;
    1788
    1789     chk_obj(object);
    1790
    1791     class_attr_info = class_attr_lookup(object->class_info, attr);
    1792     return (class_attr_info != NULL) &&
    1793            (class_attr_info->choice == ATTR_GENERATED);
    1794 }
    1795
    1796 gboolean
    1797 rpsl_attr_is_multivalued (const rpsl_object_t *object, const gchar *attr)
         /* [<][>][^][v][top][bottom][index][help] */
    1798 {
    1799     const class_attr_t *class_attr_info;
    1800
    1801     chk_obj(object);
    1802
    1803     class_attr_info = class_attr_lookup(object->class_info, attr);
    1804     return (class_attr_info == NULL) ||
    1805            (class_attr_info->number == ATTR_MULTIPLE);
    1806 }
    1807
    1808 gboolean
    1809 rpsl_attr_is_lookup (const rpsl_object_t *object, const gchar *attr)
         /* [<][>][^][v][top][bottom][index][help] */
    1810 {
    1811     const class_attr_t *class_attr_info;
    1812     const attribute_t *attr_info;
    1813
    1814     chk_obj(object);
    1815
    1816     class_attr_info = class_attr_lookup(object->class_info, attr);
    1817     if (class_attr_info == NULL) {
    1818         return FALSE;
    1819     } else {
    1820         attr_info = attribute_lookup_by_offset(class_attr_info->offset);
    1821         assert(attr_info != NULL);
    1822         return attr_info->is_lookup || attr_info->is_inverse;
    1823     }
    1824 }
    1825
    1826 gboolean
    1827 rpsl_attr_is_key (const rpsl_object_t *object, const gchar *attr)
         /* [<][>][^][v][top][bottom][index][help] */
    1828 {
    1829     const class_attr_t *class_attr_info;
    1830     const attribute_t *attr_info;
    1831
    1832     chk_obj(object);
    1833
    1834     class_attr_info = class_attr_lookup(object->class_info, attr);
    1835     if (class_attr_info == NULL) {
    1836         return FALSE;
    1837     } else {
    1838         attr_info = attribute_lookup_by_offset(class_attr_info->offset);
    1839         assert(attr_info != NULL);
    1840         return attr_info->is_primary;
    1841     }
    1842 }
    1843
    1844 gboolean
    1845 rpsl_object_is_deleted (const rpsl_object_t *object)
         /* [<][>][^][v][top][bottom][index][help] */
    1846 {
    1847     GList *attr_list;
    1848
    1849     chk_obj(object);
    1850
    1851     attr_list = g_hash_table_lookup(object->attr_lookup, "delete");
    1852     if (attr_list != NULL) {
    1853         return TRUE;
    1854     } else {
    1855         return FALSE;
    1856     }
    1857 }
    1858
    1859 static gboolean
    1860 search_errors (const GList *errors, int error_level)
         /* [<][>][^][v][top][bottom][index][help] */
    1861 {
    1862     rpsl_error_t *e;
    1863
    1864     while (errors != NULL) {
    1865         e = errors->data;
    1866         if (e->level >= error_level) {
    1867             return TRUE;
    1868         }
    1869         errors = g_list_next(errors);
    1870     }
    1871     return FALSE;
    1872 }
    1873
    1874
    1875 gboolean
    1876 rpsl_attr_has_error (const rpsl_attr_t *attr, int error_level)
         /* [<][>][^][v][top][bottom][index][help] */
    1877 {
    1878     chk_attr(attr);
    1879
    1880     return search_errors(attr->errors, error_level);
    1881 }
    1882
    1883 gboolean
    1884 rpsl_object_has_error (const rpsl_object_t *object, int error_level)
         /* [<][>][^][v][top][bottom][index][help] */
    1885 {
    1886     chk_obj(object);
    1887
    1888     return search_errors(object->errors, error_level);
    1889 }
    1890
    1891 gint
    1892 rpsl_get_attr_id (const gchar *attr_name)
         /* [<][>][^][v][top][bottom][index][help] */
    1893 {
    1894     const attribute_t *attr_info;
    1895     gboolean is_ambiguous;
    1896
    1897     attr_info = attribute_lookup(attr_name, &is_ambiguous);
    1898     if (attr_info == NULL) {
    1899         return -1;
    1900     } else {
    1901         return attr_info->id;
    1902     }
    1903 }
    1904
    1905 gint
    1906 rpsl_get_class_id (const gchar *class_name)
         /* [<][>][^][v][top][bottom][index][help] */
    1907 {
    1908     const class_t *class_info;
    1909
    1910     if (class_name == NULL) {
    1911         return -1;
    1912     }
    1913
    1914     class_info = class_lookup(class_name);
    1915     if (class_info == NULL) {
    1916         return -1;
    1917     } else {
    1918         return class_info->id;
    1919     }
    1920 }
    1921
    1922 void
    1923 rpsl_load_dictionary (int level)
         /* [<][>][^][v][top][bottom][index][help] */
    1924 {
    1925     rpsl_level = level;
    1926 }
    1927
    1928 int
    1929 rpsl_read_dictionary ()
         /* [<][>][^][v][top][bottom][index][help] */
    1930 {
    1931     return rpsl_level;
    1932 }
    1933
    1934 #if RUNTIME_CHECK
    1935 static void
    1936 rpsl_error_check (const GList *errors, const char *file, int line)
         /* [<][>][^][v][top][bottom][index][help] */
    1937 {
    1938     const rpsl_error_t *err;
    1939
    1940     while (errors != NULL) {
    1941         err = errors->data;
    1942         switch (err->level) {
    1943             case RPSL_ERRLVL_NONE:
    1944             case RPSL_ERRLVL_DEBUG:
    1945             case RPSL_ERRLVL_INFO:
    1946             case RPSL_ERRLVL_NOTICE:
    1947             case RPSL_ERRLVL_WARN:
    1948             case RPSL_ERRLVL_ERROR:
    1949             case RPSL_ERRLVL_CRIT:
    1950             case RPSL_ERRLVL_FATAL:
    1951                 break;
    1952             default:
    1953                 fprintf(stderr, "rpsl_error_check: bad level %d at %s:%d\n",
    1954                         err->level, file, line);
    1955                 exit(1);
    1956         }
    1957         /* XXX: could check attr-codes ONLY appear in attr, and so on */
    1958         switch (err->code) {
    1959             case RPSL_ERR_BADATTR:
    1960             case RPSL_ERR_UNKNOWNATTR:
    1961             case RPSL_ERR_EMPTYLIST:
    1962             case RPSL_ERR_EMPTYATTR:
    1963             case RPSL_ERR_SYNERR:
    1964             case RPSL_ERR_ONLYCOMMENTS:
    1965             case RPSL_ERR_BADCLASS:
    1966             case RPSL_ERR_UNKNOWNCLASS:
    1967             case RPSL_ERR_ATTRNOTALLOWED:
    1968             case RPSL_ERR_ATTRSINGLE:
    1969             case RPSL_ERR_ATTRGENERATED:
    1970             case RPSL_ERR_MISSINGATTR:
    1971             case RPSL_ERR_MISSINGKEY:
    1972             case RPSL_ERR_BADOFFSET:
    1973             case RPSL_ERR_NOSUCHATTR:
    1974                 break;
    1975             default:
    1976                 fprintf(stderr, "rpsl_error_check: bad code %d at %s:%d\n",
    1977                         err->code, file, line);
    1978                 exit(1);
    1979         }
    1980         if (err->descr == NULL) {
    1981             fprintf(stderr, "rpsl_error_check: NULL descr at %s:%d\n",
    1982                     file, line);
    1983             exit(1);
    1984         }
    1985         /* XXX: should check attr_num is within object */
    1986         if (err->attr_num < -1) {
    1987             fprintf(stderr, "rpsl_error_check: bad attr_num %d at %s:%d\n",
    1988                     err->attr_num, file, line);
    1989             exit(1);
    1990         }
    1991         errors = g_list_next(errors);
    1992     }
    1993 }
    1994
    1995 static void
    1996 rpsl_attr_check (const rpsl_attr_t *attr, const char *file, int line)
         /* [<][>][^][v][top][bottom][index][help] */
    1997 {
    1998     const GList *errors;
    1999     const rpsl_error_t *err;
    2000
    2001     if (attr == NULL) {
    2002         fprintf(stderr, "rpsl_attr_check: NULL attr at %s:%d\n",
    2003                 file, line);
    2004         exit(1);
    2005     }
    2006     if (attr->name == NULL) {
    2007         fprintf(stderr, "rpsl_attr_check: NULL name at %s:%d\n",
    2008                 file, line);
    2009         exit(1);
    2010     }
    2011     if (attr->lcase_name == NULL) {
    2012         fprintf(stderr, "rpsl_attr_check: NULL name at %s:%d\n",
    2013                 file, line);
    2014         exit(1);
    2015     }
    2016     if (attr->value == NULL) {
    2017         fprintf(stderr, "rpsl_attr_check: NULL value at %s:%d\n",
    2018                 file, line);
    2019         exit(1);
    2020     }
    2021     rpsl_error_check(attr->errors, file, line);
    2022     if (attr->num < -1) {
    2023         fprintf(stderr, "rpsl_attr_check: bad num %d at %s:%d\n",
    2024                 attr->num, file, line);
    2025         exit(1);
    2026     }
    2027     for (errors=attr->errors; errors != NULL; errors=g_list_next(errors)) {
    2028         err = errors->data;
    2029         if (err->attr_num != -1) {
    2030             fprintf(stderr,
    2031                     "rpsl_attr_check: attr_num (%d) != -1 at %s:%d\n",
    2032                     err->attr_num, file, line);
    2033             exit(1);
    2034         }
    2035     }
    2036     /* XXX: think of a check for attr_info.... */
    2037 }
    2038
    2039 /* XXX: could also verify keys - but that's a bit extreme */
    2040 static void
    2041 count_attr_in_list (gpointer key, gpointer value, gpointer user_data)
         /* [<][>][^][v][top][bottom][index][help] */
    2042 {
    2043     GList *l;
    2044     int sum;
    2045     int *cnt;
    2046
    2047     sum = 0;
    2048     for (l=value; l != NULL; l = g_list_next(l)) {
    2049         sum++;
    2050     }
    2051     cnt = (int *)user_data;
    2052     *cnt += sum;
    2053 }
    2054
    2055 static void
    2056 rpsl_object_check (const rpsl_object_t *obj, const char *file, int line)
         /* [<][>][^][v][top][bottom][index][help] */
    2057 {
    2058     const GList *l;
    2059     int i;
    2060     const rpsl_attr_t *attr;
    2061     const GList *errors;
    2062     const rpsl_error_t *err;
    2063     int num_attr;
    2064     gboolean attr_in_list;
    2065     int cnt;
    2066
    2067     if (obj == NULL) {
    2068         fprintf(stderr, "rpsl_object_check: NULL object at %s:%d\n",
    2069                 file, line);
    2070         exit(1);
    2071     }
    2072     if (obj->attributes == NULL) {
    2073         fprintf(stderr, "rpsl_object_check: NULL attributes at %s:%d\n",
    2074                 file, line);
    2075         exit(1);
    2076     }
    2077     if (obj->attr_lookup == NULL) {
    2078         fprintf(stderr, "rpsl_object_check: NULL attr_lookup at %s:%d\n",
    2079                 file, line);
    2080         exit(1);
    2081     }
    2082     /* make sure each attribute in the hash is in the list */
    2083     num_attr = g_list_length(obj->attributes);
    2084     cnt = 0;
    2085     g_hash_table_foreach(obj->attr_lookup, count_attr_in_list, &cnt);
    2086     if (num_attr != cnt) {
    2087         fprintf(stderr,
    2088             "rpsl_object_check: list count (%d) != hash count (%d) at %s:%d\n",
    2089             num_attr, cnt,
    2090             file, line);
    2091         exit(1);
    2092     }
    2093     for (l=obj->attributes, i=0; l != NULL; l=g_list_next(l), i++) {
    2094         attr = l->data;
    2095         rpsl_attr_check(attr, file, line);
    2096         /* make sure each attribute is in the hash table */
    2097         l = g_hash_table_lookup(obj->attr_lookup, rpsl_attr_get_name(attr));
    2098         attr_in_list = FALSE;
    2099         while ((l != NULL) && !attr_in_list) {
    2100             if (l->data == attr) {
    2101                 attr_in_list = TRUE;
    2102             }
    2103             l = g_list_next(l);
    2104         }
    2105         if (!attr_in_list) {
    2106             fprintf(stderr,
    2107                     "rpsl_object_check: attr #%d not in hash for %p %s:%d\n",
    2108                     i, obj, file, line);
    2109             exit(1);
    2110         }
    2111         if (attr->num != i) {
    2112             fprintf(stderr,
    2113                 "rpsl_object_check: attr #%d does not match offset %d %s:%d\n",
    2114                     attr->num, i, file, line);
    2115             exit(1);
    2116         }
    2117     }
    2118     rpsl_error_check(obj->errors, file, line);
    2119     for (errors=attr->errors; errors != NULL; errors=g_list_next(errors)) {
    2120         err = errors->data;
    2121         if (err->attr_num >= num_attr) {
    2122             fprintf(stderr,
    2123                 "rpsl_object_check: attr_num (%d) >= num_attr (%d) at %s:%d\n",
    2124                 err->attr_num, num_attr, file, line);
    2125             exit(1);
    2126         }
    2127     }
    2128     /* XXX: think of a check for class_info... */
    2129 }
    2130 #endif /* RUNTIME_CHECK */
    2131
    2132 #ifdef TEST
    2133
    2134 #include <stdio.h>
    2135
    2136 /* for a test, check to make sure our we convert the following values into
    2137    the expected results */
    2138 struct {
    2139     gchar *input;
    2140     gchar *expected_result;
    2141 } test_attr[] = {
    2142     /* all tests on a single-line attributes */
    2143     { "unmodified", "unmodified" },
    2144     { "also unmodified", "also unmodified" },
    2145     { "   leading whitespace", "leading whitespace" },
    2146     { "trailing whitespace ", "trailing whitespace" },
    2147     { "compressed    \t whitespace", "compressed whitespace" },
    2148     { "value   # some comment", "value" },
    2149     { " lots of      stuff# here too  ", "lots of stuff" },
    2150     { "", "" },
    2151     /* basic tests on multi-line attributes */
    2152     { "multiple\n"
    2153       " lines",
    2154       "multiple lines" },
    2155     { "multiple\n"
    2156       "\ttablines",
    2157       "multiple tablines" },
    2158     { "multiple\n"
    2159       "+pluslines",
    2160       "multiple pluslines" },
    2161     { "\n"
    2162       " \n"
    2163       "\t\n"
    2164       "+\n",
    2165       "" },
    2166     /* multi-line whitespace tests */
    2167     { " leading\n"
    2168       " multiline whitespace",
    2169       "leading multiline whitespace" },
    2170     { "\tleading\n"
    2171       "\ttabs multiline",
    2172       "leading tabs multiline" },
    2173     { "\t \tleading\n"
    2174       "++ multiline",
    2175       "leading + multiline" },
    2176     { "trailing\n"
    2177       " multiline   \t",
    2178       "trailing multiline" },
    2179     { "trailing\n"
    2180       "\ttabful multiline     ",
    2181       "trailing tabful multiline" },
    2182     { "trailing\n"
    2183       "+plus multiline\t",
    2184       "trailing plus multiline" },
    2185     { "multiline   \n"
    2186       "    whitespace   \n"
    2187       "+compression",
    2188       "multiline whitespace compression" },
    2189     { "    more \t\tmultiline  \t\n"
    2190       "+  whitespace \t \t\n"
    2191       "+compression   \t",
    2192       "more multiline whitespace compression" },
    2193     /* multi-line comment tests */
    2194     { "There # once was a man from Nantucket,\n"
    2195       "\tWhose nic-hdl # fell in the bitbucket.\n"
    2196       "\t\tHe grabbed his # nic-handle,\n"
    2197       "\t\tAnd made the mail queue # full.\n"
    2198       "\tBut # the mail bounced (we just chucked it).",
    2199       "There Whose nic-hdl He grabbed his And made the mail queue But" },
    2200     { " # this is an evil,\n"
    2201       " # but legal,\n"
    2202       " # thing to do",
    2203       "" },
    2204     { "this # is also \n"
    2205       "+    # legal, but less evil I suppose\n",
    2206       "this" },
    2207
    2208 };
    2209
    2210 #define NUM_TEST_ATTR (sizeof(test_attr) / sizeof(test_attr[0]))
    2211
    2212 int
    2213 main()
         /* [<][>][^][v][top][bottom][index][help] */
    2214 {
    2215     int i;
    2216     gchar *actual_result;
    2217     int num_error;
    2218
    2219     num_error = 0;
    2220
    2221     /* test the attribute_clean() function */
    2222     for (i=0; i<NUM_TEST_ATTR; i++) {
    2223         actual_result = attribute_clean(test_attr[i].input);
    2224         if (strcmp(actual_result, test_attr[i].expected_result) != 0) {
    2225             puts("ERROR: test failed");
    2226             puts("--------[ input ]--------");
    2227             puts(test_attr[i].input);
    2228             puts("---[ expected result ]---");
    2229             puts(test_attr[i].expected_result);
    2230             puts("----[ actual result ]----");
    2231             puts(actual_result);
    2232             puts("-------------------------");
    2233             num_error++;
    2234         }
    2235     }
    2236     if (num_error == 0) {
    2237         printf("SUCCESS: all tests passed\n");
    2238         return 0;
    2239     } else {
    2240         return 1;
    2241     }
    2242 }
    2243
    2244 #endif

/* [<][>][^][v][top][bottom][index][help] */