diff --git a/cffi-unistd.lisp b/cffi-unistd.lisp
index 8083cf7..5f58f85 100644
--- a/cffi-unistd.lisp
+++ b/cffi-unistd.lisp
@@ -121,3 +121,108 @@ or number of bytes written otherwise."
(when (< r 0)
(error-errno "dup"))
r))
+
+;; Select
+
+(defcstruct fd-set
+ (fds-bits fd-mask :count #.(/ +fd-setsize+ +nfdbits+)))
+
+(defun fd-elt (fd)
+ (/ fd +nfdbits+))
+
+(defun fd-mask (fd)
+ (ash 1 (mod fd +nfdbits+)))
+
+(defmacro fds-bits (fd fd-set)
+ `(mem-aref (foreign-slot-value ,fd-set '(:struct fd-set)
+ 'fds-bits)
+ 'fd-mask (fd-elt ,fd)))
+
+(defun fd-clr (fd fd-set)
+ (setf (fds-bits fd fd-set)
+ (logand (fds-bits fd fd-set) (lognot (fd-mask fd)))))
+
+(defun fd-isset (fd fd-set)
+ (not (zerop (logand (fds-bits fd fd-set) (fd-mask fd)))))
+
+(defun fd-set (fd fd-set)
+ (setf (fds-bits fd fd-set)
+ (logior (fds-bits fd fd-set) (fd-mask fd))))
+
+(defun fd-zero (fd-set)
+ (dotimes (i (/ (foreign-type-size 'fd-set) (foreign-type-size 'fd-mask)))
+ (setf (mem-aref (foreign-slot-value fd-set '(:struct fd-set) 'fds-bits)
+ 'fd-mask i)
+ 0)))
+
+(defcstruct timeval
+ (tv-sec :long)
+ (tv-usec :long))
+
+(defcfun ("select" c-select) :int
+ (nfds :int)
+ (readfds (:pointer (:struct fd-set)))
+ (writefds (:pointer (:struct fd-set)))
+ (exceptfds (:pointer (:struct fd-set)))
+ (timeout (:pointer (:struct timeval))))
+
+(defun select (nfds readfds writefds exceptfds timeout)
+ (let ((r (c-select nfds readfds writefds exceptfds timeout)))
+ (when (< r 0)
+ (error-errno "select"))
+ r))
+
+(defun list-to-fd-set (list fd-set)
+ (fd-zero fd-set)
+ (dolist (fd list)
+ (fd-set fd fd-set)))
+
+(defun seconds-to-timeval (seconds tv)
+ (let ((sec (floor seconds)))
+ (setf (foreign-slot-value tv '(:struct timeval) 'tv-sec)
+ sec
+ (foreign-slot-value tv '(:struct timeval) 'tv-sec)
+ (floor (* (- seconds sec) 1000000)))))
+
+(defun fd-set-filter (list fd-set)
+ (let ((result ()))
+ (dolist (fd list)
+ (when (fd-isset fd fd-set)
+ (push fd result)))
+ result))
+
+(defmacro with-selected ((readfds &optional writefds exceptfds timeout)
+ (readfds-var writefds-var exceptfds-var)
+ &body body)
+ (let ((g-readfds (gensym "R-"))
+ (g-writefds (gensym "W-"))
+ (g-exceptfds (gensym "E-"))
+ (nfds (gensym "N-"))
+ (fd-set-read (gensym "FDS-R-"))
+ (fd-set-write (gensym "FDS-W-"))
+ (fd-set-except (gensym "FDS-E-"))
+ (tv (gensym "TV-")))
+ `(let ((,g-readfds ,readfds)
+ (,g-writefds ,writefds)
+ (,g-exceptfds ,exceptfds)
+ (,nfds 0))
+ (cffi:with-foreign-objects ((,fd-set-read (:struct fd-set))
+ (,fd-set-write (:struct fd-set))
+ (,fd-set-except (:struct fd-set))
+ (,tv (:struct timeval)))
+ (list-to-fd-set ,g-readfds ,fd-set-read)
+ (list-to-fd-set ,g-writefds ,fd-set-write)
+ (list-to-fd-set ,g-exceptfds ,fd-set-except)
+ (seconds-to-timeval ,timeout ,tv)
+ (dolist (fd ,g-readfds)
+ (when (< ,nfds fd) (setq ,nfds fd)))
+ (dolist (fd ,g-writefds)
+ (when (< ,nfds fd) (setq ,nfds fd)))
+ (dolist (fd ,g-exceptfds)
+ (when (< ,nfds fd) (setq ,nfds fd)))
+ (incf ,nfds)
+ (select ,nfds ,fd-set-read ,fd-set-write ,fd-set-except ,tv)
+ (let ((,readfds-var (fd-set-filter ,g-readfds ,fd-set-read))
+ (,writefds-var (fd-set-filter ,g-writefds ,fd-set-write))
+ (,exceptfds-var (fd-set-filter ,g-exceptfds ,fd-set-except)))
+ ,@body)))))
diff --git a/grovel-unistd.lisp b/grovel-unistd.lisp
index 5c2c8fb..1bc5657 100644
--- a/grovel-unistd.lisp
+++ b/grovel-unistd.lisp
@@ -45,3 +45,7 @@
(constant (+seek-end+ "SEEK_END"))
(constant (+seek-data+ "SEEK_DATA"))
(constant (+seek-hole+ "SEEK_HOLE"))
+
+(ctype fd-mask "fd_mask")
+(constant (+fd-setsize+ "FD_SETSIZE"))
+(constant (+nfdbits+ "NFDBITS"))
diff --git a/package.lisp b/package.lisp
index 9b54c96..fea0423 100644
--- a/package.lisp
+++ b/package.lisp
@@ -44,4 +44,22 @@
#:c-pipe
#:pipe
#:with-pipe
- #:dup))
+ #:dup
+ #:fd-set
+ #:fds-bits
+ #:fd-mask
+ #:+fd-setsize+
+ #:+nfdbits+
+ #:fd-elt
+ #:fd-clr
+ #:fd-isset
+ #:fd-set
+ #:fd-zero
+ #:timeval
+ #:tv-sec
+ #:tv-usec
+ #:select
+ #:list-to-fd-set
+ #:seconds-to-timeval
+ #:fd-set-filter
+ #:with-selected))