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