OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-peercred / peercred.cc

243 lines · cc ref:
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <node_api.h>
#include <limits.h>
#include <fcntl.h>
#include <sys/file.h>

#if defined(__linux__)
#include <sys/syscall.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#else
#error "blind-peercred supports Linux, macOS, and BSD Unix sockets only"
#endif

#if defined(__linux__) && !defined(RENAME_NOREPLACE)
#define RENAME_NOREPLACE (1 << 0)
#endif

static napi_value throw_errno(napi_env env, const char *operation) {
  napi_value message;
  char buffer[256];
  snprintf(buffer, sizeof(buffer), "%s: %s", operation, strerror(errno));
  napi_create_string_utf8(env, buffer, NAPI_AUTO_LENGTH, &message);
  napi_throw_error(env, "BLIND_PEERCRED_FAILED", buffer);
  return nullptr;
}

static bool set_int64(napi_env env, napi_value object, const char *name, int64_t value) {
  napi_value key;
  napi_value encoded;
  return napi_create_string_utf8(env, name, NAPI_AUTO_LENGTH, &key) == napi_ok &&
    napi_create_int64(env, value, &encoded) == napi_ok &&
    napi_set_property(env, object, key, encoded) == napi_ok;
}

static bool get_fd(napi_env env, napi_callback_info info, const char *operation, int32_t *fd) {
  size_t argc = 1;
  napi_value argv[1];
  if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok || argc != 1) {
    napi_throw_type_error(env, "BLIND_NATIVE_ARGUMENT", operation);
    return false;
  }
  *fd = -1;
  if (napi_get_value_int32(env, argv[0], fd) != napi_ok || *fd < 0) {
    napi_throw_type_error(env, "BLIND_NATIVE_ARGUMENT", "file descriptor must be a non-negative int32");
    return false;
  }
  return true;
}

static bool get_path(napi_env env, napi_value value, const char *field, char *output, size_t capacity) {
  napi_valuetype type;
  if (napi_typeof(env, value, &type) != napi_ok || type != napi_string) {
    napi_throw_type_error(env, "BLIND_NATIVE_ARGUMENT", field);
    return false;
  }
  size_t length = 0;
  if (napi_get_value_string_utf8(env, value, nullptr, 0, &length) != napi_ok ||
      length == 0 || length + 1 > capacity) {
    napi_throw_range_error(env, "BLIND_NATIVE_ARGUMENT", "path is empty or exceeds PATH_MAX");
    return false;
  }
  size_t copied = 0;
  if (napi_get_value_string_utf8(env, value, output, capacity, &copied) != napi_ok || copied != length) {
    napi_throw_type_error(env, "BLIND_NATIVE_ARGUMENT", "path could not be encoded as UTF-8");
    return false;
  }
  output[copied] = '\0';
  return true;
}

static napi_value rename_file_no_replace(napi_env env, napi_callback_info info) {
  size_t argc = 2;
  napi_value argv[2];
  if (napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr) != napi_ok || argc != 2) {
    napi_throw_type_error(env, "BLIND_NATIVE_ARGUMENT", "renameFileNoReplace requires source and destination paths");
    return nullptr;
  }
  char source[PATH_MAX];
  char destination[PATH_MAX];
  if (!get_path(env, argv[0], "source path must be a string", source, sizeof(source)) ||
      !get_path(env, argv[1], "destination path must be a string", destination, sizeof(destination))) {
    return nullptr;
  }

  int result = -1;
#if defined(__APPLE__)
  result = renamex_np(source, destination, RENAME_EXCL);
#elif defined(__linux__)
#if defined(SYS_renameat2)
  result = static_cast<int>(syscall(SYS_renameat2, AT_FDCWD, source,
    AT_FDCWD, destination, RENAME_NOREPLACE));
#else
  errno = ENOTSUP;
#endif
#else
  errno = ENOTSUP;
#endif
  bool installed = false;
  if (result == 0) {
    installed = true;
  } else if (errno != EEXIST) {
    return throw_errno(env, "rename no-replace");
  }
  napi_value encoded;
  if (napi_get_boolean(env, installed, &encoded) != napi_ok) {
    napi_throw_error(env, "BLIND_RENAME_NOREPLACE_RESULT", "failed to encode no-replace rename result");
    return nullptr;
  }
  return encoded;
}

static napi_value rename_file_no_replace_platform_supported(napi_env env, napi_callback_info info) {
  size_t argc = 0;
  if (napi_get_cb_info(env, info, &argc, nullptr, nullptr, nullptr) != napi_ok || argc != 0) {
    napi_throw_type_error(env, "BLIND_NATIVE_ARGUMENT", "renameFileNoReplacePlatformSupported takes no arguments");
    return nullptr;
  }
  bool supported = false;
#if defined(__APPLE__)
  supported = true;
#elif defined(__linux__) && defined(SYS_renameat2)
  supported = true;
#endif
  napi_value encoded;
  if (napi_get_boolean(env, supported, &encoded) != napi_ok) {
    napi_throw_error(env, "BLIND_RENAME_NOREPLACE_RESULT", "failed to encode no-replace platform support");
    return nullptr;
  }
  return encoded;
}

static napi_value get_peer_credentials(napi_env env, napi_callback_info info) {
  int32_t fd = -1;
  if (!get_fd(env, info, "getPeerCredentials requires one file descriptor", &fd)) return nullptr;

  int64_t pid = -1;
  int64_t uid = -1;
  int64_t gid = -1;
#if defined(__linux__)
  struct ucred credentials;
  socklen_t length = sizeof(credentials);
  if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &length) != 0 || length != sizeof(credentials)) {
    return throw_errno(env, "getsockopt(SO_PEERCRED)");
  }
  pid = credentials.pid;
  uid = credentials.uid;
  gid = credentials.gid;
#else
  uid_t peer_uid;
  gid_t peer_gid;
  if (getpeereid(fd, &peer_uid, &peer_gid) != 0) return throw_errno(env, "getpeereid");
  uid = peer_uid;
  gid = peer_gid;
#endif

  napi_value result;
  if (napi_create_object(env, &result) != napi_ok ||
      !set_int64(env, result, "pid", pid) ||
      !set_int64(env, result, "uid", uid) ||
      !set_int64(env, result, "gid", gid)) {
    napi_throw_error(env, "BLIND_PEERCRED_RESULT", "failed to create peer credential result");
    return nullptr;
  }
  return result;
}

static napi_value try_exclusive_file_lock(napi_env env, napi_callback_info info) {
  int32_t fd = -1;
  if (!get_fd(env, info, "tryExclusiveFileLock requires one file descriptor", &fd)) return nullptr;
  bool acquired = false;
  if (flock(fd, LOCK_EX | LOCK_NB) == 0) {
    acquired = true;
  } else if (errno != EWOULDBLOCK && errno != EAGAIN) {
    return throw_errno(env, "flock(LOCK_EX|LOCK_NB)");
  }
  napi_value result;
  if (napi_get_boolean(env, acquired, &result) != napi_ok) {
    napi_throw_error(env, "BLIND_FILE_LOCK_RESULT", "failed to create file-lock result");
    return nullptr;
  }
  return result;
}

static napi_value release_exclusive_file_lock(napi_env env, napi_callback_info info) {
  int32_t fd = -1;
  if (!get_fd(env, info, "releaseExclusiveFileLock requires one file descriptor", &fd)) return nullptr;
  if (flock(fd, LOCK_UN) != 0) return throw_errno(env, "flock(LOCK_UN)");
  napi_value result;
  if (napi_get_undefined(env, &result) != napi_ok) {
    napi_throw_error(env, "BLIND_FILE_LOCK_RESULT", "failed to create file-unlock result");
    return nullptr;
  }
  return result;
}

static napi_value initialize(napi_env env, napi_value exports) {
  napi_value function;
  if (napi_create_function(env, "getPeerCredentials", NAPI_AUTO_LENGTH,
      get_peer_credentials, nullptr, &function) != napi_ok ||
      napi_set_named_property(env, exports, "getPeerCredentials", function) != napi_ok) {
    napi_throw_error(env, "BLIND_PEERCRED_INIT", "failed to initialize peer credential binding");
    return nullptr;
  }
  if (napi_create_function(env, "tryExclusiveFileLock", NAPI_AUTO_LENGTH,
      try_exclusive_file_lock, nullptr, &function) != napi_ok ||
      napi_set_named_property(env, exports, "tryExclusiveFileLock", function) != napi_ok ||
      napi_create_function(env, "releaseExclusiveFileLock", NAPI_AUTO_LENGTH,
      release_exclusive_file_lock, nullptr, &function) != napi_ok ||
      napi_set_named_property(env, exports, "releaseExclusiveFileLock", function) != napi_ok) {
    napi_throw_error(env, "BLIND_FILE_LOCK_INIT", "failed to initialize file-lock binding");
    return nullptr;
  }
  if (napi_create_function(env, "renameFileNoReplace", NAPI_AUTO_LENGTH,
      rename_file_no_replace, nullptr, &function) != napi_ok ||
      napi_set_named_property(env, exports, "renameFileNoReplace", function) != napi_ok) {
    napi_throw_error(env, "BLIND_RENAME_NOREPLACE_INIT", "failed to initialize no-replace rename binding");
    return nullptr;
  }
  if (napi_create_function(env, "renameFileNoReplacePlatformSupported", NAPI_AUTO_LENGTH,
      rename_file_no_replace_platform_supported, nullptr, &function) != napi_ok ||
      napi_set_named_property(env, exports, "renameFileNoReplacePlatformSupported", function) != napi_ok) {
    napi_throw_error(env, "BLIND_RENAME_NOREPLACE_INIT", "failed to initialize no-replace platform-support binding");
    return nullptr;
  }
  return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, initialize)