diff options
Diffstat (limited to 'erts/etc/unix/run_erl.c')
-rw-r--r-- | erts/etc/unix/run_erl.c | 1298 |
1 files changed, 1298 insertions, 0 deletions
diff --git a/erts/etc/unix/run_erl.c b/erts/etc/unix/run_erl.c new file mode 100644 index 0000000000..4bb148df98 --- /dev/null +++ b/erts/etc/unix/run_erl.c @@ -0,0 +1,1298 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. 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 + * + * This module implements a reader/writer process that opens two specified + * FIFOs, one for reading and one for writing; reads from the read FIFO + * and writes to stdout and the write FIFO. + * + ________ _________ + | |--<-- pipe.r (fifo1) --<--| | + | to_erl | | run_erl | (parent) + |________|-->-- pipe.w (fifo2) -->--|_________| + ^ master pty + | + | slave pty + ____V____ + | | + | "erl" | (child) + |_________| +*/ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#ifdef HAVE_WORKING_POSIX_OPENPT +#define _XOPEN_SOURCE 600 +#endif +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/select.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <dirent.h> +#include <termios.h> +#include <time.h> +#ifndef NO_SYSLOG +# include <syslog.h> +#endif +#ifdef HAVE_PTY_H +# include <pty.h> +#endif +#ifdef HAVE_UTMP_H +# include <utmp.h> +#endif +#ifdef HAVE_UTIL_H +# include <util.h> +#endif +#ifdef HAVE_SYS_IOCTL_H +# include <sys/ioctl.h> +#endif + +#include "run_erl.h" +#include "safe_string.h" /* sn_printf, strn_cpy, strn_cat, etc */ + +#ifdef O_NONBLOCK +# define DONT_BLOCK_PLEASE O_NONBLOCK +#else +# define DONT_BLOCK_PLEASE O_NDELAY +# ifndef EAGAIN +# define EAGAIN -3898734 +# endif +#endif + +#define noDEBUG + +#define DEFAULT_LOG_GENERATIONS 5 +#define LOG_MAX_GENERATIONS 1000 /* No more than 1000 log files */ +#define LOG_MIN_GENERATIONS 2 /* At least two to switch between */ +#define DEFAULT_LOG_MAXSIZE 100000 +#define LOG_MIN_MAXSIZE 1000 /* Smallast value for changing log file */ +#define LOG_STUBNAME "erlang.log." +#define LOG_PERM 0664 +#define DEFAULT_LOG_ACTIVITY_MINUTES 5 +#define DEFAULT_LOG_ALIVE_MINUTES 15 +#define DEFAULT_LOG_ALIVE_FORMAT "%a %b %e %T %Z %Y" +#define ALIVE_BUFFSIZ 256 + +#define PERM 0600 +#define STATUSFILENAME "/run_erl.log" +#define PIPE_STUBNAME "erlang.pipe" +#define PIPE_STUBLEN strlen(PIPE_STUBNAME) + +#ifndef FILENAME_MAX +#define FILENAME_MAX 250 +#endif + +#ifndef O_SYNC +#define O_SYNC 0 +#define USE_FSYNC 1 +#endif + +#define MAX(x,y) ((x) > (y) ? (x) : (y)) + +#define FILENAME_BUFSIZ FILENAME_MAX + +/* prototypes */ +static void usage(char *); +static int create_fifo(char *name, int perm); +static int open_pty_master(char **name); +static int open_pty_slave(char *name); +static void pass_on(pid_t); +static void exec_shell(char **); +static void status(const char *format,...); +static void error_logf(int priority, int line, const char *format,...); +static void catch_sigchild(int); +static int next_log(int log_num); +static int prev_log(int log_num); +static int find_next_log_num(void); +static int open_log(int log_num, int flags); +static void write_to_log(int* lfd, int* log_num, char* buf, int len); +static void daemon_init(void); +static char *simple_basename(char *path); +static void init_outbuf(void); +static int outbuf_size(void); +static void clear_outbuf(void); +static char* outbuf_first(void); +static void outbuf_delete(int bytes); +static void outbuf_append(const char* bytes, int n); +static int write_all(int fd, const char* buf, int len); +static int extract_ctrl_seq(char* buf, int len); +static void set_window_size(unsigned col, unsigned row); + + +#ifdef DEBUG +static void show_terminal_settings(struct termios *t); +#endif + +/* static data */ +static char fifo1[FILENAME_BUFSIZ], fifo2[FILENAME_BUFSIZ]; +static char statusfile[FILENAME_BUFSIZ]; +static char log_dir[FILENAME_BUFSIZ]; +static char pipename[FILENAME_BUFSIZ]; +static FILE *stdstatus = NULL; +static int log_generations = DEFAULT_LOG_GENERATIONS; +static int log_maxsize = DEFAULT_LOG_MAXSIZE; +static int log_alive_minutes = DEFAULT_LOG_ALIVE_MINUTES; +static int log_activity_minutes = DEFAULT_LOG_ACTIVITY_MINUTES; +static int log_alive_in_gmt = 0; +static char log_alive_format[ALIVE_BUFFSIZ+1]; +static int run_daemon = 0; +static char *program_name; +static int mfd; /* master pty fd */ +static unsigned protocol_ver = RUN_ERL_LO_VER; /* assume lowest to begin with */ + +/* + * Output buffer. + * + * outbuf_base <= outbuf_out <= outbuf_in <= outbuf_base+outbuf_total + */ +static char* outbuf_base; +static int outbuf_total; +static char* outbuf_out; +static char* outbuf_in; + +#if defined(NO_SYSCONF) || !defined(_SC_OPEN_MAX) +# if defined(OPEN_MAX) +# define HIGHEST_FILENO() OPEN_MAX +# else +# define HIGHEST_FILENO() 64 /* arbitrary value */ +# endif +#else +# define HIGHEST_FILENO() sysconf(_SC_OPEN_MAX) +#endif + + +#ifdef NO_SYSLOG +# define OPEN_SYSLOG() ((void) 0) +#else +# define OPEN_SYSLOG() openlog(simple_basename(program_name), \ + LOG_PID|LOG_CONS|LOG_NOWAIT,LOG_USER) +#endif + +#define ERROR0(Prio,Format) error_logf(Prio,__LINE__,Format"\n") +#define ERROR1(Prio,Format,A1) error_logf(Prio,__LINE__,Format"\n",A1) +#define ERROR2(Prio,Format,A1,A2) error_logf(Prio,__LINE__,Format"\n",A1,A2) + +#ifdef HAVE_STRERROR +# define ADD_ERRNO(Format) "errno=%d '%s'\n"Format"\n",errno,strerror(errno) +#else +# define ADD_ERRNO(Format) "errno=%d\n"Format"\n",errno +#endif +#define ERRNO_ERR0(Prio,Format) error_logf(Prio,__LINE__,ADD_ERRNO(Format)) +#define ERRNO_ERR1(Prio,Format,A1) error_logf(Prio,__LINE__,ADD_ERRNO(Format),A1) + + +int main(int argc, char **argv) +{ + int childpid; + int sfd; + int fd; + char *p, *ptyslave=NULL; + int i = 1; + int off_argv; + + program_name = argv[0]; + + if(argc<4) { + usage(argv[0]); + exit(1); + } + + init_outbuf(); + + if (!strcmp(argv[1],"-daemon")) { + daemon_init(); + ++i; + } + + off_argv = i; + strn_cpy(pipename, sizeof(pipename), argv[i++]); + strn_cpy(log_dir, sizeof(log_dir), argv[i]); + strn_cpy(statusfile, sizeof(statusfile), log_dir); + strn_cat(statusfile, sizeof(statusfile), STATUSFILENAME); + +#ifdef DEBUG + status("%s: pid is : %d\n", argv[0], getpid()); +#endif + + /* Get values for LOG file handling from the environment */ + if ((p = getenv("RUN_ERL_LOG_ALIVE_MINUTES"))) { + log_alive_minutes = atoi(p); + if (!log_alive_minutes) { + ERROR1(LOG_ERR,"Minimum value for RUN_ERL_LOG_ALIVE_MINUTES is 1 " + "(current value is %s)",p); + } + log_activity_minutes = log_alive_minutes / 3; + if (!log_activity_minutes) { + ++log_activity_minutes; + } + } + if ((p = getenv("RUN_ERL_LOG_ACTIVITY_MINUTES"))) { + log_activity_minutes = atoi(p); + if (!log_activity_minutes) { + ERROR1(LOG_ERR,"Minimum value for RUN_ERL_LOG_ACTIVITY_MINUTES is 1 " + "(current value is %s)",p); + } + } + if ((p = getenv("RUN_ERL_LOG_ALIVE_FORMAT"))) { + if (strlen(p) > ALIVE_BUFFSIZ) { + ERROR1(LOG_ERR, "RUN_ERL_LOG_ALIVE_FORMAT can contain a maximum of " + "%d characters", ALIVE_BUFFSIZ); + } + strn_cpy(log_alive_format, sizeof(log_alive_format), p); + } else { + strn_cpy(log_alive_format, sizeof(log_alive_format), DEFAULT_LOG_ALIVE_FORMAT); + } + if ((p = getenv("RUN_ERL_LOG_ALIVE_IN_UTC")) && strcmp(p,"0")) { + ++log_alive_in_gmt; + } + if ((p = getenv("RUN_ERL_LOG_GENERATIONS"))) { + log_generations = atoi(p); + if (log_generations < LOG_MIN_GENERATIONS) + ERROR1(LOG_ERR,"Minimum RUN_ERL_LOG_GENERATIONS is %d", LOG_MIN_GENERATIONS); + if (log_generations > LOG_MAX_GENERATIONS) + ERROR1(LOG_ERR,"Maximum RUN_ERL_LOG_GENERATIONS is %d", LOG_MAX_GENERATIONS); + } + + if ((p = getenv("RUN_ERL_LOG_MAXSIZE"))) { + log_maxsize = atoi(p); + if (log_maxsize < LOG_MIN_MAXSIZE) + ERROR1(LOG_ERR,"Minimum RUN_ERL_LOG_MAXSIZE is %d", LOG_MIN_MAXSIZE); + } + + /* + * Create FIFOs and open them + */ + + if(*pipename && pipename[strlen(pipename)-1] == '/') { + /* The user wishes us to find a unique pipe name in the specified */ + /* directory */ + int highest_pipe_num = 0; + DIR *dirp; + struct dirent *direntp; + + dirp = opendir(pipename); + if(!dirp) { + ERRNO_ERR1(LOG_ERR,"Can't access pipe directory '%s'.", pipename); + exit(1); + } + + /* Check the directory for existing pipes */ + + while((direntp=readdir(dirp)) != NULL) { + if(strncmp(direntp->d_name,PIPE_STUBNAME,PIPE_STUBLEN)==0) { + int num = atoi(direntp->d_name+PIPE_STUBLEN+1); + if(num > highest_pipe_num) + highest_pipe_num = num; + } + } + closedir(dirp); + strn_catf(pipename, sizeof(pipename), "%s.%d", + PIPE_STUBNAME, highest_pipe_num+1); + } /* if */ + + /* write FIFO - is read FIFO for `to_erl' program */ + strn_cpy(fifo1, sizeof(fifo1), pipename); + strn_cat(fifo1, sizeof(fifo1), ".r"); + if (create_fifo(fifo1, PERM) < 0) { + ERRNO_ERR1(LOG_ERR,"Cannot create FIFO %s for writing.", fifo1); + exit(1); + } + + /* read FIFO - is write FIFO for `to_erl' program */ + strn_cpy(fifo2, sizeof(fifo2), pipename); + strn_cat(fifo2, sizeof(fifo2), ".w"); + + /* Check that nobody is running run_erl already */ + if ((fd = open (fifo2, O_WRONLY|DONT_BLOCK_PLEASE, 0)) >= 0) { + /* Open as client succeeded -- run_erl is already running! */ + fprintf(stderr, "Erlang already running on pipe %s.\n", pipename); + close(fd); + exit(1); + } + if (create_fifo(fifo2, PERM) < 0) { + ERRNO_ERR1(LOG_ERR,"Cannot create FIFO %s for reading.", fifo2); + exit(1); + } + + /* + * Open master pseudo-terminal + */ + + if ((mfd = open_pty_master(&ptyslave)) < 0) { + ERRNO_ERR0(LOG_ERR,"Could not open pty master"); + exit(1); + } + + /* + * Now create a child process + */ + + if ((childpid = fork()) < 0) { + ERRNO_ERR0(LOG_ERR,"Cannot fork"); + exit(1); + } + if (childpid == 0) { + /* Child */ + close(mfd); + /* disassociate from control terminal */ +#ifdef USE_SETPGRP_NOARGS /* SysV */ + setpgrp(); +#elif defined(USE_SETPGRP) /* BSD */ + setpgrp(0,getpid()); +#else /* POSIX */ + setsid(); +#endif + /* Open the slave pty */ + if ((sfd = open_pty_slave(ptyslave)) < 0) { + ERRNO_ERR1(LOG_ERR,"Could not open pty slave '%s'", ptyslave); + exit(1); + } + /* But sfd may be one of the stdio fd's now, and we should be unmodern and not use dup2... */ + /* easiest to dup it up... */ + while (sfd < 3) { + sfd = dup(sfd); + } + +#ifndef NO_SYSLOG + /* Before fiddling with file descriptors we make sure syslog is turned off + or "closed". In the single case where we might want it again, + we will open it again instead. Would not want syslog to + go to some other fd... */ + if (run_daemon) { + closelog(); + } +#endif + + /* Close stdio */ + close(0); + close(1); + close(2); + + if (dup(sfd) != 0 || dup(sfd) != 1 || dup(sfd) != 2) { + status("Cannot dup\n"); + } + close(sfd); + exec_shell(argv+off_argv); /* exec_shell expects argv[2] to be */ + /* the command name, so we have to */ + /* adjust. */ + } else { + /* Parent */ + /* Ignore the SIGPIPE signal, write() will return errno=EPIPE */ + struct sigaction sig_act; + sigemptyset(&sig_act.sa_mask); + sig_act.sa_flags = 0; + sig_act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sig_act, (struct sigaction *)NULL); + + sigemptyset(&sig_act.sa_mask); + sig_act.sa_flags = SA_NOCLDSTOP; + sig_act.sa_handler = catch_sigchild; + sigaction(SIGCHLD, &sig_act, (struct sigaction *)NULL); + + /* + * read and write: enter the workloop + */ + + pass_on(childpid); + } + return 0; +} /* main() */ + +/* pass_on() + * Is the work loop of the logger. Selects on the pipe to the to_erl + * program erlang. If input arrives from to_erl it is passed on to + * erlang. + */ +static void pass_on(pid_t childpid) +{ + int len; + fd_set readfds; + fd_set writefds; + fd_set* writefds_ptr; + struct timeval timeout; + time_t last_activity; + char buf[BUFSIZ]; + char log_alive_buffer[ALIVE_BUFFSIZ+1]; + int lognum; + int rfd, wfd=0, lfd=0; + int maxfd; + int ready; + int got_some = 0; /* from to_erl */ + + /* Open the to_erl pipe for reading. + * We can't open the writing side because nobody is reading and + * we'd either hang or get an error. + */ + if ((rfd = open(fifo2, O_RDONLY|DONT_BLOCK_PLEASE, 0)) < 0) { + ERRNO_ERR1(LOG_ERR,"Could not open FIFO '%s' for reading.", fifo2); + exit(1); + } + +#ifdef DEBUG + status("run_erl: %s opened for reading\n", fifo2); +#endif + + /* Open the log file */ + + lognum = find_next_log_num(); + lfd = open_log(lognum, O_RDWR|O_APPEND|O_CREAT|O_SYNC); + + /* Enter the work loop */ + + while (1) { + int exit_status; + maxfd = MAX(rfd, mfd); + maxfd = MAX(wfd, maxfd); + FD_ZERO(&readfds); + FD_SET(rfd, &readfds); + FD_SET(mfd, &readfds); + FD_ZERO(&writefds); + if (outbuf_size() == 0) { + writefds_ptr = NULL; + } else { + FD_SET(wfd, &writefds); + writefds_ptr = &writefds; + } + time(&last_activity); + timeout.tv_sec = log_alive_minutes*60; /* don't assume old BSD bug */ + timeout.tv_usec = 0; + ready = select(maxfd + 1, &readfds, writefds_ptr, NULL, &timeout); + if (ready < 0) { + if (errno == EINTR) { + if (waitpid(childpid, &exit_status, WNOHANG) == childpid) { + /* + * The Erlang emulator has terminated. Give us some more + * time to write out any pending data before we terminate too. + */ + alarm(5); + } + FD_ZERO(&readfds); + FD_ZERO(&writefds); + } else { + /* Some error occured */ + ERRNO_ERR0(LOG_ERR,"Error in select."); + exit(1); + } + } else { + time_t now; + + if (waitpid(childpid, &exit_status, WNOHANG) == childpid) { + alarm(5); + FD_ZERO(&readfds); + FD_ZERO(&writefds); + } + + /* Check how long time we've been inactive */ + time(&now); + if(!ready || now - last_activity > log_activity_minutes*60) { + /* Either a time out: 15 minutes without action, */ + /* or something is coming in right now, but it's a long time */ + /* since last time, so let's write a time stamp this message */ + struct tm *tmptr; + if (log_alive_in_gmt) { + tmptr = gmtime(&now); + } else { + tmptr = localtime(&now); + } + if (!strftime(log_alive_buffer, ALIVE_BUFFSIZ, log_alive_format, + tmptr)) { + strn_cpy(log_alive_buffer, sizeof(log_alive_buffer), + "(could not format time in 256 positions " + "with current format string.)"); + } + log_alive_buffer[ALIVE_BUFFSIZ] = '\0'; + + sn_printf(buf, sizeof(buf), "\n===== %s%s\n", + ready?"":"ALIVE ", log_alive_buffer); + write_to_log(&lfd, &lognum, buf, strlen(buf)); + } + } + + /* + * Write any pending output first. + */ + if (FD_ISSET(wfd, &writefds)) { + int written; + char* buf = outbuf_first(); + + len = outbuf_size(); + written = write(wfd, buf, len); + if (written < 0 && errno == EAGAIN) { + /* + * Nothing was written - this is really strange because + * select() told us we could write. Ignore. + */ + } else if (written < 0) { + /* + * A write error. Assume that to_erl has terminated. + */ + clear_outbuf(); + close(wfd); + wfd = 0; + } else { + /* Delete the written part (or all) from the buffer. */ + outbuf_delete(written); + } + } + + /* + * Read master pty and write to FIFO. + */ + if (FD_ISSET(mfd, &readfds)) { +#ifdef DEBUG + status("Pty master read; "); +#endif + if ((len = read(mfd, buf, BUFSIZ)) <= 0) { + close(rfd); + if(wfd) close(wfd); + close(mfd); + unlink(fifo1); + unlink(fifo2); + if (len < 0) { + if(errno == EIO) + ERROR0(LOG_ERR,"Erlang closed the connection."); + else + ERRNO_ERR0(LOG_ERR,"Error in reading from terminal"); + exit(1); + } + exit(0); + } + + write_to_log(&lfd, &lognum, buf, len); + + /* + * Save in the output queue. + */ + + if (wfd) { + outbuf_append(buf, len); + } + } + + /* + * Read from FIFO, write to master pty + */ + if (FD_ISSET(rfd, &readfds)) { +#ifdef DEBUG + status("FIFO read; "); +#endif + if ((len = read(rfd, buf, BUFSIZ)) < 0) { + close(rfd); + if(wfd) close(wfd); + close(mfd); + unlink(fifo1); + unlink(fifo2); + ERRNO_ERR0(LOG_ERR,"Error in reading from FIFO."); + exit(1); + } + + if(!len) { + /* to_erl closed its end of the pipe */ + close(rfd); + rfd = open(fifo2, O_RDONLY|DONT_BLOCK_PLEASE, 0); + if (rfd < 0) { + ERRNO_ERR1(LOG_ERR,"Could not open FIFO '%s' for reading.", fifo2); + exit(1); + } + got_some = 0; /* reset for next session */ + } + else { + 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 = open(fifo1, O_WRONLY|DONT_BLOCK_PLEASE, 0)) < 0) { + status("Client expected on FIFO %s, but can't open (len=%d)\n", + fifo1, len); + close(rfd); + rfd = open(fifo2, O_RDONLY|DONT_BLOCK_PLEASE, 0); + if (rfd < 0) { + ERRNO_ERR1(LOG_ERR,"Could not open FIFO '%s' for reading.", fifo2); + exit(1); + } + wfd = 0; + } + else { +#ifdef DEBUG + status("run_erl: %s opened for writing\n", fifo1); +#endif + } + } + + if (!got_some && wfd && buf[0] == '\022') { + char wbuf[30]; + int wlen = sn_printf(wbuf,sizeof(wbuf),"[run_erl v%u-%u]\n", + RUN_ERL_HI_VER, RUN_ERL_LO_VER); + outbuf_append(wbuf,wlen); + } + got_some = 1; + + + /* Write the message */ +#ifdef DEBUG + status("Pty master write; "); +#endif + len = extract_ctrl_seq(buf, len); + + if(len==1 && buf[0] == '\003') { + kill(childpid,SIGINT); + } + else if (len>0 && write_all(mfd, buf, len) != len) { + ERRNO_ERR0(LOG_ERR,"Error in writing to terminal."); + close(rfd); + if(wfd) close(wfd); + close(mfd); + exit(1); + } + } +#ifdef DEBUG + status("OK\n"); +#endif + } + } +} /* pass_on() */ + +static void catch_sigchild(int sig) +{ +} + +/* + * next_log: + * Returns the index number that follows the given index number. + * (Wrapping after log_generations) + */ +static int next_log(int log_num) { + return log_num>=log_generations?1:log_num+1; +} + +/* + * prev_log: + * Returns the index number that precedes the given index number. + * (Wrapping after log_generations) + */ +static int prev_log(int log_num) { + return log_num<=1?log_generations:log_num-1; +} + +/* + * find_next_log_num() + * Searches through the log directory to check which logs that already + * exist. It finds the "hole" in the sequence, and returns the index + * number for the last log in the log sequence. If there is no hole, index + * 1 is returned. + */ +static int find_next_log_num(void) { + int i, next_gen, log_gen; + DIR *dirp; + struct dirent *direntp; + int log_exists[LOG_MAX_GENERATIONS+1]; + int stub_len = strlen(LOG_STUBNAME); + + /* Initialize exiting log table */ + + for(i=log_generations; i>=0; i--) + log_exists[i] = 0; + dirp = opendir(log_dir); + if(!dirp) { + ERRNO_ERR1(LOG_ERR,"Can't access log directory '%s'", log_dir); + exit(1); + } + + /* Check the directory for existing logs */ + + while((direntp=readdir(dirp)) != NULL) { + if(strncmp(direntp->d_name,LOG_STUBNAME,stub_len)==0) { + int num = atoi(direntp->d_name+stub_len); + if(num < 1 || num > log_generations) + continue; + log_exists[num] = 1; + } + } + closedir(dirp); + + /* Find out the next available log file number */ + + next_gen = 0; + for(i=log_generations; i>=0; i--) { + if(log_exists[i]) + if(next_gen) + break; + else + ; + else + next_gen = i; + } + + /* Find out the current log file number */ + + if(next_gen) + log_gen = prev_log(next_gen); + else + log_gen = 1; + + return log_gen; +} /* find_next_log_num() */ + +/* open_log() + * Opens a log file (with given index) for writing. Writing may be + * at the end or a trucnating write, according to flags. + * A LOGGING STARTED and time stamp message is inserted into the log file + */ +static int open_log(int log_num, int flags) +{ + char buf[FILENAME_MAX]; + time_t now; + struct tm *tmptr; + char log_buffer[ALIVE_BUFFSIZ+1]; + int lfd; + + /* Remove the next log (to keep a "hole" in the log sequence) */ + sn_printf(buf, sizeof(buf), "%s/%s%d", + log_dir, LOG_STUBNAME, next_log(log_num)); + unlink(buf); + + /* Create or continue on the current log file */ + sn_printf(buf, sizeof(buf), "%s/%s%d", log_dir, LOG_STUBNAME, log_num); + if((lfd = open(buf, flags, LOG_PERM))<0){ + ERRNO_ERR1(LOG_ERR,"Can't open log file '%s'.", buf); + exit(1); + } + + /* Write a LOGGING STARTED and time stamp into the log file */ + time(&now); + if (log_alive_in_gmt) { + tmptr = gmtime(&now); + } else { + tmptr = localtime(&now); + } + if (!strftime(log_buffer, ALIVE_BUFFSIZ, log_alive_format, + tmptr)) { + strn_cpy(log_buffer, sizeof(log_buffer), + "(could not format time in 256 positions " + "with current format string.)"); + } + log_buffer[ALIVE_BUFFSIZ] = '\0'; + + sn_printf(buf, sizeof(buf), "\n=====\n===== LOGGING STARTED %s\n=====\n", + log_buffer); + if (write_all(lfd, buf, strlen(buf)) < 0) + status("Error in writing to log.\n"); + +#if USE_FSYNC + fsync(lfd); +#endif + + return lfd; +} + +/* write_to_log() + * Writes a message to a log file. If the current log file is full, + * a new log file is opened. + */ +static void write_to_log(int* lfd, int* log_num, char* buf, int len) +{ + int size; + + /* Decide if new logfile needed, and open if so */ + + size = lseek(*lfd,0,SEEK_END); + if(size+len > log_maxsize) { + close(*lfd); + *log_num = next_log(*log_num); + *lfd = open_log(*log_num, O_RDWR|O_CREAT|O_TRUNC|O_SYNC); + } + + /* Write to log file */ + + if (write_all(*lfd, buf, len) < 0) { + status("Error in writing to log.\n"); + } + +#if USE_FSYNC + fsync(*lfd); +#endif +} + +/* create_fifo() + * Creates a new fifo with the given name and permission. + */ +static int create_fifo(char *name, int perm) +{ + if ((mkfifo(name, perm) < 0) && (errno != EEXIST)) + return -1; + return 0; +} + + +/* open_pty_master() + * Find a master device, open and return fd and slave device name. + */ + +static int open_pty_master(char **ptyslave) +{ + int mfd; + +/* Use the posix_openpt if working, as this guarantees creation of the + slave device properly. */ +#ifdef HAVE_WORKING_POSIX_OPENPT + if ((mfd = posix_openpt(O_RDWR)) >= 0) { + if ((*ptyslave = ptsname(mfd)) != NULL && + grantpt(mfd) == 0 && + unlockpt(mfd) == 0) { + + return mfd; + } + close(mfd); + } + /* fallback to openpty if it exist */ +#endif + +#ifdef HAVE_OPENPTY +# ifdef PATH_MAX +# define SLAVE_SIZE PATH_MAX +# else +# define SLAVE_SIZE 1024 +# endif + { + static char slave[SLAVE_SIZE]; + int sfd; +# undef SLAVE_SIZE + + if (openpty(&mfd, &sfd, slave, NULL, NULL) == 0) { + close(sfd); + *ptyslave = slave; + return mfd; + } + } + +#elif !defined(HAVE_WORKING_POSIX_OPENPT) + /* + * The traditional way to find ptys. We only try it if neither + * posix_openpt or openpty() are available. + */ + char *major, *minor; + + static char majorchars[] = "pqrstuvwxyzabcdePQRSTUVWXYZABCDE"; + static char minorchars[] = "0123456789abcdefghijklmnopqrstuv" + "wxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_+"; + + /* In the old time the names where /dex/ptyXY where */ + /* X is in "pqrs" and Y in "0123456789abcdef" but FreeBSD */ + /* and some Linux version has extended this. */ + + /* This code could probebly be improved alot. For example look at */ + /* http://www.xcf.berkeley.edu/~ali/K0D/UNIX/PTY/code/pty.c.html */ + /* http://www.xcf.berkeley.edu/~ali/K0D/UNIX/PTY/code/upty.h.html */ + + { + /* New style devpts or devfs /dev/pty/{m,s}{0,1....} */ + + static char ptyname[] = "/dev/pty/mX"; + + for (minor = minorchars; *minor; minor++) { + ptyname[10] = *minor; + if ((mfd = open(ptyname, O_RDWR, 0)) >= 0) { + ptyname[9] = 's'; + *ptyslave = ptyname; + return mfd; + } + } + } + + { + /* Unix98 style /dev/ptym/ptyXY and /dev/pty/ttyXY */ + + static char ptyname[] = "/dev/ptym/ptyXY"; + static char ttyname[] = "/dev/pty/ttyXY"; + + for (major = majorchars; *major; major++) { + ptyname[13] = *major; + for (minor = minorchars; *minor; minor++) { + ptyname[14] = *minor; + if ((mfd = open(ptyname, O_RDWR, 0)) >= 0) { + ttyname[12] = *major; + ttyname[13] = *minor; + *ptyslave = ttyname; + return mfd; + } + } + } + } + + { + /* Old style /dev/ptyXY */ + + static char ptyname[] = "/dev/ptyXY"; + + for (major = majorchars; *major; major++) { + ptyname[8] = *major; + for (minor = minorchars; *minor; minor++) { + ptyname[9] = *minor; + if ((mfd = open(ptyname, O_RDWR, 0)) >= 0) { + ptyname[5] = 't'; + *ptyslave = ptyname; + return mfd; + } + } + } + } +#endif /* !HAVE_OPENPTY */ + return -1; +} + +static int open_pty_slave(char *name) +{ + int sfd; +#ifdef DEBUG + struct termios tty_rmode; +#endif + + if ((sfd = open(name, O_RDWR, 0)) < 0) { + return -1; + } + +#ifdef DEBUG + if (tcgetattr(sfd, &tty_rmode) , 0) { + fprintf(stderr, "Cannot get terminals current mode\n"); + exit(-1); + } + show_terminal_settings(&tty_rmode); +#endif + + return sfd; +} + +/* exec_shell() + * Executes the named command (in argv format) in a /bin/sh. IO redirection + * should already have been taken care of, and this process should be the + * child of a fork. + */ +static void exec_shell(char **argv) +{ + char *sh, **vp; + int i; + + sh = "/bin/sh"; + if ((argv[0] = strrchr(sh, '/')) != NULL) + argv[0]++; + else + argv[0] = sh; + argv[1] = "-c"; + status("Args before exec of shell:\n"); + for (vp = argv, i = 0; *vp; vp++, i++) + status("argv[%d] = %s\n", i, *vp); + if (stdstatus) { + fclose(stdstatus); + } + execv(sh, argv); + if (run_daemon) { + OPEN_SYSLOG(); + } + ERRNO_ERR0(LOG_ERR,"Could not execv"); +} + +/* status() + * Prints the arguments to a status file + * Works like printf (see vfrpintf) + */ +static void status(const char *format,...) +{ + va_list args; + time_t now; + + if (stdstatus == NULL) + stdstatus = fopen(statusfile, "w"); + if (stdstatus == NULL) + return; + now = time(NULL); + fprintf(stdstatus, "run_erl [%d] %s", (int)getpid(), ctime(&now)); + va_start(args, format); + vfprintf(stdstatus, format, args); + va_end(args); + fflush(stdstatus); +} + +static void daemon_init(void) + /* As R Stevens wants it, to a certain extent anyway... */ +{ + pid_t pid; + int i, maxfd = HIGHEST_FILENO(); + + if ((pid = fork()) != 0) + exit(0); +#if defined(USE_SETPGRP_NOARGS) + setpgrp(); +#elif defined(USE_SETPGRP) + setpgrp(0,getpid()); +#else + setsid(); /* Seems to be the case on all current platforms */ +#endif + signal(SIGHUP, SIG_IGN); + if ((pid = fork()) != 0) + exit(0); + + /* Should change working directory to "/" and change umask now, but that + would be backward incompatible */ + + for (i = 0; i < maxfd; ++i ) { + close(i); + } + + OPEN_SYSLOG(); + run_daemon = 1; +} + +/* error_logf() + * Prints the arguments to stderr or syslog + * Works like printf (see vfprintf) + */ +static void error_logf(int priority, int line, const char *format, ...) +{ + va_list args; + va_start(args, format); + +#ifndef NO_SYSLOG + if (run_daemon) { + vsyslog(priority,format,args); + } + else +#endif + { + time_t now = time(NULL); + fprintf(stderr, "run_erl:%d [%d] %s", line, (int)getpid(), ctime(&now)); + vfprintf(stderr, format, args); + } + va_end(args); +} + +static void usage(char *pname) +{ + fprintf(stderr, "Usage: %s (pipe_name|pipe_dir/) log_dir \"command [parameters ...]\"\n", pname); + fprintf(stderr, "\nYou may also set the environment variables RUN_ERL_LOG_GENERATIONS\n"); + fprintf(stderr, "and RUN_ERL_LOG_MAXSIZE to the number of log files to use and the\n"); + fprintf(stderr, "size of the log file when to switch to the next log file\n"); +} + +/* Instead of making sure basename exists, we do our own */ +static char *simple_basename(char *path) +{ + char *ptr; + for (ptr = path; *ptr != '\0'; ++ptr) { + if (*ptr == '/') { + path = ptr + 1; + } + } + return path; +} + +static void init_outbuf(void) +{ + outbuf_total = 1; + outbuf_base = malloc(BUFSIZ); + clear_outbuf(); +} + +static void clear_outbuf(void) +{ + outbuf_in = outbuf_out = outbuf_base; +} + +static int outbuf_size(void) +{ + return outbuf_in - outbuf_out; +} + +static char* outbuf_first(void) +{ + return outbuf_out; +} + +static void outbuf_delete(int bytes) +{ + outbuf_out += bytes; + if (outbuf_out >= outbuf_in) { + outbuf_in = outbuf_out = outbuf_base; + } +} + +static void outbuf_append(const char* buf, int n) +{ + if (outbuf_base+outbuf_total < outbuf_in+n) { + /* + * The new data does not fit at the end of the buffer. + * Slide down the data to the beginning of the buffer. + */ + if (outbuf_out > outbuf_base) { + int size = outbuf_in - outbuf_out; + char* p; + + outbuf_in -= outbuf_out - outbuf_base; + p = outbuf_base; + while (size-- > 0) { + *p++ = *outbuf_out++; + } + outbuf_out = outbuf_base; + } + + /* + * Allocate a larger buffer if we still cannot fit the data. + */ + if (outbuf_base+outbuf_total < outbuf_in+n) { + int size = outbuf_in - outbuf_out; + outbuf_total = size+n; + outbuf_base = realloc(outbuf_base, outbuf_total); + outbuf_out = outbuf_base; + outbuf_in = outbuf_base + size; + } + } + + /* + * Copy data to the end of the buffer. + */ + memcpy(outbuf_in, buf, n); + outbuf_in += n; +} + +/* Call write() until entire buffer has been written or error. + * Return len or -1. + */ +static int write_all(int fd, const char* buf, int len) +{ + int left = len; + int written; + for (;;) { + written = write(fd,buf,left); + if (written == left) { + return len; + } + if (written < 0) { + return -1; + } + left -= written; + buf += written; + } +} + +/* Extract any control sequences that are ment only for run_erl + * and should not be forwarded to the pty. + */ +static int extract_ctrl_seq(char* buf, int len) +{ + static const char prefix[] = "\033_"; + static const char suffix[] = "\033\\"; + char* bufend = buf + len; + char* start = buf; + char* command; + char* end; + + for (;;) { + start = find_str(start, bufend-start, prefix); + if (!start) break; + + command = start + strlen(prefix); + end = find_str(command, bufend-command, suffix); + if (end) { + unsigned col, row; + if (sscanf(command,"version=%u", &protocol_ver)==1) { + /*fprintf(stderr,"to_erl v%u\n", protocol_ver);*/ + } + else if (sscanf(command,"winsize=%u,%u", &col, &row)==2) { + set_window_size(col,row); + } + else { + ERROR2(LOG_ERR, "Ignoring unknown ctrl command '%.*s'\n", + (int)(end-command), command); + } + + /* Remove ctrl sequence from buf */ + end += strlen(suffix); + memmove(start, end, bufend-end); + bufend -= end - start; + } + else { + ERROR2(LOG_ERR, "Missing suffix in ctrl sequence '%.*s'\n", + (int)(bufend-start), start); + break; + } + } + return bufend - buf; +} + +static void set_window_size(unsigned col, unsigned row) +{ +#ifdef TIOCSWINSZ + struct winsize ws; + ws.ws_col = col; + ws.ws_row = row; + if (ioctl(mfd, TIOCSWINSZ, &ws) < 0) { + ERRNO_ERR0(LOG_ERR,"Failed to set window size"); + } +#endif +} + + +#ifdef DEBUG + +#define S(x) ((x) > 0 ? 1 : 0) + +static void show_terminal_settings(struct termios *t) +{ + printf("c_iflag:\n"); + printf("Signal interrupt on break: BRKINT %d\n", S(t->c_iflag & BRKINT)); + printf("Map CR to NL on input: ICRNL %d\n", S(t->c_iflag & ICRNL)); + printf("Ignore break condition: IGNBRK %d\n", S(t->c_iflag & IGNBRK)); + printf("Ignore CR: IGNCR %d\n", S(t->c_iflag & IGNCR)); + printf("Ignore char with par. err's: IGNPAR %d\n", S(t->c_iflag & IGNPAR)); + printf("Map NL to CR on input: INLCR %d\n", S(t->c_iflag & INLCR)); + printf("Enable input parity check: INPCK %d\n", S(t->c_iflag & INPCK)); + printf("Strip character ISTRIP %d\n", S(t->c_iflag & ISTRIP)); + printf("Enable start/stop input ctrl IXOFF %d\n", S(t->c_iflag & IXOFF)); + printf("ditto output ctrl IXON %d\n", S(t->c_iflag & IXON)); + printf("Mark parity errors PARMRK %d\n", S(t->c_iflag & PARMRK)); + printf("\n"); + printf("c_oflag:\n"); + printf("Perform output processing OPOST %d\n", S(t->c_oflag & OPOST)); + printf("\n"); + printf("c_cflag:\n"); + printf("Ignore modem status lines CLOCAL %d\n", S(t->c_cflag & CLOCAL)); + printf("\n"); + printf("c_local:\n"); + printf("Enable echo ECHO %d\n", S(t->c_lflag & ECHO)); + printf("\n"); + printf("c_cc:\n"); + printf("c_cc[VEOF] %d\n", t->c_cc[VEOF]); +} + +#endif /* DEBUG */ + + |