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 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: efile_close(d); 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 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; ASSERT(argc == 1); 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)); }
/* 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 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; ASSERT(argc == 2); if(!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 void gc_callback(ErlNifEnv *env, void* data) { efile_data_t *d = (efile_data_t*)data; enum efile_state_t previous_state; (void)env; previous_state = erts_atomic32_cmpxchg_acqb(&d->state, EFILE_STATE_CLOSED, EFILE_STATE_IDLE); ASSERT(previous_state != EFILE_STATE_CLOSE_PENDING && previous_state != EFILE_STATE_BUSY); if(previous_state == EFILE_STATE_IDLE) { efile_close(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; }
static ERL_NIF_TERM close_nif_impl(efile_data_t *d, ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { enum efile_state_t previous_state; ASSERT(argc == 0); previous_state = erts_atomic32_cmpxchg_acqb(&d->state, EFILE_STATE_CLOSED, EFILE_STATE_BUSY); ASSERT(previous_state == EFILE_STATE_CLOSE_PENDING || previous_state == EFILE_STATE_BUSY); if(previous_state == EFILE_STATE_BUSY) { enif_demonitor_process(env, d, &d->monitor); if(!efile_close(d)) { return posix_error_to_tuple(env, d->posix_errno); } } return am_ok; }