#include "common/params.h" #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif // _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "common/util.h" #include "common/utilpp.h" #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; } static int fsync_dir(const char* path){ int fd = 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; } static 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; } static int ensure_dir_exists(std::string path) { // TODO: replace by std::filesystem::create_directories return mkdir_p(path.c_str()); } Params::Params(bool persistent_param){ params_path = persistent_param ? persistent_params_path : default_params_path; } Params::Params(std::string path) { params_path = path; } int Params::write_db_value(std::string key, std::string dat){ return write_db_value(key.c_str(), dat.c_str(), dat.length()); } int Params::write_db_value(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 int lock_fd = -1; int tmp_fd = -1; int result; std::string path; std::string tmp_path; ssize_t bytes_written; // Make sure params path exists result = ensure_dir_exists(params_path); if (result < 0) { goto cleanup; } // See if the symlink exists, otherwise create it path = params_path + "/d"; struct stat st; if (stat(path.c_str(), &st) == -1) { // Create temp folder path = params_path + "/.tmp_XXXXXX"; char *t = mkdtemp((char*)path.c_str()); if (t == NULL){ goto cleanup; } std::string tmp_dir(t); // Set permissions result = chmod(tmp_dir.c_str(), 0777); if (result < 0) { goto cleanup; } // Symlink it to temp link tmp_path = tmp_dir + ".link"; result = symlink(tmp_dir.c_str(), tmp_path.c_str()); if (result < 0) { goto cleanup; } // Move symlink to /d path = params_path + "/d"; result = rename(tmp_path.c_str(), path.c_str()); if (result < 0) { goto cleanup; } } else { // Ensure permissions are correct in case we didn't create the symlink result = chmod(path.c_str(), 0777); if (result < 0) { goto cleanup; } } // Write value to temp. tmp_path = params_path + "/.tmp_value_XXXXXX"; tmp_fd = mkstemp((char*)tmp_path.c_str()); bytes_written = write(tmp_fd, value, value_size); if (bytes_written < 0 || (size_t)bytes_written != value_size) { result = -20; goto cleanup; } // Build lock path path = params_path + "/.lock"; lock_fd = open(path.c_str(), O_CREAT, 0775); // Build key path path = params_path + "/d/" + std::string(key); // Take lock. result = flock(lock_fd, LOCK_EX); if (result < 0) { goto cleanup; } // change permissions to 0666 for apks result = fchmod(tmp_fd, 0666); if (result < 0) { goto cleanup; } // fsync to force persist the changes. result = fsync(tmp_fd); if (result < 0) { goto cleanup; } // Move temp into place. result = rename(tmp_path.c_str(), path.c_str()); if (result < 0) { goto cleanup; } // fsync parent directory path = params_path + "/d"; result = fsync_dir(path.c_str()); if (result < 0) { goto cleanup; } cleanup: // Release lock. if (lock_fd >= 0) { close(lock_fd); } if (tmp_fd >= 0) { if (result < 0) { remove(tmp_path.c_str()); } close(tmp_fd); } return result; } int Params::delete_db_value(std::string key) { int lock_fd = -1; int result; std::string path; // Build lock path, and open lockfile path = params_path + "/.lock"; lock_fd = open(path.c_str(), O_CREAT, 0775); // Take lock. result = flock(lock_fd, LOCK_EX); if (result < 0) { goto cleanup; } // Delete value. path = params_path + "/d/" + key; result = remove(path.c_str()); if (result != 0) { result = ERR_NO_VALUE; goto cleanup; } // fsync parent directory path = params_path + "/d"; result = fsync_dir(path.c_str()); if (result < 0) { goto cleanup; } cleanup: // Release lock. if (lock_fd >= 0) { close(lock_fd); } return result; } std::string Params::get(std::string key, bool block){ char* value; size_t size; int r; if (block) { r = read_db_value_blocking((const char*)key.c_str(), &value, &size); } else { r = read_db_value((const char*)key.c_str(), &value, &size); } if (r == 0){ std::string s(value, size); free(value); return s; } else { return ""; } } int Params::read_db_value(const char* key, char** value, size_t* value_sz) { std::string path = params_path + "/d/" + std::string(key); *value = static_cast(read_file(path.c_str(), value_sz)); if (*value == NULL) { return -22; } return 0; } int Params::read_db_value_blocking(const char* key, char** value, size_t* value_sz) { 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); while (!params_do_exit) { const int result = read_db_value(key, value, value_sz); if (result == 0) { break; } else { util::sleep_for(100); // 0.1 s } } std::signal(SIGINT, prev_handler_sigint); std::signal(SIGTERM, prev_handler_sigterm); return params_do_exit; // Return 0 if we had no interrupt } int Params::read_db_all(std::map *params) { int err = 0; std::string lock_path = params_path + "/.lock"; int lock_fd = open(lock_path.c_str(), 0); if (lock_fd < 0) return -1; err = flock(lock_fd, LOCK_SH); if (err < 0) { close(lock_fd); return err; } std::string key_path = params_path + "/d"; DIR *d = opendir(key_path.c_str()); if (!d) { close(lock_fd); return -1; } struct dirent *de = NULL; while ((de = readdir(d))) { if (!isalnum(de->d_name[0])) continue; std::string key = std::string(de->d_name); std::string value = util::read_file(key_path + "/" + key); (*params)[key] = value; } closedir(d); close(lock_fd); return 0; } std::vector Params::read_db_bytes(const char* param_name) { std::vector bytes; char* value; size_t sz; int result = read_db_value(param_name, &value, &sz); if (result == 0) { bytes.assign(value, value+sz); free(value); } return bytes; } bool Params::read_db_bool(const char* param_name) { std::vector bytes = read_db_bytes(param_name); return bytes.size() > 0 and bytes[0] == '1'; }