Comment: Provide better Int13Sniff URL.
[biosautoraid.git] / biosautoraid.pl
1 #! /usr/bin/perl
2 use strict;
3 use warnings;
4 use bytes;
5 use Getopt::Long;
6 use Fcntl qw(SEEK_SET);
7
8
9 sub signature($$$$;$)
10 {
11 my($hay,$hay_filename,$offset,$needle,$msg)=@_;
12
13         die if length($needle)%1;
14         my $len_bytes=length($needle)/2;
15         substr($hay,$offset,$len_bytes) eq pack("H*",$needle)
16                         or die "\"$hay_filename\" error reading signature 0x$needle @".sprintf("0x%X",$offset)
17                                         .(!$msg ? "" : "; $msg")."\n";
18 }
19
20 my $BACKUP_SECTOR_START=63;
21 my $MAX_BOOT_SIZE=0x1b6;        # LILO: leave some space for NT's and DR DOS' dirty
22
23
24 my $bin_filename="./biosautoraid.bin";
25 my $opt_part1;
26 my $opt_install;
27 my $opt_uninstall;
28 my $options_error=!GetOptions(
29                 "bin=s"      ,\$bin_filename,
30                 "part1=s"    ,\$opt_part1,
31                 "i|install"  ,\$opt_install,
32                 "u|uninstall",\$opt_uninstall,
33                 );
34 my $BIN;
35 open $BIN,$bin_filename or die "open \"$bin_filename\": $!";
36 my $bin=do { local $/; <$BIN>; } or die "read \"$bin_filename\": $!";
37 close $BIN or die "close \"$bin_filename\": $!";
38
39 my $bin_len=length($bin);
40 $bin_len>0 && !($bin_len%0x200) or die "Invalid length of \"$bin_filename\": $bin_len";
41 my $bin_short=($bin_len==0x200);        # => no 0xBEEF
42
43 signature $bin,$bin_filename,0x1FE     ,"55AA";
44 substr($bin,$MAX_BOOT_SIZE,0x1FE-$MAX_BOOT_SIZE)=~/^\x00*$/s or die "No empty magic+partitiontable: $bin_filename";
45
46 if (substr($bin,3,8) eq "BIOSRAID") {
47         signature $bin,$bin_filename,0x03      ,unpack("H*","BIOSRAID");
48         signature $bin,$bin_filename,0x43      ,"5E81A2".sprintf("%X",$BACKUP_SECTOR_START);
49         signature $bin,$bin_filename,$bin_len-2,"BEEF" if !$bin_short;
50         }
51 else {
52         signature $bin,$bin_filename,0x03      ,unpack("H*","BIOSRAI2")
53                         .sprintf("%X%X",$BACKUP_SECTOR_START,$MAX_BOOT_SIZE-0x100);
54         }
55
56 my $all_ok;
57 END {
58         warn "Syntax: $0 [--bin=<biosautoraid.bin>] [{-i|--install|-u|--uninstall} <device>]" if !$all_ok;
59         }
60
61 if (!@ARGV && !$options_error) {
62         print "OK, \"$bin_filename\" checked.\n";
63         $all_ok=1;
64         exit 0;
65         }
66
67 die "Type -i|--install or -u|--uninstall"                     if (!$opt_install && !$opt_uninstall) || $options_error;
68 die "Both -i|--install and -u|--uninstall forbidden together" if   $opt_install &&  $opt_uninstall;
69
70 @ARGV<=1 or die;
71 my $master_filename=$ARGV[0];
72
73 my $master_is_b=(-b $master_filename);
74 my $master_is_f=(-f $master_filename);
75 die "Invalid device file type: $master_filename" if !$master_is_b && !$master_is_f;
76 my $backup_filename;
77 my $backup_offset=$BACKUP_SECTOR_START*0x200;
78 if ($master_is_f) {
79         $backup_filename=$master_filename;
80         }
81 if ($master_is_b) {
82         die "Filename is block device but it is unsupported: $master_filename"
83                         if $master_filename!~m{^/dev/[hs]d[a-z]};
84
85         sub hdparm_g_start_check($$)
86         {
87         my($filename,$start_expected)=@_;
88         
89                 local *HDPARM;
90                 my $cmdname="hdparm -g '$filename'|";
91                 open HDPARM,$cmdname or die "open \"$cmdname\": $!";
92                 my $HDPARM=do { local $/; <HDPARM>; } or die "read \"$cmdname\": $!";
93                 close HDPARM or die "close \"$cmdname\": $!";
94                 my($start)=($HDPARM=~/^\s*\Q$filename\E:\s*geometry\s*=.*,\s*start\s*=\s*(\d+)\s*$/s)
95                                 or die "Unparsable output of \"$cmdname\":\n$HDPARM";
96                 die "Unexpected 'start' parameter $start (expected $start_expected) of: $filename"
97                                 if $start!=$start_expected;
98         }
99
100         hdparm_g_start_check($master_filename,0);
101         if (!$opt_part1) {
102                 $backup_filename=$master_filename."1";
103                 hdparm_g_start_check($backup_filename,$backup_offset/0x200);
104                 }
105         else {
106                 $backup_filename=$opt_part1;
107                 hdparm_g_start_check($backup_filename,0);
108                 $opt_part1=undef();
109                 }
110         $backup_offset=0;
111         }
112 die "Irelevantni parametr: --part1" if $opt_part1;
113
114 my $MASTER;
115 open $MASTER,"+<".$master_filename or die "open \"$master_filename\": $!";
116
117 # Do not write to the '/dev/hda' areas being mapped by: /dev/hda1
118 # as reboot will rewrite '/dev/hda' with the contents of: /dev/hda1
119 my $BACKUP;
120 if ($master_filename ne $backup_filename) {
121         open $BACKUP,"+<".$backup_filename or die "open \"$backup_filename\": $!";
122         die if $backup_offset;
123         }
124 else {
125         $BACKUP=$MASTER;
126         die if !$backup_offset;
127         }
128
129 sysseek $MASTER,0,SEEK_SET or die "Error seeking $master_filename";
130 my $master;
131 $bin_len==sysread $MASTER,$master,$bin_len or die "read \"$master_filename\": $!";
132 length($master)==$bin_len or die "read \"$master_filename\": ".length($master)."!=$bin_len";
133 signature $master,$master_filename,0x1FE,"55AA";
134
135 sysseek $BACKUP,$backup_offset,SEEK_SET or die "Error seeking $backup_filename";
136 my $backup;
137 $bin_len==sysread $BACKUP,$backup,$bin_len or die "read \"$backup_filename\": $!";
138 length($backup)==$bin_len or die "read \"$backup_filename\": ".length($backup)."!=$bin_len";
139
140 my $zeroes="\x00"x$bin_len;
141 die if length($zeroes)!=$bin_len;
142
143 $bin=substr($bin,0,$MAX_BOOT_SIZE).substr($master,$MAX_BOOT_SIZE,0x1FE-$MAX_BOOT_SIZE).substr($bin,0x1FE);
144
145 my $installed=substr($master,0x03,7) eq "BIOSRAI";
146 print "Already instaled? ".($installed ? "YES" : "NO")."\n";
147
148 die "Nothing to uninstall!\n" if $opt_uninstall && !$installed;
149
150 if ($installed) {
151         signature $master,$master_filename,$bin_len-2,"BEEF","Different installed SECTOR_LEN?" if !$bin_short;
152         signature $master,$master_filename,0x03      ,unpack("H*","BIOSRAI");
153         signature $master,$master_filename,0x43      ,"5E81A2".sprintf("%X",$BACKUP_SECTOR_START) if !$bin_short;
154         signature $backup,$backup_filename,0x1FE,"55AA";
155         }
156 if ($opt_install) {
157         if (!$installed) {
158                 if ($backup ne $zeroes) {
159                         # Permit different DOS-volume area and 0x1FC..0x1FD bytes.
160                         die "Sensitive bytes at backup sectors" if !(1
161                                 && (0
162                                         || substr($backup,0x200) eq substr($zeroes,0x200)
163                                         || (substr($backup,$bin_len-2) eq pack("H*","BEEF") && !$bin_short)
164                                         )
165                                 && substr($backup,0,0x03) eq substr($master,0,0x03)
166                                 && substr($backup,0x5A,$MAX_BOOT_SIZE-0x5A) eq substr($master,0x5A,$MAX_BOOT_SIZE-0x5A)
167                                 );
168                         warn "WARNING: Dropping obsolete (but similiar) backup! Use --uninstall next time.\n";
169                         }
170                 sysseek $BACKUP,$backup_offset,SEEK_SET or die "Error seeking $backup_filename";
171                 $bin_len==syswrite $BACKUP,$master or die "Error backing up the master";
172                 }
173         sysseek $MASTER,0,SEEK_SET or die "Error seeking $master_filename";
174         $bin_len==syswrite $MASTER,$bin or die "Error writing new master of BIOSautoRAID";
175         }
176 if ($opt_uninstall) {
177         sysseek $MASTER,0,SEEK_SET or die "Error seeking $master_filename";
178         $bin_len==syswrite $MASTER,$backup or die "Error writing backup back to the master";
179         sysseek $BACKUP,$backup_offset,SEEK_SET or die "Error seeking $backup_filename";
180         $bin_len==syswrite $BACKUP,$zeroes or die "Error clearing the backup area";
181         }
182
183 close $MASTER or die "close \"$master_filename\": $!";
184 if ($master_filename ne $backup_filename) {
185         close $BACKUP or die "close \"$backup_filename\": $!";
186         }
187 system("sync") and die "Error running: sync: $!";
188
189 print "OK; ".($opt_install ? ($installed ? "re" : "")."installed" : "uninstalled")."\n";
190 $all_ok=1;