improved .desktop parsing.
[dana/openbox.git] / obt / paths.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    obt/paths.c for the Openbox window manager
4    Copyright (c) 2003-2007   Dana Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "obt/bsearch.h"
20 #include "obt/paths.h"
21 #include "obt/util.h"
22
23 #ifdef HAVE_STDLIB_H
24 #  include <stdlib.h>
25 #endif
26 #ifdef HAVE_SYS_STAT_H
27 #  include <sys/stat.h>
28 #endif
29 #ifdef HAVE_SYS_TYPES_H
30 #  include <sys/types.h>
31 #endif
32 #ifdef HAVE_STRING_H
33 #  include <string.h>
34 #endif
35 #ifdef HAVE_UNISTD_H
36 #  include <unistd.h>
37 #endif
38 #ifdef HAVE_GRP_H
39 #  include <grp.h>
40 #endif
41 #ifdef HAVE_PWD_H
42 #  include <pwd.h>
43 #endif
44
45 struct _ObtPaths
46 {
47     gint   ref;
48     gchar  *config_home;
49     gchar  *data_home;
50     gchar  *cache_home;
51     GSList *config_dirs;
52     GSList *data_dirs;
53     GSList *autostart_dirs;
54     GSList *exec_dirs;
55
56     uid_t   uid;
57     gid_t  *gid;
58     guint   n_gid;
59 };
60
61 static gint slist_path_cmp(const gchar *a, const gchar *b)
62 {
63     return strcmp(a, b);
64 }
65
66 typedef GSList* (*GSListFunc) (gpointer list, gconstpointer data);
67
68 static GSList* slist_path_add(GSList *list, gpointer data, GSListFunc func)
69 {
70     g_assert(func);
71
72     if (!data)
73         return list;
74
75     if (!g_slist_find_custom(list, data, (GCompareFunc) slist_path_cmp))
76         list = func(list, data);
77     else
78         g_free(data);
79
80     return list;
81 }
82
83 static GSList* split_paths(const gchar *paths)
84 {
85     GSList *list = NULL;
86     gchar **spl, **it;
87
88     if (!paths)
89         return NULL;
90     spl = g_strsplit(paths, ":", -1);
91     for (it = spl; *it; ++it)
92         list = slist_path_add(list, *it, (GSListFunc) g_slist_append);
93     g_free(spl);
94     return list;
95 }
96
97 int gid_cmp(const void *va, const void *vb)
98 {
99     const gid_t a = *(const gid_t*)va, b = *(const gid_t*)vb;
100     return a>b ? 1 : (a == b ? 0 : -1);
101 }
102
103 static void find_uid_gid(uid_t *u, gid_t **g, guint *n)
104 {
105     struct passwd *pw;
106     const gchar *name;
107     struct group *gr;
108
109     *u = getuid();
110     pw = getpwuid(*u);
111     name = pw->pw_name;
112
113     *g = g_new(gid_t, *n=1);
114     (*g)[0] = getgid();
115
116     while ((gr = getgrent())) {
117         if (gr->gr_gid != (*g)[0]) { /* skip the main group */
118             gchar **c;
119             for (c = gr->gr_mem; *c; ++c)
120                 if (strcmp(*c, name) == 0) {
121                     *g = g_renew(gid_t, *g, ++(*n)); /* save the group */
122                     (*g)[*n-1] = gr->gr_gid;
123                     break;
124                 }
125         }
126     }
127     endgrent();
128
129     qsort(*g, *n, sizeof(gid_t), gid_cmp);
130 }
131
132 ObtPaths* obt_paths_new(void)
133 {
134     ObtPaths *p;
135     const gchar *path;
136     GSList *it;
137
138     p = g_slice_new0(ObtPaths);
139     p->ref = 1;
140
141     find_uid_gid(&p->uid, &p->gid, &p->n_gid);
142
143     path = g_getenv("XDG_CONFIG_HOME");
144     if (path && path[0] != '\0') /* not unset or empty */
145         p->config_home = g_build_filename(path, NULL);
146     else
147         p->config_home = g_build_filename(g_get_home_dir(), ".config", NULL);
148
149     path = g_getenv("XDG_DATA_HOME");
150     if (path && path[0] != '\0') /* not unset or empty */
151         p->data_home = g_build_filename(path, NULL);
152     else
153         p->data_home = g_build_filename(g_get_home_dir(), ".local",
154                                         "share", NULL);
155
156     path = g_getenv("XDG_CACHE_HOME");
157     if (path && path[0] != '\0') /* not unset or empty */
158         p->cache_home = g_build_filename(path, NULL);
159     else
160         p->cache_home = g_build_filename(g_get_home_dir(), ".cache", NULL);
161
162     path = g_getenv("XDG_CONFIG_DIRS");
163     if (path && path[0] != '\0') /* not unset or empty */
164         p->config_dirs = split_paths(path);
165     else {
166         p->config_dirs = slist_path_add(p->config_dirs,
167                                         g_strdup(CONFIGDIR),
168                                         (GSListFunc) g_slist_append);
169         p->config_dirs = slist_path_add(p->config_dirs,
170                                         g_build_filename
171                                         (G_DIR_SEPARATOR_S,
172                                          "etc", "xdg", NULL),
173                                         (GSListFunc) g_slist_append);
174     }
175     p->config_dirs = slist_path_add(p->config_dirs,
176                                     g_strdup(p->config_home),
177                                     (GSListFunc) g_slist_prepend);
178
179     for (it = p->config_dirs; it; it = g_slist_next(it)) {
180         gchar *const s = g_strdup_printf("%s/autostart", (gchar*)it->data);
181         p->autostart_dirs = g_slist_append(p->autostart_dirs, s);
182     }
183
184     path = g_getenv("XDG_DATA_DIRS");
185     if (path && path[0] != '\0') /* not unset or empty */
186         p->data_dirs = split_paths(path);
187     else {
188         p->data_dirs = slist_path_add(p->data_dirs,
189                                       g_strdup(DATADIR),
190                                       (GSListFunc) g_slist_append);
191         p->data_dirs = slist_path_add(p->data_dirs,
192                                       g_build_filename
193                                       (G_DIR_SEPARATOR_S,
194                                        "usr", "local", "share", NULL),
195                                       (GSListFunc) g_slist_append);
196         p->data_dirs = slist_path_add(p->data_dirs,
197                                       g_build_filename
198                                       (G_DIR_SEPARATOR_S,
199                                        "usr", "share", NULL),
200                                       (GSListFunc) g_slist_append);
201     }
202     p->data_dirs = slist_path_add(p->data_dirs,
203                                   g_strdup(p->data_home),
204                                   (GSListFunc) g_slist_prepend);
205
206     path = g_getenv("PATH");
207     if (path && path[0] != '\0') /* not unset or empty */
208         p->exec_dirs = split_paths(path);
209     else
210         p->exec_dirs = NULL;
211
212     return p;
213 }
214
215 void obt_paths_ref(ObtPaths *p)
216 {
217     ++p->ref;
218 }
219
220 void obt_paths_unref(ObtPaths *p)
221 {
222     if (p && --p->ref == 0) {
223         GSList *it;
224
225         for (it = p->config_dirs; it; it = g_slist_next(it))
226             g_free(it->data);
227         g_slist_free(p->config_dirs);
228         for (it = p->data_dirs; it; it = g_slist_next(it))
229             g_free(it->data);
230         g_slist_free(p->data_dirs);
231         for (it = p->autostart_dirs; it; it = g_slist_next(it))
232             g_free(it->data);
233         g_slist_free(p->autostart_dirs);
234         for (it = p->exec_dirs; it; it = g_slist_next(it))
235             g_free(it->data);
236         g_slist_free(p->exec_dirs);
237         g_free(p->config_home);
238         g_free(p->data_home);
239         g_free(p->cache_home);
240         g_free(p->gid);
241
242         g_slice_free(ObtPaths, p);
243     }
244 }
245
246 gchar *obt_paths_expand_tilde(const gchar *f)
247 {
248     gchar *ret;
249     GRegex *regex;
250
251     if (!f)
252         return NULL;
253
254     regex = g_regex_new("(?:^|(?<=[ \\t]))~(?=[/ \\t$])", G_REGEX_MULTILINE | G_REGEX_RAW, 0, NULL);
255     ret = g_regex_replace_literal(regex, f, -1, 0, g_get_home_dir(), 0, NULL);
256     g_regex_unref(regex);
257
258     return ret;
259 }
260
261 gboolean obt_paths_mkdir(const gchar *path, gint mode)
262 {
263     gboolean ret = TRUE;
264
265     g_return_val_if_fail(path != NULL, FALSE);
266     g_return_val_if_fail(path[0] != '\0', FALSE);
267
268     if (!g_file_test(path, G_FILE_TEST_IS_DIR))
269         if (mkdir(path, mode) == -1)
270             ret = FALSE;
271
272     return ret;
273 }
274
275 gboolean obt_paths_mkdir_path(const gchar *path, gint mode)
276 {
277     gboolean ret = TRUE;
278
279     g_return_val_if_fail(path != NULL, FALSE);
280     g_return_val_if_fail(path[0] == '/', FALSE);
281
282     if (!g_file_test(path, G_FILE_TEST_IS_DIR)) {
283         gchar *c, *e;
284
285         c = g_strdup(path);
286         e = c;
287         while ((e = strchr(e + 1, '/'))) {
288             *e = '\0';
289             if (!(ret = obt_paths_mkdir(c, mode)))
290                 goto parse_mkdir_path_end;
291             *e = '/';
292         }
293         ret = obt_paths_mkdir(c, mode);
294
295     parse_mkdir_path_end:
296         g_free(c);
297     }
298
299     return ret;
300 }
301
302 const gchar* obt_paths_config_home(ObtPaths *p)
303 {
304     return p->config_home;
305 }
306
307 const gchar* obt_paths_data_home(ObtPaths *p)
308 {
309     return p->data_home;
310 }
311
312 const gchar* obt_paths_cache_home(ObtPaths *p)
313 {
314     return p->cache_home;
315 }
316
317 GSList* obt_paths_config_dirs(ObtPaths *p)
318 {
319     return p->config_dirs;
320 }
321
322 GSList* obt_paths_data_dirs(ObtPaths *p)
323 {
324     return p->data_dirs;
325 }
326
327 GSList* obt_paths_autostart_dirs(ObtPaths *p)
328 {
329     return p->autostart_dirs;
330 }
331
332 static inline gboolean try_exec(const ObtPaths *const p,
333                                 const gchar *const path)
334 {
335     struct stat st;
336     BSEARCH_SETUP(guint);
337
338     if (stat(path, &st) != 0)
339         return FALSE;
340
341     if (!S_ISREG(st.st_mode))
342         return FALSE;
343     if (st.st_uid == p->uid)
344         return st.st_mode & S_IXUSR;
345     BSEARCH(guint, p->gid, 0, p->n_gid, st.st_gid);
346     if (BSEARCH_FOUND())
347         return st.st_mode & S_IXGRP;
348     return st.st_mode & S_IXOTH;
349 }
350
351 gboolean obt_paths_try_exec(ObtPaths *p, const gchar *path)
352 {
353     if (path[0] == '/') {
354         return try_exec(p, path);
355     }
356     else {
357         GSList *it;
358
359         for (it = p->exec_dirs; it; it = g_slist_next(it)) {
360             gchar *f = g_strdup_printf(it->data, G_DIR_SEPARATOR_S, path);
361             gboolean e = try_exec(p, f);
362             g_free(f);
363             if (e) return TRUE;
364         }
365     }
366
367     return FALSE;
368 }