text: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  /*
   * Thread that reads from stdout/stderr and converts them to log messages.
   * (Sort of a hack.)
   */
  #include "Dalvik.h"
  
  #include <stdlib.h>
  #include <unistd.h>
  #include <fcntl.h>
  #include <errno.h>
  
  #define kFilenoStdout   1
  #define kFilenoStderr   2
  
  #define kMaxLine    512
  
  /*
   * Hold some data.
   */
  struct BufferedData {
      char    buf[kMaxLine+1];
      int     count;
  };
  
  // fwd
  static void* stdioConverterThreadStart(void* arg);
  static bool readAndLog(int fd, BufferedData* data, const char* tag);
  
  
  /*
   * Crank up the stdout/stderr converter thread.
   *
   * Returns immediately.
   */
  bool dvmStdioConverterStartup()
  {
      gDvm.haltStdioConverter = false;
  
      dvmInitMutex(&gDvm.stdioConverterLock);
      pthread_cond_init(&gDvm.stdioConverterCond, NULL);
  
      if (pipe(gDvm.stdoutPipe) != 0) {
          ALOGW("pipe failed: %s", strerror(errno));
          return false;
      }
      if (pipe(gDvm.stderrPipe) != 0) {
          ALOGW("pipe failed: %s", strerror(errno));
          return false;
      }
  
      if (dup2(gDvm.stdoutPipe[1], kFilenoStdout) != kFilenoStdout) {
          ALOGW("dup2(1) failed: %s", strerror(errno));
          return false;
      }
      close(gDvm.stdoutPipe[1]);
      gDvm.stdoutPipe[1] = -1;
  #ifdef HAVE_ANDROID_OS
      /* don't redirect stderr on sim -- logs get written there! */
      /* (don't need this on the sim anyway) */
      if (dup2(gDvm.stderrPipe[1], kFilenoStderr) != kFilenoStderr) {
          ALOGW("dup2(2) failed: %d %s", errno, strerror(errno));
          return false;
      }
      close(gDvm.stderrPipe[1]);
      gDvm.stderrPipe[1] = -1;
  #endif
  
  
      /*
       * Create the thread.
       */
      dvmLockMutex(&gDvm.stdioConverterLock);
  
      if (!dvmCreateInternalThread(&gDvm.stdioConverterHandle,
                                   "Stdio Converter",
                                   stdioConverterThreadStart,
                                   NULL)) {
          return false;
      }
  
      while (!gDvm.stdioConverterReady) {
          dvmWaitCond(&gDvm.stdioConverterCond, &gDvm.stdioConverterLock);
      }
      dvmUnlockMutex(&gDvm.stdioConverterLock);
  
      return true;
  }
  
  /*
   * Shut down the stdio converter thread if it was started.
   *
   * Since we know the thread is just sitting around waiting for something
   * to arrive on stdout, print something.
   */
  void dvmStdioConverterShutdown()
  {
      gDvm.haltStdioConverter = true;
      if (gDvm.stdioConverterHandle == 0)    // not started, or still starting
          return;
  
      /* print something to wake it up */
      printf("Shutting down\n");
      fflush(stdout);
  
      ALOGD("Joining stdio converter...");
      pthread_join(gDvm.stdioConverterHandle, NULL);
  }
  
  /*
   * Select on stdout/stderr pipes, waiting for activity.
   *
   * DO NOT use printf from here.
   */
  static void* stdioConverterThreadStart(void* arg)
  {
      int cc;
  
      /* tell the main thread that we're ready */
      dvmLockMutex(&gDvm.stdioConverterLock);
      gDvm.stdioConverterReady = true;
      cc = pthread_cond_signal(&gDvm.stdioConverterCond);
      assert(cc == 0);
      dvmUnlockMutex(&gDvm.stdioConverterLock);
  
      /* we never do anything that affects the rest of the VM */
      dvmChangeStatus(NULL, THREAD_VMWAIT);
  
      /*
       * Allocate read buffers.
       */
      BufferedData* stdoutData = new BufferedData;
      BufferedData* stderrData = new BufferedData;
      stdoutData->count = stderrData->count = 0;
  
      /*
       * Read until shutdown time.
       */
      while (!gDvm.haltStdioConverter) {
          fd_set readfds;
          int maxFd, fdCount;
  
          FD_ZERO(&readfds);
          FD_SET(gDvm.stdoutPipe[0], &readfds);
          FD_SET(gDvm.stderrPipe[0], &readfds);
          maxFd = MAX(gDvm.stdoutPipe[0], gDvm.stderrPipe[0]);
  
          fdCount = select(maxFd+1, &readfds, NULL, NULL, NULL);
  
          if (fdCount < 0) {
              if (errno != EINTR) {
                  ALOGE("select on stdout/stderr failed");
                  break;
              }
              ALOGD("Got EINTR, ignoring");
          } else if (fdCount == 0) {
              ALOGD("WEIRD: select returned zero");
          } else {
              bool err = false;
              if (FD_ISSET(gDvm.stdoutPipe[0], &readfds)) {
                  err |= !readAndLog(gDvm.stdoutPipe[0], stdoutData,
                      "stdout");
              }
              if (FD_ISSET(gDvm.stderrPipe[0], &readfds)) {
                  err |= !readAndLog(gDvm.stderrPipe[0], stderrData,
                      "stderr");
              }
  
              /* probably EOF; give up */
              if (err) {
                  ALOGW("stdio converter got read error; shutting it down");
                  break;
              }
          }
      }
  
      close(gDvm.stdoutPipe[0]);
      close(gDvm.stderrPipe[0]);
  
      delete stdoutData;
      delete stderrData;
  
      /* change back for shutdown sequence */
      dvmChangeStatus(NULL, THREAD_RUNNING);
  #ifdef NDEBUG
      // cc is used only in assert() statements -> not used in NDEBUG
      // mode - causing variable defined but not used warning,
      // breaking the build with -Werror
      (void)cc;
  #endif
      return NULL;
  }
  
  /*
   * Data is pending on "fd".  Read as much as will fit in "data", then
   * write out any full lines and compact "data".
   */
  static bool readAndLog(int fd, BufferedData* data, const char* tag)
  {
      ssize_t actual;
      size_t want;
  
      assert(data->count < kMaxLine);
  
      want = kMaxLine - data->count;
      actual = read(fd, data->buf + data->count, want);
      if (actual <= 0) {
          ALOGW("read %s: (%d,%d) failed (%d): %s",
              tag, fd, want, (int)actual, strerror(errno));
          return false;
      } else {
          //ALOGI("read %s: %d at %d", tag, actual, data->count);
      }
      data->count += actual;
  
      /*
       * Got more data, look for an EOL.  We expect LF or CRLF, but will
       * try to handle a standalone CR.
       */
      char* cp = data->buf;
      const char* start = data->buf;
      int i = data->count;
      for (i = data->count; i > 0; i--, cp++) {
          if (*cp == '\n' || (*cp == '\r' && i != 0 && *(cp+1) != '\n')) {
              *cp = '\0';
              //ALOGW("GOT %d at %d '%s'", cp - start, start - data->buf, start);
              ALOG(LOG_INFO, tag, "%s", start);
              start = cp+1;
          }
      }
  
      /*
       * See if we overflowed.  If so, cut it off.
       */
      if (start == data->buf && data->count == kMaxLine) {
          data->buf[kMaxLine] = '\0';
          ALOG(LOG_INFO, tag, "%s!", start);
          start = cp + kMaxLine;
      }
  
      /*
       * Update "data" if we consumed some output.  If there's anything left
       * in the buffer, it's because we didn't see an EOL and need to keep
       * reading until we see one.
       */
      if (start != data->buf) {
          if (start >= data->buf + data->count) {
              /* consumed all available */
              data->count = 0;
          } else {
              /* some left over */
              int remaining = data->count - (start - data->buf);
              memmove(data->buf, start, remaining);
              data->count = remaining;
          }
      }
  
      return true;
  }
  