aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--LICENSE24
-rw-r--r--README.md199
-rw-r--r--doc/example.c65
-rw-r--r--simple-xdg-bdirs.h438
4 files changed, 726 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..cf1ab25
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org>
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4173770
--- /dev/null
+++ b/README.md
@@ -0,0 +1,199 @@
+simple-xdg-bdirs
+================
+
+[simple-xdg-bdirs.h](simple-xdg-bdirs.h) is a single header file which
+implements calculating file paths according to the
+[XDG Base Directory Specification][1].
+
+brief summary
+-------------
+
+the XDG Base Directory Specification is a popular specification which defines
+predictable locations for programs to store their config, runtime, cache, and
+data files. these locations are calculated based on specially named environment
+variables, with certain directories defined as default fallbacks.
+
+this single-header library provides a couple of simple functions to calculate
+those locations at runtime, as well as functions to build file paths against
+them.
+
+example
+-------
+
+the following example file is available as [example.c](doc/example.c), which
+you can compile and play with yourself.
+
+```C
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "../simple-xdg-bdirs.h"
+
+/* a simple example which attempts to read a value from a configuration file
+ * and write it back to a cache file */
+int main(int argc, char *argv[])
+{
+ char *s, *wdir, **rdirs, **cur;
+
+ /* alloc a null-terminated array of directories in which to search for
+ * configuration files */
+ rdirs = simple_xdg_bdirs_read_dirs(SIMPLE_XDG_BDIRS_CONFIG);
+
+ /* in case of error, errno is set, so perror works */
+ if (rdirs == NULL) {
+ perror(NULL);
+ return 1;
+ }
+
+ /* search in the config directories for the given relative path. if not
+ * found in the first, the second will be checked as a fallback etc */
+ s = simple_xdg_bdirs_fullpath_read("xdg_bdirs_test/config.conf", rdirs);
+
+ /* element strings of rdirs must also be freed */
+ for (cur = rdirs; *cur != NULL; cur++)
+ free(*cur);
+ free(rdirs);
+
+ /* check for errors again */
+ if (s == NULL) {
+ perror(NULL);
+ return 1;
+ }
+
+ /* print the fullpath of the file that was found */
+ puts(s);
+ free(s);
+
+ /* locate the directory into which runtime files should be written
+ * (things like sockets or lock files) */
+ wdir = simple_xdg_bdirs_write_dir(SIMPLE_XDG_BDIRS_RUNTIME);
+
+ if (wdir == NULL) {
+ perror(NULL);
+ return 1;
+ }
+
+ /* a convenience function that builds a fullpath from a relative path and
+ * write directory */
+ s = simple_xdg_bdirs_fullpath_write("xdg_bdirs_test.lock", wdir);
+
+ free(wdir);
+
+ if (s == NULL) {
+ perror(NULL);
+ return 1;
+ }
+
+ puts(s);
+ free(s);
+
+ return 0;
+}
+```
+
+basically, each function returns either a string or `NULL`, in which case
+`errno` will also be set. simple!
+
+interface
+---------
+
+```C
+enum simple_xdg_bdirs_ftype {
+ SIMPLE_XDG_BDIRS_DATA,
+ SIMPLE_XDG_BDIRS_CONFIG,
+ SIMPLE_XDG_BDIRS_CACHE,
+ SIMPLE_XDG_BDIRS_RUNTIME,
+};
+```
+
+one of these types is passed to each of the functions below, denoting the
+category of file to which the functions actions ought to correspond.
+
+- `SIMPLE_XDG_BDIRS_DATA` is used for persistent data created by a program
+- `SIMPLE_XDG_BDIRS_CONFIG` is used for a program's configuration files
+- `SIMPLE_XDG_BDIRS_CACHE` is used for transient data which may be deleted
+ without warning by the system
+- `SIMPLE_XDG_BDIRS_RUNTIME` is used for "runtime files", such as lock files
+ or sockets
+
+
+```C
+static char*
+simple_xdg_bdirs_write_dir(enum simple_xdg_bdirs_ftype type)
+```
+
+this function finds and returns a single string that is the path of a
+directory into which all files of type `type` ought to be written.
+
+because the location of this directory is determined at runtime, and in
+particular because it relies on the state of the filesystem and environment
+variables, it is possible that no good candidate directory will be found, in
+which case `NULL` will be returned.
+
+if a string is returned, it is guaranteed to always have a trailing '/',
+meaning that a relative path can be appended directly without fear of
+unintended results, like '/etx/xdgherbstluftwm'
+
+errors:
+- `ENOENT`: no candidate dir found
+- `EINVAL`: bad argument
+- `EOVERFLOW`: extremely-large string encountered (unlikely)
+- `ENOMEM`: malloc err
+
+
+```C
+static char**
+simple_xdg_bdirs_read_dirs(enum simple_xdg_bdirs_ftype type)
+```
+
+this function finds and returns a null-terminated array of strings that are
+the paths, in order, where one ought to search for files of type `type` when
+reading.
+
+because some filetypes have only a single, environment-determined read dir,
+it's possible that none will be found, in which case `NULL` will be
+returned.
+
+returned strings are guaranteed to always have a trailing '/', meaning that
+a relative path can be appended directly without fear of unintended results,
+like '/etx/xdgherbstluftwm'
+
+errors:
+- `ENOENT`: no candidate dirs found
+- `EINVAL`: bad argument
+- `EOVERFLOW`: extremely-large string encountered (unlikely)
+- `ENOMEM`: malloc err
+
+
+```C
+static char*
+simple_xdg_bdirs_fullpath_read(const char *rel_path, char **read_dirs)
+```
+
+take a relative path and set of directories returned from
+`simple_xdg_bdirs_read_dirs` and return either a full path to an existing
+file or, on error, NULL
+
+errors
+- `ENOENT`: target file not found or readable
+- `EINVAL`: bad argument
+- `EOVERFLOW`: extremely-large string encountered (unlikely)
+- `ENOMEM`: malloc err
+
+
+```C
+static char*
+simple_xdg_bdirs_fullpath_write(const char *rel_path, char *write_dir)
+```
+
+take a relative path and a write directory returned from
+`simple_xdg_bdirs_write_dir` and return either a full path to the targeted
+write path or, on error, NULL
+
+errors
+- `ENOENT`: write_dir is not found or not write accessible
+- `EINVAL`: bad argument
+- `EOVERFLOW`: extremely-large string encountered (unlikely)
+- `ENOMEM`: malloc err
+
+[1]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
diff --git a/doc/example.c b/doc/example.c
new file mode 100644
index 0000000..688321a
--- /dev/null
+++ b/doc/example.c
@@ -0,0 +1,65 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "../simple-xdg-bdirs.h"
+
+/* a simple example which attempts to read a value from a configuration file
+ * and write it back to a cache file */
+int main(int argc, char *argv[])
+{
+ char *s, *wdir, **rdirs, **cur;
+
+ /* alloc a null-terminated array of directories in which to search for
+ * configuration files */
+ rdirs = simple_xdg_bdirs_read_dirs(SIMPLE_XDG_BDIRS_CONFIG);
+
+ /* in case of error, errno is set, so perror works */
+ if (rdirs == NULL) {
+ perror(NULL);
+ return 1;
+ }
+
+ /* search in the config directories for the given relative path. if not
+ * found in the first, the second will be checked as a fallback etc */
+ s = simple_xdg_bdirs_fullpath_read("xdg_bdirs_test/config.conf", rdirs);
+
+ /* element strings of rdirs must also be freed */
+ for (cur = rdirs; *cur != NULL; cur++)
+ free(*cur);
+ free(rdirs);
+
+ /* check for errors again */
+ if (s == NULL) {
+ perror(NULL);
+ return 1;
+ }
+
+ /* print the fullpath of the file that was found */
+ puts(s);
+ free(s);
+
+ /* locate the directory into which runtime files should be written
+ * (things like sockets or lock files) */
+ wdir = simple_xdg_bdirs_write_dir(SIMPLE_XDG_BDIRS_RUNTIME);
+
+ if (wdir == NULL) {
+ perror(NULL);
+ return 1;
+ }
+
+ /* a convenience function that builds a fullpath from a relative path and
+ * write directory */
+ s = simple_xdg_bdirs_fullpath_write("xdg_bdirs_test.lock", wdir);
+
+ free(wdir);
+
+ if (s == NULL) {
+ perror(NULL);
+ return 1;
+ }
+
+ puts(s);
+ free(s);
+
+ return 0;
+}
diff --git a/simple-xdg-bdirs.h b/simple-xdg-bdirs.h
new file mode 100644
index 0000000..98a57b5
--- /dev/null
+++ b/simple-xdg-bdirs.h
@@ -0,0 +1,438 @@
+#ifndef SIMPLE_XDG_BDIRS_H
+#define SIMPLE_XDG_BDIRS_H
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+
+/* one of these types is passed to each of the functions below, denoting the
+ * category of file to which the functions actions ought to correspond.
+ * - `SIMPLE_XDG_BDIRS_DATA` is used for persistent data created by a program
+ * - `SIMPLE_XDG_BDIRS_CONFIG` is used for a program's configuration files
+ * - `SIMPLE_XDG_BDIRS_CACHE` is used for transient data which may be deleted
+ * without warning by the system
+ * - `SIMPLE_XDG_BDIRS_RUNTIME` is used for "runtime files", such as lock files
+ * or sockets
+ */
+enum simple_xdg_bdirs_ftype {
+ SIMPLE_XDG_BDIRS_DATA,
+ SIMPLE_XDG_BDIRS_CONFIG,
+ SIMPLE_XDG_BDIRS_CACHE,
+ SIMPLE_XDG_BDIRS_RUNTIME,
+};
+
+/* this function finds and returns a single string that is the path of a
+ * directory into which all files of type `type` ought to be written.
+ *
+ * because the location of this directory is determined at runtime, and in
+ * particular because it relies on the state of the filesystem and environment
+ * variables, it is possible that no good candidate directory will be found, in
+ * which case `NULL` will be returned.
+ *
+ * if a string is returned, it is guaranteed to always have a trailing '/',
+ * meaning that a relative path can be appended directly without fear of
+ * unintended results, like '/etx/xdgherbstluftwm'
+ *
+ * errors:
+ * ENOENT: no candidate dir found
+ * EINVAL: bad argument
+ * EOVERFLOW: extremely-large string encountered (unlikely)
+ * ENOMEM: malloc err
+ */
+static char*
+simple_xdg_bdirs_write_dir(enum simple_xdg_bdirs_ftype type)
+{
+ char *home = getenv("HOME");
+ char *xdg_data_home;
+ char *xdg_config_home;
+ char *xdg_cache_home;
+ char *xdg_runtime_dir;
+ char *empty = "";
+
+ char *s1, *s2, *r;
+ size_t l1, l2;
+
+ /* get components */
+ switch (type) {
+ case SIMPLE_XDG_BDIRS_DATA:
+ xdg_data_home = getenv("XDG_DATA_HOME");
+ if (xdg_data_home && xdg_data_home[0]) {
+ s1 = empty;
+ s2 = xdg_data_home;
+ } else if (home && home[0]) {
+ s1 = home;
+ s2 = "/.local/share";
+ } else {
+ errno = ENOENT;
+ return NULL;
+ }
+ break;
+
+ case SIMPLE_XDG_BDIRS_CONFIG:
+ xdg_config_home = getenv("XDG_CONFIG_HOME");
+ if (xdg_config_home && xdg_config_home[0]) {
+ s1 = empty;
+ s2 = xdg_config_home;
+ } else if (home && home[0]) {
+ s1 = home;
+ s2 = "/.config";
+ } else {
+ errno = ENOENT;
+ return NULL;
+ }
+ break;
+
+ case SIMPLE_XDG_BDIRS_CACHE:
+ xdg_cache_home = getenv("XDG_CACHE_HOME");
+ if (xdg_cache_home && xdg_cache_home[0]) {
+ s1 = empty;
+ s2 = xdg_cache_home;
+ } else if (home && home[0]) {
+ s1 = home;
+ s2 = "/.cache";
+ } else {
+ errno = ENOENT;
+ return NULL;
+ }
+ break;
+
+ case SIMPLE_XDG_BDIRS_RUNTIME:
+ xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
+ if (xdg_runtime_dir && xdg_runtime_dir[0]) {
+ s1 = empty;
+ s2 = xdg_runtime_dir;
+ } else {
+ errno = ENOENT;
+ return NULL;
+ }
+ break;
+ default:
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* get s1 part length */
+ for (l1 = 0; s1[l1]; l1++) {
+ if (l1 == SIZE_MAX) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ }
+
+ /* get s2 part length */
+ for (l2 = 0; s2[l2]; l1++, l2++) {
+ if (l1 == SIZE_MAX) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ }
+
+ /* extra 2 for '/' and '\0' */
+ for (l2 = 0; l2 < 2; l1++, l2++) {
+ if (l1 == SIZE_MAX) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+ }
+
+ /* build str */
+ r = malloc(l1 * sizeof(char));
+ if (r == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ for (l1 = 0; s1[l1]; l1++)
+ r[l1] = s1[l1];
+
+ for (l2 = 0; s2[l2]; l1++, l2++)
+ r[l1] = s2[l2];
+
+ r[l1] = '/';
+
+ /* slash dedupe */
+ for (l2 = 0; l2 < l1; l2++) {
+ if (r[l2] == '/' && r[l2 + 1] == '/') {
+ memmove(r + l2, r + l2 + 1, l1 - l2 - 1);
+ l1--;
+ l2--;
+ }
+ }
+
+ r[l1 + 1] = '\0';
+
+ return r;
+}
+
+/* this function finds and returns a null-terminated array of strings that are
+ * the paths, in order, where one ought to search for files of type `type` when
+ * reading.
+ *
+ * because some filetypes have only a single, environment-determined read dir,
+ * it's possible that none will be found, in which case `NULL` will be
+ * returned.
+ *
+ * returned strings are guaranteed to always have a trailing '/', meaning that
+ * a relative path can be appended directly without fear of unintended results,
+ * like '/etx/xdgherbstluftwm'
+ *
+ * errors:
+ * ENOENT: no candidate dirs found
+ * EINVAL: bad argument
+ * EOVERFLOW: extremely-large string encountered (unlikely)
+ * ENOMEM: malloc err
+ */
+static char**
+simple_xdg_bdirs_read_dirs(enum simple_xdg_bdirs_ftype type)
+{
+ char *s1 = NULL, *s2 = NULL;
+ bool flag;
+
+ size_t l1, l2, l3, l4;
+
+ char **r = NULL, **cur;
+
+ /* cache and runtime are the same as write_dir */
+ switch (type) {
+ case SIMPLE_XDG_BDIRS_CACHE:
+ r = malloc(2 * sizeof(char*));
+
+ if (r == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ r[1] = NULL;
+ r[0] = simple_xdg_bdirs_write_dir(SIMPLE_XDG_BDIRS_CACHE);
+
+ if (r[0] == NULL)
+ goto abort;
+
+ return r;
+
+ case SIMPLE_XDG_BDIRS_RUNTIME:
+ r = malloc(2 * sizeof(char*));
+
+ if (r == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ r[1] = NULL;
+ r[0] = simple_xdg_bdirs_write_dir(SIMPLE_XDG_BDIRS_RUNTIME);
+
+ if (r[0] == NULL)
+ goto abort;
+
+ return r;
+
+ /* data and config are write_dir + fallbacks */
+ case SIMPLE_XDG_BDIRS_DATA:
+ s1 = simple_xdg_bdirs_write_dir(SIMPLE_XDG_BDIRS_DATA);
+
+ s2 = getenv("XDG_DATA_DIRS");
+ if (!s2 || !s2[0])
+ s2 = "/usr/local/share:/usr/share";
+
+ break;
+
+ case SIMPLE_XDG_BDIRS_CONFIG:
+ s1 = simple_xdg_bdirs_write_dir(SIMPLE_XDG_BDIRS_CONFIG);
+
+ s2 = getenv("XDG_CONFIG_DIRS");
+ if (!s2 || !s2[0])
+ s2 = "/etc/xdg";
+
+ break;
+ default:
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* count number of fallback dirs */
+ for (flag = true, l1 = 0, l2 = 1; s2[l1]; l1++) {
+ if (l1 == SIZE_MAX) {
+ errno = EOVERFLOW;
+ goto abort;
+ }
+ if (s2[l1] == ':') {
+ if (!flag) {
+ l2++;
+ flag = true;
+ }
+ } else {
+ flag = false;
+ }
+ }
+
+ /* make r */
+ r = malloc((l2 + (s1 != NULL) + 1) * sizeof(char*));
+ if (r == NULL) {
+ errno = ENOMEM;
+ goto abort;
+ }
+
+ /* insert write_dir, if it was found */
+ if (s1 != NULL) {
+ r[0] = s1;
+ cur = r + 1;
+ } else {
+ cur = r;
+ }
+
+ for (l1 = 0, l2 = 0; s2[l1];) {
+ /* skip over multiple ':' */
+ for (; s2[l2] == ':'; l2++);
+
+ /* check for done */
+ l1 = l2;
+ if (s2[l1] == '\0')
+ break;
+
+ /* find the end of the current dir in the string */
+ for (; s2[l2] && s2[l2] != ':'; l2++);
+
+ /* copy the current string into r */
+ *cur = malloc((l2 - l1 + 2) * sizeof(char));
+ if (*cur == NULL) {
+ errno = ENOMEM;
+ goto abort;
+ }
+
+ strncpy(*cur, s2 + l1, l2 - l1);
+ (*cur)[l2 - l1] = '/';
+ (*cur)[l2 - l1 + 1] = '\0';
+
+ /* slash dedupe */
+ l3 = l2 - l1;
+
+ for (l4 = 0; l4 < l3; l4++) {
+ if ((*cur)[l4] == '/' && (*cur)[l4 + 1] == '/') {
+ memmove(*cur + l4, *cur + l4 + 1, l3 - l4);
+ l3--;
+ l4--;
+ }
+ }
+
+ (*cur)[l3 + 1] = '\0';
+
+ cur++;
+ }
+
+ *cur = NULL;
+
+ /* done ^_^ */
+ return r;
+
+abort:
+ /* whoops, something went wrong :c */
+ if (s1 != NULL)
+ free(s1);
+
+ if (r == NULL)
+ return NULL;
+
+ for (cur = r; *cur != NULL; cur++)
+ free(cur);
+
+ free(r);
+
+ return NULL;
+}
+
+static char*
+sub_simple_xdg_bdirs_join(const char *s1, const char *s2)
+{
+ size_t l1, l2, l3;
+ char *r;
+
+ l1 = strlen(s1);
+ l2 = strlen(s2);
+
+ for (l3 = l1; l3 != SIZE_MAX && l2 != 0; l3++, l2--);
+
+ if (l3 == SIZE_MAX) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
+
+ r = malloc((l3 + 1) * sizeof(char));
+ if (r == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ strcpy(r, s1);
+ strcpy(r + l1, s2);
+
+ return r;
+}
+
+/* take a relative path and set of directories returned from
+ * `simple_xdg_bdirs_read_dirs` and return either a full path to an existing
+ * file or, on error, NULL
+ *
+ * errors
+ * ENOENT: target file not found or readable
+ * EINVAL: bad argument
+ * EOVERFLOW: extremely-large string encountered (unlikely)
+ * ENOMEM: malloc err
+ */
+static char*
+simple_xdg_bdirs_fullpath_read(const char *rel_path, char **read_dirs)
+{
+ char **cur, *path;
+
+ if (rel_path == NULL && read_dirs == NULL) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ for (cur = read_dirs; *cur != NULL; cur++) {
+ path = sub_simple_xdg_bdirs_join(*cur, rel_path);
+ if (path == NULL)
+ return NULL;
+
+ if (!access(path, R_OK)) {
+ return path;
+ }
+
+ free(path);
+ }
+
+ errno = ENOENT;
+ return NULL;
+}
+
+/* take a relative path and a write directory returned from
+ * `simple_xdg_bdirs_write_dir` and return either a full path to the targeted
+ * write path or, on error, NULL
+ *
+ * errors
+ * ENOENT: write_dir is not found or not write accessible
+ * EINVAL: bad argument
+ * EOVERFLOW: extremely-large string encountered (unlikely)
+ * ENOMEM: malloc err
+ */
+static char*
+simple_xdg_bdirs_fullpath_write(const char *rel_path, char *write_dir)
+{
+ if (rel_path == NULL && write_dir == NULL) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (access(write_dir, W_OK)) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ return sub_simple_xdg_bdirs_join(write_dir, rel_path);
+}
+
+#endif