1
0
forked from mirrors/0ad
Files
0ad/source/lib/sysdep/os/unix/unix.cpp
T
bb 157c6af18e Make the space in 0 A.D. non-breaking throughout the codebase.
Avoid cases of filenames
Update years in terms and other legal(ish) documents
Don't update years in license headers, since change is not meaningful

Will add linter rule in seperate commit

Happy recompiling everyone!

Original Patch By: Nescio
Comment By: Gallaecio
Differential Revision: D2620
This was SVN commit r27786.
2023-07-27 20:54:46 +00:00

385 lines
10 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Copyright (c) 2021 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 <unistd.h>
#include <stdio.h>
#include <wchar.h>
#include "lib/code_annotation.h"
#include "lib/utf8.h"
#include "lib/sysdep/os/unix/udbg.h"
#include "lib/sysdep/sysdep.h"
#include <boost/algorithm/string/replace.hpp>
#define GNU_SOURCE
#include <dlfcn.h>
#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* UNUSED(text), bool UNUSED(manual_break), bool UNUSED(allow_suppress), bool UNUSED(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)
{
UNUSED2(err);
UNUSED2(buf);
UNUSED2(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& UNUSED(url), std::wstring& UNUSED(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);
}