LoginSignup
9
6

More than 5 years have passed since last update.

ftpでミラーリングするツール(lftp, ncftp, syncftp gem)の転送スキップルールをざっくり調べてみた

Posted at

背景

linux - How to use rsync over FTP - Server Faultによると、
ftpでミラーリングするのにはrsyncは使えずlftpとncftpなら対応しているそうです。

ミラーリングするときには既に転送済みのファイルはスキップして欲しいわけですが、どういう条件のときにスキップするかをざっくり調べてみました。

lftp

lftpはファイルの更新日時とファイルサイズを転送元と転送先のファイルで比較しています。

下記のコードでファイルのタイムスタンプの構造体が定義されています。tsが通算秒でts_precが精度(単位:秒)になっています。

https
struct FileTimestamp
{     
   time_t ts;
   int ts_prec;
   FileTimestamp() : ts(NO_DATE_YET), ts_prec(0) {}
   void set(time_t ts1,int ts1_prec) { ts=ts1; ts_prec=ts1_prec; }
   bool is_set() { return ts!=NO_DATE && ts!=NO_DATE_YET; }
   operator time_t() const { return ts; }
   time_t operator=(time_t t) { set(t,0); return t; }
};

更新日時を比較しているのが以下の箇所です。file->date + file->date.ts_precのうち、file->dateはFileTimestampのoperator time_t()でtsを返していて、それとts_precを足したものを転送元と転送先のファイルで比較しています。また

https
     FileInfo *old=target_set->FindByName(file->name);
     if(old)
     {
        if((flags&CONTINUE)
        && (old->defined&file->TYPE) && old->filetype==old->NORMAL
        && (flags&IGNORE_TIME ||
            ((file->defined&file->DATE) && (old->defined&old->DATE)
            && file->date + file->date.ts_prec < old->date - old->date.ts_prec))
        && (file->defined&file->SIZE) && (old->defined&old->SIZE)
        && file->size >= old->size)
        {
               cont_this=true;
               if(target_is_local && !script_only)
               {
                  if(access(target_name,W_OK)==-1)
                  {
                     // try to enable write access.
                     chmod(target_name,st.st_mode|0200);
                  }
               }
               stats.mod_files++;
            }
https
   if(parse_year_or_time(t,&date.tm_year,&date.tm_hour,&date.tm_min)==-1)
      ERR;

   date.tm_isdst=-1;
   date.tm_sec=30;
   int prec=30;

   if(date.tm_year==-1)
      date.tm_year=guess_year(date.tm_mon,date.tm_mday,date.tm_hour,date.tm_min) - 1900;
   else
   {
      date.tm_hour=12;
      prec=12*60*60;
   }

   fi->SetDate(mktime_from_tz(&date,tz),prec);
https
int parse_year_or_time(const char *year_or_time,int *year,int *hour,int *minute)
{
   if(year_or_time[2]==':')
   {
      if(2!=sscanf(year_or_time,"%2d:%2d",hour,minute))
     return -1;
      *year=-1;
   }
   else
   {
      if(1!=sscanf(year_or_time,"%d",year))
     return -1;;
      *hour=*minute=0;
   }
   return 0;
}
int guess_year(int month,int day,int hour,int minute)
{
   const struct tm &now=SMTask::now;
   int year=now.tm_year+1900;
   if(month     *32+        day
    > now.tm_mon*32+now.tm_mday+6)
      year--;
   return year;
}

parse_year_or_time()でftpのファイルリスト情報の日付部分が"%2d:%2d"のときはhh:mm、"%d"のときはyearと判定してします。yearのときはFtpListInfo.ccに戻ってprecを12*60*60としています。hh:mmのときはprecは30秒です。

ソースの他の箇所でも状況に応じてprecの値を設定しています。

ncftp

ncftpget(1) manual pagencftpput(1) manual pageを見ると、-Rオプションでディレクトリ以下を再帰的に転送できますが、ファイルの更新日時やサイズをチェックしてスキップしたりするわけではなく常にすべてのファイルを転送するようです。

syncftpというrubygem

一度実行するとリモートディレクトリに.syncftpというファイルを作成して、ファイルのmd5チェックサムを保存します。次回のミラーリング実行時にリモートに.syncftpファイルがある場合はそれを利用して転送が必要かを判断しています。

https
      # Read remote .syncftp
      begin
        ftp.gettextfile( remote+"/"+".syncftp", tmpname )
        @remote_md5s = YAML.load( File.open( tmpname ).read )
      rescue Net::FTPPermError => e
        raise Net::FTPPermError, e.message, caller if ftp.remote_file_exist?( remote+"/"+".syncftp" )
      end

      # Do the job Bob !
      send_dir( ftp, local, remote )

      # Write new .syncftp
      File.open( tmpname, 'w' ) do |out|
        YAML.dump( @local_md5s, out )
      end
9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6