aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/nifs/common/prim_file_nif.c
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/nifs/common/prim_file_nif.c')
-rw-r--r--erts/emulator/nifs/common/prim_file_nif.c1298
1 files changed, 1298 insertions, 0 deletions
diff --git a/erts/emulator/nifs/common/prim_file_nif.c b/erts/emulator/nifs/common/prim_file_nif.c
new file mode 100644
index 0000000000..a78ee62677
--- /dev/null
+++ b/erts/emulator/nifs/common/prim_file_nif.c
@@ -0,0 +1,1298 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson 2017-2018. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+#define STATIC_ERLANG_NIF 1
+
+#include "erl_nif.h"
+#include "config.h"
+#include "sys.h"
+
+#ifdef VALGRIND
+# include <valgrind/memcheck.h>
+#endif
+
+#include "erl_driver.h"
+#include "prim_file_nif.h"
+
+/* NIF interface declarations */
+static int load(ErlNifEnv *env, void** priv_data, ERL_NIF_TERM load_info);
+static int upgrade(ErlNifEnv *env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info);
+static void unload(ErlNifEnv *env, void* priv_data);
+
+static ErlNifResourceType *efile_resource_type;
+
+static ERL_NIF_TERM am_close;
+
+static ERL_NIF_TERM am_ok;
+static ERL_NIF_TERM am_error;
+static ERL_NIF_TERM am_continue;
+
+static ERL_NIF_TERM am_file_info;
+
+/* File modes */
+static ERL_NIF_TERM am_read;
+static ERL_NIF_TERM am_write;
+static ERL_NIF_TERM am_exclusive;
+static ERL_NIF_TERM am_append;
+static ERL_NIF_TERM am_sync;
+static ERL_NIF_TERM am_skip_type_check;
+
+/* enum efile_access_t; read and write are defined above.*/
+static ERL_NIF_TERM am_read_write;
+static ERL_NIF_TERM am_none;
+
+/* enum efile_advise_t */
+static ERL_NIF_TERM am_normal;
+static ERL_NIF_TERM am_random;
+static ERL_NIF_TERM am_sequential;
+static ERL_NIF_TERM am_will_need;
+static ERL_NIF_TERM am_dont_need;
+static ERL_NIF_TERM am_no_reuse;
+
+/* enum efile_filetype_t */
+static ERL_NIF_TERM am_device;
+static ERL_NIF_TERM am_directory;
+static ERL_NIF_TERM am_regular;
+static ERL_NIF_TERM am_symlink;
+static ERL_NIF_TERM am_other;
+
+/* enum efile_seek_t, 'eof' marker. */
+static ERL_NIF_TERM am_bof;
+static ERL_NIF_TERM am_cur;
+static ERL_NIF_TERM am_eof;
+
+static ERL_NIF_TERM read_info_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM set_permissions_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM set_owner_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM set_time_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+
+static ERL_NIF_TERM read_link_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM list_dir_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+
+static ERL_NIF_TERM make_hard_link_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM make_soft_link_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rename_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM make_dir_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM del_file_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM del_dir_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM get_device_cwd_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM get_cwd_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM set_cwd_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+
+static ERL_NIF_TERM read_file_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+
+static ERL_NIF_TERM open_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM close_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+
+/* Internal ops */
+static ERL_NIF_TERM delayed_close_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM get_handle_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM altname_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]);
+
+/* All file handle operations are passed through a wrapper that handles state
+ * transitions, marking it as busy during the course of the operation, and
+ * closing on completion if the owner died in the middle of an operation.
+ *
+ * This is pretty ugly but required as there's no way to tell when it's safe to
+ * asynchronously close a file; the event could have fired just before landing
+ * in a system call which will fail with EBADF at best or alias a newly opened
+ * fd at worst.
+ *
+ * The old driver got away with enqueueing the close operation on the same
+ * async queue as all of its other operations, but since dirty schedulers use a
+ * single global queue there's no natural way to schedule an asynchronous close
+ * "behind" other operations.
+ *
+ * The states may transition as follows:
+ *
+ * IDLE ->
+ * BUSY (file_handle_wrapper) |
+ * CLOSED (owner_death_callback)
+ *
+ * BUSY ->
+ * IDLE (file_handle_wrapper)
+ * CLOSED (close_nif_impl)
+ * CLOSE_PENDING (owner_death_callback)
+ *
+ * CLOSE_PENDING ->
+ * CLOSED (file_handle_wrapper)
+ *
+ * Should the owner of a file die, we can't close it immediately as that could
+ * potentially block a normal scheduler. When entering the CLOSED state from
+ * owner_death_callback, we will instead send a message to the erts_prim_file
+ * process that will then close the file through delayed_close_nif. */
+
+typedef ERL_NIF_TERM (*file_op_impl_t)(efile_data_t *d, ErlNifEnv *env,
+ int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM file_handle_wrapper(file_op_impl_t operation, ErlNifEnv *env,
+ int argc, const ERL_NIF_TERM argv[]);
+
+#define WRAP_FILE_HANDLE_EXPORT(name) \
+ static ERL_NIF_TERM name ## _impl (efile_data_t *d, ErlNifEnv *env, \
+ int argc, const ERL_NIF_TERM argv[]);\
+ static ERL_NIF_TERM name(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { \
+ return file_handle_wrapper( name ## _impl , env, argc, argv); \
+ }
+
+WRAP_FILE_HANDLE_EXPORT(read_nif)
+WRAP_FILE_HANDLE_EXPORT(write_nif)
+WRAP_FILE_HANDLE_EXPORT(pread_nif)
+WRAP_FILE_HANDLE_EXPORT(pwrite_nif)
+WRAP_FILE_HANDLE_EXPORT(seek_nif)
+WRAP_FILE_HANDLE_EXPORT(sync_nif)
+WRAP_FILE_HANDLE_EXPORT(truncate_nif)
+WRAP_FILE_HANDLE_EXPORT(allocate_nif)
+WRAP_FILE_HANDLE_EXPORT(advise_nif)
+WRAP_FILE_HANDLE_EXPORT(get_handle_nif)
+WRAP_FILE_HANDLE_EXPORT(ipread_s32bu_p32bu_nif)
+
+static ErlNifFunc nif_funcs[] = {
+ /* File handle ops */
+ {"open_nif", 2, open_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"close_nif", 1, close_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"read_nif", 2, read_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"write_nif", 2, write_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"pread_nif", 3, pread_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"pwrite_nif", 3, pwrite_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"seek_nif", 3, seek_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"sync_nif", 2, sync_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"truncate_nif", 1, truncate_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"allocate_nif", 3, allocate_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"advise_nif", 4, advise_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+
+ /* Filesystem ops */
+ {"make_hard_link_nif", 2, make_hard_link_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"make_soft_link_nif", 2, make_soft_link_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"rename_nif", 2, rename_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"read_info_nif", 2, read_info_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"set_permissions_nif", 2, set_permissions_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"set_owner_nif", 3, set_owner_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"set_time_nif", 4, set_time_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"read_link_nif", 1, read_link_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"list_dir_nif", 1, list_dir_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"make_dir_nif", 1, make_dir_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"del_file_nif", 1, del_file_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"del_dir_nif", 1, del_dir_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"get_device_cwd_nif", 1, get_device_cwd_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"set_cwd_nif", 1, set_cwd_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"get_cwd_nif", 0, get_cwd_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+
+ /* These operations are equivalent to chained calls of other operations,
+ * but have been moved down to avoid excessive rescheduling. */
+ {"ipread_s32bu_p32bu_nif", 3, ipread_s32bu_p32bu_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"read_file_nif", 1, read_file_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+
+ /* Internal ops. */
+ {"get_handle_nif", 1, get_handle_nif},
+ {"delayed_close_nif", 1, delayed_close_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+ {"altname_nif", 1, altname_nif, ERL_NIF_DIRTY_JOB_IO_BOUND},
+};
+
+ERL_NIF_INIT(prim_file, nif_funcs, load, NULL, upgrade, unload)
+
+static ErlNifPid erts_prim_file_pid;
+
+static void owner_death_callback(ErlNifEnv* env, void* obj, ErlNifPid* pid, ErlNifMonitor* mon);
+
+static int load(ErlNifEnv *env, void** priv_data, ERL_NIF_TERM prim_file_pid)
+{
+ ErlNifResourceTypeInit callbacks;
+
+ if(!enif_get_local_pid(env, prim_file_pid, &erts_prim_file_pid)) {
+ ASSERT(!"bad pid passed to prim_file_nif");
+ }
+
+ am_close = enif_make_atom(env, "close");
+
+ am_ok = enif_make_atom(env, "ok");
+ am_error = enif_make_atom(env, "error");
+ am_continue = enif_make_atom(env, "continue");
+
+ am_read = enif_make_atom(env, "read");
+ am_write = enif_make_atom(env, "write");
+ am_exclusive = enif_make_atom(env, "exclusive");
+ am_append = enif_make_atom(env, "append");
+ am_sync = enif_make_atom(env, "sync");
+ am_skip_type_check = enif_make_atom(env, "skip_type_check");
+
+ am_read_write = enif_make_atom(env, "read_write");
+ am_none = enif_make_atom(env, "none");
+
+ am_normal = enif_make_atom(env, "normal");
+ am_random = enif_make_atom(env, "random");
+ am_sequential = enif_make_atom(env, "sequential");
+ am_will_need = enif_make_atom(env, "will_need");
+ am_dont_need = enif_make_atom(env, "dont_need");
+ am_no_reuse = enif_make_atom(env, "no_reuse");
+
+ am_device = enif_make_atom(env, "device");
+ am_directory = enif_make_atom(env, "directory");
+ am_regular = enif_make_atom(env, "regular");
+ am_symlink = enif_make_atom(env, "symlink");
+ am_other = enif_make_atom(env, "other");
+
+ am_file_info = enif_make_atom(env, "file_info");
+
+ am_bof = enif_make_atom(env, "bof");
+ am_cur = enif_make_atom(env, "cur");
+ am_eof = enif_make_atom(env, "eof");
+
+ callbacks.down = owner_death_callback;
+ callbacks.dtor = NULL;
+ callbacks.stop = NULL;
+
+ efile_resource_type = enif_open_resource_type_x(env, "efile", &callbacks,
+ ERL_NIF_RT_CREATE, NULL);
+
+ *priv_data = NULL;
+
+ return 0;
+}
+
+static void unload(ErlNifEnv *env, void* priv_data)
+{
+
+}
+
+static int upgrade(ErlNifEnv *env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
+{
+ if(*old_priv_data != NULL) {
+ return -1; /* Don't know how to do that */
+ }
+ if(*priv_data != NULL) {
+ return -1; /* Don't know how to do that */
+ }
+ if(load(env, priv_data, load_info)) {
+ return -1;
+ }
+ return 0;
+}
+
+static ERL_NIF_TERM posix_error_to_tuple(ErlNifEnv *env, posix_errno_t posix_errno) {
+ ERL_NIF_TERM error = enif_make_atom(env, erl_errno_id(posix_errno));
+ return enif_make_tuple2(env, am_error, error);
+}
+
+static int get_file_data(ErlNifEnv *env, ERL_NIF_TERM opaque, efile_data_t **d) {
+ return enif_get_resource(env, opaque, efile_resource_type, (void **)d);
+}
+
+static ERL_NIF_TERM file_handle_wrapper(file_op_impl_t operation, ErlNifEnv *env,
+ int argc, const ERL_NIF_TERM argv[]) {
+
+ efile_data_t *d;
+
+ enum efile_state_t previous_state;
+ ERL_NIF_TERM result;
+
+ if(argc < 1 || !get_file_data(env, argv[0], &d)) {
+ return enif_make_badarg(env);
+ }
+
+ previous_state = erts_atomic32_cmpxchg_acqb(&d->state,
+ EFILE_STATE_BUSY, EFILE_STATE_IDLE);
+
+ if(previous_state == EFILE_STATE_IDLE) {
+ result = operation(d, env, argc - 1, &argv[1]);
+
+ previous_state = erts_atomic32_cmpxchg_relb(&d->state,
+ EFILE_STATE_IDLE, EFILE_STATE_BUSY);
+
+ ASSERT(previous_state != EFILE_STATE_IDLE);
+
+ if(previous_state == EFILE_STATE_CLOSE_PENDING) {
+ /* This is the only point where a change from CLOSE_PENDING is
+ * possible, and we're running synchronously, so we can't race with
+ * anything else here. */
+ posix_errno_t ignored;
+
+ erts_atomic32_set_acqb(&d->state, EFILE_STATE_CLOSED);
+ efile_close(d, &ignored);
+ }
+ } else {
+ /* CLOSE_PENDING should be impossible at this point since it requires
+ * a transition from BUSY; the only valid state here is CLOSED. */
+ ASSERT(previous_state == EFILE_STATE_CLOSED);
+
+ result = posix_error_to_tuple(env, EINVAL);
+ }
+
+ return result;
+}
+
+/* This is a special close operation used by the erts_prim_file process for
+ * cleaning up orphaned files. It differs from the ordinary close_nif in that
+ * it only works for files that have already entered the CLOSED state. */
+static ERL_NIF_TERM delayed_close_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t ignored;
+ efile_data_t *d;
+
+ ASSERT(argc == 1);
+ if(!get_file_data(env, argv[0], &d)) {
+ return enif_make_badarg(env);
+ }
+
+ ASSERT(erts_atomic32_read_acqb(&d->state) == EFILE_STATE_CLOSED);
+ efile_close(d, &ignored);
+
+ return am_ok;
+}
+
+static void owner_death_callback(ErlNifEnv* env, void* obj, ErlNifPid* pid, ErlNifMonitor* mon) {
+ efile_data_t *d = (efile_data_t*)obj;
+
+ (void)env;
+ (void)pid;
+ (void)mon;
+
+ for(;;) {
+ enum efile_state_t previous_state;
+
+ previous_state = erts_atomic32_cmpxchg_acqb(&d->state,
+ EFILE_STATE_CLOSED, EFILE_STATE_IDLE);
+
+ switch(previous_state) {
+ case EFILE_STATE_IDLE:
+ {
+ /* We cannot close the file here as that could block a normal
+ * scheduler, so we tell erts_prim_file to do it for us.
+ *
+ * This can in turn become a bottleneck (especially in cases
+ * like NFS failure), but it's less problematic than blocking
+ * thread progress. */
+ ERL_NIF_TERM message, file_ref;
+
+ file_ref = enif_make_resource(env, d);
+ message = enif_make_tuple2(env, am_close, file_ref);
+
+ if(!enif_send(env, &erts_prim_file_pid, NULL, message)) {
+ ERTS_INTERNAL_ERROR("Failed to defer prim_file close.");
+ }
+
+ return;
+ }
+ case EFILE_STATE_CLOSE_PENDING:
+ case EFILE_STATE_CLOSED:
+ /* We're either already closed or managed to mark ourselves for
+ * closure in the previous iteration. */
+ return;
+ case EFILE_STATE_BUSY:
+ /* Schedule ourselves to be closed once the current operation
+ * finishes, retrying the [IDLE -> CLOSED] transition in case we
+ * narrowly passed the [BUSY -> IDLE] one. */
+ erts_atomic32_cmpxchg_nob(&d->state,
+ EFILE_STATE_CLOSE_PENDING, EFILE_STATE_BUSY);
+ break;
+ }
+ }
+}
+
+static ERL_NIF_TERM efile_filetype_to_atom(enum efile_filetype_t type) {
+ switch(type) {
+ case EFILE_FILETYPE_DEVICE: return am_device;
+ case EFILE_FILETYPE_DIRECTORY: return am_directory;
+ case EFILE_FILETYPE_REGULAR: return am_regular;
+ case EFILE_FILETYPE_SYMLINK: return am_symlink;
+ case EFILE_FILETYPE_OTHER: return am_other;
+ }
+
+ return am_other;
+}
+
+static ERL_NIF_TERM efile_access_to_atom(enum efile_access_t type) {
+ if(type & EFILE_ACCESS_READ && !(type & EFILE_ACCESS_WRITE)) {
+ return am_read;
+ } else if(type & EFILE_ACCESS_WRITE && !(type & EFILE_ACCESS_READ)) {
+ return am_write;
+ } else if(type & EFILE_ACCESS_READ_WRITE) {
+ return am_read_write;
+ }
+
+ return am_none;
+}
+
+static enum efile_modes_t efile_translate_modelist(ErlNifEnv *env, ERL_NIF_TERM list) {
+ enum efile_modes_t modes;
+ ERL_NIF_TERM head, tail;
+
+ modes = 0;
+
+ while(enif_get_list_cell(env, list, &head, &tail)) {
+ if(enif_is_identical(head, am_read)) {
+ modes |= EFILE_MODE_READ;
+ } else if(enif_is_identical(head, am_write)) {
+ modes |= EFILE_MODE_WRITE;
+ } else if(enif_is_identical(head, am_exclusive)) {
+ modes |= EFILE_MODE_EXCLUSIVE;
+ } else if(enif_is_identical(head, am_append)) {
+ modes |= EFILE_MODE_APPEND;
+ } else if(enif_is_identical(head, am_sync)) {
+ modes |= EFILE_MODE_SYNC;
+ } else if(enif_is_identical(head, am_skip_type_check)) {
+ modes |= EFILE_MODE_SKIP_TYPE_CHECK;
+ } else {
+ /* Modes like 'raw', 'ram', 'delayed_writes' etc are handled
+ * further up the chain. */
+ }
+
+ list = tail;
+ }
+
+ if(modes & (EFILE_MODE_APPEND | EFILE_MODE_EXCLUSIVE)) {
+ /* 'append' and 'exclusive' are documented as "open for writing." */
+ modes |= EFILE_MODE_WRITE;
+ } else if(!(modes & EFILE_MODE_READ_WRITE)) {
+ /* Defaulting to read if !(W|R) is undocumented, but specifically
+ * tested against in file_SUITE. */
+ modes |= EFILE_MODE_READ;
+ }
+
+ return modes;
+}
+
+static ERL_NIF_TERM open_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+ efile_data_t *d;
+
+ ErlNifPid controlling_process;
+ enum efile_modes_t modes;
+ ERL_NIF_TERM result;
+ efile_path_t path;
+
+ if(argc != 2 || !enif_is_list(env, argv[1])) {
+ return enif_make_badarg(env);
+ }
+
+ modes = efile_translate_modelist(env, argv[1]);
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_open(&path, modes, efile_resource_type, &d))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ enif_self(env, &controlling_process);
+
+ if(enif_monitor_process(env, d, &controlling_process, &d->monitor)) {
+ /* We need to close the file manually as we haven't registered a
+ * destructor. */
+ posix_errno_t ignored;
+
+ erts_atomic32_set_acqb(&d->state, EFILE_STATE_CLOSED);
+ efile_close(d, &ignored);
+
+ return posix_error_to_tuple(env, EINVAL);
+ }
+
+ /* Note that we do not call enif_release_resource at this point. While it's
+ * normally safe to leave resource management to the GC, efile_close is a
+ * blocking operation which must not be done in the GC callback, and we
+ * can't defer it as the resource is gone as soon as it returns.
+ *
+ * We instead keep the resource alive until efile_close is called, after
+ * which it's safe to leave things to the GC. If the controlling process
+ * were to die before the user had a chance to close their file, the above
+ * monitor will tell the erts_prim_file process to close it for them. */
+ result = enif_make_resource(env, d);
+
+ return enif_make_tuple2(env, am_ok, result);
+}
+
+static ERL_NIF_TERM close_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ enum efile_state_t previous_state;
+ efile_data_t *d;
+
+ ASSERT(argc == 1);
+ if(!get_file_data(env, argv[0], &d)) {
+ return enif_make_badarg(env);
+ }
+
+ previous_state = erts_atomic32_cmpxchg_acqb(&d->state,
+ EFILE_STATE_CLOSED, EFILE_STATE_IDLE);
+
+ if(previous_state == EFILE_STATE_IDLE) {
+ posix_errno_t error;
+
+ enif_demonitor_process(env, d, &d->monitor);
+
+ if(!efile_close(d, &error)) {
+ return posix_error_to_tuple(env, error);
+ }
+
+ return am_ok;
+ } else {
+ /* CLOSE_PENDING should be impossible at this point since it requires
+ * a transition from BUSY; the only valid state here is CLOSED. */
+ ASSERT(previous_state == EFILE_STATE_CLOSED);
+
+ return posix_error_to_tuple(env, EINVAL);
+ }
+}
+
+static ERL_NIF_TERM read_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ Sint64 bytes_read, block_size;
+ SysIOVec read_vec[1];
+ ErlNifBinary result;
+
+ if(argc != 1 || !enif_is_number(env, argv[0])) {
+ return enif_make_badarg(env);
+ }
+
+ if(!enif_get_int64(env, argv[0], &block_size) || block_size < 0) {
+ return posix_error_to_tuple(env, EINVAL);
+ }
+
+ if(!enif_alloc_binary(block_size, &result)) {
+ return posix_error_to_tuple(env, ENOMEM);
+ }
+
+ read_vec[0].iov_base = result.data;
+ read_vec[0].iov_len = result.size;
+
+ bytes_read = efile_readv(d, read_vec, 1);
+ ASSERT(bytes_read <= block_size);
+
+ if(bytes_read < 0) {
+ enif_release_binary(&result);
+ return posix_error_to_tuple(env, d->posix_errno);
+ } else if(bytes_read == 0) {
+ enif_release_binary(&result);
+ return am_eof;
+ }
+
+ if(bytes_read < block_size && !enif_realloc_binary(&result, bytes_read)) {
+ ERTS_INTERNAL_ERROR("Failed to shrink read result.");
+ }
+
+ return enif_make_tuple2(env, am_ok, enif_make_binary(env, &result));
+}
+
+static ERL_NIF_TERM write_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ ErlNifIOVec vec, *input = &vec;
+ Sint64 bytes_written;
+ ERL_NIF_TERM tail;
+
+ if(argc != 1 || !enif_inspect_iovec(env, 64, argv[0], &tail, &input)) {
+ return enif_make_badarg(env);
+ }
+
+ bytes_written = efile_writev(d, input->iov, input->iovcnt);
+
+ if(bytes_written < 0) {
+ return posix_error_to_tuple(env, d->posix_errno);
+ }
+
+ if(!enif_is_empty_list(env, tail)) {
+ ASSERT(bytes_written > 0);
+ return enif_make_tuple2(env, am_continue, tail);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM pread_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ Sint64 bytes_read, block_size, offset;
+ SysIOVec read_vec[1];
+ ErlNifBinary result;
+
+ if(argc != 2 || !enif_is_number(env, argv[0])
+ || !enif_is_number(env, argv[1])) {
+ return enif_make_badarg(env);
+ }
+
+ if(!enif_get_int64(env, argv[0], &offset) ||
+ !enif_get_int64(env, argv[1], &block_size) ||
+ (offset < 0 || block_size < 0)) {
+ return posix_error_to_tuple(env, EINVAL);
+ }
+
+ if(!enif_alloc_binary(block_size, &result)) {
+ return posix_error_to_tuple(env, ENOMEM);
+ }
+
+ read_vec[0].iov_base = result.data;
+ read_vec[0].iov_len = result.size;
+
+ bytes_read = efile_preadv(d, offset, read_vec, 1);
+
+ if(bytes_read < 0) {
+ enif_release_binary(&result);
+ return posix_error_to_tuple(env, d->posix_errno);
+ } else if(bytes_read == 0) {
+ enif_release_binary(&result);
+ return am_eof;
+ }
+
+ if(bytes_read < block_size && !enif_realloc_binary(&result, bytes_read)) {
+ ERTS_INTERNAL_ERROR("Failed to shrink pread result.");
+ }
+
+ return enif_make_tuple2(env, am_ok, enif_make_binary(env, &result));
+}
+
+static ERL_NIF_TERM pwrite_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ ErlNifIOVec vec, *input = &vec;
+ Sint64 bytes_written, offset;
+ ERL_NIF_TERM tail;
+
+ if(argc != 2 || !enif_is_number(env, argv[0])
+ || !enif_inspect_iovec(env, 64, argv[1], &tail, &input)) {
+ return enif_make_badarg(env);
+ }
+
+ if(!enif_get_int64(env, argv[0], &offset) || offset < 0) {
+ return posix_error_to_tuple(env, EINVAL);
+ }
+
+ bytes_written = efile_pwritev(d, offset, input->iov, input->iovcnt);
+
+ if(bytes_written < 0) {
+ return posix_error_to_tuple(env, d->posix_errno);
+ }
+
+ if(!enif_is_empty_list(env, tail)) {
+ ASSERT(bytes_written > 0);
+ return enif_make_tuple3(env, am_continue,
+ enif_make_int64(env, bytes_written), tail);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM seek_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ Sint64 new_position, offset;
+ enum efile_seek_t seek;
+
+ if(argc != 2 || !enif_get_int64(env, argv[1], &offset)) {
+ return enif_make_badarg(env);
+ }
+
+ if(enif_is_identical(argv[0], am_bof)) {
+ seek = EFILE_SEEK_BOF;
+ } else if(enif_is_identical(argv[0], am_cur)) {
+ seek = EFILE_SEEK_CUR;
+ } else if(enif_is_identical(argv[0], am_eof)) {
+ seek = EFILE_SEEK_EOF;
+ } else {
+ return enif_make_badarg(env);
+ }
+
+ if(!efile_seek(d, seek, offset, &new_position)) {
+ return posix_error_to_tuple(env, d->posix_errno);
+ }
+
+ return enif_make_tuple2(env, am_ok, enif_make_uint64(env, new_position));
+}
+
+static ERL_NIF_TERM sync_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ int data_only;
+
+ if(argc != 1 || !enif_get_int(env, argv[0], &data_only)) {
+ return enif_make_badarg(env);
+ }
+
+ if(!efile_sync(d, data_only)) {
+ return posix_error_to_tuple(env, d->posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM truncate_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ if(argc != 0) {
+ return enif_make_badarg(env);
+ }
+
+ if(!efile_truncate(d)) {
+ return posix_error_to_tuple(env, d->posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM allocate_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ Sint64 offset, length;
+
+ if(argc != 2 || !enif_is_number(env, argv[0])
+ || !enif_is_number(env, argv[1])) {
+ return enif_make_badarg(env);
+ }
+
+ if(!enif_get_int64(env, argv[0], &offset) ||
+ !enif_get_int64(env, argv[1], &length) ||
+ (offset < 0 || length < 0)) {
+ return posix_error_to_tuple(env, EINVAL);
+ }
+
+ if(!efile_allocate(d, offset, length)) {
+ return posix_error_to_tuple(env, d->posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM advise_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ enum efile_advise_t advise;
+ Sint64 offset, length;
+
+ if(argc != 3 || !enif_is_number(env, argv[0])
+ || !enif_is_number(env, argv[1])) {
+ return enif_make_badarg(env);
+ }
+
+ if(!enif_get_int64(env, argv[0], &offset) ||
+ !enif_get_int64(env, argv[1], &length) ||
+ (offset < 0 || length < 0)) {
+ return posix_error_to_tuple(env, EINVAL);
+ }
+
+ if(enif_is_identical(argv[2], am_normal)) {
+ advise = EFILE_ADVISE_NORMAL;
+ } else if(enif_is_identical(argv[2], am_random)) {
+ advise = EFILE_ADVISE_RANDOM;
+ } else if(enif_is_identical(argv[2], am_sequential)) {
+ advise = EFILE_ADVISE_SEQUENTIAL;
+ } else if(enif_is_identical(argv[2], am_will_need)) {
+ advise = EFILE_ADVISE_WILL_NEED;
+ } else if(enif_is_identical(argv[2], am_dont_need)) {
+ advise = EFILE_ADVISE_DONT_NEED;
+ } else if(enif_is_identical(argv[2], am_no_reuse)) {
+ advise = EFILE_ADVISE_NO_REUSE;
+ } else {
+ /* The tests check for EINVAL instead of badarg. Sigh. */
+ return posix_error_to_tuple(env, EINVAL);
+ }
+
+ if(!efile_advise(d, offset, length, advise)) {
+ return posix_error_to_tuple(env, d->posix_errno);
+ }
+
+ return am_ok;
+}
+
+/* This undocumented function reads a pointer and then reads the data block
+ * described by said pointer. It was reverse-engineered from the old
+ * implementation so while all tests pass it may not be entirely correct. Our
+ * current understanding is as follows:
+ *
+ * Pointer layout:
+ *
+ * <<Size:1/integer-unit:32, Offset:1/integer-unit:32>>
+ *
+ * Where Offset is the -absolute- address to the data block.
+ *
+ * *) If we fail to read the pointer block in its entirety, we return eof.
+ * *) If the provided max_payload_size is larger than Size, we return eof.
+ * *) If we fail to read any data whatsoever at Offset, we return
+ * {ok, {Size, Offset, eof}}
+ * *) Otherwise, we return {ok, {Size, Offset, Data}}. Note that the size
+ * of Data may be smaller than Size if we encounter EOF before we could
+ * read the entire block.
+ *
+ * On errors we'll return {error, posix()} regardless of whether they
+ * happened before or after reading the pointer block. */
+static ERL_NIF_TERM ipread_s32bu_p32bu_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ Sint64 payload_offset, payload_size;
+
+ SysIOVec read_vec[1];
+ Sint64 bytes_read;
+
+ ErlNifBinary payload;
+
+ if(argc != 2 || !enif_is_number(env, argv[0])
+ || !enif_is_number(env, argv[1])) {
+ return enif_make_badarg(env);
+ }
+
+ {
+ Sint64 max_payload_size, pointer_offset;
+ unsigned char pointer_block[8];
+
+ if(!enif_get_int64(env, argv[0], &pointer_offset) ||
+ !enif_get_int64(env, argv[1], &max_payload_size) ||
+ (pointer_offset < 0 || max_payload_size >= 1u << 31)) {
+ return posix_error_to_tuple(env, EINVAL);
+ }
+
+ read_vec[0].iov_base = pointer_block;
+ read_vec[0].iov_len = sizeof(pointer_block);
+
+ bytes_read = efile_preadv(d, pointer_offset, read_vec, 1);
+
+ if(bytes_read < 0) {
+ return posix_error_to_tuple(env, d->posix_errno);
+ } else if(bytes_read < sizeof(pointer_block)) {
+ return am_eof;
+ }
+
+ payload_size = (Uint32)get_int32(&pointer_block[0]);
+ payload_offset = (Uint32)get_int32(&pointer_block[4]);
+
+ if(payload_size > max_payload_size) {
+ return am_eof;
+ }
+ }
+
+ if(!enif_alloc_binary(payload_size, &payload)) {
+ return posix_error_to_tuple(env, ENOMEM);
+ }
+
+ read_vec[0].iov_base = payload.data;
+ read_vec[0].iov_len = payload.size;
+
+ bytes_read = efile_preadv(d, payload_offset, read_vec, 1);
+
+ if(bytes_read < 0) {
+ enif_release_binary(&payload);
+ return posix_error_to_tuple(env, d->posix_errno);
+ } else if(bytes_read == 0) {
+ enif_release_binary(&payload);
+
+ return enif_make_tuple2(env, am_ok,
+ enif_make_tuple3(env,
+ enif_make_uint(env, payload_size),
+ enif_make_uint(env, payload_offset),
+ am_eof));
+ }
+
+ if(bytes_read < payload.size && !enif_realloc_binary(&payload, bytes_read)) {
+ ERTS_INTERNAL_ERROR("Failed to shrink ipread payload.");
+ }
+
+ return enif_make_tuple2(env, am_ok,
+ enif_make_tuple3(env,
+ enif_make_uint(env, payload_size),
+ enif_make_uint(env, payload_offset),
+ enif_make_binary(env, &payload)));
+}
+
+static ERL_NIF_TERM get_handle_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ if(argc != 0) {
+ return enif_make_badarg(env);
+ }
+
+ return efile_get_handle(env, d);
+}
+
+static ERL_NIF_TERM read_info_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_fileinfo_t info = {0};
+ efile_path_t path;
+ int follow_links;
+
+ if(argc != 2 || !enif_get_int(env, argv[1], &follow_links)) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_read_info(&path, follow_links, &info))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ /* #file_info as declared in file.hrl */
+ return enif_make_tuple(env, 14,
+ am_file_info,
+ enif_make_uint64(env, info.size),
+ efile_filetype_to_atom(info.type),
+ efile_access_to_atom(info.access),
+ enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info.a_time)),
+ enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info.m_time)),
+ enif_make_int64(env, MAX(EFILE_MIN_FILETIME, info.c_time)),
+ enif_make_uint(env, info.mode),
+ enif_make_uint(env, info.links),
+ enif_make_uint(env, info.major_device),
+ enif_make_uint(env, info.minor_device),
+ enif_make_uint(env, info.inode),
+ enif_make_uint(env, info.uid),
+ enif_make_uint(env, info.gid)
+ );
+}
+
+static ERL_NIF_TERM set_permissions_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t path;
+ unsigned int permissions;
+
+ if(argc != 2 || !enif_get_uint(env, argv[1], &permissions)) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_set_permissions(&path, permissions))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM set_owner_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t path;
+ int uid, gid;
+
+ if(argc != 3 || !enif_get_int(env, argv[1], &uid)
+ || !enif_get_int(env, argv[2], &gid)) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_set_owner(&path, uid, gid))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM set_time_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ Sint64 accessed, modified, created;
+ efile_path_t path;
+
+ if(argc != 4 || !enif_get_int64(env, argv[1], &accessed)
+ || !enif_get_int64(env, argv[2], &modified)
+ || !enif_get_int64(env, argv[3], &created)) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_set_time(&path, accessed, modified, created))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM read_link_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t path;
+ ERL_NIF_TERM result;
+
+ if(argc != 1) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_read_link(env, &path, &result))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return enif_make_tuple2(env, am_ok, result);
+}
+
+static ERL_NIF_TERM list_dir_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t path;
+ ERL_NIF_TERM result;
+
+ if(argc != 1) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_list_dir(env, &path, &result))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return enif_make_tuple2(env, am_ok, result);
+}
+
+static ERL_NIF_TERM rename_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t existing_path, new_path;
+
+ if(argc != 2) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &existing_path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_marshal_path(env, argv[1], &new_path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_rename(&existing_path, &new_path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM make_hard_link_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t existing_path, new_path;
+
+ if(argc != 2) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &existing_path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_marshal_path(env, argv[1], &new_path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_make_hard_link(&existing_path, &new_path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM make_soft_link_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t existing_path, new_path;
+
+ if(argc != 2) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &existing_path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_marshal_path(env, argv[1], &new_path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_make_soft_link(&existing_path, &new_path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM make_dir_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t path;
+
+ if(argc != 1) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_make_dir(&path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM del_file_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t path;
+
+ if(argc != 1) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_del_file(&path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM del_dir_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t path;
+
+ if(argc != 1) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_del_dir(&path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return am_ok;
+}
+
+static ERL_NIF_TERM get_device_cwd_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ ERL_NIF_TERM result;
+ int device_index;
+
+ if(argc != 1 || !enif_get_int(env, argv[0], &device_index)) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_get_device_cwd(env, device_index, &result))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return enif_make_tuple2(env, am_ok, result);
+}
+
+static ERL_NIF_TERM get_cwd_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+ ERL_NIF_TERM result;
+
+ if(argc != 0) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_get_cwd(env, &result))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return enif_make_tuple2(env, am_ok, result);
+}
+
+static ERL_NIF_TERM set_cwd_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t path;
+
+ if(argc != 1) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_set_cwd(&path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return am_ok;
+}
+
+/** @brief Reads an entire file into \c result, stopping after \c size bytes or
+ * EOF. It will read until EOF if size is 0. */
+static posix_errno_t read_file(efile_data_t *d, size_t size, ErlNifBinary *result) {
+ size_t initial_buffer_size;
+ ssize_t bytes_read;
+
+ if(size == 0) {
+ initial_buffer_size = 16 << 10;
+ } else {
+ initial_buffer_size = size;
+ }
+
+ if(!enif_alloc_binary(initial_buffer_size, result)) {
+ return ENOMEM;
+ }
+
+ bytes_read = 0;
+
+ for(;;) {
+ ssize_t block_bytes_read;
+ SysIOVec read_vec[1];
+
+ read_vec[0].iov_base = result->data + bytes_read;
+ read_vec[0].iov_len = result->size - bytes_read;
+
+ block_bytes_read = efile_readv(d, read_vec, 1);
+
+ if(block_bytes_read < 0) {
+ enif_release_binary(result);
+ return d->posix_errno;
+ }
+
+ bytes_read += block_bytes_read;
+
+ if(block_bytes_read < (result->size - bytes_read)) {
+ /* EOF */
+ break;
+ } else if(bytes_read == size) {
+ break;
+ }
+
+ if(!enif_realloc_binary(result, bytes_read * 2)) {
+ enif_release_binary(result);
+ return ENOMEM;
+ }
+ }
+
+ /* The file may have shrunk since we queried its size, so we have to do
+ * this even when the size is known. */
+ if(bytes_read < result->size && !enif_realloc_binary(result, bytes_read)) {
+ ERTS_INTERNAL_ERROR("Failed to shrink read_file result.");
+ }
+
+ return 0;
+}
+
+static ERL_NIF_TERM read_file_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno, ignored;
+
+ efile_fileinfo_t info = {0};
+ efile_path_t path;
+ efile_data_t *d;
+
+ ErlNifBinary result;
+
+ if(argc != 1) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_read_info(&path, 1, &info))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_open(&path, EFILE_MODE_READ, efile_resource_type, &d))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ posix_errno = read_file(d, info.size, &result);
+
+ erts_atomic32_set_acqb(&d->state, EFILE_STATE_CLOSED);
+ efile_close(d, &ignored);
+
+ if(posix_errno) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return enif_make_tuple2(env, am_ok, enif_make_binary(env, &result));
+}
+
+static ERL_NIF_TERM altname_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
+ posix_errno_t posix_errno;
+
+ efile_path_t path;
+ ERL_NIF_TERM result;
+
+ if(argc != 1) {
+ return enif_make_badarg(env);
+ }
+
+ if((posix_errno = efile_marshal_path(env, argv[0], &path))) {
+ return posix_error_to_tuple(env, posix_errno);
+ } else if((posix_errno = efile_altname(env, &path, &result))) {
+ return posix_error_to_tuple(env, posix_errno);
+ }
+
+ return enif_make_tuple2(env, am_ok, result);
+}