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