1 /**
2  *  TV headend - Timeshift
3  *  Copyright (C) 2012 Adam Sutton
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 "tvheadend.h"
20 #include "streaming.h"
21 #include "timeshift.h"
22 #include "timeshift/private.h"
23 #include "config.h"
24 #include "settings.h"
25 #include "atomic.h"
26 #include "access.h"
27 #include "atomic.h"
28 
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <string.h>
34 #include <assert.h>
35 #include <stdio.h>
36 
37 static int timeshift_index = 0;
38 
39 struct timeshift_conf timeshift_conf;
40 
41 memoryinfo_t timeshift_memoryinfo = { .my_name = "Timeshift" };
42 memoryinfo_t timeshift_memoryinfo_ram = { .my_name = "Timeshift RAM buffer" };
43 
44 /*
45  * Packet log
46  */
47 void
timeshift_packet_log0(const char * source,timeshift_t * ts,streaming_message_t * sm)48 timeshift_packet_log0
49   ( const char *source, timeshift_t *ts, streaming_message_t *sm )
50 {
51   th_pkt_t *pkt = sm->sm_data;
52   tvhtrace(LS_TIMESHIFT,
53            "ts %d pkt %s - stream %d type %c pts %10"PRId64
54            " dts %10"PRId64" dur %10d len %6zu time %14"PRId64,
55            ts->id, source,
56            pkt->pkt_componentindex,
57            SCT_ISVIDEO(pkt->pkt_type) ? pkt_frametype_to_char(pkt->v.pkt_frametype) : '-',
58            ts_rescale(pkt->pkt_pts, 1000000),
59            ts_rescale(pkt->pkt_dts, 1000000),
60            pkt->pkt_duration,
61            pktbuf_len(pkt->pkt_payload),
62            sm->sm_time);
63 }
64 
65 /*
66  * Safe values for RAM configuration
67  */
timeshift_fixup(void)68 static void timeshift_fixup ( void )
69 {
70   if (timeshift_conf.ram_only)
71     timeshift_conf.max_size = timeshift_conf.ram_size;
72 }
73 
74 /*
75  * Intialise global file manager
76  */
timeshift_init(void)77 void timeshift_init ( void )
78 {
79   htsmsg_t *m;
80 
81   memoryinfo_register(&timeshift_memoryinfo);
82   memoryinfo_register(&timeshift_memoryinfo_ram);
83 
84   timeshift_filemgr_init();
85 
86   /* Defaults */
87   memset(&timeshift_conf, 0, sizeof(timeshift_conf));
88   timeshift_conf.idnode.in_class = &timeshift_conf_class;
89   timeshift_conf.max_period       = 60;                      // Hr (60mins)
90   timeshift_conf.max_size         = 10000 * (size_t)1048576; // 10G
91 
92   idclass_register(&timeshift_conf_class);
93 
94   /* Load settings */
95   if ((m = hts_settings_load("timeshift/config"))) {
96     idnode_load(&timeshift_conf.idnode, m);
97     htsmsg_destroy(m);
98     timeshift_fixup();
99   }
100 }
101 
102 /*
103  * Terminate global file manager
104  */
timeshift_term(void)105 void timeshift_term ( void )
106 {
107   timeshift_filemgr_term();
108   free(timeshift_conf.path);
109   timeshift_conf.path = NULL;
110 
111   memoryinfo_unregister(&timeshift_memoryinfo);
112   memoryinfo_unregister(&timeshift_memoryinfo_ram);
113 }
114 
115 /*
116  * Changed settings
117  */
118 static void
timeshift_conf_class_changed(idnode_t * self)119 timeshift_conf_class_changed ( idnode_t *self )
120 {
121   timeshift_fixup();
122 }
123 
124 /*
125  * Save settings
126  */
127 static htsmsg_t *
timeshift_conf_class_save(idnode_t * self,char * filename,size_t fsize)128 timeshift_conf_class_save ( idnode_t *self, char *filename, size_t fsize )
129 {
130   htsmsg_t *m = htsmsg_create_map();
131   idnode_save(&timeshift_conf.idnode, m);
132   snprintf(filename, fsize, "timeshift/config");
133   return m;
134 }
135 
136 /*
137  * Class
138  */
139 static const void *
timeshift_conf_class_max_size_get(void * o)140 timeshift_conf_class_max_size_get ( void *o )
141 {
142   static uint64_t r;
143   r = timeshift_conf.max_size / 1048576LL;
144   return &r;
145 }
146 
147 static int
timeshift_conf_class_max_size_set(void * o,const void * v)148 timeshift_conf_class_max_size_set ( void *o, const void *v )
149 {
150   uint64_t u64 = *(uint64_t *)v * 1048576LL;
151   if (u64 != timeshift_conf.max_size) {
152     timeshift_conf.max_size = u64;
153     return 1;
154   }
155   return 0;
156 }
157 
158 static const void *
timeshift_conf_class_ram_size_get(void * o)159 timeshift_conf_class_ram_size_get ( void *o )
160 {
161   static uint64_t r;
162   r = timeshift_conf.ram_size / 1048576LL;
163   return &r;
164 }
165 
166 static int
timeshift_conf_class_ram_size_set(void * o,const void * v)167 timeshift_conf_class_ram_size_set ( void *o, const void *v )
168 {
169   uint64_t u64 = *(uint64_t *)v * 1048576LL;
170   timeshift_conf.ram_segment_size = u64 / 10;
171   if (u64 != timeshift_conf.ram_size) {
172     timeshift_conf.ram_size = u64;
173     return 1;
174   }
175   return 0;
176 }
177 
178 CLASS_DOC(timeshift)
179 
180 const idclass_t timeshift_conf_class = {
181   .ic_snode      = &timeshift_conf.idnode,
182   .ic_class      = "timeshift",
183   .ic_caption    = N_("Timeshift"),
184   .ic_doc        = tvh_doc_timeshift_class,
185   .ic_event      = "timeshift",
186   .ic_perm_def   = ACCESS_ADMIN,
187   .ic_changed    = timeshift_conf_class_changed,
188   .ic_save       = timeshift_conf_class_save,
189   .ic_properties = (const property_t[]){
190     {
191       .type   = PT_BOOL,
192       .id     = "enabled",
193       .name   = N_("Enabled"),
194       .desc   = N_("Enable/Disable timeshift."),
195       .off    = offsetof(timeshift_conf_t, enabled),
196     },
197     {
198       .type   = PT_BOOL,
199       .id     = "ondemand",
200       .name   = N_("On-demand (no first rewind)"),
201       .desc   = N_("Only activate timeshift when the client makes the first "
202                    "rewind, fast-forward or pause request. Note, "
203                    "because there is no buffer on the first request "
204                    "rewinding is not possible at that point."),
205       .off    = offsetof(timeshift_conf_t, ondemand),
206       .opts   = PO_EXPERT,
207     },
208     {
209       .type   = PT_STR,
210       .id     = "path",
211       .name   = N_("Storage path"),
212       .desc   = N_("Path to where the timeshift data will be stored. "
213                    "If nothing is specified this will default to "
214                    "CONF_DIR/timeshift/buffer."),
215       .off    = offsetof(timeshift_conf_t, path),
216       .opts   = PO_ADVANCED,
217     },
218     {
219       .type   = PT_U32,
220       .id     = "max_period",
221       .name   = N_("Maximum period (mins)"),
222       .desc   = N_("The maximum time period that will be buffered for "
223                    "any given (client) subscription."),
224       .off    = offsetof(timeshift_conf_t, max_period),
225     },
226     {
227       .type   = PT_BOOL,
228       .id     = "unlimited_period",
229       .name   = N_("Unlimited time"),
230       .desc   = N_("Allow the timeshift buffer to grow unbounded until "
231                    "your storage media runs out of space. Warning, "
232                    "enabling this option may cause your system to slow "
233                    "down or crash completely!"),
234       .off    = offsetof(timeshift_conf_t, unlimited_period),
235       .opts   = PO_EXPERT,
236     },
237     {
238       .type   = PT_S64,
239       .id     = "max_size",
240       .name   = N_("Maximum size (MB)"),
241       .desc   = N_("The maximum combined size of all timeshift buffers. "
242                    "If you specify an unlimited period it's highly "
243                    "recommended you specify a value here."),
244       .set    = timeshift_conf_class_max_size_set,
245       .get    = timeshift_conf_class_max_size_get,
246       .opts   = PO_ADVANCED,
247     },
248     {
249       .type   = PT_S64,
250       .id     = "ram_size",
251       .name   = N_("Maximum RAM size (MB)"),
252       .desc   = N_("The maximum RAM (system memory) size for timeshift "
253                    "buffers. When free RAM buffers are available they "
254                    "are used for timeshift data in preference to using "
255                    "storage."),
256       .set    = timeshift_conf_class_ram_size_set,
257       .get    = timeshift_conf_class_ram_size_get,
258       .opts   = PO_ADVANCED,
259     },
260     {
261       .type   = PT_BOOL,
262       .id     = "unlimited_size",
263       .name   = N_("Unlimited size"),
264       .desc   = N_("Allow the combined size of all timeshift buffers to "
265                    "potentially grow unbounded until your storage media "
266                    "runs out of space."),
267       .off    = offsetof(timeshift_conf_t, unlimited_size),
268       .opts   = PO_EXPERT,
269     },
270     {
271       .type   = PT_BOOL,
272       .id     = "ram_only",
273       .name   = N_("RAM only"),
274       .desc   = N_("Keep timeshift buffers in RAM only. "
275                    "With this option enabled, the amount of rewind time "
276                    "is limited by how much RAM TVHeadend is allowed."),
277       .off    = offsetof(timeshift_conf_t, ram_only),
278       .opts   = PO_ADVANCED,
279     },
280     {
281       .type   = PT_BOOL,
282       .id     = "ram_fit",
283       .name   = N_("Fit to RAM (cut rewind)"),
284       .desc   = N_("With \"RAM only\" enabled, and when \"Maximum RAM "
285                    "size\" is reached, remove the oldest segment in the "
286                    "buffer instead of replacing it completely. Note, "
287                    "this may reduce the amount of rewind time."),
288       .off    = offsetof(timeshift_conf_t, ram_fit),
289       .opts   = PO_EXPERT,
290     },
291     {
292       .type   = PT_BOOL,
293       .id     = "teletext",
294       .name   = N_("Include teletext"),
295       .desc   = N_("Include teletext in the timeshift buffer. Enabling "
296                    "this may cause issues with some services where the "
297                    "teletext DTS is invalid."),
298       .off    = offsetof(timeshift_conf_t, teletext),
299       .opts   = PO_EXPERT,
300     },
301     {}
302   }
303 };
304 
305 /*
306  * Process a packet
307  */
308 
309 static int
timeshift_packet(timeshift_t * ts,streaming_message_t * sm)310 timeshift_packet( timeshift_t *ts, streaming_message_t *sm )
311 {
312   th_pkt_t *pkt = sm->sm_data;
313   int64_t time;
314 
315   if (pkt->pkt_pts != PTS_UNSET) {
316     /* avoid to update last_wr_time for TELETEXT packets */
317     if (pkt->pkt_type != SCT_TELETEXT) {
318       time = ts_rescale(pkt->pkt_pts, 1000000);
319       if (ts->last_wr_time < time)
320         ts->last_wr_time = time;
321     }
322   }
323   sm->sm_time = ts->last_wr_time;
324   timeshift_packet_log("wr ", ts, sm);
325   streaming_target_deliver2(&ts->wr_queue.sq_st, sm);
326   return 0;
327 }
328 
329 /*
330  * Receive data
331  */
timeshift_input(void * opaque,streaming_message_t * sm)332 static void timeshift_input
333   ( void *opaque, streaming_message_t *sm )
334 {
335   int type = sm->sm_type;
336   timeshift_t *ts = opaque;
337   th_pkt_t *pkt, *pkt2;
338 
339   if (ts->exit)
340     return;
341 
342   /* Control */
343   if (type == SMT_SKIP) {
344     timeshift_write_skip(ts->rd_pipe.wr, sm->sm_data);
345     streaming_msg_free(sm);
346   } else if (type == SMT_SPEED) {
347     timeshift_write_speed(ts->rd_pipe.wr, sm->sm_code);
348     streaming_msg_free(sm);
349   } else {
350 
351     /* Change PTS/DTS offsets */
352     if (ts->packet_mode && ts->start_pts && type == SMT_PACKET) {
353       pkt = sm->sm_data;
354       pkt2 = pkt_copy_shallow(pkt);
355       pkt_ref_dec(pkt);
356       sm->sm_data = pkt2;
357       if (pkt2->pkt_pts != PTS_UNSET) pkt2->pkt_pts += ts->start_pts;
358       if (pkt2->pkt_dts != PTS_UNSET) pkt2->pkt_dts += ts->start_pts;
359     }
360 
361     /* Check for exit */
362     else if (type == SMT_EXIT ||
363         (type == SMT_STOP && sm->sm_code != SM_CODE_SOURCE_RECONFIGURED))
364       ts->exit = 1;
365 
366     else if (type == SMT_MPEGTS)
367       ts->packet_mode = 0;
368 
369     /* Send to the writer thread */
370     if (ts->packet_mode) {
371       sm->sm_time = ts->last_wr_time;
372       if ((type == SMT_PACKET) && !timeshift_packet(ts, sm))
373         goto _exit;
374     } else {
375       if (ts->ref_time == 0) {
376         ts->ref_time = getfastmonoclock();
377         sm->sm_time = 0;
378       } else {
379         sm->sm_time = getfastmonoclock() - ts->ref_time;
380       }
381     }
382     streaming_target_deliver2(&ts->wr_queue.sq_st, sm);
383 
384     /* Exit/Stop */
385 _exit:
386     if (ts->exit)
387       timeshift_write_exit(ts->rd_pipe.wr);
388   }
389 }
390 
391 static htsmsg_t *
timeshift_input_info(void * opaque,htsmsg_t * list)392 timeshift_input_info(void *opaque, htsmsg_t *list)
393 {
394   htsmsg_add_str(list, NULL, "wtimeshift input");
395   return list;
396 }
397 
398 static streaming_ops_t timeshift_input_ops = {
399   .st_cb   = timeshift_input,
400   .st_info = timeshift_input_info
401 };
402 
403 
404 /**
405  *
406  */
407 void
timeshift_destroy(streaming_target_t * pad)408 timeshift_destroy(streaming_target_t *pad)
409 {
410   timeshift_t *ts = (timeshift_t*)pad;
411   streaming_message_t *sm;
412 
413   /* Must hold global lock */
414   lock_assert(&global_lock);
415 
416   /* Ensure the threads exits */
417   // Note: this is a workaround for the fact the Q might have been flushed
418   //       in reader thread (VERY unlikely)
419   pthread_mutex_lock(&ts->state_mutex);
420   sm = streaming_msg_create(SMT_EXIT);
421   streaming_target_deliver2(&ts->wr_queue.sq_st, sm);
422   if (!ts->exit)
423     timeshift_write_exit(ts->rd_pipe.wr);
424   pthread_mutex_unlock(&ts->state_mutex);
425 
426   /* Wait for all threads */
427   pthread_join(ts->rd_thread, NULL);
428   pthread_join(ts->wr_thread, NULL);
429 
430   /* Shut stuff down */
431   streaming_queue_deinit(&ts->wr_queue);
432 
433   close(ts->rd_pipe.rd);
434   close(ts->rd_pipe.wr);
435 
436   /* Flush files */
437   timeshift_filemgr_flush(ts, NULL);
438 
439   if (ts->smt_start)
440     streaming_start_unref(ts->smt_start);
441 
442   if (ts->path)
443     free(ts->path);
444 
445   free(ts);
446   memoryinfo_free(&timeshift_memoryinfo, sizeof(timeshift_t));
447 }
448 
449 /**
450  * Create timeshift buffer
451  *
452  * max_period of buffer in seconds (0 = unlimited)
453  * max_size   of buffer in bytes   (0 = unlimited)
454  */
timeshift_create(streaming_target_t * out,time_t max_time)455 streaming_target_t *timeshift_create
456   (streaming_target_t *out, time_t max_time)
457 {
458   timeshift_t *ts = calloc(1, sizeof(timeshift_t));
459 
460   memoryinfo_alloc(&timeshift_memoryinfo, sizeof(timeshift_t));
461 
462   /* Must hold global lock */
463   lock_assert(&global_lock);
464 
465   /* Setup structure */
466   TAILQ_INIT(&ts->files);
467   ts->output     = out;
468   ts->path       = NULL;
469   ts->max_time   = max_time;
470   ts->state      = TS_LIVE;
471   ts->exit       = 0;
472   ts->full       = 0;
473   ts->vididx     = -1;
474   ts->id         = timeshift_index;
475   ts->ondemand   = timeshift_conf.ondemand;
476   ts->dobuf      = ts->ondemand ? 0 : 1;
477   ts->packet_mode= 1;
478   ts->last_wr_time = 0;
479   ts->buf_time   = 0;
480   ts->start_pts  = 0;
481   ts->ref_time   = 0;
482   ts->seek.file  = NULL;
483   ts->seek.frame = NULL;
484   ts->ram_segments = 0;
485   ts->file_segments = 0;
486   pthread_mutex_init(&ts->state_mutex, NULL);
487 
488   /* Initialise output */
489   tvh_pipe(O_NONBLOCK, &ts->rd_pipe);
490 
491   /* Initialise input */
492   streaming_queue_init(&ts->wr_queue, 0, 0);
493   streaming_target_init(&ts->input, &timeshift_input_ops, ts, 0);
494   tvhthread_create(&ts->wr_thread, NULL, timeshift_writer, ts, "tshift-wr");
495   tvhthread_create(&ts->rd_thread, NULL, timeshift_reader, ts, "tshift-rd");
496 
497   /* Update index */
498   timeshift_index++;
499 
500   return &ts->input;
501 }
502