]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_utilities/lib/selector.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_utilities / lib / selector.cpp
diff --git a/libsecurity_utilities/lib/selector.cpp b/libsecurity_utilities/lib/selector.cpp
new file mode 100644 (file)
index 0000000..d636ac7
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2000-2001,2003-2004 Apple Computer, Inc. All Rights Reserved.
+ * 
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+
+//
+// selector - I/O stream multiplexing
+//
+#include "selector.h"
+#include <security_utilities/errors.h>
+#include <security_utilities/debugging.h>
+#include <algorithm>   // min/max
+
+
+namespace Security {
+namespace UnixPlusPlus {
+
+
+//
+// construct a Selector object.
+//
+Selector::Selector() : fdMin(INT_MAX), fdMax(-1)
+{
+    // initially allocate room for FD_SETSIZE file descriptors (usually good enough)
+    fdSetSize = FD_SETSIZE / NFDBITS;
+    inSet.grow(0, fdSetSize);
+    outSet.grow(0, fdSetSize);
+    errSet.grow(0, fdSetSize);
+}
+
+Selector::~Selector()
+{ }
+
+
+//
+// Add a Client to a Selector
+//
+void Selector::add(int fd, Client &client, Type type)
+{
+    // plausibility checks
+    assert(!client.isActive());                // one Selector per client, and no re-adding
+    assert(fd >= 0);
+    
+    secdebug("selector", "add client %p fd %d type=%d", &client, fd, type);
+    
+    // grow FDSets if needed
+    unsigned int pos = fd / NFDBITS;
+    if (pos >= fdSetSize) {
+        int newSize = (fd - 1) / NFDBITS + 2;  // as much as needed + 1 spare word
+        inSet.grow(fdSetSize, newSize);
+        outSet.grow(fdSetSize, newSize);
+        errSet.grow(fdSetSize, newSize);
+    }
+    
+    // adjust boundaries
+    if (fd < fdMin)
+        fdMin = fd;
+    if (fd > fdMax)
+        fdMax = fd;
+
+    // add client
+    Client * &slot = clientMap[fd];
+    assert(!slot);
+    slot = &client;
+    client.mFd = fd;
+    client.mSelector = this;
+    client.mEvents = type;
+    set(fd, type);
+}    
+
+
+//
+// Remove a Client from a Selector
+//
+void Selector::remove(int fd)
+{
+    // sanity checks
+    assert(fd >= 0);
+    ClientMap::iterator it = clientMap.find(fd);
+    assert(it != clientMap.end());
+    assert(it->second->mSelector == this);
+
+    secdebug("selector", "remove client %p fd %d", it->second, fd);
+
+    // remove from FDSets
+    set(fd, none);
+    
+    // remove client
+    it->second->mSelector = NULL;
+    clientMap.erase(it);
+    
+    // recompute fdMin/fdMax if needed
+    if (isEmpty()) {
+        fdMin = INT_MAX;
+        fdMax = -1;
+    } else if (fd == fdMin) {
+        fdMin = clientMap.begin()->first;
+    } else if (fd == fdMax) {
+        fdMax = clientMap.rbegin()->first;
+    }
+}
+
+
+//
+// Adjust the FDSets for a single given Client according to a new event Type mask.
+//
+void Selector::set(int fd, Type type)
+{
+    assert(fd >= 0);
+    inSet.set(fd, type & input);
+    outSet.set(fd, type & output);
+    errSet.set(fd, type & critical);
+    secdebug("selector", "fd %d notifications 0x%x", fd, type);
+}
+
+
+void Selector::operator () ()
+{
+    if (!clientMap.empty())
+        singleStep(0);
+}
+
+
+void Selector::operator () (Time::Absolute stopTime)
+{
+    if (!clientMap.empty())
+        singleStep(stopTime - Time::now());
+}
+
+
+//
+// Perform a single pass through the Selector and notify all clients
+// that have selected I/O pending at this time.
+// There is not time limit on how long this may take; if the clients
+// are well written, it won't be too long.
+//
+void Selector::singleStep(Time::Interval maxWait)
+{
+    assert(!clientMap.empty());
+    secdebug("selector", "select(%d) [%d-%d] for %ld clients",
+        fdMax + 1, fdMin, fdMax, clientMap.size());
+    for (;;) { // pseudo-loop - only retries
+        struct timeval duration = maxWait.timevalInterval();
+#if defined(__APPLE__)
+        // ad-hoc fix: MacOS X's BSD rejects times of more than 100E6 seconds
+        if (duration.tv_sec > 100000000)
+            duration.tv_sec = 100000000;
+#endif
+        const int size = FDSet::words(fdMax);          // number of active words in sets
+        switch (int hits = ::select(fdMax + 1,
+                inSet.make(size), outSet.make(size), errSet.make(size),
+                &duration)) {
+        case -1:               // error
+            if (errno == EINTR)
+                continue;
+            secdebug("selector", "select failed: errno=%d", errno);
+            UnixError::throwMe();
+        case 0:                        // no events
+            secdebug("selector", "select returned nothing");
+            return;
+        default:               // some events
+            secdebug("selector", "%d pending descriptors", hits);
+            //@@@ This could be optimized as a word-merge scan.
+            //@@@ The typical case doesn't benefit from this though, though browsers might
+            //@@@ and integrated servers definitely would.
+            for (int fd = fdMin; fd <= fdMax && hits > 0; fd++) {
+                int types = 0;
+                if (inSet[fd])  types |= input;
+                if (outSet[fd]) types |= output;
+                if (errSet[fd]) types |= critical;
+                if (types) {
+                    secdebug("selector", "notify fd %d client %p type %d",
+                        fd, clientMap[fd], types);
+                    clientMap[fd]->notify(fd, types);
+                    hits--;
+                }
+            }
+            return;
+        }
+    }
+}
+
+
+}      // end namespace IPPlusPlus
+}      // end namespace Security