Rev 1253 | 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> |
||
1258 | dev | 6 | # Copyright (c) 2003-2006 by Anatoli Klassen. <anatoli@aksoft.net> |
1149 | dev | 7 | # |
1258 | dev | 8 | # Command line flags: |
9 | # -n - create a new (full) archive |
||
10 | # -c - delete old cache and create a full archive |
||
1206 | dev | 11 | # |
12 | # $Id: fsbackup.pl 1258 2006-07-14 12:50:58Z dev $ |
||
13 | # |
||
1149 | dev | 14 | ############################################# |
1258 | dev | 15 | use constant DB_DEF_CACHE_SIZE => 40960000; # size of cache in memory |
1149 | dev | 16 | |
17 | use POSIX; |
||
18 | use File::Find; |
||
19 | use Digest::MD5 qw(md5_base64); |
||
20 | use DB_File; |
||
1205 | dev | 21 | use BSD::stat; |
1149 | dev | 22 | |
23 | use constant VERB_SILENT => 0; # Silent mode, suspend all output. |
||
1258 | dev | 24 | use constant VERB_ERROR => 1; # Output all errors and warnings. |
25 | use constant VERB_ALL => 2; # Output all the available data. |
||
1149 | dev | 26 | |
1258 | dev | 27 | my $version = "3.0"; |
1149 | dev | 28 | my $list_lines_cnt = 0; |
29 | my $del_lines_cnt = 0; |
||
30 | my $cur_time = time(); |
||
31 | my %active_hash_last; |
||
32 | my %active_hash_new; |
||
33 | my $cfg_new_flag = 0; |
||
34 | my $cfg_clean_flag = 0; |
||
35 | my $config = 0; |
||
1258 | dev | 36 | my $cur_backup_size = 1536; # tar block size, to calc volumes size |
1149 | dev | 37 | my $backup_file_base; |
38 | my $prog_gzip_filter; |
||
39 | my $cur_increment_level; |
||
40 | my $cur_dir; |
||
41 | my $cur_path; |
||
42 | my $cur_file; |
||
43 | my $cur_pathitem; |
||
44 | my $file_fullpath; |
||
45 | my $file_fullpath_md5; |
||
46 | my $key; |
||
47 | my $dbobj_new; |
||
48 | my $dbobj_last; |
||
49 | my $db_hashinfo; |
||
50 | my $db_hashinfo2; |
||
51 | my $file; |
||
52 | my @volume_position=(0); |
||
1258 | dev | 53 | my @fs_path=(); # /dir[/file] |
54 | my @fs_notpath=(); # ! |
||
55 | my @fs_mask=(); # =~ |
||
56 | my @fs_filemask=(); # f~ |
||
57 | my @fs_dirmask=(); # d~ |
||
58 | my @fs_notmask=(); # =! |
||
59 | my @fs_notfilemask=(); # f! |
||
60 | my @fs_notdirmask=(); # d! |
||
1149 | dev | 61 | |
1258 | dev | 62 | # Load the config |
63 | if($ARGV[0] eq "-n" || $ARGV[0] eq "-c") { |
||
64 | $config = $ARGV[1]; |
||
1149 | dev | 65 | } |
1258 | dev | 66 | else { |
67 | $config = $ARGV[0]; |
||
68 | } |
||
1149 | dev | 69 | |
1258 | dev | 70 | if(!-f $config) { |
71 | die "Usage: fsbackup.pl [-n|-c] config_name\n"; |
||
1149 | dev | 72 | } |
73 | |||
74 | require "$config"; |
||
75 | |||
1258 | dev | 76 | $cfg_move_old_backup = 1 if(!defined($cfg_move_old_backup)); |
77 | $cfg_exit_on_empty = 1 if(!defined($cfg_exit_on_empty)); |
||
1154 | dev | 78 | |
1258 | dev | 79 | if(! -d $cfg_cache_dir) { |
80 | die "\$cfg_cache_dir ($cfg_cache_dir) not found. Set \$cfg_cache_dir variable in fsbackup.pl\n"; |
||
1149 | dev | 81 | } |
82 | |||
1258 | dev | 83 | # Convert config values |
84 | $cfg_time_limit *= 60 * 60 * 24; |
||
85 | $cfg_size_limit *= 1024; |
||
86 | $cfg_maximum_archive_size *= 1024; |
||
1149 | dev | 87 | |
1258 | dev | 88 | # Command line args |
89 | if($ARGV[0] eq "-n" || $ARGV[0] eq "-c") { |
||
90 | $cfg_new_flag = 1; |
||
1149 | dev | 91 | } |
92 | |||
1258 | dev | 93 | if($ARGV[0] eq "-c") { |
94 | $cfg_clean_flag = 1; |
||
1149 | dev | 95 | } |
96 | |||
1258 | dev | 97 | # Check config |
98 | if($cfg_backup_name !~ /^[\w\d\_.]+$/) { |
||
99 | die "Found illegal characters in $cfg_backup_name ($cfg_backup_name)."; |
||
1149 | dev | 100 | } |
101 | |||
1258 | dev | 102 | if(!grep {$_ eq $cfg_checksum} ("md5", "timesize")) { |
1149 | dev | 103 | die "Unknown checksum method:\$cfg_checksum=$cfg_checksum (allowed md5 or timesize)\n"; |
104 | } |
||
105 | |||
1258 | dev | 106 | if(! -d $cfg_local_path) { |
107 | die "Can't find \$cfg_local_path ($cfg_local_path)"; |
||
1149 | dev | 108 | } |
109 | |||
1258 | dev | 110 | my($sec, $min, $hour, $mday, $mon, $year) = localtime($cur_time); |
111 | $backup_file_base = sprintf("%s-%4.4d.%2.2d.%2.2d.%2.2d.%2.2d.%2.2d", |
||
112 | $cfg_backup_name, $year+1900, $mon+1, $mday, $hour, $min, $sec); |
||
1149 | dev | 113 | |
1258 | dev | 114 | # Prepare |
115 | print "Creating $cfg_backup_name\n" if($cfg_verbose == &VERB_ALL); |
||
1149 | dev | 116 | |
1258 | dev | 117 | chdir($cfg_root_path); |
1149 | dev | 118 | |
1258 | dev | 119 | if(! -d "$cfg_cache_dir/$cfg_backup_name") { |
120 | mkdir("$cfg_cache_dir/$cfg_backup_name", 0700); |
||
1149 | dev | 121 | } |
122 | |||
1258 | dev | 123 | # Calc increment level |
124 | if($cfg_increment_level != 0) { |
||
125 | if(open(INCREMENT_LEVEL, "<$cfg_cache_dir/$cfg_backup_name/.increment_level")) { |
||
126 | $cur_increment_level = <INCREMENT_LEVEL>; |
||
127 | $cur_increment_level++; |
||
128 | close(INCREMENT_LEVEL); |
||
129 | } |
||
130 | else { |
||
131 | print "Can't open increment level file ($cfg_cache_dir/$cfg_backup_name/.increment_level).\n"; |
||
132 | $cur_increment_level = 0; |
||
133 | } |
||
1149 | dev | 134 | |
1258 | dev | 135 | if($cur_increment_level >= $cfg_increment_level) { |
136 | $cur_increment_level=0; |
||
137 | } |
||
1149 | dev | 138 | |
1258 | dev | 139 | if($cur_increment_level == 0) { |
140 | $cfg_new_flag = 1; |
||
141 | $cfg_clean_flag = 1; |
||
142 | } |
||
1149 | dev | 143 | |
1258 | dev | 144 | if(open(INCREMENT_LEVEL, ">$cfg_cache_dir/$cfg_backup_name/.increment_level")) { |
145 | print INCREMENT_LEVEL $cur_increment_level; |
||
146 | close(INCREMENT_LEVEL); |
||
147 | } |
||
148 | else { |
||
149 | print "Can't save increment level to file ($cfg_cache_dir/$cfg_backup_name/.increment_level).\n"; |
||
150 | } |
||
151 | print "Current increment number: $cur_increment_level\n" if($cfg_verbose == &VERB_ALL); |
||
1149 | dev | 152 | } |
153 | |||
1258 | dev | 154 | # Load old cache |
155 | if(-f "$cfg_cache_dir/$cfg_backup_name/.hash" && $cfg_new_flag == 0) { |
||
156 | rename("$cfg_cache_dir/$cfg_backup_name/.hash", "$cfg_cache_dir/$cfg_backup_name/.hash.last"); |
||
157 | $db_hashinfo = new DB_File::HASHINFO ; |
||
158 | $db_hashinfo->{'cachesize'} = DB_DEF_CACHE_SIZE; |
||
159 | unless($dbobj_last = tie(%active_hash_last, "DB_File", "$cfg_cache_dir/$cfg_backup_name/.hash.last", |
||
160 | O_RDWR|O_CREAT, 0644, $db_hashinfo)) |
||
161 | { |
||
162 | print "WARNING: Error in hash, creating full backup.\n"; |
||
163 | unlink "$cfg_cache_dir/$cfg_backup_name/.hash.last"; |
||
164 | } |
||
1149 | dev | 165 | } |
166 | |||
1258 | dev | 167 | # Create new cache |
1149 | dev | 168 | unlink("$cfg_cache_dir/$cfg_backup_name/.hash"); |
169 | $db_hashinfo2 = new DB_File::HASHINFO ; |
||
1258 | dev | 170 | $db_hashinfo2->{'cachesize'} = 100000; |
171 | $dbobj_new = tie(%active_hash_new, "DB_File", "$cfg_cache_dir/$cfg_backup_name/.hash", |
||
172 | O_RDWR|O_CREAT, 0644, $db_hashinfo2) || print "Can't create or open DB File!\n"; |
||
1149 | dev | 173 | |
1258 | dev | 174 | # Save meta info |
175 | open(META, ">$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.meta") |
||
176 | || print "Can't create meta file ($cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.meta).\n"; |
||
1154 | dev | 177 | print META "fsbackup $version\n"; |
178 | print META "increment level: $cur_increment_level\n"; |
||
179 | close(META); |
||
180 | |||
1258 | dev | 181 | # Create list of files to save |
182 | open(LIST, ">$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list") |
||
183 | || print "Can't create list file ($cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list).\n"; |
||
184 | flock(LIST, 2); |
||
1151 | dev | 185 | |
1258 | dev | 186 | # Load list of dirs and files to backup from the config |
187 | while(<DATA>) { |
||
188 | chomp; |
||
189 | if(/^\!(.*)$/) { # ! |
||
190 | push @fs_notpath, $1; |
||
191 | } |
||
192 | elsif(/^\=\~(.*)$/){ # =~ |
||
193 | push @fs_mask, $1; |
||
194 | } |
||
195 | elsif(/^f\~(.*)$/){ # f~ |
||
196 | push @fs_filemask, $1; |
||
197 | } |
||
198 | elsif(/^d\~(.*)$/){ # d~ |
||
199 | push @fs_dirmask, $1; |
||
200 | } |
||
201 | elsif(/^\=\!(.*)$/){ # =! |
||
202 | push @fs_notmask, $1; |
||
203 | } |
||
204 | elsif(/^f\!(.*)$/){ # f! |
||
205 | push @fs_notfilemask, $1; |
||
206 | } |
||
207 | elsif(/^d\!(.*)$/){ # d! |
||
208 | push @fs_notdirmask, $1; |
||
209 | } |
||
210 | elsif(/^#/ || /^\s*$/){ # comment |
||
211 | next; |
||
212 | } |
||
213 | elsif(/[\/\w]+/) { # /dir[/file] |
||
214 | push @fs_path, $_; |
||
215 | } |
||
216 | else { |
||
217 | print STDERR "Syntax error: $_, ingnored.\n"; |
||
218 | } |
||
219 | } |
||
1149 | dev | 220 | |
1258 | dev | 221 | # Find dirs and files to backup |
222 | foreach $cur_pathitem (@fs_path) { |
||
223 | print "Adding $cur_pathitem ... " if($cfg_verbose == &VERB_ALL); |
||
224 | find(\&add_to_backup, $cur_pathitem); |
||
225 | print "done\n" if($cfg_verbose == &VERB_ALL); |
||
1149 | dev | 226 | } |
1258 | dev | 227 | close(LIST); |
1149 | dev | 228 | |
1258 | dev | 229 | # Create list of deleted files and dirs |
230 | open(DEL, ">$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.del") |
||
231 | || print "Can't create list file ($cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.del).\n"; |
||
232 | flock (DEL, 2); |
||
233 | while(($file, $key) = each(%active_hash_last)) { |
||
234 | $file =~ s/\'/\'\\\'\'/g; |
||
235 | $file =~ s/^\/(.*)$/$1/; |
||
236 | print DEL "rm -rf '$file'\n"; |
||
237 | $del_lines_cnt++; |
||
1149 | dev | 238 | } |
1258 | dev | 239 | close(DEL); |
1149 | dev | 240 | |
1258 | dev | 241 | # Save the cache |
1149 | dev | 242 | $dbobj_new->sync(); |
243 | untie %active_hash_new; |
||
244 | untie %active_hash_last; |
||
245 | |||
1258 | dev | 246 | # Goto root because all paths are without leading / |
247 | chdir("/"); |
||
1149 | dev | 248 | |
1258 | dev | 249 | # Tar them |
250 | if($cfg_exit_on_empty == 1 && $list_lines_cnt == 0 && $del_lines_cnt == 0) { |
||
251 | print "WARNING: Nothing to backup.\n"; |
||
252 | exit; |
||
1149 | dev | 253 | } |
1258 | dev | 254 | |
255 | print "Storing local backup\n" if($cfg_verbose == &VERB_ALL); |
||
256 | if($cfg_clean_flag == 1) { # delete old backup |
||
257 | if($cfg_save_old_backup == 0) { |
||
258 | system("$prog_rm -f $cfg_local_path/*"); |
||
259 | } |
||
260 | elsif($cfg_move_old_backup == 1) { |
||
261 | system("mkdir $cfg_local_path/OLD") if(!-d "$cfg_local_path/OLD"); |
||
262 | system("$prog_rm -f $cfg_local_path/OLD/*"); |
||
263 | system("mv -f $cfg_local_path/$cfg_backup_name* $cfg_local_path/OLD/"); |
||
264 | } |
||
1149 | dev | 265 | } |
1258 | dev | 266 | system("cp -f $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.meta $cfg_local_path/$backup_file_base.meta") |
267 | == 0 || print "Local FS .meta copy failed: $?\n"; |
||
268 | system("cp -f $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list $cfg_local_path/$backup_file_base.list") |
||
269 | == 0 || print "Local FS .list copy failed: $?\n"; |
||
270 | system("cp -f $cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.del $cfg_local_path/$backup_file_base.del") |
||
271 | == 0 || print "Local FS .del copy failed: $?\n"; |
||
1149 | dev | 272 | |
1258 | dev | 273 | # Split to volumes |
274 | for($arc_block_level = 0; $arc_block_level <= $#volume_position; $arc_block_level++) { |
||
275 | my $tmp_list_file = create_tmp_list($arc_block_level, $volume_position[$arc_block_level], |
||
276 | $volume_position[$arc_block_level+1], "$cfg_cache_dir/$cfg_backup_name/$cfg_backup_name.list"); |
||
277 | system("$prog_tar -c -f - -n -z -T $tmp_list_file > $cfg_local_path/$backup_file_base-$arc_block_level.tar.gz") == 0 || print "Local FS tar backup failed: $?\n"; |
||
1149 | dev | 278 | } |
279 | |||
1258 | dev | 280 | print "***** Backup successful complete.\n" if($cfg_verbose == &VERB_ALL); |
1149 | dev | 281 | exit (0); |
282 | |||
283 | ######################################## |
||
1253 | dev | 284 | sub add_to_backup |
285 | { |
||
286 | my($file_name, $file_dir, $md5_checksum_stat, $checksum_stat, $file_fullpath_esc); |
||
1258 | dev | 287 | my($tmp, $stat_mode, $stat_uid, $stat_gid, $stat_size, $stat_time, $stat_flags); |
1149 | dev | 288 | |
1253 | dev | 289 | $file_name = $_; |
290 | $file_fullpath = $File::Find::name; |
||
291 | $file_dir = $File::Find::dir; |
||
292 | $file_fullpath_esc = $file_fullpath; |
||
1149 | dev | 293 | $file_fullpath_esc =~ s/\'/\'\\\'\'/g; |
294 | |||
1258 | dev | 295 | # dir |
296 | if((-d $file_fullpath) && (!-l $file_fullpath)) { |
||
1253 | dev | 297 | if(check_path($file_dir, $file_name) == 1) { |
1258 | dev | 298 | ($tmp, $tmp, $stat_mode, $tmp, $stat_uid, $stat_gid, $tmp, $stat_size, $tmp, $tmp, |
299 | $stat_time, $tmp, $tmp, $tmp, $tmp, $tmp, $stat_flags, $tmp) = lstat($file_fullpath); |
||
300 | $checksum_stat = md5_base64("$stat_time/$stat_mode/$stat_uid/$stat_gid"); |
||
301 | $file_fullpath_esc =~ s/^\/(.*)$/$1/; |
||
1228 | dev | 302 | |
1258 | dev | 303 | $active_hash_new{$file_fullpath} = "D $checksum_stat"; |
304 | check_update($file_fullpath, "D $checksum_stat", $file_fullpath, $stat_size); |
||
305 | |||
306 | if($stat_flags & UF_NODUMP) { |
||
307 | push @fs_notpath, "$file_dir/$file_name"; # if nodump - skip all subitems |
||
308 | if($cfg_stopdir_prune == 1) { |
||
309 | $File::Find::prune = 1; |
||
310 | return; |
||
1253 | dev | 311 | } |
1149 | dev | 312 | } |
1253 | dev | 313 | } |
314 | else { |
||
315 | if($cfg_stopdir_prune == 1) { |
||
316 | $File::Find::prune = 1; |
||
317 | return; |
||
318 | } |
||
319 | } |
||
320 | } |
||
1258 | dev | 321 | # file or symlink |
1253 | dev | 322 | elsif((-f $file_fullpath) || (-l $file_fullpath)) { |
323 | if(check_path($file_dir, $file_name) == 1) { |
||
1258 | dev | 324 | ($tmp, $tmp, $stat_mode, $tmp, $stat_uid, $stat_gid, $tmp, $stat_size, $tmp, $tmp, |
1253 | dev | 325 | $stat_time, $tmp, $tmp, $tmp, $tmp, $tmp, $stat_flags, $tmp) = lstat($file_fullpath); |
326 | unless($stat_flags & UF_NODUMP) { |
||
1258 | dev | 327 | $checksum_stat = md5_base64("$stat_time/$stat_size/$stat_mode/$stat_uid/$stat_gid"); |
1253 | dev | 328 | $file_fullpath_md5 = $file_fullpath; |
1258 | dev | 329 | |
330 | if($cfg_time_limit != 0 && $cur_time - $cfg_time_limit > $stat_time) { |
||
331 | print "Time limit: $cur_time - $cfg_time_limit > $stat_time, file $file_fullpath ignored.\n" |
||
1253 | dev | 332 | if($cfg_verbose == &VERB_ALL); |
333 | next; |
||
334 | } |
||
335 | if($cfg_size_limit != 0 && $cfg_size_limit < $stat_size) { |
||
336 | print "Size limit: $cfg_size_limit < $stat_size, file $file_fullpath ignored.\n" |
||
337 | if($cfg_verbose == &VERB_ALL); |
||
338 | next; |
||
339 | } |
||
1149 | dev | 340 | |
1253 | dev | 341 | if(($cfg_checksum eq "md5") && (! -l $file_fullpath)) { |
342 | ($md5_checksum_stat, $tmp) = split(/\s+/, `$prog_md5sum '$file_fullpath_esc'`); |
||
1258 | dev | 343 | $active_hash_new{$file_fullpath_md5} = "F $checksum_stat/$md5_checksum_stat"; |
344 | check_update($file_fullpath, "F $checksum_stat/$md5_checksum_stat", $file_fullpath_md5, $stat_size); |
||
1205 | dev | 345 | } |
1258 | dev | 346 | else { |
347 | $active_hash_new{$file_fullpath} = "F $checksum_stat"; |
||
348 | check_update($file_fullpath, "F $checksum_stat", $file_fullpath, $stat_size); |
||
349 | } |
||
1253 | dev | 350 | } |
1205 | dev | 351 | } |
1253 | dev | 352 | } |
1149 | dev | 353 | } |
354 | |||
355 | ############################################### |
||
1258 | dev | 356 | # Check if file was updated |
357 | sub check_update |
||
358 | { |
||
359 | my($file, $checksum, $filesum, $stat_size) = @_; |
||
1149 | dev | 360 | |
1258 | dev | 361 | if($active_hash_last{$filesum} ne $checksum) { |
362 | $file =~ s/^\/(.*)$/$1/; |
||
363 | print LIST "$file\n"; |
||
364 | |||
365 | $stat_size = 0 if(-l "/$file"); |
||
366 | |||
367 | # next volume |
||
368 | $cur_backup_size += $stat_size + int(length($file)/100.0 + 1)*512; |
||
369 | if($cfg_maximum_archive_size > 0 && $cur_backup_size + 10240 >= $cfg_maximum_archive_size) { |
||
370 | my $old_val = $cur_backup_size - $stat_size - int(length($file)/100.0 + 1)*512; |
||
371 | my $tmp_pos = $#volume_position+1; |
||
372 | print "Volume $tmp_pos Done. Size: $old_val\n" if ($cfg_verbose == &VERB_ALL); |
||
373 | $cur_backup_size = $stat_size + int(length($file)/100.0 + 1)*512 + 1536; |
||
374 | push @volume_position, $list_lines_cnt; |
||
375 | } |
||
376 | $list_lines_cnt++; |
||
377 | } |
||
1149 | dev | 378 | |
1258 | dev | 379 | if($active_hash_last{$filesum} && substr($active_hash_last{$filesum}, 0, 1) ne substr($checksum, 0, 1)) { |
380 | # if old entry was a directory and now it's file or link or vice versa, leave it in hash |
||
381 | # to add it later to the delete list |
||
382 | } |
||
383 | else { |
||
384 | delete $active_hash_last{$filesum}; |
||
385 | $dbobj_last->del($filesum) if(defined $dbobj_last); |
||
386 | } |
||
1149 | dev | 387 | } |
388 | |||
389 | ############################################### |
||
1258 | dev | 390 | # 0 - don't add the file |
391 | # 1 - add the file |
||
392 | sub check_path |
||
393 | { |
||
394 | my ($dir_name, $file_name) = @_; |
||
395 | my ($item, $path); |
||
1149 | dev | 396 | |
1258 | dev | 397 | $path = "$dir_name/$file_name"; |
1149 | dev | 398 | |
1258 | dev | 399 | foreach $item (@fs_notmask) { |
400 | return 0 if($path =~ /$item/); |
||
401 | } |
||
1151 | dev | 402 | |
1258 | dev | 403 | foreach $item (@fs_notfilemask) { |
404 | return 0 if($file_name =~ /$item/); |
||
405 | } |
||
1151 | dev | 406 | |
1258 | dev | 407 | foreach $item (@fs_filemask) { |
408 | return 1 if($file_name =~ /$item/); |
||
409 | } |
||
1149 | dev | 410 | |
1258 | dev | 411 | foreach $item (@fs_notdirmask) { |
412 | return 0 if($dir_name =~ /$item/); |
||
413 | } |
||
1149 | dev | 414 | |
1258 | dev | 415 | foreach $item (@fs_mask) { |
416 | return 1 if($path =~ /$item/); |
||
417 | } |
||
1149 | dev | 418 | |
1258 | dev | 419 | foreach $item (@fs_dirmask) { |
420 | return 1 if($dir_name =~ /$item/); |
||
421 | } |
||
1149 | dev | 422 | |
1258 | dev | 423 | foreach $item (@fs_notpath) { |
424 | return 0 if(($dir_name eq $item) || ($path eq $item) || ($dir_name =~ /^$item\//)); |
||
425 | } |
||
1149 | dev | 426 | |
1258 | dev | 427 | return 1; |
428 | } |
||
1149 | dev | 429 | |
430 | ############################################### |
||
1258 | dev | 431 | # Split the big list into volume list |
432 | sub create_tmp_list |
||
433 | { |
||
434 | my($arc_block_level, $position1, $position2, $full_list_path) = @_; |
||
435 | my($tmp_list_path, $pos_counter); |
||
1149 | dev | 436 | |
1258 | dev | 437 | if($arc_block_level == 0 && $position1 == 0 && $position2 eq '') { |
438 | $tmp_list_path = $full_list_path; |
||
439 | } |
||
440 | else { |
||
441 | $pos_counter = 0; |
||
442 | $tmp_list_path = "$full_list_path.$arc_block_level"; |
||
443 | open(FULL_LIST, "<$full_list_path") || die "Can't open full list $full_list_path\n"; |
||
444 | flock(FULL_LIST, 1); |
||
445 | open(TMP_LIST, ">$tmp_list_path") || die "Can't create temp list $tmp_list_path\n"; |
||
446 | flock(TMP_LIST, 2); |
||
447 | while(<FULL_LIST>) { |
||
448 | print TMP_LIST $_ if(($pos_counter >= $position1) && ($pos_counter < $position2 || $position2 eq '')); |
||
449 | $pos_counter++; |
||
1149 | dev | 450 | } |
1258 | dev | 451 | close(TMP_LIST); |
452 | close(FULL_LIST); |
||
453 | } |
||
454 | return $tmp_list_path; |
||
1149 | dev | 455 | } |
456 | |||
457 | ############################################### |
||
458 | ############################################### |
||
459 | |||
460 | __END__ |
||
461 | |||
462 | =head1 NAME |
||
463 | |||
464 | fsbackup - file system backup and synchronization utility. |
||
465 | |||
466 | =head1 SYNOPSIS |
||
467 | |||
468 | fsbackup.pl [options] <configuration file> |
||
469 | |||
470 | =head1 DESCRIPTION |
||
471 | |||
472 | C<fsbackup.pl> is a incremental backup creation utility. |
||
1258 | dev | 473 | C<fsbackup.pl> support backup compression and encryption. Some addition |
1149 | dev | 474 | scripts allow backups SQL tables from PostgreSQL and MySQL (C<pgsql_backup.sh> |
475 | and C<mysql_backup.sh>)), save system configuration files and list of installed |
||
476 | packages (C<sysbackup.sh>). |
||
477 | Backuped with C<fsbackup.pl> files can be recovered by script C<fsrestore.sh>, |
||
478 | backuped with C<sysbackup.sh> system packeges can be reinstalled by C<sysrestore.sh> |
||
479 | |||
480 | =head1 OPTIONS |
||
481 | |||
482 | The following command-line options can be used with C<fsbackup.pl>: |
||
483 | |||
484 | =over |
||
485 | |||
486 | =item C<-n> |
||
487 | |||
488 | Create new backup without checking files in previously stored hash. |
||
489 | |||
490 | =item C<-f> |
||
491 | |||
492 | Create full backup, like as C<-n> option. |
||
493 | |||
494 | =item C<-h> |
||
495 | |||
496 | Only rebuild hash, no storing files in backup archive. |
||
497 | |||
498 | =item C<-c> |
||
499 | |||
500 | Clean incremental backup storage and create new full backup without checking |
||
501 | $cfg_increment_level config parameter. |
||
502 | |||
503 | =head1 ADDITION SCRIPTS |
||
504 | |||
505 | =item C<create_backup.sh> |
||
506 | |||
507 | Backup planner running from C<crontab>. For example: |
||
508 | |||
1154 | dev | 509 | 18 4 * * * /root/backup/fsbackup/create_backup.sh |
1149 | dev | 510 | |
511 | =item C<install.pl> |
||
512 | |||
513 | Script to install fsbackup package and some required perl modules. |
||
514 | |||
515 | =item C<fsbackup.pl> |
||
516 | |||
517 | File system backup utility. |
||
518 | |||
519 | =item C<cfg_example> |
||
520 | |||
521 | Example of configuration file. |
||
522 | |||
523 | =item C<scripts/pgsql_backup.sh> |
||
524 | |||
525 | =item C<scripts/mysql_backup.sh> |
||
526 | |||
527 | Script for backup SQL tables from PostreSQL and MySQL. |
||
528 | |||
529 | =item C<scripts/sysbackup.sh> |
||
530 | |||
531 | Script for store system configuration files and information about installed |
||
532 | packages. |
||
533 | |||
534 | =item C<scripts/fsrestore.sh> |
||
535 | |||
536 | Script for restore files backuped by C<fsbackup.pl>. |
||
537 | |||
538 | =item C<scripts/sysrestore.sh> |
||
539 | |||
540 | Script for reinstall packages stored by C<sysbackup.sh>. |
||
541 | |||
542 | |||
543 | =head1 CONFIGURATION FILE |
||
544 | |||
545 | =item B<$cfg_backup_name> = 'test_host' |
||
546 | |||
547 | Name of backup, single word. |
||
548 | |||
1154 | dev | 549 | =item B<$cfg_cache_dir> = '/root/backup/fsbackup/cache' |
1149 | dev | 550 | |
551 | Path of internal cache directory for local backup method. |
||
552 | |||
553 | =item B<$prog_md5sum> = 'md5sum -b' |
||
554 | |||
555 | =item B<$prog_tar> = 'tar' |
||
556 | |||
557 | =item B<$prog_rm> = 'rm' |
||
558 | |||
559 | =item B<$cfg_checksum> = 'timesize' |
||
560 | |||
561 | File checksum method: |
||
562 | |||
563 | timesize - checksum of file attributes (default, best speed) |
||
564 | |||
565 | md5 - checksum of file attributes + MD5 checksum of file content. |
||
566 | |||
567 | =item B<$cfg_increment_level> = 7 |
||
568 | |||
569 | Incremental level (after how many incremental copy make full refresh of backup) |
||
570 | |||
571 | =item B<$cfg_local_path> = '/var/backup/' |
||
572 | |||
573 | Path of directory to store backup on local file system for local storage type. |
||
574 | |||
575 | =item B<$cfg_time_limit> = 0 |
||
576 | |||
577 | Limit of file creation time in days. If not 0, don't backup files created or |
||
578 | modified later then $cfg_time_limit (days). |
||
579 | |||
580 | =item B<$cfg_size_limit> = 0 |
||
581 | |||
582 | Limit of maximum file size. If not 0, don't backup files witch size more then |
||
583 | $cfg_time_limit kilobytes. |
||
584 | |||
585 | =item B<$cfg_root_path> = '/' |
||
586 | |||
587 | Root path for initial chdir. |
||
588 | |||
589 | =item B<$cfg_verbose> = 3 |
||
590 | |||
591 | Verbose level. |
||
592 | |||
1258 | dev | 593 | |
594 | 1 - Output errors and warnings. |
||
595 | 2 - Output all the available data. |
||
1149 | dev | 596 | |
597 | =item B<$cfg_save_old_backup> = 1 |
||
598 | |||
599 | Save previous backup to OLD directory before rotation or before storing full backup. |
||
600 | |||
601 | |||
602 | 1 - save old backup. |
||
603 | |||
604 | =item B<$cfg_maximum_archive_size> = 0 |
||
605 | |||
606 | Size of maximum size (in KiloBytes) of single unpacked archive file (0 - unlimited file size). |
||
607 | |||
608 | =item B<$cfg_stopdir_prune> = 0 |
||
609 | |||
610 | Recursive review of the prohibited directories. |
||
611 | |||
612 | 1 - not use a recursive entrance to directory prohibited for backup (speed is increased, reduces flexibility of customization). |
||
613 | |||
614 | =item B<__DATA__> - list of backuped path and regexp mask. |
||
615 | |||
616 | /dir[/file] - backup file or directory. |
||
617 | !/dir[/file] - NOT include this file or directory to backup. |
||
618 | # - ignore this line. |
||
619 | |||
620 | Mask: |
||
621 | |||
622 | =~ - regexp mask for include file or directory to backup. |
||
623 | f~ - regexp file mask for include file to backup. |
||
624 | d~ - regexp directory mask for include directory to backup. |
||
625 | =! - regexp mask for NOT include file or directory to backup. |
||
626 | f! - regexp file mask for NOT include file to backup. |
||
627 | d! - regexp directory mask for NOT include directory to backup. |
||
628 | |||
629 | |||
630 | Operation priority: |
||
631 | |||
632 | 1. =! |
||
633 | 2. f! |
||
634 | 3. f~ |
||
635 | 4. d! |
||
636 | 5. =~ |
||
637 | 6. d~ |
||
638 | 7. !path |
||
639 | 8. path |
||
640 | |||
641 | |||
642 | =head1 COPYRIGHT |
||
643 | |||
644 | Copyright (c) 2001 by Maxim Chirkov <mc@tyumen.ru> |
||
645 | http://www.opennet.ru/dev/fsbackup/ |
||
646 | |||
1258 | dev | 647 | Copyright (c) 2003-2006 by Anatoli Klassen. <anatoli@aksoft.net> |
648 | http://www.26th.net/public/projects/fsbackup/ |
||
649 | |||
1149 | dev | 650 | =head1 BUGS |
651 | |||
652 | Look TODO file. |
||
653 | |||
654 | =head1 AUTHORS |
||
655 | |||
656 | Maxim Chirkov <mc@tyumen.ru> |
||
1258 | dev | 657 | Anatoli Klassen <anatoli@aksoft.net> |
1149 | dev | 658 | |
659 | =cut |