背景
linux - How to use rsync over FTP - Server Faultによると、
ftpでミラーリングするのにはrsyncは使えずlftpとncftpなら対応しているそうです。
ミラーリングするときには既に転送済みのファイルはスキップして欲しいわけですが、どういう条件のときにスキップするかをざっくり調べてみました。
lftp
lftpはファイルの更新日時とファイルサイズを転送元と転送先のファイルで比較しています。
下記のコードでファイルのタイムスタンプの構造体が定義されています。tsが通算秒でts_precが精度(単位:秒)になっています。
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を足したものを転送元と転送先のファイルで比較しています。また
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++;
}
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);
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を126060としています。hh:mmのときはprecは30秒です。
ソースの他の箇所でも状況に応じてprecの値を設定しています。
ncftp
ncftpget(1) manual pageとncftpput(1) manual pageを見ると、-Rオプションでディレクトリ以下を再帰的に転送できますが、ファイルの更新日時やサイズをチェックしてスキップしたりするわけではなく常にすべてのファイルを転送するようです。
syncftpというrubygem
一度実行するとリモートディレクトリに.syncftpというファイルを作成して、ファイルのmd5チェックサムを保存します。次回のミラーリング実行時にリモートに.syncftpファイルがある場合はそれを利用して転送が必要かを判断しています。
# 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