Example #1
0
static int
contract_abandon_id(ctid_t ctid) {
  int fd = 0;
  int err = 0;

  fd = contract_open(ctid, "all", "ctl", O_WRONLY);
  if (fd == -1)
    return (errno);

  err = ct_ctl_abandon(fd);
  (void) close(fd);

  return (err);
}
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);
}