#include "common/params.h" #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif // _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "common/util.h" #include "common/swaglog.h" // keep trying if x gets interrupted by a signal #define HANDLE_EINTR(x) \ ({ \ decltype(x) ret; \ int try_cnt = 0; \ do { \ ret = (x); \ } while (ret == -1 && errno == EINTR && try_cnt++ < 100); \ ret; \ }) namespace { #if defined(QCOM) || defined(QCOM2) const std::string default_params_path = "/data/params"; #else const std::string default_params_path = util::getenv_default("HOME", "/.comma/params", "/data/params"); #endif #if defined(QCOM) || defined(QCOM2) const std::string persistent_params_path = "/persist/comma/params"; #else const std::string persistent_params_path = default_params_path; #endif volatile sig_atomic_t params_do_exit = 0; void params_sig_handler(int signal) { params_do_exit = 1; } int fsync_dir(const char* path){ int fd = HANDLE_EINTR(open(path, O_RDONLY, 0755)); if (fd < 0){ return -1; } int result = fsync(fd); int result_close = close(fd); if (result_close < 0) { result = result_close; } return result; } // TODO: replace by std::filesystem::create_directories int mkdir_p(std::string path) { char * _path = (char *)path.c_str(); mode_t prev_mask = umask(0); for (char *p = _path + 1; *p; p++) { if (*p == '/') { *p = '\0'; // Temporarily truncate if (mkdir(_path, 0777) != 0) { if (errno != EEXIST) return -1; } *p = '/'; } } if (mkdir(_path, 0777) != 0) { if (errno != EEXIST) return -1; } chmod(_path, 0777); umask(prev_mask); return 0; } bool ensure_params_path(const std::string ¶m_path, const std::string &key_path) { // Make sure params path exists if (!util::file_exists(param_path) && mkdir_p(param_path) != 0) { return false; } // See if the symlink exists, otherwise create it if (!util::file_exists(key_path)) { // 1) Create temp folder // 2) Set permissions // 3) Symlink it to temp link // 4) Move symlink to /d std::string tmp_path = param_path + "/.tmp_XXXXXX"; // this should be OK since mkdtemp just replaces characters in place char *tmp_dir = mkdtemp((char *)tmp_path.c_str()); if (tmp_dir == NULL) { return false; } if (chmod(tmp_dir, 0777) != 0) { return false; } std::string link_path = std::string(tmp_dir) + ".link"; if (symlink(tmp_dir, link_path.c_str()) != 0) { return false; } // don't return false if it has been created by other if (rename(link_path.c_str(), key_path.c_str()) != 0 && errno != EEXIST) { return false; } } // Ensure permissions are correct in case we didn't create the symlink return chmod(key_path.c_str(), 0777) == 0; } class FileLock { public: FileLock(const std::string& file_name, int op) : fn_(file_name), op_(op) {} void lock() { fd_ = HANDLE_EINTR(open(fn_.c_str(), O_CREAT, 0775)); if (fd_ < 0) { LOGE("Failed to open lock file %s, errno=%d", fn_.c_str(), errno); return; } if (HANDLE_EINTR(flock(fd_, op_)) < 0) { close(fd_); LOGE("Failed to lock file %s, errno=%d", fn_.c_str(), errno); } } void unlock() { close(fd_); } private: int fd_ = -1, op_; std::string fn_; }; } // namespace Params::Params(bool persistent_param) : Params(persistent_param ? persistent_params_path : default_params_path) {} Params::Params(const std::string &path) : params_path(path) { if (!ensure_params_path(params_path, params_path + "/d")) { throw std::runtime_error(util::string_format("Failed to ensure params path, errno=%d", errno)); } } int Params::put(const char* key, const char* value, size_t value_size) { // Information about safely and atomically writing a file: https://lwn.net/Articles/457667/ // 1) Create temp file // 2) Write data to temp file // 3) fsync() the temp file // 4) rename the temp file to the real name // 5) fsync() the containing directory std::string tmp_path = params_path + "/.tmp_value_XXXXXX"; int tmp_fd = mkstemp((char*)tmp_path.c_str()); if (tmp_fd < 0) return -1; int result = -1; do { // Write value to temp. ssize_t bytes_written = HANDLE_EINTR(write(tmp_fd, value, value_size)); if (bytes_written < 0 || (size_t)bytes_written != value_size) { result = -20; break; } // change permissions to 0666 for apks if ((result = fchmod(tmp_fd, 0666)) < 0) break; // fsync to force persist the changes. if ((result = fsync(tmp_fd)) < 0) break; FileLock file_lock(params_path + "/.lock", LOCK_EX); std::lock_guard lk(file_lock); // Move temp into place. std::string path = params_path + "/d/" + std::string(key); if ((result = rename(tmp_path.c_str(), path.c_str())) < 0) break; // fsync parent directory path = params_path + "/d"; result = fsync_dir(path.c_str()); } while(0); close(tmp_fd); remove(tmp_path.c_str()); return result; } int Params::remove(const char *key) { FileLock file_lock(params_path + "/.lock", LOCK_EX); std::lock_guard lk(file_lock); // Delete value. std::string path = params_path + "/d/" + key; int result = ::remove(path.c_str()); if (result != 0) { result = ERR_NO_VALUE; return result; } // fsync parent directory path = params_path + "/d"; return fsync_dir(path.c_str()); } std::string Params::get(const char *key, bool block) { std::string path = params_path + "/d/" + key; if (!block) { return util::read_file(path); } else { // blocking read until successful params_do_exit = 0; void (*prev_handler_sigint)(int) = std::signal(SIGINT, params_sig_handler); void (*prev_handler_sigterm)(int) = std::signal(SIGTERM, params_sig_handler); std::string value; while (!params_do_exit) { if (value = util::read_file(path); !value.empty()) { break; } util::sleep_for(100); // 0.1 s } std::signal(SIGINT, prev_handler_sigint); std::signal(SIGTERM, prev_handler_sigterm); return value; } } int Params::read_db_all(std::map *params) { FileLock file_lock(params_path + "/.lock", LOCK_SH); std::lock_guard lk(file_lock); std::string key_path = params_path + "/d"; DIR *d = opendir(key_path.c_str()); if (!d) return -1; struct dirent *de = NULL; while ((de = readdir(d))) { if (isalnum(de->d_name[0])) { (*params)[de->d_name] = util::read_file(key_path + "/" + de->d_name); } } closedir(d); return 0; }