1 /*
2  *  Functions for storing program settings
3  *  Copyright (C) 2008 Andreas Öman
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <fcntl.h>
22 #include <stdio.h>
23 #include <errno.h>
24 #include <stdlib.h>
25 #include <stdarg.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <dirent.h>
29 
30 #include "htsmsg.h"
31 #include "htsmsg_binary.h"
32 #include "htsmsg_json.h"
33 #include "settings.h"
34 #include "tvheadend.h"
35 #include "filebundle.h"
36 
37 static char *settingspath = NULL;
38 
39 /**
40  *
41  */
42 const char *
hts_settings_get_root(void)43 hts_settings_get_root(void)
44 {
45   return settingspath;
46 }
47 
48 /**
49  *
50  */
51 void
hts_settings_init(const char * confpath)52 hts_settings_init(const char *confpath)
53 {
54   if (confpath)
55     settingspath = realpath(confpath, NULL);
56 }
57 
58 /**
59  *
60  */
61 void
hts_settings_done(void)62 hts_settings_done(void)
63 {
64   free(settingspath);
65 }
66 
67 /**
68  *
69  */
70 int
hts_settings_makedirs(const char * inpath)71 hts_settings_makedirs ( const char *inpath )
72 {
73   size_t x = strlen(inpath) - 1;
74   char *path = alloca(x + 2);
75 
76   if (path == NULL) return -1;
77   strcpy(path, inpath);
78 
79   while (x) {
80     if (path[x] == '/') {
81       path[x] = 0;
82       break;
83     }
84     x--;
85   }
86   return makedirs(LS_SETTINGS, path, 0700, 1, -1, -1);
87 }
88 
89 /**
90  *
91  */
92 static void
_hts_settings_buildpath(char * dst,size_t dstsize,const char * fmt,va_list ap,const char * prefix)93 _hts_settings_buildpath
94   (char *dst, size_t dstsize, const char *fmt, va_list ap, const char *prefix)
95 {
96   char tmp[PATH_MAX];
97   char *n = dst;
98 
99   vsnprintf(tmp, sizeof(tmp), fmt, ap);
100   if (*tmp != '/' && prefix)
101     snprintf(dst, dstsize, "%s/%s", prefix, tmp);
102   else
103     strlcpy(dst, tmp, dstsize);
104 
105   while(*n) {
106     if(*n == ':' || *n == '?' || *n == '*' || *n > 127 || *n < 32)
107       *n = '_';
108     n++;
109   }
110 }
111 
112 int
hts_settings_buildpath(char * dst,size_t dstsize,const char * fmt,...)113 hts_settings_buildpath
114   (char *dst, size_t dstsize, const char *fmt, ...)
115 {
116   va_list va;
117   if (!settingspath)
118     return 1;
119   va_start(va, fmt);
120   _hts_settings_buildpath(dst, dstsize, fmt, va, settingspath);
121   va_end(va);
122   return 0;
123 }
124 
125 /**
126  *
127  */
128 void
hts_settings_save(htsmsg_t * record,const char * pathfmt,...)129 hts_settings_save(htsmsg_t *record, const char *pathfmt, ...)
130 {
131   char path[PATH_MAX];
132   char tmppath[PATH_MAX + 4];
133   int fd;
134   va_list ap;
135   htsbuf_queue_t hq;
136   htsbuf_data_t *hd;
137   int ok, r, pack;
138 
139   if(settingspath == NULL)
140     return;
141 
142   /* Clean the path */
143   va_start(ap, pathfmt);
144   _hts_settings_buildpath(path, sizeof(path), pathfmt, ap, settingspath);
145   va_end(ap);
146 
147   /* Create directories */
148   if (hts_settings_makedirs(path)) return;
149 
150   tvhdebug(LS_SETTINGS, "saving to %s", path);
151 
152   /* Create tmp file */
153   snprintf(tmppath, sizeof(tmppath), "%s.tmp", path);
154   if((fd = tvh_open(tmppath, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR)) < 0) {
155     tvhalert(LS_SETTINGS, "Unable to create \"%s\" - %s",
156 	     tmppath, strerror(errno));
157     return;
158   }
159 
160   /* Store data */
161 #if ENABLE_ZLIB
162   pack = strstr(path, "/muxes/") != NULL && /* ugly, redesign API */
163          strstr(path, "/networks/") != NULL &&
164          strstr(path, "/input/") != NULL;
165 #else
166   pack = 0;
167 #endif
168   ok = 1;
169 
170   if (!pack) {
171     htsbuf_queue_init(&hq, 0);
172     htsmsg_json_serialize(record, &hq, 1);
173     TAILQ_FOREACH(hd, &hq.hq_q, hd_link)
174       if(tvh_write(fd, hd->hd_data + hd->hd_data_off, hd->hd_data_len)) {
175         tvhalert(LS_SETTINGS, "Failed to write file \"%s\" - %s",
176                  tmppath, strerror(errno));
177         ok = 0;
178         break;
179       }
180     htsbuf_queue_flush(&hq);
181   } else {
182 #if ENABLE_ZLIB
183     void *msgdata = NULL;
184     size_t msglen;
185     r = htsmsg_binary_serialize(record, &msgdata, &msglen, 2*1024*1024);
186     if (!r && msglen >= 4) {
187       r = tvh_gzip_deflate_fd_header(fd, msgdata + 4, msglen - 4, NULL, 3);
188       if (r)
189         ok = 0;
190     } else {
191       tvhalert(LS_SETTINGS, "Unable to pack the configuration data \"%s\"", path);
192     }
193     free(msgdata);
194 #endif
195   }
196   close(fd);
197 
198   /* Move */
199   if(ok) {
200     r = rename(tmppath, path);
201     if (r && errno == EISDIR) {
202       rmtree(path);
203       r = rename(tmppath, path);
204     }
205     if (r)
206       tvhalert(LS_SETTINGS, "Unable to rename file \"%s\" to \"%s\" - %s",
207 	       tmppath, path, strerror(errno));
208 
209   /* Delete tmp */
210   } else
211     unlink(tmppath);
212 }
213 
214 /**
215  *
216  */
217 static htsmsg_t *
hts_settings_load_one(const char * filename)218 hts_settings_load_one(const char *filename)
219 {
220   ssize_t n, size;
221   char *mem;
222   fb_file *fp;
223   htsmsg_t *r = NULL;
224 
225   /* Open */
226   if (!(fp = fb_open(filename, 1, 0))) return NULL;
227   size = fb_size(fp);
228 
229   /* Load data */
230   mem    = malloc(size+1);
231   n      = fb_read(fp, mem, size);
232   if (n >= 0) mem[n] = 0;
233 
234   /* Decode */
235   if(n == size) {
236     if (size > 12 && memcmp(mem, "\xff\xffGZIP00", 8) == 0) {
237 #if ENABLE_ZLIB
238       uint32_t orig = (mem[8] << 24) | (mem[9] << 16) | (mem[10] << 8) | mem[11];
239       if (orig > 10*1024*1024U) {
240         tvhalert(LS_SETTINGS, "too big gzip for %s", filename);
241         r = NULL;
242       } else if (orig > 0) {
243         uint8_t *unpacked = tvh_gzip_inflate((uint8_t *)mem + 12, size - 12, orig);
244         if (unpacked) {
245           r = htsmsg_binary_deserialize(unpacked, orig, NULL);
246           free(unpacked);
247         }
248       }
249 #endif
250     } else {
251       r = htsmsg_json_deserialize(mem);
252     }
253   }
254 
255   /* Close */
256   fb_close(fp);
257   free(mem);
258 
259   return r;
260 }
261 
262 /**
263  *
264  */
265 static htsmsg_t *
hts_settings_load_path(const char * fullpath,int depth)266 hts_settings_load_path(const char *fullpath, int depth)
267 {
268   char child[PATH_MAX];
269   struct filebundle_stat st;
270   fb_dirent **namelist, *d;
271   htsmsg_t *r, *c;
272   int n, i;
273 
274   /* Invalid */
275   if (fb_stat(fullpath, &st)) return NULL;
276 
277   /* Directory */
278   if (st.is_dir) {
279 
280     /* Get file list */
281     if((n = fb_scandir(fullpath, &namelist)) < 0)
282       return NULL;
283 
284     /* Read files */
285     r = htsmsg_create_map();
286     for(i = 0; i < n; i++) {
287       d = namelist[i];
288       if(d->name[0] != '.') {
289 
290         snprintf(child, sizeof(child), "%s/%s", fullpath, d->name);
291         if(d->type == FB_DIR && depth > 0) {
292           c = hts_settings_load_path(child, depth - 1);
293         } else {
294           c = hts_settings_load_one(child);
295         }
296         if(c != NULL)
297           htsmsg_add_msg(r, d->name, c);
298 
299       }
300       free(d);
301     }
302     free(namelist);
303 
304   /* File */
305   } else {
306     r = hts_settings_load_one(fullpath);
307   }
308 
309   return r;
310 }
311 
312 /**
313  *
314  */
315 static htsmsg_t *
hts_settings_vload(const char * pathfmt,va_list ap,int depth)316 hts_settings_vload(const char *pathfmt, va_list ap, int depth)
317 {
318   htsmsg_t *ret = NULL;
319   char fullpath[PATH_MAX];
320   va_list ap2;
321   va_copy(ap2, ap);
322 
323   /* Try normal path */
324   _hts_settings_buildpath(fullpath, sizeof(fullpath),
325                           pathfmt, ap, settingspath);
326   ret = hts_settings_load_path(fullpath, depth);
327 
328   /* Try bundle path */
329   if (!ret && *pathfmt != '/') {
330     _hts_settings_buildpath(fullpath, sizeof(fullpath),
331                             pathfmt, ap2, "data/conf");
332     ret = hts_settings_load_path(fullpath, depth);
333   }
334 
335   va_end(ap2);
336 
337   return ret;
338 }
339 
340 
341 /**
342  *
343  */
344 htsmsg_t *
hts_settings_load(const char * pathfmt,...)345 hts_settings_load(const char *pathfmt, ...)
346 {
347   va_list ap;
348   va_start(ap, pathfmt);
349   htsmsg_t *r = hts_settings_vload(pathfmt, ap, 0);
350   va_end(ap);
351   return r;
352 }
353 
354 
355 /**
356  *
357  */
358 htsmsg_t *
hts_settings_load_r(int depth,const char * pathfmt,...)359 hts_settings_load_r(int depth, const char *pathfmt, ...)
360 {
361   va_list ap;
362   va_start(ap, pathfmt);
363   htsmsg_t *r = hts_settings_vload(pathfmt, ap, depth);
364   va_end(ap);
365   return r;
366 }
367 
368 /**
369  *
370  */
371 void
hts_settings_remove(const char * pathfmt,...)372 hts_settings_remove(const char *pathfmt, ...)
373 {
374   char fullpath[PATH_MAX];
375   va_list ap;
376   struct stat st;
377 
378   va_start(ap, pathfmt);
379   _hts_settings_buildpath(fullpath, sizeof(fullpath),
380                           pathfmt, ap, settingspath);
381   va_end(ap);
382   if (stat(fullpath, &st) == 0) {
383     if (S_ISDIR(st.st_mode))
384       rmtree(fullpath);
385     else {
386       unlink(fullpath);
387       while (rmdir(dirname(fullpath)) == 0);
388     }
389   }
390 }
391 
392 /**
393  *
394  */
395 int
hts_settings_open_file(int for_write,const char * pathfmt,...)396 hts_settings_open_file(int for_write, const char *pathfmt, ...)
397 {
398   char path[PATH_MAX];
399   int flags;
400   va_list ap;
401 
402   /* Build path */
403   va_start(ap, pathfmt);
404   _hts_settings_buildpath(path, sizeof(path), pathfmt, ap, settingspath);
405   va_end(ap);
406 
407   /* Create directories */
408   if (for_write)
409     if (hts_settings_makedirs(path)) return -1;
410 
411   /* Open file */
412   flags = for_write ? O_CREAT | O_TRUNC | O_WRONLY : O_RDONLY;
413 
414   return tvh_open(path, flags, S_IRUSR | S_IWUSR);
415 }
416 
417 /*
418  * Check if a path exists
419  */
420 int
hts_settings_exists(const char * pathfmt,...)421 hts_settings_exists ( const char *pathfmt, ... )
422 {
423   va_list ap;
424   char path[PATH_MAX];
425   struct stat st;
426 
427   /* Build path */
428   va_start(ap, pathfmt);
429   _hts_settings_buildpath(path, sizeof(path), pathfmt, ap, settingspath);
430   va_end(ap);
431 
432   return (stat(path, &st) == 0);
433 }
434