aboutsummaryrefslogtreecommitdiff
path: root/libjava/java/lang/natPosixProcess.cc
blob: 252da6e80abd5b136dc29e1244f19f2b00de3472 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
// natPosixProcess.cc - Native side of POSIX process code.

/* Copyright (C) 1998, 1999, 2000, 2002, 2003, 2004, 2005, 2006, 2007
  Free Software Foundation

   This file is part of libgcj.

This software is copyrighted work licensed under the terms of the
Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
details.  */

#include <config.h>

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#include <gcj/cni.h>
#include <jvm.h>

#include <java/lang/PosixProcess$ProcessManager.h>
#include <java/lang/PosixProcess.h>
#include <java/lang/IllegalThreadStateException.h>
#include <java/lang/InternalError.h>
#include <java/lang/InterruptedException.h>
#include <java/lang/NullPointerException.h>
#include <java/lang/Thread.h>
#include <java/io/File.h>
#include <java/io/FileDescriptor.h>
#include <gnu/java/nio/channels/FileChannelImpl.h>
#include <java/io/FileInputStream.h>
#include <java/io/FileOutputStream.h>
#include <java/io/IOException.h>
#include <java/lang/OutOfMemoryError.h>
#include <java/lang/PosixProcess$EOFInputStream.h>

using gnu::java::nio::channels::FileChannelImpl;

extern char **environ;

static char *
new_string (jstring string)
{
  jsize s = _Jv_GetStringUTFLength (string);
  char *buf = (char *) _Jv_Malloc (s + 1);
  _Jv_GetStringUTFRegion (string, 0, string->length(), buf);
  buf[s] = '\0';
  return buf;
}

static void
cleanup (char **args, char **env, char *path)
{
  if (args != NULL)
    {
      for (int i = 0; args[i] != NULL; ++i)
	_Jv_Free (args[i]);
      _Jv_Free (args);
    }
  if (env != NULL)
    {
      for (int i = 0; env[i] != NULL; ++i)
	_Jv_Free (env[i]);
      _Jv_Free (env);
    }
  if (path != NULL)
    _Jv_Free (path);
}

// This makes our error handling a bit simpler and it lets us avoid
// thread bugs where we close a possibly-reopened file descriptor for
// a second time.
static void
myclose (int &fd)
{
  if (fd != -1)
    close (fd);
  fd = -1;
}

// There has to be a signal handler in order to be able to
// sigwait() on SIGCHLD.  The information passed is ignored as it
// will be recovered by the waitpid() call.
static void
sigchld_handler (int)
{
  // Ignore.
}


// Get ready to enter the main reaper thread loop.
void
java::lang::PosixProcess$ProcessManager::init ()
{
  using namespace java::lang;
  // Remenber our PID so other threads can kill us.
  reaperPID = (jlong) pthread_self ();

  // SIGCHLD is blocked in all threads in posix-threads.cc.
  // Setup the SIGCHLD handler.
  struct sigaction sa;
  memset (&sa, 0, sizeof (sa));

  sa.sa_handler = sigchld_handler;
  // We only want signals when the things exit.
  sa.sa_flags = SA_NOCLDSTOP;

  if (-1 == sigaction (SIGCHLD, &sa, NULL))
    goto error;

  // All OK.
  return;

error:
  throw new InternalError (JvNewStringUTF (strerror (errno)));
}

void
java::lang::PosixProcess$ProcessManager::waitForSignal ()
{
  // Wait for SIGCHLD
  sigset_t mask;
  pthread_sigmask (0, NULL, &mask);
  sigdelset (&mask, SIGCHLD);

  // Use sigsuspend() instead of sigwait() as sigwait() doesn't play
  // nicely with the GC's use of signals.
  sigsuspend (&mask);

  // Do not check sigsuspend return value.  The only legitimate return
  // is EINTR, but there is a known kernel bug affecting alpha-linux
  // wrt sigsuspend+handler+sigreturn that can result in a return value
  // of __NR_sigsuspend and errno unset.  Don't fail unnecessarily on
  // older kernel versions.

  // All OK.
  return;
}

jboolean java::lang::PosixProcess$ProcessManager::reap ()
{
  using namespace java::lang;

  pid_t pid;

  for (;;)
    {
      // Get the return code from a dead child process.
      int status;
      pid = waitpid ((pid_t) - 1, &status, WNOHANG);
      if (pid == -1)
	{
	  if (errno == ECHILD)
	    return false;
	  else
	    goto error;
	}

      if (pid == 0)
        return true;   // No children to wait for.

      // Look up the process in our pid map.
      PosixProcess * process = removeProcessFromMap ((jlong) pid);

      // Note that if process==NULL, then we have an unknown child.
      // This is not common, but can happen, and isn't an error.
      if (process)
	{
	  JvSynchronize sync (process);
	  process->status = WIFEXITED (status) ? WEXITSTATUS (status) : -1;
	  process->state = PosixProcess::STATE_TERMINATED;
          process->processTerminationCleanup();
	  process->notifyAll ();
	}
    }

error:
  throw new InternalError (JvNewStringUTF (strerror (errno)));
}

void
java::lang::PosixProcess$ProcessManager::signalReaper ()
{
  int c = pthread_kill ((pthread_t) reaperPID, SIGCHLD);
  if (c == 0)
    return;
  // pthread_kill() failed.
  throw new InternalError (JvNewStringUTF (strerror (c)));
}

void
java::lang::PosixProcess::nativeDestroy ()
{
  int c = kill ((pid_t) pid, SIGKILL);
  if (c == 0)
    return;
  // kill() failed.
  throw new InternalError (JvNewStringUTF (strerror (errno)));
}

void
java::lang::PosixProcess::nativeSpawn ()
{
  using namespace java::io;

  // Initialize all locals here to make cleanup simpler.
  char **args = NULL;
  char **env = NULL;
  char *path = NULL;
  int inp[2], outp[2], errp[2], msgp[2];
  inp[0] = -1;
  inp[1] = -1;
  outp[0] = -1;
  outp[1] = -1;
  errp[0] = -1;
  errp[1] = -1;
  msgp[0] = -1;
  msgp[1] = -1;
  errorStream = NULL;
  inputStream = NULL;
  outputStream = NULL;

  try
    {
      // Transform arrays to native form.
      args = (char **) _Jv_Malloc ((progarray->length + 1) * sizeof (char *));

      // Initialize so we can gracefully recover.
      jstring *elts = elements (progarray);
      for (int i = 0; i <= progarray->length; ++i)
	args[i] = NULL;

      for (int i = 0; i < progarray->length; ++i)
	args[i] = new_string (elts[i]);
      args[progarray->length] = NULL;

      if (envp)
	{
          bool need_path = true;
          bool need_ld_library_path = true;
          int i;

          // Preserve PATH and LD_LIBRARY_PATH unless specified
          // explicitly.  We need three extra slots.  Potentially PATH
          // and LD_LIBRARY_PATH will be added plus the NULL
          // termination.
	  env = (char **) _Jv_Malloc ((envp->length + 3) * sizeof (char *));
	  elts = elements (envp);

	  // Initialize so we can gracefully recover.
	  for (i = 0; i < envp->length + 3; ++i)
	    env[i] = NULL;

	  for (i = 0; i < envp->length; ++i)
            {
              env[i] = new_string (elts[i]);
              if (!strncmp (env[i], "PATH=", sizeof("PATH=")))
                need_path = false;
              if (!strncmp (env[i], "LD_LIBRARY_PATH=",
                            sizeof("LD_LIBRARY_PATH=")))
                need_ld_library_path = false;
            }

          if (need_path)
            {
	      char *path_val = getenv ("PATH");
              if (path_val)
                {
                  env[i] = (char *) _Jv_Malloc (strlen (path_val) +
                                                sizeof("PATH=") + 1);
                  strcpy (env[i], "PATH=");
                  strcat (env[i], path_val);
                  i++;
                }
            }
          if (need_ld_library_path)
            {
	      char *path_val = getenv ("LD_LIBRARY_PATH");
              if (path_val)
                {
                  env[i] =
                    (char *) _Jv_Malloc (strlen (path_val) +
                                         sizeof("LD_LIBRARY_PATH=") + 1);
                  strcpy (env[i], "LD_LIBRARY_PATH=");
                  strcat (env[i], path_val);
                  i++;
                }
            }
	  env[i] = NULL;
	}

      // We allocate this here because we can't call malloc() after
      // the fork.
      if (dir != NULL)
	path = new_string (dir->getPath ());

      // Create pipes for I/O.  MSGP is for communicating exec()
      // status.  If redirecting stderr to stdout, we don't need to
      // create the ERRP pipe.
      if (pipe (inp) || pipe (outp) || pipe (msgp)
	  || fcntl (msgp[1], F_SETFD, FD_CLOEXEC))
	throw new IOException (JvNewStringUTF (strerror (errno)));
      if (! redirect && pipe (errp))
	throw new IOException (JvNewStringUTF (strerror (errno)));

      // We create the streams before forking.  Otherwise if we had an
      // error while creating the streams we would have run the child
      // with no way to communicate with it.
      if (redirect)
	errorStream = PosixProcess$EOFInputStream::instance;
      else
	errorStream =
	  new FileInputStream (new
			       FileChannelImpl (errp[0],
						FileChannelImpl::READ));
      inputStream =
	new FileInputStream (new
			     FileChannelImpl (inp[0], FileChannelImpl::READ));
      outputStream =
	new FileOutputStream (new FileChannelImpl (outp[1],
						   FileChannelImpl::WRITE));

      // We don't use vfork() because that would cause the local
      // environment to be set by the child.

    // Use temporary for fork result to avoid dirtying an extra page.
    pid_t pid_tmp;
    if ((pid_tmp = fork ()) == -1)
      throw new IOException (JvNewStringUTF (strerror (errno)));

    if (pid_tmp == 0)
	{
	  // Child process, so remap descriptors, chdir and exec.
	  if (envp)
            environ = env;

	  // We ignore errors from dup2 because they should never occur.
	  dup2 (outp[0], 0);
	  dup2 (inp[1], 1);
	  dup2 (redirect ? inp[1] : errp[1], 2);

	  // Use close and not myclose -- we're in the child, and we
	  // aren't worried about the possible race condition.
	  close (inp[0]);
	  close (inp[1]);
	  if (! redirect)
	    {
	      close (errp[0]);
	      close (errp[1]);
	    }
	  close (outp[0]);
	  close (outp[1]);
	  close (msgp[0]);

	  // Change directory.
	  if (path != NULL)
	    {
	      if (chdir (path) != 0)
		{
		  char c = errno;
		  write (msgp[1], &c, 1);
		  _exit (127);
		}
	    }
          // Make sure all file descriptors are closed.  In
          // multi-threaded programs, there is a race between when a
          // descriptor is obtained, when we can set FD_CLOEXEC, and
          // fork().  If the fork occurs before FD_CLOEXEC is set, the
          // descriptor would leak to the execed process if we did not
          // manually close it.  So that is what we do.  Since we
          // close all the descriptors, it is redundant to set
          // FD_CLOEXEC on them elsewhere.
          int max_fd;
#ifdef HAVE_GETRLIMIT
          rlimit rl;
          int rv = getrlimit(RLIMIT_NOFILE, &rl);
          if (rv == 0)
            max_fd = rl.rlim_max - 1;
          else
            max_fd = 1024 - 1;
#else
          max_fd = 1024 - 1;
#endif
          while(max_fd > 2)
            {
              if (max_fd != msgp[1])
                close (max_fd);
              max_fd--;
            }
	  // Make sure that SIGCHLD is unblocked for the new process.
	  sigset_t mask;
	  sigemptyset (&mask);
	  sigaddset (&mask, SIGCHLD);
	  sigprocmask (SIG_UNBLOCK, &mask, NULL);

	  execvp (args[0], args);

	  // Send the parent notification that the exec failed.
	  char c = errno;
	  write (msgp[1], &c, 1);
	  _exit (127);
	}

      // Parent.  Close extra file descriptors and mark ours as
      // close-on-exec.
      pid = (jlong) pid_tmp;

      myclose (outp[0]);
      myclose (inp[1]);
      if (! redirect)
	myclose (errp[1]);
      myclose (msgp[1]);

      char c;
      int r = read (msgp[0], &c, 1);
      if (r == -1)
      throw new IOException (JvNewStringUTF (strerror (errno)));
      else if (r != 0)
      throw new IOException (JvNewStringUTF (strerror (c)));
    }
  catch (java::lang::Throwable *thrown)
    {
      // Do some cleanup we only do on failure.  If a stream object
      // has been created, we must close the stream itself (to avoid
      // duplicate closes when the stream object is collected).
      // Otherwise we simply close the underlying file descriptor.
      // We ignore errors here as they are uninteresting.

      try
	{
	  if (inputStream != NULL)
	    inputStream->close ();
	  else
	    myclose (inp[0]);
	}
      catch (java::lang::Throwable *ignore)
	{
	}

      try
	{
	  if (outputStream != NULL)
	    outputStream->close ();
	  else
	    myclose (outp[1]);
	}
      catch (java::lang::Throwable *ignore)
	{
	}

      try
	{
	  if (errorStream != NULL)
	    errorStream->close ();
	  else if (! redirect)
	    myclose (errp[0]);
	}
      catch (java::lang::Throwable *ignore)
	{
	}

      // These are potentially duplicate, but it doesn't matter due to
      // the use of myclose.
      myclose (outp[0]);
      myclose (inp[1]);
      if (! redirect)
	myclose (errp[1]);
      myclose (msgp[1]);

      exception = thrown;
    }

  myclose (msgp[0]);
  cleanup (args, env, path);
}