xref: /titanic_50/usr/src/cmd/cmd-inet/sbin/dhcpagent/async.c (revision c2934490a71fd1db5f18fa4b6f290718ef053845)
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 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <dhcpmsg.h>
31 #include <libinetutil.h>
32 
33 #include "async.h"
34 #include "util.h"
35 #include "agent.h"
36 #include "interface.h"
37 #include "script_handler.h"
38 
39 static void	async_timeout(iu_tq_t *, void *);
40 
41 /*
42  * async_pending(): checks to see if an async command is pending.  if a stale
43  *		    async command is found, cancellation is attempted.
44  *
45  *   input: struct ifslist *: the interface to check for an async command on
46  *  output: boolean_t: B_TRUE if async command is pending, B_FALSE if not
47  */
48 
49 boolean_t
50 async_pending(struct ifslist *ifsp)
51 {
52 	if (!(ifsp->if_dflags & DHCP_IF_BUSY))
53 		return (B_FALSE);
54 
55 	/*
56 	 * if the command was not started by the user (i.e., was
57 	 * started internal to the agent), then it will timeout in
58 	 * async_timeout() -- don't shoot it here.
59 	 */
60 
61 	if (!ifsp->if_async.as_user)
62 		return (B_TRUE);
63 
64 	if (ifsp->if_script_pid != -1)
65 		return (B_TRUE);
66 
67 	/*
68 	 * user command -- see if they went away.  if they went away,
69 	 * either a timeout was already sent to them or they
70 	 * control-c'd out.
71 	 */
72 
73 	if (ipc_action_pending(ifsp))
74 		return (B_TRUE);
75 
76 	/*
77 	 * it appears they went away.  try to cancel their pending
78 	 * command.  if we can't cancel it, we leave their command
79 	 * pending and it's just gonna have to complete its business
80 	 * in any case, cancel the ipc_action timer, since we know
81 	 * they've gone away.
82 	 */
83 
84 	dhcpmsg(MSG_DEBUG, "async_pending: async command left, attempting "
85 	    "cancellation");
86 
87 	ipc_action_cancel_timer(ifsp);
88 	return (async_cancel(ifsp) ? B_FALSE : B_TRUE);
89 }
90 
91 /*
92  * async_start(): starts an asynchronous command on an interface
93  *
94  *   input: struct ifslist *: the interface to start the async command on
95  *	    dhcp_ipc_type_t: the command to start
96  *	    boolean_t: B_TRUE if the command was started by a user
97  *  output: int: 1 on success, 0 on failure
98  */
99 
100 int
101 async_start(struct ifslist *ifsp, dhcp_ipc_type_t cmd, boolean_t user)
102 {
103 	iu_timer_id_t tid;
104 
105 	if (async_pending(ifsp))
106 		return (0);
107 
108 	tid = iu_schedule_timer(tq, DHCP_ASYNC_WAIT, async_timeout, ifsp);
109 	if (tid == -1)
110 		return (0);
111 
112 	hold_ifs(ifsp);
113 
114 	ifsp->if_async.as_tid	 = tid;
115 	ifsp->if_async.as_cmd	 = cmd;
116 	ifsp->if_async.as_user	 = user;
117 	ifsp->if_dflags		|= DHCP_IF_BUSY;
118 
119 	return (1);
120 }
121 
122 
123 /*
124  * async_finish(): completes an asynchronous command
125  *
126  *   input: struct ifslist *: the interface with the pending async command
127  *  output: void
128  *    note: should only be used when the command has no residual state to
129  *	    clean up
130  */
131 
132 void
133 async_finish(struct ifslist *ifsp)
134 {
135 	/*
136 	 * be defensive here. the script may still be running if
137 	 * the asynchronous action times out before it is killed by the
138 	 * script helper process.
139 	 */
140 
141 	if (ifsp->if_script_pid != -1)
142 		script_stop(ifsp);
143 
144 	/*
145 	 * in case async_timeout() has already called async_cancel(),
146 	 * and to be idempotent, check the DHCP_IF_BUSY flag
147 	 */
148 
149 	if (!(ifsp->if_dflags & DHCP_IF_BUSY))
150 		return;
151 
152 	if (ifsp->if_async.as_tid == -1) {
153 		ifsp->if_dflags &= ~DHCP_IF_BUSY;
154 		return;
155 	}
156 
157 	if (iu_cancel_timer(tq, ifsp->if_async.as_tid, NULL) == 1) {
158 		ifsp->if_dflags &= ~DHCP_IF_BUSY;
159 		ifsp->if_async.as_tid = -1;
160 		(void) release_ifs(ifsp);
161 		return;
162 	}
163 
164 	/*
165 	 * if we can't cancel this timer, we'll just leave the
166 	 * interface busy and when the timeout finally fires, we'll
167 	 * mark it free, which will just cause a minor nuisance.
168 	 */
169 
170 	dhcpmsg(MSG_WARNING, "async_finish: cannot cancel async timer");
171 }
172 
173 /*
174  * async_cancel(): cancels a pending asynchronous command
175  *
176  *   input: struct ifslist *: the interface with the pending async command
177  *  output: int: 1 if cancellation was successful, 0 on failure
178  */
179 
180 int
181 async_cancel(struct ifslist *ifsp)
182 {
183 	boolean_t do_restart = B_FALSE;
184 
185 	/*
186 	 * we decide how to cancel the command depending on our
187 	 * current state, since commands such as EXTEND may in fact
188 	 * cause us to enter back into SELECTING (if a NAK results
189 	 * from the EXTEND).
190 	 */
191 
192 	switch (ifsp->if_state) {
193 
194 	case BOUND:
195 	case INFORMATION:
196 		break;
197 
198 	case RENEWING:					/* FALLTHRU */
199 	case REBINDING:					/* FALLTHRU */
200 	case INFORM_SENT:
201 
202 		/*
203 		 * these states imply that we've sent a packet and we're
204 		 * awaiting an ACK or NAK.  just cancel the wait.
205 		 */
206 
207 		if (unregister_acknak(ifsp) == 0)
208 			return (0);
209 
210 		break;
211 
212 	case INIT:					/* FALLTHRU */
213 	case SELECTING:					/* FALLTHRU */
214 	case REQUESTING:				/* FALLTHRU */
215 	case INIT_REBOOT:
216 
217 		/*
218 		 * these states imply we're still trying to get a lease.
219 		 * jump to SELECTING and start from there -- but not until
220 		 * after we've finished the asynchronous command!
221 		 */
222 
223 		do_restart = B_TRUE;
224 		break;
225 
226 	default:
227 		dhcpmsg(MSG_WARNING, "async_cancel: cancellation in unexpected "
228 		    "state %d", ifsp->if_state);
229 		return (0);
230 	}
231 
232 	async_finish(ifsp);
233 	dhcpmsg(MSG_DEBUG, "async_cancel: asynchronous command (%d) aborted",
234 	    ifsp->if_async.as_cmd);
235 	if (do_restart)
236 		dhcp_selecting(ifsp);
237 
238 	return (1);
239 }
240 
241 /*
242  * async_timeout(): expires stale asynchronous commands
243  *
244  *   input: iu_tq_t *: the timer queue on which the timeout went off
245  *	    void *: the interface with the pending async command
246  *  output: void
247  */
248 
249 static void
250 async_timeout(iu_tq_t *tq, void *arg)
251 {
252 	struct ifslist		*ifsp = (struct ifslist *)arg;
253 
254 	if (check_ifs(ifsp) == 0) {
255 		(void) release_ifs(ifsp);
256 		return;
257 	}
258 
259 	/* we've expired now */
260 	ifsp->if_async.as_tid = -1;
261 
262 	/*
263 	 * if the command was generated internally to the agent, try
264 	 * to cancel it immediately.  otherwise, if the user has gone
265 	 * away, we cancel it in async_pending().  otherwise, we let
266 	 * it live.
267 	 */
268 
269 	if (!ifsp->if_async.as_user) {
270 		(void) async_cancel(ifsp);
271 		return;
272 	}
273 
274 	if (async_pending(ifsp)) {
275 
276 		ifsp->if_async.as_tid = iu_schedule_timer(tq, DHCP_ASYNC_WAIT,
277 		    async_timeout, ifsp);
278 
279 		if (ifsp->if_async.as_tid != -1) {
280 			hold_ifs(ifsp);
281 			dhcpmsg(MSG_DEBUG, "async_timeout: asynchronous "
282 			    "command %d still pending", ifsp->if_async.as_cmd);
283 			return;
284 		}
285 
286 		/*
287 		 * what can we do but cancel it?  we can't get called
288 		 * back again and otherwise we'll end up in the
289 		 * twilight zone with the interface permanently busy
290 		 */
291 
292 		ipc_action_finish(ifsp, DHCP_IPC_E_INT);
293 		(void) async_cancel(ifsp);
294 	}
295 }
296