root/daemon/loader.c @ d027731b1848a3f06462a0aa71fd9e5926e14c9a

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

ladishd: start terminal apps through shell

This will allow expansion of env vars used in commandline to happen
when apps are started in terminal. It already happens when apps are
started without terminal.

This breaks the default title, at least for xterm. For it, it is now
set explicitly.

Starting through shell is not used when there is no '$' in the
commandline. This should minimize the title problems when xterm is not
used.

  • 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
259void
260loader_exec_program(
261  const char * commandline,
262  const char * working_dir,
263  bool run_in_terminal,
264  const char * vgraph_name,
265  const char * app_name)
266{
267  const char * argv[8];
268  unsigned int i;
269
270  /* for non terminal processes we use forkpty() that calls login_tty() that calls setsid() */
271  /* we can successful call setsid() only once */
272  if (run_in_terminal)
273  {
274    /* no longer anything to do with lashd */
275    if (setsid() == -1)
276    {
277      log_error("Could not create new process group: %s", strerror(errno));
278    }
279  }
280
281  /* change the working dir */
282  if (chdir(working_dir) == -1)
283  {
284    log_error("Could not change directory to working dir '%s' for program '%s': %s", working_dir, argv[0], strerror(errno));
285  }
286
287  i = 0;
288
289  if (run_in_terminal)
290  {
291    if (!conf_get(LADISH_CONF_KEY_DAEMON_TERMINAL, argv + i))
292    {
293      argv[i] = LADISH_CONF_KEY_DAEMON_TERMINAL_DEFAULT;
294    }
295    i++;
296
297    if (strcmp(argv[0], "xterm") == 0 &&
298        strchr(app_name, '"') == NULL &&
299        strchr(app_name, '\'') == NULL &&
300        strchr(app_name, '`') == NULL)
301    {
302      argv[i++] = "-T";
303      argv[i++] = app_name;
304    }
305
306    argv[i++] = "-e";
307  }
308
309  if (!conf_get(LADISH_CONF_KEY_DAEMON_SHELL, argv + i))
310  {
311    argv[i] = LADISH_CONF_KEY_DAEMON_SHELL_DEFAULT;
312  }
313  i++;
314
315  argv[i++] = "-c";
316
317  argv[i++] = commandline;
318  argv[i++] = NULL;
319
320  log_info("Executing '%s' with PID %llu", commandline, (unsigned long long)getpid());
321
322  /* Execute it */
323  execvp(argv[0], (char **)argv);
324
325  log_error("Executing program '%s' failed: %s", argv[0], strerror(errno));
326
327  exit(1);
328}
329
330static
331void
332loader_read_child_output(
333  char * vgraph_name,
334  char * app_name,
335  int fd,
336  bool error,
337  char * buffer_ptr,
338  char ** buffer_ptr_ptr,
339  char * last_line,
340  unsigned int * last_line_repeat_count)
341{
342  ssize_t ret;
343  char *char_ptr;
344  char *eol_ptr;
345  size_t left;
346  size_t max_read;
347
348  do
349  {
350    max_read = CLIENT_OUTPUT_BUFFER_SIZE - 1 - (*buffer_ptr_ptr - buffer_ptr);
351    ret = read(fd, *buffer_ptr_ptr, max_read);
352    if (ret > 0)
353    {
354      (*buffer_ptr_ptr)[ret] = 0;
355      char_ptr = buffer_ptr;
356
357      while ((eol_ptr = strchr(char_ptr, '\n')) != NULL)
358      {
359        *eol_ptr = 0;
360
361        if (*last_line_repeat_count > 0 && strcmp(last_line, char_ptr) == 0)
362        {
363          if (*last_line_repeat_count == 1)
364          {
365            if (error)
366            {
367              log_error_plain("%s:%s: last stderr line repeating..", vgraph_name, app_name);
368            }
369            else
370            {
371              log_info("%s:%s: last stdout line repeating...", vgraph_name, app_name);
372            }
373          }
374
375          (*last_line_repeat_count)++;
376        }
377        else
378        {
379          loader_check_line_repeat_end(vgraph_name, app_name, error, *last_line_repeat_count);
380
381          strcpy(last_line, char_ptr);
382          *last_line_repeat_count = 1;
383
384          if (error)
385          {
386            log_error_plain("%s:%s: %s", vgraph_name, app_name, char_ptr);
387          }
388          else
389          {
390            log_info("%s:%s: %s", vgraph_name, app_name, char_ptr);
391          }
392        }
393
394        char_ptr = eol_ptr + 1;
395      }
396
397      left = ret - (char_ptr - *buffer_ptr_ptr);
398      if (left != 0)
399      {
400        /* last line does not end with newline */
401
402        if (left == CLIENT_OUTPUT_BUFFER_SIZE - 1)
403        {
404          /* line is too long to fit in buffer */
405          /* print it like it is, rest (or more) of it will be logged on next interation */
406
407          if (error)
408          {
409            log_error_plain("%s:%s: %s " ANSI_RESET ANSI_COLOR_RED "(truncated) " ANSI_RESET, vgraph_name, app_name, char_ptr);
410          }
411          else
412          {
413            log_info("%s:%s: %s " ANSI_RESET ANSI_COLOR_RED "(truncated) " ANSI_RESET, vgraph_name, app_name, char_ptr);
414          }
415
416          left = 0;
417        }
418        else
419        {
420          memmove(buffer_ptr, char_ptr, left);
421        }
422      }
423
424      *buffer_ptr_ptr = buffer_ptr + left;
425    }
426  }
427  while (ret == max_read);      /* if we have read everything as much as we can, then maybe there is more to read */
428}
429
430static void
431loader_read_childs_output(void)
432{
433  struct list_head * node_ptr;
434  struct loader_child * child_ptr;
435
436  list_for_each (node_ptr, &g_childs_list)
437  {
438    child_ptr = list_entry(node_ptr, struct loader_child, siblings);
439
440    if (!child_ptr->dead && !child_ptr->terminal)
441    {
442      loader_read_child_output(
443        child_ptr->vgraph_name,
444        child_ptr->app_name,
445        child_ptr->stdout,
446        false,
447        child_ptr->stdout_buffer,
448        &child_ptr->stdout_buffer_ptr,
449        child_ptr->stdout_last_line,
450        &child_ptr->stdout_last_line_repeat_count);
451
452      loader_read_child_output(
453        child_ptr->vgraph_name,
454        child_ptr->app_name,
455        child_ptr->stderr,
456        true,
457        child_ptr->stderr_buffer,
458        &child_ptr->stderr_buffer_ptr,
459        child_ptr->stderr_last_line,
460        &child_ptr->stderr_last_line_repeat_count);
461    }
462  }
463}
464
465void
466loader_run(void)
467{
468  loader_read_childs_output();
469  loader_childs_bury();
470}
471
472bool
473loader_execute(
474  const char * vgraph_name,
475  const char * app_name,
476  const char * working_dir,
477  bool run_in_terminal,
478  const char * commandline,
479  pid_t * pid_ptr)
480{
481  pid_t pid;
482  struct loader_child * child_ptr;
483  int stderr_pipe[2];
484
485  child_ptr = malloc(sizeof(struct loader_child));
486  if (child_ptr == NULL)
487  {
488    log_error("malloc() failed to allocate struct loader_child");
489    goto fail;
490  }
491
492  child_ptr->vgraph_name = strdup(vgraph_name);
493  if (child_ptr->vgraph_name == NULL)
494  {
495    log_error("strdup() failed to duplicate vgraph name '%s'", vgraph_name);
496    goto free_struct;
497  }
498
499  child_ptr->app_name = strdup(app_name);
500  if (child_ptr->app_name == NULL)
501  {
502    log_error("strdup() failed to duplicate app name '%s'", app_name);
503    goto free_vgraph_name;
504  }
505
506  child_ptr->dead = false;
507  child_ptr->terminal = run_in_terminal;
508  child_ptr->stdout_buffer_ptr = child_ptr->stdout_buffer;
509  child_ptr->stderr_buffer_ptr = child_ptr->stderr_buffer;
510  child_ptr->stdout_last_line_repeat_count = 0;
511  child_ptr->stderr_last_line_repeat_count = 0;
512
513  if (!run_in_terminal)
514  {
515    if (pipe(stderr_pipe) == -1)
516    {
517      log_error("Failed to create stderr pipe");
518    }
519    else
520    {
521      child_ptr->stderr = stderr_pipe[0];
522
523      if (fcntl(child_ptr->stderr, F_SETFL, O_NONBLOCK) == -1)
524      {
525        log_error("Failed to set nonblocking mode on "
526                   "stderr reading end: %s",
527                   strerror(errno));
528        close(stderr_pipe[0]);
529        close(stderr_pipe[1]);
530      }
531    }
532  }
533
534  list_add_tail(&child_ptr->siblings, &g_childs_list);
535
536  if (!run_in_terminal)
537  {
538    /* We need pty to disable libc buffering of stdout */
539    pid = forkpty(&child_ptr->stdout, NULL, NULL, NULL);
540  }
541  else
542  {
543    pid = fork();
544  }
545
546  if (pid == -1)
547  {
548    log_error("Could not fork to exec program %s:%s: %s", vgraph_name, app_name, strerror(errno));
549    list_del(&child_ptr->siblings); /* fork failed so it is not really a child process to watch for. */
550    return false;
551  }
552
553  if (pid == 0)
554  {
555    /* Need to close all open file descriptors except the std ones */
556    struct rlimit max_fds;
557    rlim_t fd;
558
559    getrlimit(RLIMIT_NOFILE, &max_fds);
560
561    for (fd = 3; fd < max_fds.rlim_cur; ++fd)
562    {
563      close(fd);
564    }
565
566    if (!run_in_terminal)
567    {
568      /* In child, close unused reading end of pipe */
569      close(stderr_pipe[0]);
570
571      dup2(stderr_pipe[1], fileno(stderr));
572    }
573
574    putenv("LD_PRELOAD=libalsapid.so");
575
576    loader_exec_program(commandline, working_dir, run_in_terminal, vgraph_name, app_name);
577
578    return false;  /* We should never get here */
579  }
580
581  if (!run_in_terminal)
582  {
583    /* In parent, close unused writing ends of pipe */
584    close(stderr_pipe[1]);
585
586    if (fcntl(child_ptr->stdout, F_SETFL, O_NONBLOCK) == -1)
587    {
588      log_error("Could not set noblocking mode on stdout "
589                 "- pty: %s", strerror(errno));
590      close(stderr_pipe[0]);
591      close(child_ptr->stdout);
592    }
593  }
594
595  log_info("Forked to run program %s:%s pid = %llu", vgraph_name, app_name, (unsigned long long)pid);
596
597  *pid_ptr = child_ptr->pid = pid;
598
599  return true;
600
601free_vgraph_name:
602  free(child_ptr->vgraph_name);
603
604free_struct:
605  free(child_ptr);
606
607fail:
608  return false;
609}
610
611unsigned int loader_get_app_count(void)
612{
613  struct list_head * node_ptr;
614  unsigned int count;
615
616  count = 0;
617
618  list_for_each(node_ptr, &g_childs_list)
619  {
620    count++;
621  }
622
623  return count;
624}
Note: See TracBrowser for help on using the browser.