@@ -345,12 +345,80 @@ def _get_files_owned_by_rpms(context, dirpath, pkgs=None, recursive=False):
345
345
return files_owned_by_rpms
346
346
347
347
348
+ def _copy_decouple (srcdir , dstdir ):
349
+ """
350
+ Copy `srcdir` to `dstdir` while decoupling symlinks.
351
+
352
+ What we mean by decoupling the `srcdir` is that any symlinks pointing
353
+ outside the directory will be copied as regular files. This means that the
354
+ directory will become independent from its surroundings with respect to
355
+ symlinks. Any symlink (or symlink chains) within the directory will be
356
+ preserved.
357
+
358
+ """
359
+
360
+ for root , dummy_dirs , files in os .walk (srcdir ):
361
+ for filename in files :
362
+ relpath = os .path .relpath (root , srcdir )
363
+ source_filepath = os .path .join (root , filename )
364
+ target_filepath = os .path .join (dstdir , relpath , filename )
365
+
366
+ # Skip and report broken symlinks
367
+ if not os .path .exists (source_filepath ):
368
+ api .current_logger ().warning (
369
+ 'File {} is a broken symlink! Will not copy the file.' .format (source_filepath ))
370
+ continue
371
+
372
+ # Copy symlinks to the target userspace
373
+ source_is_symlink = os .path .islink (source_filepath )
374
+ pointee = None
375
+ if source_is_symlink :
376
+ pointee = os .readlink (source_filepath )
377
+
378
+ # If source file is a symlink within `srcdir` then preserve it,
379
+ # otherwise resolve and copy it as a file it points to
380
+ if pointee is not None and not pointee .startswith (srcdir ):
381
+ # Follow the path until we hit a file or get back to /etc/pki
382
+ while not pointee .startswith (srcdir ) and os .path .islink (pointee ):
383
+ pointee = os .readlink (pointee )
384
+
385
+ # Pointee points to a _regular file_ outside /etc/pki so we
386
+ # copy it instead
387
+ if not pointee .startswith (srcdir ) and not os .path .islink (pointee ):
388
+ source_is_symlink = False
389
+ source_filepath = pointee
390
+ else :
391
+ # pointee points back to /etc/pki
392
+ pass
393
+
394
+ # Ensure parent directory exists
395
+ parent_dir = os .path .dirname (target_filepath )
396
+ # Note: This is secure because we know that parent_dir is located
397
+ # inside of `$target_userspace/etc/pki` which is a directory that
398
+ # is not writable by unprivileged users. If this function is used
399
+ # elsewhere we may need to be more careful before running `mkdir -p`.
400
+ run (['mkdir' , '-p' , parent_dir ])
401
+
402
+ if source_is_symlink :
403
+ # Preserve the owner and permissions of the original symlink
404
+ run (['ln' , '-s' , pointee , target_filepath ])
405
+ run (['chmod' , '--reference={}' .format (source_filepath ), target_filepath ])
406
+ continue
407
+
408
+ run (['cp' , '-a' , source_filepath , target_filepath ])
409
+
410
+
348
411
def _copy_certificates (context , target_userspace ):
349
412
"""
350
- Copy the needed certificates into the container, but preserve original ones
413
+ Copy certificates from source system into the container, but preserve
414
+ original ones
351
415
352
416
Some certificates are already installed in the container and those are
353
417
default certificates for the target OS, so we preserve these.
418
+
419
+ We respect the symlink hierarchy of the source system within the /etc/pki
420
+ folder. Dangling symlinks will be ignored.
421
+
354
422
"""
355
423
356
424
target_pki = os .path .join (target_userspace , 'etc' , 'pki' )
@@ -360,36 +428,56 @@ def _copy_certificates(context, target_userspace):
360
428
files_owned_by_rpms = _get_files_owned_by_rpms (target_context , '/etc/pki' , recursive = True )
361
429
api .current_logger ().debug ('Files owned by rpms: {}' .format (' ' .join (files_owned_by_rpms )))
362
430
431
+ # Backup container /etc/pki
363
432
run (['mv' , target_pki , backup_pki ])
364
- context .copytree_from ('/etc/pki' , target_pki )
365
433
434
+ # Copy source /etc/pki to the container
435
+ _copy_decouple ('/etc/pki' , target_pki )
436
+
437
+ # Assertion: after running _copy_decouple(), no broken symlinks exist in /etc/pki in the container
438
+ # So any broken symlinks created will be by the installed packages.
439
+
440
+ # Recover installed packages as they always get precedence
366
441
for filepath in files_owned_by_rpms :
367
442
src_path = os .path .join (backup_pki , filepath )
368
443
dst_path = os .path .join (target_pki , filepath )
369
444
370
445
# Resolve and skip any broken symlinks
371
446
is_broken_symlink = False
372
- while os .path .islink (src_path ):
373
- # The symlink points to a path relative to the target userspace so
374
- # we need to readjust it
375
- next_path = os .path .join (target_userspace , os .readlink (src_path )[1 :])
376
- if not os .path .exists (next_path ):
377
- is_broken_symlink = True
378
-
379
- # The path original path of the broken symlink in the container
380
- report_path = os .path .join (target_pki , os .path .relpath (src_path , backup_pki ))
381
- api .current_logger ().warning ('File {} is a broken symlink!' .format (report_path ))
382
- break
383
-
384
- src_path = next_path
447
+ pointee = None
448
+ if os .path .islink (src_path ):
449
+ pointee = os .path .join (target_userspace , os .readlink (src_path )[1 :])
450
+
451
+ seen = set ()
452
+ while os .path .islink (pointee ):
453
+ # The symlink points to a path relative to the target userspace so
454
+ # we need to readjust it
455
+ pointee = os .path .join (target_userspace , os .readlink (src_path )[1 :])
456
+ if not os .path .exists (pointee ) or pointee in seen :
457
+ is_broken_symlink = True
458
+
459
+ # The path original path of the broken symlink in the container
460
+ report_path = os .path .join (target_pki , os .path .relpath (src_path , backup_pki ))
461
+ api .current_logger ().warning (
462
+ 'File {} is a broken symlink! Will not copy!' .format (report_path ))
463
+ break
464
+
465
+ seen .add (pointee )
385
466
386
467
if is_broken_symlink :
387
468
continue
388
469
470
+ # Cleanup conflicting files
389
471
run (['rm' , '-rf' , dst_path ])
472
+
473
+ # Ensure destination exists
390
474
parent_dir = os .path .dirname (dst_path )
391
475
run (['mkdir' , '-p' , parent_dir ])
392
- run (['cp' , '-a' , src_path , dst_path ])
476
+
477
+ # Copy the new file
478
+ run (['cp' , '-R' , '--preserve=all' , src_path , dst_path ])
479
+
480
+ run (['rm' , '-rf' , backup_pki ])
393
481
394
482
395
483
def _prep_repository_access (context , target_userspace ):
@@ -401,6 +489,10 @@ def _prep_repository_access(context, target_userspace):
401
489
backup_yum_repos_d = os .path .join (target_etc , 'yum.repos.d.backup' )
402
490
403
491
_copy_certificates (context , target_userspace )
492
+ # NOTE(dkubek): context.call(['update-ca-trust']) seems to not be working.
493
+ # I am not really sure why. The changes to files are not
494
+ # being written to disk.
495
+ run (["chroot" , target_userspace , "/bin/bash" , "-c" , "su - -c update-ca-trust" ])
404
496
405
497
if not rhsm .skip_rhsm ():
406
498
run (['rm' , '-rf' , os .path .join (target_etc , 'rhsm' )])
0 commit comments