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