Blame src/FileSetOutput.cc

Packit 8f70b4
/*
Packit 8f70b4
 * lftp - file transfer program
Packit 8f70b4
 *
Packit 8f70b4
 * Copyright (c) 1996-2017 by Alexander V. Lukyanov (lav@yars.free.net)
Packit 8f70b4
 *
Packit 8f70b4
 * This program is free software; you can redistribute it and/or modify
Packit 8f70b4
 * it under the terms of the GNU General Public License as published by
Packit 8f70b4
 * the Free Software Foundation; either version 3 of the License, or
Packit 8f70b4
 * (at your option) any later version.
Packit 8f70b4
 *
Packit 8f70b4
 * This program is distributed in the hope that it will be useful,
Packit 8f70b4
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8f70b4
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8f70b4
 * GNU General Public License for more details.
Packit 8f70b4
 *
Packit 8f70b4
 * You should have received a copy of the GNU General Public License
Packit 8f70b4
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
Packit 8f70b4
 */
Packit 8f70b4
Packit 8f70b4
#include <config.h>
Packit 8f70b4
#include "FileSet.h"
Packit 8f70b4
Packit 8f70b4
#include <sys/types.h>
Packit 8f70b4
#include <sys/stat.h>
Packit 8f70b4
#include <utime.h>
Packit 8f70b4
#include <stdlib.h>
Packit 8f70b4
#include <unistd.h>
Packit 8f70b4
#include <sys/stat.h>
Packit 8f70b4
#include <fnmatch.h>
Packit 8f70b4
#include <locale.h>
Packit 8f70b4
#include <mbswidth.h>
Packit 8f70b4
Packit 8f70b4
CDECL_BEGIN
Packit 8f70b4
#include <filemode.h>
Packit 8f70b4
#include "human.h"
Packit 8f70b4
CDECL_END
Packit 8f70b4
Packit 8f70b4
#include "misc.h"
Packit 8f70b4
#include "ResMgr.h"
Packit 8f70b4
Packit 8f70b4
#include "FileSetOutput.h"
Packit 8f70b4
#include "ArgV.h"
Packit 8f70b4
#include "ColumnOutput.h"
Packit 8f70b4
#include "DirColors.h"
Packit 8f70b4
#include "FileGlob.h"
Packit 8f70b4
#include "CopyJob.h"
Packit 8f70b4
Packit 8f70b4
Packit 8f70b4
ResDecl	res_default_cls         ("cmd:cls-default",  "-F", FileSetOutput::ValidateArgv,ResMgr::NoClosure),
Packit 8f70b4
	res_default_comp_cls    ("cmd:cls-completion-default", "-FBa",FileSetOutput::ValidateArgv,ResMgr::NoClosure);
Packit 8f70b4
Packit 8f70b4
ResDecl res_time_style	("cmd:time-style", "%b %e  %Y|%b %e %H:%M", 0, ResMgr::NoClosure);
Packit 8f70b4
Packit 8f70b4
/* note: this sorts (add a nosort if necessary) */
Packit 8f70b4
void FileSetOutput::print(FileSet &fs, const JobRef<OutputJob>& o) const
Packit 8f70b4
{
Packit 8f70b4
   fs.Sort(sort, sort_casefold, sort_reverse);
Packit 8f70b4
   if(sort_dirs_first) fs.Sort(FileSet::DIRSFIRST, false, sort_reverse);
Packit 8f70b4
Packit 8f70b4
   ColumnOutput c;
Packit 8f70b4
Packit 8f70b4
   DirColors &col=*DirColors::GetInstance();
Packit 8f70b4
Packit 8f70b4
   const char *suffix_color = "";
Packit 8f70b4
Packit 8f70b4
   /* Most fields are only printed if at least one file has that
Packit 8f70b4
    * information; if no files have perm information, for example,
Packit 8f70b4
    * discard the entire field. */
Packit 8f70b4
   int have = fs.Have();
Packit 8f70b4
Packit 8f70b4
   for(int i = 0; fs[i]; i++) {
Packit 8f70b4
      const FileInfo *f = fs[i];
Packit 8f70b4
      if(!showdots && !list_directories &&
Packit 8f70b4
	    (!strcmp(basename_ptr(f->name),".") || !strcmp(basename_ptr(f->name),"..")))
Packit 8f70b4
	 continue;
Packit 8f70b4
Packit 8f70b4
      if(pat && *pat &&
Packit 8f70b4
	    fnmatch(pat, f->name, patterns_casefold? FNM_CASEFOLD:0))
Packit 8f70b4
	 continue;
Packit 8f70b4
Packit 8f70b4
      c.append();
Packit 8f70b4
Packit 8f70b4
      if((mode & PERMS) && (f->defined&FileInfo::MODE)) {
Packit 8f70b4
	 char mode[16];
Packit 8f70b4
	 memset(mode, 0, sizeof(mode));
Packit 8f70b4
	 strmode(f->mode, mode);
Packit 8f70b4
	 /* FIXME: f->mode doesn't have type info; it wouldn't
Packit 8f70b4
	  * be hard to fix that */
Packit 8f70b4
	 if(f->filetype == FileInfo::DIRECTORY) mode[0] = 'd';
Packit 8f70b4
	 else if(f->filetype == FileInfo::SYMLINK) mode[0] = 'l';
Packit 8f70b4
	 else mode[0] = '-';
Packit 8f70b4
Packit 8f70b4
	 c.add(mode, "");
Packit 8f70b4
      } else if(have & FileInfo::MODE) {
Packit 8f70b4
	 c.add("           ", "");
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      if((have & FileInfo::NLINKS) && (mode & NLINKS)) {
Packit 8f70b4
	 if(f->defined&f->NLINKS)
Packit 8f70b4
	    c.addf("%4i ", "", f->nlinks);
Packit 8f70b4
	 else
Packit 8f70b4
	    c.addf("%4i ", "", "");
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      if((have & FileInfo::USER) && (mode & USER)) {
Packit 8f70b4
	 c.addf("%-8.8s ", "", (f->defined&f->USER)? f->user: "");
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      if((have & FileInfo::GROUP) && (mode & GROUP)) {
Packit 8f70b4
	 c.addf("%-8.8s ", "", (f->defined&f->GROUP)? f->group: "");
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      if((mode & SIZE) && (have&FileInfo::SIZE)) {
Packit 8f70b4
	 char sz[LONGEST_HUMAN_READABLE + 2];
Packit 8f70b4
	 if((f->filetype == FileInfo::NORMAL || !size_filesonly)
Packit 8f70b4
	 && (f->defined&f->SIZE)) {
Packit 8f70b4
	    char buffer[LONGEST_HUMAN_READABLE + 1];
Packit 8f70b4
	    snprintf(sz, sizeof(sz), "%8s ",
Packit 8f70b4
	       human_readable (f->size, buffer, human_opts, 1,
Packit 8f70b4
		  output_block_size? output_block_size:1024));
Packit 8f70b4
	 } else {
Packit 8f70b4
	    snprintf(sz, sizeof(sz), "%8s ", ""); /* pad */
Packit 8f70b4
	 }
Packit 8f70b4
	 c.add(sz, "");
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      /* We use unprec dates; doing MDTMs for each file in ls is far too
Packit 8f70b4
       * slow.  If someone actually wants that (to get dates on servers with
Packit 8f70b4
       * unparsable dates, or more accurate dates), it wouldn't be
Packit 8f70b4
       * difficult.  If we did this, we could also support --full-time. */
Packit 8f70b4
      if((mode & DATE) && (have & f->DATE)) {
Packit 8f70b4
	 /* Consider a time to be recent if it is within the past six
Packit 8f70b4
	  * months.  A Gregorian year has 365.2425 * 24 * 60 * 60 ==
Packit 8f70b4
	  * 31556952 seconds on the average.  Write this value as an
Packit 8f70b4
	  * integer constant to avoid floating point hassles.  */
Packit 8f70b4
	 const int six_months_ago = SMTask::now.UnixTime() - 31556952 / 2;
Packit 8f70b4
	 bool recent = six_months_ago <= f->date;
Packit 8f70b4
Packit 8f70b4
	 const char *use_fmt=time_fmt;
Packit 8f70b4
	 if(!use_fmt)
Packit 8f70b4
	    use_fmt=ResMgr::Query("cmd:time-style",0);
Packit 8f70b4
	 if(!use_fmt || !*use_fmt)
Packit 8f70b4
	    use_fmt="%b %e  %Y\n%b %e %H:%M";
Packit 8f70b4
Packit 8f70b4
	 xstring_ca dt_mem(xstrftime(use_fmt, localtime (&f->date.ts)));
Packit 8f70b4
	 char *dt=strtok(dt_mem.get_non_const(),"\n|");
Packit 8f70b4
	 if(recent) {
Packit 8f70b4
	    char *dt1=strtok(NULL,"\n|");
Packit 8f70b4
	    if(dt1)
Packit 8f70b4
	       dt=dt1;
Packit 8f70b4
	 }
Packit 8f70b4
	 if (!(f->defined&f->DATE)) {
Packit 8f70b4
	    /* put an empty field; make sure it's the same width */
Packit 8f70b4
	    int wid = mbswidth(dt, 0);
Packit 8f70b4
	    dt = string_alloca(wid+1);
Packit 8f70b4
	    memset(dt, ' ', wid);
Packit 8f70b4
	    dt[wid] = 0;
Packit 8f70b4
	 }
Packit 8f70b4
	 c.addf("%s ", "", dt);
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      const char *nm = f->name;
Packit 8f70b4
      if(basenames) nm = basename_ptr(nm);
Packit 8f70b4
      c.add(nm, col.GetColor(f));
Packit 8f70b4
Packit 8f70b4
      if(classify)
Packit 8f70b4
	 c.add(FileInfoSuffix(*f), suffix_color);
Packit 8f70b4
Packit 8f70b4
      if((mode & LINKS) &&
Packit 8f70b4
	 f->filetype == FileInfo::SYMLINK &&
Packit 8f70b4
	 f->symlink) {
Packit 8f70b4
	 c.add(" -> ", "");
Packit 8f70b4
Packit 8f70b4
	 /* see if we have a file entry for the symlink */
Packit 8f70b4
	 FileInfo tmpfi;
Packit 8f70b4
	 FileInfo *lfi = fs.FindByName(f->symlink);
Packit 8f70b4
Packit 8f70b4
	 if(!lfi) {
Packit 8f70b4
	    /* create a temporary one */
Packit 8f70b4
	    tmpfi.SetName(f->symlink);
Packit 8f70b4
	    lfi = &tmpfi;
Packit 8f70b4
	 }
Packit 8f70b4
Packit 8f70b4
	 c.add(lfi->name, col.GetColor(lfi));
Packit 8f70b4
	 if(classify)
Packit 8f70b4
	    c.add(FileInfoSuffix(*lfi), suffix_color);
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   c.print(o, single_column? 0:width, color);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
const char *FileSetOutput::FileInfoSuffix(const FileInfo &fi) const
Packit 8f70b4
{
Packit 8f70b4
   if(!(fi.defined&fi.TYPE))
Packit 8f70b4
      return "";
Packit 8f70b4
   if(fi.filetype == FileInfo::DIRECTORY)
Packit 8f70b4
      return "/";
Packit 8f70b4
   else if(fi.filetype == FileInfo::SYMLINK)
Packit 8f70b4
      return "@";
Packit 8f70b4
   return "";
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void FileSetOutput::config(const OutputJob *o)
Packit 8f70b4
{
Packit 8f70b4
   width = o->GetWidth();
Packit 8f70b4
   if(width == -1)
Packit 8f70b4
      width = 80;
Packit 8f70b4
   color = ResMgr::QueryTriBool("color:use-color", 0, o->IsTTY());
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void FileSetOutput::long_list()
Packit 8f70b4
{
Packit 8f70b4
   single_column = true;
Packit 8f70b4
   mode = ALL;
Packit 8f70b4
   /* -l's default size is 1; otherwise 1024 */
Packit 8f70b4
   if(!output_block_size)
Packit 8f70b4
      output_block_size = 1;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
const char *FileSetOutput::parse_res(const char *res)
Packit 8f70b4
{
Packit 8f70b4
   Ref<ArgV> arg(new ArgV("",res));
Packit 8f70b4
   const char *error=parse_argv(arg);
Packit 8f70b4
   if(error)
Packit 8f70b4
      return error;
Packit 8f70b4
Packit 8f70b4
   /* shouldn't be any non-option arguments */
Packit 8f70b4
   if(arg->count() > 1)
Packit 8f70b4
      return _("non-option arguments found");
Packit 8f70b4
Packit 8f70b4
   return 0;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
const char *FileSetOutput::ValidateArgv(xstring_c *s)
Packit 8f70b4
{
Packit 8f70b4
   if(!*s) return NULL;
Packit 8f70b4
Packit 8f70b4
   FileSetOutput tmp;
Packit 8f70b4
Packit 8f70b4
   const char *ret = tmp.parse_res(*s);
Packit 8f70b4
   if(ret) return ret;
Packit 8f70b4
Packit 8f70b4
   return NULL;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
int FileSetOutput::Need() const
Packit 8f70b4
{
Packit 8f70b4
   int need=FileInfo::NAME;
Packit 8f70b4
   if(mode & PERMS)
Packit 8f70b4
      need|=FileInfo::MODE;
Packit 8f70b4
//   if(mode & SIZE) /* this must be optional */
Packit 8f70b4
//      need|=FileInfo::SIZE;
Packit 8f70b4
//   if(mode & DATE) /* this too */
Packit 8f70b4
//      need|=FileInfo::DATE;
Packit 8f70b4
   if(mode & LINKS)
Packit 8f70b4
      need|=FileInfo::SYMLINK_DEF;
Packit 8f70b4
   if(mode & USER)
Packit 8f70b4
      need|=FileInfo::USER;
Packit 8f70b4
   if(mode & GROUP)
Packit 8f70b4
      need|=FileInfo::GROUP;
Packit 8f70b4
   if(need_exact_time)
Packit 8f70b4
      need|=FileInfo::DATE;
Packit 8f70b4
Packit 8f70b4
   return need;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
#undef super
Packit 8f70b4
#define super SessionJob
Packit 8f70b4
Packit 8f70b4
clsJob::clsJob(FA *s, ArgV *a, FileSetOutput *_opts, OutputJob *_output):
Packit 8f70b4
   SessionJob(s),
Packit 8f70b4
   fso(_opts),
Packit 8f70b4
   args(a),
Packit 8f70b4
   done(0),
Packit 8f70b4
   use_cache(true),
Packit 8f70b4
   error(false),
Packit 8f70b4
   state(INIT)
Packit 8f70b4
{
Packit 8f70b4
   list_info=0;
Packit 8f70b4
Packit 8f70b4
   if(args->count() == 1)
Packit 8f70b4
      args->Add("");
Packit 8f70b4
Packit 8f70b4
   output=_output;
Packit 8f70b4
   AddWaiting(output);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
int clsJob::Done()
Packit 8f70b4
{
Packit 8f70b4
   return done && output->Done();
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
int clsJob::Do()
Packit 8f70b4
{
Packit 8f70b4
   int m=STALL;
Packit 8f70b4
Packit 8f70b4
   if(output->Done())
Packit 8f70b4
      state=DONE;
Packit 8f70b4
Packit 8f70b4
   switch(state)
Packit 8f70b4
   {
Packit 8f70b4
   case INIT:
Packit 8f70b4
      state=START_LISTING;
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
Packit 8f70b4
   case START_LISTING:
Packit 8f70b4
   {
Packit 8f70b4
      list_info=0;
Packit 8f70b4
Packit 8f70b4
      /* next: */
Packit 8f70b4
      mask.set(0);
Packit 8f70b4
      dir.set(args->getnext());
Packit 8f70b4
      if(!dir) {
Packit 8f70b4
	 /* done */
Packit 8f70b4
	 state=DONE;
Packit 8f70b4
	 return MOVED;
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      /* If the basename contains wildcards, set up the mask. */
Packit 8f70b4
      const char *bn = basename_ptr(dir);
Packit 8f70b4
      if(Glob::HasWildcards(bn)) {
Packit 8f70b4
	 /* The mask is the whole argument, not just the basename; this is
Packit 8f70b4
	  * because the whole relative paths will end up in the FileSet, and
Packit 8f70b4
	  * that's what this pattern will be matched against. */
Packit 8f70b4
	 mask.set(dir);
Packit 8f70b4
	 // leave the final / on the path, to prevent the dirname of
Packit 8f70b4
	 // "file/*" from being treated as a file
Packit 8f70b4
	 dir.truncate(bn-dir); // this can result in dir eq ""
Packit 8f70b4
      } else {
Packit 8f70b4
	 // no need to glob, just unquote metacharacters.
Packit 8f70b4
	 Glob::UnquoteWildcards(const_cast<char*>(bn));
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      list_info=new GetFileInfo(session, dir, fso->list_directories);
Packit 8f70b4
      list_info->UseCache(use_cache);
Packit 8f70b4
      list_info->Need(fso->Need());
Packit 8f70b4
Packit 8f70b4
      state=GETTING_LIST_INFO;
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
   }
Packit 8f70b4
   case GETTING_LIST_INFO:
Packit 8f70b4
   {
Packit 8f70b4
      if(!list_info->Done())
Packit 8f70b4
	 return m;
Packit 8f70b4
Packit 8f70b4
      if(list_info->Error()) {
Packit 8f70b4
	 eprintf("%s\n", list_info->ErrorText());
Packit 8f70b4
	 error=true;
Packit 8f70b4
	 state=START_LISTING;
Packit 8f70b4
	 return MOVED;
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      /* one just finished */
Packit 8f70b4
      fso->pat.move_here(mask);
Packit 8f70b4
      FileSet *res = list_info->GetResult();
Packit 8f70b4
Packit 8f70b4
      if(res)
Packit 8f70b4
	 fso->print(*res, output);
Packit 8f70b4
Packit 8f70b4
      fso->pat.set(0);
Packit 8f70b4
      delete res;
Packit 8f70b4
Packit 8f70b4
      state=START_LISTING;
Packit 8f70b4
      return MOVED;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   case DONE:
Packit 8f70b4
      if(!done)
Packit 8f70b4
      {
Packit 8f70b4
	 output->PutEOF();
Packit 8f70b4
	 done=true;
Packit 8f70b4
	 m=MOVED;
Packit 8f70b4
      }
Packit 8f70b4
      break;
Packit 8f70b4
   }
Packit 8f70b4
   return m;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void clsJob::SuspendInternal()
Packit 8f70b4
{
Packit 8f70b4
   super::SuspendInternal();
Packit 8f70b4
   if(list_info)
Packit 8f70b4
      list_info->SuspendSlave();
Packit 8f70b4
   session->SuspendSlave();
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void clsJob::ResumeInternal()
Packit 8f70b4
{
Packit 8f70b4
   if(list_info)
Packit 8f70b4
      list_info->ResumeSlave();
Packit 8f70b4
   session->ResumeSlave();
Packit 8f70b4
   super::ResumeInternal();
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void clsJob::ShowRunStatus(const SMTaskRef<StatusLine>& s)
Packit 8f70b4
{
Packit 8f70b4
   if(fso->quiet)
Packit 8f70b4
      return;
Packit 8f70b4
Packit 8f70b4
   if(!output->ShowStatusLine(s))
Packit 8f70b4
      return;
Packit 8f70b4
Packit 8f70b4
   if(list_info && !list_info->Done())
Packit 8f70b4
   {
Packit 8f70b4
      const char *curr = args->getcurr();
Packit 8f70b4
      if(!*curr)
Packit 8f70b4
	 curr = ".";
Packit 8f70b4
      const char *stat = list_info->Status();
Packit 8f70b4
      if(*stat)
Packit 8f70b4
	 s->Show("`%s' %s %s", curr, stat, output->Status(s));
Packit 8f70b4
   }
Packit 8f70b4
   else
Packit 8f70b4
	 s->Show("%s", output->Status(s));
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
xstring& clsJob::FormatStatus(xstring& s,int v,const char *prefix)
Packit 8f70b4
{
Packit 8f70b4
   Job::FormatStatus(s,v,prefix);
Packit 8f70b4
Packit 8f70b4
   if(list_info)
Packit 8f70b4
   {
Packit 8f70b4
      const char *curr = args->getcurr();
Packit 8f70b4
      if(!*curr)
Packit 8f70b4
	 curr = ".";
Packit 8f70b4
      const char *stat = list_info->Status();
Packit 8f70b4
      if(*stat)
Packit 8f70b4
	 s.appendf("%s`%s' %s\n", prefix, curr, stat);
Packit 8f70b4
   }
Packit 8f70b4
   return s;
Packit 8f70b4
}