View Javadoc
1   /*
2    * This file is part of Waarp Project (named also Waarp or GG).
3    *
4    *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5    *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
6    * individual contributors.
7    *
8    *  All Waarp Project is free software: you can redistribute it and/or
9    * modify it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License, or (at your
11   * option) any later version.
12   *
13   * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14   * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15   * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16   *
17   *  You should have received a copy of the GNU General Public License along with
18   * Waarp . If not, see <http://www.gnu.org/licenses/>.
19   */
20  
21  package org.waarp.gateway.ftp.exec;
22  
23  import org.apache.commons.exec.CommandLine;
24  import org.apache.commons.exec.DefaultExecutor;
25  import org.apache.commons.exec.ExecuteException;
26  import org.apache.commons.exec.ExecuteWatchdog;
27  import org.apache.commons.exec.PumpStreamHandler;
28  import org.waarp.common.command.exception.Reply421Exception;
29  import org.waarp.common.future.WaarpFuture;
30  import org.waarp.common.logging.SysErrLogger;
31  import org.waarp.common.logging.WaarpLogger;
32  import org.waarp.common.logging.WaarpLoggerFactory;
33  import org.waarp.common.utility.WaarpStringUtils;
34  import org.waarp.openr66.protocol.configuration.Configuration;
35  
36  import java.io.File;
37  import java.io.IOException;
38  import java.util.regex.Pattern;
39  
40  /**
41   * ExecuteExecutor class. The given argument will be executed after
42   * replacements.
43   *
44   *
45   * <br>
46   * The following replacement are done dynamically before the command is
47   * executed:<br>
48   * - #BASEPATH# is replaced by the full path for the root of FTP Directory<br>
49   * - #FILE# is replaced by the current file path relative to FTP Directory (so
50   * #BASEPATH##FILE# is the full
51   * path of the file)<br>
52   * - #USER# is replaced by the username<br>
53   * - #ACCOUNT# is replaced by the account<br>
54   * - #COMMAND# is replaced by the command issued for the file<br>
55   * - #SPECIALID# is replaced by the FTP id of the transfer (whatever in or
56   * out)<br>
57   * - #UUID# is replaced by a special UUID globally unique for the transfer, in
58   * general to be placed in -info
59   * part (for instance ##UUID## giving #uuid#)<br>
60   */
61  public class ExecuteExecutor extends AbstractExecutor {
62    private static final String EXCEPTION = "Exception: ";
63    private static final String EXEC_IN_ERROR_WITH = "\n    Exec in error with ";
64    private static final String CANNOT_EXECUTE_PRE_COMMAND =
65        "Cannot execute Pre command";
66    /**
67     * Internal Logger
68     */
69    private static final WaarpLogger logger =
70        WaarpLoggerFactory.getLogger(ExecuteExecutor.class);
71    private static final Pattern BLANK = WaarpStringUtils.BLANK;
72    private final String[] args;
73    private final String arg;
74    private final WaarpFuture futureCompletion;
75    private final long delay;
76  
77    /**
78     * @param command
79     * @param delay
80     * @param futureCompletion
81     */
82    public ExecuteExecutor(final String command, final long delay,
83                           final WaarpFuture futureCompletion) {
84      args = BLANK.split(command);
85      arg = command;
86      this.futureCompletion = futureCompletion;
87      this.delay = delay;
88    }
89  
90    @Override
91    public final void run() throws Reply421Exception {
92      // Check if the execution will be done through LocalExec daemon
93      if (AbstractExecutor.useLocalExec) {
94        final LocalExecClient localExecClient = new LocalExecClient();
95        if (localExecClient.connect()) {
96          localExecClient.runOneCommand(arg, delay, futureCompletion);
97          localExecClient.disconnect();
98          return;
99        } // else continue
100     }
101     // Execution is done internally
102     final File exec = new File(args[0]);
103     if (exec.isAbsolute() && !exec.canExecute()) {
104       logger.error("Exec command is not executable: " + args[0]);
105       throw new Reply421Exception("Pre Exec command is not executable");
106     }
107     final CommandLine commandLine = new CommandLine(args[0]);
108     for (int i = 1; i < args.length; i++) {
109       commandLine.addArgument(args[i]);
110     }
111     final DefaultExecutor defaultExecutor = new DefaultExecutor();
112     final PumpStreamHandler pumpStreamHandler =
113         new PumpStreamHandler(null, null);
114     defaultExecutor.setStreamHandler(pumpStreamHandler);
115     final int[] correctValues = { 0, 1 };
116     defaultExecutor.setExitValues(correctValues);
117     ExecuteWatchdog watchdog = null;
118     if (delay > 0) {
119       watchdog = new ExecuteWatchdog(delay);
120       defaultExecutor.setWatchdog(watchdog);
121     }
122     int status;
123     try {
124       status = defaultExecutor.execute(commandLine);//NOSONAR
125     } catch (final ExecuteException e) {
126       if (e.getExitValue() == -559038737) {
127         // Cannot run immediately so retry once
128         try {
129           Thread.sleep(Configuration.RETRYINMS);
130         } catch (final InterruptedException e1) {//NOSONAR
131           SysErrLogger.FAKE_LOGGER.ignoreLog(e1);
132         }
133         try {
134           status = defaultExecutor.execute(commandLine);//NOSONAR
135         } catch (final ExecuteException e2) {
136           try {
137             pumpStreamHandler.stop();
138           } catch (final IOException ignored) {
139             // nothing
140           }
141           logger.error("System Exception: " + e.getMessage() +
142                        "\n    Exec cannot execute command " + commandLine);
143           throw new Reply421Exception(CANNOT_EXECUTE_PRE_COMMAND);
144         } catch (final IOException e2) {
145           try {
146             pumpStreamHandler.stop();
147           } catch (final IOException ignored) {
148             // nothing
149           }
150           logger.error(
151               EXCEPTION + e.getMessage() + EXEC_IN_ERROR_WITH + commandLine);
152           throw new Reply421Exception(CANNOT_EXECUTE_PRE_COMMAND);
153         }
154         logger.info(
155             "System Exception: {} but finally get the command executed {}",
156             e.getMessage(), commandLine);
157       } else {
158         try {
159           pumpStreamHandler.stop();
160         } catch (final IOException ignored) {
161           // nothing
162         }
163         logger.error(
164             EXCEPTION + e.getMessage() + EXEC_IN_ERROR_WITH + commandLine);
165         throw new Reply421Exception(CANNOT_EXECUTE_PRE_COMMAND);
166       }
167     } catch (final IOException e) {
168       try {
169         pumpStreamHandler.stop();
170       } catch (final IOException ignored) {
171         // nothing
172       }
173       logger.error(
174           EXCEPTION + e.getMessage() + EXEC_IN_ERROR_WITH + commandLine);
175       throw new Reply421Exception(CANNOT_EXECUTE_PRE_COMMAND);
176     }
177     try {
178       pumpStreamHandler.stop();
179     } catch (final IOException ignored) {
180       // nothing
181     }
182     if (watchdog != null && watchdog.killedProcess()) {
183       // kill by the watchdoc (time out)
184       logger.error("Exec is in Time Out");
185       status = -1;
186     }
187     if (status == 0) {
188       futureCompletion.setSuccess();
189       logger.info("Exec OK with {}", commandLine);
190     } else if (status == 1) {
191       logger.warn("Exec in warning with {}", commandLine);
192       futureCompletion.setSuccess();
193     } else {
194       logger.debug("Status: {}{} Exec in error with {}", status,
195                    status == -1? " Timeout" : "", commandLine);
196       throw new Reply421Exception("Pre command executed in error");
197     }
198   }
199 }