1 /*
2 * lftp - file transfer program
3 *
4 * Copyright (c) 1996-2017 by Alexander V. Lukyanov (lav@yars.free.net)
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program 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, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <config.h>
21
22 #include <fnmatch.h>
23 #include <ctype.h>
24 #include <unistd.h>
25 #include <stdlib.h>
26 #include <cmath>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <errno.h>
30 #include <limits.h>
31 CDECL_BEGIN
32 #include "regex.h"
33 CDECL_END
34 #include "ResMgr.h"
35 #include "SMTask.h"
36 #include "misc.h"
37 #include "StringSet.h"
38 #include "log.h"
39
40 xlist_head<Resource> Resource::all_list;
41 xmap<ResType*> *ResType::types_by_name;
42
VarNameCmp(const char * good_name,const char * name)43 int ResType::VarNameCmp(const char *good_name,const char *name)
44 {
45 int res=EXACT_PREFIX+EXACT_NAME;
46 const char *colon=strchr(good_name,':');
47 if(colon && !strchr(name,':'))
48 {
49 good_name=colon+1;
50 res|=SUBSTR_PREFIX;
51 }
52 while(*good_name || *name)
53 {
54 if(*good_name==*name
55 || (*good_name && *name && strchr("-_",*good_name) && strchr("-_",*name)))
56 {
57 good_name++;
58 name++;
59 continue;
60 }
61 if(*name && !*good_name)
62 return DIFFERENT;
63 if((!*name && *good_name)
64 || (strchr("-_:",*name) && !strchr("-_:",*good_name)))
65 {
66 good_name++;
67 if(strchr(name,':'))
68 res|=SUBSTR_PREFIX;
69 else
70 res|=SUBSTR_NAME;
71 continue;
72 }
73 return DIFFERENT;
74 }
75 return res;
76 }
77
IsAlias() const78 bool ResType::IsAlias() const
79 {
80 return closure_valid==ResMgr::AliasValidate;
81 }
82
FindVar(const char * name,const ResType ** type,const char ** re_closure)83 const char *ResType::FindVar(const char *name,const ResType **type,const char **re_closure)
84 {
85 const ResType *exact_proto=0;
86 const ResType *exact_name=0;
87 int sub=0;
88
89 *type=types_by_name->lookup(name);
90 if(*type)
91 goto found; // exact match
92
93 for(const ResType *type_scan=types_by_name->each_begin(); type_scan; type_scan=types_by_name->each_next())
94 {
95 switch(VarNameCmp(type_scan->name,name))
96 {
97 case EXACT_PREFIX+EXACT_NAME:
98 *type=type_scan;
99 return 0;
100 case EXACT_PREFIX+SUBSTR_NAME:
101 if(!exact_proto && !exact_name)
102 sub=0;
103 exact_proto=*type=type_scan;
104 sub++;
105 break;
106 case SUBSTR_PREFIX+EXACT_NAME:
107 if(!exact_proto && !exact_name)
108 sub=0;
109 exact_name=*type=type_scan;
110 sub++;
111 break;
112 case SUBSTR_PREFIX+SUBSTR_NAME:
113 if(exact_proto || exact_name)
114 break;
115 sub++;
116 *type=type_scan;
117 break;
118 default:
119 break;
120 }
121 }
122 if(!*type && sub==0)
123 return _("no such variable");
124 if(sub==1)
125 goto found;
126 *type=0;
127 return _("ambiguous variable name");
128
129 found:
130 if((*type)->IsAlias()) {
131 const char *alias_c=(*type)->GetAliasTarget();
132 char *alias=alloca_strdup(alias_c);
133 char *slash=strchr(alias,'/');
134 if(slash) {
135 *slash=0;
136 if(re_closure)
137 *re_closure=alias_c+(slash+1-alias);
138 }
139 *type=types_by_name->lookup(alias);
140 if(!*type)
141 return "invalid compatibility alias";
142 }
143 return 0;
144 }
145
FindRes(const char * name)146 const ResType *ResType::FindRes(const char *name)
147 {
148 const ResType *type;
149 const char *msg=FindVar(name,&type);
150 if(msg)
151 return 0;
152 return type;
153 }
154
Set(const char * name,const char * cclosure,const char * cvalue,bool def)155 const char *ResType::Set(const char *name,const char *cclosure,const char *cvalue,bool def)
156 {
157 ResType *type;
158 // find type of given variable
159 const char *msg=FindVar(name,&type,&cclosure);
160 if(msg)
161 return msg;
162
163 return type->Set(cclosure,cvalue,def);
164 }
165
Set(const char * cclosure,const char * cvalue,bool def)166 const char *ResType::Set(const char *cclosure,const char *cvalue,bool def)
167 {
168 const char *msg=0;
169
170 xstring_c value(cvalue);
171 if(value && val_valid && (msg=val_valid(&value))!=0)
172 return msg;
173
174 xstring_c closure(cclosure);
175 if((closure || closure_valid==ResMgr::HasClosure)
176 && closure_valid && (msg=closure_valid(&closure))!=0)
177 return msg;
178
179 bool need_reconfig=false;
180
181 xlist_for_each(Resource,*(type_value_list),node,scan)
182 {
183 // find the old value
184 if(closure==scan->closure || !xstrcmp(scan->closure,closure))
185 {
186 if(def)
187 return 0;
188 need_reconfig=true;
189 delete scan;
190 break;
191 }
192 }
193 if(value)
194 {
195 (void)new Resource(this,closure,value,def);
196 need_reconfig=true;
197 }
198 if(need_reconfig)
199 ResClient::ReconfigAll(name);
200 return 0;
201 }
202
ResourceCompare(const Resource * ar,const Resource * br)203 int ResMgr::ResourceCompare(const Resource *ar,const Resource *br)
204 {
205 int diff=strcmp(ar->type->name,br->type->name);
206 if(diff)
207 return diff;
208 if(ar->closure==br->closure)
209 return 0;
210 if(ar->closure==0)
211 return -1;
212 if(br->closure==0)
213 return 1;
214 return strcmp(ar->closure,br->closure);
215 }
216
Format(xstring & buf) const217 void Resource::Format(xstring& buf) const
218 {
219 buf.appendf("set %s",type->name);
220 const char *s=closure;
221 if(s)
222 {
223 buf.append('/');
224 bool par=false;
225 if(strcspn(s," \t>|;&")!=strlen(s))
226 par=true;
227 if(par)
228 buf.append('"');
229 while(*s)
230 {
231 if(strchr("\"\\",*s))
232 buf.append('\\');
233 buf.append(*s++);
234 }
235 if(par)
236 buf.append('"');
237 }
238 buf.append(' ');
239 s=value;
240
241 bool par=false;
242 if(*s==0 || strcspn(s," \t>|;&")!=strlen(s))
243 par=true;
244 if(par)
245 buf.append('"');
246 while(*s)
247 {
248 if(strchr("\"\\",*s))
249 buf.append('\\');
250 buf.append(*s++);
251 }
252 if(par)
253 buf.append('"');
254 buf.append('\n');
255 }
256
PResourceCompare(const Resource * const * a,const Resource * const * b)257 static int PResourceCompare(const Resource *const*a,const Resource *const*b)
258 {
259 return ResMgr::ResourceCompare(*a,*b);
260 }
RefResourceCompare(const Ref<Resource> * a,const Ref<Resource> * b)261 static int RefResourceCompare(const Ref<Resource> *a,const Ref<Resource> *b)
262 {
263 return ResMgr::ResourceCompare(*a,*b);
264 }
265
Format(bool with_defaults,bool only_defaults)266 char *ResType::Format(bool with_defaults,bool only_defaults)
267 {
268 RefArray<Resource> created;
269 if(with_defaults || only_defaults)
270 {
271 for(ResType *dscan=types_by_name->each_begin(); dscan; dscan=types_by_name->each_next())
272 if((only_defaults || dscan->SimpleQuery(0)==0) && !dscan->IsAlias())
273 created.append(new Resource(dscan,
274 0,xstrdup(dscan->defvalue?dscan->defvalue:"(nil)")));
275 }
276
277 xstring buf("");
278
279 if(!only_defaults)
280 {
281 // just created Resources are also in all_list.
282 xarray<const Resource*> arr;
283 xlist_for_each(Resource,Resource::all_list,node,scan) {
284 if(!scan->def || with_defaults)
285 arr.append(scan);
286 }
287 arr.qsort(PResourceCompare);
288 for(int i=0; i<arr.count(); i++)
289 arr[i]->Format(buf);
290 }
291 else
292 {
293 created.qsort(RefResourceCompare);
294 for(int i=0; i<created.count(); i++)
295 created[i]->Format(buf);
296 }
297
298 return buf.borrow();
299 }
300
Generator(void)301 char **ResType::Generator(void)
302 {
303 StringSet res;
304
305 for(ResType *dscan=types_by_name->each_begin(); dscan; dscan=types_by_name->each_next())
306 if(!dscan->IsAlias())
307 res.Append(dscan->name);
308
309 res.qsort();
310 return res.borrow();
311 }
312
BoolValidate(xstring_c * value)313 const char *ResMgr::BoolValidate(xstring_c *value)
314 {
315 const char *v=*value;
316 const char *newval=0;
317
318 switch(v[0])
319 {
320 case 't': newval="true"; break;
321 case 'T': newval="True"; break;
322 case 'f': newval="false"; break;
323 case 'F': newval="False"; break;
324 case 'y': newval="yes"; break;
325 case 'Y': newval="Yes"; break;
326 case 'n': newval="no"; break;
327 case 'N': newval="No"; break;
328 case '1': newval="1"; break;
329 case '0': newval="0"; break;
330 case '+': newval="+"; break;
331 case '-': newval="-"; break;
332 case 'o': newval=(v[1]=='f' || v[1]=='F')?"off":"on"; break;
333 case 'O': newval=(v[1]=='f' || v[1]=='F')?"Off":"On"; break;
334 default:
335 return _("invalid boolean value");
336 }
337 if(strcmp(v,newval))
338 value->set(newval);
339
340 return 0;
341 }
342
TriBoolValidate(xstring_c * value)343 const char *ResMgr::TriBoolValidate(xstring_c *value)
344 {
345 if(!BoolValidate(value))
346 return 0;
347
348 /* not bool */
349 const char *v=*value;
350 const char *newval=0;
351
352 switch(v[0])
353 {
354 case 'a': newval="auto"; break;
355 case 'A': newval="Auto"; break;
356 default:
357 return _("invalid boolean/auto value");
358 }
359
360 if(strcmp(v,newval))
361 value->set(newval);
362
363 return 0;
364 }
365
366 static const char power_letter[] =
367 {
368 0, /* not used */
369 'K', /* kibi ('k' for kilo is a special case) */
370 'M', /* mega or mebi */
371 'G', /* giga or gibi */
372 'T', /* tera or tebi */
373 'P', /* peta or pebi */
374 'E', /* exa or exbi */
375 'Z', /* zetta or 2**70 */
376 'Y' /* yotta or 2**80 */
377 };
get_power_multiplier(char p)378 static unsigned long long get_power_multiplier(char p)
379 {
380 const char *scan=power_letter;
381 const int scale=1024;
382 unsigned long long mul=1;
383 p=toupper(p);
384 while(scan<power_letter+sizeof(power_letter)) {
385 if(p==*scan)
386 return mul;
387 mul*=scale;
388 scan++;
389 }
390 return 0;
391 }
392
NumberValidate(xstring_c * value)393 const char *ResMgr::NumberValidate(xstring_c *value)
394 {
395 const char *v=*value;
396 const char *end=v;
397
398 (void)strtoll(v,const_cast<char**>(&end),0);
399 unsigned long long m=get_power_multiplier(*end);
400
401 if(v==end || m==0 || end[m>1])
402 return _("invalid number");
403
404 return 0;
405 }
FloatValidate(xstring_c * value)406 const char *ResMgr::FloatValidate(xstring_c *value)
407 {
408 const char *v=*value;
409 const char *end=v;
410
411 (void)strtod(v,const_cast<char**>(&end));
412 unsigned long long m=get_power_multiplier(*end);
413
414 if(v==end || m==0 || end[m>1])
415 return _("invalid floating point number");
416
417 return 0;
418 }
UNumberValidate(xstring_c * value)419 const char *ResMgr::UNumberValidate(xstring_c *value)
420 {
421 const char *v=*value;
422 const char *end=v;
423
424 (void)strtoull(v,const_cast<char**>(&end),0);
425 unsigned long long m=get_power_multiplier(*end);
426
427 if(!isdigit((unsigned char)v[0])
428 || v==end || m==0 || end[m>1])
429 return _("invalid unsigned number");
430
431 return 0;
432 }
AliasValidate(xstring_c *)433 const char *ResMgr::AliasValidate(xstring_c *)
434 {
435 return "";
436 }
437
to_unumber(unsigned long long max) const438 unsigned long long ResValue::to_unumber(unsigned long long max) const
439 {
440 if (is_nil())
441 return 0;
442 const char *end=s;
443 unsigned long long v=strtoull(s,const_cast<char**>(&end),0);
444 unsigned long long m=get_power_multiplier(*end);
445 unsigned long long vm=v*m;
446 if(vm/m!=v || vm>max)
447 return max;
448 return vm;
449 }
to_number(long long min,long long max) const450 long long ResValue::to_number(long long min,long long max) const
451 {
452 if (is_nil())
453 return 0;
454 const char *end=s;
455 long long v=strtoll(s,const_cast<char**>(&end),0);
456 long long m=get_power_multiplier(*end);
457 long long vm=v*m;
458 if(vm/m!=v)
459 return v>0?max:min;
460 if(vm>max)
461 return max;
462 if(vm<min)
463 return min;
464 return vm;
465 }
operator int() const466 ResValue::operator int() const
467 {
468 return to_number(INT_MIN,INT_MAX);
469 }
operator long() const470 ResValue::operator long() const
471 {
472 return to_number(LONG_MIN,LONG_MAX);
473 }
operator unsigned() const474 ResValue::operator unsigned() const
475 {
476 return to_unumber(UINT_MAX);
477 }
operator unsigned long() const478 ResValue::operator unsigned long() const
479 {
480 return to_unumber(ULONG_MAX);
481 }
to_tri_bool(bool a) const482 bool ResValue::to_tri_bool(bool a) const
483 {
484 if(*s=='a' || *s=='A')
485 return a;
486 return to_bool();
487 }
488
Resource(ResType * type,const char * closure,const char * value,bool def)489 Resource::Resource(ResType *type,const char *closure,const char *value,bool def)
490 : type(type), value(value), closure(closure), def(def), all_node(this), type_value_node(this)
491 {
492 all_list.add_tail(all_node);
493 type->type_value_list->add_tail(type_value_node);
494 }
~Resource()495 Resource::~Resource()
496 {
497 all_node.remove();
498 type_value_node.remove();
499 }
500
ClosureMatch(const char * cl_data)501 bool Resource::ClosureMatch(const char *cl_data)
502 {
503 if(!closure && !cl_data)
504 return true;
505 if(!(closure && cl_data))
506 return false;
507 // a special case for domain name match (i.e. example.org matches *.example.org)
508 if(closure[0]=='*' && closure[1]=='.' && !strcmp(closure+2,cl_data))
509 return true;
510 if(0==fnmatch(closure,cl_data,FNM_PATHNAME))
511 return true;
512 // try to match basename; helps matching torrent metadata url to *.torrent
513 const char *bn=basename_ptr(cl_data);
514 if(bn!=cl_data && 0==fnmatch(closure,bn,FNM_PATHNAME))
515 return true;
516 return false;
517 }
518
QueryNext(const char * name,const char ** closure,Resource ** ptr)519 const char *ResMgr::QueryNext(const char *name,const char **closure,Resource **ptr)
520 {
521 xlist<Resource> *node=0;
522 if(*ptr==0)
523 {
524 const ResType *type=FindRes(name);
525 if(!type) {
526 *ptr=0;
527 *closure=0;
528 return 0;
529 }
530 node=type->type_value_list->get_next();
531 }
532 else
533 {
534 node=(*ptr)->type_value_node.get_next();
535 }
536 *ptr=node->get_obj();
537 if(*ptr) {
538 *closure=(*ptr)->closure;
539 return (*ptr)->value;
540 }
541 *closure=0;
542 return 0;
543 }
544
SimpleQuery(const char * closure) const545 const char *ResType::SimpleQuery(const char *closure) const
546 {
547 // find the value
548 xlist_for_each(Resource,*(type_value_list),node,scan)
549 if(scan->ClosureMatch(closure))
550 return scan->value;
551 return 0;
552 }
553
Query(const char * name,const char * closure)554 ResValue ResMgr::Query(const char *name,const char *closure)
555 {
556 const char *msg;
557
558 const ResType *type;
559 // find type of given variable
560 msg=FindVar(name,&type);
561 if(msg)
562 {
563 // debug only
564 // fprintf(stderr,_("Query of variable `%s' failed: %s\n"),name,msg);
565 return 0;
566 }
567
568 return type->Query(closure);
569 }
570
Query(const char * closure) const571 ResValue ResType::Query(const char *closure) const
572 {
573 const char *v=0;
574
575 if(closure)
576 v=SimpleQuery(closure);
577 if(!v)
578 v=SimpleQuery(0);
579 if(!v)
580 v=defvalue;
581
582 return v;
583 }
584
str2bool(const char * s)585 bool ResMgr::str2bool(const char *s)
586 {
587 return(strchr("TtYy1+",s[0])!=0 || !strcasecmp(s,"on"));
588 }
589
ResDecl(const char * a_name,const char * a_defvalue,ResValValid * a_val_valid,ResClValid * a_closure_valid)590 ResDecl::ResDecl(const char *a_name,const char *a_defvalue,ResValValid *a_val_valid,ResClValid *a_closure_valid)
591 {
592 name=a_name;
593 defvalue=a_defvalue;
594 val_valid=a_val_valid;
595 closure_valid=a_closure_valid;
596 Register();
597 }
ResDecls(ResType * array)598 ResDecls::ResDecls(ResType *array)
599 {
600 while(array->name)
601 array++->Register();
602 }
ResDecls(ResType * r1,ResType * r2,...)603 ResDecls::ResDecls(ResType *r1,ResType *r2,...)
604 {
605 r.append(r1);
606 r1->Register();
607 if(!r2)
608 return;
609 r.append(r2);
610 r2->Register();
611 va_list v;
612 va_start(v,r2);
613 while((r1=va_arg(v,ResType *))!=0)
614 {
615 r1->Register();
616 r.append(r1);
617 }
618 va_end(v);
619 }
~ResDecls()620 ResDecls::~ResDecls()
621 {
622 for(int i=0; i<r.count(); i++)
623 r[i]->Unregister();
624 }
625
Register()626 void ResType::Register()
627 {
628 if(!types_by_name)
629 types_by_name=new xmap<ResType*>;
630 types_by_name->add(name,this);
631 if(!type_value_list)
632 type_value_list=new xlist_head<Resource>();
633 }
Unregister()634 void ResType::Unregister()
635 {
636 if(types_by_name)
637 types_by_name->remove(name);
638 if(type_value_list) {
639 // remove all resources of this type
640 xlist_for_each_safe(Resource,*type_value_list,node,scan,next)
641 delete scan;
642 delete type_value_list;
643 type_value_list=0;
644 }
645 }
646
init(const char * s)647 void TimeIntervalR::init(const char *s)
648 {
649 double interval=0;
650 infty=false;
651 error_text=0;
652
653 if(!strncasecmp(s,"inf",3)
654 || !strcasecmp(s,"forever")
655 || !strcasecmp(s,"never"))
656 {
657 infty=true;
658 return;
659 }
660 int pos=0;
661 for(;;)
662 {
663 double prec;
664 char ch='s';
665 int pos1=strlen(s+pos);
666 int n=sscanf(s+pos,"%lf%c%n",&prec,&ch,&pos1);
667 if(n<1)
668 break;
669 ch=tolower((unsigned char)ch);
670 if(ch=='m')
671 prec*=MINUTE;
672 else if(ch=='h')
673 prec*=HOUR;
674 else if(ch=='d')
675 prec*=DAY;
676 else if(ch!='s')
677 {
678 error_text=_("Invalid time unit letter, only [smhd] are allowed.");
679 return;
680 }
681 interval+=prec;
682 pos+=pos1;
683 }
684 if(pos==0)
685 {
686 error_text=_("Invalid time format. Format is <time><unit>, e.g. 2h30m.");
687 return;
688 }
689 TimeDiff::Set(interval);
690 }
691
TimeIntervalValidate(xstring_c * s)692 const char *ResMgr::TimeIntervalValidate(xstring_c *s)
693 {
694 TimeIntervalR t(*s);
695 if(t.Error())
696 return t.ErrorText();
697 return 0;
698 }
699
init(char sep1,const char * s)700 void NumberPair::init(char sep1,const char *s)
701 {
702 sep=sep1;
703 Set(s);
704 }
parse1(const char * s)705 long long NumberPair::parse1(const char *s)
706 {
707 if(!s || !*s)
708 return 0;
709 const char *end=s;
710 long long v=strtoll(s,const_cast<char**>(&end),0);
711 long long m=get_power_multiplier(*end);
712 if(s==end || m==0 || end[m>1]) {
713 error_text=_("invalid number");
714 return 0;
715 }
716 long long vm=v*m;
717 if(vm/m!=v) {
718 error_text=_("integer overflow");
719 return 0;
720 }
721 return vm;
722 }
Set(const char * s0)723 void NumberPair::Set(const char *s0)
724 {
725 n1=n2=0;
726 no_n1=no_n2=true;
727 error_text=0;
728
729 if(!s0)
730 return;
731
732 char *s1=alloca_strdup(s0);
733 char *s2=s1;
734 while(*s2 && *s2!=sep && *s2!=':')
735 s2++;
736 if(*s2)
737 *s2++=0;
738 else
739 s2=0;
740
741 n1=parse1(s1);
742 no_n1=!*s1;
743 n2=(s2?parse1(s2):n1);
744 no_n2=(s2 && !*s2);
745
746 if(!error_text && Log::global) {
747 Log::global->Format(10,"%s translated to pair %lld%c%lld (%d,%d)\n",
748 s0,n1,sep,n2,no_n1,no_n2);
749 }
750 }
751
Range(const char * s)752 Range::Range(const char *s) : NumberPair('-')
753 {
754 if(!strcasecmp(s,"full") || !strcasecmp(s,"any"))
755 return;
756 Set(s);
757 }
758
Random()759 long long Range::Random()
760 {
761 random_init();
762
763 if(no_n1 && no_n2)
764 return random();
765 if(no_n2)
766 return n1+random();
767
768 return n1 + (long long)((n2-n1+1)*random01());
769 }
770
RangeValidate(xstring_c * s)771 const char *ResMgr::RangeValidate(xstring_c *s)
772 {
773 Range r(*s);
774 if(r.Error())
775 return r.ErrorText();
776 char *colon=strchr(s->get_non_const(),':');
777 if(colon)
778 *colon='-';
779 return 0;
780 }
781
ERegExpValidate(xstring_c * s)782 const char *ResMgr::ERegExpValidate(xstring_c *s)
783 {
784 if(**s==0)
785 return 0;
786 regex_t re;
787 int err=regcomp(&re,*s,REG_EXTENDED|REG_NOSUB);
788 if(err)
789 {
790 const int max_err_len=128;
791 char *err_msg=xstring::tmp_buf(max_err_len);
792 regerror(err,0,err_msg,max_err_len);
793 return err_msg;
794 }
795 regfree(&re);
796 return 0;
797 }
798
IPv4AddrValidate(xstring_c * value)799 const char *ResMgr::IPv4AddrValidate(xstring_c *value)
800 {
801 if(!**value)
802 return 0;
803 if(!is_ipv4_address(*value))
804 return _("Invalid IPv4 numeric address");
805 return 0;
806 }
807
808 #if INET6
IPv6AddrValidate(xstring_c * value)809 const char *ResMgr::IPv6AddrValidate(xstring_c *value)
810 {
811 if(!**value)
812 return 0;
813 if(!is_ipv6_address(*value))
814 return _("Invalid IPv6 numeric address");
815 return 0;
816 }
817 #endif
818
FileAccessible(xstring_c * value,int mode,bool want_dir)819 const char *ResMgr::FileAccessible(xstring_c *value,int mode,bool want_dir)
820 {
821 if(!**value)
822 return 0;
823 const char *f=expand_home_relative(*value);
824 xstring_c cwd;
825 const char *error=0;
826 if(f[0]!='/')
827 {
828 xgetcwd_to(cwd);
829 if(cwd)
830 f=dir_file(cwd,f);
831 }
832 struct stat st;
833 if(stat(f,&st)<0)
834 error=strerror(errno);
835 else if(want_dir ^ S_ISDIR(st.st_mode))
836 error=strerror(errno=want_dir?ENOTDIR:EISDIR);
837 else if(access(f,mode)<0)
838 error=strerror(errno);
839 else
840 value->set(f);
841 return error;
842 }
FileReadable(xstring_c * value)843 const char *ResMgr::FileReadable(xstring_c *value)
844 {
845 return FileAccessible(value,R_OK);
846 }
FileExecutable(xstring_c * value)847 const char *ResMgr::FileExecutable(xstring_c *value)
848 {
849 return FileAccessible(value,X_OK);
850 }
DirReadable(xstring_c * value)851 const char *ResMgr::DirReadable(xstring_c *value)
852 {
853 return FileAccessible(value,R_OK|X_OK,true);
854 }
FileCreatable(xstring_c * value)855 const char *ResMgr::FileCreatable(xstring_c *value)
856 {
857 if(!**value)
858 return 0;
859 const char *error=FileAccessible(value,W_OK,false);
860 if(!error || (error && errno!=ENOENT))
861 return error;
862 const char *bn=basename_ptr(*value);
863 xstring_c dir(dirname(*value));
864 if(!*dir)
865 xgetcwd_to(dir);
866 error=FileAccessible(&dir,X_OK|W_OK,true);
867 if(!error) // dir may be expanded, combine it with base file name.
868 value->set(dir_file(dir,bn));
869 return error;
870 }
871
872 #ifdef HAVE_ICONV
873 CDECL_BEGIN
874 # include <iconv.h>
875 CDECL_END
876 #endif
CharsetValidate(xstring_c * value)877 const char *ResMgr::CharsetValidate(xstring_c *value)
878 {
879 if(!**value)
880 return 0;
881 #ifdef HAVE_ICONV
882 iconv_t ic=iconv_open(*value,*value);
883 if(ic==(iconv_t)-1)
884 return _("this encoding is not supported");
885 iconv_close(ic);
886 return 0;
887 #else
888 return _("this encoding is not supported");
889 #endif
890 }
891
NoClosure(xstring_c *)892 const char *ResMgr::NoClosure(xstring_c *)
893 {
894 return _("no closure defined for this setting");
895 }
896
HasClosure(xstring_c * c)897 const char *ResMgr::HasClosure(xstring_c *c)
898 {
899 if(!*c || !**c)
900 return _("a closure is required for this setting");
901 return 0;
902 }
903
UNumberPairValidate(xstring_c * value)904 const char *ResMgr::UNumberPairValidate(xstring_c *value)
905 {
906 NumberPair pair(':',*value);
907 if(pair.Error())
908 return pair.ErrorText();
909 return 0;
910 }
ToNumberPair(int & a,int & b) const911 void ResValue::ToNumberPair(int &a,int &b) const
912 {
913 NumberPair pair(':',s);
914 if(pair.Error()) {
915 a=b=0;
916 } else {
917 a=pair.N1();
918 b=pair.HasN2()?pair.N2():a;
919 }
920 }
921
922 xlist_head<ResClient> ResClient::list;
Query(const char * name,const char * closure) const923 ResValue ResClient::Query(const char *name,const char *closure) const
924 {
925 if(!strchr(name,':'))
926 {
927 const char *prefix=ResPrefix();
928 name=xstring::cat(prefix,":",name,NULL);
929 name=alloca_strdup(name);
930 }
931 if(!closure)
932 closure=ResClosure();
933 return ResMgr::Query(name,closure);
934 }
ResClient()935 ResClient::ResClient()
936 : node(this)
937 {
938 list.add(node);
939 }
~ResClient()940 ResClient::~ResClient()
941 {
942 node.remove();
943 }
ReconfigAll(const char * r)944 void ResClient::ReconfigAll(const char *r)
945 {
946 xlist_for_each(ResClient,list,node,scan)
947 scan->Reconfig(r);
948 }
949
QueryBool(const char * closure) const950 bool ResType::QueryBool(const char *closure) const
951 {
952 return Query(closure).to_bool();
953 }
QueryBool(const char * name,const char * closure)954 bool ResMgr::QueryBool(const char *name,const char *closure)
955 {
956 return Query(name,closure).to_bool();
957 }
QueryBool(const char * name,const char * closure) const958 bool ResClient::QueryBool(const char *name,const char *closure) const
959 {
960 return Query(name,closure).to_bool();
961 }
QueryTriBool(const char * closure,bool a) const962 bool ResType::QueryTriBool(const char *closure,bool a) const
963 {
964 return Query(closure).to_tri_bool(a);
965 }
QueryTriBool(const char * name,const char * closure,bool a)966 bool ResMgr::QueryTriBool(const char *name,const char *closure,bool a)
967 {
968 return Query(name,closure).to_tri_bool(a);
969 }
QueryTriBool(const char * name,const char * closure,bool a) const970 bool ResClient::QueryTriBool(const char *name,const char *closure,bool a) const
971 {
972 return Query(name,closure).to_tri_bool(a);
973 }
974
ClassCleanup()975 void ResType::ClassCleanup()
976 {
977 xlist_for_each_safe(Resource,Resource::all_list,node,scan,next)
978 delete scan;
979 if(types_by_name) {
980 for(ResType *t=types_by_name->each_begin(); t; t=types_by_name->each_next())
981 t->Unregister();
982 delete types_by_name; types_by_name=0;
983 }
984 }
985