/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2001-2010. All Rights Reserved.
 *
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * %CopyrightEnd%
 */

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

#include <ctype.h>

#define ERTS_WANT_EXTERNAL_TAGS
#include "sys.h"
#include "erl_vm.h"
#include "erl_sys_driver.h"
#include "global.h"
#include "erl_process.h"
#include "error.h"
#include "bif.h"
#include "big.h"
#include "dist.h"
#include "erl_version.h"
#include "erl_binary.h"
#include "erl_db_util.h"
#include "register.h"
#include "external.h"
#include "packet_parser.h"
#include "erl_bits.h"

static int open_port(Process* p, Eterm name, Eterm settings, int *err_nump);
static byte* convert_environment(Process* p, Eterm env);
static char **convert_args(Eterm);
static void free_args(char **);

char *erts_default_arg0 = "default";

BIF_RETTYPE open_port_2(BIF_ALIST_2)
{
    int port_num;
    Eterm port_val;
    char *str;
    int err_num;

    if ((port_num = open_port(BIF_P, BIF_ARG_1, BIF_ARG_2, &err_num)) < 0) {
	if (port_num == -3) {
	    ASSERT(err_num == BADARG || err_num == SYSTEM_LIMIT);
	    BIF_ERROR(BIF_P, err_num);
	} else if (port_num == -2) {
	    str = erl_errno_id(err_num);
	} else {
	    str = "einval";
	}
	BIF_P->fvalue = am_atom_put(str, strlen(str));
	BIF_ERROR(BIF_P, EXC_ERROR);
    }

    erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_LINK);

    port_val = erts_port[port_num].id;
    erts_add_link(&(erts_port[port_num].nlinks), LINK_PID, BIF_P->id);
    erts_add_link(&(BIF_P->nlinks), LINK_PID, port_val);

    erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_LINK);

    erts_port_release(&erts_port[port_num]);

    BIF_RET(port_val);
}

/****************************************************************************

  PORT BIFS:

           port_command/2   -- replace Port ! {..., {command, Data}}
               port_command(Port, Data) -> true
               when port(Port), io-list(Data)

           port_control/3   -- new port_control(Port, Ctl, Data) -> Reply
	      port_control(Port, Ctl, Data) -> Reply
              where integer(Ctl), io-list(Data), io-list(Reply)

           port_close/1     -- replace Port ! {..., close}
             port_close(Port) -> true
             when port(Port)

           port_connect/2   -- replace Port ! {..., {connect, Pid}}
              port_connect(Port, Pid) 
              when port(Port), pid(Pid)

 ***************************************************************************/

static Port*
id_or_name2port(Process *c_p, Eterm id)
{
    Port *port;
    if (is_not_atom(id))
	port = erts_id2port(id, c_p, ERTS_PROC_LOCK_MAIN);
    else
	erts_whereis_name(c_p, ERTS_PROC_LOCK_MAIN, id, NULL, 0, 0, &port);
    return port;
}

#define ERTS_PORT_COMMAND_FLAG_FORCE		(((Uint32) 1) << 0)
#define ERTS_PORT_COMMAND_FLAG_NOSUSPEND	(((Uint32) 1) << 1)

static BIF_RETTYPE do_port_command(Process *BIF_P,
				   Eterm BIF_ARG_1,
				   Eterm BIF_ARG_2,
				   Eterm BIF_ARG_3,
				   Uint32 flags)
{
    BIF_RETTYPE res;
    Port *p;

    /* Trace sched out before lock check wait */    
    if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) {
	trace_virtual_sched(BIF_P, am_out);
    }
    
    if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) {
    	profile_runnable_proc(BIF_P, am_inactive);
    }

    p = id_or_name2port(BIF_P, BIF_ARG_1);
    if (!p) {
    	if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) {
	    trace_virtual_sched(BIF_P, am_in);
	}
    	if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) {
    	    profile_runnable_proc(BIF_P, am_active);
    	}
	BIF_ERROR(BIF_P, BADARG);
    }
    
    /* Trace port in, id_or_name2port causes wait */

    if (IS_TRACED_FL(p, F_TRACE_SCHED_PORTS)) {
	trace_sched_ports_where(p, am_in, am_command);
    }
    if (erts_system_profile_flags.runnable_ports && !erts_port_is_scheduled(p)) {
    	profile_runnable_port(p, am_active);
    }

    ERTS_BIF_PREP_RET(res, am_true);

    if ((flags & ERTS_PORT_COMMAND_FLAG_FORCE)
	&& !(p->drv_ptr->flags & ERL_DRV_FLAG_SOFT_BUSY)) {
	ERTS_BIF_PREP_ERROR(res, BIF_P, EXC_NOTSUP);
    }
    else if (!(flags & ERTS_PORT_COMMAND_FLAG_FORCE)
	     && p->status & ERTS_PORT_SFLG_PORT_BUSY) {
	if (flags & ERTS_PORT_COMMAND_FLAG_NOSUSPEND) {
	    ERTS_BIF_PREP_RET(res, am_false);
	}
	else {
	    erts_suspend(BIF_P, ERTS_PROC_LOCK_MAIN, p);
	    if (erts_system_monitor_flags.busy_port) {
		monitor_generic(BIF_P, am_busy_port, p->id);
	    }
	    ERTS_BIF_PREP_YIELD3(res, bif_export[BIF_port_command_3], BIF_P,
				 BIF_ARG_1, BIF_ARG_2, BIF_ARG_3);    
	}
    } else {
	int wres;
	erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
	ERTS_SMP_CHK_NO_PROC_LOCKS;
	wres = erts_write_to_port(BIF_P->id, p, BIF_ARG_2);
	erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
	if (wres != 0) {
	    ERTS_BIF_PREP_ERROR(res, BIF_P, BADARG);
	}
    }
    
    if (IS_TRACED_FL(p, F_TRACE_SCHED_PORTS)) {
	trace_sched_ports_where(p, am_out, am_command);
    }
    if (erts_system_profile_flags.runnable_ports && !erts_port_is_scheduled(p)) {
    	profile_runnable_port(p, am_inactive);
    }

    erts_port_release(p);
    /* Trace sched in after port release */
    if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) {
	trace_virtual_sched(BIF_P, am_in);
    }
    if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) {
    	profile_runnable_proc(BIF_P, am_active);
    }
    
    if (ERTS_PROC_IS_EXITING(BIF_P)) {
	KILL_CATCHES(BIF_P);	/* Must exit */
	ERTS_BIF_PREP_ERROR(res, BIF_P, EXC_ERROR);
    }
    return res;
}

BIF_RETTYPE port_command_2(BIF_ALIST_2)
{
    return do_port_command(BIF_P, BIF_ARG_1, BIF_ARG_2, NIL, 0);
}

BIF_RETTYPE port_command_3(BIF_ALIST_3)
{
    Eterm l = BIF_ARG_3;
    Uint32 flags = 0;
    while (is_list(l)) {
	Eterm* cons = list_val(l);
	Eterm car = CAR(cons);
	if (car == am_force) {
	    flags |= ERTS_PORT_COMMAND_FLAG_FORCE;
	} else if (car == am_nosuspend) {
	    flags |= ERTS_PORT_COMMAND_FLAG_NOSUSPEND;
	} else {
	    BIF_ERROR(BIF_P, BADARG);
	}
	l = CDR(cons);
    }
    if(!is_nil(l)) {
	BIF_ERROR(BIF_P, BADARG);
    }
    return do_port_command(BIF_P, BIF_ARG_1, BIF_ARG_2, BIF_ARG_3, flags);
}

BIF_RETTYPE port_call_2(BIF_ALIST_2)
{
    return port_call_3(BIF_P,BIF_ARG_1,make_small(0),BIF_ARG_2);
}

BIF_RETTYPE port_call_3(BIF_ALIST_3)
{
    Uint op;
    Port *p;
    Uint size;
    byte *bytes;
    byte *endp;
    size_t real_size;
    erts_driver_t *drv;
    byte port_input[256];	/* Default input buffer to encode in */
    byte port_result[256];	/* Buffer for result from port. */
    byte* port_resp;		/* Pointer to result buffer. */
    char *prc;
    int ret;
    Eterm res;
    Sint result_size;
    Eterm *hp;
    Eterm *hp_end;              /* To satisfy hybrid heap architecture */
    unsigned ret_flags = 0U;
    int fpe_was_unmasked;

    bytes = &port_input[0];
    port_resp = port_result;
    /* trace of port scheduling with virtual process descheduling
     * lock wait 
     */
    if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) {
    	trace_virtual_sched(BIF_P, am_out);
    }

    if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) {
    	profile_runnable_proc(BIF_P, am_inactive);
    }

    p = id_or_name2port(BIF_P, BIF_ARG_1);
    if (!p) {
    error:
	if (port_resp != port_result && 
	    !(ret_flags & DRIVER_CALL_KEEP_BUFFER)) {
	    driver_free(port_resp);
	}
	if (bytes != &port_input[0])
	    erts_free(ERTS_ALC_T_PORT_CALL_BUF, bytes);
	/* Need to virtual schedule in the process if there
	 * was an error.
	 */
    	if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) {
    	    trace_virtual_sched(BIF_P, am_in);
    	}

	if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) {
    	    profile_runnable_proc(BIF_P, am_active);
    	}

	if (p)
	    erts_port_release(p);
#ifdef ERTS_SMP
	ERTS_SMP_BIF_CHK_PENDING_EXIT(BIF_P, ERTS_PROC_LOCK_MAIN);
#else
	ERTS_BIF_CHK_EXITED(BIF_P);
#endif
	BIF_ERROR(BIF_P, BADARG);
    }

    if ((drv = p->drv_ptr) == NULL) {
	goto error;
    }
    if (drv->call == NULL) {
	goto error;
    }
    if (!term_to_Uint(BIF_ARG_2, &op)) {
	goto error;
    }
    p->caller = BIF_P->id;
    
    /* Lock taken, virtual schedule of port */
    if (IS_TRACED_FL(p, F_TRACE_SCHED_PORTS)) {
    	trace_sched_ports_where(p, am_in, am_call);
    }
    
    if (erts_system_profile_flags.runnable_ports && !erts_port_is_scheduled(p)) {
    	profile_runnable_port(p, am_active);
    }
    size = erts_encode_ext_size(BIF_ARG_3);
    if (size > sizeof(port_input))
	bytes = erts_alloc(ERTS_ALC_T_PORT_CALL_BUF, size);

    endp = bytes;
    erts_encode_ext(BIF_ARG_3, &endp);

    real_size = endp - bytes;
    if (real_size > size) {
	erl_exit(1, "%s, line %d: buffer overflow: %d word(s)\n",
		 __FILE__, __LINE__, endp - (bytes + size));
    }
    erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
    prc  = (char *) port_resp;
    fpe_was_unmasked = erts_block_fpe();
    ret = drv->call((ErlDrvData)p->drv_data, 
		    (unsigned) op,
		    (char *) bytes, 
		    (int) real_size,
		    &prc, 
		    (int) sizeof(port_result),
		    &ret_flags);
    erts_unblock_fpe(fpe_was_unmasked);
    if (IS_TRACED_FL(p, F_TRACE_SCHED_PORTS)) {
    	trace_sched_ports_where(p, am_out, am_call);
    }
    
    if (erts_system_profile_flags.runnable_ports && !erts_port_is_scheduled(p)) {
    	profile_runnable_port(p, am_inactive);
    }
   
    port_resp = (byte *) prc;
    p->caller = NIL;
    erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
#ifdef HARDDEBUG
    { 
	int z;
	printf("real_size = %ld,%d, ret = %d\r\n",real_size, 
	       (int) real_size, ret);
	printf("[");
	for(z = 0; z < real_size; ++z) {
	    printf("%d, ",(int) bytes[z]);
	}
	printf("]\r\n");
	printf("[");
	for(z = 0; z < ret; ++z) {
	    printf("%d, ",(int) port_resp[z]);
	}
	printf("]\r\n");
    }
#endif
    if (ret <= 0 || port_resp[0] != VERSION_MAGIC) { 
	/* Error or a binary without magic/ with wrong magic */
	goto error;
    }
    result_size = erts_decode_ext_size(port_resp, ret, 0);
    if (result_size < 0) {
	goto error;
    }
    hp = HAlloc(BIF_P, result_size);
    hp_end = hp + result_size;
    endp = port_resp;
    res = erts_decode_ext(&hp, &MSO(BIF_P), &endp);
    if (res == THE_NON_VALUE) {
	goto error;
    }
    HRelease(BIF_P, hp_end, hp);
    if (port_resp != port_result && !(ret_flags & DRIVER_CALL_KEEP_BUFFER)) {
	driver_free(port_resp);
    }
    if (bytes != &port_input[0])
	erts_free(ERTS_ALC_T_PORT_CALL_BUF, bytes);
    if (p)
	erts_port_release(p);
#ifdef ERTS_SMP
    ERTS_SMP_BIF_CHK_PENDING_EXIT(BIF_P, ERTS_PROC_LOCK_MAIN);
#else
    ERTS_BIF_CHK_EXITED(BIF_P);
#endif
    if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) {
    	trace_virtual_sched(BIF_P, am_in);
    }

    if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) {
    	profile_runnable_proc(BIF_P, am_active);
    }
  
    return res;
}
    
BIF_RETTYPE port_control_3(BIF_ALIST_3)
{
    Port* p;
    Uint op;
    Eterm res = THE_NON_VALUE;
    
    /* Virtual schedule out calling process before lock wait */
    if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) {
	trace_virtual_sched(BIF_P, am_out);
    }

    if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) {
    	profile_runnable_proc(BIF_P, am_inactive);
    }

    p = id_or_name2port(BIF_P, BIF_ARG_1);
    if (!p) {
    	/* Schedule the process before exiting */
    	if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) {
	    trace_virtual_sched(BIF_P, am_in);
    	}
	
	if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) {
    	    profile_runnable_proc(BIF_P, am_active);
	}
	
	BIF_ERROR(BIF_P, BADARG);
    }

    /* Trace the port for scheduling in */
    if (IS_TRACED_FL(p, F_TRACE_SCHED_PORTS)) {
    	trace_sched_ports_where(p, am_in, am_control);
    }
    
    if (erts_system_profile_flags.runnable_ports && !erts_port_is_scheduled(p)) {
    	profile_runnable_port(p, am_active);
    }

    if (term_to_Uint(BIF_ARG_2, &op))
	res = erts_port_control(BIF_P, p, op, BIF_ARG_3);
    
    /* Trace the port for scheduling out */
    if (IS_TRACED_FL(p, F_TRACE_SCHED_PORTS)) {
    	trace_sched_ports_where(p, am_out, am_control);
    }

    if (erts_system_profile_flags.runnable_ports && !erts_port_is_scheduled(p)) {
    	profile_runnable_port(p, am_inactive);
    }

    erts_port_release(p);
#ifdef ERTS_SMP
    ERTS_SMP_BIF_CHK_PENDING_EXIT(BIF_P, ERTS_PROC_LOCK_MAIN);
#else
    ERTS_BIF_CHK_EXITED(BIF_P);
#endif
    
    if (IS_TRACED_FL(BIF_P, F_TRACE_SCHED_PROCS)) {
	trace_virtual_sched(BIF_P, am_in);
    }
    
    if (erts_system_profile_flags.runnable_procs && erts_system_profile_flags.exclusive) {
    	profile_runnable_proc(BIF_P, am_active);
    }
    
    if (is_non_value(res)) {
	BIF_ERROR(BIF_P, BADARG);
    }
    BIF_RET(res);
}

BIF_RETTYPE port_close_1(BIF_ALIST_1)
{
    Port* p;
    erts_smp_proc_unlock(BIF_P, ERTS_PROC_LOCK_MAIN);
    p = id_or_name2port(NULL, BIF_ARG_1);
    if (!p) {
	erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
	BIF_ERROR(BIF_P, BADARG);
    }
    erts_do_exit_port(p, p->connected, am_normal);
    /* if !ERTS_SMP: since we terminate port with reason normal 
       we SHOULD never get an exit signal ourselves
       */
    erts_port_release(p);
    erts_smp_proc_lock(BIF_P, ERTS_PROC_LOCK_MAIN);
    BIF_RET(am_true);
}

BIF_RETTYPE port_connect_2(BIF_ALIST_2)
{
    Port* prt;
    Process* rp;
    Eterm pid = BIF_ARG_2;

    if (is_not_internal_pid(pid)) {
    error:
	BIF_ERROR(BIF_P, BADARG);
    }
    prt = id_or_name2port(BIF_P, BIF_ARG_1);
    if (!prt) {
	goto error;
    }

    rp = erts_pid2proc(BIF_P, ERTS_PROC_LOCK_MAIN,
		       pid, ERTS_PROC_LOCK_LINK);
    if (!rp) {
	erts_smp_port_unlock(prt);
	ERTS_SMP_ASSERT_IS_NOT_EXITING(BIF_P);
	goto error;
    }

    erts_add_link(&(rp->nlinks), LINK_PID, prt->id);
    erts_add_link(&(prt->nlinks), LINK_PID, pid);

    erts_smp_proc_unlock(rp, ERTS_PROC_LOCK_LINK);

    prt->connected = pid; /* internal pid */
    erts_smp_port_unlock(prt);
    BIF_RET(am_true);
}

BIF_RETTYPE port_set_data_2(BIF_ALIST_2)
{
    Port* prt;
    Eterm portid = BIF_ARG_1;
    Eterm data   = BIF_ARG_2;

    prt = id_or_name2port(BIF_P, portid);
    if (!prt) {
	BIF_ERROR(BIF_P, BADARG);
    }
    if (prt->bp != NULL) {
	free_message_buffer(prt->bp);
	prt->bp = NULL;
    }
    if (IS_CONST(data)) {
	prt->data = data;
    } else {
	Uint size;
	ErlHeapFragment* bp;
	Eterm* hp;

	size = size_object(data);
	prt->bp = bp = new_message_buffer(size);
	hp = bp->mem;
	prt->data = copy_struct(data, size, &hp, &bp->off_heap);
    }
    erts_smp_port_unlock(prt);
    BIF_RET(am_true);
}


BIF_RETTYPE port_get_data_1(BIF_ALIST_1)
{
    BIF_RETTYPE res;
    Port* prt;
    Eterm portid = BIF_ARG_1;

    prt = id_or_name2port(BIF_P, portid);
    if (!prt) {
	BIF_ERROR(BIF_P, BADARG);
    }
    if (prt->bp == NULL) {	/* MUST be CONST! */
	res = prt->data;
    } else {
	Eterm* hp = HAlloc(BIF_P, prt->bp->used_size);
	res = copy_struct(prt->data, prt->bp->used_size, &hp, &MSO(BIF_P));
    }
    erts_smp_port_unlock(prt);
    BIF_RET(res);
}

/* 
 * Open a port. Most of the work is not done here but rather in
 * the file io.c.
 * Error returns: -1 or -2 returned from open_driver (-2 implies
 * that *err_nump contains the error code; -1 means we don't really know what happened),
 * -3 if argument parsing failed or we are out of ports (*err_nump should contain
 * either BADARG or SYSTEM_LIMIT).
 */

static int
open_port(Process* p, Eterm name, Eterm settings, int *err_nump)
{
#define OPEN_PORT_ERROR(VAL) do { port_num = (VAL); goto do_return; } while (0)
    int i, port_num;
    Eterm option;
    Uint arity;
    Eterm* tp;
    Uint* nargs;
    erts_driver_t* driver;
    char* name_buf = NULL;
    SysDriverOpts opts;
    int binary_io;
    int soft_eof;
    Sint linebuf;
    Eterm edir = NIL;
    byte dir[MAXPATHLEN];

    /* These are the defaults */
    opts.packet_bytes = 0;
    opts.use_stdio = 1;
    opts.redir_stderr = 0;
    opts.read_write = 0;
    opts.hide_window = 0;
    opts.wd = NULL;
    opts.envir = NULL;
    opts.exit_status = 0;
    opts.overlapped_io = 0; 
    opts.spawn_type = ERTS_SPAWN_ANY; 
    opts.argv = NULL;
    binary_io = 0;
    soft_eof = 0;
    linebuf = 0;

    *err_nump = 0;

    if (is_not_list(settings) && is_not_nil(settings)) {
	goto badarg;
    }
    /*
     * Parse the settings.
     */

    if (is_not_nil(settings)) {
	nargs = list_val(settings);
	while (1) {
	    if (is_tuple_arity(*nargs, 2)) {
		tp = tuple_val(*nargs);
		arity = *tp++;
		option = *tp++;
		if (option == am_packet) {
		    if (is_not_small(*tp)) {
			goto badarg;
		    }
		    opts.packet_bytes = signed_val(*tp);
		    switch (opts.packet_bytes) {
		    case 1:
		    case 2:
		    case 4:
			break;
		    default:
			goto badarg;
		   }
		} else if (option == am_line) {
		    if (is_not_small(*tp)) {
			goto badarg;
		    }
		    linebuf = signed_val(*tp);
		    if (linebuf <= 0) {
			goto badarg;
		    }
		} else if (option == am_env) {
		    byte* bytes;
		    if ((bytes = convert_environment(p, *tp)) == NULL) {
			goto badarg;
		    }
		    opts.envir = (char *) bytes;
		} else if (option == am_args) {
		    char **av;
		    char **oav = opts.argv;
		    if ((av = convert_args(*tp)) == NULL) {
			goto badarg;
		    }
		    opts.argv = av;
		    if (oav) {
			opts.argv[0] = oav[0];
			oav[0] = erts_default_arg0;
			free_args(oav);
		    }

		} else if (option == am_arg0) {
		    char *a0;

		    if ((a0 = erts_convert_filename_to_native(*tp, ERTS_ALC_T_TMP, 1)) == NULL) {
			goto badarg;
		    }
		    if (opts.argv == NULL) {
			opts.argv = erts_alloc(ERTS_ALC_T_TMP, 
					       2 * sizeof(char **));
			opts.argv[0] = a0;
			opts.argv[1] = NULL;
		    } else {
			if (opts.argv[0] != erts_default_arg0) {
			    erts_free(ERTS_ALC_T_TMP, opts.argv[0]);
			}
			opts.argv[0] = a0;
		    }
		} else if (option == am_cd) {
		    edir = *tp;
		} else {
		    goto badarg;
		}
	    } else if (*nargs == am_stream) {
		opts.packet_bytes = 0;
	    } else if (*nargs == am_use_stdio) {
		opts.use_stdio = 1;
	    } else if (*nargs == am_stderr_to_stdout) {
		opts.redir_stderr = 1;
	    } else if (*nargs == am_line) {
		linebuf = 512;
	    } else if (*nargs == am_nouse_stdio) {
		opts.use_stdio = 0;
	    } else if (*nargs == am_binary) {
		binary_io = 1;
	    } else if (*nargs == am_in) {
		opts.read_write |= DO_READ;
	    } else if (*nargs == am_out) {
		opts.read_write |= DO_WRITE;
	    } else if (*nargs == am_eof) {
		soft_eof = 1;
	    } else if (*nargs == am_hide) {
		opts.hide_window = 1;
	    } else if (*nargs == am_exit_status) {
		opts.exit_status = 1;
	    } else if (*nargs == am_overlapped_io) {
		opts.overlapped_io = 1;
	    } else {
		goto badarg;
	    }
	    if (is_nil(*++nargs)) 
		break;
	    if (is_not_list(*nargs)) {
		goto badarg;
	    }
	    nargs = list_val(*nargs);
	}
    }
    if (opts.read_write == 0)	/* implement default */
	opts.read_write = DO_READ|DO_WRITE;

    /* Mutually exclusive arguments. */
    if((linebuf && opts.packet_bytes) || 
       (opts.redir_stderr && !opts.use_stdio)) {
	goto badarg;
    }

    /*
     * Parse the first argument and start the appropriate driver.
     */
    
    if (is_atom(name) || (i = is_string(name))) {
	/* a vanilla port */
	if (is_atom(name)) {
	    name_buf = (char *) erts_alloc(ERTS_ALC_T_TMP,
					   atom_tab(atom_val(name))->len+1);
	    sys_memcpy((void *) name_buf,
		       (void *) atom_tab(atom_val(name))->name, 
		       atom_tab(atom_val(name))->len);
	    name_buf[atom_tab(atom_val(name))->len] = '\0';
	} else {
	    name_buf = (char *) erts_alloc(ERTS_ALC_T_TMP, i + 1);
	    if (intlist_to_buf(name, name_buf, i) != i)
		erl_exit(1, "%s:%d: Internal error\n", __FILE__, __LINE__);
	    name_buf[i] = '\0';
	}
	driver = &vanilla_driver;
    } else {   
	if (is_not_tuple(name)) {
	    goto badarg;		/* Not a process or fd port */
	}
	tp = tuple_val(name);
	arity = *tp++;

	if (arity == make_arityval(0)) {
	    goto badarg;
	}
    
	if (*tp == am_spawn || *tp == am_spawn_driver) {	/* A process port */
	    if (arity != make_arityval(2)) {
		goto badarg;
	    }
	    name = tp[1];
	    if (is_atom(name)) {
		name_buf = (char *) erts_alloc(ERTS_ALC_T_TMP,
					       atom_tab(atom_val(name))->len+1);
		sys_memcpy((void *) name_buf,
			   (void *) atom_tab(atom_val(name))->name, 
			   atom_tab(atom_val(name))->len);
		name_buf[atom_tab(atom_val(name))->len] = '\0';
	    } else if ((i = is_string(name))) {
		name_buf = (char *) erts_alloc(ERTS_ALC_T_TMP, i + 1);
		if (intlist_to_buf(name, name_buf, i) != i)
		    erl_exit(1, "%s:%d: Internal error\n", __FILE__, __LINE__);
		name_buf[i] = '\0';
	    } else {
		goto badarg;
	    }
	    if (*tp == am_spawn_driver) {
		opts.spawn_type = ERTS_SPAWN_DRIVER;
	    }
	    driver = &spawn_driver;
	} else if (*tp == am_spawn_executable) {	/* A program */
	    /*
	     * {spawn_executable,Progname}
	     */
	    
	    if (arity != make_arityval(2)) {
		goto badarg;
	    }
	    name = tp[1];
	    if ((name_buf = erts_convert_filename_to_native(name,ERTS_ALC_T_TMP,0)) == NULL) {
		goto badarg;
	    }
	    opts.spawn_type = ERTS_SPAWN_EXECUTABLE;
	    driver = &spawn_driver;
	} else if (*tp == am_fd) { /* An fd port */
	    int n;
	    struct Sint_buf sbuf;
	    char* p;

	    if (arity != make_arityval(3)) {
		goto badarg;
	    }
	    if (is_not_small(tp[1]) || is_not_small(tp[2])) {
		goto badarg;
	    }
	    opts.ifd = unsigned_val(tp[1]);
	    opts.ofd = unsigned_val(tp[2]);

	    /* Syntesize name from input and output descriptor. */
	    name_buf = erts_alloc(ERTS_ALC_T_TMP,
				  2*sizeof(struct Sint_buf) + 2); 
	    p = Sint_to_buf(opts.ifd, &sbuf);
	    n = sys_strlen(p);
	    sys_strncpy(name_buf, p, n);
	    name_buf[n] = '/';
	    p = Sint_to_buf(opts.ofd, &sbuf);
	    sys_strcpy(name_buf+n+1, p);

	    driver = &fd_driver;
	} else {
	    goto badarg;
	}
    }

    if ((driver != &spawn_driver && opts.argv != NULL) ||
	(driver == &spawn_driver && 
	 opts.spawn_type != ERTS_SPAWN_EXECUTABLE && 
	 opts.argv != NULL)) {
	/* Argument vector only if explicit spawn_executable */
	goto badarg;
    }

    if (edir != NIL) {
	/* A working directory is expressed differently if spawn_executable, i.e. Unicode is handles 
	   for spawn_executable... */
	if (opts.spawn_type != ERTS_SPAWN_EXECUTABLE) {
	    Eterm iolist;
	    DeclareTmpHeap(heap,4,p);
	    int r;
	    
	    UseTmpHeap(4,p);
	    heap[0] = edir;
	    heap[1] = make_list(heap+2);
	    heap[2] = make_small(0);
	    heap[3] = NIL;
	    iolist = make_list(heap);
	    r = io_list_to_buf(iolist, (char*) dir, MAXPATHLEN);
	    UnUseTmpHeap(4,p);
	    if (r < 0) {
		goto badarg;
	    }
	    opts.wd = (char *) dir;
	} else {
	    if ((opts.wd = erts_convert_filename_to_native(edir,ERTS_ALC_T_TMP,0)) == NULL) {
		goto badarg;
	    }
	}
    }

    if (driver != &spawn_driver && opts.exit_status) {
	goto badarg;
    }
    
    if (IS_TRACED_FL(p, F_TRACE_SCHED_PROCS)) {
        trace_virtual_sched(p, am_out);
    }
    

    erts_smp_proc_unlock(p, ERTS_PROC_LOCK_MAIN);

    port_num = erts_open_driver(driver, p->id, name_buf, &opts, err_nump);

    erts_smp_proc_lock(p, ERTS_PROC_LOCK_MAIN);

    if (port_num < 0) {
	DEBUGF(("open_driver returned %d(%d)\n", port_num, *err_nump));
    	if (IS_TRACED_FL(p, F_TRACE_SCHED_PROCS)) {
            trace_virtual_sched(p, am_in);
    	}
	OPEN_PORT_ERROR(port_num);
    }
    
    if (IS_TRACED_FL(p, F_TRACE_SCHED_PROCS)) {
        trace_virtual_sched(p, am_in);
    }

    if (binary_io) {
	erts_port_status_bor_set(&erts_port[port_num],
				 ERTS_PORT_SFLG_BINARY_IO);
    }
    if (soft_eof) {
	erts_port_status_bor_set(&erts_port[port_num],
				 ERTS_PORT_SFLG_SOFT_EOF);
    }
    if (linebuf && erts_port[port_num].linebuf == NULL){
	erts_port[port_num].linebuf = allocate_linebuf(linebuf); 
	erts_port_status_bor_set(&erts_port[port_num],
				 ERTS_PORT_SFLG_LINEBUF_IO);
    }
 
 do_return:
    if (name_buf)
	erts_free(ERTS_ALC_T_TMP, (void *) name_buf);
    if (opts.argv) {
	free_args(opts.argv);
    }
    if (opts.wd && opts.wd != ((char *)dir)) {
	erts_free(ERTS_ALC_T_TMP, (void *) opts.wd);
    }
    return port_num;
    
 badarg:
    *err_nump = BADARG;
    OPEN_PORT_ERROR(-3);
    goto do_return;
#undef OPEN_PORT_ERROR
}

/* Arguments can be given i unicode and as raw binaries, convert filename is used to convert */
static char **convert_args(Eterm l)
{
    char **pp;
    char *b;
    int n;
    int i = 0;
    Eterm str;
    /* We require at least one element in list (argv[0]) */
    if (is_not_list(l) && is_not_nil(l)) {
	return NULL;
    }
    n = list_length(l);
    pp = erts_alloc(ERTS_ALC_T_TMP, (n + 2) * sizeof(char **));
    pp[i++] = erts_default_arg0;
    while (is_list(l)) {
	str = CAR(list_val(l));
	if ((b = erts_convert_filename_to_native(str,ERTS_ALC_T_TMP,1)) == NULL) {
	    int j;
	    for (j = 1; j < i; ++j)
		erts_free(ERTS_ALC_T_TMP, pp[j]);
	    erts_free(ERTS_ALC_T_TMP, pp);
	    return NULL;
	}	    
	pp[i++] = b;
	l = CDR(list_val(l));
    }
    pp[i] = NULL;
    return pp;
}

static void free_args(char **av)
{
    int i;
    if (av == NULL)
	return;
    for (i = 0; av[i] != NULL; ++i) {
	if (av[i] != erts_default_arg0) {
	    erts_free(ERTS_ALC_T_TMP, av[i]);
	}
    }
    erts_free(ERTS_ALC_T_TMP, av);
}
    

static byte* convert_environment(Process* p, Eterm env)
{
    Eterm all;
    Eterm* temp_heap;
    Eterm* hp;
    Uint heap_size;
    int n;
    byte* bytes;

    if ((n = list_length(env)) < 0) {
	return NULL;
    }
    heap_size = 2*(5*n+1);
    temp_heap = hp = (Eterm *) erts_alloc(ERTS_ALC_T_TMP, heap_size*sizeof(Eterm));
    bytes = NULL;		/* Indicating error */

    /*
     * All errors below are handled by jumping to 'done', to ensure that the memory
     * gets deallocated. Do NOT return directly from this function.
     */

    all = CONS(hp, make_small(0), NIL);
    hp += 2;

    while(is_list(env)) {
	Eterm tmp;
	Eterm* tp;

	tmp = CAR(list_val(env));
	if (is_not_tuple_arity(tmp, 2)) {
	    goto done;
	}
	tp = tuple_val(tmp);
	tmp = CONS(hp, make_small(0), NIL);
	hp += 2;
	if (tp[2] != am_false) {
	    tmp = CONS(hp, tp[2], tmp);
	    hp += 2;
	}
	tmp = CONS(hp, make_small('='), tmp);
	hp += 2;
	tmp = CONS(hp, tp[1], tmp);
	hp += 2;
	all = CONS(hp, tmp, all);
	hp += 2;
	env = CDR(list_val(env));
    }
    if (is_not_nil(env)) {
	goto done;
    }
    if ((n = io_list_len(all)) < 0) {
	goto done;
    }

    /*
     * Put the result in a binary (no risk for a memory leak that way).
     */
    (void) erts_new_heap_binary(p, NULL, n, &bytes);
    io_list_to_buf(all, (char*)bytes, n);

 done:
    erts_free(ERTS_ALC_T_TMP, temp_heap);
    return bytes;
}

/* ------------ decode_packet() and friends: */

struct packet_callback_args
{
    Process* p;  /* In */
    Eterm res;   /* Out */
    int string_as_bin; /* return strings as binaries (http_bin): */
    byte* aligned_ptr;
    Uint bin_sz;
    Eterm orig;
    Uint bin_offs;
    byte bin_bitoffs;
};

#define in_area(ptr,start,nbytes) \
    ((unsigned long)((char*)(ptr) - (char*)(start)) < (nbytes))

static Eterm
http_bld_string(struct packet_callback_args* pca, Uint **hpp, Uint *szp,
		const char *str, Sint len)
{
    Eterm res = THE_NON_VALUE;
    Uint size;
    int make_subbin;

    if (pca->string_as_bin) {
	size = heap_bin_size(len);
	make_subbin = (size > ERL_SUB_BIN_SIZE
		       && in_area(str, pca->aligned_ptr, pca->bin_sz));
	if (szp) {
	    *szp += make_subbin ? ERL_SUB_BIN_SIZE : size;
	}
	if (hpp) {
	    res = make_binary(*hpp);
	    if (make_subbin) {
		ErlSubBin* bin = (ErlSubBin*) *hpp;
		bin->thing_word = HEADER_SUB_BIN;
		bin->size = len;
		bin->offs = pca->bin_offs + ((byte*)str - pca->aligned_ptr);
		bin->orig = pca->orig;
		bin->bitoffs = pca->bin_bitoffs;
		bin->bitsize = 0;
		bin->is_writable = 0;
		*hpp += ERL_SUB_BIN_SIZE;
	    }
	    else {
		ErlHeapBin* bin = (ErlHeapBin*) *hpp;
		bin->thing_word = header_heap_bin(len);
		bin->size = len;
		memcpy(bin->data, str, len);
		*hpp += size;
	    }
	}
    }
    else {
	res = erts_bld_string_n(hpp, szp, str, len);
    }
    return res;
}

static int http_response_erl(void *arg, int major, int minor,
                             int status, const char* phrase, int phrase_len)
{
    /* {http_response,{Major,Minor},Status,"Phrase"} */
    struct packet_callback_args* pca = (struct packet_callback_args*) arg;    
    Eterm phrase_term, ver;
    Uint hsize = 3 + 5;
    Eterm* hp;
#ifdef DEBUG
    Eterm* hend;
#endif

    http_bld_string(pca, NULL, &hsize, phrase, phrase_len);
    hp = HAlloc(pca->p, hsize);
#ifdef DEBUG
    hend = hp + hsize;
#endif
    phrase_term = http_bld_string(pca, &hp, NULL, phrase, phrase_len);
    ver = TUPLE2(hp, make_small(major), make_small(minor));
    hp += 3;
    pca->res = TUPLE4(hp, am_http_response, ver, make_small(status), phrase_term);
    ASSERT(hp+5==hend);
    return 1;
}   
    
static Eterm http_bld_uri(struct packet_callback_args* pca,
			  Eterm** hpp, Uint* szp, const PacketHttpURI* uri)
{
    Eterm s1, s2;
    if (uri->type == URI_STAR) {
        return am_Times; /* '*' */
    }

    s1 = http_bld_string(pca, hpp, szp, uri->s1_ptr, uri->s1_len);

    switch (uri->type) {
    case URI_ABS_PATH:
        return erts_bld_tuple(hpp, szp, 2, am_abs_path, s1);
    case URI_HTTP:
    case URI_HTTPS:
        s2 = http_bld_string(pca, hpp, szp, uri->s2_ptr, uri->s2_len);
        return erts_bld_tuple
            (hpp, szp, 5, am_absoluteURI, 
             ((uri->type==URI_HTTP) ? am_http : am_https),
             s1, 
             ((uri->port==0) ? am_undefined : make_small(uri->port)),
             s2);
        
    case URI_STRING:
        return s1;
    case URI_SCHEME:
        s2 = http_bld_string(pca, hpp, szp, uri->s2_ptr, uri->s2_len);
        return erts_bld_tuple(hpp, szp, 3, am_scheme, s1, s2);
                              
    default:
        erl_exit(1, "%s, line %d: type=%u\n", __FILE__, __LINE__, uri->type);
    }
}

static int http_request_erl(void* arg, const http_atom_t* meth,
                            const char* meth_ptr, int meth_len,
                            const PacketHttpURI* uri, int major, int minor)
{
    struct packet_callback_args* pca = (struct packet_callback_args*) arg;    
    Eterm meth_term, uri_term, ver_term;
    Uint sz = 0;
    Uint* szp = &sz;
    Eterm* hp;
    Eterm** hpp = NULL;

    /* {http_request,Meth,Uri,Version} */

    for (;;) {
        meth_term = (meth!=NULL) ? meth->atom :
	    http_bld_string(pca, hpp, szp, meth_ptr, meth_len);
        uri_term = http_bld_uri(pca, hpp, szp, uri);
        ver_term = erts_bld_tuple(hpp, szp, 2,
                                  make_small(major), make_small(minor));
        pca->res = erts_bld_tuple(hpp, szp, 4, am_http_request, meth_term,
                                  uri_term, ver_term); 
        if (hpp != NULL) break;
        hpp = &hp;
        hp = HAlloc(pca->p, sz);
        szp = NULL;        
    }
    return 1;
}

static int
http_header_erl(void* arg, const http_atom_t* name, const char* name_ptr,
                int name_len, const char* value_ptr, int value_len)
{
    struct packet_callback_args* pca = (struct packet_callback_args*) arg;    
    Eterm bit_term, name_term, val_term;
    Uint sz = 6;
    Eterm* hp;
#ifdef DEBUG
    Eterm* hend;
#endif
    
    /* {http_header,Bit,Name,IValue,Value} */

    if (name == NULL) {
	http_bld_string(pca, NULL, &sz, name_ptr, name_len);
    }
    http_bld_string(pca, NULL, &sz, value_ptr, value_len);

    hp = HAlloc(pca->p, sz);
#ifdef DEBUG
    hend = hp + sz;
#endif	

    if (name != NULL) {
	bit_term = make_small(name->index+1);
	name_term = name->atom;
    }
    else {	
	bit_term = make_small(0);	
	name_term = http_bld_string(pca, &hp,NULL,name_ptr,name_len);
    }

    val_term = http_bld_string(pca, &hp, NULL, value_ptr, value_len);
    pca->res = TUPLE5(hp, am_http_header, bit_term, name_term, am_undefined, val_term);
    ASSERT(hp+6==hend);
    return 1;
}   

static int http_eoh_erl(void* arg)
{
    /* http_eoh */
    struct packet_callback_args* pca = (struct packet_callback_args*) arg;    
    pca->res = am_http_eoh;
    return 1;
}

static int http_error_erl(void* arg, const char* buf, int len)
{
    /* {http_error,Line} */
    struct packet_callback_args* pca = (struct packet_callback_args*) arg;
    Uint sz = 3;
    Eterm* hp;
#ifdef DEBUG
    Eterm* hend;
#endif

    http_bld_string(pca, NULL, &sz, buf, len);

    hp = HAlloc(pca->p, sz);
#ifdef DEBUG
    hend = hp + sz;
#endif
    pca->res = erts_bld_tuple(&hp, NULL, 2, am_http_error,
			      http_bld_string(pca, &hp, NULL, buf, len));
    ASSERT(hp==hend);
    return 1;
}

static
int ssl_tls_erl(void* arg, unsigned type, unsigned major, unsigned minor,
		const char* buf, int len, const char* prefix, int plen)
{
    struct packet_callback_args* pca = (struct packet_callback_args*) arg;
    Eterm* hp;
    Eterm ver;
    Eterm bin = new_binary(pca->p, NULL, plen+len);
    byte* bin_ptr = binary_bytes(bin);

    memcpy(bin_ptr+plen, buf, len);
    if (plen) {
        memcpy(bin_ptr, prefix, plen);
    }

    /* {ssl_tls,NIL,ContentType,{Major,Minor},Bin} */
    hp = HAlloc(pca->p, 3+6);
    ver = TUPLE2(hp, make_small(major), make_small(minor));
    hp += 3;
    pca->res = TUPLE5(hp, am_ssl_tls, NIL, make_small(type), ver, bin);
    return 1;
}


PacketCallbacks packet_callbacks_erl = {
    http_response_erl,
    http_request_erl,
    http_eoh_erl,
    http_header_erl,
    http_error_erl,
    ssl_tls_erl
};

/*
 decode_packet(Type,Bin,Options)
 Returns:
     {ok, PacketBodyBin, RestBin}
     {more, PacketSz | undefined}
     {error, invalid}
*/
BIF_RETTYPE decode_packet_3(BIF_ALIST_3)
{
    unsigned max_plen = 0;   /* Packet max length, 0=no limit */
    unsigned trunc_len = 0;  /* Truncate lines if longer, 0=no limit */
    int http_state = 0;      /* 0=request/response 1=header */
    int packet_sz;           /*-------Binaries involved: ------------------*/
    byte* bin_ptr;           /*| orig: original binary                     */
    byte bin_bitsz;          /*| bin: BIF_ARG_2, may be sub-binary of orig */
	                     /*| packet: prefix of bin                     */
    char* body_ptr;          /*| body: part of packet to return            */
    int body_sz;             /*| rest: bin without packet                  */
    struct packet_callback_args pca;
    enum PacketParseType type;
    Eterm* hp;
    Eterm* hend;
    ErlSubBin* rest;
    Eterm res;
    Eterm options;
    int code;

    if (!is_binary(BIF_ARG_2) || 
        (!is_list(BIF_ARG_3) && !is_nil(BIF_ARG_3))) {
        BIF_ERROR(BIF_P, BADARG);
    }
    switch (BIF_ARG_1) {
    case make_small(0): case am_raw: type = TCP_PB_RAW; break;
    case make_small(1): type = TCP_PB_1; break;
    case make_small(2): type = TCP_PB_2; break;
    case make_small(4): type = TCP_PB_4; break;
    case am_asn1: type = TCP_PB_ASN1; break;
    case am_sunrm: type = TCP_PB_RM; break;
    case am_cdr: type = TCP_PB_CDR; break;
    case am_fcgi: type = TCP_PB_FCGI; break;
    case am_line: type = TCP_PB_LINE_LF; break;
    case am_tpkt: type = TCP_PB_TPKT; break;
    case am_http: type = TCP_PB_HTTP; break;
    case am_httph: type = TCP_PB_HTTPH; break;
    case am_http_bin: type = TCP_PB_HTTP_BIN; break;
    case am_httph_bin: type = TCP_PB_HTTPH_BIN; break;
    case am_ssl_tls: type = TCP_PB_SSL_TLS; break;
    default:
        BIF_ERROR(BIF_P, BADARG);
    }

    options = BIF_ARG_3;
    while (!is_nil(options)) {
        Eterm* cons = list_val(options);
        if (is_tuple(CAR(cons))) {
            Eterm* tpl = tuple_val(CAR(cons));
            Uint val;
            if (tpl[0] == make_arityval(2) &&
		term_to_Uint(tpl[2],&val) && val <= UINT_MAX) {
                switch (tpl[1]) {
                case am_packet_size:
                    max_plen = val;
                    goto next_option;
                case am_line_length:
                    trunc_len = val;
                    goto next_option;
                }
            }
        }
        BIF_ERROR(BIF_P, BADARG);

    next_option:       
        options = CDR(cons);
    }


    pca.bin_sz = binary_size(BIF_ARG_2);
    ERTS_GET_BINARY_BYTES(BIF_ARG_2, bin_ptr, pca.bin_bitoffs, bin_bitsz);  
    if (pca.bin_bitoffs != 0) {
        pca.aligned_ptr = erts_alloc(ERTS_ALC_T_TMP, pca.bin_sz);
        erts_copy_bits(bin_ptr, pca.bin_bitoffs, 1, pca.aligned_ptr, 0, 1, pca.bin_sz*8);
    }
    else {
        pca.aligned_ptr = bin_ptr;
    }
    packet_sz = packet_get_length(type, (char*)pca.aligned_ptr, pca.bin_sz,
                                  max_plen, trunc_len, &http_state);
    if (!(packet_sz > 0 && packet_sz <= pca.bin_sz)) {
        if (packet_sz < 0) {
	    goto error;
        }
        else { /* not enough data */
            Eterm plen = (packet_sz==0) ? am_undefined : 
                erts_make_integer(packet_sz, BIF_P);
            Eterm* hp = HAlloc(BIF_P,3);        
            res = TUPLE2(hp, am_more, plen);
            goto done;
        }
    }
    /* We got a whole packet */

    body_ptr = (char*) pca.aligned_ptr;
    body_sz = packet_sz;
    packet_get_body(type, (const char**) &body_ptr, &body_sz);

    ERTS_GET_REAL_BIN(BIF_ARG_2, pca.orig, pca.bin_offs, pca.bin_bitoffs, bin_bitsz);
    pca.p = BIF_P;
    pca.res = THE_NON_VALUE;
    pca.string_as_bin = (type == TCP_PB_HTTP_BIN || type == TCP_PB_HTTPH_BIN);
    code = packet_parse(type, (char*)pca.aligned_ptr, packet_sz, &http_state,
			&packet_callbacks_erl, &pca);
    if (code == 0) { /* no special packet parsing, make plain binary */
        ErlSubBin* body;
        Uint hsz = 2*ERL_SUB_BIN_SIZE + 4;
        hp = HAlloc(BIF_P, hsz);
        hend = hp + hsz;

        body = (ErlSubBin *) hp;
        body->thing_word = HEADER_SUB_BIN;
        body->size = body_sz;
        body->offs = pca.bin_offs + (body_ptr - (char*)pca.aligned_ptr);
        body->orig = pca.orig;
        body->bitoffs = pca.bin_bitoffs;
        body->bitsize = 0;
        body->is_writable = 0;
        hp += ERL_SUB_BIN_SIZE;
        pca.res = make_binary(body);
    }
    else if (code > 0) {
	Uint hsz = ERL_SUB_BIN_SIZE + 4;
	ASSERT(pca.res != THE_NON_VALUE);
	hp = HAlloc(BIF_P, hsz);
	hend = hp + hsz;
    }
    else {
error:
	hp = HAlloc(BIF_P,3);        
	res = TUPLE2(hp, am_error, am_invalid);
	goto done;
    }

    rest = (ErlSubBin *) hp;
    rest->thing_word = HEADER_SUB_BIN;
    rest->size = pca.bin_sz - packet_sz;
    rest->offs = pca.bin_offs + packet_sz;
    rest->orig = pca.orig;
    rest->bitoffs = pca.bin_bitoffs;
    rest->bitsize = bin_bitsz;   /* The extra bits go into the rest. */
    rest->is_writable = 0;
    hp += ERL_SUB_BIN_SIZE;
    res = TUPLE3(hp, am_ok, pca.res, make_binary(rest));
    hp += 4;
    ASSERT(hp==hend); (void)hend;

done:
    if (pca.aligned_ptr != bin_ptr) {
        erts_free(ERTS_ALC_T_TMP, pca.aligned_ptr);
    }
    BIF_RET(res);
}

