text: #include "common/params.h"
  
  #ifndef _GNU_SOURCE
  #define _GNU_SOURCE
  #endif  // _GNU_SOURCE
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <dirent.h>
  #include <sys/file.h>
  #include <sys/stat.h>
  
  #include <map>
  #include <string>
  #include <iostream>
  #include <csignal>
  #include <string.h>
  
  #include "common/util.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 <params>/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<char*>(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<std::string, std::string> *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<char> Params::read_db_bytes(const char* param_name) {
    std::vector<char> 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<char> bytes = read_db_bytes(param_name);
    return bytes.size() > 0 and bytes[0] == '1';
  }
  