root/usr/src/common/devid/devid.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 2018, Joyent, Inc.
 */

#include <sys/types.h>
#include <sys/stropts.h>
#include <sys/debug.h>
#include <sys/isa_defs.h>
#include <sys/dditypes.h>
#include <sys/ddi_impldefs.h>
#include "devid_impl.h"

static int devid_str_decode_id(char *devidstr, ddi_devid_t *devidp,
    char **minor_namep, impl_devid_t *id);


/*
 * Validate device id.
 */
int
#ifdef  _KERNEL
ddi_devid_valid(ddi_devid_t devid)
#else   /* !_KERNEL */
devid_valid(ddi_devid_t devid)
#endif  /* _KERNEL */
{
        impl_devid_t    *id = (impl_devid_t *)devid;
        ushort_t        type;

        DEVID_ASSERT(devid != NULL);

        if (id->did_magic_hi != DEVID_MAGIC_MSB)
                return (DEVID_RET_INVALID);

        if (id->did_magic_lo != DEVID_MAGIC_LSB)
                return (DEVID_RET_INVALID);

        if (id->did_rev_hi != DEVID_REV_MSB)
                return (DEVID_RET_INVALID);

        if (id->did_rev_lo != DEVID_REV_LSB)
                return (DEVID_RET_INVALID);

        type = DEVID_GETTYPE(id);
        if ((type == DEVID_NONE) || (type > DEVID_MAXTYPE))
                return (DEVID_RET_INVALID);

        return (DEVID_RET_VALID);
}

/*
 * Return the sizeof a device id. If called with NULL devid it returns
 * the amount of space needed to determine the size.
 */
size_t
#ifdef  _KERNEL
ddi_devid_sizeof(ddi_devid_t devid)
#else   /* !_KERNEL */
devid_sizeof(ddi_devid_t devid)
#endif  /* _KERNEL */
{
        impl_devid_t    *id = (impl_devid_t *)devid;

        if (id == NULL)
                return (sizeof (*id) - sizeof (id->did_id));

        DEVID_ASSERT(DEVID_FUNC(devid_valid)(devid) == DEVID_RET_VALID);

        return (sizeof (*id) + DEVID_GETLEN(id) - sizeof (id->did_id));
}

/*
 * Compare two device id's.
 *      -1 - less than
 *      0  - equal
 *      1  - greater than
 */
int
#ifdef  _KERNEL
ddi_devid_compare(ddi_devid_t id1, ddi_devid_t id2)
#else   /* !_KERNEL */
devid_compare(ddi_devid_t id1, ddi_devid_t id2)
#endif  /* _KERNEL */
{
        int             rval;
        impl_devid_t    *i_id1  = (impl_devid_t *)id1;
        impl_devid_t    *i_id2  = (impl_devid_t *)id2;
        ushort_t        i_id1_type;
        ushort_t        i_id2_type;

        DEVID_ASSERT((id1 != NULL) && (id2 != NULL));
        DEVID_ASSERT(DEVID_FUNC(devid_valid)(id1) == DEVID_RET_VALID);
        DEVID_ASSERT(DEVID_FUNC(devid_valid)(id2) == DEVID_RET_VALID);

        /* magic and revision comparison */
        if ((rval = bcmp(id1, id2, 4)) != 0) {
                return (rval);
        }

        /* get current devid types */
        i_id1_type = DEVID_GETTYPE(i_id1);
        i_id2_type = DEVID_GETTYPE(i_id2);

        /*
         * Originaly all page83 devids used DEVID_SCSI3_WWN.
         * To avoid a possible uniqueness issue each type of page83
         * encoding supported is represented as a separate
         * devid type.  If comparing DEVID_SCSI3_WWN against
         * one of the new page83 encodings we assume that no
         * uniqueness issue exists (since we had apparently been
         * running with the old DEVID_SCSI3_WWN encoding without
         * a problem).
         */
        if ((i_id1_type == DEVID_SCSI3_WWN) ||
            (i_id2_type == DEVID_SCSI3_WWN)) {
                /*
                 * Atleast one devid is using old scsi
                 * encode algorithm.  Force devid types
                 * to same scheme for comparison.
                 */
                if (IS_DEVID_SCSI3_VPD_TYPE(i_id1_type)) {
                        i_id1_type = DEVID_SCSI3_WWN;
                }
                if (IS_DEVID_SCSI3_VPD_TYPE(i_id2_type)) {
                        i_id2_type = DEVID_SCSI3_WWN;
                }
        }

        /* type comparison */
        if (i_id1_type != i_id2_type) {
                return ((i_id1_type < i_id2_type) ? -1 : 1);
        }

        /* length comparison */
        if (DEVID_GETLEN(i_id1) != DEVID_GETLEN(i_id2)) {
                return (DEVID_GETLEN(i_id1) < DEVID_GETLEN(i_id2) ? -1 : 1);
        }

        /* id comparison */
        rval = bcmp(i_id1->did_id, i_id2->did_id, DEVID_GETLEN(i_id1));

        return (rval);
}

/*
 * Free a Device Id
 */
void
#ifdef  _KERNEL
ddi_devid_free(ddi_devid_t devid)
#else   /* !_KERNEL */
devid_free(ddi_devid_t devid)
#endif  /* _KERNEL */
{
        DEVID_ASSERT(devid != NULL);
        DEVID_FREE(devid, DEVID_FUNC(devid_sizeof)(devid));
}

/*
 * Encode a device id into a string.  See ddi_impldefs.h for details.
 */
char *
#ifdef  _KERNEL
ddi_devid_str_encode(ddi_devid_t devid, char *minor_name)
#else   /* !_KERNEL */
devid_str_encode(ddi_devid_t devid, char *minor_name)
#endif  /* _KERNEL */
{
        impl_devid_t    *id = (impl_devid_t *)devid;
        size_t          driver_len, devid_len, slen;
        char            *sbuf, *dsp, *dp, ta;
        int             i, n, ascii;

        /* "id0" is the encoded representation of a NULL device id */
        if (devid == NULL) {
                if ((sbuf = DEVID_MALLOC(4)) == NULL)
                        return (NULL);
                *(sbuf+0) = DEVID_MAGIC_MSB;
                *(sbuf+1) = DEVID_MAGIC_LSB;
                *(sbuf+2) = '0';
                *(sbuf+3) = 0;
                return (sbuf);
        }

        /* verify input */
        if (DEVID_FUNC(devid_valid)(devid) != DEVID_RET_VALID)
                return (NULL);

        /* scan the driver hint to see how long the hint is */
        for (driver_len = 0; driver_len < DEVID_HINT_SIZE; driver_len++)
                if (id->did_driver[driver_len] == '\0')
                        break;

        /* scan the contained did_id to see if it meets ascii requirements */
        devid_len = DEVID_GETLEN(id);
        for (ascii = 1, i = 0; i < devid_len; i++)
                if (!DEVID_IDBYTE_ISASCII(id->did_id[i])) {
                        ascii = 0;
                        break;
                }

        /* some types should always go hex even if they look ascii */
        if (DEVID_TYPE_BIN_FORCEHEX(id->did_type_lo))
                ascii = 0;

        /* set the length of the resulting string */
        slen = 2 + 1;                                   /* <magic><rev> "id1" */
        slen += 1 + driver_len + 1 + 1;                 /* ",<driver>@<type>" */
        slen += ascii ? devid_len : (devid_len * 2);    /* did_id field */
        if (minor_name) {
                slen += 1;                              /* '/' */
                slen += strlen(minor_name);             /* len of minor_name */
        }
        slen += 1;                                      /* NULL */

        /* allocate string */
        if ((sbuf = DEVID_MALLOC(slen)) == NULL)
                return (NULL);

        /* perform encode of id to hex string */
        dsp = sbuf;
        *dsp++ = id->did_magic_hi;
        *dsp++ = id->did_magic_lo;
        *dsp++ = DEVID_REV_BINTOASCII(id->did_rev_lo);
        *dsp++ = ',';
        for (i = 0; i < driver_len; i++)
                *dsp++ = id->did_driver[i];
        *dsp++ = '@';
        ta = DEVID_TYPE_BINTOASCII(id->did_type_lo);
        if (ascii)
                ta = DEVID_TYPE_SETASCII(ta);
        *dsp++ = ta;
        for (i = 0, dp = &id->did_id[0]; i < devid_len; i++, dp++) {
                if (ascii) {
                        if (*dp == ' ')
                                *dsp++ = '_';
                        else if (*dp == 0x00)
                                *dsp++ = '~';
                        else
                                *dsp++ = *dp;
                } else {
                        n = ((*dp) >> 4) & 0xF;
                        *dsp++ = (n < 10) ? (n + '0') : (n + ('a' - 10));
                        n = (*dp) & 0xF;
                        *dsp++ = (n < 10) ? (n + '0') : (n + ('a' - 10));
                }
        }

        if (minor_name) {
                *dsp++ = '/';
                (void) strcpy(dsp, minor_name);
        } else
                *dsp++ = 0;

        /* ensure that (strlen + 1) is correct length for free */
        DEVID_ASSERT((strlen(sbuf) + 1) == slen);
        return (sbuf);
}

/* free the string returned by devid_str_encode */
void
#ifdef  _KERNEL
ddi_devid_str_free(char *devidstr)
#else   /* !_KERNEL */
devid_str_free(char *devidstr)
#endif  /* _KERNEL */
{
        DEVID_FREE(devidstr, strlen(devidstr) + 1);
}

/*
 * given the string representation of a device id returned by calling
 * devid_str_encode (passed in as devidstr), return pointers to the
 * broken out devid and minor_name as requested. Devidstr remains
 * allocated and unmodified. The devid returned in *devidp should be freed by
 * calling devid_free.  The minor_name returned in minor_namep should
 * be freed by calling devid_str_free(minor_namep).
 *
 * See ddi_impldefs.h for format details.
 */
int
#ifdef  _KERNEL
ddi_devid_str_decode(
#else   /* !_KERNEL */
devid_str_decode(
#endif  /* _KERNEL */
    char *devidstr, ddi_devid_t *devidp, char **minor_namep)
{
        return (devid_str_decode_id(devidstr, devidp, minor_namep, NULL));
}

/* implementation for (ddi_)devid_str_decode */
static int
devid_str_decode_id(char *devidstr, ddi_devid_t *devidp,
    char **minor_namep, impl_devid_t *id)
{
        char            *str, *msp, *dsp, *dp, ta;
        int             slen, devid_len, ascii, i, n, c, pre_alloc = FALSE;
        unsigned short  id_len, type;           /* for hibyte/lobyte */

        devid_len = 0;
        if (devidp != NULL)
                *devidp = NULL;
        if (minor_namep != NULL)
                *minor_namep = NULL;
        if (id != NULL)
                pre_alloc = TRUE;

        if (devidstr == NULL)
                return (DEVID_FAILURE);

        /* the string must atleast contain the ascii two byte header */
        slen = strlen(devidstr);
        if ((slen < 3) || (devidstr[0] != DEVID_MAGIC_MSB) ||
            (devidstr[1] != DEVID_MAGIC_LSB))
                return (DEVID_FAILURE);

        /* "id0" is the encoded representation of a NULL device id */
        if ((devidstr[2] == '0') && (slen == 3))
                return (DEVID_SUCCESS);

        /* "id1,@S0" is the shortest possible, reject if shorter */
        if (slen <  7)
                return (DEVID_FAILURE);

        /* find the optional minor name, start after ',' */
        if ((msp = strchr(&devidstr[4], '/')) != NULL)
                msp++;

        /* skip devid processing if we are not asked to return it */
        if (devidp) {
                /* find the required '@' separator */
                if ((str = strchr(devidstr, '@')) == NULL)
                        return (DEVID_FAILURE);
                str++;                                  /* skip '@' */

                /* pick up <type> after the '@' and verify */
                ta = *str++;
                ascii = DEVID_TYPE_ISASCII(ta);
                type = DEVID_TYPE_ASCIITOBIN(ta);
                if (type > DEVID_MAXTYPE)
                        return (DEVID_FAILURE);

                /* determine length of id->did_id field */
                if (msp == NULL)
                        id_len = strlen(str);
                else
                        id_len = msp - str - 1;

                /* account for encoding: with hex, binary is half the size */
                if (!ascii) {
                        /* hex id field must be even length */
                        if (id_len & 1)
                                return (DEVID_FAILURE);
                        id_len /= 2;
                }

                /* add in size of the binary devid header */
                devid_len = id_len + sizeof (*id) - sizeof (id->did_id);

                /*
                 * Allocate space for devid if we are asked to decode it
                 * decode it and space wasn't pre-allocated.
                 */
                if (pre_alloc == FALSE) {
                        if ((id = (impl_devid_t *)DEVID_MALLOC(
                            devid_len)) == NULL)
                                return (DEVID_FAILURE);
                }

                /* decode header portion of the string into the binary devid */
                dsp = devidstr;
                id->did_magic_hi = *dsp++;              /* <magic> "id" */
                id->did_magic_lo = *dsp++;
                id->did_rev_hi = 0;
                id->did_rev_lo =
                    DEVID_REV_ASCIITOBIN(*dsp);         /* <rev> "1" */
                dsp++;                                  /* skip "1" */
                dsp++;                                  /* skip "," */
                for (i = 0; i < DEVID_HINT_SIZE; i++) { /* <driver>@ */
                        if (*dsp == '@')
                                break;
                        id->did_driver[i] = *dsp++;
                }
                for (; i < DEVID_HINT_SIZE; i++)
                        id->did_driver[i] = 0;

                /* we must now be at the '@' */
                if (*dsp != '@')
                        goto efree;

                /* set the type and length */
                DEVID_FORMTYPE(id, type);
                DEVID_FORMLEN(id, id_len);

                /* decode devid portion of string into the binary */
                for (i = 0, dsp = str, dp = &id->did_id[0];
                    i < id_len; i++, dp++) {
                        if (ascii) {
                                if (*dsp == '_')
                                        *dp = ' ';
                                else if (*dsp == '~')
                                        *dp = 0x00;
                                else
                                        *dp = *dsp;
                                dsp++;
                        } else {
                                c = *dsp++;
                                if (c >= '0' && c <= '9')
                                        n = (c - '0') & 0xFF;
                                else if (c >= 'a' && c <= 'f')
                                        n = (c - ('a' - 10)) & 0xFF;
                                else
                                        goto efree;
                                n <<= 4;
                                c = *dsp++;
                                if (c >= '0' && c <= '9')
                                        n |= (c - '0') & 0xFF;
                                else if (c >= 'a' && c <= 'f')
                                        n |= (c - ('a' - 10)) & 0xFF;
                                else
                                        goto efree;
                                *dp = n;
                        }
                }

                /* verify result */
                if (DEVID_FUNC(devid_valid)((ddi_devid_t)id) != DEVID_RET_VALID)
                        goto efree;
        }

        /* duplicate minor_name if we are asked to decode it */
        if (minor_namep && msp) {
                if ((*minor_namep = DEVID_MALLOC(strlen(msp) + 1)) == NULL)
                        goto efree;
                (void) strcpy(*minor_namep, msp);
        }

        /* return pointer to binary */
        if (devidp)
                *devidp = (ddi_devid_t)id;
        return (DEVID_SUCCESS);

efree:
        if ((pre_alloc == FALSE) && (id))
                DEVID_FREE(id, devid_len);
        return (DEVID_FAILURE);
}


/*
 * Compare two device id's in string form
 *      -1 - id1 less than id2
 *      0  - equal
 *      1  - id1 greater than id2
 */
int
#ifdef  _KERNEL
ddi_devid_str_compare(char *id1_str, char *id2_str)
#else   /* !_KERNEL */
devid_str_compare(char *id1_str, char *id2_str)
#endif  /* _KERNEL */
{
        int             rval    = DEVID_FAILURE;
        ddi_devid_t     devid1;
        ddi_devid_t     devid2;
#ifdef  _KERNEL
        /* kernel use static protected by lock. */
        static kmutex_t id_lock;
        static uchar_t  id1[sizeof (impl_devid_t) + MAXPATHLEN];
        static uchar_t  id2[sizeof (impl_devid_t) + MAXPATHLEN];
#else   /* !_KERNEL */
        /* userland place on stack, since malloc might fail */
        uchar_t         id1[sizeof (impl_devid_t) + MAXPATHLEN];
        uchar_t         id2[sizeof (impl_devid_t) + MAXPATHLEN];
#endif  /* _KERNEL */

#ifdef  _KERNEL
        mutex_enter(&id_lock);
#endif  /* _KERNEL */

        /*
         * encode string form of devid
         */
        if ((devid_str_decode_id(id1_str, &devid1, NULL, (impl_devid_t *)id1) ==
            DEVID_SUCCESS) &&
            (devid_str_decode_id(id2_str, &devid2, NULL, (impl_devid_t *)id2) ==
            DEVID_SUCCESS)) {
                rval = DEVID_FUNC(devid_compare)(devid1, devid2);
        }

#ifdef  _KERNEL
        mutex_exit(&id_lock);
#endif  /* _KERNEL */

        return (rval);
}