Files

1665 lines
39 KiB
C

/* Platform isolation convenience functions */
/*
* Argyll Color Management System
*
* Author: Graeme W. Gill
* Date: 28/9/97
*
* Copyright 1997 - 2013 Graeme W. Gill
* All rights reserved.
*
* This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
* see the License2.txt file for licencing details.
*/
#define USE_BEGINTHREAD /* [def] hyper-threading doesn't work on VC++6 otherwise. */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>
#include <ctype.h>
#ifdef NT
/* Set minimum OS target as XP */
# if !defined(WINVER) || WINVER < 0x0501
# if defined(WINVER)
# undef WINVER
# endif
# define WINVER 0x0501
# endif
# if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0501
# if defined(_WIN32_WINNT)
# undef _WIN32_WINNT
# endif
# define _WIN32_WINNT 0x0501
# endif
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#include <conio.h>
#include <tlhelp32.h>
#include <direct.h>
#include <process.h>
#endif
#if defined(UNIX)
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <pwd.h>
/* select() defined, but not poll(), so emulate poll() */
#if defined(FD_CLR) && !defined(POLLIN)
#include "pollem.h"
#define poll_x pollem
#else
#include <sys/poll.h> /* Else assume poll() is native */
#define poll_x poll
#endif
#endif
#ifndef SALONEINSTLIB
#include "copyright.h"
#include "aconfig.h"
#else
#include "sa_config.h"
#endif
#include "numsup.h"
#include "cgats.h"
#include "xspect.h"
#include "insttypes.h"
#include "conv.h"
#include "icoms.h"
/*
MAC_OS_X_VERSION_MIN_REQUIRED
is set to the earliest OS version the code should compile in.
This is the usual macro to enable early vs. later OS API use.
MAC_OS_X_VERSION_MAX_ALLOWED
shows the latest API's that the SDK is making available.
API's later than MAC_OS_X_VERSION_MIN_REQUIRED need to be
detected at run time (i.e soft linking).
This is usually used to allow for SDK declaration changes.
The version should be 4 digits from 1000 to 1090 for 10.0 to 10.9,
and 6 digits from 101000 and beyond for 10.1 and beyond.
*/
#ifdef UNIX_APPLE
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 101200
# define OBJC_SILENCE_GC_DEPRECATIONS 1 /* Don't warn about objc_registerThreadWithCollector() */
#endif
//#include <stdbool.h>
#include <sys/sysctl.h>
#include <sys/param.h>
#include <Carbon/Carbon.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/serial/IOSerialKeys.h>
#include <IOKit/IOBSD.h>
#include <mach/mach_init.h>
#include <mach/task_policy.h>
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
# include <AudioToolbox/AudioServices.h>
#endif
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
# include <objc/objc-auto.h>
#endif
#endif /* UNIX_APPLE */
#undef DEBUG
#ifdef DEBUG
# define DBG(xx) a1logd(g_log, 0, xx )
# define DBGA g_log, 0 /* First argument to DBGF() */
# define DBGF(xx) a1logd xx
#else
# define DBG(xx)
# define DBGF(xx)
#endif
#ifdef __BORLANDC__
#define _kbhit kbhit
#endif
/* ============================================================= */
/* MS WINDOWS */
/* ============================================================= */
#ifdef NT
/* Debug function: return Windows last error as a string */
char *GetLastErrorMessage(void) {
static char tmp[512];
char *s, *d;
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), 0, tmp, sizeof(tmp) - 1, NULL);
/* Remove any \n or \r .. */
for (s = d = tmp; *s != '\000'; s++) {
if (d != s)
*d = *s;
if (*d != '\n' && *d != '\r')
d++;
}
*d = '\000';
return tmp;
}
/* Get the next console character. Return 0 if none is available. */
/* Wait for one if wait is nz */
/* (If not_interactive set, return next stdin character if available, but discard cr or lf) */
static int con_char(int wait) {
//fprintf(stderr,"~1 con_char not_interactive %d wait %d\n",not_interactive,wait);
if (not_interactive) { /* Can't assume that it's the console */
HANDLE stdinh;
char buf[10] = { 0 };
DWORD bread;
int i, rv = 0;
if ((stdinh = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
return 0;
}
if (stdin_type == FILE_TYPE_CHAR) {
//fprintf(stderr,"~1 wait %d console:\n",wait);
if (wait || _kbhit() != 0) {
for (bread = 0; bread < 10;) {
int c = _getch();
buf[bread++] = c;
//fprintf(stderr,"~1 read 0x%x\n" ,c);
if (c == '\n' || c == '\r' || c == 0x3) {
break;
}
}
//fprintf(stderr,"~1 read %d: ",bread);
//for (i = 0; i < bread; i++)
//fprintf(stderr," 0x%x",buf[i]);
//fprintf(stderr,"\n");
if (bread > 0) {
rv = buf[0];
}
}
/* We assume pipe has been set to NOWAIT mode. */
} else if (stdin_type == FILE_TYPE_PIPE) {
int i, bib;
DWORD bytes_avail = 0;
/* Check available bytes in pipe before blocking */
if (!PeekNamedPipe(stdinh, NULL, 0, NULL, &bytes_avail, NULL) || bytes_avail == 0) {
if (!wait) {
return 0;
}
}
for (bib = 0; bib < 10;) {
if (!wait) {
if (!PeekNamedPipe(stdinh, NULL, 0, NULL, &bytes_avail, NULL) || bytes_avail == 0) {
break;
}
}
if ((!ReadFile(stdinh, buf + bib, 10 - bib, &bread, NULL) || bread == 0)
&& !wait) {
break;
}
bib += bread;
for (i = 0; i < bib; i++) {
if (buf[i] == '\n' || buf[i] == '\r' || buf[i] == 0x3) {
break;
}
}
if (i < bib) {
break; /* Found '\n' */
}
Sleep(100); /* Wait for a line ending in '\n' */
}
rv = buf[0];
/* Assume a file. This will have very limited functionality. */
/* We assume that we can't poll for console input, but will */
/* read from the file on blocking calls. */
} else {
if (wait) { /* Only attempt a read if this is blocking */
if (ReadFile(stdinh, buf, 10, &bread, NULL) && bread > 0)
rv = buf[0];
}
}
//fprintf(stderr,"~1 returning 0x%x\n",rv);
return rv;
}
/* Assume it's the interactive console */
if (wait || _kbhit() != 0) {
int c = _getch();
return c;
}
return 0;
}
/* wait for and then return the next character from the keyboard */
/* (If not_interactive set, wait for next stdin character but discard cr or lf) */
int next_con_char(void) {
return con_char(1);
}
/* If there is one, return the next character from the keyboard, else return 0 */
/* (If not_interactive set, return next stdin character if available, but discard cr or lf) */
int poll_con_char(void) {
return con_char(0);
}
/* If interactive, suck all characters from the keyboard */
void empty_con_chars(void) {
if (not_interactive) /* Don't suck all the characters from stdin */
return;
Sleep(50); /* _kbhit seems to have a bug */
while (_kbhit()) {
if (next_con_char() == 0x3) /* ^C Safety */
break;
}
}
/* Do an fgets from stdin, taking account of possible interference from */
/* non-interactive mode. */
char *con_fgets(char *buf, int size) {
if (not_interactive) {
char *rv = NULL;
HANDLE stdinh;
int i, bib;
//fprintf(stderr,"~1 top of pipe\n");
if ((stdinh = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) {
return NULL;
}
for (bib = 0; bib < size;) {
DWORD bread;
if (ReadFile(stdinh, buf + bib, size - bib, &bread, NULL) && bread > 0) {
bib += bread;
for (i = 0; i < bib; i++) {
if (buf[i] == '\n' || buf[i] == '\r') {
break;
}
}
if (i < bib) {
buf[i] = '\000';
break; /* Found '\n' */
}
}
Sleep(100); /* Wait for a line ending in '\n' */
}
return buf;
}
return fgets(buf, size, stdin);
}
/* Sleep for the given number of seconds */
void sleep(unsigned int secs) {
Sleep(secs * 1000);
}
static athread *beep_thread = NULL;
static int beep_delay;
static int beep_freq;
static int beep_msec;
/* Delayed beep handler */
static int delayed_beep(void *pp) {
msec_sleep(beep_delay);
a1logd(g_log,8, "msec_beep activate\n");
Beep(beep_freq, beep_msec);
return 0;
}
/* Activate the system beeper */
void msec_beep(int delay, int freq, int msec) {
a1logd(g_log,8, "msec_beep %d msec\n",msec);
if (delay > 0) {
if (beep_thread != NULL)
beep_thread->del(beep_thread);
beep_delay = delay;
beep_freq = freq;
beep_msec = msec;
if ((beep_thread = new_athread(delayed_beep, NULL)) == NULL)
a1logw(g_log, "msec_beep: Delayed beep failed to create thread\n");
} else {
a1logd(g_log,8, "msec_beep activate\n");
Beep(freq, msec);
}
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Hack to deal with a possibly statically initilized amutex on MSWin. */
/* We statically initilize the CRITICAL_SECTION with a sentinel */
/* LockCount value, and if this value is set, use an atomic */
/* lock to do the initilization before any other operation on it. */
/* (A more robust trick may be to declare the amutex as a pointer */
/* and create it if it is NULL using a InterlockedCompareExchange()) */
int amutex_chk(CRITICAL_SECTION *lock) {
if (lock->LockCount == amutex_static_LockCount) { /* Not initialized */
static volatile LONG ilock = 0;
/* Try ilock */
// if (InterlockedCompareExchange((LONG **)&ilock, (LONG *)1, (LONG *)0) == 0)
if (InterlockedCompareExchange(&ilock, (LONG)1, (LONG)0) == 0)
{
/* We locked it */
if (lock->LockCount == amutex_static_LockCount) { /* Still not inited */
InitializeCriticalSection(lock); /* So we init it */
}
ilock = 0; /* We're done */
} else { /* Wait for other thread to finish init */
while(ilock != 0) {
msec_sleep(1);
}
}
}
return 0;
}
int acond_timedwait_imp(HANDLE cond, CRITICAL_SECTION *lock, int msec) {
int rv;
LeaveCriticalSection(lock);
rv = WaitForSingleObject(cond, msec);
EnterCriticalSection(lock);
if (rv == WAIT_TIMEOUT)
return 1;
if (rv != WAIT_OBJECT_0)
return 2;
return 0;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#ifdef NEVER /* Not currently needed, or effective */
/* Set the current threads priority */
int set_interactive_priority() {
if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) == 0)
return 1;
return 0;
}
int set_normal_priority() {
if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL) == 0)
return 1;
return 0;
}
#endif /* NEVER */
/* If reusable, start a stopped thread. NOP if not reusable */
static void athread_start(
athread *p
) {
DBG("athread_start called\n");
if (!p->reusable)
return;
/* Signal to the thread that it should start */
amutex_lock(p->startm);
p->startv = 1;
acond_signal(p->startc);
amutex_unlock(p->startm);
}
/* If reusable, change the task and then start a stopped thread. NOP if not reusable */
static void athread_start_task(
athread *p,
int (*function)(void *context),
void *context
) {
DBG("athread_start_task called\n");
if (!p->reusable)
return;
/* Change to this task */
p->function = function;
p->context = context;
/* Signal to the thread that it should start */
amutex_lock(p->startm);
p->startv = 1;
acond_signal(p->startc);
amutex_unlock(p->startm);
}
/* If reusable, wait for the thread to stop after starting it. */
/* Return the result. NOP if not reusable */
static int athread_wait_stop(
athread *p
) {
DBG("athread_wait_stop called\n");
if (!p->reusable)
return p->result;
/* Wait for thread to stop */
amutex_lock(p->stopm);
while (p->stopv == 0)
acond_wait(p->stopc, p->stopm);
p->stopv = 0;
amutex_unlock(p->stopm);
return p->result;
}
/* Wait for the thread to exit. Return the result */
static int athread_wait(struct _athread *p) {
if (p->reusable) {
p->dofinish = 1;
athread_start(p); /* Wake thread up to cause it to exit */
}
if (p->joined)
return p->result;
WaitForSingleObject(p->th, INFINITE);
p->joined = 1;
return p->result;
}
/* Forcefully terminate the thread */
static void athread_terminate(
athread *p
) {
DBG("athread_terminate called\n");
if (p == NULL || p->joined)
return;
if (p->th != NULL) {
DBG("athread_del calling TerminateThread()\n");
TerminateThread(p->th, (DWORD)-1);
// WaitForSingleObject(p->th, INFINITE); // Don't join in case it doesn't terminate
}
p->joined = 1; /* Skip any join afterwards */
}
/* Free the thread */
static void athread_del(
athread *p
) {
DBG("athread_del called\n");
if (p == NULL)
return;
if (p->th != NULL) {
if (p->reusable && !p->joined && !p->dofinish) {
p->dofinish = 1;
athread_start(p); /* Wake thread up to cause it to exit */
}
if (!p->joined)
WaitForSingleObject(p->th, INFINITE);
CloseHandle(p->th);
}
if (p->reusable) {
acond_del(p->startc);
amutex_del(p->startm);
acond_del(p->stopc);
amutex_del(p->stopm);
}
free(p);
}
/* _beginthreadex inits the CRT properly, wheras CreateThread doesn't on VC++6 */
#ifdef USE_BEGINTHREAD
/* Thread function */
static unsigned int __stdcall threadproc(
void *lpParameter
) {
#else
DWORD WINAPI threadproc(
LPVOID lpParameter
) {
#endif
athread *p = (athread *)lpParameter;
if (p->reusable) { /* Run over and over */
for (;;) {
/* Wait for client to start us */
amutex_lock(p->startm);
while (p->startv == 0)
acond_wait(p->startc, p->startm);
p->startv = 0;
amutex_unlock(p->startm);
if (p->dofinish)
break;
p->result = p->function(p->context);
if (p->dofinish)
break;
/* Signal to the client that we've stopped */
amutex_lock(p->stopm);
p->stopv = 1;
acond_signal(p->stopc);
amutex_unlock(p->stopm);
}
} else {
p->result = p->function(p->context);
}
// p->finished = 1;
return 0;
}
athread *new_athread_reusable(
int (*function)(void *context),
void *context,
int reusable
) {
athread *p = NULL;
DBG("new_athread called\n");
if ((p = (athread *)calloc(sizeof(athread), 1)) == NULL) {
a1loge(g_log, 1, "new_athread: calloc failed\n");
return NULL;
}
p->reusable = reusable;
if (p->reusable) {
amutex_init(p->startm);
p->startv = 0;
acond_init(p->startc);
amutex_init(p->stopm);
p->stopv = 0;
acond_init(p->stopc);
}
p->function = function;
p->context = context;
p->start = athread_start;
p->start_task = athread_start_task;
p->wait_stop = athread_wait_stop;
p->wait = athread_wait;
p->terminate = athread_terminate;
p->del = athread_del;
/* Create a thread */
#ifdef USE_BEGINTHREAD
p->th = (HANDLE)_beginthreadex(NULL, 0, threadproc, (void *)p, 0, NULL);
if (p->th == (HANDLE)-1L) {
#else
p->th = CreateThread(NULL, 0, threadproc, (void *)p, 0, NULL);
if (p->th == NULL) {
#endif
a1loge(g_log, 1, "new_athread: CreateThread failed with %d\n",GetLastError());
p->th = NULL;
athread_del(p); /* Cleanup any resources */
return NULL;
}
DBG("new_athread returning OK\n");
return p;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Return the login $HOME directory. */
/* (Useful if we might be running sudo) */
/* No NT equivalent ?? */
char *login_HOME() {
return getenv("HOME");
}
/* Delete a file */
void delete_file(char *fname) {
_unlink(fname);
}
/* Given the path to a file, ensure that all the parent directories */
/* are created. return nz on error */
int create_parent_directories(char *path) {
struct _stat sbuf;
char *pp = path;
if (*pp != '\000' /* Skip drive number */
&& ((*pp >= 'a' && *pp <= 'z') || (*pp >= 'A' && *pp <= 'Z'))
&& pp[1] == ':')
pp += 2;
if (*pp == '/')
pp++; /* Skip root directory */
for (;pp != NULL && *pp != '\000';) {
if ((pp = strchr(pp, '/')) != NULL) {
*pp = '\000';
if (_stat(path,&sbuf) != 0)
{
if (_mkdir(path) != 0)
return 1;
}
*pp = '/';
pp++;
}
}
return 0;
}
/* return the number of processors */
int system_processors() {
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
return sysinfo.dwNumberOfProcessors;
}
#endif /* NT */
/* Do a string copy while replacing all '\' characters with '/' */
void copynorm_dirsep(char *d, char *s) {
#ifdef NT
for (;;) {
*d = *s;
if (*s == '\000')
break;
if (*d == '\\')
*d = '/';
s++;
d++;
}
#endif
}
/* Allocate and create a path to the given filename that is */
/* in the same directory as the given file. */
/* Returns normalized separator '/' path. */
/* Free after use */
/* Return NULL on malloc error */
char *path_to_file_in_same_dir(char *inpath, char *infile) {
size_t alen = 0;
char *rv = NULL, *cp;
/* Be very conservative */
alen = strlen(inpath) + strlen(infile) + 1;
if ((rv = malloc(alen)) == NULL) {
return NULL;
}
copynorm_dirsep(rv, inpath);
/* Locate the base filename in path */
if ((cp = strrchr(rv, '/')) == NULL) {
strcpy(rv, infile);
} else {
cp++;
strcpy(cp, infile);
}
return rv;
}
/* ============================================================= */
/* UNIX/OS X */
/* ============================================================= */
#if defined(UNIX)
/* Wait for and return the next character from the keyboard */
/* (If not_interactive set, wait for next stdin character but discard cr or lf) */
int next_con_char(void) {
struct pollfd pa[1]; /* Poll array to monitor stdin */
struct termios origs, news;
char rv = 0;
/* Configure stdin to be ready with just one character if interactive */
if (!not_interactive) {
if (tcgetattr(STDIN_FILENO, &origs) < 0)
a1logw(g_log, "next_con_char: tcgetattr failed with '%s' on stdin", strerror(errno));
news = origs;
news.c_lflag &= ~(ICANON | ECHO);
news.c_cc[VTIME] = 0;
news.c_cc[VMIN] = 1;
if (tcsetattr(STDIN_FILENO,TCSANOW, &news) < 0)
a1logw(g_log, "next_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));
}
/* Wait for stdin to have at least one character. */
/* If not_interactive set then it may be a character followed by cr and/or lf to flush it */
pa[0].fd = STDIN_FILENO;
pa[0].events = POLLIN | POLLPRI;
pa[0].revents = 0;
if (poll_x(pa, 1, -1) > 0 /* wait until there is something */
&& (pa[0].revents == POLLIN
|| pa[0].revents == POLLPRI)) { /* Something there */
char tb[10];
if (read(STDIN_FILENO, tb, 3) > 0) { /* get it and any return */
rv = tb[0];
}
} else {
a1logw(g_log, "next_con_char: poll on stdin returned unexpected value 0x%x",pa[0].revents);
}
/* Restore stdin if interactive */
if (!not_interactive && tcsetattr(STDIN_FILENO, TCSANOW, &origs) < 0) {
a1logw(g_log, "next_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));
}
return rv;
}
/* If here is one, return the next character from the keyboard, else return 0 */
/* (If not_interactive set, return next stdin character if available, but discard cr or lf) */
int poll_con_char(void) {
struct pollfd pa[1]; /* Poll array to monitor stdin */
struct termios origs, news;
char rv = 0;
/* Configure stdin to be ready with just one character if interactive */
if (!not_interactive) {
if (tcgetattr(STDIN_FILENO, &origs) < 0)
a1logw(g_log, "poll_con_char: tcgetattr failed with '%s' on stdin", strerror(errno));
news = origs;
news.c_lflag &= ~(ICANON | ECHO);
news.c_cc[VTIME] = 0;
news.c_cc[VMIN] = 1;
if (tcsetattr(STDIN_FILENO,TCSANOW, &news) < 0)
a1logw(g_log, "poll_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));
}
/* See if stdin has a character */
/* If not_interactive set then it may be a character followed by cr and/or lf to flush it */
pa[0].fd = STDIN_FILENO;
pa[0].events = POLLIN | POLLPRI;
pa[0].revents = 0;
if (poll_x(pa, 1, 0) > 0 /* don't wait if there is nothing */
&& (pa[0].revents == POLLIN
|| pa[0].revents == POLLPRI)) { /* Something is there */
char tb[10];
if (read(STDIN_FILENO, tb, 3) > 0) { /* Get it and any return */
rv = tb[0];
}
}
/* Restore stdin if interactive */
if (!not_interactive && tcsetattr(STDIN_FILENO, TCSANOW, &origs) < 0)
a1logw(g_log, "poll_con_char: tcsetattr failed with '%s' on stdin", strerror(errno));
return rv;
}
/* Discard all pending characters from stdin */
void empty_con_chars(void) {
if (not_interactive) /* Don't suck all the characters from stdin */
return;
tcflush(STDIN_FILENO, TCIFLUSH);
}
/* Do an fgets from stdin, taking account of possible interference from */
/* non-Interactive mode. */
char *con_fgets(char *s, int size) {
return fgets(s, size, stdin);
}
/* - - - - - - - - - - - - - - - - - - - - - - - - */
int acond_timedwait_imp(pthread_cond_t *cond, pthread_mutex_t *lock, int msec) {
struct timeval tv;
struct timespec ts;
int rv;
// this is unduly complicated...
gettimeofday(&tv, NULL);
ts.tv_sec = tv.tv_sec + msec/1000;
ts.tv_nsec = (tv.tv_usec + (msec % 1000) * 1000) * 1000L;
if (ts.tv_nsec > 1000000000L) {
ts.tv_nsec -= 1000000000L;
ts.tv_sec++;
}
rv = pthread_cond_timedwait(cond, lock, &ts);
return rv;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - */
#ifdef NEVER /* Not currently needed, or effective */
/* Set the current threads priority */
int set_interactive_priority() {
#ifdef UNIX_APPLE
#ifdef NEVER
int rv = 0;
struct task_category_policy tcatpolicy;
tcatpolicy.role = TASK_FOREGROUND_APPLICATION;
if (task_policy_set(mach_task_self(),
TASK_CATEGORY_POLICY, (thread_policy_t)&tcatpolicy,
TASK_CATEGORY_POLICY_COUNT) != KERN_SUCCESS)
rv = 1;
// a1logd(g_log, 8, "set_interactive_priority: set to forground got %d\n",rv);
return rv;
#else
int rv = 0;
struct thread_precedence_policy tppolicy;
tppolicy.importance = 500;
if (thread_policy_set(mach_thread_self(),
THREAD_PRECEDENCE_POLICY, (thread_policy_t)&tppolicy,
THREAD_PRECEDENCE_POLICY_COUNT) != KERN_SUCCESS)
rv = 1;
// a1logd(g_log, 8, "set_interactive_priority: set to important got %d\n",rv);
return rv;
#endif /* NEVER */
#else /* !UNIX_APPLE */
int rv;
struct sched_param param;
param.sched_priority = 32;
/* This doesn't work unless we're running as su :-( */
rv = pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);
// a1logd(g_log, 8, "set_interactive_priority: set got %d\n",rv);
return rv;
#endif /* !UNIX_APPLE */
}
int set_normal_priority() {
#ifdef UNIX_APPLE
#ifdef NEVER
int rv = 0;
struct task_category_policy tcatpolicy;
tcatpolicy.role = TASK_UNSPECIFIED;
if (task_policy_set(mach_task_self(),
TASK_CATEGORY_POLICY, (thread_policy_t)&tcatpolicy,
TASK_CATEGORY_POLICY_COUNT) != KERN_SUCCESS)
rev = 1;
// a1logd(g_log, 8, "set_normal_priority: set to normal got %d\n",rv);
#else
int rv = 0;
struct thread_precedence_policy tppolicy;
tppolicy.importance = 1;
if (thread_policy_set(mach_thread_self(),
THREAD_STANDARD_POLICY, (thread_policy_t)&tppolicy,
THREAD_STANDARD_POLICY_COUNT) != KERN_SUCCESS)
rv = 1;
// a1logd(g_log, 8, "set_normal_priority: set to standard got %d\n",rv);
return rv;
#endif /* NEVER */
#else /* !UNIX_APPLE */
struct sched_param param;
param.sched_priority = 0;
int rv;
rv = pthread_setschedparam(pthread_self(), SCHED_OTHER, &param);
// a1logd(g_log, 8, "set_normal_priority: reset got %d\n",rv);
return rv;
#endif /* !UNIX_APPLE */
}
#endif /* NEVER */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
static athread *beep_thread = NULL;
static int beep_delay;
static int beep_freq;
static int beep_msec;
/* Delayed beep handler */
static int delayed_beep(void *pp) {
msec_sleep(beep_delay);
a1logd(g_log,8, "msec_beep activate\n");
#ifdef UNIX_APPLE
# if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
AudioServicesPlayAlertSound(kUserPreferredAlert);
# else
SysBeep((beep_msec * 60)/1000);
# endif
#else /* UNIX */
/* Linux is pretty lame in this regard... */
/* Maybe we could write an 8Khz 8 bit sample to /dev/dsp, or /dev/audio ? */
/* The ALSA system is the modern way for audio output. */
/* Also check out what sox does: <http://sox.sourceforge.net/> */
fprintf(stdout, "\a"); fflush(stdout);
#endif
return 0;
}
/* Activate the system beeper */
void msec_beep(int delay, int freq, int msec) {
a1logd(g_log,8, "msec_beep %d msec\n",msec);
if (delay > 0) {
if (beep_thread != NULL)
beep_thread->del(beep_thread);
beep_delay = delay;
beep_freq = freq;
beep_msec = msec;
if ((beep_thread = new_athread(delayed_beep, NULL)) == NULL)
a1logw(g_log, "msec_beep: Delayed beep failed to create thread\n");
} else {
a1logd(g_log,8, "msec_beep activate\n");
#ifdef UNIX_APPLE
# if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
AudioServicesPlayAlertSound(kUserPreferredAlert);
# else
SysBeep((msec * 60)/1000);
# endif
#else /* UNIX */
fprintf(stdout, "\a"); fflush(stdout);
#endif
}
}
#ifdef NEVER
/* If we're UNIX and running on X11, we could do this */
/* sort of thing (from xset) to sound a beep, */
/* IF we were linking with X11: */
static void
set_bell_vol(Display *dpy, int percent) {
XKeyboardControl values;
XKeyboardState kbstate;
values.bell_percent = percent;
if (percent == DEFAULT_ON)
values.bell_percent = SERVER_DEFAULT;
XChangeKeyboardControl(dpy, KBBellPercent, &values);
if (percent == DEFAULT_ON) {
XGetKeyboardControl(dpy, &kbstate);
if (!kbstate.bell_percent) {
values.bell_percent = -percent;
XChangeKeyboardControl(dpy, KBBellPercent, &values);
}
}
return;
}
static void
set_bell_pitch(Display *dpy, int pitch) {
XKeyboardControl values;
values.bell_pitch = pitch;
XChangeKeyboardControl(dpy, KBBellPitch, &values);
return;
}
static void
set_bell_dur(Display *dpy, int duration) {
XKeyboardControl values;
values.bell_duration = duration;
XChangeKeyboardControl(dpy, KBBellDuration, &values);
return;
}
XBell(..);
#endif /* NEVER */
/* - - - - - - - - - - - - - - - - - - - - - - - - */
// ~~~ !!!! a lot of this is duplicated in the MSWIN code !!!!
// !!! Should consolidate to reduce maintanence ?? !!!!
/* If reusable, start a stopped thread. NOP if not reusable */
static void athread_start(
athread *p
) {
DBG("athread_start called\n");
if (!p->reusable)
return;
/* Signal to the thread that it should start */
amutex_lock(p->startm);
p->startv = 1;
acond_signal(p->startc);
amutex_unlock(p->startm);
}
/* If reusable, change the task and then start a stopped thread. NOP if not reusable */
static void athread_start_task(
athread *p,
int (*function)(void *context),
void *context
) {
DBG("athread_start_task called\n");
if (!p->reusable)
return;
/* Change to this task */
p->function = function;
p->context = context;
/* Signal to the thread that it should start */
amutex_lock(p->startm);
p->startv = 1;
acond_signal(p->startc);
amutex_unlock(p->startm);
}
/* If reusable, wait for the thread to stop. Return the result. NOP if not reusable */
static int athread_wait_stop(
athread *p
) {
DBG("athread_wait_stop called\n");
if (!p->reusable)
return p->result;
/* Wait for thread to stop */
amutex_lock(p->stopm);
while (p->stopv == 0)
acond_wait(p->stopc, p->stopm);
p->stopv = 0;
amutex_unlock(p->stopm);
return p->result;
}
/* Wait for the thread to exit. Return the result */
static int athread_wait(struct _athread *p) {
int rv;
if (p->reusable) {
p->dofinish = 1;
athread_start(p); /* Wake thread up to cause it to exit */
}
if (p->joined)
return p->result;
if ((rv = pthread_join(p->thid, NULL)) != 0)
warning("pthread_join of thid %d failed with %d",p->thid,rv);
p->joined = 1;
return p->result;
}
/* Forcefully terminate the thread */
static void athread_terminate(
athread *p
) {
DBG("athread_terminate called\n");
if (p == NULL || p->joined)
return;
if (p->thid != (pthread_t)0) {
DBG("athread_terminate calling pthread_cancel()\n");
pthread_cancel(p->thid);
// pthread_join(p->thid, NULL); // Don't join in case it doesn't terminate
}
p->joined = 1; /* Skip any join afterwards */
}
/* Free up thread resources */
static void athread_del(
athread *p
) {
DBG("athread_del called\n");
if (p == NULL)
return;
if (p->thid != 0) {
if (p->reusable && !p->joined && !p->dofinish) {
p->dofinish = 1;
athread_start(p); /* Wake thread up to cause it to exit */
}
if (!p->joined) {
int rv;
if ((rv = pthread_join(p->thid, NULL)) != 0)
warning("pthread_join of thid %d failed with %d",p->thid,rv);
}
}
if (p->reusable) {
acond_del(p->startc);
amutex_del(p->startm);
acond_del(p->stopc);
amutex_del(p->stopm);
}
free(p);
}
static void *threadproc(
void *param
) {
athread *p = (athread *)param;
/* Register this thread with the Objective-C garbage collector */
/* (Still needed even though marked deprecated ??) */
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 101200
objc_registerThreadWithCollector();
#endif
if (p->reusable) { /* Run over and over */
for (;;) {
/* Wait for client to start us */
amutex_lock(p->startm);
while (p->startv == 0)
acond_wait(p->startc, p->startm);
p->startv = 0;
amutex_unlock(p->startm);
if (p->dofinish) /* If we've been woken up so we can exit */
break;
p->result = p->function(p->context);
/* Signal to the client that we've stopped */
amutex_lock(p->stopm);
p->stopv = 1;
acond_signal(p->stopc);
amutex_unlock(p->stopm);
}
} else {
p->result = p->function(p->context);
}
// p->finished = 1;
return 0;
}
athread *new_athread_reusable(
int (*function)(void *context),
void *context,
int reusable
) {
int rv;
athread *p = NULL;
DBG("new_athread called\n");
if ((p = (athread *)calloc(sizeof(athread), 1)) == NULL) {
a1loge(g_log, 1, "new_athread: calloc failed\n");
return NULL;
}
p->reusable = reusable;
if (p->reusable) {
amutex_init(p->startm);
p->startv = 0;
acond_init(p->startc);
amutex_init(p->stopm);
p->stopv = 0;
acond_init(p->stopc);
}
p->function = function;
p->context = context;
p->start = athread_start;
p->start_task = athread_start_task;
p->wait_stop = athread_wait_stop;
p->wait = athread_wait;
p->del = athread_del;
#if defined(UNIX_APPLE)
{
pthread_attr_t stackSzAtrbt;
/* Default stack size is 512K - this is a bit small - raise it to 4MB */
if ((rv = pthread_attr_init(&stackSzAtrbt)) != 0
|| (rv = pthread_attr_setstacksize(&stackSzAtrbt, 4 * 1024 * 1024)) != 0) {
fprintf(stderr,"new_athread: thread_attr_setstacksize failed with %d\n",rv);
return NULL;
}
/* Create a thread */
rv = pthread_create(&p->thid, &stackSzAtrbt, threadproc, (void *)p);
if (rv != 0) {
a1loge(g_log, 1, "new_athread: pthread_create failed with %d\n",rv);
athread_del(p);
return NULL;
}
}
#else /* !APPLE */
/* Create a thread */
rv = pthread_create(&p->thid, NULL, threadproc, (void *)p);
if (rv != 0) {
a1loge(g_log, 1, "new_athread: pthread_create failed with %d\n",rv);
p->thid = 0;
athread_del(p);
return NULL;
}
#endif /* !APPLE */
DBG("About to exit new_athread()\n");
return p;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - */
/* Return the login $HOME directory. */
/* (Useful if we might be running sudo) */
char *login_HOME() {
if (getuid() == 0) { /* If we are running as root */
char *uids;
if ((uids = getenv("SUDO_UID")) != NULL) { /* And we sudo's to get it */
int uid;
struct passwd *pwd;
uid = atoi(uids);
if ((pwd = getpwuid(uid)) != NULL) {
return pwd->pw_dir;
}
}
}
return getenv("HOME");
}
/* Delete a file */
void delete_file(char *fname) {
unlink(fname);
}
/* Given the path to a file, ensure that all the parent directories */
/* are created. return nz on error */
int create_parent_directories(char *path) {
struct stat sbuf;
char *pp = path;
mode_t mode = 0700; /* Default directory mode */
if (*pp == '/')
pp++; /* Skip root directory */
for (;pp != NULL && *pp != '\000';) {
if ((pp = strchr(pp, '/')) != NULL) {
*pp = '\000';
if (stat(path,&sbuf) != 0)
{
if (mkdir(path, mode) != 0)
return 1;
} else
mode = sbuf.st_mode;
*pp = '/';
pp++;
}
}
return 0;
}
#if !defined(UNIX_APPLE) || MAC_OS_X_VERSION_MIN_REQUIRED > 1040
/* return the number of processors */
int system_processors() {
return sysconf(_SC_NPROCESSORS_ONLN);
}
#else /* OS X <= 10.4 code */
/* return the number of processors using BSD code */
int system_processors() {
int mib[4];
int numCPU;
size_t len = sizeof(numCPU);
mib[0] = CTL_HW;
mib[1] = HW_AVAILCPU;
sysctl(mib, 2, &numCPU, &len, NULL, 0);
if (numCPU < 1) {
mib[1] = HW_NCPU;
sysctl(mib, 2, &numCPU, &len, NULL, 0);
if (numCPU < 1)
numCPU = 1;
}
return numCPU;
}
#endif
#endif /* defined(UNIX) */
/* - - - - - - - - - - - - - - - - - - - - - - - - */
#if defined(UNIX_APPLE) || defined(NT)
/* Thread to monitor and kill the named processes */
static int th_kkill_nprocess(void *pp) {
kkill_nproc_ctx *ctx = (kkill_nproc_ctx *)pp;
/* set result to 0 if it ever succeeds or there was no such process */
ctx->th->result = -1;
while(ctx->stop == 0) {
if (kill_nprocess(ctx->pname, ctx->log) >= 0)
ctx->th->result = 0;
msec_sleep(20); /* Don't hog the CPU */
}
ctx->done = 1;
return 0;
}
static void kkill_nprocess_del(kkill_nproc_ctx *p) {
int i;
p->stop = 1;
DBG("kkill_nprocess del called\n");
for (i = 0; p->done == 0 && i < 100; i++) {
msec_sleep(50);
}
if (p->done == 0) { /* Hmm */
a1logw(p->log,"kkill_nprocess del failed to stop - killing thread\n");
p->th->del(p->th);
}
del_a1log(p->log);
free(p);
DBG("kkill_nprocess del done\n");
}
/* Start a thread to constantly kill a process. */
/* Call ctx->del() when done */
kkill_nproc_ctx *kkill_nprocess(char **pname, a1log *log) {
kkill_nproc_ctx *p;
DBG("kkill_nprocess called\n");
if (log != NULL && log->debug >= 8) {
int i;
a1logv(log, 8, "kkill_nprocess called with");
for (i = 0; pname[i] != NULL; i++)
a1logv(log, 8, " '%s'",pname[i]);
a1logv(log, 8, "\n");
}
if ((p = (kkill_nproc_ctx *)calloc(sizeof(kkill_nproc_ctx), 1)) == NULL) {
a1loge(log, 1, "kkill_nprocess: calloc failed\n");
return NULL;
}
p->pname = pname;
p->log = new_a1log_d(log);
p->del = kkill_nprocess_del;
if ((p->th = new_athread(th_kkill_nprocess, p)) == NULL) {
del_a1log(p->log);
free(p);
return NULL;
}
return p;
}
#ifdef NT
/* Kill a list of named process. */
/* Kill the first process found, then return */
/* return < 0 if this fails. */
/* return 0 if there is no such process */
/* return 1 if a process was killed */
int kill_nprocess(char **pname, a1log *log) {
PROCESSENTRY32 entry;
HANDLE snapshot;
int j;
/* Get a snapshot of the current processes */
if ((snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0)) == NULL) {
return -1;
}
while(Process32Next(snapshot,&entry) != FALSE) {
HANDLE proc;
if (strcmp(entry.szExeFile, "spotread.exe") == 0
&& (proc = OpenProcess(PROCESS_TERMINATE, FALSE, entry.th32ProcessID)) != NULL) {
if (TerminateProcess(proc,0) == 0) {
a1logv(log, 8, "kill_nprocess: Failed to kill '%s'\n",entry.szExeFile);
} else {
a1logv(log, 8, "kill_nprocess: Killed '%s'\n",entry.szExeFile);
}
CloseHandle(proc);
}
for (j = 0;; j++) {
if (pname[j] == NULL) /* End of list */
break;
a1logv(log, 8, "kill_nprocess: Checking process '%s' against list '%s'\n",
entry.szExeFile,pname[j]);
if (strcmp(entry.szExeFile,pname[j]) == 0) {
a1logv(log, 1, "kill_nprocess: killing process '%s' pid %d\n",
entry.szExeFile,entry.th32ProcessID);
if ((proc = OpenProcess(PROCESS_TERMINATE, FALSE, entry.th32ProcessID)) == NULL
|| TerminateProcess(proc,0) == 0) {
a1logv(log, 1, "kill_nprocess: kill process '%s' failed with %d\n",
pname[j],GetLastError());
CloseHandle(proc);
CloseHandle(snapshot);
return -1;
}
CloseHandle(proc);
/* Stop on first one found ? */
CloseHandle(snapshot);
return 1;
}
}
}
CloseHandle(snapshot);
return 0;
}
#endif /* NT */
#if defined(UNIX_APPLE)
/* Kill a list of named process. */
/* Kill the first process found, then return */
/* return < 0 if this fails. */
/* return 0 if there is no such process */
/* return 1 if a process was killed */
int kill_nprocess(char **pname, a1log *log) {
struct kinfo_proc *procList = NULL;
size_t procCount = 0;
static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };
size_t length;
int rv = 0;
int i, j;
procList = NULL;
for (;;) {
int err;
/* Establish the amount of memory needed */
length = 0;
if (sysctl(name, (sizeof(name) / sizeof(*name)) - 1,
NULL, &length,
NULL, 0) == -1) {
DBGF((DBGA,"sysctl #1 failed with %d\n", errno));
return -1;
}
/* Add some more entries in case the number of processors changed */
length += 10 * sizeof(struct kinfo_proc);
if ((procList = malloc(length)) == NULL) {
DBGF((DBGA,"malloc failed for %d bytes\n", length));
return -1;
}
/* Call again with memory */
if ((err = sysctl(name, (sizeof(name) / sizeof(*name)) - 1,
procList, &length,
NULL, 0)) == -1) {
DBGF((DBGA,"sysctl #1 failed with %d\n", errno));
free(procList);
return -1;
}
if (err == 0) {
break;
} else if (err == ENOMEM) {
free(procList);
procList = NULL;
}
}
procCount = length / sizeof(struct kinfo_proc);
/* Locate the processes */
for (i = 0; i < procCount; i++) {
for (j = 0;; j++) {
if (pname[j] == NULL) /* End of list */
break;
a1logv(log, 8, "kill_nprocess: Checking process '%s' against list '%s'\n",
procList[i].kp_proc.p_comm,pname[j]);
if (strncmp(procList[i].kp_proc.p_comm,pname[j],MAXCOMLEN) == 0) {
a1logv(log, 1, "kill_nprocess: killing process '%s' pid %d\n",
pname[j],procList[i].kp_proc.p_pid);
if (kill(procList[i].kp_proc.p_pid, SIGTERM) != 0) {
a1logv(log, 1, "kill_nprocess: kill process '%s' failed with %d\n",
pname[j],errno);
free(procList);
return -1;
}
/* Stop on first one found ? */
free(procList);
return 1;
}
}
}
free(procList);
return rv;
}
#endif /* UNIX_APPLE */
#endif /* UNIX_APPLE || NT */
/* ===================================================================== */
/* Some web support */
static char nib2hex(int nib) {
nib &= 0xf;
if (nib < 10)
return '0' + nib;
else
return 'A' + nib - 10;
}
/* Destination should be strlen(s) * 3 + 1 */
void encodeurl(char *d, char *s) {
char *dd = d;
DBGF((DBGA," encodeurl s = '%s'\n",s));
for (; *s != '\000'; s++) {
char c = *s;
if (isalnum(c)
|| c == '~'
|| c == '-'
|| c == '.'
|| c == '_')
*d++ = c;
else {
*d++ = '%';
*d++ = nib2hex(c >> 4);
*d++ = nib2hex(c);
}
}
*d++ = '\000';
DBGF((DBGA," encodeurl d = '%s'\n",dd));
}
static int hex2nib(char c) {
int nib = 0;
if (c >= '0' && c <= '9')
nib = c - '0';
else if (c >= 'A' && c <= 'F')
nib = c - 'A' + 10;
else if (c >= 'a' && c <= 'f')
nib = c - 'a' + 10;
return nib;
}
/* Destination is smaller than src */
void decodeurl(char *d, char *s) {
char *dd = d;
DBGF((DBGA," decodeurl s = '%s'\n",s));
for (; *s != '\000'; s++) {
// if (s[0] == '+')
// *d++ = ' ';
// else
if (s[0] == '%' && s[1] != '\000' && s[2] != '\000') {
*d++ = (hex2nib(s[1]) << 4) + hex2nib(s[2]);
s += 2;
} else
*d++ = s[0];
}
*d++ = '\000';
DBGF((DBGA," decodeurl d = '%s'\n",dd));
}
/* ===================================================================== */
/* Some compatibility functions */
#if defined(UNIX_APPLE)
size_t osx_strnlen(const char *string, size_t maxlen) {
const char *end = memchr(string, '\0', maxlen);
return end ? end - string : maxlen;
}
char *osx_strndup(const char *s, size_t n) {
size_t len = osx_strnlen(s, n);
char *buf = malloc(len + 1);
if (buf == NULL)
return NULL;
buf[len] = '\0';
return memcpy(buf, s, len);
}
#endif // APPLE && < 1040