mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-17 05:44:08 -07:00
Make include-what-you-use happy with some files in source/lib and fix what needs to be fixed. Ref: #8086 Signed-off-by: Ralph Sennhauser <ralph.sennhauser@gmail.com>
387 lines
10 KiB
C++
387 lines
10 KiB
C++
/* 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);
|
|
}
|