Skip to content

Commit ab13f1c

Browse files
author
Nisha K
committed
Enable multi-layer analysis with no mount
This is work towards tern-tools#1082 Since overlay mount within a container requires the container to run with privileges, we need another way of manifesting the state of the filesystem at a given layer. This change adds a new function apply_layers which uses bulk copy from one folder to another after taking care of whiteout files. This will be the new default operation of Tern's default analysis method. The other two drivers can be accessed using the --driver option. To account for the switch between using the drivers and applying layers using the bulk copy method, we introduce the prep_layers function that will make the switch. The rest of the changes involve replacing instances of mount_overlay_fs with prep_layers wherever appropriate. To enable full operation, before adding the files to the image layer during reading of the file data, we set the whiteout status of the file. Signed-off-by: Nisha K <[email protected]>
1 parent 7bcbf6c commit ab13f1c

File tree

5 files changed

+55
-17
lines changed

5 files changed

+55
-17
lines changed

tern/__main__.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,12 @@ def main():
145145
help="Change default working directory to specified"
146146
" absolute path.")
147147
parser.add_argument('-dr', '--driver', metavar="DRIVER_OPTION",
148-
help="Required when running Tern in a container."
149-
"Using 'fuse' will enable the fuse-overlayfs driver "
150-
"to mount the diff layers of the container. If no "
151-
"input is provided, 'fuse' will be used as the "
152-
"default option.")
148+
default='default',
149+
help="Choose from the following storage drivers: \n"
150+
"overlay2: Use the kernel's overlay2 storage driver\n"
151+
"fuse: Use the fuse-overlayfs system tool\n"
152+
"If no option is given, the default method of "
153+
"applying container layers in userspace will be used.")
153154

154155
# sys.version gives more information than we care to print
155156
py_ver = sys.version.replace('\n', '').split('[', maxsplit=1)[0]

tern/analyze/default/container/multi_layer.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
Functions to analyze the subsequent layers in default mode
88
"""
99

10+
import glob
1011
import logging
12+
import os
13+
import shutil
1114

1215
from tern.report import errors
1316
from tern.utils import constants
@@ -25,7 +28,7 @@
2528
logger = logging.getLogger(constants.logger_name)
2629

2730

28-
def mount_overlay_fs(image_obj, top_layer, driver=None):
31+
def mount_overlay_fs(image_obj, top_layer, driver):
2932
'''Given the image object and the top most layer, mount all the layers
3033
until the top layer using overlayfs'''
3134
tar_layers = []
@@ -35,6 +38,40 @@ def mount_overlay_fs(image_obj, top_layer, driver=None):
3538
return target
3639

3740

41+
def apply_layers(image_obj, top_layer):
42+
"""Apply image diff layers without using a kernel snapshot driver"""
43+
# All merging happens in the merge directory
44+
target = os.path.join(rootfs.get_working_dir(), constants.mergedir)
45+
layer_dir = rootfs.get_untar_dir(image_obj.layers[top_layer].tar_file)
46+
layer_contents = layer_dir + '/*'
47+
# Account for whiteout files
48+
for fd in image_obj.layers[top_layer].files:
49+
if fd.is_whiteout:
50+
# delete the corresponding file or directory in the target
51+
# directory as well as the layer contents directory
52+
deleted = fd.name.replace('.wh.', '')
53+
delpath = os.path.join(target, os.path.dirname(fd.path), deleted)
54+
if os.path.exists(delpath):
55+
if os.path.isfile(delpath):
56+
os.remove(delpath)
57+
else:
58+
shutil.rmtree(delpath)
59+
os.remove(os.path.join(layer_dir, fd.path))
60+
# Finally, bulk copy the layer contents into the target directory
61+
# if there are any files to move
62+
if os.listdir(layer_dir):
63+
rootfs.root_command(['cp', '-r'] + glob.glob(layer_contents), target)
64+
return target
65+
66+
67+
def prep_layers(image_obj, top_layer, driver='default'):
68+
"""Prepare the layer diff contents uptil the required point in time
69+
based on the driver"""
70+
if driver == 'default':
71+
return apply_layers(image_obj, top_layer)
72+
return mount_overlay_fs(image_obj, top_layer, driver)
73+
74+
3875
def fresh_analysis(image_obj, curr_layer, prereqs, options):
3976
"""This is a subroutine that is run if there is no chached results or if
4077
the user wants to redo the analysis
@@ -53,12 +90,10 @@ def fresh_analysis(image_obj, curr_layer, prereqs, options):
5390
# if there is no shell, try to see if it exists in the current layer
5491
if not prereqs.fs_shell:
5592
prereqs.fs_shell = dcom.get_shell(image_obj.layers[curr_layer])
56-
# mount diff layers from 0 till the current layer
57-
target = mount_overlay_fs(image_obj, curr_layer, options.driver)
93+
# prep layers based on the chosen driver
94+
target = prep_layers(image_obj, curr_layer, options.driver)
5895
# set this layer's host path
5996
prereqs.host_path = target
60-
# mount dev, sys and proc after mounting diff layers
61-
rootfs.prep_rootfs(target)
6297
# get commands that created the layer
6398
# for docker images this is retrieved from the image history
6499
command_list = dcom.get_commands_from_metadata(
@@ -78,7 +113,9 @@ def fresh_analysis(image_obj, curr_layer, prereqs, options):
78113
else:
79114
# fall back to executing what we know
80115
core.execute_base(image_obj.layers[curr_layer], prereqs)
81-
rootfs.unmount_rootfs()
116+
# if the driver is using some storage driver, unmount the rootfs
117+
if options.driver != 'default':
118+
rootfs.unmount_rootfs()
82119

83120

84121
def analyze_subsequent_layers(image_obj, prereqs, master_list, options):

tern/analyze/default/debug/run.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# -*- coding: utf-8 -*-
22
#
3-
# Copyright (c) 2017-2020 VMware, Inc. All Rights Reserved.
3+
# Copyright (c) 2017-2021 VMware, Inc. All Rights Reserved.
44
# SPDX-License-Identifier: BSD-2-Clause
55

66
"""
@@ -41,7 +41,7 @@ def check_image_obj(image_string):
4141
def mount_container_image(image_obj, driver=None):
4242
"""Mount the container image to make it ready to invoke scripts"""
4343
if len(image_obj.layers) > 1:
44-
target = multi_layer.mount_overlay_fs(
44+
target = multi_layer.prep_layers(
4545
image_obj, len(image_obj.layers) - 1, driver)
4646
rootfs.prep_rootfs(target)
4747
else:
@@ -106,11 +106,10 @@ def drop_into_layer(image_obj, layer_index):
106106
upto the specified layer index and drop into a shell session"""
107107
rootfs.set_up()
108108
if layer_index == 0:
109-
# mount only one layer
110109
target = rootfs.prep_base_layer(image_obj.layers[layer_index].tar_file)
111110
else:
112111
# mount all layers uptil the provided layer index
113-
target = multi_layer.mount_overlay_fs(image_obj, layer_index)
112+
target = multi_layer.prep_layers(image_obj, layer_index)
114113
mount_path = get_mount_path()
115114
print("\nWorking directory is: {}\n".format(mount_path))
116115
# check if there is a shell

tern/classes/image_layer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ def add_files(self):
322322
os.path.relpath(file_info[2], '.'))
323323
file_data.set_checksum('sha256', file_info[1])
324324
file_data.extattrs = file_info[0]
325+
file_data.set_whiteout()
325326
self.add_file(file_data)
326327

327328
def get_layer_workdir(self):

tern/utils/rootfs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ def prep_base_layer(base_layer_tar):
204204
return target_dir_path
205205

206206

207-
def mount_diff_layers(diff_layers_tar, driver=None):
207+
def mount_diff_layers(diff_layers_tar, driver='overlay2'):
208208
'''Using overlayfs, mount all the layer tarballs'''
209209
# make a list of directory paths to give to lowerdir
210210
lower_dir_paths = []
@@ -218,7 +218,7 @@ def mount_diff_layers(diff_layers_tar, driver=None):
218218
',workdir=' + workdir_path
219219
if driver == 'fuse':
220220
root_command(fuse_mount, args, merge_dir_path)
221-
else:
221+
if driver == 'overlay2':
222222
root_command(union_mount, args, merge_dir_path)
223223
return merge_dir_path
224224

0 commit comments

Comments
 (0)