Gro├čes Update:
[ipfire-2.x.git] / html / cgi-bin / backup.cgi
1 #!/usr/bin/perl
2 #
3 # IPFire CGI's - backup.cgi: manage import/export of configuration files
4 #
5 # This code is distributed under the terms of the GPL
6 #
7 # (c) The IPFire Team
8 # 2005  Franck Bourdonnec, major rewrite
9 #
10 # $Id: backup.cgi,v 1.2.2.15 2006/01/29 15:31:49 eoberlander Exp $
11 #
12 #
13
14
15 # to fully troubleshot your code, uncomment diagnostics, Carp and cluck lines
16 # use diagnostics; # need to add the file /usr/lib/perl5/5.8.x/pods/perldiag.pod before to work
17 # next look at /var/log/httpd/error_log , http://www.perl.com/pub/a/2002/05/07/mod_perl.html may help
18 #use warnings;
19 use strict;
20 #use Carp ();
21 #local $SIG{__WARN__} = \&Carp::cluck;
22 use File::Copy;
23 use Sys::Hostname;
24
25 require 'CONFIG_ROOT/general-functions.pl';
26 require "${General::swroot}/lang.pl";
27 require "${General::swroot}/header.pl";
28
29 my $errormessage = '';
30 my $warnmessage = '';
31 my $setdir = '/home/httpd/html/backup'; # location where sets are stored and imported
32 my $datafile = hostname() . '.dat';     # file containing data backup
33 my $datefile = $datafile . '.time';     # and creation date
34
35 # ask if backup crypting key exists
36 my $tmpkeyfile = "$setdir/key";         # import the backup key
37
38 # Get GUI values
39 my %settings = ();
40 &Header::getcgihash(\%settings, {'wantfile' => 1, 'filevar' => 'FH'});
41
42 ##
43 ## Backup key management
44 ##
45
46 #
47 # Export the key. root pw is required to avoid user 'noboby' uses the helper to read it and creates
48 # fake backup.
49 #
50 if ($settings{'ACTION'} eq $Lang::tr{'backup export key'})  {
51
52     my $size = 0;
53     if ($settings{'PASSWORD1'} ne '' && $settings{'PASSWORD1'} ne $settings{'PASSWORD2'} ){
54         $errormessage = $Lang::tr{'passwords do not match'}
55     } else {
56         my @lines = `/usr/local/bin/ipfirebackup -keycat $settings{'PASSWORD'}`;
57         # If previous operation succeded and the key need to be crypted, redo operation with pipe to openssl
58         if (@lines && $settings{'PASSWORD1'}) {
59             @lines = `/usr/local/bin/ipfirebackup -keycat $settings{'PASSWORD'}|openssl enc -a -e -aes256 -salt -pass pass:$settings{'PASSWORD1'} `;
60         }
61         if (@lines) {
62             use bytes;
63             foreach (@lines) {$size += length($_)};
64             print "Pragma: no-cache\n";
65             print "Cache-control: no-cache\n";
66             print "Connection: close\n";
67             print "Content-type: application/octet-stream\n";
68             print "Content-Disposition: filename=backup.key\n";
69             print "Content-Length: $size\n\n";
70             print @lines;
71             exit (0);
72         } else {
73             $errormessage = $Lang::tr{'incorrect password'};
74         }
75     }   
76 }
77 #
78 #  Import the key. Fail if key exists. This avoid creating fake backup.
79 #
80 if ($settings{'ACTION'} eq $Lang::tr{'backup import key'})  {
81     if (ref ($settings{'FH'}) ne 'Fh') {
82         $errormessage = $Lang::tr{'no cfg upload'};
83     } else {
84         if (copy ($settings{'FH'}, $tmpkeyfile) != 1) {
85             $errormessage = $Lang::tr{'save error'};
86         } else {
87             # if a password is given, decrypt the key received in $tmpkeyfile file with it.
88             # no error is produce if the password is wrong.
89             if ($settings{'PASSWORD1'}) {
90                 my @lines = `openssl enc -a -d -aes256 -salt -pass pass:$settings{'PASSWORD1'} -in $tmpkeyfile`;
91                 open(FILE,">$tmpkeyfile");
92                 print FILE @lines;
93                 close (FILE);
94             }
95             $errormessage = &get_bk_error(system ('/usr/local/bin/ipfirebackup -key import')>>8);
96         }
97     }
98 }
99 #
100 #  Import the key. Fail if key exists. Key is extracted from a non-encrypted backup (pre 1.4.10)
101 #
102 if ($settings{'ACTION'} eq $Lang::tr{'backup extract key'})  {
103     if (ref ($settings{'FH'}) ne 'Fh') {
104         $errormessage = $Lang::tr{'no cfg upload'};
105     } else {
106         if (copy ($settings{'FH'}, '/tmp/tmptarfile.tgz') != 1) {
107             $errormessage = $Lang::tr{'save error'};
108         } else {
109             system( "tar -C /tmp -xzf /tmp/tmptarfile.tgz */backup/backup.key;\
110                     mv -f /tmp${General::swroot}/backup/backup.key $tmpkeyfile;\
111                     rm -rf /tmp${General::swroot};\
112                     rm /tmp/tmptarfile.tgz");
113             $errormessage = &get_bk_error(system ('/usr/local/bin/ipfirebackup -key import')>>8);
114         }
115     }
116 }
117 #
118 #  Create the key. Cannot overwrite existing key to avoid difference with exported (saved) key
119 #
120 if ($settings{'ACTION'} eq $Lang::tr{'backup generate key'})  {
121     $errormessage = &get_bk_error(system('/usr/local/bin/ipfirebackup -key new')>>8);
122 }
123
124 my $cryptkeymissing = system ('/usr/local/bin/ipfirebackup -key exist')>>8;
125
126 &Header::showhttpheaders();
127 if ($cryptkeymissing) {  #If no key is present, force creation or import
128     &Header::openpage($Lang::tr{'backup configuration'}, 1, '');
129     &Header::openbigbox('100%', 'left', '', $errormessage);
130     if ($errormessage) {
131         &Header::openbox('100%', 'left', $Lang::tr{'error messages'});
132         print "<font class='base'>$errormessage&nbsp;</font>";
133         &Header::closebox();
134     }
135     &Header::openbox('100%', 'left', $Lang::tr{'backup key'});
136     print <<END
137     <form method = 'post' enctype = 'multipart/form-data'>
138       <table>
139         <tr>
140           <td colspan='2'>
141           $Lang::tr{'backup explain key'}:
142           <ul>
143           <li>$Lang::tr{'backup explain key li1'}
144           <li>$Lang::tr{'backup explain key li2'}
145           <li>$Lang::tr{'backup explain key li3'}
146           </ul>
147           </td>
148         </tr><tr>
149           <td width='15%'></td><td width='20%'></td><td>
150           <input type = 'submit' name = 'ACTION' value = '$Lang::tr{'backup generate key'}' />
151           </td>
152         </tr><tr>
153           <td align='right'>$Lang::tr{'backup key file'}:</td><td><input type = 'file' name = 'FH' size = '30' value='backup.key' />
154           </td><td>
155           <input type = 'submit' name = 'ACTION' value = '$Lang::tr{'backup import key'}' />
156         </tr><tr>
157           <td align='right'>$Lang::tr{'backup protect key password'}:<td><input type = 'password' name='PASSWORD1' size='10' />
158           </td>
159         </tr><tr>
160           <td align='right'>$Lang::tr{'backup clear archive'}:</td><td><input type = 'file' name = 'FH' size = '30' value='your-ipfire.tar.gz' />
161           </td><td>
162           <input type = 'submit' name = 'ACTION' value = '$Lang::tr{'backup extract key'}' />
163           </td>
164         </tr>
165       </table>
166       $Lang::tr{'notes'}:
167       <ul>
168           <li>$Lang::tr{'backup explain key no1'}
169           <li>$Lang::tr{'backup explain key no2'}
170       </ul>
171     </form>
172 END
173 ;
174     &floppybox();
175     &Header::closebox();
176     &Header::closebigbox();
177     &Header::closepage();
178     exit (0);
179 }
180
181 ##
182 ## Sets management (create/delete/import/restore)
183 ##
184
185 erase_files ($setdir);                  #clean up
186
187 #
188 # create new archive set
189 #
190 if ($settings{'ACTION'} eq $Lang::tr{'create'}) {
191     $errormessage = &get_bk_error(system('/usr/local/bin/ipfirebkcfg > /dev/null')>>8);
192     &import_set (" ".&Header::cleanhtml ($settings{'COMMENT'})) if (!$errormessage);
193 }
194 #
195 # delete a backup set
196 #
197 if ($settings{'ACTION'} eq $Lang::tr{'remove'}) {
198     erase_files (&Header::cleanhtml ($settings{'KEY'}));        # remove files
199     rmdir($settings{'KEY'});            # remove directory
200 }
201 #
202 # import an archive set
203 #
204 if ($settings{'ACTION'} eq $Lang::tr{'import'}) {
205     if (ref ($settings{'FH'}) ne 'Fh') {
206         $errormessage = $Lang::tr{'no cfg upload'};
207     } else {
208         if (!copy ($settings{'FH'}, "$setdir/$datafile")) {
209             $errormessage = $Lang::tr{'save error'};
210         } else {
211             &import_set ('&nbsp;(imported)');
212         }
213     }
214 }
215 #
216 # restore an archive
217 #
218 if ($settings{'ACTION'} eq $Lang::tr{'restore'}) {
219     if ($settings{'AreYouSure'} eq 'yes') {
220         if (!$cryptkeymissing) {                        # if keyfile exists
221             if (-e "$settings{'KEY'}/$datafile"){       # encrypted dat is required
222                 copy_files($settings{'KEY'}, $setdir);  # to working dir
223                 $errormessage = get_rs_error(system("/usr/local/bin/ipfirerscfg" 
224                                         . ($settings{'RESTOREHW'} eq 'on' ? ' --hardware' : '') 
225                                         . ' >/dev/null')>>8);
226                 if (!$errormessage) {
227                     # restored ok, recommend restarting system
228                     $warnmessage = $Lang::tr{'cfg restart'};
229                 }
230                 erase_files ($setdir);                  #clean up
231             } else {
232                 $errormessage = $Lang::tr{'missing dat'}."$settings{'KEY'}/$datafile";
233             }
234         } else {  # if keyfile does not exist
235             $errormessage = $Lang::tr{'backup missing key'};
236         }
237     
238     } else {  # not AreYouSure=yes
239         &Header::openpage($Lang::tr{'backup configuration'}, 1, '');
240         &Header::openbigbox('100%', 'left');
241         &Header::openbox('100%', 'left', $Lang::tr{'are you sure'});
242         print <<END
243 <form method = 'post'>
244   <input type = 'hidden' name = 'KEY' value ='$settings{'KEY'}' /> 
245   <input type = 'hidden' name = 'AreYouSure' value ='yes' />
246   <table align = 'center'>
247     <tr>
248       <td align = 'center'>
249         <input type = 'submit' name = 'ACTION' value = '$Lang::tr{'restore'}' />
250       </td><td>
251         <input type = 'submit' name = 'ACTION' value = '$Lang::tr{'cancel'}' />
252       </td>
253     </tr><tr>
254       <td>
255         $Lang::tr{'restore hardware settings'}: <input type = 'checkbox' name = 'RESTOREHW'>
256       </td>
257     </tr>
258 </table>
259 </form>
260 END
261 ;
262         &Header::closebox();
263         &Header::closebigbox();
264         &Header::closepage();
265         exit (0);
266     }
267 }
268 ##
269 ##  Media management
270 ##
271 #
272 # now build the list of removable device
273 #
274
275 # Read partitions sizes registered with the system
276 my %partitions;
277 foreach my $li (`/usr/local/bin/ipfirebackup -proc partitions`) {               # use suid helper...
278     # partitions{'sda1'} = 128M        if         /major minor  blocks name/
279     $partitions{$4} = &kmgt($3*1024,4) if ($li =~ /(\d+) +(\d+) +(\d+) +(.*)/);
280 }
281
282 # Search usb-storage scsi device
283 my %medias;
284     
285 foreach (`/usr/local/bin/ipfirebackup -glob '/proc/scsi/usb-storage*/*'`) {# use suid helper...
286     my $m;
287     foreach ( `cat $_` ) {      # list each line of information for the device:
288 #       Host scsi0: usb-storage
289 #       Vendor: SWISSBIT
290 #       Product: Black Silver
291 #       Serial Number: D0ED423A4F84A31E
292 #       Protocol: Transparent SCSI
293 #       Transport: Bulk
294 #       GUID: 13706828d0ed423a4f84a31e
295 #       Attached: Yes
296                                        
297         chomp;
298         my ($key,$val) = split(': ',$_,2);
299         $key =~ s/^ *//;        # remove front space
300
301         # convert 'scsi?' key to sda, sdb,... and use it as a %medias keyhash
302         if ($key =~ /Host scsi(.)/) {
303             $val = $m = 'sd' . chr(97+$1);
304             $key = 'Host';
305         }
306         $medias{$m}{$key} = $val;               # save data
307     }
308 }
309
310 #
311 # Switch mounted media
312 #
313 if ($settings{'ACTION'} eq $Lang::tr{'mount'})
314 {
315     # Find what is really mounted under backup. Can be local hard disk or any removable media
316     my $mounted = &findmounted();
317     #umount previous, even if same device already mouted.
318     system ("/usr/local/bin/ipfirebackup -U $mounted") if ($mounted ne $Lang::tr{'local hard disk'});
319     $errormessage = `/usr/local/bin/ipfirebackup -M $settings{'SELECT'}` if (grep (/$settings{'SELECT'}/,%partitions));
320 }
321 #
322 # Compute a full description of device
323 #
324 my $mounted = &findmounted();
325 my $media_des = $mounted;       # Description
326 if ($mounted ne $Lang::tr{'local hard disk'}) {
327     $_ = $mounted;      # sda1 => sda
328     tr/0-9//d;
329     $media_des = "$medias{$_}{'Product'} ($media_des, $partitions{$mounted})";
330 }
331 &Header::openpage($Lang::tr{'backup configuration'}, 1, '');
332 &Header::openbigbox('100%', 'left', '', $errormessage);
333
334 if ($errormessage) {
335     &Header::openbox('100%', 'left', $Lang::tr{'error messages'});
336     print "<font class='base'>$errormessage&nbsp;</font>";
337     &Header::closebox();
338 }
339
340 $warnmessage = "<font color=${Header::colourred}><b>$Lang::tr{'capswarning'}</b></font>: $warnmessage <p>" if ($warnmessage);
341
342 &Header::openbox('100%', 'left', $Lang::tr{'backup configuration'});
343
344 #Divide the window in two : left and right
345 print <<END
346     <table width = '100%' >
347     <tr>
348         <th width = '50%'>$Lang::tr{'current media'}:<font color=${Header::colourred}><b>$media_des</b></font></th>
349         <th width = '3%'></th>
350         <th>$Lang::tr{'choose media'}</th>
351     </tr>
352 END
353 ;
354
355 # Left part of window
356 print <<END
357     <tr><td>
358     <ul>
359     <li>$Lang::tr{'backup sets'}:
360     <table width = '80%' border='0'>
361     <tr>
362         <th  class = 'boldbase' align = 'center'>$Lang::tr{'name'}</th>
363         <th  class = 'boldbase' align = 'center' colspan = '3'>$Lang::tr{'action'}</th>
364     </tr>
365 END
366 ;
367
368 # get list of available sets by globbing directories under $setdir
369 # External device (usk key) are mounted in $setdir. -R permits finding sets in hierarchy.
370 my $i = 0;
371 foreach my $set (`ls -Rt1 $setdir`) {
372     chop ($set);        #remove ':' & newline from line
373     chop ($set);
374     if (-d $set && ($set =~ m!/.+/\d{8}_\d{6}! ) ) { # filter out things not sets !
375         if ($i++ % 2) {
376             print "<tr bgcolor = '$Header::table2colour'>";
377         } else {
378             print "<tr bgcolor = '$Header::table1colour'>";
379         }
380         my $settime = read_timefile( "$set/$datefile", "$set/$datafile" );
381         my $name = substr ($set,length($setdir)+1);
382         print<<EOF
383 <td>
384     $settime
385 </td>
386
387 <td align = 'center'>
388 <form method = 'post'>
389 <input type = 'hidden' name = 'ACTION' value ='$Lang::tr{'restore'}' />
390 <input type = 'image'  name = '$Lang::tr{'restore'}' src = '/images/reload.gif' alt = '$Lang::tr{'restore'}' title = '$Lang::tr{'restore'}' />
391 <input type = 'hidden' name = 'KEY' value = '$set' />
392 </form>
393 </td>
394
395 <td align = 'center'>
396 <a href = '/backup/$name/$datafile'><img src = '/images/floppy.gif' title = '$Lang::tr{'export'}'></a>
397 </td>
398
399 <td align = 'center'>
400 <form method = 'post'>
401 <input type = 'hidden' name = 'ACTION' value = '$Lang::tr{'remove'}' />
402 <input type = 'image'  name = '$Lang::tr{'remove'}' src = '/images/delete.gif' alt = '$Lang::tr{'remove'}' title = '$Lang::tr{'remove'}' border = '0' />
403 <input type = 'hidden' name = 'KEY' value = '$set' />
404 </form>
405 </td>
406 </tr>
407 EOF
408 ;
409     }
410 }
411 print "</table>" . ($i ? "<br>" : "$Lang::tr{'empty'}!<hr /><br>");
412 print <<EOF
413 $warnmessage
414 <form method = 'post'>
415         <li>$Lang::tr{'backup configuration'}<br>
416         $Lang::tr{'description'}:<input type = 'text' name = 'COMMENT' size='30' />
417         <input type = 'submit' name = 'ACTION' value = '$Lang::tr{'create'}' />
418 </form><p>
419 <form method = 'post' enctype = 'multipart/form-data'>
420         <li>$Lang::tr{'backup import dat file'}:<br>
421         <input type = 'file' name = 'FH' size = '20' />
422         <input type = 'submit' name = 'ACTION' value = '$Lang::tr{'import'}' />
423 </form>
424 </ul>
425 EOF
426 ;
427
428 print "</td><td></td><td valign='top'>";  # Start right part (devices selection)
429 print $Lang::tr{'backup media info'};
430
431 print "<form method = 'post'>";
432 print "<table width = '100%'><tr><td>";
433 my $nodev = 1;             # nothing present
434 foreach my $media (keys %medias) {
435     if ( $medias{$media}{'Attached'} eq 'Yes') {        # device is attached to USB bus ?
436         $nodev = 0;             # at least one device present
437         my $checked = $medias{$media}{'Host'} eq $mounted ? "checked='checked'" : '';
438         print "<input type='radio' name = 'SELECT' value = '$medias{$media}{'Host'}' $checked />";
439         print "<b>$medias{$media}{'Product'}</b><br>";
440         # list attached partitions to this media
441         foreach my $part (sort (keys (%partitions))) {
442             if ($part =~ /$medias{$media}{'Host'}./) {
443                 my $checked = $part eq $mounted ? "checked='checked'" : '';
444                 print "&nbsp;&nbsp;&nbsp;<input type='radio' name = 'SELECT' value = '$part' $checked />$part ($partitions{$part})<br>";
445             }
446         }
447     }
448 }
449 if ($nodev) {
450     print "<br>$Lang::tr{'insert removable device'}";
451     print "</td><td>";
452     print "<br><input type = 'submit' name = 'ACTION' value = '$Lang::tr{'done'}' />";
453 } else {
454     #Add an entry for the local disk
455     my $checked =  $Lang::tr{'local hard disk'} eq $mounted ? "checked='checked'" : '';
456     print "<input type = 'radio' name = 'SELECT' value = '$Lang::tr{'local hard disk'}' $checked />";
457     print "<b>$Lang::tr{'local hard disk'}</b>";
458     print "</td><td>";
459     print "<br><input type = 'submit' name = 'ACTION' value = '$Lang::tr{'mount'}' />";
460 }
461 print "</tr></table>";
462 print "</form>";
463 #
464 #Backup key
465 #
466 print<<EOF
467     <hr />
468 <form method='post'>
469     <b>$Lang::tr{'backup key'}</b><br>
470     $Lang::tr{'backup key info'}<br>
471     <table><tr>
472     <td align= 'right'>$Lang::tr{'root user password'}:
473     <td align='left'><input type = 'password' name='PASSWORD' />
474     <input type = 'submit' name = 'ACTION' value = '$Lang::tr{'backup export key'}' />
475     </tr><tr>
476     <td align='right'>$Lang::tr{'backup protect key password'}:
477     <td align='left'><input type = 'password' name='PASSWORD1' size='10' />
478     </tr><tr>
479     <td align='right'>$Lang::tr{'again'}
480     <td align='left'><input type = 'password' name='PASSWORD2'  size='10'/>
481     </tr></table>
482 </form>
483
484 EOF
485 ;
486 # End of right table
487 print "</td></tr></table>";
488
489 &floppybox();
490
491 &Header::closebox();
492 &Header::closebigbox();
493 &Header::closepage();
494
495 sub floppybox {
496     print <<END
497 <hr />
498 <form method = 'post'>
499 <table width='100%'>
500 <tr>
501     <td>
502          <b>$Lang::tr{'backup to floppy'}</b>
503     </td>
504 </tr>
505 <tr>
506     <td width='50%'>
507         $Lang::tr{'insert floppy'}
508     </td>
509     <td align='center'> 
510         <input type='submit' name='ACTION' value='$Lang::tr{'backup to floppy'}' />
511     </td> 
512 </tr>
513 </table>
514 </form>
515 END
516 ;
517     print   "<b>$Lang::tr{'alt information'}</b><pre>" .
518             `/usr/local/bin/ipfirebackup -savecfg floppy` .
519             '&nbsp;</pre>' if ($settings{'ACTION'} eq $Lang::tr{'backup to floppy'} );
520 }
521
522 # Return device name of what is mounted under 'backup'
523 sub findmounted() {
524     my $mounted = `mount|grep ' /home/httpd/html/backup '`;
525     if ($mounted) {                             # extract device name
526         $mounted =~ m!^/dev/(.*) on!;           # device on mountmoint options
527         return $1; 
528     } else {                                    # it's the normal subdir
529         return $Lang::tr{'local hard disk'};
530     }
531 }
532 # read and return a date/time string from a time file
533 sub read_timefile() {
534     my $fname = shift;   # name of file to read from
535     my $fname2 = shift;  # if first file doesn't exist, get date of this file
536
537     my $dt;
538     if (defined(open(FH, "<$fname"))) {
539         $dt = <FH>;
540         chomp $dt;
541         close(FH);
542     } else {
543         $dt = &get_fdate($fname2);    # get file date/time
544         write_timefile($fname, $dt); # write to expected time file
545     }
546     return $dt;
547 }
548 # write a date/time string to a time file
549 sub write_timefile() {
550     my $fname = shift; # name of file to write to
551     my $dt = shift;    # date/time string to write
552
553     if (open(FH, ">$fname")) {
554       print FH "$dt\n";
555       close(FH);
556     }  
557 }
558 # move a dat file without time stamp to subdir
559 sub import_set() {
560     my $dt = get_fdate("$setdir/$datafile") . shift;
561     &write_timefile("$setdir/$datefile", $dt);
562
563     # create set directory
564     my $setname = "$setdir/" . get_ddate("$setdir/$datafile");
565     mkdir($setname);
566
567     # move files to the new set directory
568     copy_files($setdir, $setname);
569     erase_files ($setdir);
570 }
571
572 # get date/time string from file
573 sub get_fdate() {
574     my $fname = shift;
575     open(DT, "/bin/date -r $fname|");
576     my $dt = <DT>;
577     close(DT);
578     chomp $dt;
579     $dt =~ s/\s+/ /g;  # remove duplicate spaces
580     return $dt;
581 }
582 # get date/time string from file for use as directory name
583 sub get_ddate() {
584     my $fname = shift;
585     open(DT, "/bin/date -r $fname +%Y%m%d_%H%M%S|");
586     my $dt = <DT>;
587     close(DT);
588     chomp $dt;
589     return $dt;
590 }
591 # copy archive files from source directory to destination directory
592 sub copy_files() {
593     my $src_dir = shift;
594     my $dest_dir = shift;
595     map (copy ("$src_dir/$_", "$dest_dir/$_"),  ($datafile, $datefile) );
596 }
597 # erase set files
598 sub erase_files() {
599     my $src_dir = shift;
600     map (unlink ("$src_dir/$_"),  ($datafile, $datefile));
601 }
602 # get backup error text
603 sub get_bk_error() {
604     my $exit_code = shift || return '';
605     if ($exit_code == 0) {
606         return '';
607     } elsif ($exit_code == 2) {
608         return $Lang::tr{'err bk 2 key'};
609     } elsif ($exit_code == 3) {
610         return $Lang::tr{'err bk 3 tar'};
611     } elsif ($exit_code == 4) {
612         return $Lang::tr{'err bk 4 gz'};
613     } elsif ($exit_code == 5) {
614         return $Lang::tr{'err bk 5 encrypt'};
615     } else {
616         return $Lang::tr{'err bk 1'};
617     }
618 }
619 # show any restore errors
620 sub get_rs_error() {
621     
622     my $exit_code = shift || return '';
623     if ($exit_code == 0) {
624         return '';
625     } elsif ($exit_code == 6) {
626         return $Lang::tr{'err rs 6 decrypt'};
627     } elsif ($exit_code == 7) {
628         return $Lang::tr{'err rs 7 untartst'};
629     } elsif ($exit_code == 8) {
630         return $Lang::tr{'err rs 8 untar'};
631     } elsif ($exit_code == 9) {
632         return $Lang::tr{'missing dat'};
633     } else {
634         return $Lang::tr{'err rs 1'}."($exit_code)";
635     }
636 }
637 sub kmgt {
638     my ($value,$length,$opt_U) = @_;
639     if      ( $value > 10**( $length + 8 ) or $opt_U eq 'T' ) {
640         return sprintf( "%d%s", int( ( $value / 1024**4 ) + .5 ), 'T' );
641     } elsif ( $value > 10**( $length + 5 ) or $opt_U eq 'G' ) {
642         return sprintf( "%d%s", int( ( $value / 1024**3 ) + .5 ), 'G' );
643     } elsif ( $value > 10**( $length + 2 ) or $opt_U eq 'M' ) {
644         return sprintf( "%d%s", int( ( $value / 1024**2 ) + .5 ), 'M' );
645     } elsif ( $value > 10**($length) or $opt_U eq 'K' ) {
646         return sprintf( "%d%s", int( ( $value / 1024 ) + .5 ), 'K' );
647     } else {
648         return $value;
649     }
650 }
651
652 1;