xref: /OK3568_Linux_fs/buildroot/support/scripts/pkg-stats (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun#!/usr/bin/env python3
2*4882a593Smuzhiyun
3*4882a593Smuzhiyun# Copyright (C) 2009 by Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
4*4882a593Smuzhiyun#
5*4882a593Smuzhiyun# This program is free software; you can redistribute it and/or modify
6*4882a593Smuzhiyun# it under the terms of the GNU General Public License as published by
7*4882a593Smuzhiyun# the Free Software Foundation; either version 2 of the License, or
8*4882a593Smuzhiyun# (at your option) any later version.
9*4882a593Smuzhiyun#
10*4882a593Smuzhiyun# This program is distributed in the hope that it will be useful,
11*4882a593Smuzhiyun# but WITHOUT ANY WARRANTY; without even the implied warranty of
12*4882a593Smuzhiyun# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13*4882a593Smuzhiyun# General Public License for more details.
14*4882a593Smuzhiyun#
15*4882a593Smuzhiyun# You should have received a copy of the GNU General Public License
16*4882a593Smuzhiyun# along with this program; if not, write to the Free Software
17*4882a593Smuzhiyun# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18*4882a593Smuzhiyun
19*4882a593Smuzhiyunimport aiohttp
20*4882a593Smuzhiyunimport argparse
21*4882a593Smuzhiyunimport asyncio
22*4882a593Smuzhiyunimport datetime
23*4882a593Smuzhiyunimport fnmatch
24*4882a593Smuzhiyunimport os
25*4882a593Smuzhiyunfrom collections import defaultdict
26*4882a593Smuzhiyunimport re
27*4882a593Smuzhiyunimport subprocess
28*4882a593Smuzhiyunimport json
29*4882a593Smuzhiyunimport sys
30*4882a593Smuzhiyun
31*4882a593Smuzhiyunbrpath = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
32*4882a593Smuzhiyun
33*4882a593Smuzhiyunsys.path.append(os.path.join(brpath, "utils"))
34*4882a593Smuzhiyunfrom getdeveloperlib import parse_developers  # noqa: E402
35*4882a593Smuzhiyunfrom cpedb import CPEDB  # noqa: E402
36*4882a593Smuzhiyun
37*4882a593SmuzhiyunINFRA_RE = re.compile(r"\$\(eval \$\(([a-z-]*)-package\)\)")
38*4882a593SmuzhiyunURL_RE = re.compile(r"\s*https?://\S*\s*$")
39*4882a593Smuzhiyun
40*4882a593SmuzhiyunRM_API_STATUS_ERROR = 1
41*4882a593SmuzhiyunRM_API_STATUS_FOUND_BY_DISTRO = 2
42*4882a593SmuzhiyunRM_API_STATUS_FOUND_BY_PATTERN = 3
43*4882a593SmuzhiyunRM_API_STATUS_NOT_FOUND = 4
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun
46*4882a593Smuzhiyunclass Defconfig:
47*4882a593Smuzhiyun    def __init__(self, name, path):
48*4882a593Smuzhiyun        self.name = name
49*4882a593Smuzhiyun        self.path = path
50*4882a593Smuzhiyun        self.developers = None
51*4882a593Smuzhiyun
52*4882a593Smuzhiyun    def set_developers(self, developers):
53*4882a593Smuzhiyun        """
54*4882a593Smuzhiyun        Fills in the .developers field
55*4882a593Smuzhiyun        """
56*4882a593Smuzhiyun        self.developers = [
57*4882a593Smuzhiyun            developer.name
58*4882a593Smuzhiyun            for developer in developers
59*4882a593Smuzhiyun            if developer.hasfile(self.path)
60*4882a593Smuzhiyun        ]
61*4882a593Smuzhiyun
62*4882a593Smuzhiyun
63*4882a593Smuzhiyundef get_defconfig_list():
64*4882a593Smuzhiyun    """
65*4882a593Smuzhiyun    Builds the list of Buildroot defconfigs, returning a list of Defconfig
66*4882a593Smuzhiyun    objects.
67*4882a593Smuzhiyun    """
68*4882a593Smuzhiyun    return [
69*4882a593Smuzhiyun        Defconfig(name[:-len('_defconfig')], os.path.join('configs', name))
70*4882a593Smuzhiyun        for name in os.listdir(os.path.join(brpath, 'configs'))
71*4882a593Smuzhiyun        if name.endswith('_defconfig')
72*4882a593Smuzhiyun    ]
73*4882a593Smuzhiyun
74*4882a593Smuzhiyun
75*4882a593Smuzhiyunclass Package:
76*4882a593Smuzhiyun    all_licenses = dict()
77*4882a593Smuzhiyun    all_license_files = list()
78*4882a593Smuzhiyun    all_versions = dict()
79*4882a593Smuzhiyun    all_ignored_cves = dict()
80*4882a593Smuzhiyun    all_cpeids = dict()
81*4882a593Smuzhiyun    # This is the list of all possible checks. Add new checks to this list so
82*4882a593Smuzhiyun    # a tool that post-processeds the json output knows the checks before
83*4882a593Smuzhiyun    # iterating over the packages.
84*4882a593Smuzhiyun    status_checks = ['cve', 'developers', 'hash', 'license',
85*4882a593Smuzhiyun                     'license-files', 'patches', 'pkg-check', 'url', 'version']
86*4882a593Smuzhiyun
87*4882a593Smuzhiyun    def __init__(self, name, path):
88*4882a593Smuzhiyun        self.name = name
89*4882a593Smuzhiyun        self.path = path
90*4882a593Smuzhiyun        self.pkg_path = os.path.dirname(path)
91*4882a593Smuzhiyun        self.infras = None
92*4882a593Smuzhiyun        self.license = None
93*4882a593Smuzhiyun        self.has_license = False
94*4882a593Smuzhiyun        self.has_license_files = False
95*4882a593Smuzhiyun        self.has_hash = False
96*4882a593Smuzhiyun        self.patch_files = []
97*4882a593Smuzhiyun        self.warnings = 0
98*4882a593Smuzhiyun        self.current_version = None
99*4882a593Smuzhiyun        self.url = None
100*4882a593Smuzhiyun        self.url_worker = None
101*4882a593Smuzhiyun        self.cpeid = None
102*4882a593Smuzhiyun        self.cves = list()
103*4882a593Smuzhiyun        self.ignored_cves = list()
104*4882a593Smuzhiyun        self.latest_version = {'status': RM_API_STATUS_ERROR, 'version': None, 'id': None}
105*4882a593Smuzhiyun        self.status = {}
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun    def pkgvar(self):
108*4882a593Smuzhiyun        return self.name.upper().replace("-", "_")
109*4882a593Smuzhiyun
110*4882a593Smuzhiyun    def set_url(self):
111*4882a593Smuzhiyun        """
112*4882a593Smuzhiyun        Fills in the .url field
113*4882a593Smuzhiyun        """
114*4882a593Smuzhiyun        self.status['url'] = ("warning", "no Config.in")
115*4882a593Smuzhiyun        pkgdir = os.path.dirname(os.path.join(brpath, self.path))
116*4882a593Smuzhiyun        for filename in os.listdir(pkgdir):
117*4882a593Smuzhiyun            if fnmatch.fnmatch(filename, 'Config.*'):
118*4882a593Smuzhiyun                fp = open(os.path.join(pkgdir, filename), "r")
119*4882a593Smuzhiyun                for config_line in fp:
120*4882a593Smuzhiyun                    if URL_RE.match(config_line):
121*4882a593Smuzhiyun                        self.url = config_line.strip()
122*4882a593Smuzhiyun                        self.status['url'] = ("ok", "found")
123*4882a593Smuzhiyun                        fp.close()
124*4882a593Smuzhiyun                        return
125*4882a593Smuzhiyun                self.status['url'] = ("error", "missing")
126*4882a593Smuzhiyun                fp.close()
127*4882a593Smuzhiyun
128*4882a593Smuzhiyun    @property
129*4882a593Smuzhiyun    def patch_count(self):
130*4882a593Smuzhiyun        return len(self.patch_files)
131*4882a593Smuzhiyun
132*4882a593Smuzhiyun    @property
133*4882a593Smuzhiyun    def has_valid_infra(self):
134*4882a593Smuzhiyun        if self.infras is None:
135*4882a593Smuzhiyun            return False
136*4882a593Smuzhiyun        return len(self.infras) > 0
137*4882a593Smuzhiyun
138*4882a593Smuzhiyun    @property
139*4882a593Smuzhiyun    def is_actual_package(self):
140*4882a593Smuzhiyun        try:
141*4882a593Smuzhiyun            if not self.has_valid_infra:
142*4882a593Smuzhiyun                return False
143*4882a593Smuzhiyun            if self.infras[0][1] == 'virtual':
144*4882a593Smuzhiyun                return False
145*4882a593Smuzhiyun        except IndexError:
146*4882a593Smuzhiyun            return False
147*4882a593Smuzhiyun        return True
148*4882a593Smuzhiyun
149*4882a593Smuzhiyun    def set_infra(self):
150*4882a593Smuzhiyun        """
151*4882a593Smuzhiyun        Fills in the .infras field
152*4882a593Smuzhiyun        """
153*4882a593Smuzhiyun        self.infras = list()
154*4882a593Smuzhiyun        with open(os.path.join(brpath, self.path), 'r') as f:
155*4882a593Smuzhiyun            lines = f.readlines()
156*4882a593Smuzhiyun            for line in lines:
157*4882a593Smuzhiyun                match = INFRA_RE.match(line)
158*4882a593Smuzhiyun                if not match:
159*4882a593Smuzhiyun                    continue
160*4882a593Smuzhiyun                infra = match.group(1)
161*4882a593Smuzhiyun                if infra.startswith("host-"):
162*4882a593Smuzhiyun                    self.infras.append(("host", infra[5:]))
163*4882a593Smuzhiyun                else:
164*4882a593Smuzhiyun                    self.infras.append(("target", infra))
165*4882a593Smuzhiyun
166*4882a593Smuzhiyun    def set_license(self):
167*4882a593Smuzhiyun        """
168*4882a593Smuzhiyun        Fills in the .status['license'] and .status['license-files'] fields
169*4882a593Smuzhiyun        """
170*4882a593Smuzhiyun        if not self.is_actual_package:
171*4882a593Smuzhiyun            self.status['license'] = ("na", "no valid package infra")
172*4882a593Smuzhiyun            self.status['license-files'] = ("na", "no valid package infra")
173*4882a593Smuzhiyun            return
174*4882a593Smuzhiyun
175*4882a593Smuzhiyun        var = self.pkgvar()
176*4882a593Smuzhiyun        self.status['license'] = ("error", "missing")
177*4882a593Smuzhiyun        self.status['license-files'] = ("error", "missing")
178*4882a593Smuzhiyun        if var in self.all_licenses:
179*4882a593Smuzhiyun            self.license = self.all_licenses[var]
180*4882a593Smuzhiyun            self.status['license'] = ("ok", "found")
181*4882a593Smuzhiyun        if var in self.all_license_files:
182*4882a593Smuzhiyun            self.status['license-files'] = ("ok", "found")
183*4882a593Smuzhiyun
184*4882a593Smuzhiyun    def set_hash_info(self):
185*4882a593Smuzhiyun        """
186*4882a593Smuzhiyun        Fills in the .status['hash'] field
187*4882a593Smuzhiyun        """
188*4882a593Smuzhiyun        if not self.is_actual_package:
189*4882a593Smuzhiyun            self.status['hash'] = ("na", "no valid package infra")
190*4882a593Smuzhiyun            self.status['hash-license'] = ("na", "no valid package infra")
191*4882a593Smuzhiyun            return
192*4882a593Smuzhiyun
193*4882a593Smuzhiyun        hashpath = self.path.replace(".mk", ".hash")
194*4882a593Smuzhiyun        if os.path.exists(os.path.join(brpath, hashpath)):
195*4882a593Smuzhiyun            self.status['hash'] = ("ok", "found")
196*4882a593Smuzhiyun        else:
197*4882a593Smuzhiyun            self.status['hash'] = ("error", "missing")
198*4882a593Smuzhiyun
199*4882a593Smuzhiyun    def set_patch_count(self):
200*4882a593Smuzhiyun        """
201*4882a593Smuzhiyun        Fills in the .patch_count, .patch_files and .status['patches'] fields
202*4882a593Smuzhiyun        """
203*4882a593Smuzhiyun        if not self.is_actual_package:
204*4882a593Smuzhiyun            self.status['patches'] = ("na", "no valid package infra")
205*4882a593Smuzhiyun            return
206*4882a593Smuzhiyun
207*4882a593Smuzhiyun        pkgdir = os.path.dirname(os.path.join(brpath, self.path))
208*4882a593Smuzhiyun        for subdir, _, _ in os.walk(pkgdir):
209*4882a593Smuzhiyun            self.patch_files = fnmatch.filter(os.listdir(subdir), '*.patch')
210*4882a593Smuzhiyun
211*4882a593Smuzhiyun        if self.patch_count == 0:
212*4882a593Smuzhiyun            self.status['patches'] = ("ok", "no patches")
213*4882a593Smuzhiyun        elif self.patch_count < 5:
214*4882a593Smuzhiyun            self.status['patches'] = ("warning", "some patches")
215*4882a593Smuzhiyun        else:
216*4882a593Smuzhiyun            self.status['patches'] = ("error", "lots of patches")
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun    def set_current_version(self):
219*4882a593Smuzhiyun        """
220*4882a593Smuzhiyun        Fills in the .current_version field
221*4882a593Smuzhiyun        """
222*4882a593Smuzhiyun        var = self.pkgvar()
223*4882a593Smuzhiyun        if var in self.all_versions:
224*4882a593Smuzhiyun            self.current_version = self.all_versions[var]
225*4882a593Smuzhiyun
226*4882a593Smuzhiyun    def set_cpeid(self):
227*4882a593Smuzhiyun        """
228*4882a593Smuzhiyun        Fills in the .cpeid field
229*4882a593Smuzhiyun        """
230*4882a593Smuzhiyun        var = self.pkgvar()
231*4882a593Smuzhiyun        if not self.is_actual_package:
232*4882a593Smuzhiyun            self.status['cpe'] = ("na", "N/A - virtual pkg")
233*4882a593Smuzhiyun            return
234*4882a593Smuzhiyun        if not self.current_version:
235*4882a593Smuzhiyun            self.status['cpe'] = ("na", "no version information available")
236*4882a593Smuzhiyun            return
237*4882a593Smuzhiyun
238*4882a593Smuzhiyun        if var in self.all_cpeids:
239*4882a593Smuzhiyun            self.cpeid = self.all_cpeids[var]
240*4882a593Smuzhiyun            # Set a preliminary status, it might be overridden by check_package_cpes()
241*4882a593Smuzhiyun            self.status['cpe'] = ("warning", "not checked against CPE dictionnary")
242*4882a593Smuzhiyun        else:
243*4882a593Smuzhiyun            self.status['cpe'] = ("error", "no verified CPE identifier")
244*4882a593Smuzhiyun
245*4882a593Smuzhiyun    def set_check_package_warnings(self):
246*4882a593Smuzhiyun        """
247*4882a593Smuzhiyun        Fills in the .warnings and .status['pkg-check'] fields
248*4882a593Smuzhiyun        """
249*4882a593Smuzhiyun        cmd = [os.path.join(brpath, "utils/check-package")]
250*4882a593Smuzhiyun        pkgdir = os.path.dirname(os.path.join(brpath, self.path))
251*4882a593Smuzhiyun        self.status['pkg-check'] = ("error", "Missing")
252*4882a593Smuzhiyun        for root, dirs, files in os.walk(pkgdir):
253*4882a593Smuzhiyun            for f in files:
254*4882a593Smuzhiyun                if f.endswith(".mk") or f.endswith(".hash") or f == "Config.in" or f == "Config.in.host":
255*4882a593Smuzhiyun                    cmd.append(os.path.join(root, f))
256*4882a593Smuzhiyun        o = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[1]
257*4882a593Smuzhiyun        lines = o.splitlines()
258*4882a593Smuzhiyun        for line in lines:
259*4882a593Smuzhiyun            m = re.match("^([0-9]*) warnings generated", line.decode())
260*4882a593Smuzhiyun            if m:
261*4882a593Smuzhiyun                self.warnings = int(m.group(1))
262*4882a593Smuzhiyun                if self.warnings == 0:
263*4882a593Smuzhiyun                    self.status['pkg-check'] = ("ok", "no warnings")
264*4882a593Smuzhiyun                else:
265*4882a593Smuzhiyun                    self.status['pkg-check'] = ("error", "{} warnings".format(self.warnings))
266*4882a593Smuzhiyun                return
267*4882a593Smuzhiyun
268*4882a593Smuzhiyun    def set_ignored_cves(self):
269*4882a593Smuzhiyun        """
270*4882a593Smuzhiyun        Give the list of CVEs ignored by the package
271*4882a593Smuzhiyun        """
272*4882a593Smuzhiyun        self.ignored_cves = list(self.all_ignored_cves.get(self.pkgvar(), []))
273*4882a593Smuzhiyun
274*4882a593Smuzhiyun    def set_developers(self, developers):
275*4882a593Smuzhiyun        """
276*4882a593Smuzhiyun        Fills in the .developers and .status['developers'] field
277*4882a593Smuzhiyun        """
278*4882a593Smuzhiyun        self.developers = [
279*4882a593Smuzhiyun            dev.name
280*4882a593Smuzhiyun            for dev in developers
281*4882a593Smuzhiyun            if dev.hasfile(self.path)
282*4882a593Smuzhiyun        ]
283*4882a593Smuzhiyun
284*4882a593Smuzhiyun        if self.developers:
285*4882a593Smuzhiyun            self.status['developers'] = ("ok", "{} developers".format(len(self.developers)))
286*4882a593Smuzhiyun        else:
287*4882a593Smuzhiyun            self.status['developers'] = ("warning", "no developers")
288*4882a593Smuzhiyun
289*4882a593Smuzhiyun    def is_status_ok(self, name):
290*4882a593Smuzhiyun        return name in self.status and self.status[name][0] == 'ok'
291*4882a593Smuzhiyun
292*4882a593Smuzhiyun    def is_status_error(self, name):
293*4882a593Smuzhiyun        return name in self.status and self.status[name][0] == 'error'
294*4882a593Smuzhiyun
295*4882a593Smuzhiyun    def is_status_na(self, name):
296*4882a593Smuzhiyun        return name in self.status and self.status[name][0] == 'na'
297*4882a593Smuzhiyun
298*4882a593Smuzhiyun    def __eq__(self, other):
299*4882a593Smuzhiyun        return self.path == other.path
300*4882a593Smuzhiyun
301*4882a593Smuzhiyun    def __lt__(self, other):
302*4882a593Smuzhiyun        return self.path < other.path
303*4882a593Smuzhiyun
304*4882a593Smuzhiyun    def __str__(self):
305*4882a593Smuzhiyun        return "%s (path='%s', license='%s', license_files='%s', hash='%s', patches=%d)" % \
306*4882a593Smuzhiyun            (self.name, self.path, self.is_status_ok('license'),
307*4882a593Smuzhiyun             self.is_status_ok('license-files'), self.status['hash'], self.patch_count)
308*4882a593Smuzhiyun
309*4882a593Smuzhiyun
310*4882a593Smuzhiyundef get_pkglist(npackages, package_list):
311*4882a593Smuzhiyun    """
312*4882a593Smuzhiyun    Builds the list of Buildroot packages, returning a list of Package
313*4882a593Smuzhiyun    objects. Only the .name and .path fields of the Package object are
314*4882a593Smuzhiyun    initialized.
315*4882a593Smuzhiyun
316*4882a593Smuzhiyun    npackages: limit to N packages
317*4882a593Smuzhiyun    package_list: limit to those packages in this list
318*4882a593Smuzhiyun    """
319*4882a593Smuzhiyun    WALK_USEFUL_SUBDIRS = ["boot", "linux", "package", "toolchain"]
320*4882a593Smuzhiyun    WALK_EXCLUDES = ["boot/common.mk",
321*4882a593Smuzhiyun                     "linux/linux-ext-.*.mk",
322*4882a593Smuzhiyun                     "package/freescale-imx/freescale-imx.mk",
323*4882a593Smuzhiyun                     "package/gcc/gcc.mk",
324*4882a593Smuzhiyun                     "package/gstreamer/gstreamer.mk",
325*4882a593Smuzhiyun                     "package/gstreamer1/gstreamer1.mk",
326*4882a593Smuzhiyun                     "package/gtk2-themes/gtk2-themes.mk",
327*4882a593Smuzhiyun                     "package/matchbox/matchbox.mk",
328*4882a593Smuzhiyun                     "package/opengl/opengl.mk",
329*4882a593Smuzhiyun                     "package/qt5/qt5.mk",
330*4882a593Smuzhiyun                     "package/x11r7/x11r7.mk",
331*4882a593Smuzhiyun                     "package/doc-asciidoc.mk",
332*4882a593Smuzhiyun                     "package/pkg-.*.mk",
333*4882a593Smuzhiyun                     "toolchain/toolchain-external/pkg-toolchain-external.mk",
334*4882a593Smuzhiyun                     "toolchain/toolchain-external/toolchain-external.mk",
335*4882a593Smuzhiyun                     "toolchain/toolchain.mk",
336*4882a593Smuzhiyun                     "toolchain/helpers.mk",
337*4882a593Smuzhiyun                     "toolchain/toolchain-wrapper.mk"]
338*4882a593Smuzhiyun    packages = list()
339*4882a593Smuzhiyun    count = 0
340*4882a593Smuzhiyun    for root, dirs, files in os.walk(brpath):
341*4882a593Smuzhiyun        root = os.path.relpath(root, brpath)
342*4882a593Smuzhiyun        rootdir = root.split("/")
343*4882a593Smuzhiyun        if len(rootdir) < 1:
344*4882a593Smuzhiyun            continue
345*4882a593Smuzhiyun        if rootdir[0] not in WALK_USEFUL_SUBDIRS:
346*4882a593Smuzhiyun            continue
347*4882a593Smuzhiyun        for f in files:
348*4882a593Smuzhiyun            if not f.endswith(".mk"):
349*4882a593Smuzhiyun                continue
350*4882a593Smuzhiyun            # Strip ending ".mk"
351*4882a593Smuzhiyun            pkgname = f[:-3]
352*4882a593Smuzhiyun            if package_list and pkgname not in package_list:
353*4882a593Smuzhiyun                continue
354*4882a593Smuzhiyun            pkgpath = os.path.join(root, f)
355*4882a593Smuzhiyun            skip = False
356*4882a593Smuzhiyun            for exclude in WALK_EXCLUDES:
357*4882a593Smuzhiyun                if re.match(exclude, pkgpath):
358*4882a593Smuzhiyun                    skip = True
359*4882a593Smuzhiyun                    continue
360*4882a593Smuzhiyun            if skip:
361*4882a593Smuzhiyun                continue
362*4882a593Smuzhiyun            p = Package(pkgname, pkgpath)
363*4882a593Smuzhiyun            packages.append(p)
364*4882a593Smuzhiyun            count += 1
365*4882a593Smuzhiyun            if npackages and count == npackages:
366*4882a593Smuzhiyun                return packages
367*4882a593Smuzhiyun    return packages
368*4882a593Smuzhiyun
369*4882a593Smuzhiyun
370*4882a593Smuzhiyundef get_config_packages():
371*4882a593Smuzhiyun    cmd = ["make", "--no-print-directory", "show-info"]
372*4882a593Smuzhiyun    js = json.loads(subprocess.check_output(cmd))
373*4882a593Smuzhiyun    return set([v["name"] for v in js.values()])
374*4882a593Smuzhiyun
375*4882a593Smuzhiyun
376*4882a593Smuzhiyundef package_init_make_info():
377*4882a593Smuzhiyun    # Fetch all variables at once
378*4882a593Smuzhiyun    variables = subprocess.check_output(["make", "BR2_HAVE_DOT_CONFIG=y", "-s", "printvars",
379*4882a593Smuzhiyun                                         "VARS=%_LICENSE %_LICENSE_FILES %_VERSION %_IGNORE_CVES %_CPE_ID"])
380*4882a593Smuzhiyun    variable_list = variables.decode().splitlines()
381*4882a593Smuzhiyun
382*4882a593Smuzhiyun    # We process first the host package VERSION, and then the target
383*4882a593Smuzhiyun    # package VERSION. This means that if a package exists in both
384*4882a593Smuzhiyun    # target and host variants, with different values (eg. version
385*4882a593Smuzhiyun    # numbers (unlikely)), we'll report the target one.
386*4882a593Smuzhiyun    variable_list = [x[5:] for x in variable_list if x.startswith("HOST_")] + \
387*4882a593Smuzhiyun                    [x for x in variable_list if not x.startswith("HOST_")]
388*4882a593Smuzhiyun
389*4882a593Smuzhiyun    for item in variable_list:
390*4882a593Smuzhiyun        # Get variable name and value
391*4882a593Smuzhiyun        pkgvar, value = item.split("=", maxsplit=1)
392*4882a593Smuzhiyun
393*4882a593Smuzhiyun        # Strip the suffix according to the variable
394*4882a593Smuzhiyun        if pkgvar.endswith("_LICENSE"):
395*4882a593Smuzhiyun            # If value is "unknown", no license details available
396*4882a593Smuzhiyun            if value == "unknown":
397*4882a593Smuzhiyun                continue
398*4882a593Smuzhiyun            pkgvar = pkgvar[:-8]
399*4882a593Smuzhiyun            Package.all_licenses[pkgvar] = value
400*4882a593Smuzhiyun
401*4882a593Smuzhiyun        elif pkgvar.endswith("_LICENSE_FILES"):
402*4882a593Smuzhiyun            if pkgvar.endswith("_MANIFEST_LICENSE_FILES"):
403*4882a593Smuzhiyun                continue
404*4882a593Smuzhiyun            pkgvar = pkgvar[:-14]
405*4882a593Smuzhiyun            Package.all_license_files.append(pkgvar)
406*4882a593Smuzhiyun
407*4882a593Smuzhiyun        elif pkgvar.endswith("_VERSION"):
408*4882a593Smuzhiyun            if pkgvar.endswith("_DL_VERSION"):
409*4882a593Smuzhiyun                continue
410*4882a593Smuzhiyun            pkgvar = pkgvar[:-8]
411*4882a593Smuzhiyun            Package.all_versions[pkgvar] = value
412*4882a593Smuzhiyun
413*4882a593Smuzhiyun        elif pkgvar.endswith("_IGNORE_CVES"):
414*4882a593Smuzhiyun            pkgvar = pkgvar[:-12]
415*4882a593Smuzhiyun            Package.all_ignored_cves[pkgvar] = value.split()
416*4882a593Smuzhiyun
417*4882a593Smuzhiyun        elif pkgvar.endswith("_CPE_ID"):
418*4882a593Smuzhiyun            pkgvar = pkgvar[:-7]
419*4882a593Smuzhiyun            Package.all_cpeids[pkgvar] = value
420*4882a593Smuzhiyun
421*4882a593Smuzhiyun
422*4882a593Smuzhiyuncheck_url_count = 0
423*4882a593Smuzhiyun
424*4882a593Smuzhiyun
425*4882a593Smuzhiyunasync def check_url_status(session, pkg, npkgs, retry=True):
426*4882a593Smuzhiyun    global check_url_count
427*4882a593Smuzhiyun
428*4882a593Smuzhiyun    try:
429*4882a593Smuzhiyun        async with session.get(pkg.url) as resp:
430*4882a593Smuzhiyun            if resp.status >= 400:
431*4882a593Smuzhiyun                pkg.status['url'] = ("error", "invalid {}".format(resp.status))
432*4882a593Smuzhiyun                check_url_count += 1
433*4882a593Smuzhiyun                print("[%04d/%04d] %s" % (check_url_count, npkgs, pkg.name))
434*4882a593Smuzhiyun                return
435*4882a593Smuzhiyun    except (aiohttp.ClientError, asyncio.TimeoutError):
436*4882a593Smuzhiyun        if retry:
437*4882a593Smuzhiyun            return await check_url_status(session, pkg, npkgs, retry=False)
438*4882a593Smuzhiyun        else:
439*4882a593Smuzhiyun            pkg.status['url'] = ("error", "invalid (err)")
440*4882a593Smuzhiyun            check_url_count += 1
441*4882a593Smuzhiyun            print("[%04d/%04d] %s" % (check_url_count, npkgs, pkg.name))
442*4882a593Smuzhiyun            return
443*4882a593Smuzhiyun
444*4882a593Smuzhiyun    pkg.status['url'] = ("ok", "valid")
445*4882a593Smuzhiyun    check_url_count += 1
446*4882a593Smuzhiyun    print("[%04d/%04d] %s" % (check_url_count, npkgs, pkg.name))
447*4882a593Smuzhiyun
448*4882a593Smuzhiyun
449*4882a593Smuzhiyunasync def check_package_urls(packages):
450*4882a593Smuzhiyun    tasks = []
451*4882a593Smuzhiyun    connector = aiohttp.TCPConnector(limit_per_host=5)
452*4882a593Smuzhiyun    async with aiohttp.ClientSession(connector=connector, trust_env=True) as sess:
453*4882a593Smuzhiyun        packages = [p for p in packages if p.status['url'][0] == 'ok']
454*4882a593Smuzhiyun        for pkg in packages:
455*4882a593Smuzhiyun            tasks.append(asyncio.ensure_future(check_url_status(sess, pkg, len(packages))))
456*4882a593Smuzhiyun        await asyncio.wait(tasks)
457*4882a593Smuzhiyun
458*4882a593Smuzhiyun
459*4882a593Smuzhiyundef check_package_latest_version_set_status(pkg, status, version, identifier):
460*4882a593Smuzhiyun    pkg.latest_version = {
461*4882a593Smuzhiyun        "status": status,
462*4882a593Smuzhiyun        "version": version,
463*4882a593Smuzhiyun        "id": identifier,
464*4882a593Smuzhiyun    }
465*4882a593Smuzhiyun
466*4882a593Smuzhiyun    if pkg.latest_version['status'] == RM_API_STATUS_ERROR:
467*4882a593Smuzhiyun        pkg.status['version'] = ('warning', "Release Monitoring API error")
468*4882a593Smuzhiyun    elif pkg.latest_version['status'] == RM_API_STATUS_NOT_FOUND:
469*4882a593Smuzhiyun        pkg.status['version'] = ('warning', "Package not found on Release Monitoring")
470*4882a593Smuzhiyun
471*4882a593Smuzhiyun    if pkg.latest_version['version'] is None:
472*4882a593Smuzhiyun        pkg.status['version'] = ('warning', "No upstream version available on Release Monitoring")
473*4882a593Smuzhiyun    elif pkg.latest_version['version'] != pkg.current_version:
474*4882a593Smuzhiyun        pkg.status['version'] = ('error', "The newer version {} is available upstream".format(pkg.latest_version['version']))
475*4882a593Smuzhiyun    else:
476*4882a593Smuzhiyun        pkg.status['version'] = ('ok', 'up-to-date')
477*4882a593Smuzhiyun
478*4882a593Smuzhiyun
479*4882a593Smuzhiyunasync def check_package_get_latest_version_by_distro(session, pkg, retry=True):
480*4882a593Smuzhiyun    url = "https://release-monitoring.org//api/project/Buildroot/%s" % pkg.name
481*4882a593Smuzhiyun    try:
482*4882a593Smuzhiyun        async with session.get(url) as resp:
483*4882a593Smuzhiyun            if resp.status != 200:
484*4882a593Smuzhiyun                return False
485*4882a593Smuzhiyun
486*4882a593Smuzhiyun            data = await resp.json()
487*4882a593Smuzhiyun            version = data['stable_versions'][0] if 'stable_versions' in data else data['version'] if 'version' in data else None
488*4882a593Smuzhiyun            check_package_latest_version_set_status(pkg,
489*4882a593Smuzhiyun                                                    RM_API_STATUS_FOUND_BY_DISTRO,
490*4882a593Smuzhiyun                                                    version,
491*4882a593Smuzhiyun                                                    data['id'])
492*4882a593Smuzhiyun            return True
493*4882a593Smuzhiyun
494*4882a593Smuzhiyun    except (aiohttp.ClientError, asyncio.TimeoutError):
495*4882a593Smuzhiyun        if retry:
496*4882a593Smuzhiyun            return await check_package_get_latest_version_by_distro(session, pkg, retry=False)
497*4882a593Smuzhiyun        else:
498*4882a593Smuzhiyun            return False
499*4882a593Smuzhiyun
500*4882a593Smuzhiyun
501*4882a593Smuzhiyunasync def check_package_get_latest_version_by_guess(session, pkg, retry=True):
502*4882a593Smuzhiyun    url = "https://release-monitoring.org/api/projects/?pattern=%s" % pkg.name
503*4882a593Smuzhiyun    try:
504*4882a593Smuzhiyun        async with session.get(url) as resp:
505*4882a593Smuzhiyun            if resp.status != 200:
506*4882a593Smuzhiyun                return False
507*4882a593Smuzhiyun
508*4882a593Smuzhiyun            data = await resp.json()
509*4882a593Smuzhiyun            # filter projects that have the right name and a version defined
510*4882a593Smuzhiyun            projects = [p for p in data['projects'] if p['name'] == pkg.name and 'stable_versions' in p]
511*4882a593Smuzhiyun            projects.sort(key=lambda x: x['id'])
512*4882a593Smuzhiyun
513*4882a593Smuzhiyun            if len(projects) > 0:
514*4882a593Smuzhiyun                check_package_latest_version_set_status(pkg,
515*4882a593Smuzhiyun                                                        RM_API_STATUS_FOUND_BY_PATTERN,
516*4882a593Smuzhiyun                                                        projects[0]['stable_versions'][0],
517*4882a593Smuzhiyun                                                        projects[0]['id'])
518*4882a593Smuzhiyun                return True
519*4882a593Smuzhiyun
520*4882a593Smuzhiyun    except (aiohttp.ClientError, asyncio.TimeoutError):
521*4882a593Smuzhiyun        if retry:
522*4882a593Smuzhiyun            return await check_package_get_latest_version_by_guess(session, pkg, retry=False)
523*4882a593Smuzhiyun        else:
524*4882a593Smuzhiyun            return False
525*4882a593Smuzhiyun
526*4882a593Smuzhiyun
527*4882a593Smuzhiyuncheck_latest_count = 0
528*4882a593Smuzhiyun
529*4882a593Smuzhiyun
530*4882a593Smuzhiyunasync def check_package_latest_version_get(session, pkg, npkgs):
531*4882a593Smuzhiyun    global check_latest_count
532*4882a593Smuzhiyun
533*4882a593Smuzhiyun    if await check_package_get_latest_version_by_distro(session, pkg):
534*4882a593Smuzhiyun        check_latest_count += 1
535*4882a593Smuzhiyun        print("[%04d/%04d] %s" % (check_latest_count, npkgs, pkg.name))
536*4882a593Smuzhiyun        return
537*4882a593Smuzhiyun
538*4882a593Smuzhiyun    if await check_package_get_latest_version_by_guess(session, pkg):
539*4882a593Smuzhiyun        check_latest_count += 1
540*4882a593Smuzhiyun        print("[%04d/%04d] %s" % (check_latest_count, npkgs, pkg.name))
541*4882a593Smuzhiyun        return
542*4882a593Smuzhiyun
543*4882a593Smuzhiyun    check_package_latest_version_set_status(pkg,
544*4882a593Smuzhiyun                                            RM_API_STATUS_NOT_FOUND,
545*4882a593Smuzhiyun                                            None, None)
546*4882a593Smuzhiyun    check_latest_count += 1
547*4882a593Smuzhiyun    print("[%04d/%04d] %s" % (check_latest_count, npkgs, pkg.name))
548*4882a593Smuzhiyun
549*4882a593Smuzhiyun
550*4882a593Smuzhiyunasync def check_package_latest_version(packages):
551*4882a593Smuzhiyun    """
552*4882a593Smuzhiyun    Fills in the .latest_version field of all Package objects
553*4882a593Smuzhiyun
554*4882a593Smuzhiyun    This field is a dict and has the following keys:
555*4882a593Smuzhiyun
556*4882a593Smuzhiyun    - status: one of RM_API_STATUS_ERROR,
557*4882a593Smuzhiyun      RM_API_STATUS_FOUND_BY_DISTRO, RM_API_STATUS_FOUND_BY_PATTERN,
558*4882a593Smuzhiyun      RM_API_STATUS_NOT_FOUND
559*4882a593Smuzhiyun    - version: string containing the latest version known by
560*4882a593Smuzhiyun      release-monitoring.org for this package
561*4882a593Smuzhiyun    - id: string containing the id of the project corresponding to this
562*4882a593Smuzhiyun      package, as known by release-monitoring.org
563*4882a593Smuzhiyun    """
564*4882a593Smuzhiyun
565*4882a593Smuzhiyun    for pkg in [p for p in packages if not p.is_actual_package]:
566*4882a593Smuzhiyun        pkg.status['version'] = ("na", "no valid package infra")
567*4882a593Smuzhiyun
568*4882a593Smuzhiyun    tasks = []
569*4882a593Smuzhiyun    connector = aiohttp.TCPConnector(limit_per_host=5)
570*4882a593Smuzhiyun    async with aiohttp.ClientSession(connector=connector, trust_env=True) as sess:
571*4882a593Smuzhiyun        packages = [p for p in packages if p.is_actual_package]
572*4882a593Smuzhiyun        for pkg in packages:
573*4882a593Smuzhiyun            tasks.append(asyncio.ensure_future(check_package_latest_version_get(sess, pkg, len(packages))))
574*4882a593Smuzhiyun        await asyncio.wait(tasks)
575*4882a593Smuzhiyun
576*4882a593Smuzhiyun
577*4882a593Smuzhiyundef check_package_cve_affects(cve, cpe_product_pkgs):
578*4882a593Smuzhiyun    for product in cve.affected_products:
579*4882a593Smuzhiyun        if product not in cpe_product_pkgs:
580*4882a593Smuzhiyun            continue
581*4882a593Smuzhiyun        for pkg in cpe_product_pkgs[product]:
582*4882a593Smuzhiyun            if cve.affects(pkg.name, pkg.current_version, pkg.ignored_cves, pkg.cpeid) == cve.CVE_AFFECTS:
583*4882a593Smuzhiyun                pkg.cves.append(cve.identifier)
584*4882a593Smuzhiyun
585*4882a593Smuzhiyun
586*4882a593Smuzhiyundef check_package_cves(nvd_path, packages):
587*4882a593Smuzhiyun    if not os.path.isdir(nvd_path):
588*4882a593Smuzhiyun        os.makedirs(nvd_path)
589*4882a593Smuzhiyun
590*4882a593Smuzhiyun    cpe_product_pkgs = defaultdict(list)
591*4882a593Smuzhiyun    for pkg in packages:
592*4882a593Smuzhiyun        if not pkg.is_actual_package:
593*4882a593Smuzhiyun            pkg.status['cve'] = ("na", "N/A")
594*4882a593Smuzhiyun            continue
595*4882a593Smuzhiyun        if not pkg.current_version:
596*4882a593Smuzhiyun            pkg.status['cve'] = ("na", "no version information available")
597*4882a593Smuzhiyun            continue
598*4882a593Smuzhiyun        if pkg.cpeid:
599*4882a593Smuzhiyun            cpe_product = cvecheck.cpe_product(pkg.cpeid)
600*4882a593Smuzhiyun            cpe_product_pkgs[cpe_product].append(pkg)
601*4882a593Smuzhiyun        else:
602*4882a593Smuzhiyun            cpe_product_pkgs[pkg.name].append(pkg)
603*4882a593Smuzhiyun
604*4882a593Smuzhiyun    for cve in cvecheck.CVE.read_nvd_dir(nvd_path):
605*4882a593Smuzhiyun        check_package_cve_affects(cve, cpe_product_pkgs)
606*4882a593Smuzhiyun
607*4882a593Smuzhiyun    for pkg in packages:
608*4882a593Smuzhiyun        if 'cve' not in pkg.status:
609*4882a593Smuzhiyun            if pkg.cves:
610*4882a593Smuzhiyun                pkg.status['cve'] = ("error", "affected by CVEs")
611*4882a593Smuzhiyun            else:
612*4882a593Smuzhiyun                pkg.status['cve'] = ("ok", "not affected by CVEs")
613*4882a593Smuzhiyun
614*4882a593Smuzhiyun
615*4882a593Smuzhiyundef check_package_cpes(nvd_path, packages):
616*4882a593Smuzhiyun    cpedb = CPEDB(nvd_path)
617*4882a593Smuzhiyun    cpedb.get_xml_dict()
618*4882a593Smuzhiyun    for p in packages:
619*4882a593Smuzhiyun        if not p.cpeid:
620*4882a593Smuzhiyun            continue
621*4882a593Smuzhiyun        if cpedb.find(p.cpeid):
622*4882a593Smuzhiyun            p.status['cpe'] = ("ok", "verified CPE identifier")
623*4882a593Smuzhiyun        else:
624*4882a593Smuzhiyun            p.status['cpe'] = ("error", "CPE version unknown in CPE database")
625*4882a593Smuzhiyun
626*4882a593Smuzhiyun
627*4882a593Smuzhiyundef calculate_stats(packages):
628*4882a593Smuzhiyun    stats = defaultdict(int)
629*4882a593Smuzhiyun    stats['packages'] = len(packages)
630*4882a593Smuzhiyun    for pkg in packages:
631*4882a593Smuzhiyun        # If packages have multiple infra, take the first one. For the
632*4882a593Smuzhiyun        # vast majority of packages, the target and host infra are the
633*4882a593Smuzhiyun        # same. There are very few packages that use a different infra
634*4882a593Smuzhiyun        # for the host and target variants.
635*4882a593Smuzhiyun        if len(pkg.infras) > 0:
636*4882a593Smuzhiyun            infra = pkg.infras[0][1]
637*4882a593Smuzhiyun            stats["infra-%s" % infra] += 1
638*4882a593Smuzhiyun        else:
639*4882a593Smuzhiyun            stats["infra-unknown"] += 1
640*4882a593Smuzhiyun        if pkg.is_status_ok('license'):
641*4882a593Smuzhiyun            stats["license"] += 1
642*4882a593Smuzhiyun        else:
643*4882a593Smuzhiyun            stats["no-license"] += 1
644*4882a593Smuzhiyun        if pkg.is_status_ok('license-files'):
645*4882a593Smuzhiyun            stats["license-files"] += 1
646*4882a593Smuzhiyun        else:
647*4882a593Smuzhiyun            stats["no-license-files"] += 1
648*4882a593Smuzhiyun        if pkg.is_status_ok('hash'):
649*4882a593Smuzhiyun            stats["hash"] += 1
650*4882a593Smuzhiyun        else:
651*4882a593Smuzhiyun            stats["no-hash"] += 1
652*4882a593Smuzhiyun        if pkg.latest_version['status'] == RM_API_STATUS_FOUND_BY_DISTRO:
653*4882a593Smuzhiyun            stats["rmo-mapping"] += 1
654*4882a593Smuzhiyun        else:
655*4882a593Smuzhiyun            stats["rmo-no-mapping"] += 1
656*4882a593Smuzhiyun        if not pkg.latest_version['version']:
657*4882a593Smuzhiyun            stats["version-unknown"] += 1
658*4882a593Smuzhiyun        elif pkg.latest_version['version'] == pkg.current_version:
659*4882a593Smuzhiyun            stats["version-uptodate"] += 1
660*4882a593Smuzhiyun        else:
661*4882a593Smuzhiyun            stats["version-not-uptodate"] += 1
662*4882a593Smuzhiyun        stats["patches"] += pkg.patch_count
663*4882a593Smuzhiyun        stats["total-cves"] += len(pkg.cves)
664*4882a593Smuzhiyun        if len(pkg.cves) != 0:
665*4882a593Smuzhiyun            stats["pkg-cves"] += 1
666*4882a593Smuzhiyun        if pkg.cpeid:
667*4882a593Smuzhiyun            stats["cpe-id"] += 1
668*4882a593Smuzhiyun        else:
669*4882a593Smuzhiyun            stats["no-cpe-id"] += 1
670*4882a593Smuzhiyun    return stats
671*4882a593Smuzhiyun
672*4882a593Smuzhiyun
673*4882a593Smuzhiyunhtml_header = """
674*4882a593Smuzhiyun<head>
675*4882a593Smuzhiyun<script src=\"https://www.kryogenix.org/code/browser/sorttable/sorttable.js\"></script>
676*4882a593Smuzhiyun<style type=\"text/css\">
677*4882a593Smuzhiyuntable {
678*4882a593Smuzhiyun  width: 100%;
679*4882a593Smuzhiyun}
680*4882a593Smuzhiyuntd {
681*4882a593Smuzhiyun  border: 1px solid black;
682*4882a593Smuzhiyun}
683*4882a593Smuzhiyuntd.centered {
684*4882a593Smuzhiyun  text-align: center;
685*4882a593Smuzhiyun}
686*4882a593Smuzhiyuntd.wrong {
687*4882a593Smuzhiyun  background: #ff9a69;
688*4882a593Smuzhiyun}
689*4882a593Smuzhiyuntd.correct {
690*4882a593Smuzhiyun  background: #d2ffc4;
691*4882a593Smuzhiyun}
692*4882a593Smuzhiyuntd.nopatches {
693*4882a593Smuzhiyun  background: #d2ffc4;
694*4882a593Smuzhiyun}
695*4882a593Smuzhiyuntd.somepatches {
696*4882a593Smuzhiyun  background: #ffd870;
697*4882a593Smuzhiyun}
698*4882a593Smuzhiyuntd.lotsofpatches {
699*4882a593Smuzhiyun  background: #ff9a69;
700*4882a593Smuzhiyun}
701*4882a593Smuzhiyun
702*4882a593Smuzhiyuntd.good_url {
703*4882a593Smuzhiyun  background: #d2ffc4;
704*4882a593Smuzhiyun}
705*4882a593Smuzhiyuntd.missing_url {
706*4882a593Smuzhiyun  background: #ffd870;
707*4882a593Smuzhiyun}
708*4882a593Smuzhiyuntd.invalid_url {
709*4882a593Smuzhiyun  background: #ff9a69;
710*4882a593Smuzhiyun}
711*4882a593Smuzhiyun
712*4882a593Smuzhiyuntd.version-good {
713*4882a593Smuzhiyun  background: #d2ffc4;
714*4882a593Smuzhiyun}
715*4882a593Smuzhiyuntd.version-needs-update {
716*4882a593Smuzhiyun  background: #ff9a69;
717*4882a593Smuzhiyun}
718*4882a593Smuzhiyuntd.version-unknown {
719*4882a593Smuzhiyun background: #ffd870;
720*4882a593Smuzhiyun}
721*4882a593Smuzhiyuntd.version-error {
722*4882a593Smuzhiyun background: #ccc;
723*4882a593Smuzhiyun}
724*4882a593Smuzhiyun
725*4882a593Smuzhiyuntd.cpe-ok {
726*4882a593Smuzhiyun  background: #d2ffc4;
727*4882a593Smuzhiyun}
728*4882a593Smuzhiyun
729*4882a593Smuzhiyuntd.cpe-nok {
730*4882a593Smuzhiyun  background: #ff9a69;
731*4882a593Smuzhiyun}
732*4882a593Smuzhiyun
733*4882a593Smuzhiyuntd.cpe-unknown {
734*4882a593Smuzhiyun background: #ffd870;
735*4882a593Smuzhiyun}
736*4882a593Smuzhiyun
737*4882a593Smuzhiyuntd.cve-ok {
738*4882a593Smuzhiyun  background: #d2ffc4;
739*4882a593Smuzhiyun}
740*4882a593Smuzhiyun
741*4882a593Smuzhiyuntd.cve-nok {
742*4882a593Smuzhiyun  background: #ff9a69;
743*4882a593Smuzhiyun}
744*4882a593Smuzhiyun
745*4882a593Smuzhiyuntd.cve-unknown {
746*4882a593Smuzhiyun background: #ffd870;
747*4882a593Smuzhiyun}
748*4882a593Smuzhiyun
749*4882a593Smuzhiyuntd.cve_ignored {
750*4882a593Smuzhiyun background: #ccc;
751*4882a593Smuzhiyun}
752*4882a593Smuzhiyun
753*4882a593Smuzhiyun</style>
754*4882a593Smuzhiyun<title>Statistics of Buildroot packages</title>
755*4882a593Smuzhiyun</head>
756*4882a593Smuzhiyun
757*4882a593Smuzhiyun<a href=\"#results\">Results</a><br/>
758*4882a593Smuzhiyun
759*4882a593Smuzhiyun<p id=\"sortable_hint\"></p>
760*4882a593Smuzhiyun"""
761*4882a593Smuzhiyun
762*4882a593Smuzhiyun
763*4882a593Smuzhiyunhtml_footer = """
764*4882a593Smuzhiyun</body>
765*4882a593Smuzhiyun<script>
766*4882a593Smuzhiyunif (typeof sorttable === \"object\") {
767*4882a593Smuzhiyun  document.getElementById(\"sortable_hint\").innerHTML =
768*4882a593Smuzhiyun  \"hint: the table can be sorted by clicking the column headers\"
769*4882a593Smuzhiyun}
770*4882a593Smuzhiyun</script>
771*4882a593Smuzhiyun</html>
772*4882a593Smuzhiyun"""
773*4882a593Smuzhiyun
774*4882a593Smuzhiyun
775*4882a593Smuzhiyundef infra_str(infra_list):
776*4882a593Smuzhiyun    if not infra_list:
777*4882a593Smuzhiyun        return "Unknown"
778*4882a593Smuzhiyun    elif len(infra_list) == 1:
779*4882a593Smuzhiyun        return "<b>%s</b><br/>%s" % (infra_list[0][1], infra_list[0][0])
780*4882a593Smuzhiyun    elif infra_list[0][1] == infra_list[1][1]:
781*4882a593Smuzhiyun        return "<b>%s</b><br/>%s + %s" % \
782*4882a593Smuzhiyun            (infra_list[0][1], infra_list[0][0], infra_list[1][0])
783*4882a593Smuzhiyun    else:
784*4882a593Smuzhiyun        return "<b>%s</b> (%s)<br/><b>%s</b> (%s)" % \
785*4882a593Smuzhiyun            (infra_list[0][1], infra_list[0][0],
786*4882a593Smuzhiyun             infra_list[1][1], infra_list[1][0])
787*4882a593Smuzhiyun
788*4882a593Smuzhiyun
789*4882a593Smuzhiyundef boolean_str(b):
790*4882a593Smuzhiyun    if b:
791*4882a593Smuzhiyun        return "Yes"
792*4882a593Smuzhiyun    else:
793*4882a593Smuzhiyun        return "No"
794*4882a593Smuzhiyun
795*4882a593Smuzhiyun
796*4882a593Smuzhiyundef dump_html_pkg(f, pkg):
797*4882a593Smuzhiyun    f.write(" <tr>\n")
798*4882a593Smuzhiyun    f.write("  <td>%s</td>\n" % pkg.path)
799*4882a593Smuzhiyun
800*4882a593Smuzhiyun    # Patch count
801*4882a593Smuzhiyun    td_class = ["centered"]
802*4882a593Smuzhiyun    if pkg.patch_count == 0:
803*4882a593Smuzhiyun        td_class.append("nopatches")
804*4882a593Smuzhiyun    elif pkg.patch_count < 5:
805*4882a593Smuzhiyun        td_class.append("somepatches")
806*4882a593Smuzhiyun    else:
807*4882a593Smuzhiyun        td_class.append("lotsofpatches")
808*4882a593Smuzhiyun    f.write("  <td class=\"%s\">%s</td>\n" %
809*4882a593Smuzhiyun            (" ".join(td_class), str(pkg.patch_count)))
810*4882a593Smuzhiyun
811*4882a593Smuzhiyun    # Infrastructure
812*4882a593Smuzhiyun    infra = infra_str(pkg.infras)
813*4882a593Smuzhiyun    td_class = ["centered"]
814*4882a593Smuzhiyun    if infra == "Unknown":
815*4882a593Smuzhiyun        td_class.append("wrong")
816*4882a593Smuzhiyun    else:
817*4882a593Smuzhiyun        td_class.append("correct")
818*4882a593Smuzhiyun    f.write("  <td class=\"%s\">%s</td>\n" %
819*4882a593Smuzhiyun            (" ".join(td_class), infra_str(pkg.infras)))
820*4882a593Smuzhiyun
821*4882a593Smuzhiyun    # License
822*4882a593Smuzhiyun    td_class = ["centered"]
823*4882a593Smuzhiyun    if pkg.is_status_ok('license'):
824*4882a593Smuzhiyun        td_class.append("correct")
825*4882a593Smuzhiyun    else:
826*4882a593Smuzhiyun        td_class.append("wrong")
827*4882a593Smuzhiyun    f.write("  <td class=\"%s\">%s</td>\n" %
828*4882a593Smuzhiyun            (" ".join(td_class), boolean_str(pkg.is_status_ok('license'))))
829*4882a593Smuzhiyun
830*4882a593Smuzhiyun    # License files
831*4882a593Smuzhiyun    td_class = ["centered"]
832*4882a593Smuzhiyun    if pkg.is_status_ok('license-files'):
833*4882a593Smuzhiyun        td_class.append("correct")
834*4882a593Smuzhiyun    else:
835*4882a593Smuzhiyun        td_class.append("wrong")
836*4882a593Smuzhiyun    f.write("  <td class=\"%s\">%s</td>\n" %
837*4882a593Smuzhiyun            (" ".join(td_class), boolean_str(pkg.is_status_ok('license-files'))))
838*4882a593Smuzhiyun
839*4882a593Smuzhiyun    # Hash
840*4882a593Smuzhiyun    td_class = ["centered"]
841*4882a593Smuzhiyun    if pkg.is_status_ok('hash'):
842*4882a593Smuzhiyun        td_class.append("correct")
843*4882a593Smuzhiyun    else:
844*4882a593Smuzhiyun        td_class.append("wrong")
845*4882a593Smuzhiyun    f.write("  <td class=\"%s\">%s</td>\n" %
846*4882a593Smuzhiyun            (" ".join(td_class), boolean_str(pkg.is_status_ok('hash'))))
847*4882a593Smuzhiyun
848*4882a593Smuzhiyun    # Current version
849*4882a593Smuzhiyun    if len(pkg.current_version) > 20:
850*4882a593Smuzhiyun        current_version = pkg.current_version[:20] + "..."
851*4882a593Smuzhiyun    else:
852*4882a593Smuzhiyun        current_version = pkg.current_version
853*4882a593Smuzhiyun    f.write("  <td class=\"centered\">%s</td>\n" % current_version)
854*4882a593Smuzhiyun
855*4882a593Smuzhiyun    # Latest version
856*4882a593Smuzhiyun    if pkg.latest_version['status'] == RM_API_STATUS_ERROR:
857*4882a593Smuzhiyun        td_class.append("version-error")
858*4882a593Smuzhiyun    if pkg.latest_version['version'] is None:
859*4882a593Smuzhiyun        td_class.append("version-unknown")
860*4882a593Smuzhiyun    elif pkg.latest_version['version'] != pkg.current_version:
861*4882a593Smuzhiyun        td_class.append("version-needs-update")
862*4882a593Smuzhiyun    else:
863*4882a593Smuzhiyun        td_class.append("version-good")
864*4882a593Smuzhiyun
865*4882a593Smuzhiyun    if pkg.latest_version['status'] == RM_API_STATUS_ERROR:
866*4882a593Smuzhiyun        latest_version_text = "<b>Error</b>"
867*4882a593Smuzhiyun    elif pkg.latest_version['status'] == RM_API_STATUS_NOT_FOUND:
868*4882a593Smuzhiyun        latest_version_text = "<b>Not found</b>"
869*4882a593Smuzhiyun    else:
870*4882a593Smuzhiyun        if pkg.latest_version['version'] is None:
871*4882a593Smuzhiyun            latest_version_text = "<b>Found, but no version</b>"
872*4882a593Smuzhiyun        else:
873*4882a593Smuzhiyun            latest_version_text = "<a href=\"https://release-monitoring.org/project/%s\"><b>%s</b></a>" % \
874*4882a593Smuzhiyun                (pkg.latest_version['id'], str(pkg.latest_version['version']))
875*4882a593Smuzhiyun
876*4882a593Smuzhiyun        latest_version_text += "<br/>"
877*4882a593Smuzhiyun
878*4882a593Smuzhiyun        if pkg.latest_version['status'] == RM_API_STATUS_FOUND_BY_DISTRO:
879*4882a593Smuzhiyun            latest_version_text += "found by <a href=\"https://release-monitoring.org/distro/Buildroot/\">distro</a>"
880*4882a593Smuzhiyun        else:
881*4882a593Smuzhiyun            latest_version_text += "found by guess"
882*4882a593Smuzhiyun
883*4882a593Smuzhiyun    f.write("  <td class=\"%s\">%s</td>\n" %
884*4882a593Smuzhiyun            (" ".join(td_class), latest_version_text))
885*4882a593Smuzhiyun
886*4882a593Smuzhiyun    # Warnings
887*4882a593Smuzhiyun    td_class = ["centered"]
888*4882a593Smuzhiyun    if pkg.warnings == 0:
889*4882a593Smuzhiyun        td_class.append("correct")
890*4882a593Smuzhiyun    else:
891*4882a593Smuzhiyun        td_class.append("wrong")
892*4882a593Smuzhiyun    f.write("  <td class=\"%s\">%d</td>\n" %
893*4882a593Smuzhiyun            (" ".join(td_class), pkg.warnings))
894*4882a593Smuzhiyun
895*4882a593Smuzhiyun    # URL status
896*4882a593Smuzhiyun    td_class = ["centered"]
897*4882a593Smuzhiyun    url_str = pkg.status['url'][1]
898*4882a593Smuzhiyun    if pkg.status['url'][0] in ("error", "warning"):
899*4882a593Smuzhiyun        td_class.append("missing_url")
900*4882a593Smuzhiyun    if pkg.status['url'][0] == "error":
901*4882a593Smuzhiyun        td_class.append("invalid_url")
902*4882a593Smuzhiyun        url_str = "<a href=%s>%s</a>" % (pkg.url, pkg.status['url'][1])
903*4882a593Smuzhiyun    else:
904*4882a593Smuzhiyun        td_class.append("good_url")
905*4882a593Smuzhiyun        url_str = "<a href=%s>Link</a>" % pkg.url
906*4882a593Smuzhiyun    f.write("  <td class=\"%s\">%s</td>\n" %
907*4882a593Smuzhiyun            (" ".join(td_class), url_str))
908*4882a593Smuzhiyun
909*4882a593Smuzhiyun    # CVEs
910*4882a593Smuzhiyun    td_class = ["centered"]
911*4882a593Smuzhiyun    if pkg.is_status_ok("cve"):
912*4882a593Smuzhiyun        td_class.append("cve-ok")
913*4882a593Smuzhiyun    elif pkg.is_status_error("cve"):
914*4882a593Smuzhiyun        td_class.append("cve-nok")
915*4882a593Smuzhiyun    elif pkg.is_status_na("cve") and not pkg.is_actual_package:
916*4882a593Smuzhiyun        td_class.append("cve-ok")
917*4882a593Smuzhiyun    else:
918*4882a593Smuzhiyun        td_class.append("cve-unknown")
919*4882a593Smuzhiyun    f.write("  <td class=\"%s\">\n" % " ".join(td_class))
920*4882a593Smuzhiyun    if pkg.is_status_error("cve"):
921*4882a593Smuzhiyun        for cve in pkg.cves:
922*4882a593Smuzhiyun            f.write("   <a href=\"https://security-tracker.debian.org/tracker/%s\">%s<br/>\n" % (cve, cve))
923*4882a593Smuzhiyun    elif pkg.is_status_na("cve"):
924*4882a593Smuzhiyun        f.write("    %s" % pkg.status['cve'][1])
925*4882a593Smuzhiyun    else:
926*4882a593Smuzhiyun        f.write("    N/A\n")
927*4882a593Smuzhiyun    f.write("  </td>\n")
928*4882a593Smuzhiyun
929*4882a593Smuzhiyun    # CVEs Ignored
930*4882a593Smuzhiyun    td_class = ["centered"]
931*4882a593Smuzhiyun    if pkg.ignored_cves:
932*4882a593Smuzhiyun        td_class.append("cve_ignored")
933*4882a593Smuzhiyun    f.write("  <td class=\"%s\">\n" % " ".join(td_class))
934*4882a593Smuzhiyun    for ignored_cve in pkg.ignored_cves:
935*4882a593Smuzhiyun        f.write("    <a href=\"https://security-tracker.debian.org/tracker/%s\">%s<br/>\n" % (ignored_cve, ignored_cve))
936*4882a593Smuzhiyun    f.write("  </td>\n")
937*4882a593Smuzhiyun
938*4882a593Smuzhiyun    # CPE ID
939*4882a593Smuzhiyun    td_class = ["left"]
940*4882a593Smuzhiyun    if pkg.is_status_ok("cpe"):
941*4882a593Smuzhiyun        td_class.append("cpe-ok")
942*4882a593Smuzhiyun    elif pkg.is_status_error("cpe"):
943*4882a593Smuzhiyun        td_class.append("cpe-nok")
944*4882a593Smuzhiyun    elif pkg.is_status_na("cpe") and not pkg.is_actual_package:
945*4882a593Smuzhiyun        td_class.append("cpe-ok")
946*4882a593Smuzhiyun    else:
947*4882a593Smuzhiyun        td_class.append("cpe-unknown")
948*4882a593Smuzhiyun    f.write("  <td class=\"%s\">\n" % " ".join(td_class))
949*4882a593Smuzhiyun    if pkg.cpeid:
950*4882a593Smuzhiyun        f.write("  <code>%s</code>\n" % pkg.cpeid)
951*4882a593Smuzhiyun    if not pkg.is_status_ok("cpe"):
952*4882a593Smuzhiyun        if pkg.is_actual_package and pkg.current_version:
953*4882a593Smuzhiyun            if pkg.cpeid:
954*4882a593Smuzhiyun                f.write("  <br/>%s <a href=\"https://nvd.nist.gov/products/cpe/search/results?namingFormat=2.3&keyword=%s\">(Search)</a>\n" %  # noqa: E501
955*4882a593Smuzhiyun                        (pkg.status['cpe'][1], ":".join(pkg.cpeid.split(":")[0:5])))
956*4882a593Smuzhiyun            else:
957*4882a593Smuzhiyun                f.write("  %s <a href=\"https://nvd.nist.gov/products/cpe/search/results?namingFormat=2.3&keyword=%s\">(Search)</a>\n" %  # noqa: E501
958*4882a593Smuzhiyun                        (pkg.status['cpe'][1], pkg.name))
959*4882a593Smuzhiyun        else:
960*4882a593Smuzhiyun            f.write("  %s\n" % pkg.status['cpe'][1])
961*4882a593Smuzhiyun
962*4882a593Smuzhiyun    f.write("  </td>\n")
963*4882a593Smuzhiyun
964*4882a593Smuzhiyun    f.write(" </tr>\n")
965*4882a593Smuzhiyun
966*4882a593Smuzhiyun
967*4882a593Smuzhiyundef dump_html_all_pkgs(f, packages):
968*4882a593Smuzhiyun    f.write("""
969*4882a593Smuzhiyun<table class=\"sortable\">
970*4882a593Smuzhiyun<tr>
971*4882a593Smuzhiyun<td>Package</td>
972*4882a593Smuzhiyun<td class=\"centered\">Patch count</td>
973*4882a593Smuzhiyun<td class=\"centered\">Infrastructure</td>
974*4882a593Smuzhiyun<td class=\"centered\">License</td>
975*4882a593Smuzhiyun<td class=\"centered\">License files</td>
976*4882a593Smuzhiyun<td class=\"centered\">Hash file</td>
977*4882a593Smuzhiyun<td class=\"centered\">Current version</td>
978*4882a593Smuzhiyun<td class=\"centered\">Latest version</td>
979*4882a593Smuzhiyun<td class=\"centered\">Warnings</td>
980*4882a593Smuzhiyun<td class=\"centered\">Upstream URL</td>
981*4882a593Smuzhiyun<td class=\"centered\">CVEs</td>
982*4882a593Smuzhiyun<td class=\"centered\">CVEs Ignored</td>
983*4882a593Smuzhiyun<td class=\"centered\">CPE ID</td>
984*4882a593Smuzhiyun</tr>
985*4882a593Smuzhiyun""")
986*4882a593Smuzhiyun    for pkg in sorted(packages):
987*4882a593Smuzhiyun        dump_html_pkg(f, pkg)
988*4882a593Smuzhiyun    f.write("</table>")
989*4882a593Smuzhiyun
990*4882a593Smuzhiyun
991*4882a593Smuzhiyundef dump_html_stats(f, stats):
992*4882a593Smuzhiyun    f.write("<a id=\"results\"></a>\n")
993*4882a593Smuzhiyun    f.write("<table>\n")
994*4882a593Smuzhiyun    infras = [infra[6:] for infra in stats.keys() if infra.startswith("infra-")]
995*4882a593Smuzhiyun    for infra in infras:
996*4882a593Smuzhiyun        f.write(" <tr><td>Packages using the <i>%s</i> infrastructure</td><td>%s</td></tr>\n" %
997*4882a593Smuzhiyun                (infra, stats["infra-%s" % infra]))
998*4882a593Smuzhiyun    f.write(" <tr><td>Packages having license information</td><td>%s</td></tr>\n" %
999*4882a593Smuzhiyun            stats["license"])
1000*4882a593Smuzhiyun    f.write(" <tr><td>Packages not having license information</td><td>%s</td></tr>\n" %
1001*4882a593Smuzhiyun            stats["no-license"])
1002*4882a593Smuzhiyun    f.write(" <tr><td>Packages having license files information</td><td>%s</td></tr>\n" %
1003*4882a593Smuzhiyun            stats["license-files"])
1004*4882a593Smuzhiyun    f.write(" <tr><td>Packages not having license files information</td><td>%s</td></tr>\n" %
1005*4882a593Smuzhiyun            stats["no-license-files"])
1006*4882a593Smuzhiyun    f.write(" <tr><td>Packages having a hash file</td><td>%s</td></tr>\n" %
1007*4882a593Smuzhiyun            stats["hash"])
1008*4882a593Smuzhiyun    f.write(" <tr><td>Packages not having a hash file</td><td>%s</td></tr>\n" %
1009*4882a593Smuzhiyun            stats["no-hash"])
1010*4882a593Smuzhiyun    f.write(" <tr><td>Total number of patches</td><td>%s</td></tr>\n" %
1011*4882a593Smuzhiyun            stats["patches"])
1012*4882a593Smuzhiyun    f.write("<tr><td>Packages having a mapping on <i>release-monitoring.org</i></td><td>%s</td></tr>\n" %
1013*4882a593Smuzhiyun            stats["rmo-mapping"])
1014*4882a593Smuzhiyun    f.write("<tr><td>Packages lacking a mapping on <i>release-monitoring.org</i></td><td>%s</td></tr>\n" %
1015*4882a593Smuzhiyun            stats["rmo-no-mapping"])
1016*4882a593Smuzhiyun    f.write("<tr><td>Packages that are up-to-date</td><td>%s</td></tr>\n" %
1017*4882a593Smuzhiyun            stats["version-uptodate"])
1018*4882a593Smuzhiyun    f.write("<tr><td>Packages that are not up-to-date</td><td>%s</td></tr>\n" %
1019*4882a593Smuzhiyun            stats["version-not-uptodate"])
1020*4882a593Smuzhiyun    f.write("<tr><td>Packages with no known upstream version</td><td>%s</td></tr>\n" %
1021*4882a593Smuzhiyun            stats["version-unknown"])
1022*4882a593Smuzhiyun    f.write("<tr><td>Packages affected by CVEs</td><td>%s</td></tr>\n" %
1023*4882a593Smuzhiyun            stats["pkg-cves"])
1024*4882a593Smuzhiyun    f.write("<tr><td>Total number of CVEs affecting all packages</td><td>%s</td></tr>\n" %
1025*4882a593Smuzhiyun            stats["total-cves"])
1026*4882a593Smuzhiyun    f.write("<tr><td>Packages with CPE ID</td><td>%s</td></tr>\n" %
1027*4882a593Smuzhiyun            stats["cpe-id"])
1028*4882a593Smuzhiyun    f.write("<tr><td>Packages without CPE ID</td><td>%s</td></tr>\n" %
1029*4882a593Smuzhiyun            stats["no-cpe-id"])
1030*4882a593Smuzhiyun    f.write("</table>\n")
1031*4882a593Smuzhiyun
1032*4882a593Smuzhiyun
1033*4882a593Smuzhiyundef dump_html_gen_info(f, date, commit):
1034*4882a593Smuzhiyun    # Updated on Mon Feb 19 08:12:08 CET 2018, Git commit aa77030b8f5e41f1c53eb1c1ad664b8c814ba032
1035*4882a593Smuzhiyun    f.write("<p><i>Updated on %s, git commit %s</i></p>\n" % (str(date), commit))
1036*4882a593Smuzhiyun
1037*4882a593Smuzhiyun
1038*4882a593Smuzhiyundef dump_html(packages, stats, date, commit, output):
1039*4882a593Smuzhiyun    with open(output, 'w') as f:
1040*4882a593Smuzhiyun        f.write(html_header)
1041*4882a593Smuzhiyun        dump_html_all_pkgs(f, packages)
1042*4882a593Smuzhiyun        dump_html_stats(f, stats)
1043*4882a593Smuzhiyun        dump_html_gen_info(f, date, commit)
1044*4882a593Smuzhiyun        f.write(html_footer)
1045*4882a593Smuzhiyun
1046*4882a593Smuzhiyun
1047*4882a593Smuzhiyundef dump_json(packages, defconfigs, stats, date, commit, output):
1048*4882a593Smuzhiyun    # Format packages as a dictionnary instead of a list
1049*4882a593Smuzhiyun    # Exclude local field that does not contains real date
1050*4882a593Smuzhiyun    excluded_fields = ['url_worker', 'name']
1051*4882a593Smuzhiyun    pkgs = {
1052*4882a593Smuzhiyun        pkg.name: {
1053*4882a593Smuzhiyun            k: v
1054*4882a593Smuzhiyun            for k, v in pkg.__dict__.items()
1055*4882a593Smuzhiyun            if k not in excluded_fields
1056*4882a593Smuzhiyun        } for pkg in packages
1057*4882a593Smuzhiyun    }
1058*4882a593Smuzhiyun    defconfigs = {
1059*4882a593Smuzhiyun        d.name: {
1060*4882a593Smuzhiyun            k: v
1061*4882a593Smuzhiyun            for k, v in d.__dict__.items()
1062*4882a593Smuzhiyun        } for d in defconfigs
1063*4882a593Smuzhiyun    }
1064*4882a593Smuzhiyun    # Aggregate infrastructures into a single dict entry
1065*4882a593Smuzhiyun    statistics = {
1066*4882a593Smuzhiyun        k: v
1067*4882a593Smuzhiyun        for k, v in stats.items()
1068*4882a593Smuzhiyun        if not k.startswith('infra-')
1069*4882a593Smuzhiyun    }
1070*4882a593Smuzhiyun    statistics['infra'] = {k[6:]: v for k, v in stats.items() if k.startswith('infra-')}
1071*4882a593Smuzhiyun    # The actual structure to dump, add commit and date to it
1072*4882a593Smuzhiyun    final = {'packages': pkgs,
1073*4882a593Smuzhiyun             'stats': statistics,
1074*4882a593Smuzhiyun             'defconfigs': defconfigs,
1075*4882a593Smuzhiyun             'package_status_checks': Package.status_checks,
1076*4882a593Smuzhiyun             'commit': commit,
1077*4882a593Smuzhiyun             'date': str(date)}
1078*4882a593Smuzhiyun
1079*4882a593Smuzhiyun    with open(output, 'w') as f:
1080*4882a593Smuzhiyun        json.dump(final, f, indent=2, separators=(',', ': '))
1081*4882a593Smuzhiyun        f.write('\n')
1082*4882a593Smuzhiyun
1083*4882a593Smuzhiyun
1084*4882a593Smuzhiyundef resolvepath(path):
1085*4882a593Smuzhiyun    return os.path.abspath(os.path.expanduser(path))
1086*4882a593Smuzhiyun
1087*4882a593Smuzhiyun
1088*4882a593Smuzhiyundef parse_args():
1089*4882a593Smuzhiyun    parser = argparse.ArgumentParser()
1090*4882a593Smuzhiyun    output = parser.add_argument_group('output', 'Output file(s)')
1091*4882a593Smuzhiyun    output.add_argument('--html', dest='html', type=resolvepath,
1092*4882a593Smuzhiyun                        help='HTML output file')
1093*4882a593Smuzhiyun    output.add_argument('--json', dest='json', type=resolvepath,
1094*4882a593Smuzhiyun                        help='JSON output file')
1095*4882a593Smuzhiyun    packages = parser.add_mutually_exclusive_group()
1096*4882a593Smuzhiyun    packages.add_argument('-c', dest='configpackages', action='store_true',
1097*4882a593Smuzhiyun                          help='Apply to packages enabled in current configuration')
1098*4882a593Smuzhiyun    packages.add_argument('-n', dest='npackages', type=int, action='store',
1099*4882a593Smuzhiyun                          help='Number of packages')
1100*4882a593Smuzhiyun    packages.add_argument('-p', dest='packages', action='store',
1101*4882a593Smuzhiyun                          help='List of packages (comma separated)')
1102*4882a593Smuzhiyun    parser.add_argument('--nvd-path', dest='nvd_path',
1103*4882a593Smuzhiyun                        help='Path to the local NVD database', type=resolvepath)
1104*4882a593Smuzhiyun    args = parser.parse_args()
1105*4882a593Smuzhiyun    if not args.html and not args.json:
1106*4882a593Smuzhiyun        parser.error('at least one of --html or --json (or both) is required')
1107*4882a593Smuzhiyun    return args
1108*4882a593Smuzhiyun
1109*4882a593Smuzhiyun
1110*4882a593Smuzhiyundef __main__():
1111*4882a593Smuzhiyun    global cvecheck
1112*4882a593Smuzhiyun
1113*4882a593Smuzhiyun    args = parse_args()
1114*4882a593Smuzhiyun
1115*4882a593Smuzhiyun    if args.nvd_path:
1116*4882a593Smuzhiyun        import cve as cvecheck
1117*4882a593Smuzhiyun
1118*4882a593Smuzhiyun    if args.packages:
1119*4882a593Smuzhiyun        package_list = args.packages.split(",")
1120*4882a593Smuzhiyun    elif args.configpackages:
1121*4882a593Smuzhiyun        package_list = get_config_packages()
1122*4882a593Smuzhiyun    else:
1123*4882a593Smuzhiyun        package_list = None
1124*4882a593Smuzhiyun    date = datetime.datetime.utcnow()
1125*4882a593Smuzhiyun    commit = subprocess.check_output(['git', '-C', brpath,
1126*4882a593Smuzhiyun                                      'rev-parse',
1127*4882a593Smuzhiyun                                      'HEAD']).splitlines()[0].decode()
1128*4882a593Smuzhiyun    print("Build package list ...")
1129*4882a593Smuzhiyun    packages = get_pkglist(args.npackages, package_list)
1130*4882a593Smuzhiyun    print("Getting developers ...")
1131*4882a593Smuzhiyun    developers = parse_developers()
1132*4882a593Smuzhiyun    print("Build defconfig list ...")
1133*4882a593Smuzhiyun    defconfigs = get_defconfig_list()
1134*4882a593Smuzhiyun    for d in defconfigs:
1135*4882a593Smuzhiyun        d.set_developers(developers)
1136*4882a593Smuzhiyun    print("Getting package make info ...")
1137*4882a593Smuzhiyun    package_init_make_info()
1138*4882a593Smuzhiyun    print("Getting package details ...")
1139*4882a593Smuzhiyun    for pkg in packages:
1140*4882a593Smuzhiyun        pkg.set_infra()
1141*4882a593Smuzhiyun        pkg.set_license()
1142*4882a593Smuzhiyun        pkg.set_hash_info()
1143*4882a593Smuzhiyun        pkg.set_patch_count()
1144*4882a593Smuzhiyun        pkg.set_check_package_warnings()
1145*4882a593Smuzhiyun        pkg.set_current_version()
1146*4882a593Smuzhiyun        pkg.set_cpeid()
1147*4882a593Smuzhiyun        pkg.set_url()
1148*4882a593Smuzhiyun        pkg.set_ignored_cves()
1149*4882a593Smuzhiyun        pkg.set_developers(developers)
1150*4882a593Smuzhiyun    print("Checking URL status")
1151*4882a593Smuzhiyun    loop = asyncio.get_event_loop()
1152*4882a593Smuzhiyun    loop.run_until_complete(check_package_urls(packages))
1153*4882a593Smuzhiyun    print("Getting latest versions ...")
1154*4882a593Smuzhiyun    loop = asyncio.get_event_loop()
1155*4882a593Smuzhiyun    loop.run_until_complete(check_package_latest_version(packages))
1156*4882a593Smuzhiyun    if args.nvd_path:
1157*4882a593Smuzhiyun        print("Checking packages CVEs")
1158*4882a593Smuzhiyun        check_package_cves(args.nvd_path, packages)
1159*4882a593Smuzhiyun        check_package_cpes(args.nvd_path, packages)
1160*4882a593Smuzhiyun    print("Calculate stats")
1161*4882a593Smuzhiyun    stats = calculate_stats(packages)
1162*4882a593Smuzhiyun    if args.html:
1163*4882a593Smuzhiyun        print("Write HTML")
1164*4882a593Smuzhiyun        dump_html(packages, stats, date, commit, args.html)
1165*4882a593Smuzhiyun    if args.json:
1166*4882a593Smuzhiyun        print("Write JSON")
1167*4882a593Smuzhiyun        dump_json(packages, defconfigs, stats, date, commit, args.json)
1168*4882a593Smuzhiyun
1169*4882a593Smuzhiyun
1170*4882a593Smuzhiyun__main__()
1171