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