Here’s a NIF:
#include <erl_nif.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
// detect operating systems that I know something about
#ifdef __APPLE__
#define PLATFORM_MACOS
#elif __linux__
#define PLATFORM_LINUX
#endif
// Useful resource:
// https://andrealeopard.com/posts/using-c-from-elixir-with-nifs/#defining-a-nif
// ERL_NIF_TERM is a "wrapper" type that represents all Erlang types
// (like binary, list, tuple, and so on) in C.
static ERL_NIF_TERM get_file_time(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
char file_path[1024];
long seconds, nanoseconds;
if (enif_get_string(env, argv[0], file_path, sizeof(file_path), ERL_NIF_LATIN1) <= 0) {
// erl_nif.h provides several enif_make_* functions
// to convert C values back to Erlang values.
return enif_make_badarg(env);
}
struct stat filestat;
if (stat(file_path, &filestat) == 0)
{
#ifdef PLATFORM_MACOS
seconds = filestat.st_mtimespec.tv_sec;
nanoseconds = filestat.st_mtimespec.tv_nsec;
#elif defined(PLATFORM_LINUX)
seconds = filestat.st_mtim.tv_sec;
nanoseconds = filestat.st_mtim.tv_nsec;
#else
seconds = filestat.st_mtime;
#endif
return enif_make_tuple2(env,
// A enif_make_atom
enif_make_atom(env, "ok"),
enif_make_tuple2(env,
// Here's a enif_make_long
enif_make_long(env, seconds),
enif_make_long(env, nanoseconds)
)
);
}
else
{
// Here's a enif_make_tuple{n}
return enif_make_tuple2(env,
// https://stackoverflow.com/questions/503878/how-to-know-what-the-errno-means
enif_make_atom(env, "error"),
enif_make_int(env, errno)
);
}
}
// Let's define the array of ErlNifFunc beforehand:
static ErlNifFunc nif_funcs[] =
{
// {erl_function_name, erl_function_arity, c_function}
{"get_file_time", 1, get_file_time}
};
// We now have to export the function we wrote to Erlang.
// We'll have to use the ERL_NIF_INIT macro. It looks like this:
ERL_NIF_INIT(Elixir.FileTime, nif_funcs, NULL, NULL, NULL, NULL);
I compiled it using this command:
gcc -fPIC -I/usr/local/lib/erlang/usr/include/ \
-dynamiclib -undefined dynamic_lookup \
-o file_time.so file_time.c
Here’s the module I wrapped it in:
defmodule FileTime do
@on_load :load_nifs
def load_nifs do
:erlang.load_nif("./file_time", 0)
end
def get_file_time(_file) do
raise "NIF get_file_time/1 had an error. :("
end
end
(If you do a string it won’t work.)
{:ok, {seconds, nanoseconds}} = FileTime.get_file_time('file_as_char_list.txt')
{:ok, datetime} = DateTime.from_unix(seconds)
I tested it on a couple files and seemed to be working ok.