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 #include "states.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_restart = 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 * jump to SELECTING and start from there -- but not until 221 * after we've finished the asynchronous command! 222 */ 223 224 do_restart = 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_restart) 237 dhcp_selecting(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