Example #1
0
void
event_process(FILE *file, ct_evthdl_t ev, int verbose)
{
	uint_t type;
	pid_t pid;
	char *s;

	type = ct_event_get_type(ev);
	if (ct_pr_event_get_pid(ev, &pid) != 0) {
		(void) fprintf(file, dgettext(TEXT_DOMAIN, "[bad event]\n"));
		return;
	}

	switch (type) {
	case CT_PR_EV_EMPTY:
		s = dgettext(TEXT_DOMAIN, "contract empty\n");
		break;
	case CT_PR_EV_FORK:
		s = dgettext(TEXT_DOMAIN, "process %d was created\n");
		break;
	case CT_PR_EV_EXIT:
		s = dgettext(TEXT_DOMAIN, "process %d exited\n");
		break;
	case CT_PR_EV_CORE:
		s = dgettext(TEXT_DOMAIN, "process %d dumped core\n");
		break;
	case CT_PR_EV_SIGNAL:
		s = dgettext(TEXT_DOMAIN,
		    "process %d received a fatal signal\n");
		break;
	case CT_PR_EV_HWERR:
		s = dgettext(TEXT_DOMAIN,
		    "process %d was killed by a hardware error\n");
		break;
	default:
		s = dgettext(TEXT_DOMAIN, "process %d sent an unknown event\n");
		break;
	}

	/*LINTED*/
	(void) fprintf(file, s, pid);
	if (!verbose)
		return;

	switch (type) {
		int i;
		const char *c;
		char buf[SIG2STR_MAX];
		ctid_t ctid;
	case CT_PR_EV_FORK:
		if (ct_pr_event_get_ppid(ev, &pid) == 0)
			(void) fprintf(file, dgettext(TEXT_DOMAIN,
			    "\tparent pid: %d\n"), pid);
		break;
	case CT_PR_EV_EXIT:
		if (ct_pr_event_get_exitstatus(ev, &i) != 0)
			break;
		(void) fprintf(file,
		    dgettext(TEXT_DOMAIN, "\twait status: 0x%x"), i);
		if (WIFEXITED(i)) {
			(void) fprintf(file, dgettext(TEXT_DOMAIN,
			    " (exited, code %d)\n"), WEXITSTATUS(i));
		} else if (WIFSIGNALED(i)) {
			int sig = WTERMSIG(i);
			(void) fprintf(file,
			    dgettext(TEXT_DOMAIN, " (signal %d"), sig);
			if (sig2str(sig, buf) == 0)
				(void) fprintf(file,
				    dgettext(TEXT_DOMAIN, " (SIG%s)"), buf);
			if (WCOREDUMP(i))
				(void) fprintf(file,
				    dgettext(TEXT_DOMAIN, ", core dumped)\n"));
			else
				(void) fprintf(file,
				    dgettext(TEXT_DOMAIN, ")\n"));
		} else {
			/*
			 * We really shouldn't get here.
			 */
			(void) fprintf(file, dgettext(TEXT_DOMAIN, "\n"));
		}
		break;
	case CT_PR_EV_CORE:
		if (ct_pr_event_get_pcorefile(ev, &c) == 0)
			(void) fprintf(file, dgettext(TEXT_DOMAIN,
			    "\tprocess core: %s\n"), c);
		if (ct_pr_event_get_gcorefile(ev, &c) == 0)
			(void) fprintf(file, dgettext(TEXT_DOMAIN,
			    "\tglobal core: %s\n"), c);
		if (ct_pr_event_get_zcorefile(ev, &c) == 0)
			(void) fprintf(file, dgettext(TEXT_DOMAIN,
			    "\tglobal zone core: %s\n"), c);
		break;
	case CT_PR_EV_SIGNAL:
		if (ct_pr_event_get_signal(ev, &i) == 0) {
			if (sig2str(i, buf) == -1)
				(void) fprintf(file, dgettext(TEXT_DOMAIN,
				    "\tsignal: %d\n"), i);
			else
				(void) fprintf(file, dgettext(TEXT_DOMAIN,
				    "\tsignal: %d (SIG%s)\n"), i, buf);
		}
		if (ct_pr_event_get_sender(ev, &pid) == 0)
			(void) fprintf(file, dgettext(TEXT_DOMAIN,
			    "\tsender pid: %d\n"), pid);
		if (ct_pr_event_get_senderct(ev, &ctid) == 0)
			(void) fprintf(file, dgettext(TEXT_DOMAIN,
			    "\tsender ctid: %d\n"), ctid);
		break;
	}
}
Example #2
0
/*
 * int method_run()
 *   Execute the type method of instp.  If it requires a fork(), wait for it
 *   to return and return its exit code in *exit_code.  Otherwise set
 *   *exit_code to 0 if the method succeeds & -1 if it fails.  If the
 *   repository connection is broken, it is rebound, but inst may not be
 *   reset.
 *   Returns
 *     0 - success
 *     EINVAL - A correct method or method context couldn't be retrieved.
 *     EIO - Contract kill failed.
 *     EFAULT - Method couldn't be executed successfully.
 *     ELOOP - Retry threshold exceeded.
 *     ECANCELED - inst was deleted from the repository before method was run
 *     ERANGE - Timeout retry threshold exceeded.
 *     EAGAIN - Failed due to external cause, retry.
 */
int
method_run(restarter_inst_t **instp, int type, int *exit_code)
{
	char *method;
	int ret_status;
	pid_t pid;
	method_restart_t restart_on;
	uint_t cte_mask;
	uint8_t need_session;
	scf_handle_t *h;
	scf_snapshot_t *snap;
	const char *mname;
	const char *errstr;
	struct method_context *mcp;
	int result = 0, timeout_fired = 0;
	int sig, r;
	boolean_t transient;
	uint64_t timeout;
	uint8_t timeout_retry;
	ctid_t ctid;
	int ctfd = -1;
	ct_evthdl_t ctev;
	uint_t evtype;
	restarter_inst_t *inst = *instp;
	int id = inst->ri_id;
	int forkerr;

	assert(PTHREAD_MUTEX_HELD(&inst->ri_lock));
	assert(instance_in_transition(inst));

	if (inst->ri_mi_deleted)
		return (ECANCELED);

	*exit_code = 0;

	assert(0 <= type && type <= 2);
	mname = method_names[type];

	if (type == METHOD_START)
		inst->ri_pre_online_hook();

	h = scf_instance_handle(inst->ri_m_inst);

	snap = scf_snapshot_create(h);
	if (snap == NULL ||
	    scf_instance_get_snapshot(inst->ri_m_inst, "running", snap) != 0) {
		log_framework(LOG_DEBUG,
		    "Could not get running snapshot for %s.  "
		    "Using editing version to run method %s.\n",
		    inst->ri_i.i_fmri, mname);
		scf_snapshot_destroy(snap);
		snap = NULL;
	}

	/*
	 * After this point, we may be logging to the instance log.
	 * Make sure we've noted where that log is as a property of
	 * the instance.
	 */
	r = libscf_note_method_log(inst->ri_m_inst, st->st_log_prefix,
	    inst->ri_logstem);
	if (r != 0) {
		log_framework(LOG_WARNING,
		    "%s: couldn't note log location: %s\n",
		    inst->ri_i.i_fmri, strerror(r));
	}

	if ((method = libscf_get_method(h, type, inst, snap, &restart_on,
	    &cte_mask, &need_session, &timeout, &timeout_retry)) == NULL) {
		if (errno == LIBSCF_PGROUP_ABSENT)  {
			log_framework(LOG_DEBUG,
			    "%s: instance has no method property group '%s'.\n",
			    inst->ri_i.i_fmri, mname);
			if (type == METHOD_REFRESH)
				log_instance(inst, B_TRUE, "No '%s' method "
				    "defined.  Treating as :true.", mname);
			else
				log_instance(inst, B_TRUE, "Method property "
				    "group '%s' is not present.", mname);
			scf_snapshot_destroy(snap);
			return (0);
		} else if (errno == LIBSCF_PROPERTY_ABSENT)  {
			log_framework(LOG_DEBUG,
			    "%s: instance has no '%s/exec' method property.\n",
			    inst->ri_i.i_fmri, mname);
			log_instance(inst, B_TRUE, "Method property '%s/exec "
			    "is not present.", mname);
			scf_snapshot_destroy(snap);
			return (0);
		} else {
			log_error(LOG_WARNING,
			    "%s: instance libscf_get_method failed\n",
			    inst->ri_i.i_fmri);
			scf_snapshot_destroy(snap);
			return (EINVAL);
		}
	}

	/* open service contract if stopping a non-transient service */
	if (type == METHOD_STOP && (!instance_is_transient_style(inst))) {
		if (inst->ri_i.i_primary_ctid == 0) {
			/* service is not running, nothing to stop */
			log_framework(LOG_DEBUG, "%s: instance has no primary "
			    "contract, no service to stop.\n",
			    inst->ri_i.i_fmri);
			scf_snapshot_destroy(snap);
			return (0);
		}
		if ((ctfd = contract_open(inst->ri_i.i_primary_ctid, "process",
		    "events", O_RDONLY)) < 0) {
			result = EFAULT;
			log_instance(inst, B_TRUE, "Could not open service "
			    "contract %ld.  Stop method not run.\n",
			    inst->ri_i.i_primary_ctid);
			goto out;
		}
	}

	if (restarter_is_null_method(method)) {
		log_framework(LOG_DEBUG, "%s: null method succeeds\n",
		    inst->ri_i.i_fmri);

		log_instance(inst, B_TRUE, "Executing %s method (null)", mname);

		if (type == METHOD_START)
			write_status(inst, mname, 0);
		goto out;
	}

	sig = restarter_is_kill_method(method);
	if (sig >= 0) {

		if (inst->ri_i.i_primary_ctid == 0) {
			log_error(LOG_ERR, "%s: :kill with no contract\n",
			    inst->ri_i.i_fmri);
			result = EINVAL;
			goto out;
		}

		log_framework(LOG_DEBUG,
		    "%s: :killing contract with signal %d\n",
		    inst->ri_i.i_fmri, sig);

		log_instance(inst, B_TRUE, "Executing %s method (:kill)",
		    mname);

		if (contract_kill(inst->ri_i.i_primary_ctid, sig,
		    inst->ri_i.i_fmri) != 0) {
			result = EIO;
			goto out;
		} else
			goto assured_kill;
	}

	log_framework(LOG_DEBUG, "%s: forking to run method %s\n",
	    inst->ri_i.i_fmri, method);

	errstr = restarter_get_method_context(RESTARTER_METHOD_CONTEXT_VERSION,
	    inst->ri_m_inst, snap, mname, method, &mcp);

	if (errstr != NULL) {
		log_error(LOG_WARNING, "%s: %s\n", inst->ri_i.i_fmri, errstr);
		result = EINVAL;
		goto out;
	}

	r = method_ready_contract(inst, type, restart_on, cte_mask);
	if (r != 0) {
		assert(r == ECANCELED);
		assert(inst->ri_mi_deleted);
		restarter_free_method_context(mcp);
		result = ECANCELED;
		goto out;
	}

	/*
	 * Validate safety of method contexts, to save children work.
	 */
	if (!restarter_rm_libs_loadable())
		log_framework(LOG_DEBUG, "%s: method contexts limited "
		    "to root-accessible libraries\n", inst->ri_i.i_fmri);

	/*
	 * If the service is restarting too quickly, send it to
	 * maintenance.
	 */
	if (type == METHOD_START) {
		method_record_start(inst);
		if (method_rate_critical(inst)) {
			log_instance(inst, B_TRUE, "Restarting too quickly, "
			    "changing state to maintenance");
			result = ELOOP;
			goto out;
		}
	}

	pid = startd_fork1(&forkerr);
	if (pid == 0)
		exec_method(inst, type, method, mcp, need_session);

	if (pid == -1) {
		if (forkerr == EAGAIN)
			result = EAGAIN;
		else
			result = EFAULT;

		log_error(LOG_WARNING,
		    "%s: Couldn't fork to execute method %s: %s\n",
		    inst->ri_i.i_fmri, method, strerror(forkerr));

		goto out;
	}

	restarter_free_method_context(mcp);

	/*
	 * Get the contract id, decide whether it is primary or transient, and
	 * stash it in inst & the repository.
	 */
	method_store_contract(inst, type, &ctid);

	/*
	 * Similarly for the start method PID.
	 */
	if (type == METHOD_START && !inst->ri_mi_deleted)
		(void) libscf_write_start_pid(inst->ri_m_inst, pid);

	if (instance_is_wait_style(inst) && type == METHOD_START) {
		/* Wait style instances don't get timeouts on start methods. */
		if (wait_register(pid, inst->ri_i.i_fmri, 1, 0)) {
			log_error(LOG_WARNING,
			    "%s: couldn't register %ld for wait\n",
			    inst->ri_i.i_fmri, pid);
			result = EFAULT;
			goto contract_out;
		}
		write_status(inst, mname, 0);

	} else {
		int r, err;
		time_t start_time;
		time_t end_time;

		/*
		 * Because on upgrade/live-upgrade we may have no chance
		 * to override faulty timeout values on the way to
		 * manifest import, all services on the path to manifest
		 * import are treated the same as INFINITE timeout services.
		 */

		start_time = time(NULL);
		if (timeout != METHOD_TIMEOUT_INFINITE && !is_timeout_ovr(inst))
			timeout_insert(inst, ctid, timeout);
		else
			timeout = METHOD_TIMEOUT_INFINITE;

		/* Unlock the instance while waiting for the method. */
		MUTEX_UNLOCK(&inst->ri_lock);

		do
			r = waitpid(pid, &ret_status, NULL);
		while (r == -1 && errno == EINTR);
		if (r == -1)
			err = errno;

		/* Re-grab the lock. */
		inst = inst_lookup_by_id(id);

		/*
		 * inst can't be removed, as the removal thread waits
		 * for completion of this one.
		 */
		assert(inst != NULL);
		*instp = inst;

		if (inst->ri_timeout != NULL && inst->ri_timeout->te_fired)
			timeout_fired = 1;

		timeout_remove(inst, ctid);

		log_framework(LOG_DEBUG,
		    "%s method for %s exited with status %d.\n", mname,
		    inst->ri_i.i_fmri, WEXITSTATUS(ret_status));

		if (r == -1) {
			log_error(LOG_WARNING,
			    "Couldn't waitpid() for %s method of %s (%s).\n",
			    mname, inst->ri_i.i_fmri, strerror(err));
			result = EFAULT;
			goto contract_out;
		}

		if (type == METHOD_START)
			write_status(inst, mname, ret_status);

		/* return ERANGE if this service doesn't retry on timeout */
		if (timeout_fired == 1 && timeout_retry == 0) {
			result = ERANGE;
			goto contract_out;
		}

		if (!WIFEXITED(ret_status)) {
			/*
			 * If method didn't exit itself (it was killed by an
			 * external entity, etc.), consider the entire
			 * method_run as failed.
			 */
			if (WIFSIGNALED(ret_status)) {
				char buf[SIG2STR_MAX];
				(void) sig2str(WTERMSIG(ret_status), buf);

				log_error(LOG_WARNING, "%s: Method \"%s\" "
				    "failed due to signal %s.\n",
				    inst->ri_i.i_fmri, method, buf);
				log_instance(inst, B_TRUE, "Method \"%s\" "
				    "failed due to signal %s", mname, buf);
			} else {
				log_error(LOG_WARNING, "%s: Method \"%s\" "
				    "failed with exit status %d.\n",
				    inst->ri_i.i_fmri, method,
				    WEXITSTATUS(ret_status));
				log_instance(inst, B_TRUE, "Method \"%s\" "
				    "failed with exit status %d", mname,
				    WEXITSTATUS(ret_status));
			}
			result = EAGAIN;
			goto contract_out;
		}

		*exit_code = WEXITSTATUS(ret_status);
		if (*exit_code != 0) {
			log_error(LOG_WARNING,
			    "%s: Method \"%s\" failed with exit status %d.\n",
			    inst->ri_i.i_fmri, method, WEXITSTATUS(ret_status));
		}

		log_instance(inst, B_TRUE, "Method \"%s\" exited with status "
		    "%d", mname, *exit_code);

		if (*exit_code != 0)
			goto contract_out;

		end_time = time(NULL);

		/* Give service contract remaining seconds to empty */
		if (timeout != METHOD_TIMEOUT_INFINITE)
			timeout -= (end_time - start_time);
	}

assured_kill:
	/*
	 * For stop methods, assure that the service contract has emptied
	 * before returning.
	 */
	if (type == METHOD_STOP && (!instance_is_transient_style(inst)) &&
	    !(contract_is_empty(inst->ri_i.i_primary_ctid))) {

		if (timeout != METHOD_TIMEOUT_INFINITE)
			timeout_insert(inst, inst->ri_i.i_primary_ctid,
			    timeout);

		for (;;) {
			do {
				r = ct_event_read_critical(ctfd, &ctev);
			} while (r == EINTR);
			if (r != 0)
				break;

			evtype = ct_event_get_type(ctev);
			ct_event_free(ctev);
			if (evtype == CT_PR_EV_EMPTY)
				break;
		}
		if (r) {
			result = EFAULT;
			log_instance(inst, B_TRUE, "Error reading service "
			    "contract %ld.\n", inst->ri_i.i_primary_ctid);
		}

		if (timeout != METHOD_TIMEOUT_INFINITE)
			if (inst->ri_timeout->te_fired)
				result = EFAULT;

		timeout_remove(inst, inst->ri_i.i_primary_ctid);
	}

contract_out:
	/* Abandon contracts for transient methods & methods that fail. */
	transient = method_is_transient(inst, type);
	if ((transient || *exit_code != 0 || result != 0) &&
	    (restarter_is_kill_method(method) < 0))
		method_remove_contract(inst, !transient, B_TRUE);

out:
	if (ctfd >= 0)
		(void) close(ctfd);
	scf_snapshot_destroy(snap);
	free(method);
	return (result);
}
Example #3
0
void
handle_events(int fd)
{
	node_contract_t *cp;
	const nc_descr_t *dp;
	ct_evthdl_t eh;
	ctid_t ctid;
	uint_t evtype;
	ctevid_t evid;
	ctevid_t nevid;
	ctid_t newct;
	uint_t flags;
	nvlist_t *ap, *sap, *rp;
	const char *evtypename;
	int err;

	while ((err = ct_event_read(fd, &eh)) == 0) {
		ctid = ct_event_get_ctid(eh);
		cp = nc_lookup(ctid);

		/*
		 * This contract has gone away.  This should be possible only
		 * if we've already abandoned it, but it may be possible to
		 * receive events for it after that.  We can't even ack here,
		 * because we don't have the ctl fd for the contract any more.
		 * Just keep going; there's nothing we can do.
		 */
		if (cp == NULL) {
			ct_event_free(eh);
			++ev_failures;
			continue;
		}

		dp = cp->nc_type->nct_events;

		evtype = ct_event_get_type(eh);
		evtypename = nc_descr_strlookup(dp, evtype);
		evid = ct_event_get_evid(eh);
		flags = ct_event_get_flags(eh);

		sap = v8plus_obj(
			VP(ctid, NUMBER, (double)ctid),
			VP(evid, STRNUMBER64, (uint64_t)evid),
			VP(type, STRING, evtypename),
			VP_V(flags, INL_OBJECT),
			    VP(info, BOOLEAN, (flags & CTE_INFO) != 0),
			    VP(ack, BOOLEAN, (flags & CTE_ACK) != 0),
			    VP(neg, BOOLEAN, (flags & CTE_NEG) != 0),
			    V8PLUS_TYPE_NONE,
			V8PLUS_TYPE_NONE);

		if (sap == NULL) {
			ct_event_free(eh);
			++ev_failures;
			continue;
		}

		if (evtype == CT_EV_NEGEND) {
			(void) ct_event_get_nevid(eh, &nevid);
			(void) ct_event_get_newct(eh, &newct);
			err = v8plus_obj_setprops(sap,
			    VP(nevid, STRNUMBER64, (uint64_t)nevid),
			    VP(newct, NUMBER, (double)newct),
			    V8PLUS_TYPE_NONE);
			if (err != 0) {
				nvlist_free(sap);
				ct_event_free(eh);
				++ev_failures;
				continue;
			}
		}

		ap = v8plus_obj(
		    VP(0, STRING, evtypename),
		    VP(1, OBJECT, sap),
		    V8PLUS_TYPE_NONE);

		nvlist_free(sap);
		ct_event_free(eh);

		if (ap == NULL) {
			++ev_failures;
			continue;
		}

		rp = v8plus_method_call(cp, "_emit", ap);
		nvlist_free(ap);
		nvlist_free(rp);
	}

	if (err != EAGAIN) {
		v8plus_panic("unexpected error from ct_event_read: %s",
		    strerror(err));
	}
}