1 /*
2 Accessing user configuration settings
3 */
4
5 /*
6 * Copyright (C) 2010 Lawrence D'Oliveiro <ldo@geek-central.gen.nz>.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or (at
11 * your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 * MA 02110-1301 USA.
22 */
23
24 #include "compat.h"
25 #include <errno.h>
26 #include "conffile.h"
27
28 /*
29 Implementation of relevant parts of the XDG Base Directory specification
30 <http://standards.freedesktop.org/basedir-spec/latest/>.
31 */
32
xdg_make_home_relative(const char * path)33 static char * xdg_make_home_relative
34 (
35 const char * path
36 )
37 /* prepends the value of $HOME onto path (assumed not to begin with a slash), or
38 NULL on error (out of memory, $HOME not defined or not absolute). Caller must dispose
39 of the result pointer. */
40 {
41 const char * const home = getenv("HOME");
42 char * result = 0;
43 size_t result_len;
44 do /*once*/
45 {
46 if (home == 0 || home[0] != '/')
47 {
48 errno = ENOENT;
49 break;
50 } /*if*/
51 /* assert strlen(home) > 0 */
52 result_len = strlen(home) + 1 + strlen(path) + 1; /* worst case */
53 result = malloc(result_len);
54 if (result == 0)
55 break;
56 strncpy(result, home, result_len);
57 if (result[strlen(result) - 1] != '/')
58 {
59 strconcat(result, result_len, "/");
60 } /*if*/
61 strconcat(result, result_len, path);
62 }
63 while (false);
64 return
65 result;
66 } /*xdg_make_home_relative*/
67
xdg_get_config_home(void)68 static char * xdg_get_config_home(void)
69 /* returns the directory for holding user-specific config files, or NULL on
70 error. Caller must dispose of the result pointer. */
71 {
72 char * result;
73 void * to_dispose = 0;
74 result = getenv("XDG_CONFIG_HOME");
75 if (result == 0)
76 {
77 to_dispose = xdg_make_home_relative(".config");
78 if (to_dispose == 0)
79 {
80 return
81 result;
82 }
83 result = to_dispose;
84 } /*if*/
85 if (result != 0 && to_dispose == 0)
86 {
87 result = strdup(result);
88 } /*if*/
89 return
90 result;
91 } /*xdg_get_config_home*/
92
xdg_config_search_path(void)93 static char * xdg_config_search_path(void)
94 /* returns a string containing the colon-separated list of config directories to search
95 (apart from the user area). Caller must dispose of the result pointer. */
96 {
97 char * result = getenv("XDG_CONFIG_DIRS");
98 if (result == 0)
99 {
100 result = "/etc";
101 /* note spec actually says default should be /etc/xdg, but /etc is the
102 conventional location for system config files. */
103 } /*if*/
104 return strdup(result);
105 } /*xdg_config_search_path*/
106
107 typedef int (*xdg_path_component_action)
108 (
109 const unsigned char * path, /* storage belongs to me, make a copy if you want to keep it */
110 size_t path_len, /* length of path string */
111 void * arg /* meaning is up to you */
112 );
113 /* return nonzero to abort the scan */
114
xdg_for_each_path_component(const unsigned char * path,size_t path_len,xdg_path_component_action action,void * actionarg,bool forwards)115 static int xdg_for_each_path_component
116 (
117 const unsigned char * path,
118 size_t path_len,
119 xdg_path_component_action action,
120 void * actionarg,
121 bool forwards /* false to do in reverse */
122 )
123 /* splits the string path with len path_len at any colon separators, calling
124 action for each component found, in forward or reverse order as specified.
125 Returns nonzero on error, or if action returned nonzero. */
126 {
127 int status;
128 const unsigned char * const path_end = path + path_len;
129 const unsigned char * path_prev;
130 const unsigned char * path_next;
131 if (forwards)
132 {
133 path_prev = path;
134 for (;;)
135 {
136 path_next = path_prev;
137 for (;;)
138 {
139 if (path_next == path_end)
140 break;
141 if (*path_next == ':')
142 break;
143 ++path_next;
144 } /*for*/
145 status = action(path_prev, path_next - path_prev, actionarg);
146 if (status != 0)
147 break;
148 if (path_next == path_end)
149 break;
150 path_prev = path_next + 1;
151 } /*for*/
152 }
153 else /* backwards */
154 {
155 path_next = path_end;
156 for (;;)
157 {
158 path_prev = path_next;
159 for (;;)
160 {
161 if (path_prev == path)
162 break;
163 --path_prev;
164 if (*path_prev == ':')
165 {
166 ++path_prev;
167 break;
168 } /*if*/
169 } /*for*/
170 status = action(path_prev, path_next - path_prev, actionarg);
171 if (status != 0)
172 break;
173 if (path_prev == path)
174 break;
175 path_next = path_prev - 1;
176 } /*for*/
177 } /*if*/
178 return status;
179 } /*xdg_for_each_path_component*/
180
181 typedef int (*xdg_item_path_action)
182 (
183 const char * path, /* a complete expanded pathname */
184 void * arg /* meaning is up to you */
185 );
186 /* return nonzero to abort the scan */
187
188 typedef struct
189 {
190 const char * itempath;
191 xdg_item_path_action action;
192 void * actionarg;
193 } xdg_for_each_config_found_context;
194
xdg_for_each_config_found_try_component(const unsigned char * dirpath,size_t dirpath_len,xdg_for_each_config_found_context * context)195 static int xdg_for_each_config_found_try_component
196 (
197 const unsigned char * dirpath,
198 size_t dirpath_len,
199 xdg_for_each_config_found_context * context
200 )
201 /* generates the full item path, and passes it to the caller's action
202 if it is accessible. */
203 {
204 char * thispath;
205 const size_t thispath_maxlen = dirpath_len + 1 + strlen(context->itempath) + 1;
206 struct stat statinfo;
207 int status = 0;
208 do /*once*/
209 {
210 thispath = malloc(thispath_maxlen);
211 if (thispath == 0)
212 {
213 status = -1;
214 break;
215 } /*if*/
216 memcpy(thispath, dirpath, dirpath_len);
217 thispath[dirpath_len] = 0;
218 if (dirpath_len != 0 && dirpath[dirpath_len - 1] != '/')
219 {
220 strconcat(thispath, thispath_maxlen, "/");
221 } /*if*/
222 strconcat(thispath, thispath_maxlen, context->itempath);
223 if (stat(thispath, &statinfo) == 0)
224 {
225 status = context->action(thispath, context->actionarg);
226 }
227 else
228 {
229 errno = 0; /* ignore stat result */
230 } /*if*/
231 }
232 while (false);
233 free(thispath);
234 return
235 status;
236 } /*xdg_for_each_config_found_try_component*/;
237
xdg_for_each_config_found(const char * itempath,xdg_item_path_action action,void * actionarg,bool forwards)238 static int xdg_for_each_config_found
239 (
240 const char * itempath, /* relative path of item to look for in each directory */
241 xdg_item_path_action action,
242 void * actionarg,
243 bool forwards /* false to do in reverse */
244 )
245 {
246 xdg_for_each_config_found_context context;
247 int status = 0;
248 const char * const home_path = xdg_get_config_home();
249 const char * const search_path = xdg_config_search_path();
250 context.itempath = itempath;
251 context.action = action;
252 context.actionarg = actionarg;
253 do /*once*/
254 {
255 if (home_path != 0 && forwards)
256 {
257 status = xdg_for_each_config_found_try_component
258 (
259 (const unsigned char *)home_path,
260 strlen(home_path),
261 &context
262 );
263 if (status != 0)
264 break;
265 } /*if*/
266 status = xdg_for_each_path_component
267 (
268 /*path =*/ (const unsigned char *)search_path,
269 /*path_len =*/ strlen(search_path),
270 /*action =*/ (xdg_path_component_action)xdg_for_each_config_found_try_component,
271 /*actionarg =*/ (void *)&context,
272 /*forwards =*/ forwards
273 );
274 if (status != 0)
275 break;
276 if (home_path != 0 && !forwards)
277 {
278 status = xdg_for_each_config_found_try_component
279 (
280 (const unsigned char *)home_path,
281 strlen(home_path),
282 &context
283 );
284 if (status != 0)
285 break;
286 } /*if*/
287 }
288 while (false);
289 if (home_path != 0)
290 free((void *)home_path);
291 free((void *)search_path);
292 return
293 status;
294 } /*xdg_for_each_config_found*/
295
296 typedef struct
297 {
298 char * result;
299 } xdg_find_first_config_path_context;
300
xdg_find_first_config_path_save_item(const char * itempath,xdg_find_first_config_path_context * context)301 static int xdg_find_first_config_path_save_item
302 (
303 const char * itempath,
304 xdg_find_first_config_path_context * context
305 )
306 {
307 context->result = strdup(itempath);
308 return
309 context->result != 0;
310 } /*xdg_find_first_config_path_save_item*/;
311
xdg_find_first_config_path(const char * itempath)312 static char * xdg_find_first_config_path
313 (
314 const char * itempath
315 )
316 /* searches for itempath in all the config directory locations in order of decreasing
317 priority, returning the expansion where it is first found, or NULL if not found.
318 Caller must dispose of the result pointer. */
319 {
320 xdg_find_first_config_path_context context;
321 context.result = 0;
322 errno = 0;
323 (void)xdg_for_each_config_found
324 (
325 /*itempath =*/ itempath,
326 /*action =*/ (xdg_item_path_action)xdg_find_first_config_path_save_item,
327 /*actionarg =*/ (void *)&context,
328 /*forwards =*/ true
329 );
330 if (context.result == 0 && errno == 0)
331 {
332 errno = ENOENT;
333 } /*if*/
334 return
335 context.result;
336 } /*xdg_find_first_config_path*/
337
338 /*
339 User-visible stuff
340 */
341
get_outputdir(void)342 char * get_outputdir(void)
343 /* allocates and returns a string containing the user-specified output
344 directory path, or NULL if not specified. */
345 {
346 #if 0 /* don't do this any more */
347 char * outputdir = getenv("OUTPUTDIR");
348 if (outputdir != 0)
349 {
350 outputdir = strdup(outputdir);
351 } /*if*/
352 return outputdir;
353 #else
354 return 0;
355 #endif
356 } /*get_outputdir*/
357
get_video_format(void)358 int get_video_format(void)
359 /* returns one of VF_NTSC or VF_PAL indicating the default video format
360 if specified, else VF_NONE if not. */
361 /* This implements the Video Format Preference proposal
362 <http://create.freedesktop.org/wiki/Video_Format_Pref>. */
363 {
364 int result = VF_NONE;
365 char * format;
366 char * conffilename = 0;
367 FILE * conffile = 0;
368 char * eol;
369 char line[40]; /* should be plenty */
370 do /*once*/
371 {
372 format = getenv("VIDEO_FORMAT");
373 if (format != 0)
374 break;
375 conffilename = xdg_find_first_config_path("video_format");
376 if (conffilename == 0)
377 break;
378 conffile = fopen(conffilename, "r");
379 if (!conffile)
380 break;
381 format = fgets(line, sizeof line, conffile);
382 eol = strchr(format, '\n');
383 if (eol)
384 {
385 *eol = 0;
386 } /*if*/
387 break;
388 }
389 while (false);
390 if (conffile)
391 {
392 fclose(conffile);
393 } /*if*/
394 free(conffilename);
395 if (format != 0)
396 {
397 if (!strcasecmp(format, "NTSC"))
398 {
399 result = VF_NTSC;
400 }
401 else if (!strcasecmp(format, "PAL"))
402 {
403 result = VF_PAL;
404 } /*if*/
405 } /*if*/
406 return result;
407 } /*get_video_format*/
408