├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── jnr │ └── unixsocket │ ├── BindHandler.java │ ├── Common.java │ ├── Credentials.java │ ├── Native.java │ ├── SockAddrUnix.java │ ├── Ucred.java │ ├── UnixDatagramChannel.java │ ├── UnixDatagramSocket.java │ ├── UnixServerSocket.java │ ├── UnixServerSocketChannel.java │ ├── UnixSocket.java │ ├── UnixSocketAddress.java │ ├── UnixSocketChannel.java │ ├── UnixSocketOptions.java │ └── impl │ ├── AbstractNativeDatagramChannel.java │ ├── AbstractNativeServerSocketChannel.java │ ├── AbstractNativeSocketChannel.java │ └── Common.java └── test ├── java └── jnr │ └── unixsocket │ ├── BasicDatagramFunctionalityTest.java │ ├── BasicFunctionalityTest.java │ ├── ChannelOptionsTest.java │ ├── CredentialsFunctionalTest.java │ ├── ForFDTest.java │ ├── SocketInteropTest.java │ ├── TcpChannelsApiSocketPair.java │ ├── TcpSocketsApiSocketPair.java │ ├── TestSocketPair.java │ ├── UnixDatagramChannelTest.java │ ├── UnixSocketChannelTest.java │ ├── UnixSocketPair.java │ └── example │ ├── LocalSyslogClient.java │ ├── UnixClient.java │ └── UnixServer.java └── resources └── background.sh /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | jdk8: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 8 20 | uses: actions/setup-java@v1.4.3 21 | with: 22 | java-version: 8 23 | - name: Build with Maven 24 | run: mvn -B package --file pom.xml 25 | 26 | jdk11: 27 | 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v2 32 | - name: Set up JDK 11 33 | uses: actions/setup-java@v1.4.3 34 | with: 35 | java-version: 11 36 | - name: Build with Maven 37 | run: mvn -B package --file pom.xml 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # maven 2 | target/ 3 | *.versionsBackup 4 | *.releaseBackup 5 | 6 | # eclipse 7 | .classpath 8 | .project 9 | .settings 10 | 11 | # intellij / android studio 12 | *.iml 13 | *.ipr 14 | *.iws 15 | .idea/ 16 | 17 | # common junk 18 | *.log 19 | *.diff 20 | *.patch 21 | *.sw[a-p] 22 | *.bak 23 | *.backup 24 | *.debug 25 | *.dump 26 | 27 | # vim 28 | .*.sw[a-p] 29 | *~ 30 | ~* 31 | 32 | # Mac filesystem dust 33 | .DS_Store 34 | 35 | # pmd 36 | .pmdruleset 37 | .pmd 38 | 39 | # merge tooling 40 | *.orig 41 | *.rej 42 | \.orig$ 43 | \.orig\..*$ 44 | \.chg\..*$ 45 | \.rej$ 46 | \.conflict\~$ 47 | 48 | .idea 49 | /.checkstyle 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![][Build Status img]][Build Status] 2 | [![][license img]][license] 3 | [![][Maven Central img]][Maven Central] 4 | [![][Javadocs img]][Javadocs] 5 | 6 | jnr-unixsocket 7 | ============== 8 | 9 | Native I/O access for java. 10 | 11 | Check out the [examples](https://github.com/jnr/jnr-unixsocket/tree/master/src/test/java/jnr/unixsocket/example) for more information. 12 | 13 | [Build Status]:https://travis-ci.org/jnr/jnr-unixsocket 14 | [Build Status img]:https://travis-ci.org/jnr/jnr-unixsocket.svg?branch=master 15 | 16 | [license]:LICENSE 17 | [license img]:https://img.shields.io/badge/license-Apache%202-blue.svg 18 | 19 | [Maven Central]:https://maven-badges.herokuapp.com/maven-central/com.github.jnr/jnr-unixsocket 20 | [Maven Central img]:https://maven-badges.herokuapp.com/maven-central/com.github.jnr/jnr-unixsocket/badge.svg 21 | 22 | [Javadocs]:http://javadoc.io/doc/com.github.jnr/jnr-unixsocket 23 | [Javadocs img]:http://javadoc.io/badge/com.github.jnr/jnr-unixsocket.svg 24 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | 5 | org.sonatype.oss 6 | oss-parent 7 | 7 8 | 9 | 10 | com.github.jnr 11 | jnr-unixsocket 12 | jar 13 | 0.38.24-SNAPSHOT 14 | jnr-unixsocket 15 | UNIX socket channels for java 16 | http://github.com/jnr/jnr-unixsocket 17 | 18 | 19 | 20 | The Apache Software License, Version 2.0 21 | http://www.apache.org/licenses/LICENSE-2.0.txt 22 | repo 23 | 24 | 25 | 26 | 27 | scm:git:git@github.com:jnr/jnr-unixsocket.git 28 | scm:git:git@github.com:jnr/jnr-unixsocket.git 29 | git@github.com:jnr/jnr-unixsocket.git 30 | 31 | 32 | 33 | 34 | wmeissner 35 | Wayne Meissner 36 | wmeissner@gmail.com 37 | 38 | 39 | felfert 40 | Fritz Elfert 41 | fritz-github@fritz-elfert.de 42 | Europe/Berlin 43 | 44 | 45 | 46 | 47 | UTF-8 48 | 8 49 | 8 50 | 51 | 52 | 53 | 54 | junit 55 | junit 56 | 4.13.1 57 | test 58 | 59 | 60 | com.github.jnr 61 | jnr-ffi 62 | 2.2.17 63 | compile 64 | 65 | 66 | com.github.jnr 67 | jnr-constants 68 | 0.10.4 69 | compile 70 | 71 | 72 | com.github.jnr 73 | jnr-enxio 74 | 0.32.18 75 | compile 76 | 77 | 78 | com.github.jnr 79 | jnr-posix 80 | 3.1.20 81 | compile 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | maven-checkstyle-plugin 90 | 2.17 91 | 92 | 93 | process-test-classes 94 | 95 | check 96 | 97 | 98 | true 99 | true 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | com.github.spotbugs 121 | spotbugs-maven-plugin 122 | 3.1.12.2 123 | 124 | 125 | 126 | com.github.spotbugs 127 | spotbugs 128 | 4.0.0-beta4 129 | 130 | 131 | 132 | 133 | process-test-classes 134 | 135 | check 136 | 137 | 138 | 139 | 140 | Max 141 | false 142 | true 143 | true 144 | false 145 | 146 | 147 | 148 | 149 | 150 | maven-pmd-plugin 151 | 3.13.0 152 | 153 | true 154 | false 155 | false 156 | 157 | 158 | /rulesets/java/basic.xml 159 | /rulesets/java/empty.xml 160 | /rulesets/java/imports.xml 161 | /rulesets/java/unnecessary.xml 162 | /rulesets/java/unusedcode.xml 163 | 164 | 165 | /rulesets/java/braces.xml 166 | /rulesets/java/finalizers.xml 167 | 168 | 169 | 170 | 171 | process-test-classes 172 | 173 | check 174 | cpd-check 175 | 176 | 177 | 178 | 179 | 180 | 181 | org.apache.felix 182 | maven-bundle-plugin 183 | 2.3.7 184 | 185 | 186 | <_nouses>true 187 | *,jnr.ffi.mapper,jnr.ffi.provider.converters,jnr.ffi.provider.jffi,com.kenai.jffi 188 | jnr.unixsocket 189 | 190 | 191 | 192 | 193 | bundle-manifest 194 | process-classes 195 | 196 | manifest 197 | 198 | 199 | 200 | 201 | 202 | org.apache.maven.plugins 203 | maven-jar-plugin 204 | 2.3.1 205 | 206 | 207 | ${project.build.outputDirectory}/META-INF/MANIFEST.MF 208 | 209 | org.jnrproject.unixsocket 210 | 211 | 212 | 213 | 214 | 215 | 216 | jar 217 | test-jar 218 | 219 | 220 | 221 | 222 | 223 | org.apache.maven.plugins 224 | maven-source-plugin 225 | 2.2.1 226 | 227 | 228 | attach-sources 229 | 230 | jar-no-fork 231 | 232 | 233 | 234 | 235 | 236 | org.apache.maven.plugins 237 | maven-assembly-plugin 238 | 2.5.5 239 | 240 | 241 | assemble-all 242 | package 243 | 244 | single 245 | 246 | 247 | 248 | jar-with-dependencies 249 | 250 | 251 | 252 | 253 | 254 | 297 | 298 | org.apache.maven.plugins 299 | maven-compiler-plugin 300 | 3.8.1 301 | 302 | 303 | 304 | 305 | org.apache.maven.wagon 306 | wagon-webdav 307 | 308 | 309 | 310 | 311 | 312 | 313 | java9 314 | 315 | [9,) 316 | 317 | 318 | 8 319 | 320 | 321 | 322 | 323 | 324 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/BindHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Fritz Elfert 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | package jnr.unixsocket; 19 | 20 | import java.io.IOException; 21 | import java.net.SocketAddress; 22 | 23 | import java.nio.channels.UnsupportedAddressTypeException; 24 | import java.nio.channels.AlreadyBoundException; 25 | 26 | import java.util.concurrent.atomic.AtomicBoolean; 27 | 28 | /** 29 | * Helper class, providing common handling of bind() handling. 30 | */ 31 | final class BindHandler { 32 | private final AtomicBoolean bound; 33 | 34 | BindHandler(boolean initialState) { 35 | bound = new AtomicBoolean(initialState); 36 | } 37 | 38 | boolean isBound() { 39 | return bound.get(); 40 | } 41 | 42 | synchronized UnixSocketAddress bind(int fd, SocketAddress local) throws IOException { 43 | if (null != local && !(local instanceof UnixSocketAddress)) { 44 | throw new UnsupportedAddressTypeException(); 45 | } 46 | if (bound.get()) { 47 | throw new AlreadyBoundException(); 48 | } else { 49 | UnixSocketAddress ret = Common.bind(fd, (UnixSocketAddress)local); 50 | bound.set(true); 51 | return ret; 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/Common.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Fritz Elfert 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | package jnr.unixsocket; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.net.SocketOption; 24 | import java.nio.file.Files; 25 | import java.util.Map; 26 | import java.util.HashMap; 27 | 28 | import jnr.constants.platform.ProtocolFamily; 29 | import jnr.constants.platform.SocketLevel; 30 | 31 | import jnr.ffi.Platform; 32 | import jnr.ffi.Platform.OS; 33 | import jnr.ffi.byref.IntByReference; 34 | 35 | /** 36 | * Helper class, providing common methods. 37 | */ 38 | final class Common { 39 | 40 | private static OS currentOS = Platform.getNativePlatform().getOS(); 41 | 42 | private Common() { 43 | } 44 | 45 | static UnixSocketAddress bind(int fd, UnixSocketAddress local) throws IOException { 46 | SockAddrUnix sa; 47 | if (null == local) { 48 | // Support autobind 49 | sa = SockAddrUnix.create(); 50 | sa.setFamily(ProtocolFamily.PF_UNIX); 51 | if (currentOS == OS.LINUX) { 52 | // On Linux, we simply set an empty path 53 | sa.setPath(""); 54 | } else { 55 | // Emulate something similar (bind to some random unique address), 56 | // but use regular namespace 57 | File f = Files.createTempFile("jnr-unixsocket-tmp", ".sock").toFile(); 58 | f.deleteOnExit(); 59 | f.delete(); 60 | sa.setPath(f.getPath()); 61 | } 62 | } else { 63 | sa = local.getStruct(); 64 | } 65 | if (Native.bind(fd, sa, sa.length()) < 0) { 66 | throw new IOException(Native.getLastErrorString()); 67 | } 68 | return getsockname(fd); 69 | } 70 | 71 | static UnixSocketAddress getsockname(int sockfd) { 72 | UnixSocketAddress local = new UnixSocketAddress(); 73 | SockAddrUnix addr = local.getStruct(); 74 | IntByReference len = new IntByReference(addr.getMaximumLength()); 75 | 76 | if (Native.libc().getsockname(sockfd, addr, len) < 0) { 77 | throw new Error(Native.getLastErrorString()); 78 | } 79 | addr.updatePath(len.getValue()); 80 | return local; 81 | } 82 | 83 | static UnixSocketAddress getpeername(int sockfd) { 84 | UnixSocketAddress remote = new UnixSocketAddress(); 85 | SockAddrUnix addr = remote.getStruct(); 86 | IntByReference len = new IntByReference(addr.getMaximumLength()); 87 | 88 | if (Native.libc().getpeername(sockfd, addr, len) < 0) { 89 | throw new Error(Native.getLastErrorString()); 90 | } 91 | addr.updatePath(len.getValue()); 92 | return remote; 93 | } 94 | 95 | static T getSocketOption(int fd, SocketOption name) throws IOException { 96 | jnr.constants.platform.SocketOption optname = rMap.get(name); 97 | if (null == optname) { 98 | throw new AssertionError("Option not found"); 99 | } 100 | Class type = name.type(); 101 | if (type == Credentials.class) { 102 | return (T) Credentials.getCredentials(fd); 103 | } 104 | if (type == Integer.class) { 105 | return (T) Integer.valueOf(Native.getsockopt(fd, SocketLevel.SOL_SOCKET, optname.intValue())); 106 | } 107 | return (T) Boolean.valueOf(Native.getboolsockopt(fd, SocketLevel.SOL_SOCKET, optname.intValue())); 108 | } 109 | 110 | static void setSocketOption(int fd, SocketOption name, 111 | Object value) throws IOException { 112 | if (null == value) { 113 | throw new IllegalArgumentException("Invalid option value"); 114 | } 115 | 116 | jnr.constants.platform.SocketOption optname = wMap.get(name); 117 | if (null == optname) { 118 | throw new AssertionError("Option not found or not writable"); 119 | } 120 | 121 | Class type = name.type(); 122 | if (type != Integer.class && type != Boolean.class) { 123 | throw new AssertionError("Unsupported option type"); 124 | } 125 | 126 | int optvalue; 127 | if (type == Integer.class) { 128 | optvalue = ((Integer)value).intValue(); 129 | } else { 130 | optvalue = ((Boolean)value).booleanValue() ? 1 : 0; 131 | } 132 | 133 | if (name == UnixSocketOptions.SO_RCVBUF || name == UnixSocketOptions.SO_SNDBUF) { 134 | int i = ((Integer)value).intValue(); 135 | if (i < 0) { 136 | throw new IllegalArgumentException("Invalid send/receive buffer size"); 137 | } 138 | } 139 | 140 | if (name == UnixSocketOptions.SO_RCVTIMEO || name == UnixSocketOptions.SO_SNDTIMEO) { 141 | int i = ((Integer)value).intValue(); 142 | if (i < 0) { 143 | throw new IllegalArgumentException("Invalid send/receive timeout"); 144 | } 145 | } 146 | 147 | if (0 != Native.setsockopt(fd, SocketLevel.SOL_SOCKET, optname, optvalue)) { 148 | throw new IOException(Native.getLastErrorString()); 149 | } 150 | } 151 | 152 | private static final Map,jnr.constants.platform.SocketOption> wMap = new HashMap<>(); 153 | private static final Map,jnr.constants.platform.SocketOption> rMap = new HashMap<>(); 154 | static { 155 | wMap.put(UnixSocketOptions.SO_RCVBUF, jnr.constants.platform.SocketOption.SO_RCVBUF); 156 | wMap.put(UnixSocketOptions.SO_SNDBUF, jnr.constants.platform.SocketOption.SO_SNDBUF); 157 | wMap.put(UnixSocketOptions.SO_RCVTIMEO, jnr.constants.platform.SocketOption.SO_RCVTIMEO); 158 | wMap.put(UnixSocketOptions.SO_SNDTIMEO, jnr.constants.platform.SocketOption.SO_SNDTIMEO); 159 | wMap.put(UnixSocketOptions.SO_KEEPALIVE, jnr.constants.platform.SocketOption.SO_KEEPALIVE); 160 | wMap.put(UnixSocketOptions.SO_PASSCRED, jnr.constants.platform.SocketOption.SO_PASSCRED); 161 | 162 | rMap.putAll(wMap); 163 | rMap.put(UnixSocketOptions.SO_PEERCRED, jnr.constants.platform.SocketOption.SO_PEERCRED); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/Credentials.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Greg Vanore 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket; 20 | 21 | import jnr.constants.platform.SocketLevel; 22 | import jnr.constants.platform.SocketOption; 23 | 24 | /** 25 | * This class represents the peer credentials, retrievable from an AF_UNIX socket. 26 | *

27 | * An instance of this class can be retrieved, using either the socket-level methods 28 | * {@link UnixSocket#getCredentials} and {@link UnixDatagramSocket#getCredentials} or by specifying 29 | * {@link UnixSocketOptions#SO_PEERCRED} as argument to one of the 30 | * channel-level methods {@link UnixSocketChannel#getOption} and {@link UnixDatagramChannel#getOption}. 31 | *

32 | * See also: socket (7) 33 | */ 34 | public final class Credentials { 35 | private final Ucred ucred; 36 | 37 | Credentials(Ucred ucred) { 38 | this.ucred = ucred; 39 | } 40 | 41 | /** 42 | * Retrieves the peer's process ID. 43 | * @return The PID. 44 | */ 45 | public int getPid() { 46 | return ucred.getPidField().intValue(); 47 | } 48 | 49 | /** 50 | * Retrieves the peer's numeric effective user ID. 51 | * @return The EUID. 52 | */ 53 | public int getUid() { 54 | return ucred.getUidField().intValue(); 55 | } 56 | 57 | /** 58 | * Retrieves the peer's numeric effective group ID. 59 | * @return The EGID. 60 | */ 61 | public int getGid() { 62 | return ucred.getGidField().intValue(); 63 | } 64 | 65 | /** 66 | * Returns a human readable description of this instance. 67 | */ 68 | @Override 69 | public java.lang.String toString() { 70 | return java.lang.String.format("[uid=%d gid=%d pid=%d]", getUid(), getGid(), getPid()); 71 | } 72 | 73 | static Credentials getCredentials(int fd) { 74 | Ucred c = new Ucred(); 75 | int error = Native.getsockopt(fd, SocketLevel.SOL_SOCKET, SocketOption.SO_PEERCRED, c); 76 | if (error != 0) { 77 | throw new UnsupportedOperationException(Native.getLastErrorString()); 78 | } 79 | 80 | return new Credentials(c); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/Native.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Wayne Meissner 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket; 20 | 21 | import java.io.IOException; 22 | import java.nio.ByteBuffer; 23 | import java.nio.ByteOrder; 24 | 25 | import jnr.constants.platform.Errno; 26 | import jnr.constants.platform.ProtocolFamily; 27 | import jnr.constants.platform.Sock; 28 | import jnr.constants.platform.SocketLevel; 29 | import jnr.constants.platform.SocketOption; 30 | import jnr.ffi.LastError; 31 | import jnr.ffi.LibraryLoader; 32 | import jnr.ffi.Platform; 33 | import jnr.ffi.Pointer; 34 | import jnr.ffi.Runtime; 35 | import jnr.ffi.Struct; 36 | import jnr.ffi.annotations.In; 37 | import jnr.ffi.annotations.Out; 38 | import jnr.ffi.annotations.Transient; 39 | import jnr.ffi.byref.IntByReference; 40 | import jnr.ffi.types.size_t; 41 | import jnr.ffi.types.ssize_t; 42 | import jnr.posix.DefaultNativeTimeval; 43 | import jnr.posix.Timeval; 44 | 45 | class Native { 46 | static final String[] libnames = Platform.getNativePlatform().getOS() == Platform.OS.SOLARIS 47 | ? new String[] { "socket", "nsl", Platform.getNativePlatform().getStandardCLibraryName() } 48 | : new String[] { Platform.getNativePlatform().getStandardCLibraryName() }; 49 | public interface LibC { 50 | 51 | int F_GETFL = jnr.constants.platform.Fcntl.F_GETFL.intValue(); 52 | int F_SETFL = jnr.constants.platform.Fcntl.F_SETFL.intValue(); 53 | int O_NONBLOCK = jnr.constants.platform.OpenFlags.O_NONBLOCK.intValue(); 54 | 55 | int socket(int domain, int type, int protocol); 56 | int listen(int fd, int backlog); 57 | int bind(int fd, @In @Out @Transient SockAddrUnix addr, int len); 58 | int accept(int fd, @Out SockAddrUnix addr, @In @Out IntByReference len); 59 | int connect(int s, @In @Transient SockAddrUnix name, int namelen); 60 | int getsockname(int fd, @Out SockAddrUnix addr, @In @Out IntByReference len); 61 | int getpeername(int fd, @Out SockAddrUnix addr, @In @Out IntByReference len); 62 | int socketpair(int domain, int type, int protocol, @Out int[] sv); 63 | int fcntl(int fd, int cmd, int data); 64 | int getsockopt(int s, int level, int optname, @Out ByteBuffer optval, @In @Out IntByReference optlen); 65 | int getsockopt(int s, int level, int optname, @Out Timeval optval, @In @Out IntByReference optlen); 66 | int setsockopt(int s, int level, int optname, @In ByteBuffer optval, int optlen); 67 | int setsockopt(int s, int level, int optname, @In Timeval optval, int optlen); 68 | String strerror(int error); 69 | @ssize_t int sendto(int s, @In ByteBuffer data, @size_t long size, int flags, @In @Transient SockAddrUnix name, int namelen); 70 | @ssize_t int recvfrom(int s, @Out ByteBuffer data, @size_t long size, int flags, @Out SockAddrUnix addr, @In @Out IntByReference len); 71 | } 72 | 73 | static final LibC INSTANCE; 74 | 75 | static { 76 | LibraryLoader loader = LibraryLoader.create(LibC.class); 77 | for (String libraryName : libnames) { 78 | loader.library(libraryName); 79 | } 80 | INSTANCE = loader.load(); 81 | } 82 | 83 | static final LibC libsocket() { 84 | return INSTANCE; 85 | } 86 | 87 | static final LibC libc() { 88 | return INSTANCE; 89 | } 90 | 91 | static int socket(ProtocolFamily domain, Sock type, int protocol) throws IOException { 92 | int fd = libsocket().socket(domain.intValue(), type.intValue(), protocol); 93 | if (fd < 0) { 94 | throw new IOException(getLastErrorString()); 95 | } 96 | return fd; 97 | } 98 | 99 | static int socketpair(ProtocolFamily domain, Sock type, int protocol, int[] sv) throws IOException { 100 | if (libsocket().socketpair(domain.intValue(), type.intValue(), protocol, sv) < 0) { 101 | throw new IOException("socketpair(2) failed " + Native.getLastErrorString()); 102 | } 103 | return 0; 104 | } 105 | 106 | static int listen(int fd, int backlog) { 107 | return libsocket().listen(fd, backlog); 108 | } 109 | 110 | static int bind(int fd, SockAddrUnix addr, int len) { 111 | return libsocket().bind(fd, addr, len); 112 | } 113 | 114 | static int accept(int fd, SockAddrUnix addr, IntByReference len) { 115 | return libsocket().accept(fd, addr, len); 116 | } 117 | 118 | static int connect(int fd, SockAddrUnix addr, int len) { 119 | return libsocket().connect(fd, addr, len); 120 | } 121 | 122 | static String getLastErrorString() { 123 | return strerror(LastError.getLastError(Runtime.getSystemRuntime())); 124 | } 125 | 126 | static Errno getLastError() { 127 | return Errno.valueOf(LastError.getLastError(Runtime.getSystemRuntime())); 128 | } 129 | 130 | static String strerror(int error) { 131 | return libc().strerror(error); 132 | } 133 | 134 | public static void setBlocking(int fd, boolean block) { 135 | int flags = libc().fcntl(fd, LibC.F_GETFL, 0); 136 | if (block) { 137 | flags &= ~LibC.O_NONBLOCK; 138 | } else { 139 | flags |= LibC.O_NONBLOCK; 140 | } 141 | libc().fcntl(fd, LibC.F_SETFL, flags); 142 | } 143 | 144 | public static int setsockopt(int s, SocketLevel level, SocketOption optname, boolean optval) { 145 | return setsockopt(s, level, optname, optval ? 1 : 0); 146 | } 147 | 148 | public static int setsockopt(int s, SocketLevel level, SocketOption optname, int optval) { 149 | if (optname == SocketOption.SO_RCVTIMEO || optname == SocketOption.SO_SNDTIMEO) { 150 | DefaultNativeTimeval t = new DefaultNativeTimeval(Runtime.getSystemRuntime()); 151 | t.setTime(new long [] {optval / 1000, ((long)optval % 1000) * 1000}); 152 | return libsocket().setsockopt(s, level.intValue(), optname.intValue(), t, DefaultNativeTimeval.size(t)); 153 | } else { 154 | ByteBuffer buf = ByteBuffer.allocate(4); 155 | buf.order(ByteOrder.nativeOrder()); 156 | buf.putInt(optval).flip(); 157 | return libsocket().setsockopt(s, level.intValue(), optname.intValue(), buf, buf.remaining()); 158 | } 159 | } 160 | 161 | public static int getsockopt (int s, SocketLevel level, int optname) { 162 | IntByReference ref; 163 | if (optname == SocketOption.SO_RCVTIMEO.intValue() || optname == SocketOption.SO_SNDTIMEO.intValue()) { 164 | DefaultNativeTimeval t = new DefaultNativeTimeval(Runtime.getSystemRuntime()); 165 | ref = new IntByReference(DefaultNativeTimeval.size(t)); 166 | Native.libsocket().getsockopt(s, level.intValue(), optname, t, ref); 167 | return (t.tv_sec.intValue() * 1000 + t.tv_usec.intValue() / 1000); 168 | } else { 169 | ByteBuffer buf = ByteBuffer.allocate(4); 170 | buf.order(ByteOrder.nativeOrder()); 171 | ref = new IntByReference(4); 172 | Native.libsocket().getsockopt(s, level.intValue(), optname, buf, ref); 173 | return buf.getInt(); 174 | } 175 | } 176 | 177 | public static int getsockopt(int s, SocketLevel level, SocketOption optname, Struct data) { 178 | Pointer struct_ptr = Struct.getMemory(data); 179 | IntByReference ref = new IntByReference(Struct.size(data)); 180 | ByteBuffer buf = ByteBuffer.wrap((byte[])struct_ptr.array()); 181 | 182 | return Native.libsocket().getsockopt(s, level.intValue(), optname.intValue(), buf, ref); 183 | } 184 | 185 | public static boolean getboolsockopt (int s, SocketLevel level, int optname) { 186 | return getsockopt(s, level, optname) != 0; 187 | } 188 | 189 | public static int sendto(int fd, ByteBuffer src, SockAddrUnix addr, int len) throws IOException { 190 | if (src == null) { 191 | throw new IllegalArgumentException("Source buffer cannot be null"); 192 | } 193 | 194 | int n; 195 | do { 196 | n = libsocket().sendto(fd, src, src.remaining(), 0, addr, len); 197 | } while (n < 0 && Errno.EINTR.equals(getLastError())); 198 | 199 | if (n > 0) { 200 | src.position(src.position() + n); 201 | } 202 | 203 | return n; 204 | } 205 | 206 | public static int recvfrom(int fd, ByteBuffer dst, SockAddrUnix addr) throws IOException { 207 | if (dst == null) { 208 | throw new IllegalArgumentException("Destination buffer cannot be null"); 209 | } 210 | if (dst.isReadOnly()) { 211 | throw new IllegalArgumentException("Read-only buffer"); 212 | } 213 | 214 | IntByReference addrlen = (null == addr) ? null : new IntByReference(addr.getMaximumLength()); 215 | int n; 216 | do { 217 | n = libsocket().recvfrom(fd, dst, dst.remaining(), 0, addr, addrlen); 218 | } while (n < 0 && Errno.EINTR.equals(getLastError())); 219 | 220 | if (n > 0) { 221 | dst.position(dst.position() + n); 222 | } 223 | 224 | return n; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/SockAddrUnix.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Wayne Meissner 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket; 20 | 21 | import static java.nio.charset.StandardCharsets.UTF_8; 22 | 23 | import jnr.constants.platform.ProtocolFamily; 24 | import jnr.ffi.Platform; 25 | import jnr.ffi.Platform.OS; 26 | import jnr.ffi.Runtime; 27 | import jnr.ffi.Struct; 28 | 29 | /** 30 | * Native unix domain socket address structure. 31 | */ 32 | abstract class SockAddrUnix extends Struct { 33 | 34 | private static transient OS currentOS = Platform.getNativePlatform().getOS(); 35 | public final static int ADDR_LENGTH = 108; 36 | public final static int HEADER_LENGTH = 2; 37 | 38 | protected abstract UTF8String getPathField(); 39 | protected abstract NumberField getFamilyField(); 40 | 41 | // This is important to be cached here for supporting abstract namespace on Linux 42 | // (which starts with a NUL byte. path is NOT NUL terminated in this case!) 43 | private java.lang.String cachedPath; 44 | 45 | SockAddrUnix() { 46 | super(Runtime.getSystemRuntime()); 47 | } 48 | 49 | /** 50 | * Sets the protocol family of this unix socket address. 51 | * 52 | * @param family The protocol family, usually {@link ProtocolFamily#PF_UNIX} 53 | */ 54 | final void setFamily(ProtocolFamily family) { 55 | getFamilyField().set(family.intValue()); 56 | } 57 | 58 | 59 | /** 60 | * Gets the protocol family of this unix socket address. 61 | * 62 | * @return The protocol family 63 | */ 64 | final ProtocolFamily getFamily() { 65 | return ProtocolFamily.valueOf(getFamilyField().intValue()); 66 | } 67 | 68 | /** 69 | * Sets the file system path of this socket address 70 | * 71 | * @param path The unix socket address 72 | */ 73 | void setPath(java.lang.String path) { 74 | cachedPath = path; 75 | getPathField().set(cachedPath); 76 | } 77 | 78 | /** 79 | * Updates the file system path of this socket address. 80 | * In order to support abstract namespaces, this MUST be 81 | * called after any native syscall that sets this 82 | * path struct like getsockname(), getpeername(), accept(). 83 | * 84 | * @param len the value of the addrlen var, set by the above syscalls. 85 | */ 86 | void updatePath(final int len) { 87 | if (currentOS == OS.LINUX) { 88 | // Linux always returns an accurate length in 89 | // order to support abstract namespace, where 90 | // path STARTS with a NUL byte. 91 | cachedPath = len == HEADER_LENGTH ? "" : getPath(len - HEADER_LENGTH); 92 | } else { 93 | // All others might return a len > 0 (typically 14) AND the path is terminated 94 | // by a NUL byte if it is shorter than sizeof(sun_path) 95 | cachedPath = getPathField().get(); 96 | int slen = len - HEADER_LENGTH; 97 | if (slen <= 0) { 98 | cachedPath = ""; 99 | } else { 100 | if (slen < getPathField().length() && slen < cachedPath.length()) { 101 | cachedPath = cachedPath.substring(0, slen); 102 | } 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * Gets the file system path of this socket address 109 | * 110 | * @return A String 111 | */ 112 | final java.lang.String getPath() { 113 | if (null == cachedPath) { 114 | cachedPath = getPathField().get(); 115 | } 116 | return cachedPath; 117 | } 118 | 119 | /** 120 | * Gets the path of this socket address, supporting abstract namespace on Linux. 121 | * 122 | * @param len The desired length of the string. 123 | * If the first character of the path is NUL, then this value ist considered 124 | * exact, otherwise it includes a trailing NUL charater and therefore the actual 125 | * string length is len - 1. 126 | */ 127 | final java.lang.String getPath(int len) { 128 | UTF8String str = getPathField(); 129 | byte [] ba = new byte[str.length()]; 130 | str.getMemory().get(str.offset(), ba, 0, len); 131 | if (0 != ba[0]) { 132 | len -= 1; 133 | } 134 | return new java.lang.String(java.util.Arrays.copyOf(ba, len), UTF_8); 135 | } 136 | 137 | /** 138 | * Gets the maximum length of this address (including len/family header) 139 | * 140 | * @return The maximum size of the address in bytes 141 | */ 142 | int getMaximumLength() { 143 | return HEADER_LENGTH + getPathField().length(); 144 | } 145 | 146 | /** 147 | * Gets the actual length of this address (including len/family header) 148 | * 149 | * @return The actual size of this address, in bytes 150 | */ 151 | int length() { 152 | if (currentOS == OS.LINUX && null != cachedPath) { 153 | return HEADER_LENGTH + cachedPath.length(); 154 | } 155 | return HEADER_LENGTH + strlen(getPathField()); 156 | } 157 | 158 | /** 159 | * Gets len/family header length 160 | * 161 | * @return The size of header, in bytes 162 | */ 163 | int getHeaderLength() { 164 | return HEADER_LENGTH; 165 | } 166 | 167 | 168 | /** 169 | * Creates a new instance of SockAddrUnix 170 | * 171 | * @return An instance of SockAddrUnix 172 | */ 173 | static SockAddrUnix create() { 174 | return Platform.getNativePlatform().isBSD() ? new BSDSockAddrUnix() : new DefaultSockAddrUnix(); 175 | } 176 | 177 | private static final int strlen(UTF8String str) { 178 | int end = str.getMemory().indexOf(str.offset(), (byte) 0); 179 | return end >= 0 ? end : str.length(); 180 | } 181 | 182 | /** 183 | * An implementation of {@link SockAddrUnix} for BSD systems 184 | */ 185 | static final class BSDSockAddrUnix extends SockAddrUnix { 186 | 187 | public final Unsigned8 sun_len = new Unsigned8(); 188 | public final Unsigned8 sun_family = new Unsigned8(); 189 | public final UTF8String sun_addr = new UTF8String(ADDR_LENGTH); 190 | 191 | @Override 192 | public void setPath(java.lang.String path) { 193 | super.setPath(path); 194 | sun_len.set(path.length()); 195 | } 196 | protected UTF8String getPathField() { 197 | return sun_addr; 198 | } 199 | protected NumberField getFamilyField() { 200 | return sun_family; 201 | } 202 | } 203 | 204 | 205 | /** 206 | * An implementation of {@link SockAddrUnix} for Linux, Solaris, et, al 207 | */ 208 | static final class DefaultSockAddrUnix extends SockAddrUnix { 209 | public final Unsigned16 sun_family = new Unsigned16(); 210 | public final UTF8String sun_addr = new UTF8String(ADDR_LENGTH); 211 | 212 | protected UTF8String getPathField() { 213 | return sun_addr; 214 | } 215 | 216 | protected NumberField getFamilyField() { 217 | return sun_family; 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/Ucred.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the JNR project. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package jnr.unixsocket; 18 | 19 | import jnr.ffi.Struct; 20 | 21 | /** 22 | * Native structure for SCM_CREDENTIALS. See 'man 7 unix'. 23 | */ 24 | final class Ucred extends Struct { 25 | final pid_t pid = new pid_t(); 26 | final uid_t uid = new uid_t(); 27 | final gid_t gid = new gid_t(); 28 | 29 | public Ucred() { 30 | super(jnr.ffi.Runtime.getSystemRuntime()); 31 | } 32 | 33 | pid_t getPidField() { 34 | return pid; 35 | } 36 | 37 | uid_t getUidField() { 38 | return uid; 39 | } 40 | 41 | gid_t getGidField() { 42 | return gid; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/UnixDatagramChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Fritz Elfert 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket; 20 | 21 | import java.io.IOException; 22 | 23 | import java.nio.ByteBuffer; 24 | import java.nio.channels.ClosedChannelException; 25 | import java.nio.channels.DatagramChannel; 26 | import java.nio.channels.MembershipKey; 27 | import java.nio.channels.UnsupportedAddressTypeException; 28 | 29 | import java.net.InetAddress; 30 | import java.net.NetworkInterface; 31 | import java.net.SocketAddress; 32 | import java.net.SocketException; 33 | import java.net.SocketOption; 34 | 35 | import java.util.concurrent.locks.ReadWriteLock; 36 | import java.util.concurrent.locks.ReentrantReadWriteLock; 37 | import java.util.Collections; 38 | import java.util.HashSet; 39 | import java.util.Set; 40 | 41 | import jnr.constants.platform.ProtocolFamily; 42 | import jnr.constants.platform.Sock; 43 | import jnr.unixsocket.impl.AbstractNativeDatagramChannel; 44 | 45 | public class UnixDatagramChannel extends AbstractNativeDatagramChannel { 46 | static enum State { 47 | UNINITIALIZED, 48 | CONNECTED, 49 | IDLE, 50 | } 51 | private State state; 52 | private UnixSocketAddress remoteAddress = null; 53 | private UnixSocketAddress localAddress = null; 54 | private final ReadWriteLock stateLock = new ReentrantReadWriteLock(); 55 | private final BindHandler bindHandler; 56 | 57 | public static final UnixDatagramChannel open() throws IOException { 58 | return new UnixDatagramChannel(); 59 | } 60 | 61 | public static final UnixDatagramChannel open(ProtocolFamily domain, int protocol) throws IOException { 62 | return new UnixDatagramChannel(domain, protocol); 63 | } 64 | 65 | public static final UnixDatagramChannel[] pair() throws IOException { 66 | int[] sockets = { -1, -1 }; 67 | Native.socketpair(ProtocolFamily.PF_UNIX, Sock.SOCK_DGRAM, 0, sockets); 68 | return new UnixDatagramChannel[] { 69 | new UnixDatagramChannel(sockets[0], State.CONNECTED, true), 70 | new UnixDatagramChannel(sockets[1], State.CONNECTED, true) 71 | }; 72 | } 73 | 74 | private UnixDatagramChannel() throws IOException { 75 | this(Native.socket(ProtocolFamily.PF_UNIX, Sock.SOCK_DGRAM, 0)); 76 | } 77 | 78 | UnixDatagramChannel(ProtocolFamily domain, int protocol) throws IOException 79 | { 80 | this(Native.socket(domain, Sock.SOCK_DGRAM, protocol)); 81 | } 82 | 83 | UnixDatagramChannel(int fd) { 84 | this(fd, State.IDLE, false); 85 | } 86 | 87 | UnixDatagramChannel(int fd, State initialState, boolean initialBoundState) { 88 | super(fd); 89 | stateLock.writeLock().lock(); 90 | try { 91 | state = initialState; 92 | bindHandler = new BindHandler(initialBoundState); 93 | } finally { 94 | stateLock.writeLock().unlock(); 95 | } 96 | } 97 | 98 | UnixDatagramChannel(int fd, UnixSocketAddress remote) throws IOException { 99 | this(fd); 100 | connect(remote); 101 | } 102 | 103 | @Override 104 | public UnixDatagramChannel bind(SocketAddress local) throws IOException { 105 | localAddress = bindHandler.bind(getFD(), local); 106 | return this; 107 | } 108 | 109 | public UnixDatagramChannel connect(UnixSocketAddress remote) { 110 | stateLock.writeLock().lock(); 111 | remoteAddress = remote; 112 | state = State.CONNECTED; 113 | stateLock.writeLock().unlock(); 114 | return this; 115 | } 116 | 117 | public UnixDatagramChannel disconnect() throws IOException { 118 | stateLock.writeLock().lock(); 119 | remoteAddress = null; 120 | state = State.IDLE; 121 | stateLock.writeLock().unlock(); 122 | return this; 123 | } 124 | 125 | boolean isBound() { 126 | return bindHandler.isBound(); 127 | } 128 | 129 | public boolean isConnected() { 130 | stateLock.readLock().lock(); 131 | boolean isConnected = state == State.CONNECTED; 132 | stateLock.readLock().unlock(); 133 | return isConnected; 134 | } 135 | 136 | public final UnixSocketAddress getRemoteSocketAddress() { 137 | if (!isConnected()) { 138 | return null; 139 | } 140 | return remoteAddress != null ? remoteAddress : (remoteAddress = Common.getpeername(getFD())); 141 | } 142 | 143 | public final UnixSocketAddress getLocalSocketAddress() { 144 | return localAddress != null ? localAddress : (localAddress = Common.getsockname(getFD())); 145 | } 146 | 147 | @Override 148 | public UnixSocketAddress receive(ByteBuffer src) throws IOException { 149 | UnixSocketAddress remote = new UnixSocketAddress(); 150 | int n = Native.recvfrom(getFD(), src, remote.getStruct()); 151 | if (n < 0) { 152 | throw new IOException(Native.getLastErrorString()); 153 | } 154 | return remote; 155 | } 156 | 157 | @Override 158 | public int send(ByteBuffer src, SocketAddress target) throws IOException { 159 | UnixSocketAddress remote = null; 160 | if (null == target) { 161 | if (isConnected()) { 162 | remote = remoteAddress; 163 | } else { 164 | throw new IllegalArgumentException("Destination address cannot be null on unconnected datagram sockets"); 165 | } 166 | } else { 167 | if (!(target instanceof UnixSocketAddress)) { 168 | throw new UnsupportedAddressTypeException(); 169 | } 170 | remote = (UnixSocketAddress)target; 171 | } 172 | SockAddrUnix sa = (null == remote) ? null : remote.getStruct(); 173 | int addrlen = (null == sa) ? 0 : sa.length(); 174 | int n = Native.sendto(getFD(), src, sa, addrlen); 175 | if (n < 0) { 176 | throw new IOException(Native.getLastErrorString()); 177 | } 178 | 179 | return n; 180 | } 181 | 182 | @Override 183 | public DatagramChannel connect(SocketAddress remote) throws IOException { 184 | if (remote instanceof UnixSocketAddress) { 185 | return connect(((UnixSocketAddress) remote)); 186 | } else { 187 | throw new UnsupportedAddressTypeException(); 188 | } 189 | } 190 | 191 | @Override 192 | public UnixDatagramSocket socket() { 193 | try { 194 | return new UnixDatagramSocket(this); 195 | } catch (SocketException e) { 196 | throw new NullPointerException("Could not create UnixDatagramSocket"); 197 | } 198 | } 199 | 200 | @Override 201 | public long write(ByteBuffer[] srcs, int offset, int length) 202 | throws IOException { 203 | 204 | if (state == State.CONNECTED) { 205 | return super.write(srcs, offset, length); 206 | } else if (state == State.IDLE) { 207 | return 0; 208 | } else { 209 | throw new ClosedChannelException(); 210 | } 211 | } 212 | 213 | @Override 214 | public int read(ByteBuffer dst) throws IOException { 215 | if (state == State.CONNECTED) { 216 | return super.read(dst); 217 | } else if (state == State.IDLE) { 218 | return 0; 219 | } else { 220 | throw new ClosedChannelException(); 221 | } 222 | } 223 | 224 | @Override 225 | public int write(ByteBuffer src) throws IOException { 226 | if (state == State.CONNECTED) { 227 | return super.write(src); 228 | } else if (state == State.IDLE) { 229 | return 0; 230 | } else { 231 | throw new ClosedChannelException(); 232 | } 233 | } 234 | 235 | 236 | @Override 237 | public SocketAddress getRemoteAddress() throws IOException { 238 | return remoteAddress; 239 | } 240 | 241 | @Override 242 | public SocketAddress getLocalAddress() throws IOException { 243 | return localAddress; 244 | } 245 | 246 | private static class DefaultOptionsHolder { 247 | static final Set> defaultOptions = defaultOptions(); 248 | 249 | private static Set> defaultOptions() { 250 | HashSet> set = new HashSet>(5); 251 | set.add(UnixSocketOptions.SO_SNDBUF); 252 | set.add(UnixSocketOptions.SO_SNDTIMEO); 253 | set.add(UnixSocketOptions.SO_RCVBUF); 254 | set.add(UnixSocketOptions.SO_RCVTIMEO); 255 | set.add(UnixSocketOptions.SO_PEERCRED); 256 | return Collections.unmodifiableSet(set); 257 | } 258 | } 259 | 260 | @Override 261 | public final Set> supportedOptions() { 262 | return DefaultOptionsHolder.defaultOptions; 263 | } 264 | 265 | @Override 266 | public T getOption(SocketOption name) throws IOException { 267 | if (!supportedOptions().contains(name)) { 268 | throw new UnsupportedOperationException("'" + name + "' not supported"); 269 | } 270 | return Common.getSocketOption(getFD(), name); 271 | } 272 | 273 | @Override 274 | public DatagramChannel setOption(SocketOption name, T value) 275 | throws IOException { 276 | if (name == null) { 277 | throw new IllegalArgumentException("name may not be null"); 278 | } 279 | if (!supportedOptions().contains(name)) { 280 | throw new UnsupportedOperationException("'" + name + "' not supported"); 281 | } 282 | Common.setSocketOption(getFD(), name, value); 283 | return this; 284 | } 285 | 286 | @Override 287 | public MembershipKey join(InetAddress group, NetworkInterface interf) { 288 | throw new UnsupportedOperationException("join is not supported"); 289 | } 290 | 291 | @Override 292 | public MembershipKey join(InetAddress group, NetworkInterface interf, InetAddress source) { 293 | throw new UnsupportedOperationException("join is not supported"); 294 | } 295 | 296 | } 297 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/UnixDatagramSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Fritz Elfert 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | package jnr.unixsocket; 20 | 21 | import java.io.IOException; 22 | import java.net.InetAddress; 23 | import java.net.SocketAddress; 24 | import java.net.SocketException; 25 | import java.net.DatagramPacket; 26 | import java.net.DatagramSocket; 27 | import java.nio.channels.DatagramChannel; 28 | import java.nio.channels.UnsupportedAddressTypeException; 29 | import java.util.concurrent.atomic.AtomicBoolean; 30 | 31 | /** 32 | * A SOCK_DGRAM variant of an AF_UNIX socket. 33 | * This specializaton of DatagramSocket delegates 34 | * most of it's funtionality to the corresponding 35 | * UnixDatagramChannel. 36 | */ 37 | public class UnixDatagramSocket extends DatagramSocket { 38 | 39 | private final UnixDatagramChannel chan; 40 | private final AtomicBoolean closed = new AtomicBoolean(false); 41 | 42 | /** 43 | * Constructs a new instance. 44 | * @param channel The channel to use. 45 | * @throws SocketException if the socket could not be created. 46 | */ 47 | UnixDatagramSocket(final UnixDatagramChannel channel) throws SocketException { 48 | chan = channel; 49 | } 50 | 51 | 52 | /** 53 | * Constructs a new unbound instance. 54 | * @throws SocketException if the socket could not be created. 55 | */ 56 | public UnixDatagramSocket() throws SocketException { 57 | chan = null; 58 | } 59 | 60 | /** 61 | * Binds this UnixDatagramSocket to a specific AF_UNIX address. 62 | *

63 | * If the address is {@code null}, then on Linux, an autobind will be performed, 64 | * which will bind this socket in Linux' abstract namespace on a unique path, chosen by 65 | * the system. On all other platforms, A temporary path in the regular filesystem will be chosen. 66 | *

67 | * @param local The {@link UnixSocketAddress} to bind to. 68 | * @throws SocketException if any error happens during the bind, or if the 69 | * socket is already bound. 70 | * @throws UnsupportedAddressTypeException if addr is a SocketAddress subclass 71 | * not supported by this socket. 72 | */ 73 | @Override 74 | public void bind(final SocketAddress local) throws SocketException { 75 | if (null != chan) { 76 | if (isClosed()) { 77 | throw new SocketException("Socket is closed"); 78 | } 79 | if (isBound()) { 80 | throw new SocketException("already bound"); 81 | } 82 | try { 83 | chan.bind(local); 84 | } catch (IOException e) { 85 | throw (SocketException)new SocketException().initCause(e); 86 | } 87 | } 88 | } 89 | 90 | @Override 91 | public synchronized void disconnect() { 92 | if (isClosed()) { 93 | return; 94 | } 95 | if (null != chan) { 96 | try { 97 | chan.disconnect(); 98 | } catch (IOException e) { 99 | ignore(); 100 | } 101 | } 102 | } 103 | 104 | @Override 105 | public synchronized void close() { 106 | if (null != chan && closed.compareAndSet(false, true)) { 107 | try { 108 | chan.close(); 109 | } catch (IOException e) { 110 | ignore(); 111 | } 112 | } 113 | } 114 | 115 | @Override 116 | public void connect(SocketAddress addr) throws SocketException { 117 | try { 118 | chan.connect(addr); 119 | } catch (IOException e) { 120 | throw (SocketException)new SocketException().initCause(e); 121 | } 122 | } 123 | 124 | @Override 125 | public void connect(InetAddress addr, int port) { 126 | throw new UnsupportedOperationException("connect(InetAddress, int) is not supported"); 127 | } 128 | 129 | @Override 130 | public DatagramChannel getChannel() { 131 | return chan; 132 | } 133 | 134 | /** 135 | * Returns the address to which this socket is connected (NOT implemented). 136 | * Since AF_UNIX sockets can not have an InetAddress, this returns always {@code null}. 137 | * Use {@link #getRemoteSocketAddress} instead, which always returns a {@link UnixSocketAddress}. 138 | * @return {@code null} always. 139 | */ 140 | @Override 141 | public InetAddress getInetAddress() { 142 | return null; 143 | } 144 | 145 | /** 146 | * Returns the address of the endpoint this socket is bound to. 147 | * 148 | * @return a {@code SocketAddress} representing the local endpoint of this 149 | * socket, or {@code null} if it is closed or not bound. 150 | * A non-null return value is always of type {@link UnixSocketAddress} 151 | * @see #bind(SocketAddress) 152 | */ 153 | @Override 154 | public SocketAddress getLocalSocketAddress() { 155 | if (isClosed()) { 156 | return null; 157 | } 158 | if (null == chan) { 159 | return null; 160 | } 161 | return chan.getLocalSocketAddress(); 162 | } 163 | 164 | /** 165 | * Returns the address of the endpoint this socket is connected to, or 166 | * {@code null} if it is unconnected. 167 | * 168 | * @return a {@code SocketAddress} representing the remote 169 | * endpoint of this socket, or {@code null} if it is 170 | * not connected. 171 | * A non-null return value is always of type {@link UnixSocketAddress} 172 | */ 173 | @Override 174 | public SocketAddress getRemoteSocketAddress() { 175 | if (!isConnected()) { 176 | return null; 177 | } 178 | return chan.getRemoteSocketAddress(); 179 | } 180 | 181 | @Override 182 | public boolean isBound() { 183 | if (null == chan) { 184 | return false; 185 | } 186 | return chan.isBound(); 187 | } 188 | 189 | @Override 190 | public boolean isClosed() { 191 | if (null == chan) { 192 | return false; 193 | } 194 | return closed.get(); 195 | } 196 | 197 | @Override 198 | public boolean isConnected() { 199 | if (null == chan) { 200 | return false; 201 | } 202 | return chan.isConnected(); 203 | } 204 | 205 | /** 206 | * Retrieves the credentials for this UNIX socket. Clients calling this 207 | * method will receive the server's credentials, and servers will receive 208 | * the client's credentials. User ID, group ID, and PID are supplied. 209 | * 210 | * See man unix 7; SCM_CREDENTIALS 211 | * 212 | * @throws UnsupportedOperationException if the underlying socket library 213 | * doesn't support the SO_PEERCRED option 214 | * @throws SocketException if fetching the socket option failed. 215 | * 216 | * @return the credentials of the remote; null if not connected 217 | */ 218 | public final Credentials getCredentials() throws SocketException { 219 | if (!chan.isConnected()) { 220 | return null; 221 | } 222 | try { 223 | return chan.getOption(UnixSocketOptions.SO_PEERCRED); 224 | } catch (IOException e) { 225 | throw (SocketException)new SocketException().initCause(e); 226 | } 227 | } 228 | 229 | @Override 230 | public int getReceiveBufferSize() throws SocketException { 231 | try { 232 | return chan.getOption(UnixSocketOptions.SO_RCVBUF).intValue(); 233 | } catch (IOException e) { 234 | throw (SocketException)new SocketException().initCause(e); 235 | } 236 | } 237 | 238 | @Override 239 | public int getSendBufferSize() throws SocketException { 240 | try { 241 | return chan.getOption(UnixSocketOptions.SO_SNDBUF).intValue(); 242 | } catch (IOException e) { 243 | throw (SocketException)new SocketException().initCause(e); 244 | } 245 | } 246 | 247 | @Override 248 | public int getSoTimeout() throws SocketException { 249 | try { 250 | return chan.getOption(UnixSocketOptions.SO_RCVTIMEO).intValue(); 251 | } catch (IOException e) { 252 | throw (SocketException)new SocketException().initCause(e); 253 | } 254 | } 255 | 256 | @Override 257 | public void setReceiveBufferSize(int size) throws SocketException { 258 | try { 259 | chan.setOption(UnixSocketOptions.SO_RCVBUF, Integer.valueOf(size)); 260 | } catch (IOException e) { 261 | throw (SocketException)new SocketException().initCause(e); 262 | } 263 | } 264 | 265 | @Override 266 | public void setSendBufferSize(int size) throws SocketException { 267 | try { 268 | chan.setOption(UnixSocketOptions.SO_SNDBUF, Integer.valueOf(size)); 269 | } catch (IOException e) { 270 | throw (SocketException)new SocketException().initCause(e); 271 | } 272 | } 273 | 274 | @Override 275 | public void setSoTimeout(int timeout) throws SocketException { 276 | try { 277 | chan.setOption(UnixSocketOptions.SO_RCVTIMEO, Integer.valueOf(timeout)); 278 | } catch (IOException e) { 279 | throw (SocketException)new SocketException().initCause(e); 280 | } 281 | } 282 | 283 | /** 284 | * Sends a datagram packet from this socket (NOT implemented). 285 | * Unfortunately, {@link java.net.DatagramPacket} is final and can not deal 286 | * with AF_UNIX addresses. Therefore, this functionality was omitted. 287 | * @see java.net.DatagramPacket 288 | * @see java.net.DatagramSocket#send 289 | * @throws UnsupportedOperationException always. 290 | */ 291 | @Override 292 | public void send(DatagramPacket p) throws IOException { 293 | throw new UnsupportedOperationException("sending DatagramPackets is not supported"); 294 | } 295 | 296 | /** 297 | * Receives a datagram packet from this socket (NOT implemented). 298 | * Unfortunately, {@link java.net.DatagramPacket} is final and can not deal 299 | * with AF_UNIX addresses. Therefore, this functionality was omitted. 300 | * @see java.net.DatagramPacket 301 | * @see java.net.DatagramSocket#receive 302 | * @throws UnsupportedOperationException always. 303 | */ 304 | @Override 305 | public synchronized void receive(DatagramPacket p) throws IOException { 306 | throw new UnsupportedOperationException("receiving DatagramPackets is not supported"); 307 | } 308 | 309 | private void ignore() { 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/UnixServerSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Wayne Meissner 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket; 20 | 21 | import java.io.IOException; 22 | import java.net.SocketAddress; 23 | 24 | import java.nio.channels.UnsupportedAddressTypeException; 25 | 26 | public class UnixServerSocket { 27 | final UnixServerSocketChannel channel; 28 | final int fd; 29 | volatile UnixSocketAddress localAddress; 30 | 31 | public UnixServerSocket() throws IOException { 32 | this.channel = new UnixServerSocketChannel(this); 33 | this.fd = channel.getFD(); 34 | } 35 | 36 | UnixServerSocket(UnixServerSocketChannel channel) { 37 | this.channel = channel; 38 | this.fd = channel.getFD(); 39 | } 40 | 41 | public UnixSocket accept() throws IOException { 42 | return new UnixSocket(channel.accept()); 43 | } 44 | 45 | public void bind(SocketAddress endpoint) throws IOException { 46 | bind(endpoint, 128); 47 | } 48 | 49 | public void bind(SocketAddress endpoint, int backlog) throws IOException { 50 | if (null != endpoint && !(endpoint instanceof UnixSocketAddress)) { 51 | throw new UnsupportedAddressTypeException(); 52 | } 53 | localAddress = Common.bind(fd, (UnixSocketAddress)endpoint); 54 | if (Native.listen(fd, backlog) < 0) { 55 | throw new IOException(Native.getLastErrorString()); 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/UnixServerSocketChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Wayne Meissner 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket; 20 | 21 | import jnr.constants.platform.ProtocolFamily; 22 | import jnr.constants.platform.Sock; 23 | import jnr.unixsocket.impl.AbstractNativeServerSocketChannel; 24 | import jnr.ffi.byref.IntByReference; 25 | 26 | import java.io.IOException; 27 | import java.nio.channels.ClosedChannelException; 28 | import java.nio.channels.NotYetBoundException; 29 | import java.nio.channels.SelectionKey; 30 | import java.nio.channels.spi.SelectorProvider; 31 | 32 | import static jnr.unixsocket.Native.getLastError; 33 | import static jnr.unixsocket.Native.getLastErrorString; 34 | 35 | /** 36 | * 37 | */ 38 | public class UnixServerSocketChannel extends AbstractNativeServerSocketChannel { 39 | 40 | private final UnixServerSocket socket; 41 | 42 | UnixServerSocketChannel(UnixServerSocket socket) throws IOException { 43 | super(Native.socket(ProtocolFamily.PF_UNIX, Sock.SOCK_STREAM, 0)); 44 | this.socket = new UnixServerSocket(this); 45 | } 46 | 47 | UnixServerSocketChannel(SelectorProvider provider, int fd) { 48 | super(provider, fd, SelectionKey.OP_ACCEPT | SelectionKey.OP_READ); 49 | this.socket = new UnixServerSocket(this); 50 | } 51 | 52 | public static UnixServerSocketChannel open() throws IOException { 53 | return new UnixServerSocket().channel; 54 | } 55 | 56 | public UnixSocketChannel accept() throws IOException { 57 | UnixSocketAddress remote = new UnixSocketAddress(); 58 | SockAddrUnix addr = remote.getStruct(); 59 | int maxLength = addr.getMaximumLength(); 60 | IntByReference len = new IntByReference(maxLength); 61 | 62 | int clientfd = -1; 63 | begin(); 64 | try { 65 | clientfd = Native.accept(getFD(), addr, len); 66 | } finally { 67 | end(clientfd >= 0); 68 | } 69 | 70 | if (clientfd < 0) { 71 | if (isBlocking()) { 72 | switch (getLastError()) { 73 | case EBADF: 74 | throw new ClosedChannelException(); 75 | case EINVAL: 76 | throw new NotYetBoundException(); 77 | default: 78 | throw new IOException("accept failed: " + getLastErrorString()); 79 | } 80 | } 81 | 82 | return null; 83 | } 84 | 85 | // Handle unnamed sockets and sockets in Linux' abstract namespace 86 | addr.updatePath(len.getValue()); 87 | 88 | // Always force the socket back to blocking mode 89 | Native.setBlocking(clientfd, true); 90 | 91 | return new UnixSocketChannel(clientfd); 92 | } 93 | 94 | public final UnixServerSocket socket() { 95 | return socket; 96 | } 97 | 98 | public final UnixSocketAddress getRemoteSocketAddress() { 99 | return null; 100 | } 101 | 102 | public final UnixSocketAddress getLocalSocketAddress() { 103 | return socket.localAddress; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/UnixSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Wayne Meissner 3 | * Copyright (C) 2016 Marcus Linke 4 | * 5 | * (ported from https://github.com/softprops/unisockets/blob/master/unisockets-core/src/main/scala/Socket.scala) 6 | * 7 | * This file is part of the JNR project. 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * 21 | */ 22 | package jnr.unixsocket; 23 | 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.io.OutputStream; 27 | import java.net.InetAddress; 28 | import java.net.SocketAddress; 29 | import java.net.SocketException; 30 | import java.nio.ByteBuffer; 31 | import java.nio.channels.Channels; 32 | import java.nio.channels.ReadableByteChannel; 33 | import java.nio.channels.SelectableChannel; 34 | import java.nio.channels.SocketChannel; 35 | import java.nio.channels.WritableByteChannel; 36 | import java.util.concurrent.atomic.AtomicBoolean; 37 | 38 | public class UnixSocket extends java.net.Socket { 39 | 40 | private UnixSocketChannel chan; 41 | 42 | private AtomicBoolean closed = new AtomicBoolean(false); 43 | private AtomicBoolean indown = new AtomicBoolean(false); 44 | private AtomicBoolean outdown = new AtomicBoolean(false); 45 | 46 | private InputStream in; 47 | private OutputStream out; 48 | 49 | public UnixSocket(UnixSocketChannel chan) { 50 | this.chan = chan; 51 | in = Channels.newInputStream(new UnselectableByteChannel(chan)); 52 | out = Channels.newOutputStream(new UnselectableByteChannel(chan)); 53 | } 54 | 55 | @Override 56 | public void bind(SocketAddress local) throws IOException { 57 | if (null != chan) { 58 | if (isClosed()) { 59 | throw new SocketException("Socket is closed"); 60 | } 61 | if (isBound()) { 62 | throw new SocketException("already bound"); 63 | } 64 | try { 65 | chan.bind(local); 66 | } catch (IOException e) { 67 | throw (SocketException)new SocketException().initCause(e); 68 | } 69 | } 70 | } 71 | 72 | @Override 73 | public void close() throws IOException { 74 | if (null != chan && closed.compareAndSet(false, true)) { 75 | try { 76 | chan.close(); 77 | } catch (IOException e) { 78 | ignore(); 79 | } 80 | } 81 | } 82 | 83 | @Override 84 | public void connect(SocketAddress addr) throws IOException { 85 | connect(addr, 0); 86 | } 87 | 88 | @Override 89 | public void connect(SocketAddress addr, int timeout) throws IOException { 90 | if (addr instanceof UnixSocketAddress) { 91 | chan.connect((UnixSocketAddress) addr); 92 | } else { 93 | throw new IllegalArgumentException("address of type " 94 | + addr.getClass() + " are not supported. Use " 95 | + UnixSocketAddress.class + " instead"); 96 | } 97 | } 98 | 99 | @Override 100 | public SocketChannel getChannel() { 101 | return chan; 102 | } 103 | 104 | @Override 105 | public InetAddress getInetAddress() { 106 | return null; 107 | } 108 | 109 | @Override 110 | public InputStream getInputStream() throws IOException { 111 | if (chan.isConnected()) { 112 | return in; 113 | } else { 114 | throw new IOException("not connected"); 115 | } 116 | } 117 | 118 | @Override 119 | public SocketAddress getLocalSocketAddress() { 120 | return chan.getLocalSocketAddress(); 121 | } 122 | 123 | @Override 124 | public OutputStream getOutputStream() throws IOException { 125 | if (chan.isConnected()) { 126 | return out; 127 | } else { 128 | throw new IOException("not connected"); 129 | } 130 | } 131 | 132 | @Override 133 | public SocketAddress getRemoteSocketAddress() { 134 | SocketAddress address = chan.getRemoteSocketAddress(); 135 | 136 | if (address != null) { 137 | return address; 138 | } else { 139 | return null; 140 | } 141 | } 142 | 143 | @Override 144 | public boolean isBound() { 145 | if (null == chan) { 146 | return false; 147 | } 148 | return chan.isBound(); 149 | } 150 | 151 | @Override 152 | public boolean isClosed() { 153 | return closed.get(); 154 | } 155 | 156 | @Override 157 | public boolean isConnected() { 158 | return chan.isConnected(); 159 | } 160 | 161 | @Override 162 | public boolean isInputShutdown() { 163 | return indown.get(); 164 | } 165 | 166 | @Override 167 | public boolean isOutputShutdown() { 168 | return outdown.get(); 169 | } 170 | 171 | @Override 172 | public void shutdownInput() throws IOException { 173 | if (indown.compareAndSet(false, true)) { 174 | chan.shutdownInput(); 175 | } 176 | } 177 | 178 | @Override 179 | public void shutdownOutput() throws IOException { 180 | if (outdown.compareAndSet(false, true)) { 181 | chan.shutdownOutput(); 182 | } 183 | } 184 | 185 | /** 186 | * Retrieves the credentials for this UNIX socket. Clients calling this 187 | * method will receive the server's credentials, and servers will receive 188 | * the client's credentials. User ID, group ID, and PID are supplied. 189 | * 190 | * See man unix 7; SCM_CREDENTIALS 191 | * 192 | * @throws UnsupportedOperationException if the underlying socket library 193 | * doesn't support the SO_PEERCRED option 194 | * @throws SocketException if fetching the socket option failed. 195 | * 196 | * @return the credentials of the remote; null if not connected 197 | */ 198 | public final Credentials getCredentials() throws SocketException { 199 | if (!chan.isConnected()) { 200 | return null; 201 | } 202 | try { 203 | return chan.getOption(UnixSocketOptions.SO_PEERCRED); 204 | } catch (IOException e) { 205 | throw (SocketException)new SocketException().initCause(e); 206 | } 207 | } 208 | 209 | @Override 210 | public boolean getKeepAlive() throws SocketException { 211 | try { 212 | return chan.getOption(UnixSocketOptions.SO_KEEPALIVE).booleanValue(); 213 | } catch (IOException e) { 214 | throw (SocketException)new SocketException().initCause(e); 215 | } 216 | } 217 | 218 | @Override 219 | public int getReceiveBufferSize() throws SocketException { 220 | try { 221 | return chan.getOption(UnixSocketOptions.SO_RCVBUF).intValue(); 222 | } catch (IOException e) { 223 | throw (SocketException)new SocketException().initCause(e); 224 | } 225 | } 226 | 227 | @Override 228 | public int getSendBufferSize() throws SocketException { 229 | try { 230 | return chan.getOption(UnixSocketOptions.SO_SNDBUF).intValue(); 231 | } catch (IOException e) { 232 | throw (SocketException)new SocketException().initCause(e); 233 | } 234 | } 235 | 236 | @Override 237 | public int getSoTimeout() throws SocketException { 238 | try { 239 | return chan.getOption(UnixSocketOptions.SO_RCVTIMEO).intValue(); 240 | } catch (IOException e) { 241 | throw (SocketException)new SocketException().initCause(e); 242 | } 243 | } 244 | 245 | @Override 246 | public void setKeepAlive(boolean on) throws SocketException { 247 | try { 248 | chan.setOption(UnixSocketOptions.SO_KEEPALIVE, Boolean.valueOf(on)); 249 | } catch (IOException e) { 250 | throw (SocketException)new SocketException().initCause(e); 251 | } 252 | } 253 | 254 | @Override 255 | public void setReceiveBufferSize(int size) throws SocketException { 256 | try { 257 | chan.setOption(UnixSocketOptions.SO_RCVBUF, Integer.valueOf(size)); 258 | } catch (IOException e) { 259 | throw (SocketException)new SocketException().initCause(e); 260 | } 261 | } 262 | 263 | @Override 264 | public void setSendBufferSize(int size) throws SocketException { 265 | try { 266 | chan.setOption(UnixSocketOptions.SO_SNDBUF, Integer.valueOf(size)); 267 | } catch (IOException e) { 268 | throw (SocketException)new SocketException().initCause(e); 269 | } 270 | } 271 | 272 | @Override 273 | public void setSoTimeout(int timeout) throws SocketException { 274 | try { 275 | chan.setOption(UnixSocketOptions.SO_RCVTIMEO, Integer.valueOf(timeout)); 276 | } catch (IOException e) { 277 | throw (SocketException)new SocketException().initCause(e); 278 | } 279 | } 280 | 281 | private void ignore() { 282 | } 283 | 284 | /** 285 | * A byte channel that doesn't implement {@link SelectableChannel}. Though 286 | * that type isn't in the public API, if the channel passed in implements 287 | * that interface then unwanted synchronization is performed which can harm 288 | * concurrency and can cause deadlocks. 289 | * 290 | * https://bugs.openjdk.java.net/browse/JDK-4774871 291 | */ 292 | static final class UnselectableByteChannel implements ReadableByteChannel, WritableByteChannel { 293 | private final UnixSocketChannel channel; 294 | 295 | UnselectableByteChannel(UnixSocketChannel channel) { 296 | this.channel = channel; 297 | } 298 | 299 | @Override 300 | public int write(ByteBuffer src) throws IOException { 301 | return channel.write(src); 302 | } 303 | 304 | @Override 305 | public int read(ByteBuffer dst) throws IOException { 306 | return channel.read(dst); 307 | } 308 | 309 | @Override 310 | public boolean isOpen() { 311 | return channel.isOpen(); 312 | } 313 | 314 | @Override 315 | public void close() throws IOException { 316 | channel.close(); 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/UnixSocketAddress.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Wayne Meissner 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket; 20 | 21 | import java.io.IOException; 22 | import java.io.ObjectInputStream; 23 | import java.io.ObjectOutputStream; 24 | 25 | import jnr.constants.platform.ProtocolFamily; 26 | 27 | /** 28 | * This class represents an AF_UNIX-style socket address. 29 | * On Linux, it supports the platform-specific abstract name space. 30 | *

31 | * Using an abstract name space is denoted by the socket path starting with 32 | * a NUL byte. Sockets in abstract name space have no entry in the file system. 33 | * When linux performs autobind, it constructs the resulting path with a 34 | * leading NUL, followed by a unique 5-digit hexadecimal number. 35 | */ 36 | public class UnixSocketAddress extends java.net.SocketAddress { 37 | 38 | private static final long serialVersionUID = 4821337010221569096L; 39 | private transient SockAddrUnix address; 40 | 41 | UnixSocketAddress() { 42 | address = SockAddrUnix.create(); 43 | address.setFamily(ProtocolFamily.PF_UNIX); 44 | } 45 | 46 | public UnixSocketAddress(java.io.File path) { 47 | address = SockAddrUnix.create(); 48 | address.setFamily(ProtocolFamily.PF_UNIX); 49 | address.setPath(path.getPath()); 50 | } 51 | 52 | public UnixSocketAddress(final String path) { 53 | address = SockAddrUnix.create(); 54 | address.setFamily(ProtocolFamily.PF_UNIX); 55 | address.setPath(path); 56 | } 57 | 58 | SockAddrUnix getStruct() { 59 | return address; 60 | } 61 | 62 | int length() { 63 | return address.length(); 64 | } 65 | 66 | /** 67 | * Retrieves the path. 68 | * @return The path of this AF_UNIX address. 69 | * Note: On Linux, can contain a leading NUL byte, if this address 70 | * resides in abstract namespace. 71 | */ 72 | public String path() { 73 | return address.getPath(); 74 | } 75 | 76 | /** 77 | * Returns a human readable path. 78 | * On Linux, AF_UNIX sockets can be bound/connected in abstract namespace. 79 | * This is denoted by a leading NUL byte in the path. 80 | * In order to be properly displayed, this method returns a path prefixed 81 | * by '@' like netstat, lsof an similar tools. 82 | * @return The human readable path of this address. 83 | */ 84 | public String humanReadablePath() { 85 | String ret = path(); 86 | // Handle abstract namespace like netstat: replace NUL by '@' 87 | if (ret.indexOf('\000') == 0) { 88 | return ret.replace('\000', '@'); 89 | } 90 | return ret; 91 | } 92 | 93 | /** 94 | * Retrieves a human readable description of this address. 95 | * @return The human readable description of this address. 96 | */ 97 | @Override 98 | public String toString() { 99 | return "[family=" + address.getFamily() + " path=" + humanReadablePath() + "]"; 100 | } 101 | 102 | @Override 103 | public boolean equals(Object _other) { 104 | if (!(_other instanceof UnixSocketAddress)) { 105 | return false; 106 | } 107 | 108 | UnixSocketAddress other = (UnixSocketAddress)_other; 109 | 110 | return address.getFamily() == other.address.getFamily() && 111 | path().equals(other.path()); 112 | } 113 | 114 | @Override 115 | public int hashCode() { 116 | return address.hashCode(); 117 | } 118 | 119 | // Serializable 120 | private void writeObject(ObjectOutputStream o) throws IOException { 121 | o.defaultWriteObject(); 122 | o.writeObject(path()); 123 | } 124 | 125 | private void readObject(ObjectInputStream o) 126 | throws IOException, ClassNotFoundException { 127 | o.defaultReadObject(); 128 | String path = (String)o.readObject(); 129 | if (null == address) { 130 | address = SockAddrUnix.create(); 131 | } 132 | address.setPath(path); 133 | address.setFamily(ProtocolFamily.PF_UNIX); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/UnixSocketChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 Wayne Meissner 3 | * Copyright (C) 2016 Marcus Linke 4 | * 5 | * This file is part of the JNR project. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package jnr.unixsocket; 21 | 22 | import java.io.IOException; 23 | import java.net.SocketAddress; 24 | import java.net.SocketOption; 25 | import java.nio.ByteBuffer; 26 | import java.nio.channels.ClosedChannelException; 27 | import java.nio.channels.SocketChannel; 28 | import java.nio.channels.UnsupportedAddressTypeException; 29 | import java.util.Collections; 30 | import java.util.HashSet; 31 | import java.util.Set; 32 | import java.util.concurrent.locks.ReadWriteLock; 33 | import java.util.concurrent.locks.ReentrantReadWriteLock; 34 | 35 | import jnr.constants.platform.Errno; 36 | import jnr.constants.platform.ProtocolFamily; 37 | import jnr.constants.platform.Sock; 38 | import jnr.unixsocket.impl.AbstractNativeSocketChannel; 39 | import jnr.ffi.LastError; 40 | 41 | /** 42 | * A {@link java.nio.channels.Channel} implementation that uses a native unix 43 | * socket 44 | */ 45 | public class UnixSocketChannel extends AbstractNativeSocketChannel { 46 | enum State { 47 | UNINITIALIZED, 48 | CONNECTED, 49 | IDLE, 50 | CONNECTING, 51 | } 52 | 53 | private State state; 54 | private UnixSocketAddress remoteAddress = null; 55 | private UnixSocketAddress localAddress = null; 56 | private final ReadWriteLock stateLock = new ReentrantReadWriteLock(); 57 | private final BindHandler bindHandler; 58 | 59 | public static final UnixSocketChannel open() throws IOException { 60 | return new UnixSocketChannel(); 61 | } 62 | 63 | public static final UnixSocketChannel open(UnixSocketAddress remote) 64 | throws IOException { 65 | UnixSocketChannel channel = new UnixSocketChannel(); 66 | 67 | try { 68 | channel.connect(remote); 69 | } catch (IOException e) { 70 | channel.close(); 71 | throw e; 72 | } 73 | return channel; 74 | } 75 | 76 | public static final UnixSocketChannel create() throws IOException { 77 | return new UnixSocketChannel(); 78 | } 79 | 80 | public static final UnixSocketChannel[] pair() throws IOException { 81 | int[] sockets = { -1, -1 }; 82 | Native.socketpair(ProtocolFamily.PF_UNIX, Sock.SOCK_STREAM, 0, sockets); 83 | return new UnixSocketChannel[] { 84 | new UnixSocketChannel(sockets[0], State.CONNECTED, true), 85 | new UnixSocketChannel(sockets[1], State.CONNECTED, true) }; 86 | } 87 | 88 | /** 89 | * Create a UnixSocketChannel to wrap an existing file descriptor 90 | * (presumably itself a UNIX socket). 91 | * 92 | * @param fd 93 | * the file descriptor to wrap 94 | * @return the new UnixSocketChannel instance 95 | */ 96 | public static final UnixSocketChannel fromFD(int fd) { 97 | return new UnixSocketChannel(fd); 98 | } 99 | 100 | UnixSocketChannel() throws IOException { 101 | this(Native.socket(ProtocolFamily.PF_UNIX, Sock.SOCK_STREAM, 0)); 102 | } 103 | 104 | UnixSocketChannel(int fd) { 105 | this(fd, State.CONNECTED, false); 106 | } 107 | 108 | UnixSocketChannel(int fd, State initialState, boolean initialBoundState) { 109 | super(fd); 110 | stateLock.writeLock().lock(); 111 | try { 112 | state = initialState; 113 | bindHandler = new BindHandler(initialBoundState); 114 | } finally { 115 | stateLock.writeLock().unlock(); 116 | } 117 | } 118 | 119 | private boolean doConnect(SockAddrUnix remote) throws IOException { 120 | if (Native.connect(getFD(), remote, remote.length()) != 0) { 121 | Errno error = Errno.valueOf(LastError.getLastError(jnr.ffi.Runtime 122 | .getSystemRuntime())); 123 | 124 | switch (error) { 125 | case EAGAIN: 126 | case EWOULDBLOCK: 127 | return false; 128 | 129 | default: 130 | throw new IOException(error.toString()); 131 | } 132 | } 133 | 134 | return true; 135 | } 136 | 137 | public boolean connect(UnixSocketAddress remote) throws IOException { 138 | remoteAddress = remote; 139 | if (!doConnect(remoteAddress.getStruct())) { 140 | stateLock.writeLock().lock(); 141 | state = State.CONNECTING; 142 | stateLock.writeLock().unlock(); 143 | return false; 144 | 145 | } else { 146 | stateLock.writeLock().lock(); 147 | state = State.CONNECTED; 148 | stateLock.writeLock().unlock(); 149 | return true; 150 | } 151 | } 152 | 153 | boolean isBound() { 154 | return bindHandler.isBound(); 155 | } 156 | 157 | public boolean isConnected() { 158 | stateLock.readLock().lock(); 159 | boolean result = state == State.CONNECTED; 160 | stateLock.readLock().unlock(); 161 | return result; 162 | } 163 | 164 | private boolean isIdle() { 165 | stateLock.readLock().lock(); 166 | boolean result = state == State.IDLE; 167 | stateLock.readLock().unlock(); 168 | return result; 169 | } 170 | 171 | public boolean isConnectionPending() { 172 | stateLock.readLock().lock(); 173 | boolean isConnectionPending = state == State.CONNECTING; 174 | stateLock.readLock().unlock(); 175 | return isConnectionPending; 176 | } 177 | 178 | public boolean finishConnect() throws IOException { 179 | stateLock.writeLock().lock(); 180 | try { 181 | switch (state) { 182 | case CONNECTED: 183 | return true; 184 | 185 | case CONNECTING: 186 | if (!doConnect(remoteAddress.getStruct())) { 187 | return false; 188 | } 189 | state = State.CONNECTED; 190 | return true; 191 | 192 | default: 193 | throw new IllegalStateException( 194 | "socket is not waiting for connect to complete"); 195 | } 196 | } finally { 197 | stateLock.writeLock().unlock(); 198 | } 199 | } 200 | 201 | public final UnixSocketAddress getRemoteSocketAddress() { 202 | if (!isConnected()) { 203 | return null; 204 | } 205 | 206 | if (remoteAddress != null) { 207 | return remoteAddress; 208 | } else { 209 | remoteAddress = Common.getpeername(getFD()); 210 | return remoteAddress; 211 | } 212 | } 213 | 214 | public final UnixSocketAddress getLocalSocketAddress() { 215 | if (localAddress != null) { 216 | return localAddress; 217 | } else { 218 | localAddress = Common.getsockname(getFD()); 219 | return localAddress; 220 | } 221 | } 222 | 223 | @Override 224 | public boolean connect(SocketAddress remote) throws IOException { 225 | if (remote instanceof UnixSocketAddress) { 226 | return connect(((UnixSocketAddress) remote)); 227 | } else { 228 | throw new UnsupportedAddressTypeException(); 229 | } 230 | } 231 | 232 | @Override 233 | public UnixSocket socket() { 234 | return new UnixSocket(this); 235 | } 236 | 237 | @Override 238 | public long write(ByteBuffer[] srcs, int offset, int length) 239 | throws IOException { 240 | 241 | if (isConnected()) { 242 | return super.write(srcs, offset, length); 243 | } else if (isIdle()) { 244 | return 0; 245 | } else { 246 | throw new ClosedChannelException(); 247 | } 248 | } 249 | 250 | @Override 251 | public int read(ByteBuffer dst) throws IOException { 252 | if (isConnected()) { 253 | return super.read(dst); 254 | } else if (isIdle()) { 255 | return 0; 256 | } else { 257 | throw new ClosedChannelException(); 258 | } 259 | } 260 | 261 | @Override 262 | public int write(ByteBuffer src) throws IOException { 263 | if (isConnected()) { 264 | return super.write(src); 265 | } else if (isIdle()) { 266 | return 0; 267 | } else { 268 | throw new ClosedChannelException(); 269 | } 270 | } 271 | 272 | @Override 273 | public SocketAddress getRemoteAddress() throws IOException { 274 | return remoteAddress; 275 | } 276 | 277 | @Override 278 | public SocketAddress getLocalAddress() throws IOException { 279 | return localAddress; 280 | } 281 | 282 | private static class DefaultOptionsHolder { 283 | static final Set> defaultOptions = defaultOptions(); 284 | 285 | private static Set> defaultOptions() { 286 | HashSet> set = new HashSet>(5); 287 | set.add(UnixSocketOptions.SO_SNDBUF); 288 | set.add(UnixSocketOptions.SO_SNDTIMEO); 289 | set.add(UnixSocketOptions.SO_RCVBUF); 290 | set.add(UnixSocketOptions.SO_RCVTIMEO); 291 | set.add(UnixSocketOptions.SO_PEERCRED); 292 | set.add(UnixSocketOptions.SO_KEEPALIVE); 293 | set.add(UnixSocketOptions.SO_PASSCRED); 294 | return Collections.unmodifiableSet(set); 295 | } 296 | } 297 | 298 | @Override 299 | public final Set> supportedOptions() { 300 | return DefaultOptionsHolder.defaultOptions; 301 | } 302 | 303 | @Override 304 | public T getOption(SocketOption name) throws IOException { 305 | if (!supportedOptions().contains(name)) { 306 | throw new UnsupportedOperationException("'" + name 307 | + "' not supported"); 308 | } 309 | return Common.getSocketOption(getFD(), name); 310 | } 311 | 312 | @Override 313 | public SocketChannel setOption(SocketOption name, T value) 314 | throws IOException { 315 | if (name == null) { 316 | throw new IllegalArgumentException("name may not be null"); 317 | } 318 | if (!supportedOptions().contains(name)) { 319 | throw new UnsupportedOperationException("'" + name 320 | + "' not supported"); 321 | } 322 | Common.setSocketOption(getFD(), name, value); 323 | return this; 324 | } 325 | 326 | @Override 327 | public synchronized UnixSocketChannel bind(SocketAddress local) throws IOException { 328 | localAddress = bindHandler.bind(getFD(), local); 329 | return this; 330 | } 331 | 332 | } 333 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/UnixSocketOptions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Fritz Elfert 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket; 20 | 21 | import java.net.SocketOption; 22 | 23 | /** 24 | * Defines common socket options for AF_UNIX sockets. 25 | */ 26 | public final class UnixSocketOptions { 27 | 28 | private static class GenericOption implements SocketOption { 29 | private final String name; 30 | private final Class type; 31 | GenericOption(String name, Class type) { 32 | this.name = name; 33 | this.type = type; 34 | } 35 | @Override public String name() { return name; } 36 | @Override public Class type() { return type; } 37 | @Override public String toString() { return name; } 38 | } 39 | 40 | /** 41 | * Get/Set size of the socket send buffer. 42 | */ 43 | public static final SocketOption SO_SNDBUF = 44 | new GenericOption("SO_SNDBUF", Integer.class); 45 | 46 | /** 47 | * Get/Set send timeout. 48 | */ 49 | public static final SocketOption SO_SNDTIMEO = 50 | new GenericOption("SO_SNDTIMEO", Integer.class); 51 | 52 | /** 53 | * Get/Set size of the socket receive buffer. 54 | */ 55 | public static final SocketOption SO_RCVBUF = 56 | new GenericOption("SO_RCVBUF", Integer.class); 57 | 58 | /** 59 | * Get/Set receive timeout. 60 | */ 61 | public static final SocketOption SO_RCVTIMEO = 62 | new GenericOption("SO_RCVTIMEO", Integer.class); 63 | 64 | /** 65 | * Keep connection alive. 66 | */ 67 | public static final SocketOption SO_KEEPALIVE = 68 | new GenericOption("SO_KEEPALIVE", Boolean.class); 69 | 70 | /** 71 | * Fetch peer credentials. 72 | */ 73 | public static final SocketOption SO_PEERCRED = 74 | new GenericOption("SO_PEERCRED", Credentials.class); 75 | 76 | /** 77 | * Enable credential transmission. 78 | */ 79 | public static final SocketOption SO_PASSCRED = 80 | new GenericOption("SO_PASSCRED", Boolean.class); 81 | 82 | } 83 | 84 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/impl/AbstractNativeDatagramChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Fritz Elfert 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket.impl; 20 | 21 | import jnr.enxio.channels.Native; 22 | import jnr.enxio.channels.NativeSelectableChannel; 23 | import jnr.enxio.channels.NativeSelectorProvider; 24 | 25 | import java.io.IOException; 26 | import java.nio.ByteBuffer; 27 | import java.nio.channels.ByteChannel; 28 | import java.nio.channels.DatagramChannel; 29 | import java.nio.channels.spi.SelectorProvider; 30 | 31 | public abstract class AbstractNativeDatagramChannel extends DatagramChannel 32 | implements ByteChannel, NativeSelectableChannel { 33 | 34 | private final Common common; 35 | 36 | public AbstractNativeDatagramChannel(int fd) { 37 | this(NativeSelectorProvider.getInstance(), fd); 38 | } 39 | 40 | AbstractNativeDatagramChannel(SelectorProvider provider, int fd) { 41 | super(provider); 42 | common = new Common(fd); 43 | } 44 | 45 | public void setFD(int fd) { 46 | common.setFD(fd); 47 | } 48 | 49 | public final int getFD() { 50 | return common.getFD(); 51 | } 52 | 53 | @Override 54 | protected void implCloseSelectableChannel() throws IOException { 55 | Native.close(common.getFD()); 56 | } 57 | 58 | @Override 59 | protected void implConfigureBlocking(boolean block) throws IOException { 60 | Native.setBlocking(common.getFD(), block); 61 | } 62 | 63 | public int read(ByteBuffer dst) throws IOException { 64 | return common.read(dst); 65 | } 66 | 67 | @Override 68 | public long read(ByteBuffer[] dsts, int offset, 69 | int length) throws IOException { 70 | return common.read(dsts, offset, length); 71 | } 72 | 73 | public int write(ByteBuffer src) throws IOException { 74 | return common.write(src); 75 | } 76 | 77 | @Override 78 | public long write(ByteBuffer[] srcs, int offset, 79 | int length) throws IOException { 80 | return common.write(srcs, offset, length); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/impl/AbstractNativeServerSocketChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Jesse Wilson 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket.impl; 20 | 21 | import jnr.constants.platform.Shutdown; 22 | import jnr.enxio.channels.Native; 23 | import jnr.enxio.channels.NativeServerSocketChannel; 24 | 25 | import java.io.IOException; 26 | import java.nio.channels.spi.SelectorProvider; 27 | 28 | public abstract class AbstractNativeServerSocketChannel extends NativeServerSocketChannel { 29 | public AbstractNativeServerSocketChannel(int fd) { 30 | super(fd); 31 | } 32 | 33 | public AbstractNativeServerSocketChannel(SelectorProvider provider, int fd, int ops) { 34 | super(provider, fd, ops); 35 | } 36 | 37 | @Override 38 | protected void implCloseSelectableChannel() throws IOException { 39 | // Shutdown to interrupt any potentially blocked threads. This is necessary on Linux. 40 | Native.shutdown(getFD(), SHUT_RD); 41 | Native.close(getFD()); 42 | } 43 | 44 | private static final int SHUT_RD = Shutdown.SHUT_RD.intValue(); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/impl/AbstractNativeSocketChannel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Marcus Linke 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket.impl; 20 | 21 | import java.io.IOException; 22 | import java.nio.ByteBuffer; 23 | import java.nio.channels.ByteChannel; 24 | import java.nio.channels.SocketChannel; 25 | import java.nio.channels.spi.SelectorProvider; 26 | 27 | import jnr.constants.platform.Errno; 28 | import jnr.constants.platform.Shutdown; 29 | import jnr.enxio.channels.Native; 30 | import jnr.enxio.channels.NativeException; 31 | import jnr.enxio.channels.NativeSelectableChannel; 32 | import jnr.enxio.channels.NativeSelectorProvider; 33 | 34 | public abstract class AbstractNativeSocketChannel extends SocketChannel 35 | implements ByteChannel, NativeSelectableChannel { 36 | 37 | private final Common common; 38 | 39 | public AbstractNativeSocketChannel(int fd) { 40 | this(NativeSelectorProvider.getInstance(), fd); 41 | } 42 | 43 | AbstractNativeSocketChannel(SelectorProvider provider, int fd) { 44 | super(provider); 45 | common = new Common(fd); 46 | } 47 | 48 | public void setFD(int fd) { 49 | common.setFD(fd); 50 | } 51 | 52 | public final int getFD() { 53 | return common.getFD(); 54 | } 55 | 56 | @Override 57 | protected void implCloseSelectableChannel() throws IOException { 58 | if (this.isConnected()) { 59 | this.shutdownInput(); 60 | this.shutdownOutput(); 61 | } 62 | 63 | Native.close(common.getFD()); 64 | } 65 | 66 | @Override 67 | protected void implConfigureBlocking(boolean block) throws IOException { 68 | Native.setBlocking(common.getFD(), block); 69 | } 70 | 71 | public int read(ByteBuffer dst) throws IOException { 72 | return common.read(dst); 73 | } 74 | 75 | @Override 76 | public long read(ByteBuffer[] dsts, int offset, 77 | int length) throws IOException { 78 | return common.read(dsts, offset, length); 79 | } 80 | 81 | public int write(ByteBuffer src) throws IOException { 82 | return common.write(src); 83 | } 84 | 85 | @Override 86 | public long write(ByteBuffer[] srcs, int offset, 87 | int length) throws IOException { 88 | return common.write(srcs, offset, length); 89 | } 90 | 91 | @Override 92 | public SocketChannel shutdownInput() throws IOException { 93 | int n = Native.shutdown(common.getFD(), SHUT_RD); 94 | if (n < 0 && Native.getLastError() != Errno.ENOTCONN) { 95 | throw new NativeException(Native.getLastErrorString(), Native.getLastError()); 96 | } 97 | return this; 98 | } 99 | 100 | @Override 101 | public SocketChannel shutdownOutput() throws IOException { 102 | int n = Native.shutdown(common.getFD(), SHUT_WR); 103 | if (n < 0 && Native.getLastError() != Errno.ENOTCONN) { 104 | throw new NativeException(Native.getLastErrorString(), Native.getLastError()); 105 | } 106 | return this; 107 | } 108 | 109 | private static final int SHUT_RD = Shutdown.SHUT_RD.intValue(); 110 | private static final int SHUT_WR = Shutdown.SHUT_WR.intValue(); 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/jnr/unixsocket/impl/Common.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Fritz Elfert 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package jnr.unixsocket.impl; 20 | 21 | import java.io.IOException; 22 | import java.nio.ByteBuffer; 23 | 24 | import jnr.constants.platform.Errno; 25 | import jnr.enxio.channels.Native; 26 | import jnr.enxio.channels.NativeException; 27 | 28 | /** 29 | * Helper class, providing common methods. 30 | */ 31 | final class Common { 32 | 33 | private int _fd = -1; 34 | 35 | Common(int fd) { 36 | _fd = fd; 37 | } 38 | 39 | void setFD(int fd) { 40 | _fd = fd; 41 | } 42 | 43 | int getFD() { 44 | return _fd; 45 | } 46 | 47 | int read(ByteBuffer dst) throws IOException { 48 | 49 | ByteBuffer buffer = ByteBuffer.allocate(dst.remaining()); 50 | 51 | int n = Native.read(_fd, buffer); 52 | 53 | buffer.flip(); 54 | 55 | dst.put(buffer); 56 | 57 | switch (n) { 58 | case 0: 59 | return -1; 60 | 61 | case -1: 62 | Errno lastError = Native.getLastError(); 63 | switch (lastError) { 64 | case EAGAIN: 65 | case EWOULDBLOCK: 66 | return 0; 67 | 68 | default: 69 | throw new NativeException(Native.getLastErrorString(), lastError); 70 | } 71 | 72 | default: { 73 | 74 | return n; 75 | } 76 | } 77 | } 78 | 79 | long read(ByteBuffer[] dsts, int offset, int length) 80 | throws IOException { 81 | long total = 0; 82 | 83 | for (int i = 0; i < length; i++) { 84 | ByteBuffer dst = dsts[offset + i]; 85 | long read = read(dst); 86 | if (read == -1) { 87 | return read; 88 | } 89 | total += read; 90 | } 91 | 92 | return total; 93 | } 94 | 95 | int write(ByteBuffer src) throws IOException { 96 | 97 | int r = src.remaining(); 98 | 99 | ByteBuffer buffer = ByteBuffer.allocate(r); 100 | 101 | buffer.put(src); 102 | 103 | buffer.position(0); 104 | 105 | int n = Native.write(_fd, buffer); 106 | 107 | if (n >=0 ) { 108 | if (n < r) { 109 | src.position(src.position()-(r-n)); 110 | } 111 | } else { 112 | Errno lastError = Native.getLastError(); 113 | switch (lastError) { 114 | case EAGAIN: 115 | case EWOULDBLOCK: 116 | src.position(src.position()-r); 117 | return 0; 118 | default: 119 | throw new NativeException(Native.getLastErrorString(), lastError); 120 | } 121 | } 122 | 123 | return n; 124 | } 125 | 126 | long write(ByteBuffer[] srcs, int offset, int length) throws IOException { 127 | 128 | long result = 0; 129 | 130 | for (int index = offset; index < length; ++index) { 131 | ByteBuffer buffer = srcs[index]; 132 | int remaining = buffer.remaining(); 133 | int written = 0; 134 | while (true) { 135 | int w = write(buffer); 136 | written += w; 137 | if (w == 0 || written == remaining) { 138 | break; 139 | } 140 | } 141 | result += written; 142 | if (written < remaining) { 143 | break; 144 | } 145 | } 146 | 147 | return result; 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/BasicDatagramFunctionalityTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Fritz Elfert 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | package jnr.unixsocket; 20 | 21 | import static junit.framework.Assert.assertEquals; 22 | import static junit.framework.Assert.assertFalse; 23 | import static junit.framework.Assert.assertTrue; 24 | import static junit.framework.Assert.fail; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.net.SocketException; 29 | import java.nio.ByteBuffer; 30 | import java.nio.channels.AlreadyBoundException; 31 | import java.nio.channels.DatagramChannel; 32 | import java.nio.charset.StandardCharsets; 33 | import java.nio.file.Files; 34 | 35 | import jnr.ffi.Platform; 36 | import jnr.ffi.Platform.OS; 37 | 38 | import org.junit.Assume; 39 | import org.junit.Test; 40 | 41 | public class BasicDatagramFunctionalityTest { 42 | private static final String DATA = "foo bar baz. The quick brown fox jumps over the lazy dog. "; 43 | volatile Throwable serverException; 44 | volatile long received = 0; 45 | 46 | private UnixSocketAddress makeAddress() throws IOException { 47 | File socketFile = Files.createTempFile("jnr-unixsocket-test", ".sock").toFile(); 48 | socketFile.delete(); 49 | socketFile.deleteOnExit(); 50 | return new UnixSocketAddress(socketFile); 51 | } 52 | 53 | private void basicOperation(final long minBytesToSend) throws Throwable { 54 | serverException = null; 55 | final StringBuffer rxdata = new StringBuffer(); 56 | final StringBuffer txdata = new StringBuffer(); 57 | final ByteBuffer rxbuf = ByteBuffer.allocate(1024); 58 | final ByteBuffer txbuf = ByteBuffer.allocate(2024); 59 | final UnixSocketAddress serverAddress = makeAddress(); 60 | 61 | Thread serverThread = new Thread("server side") { 62 | final UnixDatagramChannel serverChannel = UnixDatagramChannel.open().bind(serverAddress); 63 | 64 | public void run() { 65 | while (null == serverException) { 66 | try { 67 | rxbuf.clear(); 68 | serverChannel.receive(rxbuf); 69 | rxbuf.flip(); 70 | int count = rxbuf.limit(); 71 | rxdata.append(StandardCharsets.UTF_8.decode(rxbuf).toString()); 72 | received += count;; 73 | } catch (IOException ex) { 74 | serverException = ex; 75 | } 76 | } 77 | } 78 | }; 79 | serverThread.start(); 80 | 81 | // client logic 82 | DatagramChannel clientChannel = UnixDatagramChannel.open(); 83 | received = 0; 84 | long written = 0; 85 | while (null == serverException && written < minBytesToSend) { 86 | txbuf.put(StandardCharsets.UTF_8.encode(DATA)); 87 | txbuf.flip(); 88 | written += clientChannel.send(txbuf, serverAddress); 89 | txbuf.compact(); 90 | txdata.append(DATA); 91 | if (null != serverException) { 92 | throw new Exception().initCause(serverException); 93 | } 94 | } 95 | clientChannel.close(); 96 | while (null == serverException && received < written) { 97 | Thread.sleep(100); 98 | } 99 | 100 | assertTrue("More than 0 bytes written", written > 0); 101 | assertEquals("received", written, received); 102 | assertEquals("received data", txdata.toString(), rxdata.toString()); 103 | } 104 | 105 | @Test 106 | public void smallBasicOperationTest() throws Throwable { 107 | basicOperation(DATA.length()); 108 | } 109 | 110 | @Test 111 | public void largeBasicOperationTest() throws Throwable { 112 | Assume.assumeTrue(OS.LINUX == Platform.getNativePlatform().getOS()); 113 | 114 | basicOperation(1000L * DATA.length()); 115 | } 116 | 117 | @Test 118 | public void doubleBindTest() throws Exception { 119 | UnixDatagramChannel ch = UnixDatagramChannel.open().bind(null); 120 | try { 121 | ch.bind(null); 122 | fail("Should have thrown AlreadyBoundException"); 123 | } catch (AlreadyBoundException abx) { 124 | try { 125 | ch.socket().bind(null); 126 | fail("Should have thrown SocketException"); 127 | } catch (SocketException sx) { 128 | assertEquals("exception message", sx.getMessage(), "already bound"); 129 | } 130 | } 131 | } 132 | 133 | @Test 134 | public void pairTest() throws Exception { 135 | UnixDatagramChannel[] sp = UnixDatagramChannel.pair(); 136 | for (final UnixDatagramChannel ch : sp) { 137 | assertTrue("Channel is connected", ch.isConnected()); 138 | assertTrue("Channel is bound", ch.isBound()); 139 | assertFalse("Channel's socket is not closed", ch.socket().isClosed()); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/BasicFunctionalityTest.java: -------------------------------------------------------------------------------- 1 | 2 | package jnr.unixsocket; 3 | 4 | import jnr.enxio.channels.NativeSelectorProvider; 5 | import org.junit.After; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.net.SocketException; 12 | import java.nio.ByteBuffer; 13 | import java.nio.CharBuffer; 14 | import java.nio.channels.AlreadyBoundException; 15 | import java.nio.channels.Channels; 16 | import java.nio.channels.SelectionKey; 17 | import java.nio.channels.Selector; 18 | import java.util.Set; 19 | 20 | import static java.nio.charset.StandardCharsets.UTF_8; 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.assertFalse; 23 | import static org.junit.Assert.assertNotNull; 24 | import static org.junit.Assert.assertTrue; 25 | import static org.junit.Assert.fail; 26 | 27 | public class BasicFunctionalityTest { 28 | private static final String DATA = "blah blah"; 29 | 30 | private UnixSocketPair socketPair; 31 | private Thread server; 32 | private volatile Exception serverException; 33 | 34 | @Before 35 | public void setUp() throws Exception { 36 | socketPair = new UnixSocketPair(); 37 | } 38 | 39 | @After 40 | public void tearDown() throws Exception { 41 | socketPair.close(); 42 | } 43 | 44 | @Test 45 | public void doubleBindTest() throws Exception { 46 | UnixSocketChannel ch = UnixSocketChannel.open().bind(null); 47 | try { 48 | ch.bind(null); 49 | fail("Should have thrown AlreadyBoundException"); 50 | } catch (AlreadyBoundException abx) { 51 | try { 52 | ch.socket().bind(null); 53 | fail("Should have thrown SocketException"); 54 | } catch (SocketException sx) { 55 | assertEquals("exception message", sx.getMessage(), "already bound"); 56 | } 57 | } 58 | } 59 | 60 | @Test 61 | public void pairTest() throws Exception { 62 | UnixSocketChannel[] sp = UnixSocketChannel.pair(); 63 | for (final UnixSocketChannel ch : sp) { 64 | assertTrue("Channel is connected", ch.isConnected()); 65 | assertTrue("Channel is bound", ch.isBound()); 66 | assertFalse("Channel's socket is not closed", ch.socket().isClosed()); 67 | } 68 | } 69 | 70 | @Test 71 | public void basicOperation() throws Exception { 72 | // server logic 73 | final UnixServerSocketChannel channel = UnixServerSocketChannel.open(); 74 | final Selector sel = NativeSelectorProvider.getInstance().openSelector(); 75 | channel.configureBlocking(false); 76 | channel.socket().bind(socketPair.socketAddress()); 77 | channel.register(sel, SelectionKey.OP_ACCEPT, new ServerActor(channel, sel)); 78 | 79 | // TODO: This is ugly but simple enough. Many failures on server side will cause client to hang. 80 | server = new Thread("server side") { 81 | public void run() { 82 | try { 83 | while (sel.select() > 0) { 84 | Set keys = sel.selectedKeys(); 85 | 86 | assertNotNull(keys); 87 | assertTrue(keys.size() > 0); 88 | 89 | for (SelectionKey k : keys) { 90 | assertTrue(k.attachment() instanceof Actor); 91 | 92 | Actor a = (Actor) k.attachment(); 93 | if (!a.rxready()) { 94 | k.cancel(); 95 | } 96 | } 97 | } 98 | } catch (Exception ex) { 99 | serverException = ex; 100 | } 101 | } 102 | }; 103 | 104 | server.start(); 105 | 106 | // client logic 107 | UnixSocketChannel channel2 = UnixSocketChannel.open(socketPair.socketAddress()); 108 | 109 | assertEquals(socketPair.socketAddress(), channel2.getRemoteSocketAddress()); 110 | 111 | Channels.newOutputStream(channel2).write(DATA.getBytes(UTF_8)); 112 | 113 | InputStreamReader r = new InputStreamReader(Channels.newInputStream(channel2), UTF_8); 114 | CharBuffer result = CharBuffer.allocate(1024); 115 | r.read(result); 116 | 117 | assertEquals(DATA.length(), result.position()); 118 | 119 | result.flip(); 120 | 121 | assertEquals(DATA, result.toString()); 122 | 123 | if (serverException != null) throw serverException; 124 | } 125 | 126 | static interface Actor { 127 | public boolean rxready(); 128 | } 129 | 130 | final class ServerActor implements Actor { 131 | private final UnixServerSocketChannel channel; 132 | private final Selector selector; 133 | 134 | public ServerActor(UnixServerSocketChannel channel, Selector selector) { 135 | this.channel = channel; 136 | this.selector = selector; 137 | } 138 | 139 | public final boolean rxready() { 140 | try { 141 | UnixSocketChannel client = channel.accept(); 142 | 143 | if (client == null) { 144 | // nonblocking result 145 | return false; 146 | } 147 | assertEquals(socketPair.socketAddress(), client.getLocalSocketAddress()); 148 | assertEquals("", client.getRemoteSocketAddress().getStruct().getPath()); 149 | 150 | client.configureBlocking(false); 151 | client.register(selector, SelectionKey.OP_READ, new ClientActor(client)); 152 | 153 | return true; 154 | } catch (IOException ex) { 155 | return false; 156 | } 157 | } 158 | } 159 | 160 | final class ClientActor implements Actor { 161 | private final UnixSocketChannel channel; 162 | 163 | public ClientActor(UnixSocketChannel channel) { 164 | this.channel = channel; 165 | } 166 | 167 | public final boolean rxready() { 168 | try { 169 | ByteBuffer buf = ByteBuffer.allocate(1024); 170 | int n = channel.read(buf); 171 | assertEquals("", channel.getRemoteSocketAddress().getStruct().getPath()); 172 | 173 | assertEquals(DATA.length(), n); 174 | 175 | if (n > 0) { 176 | buf.flip(); 177 | channel.write(buf); 178 | return true; 179 | } else if (n < 0) { 180 | return false; 181 | } 182 | 183 | } catch (IOException ex) { 184 | ex.printStackTrace(); 185 | return false; 186 | } 187 | return true; 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/ChannelOptionsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Fritz Elfert 3 | * 4 | * This file is part of the JNR project. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | package jnr.unixsocket; 20 | 21 | import static junit.framework.Assert.*; 22 | 23 | import jnr.ffi.Platform; 24 | import jnr.ffi.Platform.OS; 25 | 26 | import org.junit.Assume; 27 | import org.junit.Test; 28 | 29 | public class ChannelOptionsTest { 30 | 31 | @Test 32 | public void readonlyDatagramChannelOptionTest() throws Exception { 33 | Assume.assumeTrue(OS.LINUX == Platform.getNativePlatform().getOS()); 34 | 35 | UnixDatagramChannel[] sp = UnixDatagramChannel.pair(); 36 | UnixDatagramChannel ch = sp[0]; 37 | Credentials c = ch.socket().getCredentials(); 38 | try { 39 | // SO_PEERCRED is readonly 40 | ch.setOption(UnixSocketOptions.SO_PEERCRED, c); 41 | fail("Should have thrown AssertionError"); 42 | } catch (AssertionError ae) { 43 | assertEquals("exception message", ae.getMessage(), "Option not found or not writable"); 44 | } 45 | } 46 | 47 | @Test 48 | public void readonlySocketChannelOptionTest() throws Exception { 49 | Assume.assumeTrue(OS.LINUX == Platform.getNativePlatform().getOS()); 50 | 51 | UnixSocketChannel[] sp = UnixSocketChannel.pair(); 52 | UnixSocketChannel ch = sp[0]; 53 | Credentials c = ch.socket().getCredentials(); 54 | try { 55 | // SO_PEERCRED is readonly 56 | ch.setOption(UnixSocketOptions.SO_PEERCRED, c); 57 | fail("Should have thrown AssertionError"); 58 | } catch (AssertionError ae) { 59 | assertEquals("exception message", ae.getMessage(), "Option not found or not writable"); 60 | } 61 | } 62 | 63 | @Test 64 | public void unsupportedChannelOptionTest() throws Exception { 65 | UnixDatagramChannel ch = UnixDatagramChannel.open(); 66 | try { 67 | // SO_KEEPALIVE is suitable only for SOCK_STREAM sockets 68 | ch.getOption(UnixSocketOptions.SO_KEEPALIVE); 69 | fail("Should have thrown UnsupportedOperationException"); 70 | } catch (UnsupportedOperationException uoe) { 71 | assertEquals("exception message", uoe.getMessage(), "'SO_KEEPALIVE' not supported"); 72 | } 73 | } 74 | 75 | @Test 76 | public void keepaliveOptionTest() throws Exception { 77 | UnixSocketChannel ch = UnixSocketChannel.open(); 78 | boolean origValue = ch.getOption(UnixSocketOptions.SO_KEEPALIVE).booleanValue(); 79 | assertEquals("Initial value of SO_KEEPALIVE", origValue, false); 80 | ch.setOption(UnixSocketOptions.SO_KEEPALIVE, Boolean.TRUE); 81 | boolean changedValue = ch.getOption(UnixSocketOptions.SO_KEEPALIVE).booleanValue(); 82 | assertEquals("Changed value of SO_KEEPALIVE", changedValue, true); 83 | ch.setOption(UnixSocketOptions.SO_KEEPALIVE, Boolean.FALSE); 84 | changedValue = ch.getOption(UnixSocketOptions.SO_KEEPALIVE).booleanValue(); 85 | assertEquals("Changed value of SO_KEEPALIVE", changedValue, origValue); 86 | } 87 | 88 | @Test 89 | public void invalidOptionValueTest() throws Exception { 90 | UnixSocketChannel ch = UnixSocketChannel.open(); 91 | try { 92 | ch.setOption(UnixSocketOptions.SO_RCVTIMEO, Integer.valueOf(-1)); 93 | fail("Should have thrown IllegalArgumentException"); 94 | } catch (IllegalArgumentException iae) { 95 | assertEquals("exception message", iae.getMessage(), "Invalid send/receive timeout"); 96 | } 97 | try { 98 | ch.setOption(UnixSocketOptions.SO_SNDTIMEO, Integer.valueOf(-1)); 99 | fail("Should have thrown IllegalArgumentException"); 100 | } catch (IllegalArgumentException iae) { 101 | assertEquals("exception message", iae.getMessage(), "Invalid send/receive timeout"); 102 | } 103 | try { 104 | ch.setOption(UnixSocketOptions.SO_RCVBUF, Integer.valueOf(-1)); 105 | fail("Should have thrown IllegalArgumentException"); 106 | } catch (IllegalArgumentException iae) { 107 | assertEquals("exception message", iae.getMessage(), "Invalid send/receive buffer size"); 108 | } 109 | try { 110 | ch.setOption(UnixSocketOptions.SO_SNDBUF, Integer.valueOf(-1)); 111 | fail("Should have thrown IllegalArgumentException"); 112 | } catch (IllegalArgumentException iae) { 113 | assertEquals("exception message", iae.getMessage(), "Invalid send/receive buffer size"); 114 | } 115 | } 116 | 117 | @Test 118 | // Linux doubles the values when setting. 119 | // OSX keeps settings consistent but restricts possible values to a multiple of 256 120 | // Check what other platforms do. 121 | public void socketBufferTest() throws Exception { 122 | UnixDatagramChannel ch = UnixDatagramChannel.open(); 123 | int rxs = ch.getOption(UnixSocketOptions.SO_RCVBUF); 124 | int txs = ch.getOption(UnixSocketOptions.SO_SNDBUF); 125 | assertTrue("receive buffer size >= 256", rxs >= 256); 126 | assertTrue("send buffer size >= 256", txs >= 256); 127 | /* 128 | System.out.println(String.format("rxbuf=%d, txbuf=%d", rxs, txs)); 129 | ch.setOption(UnixSocketOptions.SO_RCVBUF, rxs - 100); 130 | ch.setOption(UnixSocketOptions.SO_SNDBUF, txs - 100); 131 | rxs = ch.getOption(UnixSocketOptions.SO_RCVBUF); 132 | txs = ch.getOption(UnixSocketOptions.SO_SNDBUF); 133 | System.out.println(String.format("rxbuf=%d, txbuf=%d", rxs, txs)); 134 | */ 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/CredentialsFunctionalTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the JNR project. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package jnr.unixsocket; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertNotNull; 21 | import static org.junit.Assert.fail; 22 | 23 | import java.io.File; 24 | import java.io.FileReader; 25 | import java.io.IOException; 26 | import java.lang.management.ManagementFactory; 27 | import java.util.concurrent.Callable; 28 | import java.util.concurrent.ExecutionException; 29 | import java.util.concurrent.ExecutorService; 30 | import java.util.concurrent.Executors; 31 | import java.util.concurrent.Future; 32 | 33 | import jnr.ffi.Platform; 34 | import jnr.ffi.Platform.OS; 35 | 36 | import org.junit.Assume; 37 | import org.junit.Before; 38 | import org.junit.Rule; 39 | import org.junit.Test; 40 | import org.junit.rules.TemporaryFolder; 41 | 42 | public class CredentialsFunctionalTest { 43 | @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); 44 | 45 | private File serverSocket; 46 | private ExecutorService async = Executors.newSingleThreadExecutor(); 47 | 48 | @Before 49 | public void createSockets() throws IOException { 50 | Assume.assumeTrue(OS.LINUX == Platform.getNativePlatform().getOS()); 51 | 52 | serverSocket = tempFolder.newFile("serverSocket"); 53 | serverSocket.delete(); //JUnit is "helpful" and creates it for us 54 | } 55 | 56 | @Test(timeout=30000) 57 | public void credentials() throws IOException, ExecutionException, InterruptedException { 58 | UnixSocketAddress address = new UnixSocketAddress(serverSocket); 59 | final UnixServerSocket socket = new UnixServerSocket(); 60 | socket.bind(address); 61 | 62 | Future socketFuture = async.submit(new Callable() { 63 | public UnixSocket call() throws Exception { 64 | return socket.accept(); 65 | } 66 | }); 67 | 68 | UnixSocketChannel client = UnixSocketChannel.open(address); 69 | UnixSocket server = socketFuture.get(); 70 | 71 | assertNotNull("Client socket must be non-null.", client); 72 | assertNotNull("Server socket must be non-null.", server); 73 | 74 | Credentials clientCreds = client.socket().getCredentials(); 75 | Credentials serverCreds = server.getCredentials(); 76 | 77 | int myPid = getCurrentPid(); 78 | 79 | assertEquals("Current PID should match client credentials", 80 | myPid, clientCreds.getPid()); 81 | assertEquals("Current PID should match server credentials", 82 | myPid, serverCreds.getPid()); 83 | 84 | assertEquals("Client/server running in same process, UID should be the same", 85 | clientCreds.getUid(), serverCreds.getUid()); 86 | 87 | //don't have an easy way of getting effective GID, but they should be the same 88 | assertEquals("Client/server running in same process, GID should be the same", 89 | clientCreds.getGid(), serverCreds.getGid()); 90 | 91 | // Verify, that results from new interface are the same 92 | Credentials newCreds = client.getOption(UnixSocketOptions.SO_PEERCRED); 93 | assertNotNull(newCreds); 94 | assertEquals("Current PID should match new API PID", 95 | myPid, newCreds.getPid()); 96 | assertEquals("old/new API results (UID) should be the same", 97 | clientCreds.getUid(), newCreds.getUid()); 98 | assertEquals("old/new API results (GID) should be the same", 99 | clientCreds.getGid(), newCreds.getGid()); 100 | } 101 | 102 | public int getCurrentPid() { 103 | String[] nameParts = ManagementFactory.getRuntimeMXBean().getName().split("@", 2); 104 | assertEquals("Cannot determine PID", 2, nameParts.length); 105 | return Integer.parseInt(nameParts[0]); 106 | } 107 | 108 | /* 109 | * A Linux-only utility method. 110 | */ 111 | public int getLoginUid() throws IOException { 112 | FileReader fr = null; 113 | StringBuilder uidText = new StringBuilder(); 114 | try { 115 | fr = new FileReader("/proc/self/loginuid"); 116 | char[] buf = new char[16]; 117 | int read = -1; 118 | while ((read = fr.read(buf)) > -1) { 119 | uidText.append(buf, 0, read); 120 | } 121 | } catch (IOException ioe) { 122 | fail("Unable to determine login uid: " + ioe.getMessage()); 123 | } finally { 124 | fr.close(); 125 | } 126 | 127 | return Integer.parseInt(uidText.toString()); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/ForFDTest.java: -------------------------------------------------------------------------------- 1 | package jnr.unixsocket; 2 | 3 | import jnr.constants.platform.ProtocolFamily; 4 | import jnr.constants.platform.Sock; 5 | import org.junit.Test; 6 | 7 | import java.io.File; 8 | import java.nio.ByteBuffer; 9 | import java.nio.charset.StandardCharsets; 10 | 11 | import static junit.framework.Assert.assertEquals; 12 | import static junit.framework.Assert.assertNotNull; 13 | import static junit.framework.Assert.assertTrue; 14 | 15 | /** 16 | * Created by headius on 11/24/15. 17 | */ 18 | public class ForFDTest { 19 | private static final File SOCKADDR = new File("/tmp/jnr-unixsocket-forfd" + System.currentTimeMillis() + ".sock"); 20 | static { SOCKADDR.deleteOnExit(); } 21 | private static final UnixSocketAddress ADDRESS = new UnixSocketAddress(SOCKADDR); 22 | private static final String FOOBAR = "foobar"; 23 | 24 | private volatile Exception serverException; 25 | 26 | @Test 27 | public void testForFD() throws Exception { 28 | int fd = 0; 29 | UnixSocketChannel channel = null; 30 | 31 | try { 32 | final UnixServerSocketChannel server = UnixServerSocketChannel.open(); 33 | server.socket().bind(ADDRESS); 34 | 35 | new Thread("accept thread") { 36 | public void run() { 37 | UnixSocketChannel channel = null; 38 | 39 | try { 40 | channel = server.accept(); 41 | channel.write(ByteBuffer.wrap(FOOBAR.getBytes(StandardCharsets.UTF_8))); 42 | } catch (Exception e) { 43 | serverException = e; 44 | } finally { 45 | try {channel.close();} catch (Exception e) {} 46 | } 47 | } 48 | }.start(); 49 | 50 | fd = Native.socket(ProtocolFamily.PF_UNIX, Sock.SOCK_STREAM, 0); 51 | 52 | assertTrue("socket failed", fd > 0); 53 | 54 | int ret = Native.connect(fd, ADDRESS.getStruct(), ADDRESS.getStruct().length()); 55 | 56 | assertTrue("connect failed", ret >= 0); 57 | 58 | channel = UnixSocketChannel.fromFD(fd); 59 | 60 | assertNotNull(channel); 61 | 62 | ByteBuffer buf = ByteBuffer.allocate(1024); 63 | 64 | channel.read(buf); 65 | 66 | assertEquals(FOOBAR.length(), buf.position()); 67 | 68 | buf.flip(); 69 | String result = new String(buf.array(), buf.position(), buf.limit(), "UTF-8"); 70 | 71 | assertEquals(FOOBAR, result); 72 | 73 | if (serverException != null) throw serverException; 74 | } finally { 75 | channel.close(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/SocketInteropTest.java: -------------------------------------------------------------------------------- 1 | package jnr.unixsocket; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.rules.Timeout; 8 | import org.junit.runner.RunWith; 9 | import org.junit.runners.Parameterized; 10 | import org.junit.runners.Parameterized.Parameter; 11 | import org.junit.runners.Parameterized.Parameters; 12 | 13 | import java.io.BufferedReader; 14 | import java.io.Closeable; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.InputStreamReader; 18 | import java.io.OutputStream; 19 | import java.net.SocketException; 20 | import java.nio.channels.AsynchronousCloseException; 21 | import java.nio.channels.ClosedByInterruptException; 22 | import java.nio.channels.ClosedChannelException; 23 | import java.nio.channels.NotYetBoundException; 24 | import java.util.Arrays; 25 | import java.util.List; 26 | import java.util.concurrent.TimeUnit; 27 | 28 | import static java.nio.charset.StandardCharsets.UTF_8; 29 | import static org.junit.Assert.assertEquals; 30 | import static org.junit.Assert.assertTrue; 31 | import static org.junit.Assert.fail; 32 | import static org.junit.Assume.assumeTrue; 33 | 34 | /** 35 | * Confirm that UNIX sockets work similarly to TCP sockets. 36 | */ 37 | @RunWith(Parameterized.class) 38 | public class SocketInteropTest { 39 | @Rule 40 | public Timeout timeout = new Timeout(5, TimeUnit.SECONDS); 41 | 42 | @Parameter 43 | public TestSocketPair.Factory socketPairFactory; 44 | 45 | private TestSocketPair socketPair; 46 | 47 | @Parameters(name = "{0}") 48 | public static List parameters() { 49 | return Arrays.asList( 50 | new Object[] { UnixSocketPair.FACTORY }, 51 | new Object[] { TcpSocketsApiSocketPair.FACTORY }, 52 | new Object[] { TcpChannelsApiSocketPair.FACTORY } 53 | ); 54 | } 55 | 56 | @Before 57 | public void setUp() throws Exception { 58 | socketPair = socketPairFactory.createUnconnected(); 59 | } 60 | 61 | @After 62 | public void tearDown() throws Exception { 63 | socketPair.close(); 64 | } 65 | 66 | @Test 67 | public void serverWritesAndClientReads() throws IOException { 68 | socketPair.connectBlocking(); 69 | 70 | OutputStream serverOut = socketPair.server().getOutputStream(); 71 | serverOut.write("message from server to client\n".getBytes(UTF_8)); 72 | serverOut.flush(); 73 | 74 | InputStream clientIn = socketPair.client().getInputStream(); 75 | BufferedReader reader = new BufferedReader(new InputStreamReader(clientIn, UTF_8)); 76 | assertEquals("message from server to client", reader.readLine()); 77 | } 78 | 79 | @Test 80 | public void clientWritesAndServerReads() throws IOException { 81 | socketPair.connectBlocking(); 82 | 83 | OutputStream clientOut = socketPair.client().getOutputStream(); 84 | clientOut.write("message from client to server\n".getBytes(UTF_8)); 85 | clientOut.flush(); 86 | 87 | InputStream serverIn = socketPair.server().getInputStream(); 88 | BufferedReader reader = new BufferedReader(new InputStreamReader(serverIn, UTF_8)); 89 | assertEquals("message from client to server", reader.readLine()); 90 | } 91 | 92 | @Test 93 | public void acceptThrowsWhenServerSocketIsNotYetBound() throws IOException { 94 | try { 95 | socketPair.serverAccept(); 96 | fail(); 97 | } catch (NotYetBoundException expected) { 98 | // Thrown by channels APIs. 99 | } catch (SocketException expected) { 100 | // Thrown by sockets APIs. 101 | } 102 | } 103 | 104 | @Test 105 | public void acceptThrowsWhenServerSocketIsClosed() throws IOException { 106 | socketPair.serverBind(); 107 | socketPair.close(); 108 | try { 109 | socketPair.serverAccept(); 110 | fail(); 111 | } catch (ClosedChannelException expected) { 112 | // Thrown by channels APIs. 113 | } catch (SocketException expected) { 114 | // Thrown by sockets APIs. 115 | } 116 | } 117 | 118 | @Test 119 | public void acceptThrowsWhenServerSocketIsAsynchronouslyClosed() throws IOException { 120 | socketPair.serverBind(); 121 | closeLater(socketPair, 500, TimeUnit.MILLISECONDS); 122 | try { 123 | socketPair.serverAccept(); 124 | fail(); 125 | } catch (AsynchronousCloseException expected) { 126 | // Thrown by channels APIs. 127 | } catch (SocketException expected) { 128 | // Thrown by sockets APIs. 129 | } 130 | } 131 | 132 | private void closeLater(final Closeable closeable, final long delay, final TimeUnit timeUnit) { 133 | new Thread(getClass().getName() + ".closeLater") { 134 | @Override 135 | public void run() { 136 | try { 137 | Thread.sleep(timeUnit.toMillis(delay)); 138 | closeable.close(); 139 | } catch (IOException | InterruptedException ignored) { 140 | } 141 | } 142 | }.start(); 143 | } 144 | 145 | @Test 146 | public void acceptThrowsWhenAcceptingThreadIsInterrupted() throws IOException { 147 | // https://bugs.openjdk.java.net/browse/JDK-4386498 148 | assumeTrue("the TCP sockets API doesn't support Thread.interrupt()", 149 | socketPairFactory != TcpSocketsApiSocketPair.FACTORY); 150 | 151 | socketPair.serverBind(); 152 | interruptLater(Thread.currentThread(), 500, TimeUnit.MILLISECONDS); 153 | try { 154 | socketPair.serverAccept(); 155 | fail(); 156 | } catch (ClosedByInterruptException expected) { 157 | } 158 | // This has a side-effect of clearing the interrupted state. Otherwise later tests may fail! 159 | assertTrue(Thread.interrupted()); 160 | } 161 | 162 | private void interruptLater(final Thread target, final long delay, final TimeUnit timeUnit) { 163 | new Thread(getClass().getName() + ".interruptLater") { 164 | @Override 165 | public void run() { 166 | try { 167 | Thread.sleep(timeUnit.toMillis(delay)); 168 | target.interrupt(); 169 | } catch (InterruptedException ignored) { 170 | } 171 | } 172 | }.start(); 173 | } 174 | 175 | @Test 176 | public void concurrentReadAndWrite() throws IOException { 177 | // https://bugs.openjdk.java.net/browse/JDK-4774871 178 | assumeTrue("the TCP channels API doesn't support concurrent read and write", 179 | socketPairFactory != TcpChannelsApiSocketPair.FACTORY); 180 | 181 | socketPair.connectBlocking(); 182 | 183 | // This thread runs later. It writes messages on each socket. 184 | new Thread(getClass().getName() + ".concurrentReadAndWrite") { 185 | @Override 186 | public void run() { 187 | try { 188 | // Sleep to guarantee that the reads are in-flight before the writes are attempted. 189 | Thread.sleep(500); 190 | 191 | OutputStream clientOut = socketPair.client().getOutputStream(); 192 | clientOut.write("message from client to server\n".getBytes(UTF_8)); 193 | clientOut.flush(); 194 | 195 | OutputStream serverOut = socketPair.server().getOutputStream(); 196 | serverOut.write("message from server to client\n".getBytes(UTF_8)); 197 | serverOut.flush(); 198 | } catch (InterruptedException | IOException ignored) { 199 | } 200 | } 201 | }.start(); 202 | 203 | // This thread runs earlier. It reads messages on each socket. 204 | InputStream clientIn = socketPair.client().getInputStream(); 205 | BufferedReader clientReader = new BufferedReader(new InputStreamReader(clientIn, UTF_8)); 206 | assertEquals("message from server to client", clientReader.readLine()); 207 | 208 | InputStream serverIn = socketPair.server().getInputStream(); 209 | BufferedReader serverReader = new BufferedReader(new InputStreamReader(serverIn, UTF_8)); 210 | assertEquals("message from client to server", serverReader.readLine()); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/TcpChannelsApiSocketPair.java: -------------------------------------------------------------------------------- 1 | package jnr.unixsocket; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.net.ServerSocket; 6 | import java.net.Socket; 7 | import java.net.SocketAddress; 8 | import java.nio.channels.ServerSocketChannel; 9 | import java.nio.channels.SocketChannel; 10 | 11 | /** 12 | * TCP sockets created with the java.nio channels APIs. 13 | */ 14 | class TcpChannelsApiSocketPair extends TestSocketPair { 15 | static final Factory FACTORY = new Factory() { 16 | @Override 17 | TestSocketPair createUnconnected() throws IOException { 18 | return new TcpChannelsApiSocketPair(); 19 | } 20 | }; 21 | 22 | private final ServerSocketChannel serverSocketChannel; 23 | private InetSocketAddress serverAddress; 24 | private SocketChannel serverChannel; 25 | private SocketChannel clientChannel; 26 | 27 | TcpChannelsApiSocketPair() throws IOException { 28 | serverSocketChannel = ServerSocketChannel.open(); 29 | } 30 | 31 | @Override 32 | void serverBind() throws IOException { 33 | if (serverAddress != null) { 34 | throw new IllegalStateException("already bound"); 35 | } 36 | 37 | ServerSocket serverSocket = serverSocketChannel.socket(); 38 | serverSocket.setReuseAddress(true); 39 | serverSocketChannel.bind(new InetSocketAddress(0)); 40 | serverSocketChannel.configureBlocking(true); 41 | serverAddress = new InetSocketAddress(serverSocket.getInetAddress(), serverSocket.getLocalPort()); 42 | } 43 | 44 | @Override 45 | void clientConnect() throws IOException { 46 | if (clientChannel != null) { 47 | throw new IllegalStateException("already connected"); 48 | } 49 | 50 | clientChannel = SocketChannel.open(); 51 | clientChannel.connect(serverAddress); 52 | } 53 | 54 | @Override 55 | void serverAccept() throws IOException { 56 | if (serverChannel != null) { 57 | throw new IllegalStateException("already accepted"); 58 | } 59 | 60 | serverChannel = serverSocketChannel.accept(); 61 | } 62 | 63 | @Override 64 | SocketAddress socketAddress() { 65 | return serverAddress; 66 | } 67 | 68 | @Override 69 | Socket server() { 70 | return serverChannel.socket(); 71 | } 72 | 73 | @Override 74 | Socket client() { 75 | return clientChannel.socket(); 76 | } 77 | 78 | @Override 79 | public void close() throws IOException { 80 | closeQuietly(serverSocketChannel); 81 | closeQuietly(serverChannel); 82 | closeQuietly(clientChannel); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/TcpSocketsApiSocketPair.java: -------------------------------------------------------------------------------- 1 | package jnr.unixsocket; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.net.ServerSocket; 6 | import java.net.Socket; 7 | import java.net.SocketAddress; 8 | 9 | /** 10 | * TCP sockets created with the java.io sockets APIs. The sockets returned by 11 | * this class do not have channels. 12 | */ 13 | class TcpSocketsApiSocketPair extends TestSocketPair { 14 | static final Factory FACTORY = new Factory() { 15 | @Override 16 | TestSocketPair createUnconnected() throws IOException { 17 | return new TcpSocketsApiSocketPair(); 18 | } 19 | }; 20 | 21 | private final ServerSocket serverSocket; 22 | private InetSocketAddress serverAddress; 23 | private Socket server; 24 | private Socket client; 25 | 26 | public TcpSocketsApiSocketPair() throws IOException { 27 | serverSocket = new ServerSocket(); 28 | } 29 | 30 | @Override 31 | void serverBind() throws IOException { 32 | if (serverAddress != null) { 33 | throw new IllegalStateException("already bound"); 34 | } 35 | 36 | serverSocket.setReuseAddress(true); 37 | serverSocket.bind(new InetSocketAddress(0)); 38 | serverAddress = new InetSocketAddress(serverSocket.getInetAddress(), serverSocket.getLocalPort()); 39 | } 40 | 41 | @Override 42 | void clientConnect() throws IOException { 43 | if (client != null) { 44 | throw new IllegalStateException("already connected"); 45 | } 46 | 47 | client = new Socket(); 48 | client.connect(serverAddress); 49 | } 50 | 51 | @Override 52 | void serverAccept() throws IOException { 53 | if (server != null) { 54 | throw new IllegalStateException("already accepted"); 55 | } 56 | 57 | server = serverSocket.accept(); 58 | } 59 | 60 | @Override 61 | SocketAddress socketAddress() { 62 | return serverAddress; 63 | } 64 | 65 | @Override 66 | Socket server() { 67 | return server; 68 | } 69 | 70 | @Override 71 | Socket client() { 72 | return client; 73 | } 74 | 75 | @Override 76 | public void close() throws IOException { 77 | closeQuietly(serverSocket); 78 | closeQuietly(server); 79 | closeQuietly(client); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/TestSocketPair.java: -------------------------------------------------------------------------------- 1 | package jnr.unixsocket; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.net.Socket; 6 | import java.net.SocketAddress; 7 | 8 | /** 9 | * A TCP or UNIX socket pair for testing. 10 | */ 11 | abstract class TestSocketPair implements Closeable { 12 | void connectBlocking() throws IOException { 13 | serverBind(); 14 | clientConnect(); 15 | serverAccept(); 16 | } 17 | 18 | abstract void serverBind() throws IOException; 19 | 20 | abstract void serverAccept() throws IOException; 21 | 22 | abstract void clientConnect() throws IOException; 23 | 24 | abstract SocketAddress socketAddress(); 25 | 26 | abstract Socket server(); 27 | 28 | abstract Socket client(); 29 | 30 | final void closeQuietly(Closeable closeable) { 31 | if (closeable == null) { 32 | return; 33 | } 34 | try { 35 | closeable.close(); 36 | } catch (IOException ignored) { 37 | } 38 | } 39 | 40 | abstract static class Factory { 41 | abstract TestSocketPair createUnconnected() throws IOException; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/UnixDatagramChannelTest.java: -------------------------------------------------------------------------------- 1 | package jnr.unixsocket; 2 | 3 | import java.io.File; 4 | import java.nio.file.Files; 5 | import java.util.regex.Pattern; 6 | 7 | import org.junit.Test; 8 | import org.junit.Assume; 9 | 10 | import static junit.framework.Assert.*; 11 | 12 | import jnr.ffi.Platform; 13 | import jnr.ffi.Platform.OS; 14 | 15 | public class UnixDatagramChannelTest { 16 | 17 | @Test 18 | public void testForUnnamedSockets() throws Exception { 19 | UnixDatagramChannel[] sp = UnixDatagramChannel.pair(); 20 | 21 | // getpeername check 22 | assertEquals("remote socket path", "", sp[0].getRemoteSocketAddress().path()); 23 | assertEquals("remote socket path", "", sp[1].getRemoteSocketAddress().path()); 24 | 25 | // getsockname check 26 | assertEquals("local socket path", "", sp[0].getLocalSocketAddress().path()); 27 | assertEquals("local socket path", "", sp[1].getLocalSocketAddress().path()); 28 | } 29 | 30 | @Test 31 | public void testAutobind() throws Exception { 32 | Assume.assumeTrue(OS.LINUX == Platform.getNativePlatform().getOS()); 33 | 34 | // see http://man7.org/linux/man-pages/man7/unix.7.html 35 | final String RE = "^\\000([0-9a-f]){5}$"; 36 | 37 | UnixDatagramChannel ch = UnixDatagramChannel.open(); 38 | ch.bind(null); 39 | UnixSocketAddress a = ch.getLocalSocketAddress(); 40 | assertTrue("socket path pattern matches " + RE, a.path().matches(RE)); 41 | } 42 | 43 | @Test 44 | public void testAutobindEmulation() throws Exception { 45 | Assume.assumeTrue(OS.LINUX != Platform.getNativePlatform().getOS()); 46 | 47 | File f = Files.createTempFile("jnr-unixsocket-tmp", ".end").toFile(); 48 | f.delete(); 49 | String path = f.getPath().replaceAll("-tmp.*\\.end", "-tmp"); 50 | final String RE = "^" + Pattern.quote(path) + ".*\\.sock$"; 51 | UnixDatagramChannel ch = UnixDatagramChannel.open(); 52 | ch.bind(null); 53 | UnixSocketAddress a = ch.getLocalSocketAddress(); 54 | assertTrue("socket path pattern matches " + RE, a.path().matches(RE)); 55 | } 56 | 57 | @Test 58 | public void testAbstractNamespace() throws Exception { 59 | Assume.assumeTrue(OS.LINUX == Platform.getNativePlatform().getOS()); 60 | 61 | final String ABSTRACT = "\000foobarbaz"; 62 | 63 | UnixSocketAddress a = new UnixSocketAddress(ABSTRACT); 64 | UnixDatagramChannel ch = UnixDatagramChannel.open(); 65 | ch.bind(a); 66 | assertEquals("local socket path", ABSTRACT, ch.getLocalSocketAddress().path()); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/UnixSocketChannelTest.java: -------------------------------------------------------------------------------- 1 | package jnr.unixsocket; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.util.concurrent.CountDownLatch; 7 | 8 | import org.junit.Test; 9 | import org.junit.Assume; 10 | 11 | import static junit.framework.Assert.*; 12 | 13 | import jnr.ffi.Platform; 14 | import jnr.ffi.Platform.OS; 15 | 16 | public class UnixSocketChannelTest { 17 | 18 | @Test 19 | public void testForUnnamedSockets() throws Exception { 20 | UnixSocketChannel[] sp = UnixSocketChannel.pair(); 21 | 22 | // getpeername check 23 | assertEquals("remote socket path", "", sp[0].getRemoteSocketAddress().path()); 24 | assertEquals("remote socket path", "", sp[1].getRemoteSocketAddress().path()); 25 | 26 | // getsockname check 27 | assertEquals("local socket path", "", sp[0].getLocalSocketAddress().path()); 28 | assertEquals("local socket path", "", sp[1].getLocalSocketAddress().path()); 29 | } 30 | 31 | @Test 32 | public void testAutobind() throws Exception { 33 | Assume.assumeTrue(OS.LINUX == Platform.getNativePlatform().getOS()); 34 | 35 | // see http://man7.org/linux/man-pages/man7/unix.7.html 36 | final String RE = "^\\000([0-9a-f]){5}$"; 37 | 38 | UnixSocketChannel ch = UnixSocketChannel.open(); 39 | ch.bind(null); 40 | UnixSocketAddress a = ch.getLocalSocketAddress(); 41 | assertTrue("socket path pattern matches " + RE, a.path().matches(RE)); 42 | } 43 | 44 | @Test 45 | public void testAbstractNamespace() throws Exception { 46 | Assume.assumeTrue(OS.LINUX == Platform.getNativePlatform().getOS()); 47 | 48 | final String ABSTRACT = "\000foobarbaz"; 49 | 50 | UnixSocketAddress a = new UnixSocketAddress(ABSTRACT); 51 | UnixSocketChannel ch = UnixSocketChannel.open(); 52 | ch.bind(a); 53 | assertEquals("local socket path", ABSTRACT, ch.getLocalSocketAddress().path()); 54 | } 55 | 56 | @Test 57 | public void testInterruptRead() throws Exception { 58 | Path socketPath = getTemporarySocketFileName(); 59 | startServer(socketPath); 60 | 61 | int readTimeoutInMilliseconds = 5000; 62 | 63 | UnixSocket socket = createClient(socketPath, readTimeoutInMilliseconds); 64 | CountDownLatch readStartLatch = new CountDownLatch(1); 65 | ReadFromSocketRunnable runnable = new ReadFromSocketRunnable(readStartLatch, socket); 66 | 67 | Thread readThread = new Thread(runnable); 68 | 69 | readThread.setDaemon(true); 70 | 71 | long startTime = System.nanoTime(); 72 | readThread.start(); 73 | readStartLatch.await(); 74 | Thread.sleep(100); // Wait for the thread to call read() 75 | socket.close(); 76 | readThread.join(); 77 | long stopTime = System.nanoTime(); 78 | 79 | long duration = stopTime - startTime; 80 | long durationInMilliseconds = duration / 1_000_000; 81 | 82 | assertTrue("read() was not interrupted by close() before read() timed out", durationInMilliseconds < readTimeoutInMilliseconds); 83 | assertEquals("read() threw an exception", null, runnable.getThrownOnThread()); 84 | } 85 | 86 | private Path getTemporarySocketFileName() throws IOException { 87 | Path socketPath = Files.createTempFile("jnr-unixsocket-tests", ".sock"); 88 | Files.delete(socketPath); 89 | socketPath.toFile().deleteOnExit(); 90 | 91 | return socketPath; 92 | } 93 | 94 | private void startServer(Path socketPath) throws IOException { 95 | UnixServerSocketChannel serverChannel = UnixServerSocketChannel.open(); 96 | serverChannel.configureBlocking(false); 97 | serverChannel.socket().bind(new UnixSocketAddress(socketPath.toFile())); 98 | } 99 | 100 | private UnixSocket createClient(Path socketPath, int readTimeoutInMilliseconds) throws IOException { 101 | UnixSocketChannel clientChannel = UnixSocketChannel.open(new UnixSocketAddress(socketPath.toFile())); 102 | UnixSocket socket = new UnixSocket(clientChannel); 103 | socket.setSoTimeout(readTimeoutInMilliseconds); 104 | 105 | return socket; 106 | } 107 | 108 | private class ReadFromSocketRunnable implements Runnable { 109 | private CountDownLatch readStartLatch; 110 | private UnixSocket socket; 111 | private IOException thrownOnThread; 112 | 113 | private ReadFromSocketRunnable(CountDownLatch readStartLatch, UnixSocket socket) { 114 | this.readStartLatch = readStartLatch; 115 | this.socket = socket; 116 | } 117 | 118 | @Override 119 | public void run() { 120 | try { 121 | readStartLatch.countDown(); 122 | socket.getInputStream().read(); 123 | } catch (IOException e) { 124 | // EBADF (bad file descriptor) is thrown when read() is interrupted 125 | if (!e.getMessage().equals("Bad file descriptor")) { 126 | thrownOnThread = e; 127 | } 128 | } 129 | } 130 | 131 | private IOException getThrownOnThread() { 132 | return thrownOnThread; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/UnixSocketPair.java: -------------------------------------------------------------------------------- 1 | package jnr.unixsocket; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.Socket; 6 | import java.util.UUID; 7 | 8 | class UnixSocketPair extends TestSocketPair { 9 | static final Factory FACTORY = new Factory() { 10 | @Override 11 | TestSocketPair createUnconnected() throws IOException { 12 | return new UnixSocketPair(); 13 | } 14 | }; 15 | 16 | private final File file; 17 | private final UnixSocketAddress address; 18 | 19 | private UnixServerSocketChannel serverSocketChannel; 20 | private UnixSocketChannel serverChannel; 21 | private UnixSocketChannel clientChannel; 22 | 23 | UnixSocketPair() throws IOException { 24 | file = new File("/tmp/jnr-unixsocket-test" + UUID.randomUUID() + ".sock"); 25 | address = new UnixSocketAddress(file); 26 | serverSocketChannel = UnixServerSocketChannel.open(); 27 | } 28 | 29 | @Override 30 | void serverBind() throws IOException { 31 | serverSocketChannel.configureBlocking(true); 32 | serverSocketChannel.socket().bind(address); 33 | } 34 | 35 | @Override 36 | void clientConnect() throws IOException { 37 | if (clientChannel != null) { 38 | throw new IllegalStateException("already connected"); 39 | } 40 | 41 | clientChannel = UnixSocketChannel.open(); 42 | clientChannel.connect(new UnixSocketAddress(file)); 43 | } 44 | 45 | @Override 46 | void serverAccept() throws IOException { 47 | if (serverChannel != null) { 48 | throw new IllegalStateException("already accepted"); 49 | } 50 | 51 | serverChannel = serverSocketChannel.accept(); 52 | } 53 | 54 | @Override 55 | UnixSocketAddress socketAddress() { 56 | return address; 57 | } 58 | 59 | @Override 60 | Socket server() { 61 | return serverChannel.socket(); 62 | } 63 | 64 | @Override 65 | Socket client() { 66 | return clientChannel.socket(); 67 | } 68 | 69 | @Override 70 | public void close() throws IOException { 71 | closeQuietly(serverSocketChannel); 72 | closeQuietly(serverChannel); 73 | closeQuietly(clientChannel); 74 | file.delete(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/example/LocalSyslogClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the JNR project. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package jnr.unixsocket.example; 18 | 19 | import java.io.IOException; 20 | import java.nio.ByteBuffer; 21 | import java.nio.charset.StandardCharsets; 22 | import java.text.SimpleDateFormat; 23 | import java.util.Date; 24 | import java.util.Locale; 25 | 26 | import jnr.unixsocket.UnixSocketAddress; 27 | import jnr.unixsocket.UnixDatagramChannel; 28 | 29 | import jnr.ffi.Platform; 30 | import jnr.ffi.Platform.OS; 31 | 32 | import java.lang.management.ManagementFactory; 33 | 34 | public class LocalSyslogClient { 35 | 36 | private StringBuffer line = new StringBuffer(); 37 | private SimpleDateFormat sdf = new SimpleDateFormat("MMM dd HH:mm:ss", Locale.US); 38 | 39 | 40 | private String formatDate() { 41 | synchronized(this) { 42 | return sdf.format(new Date()); 43 | } 44 | } 45 | 46 | private void formatLine(final int pri, final String tag, final int pid, final String[] args) { 47 | line.setLength(0); 48 | line.append(String.format("<%d>", pri)) 49 | .append(formatDate()) 50 | .append(" ") 51 | .append(tag); 52 | if (0 < pid) { 53 | line.append(String.format("[%d]", pid)); 54 | } 55 | line.append(":"); 56 | for (String arg : args) { 57 | line.append(" ").append(arg); 58 | } 59 | } 60 | 61 | private enum Priority { 62 | LOG_EMERG, 63 | LOG_ALERT, 64 | LOG_CRIT, 65 | LOG_ERR, 66 | LOG_WARNING, 67 | LOG_NOTICE, 68 | LOG_INFO, 69 | LOG_DEBUG; 70 | } 71 | 72 | private enum Facility { 73 | LOG_KERN(0 << 3), 74 | LOG_USER(1 << 3), 75 | LOG_MAIL(2 << 3), 76 | LOG_DAEMON(3 << 3), 77 | LOG_AUTH(4 << 3), 78 | LOG_SYSLOG(5 << 3), 79 | LOG_LPR(6 << 3), 80 | LOG_NEWS(7 << 3), 81 | LOG_UUCP(8 << 3), 82 | LOG_CRON(9 << 3), 83 | LOG_AUTHPRIV(10 << 3), 84 | LOG_FTP(11 << 3), 85 | LOG_LOCAL0(16 << 3), 86 | LOG_LOCAL1(17 << 3), 87 | LOG_LOCAL2(18 << 3), 88 | LOG_LOCAL3(19 << 3), 89 | LOG_LOCAL4(20 << 3), 90 | LOG_LOCAL5(21 << 3), 91 | LOG_LOCAL6(22 << 3), 92 | LOG_LOCAL7(23 << 3); 93 | 94 | private int myValue; 95 | 96 | Facility(int value) { 97 | myValue = value; 98 | } 99 | 100 | public int getValue() { 101 | return myValue; 102 | } 103 | } 104 | 105 | private int makePri(Priority priority, Facility facility) { 106 | return priority.ordinal() | facility.getValue(); 107 | } 108 | 109 | private String getSocketPath() { 110 | if (Platform.getNativePlatform().getOS() == OS.DARWIN) { 111 | return "/var/run/syslog"; 112 | } 113 | return "/dev/log"; 114 | } 115 | 116 | private int getPid() { 117 | String[] nameParts = ManagementFactory.getRuntimeMXBean().getName().split("@", 2); 118 | if (2 == nameParts.length) { 119 | return Integer.parseInt(nameParts[0]); 120 | } 121 | return 0; 122 | } 123 | 124 | private void doit(String[] args) throws IOException, InterruptedException { 125 | java.io.File path = new java.io.File(getSocketPath()); 126 | if (!path.exists()) { 127 | throw new IOException(String.format("%s does not exist", path.getAbsolutePath())); 128 | } 129 | UnixSocketAddress address = new UnixSocketAddress(path); 130 | UnixDatagramChannel channel = UnixDatagramChannel.open(); 131 | int pri = makePri(Priority.LOG_WARNING, Facility.LOG_DAEMON); 132 | int pid = getPid(); 133 | String tag = "whatever"; 134 | if (args.length > 0) { 135 | formatLine(pri, tag, pid, args); 136 | ByteBuffer buf = ByteBuffer.wrap(line.toString().getBytes(StandardCharsets.UTF_8)); 137 | channel.send(buf, address); 138 | } else { 139 | formatLine(pri, tag, pid, new String[]{"The quick brown fox jumps\nover the lazy dog"}); 140 | ByteBuffer buf = ByteBuffer.wrap(line.toString().getBytes(StandardCharsets.UTF_8)); 141 | channel.send(buf, address); 142 | } 143 | } 144 | 145 | public static void main(String[] args) throws IOException, InterruptedException { 146 | LocalSyslogClient client = new LocalSyslogClient(); 147 | client.doit(args); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/example/UnixClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the JNR project. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package jnr.unixsocket.example; 18 | 19 | import java.io.IOException; 20 | import java.io.InputStreamReader; 21 | import java.io.PrintWriter; 22 | import java.nio.CharBuffer; 23 | import java.nio.channels.Channels; 24 | import java.util.concurrent.TimeUnit; 25 | import jnr.unixsocket.UnixSocketAddress; 26 | import jnr.unixsocket.UnixSocketChannel; 27 | 28 | public class UnixClient { 29 | public static void main(String[] args) throws IOException, InterruptedException { 30 | java.io.File path = new java.io.File("/tmp/fubar.sock"); 31 | int retries = 0; 32 | while (!path.exists()) { 33 | TimeUnit.MILLISECONDS.sleep(500L); 34 | retries++; 35 | if (retries > 10) { 36 | throw new IOException( 37 | String.format( 38 | "File %s does not exist after retry", 39 | path.getAbsolutePath() 40 | ) 41 | ); 42 | } 43 | } 44 | String data = "blah blah"; 45 | UnixSocketAddress address = new UnixSocketAddress(path); 46 | UnixSocketChannel channel = UnixSocketChannel.open(address); 47 | System.out.println("connected to " + channel.getRemoteSocketAddress()); 48 | PrintWriter w = new PrintWriter(Channels.newOutputStream(channel)); 49 | w.print(data); 50 | w.flush(); 51 | 52 | InputStreamReader r = new InputStreamReader(Channels.newInputStream(channel)); 53 | CharBuffer result = CharBuffer.allocate(1024); 54 | r.read(result); 55 | result.flip(); 56 | System.out.println("read from server: " + result.toString()); 57 | final int status; 58 | if (!result.toString().equals(data)) { 59 | System.out.println("ERROR: data mismatch"); 60 | status = -1; 61 | } else { 62 | System.out.println("SUCCESS"); 63 | status = 0; 64 | } 65 | System.exit(status); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/jnr/unixsocket/example/UnixServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the JNR project. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package jnr.unixsocket.example; 18 | 19 | import jnr.enxio.channels.NativeSelectorProvider; 20 | import java.io.IOException; 21 | import java.nio.ByteBuffer; 22 | import java.nio.channels.SelectionKey; 23 | import java.nio.channels.Selector; 24 | import java.util.Set; 25 | import java.util.Iterator; 26 | import java.util.logging.Level; 27 | import java.util.logging.Logger; 28 | import jnr.unixsocket.UnixServerSocket; 29 | import jnr.unixsocket.UnixServerSocketChannel; 30 | import jnr.unixsocket.UnixSocketAddress; 31 | import jnr.unixsocket.UnixSocketChannel; 32 | 33 | public class UnixServer { 34 | 35 | public static void main(String[] args) throws IOException { 36 | java.io.File path = new java.io.File("/tmp/fubar.sock"); 37 | path.deleteOnExit(); 38 | UnixSocketAddress address = new UnixSocketAddress(path); 39 | UnixServerSocketChannel channel = UnixServerSocketChannel.open(); 40 | 41 | try { 42 | Selector sel = NativeSelectorProvider.getInstance().openSelector(); 43 | channel.configureBlocking(false); 44 | channel.socket().bind(address); 45 | channel.register(sel, SelectionKey.OP_ACCEPT, new ServerActor(channel, sel)); 46 | 47 | while (sel.select() > 0) { 48 | Set keys = sel.selectedKeys(); 49 | Iterator iterator = keys.iterator(); 50 | boolean running = false; 51 | boolean cancelled = false; 52 | while ( iterator.hasNext() ) { 53 | SelectionKey k = iterator.next(); 54 | Actor a = (Actor) k.attachment(); 55 | if (a.rxready()) { 56 | running = true; 57 | } else { 58 | k.cancel(); 59 | cancelled = true; 60 | } 61 | iterator.remove(); 62 | } 63 | if (!running && cancelled) { 64 | System.out.println("No Actors Running any more"); 65 | break; 66 | } 67 | } 68 | } catch (IOException ex) { 69 | Logger.getLogger(UnixServerSocket.class.getName()).log(Level.SEVERE, null, ex); 70 | } 71 | System.out.println("UnixServer EXIT"); 72 | } 73 | 74 | static interface Actor { 75 | public boolean rxready(); 76 | } 77 | 78 | static final class ServerActor implements Actor { 79 | private final UnixServerSocketChannel channel; 80 | private final Selector selector; 81 | 82 | public ServerActor(UnixServerSocketChannel channel, Selector selector) { 83 | this.channel = channel; 84 | this.selector = selector; 85 | } 86 | public final boolean rxready() { 87 | try { 88 | UnixSocketChannel client = channel.accept(); 89 | client.configureBlocking(false); 90 | client.register(selector, SelectionKey.OP_READ, new ClientActor(client)); 91 | return true; 92 | } catch (IOException ex) { 93 | return false; 94 | } 95 | } 96 | } 97 | static final class ClientActor implements Actor { 98 | private final UnixSocketChannel channel; 99 | 100 | public ClientActor(UnixSocketChannel channel) { 101 | this.channel = channel; 102 | } 103 | 104 | public final boolean rxready() { 105 | try { 106 | ByteBuffer buf = ByteBuffer.allocate(1024); 107 | int n; 108 | 109 | while ((n = channel.read(buf)) > 0) { 110 | UnixSocketAddress remote = channel.getRemoteSocketAddress(); 111 | System.out.printf("Read in %d bytes from %s%n", n, remote); 112 | 113 | if (n > 0) { 114 | buf.flip(); 115 | channel.write(buf); 116 | buf.clear(); 117 | } else if (n < 0) { 118 | return false; 119 | } 120 | } 121 | 122 | } catch (IOException ex) { 123 | ex.printStackTrace(); 124 | return false; 125 | } 126 | return true; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/test/resources/background.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | $* > background.log 2>&1 & 3 | exit 0 4 | --------------------------------------------------------------------------------