#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
+#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
-#include <vdr/tools.h>
-
#include "data.h"
#include "misc.h"
#include "scsetup.h"
return fm;
}
+// -- cStructItem --------------------------------------------------------------
+
+cStructItem::cStructItem(void)
+{
+ comment=0; deleted=special=false;
+}
+
+cStructItem::~cStructItem()
+{
+ free(comment);
+}
+
+void cStructItem::SetComment(const char *com)
+{
+ free(comment);
+ comment=strdup(com);
+}
+
+bool cStructItem::Save(FILE *f)
+{
+ fprintf(f,"%s%s\n",*ToString(false),comment?comment:"");
+ return ferror(f)==0;
+}
+
+// -- cCommentItem -------------------------------------------------------------
+
+class cCommentItem : public cStructItem {
+public:
+ cCommentItem(void);
+ virtual cString ToString(bool hide=false) { return ""; }
+ };
+
+cCommentItem::cCommentItem(void)
+{
+ SetSpecial();
+}
+
+// -- cStructLoader ------------------------------------------------------------
+
+cStructLoader::cStructLoader(const char *Type, const char *Filename, bool rw, bool miok, bool wat, bool verb)
+:lock(true)
+{
+ path=0; mtime=0; modified=loaded=disabled=false;
+ type=Type; filename=Filename;
+ readwrite=rw; missingok=miok; watch=wat; verbose=verb;
+ cStructLoaders::Register(this);
+}
+
+cStructLoader::~cStructLoader()
+{
+ free(path);
+}
+
+void cStructLoader::AddItem(cStructItem *n, const char *com, cStructItem *ref)
+{
+ n->SetComment(com);
+ ListLock(true);
+ cStructItem *a=0;
+ if(ref) { // insert before reference
+ for(a=First(); a; a=Next(a))
+ if(Next(a)==ref) break;
+ }
+ if(!a) { // insert before first non-special
+ for(a=First(); a;) {
+ cStructItem *nn=Next(a);
+ if(nn && !nn->Special()) break;
+ a=nn;
+ }
+ }
+ Add(n,a);
+ Modified();
+ ListUnlock();
+}
+
+void cStructLoader::DelItem(cStructItem *d, bool keep)
+{
+ if(d) {
+ d->Delete();
+ if(keep) {
+ cStructItem *n=new cCommentItem;
+ n->SetComment(cString::sprintf(";%s%s",*d->ToString(false),d->Comment()?d->Comment():""));
+ Add(n,d);
+ }
+ Modified();
+ }
+}
+
+void cStructLoader::SetCfgDir(const char *cfgdir)
+{
+ free(path);
+ path=strdup(AddDirectory(cfgdir,filename));
+}
+
+time_t cStructLoader::MTime(void)
+{
+ struct stat64 st;
+ if(stat64(path,&st)!=0) {
+ PRINTF(L_GEN_ERROR,"failed fstat %s: %s",path,strerror(errno));
+ PRINTF(L_GEN_WARN,"automatic reload of %s disabled",path);
+ st.st_mtime=0;
+ }
+ return st.st_mtime;
+}
+
+bool cStructLoader::Load(bool reload)
+{
+ if(reload && !watch) return true;
+ FILE *f=fopen(path,"r");
+ if(f) {
+ int curr_mtime=MTime();
+ if(access(path,R_OK|W_OK)!=0) {
+ if(errno!=EACCES)
+ PRINTF(L_GEN_ERROR,"failed access %s: %s",path,strerror(errno));
+ PRINTF(L_GEN_WARN,"no write permission on %s. Changes will not be saved!",path);
+ readwrite=false;
+ }
+
+ loaded=true;
+ ListLock(true);
+ bool doload=false;
+ if(!reload) {
+ Clear(); Modified(false);
+ mtime=curr_mtime;
+ doload=true;
+ }
+ else if(mtime && mtime<curr_mtime) {
+ PRINTF(L_CORE_LOAD,"detected change of %s",path);
+ if(IsModified())
+ PRINTF(L_CORE_LOAD,"discarding in-memory changes");
+ for(cStructItem *a=First(); a; a=Next(a)) DelItem(a);
+ Modified(false);
+ mtime=curr_mtime;
+ doload=true;
+ }
+ if(doload) {
+ PRINTF(L_GEN_INFO,"loading %s from %s",type,path);
+ int lineNum=0, num=0;
+ char buff[4096];
+ while(fgets(buff,sizeof(buff),f)) {
+ lineNum++;
+ if(!index(buff,'\n') && !feof(f)) {
+ PRINTF(L_GEN_ERROR,"file %s readbuffer overflow line#%d",path,lineNum);
+ loaded=false;
+ break;
+ }
+ strreplace(buff,'\n',0); strreplace(buff,'\r',0); // chomp
+ bool hasContent=false;
+ char *ls;
+ for(ls=buff; *ls; ls++) {
+ if(*ls==';' || *ls=='#') { // comment
+ if(hasContent)
+ while(ls>buff && ls[-1]<=' ') ls--; // search back to non-whitespace
+ break;
+ }
+ if(*ls>' ') hasContent=true; // line contains something usefull
+ }
+ cStructItem *it=0;
+ if(hasContent) {
+ char save=*ls;
+ *ls=0; it=ParseLine(buff); *ls=save;
+ if(!it) {
+ PRINTF(L_GEN_ERROR,"file %s has error in line #%d",path,lineNum);
+ ls=buff;
+ }
+ else num++;
+ }
+ else ls=buff;
+ if(!it) it=new cCommentItem;
+ if(it) {
+ it->SetComment(ls);
+ Add(it);
+ }
+ else {
+ PRINTF(L_GEN_ERROR,"out of memory loading file %s",path);
+ loaded=false;
+ break;
+ }
+ }
+ PRINTF(L_CORE_LOAD,"loaded %d %s from %s",num,type,path);
+ PostLoad();
+ }
+ ListUnlock();
+
+ fclose(f);
+ }
+ else {
+ if(verbose) PRINTF(L_GEN_ERROR,"failed open %s: %s",path,strerror(errno));
+ loaded=missingok;
+ }
+ if(!loaded) PRINTF(L_CORE_LOAD,"loading %s terminated with error. Changes will not be saved!",path);
+ return loaded;
+}
+
+void cStructLoader::Purge(void)
+{
+ ListLock(true);
+ for(cStructItem *it=First(); it;) {
+ cStructItem *n=Next(it);
+ if(it->Deleted()) Del(it);
+ it=n;
+ }
+ ListUnlock();
+}
+
+void cStructLoader::Save(void)
+{
+ ListLock(false);
+ if(readwrite && loaded && IsModified()) {
+ cSafeFile f(path);
+ if(f.Open()) {
+ for(cStructItem *it=First(); it; it=Next(it))
+ if(!it->Deleted() && !it->Save(f)) break;
+ f.Close();
+ mtime=MTime();
+ PRINTF(L_CORE_LOAD,"saved %s to %s",type,path);
+ Modified(false);
+ }
+ }
+ ListUnlock();
+}
+
+// -- cStructLoaders -----------------------------------------------------------
+
+#define RELOAD_TIMEOUT 20000
+#define PURGE_TIMEOUT 60000
+#define SAVE_TIMEOUT 5000
+
+cStructLoader *cStructLoaders::first=0;
+cTimeMs cStructLoaders::lastReload;
+cTimeMs cStructLoaders::lastPurge;
+cTimeMs cStructLoaders::lastSave;
+
+void cStructLoaders::Register(cStructLoader *ld)
+{
+ PRINTF(L_CORE_DYN,"structloaders: registering loader %s",ld->type);
+ ld->next=first;
+ first=ld;
+}
+
+void cStructLoaders::SetCfgDir(const char *cfgdir)
+{
+ for(cStructLoader *ld=first; ld; ld=ld->next)
+ ld->SetCfgDir(cfgdir);
+}
+
+void cStructLoaders::Load(bool reload)
+{
+ if(!reload || lastReload.TimedOut()) {
+ for(cStructLoader *ld=first; ld; ld=ld->next)
+ if(!ld->disabled) ld->Load(reload);
+ lastReload.Set(RELOAD_TIMEOUT);
+ }
+}
+
+void cStructLoaders::Save(void)
+{
+ if(lastSave.TimedOut()) {
+ for(cStructLoader *ld=first; ld; ld=ld->next)
+ if(!ld->disabled) ld->Save();
+ lastSave.Set(SAVE_TIMEOUT);
+ }
+}
+
+void cStructLoaders::Purge(void)
+{
+ if(lastPurge.TimedOut()) {
+ for(cStructLoader *ld=first; ld; ld=ld->next)
+ if(!ld->disabled) ld->Purge();
+ lastPurge.Set(PURGE_TIMEOUT);
+ }
+}
+
// -- cConfRead ----------------------------------------------------------------
bool cConfRead::ConfRead(const char *type, const char *filename, bool missingok)
cPlainKey::cPlainKey(bool CanSupersede)
{
- au=del=false;
super=CanSupersede;
}
return id>0xFF ? (id>0xFFFF ? 6 : 4) : 2;
}
-bool cPlainKey::Save(FILE *f)
-{
- fprintf(f,"%s\n",*ToString(false));
- return ferror(f)==0;
-}
-
cString cPlainKey::ToString(bool hide)
{
return cString::sprintf(hide ? "%c %.*X %s %.4s..." : "%c %.*X %s %s",type,IdSize(),id,*PrintKeyNr(),*Print());
cPlainKeyType *cPlainKeys::first=0;
cPlainKeys::cPlainKeys(void)
-:cLoader("KEY")
-//,cThread("ExternalAU")
-{
- mark=0;
-}
+:cStructList<cPlainKey>("keys",KEY_FILE,true,true,true,true)
+{}
void cPlainKeys::Register(cPlainKeyType *pkt, bool Super)
{
cPlainKey *cPlainKeys::FindKeyNoTrig(int Type, int Id, int Keynr, int Size, cPlainKey *key)
{
- Lock();
- if(key) key=Next(key); else key=First();
- while(key) {
- if(!key->IsInvalid() && key->type==Type && key->id==Id && key->keynr==Keynr && (Size<0 || key->Size()==Size)) break;
- key=Next(key);
- }
- Unlock();
+ ListLock(false);
+ for(key=key?Next(key):First(); key; key=Next(key))
+ if(key->Valid() && key->type==Type && key->id==Id && key->keynr==Keynr && (Size<0 || key->Size()==Size))
+ break;
+ ListUnlock();
return key;
}
-bool cPlainKeys::NewKey(int Type, int Id, int Keynr, void *Key, int Keylen)
+cPlainKey *cPlainKeys::NewFromType(int type)
+{
+ cPlainKeyType *pkt;
+ for(pkt=first; pkt; pkt=pkt->next)
+ if(pkt->type==type) return pkt->Create();
+ PRINTF(L_CORE_LOAD,"unknown key type '%c', adding dummy",type);
+ pkt=new cPlainKeyTypeDummy(type);
+ return pkt->Create();
+}
+
+bool cPlainKeys::AddNewKey(cPlainKey *nk, const char *reason)
{
- cPlainKey *k=0;
- while((k=FindKeyNoTrig(Type,Id,Keynr,-1,k)))
- if(k->Cmp(Key,Keylen)) return false;
+ cPlainKey *k;
+ for(k=0; (k=FindKeyNoTrig(nk->type,nk->id,nk->keynr,-1,k)); )
+ if(k->Cmp(nk)) return false;
+ cPlainKey *ref=0;
+ PRINTF(L_GEN_INFO,"key update for ID %s",*nk->ToString(true));
+ for(k=0; (k=FindKeyNoTrig(nk->type,nk->id,nk->keynr,nk->Size(),k)); ) {
+ if(nk->CanSupersede()) {
+ PRINTF(L_GEN_INFO,"supersedes key: %s",*k->ToString(true));
+ DelItem(k,true);
+ }
+ if(!ref) ref=k;
+ }
+ char stamp[32], com[256];
+ time_t tt=time(0);
+ struct tm tm_r;
+ strftime(stamp,sizeof(stamp),"%d.%m.%Y %T",localtime_r(&tt,&tm_r));
+ snprintf(com,sizeof(com)," ; %s %s",reason,stamp);
+ AddItem(nk,com,ref);
+ return true;
+}
+
+bool cPlainKeys::NewKey(int Type, int Id, int Keynr, void *Key, int Keylen)
+{
cPlainKey *nk=NewFromType(Type);
if(nk) {
nk->Set(Type,Id,Keynr,Key,Keylen);
- AddNewKey(nk,2,true);
- return true;
+ return AddNewKey(nk,"from AU");
}
- else PRINTF(L_GEN_ERROR,"no memory for new key ID %c %.2x!",Type,Id);
return false;
}
-void cPlainKeys::AddNewKey(cPlainKey *nk, int mode, bool log)
+bool cPlainKeys::NewKeyParse(char *line, const char *reason)
{
- if(mode>=1) {
- nk->SetAuto();
- if(log) PRINTF(L_GEN_INFO,"key update for ID %s",*nk->ToString(true));
- if(nk->CanSupersede()) {
- cPlainKey *k=0;
- while((k=FindKeyNoTrig(nk->type,nk->id,nk->keynr,nk->Size(),k))) {
- if(!k->IsInvalid()) {
- k->SetInvalid();
- if(k->IsAuto()) Modified();
- if(log) PRINTF(L_GEN_INFO,"supersedes key: %s%s",*k->ToString(true),k->IsAuto()?" (auto)":"");
- }
- }
- }
- }
- Lock();
- switch(mode) {
- case 0: Add(nk); break;
- case 1: if(!mark) Ins(nk); else Add(nk,mark);
- mark=nk;
- break;
- case 2: Ins(nk); Modified(); break;
- }
- Unlock();
-}
-
-bool cPlainKeys::Load(const char *cfgdir)
-{
- Lock();
- Clear(); mark=0;
- cString cname=AddDirectory(cfgdir,KEY_FILE);
- ConfRead("keys",cname);
- int n=Count();
- PRINTF(L_CORE_LOAD,"loaded %d keys from %s",n,*cname);
- if(n && LOG(L_CORE_KEYS)) {
- cPlainKey *dat=First();
- while(dat) {
- if(!dat->IsInvalid()) PRINTF(L_CORE_KEYS,"keys %s",*dat->ToString(false));
- dat=Next(dat);
- }
+ cPlainKey *nk=ParseLine(line);
+ return nk && AddNewKey(nk,reason);
+}
+
+cPlainKey *cPlainKeys::ParseLine(char *line)
+{
+ char *s=skipspace(line);
+ cPlainKey *k=NewFromType(toupper(*s));
+ if(k && !k->Parse(line)) { delete k; k=0; }
+ return k;
+}
+
+cString cPlainKeys::KeyString(int Type, int Id, int Keynr)
+{
+ cPlainKey *pk=NewFromType(Type);
+ if(pk) {
+ pk->type=Type; pk->id=Id; pk->keynr=Keynr;
+ return cString::sprintf("%c %.*X %s",Type,pk->IdSize(),Id,*pk->PrintKeyNr());
}
- Unlock();
- return (n!=0);
+ return "unknown";
+}
+
+void cPlainKeys::PostLoad(void)
+{
+ if(Count() && LOG(L_CORE_KEYS))
+ for(cPlainKey *dat=First(); dat; dat=Next(dat))
+ if(dat->Valid()) PRINTF(L_CORE_KEYS,"keys %s",*dat->ToString(false));
}
void cPlainKeys::HouseKeeping(void)
{
- cLoaders::SaveCache();
if(trigger.TimedOut()) {
trigger.Set(EXT_AU_INT);
if(externalAU) PRINTF(L_CORE_AUEXTERN,"triggered from housekeeping");
}
}
-bool cPlainKeys::NewKeyParse(const char *line)
-{
- cPlainKey *nk=NewFromType(toupper(line[0]));
- if(nk && nk->Parse(line)) {
- cPlainKey *k=0;
- while((k=FindKeyNoTrig(nk->type,nk->id,nk->keynr,-1,k)))
- if(k->Cmp(nk)) break;
- if(!k) {
- AddNewKey(nk,2,true);
- return true;
- }
- }
- return false;
-}
-
void cPlainKeys::Action(void)
{
last.Set(EXT_AU_MIN);
while(fgets(buff,sizeof(buff),pipe)) {
char *line=skipspace(stripspace(buff));
if(line[0]==0 || line[0]==';' || line[0]=='#') continue;
- NewKeyParse(line);
+ NewKeyParse(line,"from ExtAU");
}
}
pipe.Close();
PRINTF(L_CORE_AUEXTERN,"done (elapsed %d)",(int)start.Elapsed());
}
-
-cPlainKey *cPlainKeys::NewFromType(int type)
-{
- cPlainKeyType *pkt=first;
- while(pkt) {
- if(pkt->type==type) return pkt->Create();
- pkt=pkt->next;
- }
- PRINTF(L_CORE_LOAD,"unknown key type '%c', adding dummy",type);
- pkt=new cPlainKeyTypeDummy(type);
- return pkt->Create();
-}
-
-cString cPlainKeys::KeyString(int Type, int Id, int Keynr)
-{
- cPlainKey *pk=NewFromType(Type);
- if(pk) {
- pk->type=Type; pk->id=Id; pk->keynr=Keynr;
- return cString::sprintf("%c %.*X %s",Type,pk->IdSize(),Id,*pk->PrintKeyNr());
- }
- return "unknown";
-}
-
-bool cPlainKeys::ParseLine(const char *line, bool fromCache)
-{
- char *s=skipspace(line);
- cPlainKey *k=NewFromType(toupper(*s));
- if(k) {
- if(k->Parse((char *)line)) AddNewKey(k,fromCache?1:0,false);
- else delete k;
- return true;
- }
- return false;
-}
-
-bool cPlainKeys::Save(FILE *f)
-{
- bool res=true;
- Lock();
- cPlainKey *dat=First();
- while(dat) {
- if(dat->IsAuto() && !dat->IsInvalid()) {
- if(!dat->Save(f)) { res=false; break; }
- }
- dat=Next(dat);
- }
- Modified(!res);
- Unlock();
- return res;
-}
#include <vdr/thread.h>
#include <vdr/tools.h>
+
#include "misc.h"
+class cStructLoaders;
class cLoaders;
class cPidFilter;
class cPlainKeys;
extern cFileMaps filemaps;
+//--------------------------------------------------------------
+
+class cStructItem : public cSimpleItem {
+private:
+ char *comment;
+ bool deleted, special;
+protected:
+ void SetSpecial(void) { special=true; }
+public:
+ cStructItem(void);
+ virtual ~cStructItem();
+ virtual cString ToString(bool hide=false)=0;
+ bool Save(FILE *f);
+ //
+ void SetComment(const char *com);
+ const char *Comment(void) const { return comment; }
+ void Delete(void) { deleted=true; }
+ bool Deleted(void) const { return deleted; }
+ bool Special(void) const { return special; }
+ bool Valid(void) const { return !deleted && !special; }
+ };
+
+//--------------------------------------------------------------
+
+class cStructLoader : public cSimpleList<cStructItem> {
+friend class cStructLoaders;
+private:
+ cStructLoader *next;
+ //
+ cRwLock lock;
+ const char *type, *filename;
+ char *path;
+ time_t mtime;
+ bool modified, readwrite, missingok, loaded, disabled, watch, verbose;
+ //
+ time_t MTime(void);
+protected:
+ virtual cStructItem *ParseLine(char *line)=0;
+ void Modified(bool mod=true) { modified=mod; }
+ bool IsModified(void) const { return modified; }
+ void ListLock(bool rw) { lock.Lock(rw); }
+ void ListUnlock(void) { lock.Unlock(); }
+ virtual void PostLoad(void) {}
+public:
+ cStructLoader(const char *Type, const char *Filename, bool rw, bool miok, bool wat, bool verb);
+ virtual ~cStructLoader();
+ void AddItem(cStructItem *n, const char *com, cStructItem *ref);
+ void DelItem(cStructItem *d, bool keep=false);
+ //
+ void SetCfgDir(const char *cfgdir);
+ bool Load(bool reload);
+ void Save(void);
+ void Purge(void);
+ void Disable(void) { disabled=true; }
+ };
+
+//--------------------------------------------------------------
+
+template<class T> class cStructList : public cStructLoader {
+public:
+ cStructList<T>(const char *Type, const char *Filename, bool rw, bool miok, bool wat, bool verb):cStructLoader(Type,Filename,rw,miok,wat,verb) {}
+ T *First(void) const { return (T *)cStructLoader::First(); }
+ T *Last(void) const { return (T *)cStructLoader::Last(); }
+ T *Next(const T *item) const { return (T *)cStructLoader::Next(item); }
+ };
+
+//--------------------------------------------------------------
+
+class cStructLoaders {
+friend class cStructLoader;
+private:
+ static cStructLoader *first;
+ static cTimeMs lastReload, lastPurge, lastSave;
+ //
+ static void Register(cStructLoader *ld);
+public:
+ static void SetCfgDir(const char *cfgdir);
+ static void Load(bool reload);
+ static void Save(void);
+ static void Purge(void);
+ };
+
// ----------------------------------------------------------------
class cConfRead {
class cMutableKey;
-class cPlainKey : public cSimpleItem {
+class cPlainKey : public cStructItem {
friend class cPlainKeys;
friend class cMutableKey;
private:
- bool au, del, super;
+ bool super;
protected:
- void SetInvalid(void) { del=true; }
- void SetAuto(void) { au=true; }
- bool IsAuto(void) const { return au; }
- bool IsInvalid(void) const { return del; }
void SetSupersede(bool val) { super=val; }
bool CanSupersede(void) const { return super; }
virtual int IdSize(void);
//
cPlainKey(bool CanSupersede);
virtual bool Parse(const char *line)=0;
- bool Save(FILE *f);
- cString ToString(bool hide=false);
+ virtual cString ToString(bool hide=false);
virtual bool Cmp(void *Key, int Keylen)=0;
virtual bool Cmp(cPlainKey *k)=0;
virtual void Get(void *mem)=0;
extern const char *externalAU;
-class cPlainKeys : public cLoader, private cConfRead, private cThread, public cSimpleList<cPlainKey> {
+class cPlainKeys : private cThread, public cStructList<cPlainKey> {
friend class cPlainKeyType;
private:
static cPlainKeyType *first;
- cPlainKey *mark;
cTimeMs trigger, last;
cLastKey lastkey;
//
static void Register(cPlainKeyType *pkt, bool Super);
cPlainKey *NewFromType(int type);
- void AddNewKey(cPlainKey *nk, int mode, bool log);
+ bool AddNewKey(cPlainKey *nk, const char *reason);
void ExternalUpdate(void);
protected:
virtual void Action(void);
+ virtual void PostLoad(void);
public:
cPlainKeys(void);
- bool Load(const char *cfgdir);
- virtual bool Save(FILE *f);
- virtual bool ParseLine(const char *line, bool Au);
+ virtual cPlainKey *ParseLine(char *line);
cPlainKey *FindKey(int Type, int Id, int Keynr, int Size, cPlainKey *key=0);
cPlainKey *FindKeyNoTrig(int Type, int Id, int Keynr, int Size, cPlainKey *key=0);
void Trigger(int Type, int Id, int Keynr);
cString KeyString(int Type, int Id, int Keynr);
bool NewKey(int Type, int Id, int Keynr, void *Key, int Keylen);
- bool NewKeyParse(const char *line);
+ bool NewKeyParse(char *line, const char *reason);
void HouseKeeping(void);
};