mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
- The EIT information is now gathered in a separate thread. - The sytem time can now be synchronized to the time broadcast in the DVB data stream. This can be enabled in the "Setup" menu by setting "SetSystemTime" to 1. Note that this works only if VDR is running under a user id that has permisson to set the system time. - The new item "Schedule" in the "Main" menu opens VDR's EPG (thanks to Robert Schneider). See the MANUAL file for a detailed description. - The new setup parameters MarginStart and MarginStop define how long (in minutes) before the official start time of a broadcast VDR shall begin recording, and how long after the official end time it shall stop recording. These are used when a recording is programmed from the "Schedules" menu. - The delay value in the dvb.c.071.diff patch to the driver has been increased to '3', because on some systems the OSD was not displayed correctly. If you are running an already patched version 0.71 driver and encounter problems with the OSD, please make sure the parameter in the ddelay call is '3', not '2'. - Fixed initializing the RCU remote control code (didn't work after switching on the system). - Problematic characters in recording names (which can come from timers that are programmed via the "Schedules" menu) are now replaced by suitable substitutes.
550 lines
12 KiB
C
550 lines
12 KiB
C
/*
|
|
* tools.c: Various tools
|
|
*
|
|
* See the main source file 'vdr.c' for copyright information and
|
|
* how to reach the author.
|
|
*
|
|
* $Id: tools.c 1.22 2000/10/29 11:21:55 kls Exp $
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include "tools.h"
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#if defined(DEBUG_OSD)
|
|
#include <ncurses.h>
|
|
#endif
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <sys/time.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#define MaxBuffer 1000
|
|
|
|
int SysLogLevel = 3;
|
|
|
|
void writechar(int filedes, char c)
|
|
{
|
|
write(filedes, &c, sizeof(c));
|
|
}
|
|
|
|
void writeint(int filedes, int n)
|
|
{
|
|
write(filedes, &n, sizeof(n));
|
|
}
|
|
|
|
char readchar(int filedes)
|
|
{
|
|
char c;
|
|
read(filedes, &c, 1);
|
|
return c;
|
|
}
|
|
|
|
bool readint(int filedes, int &n)
|
|
{
|
|
return cFile::AnyFileReady(filedes, 0) && read(filedes, &n, sizeof(n)) == sizeof(n);
|
|
}
|
|
|
|
void purge(int filedes)
|
|
{
|
|
while (cFile::AnyFileReady(filedes, 0))
|
|
readchar(filedes);
|
|
}
|
|
|
|
char *readline(FILE *f)
|
|
{
|
|
static char buffer[MaxBuffer];
|
|
if (fgets(buffer, sizeof(buffer), f) > 0) {
|
|
int l = strlen(buffer) - 1;
|
|
if (l >= 0 && buffer[l] == '\n')
|
|
buffer[l] = 0;
|
|
return buffer;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
char *strn0cpy(char *dest, const char *src, size_t n)
|
|
{
|
|
char *s = dest;
|
|
for ( ; --n && (*dest = *src) != 0; dest++, src++) ;
|
|
*dest = 0;
|
|
return s;
|
|
}
|
|
|
|
char *strreplace(char *s, char c1, char c2)
|
|
{
|
|
char *p = s;
|
|
|
|
while (*p) {
|
|
if (*p == c1)
|
|
*p = c2;
|
|
p++;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
char *skipspace(const char *s)
|
|
{
|
|
while (*s && isspace(*s))
|
|
s++;
|
|
return (char *)s;
|
|
}
|
|
|
|
bool isempty(const char *s)
|
|
{
|
|
return !(s && *skipspace(s));
|
|
}
|
|
|
|
int time_ms(void)
|
|
{
|
|
static time_t t0 = 0;
|
|
struct timeval t;
|
|
if (gettimeofday(&t, NULL) == 0) {
|
|
if (t0 == 0)
|
|
t0 = t.tv_sec; // this avoids an overflow (we only work with deltas)
|
|
return (t.tv_sec - t0) * 1000 + t.tv_usec / 1000;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void delay_ms(int ms)
|
|
{
|
|
int t0 = time_ms();
|
|
while (time_ms() - t0 < ms)
|
|
;
|
|
}
|
|
|
|
bool isnumber(const char *s)
|
|
{
|
|
while (*s) {
|
|
if (!isdigit(*s))
|
|
return false;
|
|
s++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const char *AddDirectory(const char *DirName, const char *FileName)
|
|
{
|
|
static char *buf = NULL;
|
|
delete buf;
|
|
asprintf(&buf, "%s/%s", DirName && *DirName ? DirName : ".", FileName);
|
|
return buf;
|
|
}
|
|
|
|
#define DFCMD "df -m %s"
|
|
|
|
uint FreeDiskSpaceMB(const char *Directory)
|
|
{
|
|
//TODO Find a simpler way to determine the amount of free disk space!
|
|
uint Free = 0;
|
|
char *cmd = NULL;
|
|
asprintf(&cmd, DFCMD, Directory);
|
|
FILE *p = popen(cmd, "r");
|
|
if (p) {
|
|
char *s;
|
|
while ((s = readline(p)) != NULL) {
|
|
if (*s == '/') {
|
|
uint available;
|
|
sscanf(s, "%*s %*d %*d %u", &available);
|
|
Free = available;
|
|
break;
|
|
}
|
|
}
|
|
pclose(p);
|
|
}
|
|
else
|
|
esyslog(LOG_ERR, "ERROR: can't open pipe for cmd '%s'", cmd);
|
|
delete cmd;
|
|
return Free;
|
|
}
|
|
|
|
bool DirectoryOk(const char *DirName, bool LogErrors)
|
|
{
|
|
struct stat ds;
|
|
if (stat(DirName, &ds) == 0) {
|
|
if (S_ISDIR(ds.st_mode)) {
|
|
if (access(DirName, R_OK | W_OK | X_OK) == 0)
|
|
return true;
|
|
else if (LogErrors)
|
|
esyslog(LOG_ERR, "ERROR: can't access %s", DirName);
|
|
}
|
|
else if (LogErrors)
|
|
esyslog(LOG_ERR, "ERROR: %s is not a directory", DirName);
|
|
}
|
|
else if (LogErrors)
|
|
LOG_ERROR_STR(DirName);
|
|
return false;
|
|
}
|
|
|
|
bool MakeDirs(const char *FileName, bool IsDirectory)
|
|
{
|
|
bool result = true;
|
|
char *s = strdup(FileName);
|
|
char *p = s;
|
|
if (*p == '/')
|
|
p++;
|
|
while ((p = strchr(p, '/')) != NULL || IsDirectory) {
|
|
if (p)
|
|
*p = 0;
|
|
struct stat fs;
|
|
if (stat(s, &fs) != 0 || !S_ISDIR(fs.st_mode)) {
|
|
dsyslog(LOG_INFO, "creating directory %s", s);
|
|
if (mkdir(s, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) {
|
|
esyslog(LOG_ERR, "ERROR: %s: %s", s, strerror(errno));
|
|
result = false;
|
|
break;
|
|
}
|
|
}
|
|
if (p)
|
|
*p++ = '/';
|
|
else
|
|
break;
|
|
}
|
|
delete s;
|
|
return result;
|
|
}
|
|
|
|
bool RemoveFileOrDir(const char *FileName, bool FollowSymlinks)
|
|
{
|
|
struct stat st;
|
|
if (stat(FileName, &st) == 0) {
|
|
if (S_ISDIR(st.st_mode)) {
|
|
DIR *d = opendir(FileName);
|
|
if (d) {
|
|
struct dirent *e;
|
|
while ((e = readdir(d)) != NULL) {
|
|
if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) {
|
|
char *buffer;
|
|
asprintf(&buffer, "%s/%s", FileName, e->d_name);
|
|
if (FollowSymlinks) {
|
|
int size = strlen(buffer) * 2; // should be large enough
|
|
char *l = new char[size];
|
|
int n = readlink(buffer, l, size);
|
|
if (n < 0) {
|
|
if (errno != EINVAL)
|
|
LOG_ERROR_STR(buffer);
|
|
}
|
|
else if (n < size) {
|
|
l[n] = 0;
|
|
dsyslog(LOG_INFO, "removing %s", l);
|
|
if (remove(l) < 0)
|
|
LOG_ERROR_STR(l);
|
|
}
|
|
else
|
|
esyslog(LOG_ERR, "ERROR: symlink name length (%d) exceeded anticipated buffer size (%d)", n, size);
|
|
delete l;
|
|
}
|
|
dsyslog(LOG_INFO, "removing %s", buffer);
|
|
if (remove(buffer) < 0)
|
|
LOG_ERROR_STR(buffer);
|
|
delete buffer;
|
|
}
|
|
}
|
|
closedir(d);
|
|
}
|
|
else {
|
|
LOG_ERROR_STR(FileName);
|
|
return false;
|
|
}
|
|
}
|
|
dsyslog(LOG_INFO, "removing %s", FileName);
|
|
if (remove(FileName) == 0)
|
|
return true;
|
|
}
|
|
else
|
|
LOG_ERROR_STR(FileName);
|
|
return false;
|
|
}
|
|
|
|
bool CheckProcess(pid_t pid)
|
|
{
|
|
pid_t Pid2Check = pid;
|
|
int status;
|
|
pid = waitpid(Pid2Check, &status, WNOHANG);
|
|
if (pid < 0) {
|
|
if (errno != ECHILD)
|
|
LOG_ERROR;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void KillProcess(pid_t pid, int Timeout)
|
|
{
|
|
pid_t Pid2Wait4 = pid;
|
|
for (time_t t0 = time(NULL); time(NULL) - t0 < Timeout; ) {
|
|
int status;
|
|
pid_t pid = waitpid(Pid2Wait4, &status, WNOHANG);
|
|
if (pid < 0) {
|
|
if (errno != ECHILD)
|
|
LOG_ERROR;
|
|
return;
|
|
}
|
|
if (pid == Pid2Wait4)
|
|
return;
|
|
}
|
|
esyslog(LOG_ERR, "ERROR: process %d won't end (waited %d seconds) - terminating it...", Pid2Wait4, Timeout);
|
|
if (kill(Pid2Wait4, SIGTERM) < 0) {
|
|
esyslog(LOG_ERR, "ERROR: process %d won't terminate (%s) - killing it...", Pid2Wait4, strerror(errno));
|
|
if (kill(Pid2Wait4, SIGKILL) < 0)
|
|
esyslog(LOG_ERR, "ERROR: process %d won't die (%s) - giving up", Pid2Wait4, strerror(errno));
|
|
}
|
|
}
|
|
|
|
// --- cFile -----------------------------------------------------------------
|
|
|
|
bool cFile::files[FD_SETSIZE] = { false };
|
|
int cFile::maxFiles = 0;
|
|
|
|
cFile::cFile(void)
|
|
{
|
|
f = -1;
|
|
}
|
|
|
|
cFile::~cFile()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
bool cFile::Open(const char *FileName, int Flags, mode_t Mode)
|
|
{
|
|
if (!IsOpen())
|
|
return Open(open(FileName, Flags, Mode));
|
|
esyslog(LOG_ERR, "ERROR: attempt to re-open %s", FileName);
|
|
return false;
|
|
}
|
|
|
|
bool cFile::Open(int FileDes)
|
|
{
|
|
if (FileDes >= 0) {
|
|
if (!IsOpen()) {
|
|
f = FileDes;
|
|
if (f >= 0) {
|
|
if (f < FD_SETSIZE) {
|
|
if (f >= maxFiles)
|
|
maxFiles = f + 1;
|
|
if (!files[f])
|
|
files[f] = true;
|
|
else
|
|
esyslog(LOG_ERR, "ERROR: file descriptor %d already in files[]", f);
|
|
return true;
|
|
}
|
|
else
|
|
esyslog(LOG_ERR, "ERROR: file descriptor %d is larger than FD_SETSIZE (%d)", f, FD_SETSIZE);
|
|
}
|
|
}
|
|
else
|
|
esyslog(LOG_ERR, "ERROR: attempt to re-open file descriptor %d", FileDes);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void cFile::Close(void)
|
|
{
|
|
if (f >= 0) {
|
|
close(f);
|
|
files[f] = false;
|
|
f = -1;
|
|
}
|
|
}
|
|
|
|
int cFile::ReadString(char *Buffer, int Size)
|
|
{
|
|
int rbytes = 0;
|
|
bool wait = true;
|
|
|
|
while (Ready(wait)) {
|
|
int n = read(f, Buffer + rbytes, 1);
|
|
if (n == 0)
|
|
break; // EOF
|
|
if (n < 0) {
|
|
LOG_ERROR;
|
|
return -1;
|
|
}
|
|
rbytes += n;
|
|
if (rbytes == Size || Buffer[rbytes - 1] == '\n')
|
|
break;
|
|
wait = false;
|
|
}
|
|
return rbytes;
|
|
}
|
|
|
|
bool cFile::Ready(bool Wait)
|
|
{
|
|
return f >= 0 && AnyFileReady(f, Wait ? 1000 : 0);
|
|
}
|
|
|
|
bool cFile::AnyFileReady(int FileDes, int TimeoutMs)
|
|
{
|
|
#ifdef DEBUG_OSD
|
|
refresh();
|
|
#endif
|
|
fd_set set;
|
|
FD_ZERO(&set);
|
|
for (int i = 0; i < maxFiles; i++) {
|
|
if (files[i])
|
|
FD_SET(i, &set);
|
|
}
|
|
if (0 <= FileDes && FileDes < FD_SETSIZE && !files[FileDes])
|
|
FD_SET(FileDes, &set); // in case we come in with an arbitrary descriptor
|
|
if (TimeoutMs == 0)
|
|
TimeoutMs = 10; // load gets too heavy with 0
|
|
struct timeval timeout;
|
|
timeout.tv_sec = TimeoutMs / 1000;
|
|
timeout.tv_usec = (TimeoutMs % 1000) * 1000;
|
|
return select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0 && (FileDes < 0 || FD_ISSET(FileDes, &set));
|
|
}
|
|
|
|
bool cFile::FileReady(int FileDes, int TimeoutMs)
|
|
{
|
|
#ifdef DEBUG_OSD
|
|
refresh();
|
|
#endif
|
|
fd_set set;
|
|
struct timeval timeout;
|
|
FD_ZERO(&set);
|
|
FD_SET(FileDes, &set);
|
|
if (TimeoutMs < 100)
|
|
TimeoutMs = 100;
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = TimeoutMs * 1000;
|
|
return select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 0 && FD_ISSET(FileDes, &set);
|
|
}
|
|
|
|
// --- cListObject -----------------------------------------------------------
|
|
|
|
cListObject::cListObject(void)
|
|
{
|
|
prev = next = NULL;
|
|
}
|
|
|
|
cListObject::~cListObject()
|
|
{
|
|
}
|
|
|
|
void cListObject::Append(cListObject *Object)
|
|
{
|
|
next = Object;
|
|
Object->prev = this;
|
|
}
|
|
|
|
void cListObject::Unlink(void)
|
|
{
|
|
if (next)
|
|
next->prev = prev;
|
|
if (prev)
|
|
prev->next = next;
|
|
next = prev = NULL;
|
|
}
|
|
|
|
int cListObject::Index(void)
|
|
{
|
|
cListObject *p = prev;
|
|
int i = 0;
|
|
|
|
while (p) {
|
|
i++;
|
|
p = p->prev;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
// --- cListBase -------------------------------------------------------------
|
|
|
|
cListBase::cListBase(void)
|
|
{
|
|
objects = lastObject = NULL;
|
|
}
|
|
|
|
cListBase::~cListBase()
|
|
{
|
|
Clear();
|
|
while (objects) {
|
|
cListObject *object = objects->Next();
|
|
delete objects;
|
|
objects = object;
|
|
}
|
|
}
|
|
|
|
void cListBase::Add(cListObject *Object)
|
|
{
|
|
if (lastObject)
|
|
lastObject->Append(Object);
|
|
else
|
|
objects = Object;
|
|
lastObject = Object;
|
|
}
|
|
|
|
void cListBase::Del(cListObject *Object)
|
|
{
|
|
if (Object == objects)
|
|
objects = Object->Next();
|
|
if (Object == lastObject)
|
|
lastObject = Object->Prev();
|
|
Object->Unlink();
|
|
delete Object;
|
|
}
|
|
|
|
void cListBase::Move(int From, int To)
|
|
{
|
|
Move(Get(From), Get(To));
|
|
}
|
|
|
|
void cListBase::Move(cListObject *From, cListObject *To)
|
|
{
|
|
if (From && To) {
|
|
if (From->Index() < To->Index())
|
|
To = To->Next();
|
|
if (From == objects)
|
|
objects = From->Next();
|
|
if (From == lastObject)
|
|
lastObject = From->Prev();
|
|
From->Unlink();
|
|
if (To) {
|
|
if (To->Prev())
|
|
To->Prev()->Append(From);
|
|
From->Append(To);
|
|
}
|
|
else
|
|
lastObject->Append(From);
|
|
if (!From->Prev())
|
|
objects = From;
|
|
}
|
|
}
|
|
|
|
void cListBase::Clear(void)
|
|
{
|
|
while (objects) {
|
|
cListObject *object = objects->Next();
|
|
delete objects;
|
|
objects = object;
|
|
}
|
|
objects = lastObject = NULL;
|
|
}
|
|
|
|
cListObject *cListBase::Get(int Index) const
|
|
{
|
|
if (Index < 0)
|
|
return NULL;
|
|
cListObject *object = objects;
|
|
while (object && Index-- > 0)
|
|
object = object->Next();
|
|
return object;
|
|
}
|
|
|
|
int cListBase::Count(void) const
|
|
{
|
|
int n = 0;
|
|
cListObject *object = objects;
|
|
|
|
while (object) {
|
|
n++;
|
|
object = object->Next();
|
|
}
|
|
return n;
|
|
}
|
|
|