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