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