LCOV - code coverage report
Current view: top level - source/lib/sysdep/os/unix - unix.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 11 135 8.1 %
Date: 2023-01-19 00:18:29 Functions: 3 14 21.4 %

          Line data    Source code
       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             : 
      47           0 : bool sys_IsDebuggerPresent()
      48             : {
      49           0 :     return false;
      50             : }
      51             : 
      52           0 : std::wstring sys_WideFromArgv(const char* argv_i)
      53             : {
      54             :     // argv is usually UTF-8 on Linux, unsure about OS X..
      55           0 :     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             : 
      61           0 : void sys_display_msg(const wchar_t* caption, const wchar_t* msg)
      62             : {
      63           0 :     fprintf(stderr, "%ls: %ls\n", caption, msg); // must not use fwprintf, since stderr is byte-oriented
      64           0 : }
      65             : 
      66             : #if OS_MACOSX || OS_ANDROID
      67             : static 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
      77           0 : static 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           0 :     std::string message = utf8_from_wstring(text, &err);
      86             : 
      87             :     // Replace CRLF->LF
      88           0 :     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           0 :     const char* cmd = "/usr/bin/xmessage";
      95             : 
      96           0 :     char buttons[256] = "";
      97           0 :     const char* defaultButton = "Exit";
      98             : 
      99           0 :     if(!no_continue)
     100             :     {
     101           0 :         strcat_s(buttons, sizeof(buttons), "Continue:100,");
     102           0 :         defaultButton = "Continue";
     103             :     }
     104             : 
     105           0 :     if(allow_suppress)
     106           0 :         strcat_s(buttons, sizeof(buttons), "Suppress:101,");
     107             : 
     108           0 :     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           0 :     char* const argv[] = {
     114           0 :         strdup(cmd),
     115           0 :         strdup("-geometry"), strdup("x500"), // set height so the box will always be very visible
     116           0 :         strdup("-title"), strdup("0 A.D. message"), // TODO: maybe shouldn't hard-code app name
     117           0 :         strdup("-buttons"), strdup(buttons),
     118           0 :         strdup("-default"), strdup(defaultButton),
     119           0 :         strdup(message.c_str()),
     120             :         NULL
     121           0 :     };
     122             : 
     123           0 :     pid_t cpid = fork();
     124           0 :     if(cpid == -1)
     125             :     {
     126           0 :         for(char* const* a = argv; *a; ++a)
     127           0 :             free(*a);
     128           0 :         return ERI_NOT_IMPLEMENTED;
     129             :     }
     130             : 
     131           0 :     if(cpid == 0)
     132             :     {
     133             :         // This is the child process
     134             : 
     135             :         // Set ASCII charset, to avoid font warnings from xmessage
     136           0 :         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           0 :         execv(cmd, argv);
     153             : 
     154             :         // If exec returns, it failed
     155             :         //fprintf(stderr, "Error running %s: %d\n", cmd, errno);
     156           0 :         exit(-1);
     157             :     }
     158             : 
     159             :     // This is the parent process
     160             : 
     161             :     // Avoid memory leaks
     162           0 :     for(char* const* a = argv; *a; ++a)
     163           0 :         free(*a);
     164             : 
     165           0 :     int status = 0;
     166           0 :     waitpid(cpid, &status, 0);
     167             : 
     168             :     // If it didn't exist successfully, fall back to the non-GUI prompt
     169           0 :     if(!WIFEXITED(status))
     170           0 :         return ERI_NOT_IMPLEMENTED;
     171             : 
     172           0 :     switch(WEXITSTATUS(status))
     173             :     {
     174           0 :     case 103: // Debugger
     175           0 :         udbg_launch_debugger();
     176             :         FALLTHROUGH;
     177             : 
     178           0 :     case 102: // Break
     179           0 :         if(manual_break)
     180           0 :             return ERI_BREAK;
     181           0 :         debug_break();
     182           0 :         return ERI_CONTINUE;
     183             : 
     184           0 :     case 100: // Continue
     185           0 :         if(!no_continue)
     186           0 :             return ERI_CONTINUE;
     187             :         // continue isn't allowed, so this was invalid input.
     188           0 :         return ERI_NOT_IMPLEMENTED;
     189             : 
     190           0 :     case 101: // Suppress
     191           0 :         if(allow_suppress)
     192           0 :             return ERI_SUPPRESS;
     193             :         // suppress isn't allowed, so this was invalid input.
     194           0 :         return ERI_NOT_IMPLEMENTED;
     195             : 
     196           0 :     case 104: // Exit
     197           0 :         abort();
     198             :         return ERI_EXIT;    // placebo; never reached
     199             : 
     200             :     }
     201             : 
     202             :     // Unexpected return value - fall back to the non-GUI prompt
     203           0 :     return ERI_NOT_IMPLEMENTED;
     204             : }
     205             : #endif
     206             : 
     207           0 : ErrorReactionInternal sys_display_error(const wchar_t* text, size_t flags)
     208             : {
     209           0 :     debug_printf("%s\n\n", utf8_from_wstring(text).c_str());
     210             : 
     211           0 :     const bool manual_break   = (flags & DE_MANUAL_BREAK  ) != 0;
     212           0 :     const bool allow_suppress = (flags & DE_ALLOW_SUPPRESS) != 0;
     213           0 :     const bool no_continue    = (flags & DE_NO_CONTINUE   ) != 0;
     214             : 
     215             :     // Try the GUI prompt if possible
     216           0 :     ErrorReactionInternal ret = try_gui_display_error(text, manual_break, allow_suppress, no_continue);
     217           0 :     if (ret != ERI_NOT_IMPLEMENTED)
     218           0 :         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           0 :         if(!no_continue)
     234           0 :             printf("(C)ontinue, ");
     235           0 :         if(allow_suppress)
     236           0 :             printf("(S)uppress, ");
     237           0 :         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           0 :         int c = getchar();
     242             :         // note: don't use tolower because it'll choke on EOF
     243           0 :         switch(c)
     244             :         {
     245           0 :         case EOF:
     246             :         case 'd': case 'D':
     247           0 :             udbg_launch_debugger();
     248             :             FALLTHROUGH;
     249             : 
     250           0 :         case 'b': case 'B':
     251           0 :             if(manual_break)
     252           0 :                 return ERI_BREAK;
     253           0 :             debug_break();
     254           0 :             return ERI_CONTINUE;
     255             : 
     256           0 :         case 'c': case 'C':
     257           0 :             if(!no_continue)
     258           0 :                 return ERI_CONTINUE;
     259             :             // continue isn't allowed, so this was invalid input. loop again.
     260           0 :             break;
     261           0 :         case 's': case 'S':
     262           0 :             if(allow_suppress)
     263           0 :                 return ERI_SUPPRESS;
     264             :             // suppress isn't allowed, so this was invalid input. loop again.
     265           0 :             break;
     266             : 
     267           0 :         case 'e': case 'E':
     268           0 :             abort();
     269             :             return ERI_EXIT;    // placebo; never reached
     270             :         }
     271           0 :     }
     272             : #endif
     273             : }
     274             : 
     275             : 
     276           0 : Status 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           0 :     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.
     291           0 : size_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           0 :     if(!cached_sector_size)
     296           0 :         cached_sector_size = sysconf(_SC_PAGE_SIZE);
     297           0 :     return cached_sector_size;
     298             : }
     299             : 
     300           0 : std::wstring sys_get_user_name()
     301             : {
     302             :     // Prefer LOGNAME, fall back on getlogin
     303             : 
     304           0 :     const char* logname = getenv("LOGNAME");
     305           0 :     if (logname && strcmp(logname, "") != 0)
     306           0 :         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           0 :     if (getlogin_r(buf, ARRAY_SIZE(buf)) == 0)
     317           0 :         return std::wstring(buf, buf + strlen(buf));
     318             : #endif
     319             : 
     320           0 :     return L"";
     321             : }
     322             : 
     323           2 : Status sys_generate_random_bytes(u8* buf, size_t count)
     324             : {
     325           2 :     FILE* f = fopen("/dev/urandom", "rb");
     326           2 :     if (!f)
     327           0 :         WARN_RETURN(ERR::FAIL);
     328             : 
     329           6 :     while (count)
     330             :     {
     331           2 :         size_t numread = fread(buf, 1, count, f);
     332           2 :         if (numread == 0)
     333             :         {
     334           0 :             fclose(f);
     335           0 :             WARN_RETURN(ERR::FAIL);
     336             :         }
     337           2 :         buf += numread;
     338           2 :         count -= numread;
     339             :     }
     340             : 
     341           2 :     fclose(f);
     342             : 
     343           2 :     return INFO::OK;
     344             : }
     345             : 
     346           0 : Status sys_get_proxy_config(const std::wstring& UNUSED(url), std::wstring& UNUSED(proxy))
     347             : {
     348           0 :     return INFO::SKIPPED;
     349             : }
     350             : 
     351           0 : Status sys_open_url(const std::string& url)
     352             : {
     353           0 :     pid_t pid = fork();
     354           0 :     if (pid < 0)
     355             :     {
     356           0 :         debug_warn(L"Fork failed");
     357           0 :         return ERR::FAIL;
     358             :     }
     359           0 :     else if (pid == 0)
     360             :     {
     361             :         // we are the child
     362             : 
     363           0 :         execlp(URL_OPEN_COMMAND, URL_OPEN_COMMAND, url.c_str(), (const char*)NULL);
     364             : 
     365           0 :         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           0 :         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           0 :         return INFO::OK;
     378             :     }
     379             : }
     380             : 
     381           0 : FILE* sys_OpenFile(const OsPath& pathname, const char* mode)
     382             : {
     383           0 :     return fopen(OsString(pathname).c_str(), mode);
     384           3 : }

Generated by: LCOV version 1.13