source: ps/trunk/source/lib/sysdep/os/unix/unix.cpp

Last change on this file was 24903, checked in by Vladislav Belov, 14 months ago

Fixes leaking of a file handle in case of an error. Refs #5288

Reported By: PVS-Studio

  • Property svn:eol-style set to native
File size: 10.3 KB
Line 
1/* Copyright (c) 2021 Wildfire Games.
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining
4 * a copy of this software and associated documentation files (the
5 * "Software"), to deal in the Software without restriction, including
6 * without limitation the rights to use, copy, modify, merge, publish,
7 * distribute, sublicense, and/or sell copies of the Software, and to
8 * permit persons to whom the Software is furnished to do so, subject to
9 * the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included
12 * in all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 */
22
23#include "precompiled.h"
24
25#include <unistd.h>
26#include <stdio.h>
27#include <wchar.h>
28
29#include "lib/code_annotation.h"
30#include "lib/utf8.h"
31#include "lib/sysdep/os/unix/udbg.h"
32#include "lib/sysdep/sysdep.h"
33
34#include <boost/algorithm/string/replace.hpp>
35
36#define GNU_SOURCE
37#include <dlfcn.h>
38
39#include <sys/wait.h>
40
41#if OS_MACOSX
42#define URL_OPEN_COMMAND "open"
43#else
44#define URL_OPEN_COMMAND "xdg-open"
45#endif
46
47bool sys_IsDebuggerPresent()
48{
49 return false;
50}
51
52std::wstring sys_WideFromArgv(const char* argv_i)
53{
54 // argv is usually UTF-8 on Linux, unsure about OS X..
55 return wstring_from_utf8(argv_i);
56}
57
58// these are basic POSIX-compatible backends for the sysdep.h functions.
59// Win32 has better versions which override these.
60
61void sys_display_msg(const wchar_t* caption, const wchar_t* msg)
62{
63 fprintf(stderr, "%ls: %ls\n", caption, msg); // must not use fwprintf, since stderr is byte-oriented
64}
65
66#if OS_MACOSX || OS_ANDROID
67static ErrorReactionInternal try_gui_display_error(const wchar_t* UNUSED(text), bool UNUSED(manual_break), bool UNUSED(allow_suppress), bool UNUSED(no_continue))
68{
69 // TODO: implement this, in a way that doesn't rely on X11
70 // and doesn't occasionally cause crazy errors like
71 // "The process has forked and you cannot use this
72 // CoreFoundation functionality safely. You MUST exec()."
73
74 return ERI_NOT_IMPLEMENTED;
75}
76#else
77static ErrorReactionInternal try_gui_display_error(const wchar_t* text, bool manual_break, bool allow_suppress, bool no_continue)
78{
79 // We'll run xmessage via fork/exec.
80 // To avoid bad interaction between fork and pthreads, the child process
81 // should only call async-signal-safe functions before exec.
82 // So prepare all the child's data in advance, before forking:
83
84 Status err; // ignore UTF-8 errors
85 std::string message = utf8_from_wstring(text, &err);
86
87 // Replace CRLF->LF
88 boost::algorithm::replace_all(message, "\r\n", "\n");
89
90 // TODO: we ought to wrap the text if it's very long,
91 // since xmessage doesn't do that and it'll get clamped
92 // to the screen width
93
94 const char* cmd = "/usr/bin/xmessage";
95
96 char buttons[256] = "";
97 const char* defaultButton = "Exit";
98
99 if(!no_continue)
100 {
101 strcat_s(buttons, sizeof(buttons), "Continue:100,");
102 defaultButton = "Continue";
103 }
104
105 if(allow_suppress)
106 strcat_s(buttons, sizeof(buttons), "Suppress:101,");
107
108 strcat_s(buttons, sizeof(buttons), "Break:102,Debugger:103,Exit:104");
109
110 // Since execv wants non-const strings, we strdup them all here
111 // and will clean them up later (except in the child process where
112 // memory leaks don't matter)
113 char* const argv[] = {
114 strdup(cmd),
115 strdup("-geometry"), strdup("x500"), // set height so the box will always be very visible
116 strdup("-title"), strdup("0 A.D. message"), // TODO: maybe shouldn't hard-code app name
117 strdup("-buttons"), strdup(buttons),
118 strdup("-default"), strdup(defaultButton),
119 strdup(message.c_str()),
120 NULL
121 };
122
123 pid_t cpid = fork();
124 if(cpid == -1)
125 {
126 for(char* const* a = argv; *a; ++a)
127 free(*a);
128 return ERI_NOT_IMPLEMENTED;
129 }
130
131 if(cpid == 0)
132 {
133 // This is the child process
134
135 // Set ASCII charset, to avoid font warnings from xmessage
136 setenv("LC_ALL", "C", 1);
137
138 // NOTE: setenv is not async-signal-safe, so we shouldn't really use
139 // it here (it might want some mutex that was held by another thread
140 // in the parent process and that will never be freed within this
141 // process). But setenv/getenv are not guaranteed reentrant either,
142 // and this error-reporting function might get called from a non-main
143 // thread, so we can't just call setenv before forking as it might
144 // break the other threads. And we can't just clone environ manually
145 // inside the parent thread and use execve, because other threads might
146 // be calling setenv and will break our iteration over environ.
147 // In the absence of a good easy solution, and given that this is only
148 // an error-reporting function and shouldn't get called frequently,
149 // we'll just do setenv after the fork and hope that it fails
150 // extremely rarely.
151
152 execv(cmd, argv);
153
154 // If exec returns, it failed
155 //fprintf(stderr, "Error running %s: %d\n", cmd, errno);
156 exit(-1);
157 }
158
159 // This is the parent process
160
161 // Avoid memory leaks
162 for(char* const* a = argv; *a; ++a)
163 free(*a);
164
165 int status = 0;
166 waitpid(cpid, &status, 0);
167
168 // If it didn't exist successfully, fall back to the non-GUI prompt
169 if(!WIFEXITED(status))
170 return ERI_NOT_IMPLEMENTED;
171
172 switch(WEXITSTATUS(status))
173 {
174 case 103: // Debugger
175 udbg_launch_debugger();
176 FALLTHROUGH;
177
178 case 102: // Break
179 if(manual_break)
180 return ERI_BREAK;
181 debug_break();
182 return ERI_CONTINUE;
183
184 case 100: // Continue
185 if(!no_continue)
186 return ERI_CONTINUE;
187 // continue isn't allowed, so this was invalid input.
188 return ERI_NOT_IMPLEMENTED;
189
190 case 101: // Suppress
191 if(allow_suppress)
192 return ERI_SUPPRESS;
193 // suppress isn't allowed, so this was invalid input.
194 return ERI_NOT_IMPLEMENTED;
195
196 case 104: // Exit
197 abort();
198 return ERI_EXIT; // placebo; never reached
199
200 }
201
202 // Unexpected return value - fall back to the non-GUI prompt
203 return ERI_NOT_IMPLEMENTED;
204}
205#endif
206
207ErrorReactionInternal sys_display_error(const wchar_t* text, size_t flags)
208{
209 debug_printf("%s\n\n", utf8_from_wstring(text).c_str());
210
211 const bool manual_break = (flags & DE_MANUAL_BREAK ) != 0;
212 const bool allow_suppress = (flags & DE_ALLOW_SUPPRESS) != 0;
213 const bool no_continue = (flags & DE_NO_CONTINUE ) != 0;
214
215 // Try the GUI prompt if possible
216 ErrorReactionInternal ret = try_gui_display_error(text, manual_break, allow_suppress, no_continue);
217 if (ret != ERI_NOT_IMPLEMENTED)
218 return ret;
219
220#if OS_ANDROID
221 // Android has no easy way to get user input here,
222 // so continue or exit automatically
223 if(no_continue)
224 abort();
225 else
226 return ERI_CONTINUE;
227#else
228 // Otherwise fall back to the terminal-based input
229
230 // Loop until valid input given:
231 for(;;)
232 {
233 if(!no_continue)
234 printf("(C)ontinue, ");
235 if(allow_suppress)
236 printf("(S)uppress, ");
237 printf("(B)reak, Launch (D)ebugger, or (E)xit?\n");
238 // TODO Should have some kind of timeout here.. in case you're unable to
239 // access the controlling terminal (As might be the case if launched
240 // from an xterm and in full-screen mode)
241 int c = getchar();
242 // note: don't use tolower because it'll choke on EOF
243 switch(c)
244 {
245 case EOF:
246 case 'd': case 'D':
247 udbg_launch_debugger();
248 FALLTHROUGH;
249
250 case 'b': case 'B':
251 if(manual_break)
252 return ERI_BREAK;
253 debug_break();
254 return ERI_CONTINUE;
255
256 case 'c': case 'C':
257 if(!no_continue)
258 return ERI_CONTINUE;
259 // continue isn't allowed, so this was invalid input. loop again.
260 break;
261 case 's': case 'S':
262 if(allow_suppress)
263 return ERI_SUPPRESS;
264 // suppress isn't allowed, so this was invalid input. loop again.
265 break;
266
267 case 'e': case 'E':
268 abort();
269 return ERI_EXIT; // placebo; never reached
270 }
271 }
272#endif
273}
274
275
276Status sys_StatusDescription(int err, wchar_t* buf, size_t max_chars)
277{
278 UNUSED2(err);
279 UNUSED2(buf);
280 UNUSED2(max_chars);
281
282 // don't need to do anything: lib/errors.cpp already queries
283 // libc's strerror(). if we ever end up needing translation of
284 // e.g. Qt or X errors, that'd go here.
285 return ERR::FAIL;
286}
287
288// note: just use the sector size: Linux aio doesn't really care about
289// the alignment of buffers/lengths/offsets, so we'll just pick a
290// sane value and not bother scanning all drives.
291size_t sys_max_sector_size()
292{
293 // users may call us more than once, so cache the results.
294 static size_t cached_sector_size;
295 if(!cached_sector_size)
296 cached_sector_size = sysconf(_SC_PAGE_SIZE);
297 return cached_sector_size;
298}
299
300std::wstring sys_get_user_name()
301{
302 // Prefer LOGNAME, fall back on getlogin
303
304 const char* logname = getenv("LOGNAME");
305 if (logname && strcmp(logname, "") != 0)
306 return std::wstring(logname, logname + strlen(logname));
307 // TODO: maybe we should do locale conversion?
308
309#if OS_ANDROID
310#warning TODO: sys_get_user_name: do something more appropriate and more thread-safe
311 char* buf = getlogin();
312 if (buf)
313 return std::wstring(buf, buf + strlen(buf));
314#else
315 char buf[256];
316 if (getlogin_r(buf, ARRAY_SIZE(buf)) == 0)
317 return std::wstring(buf, buf + strlen(buf));
318#endif
319
320 return L"";
321}
322
323Status sys_generate_random_bytes(u8* buf, size_t count)
324{
325 FILE* f = fopen("/dev/urandom", "rb");
326 if (!f)
327 WARN_RETURN(ERR::FAIL);
328
329 while (count)
330 {
331 size_t numread = fread(buf, 1, count, f);
332 if (numread == 0)
333 {
334 fclose(f);
335 WARN_RETURN(ERR::FAIL);
336 }
337 buf += numread;
338 count -= numread;
339 }
340
341 fclose(f);
342
343 return INFO::OK;
344}
345
346Status sys_get_proxy_config(const std::wstring& UNUSED(url), std::wstring& UNUSED(proxy))
347{
348 return INFO::SKIPPED;
349}
350
351Status sys_open_url(const std::string& url)
352{
353 pid_t pid = fork();
354 if (pid < 0)
355 {
356 debug_warn(L"Fork failed");
357 return ERR::FAIL;
358 }
359 else if (pid == 0)
360 {
361 // we are the child
362
363 execlp(URL_OPEN_COMMAND, URL_OPEN_COMMAND, url.c_str(), (const char*)NULL);
364
365 debug_printf("Failed to run '" URL_OPEN_COMMAND "' command\n");
366
367 // We can't call exit() because that'll try to free resources which were the parent's,
368 // so just abort here
369 abort();
370 }
371 else
372 {
373 // we are the parent
374
375 // TODO: maybe we should wait for the child and make sure it succeeded
376
377 return INFO::OK;
378 }
379}
380
381FILE* sys_OpenFile(const OsPath& pathname, const char* mode)
382{
383 return fopen(OsString(pathname).c_str(), mode);
384}
Note: See TracBrowser for help on using the repository browser.