The owner: "ensure that all software patent and trademark licenses in the repo are being respected". A read-only audit of what the packages carry found no blocker and these gaps, all closed here (the record is impl-review-findings-6.md, "Licensing audit"): - NOTICE names the runtime the engine ships with: glibc 2.23 as crosstool-NG modifies it (LGPL-2.1), the GCC 9.4.0 runtime (GPL-3.0 with the Runtime Library Exception), and the eight Debian jessie libraries, with the FreeType disclaimer, Lato, the hyphenation patterns, and where the corresponding source is. licenses/ gains LGPL-2.1, GPL-3.0 and the GCC exception from the pinned tarballs and Debian's copyright files from the sysroot; every package ships all of licenses/ and CREDITS.md, checked in the payload preflight. - NOTICE names the modified engine's source (the fork, branch, pinned commit, base release, the patches), as MPL-2.0 3.2 asks. - README and LICENSE no longer call the packages Apache-2.0 and MPL-2.0 only; the dead licensing.shtml reference is replaced by the UXP LICENSE and TRADEMARK files and Moonchild's legal and redist pages. - Apache-2.0 4(b) change notices on the 29 HP-derived files changed since import. - A drawn magnifier is the DuckDuckGo engines' icon; Google's logo is Google's only; the unused Bing logo is gone. - The engine's services-sync.js defaults pointed at Moonchild's Sync, key server and add-on host: blanked at bundle time, checked. - The "Blazer" keyword, the Goanna-mark tagline, a stray screenshot and a .bak dropped; the HP SDK's terms stated where it is downloaded; CREDITS records the logo's origin and the screenshot strip's licence. Deferred to the next engine build: about:credits' URL and --disable-ffmpeg (the system-libavcodec path is dead on the device). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XM9EDSp83ZFRhggY4RCT9X
500 lines
15 KiB
C++
500 lines
15 KiB
C++
/* @@@LICENSE
|
|
*
|
|
* Copyright (c) 2012 Hewlett-Packard Development Company, L.P.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* 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.
|
|
*
|
|
LICENSE@@@ */
|
|
|
|
/* Modified by the Jihad Browser project (2026), per Apache-2.0 section 4(b): this file differs from
|
|
* HP's isis-project original. The changes are this repository's git history for this file. */
|
|
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <byteswap.h>
|
|
#include <sys/stat.h>
|
|
#include <pthread.h>
|
|
|
|
#include "YapProxy.h"
|
|
#include "YapServer.h"
|
|
|
|
// Jihad Browser: the desktop daemon runs the GLib main loop directly (see
|
|
// run() below), so no Qt is needed. Original used QApplication::exec().
|
|
|
|
static const int kMaxPathLen = 256;
|
|
static const char* kSocketPathPrefix = "/tmp/yapserver.";
|
|
static const int kMaxConnections = 100;
|
|
|
|
// Minimum that is good for performance/power reasons and 32-bit (signed) counter overflow
|
|
static const int kDeadlockTimeoutMinSecs = 10;
|
|
|
|
class YapServerDeadlockPriv
|
|
{
|
|
public:
|
|
YapServerDeadlockPriv(int deadlockTimeoutMs = 15000);
|
|
~YapServerDeadlockPriv();
|
|
void attach(GMainContext *context);
|
|
void pause();
|
|
void resume();
|
|
void increment();
|
|
bool isPaused() const;
|
|
void setThreadTimeout(const gint intervalInMs);
|
|
|
|
private:
|
|
pthread_t threadId;
|
|
gint paused; // accessed by multiple threads
|
|
int deadlockTimerSourceTimeoutMs;
|
|
gint deadlockThreadTimerSourceTimeoutMs; // accessed by multiple threads
|
|
gint watchdogCount; // accessed by multiple threads
|
|
GSource* deadlockTimerSource;
|
|
GMainContext* threadMainContext;
|
|
GMainLoop* threadMainLoop;
|
|
GSource* threadTimeoutSource;
|
|
static gboolean deadlockTimerCallback(gpointer data);
|
|
static void* deadlockThread(void *arg);
|
|
static gboolean deadlockThreadTimerCallback(gpointer data);
|
|
};
|
|
|
|
class YapServerPriv
|
|
{
|
|
public:
|
|
|
|
char socketPath[kMaxPathLen];
|
|
int socketFd;
|
|
GMainLoop* mainLoop;
|
|
GMainContext* mainCtxt;
|
|
GIOChannel* ioChannel;
|
|
GSource* ioSource;
|
|
|
|
static gboolean ioCallback(GIOChannel* channel, GIOCondition condition, void* data);
|
|
|
|
YapServerDeadlockPriv* deadlockDetector;
|
|
};
|
|
|
|
YapServerDeadlockPriv::YapServerDeadlockPriv(int deadlockTimeoutMs)
|
|
: threadId(0)
|
|
, paused(0)
|
|
, watchdogCount(0)
|
|
, deadlockTimerSource(0)
|
|
, threadMainContext(0)
|
|
, threadMainLoop(0)
|
|
, threadTimeoutSource(0)
|
|
{
|
|
if (deadlockTimeoutMs < kDeadlockTimeoutMinSecs * 1000) {
|
|
g_warning("Attempted to set deadlock timeout to less than %d second minimum; "
|
|
"using %d second minimum", kDeadlockTimeoutMinSecs, kDeadlockTimeoutMinSecs);
|
|
deadlockTimeoutMs = kDeadlockTimeoutMinSecs * 1000;
|
|
}
|
|
|
|
// The deadlock timer running in its separate thread fires more slowly than the
|
|
// one on the mainloop so that we avoid timer aliasing
|
|
deadlockThreadTimerSourceTimeoutMs = deadlockTimeoutMs; // Thread hasn't started yet, okay to set directly.
|
|
deadlockTimerSourceTimeoutMs = deadlockTimeoutMs / 2;
|
|
}
|
|
|
|
YapServerDeadlockPriv::~YapServerDeadlockPriv()
|
|
{
|
|
// Both members stay zero until the watchdog THREAD starts, and it only starts on the
|
|
// first deadlockTimerCallback — half the timeout, so ~7.5 s by default. A server torn
|
|
// down before then would quit a null loop and join thread 0.
|
|
if (threadMainLoop) g_main_loop_quit(threadMainLoop);
|
|
if (threadId) pthread_join(threadId, NULL);
|
|
}
|
|
|
|
void YapServerDeadlockPriv::attach(GMainContext *context)
|
|
{
|
|
deadlockTimerSource = g_timeout_source_new(deadlockTimerSourceTimeoutMs);
|
|
g_source_set_callback(deadlockTimerSource, deadlockTimerCallback, this, NULL);
|
|
g_source_set_priority(deadlockTimerSource, G_PRIORITY_HIGH);
|
|
g_source_attach(deadlockTimerSource, context);
|
|
}
|
|
|
|
void YapServerDeadlockPriv::pause()
|
|
{
|
|
g_atomic_int_set(&paused, 1);
|
|
}
|
|
|
|
void YapServerDeadlockPriv::resume()
|
|
{
|
|
g_atomic_int_set(&paused, 0);
|
|
}
|
|
|
|
void YapServerDeadlockPriv::increment()
|
|
{
|
|
g_atomic_int_inc(&watchdogCount);
|
|
}
|
|
|
|
bool YapServerDeadlockPriv::isPaused() const
|
|
{
|
|
return g_atomic_int_get(&paused) ? true : false;
|
|
}
|
|
|
|
void YapServerDeadlockPriv::setThreadTimeout(const gint intervalInMs)
|
|
{
|
|
if (g_atomic_int_get(&deadlockThreadTimerSourceTimeoutMs) < intervalInMs) {
|
|
|
|
g_atomic_int_set(&deadlockThreadTimerSourceTimeoutMs, intervalInMs);
|
|
}
|
|
}
|
|
|
|
gboolean YapServerDeadlockPriv::deadlockTimerCallback(gpointer data)
|
|
{
|
|
static bool firstCallback = true;
|
|
|
|
YapServerDeadlockPriv* d = static_cast<YapServerDeadlockPriv*>(data);
|
|
g_atomic_int_inc(&d->watchdogCount);
|
|
|
|
//g_debug("deadlockTimerCallback!, count: %d", (int)d->watchdogCount);
|
|
|
|
if (firstCallback) {
|
|
pthread_create(&d->threadId, NULL, d->deadlockThread, d);
|
|
firstCallback = false;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean YapServerDeadlockPriv::deadlockThreadTimerCallback(gpointer data)
|
|
{
|
|
YapServerDeadlockPriv* d = static_cast<YapServerDeadlockPriv*>(data);
|
|
static gint lastCountSeen = 0;
|
|
static gint lastInterval = -1;
|
|
|
|
const gint interval = g_atomic_int_get(&(d->deadlockThreadTimerSourceTimeoutMs));
|
|
|
|
if (lastInterval < interval) {
|
|
|
|
if (lastInterval == -1) {
|
|
lastInterval = interval;
|
|
}
|
|
else {
|
|
lastInterval = interval;
|
|
deadlockThread(d);
|
|
return FALSE; // since the interval has changed, wait for next time out to run test;
|
|
}
|
|
}
|
|
|
|
// The timers used by glib are monotonic, but count time during
|
|
// suspend (see NOV-112456). As a result, both this callback and the mainloop callback
|
|
// could fire at the same time and incorrectly think there is a deadlock. To avoid
|
|
// this we check the time that has elapsed with the monotonic clock, which doesn't
|
|
// count time during suspend.
|
|
static int64_t lastTimeMs = 0;
|
|
struct timespec now;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
int64_t nowMs = now.tv_sec * 1000 + now.tv_nsec / 1000000;
|
|
if (nowMs - lastTimeMs < interval) {
|
|
g_warning("Deadlock detection timer fired too early");
|
|
return TRUE;
|
|
}
|
|
lastTimeMs = nowMs;
|
|
|
|
if (d->isPaused()) {
|
|
//g_debug("deadlockThreadTimerCallback: paused");
|
|
return TRUE;
|
|
}
|
|
|
|
gint curCount = g_atomic_int_get(&d->watchdogCount);
|
|
|
|
//g_debug("deadlockThreadTimerCallback: curCount: %d, lastCountSeen: %d, interval: %d", (int)curCount, (int)lastCountSeen, (int)interval);
|
|
|
|
if (curCount <= lastCountSeen && curCount != 0) {
|
|
// we're wedged so we give up and crash
|
|
g_critical("Deadlock detected; aborting!");
|
|
abort();
|
|
}
|
|
|
|
lastCountSeen = curCount;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void* YapServerDeadlockPriv::deadlockThread(void *arg)
|
|
{
|
|
YapServerDeadlockPriv* d = static_cast<YapServerDeadlockPriv*>(arg);
|
|
|
|
if (d->threadTimeoutSource) {
|
|
|
|
g_source_unref(d->threadTimeoutSource);
|
|
g_source_destroy(d->threadTimeoutSource);
|
|
d->threadTimeoutSource = 0;
|
|
}
|
|
|
|
if (!d->threadMainContext) {
|
|
d->threadMainContext = g_main_context_new();
|
|
}
|
|
|
|
if (!d->threadMainLoop) {
|
|
d->threadMainLoop = g_main_loop_new(d->threadMainContext, FALSE);
|
|
}
|
|
d->threadTimeoutSource = g_timeout_source_new(g_atomic_int_get (&(d->deadlockThreadTimerSourceTimeoutMs)));
|
|
g_source_set_callback(d->threadTimeoutSource, d->deadlockThreadTimerCallback, d, NULL);
|
|
g_source_attach(d->threadTimeoutSource, d->threadMainContext);
|
|
|
|
g_main_loop_run(d->threadMainLoop);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
gboolean YapServerPriv::ioCallback(GIOChannel* channel, GIOCondition condition, void* data)
|
|
{
|
|
YapServer* server = static_cast<YapServer*>(data);
|
|
server->ioCallback(channel, condition);
|
|
return TRUE;
|
|
}
|
|
|
|
YapServer::YapServer(const char* name)
|
|
{
|
|
d = new YapServerPriv;
|
|
d->socketPath[0] = '\0';
|
|
d->socketFd = -1;
|
|
d->mainLoop = 0;
|
|
d->mainCtxt = 0;
|
|
d->ioSource = 0;
|
|
// Left uninitialized upstream. YapServerPriv is allocated with a plain `new`, so
|
|
// this member holds indeterminate memory, and run()'s default deadlockTimeoutMs of
|
|
// -1 means the detector is normally NEVER created — yet ~YapServer does
|
|
// `if (d->deadlockDetector) delete d->deadlockDetector`. Upstream never noticed
|
|
// because its server never returned from run(); ours now does, on SIGTERM, and the
|
|
// garbage pointer was reliably non-null: every clean shutdown SIGSEGV'd inside
|
|
// ~YapServerDeadlockPriv (measured 2026-08-04, four for four).
|
|
d->deadlockDetector = 0;
|
|
|
|
::snprintf(d->socketPath, G_N_ELEMENTS(d->socketPath), "%s%s", kSocketPathPrefix, name);
|
|
::unlink(d->socketPath);
|
|
|
|
init();
|
|
}
|
|
|
|
YapServer::~YapServer()
|
|
{
|
|
if (d->deadlockDetector) {
|
|
delete d->deadlockDetector;
|
|
}
|
|
delete d;
|
|
}
|
|
|
|
void YapServer::init()
|
|
{
|
|
struct sockaddr_un socketAddr;
|
|
|
|
d->socketFd = ::socket(PF_LOCAL, SOCK_STREAM, 0);
|
|
if (d->socketFd < 0) {
|
|
fprintf(stderr, "YAP: Failed to create socket: %s\n", strerror(errno));
|
|
return;
|
|
}
|
|
|
|
socketAddr.sun_family = AF_LOCAL;
|
|
::strncpy(socketAddr.sun_path, d->socketPath, G_N_ELEMENTS(socketAddr.sun_path));
|
|
socketAddr.sun_path[G_N_ELEMENTS(socketAddr.sun_path)-1] = '\0';
|
|
if (::bind(d->socketFd, (struct sockaddr*) &socketAddr, SUN_LEN(&socketAddr)) != 0) {
|
|
fprintf(stderr, "YAP: Failed to bind socket: %s\n", strerror(errno));
|
|
d->socketFd = -1;
|
|
return;
|
|
}
|
|
|
|
if (::listen(d->socketFd, kMaxConnections) != 0) {
|
|
fprintf(stderr, "YAP: Failed to listen on socket: %s\n", strerror(errno));
|
|
d->socketFd = -1;
|
|
return;
|
|
}
|
|
|
|
d->mainCtxt = g_main_context_default();
|
|
d->mainLoop = g_main_loop_new(d->mainCtxt, TRUE);
|
|
|
|
d->ioChannel = g_io_channel_unix_new(d->socketFd);
|
|
d->ioSource = g_io_create_watch(d->ioChannel, (GIOCondition) (G_IO_IN | G_IO_HUP));
|
|
|
|
g_source_set_callback(d->ioSource, (GSourceFunc) YapServerPriv::ioCallback, this, NULL);
|
|
g_source_attach(d->ioSource, d->mainCtxt);
|
|
}
|
|
|
|
GMainLoop* YapServer::mainLoop() const
|
|
{
|
|
return d->mainLoop;
|
|
}
|
|
|
|
void YapServer::run(int deadlockTimeoutMs)
|
|
{
|
|
if (!d->mainLoop)
|
|
return;
|
|
|
|
if (deadlockTimeoutMs > 0) {
|
|
d->deadlockDetector = new YapServerDeadlockPriv(deadlockTimeoutMs);
|
|
if (d->mainCtxt) {
|
|
d->deadlockDetector->attach(d->mainCtxt);
|
|
}
|
|
}
|
|
|
|
g_main_loop_run(d->mainLoop); // Jihad: GLib loop instead of QApplication::exec()
|
|
}
|
|
|
|
void YapServer::pauseDeadlockDetection()
|
|
{
|
|
if (d->deadlockDetector) {
|
|
d->deadlockDetector->pause();
|
|
}
|
|
}
|
|
|
|
void YapServer::resumeDeadlockDetection()
|
|
{
|
|
if (d->deadlockDetector) {
|
|
d->deadlockDetector->resume();
|
|
}
|
|
}
|
|
|
|
void YapServer::incrementDeadlockCounter()
|
|
{
|
|
if (d->deadlockDetector) {
|
|
d->deadlockDetector->increment();
|
|
}
|
|
}
|
|
|
|
void YapServer::setDeadlockDetectionInterval(const int IntervalInSeconds) {
|
|
|
|
if (d->deadlockDetector) {
|
|
d->deadlockDetector->setThreadTimeout(IntervalInSeconds * 1000);
|
|
}
|
|
}
|
|
|
|
void YapServer::ioCallback(GIOChannel* channel, GIOCondition condition)
|
|
{
|
|
struct sockaddr_un socketAddr;
|
|
socklen_t socketAddrLen;
|
|
int socketFd = -1;
|
|
int16_t msgSocketPathLen = 0;
|
|
char* msgSocketPath = 0;
|
|
int16_t msgSocketPostfixLen = 0;
|
|
char* msgSocketPostfix = 0;
|
|
|
|
memset(&socketAddr, 0, sizeof(socketAddr));
|
|
memset(&socketAddrLen, 0, sizeof(socketAddrLen));
|
|
|
|
socketFd = ::accept(d->socketFd, (struct sockaddr*) &socketAddr, &socketAddrLen);
|
|
if (-1 == socketFd) {
|
|
fprintf(stderr, "YAP: Failed to accept inbound connection.\n");
|
|
goto Detached;
|
|
}
|
|
if (!readSocket(socketFd, (char*) &msgSocketPathLen, 2)) {
|
|
fprintf(stderr, "YAP: Failed to read message socket name length\n");
|
|
goto Detached;
|
|
}
|
|
msgSocketPathLen = bswap_16(msgSocketPathLen);
|
|
// msgSocketPathLen is signed int16: reject negatives (a hostile/corrupt 0xFFFF became -1,
|
|
// making malloc(0) then an out-of-bounds write at [-1]) and cap to a sane socket-path length.
|
|
if (msgSocketPathLen < 0 || msgSocketPathLen > kMaxPathLen) {
|
|
fprintf(stderr, "YAP: bad message socket name length %d\n", msgSocketPathLen);
|
|
goto Detached;
|
|
}
|
|
|
|
msgSocketPath = (char*) malloc(msgSocketPathLen + 1);
|
|
if (!msgSocketPath) { fprintf(stderr, "YAP: OOM socket name\n"); goto Detached; }
|
|
if (!readSocket(socketFd, msgSocketPath, msgSocketPathLen)) {
|
|
fprintf(stderr, "YAP: Failed to read message socket name\n");
|
|
goto Detached;
|
|
}
|
|
msgSocketPath[msgSocketPathLen] = 0;
|
|
|
|
::chmod( msgSocketPath, S_IRWXU | S_IRWXG | S_IRWXO );
|
|
|
|
if (!readSocket(socketFd, (char*) &msgSocketPostfixLen, 2)) {
|
|
fprintf(stderr, "YAP: Failed to read message socket name length\n");
|
|
goto Detached;
|
|
}
|
|
msgSocketPostfixLen = bswap_16(msgSocketPostfixLen);
|
|
if (msgSocketPostfixLen < 0 || msgSocketPostfixLen > kMaxPathLen) {
|
|
fprintf(stderr, "YAP: bad message postfix length %d\n", msgSocketPostfixLen);
|
|
goto Detached;
|
|
}
|
|
|
|
msgSocketPostfix = (char*) malloc(msgSocketPostfixLen + 1);
|
|
if (!msgSocketPostfix) { fprintf(stderr, "YAP: OOM postfix name\n"); goto Detached; }
|
|
if (!readSocket(socketFd, msgSocketPostfix, msgSocketPostfixLen)) {
|
|
fprintf(stderr, "YAP: Failed to read message postifx name\n");
|
|
goto Detached;
|
|
}
|
|
msgSocketPostfix[msgSocketPostfixLen] = 0;
|
|
|
|
YapProxy* proxy;
|
|
proxy = new YapProxy(this, socketFd, msgSocketPath, msgSocketPostfix);
|
|
clientConnected(proxy);
|
|
|
|
free(msgSocketPath);
|
|
free(msgSocketPostfix);
|
|
return;
|
|
|
|
Detached:
|
|
|
|
if (msgSocketPath)
|
|
free(msgSocketPath);
|
|
if (msgSocketPostfix)
|
|
free(msgSocketPostfix);
|
|
if (-1 != socketFd) {
|
|
close(socketFd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new detached YapProxy that will only record outbound messages and not send
|
|
* them.
|
|
*
|
|
* @return A new YapProxy. The caller now owns this object.
|
|
*/
|
|
YapProxy* YapServer::createRecordProxy()
|
|
{
|
|
return new YapProxy(this, -1, NULL, NULL);
|
|
}
|
|
|
|
void YapServer::deleteRecordProxy(YapProxy* proxy)
|
|
{
|
|
if (proxy != NULL) {
|
|
assert(proxy->isRecordProxy());
|
|
delete proxy;
|
|
}
|
|
}
|
|
|
|
bool YapServer::readSocket(int fd, char* buf, int len)
|
|
{
|
|
int index = 0;
|
|
|
|
while (len > 0) {
|
|
int count = ::read(fd, &buf[index], len);
|
|
if (count <= 0) {
|
|
if (errno == EAGAIN || errno == EINTR)
|
|
continue;
|
|
else {
|
|
fprintf(stderr, "Failed to read from socket. Error: %d, %s\n", errno, strerror(errno));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
index += count;
|
|
len -= count;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int YapServer::serverSocketFd() const
|
|
{
|
|
return d->socketFd;
|
|
}
|