aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2017-10-06 18:25:56 -0700
committerGarrett D'Amore <garrett@damore.org>2017-10-09 17:12:31 -0700
commitf14cdb705cc11f39a4702c28f22007ffaaf7c9e1 (patch)
tree8ca8b7b08e0ffc14a6c9283483bf1170fee31cd0 /src
parent52d37858451ad23f077294fc78b1a3f56255c32f (diff)
downloadnng-f14cdb705cc11f39a4702c28f22007ffaaf7c9e1.tar.gz
nng-f14cdb705cc11f39a4702c28f22007ffaaf7c9e1.tar.bz2
nng-f14cdb705cc11f39a4702c28f22007ffaaf7c9e1.zip
New platform API for storage methods.
This includes converting the ZeroTier transport to use these. The new API supports file creation, retrieval, and deletion. It also supports directory methods for traversal, creation, and deletion. It also has a few methods to obtain well-known directories like $TMPDIR and $HOME. A rich test suite for this functionality is added as well.
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/core/platform.h50
-rw-r--r--src/platform/posix/posix_file.c207
-rw-r--r--src/platform/windows/win_debug.c1
-rw-r--r--src/platform/windows/win_file.c270
-rw-r--r--src/transport/zerotier/zerotier.c53
6 files changed, 536 insertions, 47 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2f2f54ff..c0080fe6 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -113,6 +113,7 @@ if (NNG_PLATFORM_POSIX)
platform/posix/posix_clock.c
platform/posix/posix_debug.c
platform/posix/posix_epdesc.c
+ platform/posix/posix_file.c
platform/posix/posix_ipc.c
platform/posix/posix_pipe.c
platform/posix/posix_pipedesc.c
@@ -131,6 +132,7 @@ if (NNG_PLATFORM_WINDOWS)
platform/windows/win_impl.h
platform/windows/win_clock.c
platform/windows/win_debug.c
+ platform/windows/win_file.c
platform/windows/win_iocp.c
platform/windows/win_ipc.c
platform/windows/win_pipe.c
diff --git a/src/core/platform.h b/src/core/platform.h
index 10074677..8b709e93 100644
--- a/src/core/platform.h
+++ b/src/core/platform.h
@@ -368,21 +368,65 @@ extern void nni_plat_pipe_close(int, int);
// determined by using an environment variable (NNG_STATE_DIR), or
// using some other application-specific method.
//
+// We also support listing keys, for the case where a key must be looked
+// up -- for example to get a list of certificates, or some such.
+//
// 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.
-extern int nni_plat_file_put(const char *, const void *, int);
+extern int nni_plat_file_put(const char *, const void *, size_t);
// 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.
-extern int nni_plat_file_get(const char *, void **, int *);
+// reference arguments. The data pointer should be freed with nni_free
+// using the supplied size when no longer needed.
+extern int nni_plat_file_get(const char *, void **, size_t *);
// nni_plat_file_delete deletes the named file.
extern int nni_plat_file_delete(const char *);
+// nni_plat_dir_open attempts to "open a directory" for listing. The
+// handle for further operations is returned in the first argument, and
+// the directory name is supplied in the second.
+extern int nni_plat_dir_open(void **, const char *);
+
+// nni_plat_dir_next gets the next directory entry. Each call returns
+// a new entry (arbitrary order). When no more entries exist, it returns
+// NNG_ENOENT. The returned name is valid until the next call to this
+// function, or until the directory is closed. Only files are returned,
+// subdirectories are not reported.
+extern int nni_plat_dir_next(void *, const char **);
+
+// nni_plat_dir_close closes the directory handle, freeing all
+// resources associated with it.
+extern void nni_plat_dir_close(void *);
+
+// nni_plat_dir_create creates a directory. Any parent directories must
+// already exist. If the directory already exists, 0 is returned.
+extern int nni_plat_dir_create(const char *);
+
+// nni_plat_dir_remove removes a directory, which must already be empty.
+// If it does not exist, 0 is returned.
+extern int nni_plat_dir_remove(const char *);
+
+// nni_plat_temp_dir returns a temporary/scratch directory for the platform
+// The result should be freed with nni_strfree().
+extern char *nni_plat_temp_dir(void);
+
+// nni_plat_home_dir returns the "home" directory for the user running the
+// application. This is a convenient place to store preferences, etc.
+// Applications should append an application specific directory name.
+// The result should be freed with nni_strfree().
+extern char *nni_plat_home_dir(void);
+
+// nni_plat_join_dir joins to path components to make a path name.
+// For example. on UNIX systems nni_plat_join_dir("/tmp", "a") returns
+// "/tmp/a". The pathname returned should be freed with nni_strfree().
+extern char *nni_plat_join_dir(const char *, const char *);
+
+//
// Actual platforms we support. This is included up front so that we can
// get the specific types that are supplied by the platform.
#if defined(NNG_PLATFORM_POSIX)
diff --git a/src/platform/posix/posix_file.c b/src/platform/posix/posix_file.c
new file mode 100644
index 00000000..d7033095
--- /dev/null
+++ b/src/platform/posix/posix_file.c
@@ -0,0 +1,207 @@
+//
+// Copyright 2017 Garrett D'Amore <garrett@damore.org>
+// Copyright 2017 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>
+
+// File support.
+
+// 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;
+
+ 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;
+ int 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 ((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;
+ }
+ *datap = data;
+ *lenp = len;
+done:
+ (void) fclose(f);
+ return (rv);
+}
+
+// nni_plat_file_delete deletes the named file.
+int
+nni_plat_file_delete(const char *name)
+{
+ if (unlink(name) < 0) {
+ return (nni_plat_errno(errno));
+ }
+ return (0);
+}
+
+// nni_plat_dir_open attempts to "open a directory" for listing. The
+// handle for further operations is returned in the first argument, and
+// the directory name is supplied in the second.
+int
+nni_plat_dir_open(void **dirp, const char *name)
+{
+ DIR *dir;
+
+ if ((dir = opendir(name)) == NULL) {
+ return (nni_plat_errno(errno));
+ }
+ *dirp = dir;
+ return (0);
+}
+
+int
+nni_plat_dir_create(const char *name)
+{
+ if (mkdir(name, S_IRWXU) != 0) {
+ if (errno == EEXIST) {
+ return (0);
+ }
+ return (nni_plat_errno(errno));
+ }
+ return (0);
+}
+
+int
+nni_plat_dir_remove(const char *name)
+{
+ if (rmdir(name) != 0) {
+ if (errno == ENOENT) {
+ return (0);
+ }
+ return (nni_plat_errno(errno));
+ }
+ return (0);
+}
+
+// nni_plat_dir_next gets the next directory entry. Each call returns
+// a new entry (arbitrary order). When no more entries exist, it returns
+// NNG_ENOENT.
+int
+nni_plat_dir_next(void *dir, const char **namep)
+{
+ for (;;) {
+ struct dirent *ent;
+
+ if ((ent = readdir((DIR *) dir)) == NULL) {
+ return (NNG_ENOENT);
+ }
+ // 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;
+ }
+ *namep = ent->d_name;
+ return (0);
+ }
+}
+
+// nni_plat_dir_close closes the directory handle, freeing all
+// resources associated with it.
+void
+nni_plat_dir_close(void *dir)
+{
+ (void) closedir((DIR *) dir);
+}
+
+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_home_dir(void)
+{
+ char *home;
+
+ // POSIX says that $HOME is *REQUIRED*. We could look in getpwuid,
+ // but realistically this is simply not required.
+ if ((home = getenv("HOME")) != NULL) {
+ return (nni_strdup(home));
+ }
+ return (NULL);
+}
+
+char *
+nni_plat_join_dir(const char *prefix, const char *suffix)
+{
+ char * newdir;
+ size_t len;
+
+ len = strlen(prefix) + strlen(suffix) + 2;
+ newdir = nni_alloc(strlen(prefix) + strlen(suffix) + 2);
+ if (newdir != NULL) {
+ (void) snprintf(newdir, len, "%s/%s", prefix, suffix);
+ }
+ return (newdir);
+}
+
+#endif // NNG_PLATFORM_POSIX
diff --git a/src/platform/windows/win_debug.c b/src/platform/windows/win_debug.c
index 0113af58..00e327db 100644
--- a/src/platform/windows/win_debug.c
+++ b/src/platform/windows/win_debug.c
@@ -87,6 +87,7 @@ static struct {
{
// clang-format off
{ ERROR_FILE_NOT_FOUND, NNG_ENOENT },
+ { ERROR_PATH_NOT_FOUND, NNG_ENOENT },
{ ERROR_ACCESS_DENIED, NNG_EPERM },
{ ERROR_INVALID_HANDLE, NNG_ECLOSED },
{ ERROR_NOT_ENOUGH_MEMORY, NNG_ENOMEM },
diff --git a/src/platform/windows/win_file.c b/src/platform/windows/win_file.c
new file mode 100644
index 00000000..adb57a75
--- /dev/null
+++ b/src/platform/windows/win_file.c
@@ -0,0 +1,270 @@
+//
+// Copyright 2017 Garrett D'Amore <garrett@damore.org>
+// Copyright 2017 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_WINDOWS
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// File support.
+
+// 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)
+{
+ HANDLE h;
+ int rv = 0;
+ DWORD nwrite;
+
+ h = CreateFile(name, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (h == INVALID_HANDLE_VALUE) {
+ return (nni_win_error(GetLastError()));
+ }
+
+ if (!WriteFile(h, data, (DWORD) len, &nwrite, NULL)) {
+ rv = nni_win_error(GetLastError());
+ (void) DeleteFile(name);
+ goto done;
+ }
+ // These are regular files, synchronous operations. If we got a
+ // short write, then we should have gotten an error!
+ NNI_ASSERT(nwrite == len);
+
+done:
+ (void) CloseHandle(h);
+ 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)
+{
+ int rv = 0;
+ void * data;
+ DWORD sz;
+ DWORD nread;
+ HANDLE h;
+
+ h = CreateFile(name, GENERIC_READ, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (h == INVALID_HANDLE_VALUE) {
+ return (nni_win_error(GetLastError()));
+ }
+ // We choose not to support extraordinarily large files (>4GB)
+ if ((sz = GetFileSize(h, NULL)) == INVALID_FILE_SIZE) {
+ rv = nni_win_error(GetLastError());
+ goto done;
+ }
+ if ((data = nni_alloc((size_t) sz)) == NULL) {
+ rv = NNG_ENOMEM;
+ goto done;
+ }
+ if (!ReadFile(h, data, sz, &nread, NULL)) {
+ rv = nni_win_error(GetLastError());
+ nni_free(data, sz);
+ goto done;
+ }
+
+ // We can get a short read, indicating end of file. We return
+ // the actual number of bytes read. The fact that the data buffer
+ // is larger than this is ok, because our nni_free() routine just
+ // uses HeapFree(), which doesn't need a matching size.
+
+ *datap = data;
+ *lenp = (size_t) nread;
+done:
+ (void) CloseHandle(h);
+ return (rv);
+}
+
+// nni_plat_file_delete deletes the named file.
+int
+nni_plat_file_delete(const char *name)
+{
+ if (!DeleteFile(name)) {
+ return (nni_win_error(GetLastError()));
+ }
+ return (0);
+}
+
+// nni_plat_dir_open attempts to "open a directory" for listing. The
+// handle for further operations is returned in the first argument, and
+// the directory name is supplied in the second.
+struct dirhandle {
+ HANDLE dirh;
+ WIN32_FIND_DATA data;
+ int cont; // zero on first read, 1 thereafter.
+};
+
+int
+nni_plat_dir_open(void **dhp, const char *name)
+{
+ struct dirhandle *dh;
+ char fullpath[MAX_PATH + 1];
+
+ if ((dh = NNI_ALLOC_STRUCT(dh)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+
+ // Append wildcard to directory name
+ _snprintf(fullpath, sizeof(fullpath), "%s\\*", name);
+
+ if ((dh->dirh = FindFirstFile(fullpath, &dh->data)) ==
+ INVALID_HANDLE_VALUE) {
+ int rv;
+ rv = nni_win_error(GetLastError());
+ NNI_FREE_STRUCT(dh);
+ return (rv);
+ }
+ dh->cont = 0;
+ *dhp = dh;
+
+ return (0);
+}
+
+// nni_plat_dir_next gets the next directory entry. Each call returns
+// a new entry (arbitrary order). When no more entries exist, it returns
+// NNG_ENOENT.
+int
+nni_plat_dir_next(void *dir, const char **namep)
+{
+ struct dirhandle *dh = dir;
+ int rv;
+
+ if (dh->dirh == INVALID_HANDLE_VALUE) {
+ return (NNG_ENOENT);
+ }
+ if (dh->cont) {
+ // We need to read another entry
+ if (!FindNextFile(dh->dirh, &dh->data)) {
+ rv = GetLastError();
+ FindClose(dh->dirh);
+ dh->dirh = INVALID_HANDLE_VALUE;
+ if (rv == ERROR_NO_MORE_FILES) {
+ return (NNG_ENOENT);
+ }
+ return (nni_win_error(rv));
+ }
+ }
+ dh->cont = 1;
+
+ // Skip over directories.
+ while (dh->data.dwFileAttributes &
+ (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN)) {
+ if (!FindNextFile(dh->dirh, &dh->data)) {
+ rv = GetLastError();
+ FindClose(dh->dirh);
+ dh->dirh = INVALID_HANDLE_VALUE;
+ if (rv == ERROR_NO_MORE_FILES) {
+ return (NNG_ENOENT);
+ }
+ return (nni_win_error(rv));
+ }
+ }
+
+ // Got a good entry.
+ *namep = dh->data.cFileName;
+ return (0);
+}
+
+// nni_plat_dir_close closes the directory handle, freeing all
+// resources associated with it.
+void
+nni_plat_dir_close(void *dir)
+{
+ struct dirhandle *dh = dir;
+ if (dh->dirh != INVALID_HANDLE_VALUE) {
+ FindClose(dh->dirh);
+ }
+ NNI_FREE_STRUCT(dh);
+}
+
+int
+nni_plat_dir_create(const char *name)
+{
+ char parent[MAX_PATH + 1];
+ int len;
+
+ nni_strlcpy(parent, name, sizeof(parent));
+ len = strlen(parent);
+ while (len > 0) {
+ if ((parent[len - 1] == '/') || (parent[len - 1] == '\\')) {
+ parent[len - 1] = '\0';
+ break;
+ }
+ len--;
+ }
+
+ if (!CreateDirectoryEx(parent, name, NULL)) {
+ int rv = GetLastError();
+ if (rv == ERROR_ALREADY_EXISTS) {
+ return (0);
+ }
+ return (nni_win_error(rv));
+ }
+ return (0);
+}
+
+int
+nni_plat_dir_remove(const char *name)
+{
+ if (!RemoveDirectory(name)) {
+ int rv = GetLastError();
+ if (rv == ERROR_PATH_NOT_FOUND) {
+ return (0);
+ }
+ return (nni_win_error(rv));
+ }
+ return (0);
+}
+
+char *
+nni_plat_temp_dir(void)
+{
+ char path[MAX_PATH + 1];
+
+ if (!GetTempPath(MAX_PATH + 1, path)) {
+ return (NULL);
+ }
+ return (nni_strdup(path));
+}
+
+char *
+nni_plat_home_dir(void)
+{
+ char *homedrv;
+ char *homedir;
+ char stuff[MAX_PATH + 1];
+
+ if (((homedrv = getenv("HOMEDRIVE")) == NULL) ||
+ ((homedir = getenv("HOMEPATH")) == NULL)) {
+ return (NULL);
+ }
+ _snprintf(stuff, sizeof(stuff), "%s%s", homedrv, homedir);
+ return (nni_strdup(stuff));
+}
+
+char *
+nni_plat_join_dir(const char *prefix, const char *suffix)
+{
+ char stuff[MAX_PATH + 1];
+
+ _snprintf(stuff, sizeof(stuff), "%s\\%s", prefix, suffix);
+ return (nni_strdup(stuff));
+}
+#endif // NNG_PLATFORM_WINDOWS \ No newline at end of file
diff --git a/src/transport/zerotier/zerotier.c b/src/transport/zerotier/zerotier.c
index 56acce03..28b253d0 100644
--- a/src/transport/zerotier/zerotier.c
+++ b/src/transport/zerotier/zerotier.c
@@ -17,10 +17,6 @@
#include "core/nng_impl.h"
#include "zerotier.h"
-#ifndef _WIN32
-#include <unistd.h>
-#endif
-
#include <ZeroTierOne.h>
// These values are supplied to help folks checking status. They are the
@@ -1187,7 +1183,6 @@ zt_state_put(ZT_Node *node, void *userptr, void *thr,
enum ZT_StateObjectType objtype, const uint64_t objid[2], const void *data,
int len)
{
- FILE * file;
zt_node * ztn = userptr;
char path[NNG_MAXADDRLEN + 1];
const char *fname;
@@ -1224,27 +1219,12 @@ zt_state_put(ZT_Node *node, void *userptr, void *thr,
return;
}
- // We assume that everyone can do standard C I/O.
- // This may be a bad assumption. If that's the case,
- // the platform should supply an alternative
- // implementation. We are also assuming that we don't
- // need to worry about atomic updates. As these items
- // (keys, etc.) pretty much don't change, this should
- // be fine.
-
if (len < 0) {
- (void) unlink(path);
- return;
- }
-
- if ((file = fopen(path, "wb")) == NULL) {
+ nni_plat_file_delete(path);
return;
}
- if (fwrite(data, 1, len, file) != len) {
- (void) unlink(path);
- }
- (void) fclose(file);
+ (void) nni_plat_file_put(path, data, len);
}
static int
@@ -1252,12 +1232,11 @@ zt_state_get(ZT_Node *node, void *userptr, void *thr,
enum ZT_StateObjectType objtype, const uint64_t objid[2], void *data,
unsigned int len)
{
- FILE * file;
zt_node * ztn = userptr;
char path[NNG_MAXADDRLEN + 1];
const char *fname;
- int nread;
size_t sz;
+ void * buf;
NNI_ARG_UNUSED(objid); // we only use global files
@@ -1285,30 +1264,16 @@ zt_state_get(ZT_Node *node, void *userptr, void *thr,
return (-1);
}
- // We assume that everyone can do standard C I/O.
- // This may be a bad assumption. If that's the case,
- // the platform should supply an alternative
- // implementation. We are also assuming that we don't
- // need to worry about atomic updates. As these items
- // (keys, etc.) pretty much don't change, this should
- // be fine.
-
- if ((file = fopen(path, "rb")) == NULL) {
+ if (nni_plat_file_get(path, &buf, &sz) != 0) {
return (-1);
}
-
- // seek to end of file
- (void) fseek(file, 0, SEEK_END);
- if (ftell(file) > len) {
- fclose(file);
+ if (sz > len) {
+ nni_free(buf, sz);
return (-1);
}
- (void) fseek(file, 0, SEEK_SET);
-
- nread = (int) fread(data, 1, len, file);
- (void) fclose(file);
-
- return (nread);
+ memcpy(data, buf, sz);
+ nni_free(buf, sz);
+ return ((int) sz);
}
typedef struct zt_send_hdr {