Subversion Repositories DevTools

Rev

Rev 1107 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1105 dpurdie 1
########################################################################
2
# Copyright (C) 2008 ERG Limited, All rights reserved
3
#
4
# Module name   : CreateSdImage.pm
5
# Module type   : Makefile system
6
# Compiler(s)   : Perl
7
# Environment(s): jats
8
#
9
# Description   : Create a File System Image from a directory
10
#                 tree.
11
#                 Create the various file partitions and images
12
#                   0) Determine the partition sizes
13
#                   1) Create an empty image of the entire SD card
14
#                   2) Partition it up
15
#                   3) Determine the real size of each patition
16
#                   4) Create ext3 file system images
17
#                   5) Transfer the file system images into the SD image
18
#                   6) Install grub - if required
19
#
20
#......................................................................#
21
 
22
require 5.008_002;
23
use strict;
24
use warnings;
25
 
26
package CreateSdImage;
27
 
28
use JatsError;
29
use JatsSystem;
30
use FileUtils;
31
use File::Path;
32
 
33
 
34
# automatically export what we need into namespace of caller.
35
use Exporter();
36
our (@ISA, @EXPORT);
37
@ISA         = qw(Exporter);
38
@EXPORT      = qw(
39
                    CreateSdImage
40
                );
41
 
42
#
43
#   Data structure to describe the ouput partition(s)
44
#   Defined early so that it can be modified by the platform specific hook
45
#   Structure is a hash of hashed elements
46
#   Key: Partition number
47
#        Partition-0 is used to hold a complete (test) image
48
#
49
#   Value: A hash of other entries
50
#          These are
51
#          Key: name        - Name of the partition
52
#               root        - Partition mount point
53
#               size        - Size in Megabytes of the partition
1109 dpurdie 54
#                             Set internally.
55
#                             'rest' is auto calculated, must be last
1105 dpurdie 56
#               type        - Optional. If 'extended', then an extended
57
#                             partition will be created and all following
58
#                             partitions will be created in that extended
59
#                             partition
60
#               noprune     - Don't prune after partition
61
#                             Set internally.
62
#
63
our %partitions = (
1109 dpurdie 64
 
1105 dpurdie 65
 
1109 dpurdie 66
        1 => { name=>"rootfs",  root=>"/"           },
67
        2 => { name=>"logfs" ,  root=>"/var/log"    },
68
        3 => { name=>"afcfs",   root=>"/afc"        },
69
        4 => { name=>"varfs",   root=>"/var"        },
1105 dpurdie 70
 
71
#       4 => { type=>"extended" },
72
    );
73
 
74
#-------------------------------------------------------------------------------
75
# Function        : CreateSdImage
76
#
77
# Description     : Create an ETX3 File System Image froma directory
78
#
79
# Inputs          : $outdir             - Target directory
80
#                   $image_name         - Root name of the output image
81
#                   $WORK               - Root of file system image
82
#                   $BUILD              - Scratch Work Area
1109 dpurdie 83
#                   $diskGeometry       - Reference to disk Geometry hash
84
#                                         Keys are:
85
#                                           fullfs
86
#                                           size  
87
#                                           rootfs
88
#                                           logfs 
89
#                                           afcfs 
90
# Returns         :                         varfs 
1105 dpurdie 91
#
92
sub CreateSdImage
93
{
1109 dpurdie 94
    my ($outdir,$image_name,$WORK, $BUILD, $diskGeometry ) = @_;
1107 dpurdie 95
    my $install_grub = ( -f "$WORK/boot/grub/grub" );
1105 dpurdie 96
 
97
    #
1107 dpurdie 98
    #   Ensure that the required utility programs can be found and that they
99
    #   are being consumed from packages and not from the users path
1105 dpurdie 100
    #
1107 dpurdie 101
    Error ( "Required utility program not found in package: e2fsimage" )
102
        unless ( LocateProgInPath ('e2fsimage' ) );
103
 
104
    Error ( "Required utility program not found in package: grub" )
105
        unless ( ! $install_grub || LocateProgInPath ('grub' ) );
106
 
1109 dpurdie 107
    Error ("CreateSdImage:disk-blocks must be specified")
108
        unless ( $diskGeometry->{'size'}  );
109
    my $disk_blocks = $diskGeometry->{'size'};
1107 dpurdie 110
 
111
    #
112
    #
1105 dpurdie 113
    #   Magic: The size of the SD card
114
    #          This was determined by 'dd' ing an SD card and using the number of
115
    #          blocks that could be extracted.
116
    #
117
    my $disk_sectors = $disk_blocks * 2;
118
    $image_name .= '.raw';
119
    my $disk_image = "$BUILD/$image_name";
120
    my $pr_size = 20 * 2;                       # Sectors for a partition table
121
 
122
    #
123
    #   Determine partition start and end sectors
124
    #   Work in sectors
125
    #
126
    my  $pstart = $pr_size;
127
    my  $in_extended = 0;
128
 
129
    Message ("Calculate partition sizes");
130
    foreach my $partition ( sort keys %partitions )
131
    {
132
        my $pdata = $partitions{$partition};
133
        my $rest = 0;
134
 
1109 dpurdie 135
        #
136
        #   Determine configured size
137
        #
138
        if ( $pdata->{name}  )
139
        {
140
            Error ("CreateSdImage: Partition size not specified: $pdata->{name} ")
141
                unless ( exists($diskGeometry->{$pdata->{name}}) );
142
            $pdata->{size} =  $diskGeometry->{$pdata->{name}} || 0;
143
        }
144
 
1105 dpurdie 145
        unless ($partition )
146
        {
147
            $pdata->{size} *= 2;
148
            next;
149
        }
150
 
1109 dpurdie 151
        #
152
        #   Validate the current start
153
        #
154
        if ( $pstart >= $disk_sectors )
155
        {
156
            my @data;
157
            foreach my $partition ( sort keys %partitions )
158
            {
159
                next unless ( $partition );
160
                my $pdata = $partitions{$partition};
161
                my $name = $pdata->{name};
162
                next unless ( $name );
163
 
164
                push @data, sprintf( "Partition-%d (%10s): Sector Size=~%5dM\n",
165
                            $partition,
166
                            $name,
167
                            ($diskGeometry->{$pdata->{name}} || 'Remainder') );
168
            }
169
 
170
            Error ("Invalid Disk Configuration. Partition Sizes exceed Disk size",
171
                   "Disk Size : $disk_blocks (~" . $disk_blocks / 1024 . ")M",
172
                    @data);
173
        }
174
 
1105 dpurdie 175
        if (  exists $pdata->{type} && $pdata->{type} eq 'extended' )
176
        {
177
            $pdata->{start} = $pstart;
178
            $pdata->{end} =   $disk_sectors;
179
            $pdata->{size} = ($disk_sectors - $pstart);
180
            $pdata->{id} = 5;
181
            $in_extended = 1;
182
        }
183
        else
184
        {
185
            Error ("Partition Table setup. The 'rest' keyword must only be used on the last partition") if ( $rest );
186
            $pdata->{type} = 'Linux';
187
            $pdata->{id} = 83;
188
            $pstart += $pr_size if ( $in_extended );
189
 
190
            if ( $pdata->{size} eq 'rest' )
191
            {
192
                Error ("Partition Table setup. The 'rest' keyword can only be used once") if ( $rest );
193
                $pdata->{size} = ($disk_sectors - $pstart) / 2048;
194
                $rest = 1;
195
            }
196
 
197
            $pdata->{start} = $pstart;
198
            $pdata->{end} =   $pstart + ( $pdata->{size} * 1024 * 2);
199
            $pdata->{size} =  $pdata->{end} - $pdata->{start};
200
            $pstart = $pdata->{end};
201
        }
202
    }
203
 
204
    #
205
    #   Display calculated partition sizes
206
    #
207
    foreach my $partition ( sort keys %partitions )
208
    {
209
        next unless ( $partition );
210
        my $pdata = $partitions{$partition};
211
        my $name = $pdata->{name} || "";
212
 
213
        printf "Partition-%d (%10s): Sector Start=%10d, End=%10d, Sector Size=%10d(~%5dM), Id=%s\n",
214
                    $partition,
215
                    $name,
216
                    $pdata->{start},
217
                    $pdata->{end},
218
                    $pdata->{size},
219
                    $pdata->{size}/2/1024,
220
                    $pdata->{id};
221
    }
222
 
223
    Message ("Creating Empty SD Image");
224
    System ('dd', 'if=/dev/zero', "of=$disk_image", 'bs=1024', "count=$disk_blocks" );
225
 
226
    Message ("Creating Partition Table");
227
    open (SFDISK, "| /sbin/sfdisk  -f -L $disk_image" ) || Error ("Cannot invoke sfdisk");
228
    print SFDISK "unit: sectors\n\n";
229
    foreach my $partition ( sort keys %partitions )
230
    {
231
        next unless ( $partition );
232
        my $pdata = $partitions{$partition};
233
        printf SFDISK "Partition-%d: start=%10d, size=%10d, Id=%s\n",
234
                    $partition,
235
                    $pdata->{start},
236
                    $pdata->{size},
237
                    $pdata->{id};
238
    }
239
    close (SFDISK);
240
 
241
    ################################################################################
242
    #
243
    #   Split the complete file system image into required partitions
244
    #   Once the partition has been created it will be removed from the image
245
    #   What is left is the root file system - the final partition
246
    #
247
    #   Note: Size values are taken after the SD card has been partitioned
248
    #         The partioning process wants to place partitions on cylinder
249
    #         boundaries. This is why the number are not nice powers of two or 10
250
    #
251
    #         Partition logic:
252
    #           P1 root        - 30Meg
253
    #           P2 log         - 20Meg              Mounted as /var/log)
254
    #           P3 afc         - 150Meg
255
    #           P4 var         - Rest (~290 Meg)
256
    #
257
    #
258
    #   Note: Order is important, as dir tree will be pruned
259
    #         Be smart and determine order to create_fs
260
    #
261
    foreach my $partition ( CalcPartitionOrder() )
262
    {
263
        create_fs ($WORK, $BUILD, $partition);
264
    }
265
 
266
    #
267
    #   Insert the images into the SD image
268
    #
269
    foreach my $partition ( sort keys %partitions )
270
    {
271
        next unless ( $partition );
272
        my $pdata = $partitions{$partition};
273
        next unless ( defined $pdata->{fsname} );
274
 
275
        Message ("Insert filesystem $pdata->{name} into SD Image");
276
        my $cmd = "dd if=$pdata->{fsname} of=$disk_image bs=512 conv=notrunc seek=$pdata->{start} count=$pdata->{size}";
277
        System ($cmd);
278
    }
279
 
280
    ################################################################################
281
    #   
282
    #   Install grub into the MBR of the file system [ optional]
283
    #   This is only required for an x86 target and will detected by the
284
    #   presence of a key grub file
285
    #   The grub files will have been copied into the filesystem image earlier
286
    #
1107 dpurdie 287
    if ( $install_grub )
1105 dpurdie 288
    {
289
        Message ("Installing Grub into the MBR");
1107 dpurdie 290
 
1105 dpurdie 291
        #
292
        #   Need to run a copy of grab (built for the build machine) on the image
293
        #
294
        #   Need a command file to feed into grub
295
        #   Prevent grub from scanning bios
296
        #
297
        my $map_file = "$BUILD/device_map";
298
        open (GRUB, ">$map_file" ) || Error ("Cannot create grub device map file: $map_file");
299
        print GRUB "(hd0) $disk_image\n";
300
        close (GRUB);
301
 
302
        #
303
        #   Run grub and pipe in commands
304
        #
305
        open (GRUB, "| grub  --device-map=$map_file --batch" ) || Error ("Cannot invoke GRUB");
306
        print GRUB "device (hd0) $disk_image\n";
307
        print GRUB "root (hd0,0)\n";
308
        print GRUB "setup (hd0)\n";
309
        close (GRUB);
310
 
311
        #
312
        #   Cleanup temp files
313
        #
314
        unlink $map_file;
315
    }
316
 
317
    ################################################################################
318
    #
319
    #   Package up the file system images
320
    #   Transfer the required build artifacts directly into the package
321
    #
322
    #   This images are large. They are stored in a gzip form to compress them
323
    #
324
    #   Output to disk as:
325
    #       gunzip -c rootfs.ext3.gz | dd  of=/dev/sdb1 bs=10K
326
    #
327
    mkpath( $outdir, 0, 0775);
328
    Message ("Package up the (compressed) filesystem images");
329
    my %package_list;
330
 
331
    #
332
    #   Complete image
333
    #
334
    my $ofile = "$outdir/${image_name}.gz";
335
    $package_list{$ofile}{type} = 'image';
336
    System ("gzip -c $disk_image > $ofile");
337
 
338
#    #
339
#    #   Create test images of the internal partitions
340
#    #
341
#    mkpath( "$outdir/test", 0, 0775);
342
#    foreach my $partition ( sort keys %partitions )
343
#    {
344
#        my $pdata = $partitions{$partition};
345
#        next unless ( defined $pdata->{fsname} );
346
#
347
#        my $file = $pdata->{fsname} ;
348
#        my $ofile = StripDir($file) ;
349
#        $ofile = "$outdir/test/${ofile}.gz";
350
#        $package_list{$ofile}{type} = 'ext3';
351
#        $package_list{$ofile}{partition} = $partition;
352
#        System ("gzip -c $file > $ofile");
353
#    }
354
#
355
    #
356
    #   Useful information for testing
357
    #
358
    Message ("Useful load commands for testing");
359
    foreach my $entry ( sort keys %package_list )
360
    {
361
        next unless ( defined $package_list{$entry}{partition} );
362
 
363
        my $ifname = AbsPath( $entry );
364
        my $ofname = "/dev/sdb" . $package_list{$entry}{partition} ;
365
 
366
        my $cmd = "gunzip -c $ifname | dd bs=1M of=$ofname";
367
        Message $cmd;
368
    }
369
 
370
    foreach my $entry ( sort keys %package_list )
371
    {
372
        next if ( defined $package_list{$entry}{partition} );
373
 
374
        my $ifname = AbsPath( $entry );
375
        my $ofname = "/dev/sdb";
376
 
377
        my $cmd = "gunzip -c $ifname | dd bs=1M of=$ofname";
378
        Message $cmd;
379
    }
380
}
381
 
382
#-------------------------------------------------------------------------------
383
# Function        : create_fs
384
#
385
# Description     : Create the ext3 image of a given partition
386
#
387
# Inputs          : $WORK               - Root of data
388
#                   $BUILD              - Scratch Area
389
#                   $FS_PART            - Partition number
390
#
391
# Returns         : 
392
#
393
sub create_fs
394
{
395
    my ($WORK, $BUILD, $FS_PART) = @_;
396
    my $pdata = $partitions{$FS_PART} || Error ("Unknown partition number: $FS_PART");
397
    my $FS_IMAGE = $pdata->{name};
398
    my $FS_BASE =  $pdata->{root};
399
    my $FS_SIZE = $pdata->{size} / 2;
400
 
1107 dpurdie 401
    #
402
    #   The 'full' image may not be required
403
    #   It will have a zero size
404
    #
405
    return unless ( $FS_SIZE );
406
 
1105 dpurdie 407
    Message "Creating EXT3 file system: ${FS_IMAGE}, Partition: ${FS_PART}, Size: ${FS_SIZE} blocks";
408
 
409
    $FS_IMAGE = "${BUILD}/${FS_IMAGE}.ext3";
410
    $pdata->{fsname} = $FS_IMAGE;
411
    unlink ${FS_IMAGE};
412
 
413
    my $cmd = "e2fsimage -f ${FS_IMAGE} -d ${WORK}${FS_BASE} -u 0 -g 0 -s ${FS_SIZE}";
414
    $cmd .= " -v " if ( IsVerbose(1) );
415
    Verbose $cmd;
416
    open (E2FSIMAGE, "$cmd |" ) || Error ("Can't invoke e2fsimage");
417
    while ( <E2FSIMAGE> )
418
    {
419
        chomp;
420
        Verbose ("          $_");
421
    }
422
    close (E2FSIMAGE);
423
 
424
    $cmd = "/sbin/tune2fs -j -c 10 -i 5d ${FS_IMAGE}";
425
    Verbose $cmd;
426
    open (E2FSIMAGE, "$cmd |" ) || Error ("Can't invoke tunefs");
427
    while ( <E2FSIMAGE> )
428
    {
429
        chomp;
430
        Verbose ("          $_");
431
    }
432
    close (E2FSIMAGE);
433
 
434
    #
435
    #   Clean the subtree that has been encapsulated in the partition
436
    #   Leave the mountpoint
437
    #
438
    unless ( $pdata->{noprune} )
439
    {
440
        Verbose ("Cleaning partition data - leave mount point", $pdata->{root});
441
        foreach my $file ( glob("${WORK}/${FS_BASE}/*") )
442
        {
443
            if ( -d $file )
444
            {
445
                rmtree( $file );
446
            }
447
            else
448
            {
449
                unlink ($file);
450
            }
451
        }
452
    }
453
}
454
 
455
#-------------------------------------------------------------------------------
456
# Function        : CalcPartitionOrder
457
#
458
# Description     :  Determine the order in which we need to create the partitions
459
#                    Simple: if 'A' is a subset of 'B' then 'A' must be done first
460
#
461
# Inputs          :
462
#
463
# Returns         : 
464
#
465
sub CalcPartitionOrder
466
{
467
    my %pdata;
468
    my @porder;
469
 
470
    #
471
    #   Examine all partitions
472
    #   Partition-0: process first, don't prune
473
    #   Ignore extended partions. The only affect the partition  table
474
    #
475
    foreach my $partition ( sort keys %partitions )
476
    {
477
        if ( $partition == 0 )
478
        {
479
            push @porder, $partition;
480
            $partitions{$partition}{noprune} = 1;
481
            next;
482
        }
483
        next if ( ($partitions{$partition}{type} || '') eq 'extended'  );
484
        $pdata{$partitions{$partition}{root}} = $partition;
485
    }
486
 
487
    #
488
    #   Now we have the names of the partions
489
    #   Reverse Order
490
    #
491
    foreach my $name ( reverse sort keys %pdata )
492
    {
493
        push @porder, $pdata{$name};
494
    }
495
 
496
    #
497
    #   Mark the last partition as non-prune
498
    #
499
    my $last = $porder[$#porder];
500
    $partitions{$last}{noprune} = 1;
501
 
502
    #
503
    #   Return the order in which partitions MUST be pruned
504
    #
505
    return @porder;
506
}
507