/* * lftp - file transfer program * * Copyright (c) 1996-2015 by Alexander V. Lukyanov (lav@yars.free.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "LocalAccess.h" #include "xstring.h" #include "misc.h" #include "log.h" #include "LocalDir.h" CDECL_BEGIN #include CDECL_END #define FILES_AT_ONCE_STAT 64 #define FILES_AT_ONCE_READDIR 256 FileAccess *LocalAccess::New() { return new LocalAccess(); } void LocalAccess::ClassInit() { // register the class Register("file",LocalAccess::New); } void LocalAccess::Init() { done=false; error_code=OK; home.Set(getenv("HOME")); hostname.set("localhost"); struct passwd *p=getpwuid(getuid()); if(p) user.set(p->pw_name); } LocalAccess::LocalAccess() : FileAccess() { Init(); xstring_ca c(xgetcwd()); cwd.Set(c?c.get():"."); LogNote(10,"local cwd is `%s'",cwd.path.get()); } LocalAccess::LocalAccess(const LocalAccess *o) : FileAccess(o) { Init(); } void LocalAccess::errno_handle() { saved_errno=errno; const char *err=strerror(saved_errno); if(mode==RENAME) error.vset("rename(",file.get(),", ",file1.get(),"): ",err,NULL); else error.vset(file.get(),": ",err,NULL); if(saved_errno!=EEXIST) LogError(0,"%s",error.get()); } int LocalAccess::Done() { if(error_code<0) return error_code; if(done) return OK; switch((open_mode)mode) { case(CLOSED): case(CONNECT_VERIFY): return OK; default: return IN_PROGRESS; } } int LocalAccess::Do() { if(Error() || done) return STALL; int m=STALL; if(mode!=CLOSED) ExpandTildeInCWD(); switch((open_mode)mode) { case(CLOSED): return m; case(LIST): case(LONG_LIST): case(QUOTE_CMD): if(stream==0) { const char *cmd=0; // FIXME: shell-quote file name if(mode==LIST) { if(file && file[0]) cmd=xstring::cat("ls ",shell_encode(file).get(),NULL); else cmd="ls"; } else if(mode==LONG_LIST) { if(file && file[0]) cmd=xstring::cat("ls -l",shell_encode(file).get(),NULL); else cmd="ls -la"; } else// if(mode==QUOTE_CMD) cmd=file; LogNote(5,"running `%s'",cmd); InputFilter *f_stream=new InputFilter(cmd); f_stream->SetCwd(cwd); stream=f_stream; real_pos=0; m=MOVED; } if(stream->getfd()==-1) { if(stream->error()) { Fatal(stream->error_text); return MOVED; } TimeoutS(1); return m; } stream->Kill(SIGCONT); return m; case(CHANGE_DIR): { LocalDirectory old_cwd; old_cwd.SetFromCWD(); const char *err=old_cwd.Chdir(); if(err) { SetError(NO_FILE,err); return MOVED; } if(chdir(file)==-1) { errno_handle(); error_code=NO_FILE; } else { cwd.Set(file); old_cwd.Chdir(); } done=true; return MOVED; } case(REMOVE): case(REMOVE_DIR): { const char *f=dir_file(cwd,file); LogNote(5,"remove(%s)",f); if((mode==REMOVE?remove:rmdir)(f)==-1) { errno_handle(); error_code=NO_FILE; } done=true; return MOVED; } case(RENAME): case(LINK): case(SYMLINK): { const char *cwd_file1=dir_file(cwd,file1); int (*fn)(const char *f1,const char *f2)=( mode==RENAME ? rename : mode==LINK ? link : /*mode==SYMLINK?*/ symlink ); if(fn(mode==SYMLINK?file.get():dir_file(cwd,file),cwd_file1)==-1) { errno_handle(); error_code=NO_FILE; } done=true; return MOVED; } case(MAKE_DIR): if(mkdir_p) { const char *sl=strchr(file,'/'); while(sl) { if(sl>file) mkdir(dir_file(cwd,xstring::get_tmp(file,sl-file)),0775); sl=strchr(sl+1,'/'); } } if(mkdir(dir_file(cwd,file),0775)==-1) { errno_handle(); error_code=NO_FILE; } done=true; return MOVED; case(CHANGE_MODE): if(chmod(dir_file(cwd,file),chmod_mode)==-1) { errno_handle(); error_code=NO_FILE; } done=true; return MOVED; case(RETRIEVE): case(STORE): if(stream==0) { int o_mode=O_RDONLY; if(mode==STORE) { o_mode=O_WRONLY|O_CREAT; if(pos==0) o_mode|=O_TRUNC; } stream=new FileStream(dir_file(cwd,file),o_mode); real_pos=-1; m=MOVED; } if(stream->getfd()==-1) { if(stream->error()) { SetError(NO_FILE,stream->error_text); return MOVED; } TimeoutS(1); return m; } stream->Kill(SIGCONT); if(opt_size || opt_date) { struct stat st; if(fstat(stream->getfd(),&st)==-1) { if(opt_size) *opt_size=NO_SIZE; if(opt_date) *opt_date=NO_DATE; } else { if(opt_size) *opt_size=(S_ISREG(st.st_mode)?st.st_size:NO_SIZE); if(opt_date) *opt_date=st.st_mtime; } opt_size=0; opt_date=0; } return m; case(CONNECT_VERIFY): done=true; return MOVED; case(ARRAY_INFO): fill_array_info(); done=true; return MOVED; case MP_LIST: SetError(NOT_SUPP); return MOVED; } return m; } void LocalAccess::fill_array_info() { for(FileInfo *fi=fileset_for_info->curr(); fi; fi=fileset_for_info->next()) fi->LocalFile(fi->name,(fi->filetype!=fi->SYMLINK)); } int LocalAccess::Read(Buffer *buf0,int size) { if(error_code<0) return error_code; if(stream==0) return DO_AGAIN; int fd=stream->getfd(); if(fd==-1) return DO_AGAIN; if(real_pos==-1) { if(ascii || lseek(fd,pos,SEEK_SET)==-1) real_pos=0; else real_pos=pos; } stream->Kill(SIGCONT); read_again: int res; char *buf=buf0->GetSpace(size); #ifndef NATIVE_CRLF if(ascii) res=read(fd,buf,size/2); else #endif res=read(fd,buf,size); if(res<0) { saved_errno=errno; if(E_RETRY(saved_errno)) { Block(stream->getfd(),POLLIN); return DO_AGAIN; } if(stream->NonFatalError(saved_errno)) return DO_AGAIN; return SEE_ERRNO; } stream->clear_status(); if(res==0) return res; // eof #ifndef NATIVE_CRLF if(ascii) { char *p=buf; for(int i=res; i>0; i--) { if(*p=='\n') { memmove(p+1,p,i); *p++='\r'; res++; } p++; } } #endif real_pos+=res; if(real_pos<=pos) goto read_again; off_t shift=pos+res-real_pos; if(shift>0) { memmove(buf,buf+shift,size-shift); res-=shift; } pos+=res; return(res); } int LocalAccess::Write(const void *vbuf,int len) { const char *buf=(const char *)vbuf; if(error_code<0) return error_code; if(stream==0) return DO_AGAIN; int fd=stream->getfd(); if(fd==-1) return DO_AGAIN; if(real_pos==-1) { if(ascii || lseek(fd,pos,SEEK_SET)==-1) real_pos=0; else real_pos=pos; if(real_posKill(SIGCONT); int skip_cr=0; #ifndef NATIVE_CRLF if(ascii) { // find where line ends. const char *cr=buf; for(;;) { cr=(const char *)memchr(cr,'\r',len-(cr-buf)); if(!cr) break; if(cr-bufgetfd(),POLLOUT); return DO_AGAIN; } if(stream->NonFatalError(saved_errno)) { // in case of full disk, check file correctness. if(saved_errno==ENOSPC) { struct stat st; if(fstat(fd,&st)!=-1) { if(st.st_sizeclear_status(); if(res==len) res+=skip_cr; pos=(real_pos+=res); return res; } int LocalAccess::StoreStatus() { if(mode!=STORE) return OK; if(!stream) return IN_PROGRESS; if(stream->getfd()==-1) { if(stream->error()) SetError(NO_FILE,stream->error_text); } stream=0; if(error_code==OK && entity_date!=NO_DATE) { static struct utimbuf ut; ut.actime=ut.modtime=entity_date; utime(dir_file(cwd,file),&ut); } if(error_code<0) return error_code; return OK; } void LocalAccess::Close() { done=false; error_code=OK; stream=0; FileAccess::Close(); } const char *LocalAccess::CurrentStatus() { if(stream && stream->status) return stream->status; return ""; } bool LocalAccess::SameLocationAs(const FileAccess *fa) const { if(!SameProtoAs(fa)) return false; LocalAccess *o=(LocalAccess*)fa; if(xstrcmp(home,o->home)) return false; return !xstrcmp(cwd,o->cwd); } class LocalListInfo : public ListInfo { DIR *dir; public: LocalListInfo(FileAccess *s,const char *d) : ListInfo(s,d), dir(0) {} ~LocalListInfo() { if(dir) closedir(dir); } const char *Status(); int Do(); }; ListInfo *LocalAccess::MakeListInfo(const char *path) { return new LocalListInfo(this,path); } int LocalListInfo::Do() { int m=STALL; if(done) return STALL; if(!dir && !result) { const char *path=session->GetCwd(); dir=opendir(path); if(!dir) { SetError(xstring::format("%s: %s",path,strerror(errno))); return MOVED; } m=MOVED; } if(dir) { if(!result) result=new FileSet; int count=FILES_AT_ONCE_READDIR; for(;;) { struct dirent *f=readdir(dir); if(f==0) break; const char *name=f->d_name; if(name[0]=='~') name=dir_file(".",name); result->Add(new FileInfo(name)); if(!--count) return MOVED; // let other tasks run } closedir(dir); dir=0; result->rewind(); m=MOVED; } if(!dir && result) { int count=FILES_AT_ONCE_STAT; const char *path=session->GetCwd(); for(FileInfo *file=result->curr(); file!=0; file=result->next()) { const char *name=dir_file(path,file->name); file->LocalFile(name, follow_symlinks); if(!(file->defined&file->TYPE)) result->SubtractCurr(); if(!--count) return MOVED; // let other tasks run } result->Exclude(exclude_prefix,exclude,excluded.get_non_const()); done=true; m=MOVED; } return m; } const char *LocalListInfo::Status() { if(done) return ""; if(dir && result) return xstring::format("%s (%d)",_("Getting directory contents"),result->count()); if(result && result->count()) return xstring::format("%s (%d%%)",_("Getting files information"),result->curr_pct()); return ""; } #include "FileGlob.h" class LocalGlob : public Glob { const char *cwd; public: LocalGlob(const char *cwd,const char *pattern); const char *Status() { return "Reading directory"; } int Do(); }; Glob *LocalAccess::MakeGlob(const char *pattern) { file.set(pattern); ExpandTildeInCWD(); return new LocalGlob(cwd,file); } LocalGlob::LocalGlob(const char *c,const char *pattern) : Glob(0,pattern) { cwd=c; } int LocalGlob::Do() { if(done) return STALL; glob_t g; LocalDirectory oldcwd; oldcwd.SetFromCWD(); // check if we can return. if(oldcwd.Chdir()) { SetError(_("cannot get current directory")); return MOVED; } if(chdir(cwd)==-1) { SetError(xstring::format("chdir(%s): %s",cwd,strerror(errno))); return MOVED; } glob(pattern, 0, NULL, &g); for(unsigned i=0; i ubuf; Ref fg_data; public: LocalDirList(ArgV *a,const char *cwd); const char *Status() { return ""; } int Do(); }; DirList *LocalAccess::MakeDirList(ArgV *a) { return new LocalDirList(a,cwd); } #include "ArgV.h" LocalDirList::LocalDirList(ArgV *a,const char *cwd) : DirList(0,0) { a->setarg(0,"ls"); a->insarg(1,"-l"); InputFilter *f=new InputFilter(a); // a is consumed. f->SetCwd(cwd); ubuf=new IOBufferFDStream(f,IOBuffer::GET); } int LocalDirList::Do() { if(done) return STALL; if(buf->Eof()) { done=true; return MOVED; } if(ubuf->Error()) { SetError(ubuf->ErrorText()); return MOVED; } if(!fg_data) fg_data=ubuf->GetFgData(false); const char *b; int len; ubuf->Get(&b,&len); if(b==0) // eof { buf->PutEOF(); return MOVED; } if(len==0) return STALL; buf->Put(b,len); ubuf->Skip(len); return MOVED; } #include "modconfig.h" #ifdef MODULE_PROTO_FILE CDECL void module_init() { LocalAccess::ClassInit(); } #endif