Browse Source

initial commit

tags/v1.0
katherine 10 months ago
commit
12476abdab
Signed by: ageha GPG Key ID: 62E17859D559AEF3
4 changed files with 726 additions and 0 deletions
  1. +24
    -0
      LICENSE
  2. +199
    -0
      README.md
  3. +65
    -0
      doc/example.c
  4. +438
    -0
      simple-xdg-bdirs.h

+ 24
- 0
LICENSE View File

@@ -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>

+ 199
- 0
README.md View File

@@ -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

+ 65
- 0
doc/example.c View File

@@ -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;
}

+ 438
- 0
simple-xdg-bdirs.h View File

@@ -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

Loading…
Cancel
Save