1/*
2 * Copyright (c) 2000-2001,2003-2004 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25//
26// selector - I/O stream multiplexing
27//
28#include "selector.h"
29#include <security_utilities/errors.h>
30#include <security_utilities/debugging.h>
31#include <algorithm>	// min/max
32
33
34namespace Security {
35namespace UnixPlusPlus {
36
37
38//
39// construct a Selector object.
40//
41Selector::Selector() : fdMin(INT_MAX), fdMax(-1)
42{
43    // initially allocate room for FD_SETSIZE file descriptors (usually good enough)
44    fdSetSize = FD_SETSIZE / NFDBITS;
45    inSet.grow(0, fdSetSize);
46    outSet.grow(0, fdSetSize);
47    errSet.grow(0, fdSetSize);
48}
49
50Selector::~Selector()
51{ }
52
53
54//
55// Add a Client to a Selector
56//
57void Selector::add(int fd, Client &client, Type type)
58{
59    // plausibility checks
60    assert(!client.isActive());		// one Selector per client, and no re-adding
61    assert(fd >= 0);
62
63    secdebug("selector", "add client %p fd %d type=%d", &client, fd, type);
64
65    // grow FDSets if needed
66    unsigned int pos = fd / NFDBITS;
67    if (pos >= fdSetSize) {
68        int newSize = (fd - 1) / NFDBITS + 2;	// as much as needed + 1 spare word
69        inSet.grow(fdSetSize, newSize);
70        outSet.grow(fdSetSize, newSize);
71        errSet.grow(fdSetSize, newSize);
72    }
73
74    // adjust boundaries
75    if (fd < fdMin)
76        fdMin = fd;
77    if (fd > fdMax)
78        fdMax = fd;
79
80    // add client
81    Client * &slot = clientMap[fd];
82    assert(!slot);
83    slot = &client;
84    client.mFd = fd;
85    client.mSelector = this;
86    client.mEvents = type;
87    set(fd, type);
88}
89
90
91//
92// Remove a Client from a Selector
93//
94void Selector::remove(int fd)
95{
96    // sanity checks
97    assert(fd >= 0);
98    ClientMap::iterator it = clientMap.find(fd);
99    assert(it != clientMap.end());
100    assert(it->second->mSelector == this);
101
102    secdebug("selector", "remove client %p fd %d", it->second, fd);
103
104    // remove from FDSets
105    set(fd, none);
106
107    // remove client
108    it->second->mSelector = NULL;
109    clientMap.erase(it);
110
111    // recompute fdMin/fdMax if needed
112    if (isEmpty()) {
113        fdMin = INT_MAX;
114        fdMax = -1;
115    } else if (fd == fdMin) {
116        fdMin = clientMap.begin()->first;
117    } else if (fd == fdMax) {
118        fdMax = clientMap.rbegin()->first;
119    }
120}
121
122
123//
124// Adjust the FDSets for a single given Client according to a new event Type mask.
125//
126void Selector::set(int fd, Type type)
127{
128    assert(fd >= 0);
129    inSet.set(fd, type & input);
130    outSet.set(fd, type & output);
131    errSet.set(fd, type & critical);
132    secdebug("selector", "fd %d notifications 0x%x", fd, type);
133}
134
135
136void Selector::operator () ()
137{
138    if (!clientMap.empty())
139        singleStep(0);
140}
141
142
143void Selector::operator () (Time::Absolute stopTime)
144{
145    if (!clientMap.empty())
146        singleStep(stopTime - Time::now());
147}
148
149
150//
151// Perform a single pass through the Selector and notify all clients
152// that have selected I/O pending at this time.
153// There is not time limit on how long this may take; if the clients
154// are well written, it won't be too long.
155//
156void Selector::singleStep(Time::Interval maxWait)
157{
158    assert(!clientMap.empty());
159    secdebug("selector", "select(%d) [%d-%d] for %ld clients",
160        fdMax + 1, fdMin, fdMax, clientMap.size());
161    for (;;) {	// pseudo-loop - only retries
162        struct timeval duration = maxWait.timevalInterval();
163#if defined(__APPLE__)
164        // ad-hoc fix: MacOS X's BSD rejects times of more than 100E6 seconds
165        if (duration.tv_sec > 100000000)
166            duration.tv_sec = 100000000;
167#endif
168        const int size = FDSet::words(fdMax);		// number of active words in sets
169        switch (int hits = ::select(fdMax + 1,
170                inSet.make(size), outSet.make(size), errSet.make(size),
171                &duration)) {
172        case -1:		// error
173            if (errno == EINTR)
174                continue;
175            secdebug("selector", "select failed: errno=%d", errno);
176            UnixError::throwMe();
177        case 0:			// no events
178            secdebug("selector", "select returned nothing");
179            return;
180        default:		// some events
181            secdebug("selector", "%d pending descriptors", hits);
182            //@@@ This could be optimized as a word-merge scan.
183            //@@@ The typical case doesn't benefit from this though, though browsers might
184            //@@@ and integrated servers definitely would.
185            for (int fd = fdMin; fd <= fdMax && hits > 0; fd++) {
186                int types = 0;
187                if (inSet[fd])  types |= input;
188                if (outSet[fd]) types |= output;
189                if (errSet[fd]) types |= critical;
190                if (types) {
191                    secdebug("selector", "notify fd %d client %p type %d",
192                        fd, clientMap[fd], types);
193                    clientMap[fd]->notify(fd, types);
194                    hits--;
195                }
196            }
197            return;
198        }
199    }
200}
201
202
203}	// end namespace IPPlusPlus
204}	// end namespace Security
205