Line data Source code
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 :
47 0 : bool sys_IsDebuggerPresent()
48 : {
49 0 : return false;
50 : }
51 :
52 0 : std::wstring sys_WideFromArgv(const char* argv_i)
53 : {
54 : // argv is usually UTF-8 on Linux, unsure about OS X..
55 0 : 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 :
61 0 : void sys_display_msg(const wchar_t* caption, const wchar_t* msg)
62 : {
63 0 : fprintf(stderr, "%ls: %ls\n", caption, msg); // must not use fwprintf, since stderr is byte-oriented
64 0 : }
65 :
66 : #if OS_MACOSX || OS_ANDROID
67 : static 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
77 0 : static 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 0 : std::string message = utf8_from_wstring(text, &err);
86 :
87 : // Replace CRLF->LF
88 0 : 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 0 : const char* cmd = "/usr/bin/xmessage";
95 :
96 0 : char buttons[256] = "";
97 0 : const char* defaultButton = "Exit";
98 :
99 0 : if(!no_continue)
100 : {
101 0 : strcat_s(buttons, sizeof(buttons), "Continue:100,");
102 0 : defaultButton = "Continue";
103 : }
104 :
105 0 : if(allow_suppress)
106 0 : strcat_s(buttons, sizeof(buttons), "Suppress:101,");
107 :
108 0 : 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 0 : char* const argv[] = {
114 0 : strdup(cmd),
115 0 : strdup("-geometry"), strdup("x500"), // set height so the box will always be very visible
116 0 : strdup("-title"), strdup("0 A.D. message"), // TODO: maybe shouldn't hard-code app name
117 0 : strdup("-buttons"), strdup(buttons),
118 0 : strdup("-default"), strdup(defaultButton),
119 0 : strdup(message.c_str()),
120 : NULL
121 0 : };
122 :
123 0 : pid_t cpid = fork();
124 0 : if(cpid == -1)
125 : {
126 0 : for(char* const* a = argv; *a; ++a)
127 0 : free(*a);
128 0 : return ERI_NOT_IMPLEMENTED;
129 : }
130 :
131 0 : if(cpid == 0)
132 : {
133 : // This is the child process
134 :
135 : // Set ASCII charset, to avoid font warnings from xmessage
136 0 : 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 0 : execv(cmd, argv);
153 :
154 : // If exec returns, it failed
155 : //fprintf(stderr, "Error running %s: %d\n", cmd, errno);
156 0 : exit(-1);
157 : }
158 :
159 : // This is the parent process
160 :
161 : // Avoid memory leaks
162 0 : for(char* const* a = argv; *a; ++a)
163 0 : free(*a);
164 :
165 0 : int status = 0;
166 0 : waitpid(cpid, &status, 0);
167 :
168 : // If it didn't exist successfully, fall back to the non-GUI prompt
169 0 : if(!WIFEXITED(status))
170 0 : return ERI_NOT_IMPLEMENTED;
171 :
172 0 : switch(WEXITSTATUS(status))
173 : {
174 0 : case 103: // Debugger
175 0 : udbg_launch_debugger();
176 : FALLTHROUGH;
177 :
178 0 : case 102: // Break
179 0 : if(manual_break)
180 0 : return ERI_BREAK;
181 0 : debug_break();
182 0 : return ERI_CONTINUE;
183 :
184 0 : case 100: // Continue
185 0 : if(!no_continue)
186 0 : return ERI_CONTINUE;
187 : // continue isn't allowed, so this was invalid input.
188 0 : return ERI_NOT_IMPLEMENTED;
189 :
190 0 : case 101: // Suppress
191 0 : if(allow_suppress)
192 0 : return ERI_SUPPRESS;
193 : // suppress isn't allowed, so this was invalid input.
194 0 : return ERI_NOT_IMPLEMENTED;
195 :
196 0 : case 104: // Exit
197 0 : abort();
198 : return ERI_EXIT; // placebo; never reached
199 :
200 : }
201 :
202 : // Unexpected return value - fall back to the non-GUI prompt
203 0 : return ERI_NOT_IMPLEMENTED;
204 : }
205 : #endif
206 :
207 0 : ErrorReactionInternal sys_display_error(const wchar_t* text, size_t flags)
208 : {
209 0 : debug_printf("%s\n\n", utf8_from_wstring(text).c_str());
210 :
211 0 : const bool manual_break = (flags & DE_MANUAL_BREAK ) != 0;
212 0 : const bool allow_suppress = (flags & DE_ALLOW_SUPPRESS) != 0;
213 0 : const bool no_continue = (flags & DE_NO_CONTINUE ) != 0;
214 :
215 : // Try the GUI prompt if possible
216 0 : ErrorReactionInternal ret = try_gui_display_error(text, manual_break, allow_suppress, no_continue);
217 0 : if (ret != ERI_NOT_IMPLEMENTED)
218 0 : 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 0 : if(!no_continue)
234 0 : printf("(C)ontinue, ");
235 0 : if(allow_suppress)
236 0 : printf("(S)uppress, ");
237 0 : 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 0 : int c = getchar();
242 : // note: don't use tolower because it'll choke on EOF
243 0 : switch(c)
244 : {
245 0 : case EOF:
246 : case 'd': case 'D':
247 0 : udbg_launch_debugger();
248 : FALLTHROUGH;
249 :
250 0 : case 'b': case 'B':
251 0 : if(manual_break)
252 0 : return ERI_BREAK;
253 0 : debug_break();
254 0 : return ERI_CONTINUE;
255 :
256 0 : case 'c': case 'C':
257 0 : if(!no_continue)
258 0 : return ERI_CONTINUE;
259 : // continue isn't allowed, so this was invalid input. loop again.
260 0 : break;
261 0 : case 's': case 'S':
262 0 : if(allow_suppress)
263 0 : return ERI_SUPPRESS;
264 : // suppress isn't allowed, so this was invalid input. loop again.
265 0 : break;
266 :
267 0 : case 'e': case 'E':
268 0 : abort();
269 : return ERI_EXIT; // placebo; never reached
270 : }
271 0 : }
272 : #endif
273 : }
274 :
275 :
276 0 : Status 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 0 : 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.
291 0 : size_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 0 : if(!cached_sector_size)
296 0 : cached_sector_size = sysconf(_SC_PAGE_SIZE);
297 0 : return cached_sector_size;
298 : }
299 :
300 0 : std::wstring sys_get_user_name()
301 : {
302 : // Prefer LOGNAME, fall back on getlogin
303 :
304 0 : const char* logname = getenv("LOGNAME");
305 0 : if (logname && strcmp(logname, "") != 0)
306 0 : 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 0 : if (getlogin_r(buf, ARRAY_SIZE(buf)) == 0)
317 0 : return std::wstring(buf, buf + strlen(buf));
318 : #endif
319 :
320 0 : return L"";
321 : }
322 :
323 2 : Status sys_generate_random_bytes(u8* buf, size_t count)
324 : {
325 2 : FILE* f = fopen("/dev/urandom", "rb");
326 2 : if (!f)
327 0 : WARN_RETURN(ERR::FAIL);
328 :
329 6 : while (count)
330 : {
331 2 : size_t numread = fread(buf, 1, count, f);
332 2 : if (numread == 0)
333 : {
334 0 : fclose(f);
335 0 : WARN_RETURN(ERR::FAIL);
336 : }
337 2 : buf += numread;
338 2 : count -= numread;
339 : }
340 :
341 2 : fclose(f);
342 :
343 2 : return INFO::OK;
344 : }
345 :
346 0 : Status sys_get_proxy_config(const std::wstring& UNUSED(url), std::wstring& UNUSED(proxy))
347 : {
348 0 : return INFO::SKIPPED;
349 : }
350 :
351 0 : Status sys_open_url(const std::string& url)
352 : {
353 0 : pid_t pid = fork();
354 0 : if (pid < 0)
355 : {
356 0 : debug_warn(L"Fork failed");
357 0 : return ERR::FAIL;
358 : }
359 0 : else if (pid == 0)
360 : {
361 : // we are the child
362 :
363 0 : execlp(URL_OPEN_COMMAND, URL_OPEN_COMMAND, url.c_str(), (const char*)NULL);
364 :
365 0 : 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 0 : 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 0 : return INFO::OK;
378 : }
379 : }
380 :
381 0 : FILE* sys_OpenFile(const OsPath& pathname, const char* mode)
382 : {
383 0 : return fopen(OsString(pathname).c_str(), mode);
384 3 : }
|