flock.c 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. /*
  2. * Node-API v8 binding for asynchronous flock(LOCK_EX | LOCK_NB).
  3. * The caller owns fd through completion; this module never opens, duplicates,
  4. * closes, or explicitly unlocks it. The callback receives zero or a positive
  5. * errno; JavaScript owns the promise and syscall error construction.
  6. */
  7. #include <node_api.h>
  8. #include <errno.h>
  9. #include <limits.h>
  10. #include <stdlib.h>
  11. #include <sys/file.h>
  12. typedef struct {
  13. napi_env env;
  14. napi_ref callback;
  15. napi_async_work work;
  16. napi_async_cleanup_hook_handle cleanup;
  17. int fd;
  18. int error;
  19. bool closing;
  20. } lock_request;
  21. static void check_status(napi_status status, const char *message) {
  22. if (status != napi_ok) {
  23. napi_fatal_error("flock", NAPI_AUTO_LENGTH, message, NAPI_AUTO_LENGTH);
  24. }
  25. }
  26. static void release_request(lock_request *request) {
  27. if (request->callback != NULL) {
  28. (void)napi_delete_reference(request->env, request->callback);
  29. }
  30. if (request->work != NULL) {
  31. (void)napi_delete_async_work(request->env, request->work);
  32. }
  33. if (request->cleanup != NULL) {
  34. (void)napi_remove_async_cleanup_hook(request->cleanup);
  35. }
  36. free(request);
  37. }
  38. static napi_value throw_setup_error(napi_env env, napi_status status,
  39. const char *message) {
  40. if (status != napi_pending_exception) {
  41. status = napi_throw_error(env, "ERR_FLOCK_ASYNC_WORK", message);
  42. bool pending;
  43. /* Error construction can fail with both generic_failure and a JS exception. */
  44. check_status(napi_is_exception_pending(env, &pending), "Cannot inspect flock setup exception");
  45. if (!pending && status != napi_pending_exception) check_status(status, message);
  46. }
  47. return NULL;
  48. }
  49. static void execute_lock(napi_env env, void *data) {
  50. (void)env;
  51. lock_request *request = data;
  52. request->error = flock(request->fd, LOCK_EX | LOCK_NB) == 0 ? 0 : errno;
  53. }
  54. static void complete_lock(napi_env env, napi_status status, void *data) {
  55. lock_request *request = data;
  56. if (env != NULL && !request->closing) {
  57. napi_value callback;
  58. napi_value receiver;
  59. napi_value result;
  60. check_status(status, "flock async work did not complete");
  61. check_status(napi_get_reference_value(env, request->callback, &callback),
  62. "Cannot retrieve flock callback");
  63. check_status(napi_get_undefined(env, &receiver), "Cannot create flock receiver");
  64. check_status(napi_create_int32(env, request->error, &result),
  65. "Cannot create flock result");
  66. status = napi_call_function(env, receiver, callback, 1, &result, NULL);
  67. } else {
  68. status = napi_ok;
  69. }
  70. release_request(request);
  71. /* Node's async-work dispatcher reports callback exceptions and handles termination. */
  72. if (status != napi_pending_exception) {
  73. check_status(status, "Cannot invoke flock callback");
  74. }
  75. }
  76. static void cleanup_lock(napi_async_cleanup_hook_handle handle, void *data) {
  77. (void)handle;
  78. lock_request *request = data;
  79. request->closing = true;
  80. /*
  81. * Running work cannot be cancelled. The hook keeps the environment alive
  82. * until completion releases both the work and this hook; it never frees
  83. * memory that the execution thread can still access.
  84. */
  85. (void)napi_cancel_async_work(request->env, request->work);
  86. }
  87. static napi_value try_lock(napi_env env, napi_callback_info info) {
  88. size_t argc = 2;
  89. napi_value argv[2];
  90. double fd;
  91. napi_status status = napi_get_cb_info(env, info, &argc, argv, NULL, NULL);
  92. if (status != napi_ok) {
  93. return throw_setup_error(env, status, "Cannot read flock arguments");
  94. }
  95. if (argc < 1 || napi_get_value_double(env, argv[0], &fd) != napi_ok) {
  96. (void)napi_throw_type_error(env, NULL, "fd must be a number");
  97. return NULL;
  98. }
  99. if (!(fd >= INT_MIN && fd <= INT_MAX) || fd != (int)fd) {
  100. (void)napi_throw_range_error(env, NULL, "fd must be a signed C int");
  101. return NULL;
  102. }
  103. napi_valuetype callback_type;
  104. if (argc < 2 || napi_typeof(env, argv[1], &callback_type) != napi_ok ||
  105. callback_type != napi_function) {
  106. (void)napi_throw_type_error(env, NULL, "callback must be a function");
  107. return NULL;
  108. }
  109. lock_request *request = calloc(1, sizeof(*request));
  110. if (request == NULL) {
  111. (void)napi_throw_error(env, "ENOMEM", "Cannot allocate flock async work");
  112. return NULL;
  113. }
  114. request->env = env;
  115. request->fd = (int)fd;
  116. status = napi_create_reference(env, argv[1], 1, &request->callback);
  117. if (status != napi_ok) {
  118. release_request(request);
  119. return throw_setup_error(env, status, "Cannot retain flock callback");
  120. }
  121. napi_value name;
  122. status = napi_create_string_utf8(env, "flock", NAPI_AUTO_LENGTH, &name);
  123. if (status == napi_ok) {
  124. status = napi_create_async_work(env, NULL, name, execute_lock, complete_lock,
  125. request, &request->work);
  126. }
  127. if (status != napi_ok) {
  128. release_request(request);
  129. return throw_setup_error(env, status, "Cannot create flock async work");
  130. }
  131. status = napi_add_async_cleanup_hook(env, cleanup_lock, request, &request->cleanup);
  132. if (status != napi_ok) {
  133. release_request(request);
  134. return throw_setup_error(env, status, "Cannot register flock environment cleanup");
  135. }
  136. status = napi_queue_async_work(env, request->work);
  137. if (status != napi_ok) {
  138. /* Queue failure schedules no completion callback. */
  139. release_request(request);
  140. return throw_setup_error(env, status, "Cannot queue flock async work");
  141. }
  142. return NULL;
  143. }
  144. NAPI_MODULE_INIT() {
  145. napi_value function;
  146. if (napi_create_function(env, "tryLock", NAPI_AUTO_LENGTH, try_lock,
  147. NULL, &function) != napi_ok ||
  148. napi_set_named_property(env, exports, "tryLock", function) != napi_ok) {
  149. return NULL;
  150. }
  151. return exports;
  152. }