Blame src/MirrorJob.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
Packit 8f70b4
#include <sys/types.h>
Packit 8f70b4
#include <sys/stat.h>
Packit 8f70b4
#include <errno.h>
Packit 8f70b4
#include <fcntl.h>
Packit 8f70b4
#include <unistd.h>
Packit 8f70b4
#include <assert.h>
Packit 8f70b4
#include <mbswidth.h>
Packit 8f70b4
#include "MirrorJob.h"
Packit 8f70b4
#include "CmdExec.h"
Packit 8f70b4
#include "rmJob.h"
Packit 8f70b4
#include "mvJob.h"
Packit 8f70b4
#include "ChmodJob.h"
Packit 8f70b4
#include "mkdirJob.h"
Packit 8f70b4
#include "misc.h"
Packit 8f70b4
#include "plural.h"
Packit 8f70b4
#include "FindJob.h"
Packit 8f70b4
#include "url.h"
Packit 8f70b4
#include "CopyJob.h"
Packit 8f70b4
#include "pgetJob.h"
Packit 8f70b4
#include "log.h"
Packit 8f70b4
Packit 8f70b4
#define set_state(s) do { state=(s); \
Packit 8f70b4
   Log::global->Format(11,"mirror(%p) enters state %s\n", this, #s); } while(0)
Packit 8f70b4
#define waiting_num waiting.count()
Packit 8f70b4
#define transfer_count root_mirror->root_transfer_count
Packit 8f70b4
Packit 8f70b4
xstring& MirrorJob::FormatStatus(xstring& s,int v,const char *tab)
Packit 8f70b4
{
Packit 8f70b4
   if(Done())
Packit 8f70b4
      goto final;
Packit 8f70b4
Packit 8f70b4
   switch(state)
Packit 8f70b4
   {
Packit 8f70b4
   case(INITIAL_STATE):
Packit 8f70b4
   case(FINISHING):
Packit 8f70b4
   case(DONE):
Packit 8f70b4
   case(WAITING_FOR_TRANSFER):
Packit 8f70b4
   case(TARGET_REMOVE_OLD):
Packit 8f70b4
   case(TARGET_REMOVE_OLD_FIRST):
Packit 8f70b4
   case(TARGET_CHMOD):
Packit 8f70b4
   case(TARGET_MKDIR):
Packit 8f70b4
   case(SOURCE_REMOVING_SAME):
Packit 8f70b4
   case(LAST_EXEC):
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   case(MAKE_TARGET_DIR):
Packit 8f70b4
      s.appendf("\tmkdir `%s' [%s]\n",target_dir.get(),target_session->CurrentStatus());
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   case(CHANGING_DIR_SOURCE):
Packit 8f70b4
   case(CHANGING_DIR_TARGET):
Packit 8f70b4
      if(target_session->IsOpen())
Packit 8f70b4
	 s.appendf("\tcd `%s' [%s]\n",target_dir.get(),target_session->CurrentStatus());
Packit 8f70b4
      if(source_session->IsOpen())
Packit 8f70b4
	 s.appendf("\tcd `%s' [%s]\n",source_dir.get(),source_session->CurrentStatus());
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   case(GETTING_LIST_INFO):
Packit 8f70b4
      if(target_list_info)
Packit 8f70b4
      {
Packit 8f70b4
	 if(target_relative_dir)
Packit 8f70b4
	    s.appendf("\t%s: %s\n",target_relative_dir.get(),target_list_info->Status());
Packit 8f70b4
	 else
Packit 8f70b4
	    s.appendf("\t%s\n",target_list_info->Status());
Packit 8f70b4
      }
Packit 8f70b4
      if(source_list_info)
Packit 8f70b4
      {
Packit 8f70b4
	 if(source_relative_dir)
Packit 8f70b4
	    s.appendf("\t%s: %s\n",source_relative_dir.get(),source_list_info->Status());
Packit 8f70b4
	 else
Packit 8f70b4
	    s.appendf("\t%s\n",source_list_info->Status());
Packit 8f70b4
      }
Packit 8f70b4
      break;
Packit 8f70b4
   }
Packit 8f70b4
   return s;
Packit 8f70b4
Packit 8f70b4
final:
Packit 8f70b4
   if(stats.dirs>0)
Packit 8f70b4
      s.appendf(plural("%sTotal: %d director$y|ies$, %d file$|s$, %d symlink$|s$\n",
Packit 8f70b4
		     stats.dirs,stats.tot_files,stats.tot_symlinks),
Packit 8f70b4
	 tab,stats.dirs,stats.tot_files,stats.tot_symlinks);
Packit 8f70b4
   if(stats.new_files || stats.new_symlinks)
Packit 8f70b4
      s.appendf(plural("%sNew: %d file$|s$, %d symlink$|s$\n",
Packit 8f70b4
		     stats.new_files,stats.new_symlinks),
Packit 8f70b4
	 tab,stats.new_files,stats.new_symlinks);
Packit 8f70b4
   if(stats.mod_files || stats.mod_symlinks)
Packit 8f70b4
      s.appendf(plural("%sModified: %d file$|s$, %d symlink$|s$\n",
Packit 8f70b4
		     stats.mod_files,stats.mod_symlinks),
Packit 8f70b4
	 tab,stats.mod_files,stats.mod_symlinks);
Packit 8f70b4
   if(stats.bytes)
Packit 8f70b4
      s.appendf("%s%s\n",tab,CopyJob::FormatBytesTimeRate(stats.bytes,transfer_time_elapsed));
Packit 8f70b4
   if(stats.del_dirs || stats.del_files || stats.del_symlinks)
Packit 8f70b4
      s.appendf(plural(FlagSet(DELETE) ?
Packit 8f70b4
	       "%sRemoved: %d director$y|ies$, %d file$|s$, %d symlink$|s$\n"
Packit 8f70b4
	      :"%sTo be removed: %d director$y|ies$, %d file$|s$, %d symlink$|s$\n",
Packit 8f70b4
	      stats.del_dirs,stats.del_files,stats.del_symlinks),
Packit 8f70b4
	 tab,stats.del_dirs,stats.del_files,stats.del_symlinks);
Packit 8f70b4
   if(stats.error_count)
Packit 8f70b4
      s.appendf(plural("%s%d error$|s$ detected\n",stats.error_count),
Packit 8f70b4
	       tab,stats.error_count);
Packit 8f70b4
   return s;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void  MirrorJob::ShowRunStatus(const SMTaskRef<StatusLine>& s)
Packit 8f70b4
{
Packit 8f70b4
   int w=s->GetWidthDelayed();
Packit 8f70b4
   switch(state)
Packit 8f70b4
   {
Packit 8f70b4
   case(INITIAL_STATE):
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   // these have a sub-job
Packit 8f70b4
   case(WAITING_FOR_TRANSFER):
Packit 8f70b4
   case(TARGET_REMOVE_OLD):
Packit 8f70b4
   case(TARGET_REMOVE_OLD_FIRST):
Packit 8f70b4
   case(TARGET_CHMOD):
Packit 8f70b4
   case(TARGET_MKDIR):
Packit 8f70b4
   case(SOURCE_REMOVING_SAME):
Packit 8f70b4
   case(FINISHING):
Packit 8f70b4
   case(DONE):
Packit 8f70b4
   case(LAST_EXEC):
Packit 8f70b4
      Job::ShowRunStatus(s);
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   case(MAKE_TARGET_DIR):
Packit 8f70b4
      s->Show("mkdir `%s' [%s]",target_dir.get(),target_session->CurrentStatus());
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   case(CHANGING_DIR_SOURCE):
Packit 8f70b4
   case(CHANGING_DIR_TARGET):
Packit 8f70b4
      if(target_session->IsOpen() && (!source_session->IsOpen() || now%4>=2))
Packit 8f70b4
	 s->Show("cd `%s' [%s]",target_dir.get(),target_session->CurrentStatus());
Packit 8f70b4
      else if(source_session->IsOpen())
Packit 8f70b4
	 s->Show("cd `%s' [%s]",source_dir.get(),source_session->CurrentStatus());
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   case(GETTING_LIST_INFO):
Packit 8f70b4
      if(target_list_info && (!source_list_info || now%4>=2))
Packit 8f70b4
      {
Packit 8f70b4
	 const char *status=target_list_info->Status();
Packit 8f70b4
	 int status_w=mbswidth(status, 0);
Packit 8f70b4
	 int dw=w-status_w;
Packit 8f70b4
	 if(dw<20)
Packit 8f70b4
	    dw=20;
Packit 8f70b4
	 if(target_relative_dir)
Packit 8f70b4
	    s->Show("%s: %s",squeeze_file_name(target_relative_dir,dw),status);
Packit 8f70b4
	 else
Packit 8f70b4
	    s->Show("%s",status);
Packit 8f70b4
      }
Packit 8f70b4
      else if(source_list_info)
Packit 8f70b4
      {
Packit 8f70b4
	 const char *status=source_list_info->Status();
Packit 8f70b4
	 int status_w=mbswidth(status, 0);
Packit 8f70b4
	 int dw=w-status_w;
Packit 8f70b4
	 if(dw<20)
Packit 8f70b4
	    dw=20;
Packit 8f70b4
	 if(source_relative_dir)
Packit 8f70b4
	    s->Show("%s: %s",squeeze_file_name(source_relative_dir,dw),status);
Packit 8f70b4
	 else
Packit 8f70b4
	    s->Show("%s",status);
Packit 8f70b4
      }
Packit 8f70b4
      break;
Packit 8f70b4
   }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
xstring& MirrorJob::FormatShortStatus(xstring& s)
Packit 8f70b4
{
Packit 8f70b4
   if(bytes_to_transfer>0 && (!parent_mirror || parent_mirror->bytes_to_transfer!=bytes_to_transfer)) {
Packit 8f70b4
      long long curr_bytes_transferred=GetBytesCount();
Packit 8f70b4
      if(parent_mirror)
Packit 8f70b4
         curr_bytes_transferred+=bytes_transferred;
Packit 8f70b4
      s.appendf("%s/%s (%d%%)",
Packit 8f70b4
	 xhuman(curr_bytes_transferred),xhuman(bytes_to_transfer),
Packit 8f70b4
	 percent(curr_bytes_transferred,bytes_to_transfer));
Packit 8f70b4
      double rate=GetTransferRate();
Packit 8f70b4
      if(rate>=1)
Packit 8f70b4
	 s.append(' ').append(Speedometer::GetStrProper(rate));
Packit 8f70b4
   }
Packit 8f70b4
   return s;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void MirrorJob::TransferStarted(CopyJob *cp)
Packit 8f70b4
{
Packit 8f70b4
   if(transfer_count==0)
Packit 8f70b4
      root_mirror->transfer_start_ts=now;
Packit 8f70b4
   JobStarted(cp);
Packit 8f70b4
}
Packit 8f70b4
void MirrorJob::JobStarted(Job *j)
Packit 8f70b4
{
Packit 8f70b4
   AddWaiting(j);
Packit 8f70b4
   transfer_count++;
Packit 8f70b4
}
Packit 8f70b4
void MirrorJob::TransferFinished(Job *j)
Packit 8f70b4
{
Packit 8f70b4
   long long bytes_count=j->GetBytesCount();
Packit 8f70b4
   AddBytesTransferred(bytes_count);
Packit 8f70b4
   stats.bytes+=bytes_count;
Packit 8f70b4
   stats.time +=j->GetTimeSpent();
Packit 8f70b4
   if(j->ExitCode()==0 && verbose_report>=2) {
Packit 8f70b4
      xstring finished;
Packit 8f70b4
      const xstring& cmd=j->GetCmdLine();
Packit 8f70b4
      if(cmd[0]=='\\')
Packit 8f70b4
	 finished.append(cmd+1,cmd.length()-1);
Packit 8f70b4
      else
Packit 8f70b4
	 finished.append(cmd);
Packit 8f70b4
      const xstring& rate=Speedometer::GetStrProper(j->GetTransferRate());
Packit 8f70b4
      if(rate.length()>0)
Packit 8f70b4
	 finished.append(" (").append(rate).append(')');
Packit 8f70b4
      if(!(FlagSet(SCAN_ALL_FIRST) && finished.begins_with("mirror")))
Packit 8f70b4
	 Report(_("Finished %s"),finished.get());
Packit 8f70b4
   }
Packit 8f70b4
   JobFinished(j);
Packit 8f70b4
   if(transfer_count==0)
Packit 8f70b4
      root_mirror->transfer_time_elapsed += now-root_mirror->transfer_start_ts;
Packit 8f70b4
}
Packit 8f70b4
void MirrorJob::JobFinished(Job *j)
Packit 8f70b4
{
Packit 8f70b4
   if(j->ExitCode()!=0)
Packit 8f70b4
      stats.error_count++;
Packit 8f70b4
   RemoveWaiting(j);
Packit 8f70b4
   Delete(j);
Packit 8f70b4
   assert(transfer_count>0);
Packit 8f70b4
   transfer_count--;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
off_t MirrorJob::GetBytesCount()
Packit 8f70b4
{
Packit 8f70b4
   long long bytes_count=Job::GetBytesCount();
Packit 8f70b4
   if(!parent_mirror) {
Packit 8f70b4
      // bytes_transferred is cumulative over the mirror tree,
Packit 8f70b4
      // add it on the top only
Packit 8f70b4
      bytes_count+=bytes_transferred;
Packit 8f70b4
   }
Packit 8f70b4
   return bytes_count;
Packit 8f70b4
}
Packit 8f70b4
double MirrorJob::GetTimeSpent()
Packit 8f70b4
{
Packit 8f70b4
   double t=transfer_time_elapsed;
Packit 8f70b4
   if(transfer_count>0)
Packit 8f70b4
      t+=now-root_mirror->transfer_start_ts;
Packit 8f70b4
   return t;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void  MirrorJob::HandleFile(FileInfo *file)
Packit 8f70b4
{
Packit 8f70b4
   int	 res;
Packit 8f70b4
   struct stat st;
Packit 8f70b4
Packit 8f70b4
   // TODO: get rid of local hacks.
Packit 8f70b4
Packit 8f70b4
   const char *dst_name=file->name;
Packit 8f70b4
   if(FlagSet(TARGET_FLAT))
Packit 8f70b4
      dst_name=basename_ptr(dst_name);
Packit 8f70b4
Packit 8f70b4
   // dir_name returns pointer to static data - need to dup it.
Packit 8f70b4
   const char *source_name=dir_file(source_dir,file->name);
Packit 8f70b4
   source_name=alloca_strdup(source_name);
Packit 8f70b4
   const char *target_name=dir_file(target_dir,dst_name);
Packit 8f70b4
   target_name=alloca_strdup(target_name);
Packit 8f70b4
Packit 8f70b4
   const char *source_name_rel=dir_file(source_relative_dir,file->name);
Packit 8f70b4
   source_name_rel=alloca_strdup(source_name_rel);
Packit 8f70b4
   const char *target_name_rel=dir_file(target_relative_dir,dst_name);
Packit 8f70b4
   target_name_rel=alloca_strdup(target_name_rel);
Packit 8f70b4
Packit 8f70b4
   FileInfo::type filetype=FileInfo::NORMAL;
Packit 8f70b4
   if(file->Has(file->TYPE))
Packit 8f70b4
      filetype=file->filetype;
Packit 8f70b4
   else
Packit 8f70b4
   {
Packit 8f70b4
      FileInfo *target=target_set->FindByName(file->name);
Packit 8f70b4
      if(target && target->Has(target->TYPE))
Packit 8f70b4
	 filetype=target->filetype;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   switch(filetype)
Packit 8f70b4
   {
Packit 8f70b4
      case(FileInfo::NORMAL):
Packit 8f70b4
      case(FileInfo::REDIRECT):
Packit 8f70b4
      {
Packit 8f70b4
	 bool remove_target=false;
Packit 8f70b4
	 bool cont_this=false;
Packit 8f70b4
	 bool use_pget=(pget_n>1) && target_is_local;
Packit 8f70b4
	 if(file->Has(file->SIZE) && file->size
Packit 8f70b4
	    use_pget=false;
Packit 8f70b4
	 if(target_is_local)
Packit 8f70b4
	 {
Packit 8f70b4
	    if(lstat(target_name,&st)!=-1)
Packit 8f70b4
	    {
Packit 8f70b4
	       // few safety checks.
Packit 8f70b4
	       FileInfo *old=new_files_set->FindByName(file->name);
Packit 8f70b4
	       if(old)
Packit 8f70b4
		  goto skip;  // file has appeared after mirror start
Packit 8f70b4
	       old=old_files_set->FindByName(file->name);
Packit 8f70b4
	       if(old && ((old->Has(old->SIZE) && old->size!=st.st_size)
Packit 8f70b4
			||(old->Has(old->DATE) && old->date!=st.st_mtime)))
Packit 8f70b4
		  goto skip;  // the file has changed after mirror start
Packit 8f70b4
	       if(!script_only && access(target_name,W_OK)==-1)
Packit 8f70b4
	       {
Packit 8f70b4
		  // try to enable write access.
Packit 8f70b4
		  chmod(target_name,st.st_mode|0200);
Packit 8f70b4
	       }
Packit 8f70b4
	    }
Packit 8f70b4
	 }
Packit 8f70b4
	 FileInfo *old=target_set->FindByName(FileCopy::TempFileName(file->name));
Packit 8f70b4
	 if(old)
Packit 8f70b4
	 {
Packit 8f70b4
	    if(FlagSet(CONTINUE)
Packit 8f70b4
	    && old->Has(file->TYPE) && old->filetype==old->NORMAL
Packit 8f70b4
	    && (FlagSet(IGNORE_TIME) ||
Packit 8f70b4
		(file->Has(file->DATE) && old->Has(old->DATE)
Packit 8f70b4
		&& file->date + file->date.ts_prec < old->date - old->date.ts_prec))
Packit 8f70b4
	    && file->Has(file->SIZE) && old->Has(old->SIZE)
Packit 8f70b4
	    && file->size >= old->size)
Packit 8f70b4
	    {
Packit 8f70b4
	       cont_this=true;
Packit 8f70b4
	       stats.mod_files++;
Packit 8f70b4
	    }
Packit 8f70b4
	    else if(!to_rm_mismatched->FindByName(file->name))
Packit 8f70b4
	    {
Packit 8f70b4
	       if(!FlagSet(OVERWRITE)) {
Packit 8f70b4
		  remove_target=true;
Packit 8f70b4
		  Report(_("Removing old file `%s'"),target_name_rel);
Packit 8f70b4
	       } else {
Packit 8f70b4
		  Report(_("Overwriting old file `%s'"),target_name_rel);
Packit 8f70b4
	       }
Packit 8f70b4
	       stats.mod_files++;
Packit 8f70b4
	    }
Packit 8f70b4
	    else
Packit 8f70b4
	       stats.new_files++;
Packit 8f70b4
	 }
Packit 8f70b4
	 else if(FlagSet(ONLY_EXISTING))
Packit 8f70b4
	 {
Packit 8f70b4
	    Report(_("Skipping file `%s' (only-existing)"),source_name_rel);
Packit 8f70b4
	    goto skip;
Packit 8f70b4
	 }
Packit 8f70b4
	 else
Packit 8f70b4
	    stats.new_files++;
Packit 8f70b4
Packit 8f70b4
	 Report(_("Transferring file `%s'"),source_name_rel);
Packit 8f70b4
Packit 8f70b4
	 if(script)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV args(use_pget?"pget":"get");
Packit 8f70b4
	    if(use_pget)
Packit 8f70b4
	    {
Packit 8f70b4
	       args.Append("-n");
Packit 8f70b4
	       args.Append(pget_n);
Packit 8f70b4
	    }
Packit 8f70b4
	    if(cont_this)
Packit 8f70b4
	       args.Append("-c");
Packit 8f70b4
	    if(remove_target)
Packit 8f70b4
	       args.Append("-e");
Packit 8f70b4
	    if(FlagSet(ASCII))
Packit 8f70b4
	       args.Append("-a");
Packit 8f70b4
	    if(remove_source_files)
Packit 8f70b4
	       args.Append("-E");
Packit 8f70b4
	    args.Append("-O");
Packit 8f70b4
	    args.Append(target_is_local?target_dir.get()
Packit 8f70b4
			:target_session->GetConnectURL().get());
Packit 8f70b4
	    args.Append(source_session->GetFileURL(file->name));
Packit 8f70b4
	    xstring_ca cmd(args.CombineQuoted());
Packit 8f70b4
	    fprintf(script,"%s\n",cmd.get());
Packit 8f70b4
	    if(script_only)
Packit 8f70b4
	       goto skip;
Packit 8f70b4
	 }
Packit 8f70b4
Packit 8f70b4
	 FileCopyPeer *src_peer=0;
Packit 8f70b4
	 if(source_is_local)
Packit 8f70b4
	    src_peer=new FileCopyPeerFDStream(new FileStream(source_name,O_RDONLY),FileCopyPeer::GET);
Packit 8f70b4
	 else
Packit 8f70b4
	    src_peer=new FileCopyPeerFA(source_session->Clone(),file->name,FA::RETRIEVE);
Packit 8f70b4
Packit 8f70b4
	 FileCopyPeer *dst_peer=0;
Packit 8f70b4
	 if(target_is_local)
Packit 8f70b4
	    dst_peer=new FileCopyPeerFDStream(new FileStream(target_name,O_WRONLY|O_CREAT|(cont_this?0:O_TRUNC)),FileCopyPeer::PUT);
Packit 8f70b4
	 else
Packit 8f70b4
	    dst_peer=new FileCopyPeerFA(target_session->Clone(),dst_name,FA::STORE);
Packit 8f70b4
Packit 8f70b4
	 FileCopy *c=FileCopy::New(src_peer,dst_peer,cont_this);
Packit 8f70b4
	 if(remove_source_files)
Packit 8f70b4
	    c->RemoveSourceLater();
Packit 8f70b4
	 if(remove_target)
Packit 8f70b4
	    c->RemoveTargetFirst();
Packit 8f70b4
	 if(FlagSet(ASCII))
Packit 8f70b4
	    c->Ascii();
Packit 8f70b4
	 CopyJob *cp=(use_pget ? new pgetJob(c,file->name,pget_n) : new CopyJob(c,file->name,"mirror"));
Packit 8f70b4
	 if(file->Has(file->DATE))
Packit 8f70b4
	    cp->SetDate(file->date);
Packit 8f70b4
	 if(file->Has(file->SIZE) && !FlagSet(IGNORE_SIZE))
Packit 8f70b4
	    cp->SetSize(file->size);
Packit 8f70b4
	 TransferStarted(cp);
Packit 8f70b4
	 cp->cmdline.vset("\\transfer `",source_name_rel,"'",NULL);
Packit 8f70b4
Packit 8f70b4
	 set_state(WAITING_FOR_TRANSFER);
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
      case(FileInfo::DIRECTORY):
Packit 8f70b4
      {
Packit 8f70b4
	 if(recursion_mode==RECURSION_NEVER || FlagSet(NO_RECURSION))
Packit 8f70b4
	    goto skip;
Packit 8f70b4
Packit 8f70b4
	 bool create_target_subdir=true;
Packit 8f70b4
	 const FileInfo *old=0;
Packit 8f70b4
Packit 8f70b4
	 if(FlagSet(TARGET_FLAT)) {
Packit 8f70b4
	    create_target_subdir=false;
Packit 8f70b4
	    target_name=target_dir;
Packit 8f70b4
	    goto do_submirror;
Packit 8f70b4
	 }
Packit 8f70b4
Packit 8f70b4
	 if(target_set)
Packit 8f70b4
	    old=target_set->FindByName(file->name);
Packit 8f70b4
	 if(!old)
Packit 8f70b4
	 {
Packit 8f70b4
	    if(FlagSet(ONLY_EXISTING))
Packit 8f70b4
	    {
Packit 8f70b4
	       Report(_("Skipping directory `%s' (only-existing)"),target_name_rel);
Packit 8f70b4
	       goto skip;
Packit 8f70b4
	    }
Packit 8f70b4
	 }
Packit 8f70b4
	 else if(old->TypeIs(old->DIRECTORY))
Packit 8f70b4
	 {
Packit 8f70b4
	    create_target_subdir=false;
Packit 8f70b4
	 }
Packit 8f70b4
	 if(target_is_local && !script_only)
Packit 8f70b4
	 {
Packit 8f70b4
	    if((FlagSet(RETR_SYMLINKS)?stat:lstat)(target_name,&st)!=-1)
Packit 8f70b4
	    {
Packit 8f70b4
	       if(S_ISDIR(st.st_mode))
Packit 8f70b4
	       {
Packit 8f70b4
		  // try to enable write access
Packit 8f70b4
		  // only if not enabled as chmod can clear sgid flags on directories
Packit 8f70b4
		  if(st.st_mode!=(st.st_mode|0700))
Packit 8f70b4
		     chmod(target_name,st.st_mode|0700);
Packit 8f70b4
		  create_target_subdir=false;
Packit 8f70b4
	       }
Packit 8f70b4
	       else
Packit 8f70b4
	       {
Packit 8f70b4
		  Report(_("Removing old local file `%s'"),target_name_rel);
Packit 8f70b4
		  if(remove(target_name)==-1)
Packit 8f70b4
		  {
Packit 8f70b4
		     eprintf("mirror: remove(%s): %s\n",target_name,strerror(errno));
Packit 8f70b4
		     goto skip;
Packit 8f70b4
		  }
Packit 8f70b4
		  create_target_subdir=true;
Packit 8f70b4
	       }
Packit 8f70b4
	    }
Packit 8f70b4
	 }
Packit 8f70b4
Packit 8f70b4
      do_submirror:
Packit 8f70b4
	 // launch sub-mirror
Packit 8f70b4
	 MirrorJob *mj=new MirrorJob(this,
Packit 8f70b4
	    source_session->Clone(),target_session->Clone(),
Packit 8f70b4
	    source_name,target_name);
Packit 8f70b4
	 AddWaiting(mj);
Packit 8f70b4
	 mj->cmdline.vset("\\mirror `",source_name_rel,"'",NULL);
Packit 8f70b4
Packit 8f70b4
	 mj->source_relative_dir.set(source_name_rel);
Packit 8f70b4
	 mj->target_relative_dir.set(target_name_rel);
Packit 8f70b4
Packit 8f70b4
	 mj->create_target_dir=create_target_subdir;
Packit 8f70b4
Packit 8f70b4
	 if(verbose_report>=3) {
Packit 8f70b4
	    if(FlagSet(SCAN_ALL_FIRST))
Packit 8f70b4
	       Report(_("Scanning directory `%s'"),mj->target_relative_dir.get());
Packit 8f70b4
	    else
Packit 8f70b4
	       Report(_("Mirroring directory `%s'"),mj->target_relative_dir.get());
Packit 8f70b4
	 }
Packit 8f70b4
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
      case(FileInfo::SYMLINK):
Packit 8f70b4
      {
Packit 8f70b4
	 if(FlagSet(NO_SYMLINKS))
Packit 8f70b4
	    goto skip;
Packit 8f70b4
Packit 8f70b4
	 if(!file->symlink)
Packit 8f70b4
	    goto skip;
Packit 8f70b4
Packit 8f70b4
	 if(!target_is_local)
Packit 8f70b4
	 {
Packit 8f70b4
	    if(script)
Packit 8f70b4
	    {
Packit 8f70b4
	       ArgV args("ln");
Packit 8f70b4
	       args.Append("-s");
Packit 8f70b4
	       args.Append(file->symlink);
Packit 8f70b4
	       args.Append(target_name);
Packit 8f70b4
	       xstring_ca cmd(args.CombineQuoted());
Packit 8f70b4
	       fprintf(script,"%s\n",cmd.get());
Packit 8f70b4
	       if(script_only)
Packit 8f70b4
		  goto skip;
Packit 8f70b4
	    }
Packit 8f70b4
	    bool remove_target=false;
Packit 8f70b4
	    FileInfo *old=target_set->FindByName(file->name);
Packit 8f70b4
	    if(old && !to_rm_mismatched->FindByName(file->name))
Packit 8f70b4
	    {
Packit 8f70b4
	       Report(_("Removing old file `%s'"),target_name_rel);
Packit 8f70b4
	       remove_target=true;
Packit 8f70b4
	       stats.mod_symlinks++;
Packit 8f70b4
	    }
Packit 8f70b4
	    else
Packit 8f70b4
	       stats.new_symlinks++;
Packit 8f70b4
	    Report(_("Making symbolic link `%s' to `%s'"),target_name_rel,file->symlink.get());
Packit 8f70b4
	    mvJob *j=new mvJob(target_session->Clone(),file->symlink,target_name,FA::SYMLINK);
Packit 8f70b4
	    if(remove_target)
Packit 8f70b4
	       j->RemoveTargetFirst();
Packit 8f70b4
	    JobStarted(j);
Packit 8f70b4
	    RemoveSourceLater(file);
Packit 8f70b4
	    break;
Packit 8f70b4
	 }
Packit 8f70b4
Packit 8f70b4
	 if(script)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV args("shell");
Packit 8f70b4
	    args.Append("ln");
Packit 8f70b4
	    args.Append("-sf");
Packit 8f70b4
	    args.Append(shell_encode(file->symlink));
Packit 8f70b4
	    args.Append(shell_encode(target_name));
Packit 8f70b4
	    xstring_ca cmd(args.CombineQuoted());
Packit 8f70b4
	    fprintf(script,"%s\n",cmd.get());
Packit 8f70b4
	    if(script_only)
Packit 8f70b4
	       goto skip;
Packit 8f70b4
	 }
Packit 8f70b4
Packit 8f70b4
	 struct stat st;
Packit 8f70b4
	 if(lstat(target_name,&st)!=-1)
Packit 8f70b4
	 {
Packit 8f70b4
	    Report(_("Removing old local file `%s'"),target_name_rel);
Packit 8f70b4
	    stats.mod_symlinks++;
Packit 8f70b4
	    if(remove(target_name)==-1)
Packit 8f70b4
	    {
Packit 8f70b4
	       eprintf("mirror: remove(%s): %s\n",target_name,strerror(errno));
Packit 8f70b4
	       goto skip;
Packit 8f70b4
	    }
Packit 8f70b4
	 }
Packit 8f70b4
	 else
Packit 8f70b4
	 {
Packit 8f70b4
	    if(FlagSet(ONLY_EXISTING))
Packit 8f70b4
	    {
Packit 8f70b4
	       Report(_("Skipping symlink `%s' (only-existing)"),target_name_rel);
Packit 8f70b4
	       goto skip;
Packit 8f70b4
	    }
Packit 8f70b4
	    stats.new_symlinks++;
Packit 8f70b4
	 }
Packit 8f70b4
	 Report(_("Making symbolic link `%s' to `%s'"),target_name_rel,file->symlink.get());
Packit 8f70b4
	 res=symlink(file->symlink,target_name);
Packit 8f70b4
	 if(res==-1)
Packit 8f70b4
	    eprintf("mirror: symlink(%s): %s\n",target_name,strerror(errno));
Packit 8f70b4
	 RemoveSourceLater(file);
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
   case FileInfo::UNKNOWN:
Packit 8f70b4
      break;
Packit 8f70b4
   }
Packit 8f70b4
skip:
Packit 8f70b4
   return;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void  MirrorJob::InitSets()
Packit 8f70b4
{
Packit 8f70b4
   if(FlagSet(TARGET_FLAT) && !parent_mirror && target_set)
Packit 8f70b4
      source_set->Sort(FileSet::BYNAME_FLAT);
Packit 8f70b4
Packit 8f70b4
   source_set->Count(NULL,&stats.tot_files,&stats.tot_symlinks,&stats.tot_files);
Packit 8f70b4
Packit 8f70b4
   to_rm=new FileSet(target_set);
Packit 8f70b4
   to_rm->SubtractAny(source_set);
Packit 8f70b4
Packit 8f70b4
   if(FlagSet(DELETE_EXCLUDED) && target_set_excluded)
Packit 8f70b4
      to_rm->Merge(target_set_excluded);
Packit 8f70b4
Packit 8f70b4
   to_transfer=new FileSet(source_set);
Packit 8f70b4
Packit 8f70b4
   if(!FlagSet(TRANSFER_ALL)) {
Packit 8f70b4
      same=new FileSet(source_set);
Packit 8f70b4
Packit 8f70b4
      int ignore=0;
Packit 8f70b4
      if(FlagSet(ONLY_NEWER))
Packit 8f70b4
	 ignore|=FileInfo::IGNORE_SIZE_IF_OLDER|FileInfo::IGNORE_DATE_IF_OLDER;
Packit 8f70b4
      if(!FlagSet(UPLOAD_OLDER) && strcmp(target_session->GetProto(),"file"))
Packit 8f70b4
	 ignore|=FileInfo::IGNORE_DATE_IF_OLDER;
Packit 8f70b4
      if(FlagSet(IGNORE_TIME))
Packit 8f70b4
	 ignore|=FileInfo::DATE;
Packit 8f70b4
      if(FlagSet(IGNORE_SIZE))
Packit 8f70b4
	 ignore|=FileInfo::SIZE;
Packit 8f70b4
      to_transfer->SubtractSame(target_set,ignore);
Packit 8f70b4
Packit 8f70b4
      same->SubtractAny(to_transfer);
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(newer_than!=NO_DATE)
Packit 8f70b4
      to_transfer->SubtractNotNewerThan(newer_than);
Packit 8f70b4
   if(older_than!=NO_DATE)
Packit 8f70b4
      to_transfer->SubtractNotOlderThan(older_than);
Packit 8f70b4
   if(size_range)
Packit 8f70b4
      to_transfer->SubtractSizeOutside(size_range);
Packit 8f70b4
Packit 8f70b4
   if(FlagSet(SCAN_ALL_FIRST)) {
Packit 8f70b4
      to_mkdir=new FileSet(to_transfer);
Packit 8f70b4
      to_mkdir->SubtractNotDirs();
Packit 8f70b4
      to_mkdir->SubtractAny(target_set);
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   switch(recursion_mode) {
Packit 8f70b4
   case RECURSION_NEVER:
Packit 8f70b4
      to_transfer->SubtractDirs();
Packit 8f70b4
      break;
Packit 8f70b4
   case RECURSION_MISSING:
Packit 8f70b4
      to_transfer->SubtractDirs(target_set);
Packit 8f70b4
      break;
Packit 8f70b4
   case RECURSION_NEWER:
Packit 8f70b4
      to_transfer->SubtractNotOlderDirs(target_set);
Packit 8f70b4
      break;
Packit 8f70b4
   case RECURSION_ALWAYS:
Packit 8f70b4
      break;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(skip_noaccess)
Packit 8f70b4
      to_transfer->ExcludeUnaccessible(source_session->GetUser());
Packit 8f70b4
Packit 8f70b4
   new_files_set=new FileSet(to_transfer);
Packit 8f70b4
   new_files_set->SubtractAny(target_set);
Packit 8f70b4
   old_files_set=new FileSet(target_set);
Packit 8f70b4
   old_files_set->SubtractNotIn(to_transfer);
Packit 8f70b4
Packit 8f70b4
   to_rm_mismatched=new FileSet(old_files_set);
Packit 8f70b4
   to_rm_mismatched->SubtractSameType(to_transfer);
Packit 8f70b4
   to_rm_mismatched->SubtractNotDirs();
Packit 8f70b4
Packit 8f70b4
   if(!FlagSet(DELETE))
Packit 8f70b4
      to_transfer->SubtractAny(to_rm_mismatched);
Packit 8f70b4
Packit 8f70b4
   if(FlagSet(TARGET_FLAT) && !parent_mirror && target_set) {
Packit 8f70b4
      source_set->Unsort();
Packit 8f70b4
      to_transfer->UnsortFlat();
Packit 8f70b4
      to_transfer->SubtractDirs();
Packit 8f70b4
      same->UnsortFlat();
Packit 8f70b4
      to_mkdir->Empty();
Packit 8f70b4
      new_files_set->UnsortFlat();
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   const char *sort_by=ResMgr::Query("mirror:sort-by",0);
Packit 8f70b4
   bool desc=strstr(sort_by,"-desc");
Packit 8f70b4
   if(!strncmp(sort_by,"name",4))
Packit 8f70b4
      to_transfer->SortByPatternList(ResMgr::Query("mirror:order",0));
Packit 8f70b4
   else if(!strncmp(sort_by,"date",4))
Packit 8f70b4
      to_transfer->Sort(FileSet::BYDATE);
Packit 8f70b4
   else if(!strncmp(sort_by,"size",4))
Packit 8f70b4
      to_transfer->Sort(FileSet::BYSIZE,false,true);
Packit 8f70b4
   if(desc)
Packit 8f70b4
      to_transfer->ReverseSort();
Packit 8f70b4
Packit 8f70b4
   int dir_count=0;
Packit 8f70b4
   if(to_mkdir) {
Packit 8f70b4
      to_mkdir->Count(&dir_count,NULL,NULL,NULL);
Packit 8f70b4
      only_dirs = (dir_count==to_mkdir->count());
Packit 8f70b4
   } else {
Packit 8f70b4
      to_transfer->Count(&dir_count,NULL,NULL,NULL);
Packit 8f70b4
      only_dirs = (dir_count==to_transfer->count());
Packit 8f70b4
   }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
/* root_transfer_count of child mirrors contains the value to add or
Packit 8f70b4
   subtract from transfer_count when doing "cd", "mkdir", "ls" on the source
Packit 8f70b4
   or target directories. This would prevent other mirrors (siblings, etc)
Packit 8f70b4
   from starting more jobs.
Packit 8f70b4
Packit 8f70b4
   root_transfer_count is initialized once in ctor, so that change of
Packit 8f70b4
   mirror:parallel-directories setting won't disbalance the count.
Packit 8f70b4
*/
Packit 8f70b4
void MirrorJob::MirrorStarted()
Packit 8f70b4
{
Packit 8f70b4
   if(!parent_mirror)
Packit 8f70b4
      return;
Packit 8f70b4
   transfer_count+=root_transfer_count;
Packit 8f70b4
}
Packit 8f70b4
void MirrorJob::MirrorFinished()
Packit 8f70b4
{
Packit 8f70b4
   if(!parent_mirror)
Packit 8f70b4
      return;
Packit 8f70b4
   assert(transfer_count>=root_transfer_count);
Packit 8f70b4
   transfer_count-=root_transfer_count;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void MirrorJob::HandleChdir(FileAccessRef& session, int &redirections)
Packit 8f70b4
{
Packit 8f70b4
   if(!session->IsOpen())
Packit 8f70b4
      return;
Packit 8f70b4
   int res=session->Done();
Packit 8f70b4
   if(res<0)
Packit 8f70b4
   {
Packit 8f70b4
      if(res==FA::FILE_MOVED)
Packit 8f70b4
      {
Packit 8f70b4
	 // cd to another url.
Packit 8f70b4
	 const char *loc_c=session->GetNewLocation();
Packit 8f70b4
	 int max_redirections=ResMgr::Query("xfer:max-redirections",0);
Packit 8f70b4
	 if(loc_c && max_redirections>0)
Packit 8f70b4
	 {
Packit 8f70b4
	    if(++redirections>max_redirections)
Packit 8f70b4
	       goto cd_err_normal;
Packit 8f70b4
	    eprintf(_("%s: received redirection to `%s'\n"),"mirror",loc_c);
Packit 8f70b4
Packit 8f70b4
	    char *loc=alloca_strdup(loc_c);
Packit 8f70b4
	    ParsedURL u(loc,true);
Packit 8f70b4
Packit 8f70b4
	    bool is_file=(last_char(loc)!='/');
Packit 8f70b4
	    if(!u.proto)
Packit 8f70b4
	    {
Packit 8f70b4
	       FileAccess::Path new_cwd(session->GetNewCwd());
Packit 8f70b4
	       new_cwd.Change(0,is_file,loc);
Packit 8f70b4
	       session->PathVerify(new_cwd);
Packit 8f70b4
	       session->Roll();
Packit 8f70b4
	       return;
Packit 8f70b4
	    }
Packit 8f70b4
	    session->Close(); // loc_c is no longer valid.
Packit 8f70b4
	    session=FA::New(&u);
Packit 8f70b4
	    FileAccess::Path new_cwd(u.path,is_file,url::path_ptr(loc));
Packit 8f70b4
	    session->PathVerify(new_cwd);
Packit 8f70b4
	    return;
Packit 8f70b4
	 }
Packit 8f70b4
      }
Packit 8f70b4
   cd_err_normal:
Packit 8f70b4
      if(session==target_session && (script_only || FlagSet(SCAN_ALL_FIRST)))
Packit 8f70b4
      {
Packit 8f70b4
	 char *dir=alloca_strdup(session->GetFile());
Packit 8f70b4
	 session->Close();
Packit 8f70b4
	 session->Chdir(dir,false);
Packit 8f70b4
	 no_target_dir=true;
Packit 8f70b4
	 return;
Packit 8f70b4
      }
Packit 8f70b4
      if(session==source_session && create_target_dir
Packit 8f70b4
      && !FlagSet(NO_EMPTY_DIRS) && !skip_noaccess && parent_mirror)
Packit 8f70b4
      {
Packit 8f70b4
	 // create target dir even if failed to cd to source dir.
Packit 8f70b4
	 if(script)
Packit 8f70b4
	    fprintf(script,"mkdir %s\n",target_session->GetFileURL(target_dir).get());
Packit 8f70b4
	 if(!script_only)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV *a=new ArgV("mkdir");
Packit 8f70b4
	    a->Append(target_dir);
Packit 8f70b4
	    mkdirJob *mkj=new mkdirJob(target_session->Clone(),a);
Packit 8f70b4
	    a->CombineTo(mkj->cmdline);
Packit 8f70b4
	    JobStarted(mkj);
Packit 8f70b4
	 }
Packit 8f70b4
      }
Packit 8f70b4
      remove_this_source_dir=false;
Packit 8f70b4
      eprintf("mirror: %s\n",session->StrError(res));
Packit 8f70b4
      stats.error_count++;
Packit 8f70b4
      MirrorFinished();
Packit 8f70b4
      set_state(FINISHING);
Packit 8f70b4
      source_session->Close();
Packit 8f70b4
      target_session->Close();
Packit 8f70b4
      return;
Packit 8f70b4
   }
Packit 8f70b4
   if(res==FA::OK)
Packit 8f70b4
      session->Close();
Packit 8f70b4
}
Packit 8f70b4
void MirrorJob::HandleListInfoCreation(const FileAccessRef& session,SMTaskRef<ListInfo>& list_info,const char *relative_dir)
Packit 8f70b4
{
Packit 8f70b4
   if(state!=GETTING_LIST_INFO)
Packit 8f70b4
      return;
Packit 8f70b4
Packit 8f70b4
   if(session==target_session && no_target_dir)
Packit 8f70b4
   {
Packit 8f70b4
      target_set=new FileSet();
Packit 8f70b4
      return;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   list_info=session->MakeListInfo();
Packit 8f70b4
   if(list_info==0)
Packit 8f70b4
   {
Packit 8f70b4
      eprintf(_("mirror: protocol `%s' is not suitable for mirror\n"),
Packit 8f70b4
	       session->GetProto());
Packit 8f70b4
      MirrorFinished();
Packit 8f70b4
      set_state(FINISHING);
Packit 8f70b4
      return;
Packit 8f70b4
   }
Packit 8f70b4
   list_info->UseCache(use_cache);
Packit 8f70b4
   int need=FileInfo::ALL_INFO;
Packit 8f70b4
   if(FlagSet(IGNORE_TIME))
Packit 8f70b4
      need&=~FileInfo::DATE;
Packit 8f70b4
   if(FlagSet(IGNORE_SIZE))
Packit 8f70b4
      need&=~FileInfo::SIZE;
Packit 8f70b4
   list_info->Need(need);
Packit 8f70b4
   if(FlagSet(RETR_SYMLINKS))
Packit 8f70b4
      list_info->FollowSymlinks();
Packit 8f70b4
Packit 8f70b4
   list_info->SetExclude(relative_dir,top_exclude?top_exclude:exclude);
Packit 8f70b4
   list_info->Roll();
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void MirrorJob::HandleListInfo(SMTaskRef<ListInfo>& list_info, Ref<FileSet>& set, Ref<FileSet> *fsx)
Packit 8f70b4
{
Packit 8f70b4
   if(!list_info)
Packit 8f70b4
      return;
Packit 8f70b4
   if(!list_info->Done())
Packit 8f70b4
      return;
Packit 8f70b4
   if(list_info->Error())
Packit 8f70b4
   {
Packit 8f70b4
      eprintf("mirror: %s\n",list_info->ErrorText());
Packit 8f70b4
      stats.error_count++;
Packit 8f70b4
      MirrorFinished();
Packit 8f70b4
      set_state(FINISHING);
Packit 8f70b4
      source_list_info=0;
Packit 8f70b4
      target_list_info=0;
Packit 8f70b4
      return;
Packit 8f70b4
   }
Packit 8f70b4
   set=list_info->GetResult();
Packit 8f70b4
   if(fsx)
Packit 8f70b4
      *fsx=list_info->GetExcluded();
Packit 8f70b4
   list_info=0;
Packit 8f70b4
   set->ExcludeDots(); // don't need .. and .
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
int   MirrorJob::Do()
Packit 8f70b4
{
Packit 8f70b4
   int	 res;
Packit 8f70b4
   int	 m=STALL;
Packit 8f70b4
   FileInfo *file;
Packit 8f70b4
   Job	 *j;
Packit 8f70b4
Packit 8f70b4
   switch(state)
Packit 8f70b4
   {
Packit 8f70b4
   case(INITIAL_STATE):
Packit 8f70b4
      remove_this_source_dir=(remove_source_dirs && source_dir.last_char()!='/');
Packit 8f70b4
      if(!strcmp(target_dir,".") || !strcmp(target_dir,"..") || (FlagSet(SCAN_ALL_FIRST) && parent_mirror))
Packit 8f70b4
	 create_target_dir=false;
Packit 8f70b4
Packit 8f70b4
      source_session->Chdir(source_dir);
Packit 8f70b4
      source_redirections=0;
Packit 8f70b4
      source_session->Roll();
Packit 8f70b4
      set_state(CHANGING_DIR_SOURCE);
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
      /*fallthrough*/
Packit 8f70b4
   case(CHANGING_DIR_SOURCE):
Packit 8f70b4
      HandleChdir(source_session,source_redirections);
Packit 8f70b4
      if(state!=CHANGING_DIR_SOURCE)
Packit 8f70b4
	 return MOVED;
Packit 8f70b4
      if(source_session->IsOpen())
Packit 8f70b4
	 return m;
Packit 8f70b4
Packit 8f70b4
      source_dir.set(source_session->GetCwd().GetDirectory());
Packit 8f70b4
Packit 8f70b4
   pre_MAKE_TARGET_DIR:
Packit 8f70b4
   {
Packit 8f70b4
      if(!create_target_dir)
Packit 8f70b4
	 goto pre_CHANGING_DIR_TARGET;
Packit 8f70b4
      if(target_is_local)
Packit 8f70b4
      {
Packit 8f70b4
	 struct stat st;
Packit 8f70b4
	 if((FlagSet(RETR_SYMLINKS)?stat:lstat)(target_dir,&st)!=-1)
Packit 8f70b4
	 {
Packit 8f70b4
	    if(S_ISDIR(st.st_mode))
Packit 8f70b4
	    {
Packit 8f70b4
	       // try to enable write access
Packit 8f70b4
	       // only if not enabled as chmod can clear sgid flags on directories
Packit 8f70b4
	       if(!script_only && (st.st_mode!=(st.st_mode|0700)))
Packit 8f70b4
		  chmod(target_dir,st.st_mode|0700);
Packit 8f70b4
	       create_target_dir=false;
Packit 8f70b4
	       goto pre_CHANGING_DIR_TARGET;
Packit 8f70b4
	    }
Packit 8f70b4
	    else
Packit 8f70b4
	    {
Packit 8f70b4
	       Report(_("Removing old local file `%s'"),target_dir.get());
Packit 8f70b4
	       if(script)
Packit 8f70b4
	       {
Packit 8f70b4
		  ArgV args("rm");
Packit 8f70b4
		  args.Append(target_session->GetFileURL(target_dir));
Packit 8f70b4
		  xstring_ca cmd(args.CombineQuoted());
Packit 8f70b4
		  fprintf(script,"%s\n",cmd.get());
Packit 8f70b4
	       }
Packit 8f70b4
	       if(!script_only)
Packit 8f70b4
	       {
Packit 8f70b4
		  if(remove(target_dir)==-1)
Packit 8f70b4
		     eprintf("mirror: remove(%s): %s\n",target_dir.get(),strerror(errno));
Packit 8f70b4
	       }
Packit 8f70b4
	    }
Packit 8f70b4
	 }
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      if(FlagSet(DEPTH_FIRST))
Packit 8f70b4
	 goto pre_GETTING_LIST_INFO;
Packit 8f70b4
Packit 8f70b4
      if(target_relative_dir)
Packit 8f70b4
	 Report(_("Making directory `%s'"),target_relative_dir.get());
Packit 8f70b4
      bool mkdir_p=(parent_mirror==0 || parent_mirror->create_target_dir);
Packit 8f70b4
      if(script)
Packit 8f70b4
      {
Packit 8f70b4
	 ArgV args("mkdir");
Packit 8f70b4
	 if(mkdir_p)
Packit 8f70b4
	    args.Append("-p");
Packit 8f70b4
	 args.Append(target_session->GetFileURL(target_dir));
Packit 8f70b4
	 xstring_ca cmd(args.CombineQuoted());
Packit 8f70b4
	 fprintf(script,"%s\n",cmd.get());
Packit 8f70b4
	 if(script_only)
Packit 8f70b4
	    goto pre_CHANGING_DIR_TARGET;
Packit 8f70b4
      }
Packit 8f70b4
      target_session->Mkdir(target_dir,mkdir_p);
Packit 8f70b4
      set_state(MAKE_TARGET_DIR);
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
   }
Packit 8f70b4
      /*fallthrough*/
Packit 8f70b4
   case(MAKE_TARGET_DIR):
Packit 8f70b4
      res=target_session->Done();
Packit 8f70b4
      if(res==FA::IN_PROGRESS)
Packit 8f70b4
	 return m;
Packit 8f70b4
      target_session->Close();
Packit 8f70b4
      create_target_dir=false;
Packit 8f70b4
Packit 8f70b4
   pre_CHANGING_DIR_TARGET:
Packit 8f70b4
      target_session->Chdir(target_dir);
Packit 8f70b4
      target_redirections=0;
Packit 8f70b4
      target_session->Roll();
Packit 8f70b4
      set_state(CHANGING_DIR_TARGET);
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
      /*fallthrough*/
Packit 8f70b4
   case(CHANGING_DIR_TARGET):
Packit 8f70b4
      HandleChdir(target_session,target_redirections);
Packit 8f70b4
      if(state!=CHANGING_DIR_TARGET)
Packit 8f70b4
	 return MOVED;
Packit 8f70b4
      if(target_session->IsOpen())
Packit 8f70b4
	 return m;
Packit 8f70b4
      create_target_dir=false;
Packit 8f70b4
Packit 8f70b4
      target_dir.set(target_session->GetCwd().GetDirectory());
Packit 8f70b4
Packit 8f70b4
   pre_GETTING_LIST_INFO:
Packit 8f70b4
      set_state(GETTING_LIST_INFO);
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
      if(!source_set)
Packit 8f70b4
	 HandleListInfoCreation(source_session,source_list_info,source_relative_dir);
Packit 8f70b4
      if(!target_set && !create_target_dir
Packit 8f70b4
      && (!FlagSet(DEPTH_FIRST) || FlagSet(ONLY_EXISTING))
Packit 8f70b4
      && !(FlagSet(TARGET_FLAT) && parent_mirror))
Packit 8f70b4
	 HandleListInfoCreation(target_session,target_list_info,target_relative_dir);
Packit 8f70b4
      if(state!=GETTING_LIST_INFO)
Packit 8f70b4
      {
Packit 8f70b4
	 source_list_info=0;
Packit 8f70b4
	 target_list_info=0;
Packit 8f70b4
      }
Packit 8f70b4
      return m;	  // give time to other tasks
Packit 8f70b4
   case(GETTING_LIST_INFO):
Packit 8f70b4
      HandleListInfo(source_list_info,source_set);
Packit 8f70b4
      HandleListInfo(target_list_info,target_set,&target_set_excluded);
Packit 8f70b4
      if(state!=GETTING_LIST_INFO)
Packit 8f70b4
	 return MOVED;
Packit 8f70b4
      if(source_list_info || target_list_info)
Packit 8f70b4
	 return m;
Packit 8f70b4
Packit 8f70b4
      MirrorFinished(); // leave room for transfers.
Packit 8f70b4
Packit 8f70b4
      if(FlagSet(DEPTH_FIRST) && source_set && !target_set)
Packit 8f70b4
      {
Packit 8f70b4
	 // transfer directories first
Packit 8f70b4
	 InitSets();
Packit 8f70b4
	 to_transfer->Unsort();
Packit 8f70b4
	 to_transfer->SubtractNotDirs();
Packit 8f70b4
	 goto pre_WAITING_FOR_TRANSFER;
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      // now we have both target and source file sets.
Packit 8f70b4
      if(parent_mirror)
Packit 8f70b4
	 stats.dirs++;
Packit 8f70b4
Packit 8f70b4
      if(FlagSet(SCAN_ALL_FIRST) && parent_mirror)
Packit 8f70b4
      {
Packit 8f70b4
	 source_set->PrependPath(source_relative_dir);
Packit 8f70b4
	 if(root_mirror->source_set_recursive)
Packit 8f70b4
	    root_mirror->source_set_recursive->Merge(source_set);
Packit 8f70b4
	 else
Packit 8f70b4
	    root_mirror->source_set_recursive=source_set.borrow();
Packit 8f70b4
	 if(target_set) {
Packit 8f70b4
	    target_set->PrependPath(target_relative_dir);
Packit 8f70b4
	    if(root_mirror->target_set_recursive)
Packit 8f70b4
	       root_mirror->target_set_recursive->Merge(target_set);
Packit 8f70b4
	    else
Packit 8f70b4
	       root_mirror->target_set_recursive=target_set.borrow();
Packit 8f70b4
	 }
Packit 8f70b4
	 if(target_set_excluded) {
Packit 8f70b4
	    target_set_excluded->PrependPath(target_relative_dir);
Packit 8f70b4
	    if(root_mirror->target_set_excluded)
Packit 8f70b4
	       root_mirror->target_set_excluded->Merge(target_set_excluded);
Packit 8f70b4
	    else
Packit 8f70b4
	       root_mirror->target_set_excluded=target_set_excluded.borrow();
Packit 8f70b4
	 }
Packit 8f70b4
	 root_mirror->stats.dirs++;
Packit 8f70b4
	 transfer_count++; // parent mirror will decrement it.
Packit 8f70b4
	 goto pre_DONE;
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      if(source_set_recursive) {
Packit 8f70b4
	 source_set->Merge(source_set_recursive);
Packit 8f70b4
	 source_set_recursive=0;
Packit 8f70b4
      }
Packit 8f70b4
      if(target_set_recursive) {
Packit 8f70b4
	 target_set->Merge(target_set_recursive);
Packit 8f70b4
	 target_set_recursive=0;
Packit 8f70b4
      }
Packit 8f70b4
      InitSets();
Packit 8f70b4
Packit 8f70b4
      to_transfer->CountBytes(&bytes_to_transfer);
Packit 8f70b4
      if(parent_mirror)
Packit 8f70b4
	 parent_mirror->AddBytesToTransfer(bytes_to_transfer);
Packit 8f70b4
Packit 8f70b4
      to_rm->Count(&stats.del_dirs,&stats.del_files,&stats.del_symlinks,&stats.del_files);
Packit 8f70b4
      to_rm->rewind();
Packit 8f70b4
      to_rm_mismatched->Count(&stats.del_dirs,&stats.del_files,&stats.del_symlinks,&stats.del_files);
Packit 8f70b4
      to_rm_mismatched->rewind();
Packit 8f70b4
Packit 8f70b4
      target_set->Merge(target_set_excluded);
Packit 8f70b4
      target_set_excluded=0;
Packit 8f70b4
Packit 8f70b4
      set_state(TARGET_REMOVE_OLD_FIRST);
Packit 8f70b4
      goto TARGET_REMOVE_OLD_FIRST_label;
Packit 8f70b4
Packit 8f70b4
   pre_TARGET_MKDIR:
Packit 8f70b4
      if(!to_mkdir)
Packit 8f70b4
	 goto pre_WAITING_FOR_TRANSFER;
Packit 8f70b4
      to_mkdir->rewind();
Packit 8f70b4
      set_state(TARGET_MKDIR);
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
      /*fallthrough*/
Packit 8f70b4
   case(TARGET_MKDIR):
Packit 8f70b4
      while((j=FindDoneAwaitedJob())!=0)
Packit 8f70b4
      {
Packit 8f70b4
	 JobFinished(j);
Packit 8f70b4
	 m=MOVED;
Packit 8f70b4
      }
Packit 8f70b4
      if(max_error_count>0 && stats.error_count>=max_error_count)
Packit 8f70b4
	 goto pre_FINISHING;
Packit 8f70b4
      while(transfer_count
Packit 8f70b4
      {
Packit 8f70b4
	 file=to_mkdir->curr();
Packit 8f70b4
	 if(!file)
Packit 8f70b4
	    goto pre_WAITING_FOR_TRANSFER;
Packit 8f70b4
	 to_mkdir->next();
Packit 8f70b4
	 if(!file->TypeIs(file->DIRECTORY))
Packit 8f70b4
	    continue;
Packit 8f70b4
	 if(script)
Packit 8f70b4
	    fprintf(script,"mkdir %s\n",target_session->GetFileURL(file->name).get());
Packit 8f70b4
	 if(!script_only)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV *a=new ArgV("mkdir");
Packit 8f70b4
	    a->Append(file->name);
Packit 8f70b4
	    mkdirJob *mkj=new mkdirJob(target_session->Clone(),a);
Packit 8f70b4
	    a->CombineTo(mkj->cmdline);
Packit 8f70b4
	    JobStarted(mkj);
Packit 8f70b4
	    m=MOVED;
Packit 8f70b4
	 }
Packit 8f70b4
      }
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   pre_WAITING_FOR_TRANSFER:
Packit 8f70b4
      to_transfer->rewind();
Packit 8f70b4
      set_state(WAITING_FOR_TRANSFER);
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
      /*fallthrough*/
Packit 8f70b4
   case(WAITING_FOR_TRANSFER):
Packit 8f70b4
      while((j=FindDoneAwaitedJob())!=0)
Packit 8f70b4
      {
Packit 8f70b4
	 TransferFinished(j);
Packit 8f70b4
	 m=MOVED;
Packit 8f70b4
      }
Packit 8f70b4
      if(max_error_count>0 && stats.error_count>=max_error_count)
Packit 8f70b4
	 goto pre_FINISHING;
Packit 8f70b4
      while(transfer_count
Packit 8f70b4
      {
Packit 8f70b4
	 file=to_transfer->curr();
Packit 8f70b4
	 if(!file)
Packit 8f70b4
	 {
Packit 8f70b4
	    // go to the next step only when all transfers have finished
Packit 8f70b4
	    if(waiting_num>0)
Packit 8f70b4
	       break;
Packit 8f70b4
	    if(FlagSet(DEPTH_FIRST))
Packit 8f70b4
	    {
Packit 8f70b4
	       // we have been in the depth, don't go there again
Packit 8f70b4
	       SetFlags(DEPTH_FIRST,false);
Packit 8f70b4
	       SetFlags(NO_RECURSION,true);
Packit 8f70b4
Packit 8f70b4
	       // if we have not created any subdirs and there are only subdirs,
Packit 8f70b4
	       // then the directory would be empty - skip it.
Packit 8f70b4
	       if(FlagSet(NO_EMPTY_DIRS) && stats.dirs==0 && only_dirs)
Packit 8f70b4
		  goto pre_FINISHING_FIX_LOCAL;
Packit 8f70b4
Packit 8f70b4
	       MirrorStarted();
Packit 8f70b4
	       goto pre_MAKE_TARGET_DIR;
Packit 8f70b4
	    }
Packit 8f70b4
	    goto pre_TARGET_REMOVE_OLD;
Packit 8f70b4
	 }
Packit 8f70b4
	 HandleFile(file);
Packit 8f70b4
	 to_transfer->next();
Packit 8f70b4
	 m=MOVED;
Packit 8f70b4
      }
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   pre_TARGET_REMOVE_OLD:
Packit 8f70b4
      if(FlagSet(REMOVE_FIRST))
Packit 8f70b4
	 goto pre_TARGET_CHMOD;
Packit 8f70b4
      set_state(TARGET_REMOVE_OLD);
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
      /*fallthrough*/
Packit 8f70b4
   case(TARGET_REMOVE_OLD):
Packit 8f70b4
   case(TARGET_REMOVE_OLD_FIRST):
Packit 8f70b4
   TARGET_REMOVE_OLD_FIRST_label:
Packit 8f70b4
      while((j=FindDoneAwaitedJob())!=0)
Packit 8f70b4
      {
Packit 8f70b4
	 JobFinished(j);
Packit 8f70b4
	 m=MOVED;
Packit 8f70b4
      }
Packit 8f70b4
      if(max_error_count>0 && stats.error_count>=max_error_count)
Packit 8f70b4
	 goto pre_FINISHING;
Packit 8f70b4
      while(transfer_count
Packit 8f70b4
      {
Packit 8f70b4
	 file=0;
Packit 8f70b4
	 if(!file && state==TARGET_REMOVE_OLD_FIRST)
Packit 8f70b4
	 {
Packit 8f70b4
	    file=to_rm_mismatched->curr();
Packit 8f70b4
	    to_rm_mismatched->next();
Packit 8f70b4
	 }
Packit 8f70b4
	 if(!file && (state==TARGET_REMOVE_OLD || FlagSet(REMOVE_FIRST)))
Packit 8f70b4
	 {
Packit 8f70b4
	    file=to_rm->curr();
Packit 8f70b4
	    to_rm->next();
Packit 8f70b4
	 }
Packit 8f70b4
	 if(!file)
Packit 8f70b4
	 {
Packit 8f70b4
	    if(waiting_num>0)
Packit 8f70b4
	       break;
Packit 8f70b4
	    if(state==TARGET_REMOVE_OLD)
Packit 8f70b4
	       goto pre_TARGET_CHMOD;
Packit 8f70b4
	    goto pre_TARGET_MKDIR;
Packit 8f70b4
	 }
Packit 8f70b4
	 if(!FlagSet(DELETE))
Packit 8f70b4
	 {
Packit 8f70b4
	    if(FlagSet(REPORT_NOT_DELETED))
Packit 8f70b4
	    {
Packit 8f70b4
	       const char *target_name_rel=dir_file(target_relative_dir,file->name);
Packit 8f70b4
	       if(file->TypeIs(file->DIRECTORY))
Packit 8f70b4
		  Report(_("Old directory `%s' is not removed"),target_name_rel);
Packit 8f70b4
	       else
Packit 8f70b4
		  Report(_("Old file `%s' is not removed"),target_name_rel);
Packit 8f70b4
	    }
Packit 8f70b4
	    continue;
Packit 8f70b4
	 }
Packit 8f70b4
	 bool use_rmdir = (file->TypeIs(file->DIRECTORY)
Packit 8f70b4
			   && recursion_mode==RECURSION_NEVER);
Packit 8f70b4
	 if(script)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV args(use_rmdir?"rmdir":"rm");
Packit 8f70b4
	    if(file->TypeIs(file->DIRECTORY) && !use_rmdir)
Packit 8f70b4
	       args.Append("-r");
Packit 8f70b4
	    args.Append(target_session->GetFileURL(file->name));
Packit 8f70b4
	    xstring_ca cmd(args.CombineQuoted());
Packit 8f70b4
	    fprintf(script,"%s\n",cmd.get());
Packit 8f70b4
	 }
Packit 8f70b4
	 if(!script_only)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV *args=new ArgV(use_rmdir?"rmdir":"rm");
Packit 8f70b4
	    args->Append(dir_file(".",file->name));
Packit 8f70b4
	    args->seek(1);
Packit 8f70b4
	    rmJob *j=new rmJob(target_session->Clone(),args);
Packit 8f70b4
	    args->CombineTo(j->cmdline);
Packit 8f70b4
	    JobStarted(j);
Packit 8f70b4
	    if(file->TypeIs(file->DIRECTORY))
Packit 8f70b4
	    {
Packit 8f70b4
	       if(recursion_mode==RECURSION_NEVER)
Packit 8f70b4
		  j->Rmdir();
Packit 8f70b4
	       else
Packit 8f70b4
		  j->Recurse();
Packit 8f70b4
	    }
Packit 8f70b4
	 }
Packit 8f70b4
	 const char *target_name_rel=dir_file(target_relative_dir,file->name);
Packit 8f70b4
	 if(file->TypeIs(file->DIRECTORY))
Packit 8f70b4
	    Report(_("Removing old directory `%s'"),target_name_rel);
Packit 8f70b4
	 else
Packit 8f70b4
	    Report(_("Removing old file `%s'"),target_name_rel);
Packit 8f70b4
      }
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   pre_TARGET_CHMOD:
Packit 8f70b4
      if(FlagSet(NO_PERMS))
Packit 8f70b4
	 goto pre_FINISHING_FIX_LOCAL;
Packit 8f70b4
Packit 8f70b4
      to_transfer->rewind();
Packit 8f70b4
      if(FlagSet(TARGET_FLAT))
Packit 8f70b4
	 to_transfer->Sort(FileSet::BYNAME_FLAT);
Packit 8f70b4
      set_state(TARGET_CHMOD);
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
      /*fallthrough*/
Packit 8f70b4
   case(TARGET_CHMOD):
Packit 8f70b4
      while((j=FindDoneAwaitedJob())!=0)
Packit 8f70b4
      {
Packit 8f70b4
	 JobFinished(j);
Packit 8f70b4
	 m=MOVED;
Packit 8f70b4
      }
Packit 8f70b4
      if(max_error_count>0 && stats.error_count>=max_error_count)
Packit 8f70b4
	 goto pre_FINISHING;
Packit 8f70b4
      while(transfer_count
Packit 8f70b4
      {
Packit 8f70b4
	 file=to_transfer->curr();
Packit 8f70b4
	 if(!file)
Packit 8f70b4
	    goto pre_FINISHING_FIX_LOCAL;
Packit 8f70b4
	 to_transfer->next();
Packit 8f70b4
	 if(file->TypeIs(file->SYMLINK))
Packit 8f70b4
	    continue;
Packit 8f70b4
	 if(!file->Has(file->MODE))
Packit 8f70b4
	    continue;
Packit 8f70b4
	 mode_t mode_mask=get_mode_mask();
Packit 8f70b4
	 mode_t def_mode=(file->TypeIs(file->DIRECTORY)?0775:0664)&~mode_mask;
Packit 8f70b4
	 if(target_is_local && file->mode==def_mode)
Packit 8f70b4
	 {
Packit 8f70b4
	    struct stat st;
Packit 8f70b4
	    if(!target_is_local || lstat(dir_file(target_dir,file->name),&st)==-1)
Packit 8f70b4
	       continue;
Packit 8f70b4
	    if((st.st_mode&07777)==(file->mode&~mode_mask))
Packit 8f70b4
	       continue;
Packit 8f70b4
	 }
Packit 8f70b4
	 FileInfo *target=target_set->FindByName(file->name);
Packit 8f70b4
	 if(target && target->filetype==file->DIRECTORY && file->filetype==file->DIRECTORY
Packit 8f70b4
	 && target->mode==(file->mode&~mode_mask) && (target->mode&0200))
Packit 8f70b4
	    continue;
Packit 8f70b4
	 if(script)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV args("chmod");
Packit 8f70b4
	    args.Append(xstring::format("%03lo",(unsigned long)(file->mode&~mode_mask)));
Packit 8f70b4
	    args.Append(target_session->GetFileURL(file->name));
Packit 8f70b4
	    xstring_ca cmd(args.CombineQuoted());
Packit 8f70b4
	    fprintf(script,"%s\n",cmd.get());
Packit 8f70b4
	 }
Packit 8f70b4
	 if(!script_only)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV *a=new ArgV("chmod");
Packit 8f70b4
	    a->Append(dir_file(".",file->name));
Packit 8f70b4
	    a->seek(1);
Packit 8f70b4
	    ChmodJob *cj=new ChmodJob(target_session->Clone(),
Packit 8f70b4
				 file->mode&~mode_mask,a);
Packit 8f70b4
	    a->CombineTo(cj->cmdline);
Packit 8f70b4
	    if(!verbose_report)
Packit 8f70b4
	       cj->BeQuiet(); // chmod is not supported on all servers; be quiet.
Packit 8f70b4
	    JobStarted(cj);
Packit 8f70b4
	    m=MOVED;
Packit 8f70b4
	 }
Packit 8f70b4
      }
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   pre_FINISHING_FIX_LOCAL:
Packit 8f70b4
      if(target_is_local && !script_only)     // FIXME
Packit 8f70b4
      {
Packit 8f70b4
	 const bool flat=FlagSet(TARGET_FLAT);
Packit 8f70b4
	 to_transfer->Sort(FileSet::BYNAME_FLAT);
Packit 8f70b4
	 to_transfer->LocalUtime(target_dir,/*only_dirs=*/true,flat);
Packit 8f70b4
	 if(FlagSet(ALLOW_CHOWN))
Packit 8f70b4
	    to_transfer->LocalChown(target_dir,flat);
Packit 8f70b4
	 if(!FlagSet(NO_PERMS) && same)
Packit 8f70b4
	    same->LocalChmod(target_dir,get_mode_mask(),flat);
Packit 8f70b4
	 if(FlagSet(ALLOW_CHOWN) && same)
Packit 8f70b4
	    same->LocalChown(target_dir,flat);
Packit 8f70b4
      }
Packit 8f70b4
      if(remove_source_files && (same || to_rm_src))
Packit 8f70b4
	 goto pre_SOURCE_REMOVING_SAME;
Packit 8f70b4
   pre_FINISHING:
Packit 8f70b4
      set_state(FINISHING);
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
      /*fallthrough*/
Packit 8f70b4
   case(FINISHING):
Packit 8f70b4
      while((j=FindDoneAwaitedJob())!=0)
Packit 8f70b4
      {
Packit 8f70b4
	 JobFinished(j);
Packit 8f70b4
	 m=MOVED;
Packit 8f70b4
      }
Packit 8f70b4
      if(waiting_num>0)
Packit 8f70b4
	 break;
Packit 8f70b4
Packit 8f70b4
      // all jobs finished.
Packit 8f70b4
      if(remove_this_source_dir) {
Packit 8f70b4
	 // remove source directory once.
Packit 8f70b4
	 remove_this_source_dir=false;
Packit 8f70b4
	 if(script)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV args("rmdir");
Packit 8f70b4
	    args.Append(source_session->GetFileURL(source_dir));
Packit 8f70b4
	    xstring_ca cmd(args.CombineQuoted());
Packit 8f70b4
	    fprintf(script,"%s\n",cmd.get());
Packit 8f70b4
	 }
Packit 8f70b4
	 if(!script_only)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV *args=new ArgV("rmdir");
Packit 8f70b4
	    args->Append(source_dir);
Packit 8f70b4
	    args->seek(1);
Packit 8f70b4
	    rmJob *j=new rmJob(source_session->Clone(),args);
Packit 8f70b4
	    args->CombineTo(j->cmdline);
Packit 8f70b4
	    j->Rmdir();
Packit 8f70b4
	    JobStarted(j);
Packit 8f70b4
	 }
Packit 8f70b4
	 if(source_relative_dir)
Packit 8f70b4
	    Report(_("Removing source directory `%s'"),source_relative_dir.get());
Packit 8f70b4
	 m=MOVED;
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
Packit 8f70b4
      // all jobs finished and src dir removed, if needed.
Packit 8f70b4
Packit 8f70b4
      transfer_count++; // parent mirror will decrement it.
Packit 8f70b4
      if(parent_mirror)
Packit 8f70b4
	 parent_mirror->stats.Add(stats);
Packit 8f70b4
      else
Packit 8f70b4
      {
Packit 8f70b4
	 if(stats.HaveSomethingDone(flags) && on_change)
Packit 8f70b4
	 {
Packit 8f70b4
	    CmdExec *exec=new CmdExec(source_session->Clone(),0);
Packit 8f70b4
	    AddWaiting(exec);
Packit 8f70b4
	    exec->FeedCmd(on_change);
Packit 8f70b4
	    exec->FeedCmd("\n");
Packit 8f70b4
	    set_state(LAST_EXEC);
Packit 8f70b4
	    break;
Packit 8f70b4
	 }
Packit 8f70b4
      }
Packit 8f70b4
      goto pre_DONE;
Packit 8f70b4
Packit 8f70b4
   pre_SOURCE_REMOVING_SAME:
Packit 8f70b4
      if(!same)
Packit 8f70b4
	 same=to_rm_src.borrow();
Packit 8f70b4
      else if(to_rm_src)
Packit 8f70b4
	 same->Merge(to_rm_src);
Packit 8f70b4
      same->rewind();
Packit 8f70b4
      set_state(SOURCE_REMOVING_SAME);
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
      /*fallthrough*/
Packit 8f70b4
   case(SOURCE_REMOVING_SAME):
Packit 8f70b4
      while((j=FindDoneAwaitedJob())!=0)
Packit 8f70b4
      {
Packit 8f70b4
	 JobFinished(j);
Packit 8f70b4
	 m=MOVED;
Packit 8f70b4
      }
Packit 8f70b4
      if(max_error_count>0 && stats.error_count>=max_error_count)
Packit 8f70b4
	 goto pre_FINISHING;
Packit 8f70b4
      while(transfer_count
Packit 8f70b4
      {
Packit 8f70b4
	 file=same->curr();
Packit 8f70b4
	 same->next();
Packit 8f70b4
	 if(!file)
Packit 8f70b4
	    goto pre_FINISHING;
Packit 8f70b4
	 if(file->TypeIs(file->DIRECTORY))
Packit 8f70b4
	    continue;
Packit 8f70b4
	 if(script)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV args("rm");
Packit 8f70b4
	    args.Append(source_session->GetFileURL(file->name));
Packit 8f70b4
	    xstring_ca cmd(args.CombineQuoted());
Packit 8f70b4
	    fprintf(script,"%s\n",cmd.get());
Packit 8f70b4
	 }
Packit 8f70b4
	 if(!script_only)
Packit 8f70b4
	 {
Packit 8f70b4
	    ArgV *args=new ArgV("rm");
Packit 8f70b4
	    args->Append(dir_file(".",file->name));
Packit 8f70b4
	    args->seek(1);
Packit 8f70b4
	    rmJob *j=new rmJob(source_session->Clone(),args);
Packit 8f70b4
	    args->CombineTo(j->cmdline);
Packit 8f70b4
	    JobStarted(j);
Packit 8f70b4
	 }
Packit 8f70b4
	 const char *source_name_rel=dir_file(source_relative_dir,file->name);
Packit 8f70b4
	 Report(_("Removing source file `%s'"),source_name_rel);
Packit 8f70b4
      }
Packit 8f70b4
      break;
Packit 8f70b4
Packit 8f70b4
   case(LAST_EXEC):
Packit 8f70b4
      while((j=FindDoneAwaitedJob())!=0)
Packit 8f70b4
      {
Packit 8f70b4
	 RemoveWaiting(j);
Packit 8f70b4
	 Delete(j);
Packit 8f70b4
	 m=MOVED;
Packit 8f70b4
      }
Packit 8f70b4
      if(waiting_num>0)
Packit 8f70b4
	 break;
Packit 8f70b4
   pre_DONE:
Packit 8f70b4
      set_state(DONE);
Packit 8f70b4
      m=MOVED;
Packit 8f70b4
      bytes_transferred=0;
Packit 8f70b4
      if(!parent_mirror && FlagSet(LOOP) && stats.HaveSomethingDone(flags) && !stats.error_count)
Packit 8f70b4
      {
Packit 8f70b4
	 PrintStatus(0,"");
Packit 8f70b4
	 printf(_("Retrying mirror...\n"));
Packit 8f70b4
	 stats.Reset();
Packit 8f70b4
	 source_set=0;
Packit 8f70b4
	 target_set=0;
Packit 8f70b4
	 goto pre_GETTING_LIST_INFO;
Packit 8f70b4
      }
Packit 8f70b4
      /*fallthrough*/
Packit 8f70b4
   case(DONE):
Packit 8f70b4
      break;
Packit 8f70b4
   }
Packit 8f70b4
   // give direct parent priority over grand-parents.
Packit 8f70b4
   if(transfer_count
Packit 8f70b4
      m|=parent_mirror->Roll();
Packit 8f70b4
   return m;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
MirrorJob::MirrorJob(MirrorJob *parent,
Packit 8f70b4
   FileAccess *source,FileAccess *target,
Packit 8f70b4
   const char *new_source_dir,const char *new_target_dir)
Packit 8f70b4
 :
Packit 8f70b4
   bytes_transferred(0), bytes_to_transfer(0),
Packit 8f70b4
   source_dir(new_source_dir), target_dir(new_target_dir),
Packit 8f70b4
   transfer_time_elapsed(0), root_transfer_count(0),
Packit 8f70b4
   verbose_report(0),
Packit 8f70b4
   parent_mirror(parent), root_mirror(parent?parent->root_mirror:this)
Packit 8f70b4
{
Packit 8f70b4
Packit 8f70b4
   source_session=source;
Packit 8f70b4
   target_session=target;
Packit 8f70b4
   // TODO: get rid of this.
Packit 8f70b4
   source_is_local=!strcmp(source_session->GetProto(),"file");
Packit 8f70b4
   target_is_local=!strcmp(target_session->GetProto(),"file");
Packit 8f70b4
Packit 8f70b4
   create_target_dir=true;
Packit 8f70b4
   no_target_dir=false;
Packit 8f70b4
   remove_this_source_dir=false;
Packit 8f70b4
Packit 8f70b4
   flags=0;
Packit 8f70b4
   recursion_mode=RECURSION_ALWAYS;
Packit 8f70b4
   max_error_count=0;
Packit 8f70b4
Packit 8f70b4
   exclude=0;
Packit 8f70b4
Packit 8f70b4
   set_state(INITIAL_STATE);
Packit 8f70b4
Packit 8f70b4
   newer_than=NO_DATE;
Packit 8f70b4
   older_than=NO_DATE;
Packit 8f70b4
   size_range=0;
Packit 8f70b4
Packit 8f70b4
   script=0;
Packit 8f70b4
   script_only=false;
Packit 8f70b4
   script_needs_closing=false;
Packit 8f70b4
Packit 8f70b4
   use_cache=false;
Packit 8f70b4
   remove_source_files=false;
Packit 8f70b4
   remove_source_dirs=false;
Packit 8f70b4
   skip_noaccess=false;
Packit 8f70b4
Packit 8f70b4
   parallel=1;
Packit 8f70b4
   pget_n=1;
Packit 8f70b4
   pget_minchunk=0x10000;
Packit 8f70b4
Packit 8f70b4
   source_redirections=0;
Packit 8f70b4
   target_redirections=0;
Packit 8f70b4
Packit 8f70b4
   if(parent_mirror)
Packit 8f70b4
   {
Packit 8f70b4
      bool parallel_dirs=ResMgr::QueryBool("mirror:parallel-directories",0);
Packit 8f70b4
      // If parallel_dirs is true, allow parent mirror to continue
Packit 8f70b4
      // processing other directories, otherwise block it until we
Packit 8f70b4
      // get file sets and start transfers.
Packit 8f70b4
      // See also comment at MirrorJob::MirrorStarted().
Packit 8f70b4
      root_transfer_count=parallel_dirs?1:1024;
Packit 8f70b4
Packit 8f70b4
      // inherit flags and other things
Packit 8f70b4
      SetFlags(parent->flags,1);
Packit 8f70b4
      UseCache(parent->use_cache);
Packit 8f70b4
Packit 8f70b4
      SetExclude(parent->exclude);
Packit 8f70b4
Packit 8f70b4
      verbose_report=parent->verbose_report;
Packit 8f70b4
      newer_than=parent->newer_than;
Packit 8f70b4
      older_than=parent->older_than;
Packit 8f70b4
      size_range=parent->size_range;
Packit 8f70b4
      parallel=parent->parallel;
Packit 8f70b4
      pget_n=parent->pget_n;
Packit 8f70b4
      pget_minchunk=parent->pget_minchunk;
Packit 8f70b4
      remove_source_files=parent->remove_source_files;
Packit 8f70b4
      remove_source_dirs=parent->remove_source_dirs;
Packit 8f70b4
      skip_noaccess=parent->skip_noaccess;
Packit 8f70b4
      no_target_dir=parent->no_target_dir;
Packit 8f70b4
      recursion_mode=parent->recursion_mode;
Packit 8f70b4
Packit 8f70b4
      script=parent->script;
Packit 8f70b4
      script_needs_closing=false;
Packit 8f70b4
      script_name.set(parent->script_name);
Packit 8f70b4
      script_only=parent->script_only;
Packit 8f70b4
Packit 8f70b4
      max_error_count=parent->max_error_count;
Packit 8f70b4
   }
Packit 8f70b4
   MirrorStarted();
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
MirrorJob::~MirrorJob()
Packit 8f70b4
{
Packit 8f70b4
   if(script && script_needs_closing)
Packit 8f70b4
      fclose(script);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void MirrorJob::va_Report(const char *fmt,va_list v)
Packit 8f70b4
{
Packit 8f70b4
   if(parent_mirror)
Packit 8f70b4
   {
Packit 8f70b4
      parent_mirror->va_Report(fmt,v);
Packit 8f70b4
      return;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(verbose_report)
Packit 8f70b4
   {
Packit 8f70b4
      pid_t p=tcgetpgrp(fileno(stdout));
Packit 8f70b4
      if(p>0 && p!=getpgrp())
Packit 8f70b4
	 return;
Packit 8f70b4
Packit 8f70b4
      vfprintf(stdout,fmt,v);
Packit 8f70b4
      printf("\n");
Packit 8f70b4
      fflush(stdout);
Packit 8f70b4
   }
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void MirrorJob::Report(const char *fmt,...)
Packit 8f70b4
{
Packit 8f70b4
   va_list v;
Packit 8f70b4
   va_start(v,fmt);
Packit 8f70b4
Packit 8f70b4
   va_Report(fmt,v);
Packit 8f70b4
Packit 8f70b4
   va_end(v);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
extern "C" {
Packit 8f70b4
#include "parse-datetime.h"
Packit 8f70b4
}
Packit 8f70b4
void MirrorJob::SetNewerThan(const char *f)
Packit 8f70b4
{
Packit 8f70b4
   struct timespec ts;
Packit 8f70b4
   if(parse_datetime(&ts,f,0))
Packit 8f70b4
   {
Packit 8f70b4
      newer_than=ts.tv_sec;
Packit 8f70b4
      return;
Packit 8f70b4
   }
Packit 8f70b4
   struct stat st;
Packit 8f70b4
   if(stat(f,&st)==-1)
Packit 8f70b4
   {
Packit 8f70b4
      perror(f);
Packit 8f70b4
      return;
Packit 8f70b4
   }
Packit 8f70b4
   newer_than=st.st_mtime;
Packit 8f70b4
}
Packit 8f70b4
void MirrorJob::SetOlderThan(const char *f)
Packit 8f70b4
{
Packit 8f70b4
   struct timespec ts;
Packit 8f70b4
   if(parse_datetime(&ts,f,0))
Packit 8f70b4
   {
Packit 8f70b4
      older_than=ts.tv_sec;
Packit 8f70b4
      return;
Packit 8f70b4
   }
Packit 8f70b4
   struct stat st;
Packit 8f70b4
   if(stat(f,&st)==-1)
Packit 8f70b4
   {
Packit 8f70b4
      perror(f);
Packit 8f70b4
      return;
Packit 8f70b4
   }
Packit 8f70b4
   older_than=st.st_mtime;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
mode_t MirrorJob::get_mode_mask()
Packit 8f70b4
{
Packit 8f70b4
   mode_t mode_mask=0;
Packit 8f70b4
   if(!FlagSet(ALLOW_SUID))
Packit 8f70b4
      mode_mask|=S_ISUID|S_ISGID;
Packit 8f70b4
   if(!FlagSet(NO_UMASK))
Packit 8f70b4
   {
Packit 8f70b4
      if(target_is_local)
Packit 8f70b4
      {
Packit 8f70b4
	 mode_t u=umask(022); // get+set
Packit 8f70b4
	 umask(u);	      // retore
Packit 8f70b4
	 mode_mask|=u;
Packit 8f70b4
      }
Packit 8f70b4
      else
Packit 8f70b4
	 mode_mask|=022;   // sane default.
Packit 8f70b4
   }
Packit 8f70b4
   return mode_mask;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void MirrorJob::Fg()
Packit 8f70b4
{
Packit 8f70b4
   Job::Fg();
Packit 8f70b4
   source_session->SetPriority(1);
Packit 8f70b4
   target_session->SetPriority(1);
Packit 8f70b4
}
Packit 8f70b4
void MirrorJob::Bg()
Packit 8f70b4
{
Packit 8f70b4
   source_session->SetPriority(0);
Packit 8f70b4
   target_session->SetPriority(0);
Packit 8f70b4
   Job::Bg();
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
MirrorJob::Statistics::Statistics()
Packit 8f70b4
{
Packit 8f70b4
   Reset();
Packit 8f70b4
   error_count=0;
Packit 8f70b4
   bytes=0;
Packit 8f70b4
   time=0;
Packit 8f70b4
}
Packit 8f70b4
void MirrorJob::Statistics::Reset()
Packit 8f70b4
{
Packit 8f70b4
   tot_files=new_files=mod_files=del_files=
Packit 8f70b4
   tot_symlinks=new_symlinks=mod_symlinks=del_symlinks=
Packit 8f70b4
   dirs=del_dirs=0;
Packit 8f70b4
}
Packit 8f70b4
void MirrorJob::Statistics::Add(const Statistics &s)
Packit 8f70b4
{
Packit 8f70b4
   tot_files   +=s.tot_files;
Packit 8f70b4
   new_files   +=s.new_files;
Packit 8f70b4
   mod_files   +=s.mod_files;
Packit 8f70b4
   del_files   +=s.del_files;
Packit 8f70b4
   tot_symlinks+=s.tot_symlinks;
Packit 8f70b4
   new_symlinks+=s.new_symlinks;
Packit 8f70b4
   mod_symlinks+=s.mod_symlinks;
Packit 8f70b4
   del_symlinks+=s.del_symlinks;
Packit 8f70b4
   dirs        +=s.dirs;
Packit 8f70b4
   del_dirs    +=s.del_dirs;
Packit 8f70b4
   error_count +=s.error_count;
Packit 8f70b4
   bytes       +=s.bytes;
Packit 8f70b4
   time	       +=s.time;
Packit 8f70b4
}
Packit 8f70b4
bool MirrorJob::Statistics::HaveSomethingDone(unsigned flags)
Packit 8f70b4
{
Packit 8f70b4
   bool del=(flags&MirrorJob::DELETE);
Packit 8f70b4
   return new_files|mod_files|(del_files*del)|new_symlinks|mod_symlinks|(del_symlinks*del)|(del_dirs*del);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
const char *MirrorJob::SetScriptFile(const char *n)
Packit 8f70b4
{
Packit 8f70b4
   script_name.set(n);
Packit 8f70b4
   if(strcmp(n,"-"))
Packit 8f70b4
   {
Packit 8f70b4
      script=fopen(n,"w");
Packit 8f70b4
      if(!script)
Packit 8f70b4
	 return xstring::format("%s: %s",n,strerror(errno));
Packit 8f70b4
      setvbuf(script,NULL,_IOLBF,0);
Packit 8f70b4
      script_needs_closing=true;
Packit 8f70b4
   }
Packit 8f70b4
   else
Packit 8f70b4
   {
Packit 8f70b4
      script=stdout;
Packit 8f70b4
      script_needs_closing=false;
Packit 8f70b4
   }
Packit 8f70b4
   return 0;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
void MirrorJob::SetOnChange(const char *oc)
Packit 8f70b4
{
Packit 8f70b4
   on_change.set(oc);
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
const char *MirrorJob::AddPattern(Ref<PatternSet>& exclude,char opt,const char *optarg)
Packit 8f70b4
{
Packit 8f70b4
   PatternSet::Type type=
Packit 8f70b4
      (opt=='x'||opt=='X'||opt=='\0'?PatternSet::EXCLUDE:PatternSet::INCLUDE);
Packit 8f70b4
   PatternSet::Pattern *pattern=0;
Packit 8f70b4
   if(opt=='x' || opt=='i')
Packit 8f70b4
   {
Packit 8f70b4
      Ref<PatternSet::Regex> rx(new PatternSet::Regex(optarg));
Packit 8f70b4
      if(rx->Error())
Packit 8f70b4
	 return xstring::get_tmp(rx->ErrorText());
Packit 8f70b4
      pattern=rx.borrow();
Packit 8f70b4
   }
Packit 8f70b4
   else if(opt=='X' || opt=='I')
Packit 8f70b4
   {
Packit 8f70b4
      pattern=new PatternSet::Glob(optarg);
Packit 8f70b4
   }
Packit 8f70b4
   if(!exclude)
Packit 8f70b4
   {
Packit 8f70b4
      const char *default_exclude=ResMgr::Query("mirror:exclude-regex",0);
Packit 8f70b4
      const char *default_include=ResMgr::Query("mirror:include-regex",0);
Packit 8f70b4
Packit 8f70b4
      // don't create default pattern set if not needed
Packit 8f70b4
      if(!pattern && !(default_exclude && *default_exclude))
Packit 8f70b4
	 return NULL;
Packit 8f70b4
Packit 8f70b4
      exclude=new PatternSet;
Packit 8f70b4
      /* Make default_exclude the first pattern so that it can be
Packit 8f70b4
       * overridden by --include later, and do that only when first
Packit 8f70b4
       * explicit pattern is for exclusion - otherwise all files are
Packit 8f70b4
       * excluded by default and no default exclusion is needed. */
Packit 8f70b4
      if(type==PatternSet::EXCLUDE && default_exclude && *default_exclude)
Packit 8f70b4
      {
Packit 8f70b4
	 exclude->Add(type,new PatternSet::Regex(default_exclude));
Packit 8f70b4
	 if(default_include && *default_include)
Packit 8f70b4
	    exclude->Add(PatternSet::INCLUDE,new PatternSet::Regex(default_include));
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
   if(pattern)
Packit 8f70b4
      exclude->Add(type,pattern);
Packit 8f70b4
Packit 8f70b4
   return NULL; // no error
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
const char *MirrorJob::AddPatternsFrom(Ref<PatternSet>& exclude,char opt,const char *file)
Packit 8f70b4
{
Packit 8f70b4
   FILE *f=fopen(file,"r");
Packit 8f70b4
   if(!f)
Packit 8f70b4
      return xstring::format("%s: %s",file,strerror(errno));
Packit 8f70b4
Packit 8f70b4
   xstring line;
Packit 8f70b4
   const char *err=0;
Packit 8f70b4
   int c;
Packit 8f70b4
   while(!feof(f)) {
Packit 8f70b4
      line.truncate();
Packit 8f70b4
      while((c=getc(f))!=EOF && c!='\n')
Packit 8f70b4
	 line.append(c);
Packit 8f70b4
      if(line.length()>0) {
Packit 8f70b4
	 err=AddPattern(exclude,opt,line);
Packit 8f70b4
	 if(err)
Packit 8f70b4
	    break;
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   fclose(f);
Packit 8f70b4
   return err;
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
const char *MirrorJob::SetRecursionMode(const char *m)
Packit 8f70b4
{
Packit 8f70b4
   struct { const char name[8]; recursion_mode_t mode; } map[]={
Packit 8f70b4
      {"always", RECURSION_ALWAYS},
Packit 8f70b4
      {"never",  RECURSION_NEVER},
Packit 8f70b4
      {"missing",RECURSION_MISSING},
Packit 8f70b4
      {"newer",  RECURSION_NEWER},
Packit 8f70b4
   };
Packit 8f70b4
   unsigned i;
Packit 8f70b4
   for(i=0; i
Packit 8f70b4
      if(!strcasecmp(m,map[i].name)) {
Packit 8f70b4
	 recursion_mode=map[i].mode;
Packit 8f70b4
	 return 0;
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
   xstring list(map[0].name);
Packit 8f70b4
   for(i=1; i
Packit 8f70b4
      list.append(", ").append(map[i].name);
Packit 8f70b4
   return xstring::format(_("%s must be one of: %s"),"--recursion",list.get());
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
CMD(mirror)
Packit 8f70b4
{
Packit 8f70b4
#define args (parent->args)
Packit 8f70b4
#define eprintf parent->eprintf
Packit 8f70b4
   enum {
Packit 8f70b4
      OPT_ALLOW_CHOWN,
Packit 8f70b4
      OPT_DELETE_FIRST,
Packit 8f70b4
      OPT_IGNORE_SIZE,
Packit 8f70b4
      OPT_IGNORE_TIME,
Packit 8f70b4
      OPT_LOOP,
Packit 8f70b4
      OPT_MAX_ERRORS,
Packit 8f70b4
      OPT_NO_DEREFERENCE,
Packit 8f70b4
      OPT_NO_SYMLINKS,
Packit 8f70b4
      OPT_NO_UMASK,
Packit 8f70b4
      OPT_OLDER_THAN,
Packit 8f70b4
      OPT_ONLY_MISSING,
Packit 8f70b4
      OPT_ONLY_EXISTING,
Packit 8f70b4
      OPT_PERMS,
Packit 8f70b4
      OPT_REMOVE_SOURCE_FILES,
Packit 8f70b4
      OPT_REMOVE_SOURCE_DIRS,
Packit 8f70b4
      OPT_SCRIPT,
Packit 8f70b4
      OPT_SCRIPT_ONLY,
Packit 8f70b4
      OPT_SIZE_RANGE,
Packit 8f70b4
      OPT_USE_CACHE,
Packit 8f70b4
      OPT_USE_PGET_N,
Packit 8f70b4
      OPT_SKIP_NOACCESS,
Packit 8f70b4
      OPT_ON_CHANGE,
Packit 8f70b4
      OPT_NO_EMPTY_DIRS,
Packit 8f70b4
      OPT_DEPTH_FIRST,
Packit 8f70b4
      OPT_ASCII,
Packit 8f70b4
      OPT_SCAN_ALL_FIRST,
Packit 8f70b4
      OPT_OVERWRITE,
Packit 8f70b4
      OPT_NO_OVERWRITE,
Packit 8f70b4
      OPT_RECURSION,
Packit 8f70b4
      OPT_UPLOAD_OLDER,
Packit 8f70b4
      OPT_TRANSFER_ALL,
Packit 8f70b4
      OPT_TARGET_FLAT,
Packit 8f70b4
      OPT_DELETE_EXCLUDED,
Packit 8f70b4
   };
Packit 8f70b4
   static const struct option mirror_opts[]=
Packit 8f70b4
   {
Packit 8f70b4
      {"delete",no_argument,0,'e'},
Packit 8f70b4
      {"allow-suid",no_argument,0,'s'},
Packit 8f70b4
      {"allow-chown",no_argument,0,OPT_ALLOW_CHOWN},
Packit 8f70b4
      {"include",required_argument,0,'i'},
Packit 8f70b4
      {"exclude",required_argument,0,'x'},
Packit 8f70b4
      {"include-glob",required_argument,0,'I'},
Packit 8f70b4
      {"exclude-glob",required_argument,0,'X'},
Packit 8f70b4
      {"include-rx-from",required_argument,0,'i'+'f'},
Packit 8f70b4
      {"exclude-rx-from",required_argument,0,'x'+'f'},
Packit 8f70b4
      {"include-glob-from",required_argument,0,'I'+'f'},
Packit 8f70b4
      {"exclude-glob-from",required_argument,0,'X'+'f'},
Packit 8f70b4
      {"only-newer",no_argument,0,'n'},
Packit 8f70b4
      {"no-recursion",no_argument,0,'r'},
Packit 8f70b4
      {"no-perms",no_argument,0,'p'},
Packit 8f70b4
      {"perms",no_argument,0,OPT_PERMS},
Packit 8f70b4
      {"no-umask",no_argument,0,OPT_NO_UMASK},
Packit 8f70b4
      {"continue",no_argument,0,'c'},
Packit 8f70b4
      {"reverse",no_argument,0,'R'},
Packit 8f70b4
      {"verbose",optional_argument,0,'v'},
Packit 8f70b4
      {"newer-than",required_argument,0,'N'},
Packit 8f70b4
      {"file",required_argument,0,'f'},
Packit 8f70b4
      {"directory",required_argument,0,'F'},
Packit 8f70b4
      {"older-than",required_argument,0,OPT_OLDER_THAN},
Packit 8f70b4
      {"size-range",required_argument,0,OPT_SIZE_RANGE},
Packit 8f70b4
      {"dereference",no_argument,0,'L'},
Packit 8f70b4
      {"no-dereference",no_argument,0,OPT_NO_DEREFERENCE},
Packit 8f70b4
      {"use-cache",no_argument,0,OPT_USE_CACHE},
Packit 8f70b4
      {"Remove-source-files",no_argument,0,OPT_REMOVE_SOURCE_FILES},
Packit 8f70b4
      {"Remove-source-dirs",no_argument,0,OPT_REMOVE_SOURCE_DIRS},
Packit 8f70b4
      {"Move",no_argument,0,OPT_REMOVE_SOURCE_DIRS},
Packit 8f70b4
      {"parallel",optional_argument,0,'P'},
Packit 8f70b4
      {"ignore-time",no_argument,0,OPT_IGNORE_TIME},
Packit 8f70b4
      {"ignore-size",no_argument,0,OPT_IGNORE_SIZE},
Packit 8f70b4
      {"only-missing",no_argument,0,OPT_ONLY_MISSING},
Packit 8f70b4
      {"only-existing",no_argument,0,OPT_ONLY_EXISTING},
Packit 8f70b4
      {"log",required_argument,0,OPT_SCRIPT},
Packit 8f70b4
      {"script",    required_argument,0,OPT_SCRIPT_ONLY},
Packit 8f70b4
      {"just-print",optional_argument,0,OPT_SCRIPT_ONLY},
Packit 8f70b4
      {"dry-run",   optional_argument,0,OPT_SCRIPT_ONLY},
Packit 8f70b4
      {"delete-first",no_argument,0,OPT_DELETE_FIRST},
Packit 8f70b4
      {"use-pget-n",optional_argument,0,OPT_USE_PGET_N},
Packit 8f70b4
      {"no-symlinks",no_argument,0,OPT_NO_SYMLINKS},
Packit 8f70b4
      {"loop",no_argument,0,OPT_LOOP},
Packit 8f70b4
      {"max-errors",required_argument,0,OPT_MAX_ERRORS},
Packit 8f70b4
      {"skip-noaccess",no_argument,0,OPT_SKIP_NOACCESS},
Packit 8f70b4
      {"on-change",required_argument,0,OPT_ON_CHANGE},
Packit 8f70b4
      {"no-empty-dirs",no_argument,0,OPT_NO_EMPTY_DIRS},
Packit 8f70b4
      {"depth-first",no_argument,0,OPT_DEPTH_FIRST},
Packit 8f70b4
      {"ascii",no_argument,0,OPT_ASCII},
Packit 8f70b4
      {"target-directory",required_argument,0,'O'},
Packit 8f70b4
      {"destination-directory",required_argument,0,'O'},
Packit 8f70b4
      {"scan-all-first",no_argument,0,OPT_SCAN_ALL_FIRST},
Packit 8f70b4
      {"overwrite",no_argument,0,OPT_OVERWRITE},
Packit 8f70b4
      {"no-overwrite",no_argument,0,OPT_NO_OVERWRITE},
Packit 8f70b4
      {"recursion",required_argument,0,OPT_RECURSION},
Packit 8f70b4
      {"upload-older",no_argument,0,OPT_UPLOAD_OLDER},
Packit 8f70b4
      {"transfer-all",no_argument,0,OPT_TRANSFER_ALL},
Packit 8f70b4
      {"flat",no_argument,0,OPT_TARGET_FLAT},
Packit 8f70b4
      {"delete-excluded",no_argument,0,OPT_DELETE_EXCLUDED},
Packit 8f70b4
      {0}
Packit 8f70b4
   };
Packit 8f70b4
Packit 8f70b4
   int opt;
Packit 8f70b4
   unsigned flags=0;
Packit 8f70b4
   int max_error_count=0;
Packit 8f70b4
Packit 8f70b4
   bool use_cache=false;
Packit 8f70b4
Packit 8f70b4
   FileAccessRef source_session;
Packit 8f70b4
   FileAccessRef target_session;
Packit 8f70b4
Packit 8f70b4
   int	 verbose=0;
Packit 8f70b4
   const char *newer_than=0;
Packit 8f70b4
   const char *older_than=0;
Packit 8f70b4
   Ref<Range> size_range;
Packit 8f70b4
   bool  remove_source_files=false;
Packit 8f70b4
   bool  remove_source_dirs=false;
Packit 8f70b4
   bool	 skip_noaccess=ResMgr::QueryBool("mirror:skip-noaccess",0);
Packit 8f70b4
   int	 parallel=-1;
Packit 8f70b4
   int	 use_pget=-1;
Packit 8f70b4
   bool	 reverse=false;
Packit 8f70b4
   bool	 script_only=false;
Packit 8f70b4
   bool	 no_empty_dirs=ResMgr::QueryBool("mirror:no-empty-dirs",0);
Packit 8f70b4
   const char *script_file=0;
Packit 8f70b4
   const char *on_change=0;
Packit 8f70b4
   const char *recursion_mode=0;
Packit 8f70b4
   bool single_file=false;
Packit 8f70b4
   bool single_dir=false;
Packit 8f70b4
Packit 8f70b4
   Ref<PatternSet> exclude;
Packit 8f70b4
   Ref<PatternSet> top_exclude;
Packit 8f70b4
Packit 8f70b4
   if(!ResMgr::QueryBool("mirror:set-permissions",0))
Packit 8f70b4
      flags|=MirrorJob::NO_PERMS;
Packit 8f70b4
   if(ResMgr::QueryBool("mirror:dereference",0))
Packit 8f70b4
      flags|=MirrorJob::RETR_SYMLINKS;
Packit 8f70b4
   if(ResMgr::QueryBool("mirror:overwrite",0))
Packit 8f70b4
      flags|=MirrorJob::OVERWRITE;
Packit 8f70b4
Packit 8f70b4
   const char *source_dir=NULL;
Packit 8f70b4
   const char *target_dir=NULL;
Packit 8f70b4
Packit 8f70b4
   args->rewind();
Packit 8f70b4
   while((opt=args->getopt_long("esi:x:I:X:nrpcRvN:LP:af:F:O:",mirror_opts,0))!=EOF)
Packit 8f70b4
   {
Packit 8f70b4
      switch(opt)
Packit 8f70b4
      {
Packit 8f70b4
      case('e'):
Packit 8f70b4
	 flags|=MirrorJob::DELETE;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('s'):
Packit 8f70b4
	 flags|=MirrorJob::ALLOW_SUID;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_ALLOW_CHOWN):
Packit 8f70b4
	 flags|=MirrorJob::ALLOW_CHOWN;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('a'):
Packit 8f70b4
	 flags|=MirrorJob::ALLOW_SUID|MirrorJob::ALLOW_CHOWN|MirrorJob::NO_UMASK;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('r'):
Packit 8f70b4
	 recursion_mode="never";
Packit 8f70b4
	 break;
Packit 8f70b4
      case('n'):
Packit 8f70b4
	 flags|=MirrorJob::ONLY_NEWER;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('p'):
Packit 8f70b4
	 flags|=MirrorJob::NO_PERMS;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_PERMS):
Packit 8f70b4
	 flags&=~MirrorJob::NO_PERMS;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('c'):
Packit 8f70b4
	 flags|=MirrorJob::CONTINUE;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('x'):
Packit 8f70b4
      case('i'):
Packit 8f70b4
      case('X'):
Packit 8f70b4
      case('I'):
Packit 8f70b4
      {
Packit 8f70b4
	 const char *err=MirrorJob::AddPattern(exclude,opt,optarg);
Packit 8f70b4
	 if(err)
Packit 8f70b4
	 {
Packit 8f70b4
	    eprintf("%s: %s\n",args->a0(),err);
Packit 8f70b4
	    goto no_job;
Packit 8f70b4
	 }
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
      case('x'+'f'):
Packit 8f70b4
      case('i'+'f'):
Packit 8f70b4
      case('X'+'f'):
Packit 8f70b4
      case('I'+'f'):
Packit 8f70b4
      {
Packit 8f70b4
	 const char *err=MirrorJob::AddPatternsFrom(exclude,opt-'f',optarg);
Packit 8f70b4
	 if(err)
Packit 8f70b4
	 {
Packit 8f70b4
	    eprintf("%s: %s\n",args->a0(),err);
Packit 8f70b4
	    goto no_job;
Packit 8f70b4
	 }
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
      case('R'):
Packit 8f70b4
	 reverse=true;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('L'):
Packit 8f70b4
	 flags|=MirrorJob::RETR_SYMLINKS;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_NO_DEREFERENCE):
Packit 8f70b4
	 flags&=~MirrorJob::RETR_SYMLINKS;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('v'):
Packit 8f70b4
	 if(optarg)
Packit 8f70b4
	    verbose=atoi(optarg);
Packit 8f70b4
	 else
Packit 8f70b4
	    verbose++;
Packit 8f70b4
	 if(verbose>1)
Packit 8f70b4
	    flags|=MirrorJob::REPORT_NOT_DELETED;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('N'):
Packit 8f70b4
	 newer_than=optarg;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('f'): // mirror for a single file (or glob pattern).
Packit 8f70b4
	 single_file=true;
Packit 8f70b4
	 /*fallthrough*/
Packit 8f70b4
      case('F'): // mirror for a single directory (or glob pattern).
Packit 8f70b4
      {
Packit 8f70b4
	 xstring pattern(basename_ptr(optarg));
Packit 8f70b4
	 if(opt=='F') {
Packit 8f70b4
	    single_dir=true;
Packit 8f70b4
	    if(pattern.last_char()!='/')
Packit 8f70b4
	       pattern.append('/');
Packit 8f70b4
	 }
Packit 8f70b4
	 if(!top_exclude)
Packit 8f70b4
	    top_exclude=new PatternSet();
Packit 8f70b4
	 top_exclude->Add(PatternSet::INCLUDE,new PatternSet::Glob(pattern));
Packit 8f70b4
	 const char *dir=dirname(optarg);
Packit 8f70b4
	 if(source_dir && strcmp(source_dir,dir)) {
Packit 8f70b4
	    eprintf(_("%s: multiple --file or --directory options must have the same base directory\n"),args->a0());
Packit 8f70b4
	    goto no_job;
Packit 8f70b4
	 }
Packit 8f70b4
	 if(!source_dir)
Packit 8f70b4
	    source_dir=alloca_strdup(dir); // save the temp string
Packit 8f70b4
	 break;
Packit 8f70b4
      }
Packit 8f70b4
      case('O'):
Packit 8f70b4
	 target_dir=optarg;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_OLDER_THAN):
Packit 8f70b4
	 older_than=optarg;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_SIZE_RANGE):
Packit 8f70b4
	 size_range=new Range(optarg);
Packit 8f70b4
	 if(size_range->Error())
Packit 8f70b4
	 {
Packit 8f70b4
	    eprintf("%s: --size-range \"%s\": %s\n",
Packit 8f70b4
	       args->a0(),optarg,size_range->ErrorText());
Packit 8f70b4
	    goto no_job;
Packit 8f70b4
	 }
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_NO_UMASK):
Packit 8f70b4
	 flags|=MirrorJob::NO_UMASK;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_USE_CACHE):
Packit 8f70b4
	 use_cache=true;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_REMOVE_SOURCE_FILES):
Packit 8f70b4
	 remove_source_files=true;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_REMOVE_SOURCE_DIRS):
Packit 8f70b4
	 remove_source_dirs=true;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_IGNORE_TIME):
Packit 8f70b4
	 flags|=MirrorJob::IGNORE_TIME;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_IGNORE_SIZE):
Packit 8f70b4
	 flags|=MirrorJob::IGNORE_SIZE;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_ONLY_MISSING):
Packit 8f70b4
	 flags|=MirrorJob::IGNORE_TIME|MirrorJob::IGNORE_SIZE;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('P'):
Packit 8f70b4
	 if(optarg)
Packit 8f70b4
	    parallel=atoi(optarg);
Packit 8f70b4
	 else
Packit 8f70b4
	    parallel=3;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_USE_PGET_N):
Packit 8f70b4
	 if(optarg)
Packit 8f70b4
	    use_pget=atoi(optarg);
Packit 8f70b4
	 else
Packit 8f70b4
	    use_pget=3;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_SCRIPT_ONLY):
Packit 8f70b4
	 script_only=true;
Packit 8f70b4
      case(OPT_SCRIPT):
Packit 8f70b4
	 script_file=optarg;
Packit 8f70b4
	 if(script_file==0)
Packit 8f70b4
	    script_file="-";
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_DELETE_FIRST):
Packit 8f70b4
	 flags|=MirrorJob::REMOVE_FIRST|MirrorJob::DELETE;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_NO_SYMLINKS):
Packit 8f70b4
	 flags|=MirrorJob::NO_SYMLINKS;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_LOOP):
Packit 8f70b4
	 flags|=MirrorJob::LOOP;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_MAX_ERRORS):
Packit 8f70b4
	 max_error_count=atoi(optarg);
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_SKIP_NOACCESS):
Packit 8f70b4
	 skip_noaccess=true;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_ON_CHANGE):
Packit 8f70b4
	 on_change=optarg;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_ONLY_EXISTING):
Packit 8f70b4
	 flags|=MirrorJob::ONLY_EXISTING;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_NO_EMPTY_DIRS):
Packit 8f70b4
	 no_empty_dirs=true;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_DEPTH_FIRST):
Packit 8f70b4
	 flags|=MirrorJob::DEPTH_FIRST;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_SCAN_ALL_FIRST):
Packit 8f70b4
	 flags|=MirrorJob::SCAN_ALL_FIRST|MirrorJob::DEPTH_FIRST;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_ASCII):
Packit 8f70b4
	 flags|=MirrorJob::ASCII|MirrorJob::IGNORE_SIZE;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_OVERWRITE):
Packit 8f70b4
	 flags|=MirrorJob::OVERWRITE;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_NO_OVERWRITE):
Packit 8f70b4
	 flags&=~MirrorJob::OVERWRITE;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_RECURSION):
Packit 8f70b4
	 recursion_mode=optarg;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_UPLOAD_OLDER):
Packit 8f70b4
	 flags|=MirrorJob::UPLOAD_OLDER;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_TRANSFER_ALL):
Packit 8f70b4
	 flags|=MirrorJob::TRANSFER_ALL;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_TARGET_FLAT):
Packit 8f70b4
	 flags|=MirrorJob::TARGET_FLAT|MirrorJob::SCAN_ALL_FIRST|MirrorJob::DEPTH_FIRST;
Packit 8f70b4
	 break;
Packit 8f70b4
      case(OPT_DELETE_EXCLUDED):
Packit 8f70b4
	 flags|=MirrorJob::DELETE_EXCLUDED;
Packit 8f70b4
	 break;
Packit 8f70b4
      case('?'):
Packit 8f70b4
	 eprintf(_("Try `help %s' for more information.\n"),args->a0());
Packit 8f70b4
      no_job:
Packit 8f70b4
	 return 0;
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(exclude && xstrcasecmp(recursion_mode,"never"))
Packit 8f70b4
   {
Packit 8f70b4
      /* Users usually don't want to exclude all directories when recursing */
Packit 8f70b4
      if(exclude->GetFirstType()==PatternSet::INCLUDE)
Packit 8f70b4
	 exclude->AddFirst(PatternSet::INCLUDE,new PatternSet::Regex("/$"));
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   /* add default exclusion if no explicit patterns were specified */
Packit 8f70b4
   if(!exclude)
Packit 8f70b4
      MirrorJob::AddPattern(exclude,'\0',0);
Packit 8f70b4
Packit 8f70b4
   args->back();
Packit 8f70b4
Packit 8f70b4
   const char *arg=args->getnext();
Packit 8f70b4
   if(arg)
Packit 8f70b4
   {
Packit 8f70b4
      if(source_dir)
Packit 8f70b4
      {
Packit 8f70b4
	 eprintf(_("%s: ambiguous source directory (`%s' or `%s'?)\n"),args->a0(),
Packit 8f70b4
	    source_dir,arg);
Packit 8f70b4
	 goto no_job;
Packit 8f70b4
      }
Packit 8f70b4
      source_dir=arg;
Packit 8f70b4
      ParsedURL source_url(source_dir);
Packit 8f70b4
      if(source_url.proto && source_url.path)
Packit 8f70b4
      {
Packit 8f70b4
	 source_session=FileAccess::New(&source_url);
Packit 8f70b4
	 if(!source_session)
Packit 8f70b4
	 {
Packit 8f70b4
	    eprintf("%s: %s%s\n",args->a0(),source_url.proto.get(),
Packit 8f70b4
		     _(" - not supported protocol"));
Packit 8f70b4
	    goto no_job;
Packit 8f70b4
	 }
Packit 8f70b4
	 source_dir=alloca_strdup(source_url.path);
Packit 8f70b4
      }
Packit 8f70b4
      arg=args->getnext();
Packit 8f70b4
      if(arg)
Packit 8f70b4
      {
Packit 8f70b4
	 if(target_dir)
Packit 8f70b4
	 {
Packit 8f70b4
	    eprintf(_("%s: ambiguous target directory (`%s' or `%s'?)\n"),args->a0(),
Packit 8f70b4
	       target_dir,arg);
Packit 8f70b4
	    goto no_job;
Packit 8f70b4
	 }
Packit 8f70b4
	 target_dir=arg;
Packit 8f70b4
	 ParsedURL target_url(target_dir);
Packit 8f70b4
	 if(target_url.proto && target_url.path)
Packit 8f70b4
	 {
Packit 8f70b4
	    target_session=FileAccess::New(&target_url);
Packit 8f70b4
	    if(!target_session)
Packit 8f70b4
	    {
Packit 8f70b4
	       eprintf("%s: %s%s\n",args->a0(),target_url.proto.get(),
Packit 8f70b4
			_(" - not supported protocol"));
Packit 8f70b4
	       goto no_job;
Packit 8f70b4
	    }
Packit 8f70b4
	    target_dir=alloca_strdup(target_url.path);
Packit 8f70b4
	 }
Packit 8f70b4
	 if(last_char(arg)=='/' && basename_ptr(arg)[0]!='/' && last_char(source_dir)!='/')
Packit 8f70b4
	 {
Packit 8f70b4
	    // user wants source dir name appended.
Packit 8f70b4
	    const char *base=basename_ptr(source_dir);
Packit 8f70b4
	    if(base[0]!='/' && strcmp(base,basename_ptr(arg))) {
Packit 8f70b4
	       target_dir=xstring::cat(target_dir,base,NULL);
Packit 8f70b4
	       target_dir=alloca_strdup(target_dir); // save the buffer
Packit 8f70b4
	    }
Packit 8f70b4
	 }
Packit 8f70b4
      }
Packit 8f70b4
      else
Packit 8f70b4
      {
Packit 8f70b4
	 target_dir=basename_ptr(source_dir);
Packit 8f70b4
	 if(target_dir[0]=='/')
Packit 8f70b4
	    target_dir=".";
Packit 8f70b4
	 else if(target_dir[0]=='~') {
Packit 8f70b4
	    target_dir=dir_file(".",target_dir);
Packit 8f70b4
	    target_dir=alloca_strdup(target_dir); // save the buffer
Packit 8f70b4
	 }
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(!source_dir) {
Packit 8f70b4
      if(ResMgr::QueryBool("mirror:require-source",0)) {
Packit 8f70b4
	 eprintf(_("%s: source directory is required (mirror:require-source is set)\n"),args->a0());
Packit 8f70b4
	 return 0;
Packit 8f70b4
      }
Packit 8f70b4
      source_dir=".";
Packit 8f70b4
   }
Packit 8f70b4
   if(!target_dir)
Packit 8f70b4
      target_dir=".";
Packit 8f70b4
Packit 8f70b4
   if(!reverse)
Packit 8f70b4
   {
Packit 8f70b4
      if(!source_session)
Packit 8f70b4
	 source_session=parent->session->Clone();
Packit 8f70b4
      if(!target_session)
Packit 8f70b4
	 target_session=FileAccess::New("file");
Packit 8f70b4
   }
Packit 8f70b4
   else //reverse
Packit 8f70b4
   {
Packit 8f70b4
      if(!source_session)
Packit 8f70b4
	 source_session=FileAccess::New("file");
Packit 8f70b4
      if(!target_session)
Packit 8f70b4
	 target_session=parent->session->Clone();
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(no_empty_dirs)
Packit 8f70b4
      flags|=MirrorJob::NO_EMPTY_DIRS|MirrorJob::DEPTH_FIRST;
Packit 8f70b4
Packit 8f70b4
   if(recursion_mode && strcasecmp(recursion_mode,"always")
Packit 8f70b4
   && (flags&MirrorJob::DEPTH_FIRST)) {
Packit 8f70b4
      eprintf("%s: --recursion-mode=%s conflicts with other specified options\n",
Packit 8f70b4
	 args->a0(),recursion_mode);
Packit 8f70b4
      return 0;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   if(parallel<0) {
Packit 8f70b4
      int parallel1=ResMgr::Query("mirror:parallel-transfer-count",source_session->GetHostName());
Packit 8f70b4
      int parallel2=ResMgr::Query("mirror:parallel-transfer-count",target_session->GetHostName());
Packit 8f70b4
      if(parallel1>0)
Packit 8f70b4
	 parallel=parallel1;
Packit 8f70b4
      if(parallel2>0 && (parallel<0 || parallel>parallel2))
Packit 8f70b4
	 parallel=parallel2;
Packit 8f70b4
   }
Packit 8f70b4
   if(use_pget<0) {
Packit 8f70b4
      int use_pget1=ResMgr::Query("mirror:use-pget-n",source_session->GetHostName());
Packit 8f70b4
      int use_pget2=ResMgr::Query("mirror:use-pget-n",target_session->GetHostName());
Packit 8f70b4
      if(use_pget1>0)
Packit 8f70b4
	 use_pget=use_pget1;
Packit 8f70b4
      if(use_pget2>0 && (use_pget<0 || use_pget>use_pget2))
Packit 8f70b4
	 use_pget=use_pget2;
Packit 8f70b4
   }
Packit 8f70b4
Packit 8f70b4
   JobRef<MirrorJob> j(new MirrorJob(0,source_session.borrow(),target_session.borrow(),source_dir,target_dir));
Packit 8f70b4
   j->SetFlags(flags,1);
Packit 8f70b4
   j->SetVerbose(verbose);
Packit 8f70b4
   j->SetExclude(exclude.borrow());
Packit 8f70b4
   j->SetTopExclude(top_exclude.borrow());
Packit 8f70b4
Packit 8f70b4
   if(newer_than)
Packit 8f70b4
      j->SetNewerThan(newer_than);
Packit 8f70b4
   if(older_than)
Packit 8f70b4
      j->SetOlderThan(older_than);
Packit 8f70b4
   if(size_range)
Packit 8f70b4
      j->SetSizeRange(size_range.borrow());
Packit 8f70b4
   j->UseCache(use_cache);
Packit 8f70b4
   if(remove_source_files)
Packit 8f70b4
      j->RemoveSourceFiles();
Packit 8f70b4
   if(remove_source_dirs)
Packit 8f70b4
      j->RemoveSourceDirs();
Packit 8f70b4
   if(skip_noaccess)
Packit 8f70b4
      j->SkipNoAccess();
Packit 8f70b4
   if(parallel<0)
Packit 8f70b4
      parallel=0;
Packit 8f70b4
   if(parallel>64)
Packit 8f70b4
      parallel=64;   // a (in)sane limit.
Packit 8f70b4
   if(parallel)
Packit 8f70b4
      j->SetParallel(parallel);
Packit 8f70b4
   if(use_pget>1 && !(flags&MirrorJob::ASCII))
Packit 8f70b4
      j->SetPGet(use_pget);
Packit 8f70b4
Packit 8f70b4
   if(!recursion_mode && single_file && !single_dir)
Packit 8f70b4
      recursion_mode="never";
Packit 8f70b4
Packit 8f70b4
   if(recursion_mode) {
Packit 8f70b4
      const char *err=j->SetRecursionMode(recursion_mode);
Packit 8f70b4
      if(err) {
Packit 8f70b4
	 eprintf("%s: %s\n",args->a0(),err);
Packit 8f70b4
	 return 0;
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
   if(script_file)
Packit 8f70b4
   {
Packit 8f70b4
      const char *err=j->SetScriptFile(script_file);
Packit 8f70b4
      if(err)
Packit 8f70b4
      {
Packit 8f70b4
	 eprintf("%s: %s\n",args->a0(),err);
Packit 8f70b4
	 return 0;
Packit 8f70b4
      }
Packit 8f70b4
   }
Packit 8f70b4
   if(script_only)
Packit 8f70b4
   {
Packit 8f70b4
      j->ScriptOnly();
Packit 8f70b4
      if(!script_file)
Packit 8f70b4
	 j->SetScriptFile("-");
Packit 8f70b4
   }
Packit 8f70b4
   j->SetMaxErrorCount(max_error_count);
Packit 8f70b4
   if(on_change)
Packit 8f70b4
      j->SetOnChange(on_change);
Packit 8f70b4
Packit 8f70b4
   return j.borrow();
Packit 8f70b4
Packit 8f70b4
#undef args
Packit 8f70b4
}
Packit 8f70b4
Packit 8f70b4
#include "modconfig.h"
Packit 8f70b4
#ifndef MODULE_CMD_MIRROR
Packit 8f70b4
# define module_init cmd_mirror_module_init
Packit 8f70b4
#endif
Packit 8f70b4
CDECL void module_init()
Packit 8f70b4
{
Packit 8f70b4
   CmdExec::RegisterCommand("mirror",cmd_mirror,0,
Packit 8f70b4
      N_("\n"
Packit 8f70b4
	 "Mirror specified remote directory to local directory\n"
Packit 8f70b4
	 "\n"
Packit 8f70b4
	 " -R, --reverse          reverse mirror (put files)\n"
Packit 8f70b4
	 "Lots of other options are documented in the man page lftp(1).\n"
Packit 8f70b4
	 "\n"
Packit 8f70b4
	 "When using -R, the first directory is local and the second is remote.\n"
Packit 8f70b4
	 "If the second directory is omitted, basename of the first directory is used.\n"
Packit 8f70b4
	 "If both directories are omitted, current local and remote directories are used.\n"
Packit 8f70b4
	 "\n"
Packit 8f70b4
	 "See the man page lftp(1) for a complete documentation.\n"
Packit 8f70b4
      )
Packit 8f70b4
   );
Packit 8f70b4
}