introduce eina_vpath!

Its the successor of efl.vpath, the api is synchronous, and supports
addtional vpath paths, new apis cal always be added to use them.
This commit is contained in:
Marcel Hollerbach 2018-02-18 22:12:58 +01:00
parent 0a66978041
commit 7165003bc6
8 changed files with 488 additions and 3 deletions

View File

@ -107,7 +107,8 @@ lib/eina/eina_slice.h \
lib/eina/eina_inline_slice.x \
lib/eina/eina_inline_modinfo.x \
lib/eina/eina_freeq.h \
lib/eina/eina_slstr.h
lib/eina/eina_slstr.h \
lib/eina/eina_vpath.h
lib_eina_libeina_la_SOURCES = \
@ -183,7 +184,8 @@ lib/eina/eina_quaternion.c \
lib/eina/eina_bezier.c \
lib/eina/eina_safepointer.c \
lib/eina/eina_freeq.c \
lib/eina/eina_slstr.c
lib/eina/eina_slstr.c \
lib/eina/eina_vpath.c
if HAVE_WIN32
@ -356,7 +358,8 @@ tests/eina/eina_test_bezier.c \
tests/eina/eina_test_safepointer.c \
tests/eina/eina_test_slice.c \
tests/eina/eina_test_freeq.c \
tests/eina/eina_test_slstr.c
tests/eina/eina_test_slstr.c \
tests/eina/eina_test_vpath.c
tests_eina_eina_suite_CPPFLAGS = -I$(top_builddir)/src/lib/efl \
-DTESTS_WD=\"`pwd`\" \

View File

@ -274,6 +274,7 @@ extern "C" {
#include <eina_slstr.h>
#include <eina_debug.h>
#include <eina_promise.h>
#include <eina_vpath.h>
#undef EAPI
#define EAPI

View File

@ -68,6 +68,7 @@
#include "eina_evlog.h"
#include "eina_freeq.h"
#include "eina_slstr.h"
#include "eina_vpath.h"
/*============================================================================*
* Local *
@ -155,6 +156,7 @@ EAPI Eina_Inlist *_eina_tracking = NULL;
S(safepointer);
S(slstr);
S(promise);
S(vpath);
#undef S
struct eina_desc_setup
@ -202,6 +204,7 @@ static const struct eina_desc_setup _eina_desc_setup[] = {
S(safepointer),
S(slstr),
S(promise),
S(vpath),
#undef S
};
static const size_t _eina_desc_setup_len = sizeof(_eina_desc_setup) /

349
src/lib/eina/eina_vpath.c Normal file
View File

@ -0,0 +1,349 @@
#include <Eina.h>
#include "eina_private.h"
static Eina_Hash *vpath_data = NULL;
#ifdef CRI
#undef CRI
#endif
#define CRI(...) EINA_LOG_DOM_CRIT(_eina_vpath_log_dom, __VA_ARGS__)
#ifdef ERR
#undef ERR
#endif
#define ERR(...) EINA_LOG_DOM_ERR(_eina_vpath_log_dom, __VA_ARGS__)
#ifdef DBG
#undef DBG
#endif
#define DBG(...) EINA_LOG_DOM_DBG(_eina_vpath_log_dom, __VA_ARGS__)
static int _eina_vpath_log_dom = -1;
static inline void
_eina_vpath_data_add(const char *key, const char *value)
{
eina_hash_add(vpath_data, key, eina_stringshare_add(value));
}
static inline Eina_Stringshare*
_eina_vpath_data_get(const char *key)
{
return eina_hash_find(vpath_data, key);
}
static char*
_fallback_runtime_dir(const char *home)
{
char buf[PATH_MAX];
struct stat st;
#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
if (setuid(geteuid()) != 0)
{
fprintf(stderr,
"FATAL: Cannot setuid - errno=%i\n",
errno);
abort();
}
#endif
// fallback - make ~/.run
snprintf(buf, sizeof(buf), "%s/.run", home);
if (!!mkdir(buf, S_IRUSR | S_IWUSR | S_IXUSR))
{
if (errno == EEXIST)
{
if (stat(buf, &st) == 0)
{
// some sanity checks - but not for security
if (!(st.st_mode & S_IFDIR))
{
// fatal - exists but is not a dir
fprintf(stderr,
"FATAL: run dir '%s' exists but not a dir\n",
buf);
abort();
}
#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
if (st.st_uid != geteuid())
{
// fatal - run dir doesn't belong to user
fprintf(stderr,
"FATAL: run dir '%s' not owned by uid %i\n",
buf, (int)geteuid());
abort();
}
#endif
}
else
{
// fatal - we cant create our run dir in ~/
fprintf(stderr,
"FATAL: Cannot verify run dir '%s' errno=%i\n",
buf, errno);
abort();
}
}
else
{
// fatal - we cant create our run dir in ~/
fprintf(stderr,
"FATAL: Cannot create run dir '%s' - errno=%i\n",
buf, errno);
abort();
}
}
#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
if (setreuid(uid, geteuid()) != 0)
{
fprintf(stderr,
"FATAL: Cannot setreuid - errno=%i\n",
errno);
abort();
}
#endif
return strdup(buf);
}
static char*
_fallback_home_dir()
{
char buf[PATH_MAX];
/* Windows does not have getuid(), but home can't be NULL */
#ifdef HAVE_GETEUID
uid_t uid = geteuid();
struct stat st;
snprintf(buf, sizeof(buf), "/tmp/%i", (int)uid);
if (mkdir(buf, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
{
if (errno != EEXIST)
{
if (stat("/tmp", &st) == 0)
snprintf(buf, sizeof(buf), "/tmp");
else
snprintf(buf, sizeof(buf), "/");
}
}
if (stat(buf, &st) != 0)
{
if (stat("/tmp", &st) == 0)
snprintf(buf, sizeof(buf), "/tmp");
else
snprintf(buf, sizeof(buf), "/");
}
#else
snprintf(buf, sizeof(buf), "/");
#endif
return strdup(buf);
}
static void
_eina_vpath_interface_sys_init(void)
{
const char *home, *tmp;
// $HOME / ~/ etc.
home = eina_environment_home_get();
if (!home)
home = _fallback_home_dir();
// tmp dir - system wide
tmp = eina_environment_tmp_get();
_eina_vpath_data_add("home", home);
_eina_vpath_data_add("tmp", tmp);
}
Eina_Bool
eina_vpath_init(void)
{
vpath_data = eina_hash_string_superfast_new((Eina_Free_Cb)eina_stringshare_del);
_eina_vpath_interface_sys_init();
_eina_vpath_log_dom = eina_log_domain_register("vpath", "cyan");
return EINA_TRUE;
}
Eina_Bool
eina_vpath_shutdown(void)
{
eina_log_domain_unregister(_eina_vpath_log_dom);
_eina_vpath_log_dom = -1;
return EINA_TRUE;
}
EAPI char*
eina_vpath_resolve(const char* path)
{
// XXX: implement parse of path then look up in hash if not just create
// object where path and result are the same and return that with
// path set and result set to resolved path - return obj handler calls
// "do" on object to get the result inside fetched or failed callback.
// if it's a url then we need a new classs that overrides the do and
// begins a fetch and on finish calls the event cb or when wait is called
/* FIXME: not working for WIndows */
// /* <- full path
if (!path) return NULL;
if (path[0] == '~')
{
// ~/ <- home directory
if (path[1] == '/')
{
char buf[PATH_MAX];
const char *home = eina_hash_find(vpath_data, "home");
if (home)
{
snprintf(buf, sizeof(buf), "%s%s", home, path + 1);
return strdup(buf);
}
}
#ifdef HAVE_GETPWENT
// ~username/ <- homedir of user "username"
else
{
const char *p;
struct passwd pwent, *pwent2 = NULL;
char *name, buf[PATH_MAX], pwbuf[8129];
for (p = path + 1; *p; p++)
{
if (*p =='/') break;
}
name = alloca(p - path);
strncpy(name, path + 1, p - path - 1);
name[p - path - 1] = 0;
if (!getpwnam_r(name, &pwent, pwbuf, sizeof(pwbuf), &pwent2))
{
if ((pwent2) && (pwent.pw_dir))
{
return strdup(buf);
}
}
}
#endif /* HAVE_GETPWENT */
return NULL;
}
// (:xxx:)/* ... <- meta hash table
else if ((path[0] == '(') && (path[1] == ':'))
{
const char *p, *end, *meta;
char *name, buf[PATH_MAX];
int max_len = strlen(path);
Eina_Bool found = EINA_FALSE;
for (p = path + 2; p <= path + max_len - 2; p++)
{
if ((p[0] ==':') && (p[1] == ')'))
{
end = p;
found = EINA_TRUE;
break;
}
}
p += 2;
if (!found)
{
ERR("(: Needs to have a matching ':)'\nThe string was: %s", path);
return NULL;
}
if (*p != '/')
{
ERR("A / is expected after :)\nThe string was: %s", path);
return NULL;
}
if (found)
{
name = alloca(end - path);
strncpy(name, path + 2, end - path - 2);
name[end - path - 2] = 0;
meta = _eina_vpath_data_get(name);
if (meta)
{
snprintf(buf, sizeof(buf), "%s%s", meta, end + 2);
return strdup(buf);
}
else
{
ERR("Meta key '%s' was not registered!\nThe string was: %s", name, path);
return NULL;
}
}
}
//just return the path, since we assume that this is a normal path
else
{
return strdup(path);
}
ERR("The path has to start with either '~/' or '(:NAME:)/' or be a normal path \nThe string was: %s", path);
return NULL;
}
EAPI void
eina_vpath_interface_app_set(const char *app_domain, Eina_Prefix *app_pfx)
{
char buf[PATH_MAX];
EINA_SAFETY_ON_NULL_RETURN(app_domain);
EINA_SAFETY_ON_NULL_RETURN(app_pfx);
_eina_vpath_data_add("app.dir", eina_prefix_get(app_pfx));
_eina_vpath_data_add("app.bin", eina_prefix_bin_get(app_pfx));
_eina_vpath_data_add("app.lib", eina_prefix_lib_get(app_pfx));
_eina_vpath_data_add("app.data", eina_prefix_data_get(app_pfx));
_eina_vpath_data_add("app.locale", eina_prefix_locale_get(app_pfx));
snprintf(buf, sizeof(buf), "%s/%s",
_eina_vpath_data_get("config"), app_domain);
_eina_vpath_data_add("app.config", buf);
snprintf(buf, sizeof(buf), "%s/%s",
_eina_vpath_data_get("cache"), app_domain);
_eina_vpath_data_add("app.cache", buf);
snprintf(buf, sizeof(buf), "%s/%s",
_eina_vpath_data_get("data"), app_domain);
_eina_vpath_data_add("app.local", buf);
}
EAPI void
eina_vpath_interface_user_set(Eina_Vpath_Interface_User *user)
{
char buf[PATH_MAX];
Eina_Bool free_run = EINA_FALSE;
EINA_SAFETY_ON_NULL_RETURN(user);
if (!user->run)
{
user->run = _fallback_runtime_dir(_eina_vpath_data_get("home"));
free_run = EINA_TRUE;
}
#define ADD(a) _eina_vpath_data_add("usr." #a , user->a)
ADD(desktop);
ADD(documents);
ADD(downloads);
ADD(music);
ADD(pictures);
ADD(pub);
ADD(templates);
ADD(videos);
ADD(data);
ADD(config);
ADD(cache);
ADD(run);
#undef ADD
if (free_run)
free((char*)user->run);
}

74
src/lib/eina/eina_vpath.h Normal file
View File

@ -0,0 +1,74 @@
#ifndef EINA_VPATH_H
#define EINA_VPATH_H
#include "eina_prefix.h"
/*
* Eina_vpath
* eina vpath is a path that can be prefixed with a virtual path.
*
* A virutla path can either start with (:XXXXXXXX:) that indicates a virtual path, OR normal with / or ./ or ../ or ~
* The char sequence in between (: and :) are used as key to lookup the real value.
* The key has to be set by a interface before, otherwise you will get a error.
*
* The symbol ~ is interpretated as the home directory of the running user, and will be replaced.
* Additional infos: https://phab.enlightenment.org/w/eina_vpath/
*/
/**
* This datatype is a vpath, this means you can use the syntax described above.
*/
typedef const char* Eina_Vpath;
typedef struct
{
const char *desktop;
const char *documents;
const char *downloads;
const char *music;
const char *pictures;
const char *pub;
const char *templates;
const char *videos;
const char *data;
const char *config;
const char *cache;
const char *run;
} Eina_Vpath_Interface_User;
/**
* Make the app specific paths accessable as virtual path
*
* This will create :
* - app.dir
* - app.bin
* - app.lib
* - app.data
* - app.locale
* - app.config
* - app.cache
* - app.local
*
* If you do NOT call this api the virtual paths for app.* will be unset
*/
EAPI void eina_vpath_interface_app_set(const char *app_name, Eina_Prefix *p);
/**
* Create the desktop specific vpaths
*
* The virtual paths will be named usr.<field-name-of-struct>
*
* If you do NOT call this api the virtual paths for usr.* will be unset.
*/
EAPI void eina_vpath_interface_user_set(Eina_Vpath_Interface_User *user);
/*
* Translate a virtual path into a normal path.
*
* @return a string that is not virtual anymore
*
*/
EAPI char* eina_vpath_resolve(Eina_Vpath path);
#endif

View File

@ -87,6 +87,7 @@ static const Efl_Test_Case etc[] = {
{ "Free Queue", eina_test_freeq },
{ "Util", eina_test_util },
{ "Short Lived Strings", eina_test_slstr },
{ "Vpath", eina_test_vpath },
{ NULL, NULL }
};

View File

@ -74,5 +74,6 @@ void eina_test_safepointer(TCase *tc);
void eina_test_slice(TCase *tc);
void eina_test_freeq(TCase *tc);
void eina_test_slstr(TCase *tc);
void eina_test_vpath(TCase *tc);
#endif /* EINA_SUITE_H_ */

View File

@ -0,0 +1,53 @@
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <Eina.h>
#include <check.h>
START_TEST(eina_test_vpath_valid)
{
int ret;
char test[PATH_MAX];
ret = eina_init();
ck_assert_int_ne(ret, 0);
ck_assert_str_eq(eina_vpath_resolve("/"), "/");
ck_assert_str_eq(eina_vpath_resolve("./"), "./");
ck_assert_str_eq(eina_vpath_resolve("..bla"), "..bla");
ck_assert_str_eq(eina_vpath_resolve(".bla"), ".bla");
snprintf(test, sizeof(test), "%s/", eina_environment_home_get());
ck_assert_str_eq(eina_vpath_resolve("~/"), test);
snprintf(test, sizeof(test), "%s/bla", eina_environment_home_get());
ck_assert_str_eq(eina_vpath_resolve("(:home:)/bla"), test);
ret = eina_shutdown();
}
END_TEST
START_TEST(eina_test_vpath_invalid)
{
int ret;
ret = eina_init();
ck_assert_int_ne(ret, 0);
ck_assert_ptr_null(eina_vpath_resolve("(:asdfasdfafasdf"));
ck_assert_ptr_null(eina_vpath_resolve("(:missing_slash:)"));
ck_assert_ptr_null(eina_vpath_resolve("(:"));
ck_assert_ptr_null(eina_vpath_resolve("(:home:)"));
ck_assert_ptr_null(eina_vpath_resolve("(:wrong_meta_key:)/"));
ret = eina_shutdown();
}
END_TEST
void eina_test_vpath(TCase *tc)
{
tcase_add_test(tc, eina_test_vpath_invalid);
tcase_add_test(tc, eina_test_vpath_valid);
}