root/daemon/loader.c @ a88ec8d69f922fad91f61239c8a7c84f915b825c

Revision a88ec8d69f922fad91f61239c8a7c84f915b825c, 14.3 KB (checked in by Nedko Arnaudov <nedko@…>, 3 years ago)

ladishd: project_name -> vgraph_name (in loader module)

  • Property mode set to 100644
Line 
1/* -*- Mode: C ; c-basic-offset: 2 -*- */
2/*
3 * LADI Session Handler (ladish)
4 *
5 * Copyright (C) 2008, 2009, 2010 Nedko Arnaudov <nedko@arnaudov.name>
6 * Copyright (C) 2008 Juuso Alasuutari <juuso.alasuutari@gmail.com>
7 * Copyright (C) 2002 Robert Ham <rah@bash.sh>
8 *
9 **************************************************************************
10 * This file contains code that starts programs
11 **************************************************************************
12 *
13 * LADI Session Handler is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * LADI Session Handler is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with LADI Session Handler. If not, see <http://www.gnu.org/licenses/>
25 * or write to the Free Software Foundation, Inc.,
26 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
27 */
28
29#define LADISH_DEBUG
30
31#include "common.h"
32
33#include <unistd.h>
34#include <fcntl.h>
35#include <pty.h>                /* forkpty() */
36#include <sys/wait.h>
37
38#include "loader.h"
39#include "../proxies/conf_proxy.h"
40#include "conf.h"
41
42#define XTERM_COMMAND_EXTENSION "&& sh || sh"
43
44#define CLIENT_OUTPUT_BUFFER_SIZE 2048
45
46struct loader_child
47{
48  struct list_head  siblings;
49
50  char * vgraph_name;
51  char * app_name;
52
53  bool dead;
54  pid_t pid;
55
56  bool terminal;
57
58  int stdout;
59  char stdout_buffer[CLIENT_OUTPUT_BUFFER_SIZE];
60  char stdout_last_line[CLIENT_OUTPUT_BUFFER_SIZE];
61  unsigned int stdout_last_line_repeat_count;
62  char * stdout_buffer_ptr;
63
64  int stderr;
65  char stderr_buffer[CLIENT_OUTPUT_BUFFER_SIZE];
66  char stderr_last_line[CLIENT_OUTPUT_BUFFER_SIZE];
67  unsigned int stderr_last_line_repeat_count;
68  char * stderr_buffer_ptr;
69};
70
71static void (* g_on_child_exit)(pid_t pid);
72static struct list_head g_childs_list;
73
74static struct loader_child *
75loader_child_find_and_mark_dead(pid_t pid)
76{
77  struct list_head *node_ptr;
78  struct loader_child *child_ptr;
79
80  list_for_each (node_ptr, &g_childs_list)
81  {
82    child_ptr = list_entry(node_ptr, struct loader_child, siblings);
83    if (child_ptr->pid == pid)
84    {
85      child_ptr->dead = true;
86      return child_ptr;
87    }
88  }
89
90  return NULL;
91}
92
93static
94void
95loader_check_line_repeat_end(
96  char * vgraph_name,
97  char * app_name,
98  bool error,
99  unsigned int last_line_repeat_count)
100{
101  if (last_line_repeat_count >= 2)
102  {
103    if (error)
104    {
105      log_error_plain("%s:%s: stderr line repeated %u times", vgraph_name, app_name, last_line_repeat_count);
106    }
107    else
108    {
109      log_info("%s:%s: stdout line repeated %u times", vgraph_name, app_name, last_line_repeat_count);
110    }
111  }
112}
113
114static void
115loader_childs_bury(void)
116{
117  struct list_head *node_ptr;
118  struct list_head *next_ptr;
119  struct loader_child *child_ptr;
120
121  list_for_each_safe (node_ptr, next_ptr, &g_childs_list)
122  {
123    child_ptr = list_entry(node_ptr, struct loader_child, siblings);
124    if (child_ptr->dead)
125    {
126      loader_check_line_repeat_end(
127        child_ptr->vgraph_name,
128        child_ptr->app_name,
129        false,
130        child_ptr->stdout_last_line_repeat_count);
131
132      loader_check_line_repeat_end(
133        child_ptr->vgraph_name,
134        child_ptr->app_name,
135        true,
136        child_ptr->stderr_last_line_repeat_count);
137
138      log_debug("Bury child '%s' with PID %llu", child_ptr->app_name, (unsigned long long)child_ptr->pid);
139
140      list_del(&child_ptr->siblings);
141
142      free(child_ptr->vgraph_name);
143      free(child_ptr->app_name);
144
145      if (!child_ptr->terminal)
146      {
147        close(child_ptr->stdout);
148        close(child_ptr->stderr);
149      }
150
151      g_on_child_exit(child_ptr->pid);
152      free(child_ptr);
153    }
154  }
155}
156
157static void loader_sigchld_handler(int signum)
158{
159  int status;
160  pid_t pid;
161  struct loader_child *child_ptr;
162  int signal;
163
164  while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
165  {
166    child_ptr = loader_child_find_and_mark_dead(pid);
167
168    if (!child_ptr)
169    {
170      log_error("Termination of unknown child process with PID %llu detected", (unsigned long long)pid);
171    }
172    else
173    {
174      log_info("Termination of child process '%s' with PID %llu detected", child_ptr->app_name, (unsigned long long)pid);
175    }
176
177    if (WIFEXITED(status))
178    {
179      log_info("Child exited, status=%d", WEXITSTATUS(status));
180    }
181    else if (WIFSIGNALED(status))
182    {
183      signal = WTERMSIG(status);
184      switch (signal)
185      {
186      case SIGILL:
187      case SIGABRT:
188      case SIGSEGV:
189      case SIGFPE:
190        log_error("Child was killed by signal %d", signal);
191        break;
192      default:
193        log_info("Child was killed by signal %d", signal);
194      }
195    }
196    else if (WIFSTOPPED(status))
197    {
198      log_info("Child was stopped by signal %d", WSTOPSIG(status));
199    }
200  }
201}
202
203void loader_init(void (* on_child_exit)(pid_t pid))
204{
205  g_on_child_exit = on_child_exit;
206  signal(SIGCHLD, loader_sigchld_handler);
207  INIT_LIST_HEAD(&g_childs_list);
208}
209
210void loader_uninit(void)
211{
212  loader_childs_bury();
213}
214
215#if 0
216static void loader_exec_program_in_xterm(const char * const * argv)
217{
218  char * dst_ptr;
219  const char * const * src_ptr_ptr;
220  size_t len;
221
222  log_debug("Executing program '%s' with PID %llu in terminal", argv[0], (unsigned long long)getpid());
223
224  /* Calculate the command string length */
225  len = strlen(XTERM_COMMAND_EXTENSION) + 1;
226  for (src_ptr_ptr = argv; *src_ptr_ptr != NULL; src_ptr_ptr++)
227  {
228    len += strlen(*src_ptr_ptr) + 3; /* three additional chars per argument: two double quotes and a space */
229  }
230
231  char buf[len];                /* dynamically allocate in stack */
232
233  /* Create the command string */
234  src_ptr_ptr = argv;
235  dst_ptr = buf;
236  while (*src_ptr_ptr != NULL)
237  {
238    len = strlen(*src_ptr_ptr);
239    dst_ptr[0] = '"';
240    memcpy(dst_ptr + 1, src_ptr_ptr, len);
241    dst_ptr[1 + len] = '"';
242    dst_ptr[1 + len + 1] = ' ';
243    dst_ptr += len + 3;
244    src_ptr_ptr++;
245  }
246
247  strcat(dst_ptr, XTERM_COMMAND_EXTENSION);
248
249  /* Execute the command */
250  execlp("xterm", "xterm", "-e", "/bin/sh", "-c", buf, NULL);
251
252  log_error("Failed to execute command '%s' in terminal: %s", buf, strerror(errno));
253
254  exit(1);
255}
256#endif
257
258static void loader_exec_program(const char * commandline, const char * working_dir, bool run_in_terminal)
259{
260  const char * argv[4];
261
262  /* for non terminal processes we use forkpty() that calls login_tty() that calls setsid() */
263  /* we can successful call setsid() only once */
264  if (run_in_terminal)
265  {
266    /* no longer anything to do with lashd */
267    if (setsid() == -1)
268    {
269      log_error("Could not create new process group: %s", strerror(errno));
270    }
271  }
272
273  /* change the working dir */
274  if (chdir(working_dir) == -1)
275  {
276    log_error("Could not change directory to working dir '%s' for program '%s': %s", working_dir, argv[0], strerror(errno));
277  }
278
279  if (run_in_terminal)
280  {
281    if (!conf_get(LADISH_CONF_KEY_DAEMON_TERMINAL, argv))
282    {
283      argv[0] = LADISH_CONF_KEY_DAEMON_TERMINAL_DEFAULT;
284    }
285
286    argv[1] = "-e";
287  }
288  else
289  {
290    if (!conf_get(LADISH_CONF_KEY_DAEMON_SHELL, argv))
291    {
292      argv[0] = LADISH_CONF_KEY_DAEMON_SHELL_DEFAULT;
293    }
294
295    argv[1] = "-c";
296  }
297
298  argv[2] = commandline;
299  argv[3] = NULL;
300
301  log_info("Executing '%s' with PID %llu", commandline, (unsigned long long)getpid());
302
303  /* Execute it */
304  execvp(argv[0], (char **)argv);
305
306  log_error("Executing program '%s' failed: %s", argv[0], strerror(errno));
307
308  exit(1);
309}
310
311static
312void
313loader_read_child_output(
314  char * vgraph_name,
315  char * app_name,
316  int fd,
317  bool error,
318  char * buffer_ptr,
319  char ** buffer_ptr_ptr,
320  char * last_line,
321  unsigned int * last_line_repeat_count)
322{
323  ssize_t ret;
324  char *char_ptr;
325  char *eol_ptr;
326  size_t left;
327  size_t max_read;
328
329  do
330  {
331    max_read = CLIENT_OUTPUT_BUFFER_SIZE - 1 - (*buffer_ptr_ptr - buffer_ptr);
332    ret = read(fd, *buffer_ptr_ptr, max_read);
333    if (ret > 0)
334    {
335      (*buffer_ptr_ptr)[ret] = 0;
336      char_ptr = buffer_ptr;
337
338      while ((eol_ptr = strchr(char_ptr, '\n')) != NULL)
339      {
340        *eol_ptr = 0;
341
342        if (*last_line_repeat_count > 0 && strcmp(last_line, char_ptr) == 0)
343        {
344          if (*last_line_repeat_count == 1)
345          {
346            if (error)
347            {
348              log_error_plain("%s:%s: last stderr line repeating..", vgraph_name, app_name);
349            }
350            else
351            {
352              log_info("%s:%s: last stdout line repeating...", vgraph_name, app_name);
353            }
354          }
355
356          (*last_line_repeat_count)++;
357        }
358        else
359        {
360          loader_check_line_repeat_end(vgraph_name, app_name, error, *last_line_repeat_count);
361
362          strcpy(last_line, char_ptr);
363          *last_line_repeat_count = 1;
364
365          if (error)
366          {
367            log_error_plain("%s:%s: %s", vgraph_name, app_name, char_ptr);
368          }
369          else
370          {
371            log_info("%s:%s: %s", vgraph_name, app_name, char_ptr);
372          }
373        }
374
375        char_ptr = eol_ptr + 1;
376      }
377
378      left = ret - (char_ptr - *buffer_ptr_ptr);
379      if (left != 0)
380      {
381        /* last line does not end with newline */
382
383        if (left == CLIENT_OUTPUT_BUFFER_SIZE - 1)
384        {
385          /* line is too long to fit in buffer */
386          /* print it like it is, rest (or more) of it will be logged on next interation */
387
388          if (error)
389          {
390            log_error_plain("%s:%s: %s " ANSI_RESET ANSI_COLOR_RED "(truncated) " ANSI_RESET, vgraph_name, app_name, char_ptr);
391          }
392          else
393          {
394            log_info("%s:%s: %s " ANSI_RESET ANSI_COLOR_RED "(truncated) " ANSI_RESET, vgraph_name, app_name, char_ptr);
395          }
396
397          left = 0;
398        }
399        else
400        {
401          memmove(buffer_ptr, char_ptr, left);
402        }
403      }
404
405      *buffer_ptr_ptr = buffer_ptr + left;
406    }
407  }
408  while (ret == max_read);      /* if we have read everything as much as we can, then maybe there is more to read */
409}
410
411static void
412loader_read_childs_output(void)
413{
414  struct list_head * node_ptr;
415  struct loader_child * child_ptr;
416
417  list_for_each (node_ptr, &g_childs_list)
418  {
419    child_ptr = list_entry(node_ptr, struct loader_child, siblings);
420
421    if (!child_ptr->dead && !child_ptr->terminal)
422    {
423      loader_read_child_output(
424        child_ptr->vgraph_name,
425        child_ptr->app_name,
426        child_ptr->stdout,
427        false,
428        child_ptr->stdout_buffer,
429        &child_ptr->stdout_buffer_ptr,
430        child_ptr->stdout_last_line,
431        &child_ptr->stdout_last_line_repeat_count);
432
433      loader_read_child_output(
434        child_ptr->vgraph_name,
435        child_ptr->app_name,
436        child_ptr->stderr,
437        true,
438        child_ptr->stderr_buffer,
439        &child_ptr->stderr_buffer_ptr,
440        child_ptr->stderr_last_line,
441        &child_ptr->stderr_last_line_repeat_count);
442    }
443  }
444}
445
446void
447loader_run(void)
448{
449  loader_read_childs_output();
450  loader_childs_bury();
451}
452
453bool
454loader_execute(
455  const char * vgraph_name,
456  const char * app_name,
457  const char * working_dir,
458  bool run_in_terminal,
459  const char * commandline,
460  pid_t * pid_ptr)
461{
462  pid_t pid;
463  struct loader_child * child_ptr;
464  int stderr_pipe[2];
465
466  child_ptr = malloc(sizeof(struct loader_child));
467  if (child_ptr == NULL)
468  {
469    log_error("malloc() failed to allocate struct loader_child");
470    goto fail;
471  }
472
473  child_ptr->vgraph_name = strdup(vgraph_name);
474  if (child_ptr->vgraph_name == NULL)
475  {
476    log_error("strdup() failed to duplicate vgraph name '%s'", vgraph_name);
477    goto free_struct;
478  }
479
480  child_ptr->app_name = strdup(app_name);
481  if (child_ptr->app_name == NULL)
482  {
483    log_error("strdup() failed to duplicate app name '%s'", app_name);
484    goto free_vgraph_name;
485  }
486
487  child_ptr->dead = false;
488  child_ptr->terminal = run_in_terminal;
489  child_ptr->stdout_buffer_ptr = child_ptr->stdout_buffer;
490  child_ptr->stderr_buffer_ptr = child_ptr->stderr_buffer;
491  child_ptr->stdout_last_line_repeat_count = 0;
492  child_ptr->stderr_last_line_repeat_count = 0;
493
494  if (!run_in_terminal)
495  {
496    if (pipe(stderr_pipe) == -1)
497    {
498      log_error("Failed to create stderr pipe");
499    }
500    else
501    {
502      child_ptr->stderr = stderr_pipe[0];
503
504      if (fcntl(child_ptr->stderr, F_SETFL, O_NONBLOCK) == -1)
505      {
506        log_error("Failed to set nonblocking mode on "
507                   "stderr reading end: %s",
508                   strerror(errno));
509        close(stderr_pipe[0]);
510        close(stderr_pipe[1]);
511      }
512    }
513  }
514
515  list_add_tail(&child_ptr->siblings, &g_childs_list);
516
517  if (!run_in_terminal)
518  {
519    /* We need pty to disable libc buffering of stdout */
520    pid = forkpty(&child_ptr->stdout, NULL, NULL, NULL);
521  }
522  else
523  {
524    pid = fork();
525  }
526
527  if (pid == -1)
528  {
529    log_error("Could not fork to exec program %s:%s: %s", vgraph_name, app_name, strerror(errno));
530    list_del(&child_ptr->siblings); /* fork failed so it is not really a child process to watch for. */
531    return false;
532  }
533
534  if (pid == 0)
535  {
536    /* Need to close all open file descriptors except the std ones */
537    struct rlimit max_fds;
538    rlim_t fd;
539
540    getrlimit(RLIMIT_NOFILE, &max_fds);
541
542    for (fd = 3; fd < max_fds.rlim_cur; ++fd)
543    {
544      close(fd);
545    }
546
547    if (!run_in_terminal)
548    {
549      /* In child, close unused reading end of pipe */
550      close(stderr_pipe[0]);
551
552      dup2(stderr_pipe[1], fileno(stderr));
553    }
554
555    putenv("LD_PRELOAD=libalsapid.so");
556
557    loader_exec_program(commandline, working_dir, run_in_terminal);
558
559    return false;  /* We should never get here */
560  }
561
562  if (!run_in_terminal)
563  {
564    /* In parent, close unused writing ends of pipe */
565    close(stderr_pipe[1]);
566
567    if (fcntl(child_ptr->stdout, F_SETFL, O_NONBLOCK) == -1)
568    {
569      log_error("Could not set noblocking mode on stdout "
570                 "- pty: %s", strerror(errno));
571      close(stderr_pipe[0]);
572      close(child_ptr->stdout);
573    }
574  }
575
576  log_info("Forked to run program %s:%s pid = %llu", vgraph_name, app_name, (unsigned long long)pid);
577
578  *pid_ptr = child_ptr->pid = pid;
579
580  return true;
581
582free_vgraph_name:
583  free(child_ptr->vgraph_name);
584
585free_struct:
586  free(child_ptr);
587
588fail:
589  return false;
590}
591
592unsigned int loader_get_app_count(void)
593{
594  struct list_head * node_ptr;
595  unsigned int count;
596
597  count = 0;
598
599  list_for_each(node_ptr, &g_childs_list)
600  {
601    count++;
602  }
603
604  return count;
605}
Note: See TracBrowser for help on using the browser.