diff --git a/src/config.c b/src/config.c index 3afdfbf6..9f445ed8 100644 --- a/src/config.c +++ b/src/config.c @@ -16,13 +16,15 @@ * along with this program. If not, see . */ +#include +#include + #include "tvheadend.h" #include "settings.h" #include "config.h" #include "uuid.h" - -#include -#include +#include "htsbuf.h" +#include "spawn.h" /* ************************************************************************* * Global data @@ -956,6 +958,70 @@ config_migrate_v11 ( void ) htsmsg_destroy(dvr_config); } +/* + * Perform backup + */ +static void +dobackup(const char *oldver) +{ + char outfile[PATH_MAX], cwd[PATH_MAX]; + const char *argv[] = { + "/usr/bin/tar", "cjf", outfile, "--exclude", "backup", ".", NULL + }; + const char *root = hts_settings_get_root(); + char errtxt[128]; + const char **arg; + int code; + + tvhinfo("config", "backup: migrating config from %s (running %s)", + oldver, tvheadend_version); + + getcwd(cwd, sizeof(cwd)); + + snprintf(outfile, sizeof(outfile), "%s/backup", root); + if (makedirs(outfile, 0700)) + goto fatal; + if (chdir(root)) { + tvherror("config", "unable to find directory '%s'", root); + goto fatal; + } + + snprintf(outfile, sizeof(outfile), "%s/backup/%s.tar.bz2", + root, oldver); + tvhinfo("config", "backup: running, output file %s", outfile); + + spawnv(argv[0], (void *)argv); + + while ((code = spawn_reap(errtxt, sizeof(errtxt))) == -EAGAIN) + usleep(20000); + + if (code) { + htsbuf_queue_t q; + char *s; + htsbuf_queue_init(&q, 0); + for (arg = argv; *arg; arg++) { + htsbuf_append(&q, *arg, strlen(*arg)); + if (arg[1]) + htsbuf_append(&q, " ", 1); + } + s = htsbuf_to_string(&q); + tvherror("config", "command '%s' returned error code %d", s, code); + free(s); + htsbuf_queue_flush(&q); + goto fatal; + } + + if (chdir(cwd)) { + tvherror("config", "unable to change directory to '%s'", cwd); + goto fatal; + } + return; + +fatal: + tvherror("config", "backup: fatal error"); + exit(EXIT_FAILURE); +} + /* * Migration table */ @@ -977,12 +1043,19 @@ static const config_migrate_t config_migrate_table[] = { * Perform migrations (if required) */ static int -config_migrate ( void ) +config_migrate ( int backup ) { uint32_t v; + const char *s; /* Get the current version */ v = htsmsg_get_u32_or_default(config, "version", 0); + s = htsmsg_get_str(config, "fullversion") ?: "unknown"; + + if (backup && strcmp(s, tvheadend_version)) + dobackup(s); + else + backup = 0; /* Attempt to auto-detect versions prior to v2 */ if (!v) { @@ -993,8 +1066,11 @@ config_migrate ( void ) } /* No changes required */ - if (v == ARRAY_SIZE(config_migrate_table)) + if (v == ARRAY_SIZE(config_migrate_table)) { + if (backup) + goto update; return 0; + } /* Run migrations */ for ( ; v < ARRAY_SIZE(config_migrate_table); v++) { @@ -1003,7 +1079,9 @@ config_migrate ( void ) } /* Update */ +update: htsmsg_set_u32(config, "version", v); + htsmsg_set_str(config, "fullversion", tvheadend_version); config_save(); return 1; } @@ -1050,7 +1128,7 @@ config_check ( void ) * *************************************************************************/ void -config_init ( const char *path ) +config_init ( const char *path, int backup ) { struct stat st; char buf[1024]; @@ -1095,11 +1173,12 @@ config_init ( const char *path ) /* Store version number */ if (new) { htsmsg_set_u32(config, "version", ARRAY_SIZE(config_migrate_table)); + htsmsg_set_str(config, "fullversion", tvheadend_version); config_save(); /* Perform migrations */ } else { - if (config_migrate()) + if (config_migrate(backup)) config_check(); } } diff --git a/src/config.h b/src/config.h index 4473947d..9f08a672 100644 --- a/src/config.h +++ b/src/config.h @@ -23,7 +23,7 @@ #include "htsmsg.h" -void config_init ( const char *path ); +void config_init ( const char *path, int backup ); void config_done ( void ); void config_save ( void ); diff --git a/src/main.c b/src/main.c index 0a2ae7a5..9bc86856 100644 --- a/src/main.c +++ b/src/main.c @@ -484,7 +484,8 @@ main(int argc, char **argv) opt_dump = 0, opt_xspf = 0, opt_dbus = 0, - opt_dbus_session = 0; + opt_dbus_session = 0, + opt_nobackup = 0; const char *opt_config = NULL, *opt_user = NULL, *opt_group = NULL, @@ -507,6 +508,7 @@ main(int argc, char **argv) { 0, NULL, "Service Configuration", OPT_BOOL, NULL }, { 'c', "config", "Alternate config path", OPT_STR, &opt_config }, + { 'B', "nobackup", "Do not backup config tree at upgrade", OPT_BOOL, &opt_nobackup }, { 'f', "fork", "Fork and run as daemon", OPT_BOOL, &opt_fork }, { 'u', "user", "Run as user", OPT_STR, &opt_user }, { 'g', "group", "Run as group", OPT_STR, &opt_group }, @@ -787,7 +789,7 @@ main(int argc, char **argv) /* Initialise configuration */ uuid_init(); idnode_init(); - config_init(opt_config); + config_init(opt_config, opt_nobackup == 0); /** * Initialize subsystems diff --git a/src/settings.c b/src/settings.c index 5fea51c5..cf31d893 100644 --- a/src/settings.c +++ b/src/settings.c @@ -184,21 +184,22 @@ hts_settings_save(htsmsg_t *record, const char *pathfmt, ...) static htsmsg_t * hts_settings_load_one(const char *filename) { - ssize_t n; + ssize_t n, size; char *mem; fb_file *fp; htsmsg_t *r = NULL; /* Open */ if (!(fp = fb_open(filename, 1, 0))) return NULL; + size = fb_size(fp); /* Load data */ - mem = malloc(fb_size(fp)+1); - n = fb_read(fp, mem, fb_size(fp)); + mem = malloc(size+1); + n = fb_read(fp, mem, size); if (n >= 0) mem[n] = 0; /* Decode */ - if(n == fb_size(fp)) + if(n == size) r = htsmsg_json_deserialize(mem); /* Close */ diff --git a/src/spawn.c b/src/spawn.c index b1bdacbf..b7d8bacc 100644 --- a/src/spawn.c +++ b/src/spawn.c @@ -84,50 +84,56 @@ find_exec ( const char *name, char *out, size_t len ) } /** - * The reaper is called once a second to finish of any pending spawns + * Reap one child */ -void -spawn_reaper(void) +int +spawn_reap(char *stxt, size_t stxtlen) { pid_t pid; - int status; - char txt[100]; + int status, res; spawn_t *s; - while(1) { - pid = waitpid(-1, &status, WNOHANG); - if(pid < 1) + pid = waitpid(-1, &status, WNOHANG); + if(pid < 1) + return -EAGAIN; + + pthread_mutex_lock(&spawn_mutex); + LIST_FOREACH(s, &spawns, link) + if(s->pid == pid) break; - pthread_mutex_lock(&spawn_mutex); - LIST_FOREACH(s, &spawns, link) - if(s->pid == pid) - break; - - if (WIFEXITED(status)) { - snprintf(txt, sizeof(txt), - "exited, status=%d", WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - snprintf(txt, sizeof(txt), - "killed by signal %d", WTERMSIG(status)); - } else if (WIFSTOPPED(status)) { - snprintf(txt, sizeof(txt), - "stopped by signal %d", WSTOPSIG(status)); - } else if (WIFCONTINUED(status)) { - snprintf(txt, sizeof(txt), - "continued"); - } else { - snprintf(txt, sizeof(txt), - "unknown status"); - } - - if(s != NULL) { - LIST_REMOVE(s, link); - free((void *)s->name); - free(s); - } - pthread_mutex_unlock(&spawn_mutex); + res = -EIO; + if (WIFEXITED(status)) { + res = WEXITSTATUS(status); + if (stxt) + snprintf(stxt, stxtlen, "exited, status=%d", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + snprintf(stxt, stxtlen, "killed by signal %d, " + "stopped by signal %d", + WTERMSIG(status), + WSTOPSIG(status)); + } else if (WIFCONTINUED(status)) { + snprintf(stxt, stxtlen, "continued"); + } else { + snprintf(stxt, stxtlen, "unknown status"); } + + if(s != NULL) { + LIST_REMOVE(s, link); + free((void *)s->name); + free(s); + } + pthread_mutex_unlock(&spawn_mutex); + return res; +} + +/** + * The reaper is called once a second to finish of any pending spawns + */ +void +spawn_reaper(void) +{ + while (spawn_reap(NULL, 0) != -EAGAIN) ; } diff --git a/src/spawn.h b/src/spawn.h index 5f89426b..64e734bf 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -25,6 +25,8 @@ int spawn_and_store_stdout(const char *prog, char *argv[], char **outp); int spawnv(const char *prog, char *argv[]); +int spawn_reap(char *stxt, size_t stxtlen); + void spawn_reaper(void); #endif /* SPAWN_H */