From 7fdf5f1f1212e5feb843e3e57336fd0288b4d613 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 2 Sep 2014 09:44:52 +0200 Subject: [PATCH] ACL/DVR: Rewrite channel tag and add DVR config selection - remove also obsolete only_tag - accept only uuid for DVR entry config name - try to lock DVR entry when used in dvr_thread --- docs/html/config_access.html | 25 ++--- src/access.c | 140 +++++++++++++++++++++++----- src/access.h | 32 +++++-- src/api/api_dvr.c | 36 +++++--- src/channels.c | 46 ++++------ src/channels.h | 4 + src/config.c | 104 +++++++++++++++++++++ src/dvr/dvr.h | 11 ++- src/dvr/dvr_autorec.c | 38 +++++--- src/dvr/dvr_db.c | 147 +++++++++++++++++++++++------- src/dvr/dvr_rec.c | 23 ++++- src/htsp_server.c | 9 +- src/idnode.c | 2 +- src/main.c | 2 + src/tvheadend.h | 1 + src/webui/static/app/acleditor.js | 8 ++ 16 files changed, 479 insertions(+), 149 deletions(-) diff --git a/docs/html/config_access.html b/docs/html/config_access.html index 0e6d73bc..48cc42d9 100644 --- a/docs/html/config_access.html +++ b/docs/html/config_access.html @@ -68,13 +68,11 @@ The columns have the following functions:
Enables access to all video recording functions. This also include administration of the auto recordings. -
All Configs (VR) +
DVR Config Profile
- Allow to use all DVR configuration profiles. If not set, a DVR - configuration profile matched to the authorized user by name is always used - (the configuration profile must have same name as the user). If the DVR - configuration profile does not exists, the default profile is used. - The user is also not allowed to select another profile. + If set, the user will only be able to use the DVR config profile + equal to this value. + Note that this field is unset when the DVR Config Profile is removed.
Web interface
@@ -84,13 +82,6 @@ The columns have the following functions:
Enables access to the Configuration tab. -
Username Channel Tag Match -
- If enabled, the user will only be able to access channels with a tag the - same name as the username. - - This provides a very rudimentary way of limiting access to certain channels. -
Min Channel Num
If nonzero, the user will only be able to access channels with @@ -103,11 +94,9 @@ The columns have the following functions:
Channel Tag
- If set, the user will only be able to access channels with - a channel tag equal to this value. Note that this field stores - the tag name (not identified). It means that this field would not be - updated when the tag name is changed. A manual re-set of this field - is required (for security reasons). + If set, the user will only be able to access channels containing + this channel tag. + Note that this field is unset when the channel tag is removed.
Comment
diff --git a/src/access.c b/src/access.c index 9b970820..fa8ec0a8 100644 --- a/src/access.c +++ b/src/access.c @@ -37,6 +37,7 @@ #include "access.h" #include "settings.h" #include "channels.h" +#include "dvr/dvr.h" #include "tcp.h" struct access_entry_queue access_entries; @@ -161,6 +162,7 @@ access_destroy(access_t *a) { free(a->aa_username); free(a->aa_representative); + htsmsg_destroy(a->aa_dvrcfgs); htsmsg_destroy(a->aa_chtags); free(a); } @@ -295,10 +297,16 @@ access_update(access_t *a, access_entry_t *ae) } } - if(ae->ae_chtag && ae->ae_chtag[0] != '\0') { + if(ae->ae_dvr_config && ae->ae_dvr_config->dvr_config_name[0] != '\0') { + if (a->aa_dvrcfgs == NULL) + a->aa_dvrcfgs = htsmsg_create_list(); + htsmsg_add_str(a->aa_dvrcfgs, NULL, idnode_uuid_as_str(&ae->ae_dvr_config->dvr_id)); + } + + if(ae->ae_chtag && ae->ae_chtag->ct_name[0] != '\0') { if (a->aa_chtags == NULL) a->aa_chtags = htsmsg_create_list(); - htsmsg_add_str(a->aa_chtags, NULL, ae->ae_chtag); + htsmsg_add_str(a->aa_chtags, NULL, idnode_uuid_as_str(&ae->ae_chtag->ct_id)); } a->aa_rights |= ae->ae_rights; @@ -557,12 +565,8 @@ access_entry_update_rights(access_entry_t *ae) r |= ACCESS_ADVANCED_STREAMING; if (ae->ae_dvr) r |= ACCESS_RECORDER; - if (ae->ae_dvrallcfg) - r |= ACCESS_RECORDER_ALL; if (ae->ae_webui) r |= ACCESS_WEB_INTERFACE; - if (ae->ae_tag_only) - r |= ACCESS_TAG_ONLY; if (ae->ae_admin) r |= ACCESS_ADMIN; ae->ae_rights = r; @@ -639,6 +643,11 @@ access_entry_destroy(access_entry_t *ae) TAILQ_REMOVE(&access_entries, ae, ae_link); idnode_unlink(&ae->ae_id); + if (ae->ae_dvr_config) + LIST_REMOVE(ae, ae_dvr_config_link); + if (ae->ae_chtag) + LIST_REMOVE(ae, ae_channel_tag_link); + while((ai = TAILQ_FIRST(&ae->ae_ipmasks)) != NULL) { TAILQ_REMOVE(&ae->ae_ipmasks, ai, ai_link); @@ -653,6 +662,38 @@ access_entry_destroy(access_entry_t *ae) free(ae); } +/* + * + */ +void +access_destroy_by_dvr_config(dvr_config_t *cfg, int delconf) +{ + access_entry_t *ae; + + while ((ae = LIST_FIRST(&cfg->dvr_accesses)) != NULL) { + LIST_REMOVE(ae, ae_dvr_config_link); + ae->ae_dvr_config = NULL; + if (delconf) + access_entry_save(ae); + } +} + +/* + * + */ +void +access_destroy_by_channel_tag(channel_tag_t *ct, int delconf) +{ + access_entry_t *ae; + + while ((ae = LIST_FIRST(&ct->ct_accesses)) != NULL) { + LIST_REMOVE(ae, ae_channel_tag_link); + ae->ae_chtag = NULL; + if (delconf) + access_entry_save(ae); + } +} + /** * */ @@ -806,14 +847,66 @@ access_entry_class_password2_set(void *o, const void *v) return 0; } -static htsmsg_t * -access_entry_chtag_list ( void *o ) +static int +access_entry_chtag_set(void *o, const void *v) { - channel_tag_t *ct; - htsmsg_t *m = htsmsg_create_list(); - TAILQ_FOREACH(ct, &channel_tags, ct_link) - htsmsg_add_str(m, NULL, ct->ct_name); - return m; + access_entry_t *ae = (access_entry_t *)o; + channel_tag_t *tag = v ? channel_tag_find_by_uuid(v) : NULL; + if (tag == NULL && ae->ae_chtag) { + LIST_REMOVE(ae, ae_channel_tag_link); + ae->ae_chtag = NULL; + return 1; + } else if (ae->ae_chtag != tag) { + if (ae->ae_chtag) + LIST_REMOVE(ae, ae_channel_tag_link); + ae->ae_chtag = tag; + LIST_INSERT_HEAD(&tag->ct_accesses, ae, ae_channel_tag_link); + return 1; + } + return 0; +} + +static const void * +access_entry_chtag_get(void *o) +{ + static const char *ret; + access_entry_t *ae = (access_entry_t *)o; + if (ae->ae_chtag) + ret = idnode_uuid_as_str(&ae->ae_chtag->ct_id); + else + ret = ""; + return &ret; +} + +static int +access_entry_dvr_config_set(void *o, const void *v) +{ + access_entry_t *ae = (access_entry_t *)o; + dvr_config_t *cfg = v ? dvr_config_find_by_uuid(v) : NULL; + if (cfg == NULL && ae->ae_dvr_config) { + LIST_REMOVE(ae, ae_dvr_config_link); + ae->ae_dvr_config = NULL; + return 1; + } else if (ae->ae_dvr_config != cfg) { + if (ae->ae_dvr_config) + LIST_REMOVE(ae, ae_dvr_config_link); + ae->ae_dvr_config = cfg; + LIST_INSERT_HEAD(&cfg->dvr_accesses, ae, ae_dvr_config_link); + return 1; + } + return 0; +} + +static const void * +access_entry_dvr_config_get(void *o) +{ + static const char *ret; + access_entry_t *ae = (access_entry_t *)o; + if (ae->ae_dvr_config) + ret = idnode_uuid_as_str(&ae->ae_dvr_config->dvr_id); + else + ret = ""; + return &ret; } const idclass_t access_entry_class = { @@ -886,10 +979,13 @@ const idclass_t access_entry_class = { .off = offsetof(access_entry_t, ae_dvr), }, { - .type = PT_BOOL, - .id = "dvrallcfg", - .name = "All Configs (VR)", - .off = offsetof(access_entry_t, ae_dvrallcfg), + .type = PT_STR, + .id = "dvr_config", + .name = "DVR Config Profile", + .set = access_entry_dvr_config_set, + .get = access_entry_dvr_config_get, + .list = dvr_entry_class_config_name_list, + .off = offsetof(access_entry_t, ae_dvr_config), }, { .type = PT_BOOL, @@ -903,12 +999,6 @@ const idclass_t access_entry_class = { .name = "Admin", .off = offsetof(access_entry_t, ae_admin), }, - { - .type = PT_BOOL, - .id = "tag_only", - .name = "Username Channel Tag Match", - .off = offsetof(access_entry_t, ae_tag_only), - }, { .type = PT_U32, .id = "channel_min", @@ -926,7 +1016,9 @@ const idclass_t access_entry_class = { .id = "channel_tag", .name = "Channel Tag", .off = offsetof(access_entry_t, ae_chtag), - .list = access_entry_chtag_list, + .set = access_entry_chtag_set, + .get = access_entry_chtag_get, + .list = channel_tag_class_get_list, }, { .type = PT_STR, diff --git a/src/access.h b/src/access.h index 9315c50e..b6adad0d 100644 --- a/src/access.h +++ b/src/access.h @@ -22,6 +22,9 @@ #include "idnode.h" #include "htsmsg.h" +struct dvr_config; +struct channel_tag; + typedef struct access_ipmask { TAILQ_ENTRY(access_ipmask) ai_link; @@ -48,18 +51,25 @@ typedef struct access_entry { char *ae_password; char *ae_password2; char *ae_comment; + int ae_index; int ae_enabled; + int ae_streaming; int ae_adv_streaming; + int ae_dvr; - int ae_dvrallcfg; + struct dvr_config *ae_dvr_config; + LIST_ENTRY(access_entry) ae_dvr_config_link; + int ae_webui; int ae_admin; - int ae_tag_only; + uint32_t ae_chmin; uint32_t ae_chmax; - char *ae_chtag; + + struct channel_tag *ae_chtag; + LIST_ENTRY(access_entry) ae_channel_tag_link; uint32_t ae_rights; @@ -85,6 +95,7 @@ typedef struct access { char *aa_username; char *aa_representative; uint32_t aa_rights; + htsmsg_t *aa_dvrcfgs; uint32_t aa_chmin; uint32_t aa_chmax; htsmsg_t *aa_chtags; @@ -96,14 +107,11 @@ typedef struct access { #define ACCESS_ADVANCED_STREAMING (1<<1) #define ACCESS_WEB_INTERFACE (1<<2) #define ACCESS_RECORDER (1<<3) -#define ACCESS_RECORDER_ALL (1<<4) -#define ACCESS_TAG_ONLY (1<<5) -#define ACCESS_ADMIN (1<<6) +#define ACCESS_ADMIN (1<<4) #define ACCESS_FULL \ (ACCESS_STREAMING | ACCESS_ADVANCED_STREAMING | \ - ACCESS_WEB_INTERFACE | ACCESS_RECORDER | \ - ACCESS_RECORDER_ALL | ACCESS_ADMIN) + ACCESS_WEB_INTERFACE | ACCESS_RECORDER | ACCESS_ADMIN) /** * Create a new ticket for the requested resource and generate a id for it @@ -165,6 +173,14 @@ access_entry_create(const char *uuid, htsmsg_t *conf); void access_entry_save(access_entry_t *ae); +/** + * + */ +void +access_destroy_by_dvr_config(struct dvr_config *cfg, int delconf); +void +access_destroy_by_channel_tag(struct channel_tag *ct, int delconf); + /** * */ diff --git a/src/api/api_dvr.c b/src/api/api_dvr.c index 69369fa2..92a5b5e2 100644 --- a/src/api/api_dvr.c +++ b/src/api/api_dvr.c @@ -25,21 +25,28 @@ static const char * api_dvr_config_name( access_t *perm, const char *config_uuid ) { - dvr_config_t *cfg; + dvr_config_t *cfg = NULL; + htsmsg_field_t *f; + const char *uuid; lock_assert(&global_lock); - if (access_verify2(perm, ACCESS_RECORDER_ALL)) + if (perm->aa_dvrcfgs == NULL) return config_uuid; /* no change */ - cfg = dvr_config_find_by_name(perm->aa_username); - if (cfg) - return cfg->dvr_config_name; + config_uuid = config_uuid ?: ""; + HTSMSG_FOREACH(f, perm->aa_dvrcfgs) { + uuid = htsmsg_field_get_str(f) ?: ""; + if (strcmp(uuid, config_uuid) == 0) + return config_uuid; + if (!cfg) + cfg = dvr_config_find_by_uuid(uuid); + } - if (perm->aa_username) - tvhlog(LOG_INFO, "dvr", "User '%s' has no dvr config with identical name, using default...", perm->aa_username); + if (!cfg && perm->aa_username) + tvhlog(LOG_INFO, "dvr", "User '%s' has no valid dvr config in ACL, using default...", perm->aa_username); - return NULL; /* default */ + return cfg ? idnode_uuid_as_str(&cfg->dvr_id) : NULL; } /* @@ -71,9 +78,6 @@ api_dvr_config_create return EINVAL; if (s[0] == '\0') return EINVAL; - if (access_verify2(perm, ACCESS_ADMIN) && - access_verify2(perm, ACCESS_RECORDER_ALL | ACCESS_RECORDER)) - return EACCES; pthread_mutex_lock(&global_lock); if ((cfg = dvr_config_create(NULL, NULL, conf))) @@ -153,16 +157,20 @@ api_dvr_entry_create { dvr_entry_t *de; htsmsg_t *conf; + const char *s1, *s2; if (!(conf = htsmsg_get_map(args, "conf"))) return EINVAL; - if (access_verify2(perm, ACCESS_RECORDER_ALL)) { + pthread_mutex_lock(&global_lock); + s1 = htsmsg_get_str(conf, "config_name"); + s2 = api_dvr_config_name(perm, s1); + if (strcmp(s1 ?: "", s2 ?: "")) { htsmsg_delete_field(conf, "config_name"); - htsmsg_add_str(conf, "config_name", perm->aa_username ?: ""); + if (s2) + htsmsg_add_str(conf, "config_name", s2); } - pthread_mutex_lock(&global_lock); if ((de = dvr_entry_create(NULL, conf))) dvr_entry_save(de); pthread_mutex_unlock(&global_lock); diff --git a/src/channels.c b/src/channels.c index 6e5926c2..3dc23f4b 100644 --- a/src/channels.c +++ b/src/channels.c @@ -163,19 +163,6 @@ channel_class_tags_set ( void *obj, const void *p ) return channel_set_tags_by_list(obj, (htsmsg_t*)p); } -static htsmsg_t * -channel_class_tags_enum ( void *obj ) -{ - htsmsg_t *e, *m = htsmsg_create_map(); - htsmsg_add_str(m, "type", "api"); - htsmsg_add_str(m, "uri", "channeltag/list"); - htsmsg_add_str(m, "event", "channeltag"); - e = htsmsg_create_map(); - htsmsg_add_bool(e, "enum", 1); - htsmsg_add_msg(m, "params", e); - return m; -} - static void channel_class_icon_notify ( void *obj ) { @@ -369,7 +356,7 @@ const idclass_t channel_class = { .name = "Tags", .get = channel_class_tags_get, .set = channel_class_tags_set, - .list = channel_class_tags_enum, + .list = channel_tag_class_get_list, .rend = channel_class_tags_rend }, {} @@ -430,7 +417,8 @@ channel_access(channel_t *ch, access_t *a, const char *username) htsmsg_field_t *f; HTSMSG_FOREACH(f, a->aa_chtags) { LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) { - if (!strcmp(htsmsg_field_get_str(f) ?: "", ctm->ctm_tag->ct_name)) + if (!strcmp(htsmsg_field_get_str(f) ?: "", + idnode_uuid_as_str(&ctm->ctm_tag->ct_id))) goto chtags_ok; } } @@ -438,17 +426,6 @@ channel_access(channel_t *ch, access_t *a, const char *username) } chtags_ok: - /* Channel tag <-> user name match */ - if (ch && (a->aa_rights & ACCESS_TAG_ONLY) != 0) { - channel_tag_mapping_t *ctm; - LIST_FOREACH(ctm, &ch->ch_ctms, ctm_channel_link) { - if (!strcmp(username ?: "", ctm->ctm_tag->ct_name)) - goto tagonly_ok; - } - return 0; - } -tagonly_ok: - return 1; } @@ -761,6 +738,9 @@ channel_tag_create(const char *uuid, htsmsg_t *conf) channel_tag_t *ct; ct = calloc(1, sizeof(channel_tag_t)); + LIST_INIT(&ct->ct_ctms); + LIST_INIT(&ct->ct_autorecs); + LIST_INIT(&ct->ct_accesses); if (idnode_insert(&ct->ct_id, uuid, &channel_tag_class, IDNODE_SHORT_UUID)) { if (uuid) @@ -807,6 +787,9 @@ channel_tag_destroy(channel_tag_t *ct, int delconf) TAILQ_REMOVE(&channel_tags, ct, ct_link); idnode_unlink(&ct->ct_id); + autorec_destroy_by_channel_tag(ct, delconf); + access_destroy_by_channel_tag(ct, delconf); + free(ct->ct_name); free(ct->ct_comment); free(ct->ct_icon); @@ -849,6 +832,17 @@ channel_tag_class_get_title (idnode_t *self) return ct->ct_name ?: ""; } +/* exported for others */ +htsmsg_t * +channel_tag_class_get_list(void *o) +{ + htsmsg_t *m = htsmsg_create_map(); + htsmsg_add_str(m, "type", "api"); + htsmsg_add_str(m, "uri", "channeltag/list"); + htsmsg_add_str(m, "event", "channeltag"); + return m; +} + const idclass_t channel_tag_class = { .ic_class = "channeltag", .ic_caption = "Channel Tag", diff --git a/src/channels.h b/src/channels.h index 227caffb..fe0fc9cc 100644 --- a/src/channels.h +++ b/src/channels.h @@ -95,6 +95,8 @@ typedef struct channel_tag { struct dvr_autorec_entry_list ct_autorecs; + struct access_entry_list ct_accesses; + int ct_htsp_id; } channel_tag_t; @@ -164,6 +166,8 @@ static inline channel_tag_t *channel_tag_find_by_uuid(const char *uuid) void channel_tag_save(channel_tag_t *ct); +htsmsg_t * channel_tag_class_get_list(void *o); + int channel_access(channel_t *ch, struct access *a, const char *username); int channel_tag_map(channel_t *ch, channel_tag_t *ct); diff --git a/src/config.c b/src/config.c index 07465925..3afdfbf6 100644 --- a/src/config.c +++ b/src/config.c @@ -853,6 +853,109 @@ config_migrate_v10 ( void ) config_migrate_move("channeltags", "channel/tag"); } +static const char * +config_find_uuid( htsmsg_t *map, const char *name, const char *value ) +{ + htsmsg_t *e; + htsmsg_field_t *f; + const char *s; + + HTSMSG_FOREACH(f, map) { + if (!(e = htsmsg_field_get_map(f))) continue; + if ((s = htsmsg_get_str(e, name)) != NULL) { + if (!strcmp(s, value)) + return f->hmf_name; + } + } + return NULL; +} + +static void +config_modify_acl_dvallcfg( htsmsg_t *c, htsmsg_t *dvr_config ) +{ + uint32_t a; + const char *username, *uuid; + + username = htsmsg_get_str(c, "username"); + if (!htsmsg_get_u32(c, "dvallcfg", &a)) + if (a == 0) { + uuid = username ? config_find_uuid(dvr_config, "name", username) : NULL; + if (uuid) + htsmsg_add_str(c, "dvr_config", uuid); + } + htsmsg_delete_field(c, "dvallcfg"); +} + +static void +config_modify_acl_tag_only( htsmsg_t *c, htsmsg_t *channel_tag ) +{ + uint32_t a; + const char *username, *tag, *uuid; + + username = htsmsg_get_str(c, "username"); + tag = htsmsg_get_str(c, "channel_tag"); + if (!tag || tag[0] == '\0') + tag = NULL; + if (tag == NULL && !htsmsg_get_u32(c, "tag_only", &a)) { + if (a) { + uuid = username ? config_find_uuid(channel_tag, "name", username) : NULL; + if (uuid) + htsmsg_add_str(c, "channel_tag", uuid); + } + } else if (tag) { + uuid = config_find_uuid(channel_tag, "name", tag); + if (uuid) { + htsmsg_delete_field(c, "channel_tag"); + htsmsg_add_str(c, "channel_tag", uuid); + } + } + htsmsg_delete_field(c, "tag_only"); +} + +static void +config_modify_dvr_config_name( htsmsg_t *c, htsmsg_t *dvr_config ) +{ + const char *config_name, *uuid; + + config_name = htsmsg_get_str(c, "config_name"); + uuid = config_name ? config_find_uuid(dvr_config, "name", config_name) : NULL; + htsmsg_delete_field(c, "config_name"); + htsmsg_add_str(c, "config_name", uuid ?: ""); +} + + +static void +config_migrate_v11 ( void ) +{ + htsmsg_t *dvr_config; + htsmsg_t *channel_tag; + htsmsg_t *c, *e; + htsmsg_field_t *f; + + dvr_config = hts_settings_load("dvr/config"); + channel_tag = hts_settings_load("channel/tag"); + + if ((c = hts_settings_load("accesscontrol")) != NULL) { + HTSMSG_FOREACH(f, c) { + if (!(e = htsmsg_field_get_map(f))) continue; + config_modify_acl_dvallcfg(e, dvr_config); + config_modify_acl_tag_only(e, channel_tag); + } + htsmsg_destroy(c); + } + + if ((c = hts_settings_load("dvr/log")) != NULL) { + HTSMSG_FOREACH(f, c) { + if (!(e = htsmsg_field_get_map(f))) continue; + config_modify_dvr_config_name(e, dvr_config); + } + htsmsg_destroy(c); + } + + htsmsg_destroy(channel_tag); + htsmsg_destroy(dvr_config); +} + /* * Migration table */ @@ -867,6 +970,7 @@ static const config_migrate_t config_migrate_table[] = { config_migrate_v8, config_migrate_v9, config_migrate_v10, + config_migrate_v11 }; /* diff --git a/src/dvr/dvr.h b/src/dvr/dvr.h index 015b5b8a..355db379 100644 --- a/src/dvr/dvr.h +++ b/src/dvr/dvr.h @@ -28,6 +28,7 @@ typedef struct dvr_config { idnode_t dvr_id; + LIST_ENTRY(dvr_config) config_link; int dvr_enabled; @@ -72,6 +73,10 @@ typedef struct dvr_config { /* Duplicate detect */ int dvr_dup_detect_episode; + struct dvr_entry_list dvr_entries; + + struct access_entry_list dvr_accesses; + } dvr_config_t; extern struct dvr_config_list dvrconfigs; @@ -150,7 +155,7 @@ typedef struct dvr_entry { */ dvr_config_t *de_config; - char *de_config_name; + LIST_ENTRY(dvr_entry) de_config_link; time_t de_start; time_t de_stop; @@ -185,6 +190,7 @@ typedef struct dvr_entry { * Recording state (onyl valid if de_sched_state == DVR_RECORDING) */ dvr_rs_state_t de_rec_state; + int de_locked; /** * Number of errors (only to be modified by the recording thread) @@ -346,6 +352,7 @@ dvr_entry_update( dvr_entry_t *de, time_t de_start_extra, time_t de_stop_extra ); void dvr_init(void); +void dvr_config_init(void); void dvr_done(void); @@ -438,6 +445,8 @@ void dvr_autorec_check_serieslink(epg_serieslink_t *s); void autorec_destroy_by_channel(channel_t *ch, int delconf); +void autorec_destroy_by_channel_tag(channel_tag_t *ct, int delconf); + /** * */ diff --git a/src/dvr/dvr_autorec.c b/src/dvr/dvr_autorec.c index 8b2d3429..c07ee9b9 100644 --- a/src/dvr/dvr_autorec.c +++ b/src/dvr/dvr_autorec.c @@ -392,18 +392,6 @@ dvr_autorec_entry_class_tag_get(void *o) return &ret; } -static htsmsg_t * -dvr_autorec_entry_class_tag_list(void *o) -{ - htsmsg_t *e, *m = htsmsg_create_map(); - htsmsg_add_str(m, "type", "api"); - htsmsg_add_str(m, "uri", "channeltag/list"); - htsmsg_add_str(m, "event", "channeltag"); - e = htsmsg_create_map(); - htsmsg_add_msg(m, "params", e); - return m; -} - static int dvr_autorec_entry_class_time_set(void *o, const void *v, int *tm) { @@ -726,7 +714,7 @@ const idclass_t dvr_autorec_entry_class = { .name = "Channel Tag", .set = dvr_autorec_entry_class_tag_set, .get = dvr_autorec_entry_class_tag_get, - .list = dvr_autorec_entry_class_tag_list, + .list = channel_tag_class_get_list, }, { .type = PT_STR, @@ -913,7 +901,7 @@ dvr_autorec_changed(dvr_autorec_entry_t *dae, int purge) CHANNEL_FOREACH(ch) { RB_FOREACH(e, &ch->ch_epg_schedule, sched_link) { if(autorec_cmp(dae, e)) - dvr_entry_create_by_autorec(e, dae); + dvr_entry_create_by_autorec(e, dae); } } } @@ -936,3 +924,25 @@ autorec_destroy_by_channel(channel_t *ch, int delconf) htsmsg_add_u32(m, "reload", 1); notify_by_msg("autorec", m); } + +/* + * + */ +void +autorec_destroy_by_channel_tag(channel_tag_t *ct, int delconf) +{ + dvr_autorec_entry_t *dae; + htsmsg_t *m; + + while((dae = LIST_FIRST(&ct->ct_autorecs)) != NULL) { + LIST_REMOVE(dae, dae_channel_tag_link); + dae->dae_channel_tag = NULL; + if (delconf) + dvr_autorec_save(dae); + } + + /* Notify web clients that we have messed with the tables */ + m = htsmsg_create_map(); + htsmsg_add_u32(m, "reload", 1); + notify_by_msg("autorec", m); +} diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index 8aa1f05e..981abec7 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -43,7 +43,7 @@ struct dvr_entry_list dvrentries; static gtimer_t dvr_dbus_timer; #endif -static void dvr_entry_remove(dvr_entry_t *de, int delconf); +static void dvr_entry_destroy(dvr_entry_t *de, int delconf); static void dvr_timer_expire(void *aux); static void dvr_timer_start_recording(void *aux); static void dvr_timer_stop_recording(void *aux); @@ -421,7 +421,7 @@ dvr_entry_create(const char *uuid, htsmsg_t *conf) if(de2 != de && de2->de_start == de->de_start && de2->de_sched_state != DVR_COMPLETED) { - dvr_entry_remove(de, 0); + dvr_entry_destroy(de, 0); return NULL; } } @@ -524,7 +524,11 @@ dvr_entry_create_htsp(const char *config_uuid, const char *creator, dvr_autorec_entry_t *dae, dvr_prio_t pri) { - return _dvr_entry_create(config_uuid, NULL, + dvr_config_t *cfg = dvr_config_find_by_uuid(config_uuid); + if (!cfg) + cfg = dvr_config_find_by_name(config_uuid); + return _dvr_entry_create(cfg ? idnode_uuid_as_str(&cfg->dvr_id) : NULL, + NULL, ch, start, stop, start_extra, stop_extra, title, description, lang, content_type, creator, dae, pri); @@ -625,8 +629,10 @@ dvr_entry_dec_ref(dvr_entry_t *de) if(de->de_autorec != NULL) LIST_REMOVE(de, de_autorec_link); + if(de->de_config != NULL) + LIST_REMOVE(de, de_config_link); + free(de->de_filename); - free(de->de_config_name); free(de->de_creator); if (de->de_title) lang_str_destroy(de->de_title); if (de->de_desc) lang_str_destroy(de->de_desc); @@ -639,7 +645,7 @@ dvr_entry_dec_ref(dvr_entry_t *de) * */ static void -dvr_entry_remove(dvr_entry_t *de, int delconf) +dvr_entry_destroy(dvr_entry_t *de, int delconf) { if (delconf) hts_settings_remove("dvr/log/%s", idnode_uuid_as_str(&de->de_id)); @@ -665,6 +671,27 @@ dvr_entry_remove(dvr_entry_t *de, int delconf) dvr_entry_dec_ref(de); } +/** + * + */ +static void +dvr_entry_destroy_by_config(dvr_config_t *cfg, int delconf) +{ + dvr_entry_t *de; + dvr_config_t *def = NULL; + + while ((de = LIST_FIRST(&cfg->dvr_entries)) != NULL) { + LIST_REMOVE(de, de_config_link); + if (!def) + def = dvr_config_find_by_name_default(""); + de->de_config = def; + if (def) + LIST_INSERT_HEAD(&def->dvr_entries, de, de_config_link); + if (delconf) + dvr_entry_save(de); + } +} + /** * */ @@ -707,7 +734,7 @@ static void dvr_timer_expire(void *aux) { dvr_entry_t *de = aux; - dvr_entry_remove(de, 1); + dvr_entry_destroy(de, 1); } @@ -718,6 +745,9 @@ static dvr_entry_t *_dvr_entry_update { int save = 0; + if (de->de_locked) + return de; + /* Start/Stop */ if (e) { start = e->start; @@ -836,7 +866,7 @@ dvr_event_replaced(epg_broadcast_t *e, epg_broadcast_t *new_e) /* If this was craeted by autorec - just remove it, it'll get recreated */ if (de->de_autorec) { - dvr_entry_remove(de, 1); + dvr_entry_destroy(de, 1); /* Find match */ } else { @@ -1006,7 +1036,7 @@ dvr_entry_cancel(dvr_entry_t *de) { switch(de->de_sched_state) { case DVR_SCHEDULED: - dvr_entry_remove(de, 1); + dvr_entry_destroy(de, 1); return NULL; case DVR_RECORDING: @@ -1015,11 +1045,11 @@ dvr_entry_cancel(dvr_entry_t *de) return de; case DVR_COMPLETED: - dvr_entry_remove(de, 1); + dvr_entry_destroy(de, 1); return NULL; case DVR_MISSED_TIME: - dvr_entry_remove(de, 1); + dvr_entry_destroy(de, 1); return NULL; default: @@ -1052,7 +1082,10 @@ dvr_entry_class_save(idnode_t *self) static void dvr_entry_class_delete(idnode_t *self) { - dvr_entry_remove((dvr_entry_t *)self, 1); + dvr_entry_t *de = (dvr_entry_t *)self; + if (de->de_locked) + return; + dvr_entry_destroy(de, 1); } static const char * @@ -1070,25 +1103,52 @@ static int dvr_entry_class_config_name_set(void *o, const void *v) { dvr_entry_t *de = (dvr_entry_t *)o; - const char *s = v ?: ""; - if (strcmp(s, de->de_config_name ?: "")) { - free(de->de_config_name); - de->de_config_name = strdup(v); - de->de_config = dvr_config_find_by_name_default(de->de_config_name); + dvr_config_t *cfg; + + if (de->de_locked) + return 0; + cfg = v ? dvr_config_find_by_uuid(v) : NULL; + if (!cfg) + cfg = dvr_config_find_by_name_default(""); + if (cfg == NULL) { + if (de->de_config) { + LIST_REMOVE(de, de_config_link); + de->de_config = NULL; + return 1; + } + } else if (de->de_config != cfg) { + if (de->de_config) + LIST_REMOVE(de, de_config_link); + de->de_config = cfg; + LIST_INSERT_HEAD(&cfg->dvr_entries, de, de_config_link); return 1; } - /* for sure */ - de->de_config = dvr_config_find_by_name_default(de->de_config_name); return 0; } +static const void * +dvr_entry_class_config_name_get(void *o) +{ + static const char *ret; + dvr_entry_t *de = (dvr_entry_t *)o; + if (de->de_config) + ret = idnode_uuid_as_str(&de->de_config->dvr_id); + else + ret = ""; + return &ret; +} + static int dvr_entry_class_channel_set(void *o, const void *v) { dvr_entry_t *de = (dvr_entry_t *)o; - channel_t *ch = v ? channel_find_by_uuid(v) : NULL; - if (!de->de_config_name) - dvr_entry_class_config_name_set(o, ""); + channel_t *ch; + + if (de->de_locked) + return 0; + ch = v ? channel_find_by_uuid(v) : NULL; + if (!de->de_config) + de->de_config = dvr_config_find_by_name_default(""); if (ch == NULL) { if (de->de_channel) { LIST_REMOVE(de, de_channel_link); @@ -1134,8 +1194,10 @@ dvr_entry_class_channel_name_set(void *o, const void *v) { dvr_entry_t *de = (dvr_entry_t *)o; channel_t *ch; - if (!de->de_config_name) - dvr_entry_class_config_name_set(o, ""); + if (de->de_locked) + return 0; + if (!de->de_config) + de->de_config = dvr_config_find_by_name_default(""); if (!strcmp(de->de_channel_name ?: "", v ?: "")) return 0; ch = v ? channel_find_by_name(v) : NULL; @@ -1629,8 +1691,8 @@ const idclass_t dvr_entry_class = { .id = "config_name", .name = "DVR Configuration", .set = dvr_entry_class_config_name_set, + .get = dvr_entry_class_config_name_get, .list = dvr_entry_class_config_name_list, - .off = offsetof(dvr_entry_t, de_config_name), }, { .type = PT_STR, @@ -1813,6 +1875,8 @@ dvr_config_create(const char *name, const char *uuid, htsmsg_t *conf) name = ""; cfg = calloc(1, sizeof(dvr_config_t)); + LIST_INIT(&cfg->dvr_entries); + LIST_INIT(&cfg->dvr_accesses); if (idnode_insert(&cfg->dvr_id, uuid, &dvr_config_class, 0)) { if (uuid) @@ -1872,6 +1936,10 @@ dvr_config_destroy(dvr_config_t *cfg, int delconf) } LIST_REMOVE(cfg, config_link); idnode_unlink(&cfg->dvr_id); + + dvr_entry_destroy_by_config(cfg, delconf); + access_destroy_by_dvr_config(cfg, delconf); + free(cfg->dvr_charset_id); free(cfg->dvr_charset); free(cfg->dvr_storage); @@ -1962,12 +2030,23 @@ static int dvr_config_class_perm(idnode_t *self, access_t *a, htsmsg_t *msg_to_write) { dvr_config_t *cfg = (dvr_config_t *)self; + htsmsg_field_t *f; + const char *uuid, *my_uuid; + if (access_verify2(a, ACCESS_RECORDER)) return -1; if (!access_verify2(a, ACCESS_ADMIN)) return 0; - if (access_verify2(a, ACCESS_RECORDER_ALL)) - return 0; + if (a->aa_dvrcfgs) { + my_uuid = idnode_uuid_as_str(&cfg->dvr_id); + HTSMSG_FOREACH(f, a->aa_dvrcfgs) { + uuid = htsmsg_field_get_str(f) ?: ""; + if (!strcmp(uuid, my_uuid)) + goto fine; + } + return -1; + } +fine: if (strcmp(cfg->dvr_config_name ?: "", a->aa_username ?: "")) return -1; return 0; @@ -2433,7 +2512,7 @@ dvr_entry_delete(dvr_entry_t *de) } } - dvr_entry_remove(de, 1); + dvr_entry_destroy(de, 1); } /** @@ -2444,7 +2523,7 @@ dvr_entry_cancel_delete(dvr_entry_t *de) { switch(de->de_sched_state) { case DVR_SCHEDULED: - dvr_entry_remove(de, 1); + dvr_entry_destroy(de, 1); break; case DVR_RECORDING: @@ -2455,7 +2534,7 @@ dvr_entry_cancel_delete(dvr_entry_t *de) break; case DVR_MISSED_TIME: - dvr_entry_remove(de, 1); + dvr_entry_destroy(de, 1); break; default: @@ -2467,7 +2546,7 @@ dvr_entry_cancel_delete(dvr_entry_t *de) * */ void -dvr_init(void) +dvr_config_init(void) { htsmsg_t *m, *l; htsmsg_field_t *f; @@ -2520,7 +2599,11 @@ dvr_init(void) cfg->dvr_config_name, cfg->dvr_storage); } } +} +void +dvr_init(void) +{ #if ENABLE_INOTIFY dvr_inotify_init(); #endif @@ -2542,10 +2625,10 @@ dvr_done(void) dvr_inotify_done(); #endif pthread_mutex_lock(&global_lock); + while ((de = LIST_FIRST(&dvrentries)) != NULL) + dvr_entry_destroy(de, 0); while ((cfg = LIST_FIRST(&dvrconfigs)) != NULL) dvr_config_destroy(cfg, 0); - while ((de = LIST_FIRST(&dvrentries)) != NULL) - dvr_entry_remove(de, 0); pthread_mutex_unlock(&global_lock); dvr_autorec_done(); } diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index 472177b5..45146f10 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -66,6 +66,9 @@ dvr_rec_subscribe(dvr_entry_t *de) int flags; assert(de->de_s == NULL); + assert(!de->de_locked); + + de->de_locked = 1; if(de->de_pri < 5) weight = prio2weight[de->de_pri]; @@ -117,6 +120,8 @@ dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode) globalheaders_destroy(de->de_gh); de->de_last_error = stopcode; + + de->de_locked = 0; } @@ -177,7 +182,10 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss) struct stat st; char *filename, *s; struct tm tm; - dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); + dvr_config_t *cfg = de->de_config; + + if (de == NULL) + return -1; strncpy(path, cfg->dvr_storage, sizeof(path)); path[sizeof(path)-1] = '\0'; @@ -304,9 +312,14 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss) const source_info_t *si = &ss->ss_si; const streaming_start_component_t *ssc; int i; - dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); + dvr_config_t *cfg = de->de_config; muxer_container_type_t mc; + if (!cfg) { + dvr_rec_fatal_error(de, "Unable to determine config profile"); + return -1; + } + mc = dvr_entry_get_mc(de); de->de_mux = muxer_create(mc, &cfg->dvr_muxcnf); @@ -427,7 +440,7 @@ static void * dvr_thread(void *aux) { dvr_entry_t *de = aux; - dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); + dvr_config_t *cfg = de->de_config; streaming_queue_t *sq = &de->de_sq; streaming_message_t *sm; th_pkt_t *pkt; @@ -666,7 +679,7 @@ dvr_thread_epilog(dvr_entry_t *de) muxer_destroy(de->de_mux); de->de_mux = NULL; - dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name); - if(cfg->dvr_postproc && de->de_filename) + dvr_config_t *cfg = de->de_config; + if(cfg && cfg->dvr_postproc && de->de_filename) dvr_spawn_postproc(de,cfg->dvr_postproc); } diff --git a/src/htsp_server.c b/src/htsp_server.c index eced3aba..ff46aac0 100644 --- a/src/htsp_server.c +++ b/src/htsp_server.c @@ -652,7 +652,6 @@ htsp_build_dvrentry(dvr_entry_t *de, const char *method) htsmsg_t *out = htsmsg_create_map(); const char *s = NULL, *error = NULL; const char *p; - dvr_config_t *cfg; htsmsg_add_u32(out, "id", idnode_get_short_uuid(&de->de_id)); if (de->de_channel) @@ -666,11 +665,9 @@ htsp_build_dvrentry(dvr_entry_t *de, const char *method) if( de->de_desc && (s = lang_str_get(de->de_desc, NULL))) htsmsg_add_str(out, "description", s); - if( de->de_filename && de->de_config_name ) { - if ((cfg = dvr_config_find_by_name_default(de->de_config_name))) { - if ((p = tvh_strbegins(de->de_filename, cfg->dvr_storage))) - htsmsg_add_str(out, "path", p); - } + if( de->de_filename && de->de_config ) { + if ((p = tvh_strbegins(de->de_filename, de->de_config->dvr_storage))) + htsmsg_add_str(out, "path", p); } switch(de->de_sched_state) { diff --git a/src/idnode.c b/src/idnode.c index 1d8a0294..0ab8e0b0 100644 --- a/src/idnode.c +++ b/src/idnode.c @@ -227,7 +227,7 @@ idnode_get_short_uuid (const idnode_t *in) const char * idnode_uuid_as_str(const idnode_t *in) { - static tvh_uuid_t ret[16]; + static tvh_uuid_t __thread ret[16]; static uint8_t p = 0; bin2hex(ret[p].hex, sizeof(ret[p].hex), in->in_uuid, sizeof(in->in_uuid)); const char *s = ret[p].hex; diff --git a/src/main.c b/src/main.c index 719ba4aa..0a2ae7a5 100644 --- a/src/main.c +++ b/src/main.c @@ -821,6 +821,8 @@ main(int argc, char **argv) subscription_init(); + dvr_config_init(); + access_init(opt_firstrun, opt_noacl); #if ENABLE_TIMESHIFT diff --git a/src/tvheadend.h b/src/tvheadend.h index f32731dc..cf928a4c 100644 --- a/src/tvheadend.h +++ b/src/tvheadend.h @@ -177,6 +177,7 @@ void gtimer_disarm(gtimer_t *gti); /* * List / Queue header declarations */ +LIST_HEAD(access_entry_list, access_entry); LIST_HEAD(th_subscription_list, th_subscription); LIST_HEAD(dvr_config_list, dvr_config); LIST_HEAD(dvr_entry_list, dvr_entry); diff --git a/src/webui/static/app/acleditor.js b/src/webui/static/app/acleditor.js index eb71cf1e..76013cd6 100644 --- a/src/webui/static/app/acleditor.js +++ b/src/webui/static/app/acleditor.js @@ -12,6 +12,10 @@ tvheadend.acleditor = function(panel) items: [] }); + var list = 'enabled,username,password,prefix,streaming,adv_streaming,' + + 'dvr,dvr_config,webui,admin,channel_min,channel_max,channel_tag,' + + 'comment'; + tvheadend.idnode_grid(panel, { url: 'api/access/entry', comet: 'acl_entries', @@ -20,10 +24,14 @@ tvheadend.acleditor = function(panel) tabIndex: 0, add: { url: 'api/access/entry', + params: { + list: list, + }, create: { } }, del: true, move: true, + list: list, help: function() { new tvheadend.help('Access Control Entries', 'config_access.html'); },