1/****************************************************************************
2 * Copyright (C) 2017 Intel Corporation.   All Rights Reserved.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 ****************************************************************************/
23
24#include "common/os.h"
25#include <vector>
26#include <array>
27#include <sstream>
28
29#if defined(_WIN32)
30#include <shlobj.h>
31#endif // Windows
32
33#if defined(__APPLE__) || defined(FORCE_LINUX) || defined(__linux__) || defined(__gnu_linux__)
34#include <pthread.h>
35#endif // Linux
36
37#if defined(_MSC_VER)
38static const DWORD MS_VC_EXCEPTION = 0x406D1388;
39
40#pragma pack(push, 8)
41typedef struct tagTHREADNAME_INFO
42{
43    DWORD  dwType;     // Must be 0x1000.
44    LPCSTR szName;     // Pointer to name (in user addr space).
45    DWORD  dwThreadID; // Thread ID (-1=caller thread).
46    DWORD  dwFlags;    // Reserved for future use, must be zero.
47} THREADNAME_INFO;
48#pragma pack(pop)
49
50void LegacySetThreadName(const char* pThreadName)
51{
52    THREADNAME_INFO info;
53    info.dwType     = 0x1000;
54    info.szName     = pThreadName;
55    info.dwThreadID = GetCurrentThreadId();
56    info.dwFlags    = 0;
57
58    if (!IsDebuggerPresent())
59    {
60        // No debugger attached to interpret exception, no need to actually do it
61        return;
62    }
63
64#pragma warning(push)
65#pragma warning(disable : 6320 6322)
66    __try
67    {
68        RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
69    }
70    __except (EXCEPTION_EXECUTE_HANDLER)
71    {
72    }
73#pragma warning(pop)
74}
75#endif // _WIN32
76
77void SWR_API SetCurrentThreadName(const char* pThreadName)
78{
79#if defined(_MSC_VER)
80    // The SetThreadDescription API was brought in version 1607 of Windows 10.
81    typedef HRESULT(WINAPI * PFNSetThreadDescription)(HANDLE hThread, PCWSTR lpThreadDescription);
82    // The SetThreadDescription API works even if no debugger is attached.
83    auto pfnSetThreadDescription = reinterpret_cast<PFNSetThreadDescription>(
84        GetProcAddress(GetModuleHandleA("Kernel32.dll"), "SetThreadDescription"));
85
86    if (!pfnSetThreadDescription)
87    {
88        // try KernelBase.dll
89        pfnSetThreadDescription = reinterpret_cast<PFNSetThreadDescription>(
90            GetProcAddress(GetModuleHandleA("KernelBase.dll"), "SetThreadDescription"));
91    }
92
93    if (pfnSetThreadDescription)
94    {
95        std::string  utf8Name = pThreadName;
96        std::wstring wideName;
97        wideName.resize(utf8Name.size() + 1);
98        swprintf_s(&(wideName.front()), wideName.size(), L"%S", utf8Name.c_str());
99        HRESULT hr = pfnSetThreadDescription(GetCurrentThread(), wideName.c_str());
100        SWR_ASSERT(SUCCEEDED(hr), "Failed to set thread name to %s", pThreadName);
101
102        // Fall through - it seems like some debuggers only recognize the exception
103    }
104
105    // Fall back to exception based hack
106    LegacySetThreadName(pThreadName);
107#endif // _WIN32
108
109#if defined(FORCE_LINUX) || defined(__linux__) || defined(__gnu_linux__)
110    pthread_setname_np(pthread_self(), pThreadName);
111#endif // Linux
112}
113
114#if defined(__APPLE__) || defined(FORCE_LINUX) || defined(__linux__) || defined(__gnu_linux__)
115static void
116SplitString(std::vector<std::string>& out_segments, const std::string& input, char splitToken)
117{
118    out_segments.clear();
119
120    std::istringstream f(input);
121    std::string        s;
122    while (std::getline(f, s, splitToken))
123    {
124        if (s.size())
125        {
126            out_segments.push_back(s);
127        }
128    }
129}
130#endif // Unix
131
132void SWR_API CreateDirectoryPath(const std::string& path)
133{
134#if defined(_WIN32)
135    SHCreateDirectoryExA(nullptr, path.c_str(), nullptr);
136#endif // Windows
137
138#if defined(__APPLE__) || defined(FORCE_LINUX) || defined(__linux__) || defined(__gnu_linux__)
139    std::vector<std::string> pathSegments;
140    SplitString(pathSegments, path, '/');
141
142    std::string tmpPath;
143    for (auto const& segment : pathSegments)
144    {
145        tmpPath.push_back('/');
146        tmpPath += segment;
147
148        int result = mkdir(tmpPath.c_str(), 0777);
149        if (result == -1 && errno != EEXIST)
150        {
151            break;
152        }
153    }
154#endif // Unix
155}
156
157/// Execute Command (block until finished)
158/// @returns process exit value
159int SWR_API ExecCmd(const std::string& cmd,     ///< (In) Command line string
160                    const char* pOptEnvStrings, ///< (Optional In) Environment block for new process
161                    std::string*       pOptStdOut, ///< (Optional Out) Standard Output text
162                    std::string*       pOptStdErr, ///< (Optional Out) Standard Error text
163                    const std::string* pOptStdIn)  ///< (Optional In) Standard Input text
164{
165    int rvalue = -1;
166
167#if defined(_WIN32)
168    struct WinPipe
169    {
170        HANDLE hRead;
171        HANDLE hWrite;
172    };
173    std::array<WinPipe, 3> hPipes = {};
174
175    SECURITY_ATTRIBUTES saAttr  = {sizeof(SECURITY_ATTRIBUTES)};
176    saAttr.bInheritHandle       = TRUE; // Pipe handles are inherited by child process.
177    saAttr.lpSecurityDescriptor = NULL;
178
179    {
180        bool bFail = false;
181        for (WinPipe& p : hPipes)
182        {
183            if (!CreatePipe(&p.hRead, &p.hWrite, &saAttr, 0))
184            {
185                bFail = true;
186            }
187        }
188
189        if (bFail)
190        {
191            for (WinPipe& p : hPipes)
192            {
193                CloseHandle(p.hRead);
194                CloseHandle(p.hWrite);
195            }
196            return rvalue;
197        }
198    }
199
200    STARTUPINFOA StartupInfo{};
201    StartupInfo.cb      = sizeof(STARTUPINFOA);
202    StartupInfo.dwFlags = STARTF_USESTDHANDLES;
203    StartupInfo.dwFlags |= STARTF_USESHOWWINDOW;
204    StartupInfo.wShowWindow = SW_HIDE;
205    if (pOptStdIn)
206    {
207        StartupInfo.hStdInput = hPipes[0].hRead;
208    }
209    StartupInfo.hStdOutput = hPipes[1].hWrite;
210    StartupInfo.hStdError  = hPipes[2].hWrite;
211    PROCESS_INFORMATION procInfo{};
212
213    // CreateProcess can modify the string
214    std::string local_cmd = cmd;
215
216    BOOL ProcessValue = CreateProcessA(NULL,
217                                       (LPSTR)local_cmd.c_str(),
218                                       NULL,
219                                       NULL,
220                                       TRUE,
221                                       0,
222                                       (LPVOID)pOptEnvStrings,
223                                       NULL,
224                                       &StartupInfo,
225                                       &procInfo);
226
227    if (ProcessValue && procInfo.hProcess)
228    {
229        auto ReadFromPipe = [](HANDLE hPipe, std::string* pOutStr) {
230            char  buf[1024];
231            DWORD dwRead  = 0;
232            DWORD dwAvail = 0;
233            while (true)
234            {
235                if (!::PeekNamedPipe(hPipe, NULL, 0, NULL, &dwAvail, NULL))
236                {
237                    break;
238                }
239
240                if (!dwAvail) // no data available, return
241                {
242                    break;
243                }
244
245                if (!::ReadFile(hPipe,
246                                buf,
247                                std::min<size_t>(sizeof(buf) - 1, size_t(dwAvail)),
248                                &dwRead,
249                                NULL) ||
250                    !dwRead)
251                {
252                    // error, the child process might ended
253                    break;
254                }
255
256                buf[dwRead] = 0;
257                if (pOutStr)
258                {
259                    (*pOutStr) += buf;
260                }
261            }
262        };
263        bool   bProcessEnded = false;
264        size_t bytesWritten  = 0;
265        do
266        {
267            if (pOptStdIn && (pOptStdIn->size() > bytesWritten))
268            {
269                DWORD bytesToWrite = static_cast<DWORD>(pOptStdIn->size()) - bytesWritten;
270                if (!::WriteFile(hPipes[0].hWrite,
271                                 pOptStdIn->data() + bytesWritten,
272                                 bytesToWrite,
273                                 &bytesToWrite,
274                                 nullptr))
275                {
276                    // Failed to write to pipe
277                    break;
278                }
279                bytesWritten += bytesToWrite;
280            }
281
282            // Give some timeslice (50ms), so we won't waste 100% cpu.
283            bProcessEnded = (WaitForSingleObject(procInfo.hProcess, 50) == WAIT_OBJECT_0);
284
285            ReadFromPipe(hPipes[1].hRead, pOptStdOut);
286            ReadFromPipe(hPipes[2].hRead, pOptStdErr);
287        } while (!bProcessEnded);
288
289        DWORD exitVal = 0;
290        if (!GetExitCodeProcess(procInfo.hProcess, &exitVal))
291        {
292            exitVal = 1;
293        }
294
295        CloseHandle(procInfo.hProcess);
296        CloseHandle(procInfo.hThread);
297
298        rvalue = exitVal;
299    }
300
301    for (WinPipe& p : hPipes)
302    {
303        CloseHandle(p.hRead);
304        CloseHandle(p.hWrite);
305    }
306
307#else
308
309    // Non-Windows implementation
310
311#endif
312
313    return rvalue;
314}
315