root/libbase/Socket.cpp

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. _error
  2. connected
  3. close
  4. connect
  5. fillCache
  6. read
  7. readNonBlocking
  8. write
  9. tell
  10. seek
  11. go_to_end
  12. eof

// Socket.cpp - an IOChannel for sockets
//
//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010,
//   2011 Free Software Foundation, Inc
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#include <boost/cstdint.hpp>

namespace {
}
            
#include "GnashSystemNetHeaders.h"
#include "GnashSystemFDHeaders.h"

#include <cstring>
#include <cerrno>
#include <csignal>
#include <boost/lexical_cast.hpp>

#include "URL.h"
#include "Socket.h"
#include "log.h"
#include "utility.h"
#include "GnashAlgorithm.h"

namespace gnash {

Socket::Socket()
    :
    _connected(false),
    _socket(0),
    _size(0),
    _pos(0),
    _error(false)
{}

bool
Socket::connected() const
{
    if (_connected) return true;
    if (!_socket) return false;

    size_t retries = 10;
    fd_set fdset;
    struct timeval tval;

    while (retries-- > 0) {

        FD_ZERO(&fdset);
        FD_SET(_socket, &fdset);
            
        tval.tv_sec = 0;
        tval.tv_usec = 103;
            
        const int ret = ::select(_socket + 1, NULL, &fdset, NULL, &tval);
        
        // Select timeout
        if (ret == 0) continue;
           
        if (ret > 0) {
            int val = 0;
            socklen_t len = sizeof(val);
            // NB: the cast to char* is needed for windows and is harmless
            // for POSIX.
            if (::getsockopt(_socket, SOL_SOCKET, SO_ERROR,
                        reinterpret_cast<char*>(&val), &len) < 0) {
                log_debug("Error");
                _error = true;
                return false;
            }

            if (!val) {
                _connected = true;
                return true;
            }
            _error = true;
            return false;
        }

        // If interrupted by a system call, try again
        if (ret == -1) {
            const int err = errno;
            if (err == EINTR) {
                log_debug(_("Socket interrupted by a system call"));
                continue;
            }

            log_error(_("XMLSocket: The socket was never available"));
            _error = true;
            return false;
        }
    }
    return false;
} 
    

void
Socket::close()
{
    if (_socket) ::close(_socket);
    _socket = 0;
    _size = 0;
    _pos = 0;
    _connected = false;
    _error = false;
}

bool
Socket::connect(const std::string& hostname, boost::uint16_t port)
{

    // We use _socket here because connected() or _connected might not
    // be true if a connection attempt is underway but not completed.
    if (_socket) {
        log_error("Connection attempt while already connected");
        return false;
    }

    // If _socket is 0, either there has been no connection, or close() has
    // been called. There must not be an error in either case.
    assert(!_error);

    if (hostname.empty()) return false;

    struct sockaddr_in addr;
    std::memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;

    addr.sin_addr.s_addr = ::inet_addr(hostname.c_str());
    if (addr.sin_addr.s_addr == INADDR_NONE) {
        struct hostent* host = ::gethostbyname(hostname.c_str());
        if (!host || !host->h_addr) {
            return false;
        }
        addr.sin_addr = *reinterpret_cast<in_addr*>(host->h_addr);
    }

    addr.sin_port = htons(port);

    _socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
    if (_socket < 0) {
        const int err = errno;
        log_debug("Socket creation failed: %s", std::strerror(err));
        _socket = 0;
        return false;
    }
#ifndef _WIN32
    // Set non-blocking.
    const int flag = ::fcntl(_socket, F_GETFL, 0);
    ::fcntl(_socket, F_SETFL, flag | O_NONBLOCK);
#endif

    const struct sockaddr* a = reinterpret_cast<struct sockaddr*>(&addr);

    // Attempt connection
    if (::connect(_socket, a, sizeof(struct sockaddr)) < 0) {
        const int err = errno;
#ifndef _WIN32
        if (err != EINPROGRESS) {
            log_error("Failed to connect socket: %s", std::strerror(err));
            _socket = 0;
            return false;
        }
#else
        return false;
#endif
    }

    // Magic timeout number. Use rcfile ?
    const struct timeval tv = { 120, 0 };

    // NB: the cast to const char* is needed for windows and is harmless
    // for POSIX.
    if (::setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO,
                reinterpret_cast<const char*>(&tv), sizeof(tv))) {
        log_error("Setting socket timeout failed");
    }

    const int on = 1;
    // NB: the cast to const char* is needed for windows and is harmless
    // for POSIX.
    ::setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY,
            reinterpret_cast<const char*>(&on), sizeof(on));

    assert(_socket);
    return true;
}

void
Socket::fillCache()
{

    // Read position is always _pos + _size wrapped.
    const size_t cacheSize = arraySize(_cache);
    size_t start = (_pos + _size) % cacheSize;

    // Try to fill the whole remaining buffer.
    const size_t completeRead = cacheSize - _size;
    
    // End is start + read size, wrapped.
    size_t end = (start + completeRead) % cacheSize;
    if (end == 0) end = cacheSize;

    char* startpos = _cache + start;

    while (1) {

        // The end pos is either the end of the cache or the first 
        // unprocessed byte.
        char* endpos = _cache + ((startpos < _cache + _pos) ?
                _pos : cacheSize);

        const int thisRead = endpos - startpos;
        assert(thisRead >= 0);

        const int bytesRead = ::recv(_socket, startpos, thisRead, 0);
        
        if (bytesRead == -1) {
            const int err = errno;
#ifndef _WIN32 
            if (err == EWOULDBLOCK || err == EAGAIN) {
                // Nothing to read. Carry on.
                return;
            }
#endif
            log_error("Socket receive error %s", std::strerror(err));
            _error = true;
            return;
        }


        _size += bytesRead;

        // If there weren't enough bytes, that's it.
        if (bytesRead < thisRead) break;

        // If we wrote up to the end of the cache, try writing more to the
        // beginning.
        startpos = _cache;
    }
    
}


// Do a single read and report how many bytes were read.
std::streamsize 
Socket::read(void* dst, std::streamsize num)
{
 
    if (num < 0) return 0;

    if (_size < num && !_error) {
        fillCache();
    }

    if (_size < num) return 0;
    return readNonBlocking(dst, num);

}

std::streamsize
Socket::readNonBlocking(void* dst, std::streamsize num)
{
    if (bad()) return 0;
    
    char* ptr = static_cast<char*>(dst);
    
    if (!_size && !_error) {
        fillCache();
    }

    size_t cacheSize = arraySize(_cache);

    // First read from pos to end

    // Maximum bytes available to read.
    const size_t canRead = std::min<size_t>(_size, num);
    
    size_t toRead = canRead;

    // Space to the end (for the first read).
    const int thisRead = std::min<size_t>(canRead, cacheSize - _pos);

    std::copy(_cache + _pos, _cache + _pos + thisRead, ptr);
    _pos += thisRead;
    _size -= thisRead;
    toRead -= thisRead;

    if (toRead) {
        std::copy(_cache, _cache + toRead, ptr + thisRead);
        _pos = toRead;
        _size -= toRead;
        toRead = 0;
    }

    return canRead - toRead;
}

std::streamsize
Socket::write(const void* src, std::streamsize num)
{
    if (bad()) return 0;

    int bytesSent = 0;
    int toWrite = num;

    const char* buf = static_cast<const char*>(src);

#ifndef _WIN32
    // Prevent sigpipe (which isn't a standard C signal)
    // until leaving this function.
    const struct SignalSetter
    {
        typedef void(*SigHandler)(int);
        SignalSetter() : _h(std::signal(SIGPIPE, SIG_IGN)) {}
        ~SignalSetter() { std::signal(SIGPIPE, _h); }
    private:
        const SigHandler _h;
    } setter;
#endif

    // For broken pipe we prefer being notified with
    // a return of -1 from ::send.

    while (toWrite > 0) {
        bytesSent = ::send(_socket, buf, toWrite, 0);
        if (bytesSent < 0) {
            const int err = errno;
            log_error("Socket send error %s", std::strerror(err));
            _error = true;
            return 0;
        }

        if (!bytesSent) break;
        toWrite -= bytesSent;
        buf += bytesSent;
    }
    return num - toWrite;
}

std::streampos
Socket::tell() const
{
    log_error("tell() called for Socket");
    return static_cast<std::streamsize>(-1);
}

bool
Socket::seek(std::streampos)
{
    log_error("seek() called for Socket");
    return false;
}

void
Socket::go_to_end()
{
    log_error("go_to_end() called for Socket");
}

bool
Socket::eof() const
{
    return !_size && bad();
}

} // namespace gnash

/* [<][>][^][v][top][bottom][index][help] */