From: Jaroslav Kysela Date: Thu, 3 Dec 2015 07:57:07 +0000 (+0100) Subject: DVR: move disk space code to vfsmgr X-Git-Tag: v4.2.1~1397 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4d0738e2822124a130a9ce8f4ffcc35a503e2d6e;p=thirdparty%2Ftvheadend.git DVR: move disk space code to vfsmgr --- diff --git a/Makefile b/Makefile index 20721a849..802ab0712 100644 --- a/Makefile +++ b/Makefile @@ -275,8 +275,9 @@ SRCS-2 += \ src/dvr/dvr_rec.c \ src/dvr/dvr_autorec.c \ src/dvr/dvr_timerec.c \ + src/dvr/dvr_vfsmgr.c \ src/dvr/dvr_config.c \ - src/dvr/dvr_cutpoints.c \ + src/dvr/dvr_cutpoints.c SRCS-2 += \ src/webui/webui.c \ diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index 6e78eb2d0..c32df4364 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -33,8 +33,6 @@ #include "dvr.h" #include "spawn.h" #include "service.h" -#include "plumbing/tsfix.h" -#include "plumbing/globalheaders.h" #include "htsp_server.h" #include "atomic.h" #include "intlconv.h" @@ -42,14 +40,6 @@ #include "muxer.h" -#if ENABLE_ANDROID -#include -#define statvfs statfs -#define fstatvfs fstatfs -#else -#include -#endif - /** * */ @@ -1537,280 +1527,3 @@ dvr_thread_epilog(dvr_entry_t *de, const char *dvr_postproc) if(dvr_postproc && dvr_postproc[0]) dvr_spawn_postcmd(de, dvr_postproc, NULL); } - -/** - * - */ -static int dvr_disk_space_config_idx; -static int dvr_disk_space_config_size; -static time_t dvr_disk_space_config_lastdelete; -static int64_t dvr_bfree; -static int64_t dvr_btotal; -static pthread_mutex_t dvr_disk_space_mutex; -static gtimer_t dvr_disk_space_timer; -static tasklet_t dvr_disk_space_tasklet; - -/** - * Cleanup old recordings for this config until the dvr_cleanup_threshold is reached - * Only "Keep until space needed" recordings are deleted, starting with the oldest one - */ -static int64_t -dvr_disk_space_cleanup(dvr_config_t *cfg) -{ - dvr_entry_t *de, *oldest; - time_t stoptime; - int64_t requiredBytes, availBytes; - int64_t clearedBytes = 0, fileSize; - unsigned long int filesystemId; - struct statvfs diskdata; - struct tm tm; - int loops = 0; - char tbuf[64]; - const char *configName; - - if (!cfg || !cfg->dvr_enabled || statvfs(cfg->dvr_storage, &diskdata) == -1) - return -1; - - filesystemId = diskdata.f_fsid; - availBytes = diskdata.f_bsize * (int64_t)diskdata.f_bavail; - requiredBytes = (int64_t)cfg->dvr_cleanup_threshold*(int64_t)1024*(int64_t)1024; - configName = cfg != dvr_config_find_by_name(NULL) ? cfg->dvr_config_name : "Default profile"; - - /* When deleting a file from the disk, the system needs some time to actually do this */ - /* If calling this function to fast after the previous call, statvfs might be wrong/not updated yet */ - /* So we are risking to delete more files than needed, so allow 10s for the system to handle previous deletes */ - if (dvr_disk_space_config_lastdelete + 10 > dispatch_clock) { - tvhlog(LOG_WARNING, "dvr","disk space cleanup for config \"%s\" is not allowed now", configName); - return -1; - } - - if (diskdata.f_bsize * (int64_t)diskdata.f_blocks < requiredBytes) { - tvhlog(LOG_WARNING, "dvr","disk space cleanup for config \"%s\", required free space \"%ld MB\" is smaller than the total disk size!", - configName, requiredBytes/(int64_t)1024/(int64_t)1024); - return -1; - } - - tvhlog(LOG_INFO, "dvr","disk space cleanup for config \"%s\", required free space \"%ld MB\", current free space \"%ld MB\"", - configName, requiredBytes/(int64_t)1024/(int64_t)1024, availBytes/(int64_t)1024/(int64_t)1024); - - while (availBytes < requiredBytes) { - oldest = NULL; - stoptime = dispatch_clock; - - LIST_FOREACH(de, &dvrentries, de_global_link) { - if (de->de_sched_state != DVR_COMPLETED && - de->de_sched_state != DVR_MISSED_TIME) - continue; - - if (dvr_entry_get_stop_time(de) > stoptime) - continue; - - if (dvr_entry_get_removal_days(de) != DVR_RET_SPACENEED) // only remove the allowed ones - continue; - - if (dvr_get_filename(de) == NULL || dvr_get_filesize(de) <= 0) - continue; - - if(statvfs(dvr_get_filename(de), &diskdata) == -1) - continue; - - /* Checking for the same config is useless as it's storage path might be changed meanwhile */ - /* Check for the same file system instead */ - if (filesystemId == 0 || diskdata.f_fsid != filesystemId) - continue; - - oldest = de; // the oldest one until now - stoptime = dvr_entry_get_stop_time(de); - } - - if (oldest) { - fileSize = dvr_get_filesize(oldest); - availBytes += fileSize; - clearedBytes += fileSize; - - localtime_r(&stoptime, &tm); - if (strftime(tbuf, sizeof(tbuf), "%F %T", &tm) <= 0) - *tbuf = 0; - tvhlog(LOG_INFO, "dvr","Delete \"until space needed\" recording \"%s\" with stop time \"%s\" and file size \"%ld MB\"", - lang_str_get(oldest->de_title, NULL), tbuf, fileSize/(int64_t)1024/(int64_t)1024); - - dvr_disk_space_config_lastdelete = dispatch_clock; - dvr_entry_delete(oldest, 1); // delete actual file - if (dvr_entry_get_retention_days(oldest) == DVR_RET_ONREMOVE) - dvr_entry_destroy(oldest, 1); // also delete database entry - } - else { - tvhlog(LOG_WARNING, "dvr","%s \"until space needed\" recordings found for config \"%s\", you are running out of disk space very soon!", - loops > 0 ? "Not enough" : "No", configName); - goto finish; - } - - loops++; - if (loops >= 10) { - tvhlog(LOG_WARNING, "dvr","Not able to clear the required disk space after deleting %i \"until space needed\" recordings...", loops); - goto finish; - } - } - -finish: - tvhlog(LOG_INFO, "dvr","disk space cleanup for config \"%s\", cleared \"%ld MB\" of disk space, new free disk space \"%ld MB\"", - configName, clearedBytes/(int64_t)1024/(int64_t)1024, availBytes/(int64_t)1024/(int64_t)1024); - - return clearedBytes; -} - -/** - * Check for each dvr config if the free disk size is below the dvr_cleanup_threshold - * If so and we are using the dvr config ATM (active recording), we start the cleanup procedure - */ -static void -dvr_disk_space_check() -{ - dvr_config_t *cfg; - dvr_entry_t *de; - struct statvfs diskdata; - int64_t requiredBytes, availBytes; - int idx = 0, cleanupDone = 0; - - dvr_disk_space_config_idx++; - if (dvr_disk_space_config_idx > dvr_disk_space_config_size) - dvr_disk_space_config_idx = 1; - - LIST_FOREACH(cfg, &dvrconfigs, config_link) - { - idx++; - - if (cfg->dvr_enabled && - !cleanupDone && - idx >= dvr_disk_space_config_idx && - statvfs(cfg->dvr_storage, &diskdata) != -1) - { - availBytes = diskdata.f_bsize * (int64_t)diskdata.f_bavail; - requiredBytes = (int64_t)cfg->dvr_cleanup_threshold*(int64_t)1024*(int64_t)1024; - - if (availBytes < requiredBytes) { - LIST_FOREACH(de, &dvrentries, de_global_link) { - - /* only start cleanup if we are actually writing files right now */ - if (de->de_sched_state != DVR_RECORDING || !de->de_config || de->de_config != cfg) - continue; - - tvhlog(LOG_WARNING, "dvr","running out of free disk space for dvr config \"%s\", required free space \"%ld MB\", current free space \"%ld MB\"", - cfg != dvr_config_find_by_name(NULL) ? cfg->dvr_config_name : "Default profile", - requiredBytes/(int64_t)1024/(int64_t)1024, availBytes/(int64_t)1024/(int64_t)1024); - - /* only cleanup one directory at the time as the system needs time to delete the actual files */ - dvr_disk_space_cleanup(de->de_config); - cleanupDone = 1; - dvr_disk_space_config_idx = idx; - break; - } - } - else { - tvhlog(LOG_DEBUG, "dvr","checking free disk space for config \"%s\" : OK", - cfg != dvr_config_find_by_name(NULL) ? cfg->dvr_config_name : "Default profile"); - } - } - } - - if (!cleanupDone) - dvr_disk_space_config_idx = 0; - - dvr_disk_space_config_size = idx; -} -/** - * - */ -static void -dvr_get_disk_space_update(const char *path) -{ - struct statvfs diskdata; - - if(statvfs(path, &diskdata) == -1) - return; - - dvr_bfree = diskdata.f_bsize * (int64_t)diskdata.f_bavail; - dvr_btotal = diskdata.f_bsize * (int64_t)diskdata.f_blocks; -} - -/** - * - */ -static void -dvr_get_disk_space_tcb(void *opaque, int dearmed) -{ - if (!dearmed) { - htsmsg_t *m = htsmsg_create_map(); - pthread_mutex_lock(&dvr_disk_space_mutex); - - /* update disk space from default dvr config */ - dvr_get_disk_space_update((char *)opaque); - htsmsg_add_s64(m, "freediskspace", dvr_bfree); - htsmsg_add_s64(m, "totaldiskspace", dvr_btotal); - - /* check free disk space for each dvr config and start cleanup if needed */ - dvr_disk_space_check(); - pthread_mutex_unlock(&dvr_disk_space_mutex); - - notify_by_msg("diskspaceUpdate", m, 0); - } - - free(opaque); -} - -static void -dvr_get_disk_space_cb(void *aux) -{ - dvr_config_t *cfg; - char *path; - - lock_assert(&global_lock); - - cfg = dvr_config_find_by_name_default(NULL); - if (cfg->dvr_storage && cfg->dvr_storage[0]) { - path = strdup(cfg->dvr_storage); - tasklet_arm(&dvr_disk_space_tasklet, dvr_get_disk_space_tcb, path); - } - gtimer_arm(&dvr_disk_space_timer, dvr_get_disk_space_cb, NULL, 15); -} - -/** - * - */ -void -dvr_disk_space_init(void) -{ - dvr_config_t *cfg = dvr_config_find_by_name_default(NULL); - pthread_mutex_init(&dvr_disk_space_mutex, NULL); - dvr_get_disk_space_update(cfg->dvr_storage); - gtimer_arm(&dvr_disk_space_timer, dvr_get_disk_space_cb, NULL, 5); -} - -/** - * - */ -void -dvr_disk_space_done(void) -{ - tasklet_disarm(&dvr_disk_space_tasklet); - gtimer_disarm(&dvr_disk_space_timer); -} - -/** - * - */ -int -dvr_get_disk_space(int64_t *bfree, int64_t *btotal) -{ - int res = 0; - - pthread_mutex_lock(&dvr_disk_space_mutex); - if (dvr_bfree || dvr_btotal) { - *bfree = dvr_bfree; - *btotal = dvr_btotal; - } else { - res = -EINVAL; - } - pthread_mutex_unlock(&dvr_disk_space_mutex); - return res; -} diff --git a/src/dvr/dvr_vfsmgr.c b/src/dvr/dvr_vfsmgr.c new file mode 100644 index 000000000..7d5a1c464 --- /dev/null +++ b/src/dvr/dvr_vfsmgr.c @@ -0,0 +1,313 @@ +/* + * Digital Video Recorder - file system management + * Copyright (C) 2015 Jaroslav Kysela + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include /* basename */ + +#include "tvheadend.h" +#include "dvr.h" +#include "atomic.h" +#include "notify.h" + +#if ENABLE_ANDROID +#include +#define statvfs statfs +#define fstatvfs fstatfs +#else +#include +#endif + +static int dvr_disk_space_config_idx; +static int dvr_disk_space_config_size; +static time_t dvr_disk_space_config_lastdelete; +static int64_t dvr_bfree; +static int64_t dvr_btotal; +static pthread_mutex_t dvr_disk_space_mutex; +static gtimer_t dvr_disk_space_timer; +static tasklet_t dvr_disk_space_tasklet; + +/** + * Cleanup old recordings for this config until the dvr_cleanup_threshold is reached + * Only "Keep until space needed" recordings are deleted, starting with the oldest one + */ +static int64_t +dvr_disk_space_cleanup(dvr_config_t *cfg) +{ + dvr_entry_t *de, *oldest; + time_t stoptime; + int64_t requiredBytes, availBytes; + int64_t clearedBytes = 0, fileSize; + unsigned long int filesystemId; + struct statvfs diskdata; + struct tm tm; + int loops = 0; + char tbuf[64]; + const char *configName; + + if (!cfg || !cfg->dvr_enabled || statvfs(cfg->dvr_storage, &diskdata) == -1) + return -1; + + filesystemId = diskdata.f_fsid; + availBytes = diskdata.f_bsize * (int64_t)diskdata.f_bavail; + requiredBytes = (int64_t)cfg->dvr_cleanup_threshold*(int64_t)1024*(int64_t)1024; + configName = cfg != dvr_config_find_by_name(NULL) ? cfg->dvr_config_name : "Default profile"; + + /* When deleting a file from the disk, the system needs some time to actually do this */ + /* If calling this function to fast after the previous call, statvfs might be wrong/not updated yet */ + /* So we are risking to delete more files than needed, so allow 10s for the system to handle previous deletes */ + if (dvr_disk_space_config_lastdelete + 10 > dispatch_clock) { + tvhlog(LOG_WARNING, "dvr","disk space cleanup for config \"%s\" is not allowed now", configName); + return -1; + } + + if (diskdata.f_bsize * (int64_t)diskdata.f_blocks < requiredBytes) { + tvhlog(LOG_WARNING, "dvr","disk space cleanup for config \"%s\", required free space \"%ld MB\" is smaller than the total disk size!", + configName, requiredBytes/(int64_t)1024/(int64_t)1024); + return -1; + } + + tvhlog(LOG_INFO, "dvr","disk space cleanup for config \"%s\", required free space \"%ld MB\", current free space \"%ld MB\"", + configName, requiredBytes/(int64_t)1024/(int64_t)1024, availBytes/(int64_t)1024/(int64_t)1024); + + while (availBytes < requiredBytes) { + oldest = NULL; + stoptime = dispatch_clock; + + LIST_FOREACH(de, &dvrentries, de_global_link) { + if (de->de_sched_state != DVR_COMPLETED && + de->de_sched_state != DVR_MISSED_TIME) + continue; + + if (dvr_entry_get_stop_time(de) > stoptime) + continue; + + if (dvr_entry_get_removal_days(de) != DVR_RET_SPACENEED) // only remove the allowed ones + continue; + + if (dvr_get_filename(de) == NULL || dvr_get_filesize(de) <= 0) + continue; + + if(statvfs(dvr_get_filename(de), &diskdata) == -1) + continue; + + /* Checking for the same config is useless as it's storage path might be changed meanwhile */ + /* Check for the same file system instead */ + if (filesystemId == 0 || diskdata.f_fsid != filesystemId) + continue; + + oldest = de; // the oldest one until now + stoptime = dvr_entry_get_stop_time(de); + } + + if (oldest) { + fileSize = dvr_get_filesize(oldest); + availBytes += fileSize; + clearedBytes += fileSize; + + localtime_r(&stoptime, &tm); + if (strftime(tbuf, sizeof(tbuf), "%F %T", &tm) <= 0) + *tbuf = 0; + tvhlog(LOG_INFO, "dvr","Delete \"until space needed\" recording \"%s\" with stop time \"%s\" and file size \"%ld MB\"", + lang_str_get(oldest->de_title, NULL), tbuf, fileSize/(int64_t)1024/(int64_t)1024); + + dvr_disk_space_config_lastdelete = dispatch_clock; + dvr_entry_delete(oldest, 1); // delete actual file + if (dvr_entry_get_retention_days(oldest) == DVR_RET_ONREMOVE) + dvr_entry_destroy(oldest, 1); // also delete database entry + } + else { + tvhlog(LOG_WARNING, "dvr","%s \"until space needed\" recordings found for config \"%s\", you are running out of disk space very soon!", + loops > 0 ? "Not enough" : "No", configName); + goto finish; + } + + loops++; + if (loops >= 10) { + tvhlog(LOG_WARNING, "dvr","Not able to clear the required disk space after deleting %i \"until space needed\" recordings...", loops); + goto finish; + } + } + +finish: + tvhlog(LOG_INFO, "dvr","disk space cleanup for config \"%s\", cleared \"%ld MB\" of disk space, new free disk space \"%ld MB\"", + configName, clearedBytes/(int64_t)1024/(int64_t)1024, availBytes/(int64_t)1024/(int64_t)1024); + + return clearedBytes; +} + +/** + * Check for each dvr config if the free disk size is below the dvr_cleanup_threshold + * If so and we are using the dvr config ATM (active recording), we start the cleanup procedure + */ +static void +dvr_disk_space_check() +{ + dvr_config_t *cfg; + dvr_entry_t *de; + struct statvfs diskdata; + int64_t requiredBytes, availBytes; + int idx = 0, cleanupDone = 0; + + dvr_disk_space_config_idx++; + if (dvr_disk_space_config_idx > dvr_disk_space_config_size) + dvr_disk_space_config_idx = 1; + + LIST_FOREACH(cfg, &dvrconfigs, config_link) + { + idx++; + + if (cfg->dvr_enabled && + !cleanupDone && + idx >= dvr_disk_space_config_idx && + statvfs(cfg->dvr_storage, &diskdata) != -1) + { + availBytes = diskdata.f_bsize * (int64_t)diskdata.f_bavail; + requiredBytes = (int64_t)cfg->dvr_cleanup_threshold*(int64_t)1024*(int64_t)1024; + + if (availBytes < requiredBytes) { + LIST_FOREACH(de, &dvrentries, de_global_link) { + + /* only start cleanup if we are actually writing files right now */ + if (de->de_sched_state != DVR_RECORDING || !de->de_config || de->de_config != cfg) + continue; + + tvhlog(LOG_WARNING, "dvr","running out of free disk space for dvr config \"%s\", required free space \"%ld MB\", current free space \"%ld MB\"", + cfg != dvr_config_find_by_name(NULL) ? cfg->dvr_config_name : "Default profile", + requiredBytes/(int64_t)1024/(int64_t)1024, availBytes/(int64_t)1024/(int64_t)1024); + + /* only cleanup one directory at the time as the system needs time to delete the actual files */ + dvr_disk_space_cleanup(de->de_config); + cleanupDone = 1; + dvr_disk_space_config_idx = idx; + break; + } + } + else { + tvhlog(LOG_DEBUG, "dvr","checking free disk space for config \"%s\" : OK", + cfg != dvr_config_find_by_name(NULL) ? cfg->dvr_config_name : "Default profile"); + } + } + } + + if (!cleanupDone) + dvr_disk_space_config_idx = 0; + + dvr_disk_space_config_size = idx; +} + +/** + * + */ +static void +dvr_get_disk_space_update(const char *path) +{ + struct statvfs diskdata; + + if(statvfs(path, &diskdata) == -1) + return; + + dvr_bfree = diskdata.f_bsize * (int64_t)diskdata.f_bavail; + dvr_btotal = diskdata.f_bsize * (int64_t)diskdata.f_blocks; +} + +/** + * + */ +static void +dvr_get_disk_space_tcb(void *opaque, int dearmed) +{ + if (!dearmed) { + htsmsg_t *m = htsmsg_create_map(); + pthread_mutex_lock(&dvr_disk_space_mutex); + + /* update disk space from default dvr config */ + dvr_get_disk_space_update((char *)opaque); + htsmsg_add_s64(m, "freediskspace", dvr_bfree); + htsmsg_add_s64(m, "totaldiskspace", dvr_btotal); + + /* check free disk space for each dvr config and start cleanup if needed */ + dvr_disk_space_check(); + pthread_mutex_unlock(&dvr_disk_space_mutex); + + notify_by_msg("diskspaceUpdate", m, 0); + } + + free(opaque); +} + +static void +dvr_get_disk_space_cb(void *aux) +{ + dvr_config_t *cfg; + char *path; + + lock_assert(&global_lock); + + cfg = dvr_config_find_by_name_default(NULL); + if (cfg->dvr_storage && cfg->dvr_storage[0]) { + path = strdup(cfg->dvr_storage); + tasklet_arm(&dvr_disk_space_tasklet, dvr_get_disk_space_tcb, path); + } + gtimer_arm(&dvr_disk_space_timer, dvr_get_disk_space_cb, NULL, 15); +} + +/** + * + */ +void +dvr_disk_space_init(void) +{ + dvr_config_t *cfg = dvr_config_find_by_name_default(NULL); + pthread_mutex_init(&dvr_disk_space_mutex, NULL); + dvr_get_disk_space_update(cfg->dvr_storage); + gtimer_arm(&dvr_disk_space_timer, dvr_get_disk_space_cb, NULL, 5); +} + +/** + * + */ +void +dvr_disk_space_done(void) +{ + tasklet_disarm(&dvr_disk_space_tasklet); + gtimer_disarm(&dvr_disk_space_timer); +} + +/** + * + */ +int +dvr_get_disk_space(int64_t *bfree, int64_t *btotal) +{ + int res = 0; + + pthread_mutex_lock(&dvr_disk_space_mutex); + if (dvr_bfree || dvr_btotal) { + *bfree = dvr_bfree; + *btotal = dvr_btotal; + } else { + res = -EINVAL; + } + pthread_mutex_unlock(&dvr_disk_space_mutex); + return res; +}