1 /*
2 * lftp - file transfer program
3 *
4 * Copyright (c) 1996-2015 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 <errno.h>
22 #include <unistd.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <dirent.h>
27 #include <utime.h>
28 #include <pwd.h>
29
30 #include "LocalAccess.h"
31 #include "xstring.h"
32 #include "misc.h"
33 #include "log.h"
34 #include "LocalDir.h"
35
36 CDECL_BEGIN
37 #include <glob.h>
38 CDECL_END
39
40 #define FILES_AT_ONCE_STAT 64
41 #define FILES_AT_ONCE_READDIR 256
42
New()43 FileAccess *LocalAccess::New() { return new LocalAccess(); }
44
ClassInit()45 void LocalAccess::ClassInit()
46 {
47 // register the class
48 Register("file",LocalAccess::New);
49 }
50
Init()51 void LocalAccess::Init()
52 {
53 done=false;
54 error_code=OK;
55 home.Set(getenv("HOME"));
56 hostname.set("localhost");
57 struct passwd *p=getpwuid(getuid());
58 if(p)
59 user.set(p->pw_name);
60 }
61
LocalAccess()62 LocalAccess::LocalAccess() : FileAccess()
63 {
64 Init();
65 xstring_ca c(xgetcwd());
66 cwd.Set(c?c.get():".");
67 LogNote(10,"local cwd is `%s'",cwd.path.get());
68 }
LocalAccess(const LocalAccess * o)69 LocalAccess::LocalAccess(const LocalAccess *o) : FileAccess(o)
70 {
71 Init();
72 }
73
errno_handle()74 void LocalAccess::errno_handle()
75 {
76 saved_errno=errno;
77 const char *err=strerror(saved_errno);
78 if(mode==RENAME)
79 error.vset("rename(",file.get(),", ",file1.get(),"): ",err,NULL);
80 else
81 error.vset(file.get(),": ",err,NULL);
82 if(saved_errno!=EEXIST)
83 LogError(0,"%s",error.get());
84 }
85
Done()86 int LocalAccess::Done()
87 {
88 if(error_code<0)
89 return error_code;
90 if(done)
91 return OK;
92 switch((open_mode)mode)
93 {
94 case(CLOSED):
95 case(CONNECT_VERIFY):
96 return OK;
97 default:
98 return IN_PROGRESS;
99 }
100 }
101
Do()102 int LocalAccess::Do()
103 {
104 if(Error() || done)
105 return STALL;
106 int m=STALL;
107 if(mode!=CLOSED)
108 ExpandTildeInCWD();
109 switch((open_mode)mode)
110 {
111 case(CLOSED):
112 return m;
113 case(LIST):
114 case(LONG_LIST):
115 case(QUOTE_CMD):
116 if(stream==0)
117 {
118 const char *cmd=0;
119 // FIXME: shell-quote file name
120 if(mode==LIST)
121 {
122 if(file && file[0])
123 cmd=xstring::cat("ls ",shell_encode(file).get(),NULL);
124 else
125 cmd="ls";
126 }
127 else if(mode==LONG_LIST)
128 {
129 if(file && file[0])
130 cmd=xstring::cat("ls -l",shell_encode(file).get(),NULL);
131 else
132 cmd="ls -la";
133 }
134 else// if(mode==QUOTE_CMD)
135 cmd=file;
136 LogNote(5,"running `%s'",cmd);
137 InputFilter *f_stream=new InputFilter(cmd);
138 f_stream->SetCwd(cwd);
139 stream=f_stream;
140 real_pos=0;
141 m=MOVED;
142 }
143 if(stream->getfd()==-1)
144 {
145 if(stream->error())
146 {
147 Fatal(stream->error_text);
148 return MOVED;
149 }
150 TimeoutS(1);
151 return m;
152 }
153 stream->Kill(SIGCONT);
154 return m;
155 case(CHANGE_DIR):
156 {
157 LocalDirectory old_cwd;
158 old_cwd.SetFromCWD();
159 const char *err=old_cwd.Chdir();
160 if(err)
161 {
162 SetError(NO_FILE,err);
163 return MOVED;
164 }
165 if(chdir(file)==-1)
166 {
167 errno_handle();
168 error_code=NO_FILE;
169 }
170 else
171 {
172 cwd.Set(file);
173 old_cwd.Chdir();
174 }
175 done=true;
176 return MOVED;
177 }
178 case(REMOVE):
179 case(REMOVE_DIR): {
180 const char *f=dir_file(cwd,file);
181 LogNote(5,"remove(%s)",f);
182 if((mode==REMOVE?remove:rmdir)(f)==-1)
183 {
184 errno_handle();
185 error_code=NO_FILE;
186 }
187 done=true;
188 return MOVED;
189 }
190 case(RENAME):
191 case(LINK):
192 case(SYMLINK):
193 {
194 const char *cwd_file1=dir_file(cwd,file1);
195 int (*fn)(const char *f1,const char *f2)=(
196 mode==RENAME ? rename :
197 mode==LINK ? link :
198 /*mode==SYMLINK?*/ symlink
199 );
200 if(fn(mode==SYMLINK?file.get():dir_file(cwd,file),cwd_file1)==-1)
201 {
202 errno_handle();
203 error_code=NO_FILE;
204 }
205 done=true;
206 return MOVED;
207 }
208 case(MAKE_DIR):
209 if(mkdir_p)
210 {
211 const char *sl=strchr(file,'/');
212 while(sl)
213 {
214 if(sl>file)
215 mkdir(dir_file(cwd,xstring::get_tmp(file,sl-file)),0775);
216 sl=strchr(sl+1,'/');
217 }
218 }
219 if(mkdir(dir_file(cwd,file),0775)==-1)
220 {
221 errno_handle();
222 error_code=NO_FILE;
223 }
224 done=true;
225 return MOVED;
226 case(CHANGE_MODE):
227 if(chmod(dir_file(cwd,file),chmod_mode)==-1)
228 {
229 errno_handle();
230 error_code=NO_FILE;
231 }
232 done=true;
233 return MOVED;
234
235 case(RETRIEVE):
236 case(STORE):
237 if(stream==0)
238 {
239 int o_mode=O_RDONLY;
240 if(mode==STORE)
241 {
242 o_mode=O_WRONLY|O_CREAT;
243 if(pos==0)
244 o_mode|=O_TRUNC;
245 }
246 stream=new FileStream(dir_file(cwd,file),o_mode);
247 real_pos=-1;
248 m=MOVED;
249 }
250 if(stream->getfd()==-1)
251 {
252 if(stream->error())
253 {
254 SetError(NO_FILE,stream->error_text);
255 return MOVED;
256 }
257 TimeoutS(1);
258 return m;
259 }
260 stream->Kill(SIGCONT);
261 if(opt_size || opt_date)
262 {
263 struct stat st;
264 if(fstat(stream->getfd(),&st)==-1)
265 {
266 if(opt_size) *opt_size=NO_SIZE;
267 if(opt_date) *opt_date=NO_DATE;
268 }
269 else
270 {
271 if(opt_size)
272 *opt_size=(S_ISREG(st.st_mode)?st.st_size:NO_SIZE);
273 if(opt_date)
274 *opt_date=st.st_mtime;
275 }
276 opt_size=0;
277 opt_date=0;
278 }
279 return m;
280
281 case(CONNECT_VERIFY):
282 done=true;
283 return MOVED;
284
285 case(ARRAY_INFO):
286 fill_array_info();
287 done=true;
288 return MOVED;
289 case MP_LIST:
290 SetError(NOT_SUPP);
291 return MOVED;
292 }
293 return m;
294 }
295
fill_array_info()296 void LocalAccess::fill_array_info()
297 {
298 for(FileInfo *fi=fileset_for_info->curr(); fi; fi=fileset_for_info->next())
299 fi->LocalFile(fi->name,(fi->filetype!=fi->SYMLINK));
300 }
301
Read(Buffer * buf0,int size)302 int LocalAccess::Read(Buffer *buf0,int size)
303 {
304 if(error_code<0)
305 return error_code;
306 if(stream==0)
307 return DO_AGAIN;
308 int fd=stream->getfd();
309 if(fd==-1)
310 return DO_AGAIN;
311 if(real_pos==-1)
312 {
313 if(ascii || lseek(fd,pos,SEEK_SET)==-1)
314 real_pos=0;
315 else
316 real_pos=pos;
317 }
318 stream->Kill(SIGCONT);
319 read_again:
320 int res;
321
322 char *buf=buf0->GetSpace(size);
323 #ifndef NATIVE_CRLF
324 if(ascii)
325 res=read(fd,buf,size/2);
326 else
327 #endif
328 res=read(fd,buf,size);
329
330 if(res<0)
331 {
332 saved_errno=errno;
333 if(E_RETRY(saved_errno))
334 {
335 Block(stream->getfd(),POLLIN);
336 return DO_AGAIN;
337 }
338 if(stream->NonFatalError(saved_errno))
339 return DO_AGAIN;
340 return SEE_ERRNO;
341 }
342 stream->clear_status();
343 if(res==0)
344 return res; // eof
345
346 #ifndef NATIVE_CRLF
347 if(ascii)
348 {
349 char *p=buf;
350 for(int i=res; i>0; i--)
351 {
352 if(*p=='\n')
353 {
354 memmove(p+1,p,i);
355 *p++='\r';
356 res++;
357 }
358 p++;
359 }
360 }
361 #endif
362
363 real_pos+=res;
364 if(real_pos<=pos)
365 goto read_again;
366 off_t shift=pos+res-real_pos;
367 if(shift>0)
368 {
369 memmove(buf,buf+shift,size-shift);
370 res-=shift;
371 }
372 pos+=res;
373 return(res);
374 }
375
Write(const void * vbuf,int len)376 int LocalAccess::Write(const void *vbuf,int len)
377 {
378 const char *buf=(const char *)vbuf;
379 if(error_code<0)
380 return error_code;
381 if(stream==0)
382 return DO_AGAIN;
383 int fd=stream->getfd();
384 if(fd==-1)
385 return DO_AGAIN;
386 if(real_pos==-1)
387 {
388 if(ascii || lseek(fd,pos,SEEK_SET)==-1)
389 real_pos=0;
390 else
391 real_pos=pos;
392 if(real_pos<pos)
393 {
394 error_code=STORE_FAILED;
395 return error_code;
396 }
397 }
398 stream->Kill(SIGCONT);
399
400 int skip_cr=0;
401
402 #ifndef NATIVE_CRLF
403 if(ascii)
404 {
405 // find where line ends.
406 const char *cr=buf;
407 for(;;)
408 {
409 cr=(const char *)memchr(cr,'\r',len-(cr-buf));
410 if(!cr)
411 break;
412 if(cr-buf<len-1 && cr[1]=='\n')
413 {
414 skip_cr=1;
415 len=cr-buf;
416 break;
417 }
418 if(cr-buf==len-1)
419 {
420 if(len==1) // last CR in stream will be lost. (FIX?)
421 skip_cr=1;
422 len--;
423 break;
424 }
425 cr++;
426 }
427 }
428 #endif // NATIVE_CRLF
429
430 if(len==0)
431 {
432 pos=(real_pos+=skip_cr);
433 return skip_cr;
434 }
435
436 int res=write(fd,buf,len);
437 if(res<0)
438 {
439 saved_errno=errno;
440 if(E_RETRY(saved_errno))
441 {
442 Block(stream->getfd(),POLLOUT);
443 return DO_AGAIN;
444 }
445 if(stream->NonFatalError(saved_errno))
446 {
447 // in case of full disk, check file correctness.
448 if(saved_errno==ENOSPC)
449 {
450 struct stat st;
451 if(fstat(fd,&st)!=-1)
452 {
453 if(st.st_size<pos)
454 {
455 // workaround solaris nfs bug. It can lose data if disk is full.
456 pos=real_pos=st.st_size;
457 return DO_AGAIN;
458 }
459 }
460 }
461 return DO_AGAIN;
462 }
463 return SEE_ERRNO;
464 }
465 stream->clear_status();
466
467 if(res==len)
468 res+=skip_cr;
469 pos=(real_pos+=res);
470 return res;
471 }
472
StoreStatus()473 int LocalAccess::StoreStatus()
474 {
475 if(mode!=STORE)
476 return OK;
477 if(!stream)
478 return IN_PROGRESS;
479 if(stream->getfd()==-1)
480 {
481 if(stream->error())
482 SetError(NO_FILE,stream->error_text);
483 }
484 stream=0;
485 if(error_code==OK && entity_date!=NO_DATE)
486 {
487 static struct utimbuf ut;
488 ut.actime=ut.modtime=entity_date;
489 utime(dir_file(cwd,file),&ut);
490 }
491
492 if(error_code<0)
493 return error_code;
494
495 return OK;
496 }
497
Close()498 void LocalAccess::Close()
499 {
500 done=false;
501 error_code=OK;
502 stream=0;
503 FileAccess::Close();
504 }
505
CurrentStatus()506 const char *LocalAccess::CurrentStatus()
507 {
508 if(stream && stream->status)
509 return stream->status;
510 return "";
511 }
512
SameLocationAs(const FileAccess * fa) const513 bool LocalAccess::SameLocationAs(const FileAccess *fa) const
514 {
515 if(!SameProtoAs(fa))
516 return false;
517 LocalAccess *o=(LocalAccess*)fa;
518
519 if(xstrcmp(home,o->home))
520 return false;
521
522 return !xstrcmp(cwd,o->cwd);
523 }
524
525 class LocalListInfo : public ListInfo
526 {
527 DIR *dir;
528 public:
LocalListInfo(FileAccess * s,const char * d)529 LocalListInfo(FileAccess *s,const char *d) : ListInfo(s,d), dir(0) {}
~LocalListInfo()530 ~LocalListInfo() { if(dir) closedir(dir); }
531 const char *Status();
532 int Do();
533 };
534
MakeListInfo(const char * path)535 ListInfo *LocalAccess::MakeListInfo(const char *path)
536 {
537 return new LocalListInfo(this,path);
538 }
539
Do()540 int LocalListInfo::Do()
541 {
542 int m=STALL;
543
544 if(done)
545 return STALL;
546
547 if(!dir && !result)
548 {
549 const char *path=session->GetCwd();
550 dir=opendir(path);
551 if(!dir)
552 {
553 SetError(xstring::format("%s: %s",path,strerror(errno)));
554 return MOVED;
555 }
556 m=MOVED;
557 }
558 if(dir)
559 {
560 if(!result)
561 result=new FileSet;
562 int count=FILES_AT_ONCE_READDIR;
563 for(;;)
564 {
565 struct dirent *f=readdir(dir);
566 if(f==0)
567 break;
568 const char *name=f->d_name;
569 if(name[0]=='~')
570 name=dir_file(".",name);
571 result->Add(new FileInfo(name));
572 if(!--count)
573 return MOVED; // let other tasks run
574 }
575 closedir(dir);
576 dir=0;
577 result->rewind();
578 m=MOVED;
579 }
580 if(!dir && result)
581 {
582 int count=FILES_AT_ONCE_STAT;
583 const char *path=session->GetCwd();
584 for(FileInfo *file=result->curr(); file!=0; file=result->next())
585 {
586 const char *name=dir_file(path,file->name);
587 file->LocalFile(name, follow_symlinks);
588 if(!(file->defined&file->TYPE))
589 result->SubtractCurr();
590 if(!--count)
591 return MOVED; // let other tasks run
592 }
593 result->Exclude(exclude_prefix,exclude,excluded.get_non_const());
594 done=true;
595 m=MOVED;
596 }
597 return m;
598 }
Status()599 const char *LocalListInfo::Status()
600 {
601 if(done)
602 return "";
603 if(dir && result)
604 return xstring::format("%s (%d)",_("Getting directory contents"),result->count());
605 if(result && result->count())
606 return xstring::format("%s (%d%%)",_("Getting files information"),result->curr_pct());
607 return "";
608 }
609
610 #include "FileGlob.h"
611 class LocalGlob : public Glob
612 {
613 const char *cwd;
614 public:
615 LocalGlob(const char *cwd,const char *pattern);
Status()616 const char *Status() { return "Reading directory"; }
617 int Do();
618 };
MakeGlob(const char * pattern)619 Glob *LocalAccess::MakeGlob(const char *pattern)
620 {
621 file.set(pattern);
622 ExpandTildeInCWD();
623 return new LocalGlob(cwd,file);
624 }
625
LocalGlob(const char * c,const char * pattern)626 LocalGlob::LocalGlob(const char *c,const char *pattern)
627 : Glob(0,pattern)
628 {
629 cwd=c;
630 }
Do()631 int LocalGlob::Do()
632 {
633 if(done)
634 return STALL;
635
636 glob_t g;
637 LocalDirectory oldcwd;
638 oldcwd.SetFromCWD();
639 // check if we can return.
640 if(oldcwd.Chdir())
641 {
642 SetError(_("cannot get current directory"));
643 return MOVED;
644 }
645 if(chdir(cwd)==-1)
646 {
647 SetError(xstring::format("chdir(%s): %s",cwd,strerror(errno)));
648 return MOVED;
649 }
650
651 glob(pattern, 0, NULL, &g);
652
653 for(unsigned i=0; i<g.gl_pathc; i++)
654 {
655 struct stat st;
656 FileInfo info(g.gl_pathv[i]);
657 if(stat(g.gl_pathv[i],&st)!=-1)
658 {
659 if(dirs_only && !S_ISDIR(st.st_mode))
660 continue;
661 if(files_only && !S_ISREG(st.st_mode))
662 continue;
663 if(S_ISDIR(st.st_mode))
664 info.SetType(info.DIRECTORY);
665 else if(S_ISREG(st.st_mode))
666 info.SetType(info.NORMAL);
667 }
668 add(&info);
669 }
670 globfree(&g);
671
672 const char *err=oldcwd.Chdir();
673 const char *name=oldcwd.GetName();
674 if(err)
675 fprintf(stderr,"chdir(%s): %s",name?name:"?",err);
676
677 done=true;
678 return MOVED;
679 }
680
681 class LocalDirList : public DirList
682 {
683 SMTaskRef<IOBuffer> ubuf;
684 Ref<FgData> fg_data;
685 public:
686 LocalDirList(ArgV *a,const char *cwd);
Status()687 const char *Status() { return ""; }
688 int Do();
689 };
690
MakeDirList(ArgV * a)691 DirList *LocalAccess::MakeDirList(ArgV *a)
692 {
693 return new LocalDirList(a,cwd);
694 }
695
696 #include "ArgV.h"
LocalDirList(ArgV * a,const char * cwd)697 LocalDirList::LocalDirList(ArgV *a,const char *cwd)
698 : DirList(0,0)
699 {
700 a->setarg(0,"ls");
701 a->insarg(1,"-l");
702 InputFilter *f=new InputFilter(a); // a is consumed.
703 f->SetCwd(cwd);
704 ubuf=new IOBufferFDStream(f,IOBuffer::GET);
705 }
Do()706 int LocalDirList::Do()
707 {
708 if(done)
709 return STALL;
710
711 if(buf->Eof())
712 {
713 done=true;
714 return MOVED;
715 }
716
717 if(ubuf->Error())
718 {
719 SetError(ubuf->ErrorText());
720 return MOVED;
721 }
722 if(!fg_data)
723 fg_data=ubuf->GetFgData(false);
724 const char *b;
725 int len;
726 ubuf->Get(&b,&len);
727 if(b==0) // eof
728 {
729 buf->PutEOF();
730 return MOVED;
731 }
732 if(len==0)
733 return STALL;
734 buf->Put(b,len);
735 ubuf->Skip(len);
736 return MOVED;
737 }
738
739 #include "modconfig.h"
740 #ifdef MODULE_PROTO_FILE
module_init()741 CDECL void module_init()
742 {
743 LocalAccess::ClassInit();
744 }
745 #endif
746