+++ /dev/null
-/*
- * SPDX-License-Identifier: LGPL-2.1-only
- *
- * Copyright (C) 2015-2016 EfficiOS Inc.
- * Copyright (C) 2015-2016 Alexandre Montplaisir <alexmonthy@efficios.com>
- * Copyright (C) 2013 David Goulet <dgoulet@efficios.com>
- */
-
-package org.lttng.ust.agent.client;
-
-import java.io.BufferedReader;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.lang.management.ManagementFactory;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.charset.Charset;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.lttng.ust.agent.utils.LttngUstAgentLogger;
-
-/**
- * Client for agents to connect to a local session daemon, using a TCP socket.
- *
- * @author David Goulet
- */
-public class LttngTcpSessiondClient implements Runnable {
-
- private static final String SESSION_HOST = "127.0.0.1";
- private static final String ROOT_PORT_FILE = "/var/run/lttng/agent.port";
- private static final String USER_PORT_FILE = "/.lttng/agent.port";
- private static final Charset PORT_FILE_ENCODING = Charset.forName("UTF-8");
-
- private static final int PROTOCOL_MAJOR_VERSION = 2;
- private static final int PROTOCOL_MINOR_VERSION = 0;
-
- /** Command header from the session deamon. */
- private final CountDownLatch registrationLatch = new CountDownLatch(1);
-
- private Socket sessiondSock;
- private volatile boolean quit = false;
-
- private DataInputStream inFromSessiond;
- private DataOutputStream outToSessiond;
-
- private final ILttngTcpClientListener logAgent;
- private final int domainValue;
- private final boolean isRoot;
-
- /**
- * Constructor
- *
- * @param logAgent
- * The listener this client will operate on, typically an LTTng
- * agent.
- * @param domainValue
- * The integer to send to the session daemon representing the
- * tracing domain to handle.
- * @param isRoot
- * True if this client should connect to the root session daemon,
- * false if it should connect to the user one.
- */
- public LttngTcpSessiondClient(ILttngTcpClientListener logAgent, int domainValue, boolean isRoot) {
- this.logAgent = logAgent;
- this.domainValue = domainValue;
- this.isRoot = isRoot;
- }
-
- /**
- * Wait until this client has successfully established a connection to its
- * target session daemon.
- *
- * @param seconds
- * A timeout in seconds after which this method will return
- * anyway.
- * @return True if the the client actually established the connection, false
- * if we returned because the timeout has elapsed or the thread was
- * interrupted.
- */
- public boolean waitForConnection(int seconds) {
- try {
- return registrationLatch.await(seconds, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- return false;
- }
- }
-
- @Override
- public void run() {
- for (;;) {
- if (this.quit) {
- break;
- }
-
- try {
-
- /*
- * Connect to the session daemon before anything else.
- */
- log("Connecting to sessiond");
- connectToSessiond();
-
- /*
- * Register to the session daemon as the Java component of the
- * UST application.
- */
- log("Registering to sessiond");
- registerToSessiond();
-
- /*
- * Block on socket receive and wait for command from the
- * session daemon. This will return if and only if there is a
- * fatal error or the socket closes.
- */
- log("Waiting on sessiond commands...");
- handleSessiondCmd();
- } catch (UnknownHostException uhe) {
- uhe.printStackTrace();
- /*
- * Terminate agent thread.
- */
- close();
- } catch (IOException ioe) {
- /*
- * I/O exception may have been triggered by a session daemon
- * closing the socket. Close our own socket and
- * retry connecting after a delay.
- */
- try {
- if (this.sessiondSock != null) {
- this.sessiondSock.close();
- }
- Thread.sleep(3000);
- } catch (InterruptedException e) {
- /*
- * Retry immediately if sleep is interrupted.
- */
- } catch (IOException closeioe) {
- closeioe.printStackTrace();
- /*
- * Terminate agent thread.
- */
- close();
- }
- }
- }
- }
-
- /**
- * Dispose this client and close any socket connection it may hold.
- */
- public void close() {
- log("Closing client");
- this.quit = true;
-
- try {
- if (this.sessiondSock != null) {
- this.sessiondSock.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- private void connectToSessiond() throws IOException {
- int rootPort = getPortFromFile(ROOT_PORT_FILE);
- int userPort = getPortFromFile(getHomePath() + USER_PORT_FILE);
-
- /*
- * Check for the edge case of both files existing but pointing to the
- * same port. In this case, let the root client handle it.
- */
- if ((rootPort != 0) && (rootPort == userPort) && (!isRoot)) {
- log("User and root config files both point to port " + rootPort +
- ". Letting the root client handle it.");
- throw new IOException();
- }
-
- int portToUse = (isRoot ? rootPort : userPort);
-
- if (portToUse == 0) {
- /* No session daemon available. Stop and retry later. */
- throw new IOException();
- }
-
- this.sessiondSock = new Socket(SESSION_HOST, portToUse);
- this.inFromSessiond = new DataInputStream(sessiondSock.getInputStream());
- this.outToSessiond = new DataOutputStream(sessiondSock.getOutputStream());
- }
-
- private static String getHomePath() {
- /*
- * The environment variable LTTNG_HOME overrides HOME if
- * defined.
- */
- String homePath = System.getenv("LTTNG_HOME");
-
- if (homePath == null) {
- homePath = System.getProperty("user.home");
- }
- return homePath;
- }
-
- /**
- * Read port number from file created by the session daemon.
- *
- * @return port value if found else 0.
- */
- private static int getPortFromFile(String path) throws IOException {
- BufferedReader br = null;
-
- try {
- br = new BufferedReader(new InputStreamReader(new FileInputStream(path), PORT_FILE_ENCODING));
- String line = br.readLine();
- if (line == null) {
- /* File exists but is empty. */
- return 0;
- }
-
- int port = Integer.parseInt(line, 10);
- if (port < 0 || port > 65535) {
- /* Invalid value. Ignore. */
- port = 0;
- }
- return port;
-
- } catch (NumberFormatException e) {
- /* File contained something that was not a number. */
- return 0;
- } catch (FileNotFoundException e) {
- /* No port available. */
- return 0;
- } finally {
- if (br != null) {
- br.close();
- }
- }
- }
-
- private void registerToSessiond() throws IOException {
- byte data[] = new byte[16];
- ByteBuffer buf = ByteBuffer.wrap(data);
- String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
-
- buf.putInt(domainValue);
- buf.putInt(Integer.parseInt(pid));
- buf.putInt(PROTOCOL_MAJOR_VERSION);
- buf.putInt(PROTOCOL_MINOR_VERSION);
- this.outToSessiond.write(data, 0, data.length);
- this.outToSessiond.flush();
- }
-
- /**
- * Handle session command from the session daemon.
- */
- private void handleSessiondCmd() throws IOException {
- /* Data read from the socket */
- byte inputData[] = null;
- /* Reply data written to the socket, sent to the sessiond */
- LttngAgentResponse response;
-
- while (true) {
- /* Get header from session daemon. */
- SessiondCommandHeader cmdHeader = recvHeader();
-
- if (cmdHeader.getDataSize() > 0) {
- inputData = recvPayload(cmdHeader);
- }
-
- switch (cmdHeader.getCommandType()) {
- case CMD_REG_DONE:
- {
- /*
- * Countdown the registration latch, meaning registration is
- * done and we can proceed to continue tracing.
- */
- registrationLatch.countDown();
- /*
- * We don't send any reply to the registration done command.
- * This just marks the end of the initial session setup.
- */
- log("Registration done");
- continue;
- }
- case CMD_LIST:
- {
- SessiondCommand listLoggerCmd = new SessiondListLoggersCommand();
- response = listLoggerCmd.execute(logAgent);
- log("Received list loggers command");
- break;
- }
- case CMD_EVENT_ENABLE:
- {
- if (inputData == null) {
- /* Invalid command */
- response = LttngAgentResponse.FAILURE_RESPONSE;
- break;
- }
- SessiondCommand enableEventCmd = new SessiondEnableEventCommand(inputData);
- response = enableEventCmd.execute(logAgent);
- log("Received enable event command: " + enableEventCmd.toString());
- break;
- }
- case CMD_EVENT_DISABLE:
- {
- if (inputData == null) {
- /* Invalid command */
- response = LttngAgentResponse.FAILURE_RESPONSE;
- break;
- }
- SessiondCommand disableEventCmd = new SessiondDisableEventCommand(inputData);
- response = disableEventCmd.execute(logAgent);
- log("Received disable event command: " + disableEventCmd.toString());
- break;
- }
- case CMD_APP_CTX_ENABLE:
- {
- if (inputData == null) {
- /* This commands expects a payload, invalid command */
- response = LttngAgentResponse.FAILURE_RESPONSE;
- break;
- }
- SessiondCommand enableAppCtxCmd = new SessiondEnableAppContextCommand(inputData);
- response = enableAppCtxCmd.execute(logAgent);
- log("Received enable app-context command");
- break;
- }
- case CMD_APP_CTX_DISABLE:
- {
- if (inputData == null) {
- /* This commands expects a payload, invalid command */
- response = LttngAgentResponse.FAILURE_RESPONSE;
- break;
- }
- SessiondCommand disableAppCtxCmd = new SessiondDisableAppContextCommand(inputData);
- response = disableAppCtxCmd.execute(logAgent);
- log("Received disable app-context command");
- break;
- }
- default:
- {
- /* Unknown command, send empty reply */
- response = null;
- log("Received unknown command, ignoring");
- break;
- }
- }
-
- /* Send response to the session daemon. */
- byte[] responseData;
- if (response == null) {
- responseData = new byte[4];
- ByteBuffer buf = ByteBuffer.wrap(responseData);
- buf.order(ByteOrder.BIG_ENDIAN);
- } else {
- log("Sending response: " + response.toString());
- responseData = response.getBytes();
- }
- this.outToSessiond.write(responseData, 0, responseData.length);
- this.outToSessiond.flush();
- }
- }
-
- /**
- * Receive header data from the session daemon using the LTTng command
- * static buffer of the right size.
- */
- private SessiondCommandHeader recvHeader() throws IOException {
- byte data[] = new byte[SessiondCommandHeader.HEADER_SIZE];
- int bytesLeft = data.length;
- int bytesOffset = 0;
-
- while (bytesLeft > 0) {
- int bytesRead = this.inFromSessiond.read(data, bytesOffset, bytesLeft);
-
- if (bytesRead < 0) {
- throw new IOException();
- }
- bytesLeft -= bytesRead;
- bytesOffset += bytesRead;
- }
- return new SessiondCommandHeader(data);
- }
-
- /**
- * Receive payload from the session daemon. This MUST be done after a
- * recvHeader() so the header value of a command are known.
- *
- * The caller SHOULD use isPayload() before which returns true if a payload
- * is expected after the header.
- */
- private byte[] recvPayload(SessiondCommandHeader headerCmd) throws IOException {
- byte payload[] = new byte[(int) headerCmd.getDataSize()];
- int bytesLeft = payload.length;
- int bytesOffset = 0;
-
- /* Failsafe check so we don't waste our time reading 0 bytes. */
- if (bytesLeft == 0) {
- return null;
- }
-
- while (bytesLeft > 0) {
- int bytesRead = inFromSessiond.read(payload, bytesOffset, bytesLeft);
-
- if (bytesRead < 0) {
- throw new IOException();
- }
- bytesLeft -= bytesRead;
- bytesOffset += bytesRead;
- }
- return payload;
- }
-
- /**
- * Wrapper for this class's logging, adds the connection's characteristics
- * to help differentiate between multiple TCP clients.
- */
- private void log(String message) {
- LttngUstAgentLogger.log(getClass(),
- "(root=" + isRoot + ", domain=" + domainValue + ") " + message);
- }
-}