/*
 * pcmcia-check-broken-cis.c
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * The initial developer of the original code is David A. Hinds
 * <dahinds@users.sourceforge.net>.  Portions created by David A. Hinds
 * are Copyright (C) 1999 David A. Hinds.  All Rights Reserved.
 *
 * (C) 1999             David A. Hinds
 * (C) 2005             Dominik Brodowski
 */

#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <syslog.h>

#include <sys/types.h>
#include <sys/stat.h>

#include "cistpl.h"


#define FIRMWARE_PATH	"/lib/firmware"
#define CIS_PATH	"/etc/pcmcia/cis"
#define SOCKET_PATH	"/sys/class/pcmcia_socket/pcmcia_socket%d/cis"

struct needs_cis {
	unsigned long code;
	unsigned long ofs;
	char *info;
	char *cisfile;
};

#define NEEDS_CIS_ENTRY(_code, _ofs, _info, _cisfile) \
{ .code = _code, .ofs = _ofs, .info = _info, .cisfile = _cisfile, }

static const struct needs_cis cis_table[] = {
	/* "D-Link DE-650 Ethernet" */
	NEEDS_CIS_ENTRY(0x40, 0x0009, "D-Link PC Ethernet Card", "D-Link.cis"),
	/* "Linksys Ethernet E-CARD PC Ethernet Card */
	NEEDS_CIS_ENTRY(0x40, 0x0009, "E-CARD PC Ethernet Card", "E-CARD.cis"),
	{ },
};

static int device_has_driver()
{
	char *devpath, *path;
	struct stat sbuf;

	devpath = getenv("DEVPATH");
	if (!devpath)
		return -ENODEV;
	path = alloca(strlen(devpath) + 15);
	sprintf(path, "/sys/%s/driver", devpath);
	if (!stat(path, &sbuf))
		return 1;

	return 0;
}

static char *read_cis(char *cis_file, int *size)
{
	char *cis_path;
	char *ret;
	int rc, cis_fd;
	struct stat sbuf;

	cis_path = alloca(strlen(FIRMWARE_PATH) + strlen(cis_file) + 2);
	sprintf(cis_path, "%s/%s", FIRMWARE_PATH, cis_file);
	cis_fd = open(cis_path, O_RDONLY);
	if (cis_fd == -1) {
		cis_path = alloca(strlen(CIS_PATH) + strlen(cis_file) + 2);
		sprintf(cis_path, "%s/%s", CIS_PATH, cis_file);
		if (cis_fd == -1) {
			rc = errno;
			errno = rc;
			return NULL;
		}
	}
	fstat(cis_fd, &sbuf);
	ret = malloc(sbuf.st_size);
	if (!ret) {
		rc = errno;
		close(cis_fd);
		errno = rc;
		return NULL;
	}
	if (read(cis_fd, ret, sbuf.st_size) != sbuf.st_size) {
		rc = errno;
		free(ret);
		close(cis_fd);
		errno = rc;
		return NULL;
	}
	close(cis_fd);
	*size = sbuf.st_size;
	return ret;
}

static int write_cis(char *cis, int socket_no, int size)
{
	char *cis_path;
	int cis_fd, count, rc;

	cis_path = alloca(strlen(SOCKET_PATH) + 2);
	sprintf(cis_path, SOCKET_PATH, socket_no);

	cis_fd = open(cis_path, O_RDWR);
	if (cis_fd == -1)
		return errno;

	count = 0;
	while (count < size) {
		int c;

		c = write(cis_fd, cis+count, size-count);
		if (c <= 0) {
			rc = errno;
			close(cis_fd);
			return rc;
		}
		count += c;
	}
	close(cis_fd);
	return 0;
}

static int repair_cis(char *cis_file, int socket_no)
{
	char *cis;
	int rc, size;

	if (device_has_driver())
		return 0;

	cis = read_cis(cis_file, &size);
	if (!cis)
		return errno;

	rc = write_cis(cis, socket_no, size);
	free(cis);
	return rc;
}

static void usage(const char *progname)
{
	fprintf(stderr,
		"Usage: %s [-r|--repair] <socketname>\n", progname);
	exit(1);
}

static struct option options[] = { { "repair", 0, NULL, 'r' },
				   { NULL, 0, NULL, 0 } };

int main(int argc, char **argv)
{
	int ret;
	char *socket;
	unsigned int socket_no;
	const struct needs_cis *entry = NULL;
	tuple_t tuple;
	unsigned char buf[256];
	int opt;
	int repair = 0;

	while ((opt = getopt_long(argc, argv, "r", options, NULL)) != -1) {
		switch (opt) {
		case 'r':
			repair = 1;
			break;
		default:
			usage(argv[0]);
		}
	}

	socket = getenv("SOCKET_NO");
	if (socket)
		socket_no = (unsigned int) strtoul(socket, NULL, 0);
	else {
		if (argc < optind + 1)
			usage(argv[0]);
		socket_no = strtoul(argv[optind], NULL, 0);
	}

	ret = read_out_cis(socket_no, NULL);
	if (ret)
		return ret;

	entry = &cis_table[0];

	while (entry) {
		if (!entry->cisfile)
			return 0;

		tuple.DesiredTuple = entry->code;
		tuple.Attributes = TUPLE_RETURN_COMMON;
		tuple.TupleData = buf;
		tuple.TupleDataMax = 255;
		pcmcia_get_first_tuple(BIND_FN_ALL, &tuple);

		tuple.TupleOffset = entry->ofs;

		pcmcia_get_tuple_data(&tuple);

		if (strncmp((char *) tuple.TupleData, entry->info,
			    strlen(entry->info)) != 0) {
			entry++;
			continue;
		}

		if (repair) {
			return repair_cis(entry->cisfile, socket_no);
		} else {
			printf("%s", entry->cisfile);
			return 1;
		}
	};

	return 0;
}
