Implemented functionality to stop server command on Unix servers. Fixes #1140
authorEriks Jenkevics <eriks@netxms.org>
Wed, 11 Jan 2017 16:10:08 +0000 (18:10 +0200)
committerEriks Jenkevics <eriks@netxms.org>
Tue, 24 Jan 2017 11:04:51 +0000 (13:04 +0200)
40 files changed:
include/nms_agent.h
include/nms_cscp.h
src/agent/libnxagent/Makefile.am
src/agent/libnxagent/command_exec.cpp [new file with mode: 0644]
src/java/client/netxms-base/src/main/java/org/netxms/base/NXCPCodes.java
src/java/client/netxms-client/src/main/java/org/netxms/client/NXCSession.java
src/java/client/netxms-client/src/main/java/org/netxms/client/TextOutputListener.java
src/java/client/netxms-client/src/test/java/org/netxms/client/AgentTest.java
src/java/netxms-eclipse/NXSL/src/org/netxms/ui/eclipse/nxsl/views/ScriptExecutor.java
src/java/netxms-eclipse/ObjectManager/src/org/netxms/ui/eclipse/objectmanager/views/NodePollerView.java
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages.properties
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_ar.properties
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_de.properties
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_es.properties
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_fr.properties
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_pt.properties
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_ru.properties
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_zh_CN.properties
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/propertypages/Filter.java
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/views/AgentActionResults.java
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/views/ServerCommandResults.java
src/java/netxms-eclipse/ObjectTools/src/org/netxms/ui/eclipse/objecttools/views/ServerScriptResults.java
src/java/netxms-eclipse/ServerConfig/src/org/netxms/ui/eclipse/serverconfig/widgets/helpers/LogParserFile.java
src/libnetxms/log.cpp
src/server/core/objtools.cpp
src/server/core/session.cpp
src/server/include/nms_core.h
webui/webapp/NXSL/src/org/netxms/ui/eclipse/nxsl/views/ScriptExecutor.java
webui/webapp/ObjectManager/src/org/netxms/ui/eclipse/objectmanager/views/NodePollerView.java
webui/webapp/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages.properties
webui/webapp/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_ar.properties
webui/webapp/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_de.properties
webui/webapp/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_es.properties
webui/webapp/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_fr.properties
webui/webapp/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_pt.properties
webui/webapp/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_ru.properties
webui/webapp/ObjectTools/src/org/netxms/ui/eclipse/objecttools/messages_zh_CN.properties
webui/webapp/ObjectTools/src/org/netxms/ui/eclipse/objecttools/views/AgentActionResults.java
webui/webapp/ObjectTools/src/org/netxms/ui/eclipse/objecttools/views/ServerCommandResults.java
webui/webapp/ObjectTools/src/org/netxms/ui/eclipse/objecttools/views/ServerScriptResults.java

index d92335b..b1d6268 100644 (file)
@@ -514,6 +514,41 @@ public:
    virtual void debugPrintf(int level, const TCHAR *format, ...) = 0;
 };
 
+/**
+ * Execute server command
+ */
+class CommandExec
+{
+private:
+   int m_pipe[2];
+   pid_t m_pid;
+   UINT32 m_streamId;
+   THREAD m_outputThread;
+
+protected:
+   TCHAR *m_cmd;
+   bool m_sendOutput;
+
+   int getOutputPipe() { return m_pipe[0]; }
+
+   static THREAD_RESULT THREAD_CALL readOutput(void *pArg);
+
+   virtual void onOutput(const char *text);
+   virtual void endOfOutput();
+
+public:
+   CommandExec(const TCHAR *cmd);
+   CommandExec();
+   virtual ~CommandExec();
+
+   UINT32 getStreamId() const { return m_streamId; }
+   const TCHAR *getCommand() const { return m_cmd; }
+
+   bool execute();
+   void stop();
+
+};
+
 /**
  * Subagent's parameter information
  */
index 10edc6e..0c614d1 100644 (file)
@@ -323,7 +323,7 @@ typedef struct
 #define CMD_EXECUTE_LIBRARY_SCRIPT     0x0055
 #define CMD_GET_PREDICTION_ENGINES     0x0056
 #define CMD_GET_PREDICTED_DATA         0x0057
-//UNUSED: #define CMD_MODIFY_CONTAINER_CAT       0x0058
+#define CMD_STOP_SERVER_CMD            0x0058
 #define CMD_POLL_NODE                  0x0059
 #define CMD_POLLING_INFO               0x005A
 #define CMD_COPY_DCI                   0x005B
@@ -1160,6 +1160,7 @@ typedef struct
 #define VID_PORT_NUMBERING_SCHEME   ((UINT32)565)
 #define VID_NUM_VALUES              ((UINT32)566)
 #define VID_NUM_PSTORAGE            ((UINT32)567)
+#define VID_COMMAND_ID              ((UINT32)568)
 
 // Base variabe for single threshold in message
 #define VID_THRESHOLD_BASE          ((UINT32)0x00800000)
index 294d5a9..fac9171 100644 (file)
@@ -1,4 +1,4 @@
-SOURCES = bridge.cpp main.cpp registry.cpp tools.cpp
+SOURCES = bridge.cpp main.cpp registry.cpp tools.cpp command_exec.cpp
 
 lib_LTLIBRARIES = libnxagent.la
 
diff --git a/src/agent/libnxagent/command_exec.cpp b/src/agent/libnxagent/command_exec.cpp
new file mode 100644 (file)
index 0000000..769416a
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+** NetXMS - Network Management System
+** Copyright (C) 2003-2017 Raden Solutions
+**
+** 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+**
+** File: command_exec.cpp
+**
+**/
+
+#include "libnxagent.h"
+#include <signal.h>
+
+/**
+ * Next free stream ID
+ */
+static VolatileCounter s_nextStreamId = 0;
+
+/**
+ * Create new server command execution object from command
+ */
+CommandExec::CommandExec(const TCHAR *cmd)
+{
+   m_pid = 0;
+   m_cmd = _tcsdup_ex(cmd);
+   m_streamId = InterlockedIncrement(&s_nextStreamId);
+   m_sendOutput = false;
+   m_outputThread = INVALID_THREAD_HANDLE;
+}
+
+/**
+ * Create new server execution object
+ */
+CommandExec::CommandExec()
+{
+   m_pid = 0;
+   m_cmd = NULL;
+   m_streamId = InterlockedIncrement(&s_nextStreamId);
+   m_sendOutput = false;
+   m_outputThread = INVALID_THREAD_HANDLE;
+}
+
+/**
+ * Destructor
+ */
+CommandExec::~CommandExec()
+{
+   ThreadJoin(m_outputThread);
+   free(m_cmd);
+   stop();
+}
+
+/**
+ * Execute command
+ */
+bool CommandExec::execute()
+{
+   if (pipe(m_pipe) == -1)
+   {
+      nxlog_debug(4, _T("CommandExec::execute(): pipe() call failed (%s)"), _tcserror(errno));
+      return false;
+   }
+
+   m_pid = fork();
+   switch(m_pid)
+   {
+      case -1: // error
+         nxlog_debug(4, _T("CommandExec::execute(): fork() call failed (%s)"), _tcserror(errno));
+         close(m_pipe[0]);
+         close(m_pipe[1]);
+         return false;
+      case 0: // child
+         close(m_pipe[0]);
+         close(1);
+         close(2);
+         dup2(m_pipe[1], 1);
+         dup2(m_pipe[1], 2);
+         close(m_pipe[1]);
+#ifdef UNICODE
+         execl("/bin/sh", "/bin/sh", "-c", UTF8StringFromWideString(m_cmd), NULL);
+#else
+         execl("/bin/sh", "/bin/sh", "-c", m_cmd, NULL);
+#endif
+         exit(127);
+         break;
+      default: // parent
+         close(m_pipe[1]);
+         if (m_sendOutput)
+         {
+            m_outputThread = ThreadCreateEx(readOutput, 0, this);
+         }
+         else
+         {
+            close(m_pipe[0]);
+         }
+         return true;
+   }
+
+   return false;
+}
+
+/**
+ * Start read output thread
+ */
+THREAD_RESULT THREAD_CALL CommandExec::readOutput(void *pArg)
+{
+   int pipe = ((CommandExec *)pArg)->getOutputPipe();
+   fcntl(pipe, F_SETFD, fcntl(pipe, F_GETFD) | O_NONBLOCK);
+
+   char buffer[4096];
+   SocketPoller sp;
+   while(true)
+   {
+      sp.reset();
+      sp.add(pipe);
+      int rc = sp.poll(10000);
+      if (rc > 0)
+      {
+         rc = read(pipe, buffer, 4096);
+         if (rc > 0)
+         {
+            ((CommandExec *)pArg)->onOutput(buffer);
+         }
+         else
+         {
+            if ((rc == -1) && ((errno == EAGAIN) || (errno == EINTR)))
+            {
+               ((CommandExec *)pArg)->onOutput("");
+               continue;
+            }
+            nxlog_debug(6, _T("CommandExec::readOutput(): stopped on read (rc=%d err=%s)"), rc, _tcserror(errno));
+            break;
+         }
+      }
+      else if (rc == 0)
+      {
+         // Send empty output on timeout
+         ((CommandExec *)pArg)->onOutput("");
+      }
+      else
+      {
+         nxlog_debug(6, _T("CommandExec::readOutput(): stopped on poll (%s)"), _tcserror(errno));
+         break;
+      }
+   }
+   ((CommandExec *)pArg)->endOfOutput();
+
+   close(pipe);
+   return THREAD_OK;
+}
+
+/**
+ * kill command
+ */
+void CommandExec::stop()
+{
+   kill(m_pid, SIGKILL);
+}
+
+/**
+ * Perform action when output is generated
+ */
+void CommandExec::onOutput(const char *text)
+{
+}
+
+/**
+ * Perform action after output is generated
+ */
+void CommandExec::endOfOutput()
+{
+}
index 02a81fd..5d157b5 100644 (file)
@@ -111,7 +111,7 @@ public class NXCPCodes
        public static final int CMD_EXECUTE_LIBRARY_SCRIPT = 0x0055;
        public static final int CMD_GET_PREDICTION_ENGINES = 0x0056;
        public static final int CMD_GET_PREDICTED_DATA = 0x0057;
-       // UNUSED: public static final int CMD_MODIFY_CONTAINER_CAT = 0x0058;
+       public static final int CMD_STOP_SERVER_CMD = 0x0058;
        public static final int CMD_POLL_NODE = 0x0059;
        public static final int CMD_POLLING_INFO = 0x005A;
        public static final int CMD_COPY_DCI = 0x005B;
@@ -948,7 +948,8 @@ public class NXCPCodes
    public static final long VID_PORT_NUMBERING_SCHEME = 565;
    public static final long VID_NUM_VALUES = 566;
        public static final long VID_NUM_PSTORAGE = 567;
-
+   public static final long VID_COMMAND_ID = 568;
+   
        public static final long VID_ACL_USER_BASE = 0x00001000L;
        public static final long VID_ACL_USER_LAST = 0x00001FFFL;
        public static final long VID_ACL_RIGHTS_BASE = 0x00002000L;
index 7565a5f..b73ac55 100644 (file)
@@ -6821,7 +6821,9 @@ public class NXCSession
                }
             }
             if (m.isEndOfSequence())
+            {
                setComplete();
+            }
             return true;
          }
       } : null;
@@ -6832,10 +6834,13 @@ public class NXCSession
       }
       
       sendMessage(msg);
-      waitForRCC(msg.getMessageId());
+      NXCPMessage response = waitForRCC(msg.getMessageId());
 
       if (receiveOutput)
       {
+         if (listener != null)
+            listener.setStreamId(response.getFieldAsInt64(NXCPCodes.VID_COMMAND_ID));
+         
          synchronized(handler)
          {
             try
@@ -6848,7 +6853,23 @@ public class NXCSession
          }
          if (handler.isTimeout())
             throw new NXCException(RCC.TIMEOUT);
-      }      
+      }
+   }
+   
+   /**
+    * Stop server command
+    * 
+    * @param commandId
+    * @throws NXCException 
+    * @throws IOException 
+    */
+   public void stopServerCommand(long commandId) throws IOException, NXCException
+   {
+      final NXCPMessage msg = newMessage(NXCPCodes.CMD_STOP_SERVER_CMD);
+      msg.setFieldInt64(NXCPCodes.VID_COMMAND_ID, commandId);
+      sendMessage(msg);
+      
+      waitForRCC(msg.getMessageId());
    }
 
    /**
index e320f3a..b867215 100644 (file)
@@ -29,4 +29,11 @@ public interface TextOutputListener
     * @param text received message
     */
    public void messageReceived(String text);
+   
+   /**
+    * Called when output stream ID is known
+    * 
+    * @param streamId ID of output stream (actual meaning depends on API call)
+    */
+   public void setStreamId(long streamId);
 }
index 06bea06..b9cbc0b 100644 (file)
@@ -33,6 +33,11 @@ public class AgentTest extends AbstractSessionTest
          {
             System.out.print(text);
          }
+
+         @Override
+         public void setStreamId(long streamId)
+         {
+         }
       }, null);
 
       session.disconnect();
index 140c5f7..ba3c37c 100644 (file)
@@ -709,4 +709,9 @@ public class ScriptExecutor extends ViewPart implements ISaveablePart2, TextOutp
          }
       }
    }
+
+   @Override
+   public void setStreamId(long streamId)
+   {
+   }
 }
index 4cb5bd6..e35d107 100644 (file)
@@ -324,6 +324,11 @@ public class NodePollerView extends ViewPart
                }
             });
          }
+
+         @Override
+         public void setStreamId(long streamId)
+         {
+         }
       };
 
       Job job = new Job(String.format(Messages.get().NodePollerView_JobName, node.getObjectName(), node.getObjectId())) {
index fe48e6a..77c0a23 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index aae4021..bfbf6ca 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index 8492737..0f20bac 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index ec72631..f9e6b20 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index 8492737..0f20bac 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index 35ddb7b..681599e 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Reiniciar
 LocalCommandResults_ScrollLock=&Travar rolagem
 LocalCommandResults_SelectAll=Selecionar &tudo
 LocalCommandResults_Terminate=&Terminar
-LocalCommandResults_Terminated=\n\n*** TERMINADO ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINADO ***\n\n\n
 ObjectToolExecutor_ErrorText=A senha inserida no campo de entrada "%s" n\u00e3o \u00e9 v\u00e1lida
 ObjectToolExecutor_ErrorTitle=Falha na valida\u00e7\u00e3o da senha
 ObjectToolExecutor_JobName=Validar senhas
index 3ce37a2..2c78d43 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&\u041f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0
 LocalCommandResults_ScrollLock=\u041f&\u0440\u043e\u043a\u0440\u0443\u0442\u043a\u0430
 LocalCommandResults_SelectAll=\u0412\u044b\u0431\u0440\u0430\u0442\u044c &\u0432\u0441\u0435
 LocalCommandResults_Terminate=\u041f\u0440&\u0435\u0440\u0432\u0430\u0442\u044c
-LocalCommandResults_Terminated=\n\n*** \u041f\u0420\u0415\u0420\u0412\u0410\u041d\u041e ***\n
+LocalCommandResults_Terminated=\n\n*** \u041f\u0420\u0415\u0420\u0412\u0410\u041d\u041e ***\n\n\n
 ObjectToolExecutor_ErrorText=\u041f\u0430\u0440\u043e\u043b\u044c, \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u0432 \u043f\u043e\u043b\u0435 "%s" \u043d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c
 ObjectToolExecutor_ErrorTitle=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u0430\u0440\u043e\u043b\u044f \u043d\u0435 \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430
 ObjectToolExecutor_JobName=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u0430\u0440\u043e\u043b\u044f
index ec72631..f9e6b20 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index 36ea875..c6de9d6 100644 (file)
@@ -29,10 +29,8 @@ import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Text;
 import org.eclipse.ui.dialogs.PropertyPage;
 import org.netxms.client.ObjectMenuFilter;
-import org.netxms.client.datacollection.GraphSettings;
 import org.netxms.client.objects.MenuFiltringObj;
 import org.netxms.client.objecttools.ObjectTool;
-import org.netxms.client.objecttools.ObjectToolDetails;
 import org.netxms.ui.eclipse.objecttools.Messages;
 import org.netxms.ui.eclipse.tools.WidgetHelper;
 
index fa5b11f..fc5ab5a 100644 (file)
@@ -182,4 +182,12 @@ public class AgentActionResults extends AbstractCommandResults implements TextOu
       }
       super.dispose();
    }
+
+   /* (non-Javadoc)
+    * @see org.netxms.client.TextOutputListener#setStreamId(long)
+    */
+   @Override
+   public void setStreamId(long streamId)
+   {
+   }
 }
index cd33f49..1d77409 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * NetXMS - open source network management system
- * Copyright (C) 2003-2015 Victor Kirhenshtein
+ * Copyright (C) 2003-2017 Raden Solutions
  *
  * 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
@@ -25,6 +25,9 @@ import org.eclipse.jface.action.Action;
 import org.eclipse.jface.action.IMenuManager;
 import org.eclipse.jface.action.IToolBarManager;
 import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.ISaveablePart2;
 import org.eclipse.ui.console.IOConsoleOutputStream;
 import org.netxms.client.NXCSession;
 import org.netxms.client.TextOutputListener;
@@ -37,21 +40,26 @@ import org.netxms.ui.eclipse.shared.ConsoleSharedData;
 /**
  * View for server command execution results
  */
-public class ServerCommandResults extends AbstractCommandResults implements TextOutputListener
+public class ServerCommandResults extends AbstractCommandResults implements TextOutputListener, ISaveablePart2
 {
    public static final String ID = "org.netxms.ui.eclipse.objecttools.views.ServerCommandResults"; //$NON-NLS-1$
 
    private IOConsoleOutputStream out;
    private String lastCommand = null;
    private Action actionRestart;
+   private Action actionStop;
    private Map<String, String> lastInputValues = null;
-   
+   private long streamId = 0;
+   private NXCSession session;
+   private boolean isRunning = false;
+
    /**
     * Create actions
     */
    protected void createActions()
    {
       super.createActions();
+      session = (NXCSession)ConsoleSharedData.getSession();
       
       actionRestart = new Action(Messages.get().LocalCommandResults_Restart, SharedIcons.RESTART) {
          @Override
@@ -61,6 +69,15 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
          }
       };
       actionRestart.setEnabled(false);
+      
+      actionStop = new Action("Stop", SharedIcons.TERMINATE) {
+         @Override
+         public void run()
+         {
+            stopCommand();
+         }
+      };
+      actionStop.setEnabled(false);
    }
    
    /**
@@ -71,6 +88,7 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
    protected void fillLocalPullDown(IMenuManager manager)
    {
       manager.add(actionRestart);
+      manager.add(actionStop);
       manager.add(new Separator());
       super.fillLocalPullDown(manager);
    }
@@ -83,6 +101,7 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
    protected void fillLocalToolBar(IToolBarManager manager)
    {
       manager.add(actionRestart);
+      manager.add(actionStop);
       manager.add(new Separator());
       super.fillLocalToolBar(manager);
    }
@@ -95,6 +114,7 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
    protected void fillContextMenu(final IMenuManager manager)
    {
       manager.add(actionRestart);
+      manager.add(actionStop);
       manager.add(new Separator());
       super.fillContextMenu(manager);
    }
@@ -105,8 +125,15 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
     */
    public void executeCommand(final String command, final Map<String, String> inputValues)
    {
+      if (isRunning)
+      {
+         MessageDialog.openError(Display.getCurrent().getActiveShell(), "Error", "Command already running!");
+         return;
+      }
+      
+      isRunning = true;
       actionRestart.setEnabled(false);
-      final NXCSession session = (NXCSession)ConsoleSharedData.getSession();
+      actionStop.setEnabled(true);
       out = console.newOutputStream();
       lastCommand = command;
       lastInputValues = inputValues;
@@ -143,6 +170,8 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
                public void run()
                {
                   actionRestart.setEnabled(true);
+                  actionStop.setEnabled(false);
+                  isRunning = false;
                }
             });
          }
@@ -151,6 +180,43 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
       job.setSystem(true);
       job.start();
    }
+   
+   /**
+    * Stops running server command
+    */
+   private void stopCommand()
+   {
+      if (streamId > 0)
+      {
+         ConsoleJob job = new ConsoleJob("Stop server command for node: " + session.getObjectName(nodeId), null, Activator.PLUGIN_ID, null) {
+            @Override
+            protected void runInternal(IProgressMonitor monitor) throws Exception
+            {
+               session.stopServerCommand(streamId);
+            }
+            
+            @Override
+            protected void jobFinalize()
+            {
+               runInUIThread(new Runnable() {
+                  @Override
+                  public void run()
+                  {
+                     actionStop.setEnabled(false);
+                     actionRestart.setEnabled(true);
+                  }
+               });
+            }
+            
+            @Override
+            protected String getErrorMessage()
+            {
+               return "Failed to stop server command for node: " + session.getObjectName(nodeId);
+            }
+         };
+         job.start();
+      }
+   }
 
    /* (non-Javadoc)
     * @see org.netxms.client.ActionExecutionListener#messageReceived(java.lang.String)
@@ -174,17 +240,68 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
    @Override
    public void dispose()
    {
-      if (out != null)
-      {
-         try
-         {
-            out.close();
-         }
-         catch(IOException e)
-         {
-         }
-         out = null;
-      }
       super.dispose();
    }
+
+   /* (non-Javadoc)
+    * @see org.netxms.client.TextOutputListener#setStreamId(long)
+    */
+   @Override
+   public void setStreamId(long streamId)
+   {
+      this.streamId = streamId;
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
+    */
+   @Override
+   public void doSave(IProgressMonitor monitor)
+   {
+      stopCommand();
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart#doSaveAs()
+    */
+   @Override
+   public void doSaveAs()
+   {
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart#isDirty()
+    */
+   @Override
+   public boolean isDirty()
+   {
+      return isRunning;
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
+    */
+   @Override
+   public boolean isSaveAsAllowed()
+   {
+      return false;
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart#isSaveOnCloseNeeded()
+    */
+   @Override
+   public boolean isSaveOnCloseNeeded()
+   {
+      return isRunning;
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart2#promptToSaveOnClose()
+    */
+   @Override
+   public int promptToSaveOnClose()
+   {
+      return MessageDialog.openConfirm(Display.getCurrent().getActiveShell(), "Stop command", "Do you wish to stop the command \"" + lastCommand + "\"? ") ? 0 : 2;
+   }
 }
index 95ad211..7298dc4 100644 (file)
@@ -251,6 +251,10 @@ static bool RotateLog(bool needLock)
       TCHAR buffer[32];
       _ftprintf(m_logFileHandle, _T("%s Log file truncated.\n"), FormatLogTimestamp(buffer));
       fflush(m_logFileHandle);
+#ifndef _WIN32
+      int fd = fileno(m_logFileHandle);
+      fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
    }
 
        if (needLock)
@@ -390,6 +394,12 @@ bool LIBNETXMS_EXPORTABLE nxlog_open(const TCHAR *logName, UINT32 flags,
          _ftprintf(m_logFileHandle, _T("\n%s Log file opened (rotation policy %d, max size ") UINT64_FMT _T(")\n"),
                    FormatLogTimestamp(buffer), s_rotationMode, s_maxLogSize);
          fflush(m_logFileHandle);
+
+#ifndef _WIN32
+         int fd = fileno(m_logFileHandle);
+         fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+#endif
+
          if (s_flags & NXLOG_BACKGROUND_WRITER)
          {
             s_logBuffer.setAllocationStep(8192);
index 77b94f6..e6f235d 100644 (file)
@@ -1496,13 +1496,37 @@ cleanup:
 /**
  * Command execution data constructor
  */
-ServerCommandExecData::ServerCommandExecData(TCHAR *command, bool sendOutput, UINT32 requestId, ClientSession *session)
+ServerCommandExec::ServerCommandExec(NXCPMessage *request, ClientSession *session) : CommandExec()
 {
-   m_command = command;
-   m_sendOutput = sendOutput;
+   NetObj *object = FindObjectById(request->getFieldAsUInt32(VID_OBJECT_ID));
+   if (object != NULL)
+   {
+      StringMap *inputFields;
+      int count = request->getFieldAsInt16(VID_NUM_FIELDS);
+      if (count > 0)
+      {
+         inputFields = new StringMap();
+         UINT32 fieldId = VID_FIELD_LIST_BASE;
+         for(int i = 0; i < count; i++)
+         {
+            TCHAR *name = request->getFieldAsString(fieldId++);
+            TCHAR *value = request->getFieldAsString(fieldId++);
+            inputFields->setPreallocated(name, value);
+         }
+      }
+      else
+         inputFields = NULL;
+
+      TCHAR *cmd = request->getFieldAsString(VID_COMMAND);
+      m_cmd = ((Node *)object)->expandText(cmd, inputFields, session->getLoginName());
+      free(cmd);
+      delete inputFields;
+   }
+
+   m_sendOutput = request->getFieldAsBoolean(VID_RECEIVE_OUTPUT);
    if (m_sendOutput)
    {
-      m_requestId = requestId;
+      m_requestId = request->getId();
       m_session = session;
       m_session->incRefCount();
    }
@@ -1516,61 +1540,34 @@ ServerCommandExecData::ServerCommandExecData(TCHAR *command, bool sendOutput, UI
 /**
  * Command execution data destructor
  */
-ServerCommandExecData::~ServerCommandExecData()
+ServerCommandExec::~ServerCommandExec()
 {
    if (m_session != NULL)
       m_session->decRefCount();
-   free(m_command);
 }
 
 /**
- * Worker thread for server side command execution
+ * Send output to console
  */
-void ExecuteServerCommand(void *arg)
+void ServerCommandExec::onOutput(const char *text)
 {
-   ServerCommandExecData *data = (ServerCommandExecData *)arg;
-   DbgPrintf(5, _T("Running server-side command: %s"), data->getCommand());
-   if (data->sendOutput())
-   {
-      NXCPMessage msg;
-      msg.setCode(CMD_REQUEST_COMPLETED);
-      msg.setId(data->getRequestId());
-
-      FILE *pipe = _tpopen(data->getCommand(), _T("r"));
-      if (pipe != NULL)
-      {
-         msg.setField(VID_RCC, RCC_SUCCESS);
-         data->getSession()->sendMessage(&msg);
-
-         msg.deleteAllFields();
-         msg.setCode(CMD_COMMAND_OUTPUT);
-         while(true)
-         {
-            TCHAR line[4096];
-
-            TCHAR *ret = safe_fgetts(line, 4096, pipe);
-            if (ret == NULL)
-               break;
-
-            msg.setField(VID_MESSAGE, line);
-            data->getSession()->sendMessage(&msg);
-            msg.deleteAllFields();
-         }
+   NXCPMessage msg;
+   msg.setId(m_requestId);
+   msg.setCode(CMD_COMMAND_OUTPUT);
+   TCHAR *buffer = WideStringFromMBString(text);
+   msg.setField(VID_MESSAGE, buffer);
+   m_session->sendMessage(&msg);
+   free(buffer);
+}
 
-         pclose(pipe);
-         msg.deleteAllFields();
-         msg.setEndOfSequence();
-      }
-      else
-      {
-         msg.setField(VID_RCC, RCC_EXEC_FAILED);
-      }
-      data->getSession()->sendMessage(&msg);
-   }
-   else
-   {
-          if (_tsystem(data->getCommand()) == -1)
-         DbgPrintf(5, _T("Failed to execute command \"%s\""), data->getCommand());
-   }
-   delete data;
+/**
+ * Send message to make console stop listening to output
+ */
+void ServerCommandExec::endOfOutput()
+{
+   NXCPMessage msg;
+   msg.setId(m_requestId);
+   msg.setCode(CMD_COMMAND_OUTPUT);
+   msg.setEndOfSequence();
+   m_session->sendMessage(&msg);
 }
index 3b61bc9..1b205ae 100644 (file)
@@ -293,6 +293,7 @@ ClientSession::ClientSession(SOCKET hSocket, struct sockaddr *addr)
    m_loginTime = time(NULL);
    m_musicTypeList.add(_T("wav"));
    _tcscpy(m_language, _T("en"));
+   m_serverCommands = new HashMap<UINT32, CommandExec>(true);
 }
 
 /**
@@ -342,6 +343,8 @@ ClientSession::~ClientSession()
    {
       m_agentConn.forEach(&DeleteCallback, NULL);
    }
+
+   delete m_serverCommands;
 }
 
 /**
@@ -1344,6 +1347,9 @@ void ClientSession::processingThread()
                        case CMD_EXECUTE_SERVER_COMMAND:
                                executeServerCommand(pMsg);
                                break;
+                       case CMD_STOP_SERVER_CMD:
+                          stopServerCommand(pMsg);
+                          break;
                        case CMD_LIST_SERVER_FILES:
                                listServerFileStore(pMsg);
                                break;
@@ -12510,32 +12516,11 @@ void ClientSession::executeServerCommand(NXCPMessage *request)
                {
                        if (object->getObjectClass() == OBJECT_NODE)
                        {
-            StringMap *inputFields;
-            int count = request->getFieldAsInt16(VID_NUM_FIELDS);
-            if (count > 0)
-            {
-               inputFields = new StringMap();
-               UINT32 fieldId = VID_FIELD_LIST_BASE;
-               for(int i = 0; i < count; i++)
-               {
-                  TCHAR *name = request->getFieldAsString(fieldId++);
-                  TCHAR *value = request->getFieldAsString(fieldId++);
-                  inputFields->setPreallocated(name, value);
-               }
-            }
-            else
-            {
-               inputFields = NULL;
-            }
-
-                               TCHAR *cmd = request->getFieldAsString(VID_COMMAND);
-            TCHAR *expCmd = ((Node *)object)->expandText(cmd, inputFields, m_loginName);
-                               free(cmd);
-            delete inputFields;
-
-                               WriteAuditLog(AUDIT_OBJECTS, TRUE, m_dwUserId, m_workstation, m_id, nodeId, _T("Server command executed: %s"), expCmd);
-            ThreadPoolExecute(g_mainThreadPool, ExecuteServerCommand,
-               new ServerCommandExecData(expCmd, request->getFieldAsBoolean(VID_RECEIVE_OUTPUT), request->getId(), this));
+                          ServerCommandExec *cmd = new ServerCommandExec(request, this);
+                          registerServerCommand(cmd);
+                          cmd->execute();
+                          WriteAuditLog(AUDIT_OBJECTS, TRUE, m_dwUserId, m_workstation, m_id, nodeId, _T("Server command executed: %s"), cmd->getCommand());
+            msg.setField(VID_COMMAND_ID, cmd->getStreamId());
                                msg.setField(VID_RCC, RCC_SUCCESS);
                        }
                        else
@@ -12557,6 +12542,30 @@ void ClientSession::executeServerCommand(NXCPMessage *request)
        sendMessage(&msg);
 }
 
+/**
+ * Stop server command
+ */
+void ClientSession::stopServerCommand(NXCPMessage *request)
+{
+   NXCPMessage msg;
+
+   msg.setId(request->getId());
+   msg.setCode(CMD_REQUEST_COMPLETED);
+
+   CommandExec *cmd = m_serverCommands->get(request->getFieldAsInt64(VID_COMMAND_ID));
+   if (cmd != NULL)
+   {
+      cmd->stop();
+      msg.setField(VID_RCC, RCC_SUCCESS);
+      sendMessage(&msg);
+   }
+   else
+   {
+      msg.setField(VID_RCC, RCC_INVALID_REQUEST);
+      sendMessage(&msg);
+   }
+}
+
 /**
  * Upload file from server to agent
  */
index 96b5205..906e154 100644 (file)
@@ -451,6 +451,7 @@ private:
        ObjectIndex m_agentConn;
        StringObjectMap<UINT32> *m_subscriptions;
        MUTEX m_subscriptionLock;
+       HashMap<UINT32, CommandExec> *m_serverCommands;
 
    static THREAD_RESULT THREAD_CALL readThreadStarter(void *);
    static THREAD_RESULT THREAD_CALL writeThreadStarter(void *);
@@ -686,6 +687,7 @@ private:
        void listLibraryImages(NXCPMessage *request);
        void deleteLibraryImage(NXCPMessage *request);
        void executeServerCommand(NXCPMessage *request);
+       void stopServerCommand(NXCPMessage *request);
        void uploadFileToAgent(NXCPMessage *request);
        void listServerFileStore(NXCPMessage *request);
        void processConsoleCommand(NXCPMessage *msg);
@@ -738,6 +740,7 @@ private:
    void zmqManageSubscription(NXCPMessage *request, zmq::SubscriptionType type, bool subscribe);
    void zmqListSubscriptions(NXCPMessage *request, zmq::SubscriptionType type);
 #endif
+   void registerServerCommand(CommandExec *command) { m_serverCommands->set(command->getStreamId(), command); }
 
 public:
    ClientSession(SOCKET hSocket, struct sockaddr *addr);
@@ -875,22 +878,18 @@ enum ThreadPoolStat
 /**
  * Server command execution data
  */
-class ServerCommandExecData
+class ServerCommandExec : public CommandExec
 {
 private:
-   TCHAR *m_command;
-   bool m_sendOutput;
    UINT32 m_requestId;
    ClientSession *m_session;
 
-public:
-   ServerCommandExecData(TCHAR *command, bool sendOutput, UINT32 requestId, ClientSession *session);
-   ~ServerCommandExecData();
+   virtual void onOutput(const char *text);
+   virtual void endOfOutput();
 
-   bool sendOutput() { return m_sendOutput; }
-   const TCHAR *getCommand() { return m_command; }
-   UINT32 getRequestId() { return m_requestId; }
-   ClientSession *getSession() { return m_session; }
+public:
+   ServerCommandExec(NXCPMessage *msg, ClientSession *session);
+   ~ServerCommandExec();
 };
 
 /**
@@ -1042,8 +1041,6 @@ bool ImportObjectTool(ConfigEntry *config);
 UINT32 GetObjectToolsIntoMessage(NXCPMessage *msg, UINT32 userId, bool fullAccess);
 UINT32 GetObjectToolDetailsIntoMessage(UINT32 toolId, NXCPMessage *msg);
 
-void ExecuteServerCommand(void *arg);
-
 UINT32 ModifySummaryTable(NXCPMessage *msg, LONG *newId);
 UINT32 DeleteSummaryTable(LONG tableId);
 Table *QuerySummaryTable(LONG tableId, SummaryTable *adHocDefinition, UINT32 baseObjectId, UINT32 userId, UINT32 *rcc);
index 351e93a..97b7844 100644 (file)
@@ -709,4 +709,9 @@ public class ScriptExecutor extends ViewPart implements ISaveablePart2, TextOutp
          }
       }
    }
+
+   @Override
+   public void setStreamId(long streamId)
+   {
+   }
 }
index 741e004..3bfacd9 100644 (file)
@@ -294,6 +294,11 @@ public class NodePollerView extends ViewPart
                }
             });
          }
+
+         @Override
+         public void setStreamId(long streamId)
+         {
+         }
       };
 
       Job job = new Job(String.format(Messages.get().NodePollerView_JobName, node.getObjectName(), node.getObjectId())) {
index fe48e6a..77c0a23 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index aae4021..bfbf6ca 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index 8492737..0f20bac 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index ec72631..f9e6b20 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index 8492737..0f20bac 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index 35ddb7b..681599e 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Reiniciar
 LocalCommandResults_ScrollLock=&Travar rolagem
 LocalCommandResults_SelectAll=Selecionar &tudo
 LocalCommandResults_Terminate=&Terminar
-LocalCommandResults_Terminated=\n\n*** TERMINADO ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINADO ***\n\n\n
 ObjectToolExecutor_ErrorText=A senha inserida no campo de entrada "%s" n\u00e3o \u00e9 v\u00e1lida
 ObjectToolExecutor_ErrorTitle=Falha na valida\u00e7\u00e3o da senha
 ObjectToolExecutor_JobName=Validar senhas
index 3ce37a2..2c78d43 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&\u041f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0
 LocalCommandResults_ScrollLock=\u041f&\u0440\u043e\u043a\u0440\u0443\u0442\u043a\u0430
 LocalCommandResults_SelectAll=\u0412\u044b\u0431\u0440\u0430\u0442\u044c &\u0432\u0441\u0435
 LocalCommandResults_Terminate=\u041f\u0440&\u0435\u0440\u0432\u0430\u0442\u044c
-LocalCommandResults_Terminated=\n\n*** \u041f\u0420\u0415\u0420\u0412\u0410\u041d\u041e ***\n
+LocalCommandResults_Terminated=\n\n*** \u041f\u0420\u0415\u0420\u0412\u0410\u041d\u041e ***\n\n\n
 ObjectToolExecutor_ErrorText=\u041f\u0430\u0440\u043e\u043b\u044c, \u0432\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u0432 \u043f\u043e\u043b\u0435 "%s" \u043d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c
 ObjectToolExecutor_ErrorTitle=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u0430\u0440\u043e\u043b\u044f \u043d\u0435 \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430
 ObjectToolExecutor_JobName=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043f\u0430\u0440\u043e\u043b\u044f
index ec72631..f9e6b20 100644 (file)
@@ -96,7 +96,7 @@ LocalCommandResults_Restart=&Restart
 LocalCommandResults_ScrollLock=&Scroll lock
 LocalCommandResults_SelectAll=Select &all
 LocalCommandResults_Terminate=&Terminate
-LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n
+LocalCommandResults_Terminated=\n\n*** TERMINATED ***\n\n\n
 ObjectToolExecutor_ErrorText=Password entered in input field "%s" is not valid
 ObjectToolExecutor_ErrorTitle=Password Validation Failed
 ObjectToolExecutor_JobName=Validate passwords
index 275a78a..5fb619b 100644 (file)
@@ -182,4 +182,12 @@ public class AgentActionResults extends AbstractCommandResults implements TextOu
       }
       super.dispose();
    }
+
+   /* (non-Javadoc)
+    * @see org.netxms.client.TextOutputListener#setStreamId(long)
+    */
+   @Override
+   public void setStreamId(long streamId)
+   {
+   }
 }
index 2a4eb72..317763e 100644 (file)
@@ -25,6 +25,10 @@ import org.eclipse.jface.action.Action;
 import org.eclipse.jface.action.IMenuManager;
 import org.eclipse.jface.action.IToolBarManager;
 import org.eclipse.jface.action.Separator;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.ISaveablePart2;
 import org.netxms.client.NXCSession;
 import org.netxms.client.TextOutputListener;
 import org.netxms.ui.eclipse.console.resources.SharedIcons;
@@ -37,21 +41,26 @@ import org.netxms.ui.eclipse.widgets.TextConsole.IOConsoleOutputStream;
 /**
  * View for server command execution results
  */
-public class ServerCommandResults extends AbstractCommandResults implements TextOutputListener
+public class ServerCommandResults extends AbstractCommandResults implements TextOutputListener, ISaveablePart2
 {
    public static final String ID = "org.netxms.ui.eclipse.objecttools.views.ServerCommandResults"; //$NON-NLS-1$
 
    private IOConsoleOutputStream out;
    private String lastCommand = null;
    private Action actionRestart;
+   private Action actionStop;
    private Map<String, String> lastInputValues = null;
-   
+   private long streamId = 0;
+   private NXCSession session;
+   private boolean isRunning = false;
+
    /**
     * Create actions
     */
    protected void createActions()
    {
       super.createActions();
+      session = (NXCSession)ConsoleSharedData.getSession();
       
       actionRestart = new Action(Messages.get().LocalCommandResults_Restart, SharedIcons.RESTART) {
          @Override
@@ -61,6 +70,15 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
          }
       };
       actionRestart.setEnabled(false);
+      
+      actionStop = new Action("Stop", SharedIcons.TERMINATE) {
+         @Override
+         public void run()
+         {
+            stopCommand();
+         }
+      };
+      actionStop.setEnabled(false);
    }
    
    /**
@@ -71,6 +89,7 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
    protected void fillLocalPullDown(IMenuManager manager)
    {
       manager.add(actionRestart);
+      manager.add(actionStop);
       manager.add(new Separator());
       super.fillLocalPullDown(manager);
    }
@@ -83,6 +102,7 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
    protected void fillLocalToolBar(IToolBarManager manager)
    {
       manager.add(actionRestart);
+      manager.add(actionStop);
       manager.add(new Separator());
       super.fillLocalToolBar(manager);
    }
@@ -95,6 +115,7 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
    protected void fillContextMenu(final IMenuManager manager)
    {
       manager.add(actionRestart);
+      manager.add(actionStop);
       manager.add(new Separator());
       super.fillContextMenu(manager);
    }
@@ -105,11 +126,19 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
     */
    public void executeCommand(final String command, final Map<String, String> inputValues)
    {
+      if (isRunning)
+      {
+         MessageDialog.openError(Display.getCurrent().getActiveShell(), "Error", "Command already running!");
+         return;
+      }
+      
+      isRunning = true;
       actionRestart.setEnabled(false);
-      final NXCSession session = (NXCSession)ConsoleSharedData.getSession();
+      actionStop.setEnabled(true);
       out = console.newOutputStream();
       lastCommand = command;
       lastInputValues = inputValues;
+      final String terminated = Messages.get().LocalCommandResults_Terminated;
       ConsoleJob job = new ConsoleJob(String.format(Messages.get().ObjectToolsDynamicMenu_ExecuteOnNode, session.getObjectName(nodeId)), null, Activator.PLUGIN_ID, null) {
          @Override
          protected String getErrorMessage()
@@ -123,7 +152,10 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
             try
             {
                session.executeServerCommand(nodeId, command, inputValues, true, ServerCommandResults.this, null);
-               out.write(Messages.get(getDisplay()).LocalCommandResults_Terminated);
+               out.write(terminated);
+            }
+            catch (SWTException e)
+            {
             }
             finally
             {
@@ -138,13 +170,18 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
          @Override
          protected void jobFinalize()
          {
-            runInUIThread(new Runnable() {
-               @Override
-               public void run()
-               {
-                  actionRestart.setEnabled(true);
-               }
-            });
+            if (!console.isDisposed())
+            {
+               runInUIThread(new Runnable() {
+                  @Override
+                  public void run()
+                  {
+                     actionRestart.setEnabled(true);
+                     actionStop.setEnabled(false);
+                     isRunning = false;
+                  }
+               });
+            }
          }
       };
       job.setUser(false);
@@ -152,6 +189,43 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
       job.start();
    }
 
+    /**
+    * Stops running server command
+    */
+   private void stopCommand()
+   {
+      if (streamId > 0)
+      {
+         ConsoleJob job = new ConsoleJob("Stop server command for node: " + session.getObjectName(nodeId), null, Activator.PLUGIN_ID, null) {
+            @Override
+            protected void runInternal(IProgressMonitor monitor) throws Exception
+            {
+               session.stopServerCommand(streamId);
+            }
+            
+            @Override
+            protected void jobFinalize()
+            {
+               runInUIThread(new Runnable() {
+                  @Override
+                  public void run()
+                  {
+                     actionStop.setEnabled(false);
+                     actionRestart.setEnabled(true);
+                  }
+               });
+            }
+            
+            @Override
+            protected String getErrorMessage()
+            {
+               return "Failed to stop server command for node: " + session.getObjectName(nodeId);
+            }
+         };
+         job.start();
+      }
+   }
+
    /* (non-Javadoc)
     * @see org.netxms.client.ActionExecutionListener#messageReceived(java.lang.String)
     */
@@ -160,7 +234,7 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
    {
       try
       {
-         if (out != null)
+         if (out != null && !console.isDisposed())
             out.write(text.replace("\r", "")); //$NON-NLS-1$ //$NON-NLS-2$
       }
       catch(IOException e)
@@ -174,17 +248,68 @@ public class ServerCommandResults extends AbstractCommandResults implements Text
    @Override
    public void dispose()
    {
-      if (out != null)
-      {
-         try
-         {
-            out.close();
-         }
-         catch(IOException e)
-         {
-         }
-         out = null;
-      }
       super.dispose();
    }
+
+   /* (non-Javadoc)
+    * @see org.netxms.client.TextOutputListener#setStreamId(long)
+    */
+   @Override
+   public void setStreamId(long streamId)
+   {
+      this.streamId = streamId;
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
+    */
+   @Override
+   public void doSave(IProgressMonitor monitor)
+   {
+      stopCommand();
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart#doSaveAs()
+    */
+   @Override
+   public void doSaveAs()
+   {
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart#isDirty()
+    */
+   @Override
+   public boolean isDirty()
+   {
+      return isRunning;
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
+    */
+   @Override
+   public boolean isSaveAsAllowed()
+   {
+      return false;
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart#isSaveOnCloseNeeded()
+    */
+   @Override
+   public boolean isSaveOnCloseNeeded()
+   {
+      return isRunning;
+   }
+
+   /* (non-Javadoc)
+    * @see org.eclipse.ui.ISaveablePart2#promptToSaveOnClose()
+    */
+   @Override
+   public int promptToSaveOnClose()
+   {
+      return MessageDialog.openConfirm(Display.getCurrent().getActiveShell(), "Stop command", "Do you wish to stop the command \"" + lastCommand + "\"? ") ? 0 : 2;
+   }
 }
index fc0a34e..20f9454 100644 (file)
@@ -186,4 +186,9 @@ public class ServerScriptResults extends AbstractCommandResults implements TextO
       }
       super.dispose();
    }
+
+   @Override
+   public void setStreamId(long streamId)
+   {
+   }
 }