######################################################################## # COPYRIGHT - VIX IP PTY LTD ("VIX"). ALL RIGHTS RESERVED. # # Module name : ManifestFiles.pm # Module type : Makefile system # Environment(s): JATS Build System # Documents : MASS-00232 Format of the Linux App Upgrade Manifest File # # Description : This package extends the JATS toolset at build time # It provides additional directives to the JATS makefiles # to simplify the directives. # # This directive does all its work at 'build' time # It uses makefile.pl directives # # Operation : This package adds the JATS directive ManifestFiles # This is used to create linux manifest files # # Syntax : ManifestFiles (, Options+); # See the function header for details # #......................................................................# require 5.6.1; use strict; use warnings; use ArrayHashUtils; # # Globals # These are exposed to all of JATS, so: # Limit the number (ideally none) # Be away of the potential name conflicts # #------------------------------------------------------------------------------- # Function : BEGIN # # Description : Setup directive hooks # Register a function to be called just before we start to # generate makefiles. This will be used to process the data # collected in all the ManifestFiles directives # # Inputs : None # # Returns : None # BEGIN { RegisterMakefileGenerate (\&ManifestFiles::Generate); } #------------------------------------------------------------------------------- # Function : ManifestFiles # # Description : Create a ManifestFiles entry # # Inputs : Platform - Active Platform Selector # Options - One or more options # --Name=Name - Mandatory # --Tier=Name - Mandatory # --Architecture=xxxx - Default actitecture is the current target # --Product=yyyy - Product Family # --Debian=BaseName[,--Prod,--Debug,--Arch=xxxx,--Product=yyyy] # --Apk=BaseName[,--Prod,--Debug,--Platform=pppp] # Default platform = ANDROID # Default type = Production # --MugPackage=Package[,--Subdir=subdir[,subdir]+] # --SrcFile=xxx # --SrcFileNoCopy=xxx # --Comment=xxx - Add comment to Manifst # --NoManifestVersion - Create unversioned Manifest File # --PkgSubdir=xxx - Specifies packaging subdir # --LocalInstall - Output manifest to the 'local' directory, not the output package # --ImportManifest=Package[,--Subdir=subdir,--ReWrite] - Import Manifest from package # --Md5 - Add MD5 checksums to the Manifest File # --Dmf - Generate the Device Management Framework # combined archive ZIP file. # --DmfVersion=xxxx - Generate the Device Management Framework # combined archive ZIP using a modified # version number; only for testing! # --LineLength=nnn - Limit line length. Default is 79 # --SubManifest - Create a sub-manifest (not installable). Manifest will have no version. # Gives a default name, tier and subdir if not explicitly provided. # --[No]Signatures - Control generation of .sig files # Default is to not generate .sigFiles # --ManifestInsertionPoint[,--All,--Name,--Name=xxxx,--Tier,--Tier=Name,--Current] # Create template insertion hook # --Template=Package[,--Subdir=subdir,--File=name] # Use Specified template # --NoWarn - Supress warnings about unused packages # Will supress sanity test. Useful when generating multiple # manifests. # --UseFullVersion - Use the full version (with the project extention) when building the manifest file # Will only modify the content of the manifest file # # Notes: On sig file generation # This tool will use an internal key file to generate signatures # UNLESS the tool is being run on a special build machine with a controlled signature file # the generated signatures will not work on-target. # # # Returns : Nothing # sub ManifestFiles { my( $platforms, @elements ) = @_; Debug2( "ManifestFiles($platforms, @elements)" ); return if ( ! ActivePlatform($platforms) ); my $name; my $tier; my @files; my %fileVersions; my $mug_dir; my $default_arch = $::ScmPlatform; my $default_prod = ''; my $imported_manifest = 0; my $include_md5 = 0; my $generate_dmf = 0; my $dmf_version = $::ScmBuildVersionFull; my $useDefaultLineWidth = 1; my $is_sub_manifest = 0; # # Collect user options # foreach ( @elements ) { if ( m~^--Name=(.+)~ ) { if ( $name ) { ReportError ("ManifestFiles:--Name option is only allowed once"); next; } $name = $1; } elsif ( m~^--Tier=(.+)~ ) { if ( $tier ) { ReportError ("ManifestFiles:--Tier option is only allowed once"); next; } $tier = $1; } elsif ( m~^--LocalInstall~ ) { $ManifestFiles::localInstall = 1; } elsif ( m~^--Comment=~ ) { my $cmt = $_; $cmt =~ s~.+=~~; $cmt =~ s~\s*\n\s*~\n~g; push @files, {'cmt' => $cmt }; } elsif ( m~^--Debian=(.+)~ ) { push @files, {'file' => ManifestFiles::_LocateDebianFile($1, $default_arch, $default_prod)}; } elsif ( m~^--Apk=(.+)~ ) { my $apkData = ManifestFiles::_LocateApkFile($1, $default_arch); my ($fname, $fversion) = split($;, $apkData); $fileVersions{$fname} = $fversion; push @files, {'file' => $fname }; $useDefaultLineWidth = 0; } elsif ( m~^--SrcFile=(.+)~ ) { push @files, {'file' => LocatePreReq($1)}; } elsif ( m~^--SrcFileNoCopy=(.+)~ ) { push @files, {'filenocopy' => $1}; } elsif ( m~^--MugPackage=(.+)~ ) { if ( $mug_dir ) { ReportError ("ManifestFiles:--MugPackage option is only allowed once"); next; } $mug_dir = ManifestFiles::_LocateMugDir($1); } elsif ( m/^--Arch(.*)=(.+)/ ) { $default_arch = $2; } elsif ( m/^--Product=(.+)/ ) { $default_prod = $1; } elsif ( m/^--NoManifestVersion/i ) { $ManifestFiles::Manifest_has_version = 0; } elsif ( m/^--PkgSubdir=(.+)/i ) { if ( $ManifestFiles::pkg_subdir ) { ReportError ("ManifestFiles:--PkgSubdir option is only allowed once"); next; } $ManifestFiles::pkg_subdir = $1; } elsif ( m/^--ImportManifest=(.+)/i ) { my $import_info = ManifestFiles::_ImportManifest($1, $tier, $name); #DebugDumpData("ImportInfo", $import_info ); push @files, {'manifest' => $import_info }; # # Fill in details unless already provided # $tier = $import_info->{'tier'} unless ( defined $tier ); $name = $import_info->{'name'} unless ( defined $name ); $imported_manifest = 1; } elsif ( m/^--ManifestInsertionPoint(.*)/i ) { my $insert_info = ManifestFiles::_InsertPoint($1, $tier, $name); #DebugDumpData("InsertPoint", $insert_info ); push @files, {'insertPoint' => $insert_info }; } elsif ( m/^--Template=(.+)/i ) { my $template_info = ManifestFiles::_Template($1, $tier, $name); #DebugDumpData("TemplateInfo", $template_info ); } elsif (m/^--Md5/i) { $include_md5 = 1; $useDefaultLineWidth = 0; } elsif (m/^--Dmf/i) { $generate_dmf = 1 } elsif ( m/^--DmfVersion=(.+)/ ) { $generate_dmf = 1; $dmf_version = $1; } elsif ( m/^--LineLength=(\d+)$/i ) { $ManifestFiles::ManifestLineWidth = $1; $useDefaultLineWidth = 0; } elsif (m/^--SubManifest/i) { $is_sub_manifest = 1; $ManifestFiles::Manifest_has_version = 0; $name = $::ScmPlatform unless $name; $ManifestFiles::pkg_subdir = $name unless $ManifestFiles::pkg_subdir; $tier = 0 unless defined($tier); } elsif (m/^--(No)?Signatures/i) { $ManifestFiles::noSigs = !! $1; } elsif (m/^--(No)?Warn/i) { $ManifestFiles::noWarn = !! $1; } elsif (m/^--UseFullVersion/i) { $ManifestFiles::useFullVersion = 1; } else { ReportError ("ManifestFiles: Unknown option or argument: $_"); } } # # Sanity test the user options # ReportError ("ManifestFiles: No name specified") unless $name; ReportError ("ManifestFiles: No tier specified") unless defined ($tier); ReportError ("ManifestFiles: Cannot mix --Debian/-Apk/--SrcFile with --MugPackage in one directive") if ( $mug_dir && (@files || $imported_manifest) ); ReportError ("ManifestFiles: Must specify files to add to Manifest") unless ( $mug_dir || @files || $imported_manifest); ErrorDoExit(); # # Set ManifestLineWidth # The default is largely historical - for MOS # unless (defined $ManifestFiles::ManifestLineWidth) { $ManifestFiles::ManifestLineWidth = $useDefaultLineWidth ? 79 : 0; } Verbose("ManifestLineWidth:$ManifestFiles::ManifestLineWidth"); # # Save information for processing at the end of the parsing phase # Data collected from ALL the ManifestFiles directives will be collected # and processed into one Manifest file # my %data; $data{tier} = $tier; $data{name} = $name; $data{files} = \@files; $data{fileVersions} = \%fileVersions; $data{mugdir} = $mug_dir; $data{pkgsubdir} = $ManifestFiles::pkg_subdir; $data{md5} = $include_md5; $data{dmf} = $generate_dmf; $data{arch} = $default_arch; $data{dmf_version} = $dmf_version; $data{is_sub_manifest} = $is_sub_manifest; #DebugDumpData("DirectiveData", \%data ); push @ManifestFiles::Manifests, \%data; } #================================================================================================== # End of externally exposed methods # The remainder is package-internal # package ManifestFiles; use strict; use warnings; use Digest::file qw(digest_file_hex); use Digest::file qw(digest_file_base64); use File::Spec::Functions qw(rel2abs); use File::Basename qw(dirname); use JatsError; use FileUtils; use ReadBuildConfig; use ArrayHashUtils; use JatsSystem; # # Variables # Config - must be 'our' # our @Manifests; # Manifest entries our $Manifest_has_version = 1; # Create Manifest_xxxx by default our $pkg_subdir; # Alternate packaging our $ManifestLineWidth; # Max length of lines our %package_dirs; # Package dirs discovered and used our $ManifestFiles; # Files in the Manifest our $noWarn = 0; # Control unused package warnings our $useFullVersion = 0; # Use the full version to build the manifest contents our $pkgBase; # Where the package is being sourced from our $noSigs = 1; # Control signature generation (Force noSigs. could use use undef) our $templateMode; # In templating mode our $localInstall; # Install or Package the manifest # # Internal # Used while generating a manifest # my $certName; # Signing Cert Name my $certFile; # Path to the certificate my $keyFile; # Path to the keyfile my $opensslProg; # Path to openssl program my $opensslConf; # Path to openssl config my $manifestName; # Name of the current manifest file BEGIN { my $path = rel2abs( __FILE__ ); $pkgBase = dirname( $path ); } #------------------------------------------------------------------------------- # Bring in the DMF build requirements. use lib $pkgBase; use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); use JSON; #------------------------------------------------------------------------------- # Function : _LocateDebianFile # # Description : Locate a debian file # Internal Function # # Scan packages for the Debian package specified # The user provides the base name of the package # A Debian Package name has several fields # These are: # 1) Base Name - Provided by the user # 2) Version - Default Version will be wildcarded if it hadn't been provided by user # 3) Architecture - Wildcarded. Uses bin/arch directory # # Expect to find Debian Packages in the bin/PLATFORM subdir # # Inputs : Debian base name, complete with suboptions # # Returns : Full path of the file # sub _LocateDebianFile { my ($arg, $arch, $product) = @_; Verbose("LocateDebianFile: Processing: $arg"); my @type = qw( P D ); my @debian_file_path; my @searchPath; my $version='*'; # # Extract sub-options # --Prod[uction] # --Debug # --Arch[itecture]=yyy # --Product=yyy # --Version=yy.yy.yyyyy default value is wildcard # my ($base_name, @opts) = split( ',', $arg ); foreach ( @opts ) { if ( m/^--Arch(.*)=(.+)/ ) { $arch=$2; } elsif ( m/^--Product=(.+)/ ) { $product=$1; } elsif ( m/^--Prod/ ) { @type = 'P'; } elsif ( m/^--Debug/ ) { @type = 'D'; } elsif ( m/^--Version=(.+)/ ) { $version=$1; } else { Warning ('--Debian: Unknown Option: ' . $_); } } # # Create a list of products # ie: PRODUCT_ARCH # my @products; push @products, $product . '_' . $arch if ( $product ); push @products, $arch; # # Scan all packages for the specified debian package # foreach my $package_dir ( getPackagePaths ('--All') ) { foreach my $type ( @type ) { foreach my $prd ( @products ) { foreach my $joiner ( qw(/ .) ) { my $dir = "$package_dir/bin$joiner$prd$type"; UniquePush(\@searchPath, $dir); next unless ( -d $dir ); my @files = glob ( "$dir/${base_name}_${version}*.deb" ); next unless ( @files ); push @debian_file_path, @files; $package_dirs{$package_dir}{used} = 1; } } } foreach my $type ( @type ) { foreach my $prd ( @products ) { my $dir = "$package_dir"; UniquePush(\@searchPath, $dir); next unless ( -d $dir ); if ( $version eq "*" ){ my @files = glob ( "$dir/${base_name}_${version}_${prd}_${type}.deb" ); if ( @files ){ push @debian_file_path, @files; $package_dirs{$package_dir}{used} = 1; } } else{ if( -e "$dir/${base_name}_${version}_${prd}_${type}.deb" ) { my $file = "$dir/${base_name}_${version}_${prd}_${type}.deb"; push(@debian_file_path, $file); $package_dirs{$package_dir}{used} = 1; } elsif ( -e "$dir/${base_name}_${version}_${prd}.deb") { my $file = "$dir/${base_name}_${version}_${prd}.deb"; push(@debian_file_path, $file); $package_dirs{$package_dir}{used} = 1; } } } } } # # Keep user informed # Report errors and provide useful information # if (IsVerbose(1) || IsDebug(1) || $#debian_file_path != 0) { Message ("Search for ($base_name). In search Path", @searchPath); } ReportError ("Required Debian package not found: $base_name") unless @debian_file_path; ReportError ("Multiple matching Debian Packages located: $base_name", @debian_file_path ) if ( $#debian_file_path > 0 ); return $debian_file_path[0]; } #------------------------------------------------------------------------------- # Function : _LocateApkFile # # Description : Locate a APK file # Internal Function # # Scan packages for the APK package specified # The user provides the base name of the package # APK ( Android Packages ) # Expect to have a '-release' or '-debug' suffix, except those provided via a # 3rd party SDK. # Expect to find APK Packages in the bin/PLATFORM(P/D) subdir # Expect to find -debug in the D directory # Expect to find -release' in the P directory # # Allow for: # Full path to .apk file # .apk in package root directory # # Inputs : Apk base name, complete with suboptions # # Returns : Full path of the file $; PackageVersion # apk packages do not appear to have version numbers in the file name # Retain package version number for later processing # sub _LocateApkFile { my ($arg, $arch) = @_; Verbose("LocateApkFile: Processing: $arg"); my @type = qw( P ); my @apk_file_path; my %type = ('P' => '-release', 'D' => '-debug' ); my @searchPath; # # Extract sub-options # --Prod[uction] # --Debug # --Architecture=yyy # my ($base_name, @opts) = split( ',', $arg ); foreach ( @opts ) { if ( m/^--Arch(.*)=(.+)/ ) { $arch=$2; } elsif ( m/^--Prod/ ) { @type = 'P'; } elsif ( m/^--Debug/ ) { @type = 'D'; } else { Warning ('--Apk: Unknown Option: ' . $_); } } # # Scan all packages for the specified APK package # Try: # Raw name - for apks from the SDK or 3rd parties # PLATFORM(P|D)/baseName-(release|debug) - Expected # baseName-(release|debug) - Repackaged badly # foreach my $pkgEntry ( getPackageList() ) { next if ($pkgEntry->getType() eq 'interface'); my $pkgVersion = $pkgEntry->getVersion(); my $pkgLocal = $pkgEntry->getBase(2); my $pkgRoot = $pkgEntry->getDir(); # # Helper function # Uses closure # Notes: Test the package in dpkg_archive so that we can retain the package-version # Use the version in the interface directory if BuildPkgArchive # $pkgLocal - Local base of the package. May in the interface directory # $pkgRoot - Directory in dpkg_achive. Will have version info # $subdir - subdir within the package # $fname - File to look for # # Returns: Nothing # Maintains: apk_file_path. Tupple: filename, PackageVersion # my $testOneFile = sub { my ( $subdir, $fname) = @_; my $testFile = "$pkgRoot/$subdir"; $testFile =~ s~//~/~g; $testFile =~ s~/$~~; UniquePush(\@searchPath, $testFile); return unless (-d $testFile); $testFile .= '/' . $fname; if (-f $testFile ) { if ($pkgLocal ne $pkgRoot) { my $testFile2 = "$pkgLocal/$subdir/$fname"; $testFile2 =~ s~//~/~g; if ( -f $testFile2 ) { $testFile = $testFile2; } } $testFile = join($;, $testFile, $pkgVersion); push @apk_file_path, $testFile; } }; # # Test for the specified file in the package root # $testOneFile->("", "${base_name}.apk"); # # Test for BIN/PLATFORM # foreach my $type ( @type ) { my $typeSuffix = $type{$type}; foreach my $joiner ( qw(/ .) ) { $testOneFile->("bin$joiner$arch$type","${base_name}${typeSuffix}.apk"); } } foreach my $type ( @type ) { my $typeSuffix = $type{$type}; $testOneFile->("","${base_name}${typeSuffix}.apk"); } $package_dirs{$pkgRoot}{used} = 1 if (@apk_file_path) ; } # # Keep user informed # Report errors and provide useful information # if (IsVerbose(1) || IsDebug(1) || $#apk_file_path != 0) { Message ("Search for ($base_name). In search Path", @searchPath); } ReportError ("Required APK package not found: $base_name") unless @apk_file_path; ReportError ("Multiple matching APK Packages located: $base_name", @apk_file_path ) if ( $#apk_file_path > 0 ); #DebugDumpData("apk_file_path", \@apk_file_path); return $apk_file_path[0]; } #------------------------------------------------------------------------------- # Function : _LocateMugDir # # Description : Locate the directory containing the mugfiles # Internal Function # # Inputs : Mufile package, with embedded options # # Returns : Full path # sub _LocateMugDir { my ($mug_package) = @_; # # Locate the mugfile subdir # my $package_name = $mug_package; my @dirs = 'mug'; my $mug_dir; # # Extract sub options # --Subdir=xxxx,yyyy,zzzz # if ( $package_name =~ m/(.*?),--Subdir=(.*)/ ) { $package_name = $1; @dirs = split( ',', $2 ); } my $package = ::GetPackageEntry( $package_name ); unless ( $package ) { ReportError ("ManifestFiles: Package not known to build: $package_name"); return undef; } foreach my $subdir ( @dirs ) { my $dir = "$package->{'ROOT'}/$subdir"; if ( -d $dir ) { Warning ("Multiple Mugfile directories located. Only the first will be used", "Ignoring: $subdir" )if ( $mug_dir ); $mug_dir = $dir; } } ReportError ("Mugfile directory not found in package: $package_name") unless $mug_dir; return $mug_dir; } #------------------------------------------------------------------------------- # Function : _ImportManifest # # Description : Import an existing manifest # # Inputs : Args - PackageName[,Subdir=name,--ReWrite] # tier - May be null # name - May be null # # Returns : A hash of data to be used later # sub _ImportManifest { my ($args, $tier, $name) = @_; my @file_contents; my @item_list; # # Locate the mugfile subdir # my $package_name = $args; my @dirs = 'mug'; my $pkg_dir; my $pkg_root; my $manifest; my $first_tier; my $first_name; my $rewrite; # # Extract sub options # --Subdir=xxxx,yyyy,zzzz # --ReWrite # if ( $package_name =~ m/(.*?)(,.*)/ ) { $package_name = $1; my @subargs = split(',--', $2); foreach ( @subargs) { next unless (length($_) > 0); if (m~^Subdir=(.*)~i){ @dirs = split( ',', $1 ); } elsif (m~^ReWrite~i) { $rewrite = 1; } else { ReportError("ManifestFiles: Unknown suboption to ImportManifest:" . $_); } } } my $package = ::GetPackageEntry( $package_name ); unless ( $package ) { ReportError ("ManifestFiles: Package not known to build: $package_name"); return undef; } if (defined ($rewrite) && ( !defined($tier) || !defined($name))) { ReportError ("ManifestFiles: ImportManifest. --ReWrite cannot be used unless tier and name are specified"); return undef; } foreach my $subdir ( @dirs ) { my $dir = "$package->{'ROOT'}/$subdir"; my $root = $package->{'ROOT'}; if ( -d $dir ) { Warning ("Multiple Package directories located. Only the first will be used", "Ignoring: $subdir" )if ( $pkg_dir ); $pkg_dir = $dir; $pkg_root = $root; } } unless ($pkg_dir) { ReportError ("Package directory not found in package: $package_name"); return undef; } # # Determine Manifest File name # foreach my $file ( glob ($pkg_dir . '/Manifest*' ) ) { next unless ( -f $file ); next if ( $file =~ m~\.sig~i ); Warning ("Multiple Manifest Files find. Only the first will be used", "Using: $manifest", "Ignoring: $file" ) if ( $manifest ); $manifest = $file; } unless ($manifest) { ReportError ("ImportManifest. No Manifest found: $package_name"); return undef; } # # # open (MF, '<', $manifest ) || Error ("Cannot open the Manifest file: $manifest", $!); while ( ) { # # Clean trailing whitespace ( line-feed and new lines ) # Comment out [Version] data # s~\s+$~~; s~(\s*\[Version])~#$1~; # # Parse lines and determine files # next unless ( $_ ); if (( m~\s*#~ ) || ( m~\s*\[~ )) { push @item_list, { 'comment' => $_ }; next; } my( $aname, $atier, $afile, @additionnal_info) = split(/\s*\,\s*/, $_); # print "---------- $_\n"; # print "T: $atier, N:$aname, F:$afile\n"; my $file = { 'file_name' => $afile , 'file_info' => \@additionnal_info }; push @item_list, $file; # # Rewrite the name and tier # if ($rewrite) { $_ = join(',', $name, $tier, $afile); $first_tier = $tier; $first_name = $name; } else { # # Capture first tier and name # $first_tier = $atier unless ( defined $first_tier ); $first_name = $aname unless ( defined $first_name ); } } continue { push @file_contents, $_; } close MF; # # Create a hash of data that describes the manifest that has # just been read in. # $package_dirs{$pkg_root}{used} = 1; $manifest =~ s~.*/~~; return { 'contents' => \@file_contents, 'items' => \@item_list, 'file_base' => $pkg_dir, 'manifest' => $manifest, 'pkg_dir' => $pkg_root, 'tier' => $first_tier, 'name' => $first_name, 'rewrite' => $rewrite, }; } #------------------------------------------------------------------------------- # Function : _InsertPoint # # Description : Create a Manifest Insertion Point # # Inputs : Args - [,--All,--Name,--Name=xxxx,--Tier,--Tier=Name, --Current] # tier - May be null # name - May be null # # Returns : A hash of data to be used later # sub _InsertPoint { my ($args, $tier, $name) = @_; my @item_list; ReportError("ManifestFiles: ManifestInsertionPoint. Name not yet specified") unless defined $name; ReportError("ManifestFiles: ManifestInsertionPoint. Tier not yet specified") unless defined $tier; my $argName = $name; my $argTier = $tier; # # Extract sub options # my @subargs = split(',--', $args); foreach ( @subargs) { next unless (length($_) > 0); if (m~^All$~i){ $argName = '*'; $argTier = '*'; } elsif (m~^Name(=(.*))?~i){ if ( $1 ){ $argName = $2; } else { $argName = $name; } } elsif (m~^Tier(=(.*))?~i){ if ( $1 ){ $argTier = $2; } else { $argTier = $tier; } } elsif (m~^Current$~i){ $argName = $name; $argTier = $tier; } else { ReportError("ManifestFiles: Unknown suboption to ManifestInsertionPoint:" . $_); } } return { 'tier' => $argTier, 'name' => $argName, }; } #------------------------------------------------------------------------------- # Function : _Template # # Description : Capture templating information # # Inputs : --Template=Package[,--Subdir=subdir,File=name] # tier - May be null # name - May be null # # Returns : A hash of data to be used later # sub _Template { my ($args, $tier, $name) = @_; my $subdir = 'mug'; my $manifest; # # Can only use the Template directive once # if ($templateMode) { ReportError ("ManifestFiles:--Template option is only allowed once"); return undef; } $templateMode = 1; # # Locate the mugfile subdir # my $package_name = $args; # # Extract sub options # --Subdir=xxxx # --File=name # if ( $package_name =~ m/(.*?)(,.*)/ ) { $package_name = $1; my @subargs = split(',--', $2); foreach ( @subargs) { next unless (length($_) > 0); if (m~^Subdir=(.*)~i){ $subdir = $1; } elsif (m~^File=(.*)~i) { $manifest = $1; } else { ReportError("ManifestFiles: Unknown suboption to Template:" . $_); } } } my $package = ::GetPackageEntry( $package_name ); unless ( $package ) { ReportError ("ManifestFiles: Package not known to build: $package_name"); return undef; } my $pkg_root = $package->{'ROOT'}; my $pkgDir = CatPaths( $pkg_root, $subdir); unless (-d $pkgDir) { ReportError ("ManifestFiles: Template subdir not found in $package_name: $subdir"); return undef; } # # Determine Manifest File name, unless specified by user # unless ($manifest) { foreach my $file ( glob ($pkgDir . '/Manifest*' ) ) { next unless ( -f $file ); next if ( $file =~ m~\.sig~i ); if ( $manifest ) { Warning ("Multiple Manifest Files find. Only the first will be used", "Using: $manifest", "Ignoring: $file" ) ; next; } $manifest = $file; } unless ($manifest) { ReportError ("ImportManifest. No Manifest template found: $package_name"); return undef; } } else { $manifest = CatPaths($pkgDir, $manifest); } unless (-f $manifest) { ReportError ("ImportManifest. No Manifest template found: $package_name, $subdir, $manifest"); return undef; } # # Return paramters for later processing # $package_dirs{$pkg_root}{used} = 1; $templateMode = { 'manifest' => $manifest, 'pkg_dir' => $pkg_root, 'subdir' => $subdir, 'manifest_dir' => $pkgDir, 'package' => $package_name, }; return $templateMode; } #------------------------------------------------------------------------------- # Function : Generate # # Description : Internal Function # Process all the collected data and create directives # for the creation of the manifest # # This function will be called, just before the Makefile # is created. The function will: # 1) Create the Manifest File # 2) Package the Manifest File # 3) Package the manifest file contents # # using (mostly) normal makefile.pl directives. # # Inputs : None # # Returns : Nothing # sub Generate { Debug ("Generate"); Message ("Generating Manifest File"); # # Need at least one Manifest Entry # #DebugDumpData ( "Manifests", \@Manifests ); return unless ( @Manifests ); # # Init the signing subsystem # GenSigInit(); # # Determine the target packaging directory # Default is .../mug # $pkg_subdir = 'mug'; if ( exists $Manifests[0]->{pkgsubdir} && defined $Manifests[0]->{pkgsubdir} ) { my $subdir = $Manifests[0]->{pkgsubdir}; $pkg_subdir .= '/' . $subdir; $pkg_subdir =~ s~^mug/mug~mug~; } # # Create the Manifest File as we process the lists # Place this in the 'lib' directory: # - So that it will be deleted on clobber # - So that it can be placed in a target-specific subdir # - So that we can have one per makefile.pl # Error ("ManifestFiles: Needs local directory specified in build.pl") unless ( $::ScmLocal ); my $manifest_dir = "$::ScmPlatform.LIB"; System( "$::GBE_BIN/mkdir -p $manifest_dir" ); $manifestName = $manifest_dir . '/Manifest'; $manifestName .= '_' . $::ScmBuildVersion if ( $Manifest_has_version ); PackageFile ('*', $manifestName, '--Subdir=' . $pkg_subdir, '--Strip' ); ::ToolsetGenerate( $manifestName ); Verbose ("Generate: File: $manifestName"); open (MF, '>', $manifestName ) || Error ("Cannot create the Manifest file: $manifestName"); binmode (MF); my $version; if ($useFullVersion == 1) { $version = $::ScmBuildVersionFull; } else { $version = $::ScmBuildVersion; } if ($Manifests[0]->{is_sub_manifest} == 1) { print_mf ("# Package $::ScmBuildPackage $version built: $::CurrentTime"); } else { print_mf ("# PackageName: $::ScmBuildPackage"); print_mf ("# PackageVersion: $version"); print_mf ("# BuildDate: $::CurrentTime"); print_mf ("#"); print_mf ("[Version],$version"); print_mf ("#"); } # # Insert the certificate used to verify the files # Currently we only handle one certificate # Certificate name must end in .crt # In production the .crt file MUST be signed by the VixManifestRoot-CA # The assumtion is that the public key for VixManifestRoot-CA is on the device # unless (($Manifests[0]->{is_sub_manifest} == 1) || $noSigs) { print_mf (""); print_mf ("#"); print_mf ("# Signing Certificate"); print_mf ("[Certificate],$certName"); PackageFile ('*', $certFile, '--Subdir=' . $pkg_subdir, '--Strip' ); } # # If in templating mode, then process the base template # if ($templateMode) { templateProcessing(); } else { # # Process each tier in the order presented in the source file # foreach my $entry ( @Manifests ) { DmfGenerate($entry) if ( $entry->{dmf} ); processManifestEntry($entry); } } # # Complete the creation of the Manifest File # print_mf ("# end of $::ScmBuildPackage"); close MF; ErrorDoExit(); # # Post process files from the manifest # May include signature generation GenSignatures(); # # Sanity test of packages that did not provide a debian file # Just a hint that something may have been missed # unless ($noWarn) { my @not_used_packages; foreach my $package_dir ( getPackagePaths ('--All') ) { next if ( $package_dir =~ m~/manifest-tool/~ ); unless ( exists $package_dirs{$package_dir}{used} ) { push @not_used_packages, $package_dir; } } if ( @not_used_packages ) { Warning ("Packages that did not contribute packages to the manifest:", @not_used_packages ); } } } #------------------------------------------------------------------------------- # Function : processManifestEntry # # Description : Process a single Manifest entry # # Inputs : Globals # # Returns : # sub processManifestEntry { my ($entry) = @_; #DebugDumpData ( "Manifest Entry", $entry ); my $tier = $entry->{tier}; my $name = $entry->{name}; my $include_md5 = $entry->{md5}; my $last_was_comment = 0; my $last_was_insert = 0; # # Flag that this entry has been processed # $entry->{wasProcessed}++; # # Insert all the files that have been specified # The user specified order is preserved # # Entries may be either a file or a comment # Comments: Merge multiple comments and create blocks # # my @files = @{ $entry->{files} }; foreach my $fentry ( @files ) { if ( my $cmt = $fentry->{'cmt'} ) { print_mf ('') unless ( $last_was_comment ) ; print_mf ( map (('# ' . $_) , split ("\n", $cmt) )); $last_was_comment = 1; next; } print_mf ('#') if ( $last_was_comment); $last_was_comment = 0; if ( my $ip = $fentry->{'insertPoint'} ) { # # Create an insertion point # Looks like a comment for the purposes of backward compatability # print_mf ('') unless $last_was_insert; print_mf ("##INSERT POINT [Name:$ip->{name}] [Tier:$ip->{tier}]"); $last_was_insert = 1; next; } print_mf ('#') if ( $last_was_insert); $last_was_insert = 0; if ( my $file = $fentry->{'file'} ) { my $base_file = StripDir( $file ); my @items = ($name, $tier, $base_file); if ($include_md5) { my $md5 = digest_file_hex($file, 'MD5'); push @items, "MD5=$md5" ; } if (exists $entry->{fileVersions} && exists $entry->{fileVersions}{$file} ) { push @items, "VERSION=" . $entry->{fileVersions}{$file}; } print_mf (join (',', @items)); PackageManifestFile ($name, $tier, $file ); } if ( my $file = $fentry->{'filenocopy'} ) { print_mf ("$name,$tier,$file"); } if ( my $emf = $fentry->{'manifest'} ) { # # Insert the entire manifest # Items are: # contents # items: # file_name + arrays of file_info # or comment line to copy # file_base # manifest # #DebugDumpData ( "Embedded Manifest Entry", $emf ); if ($emf->{'rewrite'}) { foreach my $item ( @{$emf->{'items'}}) { if (defined($item->{'file_name'})) { my @items = ($name, $tier, $item->{'file_name'}); my $md5_added = 0; foreach my $info (@{$item->{'file_info'}}) { push @items, $info; $md5_added = 1 if ($info =~ m~^MD5=~i); } if ($include_md5 && $md5_added == 0) { # add md5 if requested and not already added from submanifest my $md5 = digest_file_hex($emf->{'file_base'} . '/' . $item->{'file_name'}, 'MD5'); push @items, "MD5=$md5"; } print_mf (join (',', @items)); PackageManifestFile ( $name, $tier, $emf->{'file_base'} . '/' . $item->{'file_name'} ); } elsif (defined($item->{'comment'})) { print_mf($item->{'comment'}); } } print_mf('#'); } else { print_mf ($_) foreach ( @{$emf->{'contents'}} ); foreach my $item ( @{$emf->{'items'}}) { if (defined($item->{'file_name'})) { PackageManifestFile ( $name, $tier, $emf->{'file_base'} . '/' . $item->{'file_name'} ); } } print_mf('#'); } } } # # Expand out the entire MUG directory # All .mug files in the MUG directory will be added to the manifest # The assumption is that the MUG directory has been created by # something that knows what its doing # if ( my $mugdir = $entry->{mugdir} ) { foreach my $file ( glob ($mugdir . '/*.mug' ) ) { next unless ( -f $file ); my $base_file = StripDir($file); my @items = ($name, $tier, $base_file); if ($include_md5) { my $md5 = digest_file_hex($file, 'MD5'); push @items, "MD5=$md5" ; } print_mf (join (',', @items)); PackageManifestFile ($name, $tier, $file ); } } } #------------------------------------------------------------------------------- # Function : templateProcessing # # Description : The manifest is created from a povided manifest with MUST # have one or more insertion points # # Purpose: Pulse can provide a template and SI teams can # insert there own packages # # Inputs : Globals # # Returns : # sub templateProcessing { my $insertFound; my $last_was_comment; # # Open the template manifest and process each line # print_mf( '# Start of Template from: ' . $templateMode->{'package'} ); my $tfile; my $line; open( $tfile, '<', $templateMode->{'manifest'} ) || Error ("Cound not open template manifest: $?", "File: $templateMode->{'manifest'}"); while ( <$tfile> ) { $line = $_; $line =~ s~\s+$~~; next unless ($line); # # Detect insertion point # if ($line =~ m~^##INSERT POINT~) { $line =~ m~\[Name:(.+)\].*\[Tier:(.+)\]~i; my $iname = $1; my $itier = $2; print_mf ("# Start of Template Insert: $iname, $itier"); templateInsert($iname,$itier); print_mf ("# End of Template Insert: $iname, $itier"); $insertFound = 1; next; } elsif( $line =~ m~^#$~) { # Ignore 'empty' comment lines # They will be regenerated next; } elsif( $line =~ m~^#~) { # Non empty comments are transferred # Prepend another '#' marker as some moron decided that they could parse comments for data # A double # should defeat that # print_mf ('') unless ( $last_was_comment ) ; print_mf( '#' . $line); $last_was_comment = 1; next; } elsif ($line =~ m~^\[~ ) { # Metadata from the template # Insert it as a comment print_mf( '#' . $line); next; } print_mf ('#') if ( $last_was_comment); $last_was_comment = 0; my( $aname, $atier, $afile, @additionnal_info) = split(/\s*\,\s*/, $line); my $test = CatPaths($templateMode->{manifest_dir}, $afile); ReportError("Template processing: File not found with template: $afile", $test) unless -f $test; print_mf( $line); PackageManifestFile ( $aname, $atier, $test ); } close $tfile; print_mf( '# End of Template from: ' . $templateMode->{'package'} ); # # Sanity Checks # Was an insertion point found # Were all manifest bits used # ReportError ("No insertion points in template") unless $insertFound; my @notUsed; my @overUsed; foreach my $entry ( @Manifests ) { if ( !exists $entry->{wasProcessed}) { push @notUsed, "Name: $entry->{name}, Tier: $entry->{tier}"; } elsif ($entry->{wasProcessed} > 1) { push @overUsed, "Name: $entry->{name}, Tier: $entry->{tier}"; } } Warning ("Template did not used data from: ", @notUsed) if @notUsed; Warning ("Template used data multiple times from: ", @overUsed) if @overUsed; ErrorDoExit(); } #------------------------------------------------------------------------------- # Function : templateInsert # # Description : Helper routine to insert required bits into the template # Need to scan the manifest entries and determine which need # to be inserted at this point in the template. # # Inputs : $iname - Insert name # $itier - Insert Tier # # Returns : Nothing # sub templateInsert { my ($iname, $itier) = @_; foreach my $entry ( @Manifests ) { next unless ((($iname eq '*') || ($iname eq $entry->{name}) ) && (($itier eq '*') || ($itier eq $entry->{tier})) ); processManifestEntry($entry); } } #------------------------------------------------------------------------------- # Function : print_mf # # Description : Internal Function # Print one line to the Manifest File # Checks the length of the line being created # # Inputs : $line # # Returns : # sub print_mf { foreach ( @_ ) { my $ll = length ($_); ReportError ( "Manifest line too long: $ll. Max is $ManifestLineWidth.", "Line: $_" ) if ( $ManifestLineWidth && $ll > $ManifestLineWidth); print MF $_ . "\n"; } } #------------------------------------------------------------------------------- # Function : PackageManifestFile # # Description : Process and package a manifest file # Retain a list of files in the manifest to allow postprocessing # such as signature generation # # # Inputs : $name - Target platform name # $tier - Target tier # $srcFile - Full path to the source file # # Returns : Nothing # sub PackageManifestFile { my ($name, $tier, $srcFile ) = @_; push @{$ManifestFiles}, ({srcFile => $srcFile, name =>$name, tier => $tier}); PackageFile ('*', $srcFile, '--Subdir=' . $pkg_subdir, '--Strip' ); } #------------------------------------------------------------------------------- # Function : GenSigInit # # Description : Initialise the Siganture Generation process # Populate some globals for use in creating the manifest # Bail if there are errors # # Inputs : # # Returns : # sub GenSigInit { Debug("GenSigInit:", $noSigs); return if $noSigs; my @warnings; # # Locate and setup openssl # Should be provided by a package # set OPENSSL_CONF=...../openssl.cfg # $opensslProg = ::ToolExtensionProgram( 'openssl', '', '.exe'); # Generate a dummy openssl.cnf - to keep the uitility happy (quiet) $opensslConf = FullPath(CatPaths( $::ScmRoot, $::ScmInterface, 'openssl.cfg')); TouchFile($opensslConf); ::ToolsetGenerate($opensslConf); $ENV{OPENSSL_CONF}= $opensslConf; # # Figure out default operation if openssl cannot be found # If --signature - then report error # If not specified, then default is -NoSig # unless ($opensslProg) { if (defined $noSigs) { ReportError ("The openssl utility is not available. Must be provided by a package") unless $opensslProg; } else { Verbose("openssl not found in a package. Default to no signature"); $noSigs = 1; return; } } # # Signatures 'should' be generated for a target paltform of 'MANSIG' # In the automated build system this will be a controlled machine # Warn if attemting to create signatures for a different target # push @warnings, "# Package build for platform other than MANSIG - will generate test signatures" if ($::ScmPlatform ne 'MANSIG'); # # Locate the signature file # Use an internal key that unless being run on a controlled build machine # The controlled build machine will have # GBE_MANIFEST_KEY set to a path that contains the keyfile # Yes, a user can fudge this, but not with the real file # # Internal cert created with: # openssl req -nodes -x509 -sha256 -newkey rsa:4096 -keyout VixPulseManifestTest.key -out VixPulseManifestTest.crt -days 10000 -subj "/C=AU/CN=Vix.Pulse.Manifest.Test" # $keyFile = CatPaths( $pkgBase, 'VixPulseManifestTest.key'); my $localKey = 1; if (exists $ENV{GBE_MANIFEST_KEY} ) { $keyFile = $ENV{GBE_MANIFEST_KEY}; $localKey = 0; } $certFile = $keyFile; $certFile =~ s~\.key$~.crt~; $certName = $certFile; $certName =~ s~.*[\\/]~~; ReportError ("Manifest signing key not found", $keyFile) unless -f $keyFile; ReportError ("Manifest signing key not found", $certFile) unless -f $certFile; push @warnings, "# Using uncontrolled test key to sign the manifest" if ($localKey); Warning("#############################################################", "#", @warnings, "#", "#############################################################") if (@warnings); ErrorDoExit(); Message("OpenSsl Prog: $opensslProg"); Message("OpenSsl Conf: $opensslConf"); Message("Signing with: $keyFile"); } #------------------------------------------------------------------------------- # Function : GenSignatures # # Description : Generate signatures on all packages # # Inputs : Used the array of files in $ManifestFiles # # Returns : Nothing # sub GenSignatures { Debug("GenSignatures:", $noSigs); return if $noSigs; my %sigManifest; foreach my $entry ( @{$ManifestFiles}) { Verbose("PostProcess: $entry->{srcFile}"); # # Generate the name of the signature file # Generate Packaging directives for the signature # my $sigFileName = CatPaths( "$::ScmPlatform.LIB", StripDir($entry->{srcFile}) . '.sig' ); PackageFile ('*', $sigFileName, '--Subdir=' . $pkg_subdir, '--Strip' ); ::ToolsetGenerate($sigFileName); # # Generate the name/tier for the manifest signature # ie: Manifest__.sig # Generate the name for the manifest signature # ie: Manifest_.sig # Will generate signature of signatures # my $sigManifest = join('_', $entry->{name}, $entry->{tier}); push @{$sigManifest{$sigManifest}}, $sigFileName; push @{$sigManifest{$entry->{name}}}, $sigFileName; # Generate the signature over the file genSigForFile($entry->{srcFile}, $sigFileName); } # # Create a file of signatures # Create one for each platform/tier # foreach my $nameTier ( keys %sigManifest) { Verbose("PostProcessSig: $nameTier"); my $sigSigTemp = $manifestName . '_' . $nameTier . '.sig'; my $sigSigTempCat = $sigSigTemp . '.content'; ::ToolsetGenerate($sigSigTemp, $sigSigTempCat); PackageFile ('*', $sigSigTemp, '--Subdir=' . $pkg_subdir, '--Strip' ); # # Concatenate the contents of the required .sig files # Treat them as binary files. # open (my $ofh, '>', $sigSigTempCat ) || Error( "Cannot create file: $sigSigTempCat", "Reason: $!" ); binmode ($ofh); foreach my $entry (@{$sigManifest{$nameTier}}) { Verbose("PostProcessSig: $entry"); open( my $file, '<', $entry ) || Error("Cannot open '$entry'. $!"); binmode ($file); my @lines = <$file>; close $file; foreach my $entry ( @lines ) { print $ofh $entry; } } close $ofh; # # Generate a signature over the file # genSigForFile($sigSigTempCat, $sigSigTemp); #unlink $sigSigTempCat; } unlink $opensslConf; } #------------------------------------------------------------------------------- # Function : genSigForFile # # Description : Generate a .sig file for a named file (internal) # Will be smart and only 'touch' the output file if the input has chnaged # It will maintain a fingerprint file to do this. # # Generate package signature # The required custom(vix) 'sig' file has the format: # line-1 : name of the certificate to decode # Assume its the keyname with .key -> .crt - with no path elements # line-2 : Text representation of the signature (base64) # The openssl dgst command generates a binary output by default # This is then converted into base64 with -A option # # Inputs : $srcFile - Source file to process # $tgtFile - Output file name # # Returns : # sub genSigForFile { my ($srcFile, $tgtFile) = @_; my $tmp1 = CatPaths( "$::ScmPlatform.LIB", 'temp1'); my $tmp2 = CatPaths( "$::ScmPlatform.LIB", 'temp2'); ::ToolsetGenerate($tmp1, $tmp2); Verbose("Signing: " . StripDir($srcFile)); System( '--Exit', '--NoShell', $opensslProg, 'dgst', '-sha256', '-sign', $keyFile, '-out', $tmp1, $srcFile ); System( '--Exit', '--NoShell', $opensslProg, 'base64', '-A', '-in', $tmp1, '-out', $tmp2 ); my $base64 = TagFileRead($tmp2); # # In a development environment we don't want to create the signature file every time we run. Its noisy. # Solution: Only generate a signature file if: # It doesn't exist # A copy of the signed digest doesn't exist (.fpp) # The signed digest has changed # my $fppFileName = CatPaths( "$::ScmPlatform.LIB", StripDir($srcFile) . '.fpp' ); ::ToolsetGenerate($fppFileName); my $genSig = 0; $genSig = 1 unless ( -e $tgtFile && -e $fppFileName); unless ($genSig) { my $ffp = TagFileRead($fppFileName); $genSig = ($ffp ne $base64); } if ($genSig) { FileCreate($tgtFile, $certName, $base64); rename $tmp2, $fppFileName ; } unlink $tmp1; unlink $tmp2; } #------------------------------------------------------------------------------- # Function : DmfGenerate # # Description : Import an existing manifest # # Inputs : entry - The manifest that is being processed. # # Returns : Nothing # sub DmfGenerate { my ($entry) = @_; # Get the generation time. my $gen_time = time(); my $work_dir = "$::ScmPlatform.BIN/"; System( "$::GBE_BIN/mkdir -p $work_dir" ); my $name = $entry->{name}; my $version = $entry->{dmf_version}; # Configure base manifest information. my %manifest; $manifest{'mugsetId'} = $name . '_' . $version; $manifest{'name'} = $name; $manifest{'version'} = $version; $manifest{'packageName'} = $::ScmBuildPackage; $manifest{'packageVersion'} = $::ScmBuildVersionFull; $manifest{'datetime'} = localtime($gen_time); $gen_time *= 1000; # Make to milliseconds $manifest{'timestamp'} = $gen_time; $manifest{'tier'} = $entry->{tier}; # Process each file. my @files = @{ $entry->{files} }; my $zip = Archive::Zip->new(); my $i = 0; foreach my $fentry ( @files ) { if ( my $file = $fentry->{'file'} ) { my $order = $i + 1; my $base_file = StripDir( $file ); my $publish_file = $name . '_' . $version . '_' . $order . '.aup'; my $aup_file = $work_dir . $publish_file; GenerateCesFile($file, $aup_file, 0x3, $gen_time, $publish_file); my $file_member = $zip->addFile( $aup_file, $publish_file ); $manifest{'tasks'}[$i]{'order'} = 1 * $order; $manifest{'tasks'}[$i]{'filename'} = $base_file; $manifest{'tasks'}[$i]{'download'} = $publish_file; $manifest{'tasks'}[$i]{'sha256'} = digest_file_base64($file, 'SHA-256'); $manifest{'tasks'}[$i]{'size'} = -s $file; if ($base_file =~ /\.sh$/) { $manifest{'tasks'}[$i]{'action'} = 'exec-shell'; } elsif ($base_file =~ /\.deb$/) { $manifest{'tasks'}[$i]{'action'} = 'dpkg-install'; my ($pkg_name, $pkg_version, $pkg_arch) = ($base_file =~ /([^_]*)_([^_]*)_(.*)/); $manifest{'tasks'}[$i]{'arch'} = $pkg_arch; $manifest{'tasks'}[$i]{'name'} = $pkg_name; $manifest{'tasks'}[$i]{'version'} = $pkg_version; } else { ReportError ("Manifest entry $base_file does not have a supported DMF install action"); } $i = $i + 1; } } # Encode and commit the JSON. my $json_encoder = JSON->new->allow_nonref; my $json = $json_encoder->pretty->encode( \%manifest ); my $manifest_filename = $name . '_' . $version; my $aum_filename = $manifest_filename . '_0.aum'; my $manifest_file = $work_dir . $manifest_filename . '.json'; my $aum_file = $work_dir . $aum_filename; # Save our manifest. open (J, '>', $manifest_file ) || Error ("Cannot create the DMF Manifest file"); binmode (J); print J $json; close J; GenerateCesFile($manifest_file, $aum_file, 0x2, $gen_time, $aum_filename); $zip->addFile($aum_file, $aum_filename); my $zip_filename = $work_dir . $name . '_' . $version . '.zip'; if ( $zip->writeToFileNamed($zip_filename) != AZ_OK ) { ReportError("DMF ZIP file creation failed"); } PackageFile('*', $zip_filename, '--Strip'); PackageFile('*', $manifest_file, '--Strip'); } #------------------------------------------------------------------------------- # Function : PackageFile # # Description : Package or Install a manifest file # # Inputs : Same as PackageFile # # Returns : # sub PackageFile { if ($localInstall) { ::InstallHdr(@_); } else { ::PackageFile(@_); } } #------------------------------------------------------------------------------- # Function : GenerateCesFile # # Description : Import an existing manifest # # Inputs : src_file - The input file. # dst_file - The output CES file. # content_type - The content type to report. # gen_time - The generation time for the file. # filename - The filename to embed in the CES file. # # # Returns : Nothing # sub GenerateCesFile { my ($src_file, $dst_file, $content_type, $gen_time, $filename) = @_; open (INF, '<', $src_file ) || Error ("Cannot open file $src_file for reading"); binmode (INF); open (OUTF, '>', $dst_file ) || Error ("Cannot open file $dst_file for writing"); binmode (OUTF); my $signing_key_name = ""; my $signature_size = 0; my $format_version = 0xCE500000; my $compression_method = 0; my $encryption_method = 0; my $kek_name = ""; my $encryption_key_size = 0; my $filename_size = length($filename); print OUTF pack("Z32", $signing_key_name); print OUTF pack("n", $signature_size); print OUTF pack("N", $format_version); print OUTF pack("N", $content_type); print OUTF pack("Q>", $gen_time); print OUTF pack("N", $compression_method); print OUTF pack("N", $encryption_method); print OUTF pack("Z32", $kek_name); print OUTF pack("n", $encryption_key_size); print OUTF pack("n", $filename_size); # Encryption key HERE print OUTF pack("A$filename_size", $filename); my $buf; while (read(INF,$buf,65536)) { print OUTF $buf; } print OUTF $buf; close INF; # Signature HERE # Finish with file. close OUTF; } 1;