/* * dhcp_lookup_dd_classify: perform lookup_dd(), or use existing records * if supplied, and classify the results based on the type of search criteria * being employed. Centralized policy for DN_FMANUAL and DN_FUNUSABLE flag * processing are implemented here. Classification is specialized * based on these specific search criteria: * * S_CID A CID match is requested. Perform DN_FMANUAL and * DN_FUNUSABLE processing. * S_FREE A search for free records. Only examine first * matching record. * S_LRU A search for lru records. Perform sort if needed, * and only examine first matching record. * * A matching record is detached and returned if found (ok || * manual + unusable). Other successful matches are returned in recordsp as * a cache. */ void * dhcp_lookup_dd_classify(dsvc_dnet_t *pnd, boolean_t partial, uint_t query, int count, const dn_rec_t *targetp, void **recordsp, int searchtype) { int err; uint_t rec_cnt = 0, manual = 0; dn_rec_t *dnp; dn_rec_list_t *nlp = NULL, *dnlp = NULL; dn_rec_list_t *unulp = NULL; /* list of unusables, !manual */ dn_rec_list_t *unu_m_lp = NULL; /* list of unusable + manual */ dn_rec_list_t *m_lp = NULL; /* list of manual records */ dn_rec_list_t *cachep = NULL; /* match cache */ struct in_addr swapaddr; char ntoab[INET_ADDRSTRLEN]; /* * Lookup records matching the specified criteria, or use * records from a previous lookup supplied for classification. */ if (*recordsp == NULL) { TNF_PROBE_1_DEBUG(classify, "classify classify", "classify_query%debug 'in func classify'", tnf_long, query, query); err = dhcp_lookup_dd(pnd->dh, partial, query, count, targetp, (void **)recordsp, &rec_cnt); TNF_PROBE_1_DEBUG(classify_cid_end, "classify classify_end", "classify_end%debug 'in func classify'", tnf_long, rec_cnt, rec_cnt); /* * If any error occurs, mark the dsvc_dnet_t table * for immediate close and reopen. Let the protocol * perform recover, rather than attempting time-consuming * in-place error recovery. */ if (err != DSVC_SUCCESS) { (void) mutex_lock(&pnd->pnd_mtx); pnd->flags |= DHCP_PND_ERROR; hash_Dtime(pnd->hand, 0); (void) mutex_unlock(&pnd->pnd_mtx); #ifdef DEBUG dhcpmsg(LOG_DEBUG, "classify failure %s\n", dhcpsvc_errmsg(err)); #endif /* DEBUG */ *recordsp = NULL; return (NULL); } /* * For LRU classification, sort returned records based * on dn_lease field. Discards records with valid lease * times; adjusts rec_cnt accordingly. */ if (searchtype & S_LRU) *recordsp = get_lrusort(pnd, *recordsp, &rec_cnt); } /* * Record classification: scan through all records, performing * DN_FUNUSABLE and DN_FMANUAL processing. Note that most of the * work has been performed by the datastore query. Remove the matching * entry from the singlely-linked record list, for return. Free any * non-matching entries prior to the match. Pass back any additional * entries after the match in the recordsp pointer for possible re-use * by the caching code. */ for (nlp = detach_dnrec_from_list(NULL, *recordsp, (dn_rec_list_t **)recordsp); nlp != NULL; nlp = detach_dnrec_from_list(NULL, *recordsp, (dn_rec_list_t **)recordsp)) { /* * If we find that there is a DN_FMANUAL entry that is * DN_FUNUSABLE, we fail the request, when performing a * CID search, even though there may be other CID matches. In * the CID case, those other CID matches are errors, because * there should be one and only one record for a client if that * record is marked as being DN_FMANUALly assigned. We tell * the user how many of those CID matches there are. If there * are no DN_FMANUAL records, the first matching record which * is USABLE wins. */ dnp = nlp->dnl_rec; if (dnp->dn_flags & DN_FUNUSABLE) { if ((searchtype & (S_CID|S_FREE|S_LRU)) == S_CID) { char cidbuf[DHCP_MAX_OPT_SIZE]; uint_t blen = sizeof (cidbuf); (void) octet_to_hexascii(targetp->dn_cid, targetp->dn_cid_len, cidbuf, &blen); swapaddr.s_addr = htonl(dnp->dn_cip.s_addr); dhcpmsg(LOG_NOTICE, "(%1$s,%2$s) " "currently marked as unusable.\n", cidbuf, inet_ntop(AF_INET, &swapaddr, ntoab, sizeof (ntoab))); } /* build list of unusable records */ if (dnp->dn_flags & DN_FMANUAL) { attach_dnrec_to_list(nlp, &unu_m_lp); manual++; } else attach_dnrec_to_list(nlp, &unulp); } else { if (dnp->dn_flags & DN_FMANUAL) { attach_dnrec_to_list(nlp, &m_lp); manual++; } else attach_dnrec_to_list(nlp, &cachep); /* * These searches do not require examining all * matches. */ if (searchtype & (S_FREE|S_LRU)) break; } } /* * Warnings are printed for CID searches which end with * DN_FUNUSABLE|DN_FMANUAL match(es). */ if (m_lp != NULL || unu_m_lp != NULL) { if (manual > 1) { char cidbuf[DHCP_MAX_OPT_SIZE]; uint_t blen = sizeof (cidbuf); (void) octet_to_hexascii(targetp->dn_cid, targetp->dn_cid_len, cidbuf, &blen); dhcpmsg(LOG_WARNING, "Manual allocation (%1$s) has %2$d other MANUAL" " records. It should have 0.\n", cidbuf, manual - 1); } if (unu_m_lp != NULL) { dnlp = detach_dnrec_from_list(NULL, unu_m_lp, &unu_m_lp); } else dnlp = detach_dnrec_from_list(NULL, m_lp, &m_lp); } /* Free any unusable entries */ if (unulp != NULL) dhcp_free_dd_list(pnd->dh, unulp); /* any other... */ if (dnlp == NULL) dnlp = detach_dnrec_from_list(NULL, cachep, &cachep); /* * Return any unused elements for possible caching use. These are * the additional manual + unusable (as punishment for having * multiple items), manual, and and any others. */ if (cachep != NULL) attach_dnrec_to_list(cachep, (dn_rec_list_t **)recordsp); if (m_lp != NULL) attach_dnrec_to_list(m_lp, (dn_rec_list_t **)recordsp); if (unu_m_lp != NULL) attach_dnrec_to_list(unu_m_lp, (dn_rec_list_t **)recordsp); /* * Return one of the matching record(s). */ return (dnlp); }
/* ARGSUSED */ static void svc_lock(void *cookie, dsvcd_request_t *req, size_t reqsize, door_desc_t *doorp, uint_t ndoors) { dsvcd_reply_t reply; door_desc_t door_desc; dsvcd_lock_request_t *lreq = (dsvcd_lock_request_t *)req; dsvcd_datastore_t *ds = (dsvcd_datastore_t *)cookie; dsvcd_container_t *cn; dsvcd_unlock_desc_t *ud; char conid[MAXPATHLEN]; unsigned int attempts = 0; reply.rp_version = DSVCD_DOOR_VERSION; reply.rp_retval = check_door_req(req, reqsize, sizeof (dsvcd_lock_request_t)); if (reply.rp_retval != DSVC_SUCCESS) { (void) door_return((char *)&reply, sizeof (reply), NULL, 0); return; } /* * Verify that this is a lock request; if in the future we support * other requests, we'll have to abstract this a bit. */ if (req->rq_reqtype != DSVCD_LOCK) { dhcpmsg(MSG_WARNING, "unsupported request `%d' on lock " "request door", req->rq_reqtype); reply.rp_retval = DSVC_SYNCH_ERR; (void) door_return((char *)&reply, sizeof (reply), NULL, 0); return; } if (lreq->lrq_locktype != DSVCD_RDLOCK && lreq->lrq_locktype != DSVCD_WRLOCK) { dhcpmsg(MSG_WARNING, "request for unsupported locktype `%d'", lreq->lrq_locktype); reply.rp_retval = DSVC_SYNCH_ERR; (void) door_return((char *)&reply, sizeof (reply), NULL, 0); return; } /* * Find the container; create if it doesn't already exist. We do * this as a single operation to avoid race conditions. */ (void) snprintf(conid, sizeof (conid), "%s/%s%d_%s", lreq->lrq_loctoken, ds->ds_name, lreq->lrq_conver, lreq->lrq_conname); cn = ds_get_container(ds, conid, lreq->lrq_crosshost); if (cn == NULL) { reply.rp_retval = DSVC_NO_MEMORY; (void) door_return((char *)&reply, sizeof (reply), NULL, 0); return; } /* * We need another door descriptor which is passed back with the * request. This descriptor is used when the caller wants to * gracefully unlock or when the caller terminates abnormally. */ ud = ud_create(cn, &reply.rp_retval); if (ud == NULL) { ds_release_container(ds, cn); (void) door_return((char *)&reply, sizeof (reply), NULL, 0); return; } /* * We pass a duped door descriptor with the DOOR_RELEASE flag set * instead of just passing the descriptor itself to handle the case * where the client has gone away before we door_return(). Since * we duped, the door descriptor itself will have a refcount of 2 * when we go to pass it to the client; if the client does not * exist, the DOOR_RELEASE will drop the count from 2 to 1 which * will cause a DOOR_UNREF_DATA call. * * In the regular (non-error) case, the door_return() will handoff * the descriptor to the client, bumping the refcount to 3, and * then the DOOR_RELEASE will drop the count to 2. If the client * terminates abnormally after this point, the count will drop from * 2 to 1 which will cause a DOOR_UNREF_DATA call. If the client * unlocks gracefully, the refcount will still be 2 when the unlock * door server procedure is called, and the unlock procedure will * unlock the lock and note that the lock has been unlocked (so * that we know the DOOR_UNREF_DATA call generated from the client * subsequently closing the unlock descriptor is benign). * * Note that a DOOR_UNREF_DATA call will be generated *any time* * the refcount goes from 2 to 1 -- even if *we* cause it to * happen, which by default will happen in some of the error logic * below (when we close the duped descriptor). To prevent this * scenario, we tell ud_destroy() *not* to cache the unlock * descriptor, which forces it to blow away the descriptor using * door_revoke(), making the close() that follows benign. */ door_desc.d_attributes = DOOR_DESCRIPTOR|DOOR_RELEASE; door_desc.d_data.d_desc.d_descriptor = dup(ud->ud_fd); if (door_desc.d_data.d_desc.d_descriptor == -1) { dhcpmsg(MSG_ERR, "cannot dup unlock door; denying %s " "lock request", cn->cn_id); ud_destroy(ud, B_TRUE); ds_release_container(ds, cn); reply.rp_retval = DSVC_NO_RESOURCES; (void) door_return((char *)&reply, sizeof (reply), NULL, 0); return; } /* * Acquire the actual read or write lock on the container. */ dhcpmsg(MSG_DEBUG, "tid %d: %s locking %s", thr_self(), lreq->lrq_locktype == DSVCD_RDLOCK ? "read" : "write", cn->cn_id); if (lreq->lrq_locktype == DSVCD_RDLOCK) reply.rp_retval = cn_rdlock(cn, lreq->lrq_nonblock); else if (lreq->lrq_locktype == DSVCD_WRLOCK) reply.rp_retval = cn_wrlock(cn, lreq->lrq_nonblock); dhcpmsg(MSG_DEBUG, "tid %d: %s %s lock operation: %s", thr_self(), cn->cn_id, lreq->lrq_locktype == DSVCD_RDLOCK ? "read" : "write", dhcpsvc_errmsg(reply.rp_retval)); ds_release_container(ds, cn); if (reply.rp_retval != DSVC_SUCCESS) { ud_destroy(ud, B_FALSE); (void) close(door_desc.d_data.d_desc.d_descriptor); (void) door_return((char *)&reply, sizeof (reply), NULL, 0); return; } while (door_return((char *)&reply, sizeof (reply), &door_desc, 1) == -1 && errno == EMFILE) { if (lreq->lrq_nonblock) { dhcpmsg(MSG_WARNING, "unable to grant lock; client" " is out of file descriptors"); (void) cn_unlock(cn); ud_destroy(ud, B_FALSE); (void) close(door_desc.d_data.d_desc.d_descriptor); reply.rp_retval = DSVC_BUSY; (void) door_return((char *)&reply, sizeof (reply), NULL, 0); return; } if (attempts++ == 0) { dhcpmsg(MSG_WARNING, "unable to grant lock; client" " is out of file descriptors (retrying)"); } (void) poll(NULL, 0, 100); } }
/* ARGSUSED */ static void svc_unlock(void *cookie, dsvcd_request_t *req, size_t reqsize, door_desc_t *doorp, uint_t ndoors) { dsvcd_unlock_desc_t *ud = cookie; dsvcd_container_t *cn; dsvcd_reply_t reply; /* * Although unlock descriptors are handed out to only a single * thread who has been granted a lock (ergo it seems that only one * thread should be able to call us back), there's a potential race * here if the process crashes while in this door_call(), since * both this thread and the unref kernel upcall thread may run at * the same time. Protect against this case with a mutex. */ (void) mutex_lock(&ud->ud_lock); cn = ud->ud_cn; /* * First handle the case where the lock owner has closed the unlock * descriptor, either because they have unlocked the lock and are * thus done using the descriptor, or because they crashed. In the * second case, print a message. */ if (req == DOOR_UNREF_DATA) { /* * The last reference is ours; we can free the descriptor. */ (void) mutex_unlock(&ud->ud_lock); ud_destroy(ud, B_TRUE); /* * Normal case: the caller is closing the unlock descriptor * on a lock they've already unlocked -- just return. */ if (cn == NULL) { (void) door_return(NULL, 0, NULL, 0); return; } /* * Error case: the caller has crashed while holding the * unlock descriptor (or is otherwise in violation of * protocol). Since all datastores are required to be * robust even if unexpected termination occurs, we assume * the container is not corrupt, even if the process * crashed with the write lock held. */ switch (cn_locktype(cn)) { case DSVCD_RDLOCK: dhcpmsg(MSG_WARNING, "process exited while reading " "`%s'; unlocking", cn->cn_id); (void) cn_unlock(cn); break; case DSVCD_WRLOCK: dhcpmsg(MSG_WARNING, "process exited while writing " "`%s'; unlocking", cn->cn_id); dhcpmsg(MSG_WARNING, "note that this write operation " "may or may not have succeeded"); (void) cn_unlock(cn); break; case DSVCD_NOLOCK: dhcpmsg(MSG_CRIT, "unreferenced unheld lock"); break; } (void) door_return(NULL, 0, NULL, 0); return; } /* * Verify that this is a unlock request; if in the future we support * other requests, we'll have to abstract this a bit. */ reply.rp_version = DSVCD_DOOR_VERSION; reply.rp_retval = check_door_req(req, reqsize, sizeof (dsvcd_unlock_request_t)); if (reply.rp_retval != DSVC_SUCCESS) { (void) mutex_unlock(&ud->ud_lock); (void) door_return((char *)&reply, sizeof (reply), NULL, 0); return; } if (req->rq_reqtype != DSVCD_UNLOCK) { dhcpmsg(MSG_WARNING, "unsupported request `%d' on unlock " "request door", req->rq_reqtype); (void) mutex_unlock(&ud->ud_lock); reply.rp_retval = DSVC_SYNCH_ERR; (void) door_return((char *)&reply, sizeof (reply), NULL, 0); return; } /* * Attempt to unlock an already-unlocked container; log and return. */ if (cn == NULL) { dhcpmsg(MSG_WARNING, "process tried to re-unlock a lock"); (void) mutex_unlock(&ud->ud_lock); reply.rp_retval = DSVC_SYNCH_ERR; (void) door_return((char *)&reply, sizeof (reply), NULL, 0); return; } ud->ud_cn = NULL; /* * Unlock the container; note that after cn_unlock() has been done * cn->cn_id is no longer accessible. */ dhcpmsg(MSG_DEBUG, "tid %d: unlocking %s", thr_self(), cn->cn_id); reply.rp_retval = cn_unlock(cn); dhcpmsg(MSG_DEBUG, "tid %d: unlock operation: %s", thr_self(), dhcpsvc_errmsg(reply.rp_retval)); /* * Even though we've unlocked the lock, we cannot yet destroy the * unlock descriptor (even if we revoke the door) because it's * possible the unref thread is already waiting on ud_lock. */ (void) mutex_unlock(&ud->ud_lock); (void) door_return((char *)&reply, sizeof (reply), NULL, 0); }
/* * mkdstore <table> <nrecords> <cid> <flags> <cip> <sip> <lease> <macro> * <comment> */ main(int c, char **v) { long long cid; uchar_t flags; struct in_addr cip; struct in_addr sip; int i, j; char **entries; uint_t lease; char *network = v[1]; int ct = strtol(v[2], 0L, 0L); char *server; char *macro; int err; uint32_t query; dn_rec_t dn; dn_rec_list_t *dncp = NULL; dhcp_confopt_t *dsp = NULL; #ifdef DEBUG mallocctl(MTDEBUGPATTERN, 1); mallocctl(MTINITBUFFER, 1); #endif /* DEBUG */ if (c == 1) { (void) fprintf(stderr, "/*\n * mkdstore <table> <nrecords> " "<cid> <flags> <cip> <sip> <lease> <comment>\n*/"); return (0); } cid = (c > 3) ? strtoul(v[3], 0L, 0L) : 0; flags = (c > 4) ? (char)strtol(v[4], 0L, 0L) : 0; cip.s_addr = (c > 5) ? strtoul(v[5], 0L, 0L) : 0; sip.s_addr = (c > 6) ? strtoul(v[6], 0L, 0L) : 0; lease = (c > 7) ? strtoul(v[7], 0L, 0L) : 0; macro = (c > 8) ? v[8] : 0; server = (c > 9) ? v[9] : "unknown"; entries = (char **) malloc(ct * (sizeof (char *) * 8 + 4)); /* Load current datastore. */ (void) read_dsvc_conf(&dsp); if ((i = confopt_to_datastore(dsp, &datastore)) != DSVC_SUCCESS) { (void) fprintf(stderr, "Invalid datastore: %s\n", dhcpsvc_errmsg(i)); return (EINVAL); } err = open_dd(&dh, &datastore, DSVC_DHCPNETWORK, network, DSVC_READ | DSVC_WRITE); if (err != DSVC_SUCCESS) { (void) fprintf(stderr, "Invalid network: %s trying create...\n", dhcpsvc_errmsg(err)); err = open_dd(&dh, &datastore, DSVC_DHCPNETWORK, network, DSVC_READ | DSVC_WRITE | DSVC_CREATE); if (err != DSVC_SUCCESS) { (void) fprintf(stderr, "Can't create network: %s\n", dhcpsvc_errmsg(err)); return (err); } } /* XXXX: bug: currently can't get the count as advertised */ (void) memset(&dn, '\0', sizeof (dn)); DSVC_QINIT(query); err = lookup_dd(dh, B_FALSE, query, -1, (const void *) &dn, (void **) &dncp, &nrecords); if (dncp) free_dd_list(dh, dncp); if (err != DSVC_SUCCESS) { (void) fprintf(stderr, "Bad nrecords: %s [%d]\n", dhcpsvc_errmsg(err), nrecords); return (err); } for (i = 0, j = 0; i < ct; i++) { TNF_PROBE_1(main, "main", "main%debug 'in function main'", tnf_ulong, record, i); if (cid) { (void) memcpy(dn.dn_cid, &cid, sizeof (long long)); dn.dn_cid_len = 7; } else { (void) memset(dn.dn_cid, '\0', sizeof (long long)); dn.dn_cid_len = 1; } dn.dn_sig = 0; dn.dn_flags = flags; dn.dn_cip.s_addr = cip.s_addr; dn.dn_sip.s_addr = sip.s_addr; dn.dn_lease = lease; strcpy(dn.dn_macro, macro); strcpy(dn.dn_comment, server); (void) add_dd_entry(dh, &dn); if (cid) cid += 0x100; cip.s_addr++; TNF_PROBE_0(main_end, "main", ""); } (void) close_dd(&dh); return (0); }