├── 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 |
--------------------------------------------------------------------------------