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