vdr/tools.c
Klaus Schmidinger a76a03c0d8 Version 1.3.1
- Fixed a lockup in the EPG scanner when no non-primary device was available
  (thanks to Martin Holst for reporting this one).
- Fixed a compiler warning about virtual cConfig::Load() functions (thanks to
  Lauri Tischler for reporting this one).
- Fixed a warning about character comparison in libsi/si.c (thanks to Lauri
  Tischler for reporting this one).
- The new parameter "Update channels" in the "Setup/DVB" menu can be used to
  control if and how channels will be automatically updated (see MANUAL).
  This has already been part of the 'autopid' patch by Andreas Schultz and has
  now been adopted.
- Fixed a crash in case there is no DVB hardware present (thanks to Sascha
  Volkenandt for reporting this one).
- Changed calculation of channel ids to make it work for tv stations that use
  the undefined NID value 0 (thanks to Teemu Rantanen for reporting this one).
- Enhanced the SDT filter to handle multi part sections.
- Added support for selecting preferred EPG languages (based upon a patch by
  Teemu Rantanen).
- Fixed a 'const' in libsi/si.h (thanks to Marcel Wiesweg).
- Fixed the 'su' call in 'runvdr' to make it work on systems that require the
  user name to appear before the command option (thanks to Robert Huitl).
- Fixed testing for matching section filters in case they are turned off (thanks
  to Marcel Wiesweg).
- In case of incomplete sections an error message is now logged only every 10
  seconds.
- Fixed a possible NULL pointer access in cEITScanner::Process() (thanks to
  Andreas Kool).
- The actual transponder data is now taken from the NIT and existing channels
  are adjusted if necessary. If the NIT contains new transponders, they are
  scanned for channels during the next EPG scan. Note that only the satellite
  branches are tested, cable and terrestrial need to be tested by somebody who
  actually has such equipment.
2004-01-11 18:00:00 +01:00

944 lines
21 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.78 2004/01/11 15:42:30 kls Exp $
*/
#include "tools.h"
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/vfs.h>
#include <time.h>
#include <unistd.h>
#include "i18n.h"
int SysLogLevel = 3;
int BCD2INT(int x)
{
return ((1000000 * BCDCHARTOINT((x >> 24) & 0xFF)) +
(10000 * BCDCHARTOINT((x >> 16) & 0xFF)) +
(100 * BCDCHARTOINT((x >> 8) & 0xFF)) +
BCDCHARTOINT( x & 0xFF));
}
ssize_t safe_read(int filedes, void *buffer, size_t size)
{
for (;;) {
ssize_t p = read(filedes, buffer, size);
if (p < 0 && errno == EINTR) {
dsyslog("EINTR while reading from file handle %d - retrying", filedes);
continue;
}
return p;
}
}
ssize_t safe_write(int filedes, const void *buffer, size_t size)
{
ssize_t p = 0;
ssize_t written = size;
const unsigned char *ptr = (const unsigned char *)buffer;
while (size > 0) {
p = write(filedes, ptr, size);
if (p < 0) {
if (errno == EINTR) {
dsyslog("EINTR while writing to file handle %d - retrying", filedes);
continue;
}
break;
}
ptr += p;
size -= p;
}
return p < 0 ? p : written;
}
void writechar(int filedes, char c)
{
safe_write(filedes, &c, sizeof(c));
}
char *readline(FILE *f)
{
static char buffer[MAXPARSEBUFFER];
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 *strcpyrealloc(char *dest, const char *src)
{
if (src) {
int l = max(dest ? strlen(dest) : 0, strlen(src)) + 1; // don't let the block get smaller!
dest = (char *)realloc(dest, l);
if (dest)
strcpy(dest, src);
else
esyslog("ERROR: out of memory");
}
else {
free(dest);
dest = NULL;
}
return dest;
}
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 && *p) {
if (*p == c1)
*p = c2;
p++;
}
return s;
}
char *strreplace(char *s, const char *s1, const char *s2)
{
char *p = strstr(s, s1);
if (p) {
int of = p - s;
int l = strlen(s);
int l1 = strlen(s1);
int l2 = strlen(s2);
if (l2 > l1)
s = (char *)realloc(s, strlen(s) + l2 - l1 + 1);
if (l2 != l1)
memmove(s + of + l2, s + of + l1, l - of - l1 + 1);
strncpy(s + of, s2, l2);
}
return s;
}
char *skipspace(const char *s)
{
while (*s && isspace(*s))
s++;
return (char *)s;
}
char *stripspace(char *s)
{
if (s && *s) {
for (char *p = s + strlen(s) - 1; p >= s; p--) {
if (!isspace(*p))
break;
*p = 0;
}
}
return s;
}
char *compactspace(char *s)
{
if (s && *s) {
char *t = stripspace(skipspace(s));
char *p = t;
while (p && *p) {
char *q = skipspace(p);
if (q - p > 1)
memmove(p + 1, q, strlen(q) + 1);
p++;
}
if (t != s)
memmove(s, t, strlen(t) + 1);
}
return s;
}
const char *strescape(const char *s, const char *chars)
{
static char *buffer = NULL;
const char *p = s;
char *t = NULL;
while (*p) {
if (strchr(chars, *p)) {
if (!t) {
buffer = (char *)realloc(buffer, 2 * strlen(s) + 1);
t = buffer + (p - s);
s = strcpy(buffer, s);
}
*t++ = '\\';
}
if (t)
*t++ = *p;
p++;
}
if (t)
*t = 0;
return s;
}
bool startswith(const char *s, const char *p)
{
while (*p) {
if (*p++ != *s++)
return false;
}
return true;
}
bool endswith(const char *s, const char *p)
{
const char *se = s + strlen(s) - 1;
const char *pe = p + strlen(p) - 1;
while (pe >= p) {
if (*pe-- != *se-- || (se < s && pe >= p))
return false;
}
return true;
}
bool isempty(const char *s)
{
return !(s && *skipspace(s));
}
int numdigits(int n)
{
char buf[16];
snprintf(buf, sizeof(buf), "%d", n);
return strlen(buf);
}
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)
{
if (!*s)
return false;
while (*s) {
if (!isdigit(*s))
return false;
s++;
}
return true;
}
const char *itoa(int n)
{
static char buf[16];
snprintf(buf, sizeof(buf), "%d", n);
return buf;
}
const char *AddDirectory(const char *DirName, const char *FileName)
{
static char *buf = NULL;
free(buf);
asprintf(&buf, "%s/%s", DirName && *DirName ? DirName : ".", FileName);
return buf;
}
int FreeDiskSpaceMB(const char *Directory, int *UsedMB)
{
if (UsedMB)
*UsedMB = 0;
int Free = 0;
struct statfs statFs;
if (statfs(Directory, &statFs) == 0) {
double blocksPerMeg = 1024.0 * 1024.0 / statFs.f_bsize;
if (UsedMB)
*UsedMB = int((statFs.f_blocks - statFs.f_bfree) / blocksPerMeg);
Free = int(statFs.f_bavail / blocksPerMeg);
}
else
LOG_ERROR_STR(Directory);
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("ERROR: can't access %s", DirName);
}
else if (LogErrors)
esyslog("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("creating directory %s", s);
if (mkdir(s, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) {
LOG_ERROR_STR(s);
result = false;
break;
}
}
if (p)
*p++ = '/';
else
break;
}
free(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 = MALLOC(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("removing %s", l);
if (remove(l) < 0)
LOG_ERROR_STR(l);
}
else
esyslog("ERROR: symlink name length (%d) exceeded anticipated buffer size (%d)", n, size);
free(l);
}
dsyslog("removing %s", buffer);
if (remove(buffer) < 0)
LOG_ERROR_STR(buffer);
free(buffer);
}
}
closedir(d);
}
else {
LOG_ERROR_STR(FileName);
return false;
}
}
dsyslog("removing %s", FileName);
if (remove(FileName) < 0) {
LOG_ERROR_STR(FileName);
return false;
}
}
else if (errno != ENOENT) {
LOG_ERROR_STR(FileName);
return false;
}
return true;
}
bool RemoveEmptyDirectories(const char *DirName, bool RemoveThis)
{
DIR *d = opendir(DirName);
if (d) {
bool empty = true;
struct dirent *e;
while ((e = readdir(d)) != NULL) {
if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..") && strcmp(e->d_name, "lost+found")) {
char *buffer;
asprintf(&buffer, "%s/%s", DirName, e->d_name);
struct stat st;
if (stat(buffer, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
if (!RemoveEmptyDirectories(buffer, true))
empty = false;
}
else
empty = false;
}
else {
LOG_ERROR_STR(buffer);
free(buffer);
return false;
}
free(buffer);
}
}
closedir(d);
if (RemoveThis && empty) {
dsyslog("removing %s", DirName);
if (remove(DirName) < 0) {
LOG_ERROR_STR(DirName);
return false;
}
}
return empty;
}
else
LOG_ERROR_STR(DirName);
return false;
}
char *ReadLink(const char *FileName)
{
char RealName[PATH_MAX];
const char *TargetName = NULL;
int n = readlink(FileName, RealName, sizeof(RealName) - 1);
if (n < 0) {
if (errno == ENOENT || errno == EINVAL) // file doesn't exist or is not a symlink
TargetName = FileName;
else // some other error occurred
LOG_ERROR_STR(FileName);
}
else if (n < int(sizeof(RealName))) { // got it!
RealName[n] = 0;
TargetName = RealName;
}
else
esyslog("ERROR: symlink's target name too long: %s", FileName);
return TargetName ? strdup(TargetName) : NULL;
}
bool SpinUpDisk(const char *FileName)
{
static char *buf = NULL;
for (int n = 0; n < 10; n++) {
free(buf);
if (DirectoryOk(FileName))
asprintf(&buf, "%s/vdr-%06d", *FileName ? FileName : ".", n);
else
asprintf(&buf, "%s.vdr-%06d", FileName, n);
if (access(buf, F_OK) != 0) { // the file does not exist
timeval tp1, tp2;
gettimeofday(&tp1, NULL);
int f = open(buf, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
// O_SYNC doesn't work on all file systems
if (f >= 0) {
close(f);
system("sync");
remove(buf);
gettimeofday(&tp2, NULL);
double seconds = (((long long)tp2.tv_sec * 1000000 + tp2.tv_usec) - ((long long)tp1.tv_sec * 1000000 + tp1.tv_usec)) / 1000000.0;
if (seconds > 0.5)
dsyslog("SpinUpDisk took %.2f seconds\n", seconds);
return true;
}
else
LOG_ERROR_STR(buf);
}
}
esyslog("ERROR: SpinUpDisk failed");
return false;
}
const char *WeekDayName(int WeekDay)
{
static char buffer[4];
WeekDay = WeekDay == 0 ? 6 : WeekDay - 1; // we start with monday==0!
if (0 <= WeekDay && WeekDay <= 6) {
const char *day = tr("MonTueWedThuFriSatSun");
day += WeekDay * 3;
strncpy(buffer, day, 3);
return buffer;
}
else
return "???";
}
const char *DayDateTime(time_t t)
{
static char buffer[32];
if (t == 0)
time(&t);
struct tm tm_r;
tm *tm = localtime_r(&t, &tm_r);
snprintf(buffer, sizeof(buffer), "%s %2d.%02d %02d:%02d", WeekDayName(tm->tm_wday), tm->tm_mday, tm->tm_mon + 1, tm->tm_hour, tm->tm_min);
return buffer;
}
// --- cPoller ---------------------------------------------------------------
cPoller::cPoller(int FileHandle, bool Out)
{
numFileHandles = 0;
Add(FileHandle, Out);
}
bool cPoller::Add(int FileHandle, bool Out)
{
if (FileHandle >= 0) {
for (int i = 0; i < numFileHandles; i++) {
if (pfd[i].fd == FileHandle)
return true;
}
if (numFileHandles < MaxPollFiles) {
pfd[numFileHandles].fd = FileHandle;
pfd[numFileHandles].events = Out ? POLLOUT : POLLIN;
numFileHandles++;
return true;
}
esyslog("ERROR: too many file handles in cPoller");
}
return false;
}
bool cPoller::Poll(int TimeoutMs)
{
if (numFileHandles) {
if (poll(pfd, numFileHandles, TimeoutMs) != 0)
return true; // returns true even in case of an error, to let the caller
// access the file and thus see the error code
}
return false;
}
// --- 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("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("ERROR: file descriptor %d already in files[]", f);
return true;
}
else
esyslog("ERROR: file descriptor %d is larger than FD_SETSIZE (%d)", f, FD_SETSIZE);
}
}
else
esyslog("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;
}
}
bool cFile::Ready(bool Wait)
{
return f >= 0 && AnyFileReady(f, Wait ? 1000 : 0);
}
bool cFile::AnyFileReady(int FileDes, int TimeoutMs)
{
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)
{
fd_set set;
struct timeval timeout;
FD_ZERO(&set);
FD_SET(FileDes, &set);
if (TimeoutMs >= 0) {
if (TimeoutMs < 100)
TimeoutMs = 100;
timeout.tv_sec = TimeoutMs / 1000;
timeout.tv_usec = (TimeoutMs % 1000) * 1000;
}
return select(FD_SETSIZE, &set, NULL, NULL, (TimeoutMs >= 0) ? &timeout : NULL) > 0 && FD_ISSET(FileDes, &set);
}
bool cFile::FileReadyForWriting(int FileDes, int TimeoutMs)
{
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, NULL, &set, NULL, &timeout) > 0 && FD_ISSET(FileDes, &set);
}
// --- cSafeFile -------------------------------------------------------------
cSafeFile::cSafeFile(const char *FileName)
{
f = NULL;
fileName = ReadLink(FileName);
tempName = fileName ? MALLOC(char, strlen(fileName) + 5) : NULL;
if (tempName)
strcat(strcpy(tempName, fileName), ".$$$");
}
cSafeFile::~cSafeFile()
{
if (f)
fclose(f);
unlink(tempName);
free(fileName);
free(tempName);
}
bool cSafeFile::Open(void)
{
if (!f && fileName && tempName) {
f = fopen(tempName, "w");
if (!f)
LOG_ERROR_STR(tempName);
}
return f != NULL;
}
bool cSafeFile::Close(void)
{
bool result = true;
if (f) {
if (ferror(f) != 0) {
LOG_ERROR_STR(tempName);
result = false;
}
if (fclose(f) < 0) {
LOG_ERROR_STR(tempName);
result = false;
}
f = NULL;
if (result && rename(tempName, fileName) < 0) {
LOG_ERROR_STR(fileName);
result = false;
}
}
else
result = false;
return result;
}
// --- cLockFile -------------------------------------------------------------
#define LOCKFILENAME ".lock-vdr"
#define LOCKFILESTALETIME 600 // seconds before considering a lock file "stale"
cLockFile::cLockFile(const char *Directory)
{
fileName = NULL;
f = -1;
if (DirectoryOk(Directory))
asprintf(&fileName, "%s/%s", Directory, LOCKFILENAME);
}
cLockFile::~cLockFile()
{
Unlock();
free(fileName);
}
bool cLockFile::Lock(int WaitSeconds)
{
if (f < 0 && fileName) {
time_t Timeout = time(NULL) + WaitSeconds;
do {
f = open(fileName, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (f < 0) {
if (errno == EEXIST) {
struct stat fs;
if (stat(fileName, &fs) == 0) {
if (abs(time(NULL) - fs.st_mtime) > LOCKFILESTALETIME) {
esyslog("ERROR: removing stale lock file '%s'", fileName);
if (remove(fileName) < 0) {
LOG_ERROR_STR(fileName);
break;
}
continue;
}
}
else if (errno != ENOENT) {
LOG_ERROR_STR(fileName);
break;
}
}
else {
LOG_ERROR_STR(fileName);
break;
}
if (WaitSeconds)
sleep(1);
}
} while (f < 0 && time(NULL) < Timeout);
}
return f >= 0;
}
void cLockFile::Unlock(void)
{
if (f >= 0) {
close(f);
remove(fileName);
f = -1;
}
}
// --- cListObject -----------------------------------------------------------
cListObject::cListObject(void)
{
prev = next = NULL;
}
cListObject::~cListObject()
{
}
void cListObject::Append(cListObject *Object)
{
next = Object;
Object->prev = this;
}
void cListObject::Insert(cListObject *Object)
{
prev = Object;
Object->next = 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();
}
void cListBase::Add(cListObject *Object, cListObject *After)
{
if (After && After != lastObject) {
After->Next()->Insert(Object);
After->Append(Object);
}
else {
if (lastObject)
lastObject->Append(Object);
else
objects = Object;
lastObject = Object;
}
}
void cListBase::Ins(cListObject *Object, cListObject *Before)
{
if (Before && Before != objects) {
Before->Prev()->Append(Object);
Before->Insert(Object);
}
else {
if (objects)
objects->Insert(Object);
else
lastObject = Object;
objects = Object;
}
}
void cListBase::Del(cListObject *Object, bool DeleteObject)
{
if (Object == objects)
objects = Object->Next();
if (Object == lastObject)
lastObject = Object->Prev();
Object->Unlink();
if (DeleteObject)
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);
lastObject = 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;
}
void cListBase::Sort(void)
{
bool swapped;
do {
swapped = false;
cListObject *object = objects;
while (object) {
if (object->Next() && *object->Next() < *object) {
Move(object->Next(), object);
swapped = true;
}
object = object->Next();
}
} while (swapped);
}