root/lib/libmt/mtlib.c
/*-
 * 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/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).
         *
         * DAT-72 and DAT-160 bpi values taken from "HP StorageWorks DAT160
         * tape drive white paper", dated June 2007.
         *
         * DAT-160 / SDLT220 density code (0x48) conflict information
         * found here:
         *
         * http://h20564.www2.hp.com/hpsc/doc/public/display?docId=emr_na-c01065117&sp4ts.oid=429311
         * (Document ID c01065117)
         */
        /*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" }, 
        { 0x47, 6417,   163000, "DAT-72" },
        /*
         * XXX KDM note that 0x48 is also the density code for DAT-160.
         * For some reason they used overlapping density codes.
         */
#if 0
        { 0x48, 6870,   174500, "DAT-160" },
#endif
        { 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)" },
        { 0x56, 20670,  525018, "3592B5 (unencrypted)" },
        { 0x57, 21850,  554990, "3592A6 (unencrypted)" },
        { 0x58, 15142,  384607, "LTO-5" },
        { 0x59, 21850,  554990, "3592A7 (unencrypted)" },
        { 0x5A, 15142,  384607, "LTO-6" },
        { 0x5C, 19107,  485318, "LTO-7" },
        { 0x5D, 19107,  485318, "LTO-M8" },
        { 0x5E, 20669,  524993, "LTO-8" },
        { 0x60, 23031,  584987, "LTO-9" },
        { 0x62, 21657,  550088, "LTO-10" },
        { 0x63, 22441,  570001, "LTO-10P" },
        { 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)" },
        { 0x76, 20670,  525018, "3592B5 (encrypted)" },
        { 0x77, 21850,  554990, "3592A6 (encrypted)" },
        { 0x79, 21850,  554990, "3592A7 (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);
}