/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * etm_ckpt.c * Description: * Checkpoint the ereport events for persitence across fmd restart. * * Each ereport is stored in a named buffer. Each ereport is uniquely * indentified by a id which is consists of a number of ereport fields. The * name of the buffer is derived from the id. * * All ereport ids are stored in the circular list which is saved in a * separate buffer. */ #include #include #include #include #include #include #include #include #include #include #include #include "etm_etm_proto.h" #include "etm_iosvc.h" #include "etm_ckpt.h" #include "etm_filter.h" #define ETM_ATTR_PRIMARY "primary" #define ETM_ATTR_TOD "__tod" #define ETM_LDOM_PRIMARY "primary" /* * -------------------------- private variables ------------------------------ */ static etm_ckpt_id_list_t *etm_id_lst = NULL; /* list of ereports ids */ static pthread_mutex_t etm_id_lst_lock; /* list lock */ /* * -------------------------- functions -------------------------------------- */ /* * etm_ckpt_str_hash() * Description: * Hash a class name to a number */ static uint_t etm_ckpt_str_hash(char *str) { uint_t hash = 0; /* hash value */ if (str == NULL) return (0); while (*str != '\0') hash += *str++; return (hash); } /* * etm_ckpt_id2str() * Description: * Get the string of an ereport id. It is used as the named buffer that * store the ereport. */ static void etm_ckpt_id2str(etm_ckpt_erpt_id_t *id, char *str, size_t size) { (void) snprintf(str, size, "%s_%llx_%d_%x_%d", ETM_CKPT_ERPT_PREFIX, id->ei_ena, id->ei_hash, id->ei_tod1, id->ei_pri); } /* * etm_ckpt_erpt2id() * Description: * Get the buffer name and ereport id of a given ereport */ static int etm_ckpt_erpt2id(fmd_hdl_t *hdl, nvlist_t *erpt, etm_ckpt_erpt_id_t *id, char *str, int size) { char *class = NULL; uint64_t *tod; uint_t sz; boolean_t pri = B_FALSE; bzero(id, sizeof (etm_ckpt_erpt_id_t)); /* ena */ if (nvlist_lookup_uint64(erpt, FM_EREPORT_ENA, &id->ei_ena) != 0) { fmd_hdl_debug(hdl, "Ena not found\n"); return (-1); } /* class name */ (void) nvlist_lookup_string(erpt, FM_CLASS, &class); if (class == NULL) { fmd_hdl_debug(hdl, "%s not found\n", FM_CLASS); return (-1); } if (strncmp(class, FM_EREPORT_CLASS, strlen(FM_EREPORT_CLASS)) != 0) { fmd_hdl_debug(hdl, "Only support checkpointing %s\n", FM_EREPORT_CLASS); return (-1); } id->ei_hash = etm_ckpt_str_hash(class); /* tod[1]: fractional of a second */ if (nvlist_lookup_uint64_array(erpt, ETM_ATTR_TOD, &tod, &sz) == 0) { if (sz >= 2) { id->ei_tod1 = (uint32_t)tod[1]; } } /* primary flag */ if (nvlist_lookup_boolean_value(erpt, ETM_ATTR_PRIMARY, &pri) == 0) { id->ei_pri = pri ? 1 : 0; } etm_ckpt_id2str(id, str, size); return (0); } /* * etm_ckpt_il_equal() * Description: * Test if two ereport ids are equal. */ static boolean_t etm_ckpt_il_equal(etm_ckpt_erpt_id_t *i1, etm_ckpt_erpt_id_t *i2) { return ((i1->ei_ena == i2->ei_ena) && (i1->ei_tod1 == i2->ei_tod1) && (i1->ei_pri == i2->ei_pri) && (i1->ei_hash == i2->ei_hash)); } /* * etm_ckpt_il_resize() * Description: * Increase the size of the circular list and pack its entries. */ static void etm_ckpt_il_resize(fmd_hdl_t *hdl, uint_t factor) { etm_ckpt_id_list_t *il1, *il2; /* temp lists */ size_t sz1, sz2; /* sizes of lists */ int i, next; /* temp counters */ etm_ckpt_erpt_id_t *p1, *p2, *s1, *s2; /* temp id pointers */ etm_ckpt_erpt_id_t blank; /* blank ereport id */ if (factor == 0) return; /* the present queue */ il1 = etm_id_lst; sz1 = sizeof (etm_ckpt_id_list_t) + il1->il_ids_sz; /* Create an empty queue with a new size */ sz2 = sizeof (etm_ckpt_id_list_t) + (factor * il1->il_ids_sz); il2 = fmd_hdl_zalloc(hdl, sz2, FMD_SLEEP); il2->il_ver = ETM_CKPT_VERSION; il2->il_max = factor * etm_id_lst->il_max; il2->il_ids_sz = factor * il1->il_ids_sz; /* pointers to the two arrays of entries */ bzero(&blank, sizeof (blank)); s1 = (etm_ckpt_erpt_id_t *) ((ptrdiff_t)il1 + sizeof (etm_ckpt_id_list_t)); s2 = (etm_ckpt_erpt_id_t *) ((ptrdiff_t)il2 + sizeof (etm_ckpt_id_list_t)); /* copy non-empty ereport ids from list il1 to il2. Toss the blank. */ if (il1->il_head != il1->il_tail) { for (i = il1->il_head; i != il1->il_tail; i = next) { next = (i + 1) % il1->il_max; p1 = s1 + next; if (!etm_ckpt_il_equal(p1, &blank)) { /* copy non-empty entries */ il2->il_tail = (il2->il_tail + 1) % il2->il_max; fmd_hdl_debug(hdl, "Copying entry %d to %d\n", next, il2->il_tail); p2 = s2 + il2->il_tail; *p2 = *p1; il2->il_cnt++; } } } if (factor == 1) { /* both lists have the same size, update the present list */ bcopy(il2, il1, sz1); fmd_hdl_free(hdl, il2, sz2); fmd_buf_write(hdl, NULL, ETM_CKPT_IL_BUF, (void *) il1, sz1); } else { /* replace the present list */ etm_id_lst = il2; fmd_hdl_free(hdl, il1, sz1); /* write to new buffer */ fmd_buf_destroy(hdl, NULL, ETM_CKPT_IL_BUF); fmd_buf_create(hdl, NULL, ETM_CKPT_IL_BUF, sz2); fmd_buf_write(hdl, NULL, ETM_CKPT_IL_BUF, (void *) il2, sz2); } } /* * etm_ckpt_il_find() * Description: * Find the ereport id in the list. */ /* ARGSUSED */ static int etm_ckpt_il_find(fmd_hdl_t *hdl, etm_ckpt_erpt_id_t *id) { int i, next; /* temp counter */ etm_ckpt_erpt_id_t *p, *s; /* temp erpt id */ fmd_hdl_debug(hdl, "etm_ckpt_il_find()\n"); /* empty list */ if (etm_id_lst->il_head == etm_id_lst->il_tail) { fmd_hdl_debug(hdl, "find an empty list\n"); return (-1); } s = (etm_ckpt_erpt_id_t *)((ptrdiff_t)etm_id_lst + sizeof (etm_ckpt_id_list_t)); for (i = etm_id_lst->il_head; i != etm_id_lst->il_tail; i = next) { next = (i + 1) % etm_id_lst->il_max; p = s + next; if (etm_ckpt_il_equal(p, id)) return (i); } return (-1); } /* * etm_ckpt_il_add() * Description: * Add an ereport id in the list. */ static int etm_ckpt_il_add(fmd_hdl_t *hdl, etm_ckpt_erpt_id_t *id) { int next; etm_ckpt_erpt_id_t *p, *s; /* temp id */ /* * resize the q if it is full. * If the capacity is less 80%, purge the emtpy entries to make more * room for new entries. Otherwise, double the queue size. */ next = (etm_id_lst->il_tail + 1) % etm_id_lst->il_max; if (next == etm_id_lst->il_head) { if ((etm_id_lst->il_cnt * 1.0 / etm_id_lst->il_max) < 0.8) { etm_ckpt_il_resize(hdl, 1); } else { etm_ckpt_il_resize(hdl, 2); } /* test if the list again */ next = (etm_id_lst->il_tail + 1) % etm_id_lst->il_max; if (next == etm_id_lst->il_head) { fmd_hdl_error(hdl, "List is full %d %d\n", etm_id_lst->il_head, etm_id_lst->il_tail); } } /* Add the id entry at the head */ s = (etm_ckpt_erpt_id_t *)((ptrdiff_t)etm_id_lst + sizeof (etm_ckpt_id_list_t)); etm_id_lst->il_tail = (etm_id_lst->il_tail + 1) % etm_id_lst->il_max; p = s + etm_id_lst->il_tail; *p = *id; etm_id_lst->il_cnt++; return (etm_id_lst->il_tail); } /* * etm_ckpt_il_delete() * Description: * Delete an ereport id from the list. */ int etm_ckpt_il_delete(fmd_hdl_t *hdl, etm_ckpt_erpt_id_t *id) { int i, next; /* temp counter */ etm_ckpt_erpt_id_t *p, *s; /* temp id pointers */ etm_ckpt_erpt_id_t blank; /* blank id */ /* empty list */ if (etm_id_lst->il_tail == etm_id_lst->il_head) { fmd_hdl_debug(hdl, "Empty queue(%d)\n", etm_id_lst->il_head); return (-1); } bzero(&blank, sizeof (blank)); s = (etm_ckpt_erpt_id_t *)((ptrdiff_t)etm_id_lst + sizeof (etm_ckpt_id_list_t)); /* delete leading empty entries */ for (i = etm_id_lst->il_head; i != etm_id_lst->il_tail; i = next) { next = (i + 1) % etm_id_lst->il_max; p = s + next; if (!etm_ckpt_il_equal(p, &blank)) { break; } etm_id_lst->il_cnt--; etm_id_lst->il_head = next; } /* empty queue */ if (etm_id_lst->il_head == etm_id_lst->il_tail) { fmd_hdl_debug(hdl, "Empty queue(%d)\n", etm_id_lst->il_head); return (-1); } /* find the entry and clear it */ for (i = etm_id_lst->il_head; i != etm_id_lst->il_tail; i = next) { next = (i + 1) % etm_id_lst->il_max; p = s + next; if (etm_ckpt_il_equal(p, id)) { /* clear the entry */ *p = blank; etm_id_lst->il_cnt--; /* remove the entry if it is the last one */ if (i == etm_id_lst->il_head) { etm_id_lst->il_head = next; } return (i); } } return (-1); } /* * etm_ckpt_il_restore() * Description: * Restore the idlist named buffer which is the circular list of the * the ereport ids. */ void etm_ckpt_il_restore(fmd_hdl_t *hdl) { size_t size; /* buffer size */ /* get the buffer of the id list */ size = fmd_buf_size(hdl, NULL, ETM_CKPT_IL_BUF); if (size < sizeof (etm_ckpt_id_list_t)) { fmd_hdl_debug(hdl, "Buffer name %s do not exist\n", ETM_CKPT_IL_BUF); return; } etm_id_lst = (etm_ckpt_id_list_t *)fmd_hdl_zalloc(hdl, size, FMD_SLEEP); fmd_buf_read(hdl, NULL, ETM_CKPT_IL_BUF, (void *) etm_id_lst, size); /* check version */ if (etm_id_lst->il_ver > ETM_CKPT_VERSION) { fmd_hdl_error(hdl, "Unsupport checkpoint version (%#x)\n", etm_id_lst->il_ver); fmd_hdl_free(hdl, (void *) etm_id_lst, size); etm_id_lst = NULL; return; } /* check the length */ if (etm_id_lst->il_ids_sz != (size - sizeof (etm_ckpt_id_list_t))) { fmd_hdl_debug(hdl, "Invalid ids buffer size (%d, %d)\n", etm_id_lst->il_ids_sz, size); fmd_hdl_free(hdl, (void *) etm_id_lst, size); etm_id_lst = NULL; return; } } /* * etm_ckpt_recover() * Description: * Recover ereports from the checkpointed data and dispatch them to the * ldom queue(s). */ void etm_ckpt_recover(fmd_hdl_t *hdl) { int size; /* buffer size */ int i, next; /* temp counter */ boolean_t dirty = B_FALSE; /* dirty flag */ uint64_t did; /* domain id */ char name[ETM_LINE_LEN]; /* temp str */ char ldom[ETM_LINE_LEN]; /* ldom id */ etm_ckpt_erpt_id_t *p, *s; /* temp ereport id */ etm_ckpt_erpt_id_t blank; /* blank ereport id */ etm_ckpt_erpt_buf_t *ep; /* ereport buffer */ size_t sz; /* size of ep */ char *buf; /* temp buf */ nvlist_t *nvl; /* ereport */ etm_iosvc_t *iosvc; /* iosvc data struct */ /* * restore the circular list of ereport ids */ etm_ckpt_il_restore(hdl); if (etm_id_lst == NULL) { fmd_hdl_debug(hdl, "Initialize a new id list\n"); size = sizeof (etm_ckpt_id_list_t) + ETM_CKPT_IL_MIN_SIZE * sizeof (etm_ckpt_erpt_id_t); etm_id_lst = fmd_hdl_zalloc(hdl, size, FMD_SLEEP); etm_id_lst->il_ver = ETM_CKPT_VERSION; etm_id_lst->il_max = ETM_CKPT_IL_MIN_SIZE; etm_id_lst->il_head = 0; etm_id_lst->il_tail = 0; etm_id_lst->il_ids_sz = ETM_CKPT_IL_MIN_SIZE * sizeof (etm_ckpt_erpt_id_t); fmd_buf_destroy(hdl, NULL, ETM_CKPT_IL_BUF); fmd_buf_create(hdl, NULL, ETM_CKPT_IL_BUF, size); fmd_buf_write(hdl, NULL, ETM_CKPT_IL_BUF, (void *) etm_id_lst, size); /* commit */ fmd_thr_checkpoint(hdl); return; } /* Empty list */ if ((etm_id_lst->il_head == etm_id_lst->il_tail) || (etm_id_lst->il_cnt == 0)) { return; } /* Visit all the entries in the list */ bzero(&blank, sizeof (blank)); s = (etm_ckpt_erpt_id_t *)((ptrdiff_t)etm_id_lst + sizeof (etm_ckpt_id_list_t)); for (i = etm_id_lst->il_head; i != etm_id_lst->il_tail; i = next) { next = (i + 1) % etm_id_lst->il_max; p = s + next; if (etm_ckpt_il_equal(p, &blank)) { fmd_hdl_debug(hdl, "Skip empty entry %d\n", i); continue; } etm_ckpt_id2str(p, name, sizeof (name)); fmd_hdl_debug(hdl, "Restoring entry %s\n", name); if ((sz = fmd_buf_size(hdl, NULL, name)) == 0) { fmd_hdl_error(hdl, "Clear the stale entry %s\n", name); *p = blank; continue; } ep = (etm_ckpt_erpt_buf_t *)fmd_hdl_zalloc(hdl, sz, FMD_SLEEP); fmd_buf_read(hdl, NULL, name, (void *) ep, sz); buf = (char *)((ptrdiff_t)ep + sizeof (etm_ckpt_erpt_buf_t)); nvl = NULL; if (nvlist_unpack(buf, ep->eb_len, &nvl, 0)) { fmd_hdl_debug(hdl, "failed to unpack %s\n", name); fmd_hdl_free(hdl, ep, sz); continue; } fmd_hdl_free(hdl, ep, sz); if (etm_filter_find_ldom_id(hdl, nvl, ldom, ETM_LINE_LEN, &did) || (strcmp(name, ETM_LDOM_PRIMARY) == 0)) { fmd_hdl_debug(hdl, "Discard event %s\n", name); fmd_buf_destroy(hdl, NULL, name); *p = blank; nvlist_free(nvl); dirty = B_TRUE; continue; } fmd_hdl_debug(hdl, "Dispatch %s to ldom %s\n", name, ldom); /* * Find the queue of the ldom, create it if not exist. * Then insert this event into the queue. */ iosvc = etm_iosvc_lookup(hdl, ldom, DS_INVALID_HDL, B_TRUE); if (iosvc != NULL) { (void) etm_pack_ds_msg(hdl, iosvc, NULL, 0, nvl, SP_MSG, ETM_CKPT_RESTORE); } nvlist_free(nvl); } if (dirty) { /* update the buffer of the queue */ size = sizeof (etm_ckpt_id_list_t) + etm_id_lst->il_ids_sz; fmd_buf_write(hdl, NULL, ETM_CKPT_IL_BUF, (void *) etm_id_lst, size); /* commit */ fmd_thr_checkpoint(hdl); } } /* etm_ckpt_recover */ /* * etm_ckpt_add_entry() * Description: * Save an ereport for persistence. */ int etm_ckpt_add_entry(fmd_hdl_t *hdl, nvlist_t *erpt) { etm_ckpt_erpt_id_t id; char name[ETM_LINE_LEN]; int rc; /* gen use */ size_t sz; /* size */ size_t buflen; /* sz of packed erpt */ uint8_t *buf; /* buffer of erpt */ etm_ckpt_erpt_buf_t *hdr; /* map ereport to id */ bzero(name, ETM_LINE_LEN); rc = etm_ckpt_erpt2id(hdl, erpt, &id, name, ETM_LINE_LEN); if (rc != 0) { fmd_hdl_debug(hdl, "Invalid ereport\n"); return (rc); } /* * check for a duplicate entry in the id list * find the ereport buffer and search for the id */ if (fmd_buf_size(hdl, NULL, name) > 0 && etm_ckpt_il_find(hdl, &id) >= 0) { fmd_hdl_debug(hdl, "Duplicate id %s\n", name); return (-1); } /* Create the ereport buffer */ if (nvlist_size(erpt, &buflen, NV_ENCODE_XDR) != 0) { fmd_hdl_debug(hdl, "nvlist_size fails\n"); return (-1); } sz = sizeof (etm_ckpt_erpt_buf_t) + buflen; hdr = (etm_ckpt_erpt_buf_t *)fmd_hdl_zalloc(hdl, sz, FMD_SLEEP); buf = (uint8_t *)((ptrdiff_t)hdr + sizeof (etm_ckpt_erpt_buf_t)); hdr->eb_ver = ETM_CKPT_VERSION; hdr->eb_len = buflen; if (nvlist_pack(erpt, (char **)&buf, &buflen, NV_ENCODE_XDR, 0) != 0) { fmd_hdl_free(hdl, hdr, sz); fmd_hdl_debug(hdl, "unpack fails\n"); return (-1); } fmd_hdl_debug(hdl, "Add ckpt event(%s, %d)\n", name, sz); fmd_buf_create(hdl, NULL, name, sz); fmd_buf_write(hdl, NULL, name, hdr, sz); fmd_hdl_free(hdl, hdr, sz); /* Insert the ereport id into the id list */ if (etm_ckpt_il_add(hdl, &id) < 0) { fmd_hdl_debug(hdl, "Insert id %s failed\n", name); fmd_buf_destroy(hdl, NULL, name); return (-1); } /* update the buffer of the queue */ sz = sizeof (etm_ckpt_id_list_t) + etm_id_lst->il_ids_sz; fmd_buf_write(hdl, NULL, ETM_CKPT_IL_BUF, (void *) etm_id_lst, sz); /* commit */ fmd_thr_checkpoint(hdl); return (0); } /* * etm_ckpt_delete_entry() * Description: * Delete an ereport id in the list. */ static int etm_ckpt_delete_entry(fmd_hdl_t *hdl, nvlist_t *erpt) { etm_ckpt_erpt_id_t id; char name[ETM_LINE_LEN]; int rc; /* return code */ size_t sz; /* size */ /* get id, id name */ bzero(name, ETM_LINE_LEN); if (etm_ckpt_erpt2id(hdl, erpt, &id, name, ETM_LINE_LEN) != 0) { fmd_hdl_debug(hdl, "Invalid ereport\n"); return (-1); } fmd_hdl_debug(hdl, "Delete ckpt event(%s)\n", name); /* delete the ereport buffer */ if (fmd_buf_size(hdl, NULL, name) > 0) { fmd_buf_destroy(hdl, NULL, name); } rc = etm_ckpt_il_delete(hdl, &id); if (rc < 0) { fmd_hdl_debug(hdl, "Delete id %s failed\n", name); return (rc); } /* update the buffer of the queue */ sz = sizeof (etm_ckpt_id_list_t) + etm_id_lst->il_ids_sz; fmd_buf_write(hdl, NULL, ETM_CKPT_IL_BUF, (void *) etm_id_lst, sz); /* commit */ fmd_thr_checkpoint(hdl); return (rc); } int etm_ckpt_add(fmd_hdl_t *hdl, nvlist_t *erpt) { int rc; /* return code */ (void) pthread_mutex_lock(&etm_id_lst_lock); rc = etm_ckpt_add_entry(hdl, erpt); (void) pthread_mutex_unlock(&etm_id_lst_lock); return (rc >= 0 ? 0 : rc); } int etm_ckpt_delete(fmd_hdl_t *hdl, nvlist_t *erpt) { int rc; /* return code */ (void) pthread_mutex_lock(&etm_id_lst_lock); rc = etm_ckpt_delete_entry(hdl, erpt); (void) pthread_mutex_unlock(&etm_id_lst_lock); return (rc >= 0 ? 0 : rc); } /* ARGSUSED */ void etm_ckpt_init(fmd_hdl_t *hdl) { (void) pthread_mutex_init(&etm_id_lst_lock, NULL); etm_id_lst = NULL; } void etm_ckpt_fini(fmd_hdl_t *hdl) { if (etm_id_lst != NULL) { fmd_hdl_free(hdl, etm_id_lst, sizeof (etm_ckpt_id_list_t) + etm_id_lst->il_ids_sz); } (void) pthread_mutex_destroy(&etm_id_lst_lock); }