/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2003-2016. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 */

/*
 * Description:	
 *
 * Author: 	Rickard Green
 */

/* Headers to include ... */

#ifdef __WIN32__
#	include <winsock2.h>
#	undef WIN32_LEAN_AND_MEAN
#	define WIN32_LEAN_AND_MEAN
#	include <windows.h>
typedef int socklen_t;
#else
#	if defined(__linux__) && defined(__GNUC__)
#   		define _GNU_SOURCE 1
#	endif
#	include <unistd.h>
#	include <sys/types.h>
#	include <sys/socket.h>
#	include <netinet/in.h>
#	include <fcntl.h>
#	include <netdb.h>
#	include <arpa/inet.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>

#include "erl_fixed_size_int_types.h"
#include "erl_memory_trace_parser.h"
#include "erl_memory_trace_block_table.h"
#include "ethread.h"

/* Increment when changes are made */
#define EMEM_VSN_STR "0.9"

/* Features not fully implemented yet */
#define EMEM_A_SWITCH 0
#define EMEM_C_SWITCH 0
#define EMEM_c_SWITCH 0
#define EMEM_d_SWITCH 0

/* Some system specific defines ... */
#ifdef __WIN32__
#	define ssize_t int
#	define GET_SOCK_ERRNO() (WSAGetLastError() - WSABASEERR)
#	define IS_INVALID_SOCKET(X) ((X) == INVALID_SOCKET)
#	ifdef __GNUC__
#		define INLINE __inline__
#	else
#	define INLINE __forceinline
#       endif
#	define DIR_SEP_CHAR '\\'
#else
#	define SOCKET int
#	define closesocket close
#	define GET_SOCK_ERRNO() (errno ? errno : INT_MAX)
#	define INVALID_SOCKET (-1)
#	define IS_INVALID_SOCKET(X) ((X) < 0)
#	ifdef __GNUC__
#		define INLINE __inline__
#	else
#		define INLINE
#	endif
#	define DIR_SEP_CHAR '/'
#endif

#define EM_ERL_CMD_FILE_NAME "erl_cmd.txt"
#define EM_OUTPUT_FILE_SUFFIX ".emem"

#define PRINT_OPERATIONS 0

/* In VC++, noreturn is a declspec that has to be before the types,
 * but in GNUC it is an attribute to be placed between return type
 * and function name, hence __decl_noreturn <types> __noreturn <function name>
 *
 * at some platforms (e.g. Android) __noreturn is defined at sys/cdef.h
 */
#if __GNUC__
#  define __decl_noreturn
#  ifndef __noreturn
#     define __noreturn __attribute__((noreturn))
#  endif
#else
#  if defined(__WIN32__) && defined(_MSC_VER)
#    define __noreturn
#    define __decl_noreturn __declspec(noreturn)
#  else
#    define __noreturn
#    define __decl_noreturn
#  endif
#endif

/* Our own assert() ... */
#ifdef DEBUG
#define ASSERT(A) ((A) ? (void)1 : assert_failed(__FILE__, __LINE__, #A))
#include <stdio.h>
__decl_noreturn static void __noreturn assert_failed(char *f, int l, char *a)
{
    fprintf(stderr, "%s:%d: Assertion failed: %s\n", f, l, a);
    abort();
}

#else
#define ASSERT(A) ((void) 1)
#endif

#define ERR_RET(X) return (X)
#if 1
#  undef  ERR_RET
#  define ERR_RET(X) abort()
#endif

/* #define HARD_DEBUG */


#define EM_EXIT_RESULT			(EMTBT_MIN_ERROR - 1)
#define EM_TRUNCATED_TRACE_ERROR	(EMTBT_MIN_ERROR - 2)
#define EM_INTERNAL_ERROR		(EMTBT_MIN_ERROR - 3)

#define EM_DEFAULT_BUF_SZ 8192

#define EM_LINES_UNTIL_HEADER 20
#define EM_NO_OF_OPS 400
#define EM_MAX_CONSECUTIVE_TRACE_READS 10
#define EM_MAX_NO_OF_TRACE_BUFS 1280
#define EM_MIN_TRACE_READ_SIZE (EM_DEFAULT_BUF_SZ/20)
#define EM_TIME_FIELD_WIDTH 11

__decl_noreturn static void __noreturn error(int res);
__decl_noreturn static void __noreturn error_msg(int res, char *msg);

typedef struct {
    usgnd_int_max size;
    usgnd_int_max min_size;
    usgnd_int_max max_size;
    usgnd_int_max max_ever_size;
    usgnd_int_max no;
    usgnd_int_max min_no;
    usgnd_int_max max_no;
    usgnd_int_max max_ever_no;
    usgnd_int_max allocs;
    usgnd_int_max reallocs;
    usgnd_int_max frees;
} em_mem_info;

typedef struct em_buffer_ {
    struct em_buffer_ *next;
    int write;
    char *data;
    char *data_end;
    char *end;
    size_t size;
    char start[EM_DEFAULT_BUF_SZ];
} em_buffer;

typedef struct {
    int no_writer;
    int no_reader;
    size_t tot_buf_size;
    size_t max_buf_size;
    char *name;
    em_buffer *first;
    em_buffer *last;
    ethr_mutex mutex;
    ethr_cond cond;
    int used_def_buf_a;
    em_buffer def_buf_a;
    int used_def_buf_b;
    em_buffer def_buf_b;
} em_buf_queue;

typedef struct {
    char *ptr;
    size_t size;
} em_area;

typedef struct {
    char *name;
    int ix;
} em_output_types;

typedef struct {

    /* Memory allocation functions */
    void * (*alloc)(size_t);
    void * (*realloc)(void *, size_t);
    void   (*free)(void *);

    emtbt_table *block_table;
    emtbt_table **carrier_table;

    struct {
	em_mem_info total;
	em_mem_info *btype;
	em_mem_info *allctr;
	em_mem_info **allctr_prv_crr;
	em_mem_info **allctr_usd_crr;

	struct {
	    usgnd_int_32 secs;
	    usgnd_int_32 usecs;
	} stop_time;
	emtp_op_type stop_reason;
	usgnd_int_32 exit_status;
    } info;

    /* Input ... */
    struct {
	usgnd_int_16 listen_port;
	SOCKET socket;
	usgnd_int_max total_trace_size;
	int error;
	char *error_descr;
	em_buf_queue queue;
    } input;

    /* Output ... */
    struct {
	usgnd_int_32 next_print;
	usgnd_int_32 next_print_inc;
	char *header;
	size_t header_size;
	size_t values_per_object;
	size_t values_per_line;
	size_t field_width;
	int verbose;
	int total;
	int all_allctrs;
	int no_allctrs;
	em_output_types *allctrs;
	int all_btypes;
	int no_btypes;
	em_output_types *btypes;
	int max_min_values;
	int block_counts;
	int op_counts;
	int lines_until_header;
	FILE *stream;
	char *file_name;
#if EMEM_d_SWITCH
	char *dir_name;
	FILE *erl_cmd_file;
	struct {
	    ethr_mutex *mutex;
	    ethr_cond *cond;
	} go;
#endif
	em_buf_queue queue;
    } output;

    /* Trace info */
    emtp_state *trace_state;
    emtp_info  trace_info;

} em_state;

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * Threads...                                                              *
 *                                                                         *
\*                                                                         */

static INLINE void
mutex_init(ethr_mutex *mtx)
{
    int res = ethr_mutex_init(mtx);
    if (res)
	error_msg(res, "Mutex init");
}

static INLINE void
mutex_destroy(ethr_mutex *mtx)
{
    int res = ethr_mutex_destroy(mtx);
    if (res)
	error_msg(res, "Mutex destroy");
}

static INLINE void
mutex_lock(ethr_mutex *mtx)
{
    ethr_mutex_lock(mtx);
}

static INLINE void
mutex_unlock(ethr_mutex *mtx)
{
    ethr_mutex_unlock(mtx);
}

static INLINE void
cond_init(ethr_cond *cnd)
{
    int res = ethr_cond_init(cnd);
    if (res)
	error_msg(res, "Cond init");
}

static INLINE void
cond_destroy(ethr_cond *cnd)
{
    int res = ethr_cond_destroy(cnd);
    if (res)
	error_msg(res, "Cond destroy");
}

static INLINE void
cond_wait(ethr_cond *cnd, ethr_mutex *mtx)
{
    int res = ethr_cond_wait(cnd, mtx);
    if (res != 0 && res != EINTR)
	error_msg(res, "Cond wait");
}

static INLINE void
cond_signal(ethr_cond *cnd)
{
    ethr_cond_signal(cnd);
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * Buffer queues                                                           *
 *                                                                         *
\*                                                                         */

static INLINE void
reset_buffer(em_buffer *b, size_t size)
{
    b->write = 1;
    b->next = NULL;
    if (size) {
	b->size = size;
	b->end = b->start + size;
    }
    b->data_end = b->data = b->start;
}

static void
init_queue(em_state *state, em_buf_queue *queue)
{
    reset_buffer(&queue->def_buf_a, EM_DEFAULT_BUF_SZ);
    reset_buffer(&queue->def_buf_b, EM_DEFAULT_BUF_SZ);
    queue->first = NULL;
    queue->last = NULL;
    queue->no_writer = 0;
    queue->no_reader = 0;
    queue->tot_buf_size = 0;
    queue->max_buf_size = ~0;
    queue->name = "";
    queue->used_def_buf_a = 0;
    queue->used_def_buf_b = 0;
    mutex_init(&queue->mutex);
    cond_init(&queue->cond);
}

static void
destroy_queue(em_state *state, em_buf_queue *queue)
{
    while (queue->first) {
	em_buffer *buf = queue->first;
	queue->first = queue->first->next;
	if (buf != &queue->def_buf_a && buf != &queue->def_buf_b)
	    (*state->free)((void *) buf);
    }
    mutex_destroy(&queue->mutex);
    cond_destroy(&queue->cond);
}

static void
disconnect_queue_writer(em_buf_queue *queue)
{
    mutex_lock(&queue->mutex);
    queue->no_writer = 1;
    cond_signal(&queue->cond);
    mutex_unlock(&queue->mutex);
}

static void
disconnect_queue_reader(em_buf_queue *queue)
{
    mutex_lock(&queue->mutex);
    queue->no_reader = 1;
    cond_signal(&queue->cond);
    mutex_unlock(&queue->mutex);
}

static int
is_queue_writer_disconnected(em_buf_queue *queue)
{
    int res;
    mutex_lock(&queue->mutex);
    res = queue->no_writer;
    mutex_unlock(&queue->mutex);
    return res;
}

static int
is_queue_reader_disconnected(em_buf_queue *queue)
{
    int res;
    mutex_lock(&queue->mutex);
    res = queue->no_reader;
    mutex_unlock(&queue->mutex);
    return res;
}

static INLINE void
dequeue(em_state *state, em_buf_queue *queue)
{
    em_buffer *buf;

    ASSERT(queue->first);
    ASSERT(queue->tot_buf_size > 0);

    buf = queue->first;
    queue->first = buf->next;
    if (!queue->first)
	queue->last = NULL;

    ASSERT(queue->tot_buf_size >= buf->size);
    queue->tot_buf_size -= buf->size;

    if (buf == &queue->def_buf_a)
	queue->used_def_buf_a = 0;
    else if (buf == &queue->def_buf_b)
	queue->used_def_buf_b = 0;
    else
	(*state->free)((void *) buf);

}


static INLINE em_buffer *
enqueue(em_state *state, em_buf_queue *queue, size_t min_size)
{
    em_buffer *buf;

    if (min_size > EM_DEFAULT_BUF_SZ)
	goto alloc_buf;

    if (!queue->used_def_buf_a) {
	buf = &queue->def_buf_a;
	queue->used_def_buf_a = 1;
	reset_buffer(buf, 0);
    }
    else if (!queue->used_def_buf_b) {
	buf = &queue->def_buf_b;
	queue->used_def_buf_b = 1;
	reset_buffer(buf, 0);
    }
    else {
	size_t bsize;
    alloc_buf:

	bsize = EM_DEFAULT_BUF_SZ;
	if (bsize < min_size)
	    bsize = min_size;
	
	buf = (em_buffer *) (*state->alloc)(sizeof(em_buffer)
					    + (sizeof(char)
					       * (bsize-EM_DEFAULT_BUF_SZ)));
        if (!buf)
            error(ENOMEM);
        buf->size = bsize;
        reset_buffer(buf, bsize);
    }

    if (queue->last) {
	ASSERT(queue->first);
	queue->last->write = 0;
	queue->last->next = buf;
    }
    else {
	ASSERT(!queue->first);
	queue->first = buf;
    }

    queue->tot_buf_size += buf->size;
    queue->last = buf;

    return buf;
}

static void
get_next_read_area(em_area *area, em_state *state, em_buf_queue *queue)
{
    mutex_lock(&queue->mutex);

    while (!queue->first || queue->first->data == queue->first->data_end) {
	if (queue->first && (!queue->first->write
			     || queue->first->data == queue->first->end)) {
	    dequeue(state, queue);
	    continue;
	}

	if (queue->no_writer) {
	    area->ptr = NULL;
	    area->size = 0;
	    mutex_unlock(&queue->mutex);
	    return;
	}
	cond_wait(&queue->cond, &queue->mutex);
    }

    ASSERT(queue->first->data < queue->first->data_end);

    area->ptr = queue->first->data;
    area->size =  queue->first->data_end - queue->first->data;

    queue->first->data = queue->first->data_end;

    mutex_unlock(&queue->mutex);
}

static INLINE void
wrote_area_aux(em_area *area, em_state *state, em_buf_queue *queue, int do_lock)
{
    em_buffer *buf;

    if (do_lock)
	mutex_lock(&queue->mutex);

    buf = queue->last;

    ASSERT(area->ptr);
    ASSERT(area->size);

    ASSERT(buf);
    ASSERT(buf->data_end == area->ptr);
    ASSERT(buf->end >= area->ptr + area->size);

    buf->data_end = area->ptr + area->size;

    area->ptr = NULL;
    area->size = 0;

    cond_signal(&queue->cond);

    if (do_lock)
	mutex_unlock(&queue->mutex);
}

static INLINE void
wrote_area(em_area *area, em_state *state, em_buf_queue *queue)
{
    wrote_area_aux(area, state, queue, 1);
}

static void
get_next_write_area(em_area *area, em_state *state, em_buf_queue *queue,
		    size_t size)
{
    em_buffer *buf;

    mutex_lock(&queue->mutex);

    ASSERT(!area->size || area->ptr); 

    if (area->size)
	wrote_area_aux(area, state, queue, 0);

    buf = ((queue->last && queue->last->end - queue->last->data_end >= size)
	   ? queue->last
	   : enqueue(state, queue, size));

    ASSERT(buf);
    ASSERT(buf->end - buf->data_end >= size);
    area->ptr = buf->data_end;
    area->size = buf->end - buf->data_end;

    if (queue->tot_buf_size > queue->max_buf_size) {
	fprintf(stderr,
		"emem: Maximum %s buffer size (%lu) exceeded. "
		"Terminating...\n",
		queue->name,
		(unsigned long) queue->max_buf_size);
	exit(1);
    }

    mutex_unlock(&queue->mutex);

}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * Output                                                                  *
 *                                                                         *
\*                                                                         */

static INLINE size_t
write_str(char **dstpp, char *srcp)
{
    size_t i = 0;
    if (dstpp)
	while (srcp[i])
	    *((*dstpp)++) = srcp[i++];
    else
	while (srcp[i]) i++;
    return i;
}


static size_t
write_strings(char **ptr,
	      char **strings,
	      char *first_line_prefix,
	      char *line_prefix,
	      size_t max_line_size)
{
    size_t size;
    size_t tot_size = 0;
    size_t line_size = 0;
    size_t line_prefix_size;
    sgnd_int_32 ix;

    tot_size = line_size = line_prefix_size = write_str(ptr, first_line_prefix);

    for (ix = 0; strings[ix]; ix++) {
	size = write_str(NULL, strings[ix]);
	if (line_size + 1 + size > max_line_size) {
	    tot_size += write_str(ptr, "\n");
	    tot_size += write_str(ptr, line_prefix);
	    line_size = line_prefix_size;
	}
	tot_size += write_str(ptr, " ");
	tot_size += ptr ? write_str(ptr, strings[ix]) : size;
	line_size += 1 + size;
    }

    tot_size += write_str(ptr, "\n");

    return tot_size;
}

static size_t
write_title(char **bufp, size_t *overflow, size_t width, char *str)
{
    size_t i, sz, ws;
    char *p, *endp;

    /*
     * Writes at least one '|' character at the beginning.
     * Right aligns "str".
     * If "str" is larger than "width - 1" and overflow is NULL,
     * then "str" is trucated; otherwise, string is not truncated.
     */

    if (width <= 0)
	return 0;

    if (!bufp && !overflow)
	return width;

    sz = strlen(str) + 1;
    if (sz > width) {
	ws = 0;
	if (overflow)
	    *overflow += sz - width;
	else
	    sz = width;
    }
    else {
	ws = width - sz;
	if (overflow) {
	    if (ws >= *overflow) {
		ws -= *overflow;
		*overflow = 0;
	    }
	    else {
		*overflow -= ws;
		ws = 0;
	    }
	}
	sz += ws;
    }
    if (!bufp)
	return sz;

    p = *bufp;
    endp = p + width;

    *(p++) = '|';
    while (ws > 1) {
	ws--;
	*(p++) = ' ';
    }

    i = 0;
    while (str[i] && (overflow || p < endp))
	*(p++) = str[i++];

    while (ws) {
	ws--;
	*(p++) = ' ';
    }

    ASSERT(overflow || p == endp);
    ASSERT(sz == (size_t) (p - *bufp));
    *bufp = p;
    return sz;
}

static size_t
write_obj_sub_titles(em_state *state, char **bufp, size_t *overflow)
{
    size_t field_width = state->output.field_width;
    size_t size = write_title(bufp, overflow, field_width, "size");
    if (state->output.max_min_values) {
	size += write_title(bufp, overflow, field_width, "min size");
	size += write_title(bufp, overflow, field_width, "max size");
    }
    if (state->output.block_counts) {
	size += write_title(bufp, overflow, field_width, "no");
	if (state->output.max_min_values) {
	    size += write_title(bufp, overflow, field_width,  "min no");
	    size += write_title(bufp, overflow, field_width,  "max no");
	}
    }
    if (state->output.op_counts) {
	size += write_title(bufp, overflow, field_width, "alloc()");
	size += write_title(bufp, overflow, field_width, "realloc()");
	size += write_title(bufp, overflow, field_width, "free()");
    }

    return size;
}

static size_t
write_header(em_state *state, char *ptr, int trunc)
{
#define MIN_LTEXT_SZ 18
#define HEADER_EOL_STR "|\n"
    char *p;
    char **pp;
    int i;
    size_t overflow;
    size_t *ofp;
    size_t obj_size = state->output.values_per_object*state->output.field_width;
    size_t size = 0;
    int have_seg_crr = state->trace_info.have_segment_carrier_info;

    if (ptr) {
	p = ptr;
	pp = &p;
    }
    else {
	p = NULL;
	pp = NULL;
    }

    overflow = 0;
    ofp = trunc ? NULL : &overflow;

    size += write_title(pp, ofp, EM_TIME_FIELD_WIDTH, "time");

    if (state->output.total) {
	int no = 1;
	if (have_seg_crr) {
	    if (state->info.allctr_prv_crr[state->trace_info.segment_ix])
		no++;
	    if (state->info.allctr_usd_crr[state->trace_info.segment_ix])
		no++;
	}
	size += write_title(pp, ofp, (have_seg_crr ? 3 : 1)*obj_size, "total");
    }

    for (i = 0; i < state->output.no_allctrs; i++) {
	int no = 1;
	if (state->info.allctr_prv_crr[state->output.allctrs[i].ix])
	    no++;
	if (state->info.allctr_usd_crr[state->output.allctrs[i].ix])
	    no++;
	size += write_title(pp, ofp, no*obj_size, state->output.allctrs[i].name);
    }

    for (i = 0; i < state->output.no_btypes; i++)
	size += write_title(pp, ofp, obj_size, state->output.btypes[i].name);

    size += write_str(pp, HEADER_EOL_STR);

    overflow = 0;
    size += write_title(pp, ofp, EM_TIME_FIELD_WIDTH, "");

    if (state->output.total) {
	size += write_title(pp, ofp, obj_size, (obj_size <= MIN_LTEXT_SZ
						? "alcd blks"
						: "allocated blocks"));
	if (have_seg_crr) {
	    if (state->info.allctr_prv_crr[state->trace_info.segment_ix])
		size += write_title(pp, ofp, obj_size, (obj_size <= MIN_LTEXT_SZ
							? "mpd segs"
							: "mapped segments"));
	    if (state->info.allctr_usd_crr[state->trace_info.segment_ix])
		size += write_title(pp, ofp, obj_size, (obj_size <= MIN_LTEXT_SZ
							? "chd segs"
							: "cached segments"));
	}
    }

    for (i = 0; i < state->output.no_allctrs; i++) {
	size += write_title(pp, ofp, obj_size, (obj_size <= MIN_LTEXT_SZ
						? "alcd blks"
						: "allocated blocks"));
	if (state->info.allctr_prv_crr[state->output.allctrs[i].ix])
	    size += write_title(pp, ofp, obj_size, (obj_size <= MIN_LTEXT_SZ
						    ? "prvd crrs" 
						    : "provided carriers"));
	if (state->info.allctr_usd_crr[state->output.allctrs[i].ix])
	    size += write_title(pp, ofp, obj_size, (obj_size <= MIN_LTEXT_SZ
						    ? "usd crrs"
						    : "used carriers"));
    }
    for (i = 0; i < state->output.no_btypes; i++)
	size += write_title(pp, ofp, obj_size, (obj_size <= MIN_LTEXT_SZ
						? "alcd blks"
						: "allocated blocks"));


    size += write_str(pp, HEADER_EOL_STR);
    overflow = 0;
    size += write_title(pp, ofp, EM_TIME_FIELD_WIDTH, ""); 

    if (state->output.total) {
	size += write_obj_sub_titles(state, pp, ofp);
	if (have_seg_crr) {
	    if (state->info.allctr_prv_crr[state->trace_info.segment_ix])
		size += write_obj_sub_titles(state, pp, ofp);
	    if (state->info.allctr_usd_crr[state->trace_info.segment_ix])
		size += write_obj_sub_titles(state, pp, ofp);
	}
    }

    for (i = 0; i < state->output.no_allctrs; i++) {
	size += write_obj_sub_titles(state, pp, ofp);
	if (state->info.allctr_prv_crr[state->output.allctrs[i].ix])
	    size += write_obj_sub_titles(state, pp, ofp);
	if (state->info.allctr_usd_crr[state->output.allctrs[i].ix])
	    size += write_obj_sub_titles(state, pp, ofp);
    }

    for (i = 0; i < state->output.no_btypes; i++)
	size += write_obj_sub_titles(state, pp, ofp);

    size += write_str(pp, HEADER_EOL_STR);
#undef MIN_LTEXT_SZ
#undef HEADER_EOL_STR
    return size;
}

static INLINE void
write_mem_info(em_state *state, char **p, em_mem_info *mi)
{
    int fw = state->output.field_width - 1;
    *p += sprintf(*p, "%*" USGND_INT_MAX_FSTR " ", fw, mi->size);
    if (state->output.max_min_values)
	*p += sprintf(*p,
		      "%*" USGND_INT_MAX_FSTR
		      " %*" USGND_INT_MAX_FSTR " ",
		      fw, mi->min_size,
		      fw, mi->max_size);
    if (state->output.block_counts) {
	*p += sprintf(*p, "%*" USGND_INT_MAX_FSTR " ", fw, mi->no);
	if (state->output.max_min_values)
	    *p += sprintf(*p,
			  "%*" USGND_INT_MAX_FSTR
			  " %*" USGND_INT_MAX_FSTR " ",
			  fw, mi->min_no,
			  fw, mi->max_no);
    }
    if (state->output.op_counts)
	*p += sprintf(*p,
		      "%*" USGND_INT_MAX_FSTR
		      " %*" USGND_INT_MAX_FSTR
		      " %*" USGND_INT_MAX_FSTR " ",
		      fw, mi->allocs,
		      fw, mi->reallocs,
		      fw, mi->frees);

    /* Update max ever values */
    if (mi->max_ever_size < mi->max_size)
	mi->max_ever_size = mi->max_size;
    if (mi->max_ever_no < mi->max_no)
	mi->max_ever_no = mi->max_no;
    /* Reset max/min values */
    mi->max_size = mi->min_size = mi->size;
    mi->max_no = mi->min_no = mi->no;
}

static INLINE void
write_max_ever_mem_info(em_state *state, char **p, em_mem_info *mi)
{
    int fw = state->output.field_width - 1;
    *p += sprintf(*p, "%*" USGND_INT_MAX_FSTR " ", fw, mi->max_ever_size);
    if (state->output.max_min_values)
	*p += sprintf(*p, "%*s %*s ", fw, "", fw, "");
    if (state->output.block_counts) {
	*p += sprintf(*p, "%*" USGND_INT_MAX_FSTR " ", fw, mi->max_ever_no);
	if (state->output.max_min_values)
	    *p += sprintf(*p, "%*s %*s ", fw, "", fw, "");
    }
    if (state->output.op_counts)
	*p += sprintf(*p, "%*s %*s %*s ", fw, "", fw, "", fw, "");
}

static void
print_string(em_state *state, char *str)
{
    em_area area = {NULL, 0};
    char *p;

    /* Get area */

    get_next_write_area(&area,state,&state->output.queue,write_str(NULL,str));

    p = area.ptr;
    area.size = write_str(&p, str);

    /* Leave area */

    wrote_area(&area, state, &state->output.queue);

}

static int
print_emu_arg(em_state *state)
{
    em_area area = {NULL, 0};
    char hostname[100];
    char carg[22];
    struct sockaddr_in saddr;
    struct hostent *hp;
    struct in_addr iaddr;
    usgnd_int_16 port;
    socklen_t saddr_size = sizeof(saddr);
    size_t size;
    char *format = "> Emulator command line argument: +Mit %s\n";

#ifdef __clang_analyzer__
    /* CodeChecker does not seem to understand getsockname writes to saddr */
    memset(&saddr, 0, sizeof(saddr));
#endif
    if (getsockname(state->input.socket,
		    (struct sockaddr *) &saddr,
		    &saddr_size) != 0)
	goto error;
    
    port = ntohs(saddr.sin_port);

    ASSERT(state->input.listen_port == 0 || state->input.listen_port == port);

    state->input.listen_port = port;

    if (gethostname(hostname, sizeof(hostname)) != 0)
	goto error;

    hp = gethostbyname(hostname);
    if (!hp)
	goto error;

    if (hp->h_addr_list) {
	(void) memcpy(&iaddr.s_addr, *hp->h_addr_list, sizeof(iaddr.s_addr));
	(void) sprintf(carg, "%s:%d", inet_ntoa(iaddr), (int) port);
    }
    else
	(void) sprintf(carg, "127.0.0.1:%d", (int) port);

#if EMEM_d_SWITCH

    if (state->output.erl_cmd_file) {
	fprintf(state->output.erl_cmd_file, "+Mit %s\n", carg);
	fclose(state->output.erl_cmd_file);
	state->output.erl_cmd_file = NULL;
    }

#endif

    size = strlen(format) + strlen(carg);

    /* Get area */

    get_next_write_area(&area, state, &state->output.queue, size);

    area.size = sprintf(area.ptr, format, carg);

    /* Leave area */

    wrote_area(&area, state, &state->output.queue);

    return 0;

 error:
    return GET_SOCK_ERRNO();
}

static size_t
write_allocator_info(em_state *state, char *ptr)
{
    usgnd_int_32 aix, i, j;
    char *header =	"> Allocator information:\n";
    char *allctr_str =	">    * Allocator:";
    char *crr_prv_str =	">       * Carrier providers:";
    char *blk_tp_str =	">       * Block types:";
    char *line_prefix =	">         ";
    size_t size = 0;
    char **strings;
    size_t strings_size;
    size_t max_line_size = 80;
    char *p = ptr;
    char **pp = ptr ? &p : NULL;

    strings_size = state->trace_info.max_block_type_ix + 1;
    if (strings_size < state->trace_info.max_allocator_ix + 1)
	strings_size = state->trace_info.max_allocator_ix + 1;

    strings = (char **) (*state->alloc)((strings_size + 1)*sizeof(char *));
    if (!strings)
	error(ENOMEM);

    size += write_str(pp, header);

    for (aix = 0; aix <= state->trace_info.max_allocator_ix; aix++) {
	emtp_allocator *allctr = state->trace_info.allocator[aix];
	if (!allctr->valid)
	    continue;

	strings[0] = allctr->name;
	strings[1] = NULL;
	size += write_strings(pp,strings,allctr_str,line_prefix,max_line_size);

	i = 0;
	if (allctr->carrier.provider)
	    for (j = 0; j < allctr->carrier.no_providers; j++) {
		usgnd_int_32 cpix = allctr->carrier.provider[j];
		if (cpix == state->trace_info.segment_ix)
		    strings[i++] = "segment";
		else
		    strings[i++] = state->trace_info.allocator[cpix]->name;
	    }
	strings[i] = NULL;
	size += write_strings(pp,strings,crr_prv_str,line_prefix,max_line_size);
	
	i = 0;
	for (j = 0; j <= state->trace_info.max_block_type_ix; j++)
	    if (state->trace_info.block_type[j]->allocator == aix)
		strings[i++] = state->trace_info.block_type[j]->name;
	strings[i] = NULL;
	size += write_strings(pp,strings,blk_tp_str,line_prefix,max_line_size);
    }

    (*state->free)((void *) strings);

    return size;
}

static void
print_main_header(em_state *state)
{
#if HAVE_INT_64
#define MAX_WORD_SZ_STR "64"
#else
#define MAX_WORD_SZ_STR "32"
#endif
    em_area area = {NULL, 0};
    char *format1 =
	"> emem version:            " EMEM_VSN_STR "\n"
	"> Nodename:                %s\n"
	"> Hostname:                %s\n"
	"> Pid:                     %s\n"
	"> Start time (UTC):        ";
    char *format2 = "%4.4" USGND_INT_32_FSTR
	"-%2.2" USGND_INT_32_FSTR "-%2.2" USGND_INT_32_FSTR
	" %2.2" USGND_INT_32_FSTR ":%2.2" USGND_INT_32_FSTR
	":%2.2" USGND_INT_32_FSTR ".%6.6" USGND_INT_32_FSTR "\n";
    char *format3 =
	"> Trace parser version:    %" USGND_INT_32_FSTR ".%" USGND_INT_32_FSTR
	    "\n"
	"> Actual trace version:    %" USGND_INT_32_FSTR ".%" USGND_INT_32_FSTR
	    "\n"
	"> Maximum trace word size: " MAX_WORD_SZ_STR " bits\n"
	"> Actual trace word size:  %d bits\n";
    size_t size = (strlen(format1) +
		   (state->trace_info.start_time.month
		    ? (strlen(format2) + 7*10)
		    : 1)
		   + strlen(format3)
		   + strlen(state->trace_info.nodename)
		   + strlen(state->trace_info.hostname)
		   + strlen(state->trace_info.pid)
		   + 5*10 + 1);

    if (state->output.verbose) {
	size += write_allocator_info(state, NULL);
    }

    size += write_header(state, NULL, 0);

    /* Get area */
    get_next_write_area(&area, state, &state->output.queue, size);

    area.size = sprintf(area.ptr,
			format1,
			state->trace_info.nodename,
			state->trace_info.hostname,
			state->trace_info.pid);
    if (state->trace_info.start_time.month)
	area.size += sprintf(area.ptr + area.size,
			     format2,
			     state->trace_info.start_time.year,
			     state->trace_info.start_time.month,
			     state->trace_info.start_time.day,
			     state->trace_info.start_time.hour,
			     state->trace_info.start_time.minute,
			     state->trace_info.start_time.second,
			     state->trace_info.start_time.micro_second);
    else
	*(area.ptr + area.size++) = '\n';
    area.size += sprintf(area.ptr + area.size,
			 format3,
			 state->trace_info.version.parser.major,
			 state->trace_info.version.parser.minor,
			 state->trace_info.version.trace.major,
			 state->trace_info.version.trace.minor,
			 state->trace_info.bits);

    if (state->output.verbose) {
	area.size += write_allocator_info(state, area.ptr + area.size);
    }

    area.size += write_header(state, area.ptr + area.size, 0);

    /* Leave area */
    wrote_area(&area, state, &state->output.queue);
#undef MAX_WORD_SZ_STR
}

static void
print_main_footer(em_state *state)
{
    em_area area = {NULL, 0};
    char *p;
    int i;
    char *stop_str =
	"> Trace stopped\n";
    char *exit_str =
	"> Emulator exited with code: %" USGND_INT_32_FSTR "\n";
    char *format =
	"> Total trace size:          %" USGND_INT_MAX_FSTR " bytes\n"
	"> Average band width used:   %" USGND_INT_MAX_FSTR " Kbit/s\n";
    size_t size;
    usgnd_int_max tsz = state->input.total_trace_size;
    usgnd_int_32 secs = state->info.stop_time.secs;
    usgnd_int_32 usecs = state->info.stop_time.usecs;
    usgnd_int_max bw;

    /* Max size of the max value line. Each value can at most use 21
       characters: largest possible usgnd_int_64 (20 digits) and one
       white space. */
    size = state->output.values_per_line*21 + 1;

    switch (state->info.stop_reason) {
    case EMTP_STOP:
	size += strlen(stop_str) + 1;
	break;
    case EMTP_EXIT:
	size += strlen(exit_str);
	size += 10; /* Enough for one unsgn_int_32 */
	size++;
	break;
    default:
	break;
    }
    size += strlen(format);
    size += 2*20; /* Enough for two unsgn_int_64 */
    size += 2;

    bw = (tsz + 1023)/1024;
    bw *= 1000;
    bw /= secs*1000 + usecs/1000;
    bw *= 8;

    /* Get area */

    get_next_write_area(&area, state, &state->output.queue, size);

    p = area.ptr;

    p += sprintf(p, "> %-*s", EM_TIME_FIELD_WIDTH - 2, "Maximum:");
    
    if (state->output.total) {
	int six = state->trace_info.segment_ix;
	write_max_ever_mem_info(state, &p, &state->info.total);
	if (state->trace_info.have_segment_carrier_info) {
	    if (state->info.allctr_prv_crr[six])
		write_max_ever_mem_info(state,
					&p,
					state->info.allctr_prv_crr[six]);
	    if (state->info.allctr_usd_crr[six])
		write_max_ever_mem_info(state,
					&p,
					state->info.allctr_usd_crr[six]);
	}
    }
    for (i = 0; i < state->output.no_allctrs; i++) {
	int ix = state->output.allctrs[i].ix;
	write_max_ever_mem_info(state, &p, &state->info.allctr[ix]);
	if (state->info.allctr_prv_crr[ix])
	    write_max_ever_mem_info(state,
				    &p,
				    state->info.allctr_prv_crr[ix]);
	if (state->info.allctr_usd_crr[ix])
	    write_max_ever_mem_info(state,
				    &p,
				    state->info.allctr_usd_crr[ix]);
    }
    for (i = 0; i < state->output.no_btypes; i++)
	write_max_ever_mem_info(state,
				&p,
				&state->info.btype[state->output.btypes[i].ix]);

    p += sprintf(p, "\n");

    switch (state->info.stop_reason) {
    case EMTP_STOP:
	p += sprintf(p, "%s", stop_str);
	break;
    case EMTP_EXIT:
	p += sprintf(p, exit_str, state->info.exit_status);
	break;
    default:
	break;
    }

    p += sprintf(p, format, tsz, bw);

    area.size = p - area.ptr;

    ASSERT(area.size <= size);

    /* Leave area */

    wrote_area(&area, state, &state->output.queue);

}

static void
print_info(em_state *state, usgnd_int_32 secs, char *extra)
{
    char *p;
    int i;
    size_t size;
    em_area area = {NULL, 0};

    /* Get area */

    size = 0;
    if (!state->output.lines_until_header)
	size += state->output.header_size;

    /* Max size of one line of values. Each value can at most use 21
       characters: largest possible usgnd_int_64 (20 digits) and one white
       space. */
    size += state->output.values_per_line*21 + 1;

    if (extra)
	size += write_str(NULL, extra);

    get_next_write_area(&area, state, &state->output.queue, size);

    /* Write to area */

    p = area.ptr;

    if (!state->output.lines_until_header) {
	memcpy((void *) area.ptr,
	       (void *) state->output.header,
	       state->output.header_size);
	p += state->output.header_size;
	state->output.lines_until_header = EM_LINES_UNTIL_HEADER;
    }
    else
	state->output.lines_until_header--;
    
    p += sprintf(p, "%*" USGND_INT_32_FSTR " ", EM_TIME_FIELD_WIDTH - 1, secs);
    
    if (state->output.total) {
	int six = state->trace_info.segment_ix;
	write_mem_info(state, &p, &state->info.total);
	if (state->trace_info.have_segment_carrier_info) {
	    if (state->info.allctr_prv_crr[six])
		write_mem_info(state, &p, state->info.allctr_prv_crr[six]);
	    if (state->info.allctr_usd_crr[six])
		write_mem_info(state, &p, state->info.allctr_usd_crr[six]);
	}
    }
    for (i = 0; i < state->output.no_allctrs; i++) {
	int ix = state->output.allctrs[i].ix;
	write_mem_info(state, &p, &state->info.allctr[ix]);
	if (state->info.allctr_prv_crr[ix])
	    write_mem_info(state, &p, state->info.allctr_prv_crr[ix]);
	if (state->info.allctr_usd_crr[ix]) 
	    write_mem_info(state, &p, state->info.allctr_usd_crr[ix]);
    }
    for (i = 0; i < state->output.no_btypes; i++)
	write_mem_info(state,
		       &p,
		       &state->info.btype[state->output.btypes[i].ix]);

    p += sprintf(p, "\n");

    if (extra)
	p += write_str(&p, extra);

    ASSERT(area.size >= p - area.ptr);
    area.size = p - area.ptr;

    /* Leave area */

    wrote_area(&area, state, &state->output.queue);
}

static void
reset_mem_info(em_mem_info *mi)
{
    mi->size = 0;
    mi->min_size = 0;
    mi->max_size = 0;
    mi->max_ever_size = 0;
    mi->no = 0;
    mi->min_no = 0;
    mi->max_no = 0;
    mi->max_ever_no = 0;
    mi->allocs = 0;
    mi->reallocs = 0;
    mi->frees = 0;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * State creation and destruction                                          *
 *                                                                         *
\*                                                                         */

static void
destroy_state(em_state *state)
{
    int i;
    void (*freep)(void *);

    freep = state->free;

    if (state->block_table)
	emtbt_destroy_table(state->block_table);

    if (state->carrier_table) {
	for (i = -1; i <= state->trace_info.max_allocator_ix; i++)
	    if (state->carrier_table[i])
		emtbt_destroy_table(state->carrier_table[i]);
	state->carrier_table--;
	(*freep)((void *) state->carrier_table);
    }

    if (state->info.btype) {
	state->info.btype--;
	(*freep)((void *) state->info.btype);
    }

    if (state->info.allctr) {
	state->info.allctr--;
	(*freep)((void *) state->info.allctr);
    }

    if (state->info.allctr_prv_crr) {
	for (i = -1; i <= state->trace_info.max_allocator_ix; i++)
	    if (state->info.allctr_prv_crr[i])
		(*freep)((void *) state->info.allctr_prv_crr[i]);
	state->info.allctr_prv_crr--;
	(*freep)((void *) state->info.allctr_prv_crr);
    }


    if (state->info.allctr_usd_crr) {
	for (i = -1; i <= state->trace_info.max_allocator_ix; i++)
	    if (state->info.allctr_usd_crr[i])
		(*freep)((void *) state->info.allctr_usd_crr[i]);
	state->info.allctr_usd_crr--;
	(*freep)((void *) state->info.allctr_usd_crr);
    }

    emtp_state_destroy(state->trace_state);
    destroy_queue(state, &state->input.queue);

    if (state->output.btypes)
	(*freep)((void *) state->output.btypes);
    if (state->output.allctrs)
	(*freep)((void *) state->output.allctrs);
    destroy_queue(state, &state->output.queue);

#if EMEM_d_SWITCH

    if (state->output.go.mutex) {
	mutex_destroy(state->output.go.mutex);
	(*state->free)((void *) state->output.go.mutex);
	state->output.go.mutex = NULL;
    }
    if (state->output.go.cond) {
	cond_destroy(state->output.go.cond);
	(*state->free)((void *) state->output.go.cond);
	state->output.go.cond = NULL;
    }

#endif

    if (!IS_INVALID_SOCKET(state->input.socket)) {
	closesocket(state->input.socket);
        state->input.socket = INVALID_SOCKET;
    }

    (*freep)((void *) state);
}

static em_state *
new_state(void * (*alloc)(size_t),
	  void * (*realloc)(void *, size_t),
	  void   (*free)(void *))
{
    em_state *state = NULL;

    state = (*alloc)(sizeof(em_state));
    if (!state)
	goto error;

    /* Stuff that might fail (used after the error label) */

    state->trace_state = NULL;

    /* Init state ... */

    state->alloc = alloc;
    state->realloc = realloc;
    state->free = free;

    state->block_table = NULL;
    state->carrier_table = NULL;

    reset_mem_info(&state->info.total);
    state->info.btype = NULL;
    state->info.allctr = NULL;

    state->info.allctr_prv_crr = NULL;
    state->info.allctr_usd_crr = NULL;

    state->info.stop_time.secs = 0;
    state->info.stop_time.usecs = 0;
    state->info.stop_reason = EMTP_UNDEF;
    state->info.exit_status = 0;

    state->output.next_print = 0;
    state->output.next_print_inc = 10;
    state->output.header = NULL;
    state->output.header_size = 0;
    state->output.values_per_object = 0;
    state->output.values_per_line = 0;
    state->output.field_width = 11;
    state->output.verbose = 0;
    state->output.total = 0;
    state->output.all_allctrs = 0;
    state->output.no_allctrs = 0;
    state->output.allctrs = NULL;
    state->output.all_btypes = 0;
    state->output.no_btypes = 0;
    state->output.btypes = NULL;
    state->output.max_min_values = 0;
    state->output.block_counts = 0;
    state->output.op_counts = 0;
    state->output.lines_until_header = EM_LINES_UNTIL_HEADER;

#if PRINT_OPERATIONS
    state->output.stream = stderr;
#else
    state->output.stream = stdout;
#endif
    state->output.file_name = NULL;
#if EMEM_d_SWITCH
    state->output.dir_name = NULL;
    state->output.erl_cmd_file = NULL;
    state->output.go.mutex = NULL;
    state->output.go.cond = NULL;
#endif

    init_queue(state, &state->output.queue);
    state->output.queue.max_buf_size = 10*1024*1024;
    state->output.queue.name = "output";

    state->trace_state = emtp_state_new(alloc, realloc, free);
    if (!state->trace_state)
	goto error;

    state->trace_info.version.parser.major = 0;
    state->trace_info.version.parser.minor = 0;
    state->trace_info.version.trace.major = 0;
    state->trace_info.version.trace.minor = 0;
    state->trace_info.bits = 0;
    state->trace_info.max_allocator_ix = 0;
    state->trace_info.allocator = NULL;
    state->trace_info.max_block_type_ix = 0;
    state->trace_info.block_type = NULL;

    state->input.listen_port = 0;
    state->input.socket = INVALID_SOCKET;
    state->input.total_trace_size = 0;
    state->input.error = 0;
    state->input.error_descr = NULL;

    init_queue(state, &state->input.queue);
    state->input.queue.max_buf_size = 10*1024*1024;
    state->input.queue.name = "input";

    return state;

 error:
    if (state) {
	if (state->trace_state)
	    emtp_state_destroy(state->trace_state);
	(*free)(state);
    }
    return NULL;
}


static emtbt_table *
mk_block_table(em_state *state)
{
    return emtbt_new_table(state->trace_info.bits == 64,
			   state->alloc,
			   state->realloc,
			   state->free);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 *                                                                         *
 *                                                                         *
\*                                                                         */
#if PRINT_OPERATIONS
void print_op(em_state *state, emtp_operation *op);
#endif

static INLINE void
update_max_values(em_mem_info *mi)
{
    if (mi->max_size < mi->size)
	mi->max_size = mi->size;
    if (mi->max_no < mi->no)
	mi->max_no = mi->no;
}

static INLINE void
update_min_values(em_mem_info *mi)
{
    if (mi->min_size > mi->size)
	mi->min_size = mi->size;
    if (mi->min_no > mi->no)
	mi->min_no = mi->no;
}

static INLINE void
update_alloc_op(em_mem_info *mi, usgnd_int_max size)
{
    mi->allocs++;
    mi->size += size;
    mi->no++;
    update_max_values(mi);
}

static INLINE void
update_realloc_op(em_mem_info *mi,
		  usgnd_int_max size,
		  usgnd_int_max prev_size,
		  int no_change)
{
    mi->reallocs++;
    ASSERT(mi->size >= prev_size);
    mi->size -= prev_size;
    mi->size += size;
    if (no_change) {
	if (no_change > 0)
	    mi->no++;
	else {
	    ASSERT(mi->no > 0);
	    mi->no--;
	}
    }
    update_max_values(mi);
    update_min_values(mi);
}

static INLINE void
update_free_op(em_mem_info *mi, usgnd_int_max prev_size)
{
    mi->frees++;
    ASSERT(mi->size >= prev_size);
    mi->size -= prev_size;
    ASSERT(mi->no > 0);
    mi->no--;
    update_min_values(mi);
}

static int
insert_operations(em_state *state, emtp_operation ops[], size_t len)
{
    emtbt_table *crr_table;
    emtbt_block old_blk;
    usgnd_int_32 prev_size;
    usgnd_int_max size;
    size_t i;
    int res;
    int aix, btix, crrix;

    for (i = 0; i < len; i++) {

	while (state->output.next_print <= ops[i].time.secs) {
	    print_info(state, state->output.next_print, NULL);
	    state->output.next_print += state->output.next_print_inc;
	}

	switch (ops[i].type) {

	case EMTP_ALLOC:
#if PRINT_OPERATIONS
	    print_op(state, &ops[i]);
#endif
	    btix = (int) ops[i].u.block.type;
	    aix = state->trace_info.block_type[btix]->allocator;

	    if (!ops[i].u.block.new_ptr)
		continue;

	    res = emtbt_alloc_op(state->block_table, &ops[i]);
	    if (res != 0)
		ERR_RET(res);

	    size = ops[i].u.block.new_size;

	    update_alloc_op(&state->info.btype[btix], size);
	    update_alloc_op(&state->info.allctr[aix], size);
	    update_alloc_op(&state->info.total, size);
	    break;

	case EMTP_REALLOC: {
	    int no;
#if PRINT_OPERATIONS
	    print_op(state, &ops[i]);
#endif

	    res = emtbt_realloc_op(state->block_table, &ops[i], &old_blk);
	    if (res != 0)
		ERR_RET(res);

	    size = ops[i].u.block.new_size;
	    prev_size = old_blk.size;
	    
	    if (!ops[i].u.block.prev_ptr)
		btix = (int) ops[i].u.block.type;
	    else
		btix = (int) old_blk.type;
	    aix = state->trace_info.block_type[btix]->allocator;
	    
	    no = ((!old_blk.pointer && ops[i].u.block.new_ptr)
		  ? 1
		  : ((old_blk.pointer && !ops[i].u.block.new_size)
		     ? -1
		     : 0));

	    update_realloc_op(&state->info.btype[btix], size, prev_size, no);
	    update_realloc_op(&state->info.allctr[aix], size, prev_size, no);
	    update_realloc_op(&state->info.total, size, prev_size, no);

	    break;
	}
	case EMTP_FREE:
#if PRINT_OPERATIONS
	    print_op(state, &ops[i]);
#endif

	    if (!ops[i].u.block.prev_ptr)
		continue;

	    res = emtbt_free_op(state->block_table, &ops[i], &old_blk);
	    if (res != 0)
		ERR_RET(res);

	    prev_size = old_blk.size;
	    btix = (int) old_blk.type;
	    aix = state->trace_info.block_type[btix]->allocator;


	    update_free_op(&state->info.btype[btix], prev_size);
	    update_free_op(&state->info.allctr[aix], prev_size);
	    update_free_op(&state->info.total, prev_size);

	    break;

	case EMTP_CARRIER_ALLOC:
#if PRINT_OPERATIONS
	    print_op(state, &ops[i]);
#endif

	    aix = (int) ops[i].u.block.type;

	    crrix = (int) ops[i].u.block.carrier_type;
	    if (!state->carrier_table[crrix]) {
		state->carrier_table[crrix] = mk_block_table(state);
		if (!state->carrier_table[crrix])
		    ERR_RET(ENOMEM);
	    }
	    crr_table = state->carrier_table[crrix];

	    if (!ops[i].u.block.new_ptr)
		continue;

	    res = emtbt_alloc_op(crr_table, &ops[i]);
	    if (res != 0)
		ERR_RET(res);

	    size = ops[i].u.block.new_size;

	    if (state->info.allctr_usd_crr[aix])
		update_alloc_op(state->info.allctr_usd_crr[aix], size);
	    if (state->info.allctr_prv_crr[crrix])
		update_alloc_op(state->info.allctr_prv_crr[crrix], size);
	    update_alloc_op(&state->info.allctr[crrix], size);

	    break;

	case EMTP_CARRIER_REALLOC: {
	    int no;
#if PRINT_OPERATIONS
	    print_op(state, &ops[i]);
#endif

	    crrix = (int) ops[i].u.block.carrier_type;
	    if (!state->carrier_table[crrix]) {
		state->carrier_table[crrix] = mk_block_table(state);
		if (!state->carrier_table[crrix])
		    ERR_RET(ENOMEM);
	    }
	    crr_table = state->carrier_table[crrix];

	    res = emtbt_realloc_op(crr_table, &ops[i], &old_blk);
	    if (res != 0)
		ERR_RET(res);

	    size = ops[i].u.block.new_size;
	    prev_size = old_blk.size;
	    
	    if (!ops[i].u.block.prev_ptr)
		aix = (int) ops[i].u.block.type;
	    else
		aix = (int) old_blk.type;


	    no = ((!old_blk.pointer && ops[i].u.block.new_ptr)
		  ? 1
		  : ((old_blk.pointer && !ops[i].u.block.new_size)
		     ? -1
		     : 0));

	    if (state->info.allctr_usd_crr[aix])
		update_realloc_op(state->info.allctr_usd_crr[aix],
				  size,
				  prev_size,
				  no);
	    if (state->info.allctr_prv_crr[crrix])
		update_realloc_op(state->info.allctr_prv_crr[crrix],
				  size,
				  prev_size,
				  no);
	    update_realloc_op(&state->info.allctr[crrix],
			      size,
			      prev_size,
			      no);
	    break;
	}
	case EMTP_CARRIER_FREE:
#if PRINT_OPERATIONS
	    print_op(state, &ops[i]);
#endif

	    crrix = (int) ops[i].u.block.carrier_type;
	    crr_table = state->carrier_table[crrix];
	    if (!crr_table)
		ERR_RET(EMTBT_FREE_NOBLK_ERROR);

	    if (!ops[i].u.block.prev_ptr)
		continue;

	    res = emtbt_free_op(crr_table, &ops[i], &old_blk);
	    if (res != 0)
		ERR_RET(res);

	    prev_size = old_blk.size;
	    aix = (int) old_blk.type;

	    if (state->info.allctr_usd_crr[aix])
		update_free_op(state->info.allctr_usd_crr[aix], prev_size);
	    if (state->info.allctr_prv_crr[crrix])
		update_free_op(state->info.allctr_prv_crr[crrix], prev_size);
	    update_free_op(&state->info.allctr[crrix], prev_size);

	    break;

	case EMTP_STOP:
#if PRINT_OPERATIONS
	    print_op(state, &ops[i]);
#endif
	    state->info.stop_reason = EMTP_STOP;
	    state->info.stop_time.secs = ops[i].time.secs;
	    state->info.stop_time.usecs = ops[i].time.usecs;
	    print_info(state, ops[i].time.secs, NULL);
	    return EM_EXIT_RESULT;
	case EMTP_EXIT:
#if PRINT_OPERATIONS
	    print_op(state, &ops[i]);
#endif
	    state->info.stop_reason = EMTP_EXIT;
	    state->info.exit_status = ops[i].u.exit_status;
	    state->info.stop_time.secs = ops[i].time.secs;
	    state->info.stop_time.usecs = ops[i].time.usecs;
	    print_info(state, ops[i].time.secs, NULL);
	    return EM_EXIT_RESULT;
	default:
#if PRINT_OPERATIONS
	    print_op(state, &ops[i]);
#endif
	    /* Ignore not understood operation */
	    break;
	}
    }
    return 0;
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 *                                                                         *
 *                                                                         *
\*                                                                         */

static const char *
error_string(int error)
{
    const char *str;
    const char *error_str;
    static const char unknown_error[] = "Unknown error";
    
    error_str = unknown_error;

    if (error > 0) {
	const char *str = strerror(error);
	if (str)
	    error_str = str;
    }
    else if (error < 0) {
	str = emtp_error_string(error);
	if (!str) {
	    str = emtbt_error_string(error);
	    if (!str) {
		switch (error) {
		case EM_TRUNCATED_TRACE_ERROR:
		    error_str = "Truncated trace";
		    break;
		case EM_INTERNAL_ERROR:
		    error_str = "emem internal error";
		    break;
		default:
		    break;
		}
	    }
	}

	if (str)
	    error_str = str;
    }

    return error_str;
}

__decl_noreturn static void __noreturn
error(int res)
{
    error_msg(res, NULL);
}

__decl_noreturn static void __noreturn
error_msg(int res, char *msg)
{
    fprintf(stderr,
	    "emem: %s%sFatal error: %s (%d)\n",
	    msg ? msg : "",
	    msg ? ": ": "",
	    error_string(res),
	    res);
    exit(1);
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 *                                                                         *
 *                                                                         *
\*                                                                         */

#if EMEM_d_SWITCH

static size_t
write_output_filename(char *ptr,
		      char *dirname,
		      char *nodename,
		      char *hostname,
		      char *datetime,
		      char *pid)
{
    size_t sz = 0;
    char *p = ptr;
    char **pp = ptr ? &p : NULL;
    sz += write_str(pp, dirname);
    if (pp) *((*pp)++) = DIR_SEP_CHAR;
    sz++;
    sz += write_str(pp, nodename);
    sz += write_str(pp, "_");
    sz += write_str(pp, hostname);
    sz += write_str(pp, "_");
    sz += write_str(pp, datetime);
    sz += write_str(pp, "_");
    sz += write_str(pp, pid);
    sz += write_str(pp, EM_OUTPUT_FILE_SUFFIX);
    if (pp) *((*pp)++) = '\0';
    sz++;
    return sz;
}

static char *
make_output_filename(em_state *state)
{
    char *fname;
    size_t fname_size;
    char *nodename = state->trace_info.nodename;
    char *hostname = state->trace_info.hostname;
    char *pid = state->trace_info.pid;
    char dt_buf[20];
    char *date_time = NULL;

    if (*nodename == '\0')
	nodename = "nonode";
    if (*hostname == '\0')
	hostname = "nohost";
    if (!state->trace_info.start_time.day)
	date_time = "notime";
    else {
	sprintf(dt_buf,
		"%d-%2.2d-%2.2d_%2.2d.%2.2d.%2.2d",
		state->trace_info.start_time.year % 10000,
		state->trace_info.start_time.month % 100,
		state->trace_info.start_time.day % 100,
		state->trace_info.start_time.hour % 100,
		state->trace_info.start_time.minute % 100,
		state->trace_info.start_time.second % 100);
	date_time = &dt_buf[0];
    }
    if (*pid == '\0')
	pid = "nopid";

    fname = (*state->alloc)(write_output_filename(NULL,
						  state->output.dir_name,
						  nodename,
						  hostname,
						  date_time,
						  pid));
    if (!fname)
	return NULL;

    (void) write_output_filename(fname,
				 state->output.dir_name,
				 nodename,
				 hostname,
				 date_time,
				 pid);
    return fname;
}

#endif

static int
complete_state(em_state *state)
{
    int i, j, vpo, vpl;
    void * (*allocp)(size_t);
    void * (*reallocp)(void *, size_t);
    size_t size = sizeof(emtp_info);

    if (!emtp_get_info(&state->trace_info, &size, state->trace_state)
	|| size < sizeof(emtp_info))
	return EM_INTERNAL_ERROR;

#if EMEM_d_SWITCH

    if (!state->output.stream) {
	char *fname = make_filename(state);
	mutex_lock(state->output.go.mutex);
	state->output.stream = fopen(fname, "w");
	if (!state->output.stream) {
	    disconnect_queue_reader(&state->input.queue);
	    disconnect_queue_writer(&state->output.queue);
	}
	cond_signal(state->output.go.cond);
	mutex_unlock(state->output.go.mutex);
	(*state->free)((void *) fname);
	if (!state->output.stream)
	    return EIO;
    }

#endif

    allocp = state->alloc;
    reallocp = state->realloc;

    state->carrier_table = (*allocp)((state->trace_info.max_allocator_ix+2)
				     * sizeof(emtbt_table *));
    if (!state->carrier_table)
	return ENOMEM;
    state->carrier_table++;
    for (i = -1; i <= state->trace_info.max_allocator_ix; i++)
	state->carrier_table[i] = NULL;


    state->block_table = mk_block_table(state);
    state->info.btype = (*allocp)((state->trace_info.max_block_type_ix+2)
				  * sizeof(em_mem_info));
    state->info.allctr = (*allocp)((state->trace_info.max_allocator_ix+2)
				   * sizeof(em_mem_info));
    if (!state->block_table || !state->info.btype || !state->info.allctr)
	return ENOMEM;

    state->info.btype++;
    state->info.allctr++;

    state->info.allctr_prv_crr
	= (*allocp)((state->trace_info.max_allocator_ix+2)
		    * sizeof(em_mem_info *));
    if (!state->info.allctr_prv_crr)
	return ENOMEM;
    state->info.allctr_prv_crr++;
    for (i = -1; i <= state->trace_info.max_allocator_ix; i++)
	state->info.allctr_prv_crr[i] = NULL;

    state->info.allctr_usd_crr
	= (*allocp)((state->trace_info.max_allocator_ix+2)
		    * sizeof(em_mem_info *));
    if (!state->info.allctr_usd_crr)
	return ENOMEM;
    state->info.allctr_usd_crr++;
    for (i = -1; i <= state->trace_info.max_allocator_ix; i++)
	state->info.allctr_usd_crr[i] = NULL;

    if (state->output.all_btypes) {
	if (state->output.btypes)
	    (*state->free)((void *) state->output.btypes);
	state->output.no_btypes = state->trace_info.max_block_type_ix + 2;
	state->output.btypes = (*allocp)(state->output.no_btypes
					 * sizeof(em_output_types));
	if (!state->output.btypes)
	    return ENOMEM;
    }

    if (state->output.all_allctrs) {
	if (state->output.allctrs)
	    (*state->free)((void *) state->output.allctrs);
	state->output.no_allctrs = state->trace_info.max_allocator_ix + 2;
	state->output.allctrs = (*allocp)(state->output.no_allctrs
					  * sizeof(em_output_types));
	if (!state->output.allctrs)
	    return ENOMEM;
    }

    for (i = -1; i <= state->trace_info.max_block_type_ix; i++) {
	/* Save block type if we should print info about it */
	emtp_block_type *btp = state->trace_info.block_type[i];
	reset_mem_info(&state->info.btype[i]);
	if (state->output.no_btypes) {
	    if (state->output.all_btypes) {
		state->output.btypes[i+1].name = btp->name;
		state->output.btypes[i+1].ix = btp->valid ? i : -1;
	    }
	    else {
		for (j = 0; j < state->output.no_btypes; j++)
		    if (strcmp(btp->name, state->output.btypes[j].name) == 0) {
			state->output.btypes[j].ix = i;
			break;
		    }
	    }
	}
    }

    /* Remove invalid block types */
    if (state->output.no_btypes) {
	for (i = 0, j = 0; i < state->output.no_btypes; i++) {
	    if (state->output.btypes[i].ix >= 0) {
		state->output.btypes[j].name = state->output.btypes[i].name;
		state->output.btypes[j].ix = state->output.btypes[i].ix;
		j++;
	    }
	}
	state->output.no_btypes = j;
    }

    for (i = -1; i <= state->trace_info.max_allocator_ix; i++) {
	/* Save allocator if we should print info about it */
	emtp_allocator *ap = state->trace_info.allocator[i];
	reset_mem_info(&state->info.allctr[i]);
	if (state->output.no_allctrs) {
	    if (state->output.all_allctrs) {
		state->output.allctrs[i+1].name = ap->name;
		state->output.allctrs[i+1].ix = ap->valid ? i : -1;
	    }
	    else {
		for (j = 0; j < state->output.no_allctrs; j++)
		    if (strcmp(ap->name, state->output.allctrs[j].name) == 0) {
			state->output.allctrs[j].ix = i;
			break;
		    }
	    }
	}

	/* Allocate em_mem_info if used carrier info is available */
	if (ap->flags & EMTP_ALLOCATOR_FLAG_HAVE_USED_CARRIERS_INFO
	    || (i == state->trace_info.segment_ix
		&& state->trace_info.have_segment_carrier_info)) {		
	    state->info.allctr_usd_crr[i]
		= (em_mem_info *) (*allocp)(sizeof(em_mem_info));
	    if (!state->info.allctr_usd_crr[i])
		return ENOMEM;
	    reset_mem_info(state->info.allctr_usd_crr[i]);
	}

	/* Allocate em_mem_info for carrier providers */
	if (ap->carrier.provider) {
	    sgnd_int_32 j;
	    for (j = 0; j < ap->carrier.no_providers; j++) {
		sgnd_int_32 crr_prvdr = ap->carrier.provider[j];
		if (!state->info.allctr_prv_crr[crr_prvdr]) {
		    state->info.allctr_prv_crr[crr_prvdr]
			= (em_mem_info *) (*allocp)(sizeof(em_mem_info));
		    if (!state->info.allctr_prv_crr[crr_prvdr])
			return ENOMEM;
		    reset_mem_info(state->info.allctr_prv_crr[crr_prvdr]);
		}
	    }
	}
    }

    /* Remove invalid allocators */
    if (state->output.no_allctrs) {
	for (i = 0, j = 0; i < state->output.no_allctrs; i++) {
	    if (state->output.allctrs[i].ix >= 0) {
		state->output.allctrs[j].name = state->output.allctrs[i].name;
		state->output.allctrs[j].ix = state->output.allctrs[i].ix;
		j++;
	    }
	}
	state->output.no_allctrs = j;
    }

    if (state->output.no_btypes) {
	state->output.btypes = (*reallocp)(state->output.btypes,
					   sizeof(em_output_types)
					   * state->output.no_btypes);
	if (!state->output.btypes)
	    return ENOMEM;
    }

    if (state->output.no_allctrs) {
	state->output.allctrs = (*reallocp)(state->output.allctrs,
					    sizeof(em_output_types)
					    * state->output.no_allctrs);
	if (!state->output.allctrs)
	    return ENOMEM;
    }


    vpo = 1;
    if (state->output.max_min_values)
	vpo += 2;
    if (state->output.block_counts) {
	vpo++;
	if (state->output.max_min_values)
	    vpo += 2;
    }
    if (state->output.op_counts)
	vpo += 3;

    state->output.values_per_object = vpo;

    vpl = 0;
    vpl++;					/* time */
    if (state->output.total) {
	vpl += vpo;				/* total allocated */
	if (state->trace_info.have_segment_carrier_info) {
	    vpl += vpo;				/* total carriers */
	    vpl += vpo;				/* cached carriers */
	}
    }
    for (i = 0; i < state->output.no_allctrs; i++) {
	vpl += vpo;				/* allocated */
	if (state->trace_info.have_carrier_info) {
	    if (state->info.allctr_prv_crr[state->output.allctrs[i].ix])
		vpl += vpo;			/* provided carriers */
	    vpl += vpo;				/* used carriers */
	}
    }
    vpl += state->output.no_btypes*vpo;		/* allocated */

    state->output.values_per_line = vpl;

    state->output.header_size = write_header(state, NULL, 1);
    state->output.header = (*state->alloc)(state->output.header_size + 1);
    if (!state->output.header)
	return ENOMEM;
    size = write_header(state, state->output.header, 1);
    ASSERT(state->output.header_size == size);
    return 0;
}

static int
process_trace(em_state *state)
{
    emtp_operation ops[EM_NO_OF_OPS];
    int res;
    size_t ops_len;
    em_area area;

    while (1) {
	get_next_read_area(&area, state, &state->input.queue);
	if (!area.size)
	    return EM_TRUNCATED_TRACE_ERROR;
	res = emtp_parse(state->trace_state,
			 (usgnd_int_8 **)&area.ptr, &area.size,
			 NULL, 0, NULL);
	if (res == EMTP_HEADER_PARSED)
	    break;
	if (res == EMTP_NEED_MORE_TRACE)
	    continue;

	if (res < 0)
	    return res;
	else
	    return EM_TRUNCATED_TRACE_ERROR;
    }
	
    res = complete_state(state);
    if (res != 0)
	return res;

    print_main_header(state);

    while (1) {
	if (!area.size) {
	    get_next_read_area(&area, state, &state->input.queue);
	    if (!area.size)
		return EM_TRUNCATED_TRACE_ERROR;

	}


	while (area.size) {
	    ops_len = EM_NO_OF_OPS;
	    res = emtp_parse(state->trace_state,
			     (usgnd_int_8 **)&area.ptr, &area.size,
			     ops, sizeof(emtp_operation), &ops_len);
	    if (res < 0)
		return res;

	    res = insert_operations(state, ops, ops_len);
	    if (res != 0)
		return res;

	}

    }

}

static void
usage(char *sw, char *error)
{
    FILE *filep = stdout;
#ifdef __WIN32__
#define SW_CHAR "/"
#else
#define SW_CHAR "-"
#endif

    if (error) {
	ASSERT(sw);
	filep = stderr;
	fprintf(filep, "emem: %s: %s\n", sw, error);
    }
    fprintf(filep,
	    "Usage: emem "
#if EMEM_A_SWITCH
	    "[" SW_CHAR "A <ALLOCATOR>] "
#endif
	    "[" SW_CHAR "a <ALLOCATOR>] "
	    "[" SW_CHAR "b <BLOCK TYPE>] "
#if EMEM_C_SWITCH
	    "[" SW_CHAR "C <CLASS>] "
#endif
#if EMEM_c_SWITCH
	    "[" SW_CHAR "c <CLASS>] "
#endif
	    "{"
#if EMEM_d_SWITCH
	    SW_CHAR "d <DIRNAME>|"
#endif
	    SW_CHAR "f <FILENAME>} "
	    "[" SW_CHAR "h] "
	    "[" SW_CHAR "i <SECONDS>] "
	    "[" SW_CHAR "m] "
	    "[" SW_CHAR "n] "
	    "[" SW_CHAR "o] "
	    "{" SW_CHAR "p <PORT>} "
	    "[" SW_CHAR "t] "
	    "[" SW_CHAR "v] "
	    "\n");
    if (error)
	exit(1);
    else {
	fprintf(filep, 
	    "\n"
	    "  []        - switch is allowed any number of times\n"
	    "  {}        - switch is allowed at most one time\n"
#if EMEM_d_SWITCH
	    "  |         - exclusive or\n"
#endif
	    "\n"
	    " Switches:\n"
#if EMEM_A_SWITCH
	    "  " SW_CHAR "a <A>    - display info about Allocator <A> and all block types using <A>\n"
#endif
	    "  " SW_CHAR "a <A>    - display info about allocator <A>\n"
	    "  " SW_CHAR "b <B>    - display info about block type <B>\n"
#if EMEM_C_SWITCH
	    "  " SW_CHAR "C <C>    - display info about class <C> and all block types in class <C>\n"
#endif
#if EMEM_c_SWITCH
	    "  " SW_CHAR "b <B>    - display info about class <C>\n"
#endif
#if EMEM_d_SWITCH
	    "  " SW_CHAR "d <D>    - run as daemon and set output directory to <D>\n"
#endif
	    "  " SW_CHAR "f <F>    - set output file to <F>\n"
	    "  " SW_CHAR "h        - display help and exit\n"
	    "  " SW_CHAR "i <I>    - set display interval to <I> seconds\n"
	    "  " SW_CHAR "m        - display max/min values\n"
	    "  " SW_CHAR "n        - display block/carrier/segment count values\n"
	    "  " SW_CHAR "o        - display operation count values\n"
	    "  " SW_CHAR "p <P>    - set listen port to <P>\n"
	    "  " SW_CHAR "t        - display info about total values\n"
	    "  " SW_CHAR "v        - verbose output\n");
	exit(0);
    }

#undef SW_CHAR
}


static void
parse_args(em_state *state, int argc, char *argv[])
{
    int port;
    int i;

    i = 1;
    while (i < argc) {
	if ((argv[i][0] != '-' && argv[i][0] != '/') || argv[i][2] != '\0') {
	unknown_switch:
	    usage(argv[i], "unknown switch");
	}

	switch (argv[i][1]) {
#if EMEM_A_SWITCH
	case 'A': /* TODO: Allocator + blocktypes using allocator */
#endif
	case 'a':
	    if (i + 1 >= argc)
		usage(argv[i], "missing allocator");
	    i++;
	    if (state->output.all_allctrs || strcmp(argv[i],"all") == 0) {
		state->output.all_allctrs = 1;
		break;
	    }

	    if (!state->output.allctrs)
		state->output.allctrs
		    = (*state->alloc)(sizeof(em_output_types)*argc/2);
	    if (!state->output.allctrs)
		error(ENOMEM);
	    state->output.allctrs[state->output.no_allctrs].name = argv[i];
	    state->output.allctrs[state->output.no_allctrs].ix = -1;
	    state->output.no_allctrs++;
	    break;
	case 'b':
	    if (i + 1 >= argc)
		usage(argv[i], "missing block type");
	    i++;
	    if (state->output.all_btypes || strcmp(argv[i],"all") == 0) {
		state->output.all_btypes = 1;
		break;
	    }

	    if (!state->output.btypes)
		state->output.btypes
		    = (*state->alloc)(sizeof(em_output_types)*argc/2);
	    if (!state->output.btypes)
		error(ENOMEM);
	    state->output.btypes[state->output.no_btypes].name = argv[i];
	    state->output.btypes[state->output.no_btypes].ix = -1;
	    state->output.no_btypes++;
	    break;
#if EMEM_C_SWITCH
#endif
#if EMEM_c_SWITCH
	case 'c':
	    break;
#endif
#if EMEM_d_SWITCH
	case 'd': {
	    char *p;
	    char *fname;
	    if (state->output.dir_name)
		usage(argv[i], "directory already set");
	    if (state->output.file_name)
		usage(argv[i], "file name already set");
	    if (i + 1 >= argc)
		usage(argv[i], "missing directory name");
	    state->output.dir_name = argv[i+1];
	    fname = (*state->alloc)(strlen(state->output.dir_name)
				    + 1
				    + strlen(EM_ERL_CMD_FILE_NAME)
				    + 1);
	    state->output.go.mutex = (*state->alloc)(sizeof(ethr_mutex));
	    state->output.go.cond = (*state->alloc)(sizeof(ethr_cond));
	    if (!fname || !state->output.go.mutex || !state->output.go.cond)
		error(ENOMEM);
	    p = fname;
	    (void) write_str(&p, state->output.dir_name);
	    *(p++) = DIR_SEP_CHAR;
	    (void) write_str(&p, EM_ERL_CMD_FILE_NAME);
	    *(p++) = '\0';
	    state->output.erl_cmd_file = fopen(fname, "w");
	    if (!state->output.erl_cmd_file)
		usage(argv[i], "cannot create files in directory");
	    (*state->free)((void *) fname);
	    state->output.stream = NULL;
	    mutex_init(state->output.go.mutex);
	    cond_init(state->output.go.cond);
	    i++;
	    break;
	}
#endif
	case 'f':
#if EMEM_d_SWITCH
	    if (state->output.dir_name)
		usage(argv[i], "directory already set");
#endif
	    if (state->output.file_name)
		usage(argv[i], "file name already set");
	    if (i + 1 >= argc)
		usage(argv[i], "missing file name");
	    state->output.file_name = argv[i+1];
	    state->output.stream = fopen(state->output.file_name, "w");
	    if (!state->output.stream)
		usage(argv[i], "cannot create file");
	    if (setvbuf(state->output.stream, NULL, _IONBF, 0) != 0) {
		fprintf(stderr,
			"emem: failed to set file %s in unbuffered mode\n",
			state->output.file_name);
		exit(1);
	    }
	    i++;
	    break;
	case 'h':
	    usage(NULL, NULL);
	    break;
	case 'i': {
	    int interval;
	    if (argv[i][2])
		goto unknown_switch;

	    if (i + 1 >= argc)
		usage(argv[i], "missing interval");
	    interval = atoi(argv[i+1]);
	    if (interval < 1) 
		usage(argv[i], "bad interval");
	    i++;
	    state->output.next_print_inc = interval;
	    break;
	}
	case 'm':
	    state->output.max_min_values = 1;
	    break;
	case 'n':
	    state->output.block_counts = 1;
	    break;
	case 'o':
	    state->output.op_counts = 1;
	    break;
	case 'p':
	    if (state->input.listen_port)
		usage(argv[i], "port already set");
	    if (i + 1 >= argc)
		usage(argv[i], "missing port number");
	    port = atoi(argv[i+1]);
	    if (port <= 1024 || port >= (1 << 16)) 
		usage(argv[i], "bad port number");
	    i++;
	    state->input.listen_port = (usgnd_int_16) port;
	    break;
	case 't':
	    state->output.total = 1;
	    break;
	case 'v':
	    state->output.verbose = 1;
	    break;
	default:
	    goto unknown_switch;
	}
	i++;
    }

    if (!state->output.allctrs && !state->output.btypes)
	state->output.total = 1;
}

static int
init_connection(em_state *state)
{
    int res;
    SOCKET lsock;
    SOCKET sock = INVALID_SOCKET;
    struct sockaddr_in my_addr;
    socklen_t oth_addr_len;
    struct sockaddr_in oth_addr;
#ifdef __WIN32__
    WORD wVersionRequested = MAKEWORD(2,0);
    WSADATA wsaData;

    if (WSAStartup(wVersionRequested, &wsaData) != 0)
	return EIO;

    if ((LOBYTE(wsaData.wVersion) != 2) || (HIBYTE(wsaData.wVersion) != 0))
	return EIO;
#endif

 do_socket:
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (IS_INVALID_SOCKET(sock)) {
	res = GET_SOCK_ERRNO();
	if (res == EINTR)
	    goto do_socket;
	goto error;
    }

    memset((void *) &my_addr, 0, sizeof(struct sockaddr_in));

    my_addr.sin_family = AF_INET;
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    my_addr.sin_port = htons(state->input.listen_port);

 do_bind:
    if (bind(sock, (struct sockaddr*) &my_addr, sizeof(my_addr)) < 0) {
	res = GET_SOCK_ERRNO();
	if (res == EINTR)
	    goto do_bind;
	goto error;
    }

 do_listen:
    if (listen(sock, 1) < 0) {
	res = GET_SOCK_ERRNO();
	if (res == EINTR)
	    goto do_listen;
	goto error;
    }

    lsock = sock;
    state->input.socket = sock;

    res = print_emu_arg(state);
    if (res != 0)
	goto error;

    print_string(state, "> Waiting for emulator to connect... ");

 do_accept:
    oth_addr_len = sizeof(oth_addr);
    sock = accept(lsock, (struct sockaddr *) &oth_addr, &oth_addr_len);
    if (IS_INVALID_SOCKET(sock)) {
	res = GET_SOCK_ERRNO();
	if (res == EINTR)
	    goto do_accept;
	sock = lsock;
	goto error;
    }

    print_string(state, "connected\n");

    closesocket(lsock);
    state->input.socket = sock;

    return 0;

 error:
    if (!IS_INVALID_SOCKET(sock)) {
	closesocket(sock);
	state->input.socket = INVALID_SOCKET;
    }
    return res;
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * IO threads                                                              *
 *                                                                         *
\*                                                                         */

/*
 * The input thread reads from a socket and puts the received data
 * in the input buffer queue.
 *
 * Note: There is intentionally no flow control. If the emem program
 *       cannot process data as fast as it arrives, it is supposed
 *       to crash when hitting the maximum buffersize; otherwise,
 *       the traced emulator would be slowed down.
 */
static void *
input_thread_func(void *arg)
{
    int res;
    char *edescr = NULL;
    ssize_t recv_sz;
    usgnd_int_max total_trace_size = 0;
    em_state *state = (em_state *) arg;
    em_area area = {NULL, 0};
    SOCKET sock = state->input.socket;
    em_buf_queue *queue = &state->input.queue;

    while(1) {
	get_next_write_area(&area,
			    state,
			    queue,
			    EM_MIN_TRACE_READ_SIZE);

	if (!area.ptr) {
	    res = ENOMEM;
	    edescr = "Input alloc";
	    goto stop;
	}

    do_recv:
	if (is_queue_reader_disconnected(queue)) {
	    res = 0;
	    edescr = "Input";
	    goto stop;
	}
	recv_sz = recv(sock, (void *) area.ptr, area.size, 0);
	if (recv_sz <= 0) {
	    res = GET_SOCK_ERRNO();
	    if (res == EINTR)
		goto do_recv;
	    edescr = "Input recv";
	    goto stop;
	}
	area.size = (size_t) recv_sz;
	total_trace_size += (usgnd_int_max) recv_sz;
    }

 stop:
    state->input.error = res;
    state->input.error_descr = edescr;
    state->input.total_trace_size = total_trace_size;
    disconnect_queue_writer(queue);
    if (!IS_INVALID_SOCKET(state->input.socket)) {
	closesocket(sock);
	state->input.socket = INVALID_SOCKET;
    }
    return NULL;
}


static void *
output_thread_func(void *arg)
{
    em_state *state = (em_state *) arg;
    em_area area = {NULL, 0};

#if EMEM_d_SWITCH

    if (state->output.go.mutex) {
	mutex_lock(state->output.go.mutex);
	while (!state->output.stream
	       && !is_queue_writer_disconnected(&state->output.queue))
	    cond_wait(state->output.go.cond, state->output.go.mutex);
	mutex_unlock(state->output.go.mutex);

	mutex_destroy(state->output.go.mutex);
	(*state->free)((void *) state->output.go.mutex);
	state->output.go.mutex = NULL;
	cond_destroy(state->output.go.cond);
	(*state->free)((void *) state->output.go.cond);
	state->output.go.cond = NULL;
    }

#endif

    while (1) {
	get_next_read_area(&area, state, &state->output.queue);
	if (!area.size) {
	    disconnect_queue_reader(&state->output.queue);
	    if (is_queue_writer_disconnected(&state->output.queue))
		goto stop;
	    else
		error_msg(EIO, "Output queue");
	}
	if (fwrite((void *) area.ptr,
		   sizeof(char),
		   area.size,
		   state->output.stream) != area.size) {
	    disconnect_queue_reader(&state->output.queue);
	    error_msg(0, "Write");
	}
    }

 stop:
    if (state->output.stream != stdout && state->output.stream != stderr)
	fclose(state->output.stream);
    return NULL;
}


int
main(int argc, char *argv[])
{
    int res, ires, jres;
    ethr_tid input_tid;
    ethr_tid output_tid;
    em_state *state;

    /* set stdout in unbuffered mode */
    if (setvbuf(stdout, NULL, _IONBF, 0) != 0) {
	fprintf(stderr, "emem: failed to set stdout in unbuffered mode\n");
	exit(1);
    }

    if (ethr_init(NULL) != 0 || ethr_late_init(NULL) != 0) {
	fprintf(stderr, "emem: failed to initialize thread package\n");
	exit(1);
    }
	
    state = new_state(malloc, realloc, free);
    if (!state)
	error(ENOMEM);

    parse_args(state, argc, argv);

    res = ethr_thr_create(&output_tid,
			  output_thread_func,
			  (void *) state,
			  NULL);
    if (res != 0)
	error_msg(res, "Output thread create");

#ifdef DEBUG
    print_string(state, "> [debug]\n");
#endif

    res = init_connection(state);
    if (res != 0)
	error_msg(res, "Initialize connection");

    res = ethr_thr_create(&input_tid,
			  input_thread_func,
			  (void *) state,
			  NULL);
    if (res != 0)
	error_msg(res, "Input thread create");

    res = process_trace(state);

    disconnect_queue_reader(&state->input.queue);

    jres = ethr_thr_join(input_tid, NULL);
    if (jres != 0)
	error_msg(jres, "Input thread join");

    if (res == EM_EXIT_RESULT)
	print_main_footer(state);
    disconnect_queue_writer(&state->output.queue);

    jres = ethr_thr_join(output_tid, NULL);
    if (jres != 0)
	error_msg(jres, "Output thread join");

    ires = state->input.error;

    destroy_state(state);

#ifdef __WIN32__
    WSACleanup();
#endif

    switch (res) {
    case EM_EXIT_RESULT:
	break;
    case EM_TRUNCATED_TRACE_ERROR:
	error_msg(ires, state->input.error_descr);
	break;
    default:
	error(res);
	break;
    }

    return 0;
}


#if PRINT_OPERATIONS
void
print_op(em_state *state, emtp_operation *op)
{

#if 0
    printf("%5" USGND_INT_32_FSTR ":%6.6" USGND_INT_32_FSTR " ",
	   op->time.secs, op->time.usecs);
#endif
    if (state->trace_info.version.parser.major >= 2) {

	switch (op->type) {
	case EMTP_ALLOC:
	    printf(" %" USGND_INT_MAX_FSTR " = alloc(%" USGND_INT_16_FSTR
		   ", %" USGND_INT_MAX_FSTR ")\n",
		   op->u.block.new_ptr,
		   op->u.block.type,
		   op->u.block.new_size);
	    break;
	case EMTP_REALLOC:
	    printf(" %" USGND_INT_MAX_FSTR " = realloc(%" USGND_INT_16_FSTR
		   ", %" USGND_INT_MAX_FSTR ", %" USGND_INT_MAX_FSTR ")\n",
		   op->u.block.new_ptr,
		   op->u.block.type,
		   op->u.block.prev_ptr,
		   op->u.block.new_size);
	    break;
	case EMTP_FREE:
	    printf(" free(%" USGND_INT_16_FSTR ", %" USGND_INT_MAX_FSTR ")"
		   "\n",
		   op->u.block.type,
		   op->u.block.prev_ptr);
	    break;
	case EMTP_CARRIER_ALLOC:
	    printf(" %" USGND_INT_MAX_FSTR " = carrier_alloc(%"
		   USGND_INT_16_FSTR ", %" USGND_INT_16_FSTR ", %"
		   USGND_INT_MAX_FSTR ")\n",
		   op->u.block.new_ptr,
		   op->u.block.carrier_type,
		   op->u.block.type,
		   op->u.block.new_size);
	    break;
	case EMTP_CARRIER_REALLOC:
	    printf(" %" USGND_INT_MAX_FSTR " = carrier_realloc(%"
		   USGND_INT_16_FSTR ", %" USGND_INT_16_FSTR ", %"
		   USGND_INT_MAX_FSTR ", %" USGND_INT_MAX_FSTR ")\n",
		   op->u.block.new_ptr,
		   op->u.block.carrier_type,
		   op->u.block.type,
		   op->u.block.prev_ptr,
		   op->u.block.new_size);
	case EMTP_CARRIER_FREE:
	    printf(" carrier_free(%" USGND_INT_16_FSTR ", %" USGND_INT_16_FSTR
		   ", %" USGND_INT_MAX_FSTR ")\n",
		   op->u.block.carrier_type,
		   op->u.block.type,
		   op->u.block.prev_ptr);
	    break;
	default:
	    printf(" op = %d\n", op->type);
	    break;
	}

    }
    else {

	switch (op->type) {
	case EMTP_ALLOC:
	    printf(" %" USGND_INT_MAX_FSTR " = alloc(%" USGND_INT_MAX_FSTR ")"
		   "\n",
		   op->u.block.new_ptr,
		   op->u.block.new_size);
	    break;
	case EMTP_REALLOC:
	    printf(" %" USGND_INT_MAX_FSTR " = realloc(%" USGND_INT_MAX_FSTR
		   ", %" USGND_INT_MAX_FSTR ")\n",
		   op->u.block.new_ptr,
		   op->u.block.prev_ptr,
		   op->u.block.new_size);
	    break;
	case EMTP_FREE:
	    printf(" free(%" USGND_INT_MAX_FSTR ")\n",
		   op->u.block.prev_ptr);
	    break;
	default:
	    printf(" op = %d\n", op->type);
	    break;
	}
    }
    fflush(stdout);
}
#endif
