#define MAX_FIRST_LINE_LENGTH 200
#define MAX_TIMESTAMP_LINE_LENGTH 100
#define MAX_LINE_LENGTH 32000
+#define MAX_TIMESTAMP_LEN 32
#define MAX_SECONDS_CHARS 16
#define MAX_SUBSECOND_DECIMALS 4
#define MAX_CONTEXT_NAME 64
#define MAX_PROTOCOL_NAME 64
#define MAX_PORT_DIGITS 2
#define MAX_VARIANT_DIGITS 32
-#define MAX_OUTHDR_NAME 64
+#define MAX_OUTHDR_NAME 256
#define AAL_HEADER_CHARS 12
/* TODO:
+ - support for FP over AAL0
+ - support for IuR interface FP
- support for x.25?
*/
} packet_direction_t;
+/***********************************************************************/
+/* For each line, store (in case we need to dump): */
+/* - String before time field */
+/* - String beween time field and data (if NULL assume " l ") */
typedef struct
{
gchar *before_time;
gchar *after_time;
} line_prefix_info_t;
+
/*******************************************************************/
/* Information stored external to a file (wtap) needed for dumping */
typedef struct dct2000_file_externals
gint secondline_length;
/* Hash table to store text prefix data part of displayed packets.
- Records (file offset -> pre-data-prefix-string)
+ Records (file offset -> line_prefix_info_t)
N.B. This is only needed for dumping
*/
- GHashTable *line_header_prefixes_table;
+ GHashTable *packet_prefix_table;
} dct2000_file_externals_t;
-/* This global table maps wtap -> file_external structs */
+/* This global table maps wtap -> dct2000_file_externals_t structs */
static GHashTable *file_externals_table = NULL;
/************************************************************/
-/* Functions called from wiretap */
+/* Functions called from wiretap core */
static gboolean catapult_dct2000_read(wtap *wth, int *err, gchar **err_info,
gint64 *data_offset);
static gboolean catapult_dct2000_seek_read(wtap *wth, gint64 seek_off,
packet_direction_t *direction,
int *encap);
static int write_stub_header(guchar *frame_buffer, char *timestamp_string,
- packet_direction_t direction, int encap);
+ packet_direction_t direction, int encap);
static guchar hex_from_char(gchar c);
static gchar char_from_hex(guchar hex);
static void set_ppp_info(union wtap_pseudo_header *pseudo_header,
packet_direction_t direction);
+static gint wth_equal(gconstpointer v, gconstpointer v2);
+static guint wth_hash_func(gconstpointer v);
+static gint packet_offset_equal(gconstpointer v, gconstpointer v2);
+static guint packet_offset_hash_func(gconstpointer v);
-static gint prefix_equal(gconstpointer v, gconstpointer v2);
-static guint prefix_hash_func(gconstpointer v);
static gboolean get_file_time_stamp(time_t *secs, guint32 *usecs);
static gboolean free_line_prefix_info(gpointer key, gpointer value, gpointer user_data);
/********************************************/
-/* Open file */
+/* Open file (for reading) */
/********************************************/
int catapult_dct2000_open(wtap *wth, int *err, gchar **err_info _U_)
{
gint64 offset = 0;
time_t timestamp;
guint32 usecs;
- gint firstline_length;
+ gint firstline_length = 0;
dct2000_file_externals_t *file_externals;
/* Clear errno before reading from the file */
errno = 0;
- /*********************************************************************/
- /* Need entry in file_externals table */
-
- /* Create file externals table if it doesn't yet exist */
- if (file_externals_table == NULL)
- {
- file_externals_table = g_hash_table_new(prefix_hash_func, prefix_equal);
- }
-
-
/********************************************************************/
/* First line needs to contain at least as many characters as magic */
}
- /* Allocate a new file_externals structure */
+ /*********************************************************************/
+ /* Need entry in file_externals table */
+
+ /* Create file externals table if it doesn't yet exist */
+ if (file_externals_table == NULL)
+ {
+ file_externals_table = g_hash_table_new(wth_hash_func, wth_equal);
+ }
+
+ /* Allocate a new file_externals structure for this file */
file_externals = g_malloc(sizeof(dct2000_file_externals_t));
memset((void*)file_externals, '\0', sizeof(dct2000_file_externals_t));
/* Copy this first line into buffer so could write out later */
- strncpy(file_externals->firstline, linebuff, firstline_length);
+ g_strlcpy(file_externals->firstline, linebuff, firstline_length+1);
file_externals->firstline_length = firstline_length;
return 0;
}
+ /* Allocate struct and fill in timestamp */
wth->capture.catapult_dct2000 = g_malloc(sizeof(catapult_dct2000_t));
wth->capture.catapult_dct2000->start_secs = timestamp;
wth->capture.catapult_dct2000->start_usecs = usecs;
/* Copy this second line into buffer so could write out later */
- strncpy(file_externals->secondline, linebuff, file_externals->secondline_length);
+ g_strlcpy(file_externals->secondline, linebuff, file_externals->secondline_length+1);
/************************************************************/
wth->tsprecision = WTAP_FILE_TSPREC_USEC;
- /**********************************************/
- /* Initialise line_header_prefixes_table */
- file_externals->line_header_prefixes_table =
- g_hash_table_new(prefix_hash_func, prefix_equal);
+ /***************************************************************/
+ /* Initialise packet_prefix_table (index is offset into file) */
+ file_externals->packet_prefix_table =
+ g_hash_table_new(packet_offset_hash_func, packet_offset_equal);
/* Add file_externals for this wtap into the global table */
g_hash_table_insert(file_externals_table,
/**************************************************/
-/* Read function. */
+/* Read packet function. */
/* Look for and read the next usable packet */
/* - return TRUE and details if found */
/**************************************************/
return FALSE;
}
- /* Search for a line containing a usable message */
+ /* Search for a line containing a usable packet */
while (1)
{
int line_length, seconds, useconds, data_chars;
/* Read a new line from file into linebuff */
if (read_new_line(wth->fh, &offset, &line_length) == FALSE)
{
- /* Get out when no more lines to be read */
+ /* Get out if no more lines can be read */
break;
}
- /* Try to parse the line as a message */
+ /* Try to parse the line as a frame record */
if (parse_line(line_length, &seconds, &useconds,
&before_time_offset, &after_time_offset,
&dollar_offset,
int n;
int stub_offset = 0;
line_prefix_info_t *line_prefix_info;
- char timestamp_string[32];
- sprintf(timestamp_string, "%d.%04d", seconds, useconds/100);
+ char timestamp_string[MAX_TIMESTAMP_LEN+1];
+ gint64 *pkey = NULL;
+
+ g_snprintf(timestamp_string, 32, "%d.%04d", seconds, useconds/100);
/* All packets go to Catapult DCT2000 stub dissector */
wth->phdr.pkt_encap = WTAP_ENCAP_CATAPULT_DCT2000;
/* Store the packet prefix in the hash table */
line_prefix_info = g_malloc(sizeof(line_prefix_info_t));
+ /* Create and use buffer for contents before time */
line_prefix_info->before_time = g_malloc(before_time_offset+1);
- strncpy(line_prefix_info->before_time, linebuff, before_time_offset);
+ g_strlcpy(line_prefix_info->before_time, linebuff, before_time_offset);
line_prefix_info->before_time[before_time_offset] = '\0';
- line_prefix_info->after_time = g_malloc(dollar_offset - after_time_offset);
- strncpy(line_prefix_info->after_time, linebuff+after_time_offset,
- dollar_offset - after_time_offset);
- line_prefix_info->after_time[dollar_offset - after_time_offset-1] = '\0';
+ /* Create and use buffer for contents before time.
+ Do this only if it doesn't correspond to " l ", which is by far the most
+ common case. */
+ if (((size_t)(dollar_offset - after_time_offset -1) == strlen(" l ")) &&
+ (strncmp(linebuff+after_time_offset, " l ", strlen(" l ")) == 0))
+ {
+ line_prefix_info->after_time = NULL;
+ }
+ else
+ {
+ /* Allocate & write buffer for line between timestamp and data */
+ line_prefix_info->after_time = g_malloc(dollar_offset - after_time_offset);
+ g_strlcpy(line_prefix_info->after_time, linebuff+after_time_offset,
+ dollar_offset - after_time_offset);
+ line_prefix_info->after_time[dollar_offset - after_time_offset-1] = '\0';
+ }
/* Add packet entry into table */
- g_hash_table_insert(file_externals->line_header_prefixes_table,
- (void*)this_offset, line_prefix_info);
-
+ pkey = g_malloc(sizeof(*pkey));
+ *pkey = this_offset;
+ g_hash_table_insert(file_externals->packet_prefix_table, pkey, line_prefix_info);
/* Set pseudo-header if necessary */
set_pseudo_header_info(wth, encap, this_offset, &wth->pseudo_header,
return FALSE;
}
- /* Re-read whole line (this should succeed) */
+ /* Re-read whole line (this really should succeed) */
if (read_new_line(wth->random_fh, &offset, &length) == FALSE)
{
return FALSE;
int n;
int stub_offset = 0;
char timestamp_string[32];
- sprintf(timestamp_string, "%d.%04d", seconds, useconds/100);
+ g_snprintf(timestamp_string, 32, "%d.%04d", seconds, useconds/100);
/* Make sure all packets go to catapult dct2000 dissector */
wth->phdr.pkt_encap = WTAP_ENCAP_CATAPULT_DCT2000;
/* If get here, must have failed */
*err = errno;
*err_info = g_strdup_printf("catapult dct2000: seek_read failed to read/parse "
- "line at position %lld", seek_off);
+ "line at position %" G_GINT64_MODIFIER "d",
+ seek_off);
return FALSE;
}
-/******************************************/
-/* Free dct2000-specific capture info */
-/******************************************/
+/***************************************************************************/
+/* Free dct2000-specific capture info from file that was open for reading */
+/***************************************************************************/
void catapult_dct2000_close(wtap *wth)
{
/* Look up externals for this file */
}
/* Free up its line prefix values */
- g_hash_table_foreach_remove(file_externals->line_header_prefixes_table,
+ g_hash_table_foreach_remove(file_externals->packet_prefix_table,
free_line_prefix_info, NULL);
/* Free up its line prefix table */
- g_hash_table_destroy(file_externals->line_header_prefixes_table);
+ g_hash_table_destroy(file_externals->packet_prefix_table);
/* And remove the externals entry from the global table */
g_hash_table_remove(file_externals_table, (void*)wth);
case WTAP_ENCAP_CATAPULT_DCT2000:
/* We support this */
return 0;
+
default:
+ /* But don't write to any other formats... */
return WTAP_ERR_UNSUPPORTED_ENCAP;
}
}
/*****************************************/
/* Write a single packet out to the file */
/*****************************************/
+
+static gboolean do_fwrite(const void *data, size_t size, size_t count, FILE *stream, int *err_p)
+{
+ size_t nwritten;
+
+ nwritten = fwrite(data, size, count, stream);
+ if (nwritten != count) {
+ if (nwritten == 0 && ferror(stream))
+ {
+ *err_p = errno;
+ }
+ else
+ {
+ *err_p = WTAP_ERR_SHORT_WRITE;
+ }
+
+ return FALSE;
+ }
+ return TRUE;
+}
+
gboolean catapult_dct2000_dump(wtap_dumper *wdh, const struct wtap_pkthdr *phdr,
const union wtap_pseudo_header *pseudo_header,
- const guchar *pd, int *err _U_)
+ const guchar *pd, int *err)
{
guint32 n;
line_prefix_info_t *prefix = NULL;
wdh->dump.dct2000 = g_malloc(sizeof(catapult_dct2000_t));
/* Write out saved first line */
- fwrite(file_externals->firstline, 1, file_externals->firstline_length, wdh->fh);
- fwrite("\n", 1, 1, wdh->fh);
+ if (! do_fwrite(file_externals->firstline, 1, file_externals->firstline_length, wdh->fh, err))
+ {
+ return FALSE;
+ }
+ if (! do_fwrite("\n", 1, 1, wdh->fh, err))
+ {
+ return FALSE;
+ }
/* Also write out saved second line with timestamp corresponding to the
opening time of the log.
*/
- fwrite(file_externals->secondline, 1, file_externals->secondline_length, wdh->fh);
- fwrite("\n", 1, 1, wdh->fh);
+ if (! do_fwrite(file_externals->secondline, 1, file_externals->secondline_length, wdh->fh, err))
+ {
+ return FALSE;
+ }
+ if (! do_fwrite("\n", 1, 1, wdh->fh, err))
+ {
+ return FALSE;
+ }
/* Allocate the dct2000-specific dump structure */
wdh->dump.dct2000 = g_malloc(sizeof(catapult_dct2000_t));
/* Write out this packet's prefix, including calculated timestamp */
/* Look up line data prefix using stored offset */
- prefix = (line_prefix_info_t*)g_hash_table_lookup(file_externals->line_header_prefixes_table,
- (void*)pseudo_header->dct2000.seek_off);
+ prefix = (line_prefix_info_t*)g_hash_table_lookup(file_externals->packet_prefix_table,
+ (const void*)&(pseudo_header->dct2000.seek_off));
/* Write out text before timestamp */
- fwrite(prefix->before_time, 1, strlen(prefix->before_time), wdh->fh);
+ if (! do_fwrite(prefix->before_time, 1, strlen(prefix->before_time), wdh->fh, err))
+ {
+ return FALSE;
+ }
/* Calculate time of this packet to write, relative to start of dump */
if (phdr->ts.nsecs >= wdh->dump.dct2000->start_time.nsecs)
}
/* Write out the calculated timestamp */
- fwrite(time_string, 1, strlen(time_string), wdh->fh);
+ if (! do_fwrite(time_string, 1, strlen(time_string), wdh->fh, err))
+ {
+ return FALSE;
+ }
/* Write out text between timestamp and start of hex data */
- fwrite(prefix->after_time, 1, strlen(prefix->after_time), wdh->fh);
+ if (prefix->after_time == NULL)
+ {
+ if (! do_fwrite(" l ", 1, strlen(" l "), wdh->fh, err))
+ {
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (! do_fwrite(prefix->after_time, 1, strlen(prefix->after_time), wdh->fh, err))
+ {
+ return FALSE;
+ }
+ }
/****************************************************************/
/**************************************/
/* Remainder is encapsulated protocol */
- fwrite("$", 1, 1, wdh->fh);
+ if (! do_fwrite("$", 1, 1, wdh->fh, err))
+ {
+ return FALSE;
+ }
/* Each binary byte is written out as 2 hex string chars */
for (; n < phdr->len; n++)
c[1] = char_from_hex((guchar)(pd[n] & 0x0f));
/* Write both hex chars of byte together */
- fwrite(c, 1, 2, wdh->fh);
+ if (! do_fwrite(c, 1, 2, wdh->fh, err))
+ {
+ return FALSE;
+ }
}
/* End the line */
- fwrite("\n", 1, 1, wdh->fh);
+ if (! do_fwrite("\n", 1, 1, wdh->fh, err))
+ {
+ return FALSE;
+ }
return TRUE;
}
int seconds_chars;
char subsecond_decimals_buff[MAX_SUBSECOND_DECIMALS+1];
int subsecond_decimals_chars;
+ int skip_first_byte = FALSE;
gboolean atm_header_present = FALSE;
/* Read context name until find '.' */
for (n=0; linebuff[n] != '.' && (n < MAX_CONTEXT_NAME) && (n+1 < line_length); n++)
{
- if (!isalnum(linebuff[n]) && (linebuff[n] != '_'))
+ if (!isalnum((guchar)linebuff[n]) && (linebuff[n] != '_'))
{
return FALSE;
}
(linebuff[n] != '/') && (port_digits <= MAX_PORT_DIGITS) && (n+1 < line_length);
n++, port_digits++)
{
- if (!isdigit(linebuff[n]))
+ if (!isdigit((guchar)linebuff[n]))
{
return FALSE;
}
(linebuff[n] != '/') && (protocol_chars < MAX_PROTOCOL_NAME) && (n < line_length);
n++, protocol_chars++)
{
- if (!isalnum(linebuff[n]) && linebuff[n] != '_')
+ if (!isalnum((guchar)linebuff[n]) && linebuff[n] != '_')
{
return FALSE;
}
/* Following the / is the variant number. No digits indicate 1 */
for (variant_digits = 0;
- (isdigit(linebuff[n])) && (variant_digits <= MAX_VARIANT_DIGITS) && (n+1 < line_length);
+ (isdigit((guchar)linebuff[n])) && (variant_digits <= MAX_VARIANT_DIGITS) && (n+1 < line_length);
n++, variant_digits++)
{
- if (!isdigit(linebuff[n]))
+ if (!isdigit((guchar)linebuff[n]))
{
return FALSE;
}
}
else
{
- strcpy(variant_name, "1");
+ g_strlcpy(variant_name, "1", MAX_VARIANT_DIGITS+1);
}
n++;
for (outhdr_chars = 0;
- (isdigit(linebuff[n]) || linebuff[n] == ',') &&
+ (isdigit((guchar)linebuff[n]) || linebuff[n] == ',') &&
(outhdr_chars <= MAX_OUTHDR_NAME) && (n+1 < line_length);
n++, outhdr_chars++)
{
- if (!isdigit(linebuff[n]) && (linebuff[n] != ','))
+ if (!isdigit((guchar)linebuff[n]) && (linebuff[n] != ','))
{
return FALSE;
}
/******************************************************************/
/* Now check whether we know how to use a packet of this protocol */
- if ((strcmp(protocol_name, "ip") == 0) || (strcmp(protocol_name, "sctp") == 0))
+ if ((strcmp(protocol_name, "ip") == 0) ||
+ (strcmp(protocol_name, "sctp") == 0) ||
+ (strcmp(protocol_name, "gre") == 0) ||
+ (strcmp(protocol_name, "mipv6") == 0) ||
+ (strcmp(protocol_name, "igmp") == 0))
{
*encap = WTAP_ENCAP_RAW_IP;
}
if ((strcmp(protocol_name, "fp") == 0) ||
(strcmp(protocol_name, "fp_r4") == 0) ||
(strcmp(protocol_name, "fp_r5") == 0) ||
- (strcmp(protocol_name, "fp_r6") == 0))
+ (strcmp(protocol_name, "fp_r6") == 0) ||
+ (strcmp(protocol_name, "fp_r7") == 0))
{
if ((variant > 256) && (variant % 256 == 3))
{
atm_header_present = TRUE;
}
}
+ else if (strcmp(protocol_name, "fpiur_r5") == 0)
+ {
+ /* FP (IuR) over AAL2 */
+ *encap = WTAP_ENCAP_ATM_PDUS_UNTRUNCATED;
+ atm_header_present = TRUE;
+ }
+
else
if (strcmp(protocol_name, "ppp") == 0)
else
if (strcmp(protocol_name, "isdn_l3") == 0)
{
- /* Despite the name, this does seem to correspond to L2... */
+ /* TODO: find out what this byte means... */
+ skip_first_byte = TRUE;
+ *encap = WTAP_ENCAP_ISDN;
+ }
+ else
+ if (strcmp(protocol_name, "isdn_l2") == 0)
+ {
*encap = WTAP_ENCAP_ISDN;
}
else
/* Read consecutive hex chars into atm header buffer */
for (;
- (isalnum(linebuff[n]) &&
+ ((linebuff[n] >= '0') && (linebuff[n] <= '?') &&
(n < line_length) &&
(header_chars_seen < AAL_HEADER_CHARS));
n++, header_chars_seen++)
{
aal_header_chars[header_chars_seen] = linebuff[n];
- }
-
- /* Sometimes see strange encoding of cid in last (non-digit) character */
- if (header_chars_seen == (AAL_HEADER_CHARS-1))
- {
- aal_header_chars[AAL_HEADER_CHARS-1] = linebuff[n];
- header_chars_seen++;
+ /* Next 6 characters after '9' are mapped to a->f */
+ if (!isdigit((guchar)linebuff[n]))
+ {
+ aal_header_chars[header_chars_seen] = 'a' + (linebuff[n] - '9') -1;
+ }
}
if (header_chars_seen != AAL_HEADER_CHARS || n >= line_length)
/* Find and read the timestamp */
/* Now scan to the next digit, which should be the start of the timestamp */
- for (; !isdigit(linebuff[n]) && (n < line_length); n++);
+ for (; !isdigit((guchar)linebuff[n]) && (n < line_length); n++);
if (n >= line_length)
{
return FALSE;
(n < line_length);
n++, seconds_chars++)
{
- if (!isdigit(linebuff[n]))
+ if (!isdigit((guchar)linebuff[n]))
{
/* Found a non-digit before decimal point. Fail */
return FALSE;
(n < line_length);
n++, subsecond_decimals_chars++)
{
- if (!isdigit(linebuff[n]))
+ if (!isdigit((guchar)linebuff[n]))
{
return FALSE;
}
/* Set number of chars that comprise the hex string protocol data */
*data_chars = line_length - n;
- /* Need to skip first byte (2 hex string chars) from ISDN messages.
- TODO: find out what this byte means...
- */
- if (*encap == WTAP_ENCAP_ISDN)
+ /* May need to skip first byte (2 hex string chars) */
+ if (skip_first_byte)
{
*data_offset += 2;
*data_chars -= 2;
}
+
return TRUE;
}
packet_direction_t direction, int encap)
{
int stub_offset = 0;
-
- strcpy((char*)frame_buffer, context_name);
+
+ g_strlcpy((char*)frame_buffer, context_name, MAX_CONTEXT_NAME+1);
stub_offset += (strlen(context_name) + 1);
/* Context port number */
stub_offset++;
/* Timestamp within file */
- strcpy((char*)&frame_buffer[stub_offset], timestamp_string);
+ g_strlcpy((char*)&frame_buffer[stub_offset], timestamp_string, MAX_TIMESTAMP_LEN+1);
stub_offset += (strlen(timestamp_string) + 1);
/* Protocol name */
- strcpy((char*)&frame_buffer[stub_offset], protocol_name);
+ g_strlcpy((char*)&frame_buffer[stub_offset], protocol_name, MAX_PROTOCOL_NAME+1);
stub_offset += (strlen(protocol_name) + 1);
/* Protocol variant number (as string) */
- strcpy((void*)&frame_buffer[stub_offset], variant_name);
+ g_strlcpy((void*)&frame_buffer[stub_offset], variant_name, MAX_VARIANT_DIGITS+1);
stub_offset += (strlen(variant_name) + 1);
/* Outhdr */
- strcpy((char*)&frame_buffer[stub_offset], outhdr_name);
+ g_strlcpy((char*)&frame_buffer[stub_offset], outhdr_name, MAX_OUTHDR_NAME+1);
stub_offset += (strlen(outhdr_name) + 1);
/* Direction */
/* cid is usually last byte. Unless last char is not hex digit, in which
case cid is derived from last char in ascii */
- if (isalnum(aal_header_chars[11]))
+ if (isalnum((guchar)aal_header_chars[11]))
{
pseudo_header->dct2000.inner_pseudo_header.atm.aal2_cid =
((hex_from_char(aal_header_chars[10]) << 4) |
return hex_lookup[hex];
}
-
-/********************************************/
-/* Equality test for line-prefix hash table */
-/********************************************/
-gint prefix_equal(gconstpointer v, gconstpointer v2)
+/***************************************************/
+/* Equality function for file_externals hash table */
+/***************************************************/
+gint wth_equal(gconstpointer v, gconstpointer v2)
{
return (v == v2);
}
+/***********************************************/
+/* Hash function for file_externals hash table */
+/***********************************************/
+guint wth_hash_func(gconstpointer v)
+{
+ return (guint)(unsigned long)v;
+}
+
+
+/***********************************************/
+/* Equality test for packet prefix hash tables */
+/***********************************************/
+gint packet_offset_equal(gconstpointer v, gconstpointer v2)
+{
+ /* Dereferenced pointers must have same gint64 offset value */
+ return (*(const gint64*)v == *(const gint64*)v2);
+}
+
/********************************************/
-/* Hash function for line-prefix hash table */
+/* Hash function for packet-prefix hash table */
/********************************************/
-guint prefix_hash_func(gconstpointer v)
+guint packet_offset_hash_func(gconstpointer v)
{
- /* Just use pointer itself (is actually byte offset of line in file) */
- return (guint)v;
+ /* Use low-order bits of git64 offset value */
+ return (guint)(*(const gint64*)v);
}
}
/* Free the data allocated inside a line_prefix_info_t */
-gboolean free_line_prefix_info(gpointer key _U_, gpointer value,
+gboolean free_line_prefix_info(gpointer key, gpointer value,
gpointer user_data _U_)
{
line_prefix_info_t *info = (line_prefix_info_t*)value;
+ /* Free the 64-bit key value */
+ g_free(key);
+
/* Free the strings inside */
g_free(info->before_time);
- g_free(info->after_time);
+ if (info->after_time)
+ {
+ g_free(info->after_time);
+ }
/* And the structure itself */
g_free(info);