/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 1996-2020. 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%
 */
/* This File contains functions which are called if a user hits ^C */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "sys.h"
#include "erl_vm.h"
#include "global.h"
#include "erl_process.h"
#include "version.h"
#include "error.h"
#include "version.h"
#include "erl_db.h"
#include "bif.h"
#include "erl_version.h"
#include "hash.h"
#include "atom.h"
#include "beam_load.h"
#include "erl_hl_timer.h"
#include "erl_thr_progress.h"
#include "erl_proc_sig_queue.h"
#include "dist.h"

/* Forward declarations -- should really appear somewhere else */
static void process_killer(void);
void do_break(void);
void erl_crash_dump_v(char *file, int line, char* fmt, va_list args);

#ifdef DEBUG
static void bin_check(void);
#endif

static void print_garb_info(fmtfn_t to, void *to_arg, Process* p);
#ifdef OPPROF
static void dump_frequencies(void);
#endif

static void dump_attributes(fmtfn_t to, void *to_arg, byte* ptr, int size);

extern char* erts_system_version[];

#define WRITE_BUFFER_SIZE (64*1024)

static void
port_info(fmtfn_t to, void *to_arg)
{
    int i, max = erts_ptab_max(&erts_port);
    for (i = 0; i < max; i++) {
	Port *p = erts_pix2port(i);
	if (p)
	    print_port_info(p, to, to_arg);
    }
}

void
process_info(fmtfn_t to, void *to_arg)
{
    ErtsSchedulerData *esdp = erts_get_scheduler_data();
    int i, max = erts_ptab_max(&erts_proc);
    for (i = 0; i < max; i++) {
	Process *p = erts_pix2proc(i);
	if (p && p->i != ENULL) {
	    /* Do not include processes with no heap,
	     * they are most likely just created and has invalid data
	     */
	    if (p->heap != NULL) {
                ErtsProcLocks locks = ((esdp && (p == esdp->current_process ||
                                                 p == esdp->free_process))
                                       ? ERTS_PROC_LOCK_MAIN : 0);
		print_process_info(to, to_arg, p, locks);
            }
	}
    }

    /* Look for FREE processes in the run-queues and dist entries.
       These have been removed from the ptab but we still want them
       in the crash dump for debugging. */

    /* First loop through all run-queues */
    for (i = 0; i < erts_no_schedulers + ERTS_NUM_DIRTY_RUNQS; i++) {
        ErtsRunQueue *rq = ERTS_RUNQ_IX(i);
        int j;
        for (j = 0; j < ERTS_NO_PROC_PRIO_QUEUES; j++) {
            Process *p = rq->procs.prio[j].first;
            while (p) {
                if (ERTS_PSFLG_FREE & erts_atomic32_read_acqb(&p->state))
                    print_process_info(to, to_arg, p, 0);
                p = p->next;
            }
        }
    }

    /* Then check all dist entries */
    erts_dist_print_procs_suspended_on_de(to, to_arg);

    port_info(to, to_arg);
}

static void
process_killer(void)
{
    int i, j, max = erts_ptab_max(&erts_proc);
    Process* rp;

    erts_printf("\n\nProcess Information\n\n");
    erts_printf("--------------------------------------------------\n");
    for (i = max-1; i >= 0; i--) {
	rp = erts_pix2proc(i);
	if (rp && rp->i != ENULL) {
	    int br;
	    print_process_info(ERTS_PRINT_STDOUT, NULL, rp, 0);
	    erts_printf("(k)ill (n)ext (r)eturn:\n");
	    while(1) {
		if ((j = sys_get_key(0)) <= 0)
		    erts_exit(0, "");
		switch(j) {
		case 'k':
                    ASSERT(erts_init_process_id != ERTS_INVALID_PID);
                    /* Send a 'kill' exit signal from init process */
                    erts_proc_sig_send_exit(NULL, erts_init_process_id,
                                            rp->common.id, am_kill, NIL,
                                            0);
		case 'n': br = 1; break;
		case 'r': return;
		default: return;
		}
		if (br == 1) break;
	    }
	}
    }
}

typedef struct {
    int is_first;
    fmtfn_t to;
    void *to_arg;
} PrintMonitorContext;

static int doit_print_link(ErtsLink *lnk, void *vpcontext, Sint reds)
{
    PrintMonitorContext *pcontext = vpcontext;
    fmtfn_t to = pcontext->to;
    void *to_arg = pcontext->to_arg;

    if (pcontext->is_first) {
	pcontext->is_first = 0;
	erts_print(to, to_arg, "%T", lnk->other.item);
    } else {
	erts_print(to, to_arg, ", %T", lnk->other.item);
    }
    return 1;
}
    

static int doit_print_monitor(ErtsMonitor *mon, void *vpcontext, Sint reds)
{
    ErtsMonitorData *mdp;
    PrintMonitorContext *pcontext = vpcontext;
    fmtfn_t to = pcontext->to;
    void *to_arg = pcontext->to_arg;
    char *prefix = ", ";
 
    mdp = erts_monitor_to_data(mon);
    switch (mon->type) {
    case ERTS_MON_TYPE_PROC:
    case ERTS_MON_TYPE_PORT:
    case ERTS_MON_TYPE_TIME_OFFSET:
    case ERTS_MON_TYPE_DIST_PROC:
    case ERTS_MON_TYPE_RESOURCE:
    case ERTS_MON_TYPE_NODE:

        if (pcontext->is_first) {
            pcontext->is_first = 0;
            prefix = "";
        }

        if (erts_monitor_is_target(mon)) {
            if (mon->type != ERTS_MON_TYPE_RESOURCE)
                erts_print(to, to_arg, "%s{from,%T,%T}", prefix, mon->other.item, mdp->ref);
            else {
                ErtsResource* rsrc = mon->other.ptr;
                erts_print(to, to_arg, "%s{from,{%T,%T},%T}", prefix, rsrc->type->module,
                           rsrc->type->name, mdp->ref);
            }
        }
        else {
            if (!(mon->flags & ERTS_ML_FLG_NAME))
                erts_print(to, to_arg, "%s{to,%T,%T}", prefix, mon->other.item, mdp->ref);
            else {
                ErtsMonitorDataExtended *mdep = (ErtsMonitorDataExtended *) mdp;
                Eterm node;
                if (mdep->dist)
                    node = mdep->dist->nodename;
                else
                    node = erts_this_dist_entry->sysname;
                erts_print(to, to_arg, "%s{to,{%T,%T},%T}", prefix, mdep->u.name,
                           node, mdp->ref);
            }
        }

        break;

    default:
        /* ignore other monitors... */
        break;
    }
    return 1;
}

/* Display info about an individual Erlang process */
void
print_process_info(fmtfn_t to, void *to_arg, Process *p, ErtsProcLocks orig_locks)
{
    int garbing = 0;
    int running = 0;
    int exiting = 0;
    Sint len;
    struct saved_calls *scb;
    erts_aint32_t state;
    ErtsProcLocks locks = orig_locks;

    /* display the PID */
    erts_print(to, to_arg, "=proc:%T\n", p->common.id);

    /* Display the state */
    erts_print(to, to_arg, "State: ");

    state = erts_atomic32_read_acqb(&p->state);
    erts_dump_process_state(to, to_arg, state);
    if (state & ERTS_PSFLG_GC) {
        garbing = 1;
        running = 1;
    } else if (state & (ERTS_PSFLG_RUNNING
			| ERTS_PSFLG_DIRTY_RUNNING))
        running = 1;

    if (state & ERTS_PSFLG_EXITING)
        exiting = 1;

    if (!(locks & ERTS_PROC_LOCK_MAIN)) {
        locks |= ERTS_PROC_LOCK_MAIN;
        if (ERTS_IS_CRASH_DUMPING) {
            if (erts_proc_trylock(p, locks)) {
                /* crash dumping and main lock taken, this probably means that
                   the process is doing a GC on a dirty-scheduler... so we cannot
                   do erts_proc_sig_fetch as that would potentially cause a segfault */
                locks = 0;
            }
        } else {
            erts_proc_lock(p, locks);
        }
    } else {
        ERTS_ASSERT(locks == ERTS_PROC_LOCK_MAIN && "Only main lock should be held");
    }

    /*
     * If the process is registered as a global process, display the
     * registered name
     */
    if (!ERTS_PROC_IS_EXITING(p) && p->common.u.alive.reg)
	erts_print(to, to_arg, "Name: %T\n", p->common.u.alive.reg->name);

    /*
     * Display the initial function name
     */
    erts_print(to, to_arg, "Spawned as: %T:%T/%bpu\n",
	       p->u.initial.module,
	       p->u.initial.function,
	       p->u.initial.arity);
    
    if (p->current != NULL) {
	if (running) {
	    erts_print(to, to_arg, "Last scheduled in for: ");
	} else {
	    erts_print(to, to_arg, "Current call: ");
	}
	erts_print(to, to_arg, "%T:%T/%bpu\n",
		   p->current->module,
		   p->current->function,
		   p->current->arity);
    }

    erts_print(to, to_arg, "Spawned by: %T\n", p->parent);

    if (locks & ERTS_PROC_LOCK_MAIN) {
        erts_proc_lock(p, ERTS_PROC_LOCK_MSGQ);
        len = erts_proc_sig_fetch(p);
        erts_proc_unlock(p, ERTS_PROC_LOCK_MSGQ);
    } else {
        len = p->sig_qs.len;
    }
    erts_print(to, to_arg, "Message queue length: %d\n", len);

    /* display the message queue only if there is anything in it
       and we can do it safely */
    if (!ERTS_IS_CRASH_DUMPING && p->sig_qs.first != NULL && !garbing
        && (locks & ERTS_PROC_LOCK_MAIN)) {
	erts_print(to, to_arg, "Message queue: [");
        ERTS_FOREACH_SIG_PRIVQS(
            p, mp,
            {
                if (ERTS_SIG_IS_NON_MSG((ErtsSignal *) mp))
                    erts_print(to, to_arg, mp->next ? "%T," : "%T",
                               ERL_MESSAGE_TERM(mp));
            });
	erts_print(to, to_arg, "]\n");
    }

    {
       int frags = 0;
       ErlHeapFragment *m = p->mbuf;
       while (m != NULL) {
	   frags++;
	   m = m->next;
       }
       erts_print(to, to_arg, "Number of heap fragments: %d\n", frags);
    }
    erts_print(to, to_arg, "Heap fragment data: %beu\n", MBUF_SIZE(p));

    scb = ERTS_PROC_GET_SAVED_CALLS_BUF(p);
    if (scb) {
       int i, j;

       erts_print(to, to_arg, "Last calls:");
       for (i = 0; i < scb->n; i++) {
	     erts_print(to, to_arg, " ");
	     j = scb->cur - i - 1;
	     if (j < 0)
		j += scb->len;
	     if (scb->ct[j] == &exp_send)
		erts_print(to, to_arg, "send");
	     else if (scb->ct[j] == &exp_receive)
		erts_print(to, to_arg, "'receive'");
	     else if (scb->ct[j] == &exp_timeout)
		   erts_print(to, to_arg, "timeout");
	     else
		 erts_print(to, to_arg, "%T:%T/%bpu\n",
			    scb->ct[j]->info.mfa.module,
			    scb->ct[j]->info.mfa.function,
			    scb->ct[j]->info.mfa.arity);
       }
       erts_print(to, to_arg, "\n");
    }

    /* display the links only if there are any*/
    if (!exiting && (ERTS_P_LINKS(p) || ERTS_P_MONITORS(p) || ERTS_P_LT_MONITORS(p))) {
	PrintMonitorContext context = {1, to, to_arg};
	erts_print(to, to_arg,"Link list: [");
	erts_link_tree_foreach(ERTS_P_LINKS(p), doit_print_link, &context);	
	erts_monitor_tree_foreach(ERTS_P_MONITORS(p), doit_print_monitor, &context);
	erts_monitor_list_foreach(ERTS_P_LT_MONITORS(p), doit_print_monitor, &context);
	erts_print(to, to_arg,"]\n");
    }

    if (!ERTS_IS_CRASH_DUMPING) {

	/* and the dictionary */
	if (p->dictionary != NULL && !garbing) {
	    erts_print(to, to_arg, "Dictionary: ");
	    erts_dictionary_dump(to, to_arg, p->dictionary);
	    erts_print(to, to_arg, "\n");
	}
    }
    
    /* print the number of reductions etc */
    erts_print(to, to_arg, "Reductions: %beu\n", p->reds);

    erts_print(to, to_arg, "Stack+heap: %beu\n", p->heap_sz);
    erts_print(to, to_arg, "OldHeap: %bpu\n",
               (OLD_HEAP(p) == NULL) ? 0 : (OLD_HEND(p) - OLD_HEAP(p)) );
    erts_print(to, to_arg, "Heap unused: %bpu\n", (p->hend - p->htop));
    erts_print(to, to_arg, "OldHeap unused: %bpu\n",
	       (OLD_HEAP(p) == NULL) ? 0 : (OLD_HEND(p) - OLD_HTOP(p)) );
    erts_print(to, to_arg, "BinVHeap: %b64u\n", p->off_heap.overhead);
    erts_print(to, to_arg, "OldBinVHeap: %b64u\n", BIN_OLD_VHEAP(p));
    erts_print(to, to_arg, "BinVHeap unused: %b64u\n",
               BIN_VHEAP_SZ(p) - p->off_heap.overhead);
    if (BIN_OLD_VHEAP_SZ(p) >= BIN_OLD_VHEAP(p)) {
        erts_print(to, to_arg, "OldBinVHeap unused: %b64u\n",
                   BIN_OLD_VHEAP_SZ(p) - BIN_OLD_VHEAP(p));
    } else {
        erts_print(to, to_arg, "OldBinVHeap unused: overflow\n");
    }
    erts_print(to, to_arg, "Memory: %beu\n", erts_process_memory(p, !0));

    if (garbing) {
	print_garb_info(to, to_arg, p);
    }
    
    if (ERTS_IS_CRASH_DUMPING) {
	erts_program_counter_info(to, to_arg, p);
    } else {
	erts_print(to, to_arg, "Stack dump:\n");
	if (!garbing)
	    erts_stack_dump(to, to_arg, p);
    }

    /* Display all states */
    erts_print(to, to_arg, "Internal State: ");
    erts_dump_extended_process_state(to, to_arg, state);

    erts_proc_unlock(p, locks & ~orig_locks);
}

static void
print_garb_info(fmtfn_t to, void *to_arg, Process* p)
{
    /* A scheduler is probably concurrently doing gc... */
    if (!ERTS_IS_CRASH_DUMPING)
      return;
    erts_print(to, to_arg, "New heap start: %bpX\n", p->heap);
    erts_print(to, to_arg, "New heap top: %bpX\n", p->htop);
    erts_print(to, to_arg, "Stack top: %bpX\n", p->stop);
    erts_print(to, to_arg, "Stack end: %bpX\n", p->hend);
    erts_print(to, to_arg, "Old heap start: %bpX\n", OLD_HEAP(p));
    erts_print(to, to_arg, "Old heap top: %bpX\n", OLD_HTOP(p));
    erts_print(to, to_arg, "Old heap end: %bpX\n", OLD_HEND(p));
}

void
info(fmtfn_t to, void *to_arg)
{
    erts_memory(&to, to_arg, NULL, THE_NON_VALUE);
    atom_info(to, to_arg);
    module_info(to, to_arg);
    export_info(to, to_arg);
    register_info(to, to_arg);
    erts_fun_info(to, to_arg);
    erts_node_table_info(to, to_arg);
    erts_dist_table_info(to, to_arg);
    erts_allocated_areas(&to, to_arg, NULL);
    erts_allocator_info(to, to_arg);

}

static int code_size(struct erl_module_instance* modi)
{
    int size = modi->code_length;

    if (modi->code_hdr) {
        ErtsLiteralArea* lit = modi->code_hdr->literal_area;
        if (lit)
            size += (lit->end - lit->start) * sizeof(Eterm);
    }
    return size;
}

void
loaded(fmtfn_t to, void *to_arg)
{
    int i;
    int old = 0;
    int cur = 0;
    BeamCodeHeader* code;
    Module* modp;
    ErtsCodeIndex code_ix;

    code_ix = erts_active_code_ix();
    erts_rlock_old_code(code_ix);

    /*
     * Calculate and print totals.
     */
    for (i = 0; i < module_code_size(code_ix); i++) {
	if ((modp = module_code(i, code_ix)) != NULL) {
	    cur += code_size(&modp->curr);
            old += code_size(&modp->old);
	}
    }
    erts_print(to, to_arg, "Current code: %d\n", cur);
    erts_print(to, to_arg, "Old code: %d\n", old);
    
    /*
     * Print one line per module.
     */

    for (i = 0; i < module_code_size(code_ix); i++) {
	modp = module_code(i, code_ix);
	if (!ERTS_IS_CRASH_DUMPING) {
	    /*
	     * Interactive dump; keep it brief.
	     */
	    if (modp != NULL && ((modp->curr.code_length != 0) ||
                                 (modp->old.code_length != 0))) {
		erts_print(to, to_arg, "%T %d", make_atom(modp->module),
                           code_size(&modp->curr));
		if (modp->old.code_length != 0)
		    erts_print(to, to_arg, " (%d old)", code_size(&modp->old));
		erts_print(to, to_arg, "\n");
	    }
	} else {
	    /*
	     * To crash dump; make it parseable.
	     */
	    if (modp != NULL && ((modp->curr.code_length != 0) ||
                                 (modp->old.code_length != 0))) {
		erts_print(to, to_arg, "=mod:");
		erts_print(to, to_arg, "%T", make_atom(modp->module));
		erts_print(to, to_arg, "\n");
		erts_print(to, to_arg, "Current size: %d\n",
			   code_size(&modp->curr));
		code = modp->curr.code_hdr;
		if (code != NULL && code->attr_ptr) {
		    erts_print(to, to_arg, "Current attributes: ");
		    dump_attributes(to, to_arg, code->attr_ptr,
				    code->attr_size);
		}
		if (code != NULL && code->compile_ptr) {
		    erts_print(to, to_arg, "Current compilation info: ");
		    dump_attributes(to, to_arg, code->compile_ptr,
				    code->compile_size);
		}

		if (modp->old.code_length != 0) {
		    erts_print(to, to_arg, "Old size: %d\n", code_size(&modp->old));
		    code = modp->old.code_hdr;
		    if (code->attr_ptr) {
			erts_print(to, to_arg, "Old attributes: ");
			dump_attributes(to, to_arg, code->attr_ptr,
					code->attr_size);
		    }
		    if (code->compile_ptr) {
			erts_print(to, to_arg, "Old compilation info: ");
			dump_attributes(to, to_arg, code->compile_ptr,
					code->compile_size);
		    }
		}
	    }
	}
    }
    erts_runlock_old_code(code_ix);
}


static void
dump_attributes(fmtfn_t to, void *to_arg, byte* ptr, int size)
{
    erts_print_base64(to, to_arg, ptr, size);
    erts_print(to, to_arg, "\n");
}


void
do_break(void)
{
    const char *helpstring = "BREAK: (a)bort (A)bort with dump (c)ontinue (p)roc info (i)nfo\n"
        "       (l)oaded (v)ersion (k)ill (D)b-tables (d)istribution\n";
    int i;
#ifdef __WIN32__
    char *mode; /* enough for storing "window" */

    /* check if we're in console mode and, if so,
       halt immediately if break is called */
    mode = erts_read_env("ERL_CONSOLE_MODE");
    if (mode && sys_strcmp(mode, "window") != 0)
	erts_exit(0, "");
    erts_free_read_env(mode);
#endif /* __WIN32__ */

    ASSERT(erts_thr_progress_is_blocking());

    erts_printf("\n%s", helpstring);

    while (1) {
	if ((i = sys_get_key(0)) <= 0)
	    erts_exit(0, "");
	switch (i) {
	case 'q':
	case 'a': 
	case '*': /* 
		   * The asterisk is an read error on windows, 
		   * where sys_get_key isn't that great in console mode.
		   * The usual reason for a read error is Ctrl-C. Treat this as
		   * 'a' to avoid infinite loop.
		   */
	    erts_exit(0, "");
	case 'A':		/* Halt generating crash dump */
	    erts_exit(ERTS_ERROR_EXIT, "Crash dump requested by user");
	case 'c':
	    return;
	case 'p':
	    process_info(ERTS_PRINT_STDOUT, NULL);
	    return;
	case 'm':
	    return;
	case 'o':
	    port_info(ERTS_PRINT_STDOUT, NULL);
	    return;
	case 'i':
	    info(ERTS_PRINT_STDOUT, NULL);
	    return;
	case 'l':
	    loaded(ERTS_PRINT_STDOUT, NULL);
	    return;
	case 'v':
	    erts_printf("Erlang (%s) emulator version "
		       ERLANG_VERSION "\n",
		       EMULATOR);
#if ERTS_SAVED_COMPILE_TIME
	    erts_printf("Compiled on " ERLANG_COMPILE_DATE "\n");
#endif
	    return;
	case 'd':
	    distribution_info(ERTS_PRINT_STDOUT, NULL);
	    return;
	case 'D':
	    db_info(ERTS_PRINT_STDOUT, NULL, 1);
	    return; 
	case 'k':
	    process_killer();
	    return;
#ifdef OPPROF
	case 'X':
	    dump_frequencies();
	    return;
	case 'x':
	    {
		int i;
		for (i = 0; i <= HIGHEST_OP; i++) {
		    if (opc[i].name != NULL) {
			erts_printf("%-16s %8d\n", opc[i].name, opc[i].count);
		    }
		}
	    }
	    return;
	case 'z':
	    {
		int i;
		for (i = 0; i <= HIGHEST_OP; i++)
		    opc[i].count = 0;
	    }
	    return;
#endif
#ifdef DEBUG
	case 't':
	    /* erts_p_slpq(); */
	    return;
	case 'b':
	    bin_check();
	    return;
	case 'C':
	    abort();
#endif
	case '\n':
	    continue;
	default:
	    erts_printf("Invalid option '%c'. Please enter one of the following:\n%s",
                        i, helpstring);
	}
    }

}


#ifdef OPPROF
static void
dump_frequencies(void)
{
    int i;
    FILE* fp;
    time_t now;
    static char name[] = "op_freq.dump";

    fp = fopen(name, "w");
    if (fp == NULL) {
	fprintf(stderr, "Failed to open %s for writing\n", name);
	return;
    }

    time(&now);
    fprintf(fp, "# Generated %s\n", ctime(&now));

    for (i = 0; i <= HIGHEST_OP; i++) {
	if (opc[i].name != NULL) {
	    fprintf(fp, "%s %d\n", opc[i].name, opc[i].count);
	}
    }
    fclose(fp);
    erts_printf("Frequencies dumped to %s\n", name);
}
#endif


#ifdef DEBUG

static void 
bin_check(void)
{
    Process  *rp;
    struct erl_off_heap_header* hdr;
    int i, printed = 0, max = erts_ptab_max(&erts_proc);

    for (i=0; i < max; i++) {
	rp = erts_pix2proc(i);
	if (!rp)
	    continue;
	for (hdr = rp->off_heap.first; hdr; hdr = hdr->next) {
	    if (hdr->thing_word == HEADER_PROC_BIN) {
		ProcBin *bp = (ProcBin*) hdr;
		if (!printed) {
		    erts_printf("Process %T holding binary data \n", rp->common.id);
		    printed = 1;
		}
		erts_printf("%p orig_size: %bpd, norefs = %bpd\n",
			    bp->val, 
			    bp->val->orig_size, 
			    erts_refc_read(&bp->val->intern.refc, 1));
	    }
	}
	if (printed) {
	    erts_printf("--------------------------------------\n");
	    printed = 0;
	}
    }
    /* db_bin_check() has to be rewritten for the AVL trees... */
    /*db_bin_check();*/ 
}

#endif

static Sint64 crash_dump_limit = ERTS_SINT64_MAX;
static Sint64 crash_dump_written = 0;

typedef struct LimitedWriterInfo_ {
    fmtfn_t to;
    void* to_arg;
} LimitedWriterInfo;

static int
crash_dump_limited_writer(void* vfdp, char* buf, size_t len)
{
    const char stop_msg[] = "\n=abort:CRASH DUMP SIZE LIMIT REACHED\n";
    LimitedWriterInfo* lwi = (LimitedWriterInfo *) vfdp;

    crash_dump_written += len;
    if (crash_dump_written <= crash_dump_limit) {
        return lwi->to(lwi->to_arg, buf, len);
    }

    len -= (crash_dump_written - crash_dump_limit);
    lwi->to(lwi->to_arg, buf, len);
    lwi->to(lwi->to_arg, (char*)stop_msg, sizeof(stop_msg)-1);
    if (lwi->to == &erts_write_fp) {
        fclose((FILE *) lwi->to_arg);
    }

    /* We assume that crash dump was called from erts_exit_vv() */
    erts_exit_epilogue();
}

/* XXX THIS SHOULD BE IN SYSTEM !!!! */
void
erl_crash_dump_v(char *file, int line, char* fmt, va_list args)
{
    ErtsThrPrgrData tpd_buf; /* in case we aren't a managed thread... */
    int fd;
    size_t envsz;
    time_t now;
    char env[21]; /* enough to hold any 64-bit integer */
    size_t dumpnamebufsize = MAXPATHLEN;
    char dumpnamebuf[MAXPATHLEN];
    char* dumpname;
    int secs;
    int env_erl_crash_dump_seconds_set = 1;
    int i;
    fmtfn_t to = &erts_write_fd;
    void*   to_arg;
    FILE* fp = 0;
    LimitedWriterInfo lwi;
    static char* write_buffer;  /* 'static' to avoid a leak warning in valgrind */

    if (ERTS_SOMEONE_IS_CRASH_DUMPING)
	return;

    /* Order all managed threads to block, this has to be done
       first to guarantee that this is the only thread to generate
       crash dump. */
    erts_thr_progress_fatal_error_block(&tpd_buf);

#ifdef ERTS_SYS_SUSPEND_SIGNAL
    /*
     * We suspend all scheduler threads so that we can dump some
     * data about the currently running processes and scheduler data.
     * We have to be very very careful when doing this as the schedulers
     * could be anywhere.
     */
    sys_init_suspend_handler();

    for (i = 0; i < erts_no_schedulers; i++) {
        erts_tid_t tid = ERTS_SCHEDULER_IX(i)->tid;
        if (!erts_equal_tids(tid,erts_thr_self()))
            sys_thr_suspend(tid);
    }

#endif

    /* Allow us to pass certain places without locking... */
    erts_atomic32_set_mb(&erts_writing_erl_crash_dump, 1);
    erts_tsd_set(erts_is_crash_dumping_key, (void *) 1);


    envsz = sizeof(env);
    /* ERL_CRASH_DUMP_SECONDS not set
     * if we have a heart port, break immediately
     * otherwise dump crash indefinitely (until crash is complete)
     * same as ERL_CRASH_DUMP_SECONDS = 0
     * - do not write dump
     * - do not set an alarm
     * - break immediately
     *
     * ERL_CRASH_DUMP_SECONDS = 0
     * - do not write dump
     * - do not set an alarm
     * - break immediately
     *
     * ERL_CRASH_DUMP_SECONDS < 0
     * - do not set alarm
     * - write dump until done
     *
     * ERL_CRASH_DUMP_SECONDS = S (and S positive)
     * - Don't dump file forever
     * - set alarm (set in sys)
     * - write dump until alarm or file is written completely
     */
	
    if (erts_sys_explicit_8bit_getenv("ERL_CRASH_DUMP_SECONDS", env, &envsz) == 1) {
        env_erl_crash_dump_seconds_set = 1;
        secs = atoi(env);
    } else {
        env_erl_crash_dump_seconds_set = 0;
        secs = -1;
    }

    if (secs == 0) {
        return;
    }

    /* erts_sys_prepare_crash_dump returns 1 if heart port is found, otherwise 0
     * If we don't find heart (0) and we don't have ERL_CRASH_DUMP_SECONDS set
     * we should continue writing a dump
     *
     * beware: secs -1 means no alarm
     */

    if (erts_sys_prepare_crash_dump(secs) && !env_erl_crash_dump_seconds_set ) {
	return;
    }

    crash_dump_limit = ERTS_SINT64_MAX;
    envsz = sizeof(env);
    if (erts_sys_explicit_8bit_getenv("ERL_CRASH_DUMP_BYTES", env, &envsz) == 1) {
        Sint64 limit;
        char* endptr;
        errno = 0;
        limit = ErtsStrToSint64(env, &endptr, 10);
        if (errno == 0 && limit >= 0 && endptr != env && *endptr == 0) {
            if (limit == 0)
                return;
            crash_dump_limit = limit;
            to = &crash_dump_limited_writer;
        }
    }

    if (erts_sys_explicit_8bit_getenv("ERL_CRASH_DUMP",&dumpnamebuf[0],&dumpnamebufsize) != 1)
	dumpname = "erl_crash.dump";
    else
	dumpname = &dumpnamebuf[0];
    
    erts_fprintf(stderr,"\nCrash dump is being written to: %s...", dumpname);

    fd = open(dumpname,O_WRONLY | O_CREAT | O_TRUNC,0640);
    if (fd < 0)
	return; /* Can't create the crash dump, skip it */

    /*
     * Wrap into a FILE* so that we can use buffered output. Set an
     * explicit buffer to make sure the first write does not fail because
     * of a failure to allocate a buffer.
     */
    write_buffer = (char *) erts_alloc_fnf(ERTS_ALC_T_TMP, WRITE_BUFFER_SIZE);
    if (write_buffer && (fp = fdopen(fd, "w")) != NULL) {
        setvbuf(fp, write_buffer, _IOFBF, WRITE_BUFFER_SIZE);
        lwi.to = &erts_write_fp;
        lwi.to_arg = (void*)fp;
    } else {
        lwi.to = &erts_write_fd;
        lwi.to_arg = (void*)&fd;
    }
    if (to == &crash_dump_limited_writer) {
        to_arg = (void *) &lwi;
    } else {
        to = lwi.to;
        to_arg = lwi.to_arg;
    }

    time(&now);
    erts_cbprintf(to, to_arg, "=erl_crash_dump:0.5\n%s", ctime(&now));

    if (file != NULL)
       erts_cbprintf(to, to_arg, "The error occurred in file %s, line %d\n", file, line);

    if (fmt != NULL && *fmt != '\0') {
	erts_cbprintf(to, to_arg, "Slogan: ");
	erts_vcbprintf(to, to_arg, fmt, args);
    }
    erts_cbprintf(to, to_arg, "System version: ");
    erts_print_system_version(to, to_arg, NULL);
#if ERTS_SAVED_COMPILE_TIME
    erts_cbprintf(to, to_arg, "%s\n", "Compiled: " ERLANG_COMPILE_DATE);
#endif

    erts_cbprintf(to, to_arg, "Taints: ");
    erts_print_nif_taints(to, to_arg);
    erts_cbprintf(to, to_arg, "Atoms: %d\n", atom_table_size());

    /* We want to note which thread it was that called erts_exit */
    if (erts_get_scheduler_data()) {
        erts_cbprintf(to, to_arg, "Calling Thread: scheduler:%d\n",
                      erts_get_scheduler_data()->no);
    } else {
        if (!erts_thr_getname(erts_thr_self(), dumpnamebuf, MAXPATHLEN))
            erts_cbprintf(to, to_arg, "Calling Thread: %s\n", dumpnamebuf);
        else
            erts_cbprintf(to, to_arg, "Calling Thread: %p\n", erts_thr_self());
    }

#if defined(ERTS_HAVE_TRY_CATCH)

    /*
     * erts_print_scheduler_info is not guaranteed to be safe to call
     * here for all schedulers as we may have suspended a scheduler
     * in the middle of updating the STACK_TOP and STACK_START
     * variables and thus when scanning the stack we could get
     * segmentation faults. We protect against this very unlikely
     * scenario by using the ERTS_SYS_TRY_CATCH.
     */
    for (i = 0; i < erts_no_schedulers; i++) {
        ERTS_SYS_TRY_CATCH(
            erts_print_scheduler_info(to, to_arg, ERTS_SCHEDULER_IX(i)),
            erts_cbprintf(to, to_arg, "** crashed **\n"));
    }
    for (i = 0; i < erts_no_dirty_cpu_schedulers; i++) {
        ERTS_SYS_TRY_CATCH(
            erts_print_scheduler_info(to, to_arg, ERTS_DIRTY_CPU_SCHEDULER_IX(i)),
            erts_cbprintf(to, to_arg, "** crashed **\n"));
    }
    erts_cbprintf(to, to_arg, "=dirty_cpu_run_queue\n");
    erts_print_run_queue_info(to, to_arg, ERTS_DIRTY_CPU_RUNQ);

    for (i = 0; i < erts_no_dirty_io_schedulers; i++) {
        ERTS_SYS_TRY_CATCH(
            erts_print_scheduler_info(to, to_arg, ERTS_DIRTY_IO_SCHEDULER_IX(i)),
            erts_cbprintf(to, to_arg, "** crashed **\n"));
    }
    erts_cbprintf(to, to_arg, "=dirty_io_run_queue\n");
    erts_print_run_queue_info(to, to_arg, ERTS_DIRTY_IO_RUNQ);
#endif


#ifdef ERTS_SYS_SUSPEND_SIGNAL

    /* We resume all schedulers so that we are in a known safe state
       when we write the rest of the crash dump */
    for (i = 0; i < erts_no_schedulers; i++) {
        erts_tid_t tid = ERTS_SCHEDULER_IX(i)->tid;
        if (!erts_equal_tids(tid,erts_thr_self()))
	    sys_thr_resume(tid);
    }
#endif

    /*
     * Wait for all managed threads to block. If all threads haven't blocked
     * after a minute, we go anyway and hope for the best...
     *
     * We do not release system again. We expect an exit() or abort() after
     * dump has been written.
     */
    erts_thr_progress_fatal_error_wait(60000);
    /* Either worked or not... */

#ifndef ERTS_HAVE_TRY_CATCH
    /* This is safe to call here, as all schedulers are blocked */
    for (i = 0; i < erts_no_schedulers; i++) {
        erts_print_scheduler_info(to, to_arg, ERTS_SCHEDULER_IX(i));
    }
#endif
    
    info(to, to_arg); /* General system info */
    if (erts_ptab_initialized(&erts_proc))
	process_info(to, to_arg); /* Info about each process and port */
    db_info(to, to_arg, 0);
    erts_print_bif_timer_info(to, to_arg);
    distribution_info(to, to_arg);
    erts_cbprintf(to, to_arg, "=loaded_modules\n");
    loaded(to, to_arg);
    erts_dump_fun_entries(to, to_arg);
    erts_deep_process_dump(to, to_arg);
    erts_cbprintf(to, to_arg, "=atoms\n");
    dump_atoms(to, to_arg);

    erts_cbprintf(to, to_arg, "=end\n");
    if (fp) {
        fclose(fp);
    }
    close(fd);
    erts_fprintf(stderr,"done\n");
}

void
erts_print_base64(fmtfn_t to, void *to_arg, byte* src, Uint size)
{
    static const byte base64_chars[] =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    while (size >= 3) {
        erts_putc(to, to_arg, base64_chars[src[0] >> 2]);
        erts_putc(to, to_arg, base64_chars[((src[0] & 0x03) << 4) | (src[1] >> 4)]);
        erts_putc(to, to_arg, base64_chars[((src[1] & 0x0f) << 2) | (src[2] >> 6)]);
        erts_putc(to, to_arg, base64_chars[src[2] & 0x3f]);
        size -= 3;
        src += 3;
    }
    if (size == 1) {
        erts_putc(to, to_arg, base64_chars[src[0] >> 2]);
        erts_putc(to, to_arg, base64_chars[(src[0] & 0x03) << 4]);
        erts_print(to, to_arg, "==");
    } else if (size == 2) {
        erts_putc(to, to_arg, base64_chars[src[0] >> 2]);
        erts_putc(to, to_arg, base64_chars[((src[0] & 0x03) << 4) | (src[1] >> 4)]);
        erts_putc(to, to_arg, base64_chars[(src[1] & 0x0f) << 2]);
        erts_putc(to, to_arg, '=');
    }
}
