bool w_cmd_realpath_root(json_t *args, char **errmsg) { const char *path; char *resolved; if (json_array_size(args) < 2) { ignore_result(asprintf(errmsg, "wrong number of arguments")); return false; } path = json_string_value(json_array_get(args, 1)); if (!path) { ignore_result(asprintf(errmsg, "second argument must be a string")); return false; } resolved = w_realpath(path); if (resolved) { json_array_set_new(args, 1, json_string_nocheck(resolved)); } return true; }
std::shared_ptr<w_root_t> root_resolve( const char* filename, bool auto_watch, bool* created, char** errmsg) { char *watch_path; int realpath_err; std::shared_ptr<w_root_t> root; *created = false; // Sanity check that the path is absolute if (!w_is_path_absolute_cstr(filename)) { ignore_result(asprintf(errmsg, "path \"%s\" must be absolute", filename)); w_log(W_LOG_ERR, "resolve_root: %s", *errmsg); return nullptr; } if (!strcmp(filename, "/")) { ignore_result(asprintf(errmsg, "cannot watch \"/\"")); w_log(W_LOG_ERR, "resolve_root: %s", *errmsg); return nullptr; } watch_path = w_realpath(filename); realpath_err = errno; if (!watch_path) { watch_path = (char*)filename; } w_string root_str(watch_path, W_STRING_BYTE); { auto map = watched_roots.rlock(); const auto& it = map->find(root_str); if (it != map->end()) { root = it->second; } } if (!root && watch_path == filename) { // Path didn't resolve and neither did the name they passed in ignore_result(asprintf(errmsg, "realpath(%s) -> %s", filename, strerror(realpath_err))); w_log(W_LOG_ERR, "resolve_root: %s\n", *errmsg); return nullptr; } if (root || !auto_watch) { if (!root) { ignore_result( asprintf(errmsg, "directory %s is not watched", watch_path)); w_log(W_LOG_DBG, "resolve_root: %s\n", *errmsg); } if (watch_path != filename) { free(watch_path); } if (!root) { return nullptr; } // Treat this as new activity for aging purposes; this roughly maps // to a client querying something about the root and should extend // the lifetime of the root // Note that this write potentially races with the read in consider_reap // but we're "OK" with it because the latter is performed under a write // lock and the worst case side effect is that we (safely) decide to reap // at the same instant that a new command comes in. The reap intervals // are typically on the order of days. time(&root->inner.last_cmd_timestamp); return root; } w_log(W_LOG_DBG, "Want to watch %s -> %s\n", filename, watch_path); if (!check_allowed_fs(watch_path, errmsg)) { w_log(W_LOG_ERR, "resolve_root: %s\n", *errmsg); if (watch_path != filename) { free(watch_path); } return nullptr; } if (!root_check_restrict(watch_path)) { ignore_result( asprintf(errmsg, "Your watchman administrator has configured watchman " "to prevent watching this path. None of the files " "listed in global config root_files are " "present and enforce_root_files is set to true")); w_log(W_LOG_ERR, "resolve_root: %s\n", *errmsg); if (watch_path != filename) { free(watch_path); } return nullptr; } // created with 1 ref try { root = std::make_shared<w_root_t>(root_str); } catch (const std::exception& e) { watchman::log(watchman::ERR, "while making a new root: ", e.what()); *errmsg = strdup(e.what()); } if (watch_path != filename) { free(watch_path); } if (!root) { return nullptr; } { auto wlock = watched_roots.wlock(); auto& map = *wlock; auto& existing = map[root->root_path]; if (existing) { // Someone beat us in this race root = existing; *created = false; } else { existing = root; *created = true; } } return root; }