/* * %CopyrightBegin% * * Copyright Ericsson AB 2001-2011. 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 #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; Uint size; 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 (erts_iolist_size(all, &size)) { goto done; } /* * Put the result in a binary (no risk for a memory leak that way). */ (void) erts_new_heap_binary(p, NULL, size, &bytes); io_list_to_buf(all, (char*)bytes, size); 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); }