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