The Assimilation Project  based on Assimilation version 1.1.7.1474836767
childprocess.c
Go to the documentation of this file.
1 
26 #include <projectcommon.h>
27 #include <string.h>
28 #include <glib.h>
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32 #include <gmainfd.h>
33 #include <logsourcefd.h>
34 #include <childprocess.h>
35 #include <misc.h>
36 
38 
45 FSTATIC void _childprocess_setup_child(gpointer childprocess_object);
46 FSTATIC gboolean _childprocess_timeout(gpointer childprocess_object);
47 FSTATIC void _childprocess_childexit(GPid pid, gint status, gpointer childprocess_object);
49 FSTATIC gchar* _childprocess_toString(gconstpointer);
50 
51 #define CHILDSTATE_RUNNING 0
52 
53 #ifdef WIN32
54 #ifndef WEXITSTATUS
55  /* The first four definitions below courtesy of ITAGAKI Takahiro
56  * itagaki(dot)takahiro(at)gmail(dot)com
57  * From the postgresql mailing list: Dec 26, 2006 - 18:31
58  * (FWIW: PostgreSQL is released under the PostgreSQL license
59  * (similar to the BSD/MIT licenses))
60  */
61 # define WEXITSTATUS(w) ((int) ((w) & 0x40000000))
62 # define WIFEXITED(w) (((w) & 0x40000000) == 0)
63 # define WIFSIGNALED(w) (((w) & 0x40000000) != 0)
64 # define WTERMSIG(w) ((w) & 0x3FFFFFFF)
65 # define WCOREDUMP(w) (FALSE)
66 #include <Windows.h>
67 #include <WinBase.h>
68 #endif
69 #else
70 #include <sys/wait.h>
71 #endif
72 
85 childprocess_new(gsize cpsize
86 , char** argv
87 , const char** envp
88 , ConfigContext* envmod
89 , const char* curdir
90 , void (*notify)(ChildProcess*, enum HowDied, int rc, int signal, gboolean core_dumped)
92 , gboolean save_stdout
93 , const char*logdomain
94 , const char*logprefix
95 , GLogLevelFlags loglevel
96 , guint32 timeout_seconds
97 , gpointer user_data
98 , enum ChildErrLogMode logmode
99 , const char* logname)
100 {
101  AssimObj* aself;
102  ChildProcess* self;
103  gint stdoutfd;
104  gint stderrfd;
105  GError* failcode = NULL;
106  gchar** childenv = NULL;
107 
109  g_return_val_if_fail(logprefix != NULL, NULL);
110  if (cpsize < sizeof(ChildProcess)) {
111  cpsize = sizeof(ChildProcess);
112  }
113  aself = assimobj_new(cpsize);
114  g_return_val_if_fail(aself != NULL, NULL);
115  self = NEWSUBCLASS(ChildProcess, aself);
116  childenv = assim_merge_environ(envp, envmod);
117  if (!g_spawn_async_with_pipes(
118  curdir, // Current directory
119  argv, // Arguments
120  childenv, // environment
121  G_SPAWN_DO_NOT_REAP_CHILD, // GSpawnFlags flags,
122  _childprocess_setup_child, // GSpawnChildSetupFunc child_setup,
123  self, // gpointer user_data,
124  &self->child_pid, // GPid *child_pid,
125  NULL, // gint *standard_input,
126  &stdoutfd, // gint *standard_output,
127  &stderrfd, // gint *standard_error,
128  &failcode)) { // GError **error
129 
130  // OOPS! Failed!
131  const char * msg = "unknown exec error";
132  if (failcode && failcode->message) {
133  msg = failcode->message;
134  }
135  g_critical("%s.%d: %s", __FUNCTION__, __LINE__, msg);
136  if (failcode) {
137  g_clear_error(&failcode); // sets failcode back to NULL
138  }
139  assim_free_environ(childenv); childenv = NULL;
140  UNREF(self);
141  aself = NULL;
142  return NULL;
143  }
144  DEBUGMSG2("%s.%d: Spawned process with user_data = %p", __FUNCTION__, __LINE__, self);
145 
148  self->stderr_src = logsourcefd_new(0, stderrfd, G_PRIORITY_HIGH, g_main_context_default()
149  , logdomain, loglevel, logprefix);
150  self->user_data = user_data;
151  self->logmode = logmode;
152  if (NULL == logname) {
153  logname = argv[0];
154  }
155  self->loggingname = g_strdup(logname);
156  assim_free_environ(childenv);
157  childenv = NULL;
158 
159  if (!save_stdout) {
160  LogSourceFd* logsrc;
161  logsrc = logsourcefd_new(0, stdoutfd, G_PRIORITY_HIGH
162  , g_main_context_default(), logdomain, loglevel, logprefix);
163  self->stdout_src = &logsrc->baseclass;
164  }else{
165  self->stdout_src = gmainfd_new(0, stdoutfd, G_PRIORITY_HIGH, g_main_context_default());
166  }
167  self->childsrc_id = g_child_watch_add(self->child_pid, _childprocess_childexit, self);
168 
169  self->notify = notify;
170 
171  if (0 == timeout_seconds) {
172  DEBUGMSG2("No timeout for process with user_data = %p", self);
173  self->timeoutsrc_id = 0;
174  }else{
175  self->timeoutsrc_id = g_timeout_add_seconds(timeout_seconds
176  , _childprocess_timeout, self);
177  DEBUGMSG3("%s.%d: Set %d second timeout %d for process with user_data = %p"
178  , __FUNCTION__, __LINE__, timeout_seconds, self->timeoutsrc_id, self);
179  }
180  self->child_state = CHILDSTATE_RUNNING;
181  DEBUGMSG5("%s.%d: REF child: %p", __FUNCTION__,__LINE__, self);
182  REF(self); // We do this because we need to still be here when the process exits
183  return self;
184 }
185 
187 FSTATIC void
189 {
190  ChildProcess* self = CASTTOCLASS(ChildProcess, aself);
191  if (self->stdout_src) {
192  g_source_destroy(&self->stdout_src->baseclass);
193  g_source_unref(&self->stdout_src->baseclass);
194  //self->stdout_src->finalize(self->stdout_src);
195  self->stdout_src = NULL;
196  }
197  if (self->stderr_src) {
198  g_source_destroy(&self->stderr_src->baseclass.baseclass);
199  g_source_unref(&self->stderr_src->baseclass.baseclass);
200  //self->stderr_src->baseclass.finalize(&self->stderr_src->baseclass);
201  self->stderr_src = NULL;
202  }
203  if (self->loggingname) {
204  g_free(self->loggingname);
205  self->loggingname = NULL;
206  }
207  if (self->timeoutsrc_id > 0) {
208  g_source_remove(self->timeoutsrc_id);
209  DEBUGMSG3("%s:%d: Removed timeout for process with user_data = %p"
210  , __FUNCTION__, __LINE__, self);
211  self->timeoutsrc_id = 0;
212  }
213  _assimobj_finalize(aself);
214  self = NULL;
215  aself = NULL;
216 }
217 
220 FSTATIC void
221 _childprocess_setup_child(gpointer childprocess_object)
222 {
223 #ifdef WIN32
224  (void)childprocess_object;
225 #else
226  ChildProcess* self = CASTTOCLASS(ChildProcess, childprocess_object);
227 # ifdef HAVE_SETPGID
228  setpgid(0,0);
229 # else
230  setpgrp(0, 0);
231 # endif
232  (void)self;
233 #endif
234 }
235 
237 static const struct {
238  int signal;
240 }signalmap[] = {
241  {SIGTERM, 5}, // Give them a chance to clean up
242 #ifndef WIN32
243  {SIGKILL, 10} // Give them the axe
244 #endif
245  // If it didn't die after this, then we give up
246  // - something is seriously hung...
247 };
248 
251 FSTATIC gboolean
252 _childprocess_timeout(gpointer childprocess_object)
253 {
254  ChildProcess* self;
255  DEBUGMSG("%s:%d Called from timeout for process with user_data = %p"
256  , __FUNCTION__, __LINE__, childprocess_object);
257  self = CASTTOCLASS(ChildProcess, childprocess_object);
258  if ((unsigned)(self->child_state) < DIMOF(signalmap)) {
259 #ifdef WIN32
260  TerminateProcess(self->child_pid, -1);
261 #else
262  (void)kill(self->child_pid, signalmap[self->child_state].signal);
263 #endif
264  self->timeoutsrc_id = g_timeout_add_seconds
265  ( signalmap[self->child_state].next_timeout
266  , _childprocess_timeout, self);
267  self->child_state += 1;
268  }else{
269  _childprocess_childexit(self->child_pid, 0xffffffff, self);
270  }
271  return FALSE;
272 }
273 
275 FSTATIC void
276 _childprocess_childexit(GPid pid, gint status, gpointer childprocess_object)
277 {
278  ChildProcess* self = CASTTOCLASS(ChildProcess, childprocess_object);
279  gboolean signalled = WIFSIGNALED(status);
280  int exitrc = 0;
281  int signal = 0;
282  gboolean logexit = FALSE;
283  enum HowDied howwedied = NOT_EXITED;
284  (void)pid;
285 
286  if (self->timeoutsrc_id > 0) {
287  g_source_remove(self->timeoutsrc_id);
288  DEBUGMSG3("%s.%d: Removed timeout %d for process with user_data = %p"
289  , __FUNCTION__, __LINE__, self->timeoutsrc_id, self);
290  self->timeoutsrc_id = 0;
291  }
292  // If it refused to die, then the status is invalid
293  if ((guint)(self->child_state) >= DIMOF(signalmap)) {
294  howwedied = EXITED_HUNG;
295  }else if ((guint)self->child_state != CHILDSTATE_RUNNING) {
296  // Then we tried to kill it...
297  howwedied = EXITED_TIMEOUT;
298  signal = signalled ? WTERMSIG(status) : 0;
299  }else{
300  if (signalled) {
301  signal = WTERMSIG(status);
302  howwedied = EXITED_SIGNAL;
303  }else{
304  exitrc = WEXITSTATUS(status);
305  howwedied = (exitrc == 0 ? EXITED_ZERO : EXITED_NONZERO);
306  }
307  }
308  switch (howwedied) {
309  case EXITED_SIGNAL: /*FALLTHROUGH*/
310  case EXITED_TIMEOUT: /*FALLTHROUGH*/
311  case EXITED_HUNG:
312  logexit = self->logmode > CHILD_NOLOG;
313  break;
314  case EXITED_NONZERO:
315  logexit = self->logmode >= CHILD_LOGERRS;
316  break;
317  case EXITED_ZERO:
318  logexit = self->logmode >= CHILD_LOGALL;
319  break;
320  default:
321  // We'll never produce any other values above
322  /*NOTREACHED*/
323  logexit = TRUE;
324  break;
325  }
326  if (logexit) {
327  switch (howwedied) {
328  case EXITED_SIGNAL:
329  g_warning("Child process [%s] died from signal %d%s."
330  , self->loggingname, signal, WCOREDUMP(status) ? " (core dumped)" : "");
331  break;
332  case EXITED_TIMEOUT:
333  if (signalled) {
334  g_warning("Child process [%s] timed out after %d seconds [signal %d%s]."
335  , self->loggingname, self->timeout, signal
336  , WCOREDUMP(status) ? " (core dumped)" : "");
337  }else{
338  g_warning("Child process [%s] timed out after %d seconds."
339  , self->loggingname, self->timeout);
340  }
341  break;
342  case EXITED_HUNG:
343  g_warning("Child process [%s] timed out after %d seconds and could not be killed."
344  , self->loggingname, self->timeout);
345  break;
346  case EXITED_NONZERO:
347  g_message("Child process [%s] exited with return code %d."
348  , self->loggingname, exitrc);
349  break;
350  case EXITED_ZERO:
351  g_message("Child process [%s] exited normally.", self->loggingname);
352  break;
353  default:/*NOTREACHED*/
354  break;
355  }
356  }
357 
358  DEBUGMSG2("%s.%d: Exit happened howwedied:%d", __FUNCTION__, __LINE__
359  , howwedied);
360  if (!self->stdout_src->atEOF) {
361  //DEBUGMSG3("Child %d [%s] EXITED but output is not at EOF [fd%d]", pid
362  //, self->loggingname, self->stdout_src->gfd.fd);
363  self->stdout_src->readmore(self->stdout_src);
364  }
365  if (!self->stderr_src->baseclass.atEOF) {
366  self->stderr_src->baseclass.readmore(&self->stderr_src->baseclass);
367  }
368  self->notify(self, howwedied, exitrc, signal, WCOREDUMP(status));
369  self->child_state = -1;
370  DEBUGMSG5("%s.%d: UNREF child: %p", __FUNCTION__,__LINE__, self);
371  UNREF(self); // Undo the REF(self) in our constructor
372 }
373 
374 FSTATIC char *
375 _childprocess_toString(gconstpointer aself)
376 {
377  const ChildProcess* self = CASTTOCONSTCLASS(ChildProcess, aself);
379  char* ret;
380 
381  cfg->setint(cfg, "child_pid", self->child_pid);
382  cfg->setint(cfg, "timeout", self->timeout);
383  cfg->setint(cfg, "timeoutsrc_id", self->timeoutsrc_id);
384  cfg->setint(cfg, "childsrc_id", self->childsrc_id);
385  cfg->setint(cfg, "child_state", self->child_state);
386  cfg->setstring(cfg, "loggingname", self->loggingname);
387  ret = cfg->baseclass.toString(&cfg->baseclass);
388  UNREF(cfg);
389  return ret;
390 }
int next_timeout
Definition: childprocess.c:239
FSTATIC void _childprocess_childexit(GPid pid, gint status, gpointer childprocess_object)
Function called when the child (finally) exits...
Definition: childprocess.c:276
void _assimobj_finalize(AssimObj *self)
Definition: assimobj.c:61
Log all exits - normal or abnormal.
Definition: childprocess.h:49
Defines miscellaneous interfaces.
Timed out and would not die.
Definition: childprocess.h:41
WINEXPORT ChildProcess * childprocess_new(gsize cpsize, char **argv, const char **envp, ConfigContext *envmod, const char *curdir, void(*notify)(ChildProcess *, enum HowDied, int rc, int signal, gboolean core_dumped), gboolean save_stdout, const char *logdomain, const char *logprefix, GLogLevelFlags loglevel, guint32 timeout_seconds, gpointer user_data, enum ChildErrLogMode logmode, const char *logname)
ChildProcess class. constructor.
Definition: childprocess.c:85
#define DEBUGMSG(...)
Definition: proj_classes.h:87
FSTATIC gchar * _childprocess_toString(gconstpointer)
Definition: childprocess.c:375
void(* setint)(ConfigContext *, const char *name, gint value)
Set integer value.
Definition: configcontext.h:75
#define DEBUGMSG5(...)
Definition: proj_classes.h:93
#define WINEXPORT
Definition: projectcommon.h:45
#define FSTATIC
Definition: projectcommon.h:31
#define DEBUGMSG3(...)
Definition: proj_classes.h:91
int signal
Definition: childprocess.c:238
LogSourceFd * logsourcefd_new(gsize cpsize, int fd, int priority, GMainContext *context, const char *logdomain, GLogLevelFlags loglevel, const char *prefix)
Construct a new LogSourceFd class. object and return it.
Definition: logsourcefd.c:39
#define BINDDEBUG(Cclass)
BINDDEBUG is for telling the class system where the debug variable for this class is - put it in the ...
Definition: proj_classes.h:82
AssimObj baseclass
Definition: configcontext.h:72
#define __FUNCTION__
Exited with a signal.
Definition: childprocess.h:39
Log signal, timeouts, or non-zero exits.
Definition: childprocess.h:48
FSTATIC gboolean _childprocess_timeout(gpointer childprocess_object)
Function to handle child timeouts.
Definition: childprocess.c:252
FSTATIC void _childprocess_setup_child(gpointer childprocess_object)
Function to perform setup for child between fork and exec (for UNIX-like systems) It doesn&#39;t get call...
Definition: childprocess.c:221
#define REF(obj)
Definition: assimobj.h:39
gchar *(* toString)(gconstpointer)
Produce malloc-ed string representation.
Definition: assimobj.h:58
#define CASTTOCONSTCLASS(Cclass, obj)
Safely cast &#39;obj&#39; to const C-class &#39;class&#39; - verifying that it was registered as being of type class ...
Definition: proj_classes.h:71
GMainFd baseclass
Our base class - NOT an AssimObj.
Definition: logsourcefd.h:35
Project common header file.
#define DIMOF(a)
Definition: lldp_min.c:30
FSTATIC void _childprocess_finalize(AssimObj *self)
Routine to free/destroy/finalize our ChildProcess objects.
Definition: childprocess.c:188
#define CHILDSTATE_RUNNING
Definition: childprocess.c:51
Don&#39;t log anything when it quits.
Definition: childprocess.h:46
Still running - should never be returned...
Definition: childprocess.h:36
AssimObj * assimobj_new(guint objsize)
Definition: assimobj.c:74
HowDied
Definition: childprocess.h:35
Exited with nonzero return code.
Definition: childprocess.h:38
WINEXPORT gchar ** assim_merge_environ(const gchar *const *env, ConfigContext *update)
Merge ConfigContext into possibly NULL current environment, returning a new environment.
Definition: misc.c:576
struct _ChildProcess ChildProcess
Definition: childprocess.h:33
void(* setstring)(ConfigContext *, const char *name, const char *value)
Definition: configcontext.h:87
int exitrc
Definition: cma.py:516
Timed out and was killed.
Definition: childprocess.h:40
Exited with zero return code.
Definition: childprocess.h:37
ChildErrLogMode
Definition: childprocess.h:45
#define DEBUGDECLARATIONS
Definition: proj_classes.h:79
#define DEBUGMSG2(...)
Definition: proj_classes.h:90
ConfigContext * configcontext_new(gsize objsize)
Construct a new ConfigContext object - with no values defaulted.
WINEXPORT void assim_free_environ(gchar **env)
Free the result of assim_merge_env.
Definition: misc.c:709
void(* _finalize)(AssimObj *)
Free object (private)
Definition: assimobj.h:55
Implements a gmainloop source for reading file descriptor pipes.
GMainFd * gmainfd_new(gsize cpsize, int fd, int priority, GMainContext *context)
Construct a new GMainFd object and return it.
Definition: gmainfd.c:64
Implements Child Process class.
#define CASTTOCLASS(Cclass, obj)
Safely cast &#39;obj&#39; to C-class &#39;class&#39; - verifying that it was registerd as being of type class ...
Definition: proj_classes.h:66
#define UNREF(obj)
Definition: assimobj.h:35
Implements a gmainloop source for reading file descriptor pipes.
#define NEWSUBCLASS(Cclass, obj)
Definition: proj_classes.h:67