| #!/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 |