view agent/src/heapstats-engines/jvmSockCmd.cpp @ 253:1885f234b75b

Bug 3456: Build warnings w/ GCC 7.2.1 Reviewed-by: ykubota https://github.com/HeapStats/heapstats/pull/121
author Yasumasa Suenaga <yasuenag@gmail.com>
date Tue, 31 Oct 2017 16:21:48 +0900
parents 31f4aed852e6
children 43c4b8856335
line wrap: on
line source

/*!
 * \file jvmSockCmd.cpp
 * \brief This file is used by thread dump.
 * Copyright (C) 2011-2017 Nippon Telegraph and Telephone Corporation
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

#include <fcntl.h>
#include <signal.h>

#include "globals.hpp"
#include "util.hpp"
#include "jvmSockCmd.hpp"

/*!
 * \brief JVM socket command protocol version.
 */
const char JVM_CMD_VERSION[2] = "1";

/*!
 * \brief TJVMSockCmd constructor.
 * \param temporaryPath [in] Temporary path of JVM.
 */
TJVMSockCmd::TJVMSockCmd(char const* temporaryPath) {
  /* Initialize fields. */
  memset(socketPath, 0, sizeof(socketPath));
  memset(tempPath, 0, sizeof(tempPath));

  /* Copy. */
  if (likely(temporaryPath != NULL)) {
    strncpy(tempPath, temporaryPath, sizeof(tempPath) - 1);
  } else {
    /* Set default temporary path. */
    strcpy(tempPath, "/tmp");
  }

  /* Create socket file to JVM. */
  createJvmSock();
}

/*!
 * \brief TJVMSockCmd destructor.
 */
TJVMSockCmd::~TJVMSockCmd(void) { /* None. */ }

/*!
 * \brief Execute command without params, and save response.
 * \param cmd      [in] Execute command string.
 * \param filename [in] Output file path.
 * \return Response code of execute commad line.<br>
 *         Execute command is succeed, if value is 0.<br>
 *         Value is error code, if failure execute command.<br>
 *         Even so the file was written response data, if failure.
 */
int TJVMSockCmd::exec(char const* cmd, char const* filename) {
  /* Empty paramters. */
  const TJVMSockCmdArgs conf = {{0}, {0}, {0}};

  return execute(cmd, conf, filename);
}

/*!
 * \brief Execute command, and save response.
 * \param cmd      [in] Execute command string.
 * \param conf     [in] Execute command arguments.
 * \param filename [in] Output file path.
 * \return Response code of execute commad line.<br>
 *         Execute command is succeed, if value is 0.<br>
 *         Value is error code, if failure execute command.<br>
 *         Even so the file was written response data, if failure.
 */
int TJVMSockCmd::execute(char const* cmd, const TJVMSockCmdArgs conf,
                         char const* filename) {
  /* If don't open socket yet. */
  if (unlikely(!isConnectable())) {
    /* If failure open JVM socket. */
    if (unlikely(!createJvmSock())) {
      logger->printWarnMsg("Failure open socket.");
      return -1;
    }
  }

  /* Open socket. */
  int socketFD = openJvmSock(socketPath);
  /* Socket isn't open yet. */
  if (unlikely(socketFD < 0)) {
    logger->printWarnMsg("Socket isn't open yet.");
    return -1;
  }

  int returnCode = 0;
  /* Create response file. */
  int fd = open(filename, O_CREAT | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
  if (unlikely(fd < 0)) {
    returnCode = errno;
    logger->printWarnMsgWithErrno("Could not create threaddump file");
    close(socketFD);
    return returnCode;
  }

  /*
   * About JVM socket command
   * format:
   *    ~version~\0~command~\0~param1~\0~param2~\0~param3~\0
   * version:
   *    Always version is "1".
   * command:
   *    "threaddump"  Dump information of threads on JVM.
   *    "inspectheap" Seems to like "PrintClassHistogram".
   *    "datadump"    Seems to like when press CTRL and \ key.
   *    "heapdump"    Dump heap to file like "jhat".
   *    etc..
   * param1~3:
   *    paramter is fixed three param.
   *    If no need paramter , but should write '\0'.
   * return:
   *    '0'   Succeed in command and return result data.
   *    other Raised error and return error messages.
   */

  /* Result code variable. */
  char result = '\0';
  /* Wait socket response. */
  const int WAIT_LIMIT = 1000;
  int waitCount = 0;
  try {
    /* Write command protocol version. */
    if (unlikely(write(socketFD, JVM_CMD_VERSION, strlen(JVM_CMD_VERSION) + 1) <
                 0)) {
      returnCode = errno;
      logger->printWarnMsgWithErrno("Could not send threaddump command to JVM");
      throw 1;
    }

    /* Write command string. */
    if (unlikely(write(socketFD, cmd, strlen(cmd) + 1) < 0)) {
      returnCode = errno;
      logger->printWarnMsgWithErrno("Could not send threaddump command to JVM");
      throw 2;
    }

    /* Write three params */
    for (int i = 0; i < 3; i++) {
      if (unlikely(write(socketFD, conf[i], strlen(conf[i]) + 1) < 0)) {
        returnCode = errno;
        logger->printWarnMsgWithErrno(
            "Could not send threaddump command to JVM");
        throw 3;
      }
    }

    for (; waitCount <= WAIT_LIMIT; waitCount++) {
      /* Sleep for 1 milli-second. */
      littleSleep(0, 1000000);

      /* Check response. */
      if (read(socketFD, &result, sizeof(result)) > 0) {
        break;
      }
    }

    /* Read respone and Write to file. */
    char buff[255];
    ssize_t readByte = 0;
    while ((readByte = read(socketFD, buff, sizeof(buff))) > 0) {
      if (unlikely(write(fd, buff, readByte) < 0)) {
        returnCode = errno;
        logger->printWarnMsgWithErrno("Could not receive threaddump from JVM");
        throw 4;
      }
    }
  } catch (...) {
    ; /* Failed to write file. */
  }

  /* Cleanup. */
  close(socketFD);
  if (unlikely(close(fd) < 0 && returnCode == 0)) {
    returnCode = errno;
    logger->printWarnMsgWithErrno("Could not close socket to JVM");
  }

  /* Check command execute result. */
  if (unlikely(waitCount > WAIT_LIMIT)) {
    /* Maybe JVM is busy. */
    logger->printWarnMsg("AttachListener does not respond.");
    return -1;

  } else if (unlikely(result != '0')) {
    /* Illegal command or other error. */
    logger->printWarnMsg("Failure execute socket command.");
    return result;
  }

  /* Succeed or file error. */
  return returnCode;
}

/*!
 * \brief Create JVM socket file.
 * \return Process result.
 */
bool TJVMSockCmd::createJvmSock(void) {
  /* Socket file path. */
  char sockPath[PATH_MAX + 1] = {0};

  /* Search jvm socket file. */
  if (!findJvmSock((char*)&sockPath, PATH_MAX)) {
    /* Attach file path. */
    char attachPath[PATH_MAX + 1] = {0};

    /* No exists so create socket file. */
    if (unlikely(!createAttachFile((char*)&attachPath, PATH_MAX))) {
      /* Failure create attach-file or send signal. */
      logger->printWarnMsg("Failure create socket.");
      if (strlen(attachPath) != 0) {
        unlink(attachPath);
      }
      return false;

    } else {
      /* Wait for JVM socket file. */
      bool isFoundSocket = false;
      for (int waitCount = 0; waitCount < 1000; waitCount++) {
        /* Sleep for 1 milli-second. */
        littleSleep(0, 1000000);

        /* Recheck socket exists. */
        if (findJvmSock((char*)&sockPath, PATH_MAX)) {
          isFoundSocket = true;
          break;
        }
      }

      unlink(attachPath);
      if (unlikely(!isFoundSocket)) {
        /* Not found socket file. */
        logger->printWarnMsg("Failure find JVM socket.");
        return false;
      }
    }
  }

  /* Copy socket path. */
  strncpy(socketPath, sockPath, PATH_MAX);

  /* Succeed. */
  return true;
}

/*!
 * \brief Open socket to JVM.
 * \param path [in] Path of socket file.
 * \return Socket file descriptor.
 */
int TJVMSockCmd::openJvmSock(char* path) {
  /* Sanity check. */
  if (unlikely(path == NULL || strlen(path) == 0)) {
    return -1;
  }

  /* Socket structure. */
  struct sockaddr_un addr;
  memset(&addr, 0, sizeof(addr));
  addr.sun_family = AF_UNIX;

  /* Create socket. */
  int fd = socket(PF_UNIX, SOCK_STREAM, 0);
  if (unlikely(fd < 0)) {
    /* Failure create socket. */
    return -1;
  }

  /* Copy socket path. */
  strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);

  /* Connect socket. */
  if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
    /* Cleanup. */
    close(fd);
    return -1;
  }

  /* Return socket file descriptor. */
  return fd;
}

/*!
 * \brief Find JVM socket file and get path.
 * \param path    [out] Path of socket file.
 * \param pathLen [in]  Max length of param "path".
 * \return Search result.
 */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
bool TJVMSockCmd::findJvmSock(char* path, int pathLen) {
  /* Sanity check. */
  if (unlikely(path == NULL || pathLen <= 0)) {
    return false;
  }

  /*
   * Memo
   *  Socket file is different path by JDK version.
   *
   * JDK: Most OracleJDK, OpenJDK.
   * Path: "/tmp".
   *
   * JDK: OracleJDK u23/u24, Same OpenJDK.
   * Path: System.getProperity("java.io.tmpdir").
   *
   * JDK: Few OracleJDK, OpenJDK.
   * Path: CWD or "/tmp".
   */

  char sockPath[PATH_MAX] = {0};
  pid_t selfPid = getpid();

  /* Open socket at normally temporary directory. */
  snprintf(sockPath, PATH_MAX, "/tmp/.java_pid%d", selfPid);
  int fd = openJvmSock(sockPath);

  if (fd < 0) {
    /* Open socket at designated temporary directory. */
    snprintf(sockPath, PATH_MAX, "%s/.java_pid%d", tempPath, selfPid);
    fd = openJvmSock(sockPath);
  }

  if (fd < 0) {
    /* Open socket at CWD. */
    snprintf(sockPath, PATH_MAX, "/proc/%d/cwd/.java_pid%d", selfPid, selfPid);
    fd = openJvmSock(sockPath);
  }

  /* If no exists sock. */
  if (unlikely(fd < 0)) {
    return false;
  }

  /* Cleanup. */
  close(fd);

  /* Copy path. */
  strncpy(path, sockPath, pathLen);

  return true;
}
#pragma GCC diagnostic pop

/*!
 * \brief Create attach file.
 * \param path    [out] Path of created attach file.
 * \param pathLen [in]  Max length of param "path".
 * \return Process result.
 */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
bool TJVMSockCmd::createAttachFile(char* path, int pathLen) {
  /* Sanity check. */
  if (unlikely(path == NULL || pathLen <= 0)) {
    return false;
  }

  /*
   * Memo
   *  Attach file is also different path by JDK version.
   *  JVM dosen't recognize attach file,
   *  if make attach file under incorrect path.
   *
   * JDK: Most OracleJDK, OpenJDK
   * Path: "/tmp" and CWD.
   *
   * JDK: OracleJDK u23/u24, Same OpenJDK
   * Path: System.getProperity("java.io.tmpdir") and CWD.
   */

  char attachPath[PATH_MAX] = {0};
  pid_t selfPid = getpid();
  int fd = 0;

  /* Create attach-file at current directory. */
  snprintf(attachPath, PATH_MAX, "/proc/%d/cwd/.attach_pid%d", selfPid,
           selfPid);
  fd = open(attachPath, O_CREAT | O_WRONLY,
            S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

  if (unlikely(fd < 0)) {
    /* Create attach-file at designated temporary directory. */
    snprintf(attachPath, PATH_MAX, "%s/.attach_pid%d", tempPath, selfPid);
    fd = open(attachPath, O_CREAT | O_WRONLY,
              S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  }

  if (unlikely(fd < 0)) {
    /* Create attach-file at normally temporary directory. */
    snprintf(attachPath, PATH_MAX, "/tmp/.attach_pid%d", selfPid);
    fd = open(attachPath, O_CREAT | O_WRONLY,
              S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  }

  /* If failure create attach-file. */
  if (unlikely(fd < 0)) {
    logger->printWarnMsgWithErrno("Could not create attach file");
    return false;
  }

  /* Cleanup and copy attach file path. */
  close(fd);
  strncpy(path, attachPath, pathLen);

  /* Notification means "please create and listen socket" to JVM. */
  if (unlikely(kill(selfPid, SIGQUIT) != 0)) {
    logger->printWarnMsgWithErrno("Could not send SIGQUIT to JVM");
    return false;
  }

  return true;
}
#pragma GCC diagnostic pop