mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
- Fixed saving the polarization parameter of channels that have a number in the 'source' parameter (thanks to Peter Seyringer for reporting this one). - Updated 'channels.conf.terr' (thanks to Andy Carter). - Updated 'channels.conf.cable' (thanks to Achim Lange). - First step towards a "unique channel ID". The channel ID is a human readable string, made up from several parameters of the channel's definition in the file 'channels.conf' (see man vdr(5) for details). In order for the "unique channel ID" to work, all channel definitions now must be unique with respect to the combination of their Source, Frequency and SID parameters. You may have to fix your 'channels.conf' manually if there are error messages in the log file when loading it. BE SURE TO MAKE A BACKUP COPY OF YOUR 'channels.conf' AND 'timers.conf' FILE BEFORE SWITCHING TO THIS VERSION, AND CHECK VERY CAREFULLY WHETHER YOUR TIMERS ARE STILL SET TO THE RIGHT CHANNELS! When reading an existing 'timers.conf', the channels will be identified as before by their numbers. As soon as this file is written back, the channel numbers will be replaced by the channel IDs. After that it is possible to manually edit the 'channels.conf' file and rearrange the channels without breaking the timers. Note that you can still define new timers manually by using the channel number. VDR will correctly identify the 'channel' parameter in a timer definition and use it as a channel number or a channel ID, respectively. Also, the SVDRP commands that return timer definitions will list them with channel numbers in order to stay compatible with existing applications. The channel ID is also used in the 'epg.data' file to allow EPG information from different sources to be stored, which would previously have been mixed up in case they were using the same 'service ID'. Note that the contents of an existing 'epg.data' file from a previous version will be silently ignored, since it doesn't contain the new channel IDs. When inserting EPG data into VDR via SVDRP you now also need to use the channel IDs. Currently the EPG data received from the DVB data stream only uses the 'Source' and 'Service ID' part of the channel ID. This makes it work for channels with the same service IDs on different sources (like satellites, cable or terrestrial). However, it doesn't work yet if the service IDs are not unique within a specific source. This will be fixed later. - Added missing SID parameters to 'channels.conf'. Some channels have been removed since they are apparently no longer broadcasted. - Removed dropping EPG events from "other" streams that have a duration of 86400 seconds or more (was introduced in version 1.1.10). This has become obsolete by the modification in version 1.1.13, which fixed fetching the current/next information to handle cases where the duration of an event is set wrongly and would last beyond the start time of the next event. Besides, the change in 1.1.10 broke handling EPG data for NVOD channels. - Fixed a compiler warning regarding cMenuChannels::Del() and MenuTimers::Del() hiding the base class virtual functions.
932 lines
21 KiB
C
932 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.73 2002/11/09 15:33:47 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;
|
|
|
|
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)
|
|
{
|
|
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 < 100)
|
|
TimeoutMs = 100;
|
|
timeout.tv_sec = TimeoutMs / 1000;
|
|
timeout.tv_usec = (TimeoutMs % 1000) * 1000;
|
|
return select(FD_SETSIZE, &set, NULL, NULL, &timeout) > 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 (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);
|
|
}
|
|
|