blob: c4a4a998ed109b5b4c29a025af378399ef14df24 [file] [log] [blame]
Jim Hebert91c052c2011-03-11 11:00:53 -08001# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Don Garrett25f309a2014-03-19 14:02:12 -07005"""Command to extract the dependancy tree for a given package."""
6
Mike Frysinger383367e2014-09-16 15:06:17 -04007from __future__ import print_function
8
Jim Hebert91c052c2011-03-11 11:00:53 -08009import json
Don Garrettf8bf7842014-03-20 17:03:42 -070010import portage # pylint: disable=F0401
11
Jim Hebert91c052c2011-03-11 11:00:53 -080012from parallel_emerge import DepGraphGenerator
Don Garrettf8bf7842014-03-20 17:03:42 -070013
Jim Hebertcf870d72013-06-12 15:33:34 -070014from chromite.lib import commandline
15from chromite.lib import cros_build_lib
Ralph Nathan446aee92015-03-23 14:44:56 -070016from chromite.lib import cros_logging as logging
Mike Frysingercc838832014-05-24 13:10:30 -040017
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070018def FlattenDepTree(deptree, pkgtable=None, parentcpv=None, get_cpe=False):
Don Garrett25f309a2014-03-19 14:02:12 -070019 """Simplify dependency json.
20
21Turn something like this (the parallel_emerge DepsTree format):
Jim Hebert91c052c2011-03-11 11:00:53 -080022{
23 "app-admin/eselect-1.2.9": {
24 "action": "merge",
25 "deps": {
26 "sys-apps/coreutils-7.5-r1": {
27 "action": "merge",
28 "deps": {},
29 "deptype": "runtime"
30 },
31 ...
32 }
33 }
34}
35 ...into something like this (the cros_extract_deps format):
36{
37 "app-admin/eselect-1.2.9": {
38 "deps": ["coreutils-7.5-r1"],
39 "rev_deps": [],
40 "name": "eselect",
41 "category": "app-admin",
42 "version": "1.2.9",
43 "full_name": "app-admin/eselect-1.2.9",
44 "action": "merge"
45 },
46 "sys-apps/coreutils-7.5-r1": {
47 "deps": [],
48 "rev_deps": ["app-admin/eselect-1.2.9"],
49 "name": "coreutils",
50 "category": "sys-apps",
51 "version": "7.5-r1",
52 "full_name": "sys-apps/coreutils-7.5-r1",
53 "action": "merge"
54 }
55}
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070056
57 Args:
58 deptree: The dependency tree.
59 pkgtable: The package table to update. If None, create a new one.
60 parentcpv: The parent CPV.
61 get_cpe: If set True, include CPE in the flattened dependency tree.
62
63 Returns:
64 A flattened dependency tree.
Jim Hebert91c052c2011-03-11 11:00:53 -080065 """
David James1b363582012-12-17 11:53:11 -080066 if pkgtable is None:
67 pkgtable = {}
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070068 for cpv, record in deptree.iteritems():
Jim Hebert91c052c2011-03-11 11:00:53 -080069 if cpv not in pkgtable:
David James1b363582012-12-17 11:53:11 -080070 cat, nam, ver, rev = portage.versions.catpkgsplit(cpv)
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070071 pkgtable[cpv] = {'deps': [],
72 'rev_deps': [],
73 'name': nam,
74 'category': cat,
75 'version': '%s-%s' % (ver, rev),
76 'full_name': cpv,
77 'cpes': [],
78 'action': record['action']}
79 if get_cpe:
80 pkgtable[cpv]['cpes'].extend(GetCPEFromCPV(cat, nam, ver))
81
Jim Hebert91c052c2011-03-11 11:00:53 -080082 # If we have a parent, that is a rev_dep for the current package.
83 if parentcpv:
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070084 pkgtable[cpv]['rev_deps'].append(parentcpv)
Jim Hebert91c052c2011-03-11 11:00:53 -080085 # If current package has any deps, record those.
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070086 for childcpv in record['deps']:
87 pkgtable[cpv]['deps'].append(childcpv)
Jim Hebert91c052c2011-03-11 11:00:53 -080088 # Visit the subtree recursively as well.
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070089 FlattenDepTree(record['deps'], pkgtable=pkgtable, parentcpv=cpv,
90 get_cpe=get_cpe)
Jim Hebert91c052c2011-03-11 11:00:53 -080091 return pkgtable
92
93
Jim Hebertcf870d72013-06-12 15:33:34 -070094def GetCPEFromCPV(category, package, version):
95 """Look up the CPE for a specified Portage package.
Jim Hebert91c052c2011-03-11 11:00:53 -080096
Jim Hebertcf870d72013-06-12 15:33:34 -070097 Args:
98 category: The Portage package's category, e.g. "net-misc"
99 package: The Portage package's name, e.g. "curl"
100 version: The Portage version, e.g. "7.30.0"
101
Mike Frysinger02e1e072013-11-10 22:11:34 -0500102 Returns:
103 A list of CPE Name strings, e.g.
104 ["cpe:/a:curl:curl:7.30.0", "cpe:/a:curl:libcurl:7.30.0"]
Jim Hebertcf870d72013-06-12 15:33:34 -0700105 """
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700106 equery_cmd = ['equery', 'm', '-U', '%s/%s' % (category, package)]
Jim Hebertcf870d72013-06-12 15:33:34 -0700107 lines = cros_build_lib.RunCommand(equery_cmd, error_code_ok=True,
108 print_cmd=False,
109 redirect_stdout=True).output.splitlines()
110 # Look for lines like "Remote-ID: cpe:/a:kernel:linux-pam ID: cpe"
111 # and extract the cpe URI.
112 cpes = []
113 for line in lines:
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700114 if 'ID: cpe' not in line:
Jim Hebertcf870d72013-06-12 15:33:34 -0700115 continue
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700116 cpes.append('%s:%s' % (line.split()[1], version.replace('_', '')))
Jim Hebertcf870d72013-06-12 15:33:34 -0700117 # Note that we're assuming we can combine the root of the CPE, taken
118 # from metadata.xml, and tack on the version number as used by
119 # Portage, and come up with a legitimate CPE. This works so long as
120 # Portage and CPE agree on the precise formatting of the version
Jim Hebert96aff9c2013-07-16 15:43:17 -0700121 # number, which they almost always do. The major exception we've
122 # identified thus far is that our ebuilds have a pattern of inserting
123 # underscores prior to patchlevels, that neither upstream nor CPE
124 # use. For example, our code will decide we have
Jim Hebertcf870d72013-06-12 15:33:34 -0700125 # cpe:/a:todd_miller:sudo:1.8.6_p7 yet the advisories use a format
126 # like cpe:/a:todd_miller:sudo:1.8.6p7, without the underscore. (CPE
127 # is "right" in this example, in that it matches www.sudo.ws.)
128 #
Jim Hebert96aff9c2013-07-16 15:43:17 -0700129 # Removing underscores seems to improve our chances of correctly
130 # arriving at the CPE used by NVD. However, at the end of the day,
131 # ebuild version numbers are rev'd by people who don't have "try to
132 # match NVD" as one of their goals, and there is always going to be
133 # some risk of minor formatting disagreements at the version number
134 # level, if not from stray underscores then from something else.
135 #
Jim Hebertcf870d72013-06-12 15:33:34 -0700136 # This is livable so long as you do some fuzzy version number
137 # comparison in your vulnerability monitoring, between what-we-have
138 # and what-the-advisory-says-is-affected.
139 return cpes
140
141
142def ExtractCPEList(deps_list):
143 cpe_dump = []
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700144 for cpv, record in deps_list.iteritems():
145 if record['cpes']:
146 name = '%s/%s' % (record['category'], record['name'])
147 cpe_dump.append({'ComponentName': name,
148 'Repository': 'cros',
149 'Targets': sorted(record['cpes'])})
Jim Hebertcf870d72013-06-12 15:33:34 -0700150 else:
Ralph Nathan446aee92015-03-23 14:44:56 -0700151 logging.warning('No CPE entry for %s', cpv)
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700152 return sorted(cpe_dump, key=lambda k: k['ComponentName'])
Jim Hebert91c052c2011-03-11 11:00:53 -0800153
154
Brian Harring30675052012-02-29 12:18:22 -0800155def main(argv):
Jim Hebertcf870d72013-06-12 15:33:34 -0700156 parser = commandline.ArgumentParser(description="""
157This extracts the dependency tree for the specified package, and outputs it
158to stdout, in a serialized JSON format.""")
Yu-Ju Hong997108e2014-10-27 11:21:47 -0700159 parser.add_argument('--board', default=None,
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700160 help='The board to use when computing deps.')
161 parser.add_argument('--format', default='deps',
162 choices=['deps', 'cpe'],
Yu-Ju Hongf48d3982014-10-30 16:12:16 -0700163 help='Output either traditional deps or CPE-only JSON.')
164 parser.add_argument('--output-path', default=None,
165 help='Write output to the given path.')
Jim Hebertcf870d72013-06-12 15:33:34 -0700166 known_args, unknown_args = parser.parse_known_args(argv)
Jim Hebert91c052c2011-03-11 11:00:53 -0800167
Yu-Ju Hong997108e2014-10-27 11:21:47 -0700168 lib_argv = []
169 if known_args.board:
170 lib_argv += ['--board=%s' % known_args.board]
David Jamesf147b432014-12-02 13:51:55 -0800171 lib_argv += ['--quiet', '--pretend', '--emptytree']
Jim Hebertcf870d72013-06-12 15:33:34 -0700172 lib_argv.extend(unknown_args)
Jim Hebert91c052c2011-03-11 11:00:53 -0800173
174 deps = DepGraphGenerator()
Jim Hebertcf870d72013-06-12 15:33:34 -0700175 deps.Initialize(lib_argv)
David James1b363582012-12-17 11:53:11 -0800176 deps_tree, _deps_info = deps.GenDependencyTree()
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700177 deps_list = FlattenDepTree(deps_tree, get_cpe=(known_args.format == 'cpe'))
178 if known_args.format == 'cpe':
Jim Hebertcf870d72013-06-12 15:33:34 -0700179 deps_list = ExtractCPEList(deps_list)
Yu-Ju Hongf48d3982014-10-30 16:12:16 -0700180
181 deps_output = json.dumps(deps_list, sort_keys=True, indent=2)
182 if known_args.output_path:
183 with open(known_args.output_path, 'w') as f:
184 f.write(deps_output)
185 else:
186 print(deps_output)