//
|
// Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
|
// Copyright 2018 Capitar IT Group BV <info@capitar.com>
|
//
|
// This software is supplied under the terms of the MIT License, a
|
// copy of which should be located in the distribution where this
|
// file was obtained (LICENSE.txt). A copy of the license may also be
|
// found online at https://opensource.org/licenses/MIT.
|
//
|
|
#include "core/nng_impl.h"
|
|
#ifdef NNG_PLATFORM_POSIX
|
|
#include <dirent.h>
|
#include <errno.h>
|
#include <fcntl.h>
|
#include <stdio.h>
|
#include <stdlib.h>
|
#include <string.h>
|
#include <sys/stat.h>
|
#include <sys/types.h>
|
#include <unistd.h>
|
|
// Some systems -- Android -- have BSD flock but not POSIX lockf.
|
#if defined(NNG_HAVE_FLOCK) && !defined(NNG_HAVE_LOCKF)
|
#include <sys/file.h>
|
#endif
|
|
// File support.
|
|
static int
|
nni_plat_make_parent_dirs(const char *path)
|
{
|
char *dup;
|
char *p;
|
int rv;
|
|
// creates everything up until the last component.
|
if ((dup = nni_strdup(path)) == NULL) {
|
return (NNG_ENOMEM);
|
}
|
p = dup;
|
while ((p = strchr(p, '/')) != NULL) {
|
if (p != dup) {
|
*p = '\0';
|
rv = mkdir(dup, S_IRWXU);
|
*p = '/';
|
if ((rv != 0) && (errno != EEXIST)) {
|
rv = nni_plat_errno(errno);
|
nni_strfree(dup);
|
return (rv);
|
}
|
}
|
|
// collapse grouped "/" characters
|
while (*p == '/') {
|
p++;
|
}
|
}
|
nni_strfree(dup);
|
return (0);
|
}
|
|
// nni_plat_file_put writes the named file, with the provided data,
|
// and the given size. If the file already exists it is overwritten.
|
// The permissions on the file should be limited to read and write
|
// access by the entity running the application only.
|
int
|
nni_plat_file_put(const char *name, const void *data, size_t len)
|
{
|
FILE *f;
|
int rv = 0;
|
|
// It is possible that the name contains a directory path
|
// that does not exist. In this case we try to create the
|
// entire tree.
|
if (strchr(name, '/') != NULL) {
|
if ((rv = nni_plat_make_parent_dirs(name)) != 0) {
|
return (rv);
|
}
|
}
|
|
if ((f = fopen(name, "wb")) == NULL) {
|
return (nni_plat_errno(errno));
|
}
|
if (fwrite(data, 1, len, f) != len) {
|
rv = nni_plat_errno(errno);
|
(void) unlink(name);
|
}
|
(void) fclose(f);
|
return (rv);
|
}
|
|
// nni_plat_file_get reads the entire named file, allocating storage
|
// to receive the data and returning the data and the size in the
|
// reference arguments.
|
int
|
nni_plat_file_get(const char *name, void **datap, size_t *lenp)
|
{
|
FILE * f;
|
struct stat st;
|
int rv = 0;
|
size_t len;
|
void * data;
|
|
if ((f = fopen(name, "rb")) == NULL) {
|
return (nni_plat_errno(errno));
|
}
|
|
if (stat(name, &st) != 0) {
|
rv = nni_plat_errno(errno);
|
(void) fclose(f);
|
return (rv);
|
}
|
|
len = st.st_size;
|
if (len > 0) {
|
if ((data = nni_alloc(len)) == NULL) {
|
rv = NNG_ENOMEM;
|
goto done;
|
}
|
if (fread(data, 1, len, f) != len) {
|
rv = nni_plat_errno(errno);
|
nni_free(data, len);
|
goto done;
|
}
|
} else {
|
data = NULL;
|
}
|
*datap = data;
|
*lenp = len;
|
done:
|
(void) fclose(f);
|
return (rv);
|
}
|
|
// nni_plat_file_delete deletes the named file or directory.
|
int
|
nni_plat_file_delete(const char *name)
|
{
|
if (rmdir(name) == 0) {
|
return (0);
|
}
|
if ((errno == ENOTDIR) && (unlink(name) == 0)) {
|
return (0);
|
}
|
if (errno == ENOENT) {
|
return (0);
|
}
|
return (nni_plat_errno(errno));
|
}
|
|
int
|
nni_plat_file_type(const char *name, int *typep)
|
{
|
struct stat sbuf;
|
|
if (stat(name, &sbuf) != 0) {
|
return (nni_plat_errno(errno));
|
}
|
switch (sbuf.st_mode & S_IFMT) {
|
case S_IFREG:
|
*typep = NNI_PLAT_FILE_TYPE_FILE;
|
break;
|
case S_IFDIR:
|
*typep = NNI_PLAT_FILE_TYPE_DIR;
|
break;
|
default:
|
*typep = NNI_PLAT_FILE_TYPE_OTHER;
|
break;
|
}
|
return (0);
|
}
|
|
static int
|
nni_plat_file_walk_inner(const char *name, nni_plat_file_walker walkfn,
|
void *arg, int flags, bool *stop)
|
{
|
DIR *dir;
|
|
if ((dir = opendir(name)) == NULL) {
|
return (nni_plat_errno(errno));
|
}
|
for (;;) {
|
int rv;
|
struct dirent *ent;
|
struct stat sbuf;
|
char * path;
|
int walkrv;
|
|
if ((ent = readdir(dir)) == NULL) {
|
closedir(dir);
|
return (0);
|
}
|
// Skip "." and ".." -- we would like to skip all
|
// directories, but that would require checking full
|
// paths.
|
if ((strcmp(ent->d_name, ".") == 0) ||
|
(strcmp(ent->d_name, "..") == 0)) {
|
continue;
|
}
|
if ((rv = nni_asprintf(&path, "%s/%s", name, ent->d_name)) !=
|
0) {
|
closedir(dir);
|
return (rv);
|
}
|
if (stat(path, &sbuf) != 0) {
|
if (errno == ENOENT) { // deleted while walking
|
continue;
|
}
|
rv = nni_plat_errno(errno);
|
nni_strfree(path);
|
closedir(dir);
|
return (rv);
|
}
|
if (flags & NNI_PLAT_FILE_WALK_FILES_ONLY) {
|
if ((sbuf.st_mode & S_IFMT) == S_IFREG) {
|
walkrv = walkfn(path, arg);
|
} else {
|
walkrv = NNI_PLAT_FILE_WALK_CONTINUE;
|
}
|
} else {
|
walkrv = walkfn(path, arg);
|
}
|
|
if (walkrv == NNI_PLAT_FILE_WALK_STOP) {
|
*stop = true;
|
}
|
|
if ((!*stop) && (rv != NNI_PLAT_FILE_WALK_PRUNE_CHILD) &&
|
((flags & NNI_PLAT_FILE_WALK_SHALLOW) == 0) &&
|
((sbuf.st_mode & S_IFMT) == S_IFDIR)) {
|
rv = nni_plat_file_walk_inner(
|
path, walkfn, arg, flags, stop);
|
if (rv != 0) {
|
nni_strfree(path);
|
closedir(dir);
|
return (rv);
|
}
|
}
|
|
nni_strfree(path);
|
|
if ((walkrv == NNI_PLAT_FILE_WALK_PRUNE_SIB) || (*stop)) {
|
break;
|
}
|
}
|
closedir(dir);
|
return (0);
|
}
|
|
int
|
nni_plat_file_walk(
|
const char *name, nni_plat_file_walker walkfn, void *arg, int flags)
|
{
|
bool stop = false;
|
|
return (nni_plat_file_walk_inner(name, walkfn, arg, flags, &stop));
|
}
|
|
const char *
|
nni_plat_file_basename(const char *path)
|
{
|
const char *end;
|
if ((end = strrchr(path, '/')) != NULL) {
|
return (end + 1);
|
}
|
return (path);
|
}
|
|
int
|
nni_plat_file_lock(const char *path, nni_plat_flock *lk)
|
{
|
int fd;
|
int rv;
|
if ((fd = open(path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) < 0) {
|
return (nni_plat_errno(errno));
|
}
|
#ifdef NNG_HAVE_LOCKF
|
rv = lockf(fd, F_TLOCK, 0);
|
#elif defined NNG_HAVE_FLOCK
|
rv = flock(fd, LOCK_EX | LOCK_NB);
|
#else
|
// We don't have locking support. This means you live dangerously.
|
// For example, ZeroTier cannot be sure that nothing else is using
|
// the same configuration file. If you're here, its probably an
|
// embedded scenario, and we can live with it.
|
rv = 0;
|
#endif
|
if (rv < 0) {
|
int rv = errno;
|
close(fd);
|
if (rv == EAGAIN) {
|
return (NNG_EBUSY);
|
}
|
return (nni_plat_errno(rv));
|
}
|
lk->fd = fd;
|
return (0);
|
}
|
|
void
|
nni_plat_file_unlock(nni_plat_flock *lk)
|
{
|
int fd = lk->fd;
|
lk->fd = -1;
|
(void) close(fd);
|
}
|
|
char *
|
nni_plat_temp_dir(void)
|
{
|
char *temp;
|
|
// POSIX says $TMPDIR is required.
|
if ((temp = getenv("TMPDIR")) != NULL) {
|
return (nni_strdup(temp));
|
}
|
return (nni_strdup("/tmp"));
|
}
|
|
char *
|
nni_plat_join_dir(const char *prefix, const char *suffix)
|
{
|
char *result;
|
|
if (nni_asprintf(&result, "%s/%s", prefix, suffix) == 0) {
|
return (result);
|
}
|
return (NULL);
|
}
|
|
#endif // NNG_PLATFORM_POSIX
|