mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
- The SVDRP command LSTT now accepts the new option 'id' to have the channels of the timers listed with their unique channel ids instead of their numbers (suggested by Matthias Schniedermeyer). - Added a missing #include <linux/unistd.h> to thread.c (thanks to Ville Skyttä). - Fixed the "plugins-clean" and "plugins-install" targets in the Makefile (thanks to Andreas Brachold). - Fixed handling "more than 3 byte" key sequences in cKbdRemote::ReadKeySequence() (thanks to Peter Bieringer). If you are using the PC keyboard as remote control input you may need to make VDR newly learn the keys by removing the remote.conf file. - To avoid problems with access rights when VDR shall run as 'root' it now skips all SetCaps() and SetUser() calls when it is started as 'root' and "-u root" is given. - Added missing i18n entry for the "Timer" button (thanks to Ville Skyttä) - Updated the Finnish OSD texts (thanks to Rolf Ahrenberg). - Making the "Menu" key behave consistently has not been well received by several users, so the new option "Setup/OSD/Menu button closes" can be used to get the old behavior back (which also is the default value of this option). - Dropped the default vdr user. The program now always runs under the user id it was started from, unless the '-u' option is given and it was started from the 'root' user. If you want to have a default vdr user, you can activate and adjust the "VDR_USER = vdr" line in your Make.config file (from the original patch by Ludwig Nussel). - Key macros can now be defined for all non-modeless keys (suggested by Mirko Dölle). - Adjusted the "KEY MACROS" section of vdr.5 to the new plugin calling mechanism introduced in version 1.3.32. - Removed the now obsolete "ca.conf" section from vdr.1 (thanks to Ville Skyttä). - Added missing description of L and R circular polarization to 'diseqc.conf'. - Added a note about "modprobe capability" to INSTALL (suggested by Patrick Cernko). - Fixed canonicalizing the file name in the SVDRP command GRAB to allow full path names (thanks to Stefan Huelswitt). - Added a missing '-' to the example for viewing a grabbed image on a remote host (reported by Philippe Gramoullé). - Made the "What's on now/next?" menus a lot faster by storing a pointer to each channel's schedule in the cChannel data. - Made the log messages regarding lost lock of devices "info" instead of "error" (suggested by Andreas Brachold). - The SVDRP command GRAB allows file names without extension again (suggested by Stefan Huelswitt). - Pressing '0' in the "Schedule" menu now rotates through displaying "This event on this channel", "This event on all channels" and "All events on all channels". This can be used to find reruns of a given show, or the episodes of a series. Note that if there are many channels in your channels.conf, displaying the "All events on all channels" page may take a while. - The status markers in the "Schedule" menu are now only updated if a submenu is closed in which a timer has been modified, which speeds up closing submenus. - Now only writing Dolby Digital tracks into the 'info.vdr' file of a recording if Setup.UseDolbyDigital is true (suggested by André Weidemann). - Added a leading '0' to the day in the DayDateTime() function (thanks to Rolf Ahrenberg). - No longer displaying color buttons in the recording info menu if it has been invoked from a player (reported by Jürgen Schilling).
528 lines
12 KiB
C
528 lines
12 KiB
C
/*
|
|
* thread.c: A simple thread base class
|
|
*
|
|
* See the main source file 'vdr.c' for copyright information and
|
|
* how to reach the author.
|
|
*
|
|
* $Id: thread.c 1.51 2006/01/08 16:03:56 kls Exp $
|
|
*/
|
|
|
|
#include "thread.h"
|
|
#include <errno.h>
|
|
#include <linux/unistd.h>
|
|
#include <malloc.h>
|
|
#include <stdarg.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/time.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include "tools.h"
|
|
|
|
static bool GetAbsTime(struct timespec *Abstime, int MillisecondsFromNow)
|
|
{
|
|
struct timeval now;
|
|
if (gettimeofday(&now, NULL) == 0) { // get current time
|
|
now.tv_usec += MillisecondsFromNow * 1000; // add the timeout
|
|
while (now.tv_usec >= 1000000) { // take care of an overflow
|
|
now.tv_sec++;
|
|
now.tv_usec -= 1000000;
|
|
}
|
|
Abstime->tv_sec = now.tv_sec; // seconds
|
|
Abstime->tv_nsec = now.tv_usec * 1000; // nano seconds
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// --- cCondWait -------------------------------------------------------------
|
|
|
|
cCondWait::cCondWait(void)
|
|
{
|
|
signaled = false;
|
|
pthread_mutex_init(&mutex, NULL);
|
|
pthread_cond_init(&cond, NULL);
|
|
}
|
|
|
|
cCondWait::~cCondWait()
|
|
{
|
|
pthread_cond_broadcast(&cond); // wake up any sleepers
|
|
pthread_cond_destroy(&cond);
|
|
pthread_mutex_destroy(&mutex);
|
|
}
|
|
|
|
void cCondWait::SleepMs(int TimeoutMs)
|
|
{
|
|
cCondWait w;
|
|
w.Wait(max(TimeoutMs, 3)); // making sure the time is >2ms to avoid a possible busy wait
|
|
}
|
|
|
|
bool cCondWait::Wait(int TimeoutMs)
|
|
{
|
|
pthread_mutex_lock(&mutex);
|
|
if (!signaled) {
|
|
if (TimeoutMs) {
|
|
struct timespec abstime;
|
|
if (GetAbsTime(&abstime, TimeoutMs)) {
|
|
while (!signaled) {
|
|
if (pthread_cond_timedwait(&cond, &mutex, &abstime) == ETIMEDOUT)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
pthread_cond_wait(&cond, &mutex);
|
|
}
|
|
bool r = signaled;
|
|
signaled = false;
|
|
pthread_mutex_unlock(&mutex);
|
|
return r;
|
|
}
|
|
|
|
void cCondWait::Signal(void)
|
|
{
|
|
pthread_mutex_lock(&mutex);
|
|
signaled = true;
|
|
pthread_cond_broadcast(&cond);
|
|
pthread_mutex_unlock(&mutex);
|
|
}
|
|
|
|
// --- cCondVar --------------------------------------------------------------
|
|
|
|
cCondVar::cCondVar(void)
|
|
{
|
|
pthread_cond_init(&cond, 0);
|
|
}
|
|
|
|
cCondVar::~cCondVar()
|
|
{
|
|
pthread_cond_broadcast(&cond); // wake up any sleepers
|
|
pthread_cond_destroy(&cond);
|
|
}
|
|
|
|
void cCondVar::Wait(cMutex &Mutex)
|
|
{
|
|
if (Mutex.locked) {
|
|
int locked = Mutex.locked;
|
|
Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_wait
|
|
// does an implizit unlock of the mutex
|
|
pthread_cond_wait(&cond, &Mutex.mutex);
|
|
Mutex.locked = locked;
|
|
}
|
|
}
|
|
|
|
bool cCondVar::TimedWait(cMutex &Mutex, int TimeoutMs)
|
|
{
|
|
bool r = true; // true = condition signaled false = timeout
|
|
|
|
if (Mutex.locked) {
|
|
struct timespec abstime;
|
|
if (GetAbsTime(&abstime, TimeoutMs)) {
|
|
int locked = Mutex.locked;
|
|
Mutex.locked = 0; // have to clear the locked count here, as pthread_cond_timedwait
|
|
// does an implizit unlock of the mutex.
|
|
if (pthread_cond_timedwait(&cond, &Mutex.mutex, &abstime) == ETIMEDOUT)
|
|
r = false;
|
|
Mutex.locked = locked;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
void cCondVar::Broadcast(void)
|
|
{
|
|
pthread_cond_broadcast(&cond);
|
|
}
|
|
|
|
// --- cRwLock ---------------------------------------------------------------
|
|
|
|
cRwLock::cRwLock(bool PreferWriter)
|
|
{
|
|
pthread_rwlockattr_t attr;
|
|
pthread_rwlockattr_init(&attr);
|
|
pthread_rwlockattr_setkind_np(&attr, PreferWriter ? PTHREAD_RWLOCK_PREFER_WRITER_NP : PTHREAD_RWLOCK_PREFER_READER_NP);
|
|
pthread_rwlock_init(&rwlock, &attr);
|
|
}
|
|
|
|
cRwLock::~cRwLock()
|
|
{
|
|
pthread_rwlock_destroy(&rwlock);
|
|
}
|
|
|
|
bool cRwLock::Lock(bool Write, int TimeoutMs)
|
|
{
|
|
int Result = 0;
|
|
struct timespec abstime;
|
|
if (TimeoutMs) {
|
|
if (!GetAbsTime(&abstime, TimeoutMs))
|
|
TimeoutMs = 0;
|
|
}
|
|
if (Write)
|
|
Result = TimeoutMs ? pthread_rwlock_timedwrlock(&rwlock, &abstime) : pthread_rwlock_wrlock(&rwlock);
|
|
else
|
|
Result = TimeoutMs ? pthread_rwlock_timedrdlock(&rwlock, &abstime) : pthread_rwlock_rdlock(&rwlock);
|
|
return Result == 0;
|
|
}
|
|
|
|
void cRwLock::Unlock(void)
|
|
{
|
|
pthread_rwlock_unlock(&rwlock);
|
|
}
|
|
|
|
// --- cMutex ----------------------------------------------------------------
|
|
|
|
cMutex::cMutex(void)
|
|
{
|
|
locked = 0;
|
|
pthread_mutexattr_t attr;
|
|
pthread_mutexattr_init(&attr);
|
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
|
|
pthread_mutex_init(&mutex, &attr);
|
|
}
|
|
|
|
cMutex::~cMutex()
|
|
{
|
|
pthread_mutex_destroy(&mutex);
|
|
}
|
|
|
|
void cMutex::Lock(void)
|
|
{
|
|
pthread_mutex_lock(&mutex);
|
|
locked++;
|
|
}
|
|
|
|
void cMutex::Unlock(void)
|
|
{
|
|
if (!--locked)
|
|
pthread_mutex_unlock(&mutex);
|
|
}
|
|
|
|
// --- cThread ---------------------------------------------------------------
|
|
|
|
tThreadId cThread::mainThreadId = 0;
|
|
bool cThread::emergencyExitRequested = false;
|
|
|
|
cThread::cThread(const char *Description)
|
|
{
|
|
active = running = false;
|
|
childTid = 0;
|
|
childThreadId = 0;
|
|
description = NULL;
|
|
SetDescription(Description);
|
|
}
|
|
|
|
cThread::~cThread()
|
|
{
|
|
Cancel(); // just in case the derived class didn't call it
|
|
free(description);
|
|
}
|
|
|
|
void cThread::SetPriority(int Priority)
|
|
{
|
|
if (setpriority(PRIO_PROCESS, 0, Priority) < 0)
|
|
LOG_ERROR;
|
|
}
|
|
|
|
void cThread::SetDescription(const char *Description, ...)
|
|
{
|
|
free(description);
|
|
description = NULL;
|
|
if (Description) {
|
|
va_list ap;
|
|
va_start(ap, Description);
|
|
vasprintf(&description, Description, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
|
|
void *cThread::StartThread(cThread *Thread)
|
|
{
|
|
Thread->childThreadId = ThreadId();
|
|
if (Thread->description)
|
|
dsyslog("%s thread started (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId);
|
|
Thread->Action();
|
|
if (Thread->description)
|
|
dsyslog("%s thread ended (pid=%d, tid=%d)", Thread->description, getpid(), Thread->childThreadId);
|
|
Thread->running = false;
|
|
Thread->active = false;
|
|
return NULL;
|
|
}
|
|
|
|
bool cThread::Start(void)
|
|
{
|
|
if (!active) {
|
|
active = running = true;
|
|
if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0) {
|
|
pthread_detach(childTid); // auto-reap
|
|
pthread_setschedparam(childTid, SCHED_RR, 0);
|
|
}
|
|
else {
|
|
LOG_ERROR;
|
|
active = running = false;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool cThread::Active(void)
|
|
{
|
|
if (active) {
|
|
//
|
|
// Single UNIX Spec v2 says:
|
|
//
|
|
// The pthread_kill() function is used to request
|
|
// that a signal be delivered to the specified thread.
|
|
//
|
|
// As in kill(), if sig is zero, error checking is
|
|
// performed but no signal is actually sent.
|
|
//
|
|
int err;
|
|
if ((err = pthread_kill(childTid, 0)) != 0) {
|
|
if (err != ESRCH)
|
|
LOG_ERROR;
|
|
childTid = 0;
|
|
active = running = false;
|
|
}
|
|
else
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void cThread::Cancel(int WaitSeconds)
|
|
{
|
|
running = false;
|
|
if (active) {
|
|
if (WaitSeconds > 0) {
|
|
for (time_t t0 = time(NULL) + WaitSeconds; time(NULL) < t0; ) {
|
|
if (!Active())
|
|
return;
|
|
cCondWait::SleepMs(10);
|
|
}
|
|
esyslog("ERROR: thread %d won't end (waited %d seconds) - canceling it...", childThreadId, WaitSeconds);
|
|
}
|
|
pthread_cancel(childTid);
|
|
childTid = 0;
|
|
active = false;
|
|
}
|
|
}
|
|
|
|
bool cThread::EmergencyExit(bool Request)
|
|
{
|
|
if (!Request)
|
|
return emergencyExitRequested;
|
|
esyslog("initiating emergency exit");
|
|
return emergencyExitRequested = true; // yes, it's an assignment, not a comparison!
|
|
}
|
|
|
|
_syscall0(pid_t, gettid)
|
|
|
|
tThreadId cThread::ThreadId(void)
|
|
{
|
|
return gettid();
|
|
}
|
|
|
|
void cThread::SetMainThreadId(void)
|
|
{
|
|
if (mainThreadId == 0)
|
|
mainThreadId = ThreadId();
|
|
else
|
|
esyslog("ERROR: attempt to set main thread id to %d while it already is %d", ThreadId(), mainThreadId);
|
|
}
|
|
|
|
// --- cMutexLock ------------------------------------------------------------
|
|
|
|
cMutexLock::cMutexLock(cMutex *Mutex)
|
|
{
|
|
mutex = NULL;
|
|
locked = false;
|
|
Lock(Mutex);
|
|
}
|
|
|
|
cMutexLock::~cMutexLock()
|
|
{
|
|
if (mutex && locked)
|
|
mutex->Unlock();
|
|
}
|
|
|
|
bool cMutexLock::Lock(cMutex *Mutex)
|
|
{
|
|
if (Mutex && !mutex) {
|
|
mutex = Mutex;
|
|
Mutex->Lock();
|
|
locked = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// --- cThreadLock -----------------------------------------------------------
|
|
|
|
cThreadLock::cThreadLock(cThread *Thread)
|
|
{
|
|
thread = NULL;
|
|
locked = false;
|
|
Lock(Thread);
|
|
}
|
|
|
|
cThreadLock::~cThreadLock()
|
|
{
|
|
if (thread && locked)
|
|
thread->Unlock();
|
|
}
|
|
|
|
bool cThreadLock::Lock(cThread *Thread)
|
|
{
|
|
if (Thread && !thread) {
|
|
thread = Thread;
|
|
Thread->Lock();
|
|
locked = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// --- cPipe -----------------------------------------------------------------
|
|
|
|
// cPipe::Open() and cPipe::Close() are based on code originally received from
|
|
// Andreas Vitting <Andreas@huji.de>
|
|
|
|
cPipe::cPipe(void)
|
|
{
|
|
pid = -1;
|
|
f = NULL;
|
|
}
|
|
|
|
cPipe::~cPipe()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
bool cPipe::Open(const char *Command, const char *Mode)
|
|
{
|
|
int fd[2];
|
|
|
|
if (pipe(fd) < 0) {
|
|
LOG_ERROR;
|
|
return false;
|
|
}
|
|
if ((pid = fork()) < 0) { // fork failed
|
|
LOG_ERROR;
|
|
close(fd[0]);
|
|
close(fd[1]);
|
|
return false;
|
|
}
|
|
|
|
char *mode = "w";
|
|
int iopipe = 0;
|
|
|
|
if (pid > 0) { // parent process
|
|
if (strcmp(Mode, "r") == 0) {
|
|
mode = "r";
|
|
iopipe = 1;
|
|
}
|
|
close(fd[iopipe]);
|
|
f = fdopen(fd[1 - iopipe], mode);
|
|
if ((f = fdopen(fd[1 - iopipe], mode)) == NULL) {
|
|
LOG_ERROR;
|
|
close(fd[1 - iopipe]);
|
|
}
|
|
return f != NULL;
|
|
}
|
|
else { // child process
|
|
int iofd = STDOUT_FILENO;
|
|
if (strcmp(Mode, "w") == 0) {
|
|
mode = "r";
|
|
iopipe = 1;
|
|
iofd = STDIN_FILENO;
|
|
}
|
|
close(fd[iopipe]);
|
|
if (dup2(fd[1 - iopipe], iofd) == -1) { // now redirect
|
|
LOG_ERROR;
|
|
close(fd[1 - iopipe]);
|
|
_exit(-1);
|
|
}
|
|
else {
|
|
int MaxPossibleFileDescriptors = getdtablesize();
|
|
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
|
|
close(i); //close all dup'ed filedescriptors
|
|
if (execl("/bin/sh", "sh", "-c", Command, NULL) == -1) {
|
|
LOG_ERROR_STR(Command);
|
|
close(fd[1 - iopipe]);
|
|
_exit(-1);
|
|
}
|
|
}
|
|
_exit(0);
|
|
}
|
|
}
|
|
|
|
int cPipe::Close(void)
|
|
{
|
|
int ret = -1;
|
|
|
|
if (f) {
|
|
fclose(f);
|
|
f = NULL;
|
|
}
|
|
|
|
if (pid > 0) {
|
|
int status = 0;
|
|
int i = 5;
|
|
while (i > 0) {
|
|
ret = waitpid(pid, &status, WNOHANG);
|
|
if (ret < 0) {
|
|
if (errno != EINTR && errno != ECHILD) {
|
|
LOG_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
else if (ret == pid)
|
|
break;
|
|
i--;
|
|
cCondWait::SleepMs(100);
|
|
}
|
|
if (!i) {
|
|
kill(pid, SIGKILL);
|
|
ret = -1;
|
|
}
|
|
else if (ret == -1 || !WIFEXITED(status))
|
|
ret = -1;
|
|
pid = -1;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// --- SystemExec ------------------------------------------------------------
|
|
|
|
int SystemExec(const char *Command)
|
|
{
|
|
pid_t pid;
|
|
|
|
if ((pid = fork()) < 0) { // fork failed
|
|
LOG_ERROR;
|
|
return -1;
|
|
}
|
|
|
|
if (pid > 0) { // parent process
|
|
int status;
|
|
if (waitpid(pid, &status, 0) < 0) {
|
|
LOG_ERROR;
|
|
return -1;
|
|
}
|
|
return status;
|
|
}
|
|
else { // child process
|
|
int MaxPossibleFileDescriptors = getdtablesize();
|
|
for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++)
|
|
close(i); //close all dup'ed filedescriptors
|
|
if (execl("/bin/sh", "sh", "-c", Command, NULL) == -1) {
|
|
LOG_ERROR_STR(Command);
|
|
_exit(-1);
|
|
}
|
|
_exit(0);
|
|
}
|
|
}
|
|
|