async.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/types.h>
#include <sys/stat.h>
#include <dhcpmsg.h>
#include <libinetutil.h>
#include "async.h"
#include "util.h"
#include "agent.h"
#include "interface.h"
#include "script_handler.h"
static void async_timeout(iu_tq_t *, void *);
/*
* async_pending(): checks to see if an async command is pending. if a stale
* async command is found, cancellation is attempted.
*
* input: struct ifslist *: the interface to check for an async command on
* output: boolean_t: B_TRUE if async command is pending, B_FALSE if not
*/
boolean_t
async_pending(struct ifslist *ifsp)
{
if (!(ifsp->if_dflags & DHCP_IF_BUSY))
return (B_FALSE);
/*
* if the command was not started by the user (i.e., was
* started internal to the agent), then it will timeout in
* async_timeout() -- don't shoot it here.
*/
if (!ifsp->if_async.as_user)
return (B_TRUE);
if (ifsp->if_script_pid != -1)
return (B_TRUE);
/*
* user command -- see if they went away. if they went away,
* either a timeout was already sent to them or they
* control-c'd out.
*/
if (ipc_action_pending(ifsp))
return (B_TRUE);
/*
* it appears they went away. try to cancel their pending
* command. if we can't cancel it, we leave their command
* pending and it's just gonna have to complete its business
* in any case, cancel the ipc_action timer, since we know
* they've gone away.
*/
dhcpmsg(MSG_DEBUG, "async_pending: async command left, attempting "
"cancellation");
ipc_action_cancel_timer(ifsp);
return (async_cancel(ifsp) ? B_FALSE : B_TRUE);
}
/*
* async_start(): starts an asynchronous command on an interface
*
* input: struct ifslist *: the interface to start the async command on
* dhcp_ipc_type_t: the command to start
* boolean_t: B_TRUE if the command was started by a user
* output: int: 1 on success, 0 on failure
*/
int
async_start(struct ifslist *ifsp, dhcp_ipc_type_t cmd, boolean_t user)
{
iu_timer_id_t tid;
if (async_pending(ifsp))
return (0);
tid = iu_schedule_timer(tq, DHCP_ASYNC_WAIT, async_timeout, ifsp);
if (tid == -1)
return (0);
hold_ifs(ifsp);
ifsp->if_async.as_tid = tid;
ifsp->if_async.as_cmd = cmd;
ifsp->if_async.as_user = user;
ifsp->if_dflags |= DHCP_IF_BUSY;
return (1);
}
/*
* async_finish(): completes an asynchronous command
*
* input: struct ifslist *: the interface with the pending async command
* output: void
* note: should only be used when the command has no residual state to
* clean up
*/
void
async_finish(struct ifslist *ifsp)
{
/*
* be defensive here. the script may still be running if
* the asynchronous action times out before it is killed by the
* script helper process.
*/
if (ifsp->if_script_pid != -1)
script_stop(ifsp);
/*
* in case async_timeout() has already called async_cancel(),
* and to be idempotent, check the DHCP_IF_BUSY flag
*/
if (!(ifsp->if_dflags & DHCP_IF_BUSY))
return;
if (ifsp->if_async.as_tid == -1) {
ifsp->if_dflags &= ~DHCP_IF_BUSY;
return;
}
if (iu_cancel_timer(tq, ifsp->if_async.as_tid, NULL) == 1) {
ifsp->if_dflags &= ~DHCP_IF_BUSY;
ifsp->if_async.as_tid = -1;
(void) release_ifs(ifsp);
return;
}
/*
* if we can't cancel this timer, we'll just leave the
* interface busy and when the timeout finally fires, we'll
* mark it free, which will just cause a minor nuisance.
*/
dhcpmsg(MSG_WARNING, "async_finish: cannot cancel async timer");
}
/*
* async_cancel(): cancels a pending asynchronous command
*
* input: struct ifslist *: the interface with the pending async command
* output: int: 1 if cancellation was successful, 0 on failure
*/
int
async_cancel(struct ifslist *ifsp)
{
boolean_t do_reset = B_FALSE;
/*
* we decide how to cancel the command depending on our
* current state, since commands such as EXTEND may in fact
* cause us to enter back into SELECTING (if a NAK results
* from the EXTEND).
*/
switch (ifsp->if_state) {
case BOUND:
case INFORMATION:
break;
case RENEWING: /* FALLTHRU */
case REBINDING: /* FALLTHRU */
case INFORM_SENT:
/*
* these states imply that we've sent a packet and we're
* awaiting an ACK or NAK. just cancel the wait.
*/
if (unregister_acknak(ifsp) == 0)
return (0);
break;
case INIT: /* FALLTHRU */
case SELECTING: /* FALLTHRU */
case REQUESTING: /* FALLTHRU */
case INIT_REBOOT:
/*
* these states imply we're still trying to get a lease.
* just return to a clean slate (INIT) -- but not until
* after we've finished the asynchronous command!
*/
do_reset = B_TRUE;
break;
default:
dhcpmsg(MSG_WARNING, "async_cancel: cancellation in unexpected "
"state %d", ifsp->if_state);
return (0);
}
async_finish(ifsp);
dhcpmsg(MSG_DEBUG, "async_cancel: asynchronous command (%d) aborted",
ifsp->if_async.as_cmd);
if (do_reset)
reset_ifs(ifsp);
return (1);
}
/*
* async_timeout(): expires stale asynchronous commands
*
* input: iu_tq_t *: the timer queue on which the timeout went off
* void *: the interface with the pending async command
* output: void
*/
static void
async_timeout(iu_tq_t *tq, void *arg)
{
struct ifslist *ifsp = (struct ifslist *)arg;
if (check_ifs(ifsp) == 0) {
(void) release_ifs(ifsp);
return;
}
/* we've expired now */
ifsp->if_async.as_tid = -1;
/*
* if the command was generated internally to the agent, try
* to cancel it immediately. otherwise, if the user has gone
* away, we cancel it in async_pending(). otherwise, we let
* it live.
*/
if (!ifsp->if_async.as_user) {
(void) async_cancel(ifsp);
return;
}
if (async_pending(ifsp)) {
ifsp->if_async.as_tid = iu_schedule_timer(tq, DHCP_ASYNC_WAIT,
async_timeout, ifsp);
if (ifsp->if_async.as_tid != -1) {
hold_ifs(ifsp);
dhcpmsg(MSG_DEBUG, "async_timeout: asynchronous "
"command %d still pending", ifsp->if_async.as_cmd);
return;
}
/*
* what can we do but cancel it? we can't get called
* back again and otherwise we'll end up in the
* twilight zone with the interface permanently busy
*/
ipc_action_finish(ifsp, DHCP_IPC_E_INT);
(void) async_cancel(ifsp);
}
}