diff options
Diffstat (limited to 'lib/libmt/mtlib.c')
-rw-r--r-- | lib/libmt/mtlib.c | 756 |
1 files changed, 756 insertions, 0 deletions
diff --git a/lib/libmt/mtlib.c b/lib/libmt/mtlib.c new file mode 100644 index 0000000..2329fa6 --- /dev/null +++ b/lib/libmt/mtlib.c @@ -0,0 +1,756 @@ +/*- + * Copyright (c) 2013, 2014, 2015 Spectra Logic Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + * + * Authors: Ken Merry (Spectra Logic Corporation) + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/mtio.h> +#include <sys/queue.h> +#include <sys/sbuf.h> + +#include <ctype.h> +#include <err.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdint.h> +#include <errno.h> +#include <bsdxml.h> +#include <mtlib.h> + +/* + * Called at the start of each XML element, and includes the list of + * attributes for the element. + */ +void +mt_start_element(void *user_data, const char *name, const char **attr) +{ + int i; + struct mt_status_data *mtinfo; + struct mt_status_entry *entry; + + mtinfo = (struct mt_status_data *)user_data; + + if (mtinfo->error != 0) + return; + + mtinfo->level++; + if ((u_int)mtinfo->level > (sizeof(mtinfo->cur_sb) / + sizeof(mtinfo->cur_sb[0]))) { + mtinfo->error = 1; + snprintf(mtinfo->error_str, sizeof(mtinfo->error_str), + "%s: too many nesting levels, %zd max", __func__, + sizeof(mtinfo->cur_sb) / sizeof(mtinfo->cur_sb[0])); + return; + } + + mtinfo->cur_sb[mtinfo->level] = sbuf_new_auto(); + if (mtinfo->cur_sb[mtinfo->level] == NULL) { + mtinfo->error = 1; + snprintf(mtinfo->error_str, sizeof(mtinfo->error_str), + "%s: Unable to allocate sbuf", __func__); + return; + } + + entry = malloc(sizeof(*entry)); + if (entry == NULL) { + mtinfo->error = 1; + snprintf(mtinfo->error_str, sizeof(mtinfo->error_str), + "%s: unable to allocate %zd bytes", __func__, + sizeof(*entry)); + return; + } + bzero(entry, sizeof(*entry)); + STAILQ_INIT(&entry->nv_list); + STAILQ_INIT(&entry->child_entries); + entry->entry_name = strdup(name); + mtinfo->cur_entry[mtinfo->level] = entry; + if (mtinfo->cur_entry[mtinfo->level - 1] == NULL) { + STAILQ_INSERT_TAIL(&mtinfo->entries, entry, links); + } else { + STAILQ_INSERT_TAIL( + &mtinfo->cur_entry[mtinfo->level - 1]->child_entries, + entry, links); + entry->parent = mtinfo->cur_entry[mtinfo->level - 1]; + } + for (i = 0; attr[i] != NULL; i+=2) { + struct mt_status_nv *nv; + int need_nv; + + need_nv = 0; + + if (strcmp(attr[i], "size") == 0) { + entry->size = strtoull(attr[i+1], NULL, 0); + } else if (strcmp(attr[i], "type") == 0) { + if (strcmp(attr[i+1], "int") == 0) { + entry->var_type = MT_TYPE_INT; + } else if (strcmp(attr[i+1], "uint") == 0) { + entry->var_type = MT_TYPE_UINT; + } else if (strcmp(attr[i+1], "str") == 0) { + entry->var_type = MT_TYPE_STRING; + } else if (strcmp(attr[i+1], "node") == 0) { + entry->var_type = MT_TYPE_NODE; + } else { + need_nv = 1; + } + } else if (strcmp(attr[i], "fmt") == 0) { + entry->fmt = strdup(attr[i+1]); + } else if (strcmp(attr[i], "desc") == 0) { + entry->desc = strdup(attr[i+1]); + } else { + need_nv = 1; + } + if (need_nv != 0) { + nv = malloc(sizeof(*nv)); + if (nv == NULL) { + mtinfo->error = 1; + snprintf(mtinfo->error_str, + sizeof(mtinfo->error_str), + "%s: error allocating %zd bytes", + __func__, sizeof(*nv)); + } + bzero(nv, sizeof(*nv)); + nv->name = strdup(attr[i]); + nv->value = strdup(attr[i+1]); + STAILQ_INSERT_TAIL(&entry->nv_list, nv, links); + } + } +} + +/* + * Called on XML element close. + */ +void +mt_end_element(void *user_data, const char *name) +{ + struct mt_status_data *mtinfo; + char *str; + + mtinfo = (struct mt_status_data *)user_data; + + if (mtinfo->error != 0) + return; + + if (mtinfo->cur_sb[mtinfo->level] == NULL) { + mtinfo->error = 1; + snprintf(mtinfo->error_str, sizeof(mtinfo->error_str), + "%s: no valid sbuf at level %d (name %s)", __func__, + mtinfo->level, name); + return; + } + sbuf_finish(mtinfo->cur_sb[mtinfo->level]); + str = strdup(sbuf_data(mtinfo->cur_sb[mtinfo->level])); + if (str == NULL) { + mtinfo->error = 1; + snprintf(mtinfo->error_str, sizeof(mtinfo->error_str), + "%s can't allocate %zd bytes for string", __func__, + sbuf_len(mtinfo->cur_sb[mtinfo->level])); + return; + } + + if (strlen(str) == 0) { + free(str); + str = NULL; + } + if (str != NULL) { + struct mt_status_entry *entry; + + entry = mtinfo->cur_entry[mtinfo->level]; + switch(entry->var_type) { + case MT_TYPE_INT: + entry->value_signed = strtoll(str, NULL, 0); + break; + case MT_TYPE_UINT: + entry->value_unsigned = strtoull(str, NULL, 0); + break; + default: + break; + } + } + + mtinfo->cur_entry[mtinfo->level]->value = str; + + sbuf_delete(mtinfo->cur_sb[mtinfo->level]); + mtinfo->cur_sb[mtinfo->level] = NULL; + mtinfo->cur_entry[mtinfo->level] = NULL; + mtinfo->level--; +} + +/* + * Called to handle character strings in the current element. + */ +void +mt_char_handler(void *user_data, const XML_Char *str, int len) +{ + struct mt_status_data *mtinfo; + + mtinfo = (struct mt_status_data *)user_data; + if (mtinfo->error != 0) + return; + + sbuf_bcat(mtinfo->cur_sb[mtinfo->level], str, len); +} + +void +mt_status_tree_sbuf(struct sbuf *sb, struct mt_status_entry *entry, int indent, + void (*sbuf_func)(struct sbuf *sb, struct mt_status_entry *entry, + void *arg), void *arg) +{ + struct mt_status_nv *nv; + struct mt_status_entry *entry2; + + if (sbuf_func != NULL) { + sbuf_func(sb, entry, arg); + } else { + sbuf_printf(sb, "%*sname: %s, value: %s, fmt: %s, size: %zd, " + "type: %d, desc: %s\n", indent, "", entry->entry_name, + entry->value, entry->fmt, entry->size, entry->var_type, + entry->desc); + STAILQ_FOREACH(nv, &entry->nv_list, links) { + sbuf_printf(sb, "%*snv: name: %s, value: %s\n", + indent + 1, "", nv->name, nv->value); + } + } + + STAILQ_FOREACH(entry2, &entry->child_entries, links) + mt_status_tree_sbuf(sb, entry2, indent + 2, sbuf_func, arg); +} + +void +mt_status_tree_print(struct mt_status_entry *entry, int indent, + void (*print_func)(struct mt_status_entry *entry, void *arg), void *arg) +{ + + if (print_func != NULL) { + struct mt_status_entry *entry2; + + print_func(entry, arg); + STAILQ_FOREACH(entry2, &entry->child_entries, links) + mt_status_tree_print(entry2, indent + 2, print_func, + arg); + } else { + struct sbuf *sb; + + sb = sbuf_new_auto(); + if (sb == NULL) + return; + mt_status_tree_sbuf(sb, entry, indent, NULL, NULL); + sbuf_finish(sb); + + printf("%s", sbuf_data(sb)); + sbuf_delete(sb); + } +} + +/* + * Given a parameter name in the form "foo" or "foo.bar.baz", traverse the + * tree looking for the parameter (the first case) or series of parameters + * (second case). + */ +struct mt_status_entry * +mt_entry_find(struct mt_status_entry *entry, char *name) +{ + struct mt_status_entry *entry2; + char *tmpname = NULL, *tmpname2 = NULL, *tmpstr = NULL; + + tmpname = strdup(name); + if (tmpname == NULL) + goto bailout; + + /* Save a pointer so we can free this later */ + tmpname2 = tmpname; + + tmpstr = strsep(&tmpname, "."); + + /* + * Is this the entry we're looking for? Or do we have further + * child entries that we need to grab? + */ + if (strcmp(entry->entry_name, tmpstr) == 0) { + if (tmpname == NULL) { + /* + * There are no further child entries to find. We + * have a complete match. + */ + free(tmpname2); + return (entry); + } else { + /* + * There are more child entries that we need to find. + * Fall through to the recursive search off of this + * entry, below. Use tmpname, which will contain + * everything after the first period. + */ + name = tmpname; + } + } + + /* + * Recursively look for further entries. + */ + STAILQ_FOREACH(entry2, &entry->child_entries, links) { + struct mt_status_entry *entry3; + + entry3 = mt_entry_find(entry2, name); + if (entry3 != NULL) { + free(tmpname2); + return (entry3); + } + } + +bailout: + free(tmpname2); + + return (NULL); +} + +struct mt_status_entry * +mt_status_entry_find(struct mt_status_data *status_data, char *name) +{ + struct mt_status_entry *entry, *entry2; + + STAILQ_FOREACH(entry, &status_data->entries, links) { + entry2 = mt_entry_find(entry, name); + if (entry2 != NULL) + return (entry2); + } + + return (NULL); +} + +void +mt_status_entry_free(struct mt_status_entry *entry) +{ + struct mt_status_entry *entry2, *entry3; + struct mt_status_nv *nv, *nv2; + + STAILQ_FOREACH_SAFE(entry2, &entry->child_entries, links, entry3) { + STAILQ_REMOVE(&entry->child_entries, entry2, mt_status_entry, + links); + mt_status_entry_free(entry2); + } + + free(entry->entry_name); + free(entry->value); + free(entry->fmt); + free(entry->desc); + + STAILQ_FOREACH_SAFE(nv, &entry->nv_list, links, nv2) { + STAILQ_REMOVE(&entry->nv_list, nv, mt_status_nv, links); + free(nv->name); + free(nv->value); + free(nv); + } + free(entry); +} + +void +mt_status_free(struct mt_status_data *status_data) +{ + struct mt_status_entry *entry, *entry2; + + STAILQ_FOREACH_SAFE(entry, &status_data->entries, links, entry2) { + STAILQ_REMOVE(&status_data->entries, entry, mt_status_entry, + links); + mt_status_entry_free(entry); + } +} + +void +mt_entry_sbuf(struct sbuf *sb, struct mt_status_entry *entry, char *fmt) +{ + switch(entry->var_type) { + case MT_TYPE_INT: + if (fmt != NULL) + sbuf_printf(sb, fmt, (intmax_t)entry->value_signed); + else + sbuf_printf(sb, "%jd", + (intmax_t)entry->value_signed); + break; + case MT_TYPE_UINT: + if (fmt != NULL) + sbuf_printf(sb, fmt, (uintmax_t)entry->value_unsigned); + else + sbuf_printf(sb, "%ju", + (uintmax_t)entry->value_unsigned); + break; + default: + if (fmt != NULL) + sbuf_printf(sb, fmt, entry->value); + else + sbuf_printf(sb, "%s", entry->value); + break; + } +} + +void +mt_param_parent_print(struct mt_status_entry *entry, + struct mt_print_params *print_params) +{ + if (entry->parent != NULL) + mt_param_parent_print(entry->parent, print_params); + + if (((print_params->flags & MT_PF_INCLUDE_ROOT) == 0) + && (strcmp(entry->entry_name, print_params->root_name) == 0)) + return; + + printf("%s.", entry->entry_name); +} + +void +mt_param_parent_sbuf(struct sbuf *sb, struct mt_status_entry *entry, + struct mt_print_params *print_params) +{ + if (entry->parent != NULL) + mt_param_parent_sbuf(sb, entry->parent, print_params); + + if (((print_params->flags & MT_PF_INCLUDE_ROOT) == 0) + && (strcmp(entry->entry_name, print_params->root_name) == 0)) + return; + + sbuf_printf(sb, "%s.", entry->entry_name); +} + +void +mt_param_entry_sbuf(struct sbuf *sb, struct mt_status_entry *entry, void *arg) +{ + struct mt_print_params *print_params; + + print_params = (struct mt_print_params *)arg; + + /* + * We don't want to print nodes. + */ + if (entry->var_type == MT_TYPE_NODE) + return; + + if ((print_params->flags & MT_PF_FULL_PATH) + && (entry->parent != NULL)) + mt_param_parent_sbuf(sb, entry->parent, print_params); + + sbuf_printf(sb, "%s: %s", entry->entry_name, entry->value); + if ((print_params->flags & MT_PF_VERBOSE) + && (entry->desc != NULL) + && (strlen(entry->desc) > 0)) + sbuf_printf(sb, " (%s)", entry->desc); + sbuf_printf(sb, "\n"); + +} + +void +mt_param_entry_print(struct mt_status_entry *entry, void *arg) +{ + struct mt_print_params *print_params; + + print_params = (struct mt_print_params *)arg; + + /* + * We don't want to print nodes. + */ + if (entry->var_type == MT_TYPE_NODE) + return; + + if ((print_params->flags & MT_PF_FULL_PATH) + && (entry->parent != NULL)) + mt_param_parent_print(entry->parent, print_params); + + printf("%s: %s", entry->entry_name, entry->value); + if ((print_params->flags & MT_PF_VERBOSE) + && (entry->desc != NULL) + && (strlen(entry->desc) > 0)) + printf(" (%s)", entry->desc); + printf("\n"); +} + +int +mt_protect_print(struct mt_status_data *status_data, int verbose) +{ + struct mt_status_entry *entry; + const char *prot_name = MT_PROTECTION_NAME; + struct mt_print_params print_params; + + snprintf(print_params.root_name, sizeof(print_params.root_name), + MT_PARAM_ROOT_NAME); + print_params.flags = MT_PF_FULL_PATH; + if (verbose != 0) + print_params.flags |= MT_PF_VERBOSE; + + entry = mt_status_entry_find(status_data, __DECONST(char *,prot_name)); + if (entry == NULL) + return (1); + mt_status_tree_print(entry, 0, mt_param_entry_print, &print_params); + + return (0); +} + +int +mt_param_list(struct mt_status_data *status_data, char *param_name, int quiet) +{ + struct mt_status_entry *entry; + struct mt_print_params print_params; + char root_name[20]; + + snprintf(root_name, sizeof(root_name), "mtparamget"); + strlcpy(print_params.root_name, root_name, + sizeof(print_params.root_name)); + + print_params.flags = MT_PF_FULL_PATH; + if (quiet == 0) + print_params.flags |= MT_PF_VERBOSE; + + if (param_name != NULL) { + entry = mt_status_entry_find(status_data, param_name); + if (entry == NULL) + return (1); + + mt_param_entry_print(entry, &print_params); + + return (0); + } else { + entry = mt_status_entry_find(status_data, root_name); + + STAILQ_FOREACH(entry, &status_data->entries, links) + mt_status_tree_print(entry, 0, mt_param_entry_print, + &print_params); + } + + return (0); +} + +static struct densities { + int dens; + int bpmm; + int bpi; + const char *name; +} dens[] = { + /* + * Taken from T10 Project 997D + * SCSI-3 Stream Device Commands (SSC) + * Revision 11, 4-Nov-97 + * + * LTO 1-6 definitions obtained from the eighth edition of the + * IBM TotalStorage LTO Ultrium Tape Drive SCSI Reference + * (July 2007) and the second edition of the IBM System Storage LTO + * Tape Drive SCSI Reference (February 13, 2013). + * + * IBM 3592 definitions obtained from second edition of the IBM + * System Storage Tape Drive 3592 SCSI Reference (May 25, 2012). + */ + /*Num. bpmm bpi Reference */ + { 0x1, 32, 800, "X3.22-1983" }, + { 0x2, 63, 1600, "X3.39-1986" }, + { 0x3, 246, 6250, "X3.54-1986" }, + { 0x5, 315, 8000, "X3.136-1986" }, + { 0x6, 126, 3200, "X3.157-1987" }, + { 0x7, 252, 6400, "X3.116-1986" }, + { 0x8, 315, 8000, "X3.158-1987" }, + { 0x9, 491, 37871, "X3.180" }, + { 0xA, 262, 6667, "X3B5/86-199" }, + { 0xB, 63, 1600, "X3.56-1986" }, + { 0xC, 500, 12690, "HI-TC1" }, + { 0xD, 999, 25380, "HI-TC2" }, + { 0xF, 394, 10000, "QIC-120" }, + { 0x10, 394, 10000, "QIC-150" }, + { 0x11, 630, 16000, "QIC-320" }, + { 0x12, 2034, 51667, "QIC-1350" }, + { 0x13, 2400, 61000, "X3B5/88-185A" }, + { 0x14, 1703, 43245, "X3.202-1991" }, + { 0x15, 1789, 45434, "ECMA TC17" }, + { 0x16, 394, 10000, "X3.193-1990" }, + { 0x17, 1673, 42500, "X3B5/91-174" }, + { 0x18, 1673, 42500, "X3B5/92-50" }, + { 0x19, 2460, 62500, "DLTapeIII" }, + { 0x1A, 3214, 81633, "DLTapeIV(20GB)" }, + { 0x1B, 3383, 85937, "DLTapeIV(35GB)" }, + { 0x1C, 1654, 42000, "QIC-385M" }, + { 0x1D, 1512, 38400, "QIC-410M" }, + { 0x1E, 1385, 36000, "QIC-1000C" }, + { 0x1F, 2666, 67733, "QIC-2100C" }, + { 0x20, 2666, 67733, "QIC-6GB(M)" }, + { 0x21, 2666, 67733, "QIC-20GB(C)" }, + { 0x22, 1600, 40640, "QIC-2GB(C)" }, + { 0x23, 2666, 67733, "QIC-875M" }, + { 0x24, 2400, 61000, "DDS-2" }, + { 0x25, 3816, 97000, "DDS-3" }, + { 0x26, 3816, 97000, "DDS-4" }, + { 0x27, 3056, 77611, "Mammoth" }, + { 0x28, 1491, 37871, "X3.224" }, + { 0x40, 4880, 123952, "LTO-1" }, + { 0x41, 3868, 98250, "DLTapeIV(40GB)" }, + { 0x42, 7398, 187909, "LTO-2" }, + { 0x44, 9638, 244805, "LTO-3" }, + { 0x46, 12725, 323215, "LTO-4" }, + { 0x48, 5236, 133000, "SDLTapeI(110)" }, + { 0x49, 7598, 193000, "SDLTapeI(160)" }, + { 0x4a, 0, 0, "T10000A" }, + { 0x4b, 0, 0, "T10000B" }, + { 0x4c, 0, 0, "T10000C" }, + { 0x4d, 0, 0, "T10000D" }, + { 0x51, 11800, 299720, "3592A1 (unencrypted)" }, + { 0x52, 11800, 299720, "3592A2 (unencrypted)" }, + { 0x53, 13452, 341681, "3592A3 (unencrypted)" }, + { 0x54, 19686, 500024, "3592A4 (unencrypted)" }, + { 0x55, 20670, 525018, "3592A5 (unencrypted)" }, + { 0x58, 15142, 384607, "LTO-5" }, + { 0x5A, 15142, 384607, "LTO-6" }, + { 0x71, 11800, 299720, "3592A1 (encrypted)" }, + { 0x72, 11800, 299720, "3592A2 (encrypted)" }, + { 0x73, 13452, 341681, "3592A3 (encrypted)" }, + { 0x74, 19686, 500024, "3592A4 (encrypted)" }, + { 0x75, 20670, 525018, "3592A5 (encrypted)" }, + { 0x8c, 1789, 45434, "EXB-8500c" }, + { 0x90, 1703, 43245, "EXB-8200c" }, + { 0, 0, 0, NULL } +}; + +const char * +mt_density_name(int density_num) +{ + struct densities *sd; + + /* densities 0 and 0x7f are handled as special cases */ + if (density_num == 0) + return ("default"); + if (density_num == 0x7f) + return ("same"); + + for (sd = dens; sd->dens != 0; sd++) + if (sd->dens == density_num) + break; + if (sd->dens == 0) + return ("UNKNOWN"); + return (sd->name); +} + +/* + * Given a specific density number, return either the bits per inch or bits + * per millimeter for the given density. + */ +int +mt_density_bp(int density_num, int bpi) +{ + struct densities *sd; + + for (sd = dens; sd->dens; sd++) + if (sd->dens == density_num) + break; + if (sd->dens == 0) + return (0); + if (bpi) + return (sd->bpi); + else + return (sd->bpmm); +} + +int +mt_density_num(const char *density_name) +{ + struct densities *sd; + size_t l = strlen(density_name); + + for (sd = dens; sd->dens; sd++) + if (strncasecmp(sd->name, density_name, l) == 0) + break; + return (sd->dens); +} + +/* + * Get the current status XML string. + * Returns 0 on success, -1 on failure (with errno set, and *xml_str == NULL). + */ +int +mt_get_xml_str(int mtfd, unsigned long cmd, char **xml_str) +{ + size_t alloc_len = 32768; + struct mtextget extget; + int error; + + *xml_str = NULL; + + for (;;) { + bzero(&extget, sizeof(extget)); + *xml_str = malloc(alloc_len); + if (*xml_str == NULL) + return (-1); + extget.status_xml = *xml_str; + extget.alloc_len = alloc_len; + + error = ioctl(mtfd, cmd, (caddr_t)&extget); + if (error == 0 && extget.status == MT_EXT_GET_OK) + break; + + free(*xml_str); + *xml_str = NULL; + + if (error != 0 || extget.status != MT_EXT_GET_NEED_MORE_SPACE) + return (-1); + + /* The driver needs more space, so double and try again. */ + alloc_len *= 2; + } + return (0); +} + +/* + * Populate a struct mt_status_data from the XML string via mt_get_xml_str(). + * + * Returns XML_STATUS_OK on success. + * If XML_STATUS_ERROR is returned, errno may be set to indicate the reason. + * The caller must check status_data->error. + */ +int +mt_get_status(char *xml_str, struct mt_status_data *status_data) +{ + XML_Parser parser; + int retval; + + bzero(status_data, sizeof(*status_data)); + STAILQ_INIT(&status_data->entries); + + parser = XML_ParserCreate(NULL); + if (parser == NULL) { + errno = ENOMEM; + return (XML_STATUS_ERROR); + } + + XML_SetUserData(parser, status_data); + XML_SetElementHandler(parser, mt_start_element, mt_end_element); + XML_SetCharacterDataHandler(parser, mt_char_handler); + + retval = XML_Parse(parser, xml_str, strlen(xml_str), 1); + XML_ParserFree(parser); + return (retval); +} |