2222 simple_file_manifest:: create_symlink,
2323 std:: {
2424 borrow:: Cow ,
25- collections:: BTreeMap ,
25+ collections:: { BTreeMap , BTreeSet } ,
2626 io:: Write ,
2727 path:: { Path , PathBuf } ,
2828 } ,
@@ -33,12 +33,19 @@ use {
3333/// This does not use the CodeResources rules for a bundle. Rather, it
3434/// blindly copies all files in the bundle. This means that excluded files
3535/// can be copied.
36- pub fn copy_bundle ( bundle : & DirectoryBundle , dest_dir : & Path ) -> Result < ( ) , AppleCodesignError > {
36+ ///
37+ /// Returns the set of bundle-relative paths that are installed.
38+ pub fn copy_bundle (
39+ bundle : & DirectoryBundle ,
40+ dest_dir : & Path ,
41+ ) -> Result < BTreeSet < PathBuf > , AppleCodesignError > {
3742 let settings = SigningSettings :: default ( ) ;
3843
39- let context = BundleSigningContext {
44+ let mut context = BundleSigningContext {
4045 dest_dir : dest_dir. to_path_buf ( ) ,
4146 settings : & settings,
47+ previously_installed_paths : Default :: default ( ) ,
48+ installed_paths : Default :: default ( ) ,
4249 } ;
4350
4451 for file in bundle
@@ -48,7 +55,7 @@ pub fn copy_bundle(bundle: &DirectoryBundle, dest_dir: &Path) -> Result<(), Appl
4855 context. install_file ( file. absolute_path ( ) , file. relative_path ( ) ) ?;
4956 }
5057
51- Ok ( ( ) )
58+ Ok ( context . installed_paths )
5259}
5360
5461/// A primitive for signing an Apple bundle.
@@ -186,22 +193,63 @@ impl BundleSigner {
186193 }
187194 }
188195
196+ // We keep track of root relative input paths that have been installed so we can
197+ // skip installing in case we encounter the path again when signing a parent
198+ // bundle.
199+ //
200+ // If we fail to do this, during non-shallow signing operations we may descend
201+ // into a child bundle that is outside a directory with the "nested" flag set.
202+ // Files in non-"nested" directories need to be sealed in CodeResources files
203+ // as regular files, not bundles. So we need to walk into the child bundle in
204+ // this scenario. But during the walk we want to prevent already installed files
205+ // from being processed again.
206+ //
207+ // In the case of Mach-O binaries in the above non-"nested" directory scenario,
208+ // excluding already signed files prevents the Mach-O from being signed again.
209+ // Signing the Mach-O twice could invalidate the bundle's signature and/or result
210+ // in incorrect signing settings since a bundle's main binary wouldn't be
211+ // recognized as such since we're outside the context of that bundle.
212+ //
213+ // In all cases, we prevent redundant work installing files if a file is seen
214+ // twice.
215+ let mut installed_rel_paths = BTreeSet :: < PathBuf > :: new ( ) ;
216+
189217 for ( rel, nested) in bundles {
218+ let rel_path = PathBuf :: from ( rel) ;
219+
190220 let nested_dest_dir = dest_dir. join ( rel) ;
191221 warn ! ( "entering nested bundle {}" , rel, ) ;
192222
193- if settings. shallow ( ) {
223+ let bundle_installed_rel_paths = if settings. shallow ( ) {
194224 warn ! ( "shallow signing enabled; bundle will be copied instead of signed" ) ;
195- copy_bundle ( & nested. bundle , & nested_dest_dir) ?;
225+ copy_bundle ( & nested. bundle , & nested_dest_dir) ?
196226 } else if settings. path_exclusion_pattern_matches ( rel) {
197227 // If we excluded this bundle from signing, just copy all the files.
198228 warn ! ( "bundle is in exclusion list; it will be copied instead of signed" ) ;
199- copy_bundle ( & nested. bundle , & nested_dest_dir) ?;
229+ copy_bundle ( & nested. bundle , & nested_dest_dir) ?
200230 } else {
201- nested. write_signed_bundle (
231+ let bundle_installed = installed_rel_paths
232+ . iter ( )
233+ . filter_map ( |p| {
234+ if let Ok ( p) = p. strip_prefix ( & rel_path) {
235+ Some ( p. to_path_buf ( ) )
236+ } else {
237+ None
238+ }
239+ } )
240+ . collect :: < BTreeSet < _ > > ( ) ;
241+
242+ let info = nested. write_signed_bundle (
202243 nested_dest_dir,
203244 & settings. as_nested_bundle_settings ( rel) ,
245+ bundle_installed,
204246 ) ?;
247+
248+ info. installed_rel_paths
249+ } ;
250+
251+ for p in bundle_installed_rel_paths {
252+ installed_rel_paths. insert ( rel_path. join ( p) . to_path_buf ( ) ) ;
205253 }
206254
207255 warn ! ( "leaving nested bundle {}" , rel) ;
@@ -212,7 +260,9 @@ impl BundleSigner {
212260 . get ( & None )
213261 . expect ( "main bundle should have a key" ) ;
214262
215- main. write_signed_bundle ( dest_dir, settings)
263+ Ok ( main
264+ . write_signed_bundle ( dest_dir, settings, installed_rel_paths) ?
265+ . bundle )
216266 }
217267}
218268
@@ -343,16 +393,22 @@ pub struct BundleSigningContext<'a, 'key> {
343393 pub settings : & ' a SigningSettings < ' key > ,
344394 /// Where the bundle is getting installed to.
345395 pub dest_dir : PathBuf ,
396+ /// Bundle relative paths of files that have already been installed.
397+ ///
398+ /// The already-present destination file content should be used for sealing.
399+ pub previously_installed_paths : BTreeSet < PathBuf > ,
400+ /// Bundle relative paths of files that are installed by this signing operation.
401+ pub installed_paths : BTreeSet < PathBuf > ,
346402}
347403
348404impl < ' a , ' key > BundleSigningContext < ' a , ' key > {
349405 /// Install a file (regular or symlink) in the destination directory.
350406 pub fn install_file (
351- & self ,
407+ & mut self ,
352408 source_path : & Path ,
353- dest_rel_path : & Path ,
409+ bundle_rel_path : & Path ,
354410 ) -> Result < PathBuf , AppleCodesignError > {
355- let dest_path = self . dest_dir . join ( dest_rel_path ) ;
411+ let dest_path = self . dest_dir . join ( bundle_rel_path ) ;
356412
357413 if source_path != dest_path {
358414 // Remove an existing file before installing the replacement. In
@@ -394,6 +450,11 @@ impl<'a, 'key> BundleSigningContext<'a, 'key> {
394450 }
395451 }
396452
453+ // Always record the installation even if we no-op. The intent of the
454+ // annotation is to mark files that are already present in the destination
455+ // bundle.
456+ self . installed_paths . insert ( bundle_rel_path. to_path_buf ( ) ) ;
457+
397458 Ok ( dest_path)
398459 }
399460
@@ -402,24 +463,24 @@ impl<'a, 'key> BundleSigningContext<'a, 'key> {
402463 /// Returns Mach-O metadata which can be recorded in a CodeResources file.
403464
404465 pub fn sign_and_install_macho (
405- & self ,
466+ & mut self ,
406467 source_path : & Path ,
407- dest_rel_path : & Path ,
468+ bundle_rel_path : & Path ,
408469 ) -> Result < ( PathBuf , SignedMachOInfo ) , AppleCodesignError > {
409- warn ! ( "signing Mach-O file {}" , dest_rel_path . display( ) ) ;
470+ warn ! ( "signing Mach-O file {}" , bundle_rel_path . display( ) ) ;
410471
411472 let macho_data = std:: fs:: read ( source_path) ?;
412473 let signer = MachOSigner :: new ( & macho_data) ?;
413474
414475 let mut settings = self
415476 . settings
416- . as_bundle_macho_settings ( dest_rel_path . to_string_lossy ( ) . as_ref ( ) ) ;
477+ . as_bundle_macho_settings ( bundle_rel_path . to_string_lossy ( ) . as_ref ( ) ) ;
417478
418479 // When signing a Mach-O in the context of a bundle, always define the
419480 // binary identifier from the filename so everything is consistent.
420481 // Unless an existing setting overrides it, of course.
421482 if settings. binary_identifier ( SettingsScope :: Main ) . is_none ( ) {
422- let identifier = path_identifier ( dest_rel_path ) ?;
483+ let identifier = path_identifier ( bundle_rel_path ) ?;
423484 info ! ( "setting binary identifier based on path: {}" , identifier) ;
424485
425486 settings. set_binary_identifier ( SettingsScope :: Main , & identifier) ;
@@ -430,17 +491,28 @@ impl<'a, 'key> BundleSigningContext<'a, 'key> {
430491 let mut new_data = Vec :: < u8 > :: with_capacity ( macho_data. len ( ) + 2_usize . pow ( 17 ) ) ;
431492 signer. write_signed_binary ( & settings, & mut new_data) ?;
432493
433- let dest_path = self . dest_dir . join ( dest_rel_path ) ;
494+ let dest_path = self . dest_dir . join ( bundle_rel_path ) ;
434495
435496 info ! ( "writing Mach-O to {}" , dest_path. display( ) ) ;
436497 write_macho_file ( source_path, & dest_path, & new_data) ?;
437498
438499 let info = SignedMachOInfo :: parse_data ( & new_data) ?;
439500
501+ self . installed_paths . insert ( bundle_rel_path. to_path_buf ( ) ) ;
502+
440503 Ok ( ( dest_path, info) )
441504 }
442505}
443506
507+ /// Holds metadata describing the result of a bundle signing operation.
508+ pub struct BundleSigningInfo {
509+ /// The signed bundle.
510+ pub bundle : DirectoryBundle ,
511+
512+ /// Bundle relative paths of files that are installed by this signing operation.
513+ pub installed_rel_paths : BTreeSet < PathBuf > ,
514+ }
515+
444516/// A primitive for signing a single Apple bundle.
445517///
446518/// Unlike [BundleSigner], this type only signs a single bundle and is ignorant
@@ -469,7 +541,8 @@ impl SingleBundleSigner {
469541 & self ,
470542 dest_dir : impl AsRef < Path > ,
471543 settings : & SigningSettings ,
472- ) -> Result < DirectoryBundle , AppleCodesignError > {
544+ previously_installed_paths : BTreeSet < PathBuf > ,
545+ ) -> Result < BundleSigningInfo , AppleCodesignError > {
473546 let dest_dir = dest_dir. as_ref ( ) ;
474547
475548 warn ! (
@@ -499,9 +572,11 @@ impl SingleBundleSigner {
499572 // But we still need to preserve files (hopefully just symlinks) outside the
500573 // nested bundles under `Versions/`. Since we don't nest into child bundles
501574 // here, it should be safe to handle each encountered file.
502- let context = BundleSigningContext {
575+ let mut context = BundleSigningContext {
503576 dest_dir : dest_dir. to_path_buf ( ) ,
504577 settings,
578+ previously_installed_paths,
579+ installed_paths : Default :: default ( ) ,
505580 } ;
506581
507582 for file in self
@@ -512,8 +587,13 @@ impl SingleBundleSigner {
512587 context. install_file ( file. absolute_path ( ) , file. relative_path ( ) ) ?;
513588 }
514589
515- return DirectoryBundle :: new_from_path ( dest_dir)
516- . map_err ( AppleCodesignError :: DirectoryBundle ) ;
590+ let bundle = DirectoryBundle :: new_from_path ( dest_dir)
591+ . map_err ( AppleCodesignError :: DirectoryBundle ) ?;
592+
593+ return Ok ( BundleSigningInfo {
594+ bundle,
595+ installed_rel_paths : context. installed_paths ,
596+ } ) ;
517597 } else {
518598 info ! ( "found an unversioned framework; signing like normal" ) ;
519599 }
@@ -609,15 +689,17 @@ impl SingleBundleSigner {
609689 ) ;
610690 }
611691
612- let context = BundleSigningContext {
692+ let mut context = BundleSigningContext {
613693 dest_dir : dest_dir_root. clone ( ) ,
614694 settings,
695+ previously_installed_paths,
696+ installed_paths : Default :: default ( ) ,
615697 } ;
616698
617699 resources_builder. walk_and_seal_directory (
618700 & self . root_bundle_path ,
619701 self . bundle . root_dir ( ) ,
620- & context,
702+ & mut context,
621703 ) ?;
622704
623705 let info_plist_data = std:: fs:: read ( self . bundle . info_plist_path ( ) ) ?;
@@ -674,10 +756,20 @@ impl SingleBundleSigner {
674756 let dest_path = dest_dir_root. join ( exe. relative_path ( ) ) ;
675757 info ! ( "writing signed main executable to {}" , dest_path. display( ) ) ;
676758 write_macho_file ( exe. absolute_path ( ) , & dest_path, & new_data) ?;
759+
760+ context
761+ . installed_paths
762+ . insert ( exe. relative_path ( ) . to_path_buf ( ) ) ;
677763 } else {
678764 warn ! ( "bundle has no main executable to sign specially" ) ;
679765 }
680766
681- DirectoryBundle :: new_from_path ( & dest_dir_root) . map_err ( AppleCodesignError :: DirectoryBundle )
767+ let bundle = DirectoryBundle :: new_from_path ( & dest_dir_root)
768+ . map_err ( AppleCodesignError :: DirectoryBundle ) ?;
769+
770+ Ok ( BundleSigningInfo {
771+ bundle,
772+ installed_rel_paths : context. installed_paths ,
773+ } )
682774 }
683775}
0 commit comments