#define _GNU_SOURCE
-//#ifndef TIZEN
-//#define TIZEN 1
-//#endif
-
#include <errno.h>
#include <getopt.h>
#include <signal.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
-
-#if TIZEN
-#include <termios.h>
-#endif /* TIZEN */
+#include <limits.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <netinet/in.h>
#include <fcntl.h>
+#include <sys/ioctl.h>
#include "logging.h"
#include "proc_stat.h"
-static int verbose = 0;
-static int doinfo = 0;
-static int stat_period_usec = 500000; // 0.5 seconds
-#if TIZEN
-static struct termios sterm;
-#endif /* TIZEN */
-
-static int app_pid = -1;
-static int cmd_pid = -1;
-static int cmd_idx = -1;
-static char *pname = NULL;
-static char *ename = NULL;
-static char *oname = NULL;
-
-static int isPipeOwner = 0;
-
-static int controlPort = -1;
-static int dataPort = -1;
-static int statPort = -1;
-static int control_socket = -1;
-static int data_socket = -1;
-static int stat_socket = -1;
-static FILE *ctrl_file_in = NULL;
-static FILE *ctrl_file_out = NULL;
-static FILE *data_file_out = NULL;
-static FILE *stat_file_out = NULL;
+/* Options which can be tuned via command line: */
+static int verbose = 0; /* 1: print more verbose messages in log (-v) */
+static int doinfo = 0; /* 1: enable perdiodic printing of process statistics (-i) */
+static int stat_period_usec = 500000; /* uSec, period in which statistics is printed, default 0.5 seconds (-t) */
+
+/* Variables used by program */
+static int app_pid = -1; /* pid of process, with which profctl works (usually same as cmd_pid) */
+static int cmd_pid = -1; /* pid of process executed by profctl (-1 when attaching to existing process) */
+static int cmd_idx = -1; /* index of last profctl's argument in argv[] array (rest passed to executed process) */
+static int exe_pid = -1; /* pid of "helper" process (which processes output of profiler) */
+
+/* Options which can be specified in command line: */
+static char *pname = NULL; /* named pipe file name (-p) */
+static char *ename = NULL; /* helper executable name (-e), one which processes output of the profiler */
+static char *oname = NULL; /* log file name (-o) */
+
+static int isPipeOwner = 0; /* set to one by option "-w", allows creation and deleting fifo file */
+/* Note, port numbers can be set via command line, by default no TCP ports opened. */
+static int controlPort = -1; /* TCP port used to give commands to profctl */
+static int dataPort = -1; /* TCP port used to output data from the profiler */
+static int statPort = -1; /* TCP port used to output process statistics */
+
+static int control_fd = -1; /* file or socket used to give commands to profctl (can't be not opened) */
+static int data_socket = -1; /* socket, which is used to output data from the profiler (might be not opened) */
+static int stat_socket = -1; /* socket, which is used to output process statistics (might be not opened) */
+static FILE *ctrl_file_out = NULL; /* write-only file associated with control_fd */
+static FILE *stat_file_out = NULL; /* write-only file associated with stat_socket */
+
+/* Default PIPE size for new created pipes (1 MByte) */
+#define DEFAULT_PIPE_SIZE ((size_t)1 * 1024 * 1024)
+
+/* Maximal amount of additional environment variables, which can be set via -E option. */
#ifndef ADDITIONAL_ENV_SIZE
#define ADDITIONAL_ENV_SIZE 128
#endif
-static int env_mode = 0;
+/* Values of the additional environment variables, which should be set in fork'ed process before exec. */
static char *additional_env[ADDITIONAL_ENV_SIZE];
-static int additional_env_index = 0;
+static int additional_env_index = 0; /* number of variables which contained in `additional_env' */
-volatile int global_stop = 0;
+int stop_pipe[2] = { -1, -1 }; /* pipe used to stop all threads */
+static int chld_pipe[2] = { -1, -1 }; /* pipe used to process SIGCHLD signal */
+/* Function returns non-zero value, if process `pid' still exists, */
int is_running(int pid)
{
return kill(pid, 0) == 0;
{"data", required_argument, 0, 'd'},
{"stat", required_argument, 0, 's'},
{"env", optional_argument, 0, 'E'},
+ {"help", no_argument, 0, 'h'},
{0, 0, 0, 0}
};
-static volatile int data_output_thread_runs = 0;
-static volatile int command_loop_thread_runs = 0;
-static volatile int proc_stat_thread_runs = 0;
+/* Thread, which periodically prints process statistics -- will not be created
+ * (and `proc_stat_thread_id' will be uninitialized) if `doinfo' isn't set to nonzero value. */
static pthread_t proc_stat_thread_id;
-static FILE *pipef;
-
+/* Named fifo or unnamed pipe, which is used to receive data from profiled process:
+ *
+ * * for "coreprofiler" writing end of the fifo is opened by profiling
+ * process (by coreprofiler.so library), reading end is opened by this program;
+ *
+ * * for "heaptrack" writing end of the fifo is opened by profiling process
+ * (by libprofiler.so library), reading end is opened by this program and passed
+ * to "heaptrack_interpret" helper process as standard input, standart output of
+ * helper process is redirected to writing end of unnamed pipe, and reading end
+ * of this pipe is opened by this program.
+ *
+ * In both cases, "procfctl" should just copy data from named fifo (for coreprofiler)
+ * or unnamed pipe (heaptrack) to data socket, if it is opened. This (`pipe_fd')
+ * variable contains file descriptor, from which "profctl" should read data.
+ */
+static int pipe_fd = -1;
+
+/* Forward declaration, see description in definition. */
static int openPort(int port);
+
+/* This function runs separate thread, which reads data (as described above) from
+ * `pipe_fd' and just copies it to `data_socket'. Thread terminates in one of the
+ * three cases:
+ *
+ * * then EOF condition reached in `pipe_fd' (which means, that other party is
+ * closed writing end of the fifo;
+ *
+ * * then main thread signals via `stop_pipe' to terminate all child threads;
+ *
+ * * then `data_socket' isn't opened.
+ */
static void *data_output_thread(void *arg)
{
+ (void)arg; // avoid "unused variable" warning
+
if (dataPort > 0)
{
data_socket = openPort(dataPort);
log_error("cannot open data port");
return NULL;
}
- data_file_out = fdopen(data_socket, "w");
- if (data_file_out == NULL) {
- log_system_error("fdopen(data,w)");
- return NULL;
- }
- if (fprintf(data_file_out, "ready\n") < 0 || fflush(data_file_out) != 0) {
+
+ static const char handshake[] = "ready\n";
+ if (write(data_socket, handshake, sizeof(handshake) - 1) <= 0) {
log_system_error("cannot write 'ready' prompt to data stream");
return NULL;
}
}
- do
- {
- int linesRead = 0;
-
- char *buffer = NULL;
- size_t lsize = 0;
- while (!global_stop) {
- if (getline(&buffer, &lsize, pipef) == -1) {
- if (feof(pipef)) {
- if (verbose) {
- log_message("pipe eof");
- }
- }
- else {
- log_system_error("cannot read data from pipe");
- }
+ /* Copy data from `pipe_fd` to `data_socket`, break on error
+ * or when `stop_pipe[0]` becomes readable. Variable `delay' used
+ * to introduce short delay before each next reading of `pipe_fd' and
+ * increase throughput in this way (delay allows accumulate bigger amount
+ * of data to copy, so overhead on calling read/write functions will be less). */
+ const int ReadThreshold = getpagesize();
+ int no_fionread = 0;
+ int delay = 0;
+ int const maxfd = (pipe_fd > stop_pipe[0] ? pipe_fd : stop_pipe[0]) + 1;
+ while (1) {
+ fd_set fdset;
+ struct timeval tv, *tp;
+
+ FD_ZERO(&fdset);
+ FD_SET(stop_pipe[0], &fdset);
+
+ // introduce short delay before reading pipe (to increase throughput)
+ if (delay) {
+ tv = (struct timeval){ 0, 10000 }; // 10msec
+ tp = &tv;
+ delay = 0;
+ } else {
+ FD_SET(pipe_fd, &fdset);
+ tp = NULL;
+ }
+
+ int nr = select(maxfd, &fdset, NULL, NULL, tp);
+
+ if (nr == -1) {
+ if (errno == EINTR) continue;
+ log_system_error("select");
+ abort();
+ }
+
+ if (nr == 0) continue; // timeout
+
+ if (FD_ISSET(stop_pipe[0], &fdset))
+ break; // termination request
+
+ if (FD_ISSET(pipe_fd, &fdset)) {
+ // copy data from pipe to output socket
+ ssize_t res = splice(pipe_fd, NULL, data_socket, NULL, INT_MAX, SPLICE_F_MOVE);
+ if (res == -1) {
+ log_system_error("splice(pipe_fd, data_socket)");
break;
}
- ++linesRead;
- if (fputs(buffer, data_file_out) < 0) {
- log_message("cannot write data");
+
+ if (res == 0) {
+ if (verbose) log_message("pipe eof or data socket error");
break;
}
- }
- free(buffer);
- if (verbose) {
- log_message("output thread exits (%d lines read from pipe)", linesRead);
+ if (! no_fionread) {
+ int nbytes;
+ if (ioctl(pipe_fd, FIONREAD, &nbytes) == -1) {
+ no_fionread = 1;
+ log_system_error("ioctl(FIONREAD)");
+ }
+ else if (nbytes >= 0 && nbytes < ReadThreshold) {
+ delay = 1;
+ }
+ }
}
- } while (0); // one-iteration loop
- data_output_thread_runs = 0;
- global_stop = 1;
- return NULL;
-}
-
-static void close_files_and_sockets()
-{
- if (ctrl_file_in != NULL && ctrl_file_in != stdin) {
- fclose(ctrl_file_in);
- }
- if (ctrl_file_out != NULL && ctrl_file_out != stdout) {
- fclose(ctrl_file_out);
- }
- if (data_file_out != NULL && data_file_out != stdout) {
- fclose(data_file_out);
- }
- if (stat_file_out != NULL && stat_file_out != stdout) {
- fclose(stat_file_out);
- }
- if (control_socket >= 0) {
- close(control_socket);
- }
- if (data_socket >= 0) {
- close(data_socket);
}
- if (stat_socket >= 0) {
- close(stat_socket);
+
+ if (verbose) {
+ log_message("output thread exits");
}
+
+ return NULL;
}
+
+/* This function is registered with atexit(3) and called on program termination. */
static void finish()
{
log_message("exit handler called");
-#if TIZEN
- if (controlPort < 0) {
- tcsetattr(0, TCSANOW, &sterm);
- }
-#endif /* TIZEN */
- if (pipef != NULL) {
- fclose(pipef);
+ if (pipe_fd != -1) {
+ close(pipe_fd), pipe_fd = -1;
if (isPipeOwner) {
unlink(pname);
}
}
- close_files_and_sockets();
- log_message("=== finished ===");
log_uninit();
- kill(getpid(), SIGKILL);
}
+/* This function checks, that command line option is not already applied and assigns
+ * value (c-string) to `*value'. In case if option is already assigned, the program
+ * terminates with error message. */
static void CheckValue(char **value, const char *info)
{
if (value[0] != NULL) {
value[0] = optarg;
}
+/* Function parses command line options and assigns variables appropriately.
+ * Function might return -1 in case of error (can't parse options). */
static int process_option(int argc, char **argv)
{
int option_index;
int result = -1;
+ static int env_mode = 0; // should be init once, process_option called iterative.
- result = getopt_long(argc, argv, "-p:ve:o:it:wc:d:s:E::",
+ result = getopt_long(argc, argv, "-p:ve:o:it:wc:d:s:E::h",
long_options, &option_index);
if (result == 1) {
cmd_idx = optind;
}
return -1;
+
+ case 'h':
+ printf("Usage: %s [options...] [--] [command-to-exec [args...]]\n", argv[0]);
+ puts("options are following:\n"
+ "-p, --pipe <pipe-name> profiling output (data pipe)\n"
+ "-e, --exec <path> execute program to create data pipe\n"
+ "-o, --error <log-filename> logging to specified file\n"
+ "-v, --verbose increase verbosity\n"
+ "-i, --info dump process statistics\n"
+ "-t, --timeout <msec> statistic printing period (0.5 sec default)\n"
+ "-w, --pipe-owner unlink and recreate data pipe\n"
+ "-c, --control <port> control/command TCP port\n"
+ "-d, --data <port> profiling data output TCP port\n"
+ "-s, --stat <port> statistics TCP port\n"
+ "-E, --env <env-var=value> assign additional environment variable\n"
+ "-h, --help print this help\n"
+ "\n"
+ "Note, options -p (--pipe) and -e (--exec) are mutually exclusive.\n");
+
+ exit(EXIT_SUCCESS);
default:
// NOTE: should not be reachable
abort();
return 0;
}
+
+/* This function executes function `func' with argument `arg' in separate thread.
+ * In case of error, if thread can't be created, error is logged and program aborts. */
static pthread_t SimpleThread(void* (*func)(void *), void *arg)
{
pthread_attr_t attr;
pthread_t thread;
if (pthread_create(&thread, &attr, func, arg) != 0) {
log_system_error_and_exit("pthread_create");
+ abort();
}
return thread;
}
-// TODO: should be refactored: use separated launcher
+
+/* Function sets pipe size (in bytes), which is close to the required (but
+ * might be less, because of OS limits).
+ * Function returns actual pipe size or -1 in case of error. */
+static int set_pipe_size(int fd, int pipe_size)
+{
+ int result;
+ while (1) {
+ result = fcntl(fd, F_SETPIPE_SZ, pipe_size);
+ if (result == -1) {
+ if (errno == EPERM) {
+ pipe_size = pipe_size / 2;
+ if (pipe_size >= PIPE_BUF) continue;
+ }
+ log_system_error("fcntl(F_SETPIPE_SZ)");
+ }
+ break;
+ }
+ return result;
+}
+
+
+/* Function opens named fifo or unnamed pipe and assigns `pipe_fd' variable,
+ * logic is depending on command line options. See `pipe_fd' description above
+ * for the details.
+ *
+ * Function might return -1 in case of error (if pipe can't be opened).
+ *
+ * Named fifo (for "coreprofiler") is opened in non-blocking mode, so later
+ * it can be read only via select(2) (because after first call to read(2), if
+ * other end of the fifo still not opened, you will get EOF's constantly).
+ *
+ * Unnamed pipe (for "heaptrack") is opened in non-blocking mode too, but
+ * named pipe (which is read by helper process, "heaptrack_interpret") should
+ * only be opened in blocking mode, because helper process have no mention
+ * how pipe is opened and just reads is via reagular blocking functions.
+ */
static int openFileProcess()
{
if (ename == NULL) {
- pipef = fopen(pname, "r");
- if (pipef == NULL) {
- log_system_error("fopen(%s)", pname);
+ pipe_fd = open(pname, O_RDONLY | O_NONBLOCK);
+ if (pipe_fd == -1) {
+ log_system_error("open(%s)", pname);
return -1;
}
if (verbose) {
log_message("pipe %s opened for read", pname);
}
+ fcntl(pipe_fd, F_SETFD, fcntl(pipe_fd, F_GETFD, 0) | FD_CLOEXEC);
+
+ int pipesz = set_pipe_size(pipe_fd, DEFAULT_PIPE_SIZE);
+ if (verbose && pipesz > 0) log_message("set pipe size to %d bytes", pipesz);
+
return 0;
}
return -1;
}
- /* We must start interpreter process */
+ /* Create pipe between interpreter process and profctl. */
int pipefd[2];
if (pipe(pipefd)) {
log_system_error("pipe");
log_message("pipefd: [%d, %d]", pipefd[0], pipefd[1]);
}
- int id = open(pname, O_RDONLY);
- if (id < 0) {
- log_system_error("open(%s)", pname);
- return -1;
- }
+ int pipesz = set_pipe_size(pipefd[1], DEFAULT_PIPE_SIZE);
+ if (verbose && pipesz > 0) log_message("set pipe size to %d bytes", pipesz);
- if (verbose) {
- log_message("pname id: %d", id);
- }
-
- int pid = fork();
- if (pid == -1) {
+ exe_pid = fork();
+ if (exe_pid == -1) {
log_system_error("fork");
return -1;
}
- if (pid == 0) {
+ if (exe_pid == 0) {
/* Child branch */
if (close(pipefd[0]) != 0) {
// not critical
log_system_error("CHILD close(%d)", pipefd[0]);
}
- if (dup2(pipefd[1], 1) == -1) { /* stdout */
+
+ if (dup2(pipefd[1], STDOUT_FILENO) == -1) { /* stdout */
log_system_error("CHILD dup2(%d, 1)", pipefd[1]);
return -1;
}
- if (dup2(id, 0) == -1) { /* stdin */
+ close(pipefd[1]);
+
+ /* here child hangs until libprofiler.so not opens writing end of the named fifo */
+ int id = open(pname, O_RDONLY);
+ if (id < 0) {
+ log_system_error("CHILD open(%s)", pname);
+ _exit(EXIT_FAILURE);
+ }
+
+ int pipesz = set_pipe_size(id, DEFAULT_PIPE_SIZE);
+ if (verbose && pipesz > 0) log_message("set pipe size to %d bytes", pipesz);
+
+ if (dup2(id, STDIN_FILENO) == -1) { /* stdin */
log_system_error("CHILD dup2(%d, 0)", id);
return -1;
}
- if (verbose) {
- log_message("CHILD close(%d); dup2(%d, 1); dup2(%d, 0)", pipefd[0], pipefd[1], id);
- }
- close_files_and_sockets();
- if (verbose) {
- log_message("CHILD closed files and sockets");
+ close(id);
+
+ /* redirect stderr to log */
+ int fd = get_log_fd();
+ if (fd >= 0) {
+ if (dup2(fd, STDERR_FILENO) == -1)
+ log_system_error("CHILD dup2(%d, 2)", fd);
}
+
if (verbose) {
log_message("CHILD calling execl(%s)", ename);
}
log_uninit();
execl(ename, ename, NULL);
log_system_error("CHILD execl(%s)", ename); // execl returns only in case of error
- return -1;
+ _exit(EXIT_FAILURE);
}
/* Parent */
log_system_error("PARENT close(%d)", pipefd[1]);
}
- if (close(id) != 0) {
- // not critical
- log_system_error("PARENT close(%d)", id);
- }
-
- pipef = fdopen(pipefd[0], "r");
- if (pipef == NULL) {
- log_system_error("PARENT fdopen(%d, r)", pipefd[0]);
- return -1;
- }
+ pipe_fd = pipefd[0];
+ fcntl(pipe_fd, F_SETFL, fcntl(pipe_fd, F_GETFL, 0) | O_NONBLOCK); /* make input non blocking */
if (verbose) {
- log_message("PARENT close(%d); close(%d); fdopen(%d, r)", pipefd[1], id, pipefd[0]);
+ log_message("PARENT close(%d); fdopen(%d, r)", pipefd[1], pipefd[0]);
}
return 0;
}
+
+/* Function starts listen on TCP port `port' and accepts exactly one connection to this port,
+ * function returns file descriptor for socket associated with connections.
+ * In case of error function might return -1. */
static int openPort(int port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
log_system_error("socket");
return -1;
}
+ fcntl(pipe_fd, F_SETFD, fcntl(pipe_fd, F_GETFD, 0) | FD_CLOEXEC);
int enable = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
return -1;
}
- if (listen(sock, 5) < 0) {
+ if (listen(sock, 1) < 0) {
log_system_error("listen");
close(sock);
return -1;
log_message("waiting for connection to port %d", port);
}
struct sockaddr_in caddr;
- int csize = sizeof(caddr);
+ socklen_t csize = sizeof(caddr);
int result = accept(sock, (struct sockaddr *)&caddr, &csize);
if (result < 0) {
log_system_error("accept");
log_message("accepted connection to port %d", port);
}
close(sock);
+
+ // set read timeout for socket
+ struct timeval tv = { 0, 0 };
+ if (-1 == setsockopt(result, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv))) {
+ log_system_error("setsockopt");
+ close(result);
+ return -1;
+ }
+
return result;
}
+
+/* Functions check, that string `line' contains command `command' and if it is, function
+ * parses up to two arguments of the command and assigns arguments to variables `*arg1'
+ * and `*arg2'. If no arguments supplied, defaults values `def_arg1' and `def_arg2' might
+ * be substituted instead.
+ * Return value is -1 in case of parsing error or if `line' not matches specified `command'.
+ * In case of success function returns 0. */
static int is_command(char *line, char *command, int *arg1, int def_arg1, int *arg2, int def_arg2)
{
*arg1 = def_arg1;
return 0;
}
+
+/* Function sends signal `signal' to PID `target_pid'. In case of error function
+ * reports error to the log. Return value is same as for kill(2) syscall. */
static int send_signal(int target_pid, int signal)
{
if (target_pid == -1) {
return result;
}
+
+/* This function implements command loop: it reads lines from `control_fd' file or socket,
+ * tries to parse input line as commands and execute commands. Command loop is terminated
+ * in the following cases:
+ *
+ * * EOF condition for `control_fd' file descriptor;
+ * * main thread signals to stop other threads (via `stop_pipe');
+ * * on reception of `exit' command.
+ *
+ * Following commands currently recognized (arguments in brackets are optional):
+ *
+ * * "test [pid]" -- assign default process id (if no pid specified, using `cmd_pid'
+ * value -- PID of the process started by profctl;
+ *
+ * * "exit [pid]" -- terminate specified process (or process with PID specified in "test" command)
+ * with SIGTERM signal and exit command loop;
+ *
+ * * "stop [pid]" -- send signal 41 to specified (or default) process;
+ *
+ * * "start [pid]" -- send signal 42 to specified (or default) process;
+ *
+ * * "kill [pid [signal]]" -- send specified (or SIGINT by default) signal to
+ * specified (or default) process (on Tizen platform
+ * SIGINT assumes graceful termination of graphic
+ * applications).
+ *
+ * On result of execution of each command, the or the command is echoed back, or
+ * the one of the following messages send back:
+ *
+ * * "!app_set_already (command...)" -- if "test" command received twice;
+ * * "!kill_failed (command...) : -1" -- if "kill" command was unsuccessful.
+ */
static void *command_loop_thread(void *arg)
{
- /* Read command loop */
- ssize_t len;
- char *line = malloc(len = 16);
- while (!global_stop) {
- if ((len = getline(&line, &len, ctrl_file_in)) == -1) {
- if (feof(ctrl_file_in)) {
- if (verbose) {
- log_message("command stream eof");
- }
- }
- else {
- log_system_error("cannot read command");
- }
- global_stop = 1;
+ (void)arg; // avoid "unused variable" warning
+
+ char line[2*getpagesize()];
+ char *wptr = line;
+
+ int const maxfd = (control_fd > stop_pipe[0] ? control_fd : stop_pipe[0]) + 1;
+ while (1) {
+ fd_set fdset;
+ FD_ZERO(&fdset);
+ FD_SET(control_fd, &fdset);
+ FD_SET(stop_pipe[0], &fdset);
+
+ int nr = select(maxfd, &fdset, NULL, NULL, NULL);
+ if (nr == -1) {
+ if (errno == EINTR) continue;
+ log_system_error("select");
+ abort();
+ }
+
+ if (FD_ISSET(stop_pipe[0], &fdset))
+ break; // exit requested
+
+ // there is data to read?
+ if (! FD_ISSET(control_fd, &fdset)) continue;
+
+ // read next portion of data
+ ssize_t rs = read(control_fd, wptr, sizeof(line) - (wptr - line));
+ if (rs == -1) {
+ log_system_error("read");
break;
}
- if (global_stop) {
+
+ if (rs == 0) {
+ log_message("eof on control file");
break;
}
+
+ // try to search end of line
+ char *lf = memchr(wptr, '\r', rs);
+ if (lf == NULL) lf = memchr(wptr, '\n', rs);
+ if (lf == NULL) {
+ wptr += rs;
+ continue; // line isn't complete, need more data
+ }
+
+ // replace '\n' with `\0', compute command line length
+ *lf = 0;
+ size_t const len = lf - line;
+ size_t const tail = wptr + rs - lf - 1;
+ (void)len; // avoid "unused variable" warning
+
+ // proces command line
if (verbose) {
- if ((len > 0) && (line[len - 1] == '\n')) {
- line[len - 1] = 0;
- }
log_message("line: %s", line);
}
char *reply = line; // echo input line by default
stat_file_out = stdout;
}
proc_stat_thread_id = start_write_proc_stat(stat_file_out, pid, stat_period_usec, verbose);
- proc_stat_thread_runs = 1;
}
}
else { // error
}
else if (is_command(line, "exit", &pid, app_pid, NULL, 0) == 0) {
if (pid != -1) {
- send_signal(pid, SIGHUP);
+ send_signal(pid, SIGTERM);
}
- if (verbose && !global_stop) {
- log_message("stop flag set (command_loop_thread)");
+ if (verbose) {
+ log_message("'exit' command given");
}
- global_stop = 1; // exit from the thread and then the program itself
+ break; // exit command loop
}
else if (is_command(line, "stop", &pid, app_pid, NULL, 0) == 0) {
send_signal(pid, SIGRTMIN + 7);
send_signal(pid, SIGRTMIN + 8);
}
else {
+ // Note, SIGINT have special meaning on Tizen platform: it allows
+ // gracefully terminate (call all exit handlers, etc...) .NET application,
+ // at same time SIGTERM terminates application in hard way and without
+ // creation of the crashdump. SIGABRT also terminates application in hard
+ // way, but also creates crashdump.
int arg2;
if (is_command(line, "kill", &pid, app_pid, &arg2, SIGINT) == 0) {
signal = arg2;
}
+ else {
+ log_error("unknown command '%s'", line);
+ }
}
if (signal >= 0) {
int result = send_signal(pid, signal);
}
}
if (ctrl_file_out != NULL) {
- if (fprintf(ctrl_file_out, "%s\n", reply) < 0 || fflush(ctrl_file_out) != 0) {
+ if (fprintf(ctrl_file_out, "%s\r\n", reply) < 0 || fflush(ctrl_file_out) != 0) {
log_system_error("cannot write command reply");
fclose(ctrl_file_out);
ctrl_file_out = NULL;
if (verbose && reply != line) {
log_error(reply);
}
- free(buf);
- } // while
- free(line);
- command_loop_thread_runs = 0;
+
+ // update write pointer, move tail to beginning of the buffer
+ wptr = line + tail;
+ memmove(line, lf+1, tail);
+ }
+
+ // signal all threads to exit
+ int unused = write(stop_pipe[1], "", 1);
+ (void)unused; // avoid "output result not checked" and "unused variable" warnings
+
if (verbose) {
log_message("command loop finished");
}
return NULL;
}
+
+/* This function handles SIGCHLD signal: on reception off such signal it
+ * sends byte to `chld_pipe' pipe, and program in main cycle should
+ * call waitpid(2) to get child's process return status. */
+static void sigchld_handler(int signo)
+{
+ (void)signo; // avoid "unused variable" warning
+ int unused = write(chld_pipe[1], "", 1);
+ (void)unused; // avoid "output result not checked" and "unused variable" warnings
+}
+
+
+/* Program execution starts here. Main function in general performs following
+ * actions:
+ *
+ * 1) parses command line arguments, opens files, sockets, starting child processes, etc...
+ *
+ * 2) starts one or two threads (one of which copies data from `pipe_fd' pipe to
+ * data socket, second which prints process statistics on regular intervals);
+ *
+ * 3) starts monitoring of `cmd_pid' process (one which launched previously at step 1),
+ * and when it is finished -- stop parallel threads, kill other child processes
+ * and terminate. In case, when running with "heaptrack", in main cycle also
+ * "helper processs" is monitored and cycle is break then helper process terminates.
+ * This is needed, because in absence of helper process, the profiled process might
+ * be forever blocked on writing to fifo with no readers (if helper process was
+ * terminated before the moment, when profiling process opens the fifo).
+ */
int main(int argc, char **argv)
{
time_t start_time = time(NULL);
return 1;
}
+ // open `stop_pipe' and `chld_pipe', set FD_CLOEXEC flag for both
+ {
+ if (pipe(stop_pipe) == -1) {
+ log_system_error_and_exit("pipe");
+ }
+
+ int i;
+ for (i = 0; i < 2; i++)
+ fcntl(stop_pipe[i], F_SETFD, fcntl(stop_pipe[i], F_GETFD, 0) | FD_CLOEXEC);
+
+ if (pipe(chld_pipe) == -1) {
+ log_system_error_and_exit("pipe");
+ }
+
+ for (i = 0; i < 2; i++)
+ fcntl(chld_pipe[i], F_SETFD, fcntl(chld_pipe[i], F_GETFD, 0) | FD_CLOEXEC);
+ }
+
+ signal(SIGCHLD, sigchld_handler);
+
log_pid = 1;
log_tid = 1;
FILE *log_file = fopen(oname, "w");
if (log_file == NULL) {
- log_system_error_and_exit("freopen");
+ log_system_error_and_exit("fopen '%s'", oname);
}
setlinebuf(log_file);
log_set_file(log_file);
- fclose(stderr); // NOTE: detaching from stdin/out/err streams
if (verbose) {
fprintf(log_file, "=== Started (pid: %d) ===\n", getpid());
}
}
-#if TIZEN
- struct termios term;
- if (controlPort < 0 && dataPort < 0 && statPort < 0) {
- /* stty */
- tcgetattr(0, &term);
- sterm = term;
-
- term.c_lflag &= ~(ECHO|ECHONL); /* no echo and so on */
- term.c_oflag &= ~OPOST; /* no additional CR and so on */
-
- tcsetattr(0, TCSANOW, &term);
- }
-#endif /* TIZEN */
-
- if (controlPort > 0)
- {
- fclose(stdin); // NOTE: detaching from stdin/out/err streams
- }
- if (dataPort > 0 && statPort > 0) {
- fclose(stdout); // NOTE: detaching from stdin/out/err streams
- }
-
- if (cmd_idx != -1) {
- // TODO: should be refactored: use separated launcher
-
- if (verbose) {
- log_message("launch command process %s", argv[cmd_idx]);
- for (int i = 0; i < additional_env_index; i++) {
- log_message(" additional environment: %s", additional_env[i]);
- }
- }
-
- cmd_pid = fork();
- if (!cmd_pid) {
- /* Child branch */
-
- // TODO: close unneeded descriptors
- // NOTE: /proc/self/fd/ can be used to iterate open descriptors
- close(STDIN_FILENO);
- close(STDOUT_FILENO);
- close(STDERR_FILENO);
-
- for (int i = 0; i < additional_env_index; i++) {
- putenv(additional_env[i]);
- }
- execv(argv[cmd_idx], &argv[cmd_idx]);
- exit(1);
- } else if (cmd_pid == -1) {
- log_system_error_and_exit("cannot fork command process");
- } else {
- if (verbose) {
- log_message("waiting for command process exit (pid=%d)", cmd_pid);
- }
- }
- }
-
if (controlPort > 0) {
- control_socket = openPort(controlPort);
- if (control_socket < 0) {
+ control_fd = openPort(controlPort);
+ if (control_fd < 0) {
log_error("cannot open control port");
exit(1);
}
- ctrl_file_in = fdopen(control_socket, "r");
- if (ctrl_file_in == NULL) {
- log_system_error_and_exit("fdopen(control, r)");
- }
- ctrl_file_out = fdopen(control_socket, "w");
+ ctrl_file_out = fdopen(control_fd, "w");
if (ctrl_file_out == NULL) {
log_system_error_and_exit("fdopen(control, w)");
}
}
}
else {
- ctrl_file_in = stdin;
+ ctrl_file_out = stdout;
+ control_fd = fileno(stdin);
}
+ fcntl(control_fd, F_SETFL, fcntl(control_fd, F_GETFL, 0) | O_NONBLOCK); /* make input non blocking */
+
if (pname) {
// TODO!! log to stderr as well if failed to start data_output_thread (i.e. before all 'return 1')
if (openFileProcess() != 0) {
return 1;
}
-
- if (dataPort > 0) {
- // data_output_thread will open the data port
-/*!!
- data_socket = openPort(dataPort);
- if (data_socket < 0) {
- return 1;
- }
- data_file_out = fdopen(data_socket, "w");
- if (data_file_out == NULL) {
- log_system_error("fdopen(data,w)");
- return 1;
- }
- if (fprintf(data_file_out, "ready\n") < 0 || fflush(data_file_out) != 0) {
- log_system_error("cannot write 'ready' prompt to data stream");
- return 1;
- }
-*/
- }
- else {
- data_file_out = stdout;
- }
}
if (doinfo && (statPort > 0)) {
}
}
+ // Start profiled process AFTER opening the sockets
+ // to avoid the need to kill the process if sockets can't be opened.
+ if (cmd_idx != -1) {
+
+ if (verbose) {
+ log_message("launch command process %s", argv[cmd_idx]);
+ for (int i = 0; i < additional_env_index; i++) {
+ log_message(" additional environment: %s", additional_env[i]);
+ }
+ }
+
+ cmd_pid = fork();
+ if (!cmd_pid) {
+ /* Child branch: */
+ /* redirect stdin to "/dev/null" */
+ int in = open("/dev/null", O_RDONLY);
+ if (-1 == in || -1 == dup2(in, STDIN_FILENO)) close(STDIN_FILENO);
+
+ /* redirect stdout to "/dev/null" */
+ int out = open("/dev/null", O_WRONLY);
+ if (-1 == out || -1 == dup2(out, STDOUT_FILENO)) close(STDOUT_FILENO);
+
+ /* redirect stderr to log (or left unchanged, if not possible to redirect) */
+ int fd = get_log_fd();
+ if (fd >= 0) dup2(fd, STDERR_FILENO);
+
+ for (int i = 0; i < additional_env_index; i++) {
+ putenv(additional_env[i]);
+ }
+ execv(argv[cmd_idx], &argv[cmd_idx]);
+ log_system_error("can't exec %s", argv[cmd_idx]);
+ exit(EXIT_FAILURE);
+
+ } else if (cmd_pid == -1) {
+ log_system_error_and_exit("cannot fork command process");
+ } else {
+ if (verbose) {
+ log_message("waiting for command process exit (pid=%d)", cmd_pid);
+ }
+ }
+ }
+
pthread_t data_output_thread_id;
+ memset(&data_output_thread_id, 0, sizeof(data_output_thread_id));
if (pname) {
- data_output_thread_runs = 1;
data_output_thread_id = SimpleThread(&data_output_thread, NULL);
}
- command_loop_thread_runs = 1;
pthread_t command_loop_thread_id = SimpleThread(&command_loop_thread, NULL);
- struct timespec poll_timeout;
- poll_timeout.tv_sec = 0;
- poll_timeout.tv_nsec = 250 * 1000000L; // 250 msec
-
+ int status = -1;
int last_app_pid = -1;
- while (!global_stop) {
- if (command_loop_thread_runs &&
- pthread_timedjoin_np(command_loop_thread_id, NULL, &poll_timeout) == 0)
- {
- break; // command loop thread finished
- }
- if (global_stop) {
- break;
- }
- if (data_output_thread_runs &&
- pthread_timedjoin_np(data_output_thread_id, NULL, &poll_timeout) == 0)
- {
- break; // data output thread finished
- }
- if (global_stop) {
- break;
+ int const maxfd = (chld_pipe[0] > stop_pipe[0] ? chld_pipe[0] : stop_pipe[0]) + 1;
+
+ // This is main loop, in which program waits until launched process terminates or
+ // until other thread not requests program termination (via writing to `stop_pipe[1]')
+ while (1) {
+ fd_set fdset;
+ FD_ZERO(&fdset);
+ FD_SET(stop_pipe[0], &fdset);
+ FD_SET(chld_pipe[0], &fdset);
+ struct timeval tv = { 0, 100000 }; // 100 msec
+ int nr = select(maxfd, &fdset, NULL, NULL, &tv);
+ if (nr == -1) {
+ if (errno == EINTR) continue;
+ log_system_error("select");
+ abort();
}
+
+ if (FD_ISSET(stop_pipe[0], &fdset))
+ break; // stop request
+
if (app_pid != -1) {
int app_runs = (kill(app_pid, 0) == 0);
if (!app_runs) {
if (verbose) {
log_message("controlled application finished (pid=%d)", app_pid);
}
- global_stop = 1;
- break;
+ break; // stop wairing
}
if (last_app_pid == -1) {
last_app_pid = app_pid;
}
}
}
- // TODO: should separated launcher to be used for pid control?
- if (cmd_pid != -1) {
- if (waitpid(cmd_pid, NULL, WNOHANG) > 0) {
- if (verbose) {
- log_message("command process finished (pid=%d)", cmd_pid);
+
+ // check if launched process finished, get it's return code
+ if (cmd_pid != -1 && FD_ISSET(chld_pipe[0], &fdset)) {
+ if (waitpid(cmd_pid, &status, WNOHANG) > 0) {
+ // use shell-compatible exit status as described here:
+ // https://www.tldp.org/LDP/abs/html/exitcodes.html
+ status = WIFSIGNALED(status) ? 128 + WTERMSIG(status) : WEXITSTATUS(status);
+ if (status != 0) {
+ log_error("command process finished (pid=%d) with error %d", cmd_pid, status);
+ }
+ else if (verbose) {
+ log_message("command process finished (pid=%d) with 0 exit status", cmd_pid);
}
cmd_pid = -1;
- global_stop = 1;
- break;
+ break; // stop waiting
}
}
- // sleep to reduce CPU usage
- usleep(100 * 1000); // 100 msec
- }
- if (verbose && !global_stop) {
- log_message("stop flag set (main)");
- }
- global_stop = 1;
- struct timespec join_timeout;
- join_timeout.tv_sec = 0;
- join_timeout.tv_nsec = 500 * 1000000L; // 500 msec
- if (command_loop_thread_runs) {
- if (pthread_timedjoin_np(command_loop_thread_id, NULL, &join_timeout) != 0) {
- pthread_cancel(command_loop_thread_id);
- }
- }
- if (data_output_thread_runs) {
- if (pthread_timedjoin_np(data_output_thread_id, NULL, &join_timeout) != 0) {
- pthread_cancel(data_output_thread_id);
- }
- }
- if (proc_stat_thread_runs) {
- if (pthread_timedjoin_np(proc_stat_thread_id, NULL, &join_timeout) != 0) {
- pthread_cancel(proc_stat_thread_id);
+
+ if (exe_pid > 0) {
+ int code;
+ if (waitpid(exe_pid, &code, WNOHANG) > 0) {
+ code = WIFSIGNALED(code) ? 128 + WTERMSIG(code) : WEXITSTATUS(code);
+ if (code != 0) {
+ log_error("helper process finished (pid=%d) with error %d", exe_pid, code);
+ }
+ else {
+ log_message("helper process finished (pid=%d) with 0 exit status", exe_pid);
+ }
+ exe_pid = -1;
+ break; // terminate execution
+ }
}
}
- // TODO: should be refactored: move to separated launcher
+ // terminate helper process, if it still not terminated
+ if (exe_pid > 0) kill(exe_pid, 9);
+
+ // stop other threads
+ int unused = write(stop_pipe[1], "", 1);
+ (void)unused; // avoid "output result not checked" and "unused variable" warnings
+
+ pthread_join(command_loop_thread_id, NULL);
+ if (pname) pthread_join(data_output_thread_id, NULL);
+ if (app_pid != -1) pthread_join(proc_stat_thread_id, NULL);
+
if (cmd_pid != -1) {
- int result = 0;
- for (int retry = 0; retry < 10; retry++) {
- result = waitpid(cmd_pid, NULL, WNOHANG);
- if (result != 0) {
- break;
+
+ // stop launched process forcibly, if it still not stopped
+ do {
+ // give 1 sec for application to terminate
+ fd_set fdset;
+ struct timeval tv = { 1, 0 }; // 1 sec
+ while (tv.tv_sec > 0 || tv.tv_usec > 0) {
+ FD_ZERO(&fdset);
+ FD_SET(chld_pipe[0], &fdset);
+
+ int nr = select(chld_pipe[0] + 1, &fdset, NULL, NULL, &tv);
+ if (nr == -1) {
+ if (errno == EINTR) continue;
+ log_system_error("select");
+ abort();
+ }
+
+ int res = waitpid(cmd_pid, &status, WNOHANG);
+ if (res < 0) {
+ log_system_error("waitpid(%d)", cmd_pid);
+ cmd_pid = -1;
+ break;
+ }
+ if (res == cmd_pid) {
+ cmd_pid = -1;
+ break;
+ }
}
- usleep(100 * 1000); // 100 ms
- }
- if (result == 0) {
+
+ if (cmd_pid == -1) break;
+
+ // send SIGTERM and give additional 1 sec
if (verbose) {
log_message("kill command process (pid=%d)", cmd_pid);
}
- result = send_signal(cmd_pid, SIGINT);
- if (result == 0)
- {
- for (int retry = 0; retry < 10; retry++) {
- result = waitpid(cmd_pid, NULL, WNOHANG);
- if (result != 0) {
- break;
- }
- usleep(100 * 1000); // 100 ms
+
+ if (send_signal(cmd_pid, SIGTERM) != 0) {
+ cmd_pid = -1;
+ break;
+ }
+
+ tv = (struct timeval){ 1, 0 };
+ while (tv.tv_sec > 0 || tv.tv_usec > 0) {
+ FD_ZERO(&fdset);
+ FD_SET(chld_pipe[0], &fdset);
+
+ int nr = select(chld_pipe[0] + 1, &fdset, NULL, NULL, &tv);
+ if (nr == -1) {
+ if (errno == EINTR) continue;
+ log_system_error("select");
+ abort();
}
- if (result == 0) {
- result = send_signal(cmd_pid, SIGKILL);
- if (result == 0)
- {
- result = waitpid(cmd_pid, NULL, 0);
- }
+
+ int res = waitpid(cmd_pid, &status, WNOHANG);
+ if (res < 0) {
+ log_system_error("waitpid(%d)", cmd_pid);
+ cmd_pid = -1;
+ break;
+ }
+ if (res == cmd_pid) {
+ cmd_pid = -1;
+ break;
}
}
- }
- if (result > 0) {
+
+ if (cmd_pid == -1) break;
+
+ // kill with SIGKILL
+ if (send_signal(cmd_pid, SIGKILL) != 0) {
+ cmd_pid = -1;
+ break;
+ }
+
+ if (waitpid(cmd_pid, &status, 0) < 0) {
+ log_system_error("waitpid(%d)", cmd_pid);
+ }
+
+ } while(0);
+
+ if (status != -1) {
+ status = WIFSIGNALED(status) ? 128 + WTERMSIG(status) : WEXITSTATUS(status);
if (verbose) {
- log_message("command process finished (pid=%d)", cmd_pid);
+ log_message("command process finished (pid=%d) with code %d", cmd_pid, status);
}
- cmd_pid = -1;
- } else if (result == -1) {
- log_system_error("cannot finish command process (pid=%d)", cmd_pid);
}
+ else {
+ log_system_error("cannot finish command process!");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* print "!exit <error-code>" message to `control_fd' -- this message allows to know
+ * exit code of the profiled process to the process, which controls whole workflow
+ * (usually Visual Studio) */
+ if (ctrl_file_out != NULL) {
+ if (fprintf(ctrl_file_out, "!exit %d\r\n", status) < 0 || fflush(ctrl_file_out) != 0)
+ log_system_error("cannot write command reply");
}
return 0;