root/daemon/loader.c @ 60aaa492db336e5685ff19f3a2600bbbaf074cef

Revision 60aaa492db336e5685ff19f3a2600bbbaf074cef, 15.9 KB (checked in by Nedko Arnaudov <nedko@…>, 2 years ago)

Make alsapid work when libasound is loaded with dlopen(). Fix for #180

when alsapid is preloaded libasound is not loaded yet
for some unknown reason, late call to dlvsym() fails as well,
at least for mididings (python loads _mididings.so that
implicitly loads libasound.so)

this changeset implements the late symbol lookup,
because it makes the code smaller

the actual fix is to LD_PRELOAD libasound.so as well

  • 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, 2011 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#include "../common/catdup.h"
42
43#define XTERM_COMMAND_EXTENSION "&& sh || sh"
44
45#define CLIENT_OUTPUT_BUFFER_SIZE 2048
46
47struct loader_child
48{
49  struct list_head  siblings;
50
51  char * vgraph_name;
52  char * app_name;
53  char * project_name;
54
55  bool dead;
56  pid_t pid;
57
58  bool terminal;
59
60  int stdout;
61  char stdout_buffer[CLIENT_OUTPUT_BUFFER_SIZE];
62  char stdout_last_line[CLIENT_OUTPUT_BUFFER_SIZE];
63  unsigned int stdout_last_line_repeat_count;
64  char * stdout_buffer_ptr;
65
66  int stderr;
67  char stderr_buffer[CLIENT_OUTPUT_BUFFER_SIZE];
68  char stderr_last_line[CLIENT_OUTPUT_BUFFER_SIZE];
69  unsigned int stderr_last_line_repeat_count;
70  char * stderr_buffer_ptr;
71};
72
73static void (* g_on_child_exit)(pid_t pid);
74static struct list_head g_childs_list;
75
76static struct loader_child *
77loader_child_find_and_mark_dead(pid_t pid)
78{
79  struct list_head *node_ptr;
80  struct loader_child *child_ptr;
81
82  list_for_each (node_ptr, &g_childs_list)
83  {
84    child_ptr = list_entry(node_ptr, struct loader_child, siblings);
85    if (child_ptr->pid == pid)
86    {
87      child_ptr->dead = true;
88      return child_ptr;
89    }
90  }
91
92  return NULL;
93}
94
95static
96void
97loader_check_line_repeat_end(
98  char * vgraph_name,
99  char * app_name,
100  bool error,
101  unsigned int last_line_repeat_count)
102{
103  if (last_line_repeat_count >= 2)
104  {
105    if (error)
106    {
107      log_error_plain("%s:%s: stderr line repeated %u times", vgraph_name, app_name, last_line_repeat_count);
108    }
109    else
110    {
111      log_info("%s:%s: stdout line repeated %u times", vgraph_name, app_name, last_line_repeat_count);
112    }
113  }
114}
115
116static void
117loader_childs_bury(void)
118{
119  struct list_head *node_ptr;
120  struct list_head *next_ptr;
121  struct loader_child *child_ptr;
122
123  list_for_each_safe (node_ptr, next_ptr, &g_childs_list)
124  {
125    child_ptr = list_entry(node_ptr, struct loader_child, siblings);
126    if (child_ptr->dead)
127    {
128      loader_check_line_repeat_end(
129        child_ptr->vgraph_name,
130        child_ptr->app_name,
131        false,
132        child_ptr->stdout_last_line_repeat_count);
133
134      loader_check_line_repeat_end(
135        child_ptr->vgraph_name,
136        child_ptr->app_name,
137        true,
138        child_ptr->stderr_last_line_repeat_count);
139
140      log_debug("Bury child '%s' with PID %llu", child_ptr->app_name, (unsigned long long)child_ptr->pid);
141
142      list_del(&child_ptr->siblings);
143
144      free(child_ptr->project_name);
145      free(child_ptr->vgraph_name);
146      free(child_ptr->app_name);
147
148      if (!child_ptr->terminal)
149      {
150        close(child_ptr->stdout);
151        close(child_ptr->stderr);
152      }
153
154      g_on_child_exit(child_ptr->pid);
155      free(child_ptr);
156    }
157  }
158}
159
160static void loader_sigchld_handler(int signum)
161{
162  int status;
163  pid_t pid;
164  struct loader_child *child_ptr;
165  int signal;
166
167  while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
168  {
169    child_ptr = loader_child_find_and_mark_dead(pid);
170
171    if (!child_ptr)
172    {
173      log_error("Termination of unknown child process with PID %llu detected", (unsigned long long)pid);
174    }
175    else
176    {
177      log_info("Termination of child process '%s' with PID %llu detected", child_ptr->app_name, (unsigned long long)pid);
178    }
179
180    if (WIFEXITED(status))
181    {
182      log_info("Child exited, status=%d", WEXITSTATUS(status));
183    }
184    else if (WIFSIGNALED(status))
185    {
186      signal = WTERMSIG(status);
187      switch (signal)
188      {
189      case SIGILL:
190      case SIGABRT:
191      case SIGSEGV:
192      case SIGFPE:
193        log_error("Child was killed by signal %d", signal);
194        break;
195      default:
196        log_info("Child was killed by signal %d", signal);
197      }
198    }
199    else if (WIFSTOPPED(status))
200    {
201      log_info("Child was stopped by signal %d", WSTOPSIG(status));
202    }
203  }
204}
205
206void loader_init(void (* on_child_exit)(pid_t pid))
207{
208  g_on_child_exit = on_child_exit;
209  signal(SIGCHLD, loader_sigchld_handler);
210  INIT_LIST_HEAD(&g_childs_list);
211}
212
213void loader_uninit(void)
214{
215  loader_childs_bury();
216}
217
218#if 0
219static void loader_exec_program_in_xterm(const char * const * argv)
220{
221  char * dst_ptr;
222  const char * const * src_ptr_ptr;
223  size_t len;
224
225  log_debug("Executing program '%s' with PID %llu in terminal", argv[0], (unsigned long long)getpid());
226
227  /* Calculate the command string length */
228  len = strlen(XTERM_COMMAND_EXTENSION) + 1;
229  for (src_ptr_ptr = argv; *src_ptr_ptr != NULL; src_ptr_ptr++)
230  {
231    len += strlen(*src_ptr_ptr) + 3; /* three additional chars per argument: two double quotes and a space */
232  }
233
234  char buf[len];                /* dynamically allocate in stack */
235
236  /* Create the command string */
237  src_ptr_ptr = argv;
238  dst_ptr = buf;
239  while (*src_ptr_ptr != NULL)
240  {
241    len = strlen(*src_ptr_ptr);
242    dst_ptr[0] = '"';
243    memcpy(dst_ptr + 1, src_ptr_ptr, len);
244    dst_ptr[1 + len] = '"';
245    dst_ptr[1 + len + 1] = ' ';
246    dst_ptr += len + 3;
247    src_ptr_ptr++;
248  }
249
250  strcat(dst_ptr, XTERM_COMMAND_EXTENSION);
251
252  /* Execute the command */
253  execlp("xterm", "xterm", "-e", "/bin/sh", "-c", buf, NULL);
254
255  log_error("Failed to execute command '%s' in terminal: %s", buf, strerror(errno));
256
257  exit(1);
258}
259#endif
260
261static
262void
263loader_exec_program(
264  const char * commandline,
265  const char * working_dir,
266  bool run_in_terminal,
267  const char * vgraph_name,
268  const char * project_name,
269  const char * app_name)
270{
271  const char * argv[8];
272  unsigned int i;
273
274  /* for non terminal processes we use forkpty() that calls login_tty() that calls setsid() */
275  /* we can successful call setsid() only once */
276  if (run_in_terminal)
277  {
278    /* no longer anything to do with lashd */
279    if (setsid() == -1)
280    {
281      log_error("Could not create new process group: %s", strerror(errno));
282    }
283  }
284
285  /* change the working dir */
286  if (chdir(working_dir) == -1)
287  {
288    log_error("Could not change directory to working dir '%s' for program '%s': %s", working_dir, argv[0], strerror(errno));
289  }
290
291  setenv("LADISH_APP_NAME", app_name, true);
292  setenv("LADISH_VGRAPH_NAME", vgraph_name, true);
293
294  if (project_name != NULL)
295  {
296    setenv("LADISH_PROJECT_NAME", project_name, true);
297  }
298
299  i = 0;
300
301  if (run_in_terminal)
302  {
303    if (!conf_get(LADISH_CONF_KEY_DAEMON_TERMINAL, argv + i))
304    {
305      argv[i] = LADISH_CONF_KEY_DAEMON_TERMINAL_DEFAULT;
306    }
307    i++;
308
309    if (strcmp(argv[0], "xterm") == 0 &&
310        strchr(app_name, '"') == NULL &&
311        strchr(app_name, '\'') == NULL &&
312        strchr(app_name, '`') == NULL)
313    {
314      argv[i++] = "-T";
315      argv[i++] = app_name;
316    }
317
318    argv[i++] = "-e";
319  }
320
321  if (!run_in_terminal || strchr(commandline, '$') != NULL)
322  {
323    if (!conf_get(LADISH_CONF_KEY_DAEMON_SHELL, argv + i))
324    {
325      argv[i] = LADISH_CONF_KEY_DAEMON_SHELL_DEFAULT;
326    }
327    i++;
328
329    argv[i++] = "-c";
330  }
331
332  argv[i++] = commandline;
333  argv[i++] = NULL;
334
335  log_info("Executing '%s' with PID %llu", commandline, (unsigned long long)getpid());
336
337  /* Execute it */
338  execvp(argv[0], (char **)argv);
339
340  log_error("Executing program '%s' failed: %s", argv[0], strerror(errno));
341
342  exit(1);
343}
344
345static
346void
347loader_read_child_output(
348  char * vgraph_name,
349  char * app_name,
350  int fd,
351  bool error,
352  char * buffer_ptr,
353  char ** buffer_ptr_ptr,
354  char * last_line,
355  unsigned int * last_line_repeat_count)
356{
357  ssize_t ret;
358  char *char_ptr;
359  char *eol_ptr;
360  size_t left;
361  size_t max_read;
362
363  do
364  {
365    max_read = CLIENT_OUTPUT_BUFFER_SIZE - 1 - (*buffer_ptr_ptr - buffer_ptr);
366    ret = read(fd, *buffer_ptr_ptr, max_read);
367    if (ret > 0)
368    {
369      (*buffer_ptr_ptr)[ret] = 0;
370      char_ptr = buffer_ptr;
371
372      while ((eol_ptr = strchr(char_ptr, '\n')) != NULL)
373      {
374        *eol_ptr = 0;
375
376        if (*last_line_repeat_count > 0 && strcmp(last_line, char_ptr) == 0)
377        {
378          if (*last_line_repeat_count == 1)
379          {
380            if (error)
381            {
382              log_error_plain("%s:%s: last stderr line repeating..", vgraph_name, app_name);
383            }
384            else
385            {
386              log_info("%s:%s: last stdout line repeating...", vgraph_name, app_name);
387            }
388          }
389
390          (*last_line_repeat_count)++;
391        }
392        else
393        {
394          loader_check_line_repeat_end(vgraph_name, app_name, error, *last_line_repeat_count);
395
396          strcpy(last_line, char_ptr);
397          *last_line_repeat_count = 1;
398
399          if (error)
400          {
401            log_error_plain("%s:%s: %s", vgraph_name, app_name, char_ptr);
402          }
403          else
404          {
405            log_info("%s:%s: %s", vgraph_name, app_name, char_ptr);
406          }
407        }
408
409        char_ptr = eol_ptr + 1;
410      }
411
412      left = ret - (char_ptr - *buffer_ptr_ptr);
413      if (left != 0)
414      {
415        /* last line does not end with newline */
416
417        if (left == CLIENT_OUTPUT_BUFFER_SIZE - 1)
418        {
419          /* line is too long to fit in buffer */
420          /* print it like it is, rest (or more) of it will be logged on next interation */
421
422          if (error)
423          {
424            log_error_plain("%s:%s: %s " ANSI_RESET ANSI_COLOR_RED "(truncated) " ANSI_RESET, vgraph_name, app_name, char_ptr);
425          }
426          else
427          {
428            log_info("%s:%s: %s " ANSI_RESET ANSI_COLOR_RED "(truncated) " ANSI_RESET, vgraph_name, app_name, char_ptr);
429          }
430
431          left = 0;
432        }
433        else
434        {
435          memmove(buffer_ptr, char_ptr, left);
436        }
437      }
438
439      *buffer_ptr_ptr = buffer_ptr + left;
440    }
441  }
442  while (ret == max_read);      /* if we have read everything as much as we can, then maybe there is more to read */
443}
444
445static void
446loader_read_childs_output(void)
447{
448  struct list_head * node_ptr;
449  struct loader_child * child_ptr;
450
451  list_for_each (node_ptr, &g_childs_list)
452  {
453    child_ptr = list_entry(node_ptr, struct loader_child, siblings);
454
455    if (!child_ptr->dead && !child_ptr->terminal)
456    {
457      loader_read_child_output(
458        child_ptr->vgraph_name,
459        child_ptr->app_name,
460        child_ptr->stdout,
461        false,
462        child_ptr->stdout_buffer,
463        &child_ptr->stdout_buffer_ptr,
464        child_ptr->stdout_last_line,
465        &child_ptr->stdout_last_line_repeat_count);
466
467      loader_read_child_output(
468        child_ptr->vgraph_name,
469        child_ptr->app_name,
470        child_ptr->stderr,
471        true,
472        child_ptr->stderr_buffer,
473        &child_ptr->stderr_buffer_ptr,
474        child_ptr->stderr_last_line,
475        &child_ptr->stderr_last_line_repeat_count);
476    }
477  }
478}
479
480void
481loader_run(void)
482{
483  loader_read_childs_output();
484  loader_childs_bury();
485}
486
487#define LD_PRELOAD_ADD "libalsapid.so libasound.so"
488
489static void set_ldpreload(void)
490{
491  const char * old;
492  char * new;
493
494  old = getenv("LD_PRELOAD");
495  if (old != NULL)
496  {
497    new = catdup3(LD_PRELOAD_ADD, " ", old);
498    if (new == NULL)
499    {
500      fprintf(stderr, "Memory allocation failure. Cannot hook libalsapid.so through LD_PRELOAD.\n");
501      return;
502    }
503  }
504  else
505  {
506    new = LD_PRELOAD_ADD;
507  }
508
509  printf("LD_PRELOAD set to \"%s\"\n", new);
510  setenv("LD_PRELOAD", new, 1);
511
512  if (old)
513  {
514    free(new);
515  }
516}
517
518bool
519loader_execute(
520  const char * vgraph_name,
521  const char * project_name,
522  const char * app_name,
523  const char * working_dir,
524  bool run_in_terminal,
525  const char * commandline,
526  pid_t * pid_ptr)
527{
528  pid_t pid;
529  struct loader_child * child_ptr;
530  int stderr_pipe[2];
531
532  child_ptr = malloc(sizeof(struct loader_child));
533  if (child_ptr == NULL)
534  {
535    log_error("malloc() failed to allocate struct loader_child");
536    goto fail;
537  }
538
539  child_ptr->vgraph_name = strdup(vgraph_name);
540  if (child_ptr->vgraph_name == NULL)
541  {
542    log_error("strdup() failed to duplicate vgraph name '%s'", vgraph_name);
543    goto free_struct;
544  }
545
546  if (project_name != NULL)
547  {
548    child_ptr->project_name = strdup(app_name);
549    if (child_ptr->project_name == NULL)
550    {
551      log_error("strdup() failed to duplicate project name '%s'", project_name);
552      goto free_vgraph_name;
553    }
554  }
555  else
556  {
557    child_ptr->project_name = NULL;
558  }
559
560  child_ptr->app_name = strdup(app_name);
561  if (child_ptr->app_name == NULL)
562  {
563    log_error("strdup() failed to duplicate app name '%s'", app_name);
564    goto free_project_name;
565  }
566
567  child_ptr->dead = false;
568  child_ptr->terminal = run_in_terminal;
569  child_ptr->stdout_buffer_ptr = child_ptr->stdout_buffer;
570  child_ptr->stderr_buffer_ptr = child_ptr->stderr_buffer;
571  child_ptr->stdout_last_line_repeat_count = 0;
572  child_ptr->stderr_last_line_repeat_count = 0;
573
574  if (!run_in_terminal)
575  {
576    if (pipe(stderr_pipe) == -1)
577    {
578      log_error("Failed to create stderr pipe");
579    }
580    else
581    {
582      child_ptr->stderr = stderr_pipe[0];
583
584      if (fcntl(child_ptr->stderr, F_SETFL, O_NONBLOCK) == -1)
585      {
586        log_error("Failed to set nonblocking mode on "
587                   "stderr reading end: %s",
588                   strerror(errno));
589        close(stderr_pipe[0]);
590        close(stderr_pipe[1]);
591      }
592    }
593  }
594
595  list_add_tail(&child_ptr->siblings, &g_childs_list);
596
597  if (!run_in_terminal)
598  {
599    /* We need pty to disable libc buffering of stdout */
600    pid = forkpty(&child_ptr->stdout, NULL, NULL, NULL);
601  }
602  else
603  {
604    pid = fork();
605  }
606
607  if (pid == -1)
608  {
609    log_error("Could not fork to exec program %s:%s: %s", vgraph_name, app_name, strerror(errno));
610    list_del(&child_ptr->siblings); /* fork failed so it is not really a child process to watch for. */
611    return false;
612  }
613
614  if (pid == 0)
615  {
616    /* Need to close all open file descriptors except the std ones */
617    struct rlimit max_fds;
618    rlim_t fd;
619
620    getrlimit(RLIMIT_NOFILE, &max_fds);
621
622    for (fd = 3; fd < max_fds.rlim_cur; ++fd)
623    {
624      close(fd);
625    }
626
627    if (!run_in_terminal)
628    {
629      /* In child, close unused reading end of pipe */
630      close(stderr_pipe[0]);
631
632      dup2(stderr_pipe[1], fileno(stderr));
633    }
634
635    set_ldpreload();
636
637    loader_exec_program(commandline, working_dir, run_in_terminal, vgraph_name, project_name, app_name);
638
639    return false;  /* We should never get here */
640  }
641
642  if (!run_in_terminal)
643  {
644    /* In parent, close unused writing ends of pipe */
645    close(stderr_pipe[1]);
646
647    if (fcntl(child_ptr->stdout, F_SETFL, O_NONBLOCK) == -1)
648    {
649      log_error("Could not set noblocking mode on stdout "
650                 "- pty: %s", strerror(errno));
651      close(stderr_pipe[0]);
652      close(child_ptr->stdout);
653    }
654  }
655
656  log_info("Forked to run program %s:%s pid = %llu", vgraph_name, app_name, (unsigned long long)pid);
657
658  *pid_ptr = child_ptr->pid = pid;
659
660  return true;
661
662free_project_name:
663  free(child_ptr->project_name);
664
665free_vgraph_name:
666  free(child_ptr->vgraph_name);
667
668free_struct:
669  free(child_ptr);
670
671fail:
672  return false;
673}
674
675unsigned int loader_get_app_count(void)
676{
677  struct list_head * node_ptr;
678  unsigned int count;
679
680  count = 0;
681
682  list_for_each(node_ptr, &g_childs_list)
683  {
684    count++;
685  }
686
687  return count;
688}
Note: See TracBrowser for help on using the browser.