escargot/third_party/runtime_icu_binder/RuntimeICUBinder.cpp
Seonghyun Kim f00b8128a9 Update Runtime ICU binder for Windows
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-08-01 10:16:26 +09:00

394 lines
10 KiB
C++

/*
* Copyright (c) 2020-present Samsung Electronics Co., Ltd. All rights reserved.
*
* Licensed under the MIT License (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at
*
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
#include "RuntimeICUBinder.h"
#ifdef _WIN32
#define OS_WINDOWS 1
#elif _WIN64
#define OS_WINODWS 1
#elif __APPLE__
#include "TargetConditionals.h"
#if TARGET_IPHONE_SIMULATOR
#define OS_POSIX 1
#elif TARGET_OS_IPHONE
#define OS_POSIX 1
#elif TARGET_OS_MAC
#define OS_POSIX 1
#else
#error "Unknown Apple platform"
#endif
#elif __linux__
#define OS_POSIX 1
#elif __unix__ // all unices not caught above
#define OS_POSIX 1
#elif defined(_POSIX_VERSION)
#define OS_POSIX 1
#else
#error "failed to detect target OS"
#endif
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <string>
#if defined(OS_POSIX)
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <limits.h>
#include <dlfcn.h>
#elif defined(OS_WINDOWS)
#include <Windows.h>
#endif
namespace RuntimeICUBinder {
ICU &ICU::instance()
{
static ICU instance;
return instance;
}
static void die(const char *message)
{
fputs(message, stderr);
abort();
}
static constexpr int s_icuMinVersion = 49; // icu 4.9 was released on 2012-06-06
static constexpr int s_icuMaxVersion = 2048;
#if defined(OS_POSIX)
static void *findSo(ICU::Soname name)
{
std::string soPath;
switch (name) {
case ICU::Soname::uc:
soPath = "libicuuc.so";
break;
case ICU::Soname::i18n:
soPath = "libicui18n.so";
break;
case ICU::Soname::io:
soPath = "libicuio.so";
break;
default:
die("invalid soname");
}
void *handle = dlopen(soPath.data(), RTLD_LAZY);
if (handle) {
return handle;
}
// try to find with version name
for (int i = s_icuMinVersion; i < s_icuMaxVersion; i++) {
void *handle = dlopen((soPath + "." + std::to_string(i)).data(), RTLD_LAZY);
if (handle) {
return handle;
}
}
// will fail. reopen with original name to set dlerror
dlopen(soPath.data(), RTLD_LAZY);
return nullptr;
}
#endif
void ICU::loadSo(ICU::Soname name)
{
assert(m_soHandles[name] == nullptr);
void *handle;
#if defined(OS_POSIX)
handle = findSo(name);
if (!handle) {
char *error = dlerror();
if (error) {
fputs(error, stderr);
}
die("failed to open so");
}
#else
handle = LoadLibraryA("icu.dll");
#endif
m_soHandles[name] = handle;
}
void *ICU::loadFunction(Soname soname, Function kind)
{
assert(m_functions[kind] == nullptr);
const char *name = "";
switch (kind) {
#define DECLARE_CASE(fnName, fnType, fnReturnType) \
case function##fnName: \
name = #fnName; \
break;
FOR_EACH_UC_OP(DECLARE_CASE)
FOR_EACH_UC_VOID_OP(DECLARE_CASE)
FOR_EACH_I18N_OP(DECLARE_CASE)
FOR_EACH_I18N_VOID_OP(DECLARE_CASE)
#undef DECLARE_CASE
#define DECLARE_CASE_STICKY(fnName) \
case function##fnName: \
name = #fnName; \
break;
FOR_EACH_I18N_STICKY_OP(DECLARE_CASE_STICKY)
#undef DECLARE_CASE_STICKY
default:
die("failed to load function");
}
void *fn;
#if defined(OS_POSIX)
char nameBuffer[256];
if (m_icuVersion == -1) {
fn = dlsym(ensureLoadSo(soname), name);
} else {
snprintf(nameBuffer, sizeof(nameBuffer), "%s_%d", name, m_icuVersion);
fn = dlsym(ensureLoadSo(soname), nameBuffer);
}
if (!fn) {
fputs("failed to load", stderr);
char *error = dlerror();
if (error) {
fputs(error, stderr);
}
if (m_icuVersion == -1) {
die(name);
} else {
die(nameBuffer);
}
}
#else
fn = GetProcAddress((HMODULE)ensureLoadSo(soname), name);
if (!fn) {
fputs("failed to load", stderr);
die(name);
}
#endif
m_functions[kind] = fn;
return fn;
}
#if defined(OS_POSIX)
// https://stackoverflow.com/questions/3118582/how-do-i-find-the-current-system-timezone
/* Look for tag=someValue within filename. When found, return someValue
* in the provided value parameter up to valueSize in length. If someValue
* is enclosed in quotes, remove them. */
static char *timeValueFromFile(const char *filename, const char *tag, char *value, size_t valueSize)
{
char buffer[128], *lasts;
int foundTag = 0;
FILE *fd = fopen(filename, "r");
if (fd) {
/* Process the file, line by line. */
while (fgets(buffer, sizeof(buffer), fd)) {
lasts = buffer;
/* Look for lines with tag=value. */
char *token = strtok_r(lasts, "=", &lasts);
/* Is this the tag we are looking for? */
if (token && !strcmp(token, tag)) {
/* Parse out the value. */
char *zone = strtok_r(lasts, " \t\n", &lasts);
/* If everything looks good, copy it to our return var. */
if (zone && strlen(zone) > 0) {
size_t i = 0;
size_t j = 0;
char quote = 0x00;
/* Rather than just simple copy, remove quotes while we copy. */
for (i = 0; i < strlen(zone) && i < valueSize - 1; i++) {
/* Start quote. */
if (quote == 0x00 && zone[i] == '"')
quote = zone[i];
/* End quote. */
else if (quote != 0x00 && quote == zone[i])
quote = 0x00;
/* Copy bytes. */
else {
value[j] = zone[i];
j++;
}
}
value[j] = 0x00;
foundTag = 1;
}
break;
}
}
fclose(fd);
}
if (foundTag)
return value;
return NULL;
}
#endif
static std::string extractLocaleName(std::string input)
{
if (input.find('.') != std::string::npos) {
input = input.substr(0, input.find('.'));
}
return input;
}
std::string ICU::findSystemLocale()
{
char *c = getenv("LANG");
if (c && strlen(c)) {
return extractLocaleName(c);
}
c = setlocale(LC_CTYPE, "");
if (c && strlen(c)) {
auto locale = extractLocaleName(c);
if (locale != "C") {
return locale;
}
}
return ICU::instance().uloc_getDefault();
}
std::string ICU::findSystemTimezoneName()
{
#if defined(OS_POSIX)
char filename[PATH_MAX + 1];
struct stat fstat;
std::string nowPath = "/etc/localtime";
while (true) {
lstat(nowPath.data(), &fstat);
if (nowPath.length() > 20) {
if (nowPath.substr(0, 20) == "/usr/share/zoneinfo/") {
return nowPath.substr(20);
}
}
if (S_ISLNK(fstat.st_mode)) {
int nSize = readlink(nowPath.data(), filename, sizeof(filename));
if (nSize > 0) {
filename[nSize] = 0;
nowPath = filename;
} else {
break;
}
} else {
break;
}
}
char tz[128];
size_t tzSize = sizeof tz;
/* If there is an /etc/timezone file, then we expect it to contain
* nothing except the timezone. */
FILE *fd = fopen("/etc/timezone", "r"); // Ubuntu
if (fd) {
char buffer[128];
// There should only be one line, in this case.
while (fgets(buffer, sizeof(buffer), fd)) {
char *lasts = buffer;
// We don't want a line feed on the end.
char *tag = strtok_r(lasts, " \t\n", &lasts);
// Idiot check.
if (tag && strlen(tag) > 0 && strlen(tag) < (tzSize - 1) && tag[0] != '#') {
strncpy(tz, tag, sizeof tz);
fclose(fd);
return tz;
}
}
fclose(fd);
}
if (timeValueFromFile("/etc/sysconfig/clock", "ZONE", tz, tzSize)) { // Redhat.
return tz;
} else if (timeValueFromFile("/etc/TIMEZONE", "TZ", tz, tzSize)) { // Solaris.
return tz;
}
#endif
#if defined(ANDROID)
FILE *fp = popen("getprop persist.sys.timezone", "r");
if (fp) {
char buffer[512];
fgets(buffer, sizeof(buffer), fp);
std::string ret = buffer;
fclose(fp);
if (ret.size() && ret.back() == '\n') {
ret = ret.substr(0, ret.size() - 1);
}
if (ret.size()) {
return ret;
}
}
#endif
UChar result[256];
UErrorCode status = U_ZERO_ERROR;
int32_t len = ICU::instance().ucal_getDefaultTimeZone(result, sizeof(result) / sizeof(UChar), &status);
if (U_SUCCESS(status)) {
std::string u8Result;
for (int32_t i = 0; i < len; i++) {
u8Result.push_back(result[i]);
}
return u8Result;
}
return "";
}
ICU::ICU()
{
memset(m_soHandles, 0, sizeof(void *) * ICU::Soname::SonameMax);
memset(m_functions, 0, sizeof(void *) * ICU::Function::FunctionMax);
#if defined(OS_POSIX)
// load first so
ensureLoadSo(Soname::uc);
// try to load without version
void *ptr = dlsym(so(Soname::uc), "u_tolower");
if (ptr) {
m_icuVersion = -1;
} else {
// find version
int ver = s_icuMinVersion;
bool found = false;
while (ver < s_icuMaxVersion) {
std::string fn = "u_tolower_";
fn += std::to_string(ver);
void *ptr = dlsym(so(Soname::uc), fn.data());
if (ptr) {
found = true;
m_icuVersion = ver;
break;
}
ver++;
}
if (!found) {
die("failed to read version from so");
}
}
#endif
}
} // namespace RuntimeICUBinder