blob: e6b8e71f0cb920e717751fd8f12cdc4a9ba42e8d [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include "embed.h"
#include "log.h"
#include "api_control.h"
#include "core.h"
static ProgramExitStatus
brlttyRun (void) {
while (brlttyWait(INT_MAX));
return PROG_EXIT_SUCCESS;
}
#ifdef __MINGW32__
#include "utf8.h"
static SERVICE_STATUS_HANDLE serviceStatusHandle;
static DWORD serviceState;
static ProgramExitStatus serviceExitStatus;
static BOOL
setServiceState (DWORD state, DWORD exitStatus, const char *name) {
SERVICE_STATUS status = {
.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,
.dwCurrentState = state,
.dwWin32ExitCode = (exitStatus != PROG_EXIT_SUCCESS)? ERROR_SERVICE_SPECIFIC_ERROR: NO_ERROR,
.dwServiceSpecificExitCode = exitStatus
};
switch (status.dwCurrentState) {
default:
status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
case SERVICE_START_PENDING:
case SERVICE_STOP_PENDING:
case SERVICE_STOPPED:
break;
}
switch (status.dwCurrentState) {
case SERVICE_START_PENDING:
case SERVICE_PAUSE_PENDING:
case SERVICE_CONTINUE_PENDING:
case SERVICE_STOP_PENDING:
status.dwWaitHint = 10000;
status.dwCheckPoint = 0;
default:
break;
}
serviceState = state;
if (SetServiceStatus(serviceStatusHandle, &status)) return 1;
logWindowsSystemError(name);
return 0;
}
#define SET_SERVICE_STATE(state,code) setServiceState(state, code, #state)
static void WINAPI
serviceControlHandler (DWORD code) {
switch (code) {
case SERVICE_CONTROL_STOP:
SET_SERVICE_STATE(SERVICE_STOP_PENDING, PROG_EXIT_SUCCESS);
raise(SIGTERM);
break;
case SERVICE_CONTROL_PAUSE:
SET_SERVICE_STATE(SERVICE_PAUSE_PENDING, PROG_EXIT_SUCCESS);
api.suspendDriver();
SET_SERVICE_STATE(SERVICE_PAUSED, PROG_EXIT_SUCCESS);
break;
case SERVICE_CONTROL_CONTINUE:
SET_SERVICE_STATE(SERVICE_CONTINUE_PENDING, PROG_EXIT_SUCCESS);
if (api.resumeDriver()) {
SET_SERVICE_STATE(SERVICE_RUNNING, PROG_EXIT_SUCCESS);
} else {
SET_SERVICE_STATE(SERVICE_PAUSED, PROG_EXIT_SUCCESS);
}
break;
default:
logMessage(LOG_WARNING, "unexpected service control code: %lu", code);
break;
}
}
static void
exitService (void) {
SET_SERVICE_STATE(SERVICE_STOPPED, PROG_EXIT_SUCCESS);
}
static char **
getCommandLineArguments (DWORD *argc) {
int count;
LPWSTR *arguments = CommandLineToArgvW(GetCommandLineW(), &count);
char **argv;
if ((argv = malloc(ARRAY_SIZE(argv, count+1)))) {
unsigned int index = 0;
while (index < count) {
const wchar_t *argument = arguments[index];
if (!(argv[index] = getUtf8FromWchars(argument, wcslen(argument), NULL))) {
logMallocError();
break;
}
index += 1;
}
LocalFree(arguments);
arguments = NULL;
if (index == count) {
argv[count] = NULL;
*argc = count;
return argv;
}
while (index > 0) free(argv[index-=1]);
free(argv);
} else {
logMallocError();
}
return NULL;
}
static void WINAPI
serviceMain (DWORD argc, LPSTR *argv) {
atexit(exitService);
if ((argv = getCommandLineArguments(&argc))) {
if ((serviceStatusHandle = RegisterServiceCtrlHandler("", serviceControlHandler))) {
if ((SET_SERVICE_STATE(SERVICE_START_PENDING, PROG_EXIT_SUCCESS))) {
if ((serviceExitStatus = brlttyConstruct(argc, argv)) == PROG_EXIT_SUCCESS) {
if ((SET_SERVICE_STATE(SERVICE_RUNNING, PROG_EXIT_SUCCESS))) {
serviceExitStatus = brlttyRun();
} else {
serviceExitStatus = PROG_EXIT_FATAL;
}
brlttyDestruct();
} else if (serviceExitStatus == PROG_EXIT_FORCE) {
serviceExitStatus = PROG_EXIT_SUCCESS;
}
SET_SERVICE_STATE(SERVICE_STOPPED, serviceExitStatus);
}
} else {
logWindowsSystemError("RegisterServiceCtrlHandler");
}
}
}
#endif /* __MINGW32__ */
int
main (int argc, char *argv[]) {
#ifdef __MINGW32__
{
static SERVICE_TABLE_ENTRY serviceTable[] = {
{ .lpServiceName="", .lpServiceProc=serviceMain },
{}
};
isWindowsService = 1;
if (StartServiceCtrlDispatcher(serviceTable)) return serviceExitStatus;
isWindowsService = 0;
if (GetLastError() != ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
logWindowsSystemError("StartServiceCtrlDispatcher");
return PROG_EXIT_FATAL;
}
}
#endif /* __MINGW32__ */
#ifdef INIT_PATH
#define INIT_NAME "init"
if ((getpid() == 1) || strstr(argv[0], "linuxrc")) {
fprintf(stderr, gettext("\"%s\" started as \"%s\"\n"), PACKAGE_NAME, argv[0]);
fflush(stderr);
switch (fork()) {
case -1: /* failed */
fprintf(stderr, gettext("fork of \"%s\" failed: %s\n"),
PACKAGE_NAME, strerror(errno));
fflush(stderr);
default: /* parent */
fprintf(stderr, gettext("executing \"%s\" (from \"%s\")\n"), INIT_NAME, INIT_PATH);
fflush(stderr);
executeInit:
execv(INIT_PATH, argv);
/* execv() shouldn't return */
fprintf(stderr, gettext("execution of \"%s\" failed: %s\n"), INIT_NAME, strerror(errno));
fflush(stderr);
exit(1);
case 0: { /* child */
static char *arguments[] = {"brltty", "-E", "-n", "-e", "-linfo", NULL};
argv = arguments;
argc = ARRAY_COUNT(arguments) - 1;
break;
}
}
} else if (!strstr(argv[0], "brltty")) {
/*
* If we are substituting the real init binary, then we may consider
* when someone might want to call that binary even when pid != 1.
* One example is /sbin/telinit which is a symlink to /sbin/init.
*/
goto executeInit;
}
#endif /* INIT_PATH */
#ifdef STDERR_PATH
freopen(STDERR_PATH, "a", stderr);
#endif /* STDERR_PATH */
{
ProgramExitStatus exitStatus = brlttyConstruct(argc, argv);
if (exitStatus == PROG_EXIT_SUCCESS) {
exitStatus = brlttyRun();
brlttyDestruct();
} else if (exitStatus == PROG_EXIT_FORCE) {
exitStatus = PROG_EXIT_SUCCESS;
}
return exitStatus;
}
}