1# SPDX-FileCopyrightText: 2014-2023 Espressif Systems (Shanghai) CO LTD,
2# other contributors as noted.
3#
4# SPDX-License-Identifier: GPL-2.0-or-later
5
6import configparser
7import os
8
9CONFIG_OPTIONS = [
10    "timeout",
11    "chip_erase_timeout",
12    "max_timeout",
13    "sync_timeout",
14    "md5_timeout_per_mb",
15    "erase_region_timeout_per_mb",
16    "erase_write_timeout_per_mb",
17    "mem_end_rom_timeout",
18    "serial_write_timeout",
19    "connect_attempts",
20    "write_block_attempts",
21    "reset_delay",
22    "open_port_attempts",
23    "custom_reset_sequence",
24]
25
26
27def _validate_config_file(file_path, verbose=False):
28    if not os.path.exists(file_path):
29        return False
30
31    cfg = configparser.RawConfigParser()
32    try:
33        cfg.read(file_path, encoding="UTF-8")
34        # Only consider it a valid config file if it contains [esptool] section
35        if cfg.has_section("esptool"):
36            if verbose:
37                unknown_opts = list(set(cfg.options("esptool")) - set(CONFIG_OPTIONS))
38                unknown_opts.sort()
39                no_of_unknown_opts = len(unknown_opts)
40                if no_of_unknown_opts > 0:
41                    suffix = "s" if no_of_unknown_opts > 1 else ""
42                    print(
43                        "Ignoring unknown config file option{}: {}".format(
44                            suffix, ", ".join(unknown_opts)
45                        )
46                    )
47            return True
48    except (UnicodeDecodeError, configparser.Error) as e:
49        if verbose:
50            print(f"Ignoring invalid config file {file_path}: {e}")
51    return False
52
53
54def _find_config_file(dir_path, verbose=False):
55    for candidate in ("esptool.cfg", "setup.cfg", "tox.ini"):
56        cfg_path = os.path.join(dir_path, candidate)
57        if _validate_config_file(cfg_path, verbose):
58            return cfg_path
59    return None
60
61
62def load_config_file(verbose=False):
63    set_with_env_var = False
64    env_var_path = os.environ.get("ESPTOOL_CFGFILE")
65    if env_var_path is not None and _validate_config_file(env_var_path):
66        cfg_file_path = env_var_path
67        set_with_env_var = True
68    else:
69        home_dir = os.path.expanduser("~")
70        os_config_dir = (
71            f"{home_dir}/.config/esptool"
72            if os.name == "posix"
73            else f"{home_dir}/AppData/Local/esptool/"
74        )
75        # Search priority: 1) current dir, 2) OS specific config dir, 3) home dir
76        for dir_path in (os.getcwd(), os_config_dir, home_dir):
77            cfg_file_path = _find_config_file(dir_path, verbose)
78            if cfg_file_path:
79                break
80
81    cfg = configparser.ConfigParser()
82    cfg["esptool"] = {}  # Create an empty esptool config for when no file is found
83
84    if cfg_file_path is not None:
85        # If config file is found and validated, read and parse it
86        cfg.read(cfg_file_path)
87        if verbose:
88            msg = " (set with ESPTOOL_CFGFILE)" if set_with_env_var else ""
89            print(
90                f"Loaded custom configuration from "
91                f"{os.path.abspath(cfg_file_path)}{msg}"
92            )
93    return cfg, cfg_file_path
94