1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4 
5 #include <sys/types.h>
6 #ifdef _WIN32
7 # include <evil_private.h> /* mkdir */
8 #else
9 # include <pwd.h>
10 #endif
11 
12 #include <Eina.h>
13 
14 #include "eina_internal.h"
15 #include "eina_private.h"
16 
17 static Eina_Hash *vpath_data = NULL;
18 
19 #ifdef CRI
20 #undef CRI
21 #endif
22 #define CRI(...) EINA_LOG_DOM_CRIT(_eina_vpath_log_dom, __VA_ARGS__)
23 
24 #ifdef ERR
25 #undef ERR
26 #endif
27 #define ERR(...) EINA_LOG_DOM_ERR(_eina_vpath_log_dom, __VA_ARGS__)
28 
29 #ifdef DBG
30 #undef DBG
31 #endif
32 #define DBG(...) EINA_LOG_DOM_DBG(_eina_vpath_log_dom, __VA_ARGS__)
33 
34 static int _eina_vpath_log_dom = -1;
35 
36 static inline void
_eina_vpath_data_add(const char * key,const char * value)37 _eina_vpath_data_add(const char *key, const char *value)
38 {
39    eina_hash_add(vpath_data, key, eina_stringshare_add(value));
40 }
41 
42 static inline Eina_Stringshare*
_eina_vpath_data_get(const char * key)43 _eina_vpath_data_get(const char *key)
44 {
45    return eina_hash_find(vpath_data, key);
46 }
47 
48 
49 static char *
_fallback_runtime_dir(const char * home)50 _fallback_runtime_dir(const char *home)
51 {
52    char buf[PATH_MAX];
53    struct stat st;
54 #if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
55    uid_t uid = getuid();
56 
57    if (setuid(geteuid()) != 0)
58       {
59          fprintf(stderr,
60                  "FATAL: Cannot setuid - errno=%i\n",
61                  errno);
62          abort();
63       }
64 #endif
65    // fallback - make ~/.run
66    snprintf(buf, sizeof(buf), "%s/.run", home);
67    if (!!mkdir(buf,  S_IRUSR | S_IWUSR | S_IXUSR))
68      {
69         if (errno == EEXIST)
70           {
71              if (stat(buf, &st) == 0)
72                {
73                   // some sanity checks - but not for security
74                   if (!(st.st_mode & S_IFDIR))
75                     {
76                        // fatal - exists but is not a dir
77                        fprintf(stderr,
78                                "FATAL: run dir '%s' exists but not a dir\n",
79                                buf);
80                        abort();
81                     }
82 #if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
83                   if (st.st_uid != geteuid())
84                     {
85                        // fatal - run dir doesn't belong to user
86                        fprintf(stderr,
87                                "FATAL: run dir '%s' not owned by uid %i\n",
88                                buf, (int)geteuid());
89                        abort();
90                     }
91 #endif
92                }
93              else
94                {
95                   // fatal - we cant create our run dir in ~/
96                   fprintf(stderr,
97                           "FATAL: Cannot verify run dir '%s' errno=%i\n",
98                           buf, errno);
99                   abort();
100                }
101           }
102         else
103           {
104              // fatal - we cant create our run dir in ~/
105              fprintf(stderr,
106                      "FATAL: Cannot create run dir '%s' - errno=%i\n",
107                      buf, errno);
108              abort();
109           }
110      }
111 #if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
112   if (setreuid(uid, geteuid()) != 0)
113      {
114         fprintf(stderr,
115                 "FATAL: Cannot setreuid - errno=%i\n",
116                 errno);
117         abort();
118      }
119 #endif
120 
121    return strdup(buf);
122 }
123 
124 static char *
_fallback_home_dir()125 _fallback_home_dir()
126 {
127    char buf[PATH_MAX];
128    /* Windows does not have getuid(), but home can't be NULL */
129 #ifdef HAVE_GETEUID
130    uid_t uid = geteuid();
131    struct stat st;
132 
133    snprintf(buf, sizeof(buf), "/tmp/%i", (int)uid);
134    if (mkdir(buf,  S_IRUSR | S_IWUSR | S_IXUSR) < 0)
135      {
136         if (errno != EEXIST)
137           {
138              if (stat("/tmp", &st) == 0)
139                snprintf(buf, sizeof(buf), "/tmp");
140              else
141                snprintf(buf, sizeof(buf), "/");
142           }
143      }
144    if (stat(buf, &st) != 0)
145      {
146         if (stat("/tmp", &st) == 0)
147           snprintf(buf, sizeof(buf), "/tmp");
148         else
149           snprintf(buf, sizeof(buf), "/");
150      }
151 #else
152    snprintf(buf, sizeof(buf), "/");
153 #endif
154    return strdup(buf);
155 }
156 
157 static void
_eina_vpath_interface_sys_init(void)158 _eina_vpath_interface_sys_init(void)
159 {
160    const char *home, *tmp;
161 
162    // $HOME / ~/ etc.
163    home = eina_environment_home_get();
164    if (!home)
165      {
166         char *home2 = _fallback_home_dir();
167         _eina_vpath_data_add("home", home2);
168         free(home2);
169      }
170    else
171      _eina_vpath_data_add("home", home);
172 
173    // tmp dir - system wide
174    tmp = eina_environment_tmp_get();
175    _eina_vpath_data_add("tmp", tmp);
176 }
177 
178 Eina_Bool
eina_vpath_init(void)179 eina_vpath_init(void)
180 {
181    vpath_data = eina_hash_string_superfast_new((Eina_Free_Cb)eina_stringshare_del);
182 
183    _eina_vpath_interface_sys_init();
184    eina_xdg_env_init();
185 
186    _eina_vpath_log_dom = eina_log_domain_register("vpath", "cyan");
187    return EINA_TRUE;
188 }
189 
190 Eina_Bool
eina_vpath_shutdown(void)191 eina_vpath_shutdown(void)
192 {
193    eina_hash_free(vpath_data);
194    vpath_data = NULL;
195    eina_log_domain_unregister(_eina_vpath_log_dom);
196    _eina_vpath_log_dom = -1;
197    return EINA_TRUE;
198 }
199 
200 #ifdef HAVE_GETPWENT
201 static Eina_Bool
_fetch_user_homedir(char ** str,const char * name,const char * error)202 _fetch_user_homedir(char **str, const char *name, const char *error)
203 {
204   *str = NULL;
205   struct passwd *pwent;
206 
207   pwent = getpwnam(name);
208   if (!pwent)
209     {
210        ERR("User %s not found\nThe string was: %s", name, error);
211        return EINA_FALSE;
212     }
213   *str = pwent->pw_dir;
214 
215   return EINA_TRUE;
216 }
217 #endif
218 
219 static int
_eina_vpath_resolve(const char * path,char * str,size_t size)220 _eina_vpath_resolve(const char *path, char *str, size_t size)
221 {
222    if (path[0] == '~')
223      {
224         char *home = NULL;
225         // ~/ <- home directory
226         if (path[1] == '/')
227           {
228              home = eina_hash_find(vpath_data, "home");
229              path ++;
230           }
231         // ~username/ <- homedir of user "username"
232         else
233           {
234 #ifndef HAVE_GETPWENT
235              ERR("User fetching is disabled on this system\nThe string was: %s", path);
236              return 0;
237 #else
238              const char *p;
239              char *name;
240 
241              for (p = path + 1; *p; p++)
242                {
243                   if (*p =='/') break;
244                }
245              name = alloca(p - path);
246              strncpy(name, path + 1, p - path - 1);
247              name[p - path - 1] = 0;
248 
249              if (!_fetch_user_homedir(&home, name, path))
250                return 0;
251              path = p;
252 #endif
253            }
254          if (home)
255            {
256               return snprintf(str, size, "%s%s", home, path);
257            }
258      }
259    // (:xxx:)/* ... <- meta hash table
260    else if (((path[0] == '(') && (path[1] == ':')) ||
261             ((path[0] == '$') && (path[1] == '{')))
262      {
263         const char *p, *end, *meta;
264         const char *msg_start, *msg_end;
265         char *name;
266         int offset;
267         Eina_Bool found = EINA_FALSE;
268 
269         if (path[0] == '(')
270           {
271              end = p = strstr(path + 2, ":)");
272              offset = 2;
273              msg_start = "(:";
274              msg_end = ":)";
275           }
276         else
277           {
278              end = p = strchr(path + 2, '}');
279              offset = 1;
280              msg_start = "${";
281              msg_end = "}";
282           }
283         if (p) found = EINA_TRUE;
284         p += offset;
285 
286         if (!found)
287           {
288              ERR("'%s' Needs to have a matching '%s'\nThe string was: %s", msg_start, msg_end, path);
289              return 0;
290           }
291 
292         if (*p != '/')
293           {
294              ERR("A / is expected after '%s'\nThe string was: %s", msg_end, path);
295              return 0;
296           }
297 
298         if (found)
299           {
300              name = alloca(end - path);
301              strncpy(name, path + 2, end - path - offset);
302              name[end - path - 2] = 0;
303              meta = _eina_vpath_data_get(name);
304              if (meta)
305                {
306                   return snprintf(str, size, "%s%s", meta, end + offset);
307                }
308              else
309                {
310                   ERR("Meta key '%s' was not registered!\nThe string was: %s", name, path);
311                   return 0;
312                }
313           }
314      }
315    //just return the path, since we assume that this is a normal path
316    else
317      {
318         return snprintf(str, size, "%s", path);
319      }
320    str[0] = '\0';
321    return 0;
322 }
323 
324 EAPI char *
eina_vpath_resolve(const char * path)325 eina_vpath_resolve(const char* path)
326 {
327    char buf[PATH_MAX];
328    EINA_SAFETY_ON_NULL_RETURN_VAL(path, NULL);
329 
330    if (_eina_vpath_resolve(path, buf, sizeof(buf)) > 0)
331      return strdup(buf);
332    return NULL;
333 }
334 
335 EAPI int
eina_vpath_resolve_snprintf(char * str,size_t size,const char * format,...)336 eina_vpath_resolve_snprintf(char *str, size_t size, const char *format, ...)
337 {
338    va_list args;
339    char *path;
340    int r;
341 
342    // XXX: implement parse of path then look up in hash if not just create
343    // object where path and result are the same and return that with
344    // path set and result set to resolved path - return obj handler calls
345    // "do" on object to get the result inside fetched or failed callback.
346    // if it's a url then we need a new classs that overrides the do and
347    // begins a fetch and on finish calls the event cb or when wait is called
348    /* FIXME: not working for WIndows */
349    // /* <- full path
350 
351    path = alloca(size + 1);
352 
353    va_start(args, format);
354    vsnprintf(path, size, format, args);
355    va_end(args);
356 
357    r = _eina_vpath_resolve(path, str, size);
358    if (r > 0) return r;
359 
360    ERR("The path has to start with either '~/' or '(:NAME:)/' or be a normal path \nThe string was: %s", path);
361 
362    return 0;
363 }
364 
365 EAPI void
eina_vpath_interface_app_set(const char * app_domain,Eina_Prefix * app_pfx)366 eina_vpath_interface_app_set(const char *app_domain, Eina_Prefix *app_pfx)
367 {
368    char buf[PATH_MAX];
369 
370    EINA_SAFETY_ON_NULL_RETURN(app_domain);
371    EINA_SAFETY_ON_NULL_RETURN(app_pfx);
372 
373    _eina_vpath_data_add("app.dir", eina_prefix_get(app_pfx));
374    _eina_vpath_data_add("app.bin", eina_prefix_bin_get(app_pfx));
375    _eina_vpath_data_add("app.lib", eina_prefix_lib_get(app_pfx));
376    _eina_vpath_data_add("app.data", eina_prefix_data_get(app_pfx));
377    _eina_vpath_data_add("app.locale", eina_prefix_locale_get(app_pfx));
378    snprintf(buf, sizeof(buf), "%s/%s",
379             _eina_vpath_data_get("usr.config"), app_domain);
380    _eina_vpath_data_add("app.config", buf);
381    snprintf(buf, sizeof(buf), "%s/%s",
382             _eina_vpath_data_get("usr.cache"), app_domain);
383    _eina_vpath_data_add("app.cache", buf);
384    snprintf(buf, sizeof(buf), "%s/%s",
385             _eina_vpath_data_get("usr.data"), app_domain);
386    _eina_vpath_data_add("app.local", buf);
387    snprintf(buf, sizeof(buf), "%s/%s",
388             _eina_vpath_data_get("usr.tmp"), app_domain);
389    _eina_vpath_data_add("app.tmp", buf);
390 }
391 
392 EAPI void
eina_vpath_interface_user_set(Eina_Vpath_Interface_User * user)393 eina_vpath_interface_user_set(Eina_Vpath_Interface_User *user)
394 {
395    Eina_Bool free_run = EINA_FALSE;
396 
397    EINA_SAFETY_ON_NULL_RETURN(user);
398 
399    if (!user->run)
400      {
401         user->run = _fallback_runtime_dir(_eina_vpath_data_get("home"));
402         free_run = EINA_TRUE;
403      }
404 
405 #define ADD(a) _eina_vpath_data_add("usr." #a , user->a)
406    ADD(desktop);
407    ADD(documents);
408    ADD(downloads);
409    ADD(music);
410    ADD(pictures);
411    ADD(pub);
412    ADD(templates);
413    ADD(videos);
414    ADD(data);
415    ADD(config);
416    ADD(cache);
417    ADD(run);
418    ADD(tmp);
419 #undef ADD
420 
421    if (free_run)
422      free((char *)user->run);
423 }
424