Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
unix.cpp
Go to the documentation of this file.
1 /* Copyright (c) 2012 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/utf8.h"
30 #include "lib/sysdep/sysdep.h"
31 #include "udbg.h"
32 
33 #include <boost/algorithm/string/replace.hpp>
34 
35 #define GNU_SOURCE
36 #include <dlfcn.h>
37 
38 #include <sys/wait.h>
39 
40 #if OS_MACOSX
41 #define URL_OPEN_COMMAND "open"
42 #else
43 #define URL_OPEN_COMMAND "xdg-open"
44 #endif
45 
47 {
48  return false;
49 }
50 
51 std::wstring sys_WideFromArgv(const char* argv_i)
52 {
53  // argv is usually UTF-8 on Linux, unsure about OS X..
54  return wstring_from_utf8(argv_i);
55 }
56 
57 // these are basic POSIX-compatible backends for the sysdep.h functions.
58 // Win32 has better versions which override these.
59 
60 void sys_display_msg(const wchar_t* caption, const wchar_t* msg)
61 {
62  fprintf(stderr, "%ls: %ls\n", caption, msg); // must not use fwprintf, since stderr is byte-oriented
63 }
64 
65 #if OS_MACOSX || OS_ANDROID
66 static ErrorReactionInternal try_gui_display_error(const wchar_t* text, bool manual_break, bool allow_suppress, bool no_continue)
67 {
68  // TODO: implement this, in a way that doesn't rely on X11
69  // and doesn't occasionally cause crazy errors like
70  // "The process has forked and you cannot use this
71  // CoreFoundation functionality safely. You MUST exec()."
72 
73  return ERI_NOT_IMPLEMENTED;
74 }
75 #else
76 static ErrorReactionInternal try_gui_display_error(const wchar_t* text, bool manual_break, bool allow_suppress, bool no_continue)
77 {
78  // We'll run xmessage via fork/exec.
79  // To avoid bad interaction between fork and pthreads, the child process
80  // should only call async-signal-safe functions before exec.
81  // So prepare all the child's data in advance, before forking:
82 
83  Status err; // ignore UTF-8 errors
84  std::string message = utf8_from_wstring(text, &err);
85 
86  // Replace CRLF->LF
87  boost::algorithm::replace_all(message, "\r\n", "\n");
88 
89  // TODO: we ought to wrap the text if it's very long,
90  // since xmessage doesn't do that and it'll get clamped
91  // to the screen width
92 
93  const char* cmd = "/usr/bin/xmessage";
94 
95  char buttons[256] = "";
96  const char* defaultButton = "Exit";
97 
98  if(!no_continue)
99  {
100  strcat_s(buttons, sizeof(buttons), "Continue:100,");
101  defaultButton = "Continue";
102  }
103 
104  if(allow_suppress)
105  strcat_s(buttons, sizeof(buttons), "Suppress:101,");
106 
107  strcat_s(buttons, sizeof(buttons), "Break:102,Debugger:103,Exit:104");
108 
109  // Since execv wants non-const strings, we strdup them all here
110  // and will clean them up later (except in the child process where
111  // memory leaks don't matter)
112  char* const argv[] = {
113  strdup(cmd),
114  strdup("-geometry"), strdup("x500"), // set height so the box will always be very visible
115  strdup("-title"), strdup("0 A.D. message"), // TODO: maybe shouldn't hard-code app name
116  strdup("-buttons"), strdup(buttons),
117  strdup("-default"), strdup(defaultButton),
118  strdup(message.c_str()),
119  NULL
120  };
121 
122  pid_t cpid = fork();
123  if(cpid == -1)
124  return ERI_NOT_IMPLEMENTED;
125 
126  if(cpid == 0)
127  {
128  // This is the child process
129 
130  // Set ASCII charset, to avoid font warnings from xmessage
131  setenv("LC_ALL", "C", 1);
132 
133  // NOTE: setenv is not async-signal-safe, so we shouldn't really use
134  // it here (it might want some mutex that was held by another thread
135  // in the parent process and that will never be freed within this
136  // process). But setenv/getenv are not guaranteed reentrant either,
137  // and this error-reporting function might get called from a non-main
138  // thread, so we can't just call setenv before forking as it might
139  // break the other threads. And we can't just clone environ manually
140  // inside the parent thread and use execve, because other threads might
141  // be calling setenv and will break our iteration over environ.
142  // In the absence of a good easy solution, and given that this is only
143  // an error-reporting function and shouldn't get called frequently,
144  // we'll just do setenv after the fork and hope that it fails
145  // extremely rarely.
146 
147  execv(cmd, argv);
148 
149  // If exec returns, it failed
150  //fprintf(stderr, "Error running %s: %d\n", cmd, errno);
151  exit(-1);
152  }
153 
154  // This is the parent process
155 
156  // Avoid memory leaks
157  for(char* const* a = argv; *a; ++a)
158  free(*a);
159 
160  int status = 0;
161  waitpid(cpid, &status, 0);
162 
163  // If it didn't exist successfully, fall back to the non-GUI prompt
164  if(!WIFEXITED(status))
165  return ERI_NOT_IMPLEMENTED;
166 
167  switch(WEXITSTATUS(status))
168  {
169  case 103: // Debugger
171  //-fallthrough
172 
173  case 102: // Break
174  if(manual_break)
175  return ERI_BREAK;
176  debug_break();
177  return ERI_CONTINUE;
178 
179  case 100: // Continue
180  if(!no_continue)
181  return ERI_CONTINUE;
182  // continue isn't allowed, so this was invalid input.
183  return ERI_NOT_IMPLEMENTED;
184 
185  case 101: // Suppress
186  if(allow_suppress)
187  return ERI_SUPPRESS;
188  // suppress isn't allowed, so this was invalid input.
189  return ERI_NOT_IMPLEMENTED;
190 
191  case 104: // Exit
192  abort();
193  return ERI_EXIT; // placebo; never reached
194 
195  }
196 
197  // Unexpected return value - fall back to the non-GUI prompt
198  return ERI_NOT_IMPLEMENTED;
199 }
200 #endif
201 
202 ErrorReactionInternal sys_display_error(const wchar_t* text, size_t flags)
203 {
204  debug_printf(L"%ls\n\n", text);
205 
206  const bool manual_break = (flags & DE_MANUAL_BREAK ) != 0;
207  const bool allow_suppress = (flags & DE_ALLOW_SUPPRESS) != 0;
208  const bool no_continue = (flags & DE_NO_CONTINUE ) != 0;
209 
210  // Try the GUI prompt if possible
211  ErrorReactionInternal ret = try_gui_display_error(text, manual_break, allow_suppress, no_continue);
212  if (ret != ERI_NOT_IMPLEMENTED)
213  return ret;
214 
215 #if OS_ANDROID
216  // Android has no easy way to get user input here,
217  // so continue or exit automatically
218  if(no_continue)
219  abort();
220  else
221  return ERI_CONTINUE;
222 #else
223  // Otherwise fall back to the terminal-based input
224 
225  // Loop until valid input given:
226  for(;;)
227  {
228  if(!no_continue)
229  printf("(C)ontinue, ");
230  if(allow_suppress)
231  printf("(S)uppress, ");
232  printf("(B)reak, Launch (D)ebugger, or (E)xit?\n");
233  // TODO Should have some kind of timeout here.. in case you're unable to
234  // access the controlling terminal (As might be the case if launched
235  // from an xterm and in full-screen mode)
236  int c = getchar();
237  // note: don't use tolower because it'll choke on EOF
238  switch(c)
239  {
240  case EOF:
241  case 'd': case 'D':
243  //-fallthrough
244 
245  case 'b': case 'B':
246  if(manual_break)
247  return ERI_BREAK;
248  debug_break();
249  return ERI_CONTINUE;
250 
251  case 'c': case 'C':
252  if(!no_continue)
253  return ERI_CONTINUE;
254  // continue isn't allowed, so this was invalid input. loop again.
255  break;
256  case 's': case 'S':
257  if(allow_suppress)
258  return ERI_SUPPRESS;
259  // suppress isn't allowed, so this was invalid input. loop again.
260  break;
261 
262  case 'e': case 'E':
263  abort();
264  return ERI_EXIT; // placebo; never reached
265  }
266  }
267 #endif
268 }
269 
270 
271 Status sys_StatusDescription(int err, wchar_t* buf, size_t max_chars)
272 {
273  UNUSED2(err);
274  UNUSED2(buf);
275  UNUSED2(max_chars);
276 
277  // don't need to do anything: lib/errors.cpp already queries
278  // libc's strerror(). if we ever end up needing translation of
279  // e.g. Qt or X errors, that'd go here.
280  return ERR::FAIL;
281 }
282 
283 // note: just use the sector size: Linux aio doesn't really care about
284 // the alignment of buffers/lengths/offsets, so we'll just pick a
285 // sane value and not bother scanning all drives.
287 {
288  // users may call us more than once, so cache the results.
289  static size_t cached_sector_size;
290  if(!cached_sector_size)
291  cached_sector_size = sysconf(_SC_PAGE_SIZE);
292  return cached_sector_size;
293 }
294 
295 std::wstring sys_get_user_name()
296 {
297  // Prefer LOGNAME, fall back on getlogin
298 
299  const char* logname = getenv("LOGNAME");
300  if (logname && strcmp(logname, "") != 0)
301  return std::wstring(logname, logname + strlen(logname));
302  // TODO: maybe we should do locale conversion?
303 
304 #if OS_ANDROID
305 #warning TODO: sys_get_user_name: do something more appropriate and more thread-safe
306  char* buf = getlogin();
307  if (buf)
308  return std::wstring(buf, buf + strlen(buf));
309 #else
310  char buf[256];
311  if (getlogin_r(buf, ARRAY_SIZE(buf)) == 0)
312  return std::wstring(buf, buf + strlen(buf));
313 #endif
314 
315  return L"";
316 }
317 
319 {
320  FILE* f = fopen("/dev/urandom", "rb");
321  if (!f)
323 
324  while (count)
325  {
326  size_t numread = fread(buf, 1, count, f);
327  if (numread == 0)
329  buf += numread;
330  count -= numread;
331  }
332 
333  fclose(f);
334 
335  return INFO::OK;
336 }
337 
338 Status sys_get_proxy_config(const std::wstring& UNUSED(url), std::wstring& UNUSED(proxy))
339 {
340  return INFO::SKIPPED;
341 }
342 
343 Status sys_open_url(const std::string& url)
344 {
345  pid_t pid = fork();
346  if (pid < 0)
347  {
348  debug_warn(L"Fork failed");
349  return ERR::FAIL;
350  }
351  else if (pid == 0)
352  {
353  // we are the child
354 
355  execlp(URL_OPEN_COMMAND, URL_OPEN_COMMAND, url.c_str(), (const char*)NULL);
356 
357  debug_printf(L"Failed to run '" URL_OPEN_COMMAND "' command\n");
358 
359  // We can't call exit() because that'll try to free resources which were the parent's,
360  // so just abort here
361  abort();
362  }
363  else
364  {
365  // we are the parent
366 
367  // TODO: maybe we should wait for the child and make sure it succeeded
368 
369  return INFO::OK;
370  }
371 }
372 
373 FILE* sys_OpenFile(const OsPath& pathname, const char* mode)
374 {
375  return fopen(OsString(pathname).c_str(), mode);
376 }
#define u8
Definition: types.h:39
std::wstring sys_get_user_name()
Get the current user&#39;s login name.
Definition: unix.cpp:295
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
const Status OK
Definition: status.h:386
int setenv(const char *envname, const char *envval, int overwrite)
rationale: the Windows headers declare many POSIX functions (e.g.
Definition: wposix.cpp:34
std::string utf8_from_wstring(const std::wstring &src, Status *err)
opposite of wstring_from_utf8
Definition: utf8.cpp:208
enable the Suppress button.
Definition: debug.h:96
#define URL_OPEN_COMMAND
Definition: unix.cpp:43
special return value for the display_error app hook stub to indicate that it has done nothing and tha...
Definition: debug.h:175
ErrorReactionInternal sys_display_error(const wchar_t *text, size_t flags)
show the error dialog.
Definition: unix.cpp:202
Status sys_get_proxy_config(const std::wstring &url, std::wstring &proxy)
get the proxy address for accessing the given HTTP URL.
Definition: unix.cpp:338
size_t sys_max_sector_size()
return the largest sector size [bytes] of any storage medium (HD, optical, etc.) in the system...
Definition: unix.cpp:286
static ErrorReactionInternal try_gui_display_error(const wchar_t *text, bool manual_break, bool allow_suppress, bool no_continue)
Definition: unix.cpp:76
#define ARRAY_SIZE(name)
FILE * sys_OpenFile(const OsPath &pathname, const char *mode)
open a file like with fopen (but taking an OsPath argument).
Definition: unix.cpp:373
#define UNUSED2(param)
mark a function local variable or parameter as unused and avoid the corresponding compiler warning...
exit the program immediately.
Definition: debug.h:168
Status sys_StatusDescription(int err, wchar_t *buf, size_t max_chars)
describe the current OS error state.
Definition: unix.cpp:271
Status sys_open_url(const std::string &url)
Open the user&#39;s default web browser to the given URL.
Definition: unix.cpp:343
Definition: path.h:75
void sys_display_msg(const wchar_t *caption, const wchar_t *msg)
display a message.
Definition: unix.cpp:60
int strcat_s(char *dst, size_t max_dst_chars, const char *src)
std::wstring sys_WideFromArgv(const char *argv_i)
Definition: unix.cpp:51
i64 Status
Error handling system.
Definition: status.h:171
void debug_break()
trigger a breakpoint when reached/&quot;called&quot;.
Definition: udbg.cpp:48
disallow the Continue button.
Definition: debug.h:86
Status sys_generate_random_bytes(u8 *buf, size_t count)
generate high-quality random bytes.
Definition: unix.cpp:318
do not trigger a breakpoint inside debug_DisplayError; caller will take care of this if ER_BREAK is r...
Definition: debug.h:103
std::wstring wstring_from_utf8(const std::string &src, Status *err)
convert UTF-8 to a wide string (UTF-16 or UCS-4, depending on the platform&#39;s wchar_t).
Definition: utf8.cpp:225
bool sys_IsDebuggerPresent()
Definition: unix.cpp:46
ignore and do not report again.
Definition: debug.h:163
const Status SKIPPED
Definition: status.h:392
#define WARN_RETURN(status)
Definition: status.h:255
const Status FAIL
Definition: status.h:406
#define debug_warn(expr)
display the error dialog with the given text.
Definition: debug.h:324
void udbg_launch_debugger()
Definition: udbg.cpp:62
void debug_printf(const wchar_t *fmt,...)
write a formatted string to the debug channel, subject to filtering (see below).
Definition: debug.cpp:142
static std::string OsString(const OsPath &path)
Definition: os_path.h:42
ErrorReactionInternal
all choices offered by the error dialog.
Definition: debug.h:154