- allow bootdisks to have the initrd on a module disk
[opensuse:installation-images.git] / lib / MakeFATImage.pm
1 #! /usr/bin/perl
2
3 # Write an empty floppy image.
4 #
5 # Unfortunately, mformat does not allow writing a floppy with only one FAT
6 # (instead of 2). This way we save a few kbytes (11 to be exact).
7 #
8 # Usage:
9 #
10 #   use MakeFATImage;
11 #
12 #   exported functions:
13 #     MakeFATImage(file_name, label, cluster_size);
14 #
15 #   Note: the label can contain only up to 11 chars.
16
17
18 =head1 MakeFATImage
19
20 C<MakeFATImage.pm> is a perl module that can be used to create FAT file
21 systems. It exports the following symbols:
22
23 =over
24
25 =item *
26
27 C<MakeFATImage(file_name, label, cluster_size, sec_p_track)>
28
29 =back
30
31 =head2 Usage
32
33 use MakeFATImage;
34
35 =head2 Description
36
37 =over
38
39 =item *
40
41 C<MakeFATImage(file_name, label, cluster_size, sec_p_track)>
42
43 C<MakeFATImage> creates an empty DOS FAT file system image in C<file_name>. 
44 The C<label> must not exceed 11 chars. C<cluster_size> is given in numbers
45 of sectors per cluster (typically 1 for floppy disks).
46
47 The file system created will have only 1 FAT (usually there are 2) and only a minimum
48 number of root directory entries. This way we save 11 kbytes on a 1.44M floppy.
49
50 Note: you I<can> specify such things as a C<cluster_size> of 5 sectors/cluster. Linux
51 won't choke on it. But I don't know how Win/Dos will like it.
52
53 B<Return Values>
54
55 C<MakeFATImage> returns a list with 2 elements:
56
57 C<(blocks, block_size)>
58
59 =over
60
61 =item *
62
63 C<blocks> is the number of blocks that can be used on that file system
64
65 =item *
66
67 C<block_size> is the size of a block in bytes
68
69 =back
70
71 So, C<blocks * block_size> gives the usable file system size in bytes.
72
73 On any failure, C<( )> is returned.
74
75 =back
76
77 =cut
78
79
80 require Exporter;
81 @ISA = qw ( Exporter );
82 @EXPORT = qw ( MakeFATImage );
83
84 use strict 'vars';
85 use integer;
86
87
88 # DOSDate(day, month, year)
89 # or
90 # DOSDate(unix_time)
91
92 sub DOSDate
93 {
94   my (@u);
95
96   @u = @_;
97   if(@u == 1) {
98     @u = (localtime shift)[3..5];
99     $u[1]++;
100   }
101
102   return $u[0] + ($u[1] << 5) + (($u[2] < 80 ? 0 : $u[2] - 80) << 9);
103 }
104
105
106 # DOSTime(second, minute, hour)
107 # or
108 # DOSTime(unix_time)
109
110 sub DOSTime
111 {
112   my (@u);
113
114   @u = @_;
115   if(@u == 1) {
116     @u = (localtime shift)[0..2];
117   }
118
119   return ($u[0] >> 1) + ($u[1] << 5) + ($u[2] << 11);
120 }
121
122
123 sub MakeFATImage
124 {
125   my (
126     $file_name, $id8, $id11, $heads, $tracks, $fats, $root_ents,
127     $drive_id, $fatbits, $secs_p_cluster, $sec_size, $hidden_secs,
128     $drive_number, $serial_id, $sectors, $fatsecs, $usable_secs,
129     $clusters, $rootsecs, $sec_p_track, $bs, $fs, $rs, $zs, $i, $j, @i,
130     $mbr_track, $mbr, $pt, $mboot, $mboot_file, $boot_file, $boot_msg
131   );
132
133   ( $file_name, $id11, $secs_p_cluster, $sec_p_track, $heads, $tracks, $mbr, $mboot_file, $boot_msg ) = @_;
134
135   # if $heads and $tracks are specified, assume a disk image, otherwise
136   # we'll make a floppy image
137
138   $boot_file = "${BasePath}src/mboot/boot";
139
140   if(length($id11) > 11) {
141     print STDERR "$Script: WARNING: volume label \"$id11\" too long\n";
142     return ( undef, undef )
143   }
144
145   $id8 = "SUSE";                # will be overwritten by syslinux anyway
146   $drive_id = $tracks ? 0xf8 : 0xf0;    # 0xf0: floppy; 0xf8: hard disk
147   $fats = $tracks ? 2 : 1;
148   $sec_p_track = 18 unless $sec_p_track;
149   $heads = 2 unless $heads;
150   $tracks = 80 unless $tracks;
151   $root_ents = 16;
152   $sec_size = 0x200;
153   $serial_id = 0x31415926;
154
155   $hidden_secs = $mbr ? $sec_p_track : 0;
156
157   $drive_number = $drive_id == 0xf8 ? 0x80 : 0x00;
158
159   $sectors = $sec_p_track * $heads * $tracks;
160
161   $sectors -= $sec_p_track if $mbr;
162
163   $clusters = $sectors / $secs_p_cluster;       # first approx
164
165   $fatbits = $clusters <= 0xff5 ? 12 : 16;      # see if 12 bit FAT would be ok
166
167   $fatsecs = (($fatbits * ($clusters + 2) + 7) / 8 + $sec_size - 1) / $sec_size;
168
169   $rootsecs = ($root_ents * 0x20 + $sec_size - 1) / $sec_size;
170
171   $usable_secs = $sectors - $fats * $fatsecs - $rootsecs - 1;
172   $clusters = $usable_secs / $secs_p_cluster;
173
174   $rootsecs += $usable_secs % $secs_p_cluster;  # don't waste space for nothing
175   $root_ents = $rootsecs * $sec_size / 0x20;
176
177   # boot sector
178
179   if($boot_msg) {
180     if(open F, $boot_file) {
181       read F, $i, 100;
182       close F;
183       $boot_msg = $i . $boot_msg . "\x00";
184     }
185     else {
186       undef $boot_msg;
187     }
188   }
189
190   $i = $boot_msg ? 0x3c : 0xfe;
191   $bs = pack (
192     "C3A8vCvCvvCvvvVVCCCVA11A8Z448v",
193
194     0xeb, $i, 0x90,             # jmp to boot code
195     $id8,                       # some label
196     $sec_size,                  # sector length (e.g. 512 bytes)
197     $secs_p_cluster,            # sector per cluster (e.g. 1)
198     0x1,                        # 1 reserved sector (the boot sector)
199     $fats,                      # fats (typically 1 or 2)
200     $root_ents,                 # root dir entries (multiple of 16)
201     $sectors >> 16 ? 0 : $sectors,      # total size in sectors for < 32MB
202     $drive_id,                  # drive id
203     $fatsecs,                   # sectors per fat
204     $sec_p_track,               # sectors per track
205     $heads,                     # heads
206     $hidden_secs,               # hidden sectors (aka start sector of this partition)
207     $sectors >> 16 ? $sectors : 0,      # total size in sectors for >= 32MB
208     $drive_number,              # drive number (0x00: floppy, 0x80: hard disk)
209     0,                          # reserved
210     0x29,                       # extended BPB id
211     $serial_id,                 # serial number
212     $id11,                      # volume label
213     $fatbits == 12 ? "FAT12" : "FAT16", # fat id
214     "",                         # fill up with zeroes
215     0xaa55                      # some id
216   );
217
218   if($boot_msg) {
219     substr($bs, 0x3e, length $boot_msg) = $boot_msg;
220   }
221
222   # the first fat sector
223   # ##### needs to be generalized!!!
224   $fs = $fatbits == 12 ?
225     pack( "C3Z509", $drive_id, 0xff, 0xff, "" ) :
226     pack( "C4Z508", $drive_id, 0xff, 0xff, 0xff, "" );
227
228   # the first root directory sector (add volume label)
229
230   @i = (time);
231   if(defined %ConfigData) {
232     @i = (0 , (split /\./, $ConfigData{suse_release}));
233     $i[1] *= 10;
234   }
235
236   $rs = pack (
237     "A11CZ10vvZ486",
238
239     $id11,                      # volume label
240     0x08,                       # attribute for volume label
241     "",                         # fill with zeroes
242     DOSTime(@i),                # time
243     DOSDate(time),              # date
244     ""
245   );
246
247   # sector with zeroes
248   $zs = pack ( "Z512", "" );
249
250   # ok, write out the image
251   open F, ">$file_name" or return ( undef, undef );
252
253   # boot sector
254   print F $bs;
255
256   # fat sectors
257   for($i = 0; $i < $fats; $i++) {
258     for($j = 0; $j < $fatsecs; $j++) {
259       print F $j ? $zs : $fs;
260     }
261   }
262
263   # root directory and data sectors
264   for($i = 0; $i < $sectors - $fats * $fatsecs - 1; $i++) {
265     print F $i ? $zs : ($drive_number == 0x00 ? $rs : $zs);
266   }
267
268   # we're done!
269   close F;
270
271   # write out track 0 needed for a real hd image (with a valid mbr in it)
272   if($mbr) {
273     undef $mboot;
274     if($mboot_file) {
275       die "no boot program" unless open F, $mboot_file;
276       read F, $mboot, 446;
277       close F;
278     }
279
280     $pt = 1;
281     $pt = $sectors >> 16 ? 6 : 4 if $fatbits == 16;
282
283     $mbr_track = pack (
284       "Z446CCvCCCCVVZ48v",
285       $mboot,                   # boot code, if any
286       0x80,                     # bootflag
287       1,                        # head 1st
288       1,                        # cyl/sector 1st
289       $pt,                      # partition type
290       $heads - 1,               # head last
291       ((($tracks - 1) >> 8) << 6) + $sec_p_track,       # cyl/sector last, byte 0
292       ($tracks - 1) & 0xff,     # cyl/sector last, byte 1
293       $hidden_secs,             # partition offset
294       $sectors,                 # partition size
295       "", 0xaa55
296     );
297
298     # ok, write out the image
299     open F, ">$mbr" or return ( undef, undef );
300
301     print F $mbr_track;
302
303     for($i = 0; $i < $hidden_secs - 1; $i++) { print F $zs }
304
305     close F;
306
307   }
308
309
310   return ( $clusters, $sec_size * $secs_p_cluster )
311 }
312
313
314 1;