1 // stream.cpp: Network streaming server for Cygnal, for Gnash.
2 //
3 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 // Free Software Foundation, Inc
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 //
20
21 #ifdef HAVE_CONFIG_H
22 #include "gnashconfig.h"
23 #endif
24
25 #include <sys/types.h>
26 #include <cstdint>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <iostream>
30 #include <string>
31 #include <cerrno>
32 #include <algorithm>
33 #if !defined(_WIN32) && !defined(__amigaos4__)
34 #include <sys/mman.h>
35 #elif defined(__amigaos4__)
36 #include <proto/exec.h>
37 #include <cstdlib> //for malloc/free
38 #else
39 #include <windows.h>
40 #endif
41
42 #include "GnashSystemIOHeaders.h"
43 #include "network.h"
44 #include "buffer.h"
45 #include "amf.h"
46 #include "log.h"
47 #include "cque.h"
48 #include "diskstream.h"
49 #include "cache.h"
50 #include "getclocktime.hpp"
51
52 // This is Linux specific, but offers better I/O for sending
53 // files out a network connection.
54 #ifdef HAVE_SENDFILE
55 # include <sys/sendfile.h>
56 #endif
57
58 #include <mutex>
59 static std::mutex io_mutex;
60 static std::mutex mem_mutex;
61
62 using std::string;
63
64 /// \namespace gnash
65 /// This is the main namespace for Gnash and it's libraries.
66 namespace gnash {
67
68 static Cache& cache = Cache::getDefaultInstance();
69
70 /// \def _SC_PAGESIZE
71 /// This isn't set on all systems, but is used to get the page
72 /// size used for memory allocations.
73 #ifndef _SC_PAGESIZE
74 #define _SC_PAGESIZE 8
75 #endif
76
77 /// \var MAX_PAGES
78 /// This is the maximum number of pages that we load into memory from a file.
79 const size_t MAX_PAGES = 2560;
80
81 #ifndef MAP_FAILED
82 #define MAP_FAILED 0
83 #endif
84
DiskStream()85 DiskStream::DiskStream()
86 : _state(DiskStream::NO_STATE),
87 _filefd(0),
88 _netfd(0),
89 _dataptr(nullptr),
90 _max_memload(0),
91 _filesize(0),
92 _pagesize(0),
93 _offset(0)
94 {
95 // GNASH_REPORT_FUNCTION;
96 /// \brief get the pagesize and cache the value
97 #ifdef HAVE_SYSCONF
98 _pagesize = sysconf(_SC_PAGESIZE);
99 _max_memload = _pagesize * MAX_PAGES;
100 #else
101 #if _WIN32
102 // The default page size for Win32 is 4k
103 SYSTEM_INFO si;
104 GetSystemInfo(&si);
105 _pagesize = si.dwPageSize;
106 #else
107 #ifdef __amigaos4__
108 uint32 PageSize;
109
110 IExec->GetCPUInfoTags(
111 GCIT_ExecPageSize, &PageSize,
112 TAG_DONE);
113 _pagesize = PageSize;
114 #else
115 #error "Need to define the memory page size without sysconf()!"
116 #endif
117 #endif
118 #endif
119 #ifdef USE_STATS_CACHE
120 clock_gettime (CLOCK_REALTIME, &_last_access);
121 _accesses = 1;
122 #endif
123 }
124
DiskStream(const string & str)125 DiskStream::DiskStream(const string &str)
126 : _state(DiskStream::NO_STATE),
127 _filefd(0),
128 _netfd(0),
129 _dataptr(nullptr),
130 _max_memload(0),
131 _filesize(0),
132 _pagesize(0),
133 _offset(0)
134 {
135 // GNASH_REPORT_FUNCTION;
136 /// \brief get the pagesize and cache the value
137 #ifdef HAVE_SYSCONF
138 _pagesize = sysconf(_SC_PAGESIZE);
139 _max_memload = _pagesize * MAX_PAGES;
140 #else
141 #ifdef _WIN32
142 SYSTEM_INFO si;
143 GetSystemInfo(&si);
144 _pagesize = si.dwPageSize;
145 #else
146 #ifdef __amigaos4__
147 uint32 PageSize;
148
149 IExec->GetCPUInfoTags(
150 GCIT_ExecPageSize, &PageSize,
151 TAG_DONE);
152 _pagesize = PageSize;
153 #else
154 #error "Need to define the memory page size without sysconf()!"
155 #endif
156 #endif
157 #endif
158
159 _filespec = str;
160 #ifdef USE_STATS_CACHE
161 clock_gettime (CLOCK_REALTIME, &_last_access);
162 _accesses = 1;
163 #endif
164 }
165
DiskStream(const string & str,std::uint8_t * data,size_t size)166 DiskStream::DiskStream(const string &str, std::uint8_t *data, size_t size)
167 : _state(DiskStream::NO_STATE),
168 _filefd(0),
169 _netfd(0),
170 _dataptr(nullptr),
171 _max_memload(0),
172 _pagesize(0),
173 _offset(0)
174 {
175 // GNASH_REPORT_FUNCTION;
176
177 /// \brief get the pagesize and cache the value
178 #ifdef HAVE_SYSCONF
179 _pagesize = sysconf(_SC_PAGESIZE);
180 _max_memload = _pagesize * MAX_PAGES;
181 #else
182 #ifdef _WIN32
183 SYSTEM_INFO si;
184 GetSystemInfo(&si);
185 _pagesize = si.dwPageSize;
186 #else
187 #ifdef __amigaos4__
188 uint32 PageSize;
189
190 IExec->GetCPUInfoTags(
191 GCIT_ExecPageSize, &PageSize,
192 TAG_DONE);
193 _pagesize = PageSize;
194 #else
195 #error "Need to define the memory page size without sysconf()!"
196 #endif
197 #endif
198 #endif
199
200 _dataptr = new std::uint8_t[size];
201 // Note that this is a copy operation, which may effect performance. We do this for now
202 // incase the top level pointer gets deleted. This should really be using
203 // boost::scoped_array, but we don't want that complexity till this code stabalizes.
204 std::copy(data, data + size, _dataptr);
205 _filespec = str;
206 _filesize = size;
207
208 #ifdef USE_STATS_CACHE
209 clock_gettime (CLOCK_REALTIME, &_last_access);
210 _accesses = 1;
211 #endif
212 }
213
DiskStream(const string & str,cygnal::Buffer & buf)214 DiskStream::DiskStream(const string &str, cygnal::Buffer &buf)
215 : _state(DiskStream::NO_STATE),
216 _filefd(0),
217 _netfd(0),
218 _dataptr(nullptr),
219 _max_memload(0),
220 _pagesize(0),
221 _offset(0)
222 {
223 // GNASH_REPORT_FUNCTION;
224
225 /// \brief get the pagesize and cache the value
226 #ifdef HAVE_SYSCONF
227 _pagesize = sysconf(_SC_PAGESIZE);
228 _max_memload = _pagesize * MAX_PAGES;
229 #else
230 #ifdef _WIN32
231 SYSTEM_INFO si;
232 GetSystemInfo(&si);
233 _pagesize = si.dwPageSize;
234 #else
235 #ifdef __amigaos4__
236 uint32 PageSize;
237
238 IExec->GetCPUInfoTags(
239 GCIT_ExecPageSize, &PageSize,
240 TAG_DONE);
241 _pagesize = PageSize;
242 #else
243 #error "Need to define the memory page size without sysconf()!"
244 #endif
245 #endif
246 #endif
247
248 _dataptr = new std::uint8_t[buf.size()];
249 // Note that this is a copy operation, which may effect performance. We do this for now
250 // incase the top level pointer gets deleted. This should really be using
251 // boost::scoped_array, but we don't want that complexity till this code stabalizes.
252 std::copy(buf.begin(), buf.end(), _dataptr);
253 _filespec = str;
254 _filesize = buf.size();
255
256 #ifdef USE_STATS_CACHE
257 clock_gettime (CLOCK_REALTIME, &_last_access);
258 _accesses = 1;
259 #endif
260 }
261
DiskStream(const string & str,int netfd)262 DiskStream::DiskStream(const string &str, int netfd)
263 : _state(DiskStream::NO_STATE),
264 _filefd(0),
265 _filespec(nullptr),
266 _dataptr(nullptr),
267 _max_memload(0),
268 _filesize(0),
269 _pagesize(0),
270 _offset(0)
271 {
272 // GNASH_REPORT_FUNCTION;
273 /// \brief get the pagesize and cache the value
274 #ifdef HAVE_SYSCONF
275 _pagesize = sysconf(_SC_PAGESIZE);
276 _max_memload = _pagesize * MAX_PAGES;
277 #else
278 #ifdef _WIN32
279 SYSTEM_INFO si;
280 GetSystemInfo(&si);
281 _pagesize = si.dwPageSize;
282 #else
283 #ifdef __amigaos4__
284 uint32 PageSize;
285
286 IExec->GetCPUInfoTags(
287 GCIT_ExecPageSize, &PageSize,
288 TAG_DONE);
289 _pagesize = PageSize;
290 #else
291 #error "Need to define the memory page size without sysconf()!"
292 #endif
293 #endif
294 #endif
295
296 _netfd = netfd;
297 _filespec = str;
298 #ifdef USE_STATS_CACHE
299 clock_gettime (CLOCK_REALTIME, &_last_access);
300 _accesses = 1;
301 #endif
302 }
303
~DiskStream()304 DiskStream::~DiskStream()
305 {
306 GNASH_REPORT_FUNCTION;
307 log_debug(_("Deleting %s on fd #%d"), _filespec, _filefd);
308
309 if (_filefd) {
310 ::close(_filefd);
311 }
312 if (_netfd) {
313 ::close(_netfd);
314 }
315 }
316
317 /// \brief copy another DiskStream into ourselves, so they share data
318 /// in memory.
319 DiskStream &
operator =(DiskStream * stream)320 DiskStream::operator=(DiskStream *stream)
321 {
322 GNASH_REPORT_FUNCTION;
323
324 _filespec = stream->getFilespec();
325 _filetype = stream->getFileType();
326 _filefd = stream->getFileFd();
327 _netfd = stream->getNetFd();
328 _dataptr = stream->get();
329 _state = stream->getState();
330
331 return *this;
332 }
333
334 bool
fullyPopulated()335 DiskStream::fullyPopulated()
336 {
337 // GNASH_REPORT_FUNCTION;
338
339 if ((_filesize < _max_memload) && (_dataptr != nullptr)) {
340 return true;
341 }
342 return false;
343 }
344
345 /// \brief Close the open disk file, but stay resident in memory.
346 void
close()347 DiskStream::close()
348 {
349 // GNASH_REPORT_FUNCTION;
350
351 log_debug(_("Closing %s on fd #%d"), _filespec, _filefd);
352
353 if (_filefd) {
354 ::close(_filefd);
355 }
356
357 // reset everything in case we get reopened.
358 _filefd = 0;
359 _netfd = 0;
360 _offset = 0;
361 _seekptr = _dataptr + _pagesize;
362 _state = CLOSED;
363
364 #if 0 // FIXME: don't unmap the memory for debugging
365 #ifdef _WIN32
366 UnmapViewOfFile(_dataptr);
367 #elif defined(__amigaos4__)
368 if (_dataptr) free(_dataptr);
369 #else
370 if ((_dataptr != MAP_FAILED) && (_dataptr != 0)) {
371 munmap(_dataptr, _pagesize);
372 }
373 #endif
374 #endif
375
376 }
377
378 /// \brief Load a chunk (pagesize) of the file into memory.
379 /// This loads a pagesize of the disk file into memory. We read
380 /// the file this way as it is faster and takes less resources
381 /// than read(), which add buffering we don't need.
382 /// This offset must be a multipe of the pagesize.
383 ///
384 /// @param size The amount of bytes to read, often the filesize
385 /// for smaller files below CACHE_LIMIT.
386 ///
387 /// @param offset The location in bytes in the file of the desired data.
388 ///
389 /// @return A real pointer to the location of the data at the
390 /// location pointed to by the offset.
391 std::uint8_t *
loadToMem(off_t offset)392 DiskStream::loadToMem(off_t offset)
393 {
394 // GNASH_REPORT_FUNCTION;
395
396 return loadToMem(_filesize, offset);
397 }
398
399 std::uint8_t *
loadToMem(size_t filesize,off_t offset)400 DiskStream::loadToMem(size_t filesize, off_t offset)
401 {
402 GNASH_REPORT_FUNCTION;
403
404
405 log_debug(_("%s: offset is: %d"), __FUNCTION__, offset);
406
407 // store the offset we came in with so next time we know where to start
408 _offset = offset;
409
410 /// We only map memory in pages of pagesize, so if the offset is smaller
411 /// than that, start at page 0.
412 off_t page = 0;
413 if (static_cast<size_t>(offset) < _pagesize) {
414 page = 0;
415 } else {
416 if (offset % _pagesize) {
417 // calculate the number of pages
418 page = ((offset - (offset % _pagesize)) / _pagesize) * _pagesize;
419 log_debug(_("Adjusting offset from %d to %d so it's page aligned."),
420 offset, page);
421 } else {
422 log_debug(_("Offset is page aligned already"));
423 }
424 }
425
426 // Figure out the maximum number of bytes we can load into memory.
427 size_t loadsize = 0;
428 if (filesize < _max_memload) {
429 log_debug(_("Loading entire file of %d bytes into memory segment"),
430 filesize);
431 loadsize = filesize;
432 } else {
433 log_debug(_("Loading partial file of %d bytes into memory segment"),
434 filesize, _max_memload);
435 loadsize = _max_memload;
436 }
437
438 // If we were initialized from a memory Buffer, data is being uploaded into
439 // this DiskStream, so sufficient memory will already be allocated for this data.
440 // If the data came from a disk based file, then we allocate enough memory to hold it.
441 if (_dataptr) {
442 log_debug(_("Using existing Buffer for file"));
443 return _dataptr + offset;
444 }
445
446 std::uint8_t *dataptr = nullptr;
447
448 if (_filefd) {
449 /// If the data pointer is legit, then we need to unmap that page
450 /// to mmap() a new one. If we're still in the current mapped
451 /// page, then just return the existing data pointer.
452 if (dataptr != nullptr) {
453 #ifdef _WIN32
454 UnmapViewOfFile(_dataptr);
455 #elif defined(__amigaos4__)
456 if (_dataptr) free(_dataptr);
457 #else
458 munmap(_dataptr, _pagesize);
459 #endif
460 }
461 #if 0
462 // See if the page has already been mapped in;
463 unsigned char vec[_pagesize];
464 mincore(offset, _pagesize, vec);
465 if (vec[i] & 0x1) {
466 // cached page is in memory
467 }
468 #endif
469 // if (size <= _pagesize) {
470 // size = _filesize;
471 // }
472
473 // lock in case two threads try to load the same file at the
474 // same time.
475 std::lock_guard<std::mutex> lock(mem_mutex);
476
477 #ifdef _WIN32
478 HANDLE handle = CreateFileMapping((HANDLE)_get_osfhandle(_filefd), NULL,
479 PAGE_WRITECOPY, 0, 0, NULL);
480 if (handle != NULL) {
481 dataptr = static_cast<std::uint8_t *>(MapViewOfFile(handle, FILE_MAP_COPY, 0, offset, page));
482 CloseHandle(handle);
483
484 }
485 #elif defined(__amigaos4__)
486 dataptr = static_cast<std::uint8_t *>(malloc(loadsize));
487 #else
488 dataptr = static_cast<std::uint8_t *>(mmap(nullptr, loadsize,
489 PROT_READ, MAP_SHARED,
490 _filefd, page));
491 #endif
492 } else {
493 log_error(_("Couldn't load file %s"), _filespec);
494 return nullptr;
495 }
496
497 if (dataptr == MAP_FAILED) {
498 log_error(_("Couldn't map file %s into memory: %s"),
499 _filespec, strerror(errno));
500 return nullptr;
501 } else {
502 log_debug(_("File %s a offset %d mapped to: %p"), _filespec, offset, (void *)dataptr);
503 clock_gettime (CLOCK_REALTIME, &_last_access);
504 _dataptr = dataptr;
505 // map the seekptr to the end of data
506 _seekptr = _dataptr + _pagesize;
507 _state = OPEN;
508 _offset = 0;
509 }
510
511 std::uint8_t *ptr = dataptr;
512 if (_filetype == FILETYPE_FLV) {
513 // FIXME: for now, assume all media files are in FLV format
514 _flv.reset(new cygnal::Flv);
515 std::shared_ptr<cygnal::Flv::flv_header_t> head = _flv->decodeHeader(ptr);
516 ptr += sizeof(cygnal::Flv::flv_header_t);
517 ptr += sizeof(cygnal::Flv::previous_size_t);
518 std::shared_ptr<cygnal::Flv::flv_tag_t> tag = _flv->decodeTagHeader(ptr);
519 ptr += sizeof(cygnal::Flv::flv_tag_t);
520 size_t bodysize = _flv->convert24(tag->bodysize);
521 if (tag->type == cygnal::Flv::TAG_METADATA) {
522 std::shared_ptr<cygnal::Element> metadata = _flv->decodeMetaData(ptr, bodysize);
523 if (metadata) {
524 metadata->dump();
525 }
526 }
527 }
528
529 if (filesize < _max_memload) {
530 close();
531 }
532
533 // The data pointer points to the real data past all the header bytes.
534 // _dataptr = ptr;
535
536 return _seekptr;
537 }
538
539 /// \brief Write the existing data to the Network.
540 ///
541 /// @return true is the write suceeded, false if it failed.
542 bool
writeToNet(int,int)543 DiskStream::writeToNet(int /* start */, int /* bytes */)
544 {
545 GNASH_REPORT_FUNCTION;
546
547 return false;
548 }
549
550 /// \brief Write the data in memory to disk
551 ///
552 /// @param filespec The relative path to the file to write, which goes in
553 /// a safebox for storage.
554 ///
555 /// @return true if the operation suceeded, false if it failed.
556 bool
writeToDisk()557 DiskStream::writeToDisk()
558 {
559 // GNASH_REPORT_FUNCTION;
560 return writeToDisk(_filespec, _dataptr, _filesize);
561 }
562
563 bool
writeToDisk(const std::string & filespec)564 DiskStream::writeToDisk(const std::string &filespec)
565 {
566 // GNASH_REPORT_FUNCTION;
567 return writeToDisk(filespec, _dataptr, _filesize);
568 }
569
570 bool
writeToDisk(const std::string & filespec,cygnal::Buffer & data)571 DiskStream::writeToDisk(const std::string &filespec, cygnal::Buffer &data)
572 {
573 // GNASH_REPORT_FUNCTION;
574 return writeToDisk(filespec, data.reference(), data.allocated());
575 }
576
577 bool
writeToDisk(const std::string & filespec,std::uint8_t * data,size_t size)578 DiskStream::writeToDisk(const std::string &filespec, std::uint8_t *data, size_t size)
579 {
580 // GNASH_REPORT_FUNCTION;
581
582 int fd = ::open(filespec.c_str() ,O_WRONLY|O_CREAT, S_IRWXU);
583 if (fd < 0) {
584 log_error(strerror(errno));
585 }
586 log_debug(_("Writing data (%d bytes) to disk: \"%s\""), size, filespec);
587 if(::write(fd, data, size) < 0) {
588 log_error(strerror(errno));
589 };
590 ::close(fd);
591
592 return true;
593 }
594
595 /// \brief Open a file to be streamed.
596 ///
597 /// @param filespec The full path and file name for the data to be
598 /// read.
599 ///
600 /// @return True if the file was opened successfully, false if not.
601 bool
open(const string & filespec)602 DiskStream::open(const string &filespec)
603 {
604 // GNASH_REPORT_FUNCTION;
605
606 return open(filespec, _netfd);
607 }
608
609 /// \brief Open a file to be streamed.
610 ///
611 /// @param filespec The full path and file name for the data to be
612 /// read.
613 ///
614 /// @param netfd An optional file descriptor to read data from
615 ///
616 /// @return True if the file was opened successfully, false if not.
617 bool
open(const string & filespec,int)618 DiskStream::open(const string &filespec, int /*netfd*/)
619 {
620 // GNASH_REPORT_FUNCTION;
621
622 //struct stat stats;
623 // TODO: should we use the 'netfd' passed as parameter instead ?
624 return open(filespec, _netfd, _statistics);
625 }
626
627 /// \brief Open a file to be streamed.
628 ///
629 /// @param filespec The full path and file name for the data to be
630 /// read.
631 ///
632 /// @param netfd An optional file descriptor to read data from
633 ///
634 /// @param statistics The optional data structure to use for
635 /// collecting statistics on this stream.
636 ///
637 /// @return True if the file was opened successfully, false if not.
638 bool
open(const string & filespec,int netfd,Statistics & statistics)639 DiskStream::open(const string &filespec, int netfd, Statistics &statistics)
640 {
641 GNASH_REPORT_FUNCTION;
642
643 // the file is already open
644 if (_state == OPEN) {
645 #ifdef USE_STATS_CACHE
646 _accesses++;
647 #endif
648 return true;
649 }
650
651 // If DONE, then we were previously open, but not closed, so we
652 // just reopen the same stream.
653 if ((_state == DONE) || (_state == CLOSED)) {
654 _state = OPEN;
655 return true;
656 }
657
658 _netfd = netfd;
659 _statistics = statistics;
660 _filespec = filespec;
661
662 log_debug(_("Trying to open %s"), filespec);
663
664 if (getFileStats(filespec)) {
665 std::lock_guard<std::mutex> lock(io_mutex);
666 _filefd = ::open(_filespec.c_str(), O_RDONLY);
667 log_debug (_("Opening file %s (fd #%d), %lld bytes in size."),
668 _filespec, _filefd,
669 (std::int64_t) _filesize);
670 _state = OPEN;
671 _filetype = determineFileType(filespec);
672 loadToMem(0); // load the first page into memory
673 } else {
674 log_error (_("File %s doesn't exist"), _filespec);
675 _state = DONE;
676 return false;
677 }
678
679 #ifdef USE_STATS_CACHE
680 clock_gettime (CLOCK_REALTIME, &_first_access);
681 #endif
682
683 return true;
684 }
685
686 /// \brief Stream the file that has been loaded,
687 ///
688 /// @return True if the data was streamed successfully, false if not.
689 bool
play()690 DiskStream::play()
691 {
692 // GNASH_REPORT_FUNCTION;
693
694 return play(_netfd, true);
695 }
696
697 bool
play(bool flag)698 DiskStream::play(bool flag)
699 {
700 // GNASH_REPORT_FUNCTION;
701
702 return play(_netfd, flag);
703 }
704
705 /// \brief Stream the file that has been loaded,
706 ///
707 /// @param netfd The file descriptor to use for operations
708 ///
709 /// @param flag True to only send the first packet, False plays entire file.
710 ///
711 /// @return True if the data was streamed successfully, false if not.
712 bool
play(int netfd,bool flag)713 DiskStream::play(int netfd, bool flag)
714 {
715 GNASH_REPORT_FUNCTION;
716
717 bool done = false;
718 // dump();
719
720 _netfd = netfd;
721
722 while (!done) {
723 // If flag is false, only play the one page of the file.
724 if (!flag) {
725 done = true;
726 }
727 switch (_state) {
728 case NO_STATE:
729 log_network(_("No Diskstream open %s for net fd #%d"),
730 _filespec, netfd);
731 break;
732 case CREATED:
733 case CLOSED:
734 if (_dataptr) {
735 log_network(_("Diskstream %s is closed on net fd #%d."),
736 _filespec, netfd);
737 }
738 done = true;
739 continue;
740 case OPEN:
741 loadToMem(0);
742 _offset = 0;
743 _state = PLAY;
744 // continue;
745 case PLAY:
746 {
747 size_t ret;
748 Network net;
749 if ((_filesize - _offset) < _pagesize) {
750 #ifdef HAVE_SENDFILE_XX
751 ret = sendfile(netfd, _filefd, &_offset, _filesize - _offset);
752 #else
753 ret = net.writeNet(netfd, (_dataptr + _offset), (_filesize - _offset));
754 if (ret != (_filesize - _offset)) {
755 log_error(_("In %s(%d): couldn't write %d bytes to net fd #%d! %s"),
756 __FUNCTION__, __LINE__, (_filesize - _offset),
757 netfd, strerror(errno));
758 }
759 #endif
760 log_network(_("Done playing file %s, size was: %d"),
761 _filespec, _filesize);
762 close();
763 done = true;
764 // reset to the beginning of the file
765 _offset = 0;
766 } else {
767 //log_network("\tPlaying part of file %s, offset is: %d out of %d bytes.", _filespec, _offset, _filesize);
768 #ifdef HAVE_SENDFILE_XX
769 ret = sendfile(netfd, _filefd, &_offset, _pagesize);
770 #else
771 ret = net.writeNet(netfd, (_dataptr + _offset), _pagesize);
772 if (ret != _pagesize) {
773 log_error(_("In %s(%d): couldn't write %d of bytes of data to net fd #%d! Got %d, %s"),
774 __FUNCTION__, __LINE__, _pagesize, netfd,
775 ret, strerror(errno));
776 return false;
777 }
778 _offset += _pagesize;
779 #endif
780 }
781 switch (errno) {
782 case EINVAL:
783 case ENOSYS:
784 case EFAULT:
785 log_error("%s", strerror(errno));
786 break;
787 default:
788 break;
789 }
790 break;
791 }
792 case PREVIEW:
793 break;
794 case THUMBNAIL:
795 break;
796 case PAUSE:
797 break;
798 case SEEK:
799 break;
800 case UPLOAD:
801 break;
802 case MULTICAST:
803 break;
804 case DONE:
805 log_debug(_("Restarting Disk Stream from the beginning"));
806 _offset = 0;
807 _filefd = 0;
808 _state = PLAY;
809 _seekptr = _dataptr + _pagesize;
810 _netfd = netfd;
811 continue;
812 default:
813 break;
814 }
815 }
816
817 //int blocksize = 8192;
818 // Network net;
819 // while ((_seekptr - _dataptr) >= 0) {
820 //// nbytes = net.writeNet(_netfd, (char *)_seekptr, _filesize);
821 // if (nbytes <= 0) {
822 // break;
823 // }
824 #ifdef USE_STATS_FILE
825 _statistics->addBytes(nbytes);
826 _bytes += nbytes;
827 // _seekptr += nbytes;
828 #endif
829
830 return true;
831 }
832
833 /// \brief Stream a preview of the file.
834 /// A preview is a series of video frames from
835 /// the video file. Each video frame is taken by sampling
836 /// the file at a set interval.
837 ///
838 /// @param filespec The full path and file name for the data to be
839 /// read.
840 ///
841 /// @param quantity The number of frames to stream..
842 ///
843 /// @return True if the thumbnails were streamed successfully, false if not.
844 bool
preview(const string &,int)845 DiskStream::preview(const string & /*filespec*/, int /*frames*/)
846 {
847 // GNASH_REPORT_FUNCTION;
848
849 _state = PREVIEW;
850 log_unimpl(__PRETTY_FUNCTION__);
851 return true; // Default to true
852 }
853
854 /// \brief Stream a series of thumbnails.
855 /// A thumbnail is a series of jpg images of frames from
856 /// the video file instead of video frames. Each thumbnail
857 /// is taken by sampling the file at a set interval.
858 ///
859 /// @param filespec The full path and file name for the data to be
860 /// read.
861 ///
862 /// @param quantity The number of thumbnails to stream..
863 ///
864 /// @return True if the thumbnails were streamed successfully, false if not.
865 bool
thumbnail(const string &,int)866 DiskStream::thumbnail(const string & /*filespec*/, int /*quantity*/)
867 {
868 // GNASH_REPORT_FUNCTION;
869
870 _state = THUMBNAIL;
871 log_unimpl(__PRETTY_FUNCTION__);
872 return true; // Default to true
873 }
874
875 /// \brief Pause the stream currently being played.
876 ///
877 /// @return True if the stream was paused successfully, false if not.
878 bool
pause()879 DiskStream::pause()
880 {
881 // GNASH_REPORT_FUNCTION;
882
883 _state = PAUSE;
884 log_unimpl(__PRETTY_FUNCTION__);
885 return true; // Default to true
886 }
887
888 /// \brief Seek within the stream.
889 ///
890 /// @param the offset in bytes to the location within the file to
891 /// seek to.
892 ///
893 /// @return A real pointer to the location of the data seeked to.
894 std::uint8_t *
seek(off_t offset)895 DiskStream::seek(off_t offset)
896 {
897 // GNASH_REPORT_FUNCTION;
898
899 _state = SEEK;
900 return loadToMem(offset);
901 }
902
903 /// \brief Upload a file into a sandbox.
904 /// The sandbox is an area where uploaded files can get
905 /// written to safely. For SWF content, the file name also
906 /// includes a few optional paths used to seperate
907 /// applications from each other.
908 ///
909 /// @param filespec The file name for the data to be written.
910 ///
911 /// @return True if the file was uploaded successfully, false if not.
912 bool
upload(const string &)913 DiskStream::upload(const string & /*filespec*/)
914 {
915 // GNASH_REPORT_FUNCTION;
916
917 _state = UPLOAD;
918 log_unimpl( __PRETTY_FUNCTION__);
919 return true; // Default to true
920 }
921
922 // Stream a single "real-time" source.
923 bool
multicast(const string &)924 DiskStream::multicast(const string & /*filespec*/)
925 {
926 // GNASH_REPORT_FUNCTION;
927
928 _state = MULTICAST;
929 log_unimpl(__PRETTY_FUNCTION__);
930 return true; // Default to true
931 }
932
933 DiskStream::filetype_e
determineFileType()934 DiskStream::determineFileType()
935 {
936 // GNASH_REPORT_FUNCTION;
937
938 return(determineFileType(_filespec));
939 }
940
941 // Get the file type, so we know how to set the
942 // Content-type in the header.
943 bool
getFileStats(const std::string & filespec)944 DiskStream::getFileStats(const std::string &filespec)
945 {
946 // GNASH_REPORT_FUNCTION;
947 string actual_filespec = filespec;
948 struct stat st;
949 bool try_again = false;
950
951 do {
952 // cerr << "Trying to open " << actual_filespec << "\r\n";
953 if (stat(actual_filespec.c_str(), &st) == 0) {
954 // If it's a directory, then we emulate what apache
955 // does, which is to load the index.html file in that
956 // directry if it exists.
957 if (S_ISDIR(st.st_mode)) {
958 log_debug(_("%s is a directory, appending index.html"),
959 actual_filespec.c_str());
960 if (actual_filespec[actual_filespec.size()-1] != '/') {
961 actual_filespec += '/';
962 }
963 actual_filespec += "index.html";
964 try_again = true;
965 continue;
966 } else { // not a directory
967 // log_debug("%s is a normal file\n", actual_filespec.c_str());
968 _filespec = actual_filespec;
969 _filetype = determineFileType(_filespec);
970 _filesize = st.st_size;
971 try_again = false;
972 }
973 } else {
974 _filetype = FILETYPE_NONE;
975 return false;
976 } // end of stat()
977
978 _filesize = st.st_size;
979 } while (try_again);
980
981 return true;
982 }
983
984 DiskStream::filetype_e
determineFileType(const string & filespec)985 DiskStream::determineFileType(const string &filespec)
986 {
987 // GNASH_REPORT_FUNCTION;
988
989 if (filespec.empty()) {
990 return FILETYPE_NONE;
991 }
992
993 string::size_type pos;
994
995 string name = filespec;
996
997 // transform to lower case so we match filenames which may
998 // have the suffix in upper case, or this test fails.
999 std::transform(name.begin(), name.end(), name.begin(),
1000 (int(*)(int)) tolower);
1001
1002 pos = name.rfind(".");
1003 if (pos != string::npos) {
1004 string suffix = name.substr(pos+1, name.size());
1005 _filetype = FILETYPE_NONE;
1006 if (suffix == "htm") {
1007 _filetype = FILETYPE_HTML;
1008 } else if (suffix == "html") {
1009 _filetype = FILETYPE_HTML;
1010 } else if (suffix == "ogg") {
1011 _filetype = FILETYPE_OGG;
1012 } else if (suffix == "ogv") {
1013 _filetype = FILETYPE_OGG;
1014 } else if (suffix == "swf") {
1015 _filetype = FILETYPE_SWF;
1016 } else if (suffix == "php") {
1017 _filetype = FILETYPE_PHP;
1018 } else if (suffix == "flv") {
1019 _filetype = FILETYPE_FLV;
1020 }else if (suffix == "mp3") {
1021 _filetype = FILETYPE_MP3;
1022 } else if (suffix == "flac") {
1023 _filetype = FILETYPE_FLAC;
1024 } else if (suffix == "jpg") {
1025 _filetype = FILETYPE_JPEG;
1026 } else if (suffix == "jpeg") {
1027 _filetype = FILETYPE_JPEG;
1028 } else if (suffix == "txt") {
1029 _filetype = FILETYPE_TEXT;
1030 } else if (suffix == "xml") {
1031 _filetype = FILETYPE_XML;
1032 } else if (suffix == "mp4") {
1033 _filetype = FILETYPE_MP4;
1034 } else if (suffix == "mpeg") {
1035 _filetype = FILETYPE_MP4;
1036 } else if (suffix == "png") {
1037 _filetype = FILETYPE_PNG;
1038 } else if (suffix == "gif") {
1039 _filetype = FILETYPE_GIF;
1040 }
1041 }
1042
1043 return _filetype;
1044 }
1045
1046 DiskStream::filetype_e
determineFileType(std::uint8_t * data)1047 DiskStream::determineFileType( std::uint8_t *data)
1048 {
1049 // GNASH_REPORT_FUNCTION;
1050
1051 if (data == nullptr) {
1052 return FILETYPE_NONE;
1053 }
1054
1055 // JPEG, offset 6 bytes, read the string JFIF
1056 if (memcpy(data + 6, "JFIF", 4) == nullptr) {
1057 return FILETYPE_NONE;
1058 }
1059 // SWF, offset 0, read the string FWS
1060 if (memcpy(data, "SWF", 3) == nullptr) {
1061 return FILETYPE_SWF;
1062 }
1063 // compressed SWF, offset 0, read the string CWS
1064 // FLV, offset 0, read the string FLV
1065 // PNG, offset 0, read the string PNG
1066 if (memcpy(data, "PNG", 3) == nullptr) {
1067 return FILETYPE_PNG;
1068 }
1069 // Ogg, offset 0, read the string OggS
1070 if (memcpy(data, "OggS", 4) == nullptr) {
1071 return FILETYPE_OGG;
1072 }
1073 // Theora, offset 28, read string theora
1074 if (memcpy(data + 28, "theora", 6) == nullptr) {
1075 return FILETYPE_THEORA;
1076 }
1077 // FLAC, offset 28, read string FLAC
1078 if (memcpy(data + 28, "FLAC", 4) == nullptr) {
1079 return FILETYPE_FLAC;
1080 }
1081 // Vorbis, offset 28, read string vorbis
1082 if (memcpy(data + 28, "vorbis", 6) == nullptr) {
1083 return FILETYPE_VORBIS;
1084 }
1085 // MP3, offset 0, read string ID3
1086 if (memcpy(data, "ID3", 3) == nullptr) {
1087 return FILETYPE_MP3;
1088 }
1089
1090 // HTML
1091 // offset 0, read string "\<!doctype\ html"
1092 // offset 0, read string "\<head"
1093 // offset 0, read string "\<title"
1094 // offset 0, read string "\<html"
1095 if (memcpy(data, "ID3", 3) == nullptr) {
1096 return FILETYPE_HTML;
1097 }
1098
1099 // XML, offset 0, read string "\<?xml"
1100 if (memcpy(data, "<?xml", 5) == nullptr) {
1101 return FILETYPE_XML;
1102 }
1103
1104 return FILETYPE_NONE;
1105 }
1106
1107 /// \brief Dump the internal data of this class in a human readable form.
1108 /// @remarks This should only be used for debugging purposes.
1109 void
dump()1110 DiskStream::dump()
1111 {
1112 using namespace std;
1113 // GNASH_REPORT_FUNCTION;
1114 const char *state_str[] = {
1115 "NO_STATE",
1116 "CREATED",
1117 "CLOSED",
1118 "OPEN",
1119 "PLAY",
1120 "PREVIEW",
1121 "THUMBNAIL",
1122 "PAUSE",
1123 "SEEK",
1124 "UPLOAD",
1125 "MULTICAST",
1126 "DONE"
1127 };
1128
1129 const char *type_str[] = {
1130 "NONE",
1131 "AMF",
1132 "SWF",
1133 "HTML",
1134 "PNG",
1135 "JPEG",
1136 "GIF",
1137 "MP3",
1138 "MP4",
1139 "OGG",
1140 "VORBIS",
1141 "THEORA",
1142 "DIRAC",
1143 "TEXT",
1144 "FLV",
1145 "VP6",
1146 "XML",
1147 "FLAC",
1148 "ENCODED"
1149 };
1150
1151 cerr << "State is \"" << state_str[_state] << "\"" << endl;
1152 cerr << "File type is \"" << type_str[_filetype] << "\"" << endl;
1153 cerr << "Filespec is \"" << _filespec << "\"" << endl;
1154 cerr << "Disk file descriptor is fd #" << _filefd << endl;
1155 cerr << "Network file descriptor is fd #" << _netfd << endl;
1156 cerr << "File size is " << _filesize << endl;
1157 cerr << "Memory Page size is " << _pagesize << endl;
1158 cerr << "Memory Offset is " << _offset << endl;
1159 cerr << "Base Memory Address is " << (void *)_dataptr << endl;
1160 cerr << "Seek Pointer Memory Address is " << (void *)_seekptr << endl;
1161
1162 // dump timing related data
1163 struct timespec now;
1164 clock_gettime (CLOCK_REALTIME, &now);
1165 // double time = ((now.tv_sec - _last_access.tv_sec) + ((now.tv_nsec - _last_access.tv_nsec)/1e9));
1166
1167 cerr << "Time since last access: " << fixed << ((now.tv_sec - _last_access.tv_sec) + ((now.tv_nsec - _last_access.tv_nsec)/1e9)) << " seconds ago." << endl;
1168
1169 #ifdef USE_STATS_CACHE
1170 cerr << "Time since first access: " << fixed <<
1171 ((_last_access.tv_sec - _first_access.tv_sec) + ((_last_access.tv_nsec - _first_access.tv_nsec)/1e9))
1172 << " seconds lifespan."
1173 << endl;
1174 #endif
1175
1176 }
1177
1178
1179 } // end of cygnal namespace
1180
1181 // local Variables:
1182 // mode: C++
1183 // indent-tabs-mode: nil
1184 // End:
1185