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