| # Copyright 2025 The Dawn & Tint Authors | |
| # | |
| # Redistribution and use in source and binary forms, with or without | |
| # modification, are permitted provided that the following conditions are met: | |
| # | |
| # 1. Redistributions of source code must retain the above copyright notice, this | |
| # list of conditions and the following disclaimer. | |
| # | |
| # 2. Redistributions in binary form must reproduce the above copyright notice, | |
| # this list of conditions and the following disclaimer in the documentation | |
| # and/or other materials provided with the distribution. | |
| # | |
| # 3. Neither the name of the copyright holder nor the names of its | |
| # contributors may be used to endorse or promote products derived from | |
| # this software without specific prior written permission. | |
| # | |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| """Common code for ClusterFuzz corpora generation/uploading.""" | |
| import argparse | |
| import hashlib | |
| import os | |
| import shutil | |
| import subprocess | |
| import sys | |
| DAWN_ROOT = os.path.realpath( | |
| os.path.join(os.path.dirname(__file__), '..', '..')) | |
| BUCKET = 'clusterfuzz-corpus' | |
| BUCKET_DIRECTORY = 'libfuzzer' | |
| def upload_directory_to_gcs(local_directory: str, fuzzer_name: str, | |
| clobber: bool) -> None: | |
| """Uploads the contents of a directory to the ClusterFuzz GCS bucket. | |
| Args: | |
| local_directory: The path to the local directory whose contents will | |
| be uploaded. | |
| fuzzer_name: The ClusterFuzz fuzzer name to upload the files under. | |
| clobber: Whether to clobber identically named files in the GCS bucket. | |
| """ | |
| gsutil_path = os.path.join(DAWN_ROOT, 'third_party', 'depot_tools', | |
| 'gsutil.py') | |
| if not os.path.exists(gsutil_path): | |
| raise RuntimeError(f'Unable to find gsutil.py at {gsutil_path}') | |
| cmd = [ | |
| sys.executable, | |
| '-u', # Unbuffered output. | |
| gsutil_path, | |
| '-m', # Multithreaded. | |
| '-o', # Parallel upload. | |
| 'GSUtil:parallel_composite_upload_threshold=50M', | |
| 'cp', | |
| '-r', # Recursive. | |
| ] | |
| if not clobber: | |
| cmd.append('-n') | |
| cmd.extend([ | |
| os.path.join(local_directory, '*'), | |
| f'gs://{BUCKET}/{BUCKET_DIRECTORY}/{fuzzer_name}', | |
| ]) | |
| p = subprocess.run(cmd, check=True) | |
| def hash_trace_files(trace_files: list[str], output_directory: str) -> None: | |
| """Creates copies of trace files with hash-based names. | |
| Args: | |
| trace_files: A list of filepaths to trace files to process. | |
| output_directory: A filepath to a directory that the copies will be | |
| placed in. | |
| """ | |
| for tf in trace_files: | |
| with open(tf, 'rb') as infile: | |
| digest = hashlib.md5(infile.read()).hexdigest() | |
| filename = os.path.join(output_directory, f'trace_{digest}') | |
| shutil.copyfile(tf, filename) | |
| def find_raw_trace_files(output_jsons: list[str], | |
| subdirectory: str) -> list[str]: | |
| """Finds all raw trace files produced by the test. | |
| Args: | |
| output_jsons: A list of filepaths to the output.json files produced | |
| by each shard. | |
| subdirectory: The subdirectory of the isolated output directory that | |
| is expected to contain the raw trace files. | |
| Returns: | |
| A list of filepaths, one for each found trace file. | |
| """ | |
| trace_files = [] | |
| for json_file in output_jsons: | |
| isolated_outdir = os.path.dirname(json_file) | |
| dirname = os.path.join(isolated_outdir, subdirectory) | |
| for f in os.listdir(dirname): | |
| trace_files.append(os.path.join(dirname, f)) | |
| if not trace_files: | |
| raise RuntimeError('Did not find any wire trace files') | |
| return trace_files | |
| def add_common_arguments(parser: argparse.ArgumentParser) -> None: | |
| """Adds common fuzz corpora-related arguments to a parser. | |
| Args: | |
| parser: The ArgumentParser to add arguments to. | |
| """ | |
| parser.add_argument( | |
| '--fuzzer-name', | |
| required=True, | |
| dest='fuzzer_names', | |
| action='append', | |
| help=('A fuzzer name to upload files to. Can be specified multiple ' | |
| 'times')) |