├── .appveyor.yml ├── .codecov.yml ├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── dub.sdl ├── logo.svg ├── meson.build ├── meson_options.txt ├── src └── std │ └── io │ ├── driver │ ├── package.d │ └── sync.d │ ├── exception.d │ ├── file.d │ ├── internal │ ├── iovec.d │ └── string.d │ ├── net │ ├── addr.d │ ├── dns.d │ ├── package.d │ ├── socket.d │ ├── tcp.d │ └── udp.d │ └── package.d └── travis.sh /.appveyor.yml: -------------------------------------------------------------------------------- 1 | platform: x64 2 | environment: 3 | matrix: 4 | - DC: dmd 5 | arch: x86 6 | 7 | skip_tags: false 8 | 9 | install: 10 | - ps: | 11 | #$latest = (Invoke-WebRequest "http://downloads.dlang.org/releases/LATEST").toString(); 12 | #$url = "http://downloads.dlang.org/releases/2.x/$($latest)/dmd.$($latest).windows.7z"; 13 | $url = "http://downloads.dlang.org/releases/2.x/2.093.0/dmd.2.093.0.windows.7z" 14 | (New-Object System.Net.WebClient).DownloadFile($url, "C:\dmd.7z"); 15 | 7z x C:\dmd.7z -oC:\ > $null; 16 | $env:DC="dmd" 17 | $env:DMD="dmd" 18 | $env:PATH="$($env:PATH);C:\dmd2\windows\bin;"; 19 | 20 | build: off 21 | 22 | test_script: 23 | - dub test -b unittest-cov --compiler=%DC% 24 | 25 | # Currently disabled because codecov.sh fails with an exception 26 | # after_test: 27 | # - ps: | 28 | # $env:PATH = 'C:\msys64\usr\bin;' + $env:PATH 29 | # Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh 30 | # bash codecov.sh 31 | 32 | branches: 33 | only: 34 | - master 35 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Documentation: https://github.com/codecov/support/wiki/Codecov-Yaml 2 | codecov: 3 | notify: 4 | after_n_builds: 1 # send notifications after the first upload 5 | 6 | coverage: 7 | precision: 3 8 | round: down 9 | range: 80...100 10 | 11 | status: 12 | # Learn more at https://docs.codecov.io/docs/commit-status 13 | project: true 14 | patch: true 15 | changes: true 16 | 17 | comment: false 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{c,h,d,di,dd}] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | libio.a 6 | io.so 7 | io.dylib 8 | io.dll 9 | io.a 10 | io.lib 11 | io-test-* 12 | *.exe 13 | *.o 14 | *.obj 15 | *.lst 16 | .*.swp 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | addons: 3 | apt: 4 | update: true 5 | packages: 6 | - libevent-dev 7 | - libssl-dev 8 | - pkg-config 9 | - zlib1g-dev 10 | # - ninja-build 11 | - python3 12 | - python3-pip 13 | - python3-setuptools 14 | # - meson # is too old 15 | homebrew: 16 | update: true 17 | packages: 18 | - ninja 19 | - python 20 | # - meson # is too old 21 | jobs: 22 | include: 23 | - name: "ldc-latest D compiler on Xenial Linux using dub" 24 | os: linux 25 | dist: xenial 26 | d: ldc 27 | env: 28 | - COVERAGE=false 29 | - BUILD_TOOL='dub' 30 | - name: "ldc-latest D compiler on Xenial Linux using meson" 31 | os: linux 32 | dist: xenial 33 | d: ldc 34 | env: 35 | - COVERAGE=false 36 | - BUILD_TOOL='meson' 37 | - name: "dmd-latest D compiler on Xenial Linux using dub" 38 | os: linux 39 | dist: xenial 40 | d: dmd 41 | env: 42 | - COVERAGE=false 43 | - BUILD_TOOL='dub' 44 | - name: "dmd-2.088.1 D compiler on Xenial Linux using dub" 45 | os: linux 46 | dist: xenial 47 | d: dmd-2.088.1 48 | env: 49 | - COVERAGE=true 50 | - DOCS=true 51 | - BUILD_TOOL='dub' 52 | - name: "dmd-latest D compiler on Xenial Linux using meson" 53 | os: linux 54 | dist: xenial 55 | d: dmd 56 | env: 57 | - COVERAGE=false 58 | - BUILD_TOOL='meson' 59 | - name: "dmd-2.088.1 D compiler on Xenial Linux using meson" 60 | os: linux 61 | dist: xenial 62 | d: dmd-2.088.1 63 | env: 64 | - COVERAGE=false 65 | - BUILD_TOOL='meson' 66 | - name: "dmd-nightly D compiler on Xenial Linux using dub" 67 | os: linux 68 | dist: xenial 69 | d: dmd-nightly 70 | env: 71 | - COVERAGE=false 72 | - BUILD_TOOL='dub' 73 | - name: "dmd-latest D compiler on macOS using dub" 74 | os: osx 75 | osx_image: xcode11.2 76 | d: dmd 77 | env: 78 | - COVERAGE=false 79 | - BUILD_TOOL='dub' 80 | - name: "dmd-2.088.1 D compiler on macOS using dub" 81 | os: osx 82 | osx_image: xcode11.2 83 | d: dmd-2.088.1 84 | env: 85 | - COVERAGE=false 86 | - BUILD_TOOL='dub' 87 | allow_failures: 88 | - d: dmd-nightly 89 | # - d: dmd 90 | # env: 91 | # - COVERAGE=false 92 | # - BUILD_TOOL='dub' 93 | # - d: dmd 94 | # env: 95 | # - COVERAGE=false 96 | # - BUILD_TOOL='meson' 97 | # - d: ldc 98 | # env: 99 | # - COVERAGE=false 100 | # - BUILD_TOOL='dub' 101 | 102 | before_deploy: 103 | - dub build -b ddox 104 | 105 | # https://docs.travis-ci.com/user/deployment-v2/providers/pages/ 106 | deploy: 107 | provider: pages 108 | local_dir: docs 109 | cleanup: false 110 | token: $GITHUB_TOKEN 111 | keep_history: true 112 | edge: true # opt in to dpl v2 113 | on: 114 | branch: master 115 | condition: $DOCS = true 116 | 117 | script: 118 | - ./travis.sh 119 | 120 | 121 | branches: 122 | only: 123 | - master 124 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Logo 2 | 3 | ## [![Build Status](https://travis-ci.org/MartinNowak/io.svg?branch=master)](https://travis-ci.org/MartinNowak/io) [![Build Status](https://ci.appveyor.com/api/projects/status/affs03kt2k1y48o3/branch/master?svg=true)](https://ci.appveyor.com/project/MartinNowak/io) [![codecov](https://codecov.io/gh/MartinNowak/io/branch/master/graph/badge.svg)](https://codecov.io/gh/MartinNowak/io) 4 | 5 | ## Documentation [std.io](https://martinnowak.github.io/io/std/io) 6 | 7 | IOs are thin, OS-independent abstractions over I/O devices. 8 | ```d 9 | size_t write(const scope ubyte[] buffer); 10 | size_t read(scope ubyte[] buffer); 11 | ``` 12 | 13 | IOs support [scatter/gather read/write](https://en.wikipedia.org/wiki/Vectored_I/O). 14 | ```d 15 | size_t write(const scope ubyte[][] buffers...); 16 | size_t read(scope ubyte[][] buffers...); 17 | ``` 18 | 19 | IOs are `@safe` and `@nogc`. 20 | ```d 21 | void read() @safe @nogc 22 | { 23 | auto f = File(chainPath("tmp", "file.txt")); 24 | ubyte[128] buf; 25 | f.read(buf[]); 26 | // ... 27 | } 28 | ``` 29 | 30 | IOs use exceptions for error handling. 31 | ```d 32 | try 33 | File(""); 34 | catch (IOException e) 35 | {} 36 | ``` 37 | 38 | IOs use unique ownership and are [moveable](https://dlang.org/phobos/std_algorithm_mutation.html#.move) but not copyable (Use [refCounted](https://dlang.org/phobos/std_typecons.html#refCounted) for shared ownership). 39 | ```d 40 | io2 = io.move; 41 | assert(io2.isOpen); 42 | assert(!io.isOpen); 43 | 44 | auto rc = refCounted(io2.move); 45 | auto rc2 = rc; 46 | assert(rc.isOpen); 47 | assert(rc2.isOpen); 48 | ``` 49 | 50 | IOs can be converted to polymorphic interfaces if necessary. 51 | ```d 52 | Input input = ioObject(io.move); 53 | ``` 54 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "io" 2 | description "Core I/O functionality." 3 | authors "Martin Nowak" 4 | copyright "Copyright © 2017, Martin Nowak" 5 | license "BSL-1.0 (Boost)" 6 | x:ddoxTool "scod" 7 | versions "WindowsXP" platform="windows" 8 | libs "Ws2_32" platform="windows" 9 | configuration "library" { 10 | } 11 | configuration "dip1000" { 12 | dflags "-dip1000" 13 | versions "DIP1000" 14 | } 15 | configuration "preview_in" { 16 | dflags "-preview=in" 17 | } 18 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('io', 'd', default_options: ['optimization=3'], version: '0.3.2', license: 'BSL-1.0' ) 2 | common_project_arguments = ['-release'] #, '--d-debug' 3 | add_project_arguments(common_project_arguments, language: 'd') 4 | 5 | # semver 6 | version = meson.project_version().split('.') 7 | major_version = version[0].to_int() 8 | minor_version = version[1].to_int() 9 | micro_version = version[2].to_int() 10 | 11 | # base 12 | dc = meson.get_compiler('d') 13 | 14 | # Source files 15 | 16 | sources = [ 'src/std/io/driver/package.d', 17 | 'src/std/io/driver/sync.d', 18 | 'src/std/io/exception.d', 19 | 'src/std/io/file.d', 20 | 'src/std/io/internal/iovec.d', 21 | 'src/std/io/internal/string.d', 22 | 'src/std/io/net/addr.d', 23 | 'src/std/io/net/dns.d', 24 | 'src/std/io/net/package.d', 25 | 'src/std/io/net/socket.d', 26 | 'src/std/io/net/tcp.d', 27 | 'src/std/io/net/udp.d', 28 | 'src/std/io/package.d' 29 | ] 30 | inc_dir = include_directories('src/') 31 | 32 | # Compiler and linker flags 33 | common_dflags = [] 34 | common_ldflags = [] 35 | 36 | # library 37 | if host_machine.system() == 'linux' 38 | common_ldflags += [ '-Wl,-z,relro', '-Wl,-z,now' ] 39 | endif 40 | 41 | lib_type = get_option('default_library') 42 | if lib_type == 'shared' 43 | libio_soname = 'lib@0@.so.@1@'.format(meson.project_name(), major_version) 44 | common_ldflags += ['-shared' ] 45 | if dc.get_id() == 'llvm' 46 | common_ldflags += [ '-soname', libio_soname ] 47 | endif 48 | endif 49 | 50 | 51 | io_lib = library(meson.project_name(), 52 | sources, 53 | include_directories: inc_dir, 54 | link_args : common_ldflags, 55 | install: true, 56 | version: meson.project_version(), 57 | d_unittest: false 58 | ) 59 | 60 | 61 | io_dep = declare_dependency( 62 | link_with: [io_lib], 63 | include_directories: inc_dir, 64 | dependencies: [], 65 | ) 66 | 67 | if get_option('run_test') 68 | configure_file(input: 'LICENSE.txt', output:'LICENSE.txt', copy: true) 69 | io_test_exe = executable(meson.project_name() + '-test', 70 | sources, include_directories: inc_dir, 71 | d_unittest: true, 72 | link_args: ['-main'], 73 | d_args: ['-cov'] 74 | ) 75 | test(meson.project_name() + '-test', io_test_exe) 76 | endif 77 | 78 | # Compat variables for pkgconfig 79 | pkg = import('pkgconfig') 80 | pkg.generate(io_lib, 81 | description: 'Core I/O functionality.', 82 | subdirs: 'd/' + meson.project_name(), 83 | url: 'https://github.com/MartinNowak/io' 84 | ) 85 | 86 | 87 | install_subdir('src/', 88 | strip_directory : true, 89 | install_dir: 'include/d/' + meson.project_name(), 90 | ) 91 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('run_test', type : 'boolean', value : false) 2 | -------------------------------------------------------------------------------- /src/std/io/driver/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Exchangeable driver for std.io 3 | 4 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 5 | Authors: Martin Nowak 6 | Source: $(PHOBOSSRC std/io/driver/_package.d) 7 | */ 8 | module std.io.driver; 9 | 10 | package 11 | { 12 | import std.io.file : Mode; 13 | import std.io.net.addr : AddrFamily; 14 | import std.io.net.socket : SocketType, Protocol, SocketOption; 15 | import std.io.net.dns : AddrInfo; 16 | 17 | version (Posix) 18 | import core.sys.posix.sys.socket : sockaddr, socklen_t; 19 | else version (Windows) 20 | { 21 | import core.sys.windows.windef : HANDLE; 22 | import ws2 = core.sys.windows.winsock2 : sockaddr, socklen_t; 23 | } 24 | else 25 | static assert(0, "unimplemented"); 26 | } 27 | 28 | /// All handles in std.io are based on this type. The implementation defines how it is used. 29 | struct OpaqueHandle 30 | { 31 | enum OpaqueHandle INVALID = OpaqueHandle(cast(void*)-1); 32 | void *handle; 33 | } 34 | 35 | /** 36 | The driver interface used by std.io. 37 | 38 | Swapping this by setting `driver` or `globalDriver` allows to 39 | exchange the underlying I/O implementation, e.g. for unit-testing 40 | or to integrate with an asynchronous event loop. 41 | 42 | Note that switching drivers while Files and Sockets are still open is not 43 | `@safe` and might lead to memory corruption. 44 | */ 45 | interface Driver 46 | { 47 | // FILE and SOCKET handles cannot be manipulated in @safe code, so most of 48 | // the Driver's API is @safe. 49 | @safe @nogc: 50 | /** 51 | Opaque file handle 52 | 53 | Interpretation left to driver, typically `int` file descriptor 54 | on Posix systems and `HANDLE` on Windows. 55 | */ 56 | alias FILE = OpaqueHandle; 57 | 58 | version (Posix) 59 | alias tchar = char; /// UTF-8 path on Posix, UTF-16 path on Windows 60 | else version (Windows) 61 | alias tchar = wchar; /// UTF-8 path on Posix, UTF-16 path on Windows 62 | else 63 | static assert(0, "unimplemented"); 64 | 65 | shared { 66 | /** 67 | Create/open file at `path` in `mode`. 68 | 69 | The path is UTF-8 encoded on Posix and UTF-16 encoded on Windows. 70 | It is already null-terminated for usage with C APIs. 71 | */ 72 | FILE createFile( /*in*/ const scope tchar[] path, Mode mode); 73 | /// Convert platform-specific file handle to `FILE`. 74 | version (Posix) 75 | FILE fileFromHandle(int handle); 76 | else version (Windows) 77 | FILE fileFromHandle(HANDLE handle); 78 | /// create/open file at `path` in `mode` 79 | void closeFile(scope FILE f); 80 | /// read from file into buffer 81 | size_t read(scope FILE f, scope ubyte[] buf); 82 | /// read from file into multiple buffers 83 | size_t read(scope FILE f, scope ubyte[][] bufs); 84 | /// write buffer content to file 85 | size_t write(scope FILE f, /*in*/ const scope ubyte[] buf); 86 | /// write multiple buffer contents to file 87 | size_t write(scope FILE f, /*in*/ const scope ubyte[][] bufs); 88 | /// seek file to offset 89 | ulong seek(scope FILE f, long offset, int whence); 90 | } 91 | 92 | /** 93 | Opaque socket handle 94 | 95 | Interpretation left to driver, typically `int` file descriptor 96 | on Posix systems and `SOCKET` on Windows. 97 | */ 98 | alias SOCKET = OpaqueHandle; 99 | 100 | 101 | shared { 102 | /// create socket 103 | SOCKET createSocket(AddrFamily family, SocketType type, Protocol protocol); 104 | /// Covert platform-specific socket handle to `SOCKET`. 105 | version (Posix) 106 | SOCKET socketFromHandle(int handle); 107 | else version (Windows) 108 | SOCKET socketFromHandle(ws2.SOCKET handle); 109 | /// close socket 110 | void closeSocket(scope SOCKET s); 111 | /// bind socket to `addr` 112 | void bind(scope SOCKET s, const scope sockaddr* addr, socklen_t addrlen) @system; 113 | /// connect socket to `addr` 114 | void connect(scope SOCKET s, const scope sockaddr* addr, socklen_t addrlen) @system; 115 | /// listen for incoming connections 116 | void listen(scope SOCKET s, uint backlog); 117 | /// accept an incoming connection, storing remote `addr` 118 | SOCKET accept(scope SOCKET s, scope sockaddr* addr, ref socklen_t addrlen) @system; 119 | /// get local (bound) `addr` of socket 120 | void localAddr( /*in*/ const scope SOCKET s, scope sockaddr* addr, ref socklen_t addrlen) @system; 121 | /// set socket option, type in `opt` and `optlen` is SocketOptionType!option 122 | void setSocketOption(scope SOCKET s, SocketOption option, /*in*/ const scope void* opt, 123 | uint optlen) @system; 124 | /// get socket option, type in `opt` and `optlen` is SocketOptionType!option 125 | void getSocketOption( /*in*/ const scope SOCKET s, SocketOption option, 126 | scope void* opt, socklen_t optlen) @system; 127 | /// read from socket into buffer, storing source `addr` 128 | size_t recvFrom(scope SOCKET s, scope ubyte[] buf, scope sockaddr* addr, ref socklen_t addrlen) @system; 129 | /// read from socket into multiple buffers, storing source `addr` 130 | size_t recvFrom(scope SOCKET s, scope ubyte[][] bufs, scope sockaddr* addr, ref socklen_t addrlen) @system; 131 | /// send buffer content to socket and `addr` 132 | size_t sendTo(scope SOCKET s, /*in*/ const scope ubyte[] buf, 133 | const scope sockaddr* addr, socklen_t addrlen) @system; 134 | /// send multiple buffer contents to socket and `addr` 135 | size_t sendTo(scope SOCKET s, /*in*/ const scope ubyte[][] bufs, 136 | const scope sockaddr* addr, socklen_t addrlen) @system; 137 | /// read from socket into buffer 138 | size_t recv(scope SOCKET s, scope ubyte[] buf); 139 | /// read from socket into multiple buffers 140 | size_t recv(scope SOCKET s, scope ubyte[][] bufs); 141 | /// send buffer content to socket 142 | size_t send(scope SOCKET s, /*in*/ const scope ubyte[] buf); 143 | /// send multiple buffer contents to socket 144 | size_t send(scope SOCKET s, /*in*/ const scope ubyte[][] bufs); 145 | 146 | /** 147 | Resolve `hostname` using `service`, `family`, `socktype`, and `protocol` 148 | as hints. Calls `cb` for each resolved `AddrInfo`. Iteration is 149 | terminated early when `cb` returns a non-zero value. 150 | 151 | Both `hostname` and `service` are already null-terminated for usage with C APIs. 152 | 153 | Returns: the non-zero value that terminated the iteration or 0 otherwise 154 | */ 155 | int resolve( /*in*/ const scope char[] hostname, /*in*/ const scope char[] service, AddrFamily family, 156 | SocketType socktype, Protocol protocol, 157 | scope int delegate(const scope ref AddrInfo ai) @safe @nogc cb); 158 | } 159 | 160 | /// 161 | @safe @nogc unittest 162 | { 163 | import std.io.net.addr; 164 | import std.internal.cstring : tempCString; 165 | 166 | auto res = driver.resolve("localhost", "http", AddrFamily.IPv4, 167 | SocketType.stream, Protocol.default_, (ref scope ai) { 168 | auto addr4 = ai.addr.get!SocketAddrIPv4; 169 | assert(addr4.ip == IPv4Addr(127, 0, 0, 1)); 170 | assert(addr4.port == 80); 171 | return 1; 172 | }); 173 | assert(res == 1); 174 | } 175 | } 176 | 177 | @safe unittest 178 | { 179 | import std.io.net.addr; 180 | import std.internal.cstring : tempCString; 181 | import std.process : environment; 182 | 183 | if (environment.get("SKIP_IPv6_LOOPBACK_TESTS") !is null) 184 | return; 185 | 186 | auto res = driver.resolve("localhost", "http", AddrFamily.IPv6, 187 | SocketType.stream, Protocol.default_, (ref scope ai) { 188 | auto addr6 = ai.addr.get!SocketAddrIPv6; 189 | assert(addr6.ip == IPv6Addr(0, 0, 0, 0, 0, 0, 0, 1)); 190 | assert(addr6.port == 80); 191 | return 1; 192 | }); 193 | assert(res == 1); 194 | } 195 | 196 | /** 197 | Get driver for current thread. 198 | 199 | Will default to `globalDriver` if no per-thread driver 200 | has been set. 201 | */ 202 | @property shared(Driver) driver() nothrow @trusted @nogc 203 | { 204 | if (auto d = _driver) 205 | return cast(shared(Driver)) d; 206 | else 207 | return globalDriver; 208 | } 209 | 210 | /** 211 | Set driver for current thread. 212 | 213 | Setting the per-thread driver to `null` will bring `globalDriver` 214 | in effect again. 215 | 216 | Note that this might invalidate any open File or Socket, hence it is not `@safe`. 217 | */ 218 | @property void driver(shared(Driver) d) nothrow @system @nogc 219 | { 220 | _driver = cast(Driver) d; 221 | } 222 | 223 | /** 224 | Get default driver for any thread. 225 | 226 | Lazily initializes a SyncDriver if none has been set. 227 | */ 228 | @property shared(Driver) globalDriver() nothrow @trusted @nogc 229 | { 230 | import core.atomic; 231 | import std.io.driver.sync : SyncDriver; 232 | 233 | // SyncDriver is stateless, so we can share an immutable instance 234 | static immutable Driver _syncDriver = new SyncDriver; 235 | 236 | auto d = atomicLoad!(MemoryOrder.raw)(_globalDriver); 237 | if (d is null) 238 | { 239 | // PHOBOS/COMPILER BUG: we need to insert unneccessary casts here 240 | // see https://issues.dlang.org/show_bug.cgi?id=20359 241 | cas(&_globalDriver, cast(shared(Driver))null, *cast(shared(Driver)*) &_syncDriver); 242 | d = atomicLoad!(MemoryOrder.raw)(_globalDriver); 243 | } 244 | static if (__VERSION__ < 2077) 245 | return cast(shared) d; 246 | else 247 | return d; 248 | } 249 | 250 | /** 251 | Set default driver for any thread. 252 | 253 | Note that this might invalidate any open File or Socket, hence it is not `@safe`. 254 | */ 255 | @property void globalDriver(shared(Driver) d) nothrow @system @nogc 256 | { 257 | import core.atomic; 258 | 259 | atomicStore!(MemoryOrder.raw)(_globalDriver, d); 260 | } 261 | 262 | // fetch standard handle, 0 = stdin, 1 = stdout, 2 = stderr. 263 | version(Windows) 264 | { 265 | private HANDLE _getStandardHandle(int fd) @safe @nogc 266 | { 267 | pragma(inline, true); 268 | import core.sys.windows.winbase : GetStdHandle, STD_INPUT_HANDLE, 269 | STD_OUTPUT_HANDLE, STD_ERROR_HANDLE, INVALID_HANDLE_VALUE; 270 | if (fd < 0 || fd > 2) 271 | return INVALID_HANDLE_VALUE; 272 | static immutable handles = [ 273 | STD_INPUT_HANDLE, 274 | STD_OUTPUT_HANDLE, 275 | STD_ERROR_HANDLE, 276 | ]; 277 | return () @trusted { return GetStdHandle(handles[fd]); }(); 278 | } 279 | } 280 | else version(Posix) 281 | { 282 | private int _getStandardHandle(int fd) @safe @nogc 283 | { 284 | pragma(inline, true); 285 | // simple sanity check 286 | if (fd < 0 || fd > 2) 287 | return -1; 288 | return fd; 289 | } 290 | } 291 | 292 | /** 293 | Get standard handles for the process. The items returned by these functions 294 | are OS handles, and the intention is that you convert them into an 295 | appropriate IO (e.g. File or Socket) for use. 296 | */ 297 | auto stdin() @safe @nogc 298 | { 299 | pragma(inline, true); 300 | return _getStandardHandle(0); 301 | } 302 | /// ditto 303 | auto stdout() @safe @nogc 304 | { 305 | pragma(inline, true); 306 | return _getStandardHandle(1); 307 | } 308 | /// ditto 309 | auto stderr() @safe @nogc 310 | { 311 | pragma(inline, true); 312 | return _getStandardHandle(2); 313 | } 314 | 315 | /// 316 | unittest { 317 | import std.io; 318 | import std.string : representation; 319 | // use standard handle 320 | { 321 | auto processOut = File(stdout); 322 | processOut.write("hello, world!\n".representation); 323 | } 324 | // note, opening files using OS handles does not close them on destruction. 325 | auto newProcessOut = File(stdout); 326 | newProcessOut.write("still there!\n".representation); 327 | } 328 | 329 | private: 330 | // cannot store a shared type in TLS :/ 331 | Driver _driver; 332 | shared Driver _globalDriver; 333 | -------------------------------------------------------------------------------- /src/std/io/driver/sync.d: -------------------------------------------------------------------------------- 1 | /** 2 | Synchronous driver for std.io 3 | 4 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 5 | Authors: Martin Nowak 6 | Source: $(PHOBOSSRC std/io/driver/_sync.d) 7 | */ 8 | module std.io.driver.sync; 9 | 10 | import std.io.driver; 11 | import std.io.exception : enforce; 12 | import std.io.internal.string; 13 | 14 | version (Posix) 15 | { 16 | import core.sys.posix.fcntl; 17 | import core.sys.posix.netinet.in_; 18 | import core.sys.posix.sys.socket; 19 | import core.sys.posix.sys.uio : readv, writev; 20 | import core.sys.posix.unistd : close, read, write, lseek; 21 | import std.io.internal.iovec : tempIOVecs; 22 | 23 | enum O_BINARY = 0; 24 | } 25 | else version (Windows) 26 | { 27 | import core.stdc.stdio : O_RDONLY, O_WRONLY, O_RDWR, O_APPEND, O_CREAT, 28 | O_TRUNC, O_BINARY; 29 | import core.sys.windows.winbase; 30 | import core.sys.windows.windef; 31 | import core.sys.windows.winsock2; 32 | 33 | extern (Windows) 34 | { 35 | nothrow @nogc: 36 | struct WSABUF 37 | { 38 | ULONG len; 39 | CHAR* buf; 40 | } 41 | 42 | alias WSABUF* LPWSABUF; 43 | 44 | int WSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, 45 | LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); 46 | int WSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, 47 | LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); 48 | int WSARecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, 49 | LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, sockaddr* lpFrom, 50 | LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped, 51 | LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); 52 | int WSASendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, 53 | LPDWORD lpNumberOfBytesSent, DWORD dwFlags, in sockaddr* lpTo, 54 | int iTolen, LPWSAOVERLAPPED lpOverlapped, 55 | LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); 56 | } 57 | } 58 | else 59 | static assert(0, "unimplemented"); 60 | 61 | /** 62 | Synchronous driver implementation. 63 | 64 | This driver uses the default platform APIs for synchronous I/O. 65 | */ 66 | class SyncDriver : Driver 67 | { 68 | shared @safe @nogc: 69 | 70 | //============================================================================== 71 | // files 72 | //============================================================================== 73 | 74 | override FILE createFile( /*in*/ const scope tchar[] path, Mode mode) @trusted 75 | { 76 | version (Posix) 77 | { 78 | auto fd = .open(path.ptr, mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); 79 | } 80 | else version (Windows) 81 | auto fd = .CreateFileW(path.ptr, accessMask(mode), shareMode(mode), 82 | null, creationDisposition(mode), FILE_ATTRIBUTE_NORMAL, null); 83 | else 84 | static assert(0, "unimplemented"); 85 | enforce(fd != INVALID_HANDLE_VALUE, { 86 | auto s = String("opening "); 87 | s ~= path; 88 | s ~= " failed"; 89 | return s.move; 90 | }); 91 | return h2f(fd); 92 | } 93 | 94 | version (Posix) 95 | override FILE fileFromHandle(int fd) 96 | { 97 | return h2f(fd); 98 | } 99 | else version (Windows) 100 | override FILE fileFromHandle(HANDLE fd) 101 | { 102 | return h2f(fd); 103 | } 104 | else 105 | static assert(0, "unimplemented"); 106 | 107 | override void closeFile(scope FILE f) @trusted 108 | { 109 | version (Posix) 110 | enforce(!.close(f2h(f)), "close failed".String); 111 | else 112 | enforce(CloseHandle(f2h(f)), "close failed".String); 113 | } 114 | 115 | override size_t read(scope FILE f, scope ubyte[] buf) @trusted 116 | { 117 | version (Posix) 118 | { 119 | immutable ret = .read(f2h(f), buf.ptr, buf.length); 120 | enforce(ret != -1, "read failed".String); 121 | return ret; 122 | } 123 | else version (Windows) 124 | { 125 | assert(buf.length <= uint.max); 126 | DWORD n; 127 | immutable ret = ReadFile(f2h(f), buf.ptr, cast(uint) buf.length, &n, null); 128 | enforce(ret, "read failed".String); 129 | return n; 130 | } 131 | } 132 | 133 | override size_t read(scope FILE f, scope ubyte[][] bufs) @trusted 134 | { 135 | version (Posix) 136 | { 137 | auto vecs = tempIOVecs(bufs); 138 | immutable ret = .readv(f2h(f), vecs.ptr, cast(int) vecs.length); 139 | enforce(ret != -1, "read failed".String); 140 | return ret; 141 | } 142 | else 143 | { 144 | size_t total; 145 | foreach (b; bufs) 146 | { 147 | immutable len = read(f, b); 148 | total += len; 149 | if (len < b.length) 150 | break; 151 | } 152 | return total; 153 | } 154 | } 155 | 156 | override size_t write(scope FILE f, /*in*/ const scope ubyte[] buf) @trusted 157 | { 158 | version (Posix) 159 | { 160 | immutable ret = .write(f2h(f), buf.ptr, buf.length); 161 | enforce(ret != -1, "write failed".String); 162 | return ret; 163 | } 164 | else version (Windows) 165 | { 166 | assert(buf.length <= uint.max); 167 | DWORD n; 168 | immutable ret = WriteFile(f2h(f), buf.ptr, cast(uint) buf.length, &n, null); 169 | enforce(ret, "write failed".String); 170 | return n; 171 | } 172 | } 173 | 174 | override size_t write(scope FILE f, /*in*/ const scope ubyte[][] bufs) @trusted 175 | { 176 | version (Posix) 177 | { 178 | auto vecs = tempIOVecs(bufs); 179 | immutable ret = .writev(f2h(f), vecs.ptr, cast(int) vecs.length); 180 | enforce(ret != -1, "write failed".String); 181 | return ret; 182 | } 183 | else 184 | { 185 | size_t total; 186 | foreach (b; bufs) 187 | { 188 | immutable len = write(f, b); 189 | total += len; 190 | if (len < b.length) 191 | break; 192 | } 193 | return total; 194 | } 195 | } 196 | 197 | override ulong seek(scope FILE f, long offset, int whence) @trusted 198 | { 199 | version (Posix) 200 | { 201 | immutable ret = .lseek(f2h(f), offset, whence); 202 | enforce(ret != -1, "seek failed".String); 203 | return ret; 204 | } 205 | else version (Windows) 206 | { 207 | LARGE_INTEGER off = void; 208 | off.QuadPart = offset; 209 | LARGE_INTEGER npos; 210 | immutable ret = SetFilePointerEx(f2h(f), off, &npos, whence); 211 | enforce(ret != 0, "seek failed".String); 212 | return npos.QuadPart; 213 | } 214 | } 215 | 216 | //============================================================================== 217 | // sockets 218 | //============================================================================== 219 | 220 | override SOCKET createSocket(AddrFamily family, SocketType type, Protocol protocol) 221 | { 222 | version (Windows) 223 | initWSA(); 224 | auto fd = () @trusted{ return socket(family, type, protocol); }(); 225 | enforce(fd != .INVALID_SOCKET, "creating socket Failed".String); 226 | return h2s(fd); 227 | } 228 | 229 | version (Posix) 230 | override SOCKET socketFromHandle(int fd) 231 | { 232 | return h2s(fd); 233 | } 234 | else version (Windows) 235 | override SOCKET socketFromHandle(ws2.SOCKET fd) 236 | { 237 | return h2s(fd); 238 | } 239 | else 240 | static assert(0, "unimplemented"); 241 | 242 | override void closeSocket(scope SOCKET s) @trusted 243 | { 244 | version (Posix) 245 | enforce(.close(s2h(s)) != -1, "close failed".String); 246 | else 247 | enforce(.closesocket(s2h(s)) != SOCKET_ERROR, "close failed".String); 248 | } 249 | 250 | override void bind(scope SOCKET s, const scope sockaddr* addr, socklen_t addrlen) @system 251 | { 252 | enforce(.bind(s2h(s), addr, addrlen) != -1, "bind failed".String); 253 | } 254 | 255 | override void connect(scope SOCKET s, const scope sockaddr* addr, socklen_t addrlen) @system 256 | { 257 | enforce(.connect(s2h(s), addr, addrlen) != -1, "connect failed".String); 258 | } 259 | 260 | override void listen(scope SOCKET s, uint backlog) 261 | { 262 | auto fd = s2h(s); 263 | immutable res = () @trusted{ return .listen(fd, backlog); }(); 264 | enforce(res != -1, "listen failed".String); 265 | } 266 | 267 | override SOCKET accept(scope SOCKET s, scope sockaddr* addr, ref socklen_t addrlen) @system 268 | { 269 | immutable fd = .accept(s2h(s), addr, &addrlen); 270 | enforce(fd != -1, "accept failed".String); 271 | return h2s(fd); 272 | } 273 | 274 | override void localAddr( /*in*/ const scope SOCKET s, scope sockaddr* addr, ref socklen_t addrlen) @system 275 | { 276 | immutable rc = .getsockname(s2h(s), addr, &addrlen); 277 | enforce(rc != -1, "getsockname failed".String); 278 | } 279 | 280 | override void setSocketOption(scope SOCKET s, SocketOption option, /*in*/ const scope void* opt, 281 | uint optlen) @system 282 | { 283 | immutable res = .setsockopt(s2h(s), SOL_SOCKET, option, opt, optlen); // TODO: SOL_SOCKET vs. Protocol 284 | enforce(res != -1, "setsockopt failed".String); 285 | } 286 | 287 | override void getSocketOption( /*in*/ const scope SOCKET s, SocketOption option, 288 | scope void* opt, socklen_t optlen) @system 289 | { 290 | socklen_t len = optlen; 291 | immutable res = .getsockopt(s2h(s), SOL_SOCKET, option, opt, &len); // TODO: SOL_SOCKET vs. Protocol 292 | assert(len == optlen); 293 | enforce(res != -1, "getsockopt failed".String); 294 | } 295 | 296 | override size_t recvFrom(scope SOCKET s, scope ubyte[] buf, 297 | scope sockaddr* addr, ref socklen_t addrlen) @system 298 | { 299 | version (Posix) 300 | { 301 | immutable n = .recvfrom(s2h(s), buf.ptr, buf.length, 0, addr, &addrlen); 302 | enforce(n != -1, "Failed to receive from socket.".String); 303 | } 304 | else version (Windows) 305 | { 306 | DWORD n = void, flags; 307 | immutable res = .WSARecvFrom(s2h(s), cast(WSABUF*)&buf, 1, &n, 308 | &flags, addr, &addrlen, null, null); 309 | enforce(res == 0, "recv failed".String); 310 | } 311 | return n; 312 | } 313 | 314 | override size_t recvFrom(scope SOCKET s, scope ubyte[][] bufs, 315 | scope sockaddr* addr, ref socklen_t addrlen) @system 316 | { 317 | version (Posix) 318 | { 319 | auto vecs = tempIOVecs(bufs); 320 | msghdr msg = void; 321 | msg.msg_name = addr; 322 | msg.msg_namelen = addrlen; 323 | msg.msg_iov = vecs.ptr; 324 | msg.msg_iovlen = cast(int)vecs.length; 325 | msg.msg_control = null; 326 | msg.msg_controllen = 0; 327 | msg.msg_flags = 0; 328 | immutable flags = 0; 329 | immutable n = .recvmsg(s2h(s), &msg, flags); 330 | addrlen = msg.msg_namelen; 331 | enforce(n != -1, "read failed".String); 332 | } 333 | else version (Windows) 334 | { 335 | DWORD n = void, flags; 336 | immutable res = .WSARecvFrom(s2h(s), cast(WSABUF*) bufs.ptr, 337 | cast(uint) bufs.length, &n, &flags, addr, &addrlen, null, null); 338 | enforce(res == 0, "recv failed".String); 339 | } 340 | return n; 341 | } 342 | 343 | override size_t sendTo(scope SOCKET s, /*in*/ const scope ubyte[] buf, 344 | const scope sockaddr* addr, socklen_t addrlen) @system 345 | { 346 | version (Posix) 347 | { 348 | immutable flags = 0; 349 | immutable ret = .sendto(s2h(s), buf.ptr, buf.length, flags, addr, addrlen); 350 | enforce(ret != -1, "sendTo failed".String); 351 | return ret; 352 | } 353 | else version (Windows) 354 | { 355 | DWORD n = void, flags; 356 | immutable res = .WSASendTo(s2h(s), cast(WSABUF*)&buf, 1, &n, 357 | flags, addr, addrlen, null, null); 358 | enforce(!res, "sendTo failed".String); 359 | return n; 360 | } 361 | } 362 | 363 | override size_t sendTo(scope SOCKET s, /*in*/ const scope ubyte[][] bufs, 364 | const scope sockaddr* addr, socklen_t addrlen) @system 365 | { 366 | version (Posix) 367 | { 368 | auto vecs = tempIOVecs(bufs); 369 | msghdr msg = void; 370 | msg.msg_name = cast(void*) addr; 371 | msg.msg_namelen = addrlen; 372 | msg.msg_iov = vecs.ptr; 373 | msg.msg_iovlen = cast(int)vecs.length; 374 | msg.msg_control = null; 375 | msg.msg_controllen = 0; 376 | msg.msg_flags = 0; 377 | immutable flags = 0; 378 | immutable n = .sendmsg(s2h(s), &msg, flags); 379 | enforce(n != -1, "sendTo failed".String); 380 | } 381 | else version (Windows) 382 | { 383 | DWORD n = void, flags; 384 | immutable res = .WSASendTo(s2h(s), cast(WSABUF*) bufs.ptr, 385 | cast(uint) bufs.length, &n, flags, addr, addrlen, null, null); 386 | enforce(!res, "sendTo failed".String); 387 | } 388 | return n; 389 | } 390 | 391 | override size_t recv(scope SOCKET s, scope ubyte[] buf) @trusted 392 | { 393 | version (Posix) 394 | { 395 | immutable flags = 0; 396 | immutable n = .recv(s2h(s), buf.ptr, buf.length, flags); 397 | enforce(n != -1, "recv failed".String); 398 | } 399 | else version (Windows) 400 | { 401 | DWORD n = void, flags; 402 | immutable ret = .WSARecv(s2h(s), cast(WSABUF*)&buf, 1, &n, &flags, null, null); 403 | enforce(ret == 0, "WSARecv failed".String); 404 | } 405 | return n; 406 | } 407 | 408 | override size_t recv(scope SOCKET s, scope ubyte[][] bufs) @trusted 409 | { 410 | version (Posix) 411 | { 412 | auto vecs = tempIOVecs(bufs); 413 | immutable n = .readv(s2h(s), vecs.ptr, cast(int) vecs.length); 414 | enforce(n != -1, "recv failed".String); 415 | } 416 | else version (Windows) 417 | { 418 | DWORD n = void, flags; 419 | immutable ret = .WSARecv(s2h(s), cast(WSABUF*) bufs.ptr, 420 | cast(uint) bufs.length, &n, &flags, null, null); 421 | enforce(ret == 0, "WSARecv failed".String); 422 | } 423 | return n; 424 | } 425 | 426 | override size_t send(scope SOCKET s, /*in*/ const scope ubyte[] buf) @trusted 427 | { 428 | version (Posix) 429 | { 430 | immutable n = .send(s2h(s), buf.ptr, buf.length, 0); 431 | enforce(n != -1, "send failed".String); 432 | } 433 | else version (Windows) 434 | { 435 | DWORD n = void, flags; 436 | immutable ret = .WSASend(s2h(s), cast(WSABUF*)&buf, 1, &n, flags, null, null); 437 | enforce(ret == 0, "send failed".String); 438 | } 439 | return n; 440 | } 441 | 442 | override size_t send(scope SOCKET s, /*in*/ const scope ubyte[][] bufs) @trusted 443 | { 444 | version (Posix) 445 | { 446 | auto vecs = tempIOVecs(bufs); 447 | immutable n = .writev(s2h(s), vecs.ptr, cast(int) vecs.length); 448 | enforce(n != -1, "send failed".String); 449 | } 450 | else version (Windows) 451 | { 452 | DWORD n = void, flags; 453 | immutable ret = .WSASend(s2h(s), cast(WSABUF*) bufs.ptr, 454 | cast(uint) bufs.length, &n, flags, null, null); 455 | enforce(ret == 0, "send failed".String); 456 | } 457 | return n; 458 | } 459 | 460 | //============================================================================== 461 | // DNS 462 | //============================================================================== 463 | 464 | int resolve( /*in*/ const scope char[] hostname, /*in*/ const scope char[] service, AddrFamily family, 465 | SocketType socktype, Protocol protocol, 466 | scope int delegate(const scope ref AddrInfo ai) @safe @nogc cb) @trusted 467 | { 468 | version (Posix) 469 | import core.sys.posix.netdb; 470 | else version (Windows) 471 | import core.sys.windows.winsock2; 472 | else 473 | static assert(0, "unimplemented"); 474 | import std.io.net.dns : DNSException; 475 | 476 | version (Windows) 477 | initWSA(); 478 | 479 | addrinfo hints = void; 480 | with (hints) 481 | { 482 | version (Posix) 483 | ai_flags = AI_V4MAPPED | AI_ADDRCONFIG; 484 | else 485 | ai_flags = AI_ADDRCONFIG; 486 | ai_family = family; 487 | ai_socktype = socktype; 488 | ai_protocol = protocol; 489 | ai_addrlen = 0; 490 | ai_canonname = null; 491 | ai_addr = null; 492 | ai_next = null; 493 | } 494 | addrinfo* pai; 495 | immutable ret = getaddrinfo(hostname.ptr, service.ptr, &hints, &pai); 496 | enforce!DNSException(ret == 0, { 497 | auto s = String("failed to resolve '"); 498 | s ~= hostname; 499 | s ~= ':'; 500 | s ~= service; 501 | s ~= '\''; 502 | return s.move; 503 | }, ret); 504 | scope (exit) 505 | freeaddrinfo(pai); 506 | for (; pai; pai = pai.ai_next) 507 | { 508 | static assert(AddrInfo.sizeof == addrinfo.sizeof); 509 | if (auto r = cb(*cast(const AddrInfo*) pai)) 510 | return r; 511 | } 512 | return 0; 513 | } 514 | } 515 | 516 | private: 517 | 518 | version (Posix) 519 | { 520 | enum int INVALID_HANDLE_VALUE = -1; 521 | enum int INVALID_SOCKET = -1; 522 | 523 | static assert(int.sizeof <= Driver.FILE.sizeof); 524 | 525 | /// handle to file 526 | Driver.FILE h2f(return scope int fd) pure nothrow @trusted @nogc 527 | { 528 | return Driver.FILE(cast(void*) fd); 529 | } 530 | 531 | /// file to handle 532 | int f2h(scope Driver.FILE f) pure nothrow @trusted @nogc 533 | { 534 | return cast(int) f.handle; 535 | } 536 | 537 | static assert(int.sizeof <= Driver.SOCKET.sizeof); 538 | 539 | /// handle to socket 540 | Driver.SOCKET h2s(return scope int fd) pure nothrow @trusted @nogc 541 | { 542 | return Driver.SOCKET(cast(void *)fd); 543 | } 544 | 545 | /// socket to handle 546 | inout(int) s2h(scope inout Driver.SOCKET s) pure nothrow @trusted @nogc 547 | { 548 | return cast(int) s.handle; 549 | } 550 | } 551 | else version (Windows) 552 | { 553 | static import ws2 = core.sys.windows.winsock2; 554 | 555 | static assert(HANDLE.sizeof <= Driver.FILE.sizeof); 556 | 557 | /// handle to file 558 | Driver.FILE h2f(return scope HANDLE fd) pure nothrow @trusted @nogc 559 | { 560 | return Driver.FILE(cast(void*)fd); 561 | } 562 | 563 | /// file to handle 564 | HANDLE f2h(return scope Driver.FILE f) pure nothrow @trusted @nogc 565 | { 566 | return cast(HANDLE) f.handle; 567 | } 568 | 569 | static assert(ws2.SOCKET.sizeof <= Driver.SOCKET.sizeof); 570 | 571 | /// handle to socket 572 | Driver.SOCKET h2s(return scope ws2.SOCKET fd) pure nothrow @trusted @nogc 573 | { 574 | return Driver.SOCKET(cast(void *)fd); 575 | } 576 | 577 | /// socket to handle 578 | inout(ws2.SOCKET) s2h(scope inout Driver.SOCKET s) pure nothrow @trusted @nogc 579 | { 580 | return cast(ws2.SOCKET) s.handle; 581 | } 582 | } 583 | 584 | version (Windows) 585 | { 586 | @safe @nogc: 587 | DWORD accessMask(Mode mode) 588 | { 589 | switch (mode & (Mode.read | Mode.write | Mode.readWrite | Mode.append)) 590 | { 591 | case Mode.read: 592 | return GENERIC_READ; 593 | case Mode.write: 594 | return GENERIC_WRITE; 595 | case Mode.readWrite: 596 | return GENERIC_READ | GENERIC_WRITE; 597 | case Mode.write | Mode.append: 598 | return FILE_GENERIC_WRITE & ~FILE_WRITE_DATA; 599 | case Mode.readWrite | Mode.append: 600 | return GENERIC_READ | (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA); 601 | default: 602 | enforce(0, "invalid mode for access mask".String); 603 | assert(0); 604 | } 605 | } 606 | 607 | DWORD shareMode(Mode mode) pure nothrow 608 | { 609 | // do not lock files 610 | return FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; 611 | } 612 | 613 | DWORD creationDisposition(Mode mode) pure nothrow 614 | { 615 | switch (mode & (Mode.create | Mode.truncate)) 616 | { 617 | case cast(Mode) 0: 618 | return OPEN_EXISTING; 619 | case Mode.create: 620 | return OPEN_ALWAYS; 621 | case Mode.truncate: 622 | return TRUNCATE_EXISTING; 623 | case Mode.create | Mode.truncate: 624 | return CREATE_ALWAYS; 625 | default: 626 | assert(0); 627 | } 628 | } 629 | 630 | void initWSA() @nogc 631 | { 632 | import core.atomic; 633 | import core.stdc.stdlib : atexit; 634 | 635 | static shared bool initialized; 636 | if (!atomicLoad!(MemoryOrder.raw)(initialized)) 637 | { 638 | WSADATA wd; 639 | immutable res = () @trusted{ return WSAStartup(0x2020, &wd); }(); 640 | enforce(!res, "WSAStartup failed".String); 641 | static extern (C) void cleanup() 642 | { 643 | WSACleanup(); 644 | } 645 | 646 | if (cas(&initialized, false, true)) 647 | () @trusted{ atexit(&cleanup); }(); 648 | } 649 | } 650 | } 651 | -------------------------------------------------------------------------------- /src/std/io/exception.d: -------------------------------------------------------------------------------- 1 | /// Exceptions used by std.io 2 | module std.io.exception; 3 | 4 | import std.io.internal.string; 5 | 6 | /// base class for all std.io exceptions 7 | class IOException : Exception 8 | { 9 | this(String msg) pure nothrow @safe @nogc 10 | { 11 | super(null, null, 0); 12 | this.msg = msg.move; 13 | } 14 | 15 | override void toString(scope void delegate(in char[]) sink) const 16 | { 17 | auto nothrowSink = (in char[] ch) { 18 | scope (failure) 19 | assert(0, "Throwable.toString sink should not throw."); 20 | sink(ch); 21 | }; 22 | sink(typeid(this).name); 23 | 24 | if (!msg.empty) 25 | { 26 | sink(": "); 27 | sink(msg[]); 28 | } 29 | // I/O specific error message 30 | ioError(nothrowSink); 31 | if (info) 32 | { 33 | try 34 | { 35 | sink("\n----------------"); 36 | foreach (t; info) 37 | { 38 | sink("\n"); 39 | sink(t); 40 | } 41 | } 42 | catch (Throwable) 43 | { 44 | // ignore more errors 45 | } 46 | } 47 | } 48 | 49 | protected: 50 | // The IO error message (errno, gai) 51 | void ioError(scope void delegate(in char[]) nothrow sink) const nothrow 52 | { 53 | } 54 | 55 | private: 56 | String msg; 57 | } 58 | 59 | 60 | unittest 61 | { 62 | import std.array : Appender; 63 | Appender!(char[]) buffer; 64 | 65 | void bufferSink(in char[] line) @system 66 | { 67 | buffer.put(line); 68 | } 69 | 70 | scope e = new IOException(String("Hello, World")); 71 | 72 | e.toString(&bufferSink); 73 | assert(buffer[] == "std.io.exception.IOException: Hello, World"); 74 | 75 | buffer.clear(); 76 | e.toString(line => buffer.put(line)); 77 | assert(buffer[] == "std.io.exception.IOException: Hello, World"); 78 | } 79 | 80 | /// exception used by most std.io functions 81 | class ErrnoException : IOException 82 | { 83 | immutable int errno; /// OS error code 84 | 85 | this(String msg) nothrow @safe @nogc 86 | { 87 | super(msg.move); 88 | version (Windows) 89 | { 90 | import core.sys.windows.winbase : GetLastError; 91 | 92 | this.errno = GetLastError; 93 | } 94 | else 95 | { 96 | import core.stdc.errno : errno; 97 | 98 | this.errno = errno; 99 | } 100 | } 101 | 102 | protected: 103 | override void ioError(scope void delegate(in char[]) nothrow sink) const nothrow 104 | { 105 | if (!errno) 106 | return; 107 | 108 | version (Windows) 109 | { 110 | import core.sys.windows.winbase; 111 | 112 | char[256] buf = void; 113 | immutable n = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 114 | null, errno, 0, buf.ptr, buf.length, null); 115 | sink(": "); 116 | if (n) 117 | { 118 | sink(buf[0 .. n]); 119 | sink(" "); 120 | } 121 | } 122 | else version (Posix) 123 | { 124 | import core.stdc.string : strlen, strerror_r; 125 | 126 | char[128] buf = void; 127 | const(char)* p; 128 | version (CRuntime_Glibc) 129 | p = strerror_r(errno, buf.ptr, buf.length); 130 | else if (!strerror_r(errno, buf.ptr, buf.length)) 131 | p = buf.ptr; 132 | 133 | sink(": "); 134 | if (p !is null) 135 | { 136 | sink(p[0 .. p.strlen]); 137 | sink(" "); 138 | } 139 | } 140 | 141 | import core.internal.string : signedToTempString; 142 | 143 | sink("(error="); 144 | sink(signedToTempString(errno)); 145 | sink(")"); 146 | } 147 | } 148 | 149 | // TLS storage shared for all exceptions 150 | private void[128] _store; 151 | 152 | private T staticException(T)() @nogc if (is(T : Throwable)) 153 | { 154 | // pure hack, what we actually need is @noreturn and allow to call that in pure functions 155 | static T get() 156 | { 157 | static assert(__traits(classInstanceSize, T) <= _store.length, 158 | T.stringof ~ " is too large for staticError()"); 159 | 160 | _store[0 .. __traits(classInstanceSize, T)] = typeid(T).initializer[]; 161 | return cast(T) _store.ptr; 162 | } 163 | 164 | auto res = (cast(T function() @trusted pure nothrow @nogc)&get)(); 165 | return res; 166 | } 167 | 168 | package T enforce(Ex = ErrnoException, T, Args...)(T condition, 169 | scope String delegate() pure nothrow @safe @nogc msg, auto ref Args args) @trusted @nogc 170 | { 171 | if (condition) 172 | return condition; 173 | throw staticException!Ex.__ctor(msg(), args); 174 | } 175 | 176 | package T enforce(Ex = ErrnoException, T, Args...)(T condition, String msg, auto ref Args args) @trusted @nogc 177 | { 178 | if (condition) 179 | return condition; 180 | throw staticException!Ex.__ctor(msg.move, args); 181 | } 182 | -------------------------------------------------------------------------------- /src/std/io/file.d: -------------------------------------------------------------------------------- 1 | /** 2 | Files 3 | 4 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 5 | Authors: Martin Nowak 6 | Source: $(PHOBOSSRC std/io/net/_package.d) 7 | */ 8 | module std.io.file; 9 | 10 | import std.io.exception : enforce; 11 | import std.io.internal.string; 12 | import std.io.driver; 13 | 14 | version (Posix) 15 | { 16 | import core.sys.posix.fcntl; 17 | import core.sys.posix.stdio : SEEK_SET, SEEK_CUR, SEEK_END; 18 | import core.sys.posix.sys.uio : readv, writev; 19 | import core.sys.posix.unistd : close, read, write; 20 | import std.io.internal.iovec : tempIOVecs; 21 | 22 | enum O_BINARY = 0; 23 | } 24 | else version (Windows) 25 | { 26 | import core.sys.windows.windef; 27 | import core.sys.windows.winbase; 28 | import core.sys.windows.winbase : SEEK_SET=FILE_BEGIN, SEEK_CUR=FILE_CURRENT, SEEK_END=FILE_END; 29 | import core.stdc.stdio : O_RDONLY, O_WRONLY, O_RDWR, O_APPEND, O_CREAT, 30 | O_TRUNC, O_BINARY; 31 | } 32 | else 33 | static assert(0, "unimplemented"); 34 | 35 | /// File open mode 36 | enum Mode 37 | { 38 | read = O_RDONLY, /// open for reading only 39 | write = O_WRONLY, /// open for writing only 40 | readWrite = O_RDWR, /// open for reading and writing 41 | append = O_APPEND, /// open in append mode 42 | create = O_CREAT, /// create file if missing 43 | truncate = O_TRUNC, /// truncate existing file 44 | binary = O_BINARY, /// open in binary mode 45 | } 46 | 47 | /** 48 | Convert `fopen` string modes to `Mode` enum values. 49 | The mode `m` can be one of the following strings. 50 | 51 | $(TABLE 52 | $(THEAD mode, meaning) 53 | $(TROW `"r"`, open for reading) 54 | $(TROW `"r+"`, open for reading) 55 | $(TROW `"w"`, create or truncate and open for writing) 56 | $(TROW `"w+"`, create or truncate and open for reading and writing) 57 | $(TROW `"a"`, create or truncate and open for appending) 58 | $(TROW `"a+"`, create or truncate and open for reading and appending) 59 | ) 60 | 61 | The mode string can be followed by a `"b"` flag to open files in 62 | binary mode. This only has an effect on Windows. 63 | 64 | Params: 65 | m = fopen mode to convert to `Mode` enum 66 | 67 | Macros: 68 | THEAD=$(TR $(THX $1, $+)) 69 | THX=$(TH $1)$(THX $+) 70 | TROW=$(TR $(TDX $1, $+)) 71 | TDX=$(TD $1)$(TDX $+) 72 | */ 73 | enum Mode mode(string m) = getMode(m); 74 | 75 | /// 76 | unittest 77 | { 78 | assert(mode!"r" == Mode.read); 79 | assert(mode!"r+" == Mode.readWrite); 80 | assert(mode!"w" == (Mode.create | Mode.truncate | Mode.write)); 81 | assert(mode!"w+" == (Mode.create | Mode.truncate | Mode.readWrite)); 82 | assert(mode!"a" == (Mode.create | Mode.write | Mode.append)); 83 | assert(mode!"a+" == (Mode.create | Mode.readWrite | Mode.append)); 84 | assert(mode!"rb" == (Mode.read | Mode.binary)); 85 | assert(mode!"r+b" == (Mode.readWrite | Mode.binary)); 86 | assert(mode!"wb" == (Mode.create | Mode.truncate | Mode.write | Mode.binary)); 87 | assert(mode!"w+b" == (Mode.create | Mode.truncate | Mode.readWrite | Mode.binary)); 88 | assert(mode!"ab" == (Mode.create | Mode.write | Mode.append | Mode.binary)); 89 | assert(mode!"a+b" == (Mode.create | Mode.readWrite | Mode.append | Mode.binary)); 90 | static assert(!__traits(compiles, mode!"xyz")); 91 | } 92 | 93 | private Mode getMode(string m) 94 | { 95 | switch (m) with (Mode) 96 | { 97 | case "r": 98 | return read; 99 | case "r+": 100 | return readWrite; 101 | case "w": 102 | return write | create | truncate; 103 | case "w+": 104 | return readWrite | create | truncate; 105 | case "a": 106 | return write | create | append; 107 | case "a+": 108 | return readWrite | create | append; 109 | case "rb": 110 | return read | binary; 111 | case "r+b": 112 | return readWrite | binary; 113 | case "wb": 114 | return write | create | truncate | binary; 115 | case "w+b": 116 | return readWrite | create | truncate | binary; 117 | case "ab": 118 | return write | create | append | binary; 119 | case "a+b": 120 | return readWrite | create | append | binary; 121 | default: 122 | assert(0, "Unknown open mode '" ~ m ~ "'."); 123 | } 124 | } 125 | 126 | /// File seek methods 127 | enum Seek 128 | { 129 | set = SEEK_SET, /// file offset is set to `offset` bytes from the beginning 130 | cur = SEEK_CUR, /// file offset is set to current position plus `offset` bytes 131 | end = SEEK_END, /// file offset is set to the size of the file plus `offset` bytes 132 | } 133 | 134 | /** 135 | */ 136 | struct File 137 | { 138 | @safe @nogc: 139 | /** 140 | Open a file at `path` with the options specified in `mode`. 141 | 142 | Params: 143 | path = filesystem path 144 | mode = file open flags 145 | */ 146 | this(S)(S path, Mode mode = mode!"r") @trusted if (isStringLike!S) 147 | { 148 | closeOnDestroy = true; 149 | version (Posix) 150 | { 151 | import std.internal.cstring : tempCString; 152 | 153 | f = driver.createFile(tempCString(path)[], mode); 154 | } 155 | else version (Windows) 156 | { 157 | import std.internal.cstring : tempCStringW; 158 | 159 | f = driver.createFile(tempCStringW(path)[], mode); 160 | } 161 | } 162 | 163 | /// Wrap an existing open file `handle`. If `takeOwnership` is set to true, 164 | /// then the descriptor will be closed when the destructor runs. 165 | version (Posix) 166 | this(int handle, bool takeOwnership = false) 167 | { 168 | closeOnDestroy = takeOwnership; 169 | f = driver.fileFromHandle(handle); 170 | } 171 | else version (Windows) 172 | this(HANDLE handle, bool takeOwnership = false) 173 | { 174 | closeOnDestroy = takeOwnership; 175 | f = driver.fileFromHandle(handle); 176 | } 177 | 178 | /// 179 | ~this() scope 180 | { 181 | if(closeOnDestroy) 182 | close(); 183 | } 184 | 185 | /// close the file 186 | void close() scope @trusted 187 | { 188 | if (f is Driver.FILE.INVALID) 189 | return; 190 | driver.closeFile(f); 191 | f = Driver.FILE.INVALID; 192 | closeOnDestroy = false; 193 | } 194 | 195 | /// return whether file is open 196 | bool isOpen() const scope 197 | { 198 | return f != Driver.FILE.INVALID; 199 | } 200 | 201 | /// 202 | unittest 203 | { 204 | File f; 205 | assert(!f.isOpen); 206 | f = File("LICENSE.txt"); 207 | assert(f.isOpen); 208 | f.close; 209 | assert(!f.isOpen); 210 | } 211 | 212 | /** 213 | Read from file into buffer. 214 | 215 | Params: 216 | buf = buffer to read into 217 | Returns: 218 | number of bytes read 219 | */ 220 | size_t read(scope ubyte[] buf) @trusted scope 221 | { 222 | return driver.read(f, buf); 223 | } 224 | 225 | /// 226 | unittest 227 | { 228 | auto f = File("LICENSE.txt"); 229 | ubyte[256] buf = void; 230 | assert(f.read(buf[]) == buf.length); 231 | } 232 | 233 | /** 234 | Read from file into multiple buffers. 235 | The read will be atomic on Posix platforms. 236 | 237 | Params: 238 | bufs = buffers to read into 239 | Returns: 240 | total number of bytes read 241 | */ 242 | size_t read(scope ubyte[][] bufs...) @trusted scope 243 | { 244 | return driver.read(f, bufs); 245 | } 246 | 247 | /// 248 | unittest 249 | { 250 | auto f = File("LICENSE.txt"); 251 | ubyte[256] buf = void; 252 | assert(f.read(buf[$ / 2 .. $], buf[0 .. $ / 2]) == buf.length); 253 | } 254 | 255 | @("partial reads") 256 | unittest 257 | { 258 | auto f = File("LICENSE.txt"); 259 | ubyte[256] buf = void; 260 | auto len = f.read(buf[$ / 2 .. $], buf[0 .. $ / 2]); 261 | while (len == buf.length) 262 | len = f.read(buf[$ / 2 .. $], buf[0 .. $ / 2]); 263 | assert(len < buf.length); 264 | } 265 | 266 | /** 267 | Write buffer content to file. 268 | 269 | Params: 270 | buf = buffer to write 271 | Returns: 272 | number of bytes written 273 | */ 274 | size_t write( /*in*/ const scope ubyte[] buf) @trusted scope 275 | { 276 | return driver.write(f, buf); 277 | } 278 | 279 | /// 280 | unittest 281 | { 282 | auto f = File("temp.txt", mode!"w"); 283 | scope (exit) 284 | remove("temp.txt"); 285 | ubyte[256] buf = 42; 286 | assert(f.write(buf[]) == buf.length); 287 | } 288 | 289 | /** 290 | Write multiple buffers to file. 291 | The writes will be atomic on Posix platforms. 292 | 293 | Params: 294 | bufs = buffers to write 295 | Returns: 296 | total number of bytes written 297 | */ 298 | size_t write( /*in*/ const scope ubyte[][] bufs...) @trusted scope 299 | { 300 | return driver.write(f, bufs); 301 | } 302 | 303 | /// 304 | unittest 305 | { 306 | auto f = File("temp.txt", mode!"w"); 307 | scope (exit) 308 | remove("temp.txt"); 309 | ubyte[256] buf = 42; 310 | assert(f.write(buf[$ / 2 .. $], buf[0 .. $ / 2]) == buf.length); 311 | } 312 | 313 | /** 314 | Reposition the current read and write offset in the file. 315 | 316 | Params: 317 | offset = positive or negative number of bytes to seek by 318 | whence = position in the file to seek from 319 | Returns: 320 | resulting offset in file 321 | */ 322 | ulong seek(long offset, Seek whence) scope 323 | { 324 | return driver.seek(f, offset, whence); 325 | } 326 | 327 | /// 328 | unittest 329 | { 330 | auto f = File("LICENSE.txt"); 331 | ubyte[32] buf1 = void, buf2 = void; 332 | assert(f.read(buf1[]) == buf1.length); 333 | 334 | assert(f.seek(0, Seek.cur) == buf1.length); 335 | 336 | assert(f.seek(-long(buf1.length), Seek.cur) == 0); 337 | assert(f.read(buf2[]) == buf2.length); 338 | assert(buf1[] == buf2[]); 339 | 340 | assert(f.seek(0, Seek.set) == 0); 341 | assert(f.read(buf2[]) == buf2.length); 342 | assert(buf1[] == buf2[]); 343 | 344 | f.seek(-8, Seek.end); 345 | assert(f.read(buf2[]) == 8); 346 | assert(buf1[] != buf2[]); 347 | } 348 | 349 | /// move operator for file 350 | File move() return scope nothrow /*pure Issue 18590*/ 351 | { 352 | auto f = this.f; 353 | auto cod = closeOnDestroy; 354 | this.f = Driver.FILE.INVALID; 355 | this.closeOnDestroy = false; 356 | return File(f, cod); 357 | } 358 | 359 | /// not copyable 360 | @disable this(this); 361 | 362 | private: 363 | 364 | this(return scope Driver.FILE f, bool cod) @trusted pure nothrow 365 | { 366 | this.f = f; 367 | this.closeOnDestroy = cod; 368 | } 369 | 370 | Driver.FILE f = Driver.FILE.INVALID; 371 | // close when the destructor is run. True normally unless one wraps an 372 | // existing handle (e.g. stdout). 373 | bool closeOnDestroy = false; 374 | } 375 | 376 | /// 377 | unittest 378 | { 379 | auto f = File("temp.txt", mode!"w"); 380 | scope (exit) 381 | remove("temp.txt"); 382 | f.write([0, 1]); 383 | } 384 | 385 | private: 386 | 387 | @safe unittest 388 | { 389 | import std.io : isIO; 390 | 391 | static assert(isIO!File); 392 | 393 | static File use(File f) 394 | { 395 | ubyte[4] buf = [0, 1, 2, 3]; 396 | f.write(buf); 397 | f.write([4, 5], [6, 7]); 398 | return f.move; 399 | } 400 | 401 | auto f = File("temp.txt", mode!"w"); 402 | scope(exit) remove("temp.txt"); 403 | f = use(f.move); 404 | f = File("temp.txt", Mode.read); 405 | ubyte[4] buf; 406 | f.read(buf[]); 407 | assert(buf[] == [0, 1, 2, 3]); 408 | ubyte[2] a, b; 409 | f.read(a[], b[]); 410 | assert(a[] == [4, 5]); 411 | assert(b[] == [6, 7]); 412 | } 413 | 414 | // test taking ownership 415 | unittest 416 | { 417 | static import std.stdio; // need a way to open files without this library 418 | auto f = std.stdio.File("UT_2.txt", "wb"); 419 | scope(exit) remove("UT_2.txt"); 420 | 421 | { 422 | // take ownership of the file descriptor 423 | version(Windows) 424 | auto iof = File((() @trusted => f.windowsHandle)(), true); 425 | else 426 | auto iof = File(f.fileno, true); 427 | ubyte[4] buf = [7, 8, 9, 10]; 428 | iof.write(buf[]); 429 | } 430 | 431 | // file descriptor should be closed 432 | bool caught = false; 433 | try 434 | { 435 | // make sure destructor runs here, and not in outer scope. 436 | auto f2 = f; 437 | f = std.stdio.File.init; 438 | ubyte[4] buf = [11, 12, 13, 14]; 439 | () @trusted {f2.rawWrite(buf[]);} (); 440 | } 441 | catch (Exception e) 442 | { 443 | caught = true; 444 | } 445 | assert(caught); 446 | } 447 | 448 | version (unittest) private void remove(in char[] path) @trusted @nogc 449 | { 450 | version (Posix) 451 | { 452 | import core.sys.posix.unistd : unlink; 453 | import std.internal.cstring : tempCString; 454 | 455 | enforce(unlink(tempCString(path)) != -1, "unlink failed".String); 456 | } 457 | else version (Windows) 458 | { 459 | import std.internal.cstring : tempCStringW; 460 | 461 | enforce(DeleteFileW(tempCStringW(path)), "DeleteFile failed".String); 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /src/std/io/internal/iovec.d: -------------------------------------------------------------------------------- 1 | /* 2 | Temporary iovec array 3 | 4 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 5 | Authors: Martin Nowak 6 | Source: $(PHOBOSSRC std/io/internal/_iovec.d) 7 | */ 8 | module std.io.internal.iovec; 9 | 10 | version (Posix): 11 | package(std.io): 12 | 13 | TempIOVecs tempIOVecs(return scope inout(ubyte[])[] bufs) pure nothrow @nogc @safe 14 | { 15 | return TempIOVecs(bufs); 16 | } 17 | 18 | struct TempIOVecs 19 | { 20 | @trusted @nogc pure nothrow: 21 | enum iovec* useStack = () @trusted{ return cast(iovec*) size_t.max; }(); 22 | 23 | this(return scope inout(ubyte[])[] bufs) 24 | { 25 | import core.exception : onOutOfMemoryError; 26 | 27 | iovec* ptr; 28 | if (bufs.length > stack.length) 29 | { 30 | _ptr = cast(iovec*) pureMalloc(bufs.length * iovec.sizeof); 31 | if (_ptr is null) 32 | onOutOfMemoryError; 33 | } 34 | else 35 | _ptr = stack.ptr; 36 | foreach (i, b; bufs) 37 | _ptr[i] = iovec(cast(void*) b.ptr, b.length); 38 | if (_ptr is stack.ptr) 39 | _ptr = useStack; 40 | _length = bufs.length; 41 | } 42 | 43 | ~this() scope 44 | { 45 | if (_ptr !is useStack) 46 | pureFree(_ptr); 47 | _ptr = null; 48 | } 49 | 50 | @property inout(iovec*) ptr() inout return 51 | { 52 | return _ptr is useStack ? stack.ptr : _ptr; 53 | } 54 | 55 | @property size_t length() const scope 56 | { 57 | return _length; 58 | } 59 | 60 | @disable this(); 61 | @disable this(this); 62 | 63 | private: 64 | import core.memory : pureFree, pureMalloc; 65 | import core.sys.posix.sys.uio : iovec; 66 | 67 | iovec* _ptr; 68 | size_t _length; 69 | iovec[4] stack; 70 | } 71 | -------------------------------------------------------------------------------- /src/std/io/internal/string.d: -------------------------------------------------------------------------------- 1 | /// Helper traits and functions to deal with strings 2 | module std.io.internal.string; 3 | 4 | import std.range : ElementEncodingType, isInputRange, isInfinite; 5 | import std.traits : isSomeChar, isSomeString; 6 | import std.bitmanip : bitfields; 7 | 8 | /// trait for input ranges of chars 9 | enum isStringLike(S) = (isInputRange!S && !isInfinite!S && isSomeChar!(ElementEncodingType!S)) 10 | || isSomeString!S; 11 | 12 | /// @nogc string with unique ownership 13 | struct String 14 | { 15 | nothrow pure @safe @nogc: 16 | this(const char[] s) @trusted 17 | { 18 | ptr = s.ptr; 19 | len = cast(uint) s.length; 20 | } 21 | 22 | this(S)(scope S s) if (isStringLike!S) 23 | { 24 | this ~= s; 25 | } 26 | 27 | ~this() scope @trusted 28 | { 29 | import core.memory : pureFree; 30 | 31 | if (ptr && cap) 32 | pureFree(cast(void * ) ptr); 33 | } 34 | 35 | /// https://issues.dlang.org/show_bug.cgi?id=17927 36 | const(char)[] opSlice() const return scope @trusted 37 | { 38 | return ptr[0 .. len]; 39 | } 40 | 41 | /// 42 | @property bool empty() const 43 | { 44 | return !len; 45 | } 46 | 47 | /// 48 | void opOpAssign(string op : "~")(char c) scope 49 | { 50 | reserve(1); 51 | () @trusted 52 | { 53 | (cast(char * ) ptr)[len++] = c; 54 | } 55 | (); 56 | } 57 | 58 | /// 59 | void opOpAssign(string op : "~", S)(scope S s) scope if (isStringLike!S) 60 | { 61 | import std.range : hasLength; 62 | 63 | static if (is(ElementEncodingType!S == char) && (hasLength!S || isSomeString!S)) 64 | { 65 | immutable nlen = s.length; 66 | reserve(nlen); 67 | () @trusted 68 | { 69 | (cast(char * ) ptr)[len .. len + nlen] = s[]; 70 | } 71 | (); 72 | len += nlen; 73 | } 74 | else 75 | { 76 | import std.utf : byUTF; 77 | 78 | foreach (c; s.byUTF!char) 79 | this ~= c; 80 | } 81 | } 82 | 83 | /// 84 | void put(S)(scope S s) scope if (isStringLike!S) 85 | { 86 | this ~= s; 87 | } 88 | 89 | /// non-copyable, use `move` or `clone` 90 | @disable this(this); 91 | 92 | /// 93 | String move() scope @trusted 94 | { 95 | auto optr = ptr; 96 | auto olen = len; 97 | auto ocap = cap; 98 | ptr = null; 99 | len = 0; 100 | cap = 0; 101 | return String(optr, olen, ocap); 102 | } 103 | 104 | /// 105 | String clone() scope 106 | { 107 | return String(this[]); 108 | } 109 | 110 | /// 111 | bool opEquals(scope const ref String s) scope const 112 | { 113 | return this[] == s[]; 114 | } 115 | 116 | /// 117 | int opCmp(scope const ref String s) const 118 | { 119 | return __cmp(this[], s[]); 120 | } 121 | 122 | private: 123 | 124 | this(const(char)* ptr, uint len, uint cap) 125 | { 126 | this.ptr = ptr; 127 | this.len = len; 128 | this.cap = cap; 129 | } 130 | 131 | void reserve(size_t n) scope @trusted 132 | { 133 | import core.exception : onOutOfMemoryError; 134 | import core.memory : pureMalloc, pureRealloc; 135 | 136 | if (len + n > cap) 137 | { 138 | immutable ncap = (len + cast(uint) n) * 3 / 2; 139 | if (cap) 140 | ptr = cast(char * ) pureRealloc(cast(char * ) ptr, ncap); 141 | else if (!len) 142 | ptr = cast(char * ) pureMalloc(ncap); 143 | else 144 | { 145 | // copy non-owned string on append 146 | auto nptr = cast(char * ) pureMalloc(ncap); 147 | if (nptr) 148 | nptr[0 .. len] = ptr[0 .. len]; 149 | ptr = nptr; 150 | } 151 | cap = ncap; 152 | if (ptr is null) 153 | onOutOfMemoryError(); 154 | } 155 | } 156 | 157 | const(char)* ptr; 158 | uint len, cap; 159 | } 160 | 161 | /// 162 | nothrow pure @safe @nogc unittest 163 | { 164 | auto s = String("Hello"); 165 | assert(s[] == "Hello", s[]); 166 | s ~= " String"; 167 | assert(s[] == "Hello String", s[]); 168 | auto s2 = s.clone; 169 | assert(s == s2); 170 | // TODO: we need to enable this, the cloned string snould not be identical. 171 | //assert(s.ptr != s2.ptr); 172 | } 173 | 174 | nothrow @safe @nogc unittest 175 | { 176 | static void escape(const char[] s) nothrow @safe @nogc 177 | { 178 | static const(char)[] cache; 179 | cache = s; 180 | } 181 | 182 | scope s = String("Hello"); 183 | version (DIP1000) 184 | static assert(!__traits(compiles, escape(s[]))); 185 | auto s2 = String("Hello"); 186 | // https://issues.dlang.org/show_bug.cgi?id=17927 :/ 187 | // static assert(!__traits(compiles, escape(s2[]))); 188 | } 189 | -------------------------------------------------------------------------------- /src/std/io/net/addr.d: -------------------------------------------------------------------------------- 1 | /** 2 | Network addesses 3 | 4 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 5 | Authors: Martin Nowak 6 | Source: $(PHOBOSSRC std/io/net/_addr.d) 7 | */ 8 | module std.io.net.addr; 9 | 10 | version (Posix) 11 | { 12 | import core.sys.posix.netinet.in_; 13 | import core.sys.posix.sys.socket; 14 | import core.sys.posix.sys.un; 15 | } 16 | else version (Windows) 17 | { 18 | import core.sys.windows.winsock2; 19 | 20 | static if (__VERSION__ < 2078) // https://github.com/dlang/druntime/pull/1958 21 | import core.sys.windows.winsock2 : sockaddr_storage = SOCKADDR_STORAGE; 22 | } 23 | else 24 | static assert(0, "unimplemented"); 25 | 26 | import std.typecons : tuple, Tuple; 27 | import std.io.exception : enforce, IOException; 28 | import std.io.internal.string; 29 | 30 | @safe: // TODO nothrow pure 31 | 32 | //============================================================================== 33 | /// address family 34 | version (Posix) 35 | enum AddrFamily : short 36 | { 37 | unspecified = AF_UNSPEC, 38 | IPv4 = AF_INET, /// IPv4 39 | IPv6 = AF_INET6, /// IPv6 40 | UNIX = AF_UNIX, /// Unix socket 41 | } 42 | else version (Windows) 43 | enum AddrFamily : short 44 | { 45 | unspecified = AF_UNSPEC, 46 | IPv4 = AF_INET, /// IPv4 47 | IPv6 = AF_INET6, /// IPv6 48 | } 49 | 50 | //============================================================================== 51 | /// IPv4 internet address 52 | struct IPv4Addr 53 | { 54 | @safe: 55 | /// 56 | this(ubyte[4] parts...) pure nothrow @nogc 57 | { 58 | version(BigEndian) 59 | { 60 | addr.s_addr = parts[3] | parts[2] << 8 | parts[1] << 16 | parts[0] << 24; 61 | } 62 | else 63 | { 64 | addr.s_addr = parts[0] | parts[1] << 8 | parts[2] << 16 | parts[3] << 24; 65 | } 66 | } 67 | 68 | /// 69 | this(in_addr addr) pure nothrow @nogc 70 | { 71 | this.addr = addr; 72 | } 73 | 74 | /// 75 | string toString() pure nothrow const 76 | { 77 | import std.array : appender; 78 | 79 | auto app = appender!string(); 80 | toString(app); 81 | return app.data; 82 | } 83 | 84 | /// 85 | void toString(R)(scope auto ref R r) pure nothrow const 86 | { 87 | char[INET_ADDRSTRLEN] buf = void; 88 | r.put(ipToString(addr, buf)); 89 | } 90 | 91 | /// parse IPv4 address 92 | static IPv4Addr parse(S)(S s) pure @nogc @trusted if (isStringLike!S) 93 | { 94 | IPv4Addr addr; 95 | enforce!IOException(stringToIP(s, addr.addr), "Failed to parse IPv4 address".String); 96 | return addr; 97 | } 98 | 99 | // undocumented for implicit conversion 100 | .IPAddr IPAddr() const pure nothrow @nogc 101 | { 102 | return .IPAddr(this); 103 | } 104 | 105 | /// implicit conversion to `IPAddr` 106 | alias IPAddr this; 107 | 108 | /// The any address `0.0.0.0` represents all host network interfaces 109 | enum any = IPv4Addr(0, 0, 0, 0); 110 | 111 | private: 112 | in_addr addr; 113 | } 114 | 115 | /// 116 | pure @safe unittest 117 | { 118 | auto addr = IPv4Addr(127, 0, 0, 1); 119 | assert(addr.toString == "127.0.0.1"); 120 | assert(IPv4Addr.parse(addr.toString) == addr); 121 | } 122 | 123 | //============================================================================== 124 | /// IPv6 internet address 125 | struct IPv6Addr 126 | { 127 | @safe: 128 | /// 129 | this(ushort[8] parts...) pure nothrow @nogc 130 | { 131 | foreach (i, p; parts) 132 | { 133 | version (BigEndian) 134 | addr.s6_addr16[i] = p; 135 | else 136 | addr.s6_addr16[i] = swapEndian(p); 137 | } 138 | } 139 | 140 | /// 141 | this(in6_addr addr) pure nothrow @nogc 142 | { 143 | this.addr = addr; 144 | } 145 | 146 | /// 147 | string toString() pure nothrow const 148 | { 149 | import std.array : appender; 150 | 151 | auto app = appender!string(); 152 | toString(app); 153 | return app.data; 154 | } 155 | 156 | /// 157 | void toString(R)(scope auto ref R r) pure nothrow const @trusted 158 | { 159 | char[INET6_ADDRSTRLEN] buf = void; 160 | r.put(ipToString(addr, buf)); 161 | } 162 | 163 | /// parse IPv6 address 164 | static IPv6Addr parse(S)(S s) pure @nogc @trusted if (isStringLike!S) 165 | { 166 | IPv6Addr addr; 167 | enforce!IOException(stringToIP(s, addr.addr), "Failed to parse IPv6 address".String); 168 | return addr; 169 | } 170 | 171 | // undocumented for implicit conversion 172 | .IPAddr IPAddr() const pure nothrow @nogc 173 | { 174 | return .IPAddr(this); 175 | } 176 | 177 | /// implicit conversion to `IPAddr` 178 | alias IPAddr this; 179 | 180 | /// The any address `::` represents all host network interfaces 181 | enum any = IPv6Addr(0, 0, 0, 0, 0, 0, 0, 0); 182 | 183 | private: 184 | in6_addr addr; 185 | } 186 | 187 | /// 188 | pure @safe unittest 189 | { 190 | auto addr = IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1); 191 | assert(addr.toString == "2001:db8::1"); 192 | assert(IPv6Addr.parse(addr.toString) == addr); 193 | } 194 | 195 | //============================================================================== 196 | /** 197 | Either an IPv4 or an IPv6 internet address. 198 | 199 | See_also: $(REF IPv4Addr) and $(REF IPv6Addr) 200 | */ 201 | struct IPAddr 202 | { 203 | @safe pure nothrow: 204 | /// construct from IPv4 address 205 | this(IPv4Addr addr4) @nogc 206 | { 207 | _family = AddrFamily.IPv4; 208 | this.addr4 = addr4; 209 | } 210 | 211 | /// construct from IPv4 address 212 | this(IPv6Addr addr6) @nogc 213 | { 214 | _family = AddrFamily.IPv6; 215 | this.addr6 = addr6; 216 | } 217 | 218 | /// 219 | string toString() pure nothrow const 220 | { 221 | switch (family) 222 | { 223 | case AddrFamily.IPv4: 224 | return addr4.toString; 225 | case AddrFamily.IPv6: 226 | return addr6.toString; 227 | default: 228 | assert(0); 229 | } 230 | } 231 | 232 | /// 233 | void toString(R)(scope auto ref R r) pure nothrow const 234 | { 235 | switch (family) 236 | { 237 | case AddrFamily.IPv4: 238 | return addr4.toString(r); 239 | case AddrFamily.IPv6: 240 | return addr6.toString(r); 241 | default: 242 | assert(0); 243 | } 244 | } 245 | 246 | /// get address family of the contained IP address 247 | AddrFamily family() const @nogc 248 | { 249 | return _family; 250 | } 251 | 252 | /// Get the stored IP address specified as type `T`. 253 | ref inout(T) get(T : IPv4Addr)() inout @nogc 254 | { 255 | assert(_family == AddrFamily.IPv4); 256 | return addr4; 257 | } 258 | 259 | /// ditto 260 | ref inout(T) get(T : IPv6Addr)() inout @nogc 261 | { 262 | assert(_family == AddrFamily.IPv6); 263 | return addr6; 264 | } 265 | 266 | private: 267 | AddrFamily _family; 268 | union 269 | { 270 | IPv4Addr addr4; 271 | IPv6Addr addr6; 272 | } 273 | } 274 | 275 | /// 276 | pure nothrow @safe unittest 277 | { 278 | immutable addr4 = IPv4Addr(127, 0, 0, 1); 279 | immutable addr6 = IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1); 280 | 281 | IPAddr addr = addr4; 282 | assert(addr.family == AddrFamily.IPv4); 283 | assert(addr.get!IPv4Addr == addr4); 284 | assert(addr.toString == "127.0.0.1"); 285 | 286 | addr = addr6; 287 | assert(addr.family == AddrFamily.IPv6); 288 | assert(addr.get!IPv6Addr == addr6); 289 | assert(addr.toString == "2001:db8::1"); 290 | } 291 | 292 | @trusted unittest 293 | { 294 | import core.exception : AssertError; 295 | import std.exception : assertThrown, assertNotThrown; 296 | 297 | IPAddr addr; 298 | assertThrown!AssertError(addr.get!IPv6Addr); 299 | assertThrown!AssertError(addr.get!IPv4Addr); 300 | 301 | addr = IPAddr(IPv4Addr(127, 0, 0, 1)); 302 | assertThrown!AssertError(addr.get!IPv6Addr); 303 | assertNotThrown!AssertError(addr.get!IPv4Addr); 304 | 305 | addr = IPAddr(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); 306 | assertThrown!AssertError(addr.get!IPv4Addr); 307 | assertNotThrown!AssertError(addr.get!IPv6Addr); 308 | } 309 | 310 | /// test whether type `T` is an IP address 311 | enum isIPAddr(T) = is(T == IPv4Addr) || is(T == IPv6Addr) || is(T == IPAddr); 312 | 313 | //============================================================================== 314 | /** IPv4 socket address 315 | 316 | The socket address consists of an IPv4Addr and a port number. 317 | 318 | See_also: https://tools.ietf.org/html/rfc791 319 | */ 320 | struct SocketAddrIPv4 321 | { 322 | @safe pure nothrow: 323 | /// construct socket address from `ip`, and `port`. 324 | this(IPv4Addr ip, ushort port = 0) @nogc 325 | { 326 | sa.sin_family = family; 327 | this.ip = ip; 328 | this.port = port; 329 | } 330 | 331 | /// address family for this socket address 332 | enum AddrFamily family = AddrFamily.IPv4; 333 | 334 | /// 335 | string toString() const 336 | { 337 | import std.array : appender; 338 | 339 | auto app = appender!string(); 340 | toString(app); 341 | return app.data; 342 | } 343 | 344 | /// 345 | void toString(R)(scope auto ref R r) const 346 | { 347 | import core.internal.string : unsignedToTempString; 348 | 349 | ip.toString(r); 350 | r.put(":"); 351 | r.put(unsignedToTempString(port)[]); 352 | } 353 | 354 | /// 355 | bool opEquals()(in SocketAddrIPv4 rhs) const @nogc 356 | { 357 | assert(sa.sin_family == AddrFamily.IPv4); 358 | assert(rhs.sa.sin_family == AddrFamily.IPv4); 359 | 360 | return sa.sin_port == rhs.sa.sin_port && 361 | sa.sin_addr.s_addr == rhs.sa.sin_addr.s_addr; 362 | } 363 | 364 | /// ip for address 365 | @property IPv4Addr ip() const @nogc 366 | { 367 | return IPv4Addr(sa.sin_addr); 368 | } 369 | 370 | /// ditto 371 | @property void ip(IPv4Addr ip) @nogc 372 | { 373 | sa.sin_addr = ip.addr; 374 | } 375 | 376 | /// port number for address 377 | @property ushort port() const @nogc 378 | { 379 | version (BigEndian) 380 | return sa.sin_port; 381 | else 382 | return swapEndian(sa.sin_port); 383 | } 384 | 385 | /// ditto 386 | @property void port(ushort port) @nogc 387 | { 388 | version (BigEndian) 389 | sa.sin_port = port; 390 | else 391 | sa.sin_port = swapEndian(port); 392 | } 393 | 394 | // return sockaddr and addrlen arguments for C-API calls 395 | package(std.io.net) inout(Tuple!(sockaddr*, socklen_t)) cargs() @trusted @nogc inout return scope 396 | { 397 | auto ret = tuple(cast(const sockaddr*)&sa, sa.sizeof); 398 | return * cast(typeof(return) * ) & ret; 399 | } 400 | 401 | // undocumented for implicit conversion 402 | .SocketAddr SocketAddr() const @nogc 403 | { 404 | return .SocketAddr(this); 405 | } 406 | 407 | /// implicit conversion to `SocketAddr` 408 | alias SocketAddr this; 409 | 410 | private: 411 | sockaddr_in sa; 412 | } 413 | 414 | /// 415 | unittest 416 | { 417 | auto addr = SocketAddrIPv4(IPv4Addr(127, 0, 0, 1), 8080); 418 | assert(addr.toString == "127.0.0.1:8080"); 419 | // assert(SocketAddrIPv4.parse(addr.toString) == addr); 420 | } 421 | 422 | //============================================================================== 423 | /** IPv6 socket address 424 | 425 | The socket address consists of an IPv6Addr, a port number, a scope 426 | id, a traffic class, and a flow label. 427 | 428 | See_also: https://tools.ietf.org/html/rfc2553#section-3.3 429 | */ 430 | struct SocketAddrIPv6 431 | { 432 | @safe pure nothrow: 433 | /// construct socket address from `ip`, `port`, `flowinfo`, and `scope ID` 434 | this(IPv6Addr ip, ushort port = 0, uint flowinfo = 0, uint scopeId = 0) @nogc 435 | { 436 | sa.sin6_family = family; 437 | this.ip = ip; 438 | this.port = port; 439 | this.flowinfo = flowinfo; 440 | this.scopeId = scopeId; 441 | } 442 | 443 | /// address family for this socket address 444 | enum AddrFamily family = AddrFamily.IPv6; 445 | 446 | /// 447 | string toString() const 448 | { 449 | import std.array : appender; 450 | 451 | auto app = appender!string(); 452 | toString(app); 453 | return app.data; 454 | } 455 | 456 | /// 457 | void toString(R)(scope auto ref R r) const 458 | { 459 | import core.internal.string : unsignedToTempString; 460 | 461 | r.put("["); 462 | ip.toString(r); 463 | r.put("]:"); 464 | r.put(unsignedToTempString(port)[]); 465 | } 466 | 467 | /// 468 | bool opEquals()(in SocketAddrIPv6 rhs) const @nogc 469 | { 470 | assert(sa.sin6_family == AddrFamily.IPv6); 471 | assert(rhs.sa.sin6_family == AddrFamily.IPv6); 472 | 473 | return sa.sin6_port == rhs.sa.sin6_port && 474 | sa.sin6_flowinfo == rhs.sa.sin6_flowinfo && 475 | sa.sin6_addr.s6_addr == rhs.sa.sin6_addr.s6_addr && 476 | sa.sin6_scope_id == rhs.sa.sin6_scope_id; 477 | } 478 | 479 | /// ip for address 480 | @property IPv6Addr ip() const @nogc 481 | { 482 | return IPv6Addr(sa.sin6_addr); 483 | } 484 | 485 | /// ditto 486 | @property void ip(IPv6Addr ip) @nogc 487 | { 488 | sa.sin6_addr = ip.addr; 489 | } 490 | 491 | /// port number for address 492 | @property ushort port() const @nogc 493 | { 494 | version (BigEndian) 495 | return sa.sin6_port; 496 | else 497 | return swapEndian(sa.sin6_port); 498 | } 499 | 500 | /// ditto 501 | @property void port(ushort port) @nogc 502 | { 503 | version (BigEndian) 504 | sa.sin6_port = port; 505 | else 506 | sa.sin6_port = swapEndian(port); 507 | } 508 | 509 | /// flow label and traffic class for address 510 | @property uint flowinfo() const @nogc 511 | { 512 | return sa.sin6_flowinfo; 513 | } 514 | 515 | /// ditto 516 | @property void flowinfo(uint val) @nogc 517 | { 518 | sa.sin6_flowinfo = val; 519 | } 520 | 521 | /// scope ID for address 522 | @property uint scopeId() const @nogc 523 | { 524 | return sa.sin6_scope_id; 525 | } 526 | 527 | /// ditto 528 | @property void scopeId(uint id) @nogc 529 | { 530 | sa.sin6_scope_id = id; 531 | } 532 | 533 | // return sockaddr and addrlen arguments for C-API calls 534 | package(std.io.net) inout(Tuple!(sockaddr*, socklen_t)) cargs() @trusted @nogc inout return scope 535 | { 536 | auto ret = tuple(cast(const sockaddr*)&sa, sa.sizeof); 537 | return * cast(typeof(return) * ) & ret; 538 | } 539 | 540 | // undocumented for implicit conversion 541 | .SocketAddr SocketAddr() const @nogc 542 | { 543 | return .SocketAddr(this); 544 | } 545 | 546 | /// implicit conversion to `SocketAddr` 547 | alias SocketAddr this; 548 | 549 | private: 550 | sockaddr_in6 sa; 551 | } 552 | 553 | /// 554 | unittest 555 | { 556 | auto addr = SocketAddrIPv6(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080); 557 | assert(addr.toString == "[2001:db8::1]:8080"); 558 | // assert(SocketAddrIPv6.parse(addr.toString) == addr); 559 | } 560 | 561 | //============================================================================== 562 | /** 563 | UNIX domain socket address 564 | */ 565 | version (Posix) struct SocketAddrUnix 566 | { 567 | @safe pure nothrow: 568 | /// construct socket address from `path` 569 | this(S)(S path) @nogc if (isStringLike!S) 570 | { 571 | import std.utf : byUTF; 572 | 573 | ubyte len; 574 | sa.sun_family = family; 575 | foreach (c; path.byUTF!char) 576 | sa.sun_path[len++] = c; 577 | sa.sun_path[len++] = '\0'; 578 | static if (is(typeof(sa.sun_len))) 579 | sa.sun_len = len; 580 | } 581 | 582 | /// address family for this socket address 583 | enum AddrFamily family = AddrFamily.UNIX; 584 | 585 | /// 586 | string toString() const @trusted 587 | { 588 | return path.idup; 589 | } 590 | 591 | /// 592 | void toString(R)(scope auto ref R r) const 593 | { 594 | r.put(path); 595 | } 596 | 597 | /// 598 | bool opEquals()(in SocketAddrUnix rhs) const @nogc 599 | { 600 | assert(sa.sun_family == AddrFamily.UNIX); 601 | assert(rhs.sa.sun_family == AddrFamily.UNIX); 602 | 603 | return path == rhs.path; 604 | } 605 | 606 | // return sockaddr and addrlen arguments for C-API calls 607 | package(std.io.net) inout(Tuple!(sockaddr*, socklen_t)) cargs() @trusted @nogc inout return scope 608 | { 609 | auto ret = tuple(cast(const sockaddr*)&sa, SUN_LEN(sa)); 610 | return * cast(typeof(return) * ) & ret; 611 | } 612 | 613 | // undocumented for implicit conversion 614 | .SocketAddr SocketAddr() const @nogc 615 | { 616 | return .SocketAddr(this); 617 | } 618 | 619 | /// implicit conversion to `SocketAddr` 620 | alias SocketAddr this; 621 | 622 | private: 623 | const(char)[] path() const @nogc @trusted 624 | { 625 | import core.stdc.string : strlen; 626 | 627 | auto p = cast(const char*)&sa.sun_path[0]; 628 | static if (is(typeof(sa.sun_len))) 629 | return p[0 .. sa.sun_len - 1]; 630 | else 631 | return p[0 .. strlen(p)]; 632 | } 633 | 634 | sockaddr_un sa; 635 | static if (is(typeof(sa.sun_len))) 636 | @property ref ubyte len() @nogc return 637 | { 638 | return sa.sun_len; 639 | } 640 | } 641 | 642 | /// 643 | version (Posix) unittest 644 | { 645 | auto addr = SocketAddrUnix("/var/run/unix.sock"); 646 | assert(addr.toString == "/var/run/unix.sock"); 647 | } 648 | 649 | //============================================================================== 650 | /** 651 | Generic socket address for families such as IPv4, IPv6, or Unix. 652 | */ 653 | struct SocketAddr 654 | { 655 | @safe: 656 | /// 657 | AddrFamily family() pure nothrow const @nogc 658 | { 659 | return cast(AddrFamily) storage.ss_family; 660 | } 661 | 662 | /// Construct generic SocketAddr from IP `addr` and `port`. 663 | this(IPAddr addr, ushort port = 0) pure nothrow @nogc 664 | { 665 | switch (addr.family) 666 | { 667 | case AddrFamily.IPv4: 668 | auto p = cast(SocketAddrIPv4*)&this; 669 | p.__ctor(addr.get!IPv4Addr, port); 670 | break; 671 | case AddrFamily.IPv6: 672 | auto p = cast(SocketAddrIPv6*)&this; 673 | p.__ctor(addr.get!IPv6Addr, port); 674 | break; 675 | default: 676 | assert(0, "unimplemented"); 677 | } 678 | } 679 | 680 | /// Construct generic SocketAddr from specific type `SocketAddrX`. 681 | this(SocketAddrX)(in SocketAddrX addr) pure nothrow @trusted @nogc 682 | if (isSocketAddr!SocketAddrX) 683 | { 684 | import core.stdc.string : memcpy; 685 | 686 | static assert(addr.sa.sizeof <= storage.sizeof); 687 | memcpy(&storage, &addr, addr.sizeof); 688 | } 689 | 690 | /// 691 | string toString() const 692 | { 693 | final switch (family) 694 | { 695 | case AddrFamily.unspecified: 696 | return "unspecified address"; 697 | case AddrFamily.IPv4: 698 | return (cast(const SocketAddrIPv4*)&this).toString(); 699 | case AddrFamily.IPv6: 700 | return (cast(const SocketAddrIPv6*)&this).toString(); 701 | version (Posix) 702 | case AddrFamily.UNIX: 703 | return (cast(const SocketAddrUnix*)&this).toString(); 704 | } 705 | } 706 | 707 | /// 708 | void toString(R)(scope auto ref R r) const 709 | { 710 | final switch (family) 711 | { 712 | case AddrFamily.unspecified: 713 | r.put("unspecified address"); 714 | break; 715 | case AddrFamily.IPv4: 716 | return (cast(const SocketAddrIPv4*)&this).toString(r); 717 | case AddrFamily.IPv6: 718 | return (cast(const SocketAddrIPv6*)&this).toString(r); 719 | version (Posix) 720 | case AddrFamily.UNIX: 721 | return (cast(const SocketAddrUnix*)&this).toString(r); 722 | } 723 | } 724 | 725 | /** 726 | Get specific socket addr type. 727 | 728 | Throws: 729 | IOException if `family` does not match `SocketAddrX.family` 730 | */ 731 | ref SocketAddrX get(SocketAddrX)() inout @trusted @nogc 732 | { 733 | enforce(family == SocketAddrX.family, "mismatching address family".String); 734 | return *cast(SocketAddrX*)&this; 735 | } 736 | 737 | /// 738 | bool opEquals(SocketAddrX)(in SocketAddrX rhs) pure nothrow const @trusted @nogc 739 | { 740 | if (family != rhs.family) 741 | return false; 742 | static if (!is(SocketAddrX == SocketAddr)) 743 | return *(cast(const SocketAddrX*)&this) == rhs; 744 | else 745 | { 746 | final switch (family) 747 | { 748 | case AddrFamily.unspecified: 749 | return true; 750 | case AddrFamily.IPv4: 751 | return *(cast(const SocketAddrIPv4*)&this) == *(cast(const SocketAddrIPv4*)&rhs); 752 | case AddrFamily.IPv6: 753 | return *(cast(const SocketAddrIPv6*)&this) == *(cast(const SocketAddrIPv6*)&rhs); 754 | version (Posix) 755 | case AddrFamily.UNIX: 756 | return *(cast(const SocketAddrUnix*)&this) == *( 757 | cast(const SocketAddrUnix*)&rhs); 758 | } 759 | } 760 | } 761 | 762 | // return sockaddr and addrlen arguments for C-API calls 763 | package(std.io.net) inout(Tuple!(sockaddr*, socklen_t)) cargs() pure nothrow @trusted @nogc inout return scope 764 | { 765 | final switch (family) 766 | { 767 | case AddrFamily.unspecified : return typeof(return).init; 768 | case AddrFamily.IPv4 : return (cast(inout SocketAddrIPv4 * ) & this).cargs; 769 | case AddrFamily.IPv6 : return (cast(inout SocketAddrIPv6 * ) & this).cargs; 770 | version (Posix) case AddrFamily.UNIX : return (cast(inout SocketAddrUnix * ) & this) 771 | .cargs; 772 | } 773 | } 774 | 775 | private: 776 | sockaddr_storage storage; 777 | } 778 | 779 | /// 780 | unittest 781 | { 782 | immutable addr4 = SocketAddrIPv4(IPv4Addr(127, 0, 0, 1), 1234); 783 | immutable addr6 = SocketAddrIPv6(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 1234); 784 | version (Posix) immutable addru = SocketAddrUnix("/var/run/unix.sock"); 785 | 786 | SocketAddr addr = addr4; 787 | assert(addr.family == AddrFamily.IPv4); 788 | assert(addr.get!SocketAddrIPv4 == addr4); 789 | 790 | addr = addr6; 791 | assert(addr.family == AddrFamily.IPv6); 792 | assert(addr.get!SocketAddrIPv6 == addr6); 793 | 794 | version (Posix) 795 | { 796 | addr = addru; 797 | assert(addr.family == AddrFamily.UNIX); 798 | assert(addr.get!SocketAddrUnix == addru); 799 | } 800 | } 801 | 802 | //============================================================================== 803 | // uniform socket address construction for generic code 804 | 805 | /** 806 | Construct a `SocketAddrIPv4` from an IPv4 `addr` and `port`. 807 | */ 808 | SocketAddrIPv4 socketAddr(IPv4Addr addr, ushort port = 0) @nogc 809 | { 810 | return SocketAddrIPv4(addr, port); 811 | } 812 | 813 | /** 814 | Construct a `SocketAddrIPv6` from an IPv6 `addr`, `port`, and 815 | optionally `flowinfo` and `scopeid`. 816 | */ 817 | SocketAddrIPv6 socketAddr(IPv6Addr addr, ushort port = 0, uint flowinfo = 0, uint scopeId = 0) @nogc 818 | { 819 | return SocketAddrIPv6(addr, port, flowinfo, scopeId); 820 | } 821 | 822 | /** 823 | Construct a `SocketAddrUnix` from. 824 | */ 825 | SocketAddrUnix socketAddr(S)(S path) @nogc if (isStringLike!S) 826 | { 827 | return SocketAddrUnix(path); 828 | } 829 | 830 | /// 831 | unittest 832 | { 833 | auto sa4 = socketAddr(IPv4Addr(127, 0, 0, 1)); 834 | static assert(is(typeof(sa4) == SocketAddrIPv4)); 835 | auto sa6 = socketAddr(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); 836 | static assert(is(typeof(sa6) == SocketAddrIPv6)); 837 | } 838 | 839 | /// test whether type `T` is a socket address 840 | version (Posix) 841 | enum isSocketAddr(T) = is(T == SocketAddrIPv4) || is(T == SocketAddrIPv6) 842 | || is(T == SocketAddrUnix) || is(T == SocketAddr); 843 | else version (Windows) 844 | enum isSocketAddr(T) = is(T == SocketAddrIPv4) 845 | || is(T == SocketAddrIPv6) || is(T == SocketAddr); 846 | 847 | //============================================================================== 848 | private: 849 | 850 | // Won't actually fail with proper buffer size and typed addr, so it'll never set errno and is actually pure 851 | alias pure_inet_ntop = extern (System) const(char)* function(int,scope const(void)*, char*, socklen_t) pure nothrow @nogc; 852 | // Won't actually fail with proper addr family, so it'll never set errno and is actually pure 853 | alias pure_inet_pton = extern (System) int function(int, scope const(char)*, void*) pure nothrow @nogc; 854 | 855 | version (Windows) 856 | { 857 | extern (Windows) const(char)* inet_ntop(int, in void*, char*, size_t) nothrow @nogc; 858 | extern (Windows) int inet_pton(int, in char*, void*) nothrow @nogc; 859 | } 860 | 861 | const(char)[] ipToString(in in_addr addr, return ref char[INET_ADDRSTRLEN] buf) pure nothrow @nogc @trusted 862 | { 863 | import core.stdc.string : strlen; 864 | 865 | auto f = cast(pure_inet_ntop)&inet_ntop; 866 | auto p = f(AF_INET, &addr, buf.ptr, buf.length); 867 | if (p is null) 868 | assert(0); 869 | return p[0 .. strlen(p)]; 870 | } 871 | 872 | const(char)[] ipToString(in in6_addr addr, return ref char[INET6_ADDRSTRLEN] buf) pure nothrow @nogc @trusted 873 | { 874 | import core.stdc.string : strlen; 875 | 876 | auto f = cast(pure_inet_ntop)&inet_ntop; 877 | auto p = f(AF_INET6, &addr, buf.ptr, buf.length); 878 | if (p is null) 879 | assert(0); 880 | return p[0 .. strlen(p)]; 881 | } 882 | 883 | bool stringToIP(S)(S s, ref in_addr addr) pure nothrow @nogc @trusted 884 | { 885 | // import std.internal.cstring : tempCString; // impure 886 | 887 | auto f = cast(pure_inet_pton)&inet_pton; 888 | auto cs = String(s); 889 | cs ~= '\0'; 890 | immutable res = f(AddrFamily.IPv4, cs[].ptr, &addr); 891 | if (res == -1) 892 | assert(0); 893 | return res == 1; 894 | } 895 | 896 | bool stringToIP(S)(S s, ref in6_addr addr) /*pure*/ nothrow @nogc @trusted 897 | { 898 | // import std.internal.cstring : tempCString; // impure 899 | 900 | auto f = cast(pure_inet_pton)&inet_pton; 901 | auto cs = String(s); 902 | cs ~= '\0'; 903 | immutable res = f(AddrFamily.IPv6, cs[].ptr, &addr); 904 | if (res == -1) 905 | assert(0); 906 | return res == 1; 907 | } 908 | 909 | version (Posix) ubyte SUN_LEN(in sockaddr_un sun) pure nothrow @nogc @trusted 910 | { 911 | static if (is(typeof(sun.sun_len))) 912 | return cast(ubyte)(sun.sun_path.offsetof + sun.sun_len); 913 | else 914 | { 915 | import core.stdc.string : strlen; 916 | 917 | return cast(ubyte)(sun.sun_path.offsetof + strlen(cast(const char*) sun.sun_path.ptr)); 918 | } 919 | } 920 | 921 | ushort swapEndian(ushort val) pure nothrow @nogc @safe 922 | { 923 | return ((val & 0x00FF) << 8) | ((val & 0xFF00) >> 8); 924 | } 925 | -------------------------------------------------------------------------------- /src/std/io/net/dns.d: -------------------------------------------------------------------------------- 1 | /** 2 | Types for Domain Name Resolution. 3 | 4 | Use $(LINK2 ../driver/Driver.resolve.html, `Driver.resolve`) to 5 | actually resolve a hostname and service. 6 | 7 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 8 | Authors: Martin Nowak 9 | Source: $(PHOBOSSRC std/io/net/_dns.d) 10 | */ 11 | module std.io.net.dns; 12 | 13 | import std.io.exception : enforce, ErrnoException; 14 | import std.io.internal.string; 15 | import std.io.net.addr; 16 | import std.io.net.socket; 17 | import std.range, std.traits; 18 | 19 | /** 20 | A single resolved address entry 21 | */ 22 | struct AddrInfo 23 | { 24 | pure nothrow @safe @nogc: 25 | this(addrinfo ai) 26 | { 27 | this.ai = ai; 28 | } 29 | 30 | /// resolved address family 31 | AddrFamily family() const scope 32 | { 33 | return cast(AddrFamily) ai.ai_family; 34 | } 35 | 36 | /// resolved socket type 37 | SocketType socketType() const scope 38 | { 39 | return cast(SocketType) ai.ai_socktype; 40 | } 41 | 42 | /// resolved protocol 43 | Protocol protocol() const scope 44 | { 45 | return cast(Protocol) ai.ai_protocol; 46 | } 47 | 48 | /// get resolved socket address 49 | SocketAddr addr() const @trusted scope 50 | { 51 | import core.stdc.string : memcpy; 52 | 53 | SocketAddr addr = void; 54 | memcpy( & addr, ai.ai_addr, ai.ai_addrlen); 55 | return addr; 56 | } 57 | 58 | private: 59 | version (Posix) 60 | import core.sys.posix.netdb : addrinfo; 61 | else 62 | import core.sys.windows.winsock2 : addrinfo; 63 | 64 | addrinfo ai; 65 | } 66 | 67 | /// exception thrown on name resolution errors 68 | class DNSException : ErrnoException 69 | { 70 | immutable int gaiError; /// getaddrinfo error code 71 | 72 | this(String msg, uint gaiError) @safe @nogc 73 | { 74 | super(msg.move); 75 | this.gaiError = gaiError; 76 | } 77 | 78 | protected: 79 | override void ioError(scope void delegate(in char[]) nothrow sink) const nothrow 80 | { 81 | import core.stdc.string : strlen; 82 | 83 | version (Windows) 84 | { 85 | import core.sys.windows.winbase; 86 | 87 | char[256] buf = void; 88 | immutable n = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 89 | null, gaiError, 0, buf.ptr, buf.length, null); 90 | sink(": "); 91 | if (n) 92 | { 93 | sink(buf[0 .. n]); 94 | sink(" "); 95 | } 96 | } 97 | else version (Posix) 98 | { 99 | import core.sys.posix.netdb : gai_strerror, EAI_SYSTEM; 100 | import core.stdc.string : strlen; 101 | 102 | if (gaiError == EAI_SYSTEM) 103 | return super.ioError(sink); 104 | auto p = gai_strerror(gaiError); 105 | if (p is null) 106 | return; 107 | sink(": "); 108 | if (p !is null) 109 | { 110 | sink(p[0 .. p.strlen]); 111 | sink(" "); 112 | } 113 | } 114 | 115 | import core.internal.string : signedToTempString; 116 | 117 | sink("(error="); 118 | sink(signedToTempString(gaiError)); 119 | sink(")"); 120 | } 121 | 122 | private: 123 | String msg; 124 | } 125 | -------------------------------------------------------------------------------- /src/std/io/net/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | Network IO 3 | 4 | This package imports std.io.net.addr, std.io.net.tcp, and 5 | std.io.net.udp. 6 | 7 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 8 | Authors: Martin Nowak 9 | Source: $(PHOBOSSRC std/io/net/_package.d) 10 | */ 11 | module std.io.net; 12 | 13 | /// 14 | public import std.io.net.addr; 15 | 16 | /// 17 | public import std.io.net.tcp; 18 | 19 | /// 20 | public import std.io.net.udp; 21 | -------------------------------------------------------------------------------- /src/std/io/net/socket.d: -------------------------------------------------------------------------------- 1 | /** 2 | Low-level sockets 3 | 4 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 5 | Authors: Martin Nowak 6 | Source: $(PHOBOSSRC std/io/net/_socket.d) 7 | */ 8 | module std.io.net.socket; 9 | 10 | import std.io.driver; 11 | import std.io.exception : enforce, IOException; 12 | import std.io.internal.string; 13 | import std.io.net.addr; 14 | 15 | version (Posix) 16 | { 17 | import core.sys.posix.netinet.in_; 18 | import core.sys.posix.sys.socket; 19 | } 20 | else version (Windows) 21 | { 22 | import core.sys.windows.winsock2; 23 | } 24 | else 25 | static assert(0, "unimplemented"); 26 | 27 | /// protocol family 28 | alias ProtocolFamily = AddrFamily; 29 | 30 | /** 31 | The socket type specifies the communication semantics. 32 | 33 | See_also: http://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html 34 | */ 35 | enum SocketType 36 | { 37 | unspecified = 0, // unspecified socket type, mostly as resolve hint 38 | stream = SOCK_STREAM, // sequenced, reliable, two-way, connection-based data streams 39 | dgram = SOCK_DGRAM, // unordered, unreliable datagrams of fixed length 40 | seqpacket = SOCK_SEQPACKET, // sequenced, reliable, two-way datagrams of fixed length 41 | raw = SOCK_RAW, // raw network access 42 | } 43 | 44 | /** 45 | The socket protocol to use. 46 | 47 | See_also: https://www.iana.org/assignments/protocol-numbers 48 | */ 49 | enum Protocol 50 | { 51 | default_ = 0, /// use default protocol for protocol family and socket type 52 | ip = IPPROTO_IP, /// 53 | icmp = IPPROTO_ICMP, /// 54 | igmp = IPPROTO_IGMP, /// 55 | ggp = IPPROTO_GGP, /// 56 | tcp = IPPROTO_TCP, /// 57 | pup = IPPROTO_PUP, /// 58 | udp = IPPROTO_UDP, 59 | idp = IPPROTO_IDP, 60 | nd = IPPROTO_ND, 61 | } 62 | 63 | /** 64 | Socket options. 65 | */ 66 | enum SocketOption 67 | { 68 | acceptConn = SO_ACCEPTCONN, /// get whether socket is accepting connections 69 | broadcast = SO_BROADCAST, /// broadcast for datagram sockets 70 | debug_ = SO_DEBUG, /// enable socket debugging 71 | dontRoute = SO_DONTROUTE, /// send only to directly connected hosts 72 | error = SO_ERROR, /// get pending socket errors 73 | keepAlive = SO_KEEPALIVE, /// enable keep-alive messages on connection-based sockets 74 | linger = SO_LINGER, /// linger option 75 | oobinline = SO_OOBINLINE, /// inline receive out-of-band data 76 | rcvbuf = SO_RCVBUF, /// get or set receive buffer size 77 | rcvlowat = SO_RCVLOWAT, /// min number of input bytes to process 78 | rcvtimeo = SO_RCVTIMEO, /// receiving timeout 79 | reuseAddr = SO_REUSEADDR, /// reuse bind address 80 | sndbuf = SO_SNDBUF, /// get or set send buffer size 81 | sndlowat = SO_SNDLOWAT, /// min number of output bytes to process 82 | sndtimeo = SO_SNDTIMEO, /// sending timeout 83 | type = SO_TYPE, /// get socket type 84 | } 85 | 86 | /// option value types for SocketOption 87 | alias SocketOptionType(SocketOption opt) = int; 88 | /// ditto 89 | alias SocketOptionType(SocketOption opt : SocketOption.linger) = linger; 90 | /// ditto 91 | alias SocketOptionType(SocketOption opt : SocketOption.rcvtimeo) = timeval; 92 | /// ditto 93 | alias SocketOptionType(SocketOption opt : SocketOption.sndtimeo) = timeval; 94 | 95 | /** 96 | A socket 97 | */ 98 | struct Socket 99 | { 100 | @nogc @safe: 101 | /** 102 | Construct socket for protocol `family`, socket `type`, and `protocol`. 103 | */ 104 | this(ProtocolFamily family, SocketType type, Protocol protocol = Protocol.default_) @trusted 105 | { 106 | s = driver.createSocket(family, type, protocol); 107 | closeOnDestroy = true; 108 | } 109 | 110 | /// Wrap an existing open socket `handle`. If `takeOwnership` is set to 111 | /// true, then the handle will be closed when the destructor runs. 112 | version (Posix) 113 | this(int handle, bool takeOwnership = false) 114 | { 115 | s = driver.socketFromHandle(handle); 116 | closeOnDestroy = takeOwnership; 117 | } 118 | else version (Windows) 119 | this(SOCKET handle, bool takeOwnership = false) 120 | { 121 | s = driver.socketFromHandle(handle); 122 | closeOnDestroy = takeOwnership; 123 | } 124 | 125 | /// 126 | ~this() 127 | { 128 | if(closeOnDestroy) 129 | close(); 130 | } 131 | 132 | /// close the socket 133 | void close() @trusted 134 | { 135 | if (s == Driver.SOCKET.INVALID) 136 | return; 137 | driver.closeSocket(s); 138 | s = Driver.SOCKET.INVALID; 139 | closeOnDestroy = false; 140 | } 141 | 142 | /// return whether the socket is open 143 | bool isOpen() const pure nothrow 144 | { 145 | return s != Driver.SOCKET.INVALID; 146 | } 147 | 148 | /// 149 | unittest 150 | { 151 | Socket s; 152 | assert(!s.isOpen); 153 | s = Socket(ProtocolFamily.IPv4, SocketType.dgram); 154 | assert(s.isOpen); 155 | s.close; 156 | assert(!s.isOpen); 157 | } 158 | 159 | /** 160 | Bind socket to `addr`. 161 | 162 | Params: 163 | addr = socket address to bind 164 | 165 | See_also: http://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html 166 | Throws: `ErrnoException` if binding the socket fails 167 | */ 168 | void bind(SocketAddr)(in SocketAddr addr) @trusted 169 | if (isSocketAddr!SocketAddr) 170 | { 171 | driver.bind(s, addr.cargs[]); 172 | } 173 | 174 | /** 175 | Bind UDP socket to IP `addr` and `port`. A port number of zero will be 176 | bound to an ephemeral port. 177 | 178 | Params: 179 | addr = IP address to bind 180 | port = port number to bind 181 | */ 182 | void bind(IPAddr)(IPAddr addr, ushort port = 0) if (isIPAddr!IPAddr) 183 | { 184 | bind(addr.socketAddr(port)); 185 | } 186 | 187 | /// 188 | unittest 189 | { 190 | auto s = Socket(ProtocolFamily.IPv4, SocketType.dgram); 191 | s.bind(IPv4Addr(127, 0, 0, 1), 1234); 192 | auto localAddr = s.localAddr.get!SocketAddrIPv4; 193 | assert(s.isOpen); 194 | assert(localAddr.ip == IPv4Addr(127, 0, 0, 1)); 195 | assert(localAddr.port == 1234); 196 | 197 | s = Socket(ProtocolFamily.IPv4, SocketType.dgram); 198 | s.bind(IPv4Addr(127, 0, 0, 1)); // ephemeral port 199 | localAddr = s.localAddr.get!SocketAddrIPv4; 200 | assert(s.isOpen); 201 | assert(localAddr.ip == IPv4Addr(127, 0, 0, 1)); 202 | assert(localAddr.port != 1234); 203 | assert(localAddr.port != 0); 204 | 205 | import std.io.exception : IOException; 206 | 207 | bool thrown; 208 | try 209 | // cannot rebind 210 | s.bind(IPv4Addr(127, 0, 0, 1)); 211 | catch (IOException) 212 | thrown = true; 213 | assert(thrown); 214 | } 215 | 216 | /** 217 | Connect socket to remote `addr` 218 | 219 | Params: 220 | addr = socket address to connect to 221 | */ 222 | void connect(SocketAddr)(in SocketAddr addr) @trusted 223 | if (isSocketAddr!SocketAddr) 224 | { 225 | driver.connect(s, addr.cargs[]); 226 | } 227 | 228 | /** 229 | Connect socket to remote IP `addr` and `port`. 230 | 231 | Params: 232 | addr = IP address to connect to 233 | port = port number to connect to 234 | */ 235 | void connect(IPAddr)(IPAddr addr, ushort port = 0) if (isIPAddr!IPAddr) 236 | { 237 | connect(addr.socketAddr(port)); 238 | } 239 | 240 | /// 241 | unittest 242 | { 243 | auto server = Socket(ProtocolFamily.IPv4, SocketType.dgram); 244 | server.bind(IPv4Addr(127, 0, 0, 1)); 245 | 246 | auto client = Socket(ProtocolFamily.IPv4, SocketType.dgram); 247 | client.connect(server.localAddr); 248 | } 249 | 250 | /** 251 | Listen for incoming connections. 252 | 253 | Params: 254 | backlog = maximum number of pending connections 255 | */ 256 | void listen(uint backlog = 128) @trusted 257 | { 258 | driver.listen(s, backlog); 259 | } 260 | 261 | /// 262 | unittest 263 | { 264 | auto server = Socket(ProtocolFamily.IPv4, SocketType.stream); 265 | server.bind(IPv4Addr(127, 0, 0, 1)); 266 | server.listen(); 267 | } 268 | 269 | /** 270 | Accept an incoming client connection. 271 | 272 | Params: 273 | remoteAddr = client socket address 274 | */ 275 | Socket accept(ref SocketAddr remoteAddr) @trusted 276 | { 277 | socklen_t addrlen = remoteAddr.sizeof; 278 | return Socket(driver.accept(s, cast(sockaddr*)&remoteAddr, addrlen), true); 279 | } 280 | 281 | /// 282 | unittest 283 | { 284 | auto server = Socket(ProtocolFamily.IPv4, SocketType.stream); 285 | server.bind(IPv4Addr(127, 0, 0, 1)); 286 | server.listen(); 287 | auto client = Socket(ProtocolFamily.IPv4, SocketType.stream); 288 | client.connect(server.localAddr); 289 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 290 | client.send(ping[]); 291 | 292 | // accept client connection 293 | SocketAddr clientAddr; 294 | auto conn = server.accept(clientAddr); 295 | assert(clientAddr == client.localAddr); 296 | ubyte[4] buf; 297 | assert(conn.recv(buf[]) == 4); 298 | assert(buf[] == ping[]); 299 | } 300 | 301 | /// get local addr of socket 302 | SocketAddr localAddr() const @trusted 303 | { 304 | SocketAddr ret = void; 305 | socklen_t addrlen = ret.sizeof; 306 | driver.localAddr(s, cast(sockaddr*)&ret, addrlen); 307 | assert(addrlen <= ret.sizeof); 308 | return ret; 309 | } 310 | 311 | /** 312 | Set socket option. 313 | 314 | Params: 315 | option = option to set 316 | value = value for option 317 | */ 318 | void setOption(SocketOption option)(const scope SocketOptionType!option value) @trusted 319 | { 320 | driver.setSocketOption(s, option, &value, value.sizeof); 321 | } 322 | 323 | /// 324 | unittest 325 | { 326 | auto sock = Socket(ProtocolFamily.IPv4, SocketType.dgram); 327 | sock.setOption!(SocketOption.reuseAddr)(true); 328 | } 329 | 330 | /** 331 | Get socket option. 332 | 333 | Params: 334 | option = option to get 335 | */ 336 | SocketOptionType!option getOption(SocketOption option)() const @trusted 337 | { 338 | SocketOptionType!option ret = void; 339 | socklen_t optlen = ret.sizeof; 340 | driver.getSocketOption(s, option, &ret, optlen); 341 | assert(optlen == ret.sizeof); 342 | return ret; 343 | } 344 | 345 | /// 346 | unittest 347 | { 348 | auto sock = Socket(ProtocolFamily.IPv4, SocketType.dgram); 349 | sock.setOption!(SocketOption.reuseAddr)(true); 350 | assert(!!sock.getOption!(SocketOption.reuseAddr)); 351 | assert(sock.getOption!(SocketOption.type) == SocketType.dgram); 352 | } 353 | 354 | /** 355 | Receive data from any address and port into buffer. 356 | 357 | Params: 358 | buf = buffer to read into 359 | Returns: 360 | number of bytes read and source address 361 | */ 362 | Tuple!(size_t, "size", SocketAddr, "remoteAddr") recvFrom(scope ubyte[] buf) @trusted 363 | { 364 | typeof(return) ret = void; 365 | socklen_t addrlen = ret[1].sizeof; 366 | ret[0] = driver.recvFrom(s, buf, cast(sockaddr*)&ret[1], addrlen); 367 | assert(addrlen <= ret[1].sizeof); 368 | return ret; 369 | } 370 | 371 | /// 372 | unittest 373 | { 374 | auto server = Socket(ProtocolFamily.IPv4, SocketType.dgram); 375 | server.bind(IPv4Addr(127, 0, 0, 1)); 376 | 377 | auto client = Socket(ProtocolFamily.IPv4, SocketType.dgram); 378 | client.bind(IPv4Addr(127, 0, 0, 1)); 379 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 380 | client.sendTo(server.localAddr, ping[]); 381 | 382 | ubyte[4] buf; 383 | auto res = server.recvFrom(buf[]); 384 | assert(res.size == 4); 385 | assert(res.remoteAddr == client.localAddr); 386 | assert(buf[] == ping[]); 387 | } 388 | 389 | /// 390 | unittest 391 | { 392 | auto server = Socket(ProtocolFamily.IPv4, SocketType.dgram); 393 | server.bind(IPv4Addr(127, 0, 0, 1)); 394 | 395 | auto client = Socket(ProtocolFamily.IPv4, SocketType.dgram); 396 | client.bind(IPv4Addr(127, 0, 0, 1)); 397 | ubyte[4] buf1 = [0, 1, 2, 3]; 398 | client.sendTo(server.localAddr, buf1[]); 399 | 400 | ubyte[4] buf2; 401 | auto res = server.recvFrom(buf2[]); 402 | assert(res.size == 4); 403 | assert(res.remoteAddr == client.localAddr); 404 | } 405 | 406 | /** 407 | Receive data from any address and port into multiple buffers. 408 | The read will be atomic on Posix and Windows platforms. 409 | 410 | Params: 411 | bufs = buffers to read into 412 | Returns: 413 | number of bytes read and source address 414 | */ 415 | Tuple!(size_t, "size", SocketAddr, "remoteAddr") recvFrom(scope ubyte[][] bufs...) @trusted 416 | { 417 | typeof(return) ret = void; 418 | socklen_t addrlen = ret[1].sizeof; 419 | ret[0] = driver.recvFrom(s, bufs, cast(sockaddr*)&ret[1], addrlen); 420 | assert(addrlen <= ret[1].sizeof); 421 | return ret; 422 | } 423 | 424 | /// 425 | unittest 426 | { 427 | auto server = Socket(ProtocolFamily.IPv4, SocketType.dgram); 428 | server.bind(IPv4Addr(127, 0, 0, 1)); 429 | 430 | auto client = Socket(ProtocolFamily.IPv4, SocketType.dgram); 431 | client.bind(IPv4Addr(127, 0, 0, 1)); 432 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 433 | client.sendTo(server.localAddr, ping[]); 434 | 435 | ubyte[2] a, b; 436 | auto ret = server.recvFrom(a[], b[]); 437 | assert(ret.size == 4); 438 | assert(ret.remoteAddr == client.localAddr); 439 | assert(a[] == ping[0 .. 2] && b[] == ping[2 .. 4]); 440 | } 441 | 442 | /** 443 | Send buffer content to the specified `addr`. 444 | 445 | Params: 446 | dest = destination address 447 | buf = buffer to write 448 | Returns: 449 | number of bytes written 450 | */ 451 | size_t sendTo(SocketAddr)(SocketAddr dest, const scope ubyte[] buf) @trusted 452 | if (isSocketAddr!SocketAddr) 453 | { 454 | return driver.sendTo(s, buf, dest.cargs[]); 455 | } 456 | 457 | /** 458 | Send multiple buffer contents to the specified `addr` and `port`. 459 | The write will be atomic on Posix and Windows platforms. 460 | 461 | Params: 462 | dest = destination address 463 | bufs = buffers to write 464 | Returns: 465 | total number of bytes written 466 | */ 467 | size_t sendTo(SocketAddr)(SocketAddr dest, const scope ubyte[][] bufs...) @trusted 468 | if (isSocketAddr!SocketAddr) 469 | { 470 | return driver.sendTo(s, bufs, dest.cargs[]); 471 | } 472 | 473 | /// 474 | unittest 475 | { 476 | auto server = Socket(ProtocolFamily.IPv4, SocketType.dgram); 477 | server.bind(IPv4Addr(127, 0, 0, 1)); 478 | 479 | auto client = Socket(ProtocolFamily.IPv4, SocketType.dgram); 480 | client.bind(IPv4Addr(127, 0, 0, 1)); 481 | ubyte[2] pi = ['p', 'i'], ng = ['n', 'g']; 482 | client.sendTo(server.localAddr, pi[], ng[]); 483 | 484 | ubyte[4] buf; 485 | assert(server.recv(buf[]) == 4); 486 | assert(buf[0 .. 2] == pi[] && buf[2 .. 4] == ng[]); 487 | } 488 | 489 | /** 490 | Receive from socket into buffer. 491 | 492 | Params: 493 | buf = buffer to read into 494 | Returns: 495 | number of bytes read 496 | */ 497 | size_t recv(scope ubyte[] buf) @trusted 498 | { 499 | return driver.recv(s, buf); 500 | } 501 | 502 | /** 503 | Receive from socket into multiple buffers. 504 | The read will be atomic on Posix and Windows platforms. 505 | 506 | Params: 507 | bufs = buffers to read into 508 | Returns: 509 | total number of bytes read 510 | */ 511 | size_t recv(scope ubyte[][] bufs...) @trusted 512 | { 513 | return driver.recv(s, bufs); 514 | } 515 | 516 | /** 517 | Alias to comply with input `IO` API. 518 | 519 | See_also: $(REF std,io,isInput) 520 | */ 521 | alias read = recv; 522 | 523 | /** 524 | Send buffer to connected host. 525 | 526 | Params: 527 | buf = buffer to write 528 | Returns: 529 | number of bytes written 530 | */ 531 | size_t send(const scope ubyte[] buf) @trusted 532 | { 533 | return driver.send(s, buf); 534 | } 535 | 536 | /** 537 | Send multiple buffers to connected host. 538 | The writes will be atomic on Posix platforms. 539 | 540 | Params: 541 | bufs = buffers to write 542 | Returns: 543 | total number of bytes written 544 | */ 545 | size_t send(const scope ubyte[][] bufs...) @trusted 546 | { 547 | return driver.send(s, bufs); 548 | } 549 | 550 | /** 551 | Alias to comply with output `IO` API. 552 | 553 | See_also: $(REF std,io,isOutput) 554 | */ 555 | alias write = send; 556 | 557 | /// move operator for socket 558 | Socket move() return scope nothrow /*pure Issue 18590*/ 559 | { 560 | auto s = this.s; 561 | auto cod = this.closeOnDestroy; 562 | this.s = Driver.SOCKET.INVALID; 563 | return Socket(s, cod); 564 | } 565 | 566 | /// not copyable 567 | @disable this(this); 568 | 569 | package(std.io.net): 570 | 571 | /// get socket bound to resolved `hostname` and `service` 572 | static Socket resolveBind( /*in*/ const scope char[] hostname, /*in*/ const scope char[] service, 573 | SocketType socketType) @trusted 574 | { 575 | Socket sock; 576 | immutable res = driver.resolve(hostname, service, 577 | AddrFamily.unspecified, socketType, Protocol.default_, (ref scope ai) { 578 | try 579 | { 580 | sock = Socket(ai.family, ai.socketType, ai.protocol); 581 | sock.setOption!(SocketOption.reuseAddr)(true); 582 | sock.bind(ai.addr); 583 | } 584 | catch (IOException) 585 | return 0; 586 | return 1; 587 | }); 588 | enforce(res == 1, "bind failed".String); 589 | return sock.move; 590 | } 591 | 592 | /// get socket connected to resolved `hostname` and `service` 593 | static Socket resolveConnect( /*in*/ const scope char[] hostname, /*in*/ const scope char[] service, 594 | SocketType socketType) @trusted 595 | { 596 | Socket sock; 597 | immutable res = driver.resolve(hostname, service, 598 | AddrFamily.unspecified, socketType, Protocol.default_, (ref scope ai) { 599 | try 600 | { 601 | sock = Socket(ai.family, ai.socketType, ai.protocol); 602 | sock.setOption!(SocketOption.reuseAddr)(true); 603 | sock.connect(ai.addr); 604 | } 605 | catch (IOException) 606 | return 0; 607 | return 1; 608 | }); 609 | enforce(res == 1, "connect failed".String); 610 | return sock.move; 611 | } 612 | 613 | private: 614 | import std.typecons : Tuple; 615 | 616 | this(return scope Driver.SOCKET s, bool cod) @trusted pure nothrow 617 | { 618 | this.s = s; 619 | this.closeOnDestroy = cod; 620 | } 621 | 622 | Driver.SOCKET s = Driver.SOCKET.INVALID; 623 | // close when the destructor is run. True normally unless one wraps an 624 | // existing handle (e.g. stdout). 625 | bool closeOnDestroy = false; 626 | } 627 | -------------------------------------------------------------------------------- /src/std/io/net/tcp.d: -------------------------------------------------------------------------------- 1 | /** 2 | TCP stream sockets 3 | 4 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 5 | Authors: Martin Nowak 6 | Source: $(PHOBOSSRC std/io/net/_tcp.d) 7 | */ 8 | module std.io.net.tcp; 9 | 10 | import std.io.exception : enforce; 11 | import std.io.internal.string; 12 | import std.io.net.addr; 13 | import std.io.net.socket; 14 | import std.io.driver; 15 | 16 | version (Posix) 17 | { 18 | import core.sys.posix.sys.socket; 19 | } 20 | else version (Windows) 21 | { 22 | import core.sys.windows.winsock2; 23 | } 24 | else 25 | static assert(0, "unimplemented"); 26 | 27 | /** 28 | A TCP stream socket 29 | */ 30 | struct TCP 31 | { 32 | @safe @nogc: 33 | /** 34 | Create a TCP socket, bind it to the resolved `hostname` and 35 | `port`, and listen for incoming connections. 36 | Uses the first resolved address the socket can successfully bind. 37 | A port number of zero will be bound to an ephemeral port. 38 | 39 | Params: 40 | hostname = hostname to resolve and bind 41 | port = port number to bind 42 | backlog = maximum number of pending connections 43 | Returns: 44 | TCPServer to accept incoming connections 45 | */ 46 | static TCPServer server(S)(S hostname, ushort port = 0, uint backlog = 128) 47 | if (isStringLike!S) 48 | { 49 | import std.internal.cstring : tempCString; 50 | import core.internal.string : unsignedToTempString; 51 | 52 | auto tp = () @trusted{ 53 | return tempCString(unsignedToTempString(port)[]); 54 | }(); 55 | auto sock = Socket.resolveBind(tempCString(hostname)[], tp[], SocketType.stream); 56 | sock.listen(backlog); 57 | return TCPServer(sock.move); 58 | } 59 | 60 | /** 61 | Create a TCP socket, bind it to the resolved `hostname` and 62 | `service`, and listen for incoming connections. 63 | Uses the first resolved address the socket can successfully bind. 64 | 65 | Params: 66 | hostname = hostname to resolve and bind 67 | service = service bind 68 | backlog = maximum number of pending connections 69 | Returns: 70 | TCPServer to accept incoming connections 71 | */ 72 | static TCPServer server(S1, S2)(S1 hostname, S2 service, uint backlog = 128) @trusted 73 | if (isStringLike!S1 && isStringLike!S2) 74 | { 75 | import std.internal.cstring : tempCString; 76 | 77 | auto sock = Socket.resolveBind(tempCString(hostname)[], 78 | tempCString(service)[], SocketType.stream); 79 | sock.listen(backlog); 80 | return TCPServer(sock.move); 81 | } 82 | 83 | /** 84 | Create a TCP socket, bind it to socket `addr`, and listen for 85 | incoming connections. 86 | 87 | Params: 88 | addr = socket address to bind 89 | backlog = maximum number of pending connections 90 | */ 91 | static TCPServer server(SocketAddr)(in SocketAddr addr, uint backlog = 128) 92 | if (isSocketAddr!SocketAddr) 93 | { 94 | auto sock = Socket(addr.family, SocketType.stream, Protocol.default_); 95 | sock.setOption!(SocketOption.reuseAddr)(true); 96 | sock.bind(addr); 97 | sock.listen(backlog); 98 | return TCPServer(sock.move); 99 | } 100 | 101 | /** 102 | Create a TCP socket, bind it to IP `addr` and `port`, and 103 | listen for incoming connections. A port number of zero will be 104 | bound to an ephemeral port. 105 | 106 | Params: 107 | addr = IP address to bind 108 | port = port to bind 109 | backlog = maximum number of pending connections 110 | */ 111 | static TCPServer server(IPAddr)(IPAddr addr, ushort port = 0, uint backlog = 128) 112 | if (isIPAddr!IPAddr) 113 | { 114 | return server(addr.socketAddr(port), backlog); 115 | } 116 | 117 | /** 118 | Connect to resolved `hostname` and `port`. 119 | Uses the first resolved address the socket can successfully connect to. 120 | 121 | Params: 122 | hostname = remote hostname to connect to 123 | port = remote port to connect to 124 | */ 125 | static TCP client(S)(S hostname, ushort port) if (isStringLike!S) 126 | { 127 | import std.internal.cstring : tempCString; 128 | import core.internal.string : unsignedToTempString; 129 | 130 | auto tp = () @trusted{ 131 | return tempCString(unsignedToTempString(port)[]); 132 | }(); 133 | auto sock = Socket.resolveConnect(tempCString(hostname)[], tp[], SocketType.stream); 134 | return TCP(sock.move); 135 | } 136 | 137 | /// 138 | unittest 139 | { 140 | auto server = TCP.server("localhost", 1234); 141 | // connect to remote hostname and port 142 | auto client = TCP.client("localhost", 1234); 143 | 144 | // send to connected address 145 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 146 | client.send(ping[]); 147 | 148 | auto conn = server.accept; 149 | assert(conn.remoteAddr == client.localAddr); 150 | ubyte[4] buf; 151 | assert(conn.tcp.recv(buf[]) == 4); 152 | assert(buf[] == ping[]); 153 | } 154 | 155 | /** 156 | Connect to resolved `hostname` and `service`. 157 | Uses the first resolved address the socket can successfully connect to. 158 | 159 | Params: 160 | hostname = remote hostname to connect to 161 | service = service to connect to 162 | 163 | See_also: https://www.iana.org/assignments/service-names-port-numbers 164 | */ 165 | static TCP client(S1, S2)(S1 hostname, S2 service) 166 | if (isStringLike!S1 && isStringLike!S2) 167 | { 168 | import std.internal.cstring : tempCString; 169 | 170 | auto sock = Socket.resolveConnect(tempCString(hostname)[], 171 | tempCString(service)[], SocketType.stream); 172 | return TCP(sock.move); 173 | } 174 | 175 | /// 176 | unittest 177 | { 178 | auto server = TCP.server("localhost", "msnp"); 179 | // "connect" to remote hostname and service 180 | auto client = TCP.client("localhost", "msnp"); 181 | 182 | // send to connected address 183 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 184 | client.send(ping[]); 185 | 186 | auto conn = server.accept; 187 | assert(conn.remoteAddr == client.localAddr); 188 | ubyte[4] buf; 189 | assert(conn.tcp.recv(buf[]) == 4); 190 | assert(buf[] == ping[]); 191 | } 192 | 193 | /** 194 | Connect to remote socket address. 195 | 196 | Params: 197 | addr = remote socket address to connect to 198 | */ 199 | static TCP client(SocketAddr)(in SocketAddr addr) @trusted 200 | if (isSocketAddr!SocketAddr) 201 | { 202 | auto sock = Socket(addr.family, SocketType.stream, Protocol.default_); 203 | sock.connect(addr); 204 | return TCP(sock.move); 205 | } 206 | 207 | /// 208 | unittest 209 | { 210 | auto server = TCP.server("localhost"); 211 | // "connect" to remote address 212 | auto client = TCP.client(server.localAddr); 213 | 214 | // send to connected address 215 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 216 | client.send(ping[]); 217 | 218 | auto conn = server.accept; 219 | ubyte[4] buf; 220 | assert(conn.recv(buf[]) == 4); 221 | assert(buf[] == ping[]); 222 | 223 | ubyte[4] pong = ['p', 'o', 'n', 'g']; 224 | conn.send(pong[]); 225 | 226 | assert(client.recv(buf[]) == 4); 227 | assert(buf[] == pong[]); 228 | } 229 | 230 | /** 231 | Connect to remote IP `addr` and `port`. 232 | 233 | Params: 234 | addr = remote IP address to connect to 235 | port = remote port to connect to 236 | */ 237 | static TCP client(IPAddr)(IPAddr addr, ushort port = 0) if (isIPAddr!IPAddr) 238 | { 239 | return client(addr.socketAddr(port)); 240 | } 241 | 242 | /// 243 | unittest 244 | { 245 | auto server = TCP.server(IPv4Addr(127, 0, 0, 1), 1234); 246 | // "connect" to remote IP address and port 247 | auto client = TCP.client(IPv4Addr(127, 0, 0, 1), 1234); 248 | 249 | // send to connected address 250 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 251 | client.send(ping[]); 252 | 253 | auto conn = server.accept; 254 | ubyte[4] buf; 255 | assert(conn.recv(buf[]) == 4); 256 | assert(buf[] == ping[]); 257 | } 258 | 259 | /// underlying `Socket` 260 | Socket socket; 261 | 262 | /// forward to `socket` 263 | alias socket this; 264 | } 265 | 266 | /// 267 | unittest 268 | { 269 | auto server = TCP.server(IPv4Addr(127, 0, 0, 1)); 270 | auto client = TCP.client(server.localAddr); 271 | 272 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 273 | client.write(ping[]); 274 | 275 | auto conn = server.accept; 276 | assert(conn.remoteAddr == client.localAddr); 277 | ubyte[4] buf; 278 | assert(conn.read(buf[]) == 4); 279 | assert(buf[] == ping[]); 280 | 281 | ubyte[4] pong = ['p', 'o', 'n', 'g']; 282 | conn.write(pong[]); 283 | 284 | assert(client.read(buf[]) == 4); 285 | assert(buf[] == pong[]); 286 | } 287 | 288 | /** 289 | */ 290 | struct TCPServer 291 | { 292 | @safe @nogc: 293 | /** 294 | Accepted client connection. 295 | */ 296 | static struct Client 297 | { 298 | TCP tcp; /// socket connect to client 299 | SocketAddr remoteAddr; /// remote address of client 300 | alias tcp this; /// forward to socket 301 | } 302 | 303 | /** 304 | Accept a TCP socket from a connected client. 305 | */ 306 | Client accept() @trusted 307 | { 308 | Client res; 309 | res.tcp = TCP(socket.accept(res.remoteAddr)); 310 | return res; 311 | } 312 | 313 | /// 314 | unittest 315 | { 316 | auto server = TCP.server("localhost"); 317 | auto client = TCP.client(server.localAddr); 318 | 319 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 320 | assert(client.write(ping[]) == 4); 321 | 322 | auto conn = server.accept; 323 | assert(conn.localAddr == server.localAddr); 324 | assert(conn.remoteAddr == client.localAddr); 325 | ubyte[4] buf; 326 | assert(conn.read(buf[]) == 4); 327 | assert(buf[] == ping[]); 328 | 329 | ubyte[4] pong = ['p', 'o', 'n', 'g']; 330 | conn.write(pong[]); 331 | 332 | assert(client.read(buf[]) == 4); 333 | assert(buf[] == pong[]); 334 | } 335 | 336 | /// underlying `Socket` 337 | Socket socket; 338 | 339 | /// forward to `socket` 340 | alias socket this; 341 | } 342 | -------------------------------------------------------------------------------- /src/std/io/net/udp.d: -------------------------------------------------------------------------------- 1 | /** 2 | UDP datagram sockets 3 | 4 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 5 | Authors: Martin Nowak 6 | Source: $(PHOBOSSRC std/io/net/_udp.d) 7 | */ 8 | module std.io.net.udp; 9 | 10 | import std.io.exception : enforce; 11 | import std.io.internal.string; 12 | import std.io.net.addr; 13 | import std.io.net.socket; 14 | 15 | version (Posix) 16 | { 17 | import core.sys.posix.sys.socket; 18 | } 19 | else version (Windows) 20 | { 21 | import core.sys.windows.winsock2; 22 | } 23 | else 24 | static assert(0, "unimplemented"); 25 | 26 | /** 27 | A UDP/Datagram socket 28 | */ 29 | struct UDP 30 | { 31 | @safe @nogc: 32 | /** 33 | Create a UDP socket and bind it to the resolved `hostname` and `port`. 34 | Uses the first resolved address the socket can successfully bind. 35 | A port number of zero will be bound to an ephemeral port. 36 | 37 | Params: 38 | hostname = hostname to resolve and bind 39 | port = port number to bind 40 | */ 41 | static UDP server(S)(S hostname, ushort port = 0) if (isStringLike!S) 42 | { 43 | import std.internal.cstring : tempCString; 44 | import core.internal.string : unsignedToTempString; 45 | 46 | auto tp = () @trusted{ 47 | return tempCString(unsignedToTempString(port)[]); 48 | }(); 49 | auto sock = Socket.resolveBind(tempCString(hostname)[], tp[], SocketType.dgram); 50 | return UDP(sock.move); 51 | } 52 | 53 | /// 54 | unittest 55 | { 56 | auto server = UDP.server("localhost", 1234); 57 | auto client = UDP(server.localAddr.family); 58 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 59 | client.sendTo(server.localAddr, ping[]); 60 | ubyte[4] buf; 61 | assert(server.recv(buf[]) == 4); 62 | assert(buf[] == ping[]); 63 | } 64 | 65 | /** 66 | Create a UDP socket and bind it to the resolved `hostname` and `service`. 67 | Uses the first resolved address the socket can successfully bind. 68 | 69 | Params: 70 | hostname = hostname to resolve and bind 71 | service = service name to resolve and bind 72 | */ 73 | static UDP server(S1, S2)(S1 hostname, S2 service) 74 | if (isStringLike!S1 && isStringLike!S2) 75 | { 76 | import std.internal.cstring : tempCString; 77 | 78 | auto sock = Socket.resolveBind(tempCString(hostname)[], 79 | tempCString(service)[], SocketType.dgram); 80 | return UDP(sock.move); 81 | } 82 | 83 | /// 84 | unittest 85 | { 86 | auto server = UDP.server("localhost", 1234); 87 | auto client = UDP(server.localAddr.family); 88 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 89 | client.sendTo(server.localAddr, ping[]); 90 | ubyte[4] buf; 91 | assert(server.recv(buf[]) == 4); 92 | assert(buf[] == ping[]); 93 | } 94 | 95 | /** 96 | Create a UDP socket and bind it to socket `addr`. 97 | 98 | Params: 99 | addr = socket address to bind 100 | */ 101 | static UDP server(SocketAddr)(in SocketAddr addr) 102 | if (isSocketAddr!SocketAddr) 103 | { 104 | auto res = UDP(addr.family); 105 | res.bind(addr); 106 | return res; 107 | } 108 | 109 | /// 110 | unittest 111 | { 112 | auto addr4 = SocketAddrIPv4(IPv4Addr(127, 0, 0, 1), 1234); 113 | auto server = UDP.server(addr4); 114 | assert(server.localAddr == addr4); 115 | } 116 | 117 | /** 118 | Create a UDP socket and bind it to IP `addr` and `port`. A port 119 | number of zero will be bound to an ephemeral port. 120 | 121 | Params: 122 | addr = IP address to bind 123 | port = port number to bind 124 | */ 125 | static UDP server(IPAddr)(IPAddr addr, ushort port = 0) if (isIPAddr!IPAddr) 126 | { 127 | return server(addr.socketAddr(port)); 128 | } 129 | 130 | /// 131 | unittest 132 | { 133 | auto server = UDP.server(IPv4Addr(127, 0, 0, 1)); 134 | assert(server.localAddr.get!SocketAddrIPv4.ip == IPv4Addr(127, 0, 0, 1)); 135 | assert(server.localAddr.get!SocketAddrIPv4.port != 0); 136 | } 137 | 138 | /** 139 | Connect to resolved `hostname` and `port`. 140 | Uses the first resolved address the socket can successfully connect to. 141 | 142 | UDP is a connection-less protocol. Connecting to a client to a remote 143 | address allows to use `send` without specifying a destination address. It 144 | will also apply a filter to only receive traffic from the connected 145 | address. 146 | 147 | Params: 148 | hostname = remote hostname to connect to 149 | port = remote port to connect to 150 | */ 151 | static UDP client(S)(S hostname, ushort port) if (isStringLike!S) 152 | { 153 | import std.internal.cstring : tempCString; 154 | import core.internal.string : unsignedToTempString; 155 | 156 | auto tp = () @trusted{ 157 | return tempCString(unsignedToTempString(port)[]); 158 | }(); 159 | auto sock = Socket.resolveConnect(tempCString(hostname)[], tp[], SocketType.dgram); 160 | return UDP(sock.move); 161 | } 162 | 163 | /// 164 | unittest 165 | { 166 | auto server = UDP.server("localhost", 1234); 167 | // connect to remote hostname and port 168 | auto client = UDP.client("localhost", 1234); 169 | 170 | // send to connected address 171 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 172 | client.send(ping[]); 173 | ubyte[4] buf; 174 | assert(server.recv(buf[]) == 4); 175 | assert(buf[] == ping[]); 176 | } 177 | 178 | /** 179 | Connect to resolved `hostname` and `service`. 180 | Uses the first resolved address the socket can successfully connected to. 181 | 182 | Params: 183 | hostname = remote hostname to connect to 184 | service = service to connect to 185 | 186 | See_also: https://www.iana.org/assignments/service-names-port-numbers 187 | */ 188 | static UDP client(S1, S2)(S1 hostname, S2 service) 189 | if (isStringLike!S1 && isStringLike!S2) 190 | { 191 | import std.internal.cstring : tempCString; 192 | 193 | auto sock = Socket.resolveConnect(tempCString(hostname)[], 194 | tempCString(service)[], SocketType.dgram); 195 | return UDP(sock.move); 196 | } 197 | 198 | /// 199 | unittest 200 | { 201 | auto server = UDP.server("localhost", "msnp"); 202 | // "connect" to remote hostname and service 203 | auto client = UDP.client("localhost", "msnp"); 204 | 205 | // send to connected address 206 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 207 | client.send(ping[]); 208 | ubyte[4] buf; 209 | assert(server.recv(buf[]) == 4); 210 | assert(buf[] == ping[]); 211 | } 212 | 213 | /** 214 | Connect to remote socket address. 215 | 216 | Params: 217 | addr = remote socket address to connect to 218 | */ 219 | static UDP client(SocketAddr)(in SocketAddr addr) @trusted 220 | if (isSocketAddr!SocketAddr) 221 | { 222 | auto res = UDP(addr.family); 223 | res.connect(addr); 224 | return res; 225 | } 226 | 227 | /// 228 | unittest 229 | { 230 | auto server = UDP.server("localhost"); 231 | // "connect" to remote address 232 | auto client = UDP.client(server.localAddr); 233 | 234 | // send to connected address 235 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 236 | client.send(ping[]); 237 | ubyte[4] buf; 238 | assert(server.recv(buf[]) == 4); 239 | assert(buf[] == ping[]); 240 | 241 | // can still use sendTo with other addr (unless you're on BSD/OSX) 242 | version (OSX) {} 243 | else 244 | { 245 | auto server2 = UDP.server("localhost"); 246 | client.sendTo(server2.localAddr, ping[]); 247 | assert(server2.recv(buf[]) == 4); 248 | assert(buf[] == ping[]); 249 | } 250 | 251 | // while keep sending to connected address 252 | client.send(ping[]); 253 | assert(server.recv(buf[]) == 4); 254 | assert(buf[] == ping[]); 255 | 256 | // client can only receive data from connected server 257 | server.sendTo(client.localAddr, ping[]); 258 | assert(client.recv(buf[]) == 4); 259 | assert(buf[] == ping[]); 260 | } 261 | 262 | /** 263 | Connect to remote IP `addr` and `port`. 264 | 265 | Params: 266 | addr = remote IP address to connect to 267 | port = remote port to connect to 268 | */ 269 | static UDP client(IPAddr)(IPAddr addr, ushort port = 0) if (isIPAddr!IPAddr) 270 | { 271 | return client(addr.socketAddr(port)); 272 | } 273 | 274 | /// 275 | unittest 276 | { 277 | auto server = UDP.server(IPv4Addr(127, 0, 0, 1), 1234); 278 | // "connect" to remote IP address and port 279 | auto client = UDP.client(IPv4Addr(127, 0, 0, 1), 1234); 280 | 281 | // send to connected address 282 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 283 | client.send(ping[]); 284 | ubyte[4] buf; 285 | assert(server.recv(buf[]) == 4); 286 | assert(buf[] == ping[]); 287 | } 288 | 289 | /// 290 | unittest 291 | { 292 | auto server = UDP.server(IPv4Addr(127, 0, 0, 1), 1234); 293 | // "connect" to remote IP address and port 294 | auto client = UDP.client(IPv4Addr(127, 0, 0, 1), 1234); 295 | 296 | // send to connected address 297 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 298 | client.send(ping[]); 299 | ubyte[4] buf; 300 | assert(server.recv(buf[]) == 4); 301 | assert(buf[] == ping[]); 302 | } 303 | 304 | /** 305 | Construct an unbound UDP socket for the given protocol `family`. It will 306 | be automatically bound to an address on the first call to `sendto`. 307 | 308 | Params: 309 | family = protocol family for socket 310 | */ 311 | this(ProtocolFamily family) 312 | { 313 | socket = Socket(family, SocketType.dgram); 314 | } 315 | 316 | /// 317 | unittest 318 | { 319 | auto server = UDP.server(IPv4Addr(127, 0, 0, 1)); 320 | auto client = UDP(ProtocolFamily.IPv4); 321 | // unbound 322 | SocketAddrIPv4 addr; 323 | // cannot get unbound localAddr on Windows 324 | version (Posix) 325 | { 326 | addr = client.localAddr.get!SocketAddrIPv4; 327 | assert(addr.ip == IPv4Addr.any); // 0.0.0.0 328 | assert(addr.port == 0); 329 | } 330 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 331 | client.sendTo(server.localAddr, ping[]); 332 | // now bound 333 | addr = client.localAddr.get!SocketAddrIPv4; 334 | assert(addr.ip == IPv4Addr.any); 335 | assert(addr.port != 0); 336 | 337 | // client can receive data from any peer 338 | auto peer = UDP(ProtocolFamily.IPv4); 339 | // cannot sendTo 0.0.0.0 on Windows 340 | version (Windows) 341 | addr.ip = IPv4Addr(127, 0, 0, 1); 342 | peer.sendTo(addr, ping[]); 343 | ubyte[4] buf; 344 | assert(client.recv(buf[]) == 4); 345 | assert(buf[] == ping[]); 346 | } 347 | 348 | /// move operator for socket 349 | UDP move() 350 | { 351 | return UDP(socket.move); 352 | } 353 | 354 | /// underlying `Socket` 355 | Socket socket; 356 | 357 | /// forward to `socket` 358 | alias socket this; 359 | 360 | private: 361 | // take ownership of socket 362 | this(Socket socket) 363 | { 364 | this.socket = socket.move; 365 | } 366 | } 367 | 368 | /// 369 | @safe @nogc unittest 370 | { 371 | auto server = UDP.server(IPv4Addr(127, 0, 0, 1)); 372 | auto client = UDP(ProtocolFamily.IPv4); 373 | 374 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 375 | client.sendTo(server.localAddr, ping[]); 376 | 377 | ubyte[4] buf; 378 | auto res = server.recvFrom(buf[]); 379 | assert(res.size == 4); 380 | assert(buf[] == ping[]); 381 | 382 | ubyte[4] pong = ['p', 'o', 'n', 'g']; 383 | server.sendTo(res.remoteAddr, pong[]); 384 | 385 | assert(client.recv(buf[]) == 4); 386 | assert(buf[] == pong[]); 387 | } 388 | 389 | @safe unittest 390 | { 391 | import std.process : environment; 392 | 393 | if (environment.get("SKIP_IPv6_LOOPBACK_TESTS") !is null) 394 | return; 395 | auto addr6 = SocketAddrIPv6(IPv6Addr(0, 0, 0, 0, 0, 0, 0, 1), 1234); 396 | auto server = UDP.server(addr6); 397 | assert(server.localAddr == addr6); 398 | } 399 | -------------------------------------------------------------------------------- /src/std/io/package.d: -------------------------------------------------------------------------------- 1 | /** 2 | IOs 3 | 4 | This module provides IO traits and interfaces. 5 | It also imports std.io.file and std.io.net. 6 | 7 | License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 8 | Authors: Martin Nowak 9 | Source: $(PHOBOSSRC std/io/_package.d) 10 | */ 11 | module std.io; 12 | 13 | /// 14 | public import std.io.exception : IOException; 15 | 16 | /// 17 | public import std.io.file; 18 | 19 | /// 20 | public import std.io.net; 21 | 22 | import std.traits : ReturnType; 23 | 24 | // dfmt off 25 | /** 26 | Returns `true` if `IO` is an input. An input/output device must define the 27 | primitive `read` supporting reading into a single and multiple buffers. 28 | 29 | Params: 30 | IO = type to be tested 31 | 32 | Returns: 33 | true if `IO` is an input device 34 | */ 35 | enum isInput(IO) = is(typeof(IO.init) == IO) 36 | && is(ReturnType!((IO io) @safe{ ubyte[1] buf; return io.read(buf[]); }) == size_t) 37 | && is(ReturnType!((IO io) @safe{ ubyte[1] buf1, buf2; return io.read(buf1[], buf2[]); }) == size_t) 38 | && is(ReturnType!((IO io) @safe{ ubyte[][2] bufs; return io.read(bufs); }) == size_t); 39 | 40 | /** 41 | Returns `true` if `IO` is an input. An input/output device must define the 42 | primitive `write` supporting writing a single and multiple buffers. 43 | 44 | Params: 45 | IO = type to be tested 46 | 47 | Returns: 48 | true if `IO` is an output device 49 | */ 50 | enum isOutput(IO) = is(typeof(IO.init) == IO) 51 | && is(ReturnType!((IO io) @safe{ ubyte[1] buf; return io.write(buf[]); }) == size_t) 52 | && is(ReturnType!((IO io) @safe{ ubyte[1] buf1, buf2; return io.write(buf1[], buf2[]); }) == size_t) 53 | && is(ReturnType!((IO io) @safe{ ubyte[][2] bufs; return io.write(bufs); }) == size_t); 54 | // dfmt on 55 | 56 | /** 57 | Returns `true` if `IO` is an input/output device. 58 | 59 | Params: 60 | IO = type to be tested 61 | 62 | Returns: 63 | true if `IO` is an input/output device 64 | 65 | See_also: 66 | isInput 67 | isOutput 68 | */ 69 | enum isIO(IO) = isInput!IO && isOutput!IO; 70 | 71 | /** 72 | Input interface for code requiring a polymorphic API. 73 | */ 74 | interface Input 75 | { 76 | @safe @nogc: 77 | /// read from device into buffer 78 | size_t read(scope ubyte[] buf) scope; 79 | /// read from device into multiple buffers 80 | size_t read(scope ubyte[][] bufs...) scope; 81 | } 82 | 83 | /** 84 | Output interface for code requiring a polymorphic API. 85 | */ 86 | interface Output 87 | { 88 | @safe @nogc: 89 | /// write buffer content to device 90 | size_t write(const scope ubyte[] buf) scope; 91 | /// write multiple buffer contents to device 92 | size_t write(const scope ubyte[][] bufs...) scope; 93 | } 94 | 95 | /** 96 | Returns an alias sequence `Input` and `Output`, depending on what `IO` 97 | implements. 98 | */ 99 | template IOInterfaces(IO) if (isInput!IO || isOutput!IO) 100 | { 101 | import std.meta : AliasSeq; 102 | 103 | static if (isInput!IO && isOutput!IO) 104 | alias IOInterfaces = AliasSeq!(Input, Output); 105 | else static if (isInput!IO) 106 | alias IOInterfaces = AliasSeq!(Input); 107 | else static if (isOutput!IO) 108 | alias IOInterfaces = AliasSeq!(Output); 109 | } 110 | 111 | /// 112 | @safe unittest 113 | { 114 | import std.meta : AliasSeq; 115 | 116 | static assert(is(IOInterfaces!File == AliasSeq!(Input, Output))); 117 | 118 | static struct In 119 | { 120 | @safe: 121 | size_t read(scope ubyte[]); 122 | size_t read(scope ubyte[][]...); 123 | } 124 | 125 | static assert(isInput!In); 126 | static assert(is(IOInterfaces!In == AliasSeq!(Input))); 127 | 128 | static struct Out 129 | { 130 | @safe: 131 | size_t write(const scope ubyte[]); 132 | size_t write(const scope ubyte[][]...); 133 | } 134 | 135 | static assert(isOutput!Out); 136 | static assert(is(IOInterfaces!Out == AliasSeq!(Output))); 137 | 138 | static struct S 139 | { 140 | } 141 | 142 | static assert(!is(IOInterfaces!S)); 143 | } 144 | 145 | /** 146 | A template class implementing the supported `IOInterfaces`. 147 | */ 148 | class IOObject(IO) : IOInterfaces!IO 149 | { 150 | @safe @nogc: 151 | /// construct class from `io` 152 | this(IO io) 153 | { 154 | this.io = io.move; 155 | } 156 | 157 | static if (isInput!IO) 158 | { 159 | /// read from `io` 160 | size_t read(scope ubyte[] buf) scope 161 | { 162 | return io.read(buf); 163 | } 164 | 165 | /// ditto 166 | size_t read(scope ubyte[][] bufs...) scope 167 | { 168 | return io.read(bufs); 169 | } 170 | } 171 | 172 | static if (isOutput!IO) 173 | { 174 | /// write to `io` 175 | size_t write(const scope ubyte[] buf) scope 176 | { 177 | return io.write(buf); 178 | } 179 | 180 | /// ditto 181 | size_t write(const scope ubyte[][] bufs...) scope 182 | { 183 | return io.write(bufs); 184 | } 185 | } 186 | 187 | /// forward to `io` 188 | alias io this; 189 | 190 | /// the contained `IO` 191 | IO io; 192 | } 193 | 194 | /// IFTI construction helper for `IOObject` 195 | IOObject!IO ioObject(IO)(IO io) 196 | { 197 | return new IOObject!IO(io.move); 198 | } 199 | 200 | /// 201 | @safe unittest 202 | { 203 | import std.file : remove; 204 | 205 | /// takes and interface 206 | static ubyte[] consume(scope Input input, return ubyte[] buf) @safe 207 | { 208 | 209 | return buf[0 .. input.read(buf)]; 210 | } 211 | 212 | ubyte[4] ping = ['p', 'i', 'n', 'g']; 213 | 214 | File("UT_1.txt", mode!"w").write(ping[]); 215 | scope (exit) 216 | remove("UT_1.txt"); 217 | 218 | auto file = ioObject(File("UT_1.txt")); 219 | ubyte[4] buf; 220 | assert(consume(file, buf[]) == ping[]); 221 | 222 | auto server = UDP.server("localhost", 1234); 223 | UDP.client("localhost", 1234).write(ping[]); 224 | /// can be used as scope class 225 | scope socket = new IOObject!UDP(server.move); 226 | buf[] = 0; 227 | assert(consume(socket, buf[]) == ping[]); 228 | } 229 | -------------------------------------------------------------------------------- /travis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ueo pipefail 4 | 5 | if ! { ifconfig | grep -qF ::1; }; then 6 | export SKIP_IPv6_LOOPBACK_TESTS= 7 | fi 8 | 9 | : ${CONFIG:=library} # env CONFIG=dip1000 ./travis.sh 10 | 11 | case "${BUILD_TOOL}" in 12 | meson) 13 | pip3 install --user --upgrade "pip < 21.0" 14 | pip install --user --upgrade meson ninja 15 | meson builddir -Drun_test=true 16 | ninja -C builddir test 17 | ;; 18 | dub) 19 | dub test -c ${CONFIG} 20 | ;; 21 | *) 22 | echo 'Unknown build tool named: '"${BUILD_TOOL}" 23 | exit 1 24 | ;; 25 | esac 26 | 27 | if "${COVERAGE}"; then 28 | dub test -b unittest-cov -c ${CONFIG} 29 | fi 30 | --------------------------------------------------------------------------------