This Trac instance is not used for development anymore!

We migrated our development workflow to git and Gitea.
To test the future redirection, replace trac by ariadne in the page URL.

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

Last change on this file was 27965, checked in by Vladislav Belov, 13 months ago

Revert non-ASCII characters from source and configuration files introduced in rP27786.

Fixes #6846

Differential Revision: https://code.wildfiregames.com/D5185

  • 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.