]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
Implement a basic "writer" infrastructure to emit structured text.
authorAndreas Hofmeister <andi@collax.com>
Sun, 28 Feb 2010 14:24:35 +0000 (15:24 +0100)
committerVincent <bernat@luffy.cx>
Sat, 6 Mar 2010 09:26:46 +0000 (10:26 +0100)
This patch implements a basic plain text writer and uses that in display.c.

src/Makefile.am
src/display.c
src/text_writer.c [new file with mode: 0644]
src/writer.h [new file with mode: 0644]

index d8a94768774208268c002f8590bb504bac538dd6..8770313479f73707f4fcc90cd31c5eac95f70b60 100644 (file)
@@ -15,7 +15,7 @@ lldpd_SOURCES = main.c
 lldpd_LDADD = liblldpd.la
 
 ## lldpctl
-lldpctl_SOURCES = lldpctl.c display.c
+lldpctl_SOURCES = lldpctl.c display.c writer.h text_writer.c
 lldpctl_LDADD = libcommon.la
 
 ## With SNMP...
index 04f539830d2608579b9011abc1ce062cabfa55ae..3c066bdbe948bc210347f907c08501bf26c14561 100644 (file)
@@ -15,6 +15,7 @@
  */
 
 #include "lldpd.h"
+#include "writer.h"
 
 #include <stdio.h>
 #include <unistd.h>
@@ -39,7 +40,69 @@ struct value_string {
        char *string;
 };
 
+static const struct value_string lldpd_protocol_map[] = {
+       { LLDPD_MODE_LLDP,      "LLDP" },
+       { LLDPD_MODE_CDPV1,     "CDPv1"},
+       { LLDPD_MODE_CDPV2,     "CDPv2"},
+       { LLDPD_MODE_EDP,       "EDP" },
+       { LLDPD_MODE_FDP,       "FDP"},
+       { LLDPD_MODE_SONMP,     "SONMP"},
+        { 0, NULL }
+};
+
+static const struct value_string chassis_id_subtype_map[] = {
+       { LLDP_CHASSISID_SUBTYPE_IFNAME,  "ifname"},
+       { LLDP_CHASSISID_SUBTYPE_IFALIAS, "ifalias" },
+       { LLDP_CHASSISID_SUBTYPE_LOCAL,   "local" },
+       { LLDP_CHASSISID_SUBTYPE_LLADDR,  "mac" },
+       { LLDP_CHASSISID_SUBTYPE_ADDR,    "ip" },
+       { LLDP_CHASSISID_SUBTYPE_PORT,    "unhandled" },
+       { LLDP_CHASSISID_SUBTYPE_CHASSIS, "unhandled" },
+       { 0, NULL},
+};
+
+static const struct value_string port_id_subtype_map[] = {
+       { LLDP_PORTID_SUBTYPE_IFNAME,   "ifname"},
+       { LLDP_PORTID_SUBTYPE_IFALIAS,  "ifalias" },
+       { LLDP_PORTID_SUBTYPE_LOCAL,    "local" },
+       { LLDP_PORTID_SUBTYPE_LLADDR,   "mac" },
+       { LLDP_PORTID_SUBTYPE_ADDR,     "ip" },
+       { LLDP_PORTID_SUBTYPE_PORT,     "unhandled" },
+       { LLDP_PORTID_SUBTYPE_AGENTCID, "unhandled" },
+       { 0, NULL},
+};
+
 #ifdef ENABLE_LLDPMED
+static const struct value_string chassis_med_type_map[] = {
+       { LLDPMED_CLASS_I,        "Generic Endpoint (Class I)" },
+       { LLDPMED_CLASS_II,       "Media Endpoint (Class II)" },
+       { LLDPMED_CLASS_III,      "Communication Device Endpoint (Class III)" },
+       { LLDPMED_NETWORK_DEVICE, "Network Connectivity Device" },
+       { 0, NULL },
+};
+
+static const struct value_string lldpmed_capabilit_map[] = {
+       {LLDPMED_CAP_CAP,       "Capabilities"},
+       {LLDPMED_CAP_POLICY,    "Policy"},
+       {LLDPMED_CAP_LOCATION,  "Location"},
+       {LLDPMED_CAP_MDI_PSE,   "MDI/PSE"},
+       {LLDPMED_CAP_MDI_PD,    "MDI/PD"},
+       {LLDPMED_CAP_IV,        "Inventory"},
+       { 0, NULL },
+};
+
+static const struct value_string port_med_policy_map[] = {
+       { LLDPMED_APPTYPE_VOICE ,           "Voice"},
+       { LLDPMED_APPTYPE_VOICESIGNAL,      "Voice Signaling"},
+       { LLDPMED_APPTYPE_GUESTVOICE,       "Guest Voice"},
+       { LLDPMED_APPTYPE_GUESTVOICESIGNAL, "Guest Voice Signaling"},
+       { LLDPMED_APPTYPE_SOFTPHONEVOICE,   "Softphone Voice"},
+       { LLDPMED_APPTYPE_VIDEOCONFERENCE,  "Video Conferencing"},
+       { LLDPMED_APPTYPE_VIDEOSTREAM,      "Streaming Video"},
+       { LLDPMED_APPTYPE_VIDEOSIGNAL,      "Video Signaling"},
+       { 0, NULL },
+};
+
 static const struct value_string civic_address_type_values[] = {
         { 0,    "Language" },
         { 1,    "National subdivisions" },
@@ -65,6 +128,61 @@ static const struct value_string civic_address_type_values[] = {
         { 128,  "Script" },
         { 0, NULL }
 };
+
+static const struct value_string civic_address_type_tags[] = {
+        { 0,    "language" },
+        { 1,    "country-subdivision" },
+        { 2,    "county" },
+        { 3,    "city" },
+        { 4,    "city-division" },
+        { 5,    "block" },
+        { 6,    "street" },
+        { 16,   "direction" },
+        { 17,   "street-suffix" },
+        { 18,   "street-suffix" },
+        { 19,   "number" },
+        { 20,   "number-suffix" },
+        { 21,   "landmark" },
+        { 22,   "additional" },
+        { 23,   "name" },
+        { 24,   "zip" },
+        { 25,   "building" },
+        { 26,   "unit" },
+        { 27,   "floor" },
+        { 28,   "room" },
+        { 29,   "place-type" },
+        { 128,  "Script" },
+        { 0, NULL }
+};
+
+static const struct value_string port_med_geoid_map[] = {
+       { 1, "WGS84" },
+       { 2, "NAD83" },
+       { 3, "NAD83/MLLW" },
+       { 0, NULL },
+};
+
+static const struct value_string port_med_pow_devicetype_map[] = {
+       { LLDPMED_POW_TYPE_PSE, "PSE Device" },
+       { LLDPMED_POW_TYPE_PD,  "PD Device" },
+       { 0, NULL },
+};
+
+static const struct value_string port_med_pow_source_map[] = {
+       { LLDPMED_POW_SOURCE_PRIMARY, "Primary Power Source" },
+       { LLDPMED_POW_SOURCE_BACKUP,  "Backup Power Source / Power Conservation Mode" },
+       { LLDPMED_POW_SOURCE_PSE,     "PSE" },
+       { LLDPMED_POW_SOURCE_LOCAL,   "Local"},
+       { LLDPMED_POW_SOURCE_BOTH,    "PSE + Local"},
+       { 0, NULL },
+};
+
+static const struct value_string port_med_pow_priority_map[] = {
+       { LLDPMED_POW_PRIO_CRITICAL, "critical" },
+       { LLDPMED_POW_PRIO_HIGH,     "high" },
+       { LLDPMED_POW_PRIO_LOW,      "low" },
+       { 0, NULL },
+};
 #endif
 
 #ifdef ENABLE_DOT3
@@ -126,6 +244,42 @@ static const struct value_string operational_mau_type_values[] = {
 };
 #endif
 
+static const struct value_string chassis_capability_map[] = {
+       { LLDP_CAP_OTHER,    "Other" },
+       { LLDP_CAP_REPEATER, "Repeater"},
+       { LLDP_CAP_BRIDGE,   "Bridge"},
+       { LLDP_CAP_ROUTER,   "Router"},
+       { LLDP_CAP_WLAN,     "Wlan"},
+       { LLDP_CAP_TELEPHONE,"Telephone"},
+       { LLDP_CAP_DOCSIS,   "Docsis"},
+       { LLDP_CAP_STATION,  "Station"},
+       { 0, NULL},
+};
+
+
+static const char*
+map_lookup(const struct value_string * list, int n)
+{
+
+       unsigned int i;
+
+       for( i = 0; list[i].string != NULL; i ++ ) {
+               if( list[i].value == n ) {
+                       return list[i].string;
+               }
+       }
+
+       return "unknown";
+}
+
+static char*
+u2str(unsigned n)
+{
+       static char buf[21];
+       snprintf(buf, sizeof(buf), "%u", n);
+       return buf;
+}
+
 static char*
 dump(void *data, int size, int max, char sep)
 {
@@ -282,36 +436,21 @@ get_nb_port(int s, char *interface)
 }
 
 static void
-display_cap(struct lldpd_chassis *chassis, u_int8_t bit, char *symbol)
-{
-       if (chassis->c_cap_available & bit)
-               printf("%s(%c) ", symbol,
-                   (chassis->c_cap_enabled & bit)?'E':'d');
-}
-
-static void
-pretty_print(char *string)
+display_cap(struct writer * w, struct lldpd_chassis *chassis, u_int8_t bit, char *symbol)
 {
-       char *s = NULL;
-       if (((s = strchr(string, '\n')) == NULL) && (strlen(string) < 60)) {
-               printf("%s\n", string);
-               return;
-       } else
-               printf("\n");
-       while (s != NULL) {
-               *s = '\0';
-               printf("   %s\n", string);
-               *s = '\n';
-               string = s + 1;
-               s = strchr(string, '\n');
+       if (chassis->c_cap_available & bit) {
+               tag_start(w, "capability", "Capability");
+               tag_attr (w, "type", "", symbol );
+               tag_attr (w, "enabled", "", (chassis->c_cap_enabled & bit)?"on":"off"); 
+               tag_end  (w);
        }
-       printf("   %s\n", string);
 }
 
 #ifdef ENABLE_LLDPMED
 static int
-display_fixed_precision(u_int64_t value, int intpart, int floatpart, int displaysign)
+display_fixed_precision(u_int64_t value, int intpart, int floatpart, int displaysign, char ** res)
 {
+       static char buf[64]; 
        u_int64_t tmp = value;
        int negative = 0;
        u_int32_t integer = 0;
@@ -323,178 +462,169 @@ display_fixed_precision(u_int64_t value, int intpart, int floatpart, int display
        integer = (u_int32_t)((tmp &
                (((1ULL << intpart)-1) << floatpart)) >> floatpart);
        tmp = (tmp & ((1<< floatpart) - 1))*10000/(1ULL << floatpart);
-       printf("%s%u.%04llu", displaysign?(negative?"-":"+"):"",
+       snprintf(buf, sizeof(buf),"%s%u.%04llu", displaysign?(negative?"-":"+"):"",
            integer, (unsigned long long int)tmp);
+
+       *res = buf;
+
        return negative;
 }
 
 static void
-display_latitude_or_longitude(int option, u_int64_t value)
+display_latitude_or_longitude(struct writer *w, int option, u_int64_t value)
 {
+       static char buf[70]; 
        int negative;
-       negative = display_fixed_precision(value, 9, 25, 0);
+       char * str;
+
+       if ( option == 0 ) {
+               tag_start(w, "lat", "Latitude");
+       } else {
+               tag_start(w, "lon", "Longitude");
+       }
+       negative = display_fixed_precision(value, 9, 25, 0, &str);
        if (option == 0)
-               printf("%s", negative?" South":" North");
+               snprintf(buf, sizeof(buf), "%s %s", str, negative?" S":" N");
        else
-               printf("%s", negative?" West":" East");
+               snprintf(buf, sizeof(buf), "%s %s", str, negative?" W":" E");
+
+       tag_data(w, buf);
+       tag_end(w);
 }
 
 static void
-display_med(struct lldpd_chassis *chassis, struct lldpd_port *port)
+display_med_capability(struct writer *w, struct lldpd_chassis *chassis, int cap)
+{
+       if (chassis->c_med_cap_available & cap) {
+               tag_start(w, "capability", "Capability");
+               tag_attr(w, "type", "",
+                       map_lookup(lldpmed_capabilit_map, cap));
+               tag_end(w);
+       }
+}
+
+static void
+display_med(struct writer *w, struct lldpd_chassis *chassis, struct lldpd_port *port)
 {
        int i;
        char *value;
-       printf(" LLDP-MED Device Type: ");
-       switch (chassis->c_med_type) {
-       case LLDPMED_CLASS_I:
-               printf("Generic Endpoint (Class I)");
-               break;
-       case LLDPMED_CLASS_II:
-               printf("Media Endpoint (Class II)");
-               break;
-       case LLDPMED_CLASS_III:
-               printf("Communication Device Endpoint (Class III)");
-               break;
-       case LLDPMED_NETWORK_DEVICE:
-               printf("Network Connectivity Device");
-               break;
-       default:
-               printf("Unknown (%d)", chassis->c_med_type);
-               break;
-       }
-       printf("\n LLDP-MED Capabilities:");
-       if (chassis->c_med_cap_available & LLDPMED_CAP_CAP)
-               printf(" Capabilities");
-       if (chassis->c_med_cap_available & LLDPMED_CAP_POLICY)
-               printf(" Policy");
-       if (chassis->c_med_cap_available & LLDPMED_CAP_LOCATION)
-               printf(" Location");
-       if (chassis->c_med_cap_available & LLDPMED_CAP_MDI_PSE)
-               printf(" MDI/PSE");
-       if (chassis->c_med_cap_available & LLDPMED_CAP_MDI_PD)
-               printf(" MDI/PD");
-       if (chassis->c_med_cap_available & LLDPMED_CAP_IV)
-               printf(" Inventory");
-       printf("\n");
+
+       tag_start(w, "lldp-med", "LLDP-MED");
+
+       tag_datatag(w, "device-type", "Device Type",
+               map_lookup(chassis_med_type_map, chassis->c_med_type));
+
+       display_med_capability(w, chassis, LLDPMED_CAP_CAP);
+       display_med_capability(w, chassis, LLDPMED_CAP_POLICY);
+       display_med_capability(w, chassis, LLDPMED_CAP_LOCATION);
+       display_med_capability(w, chassis, LLDPMED_CAP_MDI_PSE);
+       display_med_capability(w, chassis, LLDPMED_CAP_MDI_PD);
+       display_med_capability(w, chassis, LLDPMED_CAP_IV);
+
        for (i = 0; i < LLDPMED_APPTYPE_LAST; i++) {
                if (i+1 == port->p_med_policy[i].type) {
-                       printf(" LLDP-MED Network Policy for ");
-                       switch(port->p_med_policy[i].type) {
-                       case LLDPMED_APPTYPE_VOICE:
-                               printf("Voice");
-                               break;
-                       case LLDPMED_APPTYPE_VOICESIGNAL:
-                               printf("Voice Signaling");
-                               break;
-                       case LLDPMED_APPTYPE_GUESTVOICE:
-                               printf("Guest Voice");
-                               break;
-                       case LLDPMED_APPTYPE_GUESTVOICESIGNAL:
-                               printf("Guest Voice Signaling");
-                               break;
-                       case LLDPMED_APPTYPE_SOFTPHONEVOICE:
-                               printf("Softphone Voice");
-                               break;
-                       case LLDPMED_APPTYPE_VIDEOCONFERENCE:
-                               printf("Video Conferencing");
-                               break;
-                       case LLDPMED_APPTYPE_VIDEOSTREAM:
-                               printf("Streaming Video");
-                               break;
-                       case LLDPMED_APPTYPE_VIDEOSIGNAL:
-                               printf("Video Signaling");
-                               break;
-                       default:
-                               printf("Reserved");
-                       }
-                       printf(":\n  Policy:           ");
-                       if (port->p_med_policy[i].unknown) {
-                               printf("unknown, ");
-                       } else {
-                               printf("defined, ");
-                       }
-                       if (!port->p_med_policy[i].tagged) {
-                               printf("un");
-                       }
-                       printf("tagged");
-                       printf("\n  VLAN ID:          ");
-                       if (port->p_med_policy[i].vid == 0) {
-                               printf("Priority Tagged");
-                       } else if (port->p_med_policy[i].vid == 4095) {
-                               printf("reserved");
-                       } else {
-                               printf("%u", port->p_med_policy[i].vid);
+                       tag_start(w, "policy", "LLDP-MED Network Policy for");
+                       tag_attr(w, "apptype", "AppType",
+                                u2str(port->p_med_policy[i].type));
+                       tag_attr(w, "defined", "Defined",
+                                (port->p_med_policy[i].unknown)?"no":"yes");
+
+                       tag_datatag(w, "descr", "",
+                           map_lookup(port_med_policy_map, port->p_med_policy[i].type));
+
+                       if (port->p_med_policy[i].tagged) {
+                               tag_start(w, "vlan", "VLAN");
+                               if (port->p_med_policy[i].vid == 0) {
+                                       tag_attr(w, "vid", "", "priority");
+                               } else if (port->p_med_policy[i].vid == 4095) {
+                                       tag_attr(w, "vid", "", "reserved");
+                               } else {
+                                       tag_attr(w, "vid", "",
+                                                u2str(port->p_med_policy[i].vid));
+                               }
+                               tag_end(w);
                        }
-                       printf("\n  Layer 2 Priority: ");
-                       printf("%u", port->p_med_policy[i].priority);
-                       printf("\n  DSCP Value:       ");
-                       printf("%u\n", port->p_med_policy[i].dscp);
+
+                       tag_datatag(w, "priority", "Layer 2 Priority",
+                                   u2str(port->p_med_policy[i].priority));
+
+                       tag_datatag(w, "dscp", "DSCP Value",
+                                   u2str(port->p_med_policy[i].dscp));
+
+                       tag_end(w);
                }
        }
        for (i = 0; i < LLDPMED_LOCFORMAT_LAST; i++) {
                if (i+1 == port->p_med_location[i].format) {
-                       printf(" LLDP-MED Location Identification: ");
+                       tag_start(w, "location", "LLDP-MED Location Identification");
+
                        switch(port->p_med_location[i].format) {
                        case LLDPMED_LOCFORMAT_COORD:
-                               printf("\n   Coordinate-based data: ");
+                               tag_attr(w, "type", "Type", "coordinates");
+
                                if (port->p_med_location[i].data_len != 16)
-                                       printf("bad data length");
+                                       LLOG_WARN("bad data length");
                                else {
                                        u_int64_t l;
+                                       u_int8_t  v;
+                                       char *    s;
+
+                                       v = *(u_int8_t*)(port->p_med_location[i].data + 15);
+                                       tag_attr(w, "geoid", "Geoid",
+                                                map_lookup(port_med_geoid_map,v));
 
                                        /* Latitude and longitude */
                                        memcpy(&l, port->p_med_location[i].data,
                                            sizeof(u_int64_t));
                                        l = (ntohll(l) &
                                            0x03FFFFFFFF000000ULL) >> 24;
-                                       display_latitude_or_longitude(0, l);
-                                       printf(", ");
+                                       display_latitude_or_longitude(w,0, l);
                                        memcpy(&l, port->p_med_location[i].data + 5,
                                            sizeof(u_int64_t));
                                        l = (ntohll(l) &
                                            0x03FFFFFFFF000000ULL) >> 24;
-                                       display_latitude_or_longitude(1, l);
+                                       display_latitude_or_longitude(w,1, l);
 
                                        /* Altitude */
-                                       printf(", ");
                                        memcpy(&l, port->p_med_location[i].data + 10,
                                            sizeof(u_int64_t));
                                        l = (ntohll(l) &
                                            0x3FFFFFFF000000ULL) >> 24;
-                                       display_fixed_precision(l, 22, 8, 1);
+                                       display_fixed_precision(l, 22, 8, 1, &s);
+
+                                       tag_start(w, "altitude", "Altitude");
                                        switch ((*(u_int8_t*)(port->p_med_location[i].data +
                                                    10)) & 0xf0) {
                                        case (1 << 4):
-                                               printf(" meters"); break;
+                                               tag_attr(w, "unit", "", "m");
+                                               break;
                                        case (2 << 4):
-                                               printf(" floors"); break;
+                                               tag_attr(w, "unit", "", "floor");
+                                               break;
                                        default:
-                                               printf(" (unknown)");
+                                               tag_attr(w, "unit", "", "unknown");
                                        }
+                                       tag_data(w,s);
+                                       tag_end(w);
 
-                                       /* Datum */
-                                       switch (*(u_int8_t*)(port->p_med_location[i].data +
-                                                   15)) {
-                                       case 1:
-                                               printf(", WGS84"); break;
-                                       case 2:
-                                               printf(", NAD83"); break;
-                                       case 3:
-                                               printf(", NAD83/MLLW"); break;
-                                       }
                                }
                                break;
                        case LLDPMED_LOCFORMAT_CIVIC:
-                               printf("Civic address: ");
+                               tag_attr(w, "type", "Type", "address");
+
                                if ((port->p_med_location[i].data_len < 3) ||
                                    (port->p_med_location[i].data_len - 1 !=
                                        *(u_int8_t*)port->p_med_location[i].data))
-                                       printf("bad data length");
+                                       LLOG_WARN("bad data length");
                                else {
-                                       int l = 4, n, catype, calength, j = 0;
-                                       printf("\n%28s: %c%c", "Country",
-                                           ((char *)port->p_med_location[i].data)[2],
-                                           ((char *)port->p_med_location[i].data)[3]);
+                                       int l = 4, n, catype, calength; 
+                                       char country[3];
+                                       country[0] = ((char *)port->p_med_location[i].data)[2];
+                                       country[1] = ((char *)port->p_med_location[i].data)[3];
+                                       country[2] = 0;
+
+                                       tag_datatag(w, "country", "Country", country);
+
                                        while ((n = (port->
                                                    p_med_location[i].data_len - l)) >= 2) {
                                                catype = *(u_int8_t*)(port->
@@ -502,29 +632,20 @@ display_med(struct lldpd_chassis *chassis, struct lldpd_port *port)
                                                calength = *(u_int8_t*)(port->
                                                    p_med_location[i].data + l + 1);
                                                if (n < 2 + calength) {
-                                                       printf("bad data length");
-                                                       break;
-                                               }
-                                               for (j = 0;
-                                                    civic_address_type_values[j].string != NULL;
-                                                    j++) {
-                                                       if (civic_address_type_values[j].value ==
-                                                           catype)
-                                                               break;
-                                               }
-                                               if (civic_address_type_values[j].string == NULL) {
-                                                       printf("unknown type %d", catype);
+                                                       LLOG_WARN("bad data length");
                                                        break;
                                                }
+
                                                if ((value = strndup((char *)(port->
                                                        p_med_location[i].data + l + 2),
                                                            calength)) == NULL) {
-                                                       printf("not enough memory");
+                                                       fatalx("not enough memory");
                                                        break;
                                                }
-                                               printf("\n%28s: %s",
-                                                   civic_address_type_values[j].string,
-                                                   value);
+                                               tag_datatag(w,
+                                                       map_lookup(civic_address_type_tags,catype),
+                                                       map_lookup(civic_address_type_values,catype),
+                                                       value);
                                                free(value);
                                                l += 2 + calength;
                                        }
@@ -534,69 +655,42 @@ display_med(struct lldpd_chassis *chassis, struct lldpd_port *port)
                                if ((value = strndup((char *)(port->
                                                p_med_location[i].data),
                                            port->p_med_location[i].data_len)) == NULL) {
-                                       printf("not enough memory");
+                                       fatalx( "not enough memory");
                                        break;
                                }
-                               printf("ECS ELIN: %s", value);
+                               tag_attr(w, "type", "Type", "elin");
+                               tag_datatag(w, "ecs", "ECS ELIN", value);
                                free(value);
                                break;
                        default:
-                               printf("unknown location data format: \n   %s",
+                               LLOG_WARN("unknown location data format: \n   %s",
                                    dump(port->p_med_location[i].data,
                                        port->p_med_location[i].data_len, 20, ' '));
                        }
-                       printf("\n");
+                       tag_end(w);
                }
        }
        if (port->p_med_pow_devicetype) {
-               printf(" LLDP-MED Extended Power-over-Ethernet:\n");
-               printf("  Power Type & Source: ");
-               switch (port->p_med_pow_devicetype) {
-               case LLDPMED_POW_TYPE_PSE:
-                       printf("PSE Device");
-                       break;
-               case LLDPMED_POW_TYPE_PD:
-                       printf("PD Device");
-                       break;
-               default:
-                       printf("reserved");
-               }
-               switch (port->p_med_pow_source) {
-               case LLDPMED_POW_SOURCE_UNKNOWN:
-               case LLDPMED_POW_SOURCE_RESERVED:
-                       printf(", unknown"); break;
-               case LLDPMED_POW_SOURCE_PRIMARY:
-                       printf(", Primary Power Source");
-                       break;
-               case LLDPMED_POW_SOURCE_BACKUP:
-                       printf(", Backup Power Source / Power Conservation Mode");
-                       break;
-               case LLDPMED_POW_SOURCE_PSE:
-                       printf(", PSE"); break;
-               case LLDPMED_POW_SOURCE_LOCAL:
-                       printf(", local"); break;
-               case LLDPMED_POW_SOURCE_BOTH:
-                       printf(", PSE & local");
-                       break;
-               }
-               printf("\n  Power Priority:      ");
-               switch (port->p_med_pow_priority) {
-               case LLDPMED_POW_PRIO_CRITICAL:
-                       printf("critical"); break;
-               case LLDPMED_POW_PRIO_HIGH:
-                       printf("high"); break;
-               case LLDPMED_POW_PRIO_LOW:
-                       printf("low"); break;
-               default:
-                       printf("unknown");
-               }
-               printf("\n  Power Value:         ");
+               tag_start(w, "poe", "Extended Power-over-Ethernet");
+
+               tag_start(w, "device-type", "Power Type & Source");
+               tag_data(w, map_lookup(port_med_pow_devicetype_map, port->p_med_pow_devicetype));
+               tag_end(w);
+
+               tag_start(w, "source", "Power Source");
+               tag_data(w, map_lookup(port_med_pow_source_map, port->p_med_pow_source));
+               tag_end(w);
+               
+               tag_start(w, "priority", "Power Priority");
+               tag_data(w, map_lookup(port_med_pow_priority_map, port->p_med_pow_priority));
+               tag_end(w);
+
                if(port->p_med_pow_val < 1024) {
-                       printf("%u mW", port->p_med_pow_val * 100);
-               } else {
-                       printf("reserved");
+                       tag_start(w, "power", "Power Value");
+                       tag_data(w, u2str(port->p_med_pow_val * 100));
+                       tag_end(w);
                }
-               printf("\n");
+               tag_end(w);
        }
        if (chassis->c_med_hw ||
            chassis->c_med_sw ||
@@ -605,206 +699,208 @@ display_med(struct lldpd_chassis *chassis, struct lldpd_port *port)
            chassis->c_med_manuf ||
            chassis->c_med_model ||
            chassis->c_med_asset) {
-               printf(" LLDP-MED Inventory:\n");
+               tag_start(w, "inventory", "Inventory");
+
                if (chassis->c_med_hw)
-                       printf("   Hardware Revision: %s\n", chassis->c_med_hw);
+                       tag_datatag(w, "hardware", "Hardware Revision",
+                                       chassis->c_med_hw);
                if (chassis->c_med_sw)
-                       printf("   Software Revision: %s\n", chassis->c_med_sw);
+                       tag_datatag(w, "software", "Software Revision",
+                                       chassis->c_med_sw);
                if (chassis->c_med_fw)
-                       printf("   Firmware Revision: %s\n", chassis->c_med_fw);
+                       tag_datatag(w, "firmware", "Firmware Revision",
+                                       chassis->c_med_fw);
                if (chassis->c_med_sn)
-                       printf("   Serial Number:     %s\n", chassis->c_med_sn);
+                       tag_datatag(w, "serial", "Serial Number",
+                                       chassis->c_med_sn);
                if (chassis->c_med_manuf)
-                       printf("   Manufacturer:      %s\n",
-                              chassis->c_med_manuf);
+                       tag_datatag(w, "manufacturer", "Manufacturer",
+                                       chassis->c_med_manuf);
                if (chassis->c_med_model)
-                       printf("   Model:             %s\n",
-                              chassis->c_med_model);
+                       tag_datatag(w, "model", "Model",
+                                       chassis->c_med_model);
                if (chassis->c_med_asset)
-                       printf("   Asset ID:          %s\n",
-                              chassis->c_med_asset);
+                       tag_datatag(w, "asset", "Asset ID",
+                                       chassis->c_med_asset);
+
+               tag_end(w);
        }
+
+       tag_end(w);
 }
 #endif
 
 static void
-display_chassis(struct lldpd_chassis *chassis)
+display_chassis(struct writer * w, struct lldpd_chassis *chassis)
 {
        char *cid;
        if ((cid = (char *)malloc(chassis->c_id_len + 1)) == NULL)
                fatal(NULL);
        memcpy(cid, chassis->c_id, chassis->c_id_len);
        cid[chassis->c_id_len] = 0;
+
+       tag_start(w, "chassis", "Chassis");
+       tag_start(w, "id", "ChassisID");
+       tag_attr (w, "type", "", map_lookup(chassis_id_subtype_map, chassis->c_id_subtype));
+
        switch (chassis->c_id_subtype) {
        case LLDP_CHASSISID_SUBTYPE_IFNAME:
-               printf(" ChassisID: %s (ifName)\n", cid);
-               break;
        case LLDP_CHASSISID_SUBTYPE_IFALIAS:
-               printf(" ChassisID: %s (ifAlias)\n", cid);
-               break;
        case LLDP_CHASSISID_SUBTYPE_LOCAL:
-               printf(" ChassisID: %s (local)\n", cid);
+               tag_data (w, cid);
                break;
        case LLDP_CHASSISID_SUBTYPE_LLADDR:
-               printf(" ChassisID: %s (MAC)\n",
-                   dump(chassis->c_id, chassis->c_id_len, ETH_ALEN, ':'));
+               tag_data(w, dump(chassis->c_id, chassis->c_id_len, ETH_ALEN, ':'));
                break;
        case LLDP_CHASSISID_SUBTYPE_ADDR:
                if (*(u_int8_t*)chassis->c_id == 1) {
-                       printf(" ChassisID: %s (IP)\n",
-                           inet_ntoa(*(struct in_addr*)(chassis->c_id +
-                                   1)));
+                       tag_data(w, inet_ntoa(*(struct in_addr*)(chassis->c_id + 1)));
                        break;
                }
        case LLDP_CHASSISID_SUBTYPE_PORT:
        case LLDP_CHASSISID_SUBTYPE_CHASSIS:
        default:
-               printf(" ChassisID: %s (unhandled type)\n",
-                   dump(chassis->c_id, chassis->c_id_len, 16, ' '));
+               tag_data(w, dump(chassis->c_id, chassis->c_id_len, 16, ' '));
        }
-       printf(" SysName:   %s\n", chassis->c_name);
-       printf(" SysDescr:  "); pretty_print(chassis->c_descr);
+
+       tag_end(w);
+       
+       tag_datatag(w, "name", "SysName", chassis->c_name);
+       tag_datatag(w, "descr", "SysDescr", chassis->c_descr);
+
        if (chassis->c_mgmt.s_addr != INADDR_ANY)
-               printf(" MgmtIP:    %s\n", inet_ntoa(chassis->c_mgmt));
-       printf(" Caps:      ");
-       display_cap(chassis, LLDP_CAP_OTHER, "Other");
-       display_cap(chassis, LLDP_CAP_REPEATER, "Repeater");
-       display_cap(chassis, LLDP_CAP_BRIDGE, "Bridge");
-       display_cap(chassis, LLDP_CAP_ROUTER, "Router");
-       display_cap(chassis, LLDP_CAP_WLAN, "Wlan");
-       display_cap(chassis, LLDP_CAP_TELEPHONE, "Tel");
-       display_cap(chassis, LLDP_CAP_DOCSIS, "Docsis");
-       display_cap(chassis, LLDP_CAP_STATION, "Station");
-       printf("\n");
+               tag_datatag(w, "mgmt-ip", "MgmtIP", inet_ntoa(chassis->c_mgmt));
+
+       display_cap(w, chassis, LLDP_CAP_OTHER, "Other");
+       display_cap(w, chassis, LLDP_CAP_REPEATER, "Repeater");
+       display_cap(w, chassis, LLDP_CAP_BRIDGE, "Bridge");
+       display_cap(w, chassis, LLDP_CAP_ROUTER, "Router");
+       display_cap(w, chassis, LLDP_CAP_WLAN, "Wlan");
+       display_cap(w, chassis, LLDP_CAP_TELEPHONE, "Tel");
+       display_cap(w, chassis, LLDP_CAP_DOCSIS, "Docsis");
+       display_cap(w, chassis, LLDP_CAP_STATION, "Station");
+
+       tag_end(w);
 }
 
 #ifdef ENABLE_DOT3
 static void
-display_autoneg(struct lldpd_port *port, int bithd, int bitfd, char *desc)
+display_autoneg(struct writer * w, struct lldpd_port *port, int bithd, int bitfd, char *desc)
 {
        if (!((port->p_autoneg_advertised & bithd) ||
                (port->p_autoneg_advertised & bitfd)))
                return;
-       printf("%s ", desc);
-       if (port->p_autoneg_advertised & bithd) {
-               printf("(HD");
-               if (port->p_autoneg_advertised & bitfd) {
-                       printf(", FD) ");
-                       return;
-               }
-               printf(") ");
-               return;
-       }
-       printf("(FD) ");
+
+       tag_start(w, "advertised", "Adv");
+       tag_attr(w, "type", "", desc);
+       tag_attr(w, "hd", "HD", (port->p_autoneg_advertised & bithd)?"yes":"no");
+       tag_attr(w, "fd", "FD", (port->p_autoneg_advertised)?"yes":"no");
+       tag_end (w);
 }
 #endif
 
 static void
-display_port(struct lldpd_port *port)
+display_port(struct writer * w, struct lldpd_port *port)
 {
        char *pid;
        struct in_addr address;
-#ifdef ENABLE_DOT3
-       int i;
-#endif
 
        if ((pid = (char *)malloc(port->p_id_len + 1)) == NULL)
                fatal(NULL);
        memcpy(pid, port->p_id, port->p_id_len);
        pid[port->p_id_len] = 0;
+
+       tag_start(w, "port", "Port");
+       tag_start(w, "id", "PortID");
+       tag_attr (w, "type", "", map_lookup(port_id_subtype_map, port->p_id_subtype));
+
        switch (port->p_id_subtype) {
        case LLDP_PORTID_SUBTYPE_IFNAME:
-               printf(" PortID:    %s (ifName)\n", pid);
-               break;
        case LLDP_PORTID_SUBTYPE_IFALIAS:
-               printf(" PortID:    %s (ifAlias)\n", pid);
-               break;
        case LLDP_PORTID_SUBTYPE_LOCAL:
-               printf(" PortID:    %s (local)\n", pid);
+               tag_data (w, pid);
                break;
        case LLDP_PORTID_SUBTYPE_LLADDR:
-               printf(" PortID:    %s (MAC)\n",
-                   dump(port->p_id, port->p_id_len, ETH_ALEN, ':'));
+               tag_data(w, dump(port->p_id, port->p_id_len, ETH_ALEN, ':'));
                break;
        case LLDP_PORTID_SUBTYPE_ADDR:
                if (*(u_int8_t*)port->p_id == 1) {
                        memcpy(&address, port->p_id + 1,
                            sizeof(struct in_addr));
-                       printf(" PortID:    %s (IP)\n",
-                           inet_ntoa(address));
+                       tag_data(w, inet_ntoa(address));
                        break;
                }
        case LLDP_PORTID_SUBTYPE_PORT:
        case LLDP_PORTID_SUBTYPE_AGENTCID:
        default:
-               printf(" ChassisID: %s (unhandled type)\n",
-                   dump(port->p_id, port->p_id_len, 16, ' '));
+               tag_data(w, dump(port->p_id, port->p_id_len, 16, ' '));
        }
-       printf(" PortDescr: "); pretty_print(port->p_descr);
+
+       tag_end(w);
+
+       tag_datatag(w, "descr", "PortDescr", port->p_descr);
+
 #ifdef ENABLE_DOT3
        if (port->p_mfs)
-               printf(" MFS:       %d bytes\n", port->p_mfs);
+               tag_datatag(w, "mfs", "MFS", u2str(port->p_mfs));
+
        if (port->p_aggregid)
-               printf("\n   Port is aggregated. PortAggregID:  %d\n",
-                   port->p_aggregid);
+               tag_datatag(w, "aggregation", " Port is aggregated. PortAggregID",
+                           u2str(port->p_aggregid));
 
        if (port->p_autoneg_support || port->p_autoneg_enabled ||
            port->p_mau_type) {
-               printf("\n   Autoneg: %ssupported/%senabled\n",
-                   port->p_autoneg_support?"":"not ",
-                   port->p_autoneg_enabled?"":"not ");
+               tag_start(w, "auto-negotiation", "PMD autoneg");
+               tag_attr (w, "supported", "supported", port->p_autoneg_support?"yes":"no");
+               tag_attr (w, "enabled", "enabled", port->p_autoneg_enabled?"yes":"no");
+
                if (port->p_autoneg_enabled) {
-                       printf("   PMD autoneg: ");
-                       display_autoneg(port, LLDP_DOT3_LINK_AUTONEG_10BASE_T,
+                       display_autoneg(w, port, LLDP_DOT3_LINK_AUTONEG_10BASE_T,
                            LLDP_DOT3_LINK_AUTONEG_10BASET_FD,
                            "10Base-T");
-                       display_autoneg(port, LLDP_DOT3_LINK_AUTONEG_100BASE_TX,
+                       display_autoneg(w, port, LLDP_DOT3_LINK_AUTONEG_100BASE_TX,
                            LLDP_DOT3_LINK_AUTONEG_100BASE_TXFD,
                            "100Base-T");
-                       display_autoneg(port, LLDP_DOT3_LINK_AUTONEG_100BASE_T2,
+                       display_autoneg(w, port, LLDP_DOT3_LINK_AUTONEG_100BASE_T2,
                            LLDP_DOT3_LINK_AUTONEG_100BASE_T2FD,
                            "100Base-T2");
-                       display_autoneg(port, LLDP_DOT3_LINK_AUTONEG_1000BASE_X,
+                       display_autoneg(w, port, LLDP_DOT3_LINK_AUTONEG_1000BASE_X,
                            LLDP_DOT3_LINK_AUTONEG_1000BASE_XFD,
                            "100Base-X");
-                       display_autoneg(port, LLDP_DOT3_LINK_AUTONEG_1000BASE_T,
+                       display_autoneg(w, port, LLDP_DOT3_LINK_AUTONEG_1000BASE_T,
                            LLDP_DOT3_LINK_AUTONEG_1000BASE_TFD,
                            "1000Base-T");
-                       printf("\n");
                }
-               printf("   MAU oper type: ");
-               for (i = 0; operational_mau_type_values[i].value != 0; i++) {
-                       if (operational_mau_type_values[i].value ==
-                           port->p_mau_type) {
-                               printf("%s\n", operational_mau_type_values[i].string);
-                               break;
-                       }
-               }
-               if (operational_mau_type_values[i].value == 0)
-                       printf("unknown (%d)\n", port->p_mau_type);
+               tag_datatag(w, "current", "MAU oper type",
+                       map_lookup(operational_mau_type_values, port->p_mau_type));
+               tag_end(w);
        }
 #endif
+       tag_end(w);
 }
 
 #ifdef ENABLE_DOT1
 static void
-display_vlans(struct lldpd_port *port)
+display_vlans(struct writer *w, struct lldpd_port *port)
 {
-       int i = 0;
        int foundpvid = 0;
        struct lldpd_vlan *vlan;
        TAILQ_FOREACH(vlan, &port->p_vlans, v_entries) {
                if (port->p_pvid == vlan->v_vid)
                        foundpvid = 1;
-               printf("  %cVLAN %4d: %-20s%c",
-                   (port->p_pvid == vlan->v_vid)?'*':' ',
-                   vlan->v_vid, vlan->v_name,
-                   (i++ % 2) ? '\n' : ' ');
+
+               tag_start(w, "vlan", "VLAN");
+               tag_attr(w, "vlan-id", "", u2str(vlan->v_vid));
+               if (foundpvid)
+                       tag_attr(w, "pvid", "pvid", "yes");
+               tag_data(w, vlan->v_name);
+               tag_end(w);
+       }
+       if (!foundpvid && port->p_pvid) {
+               tag_start(w, "vlan", "VLAN");
+               tag_attr(w, "vlan-id", "", u2str(port->p_pvid));
+               tag_attr(w, "pvid", "pvid", "yes");
        }
-       if (!foundpvid && port->p_pvid)
-               printf("  *VLAN %4d\n", port->p_pvid);
-       else if (i % 2)
-               printf("\n");
 }
 #endif
 
@@ -828,6 +924,7 @@ display_age(struct lldpd_port *port)
 void
 display_interfaces(int s, int argc, char *argv[])
 {
+       struct writer * w;
        int i, nb;
        struct interfaces ifs;
 #ifdef ENABLE_DOT1
@@ -838,13 +935,14 @@ display_interfaces(int s, int argc, char *argv[])
        struct lldpd_port port;
        char sep[80];
 
+       w = txt_init( stdout );
+
        memset(sep, '-', 79);
        sep[79] = 0;
        get_interfaces(s, &ifs);
+
+       tag_start(w, "lldp", "LLDP neighbors");
        
-       printf("%s\n", sep);
-       printf("    LLDP neighbors\n");
-       printf("%s\n", sep);    
        TAILQ_FOREACH(iff, &ifs, next) {
                if (optind < argc) {
                        for (i = optind; i < argc; i++)
@@ -858,37 +956,31 @@ display_interfaces(int s, int argc, char *argv[])
                        if (!((get_chassis(s, &chassis, iff->name, i) != -1) &&
                                (get_port(s, &port, iff->name, i) != -1)))
                                continue;
-                       printf("Interface: %s (via ", iff->name);
-                       switch (port.p_protocol) {
-                       case (LLDPD_MODE_LLDP): printf("LLDP"); break;
-                       case (LLDPD_MODE_CDPV1): printf("CDPv1"); break;
-                       case (LLDPD_MODE_CDPV2): printf("CDPv2"); break;
-                       case (LLDPD_MODE_EDP): printf("EDP"); break;
-                       case (LLDPD_MODE_FDP): printf("FDP"); break;
-                       case (LLDPD_MODE_SONMP): printf("SONMP"); break;
-                       default: printf("unknown protocol"); break;
-                       }
-                       printf(") - RID: %d", chassis.c_index);
-                       printf(" - Time: %s\n", display_age(&port));
-                       display_chassis(&chassis);
-                       printf("\n");
-                       display_port(&port);
+                       tag_start(w, "interface", "Interface");
+                       tag_attr(w, "name", "", iff->name );
+                       tag_attr(w, "via" , "via", map_lookup(lldpd_protocol_map, port.p_protocol));
+                       tag_attr(w, "rid" , "RID", u2str(chassis.c_index));
+                       tag_attr(w, "age" , "Time", display_age(&port));
+
+                       display_chassis(w,&chassis);
+                       display_port(w, &port);
 #ifdef ENABLE_DOT1
                        if (get_vlans(s, &vls, iff->name, i) != -1)
                                memcpy(&port.p_vlans, &vls, sizeof(struct vlans));
                        if (!TAILQ_EMPTY(&port.p_vlans) || port.p_pvid) {
-                               printf("\n");
-                               display_vlans(&port);
+                               display_vlans(w, &port);
                        }
 #endif
 #ifdef ENABLE_LLDPMED
                        if (port.p_med_cap_enabled) {
-                               printf("\n");
-                               display_med(&chassis, &port);
+                               display_med(w, &chassis, &port);
                        }
 #endif
-                       printf("%s\n", sep);
+                       tag_end(w); /* interface */
                }
        }
+
+       tag_end(w);
+       w->finish(w);
 }
 
diff --git a/src/text_writer.c b/src/text_writer.c
new file mode 100644 (file)
index 0000000..b83ec9c
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2010 Andreas Hofmeister <andi@collax.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "writer.h"
+#include "lldpd.h"
+
+static char sep[] = "-------------------------------------------------------------------------------";
+
+struct txt_writer_private {
+       FILE *  fh;
+       int     level;
+       int     attrs;
+};
+
+void txt_start(struct writer * w , const char * tag, const char * descr ) {
+       struct txt_writer_private * p = w->priv;
+       int i = 0;
+       char buf[128];
+
+       if ( p->level == 0 ) {
+               fprintf( p->fh, "%s\n", sep );
+       } else {
+               fprintf( p->fh, "\n" );
+       }
+
+       for( i = 1; i < p->level; i ++ ) {
+               fprintf( p->fh, "  " );
+       }
+
+       snprintf(buf,sizeof(buf),"%s:", descr);
+       fprintf( p->fh, "%-13s", buf);
+
+       if ( p->level == 0 )
+               fprintf( p->fh, "\n%s", sep );
+
+       p->level ++;
+       p->attrs = 0;
+}
+
+void txt_attr(struct writer * w, const char * tag, const char * descr, const char * value ) {
+       struct txt_writer_private * p = w->priv;
+
+       if ( descr == NULL || strlen( descr ) == 0 ) {
+               fprintf( p->fh, "%s%s", (p->attrs > 0 ? ", " : " "), value );
+       } else {
+               fprintf( p->fh, "%s%s: %s", (p->attrs > 0 ? ", " : " "), descr, value );
+       }
+
+       p->attrs ++;
+}
+
+void txt_data(struct writer * w, const char * data) {
+       struct txt_writer_private * p = w->priv;
+       fprintf( p->fh, " %s", data );
+}
+
+void txt_end(struct writer * w) {
+       struct txt_writer_private * p = w->priv;
+       p->level --;
+
+       if ( p->level == 1 )
+               fprintf( p->fh, "\n%s", sep );
+}
+
+void txt_finish(struct writer * w) {
+       struct txt_writer_private * p = w->priv;
+
+       fprintf(p->fh, "\n");
+
+       free( w->priv );
+       w->priv = NULL;
+
+       free( w );
+}
+
+struct writer * txt_init(FILE * fh) {
+
+       struct writer * result;
+       struct txt_writer_private * priv;
+
+       priv = malloc( sizeof( *priv ) );
+       if ( ! priv ) {
+               fatalx("out of memory");
+               return NULL;
+       }
+
+       priv->fh = fh;
+       priv->level = 0;
+       priv->attrs = 0;
+
+       result = malloc( sizeof( struct writer ) );
+       if ( ! result ) {
+               fatalx("out of memory");
+               return NULL;
+       }
+
+       result->priv  = priv;
+       result->start = txt_start;
+       result->attr  = txt_attr;
+       result->data  = txt_data;
+       result->end   = txt_end;
+       result->finish= txt_finish;
+
+       return result;
+}
+
diff --git a/src/writer.h b/src/writer.h
new file mode 100644 (file)
index 0000000..4af1379
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010 Andreas Hofmeister <andi@collax.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _WRITER_H
+#define _WRITER_H
+
+#include <stdio.h>
+
+struct writer {
+       void    * priv;
+       void    (*start)(struct writer *, const char * tag, const char * descr);
+       void    (*attr)(struct writer *, const char * tag, const char * descr, const char * value);
+       void    (*data)(struct writer *, const char * data);
+       void    (*end)(struct writer *);
+       void    (*finish)(struct writer *);
+};
+
+#define tag_start(w,...)       w->start(w,## __VA_ARGS__)
+#define tag_attr(w,...)                w->attr(w,## __VA_ARGS__)
+#define tag_data(w,...)                w->data(w,## __VA_ARGS__)
+#define tag_end(w,...)         w->end(w,## __VA_ARGS__)
+#define tag_datatag(w,t,d,...) { w->start(w,t,d); w->data(w,## __VA_ARGS__); w->end(w); }
+
+extern struct writer * txt_init( FILE * );
+
+#endif /* _WRITER_H */