/* * lftp - file transfer program * * Copyright (c) 1996-2012 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 . */ /* Get information about a path (dir or file). If _dir is a file, get * information about that file only. If it's a directory, get information * about files in it. If _showdir is true, act like ls -d: get information * about the directory itself. * * To find out if a path is a directory, attempt to chdir into it. If it * succeeds it's a directory, otherwise it's a file (or there was an error). * * If the cache knows the file type of _dir, avoid changing directories if * possible, so cached listings don't touch the connection at all. * * List of cases: * * showdirs off: * 1. We CD into the directory. * a. We get a listing. Success. * b. We fail to get a listing. Fail. * 2. We fail to CD into the path. We fail to CD to the parent. Fail. * 3. We fail to CD into the path. We CD to the parent and fail to get a listing. * Do GetInfoArray case. * 4. We fail to CD into the path. We CD to the parent and get a listing. * a. The path we were looking for is a directory. Fail. (If it's a directory, * we need to get its contents or nothing at all.) * b. The path we were looking for is not a directory. Success. * c. The path we were looking for isn't there. Do GetInfoArray case. * * showdirs on: * * 1. We CD to the parent and get a listing. The path we were looking for * is there. Success. * 2. We CD to the parent and fail to get a listing. * a. We CD to the path. (Partial success: we know it exists and is a directory.) * b. We fail to CD to the path. Do GetInfoArray case. * 3. We fail to CD to the parent. * a. We CD to the path. The path is a directory. Success (don't try to * get more information.) * b. We fail to CD to the path. Fail. * 4. We CD to the parent and fail to get a listing OR * We CD to the parent and the listing does not contain the path. * a. We CD to the path. The path is a directory. Success (don't try to * get more information.) * b. We fail to CD to the path. Do GetInfoArray case. * * GetInfoArray case: * A. We GetInfoArray in the parent, which tells us something. We * know the path exists, and is not a directory that we have access * to. Success. * B. We GetInfoArray in the parent, which doesn't tell us anything. * We have no evidence the path exists at all. Fail. * * If we fail from something in cache, we don't know why, so turn cache off * and attempt to CD into the path to get an error message. * * All of these cases can operate out of cache, so be sure to test both, as * the code flow is often different. (GetInfoArray never operates out of cache.) */ #include #include #include "GetFileInfo.h" #include "misc.h" #include "LsCache.h" GetFileInfo::GetFileInfo(const FileAccessRef& a, const char *_dir, bool _showdir) : ListInfo(0,0), session(a), dir(_dir?_dir:""), origdir(a->GetCwd()) { showdir=_showdir; state=INITIAL; tried_dir=tried_file=tried_info=false; result=0; li=0; from_cache=0; was_directory=false; prepend_path=true; const char *bn=basename_ptr(dir); if((bn[0]=='.' && (bn[1]==0 || bn[1]=='/' || (bn[1]=='.' && (bn[2]==0 || bn[2]=='/')))) || (bn[0]=='/')) { // . .. / are directories, don't try them as a file. tried_file=true; } } GetFileInfo::~GetFileInfo() {} void GetFileInfo::PrepareToDie() { if(session) { session->Close(); session->SetCwd(origdir); } } int GetFileInfo::Do() { int res; int m=STALL; if(Done()) return m; switch(state) { case INITIAL: state=CHANGE_DIR; m=MOVED; if(use_cache) { int is_dir=-1; if(last_char(dir)=='/') is_dir=1; else is_dir=FileAccess::cache->IsDirectory(session,dir); switch(is_dir) { case 0: tried_dir = true; /* it's a file */ from_cache = true; break; case 1: if(!showdir) tried_file = true; /* it's a dir */ from_cache = true; break; } } assert(!tried_dir || !tried_file || !tried_info); /* always do at least one */ case CHANGE_DIR: { if(tried_dir && tried_file && tried_info) { /* We tried everything; no luck. Fail. */ if(saved_error_text) { SetError(saved_error_text); state=DONE; return MOVED; } /* We don't have an error message. We may have done everything * out of cache. */ tried_dir=false; // this will get error message. from_cache=false; } session->SetCwd(origdir); const char *cd_path=0; if(!tried_dir && (tried_file || !showdir)) { /* First, try to treat the path as a directory, * if we are going to show its contents */ tried_dir=true; cd_path = dir; path_to_prefix.set(dir); was_directory=true; } else if(!tried_file) { /* Try to treat the path as a file. If showdir is true, * this is done first. */ tried_file=true; /* Chdir to the parent directory of the path: */ session->Chdir(dir, false); cd_path = ".."; path_to_prefix.set(dirname(dir)); was_directory=false; } else if(!tried_info) { tried_info=true; /* This is always done after tried_file or a failed tried_dir, * so we should be in the parent, but let's make sure: */ session->Chdir(dir, false); session->Chdir("..", false); path_to_prefix.set(dirname(dir)); was_directory=false; /* We tried both; no luck. Fall back on ARRAY_INFO. */ state=GETTING_INFO_ARRAY; return MOVED; } /* We still need to Chdir() if we're operating out of cache (that's how you * look up cache entries). However, we don't really want to change the * directory of the session (ie. send a CWD if we're FTP), so set verify to * false if we're operating out of cache. */ bool cd_verify = !from_cache; /* We can *not* do this out of cache if 1: dir starts with a ~ and 2: we don't * know the home path. * * Yes we can, usually--we may know the home path in home_auto (FTP), but * GetHome won't return that. We need to do this if we *really* don't know * it. */ /* if(dir[0] == '~' && !session->GetHome()) cd_verify = true; */ session->Chdir(cd_path, cd_verify); state=CHANGING_DIR; m=MOVED; } case CHANGING_DIR: res=session->Done(); if(res==FA::IN_PROGRESS) return m; if(res<0) { /* Failed. Save the error, then go back and try to CD again. * Only save the first error, so error texts contain the full * path. */ if(!saved_error_text) saved_error_text.set(session->StrError(res)); session->Close(); if(res==FA::NO_FILE) { /* If this is a CWD to the parent, and it failed, we * can't do GetInfoArray. */ if(!was_directory) tried_info=true; state=CHANGE_DIR; return MOVED; } SetError(saved_error_text); state=DONE; return MOVED; } session->Close(); if(!from_cache) FileAccess::cache->SetDirectory(session,"",true); /* Now that we've connected, we should have the home directory path. Find out * the real name of the path. (We may have something like "~/..".) */ if(!verify_fn) { FileAccess::Path pwd(session->GetCwd()); session->SetCwd(origdir); session->Chdir(dir, false); verify_fn.set(basename_ptr(session->GetCwd())); /* go back */ session->SetCwd(pwd); } /* Special case: looking up "/". Make a phony entry. */ if(showdir && !strcmp(verify_fn, "/")) { FileInfo *fi = new FileInfo(verify_fn); fi->SetType(fi->DIRECTORY); result = new FileSet; result->Add(fi); path_to_prefix.set(dirname(dir)); state=DONE; return MOVED; } if(was_directory && showdir) { /* We could chdir to the dir, but we should not get dir listing. * We got here because either we could not get dir listing of * parent directory or the file name was not found in parent * directory index. */ FileInfo *fi = new FileInfo(dir); fi->SetType(fi->DIRECTORY); path_to_prefix.set(dirname(dir)); result = new FileSet; result->Add(fi); state=DONE; return MOVED; } /* Get a listing: */ li=session->MakeListInfo(); if(follow_symlinks) li->FollowSymlinks(); li->UseCache(use_cache); li->NoNeed(FileInfo::ALL_INFO); /* clear need */ li->Need(need); li->SetExclude(exclude_prefix, exclude); state=GETTING_LIST; m=MOVED; case GETTING_LIST: if(li->Error()) { /* If we're listing contents of dirs, and this was listing * a path (as a directory), fail: */ if(!showdir && was_directory) { SetError(li->ErrorText()); return MOVED; } if(!saved_error_text) saved_error_text.set(li->ErrorText()); /* Otherwise, go on to try the next mode. */ state=CHANGE_DIR; return MOVED; } if(!li->Done()) return m; state=DONE; m=MOVED; /* Got the list. Steal it from the listinfo: */ result=li->GetResult(); li=0; /* If this was a listing of the basename: */ if(!was_directory) { verify_fn.rtrim('/'); /* Find the file with our filename: */ const FileInfo *file = result->FindByName(verify_fn); if(!file) { /* It doesn't exist, or we have no result (failed). */ result=0; tried_file=true; from_cache=false; state=CHANGE_DIR; return MOVED; } /* If we're not listing directories as files, and the file is a * directory, we should have been able to Chdir into it to begin * with. We probably got Access Denied. Fail. */ if(!showdir && (file->defined&file->TYPE) && file->filetype==FileInfo::DIRECTORY) { result=0; if(saved_error_text) { SetError(saved_error_text); goto done; } tried_file=true; from_cache=false; state=CHANGE_DIR; return MOVED; } FileInfo *copy = new FileInfo(*file); result=new FileSet(); result->Add(copy); } goto done; case GETTING_INFO_ARRAY: if(session->IsClosed()) { /* * Try to get requested information with GetInfoArray. This * also serves as a last attempt to see if the file exists--we * only get here if everything else thinks the path doesn't exist. */ FileInfo *fi=new FileInfo(verify_fn); fi->need=need; /* We need to do at least one. */ if(!fi->need) fi->need=fi->DATE; get_info.Empty(); get_info.Add(fi); session->GetInfoArray(&get_info); } res=session->Done(); if(res==FA::IN_PROGRESS) return m; if(res < 0) { if(!saved_error_text) saved_error_text.set(session->StrError(res)); state=CHANGE_DIR; return MOVED; } session->Close(); { FileInfo *fi=get_info[0]; if(!fi->HasAny(fi->SIZE|fi->DATE)) { /* We didn't get any information. The file probably doesn't * exist. Not necessarily: it might have been a directory * that we don't have access to CD into. Some servers will * refuse to give even an MDTM for directories. We could * scan the MDTM and/or SIZE responses for "not a plain file" * for some servers (proftpd). */ state=CHANGE_DIR; return MOVED; } /* We got at least one, so the file exists. Return what we know. */ was_directory = false; result = new FileSet; result->Add(new FileInfo(*fi)); } state=DONE; return MOVED; done: case DONE: if(!done) { if(result && showdir && result->get_fnum()>0) { FileInfo *f = (*result)[0]; /* Make sure the filename is what was requested (ie ".."). */ const char *fn = basename_ptr(dir); f->SetName(*fn? fn:"."); /* If we're in show_dir mode, was_directory will always be false; * set it to whether the single file is actually a directory or not. */ if(f->defined&f->TYPE) was_directory = (f->filetype == f->DIRECTORY); } if(result && prepend_path) result->PrependPath(path_to_prefix); done=true; m=MOVED; } return m; } abort(); } const char *GetFileInfo::Status() { if(Done()) return ""; if(li && !li->Done()) return li->Status(); if(session->IsOpen()) return session->CurrentStatus(); return ""; }