data.c
author nathan
Sat, 24 Oct 2009 11:31:53 +0800
branchtrunk
changeset 32 cea1b4f741be
parent 29 640ce9201139
child 34 afc13760179b
permissions -rw-r--r--
ignore errors while scaning recursive directories
     1 /*
     2  * MP3/MPlayer plugin to VDR (C++)
     3  *
     4  * (C) 2001-2009 Stefan Huelswitt <s.huelswitt@gmx.de>
     5  *
     6  * This code is free software; you can redistribute it and/or
     7  * modify it under the terms of the GNU General Public License
     8  * as published by the Free Software Foundation; either version 2
     9  * of the License, or (at your option) any later version.
    10  *
    11  * This code is distributed in the hope that it will be useful,
    12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  * GNU General Public License for more details.
    15  *
    16  * You should have received a copy of the GNU General Public License
    17  * along with this program; if not, write to the Free Software
    18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
    19  * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
    20  */
    21 
    22 #include <ctype.h>
    23 #include <dirent.h>
    24 #include <stdlib.h>
    25 #include <stdio.h>
    26 #include <stdarg.h>
    27 #include <errno.h>
    28 #include <sys/stat.h>
    29 #include <sys/types.h>
    30 #include <unistd.h>
    31 #include <dirent.h>
    32 #include <fnmatch.h>
    33 
    34 #include <vdr/tools.h>
    35 
    36 #include "common.h"
    37 #include "data.h"
    38 #include "data-src.h"
    39 
    40 // ----------------------------------------------------------------
    41 
    42 const char *mountscript = "mount.sh";
    43 
    44 char *Quote(const char *str)
    45 {
    46   char *nstr=MALLOC(char,strlen(str)*2);
    47   char *p=nstr;
    48   while(*str) {
    49     switch(*str) {
    50       case '$':  // dollar
    51       case '\\': // backslash
    52       case '\"': // double quote
    53       case '`':  // back tick
    54                  *p++='\\'; break;
    55       }
    56     *p++=*str++;
    57     }
    58   *p=0;
    59   return nstr;
    60 }
    61 
    62 char *AddPath(const char *dir, const char *filename)
    63 {
    64   char *name=aprintf("%s/%s",dir,filename);
    65   return name;
    66 }
    67 
    68 bool CheckVDRVersion(int Version, int Major, int Minor, const char *text)
    69 {
    70   static char vv[] = VDRVERSION;
    71   int version, major, minor;
    72   if(sscanf(vv,"%d.%d.%d",&version,&major,&minor)==3) {
    73     if(version<Version ||
    74        (version==Version && major<Major) ||
    75        (version==Version && major==Major && minor<Minor)) {
    76       if(text) {
    77         esyslog("ERROR: %s plugin needs at least VDR version %d.%d.%d",text,Version,Major,Minor);
    78         fprintf(stderr,"%s plugin needs at least VDR version %d.%d.%d\n",text,Version,Major,Minor);
    79         }
    80       return false;
    81       }
    82     }
    83   else esyslog("ERROR: cannot parse VDR version string '%s'",vv);
    84   return true;
    85 }
    86 
    87 char *aprintf(const char *fmt, ...)
    88 {
    89   va_list ap;
    90   va_start(ap,fmt);
    91   char *str=0;
    92   if(vasprintf(&str,fmt,ap)<0);
    93   va_end(ap);
    94   return str;
    95 }
    96 
    97 // -- cScanDir --------------------------------------------------------------
    98 
    99 bool cScanDir::ScanDir(cFileSource *src, const char *subdir, eScanType type, const char * const *spec, const char * const *excl, bool recursiv)
   100 {
   101   bool res=true;
   102   char *f=0;
   103   char *dir=aprintf(subdir ? "%s/%s":"%s",src->BaseDir(),subdir);
   104   DIR *d=opendir(dir);
   105   if(d) {
   106     struct dirent64 *e;
   107     while((e=readdir64(d))) {
   108       if(!strcmp(e->d_name,".") || !strcmp(e->d_name,"..")) continue;
   109       free(f);
   110       if(!(f=AddPath(dir,e->d_name))) continue;
   111       struct stat64 st;
   112       if(stat64(f,&st)<0) {
   113         esyslog("ERROR: stat(1) %s: %s",f,strerror(errno));
   114         continue;
   115         }
   116       if(S_ISLNK(st.st_mode)) {
   117         char *of=f; f=ReadLink(of); free(of);
   118         if(!f) continue;
   119         if(stat64(f,&st)<0) {
   120           esyslog("ERROR: stat(2) %s: %s",f,strerror(errno));
   121           continue;
   122           }
   123         }
   124       if(S_ISDIR(st.st_mode)) {
   125         if(type==stFile && recursiv) {
   126           char *s=aprintf(subdir ? "%2$s/%1$s":"%s",e->d_name,subdir);
   127           ScanDir(src,s,type,spec,excl,recursiv);
   128           free(s);
   129           continue;
   130           }
   131         if(type!=stDir) continue;
   132         }
   133       if(S_ISREG(st.st_mode)) {
   134         if(type!=stFile) continue;
   135         if(spec) {
   136           bool ok=false;
   137           for(const char * const *m=spec; *m; m++) {
   138             int n=fnmatch(*m,e->d_name,FNM_CASEFOLD);
   139             if(n==0) { ok=true; break; }
   140             if(n!=FNM_NOMATCH) esyslog("ERROR: fnmatch(1) %s: %s",*m,strerror(errno));
   141             }
   142           if(!ok) continue;
   143           }
   144         if(excl) {
   145           bool ok=true;
   146           for(const char * const *m=excl; *m; m++) {
   147             int n=fnmatch(*m,e->d_name,FNM_CASEFOLD);
   148             if(n==0) { ok=false; break; }
   149             if(n!=FNM_NOMATCH) esyslog("ERROR: fnmatch(2) %s: %s",*m,strerror(errno));
   150             }
   151           if(!ok) continue;
   152           }
   153         }
   154       DoItem(src,subdir,e->d_name);
   155       }
   156     closedir(d);
   157     }
   158   else {
   159     esyslog("ERROR: opendir %s: %s",dir,strerror(errno));
   160     res=false;
   161     }
   162   free(dir); free(f);
   163   return res;
   164 }
   165 
   166 // -- cFileObj --------------------------------------------------------------
   167 
   168 cFileObj::cFileObj(cFileSource *Source, const char *Subdir, const char *Name, const eObjType Type)
   169 {
   170   path=fpath=0;
   171   source=Source;
   172   subdir=Subdir ? strdup(Subdir):0;
   173   name=Name ? strdup(Name):0;
   174   type=Type;
   175   Set();
   176 }
   177 
   178 cFileObj::cFileObj(const cFileObj *obj)
   179 {
   180   path=fpath=0;
   181   source=obj->source;
   182   subdir=obj->subdir ? strdup(obj->subdir):0;
   183   name=obj->name ? strdup(obj->name):0;
   184   type=obj->type;
   185   Set();
   186 }
   187 
   188 cFileObj::~cFileObj()
   189 {
   190   free(name);
   191   free(subdir);
   192   free(path);
   193   free(fpath);
   194 }
   195 
   196 int cFileObj::Compare(const cListObject &ListObject) const
   197 {
   198   cFileObj *obj=(cFileObj *)&ListObject;
   199   if(type==otParent) return obj->type==otParent ? 0:-1;
   200   if(obj->type==otParent) return 1;
   201   if(type==otBase) return obj->type==otBase ? 0:1;
   202   if(obj->type==otBase) return -1;
   203   if(type!=obj->type) {
   204     if(type==otFile) return 1;
   205     return -1;
   206     }
   207   return strcasecmp(path,obj->path);
   208 }
   209 
   210 void cFileObj::SplitAndSet(const char *Path)
   211 {
   212   free(subdir); subdir=0;
   213   const char *p=Path;
   214   if(Path[0]=='/') {
   215     int l=strlen(source->BaseDir());
   216     if(!strncasecmp(Path,source->BaseDir(),l)) p+=l+1;
   217     else {
   218       l=strlen(source->RealBaseDir());
   219       if(!strncasecmp(Path,source->RealBaseDir(),l)) p+=l+1;
   220       else {
   221         char buff[strlen(Path)+5];
   222         strcpy(buff,"/");
   223         p++;
   224         while(1) {
   225           char real[PATH_MAX+1];
   226           if(!realpath(buff,real)) {
   227             if(errno!=ENOENT && errno!=ENOTDIR)
   228               esyslog("ERROR: realpath: %s: %s",buff,strerror(errno));
   229             p=Path+1;
   230             break;
   231             }
   232           if(!strncasecmp(real,source->RealBaseDir(),l))
   233             break;
   234           const char *r=index(p,'/');
   235           if(!r) {
   236             esyslog("ERROR: can't find source basedir in '%s'. Outside source?",Path);
   237             p=Path+1;
   238             break;
   239             }
   240           strn0cpy(buff,Path,r-Path+1);
   241           p=r+1;
   242           }
   243         }
   244       }
   245     }
   246 
   247   const char *s=rindex(p,'/');
   248   if(s) {
   249     const int l=s-p+1;
   250     subdir=MALLOC(char,l);
   251     if(subdir) strn0cpy(subdir,p,l);
   252     SetName(s+1);
   253     }
   254   else
   255     SetName(p);
   256 }
   257 
   258 void cFileObj::SetName(const char *Name)
   259 {
   260   free(name);
   261   name=Name ? strdup(Name):0;
   262   Set();
   263 }
   264 
   265 void cFileObj::Set(void)
   266 {
   267   free(path);
   268   path=aprintf(subdir ? "%2$s/%1$s":"%s",name,subdir);
   269   free(fpath); fpath=0;
   270   MakeFullName(&fpath,name);
   271 }
   272 
   273 void cFileObj::MakeFullName(char **fp, const char *Name)
   274 {
   275   *fp=aprintf(subdir ? "%1$s/%3$s/%2$s":"%s/%s",source->BaseDir(),Name,subdir);
   276 }
   277 
   278 bool cFileObj::GuessType(void)
   279 {
   280   struct stat64 ds;
   281   if(!stat64(fpath,&ds)) {
   282     if(S_ISREG(ds.st_mode))      type=otFile;
   283     else if(S_ISDIR(ds.st_mode)) type=subdir ? otDir:otBase;
   284     else return false;
   285     return true;
   286     }
   287   return false;
   288 }
   289 
   290 bool cFileObj::Exists(void)
   291 {
   292   if(type==otFile) {
   293     struct stat64 ds;
   294     if(!stat64(fpath,&ds) &&
   295        S_ISREG(ds.st_mode) &&
   296        !access(fpath,R_OK)) return true;
   297     }
   298   return false;
   299 }
   300 
   301 bool cFileObj::TestName(const char *newName)
   302 {
   303   bool r=false;
   304   if(type==otFile) {
   305     char *fname;
   306     MakeFullName(&fname,newName);
   307     if(access(fname,F_OK)==0) r=true;
   308     free(fname);
   309     }
   310   return r;
   311 }
   312 
   313 bool cFileObj::Rename(const char *newName)
   314 {
   315   bool r=false;
   316   if(type==otFile) {
   317     char *fname;
   318     MakeFullName(&fname,newName);
   319     if(access(fname,F_OK) && (!rename(fpath,fname))) {
   320       SetName(newName);
   321       r=true;
   322       }
   323     free(fname);
   324     }
   325   return r;
   326 }
   327 
   328 bool cFileObj::Create(const char *newName)
   329 {
   330   bool r=false;
   331   if(type==otFile) {
   332     char *fname;
   333     MakeFullName(&fname,newName);
   334     FILE *newf;
   335     if(access(fname,F_OK) && (newf=fopen(fname,"w"))) {
   336       fclose(newf);
   337       SetName(newName);
   338       r=true;
   339       }
   340     free(fname);
   341     }
   342   return r;
   343 }
   344 
   345 bool cFileObj::Delete(void)
   346 {
   347   if(type==otFile && !unlink(fpath)) return true;
   348   return false;
   349 }
   350 
   351 // -- cDirList --------------------------------------------------------------
   352 
   353 bool cDirList::Load(cFileSource *src, const char *subdir, const char * const *excl)
   354 {
   355   static const char *excl_s[] = { ".*",0 };
   356 
   357   bool res=false;
   358   Clear();
   359   if(subdir) Add(new cFileObj(src,subdir,"..",otParent));
   360   otype=otDir;
   361   if(ScanDir(src,subdir,stDir,0,0,false)) {
   362     otype=otFile;
   363     if(!excl) excl=excl_s;
   364     if(ScanDir(src,subdir,stFile,src->Include(),excl,false)) res=true;
   365     }
   366   Sort();
   367   return res;
   368 }
   369 
   370 void cDirList::DoItem(cFileSource *src, const char *subdir, const char *name)
   371 {
   372   Add(new cFileObj(src,subdir,name,otype));
   373 }
   374 
   375 // -- cFileSource --------------------------------------------------------------
   376 
   377 cFileSource::cFileSource(void)
   378 {
   379   browsedir=browseparent=0;
   380   basedir=realbasedir=description=0; useCount=0;
   381   needsmount=false;
   382   include=0; incCount=0;
   383 }
   384 
   385 cFileSource::cFileSource(const char *Basedir, const char *Description, const bool NeedsMount, const char *Include)
   386 {
   387   browsedir=browseparent=0;
   388   basedir=realbasedir=description=0; useCount=0;
   389   include=0; incCount=0;
   390   Set(Basedir,Description,NeedsMount,Include);
   391 }
   392 
   393 cFileSource::~cFileSource()
   394 {
   395   ClearRemember();
   396   Clear();
   397 }
   398 
   399 void cFileSource::Clear(void)
   400 {
   401   free(basedir); basedir=0;
   402   free(realbasedir); realbasedir=0;
   403   free(description); description=0;
   404   for(int i=0; i<incCount; i++) free(include[i]);
   405   free(include); include=0; incCount=0;
   406 }
   407 
   408 void cFileSource::Set(const char *Basedir, const char *Description, const bool NeedsMount, const char *Include)
   409 {
   410   Clear();
   411   basedir=strdup(Basedir);
   412   description=strdup(Description);
   413   if(Include) {
   414     do {
   415       char *s=index(Include,'/');
   416       int l=s ? s-Include : strlen(Include);
   417       if(l) {
   418         char **s=(char **)realloc(include,(incCount+2)*sizeof(char *));
   419         if(s) {
   420           include=s;
   421           include[incCount]=strndup(Include,l);
   422           incCount++;
   423           include[incCount]=0;
   424           }
   425         }
   426       Include+=l+(s ? 1:0);
   427       } while(*Include>0);
   428     }
   429 #ifdef DEBUG
   430   if(include) {
   431     printf("sources: filesource %s includes (count=%d):",basedir,incCount);
   432     for(int i=0; i<incCount; i++) printf(" '%s'",include[i]);
   433     printf("\n");
   434     }
   435   else
   436     printf("sources: filesource %s has no includes set\n",basedir);
   437 #endif
   438   needsmount=NeedsMount;
   439 
   440   realbasedir=MALLOC(char,PATH_MAX+1);
   441   if(realpath(basedir,realbasedir)) {
   442     if(strcmp(basedir,realbasedir)) { esyslog("WARNING: source base %s expands to %s",basedir,realbasedir); }
   443     }
   444   else {
   445     switch(errno) {
   446       case EACCES:  esyslog("ERROR: source base %s permission denied",basedir); break;
   447       case ENOENT:  esyslog("ERROR: source base %s not found",basedir); break;
   448       case ENOTDIR: esyslog("ERROR: source base %s has invalid path",basedir); break;
   449       default:      esyslog("ERROR: source base %s realpath: %s",basedir,strerror(errno)); break;
   450       }
   451     strn0cpy(realbasedir,basedir,PATH_MAX);
   452     }
   453 }
   454 
   455 void cFileSource::SetRemember(const char *dir, const char *parent)
   456 {
   457   ClearRemember();
   458   if(dir) browsedir=strdup(dir);
   459   if(parent) browseparent=strdup(parent);
   460 }
   461 
   462 void cFileSource::ClearRemember(void)
   463 {
   464   free(browsedir); browsedir=0;
   465   free(browseparent); browseparent=0;
   466 }
   467 
   468 bool cFileSource::GetRemember(char * &dir, char * &parent)
   469 {
   470   dir=parent=0;
   471   if(browsedir) {
   472     if(browseparent) parent=strdup(browseparent);
   473     dir=strdup(browsedir);
   474     return true;
   475     }
   476   return false;
   477 }
   478 
   479 bool cFileSource::Parse(char *s)
   480 {
   481   char base[256], des[256], incl[256];
   482   int needsmount, n;
   483   if((n=sscanf(s,"%255[^;];%255[^;];%d;%255[^;]",base,des,&needsmount,incl))>=3) {
   484     char *base2=skipspace(stripspace(base));
   485     int l=strlen(base2);
   486     while(l>0 && base2[l-1]=='/') {
   487       esyslog("WARNING: removing trailing '/' from base %s",base2);
   488       base2[l-1]=0;
   489       l--;
   490       }
   491     Set(base2,skipspace(stripspace(des)),needsmount!=0,n>3?skipspace(stripspace(incl)):0);
   492 
   493     // do some checking of the basedir and issue a warning if apropriate
   494     if(access(realbasedir,R_OK)) { esyslog("WARNING: source base %s not found/permission denied",realbasedir); }
   495     else {
   496       struct stat64 ds;
   497       if(lstat64(realbasedir,&ds)) { esyslog("WARNING: can't stat source base %s",realbasedir); }
   498       else if(!S_ISDIR(ds.st_mode)) { esyslog("WARNING: source base %s is not a directory",realbasedir); }
   499       }
   500     return true;
   501     }
   502   return false;
   503 }
   504 
   505 bool cFileSource::Action(eAction act)
   506 {
   507   static const char *str[] = { "mount","unmount","eject","status" };
   508   
   509   char *cmd=aprintf("%s %s %s",mountscript,str[act],basedir);
   510   bool res=(system(cmd)==0);
   511   free(cmd);
   512   return res;
   513 }
   514 
   515 bool cFileSource::Mount(void)
   516 {
   517   bool res=false;
   518   if(needsmount && (res=Action(acMount))) ClearRemember();
   519   return res;
   520 }
   521 
   522 bool cFileSource::Unmount(void)
   523 {
   524   bool res=false;
   525   if(needsmount) {
   526     if(!useCount && (res=Action(acUnmount))) ClearRemember();
   527     }
   528   return res;
   529 }
   530 
   531 bool cFileSource::Eject(void)
   532 {
   533   bool res=false;
   534   if(needsmount) {
   535     if(!useCount && (res=Action(acEject))) ClearRemember();
   536     }
   537   return res;
   538 }
   539 
   540 bool cFileSource::Status(void)
   541 {
   542   if(needsmount) return Action(acStatus);
   543   return true;
   544 }
   545 
   546 // -- cFileSources --------------------------------------------------------------
   547 
   548 bool cFileSources::Load(const char *filename, bool dummy)
   549 {
   550   if(cConfig<cFileSource>::Load(filename,true)) {
   551     SetSource(First());
   552     return true;
   553     }
   554   return false;
   555 }
   556 
   557 cFileSource *cFileSources::FindSource(const char *filename)
   558 {
   559   cFileSource *src=First();
   560   while(src) {
   561     if(startswith(filename,src->RealBaseDir())) return src;
   562     src=Next(src);
   563     }
   564   return 0;
   565 }