Fix missing #include (needed for compiling with GCC 10)
[oweals/dinit.git] / src / dinitcheck.cc
index 2865e1722d116211128fdca9e08c4f63664438f1..cafb0fcaf24e2f25802c44782533a415fe67c0a7 100644 (file)
@@ -1,3 +1,4 @@
+#include <algorithm>
 #include <iostream>
 #include <fstream>
 #include <cstring>
 #include "dinit-util.h"
 #include "service-constants.h"
 #include "load-service.h"
+#include "options-processing.h"
 
 // dinitcheck:  utility to check Dinit configuration for correctness/lint
 
 using string = std::string;
 using string_iterator = std::string::iterator;
 
-static const char *user_home_path = nullptr;
-
-// Get user home (and set user_home_path). (The return may become invalid after
-// changing the evironment (HOME variable) or using the getpwuid() function).
-static const char * get_user_home()
-{
-    if (user_home_path == nullptr) {
-        user_home_path = getenv("HOME");
-        if (user_home_path == nullptr) {
-            struct passwd * pwuid_p = getpwuid(getuid());
-            if (pwuid_p != nullptr) {
-                user_home_path = pwuid_p->pw_dir;
-            }
-        }
-    }
-    return user_home_path;
-}
-
+// prelim_dep: A preliminary (unresolved) service dependency
 class prelim_dep
 {
+    public:
     std::string name;
     dependency_type dep_type;
 
-    public:
-    prelim_dep(std::string &name_p, dependency_type dep_type_p)
+    prelim_dep(const std::string &name_p, dependency_type dep_type_p)
         : name(name_p), dep_type(dep_type_p) { }
     prelim_dep(std::string &&name_p, dependency_type dep_type_p)
         : name(std::move(name_p)), dep_type(dep_type_p) { }
@@ -55,98 +40,203 @@ class prelim_dep
 class service_record
 {
 public:
-    service_record(std::string name, std::list<prelim_dep> dependencies_p) : dependencies(dependencies_p) {}
+    service_record(std::string name_p, std::list<prelim_dep> dependencies_p)
+            : name(name_p), dependencies(dependencies_p) {}
 
     std::string name;
-    bool finished_loading = false;  // flag used to detect cyclic dependencies
     std::list<prelim_dep> dependencies;
+
+    bool visited = false;  // flag used to detect cyclic dependencies
+    bool cycle_free = false;
 };
 
 using service_set_t = std::map<std::string, service_record *>;
 
 service_record *load_service(service_set_t &services, const std::string &name,
-        const std::vector<dinit_load::dir_entry> &service_dirs);
+        const service_dir_pathlist &service_dirs);
+
+// Add some missing standard library functionality...
+template <typename T> bool contains(std::vector<T> vec, const T& elem)
+{
+    return std::find(vec.begin(), vec.end(), elem) != vec.end();
+}
+
+static bool errors_found = false;
 
 int main(int argc, char **argv)
 {
     using namespace std;
 
-    bool add_all_service_dirs = false;
-    bool for_system = false;
-    const char * service_dir = nullptr;
-    bool service_dir_dynamic = false; // service_dir dynamically allocated?
+    service_dir_opt service_dir_opts;
+    bool am_system_init = (getuid() == 0);
 
     std::vector<std::string> services_to_check;
 
-    // Figure out service dirs
-    /* service directory name */
-    if (service_dir == nullptr && ! for_system) {
-        const char * userhome = get_user_home();
-        if (userhome != nullptr) {
-            const char * user_home = get_user_home();
-            size_t user_home_len = strlen(user_home);
-            size_t dinit_d_len = strlen("/dinit.d");
-            size_t full_len = user_home_len + dinit_d_len + 1;
-            char *service_dir_w = new char[full_len];
-            std::memcpy(service_dir_w, user_home, user_home_len);
-            std::memcpy(service_dir_w + user_home_len, "/dinit.d", dinit_d_len);
-            service_dir_w[full_len - 1] = 0;
-
-            service_dir = service_dir_w;
-            service_dir_dynamic = true;
+    // Process command line
+    if (argc > 1) {
+        for (int i = 1; i < argc; i++) {
+            if (argv[i][0] == '-') {
+                // An option...
+                if (strcmp(argv[i], "--services-dir") == 0 || strcmp(argv[i], "-d") == 0) {
+                    if (++i < argc) {
+                        service_dir_opts.set_specified_service_dir(argv[i]);
+                    }
+                    else {
+                        cerr << "dinitcheck: '--services-dir' (-d) requires an argument" << endl;
+                        return 1;
+                    }
+                }
+                else if (strcmp(argv[i], "--help") == 0) {
+                    cout << "dinitcheck: check dinit service descriptions\n"
+                            " --help                       display help\n"
+                            " --services-dir <dir>, -d <dir>\n"
+                            "                              set base directory for service description\n"
+                            "                              files\n"
+                            " <service-name>               check service with name <service-name>\n";
+                    return EXIT_SUCCESS;
+                }
+                else {
+                    std::cerr << "dinitcheck: Unrecognized option: '" << argv[i] << "' (use '--help' for help)\n";
+                    return EXIT_FAILURE;
+                }
+            }
+            else {
+                services_to_check.push_back(argv[i]);
+            }
         }
     }
 
-    if (service_dir == nullptr) {
-        service_dir = "/etc/dinit.d";
-        add_all_service_dirs = true;
-    }
-
-    std::vector<dinit_load::dir_entry> service_dirs;
+    service_dir_opts.build_paths(am_system_init);
 
-    service_dirs.emplace_back(service_dir, service_dir_dynamic);
-    if (add_all_service_dirs) {
-        service_dirs.emplace_back("/usr/local/lib/dinit.d", false);
-        service_dirs.emplace_back("/lib/dinit.d", false);
+    if (services_to_check.empty()) {
+        services_to_check.push_back("boot");
     }
 
-    // Temporary, for testing:
-    services_to_check.push_back("boot");
+    size_t num_services_to_check = services_to_check.size();
 
     // Load named service(s)
-    std::map<std::string, service_record *> service_set;
-
     // - load the service, store dependencies as strings
     // - recurse
 
-    // additional: check chain-to, other lint
+    std::map<std::string, service_record *> service_set;
 
-    for (const auto &name : services_to_check) {
+    for (size_t i = 0; i < services_to_check.size(); ++i) {
+        const std::string &name = services_to_check[i];
+        std::cout << "Checking service: " << name << "...\n";
         try {
-            load_service(service_set, name, service_dirs);
+            service_record *sr = load_service(service_set, name, service_dir_opts.get_paths());
+            service_set[name] = sr;
+            // add dependencies to services_to_check
+            for (auto &dep : sr->dependencies) {
+                if (service_set.count(dep.name) == 0 && !contains(services_to_check, dep.name)) {
+                    services_to_check.push_back(dep.name);
+                }
+            }
         }
         catch (service_load_exc &exc) {
             std::cerr << "Unable to load service '" << name << "': " << exc.exc_description << "\n";
+            errors_found = true;
+        }
+    }
+
+    // Check for circular dependencies
+    std::vector<std::tuple<service_record *, size_t>> service_chain;
+
+    for (size_t i = 0; i < num_services_to_check; ++i) {
+        service_record *root = service_set[services_to_check[i]];
+        if (! root) continue;
+        if (root->visited) continue;
+
+        // invariant: service_chain is empty
+        service_chain.emplace_back(root, 0);
+
+        // Depth first traversal. If we find a link (dependency) on a service already visited (but not
+        // marked as cycle-free), we know then that we've found a cycle.
+        while (true) {
+            auto n = service_chain.size() - 1;
+            auto &last = service_chain[n];
+            service_record *last_record = std::get<0>(last);
+            size_t &index = std::get<1>(last);
+            if (index >= last_record->dependencies.size()) {
+                // Processed all dependencies, go back up:
+                last_record->cycle_free = true;
+                service_chain.pop_back();
+                if (n == 0) break;
+                size_t &prev_index = std::get<1>(service_chain[n - 1]);
+                ++prev_index;
+                continue;
+            }
+            // Down the tree:
+            auto dep_it = std::next(last_record->dependencies.begin(), index);
+            service_record *next_link = service_set[dep_it->name];
+            if (next_link == nullptr) {
+                ++index;
+                continue;
+            }
+            if (next_link->visited) {
+                if (! next_link->cycle_free) {
+                    // We've found a cycle. Clear entries before the beginning of the cycle, then
+                    // exit the loop.
+                    auto first = std::find_if(service_chain.begin(), service_chain.end(),
+                            [next_link](std::tuple<service_record *, size_t> &a) -> bool {
+                       return std::get<0>(a) == next_link;
+                    });
+                    service_chain.erase(service_chain.begin(), first);
+                    break;
+                }
+            }
+            next_link->visited = true;
+            service_chain.emplace_back(next_link, 0);
         }
+
+        // Report only one cycle; otherwise difficult to avoid reporting duplicates or overlapping
+        // cycles.
+        if (!service_chain.empty()) break;
     }
 
-    return 0;
+    if (!service_chain.empty()) {
+        errors_found = true;
+        std::cerr << "Found dependency cycle:\n";
+        for (auto chain_link : service_chain) {
+            std::cerr << "    " << std::get<0>(chain_link)->name << " ->\n";
+        }
+        std::cerr << "    " << std::get<0>(service_chain[0])->name << ".\n";
+    }
+
+    // TODO additional: check chain-to, other lint
+
+    if (! errors_found) {
+        std::cout << "No problems found.\n";
+    }
+    else {
+        std::cout << "One or more errors found.\n";
+    }
+
+    return errors_found ? EXIT_FAILURE : EXIT_SUCCESS;
 }
 
-static void report_error(dinit_load::setting_exception &exc, const std::string &service_name, const char *setting_name)
+static void report_service_description_err(const std::string &service_name, const std::string &what)
 {
-    std::cerr << "Service '" << service_name << "', " << setting_name << ": " << exc.get_info() << "\n";
+    std::cerr << "Service '" << service_name << "': " << what << "\n";
+    errors_found = true;
+}
+
+static void report_service_description_exc(service_description_exc &exc)
+{
+    report_service_description_err(exc.service_name, exc.exc_description);
 }
 
 static void report_error(std::system_error &exc, const std::string &service_name)
 {
     std::cerr << "Service '" << service_name << "', error reading service description: " << exc.what() << "\n";
+    errors_found = true;
 }
 
 static void report_dir_error(const char *service_name, const std::string &dirpath)
 {
     std::cerr << "Service '" << service_name << "', error reading dependencies from directory " << dirpath
             << ": " << strerror(errno) << "\n";
+    errors_found = true;
 }
 
 // Process a dependency directory - filenames contained within correspond to service names which
@@ -185,7 +275,7 @@ static void process_dep_dir(const char *servicename,
 }
 
 service_record *load_service(service_set_t &services, const std::string &name,
-        const std::vector<dinit_load::dir_entry> &service_dirs)
+        const service_dir_pathlist &service_dirs)
 {
     using namespace std;
     using namespace dinit_load;
@@ -214,49 +304,7 @@ service_record *load_service(service_set_t &services, const std::string &name,
         throw service_not_found(string(name));
     }
 
-    string command;
-    list<pair<unsigned,unsigned>> command_offsets;
-    string stop_command;
-    list<pair<unsigned,unsigned>> stop_command_offsets;
-    string working_dir;
-    string pid_file;
-    string env_file;
-
-    bool do_sub_vars = false;
-
-    service_type_t service_type = service_type_t::PROCESS;
-    std::list<prelim_dep> depends;
-    string logfile;
-    service_flags_t onstart_flags;
-    int term_signal = -1;  // additional termination signal
-    bool auto_restart = false;
-    bool smooth_recovery = false;
-    string socket_path;
-    int socket_perms = 0666;
-    // Note: Posix allows that uid_t and gid_t may be unsigned types, but eg chown uses -1 as an
-    // invalid value, so it's safe to assume that we can do the same:
-    uid_t socket_uid = -1;
-    gid_t socket_gid = -1;
-    // Restart limit interval / count; default is 10 seconds, 3 restarts:
-    timespec restart_interval = { .tv_sec = 10, .tv_nsec = 0 };
-    int max_restarts = 3;
-    timespec restart_delay = { .tv_sec = 0, .tv_nsec = 200000000 };
-    timespec stop_timeout = { .tv_sec = 10, .tv_nsec = 0 };
-    timespec start_timeout = { .tv_sec = 60, .tv_nsec = 0 };
-    std::vector<service_rlimits> rlimits;
-
-    int readiness_fd = -1;      // readiness fd in service process
-    std::string readiness_var;  // environment var to hold readiness fd
-
-    uid_t run_as_uid = -1;
-    gid_t run_as_gid = -1;
-
-    string chain_to_name;
-
-    #if USE_UTMPX
-    char inittab_id[sizeof(utmpx().ut_id)] = {0};
-    char inittab_line[sizeof(utmpx().ut_line)] = {0};
-    #endif
+    service_settings_wrapper<prelim_dep> settings;
 
     string line;
     service_file.exceptions(ios::badbit);
@@ -264,270 +312,35 @@ service_record *load_service(service_set_t &services, const std::string &name,
     try {
         process_service_file(name, service_file,
                 [&](string &line, string &setting, string_iterator &i, string_iterator &end) -> void {
+
+            auto process_dep_dir_n = [&](std::list<prelim_dep> &deplist, const std::string &waitsford,
+                    dependency_type dep_type) -> void {
+                process_dep_dir(name.c_str(), service_filename, deplist, waitsford, dep_type);
+            };
+
+            auto load_service_n = [&](const string &dep_name) -> const string & {
+                return dep_name;
+            };
+
             try {
-                if (setting == "command") {
-                    command = read_setting_value(i, end, &command_offsets);
-                }
-                else if (setting == "working-dir") {
-                    working_dir = read_setting_value(i, end, nullptr);
-                }
-                else if (setting == "env-file") {
-                    env_file = read_setting_value(i, end, nullptr);
-                }
-                else if (setting == "socket-listen") {
-                    socket_path = read_setting_value(i, end, nullptr);
-                }
-                else if (setting == "socket-permissions") {
-                    string sock_perm_str = read_setting_value(i, end, nullptr);
-                    std::size_t ind = 0;
-                    try {
-                        socket_perms = std::stoi(sock_perm_str, &ind, 8);
-                        if (ind != sock_perm_str.length()) {
-                            throw std::logic_error("");
-                        }
-                    }
-                    catch (std::logic_error &exc) {
-                        throw service_description_exc(name, "socket-permissions: Badly-formed or "
-                                "out-of-range numeric value");
-                    }
-                }
-                else if (setting == "socket-uid") {
-                    string sock_uid_s = read_setting_value(i, end, nullptr);
-                    socket_uid = parse_uid_param(sock_uid_s, name, &socket_gid);
-                }
-                else if (setting == "socket-gid") {
-                    string sock_gid_s = read_setting_value(i, end, nullptr);
-                    socket_gid = parse_gid_param(sock_gid_s, name);
-                }
-                else if (setting == "stop-command") {
-                    stop_command = read_setting_value(i, end, &stop_command_offsets);
-                }
-                else if (setting == "pid-file") {
-                    pid_file = read_setting_value(i, end);
-                }
-                else if (setting == "depends-on") {
-                    string dependency_name = read_setting_value(i, end);
-                    depends.emplace_back(std::move(dependency_name), dependency_type::REGULAR);
-                }
-                else if (setting == "depends-ms") {
-                    string dependency_name = read_setting_value(i, end);
-                    depends.emplace_back(dependency_name, dependency_type::MILESTONE);
-                }
-                else if (setting == "waits-for") {
-                    string dependency_name = read_setting_value(i, end);
-                    depends.emplace_back(dependency_name, dependency_type::WAITS_FOR);
-                }
-                else if (setting == "waits-for.d") {
-                    string waitsford = read_setting_value(i, end);
-                    process_dep_dir(name.c_str(), service_filename, depends, waitsford,
-                            dependency_type::WAITS_FOR);
-                }
-                else if (setting == "logfile") {
-                    logfile = read_setting_value(i, end);
-                }
-                else if (setting == "restart") {
-                    string restart = read_setting_value(i, end);
-                    auto_restart = (restart == "yes" || restart == "true");
-                }
-                else if (setting == "smooth-recovery") {
-                    string recovery = read_setting_value(i, end);
-                    smooth_recovery = (recovery == "yes" || recovery == "true");
-                }
-                else if (setting == "type") {
-                    string type_str = read_setting_value(i, end);
-                    if (type_str == "scripted") {
-                        service_type = service_type_t::SCRIPTED;
-                    }
-                    else if (type_str == "process") {
-                        service_type = service_type_t::PROCESS;
-                    }
-                    else if (type_str == "bgprocess") {
-                        service_type = service_type_t::BGPROCESS;
-                    }
-                    else if (type_str == "internal") {
-                        service_type = service_type_t::INTERNAL;
-                    }
-                    else {
-                        throw service_description_exc(name, "Service type must be one of: \"scripted\","
-                            " \"process\", \"bgprocess\" or \"internal\"");
-                    }
-                }
-                else if (setting == "options") {
-                    std::list<std::pair<unsigned,unsigned>> indices;
-                    string onstart_cmds = read_setting_value(i, end, &indices);
-                    for (auto indexpair : indices) {
-                        string option_txt = onstart_cmds.substr(indexpair.first,
-                                indexpair.second - indexpair.first);
-                        if (option_txt == "starts-rwfs") {
-                            onstart_flags.rw_ready = true;
-                        }
-                        else if (option_txt == "starts-log") {
-                            onstart_flags.log_ready = true;
-                        }
-                        else if (option_txt == "no-sigterm") {
-                            onstart_flags.no_sigterm = true;
-                        }
-                        else if (option_txt == "runs-on-console") {
-                            onstart_flags.runs_on_console = true;
-                            // A service that runs on the console necessarily starts on console:
-                            onstart_flags.starts_on_console = true;
-                            onstart_flags.shares_console = false;
-                        }
-                        else if (option_txt == "starts-on-console") {
-                            onstart_flags.starts_on_console = true;
-                            onstart_flags.shares_console = false;
-                        }
-                        else if (option_txt == "shares-console") {
-                            onstart_flags.shares_console = true;
-                            onstart_flags.runs_on_console = false;
-                            onstart_flags.starts_on_console = false;
-                        }
-                        else if (option_txt == "pass-cs-fd") {
-                            onstart_flags.pass_cs_fd = true;
-                        }
-                        else if (option_txt == "start-interruptible") {
-                            onstart_flags.start_interruptible = true;
-                        }
-                        else if (option_txt == "skippable") {
-                            onstart_flags.skippable = true;
-                        }
-                        else if (option_txt == "signal-process-only") {
-                            onstart_flags.signal_process_only = true;
-                        }
-                        else {
-                            throw service_description_exc(name, "Unknown option: " + option_txt);
-                        }
-                    }
-                }
-                else if (setting == "load-options") {
-                    std::list<std::pair<unsigned,unsigned>> indices;
-                    string load_opts = read_setting_value(i, end, &indices);
-                    for (auto indexpair : indices) {
-                        string option_txt = load_opts.substr(indexpair.first,
-                                indexpair.second - indexpair.first);
-                        if (option_txt == "sub-vars") {
-                            // substitute environment variables in command line
-                            do_sub_vars = true;
-                        }
-                        else if (option_txt == "no-sub-vars") {
-                            do_sub_vars = false;
-                        }
-                        else {
-                            throw service_description_exc(name, "Unknown load option: " + option_txt);
-                        }
-                    }
-                }
-                else if (setting == "term-signal" || setting == "termsignal") {
-                    // Note: "termsignal" supported for legacy reasons.
-                    string signame = read_setting_value(i, end, nullptr);
-                    int signo = signal_name_to_number(signame);
-                    if (signo == -1) {
-                        throw service_description_exc(name, "Unknown/unsupported termination signal: "
-                                + signame);
-                    }
-                    else {
-                        term_signal = signo;
-                    }
-                }
-                else if (setting == "restart-limit-interval") {
-                    string interval_str = read_setting_value(i, end, nullptr);
-                    parse_timespec(interval_str, name, "restart-limit-interval", restart_interval);
-                }
-                else if (setting == "restart-delay") {
-                    string rsdelay_str = read_setting_value(i, end, nullptr);
-                    parse_timespec(rsdelay_str, name, "restart-delay", restart_delay);
-                }
-                else if (setting == "restart-limit-count") {
-                    string limit_str = read_setting_value(i, end, nullptr);
-                    max_restarts = parse_unum_param(limit_str, name, std::numeric_limits<int>::max());
-                }
-                else if (setting == "stop-timeout") {
-                    string stoptimeout_str = read_setting_value(i, end, nullptr);
-                    parse_timespec(stoptimeout_str, name, "stop-timeout", stop_timeout);
-                }
-                else if (setting == "start-timeout") {
-                    string starttimeout_str = read_setting_value(i, end, nullptr);
-                    parse_timespec(starttimeout_str, name, "start-timeout", start_timeout);
-                }
-                else if (setting == "run-as") {
-                    string run_as_str = read_setting_value(i, end, nullptr);
-                    run_as_uid = parse_uid_param(run_as_str, name, &run_as_gid);
-                }
-                else if (setting == "chain-to") {
-                    chain_to_name = read_setting_value(i, end, nullptr);
-                }
-                else if (setting == "ready-notification") {
-                    string notify_setting = read_setting_value(i, end, nullptr);
-                    if (starts_with(notify_setting, "pipefd:")) {
-                        readiness_fd = parse_unum_param(notify_setting.substr(7 /* len 'pipefd:' */),
-                                name, std::numeric_limits<int>::max());
-                    }
-                    else if (starts_with(notify_setting, "pipevar:")) {
-                        readiness_var = notify_setting.substr(8 /* len 'pipevar:' */);
-                        if (readiness_var.empty()) {
-                            throw service_description_exc(name, "Invalid pipevar variable name "
-                                    "in ready-notification");
-                        }
-                    }
-                    else {
-                        throw service_description_exc(name, "Unknown ready-notification setting: "
-                                + notify_setting);
-                    }
-                }
-                else if (setting == "inittab-id") {
-                    string inittab_setting = read_setting_value(i, end, nullptr);
-                    #if USE_UTMPX
-                    if (inittab_setting.length() > sizeof(inittab_id)) {
-                        throw service_description_exc(name, "inittab-id setting is too long");
-                    }
-                    strncpy(inittab_id, inittab_setting.c_str(), sizeof(inittab_id));
-                    #endif
-                }
-                else if (setting == "inittab-line") {
-                    string inittab_setting = read_setting_value(i, end, nullptr);
-                    #if USE_UTMPX
-                    if (inittab_setting.length() > sizeof(inittab_line)) {
-                        throw service_description_exc(name, "inittab-line setting is too long");
-                    }
-                    strncpy(inittab_line, inittab_setting.c_str(), sizeof(inittab_line));
-                    #endif
-                }
-                else if (setting == "rlimit-nofile") {
-                    string nofile_setting = read_setting_value(i, end, nullptr);
-                    service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_NOFILE);
-                    parse_rlimit(line, name, "rlimit-nofile", nofile_limits);
-                }
-                else if (setting == "rlimit-core") {
-                    string nofile_setting = read_setting_value(i, end, nullptr);
-                    service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_CORE);
-                    parse_rlimit(line, name, "rlimit-core", nofile_limits);
-                }
-                else if (setting == "rlimit-data") {
-                    string nofile_setting = read_setting_value(i, end, nullptr);
-                    service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_DATA);
-                    parse_rlimit(line, name, "rlimit-data", nofile_limits);
-                }
-                else if (setting == "rlimit-addrspace") {
-                    #if defined(RLIMIT_AS)
-                        string nofile_setting = read_setting_value(i, end, nullptr);
-                        service_rlimits &nofile_limits = find_rlimits(rlimits, RLIMIT_AS);
-                        parse_rlimit(line, name, "rlimit-addrspace", nofile_limits);
-                    #endif
-                }
-                else {
-                    throw service_description_exc(name, "Unknown setting: " + setting);
-                }
+                process_service_line(settings, name.c_str(), line, setting, i, end, load_service_n, process_dep_dir_n);
             }
-            catch (setting_exception &exc) {
-                report_error(exc, name, setting.c_str());
+            catch (service_description_exc &exc) {
+                report_service_description_exc(exc);
             }
         });
     }
     catch (std::system_error &sys_err)
     {
         report_error(sys_err, name);
-        return nullptr;
+        throw service_description_exc(name, "Error while reading service description.");
+    }
+
+    settings.finalise();
+
+    if (settings.service_type != service_type_t::INTERNAL && settings.command.length() == 0) {
+        report_service_description_err(name, "Service command not specified.");
     }
 
-    return new service_record(name, depends);
+    return new service_record(name, settings.depends);
 }