1 /*
2  * lftp - file transfer program
3  *
4  * Copyright (c) 1996-2016 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 #include "FileSet.h"
22 
23 #include <stddef.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <utime.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <sys/stat.h>
30 #include <fnmatch.h>
31 #include <assert.h>
32 
33 #include <grp.h>
34 #include <pwd.h>
35 #include <ctype.h>
36 
37 #include "misc.h"
38 #include "ResMgr.h"
39 #include "StringPool.h"
40 #include "IdNameCache.h"
41 #include "PatternSet.h"
42 
43 #ifdef HAVE_SYS_STATFS_H
44 # include <sys/statfs.h>
45 #endif
46 
47 #define fnum files.count()
48 
Merge(const FileInfo & f)49 void  FileInfo::Merge(const FileInfo& f)
50 {
51    if(strcmp(basename_ptr(name),basename_ptr(f.name)))
52       return;
53    MergeInfo(f,~defined);
54 }
MergeInfo(const FileInfo & f,unsigned dif)55 void  FileInfo::MergeInfo(const FileInfo& f,unsigned dif)
56 {
57    dif&=f.defined;
58    if(dif&MODE) {
59       SetMode(f.mode);
60       if(mode!=SYMLINK && mode!=REDIRECT)
61 	 symlink.unset();
62    }
63    if(dif&DATE || (defined&DATE && f.defined&DATE && f.date.ts_prec<date.ts_prec))
64       SetDate(f.date,f.date.ts_prec);
65    if(dif&SIZE)
66       SetSize(f.size);
67    if(dif&TYPE)
68       SetType(f.filetype);
69    if(dif&SYMLINK)
70       SetSymlink(f.symlink);
71    if(dif&USER)
72       SetUser(f.user);
73    if(dif&GROUP)
74       SetGroup(f.group);
75    if(dif&NLINKS)
76       SetNlink(f.nlinks);
77 }
78 
SetUser(const char * u)79 void FileInfo::SetUser(const char *u)
80 {
81    if(u==user)
82       return;
83    user=StringPool::Get(u);
84    def(USER);
85 }
86 
SetGroup(const char * g)87 void FileInfo::SetGroup(const char *g)
88 {
89    if(g==group)
90       return;
91    group=StringPool::Get(g);
92    def(GROUP);
93 }
94 
add_before(int pos,FileInfo * fi)95 void FileSet::add_before(int pos,FileInfo *fi)
96 {
97    files.insert(fi,pos);
98 }
Add(FileInfo * fi)99 void FileSet::Add(FileInfo *fi)
100 {
101    assert(!sorted);
102    if(!fi->name)
103    {
104       delete fi;
105       return;
106    }
107    /* add sorted */
108    int pos = FindGEIndByName(fi->name);
109    if(pos < fnum && !strcmp(files[pos]->name,fi->name)) {
110       files[pos]->Merge(*fi);
111       delete fi;
112       return;
113    }
114    add_before(pos,fi);
115 }
116 
Sub(int i)117 void FileSet::Sub(int i)
118 {
119    assert(!sorted);
120    files.remove(i);
121    if(ind>i)
122       ind--;
123 }
Borrow(int i)124 FileInfo *FileSet::Borrow(int i)
125 {
126    FileInfo *fi=files[i].borrow();
127    Sub(i);
128    return fi;
129 }
130 
assert_sorted() const131 void FileSet::assert_sorted() const
132 {
133    for(int i=0; i<fnum-1; i++)
134       assert(strcmp(files[i]->name,files[i+1]->name)<0);
135 }
136 
Merge_insert(const FileSet * set)137 void FileSet::Merge_insert(const FileSet *set)
138 {
139    if(!set)
140       return;
141    for(int i=0; i<set->fnum; i++)
142    {
143       const Ref<FileInfo>& fi=set->files[i];
144       int pos = FindGEIndByName(fi->name);
145       if(pos < fnum && !strcmp(files[pos]->name,fi->name))
146 	 files[pos]->Merge(*fi);
147       else
148 	 add_before(pos,new FileInfo(*fi));
149    }
150 }
Merge(const FileSet * set)151 void FileSet::Merge(const FileSet *set)
152 {
153    assert(!sorted);
154    if(!set || !set->fnum)
155       return;
156 
157    // estimate work to be done by Merge_insert
158    int pos = FindGEIndByName(set->files[0]->name);
159    if(fnum-pos < fnum*2/set->fnum) {
160       Merge_insert(set);
161       return;
162    }
163 
164    RefArray<FileInfo> new_set;
165    int i=0;
166    int j=0;
167    while(i<set->fnum && j<fnum) {
168       Ref<FileInfo>& fi1=files[j];
169       const Ref<FileInfo>& fi2=set->files[i];
170       int cmp = strcmp(fi1->name,fi2->name);
171       if(cmp==0) {
172 	 fi1->Merge(*fi2);
173 	 new_set.append(fi1.borrow());
174 	 i++; j++;
175       } else if(cmp>0) {
176 	 new_set.append(new FileInfo(*fi2));
177 	 i++;
178       } else {
179 	 new_set.append(fi1.borrow());
180 	 j++;
181       }
182    }
183    while(i<set->fnum) {
184       const Ref<FileInfo>& fi2=set->files[i];
185       new_set.append(new FileInfo(*fi2));
186       i++;
187    }
188    if(new_set.count()==0)
189       return;
190    while(j<fnum) {
191       Ref<FileInfo>& fi1=files[j];
192       new_set.append(fi1.borrow());
193       j++;
194    }
195    files.move_here(new_set);
196 }
197 
PrependPath(const char * path)198 void FileSet::PrependPath(const char *path)
199 {
200    for(int i=0; i<fnum; i++)
201       files[i]->SetName(dir_file(path, files[i]->name));
202 }
203 
FileSet()204 FileSet::FileSet()
205    : sort_mode(BYNAME), ind(0)
206 {
207 }
208 
~FileSet()209 FileSet::~FileSet()
210 {
211 }
212 
213 /* we don't copy the sort state--nothing needs it, and it'd
214  * be a bit of a pain to implement. */
FileSet(FileSet const * set)215 FileSet::FileSet(FileSet const *set)
216 {
217    if(!set) {
218       ind=0;
219       return;
220    }
221    ind=set->ind;
222    for(int i=0; i<set->fnum; i++)
223       files.append(new FileInfo(*(set->files[i])));
224 }
225 
files_sort_name(const Ref<FileInfo> * s1,const Ref<FileInfo> * s2)226 static int files_sort_name(const Ref<FileInfo> *s1, const Ref<FileInfo> *s2)
227 {
228    return strcmp((*s1)->name, (*s2)->name);
229 }
230 
231 static int (*compare)(const char *s1, const char *s2);
232 static int rev_cmp;
233 static RefArray<FileInfo> *files_cmp;
234 
sort_name(const int * s1,const int * s2)235 static int sort_name(const int *s1, const int *s2)
236 {
237    const FileInfo *p1=(*files_cmp)[*s1];
238    const FileInfo *p2=(*files_cmp)[*s2];
239    return compare(p1->name, p2->name) * rev_cmp;
240 }
241 
sort_size(const int * s1,const int * s2)242 static int sort_size(const int *s1, const int *s2)
243 {
244    const FileInfo *p1=(*files_cmp)[*s1];
245    const FileInfo *p2=(*files_cmp)[*s2];
246    if(p1->size > p2->size) return -rev_cmp;
247    if(p1->size < p2->size) return rev_cmp;
248    return 0;
249 }
250 
sort_dirs(const int * s1,const int * s2)251 static int sort_dirs(const int *s1, const int *s2)
252 {
253    const FileInfo *p1=(*files_cmp)[*s1];
254    const FileInfo *p2=(*files_cmp)[*s2];
255    if((p1->filetype == FileInfo::DIRECTORY) && !(p2->filetype == FileInfo::DIRECTORY)) return -rev_cmp;
256    if(!(p1->filetype == FileInfo::DIRECTORY) && (p2->filetype == FileInfo::DIRECTORY)) return rev_cmp;
257    return 0;
258 }
259 
sort_rank(const int * s1,const int * s2)260 static int sort_rank(const int *s1, const int *s2)
261 {
262    const FileInfo *p1=(*files_cmp)[*s1];
263    const FileInfo *p2=(*files_cmp)[*s2];
264    if(p1->GetRank()==p2->GetRank())
265       return sort_name(s1,s2);
266    return p1->GetRank()<p2->GetRank() ? -rev_cmp : rev_cmp;
267 }
268 
sort_date(const int * s1,const int * s2)269 static int sort_date(const int *s1, const int *s2)
270 {
271    const FileInfo *p1=(*files_cmp)[*s1];
272    const FileInfo *p2=(*files_cmp)[*s2];
273    if(p1->date==p2->date)
274       return sort_name(s1,s2);
275    return p1->date>p2->date ? -rev_cmp : rev_cmp;
276 }
277 
Sort(sort_e newsort,bool casefold,bool reverse)278 void FileSet::Sort(sort_e newsort, bool casefold, bool reverse)
279 {
280    if(newsort == BYNAME && !casefold && !reverse) {
281       Unsort();
282       return;
283    }
284 
285    if(casefold) compare = strcasecmp;
286    else compare = strcmp;
287 
288    rev_cmp=(reverse?-1:1);
289    files_cmp=&files;
290 
291    if(newsort==BYNAME_FLAT && sort_mode!=BYNAME_FLAT)
292    {
293       // save original paths to longname, store basename to name,
294       // sort files array according to short names
295       for(int i=0; i<fnum; i++)
296       {
297 	 Ref<FileInfo> const& fi=files[i];
298 	 fi->longname.move_here(fi->name);
299 	 fi->name.set(basename_ptr(fi->longname));
300       }
301       files.qsort(files_sort_name);
302    }
303 
304    xmap<bool> dup;
305    sorted.truncate();
306    for(int i=0; i<fnum; i++) {
307       if(newsort==BYNAME_FLAT && sort_mode!=BYNAME_FLAT) {
308 	 Ref<FileInfo> const& fi=files[i];
309 	 if(dup.exists(fi->name))
310 	    continue;
311 	 dup.add(fi->name,true);
312       }
313       sorted.append(i);
314    }
315 
316    switch(newsort) {
317    case BYNAME_FLAT: /*fallthrough*/
318    case BYNAME: sorted.qsort(sort_name); break;
319    case BYSIZE: sorted.qsort(sort_size); break;
320    case DIRSFIRST: sorted.qsort(sort_dirs); break;
321    case BYRANK: sorted.qsort(sort_rank); break;
322    case BYDATE: sorted.qsort(sort_date); break;
323    }
324    sort_mode=newsort;
325 }
326 
327 // reverse current sort order
ReverseSort()328 void FileSet::ReverseSort()
329 {
330    if(!sorted) {
331       Sort(BYNAME,false,true);
332       return;
333    }
334    int i=0;
335    int j=sorted.length()-1;
336    while(i<j) {
337       sorted[i]=replace_value(sorted[j],sorted[i]);
338       ++i,--j;
339    }
340 }
341 
342 /* Remove the current sort, allowing new entries to be added. */
Unsort()343 void FileSet::Unsort()
344 {
345    sorted.unset();
346    if(sort_mode==BYNAME_FLAT)
347       UnsortFlat();
348    sort_mode=BYNAME;
349 }
350 
UnsortFlat()351 void FileSet::UnsortFlat()
352 {
353    for(int i=0; i<files.count(); i++) {
354       assert(files[i]->longname!=0);
355       files[i]->name.move_here(files[i]->longname);
356    }
357    files.qsort(files_sort_name);
358 }
359 
Empty()360 void FileSet::Empty()
361 {
362    Unsort();
363    files.unset();
364    ind=0;
365 }
366 
SubtractSame(const FileSet * set,int ignore)367 void FileSet::SubtractSame(const FileSet *set,int ignore)
368 {
369    if(!set)
370       return;
371    for(int i=0; i<fnum; i++)
372    {
373       FileInfo *f=set->FindByName(files[i]->name);
374       if(f && files[i]->SameAs(f,ignore))
375 	 Sub(i--);
376    }
377 }
378 
SubtractAny(const FileSet * set)379 void FileSet::SubtractAny(const FileSet *set)
380 {
381    if(!set)
382       return;
383    for(int i=0; i<fnum; i++)
384       if(set->FindByName(files[i]->name))
385 	 Sub(i--);
386 }
387 
SubtractNotIn(const FileSet * set)388 void FileSet::SubtractNotIn(const FileSet *set)
389 {
390    if(!set) {
391       Empty();
392       return;
393    }
394    for(int i=0; i<fnum; i++)
395       if(!set->FindByName(files[i]->name))
396 	 Sub(i--);
397 }
SubtractSameType(const FileSet * set)398 void FileSet::SubtractSameType(const FileSet *set)
399 {
400    if(!set)
401       return;
402    for(int i=0; i<fnum; i++)
403    {
404       FileInfo *f=set->FindByName(files[i]->name);
405       if(f && files[i]->defined&FileInfo::TYPE && f->defined&FileInfo::TYPE
406       && files[i]->filetype==f->filetype)
407 	 Sub(i--);
408    }
409 }
SubtractDirs(const FileSet * set)410 void FileSet::SubtractDirs(const FileSet *set)
411 {
412    if(!set)
413       return;
414    for(int i=0; i<fnum; i++)
415    {
416       if(!files[i]->TypeIs(FileInfo::DIRECTORY))
417 	 continue;
418       FileInfo *f=set->FindByName(files[i]->name);
419       if(f && f->TypeIs(f->DIRECTORY))
420 	 Sub(i--);
421    }
422 }
SubtractNotOlderDirs(const FileSet * set)423 void FileSet::SubtractNotOlderDirs(const FileSet *set)
424 {
425    if(!set)
426       return;
427    for(int i=0; i<fnum; i++)
428    {
429       if(!files[i]->TypeIs(FileInfo::DIRECTORY)
430       || !files[i]->Has(FileInfo::DATE))
431 	 continue;
432       FileInfo *f=set->FindByName(files[i]->name);
433       if(f && f->TypeIs(f->DIRECTORY) && f->NotOlderThan(files[i]->date))
434 	 Sub(i--);
435    }
436 }
437 
SubtractTimeCmp(bool (FileInfo::* cmp)(time_t)const,time_t t)438 void FileSet::SubtractTimeCmp(bool (FileInfo::*cmp)(time_t) const,time_t t)
439 {
440    for(int i=0; i<fnum; i++)
441    {
442       if(files[i]->defined&FileInfo::TYPE
443       && files[i]->filetype!=FileInfo::NORMAL)
444 	 continue;
445       if((files[i].get()->*cmp)(t))
446       {
447 	 Sub(i);
448 	 i--;
449       }
450    }
451 }
452 
SubtractSizeOutside(const Range * r)453 void FileSet::SubtractSizeOutside(const Range *r)
454 {
455    for(int i=0; i<fnum; i++)
456    {
457       if(files[i]->defined&FileInfo::TYPE
458       && files[i]->filetype!=FileInfo::NORMAL)
459 	 continue;
460       if(files[i]->SizeOutside(r))
461       {
462 	 Sub(i);
463 	 i--;
464       }
465    }
466 }
SubtractDirs()467 void FileSet::SubtractDirs()
468 {
469    for(int i=0; i<fnum; i++)
470    {
471       if(files[i]->defined&FileInfo::TYPE
472       && files[i]->filetype==FileInfo::DIRECTORY)
473       {
474 	 Sub(i);
475 	 i--;
476       }
477    }
478 }
SubtractNotDirs()479 void FileSet::SubtractNotDirs()
480 {
481    for(int i=0; i<fnum; i++)
482    {
483       if(!(files[i]->defined&FileInfo::TYPE)
484       || files[i]->filetype!=FileInfo::DIRECTORY)
485       {
486 	 Sub(i);
487 	 i--;
488       }
489    }
490 }
491 
ExcludeDots()492 void FileSet::ExcludeDots()
493 {
494    for(int i=0; i<fnum; i++)
495    {
496       if(!strcmp(files[i]->name,".") || !strcmp(files[i]->name,".."))
497       {
498 	 Sub(i);
499 	 i--;
500       }
501    }
502 }
ExcludeCompound()503 void FileSet::ExcludeCompound()
504 {
505    for(int i=0; i<fnum; i++)
506    {
507       const char *name=files[i]->name;
508       if(!strncmp(name,"./~",3))
509 	 name+=3;
510       if(strchr(name,'/'))
511 	 Sub(i--);
512    }
513 }
514 
ExcludeUnaccessible(const char * user)515 void FileSet::ExcludeUnaccessible(const char *user)
516 {
517    for(int i=0; i<fnum; i++)
518    {
519       if(!files[i]->Has(FileInfo::MODE) || !files[i]->Has(FileInfo::TYPE))
520 	 continue;
521       unsigned mask=0444;
522       if(user && files[i]->Has(FileInfo::USER))
523 	 mask=(!strcmp(files[i]->user,user)?0400:0044);
524       if((files[i]->TypeIs(FileInfo::NORMAL)    && !(files[i]->mode&mask))
525       || (files[i]->TypeIs(FileInfo::DIRECTORY) && !(files[i]->mode&mask&(files[i]->mode<<2))))
526       {
527 	 Sub(i);
528 	 i--;
529       }
530    }
531 }
532 
SameAs(const FileInfo * fi,int ignore) const533 bool  FileInfo::SameAs(const FileInfo *fi,int ignore) const
534 {
535    if(defined&NAME && fi->defined&NAME)
536       if(strcmp(name,fi->name))
537 	 return false;
538    if(defined&TYPE && fi->defined&TYPE)
539       if(filetype!=fi->filetype)
540 	 return false;
541 
542    if((defined&TYPE && filetype==DIRECTORY)
543    || (fi->defined&TYPE && fi->filetype==DIRECTORY))
544       return false;  // can't guarantee directory is the same (recursively)
545 
546    if(defined&SYMLINK_DEF && fi->defined&SYMLINK_DEF)
547       return (strcmp(symlink,fi->symlink)==0);
548 
549    if(defined&DATE && fi->defined&DATE && !(ignore&DATE))
550    {
551       time_t p=date.ts_prec;
552       if(p<fi->date.ts_prec)
553 	 p=fi->date.ts_prec;
554       if(!(ignore&IGNORE_DATE_IF_OLDER && date<fi->date)
555       && labs(date-fi->date)>p)
556 	 return false;
557    }
558 
559    if(defined&SIZE && fi->defined&SIZE && !(ignore&SIZE))
560    {
561       if(!(ignore&IGNORE_SIZE_IF_OLDER && defined&DATE && fi->defined&DATE
562 	   && date<fi->date)
563       && (size!=fi->size))
564 	 return false;
565    }
566 
567    return true;
568 }
569 
NotOlderThan(time_t t) const570 bool  FileInfo::NotOlderThan(time_t t) const
571 {
572    return((defined&DATE) && date>=t);
573 }
NotNewerThan(time_t t) const574 bool  FileInfo::NotNewerThan(time_t t) const
575 {
576    return((defined&DATE) && date<=t);
577 }
OlderThan(time_t t) const578 bool  FileInfo::OlderThan(time_t t) const
579 {
580    return((defined&DATE) && date<t);
581 }
NewerThan(time_t t) const582 bool  FileInfo::NewerThan(time_t t) const
583 {
584    return((defined&DATE) && date>t);
585 }
SizeOutside(const Range * r) const586 bool  FileInfo::SizeOutside(const Range *r) const
587 {
588    return((defined&SIZE) && !r->Match(size));
589 }
590 
Count(int * d,int * f,int * s,int * o) const591 void FileSet::Count(int *d,int *f,int *s,int *o) const
592 {
593    for(int i=0; i<fnum; i++)
594    {
595       switch(files[i]->filetype)
596       {
597       case(FileInfo::DIRECTORY):
598 	 if(d) (*d)++;
599 	 break;
600       case(FileInfo::NORMAL):
601 	 if(f) (*f)++;
602 	 break;
603       case(FileInfo::SYMLINK):
604 	 if(s) (*s)++;
605 	 break;
606       default:
607 	 if(o) (*o)++;
608       }
609    }
610 }
611 
CountBytes(long long * b) const612 void FileSet::CountBytes(long long *b) const
613 {
614    for(int i=0; i<fnum; i++)
615    {
616       if(files[i]->filetype==FileInfo::NORMAL && files[i]->Has(FileInfo::SIZE))
617 	 (*b)+=files[i]->size;
618    }
619 }
620 
621 /* assumes sorted by name. binary search for name, returning the first name
622  * >= name; returns fnum if name is greater than all names. */
FindGEIndByName(const char * name) const623 int FileSet::FindGEIndByName(const char *name) const
624 {
625    int l = 0, u = fnum - 1;
626 
627    /* no files or name is greater than the max file: */
628    if(!fnum || strcmp(files[u]->name, name) < 0)
629       return fnum;
630 
631    /* we have files, and u >= name (meaning l <= name <= u); loop while
632     * this is true: */
633    while(l < u) {
634       /* find the midpoint: */
635       int m = (l + u) / 2;
636       int cmp = strcmp(files[m]->name, name);
637 
638       /* if files[m]->name > name, update the upper bound: */
639       if (cmp > 0)
640 	 u = m;
641       /* if files[m]->name < name, update the lower bound: */
642       else if (cmp < 0)
643 	 l = m+1;
644       else /* otherwise found exact match */
645 	 return m;
646    }
647 
648    return u;
649 }
650 
FindByName(const char * name) const651 FileInfo *FileSet::FindByName(const char *name) const
652 {
653    int n = FindGEIndByName(name);
654 
655    if(n < fnum && !strcmp(files[n]->name,name))
656       return files[n].get_non_const();
657 
658    return 0;
659 }
660 
do_exclude_match(const char * prefix,const FileInfo * fi,const PatternSet * x)661 static bool do_exclude_match(const char *prefix,const FileInfo *fi,const PatternSet *x)
662 {
663    const char *name=dir_file(prefix,fi->name);
664    if(fi->defined&fi->TYPE && fi->filetype==fi->DIRECTORY)
665    {
666       char *name1=alloca_strdup2(name,1);
667       strcat(name1,"/");
668       name=name1;
669    }
670    return x->MatchExclude(name);
671 }
672 
Exclude(const char * prefix,const PatternSet * x,FileSet * fsx)673 void  FileSet::Exclude(const char *prefix,const PatternSet *x,FileSet *fsx)
674 {
675    if(!x)
676       return;
677    for(int i=0; i<fnum; i++)
678    {
679       if(do_exclude_match(prefix,files[i],x))
680       {
681 	 if(fsx)
682 	    fsx->Add(Borrow(i));
683 	 else
684 	    Sub(i);
685 	 i--;
686       }
687    }
688 }
689 
690 #if 0
691 void FileSet::Dump(const char *tag) const
692 {
693    printf("%s:",tag);
694    for(int i=0; i<fnum; i++)
695       printf(" %s",files[i]->name.get());
696    printf("\n");
697 }
698 #endif
699 
700 // *** Manipulations with set of local files
701 
LocalUtime(const char * dir,bool only_dirs,bool flat)702 void FileSet::LocalUtime(const char *dir,bool only_dirs,bool flat)
703 {
704    for(int i=0; i<fnum; i++)
705    {
706       const Ref<FileInfo>& file=files[i];
707       if(file->defined & file->DATE)
708       {
709 	 if(!(file->defined & file->TYPE))
710 	    continue;
711 	 if(file->filetype==file->SYMLINK)
712 	    continue;
713 	 if(only_dirs && file->filetype!=file->DIRECTORY)
714 	    continue;
715 
716 	 const char *name=file->name;
717 	 if(flat)
718 	    name=basename_ptr(name);
719 	 const char *local_name=dir_file(dir,name);
720 	 struct utimbuf ut;
721 	 struct stat st;
722 	 ut.actime=ut.modtime=file->date;
723 
724 	 if(stat(local_name,&st)!=-1 && labs(st.st_mtime-file->date)>file->date.ts_prec)
725 	    utime(local_name,&ut);
726       }
727    }
728 }
LocalChmod(const char * dir,mode_t mask,bool flat)729 void FileSet::LocalChmod(const char *dir,mode_t mask,bool flat)
730 {
731    for(int i=0; i<fnum; i++)
732    {
733       const Ref<FileInfo>& file=files[i];
734       if(file->defined & file->MODE)
735       {
736 	 if(file->defined & file->TYPE
737 	 && file->filetype==file->SYMLINK)
738 	    continue;
739 
740 	 const char *name=file->name;
741 	 if(flat)
742 	    name=basename_ptr(name);
743 	 const char *local_name=dir_file(dir,name);
744 
745 	 struct stat st;
746 	 mode_t new_mode=file->mode&~mask;
747 
748 	 if(stat(local_name,&st)!=-1 && (st.st_mode&07777)!=new_mode)
749 	    chmod(local_name,new_mode);
750       }
751    }
752 }
LocalChown(const char * dir,bool flat)753 void FileSet::LocalChown(const char *dir,bool flat)
754 {
755    for(int i=0; i<fnum; i++)
756    {
757       const Ref<FileInfo>& file=files[i];
758       if(file->defined & (file->USER|file->GROUP))
759       {
760 #ifndef HAVE_LCHOWN
761 	 if(file->defined & file->TYPE
762 	 && file->filetype==file->SYMLINK)
763 	    continue;
764 #define lchown chown
765 #endif
766 
767 	 const char *name=file->name;
768 	 if(flat)
769 	    name=basename_ptr(name);
770 	 const char *local_name=dir_file(dir,name);
771 
772 	 struct stat st;
773 
774 	 if(lstat(local_name,&st)==-1)
775 	    continue;
776 	 uid_t new_uid=st.st_uid;
777 	 gid_t new_gid=st.st_gid;
778 	 if(file->defined&file->USER)
779 	 {
780 	    int u=PasswdCache::LookupS(file->user);
781 	    if(u!=-1)
782 	       new_uid=u;
783 	 }
784 	 if(file->defined&file->GROUP)
785 	 {
786 	    int g=GroupCache::LookupS(file->group);
787 	    if(g!=-1)
788 	       new_gid=g;
789 	 }
790 	 if(new_uid!=st.st_uid || new_gid!=st.st_gid)
791 	 {
792 	    if(lchown(local_name,new_uid,new_gid)==-1)
793 	       /* don't care */;
794 	 }
795       }
796    }
797 }
798 
operator [](int i) const799 FileInfo * FileSet::operator[](int i) const
800 {
801    if(i>=fnum || i<0)
802       return 0;
803    if(sorted)
804       i=sorted[i];
805    return files[i].get_non_const();
806 }
807 
curr()808 FileInfo *FileSet::curr()
809 {
810    return (*this)[ind];
811 }
next()812 FileInfo *FileSet::next()
813 {
814    if(ind<fnum)
815    {
816       ind++;
817       return curr();
818    }
819    return 0;
820 }
SubtractCurr()821 void FileSet::SubtractCurr()
822 {
823    Sub(ind--);
824 }
825 
SubtractByName(const char * name)826 bool FileSet::SubtractByName(const char *name)
827 {
828    int pos = FindGEIndByName(name);
829    if(pos >= fnum || strcmp(files[pos]->name,name))
830       return false;
831    Sub(pos);
832    return true;
833 }
834 
Init()835 void FileInfo::Init()
836 {
837    filetype=UNKNOWN;
838    mode=(mode_t)-1;
839    date=NO_DATE;
840    size=NO_SIZE;
841    nlinks=0;
842    defined=0;
843    need=0;
844    user=0; group=0;
845    rank=0;
846 }
847 
FileInfo(const FileInfo & fi)848 FileInfo::FileInfo(const FileInfo &fi)
849 {
850    Init();
851    name.set(fi.name);
852    symlink.set(fi.symlink);
853    user=fi.user;
854    group=fi.group;
855    defined=fi.defined;
856    filetype=fi.filetype;
857    mode=fi.mode;
858    date=fi.date;
859    size=fi.size;
860    nlinks=fi.nlinks;
861    longname.set(fi.longname);
862 }
~FileInfo()863 FileInfo::~FileInfo()
864 {
865 }
866 
867 #ifndef S_ISLNK
868 # define S_ISLNK(mode) (S_IFLNK==(mode&S_IFMT))
869 #endif
870 
LocalFile(const char * name,bool follow_symlinks)871 void FileInfo::LocalFile(const char *name, bool follow_symlinks)
872 {
873    if(!this->name)
874       SetName(name);
875 
876    struct stat st;
877    if(lstat(name,&st)==-1)
878       return;
879 
880 check_again:
881    FileInfo::type t;
882    if(S_ISDIR(st.st_mode))
883       t=FileInfo::DIRECTORY;
884    else if(S_ISREG(st.st_mode))
885       t=FileInfo::NORMAL;
886    else if(S_ISLNK(st.st_mode))
887    {
888       if(follow_symlinks)
889       {
890 	 if(stat(name,&st)!=-1)
891 	    goto check_again;
892 	 // dangling symlink, don't follow it.
893       }
894       t=FileInfo::SYMLINK;
895    }
896    else
897       return;   // ignore other type files
898 
899    SetSize(st.st_size);
900    int prec=0;
901 #if defined(HAVE_STATFS) && defined(MSDOS_SUPER_MAGIC)
902    struct statfs stfs;
903    if(statfs(name,&stfs)!=-1 && stfs.f_type==MSDOS_SUPER_MAGIC)
904       prec=1;  // MS-DOS fs has 2-second resolution
905 #endif
906    SetDate(st.st_mtime,prec);
907    SetMode(st.st_mode&07777);
908    SetType(t);
909    SetNlink(st.st_nlink);
910 
911    SetUser(PasswdCache::LookupS(st.st_uid));
912    SetGroup(GroupCache::LookupS(st.st_gid));
913 
914    if(t==SYMLINK)
915    {
916       char *buf=string_alloca(st.st_size+1);
917       int res=readlink(name,buf,st.st_size);
918       if(res!=-1)
919       {
920 	 buf[res]=0;
921 	 SetSymlink(buf);
922       }
923    }
924 }
925 
926 /* parse_ls_line: too common procedure to make it protocol specific */
927 /*
928 -rwxr-xr-x   1 lav      root         4771 Sep 12  1996 install-sh
929 -rw-r--r--   1 lav      root         1349 Feb  2 14:10 lftp.lsm
930 drwxr-xr-x   4 lav      root         1024 Feb 22 15:32 lib
931 lrwxrwxrwx   1 lav      root           33 Feb 14 17:45 ltconfig -> /usr/share/libtool/ltconfig
932 NOTE: group may be missing.
933 */
parse_ls_line(const char * line_c,int line_len,const char * tz)934 FileInfo *FileInfo::parse_ls_line(const char *line_c,int line_len,const char *tz)
935 {
936    char *line=string_alloca(line_len+1);
937    memcpy(line,line_c,line_len);
938    line[line_len]=0;
939    char *next=0;
940    FileInfo *fi=0; /* don't instantiate until we at least have something */
941 #define FIRST_TOKEN strtok_r(line," \t",&next)
942 #define NEXT_TOKEN  strtok_r(NULL," \t",&next)
943 #define ERR do{delete fi;return(0);}while(0)
944 
945    /* parse perms */
946    char *t = FIRST_TOKEN;
947    if(t==0)
948       ERR;
949 
950    fi = new FileInfo;
951    switch(t[0])
952    {
953    case('l'):  // symlink
954       fi->SetType(fi->SYMLINK);
955       break;
956    case('d'):  // directory
957       fi->SetType(fi->DIRECTORY);
958       break;
959    case('-'):  // plain file
960       fi->SetType(fi->NORMAL);
961       break;
962    case('b'): // block
963    case('c'): // char
964    case('p'): // pipe
965    case('s'): // sock
966    case('D'): // Door
967       // ignore them
968    default:
969       ERR;
970    }
971    mode_t mode=parse_perms(t+1);
972    if(mode!=(mode_t)-1)
973       fi->SetMode(mode);
974 
975    // link count
976    t = NEXT_TOKEN;
977    if(!t)
978       ERR;
979    fi->SetNlink(atoi(t));
980 
981    // user
982    t = NEXT_TOKEN;
983    if(!t)
984       ERR;
985    fi->SetUser(t);
986 
987    // group or size
988    char *group_or_size = NEXT_TOKEN;
989 
990    // size or month
991    t = NEXT_TOKEN;
992    if(!t)
993       ERR;
994    if(isdigit((unsigned char)*t))
995    {
996       // it's size, so the previous was group:
997       fi->SetGroup(group_or_size);
998       long long size;
999       int n;
1000       if(sscanf(t,"%lld%n",&size,&n)==1 && t[n]==0)
1001 	 fi->SetSize(size);
1002       t = NEXT_TOKEN;
1003       if(!t)
1004 	 ERR;
1005    }
1006    else
1007    {
1008       // it was month, so the previous was size:
1009       long long size;
1010       int n;
1011       if(sscanf(group_or_size,"%lld%n",&size,&n)==1 && group_or_size[n]==0)
1012 	 fi->SetSize(size);
1013    }
1014 
1015    struct tm date;
1016    memset(&date,0,sizeof(date));
1017 
1018    date.tm_mon=parse_month(t);
1019    if(date.tm_mon==-1)
1020       date.tm_mon=0;
1021 
1022    const char *day_of_month = NEXT_TOKEN;
1023    if(!day_of_month)
1024       ERR;
1025    date.tm_mday=atoi(day_of_month);
1026 
1027    bool year_anomaly=false;
1028 
1029    // time or year
1030    t = NEXT_TOKEN;
1031    if(!t)
1032       ERR;
1033    date.tm_isdst=-1;
1034    date.tm_hour=date.tm_min=0;
1035    date.tm_sec=30;
1036    int prec=30;
1037 
1038    if(sscanf(t,"%2d:%2d",&date.tm_hour,&date.tm_min)==2)
1039       date.tm_year=guess_year(date.tm_mon,date.tm_mday,date.tm_hour,date.tm_min) - 1900;
1040    else
1041    {
1042       if(day_of_month+strlen(day_of_month)+1 == t)
1043 	 year_anomaly=true;
1044       date.tm_year=atoi(t)-1900;
1045       /* We don't know the hour.  Set it to something other than 0, or
1046        * DST -1 will end up changing the date. */
1047       date.tm_hour = 12;
1048       date.tm_min=0;
1049       date.tm_sec=0;
1050       prec=12*HOUR;
1051    }
1052 
1053    fi->SetDate(mktime_from_tz(&date,tz),prec);
1054 
1055    char *name=strtok_r(NULL,"",&next);
1056    if(!name)
1057       ERR;
1058 
1059    // there are ls which output extra space after year.
1060    if(year_anomaly && *name==' ')
1061       name++;
1062 
1063    if(fi->filetype==fi->SYMLINK)
1064    {
1065       char *arrow=name;
1066       while((arrow=strstr(arrow," -> "))!=0)
1067       {
1068 	 if(arrow!=name && arrow[4]!=0)
1069 	 {
1070 	    *arrow=0;
1071 	    fi->SetSymlink(arrow+4);
1072 	    break;
1073 	 }
1074 	 arrow++;
1075       }
1076    }
1077    fi->SetName(name);
1078    fi->SetLongName(line_c);
1079 
1080    return fi;
1081 }
1082 
1083 
Have() const1084 int FileSet::Have() const
1085 {
1086    int bits=0;
1087 
1088    for(int i=0; i<fnum; i++)
1089       bits |= files[i]->defined;
1090 
1091    return bits;
1092 }
1093 
fnmatch_dir(const char * pattern,const FileInfo * file)1094 static int fnmatch_dir(const char *pattern,const FileInfo *file)
1095 {
1096    bool inverted = (pattern[0]=='!');
1097    if(inverted || (pattern[0]=='\\' && pattern[1]=='!'))
1098       pattern++;
1099    const char *name=file->name;
1100    if(file->defined&file->TYPE && file->filetype==file->DIRECTORY)
1101    {
1102       char *n=alloca_strdup2(name,1);
1103       strcat(n,"/");
1104       name=n;
1105    }
1106    int result=fnmatch(pattern,name,FNM_PATHNAME|FNM_CASEFOLD);
1107    if(inverted)
1108    {
1109       if(result==0)
1110 	 result=FNM_NOMATCH;
1111       else if(result==FNM_NOMATCH)
1112 	 result=0;
1113    }
1114    return result;
1115 }
1116 
SortByPatternList(const char * list_c)1117 void FileSet::SortByPatternList(const char *list_c)
1118 {
1119    const int max_rank=1000000;
1120    for(int i=0; i<fnum; i++)
1121       files[i]->SetRank(max_rank);
1122    char *list=alloca_strdup(list_c);
1123    int rank=0;
1124    for(char *p=strtok(list," "); p; p=strtok(0," "), rank++)
1125       for(int i=0; i<fnum; i++)
1126 	 if(files[i]->GetRank()==max_rank && !fnmatch_dir(p,files[i]))
1127 	    files[i]->SetRank(rank);
1128    Sort(BYRANK);
1129 }
1130 
MakeLongName()1131 void FileInfo::MakeLongName()
1132 {
1133    char filetype_s[2]="-";
1134    char &filetype_c=filetype_s[0];
1135    switch(filetype)
1136    {
1137    case NORMAL:	   break;
1138    case UNKNOWN:   break;
1139    case DIRECTORY: filetype_c='d'; break;
1140    case SYMLINK:   filetype_c='l'; break;
1141    case REDIRECT:  filetype_c='L'; break;
1142    }
1143    int mode1=(defined&MODE?mode:
1144       (filetype_c=='d'?0755:(filetype_c=='l'?0777:0644)));
1145 
1146    const char *usergroup="";
1147    if(defined&(USER|GROUP)) {
1148       usergroup=xstring::format("%.16s%s%.16s",defined&USER?user:"?",
1149 		  defined&GROUP?"/":"",defined&GROUP?group:"");
1150    }
1151 
1152    int w=20-strlen(usergroup);
1153    if(w<1)
1154       w=1;
1155    char size_str[21];
1156    if(defined&SIZE)
1157       snprintf(size_str,sizeof(size_str),"%*lld",w,(long long)size);
1158    else
1159       snprintf(size_str,sizeof(size_str),"%*s",w,"-");
1160 
1161    const char *date_str="-";
1162    if(defined&DATE)
1163       date_str=TimeDate(date).IsoDateTime();
1164 
1165    longname.vset(filetype_s,format_perms(mode1),"  ",usergroup," ",size_str,
1166       " ",date_str," ",name.get(),NULL);
1167 
1168    if(defined&SYMLINK_DEF)
1169       longname.vappend(" -> ",symlink.get(),NULL);
1170 }
1171 
EstimateMemory() const1172 size_t FileSet::EstimateMemory() const
1173 {
1174    size_t size=sizeof(FileSet)
1175       +files.count()*files.get_element_size()
1176       +sorted.count()*sorted.get_element_size();
1177    for(int i=0; i<fnum; i++)
1178    {
1179       size+=sizeof(FileInfo);
1180       size+=xstrlen(files[i]->name);
1181       size+=xstrlen(files[i]->symlink);
1182       size+=xstrlen(files[i]->longname);
1183    }
1184    return size;
1185 }
1186