vdr/thread.c
Klaus Schmidinger 9279cb21cd Version 1.5.15
- Updated the Italian OSD texts (thanks to Diego Pierotto).
- Added option -i to the pictures plugin's pic2mpg to ignore unknown file types.
- Revoked the switch to the "multiproto" driver in order to make a new stable
  version before making this big switch and forcing all users to install a
  driver that is not yet in the kernel source. The removed code will reappear
  in version 1.7.0.
  Note that you may need to switch back to an older version of your channels.conf
  file if you have already used version 1.5.14, because it introduced new parameters.
- Added the new command line option --userdump to enable core dumps in case VDR
  is run as root with option -u (thanks to Hans-Werner Hilse).
- Speeded up anti-aliased font rendering by caching the blend indexes (based on
  a suggestion by Martin Wache).
- Fixed setting the OSD area in the pictures plugin.
- Ignoring "repeat" and "release" keys in the time search entry mode during replay,
  to avoid inadvertently leaving it in case a key is pressed too long (suggested
  by Andreas Brugger).
- Improved sending all frames to devices that can handle them in fast forward
  trick speeds, including subtitles (thanks to Timo Eskola).
- The section handler is now stopped before the device is destroyed, to avoid
  accessing file handles after they have become invalid (thanks to Reinhard
  Nissl for reporting an invalid access when ending VDR, and to Deti Fliegl for
  a patch that was used to implement StopSectionHandler()).
- Fixed setting the date in the channel display of the classic and sttng skins,
  to avoid unnecessary OSD access (thanks to Marco Schlüßler).
- The free disk space is now also displayed in the title of the "Recordings"
  menu (suggested by Walter Koch).
- Changed the message "Upcoming VPS recording!" to "Upcoming recording!" because
  it applies to non-VPS recordings as well.
- Fixed a loss of a timer's 'recording' flag after modifying it via MODT.
- Fixed detecting directories in cFileNameList::Load().
- Running the thread that removes deleted recordings at a low priority to (maybe)
  avoid stuttering replay in case the thread is run during replay.
- Limiting the length of the recording name in timers in case VDR is run with
  --vfat, in order to avoid names that are too long for Windows (suggested by Rolf
  Ahrenberg).
- Using cString::sprintf() instead of asprintf() (thanks to Wolfgang Rohdewald
  for pointing out a possible problem if the return value is not checked).
  Plugin authors may want to consider doing the same. For convenience there is now
  an additional version of cString::sprintf() that accepts a va_list parameter.
- When deleting the recording that is currently replayed, the replay is now
  stopped immediately (thanks to Mikko Matilainen for reporting a possible crash
  if the Info key is pressed after deleting the currently replayed recording).
- Updated the Russian OSD texts (thanks to Oleg Roitburd).
- When determining the amount of free disk space, any deleted (but not yet removed)
  recordings on different file systems (that are mounted under the video directory)
  are no longer taken into account.
- When running out of disk space during a recording, only such deleted or old
  recordings are removed, that actually are on the video directory file system(s).
  This prevents VDR from accidentally deleting recordings on other file systems,
  which would not add any free space to the video directory.
- Implemented the cStatus, cDevice and cPlayer functions for setting subtitle tracks
  in plugins (thanks to Petri Hintukainen).
- Added cStatus::TimerChange() to inform plugins about changes to the list of timers
  (based on a patch from Benedikt Elser).
- Added new cStatus functions to the 'status' plugin.
- Added missing #include <limits.h> to epg.c and menuitems.h (thanks to Ville Skyttä).
- The new function cSkin::SetScrollbar() can be implemented by skins to display
  a scrollbar in every list menu. The 'classic' and 'sttng' skins have been
  changed accordingly, as well as the 'skincurses' plugin.
- Introduced 'operator const void * ()' in cString to catch cases where operator*()
  should be used.
- Fixed calculating the scrollbar sizes in the skins.
2008-02-17 18:00:00 +01:00

542 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.64 2008/02/15 14:17:42 kls Exp $
*/
#include "thread.h"
#include <errno.h>
#include <linux/unistd.h>
#include <malloc.h>
#include <stdarg.h>
#include <stdlib.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 implicit 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 implicit 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;
cThread::cThread(const char *Description)
{
active = running = false;
childTid = 0;
childThreadId = 0;
description = NULL;
if (Description)
SetDescription("%s", 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);
description = strdup(cString::sprintf(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;
}
#define THREAD_STOP_TIMEOUT 3000 // ms to wait for a thread to stop before newly starting it
#define THREAD_STOP_SLEEP 30 // ms to sleep while waiting for a thread to stop
bool cThread::Start(void)
{
if (!running) {
if (active) {
// Wait until the previous incarnation of this thread has completely ended
// before starting it newly:
cTimeMs RestartTimeout;
while (!running && active && RestartTimeout.Elapsed() < THREAD_STOP_TIMEOUT)
cCondWait::SleepMs(THREAD_STOP_SLEEP);
}
if (!active) {
active = running = true;
if (pthread_create(&childTid, NULL, (void *(*) (void *))&StartThread, (void *)this) == 0) {
pthread_detach(childTid); // auto-reap
}
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 && WaitSeconds > -1) {
if (WaitSeconds > 0) {
for (time_t t0 = time(NULL) + WaitSeconds; time(NULL) < t0; ) {
if (!Active())
return;
cCondWait::SleepMs(10);
}
esyslog("ERROR: %s thread %d won't end (waited %d seconds) - canceling it...", description ? description : "", childThreadId, WaitSeconds);
}
pthread_cancel(childTid);
childTid = 0;
active = false;
}
}
tThreadId cThread::ThreadId(void)
{
return syscall(__NR_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;
}
const char *mode = "w";
int iopipe = 0;
if (pid > 0) { // parent process
if (strcmp(Mode, "r") == 0) {
mode = "r";
iopipe = 1;
}
close(fd[iopipe]);
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, bool Detached)
{
pid_t pid;
if ((pid = fork()) < 0) { // fork failed
LOG_ERROR;
return -1;
}
if (pid > 0) { // parent process
int status = 0;
if (waitpid(pid, &status, 0) < 0) {
LOG_ERROR;
return -1;
}
return status;
}
else { // child process
if (Detached) {
// Fork again and let first child die - grandchild stays alive without parent
if (fork() > 0)
_exit(0);
// Start a new session
pid_t sid = setsid();
if (sid < 0)
LOG_ERROR;
// close STDIN and re-open as /dev/null
int devnull = open("/dev/null", O_RDONLY);
if (devnull < 0 || dup2(devnull, 0) < 0)
LOG_ERROR;
}
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);
}
}