1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 /*
  27  * Copyright (c) 2013 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
  28  */
  29 
  30 #include <mdb/mdb_debug.h>
  31 #include <mdb/mdb_string.h>
  32 #include <mdb/mdb_modapi.h>
  33 #include <mdb/mdb_err.h>
  34 #include <mdb/mdb_nv.h>
  35 #include <mdb/mdb.h>
  36 
  37 #define NV_NAME(v) \
  38         (((v)->v_flags & MDB_NV_EXTNAME) ? (v)->v_ename : (v)->v_lname)
  39 
  40 #define NV_SIZE(v) \
  41         (((v)->v_flags & MDB_NV_EXTNAME) ? sizeof (mdb_var_t) : \
  42         sizeof (mdb_var_t) + strlen((v)->v_lname))
  43 
  44 #define NV_HASHSZ       211
  45 
  46 static size_t
  47 nv_hashstring(const char *key)
  48 {
  49         size_t g, h = 0;
  50         const char *p;
  51 
  52         ASSERT(key != NULL);
  53 
  54         for (p = key; *p != '\0'; p++) {
  55                 h = (h << 4) + *p;
  56 
  57                 if ((g = (h & 0xf0000000)) != 0) {
  58                         h ^= (g >> 24);
  59                         h ^= g;
  60                 }
  61         }
  62 
  63         return (h);
  64 }
  65 
  66 static mdb_var_t *
  67 nv_var_alloc(const char *name, const mdb_nv_disc_t *disc,
  68         uintmax_t value, uint_t flags, uint_t um_flags, mdb_var_t *next)
  69 {
  70         size_t nbytes;
  71         mdb_var_t *v;
  72 
  73         if (flags & MDB_NV_EXTNAME)
  74                 nbytes = sizeof (mdb_var_t);
  75         else
  76                 nbytes = sizeof (mdb_var_t) + strlen(name);
  77 
  78         v = mdb_alloc(nbytes, um_flags);
  79 
  80         if (v == NULL)
  81                 return (NULL);
  82 
  83         if (flags & MDB_NV_EXTNAME) {
  84                 v->v_ename = name;
  85                 v->v_lname[0] = '\0';
  86         } else {
  87                 /*
  88                  * We don't overflow here since the mdb_var_t itself has
  89                  * room for the trailing \0.
  90                  */
  91                 (void) strcpy(v->v_lname, name);
  92                 v->v_ename = NULL;
  93         }
  94 
  95         v->v_uvalue = value;
  96         v->v_flags = flags & ~(MDB_NV_SILENT | MDB_NV_INTERPOS);
  97         v->v_disc = disc;
  98         v->v_next = next;
  99 
 100         return (v);
 101 }
 102 
 103 static void
 104 nv_var_free(mdb_var_t *v, uint_t um_flags)
 105 {
 106         if (um_flags & UM_GC)
 107                 return;
 108 
 109         if (v->v_flags & MDB_NV_OVERLOAD) {
 110                 mdb_var_t *w, *nw;
 111 
 112                 for (w = v->v_ndef; w != NULL; w = nw) {
 113                         nw = w->v_ndef;
 114                         mdb_free(w, NV_SIZE(w));
 115                 }
 116         }
 117 
 118         mdb_free(v, NV_SIZE(v));
 119 }
 120 
 121 /*
 122  * Can return NULL only if the nv's memory allocation flags include UM_NOSLEEP
 123  */
 124 mdb_nv_t *
 125 mdb_nv_create(mdb_nv_t *nv, uint_t um_flags)
 126 {
 127         nv->nv_hash = mdb_zalloc(sizeof (mdb_var_t *) * NV_HASHSZ, um_flags);
 128 
 129         if (nv->nv_hash == NULL)
 130                 return (NULL);
 131 
 132         nv->nv_hashsz = NV_HASHSZ;
 133         nv->nv_nelems = 0;
 134         nv->nv_iter_elt = NULL;
 135         nv->nv_iter_bucket = 0;
 136         nv->nv_um_flags = um_flags;
 137 
 138         return (nv);
 139 }
 140 
 141 void
 142 mdb_nv_destroy(mdb_nv_t *nv)
 143 {
 144         mdb_var_t *v, *w;
 145         size_t i;
 146 
 147         if (nv->nv_um_flags & UM_GC)
 148                 return;
 149 
 150         for (i = 0; i < nv->nv_hashsz; i++) {
 151                 for (v = nv->nv_hash[i]; v != NULL; v = w) {
 152                         w = v->v_next;
 153                         nv_var_free(v, nv->nv_um_flags);
 154                 }
 155         }
 156 
 157         mdb_free(nv->nv_hash, sizeof (mdb_var_t *) * NV_HASHSZ);
 158 }
 159 
 160 mdb_var_t *
 161 mdb_nv_lookup(mdb_nv_t *nv, const char *name)
 162 {
 163         size_t i = nv_hashstring(name) % nv->nv_hashsz;
 164         mdb_var_t *v;
 165 
 166         for (v = nv->nv_hash[i]; v != NULL; v = v->v_next) {
 167                 if (strcmp(NV_NAME(v), name) == 0)
 168                         return (v);
 169         }
 170 
 171         return (NULL);
 172 }
 173 
 174 /*
 175  * Interpose W in place of V.  We replace V with W in nv_hash, and then
 176  * set W's v_ndef overload chain to point at V.
 177  */
 178 static mdb_var_t *
 179 nv_var_interpos(mdb_nv_t *nv, size_t i, mdb_var_t *v, mdb_var_t *w)
 180 {
 181         mdb_var_t **pvp = &nv->nv_hash[i];
 182 
 183         while (*pvp != v) {
 184                 mdb_var_t *vp = *pvp;
 185                 ASSERT(vp != NULL);
 186                 pvp = &vp->v_next;
 187         }
 188 
 189         *pvp = w;
 190         w->v_next = v->v_next;
 191         w->v_ndef = v;
 192         v->v_next = NULL;
 193 
 194         return (w);
 195 }
 196 
 197 /*
 198  * Add W to the end of V's overload chain.  We simply follow v_ndef to the
 199  * end, and then append W.  We don't expect these chains to grow very long.
 200  */
 201 static mdb_var_t *
 202 nv_var_overload(mdb_var_t *v, mdb_var_t *w)
 203 {
 204         while (v->v_ndef != NULL)
 205                 v = v->v_ndef;
 206 
 207         v->v_ndef = w;
 208         return (w);
 209 }
 210 
 211 /*
 212  * Can return NULL only if the nv's memory allocation flags include UM_NOSLEEP
 213  */
 214 mdb_var_t *
 215 mdb_nv_insert(mdb_nv_t *nv, const char *name, const mdb_nv_disc_t *disc,
 216     uintmax_t value, uint_t flags)
 217 {
 218         size_t i = nv_hashstring(name) % nv->nv_hashsz;
 219         mdb_var_t *v;
 220 
 221         ASSERT(!(flags & MDB_NV_EXTNAME) || !(flags & MDB_NV_OVERLOAD));
 222         ASSERT(!(flags & MDB_NV_RDONLY) || !(flags & MDB_NV_OVERLOAD));
 223 
 224         /*
 225          * If the specified name is already hashed,
 226          * and MDB_NV_OVERLOAD is set:  insert new var into overload chain
 227          * and MDB_NV_RDONLY is set:    leave var unchanged, issue warning
 228          * otherwise:                   update var with new value
 229          */
 230         for (v = nv->nv_hash[i]; v != NULL; v = v->v_next) {
 231                 if (strcmp(NV_NAME(v), name) == 0) {
 232                         if (v->v_flags & MDB_NV_OVERLOAD) {
 233                                 mdb_var_t *w = nv_var_alloc(NV_NAME(v), disc,
 234                                     value, flags, nv->nv_um_flags, NULL);
 235 
 236                                 if (w == NULL) {
 237                                         ASSERT(nv->nv_um_flags & UM_NOSLEEP);
 238                                         return (NULL);
 239                                 }
 240 
 241                                 if (flags & MDB_NV_INTERPOS)
 242                                         v = nv_var_interpos(nv, i, v, w);
 243                                 else
 244                                         v = nv_var_overload(v, w);
 245 
 246                         } else if (v->v_flags & MDB_NV_RDONLY) {
 247                                 if (!(flags & MDB_NV_SILENT)) {
 248                                         warn("cannot modify read-only "
 249                                             "variable '%s'\n", NV_NAME(v));
 250                                 }
 251                         } else
 252                                 v->v_uvalue = value;
 253 
 254                         ASSERT(v != NULL);
 255                         return (v);
 256                 }
 257         }
 258 
 259         /*
 260          * If the specified name was not found, initialize a new element
 261          * and add it to the hash table at the beginning of this chain:
 262          */
 263         v = nv_var_alloc(name, disc, value, flags, nv->nv_um_flags,
 264             nv->nv_hash[i]);
 265 
 266         if (v == NULL) {
 267                 ASSERT(nv->nv_um_flags & UM_NOSLEEP);
 268                 return (NULL);
 269         }
 270 
 271         nv->nv_hash[i] = v;
 272         nv->nv_nelems++;
 273 
 274         return (v);
 275 }
 276 
 277 static void
 278 nv_var_defn_remove(mdb_var_t *v, mdb_var_t *corpse, uint_t um_flags)
 279 {
 280         mdb_var_t *w = v;
 281 
 282         while (v->v_ndef != NULL && v->v_ndef != corpse)
 283                 v = v->v_ndef;
 284 
 285         if (v == NULL) {
 286                 fail("var %p ('%s') not found on defn chain of %p\n",
 287                     (void *)corpse, NV_NAME(corpse), (void *)w);
 288         }
 289 
 290         v->v_ndef = corpse->v_ndef;
 291         corpse->v_ndef = NULL;
 292         nv_var_free(corpse, um_flags);
 293 }
 294 
 295 void
 296 mdb_nv_remove(mdb_nv_t *nv, mdb_var_t *corpse)
 297 {
 298         const char *cname = NV_NAME(corpse);
 299         size_t i = nv_hashstring(cname) % nv->nv_hashsz;
 300         mdb_var_t *v = nv->nv_hash[i];
 301         mdb_var_t **pvp;
 302 
 303         if (corpse->v_flags & MDB_NV_PERSIST) {
 304                 warn("cannot remove persistent variable '%s'\n", cname);
 305                 return;
 306         }
 307 
 308         if (v != corpse) {
 309                 do {
 310                         if (strcmp(NV_NAME(v), cname) == 0) {
 311                                 if (corpse->v_flags & MDB_NV_OVERLOAD) {
 312                                         nv_var_defn_remove(v, corpse,
 313                                             nv->nv_um_flags);
 314                                         return; /* No v_next changes needed */
 315                                 } else
 316                                         goto notfound;
 317                         }
 318 
 319                         if (v->v_next == corpse)
 320                                 break; /* Corpse is next on the chain */
 321 
 322                 } while ((v = v->v_next) != NULL);
 323 
 324                 if (v == NULL)
 325                         goto notfound;
 326 
 327                 pvp = &v->v_next;
 328         } else
 329                 pvp = &nv->nv_hash[i];
 330 
 331         if ((corpse->v_flags & MDB_NV_OVERLOAD) && corpse->v_ndef != NULL) {
 332                 corpse->v_ndef->v_next = corpse->v_next;
 333                 *pvp = corpse->v_ndef;
 334                 corpse->v_ndef = NULL;
 335         } else {
 336                 *pvp = corpse->v_next;
 337                 nv->nv_nelems--;
 338         }
 339 
 340         nv_var_free(corpse, nv->nv_um_flags);
 341         return;
 342 
 343 notfound:
 344         fail("var %p ('%s') not found on hash chain: nv=%p [%lu]\n",
 345             (void *)corpse, cname, (void *)nv, (ulong_t)i);
 346 }
 347 
 348 void
 349 mdb_nv_rewind(mdb_nv_t *nv)
 350 {
 351         size_t i;
 352 
 353         for (i = 0; i < nv->nv_hashsz; i++) {
 354                 if (nv->nv_hash[i] != NULL)
 355                         break;
 356         }
 357 
 358         nv->nv_iter_elt = i < nv->nv_hashsz ? nv->nv_hash[i] : NULL;
 359         nv->nv_iter_bucket = i;
 360 }
 361 
 362 mdb_var_t *
 363 mdb_nv_advance(mdb_nv_t *nv)
 364 {
 365         mdb_var_t *v = nv->nv_iter_elt;
 366         size_t i;
 367 
 368         if (v == NULL)
 369                 return (NULL);
 370 
 371         if (v->v_next != NULL) {
 372                 nv->nv_iter_elt = v->v_next;
 373                 return (v);
 374         }
 375 
 376         for (i = nv->nv_iter_bucket + 1; i < nv->nv_hashsz; i++) {
 377                 if (nv->nv_hash[i] != NULL)
 378                         break;
 379         }
 380 
 381         nv->nv_iter_elt = i < nv->nv_hashsz ? nv->nv_hash[i] : NULL;
 382         nv->nv_iter_bucket = i;
 383 
 384         return (v);
 385 }
 386 
 387 mdb_var_t *
 388 mdb_nv_peek(mdb_nv_t *nv)
 389 {
 390         return (nv->nv_iter_elt);
 391 }
 392 
 393 size_t
 394 mdb_nv_size(mdb_nv_t *nv)
 395 {
 396         return (nv->nv_nelems);
 397 }
 398 
 399 static int
 400 nv_compare(const mdb_var_t **lp, const mdb_var_t **rp)
 401 {
 402         return (strcmp(mdb_nv_get_name(*lp), mdb_nv_get_name(*rp)));
 403 }
 404 
 405 void
 406 mdb_nv_sort_iter(mdb_nv_t *nv, int (*func)(mdb_var_t *, void *),
 407     void *private, uint_t um_flags)
 408 {
 409         mdb_var_t **vps =
 410             mdb_alloc(nv->nv_nelems * sizeof (mdb_var_t *), um_flags);
 411 
 412         if (nv->nv_nelems != 0 && vps != NULL) {
 413                 mdb_var_t *v, **vpp = vps;
 414                 size_t i;
 415 
 416                 for (mdb_nv_rewind(nv); (v = mdb_nv_advance(nv)) != NULL; )
 417                         *vpp++ = v;
 418 
 419                 qsort(vps, nv->nv_nelems, sizeof (mdb_var_t *),
 420                     (int (*)(const void *, const void *))nv_compare);
 421 
 422                 for (vpp = vps, i = 0; i < nv->nv_nelems; i++) {
 423                         if (func(*vpp++, private) == -1)
 424                                 break;
 425                 }
 426 
 427                 if (!(um_flags & UM_GC))
 428                         mdb_free(vps, nv->nv_nelems * sizeof (mdb_var_t *));
 429         }
 430 }
 431 
 432 void
 433 mdb_nv_defn_iter(mdb_var_t *v, int (*func)(mdb_var_t *, void *), void *private)
 434 {
 435         if (func(v, private) == -1 || !(v->v_flags & MDB_NV_OVERLOAD))
 436                 return;
 437 
 438         for (v = v->v_ndef; v != NULL; v = v->v_ndef) {
 439                 if (func(v, private) == -1)
 440                         break;
 441         }
 442 }
 443 
 444 uintmax_t
 445 mdb_nv_get_value(const mdb_var_t *v)
 446 {
 447         if (v->v_disc)
 448                 return (v->v_disc->disc_get(v));
 449 
 450         return (v->v_uvalue);
 451 }
 452 
 453 void
 454 mdb_nv_set_value(mdb_var_t *v, uintmax_t l)
 455 {
 456         if (v->v_flags & MDB_NV_RDONLY) {
 457                 warn("cannot modify read-only variable '%s'\n", NV_NAME(v));
 458                 return;
 459         }
 460 
 461         if (v->v_disc)
 462                 v->v_disc->disc_set(v, l);
 463         else
 464                 v->v_uvalue = l;
 465 }
 466 
 467 void *
 468 mdb_nv_get_cookie(const mdb_var_t *v)
 469 {
 470         if (v->v_disc)
 471                 return ((void *)(uintptr_t)v->v_disc->disc_get(v));
 472 
 473         return (MDB_NV_COOKIE(v));
 474 }
 475 
 476 void
 477 mdb_nv_set_cookie(mdb_var_t *v, void *cookie)
 478 {
 479         mdb_nv_set_value(v, (uintmax_t)(uintptr_t)cookie);
 480 }
 481 
 482 const char *
 483 mdb_nv_get_name(const mdb_var_t *v)
 484 {
 485         return (NV_NAME(v));
 486 }
 487 
 488 mdb_var_t *
 489 mdb_nv_get_ndef(const mdb_var_t *v)
 490 {
 491         if (v->v_flags & MDB_NV_OVERLOAD)
 492                 return (v->v_ndef);
 493 
 494         return (NULL);
 495 }