Subversion Repositories general

Rev

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