├── .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 |
2 |
3 | ## [](https://travis-ci.org/MartinNowak/io) [](https://ci.appveyor.com/project/MartinNowak/io) [](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 |
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 |
--------------------------------------------------------------------------------