/* * %CopyrightBegin% * * Copyright Ericsson AB 2013. 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% */ /* * Module: run_erl.c * */ #ifdef HAVE_CONFIG_H # include "config.h" #endif /* System includes */ #include #include #include #include #include #include #include #include /* OSE includes */ #include "ose.h" #include "ose_spi/ose_spi.h" #include "efs.h" #include "pm.h" #include "ose_spi/fm.sig" /* erts includes */ #include "run_erl.h" #include "run_erl_common.h" #include "safe_string.h" /* sn_printf, strn_cpy, strn_cat, etc */ typedef struct RunErlSetup_ { SIGSELECT signo; int run_daemon; char *logdir; char *command; char *pipename; char *blockname; } RunErlSetup; typedef struct ProgramState_ { /* child process */ int ifd, ofd; OSDOMAIN domain; PROCESS progpid, mainbid; struct PmProgramInfo *info; /* to_erl */ char w_pipe[FILENAME_BUFSIZ], r_pipe[FILENAME_BUFSIZ]; } ProgramState; union SIGNAL { SIGSELECT signo; RunErlSetup setup; struct FmReadPtr fm_read_ptr; struct FmWritePtr fm_write_ptr; }; static OSBOOLEAN hunt_in_block(char *block_name, char *process_name, PROCESS *pid); static int create_child_process(char *command_string, char *blockname, ProgramState *state); static OSBOOLEAN hunt_in_block(char *block_name, char *process_name, PROCESS *pid) { struct OS_pid_list *list; PROCESS block_id = OSE_ILLEGAL_PROCESS; int i; char *name; *pid = OSE_ILLEGAL_PROCESS; list = get_bid_list(0); if (!list) return 0; for (i = 0; i < list->count; i++) { if (list->list[i] == get_bid(current_process())) continue; name = (char*)get_pid_info(list->list[i], OSE_PI_NAME); if (name) { if (strcmp(name,block_name) == 0) { block_id = list->list[i]; free_buf((union SIGNAL**)&name); break; } free_buf((union SIGNAL**)&name); } } free_buf((union SIGNAL**)&list); if (block_id == OSE_ILLEGAL_PROCESS) return 0; list = get_pid_list(block_id); if (!list) return 0; for (i = 0; i < list->count; i++) { name = (char*)get_pid_info(list->list[i], OSE_PI_NAME); if (name) { if (strcmp(name,process_name) == 0) { *pid = list->list[i]; free_buf((union SIGNAL**)&name); break; } free_buf((union SIGNAL**)&name); } } free_buf((union SIGNAL**)&list); if (*pid == OSE_ILLEGAL_PROCESS) return 0; return 1; } static int create_child_process(char *command_string, char *blockname, ProgramState *state) { char *command = command_string; char *argv; int i = 0; int ret_status; PmStatus pm_status; int tmp_io[2]; int fd_arr[3]; int ifd[2], ofd[2]; char *handle; struct PmLoadModuleInfoReply *mod_info; /* Parse out cmd and argv from the command string */ while (1) { if (command[i] == ' ' || command[i] == '\0') { if (command[i] == '\0') argv = NULL; else { command[i] = '\0'; argv = command_string + i + 1; } break; } i++; } if (blockname) handle = blockname; else handle = simple_basename(command); if (ose_pm_load_module_info(handle,&mod_info) == PM_SUCCESS) { /* Already installed */ free_buf((union SIGNAL**)&mod_info); } else if ((pm_status = ose_pm_install_load_module(0,"ELF",command,handle,0,0,NULL)) != PM_SUCCESS) { ERROR1(LOG_ERR,"ose_pm_install_load_module failed - pmstatus: 0x%08x\n", pm_status); return 0; } state->domain = PM_NEW_DOMAIN; pm_status = ose_pm_create_program(&state->domain, handle, 0, 0 , NULL, &state->progpid, &state->mainbid); if (pm_status != PM_SUCCESS) { if (pm_status == PM_EINSTALL_HANDLE_IN_USE) ERROR1(LOG_ERR,"ose_pm_create_program failed - " "install handle \"%s\" is in use. You can specify another " "install handle by using the -block option to run_erl.\n",handle); else ERROR1(LOG_ERR,"ose_pm_create_program failed - pmstatus: 0x%08x\n", pm_status); return 0; } pm_status = ose_pm_program_info(state->progpid, &state->info); /* FIXME don't forget to free this ((union SIGNAL **)&info) */ if (pm_status != PM_SUCCESS) { ERROR1(LOG_ERR,"ose_pm_program_info failed - pmstatus: 0x%08x\n", pm_status); return 0; } /* We only clone stdin+stdout, what about stderr? */ /* create pipes */ if (pipe(ifd) < 0) { if (errno == ENOENT) ERRNO_ERR0(LOG_ERR,"The /pipe file system is not available\n"); else ERRNO_ERR0(LOG_ERR,"pipe ifd failed\n"); return 0; } if (pipe(ofd) < 0) { ERRNO_ERR0(LOG_ERR,"pipe ofd failed\n"); return 0; } /* FIXME Lock? */ /* backup our stdin stdout */ if ((tmp_io[0] = dup(0)) < 0) { ERRNO_ERR0(LOG_ERR,"dup 0 failed\n"); return 0; } if ((tmp_io[1] = dup(1)) < 0) { ERRNO_ERR0(LOG_ERR,"dup 1 failed\n"); return 0; } /* set new pipe to fd 0,1 */ if (dup2(ifd[1], 1) < 0) { ERRNO_ERR0(LOG_ERR,"dup2 1 failed\n"); return 0; } if (dup2(ofd[0], 0) < 0) { ERRNO_ERR0(LOG_ERR,"dup2 0 failed\n"); return 0; } /* clone array to newly created */ fd_arr[0] = 2; /* Number of fd's */ fd_arr[1] = 0; fd_arr[2] = 1; if ((ret_status = efs_clone_array(state->info->main_process, fd_arr)) != EFS_SUCCESS) { ERROR1(LOG_ERR,"efs_close_array filed, errcode: %d\n", ret_status); return 0; } if (dup2(tmp_io[1], 1) < 0) { ERRNO_ERR0(LOG_ERR,"restoring dup2 1 failed\n"); return 0; } if (dup2(tmp_io[0], 0) < 0) { ERRNO_ERR0(LOG_ERR,"restoring dup2 1 failed\n"); return 0; } /* close loose-ends */ sf_close(tmp_io[0]); sf_close(tmp_io[1]); sf_close(ifd[1]); sf_close(ofd[0]); state->ifd = ifd[0]; state->ofd = ofd[1]; if (argv && set_env(state->progpid, "ARGV", argv)) { ERRNO_ERR0(LOG_ERR,"something went wrong with set_env\n"); } /* * Start the program. */ pm_status = ose_pm_start_program(state->progpid); if (pm_status != PM_SUCCESS) { ERROR1(LOG_ERR,"ose_pm_install_load_module failed - pmstatus: 0x%08x\n", pm_status); return 0; } return 1; } #define SET_AIO(REQ,FD,SIZE,BUFF) \ /* Make sure to clean data structure of previous request */ \ memset(&(REQ),0,sizeof(REQ)); \ (REQ).aio_fildes = FD; \ (REQ).aio_offset = FM_POSITION_CURRENT; \ (REQ).aio_nbytes = SIZE; \ (REQ).aio_buf = BUFF; \ (REQ).aio_sigevent.sigev_notify = SIGEV_NONE #define READ_AIO(REQ,FD,SIZE,BUFF) do { \ SET_AIO(REQ,FD,SIZE,BUFF); \ if (aio_read(&(REQ)) != 0) \ ERRNO_ERR1(LOG_ERR,"aio_read of child_read_req(%d) failed\n",FD); \ } while (0) #define WRITE_AIO(FD,SIZE,BUFF) do { \ struct aiocb *write_req = malloc(sizeof(struct aiocb)); \ char *write_buff = malloc(sizeof(char)*SIZE); \ memcpy(write_buff,BUFF,SIZE); \ SET_AIO(*write_req,FD,SIZE,write_buff); \ if (aio_write(write_req) != 0) \ ERRNO_ERR1(LOG_ERR,"aio_write of write_req(%d) failed\n",FD); \ } while(0) int pass_on(ProgramState *state); int pass_on(ProgramState *s) { SIGSELECT sigsel[] = {0,FM_READ_PTR_REPLY}; union SIGNAL *sig; char child_read_buff[BUFSIZ], pipe_read_buff[BUFSIZ]; struct aiocb child_read_req, pipe_read_req; int rfd, wfd = 0; FmHandle rfh, child_rfh; int outstanding_writes = 0, got_some = 0, child_done = 0; if ((rfd = sf_open(s->r_pipe, O_RDONLY, 0)) < 0) { ERRNO_ERR1(LOG_ERR,"Could not open FIFO '%s' for reading.\n", s->r_pipe); rfd = 0; return 1; } attach(NULL,s->progpid); /* Open the log file */ erts_run_erl_log_open(); efs_examine_fd(rfd,FLIB_FD_HANDLE,&rfh); efs_examine_fd(s->ifd,FLIB_FD_HANDLE,&child_rfh); READ_AIO(child_read_req,s->ifd,BUFSIZ,child_read_buff); READ_AIO(pipe_read_req,rfd,BUFSIZ,pipe_read_buff); while (1) { time_t now,last_activity; time(&last_activity); sig = receive_w_tmo(erts_run_erl_log_alive_minutes()*60000,sigsel); time(&now); if (sig) { erts_run_erl_log_activity(0,now,last_activity); } else { /* timeout */ erts_run_erl_log_activity(1,now,last_activity); continue; } switch (sig->signo) { case OS_ATTACH_SIG: { if (rfd) { sf_close(rfd); rfd = 0; } free_buf(&sig); child_done = 1; /* Make sure to to let all outstanding write request finish */ if (outstanding_writes) break; if (wfd) sf_close(wfd); return 0; } case FM_WRITE_PTR_REPLY: { if (sig->fm_write_ptr.status == EFS_SUCCESS) { if (sig->fm_write_ptr.actual < sig->fm_write_ptr.requested) { WRITE_AIO(wfd, sig->fm_write_ptr.requested-sig->fm_write_ptr.actual, sig->fm_write_ptr.buffer+sig->fm_write_ptr.actual); } } else { /* Assume to_erl has terminated. */ sf_close(wfd); wfd = 0; } free((char*)sig->fm_write_ptr.buffer); aio_dispatch(sig); if ((--outstanding_writes == 0) && child_done) { if (wfd) sf_close(wfd); return 0; } break; } case FM_READ_PTR_REPLY: { /* Child fd */ if (sig->fm_read_ptr.handle == child_rfh) { /* Child terminated */ if (sig->fm_read_ptr.status != EFS_SUCCESS || sig->fm_read_ptr.actual == 0) { if (rfd) { sf_close(rfd); rfd = 0; } if (sig->fm_read_ptr.status != EFS_SUCCESS) { ERROR0(LOG_ERR,"Erlang closed the connection."); aio_dispatch(sig); return 1; } /* child closed connection gracefully */ aio_dispatch(sig); if (outstanding_writes) { child_done = 1; break; } if (wfd) sf_close(wfd); return 0; } else { erts_run_erl_log_write(sig->fm_read_ptr.buffer, sig->fm_read_ptr.actual); if (wfd) { WRITE_AIO(wfd, sig->fm_read_ptr.actual, sig->fm_read_ptr.buffer); outstanding_writes++; } aio_dispatch(sig); READ_AIO(child_read_req, s->ifd,BUFSIZ, child_read_buff); } /* pipe fd */ } else if (sig->fm_read_ptr.handle == rfh) { if (sig->fm_read_ptr.status != EFS_SUCCESS) { if(rfd) sf_close(rfd); if(wfd) sf_close(wfd); aio_dispatch(sig); ERRNO_ERR0(LOG_ERR,"Error in reading from FIFO."); return 1; } if (sig->fm_read_ptr.actual == 0) { /* to_erl closed its end of the pipe */ aio_dispatch(sig); sf_close(rfd); rfd = sf_open(s->r_pipe,O_RDONLY|DONT_BLOCK_PLEASE, 0); if (rfd < 0) { ERRNO_ERR1(LOG_ERR,"Could not open FIFO '%s' for reading.", s->r_pipe); rfd = 0; } else { READ_AIO(pipe_read_req,rfd,BUFSIZ,pipe_read_buff); } got_some = 0; /* reset for next session */ } else { int len = sig->fm_read_ptr.actual; char *buffer = sig->fm_read_ptr.buffer; if (!wfd) { /* Try to open the write pipe to to_erl. Now that we got some data * from to_erl, to_erl should already be reading this pipe - open * should succeed. But in case of error, we just ignore it. */ if ((wfd = sf_open(s->w_pipe, O_WRONLY|DONT_BLOCK_PLEASE, 0)) < 0) { erts_run_erl_log_status("Client expected on FIFO %s, " "but can't open (len=%d)\n", s->w_pipe, sig->fm_read_ptr.actual); sf_close(rfd); rfd = sf_open(s->r_pipe, O_RDONLY|DONT_BLOCK_PLEASE, 0); if (rfd < 0) { ERRNO_ERR1(LOG_ERR,"Could not open FIFO '%s' for reading.", s->r_pipe); return 1; } wfd = 0; } else { #ifdef DEBUG erts_run_erl_log_status("run_erl: %s opened for writing\n", s->w_pipe); #endif } } if (!got_some && wfd && buffer[0] == '\014') { char wbuf[30]; int wlen = sn_printf(wbuf,sizeof(wbuf),"[run_erl v%u-%u]\n", RUN_ERL_HI_VER, RUN_ERL_LO_VER); /* For some reason this, the first write aio seems to not get an FM_WRITE_PTR_REPLY, so we do not do: outstanding_writes++; */ WRITE_AIO(wfd, wlen, wbuf); } got_some = 1; /* Write the message */ #ifdef DEBUG erts_run_erl_log_status("Pty master write; "); #endif len = erts_run_erl_extract_ctrl_seq(buffer,len); if (len > 0) { int wlen = erts_run_erl_write_all(s->ofd, buffer, len); if (wlen != len) { aio_dispatch(sig); ERRNO_ERR0(LOG_ERR,"Error in writing to terminal."); if(rfd) sf_close(rfd); if(wfd) sf_close(wfd); return 1; } } #ifdef DEBUG erts_run_erl_log_status("OK\n"); #endif aio_dispatch(sig); READ_AIO(pipe_read_req,rfd,BUFSIZ,pipe_read_buff); } } break; } default: { free_buf(&sig); break; } } } } OS_PROCESS(run_erl_process) { char *logdir, *command, *blockname; SIGSELECT sigsel[] = {1,ERTS_SIGNAL_RUN_ERL_SETUP}; union SIGNAL *sig = receive(sigsel); ProgramState state; char pipename[FILENAME_BUFSIZ]; state.info = NULL; logdir = strdup(sig->setup.logdir); command = strdup(sig->setup.command); strn_cpy(pipename,sizeof(pipename),sig->setup.pipename); if (sig->setup.blockname) blockname = strdup(sig->setup.blockname); else blockname = NULL; erts_run_erl_log_init(sig->setup.run_daemon, logdir); free_buf(&sig); if (erts_run_erl_open_fifo(pipename,state.w_pipe,state.r_pipe)) kill_proc(current_process()); if (create_child_process(command,blockname,&state)) pass_on(&state); free(logdir); free(command); if (blockname) free(blockname); if (state.info) free_buf(((union SIGNAL**)&state.info)); sf_close(state.ifd); sf_close(state.ofd); unlink(state.w_pipe); unlink(state.r_pipe); kill_proc(current_process()); } int run_erl(int argc,char **argv) { char *pipename, *logdir, *command, *blockname = NULL; int pipename_len, logdir_len, command_len, blockname_len = 0; int i = 1, run_daemon = 0; PROCESS pid; SIGSELECT sigsel[] = {0}; union SIGNAL *sig; if(argc < 4) { fprintf(stderr,RUN_ERL_USAGE,"run_erl"); return 1; } while (1) { if (argv[i][0] != '-') break; if (!strcmp(argv[i],"-daemon")) { run_daemon = 1; i++; continue; } if (!strcmp(argv[i],"-block")) { blockname = argv[i+1]; blockname_len = strlen(argv[i+1]) + 1; i+=2; continue; } fprintf(stderr,RUN_ERL_USAGE,"run_erl"); return 1; } pipename = argv[i++]; logdir = argv[i++]; command = argv[i++]; /* + 1 to include NULL at end */ logdir_len = strlen(logdir) + 1; command_len = strlen(command) + 1; pipename_len = strlen(pipename) + 1; if (run_daemon) { /* We request that the run_erl_process should be started from the main process so that it does not die when the shell command returns */ PROCESS main_pid; hunt_in_block("run_erl","main",&main_pid); sig = alloc(sizeof(sig),ERTS_SIGNAL_RUN_ERL_DAEMON); send(&sig,main_pid); sig = receive(sigsel); pid = sender(&sig); free_buf(&sig); } else { pid = create_process(OS_BG_PROC,"run_erl_process", run_erl_process, 0x800, 0, 0, 0, NULL, 0, 0); } sig = alloc(sizeof(RunErlSetup)+ logdir_len+command_len+pipename_len+blockname_len, ERTS_SIGNAL_RUN_ERL_SETUP); sig->setup.run_daemon = run_daemon; sig->setup.logdir = ((char*)sig)+sizeof(RunErlSetup); sig->setup.command = ((char*)sig)+sizeof(RunErlSetup)+logdir_len; sig->setup.pipename = ((char*)sig)+sizeof(RunErlSetup)+logdir_len+command_len; if (blockname) sig->setup.blockname = ((char*)sig)+sizeof(RunErlSetup)+ logdir_len+command_len+pipename_len; else sig->setup.blockname = NULL; strcpy(sig->setup.logdir,logdir); strcpy(sig->setup.command,command); strcpy(sig->setup.pipename,pipename); if (blockname) strcpy(sig->setup.blockname,blockname); send(&sig,pid); if (run_daemon) { /* We are a daemon, error msgs will be sent to ramlog */ start(pid); return 1; } /* We are not daemon, error msgs will be sent to stderr and we block here */ efs_clone(pid); start(pid); attach(NULL,pid); sig = receive(sigsel); return 1; }