view pack.c @ 63:d78c4d9fad13

Move to port/ code that I didn't write
author David Demelier <markand@malikania.fr>
date Wed, 09 Nov 2011 19:30:33 +0100
parents 311eaa9d004c
children b2cd1fd33bb0
line wrap: on
line source

/*
 * Copyright (c) 2011, David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/queue.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <ctype.h>

#include "pack.h"

/* --------------------------------------------------------
 * structure definitions
 * -------------------------------------------------------- */

/*
 * Conversion function pointer.
 */
typedef void (*convert_fn)(void *);

/*
 * Item structure store the integer into the largest data type
 * uint64_t.
 */

struct item {
	size_t		size;	/* 8, 16, 32 or 64 bits? */
	uint64_t	i;	/* the data */
	convert_fn	conv;	/* conversion function */
	STAILQ_ENTRY(item) next;
};

/*
 * List of item structure.
 */

STAILQ_HEAD(item_list, item);

/* --------------------------------------------------------
 * prototypes
 * -------------------------------------------------------- */

static int		pack_item_add(struct item_list *, const struct item *);
static int		pack_parse(struct item_list *, const char *, va_list);
static size_t		pack_getsize(char);
static convert_fn	pack_getconvert(char);
static void		pack_convert16(void *);
static void		pack_convert32(void *);
static void		pack_convert64(void *);
static int		pack_fatal(struct item_list *);

/* --------------------------------------------------------
 * private functions
 * -------------------------------------------------------- */

#define LENGTH(x)	(sizeof (x) / sizeof (x[0]))	

/*
 * Macros that converts byte order or keep them if the host matches
 * the byte order requested.
 */

#define CONVERT_UINT16(i) do {						\
	if (ptype != PACK_HOST_BYTEORDER)				\
		i = pack_swap16(i); 					\
} while (/* CONSTCOND */ 0)

#define CONVERT_UINT32(i) do {						\
	if (ptype != PACK_HOST_BYTEORDER)				\
		i = pack_swap32(i); 					\
} while (/* CONSTCOND */ 0)

#define CONVERT_UINT64(i) do {						\
	if (ptype != PACK_HOST_BYTEORDER)				\
		i = pack_swap64(i); 					\
} while (/* CONSTCOND */ 0)

/*
 * Associative structure, the `tok' member is used for the format
 * and define the integer size. The convert function reverse the
 * bytes if it is needed.
 */

static struct integer {
	char		tok;		/* format char */
	size_t		tocopy;		/* size */
	convert_fn	convert;	/* conversion function */
} sizes[] = {
	{ 'c',		sizeof (uint8_t),	NULL		},
	{ 's',		sizeof (uint16_t),	&pack_convert16	},
	{ 'i',		sizeof (uint32_t),	&pack_convert32	},
	{ 'l',		sizeof (uint64_t),	&pack_convert64	}
};

/*
 * Try to append a new item to the list. The function create a new item
 * object since the pack_parse() use a stack'ed item and not an
 * allocated object. Returns 0 or -1 on failure.
 */

static int
pack_item_add(struct item_list *list, const struct item *item)
{
	struct item *res;

	if ((res = malloc(sizeof (struct item))) == NULL)
		return -1;

	res->size	= item->size;
	res->i		= item->i;
	res->conv	= item->conv;

	STAILQ_INSERT_TAIL(list, res, next);

	return 0;
}

/*
 * Parse the format, return 0 on success or -1 on failure.
 */

#define PACK_GETARG(i, ap, tok)						\
do {									\
	switch ((tok)) {						\
	case 'c':							\
	case 's':							\
	case 'i':							\
		(i) = va_arg((ap), unsigned);				\
		break;							\
	case 'l':							\
		(i) = va_arg((ap), uint64_t);				\
	default:							\
		break;							\
	}								\
} while (/* CONSTCOND */ 0)

/*
 * Little helper to get number of element when the user wants a array of
 * objects. Sets the nelem to 0 on array failure or 1 on normal count.
 */

#define PACK_GETNELEM(nelem, p)						\
do {									\
	if (p[1] == '[') {						\
		if (p[2] != ']')					\
			nelem = (int) strtol(p + 2, NULL, 10);		\
		else if (p[2] == ']')					\
			nelem = va_arg(ap, int);			\
									\
		/* Go to the end of fmt and find ']' */			\
		while (*p != ']' && *p != '\0')				\
			++p;						\
	} else								\
		nelem = 1;						\
} while (/* CONSTCOND */ 0)

static int
pack_parse(struct item_list *list, const char *fmt, va_list ap)
{
	const char *p;
	char tok;
	struct item item;

	STAILQ_INIT(list);

	for (p = fmt; *p != '\0'; ++p) {
		int i, nelem = 1;

		if (isspace(*p))
			continue;

		tok = *p;
		item.size = pack_getsize(tok);

		/* Bad character */
		if (item.size == 0)
			continue;

		PACK_GETNELEM(nelem, p);
		if (nelem == 0)
			continue;

		for (i = 0; i < nelem; ++i) {
			PACK_GETARG(item.i, ap, tok);
			item.conv	= pack_getconvert(tok);

			if (pack_item_add(list, &item) < 0)
				return pack_fatal(list);
		}
	}

	return 0;
}

/*
 * Get the appropriate size associated with the `tok' character. If
 * the token is not found the result is 0.
 */

static size_t
pack_getsize(char tok)
{
	struct integer *s;
	unsigned int i;

	for (s = sizes, i = 0; i < LENGTH(sizes); ++s, ++i)
		if (s->tok == tok)
			return s->tocopy;

	return 0;
}

/*
 * Return the conversion function.
 */

static convert_fn
pack_getconvert(char tok)
{
	struct integer *s;
	unsigned int i;

	for (s = sizes, i = 0; i < LENGTH(sizes); ++s, ++i)
		if (s->tok == tok)
			return s->convert;

	return NULL;
}

/*
 * Conversion functions. They reverse the bytes without any
 * check.
 */

static void
pack_convert16(void *obj)
{
	uint16_t *x = obj;

	*x = pack_swap16(*x);
}

static void
pack_convert32(void *obj)
{
	uint32_t *x = obj;

	*x = pack_swap32(*x);
}

static void
pack_convert64(void *obj)
{
	uint64_t *x = obj;

	*x = pack_swap64(*x);
}

static int
pack_fatal(struct item_list *list)
{
	struct item *item, *tmp;

	STAILQ_FOREACH_SAFE(item, list, next, tmp)
		free(item);

	return -1;
}

/* --------------------------------------------------------
 * public functions
 * -------------------------------------------------------- */

/*
 * Function that writes everything to the file `path'. These functions
 * does not append to the file, so if you want successive call to append
 * variables use fpack instead.
 * Returns 0 on success or -1 on failure.
 */

int
pack_write(int ptype, const char *path, const char *fmt, ...)
{
	va_list ap;
	int status;

	va_start(ap, fmt);
	status = pack_vwrite(ptype, path, fmt, ap);
	va_end(ap);
	
	return status;
}

int
pack_vwrite(int ptype, const char *path, const char *fmt, va_list ap)
{
	FILE *fp;
	int status;

	if ((fp = fopen(path, "w+b")) == NULL)
		return -1;

	status = pack_vfwrite(ptype, fp, fmt, ap);
	fclose(fp);

	return status;
}

/*
 * Write to a file that is already open. The function does not call
 * fclose() so you need to do it yourself later.
 */

int
pack_fwrite(int ptype, FILE *fp, const char *fmt, ...)
{
	va_list ap;
	int status;

	va_start(ap, fmt);
	status = pack_vfwrite(ptype, fp, fmt, ap);
	va_end(ap);

	return status;
}

int
pack_vfwrite(int ptype, FILE *fp, const char *fmt, va_list ap)
{
	struct item_list list;
	int status;

	if ((status = pack_parse(&list, fmt, ap)) == 0) {
		struct item *item, *tmp;

		STAILQ_FOREACH_SAFE(item, &list, next, tmp) {
			/* 8 bits does not need to be converted */
			if (ptype != PACK_HOST_BYTEORDER && item->conv != NULL)
				item->conv(&item->i);

			fwrite(&item->i, item->size, 1, fp);

			free(item);
		}
	}

	return status;
}

/*
 * Function that read the binary file and restore values to the same format
 * as pack functions. Arguments must be pointer of enough space to store
 * the values.
 */

int
pack_read(int ptype, const char *path, const char *fmt, ...)
{
	va_list ap;
	int status;

	va_start(ap, fmt);
	status = pack_vread(ptype, path, fmt, ap);
	va_end(ap);

	return status;
}

int
pack_vread(int ptype, const char *path, const char *fmt, va_list ap)
{
	FILE *fp;
	int status;

	if ((fp = fopen(path, "rb")) == NULL)
		return -1;

	status = pack_vfread(ptype, fp, fmt, ap);
	fclose(fp);

	return status;

}

int
pack_fread(int ptype, FILE *fp, const char *fmt, ...)
{
	va_list ap;
	int status;

	va_start(ap, fmt);
	status = pack_vfread(ptype, fp, fmt, ap);
	va_end(ap);

	return status;
}

int
pack_vfread(int ptype, FILE *fp, const char *fmt, va_list ap)
{
	const char *p;
	void *ptr;
	size_t tocopy;
	convert_fn convert;

	for (p = fmt; *p != '\0'; ++p) {
		char tok;
		int nelem, i;

		if (isspace(*p))
			continue;

		tok = *p;
		tocopy = pack_getsize(tok);

		/* Bad character */
		if (tocopy == 0)
			continue;

		/* Determine how many element to read and copy */
		PACK_GETNELEM(nelem, p);
		if (nelem == 0)
			continue;

		if ((ptr = va_arg(ap, void *)) == NULL)
			continue;

		for (i = 0; i < nelem; ++i) {
			fread((char *) ptr + (tocopy * i), tocopy, 1, fp);

			/* Convert if needed */
			convert = pack_getconvert(tok);
			if (ptype != PACK_HOST_BYTEORDER && convert != NULL)
				convert((char *) ptr + (tocopy * i));
		}
	}

	return 0;
}