1#!/usr/bin/env python
2# -*- coding:utf-8 -*-
3#
4# Copyright (c) 2021 Huawei Device Co., Ltd.
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18from __future__ import print_function
19import argparse
20import os
21import re
22import sys
23import subprocess
24import zipfile
25import errno
26import pipes
27import traceback
28import time
29import copy
30import shutil
31from pprint import pprint
32import stat
33
34from tools.colored import Colored
35from tools.templates import GN_ENTRY_TEMPLATE
36from tools.templates import PROJECT_GN_TEMPLATE
37from tools.templates import PROJECT_DEMO_TEMPLATE
38from tools.templates import PROJECT_HEADER_TEMPLATE
39from tools.templates import PROJECT_XML_TEMPLATE
40from tools.run_result import RunResult
41
42FLAGS = os.O_WRONLY | os.O_CREAT | os.O_EXCL
43MODES = stat.S_IWUSR | stat.S_IRUSR
44
45CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
46SOURCE_ROOT_DIR = os.path.dirname(
47    os.path.dirname(
48        os.path.dirname(os.path.dirname(CURRENT_DIR))
49        )
50    )
51SOURCE_OUT_DIR = os.path.join(SOURCE_ROOT_DIR, "out")
52
53TDD_BUILD_GN_PATH = os.path.join(
54    SOURCE_ROOT_DIR,
55    "test/testfwk/developer_test/BUILD.gn"
56    )
57###project name must end with _fuzzer. eg. my_fuzzer,extrator_fuzzer.
58VALID_PROJECT_NAME_REGEX = re.compile(r'^[a-zA-Z0-9_-]+(_fuzzer)+$')
59
60
61def _add_environment_args(parser):
62    """Add common environment args."""
63    parser.add_argument(
64        '-t',
65        '--target_platform',
66        default='phone',
67        choices=["phone", "ivi", "plato"],
68        help="set target_platform value, default:phone")
69
70    parser.add_argument(
71        '-f',
72        '--filter',
73        default=None,
74        help="subsystem filter")
75
76
77def _get_command_string(command):
78    """Returns a shell escaped command string."""
79    return ' '.join(pipes.quote(part) for part in command)
80
81
82def parse_projects_path():
83    path_list = []
84    with open(TDD_BUILD_GN_PATH, 'r') as gn_file:
85        for line in gn_file.readlines()[4:]:
86            striped_str = line.strip()
87            if striped_str.endswith("]"):
88                break
89            path_list.append(striped_str.split(":")[0][3:])
90    return path_list
91
92
93def _get_fuzzer_yaml_config(fuzzer_name):
94    project_yaml_path = os.path.join(
95        CURRENT_DIR,
96        "projects",
97        fuzzer_name,
98        "project.yaml")
99    if not os.path.exists(project_yaml_path):
100        return {}
101    #log run stdout to fuzzlog dir
102    with open(project_yaml_path) as filehandle:
103        yaml_config = yaml.safe_load(filehandle)
104    return yaml_config
105
106
107#generate template fuzzer project
108def generate(args):
109    print("project name %s." % args.project_name)
110    print("project path %s." % args.project_path)
111    color_logger = Colored.get_project_logger()
112
113    if not VALID_PROJECT_NAME_REGEX.match(args.project_name):
114        print('Invalid project name.', file=sys.stderr)
115        return 1
116
117    template_args = {
118        'project_name': args.project_name,
119        'author': "",
120        'email': ""
121    }
122
123    project_dir_path = os.path.join(args.project_path, args.project_name)
124    print("project_dir_path %s." % project_dir_path)
125    try:
126        os.mkdir(project_dir_path)
127    except OSError as os_exception:
128        if os_exception.errno != errno.EEXIST:
129            raise
130        print(color_logger.red('%s already exists.' % project_dir_path),
131            file=sys.stderr)
132        return 1
133    color_logger.green('Writing new files to %s' % project_dir_path)
134
135    file_path = os.path.join(project_dir_path, 'project.xml')
136    if os.path.exists(file_path):
137        os.remove(file_path)
138    with os.fdopen(os.open(file_path, FLAGS, MODES), 'w') as filehandle:
139        filehandle.write(PROJECT_XML_TEMPLATE % template_args)
140
141    file_path = os.path.join(project_dir_path, "%s.cpp" % args.project_name)
142    if os.path.exists(file_path):
143        os.remove(file_path)
144    with os.fdopen(os.open(file_path, FLAGS, MODES), 'w') as filehandle:
145        filehandle.write(PROJECT_DEMO_TEMPLATE % template_args)
146
147    file_path = os.path.join(project_dir_path, "%s.h" % args.project_name)
148    if os.path.exists(file_path):
149        os.remove(file_path)
150    with os.fdopen(os.open(file_path, FLAGS, MODES), 'w') as filehandle:
151        filehandle.write(PROJECT_HEADER_TEMPLATE % template_args)
152    file_path = os.path.join(project_dir_path, "BUILD.gn")
153    if os.path.exists(file_path):
154        os.remove(file_path)
155    with os.fdopen(os.open(file_path, FLAGS, MODES), 'w') as filehandle:
156        filehandle.write(PROJECT_GN_TEMPLATE % template_args)
157
158    corpus_dir = os.path.join(project_dir_path, 'corpus')
159    if not os.path.exists(corpus_dir):
160        os.mkdir(corpus_dir)
161        if os.path.exists(os.path.join(corpus_dir, 'init')):
162            os.remove(os.path.join(corpus_dir, 'init'))
163        with os.fdopen(os.open(os.path.join(corpus_dir, 'init'), FLAGS, MODES), 'w') as filehandle:
164            filehandle.write("FUZZ")
165    return 0
166
167
168#complie fuzzer project
169def make(args, stdout=None):
170    """make fuzzer module."""
171    color_logger = Colored.get_project_logger()
172
173    pre_cmd = ['cd', SOURCE_ROOT_DIR]
174    build_target_platform = "build_platform=\"%s\""
175
176    build_script = [
177        './build.sh',
178        '--gn-args',
179        'build_example=true',
180        '--build-target'
181    ]
182    build_script.append(args.project_name)
183    build_script.append("--gn-args")
184    build_script.append(build_target_platform % args.build_platform)
185    build_script.append("--product-name")
186    build_script.append(args.build_platform)
187    build_script.append("--export-para")
188    build_script.append("PYCACHE_ENABLE:true")
189    print("BUILD_SCRIPT %s" % build_script)
190    final_cmd = "%s && %s" % (
191        _get_command_string(pre_cmd),
192        _get_command_string(build_script)
193        )
194
195    color_logger.green('Running:%s' % final_cmd)
196
197    subsystem_src_flag_file_path = os.path.join(
198        SOURCE_OUT_DIR,
199        "release/current_build_fuzz_target.txt"
200    )
201    if not os.path.exists(os.path.dirname(subsystem_src_flag_file_path)):
202        os.makedirs(os.path.dirname(subsystem_src_flag_file_path))
203    if os.path.exists(subsystem_src_flag_file_path):
204        os.remove(subsystem_src_flag_file_path)
205    with os.fdopen(os.open(subsystem_src_flag_file_path, FLAGS, MODES), 'wb') as file_handle:
206        file_handle.write(args.project_name.encode())
207
208    try:
209        if stdout:
210            ret = subprocess.check_call(build_script, cwd=SOURCE_ROOT_DIR,
211                                        stdout=stdout)
212        else:
213            ret = subprocess.check_call(build_script, cwd=SOURCE_ROOT_DIR)
214        return ret
215    except subprocess.CalledProcessError:
216        print("*" * 50)
217        print("*" * 50)
218        print(
219            'fuzzers {} build failed.'.format(args.project_name),
220            file=sys.stdout
221        )
222        return -1
223
224
225def report(args):
226    pass
227
228
229def coverage_all(args):
230    pass
231
232
233def main():
234    parser = argparse.ArgumentParser(
235        'fuzzer_helper.py',
236        description='hydra-fuzz helpers'
237    )
238    subparsers = parser.add_subparsers(dest='command')
239
240    generate_parser = subparsers.add_parser(
241        'generate',
242        help='Generate files for new project.name must end with "_fuzzer".')
243    generate_parser.add_argument('project_name')
244    generate_parser.add_argument('project_path')
245    _add_environment_args(generate_parser)
246
247    make_parser = subparsers.add_parser(
248        'make', help='Build a single fuzzer module project. ')
249    make_parser.add_argument('project_name')
250    make_parser.add_argument('build_platform')
251    _add_environment_args(make_parser)
252
253    report_parser = subparsers.add_parser(
254       'report', help='Report fuzzer log'
255    )
256    _add_environment_args(report_parser)
257    report_parser.add_argument(
258        'subfunc',
259        default="list",
260        choices=["list", "coverage", "all"]
261    )
262    report_parser.add_argument(
263        "-i",
264        "--id",
265        required=False,
266        help="report ID, e.g. empty_fuzzer.20200211184850"
267    )
268
269    args = parser.parse_args()
270
271    if args.command == 'generate':
272        return generate(args)
273    elif args.command == 'make':
274        return make(args)
275
276    elif args.command == 'report':
277        report(args)
278        return 1
279    else:
280        return 0
281
282if __name__ == "__main__":
283    main()
284