OpenRCT2/src/openrct2/platform/Posix.cpp

526 lines
14 KiB
C++
Raw Normal View History

2015-11-27 18:01:22 +01:00
/*****************************************************************************
* Copyright (c) 2014-2019 OpenRCT2 developers
2015-11-27 18:01:22 +01:00
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
2015-11-27 18:01:22 +01:00
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
2015-11-27 18:01:22 +01:00
*****************************************************************************/
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD__)
2015-11-27 18:01:22 +01:00
2018-07-21 16:17:06 +02:00
# include <cstring>
# include <ctype.h>
# include <dirent.h>
# include <errno.h>
# include <fcntl.h>
# include <fnmatch.h>
# if !defined(__EMSCRIPTEN__) && __has_include(<fts.h>)
2018-07-21 16:17:06 +02:00
# include <fts.h>
# endif
# include "../OpenRCT2.h"
# include "../config/Config.h"
# include "../core/Path.hpp"
# include "../core/String.hpp"
2018-07-21 16:17:06 +02:00
# include "../localisation/Date.h"
# include "../localisation/Language.h"
# include "../util/Util.h"
# include "platform.h"
# include <libgen.h>
# include <locale.h>
# include <locale>
2018-07-21 16:17:06 +02:00
# include <pwd.h>
# include <stdlib.h>
# include <sys/file.h>
# include <sys/stat.h>
# include <sys/time.h>
# include <time.h>
# include <unistd.h>
2015-11-27 18:01:22 +01:00
// The name of the mutex used to prevent multiple instances of the game from running
2018-07-21 16:17:06 +02:00
# define SINGLE_INSTANCE_MUTEX_NAME "openrct2.lock"
2015-11-27 18:01:22 +01:00
2018-07-21 16:17:06 +02:00
# define FILE_BUFFER_SIZE 4096
static utf8 _userDataDirectoryPath[MAX_PATH] = { 0 };
2015-11-27 18:01:22 +01:00
2018-06-22 23:04:38 +02:00
void platform_get_date_utc(rct2_date* out_date)
2015-11-27 18:01:22 +01:00
{
2018-01-29 17:06:01 +01:00
assert(out_date != nullptr);
time_t rawtime;
2018-06-22 23:04:38 +02:00
struct tm* timeinfo;
struct tm buf;
time(&rawtime);
timeinfo = gmtime_r(&rawtime, &buf);
out_date->day = timeinfo->tm_mday;
out_date->month = timeinfo->tm_mon + 1;
out_date->year = timeinfo->tm_year + 1900;
out_date->day_of_week = timeinfo->tm_wday;
2015-11-27 18:01:22 +01:00
}
2018-06-22 23:04:38 +02:00
void platform_get_time_utc(rct2_time* out_time)
2015-11-27 18:01:22 +01:00
{
2018-01-29 17:06:01 +01:00
assert(out_time != nullptr);
time_t rawtime;
2018-06-22 23:04:38 +02:00
struct tm* timeinfo;
struct tm buf;
time(&rawtime);
timeinfo = gmtime_r(&rawtime, &buf);
out_time->second = timeinfo->tm_sec;
out_time->minute = timeinfo->tm_min;
out_time->hour = timeinfo->tm_hour;
2015-11-27 18:01:22 +01:00
}
2018-06-22 23:04:38 +02:00
void platform_get_date_local(rct2_date* out_date)
{
2018-01-29 17:06:01 +01:00
assert(out_date != nullptr);
time_t rawtime;
2018-06-22 23:04:38 +02:00
struct tm* timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
out_date->day = timeinfo->tm_mday;
out_date->month = timeinfo->tm_mon + 1;
out_date->year = timeinfo->tm_year + 1900;
out_date->day_of_week = timeinfo->tm_wday;
}
2018-06-22 23:04:38 +02:00
void platform_get_time_local(rct2_time* out_time)
{
2018-01-29 17:06:01 +01:00
assert(out_time != nullptr);
time_t rawtime;
2018-06-22 23:04:38 +02:00
struct tm* timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
out_time->second = timeinfo->tm_sec;
out_time->minute = timeinfo->tm_min;
out_time->hour = timeinfo->tm_hour;
}
2018-06-22 23:04:38 +02:00
static size_t platform_utf8_to_multibyte(const utf8* path, char* buffer, size_t buffer_size)
{
auto wpath = String::ToWideChar(path);
setlocale(LC_CTYPE, "UTF-8");
size_t len = wcstombs(NULL, wpath.c_str(), 0);
bool truncated = false;
2018-06-22 23:04:38 +02:00
if (len > buffer_size - 1)
{
truncated = true;
len = buffer_size - 1;
}
wcstombs(buffer, wpath.c_str(), len);
buffer[len] = '\0';
if (truncated)
log_warning("truncated string %s", buffer);
return len;
}
2018-06-22 23:04:38 +02:00
bool platform_file_exists(const utf8* path)
2015-11-27 18:01:22 +01:00
{
char buffer[MAX_PATH];
platform_utf8_to_multibyte(path, buffer, MAX_PATH);
bool exists = access(buffer, F_OK) != -1;
log_verbose("file '%s' exists = %i", buffer, exists);
return exists;
2015-11-27 18:01:22 +01:00
}
2018-06-22 23:04:38 +02:00
bool platform_directory_exists(const utf8* path)
2015-11-27 18:01:22 +01:00
{
char buffer[MAX_PATH];
platform_utf8_to_multibyte(path, buffer, MAX_PATH);
struct stat dirinfo;
int32_t result = stat(buffer, &dirinfo);
log_verbose("checking dir %s, result = %d, is_dir = %d", buffer, result, S_ISDIR(dirinfo.st_mode));
2019-05-10 22:00:38 +02:00
return result == 0 && S_ISDIR(dirinfo.st_mode);
2015-11-27 18:01:22 +01:00
}
2018-06-22 23:04:38 +02:00
bool platform_original_game_data_exists(const utf8* path)
2015-11-27 18:01:22 +01:00
{
char buffer[MAX_PATH];
platform_utf8_to_multibyte(path, buffer, MAX_PATH);
char checkPath[MAX_PATH];
safe_strcpy(checkPath, buffer, MAX_PATH);
safe_strcat_path(checkPath, "Data", MAX_PATH);
safe_strcat_path(checkPath, "g1.dat", MAX_PATH);
return platform_file_exists(checkPath);
2015-11-27 18:01:22 +01:00
}
bool platform_original_rct1_data_exists(const utf8* path)
{
char buffer[MAX_PATH], checkPath1[MAX_PATH], checkPath2[MAX_PATH];
platform_utf8_to_multibyte(path, buffer, MAX_PATH);
safe_strcat_path(buffer, "Data", MAX_PATH);
safe_strcpy(checkPath1, buffer, MAX_PATH);
safe_strcpy(checkPath2, buffer, MAX_PATH);
safe_strcat_path(checkPath1, "CSG1.DAT", MAX_PATH);
safe_strcat_path(checkPath2, "CSG1.1", MAX_PATH);
// Since Linux is case sensitive (and macOS sometimes too), make sure we handle case properly.
std::string path1result = Path::ResolveCasing(checkPath1);
if (!path1result.empty())
{
return true;
}
else
{
std::string path2result = Path::ResolveCasing(checkPath2);
if (!path2result.empty())
{
return true;
}
}
return false;
}
// Implement our own version of getumask(), as it is documented being
// "a vaporware GNU extension".
2018-01-18 22:25:54 +01:00
static mode_t openrct2_getumask()
2015-11-27 18:01:22 +01:00
{
mode_t mask = umask(0);
umask(mask);
return 0777 & ~mask; // Keep in mind 0777 is octal
2015-11-27 18:01:22 +01:00
}
2018-06-22 23:04:38 +02:00
bool platform_ensure_directory_exists(const utf8* path)
2015-11-27 18:01:22 +01:00
{
2018-01-18 22:25:54 +01:00
mode_t mask = openrct2_getumask();
char buffer[MAX_PATH];
platform_utf8_to_multibyte(path, buffer, MAX_PATH);
log_verbose("Create directory: %s", buffer);
2018-06-22 23:04:38 +02:00
for (char* p = buffer + 1; *p != '\0'; p++)
{
if (*p == '/')
{
// Temporarily truncate
*p = '\0';
log_verbose("mkdir(%s)", buffer);
2018-06-22 23:04:38 +02:00
if (mkdir(buffer, mask) != 0)
{
if (errno != EEXIST)
{
return false;
}
}
// Restore truncation
*p = '/';
}
}
log_verbose("mkdir(%s)", buffer);
2018-06-22 23:04:38 +02:00
if (mkdir(buffer, mask) != 0)
{
if (errno != EEXIST)
{
return false;
}
}
return true;
2015-11-27 18:01:22 +01:00
}
2018-06-22 23:04:38 +02:00
bool platform_directory_delete(const utf8* path)
2015-11-27 18:01:22 +01:00
{
2018-07-21 16:17:06 +02:00
# ifdef _FTS_H
log_verbose("Recursively deleting directory %s", path);
2018-06-22 23:04:38 +02:00
FTS* ftsp;
FTSENT *p, *chp;
// fts_open only accepts non const paths, so we have to take a copy
char* ourPath = _strdup(path);
2018-06-22 23:04:38 +02:00
utf8* const patharray[2] = { ourPath, NULL };
if ((ftsp = fts_open(patharray, FTS_COMFOLLOW | FTS_LOGICAL | FTS_NOCHDIR, NULL)) == nullptr)
{
log_error("fts_open returned NULL");
free(ourPath);
return false;
}
chp = fts_children(ftsp, 0);
2018-06-22 23:04:38 +02:00
if (chp == nullptr)
{
log_verbose("No files to traverse, deleting directory %s", path);
if (remove(path) != 0)
{
log_error("Failed to remove %s, errno = %d", path, errno);
}
free(ourPath);
return true; // No files to traverse
}
2018-06-22 23:04:38 +02:00
while ((p = fts_read(ftsp)) != nullptr)
{
switch (p->fts_info)
{
case FTS_DP: // Directory postorder, which means
// the directory is empty
2018-06-22 23:04:38 +02:00
case FTS_F: // File
if (remove(p->fts_path))
{
log_error("Could not remove %s", p->fts_path);
fts_close(ftsp);
free(ourPath);
return false;
}
break;
case FTS_ERR:
log_error("Error traversing %s", path);
fts_close(ftsp);
free(ourPath);
return false;
}
}
free(ourPath);
fts_close(ftsp);
2018-07-21 16:17:06 +02:00
# else
log_warning("OpenRCT2 was compiled without fts.h, deleting '%s' not done.", path);
2018-07-21 16:17:06 +02:00
# endif // _FTS_H
return true;
2015-11-27 18:01:22 +01:00
}
std::string platform_get_absolute_path(const utf8* relative_path, const utf8* base_path)
{
std::string result;
if (relative_path != nullptr)
{
std::string pathToResolve;
if (base_path == nullptr)
{
pathToResolve = std::string(relative_path);
}
else
{
pathToResolve = std::string(base_path) + std::string("/") + relative_path;
}
auto realpathResult = realpath(pathToResolve.c_str(), nullptr);
if (realpathResult != nullptr)
{
result = std::string(realpathResult);
free(realpathResult);
}
}
return result;
}
2015-11-27 18:01:22 +01:00
bool platform_lock_single_instance()
{
char pidFilePath[MAX_PATH];
safe_strcpy(pidFilePath, _userDataDirectoryPath, sizeof(pidFilePath));
safe_strcat_path(pidFilePath, SINGLE_INSTANCE_MUTEX_NAME, sizeof(pidFilePath));
// We will never close this file manually. The operating system will
// take care of that, because flock keeps the lock as long as the
// file is open and closes it automatically on file close.
// This is intentional.
int32_t pidFile = open(pidFilePath, O_CREAT | O_RDWR, 0666);
2018-06-22 23:04:38 +02:00
if (pidFile == -1)
{
log_warning("Cannot open lock file for writing.");
return false;
}
2018-06-22 23:04:38 +02:00
2017-11-06 23:58:06 +01:00
struct flock lock;
2017-11-06 23:58:06 +01:00
lock.l_start = 0;
lock.l_len = 0;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
2018-06-22 23:04:38 +02:00
if (fcntl(pidFile, F_SETLK, &lock) == -1)
2017-11-07 09:15:22 +01:00
{
2018-06-22 23:04:38 +02:00
if (errno == EWOULDBLOCK)
{
log_warning("Another OpenRCT2 session has been found running.");
2017-11-07 09:15:22 +01:00
return false;
}
2017-11-06 23:58:06 +01:00
log_error("flock returned an uncatched errno: %d", errno);
return false;
2017-11-07 04:49:02 +01:00
}
2017-11-07 05:19:43 +01:00
return true;
}
2015-11-27 18:01:22 +01:00
2018-06-22 23:04:38 +02:00
int32_t platform_get_drives()
{
// POSIX systems do not know drives. Return 0.
return 0;
2015-11-27 18:01:22 +01:00
}
2018-06-22 23:04:38 +02:00
bool platform_file_copy(const utf8* srcPath, const utf8* dstPath, bool overwrite)
2015-11-27 18:01:22 +01:00
{
log_verbose("Copying %s to %s", srcPath, dstPath);
2018-06-22 23:04:38 +02:00
FILE* dstFile;
2018-06-22 23:04:38 +02:00
if (overwrite)
{
dstFile = fopen(dstPath, "wb");
2018-06-22 23:04:38 +02:00
}
else
{
// Portability note: check your libc's support for "wbx"
dstFile = fopen(dstPath, "wbx");
}
2018-06-22 23:04:38 +02:00
if (dstFile == nullptr)
{
if (errno == EEXIST)
{
log_warning("platform_file_copy: Not overwriting %s, because overwrite flag == false", dstPath);
return false;
}
log_error("Could not open destination file %s for copying", dstPath);
return false;
}
// Open both files and check whether they are opened correctly
2018-06-22 23:04:38 +02:00
FILE* srcFile = fopen(srcPath, "rb");
if (srcFile == nullptr)
{
fclose(dstFile);
log_error("Could not open source file %s for copying", srcPath);
return false;
}
size_t amount_read = 0;
size_t file_offset = 0;
// Copy file in FILE_BUFFER_SIZE-d chunks
2018-06-22 23:04:38 +02:00
char* buffer = (char*)malloc(FILE_BUFFER_SIZE);
while ((amount_read = fread(buffer, FILE_BUFFER_SIZE, 1, srcFile)))
{
fwrite(buffer, amount_read, 1, dstFile);
file_offset += amount_read;
}
// Finish the left-over data from file, which may not be a full
// FILE_BUFFER_SIZE-d chunk.
fseek(srcFile, file_offset, SEEK_SET);
amount_read = fread(buffer, 1, FILE_BUFFER_SIZE, srcFile);
fwrite(buffer, amount_read, 1, dstFile);
fclose(srcFile);
fclose(dstFile);
free(buffer);
return true;
2015-11-27 18:01:22 +01:00
}
2018-06-22 23:04:38 +02:00
bool platform_file_move(const utf8* srcPath, const utf8* dstPath)
2015-11-27 18:01:22 +01:00
{
return rename(srcPath, dstPath) == 0;
2015-11-27 18:01:22 +01:00
}
2018-06-22 23:04:38 +02:00
bool platform_file_delete(const utf8* path)
2015-11-27 18:01:22 +01:00
{
int32_t ret = unlink(path);
return ret == 0;
2015-11-27 18:01:22 +01:00
}
2018-06-22 23:04:38 +02:00
time_t platform_file_get_modified_time(const utf8* path)
{
struct stat buf;
2018-06-22 23:04:38 +02:00
if (stat(path, &buf) == 0)
{
return buf.st_mtime;
}
return 100;
2015-11-27 18:01:22 +01:00
}
2018-06-22 23:04:38 +02:00
uint8_t platform_get_locale_temperature_format()
{
// LC_MEASUREMENT is GNU specific.
2018-07-21 16:17:06 +02:00
# ifdef LC_MEASUREMENT
2018-06-22 23:04:38 +02:00
const char* langstring = setlocale(LC_MEASUREMENT, "");
2018-07-21 16:17:06 +02:00
# else
2018-06-22 23:04:38 +02:00
const char* langstring = setlocale(LC_ALL, "");
2018-07-21 16:17:06 +02:00
# endif
2018-06-22 23:04:38 +02:00
if (langstring != nullptr)
{
if (!fnmatch("*_US*", langstring, 0) || !fnmatch("*_BS*", langstring, 0) || !fnmatch("*_BZ*", langstring, 0)
|| !fnmatch("*_PW*", langstring, 0))
{
return TEMPERATURE_FORMAT_F;
}
}
return TEMPERATURE_FORMAT_C;
2015-11-27 18:01:22 +01:00
}
uint8_t platform_get_locale_date_format()
2017-04-09 04:50:34 +02:00
{
const std::time_base::dateorder dateorder = std::use_facet<std::time_get<char>>(std::locale()).date_order();
switch (dateorder)
{
case std::time_base::mdy:
return DATE_FORMAT_MONTH_DAY_YEAR;
case std::time_base::ymd:
return DATE_FORMAT_YEAR_MONTH_DAY;
case std::time_base::ydm:
return DATE_FORMAT_YEAR_DAY_MONTH;
default:
return DATE_FORMAT_DAY_MONTH_YEAR;
}
2017-04-09 04:50:34 +02:00
}
2016-01-03 02:48:52 +01:00
datetime64 platform_get_datetime_now_utc()
{
const datetime64 epochAsTicks = 621355968000000000;
2016-01-03 02:48:52 +01:00
struct timeval tv;
gettimeofday(&tv, NULL);
2016-01-03 02:48:52 +01:00
// Epoch starts from: 1970-01-01T00:00:00Z
// Convert to ticks from 0001-01-01T00:00:00Z
uint64_t utcEpochTicks = (uint64_t)tv.tv_sec * 10000000ULL + tv.tv_usec * 10;
datetime64 utcNow = epochAsTicks + utcEpochTicks;
return utcNow;
2016-01-03 02:48:52 +01:00
}
std::string platform_get_username()
2018-06-22 23:04:38 +02:00
{
std::string result;
auto pw = getpwuid(getuid());
if (pw != nullptr)
2018-06-22 23:04:38 +02:00
{
result = std::string(pw->pw_name);
}
return result;
}
bool platform_process_is_elevated()
{
2018-07-21 16:17:06 +02:00
# ifndef __EMSCRIPTEN__
2018-06-22 23:04:38 +02:00
return (geteuid() == 0);
2018-07-21 16:17:06 +02:00
# else
2018-06-22 23:04:38 +02:00
return false;
2018-07-21 16:17:06 +02:00
# endif // __EMSCRIPTEN__
}
std::string platform_get_rct1_steam_dir()
{
return "app_285310" PATH_SEPARATOR "depot_285311";
}
std::string platform_get_rct2_steam_dir()
{
return "app_285330" PATH_SEPARATOR "depot_285331";
}
2015-11-27 18:01:22 +01:00
#endif