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