aboutsummaryrefslogtreecommitdiff
path: root/flang/runtime/execute.cpp
blob: 0f5bc5059e21dc8d57854d325c8813e13920a873 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
//===-- runtime/execute.cpp -----------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "flang/Runtime/execute.h"
#include "environment.h"
#include "stat.h"
#include "terminator.h"
#include "tools.h"
#include "flang/Runtime/descriptor.h"
#include <cstdlib>
#include <future>
#include <limits>
#ifdef _WIN32
#include "flang/Common/windows-include.h"
#else
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#endif

namespace Fortran::runtime {

// cmdstat specified in 16.9.73
// −1 if the processor does not support command line execution,
// a processor-dependent positive value if an error condition occurs
// −2 if no error condition occurs but WAIT is present with the value false
// and the processor does not support asynchronous execution. Otherwise it is
// assigned the value 0
enum CMD_STAT {
  ASYNC_NO_SUPPORT_ERR = -2,
  NO_SUPPORT_ERR = -1,
  CMD_EXECUTED = 0,
  FORK_ERR = 1,
  EXECL_ERR = 2,
  INVALID_CL_ERR = 3,
  SIGNAL_ERR = 4
};

// Override CopyCharsToDescriptor in tools.h, pass string directly
void CopyCharsToDescriptor(const Descriptor &value, const char *rawValue) {
  CopyCharsToDescriptor(value, rawValue, std::strlen(rawValue));
}

void CheckAndCopyCharsToDescriptor(
    const Descriptor *value, const char *rawValue) {
  if (value) {
    CopyCharsToDescriptor(*value, rawValue);
  }
}

void CheckAndStoreIntToDescriptor(
    const Descriptor *intVal, std::int64_t value, Terminator &terminator) {
  if (intVal) {
    StoreIntToDescriptor(intVal, value, terminator);
  }
}

// If a condition occurs that would assign a nonzero value to CMDSTAT but
// the CMDSTAT variable is not present, error termination is initiated.
int TerminationCheck(int status, const Descriptor *cmdstat,
    const Descriptor *cmdmsg, Terminator &terminator) {
  if (status == -1) {
    if (!cmdstat) {
      terminator.Crash("Execution error with system status code: %d", status);
    } else {
      StoreIntToDescriptor(cmdstat, EXECL_ERR, terminator);
      CheckAndCopyCharsToDescriptor(cmdmsg, "Execution error");
    }
  }
#ifdef _WIN32
  // On WIN32 API std::system returns exit status directly
  int exitStatusVal{status};
  if (exitStatusVal == 1) {
#else
  int exitStatusVal{WEXITSTATUS(status)};
  if (exitStatusVal == 127 || exitStatusVal == 126) {
#endif
    if (!cmdstat) {
      terminator.Crash(
          "Invalid command quit with exit status code: %d", exitStatusVal);
    } else {
      StoreIntToDescriptor(cmdstat, INVALID_CL_ERR, terminator);
      CheckAndCopyCharsToDescriptor(cmdmsg, "Invalid command line");
    }
  }
#if defined(WIFSIGNALED) && defined(WTERMSIG)
  if (WIFSIGNALED(status)) {
    if (!cmdstat) {
      terminator.Crash("killed by signal: %d", WTERMSIG(status));
    } else {
      StoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator);
      CheckAndCopyCharsToDescriptor(cmdmsg, "killed by signal");
    }
  }
#endif
#if defined(WIFSTOPPED) && defined(WSTOPSIG)
  if (WIFSTOPPED(status)) {
    if (!cmdstat) {
      terminator.Crash("stopped by signal: %d", WSTOPSIG(status));
    } else {
      StoreIntToDescriptor(cmdstat, SIGNAL_ERR, terminator);
      CheckAndCopyCharsToDescriptor(cmdmsg, "stopped by signal");
    }
  }
#endif
  return exitStatusVal;
}

void RTNAME(ExecuteCommandLine)(const Descriptor &command, bool wait,
    const Descriptor *exitstat, const Descriptor *cmdstat,
    const Descriptor *cmdmsg, const char *sourceFile, int line) {
  Terminator terminator{sourceFile, line};
  char *newCmd{EnsureNullTerminated(
      command.OffsetElement(), command.ElementBytes(), terminator)};

  if (exitstat) {
    RUNTIME_CHECK(terminator, IsValidIntDescriptor(exitstat));
  }

  if (cmdstat) {
    RUNTIME_CHECK(terminator, IsValidIntDescriptor(cmdstat));
    // Assigned 0 as specifed in standard, if error then overwrite
    StoreIntToDescriptor(cmdstat, CMD_EXECUTED, terminator);
  }

  if (cmdmsg) {
    RUNTIME_CHECK(terminator, IsValidCharDescriptor(cmdmsg));
  }

  if (wait) {
    // either wait is not specified or wait is true: synchronous mode
    int status{std::system(newCmd)};
    int exitStatusVal{TerminationCheck(status, cmdstat, cmdmsg, terminator)};
    // If sync, assigned processor-dependent exit status. Otherwise unchanged
    CheckAndStoreIntToDescriptor(exitstat, exitStatusVal, terminator);
  } else {
// Asynchronous mode
#ifdef _WIN32
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    // add "cmd.exe /c " to the beginning of command
    const char *prefix{"cmd.exe /c "};
    char *newCmdWin{static_cast<char *>(AllocateMemoryOrCrash(
        terminator, std::strlen(prefix) + std::strlen(newCmd) + 1))};
    std::strcpy(newCmdWin, prefix);
    std::strcat(newCmdWin, newCmd);

    // Convert the char to wide char
    const size_t sizeNeeded{mbstowcs(NULL, newCmdWin, 0) + 1};
    wchar_t *wcmd{static_cast<wchar_t *>(
        AllocateMemoryOrCrash(terminator, sizeNeeded * sizeof(wchar_t)))};
    if (std::mbstowcs(wcmd, newCmdWin, sizeNeeded) == static_cast<size_t>(-1)) {
      terminator.Crash("Char to wide char failed for newCmd");
    }
    FreeMemory(newCmdWin);

    if (CreateProcess(nullptr, wcmd, nullptr, nullptr, FALSE, 0, nullptr,
            nullptr, &si, &pi)) {
      // Close handles so it will be removed when terminated
      CloseHandle(pi.hProcess);
      CloseHandle(pi.hThread);
    } else {
      if (!cmdstat) {
        terminator.Crash(
            "CreateProcess failed with error code: %lu.", GetLastError());
      } else {
        StoreIntToDescriptor(cmdstat, (uint32_t)GetLastError(), terminator);
        CheckAndCopyCharsToDescriptor(cmdmsg, "CreateProcess failed.");
      }
    }
    FreeMemory(wcmd);
#else
    pid_t pid{fork()};
    if (pid < 0) {
      if (!cmdstat) {
        terminator.Crash("Fork failed with pid: %d.", pid);
      } else {
        StoreIntToDescriptor(cmdstat, FORK_ERR, terminator);
        CheckAndCopyCharsToDescriptor(cmdmsg, "Fork failed");
      }
    } else if (pid == 0) {
      // Create a new session, let init process take care of zombie child
      if (setsid() == -1) {
        if (!cmdstat) {
          terminator.Crash("setsid() failed with errno: %d, asynchronous "
                           "process initiation failed.",
              errno);
        } else {
          StoreIntToDescriptor(cmdstat, ASYNC_NO_SUPPORT_ERR, terminator);
          CheckAndCopyCharsToDescriptor(cmdmsg,
              "setsid() failed, asynchronous process initiation failed.");
        }
        exit(EXIT_FAILURE);
      }
      int status{std::system(newCmd)};
      TerminationCheck(status, cmdstat, cmdmsg, terminator);
      exit(status);
    }
#endif
  }
  // Deallocate memory if EnsureNullTerminated dynamically allocated memory
  if (newCmd != command.OffsetElement()) {
    FreeMemory(newCmd);
  }
}

} // namespace Fortran::runtime