blob: 05cf2064518a5e3c6a6642f7cab0c0d0ebb20fdc [file] [log] [blame]
Mike Frysinger9f7e4ee2013-03-13 15:43:03 -04001#!/usr/bin/python
Jim Hebert91c052c2011-03-11 11:00:53 -08002# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import json
7import portage
Jim Hebert91c052c2011-03-11 11:00:53 -08008from parallel_emerge import DepGraphGenerator
Jim Hebertcf870d72013-06-12 15:33:34 -07009from chromite.lib import commandline
10from chromite.lib import cros_build_lib
Jim Hebert91c052c2011-03-11 11:00:53 -080011
David James1b363582012-12-17 11:53:11 -080012def FlattenDepTree(deptree, pkgtable=None, parentcpv=None):
Jim Hebert91c052c2011-03-11 11:00:53 -080013 """
14 Turn something like this (the parallel_emerge DepsTree format):
15{
16 "app-admin/eselect-1.2.9": {
17 "action": "merge",
18 "deps": {
19 "sys-apps/coreutils-7.5-r1": {
20 "action": "merge",
21 "deps": {},
22 "deptype": "runtime"
23 },
24 ...
25 }
26 }
27}
28 ...into something like this (the cros_extract_deps format):
29{
30 "app-admin/eselect-1.2.9": {
31 "deps": ["coreutils-7.5-r1"],
32 "rev_deps": [],
33 "name": "eselect",
34 "category": "app-admin",
35 "version": "1.2.9",
36 "full_name": "app-admin/eselect-1.2.9",
37 "action": "merge"
38 },
39 "sys-apps/coreutils-7.5-r1": {
40 "deps": [],
41 "rev_deps": ["app-admin/eselect-1.2.9"],
42 "name": "coreutils",
43 "category": "sys-apps",
44 "version": "7.5-r1",
45 "full_name": "sys-apps/coreutils-7.5-r1",
46 "action": "merge"
47 }
48}
49 """
David James1b363582012-12-17 11:53:11 -080050 if pkgtable is None:
51 pkgtable = {}
Jim Hebert91c052c2011-03-11 11:00:53 -080052 for cpv, record in deptree.items():
53 if cpv not in pkgtable:
David James1b363582012-12-17 11:53:11 -080054 cat, nam, ver, rev = portage.versions.catpkgsplit(cpv)
Jim Hebert91c052c2011-03-11 11:00:53 -080055 pkgtable[cpv] = {"deps": [],
56 "rev_deps": [],
57 "name": nam,
58 "category": cat,
59 "version": "%s-%s" % (ver, rev),
60 "full_name": cpv,
Jim Hebertcf870d72013-06-12 15:33:34 -070061 "cpes": GetCPEFromCPV(cat, nam, ver),
Jim Hebert91c052c2011-03-11 11:00:53 -080062 "action": record["action"]}
63 # If we have a parent, that is a rev_dep for the current package.
64 if parentcpv:
65 pkgtable[cpv]["rev_deps"].append(parentcpv)
66 # If current package has any deps, record those.
67 for childcpv in record["deps"]:
68 pkgtable[cpv]["deps"].append(childcpv)
69 # Visit the subtree recursively as well.
70 FlattenDepTree(record["deps"], pkgtable=pkgtable, parentcpv=cpv)
71 return pkgtable
72
73
Jim Hebertcf870d72013-06-12 15:33:34 -070074def GetCPEFromCPV(category, package, version):
75 """Look up the CPE for a specified Portage package.
Jim Hebert91c052c2011-03-11 11:00:53 -080076
Jim Hebertcf870d72013-06-12 15:33:34 -070077 Args:
78 category: The Portage package's category, e.g. "net-misc"
79 package: The Portage package's name, e.g. "curl"
80 version: The Portage version, e.g. "7.30.0"
81
Mike Frysinger02e1e072013-11-10 22:11:34 -050082 Returns:
83 A list of CPE Name strings, e.g.
84 ["cpe:/a:curl:curl:7.30.0", "cpe:/a:curl:libcurl:7.30.0"]
Jim Hebertcf870d72013-06-12 15:33:34 -070085 """
86 equery_cmd = ["equery", "m", "-U", "%s/%s" % (category, package)]
87 lines = cros_build_lib.RunCommand(equery_cmd, error_code_ok=True,
88 print_cmd=False,
89 redirect_stdout=True).output.splitlines()
90 # Look for lines like "Remote-ID: cpe:/a:kernel:linux-pam ID: cpe"
91 # and extract the cpe URI.
92 cpes = []
93 for line in lines:
94 if "ID: cpe" not in line:
95 continue
Jim Hebert96aff9c2013-07-16 15:43:17 -070096 cpes.append("%s:%s" % (line.split()[1], version.replace("_", "")))
Jim Hebertcf870d72013-06-12 15:33:34 -070097 # Note that we're assuming we can combine the root of the CPE, taken
98 # from metadata.xml, and tack on the version number as used by
99 # Portage, and come up with a legitimate CPE. This works so long as
100 # Portage and CPE agree on the precise formatting of the version
Jim Hebert96aff9c2013-07-16 15:43:17 -0700101 # number, which they almost always do. The major exception we've
102 # identified thus far is that our ebuilds have a pattern of inserting
103 # underscores prior to patchlevels, that neither upstream nor CPE
104 # use. For example, our code will decide we have
Jim Hebertcf870d72013-06-12 15:33:34 -0700105 # cpe:/a:todd_miller:sudo:1.8.6_p7 yet the advisories use a format
106 # like cpe:/a:todd_miller:sudo:1.8.6p7, without the underscore. (CPE
107 # is "right" in this example, in that it matches www.sudo.ws.)
108 #
Jim Hebert96aff9c2013-07-16 15:43:17 -0700109 # Removing underscores seems to improve our chances of correctly
110 # arriving at the CPE used by NVD. However, at the end of the day,
111 # ebuild version numbers are rev'd by people who don't have "try to
112 # match NVD" as one of their goals, and there is always going to be
113 # some risk of minor formatting disagreements at the version number
114 # level, if not from stray underscores then from something else.
115 #
Jim Hebertcf870d72013-06-12 15:33:34 -0700116 # This is livable so long as you do some fuzzy version number
117 # comparison in your vulnerability monitoring, between what-we-have
118 # and what-the-advisory-says-is-affected.
119 return cpes
120
121
122def ExtractCPEList(deps_list):
123 cpe_dump = []
124 for cpv, record in deps_list.items():
125 if record["cpes"]:
Jim Heberteef14932013-07-09 11:34:08 -0700126 cpe_dump.append({"Name": cpv, "Targets": sorted(record["cpes"]),
127 "Repository": "cros"})
Jim Hebertcf870d72013-06-12 15:33:34 -0700128 else:
129 cros_build_lib.Warning("No CPE entry for %s", cpv)
Jim Heberteef14932013-07-09 11:34:08 -0700130 return sorted(cpe_dump, key=lambda k: k["Name"])
Jim Hebert91c052c2011-03-11 11:00:53 -0800131
132
Brian Harring30675052012-02-29 12:18:22 -0800133def main(argv):
Jim Hebertcf870d72013-06-12 15:33:34 -0700134 parser = commandline.ArgumentParser(description="""
135This extracts the dependency tree for the specified package, and outputs it
136to stdout, in a serialized JSON format.""")
137 parser.add_argument("--board", required=True,
138 help="The board to use when computing deps.")
139 parser.add_argument("--format", default="deps",
140 choices=["deps", "cpe"],
141 help="Output either traditional deps or CPE-only JSON")
142 # Even though this is really just a pass-through to DepGraphGenerator,
143 # handling it as a known arg here allows us to specify a default more
144 # elegantly than testing for its presence in the unknown_args later.
145 parser.add_argument("--root-deps", default="rdeps",
146 help="Which deps to report (defaults to rdeps)")
147 known_args, unknown_args = parser.parse_known_args(argv)
Jim Hebert91c052c2011-03-11 11:00:53 -0800148
Jim Hebertcf870d72013-06-12 15:33:34 -0700149 lib_argv = ["--board=%s" % known_args.board,
150 "--root-deps=%s" % known_args.root_deps,
151 "--quiet", "--pretend", "--emptytree"]
152 lib_argv.extend(unknown_args)
Jim Hebert91c052c2011-03-11 11:00:53 -0800153
154 deps = DepGraphGenerator()
Jim Hebertcf870d72013-06-12 15:33:34 -0700155 deps.Initialize(lib_argv)
David James1b363582012-12-17 11:53:11 -0800156 deps_tree, _deps_info = deps.GenDependencyTree()
Jim Hebertcf870d72013-06-12 15:33:34 -0700157 deps_list = FlattenDepTree(deps_tree)
158 if known_args.format == "cpe":
159 deps_list = ExtractCPEList(deps_list)
160 print json.dumps(deps_list, sort_keys=True, indent=2)