1 #include <lib/base/filepush.h>
2 #include <lib/base/eerror.h>
3 #include <lib/base/nconfig.h>
11 #include <sys/types.h>
17 #define MAJORMMCBLK 179
18 #define LIMIT_FILESIZE_NOHDD 2*1024*1024*1024LL // 2GBytes
20 //FILE *f = fopen("/log.ts", "wb");
21 static bool g_is_diskfull = false;
23 eFilePushThread::eFilePushThread(int io_prio_class, int io_prio_level, int blocksize)
24 :prio_class(io_prio_class), prio(io_prio_level), m_messagepump(eApp, 0)
28 m_send_pvr_commit = 0;
30 m_blocksize = blocksize;
33 CONNECT(m_messagepump.recv_msg, eFilePushThread::recvEvent);
34 m_hdd_connected = false;
37 static void signal_handler(int x)
41 void eFilePushThread::thread()
43 setIoPrio(prio_class, prio);
46 size_t bytes_read = 0;
48 off_t current_span_offset = 0;
49 size_t current_span_remaining = 0;
51 size_t written_since_last_sync = 0;
54 if(ePythonConfigQuery::getConfigValue("config.usage.timeshift_path", tspath) == -1) {
55 eDebug("could not query ts path from config");
61 struct dirent *tsdir_entry;
62 tsdir_info = opendir("/sys/block");
63 if (tsdir_info != NULL) {
64 m_hdd_connected = false;
65 while (tsdir_entry = readdir(tsdir_info)) {
66 if (strncmp(tsdir_entry->d_name, "sd", 2) == 0) {
67 eDebug("HDD found: %s", tsdir_entry->d_name);
68 m_hdd_connected = true;
74 struct stat tspath_st;
75 if (stat(tspath.c_str(), &tspath_st) == 0) {
76 if (major(tspath_st.st_dev) == MAJORSD_) {
77 eDebug("Timeshift location on HDD!");
78 m_hdd_connected = true;
79 } else if (major(tspath_st.st_dev) == MAJORMMCBLK) {
80 eDebug("Timeshift location on eMMC!");
81 m_hdd_connected = false;
86 eDebug("FILEPUSH THREAD START");
88 /* we set the signal to not restart syscalls, so we can detect our signal. */
90 act.sa_handler = signal_handler; // no, SIG_IGN doesn't do it. we want to receive the -EINTR
92 sigaction(SIGUSR1, &act, 0);
96 /* m_stop must be evaluated after each syscall. */
99 /* first try flushing the bufptr */
100 if (m_buf_start != m_buf_end)
102 /* filterRecordData wants to work on multiples of blocksize.
103 if it returns a negative result, it means that this many bytes should be skipped
104 *in front* of the buffer. Then, it will be called again. with the newer, shorter buffer.
105 if filterRecordData wants to skip more data then currently available, it must do that internally.
106 Skipped bytes will also not be output.
108 if it returns a positive result, that means that only these many bytes should be used
111 In either case, current_span_remaining is given as a reference and can be modified. (Of course it
112 doesn't make sense to decrement it to a non-zero value unless you return 0 because that would just
113 skip some data). This is probably a very special application for fast-forward, where the current
114 span is to be cancelled after a complete iframe has been output.
116 we always call filterRecordData with our full buffer (otherwise we couldn't easily strip from the end)
118 we filter data only once, of course, but it might not get immediately written.
119 that's what m_filter_end is for - it points to the start of the unfiltered data.
126 filter_res = filterRecordData(m_buffer + m_filter_end, m_buf_end - m_filter_end, current_span_remaining);
130 eDebug("[eFilePushThread] filterRecordData re-syncs and skips %d bytes", -filter_res);
131 m_buf_start = m_filter_end + -filter_res; /* this will also drop unwritten data */
132 ASSERT(m_buf_start <= m_buf_end); /* otherwise filterRecordData skipped more data than available. */
133 continue; /* try again */
136 /* adjust end of buffer to strip dropped tail bytes */
137 m_buf_end = m_filter_end + filter_res;
138 /* mark data as filtered. */
139 m_filter_end = m_buf_end;
142 ASSERT(m_filter_end == m_buf_end);
144 if (m_buf_start == m_buf_end)
147 /* now write out data. it will be 'aligned' (according to filterRecordData).
148 absolutely forbidden is to return EINTR and consume a non-aligned number of bytes.
150 int w = write(m_fd_dest, m_buffer + m_buf_start, m_buf_end - m_buf_start);
151 // fwrite(m_buffer + m_buf_start, 1, m_buf_end - m_buf_start, f);
152 // eDebug("wrote %d bytes", w);
155 if (w < 0 && (errno == EINTR || errno == EAGAIN || errno == EBUSY))
157 eDebug("eFilePushThread WRITE ERROR");
158 sendEvent(evtWriteError);
161 if (statfs(tspath.c_str(), &fs) < 0) {
162 eDebug("statfs failed!");
164 if ((off_t)fs.f_bavail < 1) {
165 eDebug("not enough diskspace for timeshift!");
166 g_is_diskfull = true;
169 // ... we would stop the thread
172 written_since_last_sync += w;
174 if (written_since_last_sync >= 512*1024)
176 int toflush = written_since_last_sync > 2*1024*1024 ?
177 2*1024*1024 : written_since_last_sync &~ 4095; // write max 2MB at once
178 dest_pos = lseek(m_fd_dest, 0, SEEK_CUR);
180 posix_fadvise(m_fd_dest, dest_pos, toflush, POSIX_FADV_DONTNEED);
181 written_since_last_sync -= toflush;
184 // printf("FILEPUSH: wrote %d bytes\n", w);
189 if (!m_hdd_connected) {
190 struct stat limit_filesize;
191 if (fstat(m_fd_dest, &limit_filesize) == 0) {
192 if (limit_filesize.st_size > LIMIT_FILESIZE_NOHDD) {
193 eDebug("eFilePushThread %lld > %lld LIMIT FILESIZE", limit_filesize.st_size, LIMIT_FILESIZE_NOHDD);
194 sendEvent(evtWriteError);
196 g_is_diskfull = true;
202 /* now fill our buffer. */
204 if (m_sg && !current_span_remaining)
206 m_sg->getNextSourceSpan(m_current_position, bytes_read, current_span_offset, current_span_remaining);
207 ASSERT(!(current_span_remaining % m_blocksize));
208 m_current_position = current_span_offset;
212 size_t maxread = sizeof(m_buffer);
214 /* if we have a source span, don't read past the end */
215 if (m_sg && maxread > current_span_remaining)
216 maxread = current_span_remaining;
218 /* align to blocksize */
219 maxread -= maxread % m_blocksize;
226 m_buf_end = m_source->read(m_current_position, m_buffer, maxread);
231 if (errno == EINTR || errno == EBUSY || errno == EAGAIN)
233 if (errno == EOVERFLOW)
235 eWarning("OVERFLOW while recording");
238 eDebug("eFilePushThread *read error* (%m) - not yet handled");
241 /* a read might be mis-aligned in case of a short read. */
242 int d = m_buf_end % m_blocksize;
248 /* on EOF, try COMMITting once. */
249 if (m_send_pvr_commit)
254 switch (poll(&pfd, 1, 250)) // wait for 250ms
257 eDebug("wait for driver eof timeout");
260 eDebug("wait for driver eof ok");
263 eDebug("wait for driver eof aborted by signal");
268 /* in stream_mode, we are sending EOF events
269 over and over until somebody responds.
271 in stream_mode, think of evtEOF as "buffer underrun occured". */
276 eDebug("reached EOF, but we are in stream mode. delaying 1 second.");
283 m_current_position += m_buf_end;
284 bytes_read += m_buf_end;
286 current_span_remaining -= m_buf_end;
288 // printf("FILEPUSH: read %d bytes\n", m_buf_end);
291 sendEvent(evtUser+3);
292 g_is_diskfull = false;
295 fdatasync(m_fd_dest);
297 eDebug("FILEPUSH THREAD STOP");
300 void eFilePushThread::start(int fd, int fd_dest)
302 eRawFile *f = new eRawFile();
303 ePtr<iTsSource> source = f;
305 start(source, fd_dest);
308 int eFilePushThread::start(const char *file, int fd_dest)
310 eRawFile *f = new eRawFile();
311 ePtr<iTsSource> source = f;
312 if (f->open(file) < 0)
314 start(source, fd_dest);
318 void eFilePushThread::start(ePtr<iTsSource> &source, int fd_dest)
322 m_current_position = 0;
326 void eFilePushThread::stop()
328 /* if we aren't running, don't bother stopping. */
334 eDebug("stopping thread."); /* just do it ONCE. it won't help to do this more than once. */
339 void eFilePushThread::pause()
344 void eFilePushThread::resume()
350 void eFilePushThread::flush()
352 m_buf_start = m_buf_end = m_filter_end = 0;
355 void eFilePushThread::enablePVRCommit(int s)
357 m_send_pvr_commit = s;
360 void eFilePushThread::setStreamMode(int s)
365 void eFilePushThread::setScatterGather(iFilePushScatterGather *sg)
370 void eFilePushThread::sendEvent(int evt)
372 m_messagepump.send(evt);
375 void eFilePushThread::recvEvent(const int &evt)
380 int eFilePushThread::filterRecordData(const unsigned char *data, int len, size_t ¤t_span_remaining)