#include "pamc_private.h"

/*
 * Copyright (c) 1998 Andrew G. Morgan <morgan@linux.kernel.org>
 * All rights reserved.
 *
 * The license for this source code should accompany this file.  Its
 * md5 checksum is: cac41ac118b52c96cf248baec7237381
 */

/*
 * These functions terminate the loaded agents
 */

static int pamc_close_current_agent(pamc_handle_t pch)
{
    int retval = PAMC_CONTROL_FAIL, i;
    unsigned char goodbye[8];
    struct pamc_agent_struct *current_agent;

    D(("called"));
    if (pch == NULL || (current_agent = pch->current) == NULL) {
	D(("failed due to no current agent [%p]", pch));
	return retval;
    }

    /* signal agent to die */
    pamc_write__u32(goodbye, 4);
    pamc_write__u32(goodbye+4, PAMC_CONTROL_EXIT);
    while (write(current_agent->to_agent, goodbye, sizeof(goodbye)) == -1) {
	if (errno != EINTR) {
	    D(("failed to signal abort"));
	    break;
	}
    }

    /* scrub agent_id */
    pamc_scrub((void **) (&current_agent->id), strlen(current_agent->id));

    /* zip up stack */
    if (current_agent->previous) {
	current_agent->previous->next = current_agent->next;
    }
    if (current_agent->next) {
	current_agent->next->previous = current_agent->previous;
    }
    current_agent->next = current_agent->previous = NULL;

    /* close pipes to agent */
    close(current_agent->from_agent);
    current_agent->from_agent = -1;
    close(current_agent->to_agent);
    current_agent->to_agent = -1;

    /* wait up to 60 seconds for agent to exit */
    for (i=0; i<600; ++i) {
	pid_t pid;
	int status;
	struct timeval timeout;

	/* sleep a little */
	timeout.tv_sec = 0;
	timeout.tv_usec = 100000;
	select(0, NULL, NULL, NULL, &timeout);

	/* wait for the child to exit */
	while ((pid = waitpid(current_agent->pid, &status, WNOHANG))) {
	    if (pid == (pid_t) -1) {
		if (errno == EINTR) {
		    D(("got a signal"));
		    continue;
		}
		D(("error in waitpid() call: %s", strerror(errno)));
	    } else if (WIFEXITED(status) && !WEXITSTATUS(status)) {
		D(("agent is happy"));
		retval = PAMC_CONTROL_OK;
	    }
	    break;
	}

	/* has the child gone? */
	if (pid) {
	    D(("agent %d has exited (%d)", current_agent->pid, pid));
	    break;
	}
    }
    current_agent->pid = 0;

    /* liberate agent structure */
    free(current_agent);
    pch->current = current_agent = NULL;

    D(("done"));
    return retval;
}

/*
 * terminate all agents.
 */

int pamc_end(pamc_handle_t *pch_p)
{
    int status=PAMC_CONTROL_OK;        /* client only fails for BUSY agents */

    D(("called: XXX - we need to check if any agents are busy!"));
    if (pch_p == NULL || *pch_p == NULL) {
	D(("failed to find &handle [%p]", pch_p));
	return PAMC_CONTROL_FAIL;
    }

    /* spin through agents */
    while (((*pch_p)->current=(*pch_p)->stack) != NULL) {
	(*pch_p)->stack = (*pch_p)->stack->next;
	if ((*pch_p)->current->status == PAMC_CONTROL_BUSY) {
	    D(("agent [%s] is in BUSY state", (*pch_p)->current->id));
	}
	/* close the agent */
	if (pamc_close_current_agent(*pch_p) != status
	    && status == PAMC_CONTROL_OK) {
	    D(("agent was not happy about being told to exit"));
	    status = PAMC_CONTROL_FAIL;
	}
    }

    /* liberate path */
    pamc_scrub((void **) &((*pch_p)->path), strlen((*pch_p)->path));

    /* liberate pch_p too */
    pamc_scrub((void **) pch_p, sizeof(**pch_p));
    pch_p = NULL;

    /* all done */
    D(("done [%d]", status));
    return status;
}

