- fix hostname display for ssh install (#230617)
[opensuse:installation-images.git] / etc / mkbootdisk
1 #! /usr/bin/perl
2
3 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
4 #
5 # Create SUSE Linux boot disks.
6 #
7 # Try 'mkbootdisk --help' for a usage summary.
8 #
9 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
10
11 use strict 'vars';
12 use integer;
13
14 %::ConfigData = ( full_product_name => "openSUSE 10.2" );
15
16
17 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
18 #
19 # Basic FAT manipulation functions.
20 #
21 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
22 {
23   package FAT;
24
25   use strict 'vars';
26   use integer;
27
28
29   sub new
30   {
31     my $self = {};
32
33     bless $self;
34
35     $self->{offset} = 0;
36     $self->{image} = "\x00" x 0x200;
37
38     return $self
39   }
40
41   sub image
42   {
43     my $self = shift;
44
45     return $self->{image};
46   }
47
48
49   sub offset
50   {
51     my $self = shift;
52
53     $self->{offset} = shift if @_;
54
55     return $self->{offset};
56   }
57
58
59   sub write_image
60   {
61     my $self = shift;
62
63     if(@_) {
64       my $file = shift;
65       open W1, ">$file";
66       print W1 $self->image;
67       close W1;
68     }
69   }
70
71
72   sub read_image
73   {
74     my $self = shift;
75
76     if(@_) {
77       my $file = shift;
78       my $image;
79       open F1, $file;
80       read F1, $image, -s($file);
81       close F1;
82       $self->{image} = $image;
83     }
84   }
85
86
87   sub resize_image
88   {
89     my $self = shift;
90     my $new_size = shift;
91
92     my $len = $new_size + $self->{offset} - length($self->image);
93     $self->{image} .= "\x00" x $len if $len > 0;
94   }
95
96
97   sub _string
98   {
99     my $self = shift;
100     my $ofs = $self->{offset} + shift;
101     my $len = 0 + shift;
102
103     substr($self->{image}, $ofs, $len) = pack("a$len", shift) if @_[0];
104     return substr($self->{image}, $ofs, $len);
105   }
106
107
108   sub _byte
109   {
110     my $self = shift;
111     my $ofs = $self->{offset} + shift;
112
113     substr($self->{image}, $ofs, 1) = pack("C", shift) if @_[0];
114     return unpack("C", substr($self->{image}, $ofs, 1));
115   }
116
117
118   sub _word
119   {
120     my $self = shift;
121     my $ofs = $self->{offset} + shift;
122
123     substr($self->{image}, $ofs, 2) = pack("v", shift) if @_[0];
124     return unpack("v", substr($self->{image}, $ofs, 2));
125   }
126
127
128   sub _dword
129   {
130     my $self = shift;
131     my $ofs = $self->{offset} + shift;
132
133     substr($self->{image}, $ofs, 4) = pack("V", shift) if @_[0];
134     return unpack("V", substr($self->{image}, $ofs, 4));
135   }
136
137
138   sub sector
139   {
140     my $self = shift;
141     my $sec = shift;
142     my $len = $self->sector_size;
143     my $ofs = $sec * $len + $self->{offset};
144
145     if(@_) {
146       my $buf = shift;
147       my $xlen = $len - length($buf);
148       $buf .= "\x00" x $xlen if $xlen > 0;
149       substr($self->{image}, $ofs, $len) = $buf;
150     }
151
152     return substr($self->{image}, $ofs, $len);
153   }
154
155
156   sub cluster
157   {
158     my $self = shift;
159     my $cl_nr = shift;
160     my $len = $self->sector_size * $self->cluster_size;
161
162     return undef if $cl_nr < 2;
163
164     my $ofs = ($cl_nr - 2) * $len + $self->{offset};
165
166     $ofs += ($self->res_sectors + $self->fats * $self->fat_size + $self->_root_sectors) * $self->sector_size;
167
168     if(@_) {
169       my $buf = shift;
170       my $xlen = $len - length($buf);
171       $buf .= "\x00" x $xlen if $xlen > 0;
172       substr($self->{image}, $ofs, $len) = $buf;
173     }
174
175     return substr($self->{image}, $ofs, $len);
176   }
177
178
179   #
180   # dir_entry(cluster, entry_index [, buffer])
181   #
182   sub dir_entry
183   {
184     my $self = shift;
185     my $cl_nr = shift;
186     my $entry = shift;
187     my $len = 32;
188     my $ofs;
189
190     return undef if $cl_nr < 2 && $cl_nr != 0;
191
192     $ofs = $self->res_sectors + $self->fats * $self->fat_size;
193     if($cl_nr >= 2) {
194       $ofs += $self->_root_sectors + ($cl_nr - 2) * $self->cluster_size;
195     }
196
197     $ofs = $ofs * $self->sector_size + $self->{offset} + ($entry << 5);
198
199     if(@_) {
200       my $buf = shift;
201       my $xlen = $len - length($buf);
202       $buf .= "\x00" x $xlen if $xlen > 0;
203       substr($self->{image}, $ofs, $len) = $buf;
204     }
205
206     return substr($self->{image}, $ofs, $len);
207   }
208
209
210   # dos_date(day, month, year)
211   # or
212   # dos_date(unix_time)
213
214   sub dos_date
215   {
216     my (@u);
217
218     @u = @_;
219     if(@u == 1) {
220       @u = (localtime shift)[3..5];
221       $u[1]++;
222     }
223
224     return pack("v", $u[0] + ($u[1] << 5) + (($u[2] < 80 ? 0 : $u[2] - 80) << 9));
225   }
226
227
228   # dos_time(second, minute, hour)
229   # or
230   # dos_time(unix_time)
231
232   sub dos_time
233   {
234     my (@u);
235
236     @u = @_;
237     if(@u == 1) {
238       @u = (localtime shift)[0..2];
239     }
240
241     return pack("v", ($u[0] >> 1) + ($u[1] << 5) + ($u[2] << 11));
242   }
243
244
245   sub fs_date
246   {
247     my $self = shift;
248
249     $self->{fs_date} = dos_date(@_) if @_;
250
251     return $self->{fs_date};
252   }
253
254
255   sub fs_time
256   {
257     my $self = shift;
258
259     $self->{fs_time} = dos_time(@_) if @_;
260
261     return $self->{fs_time};
262   }
263
264
265   #
266   # dir_entry(name, attribute, time, date, start, size);
267   #
268   sub new_dir_entry
269   {
270     my ($name, $attribute, $time, $date, $start, $size) = @_;
271
272     return pack("A11CZ10a2a2vV", $name, $attribute, "", $time, $date, $start, $size);
273   }
274
275
276   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
277   sub boot_code
278   {
279     my $self = shift;
280
281     $self->_word(0, 0xfeeb);
282     $self->_byte(2, 0x90);
283
284     if(@_) {
285       my $code = shift;
286       $self->_byte(1, 0x3c);
287       $self->_string(0x3e, length($code), $code);
288     }
289   }
290
291
292   sub manuf_id
293   {
294     my $self = shift;
295
296     if(@_) {
297       return unpack("A8", $self->_string(0x03, 8, pack("A8", shift)));
298     }
299     else {
300       return unpack("A8", $self->_string(0x03, 8));
301     }
302   }
303
304
305   sub sector_size
306   {
307     my $self = shift;
308
309     return $self->_word(0x0b, shift);
310   }
311
312
313   sub cluster_size
314   {
315     my $self = shift;
316
317     return $self->_byte(0x0d, shift);
318   }
319
320
321   sub res_sectors
322   {
323     my $self = shift;
324
325     return $self->_word(0x0e, shift);
326   }
327
328
329   sub fats
330   {
331     my $self = shift;
332
333     return $self->_byte(0x10, shift);
334   }
335
336
337   sub root_entries
338   {
339     my $self = shift;
340
341     if(@_) {
342       my $entries = shift;
343       my $sec_size = $self->sector_size;
344       if($sec_size) {
345         $entries = (((($entries << 5) + $sec_size - 1) / $sec_size) * $sec_size) >> 5;
346       }
347       return $self->_word(0x11, $entries);
348     }
349     else {
350       return $self->_word(0x11, shift);
351     }
352   }
353
354
355   sub sectors
356   {
357     my $self = shift;
358     my $secs;
359
360     if(@_) {
361       $secs = shift;
362       if($secs >> 16) {
363         $self->_dword(0x20, $secs);
364       }
365       else {
366         $self->_word(0x13, $secs);
367       }
368     }
369
370     $secs = $self->_word(0x13);
371     $secs = $self->_dword(0x20) unless $secs;
372
373     return $secs;
374   }
375
376
377   sub media_id
378   {
379     my $self = shift;
380
381     return $self->_byte(0x15, shift);
382   }
383
384
385   sub fat_size
386   {
387     my $self = shift;
388
389     return $self->_word(0x16, shift);
390   }
391
392
393   sub track_size
394   {
395     my $self = shift;
396
397     return $self->_word(0x18, shift);
398   }
399
400
401   sub heads
402   {
403     my $self = shift;
404
405     return $self->_word(0x1a, shift);
406   }
407
408
409   sub hidden_sectors
410   {
411     my $self = shift;
412
413     return $self->_dword(0x1c, shift);
414   }
415
416
417   sub drive_id
418   {
419     my $self = shift;
420
421     return $self->_byte(0x24, shift);
422   }
423
424
425   sub extended_bpb
426   {
427     my $self = shift;
428
429     return $self->_byte(0x26, shift);
430   }
431
432
433   sub serial
434   {
435     my $self = shift;
436
437     return $self->_dword(0x27, shift);
438   }
439
440
441   sub volume_id
442   {
443     my $self = shift;
444
445     if(@_) {
446       return unpack("A11", $self->_string(0x2b, 11, pack("A11", shift)));
447     }
448     else {
449       return unpack("A11", $self->_string(0x2b, 11));
450     }
451   }
452
453
454   sub fat_bits
455   {
456     my $self = shift;
457
458     if(@_) {
459       my $bits = shift;
460       $bits = 16 unless $bits == 12 || $bits == 32;
461       $self->_string(0x36, 8, sprintf("FAT%-5u", $bits));
462     }
463
464     my $id = $self->_string(0x36, 8);
465     if($id =~ /FAT(\d+)/) {
466       $id = $1 + 0;
467     }
468     else {
469       $id = undef;
470     }
471
472     return $id;
473   }
474
475
476   sub _root_sectors
477   {
478     my $self = shift;
479
480     return (($self->root_entries << 5) + $self->sector_size - 1) / $self->sector_size;
481   }
482
483
484   sub _data_sectors
485   {
486     my $self = shift;
487
488     return $self->sectors - $self->res_sectors - $self->fats * $self->fat_size - $self->_root_sectors;
489   }
490
491
492   sub clusters
493   {
494     my $self = shift;
495
496     return $self->_data_sectors / $self->cluster_size;
497   }
498
499
500   sub cluster_to_sector
501   {
502     my $self = shift;
503     my $cl_nr = shift;
504
505     return undef if $cl_nr < 2;
506
507     return $self->res_sectors + $self->fats * $self->fat_size + $self->_root_sectors +
508       ($cl_nr - 2) * $self->cluster_size;
509   }
510
511
512   sub sector_to_cluster
513   {
514     my $self = shift;
515     my $sec_nr = shift;
516
517     $sec_nr -= $self->res_sectors + $self->fats * $self->fat_size + $self->_root_sectors;
518
519     return undef if $sec_nr < 0;
520
521     return $sec_nr / $self->cluster_size + 2;
522   }
523
524
525   sub wasted_sectors
526   {
527     my $self = shift;
528
529     return $self->_data_sectors - $self->clusters * $self->cluster_size;
530   }
531
532
533   sub fat_entry
534   {
535     my $self = shift;
536     my $cl_nr = shift;
537     my $bits = $self->fat_bits;
538     my $fats = $self->fats;
539     my ($cl, $i, $ofs);
540
541     return undef unless $bits;
542
543     if(@_) {
544       for($i = 0; $i < $fats; $i++) {
545         if($bits == 12) {
546           $ofs = ($self->res_sectors + $self->fat_size * $i) * $self->sector_size + $cl_nr + ($cl_nr >> 1);
547           $cl = $self->_word($ofs);
548           if($cl_nr & 1) {
549             $cl = ($cl & ~0xfff0) + (($_[0] << 4) & 0xfff0);
550           }
551           else {
552             $cl = ($cl & ~0xfff) + ($_[0] & 0xfff);
553           }
554           $self->_word($ofs, $cl);
555         }
556         elsif($bits == 16) {
557           $self->_word(($self->res_sectors + $self->fat_size * $i) * $self->sector_size + ($cl_nr << 1), $_[0]);
558         }
559       }
560     }
561
562     if($bits == 12) {
563       $cl = $self->_word($self->res_sectors * $self->sector_size + $cl_nr + ($cl_nr >> 1));
564       if($cl_nr & 1) {
565         $cl >>= 4;
566       }
567       else {
568         $cl &= 0xfff;
569       }
570     }
571     elsif($bits == 16) {
572       $cl = $self->_word($self->res_sectors * $self->sector_size + ($cl_nr << 1));
573     }
574
575     return $cl;
576   }
577
578
579   sub free_cluster
580   {
581     my $self = shift;
582     my $clusters = $self->clusters + 2;
583     my $cl_nr;
584
585     for($cl_nr = 2; $cl_nr < $clusters; $cl_nr ++) {
586       return $cl_nr unless $self->fat_entry($cl_nr);
587     }
588
589     return undef;
590   }
591
592
593   sub add_file
594   {
595     my $self = shift;
596     my ($cl_nr, $idx, $name, $attr, $buf) = @_;
597     my $cl_len = $self->cluster_size * $self->sector_size;
598     my $len = length($buf);
599     my ($i, $cl, $start, $next);
600
601     if($len) {
602       $start = $self->free_cluster;
603       return undef unless $start;
604     }
605
606     $self->dir_entry($cl_nr, $idx, new_dir_entry(
607       $name, $attr, $self->fs_time, $self->fs_date, $start, $len
608     ));
609
610     return 1 unless $len;
611
612     for($i = 0; $i < $len; $i += $cl_len) {
613       $self->cluster($start, substr($buf, $i, $cl_len));
614       $self->fat_entry($start, 0xffff);
615       if($i + $cl_len < $len) {
616         $next = $self->free_cluster;
617         return undef unless $next;
618         $self->fat_entry($start, $next);
619       }
620       $start = $next;
621     }
622
623     return 1;
624   }
625
626
627   sub add_dir
628   {
629     my $self = shift;
630     my ($cl_nr, $idx, $name, $attr) = @_;
631     my $start = $self->free_cluster;
632
633     return undef unless $start;
634
635     $self->dir_entry($cl_nr, $idx, new_dir_entry(
636       $name, 0x10 | $attr, $self->fs_time, $self->fs_date, $start, 0
637     ));
638     $self->fat_entry($start, 0xffff);
639
640     $self->dir_entry($start, 0, new_dir_entry(
641       ".", 0x10 | $attr, $self->fs_time, $self->fs_date, $start, 0
642     ));
643     $self->dir_entry($start, 1, new_dir_entry(
644       "..", 0x10 | $attr, $self->fs_time, $self->fs_date, $cl_nr, 0
645     ));
646
647     return $start;
648   }
649
650
651   sub init_fs
652   {
653     my $self = shift;
654     my ($clusters, $i, $ofs, $buf);
655
656     $self->_word($self->sector_size - 2, 0xaa55);
657     $self->resize_image($self->sector_size * $self->sectors);
658
659     $clusters = $self->clusters;
660
661     if(!$self->fat_bits && !$self->fat_size) {
662       for(my $i = 0; $i < 2; $i++) {
663         # two iterations should be enough
664         $self->fat_bits($clusters <= 0xff5 ? 12 : 16);
665         $self->fat_size((($self->fat_bits * ($clusters + 2) + 7) / 8 + $self->sector_size - 1) / $self->sector_size);
666         $clusters = $self->clusters;
667       }
668     }
669
670     for($i = $self->res_sectors; $i < $self->sectors; $i++) {
671       # clear all sectors
672       $self->sector($i, undef);
673     }
674
675     for($i = 0; $i < $self->fats; $i++) {
676       $ofs = ($self->res_sectors + $i * $self->fat_size) * $self->sector_size;
677       $self->_byte($ofs, $self->media_id);
678       $self->_word($ofs + 1, 0xffff);
679       $self->_byte($ofs + 3, 0xff) if $self->fat_bits >= 16;
680       $self->_dword($ofs + 4, 0xffffffff) if $self->fat_bits == 32;
681     }
682
683     $self->add_file(0, 0, $self->volume_id, 8, undef);
684   }
685 }
686
687
688 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
689 #
690 # Create special FAT image.
691 #
692 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
693 {
694   package MakeFAT;
695
696   use strict 'vars';
697   use integer;
698
699   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
700
701   my $boot_msg = "\r
702   I'm $::ConfigData{full_product_name} Boot Disk <disk>. I cannot boot. :-(\r
703   \r
704   Please try Boot Disk 1.\r\n";
705
706
707   # Not more than 1024 chars (1 cluster)! --> Or adjust cluster size!
708   my $readme =
709   "This is $::ConfigData{full_product_name} Boot Disk <disk>.
710
711   <x_readme>
712   To access Boot Disk data, you have to join the individual disk images first:
713
714     cat bootdsk? >/tmp/bootdisk
715
716   Then mount it as usual:
717
718     mount -oloop /tmp/bootdisk /mnt
719
720   When you're done, unmount it:
721
722     umount /mnt
723
724   If you have changed Boot Disk data and want to get separate Boot Disk images
725   of floppy size back, split it:
726
727     split -a 1 -b 1440k /tmp/bootdisk /tmp/bootdsk
728
729   The new Boot Disks are /tmp/bootdsk[a-<last_disk_letter>].\n";
730
731
732   my $x_readme =
733   "\n***  There is nothing for you to change on this disk.  ***\n";
734
735
736   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
737
738   # cluster size, extra root dir sectors
739   my @format = (
740     [ 4, 7 ],   # 1 (at least 96 root entries, default is 128 (16 + 16 * _7_))
741     undef,      # 1
742     [ 4, 5 ],   # 2
743     [ 4, 7 ],   # 3
744     [ 4, 5 ],   # 4
745     [ 4, 7 ],   # 5
746     [ 4, 5 ],   # 6
747     [ 4, 6 ],   # 7
748     [ 4, 7 ],   # 8
749     [ 4, 8 ],   # 9
750   );
751
752   my $opt_disks = 2;
753   my ($serial, $fat);
754
755
756   sub set_boot_msg
757   {
758     my $msg = shift;
759     my $code =
760       "\xfa\x31\xc9\x8e\xd1\x89\xcc\x8e\xd9\x8e\xc1\xfb\xfc\xe8\x00\x00" .
761       "\x5e\x81\xc6\x13\x00\xac\x08\xc0\x74\xfe\xbb\x07\x00\xb4\x0e\xcd" .
762       "\x10\xeb\xf2";
763
764     $fat->boot_code($code . $msg . "\x00");
765   }
766
767
768   # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
769
770   sub create_image
771   {
772     my ($cl_size, $x_root, $i, $ldsk);
773
774     $cl_size = $format[$opt_disks] ? $format[$opt_disks][0] : $format[0][0];
775     $x_root = $format[$opt_disks] ? $format[$opt_disks][1] : $format[0][1];
776
777     $fat = FAT::new;
778
779     $fat->resize_image(1440 * 1024 * $opt_disks);
780
781     $fat->sector_size(0x200);
782     $fat->res_sectors(1);
783     $fat->extended_bpb(0x29);
784
785     $fat->sectors(1440 * 2 * $opt_disks);
786     $fat->track_size(18);
787     $fat->heads(2);
788     $fat->cluster_size($cl_size);
789     $fat->fats(1);
790     $fat->root_entries((16 * $x_root) + 1);
791
792     $fat->media_id(0xf0);
793     $fat->drive_id(0x00);
794
795     $fat->serial($serial + 0);
796     $fat->volume_id("BOOTDISK1");
797     $fat->manuf_id("SUSE");
798
799     $fat->fs_date(time);
800     $fat->fs_time(0, 10, 9);
801
802     $fat->init_fs;
803
804     $i = $readme;
805     $i =~ s/<x_readme>//g;
806     $i =~ s/<disk>/1/g;
807     $ldsk = chr($opt_disks - 1 + ord('a'));
808     $i =~ s/<last_disk_letter>/$ldsk/g;
809     $fat->add_file(0, 1, "README  TXT", 0, $i);
810   }
811
812
813   sub create_small_image
814   {
815     my $disk = shift;
816     my ($max_cl, $i, $dsk, $ldsk);
817
818     $fat->offset($disk * 1440 * 1024);
819
820     $fat->sector_size(0x200);
821     $fat->res_sectors(1);
822     $fat->extended_bpb(0x29);
823
824     $fat->sectors(1440 * 2);
825     $fat->track_size(18);
826     $fat->heads(2);
827     $fat->cluster_size(2);
828     $fat->fats(1);
829     $fat->root_entries((16 * 1) + 1);
830
831     $fat->media_id(0xf0);
832     $fat->drive_id(0x00);
833
834     $fat->serial($serial + ($disk & 0xf));
835
836     $dsk = $disk + 1;
837
838     $fat->volume_id("BOOTDISK$dsk");
839     $fat->manuf_id("SUSE");
840
841     $i = $boot_msg;
842     $i =~ s/<disk>/$dsk/g;
843     set_boot_msg($i);
844
845     $fat->init_fs;
846
847     $i = $readme;
848     $i =~ s/<x_readme>/$x_readme/g;
849     $i =~ s/<disk>/$dsk/g;
850     $ldsk = chr($opt_disks - 1 + ord('a'));
851     $i =~ s/<last_disk_letter>/$ldsk/g;
852     $fat->add_file(0, 1, "README  TXT", 0, $i);
853
854     $max_cl = $fat->clusters + 2;
855
856     for($i = 3; $i < $max_cl; $i++) {
857       $fat->fat_entry($i, 0xfff7);
858     }
859
860     # printf "res = %u\n", $fat->cluster_to_sector(2);
861
862     if($i = $fat->wasted_sectors) {
863       warn "small image: $i sectors wasted\n"
864     }
865   }
866
867
868   sub Image
869   {
870     my ($i, $res_sectors, $start_sec, $sec, $cl, $clusters, $free_clusters);
871     my ($fat_entry, $file, $verbose);
872
873     ($file, $opt_disks, $verbose) = @_;
874
875     $serial = int(rand(0x10000000)) << 4;
876
877     create_image;
878
879     for($i = 1; $i < $opt_disks; $i++) {
880       create_small_image $i;
881     }
882
883     $res_sectors = $fat->cluster_to_sector(2 + 1);      # 1 cluster for 'README'
884
885     # print "res_sectors = $res_sectors\n";
886
887     $fat->offset(0);
888
889     for($i = 1; $i < $opt_disks; $i++) {
890       $start_sec = 1440 * 2 * $i;
891       for($sec = $start_sec; $sec < $start_sec + $res_sectors; $sec ++) {
892         $cl = $fat->sector_to_cluster($sec);
893         $fat->fat_entry($cl, 0xfff7);
894         # print "sec = $sec, $cl\n";
895       }
896     }
897
898     $clusters = $fat->clusters + 2;
899     $free_clusters = 0;
900
901     for($i = 2; $i < $clusters; $i++) {
902       $fat_entry = $fat->fat_entry($i);
903       $free_clusters++ if defined($fat_entry) && $fat_entry == 0;
904     }
905
906     if($verbose) {
907       printf "      image size = %u\n", $fat->sectors * $fat->sector_size;
908       printf "        manuf id = \"%s\"\n", $fat->manuf_id;
909       printf "     sector size = 0x%x\n", $fat->sector_size;
910       printf " sectors/cluster = %u\n", $fat->cluster_size;
911       printf "reserved sectors = %u\n", $fat->res_sectors;
912       printf "            fats = %u\n", $fat->fats;
913       printf "root dir entries = %u\n", $fat->root_entries;
914       printf "         sectors = %u\n", $fat->sectors;
915       printf "        media id = 0x%02x\n", $fat->media_id;
916       printf "     sectors/fat = %u\n", $fat->fat_size;
917       printf "   sectors/track = %u\n", $fat->track_size;
918       printf "           heads = %u\n", $fat->heads;
919       printf "  hidden sectors = %u\n", $fat->hidden_sectors;
920       printf "        drive id = 0x%02x\n", $fat->drive_id;
921       printf " extended bpb id = 0x%02x\n", $fat->extended_bpb;
922       printf "          serial = 0x%08x\n", $fat->serial;
923       printf "       volume id = \"%s\"\n", $fat->volume_id;
924       printf "        fat bits = %u\n", $fat->fat_bits;
925       printf "        clusters = %u\n", $fat->clusters;
926       printf "   free clusters = %u (%uk)\n", $free_clusters, ($free_clusters * $fat->cluster_size * $fat->sector_size) >> 10;
927       printf "  wasted sectors = %u\n", $fat->wasted_sectors if $fat->wasted_sectors;
928     }
929
930     $fat->write_image($file) if $file;
931
932     return ( $free_clusters, $fat->cluster_size * $fat->sector_size );
933   }
934 }
935
936
937
938 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
939 #
940 # Parse command line and do something.
941 #
942 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
943
944 use Getopt::Long;
945
946 sub cleanup;
947 sub dir_sort_func;
948 sub dir_sort;
949 sub help;
950 sub dir_size;
951 sub get_disk_number;
952 sub unpack_bootlogo;
953
954 my $opt_file = "./bootdisk";
955 my $opt_verbose = 0;
956 my $opt_src = undef;
957 my $opt_64 = 0;
958 my $opt_96 = 0;
959 my $opt_keep = undef;
960 my $opt_syslinux = -x "/usr/bin/mcopy" ? "/usr/bin/syslinux" : "/usr/bin/syslinux-nomtools";
961 $opt_syslinux = "/usr/bin/syslinux-nomtools" if -x "/usr/bin/syslinux-nomtools";
962 my $opt_disk = undef;
963 my $opt_backup_mbr = undef;
964
965 my ($boot_disks, $tmp_dir, $buf, $i);
966
967 END { cleanup }
968 $SIG{INT} = \&cleanup;
969 $SIG{TERM} = \&cleanup;
970
971 $ENV{PATH} = "/bin:/usr/bin:/sbin:/usr/sbin";
972
973
974 chomp (my $arch = `uname -m`);
975 $opt_64 = 1 if $arch eq 'x86_64';
976
977 GetOptions(
978   'help|h'       => \&help,
979   'verbose|v'    => \$opt_verbose,
980   'out|o=s'      => \$opt_file,
981   '96'           => sub { $opt_64 = 0; $opt_96 = 1 },
982   '64'           => sub { $opt_64 = 1; $opt_96 = 0 },
983   '32'           => sub { $opt_64 = 0; $opt_96 = 0 },
984   'keep'         => \$opt_keep,
985   'syslinux=s'   => \$opt_syslinux,
986   'partition=s'  => \$opt_disk,
987   'backup-mbr=s' => \$opt_backup_mbr,
988 );
989
990 $opt_src = shift;
991
992 help unless $opt_src && -d($opt_src);
993
994 die "error: must be root to run this script\n" if $<;
995
996 die "error: $opt_syslinux not found\nPlease install package \"syslinux\" first.\n" unless -f($opt_syslinux) && -x($opt_syslinux);
997
998 chomp ($tmp_dir = `mktemp -d /tmp/mkbootdisk.XXXXXXXXXX`);
999 die "error: mktemp failed\n" if $?;
1000
1001 $arch = $opt_64 ? 'x86_64' : 'i386';
1002
1003 my $src = "$opt_src/boot/$arch/loader";
1004 $opt_64 = $opt_96 = 0 if -f "$src/isolinux.cfg";
1005 $src = "$opt_src/boot/loader" unless -f "$src/isolinux.cfg";
1006 $src = "$opt_src/loader" unless -f "$src/isolinux.cfg";
1007 $src = $opt_src unless -f "$src/isolinux.cfg";
1008
1009 die "$opt_src: no $arch installation source\n" unless -f "$src/isolinux.cfg";
1010
1011 mkdir "$tmp_dir/src", 0755;
1012 mkdir "$tmp_dir/mp", 0755;
1013 system "cp -a '$src'/* $tmp_dir/src" and die "error: failed to copy boot loader files\n";
1014
1015 $src = "$tmp_dir/src";
1016
1017 # delete unnecessary files
1018 system "rm -f $tmp_dir/src/{*live*,directory.yast}";
1019 # system "rm -f $tmp_dir/src/{06400480,16001200}.spl";
1020 if(!$opt_96) {
1021   for my $f (<$src/*64>) {
1022     if($opt_64) {
1023       (my $s = $f) =~ s/64$//;
1024       rename $f, $s;
1025     }
1026     else {
1027       unlink $f;
1028     }
1029   }
1030 }
1031
1032 # prepare syslinux config file
1033
1034 open F, "$src/isolinux.cfg"; my @cfg = <F>; close F;
1035 push @cfg, "disksize\t2880\n" unless defined $opt_disk;
1036 open F, ">$src/syslinux.cfg"; print F @cfg; close F,
1037 unlink "$src/isolinux.cfg";
1038 unlink "$src/isolinux.bin";
1039
1040 system "cp $opt_syslinux $src/ldlinux.sys" and die "error: no syslinux?\n";
1041
1042 $boot_disks = get_disk_number $src;
1043 $boot_disks = 2 if $boot_disks < 2;
1044
1045 unlink "$src/ldlinux.sys";
1046
1047 my $mp;
1048
1049 if($opt_disk) {
1050   # make (usb) disk bootable
1051
1052   # mbr taken from makebootfat package
1053   my $new_mbr =
1054     "\xeb\x58\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" .
1055     "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" .
1056     "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" .
1057     "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" .
1058     "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" .
1059     "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfa\x31\xc0\x8e\xd8\x8e" .
1060     "\xc0\x8e\xd0\xbc\x00\x7c\xfb\xfc\x89\xe6\xbf\x00\x06\xb9\x00\x01" .
1061     "\xf3\xa5\xea\x77\x06\x00\x00\x88\x16\x00\x08\xbe\x9b\x07\xf6\xc2" .
1062     "\x80\x74\x03\xbe\x9f\x07\xe8\xc7\x00\xb4\x08\xcd\x13\x31\xc0\x88" .
1063     "\xf0\x40\xa3\x74\x07\x80\xe1\x3f\x88\x0e\x76\x07\xbe\xbe\x07\x31" .
1064     "\xc0\xb9\x04\x00\xf6\x04\x80\x74\x03\x40\x89\xf7\x83\xc6\x10\xe2" .
1065     "\xf3\x83\xf8\x01\x74\x03\xe9\x88\x00\x8a\x16\x00\x08\xb8\x00\x41" .
1066     "\xbb\xaa\x55\x31\xc9\x30\xf6\xf9\xcd\x13\x72\x2e\x81\xfb\x55\xaa" .
1067     "\x75\x28\xf6\xc1\x01\x74\x23\xbe\xa3\x07\xe8\x73\x00\x57\xbe\x64" .
1068     "\x07\x8b\x5d\x08\x89\x5c\x08\x8b\x5d\x0a\x89\x5c\x0a\x8a\x16\x00" .
1069     "\x08\x8c\xd8\x8e\xc0\xb8\x00\x42\xeb\x34\xbe\xa9\x07\xe8\x50\x00" .
1070     "\x57\x8b\x45\x08\x8b\x55\x0a\xf7\x36\x76\x07\x42\x89\xd1\x31\xd2" .
1071     "\xf7\x36\x74\x07\x88\xc5\xd1\xe8\xd1\xe8\x24\xc0\x08\xc1\x88\xd6" .
1072     "\x8a\x16\x00\x08\x8c\xd8\x8e\xc0\xbb\x00\x7c\xb8\x01\x02\xcd\x13" .
1073     "\x72\x16\x5e\x81\x3e\xfe\x7d\x55\xaa\x75\x08\xfa\xea\x00\x7c\x00" .
1074     "\x00\x77\x05\xbe\x78\x07\xeb\x03\xbe\x8e\x07\xe8\x02\x00\xeb\xfe" .
1075     "\xac\x20\xc0\x74\x0c\xb4\x0e\x8a\x3e\x62\x04\xb3\x07\xcd\x10\xeb" .
1076     "\xef\xc3\x00\x00\x10\x00\x01\x00\x00\x7c\x00\x00\x00\x00\x00\x00" .
1077     "\x00\x00\x00\x00\x00\x00\x00\x00\x4e\x6f\x20\x6f\x70\x65\x72\x61" .
1078     "\x74\x69\x6e\x67\x20\x73\x79\x73\x74\x65\x6d\x0d\x0a\x00\x44\x69" .
1079     "\x73\x6b\x20\x65\x72\x72\x6f\x72\x0d\x0a\x00\x46\x44\x44\x00\x48" .
1080     "\x44\x44\x00\x20\x45\x42\x49\x4f\x53\x0d\x0a\x00";
1081
1082   my $part = $opt_disk;
1083
1084   $opt_disk =~ s/(\d+)$//;
1085   my $pn = $1;
1086
1087   die "not a partition: $opt_disk\n" unless $pn ne "";
1088
1089   $opt_disk =~ s/(?<=\d)p$//;
1090
1091   print "disk $opt_disk, partition $part\n";
1092
1093   die "sorry, must be a primary partition (number 1 - 4)\n" if $pn < 1 || $pn > 4;
1094
1095   my ($bpc, $fatsize);
1096
1097   for (`fsck.vfat -v $part 2>/dev/null`) {
1098     if(/(\d+)\s+bytes\s+per\s+cluster/) {
1099       $bpc = $1;
1100       next;
1101     }
1102     if(/FATs,\s+(\d+)\s+bit\s+entries/) {
1103       $fatsize = $1;
1104       next;
1105     }
1106   }
1107
1108   die "not a FAT file system\n" unless $bpc >= 512 && $fatsize >= 12;
1109
1110   die "must be 16 bit FAT\n" unless $fatsize == 16;
1111
1112   die "cluster too large (max. 32k)\n" if $bpc > 0x8000;
1113
1114   system "$opt_syslinux $part" and die "error: syslinux failed\n";
1115
1116   system "activate $opt_disk $pn" and die "error: activate failed\n";
1117
1118   system "mount -tmsdos $part $tmp_dir/mp" and die "error: mount failed\n";
1119   $mp = "$tmp_dir/mp";
1120
1121   for (dir_sort $src) {
1122     if($_ ne 'bootlogo') {
1123       system "cp -r $src/$_ $mp" and die "error: copy failed\n";
1124     }
1125     else {
1126       for my $i (unpack_bootlogo $src) {
1127         system "cp -r $src/$i $mp" and die "error: copy failed\n";
1128       }
1129     }
1130   }
1131
1132   system "umount $mp" and die "error: umount failed\n";
1133   undef $mp;
1134
1135   if($opt_backup_mbr) {
1136     my $backup;
1137     open F, "$opt_disk";
1138     sysread F, $backup, 0x200;
1139     close F;
1140
1141     die "mbr backup failed\n" unless length($backup) == 0x200;
1142
1143     open W, ">$opt_backup_mbr" or die "$opt_backup_mbr: $!\n";
1144     die "mbr backup failed\n" unless syswrite(W, $backup) == 0x200;
1145     close W;
1146   }
1147
1148   open W, ">$opt_disk" or die "$opt_disk: $!\n";
1149   die "writing mbr failed\n" unless syswrite(W, $new_mbr) == length($new_mbr);
1150   close W;
1151
1152 }
1153 else {
1154   # make boot disk images
1155
1156   MakeFAT::Image("$tmp_dir/img", $boot_disks, $opt_verbose);
1157
1158   system "$opt_syslinux $tmp_dir/img" and die "error: syslinux failed\n";
1159
1160   system "mount -tmsdos -oloop $tmp_dir/img $tmp_dir/mp" and die "error: mount failed\n";
1161   $mp = "$tmp_dir/mp";
1162   for (dir_sort $src) {
1163     if($_ ne 'bootlogo') {
1164       system "cp -r $src/$_ $mp" and die "error: copy failed\n";
1165     }
1166     else {
1167       for my $i (unpack_bootlogo $src) {
1168         system "cp -r $src/$i $mp" and die "error: copy failed\n";
1169       }
1170     }
1171   }
1172
1173   my $size = 0;
1174   for (`df -Pk $tmp_dir/mp`) {
1175     $i = (split)[3];
1176     if($i =~ /^\d+$/) {
1177       $size = $i;
1178       last;
1179     }
1180   }
1181   system "umount $mp" and die "error: umount failed\n";
1182   undef $mp;
1183
1184   $i = "32 bit";
1185   $i = "64 bit" if $opt_64;
1186   $i = "32+64 bit" if $opt_96;
1187
1188   print "Writing $boot_disks boot disks ($i, ${size}k free).\n";
1189
1190   open F, "$tmp_dir/img";
1191   for ($i = 1; $i <= $boot_disks; $i++) {
1192     unlink "$opt_file$i";
1193     open W, ">$opt_file$i";
1194     sysread F, $buf, 1440*1024;
1195     syswrite W, $buf;
1196     close W;
1197   }
1198   close F;
1199
1200 }
1201
1202
1203 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1204 #
1205 # Unmount image and remove temorary files.
1206 #
1207 sub cleanup
1208 {
1209   system "umount $mp" if $mp;
1210   undef $mp;
1211   system "rm -r $tmp_dir" if ! $opt_keep && -d "$tmp_dir";
1212   undef $tmp_dir;
1213 }
1214
1215
1216 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1217 #
1218 # Sorting function to ensure files are written in the correct order.
1219 #
1220 sub dir_sort_func
1221 {
1222   my ($wa, $wb, $i, $p, $r);
1223
1224   $p = 2;
1225   for $i qw ( memtest biostest initrd64 initrd .*\.spl linux64 linux bootlogo message .*\.cfg ) {
1226     if($i eq 'bootlogo') {
1227       $r = $p;
1228       $p <<= 1;
1229     }
1230     $wa = $p if $a =~ /^$i$/;
1231     $wb = $p if $b =~ /^$i$/;
1232     $p <<= 1;
1233   }
1234
1235   $wa = $r unless $wa;
1236   $wb = $r unless $wb;
1237
1238   return $wb - $wa + ($a cmp $b);
1239 }
1240
1241
1242 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1243 #
1244 # Sort directory.
1245 #
1246 sub dir_sort
1247 {
1248   my ($i, $size, @dir);
1249
1250   opendir D, shift;
1251   @dir = grep { !/^\./ } readdir D;
1252   closedir D;
1253
1254   return ( sort dir_sort_func @dir );
1255 }
1256
1257
1258 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1259 sub help
1260 {
1261   (my $p = $0) =~ s:.*/::;
1262
1263   print STDERR
1264   "Usage: $p [options] cd_mount_point\n" .
1265   "Create boot disk images from SUSE Linux DVD or CD1 or make (USB) disk bootable.\n" .
1266   "Options:\n" .
1267   "  --out file\t\twrite disks as fileN (default: bootdisk)\n" .
1268   "  --32\t\t\tcreate boot disks for 32 bit arch\n" .
1269   "  --64\t\t\tcreate boot disks for 64 bit arch\n" .
1270   "  --partition device\tmake this disk with this partition bootable\n" .
1271   "  --backup-mbr file\tsave mbr to file\n" .
1272   "Examples:\n" .
1273   "  $p /media/cdrom\n" .
1274   "  - write boot disks as bootdisk1 ... bootdiskN (N is approx. 8)\n" .
1275   "  $p --64 --out foo /media/cdrom\n" .
1276   "  - write 64 bit boot disks as foo1 ... fooN\n" .
1277   "  $p --partition /dev/sdb1 --backup-mbr mbr_old /media/cdrom\n" .
1278   "  - copy install files to /dev/sdb1 and write new mbr to /dev/sdb\n";
1279
1280   exit 0;
1281 }
1282
1283 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1284 sub dir_size
1285 {
1286   my ($i, $size);
1287   my ($dir, $block_size) = @_;
1288
1289   for $i (<$dir/*>) {
1290     next if -l $i;
1291     next if $i =~ m|/\.\.?$|;
1292     $size += dir_size($i, $block_size) if -d $i;
1293     $size += ((-s $i) + $block_size - 1) / $block_size if -f $i;
1294   }
1295
1296   return $size;
1297 }
1298
1299
1300 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1301 sub get_disk_number
1302 {
1303   my ( $est, $i, $blocks, $block_size, $dir_size, $disks );
1304
1305   my $src = shift;
1306
1307   # minimum estimate
1308   $est = dir_size($src, 2048) / 720 + 1;
1309
1310   for($i = $est; $i < $est + 10; $i++) {        # max 10 tries
1311     ($blocks, $block_size) = MakeFAT::Image(undef, $i);
1312     # print "$i: $est, $blocks, $block_size\n";
1313
1314     $dir_size = dir_size($src, $block_size);
1315     # print "$blocks, $dir_size\n";
1316     if($blocks >= $dir_size) {
1317       $disks = $i;
1318       last;
1319     }
1320     undef $blocks;
1321   }
1322
1323   return $disks;
1324 }
1325
1326
1327 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1328 sub unpack_bootlogo
1329 {
1330   my ($dir, $tmp, $files, @files, @ext);
1331   local $_;
1332
1333   $dir = shift;
1334   $tmp = "$dir/bootlogo.unpacked";
1335
1336   mkdir "$tmp", 0755;
1337
1338   @files = `cpio --quiet -t <$dir/bootlogo`;
1339
1340   system "cd $tmp; cpio --quiet -i <../bootlogo";
1341
1342   for (@files) {
1343     chomp;
1344     if(-k("$tmp/$_") && ! -l("$tmp/$_")) {
1345       push @ext, $_;
1346       undef $_;
1347     }
1348   }
1349
1350   open P, "| cd $tmp; cpio --quiet -o >../bootlogo";
1351   print P "$_\n" for grep $_, @files;
1352   close P;
1353
1354   system "mv $tmp/$_ $dir" for @ext;
1355
1356   return ( 'bootlogo', @ext );
1357 }
1358
1359
1360