├── LICENSE.html ├── README.md ├── project.clj ├── src └── net │ └── n01se │ ├── clojure_jna.clj │ └── clojure_jna │ ├── libc_utils.clj │ └── linux.clj └── test └── net └── n01se └── clojure_jna └── test.clj /LICENSE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Eclipse Public License - Version 1.0 8 | 25 | 26 | 27 | 28 | 29 | 30 |

Eclipse Public License - v 1.0

31 | 32 |

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR 34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS 35 | AGREEMENT.

36 | 37 |

1. DEFINITIONS

38 | 39 |

"Contribution" means:

40 | 41 |

a) in the case of the initial Contributor, the initial 42 | code and documentation distributed under this Agreement, and

43 |

b) in the case of each subsequent Contributor:

44 |

i) changes to the Program, and

45 |

ii) additions to the Program;

46 |

where such changes and/or additions to the Program 47 | originate from and are distributed by that particular Contributor. A 48 | Contribution 'originates' from a Contributor if it was added to the 49 | Program by such Contributor itself or anyone acting on such 50 | Contributor's behalf. Contributions do not include additions to the 51 | Program which: (i) are separate modules of software distributed in 52 | conjunction with the Program under their own license agreement, and (ii) 53 | are not derivative works of the Program.

54 | 55 |

"Contributor" means any person or entity that distributes 56 | the Program.

57 | 58 |

"Licensed Patents" mean patent claims licensable by a 59 | Contributor which are necessarily infringed by the use or sale of its 60 | Contribution alone or when combined with the Program.

61 | 62 |

"Program" means the Contributions distributed in accordance 63 | with this Agreement.

64 | 65 |

"Recipient" means anyone who receives the Program under 66 | this Agreement, including all Contributors.

67 | 68 |

2. GRANT OF RIGHTS

69 | 70 |

a) Subject to the terms of this Agreement, each 71 | Contributor hereby grants Recipient a non-exclusive, worldwide, 72 | royalty-free copyright license to reproduce, prepare derivative works 73 | of, publicly display, publicly perform, distribute and sublicense the 74 | Contribution of such Contributor, if any, and such derivative works, in 75 | source code and object code form.

76 | 77 |

b) Subject to the terms of this Agreement, each 78 | Contributor hereby grants Recipient a non-exclusive, worldwide, 79 | royalty-free patent license under Licensed Patents to make, use, sell, 80 | offer to sell, import and otherwise transfer the Contribution of such 81 | Contributor, if any, in source code and object code form. This patent 82 | license shall apply to the combination of the Contribution and the 83 | Program if, at the time the Contribution is added by the Contributor, 84 | such addition of the Contribution causes such combination to be covered 85 | by the Licensed Patents. The patent license shall not apply to any other 86 | combinations which include the Contribution. No hardware per se is 87 | licensed hereunder.

88 | 89 |

c) Recipient understands that although each Contributor 90 | grants the licenses to its Contributions set forth herein, no assurances 91 | are provided by any Contributor that the Program does not infringe the 92 | patent or other intellectual property rights of any other entity. Each 93 | Contributor disclaims any liability to Recipient for claims brought by 94 | any other entity based on infringement of intellectual property rights 95 | or otherwise. As a condition to exercising the rights and licenses 96 | granted hereunder, each Recipient hereby assumes sole responsibility to 97 | secure any other intellectual property rights needed, if any. For 98 | example, if a third party patent license is required to allow Recipient 99 | to distribute the Program, it is Recipient's responsibility to acquire 100 | that license before distributing the Program.

101 | 102 |

d) Each Contributor represents that to its knowledge it 103 | has sufficient copyright rights in its Contribution, if any, to grant 104 | the copyright license set forth in this Agreement.

105 | 106 |

3. REQUIREMENTS

107 | 108 |

A Contributor may choose to distribute the Program in object code 109 | form under its own license agreement, provided that:

110 | 111 |

a) it complies with the terms and conditions of this 112 | Agreement; and

113 | 114 |

b) its license agreement:

115 | 116 |

i) effectively disclaims on behalf of all Contributors 117 | all warranties and conditions, express and implied, including warranties 118 | or conditions of title and non-infringement, and implied warranties or 119 | conditions of merchantability and fitness for a particular purpose;

120 | 121 |

ii) effectively excludes on behalf of all Contributors 122 | all liability for damages, including direct, indirect, special, 123 | incidental and consequential damages, such as lost profits;

124 | 125 |

iii) states that any provisions which differ from this 126 | Agreement are offered by that Contributor alone and not by any other 127 | party; and

128 | 129 |

iv) states that source code for the Program is available 130 | from such Contributor, and informs licensees how to obtain it in a 131 | reasonable manner on or through a medium customarily used for software 132 | exchange.

133 | 134 |

When the Program is made available in source code form:

135 | 136 |

a) it must be made available under this Agreement; and

137 | 138 |

b) a copy of this Agreement must be included with each 139 | copy of the Program.

140 | 141 |

Contributors may not remove or alter any copyright notices contained 142 | within the Program.

143 | 144 |

Each Contributor must identify itself as the originator of its 145 | Contribution, if any, in a manner that reasonably allows subsequent 146 | Recipients to identify the originator of the Contribution.

147 | 148 |

4. COMMERCIAL DISTRIBUTION

149 | 150 |

Commercial distributors of software may accept certain 151 | responsibilities with respect to end users, business partners and the 152 | like. While this license is intended to facilitate the commercial use of 153 | the Program, the Contributor who includes the Program in a commercial 154 | product offering should do so in a manner which does not create 155 | potential liability for other Contributors. Therefore, if a Contributor 156 | includes the Program in a commercial product offering, such Contributor 157 | ("Commercial Contributor") hereby agrees to defend and 158 | indemnify every other Contributor ("Indemnified Contributor") 159 | against any losses, damages and costs (collectively "Losses") 160 | arising from claims, lawsuits and other legal actions brought by a third 161 | party against the Indemnified Contributor to the extent caused by the 162 | acts or omissions of such Commercial Contributor in connection with its 163 | distribution of the Program in a commercial product offering. The 164 | obligations in this section do not apply to any claims or Losses 165 | relating to any actual or alleged intellectual property infringement. In 166 | order to qualify, an Indemnified Contributor must: a) promptly notify 167 | the Commercial Contributor in writing of such claim, and b) allow the 168 | Commercial Contributor to control, and cooperate with the Commercial 169 | Contributor in, the defense and any related settlement negotiations. The 170 | Indemnified Contributor may participate in any such claim at its own 171 | expense.

172 | 173 |

For example, a Contributor might include the Program in a commercial 174 | product offering, Product X. That Contributor is then a Commercial 175 | Contributor. If that Commercial Contributor then makes performance 176 | claims, or offers warranties related to Product X, those performance 177 | claims and warranties are such Commercial Contributor's responsibility 178 | alone. Under this section, the Commercial Contributor would have to 179 | defend claims against the other Contributors related to those 180 | performance claims and warranties, and if a court requires any other 181 | Contributor to pay any damages as a result, the Commercial Contributor 182 | must pay those damages.

183 | 184 |

5. NO WARRANTY

185 | 186 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, 189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 191 | responsible for determining the appropriateness of using and 192 | distributing the Program and assumes all risks associated with its 193 | exercise of rights under this Agreement , including but not limited to 194 | the risks and costs of program errors, compliance with applicable laws, 195 | damage to or loss of data, programs or equipment, and unavailability or 196 | interruption of operations.

197 | 198 |

6. DISCLAIMER OF LIABILITY

199 | 200 |

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT 201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

208 | 209 |

7. GENERAL

210 | 211 |

If any provision of this Agreement is invalid or unenforceable under 212 | applicable law, it shall not affect the validity or enforceability of 213 | the remainder of the terms of this Agreement, and without further action 214 | by the parties hereto, such provision shall be reformed to the minimum 215 | extent necessary to make such provision valid and enforceable.

216 | 217 |

If Recipient institutes patent litigation against any entity 218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 219 | Program itself (excluding combinations of the Program with other 220 | software or hardware) infringes such Recipient's patent(s), then such 221 | Recipient's rights granted under Section 2(b) shall terminate as of the 222 | date such litigation is filed.

223 | 224 |

All Recipient's rights under this Agreement shall terminate if it 225 | fails to comply with any of the material terms or conditions of this 226 | Agreement and does not cure such failure in a reasonable period of time 227 | after becoming aware of such noncompliance. If all Recipient's rights 228 | under this Agreement terminate, Recipient agrees to cease use and 229 | distribution of the Program as soon as reasonably practicable. However, 230 | Recipient's obligations under this Agreement and any licenses granted by 231 | Recipient relating to the Program shall continue and survive.

232 | 233 |

Everyone is permitted to copy and distribute copies of this 234 | Agreement, but in order to avoid inconsistency the Agreement is 235 | copyrighted and may only be modified in the following manner. The 236 | Agreement Steward reserves the right to publish new versions (including 237 | revisions) of this Agreement from time to time. No one other than the 238 | Agreement Steward has the right to modify this Agreement. The Eclipse 239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may 240 | assign the responsibility to serve as the Agreement Steward to a 241 | suitable separate entity. Each new version of the Agreement will be 242 | given a distinguishing version number. The Program (including 243 | Contributions) may always be distributed subject to the version of the 244 | Agreement under which it was received. In addition, after a new version 245 | of the Agreement is published, Contributor may elect to distribute the 246 | Program (including its Contributions) under the new version. Except as 247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no 248 | rights or licenses to the intellectual property of any Contributor under 249 | this Agreement, whether expressly, by implication, estoppel or 250 | otherwise. All rights in the Program not expressly granted under this 251 | Agreement are reserved.

252 | 253 |

This Agreement is governed by the laws of the State of New York and 254 | the intellectual property laws of the United States of America. No party 255 | to this Agreement will bring a legal action under this Agreement more 256 | than one year after the cause of action arose. Each party waives its 257 | rights to a jury trial in any resulting litigation.

258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Dynamically load and use native C libs from Clojure using JNA** 2 | 3 | clojure-jna is available from clojars. Just add this to your Leiningen 4 | `project.clj` in the `:dependencies` section: 5 | 6 | ```clojure 7 | [net.n01se/clojure-jna "1.0.0"] 8 | ``` 9 | === 10 | Usage 11 | === 12 | 13 | ```clojure 14 | (require '[net.n01se.clojure-jna :as jna]) 15 | (jna/invoke Integer c/printf "My number: %d\n" 5) 16 | ; My number: 5 17 | ;=> 13 18 | ``` 19 | 20 | The first argument to jna-invoke is the native function's return value. The 21 | second is a symbol, in this case c/printf. The `c` part is the name of the 22 | library, in this case `libc`. The `printf` part is of course the name of the 23 | function to call. The rest are arguments to the native function. 24 | 25 | That 13 is `printf`s return value -- I guess it's the number of bytes printed or 26 | something? Anyway, feel free to ignore it just like all C programs do. 27 | 28 | If you're going to be calling the same function a few times, you might find it 29 | convenient to be able to call it like a regular Clojure function. Use jna-fn 30 | for that: 31 | 32 | ```clojure 33 | (doc jna/to-fn) 34 | ; ------------------------- 35 | ; net.n01se.clojure-jna/to-fn 36 | ; ([return-type function-symbol]) 37 | ; Macro 38 | ; Return a Clojure function that wraps a native library function: 39 | ; (def c-printf (jna/to-fn Integer c/printf)) 40 | ; (c-printf "My number: %d\n" 5) 41 | 42 | (def c-printf (jna/to-fn Integer c/printf)) 43 | 44 | (c-printf "My number: %d\n" 5) 45 | ; My number: 5 46 | ;=> 13 47 | 48 | (c-printf "My number: %d\n" 10) 49 | ; My number: 10 50 | ;=> 14 51 | ``` 52 | 53 | If you're going to be calling a bunch of functions from the same native lib, you 54 | might like to use jna-ns to create a Clojure namespace full of Clojure functions 55 | that wrap the native functions: 56 | 57 | ```clojure 58 | (doc jna/to-ns) 59 | ; ------------------------- 60 | ; net.n01se.clojure-jna/to-ns 61 | ; ([new-ns libname fnspecs]) 62 | ; Macro 63 | ; Create a namespace full of Clojure functions that wrap functions from 64 | ; a native library: 65 | ; (jna/to-ns native-c c [Integer printf, Integer open, Integer close]) 66 | ; (native-c/printf "one %s two\n" "hello") 67 | 68 | (jna/to-ns native-c c [Integer printf, Integer open, Integer close]) 69 | ;=> # 70 | 71 | (native-c/printf "one %s two\n" "hello") 72 | ; one hello two 73 | ;=> 14 74 | 75 | (native-c/open "README") 76 | ;=> -1 77 | ``` 78 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject net.n01se/clojure-jna "1.1.0-SNAPSHOT" 2 | :description "Access native libraries from Clojure" 3 | :url "http://github.com/Chouser/clojure-jna/" 4 | :scm {:name "git" 5 | :url "https://github.com/Chouser/clojure-jna/"} 6 | :dependencies [[net.java.dev.jna/jna "4.0.0"]] 7 | :license {:name "Eclipse Public License - v 1.0" 8 | :url "http://www.eclipse.org/legal/epl-v10.html" 9 | :distribution :repo 10 | :comments "same as Clojure"} 11 | :min-lein-version "2.0.0" 12 | :profiles {:1.2 {:dependencies [[org.clojure/clojure "1.2.0"]]} 13 | :1.3 {:dependencies [[org.clojure/clojure "1.3.0"]]} 14 | :1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]} 15 | :1.5 {:dependencies [[org.clojure/clojure "1.5.0"]]}} 16 | :aliases {"1.2" ["with-profile" "1.2"] 17 | "1.3" ["with-profile" "1.3"] 18 | "1.4" ["with-profile" "1.4"] 19 | "1.5" ["with-profile" "1.5"]}) 20 | -------------------------------------------------------------------------------- /src/net/n01se/clojure_jna.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Chris Houser, May 2009. All rights reserved. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns net.n01se.clojure-jna 10 | "Dynamically load and use native C libs from Clojure using JNA" 11 | {:author "Chris Houser"} 12 | (:import (com.sun.jna Native))) 13 | 14 | (defn- get-function [s] 15 | `(com.sun.jna.Function/getFunction ~(namespace s) ~(name s))) 16 | 17 | (defmacro invoke 18 | "Call a native library function: 19 | (jna/invoke Integer c/printf \"My number: %d\\n\" 5)" 20 | [return-type function-symbol & args] 21 | `(.invoke ~(get-function function-symbol) ~return-type (to-array [~@args]))) 22 | 23 | (defmacro to-fn 24 | "Return a Clojure function that wraps a native library function: 25 | (def c-printf (jna/to-fn Integer c/printf)) 26 | (c-printf \"My number: %d\\n\" 5)" 27 | [return-type function-symbol] 28 | `(let [func# ~(get-function function-symbol)] 29 | (fn [& args#] 30 | (.invoke func# ~return-type (to-array args#))))) 31 | 32 | (defmacro to-ns 33 | "Create a namespace full of Clojure functions that wrap functions from 34 | a native library: 35 | (jna/to-ns native-c c [Integer printf, Integer open, Integer close]) 36 | (native-c/printf \"one %s two\\n\" \"hello\")" 37 | [new-ns libname fnspecs] 38 | `(do 39 | (create-ns '~new-ns) 40 | ~@(for [[return-type fn-name] (partition 2 fnspecs)] 41 | `(intern '~new-ns '~fn-name 42 | (to-fn ~return-type ~(symbol (str libname) (str fn-name))))) 43 | (the-ns '~new-ns))) 44 | 45 | (defn make-cbuf 46 | "Create a direct ByteBuffer of the given size with little-endian 47 | byte order. This is useful for creating structs to pass to 48 | native functions. See also 'pointer'" 49 | [size] 50 | (-> (java.nio.ByteBuffer/allocateDirect size) 51 | (.order java.nio.ByteOrder/LITTLE_ENDIAN))) 52 | 53 | (defn pointer 54 | "Pass in a ByteBuffer (such as created by make-cbuf) and this will 55 | return a JNA Pointer that can be passed directly to JNA-wrapped 56 | native functions." 57 | [direct-buffer] 58 | (when direct-buffer 59 | (Native/getDirectBufferPointer direct-buffer))) 60 | 61 | (defn when-err 62 | "If value is negative one (-1), throws an excpetion with the given 63 | msg and the current errno. Otherwise returns value." 64 | [value msg] 65 | (if (== -1 value) 66 | (throw (Exception. (str msg ", errno: " (Native/getLastError)))) 67 | value)) 68 | -------------------------------------------------------------------------------- /src/net/n01se/clojure_jna/libc_utils.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Chris Houser, May 2009. All rights reserved. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns net.n01se.clojure-jna.libc-utils 10 | "Convenience wrappers for libc functions. Currently just 'select'" 11 | {:author "Chris Houser"} 12 | (:require [net.n01se.clojure-jna :as jna :refer [make-cbuf pointer when-err]])) 13 | 14 | (jna/to-ns libc c [Integer select]) 15 | 16 | (defn select 17 | "Block for timeout-secs waiting for events on the given file 18 | descriptors. Each of readfds, writefds, and exceptfds must be 19 | a collection of zero or more file descriptors (that is, integers), 20 | or nil. Returns a vector of three sets indicating the file 21 | descriptors that have waiting events." 22 | [readfds & [writefds exceptfds timeout-secs]] 23 | (let [FD_SETSIZE 1024 24 | bytes-per-int 4 25 | bits-per-byte 8 26 | FD_NFDBITS (* bytes-per-int bits-per-byte) 27 | howmany (quot (+ FD_SETSIZE FD_NFDBITS -1) FD_NFDBITS) 28 | fd-bytes (* howmany bytes-per-int) 29 | 30 | set-to-buf (fn [fds] 31 | (when (seq fds) 32 | (let [set-buf (.asIntBuffer (make-cbuf fd-bytes))] 33 | (doseq [fd fds] 34 | (let [index (quot fd FD_NFDBITS) 35 | offset (rem fd FD_NFDBITS)] 36 | (.put set-buf index 37 | (bit-or (.get set-buf index) 38 | (bit-shift-left 1 offset))))) 39 | set-buf))) 40 | 41 | buf-to-set (fn [fdset fdbuf] 42 | (set (remove 43 | #(let [index (quot % FD_NFDBITS) 44 | offset (rem % FD_NFDBITS)] 45 | (zero? (bit-and (.get fdbuf index) 46 | (bit-shift-left 1 offset)))) 47 | fdset))) 48 | 49 | readfds-buf (set-to-buf readfds) 50 | writefds-buf (set-to-buf writefds) 51 | exceptfds-buf (set-to-buf exceptfds) 52 | 53 | timeval (when timeout-secs 54 | (pointer 55 | (-> (make-cbuf 16) 56 | (.putLong (long timeout-secs)) 57 | (.putLong (long (* (rem timeout-secs 1) 1000000))))))] 58 | (when-err (libc/select 59 | (inc (apply max (concat readfds writefds exceptfds))) 60 | (pointer readfds-buf) 61 | (pointer writefds-buf) 62 | (pointer exceptfds-buf) 63 | timeval) 64 | "Error in select") 65 | 66 | [(buf-to-set readfds readfds-buf) 67 | (buf-to-set writefds writefds-buf) 68 | (buf-to-set exceptfds exceptfds-buf)])) 69 | 70 | -------------------------------------------------------------------------------- /src/net/n01se/clojure_jna/linux.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Chris Houser, May 2009. All rights reserved. 2 | ;; The use and distribution terms for this software are covered by the 3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ;; which can be found in the file epl-v10.html at the root of this distribution. 5 | ;; By using this software in any fashion, you are agreeing to be bound by 6 | ;; the terms of this license. 7 | ;; You must not remove this notice, or any other, from this software. 8 | 9 | (ns net.n01se.clojure-jna.linux 10 | "Convenient clojure functions providing linux-specific features via JNA" 11 | {:author "Chris Houser"} 12 | (:require [net.n01se.clojure-jna :refer [to-ns make-cbuf pointer when-err]] 13 | [net.n01se.clojure-jna.libc-utils :refer [select]])) 14 | 15 | (to-ns libc c [Integer inotify_init, 16 | Integer inotify_add_watch, 17 | Integer inotify_rm_watch, 18 | Integer pipe, 19 | Integer ioctl, 20 | Integer read, 21 | Integer write, 22 | Integer close]) 23 | 24 | (def #^{:private true} FIONREAD 0x541B) 25 | 26 | (def #^{:private true} flag-bits 27 | {:access 0x00000001 ; File was accessed 28 | :modify 0x00000002 ; File was modified 29 | :attrib 0x00000004 ; Metadata changed 30 | :close-write 0x00000008 ; Writtable file was closed 31 | :close-nowrite 0x00000010 ; Unwrittable file closed 32 | :close 0x00000018 33 | :open 0x00000020 ; File was opened 34 | :moved-from 0x00000040 ; File was moved from X 35 | :moved-to 0x00000080 ; File was moved to Y 36 | :moved 0x000000C0 37 | :create 0x00000100 ; Subfile was created 38 | :delete 0x00000200 ; Subfile was deleted 39 | :delete-self 0x00000400 ; Self was deleted 40 | :move-self 0x00000800 ; Self was moved 41 | :unmount 0x00002000 ; Backing fs was unmounted 42 | :q-overflow 0x00004000 ; Event queued overflowed 43 | :ignored 0x00008000 ; File was ignored 44 | :onlydir 0x01000000 ; only watch the path if it is a directory 45 | :dont-follow 0x02000000 ; don't follow a sym link 46 | :mask-add 0x20000000 ; add to the mask of an already existing watch 47 | :isdir 0x40000000 ; event occurred against dir 48 | :oneshot 0x80000000 ; only send event once 49 | :all-events 0x00000FFF}) 50 | 51 | (defn flags-mask-fn 52 | "Returns a bit-mask number for the flags collection given. Valid 53 | flags are keyword forms of the flags listed in the inotify(7) man 54 | page. For example, use :access for IN_ACCESS. Note you probably 55 | don't need to call this function directly: add-iwatch can take the 56 | same collection of flags, or you can use the flags-mask macro." 57 | [flags] 58 | (reduce bit-or (map #(or (flag-bits %) 59 | (throw (Exception. (str "Invalid flag " %)))) 60 | flags))) 61 | 62 | (defmacro flags-mask 63 | "Returns a bit-mask number for the flags given. Valid flags are 64 | symbol forms of the flags listed in the inotify(7) man page. For 65 | example, use (flags-mask access) for IN_ACCESS. Throws an exception 66 | at compile time if invalid flags are given." 67 | [& flags] 68 | (flags-mask-fn (map #(keyword (str %)) flags))) 69 | 70 | (defn- mask-flags 71 | "Returns a set of flag keywords for the event bit-mask" 72 | [mask] 73 | (set (map key (filter #(zero? (bit-and-not (val %) mask)) flag-bits)))) 74 | 75 | (defn- read-events 76 | "Agent action that blocks on the inotify fd and a control pipe. 77 | inotify events are read and dispatched in this thread. The control 78 | pipe can cause the agent to close the fd's and stop looping, or just 79 | to self-send once to load any new definition of read-events." 80 | [_ inoti] 81 | (let [[readfds] (select #{(:readfd inoti) (:ifd inoti)})] ; blocks 82 | (if (readfds (:readfd inoti)) 83 | 84 | ; IPC from some controlling thread 85 | (let [buf (make-cbuf 1)] 86 | (when-err (libc/read (:readfd inoti) (pointer buf) 1) 87 | "Error reading from pipe") 88 | (condp = (int (.get buf)) 89 | 0 (do 90 | (when-err (libc/close (:readfd inoti)) 91 | "Error closing pipe (read)") 92 | (when-err (libc/close (:writefd inoti)) 93 | "Error closing pipe (write)") 94 | (reset! (:wdmap inoti) :done) 95 | :closing) 96 | 1 (do 97 | (send-off *agent* read-events inoti) 98 | :forced-reload))) 99 | 100 | ; inotify event 101 | (let [byte-count-buf (make-cbuf 4)] 102 | (when-err (libc/ioctl (:ifd inoti) FIONREAD (pointer byte-count-buf)) 103 | "Error getting byte-count via ioctl") 104 | 105 | (let [byte-count (.getInt byte-count-buf) 106 | event-buf (make-cbuf byte-count)] 107 | (when-err (libc/read (:ifd inoti) (pointer event-buf) byte-count) 108 | "Error reading events") 109 | (while (.hasRemaining event-buf) 110 | (let [wd (.getInt event-buf) 111 | mask (.getInt event-buf) 112 | cookie (.getInt event-buf) 113 | name-len (.getInt event-buf) 114 | name-bytes (make-array Byte/TYPE name-len)] 115 | (when (pos? name-len) 116 | (.get event-buf name-bytes)) 117 | ; There's a race here when a watch is added to the fd 118 | ; before it's in wdmap. However, if the event had come in 119 | ; a moment earlier it would have been lost too, so we'll 120 | ; just ignore events whose wd is not in wdmap. 121 | (when-let [handler (get @(:wdmap inoti) wd)] 122 | (let [event {:wd wd, :mask mask, :flags (mask-flags mask) 123 | :cookie cookie}] 124 | (if (pos? name-len) 125 | (let [name-str (String. name-bytes)] 126 | (assoc event :name (.substring 127 | name-str 0 (.indexOf name-str 0)))) 128 | event)))))) 129 | 130 | (send-off *agent* read-events inoti) 131 | :normal)))) 132 | 133 | 134 | (defn iinit 135 | "Creates a new inotify instance and returns an object representing 136 | it. Use add-iwatch to add files or directories to the watch list." 137 | [] 138 | (let [buf (make-cbuf 16) 139 | _ (when-err (libc/pipe (pointer buf)) "Error creating pipe") 140 | inoti {:agent (agent :init) 141 | :readfd (.getInt buf) 142 | :writefd (.getInt buf) 143 | :ifd (libc/inotify_init) 144 | :wdmap (atom {})}] 145 | (send-off (:agent inoti) read-events inoti) 146 | inoti)) 147 | 148 | (defn add-iwatch 149 | "Adds pathname to the watchlist of the given inotify object. Use 150 | the flags-mask macro to build flags, or pass in a collection of 151 | keywords (see flags-mask-fn for details). The handler function will 152 | be called when the given event occurs, with a single arg: a hash-map 153 | with keys like :flags (a set of keywords indicating the kind of 154 | event that occured), :name (for the name of the file updated when 155 | watching a directory), :cookie, etc. See inotify(7) man page for 156 | more details on each of these fields. The function will be called 157 | in the inotify reader thread, so it should complete promptly or 158 | coordinate with another thread to complete its work. Returns 159 | a watch descriptor that can safely be ignored unless you want to 160 | explicitly pass it to rm-iwatch later." 161 | [inoti pathname flags handler] 162 | (let [wd (when-err (libc/inotify_add_watch 163 | (:ifd inoti) 164 | pathname 165 | (if (number? flags) flags (flags-mask-fn flags))) 166 | "Error adding inotify watch")] 167 | (swap! (:wdmap inoti) assoc wd handler) 168 | wd)) 169 | 170 | (defn rm-iwatch 171 | "Removes an item from the given inotify object's watchlist, as 172 | specified by the given watch descriptor." 173 | [inoti wd] 174 | (let [wd (when-err (libc/inotify_rm_watch (:ifd inoti) wd) 175 | "Error adding inotify watch")] 176 | (swap! (:wdmap inoti) dissoc wd) 177 | inoti)) 178 | 179 | (defn iclose 180 | "Closes down an inotify object. Blocks until the file descriptors 181 | are all closed and the agent has stopped looping." 182 | [inoti] 183 | (libc/write (:writefd inoti) (pointer (make-cbuf 1)) 1) 184 | (while (not= @(:agent inoti) :done) 185 | (send-off (:agent inoti) #(if (= % :closing) :done %)) 186 | (await (:agent inoti))) 187 | inoti) 188 | 189 | (defn- reload-read-events [inoti] 190 | (let [buf (make-cbuf 1)] 191 | (.put buf (byte 1)) 192 | (libc/write (:writefd inoti) (pointer buf) 1)) 193 | nil) 194 | 195 | 196 | (comment 197 | ; Example usage: 198 | 199 | (def x (iinit)) 200 | 201 | (add-iwatch x "/tmp" (flags-mask create delete) 202 | #(if (:create (:flags %)) 203 | (println "Created file" (:name %) "in /tmp") 204 | (println "Deleted file" (:name %) "in /tmp"))) 205 | 206 | (def wd (add-iwatch x "/tmp" (flags-mask moved) prn)) 207 | (rm-iwatch x wd) 208 | 209 | (iclose x) 210 | ) 211 | 212 | -------------------------------------------------------------------------------- /test/net/n01se/clojure_jna/test.clj: -------------------------------------------------------------------------------- 1 | (ns net.n01se.clojure-jna.test 2 | (:require [net.n01se.clojure-jna :as jna] 3 | [clojure.test :refer [deftest is]])) 4 | 5 | (deftest test-jna-invoke 6 | (is (= 13 (jna/invoke Integer c/printf "My number: %d\n" 5)))) 7 | 8 | (deftest test-jna-fn 9 | (let [c-printf (jna/to-fn Integer c/printf)] 10 | (is (= 13 (c-printf "My number: %d\n" 5))))) 11 | 12 | (deftest test-jna-ns 13 | (jna/to-ns native-c c [Integer printf, Integer open, Integer close]) 14 | (is (= 0 (eval '(native-c/close 0)))) 15 | (is (= 14 (eval '(native-c/printf "one %s two\n" "hello"))))) 16 | --------------------------------------------------------------------------------