1107 lines
28 KiB
C
1107 lines
28 KiB
C
|
|
/* XDG Base Directory Specifications support library. */
|
|
/* Implements equivalent cross platform functionality too. */
|
|
|
|
/*************************************************************************
|
|
Copyright 2011, 2013 Graeme W. Gill
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
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 OR COPYRIGHT HOLDERS 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.
|
|
|
|
*************************************************************************/
|
|
|
|
/*
|
|
This function provides support for the XDG Base Directory Specifications
|
|
in a cross platform compatible way.
|
|
|
|
[ Note that for MSWin each path in a set is separated by a ';' character.
|
|
and that DATA and CONF will be in the same directory. ]
|
|
|
|
The following paths are used for each of the 5 XDG concepts, listed in order
|
|
of priority:
|
|
|
|
Per user application related data.
|
|
|
|
Per user application configuration settings.
|
|
|
|
Per user application cache storage area.
|
|
|
|
Local system wide application related data.
|
|
|
|
Local system wide application configuration settings.
|
|
|
|
Unix:
|
|
$XDG_DATA_HOME
|
|
$HOME/.local/share
|
|
|
|
$XDG_CONFIG_HOME
|
|
$HOME/.config
|
|
|
|
$XDG_CACHE_HOME
|
|
$HOME/.cache
|
|
|
|
$XDG_DATA_DIRS
|
|
/usr/local/share:/usr/share
|
|
|
|
$XDG_CONFIG_DIRS
|
|
/etc/xdg
|
|
|
|
OS X:
|
|
$XDG_DATA_HOME
|
|
$HOME/Library/Application Support
|
|
|
|
$XDG_CONFIG_HOME
|
|
$HOME/Library/Preferences
|
|
|
|
$XDG_CACHE_HOME
|
|
$HOME/Library/Caches
|
|
|
|
$XDG_DATA_DIRS
|
|
/Library/Application Support
|
|
|
|
$XDG_CONFIG_DIRS
|
|
/Library/Preferences
|
|
|
|
MSWin:
|
|
$XDG_DATA_HOME
|
|
$APPDATA
|
|
$HOME/.local/share
|
|
|
|
$XDG_CONFIG_HOME
|
|
$APPDATA
|
|
$HOME/.config
|
|
|
|
$XDG_CACHE_HOME
|
|
$APPDATA/Cache
|
|
$HOME/.cache
|
|
|
|
$XDG_DATA_DIRS
|
|
$ALLUSERSPROFILE
|
|
|
|
$XDG_CONFIG_DIRS
|
|
$ALLUSERSPROFILE
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <signal.h>
|
|
#ifndef NT
|
|
# include <unistd.h>
|
|
# include <glob.h>
|
|
#else
|
|
# include <io.h>
|
|
# include <direct.h>
|
|
#endif
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#ifndef SALONEINSTLIB
|
|
# include "copyright.h"
|
|
# include "aconfig.h"
|
|
#else
|
|
# include "sa_config.h"
|
|
#endif
|
|
#include "numsup.h"
|
|
#include "conv.h"
|
|
#include "aglob.h"
|
|
#include "xdg_bds.h"
|
|
|
|
|
|
#undef DEBUG
|
|
|
|
#ifdef DEBUG
|
|
#define DBGA g_log, 0
|
|
#define DBG(xxx) a1logd xxx ;
|
|
#else
|
|
#define DBG(xxx)
|
|
#endif /* DEBUG */
|
|
|
|
#ifdef NT
|
|
# define stat _stat
|
|
# define mode_t int
|
|
# define mkdir(A,B) _mkdir(A)
|
|
# define mputenv _putenv
|
|
# define unlink _unlink
|
|
# define rmdir _rmdir
|
|
#else
|
|
/* UNIX putenv is a pain.. */
|
|
static void mputenv(char *ss) {
|
|
int ll = strlen(ss);
|
|
ss = strdup(ss);
|
|
if (ll > 0 && ss[ll-1]== '=') {
|
|
ss[ll-1] = '\000';
|
|
unsetenv(ss);
|
|
} else {
|
|
putenv(ss);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Append a string. Free in. Return NULL on error. */
|
|
static char *append(char *in, char *app) {
|
|
char *rv;
|
|
|
|
if ((rv = malloc(strlen(in) + strlen(app) + 1)) == NULL) {
|
|
a1loge(g_log, 1, "xdg_bds: append malloc failed\n");
|
|
free(in);
|
|
return NULL;
|
|
}
|
|
strcpy(rv, in);
|
|
strcat(rv, app);
|
|
free(in);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Append a ':' or ';' then a string. Free in. */
|
|
/* Return NULL on error. */
|
|
static char *cappend(char *in, char *app) {
|
|
int inlen, aplen;
|
|
char *rv;
|
|
|
|
inlen = strlen(in);
|
|
aplen = strlen(app);
|
|
|
|
if ((rv = malloc(inlen + 1 + aplen + 1)) == NULL) {
|
|
a1loge(g_log, 1, "xdg_bds: cappend malloc failed\n");
|
|
free(in);
|
|
return NULL;
|
|
}
|
|
strcpy(rv, in);
|
|
if (inlen > 0 && in[inlen-1] != SSEP && aplen > 0)
|
|
strcat(rv, SSEPS);
|
|
strcat(rv, app);
|
|
free(in);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Append a '/' then a string. Free in. */
|
|
/* Return NULL on error. */
|
|
static char *dappend(char *in, char *app) {
|
|
int inlen, aplen;
|
|
char *rv;
|
|
|
|
inlen = strlen(in);
|
|
aplen = strlen(app);
|
|
|
|
if ((rv = malloc(inlen + 1 + aplen + 1)) == NULL) {
|
|
a1loge(g_log, 1, "xdg_bds: dappend malloc failed\n");
|
|
free(in);
|
|
return NULL;
|
|
}
|
|
strcpy(rv, in);
|
|
if (inlen > 0 && in[inlen-1] != '/')
|
|
strcat(rv, "/");
|
|
strcat(rv, app);
|
|
free(in);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
/* Do internal cleanup */
|
|
static void xdg_ifree(char ***paths, char **fnames, int nopaths) {
|
|
int i;
|
|
|
|
if (paths != NULL) {
|
|
if (*paths != NULL) {
|
|
for (i = 0; i < nopaths; i++) {
|
|
if ((*paths)[i] != NULL)
|
|
free ((*paths)[i]);
|
|
}
|
|
}
|
|
free(*paths);
|
|
*paths = NULL;
|
|
}
|
|
if (fnames != NULL) {
|
|
for (i = 0; i < nopaths; i++) {
|
|
if (fnames[i] != NULL)
|
|
free (fnames[i]);
|
|
}
|
|
free(fnames);
|
|
}
|
|
}
|
|
|
|
/* Free a return value */
|
|
void xdg_free(char **paths, int nopaths) {
|
|
int i;
|
|
|
|
if (paths != NULL) {
|
|
for (i = 0; i < nopaths; i++) {
|
|
if (paths[i] != NULL)
|
|
free (paths[i]);
|
|
}
|
|
free(paths);
|
|
}
|
|
}
|
|
|
|
/* Return the number of matching full paths to the given subpath for the */
|
|
/* type of storage and access required. Return 0 if there is an error. */
|
|
/* The files are always unique (ie. the first match to a given filename */
|
|
/* in the possible XDG list of directories is returned, and files with */
|
|
/* the same name in other XDG directories are ignored), unless the */
|
|
/* xdg_all_matches flag is set. */
|
|
/* If xdg_read, then first user then local scope are searched. */
|
|
/* If xdf_write, then only the specified scope is used. */
|
|
/* Wildcards should not be used for xdg_write. */
|
|
/* The list should be free'd using xdg_free() after use. */
|
|
/* XDG environment variables and the subpath are assumed to be using */
|
|
/* the '/' path separator. Multiple read paths are separated by SSEP */
|
|
/* When "xdg_write", the necessary path to the file will be created. */
|
|
/* If we're running as sudo and are creating a user dir/file, */
|
|
/* we drop to using the underlying SUDO_UID/GID. If we are creating a */
|
|
/* local system dir/file as sudo and have dropped to the SUDO_UID/GID, */
|
|
/* then revert back to root uid/gid. */
|
|
int xdg_bds(
|
|
xdg_error *er, /* Return an error code */
|
|
char ***paths, /* Retun array pointers to paths */
|
|
xdg_storage_type st, /* Specify the storage type */
|
|
xdg_op_type op, /* Operation type */
|
|
xdg_scope sc, /* Scope if write */
|
|
xdg_options opt, /* Options flags */
|
|
char *pfname /* Sub-path and file name(s) */
|
|
) {
|
|
char *path = NULL; /* Directory paths to search, separated by ':' or ';' */
|
|
char **fnames = NULL; /* Filename component of each path being returned */
|
|
int npaths = 0; /* Number of paths being returned */
|
|
int napaths = 0; /* Number of paths allocated */
|
|
|
|
DBG((DBGA,"xdg_bds called with st %s, op %s, sc %s, pfnames '%s'\n",
|
|
st == xdg_data ? "data" : st == xdg_conf ? "config" : st == xdg_cache ? "cache" : "unknown",
|
|
op == xdg_write ? "write" : op == xdg_read ? "read" : "unknown",
|
|
sc == xdg_user ? "user" : sc == xdg_local ? "local" : "unknown",
|
|
pfname))
|
|
|
|
*paths = NULL;
|
|
|
|
/* Initial, empty path */
|
|
if ((path = strdup("")) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
xdg_ifree(paths, fnames, npaths);
|
|
return 0;
|
|
}
|
|
|
|
/* Create a set of ':'/';' separated search paths */
|
|
|
|
/* User scope */
|
|
if (op == xdg_read || sc == xdg_user) {
|
|
if (st == xdg_data) {
|
|
char *xdg, *home;
|
|
if ((xdg = getenv("XDG_DATA_HOME")) != NULL) {
|
|
if ((path = cappend(path, xdg)) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
#ifdef NT
|
|
} else if (login_HOME() == NULL && (xdg = getenv("APPDATA")) != NULL) {
|
|
if ((path = cappend(path, xdg)) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
#endif
|
|
} else {
|
|
if ((home = login_HOME()) == NULL
|
|
#ifdef NT
|
|
&& (home = getenv("APPDATA")) == NULL
|
|
#endif
|
|
) {
|
|
if (er != NULL) *er = xdg_nohome;
|
|
free(path);
|
|
DBG((DBGA,"no $HOME\n"))
|
|
return 0;
|
|
}
|
|
if ((path = cappend(path, home)) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
#ifdef NT
|
|
if (login_HOME() != NULL)
|
|
path = dappend(path, ".local/share");
|
|
#else
|
|
#ifdef UNIX_APPLE
|
|
path = dappend(path, "Library/Application Support");
|
|
#else /* Unix, Default */
|
|
path = dappend(path, ".local/share");
|
|
#endif
|
|
#endif
|
|
if (path == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
}
|
|
} else if (st == xdg_conf) {
|
|
char *xdg, *home;
|
|
if ((xdg = getenv("XDG_CONFIG_HOME")) != NULL) {
|
|
if ((path = cappend(path, xdg)) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
#ifdef NT
|
|
} else if (login_HOME() == NULL && (xdg = getenv("APPDATA")) != NULL) {
|
|
if ((path = cappend(path, xdg)) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
#endif
|
|
} else {
|
|
if ((home = login_HOME()) == NULL
|
|
#ifdef NT
|
|
&& (home = getenv("APPDATA")) == NULL
|
|
#endif
|
|
) {
|
|
if (er != NULL) *er = xdg_nohome;
|
|
free(path);
|
|
DBG((DBGA,"no $HOME\n"))
|
|
return 0;
|
|
}
|
|
if ((path = cappend(path, home)) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
#ifdef NT
|
|
if (login_HOME() != NULL)
|
|
path = dappend(path, ".config");
|
|
#else
|
|
#ifdef UNIX_APPLE
|
|
path = dappend(path, "Library/Preferences");
|
|
#else /* Unix, Default */
|
|
path = dappend(path, ".config");
|
|
#endif
|
|
#endif
|
|
if (path == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
}
|
|
} else if (st == xdg_cache) {
|
|
char *xdg, *home;
|
|
if ((xdg = getenv("XDG_CACHE_HOME")) != NULL) {
|
|
if ((path = cappend(path, xdg)) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
#ifdef NT
|
|
} else if (login_HOME() == NULL && (xdg = getenv("APPDATA")) != NULL) {
|
|
if ((path = cappend(path, xdg)) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
if ((path = dappend(path, "Cache")) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
#endif
|
|
} else {
|
|
if ((home = login_HOME()) == NULL
|
|
#ifdef NT
|
|
&& (home = getenv("APPDATA")) == NULL
|
|
#endif
|
|
) {
|
|
if (er != NULL) *er = xdg_nohome;
|
|
free(path);
|
|
DBG((DBGA,"no $HOME\n"))
|
|
return 0;
|
|
}
|
|
if ((path = cappend(path, home)) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
#ifdef NT
|
|
if (login_HOME() != NULL)
|
|
path = dappend(path, ".cache");
|
|
else
|
|
path = dappend(path, "Cache");
|
|
#else
|
|
#ifdef UNIX_APPLE
|
|
path = dappend(path, "Library/Caches");
|
|
#else /* Unix, Default */
|
|
path = dappend(path, ".cache");
|
|
#endif
|
|
#endif
|
|
if (path == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Local system scope */
|
|
if (op == xdg_read || sc == xdg_local) {
|
|
char *xdg;
|
|
if (st == xdg_data) {
|
|
if ((xdg = getenv("XDG_DATA_DIRS")) != NULL) {
|
|
if ((path = cappend(path, xdg)) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
} else {
|
|
#ifdef NT
|
|
/*
|
|
QT uses $COMMON_APPDATA expected to be
|
|
C:\Documents and Settings\All Users\Application Data\
|
|
while others use $CommonAppData.
|
|
Both seem poorly supported,
|
|
*/
|
|
char *home;
|
|
if ((home = getenv("ALLUSERSPROFILE")) == NULL
|
|
) {
|
|
if (er != NULL) *er = xdg_noalluserprofile;
|
|
free(path);
|
|
DBG((DBGA,"no $ALLUSERSPROFILE\n"))
|
|
return 0;
|
|
}
|
|
path = cappend(path, home);
|
|
#else
|
|
#ifdef UNIX_APPLE
|
|
path = cappend(path, "/Library");
|
|
#else
|
|
path = cappend(path, "/usr/local/share:/usr/share");
|
|
#endif
|
|
#endif
|
|
if (path == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
}
|
|
} else if (st == xdg_conf) {
|
|
if ((xdg = getenv("XDG_CONFIG_DIRS")) != NULL) {
|
|
if ((path = cappend(path, xdg)) == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
} else {
|
|
#ifdef NT
|
|
char *home;
|
|
if ((home = getenv("ALLUSERSPROFILE")) == NULL
|
|
) {
|
|
if (er != NULL) *er = xdg_noalluserprofile;
|
|
free(path);
|
|
DBG((DBGA,"no $ALLUSERSPROFILE\n"))
|
|
return 0;
|
|
}
|
|
path = cappend(path, home);
|
|
#else
|
|
#ifdef UNIX_APPLE
|
|
path = cappend(path, "/Library/Preferences");
|
|
#else
|
|
path = cappend(path, "/etc/xdg");
|
|
#endif
|
|
#endif
|
|
if (path == NULL) {
|
|
if (er != NULL) *er = xdg_alloc;
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef NT
|
|
/* Replace all backslashes with forward slashes */
|
|
{
|
|
char *cp;
|
|
for (cp = path; *cp != '\000'; cp++) {
|
|
if (*cp == '\\')
|
|
*cp = '/';
|
|
}
|
|
}
|
|
#endif
|
|
|
|
DBG((DBGA,"Paths to search '%s'\n",path));
|
|
|
|
/* Hmm. */
|
|
if (strlen(path) == 0) {
|
|
free(path);
|
|
if (er != NULL) *er = xdg_nopath;
|
|
*paths = NULL;
|
|
return 0;
|
|
}
|
|
|
|
{
|
|
char *spath = NULL; /* sub path out of paths */
|
|
char *cp, *ep;
|
|
|
|
/* For each search path */
|
|
for (cp = path; *cp != '\000';) {
|
|
char *sname = NULL; /* sub name out of pfnames */
|
|
char *ncp, *nep;
|
|
|
|
/* Copy search path */
|
|
if ((ep = strchr(cp, SSEP)) == NULL)
|
|
ep = cp + strlen(cp);
|
|
if ((ep - cp) == 0) {
|
|
free(path);
|
|
if (er != NULL) *er = xdg_mallformed;
|
|
xdg_ifree(paths, fnames, npaths);
|
|
return 0;
|
|
}
|
|
if ((spath = (char *)malloc(ep - cp + 1)) == NULL) {
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
free(path);
|
|
if (er != NULL) *er = xdg_alloc;
|
|
xdg_ifree(paths, fnames, npaths);
|
|
return 0;
|
|
}
|
|
memmove(spath, cp, ep - cp);
|
|
spath[ep - cp] = '\000';
|
|
|
|
/* For each filename part */
|
|
for (ncp = pfname; *ncp != '\000';) {
|
|
int rlen = 0; /* Number of chars of search path up to subpath & filename */
|
|
char *pp;
|
|
char *schpath; /* Path to search */
|
|
|
|
/* Copy filename path */
|
|
if ((nep = strchr(ncp, SSEP)) == NULL)
|
|
nep = ncp + strlen(ncp);
|
|
if ((nep - ncp) == 0) {
|
|
free(spath);
|
|
free(path);
|
|
if (er != NULL) *er = xdg_mallformed;
|
|
xdg_ifree(paths, fnames, npaths);
|
|
return 0;
|
|
}
|
|
if ((sname = (char *)malloc(nep - ncp + 1)) == NULL) {
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
free(spath);
|
|
free(path);
|
|
if (er != NULL) *er = xdg_alloc;
|
|
xdg_ifree(paths, fnames, npaths);
|
|
return 0;
|
|
}
|
|
memmove(sname, ncp, nep - ncp);
|
|
sname[nep - ncp] = '\000';
|
|
|
|
/* append subpath & subname */
|
|
if ((schpath = strdup(spath)) == NULL
|
|
|| (schpath = dappend(schpath, sname)) == NULL) {
|
|
free(sname);
|
|
free(spath);
|
|
free(path);
|
|
if (er != NULL) *er = xdg_alloc;
|
|
xdg_ifree(paths, fnames, npaths);
|
|
return 0;
|
|
}
|
|
DBG((DBGA,"Full path to check '%s'\n",schpath));
|
|
|
|
/* Figure out where the filename starts */
|
|
if ((pp = strrchr(schpath, '/')) == NULL)
|
|
rlen = 0;
|
|
else
|
|
rlen = pp - schpath + 1;
|
|
|
|
if (op == xdg_read) {
|
|
char *fpath; /* Full path of matched */
|
|
aglob gg; /* Glob structure */
|
|
|
|
/* Setup the file glob */
|
|
if (aglob_create(&gg, schpath)) {
|
|
free(schpath);
|
|
free(sname);
|
|
free(spath);
|
|
free(path);
|
|
if (er != NULL) *er = xdg_alloc;
|
|
xdg_ifree(paths, fnames, npaths);
|
|
return 0;
|
|
}
|
|
|
|
/* While we have matching filenames */
|
|
DBG((DBGA,"Getting glob results for '%s'\n",schpath))
|
|
free(schpath);
|
|
for (;;) {
|
|
int i;
|
|
|
|
if ((fpath = aglob_next(&gg)) == NULL) {
|
|
if (gg.merr) { /* Malloc error */
|
|
free(sname);
|
|
free(spath);
|
|
free(path);
|
|
aglob_cleanup(&gg);
|
|
if (er != NULL) *er = xdg_alloc;
|
|
xdg_ifree(paths, fnames, npaths);
|
|
return 0;
|
|
}
|
|
break; /* No more matches */
|
|
}
|
|
DBG((DBGA,"Found match with '%s'\n",fpath))
|
|
|
|
if (opt != xdg_all_matches) {
|
|
/* Check that this one hasn't already been found */
|
|
/* in a different search directory */
|
|
for (i = 0; i < npaths; i++) {
|
|
if (strcmp(fpath + rlen, fnames[i]) == 0) {
|
|
/* Already been found earlier - ignore it */
|
|
break;
|
|
}
|
|
}
|
|
if (i < npaths) {
|
|
free(fpath);
|
|
DBG((DBGA,"Ignoring it because it's already in list\n"))
|
|
continue; /* Ignore it */
|
|
}
|
|
}
|
|
|
|
/* Found a file, so append it to the list */
|
|
if (npaths >= napaths) { /* Need more space in arrays */
|
|
napaths = napaths * 2 + 1;
|
|
if ((*paths = realloc(*paths, sizeof(char *) * napaths)) == NULL
|
|
|| (fnames = realloc(fnames, sizeof(char *) * napaths)) == NULL) {
|
|
a1loge(g_log, 1, "xdg_bds: realloc failed\n");
|
|
free(fpath);
|
|
free(sname);
|
|
free(spath);
|
|
free(path);
|
|
aglob_cleanup(&gg);
|
|
if (er != NULL) *er = xdg_alloc;
|
|
xdg_ifree(paths, fnames, npaths);
|
|
return 0;
|
|
}
|
|
}
|
|
if (((*paths)[npaths] = strdup(fpath)) == NULL) {
|
|
a1loge(g_log, 1, "xdg_bds: strdup failed\n");
|
|
free(fpath);
|
|
free(sname);
|
|
free(spath);
|
|
free(path);
|
|
aglob_cleanup(&gg);
|
|
if (er != NULL) *er = xdg_alloc;
|
|
xdg_ifree(paths, fnames, npaths);
|
|
return 0;
|
|
}
|
|
/* The non-searchpath part of the name found */
|
|
if ((fnames[npaths] = strdup(fpath + rlen)) == NULL) {
|
|
a1loge(g_log, 1, "xdg_bds: strdup failed\n");
|
|
free((*paths)[npaths]);
|
|
free(fpath);
|
|
free(sname);
|
|
free(spath);
|
|
free(path);
|
|
aglob_cleanup(&gg);
|
|
if (er != NULL) *er = xdg_alloc;
|
|
xdg_ifree(paths, fnames, npaths);
|
|
return 0;
|
|
}
|
|
free(fpath);
|
|
fpath = NULL;
|
|
npaths++;
|
|
}
|
|
aglob_cleanup(&gg);
|
|
|
|
/* Fall through to next search path */
|
|
|
|
} else { /* op == xdg_write */
|
|
char *pp = schpath;
|
|
struct stat sbuf;
|
|
mode_t mode = 0700; /* Default directory mode */
|
|
|
|
if (sc == xdg_user)
|
|
mode = 0700; /* Default directory mode for user */
|
|
else
|
|
mode = 0755; /* Default directory mode local system shared */
|
|
#ifndef NT
|
|
/* If we're creating a user dir/file and running as root sudo */
|
|
if (sc == xdg_user && geteuid() == 0) {
|
|
char *uids, *gids;
|
|
int uid, gid;
|
|
DBG((DBGA,"We're setting a user dir/file running as root\n"))
|
|
|
|
if ((uids = getenv("SUDO_UID")) != NULL
|
|
&& (gids = getenv("SUDO_GID")) != NULL) {
|
|
uid = atoi(uids);
|
|
gid = atoi(gids);
|
|
if (setegid(gid) || seteuid(uid)) {
|
|
DBG((DBGA,"seteuid or setegid failed\n"))
|
|
} else {
|
|
DBG((DBGA,"Set euid %d and egid %d\n",uid,gid))
|
|
}
|
|
}
|
|
/* If setting local system dir/file and not effective root, but sudo */
|
|
} else if (sc == xdg_local && getuid() == 0 && geteuid() != 0) {
|
|
if (getenv("SUDO_UID") != NULL
|
|
&& getenv("SUDO_GID") != NULL) {
|
|
DBG((DBGA,"We're setting a local system dir/file with uid = 0 && euid != 0\n"))
|
|
if (setegid(getgid()) || seteuid(getuid())) {
|
|
DBG((DBGA,"seteuid or setegid failed\n"))
|
|
} else {
|
|
DBG((DBGA,"Set euid %d, egid %d\n",geteuid(),getegid()))
|
|
}
|
|
}
|
|
}
|
|
#endif /* !NT */
|
|
|
|
#ifdef NT
|
|
if (*pp != '\000' /* Skip drive number */
|
|
&& ((*pp >= 'a' && *pp <= 'z') || (*pp >= 'A' && *pp <= 'Z'))
|
|
&& pp[1] == ':')
|
|
pp += 2;
|
|
#endif
|
|
if (*pp == '/')
|
|
pp++; /* Skip root directory */
|
|
|
|
/* Check each directory in hierarchy, and */
|
|
/* create it if it doesn't exist. */
|
|
DBG((DBGA,"About to check & create whole path '%s'\n",schpath))
|
|
for (;pp != NULL && *pp != '\000';) {
|
|
if ((pp = strchr(pp, '/')) != NULL) {
|
|
*pp = '\000';
|
|
DBG((DBGA,"Checking path '%s'\n",schpath))
|
|
if (stat(schpath,&sbuf) != 0) {
|
|
/* Doesn't exist */
|
|
DBG((DBGA,"Path '%s' doesn't exist - creating it\n",schpath))
|
|
if (mkdir(schpath, mode) != 0) {
|
|
/* Depending on the permissions this can be flakey... */
|
|
DBG((DBGA,"mkdir failed - ignoring & continuing\n"))
|
|
// DBG((DBGA,"mkdir failed - giving up on this one\n"))
|
|
// break;
|
|
}
|
|
} else {
|
|
mode = sbuf.st_mode;
|
|
}
|
|
*pp = '/';
|
|
pp++;
|
|
}
|
|
}
|
|
|
|
/* If we got to the end of the hierarchy, */
|
|
/* then the path looks good to write to, */
|
|
/* so create a list of one and we're done */
|
|
if (pp == NULL || *pp == '\000') {
|
|
|
|
if ((*paths = malloc(sizeof(char *))) == NULL) {
|
|
a1loge(g_log, 1, "xdg_bds: malloc failed\n");
|
|
free(schpath);
|
|
free(sname);
|
|
free(spath);
|
|
free(path);
|
|
if (er != NULL) *er = xdg_alloc;
|
|
return 0;
|
|
}
|
|
if (((*paths)[npaths] = schpath) == NULL) {
|
|
free(sname);
|
|
free(spath);
|
|
free(path);
|
|
if (er != NULL) *er = xdg_alloc;
|
|
free(*paths);
|
|
return 0;
|
|
}
|
|
npaths++;
|
|
DBG((DBGA,"Returning 0: '%s'\n",(*paths)[0]))
|
|
free(sname);
|
|
free(spath);
|
|
free(path);
|
|
return npaths;
|
|
}
|
|
}
|
|
|
|
/* Move on to the next name part */
|
|
free(sname); sname = NULL;
|
|
if (*nep == SSEP)
|
|
ncp = nep+1;
|
|
else
|
|
ncp = nep;
|
|
}
|
|
|
|
/* Move on to the next search path */
|
|
free(spath); spath = NULL;
|
|
if (*ep == SSEP)
|
|
cp = ep+1;
|
|
else
|
|
cp = ep;
|
|
}
|
|
}
|
|
|
|
/* We're done looking through search paths */
|
|
free(path);
|
|
|
|
if (npaths == 0) { /* Didn't find anything */
|
|
if (er != NULL) *er = xdg_nopath;
|
|
xdg_ifree(paths, fnames, npaths);
|
|
} else {
|
|
xdg_ifree(NULL, fnames, npaths);
|
|
#ifdef DEBUG
|
|
{
|
|
int i;
|
|
a1logd(DBGA,"Returning list\n");
|
|
for (i = 0; i < npaths; i++)
|
|
a1logd(DBGA," %d: '%s'\n",i,(*paths)[i]);
|
|
}
|
|
#endif
|
|
}
|
|
return npaths;
|
|
}
|
|
|
|
/* Return a string corresponding to the error value */
|
|
char *xdg_errstr(xdg_error er) {
|
|
switch (er) {
|
|
case xdg_ok:
|
|
return "OK";
|
|
case xdg_alloc:
|
|
return "memory allocation failed";
|
|
case xdg_nohome:
|
|
return "There is no $HOME";
|
|
case xdg_noalluserprofile:
|
|
return "There's no $ALLUSERSPROFILE is no $ALLUSERSPROFILE";
|
|
case xdg_nopath:
|
|
return "There is no resulting path";
|
|
case xdg_mallformed:
|
|
return "Malformed path fount";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------- */
|
|
#ifdef STANDALONE_TEST
|
|
/* test code */
|
|
|
|
/* Return nz on error */
|
|
static int touch(char *name) {
|
|
FILE *fp;
|
|
|
|
if ((fp = fopen(name,"w")) == NULL)
|
|
return 1;
|
|
|
|
if (fclose(fp))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Check a file can be opened */
|
|
/* Return nz on error */
|
|
static int check(char *name) {
|
|
FILE *fp;
|
|
|
|
if ((fp = fopen(name,"r")) == NULL)
|
|
return 1;
|
|
|
|
if (fclose(fp))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Delete a path and a file */
|
|
/* Return nz on error */
|
|
static int delpath(char *path, int depth) {
|
|
int i;
|
|
char *pp;
|
|
|
|
for (i = 0; i < depth;) {
|
|
// a1logd(DBGA,"deleting '%s'\n",path);
|
|
if (i == 0) {
|
|
if (unlink(path)) {
|
|
// a1logd(DBGA,"unlink '%s' failed\n",path);
|
|
return 1;
|
|
}
|
|
} else {
|
|
if (rmdir(path)) {
|
|
// a1logd(DBGA,"rmdir '%s' failed\n",path);
|
|
return 1;
|
|
}
|
|
}
|
|
i++;
|
|
if (i == depth)
|
|
return 0;
|
|
|
|
if ((pp = strrchr(path, '/')) == NULL)
|
|
return 0;
|
|
*pp = '\000';
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Run a test */
|
|
static int runtest(
|
|
xdg_storage_type st, /* Specify the storage type */
|
|
xdg_scope sc, /* Scope if write */
|
|
char *pfname, /* Sub-path and file name */
|
|
char *env, /* Environment variable being set */
|
|
char *envv, /* Value to set it to */
|
|
char *defv, /* default variable needed for read */
|
|
int depth /* Cleanup depth */
|
|
) {
|
|
xdg_error er;
|
|
char *xval;
|
|
char **paths;
|
|
int nopaths;
|
|
char buf[200];
|
|
|
|
if ((xval = getenv(env)) != NULL) /* Save value before mods */
|
|
xval = strdup(xval);
|
|
if (*env != '\000') { /* If it is to be set */
|
|
sprintf(buf, "%s=%s",env,envv);
|
|
mputenv(buf);
|
|
}
|
|
|
|
printf("\nTesting Variable %s\n",env);
|
|
if ((nopaths = xdg_bds(&er, &paths, st, xdg_write, sc, xdg_none, pfname)) == 0) {
|
|
printf("Write test failed with %s\n",xdg_errstr(er));
|
|
return 1;
|
|
}
|
|
printf("Create %s %s returned '%s'\n",
|
|
st == xdg_data ? "Data" : st == xdg_data ? "Conf" : "Cache",
|
|
sc == xdg_user ? "User" : "Local", paths[0]);
|
|
if (touch(paths[0])) {
|
|
printf("Creating file %s failed\n",paths[0]);
|
|
return 1;
|
|
}
|
|
if (check(paths[0])) {
|
|
printf("Checking file %s failed\n",paths[0]);
|
|
return 1;
|
|
}
|
|
|
|
if (sc == xdg_local && *env != '\000') { /* Add another path */
|
|
sprintf(buf, "%s=xdgtestXXX%c%s",env,SSEP,envv);
|
|
mputenv(buf);
|
|
}
|
|
|
|
if (defv != NULL) {
|
|
sprintf(buf, "%s=xdg_NOT_%s",defv,defv);
|
|
mputenv(buf);
|
|
}
|
|
xdg_free(paths, nopaths);
|
|
|
|
if ((nopaths = xdg_bds(&er, &paths, st, xdg_read, sc, xdg_none, pfname)) < 1) {
|
|
printf("Read test failed with %s\n",xdg_errstr(er));
|
|
return 1;
|
|
}
|
|
printf(" Read %s %s returned '%s'\n",
|
|
st == xdg_data ? "Data" : st == xdg_data ? "Conf" : "Cache",
|
|
sc == xdg_user ? "User" : "Local",paths[0]);
|
|
if (check(paths[0])) {
|
|
printf("Checking file %s failed\n",paths[0]);
|
|
return 1;
|
|
}
|
|
if (delpath(paths[0], depth)) {
|
|
printf("Warning: Deleting file %s failed\n",paths[0]);
|
|
}
|
|
xdg_free(paths, nopaths);
|
|
|
|
/* Restore variables value */
|
|
if (xval == NULL)
|
|
sprintf(buf, "%s=",env);
|
|
else
|
|
sprintf(buf, "%s=%s",env,xval);
|
|
mputenv(buf);
|
|
|
|
if (defv != NULL) {
|
|
sprintf(buf, "%s=",defv);
|
|
mputenv(buf);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef struct {
|
|
xdg_storage_type st; /* Storage type */
|
|
xdg_scope sc; /* Scope if write */
|
|
char *defv[2]; /* Default variables needed for user & local tests on read */
|
|
char *envn[10]; /* Environment variable name to set */
|
|
} testcase;
|
|
|
|
int
|
|
main() {
|
|
char buf1[200], buf2[200];
|
|
int i, j;
|
|
|
|
#ifdef NT
|
|
testcase cases[5] = {
|
|
{ xdg_data, xdg_user, {"ALLUSERSPROFILE", NULL},
|
|
{ "XDG_DATA_HOME", "APPDATA", "HOME", "APPDATA", NULL } },
|
|
{ xdg_conf, xdg_user, {"ALLUSERSPROFILE", NULL},
|
|
{ "XDG_CONFIG_HOME", "APPDATA", "HOME", "APPDATA", NULL } },
|
|
{ xdg_cache, xdg_user, {NULL, NULL},
|
|
{ "XDG_CACHE_HOME", "APPDATA", "HOME", "APPDATA", NULL } },
|
|
{ xdg_data, xdg_local, {NULL, "HOME"},
|
|
{ "XDG_DATA_DIRS", "ALLUSERSPROFILE", NULL } },
|
|
{ xdg_conf, xdg_local, {NULL, "HOME"},
|
|
{ "XDG_CONFIG_DIRS", "ALLUSERSPROFILE", NULL } }
|
|
};
|
|
#else /* Apple, Unix, Default */
|
|
testcase cases[5] = {
|
|
{ xdg_data, xdg_user, {NULL, NULL},
|
|
{ "XDG_DATA_HOME", "HOME", NULL } },
|
|
{ xdg_conf, xdg_user, {NULL, NULL},
|
|
{ "XDG_CONFIG_HOME", "HOME", NULL } },
|
|
{ xdg_cache, xdg_user, {NULL, NULL},
|
|
{ "XDG_CACHE_HOME", "HOME", NULL } },
|
|
{ xdg_data, xdg_local, {NULL, "HOME"},
|
|
{ "XDG_DATA_DIRS", "", NULL } },
|
|
{ xdg_conf, xdg_local, {NULL, "HOME"},
|
|
{ "XDG_CONFIG_DIRS", "", NULL } }
|
|
};
|
|
#endif
|
|
|
|
/* First clear all the environment variables */
|
|
for (i = 0; i < 5; i++) {
|
|
for (j = 0; ;j++) {
|
|
if (cases[i].envn[j] == NULL)
|
|
break;
|
|
sprintf(buf1, "%s=",cases[i].envn[j]);
|
|
mputenv(buf1);
|
|
}
|
|
}
|
|
|
|
/* Then run all the tests */
|
|
for (i = 0; i < 5; i++) {
|
|
for (j = 0; ;j++) {
|
|
if (cases[i].envn[j] == NULL)
|
|
break;
|
|
sprintf(buf1, "xdgtest%d",i);
|
|
sprintf(buf2, "application/%s",cases[i].st == xdg_data ? "data" :
|
|
cases[i].st == xdg_conf ? "config" : "cache");
|
|
if (runtest(cases[i].st, cases[i].sc, buf2, cases[i].envn[j],buf1,
|
|
cases[i].defv[cases[i].sc == xdg_user ? 0 : 1],9))
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
printf("Test completed OK\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* STANDALONE_TEST */
|
|
|
|
|