Subversion Repositories general

Rev

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