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