/*
 * Copyright (c) 1999, 2000  Motoyuki Kasahara.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer,
 *    without modification, immediately at the beginning of the file.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/fcntl.h>

#include "pccard_low.h"

/*
 * Socket file name that pccardd binds.
 */
#define SERVER_SOCKET_NAME "/var/tmp/.pccardd"

/*
 * File name of my socket.
 */
#define CLIENT_SOCKET_NAME "/tmp/.pccard"

/*
 * Lock file name for my socket.
 */
#define LOCK_FILE_NAME "/tmp/.pccard.lock"

/*
 * Unexported function.
 */
static void pccard_parse_response(const char *, int *, char *, char *,
    char *, int *);

/*
 * File descriptor of my socket.
 */
static int socket_file = -1;

/*
 * File descriptor of my socket.
 */
static int lock_file = -1;

/*
 * Address of my socket.
 */
static struct sockaddr_un client_address;

/*
 * Address of pccardd's socket.
 */
static struct sockaddr_un server_address;

/*
 * Message handlers.
 */
extern void (*pccard_output_error_message)(const char *, ...);
extern void (*pccard_output_warning_message)(const char *, ...);
extern void (*pccard_output_debug_message)(const char *, ...);

/*
 * Debug flag.
 */
extern int pccard_debug_flag;

/*
 * Open a socket for talking to pccardd.
 * If an error occurs, -1 is returned.  Otherwise 0 is returned.
 */
int
pccard_open_socket(void)
{
    struct flock lock;
    struct stat st;

    if (socket_file >= 0)
	pccard_close_socket();

    /*
     * Open the lock file.
     */
    lock_file = open(LOCK_FILE_NAME, O_RDWR | O_CREAT, 0600);
    if (lock_file == -1) {
	pccard_output_error_message("open() failed, %s: %s", strerror(errno),
	    LOCK_FILE_NAME);
	pccard_close_socket();
	return -1;
    }

    if (pccard_debug_flag)
	pccard_output_debug_message("open the lock file: %s", LOCK_FILE_NAME);

    /*
     * Lock the file.
     */
    lock.l_type = F_WRLCK;
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    lock.l_len = 1;
    lock.l_pid = 0;
    if (fcntl(lock_file, F_SETLK, &lock) == -1) {
	pccard_output_error_message("fnctl(F_SETLK) failed, %s: %s", 
	    strerror(errno), LOCK_FILE_NAME);
	pccard_output_error_message("another client might be running");
	pccard_close_socket();
	return -1;
    }

    if (pccard_debug_flag)
	pccard_output_debug_message("lock the file: %s", LOCK_FILE_NAME);

    /*
     * Remove the old socket, if exists.
     */
    if (stat(CLIENT_SOCKET_NAME, &st) == 0) {
	if (unlink(CLIENT_SOCKET_NAME) == -1) {
	    pccard_output_error_message("unlink() failed, %s: %s",
		strerror(errno), CLIENT_SOCKET_NAME);
	    pccard_close_socket();
	    return -1;
	}
	if (pccard_debug_flag) {
	    pccard_output_debug_message("remove the old socket file: %s",
		CLIENT_SOCKET_NAME);
	}
    }

    /*
     * Create an UNIX domain socket for me (i.e. client).
     */
    socket_file = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (socket_file == -1) {
	pccard_output_error_message("socket() failed, %s", strerror(errno));
	pccard_close_socket();
	return -1;
    }

    /*
     * Set address for the socket.
     */
    memset(&client_address, 0, sizeof(client_address));
    client_address.sun_family = AF_UNIX;
    strcpy(client_address.sun_path, CLIENT_SOCKET_NAME);

    /*
     * Bind the address to the socket.
     */
    if (bind(socket_file, (struct sockaddr *)&client_address,
	SUN_LEN(&client_address)) == -1) {
	pccard_output_error_message("bind() failed, %s: %s", strerror(errno),
	    client_address.sun_path);
	pccard_close_socket();
	return -1;
    }

    /*
     * Set address of the pccardd's socket.
     */
    memset(&server_address, 0, sizeof(server_address));
    server_address.sun_family = AF_UNIX;
    strcpy(server_address.sun_path, SERVER_SOCKET_NAME);

    if (pccard_debug_flag) {
	pccard_output_debug_message("open the socket file: %s",
	    CLIENT_SOCKET_NAME);
    }

    return 0;
}


/*
 * Close the lock file and the socket used to talk to pccardd, and remove
 * them.  We ignore an error at removal of the socket file.
 */
void
pccard_close_socket(void)
{
    struct flock lock;
    int is_locked;
    pid_t pid;

    /*
     * Close the lock file.
     * Also remove the file if the process locks the file.
     */
    if (lock_file != -1) {
	pid = getpid();
	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 1;
	lock.l_pid = pid;
	if (fcntl(lock_file, F_GETLK, &lock) == 0 && lock.l_pid == pid)
	    is_locked = 1;
	else
	    is_locked = 0;

	close(lock_file);
	if (pccard_debug_flag) {
	    pccard_output_debug_message("close the lock file: %s",
		LOCK_FILE_NAME);
	}
	if (is_locked) {
	    if (unlink(LOCK_FILE_NAME) == -1) {
		pccard_output_warning_message("unlink() failed, %s: %s",
		    strerror(errno), LOCK_FILE_NAME);
	    } else if (pccard_debug_flag) {
		pccard_output_debug_message("remove the lock file: %s",
		    LOCK_FILE_NAME);
	    }
	}
	lock_file = -1;
    }

    /*
     * Close and remove the socket file.
     */
    if (socket_file != -1) {
	close(socket_file);
	if (pccard_debug_flag) {
	    pccard_output_debug_message("close the socket file: %s",
		CLIENT_SOCKET_NAME);
	}
	if (unlink(CLIENT_SOCKET_NAME) == -1) {
	    pccard_output_warning_message("unlink() failed, %s: %s",
		strerror(errno), CLIENT_SOCKET_NAME);
	} else if (pccard_debug_flag) {
	    pccard_output_debug_message("remove the socket file: %s",
		CLIENT_SOCKET_NAME);
	}
	socket_file = -1;
    }
}


/*
 * Send a request command to pccardd.
 * If an error occurs, -1 is returned.  Otherwise 0 is returned.
 */
int
pccard_send_request(const char *command)
{
    size_t command_length;
    int saved_errno;

    if (socket_file == -1) {
	pccard_output_error_message("socket file is not opened");
	return -1;
    }

    /*
     * Send a request.
     */
    command_length = strlen(command);
    errno = 0;
    if (sendto(socket_file, command, command_length, 0,
	(struct sockaddr *)&server_address, SUN_LEN(&server_address))
	!= command_length) {
	saved_errno = errno;
	pccard_output_error_message("sendto() failed, %s: %s",
	    strerror(errno), SERVER_SOCKET_NAME);
	errno = saved_errno;

	/*
	 * If no such file exist, or connection is refused, we guess
	 * that pccardd is not running.
	 */
	if (errno == ENOENT || errno == ECONNREFUSED)
	    pccard_output_error_message("pccardd doesn't seem to be running");
	return -1;
    }

    if (pccard_debug_flag)
	pccard_output_debug_message("send the sring to pccardd: %s", command);

    return 0;
}


/*
 * Wait until the socket is ready for reading.
 * If an error occurs, -1 is returned.  Otherwise 0 is returned.
 */
int
pccard_wait_response(void)
{
    fd_set fdset;

    if (socket_file == -1) {
	pccard_output_error_message("socket file is not opened");
	return -1;
    }

    for (;;) {
	FD_ZERO(&fdset);
	FD_SET(socket_file, &fdset);
	if (select(socket_file + 1, &fdset, NULL, NULL, NULL) != -1)
	    break;
	else if (errno != EINTR) {
	    pccard_output_error_message("select() failed, %s",
		strerror(errno));
	    return -1;
	}
    }

    return 0;
}


/*
 * Receive a response from pccardd.
 * If an error occurs, -1 is returned.
 * Otherwise the response message is set to `buffer' and 0 is returned.
 */
int
pccard_receive_response(char *buffer)
{
    int from_length;
    ssize_t receive_length;
    struct sockaddr_un from_address; 

    if (socket_file == -1) {
	pccard_output_error_message("socket file is not opened");
	return -1;
    }

    receive_length = recvfrom(socket_file, buffer, PCCARD_MAX_RESPONSE_LENGTH,
	0, (struct sockaddr *)&from_address, &from_length);
    if (receive_length == -1) {
	pccard_output_error_message("recvfrom() failed, %s", strerror(errno));
	return -1;
    }
    *(buffer + receive_length) = '\0';

    if (pccard_debug_flag) {
	pccard_output_debug_message("receive the string from pccardd: %s",
	    buffer);
    }

    return 0;
}


/*
 * Query how many slots the system has.
 * The number of slots is returned, if the query is succeeded.
 * Otherwise -1 is returned.
 */
int
pccard_query_number_of_slots(void)
{
    char buffer[PCCARD_MAX_RESPONSE_LENGTH + 1];
    int slots;

    if (pccard_send_request("S") == -1)
	return -1;
    if (pccard_wait_response() == -1)
	return -1;
    if (pccard_receive_response(buffer) == -1)
	return -1;
    slots = atoi(buffer);

    if (pccard_debug_flag)
	pccard_output_debug_message("the number of slots: nslots=%d", slots);

    return slots;
}


/*
 * Query information of the slot `slot'.
 * If the query is succeeded, `manufacturer', `version', `driver' and
 * `status' of the slot are set by this function, and 0 is returned.
 * Otherwise -1 is returned.
 */
int
pccard_query_slot_information(int slot, char *manufacturer, char *version,
    char *driver, int *status)
{
    char request_buffer[PCCARD_MAX_REQUEST_LENGTH + 1];
    char response_buffer[PCCARD_MAX_RESPONSE_LENGTH + 1];
    int response_slot;

    /*
     * Send a request and receive a response.
     */
    sprintf(request_buffer, "N%d", slot);
    if (pccard_send_request(request_buffer) == -1)
	return -1;
    if (pccard_wait_response() == -1)
	return -1;
    if (pccard_receive_response(response_buffer)  == -1)
	return -1;

    /*
     * Parse the response.
     * (`response_slot' is dummy argument).
     */
    pccard_parse_response(response_buffer, &response_slot, manufacturer,
	version, driver, status);

    if (pccard_debug_flag) {
	pccard_output_debug_message("slot status: "
	    "slot=%d, manufacturer=%s, version=%s, driver=%s, status=%d",
	    slot, manufacturer, version, driver, *status);
    }

    return 0;
}


/*
 * Send insertion request of the slot `slot'.
 * If the request is succeeded, 0 is returned.
 * Otherwise -1 is returned.
 */
int
pccard_query_insertion(int slot)
{
    char request_buffer[PCCARD_MAX_REQUEST_LENGTH + 1];
    char response_buffer[PCCARD_MAX_RESPONSE_LENGTH + 1];

    sprintf(request_buffer, "P%d", slot);
    if (pccard_send_request(request_buffer) == -1)
	return -1;
    if (pccard_wait_response() == -1)
	return -1;
    if (pccard_receive_response(response_buffer) == -1)
	return -1;

    if (pccard_debug_flag)
	pccard_output_debug_message("activate slot: slot=%d", slot);

    return 0;
}


/*
 * Send removal request of the slot `slot'.
 * If the request is succeeded, 0 is returned.
 * Otherwise -1 is returned.
 */
int
pccard_query_removal(int slot)
{
    char request_buffer[PCCARD_MAX_REQUEST_LENGTH + 1];
    char response_buffer[PCCARD_MAX_RESPONSE_LENGTH + 1];

    sprintf(request_buffer, "Q%d", slot);
    if (pccard_send_request(request_buffer) == -1)
	return -1;
    if (pccard_wait_response() == -1)
	return -1;
    if (pccard_receive_response(response_buffer) == -1)
	return -1;

    if (pccard_debug_flag)
	pccard_output_debug_message("inactivate slot: slot=%d", slot);

    return 0;
}


/*
 * Receive information that pccardd tells me that status of a slot has
 * been changed.
 * Upon return, `slot', `manufacturer', `version', `driver' and `status'
 * of the slot are set by this function.
 */
int
pccard_status_changed(int *slot, char *manufacturer, char *version,
    char *driver, int *status)
{
    char response_buffer[PCCARD_MAX_RESPONSE_LENGTH + 1];

    /*
     * Receive information.
     */
    if (pccard_receive_response(response_buffer) == -1)
	return -1;

    pccard_parse_response(response_buffer, slot, manufacturer, version,
	driver, status);

    if (pccard_debug_flag) {
	pccard_output_debug_message("status changed: "
	    "slot=%d, manufacturer=%s, version=%s, driver=%s, status=%d",
	    *slot, manufacturer, version, driver, *status);
    }

    return 0;
}


/*
 * Parse `response' sent from pccardd.
 * Get a slot number, manufacturer, version, driver and status
 * in the repsonse, put them into the arguments with the corresponding
 * names.
 */
static void
pccard_parse_response(const char *response, int *slot, char *manufacturer,
    char *version, char *driver, int *status)
{
    const char *token_start;
    const char *token_end;
    const char *p;

    /*
     * Initialize parameters.
     */
    *manufacturer = '\0';
    *version = '\0';
    *driver = '\0';
    *status = PCCARD_STATUS_UNKNOWN;

    /*
     * Get a slot number in the response.
     */
    p = response;
    *slot = atoi(p);
    while (*p != '~' && *p != '\0')
	p++;
    if (*p == '\0')
	return;
    p++;

    /*
     * Get a manufacturer in the response.
     */
    while (*p == ' ' || *p == '\t')
	p++;
    token_start = p;
    token_end = p;
    while (*p != '~' && *p != '\0') {
	if (*p != ' ' && *p != '\t')
	    token_end = p + 1;
	p++;
    }
    memcpy(manufacturer, token_start, (token_end - token_start));
    *(manufacturer + (token_end - token_start)) = '\0';
    if (*p == '\0')
	return;
    p++;

    /*
     * Get a version in the response.
     */
    while (*p == ' ' || *p == '\t')
	p++;
    token_start = p;
    token_end = p;
    while (*p != '~' && *p != '\0') {
	if (*p != ' ' && *p != '\t')
	    token_end = p + 1;
	p++;
    }
    memcpy(version, token_start, (token_end - token_start));
    *(version + (token_end - token_start)) = '\0';
    if (*p == '\0')
	return;
    p++;

    /*
     * Get a driver in the response.
     */
    while (*p == ' ' || *p == '\t')
	p++;
    token_start = p;
    token_end = p;
    while (*p != '~' && *p != '\0') {
	if (*p != ' ' && *p != '\t')
	    token_end = p + 1;
	p++;
    }
    memcpy(driver, token_start, (token_end - token_start));
    *(driver + (token_end - token_start)) = '\0';
    if (*p == '\0')
	return;
    p++;

    /*
     * Get status.
     */
    *status = atoi(p);
}


/*
 * Return the file descriptor of my socket.
 *
 * Note: This function is useful when an application program handles
 * an event by itself, especially handles I/O event of some files,
 * or gets both X and file events (like XtAppAddInput()).
 * But, don't read, write, open, nor close the socket file in the
 * application.
 */
int
pccard_socket_file(void)
{
    return socket_file;
}




syntax highlighted by Code2HTML, v. 0.9.1