Subversion Repositories general

Rev

Rev 1153 | Rev 1205 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1149 dev 1
#!/usr/bin/perl
2
# fsbackup - file system backup and synchronization utility. 
3
#
4
# http://www.opennet.ru/dev/fsbackup/
5
# Copyright (c) 2001-2002 by Maxim Chirkov. <mc@tyumen.ru>
6
#
1153 dev 7
# Ключи:
8
# -n - создаем новый архив независимо от состояния хэша.
9
# -f - full_backup - полный бэкап в архив, без хэша.
10
# -h - hash - только генерация хэша, без помещения файлов в архив.
11
# -c - clean - очиска хранилища с инкрементальным бэкапом и создание нового бэкапа.
1149 dev 12
 
13
#############################################
1154 dev 14
use constant DB_DEF_CACHE_SIZE => 40960000; # Размер кэша для размежения хэша в памяти
1149 dev 15
 
16
use POSIX;
17
use File::Find;
18
use Digest::MD5 qw(md5_base64);
19
use Net::FTP;
20
use DB_File;
21
 
22
use constant VERB_SILENT => 0; # Silent mode, suspend all output.
23
use constant VERB_ERROR => 1; # Output all errors and warnings.
24
use constant VERB_ALL => 2; # Output all the  available  data.
25
 
1154 dev 26
my $version = "2.0";
1149 dev 27
my $list_lines_cnt = 0;
28
my $del_lines_cnt = 0;
29
my $cur_time = time();
30
my %active_hash_last;
31
my %active_hash_new;
32
my $cfg_new_flag = 0;
33
my $cfg_clean_flag = 0;
34
my $config = 0;
1153 dev 35
my $cur_backup_size = 1536; # Размер блока tar
1149 dev 36
my $backup_file_base;
37
my $prog_pgp_filter;
38
my $prog_gzip_filter;
39
my $arc_ext;
40
my $ftp;
41
my $cur_increment_level;
42
my $cur_dir;
43
my $cur_path;
44
my $cur_file;
45
my $cur_pathitem;
46
my $file_fullpath;
47
my $file_fullpath_md5;
48
my $key;
49
my $dbobj_new;
50
my $dbobj_last;
51
my $db_hashinfo;
52
my $db_hashinfo2;
53
my $file;
54
my @volume_position=(0);
1153 dev 55
my @fs_path=();	       #  /dir[/file] - путь к файлу/директории для бэкапа.
56
my @fs_notpath=();     #  ! - отрицание пути, не помещать в бэкап. Всегда должен быть первым символом.
57
my @fs_mask=();        #  =~ - маска для файла или директории, а не абсолютный путь. Первый или второй символ.
58
my @fs_filemask=();    #  f~ - маска для файла. Первый или второй символ.
59
my @fs_dirmask=();     #  d~ - маска для директории. Первый или второй символ.
60
my @fs_notmask=();     #  =! - "НЕ" маска для файла или директории, а не абсолютный путь. Первый или второй символ.
61
my @fs_notfilemask=(); #  f! - "НЕ" маска для файла. Первый или второй символ.
62
my @fs_notdirmask=();  #  d! - "НЕ" маска для директории. Первый или второй символ.
1149 dev 63
 
1153 dev 64
# ------------- Обработка параметров командной строки
1149 dev 65
 
66
if ($ARGV[0] eq "-n" || $ARGV[0] eq "-h" || $ARGV[0] eq "-f" || $ARGV[0] eq "-c"){
67
    $cfg_new_flag=1;
68
    $config = $ARGV[1];
69
} else {
70
    $cfg_new_flag=0;
71
    $config = $ARGV[0];
72
}
73
 
74
if ( ! -f $config){
75
    die "Usage: fsbackup.pl [-n|-f|-h|-c] config_name\n";
76
}
77
 
78
require "$config";
79
 
1154 dev 80
$cfg_move_old_backup=1 if(!defined($cfg_move_old_backup));
81
$cfg_exit_on_empty=1   if(!defined($cfg_exit_on_empty));
82
 
1149 dev 83
if ( ! -d $cfg_cache_dir){
84
    die "\$cfg_cache_dir ($cfg_cache_dir) not found. Set \$cfg_cache_dir varisble in fsbackup.pl\n";
85
}
86
 
1153 dev 87
$cfg_time_limit *= 60 * 60 * 24; # Дни в секунды.
88
$cfg_size_limit *= 1024;	 # Килобайты в байты.
89
$cfg_maximum_archive_size *= 1024;	 # Килобайты в байты.
1149 dev 90
 
1151 dev 91
chdir($cfg_root_path);
1149 dev 92
 
93
if ($ARGV[0] eq "-h"){
94
    $cfg_backup_style = "hash";
95
}
96
if ($ARGV[0] eq "-f" ){
97
    $cfg_backup_style = "full_backup";
98
}
99
 
100
if ($ARGV[0] eq "-c" ){
101
    $cfg_clean_flag=1;
102
} else {
103
    $cfg_clean_flag=0;
104
}
105
 
1153 dev 106
#------------------- Проверяем переменные в файле конфигурации.
1149 dev 107
if ($cfg_backup_name !~ /^[\w\d\_]+$/){
108
    die "Found illegal characters in $cfg_backup_name ($cfg_backup_name).";
109
}
110
 
111
if (! grep {$_ eq $cfg_checksum} ("md5", "timesize")){
112
    die "Unknown checksum method:\$cfg_checksum=$cfg_checksum (allowed md5 or timesize)\n";
113
}
114
 
115
if (! grep {$_ eq $cfg_backup_style} ("backup", "full_backup", "sync", "hash")){
116
    die "Unknown backup_style:\$cfg_backup_style=$cfg_backup_style\n";
117
}
118
 
119
 
120
if ($cfg_backup_style eq "full_backup" || $cfg_backup_style eq "hash"){
121
    $cfg_new_flag=1;
122
    $cfg_clean_flag=1;
123
}
124
 
125
if (! grep {$_ eq $cfg_type} ("local", "remote_ssh", "remote_ftp")){
126
    die "Unknown backup target:\$cfg_type=$cfg_type\n";
127
}
128
 
129
if (($cfg_type eq "local") && (! -d $cfg_local_path)){
130
    die "Can't find \$cfg_local_path ($cfg_local_path)";
131
}
132
 
133
if ($cfg_backup_style eq "backup"){
134
    my ($sec,$min,$hour,$mday,$mon,$year) = localtime($cur_time);
135
    $backup_file_base = sprintf ("%s-%4.4d.%2.2d.%2.2d.%2.2d.%2.2d.%2.2d",
136
		$cfg_backup_name,$year+1900,$mon+1,$mday,$hour,$min,$sec);
137
}else{
138
    $backup_file_base="$cfg_backup_name";
139
}
140
 
141
print "Creating $cfg_type $cfg_backup_style: $cfg_backup_name\n" if ($cfg_verbose == &VERB_ALL);
142
 
143
if ($cfg_pgp_userid ne "" && $prog_pgp ne ""){
144
    print "PGP: enabled\n" if ($cfg_verbose == &VERB_ALL);
145
 
146
#    PGP 2.6 (pgp)
147
#    $prog_pgp_filter="| $prog_pgp -ef $cfg_pgp_userid -z'$cfg_pgp_userid' ";
148
#    PGP 5.0 (pgpe)
149
#    $prog_pgp_filter="| $prog_pgp -f $cfg_pgp_userid";
150
#    GnuPG (pgp)
151
    $prog_pgp_filter="| $prog_pgp -v --batch -e -r $cfg_pgp_userid";
152
} else {
153
    $prog_pgp_filter="";
154
}
155
 
156
if ($prog_gzip ne ""){
157
    $prog_gzip_filter="| $prog_gzip";
158
    $arc_ext=".gz";
159
} else {
160
    $prog_gzip_filter="";
161
    $arc_ext="";
162
 
163
}
164
 
165
if (! -d "$cfg_cache_dir/$cfg_backup_name"){
166
    mkdir("$cfg_cache_dir/$cfg_backup_name", 0700);
167
}
168
 
1153 dev 169
# ---------- Активируем FTP соединение 
1149 dev 170
 
171
ftp_connect();
172
 
1153 dev 173
#----------- Вычисляем уровень инкрементальности.
1149 dev 174
if ($cfg_increment_level != 0 && $cfg_backup_style eq "backup"){
1154 dev 175
    if(open(INCREMENT_LEVEL, "<$cfg_cache_dir/$cfg_backup_name/.increment_level")) {
176
        $cur_increment_level = <INCREMENT_LEVEL>;
177
        $cur_increment_level++;
178
        close (INCREMENT_LEVEL);
179
    }
180
    else {
181
        print "Can't open increment level file ($cfg_cache_dir/$cfg_backup_name/.increment_level).\n";
182
        $cur_increment_level = 0;
183
    }
1152 dev 184
 
1154 dev 185
    if ($cur_increment_level >= $cfg_increment_level){
186
        $cur_increment_level=0;
187
    }
1152 dev 188
 
1154 dev 189
    if ($cur_increment_level == 0){
1149 dev 190
	$cfg_new_flag=1;
191
	$cfg_clean_flag=1;
192
    }
1154 dev 193
    if(open(INCREMENT_LEVEL, ">$cfg_cache_dir/$cfg_backup_name/.increment_level")) {
194
        print INCREMENT_LEVEL $cur_increment_level;
195
        close (INCREMENT_LEVEL);
196
    }
197
    else {
198
        print "Can't save increment level to file ($cfg_cache_dir/$cfg_backup_name/.increment_level).\n";
199
    }
1149 dev 200
    print "Current increment number: $cur_increment_level\n" if ($cfg_verbose == &VERB_ALL);
201
}
202
################################################
1153 dev 203
#----------- Считываем хэш в память.
1149 dev 204
 
205
if ( (-f "$cfg_cache_dir/$cfg_backup_name/.hash" || $cfg_type ne "local" ) && $cfg_new_flag == 0){
1153 dev 206
# Считываем текущий хеш в память.
1149 dev 207
 
208
if ( $cfg_type eq "local"){
209
    rename ("$cfg_cache_dir/$cfg_backup_name/.hash", "$cfg_cache_dir/$cfg_backup_name/.hash.last");
210
}elsif ( $cfg_type eq "remote_ssh"){
211
    system ("$prog_ssh -l $cfg_remote_login $cfg_remote_host 'cat $cfg_remote_path/.hash' > $cfg_cache_dir/$cfg_backup_name/.hash.last") == 0 || print "SSH connection failed: $?\n";
212
} elsif ( $cfg_type eq "remote_ftp"){
213
    unlink ("$cfg_cache_dir/$cfg_backup_name/.hash.last");
214
    $ftp->get(".hash", "$cfg_cache_dir/$cfg_backup_name/.hash.last")|| print "FTP error, Can't GET .hash\n";
215
}
216
        $db_hashinfo = new DB_File::HASHINFO ;
217
        $db_hashinfo->{'cachesize'} =  DB_DEF_CACHE_SIZE;
218
        if (! ($dbobj_last = tie(%active_hash_last, "DB_File", "$cfg_cache_dir/$cfg_backup_name/.hash.last", O_RDWR|O_CREAT, 0644, $db_hashinfo ))){
219
	    print "WARNING: Error in hash, creating full backup.\n" if ($cfg_verbose >= &VERB_ERROR);
220
	    unlink "$cfg_cache_dir/$cfg_backup_name/.hash.last";
221
	    $dbobj_last = tie(%active_hash_last, "DB_File", "$cfg_cache_dir/$cfg_backup_name/.hash.last", O_RDWR|O_CREAT, 0644, $db_hashinfo )||print "Can't create or open DB File!";
222
	}
223
	# $dbobj->del($key);
224
	# $dbobj->sync();
225
 
226
}
227
 
1153 dev 228
# Закрываем ftp соединение. Следующий блок может выполняться гораздо дольше 
229
# чем таймаут ftp.
1149 dev 230
if ( $cfg_type eq "remote_ftp"){
231
    $ftp->quit;
232
}
1153 dev 233
#Создаем новый хеш.
1149 dev 234
unlink("$cfg_cache_dir/$cfg_backup_name/.hash");
235
$db_hashinfo2 = new DB_File::HASHINFO ;
236
$db_hashinfo2->{'cachesize'} =  100000;
237
$dbobj_new = tie(%active_hash_new, "DB_File", "$cfg_cache_dir/$cfg_backup_name/.hash", O_RDWR|O_CREAT, 0644, $db_hashinfo2) || print "Can't create or open DB File!\n";
238
 
1153 dev 239
# Создаем список файлов для помещения в архив.
1149 dev 240
open (LIST, ">$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list")|| print "Can't create list file ($cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list).\n";
241
flock (LIST, 2);
242
 
1153 dev 243
# Создаем список директорий в архиве.
1149 dev 244
open (DIRS, ">$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.dir")|| print "Can't create list file ($cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.dir).\n";
245
flock (DIRS, 2);
246
 
1154 dev 247
open (META, ">$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.meta")|| print "Can't create meta file ($cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.meta).\n";
248
print META "fsbackup $version\n";
249
print META "increment level: $cur_increment_level\n";
250
close(META);
251
 
1153 dev 252
# Считываем список подлежащих бэкапу директорий в память.
1151 dev 253
 
1149 dev 254
while(<DATA>){
255
    chomp;
256
    $cur_path = $_;
257
    if ($cur_path =~ /^\!(.*)$/){		#  !
258
	push @fs_notpath, $1;
259
 
260
    } elsif ($cur_path =~ /^\=\~(.*)$/){	#  =~
261
	push @fs_mask, $1;
262
 
263
    } elsif ($cur_path =~ /^f\~(.*)$/){		#  f~
264
	push @fs_filemask, $1;
265
 
266
    } elsif ($cur_path =~ /^d\~(.*)$/){		#  d~
267
	push @fs_dirmask, $1;
268
 
269
    } elsif ($cur_path =~ /^\=\!(.*)$/){	#  =!
270
	push @fs_notmask, $1;
271
 
272
    } elsif ($cur_path =~ /^f\!(.*)$/){		#  f!
273
	push @fs_notfilemask, $1;
274
 
275
    } elsif ($cur_path =~ /^d\!(.*)$/){		#  d!
276
	push @fs_notdirmask, $1;
277
 
278
    } elsif ($cur_path =~ /^#/ || $cur_path =~ /^\s*$/){ #  comment
279
	next;
280
 
281
    } elsif ($cur_path =~ /[\/\w]+/) {		#  /dir[/file]
1151 dev 282
	push @fs_path, $cur_path;
1149 dev 283
 
284
    } else {
285
	print STDERR "Syntax error: $cur_path, ingnored.\n" if ($cfg_verbose >= &VERB_ALL);
286
    }
287
}
288
 
289
#--------------------------------------------------------------------
1153 dev 290
# Последовательно просматририваем весь список директорий отмеченных для бэкапа
1149 dev 291
 
292
 
293
foreach $cur_pathitem (@fs_path){
294
    print "Adding $cur_pathitem....\n" if ($cfg_verbose == &VERB_ALL);
295
    find (\&add_to_backup, $cur_pathitem);
296
    print "done\n" if ($cfg_verbose == &VERB_ALL);
297
}
298
close (LIST);
299
close (DIRS);
300
#------------
1153 dev 301
# Составляем список удаленных файлов.
1149 dev 302
 
303
    open (DEL, ">$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.del")|| print "Can't create list file ($cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.del).\n";
304
    flock (DEL, 2);
305
    if ($cfg_backup_style ne "hash"){
306
	while(($file, $key)= each(%active_hash_last)){
307
	    $file =~ s/\'/\'\\\'\'/g;
308
	    $file =~ s/^\/(.*)$/$1/;
309
	    print DEL "rm -rf '$file'\n";
310
	    $del_lines_cnt++;
311
	}
312
    }
313
    close(DEL);
314
 
1153 dev 315
# Записываем хэш на диск.
1149 dev 316
$dbobj_new->sync();
317
untie %active_hash_new;
318
untie %active_hash_last;
319
 
1153 dev 320
chdir ("/"); # Переходим в корень, так как все пути у нас без корневого /
1149 dev 321
 
1153 dev 322
# Активируем FTP соединение второй раз.
1149 dev 323
ftp_connect();
324
 
325
#------------
1153 dev 326
# Если только обновляем хэш, то записываем его и выходим.
1149 dev 327
 
1153 dev 328
if ($cfg_backup_style eq "hash"){ # Только создать хэшь без архивирования.
1149 dev 329
 
330
    if ( $cfg_type eq "local"){
331
	system( "cp -f $cfg_cache_dir/$cfg_backup_name/.hash $cfg_local_path/.hash") == 0 || print "Local FS copy hash failed: $?";
332
    } elsif ( $cfg_type eq "remote_ssh"){
333
	system( "cat $cfg_cache_dir/$cfg_backup_name/.hash | $prog_ssh -l $cfg_remote_login $cfg_remote_host 'cat - > $cfg_remote_path/.hash'") == 0 || print "SSH connection failed (copy hash): $?\n";
334
    } elsif ( $cfg_type eq "remote_ftp"){
335
	$ftp->delete(".hash");
336
	$ftp->put("$cfg_cache_dir/$cfg_backup_name/.hash", ".hash")|| print "Can't upload .hash to remote server via FTP\n";
337
    }
338
    exit (0);
339
}
340
 
341
#------------
1153 dev 342
# Архивируем и передаем в хранилище.
1149 dev 343
 
1154 dev 344
if ($cfg_exit_on_empty == 1 && $list_lines_cnt == 0 && $del_lines_cnt == 0){
345
print "$cfg_exit_on_empty\n";
1149 dev 346
    print "WARNING: Nothing to backup.\n" if ($cfg_verbose >= &VERB_ALL);
347
    exit;
348
}
349
if ( $cfg_type eq "local"){
350
 
351
    print "Storing local backup...\n" if ($cfg_verbose == &VERB_ALL);
352
    if ($cfg_backup_style eq "sync"){
1153 dev 353
	if ($cfg_clean_flag == 1){ # Удалить старые копии
1151 dev 354
	    print "WARNING: If you really shure to delete $cfg_local_path before sync operatioun uncomment line 'system( \"find \$cfg_local_path -not -path '\$cfg_local_path' -maxdepth 1 -exec \$prog_rm -rf \{\} \\;\");'" if ($cfg_verbose >= &VERB_ALL);
355
#    	    system( "find $cfg_local_path -not -path '$cfg_local_path' -maxdepth 1 -exec $prog_rm -rf \{\} \\;");
1149 dev 356
	}
357
 
358
	system( "cd $cfg_local_path; sh $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.del");
359
	system( "$prog_tar -c -f - -T $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list| $prog_tar -xf - -C $cfg_local_path") == 0 || print "Local FS sync failed (tar|untar): $?\n";
360
	system( "cd $cfg_local_path; sh $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.dir");
361
	system( "cp -f $cfg_cache_dir/$cfg_backup_name/.hash $cfg_local_path/$backup_file_base.hash") == 0 || print "Local FS copy failed: $?\n";
362
 
363
    } else {
1153 dev 364
	if ($cfg_clean_flag == 1){ # Удалить старые копии
1149 dev 365
	    if ($cfg_save_old_backup == 0){
366
		system( "$prog_rm -f $cfg_local_path/*");
1154 dev 367
	    } elsif($cfg_move_old_backup == 1) {
1149 dev 368
		if (! -d "$cfg_local_path/OLD"){
369
		    system( "mkdir $cfg_local_path/OLD");
370
		}
371
		system( "$prog_rm -f $cfg_local_path/OLD/*");
372
		system( "mv -f $cfg_local_path/$cfg_backup_name* $cfg_local_path/OLD/");
373
		# system( "$prog_rm -f $cfg_local_path/*");
374
	    }
375
	}
1154 dev 376
	system( "cp -f $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.meta $cfg_local_path/$backup_file_base.meta") == 0 || print "Local FS .meta copy failed: $?\n";
1149 dev 377
	system( "cp -f $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list $cfg_local_path/$backup_file_base.list") == 0 || print "Local FS .list copy failed: $?\n";
378
	system( "cp -f $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.dir $cfg_local_path/$backup_file_base.dir") == 0 || print "Local FS .dir copy failed: $?\n";
379
	system( "cp -f $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.del $cfg_local_path/$backup_file_base.del") == 0 || print "Local FS .del copy failed: $?\n";
1154 dev 380
	#system( "cp -f $cfg_cache_dir/$cfg_backup_name/.hash $cfg_local_path/$backup_file_base.hash") == 0 || print "Local FS .hash copy failed: $?\n";
1153 dev 381
	# Обрабатываем разбиение на тома
1149 dev 382
	for ($arc_block_level=0; $arc_block_level <= $#volume_position; $arc_block_level++){
1154 dev 383
	    my $tmp_list_file = create_tmp_list($arc_block_level, $volume_position[$arc_block_level], $volume_position[$arc_block_level+1], "$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list");
1149 dev 384
	    system( "$prog_tar -c -f - -T $tmp_list_file $prog_gzip_filter $prog_pgp_filter > $cfg_local_path/$backup_file_base-$arc_block_level.tar${arc_ext}") == 0 || print "Local FS tar backup failed: $?\n";
385
	}
386
    }
387
 
388
} elsif ( $cfg_type eq "remote_ssh"){
389
    print "Storing remote ssh backup...\n" if ($cfg_verbose == &VERB_ALL);
390
    if ($cfg_backup_style eq "sync"){
1153 dev 391
	if ($cfg_clean_flag == 1){ # Удалить старые копии
1151 dev 392
	    system( "$prog_ssh -l $cfg_remote_login $cfg_remote_host find $cfg_remote_path -not -path '$cfg_remote_path' -maxdepth 1 -exec rm -rf \{\} \\;");
1149 dev 393
	}
394
	system( "cat $cfg_cache_dir/$cfg_backup_name/.hash | $prog_ssh -l $cfg_remote_login $cfg_remote_host 'cat - > $cfg_remote_path/.hash'") == 0 || print "SSH connection failed (store .hash): $?\n";
395
	system( "cat $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.del | $prog_ssh -l $cfg_remote_login $cfg_remote_host 'cat - > $cfg_remote_path/.del'") == 0 || print "SSH connection failed (store .hash): $?\n";
396
	system( "cat $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.dir | $prog_ssh -l $cfg_remote_login $cfg_remote_host 'cat - > $cfg_remote_path/.dir'") == 0 || print "SSH connection failed (store .hash): $?\n";
397
        system("$prog_ssh -l $cfg_remote_login $cfg_remote_host '(cd $cfg_remote_path; sh .del)'");
398
        system( "$prog_tar -c -f - -T $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list $prog_gzip_filter| $prog_ssh -l $cfg_remote_login $cfg_remote_host tar -xf - -C $cfg_remote_path") == 0 || print "SSH connection failed (tar): $?\n";;
399
        system("$prog_ssh -l $cfg_remote_login $cfg_remote_host '(cd $cfg_remote_path; sh .dir)'");
400
 
401
 
402
	open (DEL, "<$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.del");
403
	flock (DEL, 1);
404
	while(<DEL>){
405
	    chomp;
406
	    $cur_file = $_;
407
	    $cur_file =~ s/\'/\'\\\'\'/g;
408
    	    system("$prog_ssh -l $cfg_remote_login $cfg_remote_host rm -f '$cfg_remote_path/$cur_file'");
409
	}
410
	close(DEL);
411
    } else {
1153 dev 412
	if ($cfg_clean_flag == 1){ # Удалить старые копии
1149 dev 413
 
414
	    if ($cfg_save_old_backup == 0){
415
		system( "$prog_ssh -l $cfg_remote_login $cfg_remote_host rm -f $cfg_remote_path/*");
416
	    } else {
417
		system( "$prog_ssh -l $cfg_remote_login $cfg_remote_host '(if [ ! -d $cfg_remote_path/OLD ]; then mkdir $cfg_remote_path/OLD; fi)'");
418
		system( "$prog_ssh -l $cfg_remote_login $cfg_remote_host rm -f $cfg_remote_path/OLD/*");
419
		system( "$prog_ssh -l $cfg_remote_login $cfg_remote_host mv -f $cfg_remote_path/$cfg_backup_name* $cfg_remote_path/OLD/");
420
    		# system( "$prog_ssh -l $cfg_remote_login $cfg_remote_host rm -f $cfg_remote_path/*");
421
	    }
422
	}
423
	system( "cat $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list | $prog_ssh -l $cfg_remote_login $cfg_remote_host 'cat - > $cfg_remote_path/$backup_file_base.list'") == 0 || print "SSH connection failed (copy .list): $?\n";
424
	system( "cat $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.dir | $prog_ssh -l $cfg_remote_login $cfg_remote_host 'cat - > $cfg_remote_path/$backup_file_base.dir'") == 0 || print "SSH connection failed (copy .dir): $?\n";
425
        system( "cat $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.del | $prog_ssh -l $cfg_remote_login $cfg_remote_host 'cat - > $cfg_remote_path/$backup_file_base.del'") == 0 || print "SSH connection failed (copy .del): $?\n";
426
        system( "cat $cfg_cache_dir/$cfg_backup_name/.hash | $prog_ssh -l $cfg_remote_login $cfg_remote_host 'cat - > $cfg_remote_path/$backup_file_base.hash'") == 0 || print "SSH connection failed (copy .hash): $?\n";
427
	system( "cat $cfg_cache_dir/$cfg_backup_name/.hash | $prog_ssh -l $cfg_remote_login $cfg_remote_host 'cat - > $cfg_remote_path/.hash'") == 0 || print "SSH connection failed (cache .hash): $?\n";
1153 dev 428
	# Обрабатываем разбиение на тома
1149 dev 429
	for ($arc_block_level=0; $arc_block_level <= $#volume_position; $arc_block_level++){
1154 dev 430
	    my $tmp_list_file = create_tmp_list($arc_block_level, $volume_position[$arc_block_level], $volume_position[$arc_block_level+1], "$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list");
1149 dev 431
            system( "$prog_tar -c -f - -T $tmp_list_file $prog_gzip_filter $prog_pgp_filter| $prog_ssh -l $cfg_remote_login $cfg_remote_host 'cat - > $cfg_remote_path/$backup_file_base-$arc_block_level.tar${arc_ext}'") == 0 || print "SSH connection failed (tar): $?\n";
432
	}
433
    }
434
} elsif ( $cfg_type eq "remote_ftp"){
435
    print "Storing remote ftp backup...\n" if ($cfg_verbose == &VERB_ALL);
436
 
437
    if ($cfg_backup_style eq "sync"){
438
	print "WARNING: Backup style 'sync' only allowed for local and remote_ssh storage.\n" if ($cfg_verbose >= &VERB_ALL);
439
    } else {
1153 dev 440
	if ($cfg_clean_flag == 1){ # Удалить старые копии
1149 dev 441
	    if ($cfg_save_old_backup == 0){
442
		foreach $cur_dir ($ftp->ls()){
443
    		    $ftp->delete($cur_dir);
444
		}
445
	    } else {
446
		$ftp->mkdir("$cfg_remote_path/OLD");
447
		$ftp->cwd("$cfg_remote_path/OLD");
448
		foreach $cur_dir ($ftp->ls()){
449
    		    $ftp->delete($cur_dir);
450
		}
451
		$ftp->cwd("$cfg_remote_path");
452
		foreach $cur_dir ($ftp->ls()){
453
		    if ($cur_dir =~ /$cfg_backup_name/){
454
    			$ftp->rename($cur_dir,"$cfg_remote_path/OLD/$cur_dir");
455
		    }
456
		}
457
		foreach $cur_dir ($ftp->ls()){
458
    		    $ftp->delete($cur_dir);
459
		}
460
	    }
461
	}
462
	$ftp->delete("$backup_file_base.list");
463
	$ftp->put("$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list", "$backup_file_base.list") || print "Can't PUT .list file to remote FTP server\n";
464
	$ftp->delete("$backup_file_base.dir");
465
	$ftp->put("$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.dir", "$backup_file_base.dir")|| print "Can't PUT .dir file to remote FTP server\n";
466
	$ftp->delete("$backup_file_base.del");
467
	$ftp->put("$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.del", "$backup_file_base.del")|| print "Can't PUT .del file to remote FTP server\n";
468
        $ftp->delete("$backup_file_base.hash");
469
        $ftp->put("$cfg_cache_dir/$cfg_backup_name/.hash", "$backup_file_base.hash")|| print "Can't PUT old .hash file to remote FTP server\n";
470
	$ftp->delete(".hash");
471
	$ftp->put("$cfg_cache_dir/$cfg_backup_name/.hash", ".hash")|| print "Can't PUT new .hash file to remote FTP server\n";
1153 dev 472
	# Обрабатываем разбиение на тома
1149 dev 473
	for ($arc_block_level=0; $arc_block_level <= $#volume_position; $arc_block_level++){
1154 dev 474
	    my $tmp_list_file = create_tmp_list($arc_block_level, $volume_position[$arc_block_level], $volume_position[$arc_block_level+1], "$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list");
1149 dev 475
	    $ftp->delete("$backup_file_base-$arc_block_level.tar${arc_ext}");
476
	    open (TAR,"$prog_tar -c -f - -T $tmp_list_file $prog_gzip_filter $prog_pgp_filter|")|| print "tar failed: $?\n";
477
    	    flock(TAR,1);
478
	    $ftp->put(*TAR, "$backup_file_base-$arc_block_level.tar${arc_ext}")|| print "Can't store backup archive to remote FTP server.\n";
479
	    close(TAR);
480
	}
481
    	$ftp->quit;
482
    }
483
}
484
 
485
if ( $cfg_type eq "remote_ftp"){
486
    $ftp->quit;
487
}
488
print "***** Backup successful complete.\n" if ($cfg_verbose == &VERB_ALL);
489
exit (0);
490
 
491
 
492
########################################
493
sub add_to_backup{
494
  my($file_name, $file_dir, $md5_checksum_stat, $checksum_stat);
495
  my($tmp, $stat_mode, $stat_uid, $stat_gid, $stat_size, $stat_mtime, $stat_time);
496
 
497
  $file_name  = $_;
498
  $file_fullpath  = $File::Find::name;
499
  $file_dir  = $File::Find::dir;
500
  my $file_fullpath_esc = $file_fullpath;
501
  $file_fullpath_esc =~ s/\'/\'\\\'\'/g;
502
 
1153 dev 503
  # Создаем список директорий
1151 dev 504
  if ((-d $file_fullpath) && (! -l $file_fullpath)){
1149 dev 505
      if (check_path($file_dir, $file_name) == 1){
506
	if ($cfg_backup_style ne "hash"){
1151 dev 507
	   ($tmp, $tmp, $stat_mode, $tmp, $stat_uid, $stat_gid, $tmp, $stat_size, $tmp, $stat_mtime, $stat_time) = stat($file_fullpath);
1149 dev 508
	    $stat_mode = sprintf ("%04o", $stat_mode & 07777);
509
	    $file_fullpath_esc =~ s/^\/(.*)$/$1/;
510
	    my ($sec,$min,$hour,$mday,$mon,$year) = localtime($stat_time);
511
	    $stat_time = sprintf ("%4.4d%2.2d%2.2d%2.2d%2.2d.%2.2d",
512
		                   $year+1900,$mon+1,$mday,$hour,$min,$sec);
1154 dev 513
    	    print DIRS "mkdir -p '$file_fullpath_esc'\n";
1149 dev 514
    	    print DIRS "chmod $stat_mode '$file_fullpath_esc'\n";
515
    	    print DIRS "chown $stat_uid:$stat_gid '$file_fullpath_esc'\n";
516
    	    print DIRS "touch -t $stat_time '$file_fullpath_esc'\n";
517
	    $cur_backup_size += int(length($file_fullpath)/100.0 + 1)*512;
518
	    if ($cfg_maximum_archive_size > 0 && $cur_backup_size + 10240 >= $cfg_maximum_archive_size){
519
	        my $old_val = $cur_backup_size - $stat_size - int(length($file_fullpath)/100.0 + 1)*512;
520
		my $tmp_pos= $#volume_position+1;
521
	        print "Volume $tmp_pos Done. Size: $old_val\n" if ($cfg_verbose == &VERB_ALL);
522
		$cur_backup_size = $stat_size + int(length($file_fullpath)/100.0 + 1)*512 + 1536;
523
	    	push @volume_position, $list_lines_cnt;
524
	    }
1154 dev 525
	    $active_hash_new{$file_fullpath} = "D";
526
	    check_update($file_fullpath, "D", $file_fullpath, $stat_size);
1149 dev 527
	}
528
      } else {
529
          if ($cfg_stopdir_prune == 1){
530
              $File::Find::prune = 1;
531
              return;
532
	  }
533
      }
1153 dev 534
  # Работаем с файлами
1151 dev 535
  } elsif ((-f $file_fullpath) || (-l $file_fullpath)){
1149 dev 536
      if (check_path($file_dir, $file_name) == 1){
1151 dev 537
	  ($tmp, $tmp, $stat_mode, $tmp, $stat_uid, $stat_gid, $tmp, $stat_size, $tmp, $stat_mtime) = stat($file_fullpath);
1149 dev 538
	      $checksum_stat= md5_base64("$stat_mtime/$stat_size/$stat_mode/$stat_uid/$stat_gid");
539
	      # $file_fullpath_md5 = md5_base64($file_fullpath);
540
	      $file_fullpath_md5 = $file_fullpath;
541
	  if ($cfg_time_limit != 0 && $cur_time - $cfg_time_limit > $stat_mtime){
542
	      print "Time limit: $cur_time - $cfg_time_limit > $stat_mtime, file $file_fullpath ignored.\n" if ($cfg_verbose == &VERB_ALL);
543
	      next;
544
	  }
545
	  if ($cfg_size_limit != 0 && $cfg_size_limit < $stat_size){
546
	      print "Size limit: $cfg_size_limit < $stat_size, file $file_fullpath ignored.\n" if ($cfg_verbose == &VERB_ALL);
547
	      next;
548
	  }
549
 
1151 dev 550
	  if (($cfg_checksum eq "md5") && (! -l $file_fullpath)){
551
	      ($md5_checksum_stat, $tmp) = split(/\s+/, `$prog_md5sum '$file_fullpath_esc'`);
1149 dev 552
	      $active_hash_new{$file_fullpath_md5} = "$checksum_stat/$md5_checksum_stat";
553
	      check_update($file_fullpath, "$checksum_stat/$md5_checksum_stat", $file_fullpath_md5, $stat_size);
554
	  } else {
555
	      $active_hash_new{$file_fullpath} = $checksum_stat;
556
	      check_update($file_fullpath, $checksum_stat, $file_fullpath, $stat_size);
557
	  }
558
      }
559
  }
560
}
561
 
562
###############################################
1153 dev 563
# Проверяем изменился ли файл или нет, если да апдейтим лог.
1149 dev 564
sub check_update{
565
     my ($file, $checksum, $filesum, $stat_size) = @_;
566
 
1154 dev 567
    if (($active_hash_last{$filesum} ne $checksum) && ($checksum ne "D")){
1149 dev 568
	if ($cfg_backup_style ne "hash"){
569
		$file =~ s/^\/(.*)$/$1/;
570
	        print LIST "$file\n";
571
 
1153 dev 572
	        # Обрабатываем случай разбиения гиганских архивов.
1149 dev 573
		if (-l "/$file"){
574
		    $stat_size = 0;
575
		}
576
	        $cur_backup_size += $stat_size + int(length($file)/100.0 + 1)*512;
577
#	  	print "$cur_backup_size:$stat_size:$file\n";
578
	        if ($cfg_maximum_archive_size > 0 && $cur_backup_size + 10240 >= $cfg_maximum_archive_size){
579
	        my $old_val = $cur_backup_size - $stat_size - int(length($file)/100.0 + 1)*512;
580
		my $tmp_pos= $#volume_position+1;
581
	        print "Volume $tmp_pos Done. Size: $old_val\n" if ($cfg_verbose == &VERB_ALL);
582
	        $cur_backup_size = $stat_size + int(length($file)/100.0 + 1)*512 + 1536;
583
	        push @volume_position, $list_lines_cnt;
584
	  }
585
 
586
	}
587
	$list_lines_cnt++;
588
    }
1154 dev 589
    if(($active_hash_last{$filesum} eq "D") && ($checksum ne "D")
590
      || ($active_hash_last{$filesum} ne "D") && ($checksum eq "D"))
591
    {
592
        # if old entry was a directory and now it's file or link or vice versa, leave it in hash 
593
        # to add it later to the delete list
1149 dev 594
    }
1154 dev 595
    else {
596
        delete $active_hash_last{$filesum};
597
        if (defined $dbobj_last){
598
	    $dbobj_last->del($filesum);
599
        }
600
    }
1149 dev 601
}
602
 
603
###############################################
1153 dev 604
# 0 - не добавлять файл
605
# 1 - добавть файл
1149 dev 606
 
607
sub check_path {
608
    my ($dir_name, $file_name) = @_;
609
    my ($item, $path);
610
 
611
    $path = "$dir_name/$file_name";
612
 
1151 dev 613
 
614
 
1149 dev 615
    foreach $item (@fs_notmask){
616
	if ($path =~ /$item/){
617
	    return 0;
618
	}
619
    }
620
 
621
    foreach $item (@fs_notfilemask){
622
	if ($file_name =~ /$item/){
623
	    return 0;
624
	}
625
    }
626
 
627
    foreach $item (@fs_filemask){
628
	if ($file_name =~ /$item/){
629
	    return 1;
630
	}
631
    }
632
 
633
    foreach $item (@fs_notdirmask){
634
	if ($dir_name =~ /$item/){
635
	    return 0;
636
	}
637
    }
638
 
639
 
640
    foreach $item (@fs_mask){
641
	if ($path =~ /$item/){
642
	    return 1;
643
	}
644
    }
645
 
646
    foreach $item (@fs_dirmask){
647
	if ($dir_name =~ /$item/){
648
	    return 1;
649
	}
650
    }
651
 
652
 
653
    foreach $item (@fs_notpath){
654
	if (($dir_name eq $item) || ($path eq $item) || ($dir_name =~ /^$item\//)){
655
	    return 0;
656
	}
657
    }
658
 
659
    return 1;
660
}
661
###############################################
1153 dev 662
# Устанавливаем соединение с удаленным сервером по FTP.
1149 dev 663
 
664
sub ftp_connect{
665
    if ( $cfg_type eq "remote_ftp"){
1151 dev 666
	$ftp = Net::FTP->new($cfg_remote_host, Timeout => 30, Debug => 0) || die "Can't connect to ftp server.\n";
1149 dev 667
	$ftp->login($cfg_remote_login, $cfg_remote_password) || die "Can't login to ftp server.\n";
668
        $ftp->cwd($cfg_remote_path) || die "Path $cfg_remote_path not found on ftp server.\n";
669
	$ftp->binary();    
670
    }
671
}
672
###############################################
1153 dev 673
# Содание списка файлов для помещения в определенный том многотомного архива.
1149 dev 674
 
1154 dev 675
sub create_tmp_list{
1149 dev 676
	my ($arc_block_level, $position1, $position2, $full_list_path) = @_;
677
	my ($tmp_list_path, $pos_counter);
678
 
679
    if ($arc_block_level == 0 && $position1 == 0 && $position2 eq ''){
680
	$tmp_list_path = $full_list_path;
681
    } else {
682
	$pos_counter = 0;
683
	$tmp_list_path = "$full_list_path.$arc_block_level";
684
	open(FULL_LIST, "<$full_list_path")|| die "Can't open full list $full_list_path\n";
685
	flock(FULL_LIST, 1);
686
	open(TMP_LIST, ">$tmp_list_path")|| die "Can't create temp list $tmp_list_path\n";
687
	flock(TMP_LIST, 2);
688
	while(<FULL_LIST>){
689
	    if (($pos_counter >= $position1) && ($pos_counter < $position2 || $position2 eq '')){
690
		print TMP_LIST $_;
691
	    }
692
	    $pos_counter++;
693
	}
694
	close(TMP_LIST);
695
	close(FULL_LIST);
696
    }
697
    return $tmp_list_path;
698
}
699
###############################################
700
###############################################
701
 
702
__END__
703
 
704
=head1 NAME
705
 
706
fsbackup - file system backup and synchronization utility. 
707
 
708
=head1 SYNOPSIS
709
 
710
    fsbackup.pl [options] <configuration file>
711
 
712
=head1 DESCRIPTION
713
 
714
C<fsbackup.pl> is a incremental backup creation utility. 
715
C<fsbackup.pl> support backup compression and encryption. Backup can be stored
716
on local file system and on remote host stored over SSH or FTP. Some addition 
717
scripts allow backups SQL tables from PostgreSQL and MySQL (C<pgsql_backup.sh> 
718
and C<mysql_backup.sh>)), save system configuration files and list of installed 
719
packages (C<sysbackup.sh>). 
720
Backuped with C<fsbackup.pl> files can be recovered by script C<fsrestore.sh>,
721
backuped with C<sysbackup.sh> system packeges can be reinstalled by C<sysrestore.sh>
722
 
723
=head1 OPTIONS
724
 
725
The following command-line options can be used with C<fsbackup.pl>:
726
 
727
=over
728
 
729
=item C<-n>
730
 
731
Create new backup without checking files in previously stored hash.
732
 
733
=item C<-f>
734
 
735
Create full backup, like as C<-n> option.
736
 
737
=item C<-h>
738
 
739
Only rebuild hash, no storing files in backup archive.
740
 
741
=item C<-c>
742
 
743
Clean incremental backup storage and create new full backup without checking
744
$cfg_increment_level config parameter.
745
 
746
=head1 ADDITION SCRIPTS
747
 
748
=item C<create_backup.sh>
749
 
750
Backup planner running from C<crontab>. For example: 
751
 
1154 dev 752
18 4 * * * /root/backup/fsbackup/create_backup.sh
1149 dev 753
 
754
=item C<install.pl>
755
 
756
Script to install fsbackup package and some required perl modules.
757
 
758
=item C<fsbackup.pl>
759
 
760
File system backup utility.
761
 
762
=item C<cfg_example>
763
 
764
Example of configuration file.
765
 
766
=item C<scripts/pgsql_backup.sh>
767
 
768
=item C<scripts/mysql_backup.sh>
769
 
770
Script for backup SQL tables from PostreSQL and MySQL.
771
 
772
=item C<scripts/sysbackup.sh>
773
 
774
Script for store system configuration files and information about installed
775
packages.
776
 
777
=item C<scripts/fsrestore.sh>
778
 
779
Script for restore files backuped by C<fsbackup.pl>.
780
 
781
=item C<scripts/sysrestore.sh>
782
 
783
Script for reinstall packages stored by C<sysbackup.sh>.
784
 
785
 
786
=head1 CONFIGURATION FILE
787
 
788
=item B<$cfg_backup_name> = 'test_host'
789
 
790
Name of backup, single word.
791
 
1154 dev 792
=item B<$cfg_cache_dir> = '/root/backup/fsbackup/cache'
1149 dev 793
 
794
Path of internal cache directory for local backup method.
795
 
796
=item B<$prog_md5sum> = 'md5sum -b'
797
 
798
=item B<$prog_tar> = 'tar'
799
 
800
=item B<$prog_ssh> = 'ssh'
801
 
802
=item B<$prog_rm> = 'rm'
803
 
804
=item B<$prog_gzip> = 'gzip'
805
 
806
=item B<$prog_pgp> = 'gpg'
807
 
808
Full path of some external program running from C<fsbackup.pl>.
809
B<$prog_gzip = ''> - not use compression, B<$prog_pgp = ''> - not use 
810
encryption.
811
 
812
=item B<$cfg_checksum> = 'timesize'
813
 
814
File checksum method: 
815
 
816
timesize - checksum of file attributes (default, best speed) 
817
 
818
md5      - checksum of file attributes + MD5 checksum of file content.
819
 
820
=item B<$cfg_backup_style> = 'backup'
821
 
822
Backup style:
823
 
824
backup - incremental backup (copy only new and changed files).
825
 
826
full_backup - full backup (copy all files).	
827
 
828
sync - file tree synchronization.
829
 
830
hash - hash creation without storing archive (spying for new or changed files).
831
 
832
=item B<$cfg_increment_level> = 7
833
 
834
Incremental level (after how many incremental copy make full refresh of backup)
835
 
836
=item B<$cfg_type> = 'remote_ssh'
837
 
838
Type of backup storage:
839
 
840
    local  - store backup on local file system.
841
    remote_ssh - store backup on remote host over SSH connection.
842
    remote_ftp - store backup on remote FTP server.
843
 
844
 
845
=item B<$cfg_remote_host> = 'backup-server.test.ru'
846
 
847
=item B<$cfg_remote_login> = 'backup_login'
848
 
849
=item B<$cfg_remote_path> = '/home/backup_login/backup'
850
 
851
Connection parameters for remote_ssh storage type.
852
 
853
=item B<$cfg_remote_password> = 'Test1234'
854
 
855
Password of remote login for remote_ftp storage type.
856
 
857
=item B<$cfg_local_path> = '/var/backup/'
858
 
859
Path of directory to store backup on local file system for local storage type.
860
 
861
=item B<$cfg_time_limit> = 0
862
 
863
Limit of file creation time in days. If not 0, don't backup files created or 
864
modified later then $cfg_time_limit (days).
865
 
866
=item B<$cfg_size_limit> = 0
867
 
868
Limit of maximum file size. If not 0, don't backup files witch size more then 
869
$cfg_time_limit kilobytes.
870
 
871
=item B<$cfg_root_path> = '/'
872
 
873
Root path for initial chdir.
874
 
875
=item B<$cfg_pgp_userid> = ''
876
 
877
Name of user in public key ring with public key will be used for PGP encryption.
878
Not use encryption if not set.
879
 
880
=item B<$cfg_verbose> = 3
881
 
882
Verbose level.
883
 
884
 
885
    1	- Output errors and warnings.
886
    2	- Output all the  available  data.
887
 
888
=item B<$cfg_save_old_backup> = 1
889
 
890
Save previous backup to OLD directory before rotation or before storing full backup.
891
 
892
 
893
    1 - save old backup.
894
 
895
=item B<$cfg_maximum_archive_size> = 0
896
 
897
Size of maximum size (in KiloBytes) of single unpacked archive file (0 - unlimited file size).
898
 
899
=item B<$cfg_stopdir_prune> = 0
900
 
901
Recursive review of the prohibited directories.
902
 
903
    1 - not use a recursive entrance to directory prohibited for backup (speed is increased, reduces flexibility of customization).
904
 
905
=item B<__DATA__> - list of backuped path and regexp mask.
906
 
907
    /dir[/file] - backup file or directory.
908
    !/dir[/file] - NOT include this file or directory to backup.
909
    # - ignore this line.
910
 
911
Mask:
912
 
913
    =~ - regexp mask for include file or directory to backup.
914
    f~ - regexp file mask for include file to backup.
915
    d~ - regexp directory mask for include directory to backup.
916
    =! - regexp mask for NOT include file or directory to backup.
917
    f! - regexp file mask for NOT include file to backup.
918
    d! - regexp directory mask for NOT include directory to backup.
919
 
920
 
921
Operation priority:
922
 
923
    1. =!
924
    2. f!
925
    3. f~
926
    4. d!
927
    5. =~
928
    6. d~
929
    7. !path
930
    8. path
931
 
932
 
933
=head1 COPYRIGHT
934
 
935
Copyright (c) 2001 by Maxim Chirkov <mc@tyumen.ru>
936
http://www.opennet.ru/dev/fsbackup/
937
 
938
=head1 BUGS
939
 
940
Look TODO file.
941
 
942
=head1 AUTHORS
943
 
944
Maxim Chirkov <mc@tyumen.ru>
945
 
946
=cut