#!/usr/bin/perl # # Copyright (c) International Business Machines Corp., 2006 # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See # the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # # mkpfi # # This perl program is assembles PFI files from a config file. # # Author: Oliver Lohmann (oliloh@de.ibm.com) # use warnings; use strict; use lib "/usr/lib/perl5"; # Please change this path as you need it, or # make a proposal how this could be done # nicer. use Getopt::Long; use Pod::Usage; use Config::IniFiles; use File::Temp; # ---------------------------------------------------------------------------- # Versions our $version : unique = "0.1"; our $pfi_version : unique = "0x1"; # ---------------------------------------------------------------------------- # Globals my $verbose = 0; my $cfg; my %opts = (); my %files = (config => ""); my @tmp_files; my %tools = (ubicrc32 => "ubicrc32"); # ---------------------------------------------------------------------------- # Processing the input sections # # The idea is to combine each section entry with a function # in order to allow some kind of preprocessing for the values # before they are written into the PFI file. # This is especially useful to be more verbose and # user-friendly in the layout file. # # All key-function hashes are applied after the general # validation of the configuration file. # If any mandatory key is missing in a section the user # will be informed and the PFI creation process is aborted. # # Default keys will be checked for their presence inside the config # file. If they are missing, they will be generated with appr. values. # Mandatory keys for UBI volumes. my %ubi_keys = ("ubi_ids" => \&check_id_list, "ubi_size" => \&replace_num, "ubi_type" => \&replace_type, "ubi_names" => \&remove_spaces, "ubi_alignment" => \&replace_num); # Mandatory keys for RAW sections. my %raw_keys = ("raw_starts" => \&expand_starts, "raw_total_size" => \&replace_num); # Common default keys for documentation and control purposes. my %common_keys = ("flags" => \&replace_num, "label" => \&do_nothing); # Define any defaults here. Values which maintained in this default # region need not to be specified by the user explicitly. my %def_ubi_keys = ("ubi_alignment" => [\&set_default, "0x1"]); my %def_raw_keys = (); my %def_common_keys = ("flags" => [\&set_default, "0x0"], "label" => [\&generate_label, ""]); # ---------------------------------------------------------------------------- # Input keys, actually the path to the input data. my %input_keys = ("image" => \&do_nothing); # Placeholder keys allow the replacement via a special # purpose function. E.g. the bootenv_file key will be used # to generate bootenv binary data from an text file and # replace the bootenv_file key with an image key to handle it # in the same way in the further creation process. my %input_placeholder_keys = ("bootenv_file" => \&create_bootenv_image); # ---------------------------------------------------------------------------- # Helper # @brief Get current time string. sub get_date { my $tmp = scalar localtime; $tmp =~ s/ /_/g; return $tmp; } # @brief Print an info message to stdout. sub INFO($) { my $str = shift; if (!$verbose) { return; } print STDOUT $str; } # @brief Print an error message to stderr. sub ERR($) { my $str = shift; print STDERR $str; } # @brief Print a warning message to stderr. sub WARN($) { my $str = shift; print STDERR $str; } sub parse_command_line($) { my $opt = shift; my $result = GetOptions( "help" => \$$opt{'help'}, "man" => \$$opt{'man'}, "config=s" => \$$opt{'config'}, "verbose" => \$$opt{'verbose'}, ) or pod2usage(2); pod2usage(1) if defined ($$opt{help}); pod2usage(-verbose => 2) if defined ($$opt{man}); $verbose = $$opt{verbose} if defined $$opt{verbose}; if (!defined $$opt{config}) { ERR("[ ERROR: No config file specified. Aborting...\n"); exit 1; } } # @brief Check if all needed tools are in PATH. sub check_tools { my $err = 0; my $key; foreach $key (keys %tools) { if (`which $tools{$key}` eq "") { ERR("\n") if ($err == 0); ERR("! Please add the tool \'$tools{$key}\' " . "to your path!\n"); $err = 1; } } die "[ ERROR: Did not find all needed tools!\n" if $err; } sub open_cfg_file($) { my $fname = shift; my $res = new Config::IniFiles( -file => $fname ); die "[ ERROR: Cannot load your config file!\n" if (!defined $res); return $res; } sub set_default($$$$) { my ($cfg, $section, $parameter, $def_value) = @_; $cfg->newval($section, $parameter, $def_value); return; } sub generate_label($$$$) { my ($cfg, $section, $parameter, $def_value) = @_; my $new_label = $def_value . $section; $new_label .= "_" . get_date; $cfg->newval($section, $parameter, $new_label); return; } # @brief Converts any num to a unified hex string, i.e the resulting value # always starts with "0x" and is aligned to 8 hexdigits. # @return Returns 0 on success, otherwise an error occured. # sub any_num_to_hex($$) { my $val = shift; my $res = shift; # M(iB) if ($val =~ m/([0-9]+)[Mm][i]?[Bb]?/g) { $$res = sprintf("0x%08x", $1 * 1024 * 1024); } # k(iB) elsif ($val =~ m/([0-9]+)[kK][i]?[Bb]?/g) { $$res = sprintf("0x%08x", $1 * 1024); } # hex elsif ($val =~ m/0x?([0-9a-fA-F]+)/g) { $$res = sprintf("0x%08x", hex $1); } # decimal elsif ($val =~ m/^([0-9]+)$/g) { $$res = sprintf("0x%08x", $1); } else { $$res = ""; return -1; } return 0; } sub remove_spaces($$$) { my ($cfg, $section, $parameter) = @_; my ($start, @starts, @new_starts); my $val = $cfg->val($section, $parameter); my $res; $val =~ s/ //g; # spaces $cfg->newval($section, $parameter, $val); } sub expand_starts($$$) { my ($cfg, $section, $parameter) = @_; my ($start, @starts, @new_starts); my $val = $cfg->val($section, $parameter); my $res; $val =~ s/ //g; # spaces @starts = split(/,/, $val); foreach $start (@starts) { if (any_num_to_hex($start, \$res) != 0) { ERR("[ ERROR: [$section]\n"); ERR("[ Expecting a list of numeric " . "values for parameter: $parameter\n"); exit 1; } push (@new_starts, $res); } $res = join(',', @starts); $cfg->newval($section, $parameter, $res); } sub check_id_list($$$) { my ($cfg, $section, $parameter) = @_; my $val = $cfg->val($section, $parameter); my $res; if (!($val =~ m/^[0-9]+[,0-9]*/)) { ERR("[ ERROR: Syntax error in 'ubi_ids' in " . "section '$section': $val\n"); ERR("[ Aborting... "); exit 1; } } sub replace_type($$$) { my ($cfg, $section, $parameter) = @_; my $val = $cfg->val($section, $parameter); my $res; $res = lc($val); grep {$res eq $_} ('static', 'dynamic') or die "[ ERROR: Unknown UBI Volume Type in " . "section '$section': $val\n"; $cfg->newval($section, $parameter, $res); } sub replace_num($$$) { my ($cfg, $section, $parameter) = @_; my $val = $cfg->val($section, $parameter); my $res = ""; if (any_num_to_hex($val, \$res) != 0) { ERR("[ ERROR: [$section]\n"); ERR("[ Expecting a numeric value " . "for parameter: $parameter\n"); exit 1; } $cfg->newval($section, $parameter, $res); } sub do_nothing($$$) { my ($cfg, $section, $parameter) = @_; return; } sub bootenv_sanity_check($) { my $env = shift; # hash array containing bootenv my %pdd = (); defined($$env{'pdd'}) or return "'pdd' not defined"; foreach (split /,/, $$env{'pdd'}) { defined($$env{$_}) or return "undefined '$_' in pdd"; $pdd{$_} = 1; } defined $$env{'pdd_preserve'} or return ""; foreach (split /,/, $$env{'pdd_preserve'}) { defined($pdd{$_}) or return "pdd_preserve field '$_' not in pdd"; } return ""; } sub create_bootenv_image($$$) { my ($cfg, $section, $parameter) = @_; my $txt_fn = $cfg->val($section, "bootenv_file"); my $in; my %value = (); my @key = (); open $in, "<", $txt_fn or die "[ ERROR: can't open bootenv file '$txt_fn'.\n"; while (<$in>) { next if (/^\s*(\#.*)?$/); # Skip comments/whitespace. if (/^(\S+?)\+\=(.*)$/) { defined($value{$1}) or die "$txt_fn:$.: error: appending to" . " non-existent '$1'\n"; $value{$1} .= $2; } elsif (/^(\S+?)\=(.*)$/) { not defined($value{$1}) or die "$txt_fn:$.: error: trying to" . " redefine '$1'\n"; push @key, $1; $value{$1} = $2; } else { die "$txt_fn:$.: error: unrecognized syntax\n"; } } close $in; $_ = &bootenv_sanity_check(\%value) and die "$txt_fn: error: $_\n"; my $tmp_file = new File::Temp(); push (@tmp_files, $tmp_file); foreach (@key) { print $tmp_file "$_=", $value{$_}, "\0"; } close $tmp_file; $cfg->newval($section, "image", $tmp_file-> filename); } sub process_keys($$$) { my ($cfg, $section, $keys) = @_; my @parameters = $cfg->Parameters($section); my $i; for ($i = 0 ; $i < scalar(@parameters) ; $i++ ) { if (defined($$keys{$parameters[$i]})) { $$keys{$parameters[$i]}->($cfg, $section, $parameters[$i]); } } } sub is_in_keylist($$) { my ($key, $keys) = @_; my $i; for ($i = 0; $i < scalar(@$keys); $i++) { if ($$keys[$i] eq $key) { return 1; } } return 0; } sub check_default_keys($$$) { my ($cfg, $section, $keys) = @_; my @parameters = $cfg->Parameters($section); my $key; foreach $key (keys %$keys) { if (!is_in_keylist($key, \@parameters)) { $$keys{$key}[0]-> ($cfg, $section, $key, $$keys{$key}[1]); } } } sub check_keys($$$) { my ($cfg, $section, $keys) = @_; my @parameters = $cfg->Parameters($section); my ($i, $key, $err); $err = 0; for ($i = 0 ; $i < scalar(@$keys) ; $i++ ) { if (!is_in_keylist($$keys[$i], \@parameters)) { ERR("[ ERROR: [$section]\n") if $err == 0; $err = 1; ERR("[ Missing key '$$keys[$i]'\n"); } } if ($err) { ERR("[ Aborting...\n"); exit 1; } } sub push_pfi_data($$$$$) { my ($cfg, $section, $pfi_infos, $keys, $mode) = @_; my ($tmp, $i, $hdr); my %pfi_info = (); $pfi_info{'mode'} = $mode; $pfi_info{'image'} = $cfg->val($section, "image"); # Build the PFI header $hdr = sprintf("PFI!\n"); $hdr .= sprintf("version=0x%08x\n", hex $pfi_version); $hdr .= sprintf("mode=$mode\n"); # calculate the size of the binary data part $tmp = -s $cfg->val($section, "image"); if (!defined $tmp) { ERR("[ ERROR: [$section]\n"); ERR("[ Missing input image: " . $cfg->val($section, "image") . "\n"); exit 1; } # Check for the image to fit into the given space my $quota; if ($mode eq 'raw') { $quota = oct $cfg->val($section, "raw_total_size"); } elsif ($mode eq 'ubi') { $quota = oct $cfg->val($section, "ubi_size"); } $tmp <= $quota or die "[ERROR: image file too big: " . $cfg->val($section, "image") . "\n"; $pfi_info{'size'} = $tmp; $hdr .= sprintf("size=0x%08x\n", $tmp); my $img_file = $cfg->val($section, "image"); my $crc32 = `$tools{'ubicrc32'} $img_file 2>&1`; if (any_num_to_hex($crc32, \$tmp) != 0) { die "[ ERROR: $tools{'ubicrc32'} returned with errors"; } $hdr .= sprintf("crc=$tmp\n"); # Process all remaining keys for ($i = 0; $i < scalar (@$keys); $i++) { if ($$keys[$i] eq "image") { # special case image input file if (! -e ($tmp = $cfg->val($section, "image"))) { ERR("[ ERROR: [$section]\n"); ERR("[ Cannot find input file $tmp\n"); exit 1; } next; } $hdr .= sprintf("%s=%s\n", $$keys[$i], $cfg->val($section, $$keys[$i])); } $hdr .= sprintf("\n"); # end marker for PFI-header $pfi_info{'header'} = $hdr; # store in the header list push @$pfi_infos, \%pfi_info; } sub process_section($$$$$$) { my ($cfg, $section, $pfi_infos, $custom_keys, $def_custom_keys, $mode) = @_; my @keys = (keys %common_keys, keys %$custom_keys); my @complete_keys = (@keys, keys %input_keys); # set defaults if necessary check_default_keys($cfg, $section, $def_custom_keys); check_default_keys($cfg, $section, \%def_common_keys); # check for placeholders... process_keys($cfg, $section, \%input_placeholder_keys); # VALIDATE layout.cfg entries check_keys($cfg, $section, \@complete_keys); # execute linked functions (if any) process_keys($cfg, $section, \%common_keys); process_keys($cfg, $section, $custom_keys); push_pfi_data($cfg, $section, $pfi_infos, \@keys, $mode); } sub get_section_info($$) { my ($cfg, $section) = @_; my @parameters = $cfg->Parameters($section); my ($ubi, $raw, $i, @res); $ubi = $raw = 0; for ($i = 0 ; $i < scalar(@parameters) ; $i++ ) { if ($parameters[$i] =~ m/ubi_/gi) { $ubi = 1; @res = (\%ubi_keys, \%def_ubi_keys, "ubi"); } if ($parameters[$i] =~ m/raw_/gi) { $raw = 1; @res = (\%raw_keys, \%def_raw_keys, "raw"); } } if (($ubi + $raw) != 1) { # double definition in section ERR("[ ERROR: Layout error in section '$section'\n"); exit 1; } return @res; } sub mk_target_list($$) { my $val = shift; my $tmp = shift; my $complete = 0; if ($val =~ m/\((.*)\)/g) { $val = $1; $complete = 1; } $val =~ s/ //g; # spaces @$tmp = split(/,/, $val); return $complete; } sub copy_bytes($$$) { my ($in, $out, $to_copy) = @_; while ($to_copy) { my $buf; my $bufsize = 1024*1024; $bufsize < $to_copy or $bufsize = $to_copy; read($in, $buf, $bufsize) == $bufsize or die "[ ERROR: Image file shrunk during operation\n"; print $out $buf; $to_copy -= $bufsize; } } sub write_target($$) { my ($pfi_infos, $target) = @_; my ($pfi_info); INFO("[ Writting target pfi file: '$target.pfi'...\n"); if (-e "$target.pfi") { WARN("! Replaced old pfi...\n"); `rm -f $target.pfi`; } open(FILE, ">", "$target.pfi") or die "[ ERROR: Cannot create output file: $target.pfi\n"; binmode(FILE); # @FIXME sort by mode (first raw, then ubi) # Currently this ordering is based on a string comparism. :-) @$pfi_infos = sort {(lc $$a{'mode'}) cmp (lc $$b{'mode'})} @$pfi_infos; # Print all headers first foreach $pfi_info (@$pfi_infos) { print FILE $$pfi_info{'header'}; } # Print the linked data sections print FILE "DATA\n"; foreach $pfi_info (@$pfi_infos) { open(IMAGE, "<", $$pfi_info{'image'}) or die "[ ERROR: Cannot open input image: " . "$$pfi_info{'image'}" . "\n"; binmode(IMAGE); ©_bytes(\*IMAGE, \*FILE, $$pfi_info{'size'}); close(IMAGE) or die "[ ERROR: Cannot close input image: " . "$$pfi_info{'image'}" . "\n"; } close(FILE) or die "[ ERROR: Cannot close output file: $target.pfi\n"; } sub process_config($) { my $cfg = shift; my @sections = $cfg->Sections; my ($i, $j, $keylist, $def_keylist, $mode, $tmp, @tlist, $complete,@pfi_infos); my @parameters = $cfg->Parameters("targets") or die "[ ERROR: Config file has no 'targets' section!\n"; for ($i = 0 ; $i < scalar(@parameters) ; $i++ ) { INFO("[ Processing target '$parameters[$i]'...\n"); @pfi_infos = (); # get a list of subtargets $complete = mk_target_list($cfg->val("targets", $parameters[$i]), \@tlist); # build all subtargets for ($j = 0 ; $j < scalar(@tlist) ; $j++ ) { ($keylist, $def_keylist, $mode) = get_section_info($cfg, $tlist[$j]); process_section($cfg, $tlist[$j], \@pfi_infos, $keylist, $def_keylist, $mode); } write_target(\@pfi_infos, $parameters[$i]); } INFO("[ Success.\n"); } sub clear_files() { # @FIXME: # Works implicitly and Fedora seems to have removed # the cleanup call. Thus for now, inactive. # File::Temp::cleanup(); } require 5.008_000; # Tested with version 5.8.0. select STDOUT; $| = 1; # make STDOUT output unbuffered select STDERR; $| = 1; # make STDERR output unbuffered parse_command_line(\%opts); check_tools; $cfg = open_cfg_file($opts{config}); process_config($cfg); clear_files; __END__ =head1 NAME mkpfi - Using GetOpt::Long, Pod::Usage, Config::IniFiles =head1 SYNOPSIS mkpfi [OPTIONS ...] OPTION [--config] [--help] [--man] =head1 ABSTRACT Perl script for generating pdd pfi files from given config files. =head1 OPTIONS =over =item B<--help> Print out brief help message. =item B<--usage> Print usage. =item B<--config> Config input file. =item B<--man> Print manual page, same as 'perldoc mkpfi'. =item B<--verbose> Be verbose! =back =head1 BUGS Report via MTD mailing list =head1 SEE ALSO http://www.linux-mtd.infradead.org/ =head1 AUTHOR Oliver Lohmann (oliloh@de.ibm.com) =cut