xref: /illumos-gate/usr/src/cmd/svc/startd/transition.c (revision f875b4ebb1dd9fdbeb043557cab38ab3bf7f6e01)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * transition.c - Graph State Machine
30  *
31  * The graph state machine is implemented here, with a typical approach
32  * of a function per state.  Separating the implementation allows more
33  * clarity into the actions taken on notification of state change, as well
34  * as a place for future expansion including hooks for configurable actions.
35  * All functions are called with dgraph_lock held.
36  *
37  * The start action for this state machine is not explicit.  The states
38  * (ONLINE and DEGRADED) which need to know when they're entering the state
39  * due to a daemon restart implement this understanding by checking for
40  * transition from uninitialized.  In the future, this would likely be better
41  * as an explicit start action instead of relying on an overloaded transition.
42  *
43  * All gt_enter functions use the same set of return codes.
44  *    0              success
45  *    ECONNABORTED   repository connection aborted
46  */
47 
48 #include "startd.h"
49 
50 static int
51 gt_running(restarter_instance_state_t state)
52 {
53 	if (state == RESTARTER_STATE_ONLINE ||
54 	    state == RESTARTER_STATE_DEGRADED)
55 		return (1);
56 
57 	return (0);
58 }
59 
60 static int
61 gt_enter_uninit(scf_handle_t *h, graph_vertex_t *v,
62     restarter_instance_state_t old_state, restarter_error_t rerr)
63 {
64 	int err;
65 	scf_instance_t *inst;
66 
67 	/* Initialize instance by refreshing it. */
68 
69 	err = libscf_fmri_get_instance(h, v->gv_name, &inst);
70 	switch (err) {
71 	case 0:
72 		break;
73 
74 	case ECONNABORTED:
75 		return (ECONNABORTED);
76 
77 	case ENOENT:
78 		return (0);
79 
80 	case EINVAL:
81 	case ENOTSUP:
82 	default:
83 		bad_error("libscf_fmri_get_instance", err);
84 	}
85 
86 	err = refresh_vertex(v, inst);
87 	if (err == 0)
88 		graph_enable_by_vertex(v, v->gv_flags & GV_ENABLED, 0);
89 
90 	scf_instance_destroy(inst);
91 
92 	/* If the service was running, propagate a stop event. */
93 	if (gt_running(old_state)) {
94 		log_framework(LOG_DEBUG, "Propagating stop of %s.\n",
95 		    v->gv_name);
96 
97 		graph_transition_propagate(v, PROPAGATE_STOP, rerr);
98 	}
99 
100 	graph_transition_sulogin(RESTARTER_STATE_UNINIT, old_state);
101 	return (0);
102 }
103 
104 /* ARGSUSED */
105 static int
106 gt_enter_maint(scf_handle_t *h, graph_vertex_t *v,
107     restarter_instance_state_t old_state, restarter_error_t rerr)
108 {
109 	/*
110 	 * If the service was running, propagate a stop event.  If the
111 	 * service was not running the maintenance transition may satisfy
112 	 * optional dependencies and should be propagated to determine
113 	 * whether new dependents are satisfiable.
114 	 */
115 	if (gt_running(old_state)) {
116 		log_framework(LOG_DEBUG, "Propagating maintenance (stop) of "
117 		    "%s.\n", v->gv_name);
118 
119 		graph_transition_propagate(v, PROPAGATE_STOP, rerr);
120 	} else {
121 		log_framework(LOG_DEBUG, "Propagating maintenance of %s.\n",
122 		    v->gv_name);
123 
124 		graph_transition_propagate(v, PROPAGATE_SAT, rerr);
125 	}
126 
127 	graph_transition_sulogin(RESTARTER_STATE_MAINT, old_state);
128 	return (0);
129 }
130 
131 /* ARGSUSED */
132 static int
133 gt_enter_offline(scf_handle_t *h, graph_vertex_t *v,
134     restarter_instance_state_t old_state, restarter_error_t rerr)
135 {
136 	/*
137 	 * If the instance should be enabled, see if we can start it.
138 	 * Otherwise send a disable command.
139 	 */
140 	if (v->gv_flags & GV_ENABLED) {
141 		graph_start_if_satisfied(v);
142 	} else {
143 		if (gt_running(old_state) && v->gv_post_disable_f)
144 			v->gv_post_disable_f();
145 		vertex_send_event(v, RESTARTER_EVENT_TYPE_DISABLE);
146 	}
147 
148 	/*
149 	 * If the service was running, propagate a stop event.  If the
150 	 * service was not running the offline transition may satisfy
151 	 * optional dependencies and should be propagated to determine
152 	 * whether new dependents are satisfiable.
153 	 */
154 	if (gt_running(old_state)) {
155 		log_framework(LOG_DEBUG, "Propagating stop of %s.\n",
156 		    v->gv_name);
157 
158 		graph_transition_propagate(v, PROPAGATE_STOP, rerr);
159 	} else {
160 		log_framework(LOG_DEBUG, "Propagating offline of %s.\n",
161 		    v->gv_name);
162 
163 		graph_transition_propagate(v, PROPAGATE_SAT, rerr);
164 	}
165 
166 	graph_transition_sulogin(RESTARTER_STATE_OFFLINE, old_state);
167 	return (0);
168 }
169 
170 /* ARGSUSED */
171 static int
172 gt_enter_disabled(scf_handle_t *h, graph_vertex_t *v,
173     restarter_instance_state_t old_state, restarter_error_t rerr)
174 {
175 	/*
176 	 * If the instance should be disabled, no problem.  Otherwise,
177 	 * send an enable command, which should result in the instance
178 	 * moving to OFFLINE.
179 	 */
180 	if (v->gv_flags & GV_ENABLED) {
181 		vertex_send_event(v, RESTARTER_EVENT_TYPE_ENABLE);
182 	} else if (gt_running(old_state) && v->gv_post_disable_f) {
183 		v->gv_post_disable_f();
184 	}
185 
186 	/*
187 	 * If the service was running, propagate this as a stop.  If the
188 	 * service was not running the disabled transition may satisfy
189 	 * optional dependencies and should be propagated to determine
190 	 * whether new dependents are satisfiable.
191 	 */
192 	if (gt_running(old_state)) {
193 		log_framework(LOG_DEBUG, "Propagating stop of %s.\n",
194 		    v->gv_name);
195 
196 		graph_transition_propagate(v, PROPAGATE_STOP, rerr);
197 
198 	} else {
199 		log_framework(LOG_DEBUG, "Propagating disable of %s.\n",
200 		    v->gv_name);
201 
202 		graph_transition_propagate(v, PROPAGATE_SAT, rerr);
203 	}
204 
205 	graph_transition_sulogin(RESTARTER_STATE_DISABLED, old_state);
206 	return (0);
207 }
208 
209 static int
210 gt_internal_online_or_degraded(scf_handle_t *h, graph_vertex_t *v,
211     restarter_instance_state_t old_state, restarter_error_t rerr)
212 {
213 	int r;
214 
215 	/*
216 	 * If the instance has just come up, update the start
217 	 * snapshot.
218 	 */
219 	if (gt_running(old_state) == 0) {
220 		/*
221 		 * Don't fire if we're just recovering state
222 		 * after a restart.
223 		 */
224 		if (old_state != RESTARTER_STATE_UNINIT &&
225 		    v->gv_post_online_f)
226 			v->gv_post_online_f();
227 
228 		r = libscf_snapshots_poststart(h, v->gv_name, B_TRUE);
229 		switch (r) {
230 		case 0:
231 		case ENOENT:
232 			/*
233 			 * If ENOENT, the instance must have been
234 			 * deleted.  Pretend we were successful since
235 			 * we should get a delete event later.
236 			 */
237 			break;
238 
239 		case ECONNABORTED:
240 			return (ECONNABORTED);
241 
242 		case EACCES:
243 		case ENOTSUP:
244 		default:
245 			bad_error("libscf_snapshots_poststart", r);
246 		}
247 	}
248 	if (!(v->gv_flags & GV_ENABLED))
249 		vertex_send_event(v, RESTARTER_EVENT_TYPE_DISABLE);
250 
251 	if (gt_running(old_state) == 0) {
252 		log_framework(LOG_DEBUG, "Propagating start of %s.\n",
253 		    v->gv_name);
254 
255 		graph_transition_propagate(v, PROPAGATE_START, rerr);
256 	} else if (rerr == RERR_REFRESH) {
257 		/* For refresh we'll get a message sans state change */
258 
259 		log_framework(LOG_DEBUG, "Propagating refresh of %s.\n",
260 		    v->gv_name);
261 
262 		graph_transition_propagate(v, PROPAGATE_STOP, rerr);
263 	}
264 
265 	return (0);
266 }
267 
268 static int
269 gt_enter_online(scf_handle_t *h, graph_vertex_t *v,
270     restarter_instance_state_t old_state, restarter_error_t rerr)
271 {
272 	int r;
273 
274 	r = gt_internal_online_or_degraded(h, v, old_state, rerr);
275 	if (r != 0)
276 		return (r);
277 
278 	graph_transition_sulogin(RESTARTER_STATE_ONLINE, old_state);
279 	return (0);
280 }
281 
282 static int
283 gt_enter_degraded(scf_handle_t *h, graph_vertex_t *v,
284     restarter_instance_state_t old_state, restarter_error_t rerr)
285 {
286 	int r;
287 
288 	r = gt_internal_online_or_degraded(h, v, old_state, rerr);
289 	if (r != 0)
290 		return (r);
291 
292 	graph_transition_sulogin(RESTARTER_STATE_DEGRADED, old_state);
293 	return (0);
294 }
295 
296 /*
297  * gt_transition() implements the state transition for the graph
298  * state machine.  It can return:
299  *    0              success
300  *    ECONNABORTED   repository connection aborted
301  *
302  * v->gv_state should be set to the state we're transitioning to before
303  * calling this function.
304  */
305 int
306 gt_transition(scf_handle_t *h, graph_vertex_t *v, restarter_error_t rerr,
307     restarter_instance_state_t old_state)
308 {
309 	int err;
310 	int lost_repository = 0;
311 
312 	/*
313 	 * If there's a common set of work to be done on exit from the
314 	 * old_state, include it as a separate set of functions here.  For
315 	 * now there's no such work, so there are no gt_exit functions.
316 	 */
317 
318 	err = vertex_subgraph_dependencies_shutdown(h, v, old_state);
319 	switch (err) {
320 	case 0:
321 		break;
322 
323 	case ECONNABORTED:
324 		lost_repository = 1;
325 		break;
326 
327 	default:
328 		bad_error("vertex_subgraph_dependencies_shutdown", err);
329 	}
330 
331 	/*
332 	 * Now call the appropriate gt_enter function for the new state.
333 	 */
334 	switch (v->gv_state) {
335 	case RESTARTER_STATE_UNINIT:
336 		err = gt_enter_uninit(h, v, old_state, rerr);
337 		break;
338 
339 	case RESTARTER_STATE_DISABLED:
340 		err = gt_enter_disabled(h, v, old_state, rerr);
341 		break;
342 
343 	case RESTARTER_STATE_OFFLINE:
344 		err = gt_enter_offline(h, v, old_state, rerr);
345 		break;
346 
347 	case RESTARTER_STATE_ONLINE:
348 		err = gt_enter_online(h, v, old_state, rerr);
349 		break;
350 
351 	case RESTARTER_STATE_DEGRADED:
352 		err = gt_enter_degraded(h, v, old_state, rerr);
353 		break;
354 
355 	case RESTARTER_STATE_MAINT:
356 		err = gt_enter_maint(h, v, old_state, rerr);
357 		break;
358 
359 	default:
360 		/* Shouldn't be in an invalid state. */
361 #ifndef NDEBUG
362 		uu_warn("%s:%d: Invalid state %d.\n", __FILE__, __LINE__,
363 		    v->gv_state);
364 #endif
365 		abort();
366 	}
367 
368 	switch (err) {
369 	case 0:
370 		break;
371 
372 	case ECONNABORTED:
373 		lost_repository = 1;
374 		break;
375 
376 	default:
377 #ifndef NDEBUG
378 		uu_warn("%s:%d: "
379 		    "gt_enter_%s() failed with unexpected error %d.\n",
380 		    __FILE__, __LINE__, instance_state_str[v->gv_state], err);
381 #endif
382 		abort();
383 	}
384 
385 	return (lost_repository ? ECONNABORTED : 0);
386 }
387