xref: /illumos-gate/usr/src/cmd/svc/startd/transition.c (revision 1a220b56b93ff1dc80855691548503117af4cc10)
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 2006 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 needs 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 	vertex_subgraph_dependencies_shutdown(h, v, gt_running(old_state));
68 
69 	/* Initialize instance by refreshing it. */
70 
71 	err = libscf_fmri_get_instance(h, v->gv_name, &inst);
72 	switch (err) {
73 	case 0:
74 		break;
75 
76 	case ECONNABORTED:
77 		return (ECONNABORTED);
78 
79 	case ENOENT:
80 		return (0);
81 
82 	case EINVAL:
83 	case ENOTSUP:
84 	default:
85 		bad_error("libscf_fmri_get_instance", err);
86 	}
87 
88 	err = refresh_vertex(v, inst);
89 	if (err == 0)
90 		graph_enable_by_vertex(v, v->gv_flags & GV_ENABLED, 0);
91 
92 	scf_instance_destroy(inst);
93 
94 	/* If the service was running, propagate a stop event. */
95 	if (gt_running(old_state)) {
96 		log_framework(LOG_DEBUG, "Propagating stop of %s.\n",
97 		    v->gv_name);
98 
99 		graph_transition_propagate(v, RESTARTER_EVENT_TYPE_STOP, rerr);
100 	}
101 
102 	graph_transition_sulogin(RESTARTER_STATE_UNINIT, old_state);
103 	return (0);
104 }
105 
106 static int
107 gt_enter_maint(scf_handle_t *h, graph_vertex_t *v,
108     restarter_instance_state_t old_state, restarter_error_t rerr)
109 {
110 	vertex_subgraph_dependencies_shutdown(h, v, gt_running(old_state));
111 
112 	if (gt_running(old_state)) {
113 		log_framework(LOG_DEBUG, "Propagating stop of %s.\n",
114 		    v->gv_name);
115 
116 		graph_transition_propagate(v, RESTARTER_EVENT_TYPE_STOP, rerr);
117 	} else if (v->gv_state == RESTARTER_STATE_MAINT) {
118 		log_framework(LOG_DEBUG, "Propagating maintenance of %s.\n",
119 		    v->gv_name);
120 
121 		graph_transition_propagate(v, RESTARTER_EVENT_TYPE_START, rerr);
122 	}
123 
124 	graph_transition_sulogin(RESTARTER_STATE_MAINT, old_state);
125 	return (0);
126 }
127 
128 static int
129 gt_enter_offline(scf_handle_t *h, graph_vertex_t *v,
130     restarter_instance_state_t old_state, restarter_error_t rerr)
131 {
132 	vertex_subgraph_dependencies_shutdown(h, v, gt_running(old_state));
133 
134 	/*
135 	 * If the instance should be enabled, see if we can start it.
136 	 * Otherwise send a disable command.
137 	 */
138 	if (v->gv_flags & GV_ENABLED) {
139 		graph_start_if_satisfied(v);
140 	} else {
141 		if (gt_running(old_state) && v->gv_post_disable_f)
142 			v->gv_post_disable_f();
143 		vertex_send_event(v, RESTARTER_EVENT_TYPE_DISABLE);
144 	}
145 
146 	if (gt_running(old_state)) {
147 		log_framework(LOG_DEBUG, "Propagating stop of %s.\n",
148 		    v->gv_name);
149 
150 		graph_transition_propagate(v, RESTARTER_EVENT_TYPE_STOP, rerr);
151 	}
152 
153 	graph_transition_sulogin(RESTARTER_STATE_OFFLINE, old_state);
154 	return (0);
155 }
156 
157 static int
158 gt_enter_disabled(scf_handle_t *h, graph_vertex_t *v,
159     restarter_instance_state_t old_state, restarter_error_t rerr)
160 {
161 	vertex_subgraph_dependencies_shutdown(h, v, gt_running(old_state));
162 
163 	/*
164 	 * If the instance should be disabled, no problem.  Otherwise,
165 	 * send an enable command, which should result in the instance
166 	 * moving to OFFLINE.
167 	 */
168 	if (v->gv_flags & GV_ENABLED) {
169 		vertex_send_event(v, RESTARTER_EVENT_TYPE_ENABLE);
170 	} else if (gt_running(old_state) && v->gv_post_disable_f) {
171 		v->gv_post_disable_f();
172 	}
173 
174 	/*
175 	 * If the service was running, propagate this as a stop.
176 	 * Otherwise, we treat other transitions as a start propagate,
177 	 * since they can satisfy optional_all dependencies.
178 	 */
179 	if (gt_running(old_state)) {
180 		log_framework(LOG_DEBUG, "Propagating stop of %s.\n",
181 		    v->gv_name);
182 
183 		graph_transition_propagate(v, RESTARTER_EVENT_TYPE_STOP, rerr);
184 
185 	} else if (v->gv_state == RESTARTER_STATE_DISABLED) {
186 		log_framework(LOG_DEBUG, "Propagating disable of %s.\n",
187 		    v->gv_name);
188 
189 		graph_transition_propagate(v, RESTARTER_EVENT_TYPE_START, rerr);
190 	}
191 
192 	graph_transition_sulogin(RESTARTER_STATE_DISABLED, old_state);
193 	return (0);
194 }
195 
196 static int
197 gt_internal_online_or_degraded(scf_handle_t *h, graph_vertex_t *v,
198     restarter_instance_state_t old_state, restarter_error_t rerr)
199 {
200 	int r;
201 
202 	/*
203 	 * If the instance has just come up, update the start
204 	 * snapshot.
205 	 */
206 	if (gt_running(old_state) == 0) {
207 		/*
208 		 * Don't fire if we're just recovering state
209 		 * after a restart.
210 		 */
211 		if (old_state != RESTARTER_STATE_UNINIT &&
212 		    v->gv_post_online_f)
213 			v->gv_post_online_f();
214 
215 		r = libscf_snapshots_poststart(h, v->gv_name, B_TRUE);
216 		switch (r) {
217 		case 0:
218 		case ENOENT:
219 			/*
220 			 * If ENOENT, the instance must have been
221 			 * deleted.  Pretend we were successful since
222 			 * we should get a delete event later.
223 			 */
224 			break;
225 
226 		case ECONNABORTED:
227 			return (ECONNABORTED);
228 
229 		case EACCES:
230 		case ENOTSUP:
231 		default:
232 			bad_error("libscf_snapshots_poststart", r);
233 		}
234 	}
235 	if (!(v->gv_flags & GV_ENABLED))
236 		vertex_send_event(v, RESTARTER_EVENT_TYPE_DISABLE);
237 
238 	if (gt_running(old_state) == 0) {
239 		log_framework(LOG_DEBUG, "Propagating start of %s.\n",
240 		    v->gv_name);
241 
242 		graph_transition_propagate(v,
243 		    RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON, rerr);
244 	} else if (rerr == RERR_REFRESH) {
245 		/* For refresh we'll get a message sans state change */
246 
247 		log_framework(LOG_DEBUG, "Propagating refresh of %s.\n",
248 		    v->gv_name);
249 
250 		graph_transition_propagate(v, RESTARTER_EVENT_TYPE_STOP, rerr);
251 	}
252 
253 	return (0);
254 }
255 
256 static int
257 gt_enter_online(scf_handle_t *h, graph_vertex_t *v,
258     restarter_instance_state_t old_state, restarter_error_t rerr)
259 {
260 	int r;
261 
262 	r = gt_internal_online_or_degraded(h, v, old_state, rerr);
263 	if (r != 0)
264 		return (r);
265 
266 	graph_transition_sulogin(RESTARTER_STATE_ONLINE, old_state);
267 	return (0);
268 }
269 
270 static int
271 gt_enter_degraded(scf_handle_t *h, graph_vertex_t *v,
272     restarter_instance_state_t old_state, restarter_error_t rerr)
273 {
274 	int r;
275 
276 	r = gt_internal_online_or_degraded(h, v, old_state, rerr);
277 	if (r != 0)
278 		return (r);
279 
280 	graph_transition_sulogin(RESTARTER_STATE_DEGRADED, old_state);
281 	return (0);
282 }
283 
284 /*
285  * gt_transition() implements the state transition for the graph
286  * state machine.  It can return:
287  *    0              success
288  *    ECONNABORTED   repository connection aborted
289  */
290 int
291 gt_transition(scf_handle_t *h, graph_vertex_t *v, restarter_error_t rerr,
292     restarter_instance_state_t old_state, restarter_instance_state_t new_state)
293 {
294 	int err = 0;
295 
296 	/*
297 	 * If there's a common set of work to be done on exit from the
298 	 * old_state, include it as a separate set of functions here.  For
299 	 * now there's no such work, so there are no gt_exit functions.
300 	 */
301 
302 	/*
303 	 * Now call the appropriate gt_enter function for the new state.
304 	 */
305 	switch (new_state) {
306 	case RESTARTER_STATE_UNINIT:
307 		err = gt_enter_uninit(h, v, old_state, rerr);
308 		break;
309 
310 	case RESTARTER_STATE_DISABLED:
311 		err = gt_enter_disabled(h, v, old_state, rerr);
312 		break;
313 
314 	case RESTARTER_STATE_OFFLINE:
315 		err = gt_enter_offline(h, v, old_state, rerr);
316 		break;
317 
318 	case RESTARTER_STATE_ONLINE:
319 		err = gt_enter_online(h, v, old_state, rerr);
320 		break;
321 
322 	case RESTARTER_STATE_DEGRADED:
323 		err = gt_enter_degraded(h, v, old_state, rerr);
324 		break;
325 
326 	case RESTARTER_STATE_MAINT:
327 		err = gt_enter_maint(h, v, old_state, rerr);
328 		break;
329 
330 	default:
331 		/* Shouldn't have been passed an invalid state. */
332 #ifndef NDEBUG
333 		uu_warn("%s:%d: Uncaught case %d.\n", __FILE__, __LINE__,
334 		    new_state);
335 #endif
336 		abort();
337 	}
338 
339 	return (err);
340 }
341