/* Copyright (C) 2009 Red Hat, 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 2 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, see . */ #include #include #include "event_sources.h" #include "debug.h" #include "utils.h" #ifdef USING_EVENT_FD #include #endif #define NUM_EPOLL_EVENTS 10 #ifdef USING_EVENT_FD #define WRITE_FD _event_fd #define EVENT_DATA_TYPE eventfd_t #else #define WRITE_FD _event_write_fd #define EVENT_DATA_TYPE uint8_t #endif class EventWrapper { public: EventWrapper(EventSources& owner, EventSource& event) : _owner (owner) , _event (&event) , _refs (1) { } EventWrapper* ref() { _refs++; return this; } void unref() { if (!--_refs) { _owner.remove_wrapper(this); delete this; } } EventSource* get_event() { return _event; } void invalidate() { _event = NULL; } private: EventSources& _owner; EventSource* _event; int _refs; }; EventSources::EventSources() { _epoll = epoll_create(NUM_EPOLL_EVENTS); if (_epoll == -1) { THROW("create epool failed"); } } EventSources::~EventSources() { Events::iterator iter = _events.begin(); for (; iter != _events.end(); iter++) { delete *iter; } close(_epoll); } bool EventSources::wait_events(int timeout_ms) { struct epoll_event events[NUM_EPOLL_EVENTS]; int num_events = epoll_wait(_epoll, events, NUM_EPOLL_EVENTS, timeout_ms); if (num_events == -1) { if (errno == EINTR) { return false; } THROW("wait error eventfd failed"); } for (int i = 0; i < num_events; i++) { ((EventWrapper*)events[i].data.ptr)->ref(); } for (int i = 0; i < num_events; i++) { EventWrapper* wrapper; EventSource* event; wrapper = (EventWrapper *)events[i].data.ptr; if ((event = wrapper->get_event())) { event->action(); } wrapper->unref(); } return false; } void EventSources::add_trigger(Trigger& trigger) { int fd = trigger.get_fd(); EventWrapper* wrapper = new EventWrapper(*this, trigger); struct epoll_event event; event.data.ptr = wrapper; event.events = EPOLLIN; if (epoll_ctl(_epoll, EPOLL_CTL_ADD, fd, &event) == -1) { THROW("epoll add failed"); } _events.push_back(wrapper); } void EventSources_p::remove_wrapper(EventWrapper* wrapper) { Events::iterator iter = _events.begin(); for (;; iter++) { if (iter == _events.end()) { THROW("wrapper not found"); } if ((*iter) == wrapper) { _events.erase(iter); return; } } } void EventSources::remove_trigger(Trigger& trigger) { Events::iterator iter = _events.begin(); for (;; iter++) { if (iter == _events.end()) { THROW("trigger not found"); } if ((*iter)->get_event() == &trigger) { (*iter)->invalidate(); (*iter)->unref(); break; } } int fd = trigger.get_fd(); if (epoll_ctl(_epoll, EPOLL_CTL_DEL, fd, NULL) == -1) { THROW("epoll remove failed"); } } EventSources::Trigger::Trigger() { #ifdef USING_EVENT_FD _event_fd = eventfd(0, 0); if (_event_fd == -1) { THROW("create eventfd failed"); } #else int fd[2]; if (pipe(fd) == -1) { THROW("create pipe failed"); } _event_fd = fd[0]; _event_write_fd = fd[1]; #endif int flags; if ((flags = fcntl(_event_fd, F_GETFL)) == -1) { THROW("failed to set eventfd non block: %s", strerror(errno)); } if (fcntl(_event_fd, F_SETFL, flags | O_NONBLOCK) == -1) { THROW("failed to set eventfd non block: %s", strerror(errno)); } } EventSources::Trigger::~Trigger() { close(_event_fd); #ifndef USING_EVENT_FD close(_event_write_fd); #endif } void EventSources::Trigger::trigger() { Lock lock(_lock); if (_pending_int) { return; } _pending_int = true; static const EVENT_DATA_TYPE val = 1; if (::write(WRITE_FD, &val, sizeof(val)) != sizeof(val)) { THROW("write event failed"); } } bool Trigger_p::reset_event() { Lock lock(_lock); if (!_pending_int) { return false; } EVENT_DATA_TYPE val; if (read(_event_fd, &val, sizeof(val)) != sizeof(val)) { THROW("event read error"); } _pending_int = false; return true; } void EventSources::Trigger::reset() { reset_event(); } void EventSources::Trigger::action() { if (reset_event()) { on_event(); } } static void set_non_blocking(int fd) { int flags; if ((flags = fcntl(fd, F_GETFL)) == -1) { THROW("failed to set socket non block: %s", strerror(errno)); } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { THROW("failed to set socket non block: %s", strerror(errno)); } } static void set_blocking(int fd) { int flags; if ((flags = fcntl(fd, F_GETFL)) == -1) { THROW("failed to clear socket non block: %s", strerror(errno)); } if (fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) == -1) { THROW("failed to clear socket non block: %s", strerror(errno)); } } static void add_to_poll(int fd, int epoll, EventWrapper* wrapper) { struct epoll_event event; event.data.ptr = wrapper; event.events = EPOLLIN | EPOLLOUT | EPOLLET; if (epoll_ctl(epoll, EPOLL_CTL_ADD, fd, &event) == -1) { THROW("epoll add failed"); } } void EventSources::add_socket(Socket& socket) { int fd = socket.get_socket(); set_non_blocking(fd); EventWrapper* wrapper = new EventWrapper(*this, socket); add_to_poll(fd, _epoll, wrapper); _events.push_back(wrapper); } static bool remove_event(EventSources_p::Events& events, EventSource& event) { EventSources_p::Events::iterator iter = events.begin(); for (;; iter++) { if (iter == events.end()) { return false; } if ((*iter)->get_event() == &event) { (*iter)->invalidate(); (*iter)->unref(); return true; } } } void EventSources::remove_socket(Socket& socket) { if (!remove_event(_events, socket)) { THROW("socket not found"); } int fd = socket.get_socket(); if (epoll_ctl(_epoll, EPOLL_CTL_DEL, fd, NULL) == -1) { THROW("epoll remove failed"); } set_blocking(fd); } void EventSources::add_file(File& file) { int fd = file.get_fd(); set_non_blocking(fd); EventWrapper* wrapper = new EventWrapper(*this, file); add_to_poll(fd, _epoll, wrapper); _events.push_back(wrapper); } void EventSources::remove_file(File& file) { if (!remove_event(_events, file)) { THROW("file not found"); } int fd = file.get_fd(); if (epoll_ctl(_epoll, EPOLL_CTL_DEL, fd, NULL) == -1) { THROW("epoll remove failed"); } set_blocking(fd); } void EventSources::add_handle(Handle& file) { } void EventSources::remove_handle(Handle& file) { }