/*
 * %CopyrightBegin%
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright Ericsson AB 1996-2026. 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_code.h"
#include "erl_hl_timer.h"
#include "erl_thr_progress.h"
#include "erl_proc_sig_queue.h"
#include "dist.h"
#include "erl_crash_dump.h"

/* Forward declarations -- should really appear somewhere else */
static void process_killer(void);
void do_break(void);

#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, const byte* ptr, int size);

extern char* erts_system_version[];

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) {
	    print_process_info(ERTS_PRINT_STDOUT, NULL, rp, 0);
	    erts_printf("(k)ill (n)ext (r)eturn:\n");
            if ((j = sys_get_key(0)) <= 0)
                erts_exit(0, "");
            switch(j) {
            case 'k':
            {
                Process *init_proc;

                ASSERT(erts_init_process_id != ERTS_INVALID_PID);
                init_proc = erts_proc_lookup_raw(erts_init_process_id);

                /* Send a 'kill' exit signal from init process */
                erts_proc_sig_send_exit(&init_proc->common,
                                        erts_init_process_id,
                                        rp->common.id,
                                        am_kill, NIL, 0, 0);
                break;
            }
            case 'n': break;
            default: return;
            }
	}
    }
}

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 (ERTS_ML_GET_TYPE(mon)) {
    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_DIST_PORT:
    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 (ERTS_ML_GET_TYPE(mon) != 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,
	       (Uint)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,
		   (Uint)p->current->arity);
    }

    erts_print(to, to_arg, "Spawned by: %T\n",
               p->parent == am_undefined ? NIL : 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.mq_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)) {
        ErtsMessage *mp;
	erts_print(to, to_arg, "Message queue: [");
        for (mp = p->sig_qs.first; mp; mp = mp->next) {
            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\n");
	     else if (scb->ct[j] == &exp_receive)
		erts_print(to, to_arg, "'receive'\n");
	     else if (scb->ct[j] == &exp_timeout)
		   erts_print(to, to_arg, "timeout\n");
	     else
		 erts_print(to, to_arg, "%T:%T/%bpu\n",
			    scb->ct[j]->info.mfa.module,
			    scb->ct[j]->info.mfa.function,
			    (Uint)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", p->bin_old_vheap);
    erts_print(to, to_arg, "BinVHeap unused: %b64u\n",
               p->bin_vheap_sz - p->off_heap.overhead);
    if (p->bin_old_vheap_sz >= p->bin_old_vheap) {
        erts_print(to, to_arg, "OldBinVHeap unused: %b64u\n",
                   p->bin_old_vheap_sz - p->bin_old_vheap);
    } 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;
    const 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, const 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 *clearscreen = "\033[J";
    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, "detached") == 0)
	erts_exit(0, "");
    erts_free_read_env(mode);
#else
    char *clearscreen = "\E[J";
#endif

    ASSERT(erts_thr_progress_is_blocking());

    /* If we are writing to something known to be a tty we clear the screen
       after doing newline as the shell tab completion may have written
       things there. */
    if (!isatty(fileno(stdin)) || !isatty(fileno(stdout))) {
        clearscreen = "";
    }

    erts_printf("\n%s%s", clearscreen, 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\n");
	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);
	    return;
	case 'd':
	    distribution_info(ERTS_PRINT_STDOUT, NULL);
	    return;
	case 'D':
	    db_info(ERTS_PRINT_STDOUT, NULL, true);
	    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;
    struct erl_off_heap_header* oh_list;
    int i, printed = 0, max = erts_ptab_max(&erts_proc);


    for (i=0; i < max; i++) {
	rp = erts_pix2proc(i);
	if (!rp)
	    continue;

        oh_list = rp->off_heap.first;
        for (;;) {
            for (hdr = oh_list; hdr; hdr = hdr->next) {
                if (hdr->thing_word == HEADER_BIN_REF) {
                    Binary *bin = ((BinRef*)hdr)->val;
                    if (!printed) {
                        erts_printf("Process %T holding binary data \n", rp->common.id);
                        printed = 1;
                    }
                    erts_printf("%p orig_size: %bpd, norefs = %bpd\n",
                                bin,
                                bin->orig_size,
                                erts_refc_read(&bin->intern.refc, 1));
                }
            }
            if (oh_list == rp->wrt_bins)
                break;
            oh_list = rp->wrt_bins;
        }

	if (printed) {
	    erts_printf("--------------------------------------\n");
	    printed = 0;
	}
    }
    /* db_bin_check() has to be rewritten for the AVL trees... */
    /*db_bin_check();*/ 
}

#endif

void
erts_print_base64(fmtfn_t to, void *to_arg, const 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, '=');
    }
}
