kqueue.lisp - clic - Clic is an command line interactive client for gopher written in Common LISP (HTM) git clone git://bitreich.org/clic/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/clic/ (DIR) Log (DIR) Files (DIR) Refs (DIR) Tags (DIR) README (DIR) LICENSE --- kqueue.lisp (19988B) --- 1 ;;;-*-Mode: LISP; Package: CCL -*- 2 ;; 3 ;; KQUEUE.LISP 4 ;; 5 ;; KQUEUE - BSD kernel event notification mechanism support for Common LISP. 6 ;; Copyright (C) 2007 Terje Norderhaug <terje@in-progress.com> 7 ;; Released under LGPL - see <http://www.gnu.org>. 8 ;; Alternative licensing available upon request. 9 ;; 10 ;; DISCLAIMER: The user of this module should understand that executing code is a potentially hazardous 11 ;; activity, and that many dangers and obstacles, marked or unmarked, may exist within this code. 12 ;; As a condition of your use of the module, you assume all risk of personal injury, death, or property 13 ;; loss, and all other bad things that may happen, even if caused by negligence, ignorance or stupidity. 14 ;; The author is is no way responsible, and besides, does not have "deep pockets" nor any spare change. 15 ;; 16 ;; Version: 0.20 alpha (July 26, 2009) - subject to major revisions, so consider yourself warned. 17 ;; Tested with Macintosh Common LISP 5.1 and 5.2, but is intended to be platform and system independent in the future. 18 ;; 19 ;; Email feedback and improvements to <terje@in-progress.com>. 20 ;; Updated versions will be available from <http://www.in-progress.com/src/>. 21 ;; 22 ;; RELATED IMPLEMENTATIONS 23 ;; There is another kevent.lisp for other platforms by Risto Laakso (merge?). 24 ;; Also a Scheme kevent.ss by Jose Antonio Ortega. 25 ;; 26 ;; SEE ALSO: 27 ;; http://people.freebsd.org/~jlemon/papers/kqueue.pdf 28 ;; http://developer.apple.com/samplecode/FileNotification/index.html 29 ;; The Man page for kqueue() or kevent(). 30 ;; PyKQueue - Python OO interface to KQueue. 31 ;; LibEvent - an event notification library in C by Niels Provos. 32 ;; Liboop - another abstract library in C on top of kevent or other kernel notification. 33 34 #| HISTORY: 35 36 2007-Oct-18 terje version 0.1 released on the Info-MCL mailing list. 37 2008-Aug-21 terje load-framework-bundle is not needed under MCL 5.2 38 2008-Aug-21 terje rename get-addr to lookup-function-in-bundle (only for pre MCL 5.2) 39 2009-Jul-19 terje uses kevent-error condition and strerror. 40 2009-Jul-24 terje reports errors unless nil-if-not-found in lookup-function-in-bundle. 41 2009-Jul-24 terje kevent :variant for C's intptr_t type for 64bit (and osx 10.5) compatibility. 42 2009-Jul-25 terje 64bit support, dynamically determined for PPC. Kudos to Glen Foy for helping out. 43 2009-Jul-25 terje make-kevent function. 44 |# 45 46 #| IMPLEMENTATION NOTES: 47 48 kevents are copied into and from the kernel, so the records don't have to be kept in the app! 49 kevents does not work in OSX before 10.3. 50 *kevent-record* has to be explcitly set to :kevent64 to work on 64bit intel macs. 51 Consider using sysctlbyname() to test for 64bit, 52 combining hw.cpu64bit_capable, hw.optional.x86_64 and hw.optional.64bitops 53 |# 54 55 (in-package :ccl) 56 57 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 58 59 #-ccl-5.2 ; has been added to MCL 5.2 60 (defmethod load-framework-bundle ((framework-name string) &key (load-executable t)) 61 ;; FRAMWORK CALL FUNCTIONALITY FROM BSD.LISP 62 ;; (C) 2003 Brendan Burns <bburns@cs.umass.edu> 63 ;; Released under LGPL. 64 (with-cfstrs ((framework framework-name)) 65 (let ((err 0) 66 (baseURL nil) 67 (bundleURL nil) 68 (result nil)) 69 (rlet ((folder :fsref)) 70 ;; Find the folder holding the bundle 71 (setf err (#_FSFindFolder #$kOnAppropriateDisk #$kFrameworksFolderType 72 t folder)) 73 74 ;; if everything's cool, make a URL for it 75 (when (zerop err) 76 (setf baseURL (#_CFURLCreateFromFSRef (%null-ptr) folder)) 77 (if (%null-ptr-p baseURL) 78 (setf err #$coreFoundationUnknownErr))) 79 80 ;; if everything's cool, make a URL for the bundle 81 (when (zerop err) 82 (setf bundleURL (#_CFURLCreateCopyAppendingPathComponent (%null-ptr) 83 baseURL framework nil)) 84 (if (%null-ptr-p bundleURL) 85 (setf err #$coreFoundationUnknownErr))) 86 87 ;; if everything's cool, load it 88 (when (zerop err) 89 (setf result (#_CFBundleCreate (%null-ptr) bundleURL)) 90 (if (%null-ptr-p result) 91 (setf err #$coreFoundationUnknownErr))) 92 93 ;; if everything's cool, and the user wants it loaded, load it 94 (when (and load-executable (zerop err)) 95 (if (not (#_CFBundleLoadExecutable result)) 96 (setf err #$coreFoundationUnknownErr))) 97 98 ;; if there's an error, but we've got a pointer, free it and clear result 99 (when (and (not (zerop err)) (not (%null-ptr-p result))) 100 (#_CFRelease result) 101 (setf result nil)) 102 103 ;; free the URLs if there non-null 104 (when (not (%null-ptr-p bundleURL)) 105 (#_CFRelease bundleURL)) 106 (when (not (%null-ptr-p baseURL)) 107 (#_CFRelease baseURL)) 108 109 ;; return pointer + error value 110 (values result err))))) 111 112 #+ignore 113 (defun get-addr (bundle name) 114 (let* ((addr (#_CFBundleGetFunctionPointerForName bundle name))) 115 (rlet ((buf :long)) 116 (setf (%get-ptr buf) addr) 117 (ash (%get-signed-long buf) -2)))) 118 119 #-ccl-5.2 120 (defun lookup-function-in-bundle (name bundle &optional nil-if-not-found) 121 (with-cfstrs ((str name)) 122 (let* ((addr (#_CFBundleGetFunctionPointerForName bundle str))) 123 (if (%null-ptr-p addr) 124 (unless nil-if-not-found 125 (error "Couldn't resolve address of foreign function ~s" name)) 126 (rlet ((buf :long)) ;; mcl 5.2 uses %fixnum-from-macptr here 127 (setf (%get-ptr buf) addr) 128 (ash (%get-signed-long buf) -2)))))) 129 130 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 131 ;; Convenient way to declare BSD system calls 132 133 #+ignore 134 (defparameter *system-bundle* 135 #+ccl-5.2 (get-bundle-for-framework-name "System.framework") 136 #-ccl-5.2 137 (let ((bundle (load-framework-bundle "System.framework"))) 138 (terminate-when-unreachable bundle (lambda (b)(#_CFRelease b))) 139 bundle)) 140 141 (defmacro declare-bundle-ff (name name-string &rest arglist &aux (fn (gensym (format nil "ff_~A_" (string name))))) 142 ;; Is there an existing define-trap like macro for this? or could one be modified for use with bundles? 143 `(progn 144 (defloadvar ,fn 145 (let* ((bundle #+ccl-5.2 (get-bundle-for-framework-name "System.framework") 146 #-ccl-5.2 147 (let ((bundle (load-framework-bundle "System.framework"))) 148 (terminate-when-unreachable bundle (lambda (b)(#_CFRelease b))) 149 bundle))) 150 (lookup-function-in-bundle ,name-string bundle))) 151 ,(let ((args (do ((arglist arglist (cddr arglist)) 152 (result)) 153 ((not (cdr arglist)) (nreverse result)) 154 (push (second arglist) result)))) 155 `(defun ,name ,args 156 (ppc-ff-call ,fn ,@arglist))))) 157 158 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 159 160 (declare-bundle-ff %system-kqueue "kqueue" 161 :signed-fullword) ;; returns a file descriptor no! 162 163 (defun system-kqueue () 164 (let ((kq (%system-kqueue))) 165 (if (= kq -1) 166 (ecase (%system-errno) 167 (12 (error "The kernel failed to allocate enough memory for the kernel queue")) ; ENOMEM 168 (24 (error "The per-process descriptor table is full")) ; EMFILE 169 (23 (error "The system file table is full"))) ; ENFILE 170 kq))) 171 172 (declare-bundle-ff %system-kevent "kevent" 173 :unsigned-fullword kq 174 :address ke 175 :unsigned-fullword nke 176 :address ko 177 :unsigned-fullword nko 178 :address timeout 179 :signed-fullword) 180 181 (declare-bundle-ff %system-open "open" 182 :address name 183 :unsigned-fullword mode 184 :unsigned-fullword arg 185 :signed-fullword) 186 187 (declare-bundle-ff %system-close "close" 188 :unsigned-fullword fd 189 :signed-fullword) 190 191 (declare-bundle-ff %system-errno* "__error" 192 :signed-fullword) 193 194 (declare-bundle-ff %system-strerror "strerror" 195 :signed-fullword errno 196 :address) 197 198 (defun %system-errno () 199 (%get-fixnum (%int-to-ptr (%system-errno*)))) 200 201 ; (%system-errno) 202 203 (defconstant $O-EVTONLY #x8000) 204 ; (defconstant $O-NONBLOCK #x800 "Non blocking mode") 205 206 (defun system-open (posix-namestring) 207 "Low level open function, as in C, returns an fd number" 208 (with-cstrs ((name posix-namestring)) 209 (%system-open name $O-EVTONLY 0))) 210 211 (defun system-close (fd) 212 (%system-close fd)) 213 214 (defrecord timespec 215 (sec :unsigned-long) 216 (usec :unsigned-long)) 217 218 (defVar *kevent-record* nil) 219 220 (def-ccl-pointers determine-64bit-kevents () 221 (setf *kevent-record* 222 (if (ccl::gestalt #$gestaltPowerPCProcessorFeatures 223 #+ccl-5.2 #$gestaltPowerPCHas64BitSupport #-ccl-5.2 6) 224 :kevent32 225 :kevent64))) 226 227 (defrecord :kevent32 228 (ident :unsigned-long) ; uintptr_t 229 (filter :short) 230 (flags :unsigned-short) 231 (fflags :unsigned-long) 232 (data :long) ; intptr_t 233 (udata :pointer)) 234 235 (defrecord :kevent64 236 (:variant ; uintptr_t 237 ((ident64 :uint64)) 238 ((ident :unsigned-long))) 239 (filter :short) 240 (flags :unsigned-short) 241 (fflags :unsigned-long) 242 (:variant ; intptr_t 243 ((data64 :sint64)) 244 ((data :long))) 245 (:variant ; RMCL :pointer is 32bit 246 ((udata64 :uint64)) 247 ((udata :pointer)))) 248 249 (defun make-kevent (&key (ident 0) (filter 0) (flags 0) (fflags 0) (data 0) (udata *null-ptr*)) 250 (ecase *kevent-record* 251 (:kevent64 252 (make-record kevent64 253 :ident ident 254 :filter filter 255 :flags flags 256 :fflags fflags 257 :data data 258 :udata udata)) 259 (:kevent32 260 (make-record kevent32 261 :ident ident 262 :filter filter 263 :flags flags 264 :fflags fflags 265 :data data 266 :udata udata)))) 267 268 (defun kevent-rref (ke field) 269 (ecase *kevent-record* 270 (:kevent32 271 (ecase field 272 (:ident (rref ke :kevent32.ident)) 273 (:filter (rref ke :kevent32.filter)) 274 (:flags (rref ke :kevent32.flags)) 275 (:fflags (rref ke :kevent32.fflags)) 276 (:data (rref ke :kevent32.data)) 277 (:udata (rref ke :kevent32.udata)))) 278 (:kevent64 279 (ecase field 280 (:ident (rref ke :kevent64.ident)) 281 (:filter (rref ke :kevent64.filter)) 282 (:flags (rref ke :kevent64.flags)) 283 (:fflags (rref ke :kevent64.fflags)) 284 (:data (rref ke :kevent64.data)) 285 (:udata (rref ke :kevent64.udata)))))) 286 287 (defun kevent-filter (ke) 288 (kevent-rref ke :filter)) 289 290 (defun kevent-flags (ke) 291 (kevent-rref ke :flags)) 292 293 (defun kevent-data (ke) 294 (kevent-rref ke :data)) 295 296 297 ;; FILTER TYPES: 298 299 (eval-when (:compile-toplevel :load-toplevel :execute) ; added by binghe 300 301 (defconstant $kevent-read-filter -1 "Data available to read") 302 (defconstant $kevent-write-filter -2 "Writing is possible") 303 (defconstant $kevent-aio-filter -3 "AIO system call has been made") 304 (defconstant $kevent-vnode-filter -4 "Event occured on a file descriptor") 305 (defconstant $kevent-proc-filter -5 "Process performed one or more of the requested events") 306 (defconstant $kevent-signal-filter -6 "Attempted to deliver a signal to a process") 307 (defconstant $kevent-timer-filter -7 "Establishes an arbitrary timer") 308 (defconstant $kevent-netdev-filter -8 "Event occured on a network device") 309 (defconstant $kevent-filesystem-filter -9) 310 311 ) ; eval-when 312 313 ; FLAGS: 314 315 (defconstant $kevent-add #x01) 316 (defconstant $kevent-delete #x02) 317 (defconstant $kevent-enable #x04) 318 (defconstant $kevent-disable #x08) 319 (defconstant $kevent-oneshot #x10) 320 (defconstant $kevent-clear #x20) 321 (defconstant $kevent-error #x4000) 322 (defconstant $kevent-eof #x8000 "EV_EOF") 323 324 ;; FFLAGS: 325 326 (defconstant $kevent-file-delete #x01 "The file was unlinked from the file system") 327 (defconstant $kevent-file-write #x02 "A write occurred on the file") 328 (defconstant $kevent-file-extend #x04 "The file was extended") 329 (defconstant $kevent-file-attrib #x08 "The file had its attributes changed") 330 (defconstant $kevent-file-link #x10 "The link count on the file changed") 331 (defconstant $kevent-file-rename #x20 "The file was renamed") 332 (defconstant $kevent-file-revoke #x40 "Access to the file was revoked or the file system was unmounted") 333 (defconstant $kevent-file-all (logior $kevent-file-delete $kevent-file-write $kevent-file-extend 334 $kevent-file-attrib $kevent-file-link $kevent-file-rename $kevent-file-revoke)) 335 336 337 (defconstant $kevent-net-linkup #x01 "Link is up") 338 (defconstant $kevent-net-linkdown #x02 "Link is down") 339 (defconstant $kevent-net-linkinvalid #x04 "Link state is invalid") 340 (defconstant $kevent-net-added #x08 "IP adress added") 341 (defconstant $kevent-net-deleted #x10 "IP adress deleted") 342 343 (define-condition kevent-error (simple-error) 344 ((errno :initform NIL :initarg :errno) 345 (ko :initform nil :type (or null kevent) :initarg :ko) 346 (syserr :initform (%system-errno))) 347 (:report 348 (lambda (c s) 349 (with-slots (errno ko syserr) c 350 (format s "kevent system call error ~A [~A]" errno syserr) 351 (when errno 352 (format s "(~A)" (%get-cstring (%system-strerror errno)))) 353 (when ko 354 (format s " for ") 355 (let ((*standard-output* s)) 356 (print-record ko *kevent-record*))))))) 357 358 (defun %kevent (kq &optional ke ko (timeout 0)) 359 (check-type kq integer) 360 (rlet ((&timeout :timespec :sec timeout :usec 1)) 361 (let ((num (with-timer ;; does not seem to make a difference... 362 (%system-kevent kq (or ke (%null-ptr))(if ke 1 0)(or ko (%null-ptr))(if ko 1 0) &timeout)))) 363 ; "If an error occurs while processing an element of the changelist and there 364 ; is enough room in the eventlist, then the event will be placed in the eventlist with 365 ; EV_ERROR set in flags and the system error in data." 366 (when (and ko (plusp (logand $kevent-error (kevent-flags ko)))) 367 (error 'kevent-error 368 :errno (kevent-data ko) 369 :ko ko)) 370 ; "Otherwise, -1 will be returned, and errno will be set to indicate the error condition." 371 (when (= num -1) 372 ;; hack - opentransport provides the constants for the errors documented for the call 373 (case (%system-errno) 374 (0 (error "kevent system call failed with an unspecified error")) ;; should not happen! 375 (13 (error "The process does not have permission to register a filter")) 376 (14 (error "There was an error reading or writing the kevent structure")) ; EFAULT 377 (9 (error "The specified descriptor is invalid")) ; EBADF 378 (4 (error "A signal was delivered before the timeout expired and before any events were placed on the kqueue for return.")) ; EINTR 379 (22 (error "The specified time limit or filter is invalid")) ; EINVAL 380 (2 (error "The event could not be found to be modified or deleted")) ; ENOENT 381 (12 (error "No memory was available to register the event")) ; ENOMEM 382 (78 (error "The specified process to attach to does not exist"))) ; ESRCH 383 ;; shouldn't get here... 384 (errchk (%system-errno)) 385 (error "error ~A" (%system-errno))) 386 (unless (zerop num) 387 (values ko num))))) 388 389 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 390 ;; CLOS INTERFACE 391 392 (defclass kqueue () 393 ((kq :initform (system-kqueue) 394 :documentation "file descriptor referencing the kqueue") 395 (fds :initform NIL)) ;; ## better if kept on top level, perhaps as a hash table... 396 (:documentation "A kernal event notification channel")) 397 398 (defmethod initialize-instance :after ((q kqueue) &rest rest) 399 (declare (ignore rest)) 400 (terminate-when-unreachable q 'kqueue-close)) 401 402 (defmethod kqueue-close ((q kqueue)) 403 (with-slots (kq fds) q 404 (when (or kq fds) ;; allow repeated close 405 (system-close kq) 406 (setf fds NIL) 407 (setf kq NIL)))) 408 409 (defmethod kqueue-poll ((q kqueue)) 410 "Polls a kqueue for kevents" 411 ;; may not have to be cleared, but just in case: 412 (flet ((kqueue-poll2 (ko) 413 (let ((result (with-slots (kq) q 414 (without-interrupts 415 (%kevent kq NIL ko))))) 416 (when result 417 (let ((type (kevent-filter result))) 418 (ecase type 419 (0 (values)) 420 (#.$kevent-read-filter 421 (values 422 :read 423 (kevent-rref result :ident) 424 (kevent-rref result :flags) 425 (kevent-rref result :fflags) 426 (kevent-rref result :data) 427 (kevent-rref result :udata))) 428 (#.$kevent-write-filter :write) 429 (#.$kevent-aio-filter :aio) 430 (#.$kevent-vnode-filter 431 (values 432 :vnode 433 (cdr (assoc (kevent-rref result :ident) (slot-value q 'fds))) 434 (kevent-rref result :flags) 435 (kevent-rref result :fflags) 436 (kevent-rref result :data) 437 (kevent-rref result :udata))) 438 (#.$kevent-filesystem-filter :filesystem))))))) 439 (ecase *kevent-record* 440 (:kevent64 441 (rlet ((ko :kevent64 :ident 0 :filter 0 :flags 0 :fflags 0 :data 0 :udata (%null-ptr))) 442 (kqueue-poll2 ko))) 443 (:kevent32 444 (rlet ((ko :kevent32 :ident 0 :filter 0 :flags 0 :fflags 0 :data 0 :udata (%null-ptr))) 445 (kqueue-poll2 ko)))))) 446 447 (defmethod kqueue-subscribe ((q kqueue) &key ident filter (flags 0) (fflags 0) (data 0) (udata (%null-ptr))) 448 (let ((ke (make-kevent :ident ident 449 :filter filter 450 :flags flags 451 :fflags fflags 452 :data data 453 :udata udata))) 454 (with-slots (kq) q 455 (without-interrupts 456 (%kevent kq ke))))) 457 458 (defmethod kqueue-vnode-subscribe ((q kqueue) pathname) 459 "Makes the queue report an event when there is a change to a directory or file" 460 (let* ((namestring (posix-namestring (full-pathname pathname))) 461 (fd (system-open namestring))) 462 (with-slots (fds) q 463 (push (cons fd pathname) fds)) 464 (kqueue-subscribe q 465 :ident fd 466 :filter $kevent-vnode-filter 467 :flags (logior $kevent-add $kevent-clear) 468 :fflags $kevent-file-all) 469 namestring)) 470 471 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 472 473 #+test 474 (defun kevent-d (pathname &optional (*standard-output* (fred))) 475 "Report changes to a file or directory" 476 (loop 477 with kqueue = (make-instance 'kqueue) 478 with sub = (kqueue-vnode-subscribe kqueue pathname) 479 for i from 1 to 60 480 for result = (multiple-value-list (kqueue-poll kqueue)) 481 unless (equal result '(NIL)) 482 do (progn 483 (format T "~A~%" result) 484 (force-output)) 485 ; do (process-allow-schedule) 486 do (sleep 1) 487 finally (write-line "Done") 488 )) 489 490 #| 491 492 ; Report changes to this file in a fred window (save this document to see what happens): 493 494 (process-run-function "kevent-d" #'kevent-d *loading-file-source-file* 495 (fred)) 496 497 ; Reports files added or removed from the directory of this file: 498 499 (process-run-function "kevent-d" #'kevent-d 500 (make-pathname :directory (pathname-directory *loading-file-source-file*)) 501 (fred)) 502 |# 503 504 505 506